@livestore/common 0.5.0-dev.0 → 0.5.0-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 (302) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/ClientSessionLeaderThreadProxy.d.ts.map +1 -1
  3. package/dist/MaterializationJournal.d.ts +2 -2
  4. package/dist/MaterializationJournal.d.ts.map +1 -1
  5. package/dist/MaterializationJournal.js +7 -7
  6. package/dist/StateHead.d.ts +1 -1
  7. package/dist/StateHead.d.ts.map +1 -1
  8. package/dist/StateHead.js +4 -4
  9. package/dist/WorkerTransportError.d.ts.map +1 -1
  10. package/dist/__tests__/fixture.d.ts +7 -7
  11. package/dist/__tests__/fixture.js +2 -2
  12. package/dist/adapter-types.d.ts +4 -0
  13. package/dist/adapter-types.d.ts.map +1 -1
  14. package/dist/adapter-types.js +4 -4
  15. package/dist/bounded-collections.d.ts.map +1 -1
  16. package/dist/bounded-collections.js.map +1 -1
  17. package/dist/debug-info.d.ts.map +1 -1
  18. package/dist/debug-info.js +9 -8
  19. package/dist/debug-info.js.map +1 -1
  20. package/dist/defs.d.ts +4 -4
  21. package/dist/defs.js +2 -2
  22. package/dist/devtools/devtools-compatibility.test.js +1 -1
  23. package/dist/devtools/devtools-messages-client-session.d.ts +4 -4
  24. package/dist/devtools/devtools-messages-client-session.js +4 -4
  25. package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
  26. package/dist/devtools/devtools-messages-common.js +1 -1
  27. package/dist/devtools/devtools-messages-leader.d.ts +9 -9
  28. package/dist/devtools/devtools-messages-leader.js +4 -4
  29. package/dist/devtools/devtools-messages.js +3 -3
  30. package/dist/devtools/devtools-sessioninfo.d.ts.map +1 -1
  31. package/dist/devtools/mod.d.ts.map +1 -1
  32. package/dist/devtools/mod.js +3 -3
  33. package/dist/errors.d.ts +4 -4
  34. package/dist/errors.d.ts.map +1 -1
  35. package/dist/errors.js +1 -1
  36. package/dist/errors.js.map +1 -1
  37. package/dist/index.js +22 -22
  38. package/dist/leader-thread/LeaderSyncProcessor.d.ts +27 -6
  39. package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
  40. package/dist/leader-thread/LeaderSyncProcessor.js +65 -55
  41. package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
  42. package/dist/leader-thread/RejectedPushError.d.ts +9 -9
  43. package/dist/leader-thread/RejectedPushError.d.ts.map +1 -1
  44. package/dist/leader-thread/RejectedPushError.js +1 -1
  45. package/dist/leader-thread/connection.d.ts.map +1 -1
  46. package/dist/leader-thread/connection.js +2 -2
  47. package/dist/leader-thread/eventlog.d.ts +11 -1
  48. package/dist/leader-thread/eventlog.d.ts.map +1 -1
  49. package/dist/leader-thread/eventlog.js +13 -13
  50. package/dist/leader-thread/eventlog.js.map +1 -1
  51. package/dist/leader-thread/leader-worker-devtools.d.ts +1 -1
  52. package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
  53. package/dist/leader-thread/leader-worker-devtools.js +4 -4
  54. package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
  55. package/dist/leader-thread/make-leader-thread-layer.d.ts +6 -0
  56. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
  57. package/dist/leader-thread/make-leader-thread-layer.js +43 -29
  58. package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
  59. package/dist/leader-thread/make-leader-thread-layer.test.js +2 -2
  60. package/dist/leader-thread/materialize-event.d.ts.map +1 -1
  61. package/dist/leader-thread/materialize-event.js +9 -9
  62. package/dist/leader-thread/mod.js +9 -9
  63. package/dist/leader-thread/recreate-db.d.ts +5 -1
  64. package/dist/leader-thread/recreate-db.d.ts.map +1 -1
  65. package/dist/leader-thread/recreate-db.js +21 -25
  66. package/dist/leader-thread/recreate-db.js.map +1 -1
  67. package/dist/leader-thread/shutdown-channel.js +1 -1
  68. package/dist/leader-thread/stream-events.d.ts.map +1 -1
  69. package/dist/leader-thread/stream-events.js +3 -5
  70. package/dist/leader-thread/stream-events.js.map +1 -1
  71. package/dist/leader-thread/types.d.ts +6 -4
  72. package/dist/leader-thread/types.d.ts.map +1 -1
  73. package/dist/leader-thread/types.js +5 -1
  74. package/dist/leader-thread/types.js.map +1 -1
  75. package/dist/make-client-session.d.ts +1 -1
  76. package/dist/make-client-session.d.ts.map +1 -1
  77. package/dist/make-client-session.js +2 -2
  78. package/dist/materializer-helper.d.ts.map +1 -1
  79. package/dist/materializer-helper.js +4 -4
  80. package/dist/materializer-helper.js.map +1 -1
  81. package/dist/otel.d.ts.map +1 -1
  82. package/dist/rematerialize-from-eventlog.d.ts +3 -1
  83. package/dist/rematerialize-from-eventlog.d.ts.map +1 -1
  84. package/dist/rematerialize-from-eventlog.js +16 -9
  85. package/dist/rematerialize-from-eventlog.js.map +1 -1
  86. package/dist/schema/EventDef/define.d.ts +2 -2
  87. package/dist/schema/EventDef/define.d.ts.map +1 -1
  88. package/dist/schema/EventDef/define.test.js +1 -1
  89. package/dist/schema/EventDef/deprecated.d.ts +1 -1
  90. package/dist/schema/EventDef/deprecated.d.ts.map +1 -1
  91. package/dist/schema/EventDef/deprecated.test.js +2 -2
  92. package/dist/schema/EventDef/facts.d.ts.map +1 -1
  93. package/dist/schema/EventDef/materializer.d.ts +2 -2
  94. package/dist/schema/EventDef/materializer.d.ts.map +1 -1
  95. package/dist/schema/EventDef/mod.js +5 -5
  96. package/dist/schema/EventSequenceNumber/client.d.ts +3 -8
  97. package/dist/schema/EventSequenceNumber/client.d.ts.map +1 -1
  98. package/dist/schema/EventSequenceNumber/client.js +12 -15
  99. package/dist/schema/EventSequenceNumber/client.js.map +1 -1
  100. package/dist/schema/EventSequenceNumber/mod.js +2 -2
  101. package/dist/schema/EventSequenceNumber.test.js +1 -1
  102. package/dist/schema/LiveStoreEvent/client.d.ts +8 -8
  103. package/dist/schema/LiveStoreEvent/client.d.ts.map +1 -1
  104. package/dist/schema/LiveStoreEvent/client.js +1 -1
  105. package/dist/schema/LiveStoreEvent/client.js.map +1 -1
  106. package/dist/schema/LiveStoreEvent/client.test.js +2 -2
  107. package/dist/schema/LiveStoreEvent/global.d.ts.map +1 -1
  108. package/dist/schema/LiveStoreEvent/global.js +1 -1
  109. package/dist/schema/LiveStoreEvent/input.d.ts +1 -1
  110. package/dist/schema/LiveStoreEvent/input.d.ts.map +1 -1
  111. package/dist/schema/LiveStoreEvent/mod.js +4 -4
  112. package/dist/schema/events.js +1 -1
  113. package/dist/schema/mod.js +11 -11
  114. package/dist/schema/schema.d.ts +1 -1
  115. package/dist/schema/schema.d.ts.map +1 -1
  116. package/dist/schema/schema.js +3 -3
  117. package/dist/schema/state/mod.js +2 -2
  118. package/dist/schema/state/sqlite/client-document-def.d.ts +1 -1
  119. package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -1
  120. package/dist/schema/state/sqlite/client-document-def.js +8 -8
  121. package/dist/schema/state/sqlite/client-document-def.js.map +1 -1
  122. package/dist/schema/state/sqlite/client-document-def.test.js +24 -10
  123. package/dist/schema/state/sqlite/client-document-def.test.js.map +1 -1
  124. package/dist/schema/state/sqlite/column-annotations.d.ts.map +1 -1
  125. package/dist/schema/state/sqlite/column-annotations.test.js +1 -1
  126. package/dist/schema/state/sqlite/column-def.d.ts.map +1 -1
  127. package/dist/schema/state/sqlite/column-def.js +17 -3
  128. package/dist/schema/state/sqlite/column-def.js.map +1 -1
  129. package/dist/schema/state/sqlite/column-def.test.js +60 -5
  130. package/dist/schema/state/sqlite/column-def.test.js.map +1 -1
  131. package/dist/schema/state/sqlite/column-spec.d.ts.map +1 -1
  132. package/dist/schema/state/sqlite/column-spec.js +1 -1
  133. package/dist/schema/state/sqlite/column-spec.test.js +2 -2
  134. package/dist/schema/state/sqlite/column-spec.test.js.map +1 -1
  135. package/dist/schema/state/sqlite/db-schema/ast/fingerprint-digest.d.ts +3 -0
  136. package/dist/schema/state/sqlite/db-schema/ast/fingerprint-digest.d.ts.map +1 -0
  137. package/dist/schema/state/sqlite/db-schema/ast/fingerprint-digest.js +5 -0
  138. package/dist/schema/state/sqlite/db-schema/ast/fingerprint-digest.js.map +1 -0
  139. package/dist/schema/state/sqlite/db-schema/ast/fingerprint.d.ts +5 -0
  140. package/dist/schema/state/sqlite/db-schema/ast/fingerprint.d.ts.map +1 -0
  141. package/dist/schema/state/sqlite/db-schema/ast/fingerprint.js +183 -0
  142. package/dist/schema/state/sqlite/db-schema/ast/fingerprint.js.map +1 -0
  143. package/dist/schema/state/sqlite/db-schema/ast/fingerprint.test.d.ts +2 -0
  144. package/dist/schema/state/sqlite/db-schema/ast/fingerprint.test.d.ts.map +1 -0
  145. package/dist/schema/state/sqlite/db-schema/ast/fingerprint.test.js +142 -0
  146. package/dist/schema/state/sqlite/db-schema/ast/fingerprint.test.js.map +1 -0
  147. package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts +1 -6
  148. package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts.map +1 -1
  149. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +1 -82
  150. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js.map +1 -1
  151. package/dist/schema/state/sqlite/db-schema/ast/validate.d.ts.map +1 -1
  152. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts.map +1 -1
  153. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js +2 -1
  154. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js.map +1 -1
  155. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js +1 -1
  156. package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts.map +1 -1
  157. package/dist/schema/state/sqlite/db-schema/dsl/mod.js +1 -1
  158. package/dist/schema/state/sqlite/db-schema/has-json-string-encoding.d.ts +3 -0
  159. package/dist/schema/state/sqlite/db-schema/has-json-string-encoding.d.ts.map +1 -0
  160. package/dist/schema/state/sqlite/db-schema/has-json-string-encoding.js +6 -0
  161. package/dist/schema/state/sqlite/db-schema/has-json-string-encoding.js.map +1 -0
  162. package/dist/schema/state/sqlite/db-schema/mod.js +2 -2
  163. package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
  164. package/dist/schema/state/sqlite/mod.js +10 -9
  165. package/dist/schema/state/sqlite/mod.js.map +1 -1
  166. package/dist/schema/state/sqlite/persistence.d.ts.map +1 -1
  167. package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -1
  168. package/dist/schema/state/sqlite/query-builder/astToSql.d.ts.map +1 -1
  169. package/dist/schema/state/sqlite/query-builder/astToSql.js +2 -7
  170. package/dist/schema/state/sqlite/query-builder/astToSql.js.map +1 -1
  171. package/dist/schema/state/sqlite/query-builder/impl.d.ts +1 -3
  172. package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -1
  173. package/dist/schema/state/sqlite/query-builder/impl.js +2 -2
  174. package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -1
  175. package/dist/schema/state/sqlite/query-builder/impl.test.js +2 -2
  176. package/dist/schema/state/sqlite/query-builder/mod.js +2 -2
  177. package/dist/schema/state/sqlite/schema-helpers.d.ts.map +1 -1
  178. package/dist/schema/state/sqlite/schema-helpers.js +1 -1
  179. package/dist/schema/state/sqlite/schema-helpers.test.js +2 -2
  180. package/dist/schema/state/sqlite/system-tables/eventlog-tables.js +3 -3
  181. package/dist/schema/state/sqlite/system-tables/mod.js +2 -2
  182. package/dist/schema/state/sqlite/system-tables/state-tables.d.ts +60 -17
  183. package/dist/schema/state/sqlite/system-tables/state-tables.d.ts.map +1 -1
  184. package/dist/schema/state/sqlite/system-tables/state-tables.js +14 -5
  185. package/dist/schema/state/sqlite/system-tables/state-tables.js.map +1 -1
  186. package/dist/schema/state/sqlite/table-def.d.ts +1 -1
  187. package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
  188. package/dist/schema/state/sqlite/table-def.js +24 -8
  189. package/dist/schema/state/sqlite/table-def.js.map +1 -1
  190. package/dist/schema/state/sqlite/table-def.test.js +54 -1
  191. package/dist/schema/state/sqlite/table-def.test.js.map +1 -1
  192. package/dist/schema/unknown-events.d.ts.map +1 -1
  193. package/dist/schema/unknown-events.js +1 -1
  194. package/dist/schema/unknown-events.js.map +1 -1
  195. package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js +26 -4
  196. package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js.map +1 -1
  197. package/dist/schema-management/common.d.ts.map +1 -1
  198. package/dist/schema-management/common.js +2 -2
  199. package/dist/schema-management/migrations.d.ts +4 -4
  200. package/dist/schema-management/migrations.d.ts.map +1 -1
  201. package/dist/schema-management/migrations.js +9 -9
  202. package/dist/schema-management/migrations.js.map +1 -1
  203. package/dist/schema-management/validate-schema.d.ts.map +1 -1
  204. package/dist/schema-management/validate-schema.js +1 -1
  205. package/dist/session-id-symbol.d.ts.map +1 -1
  206. package/dist/sql-queries/index.js +3 -3
  207. package/dist/sql-queries/sql-queries.d.ts.map +1 -1
  208. package/dist/sql-queries/sql-queries.js +3 -3
  209. package/dist/sql-queries/sql-queries.js.map +1 -1
  210. package/dist/sql-queries/sql-query-builder.d.ts.map +1 -1
  211. package/dist/sql-queries/sql-query-builder.js +1 -1
  212. package/dist/sql-queries/types.d.ts.map +1 -1
  213. package/dist/sqlite-db-helper.d.ts +2 -2
  214. package/dist/sqlite-db-helper.d.ts.map +1 -1
  215. package/dist/sqlite-db-helper.js +2 -2
  216. package/dist/sync/ClientSessionSyncProcessor.d.ts +3 -3
  217. package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
  218. package/dist/sync/ClientSessionSyncProcessor.js +7 -7
  219. package/dist/sync/errors.js +1 -1
  220. package/dist/sync/index.js +6 -6
  221. package/dist/sync/mock-sync-backend.d.ts.map +1 -1
  222. package/dist/sync/mock-sync-backend.js +4 -4
  223. package/dist/sync/mock-sync-backend.test.js +2 -2
  224. package/dist/sync/next/compact-events.d.ts.map +1 -1
  225. package/dist/sync/next/compact-events.js +3 -3
  226. package/dist/sync/next/facts.d.ts.map +1 -1
  227. package/dist/sync/next/facts.js +1 -1
  228. package/dist/sync/next/history-dag-common.d.ts +1 -1
  229. package/dist/sync/next/history-dag-common.d.ts.map +1 -1
  230. package/dist/sync/next/history-dag-common.js +1 -1
  231. package/dist/sync/next/history-dag.d.ts.map +1 -1
  232. package/dist/sync/next/history-dag.js +3 -3
  233. package/dist/sync/next/mod.js +5 -5
  234. package/dist/sync/next/rebase-events.d.ts.map +1 -1
  235. package/dist/sync/next/rebase-events.js +2 -2
  236. package/dist/sync/next/test/compact-events.calculator.test.js +4 -4
  237. package/dist/sync/next/test/compact-events.test.js +4 -4
  238. package/dist/sync/next/test/event-fixtures.d.ts +1 -1
  239. package/dist/sync/next/test/event-fixtures.d.ts.map +1 -1
  240. package/dist/sync/next/test/event-fixtures.js +5 -5
  241. package/dist/sync/next/test/history-dag-common.test.js +2 -2
  242. package/dist/sync/next/test/mod.js +1 -1
  243. package/dist/sync/sync-backend-kv.js +1 -1
  244. package/dist/sync/sync-backend.d.ts.map +1 -1
  245. package/dist/sync/sync-backend.js +1 -1
  246. package/dist/sync/sync.js +2 -2
  247. package/dist/sync/syncstate.d.ts +3 -3
  248. package/dist/sync/syncstate.d.ts.map +1 -1
  249. package/dist/sync/syncstate.js +2 -2
  250. package/dist/sync/syncstate.js.map +1 -1
  251. package/dist/sync/syncstate.test.js +3 -3
  252. package/dist/sync/transport-chunking.d.ts.map +1 -1
  253. package/dist/sync/validate-push-payload.d.ts.map +1 -1
  254. package/dist/sync/validate-push-payload.js +2 -2
  255. package/dist/testing/event-factory.d.ts.map +1 -1
  256. package/dist/testing/event-factory.js +2 -2
  257. package/dist/testing/event-factory.js.map +1 -1
  258. package/dist/testing/mod.js +1 -1
  259. package/dist/util.d.ts.map +1 -1
  260. package/dist/util.js +1 -0
  261. package/dist/util.js.map +1 -1
  262. package/dist/version.d.ts.map +1 -1
  263. package/dist/version.js.map +1 -1
  264. package/package.json +40 -39
  265. package/src/adapter-types.ts +4 -0
  266. package/src/debug-info.ts +11 -8
  267. package/src/defs.ts +2 -2
  268. package/src/leader-thread/LeaderSyncProcessor.ts +121 -97
  269. package/src/leader-thread/eventlog.ts +13 -5
  270. package/src/leader-thread/leader-worker-devtools.ts +1 -1
  271. package/src/leader-thread/make-leader-thread-layer.ts +49 -23
  272. package/src/leader-thread/recreate-db.ts +26 -25
  273. package/src/leader-thread/stream-events.ts +1 -3
  274. package/src/leader-thread/types.ts +5 -0
  275. package/src/materializer-helper.ts +1 -1
  276. package/src/rematerialize-from-eventlog.ts +28 -13
  277. package/src/schema/EventSequenceNumber/client.ts +12 -15
  278. package/src/schema/schema.ts +1 -1
  279. package/src/schema/state/sqlite/client-document-def.test.ts +23 -7
  280. package/src/schema/state/sqlite/client-document-def.ts +2 -2
  281. package/src/schema/state/sqlite/column-def.test.ts +84 -3
  282. package/src/schema/state/sqlite/column-def.ts +16 -1
  283. package/src/schema/state/sqlite/db-schema/ast/fingerprint-digest.ts +6 -0
  284. package/src/schema/state/sqlite/db-schema/ast/fingerprint.test.ts +209 -0
  285. package/src/schema/state/sqlite/db-schema/ast/fingerprint.ts +225 -0
  286. package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +1 -90
  287. package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +1 -0
  288. package/src/schema/state/sqlite/db-schema/has-json-string-encoding.ts +9 -0
  289. package/src/schema/state/sqlite/mod.ts +2 -1
  290. package/src/schema/state/sqlite/query-builder/astToSql.ts +1 -8
  291. package/src/schema/state/sqlite/system-tables/state-tables.ts +13 -2
  292. package/src/schema/state/sqlite/table-def.test.ts +66 -0
  293. package/src/schema/state/sqlite/table-def.ts +21 -5
  294. package/src/schema-management/__tests__/migrations-autoincrement-quoting.test.ts +33 -2
  295. package/src/schema-management/migrations.ts +7 -6
  296. package/src/sql-queries/sql-queries.ts +1 -1
  297. package/src/util.ts +1 -0
  298. package/dist/schema/state/sqlite/db-schema/hash.d.ts +0 -2
  299. package/dist/schema/state/sqlite/db-schema/hash.d.ts.map +0 -1
  300. package/dist/schema/state/sqlite/db-schema/hash.js +0 -15
  301. package/dist/schema/state/sqlite/db-schema/hash.js.map +0 -1
  302. package/src/schema/state/sqlite/db-schema/hash.ts +0 -13
