@livestore/common 0.4.0-dev.22 → 0.4.0-dev.23

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 (313) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/ClientSessionLeaderThreadProxy.d.ts +9 -9
  3. package/dist/ClientSessionLeaderThreadProxy.d.ts.map +1 -1
  4. package/dist/WorkerTransportError.d.ts +11 -0
  5. package/dist/WorkerTransportError.d.ts.map +1 -0
  6. package/dist/WorkerTransportError.js +11 -0
  7. package/dist/WorkerTransportError.js.map +1 -0
  8. package/dist/adapter-types.d.ts +3 -3
  9. package/dist/adapter-types.d.ts.map +1 -1
  10. package/dist/adapter-types.js.map +1 -1
  11. package/dist/bounded-collections.d.ts.map +1 -1
  12. package/dist/bounded-collections.js +6 -4
  13. package/dist/bounded-collections.js.map +1 -1
  14. package/dist/debug-info.js +4 -4
  15. package/dist/debug-info.js.map +1 -1
  16. package/dist/devtools/devtools-messages-common.js +1 -1
  17. package/dist/devtools/devtools-messages-common.js.map +1 -1
  18. package/dist/devtools/mod.js +1 -1
  19. package/dist/devtools/mod.js.map +1 -1
  20. package/dist/errors.d.ts +15 -15
  21. package/dist/errors.d.ts.map +1 -1
  22. package/dist/errors.js +11 -11
  23. package/dist/errors.js.map +1 -1
  24. package/dist/index.d.ts +2 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +2 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/leader-thread/LeaderSyncProcessor.d.ts +20 -6
  29. package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
  30. package/dist/leader-thread/LeaderSyncProcessor.js +287 -257
  31. package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
  32. package/dist/leader-thread/RejectedPushError.d.ts +107 -0
  33. package/dist/leader-thread/RejectedPushError.d.ts.map +1 -0
  34. package/dist/leader-thread/RejectedPushError.js +78 -0
  35. package/dist/leader-thread/RejectedPushError.js.map +1 -0
  36. package/dist/leader-thread/connection.js +1 -1
  37. package/dist/leader-thread/connection.js.map +1 -1
  38. package/dist/leader-thread/eventlog.d.ts.map +1 -1
  39. package/dist/leader-thread/eventlog.js +12 -11
  40. package/dist/leader-thread/eventlog.js.map +1 -1
  41. package/dist/leader-thread/leader-worker-devtools.d.ts +1 -2
  42. package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
  43. package/dist/leader-thread/leader-worker-devtools.js +25 -14
  44. package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
  45. package/dist/leader-thread/make-leader-thread-layer.d.ts +8 -3
  46. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
  47. package/dist/leader-thread/make-leader-thread-layer.js +7 -10
  48. package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
  49. package/dist/leader-thread/make-leader-thread-layer.test.js +1 -1
  50. package/dist/leader-thread/make-leader-thread-layer.test.js.map +1 -1
  51. package/dist/leader-thread/materialize-event.js +4 -4
  52. package/dist/leader-thread/materialize-event.js.map +1 -1
  53. package/dist/leader-thread/recreate-db.js +1 -1
  54. package/dist/leader-thread/recreate-db.js.map +1 -1
  55. package/dist/leader-thread/shutdown-channel.d.ts +2 -2
  56. package/dist/leader-thread/shutdown-channel.d.ts.map +1 -1
  57. package/dist/leader-thread/shutdown-channel.js +2 -2
  58. package/dist/leader-thread/shutdown-channel.js.map +1 -1
  59. package/dist/leader-thread/stream-events.d.ts.map +1 -1
  60. package/dist/leader-thread/stream-events.js +4 -3
  61. package/dist/leader-thread/stream-events.js.map +1 -1
  62. package/dist/leader-thread/types.d.ts +7 -6
  63. package/dist/leader-thread/types.d.ts.map +1 -1
  64. package/dist/leader-thread/types.js.map +1 -1
  65. package/dist/logging.js +4 -4
  66. package/dist/logging.js.map +1 -1
  67. package/dist/make-client-session.js +2 -2
  68. package/dist/make-client-session.js.map +1 -1
  69. package/dist/materializer-helper.js +6 -6
  70. package/dist/materializer-helper.js.map +1 -1
  71. package/dist/otel.d.ts +1 -1
  72. package/dist/otel.d.ts.map +1 -1
  73. package/dist/otel.js +2 -2
  74. package/dist/otel.js.map +1 -1
  75. package/dist/rematerialize-from-eventlog.d.ts +1 -1
  76. package/dist/rematerialize-from-eventlog.d.ts.map +1 -1
  77. package/dist/rematerialize-from-eventlog.js +11 -9
  78. package/dist/rematerialize-from-eventlog.js.map +1 -1
  79. package/dist/schema/EventDef/define.d.ts +2 -2
  80. package/dist/schema/EventDef/define.d.ts.map +1 -1
  81. package/dist/schema/EventDef/define.js +4 -4
  82. package/dist/schema/EventDef/define.js.map +1 -1
  83. package/dist/schema/EventDef/deprecated.js +3 -3
  84. package/dist/schema/EventDef/deprecated.js.map +1 -1
  85. package/dist/schema/EventDef/deprecated.test.js +1 -1
  86. package/dist/schema/EventDef/deprecated.test.js.map +1 -1
  87. package/dist/schema/EventSequenceNumber/client.d.ts.map +1 -1
  88. package/dist/schema/EventSequenceNumber/client.js +11 -11
  89. package/dist/schema/EventSequenceNumber/client.js.map +1 -1
  90. package/dist/schema/EventSequenceNumber.test.js +1 -1
  91. package/dist/schema/EventSequenceNumber.test.js.map +1 -1
  92. package/dist/schema/LiveStoreEvent/client.d.ts.map +1 -1
  93. package/dist/schema/LiveStoreEvent/client.js +6 -3
  94. package/dist/schema/LiveStoreEvent/client.js.map +1 -1
  95. package/dist/schema/LiveStoreEvent/client.test.d.ts +2 -0
  96. package/dist/schema/LiveStoreEvent/client.test.d.ts.map +1 -0
  97. package/dist/schema/LiveStoreEvent/client.test.js +83 -0
  98. package/dist/schema/LiveStoreEvent/client.test.js.map +1 -0
  99. package/dist/schema/schema.d.ts.map +1 -1
  100. package/dist/schema/schema.js +7 -4
  101. package/dist/schema/schema.js.map +1 -1
  102. package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -1
  103. package/dist/schema/state/sqlite/client-document-def.js +18 -6
  104. package/dist/schema/state/sqlite/client-document-def.js.map +1 -1
  105. package/dist/schema/state/sqlite/client-document-def.test.js +1 -1
  106. package/dist/schema/state/sqlite/client-document-def.test.js.map +1 -1
  107. package/dist/schema/state/sqlite/column-annotations.d.ts.map +1 -1
  108. package/dist/schema/state/sqlite/column-annotations.js +1 -1
  109. package/dist/schema/state/sqlite/column-annotations.js.map +1 -1
  110. package/dist/schema/state/sqlite/column-annotations.test.js +1 -1
  111. package/dist/schema/state/sqlite/column-annotations.test.js.map +1 -1
  112. package/dist/schema/state/sqlite/column-def.d.ts.map +1 -1
  113. package/dist/schema/state/sqlite/column-def.js +36 -34
  114. package/dist/schema/state/sqlite/column-def.js.map +1 -1
  115. package/dist/schema/state/sqlite/column-def.test.js +7 -6
  116. package/dist/schema/state/sqlite/column-def.test.js.map +1 -1
  117. package/dist/schema/state/sqlite/column-spec.d.ts.map +1 -1
  118. package/dist/schema/state/sqlite/column-spec.js +8 -8
  119. package/dist/schema/state/sqlite/column-spec.js.map +1 -1
  120. package/dist/schema/state/sqlite/column-spec.test.js +1 -1
  121. package/dist/schema/state/sqlite/column-spec.test.js.map +1 -1
  122. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +2 -2
  123. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js.map +1 -1
  124. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts +2 -2
  125. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts.map +1 -1
  126. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js +11 -2
  127. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js.map +1 -1
  128. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js +1 -1
  129. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js.map +1 -1
  130. package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts +1 -1
  131. package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts.map +1 -1
  132. package/dist/schema/state/sqlite/db-schema/dsl/mod.js +1 -1
  133. package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -1
  134. package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
  135. package/dist/schema/state/sqlite/mod.js +3 -5
  136. package/dist/schema/state/sqlite/mod.js.map +1 -1
  137. package/dist/schema/state/sqlite/query-builder/api.d.ts +10 -2
  138. package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -1
  139. package/dist/schema/state/sqlite/query-builder/astToSql.js +11 -11
  140. package/dist/schema/state/sqlite/query-builder/astToSql.js.map +1 -1
  141. package/dist/schema/state/sqlite/query-builder/impl.d.ts +1 -1
  142. package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -1
  143. package/dist/schema/state/sqlite/query-builder/impl.js +28 -14
  144. package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -1
  145. package/dist/schema/state/sqlite/query-builder/impl.test.js +3 -2
  146. package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -1
  147. package/dist/schema/state/sqlite/schema-helpers.js +2 -2
  148. package/dist/schema/state/sqlite/schema-helpers.js.map +1 -1
  149. package/dist/schema/state/sqlite/table-def.d.ts +5 -3
  150. package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
  151. package/dist/schema/state/sqlite/table-def.js +1 -1
  152. package/dist/schema/state/sqlite/table-def.js.map +1 -1
  153. package/dist/schema/state/sqlite/table-def.test.js +57 -4
  154. package/dist/schema/state/sqlite/table-def.test.js.map +1 -1
  155. package/dist/schema/unknown-events.d.ts +1 -1
  156. package/dist/schema/unknown-events.d.ts.map +1 -1
  157. package/dist/schema/unknown-events.js +1 -1
  158. package/dist/schema/unknown-events.js.map +1 -1
  159. package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js +1 -1
  160. package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js.map +1 -1
  161. package/dist/schema-management/common.js +2 -2
  162. package/dist/schema-management/common.js.map +1 -1
  163. package/dist/schema-management/migrations.js +1 -1
  164. package/dist/schema-management/migrations.js.map +1 -1
  165. package/dist/sql-queries/sql-queries.js +8 -6
  166. package/dist/sql-queries/sql-queries.js.map +1 -1
  167. package/dist/sql-queries/sql-query-builder.d.ts.map +1 -1
  168. package/dist/sql-queries/sql-query-builder.js.map +1 -1
  169. package/dist/sqlite-db-helper.js +3 -3
  170. package/dist/sqlite-db-helper.js.map +1 -1
  171. package/dist/sqlite-types.d.ts +2 -2
  172. package/dist/sqlite-types.d.ts.map +1 -1
  173. package/dist/sqlite-types.js.map +1 -1
  174. package/dist/sync/ClientSessionSyncProcessor.d.ts +8 -9
  175. package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
  176. package/dist/sync/ClientSessionSyncProcessor.js +95 -113
  177. package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
  178. package/dist/sync/errors.d.ts +0 -38
  179. package/dist/sync/errors.d.ts.map +1 -1
  180. package/dist/sync/errors.js +3 -20
  181. package/dist/sync/errors.js.map +1 -1
  182. package/dist/sync/mock-sync-backend.d.ts +5 -3
  183. package/dist/sync/mock-sync-backend.d.ts.map +1 -1
  184. package/dist/sync/mock-sync-backend.js +70 -68
  185. package/dist/sync/mock-sync-backend.js.map +1 -1
  186. package/dist/sync/next/compact-events.js +6 -6
  187. package/dist/sync/next/compact-events.js.map +1 -1
  188. package/dist/sync/next/facts.d.ts.map +1 -1
  189. package/dist/sync/next/facts.js +6 -6
  190. package/dist/sync/next/facts.js.map +1 -1
  191. package/dist/sync/next/history-dag-common.d.ts.map +1 -1
  192. package/dist/sync/next/history-dag-common.js +6 -6
  193. package/dist/sync/next/history-dag-common.js.map +1 -1
  194. package/dist/sync/next/history-dag.js +3 -3
  195. package/dist/sync/next/history-dag.js.map +1 -1
  196. package/dist/sync/next/rebase-events.js +1 -1
  197. package/dist/sync/next/rebase-events.js.map +1 -1
  198. package/dist/sync/next/test/compact-events.calculator.test.js +2 -2
  199. package/dist/sync/next/test/compact-events.calculator.test.js.map +1 -1
  200. package/dist/sync/next/test/compact-events.test.d.ts.map +1 -1
  201. package/dist/sync/next/test/compact-events.test.js +2 -2
  202. package/dist/sync/next/test/compact-events.test.js.map +1 -1
  203. package/dist/sync/next/test/event-fixtures.d.ts.map +1 -1
  204. package/dist/sync/next/test/event-fixtures.js +2 -2
  205. package/dist/sync/next/test/event-fixtures.js.map +1 -1
  206. package/dist/sync/sync-backend-kv.d.ts.map +1 -1
  207. package/dist/sync/sync-backend-kv.js.map +1 -1
  208. package/dist/sync/sync-backend.d.ts +3 -3
  209. package/dist/sync/sync-backend.d.ts.map +1 -1
  210. package/dist/sync/sync-backend.js +1 -1
  211. package/dist/sync/sync-backend.js.map +1 -1
  212. package/dist/sync/sync.d.ts +20 -0
  213. package/dist/sync/sync.d.ts.map +1 -1
  214. package/dist/sync/syncstate.d.ts +4 -17
  215. package/dist/sync/syncstate.d.ts.map +1 -1
  216. package/dist/sync/syncstate.js +51 -74
  217. package/dist/sync/syncstate.js.map +1 -1
  218. package/dist/sync/syncstate.test.js +112 -96
  219. package/dist/sync/syncstate.test.js.map +1 -1
  220. package/dist/sync/transport-chunking.js +3 -3
  221. package/dist/sync/transport-chunking.js.map +1 -1
  222. package/dist/sync/validate-push-payload.d.ts +2 -2
  223. package/dist/sync/validate-push-payload.d.ts.map +1 -1
  224. package/dist/sync/validate-push-payload.js +4 -6
  225. package/dist/sync/validate-push-payload.js.map +1 -1
  226. package/dist/util.js +2 -2
  227. package/dist/util.js.map +1 -1
  228. package/dist/version.d.ts.map +1 -1
  229. package/dist/version.js +2 -5
  230. package/dist/version.js.map +1 -1
  231. package/package.json +66 -12
  232. package/src/ClientSessionLeaderThreadProxy.ts +9 -9
  233. package/src/WorkerTransportError.ts +12 -0
  234. package/src/adapter-types.ts +9 -3
  235. package/src/bounded-collections.ts +6 -5
  236. package/src/debug-info.ts +4 -4
  237. package/src/devtools/devtools-messages-common.ts +1 -1
  238. package/src/devtools/mod.ts +1 -1
  239. package/src/errors.ts +18 -17
  240. package/src/index.ts +2 -0
  241. package/src/leader-thread/LeaderSyncProcessor.ts +421 -392
  242. package/src/leader-thread/RejectedPushError.ts +106 -0
  243. package/src/leader-thread/connection.ts +1 -1
  244. package/src/leader-thread/eventlog.ts +16 -14
  245. package/src/leader-thread/leader-worker-devtools.ts +96 -66
  246. package/src/leader-thread/make-leader-thread-layer.test.ts +1 -1
  247. package/src/leader-thread/make-leader-thread-layer.ts +33 -31
  248. package/src/leader-thread/materialize-event.ts +4 -4
  249. package/src/leader-thread/recreate-db.ts +1 -1
  250. package/src/leader-thread/shutdown-channel.ts +2 -6
  251. package/src/leader-thread/stream-events.ts +10 -5
  252. package/src/leader-thread/types.ts +7 -6
  253. package/src/logging.ts +4 -4
  254. package/src/make-client-session.ts +2 -2
  255. package/src/materializer-helper.ts +9 -9
  256. package/src/otel.ts +3 -2
  257. package/src/rematerialize-from-eventlog.ts +60 -60
  258. package/src/schema/EventDef/define.ts +6 -6
  259. package/src/schema/EventDef/deprecated.test.ts +2 -1
  260. package/src/schema/EventDef/deprecated.ts +3 -3
  261. package/src/schema/EventSequenceNumber/client.ts +11 -11
  262. package/src/schema/EventSequenceNumber.test.ts +2 -1
  263. package/src/schema/LiveStoreEvent/client.test.ts +97 -0
  264. package/src/schema/LiveStoreEvent/client.ts +6 -3
  265. package/src/schema/schema.ts +9 -4
  266. package/src/schema/state/sqlite/client-document-def.test.ts +2 -1
  267. package/src/schema/state/sqlite/client-document-def.ts +20 -6
  268. package/src/schema/state/sqlite/column-annotations.test.ts +2 -1
  269. package/src/schema/state/sqlite/column-annotations.ts +2 -1
  270. package/src/schema/state/sqlite/column-def.test.ts +8 -6
  271. package/src/schema/state/sqlite/column-def.ts +41 -36
  272. package/src/schema/state/sqlite/column-spec.test.ts +3 -1
  273. package/src/schema/state/sqlite/column-spec.ts +9 -8
  274. package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +2 -2
  275. package/src/schema/state/sqlite/db-schema/dsl/field-defs.test.ts +2 -1
  276. package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +13 -4
  277. package/src/schema/state/sqlite/db-schema/dsl/mod.ts +3 -3
  278. package/src/schema/state/sqlite/mod.ts +4 -5
  279. package/src/schema/state/sqlite/query-builder/api.ts +12 -5
  280. package/src/schema/state/sqlite/query-builder/astToSql.ts +11 -11
  281. package/src/schema/state/sqlite/query-builder/impl.test.ts +4 -2
  282. package/src/schema/state/sqlite/query-builder/impl.ts +26 -12
  283. package/src/schema/state/sqlite/schema-helpers.ts +2 -2
  284. package/src/schema/state/sqlite/table-def.test.ts +67 -4
  285. package/src/schema/state/sqlite/table-def.ts +8 -15
  286. package/src/schema/unknown-events.ts +2 -2
  287. package/src/schema-management/__tests__/migrations-autoincrement-quoting.test.ts +3 -1
  288. package/src/schema-management/common.ts +2 -2
  289. package/src/schema-management/migrations.ts +1 -1
  290. package/src/sql-queries/sql-queries.ts +10 -6
  291. package/src/sql-queries/sql-query-builder.ts +1 -0
  292. package/src/sqlite-db-helper.ts +3 -3
  293. package/src/sqlite-types.ts +3 -2
  294. package/src/sync/ClientSessionSyncProcessor.ts +148 -152
  295. package/src/sync/errors.ts +10 -22
  296. package/src/sync/mock-sync-backend.ts +139 -97
  297. package/src/sync/next/compact-events.ts +5 -5
  298. package/src/sync/next/facts.ts +7 -6
  299. package/src/sync/next/history-dag-common.ts +9 -6
  300. package/src/sync/next/history-dag.ts +3 -3
  301. package/src/sync/next/rebase-events.ts +1 -1
  302. package/src/sync/next/test/compact-events.calculator.test.ts +3 -2
  303. package/src/sync/next/test/compact-events.test.ts +4 -3
  304. package/src/sync/next/test/event-fixtures.ts +2 -2
  305. package/src/sync/sync-backend-kv.ts +1 -0
  306. package/src/sync/sync-backend.ts +5 -4
  307. package/src/sync/sync.ts +21 -0
  308. package/src/sync/syncstate.test.ts +513 -435
  309. package/src/sync/syncstate.ts +80 -86
  310. package/src/sync/transport-chunking.ts +3 -3
  311. package/src/sync/validate-push-payload.ts +4 -6
  312. package/src/util.ts +2 -2
  313. package/src/version.ts +2 -6
