@livestore/common 0.3.2-dev.9 → 0.4.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/ClientSessionLeaderThreadProxy.d.ts +2 -2
  3. package/dist/ClientSessionLeaderThreadProxy.d.ts.map +1 -1
  4. package/dist/adapter-types.d.ts +4 -4
  5. package/dist/adapter-types.d.ts.map +1 -1
  6. package/dist/debug-info.d.ts +17 -17
  7. package/dist/devtools/devtools-messages-client-session.d.ts +38 -38
  8. package/dist/devtools/devtools-messages-common.d.ts +6 -6
  9. package/dist/devtools/devtools-messages-leader.d.ts +28 -28
  10. package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
  11. package/dist/devtools/devtools-messages-leader.js.map +1 -1
  12. package/dist/leader-thread/LeaderSyncProcessor.js +3 -1
  13. package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
  14. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
  15. package/dist/leader-thread/make-leader-thread-layer.js +21 -4
  16. package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
  17. package/dist/leader-thread/shutdown-channel.d.ts +2 -2
  18. package/dist/leader-thread/shutdown-channel.d.ts.map +1 -1
  19. package/dist/leader-thread/shutdown-channel.js +2 -2
  20. package/dist/leader-thread/shutdown-channel.js.map +1 -1
  21. package/dist/leader-thread/types.d.ts +1 -1
  22. package/dist/leader-thread/types.d.ts.map +1 -1
  23. package/dist/materializer-helper.d.ts +3 -3
  24. package/dist/materializer-helper.d.ts.map +1 -1
  25. package/dist/materializer-helper.js +2 -2
  26. package/dist/materializer-helper.js.map +1 -1
  27. package/dist/rematerialize-from-eventlog.js +1 -1
  28. package/dist/rematerialize-from-eventlog.js.map +1 -1
  29. package/dist/schema/EventDef.d.ts +104 -178
  30. package/dist/schema/EventSequenceNumber.d.ts +5 -0
  31. package/dist/schema/EventSequenceNumber.d.ts.map +1 -1
  32. package/dist/schema/EventSequenceNumber.js +7 -2
  33. package/dist/schema/EventSequenceNumber.js.map +1 -1
  34. package/dist/schema/EventSequenceNumber.test.js +2 -2
  35. package/dist/schema/LiveStoreEvent.d.ts +6 -5
  36. package/dist/schema/LiveStoreEvent.d.ts.map +1 -1
  37. package/dist/schema/LiveStoreEvent.js +5 -0
  38. package/dist/schema/LiveStoreEvent.js.map +1 -1
  39. package/dist/schema/schema.d.ts +3 -0
  40. package/dist/schema/schema.d.ts.map +1 -1
  41. package/dist/schema/schema.js.map +1 -1
  42. package/dist/schema/state/sqlite/client-document-def.d.ts +3 -2
  43. package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -1
  44. package/dist/schema/state/sqlite/client-document-def.js +6 -4
  45. package/dist/schema/state/sqlite/client-document-def.js.map +1 -1
  46. package/dist/schema/state/sqlite/client-document-def.test.js +76 -1
  47. package/dist/schema/state/sqlite/client-document-def.test.js.map +1 -1
  48. package/dist/schema/state/sqlite/column-annotations.d.ts +34 -0
  49. package/dist/schema/state/sqlite/column-annotations.d.ts.map +1 -0
  50. package/dist/schema/state/sqlite/column-annotations.js +50 -0
  51. package/dist/schema/state/sqlite/column-annotations.js.map +1 -0
  52. package/dist/schema/state/sqlite/column-annotations.test.d.ts +2 -0
  53. package/dist/schema/state/sqlite/column-annotations.test.d.ts.map +1 -0
  54. package/dist/schema/state/sqlite/column-annotations.test.js +179 -0
  55. package/dist/schema/state/sqlite/column-annotations.test.js.map +1 -0
  56. package/dist/schema/state/sqlite/column-def.d.ts +15 -0
  57. package/dist/schema/state/sqlite/column-def.d.ts.map +1 -0
  58. package/dist/schema/state/sqlite/column-def.js +242 -0
  59. package/dist/schema/state/sqlite/column-def.js.map +1 -0
  60. package/dist/schema/state/sqlite/column-def.test.d.ts +2 -0
  61. package/dist/schema/state/sqlite/column-def.test.d.ts.map +1 -0
  62. package/dist/schema/state/sqlite/column-def.test.js +529 -0
  63. package/dist/schema/state/sqlite/column-def.test.js.map +1 -0
  64. package/dist/schema/state/sqlite/column-spec.d.ts +11 -0
  65. package/dist/schema/state/sqlite/column-spec.d.ts.map +1 -0
  66. package/dist/schema/state/sqlite/column-spec.js +39 -0
  67. package/dist/schema/state/sqlite/column-spec.js.map +1 -0
  68. package/dist/schema/state/sqlite/column-spec.test.d.ts +2 -0
  69. package/dist/schema/state/sqlite/column-spec.test.d.ts.map +1 -0
  70. package/dist/schema/state/sqlite/column-spec.test.js +146 -0
  71. package/dist/schema/state/sqlite/column-spec.test.js.map +1 -0
  72. package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts +1 -0
  73. package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts.map +1 -1
  74. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +1 -0
  75. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js.map +1 -1
  76. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts +17 -4
  77. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts.map +1 -1
  78. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js +2 -0
  79. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js.map +1 -1
  80. package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts +65 -165
  81. package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts.map +1 -1
  82. package/dist/schema/state/sqlite/db-schema/dsl/mod.js +1 -0
  83. package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -1
  84. package/dist/schema/state/sqlite/mod.d.ts +2 -0
  85. package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
  86. package/dist/schema/state/sqlite/mod.js +2 -0
  87. package/dist/schema/state/sqlite/mod.js.map +1 -1
  88. package/dist/schema/state/sqlite/query-builder/api.d.ts +309 -560
  89. package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -1
  90. package/dist/schema/state/sqlite/query-builder/astToSql.d.ts +1 -0
  91. package/dist/schema/state/sqlite/query-builder/astToSql.d.ts.map +1 -1
  92. package/dist/schema/state/sqlite/query-builder/astToSql.js +8 -6
  93. package/dist/schema/state/sqlite/query-builder/astToSql.js.map +1 -1
  94. package/dist/schema/state/sqlite/system-tables.d.ts +464 -46
  95. package/dist/schema/state/sqlite/system-tables.d.ts.map +1 -1
  96. package/dist/schema/state/sqlite/table-def.d.ts +159 -152
  97. package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
  98. package/dist/schema/state/sqlite/table-def.js +45 -6
  99. package/dist/schema/state/sqlite/table-def.js.map +1 -1
  100. package/dist/schema/state/sqlite/table-def.test.d.ts +2 -0
  101. package/dist/schema/state/sqlite/table-def.test.d.ts.map +1 -0
  102. package/dist/schema/state/sqlite/table-def.test.js +192 -0
  103. package/dist/schema/state/sqlite/table-def.test.js.map +1 -0
  104. package/dist/schema-management/common.d.ts +1 -1
  105. package/dist/schema-management/common.d.ts.map +1 -1
  106. package/dist/schema-management/common.js +11 -2
  107. package/dist/schema-management/common.js.map +1 -1
  108. package/dist/schema-management/migrations.d.ts +0 -1
  109. package/dist/schema-management/migrations.d.ts.map +1 -1
  110. package/dist/schema-management/migrations.js +4 -30
  111. package/dist/schema-management/migrations.js.map +1 -1
  112. package/dist/schema-management/migrations.test.d.ts +2 -0
  113. package/dist/schema-management/migrations.test.d.ts.map +1 -0
  114. package/dist/schema-management/migrations.test.js +52 -0
  115. package/dist/schema-management/migrations.test.js.map +1 -0
  116. package/dist/sql-queries/types.d.ts +37 -133
  117. package/dist/sqlite-db-helper.d.ts +3 -1
  118. package/dist/sqlite-db-helper.d.ts.map +1 -1
  119. package/dist/sqlite-db-helper.js +16 -0
  120. package/dist/sqlite-db-helper.js.map +1 -1
  121. package/dist/sqlite-types.d.ts +4 -4
  122. package/dist/sqlite-types.d.ts.map +1 -1
  123. package/dist/sync/ClientSessionSyncProcessor.d.ts +2 -2
  124. package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
  125. package/dist/sync/ClientSessionSyncProcessor.js +8 -7
  126. package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
  127. package/dist/sync/sync.d.ts.map +1 -1
  128. package/dist/sync/sync.js.map +1 -1
  129. package/dist/util.d.ts +3 -3
  130. package/dist/util.d.ts.map +1 -1
  131. package/dist/util.js.map +1 -1
  132. package/dist/version.d.ts +1 -1
  133. package/dist/version.js +1 -1
  134. package/package.json +4 -4
  135. package/src/ClientSessionLeaderThreadProxy.ts +2 -2
  136. package/src/adapter-types.ts +6 -4
  137. package/src/devtools/devtools-messages-leader.ts +3 -3
  138. package/src/leader-thread/LeaderSyncProcessor.ts +3 -1
  139. package/src/leader-thread/make-leader-thread-layer.ts +26 -7
  140. package/src/leader-thread/shutdown-channel.ts +2 -2
  141. package/src/leader-thread/types.ts +1 -1
  142. package/src/materializer-helper.ts +5 -11
  143. package/src/rematerialize-from-eventlog.ts +2 -2
  144. package/src/schema/EventSequenceNumber.test.ts +2 -2
  145. package/src/schema/EventSequenceNumber.ts +8 -2
  146. package/src/schema/LiveStoreEvent.ts +7 -1
  147. package/src/schema/schema.ts +4 -0
  148. package/src/schema/state/sqlite/client-document-def.test.ts +89 -1
  149. package/src/schema/state/sqlite/client-document-def.ts +7 -4
  150. package/src/schema/state/sqlite/column-annotations.test.ts +212 -0
  151. package/src/schema/state/sqlite/column-annotations.ts +77 -0
  152. package/src/schema/state/sqlite/column-def.test.ts +665 -0
  153. package/src/schema/state/sqlite/column-def.ts +290 -0
  154. package/src/schema/state/sqlite/column-spec.test.ts +223 -0
  155. package/src/schema/state/sqlite/column-spec.ts +42 -0
  156. package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +2 -0
  157. package/src/schema/state/sqlite/db-schema/dsl/__snapshots__/field-defs.test.ts.snap +15 -0
  158. package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +20 -2
  159. package/src/schema/state/sqlite/db-schema/dsl/mod.ts +1 -0
  160. package/src/schema/state/sqlite/mod.ts +2 -0
  161. package/src/schema/state/sqlite/query-builder/api.ts +4 -3
  162. package/src/schema/state/sqlite/query-builder/astToSql.ts +9 -7
  163. package/src/schema/state/sqlite/table-def.test.ts +241 -0
  164. package/src/schema/state/sqlite/table-def.ts +222 -16
  165. package/src/schema-management/common.ts +10 -3
  166. package/src/schema-management/migrations.ts +4 -33
  167. package/src/sqlite-db-helper.ts +19 -1
  168. package/src/sqlite-types.ts +4 -4
  169. package/src/sync/ClientSessionSyncProcessor.ts +13 -8
  170. package/src/sync/sync.ts +2 -0
  171. package/src/util.ts +7 -2
  172. package/src/version.ts +1 -1
