pg-mvc-service 2.0.128 → 2.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/dist/Service.js CHANGED
@@ -41,7 +41,17 @@ class Service {
41
41
  description: 'サーバー内部エラー(予期せぬエラー)'
42
42
  }];
43
43
  }
44
- constructor(request, response) {
44
+ setResponse(status, data) {
45
+ var _a, _b;
46
+ if (this.module === 'express') {
47
+ (_a = this.res) === null || _a === void 0 ? void 0 : _a.status(status).json(data);
48
+ }
49
+ else {
50
+ (_b = this.c) === null || _b === void 0 ? void 0 : _b.json(data, status);
51
+ }
52
+ }
53
+ constructor(param1, param2) {
54
+ this.module = 'express';
45
55
  this.method = 'GET';
46
56
  this.endpoint = '';
47
57
  this.apiCode = '';
@@ -49,9 +59,9 @@ class Service {
49
59
  this.apiUserAvailable = '';
50
60
  this.request = new RequestType_1.RequestType();
51
61
  this.response = new ResponseType_1.ResponseType();
52
- this.isTest = process.env.NODE_ENV === 'test';
53
62
  this.tags = [];
54
63
  this.errorList = [];
64
+ // Context<{ Bindings: Bindings }>
55
65
  this.dbUser = process.env.DB_USER;
56
66
  this.dbHost = process.env.DB_HOST;
57
67
  this.dbName = process.env.DB_DATABASE;
@@ -59,12 +69,26 @@ class Service {
59
69
  this.dbPort = process.env.DB_PORT;
60
70
  this.dbIsSslConnect = process.env.DB_IS_SSL === 'true';
61
71
  this.isExecuteRollback = false;
62
- this.req = request;
63
- this.res = response;
72
+ if (param2 !== undefined) {
73
+ // Express の場合: (request, response)
74
+ this.module = 'express';
75
+ this.req = param1;
76
+ this.res = param2;
77
+ }
78
+ else {
79
+ // Hono の場合: (c)
80
+ this.module = 'hono';
81
+ this.c = param1;
82
+ }
64
83
  }
65
84
  inintialize() {
66
85
  return __awaiter(this, void 0, void 0, function* () {
67
- this.request.setRequest(this.req);
86
+ if (this.module === "express") {
87
+ yield this.request.setRequest(this.module, this.req);
88
+ }
89
+ else {
90
+ yield this.request.setRequest(this.module, this.c);
91
+ }
68
92
  yield this.checkMaintenance();
69
93
  yield this.middleware();
70
94
  });
@@ -99,7 +123,7 @@ class Service {
99
123
  return __awaiter(this, void 0, void 0, function* () { });
100
124
  }
101
125
  resSuccess() {
102
- this.res.status(200).json(this.response.ResponseData);
126
+ this.setResponse(200, this.response.ResponseData);
103
127
  }
104
128
  outputErrorLog(ex) {
105
129
  return __awaiter(this, void 0, void 0, function* () { });
@@ -110,61 +134,54 @@ class Service {
110
134
  console.error(ex);
111
135
  });
112
136
  if (ex instanceof Exception_1.AuthException) {
113
- this.res.status(401).json({
137
+ this.setResponse(401, {
114
138
  message: "Authentication expired. Please login again."
115
139
  });
116
140
  return;
117
141
  }
118
142
  else if (ex instanceof Exception_1.ForbiddenException) {
119
- this.res.status(403).json({
143
+ this.setResponse(403, {
120
144
  message: 'Forbidden error'
121
145
  });
122
146
  return;
123
147
  }
124
148
  else if (ex instanceof Exception_1.InputErrorException) {
125
- this.res.status(400).json({
149
+ this.setResponse(400, {
126
150
  errorCode: `${this.apiCode}-${ex.ErrorId}`,
127
151
  errorMessage: ex.message
128
152
  });
129
153
  return;
130
154
  }
131
155
  else if (ex instanceof Exception_1.DbConflictException) {
132
- this.res.status(409).json({
156
+ this.setResponse(409, {
133
157
  errorCode: `${this.apiCode}-${ex.ErrorId}`,
134
158
  errorMessage: ex.message
135
159
  });
136
160
  return;
137
161
  }
138
162
  else if (ex instanceof Exception_1.UnprocessableException) {
139
- this.res.status(422).json({
163
+ this.setResponse(422, {
140
164
  errorCode: `${this.apiCode}-${ex.ErrorId}`,
141
165
  errorMessage: ex.message
142
166
  });
143
167
  return;
144
168
  }
145
169
  else if (ex instanceof Exception_1.MaintenanceException) {
146
- this.res.status(503).json({
170
+ this.setResponse(503, {
147
171
  errorMessage: ex.message
148
172
  });
149
173
  return;
150
174
  }
151
175
  else if (ex instanceof Exception_1.NotFoundException) {
152
- this.res.status(404).json({
176
+ this.setResponse(404, {
153
177
  errorCode: `${this.apiCode}-${ex.ErrorId}`,
154
178
  errorMessage: ex.message
155
179
  });
156
180
  return;
157
181
  }
158
- if (this.isTest) {
159
- this.res.status(500).json({
160
- message: ex.stack
161
- });
162
- }
163
- else {
164
- this.res.status(500).json({
165
- message: 'Internal server error'
166
- });
167
- }
182
+ this.setResponse(500, {
183
+ message: 'Internal server error'
184
+ });
168
185
  return;
169
186
  }
170
187
  get Pool() {
@@ -388,32 +388,30 @@ class WhereExpression {
388
388
  japanese: true
389
389
  };
390
390
  }
391
- const objs = {};
391
+ let sql = expression;
392
+ // 1. ひらがな → カタカナ (TRANSLATEを使用)
393
+ // NORMALIZEはひらがなをカタカナにはしないので、これは残します
392
394
  if ((replaceOption === null || replaceOption === void 0 ? void 0 : replaceOption.hiraganaToKatakana) === true) {
393
- objs['あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをんゔがぎぐげござじずぜぞだぢづでどばびぶべぼぱぴぷぺぽぁぃぅぇぉゃゅょっー、。・「」゛゜']
394
- = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンヴガギグゲゴザジズゼゾダヂヅデドバビブベボパピプペポァィゥェォャュョッー、。・「」゛゜';
395
+ const from = 'あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをんゔがぎぐげござじずぜぞだぢづでどばびぶべぼぱぴぷぺぽぁぃぅぇぉゃゅょっー、。・「」゛゜';
396
+ const to = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンヴガギグゲゴザジズゼゾダヂヅデドバビブベボパピプペポァィゥェォャュョッー、。・「」゛゜';
397
+ sql = `TRANSLATE(${sql}, '${from}', '${to}')`;
395
398
  }
399
+ // 2. 漢数字 → 算用数字 (TRANSLATEを使用)
400
+ // これもNORMALIZEの管轄外なので残しますが、1つのTRANSLATEにまとめます(SQLが速くなります)
396
401
  if (((_a = replaceOption === null || replaceOption === void 0 ? void 0 : replaceOption.numeric) === null || _a === void 0 ? void 0 : _a.japanese) === true) {
397
- objs['零〇'] = '00';
398
- objs['一壱弌'] = '111';
399
- objs['二弐'] = '22';
400
- objs['三参'] = '33';
401
- objs['四肆'] = '44';
402
- objs['五伍'] = '55';
403
- objs['六陸'] = '66';
404
- objs['七漆'] = '77';
405
- objs['八捌'] = '88';
406
- objs['九玖'] = '99';
402
+ const from = '零〇一壱弌二弐三参四肆五伍六陸七漆八捌九玖';
403
+ const to = '001112233445566778899';
404
+ sql = `TRANSLATE(${sql}, '${from}', '${to}')`;
407
405
  }
406
+ // 3. 全角半角の統一 (ここを NORMALIZE に変更!)
407
+ // 元の halfToFull ブロックを削除し、この処理に置き換えます
408
408
  if ((replaceOption === null || replaceOption === void 0 ? void 0 : replaceOption.halfToFull) === true) {
409
- objs['0123456789'] = '0123456789';
410
- objs['アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンヴガギグゲゴザジズゼゾダヂヅデドバビブベボパピプペポァィゥェォャュョッー、。・「」゛゜']
411
- = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンヴガギグゲゴザジズゼゾダヂヅデドハハビブベボパピプペポァィゥェォャュョッー、。・「」 ゚';
412
- objs['ABCDEFGHIJKLMNOPQRSTUVWXYZ'] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
413
- objs['abcdefghijklmnopqrstuvwxyz'] = 'abcdefghijklmnopqrstuvwxyz';
409
+ // NORMALIZE(..., NFKC) は以下の処理を自動で行います
410
+ // - 全角英数字 → 半角英数字 (例: A → A)
411
+ // - 半角カナ → 全角カナ (例: ア → ア)
412
+ // - 濁点の結合 (例: カ + ゙ → ガ)
413
+ sql = `NORMALIZE(${sql}, NFKC)`;
414
414
  }
415
- let sql = expression;
416
- Object.keys(objs).forEach(key => sql = `TRANSLATE(${sql} ,'${key}','${objs[key]}')`);
417
415
  return sql;
418
416
  }
419
417
  }
@@ -1,4 +1,13 @@
1
1
  "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
2
11
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
13
  };
@@ -91,7 +100,6 @@ class RequestType extends ReqResType_1.default {
91
100
  }
92
101
  return this.headers;
93
102
  }
94
- get RemoteAddress() { return this.remoteAddress; }
95
103
  get Authorization() {
96
104
  var _a;
97
105
  const authorization = (_a = this.Headers['authorization']) !== null && _a !== void 0 ? _a : '';
@@ -100,23 +108,41 @@ class RequestType extends ReqResType_1.default {
100
108
  }
101
109
  return authorization.replace(/^Bearer\s/, '');
102
110
  }
103
- setRequest(request) {
104
- var _a, _b, _c;
105
- this.createBody(request);
106
- this.params = {};
107
- if (request.params !== undefined) {
108
- for (const [key, value] of Object.entries(request.params)) {
109
- const index = this.paramProperties.findIndex(property => property.key === key);
110
- if (index === -1) {
111
- throw new Error(`${key} is not set in paramProperties.`);
111
+ setRequest(module, request) {
112
+ return __awaiter(this, void 0, void 0, function* () {
113
+ var _a, _b, _c, _d;
114
+ yield this.createBody(module, request);
115
+ this.params = {};
116
+ if (module === 'express') {
117
+ const req = request;
118
+ if (req.params !== undefined) {
119
+ for (const [key, value] of Object.entries(req.params)) {
120
+ const index = this.paramProperties.findIndex((p) => p.key === key);
121
+ if (index === -1)
122
+ throw new Error(`${key} is not set in paramProperties.`);
123
+ const prop = this.paramProperties[index];
124
+ this.params[key] = this.convertValue(prop, value, [key, `(pathIndex: ${index})`], false);
125
+ }
112
126
  }
113
- const property = this.paramProperties[index];
114
- this.params[key] = this.convertValue(property, value, [key, `(pathIndex: ${index})`], false);
127
+ this.params = (_a = req.params) !== null && _a !== void 0 ? _a : {};
128
+ this.headers = (_b = req.headers) !== null && _b !== void 0 ? _b : {};
115
129
  }
116
- }
117
- this.params = (_a = request.params) !== null && _a !== void 0 ? _a : {};
118
- this.headers = (_b = request.headers) !== null && _b !== void 0 ? _b : {};
119
- this.remoteAddress = (_c = request.socket) === null || _c === void 0 ? void 0 : _c.remoteAddress;
130
+ else {
131
+ const c = request;
132
+ for (let index = 0; index < this.paramProperties.length; index++) {
133
+ const prop = this.paramProperties[index];
134
+ const value = (_d = (_c = c.req).param) === null || _d === void 0 ? void 0 : _d.call(_c, prop.key); // hono の param()
135
+ if (value !== undefined) {
136
+ this.params[prop.key] = this.convertValue(prop, value, [prop.key, `(pathIndex: ${index})`], false);
137
+ }
138
+ }
139
+ const headersObj = {};
140
+ c.req.raw.headers.forEach((v, k) => {
141
+ headersObj[k.toLowerCase()] = v;
142
+ });
143
+ this.headers = headersObj;
144
+ }
145
+ });
120
146
  }
121
147
  createErrorMessage(code, keys, value) {
122
148
  var _a, _b, _c, _d, _e;
@@ -240,179 +266,198 @@ class RequestType extends ReqResType_1.default {
240
266
  * @param {Object} body - Request body object, リクエストボディオブジェクト
241
267
  * @throws {InputErrorException} Thrown when the input value is invalid, 入力値が不正な場合にスローされます
242
268
  */
243
- createBody(request) {
244
- if (request.method === 'GET' || request.method === 'DELETE') {
245
- this.data = request.query;
246
- }
247
- else {
248
- this.data = request.body;
249
- }
250
- if (this.data === undefined) {
251
- this.data = {};
252
- }
253
- for (const key of Object.keys(this.properties)) {
254
- // NULLチェック
255
- if (key in this.data === false || this.data[key] === null || this.data[key] === "") {
256
- if (this.properties[key].type === 'array' && ['GET', 'DELETE'].includes(request.method)) {
257
- // GET,DELETEメソッドの場合、?array=1&array=2で配列となるが、
258
- // ?array=1のみで終わる場合は配列にならないため、直接配列にしている
259
- // この処理で空文字やnullが入った場合の対処をここで行う
260
- const itemProperty = this.properties[key].item;
261
- if (itemProperty.type.endsWith('?')) {
262
- const tempValue = this.data[key];
263
- this.data[key] = [];
264
- if (tempValue !== undefined) {
265
- if (itemProperty.type === 'string?') {
266
- this.data[key][0] = tempValue;
267
- }
268
- else {
269
- this.data[key][0] = null;
270
- }
271
- }
272
- continue;
273
- }
274
- else {
275
- this.throwInputError("REQUIRE_00", [key, 0], "");
276
- }
269
+ createBody(module, request) {
270
+ return __awaiter(this, void 0, void 0, function* () {
271
+ let method = '';
272
+ if (module === 'express') {
273
+ const req = request;
274
+ method = req.method;
275
+ if (method === 'GET' || method === 'DELETE') {
276
+ this.data = req.query;
277
277
  }
278
278
  else {
279
- if (this.properties[key].type.endsWith('?')) {
280
- this.changeBody([key], null);
281
- continue;
282
- }
283
- else {
284
- this.throwInputError("REQUIRE_01", [key], "");
285
- }
279
+ this.data = req.body;
286
280
  }
287
281
  }
288
- const value = this.data[key];
289
- switch (this.properties[key].type) {
290
- case 'object':
291
- case 'object?':
292
- if (typeof value === 'object') {
293
- this.setObject([key], value);
294
- }
295
- else {
296
- this.throwInputError("OBJECT_01", [key], value);
297
- }
298
- break;
299
- case 'array':
300
- case 'array?':
301
- if (Array.isArray(value)) {
302
- this.setArray([key], value);
282
+ else {
283
+ const c = request;
284
+ method = c.req.method;
285
+ if (method === 'GET' || method === 'DELETE') {
286
+ const url = new URL(c.req.url);
287
+ this.data = Object.fromEntries(url.searchParams.entries());
288
+ }
289
+ else {
290
+ // JSON を想定(form 等なら parseBody() を使う)
291
+ this.data = yield c.req.json();
292
+ }
293
+ }
294
+ if (this.data === undefined) {
295
+ this.data = {};
296
+ }
297
+ for (const key of Object.keys(this.properties)) {
298
+ // NULLチェック
299
+ if (key in this.data === false || this.data[key] === null || this.data[key] === "") {
300
+ if (this.properties[key].type === 'array' && ['GET', 'DELETE'].includes(method)) {
301
+ // GET,DELETEメソッドの場合、?array=1&array=2で配列となるが、
302
+ // ?array=1のみで終わる場合は配列にならないため、直接配列にしている
303
+ // この処理で空文字やnullが入った場合の対処をここで行う
304
+ const itemProperty = this.properties[key].item;
305
+ if (itemProperty.type.endsWith('?')) {
306
+ const tempValue = this.data[key];
307
+ this.data[key] = [];
308
+ if (tempValue !== undefined) {
309
+ if (itemProperty.type === 'string?') {
310
+ this.data[key][0] = tempValue;
311
+ }
312
+ else {
313
+ this.data[key][0] = null;
314
+ }
315
+ }
316
+ continue;
317
+ }
318
+ else {
319
+ this.throwInputError("REQUIRE_00", [key, 0], "");
320
+ }
303
321
  }
304
322
  else {
305
- if (request.method === 'GET' || request.method === 'DELETE') {
306
- // GET,DELETEメソッドの場合、?array=1&array=2で配列となるが、
307
- // ?array=1のみで終わる場合は配列にならないため、直接配列にしている
308
- const type = this.properties[key].item.type;
309
- if (type === 'object' || type === 'object?' || type === 'array' || type === 'array?' || type === 'map' || type === 'map?') {
310
- throw new Error("GETまたはDELETEメソッドでは配列型にobject, array, mapを使用することはできません。");
311
- }
312
- if (type === 'enum' || type === 'enum?') {
313
- const tempProp = {
314
- type: type,
315
- description: this.properties[key].item.description,
316
- enumType: this.properties[key].item.enumType,
317
- enums: this.properties[key].item.enums,
318
- };
319
- this.data[key] = [this.convertValue(tempProp, value, [key, 0], true)];
320
- }
321
- else if (type === 'string' || type === 'string?') {
322
- const tempProp = {
323
- type: type,
324
- description: this.properties[key].item.description,
325
- maxLength: this.properties[key].item.maxLength,
326
- regExp: this.properties[key].item.regExp
327
- };
328
- this.data[key] = [this.convertValue(tempProp, value, [key, 0], true)];
329
- }
330
- else if (type === 'number' || type === 'number?') {
331
- const tempProp = {
332
- type: type,
333
- description: this.properties[key].item.description,
334
- max: this.properties[key].item.max,
335
- min: this.properties[key].item.min,
336
- };
337
- this.data[key] = [this.convertValue(tempProp, value, [key, 0], true)];
338
- }
339
- else {
340
- const tempProp = {
341
- type: type,
342
- description: this.properties[key].item.description
343
- };
344
- this.data[key] = [this.convertValue(tempProp, value, [key, 0], true)];
345
- }
323
+ if (this.properties[key].type.endsWith('?')) {
324
+ this.changeBody([key], null);
325
+ continue;
346
326
  }
347
327
  else {
348
- this.throwInputError("ARRAY_01", [key], value);
328
+ this.throwInputError("REQUIRE_01", [key], "");
349
329
  }
350
330
  }
351
- break;
352
- case 'map':
353
- case 'map?':
354
- // tODO : ここは共通化したい
355
- const mapData = {};
356
- for (const [mapKey, mapValue] of Object.entries(value)) {
357
- switch (this.properties[key].mapType) {
358
- case 'number':
359
- if (this.isNumber(mapValue) === false) {
360
- this.throwInputError("MAP_01", [key], value);
331
+ }
332
+ const value = this.data[key];
333
+ switch (this.properties[key].type) {
334
+ case 'object':
335
+ case 'object?':
336
+ if (typeof value === 'object') {
337
+ this.setObject([key], value);
338
+ }
339
+ else {
340
+ this.throwInputError("OBJECT_01", [key], value);
341
+ }
342
+ break;
343
+ case 'array':
344
+ case 'array?':
345
+ if (Array.isArray(value)) {
346
+ this.setArray([key], value);
347
+ }
348
+ else {
349
+ if (method === 'GET' || method === 'DELETE') {
350
+ // GET,DELETEメソッドの場合、?array=1&array=2で配列となるが、
351
+ // ?array=1のみで終わる場合は配列にならないため、直接配列にしている
352
+ const type = this.properties[key].item.type;
353
+ if (type === 'object' || type === 'object?' || type === 'array' || type === 'array?' || type === 'map' || type === 'map?') {
354
+ throw new Error("GETまたはDELETEメソッドでは配列型にobject, array, mapを使用することはできません。");
361
355
  }
362
- mapData[mapKey] = Number(mapValue);
363
- break;
364
- case 'string':
365
- switch (typeof mapValue) {
366
- case 'number':
367
- mapData[mapKey] = mapValue.toString();
368
- break;
369
- case 'string':
370
- mapData[mapKey] = mapValue;
371
- break;
372
- default:
373
- this.throwInputError("MAP_02", [key], value);
356
+ if (type === 'enum' || type === 'enum?') {
357
+ const tempProp = {
358
+ type: type,
359
+ description: this.properties[key].item.description,
360
+ enumType: this.properties[key].item.enumType,
361
+ enums: this.properties[key].item.enums,
362
+ };
363
+ this.data[key] = [this.convertValue(tempProp, value, [key, 0], true)];
374
364
  }
375
- break;
376
- case 'bool':
377
- switch (typeof mapValue) {
378
- case 'boolean':
379
- mapData[mapKey] = mapValue;
380
- break;
381
- case 'number':
382
- if (mapValue !== 0 && mapValue !== 1) {
383
- this.throwInputError("MAP_03", [key], mapValue);
384
- }
385
- mapData[mapKey] = mapValue === 1;
386
- break;
387
- case 'string':
388
- if (mapValue !== 'true' && mapValue !== 'false') {
389
- this.throwInputError("MAP_04", [key], mapValue);
390
- }
391
- mapData[mapKey] = mapValue === 'true';
392
- break;
393
- default:
394
- this.throwInputError("MAP_05", [key], mapValue);
365
+ else if (type === 'string' || type === 'string?') {
366
+ const tempProp = {
367
+ type: type,
368
+ description: this.properties[key].item.description,
369
+ maxLength: this.properties[key].item.maxLength,
370
+ regExp: this.properties[key].item.regExp
371
+ };
372
+ this.data[key] = [this.convertValue(tempProp, value, [key, 0], true)];
395
373
  }
396
- break;
374
+ else if (type === 'number' || type === 'number?') {
375
+ const tempProp = {
376
+ type: type,
377
+ description: this.properties[key].item.description,
378
+ max: this.properties[key].item.max,
379
+ min: this.properties[key].item.min,
380
+ };
381
+ this.data[key] = [this.convertValue(tempProp, value, [key, 0], true)];
382
+ }
383
+ else {
384
+ const tempProp = {
385
+ type: type,
386
+ description: this.properties[key].item.description
387
+ };
388
+ this.data[key] = [this.convertValue(tempProp, value, [key, 0], true)];
389
+ }
390
+ }
391
+ else {
392
+ this.throwInputError("ARRAY_01", [key], value);
393
+ }
397
394
  }
398
- }
399
- this.changeBody([key], mapData);
400
- break;
401
- case 'enum':
402
- case 'enum?':
403
- this.setEnum([key], value);
404
- break;
405
- default:
406
- this.convertInput([key], value);
407
- break;
395
+ break;
396
+ case 'map':
397
+ case 'map?':
398
+ // tODO : ここは共通化したい
399
+ const mapData = {};
400
+ for (const [mapKey, mapValue] of Object.entries(value)) {
401
+ switch (this.properties[key].mapType) {
402
+ case 'number':
403
+ if (this.isNumber(mapValue) === false) {
404
+ this.throwInputError("MAP_01", [key], value);
405
+ }
406
+ mapData[mapKey] = Number(mapValue);
407
+ break;
408
+ case 'string':
409
+ switch (typeof mapValue) {
410
+ case 'number':
411
+ mapData[mapKey] = mapValue.toString();
412
+ break;
413
+ case 'string':
414
+ mapData[mapKey] = mapValue;
415
+ break;
416
+ default:
417
+ this.throwInputError("MAP_02", [key], value);
418
+ }
419
+ break;
420
+ case 'bool':
421
+ switch (typeof mapValue) {
422
+ case 'boolean':
423
+ mapData[mapKey] = mapValue;
424
+ break;
425
+ case 'number':
426
+ if (mapValue !== 0 && mapValue !== 1) {
427
+ this.throwInputError("MAP_03", [key], mapValue);
428
+ }
429
+ mapData[mapKey] = mapValue === 1;
430
+ break;
431
+ case 'string':
432
+ if (mapValue !== 'true' && mapValue !== 'false') {
433
+ this.throwInputError("MAP_04", [key], mapValue);
434
+ }
435
+ mapData[mapKey] = mapValue === 'true';
436
+ break;
437
+ default:
438
+ this.throwInputError("MAP_05", [key], mapValue);
439
+ }
440
+ break;
441
+ }
442
+ }
443
+ this.changeBody([key], mapData);
444
+ break;
445
+ case 'enum':
446
+ case 'enum?':
447
+ this.setEnum([key], value);
448
+ break;
449
+ default:
450
+ this.convertInput([key], value);
451
+ break;
452
+ }
408
453
  }
409
- }
410
- // 不要項目チェック
411
- for (const [key, value] of Object.entries(this.data)) {
412
- if (key in this.properties === false) {
413
- this.throwInputError("UNNECESSARY_01", [key], value);
454
+ // 不要項目チェック
455
+ for (const [key, value] of Object.entries(this.data)) {
456
+ if (key in this.properties === false) {
457
+ this.throwInputError("UNNECESSARY_01", [key], value);
458
+ }
414
459
  }
415
- }
460
+ });
416
461
  }
417
462
  /**
418
463
  * Sets the value for an enum type based on the specified keys.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pg-mvc-service",
3
- "version": "2.0.128",
3
+ "version": "2.1.0",
4
4
  "description": "",
5
5
  "homepage": "https://github.com/n-daira/npm-pack_mvc-service#readme",
6
6
  "bugs": {
@@ -31,6 +31,7 @@
31
31
  "@types/express": "5.0.1",
32
32
  "axios": "1.10.0",
33
33
  "crypto": "1.0.1",
34
+ "hono": "4.11.2",
34
35
  "node-cron": "4.2.1",
35
36
  "pdf-lib": "1.17.1",
36
37
  "sharp": "0.34.1",
package/src/Service.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import axios, { AxiosResponse } from "axios";
2
2
  import { Request, Response } from 'express';
3
+ import { Hono, Context } from 'hono' // Context を追加
3
4
  import { Pool, type PoolClient } from 'pg';
4
5
  import { MaintenanceException, AuthException, InputErrorException, ForbiddenException, DbConflictException, UnprocessableException, NotFoundException } from './exceptions/Exception';
5
6
  import { RequestType } from './reqestResponse/RequestType';
@@ -10,14 +11,18 @@ import { StringClient } from './clients/StringClient';
10
11
  import { EncryptClient } from './clients/EncryptClient';
11
12
  import PoolManager from './PoolManager';
12
13
 
14
+ type TStatusCode = 200 | 201 | 400 | 401 | 403 | 404 | 409 | 422 | 500 | 503;
15
+
13
16
  export type MethodType = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
14
17
  export interface IError {
15
- status: 400 | 401 | 404 | 409 | 422 | 500,
18
+ status: TStatusCode,
16
19
  code: string;
17
20
  description: string;
18
21
  }
19
22
 
23
+
20
24
  export class Service {
25
+ protected readonly module: 'express' | 'hono' = 'express';
21
26
  protected readonly method: MethodType = 'GET';
22
27
  get Method(): MethodType { return this.method; }
23
28
  protected readonly endpoint: string = '';
@@ -33,7 +38,6 @@ export class Service {
33
38
  get AuthToken(): string { return this.request.Authorization ?? ''; }
34
39
  protected readonly response: ResponseType = new ResponseType();
35
40
  get Response(): ResponseType { return this.response }; // swaggerで必要なので、ここだけ宣言
36
- protected readonly isTest: boolean = process.env.NODE_ENV === 'test';
37
41
  protected readonly tags: Array<string> = [];
38
42
  get Tags(): Array<string> { return this.tags; }
39
43
  protected readonly errorList: Array<IError> = [];
@@ -45,18 +49,43 @@ export class Service {
45
49
  }];
46
50
  }
47
51
 
48
- protected readonly req: Request;
49
- protected readonly res: Response;
50
- constructor(request: Request, response: Response) {
51
- this.req = request;
52
- this.res = response;
52
+ protected readonly req?: Request;
53
+ protected readonly res?: Response;
54
+ protected readonly c?: Context;
55
+ protected setResponse(status: TStatusCode, data: any) {
56
+ if (this.module === 'express') {
57
+ this.res?.status(status).json(data);
58
+ } else {
59
+ this.c?.json(data, status)
60
+ }
61
+ }
62
+
63
+ constructor(request: Request, response: Response);
64
+ constructor(c: Context);
65
+ constructor(param1: Request | Context, param2?: Response) {
66
+ if (param2 !== undefined) {
67
+ // Express の場合: (request, response)
68
+ this.module = 'express';
69
+ this.req = param1 as Request;
70
+ this.res = param2;
71
+ } else {
72
+ // Hono の場合: (c)
73
+ this.module = 'hono';
74
+ this.c = param1 as Context;
75
+ }
53
76
  }
54
77
 
55
78
  public async inintialize(): Promise<void> {
56
- this.request.setRequest(this.req);
79
+ if (this.module === "express") {
80
+ await this.request.setRequest(this.module, this.req as Request);
81
+ } else {
82
+ await this.request.setRequest(this.module, this.c as Context);
83
+ }
84
+
57
85
  await this.checkMaintenance();
58
86
  await this.middleware();
59
87
  }
88
+ // Context<{ Bindings: Bindings }>
60
89
 
61
90
  protected dbUser?: string = process.env.DB_USER;
62
91
  protected dbHost?: string = process.env.DB_HOST;
@@ -91,7 +120,7 @@ export class Service {
91
120
  protected async middleware(): Promise<void>{ }
92
121
 
93
122
  public resSuccess(): void {
94
- this.res.status(200).json(this.response.ResponseData);
123
+ this.setResponse(200, this.response.ResponseData);
95
124
  }
96
125
 
97
126
  protected async outputErrorLog(ex: any): Promise<void>{ }
@@ -102,55 +131,49 @@ export class Service {
102
131
  });
103
132
 
104
133
  if (ex instanceof AuthException) {
105
- this.res.status(401).json({
134
+ this.setResponse(401, {
106
135
  message : "Authentication expired. Please login again."
107
136
  });
108
137
  return;
109
138
  } else if (ex instanceof ForbiddenException) {
110
- this.res.status(403).json({
139
+ this.setResponse(403, {
111
140
  message : 'Forbidden error'
112
141
  });
113
142
  return;
114
143
  } else if (ex instanceof InputErrorException) {
115
- this.res.status(400).json({
144
+ this.setResponse(400, {
116
145
  errorCode : `${this.apiCode}-${ex.ErrorId}`,
117
146
  errorMessage : ex.message
118
147
  });
119
148
  return;
120
149
  } else if (ex instanceof DbConflictException) {
121
- this.res.status(409).json({
150
+ this.setResponse(409, {
122
151
  errorCode : `${this.apiCode}-${ex.ErrorId}`,
123
152
  errorMessage : ex.message
124
153
  });
125
154
  return;
126
155
  } else if (ex instanceof UnprocessableException) {
127
- this.res.status(422).json({
156
+ this.setResponse(422, {
128
157
  errorCode : `${this.apiCode}-${ex.ErrorId}`,
129
158
  errorMessage : ex.message
130
159
  });
131
160
  return;
132
161
  } else if (ex instanceof MaintenanceException) {
133
- this.res.status(503).json({
162
+ this.setResponse(503, {
134
163
  errorMessage : ex.message
135
164
  });
136
165
  return;
137
166
  } else if (ex instanceof NotFoundException) {
138
- this.res.status(404).json({
167
+ this.setResponse(404, {
139
168
  errorCode : `${this.apiCode}-${ex.ErrorId}`,
140
169
  errorMessage : ex.message
141
170
  });
142
171
  return;
143
172
  }
144
173
 
145
- if (this.isTest) {
146
- this.res.status(500).json({
147
- message : ex.stack
148
- });
149
- } else {
150
- this.res.status(500).json({
151
- message : 'Internal server error'
152
- });
153
- }
174
+ this.setResponse(500, {
175
+ message : 'Internal server error'
176
+ });
154
177
 
155
178
  return;
156
179
  }
@@ -423,36 +423,33 @@ export class WhereExpression {
423
423
  }
424
424
  }
425
425
 
426
- const objs: { [key: string]: string } = {}
426
+ let sql = expression;
427
+ // 1. ひらがな → カタカナ (TRANSLATEを使用)
428
+ // NORMALIZEはひらがなをカタカナにはしないので、これは残します
427
429
  if (replaceOption?.hiraganaToKatakana === true) {
428
- objs['あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをんゔがぎぐげござじずぜぞだぢづでどばびぶべぼぱぴぷぺぽぁぃぅぇぉゃゅょっー、。・「」゛゜']
429
- = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンヴガギグゲゴザジズゼゾダヂヅデドバビブベボパピプペポァィゥェォャュョッー、。・「」゛゜';
430
+ const from = 'あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをんゔがぎぐげござじずぜぞだぢづでどばびぶべぼぱぴぷぺぽぁぃぅぇぉゃゅょっー、。・「」゛゜';
431
+ const to = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンヴガギグゲゴザジズゼゾダヂヅデドバビブベボパピプペポァィゥェォャュョッー、。・「」゛゜';
432
+ sql = `TRANSLATE(${sql}, '${from}', '${to}')`;
430
433
  }
431
434
 
435
+ // 2. 漢数字 → 算用数字 (TRANSLATEを使用)
436
+ // これもNORMALIZEの管轄外なので残しますが、1つのTRANSLATEにまとめます(SQLが速くなります)
432
437
  if (replaceOption?.numeric?.japanese === true) {
433
- objs['零〇'] = '00';
434
- objs['一壱弌'] = '111';
435
- objs['二弐'] = '22';
436
- objs['三参'] = '33';
437
- objs['四肆'] = '44';
438
- objs['五伍'] = '55';
439
- objs['六陸'] = '66';
440
- objs['七漆'] = '77';
441
- objs['八捌'] = '88';
442
- objs['九玖'] = '99';
438
+ const from = '零〇一壱弌二弐三参四肆五伍六陸七漆八捌九玖';
439
+ const to = '001112233445566778899';
440
+ sql = `TRANSLATE(${sql}, '${from}', '${to}')`;
443
441
  }
444
442
 
443
+ // 3. 全角半角の統一 (ここを NORMALIZE に変更!)
444
+ // 元の halfToFull ブロックを削除し、この処理に置き換えます
445
445
  if (replaceOption?.halfToFull === true) {
446
- objs['0123456789'] = '0123456789';
447
- objs['アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンヴガギグゲゴザジズゼゾダヂヅデドバビブベボパピプペポァィゥェォャュョッー、。・「」゛゜']
448
- = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンヴガギグゲゴザジズゼゾダヂヅデドハハビブベボパピプペポァィゥェォャュョッー、。・「」 ゚';
449
- objs['ABCDEFGHIJKLMNOPQRSTUVWXYZ'] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
450
- objs['abcdefghijklmnopqrstuvwxyz'] = 'abcdefghijklmnopqrstuvwxyz';
446
+ // NORMALIZE(..., NFKC) は以下の処理を自動で行います
447
+ // - 全角英数字 → 半角英数字 (例: A → A)
448
+ // - 半角カナ → 全角カナ (例: ア → ア)
449
+ // - 濁点の結合 (例: カ + ゙ → ガ)
450
+ sql = `NORMALIZE(${sql}, NFKC)`;
451
451
  }
452
452
 
453
- let sql = expression;
454
- Object.keys(objs).forEach(key => sql = `TRANSLATE(${sql} ,'${key}','${objs[key]}')`);
455
-
456
453
  return sql;
457
454
  }
458
455
  }
@@ -4,6 +4,7 @@ import { InputErrorException } from '../exceptions/Exception';
4
4
  import StringUtil from '../Utils/StringUtil';
5
5
  import { ValidateStringUtil } from 'type-utils-n-daira';
6
6
  import { IError } from '../Service';
7
+ import { Context } from 'hono';
7
8
 
8
9
  // エラーメッセージの型定義
9
10
  export interface ErrorMessageType {
@@ -115,8 +116,7 @@ export class RequestType extends ReqResType {
115
116
  }
116
117
  return this.headers;
117
118
  }
118
- private remoteAddress: string | undefined;
119
- get RemoteAddress(): string | undefined { return this.remoteAddress; }
119
+
120
120
  get Authorization(): string | null {
121
121
  const authorization = this.Headers['authorization'] ?? '';
122
122
  if (authorization.startsWith('Bearer ') === false) {
@@ -125,24 +125,39 @@ export class RequestType extends ReqResType {
125
125
  return authorization.replace(/^Bearer\s/, '');
126
126
  }
127
127
 
128
- public setRequest(request: Request) {
129
- this.createBody(request);
128
+ public async setRequest(module: 'express' | 'hono', request: Request | Context): Promise<void> {
129
+ await this.createBody(module, request);
130
130
 
131
131
  this.params = {};
132
- if (request.params !== undefined) {
133
- for (const [key, value] of Object.entries(request.params)) {
134
- const index = this.paramProperties.findIndex(property => property.key === key);
135
- if (index === -1) {
136
- throw new Error(`${key} is not set in paramProperties.`);
132
+ if (module === 'express') {
133
+ const req = request as Request;
134
+ if (req.params !== undefined) {
135
+ for (const [key, value] of Object.entries(req.params)) {
136
+ const index = this.paramProperties.findIndex((p) => p.key === key);
137
+ if (index === -1) throw new Error(`${key} is not set in paramProperties.`);
138
+ const prop = this.paramProperties[index];
139
+ this.params[key] = this.convertValue(prop, value, [key, `(pathIndex: ${index})`], false);
140
+ }
141
+ }
142
+
143
+ this.params = req.params ?? {};
144
+ this.headers = req.headers ?? {};
145
+ } else {
146
+ const c = request as Context;
147
+ for (let index = 0; index < this.paramProperties.length; index++) {
148
+ const prop = this.paramProperties[index];
149
+ const value = (c.req as any).param?.(prop.key); // hono の param()
150
+ if (value !== undefined) {
151
+ this.params[prop.key] = this.convertValue(prop, value, [prop.key, `(pathIndex: ${index})`], false);
137
152
  }
138
- const property = this.paramProperties[index];
139
- this.params[key] = this.convertValue(property, value, [key, `(pathIndex: ${index})`], false);
140
153
  }
141
- }
142
- this.params = request.params ?? {};
143
- this.headers = request.headers ?? {};
144
154
 
145
- this.remoteAddress = request.socket?.remoteAddress;
155
+ const headersObj: Record<string, string> = {};
156
+ c.req.raw.headers.forEach((v, k) => {
157
+ headersObj[k.toLowerCase()] = v;
158
+ });
159
+ this.headers = headersObj as any;
160
+ }
146
161
  }
147
162
 
148
163
  private createErrorMessage(code:
@@ -296,13 +311,29 @@ export class RequestType extends ReqResType {
296
311
  * @param {Object} body - Request body object, リクエストボディオブジェクト
297
312
  * @throws {InputErrorException} Thrown when the input value is invalid, 入力値が不正な場合にスローされます
298
313
  */
299
- private createBody(request: Request) {
300
- if (request.method === 'GET' || request.method === 'DELETE') {
301
- this.data = request.query;
314
+ private async createBody(module: 'express' | 'hono', request: Request | Context): Promise<void> {
315
+ let method = '';
316
+ if (module === 'express') {
317
+ const req = request as Request;
318
+ method = req.method;
319
+ if (method === 'GET' || method === 'DELETE') {
320
+ this.data = req.query;
321
+ } else {
322
+ this.data = req.body;
323
+ }
302
324
  } else {
303
- this.data = request.body;
325
+ const c = request as Context;
326
+ method = c.req.method;
327
+ if (method === 'GET' || method === 'DELETE') {
328
+ const url = new URL(c.req.url);
329
+ this.data = Object.fromEntries(url.searchParams.entries());
330
+ } else {
331
+ // JSON を想定(form 等なら parseBody() を使う)
332
+ this.data = await c.req.json();
333
+ }
304
334
  }
305
335
 
336
+
306
337
  if (this.data === undefined) {
307
338
  this.data = {};
308
339
  }
@@ -311,7 +342,7 @@ export class RequestType extends ReqResType {
311
342
 
312
343
  // NULLチェック
313
344
  if (key in this.data === false || this.data[key] === null || this.data[key] === "") {
314
- if (this.properties[key].type === 'array' && ['GET', 'DELETE'].includes(request.method)) {
345
+ if (this.properties[key].type === 'array' && ['GET', 'DELETE'].includes(method)) {
315
346
  // GET,DELETEメソッドの場合、?array=1&array=2で配列となるが、
316
347
  // ?array=1のみで終わる場合は配列にならないため、直接配列にしている
317
348
  // この処理で空文字やnullが入った場合の対処をここで行う
@@ -355,7 +386,7 @@ export class RequestType extends ReqResType {
355
386
  if (Array.isArray(value)) {
356
387
  this.setArray([key], value);
357
388
  } else {
358
- if (request.method === 'GET' || request.method === 'DELETE') {
389
+ if (method === 'GET' || method === 'DELETE') {
359
390
  // GET,DELETEメソッドの場合、?array=1&array=2で配列となるが、
360
391
  // ?array=1のみで終わる場合は配列にならないため、直接配列にしている
361
392
  const type = this.properties[key].item.type;