@livestore/livestore 0.3.2-dev.1 → 0.3.2-dev.11

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 (59) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/live-queries/base-class.d.ts +5 -0
  3. package/dist/live-queries/base-class.d.ts.map +1 -1
  4. package/dist/live-queries/base-class.js +1 -1
  5. package/dist/live-queries/base-class.js.map +1 -1
  6. package/dist/live-queries/computed.d.ts.map +1 -1
  7. package/dist/live-queries/computed.js +7 -0
  8. package/dist/live-queries/computed.js.map +1 -1
  9. package/dist/live-queries/db-query.d.ts.map +1 -1
  10. package/dist/live-queries/db-query.js +12 -2
  11. package/dist/live-queries/db-query.js.map +1 -1
  12. package/dist/live-queries/db-query.test.js +8 -10
  13. package/dist/live-queries/db-query.test.js.map +1 -1
  14. package/dist/live-queries/signal.d.ts.map +1 -1
  15. package/dist/live-queries/signal.js +7 -0
  16. package/dist/live-queries/signal.js.map +1 -1
  17. package/dist/mod.d.ts +2 -1
  18. package/dist/mod.d.ts.map +1 -1
  19. package/dist/mod.js +1 -0
  20. package/dist/mod.js.map +1 -1
  21. package/dist/reactive.d.ts.map +1 -1
  22. package/dist/reactive.js +2 -0
  23. package/dist/reactive.js.map +1 -1
  24. package/dist/store/create-store.d.ts +5 -7
  25. package/dist/store/create-store.d.ts.map +1 -1
  26. package/dist/store/create-store.js +4 -4
  27. package/dist/store/create-store.js.map +1 -1
  28. package/dist/store/store-shutdown.test.d.ts +2 -0
  29. package/dist/store/store-shutdown.test.d.ts.map +1 -0
  30. package/dist/store/store-shutdown.test.js +103 -0
  31. package/dist/store/store-shutdown.test.js.map +1 -0
  32. package/dist/store/store-types.d.ts +4 -4
  33. package/dist/store/store-types.d.ts.map +1 -1
  34. package/dist/store/store-types.js.map +1 -1
  35. package/dist/store/store.d.ts +11 -2
  36. package/dist/store/store.d.ts.map +1 -1
  37. package/dist/store/store.js +76 -43
  38. package/dist/store/store.js.map +1 -1
  39. package/dist/utils/dev.d.ts.map +1 -1
  40. package/dist/utils/dev.js +14 -8
  41. package/dist/utils/dev.js.map +1 -1
  42. package/dist/utils/tests/fixture.d.ts +33 -2
  43. package/dist/utils/tests/fixture.d.ts.map +1 -1
  44. package/dist/utils/tests/fixture.js +16 -3
  45. package/dist/utils/tests/fixture.js.map +1 -1
  46. package/package.json +12 -38
  47. package/src/live-queries/__snapshots__/db-query.test.ts.snap +14 -14
  48. package/src/live-queries/base-class.ts +5 -1
  49. package/src/live-queries/computed.ts +7 -0
  50. package/src/live-queries/db-query.test.ts +8 -10
  51. package/src/live-queries/db-query.ts +12 -3
  52. package/src/live-queries/signal.ts +7 -0
  53. package/src/mod.ts +2 -1
  54. package/src/reactive.ts +2 -0
  55. package/src/store/create-store.ts +18 -16
  56. package/src/store/store-types.ts +9 -5
  57. package/src/store/store.ts +67 -8
  58. package/src/utils/dev.ts +14 -7
  59. package/src/utils/tests/fixture.ts +18 -3
@@ -22,7 +22,18 @@ import type { LiveStoreSchema } from '@livestore/common/schema'
22
22
  import { getEventDef, LiveStoreEvent, SystemTables } from '@livestore/common/schema'
23
23
  import { assertNever, isDevEnv, notYetImplemented } from '@livestore/utils'
24
24
  import type { Scope } from '@livestore/utils/effect'
25
- import { Cause, Effect, Fiber, Inspectable, Option, OtelTracer, Runtime, Schema, Stream } from '@livestore/utils/effect'
25
+ import {
26
+ Cause,
27
+ Effect,
28
+ Exit,
29
+ Fiber,
30
+ Inspectable,
31
+ Option,
32
+ OtelTracer,
33
+ Runtime,
34
+ Schema,
35
+ Stream,
36
+ } from '@livestore/utils/effect'
26
37
  import { nanoid } from '@livestore/utils/nanoid'
27
38
  import * as otel from '@opentelemetry/api'
28
39
 
@@ -54,7 +65,7 @@ if (isDevEnv()) {
54
65
  exposeDebugUtils()
55
66
  }
56
67
 
