@objectql/core 1.8.2 → 1.8.4
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 +19 -0
- package/dist/ai-agent.d.ts +1 -1
- package/dist/ai-agent.js +8 -39
- package/dist/ai-agent.js.map +1 -1
- package/dist/app.d.ts +1 -0
- package/dist/app.js +8 -0
- package/dist/app.js.map +1 -1
- package/package.json +2 -2
- package/src/ai-agent.ts +9 -37
- package/src/app.ts +9 -0
- package/test/app.test.ts +587 -0
- package/test/object.test.ts +183 -0
- package/test/util.test.ts +470 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { registerObjectHelper, getConfigsHelper } from '../src/object';
|
|
2
|
+
import { ObjectConfig, MetadataRegistry } from '@objectql/types';
|
|
3
|
+
|
|
4
|
+
describe('Object Helper Functions', () => {
|
|
5
|
+
let metadata: MetadataRegistry;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
metadata = new MetadataRegistry();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe('registerObjectHelper', () => {
|
|
12
|
+
it('should register object with normalized fields', () => {
|
|
13
|
+
const object: ObjectConfig = {
|
|
14
|
+
name: 'todo',
|
|
15
|
+
fields: {
|
|
16
|
+
title: { type: 'text' },
|
|
17
|
+
completed: { type: 'boolean' }
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
registerObjectHelper(metadata, object);
|
|
22
|
+
|
|
23
|
+
const registered = metadata.get<ObjectConfig>('object', 'todo');
|
|
24
|
+
expect(registered).toBeDefined();
|
|
25
|
+
expect(registered?.name).toBe('todo');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should add name property to fields', () => {
|
|
29
|
+
const object: ObjectConfig = {
|
|
30
|
+
name: 'todo',
|
|
31
|
+
fields: {
|
|
32
|
+
title: { type: 'text' },
|
|
33
|
+
status: { type: 'select', options: ['active', 'done'] }
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
registerObjectHelper(metadata, object);
|
|
38
|
+
|
|
39
|
+
const registered = metadata.get<ObjectConfig>('object', 'todo');
|
|
40
|
+
expect(registered?.fields?.title.name).toBe('title');
|
|
41
|
+
expect(registered?.fields?.status.name).toBe('status');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should not override existing name property', () => {
|
|
45
|
+
const object: ObjectConfig = {
|
|
46
|
+
name: 'todo',
|
|
47
|
+
fields: {
|
|
48
|
+
title: { type: 'text', name: 'customTitle' }
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
registerObjectHelper(metadata, object);
|
|
53
|
+
|
|
54
|
+
const registered = metadata.get<ObjectConfig>('object', 'todo');
|
|
55
|
+
expect(registered?.fields?.title.name).toBe('customTitle');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should handle object without fields', () => {
|
|
59
|
+
const object: ObjectConfig = {
|
|
60
|
+
name: 'empty'
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
registerObjectHelper(metadata, object);
|
|
64
|
+
|
|
65
|
+
const registered = metadata.get<ObjectConfig>('object', 'empty');
|
|
66
|
+
expect(registered).toBeDefined();
|
|
67
|
+
expect(registered?.name).toBe('empty');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should register object with complex field configurations', () => {
|
|
71
|
+
const object: ObjectConfig = {
|
|
72
|
+
name: 'project',
|
|
73
|
+
fields: {
|
|
74
|
+
name: {
|
|
75
|
+
type: 'text',
|
|
76
|
+
required: true,
|
|
77
|
+
unique: true,
|
|
78
|
+
max_length: 100
|
|
79
|
+
},
|
|
80
|
+
owner: {
|
|
81
|
+
type: 'lookup',
|
|
82
|
+
reference_to: 'users'
|
|
83
|
+
},
|
|
84
|
+
tags: {
|
|
85
|
+
type: 'select',
|
|
86
|
+
multiple: true,
|
|
87
|
+
options: ['urgent', 'important']
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
registerObjectHelper(metadata, object);
|
|
93
|
+
|
|
94
|
+
const registered = metadata.get<ObjectConfig>('object', 'project');
|
|
95
|
+
expect(registered?.fields?.name.required).toBe(true);
|
|
96
|
+
expect(registered?.fields?.name.unique).toBe(true);
|
|
97
|
+
expect(registered?.fields?.owner.reference_to).toBe('users');
|
|
98
|
+
expect(registered?.fields?.tags.multiple).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('getConfigsHelper', () => {
|
|
103
|
+
it('should return empty object when no objects registered', () => {
|
|
104
|
+
const configs = getConfigsHelper(metadata);
|
|
105
|
+
expect(configs).toEqual({});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should return all registered objects', () => {
|
|
109
|
+
const todo: ObjectConfig = {
|
|
110
|
+
name: 'todo',
|
|
111
|
+
fields: { title: { type: 'text' } }
|
|
112
|
+
};
|
|
113
|
+
const project: ObjectConfig = {
|
|
114
|
+
name: 'project',
|
|
115
|
+
fields: { name: { type: 'text' } }
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
registerObjectHelper(metadata, todo);
|
|
119
|
+
registerObjectHelper(metadata, project);
|
|
120
|
+
|
|
121
|
+
const configs = getConfigsHelper(metadata);
|
|
122
|
+
expect(Object.keys(configs)).toHaveLength(2);
|
|
123
|
+
expect(configs.todo).toBeDefined();
|
|
124
|
+
expect(configs.project).toBeDefined();
|
|
125
|
+
expect(configs.todo.name).toBe('todo');
|
|
126
|
+
expect(configs.project.name).toBe('project');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should return configs as key-value pairs by object name', () => {
|
|
130
|
+
const objects: ObjectConfig[] = [
|
|
131
|
+
{ name: 'users', fields: { name: { type: 'text' } } },
|
|
132
|
+
{ name: 'tasks', fields: { title: { type: 'text' } } },
|
|
133
|
+
{ name: 'projects', fields: { name: { type: 'text' } } }
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
objects.forEach(obj => registerObjectHelper(metadata, obj));
|
|
137
|
+
|
|
138
|
+
const configs = getConfigsHelper(metadata);
|
|
139
|
+
expect(configs.users.name).toBe('users');
|
|
140
|
+
expect(configs.tasks.name).toBe('tasks');
|
|
141
|
+
expect(configs.projects.name).toBe('projects');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should reflect latest state after registration', () => {
|
|
145
|
+
let configs = getConfigsHelper(metadata);
|
|
146
|
+
expect(Object.keys(configs)).toHaveLength(0);
|
|
147
|
+
|
|
148
|
+
registerObjectHelper(metadata, {
|
|
149
|
+
name: 'todo',
|
|
150
|
+
fields: { title: { type: 'text' } }
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
configs = getConfigsHelper(metadata);
|
|
154
|
+
expect(Object.keys(configs)).toHaveLength(1);
|
|
155
|
+
|
|
156
|
+
registerObjectHelper(metadata, {
|
|
157
|
+
name: 'project',
|
|
158
|
+
fields: { name: { type: 'text' } }
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
configs = getConfigsHelper(metadata);
|
|
162
|
+
expect(Object.keys(configs)).toHaveLength(2);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should return configs after unregistration', () => {
|
|
166
|
+
registerObjectHelper(metadata, {
|
|
167
|
+
name: 'todo',
|
|
168
|
+
fields: { title: { type: 'text' } }
|
|
169
|
+
});
|
|
170
|
+
registerObjectHelper(metadata, {
|
|
171
|
+
name: 'project',
|
|
172
|
+
fields: { name: { type: 'text' } }
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
metadata.unregister('object', 'todo');
|
|
176
|
+
|
|
177
|
+
const configs = getConfigsHelper(metadata);
|
|
178
|
+
expect(Object.keys(configs)).toHaveLength(1);
|
|
179
|
+
expect(configs.todo).toBeUndefined();
|
|
180
|
+
expect(configs.project).toBeDefined();
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
});
|
|
@@ -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
|
+
});
|