@objectstack/objectql 4.0.3 → 4.0.5
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/dist/index.d.mts +500 -1111
- package/dist/index.d.ts +500 -1111
- package/dist/index.js +1364 -279
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1359 -279
- package/dist/index.mjs.map +1 -1
- package/package.json +32 -6
- package/.turbo/turbo-build.log +0 -22
- package/CHANGELOG.md +0 -711
- package/src/engine.test.ts +0 -599
- package/src/engine.ts +0 -1548
- package/src/index.ts +0 -41
- package/src/kernel-factory.ts +0 -48
- package/src/metadata-facade.ts +0 -96
- package/src/plugin.integration.test.ts +0 -995
- package/src/plugin.ts +0 -534
- package/src/protocol-data.test.ts +0 -245
- package/src/protocol-discovery.test.ts +0 -213
- package/src/protocol-feed.test.ts +0 -303
- package/src/protocol-meta.test.ts +0 -440
- package/src/protocol.ts +0 -1235
- package/src/registry.test.ts +0 -494
- package/src/registry.ts +0 -716
- package/src/util.test.ts +0 -226
- package/src/util.ts +0 -219
- package/tsconfig.json +0 -10
package/src/util.test.ts
DELETED
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
toTitleCase,
|
|
4
|
-
convertIntrospectedSchemaToObjects,
|
|
5
|
-
} from './util';
|
|
6
|
-
import type { IntrospectedSchema } from './util';
|
|
7
|
-
|
|
8
|
-
describe('toTitleCase', () => {
|
|
9
|
-
it('should convert snake_case to Title Case', () => {
|
|
10
|
-
expect(toTitleCase('first_name')).toBe('First Name');
|
|
11
|
-
expect(toTitleCase('project_task')).toBe('Project Task');
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('should capitalize single words', () => {
|
|
15
|
-
expect(toTitleCase('name')).toBe('Name');
|
|
16
|
-
expect(toTitleCase('status')).toBe('Status');
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('should handle multiple underscores', () => {
|
|
20
|
-
expect(toTitleCase('long_multi_word_name')).toBe('Long Multi Word Name');
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('should handle empty string', () => {
|
|
24
|
-
expect(toTitleCase('')).toBe('');
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
describe('convertIntrospectedSchemaToObjects', () => {
|
|
29
|
-
const sampleSchema: IntrospectedSchema = {
|
|
30
|
-
tables: {
|
|
31
|
-
users: {
|
|
32
|
-
name: 'users',
|
|
33
|
-
columns: [
|
|
34
|
-
{ name: 'id', type: 'integer', nullable: false, isPrimary: true },
|
|
35
|
-
{ name: 'name', type: 'varchar', nullable: false, maxLength: 255 },
|
|
36
|
-
{ name: 'email', type: 'varchar', nullable: false, isUnique: true, maxLength: 320 },
|
|
37
|
-
{ name: 'bio', type: 'text', nullable: true },
|
|
38
|
-
{ name: 'age', type: 'integer', nullable: true },
|
|
39
|
-
{ name: 'is_active', type: 'boolean', nullable: false, defaultValue: true },
|
|
40
|
-
{ name: 'created_at', type: 'timestamp', nullable: false },
|
|
41
|
-
{ name: 'updated_at', type: 'timestamp', nullable: true },
|
|
42
|
-
],
|
|
43
|
-
foreignKeys: [],
|
|
44
|
-
primaryKeys: ['id'],
|
|
45
|
-
},
|
|
46
|
-
posts: {
|
|
47
|
-
name: 'posts',
|
|
48
|
-
columns: [
|
|
49
|
-
{ name: 'id', type: 'integer', nullable: false, isPrimary: true },
|
|
50
|
-
{ name: 'title', type: 'varchar', nullable: false, maxLength: 500 },
|
|
51
|
-
{ name: 'body', type: 'text', nullable: true },
|
|
52
|
-
{ name: 'author_id', type: 'integer', nullable: false },
|
|
53
|
-
{ name: 'metadata', type: 'jsonb', nullable: true },
|
|
54
|
-
{ name: 'published_at', type: 'date', nullable: true },
|
|
55
|
-
{ name: 'created_at', type: 'timestamp', nullable: false },
|
|
56
|
-
{ name: 'updated_at', type: 'timestamp', nullable: true },
|
|
57
|
-
],
|
|
58
|
-
foreignKeys: [
|
|
59
|
-
{
|
|
60
|
-
columnName: 'author_id',
|
|
61
|
-
referencedTable: 'users',
|
|
62
|
-
referencedColumn: 'id',
|
|
63
|
-
constraintName: 'fk_posts_author',
|
|
64
|
-
},
|
|
65
|
-
],
|
|
66
|
-
primaryKeys: ['id'],
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
it('should convert all tables by default', () => {
|
|
72
|
-
const objects = convertIntrospectedSchemaToObjects(sampleSchema);
|
|
73
|
-
expect(objects).toHaveLength(2);
|
|
74
|
-
expect(objects.map((o) => o.name)).toEqual(['users', 'posts']);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('should skip system columns by default', () => {
|
|
78
|
-
const objects = convertIntrospectedSchemaToObjects(sampleSchema);
|
|
79
|
-
const users = objects.find((o) => o.name === 'users')!;
|
|
80
|
-
const fieldNames = Object.keys(users.fields);
|
|
81
|
-
expect(fieldNames).not.toContain('id');
|
|
82
|
-
expect(fieldNames).not.toContain('created_at');
|
|
83
|
-
expect(fieldNames).not.toContain('updated_at');
|
|
84
|
-
expect(fieldNames).toContain('name');
|
|
85
|
-
expect(fieldNames).toContain('email');
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('should include system columns when skipSystemColumns=false', () => {
|
|
89
|
-
const objects = convertIntrospectedSchemaToObjects(sampleSchema, {
|
|
90
|
-
skipSystemColumns: false,
|
|
91
|
-
});
|
|
92
|
-
const users = objects.find((o) => o.name === 'users')!;
|
|
93
|
-
const fieldNames = Object.keys(users.fields);
|
|
94
|
-
expect(fieldNames).toContain('id');
|
|
95
|
-
expect(fieldNames).toContain('created_at');
|
|
96
|
-
expect(fieldNames).toContain('updated_at');
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('should map varchar to text and text to textarea', () => {
|
|
100
|
-
const objects = convertIntrospectedSchemaToObjects(sampleSchema);
|
|
101
|
-
const users = objects.find((o) => o.name === 'users')!;
|
|
102
|
-
expect(users.fields.name.type).toBe('text');
|
|
103
|
-
expect(users.fields.bio.type).toBe('textarea');
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('should map integer to number', () => {
|
|
107
|
-
const objects = convertIntrospectedSchemaToObjects(sampleSchema);
|
|
108
|
-
const users = objects.find((o) => o.name === 'users')!;
|
|
109
|
-
expect(users.fields.age.type).toBe('number');
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('should map boolean to boolean', () => {
|
|
113
|
-
const objects = convertIntrospectedSchemaToObjects(sampleSchema);
|
|
114
|
-
const users = objects.find((o) => o.name === 'users')!;
|
|
115
|
-
expect(users.fields.is_active.type).toBe('boolean');
|
|
116
|
-
expect(users.fields.is_active.defaultValue).toBe(true);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it('should map jsonb to json', () => {
|
|
120
|
-
const objects = convertIntrospectedSchemaToObjects(sampleSchema);
|
|
121
|
-
const posts = objects.find((o) => o.name === 'posts')!;
|
|
122
|
-
expect(posts.fields.metadata.type).toBe('json');
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('should map date to date', () => {
|
|
126
|
-
const objects = convertIntrospectedSchemaToObjects(sampleSchema);
|
|
127
|
-
const posts = objects.find((o) => o.name === 'posts')!;
|
|
128
|
-
expect(posts.fields.published_at.type).toBe('date');
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it('should map foreign keys to lookup fields', () => {
|
|
132
|
-
const objects = convertIntrospectedSchemaToObjects(sampleSchema);
|
|
133
|
-
const posts = objects.find((o) => o.name === 'posts')!;
|
|
134
|
-
expect(posts.fields.author_id.type).toBe('lookup');
|
|
135
|
-
expect(posts.fields.author_id.reference).toBe('users');
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('should set unique constraint', () => {
|
|
139
|
-
const objects = convertIntrospectedSchemaToObjects(sampleSchema);
|
|
140
|
-
const users = objects.find((o) => o.name === 'users')!;
|
|
141
|
-
expect(users.fields.email.unique).toBe(true);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it('should set maxLength for text fields', () => {
|
|
145
|
-
const objects = convertIntrospectedSchemaToObjects(sampleSchema);
|
|
146
|
-
const users = objects.find((o) => o.name === 'users')!;
|
|
147
|
-
expect(users.fields.name.maxLength).toBe(255);
|
|
148
|
-
expect(users.fields.email.maxLength).toBe(320);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it('should set required based on nullable', () => {
|
|
152
|
-
const objects = convertIntrospectedSchemaToObjects(sampleSchema);
|
|
153
|
-
const users = objects.find((o) => o.name === 'users')!;
|
|
154
|
-
expect(users.fields.name.required).toBe(true);
|
|
155
|
-
expect(users.fields.bio.required).toBe(false);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('should generate labels from table/field names', () => {
|
|
159
|
-
const objects = convertIntrospectedSchemaToObjects(sampleSchema);
|
|
160
|
-
const users = objects.find((o) => o.name === 'users')!;
|
|
161
|
-
expect(users.label).toBe('Users');
|
|
162
|
-
expect(users.fields.is_active.label).toBe('Is Active');
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('should exclude tables when excludeTables is specified', () => {
|
|
166
|
-
const objects = convertIntrospectedSchemaToObjects(sampleSchema, {
|
|
167
|
-
excludeTables: ['posts'],
|
|
168
|
-
});
|
|
169
|
-
expect(objects).toHaveLength(1);
|
|
170
|
-
expect(objects[0].name).toBe('users');
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it('should include only specified tables when includeTables is specified', () => {
|
|
174
|
-
const objects = convertIntrospectedSchemaToObjects(sampleSchema, {
|
|
175
|
-
includeTables: ['posts'],
|
|
176
|
-
});
|
|
177
|
-
expect(objects).toHaveLength(1);
|
|
178
|
-
expect(objects[0].name).toBe('posts');
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it('should handle empty schema', () => {
|
|
182
|
-
const objects = convertIntrospectedSchemaToObjects({ tables: {} });
|
|
183
|
-
expect(objects).toHaveLength(0);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it('should handle numeric types (float, decimal, real)', () => {
|
|
187
|
-
const schema: IntrospectedSchema = {
|
|
188
|
-
tables: {
|
|
189
|
-
metrics: {
|
|
190
|
-
name: 'metrics',
|
|
191
|
-
columns: [
|
|
192
|
-
{ name: 'price', type: 'decimal', nullable: false },
|
|
193
|
-
{ name: 'weight', type: 'float', nullable: true },
|
|
194
|
-
{ name: 'score', type: 'real', nullable: true },
|
|
195
|
-
{ name: 'quantity', type: 'bigint', nullable: false },
|
|
196
|
-
],
|
|
197
|
-
foreignKeys: [],
|
|
198
|
-
primaryKeys: [],
|
|
199
|
-
},
|
|
200
|
-
},
|
|
201
|
-
};
|
|
202
|
-
const objects = convertIntrospectedSchemaToObjects(schema);
|
|
203
|
-
const metrics = objects[0];
|
|
204
|
-
expect(metrics.fields.price.type).toBe('number');
|
|
205
|
-
expect(metrics.fields.weight.type).toBe('number');
|
|
206
|
-
expect(metrics.fields.score.type).toBe('number');
|
|
207
|
-
expect(metrics.fields.quantity.type).toBe('number');
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
it('should handle time type', () => {
|
|
211
|
-
const schema: IntrospectedSchema = {
|
|
212
|
-
tables: {
|
|
213
|
-
schedule: {
|
|
214
|
-
name: 'schedule',
|
|
215
|
-
columns: [
|
|
216
|
-
{ name: 'start_time', type: 'time', nullable: false },
|
|
217
|
-
],
|
|
218
|
-
foreignKeys: [],
|
|
219
|
-
primaryKeys: [],
|
|
220
|
-
},
|
|
221
|
-
},
|
|
222
|
-
};
|
|
223
|
-
const objects = convertIntrospectedSchemaToObjects(schema);
|
|
224
|
-
expect(objects[0].fields.start_time.type).toBe('time');
|
|
225
|
-
});
|
|
226
|
-
});
|
package/src/util.ts
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
import type { ServiceObject } from '@objectstack/spec/data';
|
|
4
|
-
|
|
5
|
-
// ── Introspection Types ──────────────────────────────────────────────────────
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Column metadata from database introspection.
|
|
9
|
-
*/
|
|
10
|
-
export interface IntrospectedColumn {
|
|
11
|
-
/** Column name */
|
|
12
|
-
name: string;
|
|
13
|
-
/** Native database type (e.g., 'varchar', 'integer', 'timestamp') */
|
|
14
|
-
type: string;
|
|
15
|
-
/** Whether the column is nullable */
|
|
16
|
-
nullable: boolean;
|
|
17
|
-
/** Default value if any */
|
|
18
|
-
defaultValue?: unknown;
|
|
19
|
-
/** Whether this is a primary key */
|
|
20
|
-
isPrimary?: boolean;
|
|
21
|
-
/** Whether this column has a unique constraint */
|
|
22
|
-
isUnique?: boolean;
|
|
23
|
-
/** Maximum length for string types */
|
|
24
|
-
maxLength?: number;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Foreign key relationship metadata.
|
|
29
|
-
*/
|
|
30
|
-
export interface IntrospectedForeignKey {
|
|
31
|
-
/** Column name in the source table */
|
|
32
|
-
columnName: string;
|
|
33
|
-
/** Referenced table name */
|
|
34
|
-
referencedTable: string;
|
|
35
|
-
/** Referenced column name */
|
|
36
|
-
referencedColumn: string;
|
|
37
|
-
/** Constraint name */
|
|
38
|
-
constraintName?: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Table metadata from database introspection.
|
|
43
|
-
*/
|
|
44
|
-
export interface IntrospectedTable {
|
|
45
|
-
/** Table name */
|
|
46
|
-
name: string;
|
|
47
|
-
/** List of columns */
|
|
48
|
-
columns: IntrospectedColumn[];
|
|
49
|
-
/** List of foreign key relationships */
|
|
50
|
-
foreignKeys: IntrospectedForeignKey[];
|
|
51
|
-
/** Primary key columns */
|
|
52
|
-
primaryKeys: string[];
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Complete database schema introspection result.
|
|
57
|
-
*/
|
|
58
|
-
export interface IntrospectedSchema {
|
|
59
|
-
/** Map of table name to table metadata */
|
|
60
|
-
tables: Record<string, IntrospectedTable>;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// ── Utility Functions ────────────────────────────────────────────────────────
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Convert a snake_case or plain string to Title Case.
|
|
67
|
-
*
|
|
68
|
-
* @example
|
|
69
|
-
* toTitleCase('first_name') // => 'First Name'
|
|
70
|
-
* toTitleCase('project_task') // => 'Project Task'
|
|
71
|
-
*/
|
|
72
|
-
export function toTitleCase(str: string): string {
|
|
73
|
-
return str
|
|
74
|
-
.replace(/_/g, ' ')
|
|
75
|
-
.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Map a native database column type to an ObjectStack FieldType.
|
|
80
|
-
*/
|
|
81
|
-
function mapDatabaseTypeToFieldType(
|
|
82
|
-
dbType: string
|
|
83
|
-
): 'text' | 'textarea' | 'number' | 'boolean' | 'datetime' | 'date' | 'time' | 'json' {
|
|
84
|
-
const type = dbType.toLowerCase();
|
|
85
|
-
|
|
86
|
-
// Text types
|
|
87
|
-
if (type.includes('char') || type.includes('varchar') || type.includes('text')) {
|
|
88
|
-
if (type.includes('text')) return 'textarea';
|
|
89
|
-
return 'text';
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Numeric types
|
|
93
|
-
if (
|
|
94
|
-
type.includes('int') || type === 'integer' || type === 'bigint' || type === 'smallint'
|
|
95
|
-
) {
|
|
96
|
-
return 'number';
|
|
97
|
-
}
|
|
98
|
-
if (
|
|
99
|
-
type.includes('float') || type.includes('double') || type.includes('decimal') ||
|
|
100
|
-
type.includes('numeric') || type === 'real'
|
|
101
|
-
) {
|
|
102
|
-
return 'number';
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Boolean
|
|
106
|
-
if (type.includes('bool')) {
|
|
107
|
-
return 'boolean';
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Date / Time types
|
|
111
|
-
if (type.includes('timestamp') || type === 'datetime') {
|
|
112
|
-
return 'datetime';
|
|
113
|
-
}
|
|
114
|
-
if (type === 'date') {
|
|
115
|
-
return 'date';
|
|
116
|
-
}
|
|
117
|
-
if (type === 'time') {
|
|
118
|
-
return 'time';
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// JSON types
|
|
122
|
-
if (type === 'json' || type === 'jsonb') {
|
|
123
|
-
return 'json';
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Default to text
|
|
127
|
-
return 'text';
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Convert an introspected database schema to ObjectStack object definitions.
|
|
132
|
-
*
|
|
133
|
-
* This allows using existing database tables without manually defining metadata.
|
|
134
|
-
*
|
|
135
|
-
* @param introspectedSchema - The schema returned from driver.introspectSchema()
|
|
136
|
-
* @param options - Optional filtering / conversion settings
|
|
137
|
-
* @returns Array of ServiceObject definitions that can be registered with ObjectQL
|
|
138
|
-
*
|
|
139
|
-
* @example
|
|
140
|
-
* ```typescript
|
|
141
|
-
* const schema = await driver.introspectSchema();
|
|
142
|
-
* const objects = convertIntrospectedSchemaToObjects(schema);
|
|
143
|
-
* for (const obj of objects) {
|
|
144
|
-
* engine.registerObject(obj);
|
|
145
|
-
* }
|
|
146
|
-
* ```
|
|
147
|
-
*/
|
|
148
|
-
export function convertIntrospectedSchemaToObjects(
|
|
149
|
-
introspectedSchema: IntrospectedSchema,
|
|
150
|
-
options?: {
|
|
151
|
-
/** Tables to exclude from conversion */
|
|
152
|
-
excludeTables?: string[];
|
|
153
|
-
/** Tables to include (if specified, only these will be converted) */
|
|
154
|
-
includeTables?: string[];
|
|
155
|
-
/** Whether to skip system columns like id, created_at, updated_at (default: true) */
|
|
156
|
-
skipSystemColumns?: boolean;
|
|
157
|
-
}
|
|
158
|
-
): ServiceObject[] {
|
|
159
|
-
const objects: ServiceObject[] = [];
|
|
160
|
-
const excludeTables = options?.excludeTables || [];
|
|
161
|
-
const includeTables = options?.includeTables;
|
|
162
|
-
const skipSystemColumns = options?.skipSystemColumns !== false;
|
|
163
|
-
|
|
164
|
-
for (const [tableName, table] of Object.entries(introspectedSchema.tables)) {
|
|
165
|
-
if (excludeTables.includes(tableName)) continue;
|
|
166
|
-
if (includeTables && !includeTables.includes(tableName)) continue;
|
|
167
|
-
|
|
168
|
-
const fields: Record<string, any> = {};
|
|
169
|
-
|
|
170
|
-
for (const column of table.columns) {
|
|
171
|
-
// Skip system columns if requested
|
|
172
|
-
if (skipSystemColumns && ['id', 'created_at', 'updated_at'].includes(column.name)) {
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Check for foreign key → lookup field
|
|
177
|
-
const foreignKey = table.foreignKeys.find((fk) => fk.columnName === column.name);
|
|
178
|
-
|
|
179
|
-
if (foreignKey) {
|
|
180
|
-
fields[column.name] = {
|
|
181
|
-
name: column.name,
|
|
182
|
-
type: 'lookup' as const,
|
|
183
|
-
reference: foreignKey.referencedTable,
|
|
184
|
-
label: toTitleCase(column.name),
|
|
185
|
-
required: !column.nullable,
|
|
186
|
-
};
|
|
187
|
-
} else {
|
|
188
|
-
const fieldType = mapDatabaseTypeToFieldType(column.type);
|
|
189
|
-
|
|
190
|
-
const field: Record<string, any> = {
|
|
191
|
-
name: column.name,
|
|
192
|
-
type: fieldType,
|
|
193
|
-
label: toTitleCase(column.name),
|
|
194
|
-
required: !column.nullable,
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
if (column.isUnique) {
|
|
198
|
-
field.unique = true;
|
|
199
|
-
}
|
|
200
|
-
if (column.maxLength && (fieldType === 'text' || fieldType === 'textarea')) {
|
|
201
|
-
field.maxLength = column.maxLength;
|
|
202
|
-
}
|
|
203
|
-
if (column.defaultValue != null) {
|
|
204
|
-
field.defaultValue = column.defaultValue;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
fields[column.name] = field;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
objects.push({
|
|
212
|
-
name: tableName,
|
|
213
|
-
label: toTitleCase(tableName),
|
|
214
|
-
fields,
|
|
215
|
-
} as ServiceObject);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return objects;
|
|
219
|
-
}
|