57
- export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext = {}> extends Inspectable.Class {
68
+ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}> extends Inspectable.Class {
58
69
  readonly storeId: string
59
70
  reactivityGraph: ReactivityGraph
60
71
  sqliteDbWrapper: SqliteDbWrapper
@@ -68,6 +79,9 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
68
79
  */
69
80
  tableRefs: { [key: string]: Ref<null, ReactivityGraphContext, RefreshReason> }
70
81
 
82
+ /** Tracks whether the store has been shut down */
83
+ private isShutdown = false
84
+
71
85
  private effectContext: {
72
86
  runtime: Runtime.Runtime<Scope.Scope>
73
87
  lifetimeScope: Scope.Scope
@@ -282,6 +296,15 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
282
296
  return this.clientSession.clientId
283
297
  }
284
298
 
299
+ private checkShutdown = (operation: string): void => {
300
+ if (this.isShutdown) {
301
+ throw new UnexpectedError({
302
+ cause: `Store has been shut down (while performing "${operation}").`,
303
+ note: `You cannot perform this operation after the store has been shut down.`,
304
+ })
305
+ }
306
+ }
307
+
285
308
  /**
286
309
  * Subscribe to the results of a query
287
310
  * Returns a function to cancel the subscription.
@@ -309,8 +332,10 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
309
332
  /** If provided, the stack info will be added to the `activeSubscriptions` set of the query */
310
333
  stackInfo?: StackInfo
311
334
  },
312
- ): Unsubscribe =>
313
- this.otel.tracer.startActiveSpan(
335
+ ): Unsubscribe => {
336
+ this.checkShutdown('subscribe')
337
+
338
+ return this.otel.tracer.startActiveSpan(
314
339
  `LiveStore.subscribe`,
315
340
  { attributes: { label: options?.label, queryLabel: isQueryBuilder(query) ? query.toString() : query.label } },
316
341
  options?.otelContext ?? this.otel.queriesSpanContext,
@@ -369,6 +394,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
369
394
  return unsubscribe
370
395
  },
371
396
  )
397
+ }
372
398
 
