@objectql/core 1.8.3 → 1.9.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,470 @@
1
+ import { toTitleCase, convertIntrospectedSchemaToObjects } from '../src/util';
2
+ import { IntrospectedSchema } from '@objectql/types';
3
+
4
+ describe('Utility Functions', () => {
5
+ describe('toTitleCase', () => {
6
+ it('should convert snake_case to Title Case', () => {
7
+ expect(toTitleCase('hello_world')).toBe('Hello World');
8
+ expect(toTitleCase('first_name')).toBe('First Name');
9
+ expect(toTitleCase('user_id')).toBe('User Id');
10
+ });
11
+
12
+ it('should capitalize first letter of each word', () => {
13
+ expect(toTitleCase('hello')).toBe('Hello');
14
+ expect(toTitleCase('test_string')).toBe('Test String');
15
+ });
16
+
17
+ it('should handle single word', () => {
18
+ expect(toTitleCase('name')).toBe('Name');
19
+ expect(toTitleCase('id')).toBe('Id');
20
+ });
21
+
22
+ it('should handle empty string', () => {
23
+ expect(toTitleCase('')).toBe('');
24
+ });
25
+
26
+ it('should handle multiple underscores', () => {
27
+ expect(toTitleCase('first__name')).toBe('First Name');
28
+ });
29
+
30
+ it('should handle strings without underscores', () => {
31
+ expect(toTitleCase('hello')).toBe('Hello');
32
+ });
33
+ });
34
+
35
+ describe('convertIntrospectedSchemaToObjects', () => {
36
+ it('should convert simple table to object config', () => {
37
+ const schema: IntrospectedSchema = {
38
+ tables: {
39
+ users: {
40
+ name: 'users',
41
+ columns: [
42
+ {
43
+ name: 'name',
44
+ type: 'VARCHAR',
45
+ nullable: false,
46
+ isUnique: false
47
+ },
48
+ {
49
+ name: 'email',
50
+ type: 'VARCHAR',
51
+ nullable: false,
52
+ isUnique: true
53
+ }
54
+ ],
55
+ foreignKeys: [],
56
+ primaryKeys: ['id']
57
+ }
58
+ }
59
+ };
60
+
61
+ const objects = convertIntrospectedSchemaToObjects(schema);
62
+
63
+ expect(objects).toHaveLength(1);
64
+ expect(objects[0].name).toBe('users');
65
+ expect(objects[0].label).toBe('Users');
66
+ expect(objects[0].fields?.name).toBeDefined();
67
+ expect(objects[0].fields?.email).toBeDefined();
68
+ });
69
+
70
+ it('should skip system columns by default', () => {
71
+ const schema: IntrospectedSchema = {
72
+ tables: {
73
+ tasks: {
74
+ name: 'tasks',
75
+ columns: [
76
+ { name: 'id', type: 'INTEGER', nullable: false, isUnique: true },
77
+ { name: 'title', type: 'VARCHAR', nullable: false, isUnique: false },
78
+ { name: 'created_at', type: 'TIMESTAMP', nullable: true, isUnique: false },
79
+ { name: 'updated_at', type: 'TIMESTAMP', nullable: true, isUnique: false }
80
+ ],
81
+ foreignKeys: [],
82
+ primaryKeys: ['id']
83
+ }
84
+ }
85
+ };
86
+
87
+ const objects = convertIntrospectedSchemaToObjects(schema);
88
+
89
+ expect(objects[0].fields?.id).toBeUndefined();
90
+ expect(objects[0].fields?.created_at).toBeUndefined();
91
+ expect(objects[0].fields?.updated_at).toBeUndefined();
92
+ expect(objects[0].fields?.title).toBeDefined();
93
+ });
94
+
95
+ it('should include system columns when skipSystemColumns is false', () => {
96
+ const schema: IntrospectedSchema = {
97
+ tables: {
98
+ tasks: {
99
+ name: 'tasks',
100
+ columns: [
101
+ { name: 'id', type: 'INTEGER', nullable: false, isUnique: true },
102
+ { name: 'title', type: 'VARCHAR', nullable: false, isUnique: false }
103
+ ],
104
+ foreignKeys: [],
105
+ primaryKeys: ['id']
106
+ }
107
+ }
108
+ };
109
+
110
+ const objects = convertIntrospectedSchemaToObjects(schema, {
111
+ skipSystemColumns: false
112
+ });
113
+
114
+ expect(objects[0].fields?.id).toBeDefined();
115
+ expect(objects[0].fields?.title).toBeDefined();
116
+ });
117
+
118
+ it('should exclude tables in excludeTables list', () => {
119
+ const schema: IntrospectedSchema = {
120
+ tables: {
121
+ users: {
122
+ name: 'users',
123
+ columns: [{ name: 'name', type: 'VARCHAR', nullable: false, isUnique: false }],
124
+ foreignKeys: [],
125
+ primaryKeys: ['id']
126
+ },
127
+ migrations: {
128
+ name: 'migrations',
129
+ columns: [{ name: 'version', type: 'INTEGER', nullable: false, isUnique: false }],
130
+ foreignKeys: [],
131
+ primaryKeys: ['id']
132
+ }
133
+ }
134
+ };
135
+
136
+ const objects = convertIntrospectedSchemaToObjects(schema, {
137
+ excludeTables: ['migrations']
138
+ });
139
+
140
+ expect(objects).toHaveLength(1);
141
+ expect(objects[0].name).toBe('users');
142
+ });
143
+
144
+ it('should include only tables in includeTables list', () => {
145
+ const schema: IntrospectedSchema = {
146
+ tables: {
147
+ users: {
148
+
149
+ name: 'users',
150
+
151
+ columns: [{ name: 'name', type: 'VARCHAR', nullable: false, isUnique: false }],
152
+ foreignKeys: [],
153
+ primaryKeys: ['id']
154
+ },
155
+ tasks: {
156
+
157
+ name: 'tasks',
158
+
159
+ columns: [{ name: 'title', type: 'VARCHAR', nullable: false, isUnique: false }],
160
+ foreignKeys: [],
161
+ primaryKeys: ['id']
162
+ },
163
+ logs: {
164
+
165
+ name: 'logs',
166
+
167
+ columns: [{ name: 'message', type: 'TEXT', nullable: false, isUnique: false }],
168
+ foreignKeys: [],
169
+ primaryKeys: ['id']
170
+ }
171
+ }
172
+ };
173
+
174
+ const objects = convertIntrospectedSchemaToObjects(schema, {
175
+ includeTables: ['users', 'tasks']
176
+ });
177
+
178
+ expect(objects).toHaveLength(2);
179
+ expect(objects.find(o => o.name === 'users')).toBeDefined();
180
+ expect(objects.find(o => o.name === 'tasks')).toBeDefined();
181
+ expect(objects.find(o => o.name === 'logs')).toBeUndefined();
182
+ });
183
+
184
+ it('should map database types to field types correctly', () => {
185
+ const schema: IntrospectedSchema = {
186
+ tables: {
187
+ test_types: {
188
+
189
+ name: 'test_types',
190
+
191
+ columns: [
192
+ { name: 'text_field', type: 'VARCHAR', nullable: false, isUnique: false },
193
+ { name: 'long_text', type: 'TEXT', nullable: false, isUnique: false },
194
+ { name: 'number_field', type: 'INTEGER', nullable: false, isUnique: false },
195
+ { name: 'float_field', type: 'FLOAT', nullable: false, isUnique: false },
196
+ { name: 'bool_field', type: 'BOOLEAN', nullable: false, isUnique: false },
197
+ { name: 'date_field', type: 'DATE', nullable: false, isUnique: false },
198
+ { name: 'datetime_field', type: 'TIMESTAMP', nullable: false, isUnique: false },
199
+ { name: 'json_field', type: 'JSON', nullable: false, isUnique: false }
200
+ ],
201
+ foreignKeys: [],
202
+ primaryKeys: ['id']
203
+ }
204
+ }
205
+ };
206
+
207
+ const objects = convertIntrospectedSchemaToObjects(schema);
208
+ const fields = objects[0].fields!;
209
+
210
+ expect(fields.text_field.type).toBe('text');
211
+ expect(fields.long_text.type).toBe('textarea');
212
+ expect(fields.number_field.type).toBe('number');
213
+ expect(fields.float_field.type).toBe('number');
214
+ expect(fields.bool_field.type).toBe('boolean');
215
+ expect(fields.date_field.type).toBe('date');
216
+ expect(fields.datetime_field.type).toBe('datetime');
217
+ expect(fields.json_field.type).toBe('object');
218
+ });
219
+
220
+ it('should set required flag based on nullable', () => {
221
+ const schema: IntrospectedSchema = {
222
+ tables: {
223
+ test: {
224
+
225
+ name: 'test',
226
+
227
+ columns: [
228
+ { name: 'required_field', type: 'VARCHAR', nullable: false, isUnique: false },
229
+ { name: 'optional_field', type: 'VARCHAR', nullable: true, isUnique: false }
230
+ ],
231
+ foreignKeys: [],
232
+ primaryKeys: ['id']
233
+ }
234
+ }
235
+ };
236
+
237
+ const objects = convertIntrospectedSchemaToObjects(schema);
238
+ const fields = objects[0].fields!;
239
+
240
+ expect(fields.required_field.required).toBe(true);
241
+ expect(fields.optional_field.required).toBe(false);
242
+ });
243
+
244
+ it('should set unique flag for unique columns', () => {
245
+ const schema: IntrospectedSchema = {
246
+ tables: {
247
+ test: {
248
+
249
+ name: 'test',
250
+
251
+ columns: [
252
+ { name: 'email', type: 'VARCHAR', nullable: false, isUnique: true },
253
+ { name: 'name', type: 'VARCHAR', nullable: false, isUnique: false }
254
+ ],
255
+ foreignKeys: [],
256
+ primaryKeys: ['id']
257
+ }
258
+ }
259
+ };
260
+
261
+ const objects = convertIntrospectedSchemaToObjects(schema);
262
+ const fields = objects[0].fields!;
263
+
264
+ expect(fields.email.unique).toBe(true);
265
+ expect(fields.name.unique).toBeUndefined();
266
+ });
267
+
268
+ it('should convert foreign keys to lookup fields', () => {
269
+ const schema: IntrospectedSchema = {
270
+ tables: {
271
+ tasks: {
272
+
273
+ name: 'tasks',
274
+
275
+ columns: [
276
+ { name: 'title', type: 'VARCHAR', nullable: false, isUnique: false },
277
+ { name: 'user_id', type: 'INTEGER', nullable: false, isUnique: false }
278
+ ],
279
+ foreignKeys: [
280
+ {
281
+ columnName: 'user_id',
282
+ referencedTable: 'users',
283
+ referencedColumn: 'id'
284
+ }
285
+ ]
286
+ }
287
+ }
288
+ };
289
+
290
+ const objects = convertIntrospectedSchemaToObjects(schema);
291
+ const fields = objects[0].fields!;
292
+
293
+ expect(fields.user_id.type).toBe('lookup');
294
+ expect(fields.user_id.reference_to).toBe('users');
295
+ });
296
+
297
+ it('should add max_length for text fields', () => {
298
+ const schema: IntrospectedSchema = {
299
+ tables: {
300
+ test: {
301
+
302
+ name: 'test',
303
+
304
+ columns: [
305
+ {
306
+ name: 'short_text',
307
+ type: 'VARCHAR',
308
+ nullable: false,
309
+ isUnique: false,
310
+ maxLength: 100
311
+ }
312
+ ],
313
+ foreignKeys: [],
314
+ primaryKeys: ['id']
315
+ }
316
+ }
317
+ };
318
+
319
+ const objects = convertIntrospectedSchemaToObjects(schema);
320
+ const fields = objects[0].fields!;
321
+
322
+ expect(fields.short_text.max_length).toBe(100);
323
+ });
324
+
325
+ it('should add default value when present', () => {
326
+ const schema: IntrospectedSchema = {
327
+ tables: {
328
+ test: {
329
+
330
+ name: 'test',
331
+
332
+ columns: [
333
+ {
334
+ name: 'status',
335
+ type: 'VARCHAR',
336
+ nullable: false,
337
+ isUnique: false,
338
+ defaultValue: 'active'
339
+ }
340
+ ],
341
+ foreignKeys: [],
342
+ primaryKeys: ['id']
343
+ }
344
+ }
345
+ };
346
+
347
+ const objects = convertIntrospectedSchemaToObjects(schema);
348
+ const fields = objects[0].fields!;
349
+
350
+ expect(fields.status.defaultValue).toBe('active');
351
+ });
352
+
353
+ it('should handle empty schema', () => {
354
+ const schema: IntrospectedSchema = {
355
+ tables: {}
356
+ };
357
+
358
+ const objects = convertIntrospectedSchemaToObjects(schema);
359
+ expect(objects).toHaveLength(0);
360
+ });
361
+
362
+ it('should handle multiple tables', () => {
363
+ const schema: IntrospectedSchema = {
364
+ tables: {
365
+ users: {
366
+
367
+ name: 'users',
368
+
369
+ columns: [{ name: 'name', type: 'VARCHAR', nullable: false, isUnique: false }],
370
+ foreignKeys: [],
371
+ primaryKeys: ['id']
372
+ },
373
+ tasks: {
374
+
375
+ name: 'tasks',
376
+
377
+ columns: [{ name: 'title', type: 'VARCHAR', nullable: false, isUnique: false }],
378
+ foreignKeys: [],
379
+ primaryKeys: ['id']
380
+ },
381
+ projects: {
382
+
383
+ name: 'projects',
384
+
385
+ columns: [{ name: 'name', type: 'VARCHAR', nullable: false, isUnique: false }],
386
+ foreignKeys: [],
387
+ primaryKeys: ['id']
388
+ }
389
+ }
390
+ };
391
+
392
+ const objects = convertIntrospectedSchemaToObjects(schema);
393
+ expect(objects).toHaveLength(3);
394
+ });
395
+
396
+ it('should map various numeric types', () => {
397
+ const schema: IntrospectedSchema = {
398
+ tables: {
399
+ numbers: {
400
+
401
+ name: 'numbers',
402
+
403
+ columns: [
404
+ { name: 'int_field', type: 'INT', nullable: false, isUnique: false },
405
+ { name: 'bigint_field', type: 'BIGINT', nullable: false, isUnique: false },
406
+ { name: 'smallint_field', type: 'SMALLINT', nullable: false, isUnique: false },
407
+ { name: 'decimal_field', type: 'DECIMAL', nullable: false, isUnique: false },
408
+ { name: 'numeric_field', type: 'NUMERIC', nullable: false, isUnique: false },
409
+ { name: 'real_field', type: 'REAL', nullable: false, isUnique: false },
410
+ { name: 'double_field', type: 'DOUBLE PRECISION', nullable: false, isUnique: false }
411
+ ],
412
+ foreignKeys: [],
413
+ primaryKeys: ['id']
414
+ }
415
+ }
416
+ };
417
+
418
+ const objects = convertIntrospectedSchemaToObjects(schema);
419
+ const fields = objects[0].fields!;
420
+
421
+ expect(fields.int_field.type).toBe('number');
422
+ expect(fields.bigint_field.type).toBe('number');
423
+ expect(fields.smallint_field.type).toBe('number');
424
+ expect(fields.decimal_field.type).toBe('number');
425
+ expect(fields.numeric_field.type).toBe('number');
426
+ expect(fields.real_field.type).toBe('number');
427
+ expect(fields.double_field.type).toBe('number');
428
+ });
429
+
430
+ it('should map time type correctly', () => {
431
+ const schema: IntrospectedSchema = {
432
+ tables: {
433
+ times: {
434
+
435
+ name: 'times',
436
+
437
+ columns: [
438
+ { name: 'time_field', type: 'TIME', nullable: false, isUnique: false }
439
+ ],
440
+ foreignKeys: [],
441
+ primaryKeys: ['id']
442
+ }
443
+ }
444
+ };
445
+
446
+ const objects = convertIntrospectedSchemaToObjects(schema);
447
+ expect(objects[0].fields?.time_field.type).toBe('time');
448
+ });
449
+
450
+ it('should default unknown types to text', () => {
451
+ const schema: IntrospectedSchema = {
452
+ tables: {
453
+ test: {
454
+
455
+ name: 'test',
456
+
457
+ columns: [
458
+ { name: 'unknown_field', type: 'CUSTOM_TYPE', nullable: false, isUnique: false }
459
+ ],
460
+ foreignKeys: [],
461
+ primaryKeys: ['id']
462
+ }
463
+ }
464
+ };
465
+
466
+ const objects = convertIntrospectedSchemaToObjects(schema);
467
+ expect(objects[0].fields?.unknown_field.type).toBe('text');
468
+ });
469
+ });
470
+ });