@objectstack/driver-sql 3.2.9
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/.turbo/turbo-build.log +22 -0
- package/LICENSE +202 -0
- package/dist/index.d.mts +130 -0
- package/dist/index.d.ts +130 -0
- package/dist/index.js +1044 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1007 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +32 -0
- package/src/index.ts +30 -0
- package/src/sql-driver-advanced.test.ts +499 -0
- package/src/sql-driver-introspection.test.ts +164 -0
- package/src/sql-driver-queryast.test.ts +180 -0
- package/src/sql-driver-schema.test.ts +246 -0
- package/src/sql-driver.test.ts +108 -0
- package/src/sql-driver.ts +1240 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
4
|
+
import { SqlDriver } from '../src/index.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* QueryAST format tests — verifies compatibility with @objectstack/spec QueryAST.
|
|
8
|
+
*/
|
|
9
|
+
describe('SqlDriver (QueryAST Format)', () => {
|
|
10
|
+
let driver: SqlDriver;
|
|
11
|
+
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
driver = new SqlDriver({
|
|
14
|
+
client: 'better-sqlite3',
|
|
15
|
+
connection: { filename: ':memory:' },
|
|
16
|
+
useNullAsDefault: true,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const k = (driver as any).knex;
|
|
20
|
+
|
|
21
|
+
await k.schema.createTable('products', (t: any) => {
|
|
22
|
+
t.string('id').primary();
|
|
23
|
+
t.string('name');
|
|
24
|
+
t.float('price');
|
|
25
|
+
t.string('category');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
await k('products').insert([
|
|
29
|
+
{ id: '1', name: 'Laptop', price: 1200, category: 'Electronics' },
|
|
30
|
+
{ id: '2', name: 'Mouse', price: 25, category: 'Electronics' },
|
|
31
|
+
{ id: '3', name: 'Desk', price: 350, category: 'Furniture' },
|
|
32
|
+
{ id: '4', name: 'Chair', price: 200, category: 'Furniture' },
|
|
33
|
+
{ id: '5', name: 'Monitor', price: 400, category: 'Electronics' },
|
|
34
|
+
]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(async () => {
|
|
38
|
+
await driver.disconnect();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('Driver Metadata', () => {
|
|
42
|
+
it('should expose driver metadata for ObjectStack compatibility', () => {
|
|
43
|
+
expect(driver.name).toBe('com.objectstack.driver.sql');
|
|
44
|
+
expect(driver.version).toBeDefined();
|
|
45
|
+
expect(driver.supports).toBeDefined();
|
|
46
|
+
expect(driver.supports.transactions).toBe(true);
|
|
47
|
+
expect(driver.supports.joins).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('Lifecycle Methods', () => {
|
|
52
|
+
it('should support connect method', async () => {
|
|
53
|
+
await expect(driver.connect()).resolves.toBeUndefined();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should support checkHealth method', async () => {
|
|
57
|
+
const healthy = await driver.checkHealth();
|
|
58
|
+
expect(healthy).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should support disconnect method', async () => {
|
|
62
|
+
const testDriver = new SqlDriver({
|
|
63
|
+
client: 'better-sqlite3',
|
|
64
|
+
connection: { filename: ':memory:' },
|
|
65
|
+
useNullAsDefault: true,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await expect(testDriver.disconnect()).resolves.toBeUndefined();
|
|
69
|
+
const healthy = await testDriver.checkHealth();
|
|
70
|
+
expect(healthy).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('QueryAST Format Support', () => {
|
|
75
|
+
it('should support QueryAST with "top" instead of "limit"', async () => {
|
|
76
|
+
const results = await driver.find('products', {
|
|
77
|
+
fields: ['name', 'price'],
|
|
78
|
+
top: 2,
|
|
79
|
+
sort: [{ field: 'price', order: 'asc' as const }],
|
|
80
|
+
} as any);
|
|
81
|
+
|
|
82
|
+
expect(results.length).toBe(2);
|
|
83
|
+
expect(results[0].name).toBe('Mouse');
|
|
84
|
+
expect(results[1].name).toBe('Chair');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should support QueryAST sort format with object notation', async () => {
|
|
88
|
+
const results = await driver.find('products', {
|
|
89
|
+
fields: ['name'],
|
|
90
|
+
sort: [
|
|
91
|
+
{ field: 'category', order: 'asc' as const },
|
|
92
|
+
{ field: 'price', order: 'desc' as const },
|
|
93
|
+
],
|
|
94
|
+
} as any);
|
|
95
|
+
|
|
96
|
+
expect(results.length).toBe(5);
|
|
97
|
+
expect(results[0].name).toBe('Laptop');
|
|
98
|
+
expect(results[3].name).toBe('Desk');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should support QueryAST with filters and pagination', async () => {
|
|
102
|
+
const results = await driver.find('products', {
|
|
103
|
+
filters: [['category', '=', 'Electronics']],
|
|
104
|
+
skip: 1,
|
|
105
|
+
top: 1,
|
|
106
|
+
sort: [{ field: 'price', order: 'asc' as const }],
|
|
107
|
+
} as any);
|
|
108
|
+
|
|
109
|
+
expect(results.length).toBe(1);
|
|
110
|
+
expect(results[0].name).toBe('Monitor');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should support aggregations in QueryAST format', async () => {
|
|
114
|
+
const results = await driver.aggregate('products', {
|
|
115
|
+
aggregations: [
|
|
116
|
+
{ function: 'sum' as const, field: 'price', alias: 'total_price' },
|
|
117
|
+
{ function: 'count' as const, field: '*', alias: 'count' },
|
|
118
|
+
],
|
|
119
|
+
groupBy: ['category'],
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
expect(results.length).toBe(2);
|
|
123
|
+
|
|
124
|
+
const electronics = results.find((r: any) => r.category === 'Electronics');
|
|
125
|
+
const furniture = results.find((r: any) => r.category === 'Furniture');
|
|
126
|
+
|
|
127
|
+
expect(electronics).toBeDefined();
|
|
128
|
+
expect(electronics.total_price).toBe(1625);
|
|
129
|
+
|
|
130
|
+
expect(furniture).toBeDefined();
|
|
131
|
+
expect(furniture.total_price).toBe(550);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should support count with QueryAST format', async () => {
|
|
135
|
+
const count = await driver.count('products', {
|
|
136
|
+
filters: [['price', '>', 300]],
|
|
137
|
+
} as any);
|
|
138
|
+
expect(count).toBe(3);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('Backward Compatibility', () => {
|
|
143
|
+
it('should still support legacy UnifiedQuery format with "limit"', async () => {
|
|
144
|
+
const results = await driver.find('products', {
|
|
145
|
+
fields: ['name'],
|
|
146
|
+
limit: 2,
|
|
147
|
+
sort: [['price', 'asc']],
|
|
148
|
+
} as any);
|
|
149
|
+
|
|
150
|
+
expect(results.length).toBe(2);
|
|
151
|
+
expect(results[0].name).toBe('Mouse');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should still support legacy aggregate format', async () => {
|
|
155
|
+
const results = await driver.aggregate('products', {
|
|
156
|
+
aggregate: [{ func: 'avg', field: 'price', alias: 'avg_price' }],
|
|
157
|
+
groupBy: ['category'],
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
expect(results.length).toBe(2);
|
|
161
|
+
const electronics = results.find((r: any) => r.category === 'Electronics');
|
|
162
|
+
expect(electronics.avg_price).toBeCloseTo(541.67, 1);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('Mixed Format Support', () => {
|
|
167
|
+
it('should handle query with both top and skip', async () => {
|
|
168
|
+
const results = await driver.find('products', {
|
|
169
|
+
top: 3,
|
|
170
|
+
skip: 2,
|
|
171
|
+
sort: [{ field: 'name', order: 'asc' as const }],
|
|
172
|
+
} as any);
|
|
173
|
+
|
|
174
|
+
expect(results.length).toBe(3);
|
|
175
|
+
expect(results[0].name).toBe('Laptop');
|
|
176
|
+
expect(results[1].name).toBe('Monitor');
|
|
177
|
+
expect(results[2].name).toBe('Mouse');
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
});
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
4
|
+
import { SqlDriver } from '../src/index.js';
|
|
5
|
+
|
|
6
|
+
describe('SqlDriver Schema Sync (SQLite)', () => {
|
|
7
|
+
let driver: SqlDriver;
|
|
8
|
+
let knexInstance: any;
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
driver = new SqlDriver({
|
|
12
|
+
client: 'better-sqlite3',
|
|
13
|
+
connection: { filename: ':memory:' },
|
|
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
|
+
{
|
|
26
|
+
name: 'test_obj',
|
|
27
|
+
fields: {
|
|
28
|
+
name: { type: 'string' },
|
|
29
|
+
age: { type: 'integer' },
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
await driver.initObjects(objects);
|
|
35
|
+
|
|
36
|
+
const exists = await knexInstance.schema.hasTable('test_obj');
|
|
37
|
+
expect(exists).toBe(true);
|
|
38
|
+
|
|
39
|
+
const columns = await knexInstance('test_obj').columnInfo();
|
|
40
|
+
expect(columns).toHaveProperty('id');
|
|
41
|
+
expect(columns).toHaveProperty('created_at');
|
|
42
|
+
expect(columns).toHaveProperty('updated_at');
|
|
43
|
+
expect(columns).toHaveProperty('name');
|
|
44
|
+
expect(columns).toHaveProperty('age');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should add new columns if table exists', async () => {
|
|
48
|
+
await knexInstance.schema.createTable('test_obj', (t: any) => {
|
|
49
|
+
t.string('id').primary();
|
|
50
|
+
t.string('name');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
await knexInstance('test_obj').insert({ id: '1', name: 'Old Data' });
|
|
54
|
+
|
|
55
|
+
const objects = [
|
|
56
|
+
{
|
|
57
|
+
name: 'test_obj',
|
|
58
|
+
fields: {
|
|
59
|
+
name: { type: 'string' },
|
|
60
|
+
age: { type: 'integer' },
|
|
61
|
+
active: { type: 'boolean' },
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
await driver.initObjects(objects);
|
|
67
|
+
|
|
68
|
+
const columns = await knexInstance('test_obj').columnInfo();
|
|
69
|
+
expect(columns).toHaveProperty('age');
|
|
70
|
+
expect(columns).toHaveProperty('active');
|
|
71
|
+
|
|
72
|
+
const row = await knexInstance('test_obj').where('id', '1').first();
|
|
73
|
+
expect(row.name).toBe('Old Data');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should not delete existing columns', async () => {
|
|
77
|
+
await knexInstance.schema.createTable('test_obj', (t: any) => {
|
|
78
|
+
t.string('id').primary();
|
|
79
|
+
t.string('name');
|
|
80
|
+
t.string('extra_column');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const objects = [
|
|
84
|
+
{
|
|
85
|
+
name: 'test_obj',
|
|
86
|
+
fields: {
|
|
87
|
+
name: { type: 'string' },
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
await driver.initObjects(objects);
|
|
93
|
+
|
|
94
|
+
const columns = await knexInstance('test_obj').columnInfo();
|
|
95
|
+
expect(columns).toHaveProperty('name');
|
|
96
|
+
expect(columns).toHaveProperty('extra_column');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should not fail if table creation is repeated', async () => {
|
|
100
|
+
const objects = [
|
|
101
|
+
{
|
|
102
|
+
name: 'test_obj',
|
|
103
|
+
fields: {
|
|
104
|
+
name: { type: 'string' },
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
await driver.initObjects(objects);
|
|
110
|
+
await driver.initObjects(objects);
|
|
111
|
+
|
|
112
|
+
const exists = await knexInstance.schema.hasTable('test_obj');
|
|
113
|
+
expect(exists).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should create json column for multiple=true fields', async () => {
|
|
117
|
+
const objects = [
|
|
118
|
+
{
|
|
119
|
+
name: 'multi_test',
|
|
120
|
+
fields: {
|
|
121
|
+
tags: { type: 'select', multiple: true } as any,
|
|
122
|
+
users: { type: 'lookup', reference_to: 'user', multiple: true } as any,
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
await driver.initObjects(objects);
|
|
128
|
+
|
|
129
|
+
await driver.create('multi_test', {
|
|
130
|
+
tags: ['a', 'b'],
|
|
131
|
+
users: ['u1', 'u2'],
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const results = await driver.find('multi_test', {});
|
|
135
|
+
const row = results[0];
|
|
136
|
+
|
|
137
|
+
expect(row.tags).toEqual(['a', 'b']);
|
|
138
|
+
expect(row.users).toEqual(['u1', 'u2']);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should create percent column', async () => {
|
|
142
|
+
const objects = [
|
|
143
|
+
{
|
|
144
|
+
name: 'percent_test',
|
|
145
|
+
fields: {
|
|
146
|
+
completion: { type: 'percent' } as any,
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
await driver.initObjects(objects);
|
|
152
|
+
|
|
153
|
+
const columns = await knexInstance('percent_test').columnInfo();
|
|
154
|
+
expect(columns).toHaveProperty('completion');
|
|
155
|
+
|
|
156
|
+
await driver.create('percent_test', { completion: 0.85 });
|
|
157
|
+
const res = await driver.find('percent_test', {});
|
|
158
|
+
expect(res[0].completion).toBe(0.85);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should handle special fields (formula, summary, auto_number)', async () => {
|
|
162
|
+
const objects = [
|
|
163
|
+
{
|
|
164
|
+
name: 'special_fields_test',
|
|
165
|
+
fields: {
|
|
166
|
+
total: { type: 'formula', expression: 'price * qty', data_type: 'number' } as any,
|
|
167
|
+
child_count: { type: 'summary', summary_object: 'child_obj', summary_type: 'count' } as any,
|
|
168
|
+
invoice_no: { type: 'auto_number', auto_number_format: 'INV-{0000}' } as any,
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
await driver.initObjects(objects);
|
|
174
|
+
|
|
175
|
+
const columns = await knexInstance('special_fields_test').columnInfo();
|
|
176
|
+
|
|
177
|
+
expect(columns).not.toHaveProperty('total');
|
|
178
|
+
expect(columns).toHaveProperty('child_count');
|
|
179
|
+
expect(columns).toHaveProperty('invoice_no');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should create database constraints (unique, required)', async () => {
|
|
183
|
+
const objects = [
|
|
184
|
+
{
|
|
185
|
+
name: 'constraint_test',
|
|
186
|
+
fields: {
|
|
187
|
+
unique_field: { type: 'string', unique: true } as any,
|
|
188
|
+
required_field: { type: 'string', required: true } as any,
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
await driver.initObjects(objects);
|
|
194
|
+
|
|
195
|
+
await driver.create('constraint_test', { unique_field: 'u1', required_field: 'r1' });
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
await driver.create('constraint_test', { unique_field: 'u1', required_field: 'r2' });
|
|
199
|
+
expect.unreachable('Should throw error for unique violation');
|
|
200
|
+
} catch (e: any) {
|
|
201
|
+
expect(e.message).toMatch(/UNIQUE constraint failed|duplicate key value/);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
await driver.create('constraint_test', { unique_field: 'u2' });
|
|
206
|
+
expect.unreachable('Should throw error for not null violation');
|
|
207
|
+
} catch (e: any) {
|
|
208
|
+
expect(e.message).toMatch(/NOT NULL constraint failed|null value in column/);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should handle new field types (email, file, location)', async () => {
|
|
213
|
+
const objects = [
|
|
214
|
+
{
|
|
215
|
+
name: 'new_types_test',
|
|
216
|
+
fields: {
|
|
217
|
+
email: { type: 'email' } as any,
|
|
218
|
+
profile_pic: { type: 'image' } as any,
|
|
219
|
+
resume: { type: 'file' } as any,
|
|
220
|
+
office_loc: { type: 'location' } as any,
|
|
221
|
+
work_hours: { type: 'time' } as any,
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
];
|
|
225
|
+
|
|
226
|
+
await driver.initObjects(objects);
|
|
227
|
+
const cols = await knexInstance('new_types_test').columnInfo();
|
|
228
|
+
|
|
229
|
+
expect(cols).toHaveProperty('email');
|
|
230
|
+
expect(cols).toHaveProperty('profile_pic');
|
|
231
|
+
expect(cols).toHaveProperty('resume');
|
|
232
|
+
|
|
233
|
+
await driver.create('new_types_test', {
|
|
234
|
+
email: 'test@example.com',
|
|
235
|
+
profile_pic: { url: 'http://img.com/1.png' },
|
|
236
|
+
office_loc: { lat: 10, lng: 20 },
|
|
237
|
+
work_hours: '09:00:00',
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
const res = await driver.find('new_types_test', {});
|
|
241
|
+
const row = res[0];
|
|
242
|
+
|
|
243
|
+
expect(row.email).toBe('test@example.com');
|
|
244
|
+
expect(row.office_loc).toEqual({ lat: 10, lng: 20 });
|
|
245
|
+
});
|
|
246
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
4
|
+
import { SqlDriver } from '../src/index.js';
|
|
5
|
+
|
|
6
|
+
describe('SqlDriver (SQLite Integration)', () => {
|
|
7
|
+
let driver: SqlDriver;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
driver = new SqlDriver({
|
|
11
|
+
client: 'better-sqlite3',
|
|
12
|
+
connection: { filename: ':memory:' },
|
|
13
|
+
useNullAsDefault: true,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const k = (driver as any).knex;
|
|
17
|
+
|
|
18
|
+
await k.schema.createTable('users', (t: any) => {
|
|
19
|
+
t.string('id').primary();
|
|
20
|
+
t.string('name');
|
|
21
|
+
t.integer('age');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
await k('users').insert([
|
|
25
|
+
{ id: '1', name: 'Alice', age: 25 },
|
|
26
|
+
{ id: '2', name: 'Bob', age: 17 },
|
|
27
|
+
{ id: '3', name: 'Charlie', age: 30 },
|
|
28
|
+
{ id: '4', name: 'Dave', age: 17 },
|
|
29
|
+
]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(async () => {
|
|
33
|
+
await driver.disconnect();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should be instantiable', () => {
|
|
37
|
+
expect(driver).toBeDefined();
|
|
38
|
+
expect(driver).toBeInstanceOf(SqlDriver);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should find objects with filters', async () => {
|
|
42
|
+
const results = await driver.find('users', {
|
|
43
|
+
fields: ['name', 'age'],
|
|
44
|
+
where: { age: { $gt: 18 } },
|
|
45
|
+
orderBy: [{ field: 'name', order: 'asc' }],
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
expect(results.length).toBe(2);
|
|
49
|
+
expect(results.map((r: any) => r.name)).toEqual(['Alice', 'Charlie']);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should apply simple AND/OR logic', async () => {
|
|
53
|
+
const results = await driver.find('users', {
|
|
54
|
+
where: {
|
|
55
|
+
$or: [{ age: 17 }, { age: { $gt: 29 } }],
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
const names = results.map((r: any) => r.name).sort();
|
|
59
|
+
expect(names).toEqual(['Bob', 'Charlie', 'Dave']);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should find one object by id', async () => {
|
|
63
|
+
const [alice] = await driver.find('users', { where: { name: 'Alice' } });
|
|
64
|
+
expect(alice).toBeDefined();
|
|
65
|
+
|
|
66
|
+
const fetched = await driver.findOne('users', alice.id as any);
|
|
67
|
+
expect(fetched).toBeDefined();
|
|
68
|
+
expect(fetched.name).toBe('Alice');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should create an object', async () => {
|
|
72
|
+
await driver.create('users', { name: 'Eve', age: 22 });
|
|
73
|
+
|
|
74
|
+
const [eve] = await driver.find('users', { where: { name: 'Eve' } });
|
|
75
|
+
expect(eve).toBeDefined();
|
|
76
|
+
expect(eve.age).toBe(22);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should update an object', async () => {
|
|
80
|
+
const [bob] = await driver.find('users', { where: { name: 'Bob' } });
|
|
81
|
+
await driver.update('users', bob.id, { age: 18 });
|
|
82
|
+
|
|
83
|
+
const updated = await driver.findOne('users', bob.id as any);
|
|
84
|
+
expect(updated.age).toBe(18);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should delete an object', async () => {
|
|
88
|
+
const [charlie] = await driver.find('users', { where: { name: 'Charlie' } });
|
|
89
|
+
await driver.delete('users', charlie.id);
|
|
90
|
+
|
|
91
|
+
const deleted = await driver.findOne('users', charlie.id as any);
|
|
92
|
+
expect(deleted).toBeNull();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should count objects', async () => {
|
|
96
|
+
const count = await driver.count('users', { age: 17 } as any);
|
|
97
|
+
expect(count).toBe(2);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should map _id to id if provided', async () => {
|
|
101
|
+
const created = await driver.create('users', { _id: 'custom-id', name: 'Frank', age: 40 });
|
|
102
|
+
|
|
103
|
+
expect(created.id).toBe('custom-id');
|
|
104
|
+
const fetched = await driver.findOne('users', 'custom-id' as any);
|
|
105
|
+
expect(fetched).toBeDefined();
|
|
106
|
+
expect(fetched.name).toBe('Frank');
|
|
107
|
+
});
|
|
108
|
+
});
|