@constructive-io/graphql-codegen 2.23.2 → 2.24.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.
- package/README.md +147 -2
- package/cli/codegen/babel-ast.d.ts +46 -0
- package/cli/codegen/babel-ast.js +145 -0
- package/cli/codegen/barrel.d.ts +7 -2
- package/cli/codegen/barrel.js +159 -97
- package/cli/codegen/client.js +61 -0
- package/cli/codegen/custom-mutations.d.ts +2 -12
- package/cli/codegen/custom-mutations.js +116 -124
- package/cli/codegen/custom-queries.d.ts +2 -10
- package/cli/codegen/custom-queries.js +246 -335
- package/cli/codegen/index.d.ts +3 -0
- package/cli/codegen/index.js +72 -3
- package/cli/codegen/invalidation.d.ts +20 -0
- package/cli/codegen/invalidation.js +327 -0
- package/cli/codegen/mutation-keys.d.ts +24 -0
- package/cli/codegen/mutation-keys.js +247 -0
- package/cli/codegen/mutations.d.ts +3 -19
- package/cli/codegen/mutations.js +372 -383
- package/cli/codegen/orm/barrel.d.ts +1 -1
- package/cli/codegen/orm/barrel.js +42 -10
- package/cli/codegen/orm/client-generator.d.ts +1 -19
- package/cli/codegen/orm/client-generator.js +108 -77
- package/cli/codegen/orm/custom-ops-generator.d.ts +1 -12
- package/cli/codegen/orm/custom-ops-generator.js +192 -235
- package/cli/codegen/orm/input-types-generator.d.ts +13 -1
- package/cli/codegen/orm/input-types-generator.js +403 -147
- package/cli/codegen/orm/model-generator.d.ts +1 -19
- package/cli/codegen/orm/model-generator.js +229 -234
- package/cli/codegen/queries.d.ts +3 -11
- package/cli/codegen/queries.js +582 -389
- package/cli/codegen/query-keys.d.ts +15 -0
- package/cli/codegen/query-keys.js +477 -0
- package/cli/codegen/scalars.js +1 -0
- package/cli/codegen/schema-types-generator.d.ts +15 -10
- package/cli/codegen/schema-types-generator.js +87 -175
- package/cli/codegen/type-resolver.d.ts +1 -30
- package/cli/codegen/type-resolver.js +0 -53
- package/cli/codegen/types.d.ts +1 -1
- package/cli/codegen/types.js +76 -21
- package/cli/commands/generate.js +1 -0
- package/cli/index.js +1 -0
- package/esm/cli/codegen/babel-ast.d.ts +46 -0
- package/esm/cli/codegen/babel-ast.js +97 -0
- package/esm/cli/codegen/barrel.d.ts +7 -2
- package/esm/cli/codegen/barrel.js +126 -97
- package/esm/cli/codegen/client.js +61 -0
- package/esm/cli/codegen/custom-mutations.d.ts +2 -12
- package/esm/cli/codegen/custom-mutations.js +83 -124
- package/esm/cli/codegen/custom-queries.d.ts +2 -10
- package/esm/cli/codegen/custom-queries.js +214 -336
- package/esm/cli/codegen/index.d.ts +3 -0
- package/esm/cli/codegen/index.js +68 -2
- package/esm/cli/codegen/invalidation.d.ts +20 -0
- package/esm/cli/codegen/invalidation.js +291 -0
- package/esm/cli/codegen/mutation-keys.d.ts +24 -0
- package/esm/cli/codegen/mutation-keys.js +211 -0
- package/esm/cli/codegen/mutations.d.ts +3 -19
- package/esm/cli/codegen/mutations.js +340 -384
- package/esm/cli/codegen/orm/barrel.d.ts +1 -1
- package/esm/cli/codegen/orm/barrel.js +10 -11
- package/esm/cli/codegen/orm/client-generator.d.ts +1 -19
- package/esm/cli/codegen/orm/client-generator.js +76 -78
- package/esm/cli/codegen/orm/custom-ops-generator.d.ts +1 -12
- package/esm/cli/codegen/orm/custom-ops-generator.js +160 -236
- package/esm/cli/codegen/orm/input-types-generator.d.ts +13 -1
- package/esm/cli/codegen/orm/input-types-generator.js +371 -148
- package/esm/cli/codegen/orm/model-generator.d.ts +1 -19
- package/esm/cli/codegen/orm/model-generator.js +197 -235
- package/esm/cli/codegen/queries.d.ts +3 -11
- package/esm/cli/codegen/queries.js +550 -390
- package/esm/cli/codegen/query-keys.d.ts +15 -0
- package/esm/cli/codegen/query-keys.js +441 -0
- package/esm/cli/codegen/scalars.js +1 -0
- package/esm/cli/codegen/schema-types-generator.d.ts +15 -10
- package/esm/cli/codegen/schema-types-generator.js +54 -175
- package/esm/cli/codegen/type-resolver.d.ts +1 -30
- package/esm/cli/codegen/type-resolver.js +0 -49
- package/esm/cli/codegen/types.d.ts +1 -1
- package/esm/cli/codegen/types.js +44 -22
- package/esm/cli/commands/generate.js +1 -0
- package/esm/cli/index.js +1 -0
- package/esm/types/config.d.ts +75 -0
- package/esm/types/config.js +19 -1
- package/package.json +6 -4
- package/types/config.d.ts +75 -0
- package/types/config.js +20 -2
- package/cli/codegen/ts-ast.d.ts +0 -124
- package/cli/codegen/ts-ast.js +0 -280
- package/esm/cli/codegen/ts-ast.d.ts +0 -124
- package/esm/cli/codegen/ts-ast.js +0 -260
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import * as t from '@babel/types';
|
|
2
|
+
import { generateCode, addLineComment } from '../babel-ast';
|
|
3
|
+
import { getTableNames, getFilterTypeName, getConditionTypeName, getOrderByTypeName, isRelationField, getGeneratedFileHeader, } from '../utils';
|
|
3
4
|
import { pluralize } from 'inflekt';
|
|
4
5
|
import { getTypeBaseName } from '../type-resolver';
|
|
5
6
|
import { scalarToTsType, scalarToFilterType } from '../scalars';
|
|
@@ -57,6 +58,86 @@ function typeRefToTs(typeRef) {
|
|
|
57
58
|
function isRequired(typeRef) {
|
|
58
59
|
return typeRef.kind === 'NON_NULL';
|
|
59
60
|
}
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// Babel AST Helper Functions
|
|
63
|
+
// ============================================================================
|
|
64
|
+
/**
|
|
65
|
+
* Parse a type string into a TSType node
|
|
66
|
+
*/
|
|
67
|
+
function parseTypeString(typeStr) {
|
|
68
|
+
// Handle union types like "string | null"
|
|
69
|
+
if (typeStr.includes(' | ')) {
|
|
70
|
+
const parts = typeStr.split(' | ').map((p) => p.trim());
|
|
71
|
+
return t.tsUnionType(parts.map((p) => parseTypeString(p)));
|
|
72
|
+
}
|
|
73
|
+
// Handle array types like "string[]"
|
|
74
|
+
if (typeStr.endsWith('[]')) {
|
|
75
|
+
const elementType = typeStr.slice(0, -2);
|
|
76
|
+
return t.tsArrayType(parseTypeString(elementType));
|
|
77
|
+
}
|
|
78
|
+
// Handle generic types like "Record<string, unknown>"
|
|
79
|
+
if (typeStr.includes('<')) {
|
|
80
|
+
const match = typeStr.match(/^([^<]+)<(.+)>$/);
|
|
81
|
+
if (match) {
|
|
82
|
+
const [, baseName, params] = match;
|
|
83
|
+
const typeParams = params.split(',').map((p) => parseTypeString(p.trim()));
|
|
84
|
+
return t.tsTypeReference(t.identifier(baseName), t.tsTypeParameterInstantiation(typeParams));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Handle primitive types
|
|
88
|
+
switch (typeStr) {
|
|
89
|
+
case 'string':
|
|
90
|
+
return t.tsStringKeyword();
|
|
91
|
+
case 'number':
|
|
92
|
+
return t.tsNumberKeyword();
|
|
93
|
+
case 'boolean':
|
|
94
|
+
return t.tsBooleanKeyword();
|
|
95
|
+
case 'null':
|
|
96
|
+
return t.tsNullKeyword();
|
|
97
|
+
case 'unknown':
|
|
98
|
+
return t.tsUnknownKeyword();
|
|
99
|
+
default:
|
|
100
|
+
return t.tsTypeReference(t.identifier(typeStr));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Create an interface property signature
|
|
105
|
+
*/
|
|
106
|
+
function createPropertySignature(name, typeStr, optional) {
|
|
107
|
+
const prop = t.tsPropertySignature(t.identifier(name), t.tsTypeAnnotation(parseTypeString(typeStr)));
|
|
108
|
+
prop.optional = optional;
|
|
109
|
+
return prop;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Create an exported interface declaration
|
|
113
|
+
*/
|
|
114
|
+
function createExportedInterface(name, properties) {
|
|
115
|
+
const props = properties.map((p) => createPropertySignature(p.name, p.type, p.optional));
|
|
116
|
+
const body = t.tsInterfaceBody(props);
|
|
117
|
+
const interfaceDecl = t.tsInterfaceDeclaration(t.identifier(name), null, null, body);
|
|
118
|
+
return t.exportNamedDeclaration(interfaceDecl);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Create an exported type alias declaration
|
|
122
|
+
*/
|
|
123
|
+
function createExportedTypeAlias(name, typeStr) {
|
|
124
|
+
const typeAlias = t.tsTypeAliasDeclaration(t.identifier(name), null, parseTypeString(typeStr));
|
|
125
|
+
return t.exportNamedDeclaration(typeAlias);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Create a union type from string literals
|
|
129
|
+
*/
|
|
130
|
+
function createStringLiteralUnion(values) {
|
|
131
|
+
return t.tsUnionType(values.map((v) => t.tsLiteralType(t.stringLiteral(v))));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Add a section comment to the first statement in an array
|
|
135
|
+
*/
|
|
136
|
+
function addSectionComment(statements, sectionName) {
|
|
137
|
+
if (statements.length > 0) {
|
|
138
|
+
addLineComment(statements[0], `============ ${sectionName} ============`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
60
141
|
/** Configuration for all scalar filter types - matches PostGraphile's generated filters */
|
|
61
142
|
const SCALAR_FILTER_CONFIGS = [
|
|
62
143
|
{
|
|
@@ -154,13 +235,15 @@ function buildScalarFilterProperties(config) {
|
|
|
154
235
|
return props;
|
|
155
236
|
}
|
|
156
237
|
/**
|
|
157
|
-
*
|
|
238
|
+
* Generate scalar filter type statements
|
|
158
239
|
*/
|
|
159
|
-
function
|
|
160
|
-
|
|
240
|
+
function generateScalarFilterTypes() {
|
|
241
|
+
const statements = [];
|
|
161
242
|
for (const config of SCALAR_FILTER_CONFIGS) {
|
|
162
|
-
|
|
243
|
+
statements.push(createExportedInterface(config.name, buildScalarFilterProperties(config)));
|
|
163
244
|
}
|
|
245
|
+
addSectionComment(statements, 'Scalar Filter Types');
|
|
246
|
+
return statements;
|
|
164
247
|
}
|
|
165
248
|
// ============================================================================
|
|
166
249
|
// Enum Types Collector
|
|
@@ -189,19 +272,24 @@ function collectEnumTypesFromTables(tables, typeRegistry) {
|
|
|
189
272
|
return enumTypes;
|
|
190
273
|
}
|
|
191
274
|
/**
|
|
192
|
-
*
|
|
275
|
+
* Generate enum type statements
|
|
193
276
|
*/
|
|
194
|
-
function
|
|
277
|
+
function generateEnumTypes(typeRegistry, enumTypeNames) {
|
|
195
278
|
if (enumTypeNames.size === 0)
|
|
196
|
-
return;
|
|
197
|
-
|
|
279
|
+
return [];
|
|
280
|
+
const statements = [];
|
|
198
281
|
for (const typeName of Array.from(enumTypeNames).sort()) {
|
|
199
282
|
const typeInfo = typeRegistry.get(typeName);
|
|
200
283
|
if (!typeInfo || typeInfo.kind !== 'ENUM' || !typeInfo.enumValues)
|
|
201
284
|
continue;
|
|
202
|
-
const
|
|
203
|
-
|
|
285
|
+
const unionType = createStringLiteralUnion(typeInfo.enumValues);
|
|
286
|
+
const typeAlias = t.tsTypeAliasDeclaration(t.identifier(typeName), null, unionType);
|
|
287
|
+
statements.push(t.exportNamedDeclaration(typeAlias));
|
|
204
288
|
}
|
|
289
|
+
if (statements.length > 0) {
|
|
290
|
+
addSectionComment(statements, 'Enum Types');
|
|
291
|
+
}
|
|
292
|
+
return statements;
|
|
205
293
|
}
|
|
206
294
|
// ============================================================================
|
|
207
295
|
// Entity Types Generator (AST-based)
|
|
@@ -226,40 +314,47 @@ function buildEntityProperties(table) {
|
|
|
226
314
|
return properties;
|
|
227
315
|
}
|
|
228
316
|
/**
|
|
229
|
-
*
|
|
230
|
-
*/
|
|
231
|
-
function addEntityType(sourceFile, table) {
|
|
232
|
-
const { typeName } = getTableNames(table);
|
|
233
|
-
sourceFile.addInterface(createInterface(typeName, buildEntityProperties(table)));
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* Add all entity types
|
|
317
|
+
* Generate entity type statements
|
|
237
318
|
*/
|
|
238
|
-
function
|
|
239
|
-
|
|
319
|
+
function generateEntityTypes(tables) {
|
|
320
|
+
const statements = [];
|
|
240
321
|
for (const table of tables) {
|
|
241
|
-
|
|
322
|
+
const { typeName } = getTableNames(table);
|
|
323
|
+
statements.push(createExportedInterface(typeName, buildEntityProperties(table)));
|
|
242
324
|
}
|
|
325
|
+
if (statements.length > 0) {
|
|
326
|
+
addSectionComment(statements, 'Entity Types');
|
|
327
|
+
}
|
|
328
|
+
return statements;
|
|
243
329
|
}
|
|
244
330
|
// ============================================================================
|
|
245
331
|
// Relation Helper Types Generator (AST-based)
|
|
246
332
|
// ============================================================================
|
|
247
333
|
/**
|
|
248
|
-
*
|
|
334
|
+
* Generate relation helper type statements (ConnectionResult, PageInfo)
|
|
249
335
|
*/
|
|
250
|
-
function
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
336
|
+
function generateRelationHelperTypes() {
|
|
337
|
+
const statements = [];
|
|
338
|
+
// ConnectionResult<T> interface with type parameter
|
|
339
|
+
const connectionResultProps = [
|
|
340
|
+
createPropertySignature('nodes', 'T[]', false),
|
|
341
|
+
createPropertySignature('totalCount', 'number', false),
|
|
342
|
+
createPropertySignature('pageInfo', 'PageInfo', false),
|
|
343
|
+
];
|
|
344
|
+
const connectionResultBody = t.tsInterfaceBody(connectionResultProps);
|
|
345
|
+
const connectionResultDecl = t.tsInterfaceDeclaration(t.identifier('ConnectionResult'), t.tsTypeParameterDeclaration([
|
|
346
|
+
t.tsTypeParameter(null, null, 'T'),
|
|
347
|
+
]), null, connectionResultBody);
|
|
348
|
+
statements.push(t.exportNamedDeclaration(connectionResultDecl));
|
|
349
|
+
// PageInfo interface
|
|
350
|
+
statements.push(createExportedInterface('PageInfo', [
|
|
258
351
|
{ name: 'hasNextPage', type: 'boolean', optional: false },
|
|
259
352
|
{ name: 'hasPreviousPage', type: 'boolean', optional: false },
|
|
260
353
|
{ name: 'startCursor', type: 'string | null', optional: true },
|
|
261
354
|
{ name: 'endCursor', type: 'string | null', optional: true },
|
|
262
355
|
]));
|
|
356
|
+
addSectionComment(statements, 'Relation Helper Types');
|
|
357
|
+
return statements;
|
|
263
358
|
}
|
|
264
359
|
// ============================================================================
|
|
265
360
|
// Entity Relation Types Generator (AST-based)
|
|
@@ -332,44 +427,65 @@ function buildEntityRelationProperties(table, tableByName) {
|
|
|
332
427
|
return properties;
|
|
333
428
|
}
|
|
334
429
|
/**
|
|
335
|
-
*
|
|
430
|
+
* Generate entity relation type statements
|
|
336
431
|
*/
|
|
337
|
-
function
|
|
338
|
-
|
|
432
|
+
function generateEntityRelationTypes(tables, tableByName) {
|
|
433
|
+
const statements = [];
|
|
339
434
|
for (const table of tables) {
|
|
340
435
|
const { typeName } = getTableNames(table);
|
|
341
|
-
|
|
436
|
+
statements.push(createExportedInterface(`${typeName}Relations`, buildEntityRelationProperties(table, tableByName)));
|
|
342
437
|
}
|
|
438
|
+
if (statements.length > 0) {
|
|
439
|
+
addSectionComment(statements, 'Entity Relation Types');
|
|
440
|
+
}
|
|
441
|
+
return statements;
|
|
343
442
|
}
|
|
344
443
|
/**
|
|
345
|
-
*
|
|
444
|
+
* Generate entity types with relations (intersection types)
|
|
346
445
|
*/
|
|
347
|
-
function
|
|
348
|
-
|
|
446
|
+
function generateEntityWithRelations(tables) {
|
|
447
|
+
const statements = [];
|
|
349
448
|
for (const table of tables) {
|
|
350
449
|
const { typeName } = getTableNames(table);
|
|
351
|
-
|
|
450
|
+
statements.push(createExportedTypeAlias(`${typeName}WithRelations`, `${typeName} & ${typeName}Relations`));
|
|
451
|
+
}
|
|
452
|
+
if (statements.length > 0) {
|
|
453
|
+
addSectionComment(statements, 'Entity Types With Relations');
|
|
352
454
|
}
|
|
455
|
+
return statements;
|
|
353
456
|
}
|
|
354
457
|
// ============================================================================
|
|
355
458
|
// Entity Select Types Generator (AST-based)
|
|
356
459
|
// ============================================================================
|
|
357
460
|
/**
|
|
358
|
-
* Build the
|
|
461
|
+
* Build the Select type as a TSTypeLiteral
|
|
359
462
|
*/
|
|
360
|
-
function
|
|
361
|
-
const
|
|
463
|
+
function buildSelectTypeLiteral(table, tableByName) {
|
|
464
|
+
const members = [];
|
|
362
465
|
// Add scalar fields
|
|
363
466
|
for (const field of table.fields) {
|
|
364
467
|
if (!isRelationField(field.name, table)) {
|
|
365
|
-
|
|
468
|
+
const prop = t.tsPropertySignature(t.identifier(field.name), t.tsTypeAnnotation(t.tsBooleanKeyword()));
|
|
469
|
+
prop.optional = true;
|
|
470
|
+
members.push(prop);
|
|
366
471
|
}
|
|
367
472
|
}
|
|
368
473
|
// Add belongsTo relations
|
|
369
474
|
for (const relation of table.relations.belongsTo) {
|
|
370
475
|
if (relation.fieldName) {
|
|
371
476
|
const relatedTypeName = getRelatedTypeName(relation.referencesTable, tableByName);
|
|
372
|
-
|
|
477
|
+
const prop = t.tsPropertySignature(t.identifier(relation.fieldName), t.tsTypeAnnotation(t.tsUnionType([
|
|
478
|
+
t.tsBooleanKeyword(),
|
|
479
|
+
t.tsTypeLiteral([
|
|
480
|
+
(() => {
|
|
481
|
+
const selectProp = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${relatedTypeName}Select`))));
|
|
482
|
+
selectProp.optional = true;
|
|
483
|
+
return selectProp;
|
|
484
|
+
})(),
|
|
485
|
+
]),
|
|
486
|
+
])));
|
|
487
|
+
prop.optional = true;
|
|
488
|
+
members.push(prop);
|
|
373
489
|
}
|
|
374
490
|
}
|
|
375
491
|
// Add hasMany relations
|
|
@@ -378,12 +494,33 @@ function buildSelectTypeBody(table, tableByName) {
|
|
|
378
494
|
const relatedTypeName = getRelatedTypeName(relation.referencedByTable, tableByName);
|
|
379
495
|
const filterName = getRelatedFilterName(relation.referencedByTable, tableByName);
|
|
380
496
|
const orderByName = getRelatedOrderByName(relation.referencedByTable, tableByName);
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
497
|
+
const prop = t.tsPropertySignature(t.identifier(relation.fieldName), t.tsTypeAnnotation(t.tsUnionType([
|
|
498
|
+
t.tsBooleanKeyword(),
|
|
499
|
+
t.tsTypeLiteral([
|
|
500
|
+
(() => {
|
|
501
|
+
const p = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${relatedTypeName}Select`))));
|
|
502
|
+
p.optional = true;
|
|
503
|
+
return p;
|
|
504
|
+
})(),
|
|
505
|
+
(() => {
|
|
506
|
+
const p = t.tsPropertySignature(t.identifier('first'), t.tsTypeAnnotation(t.tsNumberKeyword()));
|
|
507
|
+
p.optional = true;
|
|
508
|
+
return p;
|
|
509
|
+
})(),
|
|
510
|
+
(() => {
|
|
511
|
+
const p = t.tsPropertySignature(t.identifier('filter'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(filterName))));
|
|
512
|
+
p.optional = true;
|
|
513
|
+
return p;
|
|
514
|
+
})(),
|
|
515
|
+
(() => {
|
|
516
|
+
const p = t.tsPropertySignature(t.identifier('orderBy'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(orderByName)))));
|
|
517
|
+
p.optional = true;
|
|
518
|
+
return p;
|
|
519
|
+
})(),
|
|
520
|
+
]),
|
|
521
|
+
])));
|
|
522
|
+
prop.optional = true;
|
|
523
|
+
members.push(prop);
|
|
387
524
|
}
|
|
388
525
|
}
|
|
389
526
|
// Add manyToMany relations
|
|
@@ -392,33 +529,69 @@ function buildSelectTypeBody(table, tableByName) {
|
|
|
392
529
|
const relatedTypeName = getRelatedTypeName(relation.rightTable, tableByName);
|
|
393
530
|
const filterName = getRelatedFilterName(relation.rightTable, tableByName);
|
|
394
531
|
const orderByName = getRelatedOrderByName(relation.rightTable, tableByName);
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
532
|
+
const prop = t.tsPropertySignature(t.identifier(relation.fieldName), t.tsTypeAnnotation(t.tsUnionType([
|
|
533
|
+
t.tsBooleanKeyword(),
|
|
534
|
+
t.tsTypeLiteral([
|
|
535
|
+
(() => {
|
|
536
|
+
const p = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${relatedTypeName}Select`))));
|
|
537
|
+
p.optional = true;
|
|
538
|
+
return p;
|
|
539
|
+
})(),
|
|
540
|
+
(() => {
|
|
541
|
+
const p = t.tsPropertySignature(t.identifier('first'), t.tsTypeAnnotation(t.tsNumberKeyword()));
|
|
542
|
+
p.optional = true;
|
|
543
|
+
return p;
|
|
544
|
+
})(),
|
|
545
|
+
(() => {
|
|
546
|
+
const p = t.tsPropertySignature(t.identifier('filter'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(filterName))));
|
|
547
|
+
p.optional = true;
|
|
548
|
+
return p;
|
|
549
|
+
})(),
|
|
550
|
+
(() => {
|
|
551
|
+
const p = t.tsPropertySignature(t.identifier('orderBy'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(orderByName)))));
|
|
552
|
+
p.optional = true;
|
|
553
|
+
return p;
|
|
554
|
+
})(),
|
|
555
|
+
]),
|
|
556
|
+
])));
|
|
557
|
+
prop.optional = true;
|
|
558
|
+
members.push(prop);
|
|
401
559
|
}
|
|
402
560
|
}
|
|
403
561
|
// Add hasOne relations
|
|
404
562
|
for (const relation of table.relations.hasOne) {
|
|
405
563
|
if (relation.fieldName) {
|
|
406
564
|
const relatedTypeName = getRelatedTypeName(relation.referencedByTable, tableByName);
|
|
407
|
-
|
|
565
|
+
const prop = t.tsPropertySignature(t.identifier(relation.fieldName), t.tsTypeAnnotation(t.tsUnionType([
|
|
566
|
+
t.tsBooleanKeyword(),
|
|
567
|
+
t.tsTypeLiteral([
|
|
568
|
+
(() => {
|
|
569
|
+
const selectProp = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${relatedTypeName}Select`))));
|
|
570
|
+
selectProp.optional = true;
|
|
571
|
+
return selectProp;
|
|
572
|
+
})(),
|
|
573
|
+
]),
|
|
574
|
+
])));
|
|
575
|
+
prop.optional = true;
|
|
576
|
+
members.push(prop);
|
|
408
577
|
}
|
|
409
578
|
}
|
|
410
|
-
|
|
411
|
-
return lines.join('\n');
|
|
579
|
+
return t.tsTypeLiteral(members);
|
|
412
580
|
}
|
|
413
581
|
/**
|
|
414
|
-
*
|
|
582
|
+
* Generate entity Select type statements
|
|
415
583
|
*/
|
|
416
|
-
function
|
|
417
|
-
|
|
584
|
+
function generateEntitySelectTypes(tables, tableByName) {
|
|
585
|
+
const statements = [];
|
|
418
586
|
for (const table of tables) {
|
|
419
587
|
const { typeName } = getTableNames(table);
|
|
420
|
-
|
|
588
|
+
const typeAlias = t.tsTypeAliasDeclaration(t.identifier(`${typeName}Select`), null, buildSelectTypeLiteral(table, tableByName));
|
|
589
|
+
statements.push(t.exportNamedDeclaration(typeAlias));
|
|
590
|
+
}
|
|
591
|
+
if (statements.length > 0) {
|
|
592
|
+
addSectionComment(statements, 'Entity Select Types');
|
|
421
593
|
}
|
|
594
|
+
return statements;
|
|
422
595
|
}
|
|
423
596
|
// ============================================================================
|
|
424
597
|
// Table Filter Types Generator (AST-based)
|
|
@@ -449,14 +622,18 @@ function buildTableFilterProperties(table) {
|
|
|
449
622
|
return properties;
|
|
450
623
|
}
|
|
451
624
|
/**
|
|
452
|
-
*
|
|
625
|
+
* Generate table filter type statements
|
|
453
626
|
*/
|
|
454
|
-
function
|
|
455
|
-
|
|
627
|
+
function generateTableFilterTypes(tables) {
|
|
628
|
+
const statements = [];
|
|
456
629
|
for (const table of tables) {
|
|
457
630
|
const filterName = getFilterTypeName(table);
|
|
458
|
-
|
|
631
|
+
statements.push(createExportedInterface(filterName, buildTableFilterProperties(table)));
|
|
459
632
|
}
|
|
633
|
+
if (statements.length > 0) {
|
|
634
|
+
addSectionComment(statements, 'Table Filter Types');
|
|
635
|
+
}
|
|
636
|
+
return statements;
|
|
460
637
|
}
|
|
461
638
|
// ============================================================================
|
|
462
639
|
// Condition Types Generator (AST-based)
|
|
@@ -482,22 +659,26 @@ function buildTableConditionProperties(table) {
|
|
|
482
659
|
return properties;
|
|
483
660
|
}
|
|
484
661
|
/**
|
|
485
|
-
*
|
|
662
|
+
* Generate table condition type statements
|
|
486
663
|
*/
|
|
487
|
-
function
|
|
488
|
-
|
|
664
|
+
function generateTableConditionTypes(tables) {
|
|
665
|
+
const statements = [];
|
|
489
666
|
for (const table of tables) {
|
|
490
667
|
const conditionName = getConditionTypeName(table);
|
|
491
|
-
|
|
668
|
+
statements.push(createExportedInterface(conditionName, buildTableConditionProperties(table)));
|
|
669
|
+
}
|
|
670
|
+
if (statements.length > 0) {
|
|
671
|
+
addSectionComment(statements, 'Table Condition Types');
|
|
492
672
|
}
|
|
673
|
+
return statements;
|
|
493
674
|
}
|
|
494
675
|
// ============================================================================
|
|
495
676
|
// OrderBy Types Generator (AST-based)
|
|
496
677
|
// ============================================================================
|
|
497
678
|
/**
|
|
498
|
-
* Build OrderBy union type
|
|
679
|
+
* Build OrderBy union type values
|
|
499
680
|
*/
|
|
500
|
-
function
|
|
681
|
+
function buildOrderByValues(table) {
|
|
501
682
|
const values = ['PRIMARY_KEY_ASC', 'PRIMARY_KEY_DESC', 'NATURAL'];
|
|
502
683
|
for (const field of table.fields) {
|
|
503
684
|
if (isRelationField(field.name, table))
|
|
@@ -506,19 +687,24 @@ function buildOrderByUnion(table) {
|
|
|
506
687
|
values.push(`${upperSnake}_ASC`);
|
|
507
688
|
values.push(`${upperSnake}_DESC`);
|
|
508
689
|
}
|
|
509
|
-
return values
|
|
690
|
+
return values;
|
|
510
691
|
}
|
|
511
692
|
/**
|
|
512
|
-
*
|
|
513
|
-
* Uses inflection from table metadata for correct pluralization
|
|
693
|
+
* Generate OrderBy type statements
|
|
514
694
|
*/
|
|
515
|
-
function
|
|
516
|
-
|
|
695
|
+
function generateOrderByTypes(tables) {
|
|
696
|
+
const statements = [];
|
|
517
697
|
for (const table of tables) {
|
|
518
|
-
// Use getOrderByTypeName which respects table.inflection.orderByType
|
|
519
698
|
const enumName = getOrderByTypeName(table);
|
|
520
|
-
|
|
699
|
+
const values = buildOrderByValues(table);
|
|
700
|
+
const unionType = createStringLiteralUnion(values);
|
|
701
|
+
const typeAlias = t.tsTypeAliasDeclaration(t.identifier(enumName), null, unionType);
|
|
702
|
+
statements.push(t.exportNamedDeclaration(typeAlias));
|
|
703
|
+
}
|
|
704
|
+
if (statements.length > 0) {
|
|
705
|
+
addSectionComment(statements, 'OrderBy Types');
|
|
521
706
|
}
|
|
707
|
+
return statements;
|
|
522
708
|
}
|
|
523
709
|
// ============================================================================
|
|
524
710
|
// CRUD Input Types Generator (AST-based)
|
|
@@ -541,27 +727,30 @@ function buildCreateDataFields(table) {
|
|
|
541
727
|
return fields;
|
|
542
728
|
}
|
|
543
729
|
/**
|
|
544
|
-
*
|
|
545
|
-
*
|
|
546
|
-
* ts-morph doesn't handle nested object types in interface properties well,
|
|
547
|
-
* so we build this manually with pre-doubled indentation (4→2, 8→4) since
|
|
548
|
-
* getMinimalFormattedOutput halves all indentation.
|
|
730
|
+
* Build Create input interface as AST
|
|
549
731
|
*/
|
|
550
732
|
function buildCreateInputInterface(table) {
|
|
551
733
|
const { typeName, singularName } = getTableNames(table);
|
|
552
734
|
const fields = buildCreateDataFields(table);
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
735
|
+
// Build the nested object type for the entity data
|
|
736
|
+
const nestedProps = fields.map((field) => {
|
|
737
|
+
const prop = t.tsPropertySignature(t.identifier(field.name), t.tsTypeAnnotation(parseTypeString(field.type)));
|
|
738
|
+
prop.optional = field.optional;
|
|
739
|
+
return prop;
|
|
740
|
+
});
|
|
741
|
+
const nestedObjectType = t.tsTypeLiteral(nestedProps);
|
|
742
|
+
// Build the main interface properties
|
|
743
|
+
const mainProps = [
|
|
744
|
+
(() => {
|
|
745
|
+
const prop = t.tsPropertySignature(t.identifier('clientMutationId'), t.tsTypeAnnotation(t.tsStringKeyword()));
|
|
746
|
+
prop.optional = true;
|
|
747
|
+
return prop;
|
|
748
|
+
})(),
|
|
749
|
+
t.tsPropertySignature(t.identifier(singularName), t.tsTypeAnnotation(nestedObjectType)),
|
|
557
750
|
];
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
}
|
|
562
|
-
lines.push(' };');
|
|
563
|
-
lines.push('}');
|
|
564
|
-
return lines.join('\n');
|
|
751
|
+
const body = t.tsInterfaceBody(mainProps);
|
|
752
|
+
const interfaceDecl = t.tsInterfaceDeclaration(t.identifier(`Create${typeName}Input`), null, null, body);
|
|
753
|
+
return t.exportNamedDeclaration(interfaceDecl);
|
|
565
754
|
}
|
|
566
755
|
/**
|
|
567
756
|
* Build Patch type properties
|
|
@@ -584,35 +773,41 @@ function buildPatchProperties(table) {
|
|
|
584
773
|
return properties;
|
|
585
774
|
}
|
|
586
775
|
/**
|
|
587
|
-
*
|
|
776
|
+
* Generate CRUD input type statements for a table
|
|
588
777
|
*/
|
|
589
|
-
function
|
|
778
|
+
function generateCrudInputTypes(table) {
|
|
779
|
+
const statements = [];
|
|
590
780
|
const { typeName } = getTableNames(table);
|
|
591
781
|
const patchName = `${typeName}Patch`;
|
|
592
|
-
// Create input
|
|
593
|
-
|
|
782
|
+
// Create input
|
|
783
|
+
statements.push(buildCreateInputInterface(table));
|
|
594
784
|
// Patch interface
|
|
595
|
-
|
|
785
|
+
statements.push(createExportedInterface(patchName, buildPatchProperties(table)));
|
|
596
786
|
// Update input
|
|
597
|
-
|
|
787
|
+
statements.push(createExportedInterface(`Update${typeName}Input`, [
|
|
598
788
|
{ name: 'clientMutationId', type: 'string', optional: true },
|
|
599
789
|
{ name: 'id', type: 'string', optional: false },
|
|
600
790
|
{ name: 'patch', type: patchName, optional: false },
|
|
601
791
|
]));
|
|
602
792
|
// Delete input
|
|
603
|
-
|
|
793
|
+
statements.push(createExportedInterface(`Delete${typeName}Input`, [
|
|
604
794
|
{ name: 'clientMutationId', type: 'string', optional: true },
|
|
605
795
|
{ name: 'id', type: 'string', optional: false },
|
|
606
796
|
]));
|
|
797
|
+
return statements;
|
|
607
798
|
}
|
|
608
799
|
/**
|
|
609
|
-
*
|
|
800
|
+
* Generate all CRUD input type statements
|
|
610
801
|
*/
|
|
611
|
-
function
|
|
612
|
-
|
|
802
|
+
function generateAllCrudInputTypes(tables) {
|
|
803
|
+
const statements = [];
|
|
613
804
|
for (const table of tables) {
|
|
614
|
-
|
|
805
|
+
statements.push(...generateCrudInputTypes(table));
|
|
806
|
+
}
|
|
807
|
+
if (statements.length > 0) {
|
|
808
|
+
addSectionComment(statements, 'CRUD Input Types');
|
|
615
809
|
}
|
|
810
|
+
return statements;
|
|
616
811
|
}
|
|
617
812
|
// ============================================================================
|
|
618
813
|
// Custom Input Types Generator (AST-based)
|
|
@@ -640,7 +835,7 @@ export function collectInputTypeNames(operations) {
|
|
|
640
835
|
}
|
|
641
836
|
/**
|
|
642
837
|
* Build a set of exact table CRUD input type names to skip
|
|
643
|
-
* These are generated by
|
|
838
|
+
* These are generated by generateAllCrudInputTypes, so we don't need to regenerate them
|
|
644
839
|
*/
|
|
645
840
|
function buildTableCrudTypeNames(tables) {
|
|
646
841
|
const crudTypes = new Set();
|
|
@@ -655,10 +850,10 @@ function buildTableCrudTypeNames(tables) {
|
|
|
655
850
|
return crudTypes;
|
|
656
851
|
}
|
|
657
852
|
/**
|
|
658
|
-
*
|
|
853
|
+
* Generate custom input type statements from TypeRegistry
|
|
659
854
|
*/
|
|
660
|
-
function
|
|
661
|
-
|
|
855
|
+
function generateCustomInputTypes(typeRegistry, usedInputTypes, tableCrudTypes) {
|
|
856
|
+
const statements = [];
|
|
662
857
|
const generatedTypes = new Set();
|
|
663
858
|
const typesToGenerate = new Set(Array.from(usedInputTypes));
|
|
664
859
|
// Filter out types we've already generated (exact matches for table CRUD types only)
|
|
@@ -681,8 +876,11 @@ function addCustomInputTypes(sourceFile, typeRegistry, usedInputTypes, tableCrud
|
|
|
681
876
|
generatedTypes.add(typeName);
|
|
682
877
|
const typeInfo = typeRegistry.get(typeName);
|
|
683
878
|
if (!typeInfo) {
|
|
684
|
-
|
|
685
|
-
|
|
879
|
+
// Add comment for missing type
|
|
880
|
+
const commentStmt = t.emptyStatement();
|
|
881
|
+
addLineComment(commentStmt, ` Type '${typeName}' not found in schema`);
|
|
882
|
+
statements.push(commentStmt);
|
|
883
|
+
statements.push(createExportedTypeAlias(typeName, 'Record<string, unknown>'));
|
|
686
884
|
continue;
|
|
687
885
|
}
|
|
688
886
|
if (typeInfo.kind === 'INPUT_OBJECT' && typeInfo.inputFields) {
|
|
@@ -699,17 +897,25 @@ function addCustomInputTypes(sourceFile, typeRegistry, usedInputTypes, tableCrud
|
|
|
699
897
|
typesToGenerate.add(baseType);
|
|
700
898
|
}
|
|
701
899
|
}
|
|
702
|
-
|
|
900
|
+
statements.push(createExportedInterface(typeName, properties));
|
|
703
901
|
}
|
|
704
902
|
else if (typeInfo.kind === 'ENUM' && typeInfo.enumValues) {
|
|
705
|
-
const
|
|
706
|
-
|
|
903
|
+
const unionType = createStringLiteralUnion(typeInfo.enumValues);
|
|
904
|
+
const typeAlias = t.tsTypeAliasDeclaration(t.identifier(typeName), null, unionType);
|
|
905
|
+
statements.push(t.exportNamedDeclaration(typeAlias));
|
|
707
906
|
}
|
|
708
907
|
else {
|
|
709
|
-
|
|
710
|
-
|
|
908
|
+
// Add comment for unsupported type kind
|
|
909
|
+
const commentStmt = t.emptyStatement();
|
|
910
|
+
addLineComment(commentStmt, ` Type '${typeName}' is ${typeInfo.kind}`);
|
|
911
|
+
statements.push(commentStmt);
|
|
912
|
+
statements.push(createExportedTypeAlias(typeName, 'unknown'));
|
|
711
913
|
}
|
|
712
914
|
}
|
|
915
|
+
if (statements.length > 0) {
|
|
916
|
+
addSectionComment(statements, 'Custom Input Types (from schema)');
|
|
917
|
+
}
|
|
918
|
+
return statements;
|
|
713
919
|
}
|
|
714
920
|
// ============================================================================
|
|
715
921
|
// Payload/Return Types Generator (AST-based)
|
|
@@ -729,10 +935,10 @@ export function collectPayloadTypeNames(operations) {
|
|
|
729
935
|
return payloadTypes;
|
|
730
936
|
}
|
|
731
937
|
/**
|
|
732
|
-
*
|
|
938
|
+
* Generate payload/return type statements
|
|
733
939
|
*/
|
|
734
|
-
function
|
|
735
|
-
|
|
940
|
+
function generatePayloadTypes(typeRegistry, usedPayloadTypes, alreadyGeneratedTypes) {
|
|
941
|
+
const statements = [];
|
|
736
942
|
const generatedTypes = new Set(alreadyGeneratedTypes);
|
|
737
943
|
const typesToGenerate = new Set(Array.from(usedPayloadTypes));
|
|
738
944
|
const skipTypes = new Set([
|
|
@@ -790,63 +996,77 @@ function addPayloadTypes(sourceFile, typeRegistry, usedPayloadTypes, alreadyGene
|
|
|
790
996
|
}
|
|
791
997
|
}
|
|
792
998
|
}
|
|
793
|
-
|
|
794
|
-
// Build Select type
|
|
795
|
-
const
|
|
999
|
+
statements.push(createExportedInterface(typeName, interfaceProps));
|
|
1000
|
+
// Build Select type
|
|
1001
|
+
const selectMembers = [];
|
|
796
1002
|
for (const field of typeInfo.fields) {
|
|
797
1003
|
const baseType = getTypeBaseName(field.type);
|
|
798
1004
|
if (baseType === 'Query' || baseType === 'Mutation')
|
|
799
1005
|
continue;
|
|
800
1006
|
const nestedType = baseType ? typeRegistry.get(baseType) : null;
|
|
1007
|
+
let propType;
|
|
801
1008
|
if (nestedType?.kind === 'OBJECT') {
|
|
802
|
-
|
|
1009
|
+
propType = t.tsUnionType([
|
|
1010
|
+
t.tsBooleanKeyword(),
|
|
1011
|
+
t.tsTypeLiteral([
|
|
1012
|
+
(() => {
|
|
1013
|
+
const p = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${baseType}Select`))));
|
|
1014
|
+
p.optional = true;
|
|
1015
|
+
return p;
|
|
1016
|
+
})(),
|
|
1017
|
+
]),
|
|
1018
|
+
]);
|
|
803
1019
|
}
|
|
804
1020
|
else {
|
|
805
|
-
|
|
1021
|
+
propType = t.tsBooleanKeyword();
|
|
806
1022
|
}
|
|
1023
|
+
const prop = t.tsPropertySignature(t.identifier(field.name), t.tsTypeAnnotation(propType));
|
|
1024
|
+
prop.optional = true;
|
|
1025
|
+
selectMembers.push(prop);
|
|
807
1026
|
}
|
|
808
|
-
|
|
809
|
-
|
|
1027
|
+
const selectTypeAlias = t.tsTypeAliasDeclaration(t.identifier(`${typeName}Select`), null, t.tsTypeLiteral(selectMembers));
|
|
1028
|
+
statements.push(t.exportNamedDeclaration(selectTypeAlias));
|
|
1029
|
+
}
|
|
1030
|
+
if (statements.length > 0) {
|
|
1031
|
+
addSectionComment(statements, 'Payload/Return Types (for custom operations)');
|
|
810
1032
|
}
|
|
1033
|
+
return statements;
|
|
811
1034
|
}
|
|
812
1035
|
// ============================================================================
|
|
813
1036
|
// Main Generator (AST-based)
|
|
814
1037
|
// ============================================================================
|
|
815
1038
|
/**
|
|
816
|
-
* Generate comprehensive input-types.ts file using
|
|
1039
|
+
* Generate comprehensive input-types.ts file using Babel AST
|
|
817
1040
|
*/
|
|
818
1041
|
export function generateInputTypesFile(typeRegistry, usedInputTypes, tables, usedPayloadTypes) {
|
|
819
|
-
const
|
|
820
|
-
const sourceFile = createSourceFile(project, 'input-types.ts');
|
|
821
|
-
// Add file header
|
|
822
|
-
sourceFile.insertText(0, createFileHeader('GraphQL types for ORM client') + '\n');
|
|
1042
|
+
const statements = [];
|
|
823
1043
|
// 1. Scalar filter types
|
|
824
|
-
|
|
1044
|
+
statements.push(...generateScalarFilterTypes());
|
|
825
1045
|
// 2. Enum types used by table fields
|
|
826
1046
|
if (tables && tables.length > 0) {
|
|
827
1047
|
const enumTypes = collectEnumTypesFromTables(tables, typeRegistry);
|
|
828
|
-
|
|
1048
|
+
statements.push(...generateEnumTypes(typeRegistry, enumTypes));
|
|
829
1049
|
}
|
|
830
1050
|
// 3. Entity and relation types (if tables provided)
|
|
831
1051
|
if (tables && tables.length > 0) {
|
|
832
1052
|
const tableByName = new Map(tables.map((table) => [table.name, table]));
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
1053
|
+
statements.push(...generateEntityTypes(tables));
|
|
1054
|
+
statements.push(...generateRelationHelperTypes());
|
|
1055
|
+
statements.push(...generateEntityRelationTypes(tables, tableByName));
|
|
1056
|
+
statements.push(...generateEntityWithRelations(tables));
|
|
1057
|
+
statements.push(...generateEntitySelectTypes(tables, tableByName));
|
|
838
1058
|
// 4. Table filter types
|
|
839
|
-
|
|
1059
|
+
statements.push(...generateTableFilterTypes(tables));
|
|
840
1060
|
// 4b. Table condition types (simple equality filter)
|
|
841
|
-
|
|
1061
|
+
statements.push(...generateTableConditionTypes(tables));
|
|
842
1062
|
// 5. OrderBy types
|
|
843
|
-
|
|
1063
|
+
statements.push(...generateOrderByTypes(tables));
|
|
844
1064
|
// 6. CRUD input types
|
|
845
|
-
|
|
1065
|
+
statements.push(...generateAllCrudInputTypes(tables));
|
|
846
1066
|
}
|
|
847
1067
|
// 7. Custom input types from TypeRegistry
|
|
848
1068
|
const tableCrudTypes = tables ? buildTableCrudTypeNames(tables) : undefined;
|
|
849
|
-
|
|
1069
|
+
statements.push(...generateCustomInputTypes(typeRegistry, usedInputTypes, tableCrudTypes));
|
|
850
1070
|
// 8. Payload/return types for custom operations
|
|
851
1071
|
if (usedPayloadTypes && usedPayloadTypes.size > 0) {
|
|
852
1072
|
const alreadyGeneratedTypes = new Set();
|
|
@@ -856,10 +1076,13 @@ export function generateInputTypesFile(typeRegistry, usedInputTypes, tables, use
|
|
|
856
1076
|
alreadyGeneratedTypes.add(typeName);
|
|
857
1077
|
}
|
|
858
1078
|
}
|
|
859
|
-
|
|
1079
|
+
statements.push(...generatePayloadTypes(typeRegistry, usedPayloadTypes, alreadyGeneratedTypes));
|
|
860
1080
|
}
|
|
1081
|
+
// Generate code with file header
|
|
1082
|
+
const header = getGeneratedFileHeader('GraphQL types for ORM client');
|
|
1083
|
+
const code = generateCode(statements);
|
|
861
1084
|
return {
|
|
862
1085
|
fileName: 'input-types.ts',
|
|
863
|
-
content:
|
|
1086
|
+
content: header + '\n' + code,
|
|
864
1087
|
};
|
|
865
1088
|
}
|