373
399
  subscribeStream = <TResult>(
374
400
  query$: LiveQueryDef<TResult>,
@@ -417,6 +443,8 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
417
443
  | { query: string; bindValues: Bindable; schema?: Schema.Schema<TResult> },
418
444
  options?: { otelContext?: otel.Context; debugRefreshReason?: RefreshReason },
419
445
  ): TResult => {
446
+ this.checkShutdown('query')
447
+
420
448
  if (typeof query === 'object' && 'query' in query && 'bindValues' in query) {
421
449
  const res = this.sqliteDbWrapper.cachedSelect(query.query, prepareBindValues(query.bindValues, query.query), {
422
450
  otelContext: options?.otelContext,
@@ -438,6 +466,12 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
438
466
 
439
467
  const sqlRes = query.asSql()
440
468
  const schema = getResultSchema(query)
469
+
470
+ // Replace SessionIdSymbol in bind values before executing the query
471
+ if (sqlRes.bindValues) {
472
+ replaceSessionIdSymbol(sqlRes.bindValues, this.clientSession.sessionId)
473
+ }
474
+
441
475
  const rawRes = this.sqliteDbWrapper.cachedSelect(sqlRes.query, sqlRes.bindValues as any as PreparedBindValues, {
442
476
  otelContext: options?.otelContext,
443
477
  queriedTables: new Set([query[QueryBuilderAstSymbol].tableDef.sqliteDef.name]),
@@ -473,6 +507,8 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
473
507
  * ```
474
508
  */
475
509
  setSignal = <T>(signalDef: SignalDef<T>, value: T | ((prev: T) => T)): void => {
510
+ this.checkShutdown('setSignal')
511
+
476
512
  const signalRef = signalDef.make(this.reactivityGraph.context!)
477
513
  const newValue: T = typeof value === 'function' ? (value as any)(signalRef.value.get()) : value
478
514
  signalRef.value.set(newValue)
@@ -557,6 +593,8 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
557
593
  ) => void,
558
594
  ): void
559
595
  } = (firstEventOrTxnFnOrOptions: any, ...restEvents: any[]) => {
596
+ this.checkShutdown('commit')
597
+
560
598
  const { events, options } = this.getCommitArgs(firstEventOrTxnFnOrOptions, restEvents)
561
599
 
562
600
  for (const event of events) {
@@ -660,10 +698,14 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
660
698
  * ```
661
699
  */
662
700
  events = (_options?: StoreEventsOptions<TSchema>): AsyncIterable<LiveStoreEvent.ForSchema<TSchema>> => {
701
+ this.checkShutdown('events')
702
+
663
703
  return notYetImplemented(`store.events() is not yet implemented but planned soon`)
664
704
  }
665
705
 
666
706
  eventsStream = (_options?: StoreEventsOptions<TSchema>): Stream.Stream<LiveStoreEvent.ForSchema<TSchema>> => {
707
+ this.checkShutdown('eventsStream')
708
+
667
709
  return notYetImplemented(`store.eventsStream() is not yet implemented but planned soon`)
668
710
  }
669
711
 
@@ -672,6 +714,8 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
672
714
  * We might need a better solution for this. Let's see.
673
715
  */
674
716
  manualRefresh = (options?: { label?: string }) => {
717
+ this.checkShutdown('manualRefresh')
718
+
675
719
  const { label } = options ?? {}
676
720
  this.otel.tracer.startActiveSpan(
677
721
  'LiveStore:manualRefresh',
@@ -690,10 +734,25 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
690
734
  *
691
735
  * This is called automatically when the store was created using the React or Effect API.
692
736
  */
693
- shutdown = async (cause?: Cause.Cause<UnexpectedError>) => {
694
- await this.clientSession
695
- .shutdown(cause ?? Cause.fail(IntentionalShutdownCause.make({ reason: 'manual' })))
696
- .pipe(this.runEffectFork, Fiber.join, Effect.runPromise)
737
+ shutdownPromise = async (cause?: UnexpectedError) => {
738
+ this.checkShutdown('shutdownPromise')
739
+
740
+ this.isShutdown = true
741
+ await this.shutdown(cause ? Cause.fail(cause) : undefined).pipe(this.runEffectFork, Fiber.join, Effect.runPromise)
742
+ }
743
+
744
+ /**
745
+ * Shuts down the store and closes the client session.
746
+ *
747
+ * This is called automatically when the store was created using the React or Effect API.
748
+ */
749
+ shutdown = (cause?: Cause.Cause<UnexpectedError>): Effect.Effect<void> => {
750
+ this.checkShutdown('shutdown')
751
+
752
+ this.isShutdown = true
753
+ return this.clientSession.shutdown(
754
+ cause ? Exit.failCause(cause) : Exit.succeed(IntentionalShutdownCause.make({ reason: 'manual' })),
755
+ )
697
756
  }
698
757
 
699
758
  /**
package/src/utils/dev.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { isDevEnv } from '@livestore/utils'
1
+ import type { SqliteDb } from '@livestore/common'
2
+ import { prettyBytes } from '@livestore/utils'
2
3
  import { Effect } from '@livestore/utils/effect'
3
4
 
4
5
  export const downloadBlob = (
@@ -26,11 +27,17 @@ export const downloadURL = (data: string, fileName: string) => {
26
27
  }
27
28
 
28
29
  export const exposeDebugUtils = () => {
29
- if (isDevEnv()) {
30
- globalThis.__debugLiveStoreUtils = {
31
- downloadBlob,
32
- runSync: (effect: Effect.Effect<any, any, never>) => Effect.runSync(effect),
33
- runFork: (effect: Effect.Effect<any, any, never>) => Effect.runFork(effect),
34
- }
30
+ globalThis.__debugLiveStoreUtils = {
31
+ downloadBlob,
32
+ runSync: (effect: Effect.Effect<any, any, never>) => Effect.runSync(effect),
33
+ runFork: (effect: Effect.Effect<any, any, never>) => Effect.runFork(effect),
34
+ dumpDb: (db: SqliteDb) => {
35
+ const tables = db.select<{ name: string }>(`SELECT name FROM sqlite_master WHERE type='table'`)
36
+ for (const table of tables) {
37
+ const rows = db.select<any>(`SELECT * FROM ${table.name}`)
38
+ console.log(`Table: ${table.name} (${prettyBytes(table.name.length)}, ${rows.length} rows)`)
39
+ console.table(rows)
40
+ }
41
+ },
35
42
  }
36
43
  }
@@ -1,6 +1,6 @@
1
1
  import { makeInMemoryAdapter } from '@livestore/adapter-web'
2
2
  import { provideOtel } from '@livestore/common'
3
- import { createStore, makeSchema, State } from '@livestore/livestore'
3
+ import { createStore, Events, makeSchema, State } from '@livestore/livestore'
4
4
  import { Effect, Schema } from '@livestore/utils/effect'
5
5
  import type * as otel from '@opentelemetry/api'
6
6
 
@@ -37,8 +37,23 @@ export const app = State.SQLite.clientDocument({
37
37
 
38
38
  export const tables = { todos, app }
39
39
 
40
- export const state = State.SQLite.makeState({ tables, materializers: {} })
41
- export const schema = makeSchema({ state, events: {} })
40
+ export const events = {
41
+ todoCreated: Events.synced({
42
+ name: 'todo.created',
43
+ schema: Schema.Struct({
44
+ id: Schema.String,
45
+ text: Schema.String,
46
+ completed: Schema.Boolean,
47
+ }),
48
+ }),
49
+ }
50
+
51
+ const materializers = State.SQLite.materializers(events, {
52
+ 'todo.created': ({ id, text, completed }) => tables.todos.insert({ id, text, completed }),
53
+ })
54
+
55
+ export const state = State.SQLite.makeState({ tables, materializers })
56
+ export const schema = makeSchema({ state, events })
42
57
 
43
58
  export const makeTodoMvc = ({
44
59
  otelTracer,