@livestore/livestore 0.0.25 → 0.0.27

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 (206) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/QueryCache.d.ts +1 -1
  3. package/dist/QueryCache.d.ts.map +1 -1
  4. package/dist/QueryCache.js +50 -60
  5. package/dist/QueryCache.js.map +1 -1
  6. package/dist/__tests__/react/fixture.d.ts +21 -6
  7. package/dist/__tests__/react/fixture.d.ts.map +1 -1
  8. package/dist/__tests__/react/fixture.js +13 -14
  9. package/dist/__tests__/react/fixture.js.map +1 -1
  10. package/dist/__tests__/react/useQuery.test.js +5 -5
  11. package/dist/__tests__/react/useQuery.test.js.map +1 -1
  12. package/dist/__tests__/react/useRow.test.d.ts +2 -0
  13. package/dist/__tests__/react/useRow.test.d.ts.map +1 -0
  14. package/dist/__tests__/react/{useComponentState.test.js → useRow.test.js} +21 -26
  15. package/dist/__tests__/react/useRow.test.js.map +1 -0
  16. package/dist/__tests__/react/utils/stack-info.test.js +32 -0
  17. package/dist/__tests__/react/utils/stack-info.test.js.map +1 -1
  18. package/dist/__tests__/reactiveQueries/sql.test.js +4 -4
  19. package/dist/__tests__/reactiveQueries/sql.test.js.map +1 -1
  20. package/dist/effect/LiveStore.d.ts +3 -3
  21. package/dist/effect/LiveStore.d.ts.map +1 -1
  22. package/dist/effect/LiveStore.js +1 -1
  23. package/dist/effect/LiveStore.js.map +1 -1
  24. package/dist/global-state.d.ts +19 -0
  25. package/dist/global-state.d.ts.map +1 -0
  26. package/dist/global-state.js +20 -0
  27. package/dist/global-state.js.map +1 -0
  28. package/dist/inMemoryDatabase.d.ts +3 -3
  29. package/dist/inMemoryDatabase.d.ts.map +1 -1
  30. package/dist/inMemoryDatabase.js +13 -7
  31. package/dist/inMemoryDatabase.js.map +1 -1
  32. package/dist/index.d.ts +5 -9
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +4 -5
  35. package/dist/index.js.map +1 -1
  36. package/dist/migrations.d.ts +4 -4
  37. package/dist/migrations.d.ts.map +1 -1
  38. package/dist/migrations.js +34 -28
  39. package/dist/migrations.js.map +1 -1
  40. package/dist/react/LiveStoreContext.js.map +1 -1
  41. package/dist/react/LiveStoreProvider.d.ts +2 -2
  42. package/dist/react/LiveStoreProvider.d.ts.map +1 -1
  43. package/dist/react/LiveStoreProvider.js.map +1 -1
  44. package/dist/react/index.d.ts +1 -2
  45. package/dist/react/index.d.ts.map +1 -1
  46. package/dist/react/index.js +1 -1
  47. package/dist/react/index.js.map +1 -1
  48. package/dist/react/useQuery.d.ts +3 -0
  49. package/dist/react/useQuery.d.ts.map +1 -1
  50. package/dist/react/useQuery.js +5 -4
  51. package/dist/react/useQuery.js.map +1 -1
  52. package/dist/react/useRow.d.ts +33 -0
  53. package/dist/react/useRow.d.ts.map +1 -0
  54. package/dist/react/useRow.js +136 -0
  55. package/dist/react/useRow.js.map +1 -0
  56. package/dist/react/useTemporaryQuery.d.ts +2 -0
  57. package/dist/react/useTemporaryQuery.d.ts.map +1 -1
  58. package/dist/react/useTemporaryQuery.js +28 -11
  59. package/dist/react/useTemporaryQuery.js.map +1 -1
  60. package/dist/react/utils/stack-info.d.ts.map +1 -1
  61. package/dist/react/utils/stack-info.js +3 -2
  62. package/dist/react/utils/stack-info.js.map +1 -1
  63. package/dist/react/utils/useStateRefWithReactiveInput.js.map +1 -1
  64. package/dist/reactive.d.ts +1 -1
  65. package/dist/reactive.d.ts.map +1 -1
  66. package/dist/reactive.js +47 -44
  67. package/dist/reactive.js.map +1 -1
  68. package/dist/reactiveQueries/base-class.d.ts +6 -2
  69. package/dist/reactiveQueries/base-class.d.ts.map +1 -1
  70. package/dist/reactiveQueries/base-class.js +10 -12
  71. package/dist/reactiveQueries/base-class.js.map +1 -1
  72. package/dist/reactiveQueries/graphql.d.ts +2 -2
  73. package/dist/reactiveQueries/graphql.d.ts.map +1 -1
  74. package/dist/reactiveQueries/graphql.js +56 -50
  75. package/dist/reactiveQueries/graphql.js.map +1 -1
  76. package/dist/reactiveQueries/js.d.ts +1 -2
  77. package/dist/reactiveQueries/js.d.ts.map +1 -1
  78. package/dist/reactiveQueries/js.js +25 -15
  79. package/dist/reactiveQueries/js.js.map +1 -1
  80. package/dist/reactiveQueries/sql.d.ts +3 -3
  81. package/dist/reactiveQueries/sql.d.ts.map +1 -1
  82. package/dist/reactiveQueries/sql.js +39 -34
  83. package/dist/reactiveQueries/sql.js.map +1 -1
  84. package/dist/row-query.d.ts +21 -0
  85. package/dist/row-query.d.ts.map +1 -0
  86. package/dist/row-query.js +77 -0
  87. package/dist/row-query.js.map +1 -0
  88. package/dist/schema/action.d.ts +30 -0
  89. package/dist/schema/action.d.ts.map +1 -0
  90. package/dist/schema/action.js +3 -0
  91. package/dist/schema/action.js.map +1 -0
  92. package/dist/schema/index.d.ts +28 -0
  93. package/dist/schema/index.d.ts.map +1 -0
  94. package/dist/schema/index.js +26 -0
  95. package/dist/schema/index.js.map +1 -0
  96. package/dist/schema/system-tables.d.ts +24 -0
  97. package/dist/schema/system-tables.d.ts.map +1 -0
  98. package/dist/schema/system-tables.js +11 -0
  99. package/dist/schema/system-tables.js.map +1 -0
  100. package/dist/schema/table-def.d.ts +161 -0
  101. package/dist/schema/table-def.d.ts.map +1 -0
  102. package/dist/schema/table-def.js +53 -0
  103. package/dist/schema/table-def.js.map +1 -0
  104. package/dist/storage/in-memory/index.d.ts +1 -1
  105. package/dist/storage/in-memory/index.d.ts.map +1 -1
  106. package/dist/storage/in-memory/index.js +6 -7
  107. package/dist/storage/in-memory/index.js.map +1 -1
  108. package/dist/storage/index.d.ts +1 -1
  109. package/dist/storage/index.d.ts.map +1 -1
  110. package/dist/storage/tauri/index.d.ts +1 -1
  111. package/dist/storage/tauri/index.d.ts.map +1 -1
  112. package/dist/storage/tauri/index.js +25 -23
  113. package/dist/storage/tauri/index.js.map +1 -1
  114. package/dist/storage/utils/idb.js +3 -1
  115. package/dist/storage/utils/idb.js.map +1 -1
  116. package/dist/storage/web-worker/index.d.ts +1 -1
  117. package/dist/storage/web-worker/index.d.ts.map +1 -1
  118. package/dist/storage/web-worker/index.js +38 -34
  119. package/dist/storage/web-worker/index.js.map +1 -1
  120. package/dist/storage/web-worker/worker.d.ts +1 -1
  121. package/dist/storage/web-worker/worker.d.ts.map +1 -1
  122. package/dist/storage/web-worker/worker.js +1 -1
  123. package/dist/storage/web-worker/worker.js.map +1 -1
  124. package/dist/store.d.ts +9 -10
  125. package/dist/store.d.ts.map +1 -1
  126. package/dist/store.js +282 -265
  127. package/dist/store.js.map +1 -1
  128. package/dist/utils/bounded-collections.d.ts.map +1 -0
  129. package/dist/utils/bounded-collections.js +90 -0
  130. package/dist/utils/bounded-collections.js.map +1 -0
  131. package/dist/utils/otel.d.ts.map +1 -0
  132. package/dist/{otel.js → utils/otel.js} +1 -1
  133. package/dist/utils/otel.js.map +1 -0
  134. package/dist/utils/util.d.ts.map +1 -0
  135. package/dist/utils/util.js.map +1 -0
  136. package/package.json +12 -12
  137. package/src/QueryCache.ts +2 -2
  138. package/src/__tests__/react/fixture.tsx +15 -15
  139. package/src/__tests__/react/useQuery.test.tsx +5 -5
  140. package/src/__tests__/react/{useComponentState.test.tsx → useRow.test.tsx} +27 -28
  141. package/src/__tests__/react/utils/stack-info.test.ts +34 -0
  142. package/src/__tests__/reactiveQueries/sql.test.ts +4 -4
  143. package/src/effect/LiveStore.ts +6 -6
  144. package/src/global-state.ts +26 -0
  145. package/src/inMemoryDatabase.ts +6 -4
  146. package/src/index.ts +18 -17
  147. package/src/migrations.ts +41 -35
  148. package/src/react/LiveStoreProvider.tsx +2 -2
  149. package/src/react/index.ts +7 -9
  150. package/src/react/useQuery.ts +10 -4
  151. package/src/react/useRow.ts +221 -0
  152. package/src/react/useTemporaryQuery.ts +43 -11
  153. package/src/react/utils/stack-info.ts +4 -2
  154. package/src/reactive.ts +1 -1
  155. package/src/reactiveQueries/base-class.ts +8 -2
  156. package/src/reactiveQueries/graphql.ts +4 -3
  157. package/src/reactiveQueries/js.ts +3 -4
  158. package/src/reactiveQueries/sql.ts +6 -6
  159. package/src/row-query.ts +142 -0
  160. package/src/schema/action.ts +41 -0
  161. package/src/schema/index.ts +63 -0
  162. package/src/schema/system-tables.ts +21 -0
  163. package/src/schema/table-def.ts +199 -0
  164. package/src/storage/in-memory/index.ts +1 -1
  165. package/src/storage/index.ts +2 -1
  166. package/src/storage/tauri/index.ts +2 -2
  167. package/src/storage/web-worker/index.ts +1 -1
  168. package/src/storage/web-worker/worker.ts +2 -2
  169. package/src/store.ts +39 -27
  170. package/dist/__tests__/react/useComponentState.test.d.ts +0 -2
  171. package/dist/__tests__/react/useComponentState.test.d.ts.map +0 -1
  172. package/dist/__tests__/react/useComponentState.test.js.map +0 -1
  173. package/dist/bounded-collections.d.ts.map +0 -1
  174. package/dist/bounded-collections.js +0 -103
  175. package/dist/bounded-collections.js.map +0 -1
  176. package/dist/componentKey.d.ts +0 -20
  177. package/dist/componentKey.d.ts.map +0 -1
  178. package/dist/componentKey.js +0 -3
  179. package/dist/componentKey.js.map +0 -1
  180. package/dist/otel.d.ts.map +0 -1
  181. package/dist/otel.js.map +0 -1
  182. package/dist/react/useComponentState.d.ts +0 -50
  183. package/dist/react/useComponentState.d.ts.map +0 -1
  184. package/dist/react/useComponentState.js +0 -226
  185. package/dist/react/useComponentState.js.map +0 -1
  186. package/dist/reactiveQueries/graph.d.ts +0 -10
  187. package/dist/reactiveQueries/graph.d.ts.map +0 -1
  188. package/dist/reactiveQueries/graph.js +0 -6
  189. package/dist/reactiveQueries/graph.js.map +0 -1
  190. package/dist/schema.d.ts +0 -81
  191. package/dist/schema.d.ts.map +0 -1
  192. package/dist/schema.js +0 -46
  193. package/dist/schema.js.map +0 -1
  194. package/dist/util.d.ts.map +0 -1
  195. package/dist/util.js.map +0 -1
  196. package/src/componentKey.ts +0 -9
  197. package/src/react/useComponentState.ts +0 -385
  198. package/src/reactiveQueries/graph.ts +0 -15
  199. package/src/schema.ts +0 -143
  200. /package/dist/{bounded-collections.d.ts → utils/bounded-collections.d.ts} +0 -0
  201. /package/dist/{otel.d.ts → utils/otel.d.ts} +0 -0
  202. /package/dist/{util.d.ts → utils/util.d.ts} +0 -0
  203. /package/dist/{util.js → utils/util.js} +0 -0
  204. /package/src/{bounded-collections.ts → utils/bounded-collections.ts} +0 -0
  205. /package/src/{otel.ts → utils/otel.ts} +0 -0
  206. /package/src/{util.ts → utils/util.ts} +0 -0