@@ -11,8 +11,22 @@ import {
11
11
  UnknownError,
12
12
  } from '../index.ts'
13
13
  import type { LiveStoreSchema } from '../schema/mod.ts'
14
- import { configureConnection } from './connection.ts'
14
+ import { SystemTables } from '../schema/mod.ts'
15
+ import { configureConnection, execSql } from './connection.ts'
15
16
  import type { MaterializeEvent } from './types.ts'
17
+ import { STATE_REBUILD_BATCH_SIZE_DEFAULT } from './types.ts'
18
+
19
+ export const hasCompletedState = (db: SqliteDb): boolean => {
20
+ const tableNames = new Set(db.select<{ name: string }>('SELECT name FROM sqlite_master').map((_) => _.name))
21
+ return (
22
+ SystemTables.stateSystemTables.every((table) => tableNames.has(table.sqliteDef.name)) &&
23
+ db.select(`SELECT id FROM ${SystemTables.REBUILD_META_TABLE} WHERE id = 1`).length === 1
24
+ )
25
+ }
26
+
27
+ /** Marks a successfully prepared state database as safe to use without rebuilding it. */
28
+ export const markStateAsCompleted = (db: SqliteDb): Effect.Effect<void, SqliteError> =>
29
+ execSql(db, `INSERT OR IGNORE INTO ${SystemTables.REBUILD_META_TABLE} (id) VALUES (1)`, {})
16
30
 
