@livestore/common 0.3.1-dev.0 → 0.3.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 (79) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/adapter-types.d.ts +5 -0
  3. package/dist/adapter-types.d.ts.map +1 -1
  4. package/dist/adapter-types.js.map +1 -1
  5. package/dist/devtools/devtools-messages-client-session.d.ts +21 -21
  6. package/dist/devtools/devtools-messages-common.d.ts +6 -6
  7. package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
  8. package/dist/devtools/devtools-messages-leader.d.ts +24 -24
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +1 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
  14. package/dist/leader-thread/LeaderSyncProcessor.js +13 -4
  15. package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
  16. package/dist/leader-thread/eventlog.d.ts.map +1 -1
  17. package/dist/leader-thread/eventlog.js +2 -0
  18. package/dist/leader-thread/eventlog.js.map +1 -1
  19. package/dist/leader-thread/materialize-event.d.ts +3 -3
  20. package/dist/leader-thread/materialize-event.d.ts.map +1 -1
  21. package/dist/leader-thread/materialize-event.js +20 -9
  22. package/dist/leader-thread/materialize-event.js.map +1 -1
  23. package/dist/leader-thread/mod.d.ts +1 -0
  24. package/dist/leader-thread/mod.d.ts.map +1 -1
  25. package/dist/leader-thread/mod.js +1 -0
  26. package/dist/leader-thread/mod.js.map +1 -1
  27. package/dist/leader-thread/types.d.ts +1 -0
  28. package/dist/leader-thread/types.d.ts.map +1 -1
  29. package/dist/materializer-helper.d.ts +13 -2
  30. package/dist/materializer-helper.d.ts.map +1 -1
  31. package/dist/materializer-helper.js +25 -11
  32. package/dist/materializer-helper.js.map +1 -1
  33. package/dist/rematerialize-from-eventlog.js +2 -2
  34. package/dist/rematerialize-from-eventlog.js.map +1 -1
  35. package/dist/schema/EventDef.d.ts +6 -1
  36. package/dist/schema/EventDef.d.ts.map +1 -1
  37. package/dist/schema/EventDef.js +3 -0
  38. package/dist/schema/EventDef.js.map +1 -1
  39. package/dist/schema/LiveStoreEvent.d.ts +35 -1
  40. package/dist/schema/LiveStoreEvent.d.ts.map +1 -1
  41. package/dist/schema/LiveStoreEvent.js +22 -4
  42. package/dist/schema/LiveStoreEvent.js.map +1 -1
  43. package/dist/schema/state/sqlite/query-builder/api.d.ts +1 -1
  44. package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -1
  45. package/dist/schema/state/sqlite/query-builder/impl.test.d.ts +57 -62
  46. package/dist/schema/state/sqlite/query-builder/impl.test.d.ts.map +1 -1
  47. package/dist/schema/state/sqlite/system-tables.d.ts +350 -407
  48. package/dist/schema/state/sqlite/system-tables.d.ts.map +1 -1
  49. package/dist/schema/state/sqlite/table-def.d.ts +1 -1
  50. package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
  51. package/dist/sqlite-db-helper.d.ts +7 -0
  52. package/dist/sqlite-db-helper.d.ts.map +1 -0
  53. package/dist/sqlite-db-helper.js +29 -0
  54. package/dist/sqlite-db-helper.js.map +1 -0
  55. package/dist/sync/ClientSessionSyncProcessor.d.ts +6 -1
  56. package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
  57. package/dist/sync/ClientSessionSyncProcessor.js +21 -6
  58. package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
  59. package/dist/version.d.ts +1 -1
  60. package/dist/version.d.ts.map +1 -1
  61. package/dist/version.js +1 -1
  62. package/dist/version.js.map +1 -1
  63. package/package.json +4 -4
  64. package/src/adapter-types.ts +5 -0
  65. package/src/index.ts +1 -0
  66. package/src/leader-thread/LeaderSyncProcessor.ts +17 -2
  67. package/src/leader-thread/eventlog.ts +2 -0
  68. package/src/leader-thread/materialize-event.ts +25 -11
  69. package/src/leader-thread/mod.ts +1 -0
  70. package/src/leader-thread/types.ts +4 -1
  71. package/src/materializer-helper.ts +45 -19
  72. package/src/rematerialize-from-eventlog.ts +2 -2
  73. package/src/schema/EventDef.ts +11 -1
  74. package/src/schema/LiveStoreEvent.ts +29 -4
  75. package/src/schema/state/sqlite/query-builder/api.ts +1 -1
  76. package/src/schema/state/sqlite/table-def.ts +1 -1
  77. package/src/sqlite-db-helper.ts +41 -0
  78. package/src/sync/ClientSessionSyncProcessor.ts +34 -8
  79. package/src/version.ts +1 -1
