@livestore/common 0.4.0-dev.9 → 0.5.0-dev.0

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 (580) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/ClientSessionLeaderThreadProxy.d.ts +27 -12
  3. package/dist/ClientSessionLeaderThreadProxy.d.ts.map +1 -1
  4. package/dist/ClientSessionLeaderThreadProxy.js.map +1 -1
  5. package/dist/MaterializationJournal.d.ts +44 -0
  6. package/dist/MaterializationJournal.d.ts.map +1 -0
  7. package/dist/MaterializationJournal.js +112 -0
  8. package/dist/MaterializationJournal.js.map +1 -0
  9. package/dist/StateHead.d.ts +26 -0
  10. package/dist/StateHead.d.ts.map +1 -0
  11. package/dist/StateHead.js +50 -0
  12. package/dist/StateHead.js.map +1 -0
  13. package/dist/WorkerTransportError.d.ts +11 -0
  14. package/dist/WorkerTransportError.d.ts.map +1 -0
  15. package/dist/WorkerTransportError.js +11 -0
  16. package/dist/WorkerTransportError.js.map +1 -0
  17. package/dist/__tests__/fixture.d.ts +1 -1
  18. package/dist/__tests__/fixture.d.ts.map +1 -1
  19. package/dist/__tests__/fixture.js +3 -3
  20. package/dist/__tests__/fixture.js.map +1 -1
  21. package/dist/adapter-types.d.ts +59 -57
  22. package/dist/adapter-types.d.ts.map +1 -1
  23. package/dist/adapter-types.js +31 -22
  24. package/dist/adapter-types.js.map +1 -1
  25. package/dist/bounded-collections.d.ts.map +1 -1
  26. package/dist/bounded-collections.js +6 -4
  27. package/dist/bounded-collections.js.map +1 -1
  28. package/dist/debug-info.d.ts +56 -56
  29. package/dist/debug-info.d.ts.map +1 -1
  30. package/dist/debug-info.js +46 -32
  31. package/dist/debug-info.js.map +1 -1
  32. package/dist/defs.d.ts +9 -9
  33. package/dist/defs.js +2 -2
  34. package/dist/devtools/devtools-compatibility.test.d.ts +2 -0
  35. package/dist/devtools/devtools-compatibility.test.d.ts.map +1 -0
  36. package/dist/devtools/devtools-compatibility.test.js +16 -0
  37. package/dist/devtools/devtools-compatibility.test.js.map +1 -0
  38. package/dist/devtools/devtools-messages-client-session.d.ts +531 -312
  39. package/dist/devtools/devtools-messages-client-session.d.ts.map +1 -1
  40. package/dist/devtools/devtools-messages-client-session.js +86 -67
  41. package/dist/devtools/devtools-messages-client-session.js.map +1 -1
  42. package/dist/devtools/devtools-messages-common.d.ts +32 -35
  43. package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
  44. package/dist/devtools/devtools-messages-common.js +12 -14
  45. package/dist/devtools/devtools-messages-common.js.map +1 -1
  46. package/dist/devtools/devtools-messages-leader.d.ts +417 -320
  47. package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
  48. package/dist/devtools/devtools-messages-leader.js +117 -87
  49. package/dist/devtools/devtools-messages-leader.js.map +1 -1
  50. package/dist/devtools/devtools-sessioninfo.d.ts +28 -17
  51. package/dist/devtools/devtools-sessioninfo.d.ts.map +1 -1
  52. package/dist/devtools/devtools-sessioninfo.js +12 -6
  53. package/dist/devtools/devtools-sessioninfo.js.map +1 -1
  54. package/dist/devtools/mod.d.ts +17 -7
  55. package/dist/devtools/mod.d.ts.map +1 -1
  56. package/dist/devtools/mod.js +19 -8
  57. package/dist/devtools/mod.js.map +1 -1
  58. package/dist/errors.d.ts +52 -46
  59. package/dist/errors.d.ts.map +1 -1
  60. package/dist/errors.js +31 -21
  61. package/dist/errors.js.map +1 -1
  62. package/dist/index.d.ts +7 -1
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +7 -1
  65. package/dist/index.js.map +1 -1
  66. package/dist/leader-thread/LeaderSyncProcessor.d.ts +116 -21
  67. package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
  68. package/dist/leader-thread/LeaderSyncProcessor.js +596 -470
  69. package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
  70. package/dist/leader-thread/RejectedPushError.d.ts +138 -0
  71. package/dist/leader-thread/RejectedPushError.d.ts.map +1 -0
  72. package/dist/leader-thread/RejectedPushError.js +97 -0
  73. package/dist/leader-thread/RejectedPushError.js.map +1 -0
  74. package/dist/leader-thread/connection.d.ts +1 -0
  75. package/dist/leader-thread/connection.d.ts.map +1 -1
  76. package/dist/leader-thread/connection.js +10 -9
  77. package/dist/leader-thread/connection.js.map +1 -1
  78. package/dist/leader-thread/eventlog.d.ts +20 -15
  79. package/dist/leader-thread/eventlog.d.ts.map +1 -1
  80. package/dist/leader-thread/eventlog.js +80 -20
  81. package/dist/leader-thread/eventlog.js.map +1 -1
  82. package/dist/leader-thread/leader-worker-devtools.d.ts +1 -2
  83. package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
  84. package/dist/leader-thread/leader-worker-devtools.js +110 -62
  85. package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
  86. package/dist/leader-thread/make-leader-thread-layer.d.ts +19 -7
  87. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
  88. package/dist/leader-thread/make-leader-thread-layer.js +67 -45
  89. package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
  90. package/dist/leader-thread/make-leader-thread-layer.test.d.ts +2 -0
  91. package/dist/leader-thread/make-leader-thread-layer.test.d.ts.map +1 -0
  92. package/dist/leader-thread/make-leader-thread-layer.test.js +32 -0
  93. package/dist/leader-thread/make-leader-thread-layer.test.js.map +1 -0
  94. package/dist/leader-thread/materialize-event.d.ts +3 -2
  95. package/dist/leader-thread/materialize-event.d.ts.map +1 -1
  96. package/dist/leader-thread/materialize-event.js +32 -9
  97. package/dist/leader-thread/materialize-event.js.map +1 -1
  98. package/dist/leader-thread/mod.d.ts +1 -0
  99. package/dist/leader-thread/mod.d.ts.map +1 -1
  100. package/dist/leader-thread/mod.js +1 -0
  101. package/dist/leader-thread/mod.js.map +1 -1
  102. package/dist/leader-thread/recreate-db.d.ts +2 -2
  103. package/dist/leader-thread/recreate-db.d.ts.map +1 -1
  104. package/dist/leader-thread/recreate-db.js +19 -40
  105. package/dist/leader-thread/recreate-db.js.map +1 -1
  106. package/dist/leader-thread/shutdown-channel.d.ts +3 -7
  107. package/dist/leader-thread/shutdown-channel.d.ts.map +1 -1
  108. package/dist/leader-thread/shutdown-channel.js +2 -3
  109. package/dist/leader-thread/shutdown-channel.js.map +1 -1
  110. package/dist/leader-thread/stream-events.d.ts +53 -0
  111. package/dist/leader-thread/stream-events.d.ts.map +1 -0
  112. package/dist/leader-thread/stream-events.js +164 -0
  113. package/dist/leader-thread/stream-events.js.map +1 -0
  114. package/dist/leader-thread/types.d.ts +92 -51
  115. package/dist/leader-thread/types.d.ts.map +1 -1
  116. package/dist/leader-thread/types.js +17 -4
  117. package/dist/leader-thread/types.js.map +1 -1
  118. package/dist/logging.d.ts +15 -0
  119. package/dist/logging.d.ts.map +1 -0
  120. package/dist/logging.js +2 -0
  121. package/dist/logging.js.map +1 -0
  122. package/dist/make-client-session.d.ts +6 -5
  123. package/dist/make-client-session.d.ts.map +1 -1
  124. package/dist/make-client-session.js +10 -7
  125. package/dist/make-client-session.js.map +1 -1
  126. package/dist/materializer-helper.d.ts +6 -7
  127. package/dist/materializer-helper.d.ts.map +1 -1
  128. package/dist/materializer-helper.js +22 -40
  129. package/dist/materializer-helper.js.map +1 -1
  130. package/dist/otel.d.ts +4 -4
  131. package/dist/otel.d.ts.map +1 -1
  132. package/dist/otel.js +8 -8
  133. package/dist/otel.js.map +1 -1
  134. package/dist/rematerialize-from-eventlog.d.ts +3 -3
  135. package/dist/rematerialize-from-eventlog.d.ts.map +1 -1
  136. package/dist/rematerialize-from-eventlog.js +52 -39
  137. package/dist/rematerialize-from-eventlog.js.map +1 -1
  138. package/dist/schema/EventDef/define.d.ts +161 -0
  139. package/dist/schema/EventDef/define.d.ts.map +1 -0
  140. package/dist/schema/EventDef/define.js +140 -0
  141. package/dist/schema/EventDef/define.js.map +1 -0
  142. package/dist/schema/EventDef/define.test.d.ts +2 -0
  143. package/dist/schema/EventDef/define.test.d.ts.map +1 -0
  144. package/dist/schema/EventDef/define.test.js +23 -0
  145. package/dist/schema/EventDef/define.test.js.map +1 -0
  146. package/dist/schema/EventDef/deprecated.d.ts +94 -0
  147. package/dist/schema/EventDef/deprecated.d.ts.map +1 -0
  148. package/dist/schema/EventDef/deprecated.js +143 -0
  149. package/dist/schema/EventDef/deprecated.js.map +1 -0
  150. package/dist/schema/EventDef/deprecated.test.d.ts +2 -0
  151. package/dist/schema/EventDef/deprecated.test.d.ts.map +1 -0
  152. package/dist/schema/EventDef/deprecated.test.js +95 -0
  153. package/dist/schema/EventDef/deprecated.test.js.map +1 -0
  154. package/dist/schema/EventDef/event-def.d.ts +110 -0
  155. package/dist/schema/EventDef/event-def.d.ts.map +1 -0
  156. package/dist/schema/EventDef/event-def.js +2 -0
  157. package/dist/schema/EventDef/event-def.js.map +1 -0
  158. package/dist/schema/EventDef/facts.d.ts +118 -0
  159. package/dist/schema/EventDef/facts.d.ts.map +1 -0
  160. package/dist/schema/EventDef/facts.js +53 -0
  161. package/dist/schema/EventDef/facts.js.map +1 -0
  162. package/dist/schema/EventDef/materializer.d.ts +155 -0
  163. package/dist/schema/EventDef/materializer.d.ts.map +1 -0
  164. package/dist/schema/EventDef/materializer.js +83 -0
  165. package/dist/schema/EventDef/materializer.js.map +1 -0
  166. package/dist/schema/EventDef/mod.d.ts +6 -0
  167. package/dist/schema/EventDef/mod.d.ts.map +1 -0
  168. package/dist/schema/EventDef/mod.js +6 -0
  169. package/dist/schema/EventDef/mod.js.map +1 -0
  170. package/dist/schema/EventSequenceNumber/client.d.ts +136 -0
  171. package/dist/schema/EventSequenceNumber/client.d.ts.map +1 -0
  172. package/dist/schema/EventSequenceNumber/client.js +192 -0
  173. package/dist/schema/EventSequenceNumber/client.js.map +1 -0
  174. package/dist/schema/EventSequenceNumber/global.d.ts +15 -0
  175. package/dist/schema/EventSequenceNumber/global.d.ts.map +1 -0
  176. package/dist/schema/EventSequenceNumber/global.js +14 -0
  177. package/dist/schema/EventSequenceNumber/global.js.map +1 -0
  178. package/dist/schema/EventSequenceNumber/mod.d.ts +37 -0
  179. package/dist/schema/EventSequenceNumber/mod.d.ts.map +1 -0
  180. package/dist/schema/EventSequenceNumber/mod.js +37 -0
  181. package/dist/schema/EventSequenceNumber/mod.js.map +1 -0
  182. package/dist/schema/EventSequenceNumber.test.js +44 -44
  183. package/dist/schema/EventSequenceNumber.test.js.map +1 -1
  184. package/dist/schema/LiveStoreEvent/client.d.ts +168 -0
  185. package/dist/schema/LiveStoreEvent/client.d.ts.map +1 -0
  186. package/dist/schema/LiveStoreEvent/client.js +179 -0
  187. package/dist/schema/LiveStoreEvent/client.js.map +1 -0
  188. package/dist/schema/LiveStoreEvent/client.test.d.ts +2 -0
  189. package/dist/schema/LiveStoreEvent/client.test.d.ts.map +1 -0
  190. package/dist/schema/LiveStoreEvent/client.test.js +120 -0
  191. package/dist/schema/LiveStoreEvent/client.test.js.map +1 -0
  192. package/dist/schema/LiveStoreEvent/for-event-def.d.ts +52 -0
  193. package/dist/schema/LiveStoreEvent/for-event-def.d.ts.map +1 -0
  194. package/dist/schema/LiveStoreEvent/for-event-def.js +2 -0
  195. package/dist/schema/LiveStoreEvent/for-event-def.js.map +1 -0
  196. package/dist/schema/LiveStoreEvent/global.d.ts +36 -0
  197. package/dist/schema/LiveStoreEvent/global.d.ts.map +1 -0
  198. package/dist/schema/LiveStoreEvent/global.js +31 -0
  199. package/dist/schema/LiveStoreEvent/global.js.map +1 -0
  200. package/dist/schema/LiveStoreEvent/input.d.ts +46 -0
  201. package/dist/schema/LiveStoreEvent/input.d.ts.map +1 -0
  202. package/dist/schema/LiveStoreEvent/input.js +26 -0
  203. package/dist/schema/LiveStoreEvent/input.js.map +1 -0
  204. package/dist/schema/LiveStoreEvent/mod.d.ts +5 -0
  205. package/dist/schema/LiveStoreEvent/mod.d.ts.map +1 -0
  206. package/dist/schema/LiveStoreEvent/mod.js +5 -0
  207. package/dist/schema/LiveStoreEvent/mod.js.map +1 -0
  208. package/dist/schema/events.d.ts +1 -1
  209. package/dist/schema/events.d.ts.map +1 -1
  210. package/dist/schema/events.js +1 -1
  211. package/dist/schema/events.js.map +1 -1
  212. package/dist/schema/mod.d.ts +7 -4
  213. package/dist/schema/mod.d.ts.map +1 -1
  214. package/dist/schema/mod.js +6 -4
  215. package/dist/schema/mod.js.map +1 -1
  216. package/dist/schema/schema.d.ts +16 -1
  217. package/dist/schema/schema.d.ts.map +1 -1
  218. package/dist/schema/schema.js +34 -5
  219. package/dist/schema/schema.js.map +1 -1
  220. package/dist/schema/state/sqlite/client-document-def.d.ts +14 -18
  221. package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -1
  222. package/dist/schema/state/sqlite/client-document-def.js +54 -30
  223. package/dist/schema/state/sqlite/client-document-def.js.map +1 -1
  224. package/dist/schema/state/sqlite/client-document-def.test.js +146 -6
  225. package/dist/schema/state/sqlite/client-document-def.test.js.map +1 -1
  226. package/dist/schema/state/sqlite/column-annotations.d.ts +13 -13
  227. package/dist/schema/state/sqlite/column-annotations.d.ts.map +1 -1
  228. package/dist/schema/state/sqlite/column-annotations.js +13 -15
  229. package/dist/schema/state/sqlite/column-annotations.js.map +1 -1
  230. package/dist/schema/state/sqlite/column-annotations.test.js +19 -43
  231. package/dist/schema/state/sqlite/column-annotations.test.js.map +1 -1
  232. package/dist/schema/state/sqlite/column-def.d.ts +1 -1
  233. package/dist/schema/state/sqlite/column-def.d.ts.map +1 -1
  234. package/dist/schema/state/sqlite/column-def.js +132 -57
  235. package/dist/schema/state/sqlite/column-def.js.map +1 -1
  236. package/dist/schema/state/sqlite/column-def.test.js +155 -70
  237. package/dist/schema/state/sqlite/column-def.test.js.map +1 -1
  238. package/dist/schema/state/sqlite/column-spec.d.ts.map +1 -1
  239. package/dist/schema/state/sqlite/column-spec.js +30 -12
  240. package/dist/schema/state/sqlite/column-spec.js.map +1 -1
  241. package/dist/schema/state/sqlite/column-spec.test.js +26 -17
  242. package/dist/schema/state/sqlite/column-spec.test.js.map +1 -1
  243. package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts +2 -2
  244. package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts.map +1 -1
  245. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +15 -6
  246. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js.map +1 -1
  247. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts +25 -19
  248. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts.map +1 -1
  249. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js +22 -16
  250. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js.map +1 -1
  251. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js +28 -17
  252. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js.map +1 -1
  253. package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts +14 -12
  254. package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts.map +1 -1
  255. package/dist/schema/state/sqlite/db-schema/dsl/mod.js +6 -4
  256. package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -1
  257. package/dist/schema/state/sqlite/mod.d.ts +3 -5
  258. package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
  259. package/dist/schema/state/sqlite/mod.js +6 -8
  260. package/dist/schema/state/sqlite/mod.js.map +1 -1
  261. package/dist/schema/state/sqlite/persistence.d.ts +11 -0
  262. package/dist/schema/state/sqlite/persistence.d.ts.map +1 -0
  263. package/dist/schema/state/sqlite/persistence.js +10 -0
  264. package/dist/schema/state/sqlite/persistence.js.map +1 -0
  265. package/dist/schema/state/sqlite/query-builder/api.d.ts +58 -29
  266. package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -1
  267. package/dist/schema/state/sqlite/query-builder/astToSql.d.ts.map +1 -1
  268. package/dist/schema/state/sqlite/query-builder/astToSql.js +105 -24
  269. package/dist/schema/state/sqlite/query-builder/astToSql.js.map +1 -1
  270. package/dist/schema/state/sqlite/query-builder/impl.d.ts +4 -2
  271. package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -1
  272. package/dist/schema/state/sqlite/query-builder/impl.js +42 -27
  273. package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -1
  274. package/dist/schema/state/sqlite/query-builder/impl.test.js +402 -498
  275. package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -1
  276. package/dist/schema/state/sqlite/schema-helpers.d.ts +2 -2
  277. package/dist/schema/state/sqlite/schema-helpers.d.ts.map +1 -1
  278. package/dist/schema/state/sqlite/schema-helpers.js +24 -14
  279. package/dist/schema/state/sqlite/schema-helpers.js.map +1 -1
  280. package/dist/schema/state/sqlite/schema-helpers.test.d.ts +2 -0
  281. package/dist/schema/state/sqlite/schema-helpers.test.d.ts.map +1 -0
  282. package/dist/schema/state/sqlite/schema-helpers.test.js +36 -0
  283. package/dist/schema/state/sqlite/schema-helpers.test.js.map +1 -0
  284. package/dist/schema/state/sqlite/system-tables/eventlog-tables.d.ts +515 -0
  285. package/dist/schema/state/sqlite/system-tables/eventlog-tables.d.ts.map +1 -0
  286. package/dist/schema/state/sqlite/system-tables/eventlog-tables.js +54 -0
  287. package/dist/schema/state/sqlite/system-tables/eventlog-tables.js.map +1 -0
  288. package/dist/schema/state/sqlite/system-tables/mod.d.ts +3 -0
  289. package/dist/schema/state/sqlite/system-tables/mod.d.ts.map +1 -0
  290. package/dist/schema/state/sqlite/system-tables/mod.js +3 -0
  291. package/dist/schema/state/sqlite/system-tables/mod.js.map +1 -0
  292. package/dist/schema/state/sqlite/system-tables/state-tables.d.ts +678 -0
  293. package/dist/schema/state/sqlite/system-tables/state-tables.d.ts.map +1 -0
  294. package/dist/schema/state/sqlite/system-tables/state-tables.js +87 -0
  295. package/dist/schema/state/sqlite/system-tables/state-tables.js.map +1 -0
  296. package/dist/schema/state/sqlite/table-def.d.ts +19 -20
  297. package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
  298. package/dist/schema/state/sqlite/table-def.js +54 -4
  299. package/dist/schema/state/sqlite/table-def.js.map +1 -1
  300. package/dist/schema/state/sqlite/table-def.test.js +226 -20
  301. package/dist/schema/state/sqlite/table-def.test.js.map +1 -1
  302. package/dist/schema/unknown-events.d.ts +47 -0
  303. package/dist/schema/unknown-events.d.ts.map +1 -0
  304. package/dist/schema/unknown-events.js +70 -0
  305. package/dist/schema/unknown-events.js.map +1 -0
  306. package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.d.ts +2 -0
  307. package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.d.ts.map +1 -0
  308. package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js +73 -0
  309. package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js.map +1 -0
  310. package/dist/schema-management/common.js +2 -2
  311. package/dist/schema-management/common.js.map +1 -1
  312. package/dist/schema-management/migrations.d.ts +32 -2
  313. package/dist/schema-management/migrations.d.ts.map +1 -1
  314. package/dist/schema-management/migrations.js +38 -6
  315. package/dist/schema-management/migrations.js.map +1 -1
  316. package/dist/schema-management/validate-schema.d.ts +3 -3
  317. package/dist/schema-management/validate-schema.d.ts.map +1 -1
  318. package/dist/schema-management/validate-schema.js +2 -2
  319. package/dist/schema-management/validate-schema.js.map +1 -1
  320. package/dist/session-id-symbol.d.ts +29 -0
  321. package/dist/session-id-symbol.d.ts.map +1 -0
  322. package/dist/session-id-symbol.js +44 -0
  323. package/dist/session-id-symbol.js.map +1 -0
  324. package/dist/sql-queries/sql-queries.d.ts.map +1 -1
  325. package/dist/sql-queries/sql-queries.js +26 -16
  326. package/dist/sql-queries/sql-queries.js.map +1 -1
  327. package/dist/sql-queries/sql-query-builder.d.ts.map +1 -1
  328. package/dist/sql-queries/sql-query-builder.js.map +1 -1
  329. package/dist/sql-queries/types.d.ts +6 -7
  330. package/dist/sql-queries/types.d.ts.map +1 -1
  331. package/dist/sql-queries/types.js.map +1 -1
  332. package/dist/sqlite-db-helper.d.ts +21 -1
  333. package/dist/sqlite-db-helper.d.ts.map +1 -1
  334. package/dist/sqlite-db-helper.js +40 -5
  335. package/dist/sqlite-db-helper.js.map +1 -1
  336. package/dist/sqlite-types.d.ts +8 -11
  337. package/dist/sqlite-types.d.ts.map +1 -1
  338. package/dist/sqlite-types.js +3 -3
  339. package/dist/sqlite-types.js.map +1 -1
  340. package/dist/sync/ClientSessionSyncProcessor.d.ts +47 -25
  341. package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
  342. package/dist/sync/ClientSessionSyncProcessor.js +269 -154
  343. package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
  344. package/dist/sync/errors.d.ts +12 -51
  345. package/dist/sync/errors.d.ts.map +1 -1
  346. package/dist/sync/errors.js +7 -24
  347. package/dist/sync/errors.js.map +1 -1
  348. package/dist/sync/index.d.ts +2 -0
  349. package/dist/sync/index.d.ts.map +1 -1
  350. package/dist/sync/index.js +2 -0
  351. package/dist/sync/index.js.map +1 -1
  352. package/dist/sync/mock-sync-backend.d.ts +11 -10
  353. package/dist/sync/mock-sync-backend.d.ts.map +1 -1
  354. package/dist/sync/mock-sync-backend.js +97 -74
  355. package/dist/sync/mock-sync-backend.js.map +1 -1
  356. package/dist/sync/mock-sync-backend.test.d.ts +2 -0
  357. package/dist/sync/mock-sync-backend.test.d.ts.map +1 -0
  358. package/dist/sync/mock-sync-backend.test.js +49 -0
  359. package/dist/sync/mock-sync-backend.test.js.map +1 -0
  360. package/dist/sync/next/compact-events.d.ts.map +1 -1
  361. package/dist/sync/next/compact-events.js +11 -12
  362. package/dist/sync/next/compact-events.js.map +1 -1
  363. package/dist/sync/next/facts.d.ts +5 -5
  364. package/dist/sync/next/facts.d.ts.map +1 -1
  365. package/dist/sync/next/facts.js +7 -8
  366. package/dist/sync/next/facts.js.map +1 -1
  367. package/dist/sync/next/history-dag-common.d.ts +58 -15
  368. package/dist/sync/next/history-dag-common.d.ts.map +1 -1
  369. package/dist/sync/next/history-dag-common.js +219 -9
  370. package/dist/sync/next/history-dag-common.js.map +1 -1
  371. package/dist/sync/next/history-dag.d.ts.map +1 -1
  372. package/dist/sync/next/history-dag.js +12 -11
  373. package/dist/sync/next/history-dag.js.map +1 -1
  374. package/dist/sync/next/rebase-events.d.ts +5 -5
  375. package/dist/sync/next/rebase-events.d.ts.map +1 -1
  376. package/dist/sync/next/rebase-events.js +6 -6
  377. package/dist/sync/next/rebase-events.js.map +1 -1
  378. package/dist/sync/next/test/compact-events.calculator.test.js +4 -4
  379. package/dist/sync/next/test/compact-events.calculator.test.js.map +1 -1
  380. package/dist/sync/next/test/compact-events.test.d.ts.map +1 -1
  381. package/dist/sync/next/test/compact-events.test.js +2 -2
  382. package/dist/sync/next/test/compact-events.test.js.map +1 -1
  383. package/dist/sync/next/test/event-fixtures.d.ts +9 -9
  384. package/dist/sync/next/test/event-fixtures.d.ts.map +1 -1
  385. package/dist/sync/next/test/event-fixtures.js +12 -12
  386. package/dist/sync/next/test/event-fixtures.js.map +1 -1
  387. package/dist/sync/next/test/history-dag-common.test.d.ts +2 -0
  388. package/dist/sync/next/test/history-dag-common.test.d.ts.map +1 -0
  389. package/dist/sync/next/test/history-dag-common.test.js +73 -0
  390. package/dist/sync/next/test/history-dag-common.test.js.map +1 -0
  391. package/dist/sync/sync-backend-kv.d.ts +3 -3
  392. package/dist/sync/sync-backend-kv.d.ts.map +1 -1
  393. package/dist/sync/sync-backend-kv.js +6 -4
  394. package/dist/sync/sync-backend-kv.js.map +1 -1
  395. package/dist/sync/sync-backend.d.ts +41 -21
  396. package/dist/sync/sync-backend.d.ts.map +1 -1
  397. package/dist/sync/sync-backend.js +45 -4
  398. package/dist/sync/sync-backend.js.map +1 -1
  399. package/dist/sync/sync.d.ts +23 -2
  400. package/dist/sync/sync.d.ts.map +1 -1
  401. package/dist/sync/syncstate.d.ts +115 -191
  402. package/dist/sync/syncstate.d.ts.map +1 -1
  403. package/dist/sync/syncstate.js +108 -129
  404. package/dist/sync/syncstate.js.map +1 -1
  405. package/dist/sync/syncstate.test.js +289 -161
  406. package/dist/sync/syncstate.test.js.map +1 -1
  407. package/dist/sync/transport-chunking.d.ts +35 -0
  408. package/dist/sync/transport-chunking.d.ts.map +1 -0
  409. package/dist/sync/transport-chunking.js +47 -0
  410. package/dist/sync/transport-chunking.js.map +1 -0
  411. package/dist/sync/validate-push-payload.d.ts +2 -2
  412. package/dist/sync/validate-push-payload.d.ts.map +1 -1
  413. package/dist/sync/validate-push-payload.js +4 -6
  414. package/dist/sync/validate-push-payload.js.map +1 -1
  415. package/dist/testing/event-factory.d.ts +3 -3
  416. package/dist/testing/event-factory.d.ts.map +1 -1
  417. package/dist/testing/event-factory.js +5 -7
  418. package/dist/testing/event-factory.js.map +1 -1
  419. package/dist/util.d.ts +11 -4
  420. package/dist/util.d.ts.map +1 -1
  421. package/dist/util.js +21 -4
  422. package/dist/util.js.map +1 -1
  423. package/dist/version.d.ts +24 -5
  424. package/dist/version.d.ts.map +1 -1
  425. package/dist/version.js +25 -8
  426. package/dist/version.js.map +1 -1
  427. package/package.json +50 -16
  428. package/src/ClientSessionLeaderThreadProxy.ts +27 -12
  429. package/src/MaterializationJournal.ts +184 -0
  430. package/src/StateHead.ts +80 -0
  431. package/src/WorkerTransportError.ts +12 -0
  432. package/src/__tests__/fixture.ts +3 -3
  433. package/src/adapter-types.ts +62 -56
  434. package/src/bounded-collections.ts +6 -5
  435. package/src/debug-info.ts +54 -43
  436. package/src/defs.ts +2 -2
  437. package/src/devtools/devtools-compatibility.test.ts +19 -0
  438. package/src/devtools/devtools-messages-client-session.ts +72 -63
  439. package/src/devtools/devtools-messages-common.ts +18 -22
  440. package/src/devtools/devtools-messages-leader.ts +89 -68
  441. package/src/devtools/devtools-sessioninfo.ts +17 -12
  442. package/src/devtools/mod.ts +15 -7
  443. package/src/errors.ts +58 -38
  444. package/src/index.ts +11 -1
  445. package/src/leader-thread/LeaderSyncProcessor.ts +927 -674
  446. package/src/leader-thread/RejectedPushError.ts +119 -0
  447. package/src/leader-thread/connection.ts +13 -9
  448. package/src/leader-thread/eventlog.ts +114 -41
  449. package/src/leader-thread/leader-worker-devtools.ts +223 -124
  450. package/src/leader-thread/make-leader-thread-layer.test.ts +44 -0
  451. package/src/leader-thread/make-leader-thread-layer.ts +170 -82
  452. package/src/leader-thread/materialize-event.ts +51 -16
  453. package/src/leader-thread/mod.ts +1 -0
  454. package/src/leader-thread/recreate-db.ts +26 -61
  455. package/src/leader-thread/shutdown-channel.ts +3 -18
  456. package/src/leader-thread/stream-events.ts +209 -0
  457. package/src/leader-thread/types.ts +77 -65
  458. package/src/logging.ts +15 -0
  459. package/src/make-client-session.ts +16 -10
  460. package/src/materializer-helper.ts +31 -49
  461. package/src/otel.ts +15 -13
  462. package/src/rematerialize-from-eventlog.ts +106 -93
  463. package/src/schema/EventDef/define.test.ts +28 -0
  464. package/src/schema/EventDef/define.ts +218 -0
  465. package/src/schema/EventDef/deprecated.test.ts +122 -0
  466. package/src/schema/EventDef/deprecated.ts +172 -0
  467. package/src/schema/EventDef/event-def.ts +123 -0
  468. package/src/schema/EventDef/facts.ts +135 -0
  469. package/src/schema/EventDef/materializer.ts +172 -0
  470. package/src/schema/EventDef/mod.ts +5 -0
  471. package/src/schema/EventSequenceNumber/client.ts +256 -0
  472. package/src/schema/EventSequenceNumber/global.ts +19 -0
  473. package/src/schema/EventSequenceNumber/mod.ts +37 -0
  474. package/src/schema/EventSequenceNumber.test.ts +72 -53
  475. package/src/schema/LiveStoreEvent/client.test.ts +142 -0
  476. package/src/schema/LiveStoreEvent/client.ts +236 -0
  477. package/src/schema/LiveStoreEvent/for-event-def.ts +60 -0
  478. package/src/schema/LiveStoreEvent/global.ts +45 -0
  479. package/src/schema/LiveStoreEvent/input.ts +63 -0
  480. package/src/schema/LiveStoreEvent/mod.ts +4 -0
  481. package/src/schema/events.ts +1 -1
  482. package/src/schema/mod.ts +7 -4
  483. package/src/schema/schema.ts +48 -6
  484. package/src/schema/state/sqlite/client-document-def.test.ts +172 -14
  485. package/src/schema/state/sqlite/client-document-def.ts +126 -98
  486. package/src/schema/state/sqlite/column-annotations.test.ts +27 -45
  487. package/src/schema/state/sqlite/column-annotations.ts +27 -30
  488. package/src/schema/state/sqlite/column-def.test.ts +186 -77
  489. package/src/schema/state/sqlite/column-def.ts +169 -62
  490. package/src/schema/state/sqlite/column-spec.test.ts +34 -19
  491. package/src/schema/state/sqlite/column-spec.ts +38 -11
  492. package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +17 -7
  493. package/src/schema/state/sqlite/db-schema/dsl/__snapshots__/field-defs.test.ts.snap +123 -63
  494. package/src/schema/state/sqlite/db-schema/dsl/field-defs.test.ts +37 -17
  495. package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +65 -45
  496. package/src/schema/state/sqlite/db-schema/dsl/mod.ts +48 -40
  497. package/src/schema/state/sqlite/mod.ts +9 -12
  498. package/src/schema/state/sqlite/persistence.ts +11 -0
  499. package/src/schema/state/sqlite/query-builder/api.ts +61 -23
  500. package/src/schema/state/sqlite/query-builder/astToSql.ts +117 -23
  501. package/src/schema/state/sqlite/query-builder/impl.test.ts +489 -551
  502. package/src/schema/state/sqlite/query-builder/impl.ts +43 -26
  503. package/src/schema/state/sqlite/schema-helpers.test.ts +44 -0
  504. package/src/schema/state/sqlite/schema-helpers.ts +30 -22
  505. package/src/schema/state/sqlite/system-tables/eventlog-tables.ts +64 -0
  506. package/src/schema/state/sqlite/system-tables/mod.ts +2 -0
  507. package/src/schema/state/sqlite/system-tables/state-tables.ts +109 -0
  508. package/src/schema/state/sqlite/table-def.test.ts +295 -46
  509. package/src/schema/state/sqlite/table-def.ts +109 -64
  510. package/src/schema/unknown-events.ts +132 -0
  511. package/src/schema-management/__tests__/migrations-autoincrement-quoting.test.ts +88 -0
  512. package/src/schema-management/common.ts +2 -2
  513. package/src/schema-management/migrations.ts +42 -9
  514. package/src/schema-management/validate-schema.ts +3 -3
  515. package/src/session-id-symbol.ts +59 -0
  516. package/src/sql-queries/sql-queries.ts +26 -18
  517. package/src/sql-queries/sql-query-builder.ts +1 -0
  518. package/src/sql-queries/types.ts +6 -7
  519. package/src/sqlite-db-helper.ts +67 -5
  520. package/src/sqlite-types.ts +9 -11
  521. package/src/sync/ClientSessionSyncProcessor.ts +388 -194
  522. package/src/sync/errors.ts +18 -28
  523. package/src/sync/index.ts +2 -0
  524. package/src/sync/mock-sync-backend.test.ts +78 -0
  525. package/src/sync/mock-sync-backend.ts +196 -108
  526. package/src/sync/next/compact-events.ts +10 -11
  527. package/src/sync/next/facts.ts +13 -14
  528. package/src/sync/next/history-dag-common.ts +306 -26
  529. package/src/sync/next/history-dag.ts +20 -13
  530. package/src/sync/next/rebase-events.ts +12 -12
  531. package/src/sync/next/test/compact-events.calculator.test.ts +5 -4
  532. package/src/sync/next/test/compact-events.test.ts +4 -3
  533. package/src/sync/next/test/event-fixtures.ts +14 -14
  534. package/src/sync/next/test/history-dag-common.test.ts +87 -0
  535. package/src/sync/sync-backend-kv.ts +7 -4
  536. package/src/sync/sync-backend.ts +75 -26
  537. package/src/sync/sync.ts +24 -2
  538. package/src/sync/syncstate.test.ts +657 -442
  539. package/src/sync/syncstate.ts +175 -160
  540. package/src/sync/transport-chunking.ts +85 -0
  541. package/src/sync/validate-push-payload.ts +6 -8
  542. package/src/testing/event-factory.ts +10 -12
  543. package/src/util.ts +20 -11
  544. package/src/version.ts +32 -8
  545. package/dist/schema/EventDef.d.ts +0 -126
  546. package/dist/schema/EventDef.d.ts.map +0 -1
  547. package/dist/schema/EventDef.js +0 -46
  548. package/dist/schema/EventDef.js.map +0 -1
  549. package/dist/schema/EventSequenceNumber.d.ts +0 -80
  550. package/dist/schema/EventSequenceNumber.d.ts.map +0 -1
  551. package/dist/schema/EventSequenceNumber.js +0 -139
  552. package/dist/schema/EventSequenceNumber.js.map +0 -1
  553. package/dist/schema/LiveStoreEvent.d.ts +0 -306
  554. package/dist/schema/LiveStoreEvent.d.ts.map +0 -1
  555. package/dist/schema/LiveStoreEvent.js +0 -147
  556. package/dist/schema/LiveStoreEvent.js.map +0 -1
  557. package/dist/schema/state/sqlite/system-tables.d.ts +0 -976
  558. package/dist/schema/state/sqlite/system-tables.d.ts.map +0 -1
  559. package/dist/schema/state/sqlite/system-tables.js +0 -81
  560. package/dist/schema/state/sqlite/system-tables.js.map +0 -1
  561. package/dist/sql-queries/misc.d.ts +0 -2
  562. package/dist/sql-queries/misc.d.ts.map +0 -1
  563. package/dist/sql-queries/misc.js +0 -2
  564. package/dist/sql-queries/misc.js.map +0 -1
  565. package/dist/sync/next/graphology.d.ts +0 -8
  566. package/dist/sync/next/graphology.d.ts.map +0 -1
  567. package/dist/sync/next/graphology.js +0 -30
  568. package/dist/sync/next/graphology.js.map +0 -1
  569. package/dist/sync/next/graphology_.d.ts +0 -3
  570. package/dist/sync/next/graphology_.d.ts.map +0 -1
  571. package/dist/sync/next/graphology_.js +0 -3
  572. package/dist/sync/next/graphology_.js.map +0 -1
  573. package/src/schema/EventDef.ts +0 -222
  574. package/src/schema/EventSequenceNumber.ts +0 -199
  575. package/src/schema/LiveStoreEvent.ts +0 -286
  576. package/src/schema/state/sqlite/system-tables.ts +0 -106
  577. package/src/sql-queries/misc.ts +0 -2
  578. package/src/sync/next/ambient.d.ts +0 -3
  579. package/src/sync/next/graphology.ts +0 -41
  580. package/src/sync/next/graphology_.ts +0 -2