@@ -9,9 +9,15 @@ export type ColumnDefinition<TEncoded, TDecoded> = {
9
9
  readonly nullable: boolean
10
10
  /** @default false */
11
11
  readonly primaryKey: boolean
12
+ /** @default false */
13
+ readonly autoIncrement: boolean
14
+ }
15
+
16
+ export declare namespace ColumnDefinition {
17
+ export type Any = ColumnDefinition<any, any>
12
18
  }
13
19
 
14
- export const isColumnDefinition = (value: unknown): value is ColumnDefinition<any, any> => {
20
+ export const isColumnDefinition = (value: unknown): value is ColumnDefinition.Any => {
15
21
  const validColumnTypes = ['text', 'integer', 'real', 'blob'] as const
16
22
  return (
17
23
  typeof value === 'object' &&
@@ -26,6 +32,7 @@ export type ColumnDefinitionInput = {
26
32
  readonly default?: unknown | NoDefault
27
33
  readonly nullable?: boolean
28
34
  readonly primaryKey?: boolean
35
+ readonly autoIncrement?: boolean
29
36
  }
30
37
 
31
38
  export const NoDefault = Symbol.for('NoDefault')
@@ -46,6 +53,7 @@ export type ColDefFn<TColumnType extends FieldColumnType> = {
46
53
  default: Option.None<never>
47
54
  nullable: false
48
55
  primaryKey: false
56
+ autoIncrement: false
49
57
  }
50
58
  <
51
59
  TEncoded extends DefaultEncodedForColumnType<TColumnType>,
@@ -53,11 +61,13 @@ export type ColDefFn<TColumnType extends FieldColumnType> = {
53
61
  const TNullable extends boolean = false,
54
62
  const TDefault extends TDecoded | SqlDefaultValue | NoDefault | (TNullable extends true ? null : never) = NoDefault,
55
63
  const TPrimaryKey extends boolean = false,
64
+ const TAutoIncrement extends boolean = false,
56
65
  >(args: {
57
66
  schema?: Schema.Schema<TDecoded, TEncoded>
58
67
  default?: TDefault
59
68
  nullable?: TNullable
60
69
  primaryKey?: TPrimaryKey
70
+ autoIncrement?: TAutoIncrement
61
71
  }): {
62
72
  columnType: TColumnType
63
73
  schema: TNullable extends true
@@ -66,6 +76,7 @@ export type ColDefFn<TColumnType extends FieldColumnType> = {
66
76
  default: TDefault extends NoDefault ? Option.None<never> : Option.Some<NoInfer<TDefault>>
67
77
  nullable: NoInfer<TNullable>
68
78
  primaryKey: NoInfer<TPrimaryKey>
79
+ autoIncrement: NoInfer<TAutoIncrement>
69
80
  }
70
81
  }
71
82
 
@@ -83,6 +94,7 @@ const makeColDef =
83
94
  default: default_,
84
95
  nullable,
85
96
  primaryKey: def?.primaryKey ?? false,
97
+ autoIncrement: def?.autoIncrement ?? false,
86
98
  } as any
87
99
  }
88
100
 
@@ -115,12 +127,14 @@ export type SpecializedColDefFn<
115
127
  default: Option.None<never>
116
128
  nullable: false
117
129
  primaryKey: false
130
+ autoIncrement: false
118
131
  }
119
132
  <
120
133
  TDecoded = TBaseDecoded,
121
134
  const TNullable extends boolean = false,
122
135
  const TDefault extends TDecoded | NoDefault | (TNullable extends true ? null : never) = NoDefault,
123
136
  const TPrimaryKey extends boolean = false,
137
+ const TAutoIncrement extends boolean = false,
124
138
  >(
125
139
  args: TAllowsCustomSchema extends true
126
140
  ? {
@@ -128,11 +142,13 @@ export type SpecializedColDefFn<
128
142
  default?: TDefault
129
143
  nullable?: TNullable
130
144
  primaryKey?: TPrimaryKey
145
+ autoIncrement?: TAutoIncrement
131
146
  }
132
147
  : {
133
148
  default?: TDefault
134
149
  nullable?: TNullable
135
150
  primaryKey?: TPrimaryKey
151
+ autoIncrement?: TAutoIncrement
136
152
  },
137
153
  ): {
138
154
  columnType: TColumnType
@@ -142,6 +158,7 @@ export type SpecializedColDefFn<
142
158
  default: TDefault extends NoDefault ? Option.None<never> : Option.Some<TDefault>
143
159
  nullable: NoInfer<TNullable>
144
160
  primaryKey: NoInfer<TPrimaryKey>
161
+ autoIncrement: NoInfer<TAutoIncrement>
145
162
  }
146
163
  }
