@livestore/common 0.4.0-dev.1 → 0.4.0-dev.2
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/devtools/devtools-messages-client-session.d.ts +21 -21
- package/dist/devtools/devtools-messages-common.d.ts +6 -6
- package/dist/devtools/devtools-messages-leader.d.ts +24 -24
- package/dist/schema/state/sqlite/column-def.d.ts +6 -2
- package/dist/schema/state/sqlite/column-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/column-def.js +122 -185
- package/dist/schema/state/sqlite/column-def.js.map +1 -1
- package/dist/schema/state/sqlite/column-def.test.js +116 -73
- package/dist/schema/state/sqlite/column-def.test.js.map +1 -1
- package/dist/schema/state/sqlite/table-def.test.js +7 -2
- package/dist/schema/state/sqlite/table-def.test.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
- package/src/schema/state/sqlite/column-def.test.ts +150 -93
- package/src/schema/state/sqlite/column-def.ts +128 -203
- package/src/schema/state/sqlite/table-def.test.ts +11 -2
- package/src/version.ts +1 -1
@@ -4,239 +4,176 @@ import { AutoIncrement, ColumnType, Default, PrimaryKeyId, Unique } from "./colu
|
|
4
4
|
import { SqliteDsl } from "./db-schema/mod.js";
|
5
5
|
/**
|
6
6
|
* Maps a schema to a SQLite column definition, respecting column annotations.
|
7
|
+
*
|
8
|
+
* Note: When used with schema-based table definitions, optional fields (| undefined)
|
9
|
+
* are transformed to nullable fields (| null) to match SQLite's NULL semantics.
|
10
|
+
* Fields with both null and undefined will emit a warning as this is a lossy conversion.
|
7
11
|
*/
|
8
|
-
export const getColumnDefForSchema = (schema, propertySignature) => {
|
12
|
+
export const getColumnDefForSchema = (schema, propertySignature, forceNullable = false) => {
|
9
13
|
const ast = schema.ast;
|
10
|
-
//
|
14
|
+
// Extract annotations
|
11
15
|
const getAnnotation = (annotationId) => propertySignature
|
12
16
|
? hasPropertyAnnotation(propertySignature, annotationId)
|
13
17
|
: SchemaAST.getAnnotation(annotationId)(ast);
|
14
|
-
const
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
18
|
+
const columnType = SchemaAST.getAnnotation(ColumnType)(ast);
|
19
|
+
// Check if schema has null (e.g., Schema.NullOr) or undefined or if it's forced nullable (optional field)
|
20
|
+
const isNullable = forceNullable || hasNull(ast) || hasUndefined(ast);
|
21
|
+
// Get base column definition with nullable flag
|
22
|
+
const baseColumn = Option.isSome(columnType)
|
23
|
+
? getColumnForType(columnType.value, isNullable)
|
24
|
+
: getColumnForSchema(schema, isNullable);
|
25
|
+
// Apply annotations
|
26
|
+
const primaryKey = getAnnotation(PrimaryKeyId).pipe(Option.getOrElse(() => false));
|
27
|
+
const autoIncrement = getAnnotation(AutoIncrement).pipe(Option.getOrElse(() => false));
|
28
|
+
const defaultValue = getAnnotation(Default);
|
29
|
+
return {
|
30
|
+
...baseColumn,
|
31
|
+
...(primaryKey && { primaryKey: true }),
|
32
|
+
...(autoIncrement && { autoIncrement: true }),
|
33
|
+
...(Option.isSome(defaultValue) && { default: Option.some(defaultValue.value) }),
|
19
34
|
};
|
20
|
-
// 2. Resolve the core type and nullable info
|
21
|
-
const typeInfo = resolveType(ast);
|
22
|
-
// 3. Create column definition based on resolved type
|
23
|
-
let columnDef;
|
24
|
-
// Custom column type overrides everything
|
25
|
-
if (Option.isSome(annotations.columnType)) {
|
26
|
-
columnDef = createColumnFromType(annotations.columnType.value, typeInfo.coreType);
|
27
|
-
}
|
28
|
-
// Lossy case: both null and undefined need JSON
|
29
|
-
else if (typeInfo.hasNull && typeInfo.hasUndefined) {
|
30
|
-
columnDef = {
|
31
|
-
...SqliteDsl.text(),
|
32
|
-
nullable: true,
|
33
|
-
schema: Schema.parseJson(schema),
|
34
|
-
};
|
35
|
-
}
|
36
|
-
// Regular nullable/optional case
|
37
|
-
else if (typeInfo.hasNull || typeInfo.hasUndefined) {
|
38
|
-
const baseColumnDef = createColumnFromAST(typeInfo.coreType, Schema.make(typeInfo.coreType));
|
39
|
-
const isComplexOptional = typeInfo.hasUndefined && !isPrimitiveAST(typeInfo.coreType);
|
40
|
-
columnDef = {
|
41
|
-
...baseColumnDef,
|
42
|
-
nullable: true,
|
43
|
-
schema: isComplexOptional ? Schema.parseJson(schema) : schema,
|
44
|
-
};
|
45
|
-
}
|
46
|
-
// Non-nullable type
|
47
|
-
else {
|
48
|
-
columnDef = createColumnFromAST(ast, schema);
|
49
|
-
}
|
50
|
-
// 4. Apply annotations
|
51
|
-
const result = { ...columnDef };
|
52
|
-
if (annotations.primaryKey)
|
53
|
-
result.primaryKey = true;
|
54
|
-
if (annotations.autoIncrement)
|
55
|
-
result.autoIncrement = true;
|
56
|
-
if (Option.isSome(annotations.defaultValue)) {
|
57
|
-
result.default = Option.some(annotations.defaultValue.value);
|
58
|
-
}
|
59
|
-
return result;
|
60
35
|
};
|
61
|
-
/**
|
62
|
-
* Checks if a property signature has a specific annotation, checking both
|
63
|
-
* the property signature itself and its type AST.
|
64
|
-
*/
|
65
36
|
const hasPropertyAnnotation = (propertySignature, annotationId) => {
|
66
|
-
// When using Schema.optional(Schema.String).pipe(withPrimaryKey) in a struct,
|
67
|
-
// the annotation ends up on a PropertySignatureDeclaration, not the Union type
|
68
|
-
// Check if this is a PropertySignatureDeclaration with annotations
|
69
37
|
if ('annotations' in propertySignature && propertySignature.annotations) {
|
70
38
|
const annotation = SchemaAST.getAnnotation(annotationId)(propertySignature);
|
71
|
-
if (Option.isSome(annotation))
|
39
|
+
if (Option.isSome(annotation))
|
72
40
|
return annotation;
|
73
|
-
}
|
74
41
|
}
|
75
|
-
// Otherwise check the type AST
|
76
42
|
return SchemaAST.getAnnotation(annotationId)(propertySignature.type);
|
77
43
|
};
|
78
44
|
/**
|
79
45
|
* Maps schema property signatures to SQLite column definitions.
|
80
|
-
*
|
46
|
+
* Optional fields (| undefined) become nullable columns (| null).
|
81
47
|
*/
|
82
48
|
export const schemaFieldsToColumns = (propertySignatures) => {
|
83
49
|
const columns = {};
|
84
50
|
const uniqueColumns = [];
|
85
51
|
for (const prop of propertySignatures) {
|
86
|
-
if (typeof prop.name
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
if (hasUnique) {
|
95
|
-
uniqueColumns.push(prop.name);
|
52
|
+
if (typeof prop.name !== 'string')
|
53
|
+
continue;
|
54
|
+
const fieldSchema = Schema.make(prop.type);
|
55
|
+
// Warn about lossy conversion for fields with both null and undefined
|
56
|
+
if (prop.isOptional) {
|
57
|
+
const { hasNull, hasUndefined } = checkNullUndefined(fieldSchema.ast);
|
58
|
+
if (hasNull && hasUndefined) {
|
59
|
+
console.warn(`Field '${prop.name}' has both null and undefined - treating | undefined as | null`);
|
96
60
|
}
|
97
61
|
}
|
62
|
+
// Get column definition - pass nullable flag for optional fields
|
63
|
+
const columnDef = getColumnDefForSchema(fieldSchema, prop, prop.isOptional);
|
64
|
+
// Check for primary key and unique annotations
|
65
|
+
const hasPrimaryKey = hasPropertyAnnotation(prop, PrimaryKeyId).pipe(Option.getOrElse(() => false));
|
66
|
+
const hasUnique = hasPropertyAnnotation(prop, Unique).pipe(Option.getOrElse(() => false));
|
67
|
+
// Build final column
|
68
|
+
columns[prop.name] = {
|
69
|
+
...columnDef,
|
70
|
+
...(hasPrimaryKey && { primaryKey: true }),
|
71
|
+
};
|
72
|
+
// Validate primary key + nullable
|
73
|
+
const column = columns[prop.name];
|
74
|
+
if (column?.primaryKey && column.nullable) {
|
75
|
+
throw new Error('Primary key columns cannot be nullable');
|
76
|
+
}
|
77
|
+
if (hasUnique)
|
78
|
+
uniqueColumns.push(prop.name);
|
98
79
|
}
|
99
80
|
return { columns, uniqueColumns };
|
100
81
|
};
|
101
|
-
|
102
|
-
* Converts a schema field and its property signature to a SQLite column definition.
|
103
|
-
*/
|
104
|
-
const schemaFieldToColumn = (fieldSchema, propertySignature, forceHasPrimaryKey) => {
|
105
|
-
// Determine column type based on schema type
|
106
|
-
const columnDef = getColumnDefForSchema(fieldSchema, propertySignature);
|
107
|
-
// Create a new object with appropriate properties
|
108
|
-
const result = {
|
109
|
-
columnType: columnDef.columnType,
|
110
|
-
schema: columnDef.schema,
|
111
|
-
default: columnDef.default,
|
112
|
-
nullable: columnDef.nullable,
|
113
|
-
primaryKey: columnDef.primaryKey,
|
114
|
-
autoIncrement: columnDef.autoIncrement,
|
115
|
-
};
|
116
|
-
// Set primaryKey property explicitly
|
117
|
-
if (forceHasPrimaryKey || columnDef.primaryKey) {
|
118
|
-
result.primaryKey = true;
|
119
|
-
}
|
120
|
-
else {
|
121
|
-
result.primaryKey = false;
|
122
|
-
}
|
123
|
-
// Check for invalid primary key + nullable combination
|
124
|
-
if (result.primaryKey && (propertySignature.isOptional || columnDef.nullable)) {
|
125
|
-
return shouldNeverHappen(`Primary key columns cannot be nullable. Found nullable primary key for column. ` +
|
126
|
-
`Either remove the primary key annotation or use a non-nullable schema.`);
|
127
|
-
}
|
128
|
-
// Set nullable property explicitly
|
129
|
-
if (propertySignature.isOptional) {
|
130
|
-
result.nullable = true;
|
131
|
-
}
|
132
|
-
else if (columnDef.nullable) {
|
133
|
-
result.nullable = true;
|
134
|
-
}
|
135
|
-
else {
|
136
|
-
result.nullable = false;
|
137
|
-
}
|
138
|
-
// Only add autoIncrement if it's true
|
139
|
-
if (columnDef.autoIncrement) {
|
140
|
-
result.autoIncrement = true;
|
141
|
-
}
|
142
|
-
return result;
|
143
|
-
};
|
144
|
-
/**
|
145
|
-
* Resolves type information from an AST, unwrapping unions and tracking nullability.
|
146
|
-
*/
|
147
|
-
const resolveType = (ast) => {
|
148
|
-
if (!SchemaAST.isUnion(ast)) {
|
149
|
-
return { coreType: ast, hasNull: false, hasUndefined: false };
|
150
|
-
}
|
82
|
+
const checkNullUndefined = (ast) => {
|
151
83
|
let hasNull = false;
|
152
84
|
let hasUndefined = false;
|
153
|
-
let coreType;
|
154
85
|
const visit = (type) => {
|
155
|
-
if (SchemaAST.isUndefinedKeyword(type))
|
86
|
+
if (SchemaAST.isUndefinedKeyword(type))
|
156
87
|
hasUndefined = true;
|
157
|
-
|
158
|
-
else if (SchemaAST.isLiteral(type) && type.literal === null) {
|
88
|
+
else if (SchemaAST.isLiteral(type) && type.literal === null)
|
159
89
|
hasNull = true;
|
160
|
-
|
161
|
-
else if (SchemaAST.isUnion(type)) {
|
90
|
+
else if (SchemaAST.isUnion(type))
|
162
91
|
type.types.forEach(visit);
|
163
|
-
}
|
164
|
-
else if (!coreType) {
|
165
|
-
coreType = type;
|
166
|
-
}
|
167
92
|
};
|
168
|
-
ast
|
169
|
-
return {
|
93
|
+
visit(ast);
|
94
|
+
return { hasNull, hasUndefined };
|
170
95
|
};
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
if (SchemaAST.isRefinement(ast)) {
|
177
|
-
// Special case for Schema.Int
|
178
|
-
const identifier = SchemaAST.getIdentifierAnnotation(ast).pipe(Option.getOrElse(() => ''));
|
179
|
-
if (identifier === 'Int')
|
180
|
-
return SqliteDsl.integer();
|
181
|
-
return createColumnFromAST(ast.from, Schema.make(ast.from));
|
182
|
-
}
|
183
|
-
if (SchemaAST.isTransformation(ast)) {
|
184
|
-
return createColumnFromAST(ast.to, Schema.make(ast.to));
|
96
|
+
const hasNull = (ast) => {
|
97
|
+
if (SchemaAST.isLiteral(ast) && ast.literal === null)
|
98
|
+
return true;
|
99
|
+
if (SchemaAST.isUnion(ast)) {
|
100
|
+
return ast.types.some((type) => hasNull(type));
|
185
101
|
}
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
if (SchemaAST.
|
190
|
-
return
|
191
|
-
if (SchemaAST.
|
192
|
-
return
|
193
|
-
// Literals
|
194
|
-
if (SchemaAST.isLiteral(ast)) {
|
195
|
-
const value = ast.literal;
|
196
|
-
if (typeof value === 'string')
|
197
|
-
return SqliteDsl.text();
|
198
|
-
if (typeof value === 'number')
|
199
|
-
return SqliteDsl.real();
|
200
|
-
if (typeof value === 'boolean')
|
201
|
-
return SqliteDsl.boolean();
|
102
|
+
return false;
|
103
|
+
};
|
104
|
+
const hasUndefined = (ast) => {
|
105
|
+
if (SchemaAST.isUndefinedKeyword(ast))
|
106
|
+
return true;
|
107
|
+
if (SchemaAST.isUnion(ast)) {
|
108
|
+
return ast.types.some((type) => hasUndefined(type));
|
202
109
|
}
|
203
|
-
|
204
|
-
return SqliteDsl.json({ schema });
|
110
|
+
return false;
|
205
111
|
};
|
206
|
-
|
207
|
-
* Creates a column from a specific column type string.
|
208
|
-
*/
|
209
|
-
const createColumnFromType = (columnType, ast) => {
|
112
|
+
const getColumnForType = (columnType, nullable = false) => {
|
210
113
|
switch (columnType) {
|
211
114
|
case 'text':
|
212
|
-
return SqliteDsl.text();
|
115
|
+
return SqliteDsl.text({ nullable });
|
213
116
|
case 'integer':
|
214
|
-
|
215
|
-
return SchemaAST.isBooleanKeyword(ast) ? SqliteDsl.boolean() : SqliteDsl.integer();
|
117
|
+
return SqliteDsl.integer({ nullable });
|
216
118
|
case 'real':
|
217
|
-
return SqliteDsl.real();
|
119
|
+
return SqliteDsl.real({ nullable });
|
218
120
|
case 'blob':
|
219
|
-
return SqliteDsl.blob();
|
121
|
+
return SqliteDsl.blob({ nullable });
|
220
122
|
default:
|
221
123
|
return shouldNeverHappen(`Unsupported column type: ${columnType}`);
|
222
124
|
}
|
223
125
|
};
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
const
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
126
|
+
const getColumnForSchema = (schema, nullable = false) => {
|
127
|
+
const ast = schema.ast;
|
128
|
+
// Strip nullable wrapper to get core type
|
129
|
+
const coreAst = stripNullable(ast);
|
130
|
+
const coreSchema = stripNullable(ast) === ast ? schema : Schema.make(coreAst);
|
131
|
+
// Special case: Boolean is transformed to integer in SQLite
|
132
|
+
if (SchemaAST.isBooleanKeyword(coreAst)) {
|
133
|
+
return SqliteDsl.boolean({ nullable });
|
134
|
+
}
|
135
|
+
// Get the encoded AST - what actually gets stored in SQLite
|
136
|
+
const encodedAst = Schema.encodedSchema(coreSchema).ast;
|
137
|
+
// Check if the encoded type matches SQLite native types
|
138
|
+
if (SchemaAST.isStringKeyword(encodedAst)) {
|
139
|
+
return SqliteDsl.text({ schema: coreSchema, nullable });
|
140
|
+
}
|
141
|
+
if (SchemaAST.isNumberKeyword(encodedAst)) {
|
142
|
+
// Special cases for integer columns
|
143
|
+
const id = SchemaAST.getIdentifierAnnotation(coreAst).pipe(Option.getOrElse(() => ''));
|
144
|
+
if (id === 'Int' || id === 'DateFromNumber') {
|
145
|
+
return SqliteDsl.integer({ schema: coreSchema, nullable });
|
146
|
+
}
|
147
|
+
return SqliteDsl.real({ schema: coreSchema, nullable });
|
233
148
|
}
|
234
|
-
|
235
|
-
|
149
|
+
// Literals based on their type
|
150
|
+
if (SchemaAST.isLiteral(coreAst)) {
|
151
|
+
const value = coreAst.literal;
|
152
|
+
if (typeof value === 'boolean')
|
153
|
+
return SqliteDsl.boolean({ nullable });
|
236
154
|
}
|
237
|
-
|
238
|
-
|
155
|
+
// Literals based on their encoded type
|
156
|
+
if (SchemaAST.isLiteral(encodedAst)) {
|
157
|
+
const value = encodedAst.literal;
|
158
|
+
if (typeof value === 'string')
|
159
|
+
return SqliteDsl.text({ schema: coreSchema, nullable });
|
160
|
+
if (typeof value === 'number') {
|
161
|
+
// Check if the original schema is Int
|
162
|
+
const id = SchemaAST.getIdentifierAnnotation(coreAst).pipe(Option.getOrElse(() => ''));
|
163
|
+
if (id === 'Int') {
|
164
|
+
return SqliteDsl.integer({ schema: coreSchema, nullable });
|
165
|
+
}
|
166
|
+
return SqliteDsl.real({ schema: coreSchema, nullable });
|
167
|
+
}
|
239
168
|
}
|
240
|
-
|
169
|
+
// Everything else needs JSON encoding
|
170
|
+
return SqliteDsl.json({ schema: coreSchema, nullable });
|
171
|
+
};
|
172
|
+
const stripNullable = (ast) => {
|
173
|
+
if (!SchemaAST.isUnion(ast))
|
174
|
+
return ast;
|
175
|
+
// Find non-null/undefined type
|
176
|
+
const core = ast.types.find((type) => !(SchemaAST.isLiteral(type) && type.literal === null) && !SchemaAST.isUndefinedKeyword(type));
|
177
|
+
return core || ast;
|
241
178
|
};
|
242
179
|
//# sourceMappingURL=column-def.js.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"column-def.js","sourceRoot":"","sources":["../../../../src/schema/state/sqlite/column-def.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,
|
1
|
+
{"version":3,"file":"column-def.js","sourceRoot":"","sources":["../../../../src/schema/state/sqlite/column-def.ts"],"names":[],"mappings":"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"}
|
@@ -19,6 +19,14 @@ describe('getColumnDefForSchema', () => {
|
|
19
19
|
it('should map Schema.Date to text column', () => {
|
20
20
|
const columnDef = State.SQLite.getColumnDefForSchema(Schema.Date);
|
21
21
|
expect(columnDef.columnType).toBe('text');
|
22
|
+
expect(Schema.encodedSchema(columnDef.schema).toString()).toBe('string');
|
23
|
+
expect(Schema.typeSchema(columnDef.schema).toString()).toBe('Date');
|
24
|
+
});
|
25
|
+
it('should map Schema.DateFromNumber to integer column', () => {
|
26
|
+
const columnDef = State.SQLite.getColumnDefForSchema(Schema.DateFromNumber);
|
27
|
+
expect(columnDef.columnType).toBe('integer');
|
28
|
+
expect(Schema.encodedSchema(columnDef.schema).toString()).toBe('number');
|
29
|
+
expect(Schema.typeSchema(columnDef.schema).toString()).toBe('DateFromSelf');
|
22
30
|
});
|
23
31
|
it('should map Schema.BigInt to text column', () => {
|
24
32
|
const columnDef = State.SQLite.getColumnDefForSchema(Schema.BigInt);
|
@@ -80,7 +88,7 @@ describe('getColumnDefForSchema', () => {
|
|
80
88
|
encode: String,
|
81
89
|
}));
|
82
90
|
const columnDef = State.SQLite.getColumnDefForSchema(StringToNumber);
|
83
|
-
expect(columnDef.columnType).toBe('
|
91
|
+
expect(columnDef.columnType).toBe('text'); // Based on the encoded type (String)
|
84
92
|
});
|
85
93
|
it('should handle Date transformations', () => {
|
86
94
|
const columnDef = State.SQLite.getColumnDefForSchema(Schema.Date);
|
@@ -233,6 +241,112 @@ describe('getColumnDefForSchema', () => {
|
|
233
241
|
expect(columnDef.columnType).toBe('text'); // Complex type stored as JSON
|
234
242
|
});
|
235
243
|
});
|
244
|
+
describe('schema-based table definitions', () => {
|
245
|
+
it('should handle optional fields in schema', () => {
|
246
|
+
const UserSchema = Schema.Struct({
|
247
|
+
id: Schema.String,
|
248
|
+
name: Schema.String,
|
249
|
+
email: Schema.optional(Schema.String),
|
250
|
+
age: Schema.optional(Schema.Number),
|
251
|
+
});
|
252
|
+
const userTable = State.SQLite.table({
|
253
|
+
name: 'users',
|
254
|
+
schema: UserSchema,
|
255
|
+
});
|
256
|
+
// Optional fields should be nullable
|
257
|
+
expect(userTable.sqliteDef.columns.email.nullable).toBe(true);
|
258
|
+
expect(userTable.sqliteDef.columns.age.nullable).toBe(true);
|
259
|
+
// Non-optional fields should not be nullable
|
260
|
+
expect(userTable.sqliteDef.columns.id.nullable).toBe(false);
|
261
|
+
expect(userTable.sqliteDef.columns.name.nullable).toBe(false);
|
262
|
+
// Row schema should show | null for optional fields
|
263
|
+
expect(userTable.rowSchema.fields.email.toString()).toBe('string | null');
|
264
|
+
expect(userTable.rowSchema.fields.age.toString()).toBe('number | null');
|
265
|
+
});
|
266
|
+
it('should handle optional boolean with proper transformation', () => {
|
267
|
+
const schema = Schema.Struct({
|
268
|
+
id: Schema.String,
|
269
|
+
active: Schema.optional(Schema.Boolean),
|
270
|
+
});
|
271
|
+
const table = State.SQLite.table({ name: 'test', schema });
|
272
|
+
expect(table.sqliteDef.columns.active.nullable).toBe(true);
|
273
|
+
expect(table.sqliteDef.columns.active.columnType).toBe('integer');
|
274
|
+
expect(table.sqliteDef.columns.active.schema.toString()).toBe('(number <-> boolean) | null');
|
275
|
+
expect(table.rowSchema.fields.active.toString()).toBe('(number <-> boolean) | null');
|
276
|
+
});
|
277
|
+
it('should handle optional complex types with JSON encoding', () => {
|
278
|
+
const schema = Schema.Struct({
|
279
|
+
id: Schema.String,
|
280
|
+
metadata: Schema.optional(Schema.Struct({ color: Schema.String })),
|
281
|
+
tags: Schema.optional(Schema.Array(Schema.String)),
|
282
|
+
});
|
283
|
+
const table = State.SQLite.table({ name: 'test', schema });
|
284
|
+
expect(table.sqliteDef.columns.metadata.nullable).toBe(true);
|
285
|
+
expect(table.sqliteDef.columns.metadata.columnType).toBe('text');
|
286
|
+
expect(table.rowSchema.fields.metadata.toString()).toBe('(parseJson <-> { readonly color: string }) | null');
|
287
|
+
expect(table.sqliteDef.columns.tags.nullable).toBe(true);
|
288
|
+
expect(table.sqliteDef.columns.tags.columnType).toBe('text');
|
289
|
+
expect(table.rowSchema.fields.tags.toString()).toBe('(parseJson <-> ReadonlyArray<string>) | null');
|
290
|
+
});
|
291
|
+
it('should handle Schema.NullOr', () => {
|
292
|
+
const schema = Schema.Struct({
|
293
|
+
id: Schema.String,
|
294
|
+
description: Schema.NullOr(Schema.String),
|
295
|
+
count: Schema.NullOr(Schema.Int),
|
296
|
+
});
|
297
|
+
const table = State.SQLite.table({ name: 'test', schema });
|
298
|
+
expect(table.sqliteDef.columns.description.nullable).toBe(true);
|
299
|
+
expect(table.sqliteDef.columns.count.nullable).toBe(true);
|
300
|
+
expect(table.rowSchema.fields.description.toString()).toBe('string | null');
|
301
|
+
expect(table.rowSchema.fields.count.toString()).toBe('Int | null');
|
302
|
+
});
|
303
|
+
it('should handle Schema.NullOr with complex types', () => {
|
304
|
+
const schema = Schema.Struct({
|
305
|
+
data: Schema.NullOr(Schema.Struct({ value: Schema.Number })),
|
306
|
+
}).annotations({ title: 'test' });
|
307
|
+
const table = State.SQLite.table({ schema });
|
308
|
+
expect(table.sqliteDef.columns.data.nullable).toBe(true);
|
309
|
+
expect(table.sqliteDef.columns.data.columnType).toBe('text');
|
310
|
+
expect(table.rowSchema.fields.data.toString()).toBe('(parseJson <-> { readonly value: number }) | null');
|
311
|
+
});
|
312
|
+
it('should handle mixed nullable and optional fields', () => {
|
313
|
+
const schema = Schema.Struct({
|
314
|
+
nullableText: Schema.NullOr(Schema.String),
|
315
|
+
optionalText: Schema.optional(Schema.String),
|
316
|
+
optionalJson: Schema.optional(Schema.Struct({ x: Schema.Number })),
|
317
|
+
}).annotations({ title: 'test' });
|
318
|
+
const table = State.SQLite.table({ schema });
|
319
|
+
// Both should be nullable at column level
|
320
|
+
expect(table.sqliteDef.columns.nullableText.nullable).toBe(true);
|
321
|
+
expect(table.sqliteDef.columns.optionalText.nullable).toBe(true);
|
322
|
+
expect(table.sqliteDef.columns.optionalJson.nullable).toBe(true);
|
323
|
+
// Schema representations
|
324
|
+
expect(table.rowSchema.fields.nullableText.toString()).toBe('string | null');
|
325
|
+
expect(table.rowSchema.fields.optionalText.toString()).toBe('string | null');
|
326
|
+
expect(table.rowSchema.fields.optionalJson.toString()).toBe('(parseJson <-> { readonly x: number }) | null');
|
327
|
+
});
|
328
|
+
// TODO bring back some time later
|
329
|
+
// it('should handle lossy Schema.optional(Schema.NullOr(...)) with JSON encoding', () => {
|
330
|
+
// const schema = Schema.Struct({
|
331
|
+
// id: Schema.String,
|
332
|
+
// lossyText: Schema.optional(Schema.NullOr(Schema.String)),
|
333
|
+
// lossyComplex: Schema.optional(Schema.NullOr(Schema.Struct({ value: Schema.Number }))),
|
334
|
+
// }).annotations({ title: 'lossy_test' })
|
335
|
+
// const table = State.SQLite.table({ schema })
|
336
|
+
// // Check column definitions for lossy fields
|
337
|
+
// expect(table.sqliteDef.columns.lossyText.nullable).toBe(true)
|
338
|
+
// expect(table.sqliteDef.columns.lossyText.columnType).toBe('text')
|
339
|
+
// expect(table.sqliteDef.columns.lossyComplex.nullable).toBe(true)
|
340
|
+
// expect(table.sqliteDef.columns.lossyComplex.columnType).toBe('text')
|
341
|
+
// // Check schema representations - should use parseJson for lossy encoding
|
342
|
+
// expect((table.rowSchema as any).fields.lossyText.toString()).toBe('(parseJson <-> string | null)')
|
343
|
+
// expect((table.rowSchema as any).fields.lossyComplex.toString()).toBe(
|
344
|
+
// '(parseJson <-> { readonly value: number } | null)',
|
345
|
+
// )
|
346
|
+
// // Note: Since we're converting undefined to null, this is a lossy transformation.
|
347
|
+
// // The test now just verifies that the schemas are set up correctly for JSON encoding.
|
348
|
+
// })
|
349
|
+
});
|
236
350
|
describe('annotations', () => {
|
237
351
|
describe('withColumnType', () => {
|
238
352
|
it('should respect column type annotation for text', () => {
|
@@ -276,11 +390,6 @@ describe('getColumnDefForSchema', () => {
|
|
276
390
|
const UserSchema = Schema.Struct({
|
277
391
|
id: Schema.String.pipe(withPrimaryKey),
|
278
392
|
name: Schema.String,
|
279
|
-
email: Schema.optional(Schema.String),
|
280
|
-
nullable: Schema.NullOr(Schema.Int),
|
281
|
-
optionalComplex: Schema.optional(Schema.Struct({ color: Schema.String })),
|
282
|
-
optionalNullableText: Schema.optional(Schema.NullOr(Schema.String)),
|
283
|
-
optionalNullableComplex: Schema.optional(Schema.NullOr(Schema.Struct({ color: Schema.String }))),
|
284
393
|
});
|
285
394
|
const userTable = State.SQLite.table({
|
286
395
|
name: 'users',
|
@@ -289,73 +398,7 @@ describe('getColumnDefForSchema', () => {
|
|
289
398
|
expect(userTable.sqliteDef.columns.id.primaryKey).toBe(true);
|
290
399
|
expect(userTable.sqliteDef.columns.id.nullable).toBe(false);
|
291
400
|
expect(userTable.sqliteDef.columns.name.primaryKey).toBe(false);
|
292
|
-
expect(userTable.sqliteDef.columns.
|
293
|
-
expect(userTable.sqliteDef.columns.email.nullable).toBe(true);
|
294
|
-
expect(userTable.sqliteDef.columns.nullable.primaryKey).toBe(false);
|
295
|
-
expect(userTable.sqliteDef.columns.nullable.nullable).toBe(true);
|
296
|
-
expect(userTable.sqliteDef.columns.optionalComplex.nullable).toBe(true);
|
297
|
-
expect(userTable.rowSchema.fields.email.toString()).toBe('string | undefined');
|
298
|
-
expect(userTable.rowSchema.fields.nullable.toString()).toBe('Int | null');
|
299
|
-
expect(userTable.rowSchema.fields.optionalComplex.toString()).toBe('(parseJson <-> { readonly color: string } | undefined)');
|
300
|
-
});
|
301
|
-
it('should handle Schema.NullOr with complex types', () => {
|
302
|
-
const schema = Schema.Struct({
|
303
|
-
data: Schema.NullOr(Schema.Struct({ value: Schema.Number })),
|
304
|
-
}).annotations({ title: 'test' });
|
305
|
-
const table = State.SQLite.table({ schema });
|
306
|
-
expect(table.sqliteDef.columns.data.nullable).toBe(true);
|
307
|
-
expect(table.sqliteDef.columns.data.columnType).toBe('text');
|
308
|
-
expect(table.rowSchema.fields.data.toString()).toBe('{ readonly value: number } | null');
|
309
|
-
});
|
310
|
-
it('should handle mixed nullable and optional fields', () => {
|
311
|
-
const schema = Schema.Struct({
|
312
|
-
nullableText: Schema.NullOr(Schema.String),
|
313
|
-
optionalText: Schema.optional(Schema.String),
|
314
|
-
optionalJson: Schema.optional(Schema.Struct({ x: Schema.Number })),
|
315
|
-
}).annotations({ title: 'test' });
|
316
|
-
const table = State.SQLite.table({ schema });
|
317
|
-
// Both should be nullable at column level
|
318
|
-
expect(table.sqliteDef.columns.nullableText.nullable).toBe(true);
|
319
|
-
expect(table.sqliteDef.columns.optionalText.nullable).toBe(true);
|
320
|
-
expect(table.sqliteDef.columns.optionalJson.nullable).toBe(true);
|
321
|
-
// But different schema representations
|
322
|
-
expect(table.rowSchema.fields.nullableText.toString()).toBe('string | null');
|
323
|
-
expect(table.rowSchema.fields.optionalText.toString()).toBe('string | undefined');
|
324
|
-
expect(table.rowSchema.fields.optionalJson.toString()).toBe('(parseJson <-> { readonly x: number } | undefined)');
|
325
|
-
});
|
326
|
-
it('should handle lossy Schema.optional(Schema.NullOr(...)) with JSON encoding', () => {
|
327
|
-
const schema = Schema.Struct({
|
328
|
-
id: Schema.String,
|
329
|
-
lossyText: Schema.optional(Schema.NullOr(Schema.String)),
|
330
|
-
lossyComplex: Schema.optional(Schema.NullOr(Schema.Struct({ value: Schema.Number }))),
|
331
|
-
}).annotations({ title: 'lossy_test' });
|
332
|
-
const table = State.SQLite.table({ schema });
|
333
|
-
// Check column definitions for lossy fields
|
334
|
-
expect(table.sqliteDef.columns.lossyText.nullable).toBe(true);
|
335
|
-
expect(table.sqliteDef.columns.lossyText.columnType).toBe('text');
|
336
|
-
expect(table.sqliteDef.columns.lossyComplex.nullable).toBe(true);
|
337
|
-
expect(table.sqliteDef.columns.lossyComplex.columnType).toBe('text');
|
338
|
-
// Check schema representations - should use parseJson for lossless encoding
|
339
|
-
expect(table.rowSchema.fields.lossyText.toString()).toBe('(parseJson <-> string | null | undefined)');
|
340
|
-
expect(table.rowSchema.fields.lossyComplex.toString()).toBe('(parseJson <-> { readonly value: number } | null | undefined)');
|
341
|
-
// Test actual data round-tripping to ensure losslessness
|
342
|
-
// Note: Missing field case is challenging with current Effect Schema design
|
343
|
-
// as optional fields are handled at struct level, not field level
|
344
|
-
const testCases = [
|
345
|
-
// For now, test only cases where both lossy fields are present
|
346
|
-
{ name: 'both explicit null', data: { id: '2', lossyText: null, lossyComplex: null } },
|
347
|
-
{ name: 'text value, complex null', data: { id: '3', lossyText: 'hello', lossyComplex: null } },
|
348
|
-
{ name: 'text null, complex value', data: { id: '4', lossyText: null, lossyComplex: { value: 42 } } },
|
349
|
-
{ name: 'both values', data: { id: '5', lossyText: 'world', lossyComplex: { value: 42 } } },
|
350
|
-
];
|
351
|
-
testCases.forEach((testCase) => {
|
352
|
-
// Encode through insert schema
|
353
|
-
const encoded = Schema.encodeSync(table.insertSchema)(testCase.data);
|
354
|
-
// Decode through row schema
|
355
|
-
const decoded = Schema.decodeSync(table.rowSchema)(encoded);
|
356
|
-
// Check for losslessness
|
357
|
-
expect(decoded).toEqual(testCase.data);
|
358
|
-
});
|
401
|
+
expect(userTable.sqliteDef.columns.name.nullable).toBe(false);
|
359
402
|
});
|
360
403
|
it('should throw when primary key is used with optional schema', () => {
|
361
404
|
// Note: Schema.optional returns a property signature, not a schema, so we can't pipe it
|