@livestore/common 0.0.0-snapshot-909cdd1ac2fd591945c2be2b0f53e14d87f3c9d4

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 (220) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/__tests__/fixture.d.ts +72 -0
  3. package/dist/__tests__/fixture.d.ts.map +1 -0
  4. package/dist/__tests__/fixture.js +16 -0
  5. package/dist/__tests__/fixture.js.map +1 -0
  6. package/dist/adapter-types.d.ts +202 -0
  7. package/dist/adapter-types.d.ts.map +1 -0
  8. package/dist/adapter-types.js +49 -0
  9. package/dist/adapter-types.js.map +1 -0
  10. package/dist/bounded-collections.d.ts +36 -0
  11. package/dist/bounded-collections.d.ts.map +1 -0
  12. package/dist/bounded-collections.js +98 -0
  13. package/dist/bounded-collections.js.map +1 -0
  14. package/dist/debug-info.d.ts +122 -0
  15. package/dist/debug-info.d.ts.map +1 -0
  16. package/dist/debug-info.js +47 -0
  17. package/dist/debug-info.js.map +1 -0
  18. package/dist/derived-mutations.d.ts +109 -0
  19. package/dist/derived-mutations.d.ts.map +1 -0
  20. package/dist/derived-mutations.js +54 -0
  21. package/dist/derived-mutations.js.map +1 -0
  22. package/dist/derived-mutations.test.d.ts +2 -0
  23. package/dist/derived-mutations.test.d.ts.map +1 -0
  24. package/dist/derived-mutations.test.js +93 -0
  25. package/dist/derived-mutations.test.js.map +1 -0
  26. package/dist/devtools/devtools-bridge.d.ts +12 -0
  27. package/dist/devtools/devtools-bridge.d.ts.map +1 -0
  28. package/dist/devtools/devtools-bridge.js +2 -0
  29. package/dist/devtools/devtools-bridge.js.map +1 -0
  30. package/dist/devtools/devtools-messages.d.ts +705 -0
  31. package/dist/devtools/devtools-messages.d.ts.map +1 -0
  32. package/dist/devtools/devtools-messages.js +178 -0
  33. package/dist/devtools/devtools-messages.js.map +1 -0
  34. package/dist/devtools/devtools-window-message.d.ts +29 -0
  35. package/dist/devtools/devtools-window-message.d.ts.map +1 -0
  36. package/dist/devtools/devtools-window-message.js +33 -0
  37. package/dist/devtools/devtools-window-message.js.map +1 -0
  38. package/dist/devtools/index.d.ts +42 -0
  39. package/dist/devtools/index.d.ts.map +1 -0
  40. package/dist/devtools/index.js +49 -0
  41. package/dist/devtools/index.js.map +1 -0
  42. package/dist/index.d.ts +19 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +15 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/init-singleton-tables.d.ts +4 -0
  47. package/dist/init-singleton-tables.d.ts.map +1 -0
  48. package/dist/init-singleton-tables.js +16 -0
  49. package/dist/init-singleton-tables.js.map +1 -0
  50. package/dist/mutation.d.ts +13 -0
  51. package/dist/mutation.d.ts.map +1 -0
  52. package/dist/mutation.js +43 -0
  53. package/dist/mutation.js.map +1 -0
  54. package/dist/query-info.d.ts +47 -0
  55. package/dist/query-info.d.ts.map +1 -0
  56. package/dist/query-info.js +38 -0
  57. package/dist/query-info.js.map +1 -0
  58. package/dist/rehydrate-from-mutationlog.d.ts +14 -0
  59. package/dist/rehydrate-from-mutationlog.d.ts.map +1 -0
  60. package/dist/rehydrate-from-mutationlog.js +72 -0
  61. package/dist/rehydrate-from-mutationlog.js.map +1 -0
  62. package/dist/schema/index.d.ts +60 -0
  63. package/dist/schema/index.d.ts.map +1 -0
  64. package/dist/schema/index.js +66 -0
  65. package/dist/schema/index.js.map +1 -0
  66. package/dist/schema/mutations.d.ts +227 -0
  67. package/dist/schema/mutations.d.ts.map +1 -0
  68. package/dist/schema/mutations.js +68 -0
  69. package/dist/schema/mutations.js.map +1 -0
  70. package/dist/schema/schema-helpers.d.ts +4 -0
  71. package/dist/schema/schema-helpers.d.ts.map +1 -0
  72. package/dist/schema/schema-helpers.js +30 -0
  73. package/dist/schema/schema-helpers.js.map +1 -0
  74. package/dist/schema/system-tables.d.ts +331 -0
  75. package/dist/schema/system-tables.d.ts.map +1 -0
  76. package/dist/schema/system-tables.js +46 -0
  77. package/dist/schema/system-tables.js.map +1 -0
  78. package/dist/schema/table-def.d.ts +135 -0
  79. package/dist/schema/table-def.d.ts.map +1 -0
  80. package/dist/schema/table-def.js +70 -0
  81. package/dist/schema/table-def.js.map +1 -0
  82. package/dist/schema-management/common.d.ts +13 -0
  83. package/dist/schema-management/common.d.ts.map +1 -0
  84. package/dist/schema-management/common.js +25 -0
  85. package/dist/schema-management/common.js.map +1 -0
  86. package/dist/schema-management/migrations.d.ts +23 -0
  87. package/dist/schema-management/migrations.d.ts.map +1 -0
  88. package/dist/schema-management/migrations.js +116 -0
  89. package/dist/schema-management/migrations.js.map +1 -0
  90. package/dist/schema-management/validate-mutation-defs.d.ts +8 -0
  91. package/dist/schema-management/validate-mutation-defs.d.ts.map +1 -0
  92. package/dist/schema-management/validate-mutation-defs.js +39 -0
  93. package/dist/schema-management/validate-mutation-defs.js.map +1 -0
  94. package/dist/sql-queries/index.d.ts +4 -0
  95. package/dist/sql-queries/index.d.ts.map +1 -0
  96. package/dist/sql-queries/index.js +4 -0
  97. package/dist/sql-queries/index.js.map +1 -0
  98. package/dist/sql-queries/misc.d.ts +2 -0
  99. package/dist/sql-queries/misc.d.ts.map +1 -0
  100. package/dist/sql-queries/misc.js +2 -0
  101. package/dist/sql-queries/misc.js.map +1 -0
  102. package/dist/sql-queries/sql-queries.d.ts +72 -0
  103. package/dist/sql-queries/sql-queries.d.ts.map +1 -0
  104. package/dist/sql-queries/sql-queries.js +191 -0
  105. package/dist/sql-queries/sql-queries.js.map +1 -0
  106. package/dist/sql-queries/sql-query-builder.d.ts +47 -0
  107. package/dist/sql-queries/sql-query-builder.d.ts.map +1 -0
  108. package/dist/sql-queries/sql-query-builder.js +60 -0
  109. package/dist/sql-queries/sql-query-builder.js.map +1 -0
  110. package/dist/sql-queries/types.d.ts +50 -0
  111. package/dist/sql-queries/types.d.ts.map +1 -0
  112. package/dist/sql-queries/types.js +5 -0
  113. package/dist/sql-queries/types.js.map +1 -0
  114. package/dist/sync/index.d.ts +2 -0
  115. package/dist/sync/index.d.ts.map +1 -0
  116. package/dist/sync/index.js +2 -0
  117. package/dist/sync/index.js.map +1 -0
  118. package/dist/sync/next/compact-events.d.ts +15 -0
  119. package/dist/sync/next/compact-events.d.ts.map +1 -0
  120. package/dist/sync/next/compact-events.js +176 -0
  121. package/dist/sync/next/compact-events.js.map +1 -0
  122. package/dist/sync/next/facts.d.ts +37 -0
  123. package/dist/sync/next/facts.d.ts.map +1 -0
  124. package/dist/sync/next/facts.js +156 -0
  125. package/dist/sync/next/facts.js.map +1 -0
  126. package/dist/sync/next/graphology.d.ts +8 -0
  127. package/dist/sync/next/graphology.d.ts.map +1 -0
  128. package/dist/sync/next/graphology.js +36 -0
  129. package/dist/sync/next/graphology.js.map +1 -0
  130. package/dist/sync/next/graphology_.d.ts +3 -0
  131. package/dist/sync/next/graphology_.d.ts.map +1 -0
  132. package/dist/sync/next/graphology_.js +3 -0
  133. package/dist/sync/next/graphology_.js.map +1 -0
  134. package/dist/sync/next/history-dag.d.ts +30 -0
  135. package/dist/sync/next/history-dag.d.ts.map +1 -0
  136. package/dist/sync/next/history-dag.js +69 -0
  137. package/dist/sync/next/history-dag.js.map +1 -0
  138. package/dist/sync/next/mod.d.ts +5 -0
  139. package/dist/sync/next/mod.d.ts.map +1 -0
  140. package/dist/sync/next/mod.js +5 -0
  141. package/dist/sync/next/mod.js.map +1 -0
  142. package/dist/sync/next/rebase-events.d.ts +27 -0
  143. package/dist/sync/next/rebase-events.d.ts.map +1 -0
  144. package/dist/sync/next/rebase-events.js +41 -0
  145. package/dist/sync/next/rebase-events.js.map +1 -0
  146. package/dist/sync/next/test/compact-events.calculator.test.d.ts +2 -0
  147. package/dist/sync/next/test/compact-events.calculator.test.d.ts.map +1 -0
  148. package/dist/sync/next/test/compact-events.calculator.test.js +101 -0
  149. package/dist/sync/next/test/compact-events.calculator.test.js.map +1 -0
  150. package/dist/sync/next/test/compact-events.test.d.ts +2 -0
  151. package/dist/sync/next/test/compact-events.test.d.ts.map +1 -0
  152. package/dist/sync/next/test/compact-events.test.js +201 -0
  153. package/dist/sync/next/test/compact-events.test.js.map +1 -0
  154. package/dist/sync/next/test/mod.d.ts +2 -0
  155. package/dist/sync/next/test/mod.d.ts.map +1 -0
  156. package/dist/sync/next/test/mod.js +2 -0
  157. package/dist/sync/next/test/mod.js.map +1 -0
  158. package/dist/sync/next/test/mutation-fixtures.d.ts +73 -0
  159. package/dist/sync/next/test/mutation-fixtures.d.ts.map +1 -0
  160. package/dist/sync/next/test/mutation-fixtures.js +161 -0
  161. package/dist/sync/next/test/mutation-fixtures.js.map +1 -0
  162. package/dist/sync/sync.d.ts +45 -0
  163. package/dist/sync/sync.d.ts.map +1 -0
  164. package/dist/sync/sync.js +12 -0
  165. package/dist/sync/sync.js.map +1 -0
  166. package/dist/util.d.ts +25 -0
  167. package/dist/util.d.ts.map +1 -0
  168. package/dist/util.js +38 -0
  169. package/dist/util.js.map +1 -0
  170. package/dist/version.d.ts +10 -0
  171. package/dist/version.d.ts.map +1 -0
  172. package/dist/version.js +12 -0
  173. package/dist/version.js.map +1 -0
  174. package/package.json +61 -0
  175. package/src/__tests__/fixture.ts +23 -0
  176. package/src/adapter-types.ts +216 -0
  177. package/src/ambient.d.ts +3 -0
  178. package/src/bounded-collections.ts +121 -0
  179. package/src/debug-info.ts +76 -0
  180. package/src/derived-mutations.test.ts +101 -0
  181. package/src/derived-mutations.ts +170 -0
  182. package/src/devtools/devtools-bridge.ts +13 -0
  183. package/src/devtools/devtools-messages.ts +247 -0
  184. package/src/devtools/devtools-window-message.ts +27 -0
  185. package/src/devtools/index.ts +49 -0
  186. package/src/index.ts +20 -0
  187. package/src/init-singleton-tables.ts +24 -0
  188. package/src/mutation.ts +69 -0
  189. package/src/query-info.ts +104 -0
  190. package/src/rehydrate-from-mutationlog.ts +131 -0
  191. package/src/schema/index.ts +144 -0
  192. package/src/schema/mutations.ts +313 -0
  193. package/src/schema/schema-helpers.ts +49 -0
  194. package/src/schema/system-tables.ts +84 -0
  195. package/src/schema/table-def.ts +312 -0
  196. package/src/schema-management/common.ts +44 -0
  197. package/src/schema-management/migrations.ts +188 -0
  198. package/src/schema-management/validate-mutation-defs.ts +63 -0
  199. package/src/sql-queries/index.ts +3 -0
  200. package/src/sql-queries/misc.ts +2 -0
  201. package/src/sql-queries/sql-queries.ts +359 -0
  202. package/src/sql-queries/sql-query-builder.ts +135 -0
  203. package/src/sql-queries/types.ts +97 -0
  204. package/src/sync/index.ts +1 -0
  205. package/src/sync/next/ambient.d.ts +3 -0
  206. package/src/sync/next/compact-events.ts +218 -0
  207. package/src/sync/next/facts.ts +229 -0
  208. package/src/sync/next/graphology.ts +49 -0
  209. package/src/sync/next/graphology_.ts +2 -0
  210. package/src/sync/next/history-dag.ts +109 -0
  211. package/src/sync/next/mod.ts +4 -0
  212. package/src/sync/next/rebase-events.ts +97 -0
  213. package/src/sync/next/test/compact-events.calculator.test.ts +121 -0
  214. package/src/sync/next/test/compact-events.test.ts +232 -0
  215. package/src/sync/next/test/mod.ts +1 -0
  216. package/src/sync/next/test/mutation-fixtures.ts +230 -0
  217. package/src/sync/sync.ts +46 -0
  218. package/src/util.ts +56 -0
  219. package/src/version.ts +13 -0
  220. package/tsconfig.json +11 -0
