@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.
@@ -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
@@ -5,5 +5,5 @@
5
5
  "rootDir": "./src"
6
6
  },
7
7
  "include": ["src/**/*"],
8
- "references": [{ "path": "../core" }]
8
+ "references": [{ "path": "../types" }]
9
9
  }