@livestore/common 0.4.0-dev.16 → 0.4.0-dev.18

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.
@@ -273,11 +273,27 @@ describe('getColumnDefForSchema', () => {
273
273
  INACTIVE: 'inactive',
274
274
  })
275
275
 
276
- const StatusUnion = Schema.Union(Schema.Literal('pending'), Schema.Literal('active'), Schema.Literal('inactive'))
276
+ const StatusUnion = Schema.Literal('pending', 'active', 'inactive')
277
277
 
278
278
  expect(State.SQLite.getColumnDefForSchema(StatusEnum).columnType).toBe('text')
279
279
  expect(State.SQLite.getColumnDefForSchema(StatusUnion).columnType).toBe('text')
280
280
  })
281
+
282
+ it('should handle unions of numeric literals as integer column', () => {
283
+ const IntervalSchema = Schema.Literal(1, 5, 15, 30)
284
+
285
+ const columnDef = State.SQLite.getColumnDefForSchema(IntervalSchema)
286
+
287
+ expect(columnDef.columnType).toBe('integer')
288
+ })
289
+
290
+ it('should handle unions of non-integer numeric literals as real column', () => {
291
+ const PercentSchema = Schema.Literal(0.1, 0.2, 0.25)
292
+
293
+ const columnDef = State.SQLite.getColumnDefForSchema(PercentSchema)
294
+
295
+ expect(columnDef.columnType).toBe('real')
296
+ })
281
297
  })
282
298
 