17
31
  export const recreateDb = ({
18
32
  dbState,
@@ -20,12 +34,14 @@ export const recreateDb = ({
20
34
  schema,
21
35
  bootStatusQueue,
22
36
  materializeEvent,
37
+ stateRebuildBatchSize = STATE_REBUILD_BATCH_SIZE_DEFAULT,
23
38
  }: {
24
39
  dbState: SqliteDb
25
40
  dbEventlog: SqliteDb
26
41
  schema: LiveStoreSchema
27
42
  bootStatusQueue: Queue.Queue<BootStatus>
28
43
  materializeEvent: MaterializeEvent
44
+ stateRebuildBatchSize?: number
29
45
  }): Effect.Effect<{ migrationsReport: MigrationsReport }, UnknownError | MaterializeError | SqliteError> =>
30
46
  Effect.gen(function* () {
31
47
  const hooks = schema.state.sqlite.migrations.hooks
@@ -36,50 +52,35 @@ export const recreateDb = ({
36
52
  }),
37
53
  )
38
54
 
39
- // NOTE to speed up the operations below, we're creating a temporary in-memory database
40
- // and later we'll overwrite the persisted database with the new data
41
- // TODO bring back this optimization
42
- // const tmpDb = yield* makeSqliteDb({ _tag: 'in-memory' })
43
- const tmpDb = dbState
44
- yield* configureConnection(tmpDb, { foreignKeys: true })
55
+ yield* configureConnection(dbState, { foreignKeys: true })
45
56
 
46
57
  // @effect-diagnostics-next-line anyUnknownInErrorContext:off -- user hook errors are immediately normalized to LiveStore UnknownError
47
- yield* Effect.trySyncOrPromiseOrEffect(() => hooks?.init?.(tmpDb)).pipe(UnknownError.mapToUnknownError)
58
+ yield* Effect.trySyncOrPromiseOrEffect(() => hooks?.init?.(dbState)).pipe(UnknownError.mapToUnknownError)
48
59
 
49
60
  const migrationsReport = yield* migrateDb({
50
- db: tmpDb,
61
+ db: dbState,
51
62
  schema,
52
63
  onProgress: ({ done, total }) => Queue.offer(bootStatusQueue, { stage: 'migrating', progress: { done, total } }),
53
64
  })
54
65
 
55
66
  // @effect-diagnostics-next-line anyUnknownInErrorContext:off -- user hook errors are immediately normalized to LiveStore UnknownError
56
- yield* Effect.trySyncOrPromiseOrEffect(() => hooks?.pre?.(tmpDb)).pipe(UnknownError.mapToUnknownError)
67
+ yield* Effect.trySyncOrPromiseOrEffect(() => hooks?.pre?.(dbState)).pipe(UnknownError.mapToUnknownError)
57
68
 
58
69
  yield* rematerializeFromEventlog({
59
- // db: tmpDb,
60
70
  dbEventlog,
71
+ dbState,
61
72
  schema,
62
73
  materializeEvent,
74
+ batchSize: stateRebuildBatchSize,
63
75
  onProgress: ({ done, total }) =>
64
76
  Queue.offer(bootStatusQueue, { stage: 'rehydrating', progress: { done, total } }),
65
77
  })
66
78
 
67
79
  // @effect-diagnostics-next-line anyUnknownInErrorContext:off -- user hook errors are immediately normalized to LiveStore UnknownError
68
- yield* Effect.trySyncOrPromiseOrEffect(() => hooks?.post?.(tmpDb)).pipe(UnknownError.mapToUnknownError)
69
-
70
- // TODO bring back
71
- // Import the temporary in-memory database into the persistent database
72
- // yield* Effect.sync(() => db.import(tmpDb)).pipe(
73
- // Effect.withSpan('@livestore/common:leader-thread:recreateDb:import'),
74
- // )
75
-
76
- // TODO maybe bring back re-using this initial snapshot to avoid calling `.export()` again
77
- // We've disabled this for now as it made the code too complex, as we often run syncing right after
78
- // so the snapshot is no longer up to date
79
- // const snapshotFromTmpDb = tmpDb.export()
80
+ yield* Effect.trySyncOrPromiseOrEffect(() => hooks?.post?.(dbState)).pipe(UnknownError.mapToUnknownError)
80
81
 
81
- // TODO bring back
82
- // tmpDb.close()
82
+ // Keep this out of finalizers, which also run on failure and interruption.
83
+ yield* markStateAsCompleted(dbState)
83
84
 
84
85
  return { migrationsReport }
85
86
  }).pipe(
@@ -151,9 +151,7 @@ export const streamEventsWithSyncState = ({
151
151
  */
152
152
  const waitForHead = EventSequenceNumber.Client.isGreaterThanOrEqual(cursor, head)
153
153
  const maybeHead =
154
- waitForHead === true
155
- ? yield* Queue.take(headQueue).pipe(Effect.map(Option.some))
156
- : yield* Queue.poll(headQueue)
154
+ waitForHead === true ? yield* Queue.take(headQueue).pipe(Effect.asSome) : yield* Queue.poll(headQueue)
157
155
  const nextHead = Option.getOrElse(maybeHead, () => head)
158
156
  const hardStop = options.until?.global ?? Number.POSITIVE_INFINITY
159
157
  const target = EventSequenceNumber.Client.Composite.make({
@@ -139,6 +139,11 @@ export type InitialBlockingSyncContext = {
139
139
  export const STREAM_EVENTS_BATCH_SIZE_DEFAULT = 100
140
140
  export const STREAM_EVENTS_BATCH_SIZE_MAX = 1_000
141
141
 
142
+ export const STATE_REBUILD_BATCH_SIZE_DEFAULT = 100
143
+ export const StateRebuildBatchSizeSchema = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)).annotate({
144
+ title: 'stateRebuildBatchSize',
145
+ })
146
+
142
147
  export const StreamEventsOptionsFields = {
143
148
  since: Schema.optional(EventSequenceNumber.Client.Composite),
144
149
  until: Schema.optional(EventSequenceNumber.Client.Composite),
@@ -55,7 +55,7 @@ export const getExecStatementsFromMaterializer = ({
55
55
  const { query, bindValues } = rawQueryOrQueryBuilder.asSql()
56
56
  const rawResults = dbState.select(query, prepareBindValues(bindValues, query))
57
57
  const resultSchema = getResultSchema(rawQueryOrQueryBuilder)
58
- return Schema.decodeUnknownSync(resultSchema)(rawResults)
58
+ return Schema.decodeSync(resultSchema)(rawResults)
59
59
  } else {
60
60
  const { query, bindValues } = rawQueryOrQueryBuilder
61
61
  return dbState.select(query, prepareBindValues(bindValues, query))
@@ -3,8 +3,10 @@ import { Effect, Option, ReadonlyArray as EffectArray, Schema, Stream } from '@l
3
3
 
4
4
  import { type SqliteDb, UnknownError } from './adapter-types.ts'
5
5
  import type { MaterializeEvent } from './leader-thread/mod.ts'
6
+ import { STATE_REBUILD_BATCH_SIZE_DEFAULT, StateRebuildBatchSizeSchema } from './leader-thread/types.ts'
6
7
  import type { EventDef, LiveStoreSchema } from './schema/mod.ts'
7
8
  import { EventSequenceNumber, LiveStoreEvent, SystemTables } from './schema/mod.ts'
9
+ import { withSavepoint } from './sqlite-db-helper.ts'
8
10
  import type { PreparedBindValues } from './util.ts'
9
11
  import { sql } from './util.ts'
10
12
 
@@ -14,17 +16,26 @@ const jsonParse = Schema.decodeUnknownSync(Schema.fromJsonString(Schema.Unknown)
14
16
  export const rematerializeFromEventlog = Effect.fn('@livestore/common:rematerializeFromEventlog')(function* ({
15
17
  dbEventlog,
16
18
  // TODO re-use this db when bringing back the boot in-memory db implementation
17
- // db,
19
+ dbState,
18
20
  schema,
19
21
  onProgress,
20
22
  materializeEvent,
23
+ batchSize = STATE_REBUILD_BATCH_SIZE_DEFAULT,
21
24
  }: {
22
25
  dbEventlog: SqliteDb
23
- // db: SqliteDb
26
+ dbState: SqliteDb
24
27
  schema: LiveStoreSchema
25
28
  onProgress: (_: { done: number; total: number }) => Effect.Effect<void>
26
29
  materializeEvent: MaterializeEvent
30
+ batchSize?: number
27
31
  }) {
32
+ if (Schema.is(StateRebuildBatchSizeSchema)(batchSize) === false) {
33
+ return yield* UnknownError.make({
34
+ cause: `Invalid stateRebuildBatchSize: ${String(batchSize)}. Expected a positive integer.`,
35
+ payload: { batchSize },
36
+ })
37
+ }
38
+
28
39
  const eventsCount = dbEventlog.select<{ count: number }>(
29
40
  `SELECT COUNT(*) AS count FROM ${SystemTables.EVENTLOG_META_TABLE}`,
30
41
  )[0]!.count
@@ -85,13 +96,11 @@ This likely means the schema has changed in an incompatible way.
85
96
  yield* materializeEvent(eventEncoded, { skipEventlog: true })
86
97
  })
87
98
 
88
- const CHUNK_SIZE = 100
89
-
90
99
  const stmt = dbEventlog.prepare(sql`\
91
100
  SELECT * FROM ${SystemTables.EVENTLOG_META_TABLE}
92
101
  WHERE seqNumGlobal > $seqNumGlobal OR (seqNumGlobal = $seqNumGlobal AND seqNumClient > $seqNumClient)
93
102
  ORDER BY seqNumGlobal ASC, seqNumClient ASC
94
- LIMIT ${CHUNK_SIZE}
103
+ LIMIT $batchSize
95
104
  `)
96
105
 
97
106
  let processedEvents = 0
@@ -101,6 +110,7 @@ LIMIT ${CHUNK_SIZE}
101
110
  const rows = stmt.select<SystemTables.EventlogMetaRow>({
102
111
  $seqNumGlobal: lastId.global,
103
112
  $seqNumClient: lastId.client,
113
+ $batchSize: batchSize,
104
114
  } as any as PreparedBindValues)
105
115
 
106
116
  if (EffectArray.isReadonlyArrayNonEmpty(rows) === false) {
@@ -125,14 +135,19 @@ LIMIT ${CHUNK_SIZE}
125
135
  }),
126
136
  ).pipe(
127
137
  Stream.bufferArray({ capacity: 2 }),
128
- Stream.tap((row) =>
129
- Effect.gen(function* () {
130
- yield* processEvent(row)
131
-
132
- processedEvents++
133
- yield* onProgress({ done: processedEvents, total: eventsCount })
134
- }),
138
+ Stream.runForEachArray((rows) =>
139
+ Effect.forEach(
140
+ rows,
141
+ (row) =>
142
+ Effect.gen(function* () {
143
+ yield* processEvent(row)
144
+
145
+ processedEvents++
146
+ yield* onProgress({ done: processedEvents, total: eventsCount })
147
+ }),
148
+ { discard: true },
149
+ ).pipe(withSavepoint(dbState)),
135
150
  ),
136
- Stream.runDrain,
151
+ Effect.ensuring(Effect.sync(() => stmt.finalize())),
137
152
  )
138
153
  }, Effect.withPerformanceMeasure('@livestore/common:rematerializeFromEventlog'))
@@ -181,22 +181,9 @@ const CompositeSchema = S.Struct({
181
181
  .annotate({ title: 'EventSequenceNumber.Composite' })
182
182
  .pipe(S.overrideToFormatter(() => (seqNum) => toString(seqNum)))
183
183
 
184
- /**
185
- * Creates a validated Composite sequence number from input.
186
- * If rebaseGeneration is omitted, defaults to REBASE_GENERATION_DEFAULT (0).
187
- */
188
- const makeComposite = (seqNum: CompositeInput): Composite => {
189
- return S.is(CompositeSchema)(seqNum) === true
190
- ? seqNum
191
- : S.decodeSync(CompositeSchema)({
192
- ...seqNum,
193
- rebaseGeneration: seqNum.rebaseGeneration ?? REBASE_GENERATION_DEFAULT,
194
- })
195
- }
196
-
197
184
  /**
198
185
  * Effect Schema for the composite event sequence number (global + client + rebaseGeneration).
199
- * Also includes a `make` helper for creating validated Composite values.
186
+ * Its `make` validates input and defaults an omitted rebaseGeneration to REBASE_GENERATION_DEFAULT (0).
200
187
  *
201
188
  * @example
202
189
  * ```ts
@@ -209,7 +196,17 @@ const makeComposite = (seqNum: CompositeInput): Composite => {
209
196
  * const validated = EventSequenceNumber.Client.Composite.make({ global: 5, client: 0, rebaseGeneration: 0 })
210
197
  * ```
211
198
  */
212
- export const Composite = Object.assign(CompositeSchema, { make: makeComposite })
199
+ export const Composite: typeof CompositeSchema & { make(seqNum: CompositeInput): Composite } =
200
+ class extends CompositeSchema {
201
+ static override make(seqNum: CompositeInput): Composite {
202
+ return S.is(CompositeSchema)(seqNum) === true
203
+ ? seqNum
204
+ : S.decodeSync(CompositeSchema)({
205
+ ...seqNum,
206
+ rebaseGeneration: seqNum.rebaseGeneration ?? REBASE_GENERATION_DEFAULT,
207
+ })
208
+ }
209
+ }
213
210
 
214
211
  /** The root sequence number (global=0, client=0, rebaseGeneration=0). Parent of the first event. */
215
212
  export const ROOT = {
@@ -73,7 +73,7 @@ export interface InternalState {
73
73
  readonly tables: Map<string, TableDef.Any>
74
74
  readonly migrations: MigrationOptions
75
75
  /** Compound hash of all table defs etc */
76
- readonly hash: number
76
+ readonly hash: string
77
77
  }
78
78
  readonly materializers: Map<string, Materializer>
79
79
  }
@@ -10,9 +10,25 @@ import {
10
10
  createOptimisticEventSchema,
11
11
  mergeDefaultValues,
12
12
  } from './client-document-def.ts'
13
+ import { fingerprint } from './db-schema/ast/fingerprint.ts'
13
14
  import { getResultSchema } from './query-builder/impl.ts'
14
15
 
15
16
  describe('client document table', () => {
17
+ test('tracks application value-schema changes in the storage fingerprint', () => {
18
+ const first = clientDocument({
19
+ name: 'documents',
20
+ schema: Schema.Struct({ title: Schema.String.check(Schema.isMinLength(1)) }),
21
+ default: { id: 'default', value: { title: 'valid' } },
22
+ })
23
+ const second = clientDocument({
24
+ name: 'documents',
25
+ schema: Schema.Struct({ title: Schema.String.check(Schema.isMinLength(2)) }),
26
+ default: { id: 'default', value: { title: 'valid' } },
27
+ })
28
+
29
+ expect(fingerprint(first.sqliteDef.ast)).not.toBe(fingerprint(second.sqliteDef.ast))
30
+ })
31
+
16
32
  test('set event', () => {
17
33
  expect(patchId(tables.UiState.set({ showSidebar: false }, 'session-1'))).toMatchInlineSnapshot(`
18
34
  {
@@ -302,7 +318,7 @@ describe('client document table', () => {
302
318
  })
303
319
  const rowSchema = Schema.fromJsonString(optimisticSchema)
304
320
 
305
- expect(Schema.decodeUnknownSync(rowSchema)(JSON.stringify(value))).toEqual(defaultValue)
321
+ expect(Schema.decodeSync(rowSchema)(JSON.stringify(value))).toEqual(defaultValue)
306
322
  })
307
323
 
308
324
  test('decodes valid persisted JSON (encoded shape)', () => {
@@ -313,7 +329,7 @@ describe('client document table', () => {
313
329
  })
314
330
  const rowSchema = Schema.fromJsonString(optimisticSchema)
315
331
 
316
- expect(Schema.decodeUnknownSync(rowSchema)(JSON.stringify(validPayload))).toEqual({
332
+ expect(Schema.decodeSync(rowSchema)(JSON.stringify(validPayload))).toEqual({
317
333
  createdAt: new Date(42),
318
334
  avatar: new Uint8Array([1, 2]),
319
335
  })
@@ -327,7 +343,7 @@ describe('client document table', () => {
327
343
  })
328
344
  const rowSchema = Schema.fromJsonString(optimisticSchema)
329
345
 
330
- expect(Schema.decodeUnknownSync(rowSchema)(JSON.stringify(extraFieldsPayload))).toEqual({
346
+ expect(Schema.decodeSync(rowSchema)(JSON.stringify(extraFieldsPayload))).toEqual({
331
347
  createdAt: new Date(42),
332
348
  avatar: new Uint8Array([1, 2]),
333
349
  })
@@ -345,7 +361,7 @@ describe('client document table', () => {
345
361
  value: JSON.stringify(value),
346
362
  }
347
363
 
348
- expect(Schema.decodeUnknownSync(Doc.rowSchema)(row)).toEqual({ id: 'row-1', value: defaultValue })
364
+ expect(Schema.decodeSync(Doc.rowSchema)(row)).toEqual({ id: 'row-1', value: defaultValue })
349
365
  })
350
366
 
351
367
  test('decodes clientDocument rowSchema with valid encoded JSON', () => {
@@ -360,7 +376,7 @@ describe('client document table', () => {
360
376
  value: JSON.stringify(validPayload),
361
377
  }
362
378
 
363
- expect(Schema.decodeUnknownSync(Doc.rowSchema)(row)).toEqual({
379
+ expect(Schema.decodeSync(Doc.rowSchema)(row)).toEqual({
364
380
  id: 'row-1',
365
381
  value: { createdAt: new Date(42), avatar: new Uint8Array([1, 2]) },
366
382
  })
@@ -382,7 +398,7 @@ describe('client document table', () => {
382
398
  },
383
399
  ]
384
400
 
385
- expect(Schema.decodeUnknownSync(resultSchema)(rawDbResults)).toEqual(defaultValue)
401
+ expect(Schema.decodeSync(resultSchema)(rawDbResults)).toEqual(defaultValue)
386
402
  })
387
403
 
388
404
  test('decodes RowQuery result schema with valid encoded JSON', () => {
@@ -401,7 +417,7 @@ describe('client document table', () => {
401
417
  },
402
418
  ]
403
419
 
404
- expect(Schema.decodeUnknownSync(resultSchema)(rawDbResults)).toEqual({
420
+ expect(Schema.decodeSync(resultSchema)(rawDbResults)).toEqual({
405
421
  createdAt: new Date(42),
406
422
  avatar: new Uint8Array([1, 2]),
407
423
  })
@@ -241,7 +241,7 @@ export const createOptimisticEventSchema = ({
241
241
 
242
242
  if (hasValidFields === true) {
243
243
  try {
244
- const decoded = Schema.decodeUnknownSync(targetSchema)(partialResult)
244
+ const decoded = Schema.decodeSync(targetSchema)(partialResult)
245
245
  return encodeTarget(decoded)
246
246
  } catch {
247
247
  // Even filtered fields don't match schema
@@ -264,7 +264,7 @@ export const createOptimisticEventSchema = ({
264
264
 
265
265
  // Try to decode the merged value
266
266
  try {
267
- const decoded = Schema.decodeUnknownSync(valueSchema)(merged)
267
+ const decoded = Schema.decodeSync(valueSchema)(merged)
268
268
  return encodeTarget(decoded)
269
269
  } catch {
270
270
  // Merged value still doesn't match (e.g., type changes)
@@ -1,7 +1,7 @@
1
1
  import { TestSchema } from 'effect/testing'
2
2
  import { assert, describe, expect, it } from 'vitest'
3
3
 
4
- import { Schema, SchemaAST, Struct } from '@livestore/utils/effect'
4
+ import { Schema, SchemaAST, SchemaTransformation, Struct } from '@livestore/utils/effect'
5
5
 
6
6
  import * as State from '../mod.ts'
7
7
  import { withAutoIncrement, withColumnType, withDefault, withPrimaryKey, withUnique } from './column-annotations.ts'
@@ -26,6 +26,9 @@ describe('getColumnDefForSchema', () => {
26
26
  it('should map Schema.Date to text column', () => {
27
27
  const columnDef = State.SQLite.getColumnDefForSchema(Schema.Date)
28
28
  expect(columnDef.columnType).toBe('text')
29
+ const date = new Date('2026-01-02T03:04:05.678Z')
30
+ expect(Schema.encodeUnknownSync(columnDef.schema)(date)).toBe(date.toISOString())
31
+ expect(Schema.decodeSync(columnDef.schema)(date.toISOString())).toEqual(date)
29
32
  expect(Schema.toEncoded(columnDef.schema).ast._tag).toBe('String')
30
33
  expect(
31
34
  SchemaAST.resolveAt<{ readonly id: string }>('representation')(Schema.toType(columnDef.schema).ast)?.id,
@@ -47,6 +50,84 @@ describe('getColumnDefForSchema', () => {
47
50
  expect(State.SQLite.getColumnDefForSchema(schema).columnType).toBe('integer')
48
51
  })
49
52
 
53
+ it('should preserve bare Date checks on encoding and decoding', () => {
54
+ const schema = Schema.Date.check(Schema.isGreaterThanDate(new Date(0)))
55
+ const columnDef = State.SQLite.getColumnDefForSchema(schema)
56
+ const date = new Date('2026-01-02T03:04:05.678Z')
57
+
58
+ expect(Schema.encodeUnknownSync(columnDef.schema)(date)).toBe(date.toISOString())
59
+ expect(Schema.decodeSync(columnDef.schema)(date.toISOString())).toEqual(date)
60
+ expect(() => Schema.encodeUnknownSync(columnDef.schema)(new Date(0))).toThrow()
61
+ expect(() => Schema.decodeSync(columnDef.schema)(new Date(0).toISOString())).toThrow()
62
+ })
63
+
64
+ it('should round-trip transformations with an encoded Date', () => {
65
+ const schema = Schema.Date.pipe(
66
+ Schema.decodeTo(
67
+ Schema.String,
68
+ SchemaTransformation.transform({
69
+ decode: (date) => date.toISOString(),
70
+ encode: (value) => new Date(value),
71
+ }),
72
+ ),
73
+ )
74
+ const columnDef = State.SQLite.getColumnDefForSchema(schema)
75
+ const value = '2026-01-02T03:04:05.678Z'
76
+
77
+ expect(columnDef.columnType).toBe('text')
78
+ expect(Schema.encodeUnknownSync(columnDef.schema)(value)).toBe(value)
79
+ expect(Schema.decodeSync(columnDef.schema)(value)).toBe(value)
80
+ })
81
+
82
+ it.each(['nullable', 'optional'] as const)(
83
+ 'should round-trip %s transformations with an encoded Date',
84
+ (wrapper) => {
85
+ const schema = Schema.Date.pipe(
86
+ Schema.decodeTo(
87
+ Schema.String,
88
+ SchemaTransformation.transform({
89
+ decode: (date) => date.toISOString(),
90
+ encode: (value) => new Date(value),
91
+ }),
92
+ ),
93
+ )
94
+ const field = wrapper === 'nullable' ? Schema.NullOr(schema) : Schema.optional(schema)
95
+ const { columns } = State.SQLite.table({ name: 'stamps', schema: Schema.Struct({ value: field }) }).sqliteDef
96
+ const columnDef = columns.value
97
+ const value = '2026-01-02T03:04:05.678Z'
98
+
99
+ expect(columnDef.nullable).toBe(true)
100
+ expect(columnDef.columnType).toBe('text')
101
+ const encoded = Schema.encodeUnknownSync(columnDef.schema)(value)
102
+ expect(Schema.decodeSync(columnDef.schema)(encoded)).toBe(value)
103
+ expect(encoded).toBe(value)
104
+ expect(Schema.encodeUnknownSync(columnDef.schema)(null)).toBeNull()
105
+ expect(Schema.decodeSync(columnDef.schema)(null)).toBeNull()
106
+ },
107
+ )
108
+
109
+ it('should round-trip transformations with an encoded nullable Date', () => {
110
+ const schema = Schema.NullOr(Schema.Date).pipe(
111
+ Schema.decodeTo(
112
+ Schema.String,
113
+ SchemaTransformation.transform({
114
+ decode: (date) => (date === null ? 'none' : date.toISOString()),
115
+ encode: (value) => (value === 'none' ? null : new Date(value)),
116
+ }),
117
+ ),
118
+ )
119
+ const { columns } = State.SQLite.table({ name: 'stamps', schema: Schema.Struct({ value: schema }) }).sqliteDef
120
+ const columnDef = columns.value
121
+ const value = '2026-01-02T03:04:05.678Z'
122
+ const encoded = Schema.encodeUnknownSync(columnDef.schema)(value)
123
+
124
+ expect(Schema.decodeSync(columnDef.schema)(encoded)).toBe(value)
125
+ expect(encoded).toBe(value)
126
+ expect(columnDef.nullable).toBe(true)
127
+ expect(Schema.encodeUnknownSync(columnDef.schema)('none')).toBeNull()
128
+ expect(Schema.decodeSync(columnDef.schema)(null)).toBe('none')
129
+ })
130
+
50
131
  it('should map Schema.BigInt to text column', () => {
51
132
  const columnDef = State.SQLite.getColumnDefForSchema(Schema.BigInt)
52
133
  expect(columnDef.columnType).toBe('text')
@@ -322,8 +403,8 @@ describe('getColumnDefForSchema', () => {
322
403
 
323
404
  expect(columnDef.columnType).toBe('blob')
324
405
  expect(columnDef.schema).toBe(schema)
325
- expect(Schema.decodeUnknownSync(columnDef.schema)(new Uint8Array([1]))).toEqual(new Uint8Array([1]))
326
- expect(() => Schema.decodeUnknownSync(columnDef.schema)(new Uint8Array())).toThrow()
406
+ expect(Schema.decodeSync(columnDef.schema)(new Uint8Array([1]))).toEqual(new Uint8Array([1]))
407
+ expect(() => Schema.decodeSync(columnDef.schema)(new Uint8Array())).toThrow()
327
408
  })
328
409
  })
329
410
 
@@ -167,6 +167,21 @@ const getColumnForSchema = (schema: Schema.Top, nullable = false): SqliteDsl.Col
167
167
  // Get the encoded AST - what actually gets stored in SQLite
168
168
  const encodedAst = Schema.toEncoded(coreSchema).ast
169
169
 
170
+ // Adapt Date values on the encoded side to ISO text, then run the original
171
+ // codec so its checks and transformations are preserved in both directions.
172
+ if (hasDateRepresentation(stripNullable(encodedAst)) === true) {
173
+ const encodedNullable = hasNull(encodedAst)
174
+ const dateStorage = encodedNullable === true ? Schema.NullOr(Schema.DateFromString) : Schema.DateFromString
175
+ const adaptedSchema = dateStorage.pipe(Schema.decodeTo(coreSchema))
176
+ return {
177
+ ...SqliteDsl.text(),
178
+ schema: nullable === true ? Schema.NullOr(adaptedSchema) : adaptedSchema,
179
+ // Encoded nulls may decode to non-null values, so allow SQL NULL without
180
+ // bypassing the original codec's null transformation.
181
+ nullable: nullable === true || encodedNullable === true,
182
+ }
183
+ }
184
+
170
185
  // Check if the encoded type matches SQLite native types
171
186
  if (SchemaAST.isString(encodedAst) === true) {
172
187
  return SqliteDsl.text({ schema: coreSchema, nullable })
@@ -210,7 +225,7 @@ const stripNullable = (ast: SchemaAST.AST): SchemaAST.AST => {
210
225
  return coreTypes[0]!
211
226
  }
212
227
 
213
- return new SchemaAST.Union(coreTypes, ast.mode, ast.annotations)
228
+ return new SchemaAST.Union(coreTypes, ast.options, ast.annotations)
214
229
  }
215
230
 
216
231
  const getLiteralColumnDefinition = (
@@ -0,0 +1,6 @@
1
+ import { sha256 } from '@noble/hashes/sha2.js'
2
+
3
+ import { Base64Url } from '@livestore/utils/effect'
4
+
5
+ /** Preserve every digest bit while staying inside the 56-character SQLite VFS filename limit. */
6
+ export const digestToFingerprint = (bytes: Uint8Array): string => Base64Url.encode(sha256(bytes))