147
164
 
@@ -176,6 +193,7 @@ const makeSpecializedColDef: MakeSpecializedColDefFn = (columnType, opts) => (de
176
193
  default: default_,
177
194
  nullable,
178
195
  primaryKey: def?.primaryKey ?? false,
196
+ autoIncrement: def?.autoIncrement ?? false,
179
197
  } as any
180
198
  }
181
199
 
@@ -214,7 +232,7 @@ export type DefaultEncodedForColumnType<TColumnType extends FieldColumnType> = T
214
232
  : TColumnType extends 'real'
215
233
  ? number
216
234
  : TColumnType extends 'blob'
217
- ? Uint8Array
235
+ ? Uint8Array<ArrayBuffer>
218
236
  : never
219
237
 
220
238
  export const defaultSchemaForColumnType = <TColumnType extends FieldColumnType>(
@@ -92,6 +92,7 @@ const columsToAst = (columns: Columns): ReadonlyArray<SqliteAst.Column> => {
92
92
  default: column.default as any,
93
93
  nullable: column.nullable ?? false,
94
94
  primaryKey: column.primaryKey ?? false,
95
+ autoIncrement: column.autoIncrement ?? false,
95
96
  type: { _tag: column.columnType },
96
97
  } satisfies SqliteAst.Column
97
98
  })
