@objectql/driver-knex 1.0.0 → 1.2.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/CHANGELOG.md +22 -0
- package/README.md +29 -0
- package/dist/index.d.ts +11 -1
- package/dist/index.js +287 -46
- package/dist/index.js.map +1 -1
- package/package.json +9 -6
- package/src/index.ts +276 -41
- package/test/index.test.ts +95 -45
- package/test/schema.test.ts +253 -0
- package/tsconfig.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { KnexDriver } from '../src';
|
|
2
|
+
|
|
3
|
+
describe('KnexDriver Schema Sync (SQLite)', () => {
|
|
4
|
+
let driver: KnexDriver;
|
|
5
|
+
let knexInstance: any;
|
|
6
|
+
|
|
7
|
+
beforeEach(async () => {
|
|
8
|
+
// Init ephemeral in-memory database
|
|
9
|
+
driver = new KnexDriver({
|
|
10
|
+
client: 'sqlite3',
|
|
11
|
+
connection: {
|
|
12
|
+
filename: ':memory:'
|
|
13
|
+
},
|
|
14
|
+
useNullAsDefault: true
|
|
15
|
+
});
|
|
16
|
+
knexInstance = (driver as any).knex;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(async () => {
|
|
20
|
+
await knexInstance.destroy();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should create table if not exists', async () => {
|
|
24
|
+
const objects = [{
|
|
25
|
+
name: 'test_obj',
|
|
26
|
+
fields: {
|
|
27
|
+
name: { type: 'string' },
|
|
28
|
+
age: { type: 'integer' }
|
|
29
|
+
}
|
|
30
|
+
}];
|
|
31
|
+
|
|
32
|
+
await driver.init(objects);
|
|
33
|
+
|
|
34
|
+
const exists = await knexInstance.schema.hasTable('test_obj');
|
|
35
|
+
expect(exists).toBe(true);
|
|
36
|
+
|
|
37
|
+
const columns = await knexInstance('test_obj').columnInfo();
|
|
38
|
+
expect(columns).toHaveProperty('id');
|
|
39
|
+
expect(columns).toHaveProperty('created_at');
|
|
40
|
+
expect(columns).toHaveProperty('updated_at');
|
|
41
|
+
expect(columns).toHaveProperty('name');
|
|
42
|
+
expect(columns).toHaveProperty('age');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should add new columns if table exists', async () => {
|
|
46
|
+
// 1. Setup existing table with subset of columns
|
|
47
|
+
await knexInstance.schema.createTable('test_obj', (t: any) => {
|
|
48
|
+
t.string('id').primary();
|
|
49
|
+
t.string('name');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// 2. Insert some data
|
|
53
|
+
await knexInstance('test_obj').insert({ id: '1', name: 'Old Data' });
|
|
54
|
+
|
|
55
|
+
// 3. Init with new fields
|
|
56
|
+
const objects = [{
|
|
57
|
+
name: 'test_obj',
|
|
58
|
+
fields: {
|
|
59
|
+
name: { type: 'string' },
|
|
60
|
+
age: { type: 'integer' }, // New field
|
|
61
|
+
active: { type: 'boolean' } // New field
|
|
62
|
+
}
|
|
63
|
+
}];
|
|
64
|
+
|
|
65
|
+
await driver.init(objects);
|
|
66
|
+
|
|
67
|
+
// 4. Verify columns
|
|
68
|
+
const columns = await knexInstance('test_obj').columnInfo();
|
|
69
|
+
expect(columns).toHaveProperty('age');
|
|
70
|
+
expect(columns).toHaveProperty('active');
|
|
71
|
+
|
|
72
|
+
// 5. Verify data is intact
|
|
73
|
+
const row = await knexInstance('test_obj').where('id', '1').first();
|
|
74
|
+
expect(row.name).toBe('Old Data');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should not delete existing columns', async () => {
|
|
78
|
+
// 1. Setup table with extra column
|
|
79
|
+
await knexInstance.schema.createTable('test_obj', (t: any) => {
|
|
80
|
+
t.string('id').primary();
|
|
81
|
+
t.string('name');
|
|
82
|
+
t.string('extra_column'); // Should stay
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// 2. Init with only 'name'
|
|
86
|
+
const objects = [{
|
|
87
|
+
name: 'test_obj',
|
|
88
|
+
fields: {
|
|
89
|
+
name: { type: 'string' }
|
|
90
|
+
}
|
|
91
|
+
}];
|
|
92
|
+
|
|
93
|
+
await driver.init(objects);
|
|
94
|
+
|
|
95
|
+
const columns = await knexInstance('test_obj').columnInfo();
|
|
96
|
+
expect(columns).toHaveProperty('name');
|
|
97
|
+
expect(columns).toHaveProperty('extra_column'); // Preservation check
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should not fail if table creation is repeated', async () => {
|
|
101
|
+
const objects = [{
|
|
102
|
+
name: 'test_obj',
|
|
103
|
+
fields: {
|
|
104
|
+
name: { type: 'string' }
|
|
105
|
+
}
|
|
106
|
+
}];
|
|
107
|
+
|
|
108
|
+
// First init
|
|
109
|
+
await driver.init(objects);
|
|
110
|
+
|
|
111
|
+
// Second init (should be idempotent-ish, or just skip creation)
|
|
112
|
+
await driver.init(objects);
|
|
113
|
+
|
|
114
|
+
const exists = await knexInstance.schema.hasTable('test_obj');
|
|
115
|
+
expect(exists).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should create json column for multiple=true fields', async () => {
|
|
119
|
+
const objects = [{
|
|
120
|
+
name: 'multi_test',
|
|
121
|
+
fields: {
|
|
122
|
+
tags: { type: 'select', multiple: true } as any,
|
|
123
|
+
users: { type: 'lookup', reference_to: 'user', multiple: true } as any
|
|
124
|
+
}
|
|
125
|
+
}];
|
|
126
|
+
|
|
127
|
+
await driver.init(objects);
|
|
128
|
+
|
|
129
|
+
const columns = await knexInstance('multi_test').columnInfo();
|
|
130
|
+
// Types in SQLite might be generic, but verifying we can insert/read array is best.
|
|
131
|
+
|
|
132
|
+
// Try inserting array data
|
|
133
|
+
await driver.create('multi_test', {
|
|
134
|
+
tags: ['a', 'b'],
|
|
135
|
+
users: ['u1', 'u2']
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const results = await driver.find('multi_test', {});
|
|
139
|
+
const row = results[0];
|
|
140
|
+
|
|
141
|
+
// Driver should automatically parse JSON columns for SQLite
|
|
142
|
+
expect(row.tags).toEqual(['a', 'b']);
|
|
143
|
+
expect(row.users).toEqual(['u1', 'u2']);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should create percent column', async () => {
|
|
147
|
+
const objects = [{
|
|
148
|
+
name: 'percent_test',
|
|
149
|
+
fields: {
|
|
150
|
+
completion: { type: 'percent' } as any
|
|
151
|
+
}
|
|
152
|
+
}];
|
|
153
|
+
|
|
154
|
+
await driver.init(objects);
|
|
155
|
+
|
|
156
|
+
const columns = await knexInstance('percent_test').columnInfo();
|
|
157
|
+
expect(columns).toHaveProperty('completion');
|
|
158
|
+
|
|
159
|
+
// Insert a percentage
|
|
160
|
+
await driver.create('percent_test', { completion: 0.85 });
|
|
161
|
+
const res = await driver.find('percent_test', {});
|
|
162
|
+
expect(res[0].completion).toBe(0.85);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should handle special fields (formula, summary, auto_number)', async () => {
|
|
166
|
+
const objects = [{
|
|
167
|
+
name: 'special_fields_test',
|
|
168
|
+
fields: {
|
|
169
|
+
// Formula should NOT create a column
|
|
170
|
+
total: { type: 'formula', expression: 'price * qty', data_type: 'number' } as any,
|
|
171
|
+
// Summary should create a numeric column
|
|
172
|
+
child_count: { type: 'summary', summary_object: 'child_obj', summary_type: 'count' } as any,
|
|
173
|
+
// Auto Number should create a string column
|
|
174
|
+
invoice_no: { type: 'auto_number', auto_number_format: 'INV-{0000}' } as any
|
|
175
|
+
}
|
|
176
|
+
}];
|
|
177
|
+
|
|
178
|
+
await driver.init(objects);
|
|
179
|
+
|
|
180
|
+
const columns = await knexInstance('special_fields_test').columnInfo();
|
|
181
|
+
|
|
182
|
+
expect(columns).not.toHaveProperty('total');
|
|
183
|
+
expect(columns).toHaveProperty('child_count');
|
|
184
|
+
expect(columns).toHaveProperty('invoice_no');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should create database constraints (unique, required)', async () => {
|
|
188
|
+
const objects = [{
|
|
189
|
+
name: 'constraint_test',
|
|
190
|
+
fields: {
|
|
191
|
+
unique_field: { type: 'string', unique: true } as any,
|
|
192
|
+
required_field: { type: 'string', required: true } as any
|
|
193
|
+
}
|
|
194
|
+
}];
|
|
195
|
+
|
|
196
|
+
await driver.init(objects);
|
|
197
|
+
|
|
198
|
+
// Verify Unique using negative test?
|
|
199
|
+
// SQLite enforces unique.
|
|
200
|
+
await driver.create('constraint_test', { unique_field: 'u1', required_field: 'r1' });
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
await driver.create('constraint_test', { unique_field: 'u1', required_field: 'r2' });
|
|
204
|
+
fail('Should throw error for unique violation');
|
|
205
|
+
} catch (e: any) {
|
|
206
|
+
expect(e.message).toMatch(/UNIQUE constraint failed|duplicate key value/);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
await driver.create('constraint_test', { unique_field: 'u2' });
|
|
211
|
+
fail('Should throw error for not null violation');
|
|
212
|
+
} catch (e: any) {
|
|
213
|
+
expect(e.message).toMatch(/NOT NULL constraint failed|null value in column/);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('should handle new field types (email, file, location)', async () => {
|
|
218
|
+
const objects = [{
|
|
219
|
+
name: 'new_types_test',
|
|
220
|
+
fields: {
|
|
221
|
+
email: { type: 'email' } as any,
|
|
222
|
+
profile_pic: { type: 'image' } as any,
|
|
223
|
+
resume: { type: 'file' } as any,
|
|
224
|
+
office_loc: { type: 'location' } as any,
|
|
225
|
+
work_hours: { type: 'time' } as any
|
|
226
|
+
}
|
|
227
|
+
}];
|
|
228
|
+
|
|
229
|
+
await driver.init(objects);
|
|
230
|
+
const cols = await knexInstance('new_types_test').columnInfo();
|
|
231
|
+
|
|
232
|
+
expect(cols).toHaveProperty('email');
|
|
233
|
+
// File/Image/Location/Avatar are stored as JSON in KnexDriver mapping
|
|
234
|
+
// But columnInfo might report 'text' for sqlite or 'json' for postgres.
|
|
235
|
+
// We just ensure they were created.
|
|
236
|
+
expect(cols).toHaveProperty('profile_pic');
|
|
237
|
+
expect(cols).toHaveProperty('resume');
|
|
238
|
+
|
|
239
|
+
// Test Insert for complex types
|
|
240
|
+
await driver.create('new_types_test', {
|
|
241
|
+
email: 'test@example.com',
|
|
242
|
+
profile_pic: { url: 'http://img.com/1.png' },
|
|
243
|
+
office_loc: { lat: 10, lng: 20 },
|
|
244
|
+
work_hours: '09:00:00'
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const res = await driver.find('new_types_test', {});
|
|
248
|
+
const row = res[0];
|
|
249
|
+
|
|
250
|
+
expect(row.email).toBe('test@example.com');
|
|
251
|
+
expect(row.office_loc).toEqual({ lat: 10, lng: 20 }); // Auto-parsed by driver logic
|
|
252
|
+
});
|
|
253
|
+
});
|
package/tsconfig.json
CHANGED