@@ -0,0 +1,312 @@
1
+ import type { Nullable, PrettifyFlat } from '@livestore/db-schema'
2
+ import { SqliteDsl } from '@livestore/db-schema'
3
+ import { shouldNeverHappen } from '@livestore/utils'
4
+ import { ReadonlyRecord, Schema } from '@livestore/utils/effect'
5
+
6
+ import type { DerivedMutationHelperFns } from '../derived-mutations.js'
7
+ import { makeDerivedMutationDefsForTable } from '../derived-mutations.js'
8
+
9
+ export const { blob, boolean, column, datetime, integer, isColumnDefinition, json, real, text } = SqliteDsl
10
+
11
+ export { type SqliteDsl } from '@livestore/db-schema'
12
+
13
+ export type StateType = 'singleton' | 'dynamic'
14
+
15
+ export type DefaultSqliteTableDef = SqliteDsl.TableDefinition<string, SqliteDsl.Columns>
16
+ export type DefaultSqliteTableDefConstrained = SqliteDsl.TableDefinition<string, SqliteDsl.ConstraintColumns>
17
+
18
+ // export type TableDefConstraint<
19
+ // TSqliteDef extends DefaultSqliteTableDef = DefaultSqliteTableDef,
20
+ // TIsSingleColumn extends boolean = boolean,
21
+ // TOptions extends TableOptions = TableOptions,
22
+ // > = TableDefBase<TSqliteDef, TIsSingleColumn, TOptions> & { schema: Schema.Schema<any> }
23
+
24
+ // /**
25
+ // * NOTE in the past we used to have a single `TableDef` but there are some TS issues when indroducing
26
+ // * `schema: SqliteDsl.StructSchemaForColumns<TSqliteDef>` so we split it into two types
27
+ // * and only use `TableDefConstraint` in some places
28
+ // */
29
+ // export type TableDefBase<
30
+ // TSqliteDef extends DefaultSqliteTableDef = DefaultSqliteTableDef,
31
+ // TIsSingleColumn extends boolean = boolean,
32
+ // TOptions extends TableOptions = TableOptions,
33
+ // > = {
34
+ // sqliteDef: TSqliteDef
35
+ // // schema: SqliteDsl.StructSchemaForColumns<TSqliteDef>
36
+ // // schema: any;
37
+ // isSingleColumn: TIsSingleColumn
38
+ // options: TOptions
39
+ // }
40
+
41
+ export type TableDef<
42
+ TSqliteDef extends DefaultSqliteTableDef = DefaultSqliteTableDefConstrained,
43
+ TIsSingleColumn extends boolean = boolean,
44
+ TOptions extends TableOptions = TableOptions,
45
+ // NOTE we're not using `SqliteDsl.StructSchemaForColumns<TSqliteDef['columns']>`
46
+ // as we don't want the alias type for users to show up
47
+ TSchema = Schema.Schema<
48
+ SqliteDsl.AnyIfConstained<
49
+ TSqliteDef['columns'],
50
+ { readonly [K in keyof TSqliteDef['columns']]: Schema.Schema.Type<TSqliteDef['columns'][K]['schema']> }
51
+ >,
52
+ SqliteDsl.AnyIfConstained<
53
+ TSqliteDef['columns'],
54
+ { readonly [K in keyof TSqliteDef['columns']]: Schema.Schema.Encoded<TSqliteDef['columns'][K]['schema']> }
55
+ >
56
+ >,
57
+ > = {
58
+ sqliteDef: TSqliteDef
59
+ // TODO move this into options (for now it's duplicated)
60
+ isSingleColumn: TIsSingleColumn
61
+ options: TOptions
62
+ schema: TSchema
63
+ } & (TOptions['deriveMutations']['enabled'] extends true
64
+ ? DerivedMutationHelperFns<TSqliteDef['columns'], TOptions>
65
+ : {})
66
+
67
+ export type TableOptionsInput = Partial<
68
+ Omit<TableOptions, 'isSingleColumn' | 'deriveMutations'> & {
69
+ indexes: SqliteDsl.Index[]
70
+ deriveMutations:
71
+ | boolean
72
+ | {
73
+ enabled: true
74
+ localOnly?: boolean
75
+ }
76
+ }
77
+ >
78
+
79
+ export type TableOptions = {
80
+ /**
81
+ * Setting this to true will have the following consequences:
82
+ * - An `id` column will be added with `primaryKey: true` and `"singleton"` as default value and only allowed value
83
+ * - LiveStore will automatically create the singleton row when booting up
84
+ * - LiveStore will fail if there is already a column defined with `primaryKey: true`
85
+ *
86
+ * @default false
87
+ */
88
+ isSingleton: boolean
89
+ disableAutomaticIdColumn: boolean
90
+ /**
91
+ * Setting this to true will automatically derive insert, update and delete mutations for this table. Example:
92
+ *
93
+ * ```ts
94
+ * const todos = table('todos', { ... }, { deriveMutations: true })
95
+ * todos.insert({ id: '1', text: 'Hello' })
96
+ * ```
97
+ *
98
+ * This is also a prerequisite for using the `useRow`, `useAtom` and `rowQuery` APIs.
99
+ *
100
+ * Important: When using this option, make sure you're following the "Rules of mutations" for the table schema.
101
+ */
102
+ deriveMutations:
103
+ | { enabled: false }
104
+ | {
105
+ enabled: true
106
+ /**
107
+ * When set to true, the mutations won't be synced over the network
108
+ */
109
+ localOnly: boolean
110
+ }
111
+ /** Derived based on whether the table definition has one or more columns (besides the `id` column) */
112
+ isSingleColumn: boolean
113
+ }
114
+
115
+ export const table = <
116
+ TName extends string,
117
+ TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefinition<any, any>,
118
+ const TOptionsInput extends TableOptionsInput = TableOptionsInput,
119
+ >(
120
+ name: TName,
121
+ columnOrColumns: TColumns,
122
+ options?: TOptionsInput,
123
+ ): TableDef<
124
+ SqliteDsl.TableDefinition<
125
+ TName,
126
+ PrettifyFlat<
127
+ WithId<
128
+ TColumns extends SqliteDsl.Columns ? TColumns : { value: TColumns },
129
+ WithDefaults<TOptionsInput, SqliteDsl.IsSingleColumn<TColumns>>
130
+ >
131
+ >
132
+ >,
133
+ SqliteDsl.IsSingleColumn<TColumns>,
134
+ WithDefaults<TOptionsInput, SqliteDsl.IsSingleColumn<TColumns>>
135
+ > => {
136
+ const tablePath = name
137
+
138
+ const options_: TableOptions = {
139
+ isSingleton: options?.isSingleton ?? false,
140
+ disableAutomaticIdColumn: options?.disableAutomaticIdColumn ?? false,
141
+ deriveMutations:
142
+ options?.deriveMutations === true
143
+ ? { enabled: true as const, localOnly: false }
144
+ : options?.deriveMutations === false
145
+ ? { enabled: false as const }
146
+ : options?.deriveMutations === undefined
147
+ ? { enabled: false as const }
148
+ : { enabled: true as const, localOnly: options.deriveMutations.localOnly ?? false },
149
+ isSingleColumn: SqliteDsl.isColumnDefinition(columnOrColumns) === true,
150
+ }
151
+
152
+ const columns = (
153
+ SqliteDsl.isColumnDefinition(columnOrColumns) ? { value: columnOrColumns } : columnOrColumns
154
+ ) as SqliteDsl.Columns
155
+
156
+ if (options_.disableAutomaticIdColumn === true) {
157
+ if (columns.id === undefined && options_.isSingleton === true) {
158
+ shouldNeverHappen(
159
+ `Cannot create table ${name} with "isSingleton: true" because there is no column with name "id" and "disableAutomaticIdColumn: true" is set`,
160
+ )
161
+ }
162
+ } else if (columns.id === undefined && ReadonlyRecord.some(columns, (_) => _.primaryKey === true) === false) {
163
+ if (options_.isSingleton) {
164
+ columns.id = SqliteDsl.text({ schema: Schema.Literal('singleton'), primaryKey: true, default: 'singleton' })
165
+ } else {
166
+ columns.id = SqliteDsl.text({ primaryKey: true })
167
+ }
168
+ }
169
+
170
+ const sqliteDef = SqliteDsl.table(tablePath, columns, options?.indexes ?? [])
171
+
172
+ // TODO also enforce this on the type level
173
+ if (options_.isSingleton) {
174
+ for (const column of sqliteDef.ast.columns) {
175
+ if (column.nullable === false && column.default._tag === 'None') {
176
+ shouldNeverHappen(
177
+ `When creating a singleton table, each column must be either nullable or have a default value. Column '${column.name}' is neither.`,
178
+ )
179
+ }
180
+ }
181
+ }
182
+
183
+ const isSingleColumn = SqliteDsl.isColumnDefinition(columnOrColumns) === true
184
+
185
+ const schema = SqliteDsl.structSchemaForTable(sqliteDef)
186
+ const tableDef = { sqliteDef, isSingleColumn, options: options_, schema } satisfies TableDef
187
+
188
+ if (tableHasDerivedMutations(tableDef)) {
189
+ const derivedMutationDefs = makeDerivedMutationDefsForTable(tableDef)
190
+
191
+ tableDef.insert = (valuesOrValue: any) => {
192
+ if (isSingleColumn && options_.isSingleton) {
193
+ return derivedMutationDefs.insert({ id: 'singleton', value: { value: valuesOrValue } })
194
+ } else {
195
+ return derivedMutationDefs.insert(valuesOrValue as any)
196
+ }
197
+ }
198
+
199
+ tableDef.update = (argsOrValues: any) => {
200
+ if (isSingleColumn && options_.isSingleton) {
201
+ return derivedMutationDefs.update({ where: { id: 'singleton' }, values: { value: argsOrValues } as any })
202
+ } else {
203
+ return derivedMutationDefs.update(argsOrValues as any)
204
+ }
205
+ }
206
+
207
+ tableDef.delete = (args: any) => derivedMutationDefs.delete(args)
208
+ }
209
+
210
+ return tableDef as any
211
+ }
212
+
213
+ export const tableHasDerivedMutations = <TTableDef extends TableDef>(
214
+ tableDef: TTableDef,
215
+ ): tableDef is TTableDef & {
216
+ options: { deriveMutations: { enabled: true; localOnly: boolean } }
217
+ } & DerivedMutationHelperFns<TTableDef['sqliteDef']['columns'], TTableDef['options']> =>
218
+ tableDef.options.deriveMutations.enabled === true
219
+
220
+ export const tableIsSingleton = <TTableDef extends TableDef>(
221
+ tableDef: TTableDef,
222
+ ): tableDef is TTableDef & { options: { isSingleton: true } } => tableDef.options.isSingleton === true
223
+
224
+ type WithId<TColumns extends SqliteDsl.Columns, TOptions extends TableOptions> = TColumns &
225
+ ('id' extends keyof TColumns
226
+ ? {}
227
+ : TOptions['disableAutomaticIdColumn'] extends true
228
+ ? {}
229
+ : TOptions['isSingleton'] extends true
230
+ ? {
231
+ id: SqliteDsl.ColumnDefinition<'singleton', 'singleton'>
232
+ }
233
+ : {
234
+ id: SqliteDsl.ColumnDefinition<string, string>
235
+ })
236
+
237
+ type WithDefaults<TOptionsInput extends TableOptionsInput, TIsSingleColumn extends boolean> = {
238
+ isSingleton: TOptionsInput['isSingleton'] extends true ? true : false
239
+ disableAutomaticIdColumn: TOptionsInput['disableAutomaticIdColumn'] extends true ? true : false
240
+ deriveMutations: TOptionsInput['deriveMutations'] extends true
241
+ ? { enabled: true; localOnly: boolean }
242
+ : TOptionsInput['deriveMutations'] extends false
243
+ ? { enabled: false }
244
+ : TOptionsInput['deriveMutations'] extends { enabled: true; localOnly?: boolean }
245
+ ? {
246
+ enabled: true
247
+ localOnly: TOptionsInput['deriveMutations']['localOnly'] extends true ? true : false
248
+ }
249
+ : never
250
+ isSingleColumn: TIsSingleColumn
251
+ }
252
+
253
+ export namespace FromTable {
254
+ // TODO this sometimes doesn't preserve the order of columns
255
+ export type RowDecoded<TTableDef extends TableDef> = PrettifyFlat<
256
+ Nullable<Pick<RowDecodedAll<TTableDef>, NullableColumnNames<TTableDef>>> &
257
+ Omit<RowDecodedAll<TTableDef>, NullableColumnNames<TTableDef>>
258
+ >
259
+
260
+ export type NullableColumnNames<TTableDef extends TableDef> = FromColumns.NullableColumnNames<
261
+ TTableDef['sqliteDef']['columns']
262
+ >
263
+
264
+ export type Columns<TTableDef extends TableDef> = {
265
+ [K in keyof TTableDef['sqliteDef']['columns']]: TTableDef['sqliteDef']['columns'][K]['columnType']
266
+ }
267
+
268
+ export type RowEncodeNonNullable<TTableDef extends TableDef> = {
269
+ [K in keyof TTableDef['sqliteDef']['columns']]: Schema.Schema.Encoded<
270
+ TTableDef['sqliteDef']['columns'][K]['schema']
271
+ >
272
+ }
273
+
274
+ export type RowEncoded<TTableDef extends TableDef> = PrettifyFlat<
275
+ Nullable<Pick<RowEncodeNonNullable<TTableDef>, NullableColumnNames<TTableDef>>> &
276
+ Omit<RowEncodeNonNullable<TTableDef>, NullableColumnNames<TTableDef>>
277
+ >
278
+
279
+ export type RowDecodedAll<TTableDef extends TableDef> = {
280
+ [K in keyof TTableDef['sqliteDef']['columns']]: Schema.Schema.Type<TTableDef['sqliteDef']['columns'][K]['schema']>
281
+ }
282
+ }
283
+
284
+ export namespace FromColumns {
285
+ // TODO this sometimes doesn't preserve the order of columns
286
+ export type RowDecoded<TColumns extends SqliteDsl.Columns> = PrettifyFlat<
287
+ Nullable<Pick<RowDecodedAll<TColumns>, NullableColumnNames<TColumns>>> &
288
+ Omit<RowDecodedAll<TColumns>, NullableColumnNames<TColumns>>
289
+ >
290
+
291
+ export type RowDecodedAll<TColumns extends SqliteDsl.Columns> = {
292
+ [K in keyof TColumns]: Schema.Schema.Type<TColumns[K]['schema']>
293
+ }
294
+
295
+ export type RowEncoded<TColumns extends SqliteDsl.Columns> = PrettifyFlat<
296
+ Nullable<Pick<RowEncodeNonNullable<TColumns>, NullableColumnNames<TColumns>>> &
297
+ Omit<RowEncodeNonNullable<TColumns>, NullableColumnNames<TColumns>>
298
+ >
299
+
300
+ export type RowEncodeNonNullable<TColumns extends SqliteDsl.Columns> = {
301
+ [K in keyof TColumns]: Schema.Schema.Encoded<TColumns[K]['schema']>
302
+ }
303
+
304
+ export type NullableColumnNames<TColumns extends SqliteDsl.Columns> = keyof {
305
+ [K in keyof TColumns as TColumns[K]['default'] extends true ? K : never]: {}
306
+ }
307
+
308
+ export type RequiredInsertColumnNames<TColumns extends SqliteDsl.Columns> =
309
+ SqliteDsl.FromColumns.RequiredInsertColumnNames<TColumns>
310
+
311
+ export type InsertRowDecoded<TColumns extends SqliteDsl.Columns> = SqliteDsl.FromColumns.InsertRowDecoded<TColumns>
312
+ }
@@ -0,0 +1,44 @@
1
+ import type { SynchronousDatabase } from '../adapter-types.js'
2
+ import type { ParamsObject } from '../util.js'
3
+ import { prepareBindValues } from '../util.js'
4
+
5
+ // TODO bring back statement caching
6
+ // will require proper scope-aware cleanup etc (for testing and apps with multiple LiveStore instances)
7
+ // const cachedStmts = new Map<string, PreparedStatement>()
8
+
9
+ export const dbExecute = (db: SynchronousDatabase, queryStr: string, bindValues?: ParamsObject) => {
10
+ // let stmt = cachedStmts.get(queryStr)
11
+ // if (!stmt) {
12
+ const stmt = db.prepare(queryStr)
13
+ // cachedStmts.set(queryStr, stmt)
14
+ // }
15
+
16
+ const preparedBindValues = bindValues ? prepareBindValues(bindValues, queryStr) : undefined
17
+
18
+ stmt.execute(preparedBindValues)
19
+
20
+ stmt.finalize()
21
+ }
22
+
23
+ export const dbSelect = <T>(db: SynchronousDatabase, queryStr: string, bindValues?: ParamsObject) => {
24
+ // let stmt = cachedStmts.get(queryStr)
25
+ // if (!stmt) {
26
+ const stmt = db.prepare(queryStr)
27
+ // cachedStmts.set(queryStr, stmt)
28
+ // }
29
+
30
+ const res = stmt.select<T>(bindValues ? prepareBindValues(bindValues, queryStr) : undefined)
31
+ stmt.finalize()
32
+ return res
33
+ }
34
+
35
+ export interface SchemaManager {
36
+ getMutationDefInfos: () => ReadonlyArray<MutationDefInfo>
37
+
38
+ setMutationDefInfo: (mutationDefInfo: MutationDefInfo) => void
39
+ }
40
+
41
+ export type MutationDefInfo = {
42
+ mutationName: string
43
+ schemaHash: number
44
+ }
@@ -0,0 +1,188 @@
1
+ import { SqliteAst, SqliteDsl } from '@livestore/db-schema'
2
+ import { memoizeByStringifyArgs } from '@livestore/utils'
3
+ import { Effect, Schema as EffectSchema } from '@livestore/utils/effect'
4
+
5
+ import type { SynchronousDatabase } from '../adapter-types.js'
6
+ import type { LiveStoreSchema } from '../schema/index.js'
7
+ import type { SchemaMetaRow, SchemaMutationsMetaRow } from '../schema/system-tables.js'
8
+ import {
9
+ SCHEMA_META_TABLE,
10
+ SCHEMA_MUTATIONS_META_TABLE,
11
+ schemaMetaTable,
12
+ schemaMutationsMetaTable,
13
+ systemTables,
14
+ } from '../schema/system-tables.js'
15
+ import { sql } from '../util.js'
16
+ import type { SchemaManager } from './common.js'
17
+ import { dbExecute, dbSelect } from './common.js'
18
+ import { validateSchema } from './validate-mutation-defs.js'
19
+
20
+ const getMemoizedTimestamp = memoizeByStringifyArgs(() => new Date().toISOString())
21
+
22
+ export const makeSchemaManager = (db: SynchronousDatabase): Effect.Effect<SchemaManager> =>
23
+ Effect.gen(function* () {
24
+ yield* migrateTable({
25
+ db,
26
+ tableAst: schemaMutationsMetaTable.sqliteDef.ast,
27
+ behaviour: 'create-if-not-exists',
28
+ })
29
+
30
+ return {
31
+ getMutationDefInfos: () =>
32
+ dbSelect<SchemaMutationsMetaRow>(db, sql`SELECT * FROM ${SCHEMA_MUTATIONS_META_TABLE}`),
33
+
34
+ setMutationDefInfo: (info) => {
35
+ dbExecute(
36
+ db,
37
+ sql`INSERT OR REPLACE INTO ${SCHEMA_MUTATIONS_META_TABLE} (mutationName, schemaHash, updatedAt) VALUES ($mutationName, $schemaHash, $updatedAt)`,
38
+ {
39
+ mutationName: info.mutationName,
40
+ schemaHash: info.schemaHash,
41
+ updatedAt: new Date().toISOString(),
42
+ },
43
+ )
44
+ },
45
+ }
46
+ })
47
+
48
+ // TODO more graceful DB migration (e.g. backup DB before destructive migrations)
49
+ export const migrateDb = ({
50
+ db,
51
+ schema,
52
+ onProgress,
53
+ }: {
54
+ db: SynchronousDatabase
55
+ schema: LiveStoreSchema
56
+ onProgress?: (opts: { done: number; total: number }) => Effect.Effect<void>
57
+ }) =>
58
+ Effect.gen(function* () {
59
+ yield* migrateTable({
60
+ db,
61
+ tableAst: schemaMetaTable.sqliteDef.ast,
62
+ behaviour: 'create-if-not-exists',
63
+ })
64
+
65
+ // TODO enforce that migrating tables isn't allowed once the store is running
66
+
67
+ const schemaManager = yield* makeSchemaManager(db)
68
+ yield* validateSchema(schema, schemaManager)
69
+
70
+ const schemaMetaRows = dbSelect<SchemaMetaRow>(db, sql`SELECT * FROM ${SCHEMA_META_TABLE}`)
71
+
72
+ const dbSchemaHashByTable = Object.fromEntries(
73
+ schemaMetaRows.map(({ tableName, schemaHash }) => [tableName, schemaHash]),
74
+ )
75
+
76
+ const tableDefs = new Set([
77
+ // NOTE it's important the `SCHEMA_META_TABLE` comes first since we're writing to it below
78
+ ...systemTables,
79
+ ...Array.from(schema.tables.values()).filter((_) => _.sqliteDef.name !== SCHEMA_META_TABLE),
80
+ ])
81
+
82
+ const tablesToMigrate = new Set<{ tableAst: SqliteAst.Table; schemaHash: number }>()
83
+
84
+ for (const tableDef of tableDefs) {
85
+ const tableAst = tableDef.sqliteDef.ast
86
+ const tableName = tableAst.name
87
+ const dbSchemaHash = dbSchemaHashByTable[tableName]
88
+ const schemaHash = SqliteAst.hash(tableAst)
89
+
90
+ if (schemaHash !== dbSchemaHash) {
91
+ tablesToMigrate.add({ tableAst, schemaHash })
92
+
93
+ console.log(
94
+ `Schema hash mismatch for table '${tableName}' (DB: ${dbSchemaHash}, expected: ${schemaHash}), migrating table...`,
95
+ )
96
+ }
97
+ }
98
+
99
+ let processedTables = 0
100
+ const tablesCount = tablesToMigrate.size
101
+
102
+ for (const { tableAst, schemaHash } of tablesToMigrate) {
103
+ yield* migrateTable({ db, tableAst, schemaHash, behaviour: 'create-if-not-exists' })
104
+
105
+ if (onProgress !== undefined) {
106
+ processedTables++
107
+ yield* onProgress({ done: processedTables, total: tablesCount })
108
+ }
109
+ }
110
+ })
111
+
112
+ export const migrateTable = ({
113
+ db,
114
+ tableAst,
115
+ schemaHash = SqliteAst.hash(tableAst),
116
+ behaviour,
117
+ skipMetaTable = false,
118
+ }: {
119
+ db: SynchronousDatabase
120
+ tableAst: SqliteAst.Table
121
+ schemaHash?: number
122
+ behaviour: 'drop-and-recreate' | 'create-if-not-exists'
123
+ skipMetaTable?: boolean
124
+ }) =>
125
+ Effect.gen(function* () {
126
+ // console.log(`Migrating table '${tableAst.name}'...`)
127
+ const tableName = tableAst.name
128
+ const columnSpec = makeColumnSpec(tableAst)
129
+
130
+ if (behaviour === 'drop-and-recreate') {
131
+ // TODO need to possibly handle cascading deletes due to foreign keys
132
+ dbExecute(db, sql`drop table if exists ${tableName}`)
133
+ dbExecute(db, sql`create table if not exists ${tableName} (${columnSpec}) strict`)
134
+ } else if (behaviour === 'create-if-not-exists') {
135
+ dbExecute(db, sql`create table if not exists ${tableName} (${columnSpec}) strict`)
136
+ }
137
+
138
+ for (const index of tableAst.indexes) {
139
+ dbExecute(db, createIndexFromDefinition(tableName, index))
140
+ }
141
+
142
+ if (skipMetaTable !== true) {
143
+ const updatedAt = getMemoizedTimestamp()
144
+
145
+ dbExecute(
146
+ db,
147
+ sql`
148
+ INSERT INTO ${SCHEMA_META_TABLE} (tableName, schemaHash, updatedAt) VALUES ($tableName, $schemaHash, $updatedAt)
149
+ ON CONFLICT (tableName) DO UPDATE SET schemaHash = $schemaHash, updatedAt = $updatedAt;
150
+ `,
151
+ { tableName, schemaHash, updatedAt },
152
+ )
153
+ }
154
+ }).pipe(Effect.withSpan('@livestore/common:migrateTable', { attributes: { tableName: tableAst.name } }))
155
+
156
+ const createIndexFromDefinition = (tableName: string, index: SqliteAst.Index) => {
157
+ const uniqueStr = index.unique ? 'UNIQUE' : ''
158
+ return sql`create ${uniqueStr} index if not exists ${index.name} on ${tableName} (${index.columns.join(', ')})`
159
+ }
160
+
161
+ export const makeColumnSpec = (tableAst: SqliteAst.Table) => {
162
+ const primaryKeys = tableAst.columns.filter((_) => _.primaryKey).map((_) => _.name)
163
+ const columnDefStrs = tableAst.columns.map(toSqliteColumnSpec)
164
+ if (primaryKeys.length > 0) {
165
+ columnDefStrs.push(`PRIMARY KEY (${primaryKeys.join(', ')})`)
166
+ }
167
+
168
+ return columnDefStrs.join(', ')
169
+ }
170
+
171
+ /** NOTE primary keys are applied on a table level not on a column level to account for multi-column primary keys */
172
+ const toSqliteColumnSpec = (column: SqliteAst.Column) => {
173
+ const columnTypeStr = column.type._tag
174
+ const nullableStr = column.nullable === false ? 'not null' : ''
175
+ const defaultValueStr = (() => {
176
+ if (column.default._tag === 'None') return ''
177
+
178
+ if (SqliteDsl.isSqlDefaultValue(column.default.value)) return `default ${column.default.value.sql}`
179
+
180
+ const encodeValue = EffectSchema.encodeSync(column.schema)
181
+ const encodedDefaultValue = encodeValue(column.default.value)
182
+
183
+ if (columnTypeStr === 'text') return `default '${encodedDefaultValue}'`
184
+ return `default ${encodedDefaultValue}`
185
+ })()
186
+
187
+ return `${column.name} ${columnTypeStr} ${nullableStr} ${defaultValueStr}`
188
+ }
@@ -0,0 +1,63 @@
1
+ import { Effect, Schema } from '@livestore/utils/effect'
2
+
3
+ import { UnexpectedError } from '../adapter-types.js'
4
+ import type { LiveStoreSchema } from '../schema/index.js'
5
+ import type { MutationDef } from '../schema/mutations.js'
6
+ import type { MutationDefInfo, SchemaManager } from './common.js'
7
+
8
+ export const validateSchema = (schema: LiveStoreSchema, schemaManager: SchemaManager) =>
9
+ Effect.gen(function* () {
10
+ // Validate mutation definitions
11
+ const registeredMutationDefInfos = schemaManager.getMutationDefInfos()
12
+
13
+ const missingMutationDefs = registeredMutationDefInfos.filter(
14
+ (registeredMutationDefInfo) => !schema.mutations.has(registeredMutationDefInfo.mutationName),
15
+ )
16
+
17
+ if (missingMutationDefs.length > 0) {
18
+ yield* new UnexpectedError({
19
+ cause: `Missing mutation definitions: ${missingMutationDefs.map((info) => info.mutationName).join(', ')}`,
20
+ })
21
+ }
22
+
23
+ for (const [, mutationDef] of schema.mutations) {
24
+ const registeredMutationDefInfo = registeredMutationDefInfos.find(
25
+ (info) => info.mutationName === mutationDef.name,
26
+ )
27
+
28
+ validateMutationDef(mutationDef, schemaManager, registeredMutationDefInfo)
29
+ }
30
+
31
+ // Validate table schemas
32
+ })
33
+
34
+ export const validateMutationDef = (
35
+ mutationDef: MutationDef.Any,
36
+ schemaManager: SchemaManager,
37
+ registeredMutationDefInfo: MutationDefInfo | undefined,
38
+ ) => {
39
+ const schemaHash = Schema.hash(mutationDef.schema)
40
+
41
+ if (registeredMutationDefInfo === undefined) {
42
+ schemaManager.setMutationDefInfo({
43
+ schemaHash,
44
+ mutationName: mutationDef.name,
45
+ })
46
+
47
+ return
48
+ }
49
+
50
+ if (schemaHash === registeredMutationDefInfo.schemaHash) return
51
+
52
+ // TODO bring back some form of schema compatibility check (see https://github.com/livestorejs/livestore/issues/69)
53
+ // const newSchemaIsCompatibleWithOldSchema = Schema.isSubType(jsonSchemaDefFromMgmtStore, mutationDef.schema)
54
+
55
+ // if (!newSchemaIsCompatibleWithOldSchema) {
56
+ // shouldNeverHappen(`Schema for mutation ${mutationDef.name} has changed in an incompatible way`)
57
+ // }
58
+
59
+ schemaManager.setMutationDefInfo({
60
+ schemaHash,
61
+ mutationName: mutationDef.name,
62
+ })
63
+ }
@@ -0,0 +1,3 @@
1
+ export * from './sql-queries.js'
2
+ export * from './sql-query-builder.js'
3
+ export * from './types.js'
@@ -0,0 +1,2 @@
1
+ export const objectEntries = <T extends Record<string, any>>(obj: T): [keyof T & string, T[keyof T]][] =>
2
+ Object.entries(obj) as [keyof T & string, T[keyof T]][]