@@ -1,14 +1,14 @@
1
1
  import { shouldNeverHappen } from '@livestore/utils'
2
2
  import * as otel from '@opentelemetry/api'
3
3
 
4
- import { getDurationMsFromSpan } from '../otel.js'
4
+ import { dbGraph } from '../global-state.js'
5
5
  import type { Thunk } from '../reactive.js'
6
6
  import type { RefreshReason } from '../store.js'
7
- import type { Bindable } from '../util.js'
8
- import { prepareBindValues } from '../util.js'
9
- import { type GetAtomResult, LiveStoreQueryBase, makeGetAtomResult } from './base-class.js'
10
- import type { DbContext } from './graph.js'
11
- import { dbGraph } from './graph.js'
7
+ import { getDurationMsFromSpan } from '../utils/otel.js'
8
+ import type { Bindable } from '../utils/util.js'
9
+ import { prepareBindValues } from '../utils/util.js'
10
+ import type { DbContext, GetAtomResult } from './base-class.js'
11
+ import { LiveStoreQueryBase, makeGetAtomResult } from './base-class.js'
12
12
  import { LiveStoreJSQuery } from './js.js'
13
13
 
14
14
  export const querySQL = <Row>(
@@ -0,0 +1,142 @@
1
+ import { shouldNeverHappen } from '@livestore/utils'
2
+ import { pipe, ReadonlyRecord, Schema, TreeFormatter } from '@livestore/utils/effect'
3
+ import type * as otel from '@opentelemetry/api'
4
+ import { SqliteAst, SqliteDsl } from 'effect-db-schema'
5
+
6
+ import type { InMemoryDatabase } from './inMemoryDatabase.js'
7
+ import { migrateTable } from './migrations.js'
8
+ import type { LiveStoreJSQuery } from './reactiveQueries/js.js'
9
+ import { LiveStoreSQLQuery } from './reactiveQueries/sql.js'
10
+ import { SCHEMA_META_TABLE } from './schema/index.js'
11
+ import type { TableDef } from './schema/table-def.js'
12
+ import type { Store } from './store.js'
13
+ import { prepareBindValues, sql } from './utils/util.js'
14
+
15
+ export type RowQueryArgs<TTableDef extends TableDef> = TTableDef['options']['isSingleton'] extends true
16
+ ? {
17
+ table: TTableDef
18
+ store: Store
19
+ otelContext?: otel.Context
20
+ }
21
+ : {
22
+ table: TTableDef
23
+ store: Store
24
+ otelContext?: otel.Context
25
+ id: string
26
+ }
27
+
28
+ // TODO also allow other where clauses and multiple rows
29
+ export const rowQuery = <TTableDef extends TableDef>(
30
+ args: RowQueryArgs<TTableDef>,
31
+ ): LiveStoreJSQuery<RowResult<TTableDef>> => {
32
+ const { table, store } = args
33
+ const otelContext = args.otelContext ?? store.otel.queriesSpanContext
34
+ const id: string | undefined = (args as any).id
35
+
36
+ // Validate query args
37
+ if (table.options.isSingleton === true && id !== undefined) {
38
+ shouldNeverHappen(`Cannot query state table ${table.schema.name} with id "${id}" as it is a singleton`)
39
+ } else if (table.options.isSingleton !== true && id === undefined) {
40
+ shouldNeverHappen(`Cannot query state table ${table.schema.name} without id`)
41
+ }
42
+
43
+ const stateSchema = table.schema
44
+ const componentTableName = stateSchema.name
45
+
46
+ type TComponentState = SqliteDsl.FromColumns.RowDecoded<TTableDef['schema']['columns']>
47
+
48
+ // TODO find a better solution for this
49
+ if (store.tableRefs[componentTableName] === undefined) {
50
+ const schemaHash = SqliteAst.hash(stateSchema.ast)
51
+ const res = store.inMemoryDB.select<{ schemaHash: number }>(
52
+ sql`SELECT schemaHash FROM ${SCHEMA_META_TABLE} WHERE tableName = '${componentTableName}'`,
53
+ )
54
+ if (res.length === 0 || res[0]!.schemaHash !== schemaHash) {
55
+ migrateTable({
56
+ db: store._proxyDb,
57
+ tableAst: stateSchema.ast,
58
+ otelContext,
59
+ schemaHash,
60
+ })
61
+ }
62
+
63
+ store.tableRefs[componentTableName] = store.graph.makeRef(null, {
64
+ equal: () => false,
65
+ label: componentTableName,
66
+ meta: { liveStoreRefType: 'table' },
67
+ })
68
+ }
69
+
70
+ // TODO find a way to only do this if necessary
71
+ insertRowWithDefaultValuesOrIgnore({
72
+ db: store._proxyDb,
73
+ id: id ?? 'singleton',
74
+ stateSchema,
75
+ otelContext,
76
+ })
77
+
78
+ const whereClause = id === undefined ? '' : `where id = '${id}'`
79
+ const queryStr = sql`select * from ${componentTableName} ${whereClause} limit 1`
80
+
81
+ return new LiveStoreSQLQuery({
82
+ label: `localState:query:${stateSchema.name}${id === undefined ? '' : `:${id}`}`,
83
+ genQueryString: queryStr,
84
+ queriedTables: new Set([componentTableName]),
85
+ }).pipe<TComponentState>((results) => {
86
+ if (results.length === 0) return shouldNeverHappen(`No results for query ${queryStr}`)
87
+
88
+ const componentStateEffectSchema = SqliteDsl.structSchemaForTable(stateSchema)
89
+ const parseResult = Schema.parseEither(componentStateEffectSchema)(results[0]!)
90
+
91
+ if (parseResult._tag === 'Left') {
92
+ console.error('decode error', TreeFormatter.formatErrors(parseResult.left.errors), 'results', results)
93
+ return shouldNeverHappen(`Error decoding query result for ${queryStr}`)
94
+ }
95
+
96
+ return table.isSingleColumn === true ? parseResult.right.value : parseResult.right
97
+ }) as unknown as LiveStoreJSQuery<RowResult<TTableDef>>
98
+ }
99
+
100
+ type GetValForKey<T, K> = K extends keyof T ? T[K] : never
101
+
102
+ export type RowResult<TTableDef extends TableDef> = TTableDef['isSingleColumn'] extends true
103
+ ? GetValForKey<SqliteDsl.FromColumns.RowDecoded<TTableDef['schema']['columns']>, 'value'>
104
+ : SqliteDsl.FromColumns.RowDecoded<TTableDef['schema']['columns']>
105
+
106
+ export type RowResultEncoded<TTableDef extends TableDef> = TTableDef['isSingleColumn'] extends true
107
+ ? GetValForKey<SqliteDsl.FromColumns.RowEncoded<TTableDef['schema']['columns']>, 'value'>
108
+ : SqliteDsl.FromColumns.RowEncoded<TTableDef['schema']['columns']>
109
+
110
+ const insertRowWithDefaultValuesOrIgnore = ({
111
+ db,
112
+ id,
113
+ stateSchema,
114
+ otelContext,
115
+ }: {
116
+ db: InMemoryDatabase
117
+ id: string
118
+ stateSchema: SqliteDsl.TableDefinition<string, SqliteDsl.Columns>
119
+ otelContext: otel.Context
120
+ }) => {
121
+ const columnNames = Object.keys(stateSchema.columns)
122
+ const columnValues = columnNames.map((name) => `$${name}`).join(', ')
123
+
124
+ const tableName = stateSchema.name
125
+ const insertQuery = sql`insert into ${tableName} (${columnNames.join(
126
+ ', ',
127
+ )}) select ${columnValues} where not exists(select 1 from ${tableName} where id = '${id}')`
128
+
129
+ const defaultValues = pipe(
130
+ stateSchema.columns,
131
+ ReadonlyRecord.filter((_, key) => key !== 'id'),
132
+ ReadonlyRecord.map((column, columnName) =>
133
+ column.default === undefined
134
+ ? column.nullable === true
135
+ ? null
136
+ : shouldNeverHappen(`Column ${columnName} has no default value and is not nullable`)
137
+ : Schema.encodeSync(column.type.codec)(column.default ?? null),
138
+ ),
139
+ )
140
+
141
+ void db.execute(insertQuery, prepareBindValues({ ...defaultValues, id }, insertQuery), [tableName], { otelContext })
142
+ }
@@ -0,0 +1,41 @@
1
+ export type SQLWriteStatement = {
2
+ sql: string
3
+
4
+ /** Tables written by the statement */
5
+ writeTables: ReadonlyArray<string>
6
+ // TODO refactor this
7
+ argsAlreadyBound?: boolean
8
+ }
9
+
10
+ export type ActionDefinition<TArgs = any> = {
11
+ statement: SQLWriteStatement | ((args: TArgs) => SQLWriteStatement)
12
+ prepareBindValues?: (args: TArgs) => any
13
+ }
14
+
15
+ export type ActionDefinitions<TArgsMap extends Record<string, any>> = {
16
+ [key in keyof TArgsMap]: ActionDefinition<TArgsMap[key]>
17
+ }
18
+
19
+ export const defineActions = <A extends ActionDefinitions<any>>(actions: A) => actions
20
+ export const defineAction = <TArgs extends Record<string, any>>(
21
+ action: ActionDefinition<TArgs>,
22
+ ): ActionDefinition<TArgs> => action
23
+
24
+ export type GetApplyEventArgs<TActionDefinitionsMap> = RecordValues<{
25
+ [eventType in keyof TActionDefinitionsMap]: {
26
+ eventType: eventType
27
+ args: GetActionArgs<TActionDefinitionsMap[eventType]>
28
+ }
29
+ }>
30
+
31
+ type RecordValues<T> = T extends Record<string, infer V> ? V : never
32
+
33
+ export type GetActionArgs<A> = A extends ActionDefinition<infer TArgs> ? TArgs : never
34
+
35
+ // TODO get rid of this
36
+ declare global {
37
+ // NOTE Can be extended
38
+ interface LiveStoreActionDefinitionsTypes {
39
+ [key: string]: ActionDefinition
40
+ }
41
+ }
@@ -0,0 +1,63 @@
1
+ import type { SqliteDsl } from 'effect-db-schema'
2
+
3
+ import type { ActionDefinitions } from './action.js'
4
+ import { systemTables } from './system-tables.js'
5
+ import type { TableDef } from './table-def.js'
6
+
7
+ export * from './action.js'
8
+ export * from './system-tables.js'
9
+ export * as DbSchema from './table-def.js'
10
+
11
+ // export { SqliteDsl as DbSchema } from 'effect-db-schema'
12
+
13
+ export type LiveStoreSchema<TDbSchema extends SqliteDsl.DbSchema = SqliteDsl.DbSchema> = {
14
+ /** Only used on type-level */
15
+ readonly _DbSchemaType: TDbSchema
16
+
17
+ readonly tables: Map<string, TableDef>
18
+ readonly actions: ActionDefinitions<any>
19
+ }
20
+
21
+ export type InputSchema = {
22
+ tables: Record<string, TableDef> | ReadonlyArray<TableDef>
23
+ actions: ActionDefinitions<any>
24
+ }
25
+
26
+ export const makeSchema = <TInputSchema extends InputSchema>(
27
+ /** Note when using the object-notation for tables, the object keys are ignored and not used as table names */
28
+ schema: TInputSchema,
29
+ ): LiveStoreSchema<DbSchemaFromInputSchemaTables<TInputSchema['tables']>> => {
30
+ const inputTables: ReadonlyArray<TableDef> = Array.isArray(schema.tables)
31
+ ? schema.tables
32
+ : // TODO validate that table names are unique in this case
33
+ Object.values(schema.tables)
34
+
35
+ const tables = new Map<string, TableDef>()
36
+
37
+ for (const tableDef of inputTables) {
38
+ // TODO validate tables (e.g. index names are unique)
39
+ tables.set(tableDef.schema.ast.name, tableDef)
40
+ }
41
+
42
+ for (const tableDef of systemTables) {
43
+ tables.set(tableDef.schema.name, tableDef)
44
+ }
45
+
46
+ return {
47
+ _DbSchemaType: Symbol('livestore.DbSchemaType') as any,
48
+ tables,
49
+ actions: schema.actions,
50
+ } satisfies LiveStoreSchema
51
+ }
52
+
53
+ /**
54
+ * In case of ...
55
+ * - array: we use the table name of each array item (= table definition) as the object key
56
+ * - object: we discard the keys of the input object and use the table name of each object value (= table definition) as the new object key
57
+ */
58
+ export type DbSchemaFromInputSchemaTables<TTables extends InputSchema['tables']> =
59
+ TTables extends ReadonlyArray<TableDef>
60
+ ? { [K in TTables[number] as K['schema']['name']]: K['schema'] }
61
+ : TTables extends Record<string, TableDef>
62
+ ? { [K in keyof TTables as TTables[K]['schema']['name']]: TTables[K]['schema'] }
63
+ : never
@@ -0,0 +1,21 @@
1
+ import { SqliteAst as __SqliteAst, SqliteDsl } from 'effect-db-schema'
2
+
3
+ import type { FromTable } from './table-def.js'
4
+ import { table } from './table-def.js'
5
+
6
+ export const SCHEMA_META_TABLE = '__livestore_schema'
7
+
8
+ const schemaMetaTable = table(
9
+ SCHEMA_META_TABLE,
10
+ {
11
+ tableName: SqliteDsl.text({ primaryKey: true }),
12
+ schemaHash: SqliteDsl.integer({ nullable: false }),
13
+ /** ISO date format */
14
+ updatedAt: SqliteDsl.text({ nullable: false }),
15
+ },
16
+ { disableAutomaticIdColumn: true },
17
+ )
18
+
19
+ export type SchemaMetaRow = FromTable.RowDecoded<typeof schemaMetaTable>
20
+
21
+ export const systemTables = [schemaMetaTable]
@@ -0,0 +1,199 @@
1
+ import { shouldNeverHappen } from '@livestore/utils'
2
+ import { Schema } from '@livestore/utils/effect'
3
+ import type { Nullable, PrettifyFlat } from 'effect-db-schema'
4
+ import { SqliteAst, SqliteDsl } from 'effect-db-schema'
5
+
6
+ export const {
7
+ blob,
8
+ blobWithSchema,
9
+ boolean,
10
+ column,
11
+ datetime,
12
+ integer,
13
+ isColumnDefinition,
14
+ json,
15
+ real,
16
+ text,
17
+ textWithSchema,
18
+ } = SqliteDsl
19
+
20
+ export { SqliteDsl as __SqliteDsl } from 'effect-db-schema'
21
+
22
+ import { dynamicallyRegisteredTables } from '../global-state.js'
23
+
24
+ export type StateType = 'singleton' | 'dynamic'
25
+
26
+ export type DefaultSqliteTableDef = SqliteDsl.TableDefinition<string, SqliteDsl.Columns>
27
+
28
+ export type TableDef<
29
+ TTableDef extends DefaultSqliteTableDef = DefaultSqliteTableDef,
30
+ TIsSingleColumn extends boolean = boolean,
31
+ TOptions extends TableOptions = TableOptions,
32
+ > = {
33
+ schema: TTableDef
34
+ isSingleColumn: TIsSingleColumn
35
+ options: TOptions
36
+ }
37
+
38
+ export type TableOptionsInput = Partial<TableOptions & { indexes: SqliteDsl.Index[] }>
39
+
40
+ export type TableOptions = {
41
+ /**
42
+ * Setting this to true will have the following consequences:
43
+ * - An `id` column will be added with `primaryKey: true` and `"singleton"` as default value and only allowed value
44
+ * - LiveStore will automatically create the singleton row when the table is created
45
+ * - LiveStore will fail if there is already a column defined with `primaryKey: true`
46
+ *
47
+ * @default false
48
+ */
49
+ isSingleton: boolean
50
+ // TODO
51
+ dynamicRegistration: boolean
52
+ disableAutomaticIdColumn: boolean
53
+ }
54
+
55
+ export const table = <
56
+ TName extends string,
57
+ TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefinition<any, any>,
58
+ const TOptionsInput extends TableOptionsInput = TableOptionsInput,
59
+ >(
60
+ name: TName,
61
+ columnOrColumns: TColumns,
62
+ // type?: TStateType,
63
+ options?: TOptionsInput,
64
+ ): TableDef<
65
+ SqliteDsl.TableDefinition<
66
+ TName,
67
+ PrettifyFlat<
68
+ WithId<TColumns extends SqliteDsl.Columns ? TColumns : { value: TColumns }, WithDefaults<TOptionsInput>>
69
+ >
70
+ >,
71
+ TColumns extends SqliteDsl.ColumnDefinition<any, any> ? true : false,
72
+ WithDefaults<TOptionsInput>
73
+ > => {
74
+ const tablePath = name
75
+
76
+ const options_: TableOptions = {
77
+ isSingleton: options?.isSingleton ?? false,
78
+ dynamicRegistration: options?.dynamicRegistration ?? false,
79
+ disableAutomaticIdColumn: options?.disableAutomaticIdColumn ?? false,
80
+ }
81
+
82
+ const columns = (
83
+ SqliteDsl.isColumnDefinition(columnOrColumns) ? { value: columnOrColumns } : columnOrColumns
84
+ ) as SqliteDsl.Columns
85
+
86
+ if (options_.disableAutomaticIdColumn === true) {
87
+ if (columns.id === undefined && options_.isSingleton === true) {
88
+ shouldNeverHappen(
89
+ `Cannot create table ${name} with "isSingleton: true" because there is no column with name "id" and "disableAutomaticIdColumn: true" is set`,
90
+ )
91
+ }
92
+ } else {
93
+ if (columns.id === undefined) {
94
+ if (options_.isSingleton) {
95
+ columns.id = SqliteDsl.textWithSchema(Schema.literal('singleton'), { primaryKey: true, default: 'singleton' })
96
+ } else {
97
+ columns.id = SqliteDsl.text({ primaryKey: true })
98
+ }
99
+ }
100
+ }
101
+
102
+ const schema = SqliteDsl.table(tablePath, columns, options?.indexes ?? [])
103
+
104
+ if (options_.isSingleton) {
105
+ for (const column of schema.ast.columns) {
106
+ if (column.nullable === false && column.default === undefined) {
107
+ shouldNeverHappen(
108
+ `When creating a singleton table, each column must be either nullable or have a default value. Column '${column.name}' is neither.`,
109
+ )
110
+ }
111
+ }
112
+ }
113
+
114
+ const isSingleColumn = SqliteDsl.isColumnDefinition(columnOrColumns) === true
115
+
116
+ const tableDef = { schema, isSingleColumn, options: options_ }
117
+
118
+ if (dynamicallyRegisteredTables.has(tablePath)) {
119
+ if (SqliteAst.hash(dynamicallyRegisteredTables.get(tablePath)!.schema.ast) !== SqliteAst.hash(schema.ast)) {
120
+ console.error('previous tableDef', dynamicallyRegisteredTables.get(tablePath), 'new tableDef', schema.ast)
121
+ shouldNeverHappen(`Table with name "${name}" was already previously defined with a different definition`)
122
+ }
123
+ } else {
124
+ dynamicallyRegisteredTables.set(tablePath, tableDef)
125
+ }
126
+
127
+ return tableDef as any
128
+ }
129
+
130
+ type WithId<TColumns extends SqliteDsl.Columns, TOptions extends TableOptions> = TColumns &
131
+ (TOptions['disableAutomaticIdColumn'] extends true
132
+ ? {}
133
+ : TOptions['isSingleton'] extends true
134
+ ? {
135
+ id: SqliteDsl.ColumnDefinition<SqliteDsl.FieldType.FieldTypeText<'singleton', 'singleton'>, false>
136
+ }
137
+ : {
138
+ id: SqliteDsl.ColumnDefinition<SqliteDsl.FieldType.FieldTypeText<string, string>, false>
139
+ })
140
+
141
+ type WithDefaults<TOptionsInput extends TableOptionsInput> = {
142
+ isSingleton: TOptionsInput['isSingleton'] extends true ? true : false
143
+ dynamicRegistration: TOptionsInput['dynamicRegistration'] extends true ? true : false
144
+ disableAutomaticIdColumn: TOptionsInput['disableAutomaticIdColumn'] extends true ? true : false
145
+ }
146
+
147
+ export namespace FromTable {
148
+ // TODO this sometimes doesn't preserve the order of columns
149
+ export type RowDecoded<TTableDef extends TableDef> = PrettifyFlat<
150
+ Nullable<Pick<RowDecodedAll<TTableDef>, NullableColumnNames<TTableDef>>> &
151
+ Omit<RowDecodedAll<TTableDef>, NullableColumnNames<TTableDef>>
152
+ >
153
+
154
+ export type NullableColumnNames<TTableDef extends TableDef> = FromColumns.NullableColumnNames<
155
+ TTableDef['schema']['columns']
156
+ >
157
+
158
+ export type Columns<TTableDef extends TableDef> = {
159
+ [K in keyof TTableDef['schema']['columns']]: TTableDef['schema']['columns'][K]['type']['columnType']
160
+ }
161
+
162
+ export type RowEncodeNonNullable<TTableDef extends TableDef> = {
163
+ [K in keyof TTableDef['schema']['columns']]: Schema.Schema.From<TTableDef['schema']['columns'][K]['type']['codec']>
164
+ }
165
+
166
+ export type RowEncoded<TTableDef extends TableDef> = PrettifyFlat<
167
+ Nullable<Pick<RowEncodeNonNullable<TTableDef>, NullableColumnNames<TTableDef>>> &
168
+ Omit<RowEncodeNonNullable<TTableDef>, NullableColumnNames<TTableDef>>
169
+ >
170
+
171
+ export type RowDecodedAll<TTableDef extends TableDef> = {
172
+ [K in keyof TTableDef['schema']['columns']]: Schema.Schema.To<TTableDef['schema']['columns'][K]['type']['codec']>
173
+ }
174
+ }
175
+
176
+ export namespace FromColumns {
177
+ // TODO this sometimes doesn't preserve the order of columns
178
+ export type RowDecoded<TColumns extends SqliteDsl.Columns> = PrettifyFlat<
179
+ Nullable<Pick<RowDecodedAll<TColumns>, NullableColumnNames<TColumns>>> &
180
+ Omit<RowDecodedAll<TColumns>, NullableColumnNames<TColumns>>
181
+ >
182
+
183
+ export type RowDecodedAll<TColumns extends SqliteDsl.Columns> = {
184
+ [K in keyof TColumns]: Schema.Schema.To<TColumns[K]['type']['codec']>
185
+ }
186
+
187
+ export type RowEncoded<TColumns extends SqliteDsl.Columns> = PrettifyFlat<
188
+ Nullable<Pick<RowEncodeNonNullable<TColumns>, NullableColumnNames<TColumns>>> &
189
+ Omit<RowEncodeNonNullable<TColumns>, NullableColumnNames<TColumns>>
190
+ >
191
+
192
+ export type RowEncodeNonNullable<TColumns extends SqliteDsl.Columns> = {
193
+ [K in keyof TColumns]: Schema.Schema.From<TColumns[K]['type']['codec']>
194
+ }
195
+
196
+ export type NullableColumnNames<TColumns extends SqliteDsl.Columns> = keyof {
197
+ [K in keyof TColumns as TColumns[K] extends SqliteDsl.ColumnDefinition<any, true> ? K : never]: {}
198
+ }
199
+ }
@@ -1,6 +1,6 @@
1
1
  import type * as otel from '@opentelemetry/api'
2
2
 
3
- import type { PreparedBindValues } from '../../util.js'
3
+ import type { PreparedBindValues } from '../../utils/util.js'
4
4
  import type { Storage, StorageOtelProps } from '../index.js'
5
5
 
6
6
  export type StorageOptionsWebInMemory = {
@@ -8,11 +8,12 @@
8
8
 
9
9
  import type * as otel from '@opentelemetry/api'
10
10
 
11
- import type { PreparedBindValues } from '../util.js'
11
+ import type { PreparedBindValues } from '../utils/util.js'
12
12
 
13
13
  export type StorageInit = (otelProps: StorageOtelProps) => Promise<Storage> | Storage
14
14
 
15
15
  export interface Storage {
16
+ // TODO consider transferables for `bindValues` (e.g. Uint8Array values)
16
17
  execute(query: string, bindValues?: PreparedBindValues, parentSpan?: otel.Span): void
17
18
 
18
19
  /** Return a snapshot of persisted data from the storage */
@@ -2,8 +2,8 @@ import { getTraceParentHeader } from '@livestore/utils'
2
2
  import type * as otel from '@opentelemetry/api'
3
3
  import { invoke } from '@tauri-apps/api'
4
4
 
5
- import type { PreparedBindValues } from '../../util.js'
6
- import { prepareBindValues } from '../../util.js'
5
+ import type { PreparedBindValues } from '../../utils/util.js'
6
+ import { prepareBindValues } from '../../utils/util.js'
7
7
  import type { Storage, StorageOtelProps } from '../index.js'
8
8
 
9
9
  export type StorageOptionsTauri = {
@@ -2,7 +2,7 @@ import { casesHandled } from '@livestore/utils'
2
2
  import type * as otel from '@opentelemetry/api'
3
3
  import * as Comlink from 'comlink'
4
4
 
5
- import type { PreparedBindValues } from '../../util.js'
5
+ import type { PreparedBindValues } from '../../utils/util.js'
6
6
  import type { Storage, StorageOtelProps } from '../index.js'
7
7
  import { IDB } from '../utils/idb.js'
8
8
  import type { WrappedWorker } from './worker.js'
@@ -8,8 +8,8 @@ import type * as SqliteWasm from 'sqlite-esm'
8
8
  import sqlite3InitModule from 'sqlite-esm'
9
9
 
10
10
  // import { v4 as uuid } from 'uuid'
11
- import type { Bindable } from '../../util.js'
12
- import { casesHandled, sql } from '../../util.js'
11
+ import type { Bindable } from '../../utils/util.js'
12
+ import { casesHandled, sql } from '../../utils/util.js'
13
13
  import { IDB } from '../utils/idb.js'
14
14
  import type { StorageOptionsWeb } from './index.js'
15
15