@livestore/common 0.3.0-dev.5 → 0.3.0-dev.6

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 (50) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/derived-mutations.d.ts +4 -4
  3. package/dist/derived-mutations.d.ts.map +1 -1
  4. package/dist/derived-mutations.test.js.map +1 -1
  5. package/dist/devtools/devtools-messages.d.ts +39 -39
  6. package/dist/leader-thread/apply-mutation.d.ts +5 -2
  7. package/dist/leader-thread/apply-mutation.d.ts.map +1 -1
  8. package/dist/leader-thread/apply-mutation.js +37 -25
  9. package/dist/leader-thread/apply-mutation.js.map +1 -1
  10. package/dist/leader-thread/leader-sync-processor.js +10 -5
  11. package/dist/leader-thread/leader-sync-processor.js.map +1 -1
  12. package/dist/leader-thread/recreate-db.d.ts.map +1 -1
  13. package/dist/leader-thread/recreate-db.js +9 -3
  14. package/dist/leader-thread/recreate-db.js.map +1 -1
  15. package/dist/mutation.d.ts +9 -2
  16. package/dist/mutation.d.ts.map +1 -1
  17. package/dist/mutation.js +5 -5
  18. package/dist/mutation.js.map +1 -1
  19. package/dist/query-builder/impl.d.ts +1 -1
  20. package/dist/rehydrate-from-mutationlog.d.ts +2 -2
  21. package/dist/rehydrate-from-mutationlog.d.ts.map +1 -1
  22. package/dist/rehydrate-from-mutationlog.js +13 -19
  23. package/dist/rehydrate-from-mutationlog.js.map +1 -1
  24. package/dist/schema/MutationEvent.d.ts +6 -6
  25. package/dist/schema/MutationEvent.d.ts.map +1 -1
  26. package/dist/schema/MutationEvent.js.map +1 -1
  27. package/dist/schema/system-tables.d.ts +11 -11
  28. package/dist/schema/system-tables.d.ts.map +1 -1
  29. package/dist/schema/system-tables.js +6 -5
  30. package/dist/schema/system-tables.js.map +1 -1
  31. package/dist/sync/client-session-sync-processor.d.ts +2 -2
  32. package/dist/sync/client-session-sync-processor.d.ts.map +1 -1
  33. package/dist/sync/next/rebase-events.d.ts +2 -2
  34. package/dist/sync/next/rebase-events.d.ts.map +1 -1
  35. package/dist/sync/next/test/mutation-fixtures.d.ts +7 -7
  36. package/dist/version.d.ts +1 -1
  37. package/dist/version.js +1 -1
  38. package/package.json +6 -5
  39. package/src/derived-mutations.test.ts +1 -1
  40. package/src/derived-mutations.ts +9 -5
  41. package/src/leader-thread/apply-mutation.ts +48 -30
  42. package/src/leader-thread/leader-sync-processor.ts +12 -5
  43. package/src/leader-thread/recreate-db.ts +9 -5
  44. package/src/mutation.ts +17 -7
  45. package/src/rehydrate-from-mutationlog.ts +15 -23
  46. package/src/schema/MutationEvent.ts +4 -3
  47. package/src/schema/system-tables.ts +6 -5
  48. package/src/sync/client-session-sync-processor.ts +2 -2
  49. package/src/sync/next/rebase-events.ts +2 -2
  50. package/src/version.ts +1 -1
@@ -10,18 +10,18 @@ export declare const facts: {
10
10
  };