@@ -1,5 +1,6 @@
1
- import { Schema } from '@livestore/utils/effect';
1
+ import { TestSchema } from 'effect/testing';
2
2
  import { describe, expect, it } from 'vitest';
3
+ import { Schema, SchemaTransformation } from '@livestore/utils/effect';
3
4
  import { State } from "../../../mod.js";
4
5
  import { getResultSchema } from "./impl.js";
5
6
  const todos = State.SQLite.table({
@@ -8,7 +9,7 @@ const todos = State.SQLite.table({
8
9
  id: State.SQLite.text({ primaryKey: true }),
9
10
  text: State.SQLite.text({ default: '', nullable: false }),
10
11
  completed: State.SQLite.boolean({ default: false, nullable: false }),
11
- status: State.SQLite.text({ schema: Schema.Literal('active', 'completed') }),
12
+ status: State.SQLite.text({ schema: Schema.Literals(['active', 'completed']) }),
12
13
  deletedAt: State.SQLite.datetime({ nullable: true }),
13
14
  // TODO consider leaning more into Effect schema
14
15
  // other: Schema.Number.pipe(State.SQLite.asInteger),
@@ -19,7 +20,7 @@ const todosWithIntId = State.SQLite.table({
19
20
  columns: {
20
21
  id: State.SQLite.integer({ primaryKey: true }),
21
22
  text: State.SQLite.text({ default: '', nullable: false }),
22
- status: State.SQLite.text({ schema: Schema.Literal('active', 'completed') }),
23
+ status: State.SQLite.text({ schema: Schema.Literals(['active', 'completed']) }),
23
24
  },
24
25
  });
25
26
  const comments = State.SQLite.table({
@@ -33,14 +34,14 @@ const comments = State.SQLite.table({
33
34
  const UiState = State.SQLite.clientDocument({
34
35
  name: 'UiState',
35
36
  schema: Schema.Struct({
36
- filter: Schema.Literal('all', 'active', 'completed'),
37
+ filter: Schema.Literals(['all', 'active', 'completed']),
37
38
  }),
38
39
  default: { value: { filter: 'all' } },
39
40
  });
40
41
  const UiStateWithDefaultId = State.SQLite.clientDocument({
41
42
  name: 'UiState',
42
43
  schema: Schema.Struct({
43
- filter: Schema.Literal('all', 'active', 'completed'),
44
+ filter: Schema.Literals(['all', 'active', 'completed']),
44
45
  }),
45
46
  default: {
46
47
  id: 'static',
@@ -53,10 +54,10 @@ const issue = State.SQLite.table({
53
54
  id: State.SQLite.integer({ primaryKey: true }),
54
55
  title: State.SQLite.text({ default: '' }),
55
56
  creator: State.SQLite.text({ default: '' }),
56
- priority: State.SQLite.integer({ schema: Schema.Literal(0, 1, 2, 3, 4), default: 0 }),
57
- created: State.SQLite.integer({ schema: Schema.DateFromNumber }),
58
- deleted: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
59
- modified: State.SQLite.integer({ schema: Schema.DateFromNumber }),
57
+ priority: State.SQLite.integer({ schema: Schema.Literals([0, 1, 2, 3, 4]), default: 0 }),
58
+ created: State.SQLite.integer({ schema: Schema.DateFromMillis }),
59
+ deleted: State.SQLite.integer({ nullable: true, schema: Schema.DateFromMillis }),
60
+ modified: State.SQLite.integer({ schema: Schema.DateFromMillis }),
60
61
  kanbanorder: State.SQLite.text({ nullable: false, default: '' }),
61
62
  },
62
63
  indexes: [
@@ -64,323 +65,307 @@ const issue = State.SQLite.table({
64
65
  { name: 'issue_created', columns: ['created'] },
65
66
  ],
66
67
  });
67
- const db = { todos, todosWithIntId, comments, issue, UiState, UiStateWithDefaultId };
68
- const dump = (qb) => ({
69
- bindValues: qb.asSql().bindValues,
70
- query: qb.asSql().query,
71
- schema: getResultSchema(qb).toString(),
68
+ const selections = State.SQLite.table({
69
+ name: 'selections',
70
+ columns: {
71
+ id: State.SQLite.integer({ primaryKey: true }),
72
+ group: State.SQLite.text({}),
73
+ },
74
+ });
75
+ const Source = Schema.Literals(['google', 'linkedin', 'facebook']);
76
+ const ProfileAttribute = Schema.Struct({ key: Schema.String, value: Schema.String });
77
+ const personProfiles = State.SQLite.table({
78
+ name: 'person_profiles',
79
+ columns: {
80
+ personId: State.SQLite.text({ primaryKey: true }),
81
+ sources: State.SQLite.json({ schema: Schema.Array(Source), default: [] }),
82
+ tags: State.SQLite.json({ schema: Schema.Array(Schema.String), default: [] }),
83
+ attributes: State.SQLite.json({ schema: Schema.Array(ProfileAttribute), default: [] }),
84
+ /** Nullable JSON array column for testing JSON_CONTAINS on nullable columns */
85
+ optionalTags: State.SQLite.json({ schema: Schema.Array(Schema.String), nullable: true }),
86
+ },
72
87
  });
88
+ const db = { todos, todosWithIntId, comments, issue, selections, UiState, UiStateWithDefaultId, personProfiles };
73
89
  describe('query builder', () => {
74
90
  describe('basic queries', () => {
75
91
  it('should handle simple SELECT queries', () => {
76
- expect(dump(db.todos)).toMatchInlineSnapshot(`
77
- {
78
- "bindValues": [],
79
- "query": "SELECT * FROM 'todos'",
80
- "schema": "ReadonlyArray<todos>",
81
- }
82
- `);
83
- expect(dump(db.todos.select('id'))).toMatchInlineSnapshot(`
84
- {
85
- "bindValues": [],
86
- "query": "SELECT id FROM 'todos'",
87
- "schema": "ReadonlyArray<({ readonly id: string } <-> string)>",
88
- }
89
- `);
90
- expect(dump(db.todos.select('id', 'text'))).toMatchInlineSnapshot(`
91
- {
92
- "bindValues": [],
93
- "query": "SELECT id, text FROM 'todos'",
94
- "schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
95
- }
96
- `);
92
+ expect(db.todos.asSql()).toEqual({
93
+ bindValues: [],
94
+ query: "SELECT * FROM 'todos'",
95
+ usedTables: new Set(['todos']),
96
+ });
97
+ expect(db.todos.select('id').asSql()).toEqual({
98
+ bindValues: [],
99
+ query: 'SELECT "id" FROM \'todos\'',
100
+ usedTables: new Set(['todos']),
101
+ });
102
+ expect(db.todos.select('id', 'text').asSql()).toEqual({
103
+ bindValues: [],
104
+ query: 'SELECT "id", "text" FROM \'todos\'',
105
+ usedTables: new Set(['todos']),
106
+ });
107
+ });
108
+ it('derives result schemas for SELECT queries', async () => {
109
+ const selectedRows = new TestSchema.Asserts(getResultSchema(db.todos.select('id', 'text')));
110
+ await selectedRows.decoding().succeed([{ id: '123', text: 'Buy milk' }]);
111
+ const selectedColumn = new TestSchema.Asserts(getResultSchema(db.todos.select('id')));
112
+ await selectedColumn.decoding().succeed([{ id: '123' }], ['123']);
97
113
  });
98
114
  it('should handle .first()', () => {
99
- expect(dump(db.todos.select('id', 'text').first())).toMatchInlineSnapshot(`
100
- {
101
- "bindValues": [
102
- 1,
103
- ],
104
- "query": "SELECT id, text FROM 'todos' LIMIT ?",
105
- "schema": "(ReadonlyArray<{ readonly id: string; readonly text: string } | undefined> <-> { readonly id: string; readonly text: string } | undefined)",
106
- }
107
- `);
108
- expect(dump(db.todos.select('id', 'text').first({ behaviour: 'error' }))).toMatchInlineSnapshot(`
109
- {
110
- "bindValues": [
111
- 1,
112
- ],
113
- "query": "SELECT id, text FROM 'todos' LIMIT ?",
114
- "schema": "(ReadonlyArray<{ readonly id: string; readonly text: string }> <-> { readonly id: string; readonly text: string })",
115
- }
116
- `);
117
- expect(dump(db.todos.select('id', 'text').first({ behaviour: 'fallback', fallback: () => undefined }))).toMatchInlineSnapshot(`
118
- {
119
- "bindValues": [
120
- 1,
121
- ],
122
- "query": "SELECT id, text FROM 'todos' LIMIT ?",
123
- "schema": "(ReadonlyArray<{ readonly id: string; readonly text: string }> | readonly [undefined] <-> { readonly id: string; readonly text: string } | undefined)",
124
- }
125
- `);
115
+ expect(db.todos.select('id', 'text').first().asSql()).toEqual({
116
+ bindValues: [1],
117
+ query: 'SELECT "id", "text" FROM \'todos\' LIMIT ?',
118
+ usedTables: new Set(['todos']),
119
+ });
120
+ expect(db.todos.select('id', 'text').first({ behaviour: 'error' }).asSql()).toEqual({
121
+ bindValues: [1],
122
+ query: 'SELECT "id", "text" FROM \'todos\' LIMIT ?',
123
+ usedTables: new Set(['todos']),
124
+ });
125
+ expect(db.todos
126
+ .select('id', 'text')
127
+ .first({ behaviour: 'fallback', fallback: () => undefined })
128
+ .asSql()).toEqual({
129
+ bindValues: [1],
130
+ query: 'SELECT "id", "text" FROM \'todos\' LIMIT ?',
131
+ usedTables: new Set(['todos']),
132
+ });
133
+ });
134
+ it('derives result schemas for .first()', async () => {
135
+ const firstOrUndefined = new TestSchema.Asserts(getResultSchema(db.todos.select('id', 'text').first()));
136
+ await firstOrUndefined.decoding().succeed([], undefined);
137
+ await firstOrUndefined.decoding().succeed([{ id: '123', text: 'Buy milk' }], { id: '123', text: 'Buy milk' });
138
+ const firstOrError = new TestSchema.Asserts(getResultSchema(db.todos.select('id', 'text').first({ behaviour: 'error' })));
139
+ await firstOrError.decoding().succeed([{ id: '123', text: 'Buy milk' }], { id: '123', text: 'Buy milk' });
140
+ await firstOrError.decoding().fail([], 'Unable to retrieve the first element of an empty array');
141
+ const firstOrFallback = new TestSchema.Asserts(getResultSchema(db.todos.select('id', 'text').first({ behaviour: 'fallback', fallback: () => undefined })));
142
+ await firstOrFallback.decoding().succeed([], undefined);
126
143
  });
127
144
  it('should handle WHERE clauses', () => {
128
- expect(dump(db.todos.select('id', 'text').where('completed', true))).toMatchInlineSnapshot(`
129
- {
130
- "bindValues": [
131
- 1,
132
- ],
133
- "query": "SELECT id, text FROM 'todos' WHERE completed = ?",
134
- "schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
135
- }
136
- `);
137
- expect(dump(db.todos.select('id', 'text').where('completed', '!=', true))).toMatchInlineSnapshot(`
138
- {
139
- "bindValues": [
140
- 1,
141
- ],
142
- "query": "SELECT id, text FROM 'todos' WHERE completed != ?",
143
- "schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
144
- }
145
- `);
146
- expect(dump(db.todos.select('id', 'text').where({ completed: true }))).toMatchInlineSnapshot(`
147
- {
148
- "bindValues": [
149
- 1,
150
- ],
151
- "query": "SELECT id, text FROM 'todos' WHERE completed = ?",
152
- "schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
153
- }
154
- `);
155
- expect(dump(db.todos.select('id', 'text').where({ completed: undefined }))).toMatchInlineSnapshot(`
156
- {
157
- "bindValues": [],
158
- "query": "SELECT id, text FROM 'todos'",
159
- "schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
160
- }
161
- `);
162
- expect(dump(db.todos.select('id', 'text').where({ deletedAt: { op: '<=', value: new Date('2024-01-01') } }))).toMatchInlineSnapshot(`
163
- {
164
- "bindValues": [
165
- "2024-01-01T00:00:00.000Z",
166
- ],
167
- "query": "SELECT id, text FROM 'todos' WHERE deletedAt <= ?",
168
- "schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
169
- }
170
- `);
171
- expect(dump(db.todos.select('id', 'text').where({ status: { op: 'IN', value: ['active'] } }))).toMatchInlineSnapshot(`
172
- {
173
- "bindValues": [
174
- "active",
175
- ],
176
- "query": "SELECT id, text FROM 'todos' WHERE status IN (?)",
177
- "schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
178
- }
179
- `);
180
- expect(dump(db.todos.select('id', 'text').where({ status: { op: 'NOT IN', value: ['active', 'completed'] } }))).toMatchInlineSnapshot(`
181
- {
182
- "bindValues": [
183
- "active",
184
- "completed",
185
- ],
186
- "query": "SELECT id, text FROM 'todos' WHERE status NOT IN (?, ?)",
187
- "schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
188
- }
189
- `);
190
- expect(dump(db.todos
145
+ expect(db.todos.select('id', 'text').where('completed', true).asSql()).toEqual({
146
+ bindValues: [1],
147
+ query: 'SELECT "id", "text" FROM \'todos\' WHERE "completed" = ?',
148
+ usedTables: new Set(['todos']),
149
+ });
150
+ expect(db.todos.select('id', 'text').where('completed', '!=', true).asSql()).toEqual({
151
+ bindValues: [1],
152
+ query: 'SELECT "id", "text" FROM \'todos\' WHERE "completed" != ?',
153
+ usedTables: new Set(['todos']),
154
+ });
155
+ expect(db.todos.select('id', 'text').where({ completed: true }).asSql()).toEqual({
156
+ bindValues: [1],
157
+ query: 'SELECT "id", "text" FROM \'todos\' WHERE "completed" = ?',
158
+ usedTables: new Set(['todos']),
159
+ });
160
+ expect(db.todos.select('id', 'text').where({ completed: undefined }).asSql()).toEqual({
161
+ bindValues: [],
162
+ query: 'SELECT "id", "text" FROM \'todos\'',
163
+ usedTables: new Set(['todos']),
164
+ });
165
+ expect(db.todos
166
+ .select('id', 'text')
167
+ .where({ deletedAt: { op: '<=', value: new Date('2024-01-01') } })
168
+ .asSql()).toEqual({
169
+ bindValues: ['2024-01-01T00:00:00.000Z'],
170
+ query: 'SELECT "id", "text" FROM \'todos\' WHERE "deletedAt" <= ?',
171
+ usedTables: new Set(['todos']),
172
+ });
173
+ expect(db.todos
174
+ .select('id', 'text')
175
+ .where({ status: { op: 'IN', value: ['active'] } })
176
+ .asSql()).toEqual({
177
+ bindValues: ['active'],
178
+ query: 'SELECT "id", "text" FROM \'todos\' WHERE "status" IN (?)',
179
+ usedTables: new Set(['todos']),
180
+ });
181
+ expect(db.todos
182
+ .select('id', 'text')
183
+ .where({ status: { op: 'NOT IN', value: ['active', 'completed'] } })
184
+ .asSql()).toEqual({
185
+ bindValues: ['active', 'completed'],
186
+ query: 'SELECT "id", "text" FROM \'todos\' WHERE "status" NOT IN (?, ?)',
187
+ usedTables: new Set(['todos']),
188
+ });
189
+ expect(db.todos
191
190
  .select('id', 'text')
192
191
  .where({ completed: false })
193
192
  .where({ status: { op: 'IN', value: ['active'] } })
194
- .where({ deletedAt: undefined }))).toMatchInlineSnapshot(`
195
- {
196
- "bindValues": [
197
- 0,
198
- "active",
199
- ],
200
- "query": "SELECT id, text FROM 'todos' WHERE completed = ? AND status IN (?)",
201
- "schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
202
- }
203
- `);
193
+ .where({ deletedAt: undefined })
194
+ .asSql()).toEqual({
195
+ bindValues: [0, 'active'],
196
+ query: 'SELECT "id", "text" FROM \'todos\' WHERE "completed" = ? AND "status" IN (?)',
197
+ usedTables: new Set(['todos']),
198
+ });
204
199
  });
205
200
  it('should handle OFFSET and LIMIT clauses', () => {
206
- expect(dump(db.todos.select('id', 'text').where('completed', true).offset(10).limit(10))).toMatchInlineSnapshot(`
207
- {
208
- "bindValues": [
209
- 1,
210
- 10,
211
- 10,
212
- ],
213
- "query": "SELECT id, text FROM 'todos' WHERE completed = ? OFFSET ? LIMIT ?",
214
- "schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
215
- }
216
- `);
201
+ expect(db.todos.select('id', 'text').where('completed', true).offset(10).limit(10).asSql()).toEqual({
202
+ bindValues: [1, 10, 10],
203
+ query: 'SELECT "id", "text" FROM \'todos\' WHERE "completed" = ? LIMIT ? OFFSET ?',
204
+ usedTables: new Set(['todos']),
205
+ });
217
206
  });
218
207
  it('should handle OFFSET and LIMIT clauses correctly', () => {
219
208
  // Test with both offset and limit
220
- expect(dump(db.todos.select('id', 'text').where('completed', true).offset(5).limit(10))).toMatchInlineSnapshot(`
221
- {
222
- "bindValues": [
223
- 1,
224
- 5,
225
- 10,
226
- ],
227
- "query": "SELECT id, text FROM 'todos' WHERE completed = ? OFFSET ? LIMIT ?",
228
- "schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
229
- }
230
- `);
209
+ expect(db.todos.select('id', 'text').where('completed', true).offset(5).limit(10).asSql()).toEqual({
210
+ bindValues: [1, 10, 5],
211
+ query: 'SELECT "id", "text" FROM \'todos\' WHERE "completed" = ? LIMIT ? OFFSET ?',
212
+ usedTables: new Set(['todos']),
213
+ });
231
214
  // Test with only offset
232
- expect(dump(db.todos.select('id', 'text').where('completed', true).offset(5))).toMatchInlineSnapshot(`
233
- {
234
- "bindValues": [
235
- 1,
236
- 5,
237
- ],
238
- "query": "SELECT id, text FROM 'todos' WHERE completed = ? OFFSET ?",
239
- "schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
240
- }
241
- `);
215
+ expect(db.todos.select('id', 'text').where('completed', true).offset(5).asSql()).toEqual({
216
+ bindValues: [1, 5],
217
+ query: 'SELECT "id", "text" FROM \'todos\' WHERE "completed" = ? OFFSET ?',
218
+ usedTables: new Set(['todos']),
219
+ });
242
220
  // Test with only limit
243
- expect(dump(db.todos.select('id', 'text').where('completed', true).limit(10))).toMatchInlineSnapshot(`
244
- {
245
- "bindValues": [
246
- 1,
247
- 10,
248
- ],
249
- "query": "SELECT id, text FROM 'todos' WHERE completed = ? LIMIT ?",
250
- "schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
251
- }
252
- `);
221
+ expect(db.todos.select('id', 'text').where('completed', true).limit(10).asSql()).toEqual({
222
+ bindValues: [1, 10],
223
+ query: 'SELECT "id", "text" FROM \'todos\' WHERE "completed" = ? LIMIT ?',
224
+ usedTables: new Set(['todos']),
225
+ });
253
226
  });
254
227
  it('should handle COUNT queries', () => {
255
- expect(dump(db.todos.count())).toMatchInlineSnapshot(`
256
- {
257
- "bindValues": [],
258
- "query": "SELECT COUNT(*) as count FROM 'todos'",
259
- "schema": "(ReadonlyArray<({ readonly count: number } <-> number)> <-> number)",
260
- }
261
- `);
262
- expect(dump(db.todos.count().where('completed', true))).toMatchInlineSnapshot(`
263
- {
264
- "bindValues": [
265
- 1,
266
- ],
267
- "query": "SELECT COUNT(*) as count FROM 'todos' WHERE completed = ?",
268
- "schema": "(ReadonlyArray<({ readonly count: number } <-> number)> <-> number)",
269
- }
270
- `);
271
- expect(dump(db.todos.where('completed', true).count())).toMatchInlineSnapshot(`
272
- {
273
- "bindValues": [
274
- 1,
275
- ],
276
- "query": "SELECT COUNT(*) as count FROM 'todos' WHERE completed = ?",
277
- "schema": "(ReadonlyArray<({ readonly count: number } <-> number)> <-> number)",
278
- }
279
- `);
228
+ expect(db.todos.count().asSql()).toEqual({
229
+ bindValues: [],
230
+ query: "SELECT COUNT(*) as count FROM 'todos'",
231
+ usedTables: new Set(['todos']),
232
+ });
233
+ expect(db.todos.count().where('completed', true).asSql()).toEqual({
234
+ bindValues: [1],
235
+ query: 'SELECT COUNT(*) as count FROM \'todos\' WHERE "completed" = ?',
236
+ usedTables: new Set(['todos']),
237
+ });
238
+ expect(db.todos.where('completed', true).count().asSql()).toEqual({
239
+ bindValues: [1],
240
+ query: 'SELECT COUNT(*) as count FROM \'todos\' WHERE "completed" = ?',
241
+ usedTables: new Set(['todos']),
242
+ });
243
+ });
244
+ it('derives result schemas for COUNT queries', async () => {
245
+ const countResult = new TestSchema.Asserts(getResultSchema(db.todos.count()));
246
+ await countResult.decoding().succeed([{ count: 3 }], 3);
280
247
  });
281
248
  it('should handle NULL comparisons', () => {
282
- expect(dump(db.todos.select('id', 'text').where('deletedAt', '=', null))).toMatchInlineSnapshot(`
283
- {
284
- "bindValues": [],
285
- "query": "SELECT id, text FROM 'todos' WHERE deletedAt IS NULL",
286
- "schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
287
- }
288
- `);
289
- expect(dump(db.todos.select('id', 'text').where('deletedAt', '!=', null))).toMatchInlineSnapshot(`
290
- {
291
- "bindValues": [],
292
- "query": "SELECT id, text FROM 'todos' WHERE deletedAt IS NOT NULL",
293
- "schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
294
- }
295
- `);
249
+ expect(db.todos.select('id', 'text').where('deletedAt', '=', null).asSql()).toEqual({
250
+ bindValues: [],
251
+ query: 'SELECT "id", "text" FROM \'todos\' WHERE "deletedAt" IS NULL',
252
+ usedTables: new Set(['todos']),
253
+ });
254
+ expect(db.todos.select('id', 'text').where('deletedAt', '!=', null).asSql()).toEqual({
255
+ bindValues: [],
256
+ query: 'SELECT "id", "text" FROM \'todos\' WHERE "deletedAt" IS NOT NULL',
257
+ usedTables: new Set(['todos']),
258
+ });
296
259
  });
297
260
  it('should handle orderBy', () => {
298
- expect(dump(db.todos.orderBy('completed', 'desc'))).toMatchInlineSnapshot(`
299
- {
300
- "bindValues": [],
301
- "query": "SELECT * FROM 'todos' ORDER BY completed desc",
302
- "schema": "ReadonlyArray<todos>",
303
- }
304
- `);
305
- expect(dump(db.todos.orderBy([{ col: 'completed', direction: 'desc' }]))).toMatchInlineSnapshot(`
306
- {
307
- "bindValues": [],
308
- "query": "SELECT * FROM 'todos' ORDER BY completed desc",
309
- "schema": "ReadonlyArray<todos>",
310
- }
311
- `);
312
- expect(dump(db.todos.orderBy([]))).toMatchInlineSnapshot(`
313
- {
314
- "bindValues": [],
315
- "query": "SELECT * FROM 'todos'",
316
- "schema": "ReadonlyArray<todos>",
317
- }
318
- `);
261
+ expect(db.todos.orderBy('completed', 'desc').asSql()).toEqual({
262
+ bindValues: [],
263
+ query: 'SELECT * FROM \'todos\' ORDER BY "completed" desc',
264
+ usedTables: new Set(['todos']),
265
+ });
266
+ expect(db.todos.orderBy([{ col: 'completed', direction: 'desc' }]).asSql()).toEqual({
267
+ bindValues: [],
268
+ query: 'SELECT * FROM \'todos\' ORDER BY "completed" desc',
269
+ usedTables: new Set(['todos']),
270
+ });
271
+ expect(db.todos.orderBy([]).asSql()).toEqual({
272
+ bindValues: [],
273
+ query: "SELECT * FROM 'todos'",
274
+ usedTables: new Set(['todos']),
275
+ });
276
+ });
277
+ it('should handle JSON_CONTAINS operator for JSON array columns', () => {
278
+ expect(db.personProfiles.where({ sources: { op: 'JSON_CONTAINS', value: 'google' } }).asSql()).toEqual({
279
+ bindValues: ['google'],
280
+ query: 'SELECT * FROM \'person_profiles\' WHERE EXISTS (SELECT 1 FROM json_each("sources") WHERE value = ?)',
281
+ usedTables: new Set(['person_profiles']),
282
+ });
283
+ // With select
284
+ expect(db.personProfiles
285
+ .select('personId')
286
+ .where({ sources: { op: 'JSON_CONTAINS', value: 'linkedin' } })
287
+ .asSql()).toEqual({
288
+ bindValues: ['linkedin'],
289
+ query: 'SELECT "personId" FROM \'person_profiles\' WHERE EXISTS (SELECT 1 FROM json_each("sources") WHERE value = ?)',
290
+ usedTables: new Set(['person_profiles']),
291
+ });
292
+ // With plain string array column
293
+ expect(db.personProfiles.where({ tags: { op: 'JSON_CONTAINS', value: 'important' } }).asSql()).toEqual({
294
+ bindValues: ['important'],
295
+ query: 'SELECT * FROM \'person_profiles\' WHERE EXISTS (SELECT 1 FROM json_each("tags") WHERE value = ?)',
296
+ usedTables: new Set(['person_profiles']),
297
+ });
298
+ });
299
+ it('should handle JSON_NOT_CONTAINS operator for JSON array columns', () => {
300
+ expect(db.personProfiles.where({ sources: { op: 'JSON_NOT_CONTAINS', value: 'google' } }).asSql()).toEqual({
301
+ bindValues: ['google'],
302
+ query: 'SELECT * FROM \'person_profiles\' WHERE NOT EXISTS (SELECT 1 FROM json_each("sources") WHERE value = ?)',
303
+ usedTables: new Set(['person_profiles']),
304
+ });
305
+ });
306
+ it('should JSON-stringify object elements for JSON_CONTAINS', () => {
307
+ expect(db.personProfiles
308
+ .where({
309
+ attributes: { op: 'JSON_CONTAINS', value: { key: 'language', value: 'typescript' } },
310
+ })
311
+ .asSql()).toEqual({
312
+ bindValues: ['{"key":"language","value":"typescript"}'],
313
+ query: 'SELECT * FROM \'person_profiles\' WHERE EXISTS (SELECT 1 FROM json_each("attributes") WHERE value = ?)',
314
+ usedTables: new Set(['person_profiles']),
315
+ });
316
+ });
317
+ it('should handle combining JSON_CONTAINS with other WHERE clauses', () => {
318
+ expect(db.personProfiles
319
+ .where({ sources: { op: 'JSON_CONTAINS', value: 'google' } })
320
+ .where({ sources: { op: 'JSON_NOT_CONTAINS', value: 'facebook' } })
321
+ .asSql()).toEqual({
322
+ bindValues: ['google', 'facebook'],
323
+ query: 'SELECT * FROM \'person_profiles\' WHERE EXISTS (SELECT 1 FROM json_each("sources") WHERE value = ?) AND NOT EXISTS (SELECT 1 FROM json_each("sources") WHERE value = ?)',
324
+ usedTables: new Set(['person_profiles']),
325
+ });
326
+ });
327
+ it('should handle JSON_CONTAINS on nullable JSON array columns', () => {
328
+ expect(db.personProfiles.where({ optionalTags: { op: 'JSON_CONTAINS', value: 'important' } }).asSql()).toEqual({
329
+ bindValues: ['important'],
330
+ query: 'SELECT * FROM \'person_profiles\' WHERE EXISTS (SELECT 1 FROM json_each("optionalTags") WHERE value = ?)',
331
+ usedTables: new Set(['person_profiles']),
332
+ });
333
+ // With JSON_NOT_CONTAINS
334
+ expect(db.personProfiles.where({ optionalTags: { op: 'JSON_NOT_CONTAINS', value: 'archived' } }).asSql()).toEqual({
335
+ bindValues: ['archived'],
336
+ query: 'SELECT * FROM \'person_profiles\' WHERE NOT EXISTS (SELECT 1 FROM json_each("optionalTags") WHERE value = ?)',
337
+ usedTables: new Set(['person_profiles']),
338
+ });
339
+ });
340
+ it('should throw error when using JSON_CONTAINS on non-JSON array column', () => {
341
+ expect(() =>
342
+ // Type system prevents this at compile time for non-array columns, but test runtime check
343
+ db.todos.where({ status: { op: 'JSON_CONTAINS', value: 'active' } }).asSql()).toThrow('JSON_CONTAINS operator can only be used on JSON array columns');
319
344
  });
320
345
  });
321
- // describe('getOrCreate queries', () => {
322
- // it('should handle getOrCreate queries', () => {
323
- // expect(dump(db.UiState.getOrCreate('sessionid-1'))).toMatchInlineSnapshot(`
324
- // {
325
- // "bindValues": [
326
- // "sessionid-1",
327
- // ],
328
- // "query": "SELECT * FROM 'UiState' WHERE id = ?",
329
- // "schema": "...", // TODO determine schema
330
- // }
331
- // `)
332
- // })
333
- // it('should handle getOrCreate queries with default id', () => {
334
- // expect(dump(db.UiStateWithDefaultId.getOrCreate())).toMatchInlineSnapshot(`
335
- // {
336
- // "bindValues": [],
337
- // "query": "SELECT * FROM 'UiState' WHERE id = ?",
338
- // "schema": "...", // TODO determine schema
339
- // }
340
- // `)
341
- // })
342
- // // it('should handle row queries with numbers', () => {
343
- // // expect(dump(db.todosWithIntId.getOrCreate(123, { insertValues: { status: 'active' } }))).toMatchInlineSnapshot(`
344
- // // {
345
- // // "bindValues": [
346
- // // 123,
347
- // // ],
348
- // // "query": "SELECT * FROM 'todos_with_int_id' WHERE id = ?",
349
- // // "schema": "...", // TODO determine schema
350
- // // }
351
- // // `)
352
- // // })
353
- // })
354
346
  describe('write operations', () => {
355
347
  it('should handle INSERT queries', () => {
356
- expect(dump(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }))).toMatchInlineSnapshot(`
357
- {
358
- "bindValues": [
359
- "123",
360
- "Buy milk",
361
- "active",
362
- ],
363
- "query": "INSERT INTO 'todos' (id, text, status) VALUES (?, ?, ?)",
364
- "schema": "number",
365
- }
366
- `);
348
+ expect(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).asSql()).toEqual({
349
+ bindValues: ['123', 'Buy milk', 'active'],
350
+ query: `INSERT INTO 'todos' ("id", "text", "status") VALUES (?, ?, ?)`,
351
+ usedTables: new Set(['todos']),
352
+ });
353
+ });
354
+ it('derives result schemas for write queries', async () => {
355
+ const insertResult = new TestSchema.Asserts(getResultSchema(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' })));
356
+ await insertResult.decoding().succeed(1);
367
357
  });
368
358
  it('should handle INSERT queries with undefined values', () => {
369
- expect(dump(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }))).toMatchInlineSnapshot(`
370
- {
371
- "bindValues": [
372
- "123",
373
- "Buy milk",
374
- "active",
375
- ],
376
- "query": "INSERT INTO 'todos' (id, text, status) VALUES (?, ?, ?)",
377
- "schema": "number",
378
- }
379
- `);
359
+ expect(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).asSql()).toEqual({
360
+ bindValues: ['123', 'Buy milk', 'active'],
361
+ query: 'INSERT INTO \'todos\' ("id", "text", "status") VALUES (?, ?, ?)',
362
+ usedTables: new Set(['todos']),
363
+ });
380
364
  });
381
365
  // Test helped to catch a bindValues ordering bug
382
366
  it('should handle INSERT queries (issue)', () => {
383
- expect(dump(db.issue.insert({
367
+ expect(db.issue
368
+ .insert({
384
369
  id: 1,
385
370
  title: 'Revert the user profile page',
386
371
  priority: 2,
@@ -388,214 +373,144 @@ describe('query builder', () => {
388
373
  modified: new Date('2024-12-29T17:15:20.507Z'),
389
374
  kanbanorder: 'a2',
390
375
  creator: 'John Doe',
391
- }))).toMatchInlineSnapshot(`
392
- {
393
- "bindValues": [
394
- 1,
395
- "Revert the user profile page",
396
- 2,
397
- 1722532520507,
398
- 1735492520507,
399
- "a2",
400
- "John Doe",
401
- ],
402
- "query": "INSERT INTO 'issue' (id, title, priority, created, modified, kanbanorder, creator) VALUES (?, ?, ?, ?, ?, ?, ?)",
403
- "schema": "number",
404
- }
405
- `);
376
+ })
377
+ .asSql()).toEqual({
378
+ bindValues: [1, 'Revert the user profile page', 2, 1722532520507, 1735492520507, 'a2', 'John Doe'],
379
+ query: 'INSERT INTO \'issue\' ("id", "title", "priority", "created", "modified", "kanbanorder", "creator") VALUES (?, ?, ?, ?, ?, ?, ?)',
380
+ usedTables: new Set(['issue']),
381
+ });
406
382
  });
407
383
  it('should handle UPDATE queries', () => {
408
- expect(dump(db.todos.update({ status: 'completed' }).where({ id: '123' }))).toMatchInlineSnapshot(`
409
- {
410
- "bindValues": [
411
- "completed",
412
- "123",
413
- ],
414
- "query": "UPDATE 'todos' SET status = ? WHERE id = ?",
415
- "schema": "number",
416
- }
417
- `);
384
+ expect(db.todos.update({ status: 'completed' }).where({ id: '123' }).asSql()).toEqual({
385
+ bindValues: ['completed', '123'],
386
+ query: 'UPDATE \'todos\' SET "status" = ? WHERE "id" = ?',
387
+ usedTables: new Set(['todos']),
388
+ });
418
389
  // empty update set
419
- expect(dump(db.todos.update({}).where({ id: '123' }))).toMatchInlineSnapshot(`
420
- {
421
- "bindValues": [],
422
- "query": "SELECT 1",
423
- "schema": "number",
424
- }
425
- `);
390
+ expect(db.todos.update({}).where({ id: '123' }).asSql()).toEqual({
391
+ bindValues: [],
392
+ query: 'SELECT 1',
393
+ usedTables: new Set(['todos']),
394
+ });
426
395
  });
427
396
  it('should handle UPDATE queries with undefined values', () => {
428
- expect(dump(db.todos.update({ text: 'some text' }).where({ id: '123' }))).toMatchInlineSnapshot(`
429
- {
430
- "bindValues": [
431
- "some text",
432
- "123",
433
- ],
434
- "query": "UPDATE 'todos' SET text = ? WHERE id = ?",
435
- "schema": "number",
436
- }
437
- `);
397
+ expect(db.todos.update({ text: 'some text' }).where({ id: '123' }).asSql()).toEqual({
398
+ bindValues: ['some text', '123'],
399
+ query: 'UPDATE \'todos\' SET "text" = ? WHERE "id" = ?',
400
+ usedTables: new Set(['todos']),
401
+ });
438
402
  });
439
403
  it('should handle UPDATE queries with undefined values (issue)', () => {
440
- expect(dump(db.issue.update({ priority: 2, creator: 'John Doe' }).where({ id: 1 }))).toMatchInlineSnapshot(`
441
- {
442
- "bindValues": [
443
- 2,
444
- "John Doe",
445
- 1,
446
- ],
447
- "query": "UPDATE 'issue' SET priority = ?, creator = ? WHERE id = ?",
448
- "schema": "number",
449
- }
450
- `);
404
+ expect(db.issue.update({ priority: 2, creator: 'John Doe' }).where({ id: 1 }).asSql()).toEqual({
405
+ bindValues: [2, 'John Doe', 1],
406
+ query: 'UPDATE \'issue\' SET "priority" = ?, "creator" = ? WHERE "id" = ?',
407
+ usedTables: new Set(['issue']),
408
+ });
451
409
  });
452
410
  it('should handle DELETE queries', () => {
453
- expect(dump(db.todos.delete().where({ status: 'completed' }))).toMatchInlineSnapshot(`
454
- {
455
- "bindValues": [
456
- "completed",
457
- ],
458
- "query": "DELETE FROM 'todos' WHERE status = ?",
459
- "schema": "number",
460
- }
461
- `);
411
+ expect(db.todos.delete().where({ status: 'completed' }).asSql()).toEqual({
412
+ bindValues: ['completed'],
413
+ query: 'DELETE FROM \'todos\' WHERE "status" = ?',
414
+ usedTables: new Set(['todos']),
415
+ });
462
416
  });
463
417
  it('should handle INSERT with ON CONFLICT', () => {
464
- expect(dump(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).onConflict('id', 'ignore'))).toMatchInlineSnapshot(`
465
- {
466
- "bindValues": [
467
- "123",
468
- "Buy milk",
469
- "active",
470
- ],
471
- "query": "INSERT INTO 'todos' (id, text, status) VALUES (?, ?, ?) ON CONFLICT (id) DO NOTHING",
472
- "schema": "number",
473
- }
474
- `);
475
- expect(dump(db.todos
418
+ expect(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).onConflict('id', 'ignore').asSql()).toEqual({
419
+ bindValues: ['123', 'Buy milk', 'active'],
420
+ query: 'INSERT INTO \'todos\' ("id", "text", "status") VALUES (?, ?, ?) ON CONFLICT ("id") DO NOTHING',
421
+ usedTables: new Set(['todos']),
422
+ });
423
+ expect(db.todos
476
424
  .insert({ id: '123', text: 'Buy milk', status: 'active' })
477
- .onConflict('id', 'update', { text: 'Buy soy milk', status: 'active' }))).toMatchInlineSnapshot(`
478
- {
479
- "bindValues": [
480
- "123",
481
- "Buy milk",
482
- "active",
483
- "Buy soy milk",
484
- "active",
485
- ],
486
- "query": "INSERT INTO 'todos' (id, text, status) VALUES (?, ?, ?) ON CONFLICT (id) DO UPDATE SET text = ?, status = ?",
487
- "schema": "number",
488
- }
489
- `);
490
- expect(dump(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).onConflict('id', 'replace'))).toMatchInlineSnapshot(`
491
- {
492
- "bindValues": [
493
- "123",
494
- "Buy milk",
495
- "active",
496
- ],
497
- "query": "INSERT OR REPLACE INTO 'todos' (id, text, status) VALUES (?, ?, ?)",
498
- "schema": "number",
499
- }
500
- `);
425
+ .onConflict('id', 'update', { text: 'Buy soy milk', status: 'active' })
426
+ .asSql()).toEqual({
427
+ bindValues: ['123', 'Buy milk', 'active', 'Buy soy milk', 'active'],
428
+ query: 'INSERT INTO \'todos\' ("id", "text", "status") VALUES (?, ?, ?) ON CONFLICT ("id") DO UPDATE SET "text" = ?, "status" = ?',
429
+ usedTables: new Set(['todos']),
430
+ });
431
+ expect(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).onConflict('id', 'replace').asSql()).toEqual({
432
+ bindValues: ['123', 'Buy milk', 'active'],
433
+ query: 'INSERT OR REPLACE INTO \'todos\' ("id", "text", "status") VALUES (?, ?, ?)',
434
+ usedTables: new Set(['todos']),
435
+ });
436
+ });
437
+ it('should quote reserved column names', () => {
438
+ expect(db.selections.insert({ id: 1, group: 'alpha' }).onConflict('id', 'ignore').asSql()).toEqual({
439
+ bindValues: [1, 'alpha'],
440
+ query: 'INSERT INTO \'selections\' ("id", "group") VALUES (?, ?) ON CONFLICT ("id") DO NOTHING',
441
+ usedTables: new Set(['selections']),
442
+ });
443
+ expect(db.selections.update({ group: 'beta' }).where({ id: 1 }).asSql()).toEqual({
444
+ bindValues: ['beta', 1],
445
+ query: 'UPDATE \'selections\' SET "group" = ? WHERE "id" = ?',
446
+ usedTables: new Set(['selections']),
447
+ });
501
448
  });
502
449
  it('should handle ON CONFLICT with multiple columns', () => {
503
- expect(dump(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).onConflict(['id', 'status'], 'ignore'))).toMatchInlineSnapshot(`
504
- {
505
- "bindValues": [
506
- "123",
507
- "Buy milk",
508
- "active",
509
- ],
510
- "query": "INSERT INTO 'todos' (id, text, status) VALUES (?, ?, ?) ON CONFLICT (id, status) DO NOTHING",
511
- "schema": "number",
512
- }
513
- `);
450
+ expect(db.todos
451
+ .insert({ id: '123', text: 'Buy milk', status: 'active' })
452
+ .onConflict(['id', 'status'], 'ignore')
453
+ .asSql()).toEqual({
454
+ bindValues: ['123', 'Buy milk', 'active'],
455
+ query: 'INSERT INTO \'todos\' ("id", "text", "status") VALUES (?, ?, ?) ON CONFLICT ("id", "status") DO NOTHING',
456
+ usedTables: new Set(['todos']),
457
+ });
514
458
  });
515
459
  it('should handle RETURNING clause', () => {
516
- expect(dump(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).returning('id'))).toMatchInlineSnapshot(`
517
- {
518
- "bindValues": [
519
- "123",
520
- "Buy milk",
521
- "active",
522
- ],
523
- "query": "INSERT INTO 'todos' (id, text, status) VALUES (?, ?, ?) RETURNING id",
524
- "schema": "ReadonlyArray<{ readonly id: string }>",
525
- }
526
- `);
527
- expect(dump(db.todos.update({ status: 'completed' }).where({ id: '123' }).returning('id'))).toMatchInlineSnapshot(`
528
- {
529
- "bindValues": [
530
- "completed",
531
- "123",
532
- ],
533
- "query": "UPDATE 'todos' SET status = ? WHERE id = ? RETURNING id",
534
- "schema": "ReadonlyArray<{ readonly id: string }>",
535
- }
536
- `);
537
- expect(dump(db.todos.delete().where({ status: 'completed' }).returning('id'))).toMatchInlineSnapshot(`
538
- {
539
- "bindValues": [
540
- "completed",
541
- ],
542
- "query": "DELETE FROM 'todos' WHERE status = ? RETURNING id",
543
- "schema": "ReadonlyArray<{ readonly id: string }>",
544
- }
545
- `);
460
+ expect(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).returning('id').asSql()).toEqual({
461
+ bindValues: ['123', 'Buy milk', 'active'],
462
+ query: 'INSERT INTO \'todos\' ("id", "text", "status") VALUES (?, ?, ?) RETURNING "id"',
463
+ usedTables: new Set(['todos']),
464
+ });
465
+ expect(db.todos.update({ status: 'completed' }).where({ id: '123' }).returning('id').asSql()).toEqual({
466
+ bindValues: ['completed', '123'],
467
+ query: 'UPDATE \'todos\' SET "status" = ? WHERE "id" = ? RETURNING "id"',
468
+ usedTables: new Set(['todos']),
469
+ });
470
+ expect(db.todos.delete().where({ status: 'completed' }).returning('id').asSql()).toEqual({
471
+ bindValues: ['completed'],
472
+ query: 'DELETE FROM \'todos\' WHERE "status" = ? RETURNING "id"',
473
+ usedTables: new Set(['todos']),
474
+ });
475
+ });
476
+ it('derives result schemas for RETURNING clauses', async () => {
477
+ const returningId = new TestSchema.Asserts(getResultSchema(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).returning('id')));
478
+ await returningId.decoding().succeed([{ id: '123' }]);
546
479
  });
547
480
  it('should handle where().delete() - preserving where clauses', () => {
548
- expect(dump(db.todos.where({ status: 'completed' }).delete())).toMatchInlineSnapshot(`
549
- {
550
- "bindValues": [
551
- "completed",
552
- ],
553
- "query": "DELETE FROM 'todos' WHERE status = ?",
554
- "schema": "number",
555
- }
556
- `);
481
+ expect(db.todos.where({ status: 'completed' }).delete().asSql()).toEqual({
482
+ bindValues: ['completed'],
483
+ query: 'DELETE FROM \'todos\' WHERE "status" = ?',
484
+ usedTables: new Set(['todos']),
485
+ });
557
486
  // Multiple where clauses
558
- expect(dump(db.todos.where({ status: 'completed' }).where({ deletedAt: null }).delete())).toMatchInlineSnapshot(`
559
- {
560
- "bindValues": [
561
- "completed",
562
- ],
563
- "query": "DELETE FROM 'todos' WHERE status = ? AND deletedAt IS NULL",
564
- "schema": "number",
565
- }
566
- `);
487
+ expect(db.todos.where({ status: 'completed' }).where({ deletedAt: null }).delete().asSql()).toEqual({
488
+ bindValues: ['completed'],
489
+ query: 'DELETE FROM \'todos\' WHERE "status" = ? AND "deletedAt" IS NULL',
490
+ usedTables: new Set(['todos']),
491
+ });
567
492
  });
568
493
  it('should handle where().update() - preserving where clauses', () => {
569
- expect(dump(db.todos.where({ id: '123' }).update({ status: 'completed' }))).toMatchInlineSnapshot(`
570
- {
571
- "bindValues": [
572
- "completed",
573
- "123",
574
- ],
575
- "query": "UPDATE 'todos' SET status = ? WHERE id = ?",
576
- "schema": "number",
577
- }
578
- `);
494
+ expect(db.todos.where({ id: '123' }).update({ status: 'completed' }).asSql()).toEqual({
495
+ bindValues: ['completed', '123'],
496
+ query: 'UPDATE \'todos\' SET "status" = ? WHERE "id" = ?',
497
+ usedTables: new Set(['todos']),
498
+ });
579
499
  // Multiple where clauses
580
- expect(dump(db.todos.where({ id: '123' }).where({ deletedAt: null }).update({ status: 'completed' }))).toMatchInlineSnapshot(`
581
- {
582
- "bindValues": [
583
- "completed",
584
- "123",
585
- ],
586
- "query": "UPDATE 'todos' SET status = ? WHERE id = ? AND deletedAt IS NULL",
587
- "schema": "number",
588
- }
589
- `);
500
+ expect(db.todos.where({ id: '123' }).where({ deletedAt: null }).update({ status: 'completed' }).asSql()).toEqual({
501
+ bindValues: ['completed', '123'],
502
+ query: 'UPDATE \'todos\' SET "status" = ? WHERE "id" = ? AND "deletedAt" IS NULL',
503
+ usedTables: new Set(['todos']),
504
+ });
590
505
  });
591
506
  it('should have equivalent behavior for both delete patterns', () => {
592
- const pattern1 = dump(db.todos.where({ status: 'completed', id: '123' }).delete());
593
- const pattern2 = dump(db.todos.delete().where({ status: 'completed', id: '123' }));
507
+ const pattern1 = db.todos.where({ status: 'completed', id: '123' }).delete().asSql();
508
+ const pattern2 = db.todos.delete().where({ status: 'completed', id: '123' }).asSql();
594
509
  expect(pattern1).toEqual(pattern2);
595
510
  });
596
511
  it('should have equivalent behavior for both update patterns', () => {
597
- const pattern1 = dump(db.todos.where({ id: '123' }).update({ status: 'completed', text: 'Updated' }));
598
- const pattern2 = dump(db.todos.update({ status: 'completed', text: 'Updated' }).where({ id: '123' }));
512
+ const pattern1 = db.todos.where({ id: '123' }).update({ status: 'completed', text: 'Updated' }).asSql();
513
+ const pattern2 = db.todos.update({ status: 'completed', text: 'Updated' }).where({ id: '123' }).asSql();
599
514
  expect(pattern1).toEqual(pattern2);
600
515
  });
601
516
  });
@@ -606,14 +521,14 @@ describe('query builder', () => {
606
521
  contactLastName: Schema.String,
607
522
  contactEmail: Schema.String.pipe(State.SQLite.withUnique),
608
523
  });
609
- const Nested = Schema.transform(Flat, Schema.Struct({
524
+ const Nested = Flat.pipe(Schema.decodeTo(Schema.Struct({
610
525
  id: Schema.String,
611
526
  contact: Schema.Struct({
612
527
  firstName: Schema.String,
613
528
  lastName: Schema.String,
614
529
  email: Schema.String,
615
530
  }),
616
- }), {
531
+ }), SchemaTransformation.transform({
617
532
  decode: ({ id, contactFirstName, contactLastName, contactEmail }) => ({
618
533
  id,
619
534
  contact: {
@@ -628,7 +543,7 @@ describe('query builder', () => {
628
543
  contactLastName: contact.lastName,
629
544
  contactEmail: contact.email,
630
545
  }),
631
- });
546
+ })));
632
547
  const makeContactsTable = () => State.SQLite.table({
633
548
  name: 'contacts',
634
549
  schema: Nested,
@@ -638,7 +553,7 @@ describe('query builder', () => {
638
553
  const contactsTable = makeContactsTable();
639
554
  void contactsTable;
640
555
  });
641
- it('fails to encode nested inserts because flat columns are required', () => {
556
+ it('encodes flat insert values for transformed schemas', () => {
642
557
  const contactsTable = makeContactsTable();
643
558
  expect(contactsTable
644
559
  // TODO in the future we should use decoded types here instead of encoded
@@ -648,22 +563,13 @@ describe('query builder', () => {
648
563
  contactLastName: 'Lovelace',
649
564
  contactEmail: 'ada@example.com',
650
565
  })
651
- .asSql()).toMatchInlineSnapshot(`
652
- {
653
- "bindValues": [
654
- "person-1",
655
- "Ada",
656
- "Lovelace",
657
- "ada@example.com",
658
- ],
659
- "query": "INSERT INTO 'contacts' (id, contactFirstName, contactLastName, contactEmail) VALUES (?, ?, ?, ?)",
660
- "usedTables": Set {
661
- "contacts",
662
- },
663
- }
664
- `);
665
- });
666
- it('fails to encode nested inserts because flat columns are required', () => {
566
+ .asSql()).toEqual({
567
+ bindValues: ['person-1', 'Ada', 'Lovelace', 'ada@example.com'],
568
+ query: 'INSERT INTO \'contacts\' ("id", "contactFirstName", "contactLastName", "contactEmail") VALUES (?, ?, ?, ?)',
569
+ usedTables: new Set(['contacts']),
570
+ });
571
+ });
572
+ it('rejects nested insert values because flat columns are required', () => {
667
573
  const contactsTable = makeContactsTable();
668
574
  expect(() => contactsTable
669
575
  .insert({
@@ -675,14 +581,12 @@ describe('query builder', () => {
675
581
  email: 'ada@example.com',
676
582
  },
677
583
  })
678
- .asSql()).toThrowErrorMatchingInlineSnapshot(`
679
- [ParseError: contacts\n└─ ["contactFirstName"]\n └─ is missing]
680
- `);
584
+ .asSql()).toThrowError(/contactFirstName/);
681
585
  });
682
586
  });
683
587
  });
684
588
  // TODO nested queries
685
- // const rawSql = <A, I>(sql: string, params: { [key: string]: any }, schema: Schema.Schema<A, I>) =>
589
+ // const rawSql = <A, I>(sql: string, params: { [key: string]: any }, schema: Schema.Codec<A, I>) =>
686
590
  // ({
687
591
  // sql,
688
592
  // params,