283
299
  describe('binary data', () => {
@@ -552,7 +568,7 @@ describe('getColumnDefForSchema', () => {
552
568
 
553
569
  it('should work with column type annotation', () => {
554
570
  const UserSchema = Schema.Struct({
555
- id: Schema.Number.pipe(withColumnType('integer')).pipe(withPrimaryKey),
571
+ id: Schema.Number.pipe(withColumnType('integer'), withPrimaryKey),
556
572
  name: Schema.String,
557
573
  })
558
574
 
@@ -584,7 +600,7 @@ describe('getColumnDefForSchema', () => {
584
600
  describe('withAutoIncrement', () => {
585
601
  it('should add autoIncrement annotation to schema', () => {
586
602
  const UserSchema = Schema.Struct({
587
- id: Schema.Int.pipe(withPrimaryKey).pipe(withAutoIncrement),
603
+ id: Schema.Int.pipe(withPrimaryKey, withAutoIncrement),
588
604
  name: Schema.String,
589
605
  })
590
606
  const userTable = State.SQLite.table({
@@ -704,7 +720,7 @@ describe('getColumnDefForSchema', () => {
704
720
 
705
721
  describe('combined annotations', () => {
706
722
  it('should work with multiple annotations', () => {
707
- const schema = Schema.Uint8ArrayFromBase64.pipe(withColumnType('blob')).pipe(withPrimaryKey)
723
+ const schema = Schema.Uint8ArrayFromBase64.pipe(withColumnType('blob'), withPrimaryKey)
708
724
 
709
725
  const UserSchema = Schema.Struct({
710
726
  id: schema,
@@ -722,7 +738,7 @@ describe('getColumnDefForSchema', () => {
722
738
 
723
739
  it('should combine all annotations', () => {
724
740
  const UserSchema = Schema.Struct({
725
- id: Schema.Int.pipe(withPrimaryKey).pipe(withAutoIncrement),
741
+ id: Schema.Int.pipe(withPrimaryKey, withAutoIncrement),
726
742
  email: Schema.String.pipe(withUnique),
727
743
  status: Schema.String.pipe(withDefault('active')),
728
744
  metadata: Schema.Unknown.pipe(withColumnType('text')),
@@ -179,33 +179,12 @@ const getColumnForSchema = (schema: Schema.Schema.AnyNoContext, nullable = false
179
179
  return SqliteDsl.real({ schema: coreSchema, nullable })
180
180
  }
181
181
 
182
- // Literals based on their type
183
- if (SchemaAST.isLiteral(coreAst)) {
184
- const value = coreAst.literal
185
- if (typeof value === 'boolean') return SqliteDsl.boolean({ nullable })
186
- }
187
-
188
- if (isLiteralUnionOf(coreAst, (value): value is string => typeof value === 'string')) {
189
- return SqliteDsl.text({ schema: coreSchema, nullable })
190
- }
182
+ const literalColumn = getLiteralColumnDefinition(encodedAst, coreSchema, nullable, coreAst)
183
+ if (literalColumn) return literalColumn
191
184
 
192
- // Literals based on their encoded type
193
- if (SchemaAST.isLiteral(encodedAst)) {
194
- const value = encodedAst.literal
195
- if (typeof value === 'string') return SqliteDsl.text({ schema: coreSchema, nullable })
196
- if (typeof value === 'number') {
197
- // Check if the original schema is Int
198
- const id = SchemaAST.getIdentifierAnnotation(coreAst).pipe(Option.getOrElse(() => ''))
199
- if (id === 'Int') {
200
- return SqliteDsl.integer({ schema: coreSchema, nullable })
201
- }
202
- return SqliteDsl.real({ schema: coreSchema, nullable })
203
- }
204
- }
205
-
206
- if (isLiteralUnionOf(encodedAst, (value): value is string => typeof value === 'string')) {
207
- return SqliteDsl.text({ schema: coreSchema, nullable })
208
- }
185
+ // Fallback to checking the original AST in case the encoded schema differs
186
+ const coreLiteralColumn = getLiteralColumnDefinition(coreAst, coreSchema, nullable, coreAst)
187
+ if (coreLiteralColumn) return coreLiteralColumn
209
188
 
210
189
  // Everything else needs JSON encoding
211
190
  return SqliteDsl.json({ schema: coreSchema, nullable })
@@ -230,10 +209,57 @@ const stripNullable = (ast: SchemaAST.AST): SchemaAST.AST => {
230
209
  return SchemaAST.Union.make(coreTypes, ast.annotations)
231
210
  }
232
211
 
233
- const isLiteralUnionOf = <T extends SchemaAST.LiteralValue>(
212
+ const getLiteralColumnDefinition = (
234
213
  ast: SchemaAST.AST,
235
- predicate: (value: SchemaAST.LiteralValue) => value is T,
236
- ): ast is SchemaAST.Union & { types: ReadonlyArray<SchemaAST.Literal & { literal: T }> } =>
237
- SchemaAST.isUnion(ast) &&
238
- ast.types.length > 0 &&
239
- ast.types.every((type) => SchemaAST.isLiteral(type) && predicate(type.literal))
214
+ schema: Schema.Schema.AnyNoContext,
215
+ nullable: boolean,
216
+ sourceAst: SchemaAST.AST,
217
+ ): SqliteDsl.ColumnDefinition.Any | null => {
218
+ const literalValues = extractLiteralValues(ast)
219
+ if (!literalValues) return null
220
+
221
+ const literalType = getLiteralValueType(literalValues)
222
+ switch (literalType) {
223
+ case 'string':
224
+ return SqliteDsl.text({ schema, nullable })
225
+ case 'number': {
226
+ const id = SchemaAST.getIdentifierAnnotation(sourceAst).pipe(Option.getOrElse(() => ''))
227
+ if (id === 'Int' || id === 'DateFromNumber') {
228
+ return SqliteDsl.integer({ schema, nullable })
229
+ }
230
+
231
+ const useIntegerColumn =
232
+ literalValues.length > 1 && literalValues.every((value) => typeof value === 'number' && Number.isInteger(value))
233
+
234
+ return useIntegerColumn ? SqliteDsl.integer({ schema, nullable }) : SqliteDsl.real({ schema, nullable })
235
+ }
236
+ case 'boolean':
237
+ return SqliteDsl.boolean({ nullable })
238
+ case 'bigint':
239
+ return SqliteDsl.integer({ schema, nullable })
240
+ default:
241
+ return null
242
+ }
243
+ }
244
+
245
+ const extractLiteralValues = (ast: SchemaAST.AST): ReadonlyArray<SchemaAST.LiteralValue> | null => {
246
+ if (SchemaAST.isLiteral(ast)) return [ast.literal]
247
+
248
+ if (SchemaAST.isUnion(ast) && ast.types.length > 0 && ast.types.every((type) => SchemaAST.isLiteral(type))) {
249
+ return ast.types.map((type) => (type as SchemaAST.Literal).literal)
250
+ }
251
+
252
+ return null
253
+ }
254
+
255
+ const getLiteralValueType = (
256
+ literals: ReadonlyArray<SchemaAST.LiteralValue>,
257
+ ): 'string' | 'number' | 'boolean' | 'bigint' | null => {
258
+ const literalTypes = new Set(literals.map((value) => (value === null ? 'null' : typeof value)))
259
+ if (literalTypes.size !== 1) return null
260
+
261
+ const [literalType] = literalTypes
262
+ return literalType === 'string' || literalType === 'number' || literalType === 'boolean' || literalType === 'bigint'
263
+ ? literalType
264
+ : null
265
+ }
@@ -35,7 +35,6 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
35
35
  select: { columns },
36
36
  }) as any
37
37
  },
38
- // biome-ignore lint/complexity/useArrowFunction: prefer function over arrow function for this case
39
38
  where: function () {
40
39
  if (ast._tag === 'InsertQuery') return invalidQueryBuilder('Cannot use where with insert')
41
40
  if (ast._tag === 'RowQuery') return invalidQueryBuilder('Cannot use where with row')
package/src/version.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // import packageJson from '../package.json' with { type: 'json' }
3
3
  // export const liveStoreVersion = packageJson.version
4
4
 
5
- export const liveStoreVersion = '0.4.0-dev.16' as const
5
+ export const liveStoreVersion = '0.4.0-dev.18' as const
6
6
 
7
7
  /**
8
8
  * CRITICAL: Increment this version whenever you modify client-side EVENTLOG table schemas.