@@ -1,26 +1,27 @@
1
- import { isNil, isReadonlyArray } from '@livestore/utils'
2
- import { Schema } from '@livestore/utils/effect'
1
+ import { isDevEnv, isNil, isReadonlyArray } from '@livestore/utils'
2
+ import { Hash, Option, Schema } from '@livestore/utils/effect'
3
3
 
4
4
  import type { SqliteDb } from './adapter-types.js'
5
5
  import { SessionIdSymbol } from './adapter-types.js'
6
6
  import type { EventDef, Materializer, MaterializerContextQuery, MaterializerResult } from './schema/EventDef.js'
7
7
  import type * as LiveStoreEvent from './schema/LiveStoreEvent.js'
8
+ import { getEventDef, type LiveStoreSchema } from './schema/schema.js'
8
9
  import type { QueryBuilder } from './schema/state/sqlite/query-builder/api.js'
9
10
  import { isQueryBuilder } from './schema/state/sqlite/query-builder/api.js'
10
11
  import { getResultSchema } from './schema/state/sqlite/query-builder/impl.js'
11
- import { type BindValues } from './sql-queries/sql-queries.js'
12
+ import type { BindValues } from './sql-queries/sql-queries.js'
12
13
  import type { ParamsObject, PreparedBindValues } from './util.js'
13
14
  import { prepareBindValues } from './util.js'
14
15
 