@@ -31,7 +31,7 @@ export type DbSchemaFromInputSchema<TSchemaInput extends DbSchemaInput> =
31
31
  export const makeDbSchema = <TDbSchemaInput extends DbSchemaInput>(
32
32
  schema: TDbSchemaInput,
33
33
  ): DbSchemaFromInputSchema<TDbSchemaInput> => {
34
- return Array.isArray(schema) ? Object.fromEntries(schema.map((_) => [_.name, _])) : (schema as any)
34
+ return Array.isArray(schema) === true ? Object.fromEntries(schema.map((_) => [_.name, _])) : (schema as any)
35
35
  }
36
36
 
37
37
  export const table = <TTableName extends string, TColumns extends Columns, TIndexes extends Index[]>(
@@ -76,6 +76,7 @@ export const insertStructSchemaForTable = <TTableDefinition extends TableDefinit
76
76
  Object.fromEntries(
77
77
  tableDef.ast.columns.map((column) => [
78
78
  column.name,
79
+
79
80
  column.nullable === true || column.default._tag === 'Some' ? Schema.optional(column.schema) : column.schema,
80
81
  ]),
81
82
  ),
@@ -195,8 +196,7 @@ export namespace FromColumns {
195
196
  }
196
197
 
197
198
  export type NullableColumnNames<TColumns extends Columns> = keyof {
198
- // TODO double check why there is a `true` in the type
199
- [K in keyof TColumns as TColumns[K] extends ColumnDefinition<any, true> ? K : never]: {}
199
+ [K in keyof TColumns as TColumns[K]['nullable'] extends true ? K : never]: {}
200
200
  }
201
201
 
202
202
  export type RequiredInsertColumns<TColumns extends Columns> = {
@@ -22,16 +22,15 @@ export * from './column-spec.ts'
22
22
  export * from './table-def.ts'
23
23
 
24
24
  export const makeState = <TStateInput extends InputState>(inputSchema: TStateInput): InternalState => {
25
- const inputTables: ReadonlyArray<TableDef> = Array.isArray(inputSchema.tables)
26
- ? inputSchema.tables
27
- : Object.values(inputSchema.tables)
25
+ const inputTables: ReadonlyArray<TableDef> =
26
+ Array.isArray(inputSchema.tables) === true ? inputSchema.tables : Object.values(inputSchema.tables)
28
27
 
29
28
  const tables = new Map<string, TableDef.Any>()
30
29
 
31
30
  for (const tableDef of inputTables) {
32
31
  const sqliteDef = tableDef.sqliteDef
33
32
  // TODO validate tables (e.g. index names are unique)
34
- if (tables.has(sqliteDef.ast.name)) {
33
+ if (tables.has(sqliteDef.ast.name) === true) {
35
34
  shouldNeverHappen(`Duplicate table name: ${sqliteDef.ast.name}. Please use unique names for tables.`)
36
35
  }
37
36
  tables.set(sqliteDef.ast.name, tableDef)
@@ -48,7 +47,7 @@ export const makeState = <TStateInput extends InputState>(inputSchema: TStateInp
48
47
  }
49
48
 
50
49
  for (const tableDef of inputTables) {
51
- if (tableIsClientDocumentTable(tableDef)) {
50
+ if (tableIsClientDocumentTable(tableDef) === true) {
52
51
  materializers.set(
53
52
  tableDef[ClientDocumentTableDefSymbol].derived.setEventDef.name,
54
53
  tableDef[ClientDocumentTableDefSymbol].derived.setMaterializer,
@@ -230,9 +230,8 @@ export namespace QueryBuilder {
230
230
  /** Select multiple columns */
231
231
  <TColumns extends keyof TTableDef['sqliteDef']['columns'] & string>(
232
232
  ...columns: TColumns[]
233
- // TODO also support arbitrary SQL selects
234
- // params: QueryBuilderSelectParams,
235
- ): QueryBuilder<
233
+ )// params: QueryBuilderSelectParams, // TODO also support arbitrary SQL selects
234
+ : QueryBuilder<
236
235
  ReadonlyArray<{
237
236
  readonly [K in TColumns]: TTableDef['sqliteDef']['columns'][K]['schema']['Type']
238
237
  }>,
@@ -358,12 +357,20 @@ export namespace QueryBuilder {
358
357
  >
359
358
 
360
359
  /**
361
- * Insert a new row into the table
360
+ * Insert a new row into the table.
361
+ *
362
+ * @remarks
363
+ *
364
+ * Follows SQL semantics: nullable columns and columns with defaults are omittable.
365
+ * `NullOr(S)` and `optional(NullOr(S))` both produce nullable columns, so both are omittable.
366
+ *
367
+ * @example
362
368
  *
363
- * Example:
364
369
  * ```ts
365
370
  * db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' })
366
371
  * ```
372
+ *
373
+ * @param values - The row values to insert.
367
374
  */
368
375
  readonly insert: (
369
376
  values: TTableDef['insertSchema']['Type'],
@@ -11,11 +11,11 @@ import type { QueryBuilderAst } from './api.ts'
11
11
  * Returns the element schema, or undefined if not a JSON array transformation.
12
12
  */
13
13
  const extractArrayElementFromTransformation = (ast: SchemaAST.AST): Schema.Schema.Any | undefined => {
14
- if (!SchemaAST.isTransformation(ast)) return undefined
14
+ if (SchemaAST.isTransformation(ast) === false) return undefined
15
15
 
16
16
  const toAst = ast.to
17
17
  // Check if the "to" side is a TupleType (Effect's internal representation of Array)
18
- if (!SchemaAST.isTupleType(toAst)) return undefined
18
+ if (SchemaAST.isTupleType(toAst) === false) return undefined
19
19
 
20
20
  // For Schema.Array, rest contains { type: AST } elements - get the first one's type
21
21
  const restElement = toAst.rest[0]
@@ -34,13 +34,13 @@ const getJsonArrayElementSchema = (colSchema: Schema.Schema.Any): Schema.Schema.
34
34
 
35
35
  // Case 1: Direct transformation (non-nullable JSON array)
36
36
  // Schema.parseJson(Schema.Array(ElementSchema)) creates a Transformation AST
37
- if (SchemaAST.isTransformation(ast)) {
37
+ if (SchemaAST.isTransformation(ast) === true) {
38
38
  return extractArrayElementFromTransformation(ast)
39
39
  }
40
40
 
41
41
  // Case 2: Nullable JSON array - Schema.NullOr wraps the parseJson in a Union
42
42
  // Structure: Union([Transformation (JSON array), Literal (null)])
43
- if (SchemaAST.isUnion(ast)) {
43
+ if (SchemaAST.isUnion(ast) === true) {
44
44
  for (const member of ast.types) {
45
45
  const result = extractArrayElementFromTransformation(member)
46
46
  if (result !== undefined) return result
@@ -63,7 +63,7 @@ const encodeJsonArrayElementValue = (elementSchema: Schema.Schema.Any, value: un
63
63
  return JSON.stringify(encoded)
64
64
  }
65
65
  if (typeof encoded === 'boolean') {
66
- return encoded ? 1 : 0
66
+ return encoded === true ? 1 : 0
67
67
  }
68
68
 
69
69
  return encoded
@@ -118,9 +118,9 @@ const formatWhereClause = (
118
118
  // Handle array values for IN/NOT IN operators
119
119
  const isArray = op === 'IN' || op === 'NOT IN'
120
120
 
121
- if (isArray) {
121
+ if (isArray === true) {
122
122
  // Verify value is an array
123
- if (!Array.isArray(value)) {
123
+ if (Array.isArray(value) === false) {
124
124
  return shouldNeverHappen(`Expected array value for ${op} operator but got`, value)
125
125
  }
126
126
 
@@ -145,7 +145,7 @@ const formatWhereClause = (
145
145
  }
146
146
 
147
147
  const formatReturningClause = (returning?: string[]): string => {
148
- if (!returning || returning.length === 0) return ''
148
+ if (returning == null || returning.length === 0) return ''
149
149
  return ` RETURNING ${returning.map(quoteIdentifier).join(', ')}`
150
150
  }
151
151
 
@@ -169,7 +169,7 @@ export const astToSql = (ast: QueryBuilderAst): { query: string; bindValues: Sql
169
169
  let conflictClause = '' // Store the ON CONFLICT clause separately
170
170
 
171
171
  // Handle ON CONFLICT clause
172
- if (ast.onConflict) {
172
+ if (ast.onConflict !== undefined) {
173
173
  // Handle REPLACE specifically as it changes the INSERT verb
174
174
  if (ast.onConflict.action._tag === 'replace') {
175
175
  insertVerb = 'INSERT OR REPLACE'
@@ -249,7 +249,7 @@ export const astToSql = (ast: QueryBuilderAst): { query: string; bindValues: Sql
249
249
  .join(', ')}`
250
250
 
251
251
  const whereClause = formatWhereClause(ast.where, ast.tableDef, bindValues)
252
- if (whereClause) query += ` ${whereClause}`
252
+ if (whereClause !== undefined) query += ` ${whereClause}`
253
253
 
254
254
  query += formatReturningClause(ast.returning)
255
255
  return { query, bindValues, usedTables }
@@ -260,7 +260,7 @@ export const astToSql = (ast: QueryBuilderAst): { query: string; bindValues: Sql
260
260
  let query = `DELETE FROM '${ast.tableDef.sqliteDef.name}'`
261
261
 
262
262
  const whereClause = formatWhereClause(ast.where, ast.tableDef, bindValues)
263
- if (whereClause) query += ` ${whereClause}`
263
+ if (whereClause !== undefined) query += ` ${whereClause}`
264
264
 
265
265
  query += formatReturningClause(ast.returning)
266
266
  return { query, bindValues, usedTables }
@@ -1,6 +1,8 @@
1
- import { Schema } from '@livestore/utils/effect'
2
1
  import { describe, expect, it } from 'vitest'
3
2
 
3
+ import { Schema } from '@livestore/utils/effect'
4
+ import { objectToString } from '@livestore/utils'
5
+
4
6
  import { State } from '../../../mod.ts'
5
7
  import type { QueryBuilder } from './api.ts'
6
8
  import { getResultSchema } from './impl.ts'
@@ -101,7 +103,7 @@ const db = { todos, todosWithIntId, comments, issue, selections, UiState, UiStat
101
103
  const dump = (qb: QueryBuilder<any, any, any>) => ({
102
104
  bindValues: qb.asSql().bindValues,
103
105
  query: qb.asSql().query,
104
- schema: getResultSchema(qb).toString(),
106
+ schema: objectToString(getResultSchema(qb)),
105
107
  })
106
108
 
107
109
  describe('query builder', () => {
@@ -9,7 +9,7 @@ import { astToSql } from './astToSql.ts'
9
9
  export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
10
10
  tableDef: TTableDef,
11
11
  ast: QueryBuilderAst = emptyAst(tableDef),
12
- ): QueryBuilder<TResult, TTableDef, never> => {
12
+ ): QueryBuilder<TResult, TTableDef> => {
13
13
  const api = {
14
14
  select() {
15
15
  assertSelectQueryBuilderAst(ast)
@@ -18,6 +18,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
18
18
 
19
19
  // Pluck if there's only one column selected
20
20
  if (params.length === 1) {
21
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- arguments-based overload dispatch requires manual narrowing
21
22
  const [col] = params as any as [string]
22
23
  return makeQueryBuilder(tableDef, {
23
24
  ...ast,
@@ -26,8 +27,10 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
26
27
  })
27
28
  }
28
29
 
30
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- arguments-based overload dispatch requires manual narrowing
29
31
  const columns = params as unknown as ReadonlyArray<string>
30
32
 
33
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- query builder return type depends on AST state; consumer type safety enforced by api.ts
31
34
  return makeQueryBuilder(tableDef, {
32
35
  ...ast,
33
36
  resultSchemaSingle:
@@ -44,7 +47,8 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
44
47
  const newOps = Object.entries(params)
45
48
  .filter(([, value]) => value !== undefined)
46
49
  .map<QueryBuilderAst.Where>(([col, value]) =>
47
- Predicate.hasProperty(value, 'op') && Predicate.hasProperty(value, 'value')
50
+ Predicate.hasProperty(value, 'op') === true && Predicate.hasProperty(value, 'value') === true
51
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- where clause construction; shape validated at runtime
48
52
  ? ({ col, op: value.op, value: value.value } as any)
49
53
  : { col, op: '=', value },
50
54
  )
@@ -54,6 +58,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
54
58
  case 'SelectQuery':
55
59
  case 'UpdateQuery':
56
60
  case 'DeleteQuery': {
61
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- query builder return type depends on AST state; consumer type safety enforced by api.ts
57
62
  return makeQueryBuilder(tableDef, {
58
63
  ...ast,
59
64
  where: [...ast.where, ...newOps],
@@ -74,6 +79,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
74
79
  case 'SelectQuery':
75
80
  case 'UpdateQuery':
76
81
  case 'DeleteQuery': {
82
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- query builder return type depends on AST state; consumer type safety enforced by api.ts
77
83
  return makeQueryBuilder(tableDef, {
78
84
  ...ast,
79
85
  where: [...ast.where, { col, op, value }],
@@ -91,6 +97,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
91
97
  if (arguments.length === 0 || arguments.length > 2) return invalidQueryBuilder()
92
98
 
93
99
  if (arguments.length === 1) {
100
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- arguments-based overload dispatch requires manual narrowing
94
101
  const params = arguments[0] as QueryBuilder.OrderByParams<TTableDef>
95
102
  return makeQueryBuilder(tableDef, {
96
103
  ...ast,
@@ -98,8 +105,10 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
98
105
  })
99
106
  }
100
107
 
108
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- arguments-based overload dispatch requires manual narrowing
101
109
  const [col, direction] = arguments as any as [keyof TTableDef['sqliteDef']['columns'] & string, 'asc' | 'desc']
102
110
 
111
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- query builder return type depends on AST state; consumer type safety enforced by api.ts
103
112
  return makeQueryBuilder(tableDef, {
104
113
  ...ast,
105
114
  orderBy: [...ast.orderBy, { col, direction }],
@@ -116,7 +125,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
116
125
  return makeQueryBuilder(tableDef, { ...ast, offset: Option.some(offset) })
117
126
  },
118
127
  count: () => {
119
- if (isRowQuery(ast) || ast._tag === 'InsertQuery' || ast._tag === 'UpdateQuery' || ast._tag === 'DeleteQuery')
128
+ if (isRowQuery(ast) === true || ast._tag === 'InsertQuery' || ast._tag === 'UpdateQuery' || ast._tag === 'DeleteQuery')
120
129
  return invalidQueryBuilder()
121
130
 
122
131
  return makeQueryBuilder(tableDef, {
@@ -171,6 +180,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
171
180
  insert: (values) => {
172
181
  const filteredValues = Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined))
173
182
 
183
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- query builder return type depends on AST state; consumer type safety enforced by api.ts
174
184
  return makeQueryBuilder(tableDef, {
175
185
  _tag: 'InsertQuery',
176
186
  tableDef,
@@ -185,7 +195,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
185
195
  action: 'ignore' | 'replace' | 'update',
186
196
  updateValues?: Record<string, unknown>,
187
197
  ) => {
188
- const targets = Array.isArray(targetOrTargets) ? targetOrTargets : [targetOrTargets]
198
+ const targets = Array.isArray(targetOrTargets) === true ? targetOrTargets : [targetOrTargets]
189
199
 
190
200
  assertInsertQueryBuilderAst(ast)
191
201
 
@@ -199,6 +209,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
199
209
  Match.exhaustive,
200
210
  )
201
211
 
212
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- query builder return type depends on AST state; consumer type safety enforced by api.ts
202
213
  return makeQueryBuilder(tableDef, {
203
214
  ...ast,
204
215
  onConflict,
@@ -208,6 +219,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
208
219
  returning: (...columns) => {
209
220
  assertWriteQueryBuilderAst(ast)
210
221
 
222
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- query builder return type depends on AST state; consumer type safety enforced by api.ts
211
223
  return makeQueryBuilder(tableDef, {
212
224
  ...ast,
213
225
  returning: columns,
@@ -221,6 +233,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
221
233
  // Preserve where clauses if coming from a SelectQuery
222
234
  const whereClause = ast._tag === 'SelectQuery' ? ast.where : []
223
235
 
236
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- query builder return type depends on AST state; consumer type safety enforced by api.ts
224
237
  return makeQueryBuilder(tableDef, {
225
238
  _tag: 'UpdateQuery',
226
239
  tableDef,
@@ -235,6 +248,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
235
248
  // Preserve where clauses if coming from a SelectQuery
236
249
  const whereClause = ast._tag === 'SelectQuery' ? ast.where : []
237
250
 
251
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- query builder return type depends on AST state; consumer type safety enforced by api.ts
238
252
  return makeQueryBuilder(tableDef, {
239
253
  _tag: 'DeleteQuery',
240
254
  tableDef,
@@ -248,6 +262,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
248
262
  return {
249
263
  [QueryBuilderTypeId]: QueryBuilderTypeId,
250
264
  [QueryBuilderAstSymbol]: ast,
265
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- phantom type field for generic inference only
251
266
  ResultType: 'only-for-type-inference' as TResult,
252
267
  asSql: () => astToSql(ast),
253
268
  toString: () => {
@@ -277,19 +292,19 @@ const emptyAst = (tableDef: TableDefBase): QueryBuilderAst.SelectQuery => ({
277
292
 
278
293
  // Helper functions
279
294
 
280
- function assertSelectQueryBuilderAst(ast: QueryBuilderAst): asserts ast is QueryBuilderAst.SelectQuery {
295
+ const assertSelectQueryBuilderAst: (ast: QueryBuilderAst) => asserts ast is QueryBuilderAst.SelectQuery = (ast) => {
281
296
  if (ast._tag !== 'SelectQuery') {
282
297
  return shouldNeverHappen(`Expected SelectQuery but got ${ast._tag}`)
283
298
  }
284
299
  }
285
300
 
286
- function assertInsertQueryBuilderAst(ast: QueryBuilderAst): asserts ast is QueryBuilderAst.InsertQuery {
301
+ const assertInsertQueryBuilderAst: (ast: QueryBuilderAst) => asserts ast is QueryBuilderAst.InsertQuery = (ast) => {
287
302
  if (ast._tag !== 'InsertQuery') {
288
303
  return shouldNeverHappen(`Expected InsertQuery but got ${ast._tag}`)
289
304
  }
290
305
  }
291
306
 
292
- function assertWriteQueryBuilderAst(ast: QueryBuilderAst): asserts ast is QueryBuilderAst.WriteQuery {
307
+ const assertWriteQueryBuilderAst: (ast: QueryBuilderAst) => asserts ast is QueryBuilderAst.WriteQuery = (ast) => {
293
308
  if (ast._tag !== 'InsertQuery' && ast._tag !== 'UpdateQuery' && ast._tag !== 'DeleteQuery') {
294
309
  return shouldNeverHappen(`Expected WriteQuery but got ${ast._tag}`)
295
310
  }
@@ -298,7 +313,7 @@ function assertWriteQueryBuilderAst(ast: QueryBuilderAst): asserts ast is QueryB
298
313
  const isRowQuery = (ast: QueryBuilderAst): ast is QueryBuilderAst.RowQuery => ast._tag === 'RowQuery'
299
314
 
300
315
  export const invalidQueryBuilder = (msg?: string) => {
301
- return shouldNeverHappen(`Invalid query builder${msg ? `: ${msg}` : ''}`)
316
+ return shouldNeverHappen(`Invalid query builder${msg !== undefined ? `: ${msg}` : ''}`)
302
317
  }
303
318
 
304
319
  export const getResultSchema = (qb: QueryBuilder<any, any, any>): Schema.Schema<any> => {
@@ -328,7 +343,7 @@ export const getResultSchema = (qb: QueryBuilder<any, any, any>): Schema.Schema<
328
343
  case 'UpdateQuery':
329
344
  case 'DeleteQuery': {
330
345
  // For write operations with RETURNING clause, we need to return the appropriate schema
331
- if (queryAst.returning && queryAst.returning.length > 0) {
346
+ if (queryAst.returning !== undefined && queryAst.returning.length > 0) {
332
347
  // Create a schema for the returned columns
333
348
  return queryAst.tableDef.rowSchema.pipe(Schema.pick(...queryAst.returning), Schema.Array)
334
349
  }
@@ -344,8 +359,7 @@ export const getResultSchema = (qb: QueryBuilder<any, any, any>): Schema.Schema<
344
359
  Schema.headOrElse(),
345
360
  )
346
361
  }
347
- default: {
348
- casesHandled(queryAst)
349
- }
362
+ default:
363
+ return casesHandled(queryAst)
350
364
  }
351
365
  }
@@ -13,7 +13,7 @@ export const getDefaultValuesEncoded = <TTableDef extends TableDefBase>(
13
13
  ReadonlyRecord.filter((col, key) => {
14
14
  if (fallbackValues?.[key] !== undefined) return true
15
15
  if (key === 'id') return false
16
- return col!.default._tag === 'None' || SqliteDsl.isSqlDefaultValue(col!.default.value) === false
16
+ return col!.default._tag === 'None' || !SqliteDsl.isSqlDefaultValue(col!.default.value)
17
17
  }),
18
18
  ReadonlyRecord.map((column, columnName) => {
19
19
  if (fallbackValues?.[columnName] !== undefined) return fallbackValues[columnName]
@@ -39,7 +39,7 @@ export const getDefaultValuesDecoded = <TTableDef extends TableDefBase>(
39
39
  ReadonlyRecord.filter((col, key) => {
40
40
  if (fallbackValues?.[key] !== undefined) return true
41
41
  if (key === 'id') return false
42
- return col!.default._tag === 'None' || SqliteDsl.isSqlDefaultValue(col!.default.value) === false
42
+ return col!.default._tag === 'None' || !SqliteDsl.isSqlDefaultValue(col!.default.value)
43
43
  }),
44
44
  ReadonlyRecord.map((column, columnName) => {
45
45
  if (fallbackValues?.[columnName] !== undefined) return fallbackValues[columnName]
@@ -1,5 +1,8 @@
1
+ import { objectToString } from '@livestore/utils'
2
+
1
3
  import { Schema } from '@livestore/utils/effect'
2
- import { describe, expect, it } from 'vitest'
4
+ import { describe, expect, expectTypeOf, it } from 'vitest'
5
+
3
6
  import { State } from '../../mod.ts'
4
7
 
5
8
  describe('table function overloads', () => {
@@ -79,11 +82,11 @@ describe('table function overloads', () => {
79
82
  expect(todosTable.sqliteDef.columns).toHaveProperty('optionalComplex')
80
83
 
81
84
  expect(todosTable.sqliteDef.columns.optionalBoolean.nullable).toBe(true)
82
- expect(todosTable.sqliteDef.columns.optionalBoolean.schema.toString()).toBe('(number <-> boolean) | null')
85
+ expect(objectToString(todosTable.sqliteDef.columns.optionalBoolean.schema)).toBe('(number <-> boolean) | null')
83
86
  expect((todosTable.rowSchema as any).fields.optionalBoolean.toString()).toBe('(number <-> boolean) | null')
84
87
 
85
88
  expect(todosTable.sqliteDef.columns.optionalComplex.nullable).toBe(true)
86
- expect(todosTable.sqliteDef.columns.optionalComplex.schema.toString()).toBe(
89
+ expect(objectToString(todosTable.sqliteDef.columns.optionalComplex.schema)).toBe(
87
90
  '(parseJson <-> { readonly color: string } | undefined) | null',
88
91
  )
89
92
  expect((todosTable.rowSchema as any).fields.optionalComplex.toString()).toBe(
@@ -91,6 +94,22 @@ describe('table function overloads', () => {
91
94
  )
92
95
  })
93
96
 
97
+ it('should allow explicit first two generic arguments without options generic', () => {
98
+ const columns = {
99
+ id: State.SQLite.text({ primaryKey: true }),
100
+ text: State.SQLite.text({ default: '' }),
101
+ }
102
+
103
+ const todosTable = State.SQLite.table<'todos', typeof columns>({
104
+ name: 'todos',
105
+ columns,
106
+ })
107
+
108
+ expect(todosTable.sqliteDef.name).toBe('todos')
109
+ expect(todosTable.sqliteDef.columns).toHaveProperty('id')
110
+ expect(todosTable.sqliteDef.columns).toHaveProperty('text')
111
+ })
112
+
94
113
  it('should work with schema parameter', () => {
95
114
  const TodoSchema = Schema.Struct({
96
115
  id: Schema.String,
@@ -301,6 +320,50 @@ describe('table function overloads', () => {
301
320
  expect(userTable.sqliteDef.columns.metadata.nullable).toBe(true)
302
321
  })
303
322
 
323
+ it('should allow omitting nullable fields in insert()', () => {
324
+ const UserSchema = Schema.Struct({
325
+ id: Schema.String.pipe(State.SQLite.withPrimaryKey),
326
+ undefined: Schema.Undefined,
327
+ null: Schema.Null,
328
+ undefinedOrString: Schema.UndefinedOr(Schema.String),
329
+ nullOrString: Schema.NullOr(Schema.String),
330
+ optionalString: Schema.optional(Schema.String),
331
+ optionalNullOrString: Schema.optional(Schema.NullOr(Schema.String)),
332
+ })
333
+
334
+ const usersTable = State.SQLite.table({
335
+ name: 'users',
336
+ schema: UserSchema,
337
+ })
338
+
339
+ // Non-nullable fields (id) are required — omitting id should be rejected
340
+ expectTypeOf<{ undefined: undefined }>().not.toExtend<Parameters<typeof usersTable.insert>[0]>()
341
+
342
+ // Nullable fields (NullOr, optional+NullOr) are omittable — SQL defaults to NULL
343
+ expectTypeOf(usersTable.insert)
344
+ .toBeCallableWith({ id: '1' })
345
+ .toBeCallableWith({ id: '1', undefined: undefined })
346
+ .toBeCallableWith({ id: '1', undefined: undefined, null: null })
347
+ .toBeCallableWith({ id: '1', undefined: undefined, null: null, undefinedOrString: undefined })
348
+ .toBeCallableWith({ id: '1', undefined: undefined, null: null, undefinedOrString: 'string' })
349
+ .toBeCallableWith({ id: '1', undefined: undefined, null: null, undefinedOrString: 'string', nullOrString: null })
350
+ .toBeCallableWith({ id: '1', undefined: undefined, null: null, undefinedOrString: 'string', nullOrString: 'string' })
351
+ .toBeCallableWith({ id: '1', undefined: undefined, null: null, undefinedOrString: 'string', nullOrString: 'string', optionalString: 'string' })
352
+ .toBeCallableWith({ id: '1', undefined: undefined, null: null, undefinedOrString: 'string', nullOrString: 'string', optionalString: 'string', optionalNullOrString: null })
353
+ .toBeCallableWith({ id: '1', undefined: undefined, null: null, undefinedOrString: 'string', nullOrString: 'string', optionalString: 'string', optionalNullOrString: 'string' })
354
+
355
+ expect(() => usersTable.insert({ id: '1' }).asSql()).not.toThrow()
356
+ expect(() => usersTable.insert({ id: '1', undefined: undefined }).asSql()).not.toThrow()
357
+ expect(() => usersTable.insert({ id: '1', undefined: undefined, null: null }).asSql()).not.toThrow()
358
+ expect(() => usersTable.insert({ id: '1', undefined: undefined, null: null, undefinedOrString: undefined }).asSql()).not.toThrow()
359
+ expect(() => usersTable.insert({ id: '1', undefined: undefined, null: null, undefinedOrString: 'string' }).asSql()).not.toThrow()
360
+ expect(() => usersTable.insert({ id: '1', undefined: undefined, null: null, undefinedOrString: 'string', nullOrString: null }).asSql()).not.toThrow()
361
+ expect(() => usersTable.insert({ id: '1', undefined: undefined, null: null, undefinedOrString: 'string', nullOrString: 'string' }).asSql()).not.toThrow()
362
+ expect(() => usersTable.insert({ id: '1', undefined: undefined, null: null, undefinedOrString: 'string', nullOrString: 'string', optionalString: 'string' }).asSql()).not.toThrow()
363
+ expect(() => usersTable.insert({ id: '1', undefined: undefined, null: null, undefinedOrString: 'string', nullOrString: 'string', optionalString: 'string', optionalNullOrString: null }).asSql()).not.toThrow()
364
+ expect(() => usersTable.insert({ id: '1', undefined: undefined, null: null, undefinedOrString: 'string', nullOrString: 'string', optionalString: 'string', optionalNullOrString: 'string' }).asSql()).not.toThrow()
365
+ })
366
+
304
367
  it('supports discriminated unions with parsed JSON payloads', () => {
305
368
  const CircleDataSchema = Schema.Struct({
306
369
  radius: Schema.Number,
@@ -327,7 +390,7 @@ describe('table function overloads', () => {
327
390
 
328
391
  expect(shapes.sqliteDef.columns.kind.columnType).toBe('text')
329
392
 
330
- const kindSchema = shapes.sqliteDef.columns.kind.schema.toString()
393
+ const kindSchema = objectToString(shapes.sqliteDef.columns.kind.schema)
331
394
  expect(kindSchema).toContain('"circle" | "square"')
332
395
 
333
396
  expect(() =>
@@ -168,10 +168,7 @@ export function table<
168
168
  name: TName
169
169
  schema: TSchema
170
170
  } & Partial<TOptionsInput>,
171
- ): TableDef<
172
- SqliteTableDefForSchemaInput<TName, Schema.Schema.Type<TSchema>, Schema.Schema.Encoded<TSchema>, TSchema>,
173
- TableOptions
174
- >
171
+ ): TableDef<SqliteTableDefForSchemaInput<TName, Schema.Schema.Type<TSchema>, Schema.Schema.Encoded<TSchema>, TSchema>>
175
172
 
176
173
  // Overload 3: With schema and no name (uses schema annotations)
177
174
  export function table<
@@ -181,10 +178,7 @@ export function table<
181
178
  args: {
182
179
  schema: TSchema
183
180
  } & Partial<TOptionsInput>,
184
- ): TableDef<
185
- SqliteTableDefForSchemaInput<string, Schema.Schema.Type<TSchema>, Schema.Schema.Encoded<TSchema>, TSchema>,
186
- TableOptions
187
- >
181
+ ): TableDef<SqliteTableDefForSchemaInput<string, Schema.Schema.Type<TSchema>, Schema.Schema.Encoded<TSchema>, TSchema>>
188
182
 
189
183
  // Implementation
190
184
  export function table<
@@ -216,9 +210,7 @@ export function table<
216
210
  if ('columns' in args) {
217
211
  tableName = args.name
218
212
  const columnOrColumns = args.columns
219
- columns = (
220
- SqliteDsl.isColumnDefinition(columnOrColumns) ? { value: columnOrColumns } : columnOrColumns
221
- ) as SqliteDsl.Columns
213
+ columns = SqliteDsl.isColumnDefinition(columnOrColumns) === true ? { value: columnOrColumns } : columnOrColumns
222
214
  additionalIndexes = []
223
215
  } else if ('schema' in args) {
224
216
  const result = schemaFieldsToColumns(Schema.getResolvedPropertySignatures(args.schema))
@@ -377,15 +369,16 @@ export type ToColumns<TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefin
377
369
  : never
378
370
 
379
371
  export declare namespace SchemaToColumns {
372
+ /** Checks if `null` or `undefined` is assignable to `T`, matching the runtime nullable detection. */
373
+ type IsNullable<T> = null extends T ? true : undefined extends T ? true : false
374
+
380
375
  // Type helper to create column definition with proper schema
381
- export type ColumnDefForType<TEncoded, TType> = SqliteDsl.ColumnDefinition<TEncoded, TType>
376
+ export type ColumnDefForType<TEncoded, TType> = SqliteDsl.ColumnDefinition<TEncoded, TType, IsNullable<TEncoded>>
382
377
 
383
- // Create columns type from schema Type and Encoded
384
378
  export type FromTypes<TType, TEncoded> =
385
379
  TEncoded extends Record<string, any>
386
380
  ? {
387
- [K in keyof TEncoded]-?: ColumnDefForType<
388
- TEncoded[K],
381
+ [K in keyof TEncoded]-?: ColumnDefForType<TEncoded[K],
389
382
  TType extends Record<string, any> ? (K extends keyof TType ? TType[K] : TEncoded[K]) : TEncoded[K]
390
383
  >
391
384
  }
@@ -17,7 +17,7 @@ export namespace UnknownEvents {
17
17
  export type Callback = (
18
18
  context: UnknownEventContext,
19
19
  error: UnknownEventError,
20
- ) => Effect.SyncOrPromiseOrEffect<void, unknown, never>
20
+ ) => Effect.SyncOrPromiseOrEffect<void, unknown>
21
21
 
22
22
  export type HandlingConfig =
23
23
  | { readonly strategy: 'warn' }
@@ -60,7 +60,7 @@ const handleUnknownEvent = ({
60
60
 
61
61
  switch (config.strategy) {
62
62
  case 'fail': {
63
- return yield* Effect.fail(error)
63
+ return yield* error
64
64
  }
65
65
  case 'warn': {
66
66
  yield* Effect.logWarning('@livestore/common:schema:unknown-event', context)
@@ -1,5 +1,7 @@
1
- import { Effect, Option, Schema } from '@livestore/utils/effect'
2
1
  import { describe, expect, it } from 'vitest'
2
+
3
+ import { Effect, Option, Schema } from '@livestore/utils/effect'
4
+
3
5
  import { SqliteAst } from '../../schema/state/sqlite/db-schema/mod.ts'
4
6
  import type { PreparedStatement, SqliteDb } from '../../sqlite-types.ts'
5
7
  import type { PreparedBindValues } from '../../util.ts'
@@ -13,7 +13,7 @@ export const dbExecute = (db: SqliteDb, queryStr: string, bindValues?: ParamsObj
13
13
  // cachedStmts.set(queryStr, stmt)
14
14
  // }
15
15
 
16
- const preparedBindValues = bindValues ? prepareBindValues(bindValues, queryStr) : undefined
16
+ const preparedBindValues = bindValues !== undefined ? prepareBindValues(bindValues, queryStr) : undefined
17
17
 
18
18
  try {
19
19
  stmt.execute(preparedBindValues)
@@ -34,7 +34,7 @@ export const dbSelect = <T>(db: SqliteDb, queryStr: string, bindValues?: ParamsO
34
34
  // cachedStmts.set(queryStr, stmt)
35
35
  // }
36
36
 
37
- const res = stmt.select<T>(bindValues ? prepareBindValues(bindValues, queryStr) : undefined)
37
+ const res = stmt.select<T>(bindValues !== undefined ? prepareBindValues(bindValues, queryStr) : undefined)
38
38
  stmt.finalize()
39
39
  return res
40
40
  }
@@ -200,7 +200,7 @@ export const migrateTable = ({
200
200
  )
201
201
 
202
202
  const createIndexFromDefinition = (tableName: string, index: SqliteAst.Index) => {
203
- const uniqueStr = index.unique ? 'UNIQUE' : ''
203
+ const uniqueStr = index.unique === true ? 'UNIQUE' : ''
204
204
  return sql`create ${uniqueStr} index if not exists "${index.name}" on "${tableName}" (${index.columns
205
205
  .map((col) => `"${col}"`)
206
206
  .join(', ')})`