@@ -16,6 +16,8 @@ export {
16
16
  clientDocument,
17
17
  tableIsClientDocumentTable,
18
18
  } from './client-document-def.ts'
19
+ export * from './column-annotations.ts'
20
+ export * from './column-spec.ts'
19
21
  export * from './table-def.ts'
20
22
 
21
23
  export const makeState = <TStateInput extends InputState>(inputSchema: TStateInput): InternalState => {
@@ -122,7 +122,7 @@ export type QueryBuilder<
122
122
  readonly [QueryBuilderTypeId]: QueryBuilderTypeId
123
123
  readonly [QueryBuilderAstSymbol]: QueryBuilderAst
124
124
  readonly ResultType: TResult
125
- readonly asSql: () => { query: string; bindValues: SqlValue[] }
125
+ readonly asSql: () => { query: string; bindValues: SqlValue[]; usedTables: Set<string> }
126
126
  readonly toString: () => string
127
127
  } & Omit<QueryBuilder.ApiFull<TResult, TTableDef, TWithout>, TWithout>
128
128
 
@@ -264,14 +264,15 @@ export namespace QueryBuilder {
264
264
  * Example:
265
265
  * ```ts
266
266
  * db.todos.orderBy('createdAt', 'desc')
267
+ * db.todos.orderBy([{ col: 'createdAt', direction: 'desc' }])
267
268
  * ```
268
269
  */
269
270
  readonly orderBy: {
270
- <TColName extends keyof TTableDef['sqliteDef']['columns'] & string>(
271
+ <const TColName extends keyof TTableDef['sqliteDef']['columns'] & string>(
271
272
  col: TColName,
272
273
  direction: 'asc' | 'desc',
273
274
  ): QueryBuilder<TResult, TTableDef, TWithout | 'returning' | 'onConflict'>
274
- <TParams extends QueryBuilder.OrderByParams<TTableDef>>(
275
+ <const TParams extends QueryBuilder.OrderByParams<TTableDef>>(
275
276
  params: TParams,
276
277
  ): QueryBuilder<TResult, TTableDef, TWithout | 'returning' | 'onConflict'>
277
278
  }
@@ -65,8 +65,9 @@ const formatReturningClause = (returning?: string[]): string => {
65
65
  return ` RETURNING ${returning.join(', ')}`
66
66
  }
67
67
 
68
- export const astToSql = (ast: QueryBuilderAst): { query: string; bindValues: SqlValue[] } => {
68
+ export const astToSql = (ast: QueryBuilderAst): { query: string; bindValues: SqlValue[]; usedTables: Set<string> } => {
69
69
  const bindValues: SqlValue[] = []
70
+ const usedTables = new Set<string>([ast.tableDef.sqliteDef.name])
70
71
 
71
72
  // INSERT query
72
73
  if (ast._tag === 'InsertQuery') {
@@ -134,7 +135,7 @@ export const astToSql = (ast: QueryBuilderAst): { query: string; bindValues: Sql
134
135
  query += conflictClause
135
136
 
136
137
  query += formatReturningClause(ast.returning)
137
- return { query, bindValues }
138
+ return { query, bindValues, usedTables }
138
139
  }
139
140
 
140
141
  // UPDATE query
@@ -145,7 +146,7 @@ export const astToSql = (ast: QueryBuilderAst): { query: string; bindValues: Sql
145
146
  console.warn(
146
147
  `UPDATE query requires at least one column to set (for table ${ast.tableDef.sqliteDef.name}). Running no-op query instead to skip this update query.`,
147
148
  )
148
- return { query: 'SELECT 1', bindValues: [] }
149
+ return { query: 'SELECT 1', bindValues: [], usedTables }
149
150
  // return shouldNeverHappen('UPDATE query requires at least one column to set.')
150
151
  }
151
152
 
@@ -162,7 +163,7 @@ export const astToSql = (ast: QueryBuilderAst): { query: string; bindValues: Sql
162
163
  if (whereClause) query += ` ${whereClause}`
163
164
 
164
165
  query += formatReturningClause(ast.returning)
165
- return { query, bindValues }
166
+ return { query, bindValues, usedTables }
166
167
  }
167
168
 
168
169
  // DELETE query
@@ -173,7 +174,7 @@ export const astToSql = (ast: QueryBuilderAst): { query: string; bindValues: Sql
173
174
  if (whereClause) query += ` ${whereClause}`
174
175
 
175
176
  query += formatReturningClause(ast.returning)
176
- return { query, bindValues }
177
+ return { query, bindValues, usedTables }
177
178
  }
178
179
 
179
180
  // COUNT query
@@ -185,7 +186,7 @@ export const astToSql = (ast: QueryBuilderAst): { query: string; bindValues: Sql
185
186
  .filter((clause) => clause.length > 0)
186
187
  .join(' ')
187
188
 
188
- return { query, bindValues }
189
+ return { query, bindValues, usedTables }
189
190
  }
190
191
 
191
192
  // ROW query
@@ -202,6 +203,7 @@ export const astToSql = (ast: QueryBuilderAst): { query: string; bindValues: Sql
202
203
  return {
203
204
  query: `SELECT * FROM '${ast.tableDef.sqliteDef.name}' WHERE id = ?`,
204
205
  bindValues: [encodedId as SqlValue],
206
+ usedTables,
205
207
  }
206
208
  }
207
209
 
@@ -228,5 +230,5 @@ export const astToSql = (ast: QueryBuilderAst): { query: string; bindValues: Sql
228
230
  .filter((clause) => clause.length > 0)
229
231
  .join(' ')
230
232
 
231
- return { query, bindValues }
233
+ return { query, bindValues, usedTables }
232
234
  }
@@ -0,0 +1,241 @@
1
+ import { Schema } from '@livestore/utils/effect'
2
+ import { describe, expect, it } from 'vitest'
3
+ import { State } from '../../mod.ts'
4
+
5
+ describe('table function overloads', () => {
6
+ it('should extract table name from title annotation', () => {
7
+ const TodoSchema = Schema.Struct({
8
+ id: Schema.String,
9
+ text: Schema.String,
10
+ completed: Schema.Boolean,
11
+ }).annotations({ title: 'todos' })
12
+
13
+ const todosTable = State.SQLite.table({
14
+ schema: TodoSchema,
15
+ })
16
+
17
+ expect(todosTable.sqliteDef.name).toBe('todos')
18
+ })
19
+
20
+ it('should extract table name from identifier annotation', () => {
21
+ const TodoSchema = Schema.Struct({
22
+ id: Schema.String,
23
+ text: Schema.String,
24
+ completed: Schema.Boolean,
25
+ }).annotations({ identifier: 'TodoItem' })
26
+
27
+ const todosTable = State.SQLite.table({ schema: TodoSchema })
28
+
29
+ expect(todosTable.sqliteDef.name).toBe('TodoItem')
30
+ })
31
+
32
+ it('should prefer title over identifier annotation', () => {
33
+ const TodoSchema = Schema.Struct({
34
+ id: Schema.String,
35
+ text: Schema.String,
36
+ completed: Schema.Boolean,
37
+ }).annotations({
38
+ title: 'todos',
39
+ identifier: 'TodoItem',
40
+ })
41
+
42
+ const todosTable = State.SQLite.table({ schema: TodoSchema })
43
+
44
+ expect(todosTable.sqliteDef.name).toBe('todos')
45
+ })
46
+
47
+ it('should throw when schema has no name, title, or identifier', () => {
48
+ const TodoSchema = Schema.Struct({
49
+ id: Schema.String,
50
+ text: Schema.String,
51
+ completed: Schema.Boolean,
52
+ })
53
+
54
+ expect(() => State.SQLite.table({ schema: TodoSchema })).toThrow(
55
+ 'When using schema without explicit name, the schema must have a title or identifier annotation',
56
+ )
57
+ })
58
+
59
+ it('should work with columns parameter', () => {
60
+ const todosTable = State.SQLite.table({
61
+ name: 'todos',
62
+ columns: {
63
+ id: State.SQLite.text({ primaryKey: true }),
64
+ text: State.SQLite.text({ default: '' }),
65
+ completed: State.SQLite.boolean({ default: false }),
66
+ optionalComplex: State.SQLite.json({
67
+ nullable: true,
68
+ schema: Schema.Struct({ color: Schema.String }).pipe(Schema.UndefinedOr),
69
+ }),
70
+ },
71
+ })
72
+
73
+ expect((todosTable.rowSchema as any).fields.completed.toString()).toMatchInlineSnapshot(`"(number <-> boolean)"`)
74
+ expect(todosTable.sqliteDef.name).toBe('todos')
75
+ expect(todosTable.sqliteDef.columns).toHaveProperty('id')
76
+ expect(todosTable.sqliteDef.columns).toHaveProperty('text')
77
+ expect(todosTable.sqliteDef.columns).toHaveProperty('completed')
78
+ expect(todosTable.sqliteDef.columns).toHaveProperty('optionalComplex')
79
+ expect(todosTable.sqliteDef.columns.optionalComplex.nullable).toBe(true)
80
+ expect((todosTable.rowSchema as any).fields.optionalComplex.toString()).toBe(
81
+ '(parseJson <-> { readonly color: string } | undefined) | null',
82
+ )
83
+ })
84
+
85
+ it('should work with schema parameter', () => {
86
+ const TodoSchema = Schema.Struct({
87
+ id: Schema.String,
88
+ text: Schema.String,
89
+ completed: Schema.Boolean,
90
+ })
91
+
92
+ const todosTable = State.SQLite.table({
93
+ name: 'todos',
94
+ schema: TodoSchema,
95
+ })
96
+
97
+ expect(todosTable.sqliteDef.name).toBe('todos')
98
+ expect(todosTable.sqliteDef.columns).toHaveProperty('id')
99
+ expect(todosTable.sqliteDef.columns).toHaveProperty('text')
100
+ expect(todosTable.sqliteDef.columns).toHaveProperty('completed')
101
+ })
102
+
103
+ it('should work with single column', () => {
104
+ const simpleTable = State.SQLite.table({
105
+ name: 'simple',
106
+ columns: State.SQLite.text({ primaryKey: true }),
107
+ })
108
+
109
+ expect(simpleTable.sqliteDef.name).toBe('simple')
110
+ expect(simpleTable.sqliteDef.columns).toHaveProperty('value')
111
+ expect(simpleTable.sqliteDef.columns.value.primaryKey).toBe(true)
112
+ })
113
+
114
+ it('should handle optional fields in schema', () => {
115
+ const UserSchema = Schema.Struct({
116
+ id: Schema.String,
117
+ name: Schema.String,
118
+ email: Schema.optional(Schema.String),
119
+ })
120
+
121
+ const userTable = State.SQLite.table({
122
+ name: 'users',
123
+ schema: UserSchema,
124
+ })
125
+
126
+ expect(userTable.sqliteDef.columns.id.nullable).toBe(false)
127
+ expect(userTable.sqliteDef.columns.name.nullable).toBe(false)
128
+ expect(userTable.sqliteDef.columns.email.nullable).toBe(true)
129
+ })
130
+
131
+ it('should handle Schema.Int as integer column', () => {
132
+ const CounterSchema = Schema.Struct({
133
+ id: Schema.String,
134
+ count: Schema.Int,
135
+ })
136
+
137
+ const counterTable = State.SQLite.table({
138
+ name: 'counters',
139
+ schema: CounterSchema,
140
+ })
141
+
142
+ expect(counterTable.sqliteDef.columns.count.columnType).toBe('integer')
143
+ })
144
+
145
+ it('should work with Schema.Class', () => {
146
+ class User extends Schema.Class<User>('User')({
147
+ id: Schema.String,
148
+ name: Schema.String,
149
+ email: Schema.optional(Schema.String),
150
+ age: Schema.Int,
151
+ }) {}
152
+
153
+ const userTable = State.SQLite.table({
154
+ name: 'users',
155
+ schema: User,
156
+ })
157
+
158
+ expect(userTable.sqliteDef.name).toBe('users')
159
+ expect(userTable.sqliteDef.columns).toHaveProperty('id')
160
+ expect(userTable.sqliteDef.columns).toHaveProperty('name')
161
+ expect(userTable.sqliteDef.columns).toHaveProperty('email')
162
+ expect(userTable.sqliteDef.columns).toHaveProperty('age')
163
+
164
+ // Check column types
165
+ expect(userTable.sqliteDef.columns.id.columnType).toBe('text')
166
+ expect(userTable.sqliteDef.columns.name.columnType).toBe('text')
167
+ expect(userTable.sqliteDef.columns.email.columnType).toBe('text')
168
+ expect(userTable.sqliteDef.columns.email.nullable).toBe(true)
169
+ expect(userTable.sqliteDef.columns.age.columnType).toBe('integer')
170
+ })
171
+
172
+ it('should extract table name from Schema.Class identifier', () => {
173
+ class TodoItem extends Schema.Class<TodoItem>('TodoItem')({
174
+ id: Schema.String,
175
+ text: Schema.String,
176
+ completed: Schema.Boolean,
177
+ }) {}
178
+
179
+ // Schema.Class doesn't set identifier/title annotations, so we need to provide an explicit name
180
+ const todosTable = State.SQLite.table({
181
+ name: 'TodoItem',
182
+ schema: TodoItem,
183
+ })
184
+
185
+ expect(todosTable.sqliteDef.name).toBe('TodoItem')
186
+ })
187
+
188
+ it('should properly infer types from schema', () => {
189
+ const UserSchema = Schema.Struct({
190
+ id: Schema.String,
191
+ name: Schema.String,
192
+ age: Schema.Int,
193
+ active: Schema.Boolean,
194
+ metadata: Schema.optional(Schema.Record({ key: Schema.String, value: Schema.Unknown })),
195
+ })
196
+
197
+ const userTable = State.SQLite.table({
198
+ name: 'users',
199
+ schema: UserSchema,
200
+ })
201
+
202
+ // Test that Type is properly inferred
203
+ type UserType = typeof userTable.Type
204
+ const _userTypeCheck: UserType = {
205
+ id: 'test-id',
206
+ name: 'John',
207
+ age: 30,
208
+ active: true,
209
+ metadata: { key1: 'value1', key2: 123 },
210
+ }
211
+
212
+ // Test that columns have proper schema types
213
+ type IdColumn = typeof userTable.sqliteDef.columns.id
214
+ type NameColumn = typeof userTable.sqliteDef.columns.name
215
+ type AgeColumn = typeof userTable.sqliteDef.columns.age
216
+ type ActiveColumn = typeof userTable.sqliteDef.columns.active
217
+ type MetadataColumn = typeof userTable.sqliteDef.columns.metadata
218
+
219
+ // Should derive proper column schema
220
+ expect((userTable.rowSchema as any).fields.age.toString()).toMatchInlineSnapshot(`"number"`)
221
+ expect((userTable.rowSchema as any).fields.active.toString()).toMatchInlineSnapshot(`"(number <-> boolean)"`)
222
+ expect((userTable.rowSchema as any).fields.metadata.toString()).toMatchInlineSnapshot(
223
+ `"(parseJson <-> { readonly [x: string]: unknown } | undefined)"`,
224
+ )
225
+
226
+ // These should compile without errors
227
+ const _idCheck: IdColumn['schema']['Type'] = 'string'
228
+ const _nameCheck: NameColumn['schema']['Type'] = 'string'
229
+ const _ageCheck: AgeColumn['schema']['Type'] = 123
230
+ const _activeCheck: ActiveColumn['schema']['Type'] = true
231
+ const _metadataCheck: MetadataColumn['schema']['Type'] = { foo: 'bar' }
232
+
233
+ // Verify column definitions
234
+ expect(userTable.sqliteDef.columns.id.columnType).toBe('text')
235
+ expect(userTable.sqliteDef.columns.name.columnType).toBe('text')
236
+ expect(userTable.sqliteDef.columns.age.columnType).toBe('integer')
237
+ expect(userTable.sqliteDef.columns.active.columnType).toBe('integer')
238
+ expect(userTable.sqliteDef.columns.metadata.columnType).toBe('text')
239
+ expect(userTable.sqliteDef.columns.metadata.nullable).toBe(true)
240
+ })
241
+ })