15
- export const getExecArgsFromEvent = ({
16
+ export const getExecStatementsFromMaterializer = ({
16
17
  eventDef,
17
18
  materializer,
18
- db,
19
+ dbState,
19
20
  event,
20
21
  }: {
21
22
  eventDef: EventDef.AnyWithoutFn
22
23
  materializer: Materializer
23
- db: SqliteDb
24
+ dbState: SqliteDb
24
25
  /** Both encoded and decoded events are supported to reduce the number of times we need to decode/encode */
25
26
  event:
26
27
  | {
@@ -53,25 +54,25 @@ export const getExecArgsFromEvent = ({
53
54
  ) => {
54
55
  if (isQueryBuilder(rawQueryOrQueryBuilder)) {
55
56
  const { query, bindValues } = rawQueryOrQueryBuilder.asSql()
56
- const rawResults = db.select(query, prepareBindValues(bindValues, query))
57
+ const rawResults = dbState.select(query, prepareBindValues(bindValues, query))
57
58
  const resultSchema = getResultSchema(rawQueryOrQueryBuilder)
58
59
  return Schema.decodeSync(resultSchema)(rawResults)
59
60
  } else {
60
61
  const { query, bindValues } = rawQueryOrQueryBuilder
61
- return db.select(query, prepareBindValues(bindValues, query))
62
+ return dbState.select(query, prepareBindValues(bindValues, query))
62
63
  }
63
64
  }
64
65
 
65
- const res = materializer(eventArgsDecoded, {
66
- eventDef,
67
- query,
68
- // TODO properly implement this
69
- currentFacts: new Map(),
70
- })
71
-
72
- const statementRes = mapMaterializerResult(res)
66
+ const statementResults = fromMaterializerResult(
67
+ materializer(eventArgsDecoded, {
68
+ eventDef,
69
+ query,
70
+ // TODO properly implement this
71
+ currentFacts: new Map(),
72
+ }),
73
+ )
73
74
 
74
- return statementRes.map((statementRes) => {
75
+ return statementResults.map((statementRes) => {
75
76
  const statementSql = statementRes.sql
76
77
 
77
78
  const bindValues = typeof statementRes === 'string' ? eventArgsEncoded : statementRes.bindValues
@@ -82,7 +83,32 @@ export const getExecArgsFromEvent = ({
82
83
  })
83
84
  }
84
85
 
85
- const mapMaterializerResult = (
86
+ export const makeMaterializerHash =
87
+ ({ schema, dbState }: { schema: LiveStoreSchema; dbState: SqliteDb }) =>
88
+ (event: LiveStoreEvent.AnyEncodedGlobal): Option.Option<number> => {
89
+ if (isDevEnv()) {
90
+ const { eventDef, materializer } = getEventDef(schema, event.name)
91
+ const materializerResults = getExecStatementsFromMaterializer({
92
+ eventDef,
93
+ materializer,
94
+ dbState,
95
+ event: { decoded: undefined, encoded: event },
96
+ })
97
+ return Option.some(Hash.string(JSON.stringify(materializerResults)))
98
+ }
99
+
100
+ return Option.none()
101
+ }
102
+
103
+ export const hashMaterializerResults = (
104
+ materializerResults: ReadonlyArray<{
105
+ statementSql: string
106
+ bindValues: PreparedBindValues
107
+ writeTables: ReadonlySet<string> | undefined
108
+ }>,
109
+ ) => Hash.string(JSON.stringify(materializerResults))
110
+
111
+ const fromMaterializerResult = (
86
112
  materializerResult: MaterializerResult | ReadonlyArray<MaterializerResult>,
87
113
  ): ReadonlyArray<{
88
114
  sql: string
@@ -90,7 +116,7 @@ const mapMaterializerResult = (
90
116
  writeTables: ReadonlySet<string> | undefined
91
117
  }> => {
92
118
  if (isReadonlyArray(materializerResult)) {
93
- return materializerResult.flatMap(mapMaterializerResult)
119
+ return materializerResult.flatMap(fromMaterializerResult)
94
120
  }
95
121
  if (isQueryBuilder(materializerResult)) {
96
122
  const { query, bindValues } = materializerResult.asSql()
@@ -27,13 +27,13 @@ export const rematerializeFromEventlog = ({
27
27
  `SELECT COUNT(*) AS count FROM ${SystemTables.EVENTLOG_META_TABLE}`,
28
28
  )[0]!.count
29
29
 
30
- const hashEvent = memoizeByRef((event: EventDef.AnyWithoutFn) => Schema.hash(event.schema))
30
+ const hashEventDef = memoizeByRef((event: EventDef.AnyWithoutFn) => Schema.hash(event.schema))
31
31
 
32
32
  const processEvent = (row: SystemTables.EventlogMetaRow) =>
33
33
  Effect.gen(function* () {
34
34
  const eventDef = getEventDef(schema, row.name)
35
35
 
36
- if (hashEvent(eventDef.eventDef) !== row.schemaHash) {
36
+ if (hashEventDef(eventDef.eventDef) !== row.schemaHash) {
37
37
  yield* Effect.logWarning(
38
38
  `Schema hash mismatch for event definition ${row.name}. Trying to materialize event anyway.`,
39
39
  )
@@ -27,12 +27,18 @@ export type EventDef<TName extends string, TType, TEncoded = TType, TDerived ext
27
27
  derived: TDerived
28
28
  }
29
29
 
30
- /** Helper function to construct a partial mutation event */
30
+ /** Helper function to construct a partial event */
31
31
  (args: TType): {
32
32
  name: TName
33
33
  args: TType
34
34
  }
35
35
 
36
+ /** Helper function to construct a partial encoded event */
37
+ encoded: (args: TEncoded) => {
38
+ name: TName
39
+ args: TEncoded
40
+ }
41
+
36
42
  readonly Event: {
37
43
  name: TName
38
44
  args: TType
@@ -123,6 +129,10 @@ export const defineEvent = <TName extends string, TType, TEncoded = TType, TDeri
123
129
 
124
130
  Object.defineProperty(makePartialEvent, 'name', { value: name })
125
131
  Object.defineProperty(makePartialEvent, 'schema', { value: schema })
132
+ Object.defineProperty(makePartialEvent, 'encoded', {
133
+ value: (args: TEncoded) => ({ name: name, args }),
134
+ })
135
+
126
136
  Object.defineProperty(makePartialEvent, 'options', {
127
137
  value: {
128
138
  clientOnly: options?.clientOnly ?? false,
@@ -171,12 +171,25 @@ export class EncodedWithMeta extends Schema.Class<EncodedWithMeta>('LiveStoreEve
171
171
  Schema.TaggedStruct('unset', {}),
172
172
  ),
173
173
  syncMetadata: Schema.Option(Schema.JsonValue),
174
+ /** Used to detect if the materializer is side effecting (during dev) */
175
+ materializerHashLeader: Schema.Option(Schema.Number),
176
+ materializerHashSession: Schema.Option(Schema.Number),
174
177
  }).pipe(
175
178
  Schema.mutable,
176
179
  Schema.optional,
177
180
  Schema.withDefaults({
178
- constructor: () => ({ sessionChangeset: { _tag: 'unset' as const }, syncMetadata: Option.none() }),
179
- decoding: () => ({ sessionChangeset: { _tag: 'unset' as const }, syncMetadata: Option.none() }),
181
+ constructor: () => ({
182
+ sessionChangeset: { _tag: 'unset' as const },
183
+ syncMetadata: Option.none(),
184
+ materializerHashLeader: Option.none(),
185
+ materializerHashSession: Option.none(),
186
+ }),
187
+ decoding: () => ({
188
+ sessionChangeset: { _tag: 'unset' as const },
189
+ syncMetadata: Option.none(),
190
+ materializerHashLeader: Option.none(),
191
+ materializerHashSession: Option.none(),
192
+ }),
180
193
  }),
181
194
  ),
182
195
  }) {
@@ -214,12 +227,24 @@ export class EncodedWithMeta extends Schema.Class<EncodedWithMeta>('LiveStoreEve
214
227
  ...EventSequenceNumber.nextPair(parentSeqNum, isClient),
215
228
  })
216
229
 
217
- static fromGlobal = (event: AnyEncodedGlobal, syncMetadata: Option.Option<Schema.JsonValue>) =>
230
+ static fromGlobal = (
231
+ event: AnyEncodedGlobal,
232
+ meta: {
233
+ syncMetadata: Option.Option<Schema.JsonValue>
234
+ materializerHashLeader: Option.Option<number>
235
+ materializerHashSession: Option.Option<number>
236
+ },
237
+ ) =>
218
238
  new EncodedWithMeta({
219
239
  ...event,
220
240
  seqNum: { global: event.seqNum, client: EventSequenceNumber.clientDefault },
221
241
  parentSeqNum: { global: event.parentSeqNum, client: EventSequenceNumber.clientDefault },
222
- meta: { sessionChangeset: { _tag: 'unset' as const }, syncMetadata },
242
+ meta: {
243
+ sessionChangeset: { _tag: 'unset' as const },
244
+ syncMetadata: meta.syncMetadata,
245
+ materializerHashLeader: meta.materializerHashLeader,
246
+ materializerHashSession: meta.materializerHashSession,
247
+ },
223
248
  })
224
249
 
225
250
  toGlobal = (): AnyEncodedGlobal => ({
@@ -117,7 +117,7 @@ export type QueryBuilder<
117
117
  > = {
118
118
  readonly [QueryBuilderTypeId]: QueryBuilderTypeId
119
119
  readonly [QueryBuilderAstSymbol]: QueryBuilderAst
120
- readonly ['ResultType']: TResult
120
+ readonly ResultType: TResult
121
121
  readonly asSql: () => { query: string; bindValues: SqlValue[] }
122
122
  readonly toString: () => string
123
123
  } & Omit<QueryBuilder.ApiFull<TResult, TTableDef, TWithout>, TWithout>
@@ -177,7 +177,7 @@ export namespace FromColumns {
177
177
  export type InsertRowDecoded<TColumns extends SqliteDsl.Columns> = SqliteDsl.FromColumns.InsertRowDecoded<TColumns>
178
178
  }
179
179
 
180
- type SqliteTableDefForInput<
180
+ export type SqliteTableDefForInput<
181
181
  TName extends string,
182
182
  TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefinition<any, any>,
183
183
  > = SqliteDsl.TableDefinition<TName, PrettifyFlat<ToColumns<TColumns>>>
@@ -0,0 +1,41 @@
1
+ import { Schema } from '@livestore/utils/effect'
2
+
3
+ import type { SqliteDb } from './adapter-types.js'
4
+ import { getResultSchema, isQueryBuilder } from './schema/state/sqlite/query-builder/mod.js'
5
+ import type { PreparedBindValues } from './util.js'
6
+
7
+ export const makeExecute = (
8
+ execute: (
9
+ queryStr: string,
10
+ bindValues: PreparedBindValues | undefined,
11
+ options?: { onRowsChanged?: (rowsChanged: number) => void },
12
+ ) => void,
13
+ ): SqliteDb['execute'] => {
14
+ return (...args: any[]) => {
15
+ const [queryStrOrQueryBuilder, bindValuesOrOptions, maybeOptions] = args
16
+
17
+ if (isQueryBuilder(queryStrOrQueryBuilder)) {
18
+ const { query, bindValues } = queryStrOrQueryBuilder.asSql()
19
+ return execute(query, bindValues as unknown as PreparedBindValues, bindValuesOrOptions)
20
+ } else {
21
+ return execute(queryStrOrQueryBuilder, bindValuesOrOptions, maybeOptions)
22
+ }
23
+ }
24
+ }
25
+
26
+ export const makeSelect = <T>(
27
+ select: (queryStr: string, bindValues: PreparedBindValues | undefined) => ReadonlyArray<T>,
28
+ ): SqliteDb['select'] => {
29
+ return (...args: any[]) => {
30
+ const [queryStrOrQueryBuilder, maybeBindValues] = args
31
+
32
+ if (isQueryBuilder(queryStrOrQueryBuilder)) {
33
+ const { query, bindValues } = queryStrOrQueryBuilder.asSql()
34
+ const resultSchema = getResultSchema(queryStrOrQueryBuilder)
35
+ const results = select(query, bindValues as unknown as PreparedBindValues)
36
+ return Schema.decodeSync(resultSchema)(results)
37
+ } else {
38
+ return select(queryStrOrQueryBuilder, maybeBindValues)
39
+ }
40
+ }
41
+ }
@@ -1,6 +1,6 @@
1
1
  /// <reference lib="dom" />
2
2
  import { LS_DEV, shouldNeverHappen, TRACE_VERBOSE } from '@livestore/utils'
3
- import type { Runtime, Scope } from '@livestore/utils/effect'
3
+ import { Option, type Runtime, type Scope } from '@livestore/utils/effect'
4
4
  import { BucketQueue, Effect, FiberHandle, Queue, Schema, Stream, Subscribable } from '@livestore/utils/effect'
5
5
  import * as otel from '@opentelemetry/api'
6
6
 
@@ -38,10 +38,11 @@ export const makeClientSessionSyncProcessor = ({
38
38
  runtime: Runtime.Runtime<Scope.Scope>
39
39
  materializeEvent: (
40
40
  eventDecoded: LiveStoreEvent.PartialAnyDecoded,
41
- options: { otelContext: otel.Context; withChangeset: boolean },
41
+ options: { otelContext: otel.Context; withChangeset: boolean; materializerHashLeader: Option.Option<number> },
42
42
  ) => {
43
43
  writeTables: Set<string>
44
44
  sessionChangeset: { _tag: 'sessionChangeset'; data: Uint8Array; debug: any } | { _tag: 'no-op' } | { _tag: 'unset' }
45
+ materializerHash: Option.Option<number>
45
46
  }
46
47
  rollback: (changeset: Uint8Array) => void
47
48
  refreshTables: (tables: Set<string>) => void
@@ -67,6 +68,7 @@ export const makeClientSessionSyncProcessor = ({
67
68
  }),
68
69
  }
69
70
 
71
+ /** Only used for debugging / observability, it's not relied upon for correctness of the sync processor. */
70
72
  const syncStateUpdateQueue = Queue.unbounded<SyncState.SyncState>().pipe(Effect.runSync)
71
73
  const isClientEvent = (eventEncoded: LiveStoreEvent.EncodedWithMeta) =>
72
74
  getEventDef(schema, eventEncoded.name).eventDef.options.clientOnly
@@ -116,17 +118,28 @@ export const makeClientSessionSyncProcessor = ({
116
118
  syncStateRef.current = mergeResult.newSyncState
117
119
  syncStateUpdateQueue.offer(mergeResult.newSyncState).pipe(Effect.runSync)
118
120
 
121
+ // Materialize events to state
119
122
  const writeTables = new Set<string>()
120
123
  for (const event of mergeResult.newEvents) {
121
124
  // TODO avoid encoding and decoding here again
122
125
  const decodedEventDef = Schema.decodeSync(eventSchema)(event)
123
- const res = materializeEvent(decodedEventDef, { otelContext, withChangeset: true })
124
- for (const table of res.writeTables) {
126
+ const {
127
+ writeTables: newWriteTables,
128
+ sessionChangeset,
129
+ materializerHash,
130
+ } = materializeEvent(decodedEventDef, {
131
+ otelContext,
132
+ withChangeset: true,
133
+ materializerHashLeader: Option.none(),
134
+ })
135
+ for (const table of newWriteTables) {
125
136
  writeTables.add(table)
126
137
  }
127
- event.meta.sessionChangeset = res.sessionChangeset
138
+ event.meta.sessionChangeset = sessionChangeset
139
+ event.meta.materializerHashSession = materializerHash
128
140
  }
129
141
 
142
+ // Trigger push to leader
130
143
  // console.debug('pushToLeader', encodedEventDefs.length, ...encodedEventDefs.map((_) => _.toJSON()))
131
144
  BucketQueue.offerAll(leaderPushQueue, encodedEventDefs).pipe(Effect.runSync)
132
145
 
@@ -242,6 +255,7 @@ export const makeClientSessionSyncProcessor = ({
242
255
  }
243
256
  }
244
257
 
258
+ // Pushing rebased pending events to leader
245
259
  yield* BucketQueue.offerAll(leaderPushQueue, mergeResult.newSyncState.pending)
246
260
  } else {
247
261
  span.addEvent('merge:pull:advance', {
@@ -261,12 +275,21 @@ export const makeClientSessionSyncProcessor = ({
261
275
  for (const event of mergeResult.newEvents) {
262
276
  // TODO apply changeset if available (will require tracking of write tables as well)
263
277
  const decodedEventDef = Schema.decodeSync(eventSchema)(event)
264
- const res = materializeEvent(decodedEventDef, { otelContext, withChangeset: true })
265
- for (const table of res.writeTables) {
278
+ const {
279
+ writeTables: newWriteTables,
280
+ sessionChangeset,
281
+ materializerHash,
282
+ } = materializeEvent(decodedEventDef, {
283
+ otelContext,
284
+ withChangeset: true,
285
+ materializerHashLeader: event.meta.materializerHashLeader,
286
+ })
287
+ for (const table of newWriteTables) {
266
288
  writeTables.add(table)
267
289
  }
268
290
 
269
- event.meta.sessionChangeset = res.sessionChangeset
291
+ event.meta.sessionChangeset = sessionChangeset
292
+ event.meta.materializerHashSession = materializerHash
270
293
  }
271
294
 
272
295
  refreshTables(writeTables)
@@ -321,6 +344,9 @@ export interface ClientSessionSyncProcessor {
321
344
  writeTables: Set<string>
322
345
  }
323
346
  boot: Effect.Effect<void, UnexpectedError, Scope.Scope>
347
+ /**
348
+ * Only used for debugging / observability.
349
+ */
324
350
  syncState: Subscribable.Subscribable<SyncState.SyncState>
325
351
  debug: {
326
352
  print: () => void
package/src/version.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // import packageJson from '../package.json' with { type: 'json' }
3
3
  // export const liveStoreVersion = packageJson.version
4
4
 
5
- export const liveStoreVersion = '0.3.1-dev.0' as const
5
+ export const liveStoreVersion = '0.3.1' as const
6
6
 
7
7
  /**
8
8
  * This version number is incremented whenever the internal storage format changes in a breaking way.