@constructive-io/graphql-codegen 3.2.0 → 3.2.1
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 +25 -30
- package/cli/index.js +14 -12
- package/cli/shared.d.ts +2 -0
- package/cli/shared.js +19 -6
- package/esm/cli/index.js +15 -13
- package/esm/cli/shared.d.ts +2 -0
- package/esm/cli/shared.js +17 -5
- package/esm/index.d.ts +1 -1
- package/esm/index.js +1 -1
- package/index.d.ts +1 -1
- package/index.js +2 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -661,7 +661,7 @@ Filter patterns support wildcards:
|
|
|
661
661
|
|
|
662
662
|
The CLI provides a convenient way to run code generation from the command line.
|
|
663
663
|
|
|
664
|
-
### `graphql-
|
|
664
|
+
### `graphql-codegen`
|
|
665
665
|
|
|
666
666
|
Generate React Query hooks and/or ORM client from various sources.
|
|
667
667
|
|
|
@@ -682,62 +682,57 @@ Database Options (for pgpm modes):
|
|
|
682
682
|
Generator Options:
|
|
683
683
|
--react-query Generate React Query hooks
|
|
684
684
|
--orm Generate ORM client
|
|
685
|
-
-t, --target <name> Target name in config file
|
|
686
685
|
-o, --output <dir> Output directory
|
|
686
|
+
-t, --target <name> Target name (for multi-target configs)
|
|
687
687
|
-a, --authorization <token> Authorization header value
|
|
688
688
|
--browser-compatible Generate browser-compatible code (default: true)
|
|
689
689
|
Set to false for Node.js with localhost DNS fix
|
|
690
690
|
--skip-custom-operations Only generate table CRUD operations
|
|
691
691
|
--dry-run Preview without writing files
|
|
692
|
-
--keep-db Keep ephemeral database after generation (pgpm modes)
|
|
693
692
|
-v, --verbose Show detailed output
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
--poll-interval <ms> Polling interval in milliseconds (default: 5000)
|
|
698
|
-
--debounce <ms> Debounce delay in milliseconds (default: 500)
|
|
699
|
-
--touch <path> Touch file after regeneration
|
|
700
|
-
--no-clear Don't clear console on regeneration
|
|
693
|
+
--keep-db Keep ephemeral database after generation (pgpm modes)
|
|
694
|
+
-h, --help Show help message
|
|
695
|
+
--version Show version number
|
|
701
696
|
```
|
|
702
697
|
|
|
703
698
|
Examples:
|
|
704
699
|
|
|
705
700
|
```bash
|
|
706
701
|
# Generate React Query hooks from an endpoint
|
|
707
|
-
npx graphql-
|
|
702
|
+
npx @constructive-io/graphql-codegen --endpoint https://api.example.com/graphql --output ./generated --react-query
|
|
708
703
|
|
|
709
704
|
# Generate ORM client from an endpoint
|
|
710
|
-
npx graphql-
|
|
705
|
+
npx @constructive-io/graphql-codegen --endpoint https://api.example.com/graphql --output ./generated --orm
|
|
711
706
|
|
|
712
707
|
# Generate both React Query hooks and ORM client
|
|
713
|
-
npx graphql-
|
|
708
|
+
npx @constructive-io/graphql-codegen --endpoint https://api.example.com/graphql --output ./generated --react-query --orm
|
|
714
709
|
|
|
715
|
-
# Generate from
|
|
716
|
-
npx graphql-
|
|
710
|
+
# Generate from schema file
|
|
711
|
+
npx @constructive-io/graphql-codegen --schema-file ./schema.graphql --output ./generated --react-query
|
|
717
712
|
|
|
718
|
-
# Generate
|
|
719
|
-
npx graphql-
|
|
720
|
-
```
|
|
713
|
+
# Generate from database with schemas
|
|
714
|
+
npx @constructive-io/graphql-codegen --schemas public,app_public --output ./generated --react-query
|
|
721
715
|
|
|
722
|
-
|
|
716
|
+
# Generate from database with API names
|
|
717
|
+
npx @constructive-io/graphql-codegen --api-names my_api --output ./generated --orm
|
|
723
718
|
|
|
724
|
-
|
|
719
|
+
# Use config file
|
|
720
|
+
npx @constructive-io/graphql-codegen --config ./graphql-codegen.config.ts
|
|
725
721
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
-f, --format <format> Config format: ts, js, json (default: ts)
|
|
729
|
-
-o, --output <path> Output path for config file
|
|
722
|
+
# Generate specific target from multi-target config
|
|
723
|
+
npx @constructive-io/graphql-codegen --config ./graphql-codegen.config.ts --target admin
|
|
730
724
|
```
|
|
731
725
|
|
|
732
|
-
###
|
|
726
|
+
### Using with Constructive CLI
|
|
733
727
|
|
|
734
|
-
|
|
728
|
+
The `@constructive-io/cli` package includes the codegen command:
|
|
735
729
|
|
|
736
730
|
```bash
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
731
|
+
# Install Constructive CLI
|
|
732
|
+
npm install -g @constructive-io/cli
|
|
733
|
+
|
|
734
|
+
# Run codegen via cnc
|
|
735
|
+
cnc codegen --endpoint http://localhost:5555/graphql --output ./codegen --react-query
|
|
741
736
|
```
|
|
742
737
|
|
|
743
738
|
## Architecture
|
package/cli/index.js
CHANGED
|
@@ -94,22 +94,24 @@ const commands = async (argv, prompter, _options) => {
|
|
|
94
94
|
}
|
|
95
95
|
// No config file - prompt for options using shared questions
|
|
96
96
|
const answers = await prompter.prompt(argv, shared_1.codegenQuestions);
|
|
97
|
+
// Convert kebab-case CLI args to camelCase for internal use
|
|
98
|
+
const camelized = (0, shared_1.camelizeArgv)(answers);
|
|
97
99
|
// Build db config if schemas or apiNames provided
|
|
98
|
-
const db = (
|
|
99
|
-
schemas:
|
|
100
|
-
apiNames:
|
|
100
|
+
const db = (camelized.schemas || camelized.apiNames) ? {
|
|
101
|
+
schemas: camelized.schemas,
|
|
102
|
+
apiNames: camelized.apiNames,
|
|
101
103
|
} : undefined;
|
|
102
104
|
const result = await (0, generate_1.generate)({
|
|
103
|
-
endpoint:
|
|
104
|
-
schemaFile:
|
|
105
|
+
endpoint: camelized.endpoint,
|
|
106
|
+
schemaFile: camelized.schemaFile,
|
|
105
107
|
db,
|
|
106
|
-
output:
|
|
107
|
-
authorization:
|
|
108
|
-
reactQuery:
|
|
109
|
-
orm:
|
|
110
|
-
browserCompatible:
|
|
111
|
-
dryRun:
|
|
112
|
-
verbose:
|
|
108
|
+
output: camelized.output,
|
|
109
|
+
authorization: camelized.authorization,
|
|
110
|
+
reactQuery: camelized.reactQuery,
|
|
111
|
+
orm: camelized.orm,
|
|
112
|
+
browserCompatible: camelized.browserCompatible,
|
|
113
|
+
dryRun: camelized.dryRun,
|
|
114
|
+
verbose: camelized.verbose,
|
|
113
115
|
});
|
|
114
116
|
(0, shared_1.printResult)(result);
|
|
115
117
|
prompter.close();
|
package/cli/shared.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type { GenerateResult } from '../core/generate';
|
|
|
12
12
|
export declare const splitCommas: (input: string | undefined) => string[] | undefined;
|
|
13
13
|
/**
|
|
14
14
|
* Interface for codegen CLI answers
|
|
15
|
+
* CLI accepts kebab-case arguments, converted to camelCase for internal use
|
|
15
16
|
*/
|
|
16
17
|
export interface CodegenAnswers {
|
|
17
18
|
endpoint?: string;
|
|
@@ -34,3 +35,4 @@ export declare const codegenQuestions: Question[];
|
|
|
34
35
|
* Print the result of a generate operation
|
|
35
36
|
*/
|
|
36
37
|
export declare function printResult(result: GenerateResult): void;
|
|
38
|
+
export declare const camelizeArgv: (argv: Record<string, any>) => any;
|
package/cli/shared.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.codegenQuestions = exports.splitCommas = void 0;
|
|
3
|
+
exports.camelizeArgv = exports.codegenQuestions = exports.splitCommas = void 0;
|
|
4
4
|
exports.printResult = printResult;
|
|
5
|
+
const inflekt_1 = require("inflekt");
|
|
6
|
+
const transform_keys_1 = require("inflekt/transform-keys");
|
|
5
7
|
/**
|
|
6
8
|
* Sanitize function that splits comma-separated strings into arrays
|
|
7
9
|
*/
|
|
@@ -22,7 +24,7 @@ exports.codegenQuestions = [
|
|
|
22
24
|
required: false,
|
|
23
25
|
},
|
|
24
26
|
{
|
|
25
|
-
name: '
|
|
27
|
+
name: 'schema-file',
|
|
26
28
|
message: 'Path to GraphQL schema file',
|
|
27
29
|
type: 'text',
|
|
28
30
|
required: false,
|
|
@@ -43,14 +45,14 @@ exports.codegenQuestions = [
|
|
|
43
45
|
sanitize: exports.splitCommas,
|
|
44
46
|
},
|
|
45
47
|
{
|
|
46
|
-
name: '
|
|
48
|
+
name: 'api-names',
|
|
47
49
|
message: 'API names (comma-separated)',
|
|
48
50
|
type: 'text',
|
|
49
51
|
required: false,
|
|
50
52
|
sanitize: exports.splitCommas,
|
|
51
53
|
},
|
|
52
54
|
{
|
|
53
|
-
name: '
|
|
55
|
+
name: 'react-query',
|
|
54
56
|
message: 'Generate React Query hooks?',
|
|
55
57
|
type: 'confirm',
|
|
56
58
|
required: false,
|
|
@@ -66,7 +68,7 @@ exports.codegenQuestions = [
|
|
|
66
68
|
useDefault: true,
|
|
67
69
|
},
|
|
68
70
|
{
|
|
69
|
-
name: '
|
|
71
|
+
name: 'browser-compatible',
|
|
70
72
|
message: 'Generate browser-compatible code?',
|
|
71
73
|
type: 'confirm',
|
|
72
74
|
required: false,
|
|
@@ -80,7 +82,7 @@ exports.codegenQuestions = [
|
|
|
80
82
|
required: false,
|
|
81
83
|
},
|
|
82
84
|
{
|
|
83
|
-
name: '
|
|
85
|
+
name: 'dry-run',
|
|
84
86
|
message: 'Preview without writing files?',
|
|
85
87
|
type: 'confirm',
|
|
86
88
|
required: false,
|
|
@@ -112,3 +114,14 @@ function printResult(result) {
|
|
|
112
114
|
process.exit(1);
|
|
113
115
|
}
|
|
114
116
|
}
|
|
117
|
+
const isTopLevel = (_key, path) => path.length === 0;
|
|
118
|
+
const camelizeArgv = (argv) => (0, transform_keys_1.inflektTree)(argv, (key) => {
|
|
119
|
+
// inflection.camelize expects underscores, so replace hyphens first
|
|
120
|
+
const underscored = key.replace(/-/g, '_');
|
|
121
|
+
return (0, inflekt_1.camelize)(underscored, true);
|
|
122
|
+
}, {
|
|
123
|
+
skip: (key, path) => !isTopLevel(key, path) ||
|
|
124
|
+
key === '_' ||
|
|
125
|
+
key.startsWith('_')
|
|
126
|
+
});
|
|
127
|
+
exports.camelizeArgv = camelizeArgv;
|
package/esm/cli/index.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { CLI, getPackageJson } from 'inquirerer';
|
|
9
9
|
import { generate } from '../core/generate';
|
|
10
10
|
import { findConfigFile, loadConfigFile } from '../core/config';
|
|
11
|
-
import { codegenQuestions, printResult } from './shared';
|
|
11
|
+
import { camelizeArgv, codegenQuestions, printResult } from './shared';
|
|
12
12
|
const usage = `
|
|
13
13
|
graphql-codegen - GraphQL SDK generator for Constructive databases
|
|
14
14
|
|
|
@@ -91,22 +91,24 @@ export const commands = async (argv, prompter, _options) => {
|
|
|
91
91
|
}
|
|
92
92
|
// No config file - prompt for options using shared questions
|
|
93
93
|
const answers = await prompter.prompt(argv, codegenQuestions);
|
|
94
|
+
// Convert kebab-case CLI args to camelCase for internal use
|
|
95
|
+
const camelized = camelizeArgv(answers);
|
|
94
96
|
// Build db config if schemas or apiNames provided
|
|
95
|
-
const db = (
|
|
96
|
-
schemas:
|
|
97
|
-
apiNames:
|
|
97
|
+
const db = (camelized.schemas || camelized.apiNames) ? {
|
|
98
|
+
schemas: camelized.schemas,
|
|
99
|
+
apiNames: camelized.apiNames,
|
|
98
100
|
} : undefined;
|
|
99
101
|
const result = await generate({
|
|
100
|
-
endpoint:
|
|
101
|
-
schemaFile:
|
|
102
|
+
endpoint: camelized.endpoint,
|
|
103
|
+
schemaFile: camelized.schemaFile,
|
|
102
104
|
db,
|
|
103
|
-
output:
|
|
104
|
-
authorization:
|
|
105
|
-
reactQuery:
|
|
106
|
-
orm:
|
|
107
|
-
browserCompatible:
|
|
108
|
-
dryRun:
|
|
109
|
-
verbose:
|
|
105
|
+
output: camelized.output,
|
|
106
|
+
authorization: camelized.authorization,
|
|
107
|
+
reactQuery: camelized.reactQuery,
|
|
108
|
+
orm: camelized.orm,
|
|
109
|
+
browserCompatible: camelized.browserCompatible,
|
|
110
|
+
dryRun: camelized.dryRun,
|
|
111
|
+
verbose: camelized.verbose,
|
|
110
112
|
});
|
|
111
113
|
printResult(result);
|
|
112
114
|
prompter.close();
|
package/esm/cli/shared.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type { GenerateResult } from '../core/generate';
|
|
|
12
12
|
export declare const splitCommas: (input: string | undefined) => string[] | undefined;
|
|
13
13
|
/**
|
|
14
14
|
* Interface for codegen CLI answers
|
|
15
|
+
* CLI accepts kebab-case arguments, converted to camelCase for internal use
|
|
15
16
|
*/
|
|
16
17
|
export interface CodegenAnswers {
|
|
17
18
|
endpoint?: string;
|
|
@@ -34,3 +35,4 @@ export declare const codegenQuestions: Question[];
|
|
|
34
35
|
* Print the result of a generate operation
|
|
35
36
|
*/
|
|
36
37
|
export declare function printResult(result: GenerateResult): void;
|
|
38
|
+
export declare const camelizeArgv: (argv: Record<string, any>) => any;
|
package/esm/cli/shared.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { camelize } from 'inflekt';
|
|
2
|
+
import { inflektTree } from 'inflekt/transform-keys';
|
|
1
3
|
/**
|
|
2
4
|
* Sanitize function that splits comma-separated strings into arrays
|
|
3
5
|
*/
|
|
@@ -17,7 +19,7 @@ export const codegenQuestions = [
|
|
|
17
19
|
required: false,
|
|
18
20
|
},
|
|
19
21
|
{
|
|
20
|
-
name: '
|
|
22
|
+
name: 'schema-file',
|
|
21
23
|
message: 'Path to GraphQL schema file',
|
|
22
24
|
type: 'text',
|
|
23
25
|
required: false,
|
|
@@ -38,14 +40,14 @@ export const codegenQuestions = [
|
|
|
38
40
|
sanitize: splitCommas,
|
|
39
41
|
},
|
|
40
42
|
{
|
|
41
|
-
name: '
|
|
43
|
+
name: 'api-names',
|
|
42
44
|
message: 'API names (comma-separated)',
|
|
43
45
|
type: 'text',
|
|
44
46
|
required: false,
|
|
45
47
|
sanitize: splitCommas,
|
|
46
48
|
},
|
|
47
49
|
{
|
|
48
|
-
name: '
|
|
50
|
+
name: 'react-query',
|
|
49
51
|
message: 'Generate React Query hooks?',
|
|
50
52
|
type: 'confirm',
|
|
51
53
|
required: false,
|
|
@@ -61,7 +63,7 @@ export const codegenQuestions = [
|
|
|
61
63
|
useDefault: true,
|
|
62
64
|
},
|
|
63
65
|
{
|
|
64
|
-
name: '
|
|
66
|
+
name: 'browser-compatible',
|
|
65
67
|
message: 'Generate browser-compatible code?',
|
|
66
68
|
type: 'confirm',
|
|
67
69
|
required: false,
|
|
@@ -75,7 +77,7 @@ export const codegenQuestions = [
|
|
|
75
77
|
required: false,
|
|
76
78
|
},
|
|
77
79
|
{
|
|
78
|
-
name: '
|
|
80
|
+
name: 'dry-run',
|
|
79
81
|
message: 'Preview without writing files?',
|
|
80
82
|
type: 'confirm',
|
|
81
83
|
required: false,
|
|
@@ -107,3 +109,13 @@ export function printResult(result) {
|
|
|
107
109
|
process.exit(1);
|
|
108
110
|
}
|
|
109
111
|
}
|
|
112
|
+
const isTopLevel = (_key, path) => path.length === 0;
|
|
113
|
+
export const camelizeArgv = (argv) => inflektTree(argv, (key) => {
|
|
114
|
+
// inflection.camelize expects underscores, so replace hyphens first
|
|
115
|
+
const underscored = key.replace(/-/g, '_');
|
|
116
|
+
return camelize(underscored, true);
|
|
117
|
+
}, {
|
|
118
|
+
skip: (key, path) => !isTopLevel(key, path) ||
|
|
119
|
+
key === '_' ||
|
|
120
|
+
key.startsWith('_')
|
|
121
|
+
});
|
package/esm/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export { defineConfig } from './types/config';
|
|
|
13
13
|
export { generate } from './core/generate';
|
|
14
14
|
export type { GenerateOptions, GenerateResult } from './core/generate';
|
|
15
15
|
export { findConfigFile, loadConfigFile } from './core/config';
|
|
16
|
-
export { codegenQuestions, splitCommas, printResult } from './cli/shared';
|
|
16
|
+
export { codegenQuestions, splitCommas, printResult, camelizeArgv } from './cli/shared';
|
|
17
17
|
export type { CodegenAnswers } from './cli/shared';
|
|
18
18
|
export { buildSchemaFromDatabase, buildSchemaSDLFromDatabase, } from './core/database';
|
|
19
19
|
export type { BuildSchemaFromDatabaseOptions, BuildSchemaFromDatabaseResult, } from './core/database';
|
package/esm/index.js
CHANGED
|
@@ -20,6 +20,6 @@ export { generate } from './core/generate';
|
|
|
20
20
|
// Config utilities
|
|
21
21
|
export { findConfigFile, loadConfigFile } from './core/config';
|
|
22
22
|
// CLI shared utilities (for packages/cli to import)
|
|
23
|
-
export { codegenQuestions, splitCommas, printResult } from './cli/shared';
|
|
23
|
+
export { codegenQuestions, splitCommas, printResult, camelizeArgv } from './cli/shared';
|
|
24
24
|
// Database schema utilities (re-exported from core for convenience)
|
|
25
25
|
export { buildSchemaFromDatabase, buildSchemaSDLFromDatabase, } from './core/database';
|
package/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export { defineConfig } from './types/config';
|
|
|
13
13
|
export { generate } from './core/generate';
|
|
14
14
|
export type { GenerateOptions, GenerateResult } from './core/generate';
|
|
15
15
|
export { findConfigFile, loadConfigFile } from './core/config';
|
|
16
|
-
export { codegenQuestions, splitCommas, printResult } from './cli/shared';
|
|
16
|
+
export { codegenQuestions, splitCommas, printResult, camelizeArgv } from './cli/shared';
|
|
17
17
|
export type { CodegenAnswers } from './cli/shared';
|
|
18
18
|
export { buildSchemaFromDatabase, buildSchemaSDLFromDatabase, } from './core/database';
|
|
19
19
|
export type { BuildSchemaFromDatabaseOptions, BuildSchemaFromDatabaseResult, } from './core/database';
|
package/index.js
CHANGED
|
@@ -21,7 +21,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
21
21
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
22
22
|
};
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.buildSchemaSDLFromDatabase = exports.buildSchemaFromDatabase = exports.printResult = exports.splitCommas = exports.codegenQuestions = exports.loadConfigFile = exports.findConfigFile = exports.generate = exports.defineConfig = void 0;
|
|
24
|
+
exports.buildSchemaSDLFromDatabase = exports.buildSchemaFromDatabase = exports.camelizeArgv = exports.printResult = exports.splitCommas = exports.codegenQuestions = exports.loadConfigFile = exports.findConfigFile = exports.generate = exports.defineConfig = void 0;
|
|
25
25
|
// Core types
|
|
26
26
|
__exportStar(require("./types"), exports);
|
|
27
27
|
// Core query building
|
|
@@ -45,6 +45,7 @@ var shared_1 = require("./cli/shared");
|
|
|
45
45
|
Object.defineProperty(exports, "codegenQuestions", { enumerable: true, get: function () { return shared_1.codegenQuestions; } });
|
|
46
46
|
Object.defineProperty(exports, "splitCommas", { enumerable: true, get: function () { return shared_1.splitCommas; } });
|
|
47
47
|
Object.defineProperty(exports, "printResult", { enumerable: true, get: function () { return shared_1.printResult; } });
|
|
48
|
+
Object.defineProperty(exports, "camelizeArgv", { enumerable: true, get: function () { return shared_1.camelizeArgv; } });
|
|
48
49
|
// Database schema utilities (re-exported from core for convenience)
|
|
49
50
|
var database_1 = require("./core/database");
|
|
50
51
|
Object.defineProperty(exports, "buildSchemaFromDatabase", { enumerable: true, get: function () { return database_1.buildSchemaFromDatabase; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constructive-io/graphql-codegen",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.1",
|
|
4
4
|
"description": "GraphQL SDK generator for Constructive databases with React Query hooks",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"graphql",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"find-and-require-package-json": "^0.9.0",
|
|
66
66
|
"gql-ast": "^2.6.0",
|
|
67
67
|
"graphql": "15.10.1",
|
|
68
|
-
"inflekt": "^0.3.
|
|
68
|
+
"inflekt": "^0.3.1",
|
|
69
69
|
"inquirerer": "^4.4.0",
|
|
70
70
|
"jiti": "^2.6.1",
|
|
71
71
|
"oxfmt": "^0.26.0",
|
|
@@ -99,5 +99,5 @@
|
|
|
99
99
|
"tsx": "^4.21.0",
|
|
100
100
|
"typescript": "^5.9.3"
|
|
101
101
|
},
|
|
102
|
-
"gitHead": "
|
|
102
|
+
"gitHead": "013a736cd2b09d465697c58c1b807858129c16b5"
|
|
103
103
|
}
|