11
11
  export declare const mutations: {
12
12
  createTodo: MutationDef<"createTodo", {
13
- readonly id: string;
14
13
  readonly text: string;
15
- }, {
16
14
  readonly id: string;
15
+ }, {
17
16
  readonly text: string;
17
+ readonly id: string;
18
18
  }>;
19
19
  upsertTodo: MutationDef<"upsertTodo", {
20
20
  readonly id: string;
21
21
  readonly text?: string | undefined;
22
22
  }, {
23
- readonly id: string;
24
23
  readonly text?: string | undefined;
24
+ readonly id: string;
25
25
  }>;
26
26
  completeTodo: MutationDef<"completeTodo", {
27
27
  readonly id: string;
@@ -51,18 +51,18 @@ export declare const mutations: {
51
51
  readonly readonly: boolean;
52
52
  }>;
53
53
  setTextTodo: MutationDef<"setTextTodo", {
54
- readonly id: string;
55
54
  readonly text: string;
56
- }, {
57
55
  readonly id: string;
56
+ }, {
58
57
  readonly text: string;
58
+ readonly id: string;
59
59
  }>;
60
60
  setInputValue: MutationDef<"setInputValue", {
61
- readonly id: string;
62
61
  readonly text: string;
63
- }, {
64
62
  readonly id: string;
63
+ }, {
65
64
  readonly text: string;
65
+ readonly id: string;
66
66
  }>;
67
67
  };
68
68
  export type PartialEvent = {
package/dist/version.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const liveStoreVersion: "0.3.0-dev.4";
1
+ export declare const liveStoreVersion: "0.3.0-dev.6";
2
2
  /**
3
3
  * This version number is incremented whenever the internal storage format changes in a breaking way.
4
4
  * Whenever this version changes, LiveStore will start with fresh database files. Old database files are not deleted.
package/dist/version.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // TODO bring back when Expo and Playwright supports `with` imports
2
2
  // import packageJson from '../package.json' with { type: 'json' }
3
3
  // export const liveStoreVersion = packageJson.version
4
- export const liveStoreVersion = '0.3.0-dev.4';
4
+ export const liveStoreVersion = '0.3.0-dev.6';
5
5
  /**
6
6
  * This version number is incremented whenever the internal storage format changes in a breaking way.
7
7
  * Whenever this version changes, LiveStore will start with fresh database files. Old database files are not deleted.
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@livestore/common",
3
- "version": "0.3.0-dev.5",
3
+ "version": "0.3.0-dev.6",
4
4
  "type": "module",
5
+ "sideEffects": false,
5
6
  "exports": {
6
7
  ".": {
7
8
  "types": "./dist/index.d.ts",
@@ -49,12 +50,12 @@
49
50
  }
50
51
  },
51
52
  "dependencies": {
52
- "@opentelemetry/api": "^1.9.0",
53
+ "@opentelemetry/api": "1.9.0",
53
54
  "graphology": "0.26.0-alpha1",
54
55
  "graphology-dag": "0.4.1",
55
- "graphology-types": "0.24.7",
56
- "@livestore/utils": "0.3.0-dev.5",
57
- "@livestore/db-schema": "0.3.0-dev.5"
56
+ "graphology-types": "0.24.8",
57
+ "@livestore/db-schema": "0.3.0-dev.6",
58
+ "@livestore/utils": "0.3.0-dev.6"
58
59
  },
59
60
  "devDependencies": {
60
61
  "vitest": "^2.1.4"
@@ -94,7 +94,7 @@ describe('derived mutations', () => {
94
94
  })
95
95
  })
96
96
 
97
- const patchId = (muationEvent: MutationEvent.PartialAny) => {
97
+ const patchId = (muationEvent: MutationEvent.PartialAnyDecoded) => {
98
98
  // TODO use new id paradigm
99
99
  const id = `00000000-0000-0000-0000-000000000000`
100
100
  return { ...muationEvent, id }
@@ -136,8 +136,10 @@ export namespace DerivedMutationHelperFns {
136
136
  > = SqliteDsl.AnyIfConstained<
137
137
  TColumns,
138
138
  UseShortcut<TOptions> extends true
139
- ? (values?: GetValForKey<SqliteDsl.FromColumns.InsertRowDecoded<TColumns>, 'value'>) => MutationEvent.PartialAny
140
- : (values: SqliteDsl.FromColumns.InsertRowDecoded<TColumns>) => MutationEvent.PartialAny
139
+ ? (
140
+ values?: GetValForKey<SqliteDsl.FromColumns.InsertRowDecoded<TColumns>, 'value'>,
141
+ ) => MutationEvent.PartialAnyDecoded
142
+ : (values: SqliteDsl.FromColumns.InsertRowDecoded<TColumns>) => MutationEvent.PartialAnyDecoded
141
143
  >
142
144
 
143
145
  export type UpdateMutationFn<
@@ -146,17 +148,19 @@ export namespace DerivedMutationHelperFns {
146
148
  > = SqliteDsl.AnyIfConstained<
147
149
  TColumns,
148
150
  UseShortcut<TOptions> extends true
149
- ? (values: Partial<GetValForKey<SqliteDsl.FromColumns.RowDecoded<TColumns>, 'value'>>) => MutationEvent.PartialAny
151
+ ? (
152
+ values: Partial<GetValForKey<SqliteDsl.FromColumns.RowDecoded<TColumns>, 'value'>>,
153
+ ) => MutationEvent.PartialAnyDecoded
150
154
  : (args: {
151
155
  where: Partial<SqliteDsl.FromColumns.RowDecoded<TColumns>>
152
156
  values: Partial<SqliteDsl.FromColumns.RowDecoded<TColumns>>
153
- }) => MutationEvent.PartialAny
157
+ }) => MutationEvent.PartialAnyDecoded
154
158
  >
155
159
 
156
160
  export type DeleteMutationFn<
157
161
  TColumns extends SqliteDsl.ConstraintColumns,
158
162
  _TOptions extends DbSchema.TableOptions,
159
- > = (args: { where: Partial<SqliteDsl.FromColumns.RowDecoded<TColumns>> }) => MutationEvent.PartialAny
163
+ > = (args: { where: Partial<SqliteDsl.FromColumns.RowDecoded<TColumns>> }) => MutationEvent.PartialAnyDecoded
160
164
 
161
165
  type UseShortcut<TOptions extends DbSchema.TableOptions> = TOptions['isSingleColumn'] extends true
162
166
  ? TOptions['isSingleton'] extends true
@@ -3,20 +3,25 @@ import type { Scope } from '@livestore/utils/effect'
3
3
  import { Effect, Option, Schema } from '@livestore/utils/effect'
4
4
 
5
5
  import type { SqliteError, SynchronousDatabase, UnexpectedError } from '../index.js'
6
+ import { getExecArgsFromMutation } from '../mutation.js'
6
7
  import {
7
- getExecArgsFromMutation,
8
+ type LiveStoreSchema,
8
9
  MUTATION_LOG_META_TABLE,
10
+ type MutationEvent,
9
11
  mutationLogMetaTable,
10
12
  SESSION_CHANGESET_META_TABLE,
11
13
  sessionChangesetMetaTable,
12
- } from '../index.js'
13
- import type { LiveStoreSchema, MutationEvent } from '../schema/mod.js'
14
+ } from '../schema/mod.js'
14
15
  import { insertRow } from '../sql-queries/index.js'
15
16
  import { execSql, execSqlPrepared } from './connection.js'
16
17
  import { LeaderThreadCtx } from './types.js'
17
18
 
18
19
  export type ApplyMutation = (
19
20
  mutationEventEncoded: MutationEvent.AnyEncoded,
21
+ options?: {
22
+ /** Needed for rehydrateFromMutationLog */
23
+ skipMutationLog?: boolean
24
+ },
20
25
  ) => Effect.Effect<void, SqliteError | UnexpectedError>
