@livestore/common 0.3.2-dev.9 → 0.4.0-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/ClientSessionLeaderThreadProxy.d.ts +2 -2
- package/dist/ClientSessionLeaderThreadProxy.d.ts.map +1 -1
- package/dist/adapter-types.d.ts +4 -4
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/debug-info.d.ts +17 -17
- package/dist/devtools/devtools-messages-client-session.d.ts +38 -38
- package/dist/devtools/devtools-messages-common.d.ts +6 -6
- package/dist/devtools/devtools-messages-leader.d.ts +28 -28
- package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-leader.js.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +3 -1
- package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +21 -4
- package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
- package/dist/leader-thread/shutdown-channel.d.ts +2 -2
- package/dist/leader-thread/shutdown-channel.d.ts.map +1 -1
- package/dist/leader-thread/shutdown-channel.js +2 -2
- package/dist/leader-thread/shutdown-channel.js.map +1 -1
- package/dist/leader-thread/types.d.ts +1 -1
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/materializer-helper.d.ts +3 -3
- package/dist/materializer-helper.d.ts.map +1 -1
- package/dist/materializer-helper.js +2 -2
- package/dist/materializer-helper.js.map +1 -1
- package/dist/rematerialize-from-eventlog.js +1 -1
- package/dist/rematerialize-from-eventlog.js.map +1 -1
- package/dist/schema/EventDef.d.ts +104 -178
- package/dist/schema/EventSequenceNumber.d.ts +5 -0
- package/dist/schema/EventSequenceNumber.d.ts.map +1 -1
- package/dist/schema/EventSequenceNumber.js +7 -2
- package/dist/schema/EventSequenceNumber.js.map +1 -1
- package/dist/schema/EventSequenceNumber.test.js +2 -2
- package/dist/schema/LiveStoreEvent.d.ts +6 -5
- package/dist/schema/LiveStoreEvent.d.ts.map +1 -1
- package/dist/schema/LiveStoreEvent.js +5 -0
- package/dist/schema/LiveStoreEvent.js.map +1 -1
- package/dist/schema/schema.d.ts +3 -0
- package/dist/schema/schema.d.ts.map +1 -1
- package/dist/schema/schema.js.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.d.ts +3 -2
- package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.js +6 -4
- package/dist/schema/state/sqlite/client-document-def.js.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.test.js +76 -1
- package/dist/schema/state/sqlite/client-document-def.test.js.map +1 -1
- package/dist/schema/state/sqlite/column-annotations.d.ts +34 -0
- package/dist/schema/state/sqlite/column-annotations.d.ts.map +1 -0
- package/dist/schema/state/sqlite/column-annotations.js +50 -0
- package/dist/schema/state/sqlite/column-annotations.js.map +1 -0
- package/dist/schema/state/sqlite/column-annotations.test.d.ts +2 -0
- package/dist/schema/state/sqlite/column-annotations.test.d.ts.map +1 -0
- package/dist/schema/state/sqlite/column-annotations.test.js +179 -0
- package/dist/schema/state/sqlite/column-annotations.test.js.map +1 -0
- package/dist/schema/state/sqlite/column-spec.d.ts +11 -0
- package/dist/schema/state/sqlite/column-spec.d.ts.map +1 -0
- package/dist/schema/state/sqlite/column-spec.js +39 -0
- package/dist/schema/state/sqlite/column-spec.js.map +1 -0
- package/dist/schema/state/sqlite/column-spec.test.d.ts +2 -0
- package/dist/schema/state/sqlite/column-spec.test.d.ts.map +1 -0
- package/dist/schema/state/sqlite/column-spec.test.js +146 -0
- package/dist/schema/state/sqlite/column-spec.test.js.map +1 -0
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts +1 -0
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts.map +1 -1
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +1 -0
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts +17 -4
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js +2 -0
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts +65 -165
- package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.js +1 -0
- package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -1
- package/dist/schema/state/sqlite/mod.d.ts +2 -0
- package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
- package/dist/schema/state/sqlite/mod.js +2 -0
- package/dist/schema/state/sqlite/mod.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/api.d.ts +309 -560
- package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/astToSql.d.ts +1 -0
- package/dist/schema/state/sqlite/query-builder/astToSql.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/astToSql.js +8 -6
- package/dist/schema/state/sqlite/query-builder/astToSql.js.map +1 -1
- package/dist/schema/state/sqlite/system-tables.d.ts +464 -46
- package/dist/schema/state/sqlite/system-tables.d.ts.map +1 -1
- package/dist/schema/state/sqlite/table-def.d.ts +161 -152
- package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/table-def.js +251 -5
- package/dist/schema/state/sqlite/table-def.js.map +1 -1
- package/dist/schema/state/sqlite/table-def.test.d.ts +2 -0
- package/dist/schema/state/sqlite/table-def.test.d.ts.map +1 -0
- package/dist/schema/state/sqlite/table-def.test.js +635 -0
- package/dist/schema/state/sqlite/table-def.test.js.map +1 -0
- package/dist/schema-management/common.d.ts +1 -1
- package/dist/schema-management/common.d.ts.map +1 -1
- package/dist/schema-management/common.js +11 -2
- package/dist/schema-management/common.js.map +1 -1
- package/dist/schema-management/migrations.d.ts +0 -1
- package/dist/schema-management/migrations.d.ts.map +1 -1
- package/dist/schema-management/migrations.js +4 -30
- package/dist/schema-management/migrations.js.map +1 -1
- package/dist/schema-management/migrations.test.d.ts +2 -0
- package/dist/schema-management/migrations.test.d.ts.map +1 -0
- package/dist/schema-management/migrations.test.js +52 -0
- package/dist/schema-management/migrations.test.js.map +1 -0
- package/dist/sql-queries/types.d.ts +37 -133
- package/dist/sqlite-db-helper.d.ts +3 -1
- package/dist/sqlite-db-helper.d.ts.map +1 -1
- package/dist/sqlite-db-helper.js +16 -0
- package/dist/sqlite-db-helper.js.map +1 -1
- package/dist/sqlite-types.d.ts +4 -4
- package/dist/sqlite-types.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.d.ts +2 -2
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +8 -7
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
- package/dist/sync/sync.d.ts.map +1 -1
- package/dist/sync/sync.js.map +1 -1
- package/dist/util.d.ts +3 -3
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
- package/src/ClientSessionLeaderThreadProxy.ts +2 -2
- package/src/adapter-types.ts +6 -4
- package/src/devtools/devtools-messages-leader.ts +3 -3
- package/src/leader-thread/LeaderSyncProcessor.ts +3 -1
- package/src/leader-thread/make-leader-thread-layer.ts +26 -7
- package/src/leader-thread/shutdown-channel.ts +2 -2
- package/src/leader-thread/types.ts +1 -1
- package/src/materializer-helper.ts +5 -11
- package/src/rematerialize-from-eventlog.ts +2 -2
- package/src/schema/EventSequenceNumber.test.ts +2 -2
- package/src/schema/EventSequenceNumber.ts +8 -2
- package/src/schema/LiveStoreEvent.ts +7 -1
- package/src/schema/schema.ts +4 -0
- package/src/schema/state/sqlite/client-document-def.test.ts +89 -1
- package/src/schema/state/sqlite/client-document-def.ts +7 -4
- package/src/schema/state/sqlite/column-annotations.test.ts +212 -0
- package/src/schema/state/sqlite/column-annotations.ts +77 -0
- package/src/schema/state/sqlite/column-spec.test.ts +223 -0
- package/src/schema/state/sqlite/column-spec.ts +42 -0
- package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +2 -0
- package/src/schema/state/sqlite/db-schema/dsl/__snapshots__/field-defs.test.ts.snap +15 -0
- package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +20 -2
- package/src/schema/state/sqlite/db-schema/dsl/mod.ts +1 -0
- package/src/schema/state/sqlite/mod.ts +2 -0
- package/src/schema/state/sqlite/query-builder/api.ts +4 -3
- package/src/schema/state/sqlite/query-builder/astToSql.ts +9 -7
- package/src/schema/state/sqlite/table-def.test.ts +798 -0
- package/src/schema/state/sqlite/table-def.ts +472 -16
- package/src/schema-management/common.ts +10 -3
- package/src/schema-management/migrations.ts +4 -33
- package/src/sqlite-db-helper.ts +19 -1
- package/src/sqlite-types.ts +4 -4
- package/src/sync/ClientSessionSyncProcessor.ts +13 -8
- package/src/sync/sync.ts +2 -0
- package/src/util.ts +7 -2
- package/src/version.ts +1 -1
@@ -0,0 +1,798 @@
|
|
1
|
+
import { Schema } from '@livestore/utils/effect'
|
2
|
+
import { describe, expect, it } from 'vitest'
|
3
|
+
import { State } from '../../mod.ts'
|
4
|
+
import { withAutoIncrement, withColumnType, withDefault, withPrimaryKey, withUnique } from './column-annotations.ts'
|
5
|
+
|
6
|
+
describe('table function overloads', () => {
|
7
|
+
it('should extract table name from title annotation', () => {
|
8
|
+
const TodoSchema = Schema.Struct({
|
9
|
+
id: Schema.String,
|
10
|
+
text: Schema.String,
|
11
|
+
completed: Schema.Boolean,
|
12
|
+
}).annotations({ title: 'todos' })
|
13
|
+
|
14
|
+
const todosTable = State.SQLite.table({
|
15
|
+
schema: TodoSchema,
|
16
|
+
})
|
17
|
+
|
18
|
+
expect(todosTable.sqliteDef.name).toBe('todos')
|
19
|
+
})
|
20
|
+
|
21
|
+
it('should extract table name from identifier annotation', () => {
|
22
|
+
const TodoSchema = Schema.Struct({
|
23
|
+
id: Schema.String,
|
24
|
+
text: Schema.String,
|
25
|
+
completed: Schema.Boolean,
|
26
|
+
}).annotations({ identifier: 'TodoItem' })
|
27
|
+
|
28
|
+
const todosTable = State.SQLite.table({ schema: TodoSchema })
|
29
|
+
|
30
|
+
expect(todosTable.sqliteDef.name).toBe('TodoItem')
|
31
|
+
})
|
32
|
+
|
33
|
+
it('should prefer title over identifier annotation', () => {
|
34
|
+
const TodoSchema = Schema.Struct({
|
35
|
+
id: Schema.String,
|
36
|
+
text: Schema.String,
|
37
|
+
completed: Schema.Boolean,
|
38
|
+
}).annotations({
|
39
|
+
title: 'todos',
|
40
|
+
identifier: 'TodoItem',
|
41
|
+
})
|
42
|
+
|
43
|
+
const todosTable = State.SQLite.table({ schema: TodoSchema })
|
44
|
+
|
45
|
+
expect(todosTable.sqliteDef.name).toBe('todos')
|
46
|
+
})
|
47
|
+
|
48
|
+
it('should throw when schema has no name, title, or identifier', () => {
|
49
|
+
const TodoSchema = Schema.Struct({
|
50
|
+
id: Schema.String,
|
51
|
+
text: Schema.String,
|
52
|
+
completed: Schema.Boolean,
|
53
|
+
})
|
54
|
+
|
55
|
+
expect(() => State.SQLite.table({ schema: TodoSchema })).toThrow(
|
56
|
+
'When using schema without explicit name, the schema must have a title or identifier annotation',
|
57
|
+
)
|
58
|
+
})
|
59
|
+
|
60
|
+
it('should work with columns parameter', () => {
|
61
|
+
const todosTable = State.SQLite.table({
|
62
|
+
name: 'todos',
|
63
|
+
columns: {
|
64
|
+
id: State.SQLite.text({ primaryKey: true }),
|
65
|
+
text: State.SQLite.text({ default: '' }),
|
66
|
+
completed: State.SQLite.boolean({ default: false }),
|
67
|
+
},
|
68
|
+
})
|
69
|
+
|
70
|
+
expect(todosTable.sqliteDef.name).toBe('todos')
|
71
|
+
expect(todosTable.sqliteDef.columns).toHaveProperty('id')
|
72
|
+
expect(todosTable.sqliteDef.columns).toHaveProperty('text')
|
73
|
+
expect(todosTable.sqliteDef.columns).toHaveProperty('completed')
|
74
|
+
})
|
75
|
+
|
76
|
+
it('should work with schema parameter', () => {
|
77
|
+
const TodoSchema = Schema.Struct({
|
78
|
+
id: Schema.String,
|
79
|
+
text: Schema.String,
|
80
|
+
completed: Schema.Boolean,
|
81
|
+
})
|
82
|
+
|
83
|
+
const todosTable = State.SQLite.table({
|
84
|
+
name: 'todos',
|
85
|
+
schema: TodoSchema,
|
86
|
+
})
|
87
|
+
|
88
|
+
expect(todosTable.sqliteDef.name).toBe('todos')
|
89
|
+
expect(todosTable.sqliteDef.columns).toHaveProperty('id')
|
90
|
+
expect(todosTable.sqliteDef.columns).toHaveProperty('text')
|
91
|
+
expect(todosTable.sqliteDef.columns).toHaveProperty('completed')
|
92
|
+
})
|
93
|
+
|
94
|
+
it('should work with single column', () => {
|
95
|
+
const simpleTable = State.SQLite.table({
|
96
|
+
name: 'simple',
|
97
|
+
columns: State.SQLite.text({ primaryKey: true }),
|
98
|
+
})
|
99
|
+
|
100
|
+
expect(simpleTable.sqliteDef.name).toBe('simple')
|
101
|
+
expect(simpleTable.sqliteDef.columns).toHaveProperty('value')
|
102
|
+
expect(simpleTable.sqliteDef.columns.value.primaryKey).toBe(true)
|
103
|
+
})
|
104
|
+
|
105
|
+
it('should handle optional fields in schema', () => {
|
106
|
+
const UserSchema = Schema.Struct({
|
107
|
+
id: Schema.String,
|
108
|
+
name: Schema.String,
|
109
|
+
email: Schema.optional(Schema.String),
|
110
|
+
})
|
111
|
+
|
112
|
+
const userTable = State.SQLite.table({
|
113
|
+
name: 'users',
|
114
|
+
schema: UserSchema,
|
115
|
+
})
|
116
|
+
|
117
|
+
expect(userTable.sqliteDef.columns.id.nullable).toBe(false)
|
118
|
+
expect(userTable.sqliteDef.columns.name.nullable).toBe(false)
|
119
|
+
expect(userTable.sqliteDef.columns.email.nullable).toBe(true)
|
120
|
+
})
|
121
|
+
|
122
|
+
it('should handle Schema.Int as integer column', () => {
|
123
|
+
const CounterSchema = Schema.Struct({
|
124
|
+
id: Schema.String,
|
125
|
+
count: Schema.Int,
|
126
|
+
})
|
127
|
+
|
128
|
+
const counterTable = State.SQLite.table({
|
129
|
+
name: 'counters',
|
130
|
+
schema: CounterSchema,
|
131
|
+
})
|
132
|
+
|
133
|
+
expect(counterTable.sqliteDef.columns.count.columnType).toBe('integer')
|
134
|
+
})
|
135
|
+
|
136
|
+
it('should work with Schema.Class', () => {
|
137
|
+
class User extends Schema.Class<User>('User')({
|
138
|
+
id: Schema.String,
|
139
|
+
name: Schema.String,
|
140
|
+
email: Schema.optional(Schema.String),
|
141
|
+
age: Schema.Int,
|
142
|
+
}) {}
|
143
|
+
|
144
|
+
const userTable = State.SQLite.table({
|
145
|
+
name: 'users',
|
146
|
+
schema: User,
|
147
|
+
})
|
148
|
+
|
149
|
+
expect(userTable.sqliteDef.name).toBe('users')
|
150
|
+
expect(userTable.sqliteDef.columns).toHaveProperty('id')
|
151
|
+
expect(userTable.sqliteDef.columns).toHaveProperty('name')
|
152
|
+
expect(userTable.sqliteDef.columns).toHaveProperty('email')
|
153
|
+
expect(userTable.sqliteDef.columns).toHaveProperty('age')
|
154
|
+
|
155
|
+
// Check column types
|
156
|
+
expect(userTable.sqliteDef.columns.id.columnType).toBe('text')
|
157
|
+
expect(userTable.sqliteDef.columns.name.columnType).toBe('text')
|
158
|
+
expect(userTable.sqliteDef.columns.email.columnType).toBe('text')
|
159
|
+
expect(userTable.sqliteDef.columns.email.nullable).toBe(true)
|
160
|
+
expect(userTable.sqliteDef.columns.age.columnType).toBe('integer')
|
161
|
+
})
|
162
|
+
|
163
|
+
it('should extract table name from Schema.Class identifier', () => {
|
164
|
+
class TodoItem extends Schema.Class<TodoItem>('TodoItem')({
|
165
|
+
id: Schema.String,
|
166
|
+
text: Schema.String,
|
167
|
+
completed: Schema.Boolean,
|
168
|
+
}) {}
|
169
|
+
|
170
|
+
// Schema.Class doesn't set identifier/title annotations, so we need to provide an explicit name
|
171
|
+
const todosTable = State.SQLite.table({
|
172
|
+
name: 'TodoItem',
|
173
|
+
schema: TodoItem,
|
174
|
+
})
|
175
|
+
|
176
|
+
expect(todosTable.sqliteDef.name).toBe('TodoItem')
|
177
|
+
})
|
178
|
+
|
179
|
+
it('should properly infer types from schema', () => {
|
180
|
+
const UserSchema = Schema.Struct({
|
181
|
+
id: Schema.String,
|
182
|
+
name: Schema.String,
|
183
|
+
age: Schema.Int,
|
184
|
+
active: Schema.Boolean,
|
185
|
+
metadata: Schema.optional(Schema.Record({ key: Schema.String, value: Schema.Unknown })),
|
186
|
+
})
|
187
|
+
|
188
|
+
const userTable = State.SQLite.table({
|
189
|
+
name: 'users',
|
190
|
+
schema: UserSchema,
|
191
|
+
})
|
192
|
+
|
193
|
+
// Test that Type is properly inferred
|
194
|
+
type UserType = typeof userTable.Type
|
195
|
+
const _userTypeCheck: UserType = {
|
196
|
+
id: 'test-id',
|
197
|
+
name: 'John',
|
198
|
+
age: 30,
|
199
|
+
active: true,
|
200
|
+
metadata: { key1: 'value1', key2: 123 },
|
201
|
+
}
|
202
|
+
|
203
|
+
// Test that columns have proper schema types
|
204
|
+
type IdColumn = typeof userTable.sqliteDef.columns.id
|
205
|
+
type NameColumn = typeof userTable.sqliteDef.columns.name
|
206
|
+
type AgeColumn = typeof userTable.sqliteDef.columns.age
|
207
|
+
type ActiveColumn = typeof userTable.sqliteDef.columns.active
|
208
|
+
type MetadataColumn = typeof userTable.sqliteDef.columns.metadata
|
209
|
+
|
210
|
+
// Should derive proper column schema
|
211
|
+
expect((userTable.rowSchema as any).fields.age.toString()).toMatchInlineSnapshot(`"number"`)
|
212
|
+
expect((userTable.rowSchema as any).fields.active.toString()).toMatchInlineSnapshot(`"(number <-> boolean)"`)
|
213
|
+
expect((userTable.rowSchema as any).fields.metadata.toString()).toMatchInlineSnapshot(
|
214
|
+
`"(parseJson <-> { readonly [x: string]: unknown })"`,
|
215
|
+
)
|
216
|
+
|
217
|
+
// These should compile without errors
|
218
|
+
const _idCheck: IdColumn['schema']['Type'] = 'string'
|
219
|
+
const _nameCheck: NameColumn['schema']['Type'] = 'string'
|
220
|
+
const _ageCheck: AgeColumn['schema']['Type'] = 123
|
221
|
+
const _activeCheck: ActiveColumn['schema']['Type'] = true
|
222
|
+
const _metadataCheck: MetadataColumn['schema']['Type'] = { foo: 'bar' }
|
223
|
+
|
224
|
+
// Verify column definitions
|
225
|
+
expect(userTable.sqliteDef.columns.id.columnType).toBe('text')
|
226
|
+
expect(userTable.sqliteDef.columns.name.columnType).toBe('text')
|
227
|
+
expect(userTable.sqliteDef.columns.age.columnType).toBe('integer')
|
228
|
+
expect(userTable.sqliteDef.columns.active.columnType).toBe('integer')
|
229
|
+
expect(userTable.sqliteDef.columns.metadata.columnType).toBe('text')
|
230
|
+
expect(userTable.sqliteDef.columns.metadata.nullable).toBe(true)
|
231
|
+
})
|
232
|
+
})
|
233
|
+
|
234
|
+
describe('getColumnDefForSchema', () => {
|
235
|
+
describe('basic types', () => {
|
236
|
+
it('should map Schema.String to text column', () => {
|
237
|
+
const columnDef = State.SQLite.getColumnDefForSchema(Schema.String)
|
238
|
+
expect(columnDef.columnType).toBe('text')
|
239
|
+
})
|
240
|
+
|
241
|
+
it('should map Schema.Number to real column', () => {
|
242
|
+
const columnDef = State.SQLite.getColumnDefForSchema(Schema.Number)
|
243
|
+
expect(columnDef.columnType).toBe('real')
|
244
|
+
})
|
245
|
+
|
246
|
+
it('should map Schema.Boolean to integer column', () => {
|
247
|
+
const columnDef = State.SQLite.getColumnDefForSchema(Schema.Boolean)
|
248
|
+
expect(columnDef.columnType).toBe('integer')
|
249
|
+
})
|
250
|
+
|
251
|
+
it('should map Schema.Date to text column', () => {
|
252
|
+
const columnDef = State.SQLite.getColumnDefForSchema(Schema.Date)
|
253
|
+
expect(columnDef.columnType).toBe('text')
|
254
|
+
})
|
255
|
+
|
256
|
+
it('should map Schema.BigInt to text column', () => {
|
257
|
+
const columnDef = State.SQLite.getColumnDefForSchema(Schema.BigInt)
|
258
|
+
expect(columnDef.columnType).toBe('text')
|
259
|
+
})
|
260
|
+
})
|
261
|
+
|
262
|
+
describe('refinements', () => {
|
263
|
+
it('should map Schema.Int to integer column', () => {
|
264
|
+
const columnDef = State.SQLite.getColumnDefForSchema(Schema.Int)
|
265
|
+
expect(columnDef.columnType).toBe('integer')
|
266
|
+
})
|
267
|
+
|
268
|
+
it('should map string refinements to text column', () => {
|
269
|
+
const refinements = [
|
270
|
+
{ schema: Schema.NonEmptyString, name: 'NonEmptyString' },
|
271
|
+
{ schema: Schema.Trim, name: 'Trim' },
|
272
|
+
{ schema: Schema.UUID, name: 'UUID' },
|
273
|
+
{ schema: Schema.ULID, name: 'ULID' },
|
274
|
+
{ schema: Schema.String.pipe(Schema.minLength(5)), name: 'minLength' },
|
275
|
+
{ schema: Schema.String.pipe(Schema.pattern(/^[A-Z]+$/)), name: 'pattern' },
|
276
|
+
]
|
277
|
+
|
278
|
+
for (const { schema, name } of refinements) {
|
279
|
+
const columnDef = State.SQLite.getColumnDefForSchema(schema)
|
280
|
+
expect(columnDef.columnType, `${name} should map to text`).toBe('text')
|
281
|
+
}
|
282
|
+
})
|
283
|
+
|
284
|
+
it('should map number refinements to real column', () => {
|
285
|
+
const refinements = [
|
286
|
+
{ schema: Schema.Finite, name: 'Finite' },
|
287
|
+
{ schema: Schema.Number.pipe(Schema.positive()), name: 'positive' },
|
288
|
+
{ schema: Schema.Number.pipe(Schema.between(0, 100)), name: 'between' },
|
289
|
+
]
|
290
|
+
|
291
|
+
for (const { schema, name } of refinements) {
|
292
|
+
const columnDef = State.SQLite.getColumnDefForSchema(schema)
|
293
|
+
expect(columnDef.columnType, `${name} should map to real`).toBe('real')
|
294
|
+
}
|
295
|
+
})
|
296
|
+
})
|
297
|
+
|
298
|
+
describe('literal types', () => {
|
299
|
+
it('should map string literals to text column', () => {
|
300
|
+
const columnDef = State.SQLite.getColumnDefForSchema(Schema.Literal('active'))
|
301
|
+
expect(columnDef.columnType).toBe('text')
|
302
|
+
})
|
303
|
+
|
304
|
+
it('should map number literals to real column', () => {
|
305
|
+
const columnDef = State.SQLite.getColumnDefForSchema(Schema.Literal(42))
|
306
|
+
expect(columnDef.columnType).toBe('real')
|
307
|
+
})
|
308
|
+
|
309
|
+
it('should map boolean literals to integer column', () => {
|
310
|
+
const columnDef = State.SQLite.getColumnDefForSchema(Schema.Literal(true))
|
311
|
+
expect(columnDef.columnType).toBe('integer')
|
312
|
+
})
|
313
|
+
})
|
314
|
+
|
315
|
+
describe('transformations', () => {
|
316
|
+
it('should map transformations based on target type', () => {
|
317
|
+
const StringToNumber = Schema.String.pipe(
|
318
|
+
Schema.transform(Schema.Number, {
|
319
|
+
decode: Number.parseFloat,
|
320
|
+
encode: String,
|
321
|
+
}),
|
322
|
+
)
|
323
|
+
|
324
|
+
const columnDef = State.SQLite.getColumnDefForSchema(StringToNumber)
|
325
|
+
expect(columnDef.columnType).toBe('real') // Based on the target type (Number)
|
326
|
+
})
|
327
|
+
|
328
|
+
it('should handle Date transformations', () => {
|
329
|
+
const columnDef = State.SQLite.getColumnDefForSchema(Schema.Date)
|
330
|
+
expect(columnDef.columnType).toBe('text')
|
331
|
+
})
|
332
|
+
})
|
333
|
+
|
334
|
+
describe('complex types', () => {
|
335
|
+
it('should map structs to json column', () => {
|
336
|
+
const UserSchema = Schema.Struct({
|
337
|
+
name: Schema.String,
|
338
|
+
age: Schema.Number,
|
339
|
+
})
|
340
|
+
|
341
|
+
const columnDef = State.SQLite.getColumnDefForSchema(UserSchema)
|
342
|
+
expect(columnDef.columnType).toBe('text')
|
343
|
+
})
|
344
|
+
|
345
|
+
it('should map arrays to json column', () => {
|
346
|
+
const columnDef = State.SQLite.getColumnDefForSchema(Schema.Array(Schema.String))
|
347
|
+
expect(columnDef.columnType).toBe('text')
|
348
|
+
})
|
349
|
+
|
350
|
+
it('should map records to json column', () => {
|
351
|
+
const columnDef = State.SQLite.getColumnDefForSchema(Schema.Record({ key: Schema.String, value: Schema.Number }))
|
352
|
+
expect(columnDef.columnType).toBe('text')
|
353
|
+
})
|
354
|
+
|
355
|
+
it('should map tuples to json column', () => {
|
356
|
+
const columnDef = State.SQLite.getColumnDefForSchema(Schema.Tuple(Schema.String, Schema.Number))
|
357
|
+
expect(columnDef.columnType).toBe('text')
|
358
|
+
})
|
359
|
+
|
360
|
+
it('should map tagged unions to json column', () => {
|
361
|
+
const ResultSchema = Schema.Union(
|
362
|
+
Schema.Struct({ _tag: Schema.Literal('success'), value: Schema.String }),
|
363
|
+
Schema.Struct({ _tag: Schema.Literal('error'), error: Schema.String }),
|
364
|
+
)
|
365
|
+
|
366
|
+
const columnDef = State.SQLite.getColumnDefForSchema(ResultSchema)
|
367
|
+
expect(columnDef.columnType).toBe('text')
|
368
|
+
})
|
369
|
+
})
|
370
|
+
|
371
|
+
describe('nested schemas', () => {
|
372
|
+
it('should handle deeply nested schemas', () => {
|
373
|
+
const NestedSchema = Schema.Struct({
|
374
|
+
level1: Schema.Struct({
|
375
|
+
level2: Schema.Struct({
|
376
|
+
value: Schema.String,
|
377
|
+
}),
|
378
|
+
}),
|
379
|
+
})
|
380
|
+
|
381
|
+
const columnDef = State.SQLite.getColumnDefForSchema(NestedSchema)
|
382
|
+
expect(columnDef.columnType).toBe('text')
|
383
|
+
})
|
384
|
+
|
385
|
+
it('should handle optional nested schemas', () => {
|
386
|
+
const columnDef = State.SQLite.getColumnDefForSchema(
|
387
|
+
Schema.Union(Schema.Struct({ name: Schema.String }), Schema.Undefined),
|
388
|
+
)
|
389
|
+
expect(columnDef.columnType).toBe('text')
|
390
|
+
})
|
391
|
+
})
|
392
|
+
|
393
|
+
describe('edge cases', () => {
|
394
|
+
it('should default to json column for unhandled types', () => {
|
395
|
+
// Test various edge cases that all result in JSON columns
|
396
|
+
const edgeCases = [
|
397
|
+
{ schema: Schema.Unknown, name: 'Unknown' },
|
398
|
+
{ schema: Schema.Any, name: 'Any' },
|
399
|
+
{ schema: Schema.Null, name: 'Null' },
|
400
|
+
{ schema: Schema.Undefined, name: 'Undefined' },
|
401
|
+
{ schema: Schema.Void, name: 'Void' },
|
402
|
+
]
|
403
|
+
|
404
|
+
for (const { schema, name } of edgeCases) {
|
405
|
+
const columnDef = State.SQLite.getColumnDefForSchema(schema)
|
406
|
+
expect(columnDef.columnType, `${name} should map to text (JSON storage)`).toBe('text')
|
407
|
+
}
|
408
|
+
})
|
409
|
+
|
410
|
+
it('should handle never schema', () => {
|
411
|
+
// Create a schema that should never validate
|
412
|
+
const neverSchema = Schema.String.pipe(Schema.filter(() => false, { message: () => 'Always fails' }))
|
413
|
+
|
414
|
+
const columnDef = State.SQLite.getColumnDefForSchema(neverSchema)
|
415
|
+
expect(columnDef.columnType).toBe('text')
|
416
|
+
})
|
417
|
+
|
418
|
+
it('should handle symbol schema', () => {
|
419
|
+
const columnDef = State.SQLite.getColumnDefForSchema(Schema.Symbol)
|
420
|
+
expect(columnDef.columnType).toBe('text')
|
421
|
+
})
|
422
|
+
})
|
423
|
+
|
424
|
+
describe('custom schemas', () => {
|
425
|
+
it('should handle Schema.extend', () => {
|
426
|
+
const BaseSchema = Schema.Struct({
|
427
|
+
id: Schema.String,
|
428
|
+
createdAt: Schema.Date,
|
429
|
+
})
|
430
|
+
|
431
|
+
const ExtendedSchema = Schema.Struct({
|
432
|
+
...BaseSchema.fields,
|
433
|
+
name: Schema.String,
|
434
|
+
updatedAt: Schema.Date,
|
435
|
+
})
|
436
|
+
|
437
|
+
const columnDef = State.SQLite.getColumnDefForSchema(ExtendedSchema)
|
438
|
+
expect(columnDef.columnType).toBe('text')
|
439
|
+
})
|
440
|
+
|
441
|
+
it('should handle Schema.pick', () => {
|
442
|
+
const UserSchema = Schema.Struct({
|
443
|
+
id: Schema.String,
|
444
|
+
name: Schema.String,
|
445
|
+
email: Schema.String,
|
446
|
+
})
|
447
|
+
|
448
|
+
const PickedSchema = UserSchema.pipe(Schema.pick('id', 'name'))
|
449
|
+
|
450
|
+
const columnDef = State.SQLite.getColumnDefForSchema(PickedSchema)
|
451
|
+
expect(columnDef.columnType).toBe('text')
|
452
|
+
})
|
453
|
+
|
454
|
+
it('should handle Schema.omit', () => {
|
455
|
+
const UserSchema = Schema.Struct({
|
456
|
+
id: Schema.String,
|
457
|
+
name: Schema.String,
|
458
|
+
password: Schema.String,
|
459
|
+
})
|
460
|
+
|
461
|
+
const PublicUserSchema = UserSchema.pipe(Schema.omit('password'))
|
462
|
+
|
463
|
+
const columnDef = State.SQLite.getColumnDefForSchema(PublicUserSchema)
|
464
|
+
expect(columnDef.columnType).toBe('text')
|
465
|
+
})
|
466
|
+
})
|
467
|
+
|
468
|
+
describe('annotations', () => {
|
469
|
+
it('should handle schemas with custom annotations', () => {
|
470
|
+
const AnnotatedString = Schema.String.annotations({ description: 'A special string' })
|
471
|
+
const AnnotatedNumber = Schema.Number.annotations({ min: 0, max: 100 })
|
472
|
+
|
473
|
+
expect(State.SQLite.getColumnDefForSchema(AnnotatedString).columnType).toBe('text')
|
474
|
+
expect(State.SQLite.getColumnDefForSchema(AnnotatedNumber).columnType).toBe('real')
|
475
|
+
})
|
476
|
+
})
|
477
|
+
|
478
|
+
describe('enums and literal unions', () => {
|
479
|
+
it('should handle enums and literal unions as text', () => {
|
480
|
+
const StatusEnum = Schema.Enums({
|
481
|
+
PENDING: 'pending',
|
482
|
+
ACTIVE: 'active',
|
483
|
+
INACTIVE: 'inactive',
|
484
|
+
})
|
485
|
+
|
486
|
+
const StatusUnion = Schema.Union(Schema.Literal('pending'), Schema.Literal('active'), Schema.Literal('inactive'))
|
487
|
+
|
488
|
+
expect(State.SQLite.getColumnDefForSchema(StatusEnum).columnType).toBe('text')
|
489
|
+
expect(State.SQLite.getColumnDefForSchema(StatusUnion).columnType).toBe('text')
|
490
|
+
})
|
491
|
+
})
|
492
|
+
|
493
|
+
describe('binary data', () => {
|
494
|
+
it('should handle Uint8Array as blob column', () => {
|
495
|
+
const columnDef = State.SQLite.getColumnDefForSchema(Schema.Uint8Array)
|
496
|
+
expect(columnDef.columnType).toBe('text') // Stored as JSON
|
497
|
+
})
|
498
|
+
})
|
499
|
+
|
500
|
+
describe('recursive schemas', () => {
|
501
|
+
it('should handle recursive schemas as json', () => {
|
502
|
+
interface TreeNode {
|
503
|
+
readonly value: string
|
504
|
+
readonly children: ReadonlyArray<TreeNode>
|
505
|
+
}
|
506
|
+
const TreeNode: Schema.Schema<TreeNode> = Schema.Struct({
|
507
|
+
value: Schema.String,
|
508
|
+
children: Schema.Array(Schema.suspend(() => TreeNode)),
|
509
|
+
})
|
510
|
+
|
511
|
+
const columnDef = State.SQLite.getColumnDefForSchema(TreeNode)
|
512
|
+
expect(columnDef.columnType).toBe('text') // Complex type stored as JSON
|
513
|
+
})
|
514
|
+
})
|
515
|
+
|
516
|
+
describe('annotations', () => {
|
517
|
+
describe('withColumnType', () => {
|
518
|
+
it('should respect column type annotation for text', () => {
|
519
|
+
const schema = Schema.Number.pipe(withColumnType('text'))
|
520
|
+
const columnDef = State.SQLite.getColumnDefForSchema(schema)
|
521
|
+
expect(columnDef.columnType).toBe('text')
|
522
|
+
})
|
523
|
+
|
524
|
+
it('should respect column type annotation for integer', () => {
|
525
|
+
const schema = Schema.String.pipe(withColumnType('integer'))
|
526
|
+
const columnDef = State.SQLite.getColumnDefForSchema(schema)
|
527
|
+
expect(columnDef.columnType).toBe('integer')
|
528
|
+
})
|
529
|
+
|
530
|
+
it('should respect column type annotation for real', () => {
|
531
|
+
const schema = Schema.Boolean.pipe(withColumnType('real'))
|
532
|
+
const columnDef = State.SQLite.getColumnDefForSchema(schema)
|
533
|
+
expect(columnDef.columnType).toBe('real')
|
534
|
+
})
|
535
|
+
|
536
|
+
it('should respect column type annotation for blob', () => {
|
537
|
+
const schema = Schema.String.pipe(withColumnType('blob'))
|
538
|
+
const columnDef = State.SQLite.getColumnDefForSchema(schema)
|
539
|
+
expect(columnDef.columnType).toBe('blob')
|
540
|
+
})
|
541
|
+
|
542
|
+
it('should override default type mapping', () => {
|
543
|
+
// Number normally maps to real, but we override to text
|
544
|
+
const schema = Schema.Number.pipe(withColumnType('text'))
|
545
|
+
const columnDef = State.SQLite.getColumnDefForSchema(schema)
|
546
|
+
expect(columnDef.columnType).toBe('text')
|
547
|
+
})
|
548
|
+
|
549
|
+
it('should work with dual API', () => {
|
550
|
+
// Test both forms of the dual API
|
551
|
+
const schema1 = withColumnType(Schema.String, 'integer')
|
552
|
+
const schema2 = Schema.String.pipe(withColumnType('integer'))
|
553
|
+
|
554
|
+
const columnDef1 = State.SQLite.getColumnDefForSchema(schema1)
|
555
|
+
const columnDef2 = State.SQLite.getColumnDefForSchema(schema2)
|
556
|
+
|
557
|
+
expect(columnDef1.columnType).toBe('integer')
|
558
|
+
expect(columnDef2.columnType).toBe('integer')
|
559
|
+
})
|
560
|
+
})
|
561
|
+
|
562
|
+
describe('withPrimaryKey', () => {
|
563
|
+
it('should add primary key annotation to schema', () => {
|
564
|
+
const UserSchema = Schema.Struct({
|
565
|
+
id: Schema.String.pipe(withPrimaryKey),
|
566
|
+
name: Schema.String,
|
567
|
+
email: Schema.optional(Schema.String),
|
568
|
+
nullable: Schema.NullOr(Schema.Int),
|
569
|
+
})
|
570
|
+
|
571
|
+
const userTable = State.SQLite.table({
|
572
|
+
name: 'users',
|
573
|
+
schema: UserSchema,
|
574
|
+
})
|
575
|
+
|
576
|
+
expect(userTable.sqliteDef.columns.id.primaryKey).toBe(true)
|
577
|
+
expect(userTable.sqliteDef.columns.id.nullable).toBe(false)
|
578
|
+
expect(userTable.sqliteDef.columns.name.primaryKey).toBe(false)
|
579
|
+
expect(userTable.sqliteDef.columns.email.primaryKey).toBe(false)
|
580
|
+
expect(userTable.sqliteDef.columns.email.nullable).toBe(true)
|
581
|
+
expect(userTable.sqliteDef.columns.nullable.primaryKey).toBe(false)
|
582
|
+
expect(userTable.sqliteDef.columns.nullable.nullable).toBe(true)
|
583
|
+
})
|
584
|
+
|
585
|
+
it('should throw when primary key is used with optional schema', () => {
|
586
|
+
// Note: Schema.optional returns a property signature, not a schema, so we can't pipe it
|
587
|
+
// Instead, we use Schema.Union to create an optional schema that can be piped
|
588
|
+
const optionalString = Schema.Union(Schema.String, Schema.Undefined)
|
589
|
+
const UserSchema = Schema.Struct({
|
590
|
+
id: optionalString.pipe(withPrimaryKey),
|
591
|
+
name: Schema.String,
|
592
|
+
})
|
593
|
+
|
594
|
+
expect(() =>
|
595
|
+
State.SQLite.table({
|
596
|
+
name: 'users',
|
597
|
+
schema: UserSchema,
|
598
|
+
}),
|
599
|
+
).toThrow('Primary key columns cannot be nullable')
|
600
|
+
})
|
601
|
+
|
602
|
+
it('should throw when primary key is used with NullOr schema', () => {
|
603
|
+
const UserSchema = Schema.Struct({
|
604
|
+
id: Schema.NullOr(Schema.String).pipe(withPrimaryKey),
|
605
|
+
name: Schema.String,
|
606
|
+
})
|
607
|
+
|
608
|
+
expect(() =>
|
609
|
+
State.SQLite.table({
|
610
|
+
name: 'users',
|
611
|
+
schema: UserSchema,
|
612
|
+
}),
|
613
|
+
).toThrow('Primary key columns cannot be nullable')
|
614
|
+
})
|
615
|
+
|
616
|
+
it('should work with column type annotation', () => {
|
617
|
+
const UserSchema = Schema.Struct({
|
618
|
+
id: Schema.Number.pipe(withColumnType('integer')).pipe(withPrimaryKey),
|
619
|
+
name: Schema.String,
|
620
|
+
})
|
621
|
+
|
622
|
+
const userTable = State.SQLite.table({
|
623
|
+
name: 'users',
|
624
|
+
schema: UserSchema,
|
625
|
+
})
|
626
|
+
|
627
|
+
expect(userTable.sqliteDef.columns.id.columnType).toBe('integer')
|
628
|
+
expect(userTable.sqliteDef.columns.id.primaryKey).toBe(true)
|
629
|
+
})
|
630
|
+
|
631
|
+
it('should work with Schema.Int and primary key', () => {
|
632
|
+
const UserSchema = Schema.Struct({
|
633
|
+
id: Schema.Int.pipe(withPrimaryKey),
|
634
|
+
name: Schema.String,
|
635
|
+
})
|
636
|
+
|
637
|
+
const userTable = State.SQLite.table({
|
638
|
+
name: 'users',
|
639
|
+
schema: UserSchema,
|
640
|
+
})
|
641
|
+
|
642
|
+
expect(userTable.sqliteDef.columns.id.columnType).toBe('integer')
|
643
|
+
expect(userTable.sqliteDef.columns.id.primaryKey).toBe(true)
|
644
|
+
})
|
645
|
+
})
|
646
|
+
|
647
|
+
describe('withAutoIncrement', () => {
|
648
|
+
it('should add autoIncrement annotation to schema', () => {
|
649
|
+
const UserSchema = Schema.Struct({
|
650
|
+
id: Schema.Int.pipe(withPrimaryKey).pipe(withAutoIncrement),
|
651
|
+
name: Schema.String,
|
652
|
+
})
|
653
|
+
const userTable = State.SQLite.table({
|
654
|
+
name: 'users',
|
655
|
+
schema: UserSchema,
|
656
|
+
})
|
657
|
+
expect(userTable.sqliteDef.columns.id.autoIncrement).toBe(true)
|
658
|
+
expect(userTable.sqliteDef.columns.id.primaryKey).toBe(true)
|
659
|
+
expect(userTable.sqliteDef.columns.id.columnType).toBe('integer')
|
660
|
+
})
|
661
|
+
})
|
662
|
+
|
663
|
+
describe('withDefault', () => {
|
664
|
+
it('should add default value annotation to schema', () => {
|
665
|
+
const UserSchema = Schema.Struct({
|
666
|
+
id: Schema.String,
|
667
|
+
active: Schema.Boolean.pipe(withDefault(true)),
|
668
|
+
createdAt: Schema.String.pipe(withDefault('CURRENT_TIMESTAMP')),
|
669
|
+
})
|
670
|
+
const userTable = State.SQLite.table({
|
671
|
+
name: 'users',
|
672
|
+
schema: UserSchema,
|
673
|
+
})
|
674
|
+
expect(userTable.sqliteDef.columns.active.default._tag).toBe('Some')
|
675
|
+
expect(
|
676
|
+
userTable.sqliteDef.columns.active.default._tag === 'Some' &&
|
677
|
+
userTable.sqliteDef.columns.active.default.value,
|
678
|
+
).toBe(true)
|
679
|
+
expect(userTable.sqliteDef.columns.createdAt.default._tag).toBe('Some')
|
680
|
+
expect(
|
681
|
+
userTable.sqliteDef.columns.createdAt.default._tag === 'Some' &&
|
682
|
+
userTable.sqliteDef.columns.createdAt.default.value,
|
683
|
+
).toBe('CURRENT_TIMESTAMP')
|
684
|
+
})
|
685
|
+
|
686
|
+
it('should work with dual API', () => {
|
687
|
+
const schema1 = withDefault(Schema.Int, 0)
|
688
|
+
const schema2 = Schema.Int.pipe(withDefault(0))
|
689
|
+
const UserSchema1 = Schema.Struct({ count: schema1 })
|
690
|
+
const UserSchema2 = Schema.Struct({ count: schema2 })
|
691
|
+
const table1 = State.SQLite.table({ name: 't1', schema: UserSchema1 })
|
692
|
+
const table2 = State.SQLite.table({ name: 't2', schema: UserSchema2 })
|
693
|
+
expect(table1.sqliteDef.columns.count.default._tag).toBe('Some')
|
694
|
+
expect(
|
695
|
+
table1.sqliteDef.columns.count.default._tag === 'Some' && table1.sqliteDef.columns.count.default.value,
|
696
|
+
).toBe(0)
|
697
|
+
expect(table2.sqliteDef.columns.count.default._tag).toBe('Some')
|
698
|
+
expect(
|
699
|
+
table2.sqliteDef.columns.count.default._tag === 'Some' && table2.sqliteDef.columns.count.default.value,
|
700
|
+
).toBe(0)
|
701
|
+
})
|
702
|
+
})
|
703
|
+
|
704
|
+
describe('withUnique', () => {
|
705
|
+
it('should create unique index for column with unique annotation', () => {
|
706
|
+
const UserSchema = Schema.Struct({
|
707
|
+
id: Schema.String,
|
708
|
+
email: Schema.String.pipe(withUnique),
|
709
|
+
username: Schema.String.pipe(withUnique),
|
710
|
+
})
|
711
|
+
const userTable = State.SQLite.table({
|
712
|
+
name: 'users',
|
713
|
+
schema: UserSchema,
|
714
|
+
})
|
715
|
+
|
716
|
+
// Check that unique indexes were created
|
717
|
+
const uniqueIndexes = userTable.sqliteDef.indexes?.filter((idx) => idx.isUnique) || []
|
718
|
+
expect(uniqueIndexes).toHaveLength(2)
|
719
|
+
expect(
|
720
|
+
uniqueIndexes.some((idx) => idx.name === 'idx_users_email_unique' && idx.columns.includes('email')),
|
721
|
+
).toBe(true)
|
722
|
+
expect(
|
723
|
+
uniqueIndexes.some((idx) => idx.name === 'idx_users_username_unique' && idx.columns.includes('username')),
|
724
|
+
).toBe(true)
|
725
|
+
})
|
726
|
+
|
727
|
+
it('should combine unique indexes with user-provided indexes', () => {
|
728
|
+
const UserSchema = Schema.Struct({
|
729
|
+
id: Schema.String,
|
730
|
+
email: Schema.String.pipe(withUnique),
|
731
|
+
})
|
732
|
+
const userTable = State.SQLite.table({
|
733
|
+
name: 'users',
|
734
|
+
schema: UserSchema,
|
735
|
+
indexes: [{ name: 'idx_custom', columns: ['id', 'email'] }],
|
736
|
+
})
|
737
|
+
|
738
|
+
// Should have both custom index and unique index
|
739
|
+
expect(userTable.sqliteDef.indexes).toHaveLength(2)
|
740
|
+
expect(userTable.sqliteDef.indexes?.some((idx) => idx.name === 'idx_custom')).toBe(true)
|
741
|
+
expect(userTable.sqliteDef.indexes?.some((idx) => idx.name === 'idx_users_email_unique')).toBe(true)
|
742
|
+
})
|
743
|
+
})
|
744
|
+
|
745
|
+
describe('combined annotations', () => {
|
746
|
+
it('should work with multiple annotations', () => {
|
747
|
+
const schema = Schema.Uint8ArrayFromBase64.pipe(withColumnType('blob')).pipe(withPrimaryKey)
|
748
|
+
|
749
|
+
const UserSchema = Schema.Struct({
|
750
|
+
id: schema,
|
751
|
+
name: Schema.String,
|
752
|
+
})
|
753
|
+
|
754
|
+
const userTable = State.SQLite.table({
|
755
|
+
name: 'users',
|
756
|
+
schema: UserSchema,
|
757
|
+
})
|
758
|
+
|
759
|
+
expect(userTable.sqliteDef.columns.id.columnType).toBe('blob')
|
760
|
+
expect(userTable.sqliteDef.columns.id.primaryKey).toBe(true)
|
761
|
+
})
|
762
|
+
|
763
|
+
it('should combine all annotations', () => {
|
764
|
+
const UserSchema = Schema.Struct({
|
765
|
+
id: Schema.Int.pipe(withPrimaryKey).pipe(withAutoIncrement),
|
766
|
+
email: Schema.String.pipe(withUnique),
|
767
|
+
status: Schema.String.pipe(withDefault('active')),
|
768
|
+
metadata: Schema.Unknown.pipe(withColumnType('text')),
|
769
|
+
})
|
770
|
+
const userTable = State.SQLite.table({
|
771
|
+
name: 'users',
|
772
|
+
schema: UserSchema,
|
773
|
+
})
|
774
|
+
|
775
|
+
// Check id column
|
776
|
+
expect(userTable.sqliteDef.columns.id.primaryKey).toBe(true)
|
777
|
+
expect(userTable.sqliteDef.columns.id.autoIncrement).toBe(true)
|
778
|
+
expect(userTable.sqliteDef.columns.id.columnType).toBe('integer')
|
779
|
+
|
780
|
+
// Check email column and unique index
|
781
|
+
expect(userTable.sqliteDef.columns.email.columnType).toBe('text')
|
782
|
+
expect(userTable.sqliteDef.indexes?.some((idx) => idx.name === 'idx_users_email_unique' && idx.isUnique)).toBe(
|
783
|
+
true,
|
784
|
+
)
|
785
|
+
|
786
|
+
// Check status column
|
787
|
+
expect(userTable.sqliteDef.columns.status.default._tag).toBe('Some')
|
788
|
+
expect(
|
789
|
+
userTable.sqliteDef.columns.status.default._tag === 'Some' &&
|
790
|
+
userTable.sqliteDef.columns.status.default.value,
|
791
|
+
).toBe('active')
|
792
|
+
|
793
|
+
// Check metadata column
|
794
|
+
expect(userTable.sqliteDef.columns.metadata.columnType).toBe('text')
|
795
|
+
})
|
796
|
+
})
|
797
|
+
})
|
798
|
+
})
|