create-openibm 0.1.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 +154 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +156 -0
- package/dist/scaffold.d.ts +12 -0
- package/dist/scaffold.js +22 -0
- package/dist/templates/base.d.ts +2 -0
- package/dist/templates/base.js +58 -0
- package/dist/templates/basic.d.ts +11 -0
- package/dist/templates/basic.js +323 -0
- package/dist/templates/express.d.ts +2 -0
- package/dist/templates/express.js +199 -0
- package/dist/templates/nestjs.d.ts +2 -0
- package/dist/templates/nestjs.js +477 -0
- package/package.json +56 -0
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import { linkDevScriptMjs } from './basic.js';
|
|
2
|
+
export function nestjsFiles(a) {
|
|
3
|
+
const hasProgram = a.features.includes('program');
|
|
4
|
+
const hasTable = a.features.includes('table');
|
|
5
|
+
return {
|
|
6
|
+
'package.json': packageJson(a),
|
|
7
|
+
'tsconfig.json': tsconfig(),
|
|
8
|
+
'src/main.ts': mainTs(a),
|
|
9
|
+
'src/app.module.ts': appModuleTs(),
|
|
10
|
+
'src/ibmi/ibmi.module.ts': ibmiModuleTs(),
|
|
11
|
+
'src/ibmi/ibmi.service.ts': ibmiServiceTs(a),
|
|
12
|
+
'src/ibmi/ibmi.controller.ts': ibmiControllerTs(a, hasProgram, hasTable),
|
|
13
|
+
'src/ibmi/dto/ibmi-validators.ts': ibmiValidatorsNestTs(),
|
|
14
|
+
...(hasProgram ? { 'src/ibmi/dto/calculate.dto.ts': calculateDtoTs() } : {}),
|
|
15
|
+
'scripts/link-dev.mjs': linkDevScriptMjs(),
|
|
16
|
+
'README.md': readme(a, hasProgram, hasTable),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
// ── package.json ──────────────────────────────────────────────────────────────
|
|
20
|
+
function packageJson(a) {
|
|
21
|
+
return JSON.stringify({
|
|
22
|
+
name: a.projectName,
|
|
23
|
+
version: '0.1.0',
|
|
24
|
+
private: true,
|
|
25
|
+
type: 'module',
|
|
26
|
+
scripts: {
|
|
27
|
+
generate: 'openibm generate',
|
|
28
|
+
build: 'tsc --project tsconfig.json',
|
|
29
|
+
dev: 'node --env-file=.env --import tsx/esm src/main.ts',
|
|
30
|
+
start: 'node dist/main.js',
|
|
31
|
+
'link:dev': 'node scripts/link-dev.mjs',
|
|
32
|
+
},
|
|
33
|
+
dependencies: {
|
|
34
|
+
'@nestjs/common': '^10.0.0',
|
|
35
|
+
'@nestjs/core': '^10.0.0',
|
|
36
|
+
'@nestjs/platform-express': '^10.0.0',
|
|
37
|
+
'@nestjs/swagger': '^7.0.0',
|
|
38
|
+
'@openibm/driver': '^0.1.0',
|
|
39
|
+
'@openibm/types': '^0.1.0',
|
|
40
|
+
'class-transformer': '^0.5.0',
|
|
41
|
+
'class-validator': '^0.14.0',
|
|
42
|
+
morgan: '^1.10.0',
|
|
43
|
+
'reflect-metadata': '^0.1.14',
|
|
44
|
+
rxjs: '^7.8.0',
|
|
45
|
+
...(a.transport === 'odbc' ? { odbc: '^2.4.0' } : {}),
|
|
46
|
+
...(a.transport === 'ssh' ? { ssh2: '^1.15.0' } : {}),
|
|
47
|
+
},
|
|
48
|
+
devDependencies: {
|
|
49
|
+
'@openibm/client': '^0.1.0',
|
|
50
|
+
'@types/morgan': '^1.9.0',
|
|
51
|
+
'@types/node': '^22.0.0',
|
|
52
|
+
tsx: '^4.0.0',
|
|
53
|
+
typescript: '^5.7.0',
|
|
54
|
+
},
|
|
55
|
+
engines: { node: '>=20.0.0' },
|
|
56
|
+
}, null, 4) + '\n';
|
|
57
|
+
}
|
|
58
|
+
// ── tsconfig.json ─────────────────────────────────────────────────────────────
|
|
59
|
+
function tsconfig() {
|
|
60
|
+
return JSON.stringify({
|
|
61
|
+
compilerOptions: {
|
|
62
|
+
target: 'ES2022',
|
|
63
|
+
module: 'NodeNext',
|
|
64
|
+
moduleResolution: 'NodeNext',
|
|
65
|
+
outDir: './dist',
|
|
66
|
+
rootDir: './src',
|
|
67
|
+
strict: true,
|
|
68
|
+
esModuleInterop: true,
|
|
69
|
+
skipLibCheck: true,
|
|
70
|
+
experimentalDecorators: true,
|
|
71
|
+
emitDecoratorMetadata: true,
|
|
72
|
+
forceConsistentCasingInFileNames: true,
|
|
73
|
+
},
|
|
74
|
+
include: ['src'],
|
|
75
|
+
exclude: ['node_modules', 'dist'],
|
|
76
|
+
}, null, 4) + '\n';
|
|
77
|
+
}
|
|
78
|
+
// ── src/main.ts ───────────────────────────────────────────────────────────────
|
|
79
|
+
function mainTs(a) {
|
|
80
|
+
return [
|
|
81
|
+
`import 'reflect-metadata';`,
|
|
82
|
+
`import { NestFactory } from '@nestjs/core';`,
|
|
83
|
+
`import { ValidationPipe } from '@nestjs/common';`,
|
|
84
|
+
`import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';`,
|
|
85
|
+
`import morgan from 'morgan';`,
|
|
86
|
+
`import { AppModule } from './app.module.js';`,
|
|
87
|
+
``,
|
|
88
|
+
`async function bootstrap(): Promise<void> {`,
|
|
89
|
+
` const app = await NestFactory.create(AppModule);`,
|
|
90
|
+
``,
|
|
91
|
+
` // ── HTTP request logging ─────────────────────────────────────────────────`,
|
|
92
|
+
` app.use(morgan('dev'));`,
|
|
93
|
+
``,
|
|
94
|
+
` // ── Global validation pipe (class-validator + class-transformer) ─────────`,
|
|
95
|
+
` app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));`,
|
|
96
|
+
``,
|
|
97
|
+
` // ── Swagger / OpenAPI ────────────────────────────────────────────────────`,
|
|
98
|
+
` const config = new DocumentBuilder()`,
|
|
99
|
+
` .setTitle('${a.projectName}')`,
|
|
100
|
+
` .setDescription('IBM i REST API')`,
|
|
101
|
+
` .setVersion('1.0.0')`,
|
|
102
|
+
` .build();`,
|
|
103
|
+
` const document = SwaggerModule.createDocument(app, config);`,
|
|
104
|
+
` SwaggerModule.setup('api-docs', app, document);`,
|
|
105
|
+
``,
|
|
106
|
+
` const port = Number(process.env.PORT ?? 3000);`,
|
|
107
|
+
` await app.listen(port);`,
|
|
108
|
+
` console.log(\`Application: http://localhost:\${port}\`);`,
|
|
109
|
+
` console.log(\`Swagger UI: http://localhost:\${port}/api-docs\`);`,
|
|
110
|
+
`}`,
|
|
111
|
+
``,
|
|
112
|
+
`bootstrap().catch(console.error);`,
|
|
113
|
+
``,
|
|
114
|
+
].join('\n');
|
|
115
|
+
}
|
|
116
|
+
// ── src/app.module.ts ─────────────────────────────────────────────────────────
|
|
117
|
+
function appModuleTs() {
|
|
118
|
+
return [
|
|
119
|
+
`import { Module } from '@nestjs/common';`,
|
|
120
|
+
`import { IBMiModule } from './ibmi/ibmi.module.js';`,
|
|
121
|
+
``,
|
|
122
|
+
`@Module({`,
|
|
123
|
+
` imports: [IBMiModule],`,
|
|
124
|
+
`})`,
|
|
125
|
+
`export class AppModule {}`,
|
|
126
|
+
``,
|
|
127
|
+
].join('\n');
|
|
128
|
+
}
|
|
129
|
+
// ── src/ibmi/ibmi.module.ts ───────────────────────────────────────────────────
|
|
130
|
+
function ibmiModuleTs() {
|
|
131
|
+
return [
|
|
132
|
+
`import { Module } from '@nestjs/common';`,
|
|
133
|
+
`import { IBMiService } from './ibmi.service.js';`,
|
|
134
|
+
`import { IBMiController } from './ibmi.controller.js';`,
|
|
135
|
+
``,
|
|
136
|
+
`@Module({`,
|
|
137
|
+
` providers: [IBMiService],`,
|
|
138
|
+
` controllers: [IBMiController],`,
|
|
139
|
+
` exports: [IBMiService],`,
|
|
140
|
+
`})`,
|
|
141
|
+
`export class IBMiModule {}`,
|
|
142
|
+
``,
|
|
143
|
+
].join('\n');
|
|
144
|
+
}
|
|
145
|
+
// ── src/ibmi/ibmi.service.ts ──────────────────────────────────────────────────
|
|
146
|
+
function ibmiServiceTs(a) {
|
|
147
|
+
const httpOptions = (a.transport === 'http' || a.transport === 'https')
|
|
148
|
+
? [
|
|
149
|
+
` http: {`,
|
|
150
|
+
` port: Number(process.env.IBMI_PORT ?? '${a.transport === 'https' ? 47700 : 57700}'),`,
|
|
151
|
+
` database: process.env.IBMI_DATABASE ?? '*LOCAL',`,
|
|
152
|
+
` },`,
|
|
153
|
+
].join('\n')
|
|
154
|
+
: '';
|
|
155
|
+
const sshOptions = a.transport === 'ssh'
|
|
156
|
+
? ` ssh: { port: Number(process.env.IBMI_SSH_PORT ?? '22') },`
|
|
157
|
+
: '';
|
|
158
|
+
const odbcOptions = a.transport === 'odbc'
|
|
159
|
+
? ` odbc: { poolSize: 5 },`
|
|
160
|
+
: '';
|
|
161
|
+
const extras = [httpOptions, sshOptions, odbcOptions].filter(Boolean).join('\n');
|
|
162
|
+
return [
|
|
163
|
+
`import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';`,
|
|
164
|
+
`import { createClient } from '../generated/ibmi/index.js';`,
|
|
165
|
+
``,
|
|
166
|
+
`@Injectable()`,
|
|
167
|
+
`export class IBMiService implements OnModuleInit, OnModuleDestroy {`,
|
|
168
|
+
` readonly client = createClient({`,
|
|
169
|
+
` transport: (process.env.IBMI_TRANSPORT ?? '${a.transport}') as '${a.transport}',`,
|
|
170
|
+
` system: process.env.IBMI_SYSTEM ?? 'localhost',`,
|
|
171
|
+
` username: process.env.IBMI_USER ?? '',`,
|
|
172
|
+
` password: process.env.IBMI_PASS ?? '',`,
|
|
173
|
+
...(extras ? [extras] : []),
|
|
174
|
+
` });`,
|
|
175
|
+
``,
|
|
176
|
+
` async onModuleInit(): Promise<void> {`,
|
|
177
|
+
` await this.client.connect();`,
|
|
178
|
+
` }`,
|
|
179
|
+
``,
|
|
180
|
+
` async onModuleDestroy(): Promise<void> {`,
|
|
181
|
+
` await this.client.disconnect();`,
|
|
182
|
+
` }`,
|
|
183
|
+
`}`,
|
|
184
|
+
``,
|
|
185
|
+
].join('\n');
|
|
186
|
+
}
|
|
187
|
+
// ── src/ibmi/ibmi.controller.ts ───────────────────────────────────────────────
|
|
188
|
+
function ibmiControllerTs(a, hasProgram, hasTable) {
|
|
189
|
+
const nestImports = ['Controller', 'Get', 'Post', 'Param', 'Body'];
|
|
190
|
+
const swaggerImports = ['ApiOperation', 'ApiResponse', 'ApiTags'];
|
|
191
|
+
if (hasTable)
|
|
192
|
+
swaggerImports.push('ApiParam', 'ApiQuery');
|
|
193
|
+
if (hasProgram)
|
|
194
|
+
swaggerImports.push('ApiBody');
|
|
195
|
+
const imports = [
|
|
196
|
+
`import { ${nestImports.join(', ')} } from '@nestjs/common';`,
|
|
197
|
+
`import { ${swaggerImports.join(', ')} } from '@nestjs/swagger';`,
|
|
198
|
+
`import { IBMiService } from './ibmi.service.js';`,
|
|
199
|
+
...(hasProgram ? [`import { CalculateDto } from './dto/calculate.dto.js';`] : []),
|
|
200
|
+
];
|
|
201
|
+
const methods = [
|
|
202
|
+
` @Get('health')`,
|
|
203
|
+
` @ApiOperation({ summary: 'Connection status' })`,
|
|
204
|
+
` @ApiResponse({ status: 200, description: 'OK', schema: { example: { status: 'ok', connected: true } } })`,
|
|
205
|
+
` health() {`,
|
|
206
|
+
` return { status: 'ok', connected: this.ibmi.client.isConnected() };`,
|
|
207
|
+
` }`,
|
|
208
|
+
];
|
|
209
|
+
if (hasTable) {
|
|
210
|
+
methods.push(``, ` @Get('customers')`, ` @ApiOperation({ summary: 'List customers filtered by state' })`, ` @ApiQuery({ name: 'state', required: false, example: 'TX', description: 'State code — CHAR(2)' })`, ` @ApiResponse({ status: 200, description: 'Array of customer rows' })`, ` async findCustomers(@Param() params: Record<string, string>) {`, ` const state = params['state'] ?? 'TX';`, ` return this.ibmi.client.query.Customer.findMany({`, ` where: { state },`, ` orderBy: { customerId: 'asc' },`, ` take: 20,`, ` });`, ` }`, ``, ` @Get('customers/:id')`, ` @ApiOperation({ summary: 'Find customer by ID' })`, ` @ApiParam({ name: 'id', type: Number })`, ` @ApiResponse({ status: 200, description: 'Customer row' })`, ` @ApiResponse({ status: 404, description: 'Not found' })`, ` async findCustomer(@Param('id') id: string) {`, ` return this.ibmi.client.query.Customer.findFirst({`, ` where: { customerId: Number(id) },`, ` });`, ` }`);
|
|
211
|
+
}
|
|
212
|
+
if (hasProgram) {
|
|
213
|
+
methods.push(``, ` @Post('calculate')`, ` @ApiOperation({ summary: 'Call SimpleCalc IBM i program' })`, ` @ApiBody({ type: CalculateDto })`, ` @ApiResponse({ status: 200, description: 'Result', schema: { example: { input: 5, output: 500 } } })`, ` async calculate(@Body() body: CalculateDto) {`, ` const result = await this.ibmi.client.program.SimpleCalc({ input: body.input });`, ` return { input: body.input, output: result.output };`, ` }`);
|
|
214
|
+
}
|
|
215
|
+
void a;
|
|
216
|
+
return [
|
|
217
|
+
...imports,
|
|
218
|
+
``,
|
|
219
|
+
`@ApiTags('IBM i')`,
|
|
220
|
+
`@Controller()`,
|
|
221
|
+
`export class IBMiController {`,
|
|
222
|
+
` constructor(private readonly ibmi: IBMiService) {}`,
|
|
223
|
+
``,
|
|
224
|
+
...methods,
|
|
225
|
+
`}`,
|
|
226
|
+
``,
|
|
227
|
+
].join('\n');
|
|
228
|
+
}
|
|
229
|
+
// ── src/ibmi/dto/ibmi-validators.ts ──────────────────────────────────────────
|
|
230
|
+
function ibmiValidatorsNestTs() {
|
|
231
|
+
return [
|
|
232
|
+
`/**`,
|
|
233
|
+
` * IBM i data-type validators as class-validator decorators.`,
|
|
234
|
+
` * Use these in your NestJS DTOs alongside standard class-validator decorators.`,
|
|
235
|
+
` *`,
|
|
236
|
+
` * Usage:`,
|
|
237
|
+
` * import { IbmiChar, IbmiPackedDecimal, IbmiInt } from './ibmi-validators.js';`,
|
|
238
|
+
` *`,
|
|
239
|
+
` * export class MyDto {`,
|
|
240
|
+
` * @IbmiChar(8) name!: string;`,
|
|
241
|
+
` * @IbmiPackedDecimal(6, 2) balance!: number;`,
|
|
242
|
+
` * @IbmiInt() id!: number;`,
|
|
243
|
+
` * }`,
|
|
244
|
+
` */`,
|
|
245
|
+
`import { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator';`,
|
|
246
|
+
``,
|
|
247
|
+
`// ── String types ─────────────────────────────────────────────────────────────`,
|
|
248
|
+
``,
|
|
249
|
+
`/** CHAR(n) — validates that the string is at most n characters */`,
|
|
250
|
+
`export function IbmiChar(length: number, options?: ValidationOptions) {`,
|
|
251
|
+
` return (object: object, propertyName: string) => {`,
|
|
252
|
+
` registerDecorator({`,
|
|
253
|
+
` name: 'ibmiChar',`,
|
|
254
|
+
` target: (object as { constructor: Function }).constructor,`,
|
|
255
|
+
` propertyName,`,
|
|
256
|
+
` constraints: [length],`,
|
|
257
|
+
` options: { message: \`\${propertyName} must be at most \${length} chars (CHAR(\${length}))\`, ...options },`,
|
|
258
|
+
` validator: {`,
|
|
259
|
+
` validate(value: unknown, args: ValidationArguments) {`,
|
|
260
|
+
` return typeof value === 'string' && value.length <= (args.constraints[0] as number);`,
|
|
261
|
+
` },`,
|
|
262
|
+
` },`,
|
|
263
|
+
` });`,
|
|
264
|
+
` };`,
|
|
265
|
+
`}`,
|
|
266
|
+
``,
|
|
267
|
+
`/** VARCHAR(n) — validates that the string is at most n characters */`,
|
|
268
|
+
`export function IbmiVarChar(maxLength: number, options?: ValidationOptions) {`,
|
|
269
|
+
` return (object: object, propertyName: string) => {`,
|
|
270
|
+
` registerDecorator({`,
|
|
271
|
+
` name: 'ibmiVarChar',`,
|
|
272
|
+
` target: (object as { constructor: Function }).constructor,`,
|
|
273
|
+
` propertyName,`,
|
|
274
|
+
` constraints: [maxLength],`,
|
|
275
|
+
` options: { message: \`\${propertyName} must be at most \${maxLength} chars (VARCHAR(\${maxLength}))\`, ...options },`,
|
|
276
|
+
` validator: {`,
|
|
277
|
+
` validate(value: unknown, args: ValidationArguments) {`,
|
|
278
|
+
` return typeof value === 'string' && value.length <= (args.constraints[0] as number);`,
|
|
279
|
+
` },`,
|
|
280
|
+
` },`,
|
|
281
|
+
` });`,
|
|
282
|
+
` };`,
|
|
283
|
+
`}`,
|
|
284
|
+
``,
|
|
285
|
+
`// ── Decimal types ────────────────────────────────────────────────────────────`,
|
|
286
|
+
``,
|
|
287
|
+
`/**`,
|
|
288
|
+
` * DECIMAL(precision, scale) — packed decimal / zoned decimal.`,
|
|
289
|
+
` * e.g. @IbmiPackedDecimal(9, 2) allows -9999999.99 to 9999999.99`,
|
|
290
|
+
` */`,
|
|
291
|
+
`export function IbmiPackedDecimal(precision: number, scale: number, options?: ValidationOptions) {`,
|
|
292
|
+
` return (object: object, propertyName: string) => {`,
|
|
293
|
+
` registerDecorator({`,
|
|
294
|
+
` name: 'ibmiPackedDecimal',`,
|
|
295
|
+
` target: (object as { constructor: Function }).constructor,`,
|
|
296
|
+
` propertyName,`,
|
|
297
|
+
` constraints: [precision, scale],`,
|
|
298
|
+
` options: { message: \`\${propertyName} must fit DECIMAL(\${precision},\${scale})\`, ...options },`,
|
|
299
|
+
` validator: {`,
|
|
300
|
+
` validate(value: unknown, args: ValidationArguments) {`,
|
|
301
|
+
` const [p, s] = args.constraints as [number, number];`,
|
|
302
|
+
` if (typeof value !== 'number') return false;`,
|
|
303
|
+
` if (Math.abs(value) >= Math.pow(10, p - s)) return false;`,
|
|
304
|
+
` return Number.isInteger(Math.round(value * Math.pow(10, s)));`,
|
|
305
|
+
` },`,
|
|
306
|
+
` },`,
|
|
307
|
+
` });`,
|
|
308
|
+
` };`,
|
|
309
|
+
`}`,
|
|
310
|
+
``,
|
|
311
|
+
`/** NUMERIC(precision, scale) — alias for IbmiPackedDecimal */`,
|
|
312
|
+
`export const IbmiNumeric = IbmiPackedDecimal;`,
|
|
313
|
+
``,
|
|
314
|
+
`// ── Integer types ────────────────────────────────────────────────────────────`,
|
|
315
|
+
``,
|
|
316
|
+
`/** INTEGER — 4-byte signed integer (-2,147,483,648 to 2,147,483,647) */`,
|
|
317
|
+
`export function IbmiInt(options?: ValidationOptions) {`,
|
|
318
|
+
` return (object: object, propertyName: string) => {`,
|
|
319
|
+
` registerDecorator({`,
|
|
320
|
+
` name: 'ibmiInt',`,
|
|
321
|
+
` target: (object as { constructor: Function }).constructor,`,
|
|
322
|
+
` propertyName,`,
|
|
323
|
+
` constraints: [],`,
|
|
324
|
+
` options: { message: \`\${propertyName} must be a 4-byte signed integer\`, ...options },`,
|
|
325
|
+
` validator: {`,
|
|
326
|
+
` validate(value: unknown) {`,
|
|
327
|
+
` return typeof value === 'number'`,
|
|
328
|
+
` && Number.isInteger(value)`,
|
|
329
|
+
` && value >= -2_147_483_648`,
|
|
330
|
+
` && value <= 2_147_483_647;`,
|
|
331
|
+
` },`,
|
|
332
|
+
` },`,
|
|
333
|
+
` });`,
|
|
334
|
+
` };`,
|
|
335
|
+
`}`,
|
|
336
|
+
``,
|
|
337
|
+
`/** SMALLINT — 2-byte signed integer (-32,768 to 32,767) */`,
|
|
338
|
+
`export function IbmiSmallInt(options?: ValidationOptions) {`,
|
|
339
|
+
` return (object: object, propertyName: string) => {`,
|
|
340
|
+
` registerDecorator({`,
|
|
341
|
+
` name: 'ibmiSmallInt',`,
|
|
342
|
+
` target: (object as { constructor: Function }).constructor,`,
|
|
343
|
+
` propertyName,`,
|
|
344
|
+
` constraints: [],`,
|
|
345
|
+
` options: { message: \`\${propertyName} must be a 2-byte signed integer\`, ...options },`,
|
|
346
|
+
` validator: {`,
|
|
347
|
+
` validate(value: unknown) {`,
|
|
348
|
+
` return typeof value === 'number'`,
|
|
349
|
+
` && Number.isInteger(value)`,
|
|
350
|
+
` && value >= -32_768`,
|
|
351
|
+
` && value <= 32_767;`,
|
|
352
|
+
` },`,
|
|
353
|
+
` },`,
|
|
354
|
+
` });`,
|
|
355
|
+
` };`,
|
|
356
|
+
`}`,
|
|
357
|
+
``,
|
|
358
|
+
`// ── Date / time types ────────────────────────────────────────────────────────`,
|
|
359
|
+
``,
|
|
360
|
+
`/** DATE — validates ISO date string YYYY-MM-DD */`,
|
|
361
|
+
`export function IbmiDate(options?: ValidationOptions) {`,
|
|
362
|
+
` return (object: object, propertyName: string) => {`,
|
|
363
|
+
` registerDecorator({`,
|
|
364
|
+
` name: 'ibmiDate',`,
|
|
365
|
+
` target: (object as { constructor: Function }).constructor,`,
|
|
366
|
+
` propertyName,`,
|
|
367
|
+
` constraints: [],`,
|
|
368
|
+
` options: { message: \`\${propertyName} must be YYYY-MM-DD (IBM i DATE)\`, ...options },`,
|
|
369
|
+
` validator: { validate: (v: unknown) => typeof v === 'string' && /^\\d{4}-\\d{2}-\\d{2}$/.test(v) },`,
|
|
370
|
+
` });`,
|
|
371
|
+
` };`,
|
|
372
|
+
`}`,
|
|
373
|
+
``,
|
|
374
|
+
`/** TIMESTAMP — validates IBM i format YYYY-MM-DD-HH.MM.SS.ffffff */`,
|
|
375
|
+
`export function IbmiTimestamp(options?: ValidationOptions) {`,
|
|
376
|
+
` return (object: object, propertyName: string) => {`,
|
|
377
|
+
` registerDecorator({`,
|
|
378
|
+
` name: 'ibmiTimestamp',`,
|
|
379
|
+
` target: (object as { constructor: Function }).constructor,`,
|
|
380
|
+
` propertyName,`,
|
|
381
|
+
` constraints: [],`,
|
|
382
|
+
` options: { message: \`\${propertyName} must be YYYY-MM-DD-HH.MM.SS.ffffff (IBM i TIMESTAMP)\`, ...options },`,
|
|
383
|
+
` validator: {`,
|
|
384
|
+
` validate: (v: unknown) =>`,
|
|
385
|
+
` typeof v === 'string' && /^\\d{4}-\\d{2}-\\d{2}-\\d{2}\\.\\d{2}\\.\\d{2}\\.\\d{6}$/.test(v),`,
|
|
386
|
+
` },`,
|
|
387
|
+
` });`,
|
|
388
|
+
` };`,
|
|
389
|
+
`}`,
|
|
390
|
+
``,
|
|
391
|
+
].join('\n');
|
|
392
|
+
}
|
|
393
|
+
// ── src/ibmi/dto/calculate.dto.ts ─────────────────────────────────────────────
|
|
394
|
+
function calculateDtoTs() {
|
|
395
|
+
return [
|
|
396
|
+
`import { IsNumber } from 'class-validator';`,
|
|
397
|
+
`import { ApiProperty } from '@nestjs/swagger';`,
|
|
398
|
+
`import { IbmiInt } from './ibmi-validators.js';`,
|
|
399
|
+
``,
|
|
400
|
+
`export class CalculateDto {`,
|
|
401
|
+
` @ApiProperty({ example: 5, description: 'Input integer (IBM i INTEGER: -2147483648 to 2147483647)' })`,
|
|
402
|
+
` @IsNumber()`,
|
|
403
|
+
` @IbmiInt()`,
|
|
404
|
+
` input!: number;`,
|
|
405
|
+
`}`,
|
|
406
|
+
``,
|
|
407
|
+
].join('\n');
|
|
408
|
+
}
|
|
409
|
+
// ── README.md ─────────────────────────────────────────────────────────────────
|
|
410
|
+
function readme(a, hasProgram, hasTable) {
|
|
411
|
+
const endpoints = [
|
|
412
|
+
`| \`GET /health\` | Connection status |`,
|
|
413
|
+
`| \`GET /api-docs\` | Swagger UI |`,
|
|
414
|
+
...(hasTable ? [`| \`GET /customers\` | Query DB2 table (filtered by state) |`] : []),
|
|
415
|
+
...(hasTable ? [`| \`GET /customers/:id\`| Find customer by ID |`] : []),
|
|
416
|
+
...(hasProgram ? [`| \`POST /calculate\` | Call IBM i program (\`{ input: number }\`) |`] : []),
|
|
417
|
+
];
|
|
418
|
+
return [
|
|
419
|
+
`# ${a.projectName}`,
|
|
420
|
+
``,
|
|
421
|
+
`IBM i NestJS application generated by [create-openibm](https://www.npmjs.com/package/create-openibm).`,
|
|
422
|
+
``,
|
|
423
|
+
`## Setup`,
|
|
424
|
+
``,
|
|
425
|
+
`\`\`\`bash`,
|
|
426
|
+
`cp .env.example .env`,
|
|
427
|
+
`# fill in IBMI_SYSTEM, IBMI_USER, IBMI_PASS`,
|
|
428
|
+
`npm run generate`,
|
|
429
|
+
`\`\`\``,
|
|
430
|
+
``,
|
|
431
|
+
`## Development`,
|
|
432
|
+
``,
|
|
433
|
+
`\`\`\`bash`,
|
|
434
|
+
`npm run dev`,
|
|
435
|
+
`\`\`\``,
|
|
436
|
+
``,
|
|
437
|
+
`## Architecture`,
|
|
438
|
+
``,
|
|
439
|
+
`- **\`IBMiModule\`** — NestJS module that provides and exports \`IBMiService\``,
|
|
440
|
+
`- **\`IBMiService\`** — wraps the generated client; connects on \`onModuleInit\`, disconnects on \`onModuleDestroy\``,
|
|
441
|
+
`- **\`IBMiController\`** — exposes HTTP endpoints backed by the IBM i client`,
|
|
442
|
+
``,
|
|
443
|
+
`## Endpoints`,
|
|
444
|
+
``,
|
|
445
|
+
`| Route | Description |`,
|
|
446
|
+
`|---|---|`,
|
|
447
|
+
...endpoints,
|
|
448
|
+
``,
|
|
449
|
+
`## IBM i validators`,
|
|
450
|
+
``,
|
|
451
|
+
`\`src/ibmi/dto/ibmi-validators.ts\` provides class-validator decorators for IBM i data types:`,
|
|
452
|
+
``,
|
|
453
|
+
`\`\`\`ts`,
|
|
454
|
+
`import { IbmiChar, IbmiPackedDecimal, IbmiInt } from './ibmi-validators.js';`,
|
|
455
|
+
`import { IsString, IsNumber } from 'class-validator';`,
|
|
456
|
+
`import { ApiProperty } from '@nestjs/swagger';`,
|
|
457
|
+
``,
|
|
458
|
+
`export class CustomerDto {`,
|
|
459
|
+
` @ApiProperty({ example: 'Smith', description: 'CHAR(8)' })`,
|
|
460
|
+
` @IsString()`,
|
|
461
|
+
` @IbmiChar(8)`,
|
|
462
|
+
` lastName!: string;`,
|
|
463
|
+
``,
|
|
464
|
+
` @ApiProperty({ example: 0.00, description: 'DECIMAL(6,2)' })`,
|
|
465
|
+
` @IsNumber()`,
|
|
466
|
+
` @IbmiPackedDecimal(6, 2)`,
|
|
467
|
+
` balanceDue!: number;`,
|
|
468
|
+
``,
|
|
469
|
+
` @ApiProperty({ example: 1, description: 'INTEGER' })`,
|
|
470
|
+
` @IsNumber()`,
|
|
471
|
+
` @IbmiInt()`,
|
|
472
|
+
` customerId!: number;`,
|
|
473
|
+
`}`,
|
|
474
|
+
`\`\`\``,
|
|
475
|
+
``,
|
|
476
|
+
].join('\n');
|
|
477
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-openibm",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Create a new IBM i application — interactive scaffold with Express, NestJS, or plain Node.js",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://github.com/satyamlohiya/openibm",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/satyamlohiya/openibm.git",
|
|
10
|
+
"directory": "packages/create"
|
|
11
|
+
},
|
|
12
|
+
"author": "Satyam Lohiya",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"ibm-i",
|
|
15
|
+
"ibmi",
|
|
16
|
+
"as400",
|
|
17
|
+
"iseries",
|
|
18
|
+
"create-app",
|
|
19
|
+
"scaffold",
|
|
20
|
+
"cli",
|
|
21
|
+
"xmlservice"
|
|
22
|
+
],
|
|
23
|
+
"type": "module",
|
|
24
|
+
"bin": {
|
|
25
|
+
"create-openibm": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"sideEffects": false,
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc --project tsconfig.json",
|
|
35
|
+
"dev": "tsc --project tsconfig.json --watch",
|
|
36
|
+
"typecheck": "tsc --noEmit",
|
|
37
|
+
"clean": "rm -rf dist",
|
|
38
|
+
"try": "node scripts/try.mjs",
|
|
39
|
+
"link": "pnpm run build && pnpm link --global",
|
|
40
|
+
"unlink": "pnpm unlink --global create-openibm",
|
|
41
|
+
"prepublishOnly": "npm run build"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@clack/prompts": "^0.9.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^22.0.0",
|
|
48
|
+
"typescript": "^5.7.0"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=20.0.0"
|
|
52
|
+
},
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public"
|
|
55
|
+
}
|
|
56
|
+
}
|