21
26
 
22
27
  export const makeApplyMutation: Effect.Effect<ApplyMutation, never, Scope.Scope | LeaderThreadCtx> = Effect.gen(
@@ -31,15 +36,27 @@ export const makeApplyMutation: Effect.Effect<ApplyMutation, never, Scope.Scope
31
36
  [...leaderThreadCtx.schema.mutations.entries()].map(([k, v]) => [k, Schema.hash(v.schema)] as const),
32
37
  )
33
38
 
34
- return (mutationEventEncoded) =>
39
+ return (mutationEventEncoded, options) =>
35
40
  Effect.gen(function* () {
36
- const { mutationEventSchema, schema, db, dbLog } = leaderThreadCtx
37
- const mutationEventDecoded = Schema.decodeUnknownSync(mutationEventSchema)(mutationEventEncoded)
41
+ const { schema, db, dbLog } = leaderThreadCtx
42
+ const skipMutationLog = options?.skipMutationLog ?? false
38
43
 
39
- const mutationName = mutationEventDecoded.mutation
44
+ const mutationName = mutationEventEncoded.mutation
40
45
  const mutationDef = schema.mutations.get(mutationName) ?? shouldNeverHappen(`Unknown mutation: ${mutationName}`)
41
46
 
42
- const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
47
+ const execArgsArr = getExecArgsFromMutation({
48
+ mutationDef,
49
+ mutationEvent: { decoded: undefined, encoded: mutationEventEncoded },
50
+ })
51
+
52
+ // NOTE we might want to bring this back if we want to debug no-op mutations
53
+ // const makeExecuteOptions = (statementSql: string, bindValues: any) => ({
54
+ // onRowsChanged: (rowsChanged: number) => {
55
+ // if (rowsChanged === 0) {
56
+ // console.warn(`Mutation "${mutationDef.name}" did not affect any rows:`, statementSql, bindValues)
57
+ // }
58
+ // },
59
+ // })
43
60
 
44
61
  // console.group('[@livestore/common:leader-thread:applyMutation]', { mutationName })
45
62
 
@@ -53,30 +70,28 @@ export const makeApplyMutation: Effect.Effect<ApplyMutation, never, Scope.Scope
53
70
 
54
71
  const changeset = session.changeset()
55
72
  session.finish()
56
- // NOTE for no-op mutations (e.g. if the state didn't change) the changeset will be empty
57
- // TODO possibly write a null value instead of omitting the row
58
- if (changeset !== undefined && changeset.length > 0) {
59
- // TODO use prepared statements
60
- yield* execSql(
61
- db,
62
- ...insertRow({
63
- tableName: SESSION_CHANGESET_META_TABLE,
64
- columns: sessionChangesetMetaTable.sqliteDef.columns,
65
- values: {
66
- idGlobal: mutationEventEncoded.id.global,
67
- idLocal: mutationEventEncoded.id.local,
68
- changeset,
69
- debug: execArgsArr,
70
- },
71
- }),
72
- )
73
- }
73
+
74
+ // TODO use prepared statements
75
+ yield* execSql(
76
+ db,
77
+ ...insertRow({
78
+ tableName: SESSION_CHANGESET_META_TABLE,
79
+ columns: sessionChangesetMetaTable.sqliteDef.columns,
80
+ values: {
81
+ idGlobal: mutationEventEncoded.id.global,
82
+ idLocal: mutationEventEncoded.id.local,
83
+ // NOTE the changeset will be empty (i.e. null) for no-op mutations
84
+ changeset: changeset ?? null,
85
+ debug: execArgsArr,
86
+ },
87
+ }),
88
+ )
74
89
 
75
90
  // console.groupEnd()
76
91
 
77
92
  // write to mutation_log
78
- const excludeFromMutationLog = shouldExcludeMutationFromLog(mutationName, mutationEventDecoded)
79
- if (excludeFromMutationLog === false) {
93
+ const excludeFromMutationLog = shouldExcludeMutationFromLog(mutationName, mutationEventEncoded)
94
+ if (skipMutationLog === false && excludeFromMutationLog === false) {
80
95
  yield* insertIntoMutationLog(mutationEventEncoded, dbLog, mutationDefSchemaHashMap)
81
96
  } else {
82
97
  // console.debug('[@livestore/common:leader-thread] skipping mutation log write', mutation, statementSql, bindValues)
@@ -132,11 +147,14 @@ const makeShouldExcludeMutationFromLog = memoizeByRef((schema: LiveStoreSchema)
132
147
  ? (migrationOptions.excludeMutations ?? new Set(['livestore.RawSql']))
133
148
  : new Set(['livestore.RawSql'])
134
149
 
135
- return (mutationName: string, mutationEventDecoded: MutationEvent.AnyDecoded): boolean => {
150
+ return (mutationName: string, mutationEventEncoded: MutationEvent.AnyEncoded): boolean => {
136
151
  if (mutationLogExclude.has(mutationName)) return true
137
152
 
138
153
  const mutationDef = schema.mutations.get(mutationName) ?? shouldNeverHappen(`Unknown mutation: ${mutationName}`)
139
- const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
154
+ const execArgsArr = getExecArgsFromMutation({
155
+ mutationDef,
156
+ mutationEvent: { decoded: undefined, encoded: mutationEventEncoded },
157
+ })
140
158
 
141
159
  return execArgsArr.some((_) => _.statementSql.includes('__livestore'))
142
160
  }
@@ -509,9 +509,9 @@ const backgroundBackendPulling = ({
509
509
  })
510
510
  }
511
511
 
512
- const fiber = yield* applyMutationItemsRef.current!({
513
- batchItems: updateResult.newEvents,
514
- }).pipe(Effect.fork)
512
+ trimChangesetRows(db, newBackendHead)
513
+
514
+ const fiber = yield* applyMutationItemsRef.current!({ batchItems: updateResult.newEvents }).pipe(Effect.fork)
515
515
 
516
516
  yield* Ref.set(stateRef, {
517
517
  _tag: 'applying-syncstate-advance',
@@ -519,7 +519,6 @@ const backgroundBackendPulling = ({
519
519
  syncState: updateResult.newSyncState,
520
520
  fiber,
521
521
  })
522
- // console.log('setRef:applying-syncstate-advance after backgroundBackendPulling', -1)
523
522
  })
524
523
 
525
524
  yield* syncBackend.pull(cursorInfo).pipe(
@@ -574,7 +573,9 @@ const rollback = ({
574
573
  // Apply changesets in reverse order
575
574
  for (let i = rollbackEvents.length - 1; i >= 0; i--) {
576
575
  const { changeset } = rollbackEvents[i]!
577
- db.makeChangeset(changeset).invert().apply()
576
+ if (changeset !== null) {
577
+ db.makeChangeset(changeset).invert().apply()
578
+ }
578
579
  }
579
580
 
580
581
  // Delete the changeset rows
@@ -668,3 +669,9 @@ const backgroundBackendPushing = ({
668
669
  }
669
670
  }
670
671
  }).pipe(Effect.interruptible, Effect.withSpan('@livestore/common:leader-thread:syncing:backend-pushing'))
672
+
673
+ const trimChangesetRows = (db: SynchronousDatabase, newHead: EventId.EventId) => {
674
+ // Since we're using the session changeset rows to query for the current head,
675
+ // we're keeping at least one row for the current head, and thus are using `<` instead of `<=`
676
+ db.execute(sql`DELETE FROM ${SESSION_CHANGESET_META_TABLE} WHERE idGlobal < ${newHead.global}`)
677
+ }
@@ -24,7 +24,9 @@ export const recreateDb: Effect.Effect<
24
24
 
25
25
  // NOTE to speed up the operations below, we're creating a temporary in-memory database
26
26
  // and later we'll overwrite the persisted database with the new data
27
- const tmpSyncDb = yield* makeSyncDb({ _tag: 'in-memory' })
27
+ // TODO bring back this optimization
28
+ // const tmpSyncDb = yield* makeSyncDb({ _tag: 'in-memory' })
29
+ const tmpSyncDb = db
28
30
  yield* configureConnection(tmpSyncDb, { fkEnabled: true })
29
31
 
30
32
  const initDb = (hooks: Partial<MigrationHooks> | undefined) =>
@@ -91,17 +93,19 @@ export const recreateDb: Effect.Effect<
91
93
  }
92
94
  }
93
95
 
96
+ // TODO bring back
94
97
  // Import the temporary in-memory database into the persistent database
95
- yield* Effect.sync(() => db.import(tmpSyncDb)).pipe(
96
- Effect.withSpan('@livestore/common:leader-thread:recreateDb:import'),
97
- )
98
+ // yield* Effect.sync(() => db.import(tmpSyncDb)).pipe(
99
+ // Effect.withSpan('@livestore/common:leader-thread:recreateDb:import'),
100
+ // )
98
101
 
99
102
  // TODO maybe bring back re-using this initial snapshot to avoid calling `.export()` again
100
103
  // We've disabled this for now as it made the code too complex, as we often run syncing right after
101
104
  // so the snapshot is no longer up to date
102
105
  // const snapshotFromTmpDb = tmpSyncDb.export()
103
106
 
104
- tmpSyncDb.close()
107
+ // TODO bring back
108
+ // tmpSyncDb.close()
105
109
  }).pipe(
106
110
  Effect.scoped, // NOTE we're closing the scope here so finalizers are called when the effect is done
107
111
  Effect.withSpan('@livestore/common:leader-thread:recreateDb'),
package/src/mutation.ts CHANGED
@@ -8,10 +8,19 @@ import { prepareBindValues } from './util.js'
8
8
 
9
9
  export const getExecArgsFromMutation = ({
10
10
  mutationDef,
11
- mutationEventDecoded,
11
+ mutationEvent,
12
12
  }: {
13
13
  mutationDef: MutationDef.Any
14
- mutationEventDecoded: MutationEvent.AnyDecoded | MutationEvent.PartialAny
14
+ /** Both encoded and decoded mutation events are supported to reduce the number of times we need to decode/encode */
15
+ mutationEvent:
16
+ | {
17
+ decoded: MutationEvent.AnyDecoded | MutationEvent.PartialAnyDecoded
18
+ encoded: undefined
19
+ }
20
+ | {
21
+ decoded: undefined
22
+ encoded: MutationEvent.AnyEncoded | MutationEvent.PartialAnyEncoded
23
+ }
15
24
  }): ReadonlyArray<{
16
25
  statementSql: string
17
26
  bindValues: PreparedBindValues
@@ -23,7 +32,9 @@ export const getExecArgsFromMutation = ({
23
32
 
24
33
  switch (typeof mutationDef.sql) {
25
34
  case 'function': {
26
- const res = mutationDef.sql(mutationEventDecoded.args)
35
+ const mutationArgsDecoded =
36
+ mutationEvent.decoded?.args ?? Schema.decodeUnknownSync(mutationDef.schema)(mutationEvent.encoded!.args)
37
+ const res = mutationDef.sql(mutationArgsDecoded)
27
38
  statementRes = Array.isArray(res) ? res : [res]
28
39
  break
29
40
  }
@@ -40,10 +51,9 @@ export const getExecArgsFromMutation = ({
40
51
  return statementRes.map((statementRes) => {
41
52
  const statementSql = typeof statementRes === 'string' ? statementRes : statementRes.sql
42
53
 
43
- const bindValues =
44
- typeof statementRes === 'string'
45
- ? Schema.encodeUnknownSync(mutationDef.schema)(mutationEventDecoded.args)
46
- : statementRes.bindValues
54
+ const mutationArgsEncoded =
55
+ mutationEvent.encoded?.args ?? Schema.encodeUnknownSync(mutationDef.schema)(mutationEvent.decoded!.args)
56
+ const bindValues = typeof statementRes === 'string' ? mutationArgsEncoded : statementRes.bindValues
47
57
 
48
58
  const writeTables = typeof statementRes === 'string' ? undefined : statementRes.writeTables
49
59
 
@@ -1,8 +1,8 @@
1
- import { isDevEnv, memoizeByRef, shouldNeverHappen } from '@livestore/utils'
1
+ import { memoizeByRef, shouldNeverHappen } from '@livestore/utils'
2
2
  import { Chunk, Effect, Option, Schema, Stream } from '@livestore/utils/effect'
3
3
 
4
4
  import { type MigrationOptionsFromMutationLog, type SynchronousDatabase, UnexpectedError } from './adapter-types.js'
5
- import { getExecArgsFromMutation } from './mutation.js'
5
+ import { makeApplyMutation } from './leader-thread/apply-mutation.js'
6
6
  import type { LiveStoreSchema, MutationDef, MutationEvent, MutationLogMetaRow } from './schema/mod.js'
7
7
  import { EventId, MUTATION_LOG_META_TABLE } from './schema/mod.js'
8
8
  import type { PreparedBindValues } from './util.js'
@@ -10,7 +10,8 @@ import { sql } from './util.js'
10
10
 
11
11
  export const rehydrateFromMutationLog = ({
12
12
  logDb,
13
- db,
13
+ // TODO re-use this db when bringing back the boot in-memory db implementation
14
+ // db,
14
15
  schema,
15
16
  migrationOptions,
16
17
  onProgress,
@@ -28,6 +29,8 @@ export const rehydrateFromMutationLog = ({
28
29
 
29
30
  const hashMutation = memoizeByRef((mutation: MutationDef.Any) => Schema.hash(mutation.schema))
30
31
 
32
+ const applyMutation = yield* makeApplyMutation
33
+
31
34
  const processMutation = (row: MutationLogMetaRow) =>
32
35
  Effect.gen(function* () {
33
36
  const mutationDef = schema.mutations.get(row.mutation) ?? shouldNeverHappen(`Unknown mutation ${row.mutation}`)
@@ -40,7 +43,10 @@ export const rehydrateFromMutationLog = ({
40
43
  )
41
44
  }
42
45
 
43
- const argsDecoded = yield* Schema.decodeUnknown(Schema.parseJson(mutationDef.schema))(row.argsJson).pipe(
46
+ const args = JSON.parse(row.argsJson)
47
+
48
+ // Checking whether the schema has changed in an incompatible way
49
+ yield* Schema.decodeUnknown(mutationDef.schema)(args).pipe(
44
50
  Effect.mapError((cause) =>
45
51
  UnexpectedError.make({
46
52
  cause,
@@ -53,28 +59,14 @@ This likely means the schema has changed in an incompatible way.
53
59
  ),
54
60
  )
55
61
 
56
- const mutationEventDecoded = {
62
+ const mutationEventEncoded = {
57
63
  id: { global: row.idGlobal, local: row.idLocal },
58
64
  parentId: { global: row.parentIdGlobal, local: row.parentIdLocal },
59
65
  mutation: row.mutation,
60
- args: argsDecoded,
61
- } satisfies MutationEvent.AnyDecoded
62
-
63
- const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
64
-
65
- const makeExecuteOptions = (statementSql: string, bindValues: any) => ({
66
- onRowsChanged: (rowsChanged: number) => {
67
- if (rowsChanged === 0 && migrationOptions.logging?.excludeAffectedRows?.(statementSql) !== true) {
68
- console.warn(`Mutation "${mutationDef.name}" did not affect any rows:`, statementSql, bindValues)
69
- }
70
- },
71
- })
72
-
73
- for (const { statementSql, bindValues } of execArgsArr) {
74
- // TODO cache prepared statements for mutations
75
- db.execute(statementSql, bindValues, isDevEnv() ? makeExecuteOptions(statementSql, bindValues) : undefined)
76
- // console.log(`Re-executed mutation ${mutationSql}`, bindValues)
77
- }
66
+ args,
67
+ } satisfies MutationEvent.AnyEncoded
68
+
69
+ yield* applyMutation(mutationEventEncoded, { skipMutationLog: true })
78
70
  }).pipe(Effect.withSpan(`@livestore/common:rehydrateFromMutationLog:processMutation`))
79
71
 
80
72
  const CHUNK_SIZE = 100
@@ -54,7 +54,7 @@ export const AnyEncodedGlobal = Schema.Struct({
54
54
  }).annotations({ title: 'MutationEvent.AnyEncodedGlobal' })
55
55
  export type AnyEncodedGlobal = typeof AnyEncodedGlobal.Type
56
56
 
57
- export type PartialAny = MutationEventPartial<MutationDef.Any>
57
+ export type PartialAnyDecoded = MutationEventPartial<MutationDef.Any>
58
58
  export type PartialAnyEncoded = MutationEventPartialEncoded<MutationDef.Any>
59
59
 
60
60
  export type PartialForSchema<TSchema extends LiveStoreSchema> = {
@@ -65,8 +65,9 @@ export type ForSchema<TSchema extends LiveStoreSchema> = {
65
65
  [K in keyof TSchema['_MutationDefMapType']]: MutationEvent<TSchema['_MutationDefMapType'][K]>
66
66
  }[keyof TSchema['_MutationDefMapType']]
67
67
 
68
- export const isPartialMutationEvent = (mutationEvent: AnyDecoded | PartialAny): mutationEvent is PartialAny =>
69
- 'id' in mutationEvent === false && 'parentId' in mutationEvent === false
68
+ export const isPartialMutationEvent = (
69
+ mutationEvent: AnyDecoded | PartialAnyDecoded,
70
+ ): mutationEvent is PartialAnyDecoded => 'id' in mutationEvent === false && 'parentId' in mutationEvent === false
70
71
 
71
72
  export type ForMutationDefRecord<TMutationsDefRecord extends MutationDefRecord> = Schema.Schema<
72
73
  {
@@ -49,12 +49,13 @@ export const sessionChangesetMetaTable = table(
49
49
  // TODO bring back primary key
50
50
  idGlobal: SqliteDsl.integer({ schema: EventId.GlobalEventId }),
51
51
  idLocal: SqliteDsl.integer({ schema: EventId.LocalEventId }),
52
- // idGlobal: SqliteDsl.integer({ primaryKey: true }),
53
- // idLocal: SqliteDsl.integer({ primaryKey: true }),
54
- changeset: SqliteDsl.blob({}),
52
+ changeset: SqliteDsl.blob({ nullable: true }),
55
53
  debug: SqliteDsl.json({ nullable: true }),
56
54
  },
57
- { disableAutomaticIdColumn: true },
55
+ {
56
+ disableAutomaticIdColumn: true,
57
+ indexes: [{ columns: ['idGlobal', 'idLocal'], name: 'idx_session_changeset_id' }],
58
+ },
58
59
  )
59
60
 
60
61
  export type SessionChangesetMetaRow = FromTable.RowDecoded<typeof sessionChangesetMetaTable>
@@ -84,7 +85,7 @@ export const mutationLogMetaTable = table(
84
85
  disableAutomaticIdColumn: true,
85
86
  indexes: [
86
87
  { columns: ['idGlobal'], name: 'idx_idGlobal' },
87
- { columns: ['idGlobal', 'idLocal'], name: 'idx_idGlobal_idLocal' },
88
+ { columns: ['idGlobal', 'idLocal'], name: 'idx_mutationlog_id' },
88
89
  ],
89
90
  },
90
91
  )
@@ -34,7 +34,7 @@ export const makeClientSessionSyncProcessor = ({
34
34
  pushToLeader: (batch: ReadonlyArray<MutationEvent.AnyEncoded>) => void
35
35
  pullFromLeader: ClientSessionLeaderThreadProxy['mutations']['pull']
36
36
  applyMutation: (
37
- mutationEventDecoded: MutationEvent.PartialAny,
37
+ mutationEventDecoded: MutationEvent.PartialAnyDecoded,
38
38
  options: { otelContext: otel.Context; withChangeset: boolean },
39
39
  ) => {
40
40
  writeTables: Set<string>
@@ -196,7 +196,7 @@ export const makeClientSessionSyncProcessor = ({
196
196
 
197
197
  export interface ClientSessionSyncProcessor {
198
198
  push: (
199
- batch: ReadonlyArray<MutationEvent.PartialAny>,
199
+ batch: ReadonlyArray<MutationEvent.PartialAnyDecoded>,
200
200
  options: { otelContext: otel.Context },
201
201
  ) => {
202
202
  writeTables: Set<string>
@@ -19,13 +19,13 @@ export type RebaseInput = {
19
19
  newRemoteEvents: RebaseEventWithConflict[]
20
20
  pendingLocalEvents: RebaseEventWithConflict[]
21
21
  validate: (args: {
22
- rebasedLocalEvents: MutationEvent.PartialAny[]
22
+ rebasedLocalEvents: MutationEvent.PartialAnyDecoded[]
23
23
  mutationDefs: Record<string, MutationDef.Any>
24
24
  }) => FactValidationResult
25
25
  }
26
26
 
27
27
  export type RebaseOutput = {
28
- rebasedLocalEvents: MutationEvent.PartialAny[]
28
+ rebasedLocalEvents: MutationEvent.PartialAnyDecoded[]
29
29
  }
30
30
 
31
31
  export type RebaseFn = (input: RebaseInput) => RebaseOutput
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.0-dev.4' as const
5
+ export const liveStoreVersion = '0.3.0-dev.6' as const
6
6
 
7
7
  /**
8
8
  * This version number is incremented whenever the internal storage format changes in a breaking way.