@vereign/core 1.0.0 → 1.2.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 CHANGED
@@ -6,4 +6,5 @@ This project uses .nvmrc
6
6
 
7
7
  1. To switch to the proper nodejs version - `nvm use` or `nvm install`
8
8
  2. Build the library - `pnpm build`
9
- 3. Run test - `pnpm test`
9
+ 3. Run test - `pnpm test`
10
+ 4. To dun dev mode - `pnpm dev`
package/dist/index.d.mts CHANGED
@@ -8,6 +8,9 @@ declare class App {
8
8
  constructor(logger: Logger, appVersion: string);
9
9
  private initializeMiddlewares;
10
10
  private initializeRoutes;
11
+ /**
12
+ * General error handler for all errors, so we can throw from anywhere in the project
13
+ */
11
14
  private initializeErrorHandling;
12
15
  listen(port: number): void;
13
16
  }
package/dist/index.d.ts CHANGED
@@ -8,6 +8,9 @@ declare class App {
8
8
  constructor(logger: Logger, appVersion: string);
9
9
  private initializeMiddlewares;
10
10
  private initializeRoutes;
11
+ /**
12
+ * General error handler for all errors, so we can throw from anywhere in the project
13
+ */
11
14
  private initializeErrorHandling;
12
15
  listen(port: number): void;
13
16
  }
package/dist/index.js CHANGED
@@ -3,8 +3,22 @@ var __create = Object.create;
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
6
7
  var __getProtoOf = Object.getPrototypeOf;
7
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
10
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
11
+ var __spreadValues = (a, b) => {
12
+ for (var prop in b || (b = {}))
13
+ if (__hasOwnProp.call(b, prop))
14
+ __defNormalProp(a, prop, b[prop]);
15
+ if (__getOwnPropSymbols)
16
+ for (var prop of __getOwnPropSymbols(b)) {
17
+ if (__propIsEnum.call(b, prop))
18
+ __defNormalProp(a, prop, b[prop]);
19
+ }
20
+ return a;
21
+ };
8
22
  var __export = (target, all) => {
9
23
  for (var name in all)
10
24
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -26,6 +40,26 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
40
  mod
27
41
  ));
28
42
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
43
+ var __async = (__this, __arguments, generator) => {
44
+ return new Promise((resolve, reject) => {
45
+ var fulfilled = (value) => {
46
+ try {
47
+ step(generator.next(value));
48
+ } catch (e) {
49
+ reject(e);
50
+ }
51
+ };
52
+ var rejected = (value) => {
53
+ try {
54
+ step(generator.throw(value));
55
+ } catch (e) {
56
+ reject(e);
57
+ }
58
+ };
59
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
60
+ step((generator = generator.apply(__this, __arguments)).next());
61
+ });
62
+ };
29
63
 
30
64
  // src/index.ts
31
65
  var index_exports = {};
@@ -43,7 +77,7 @@ var HealthController = class {
43
77
  constructor(logger, appVersion) {
44
78
  this.check = (_, res) => {
45
79
  this.logger.info("Health check endpoint called");
46
- res.status(200).json({ status: "ok", appVersion: this.appVersion });
80
+ res.json({ status: "ok", appVersion: this.appVersion });
47
81
  };
48
82
  this.logger = logger;
49
83
  this.appVersion = appVersion;
@@ -63,40 +97,175 @@ var HealthRouter = class {
63
97
  // src/chmed16/router.ts
64
98
  var import_express2 = __toESM(require("express"));
65
99
 
100
+ // src/chmed16/controller.ts
101
+ var import_chmed_parser = require("@vereign/chmed-parser");
102
+ var import_pdf_generator = require("@vereign/pdf-generator");
103
+
104
+ // src/app-error.ts
105
+ var z = __toESM(require("zod/v4"));
106
+ var AppError = class extends Error {
107
+ constructor(status, message, code, details) {
108
+ super(message);
109
+ this.status = status;
110
+ this.code = code;
111
+ this.details = details;
112
+ Error.captureStackTrace(this, this.constructor);
113
+ }
114
+ toJSON() {
115
+ return __spreadValues({
116
+ status: this.status,
117
+ code: this.code,
118
+ message: this.message
119
+ }, this.details ? { errors: this.details } : {});
120
+ }
121
+ };
122
+ var errorDetailsSchema = z.object({
123
+ path: z.string(),
124
+ message: z.string(),
125
+ code: z.string().optional()
126
+ });
127
+ var appErrorSchema = z.object({
128
+ status: z.number().int(),
129
+ code: z.string(),
130
+ message: z.string(),
131
+ errors: z.array(errorDetailsSchema).optional()
132
+ });
133
+
66
134
  // src/chmed16/controller.ts
67
135
  var CHMED16AController = class {
68
136
  constructor(logger) {
69
- this.process = (req, res) => {
70
- const { chmed } = req.body;
71
- const { lang } = req.query;
72
- this.logger.info(`Processing CHMED16A string: ${chmed}, language: ${lang}`);
73
- res.status(200).json({ receivedString: chmed, language: lang, status: "processed" });
74
- };
137
+ this.process = (req, res, next) => __async(this, null, function* () {
138
+ const { chmed, lang } = req.body;
139
+ const start = Date.now();
140
+ try {
141
+ this.logger.info("Processing started");
142
+ const json = yield this.parseChmedData(chmed, lang);
143
+ yield this.generatePdfResponse(json, res);
144
+ this.logger.info(`Total request time: ${Date.now() - start} ms`);
145
+ } catch (error) {
146
+ next(error);
147
+ }
148
+ });
75
149
  this.logger = logger;
150
+ this.sds = new import_chmed_parser.SDS("https://sds-test.hin.ch", logger);
151
+ this.parser = new import_chmed_parser.CHMEDParser(logger);
152
+ }
153
+ parseChmedData(chmed, lang) {
154
+ return __async(this, null, function* () {
155
+ try {
156
+ const parseStart = Date.now();
157
+ const json = yield this.parser.parse(chmed, this.sds, lang);
158
+ this.logger.info(`Parsing took ${Date.now() - parseStart} ms`);
159
+ return json;
160
+ } catch (error) {
161
+ this.logger.error("Parsing failed:", error);
162
+ throw new AppError(
163
+ 400,
164
+ "Failed to parse CHMED data. Please check the input format.",
165
+ "PARSE_ERROR",
166
+ [
167
+ {
168
+ path: "chmed",
169
+ message: "Invalid CHMED format or structure",
170
+ code: "invalid_format"
171
+ }
172
+ ]
173
+ );
174
+ }
175
+ });
176
+ }
177
+ generatePdfResponse(json, res) {
178
+ return __async(this, null, function* () {
179
+ const pdfStart = Date.now();
180
+ try {
181
+ const pdf = new import_pdf_generator.PDFLib(this.logger);
182
+ const pdfBuffer = yield pdf.generateBuffer(json);
183
+ res.setHeader("Content-Type", "application/pdf");
184
+ res.setHeader("Content-Disposition", 'inline; filename="chmed16a.pdf"');
185
+ res.send(pdfBuffer);
186
+ this.logger.info(`PDF generation took ${Date.now() - pdfStart} ms`);
187
+ } catch (error) {
188
+ this.logger.error("PDF generation failed:", error);
189
+ throw new AppError(
190
+ 500,
191
+ "Failed to generate PDF. The parsed data may be invalid.",
192
+ "PDF_GENERATION_ERROR",
193
+ [
194
+ {
195
+ path: "pdf_generation",
196
+ message: "PDF creation failed due to invalid or corrupted data",
197
+ code: "generation_failed"
198
+ }
199
+ ]
200
+ );
201
+ }
202
+ });
76
203
  }
77
204
  };
78
205
 
79
206
  // src/chmed16/middleware.ts
80
- var import_zod2 = require("zod");
207
+ var import_zod = require("zod");
81
208
 
82
209
  // src/chmed16/request.ts
83
- var import_zod = __toESM(require("zod"));
84
- var bodySchema = import_zod.default.object({
85
- chmed: import_zod.default.string().min(1, "string is required"),
86
- lang: import_zod.default.string().min(1, "lang is required")
87
- });
210
+ var z2 = __toESM(require("zod/v4"));
211
+ var chmedPattern = /^CHMED[0-9A-Za-z]+/;
212
+ var bodySchema = z2.object({
213
+ chmed: z2.string().min(5, "string is required").refine(
214
+ (val) => {
215
+ if (chmedPattern.test(val)) return true;
216
+ try {
217
+ const url = new URL(val);
218
+ const hash = url.hash.slice(1);
219
+ return chmedPattern.test(hash);
220
+ } catch (e) {
221
+ return false;
222
+ }
223
+ },
224
+ { message: "chmed must be a valid CHMED string" }
225
+ ).meta({
226
+ description: "A valid CHMED string",
227
+ example: "CHMED16A1H4sIAAAAAAAACr2WX2/iOBDAv4rl1wtZ/yEh8LTlaHuVll1Eu620qz4YGIiV4CDHqbbb49v0M9zTvfHFbhwaWvpvV6eqgIMzGTv+zYzHc0NHymkwjvZu6NFntQTao4PCKhrQT3e3x1ZNwKKgP0A1yrvtqMV5S0gUHYOZ4bOeCOipswBeoQ/WlM6qsgTidb7pFUolYwxv/tTu+k7Hv8Is8GYG2B2lhfFvY50uSZIEG/5QfjhUOvdKuKhwUS/l4wR0udKQh9MUNU5mJe19v6Fn1yuoV3Ku/AAeyjAOedjGJriMO6FgHC9hFCby7+HB8fBwwBin68uADmHmDTD4VLohGBzsF2RhSXssoONpPf0JqrTXwbYToRilMY8uG5HYiqJOeyfid6JuZyeSTSdu1PnuWWe7FPUQhzc4XW/Kr0ajhYUf8Jg2jnbP+fpyva6h9BRdaNx2Qv8KKqSUIo5rs22nkIhabDUG7sgWCE0FE6zFui3hJx24s2JrkhMzhrK2yQD1WcgCjs3/Y7v0q98uAGMhQ/WzbDEuvTVPi9nEgjFo2oAerFYnPj5QfqYmOTgHBK5cHhJtSFakOZhyc1tZmFvQsCQXPpAsUdU83/xTggkJTjIuFI4fffFdWNXWPK0mfm2NMSlnIolrqzSk7f9L+oDyF6QDWFkoS108Ih0WdoFcpDJqkm5uzUIvCG4cMlRp/hO0Q6x9qic0URRLlryF336fpp9XLi2m6cxW0+zDX2B/7lN9fZmGPIsT0M+TkZpmD/YBJoZIdCL5lmT8l2QnZg6Ze+KmV4CeCTrZcPEdjOi2u51Evq+bRspm2pSFIR8IzuByjMAcFvto54VFpCU5naa5moMJfj8Wa1j+3A6LuiwRb5VLmsZfhR1WZQb5FdhypYyp8ADZ32jqh16qnEjyg7jN7SLX05SAxtyTLl/YZPw+KsWOLZEJa0dv5cemve7Hc4RyxWr+BKoPmvRhpuw8JDwiLSIZGWpToavI1Z1fDzFL4rZr+Dm5wI0LRKHH3ebfafaiER7lz3tj3DtaRDxm3fh9t6hPNza9dumyKh/5WLuHyHKLakiuzILwFmthC8hMGYW+v4BpWkLuz4/6GX6fDW/xNL7buJfbbfa+2C+dlZzsTkuOLq2w7tr3/b2D6et4+OrREVZPddnkbzAlwNLrozYWDTvU2ghYttWf1jOX5oPjDvCkQO1OzDgWVTIWss6C30q/duzUpSOaivvSkYkzHvek6MnkD8Z79QTjJYYc7WsP6LPRXV2KNYHKcyC+mMnqYgaMB1UelCwqR8Bm+eYWTebdisVQ39eXdP0fL5lXGtoKAAA"
228
+ }),
229
+ lang: z2.enum(["en", "de", "fr", "it"]).optional().meta({
230
+ description: "Language code",
231
+ example: "en"
232
+ })
233
+ }).meta({ description: "Request body for CHMED endpoint" });
88
234
 
89
235
  // src/chmed16/middleware.ts
90
- var validateBody = (req, res, next) => {
91
- try {
92
- req.body = bodySchema.parse(req.body);
93
- next();
94
- } catch (error) {
95
- if (error instanceof import_zod2.ZodError) {
96
- return res.status(400).json({ error: error.issues.map((e) => e.message).join(", ") });
236
+ var validateBody = (logger) => {
237
+ return (req, res, next) => {
238
+ try {
239
+ req.body = bodySchema.parse(req.body);
240
+ next();
241
+ } catch (e) {
242
+ logger.debug("Body validation failed", { error: e });
243
+ if (e instanceof import_zod.ZodError) {
244
+ const details = e.issues.map((err) => ({
245
+ path: err.path.join("."),
246
+ message: err.message,
247
+ code: err.code
248
+ }));
249
+ logger.warn("Validation failed", { details });
250
+ return res.status(400).json(
251
+ new AppError(
252
+ 400,
253
+ "Invalid request body",
254
+ "BAD_REQUEST",
255
+ details
256
+ ).toJSON()
257
+ );
258
+ }
259
+ logger.error("Unexpected error during body validation", { error: e });
260
+ return res.status(500).json(
261
+ new AppError(
262
+ 500,
263
+ "Internal Server Error",
264
+ "INTERNAL_SERVER_ERROR"
265
+ ).toJSON()
266
+ );
97
267
  }
98
- return res.status(500).json({ error: "Internal Server Error" });
99
- }
268
+ };
100
269
  };
101
270
 
102
271
  // src/chmed16/router.ts
@@ -104,12 +273,17 @@ var CHMED16ARouter = class {
104
273
  static getRouter(logger) {
105
274
  const router = import_express2.default.Router();
106
275
  const controller = new CHMED16AController(logger);
107
- router.post("/chmed", validateBody, controller.process);
276
+ router.post(
277
+ "/chmed",
278
+ validateBody(logger),
279
+ controller.process.bind(controller)
280
+ );
108
281
  return router;
109
282
  }
110
283
  };
111
284
 
112
285
  // src/index.ts
286
+ var import_zod2 = require("zod");
113
287
  var App = class {
114
288
  constructor(logger, appVersion) {
115
289
  this.logger = logger;
@@ -127,14 +301,53 @@ var App = class {
127
301
  });
128
302
  }
129
303
  initializeRoutes() {
130
- this.app.use("/v1", HealthRouter.getRouter(this.logger, this.appVersion));
304
+ this.app.use("/", HealthRouter.getRouter(this.logger, this.appVersion));
131
305
  this.app.use("/v1", CHMED16ARouter.getRouter(this.logger));
132
306
  }
307
+ /**
308
+ * General error handler for all errors, so we can throw from anywhere in the project
309
+ */
133
310
  initializeErrorHandling() {
134
- this.app.use((err, _, res) => {
135
- this.logger.error(err.stack);
136
- res.status(500).json({ error: "Internal Server Error" });
137
- });
311
+ this.app.use(
312
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
313
+ (e, req, res, next) => {
314
+ this.logger.debug("General Error Handler");
315
+ this.logger.debug(e);
316
+ if (e instanceof import_zod2.ZodError) {
317
+ const details = e.issues.map((err) => ({
318
+ path: err.path.join("."),
319
+ message: err.message,
320
+ code: err.code
321
+ }));
322
+ return res.status(400).json(
323
+ new AppError(
324
+ 400,
325
+ "Invalid request body",
326
+ "BAD_REQUEST",
327
+ details
328
+ ).toJSON()
329
+ );
330
+ }
331
+ if (e instanceof AppError) {
332
+ if (e.code === "PARSE_ERROR") {
333
+ this.logger.warn(`Parse error: ${e.message}`);
334
+ } else if (e.code === "PDF_GENERATION_ERROR") {
335
+ this.logger.error(`PDF generation error: ${e.message}`);
336
+ } else {
337
+ this.logger.error(`App error: ${e.message}`);
338
+ }
339
+ return res.status(e.status).json(e.toJSON());
340
+ }
341
+ this.logger.error("Unexpected error:", e);
342
+ return res.status(500).json(
343
+ new AppError(
344
+ 500,
345
+ "Internal Server Error",
346
+ "INTERNAL_SERVER_ERROR"
347
+ ).toJSON()
348
+ );
349
+ }
350
+ );
138
351
  }
139
352
  listen(port) {
140
353
  this.app.listen(port, () => {
package/dist/index.mjs CHANGED
@@ -1,3 +1,40 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
3
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
4
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
5
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
+ var __spreadValues = (a, b) => {
7
+ for (var prop in b || (b = {}))
8
+ if (__hasOwnProp.call(b, prop))
9
+ __defNormalProp(a, prop, b[prop]);
10
+ if (__getOwnPropSymbols)
11
+ for (var prop of __getOwnPropSymbols(b)) {
12
+ if (__propIsEnum.call(b, prop))
13
+ __defNormalProp(a, prop, b[prop]);
14
+ }
15
+ return a;
16
+ };
17
+ var __async = (__this, __arguments, generator) => {
18
+ return new Promise((resolve, reject) => {
19
+ var fulfilled = (value) => {
20
+ try {
21
+ step(generator.next(value));
22
+ } catch (e) {
23
+ reject(e);
24
+ }
25
+ };
26
+ var rejected = (value) => {
27
+ try {
28
+ step(generator.throw(value));
29
+ } catch (e) {
30
+ reject(e);
31
+ }
32
+ };
33
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
34
+ step((generator = generator.apply(__this, __arguments)).next());
35
+ });
36
+ };
37
+
1
38
  // src/index.ts
2
39
  import express3 from "express";
3
40
 
@@ -9,7 +46,7 @@ var HealthController = class {
9
46
  constructor(logger, appVersion) {
10
47
  this.check = (_, res) => {
11
48
  this.logger.info("Health check endpoint called");
12
- res.status(200).json({ status: "ok", appVersion: this.appVersion });
49
+ res.json({ status: "ok", appVersion: this.appVersion });
13
50
  };
14
51
  this.logger = logger;
15
52
  this.appVersion = appVersion;
@@ -29,16 +66,109 @@ var HealthRouter = class {
29
66
  // src/chmed16/router.ts
30
67
  import express2 from "express";
31
68
 
69
+ // src/chmed16/controller.ts
70
+ import { CHMEDParser, SDS } from "@vereign/chmed-parser";
71
+ import { PDFLib } from "@vereign/pdf-generator";
72
+
73
+ // src/app-error.ts
74
+ import * as z from "zod/v4";
75
+ var AppError = class extends Error {
76
+ constructor(status, message, code, details) {
77
+ super(message);
78
+ this.status = status;
79
+ this.code = code;
80
+ this.details = details;
81
+ Error.captureStackTrace(this, this.constructor);
82
+ }
83
+ toJSON() {
84
+ return __spreadValues({
85
+ status: this.status,
86
+ code: this.code,
87
+ message: this.message
88
+ }, this.details ? { errors: this.details } : {});
89
+ }
90
+ };
91
+ var errorDetailsSchema = z.object({
92
+ path: z.string(),
93
+ message: z.string(),
94
+ code: z.string().optional()
95
+ });
96
+ var appErrorSchema = z.object({
97
+ status: z.number().int(),
98
+ code: z.string(),
99
+ message: z.string(),
100
+ errors: z.array(errorDetailsSchema).optional()
101
+ });
102
+
32
103
  // src/chmed16/controller.ts
33
104
  var CHMED16AController = class {
34
105
  constructor(logger) {
35
- this.process = (req, res) => {
36
- const { chmed } = req.body;
37
- const { lang } = req.query;
38
- this.logger.info(`Processing CHMED16A string: ${chmed}, language: ${lang}`);
39
- res.status(200).json({ receivedString: chmed, language: lang, status: "processed" });
40
- };
106
+ this.process = (req, res, next) => __async(this, null, function* () {
107
+ const { chmed, lang } = req.body;
108
+ const start = Date.now();
109
+ try {
110
+ this.logger.info("Processing started");
111
+ const json = yield this.parseChmedData(chmed, lang);
112
+ yield this.generatePdfResponse(json, res);
113
+ this.logger.info(`Total request time: ${Date.now() - start} ms`);
114
+ } catch (error) {
115
+ next(error);
116
+ }
117
+ });
41
118
  this.logger = logger;
119
+ this.sds = new SDS("https://sds-test.hin.ch", logger);
120
+ this.parser = new CHMEDParser(logger);
121
+ }
122
+ parseChmedData(chmed, lang) {
123
+ return __async(this, null, function* () {
124
+ try {
125
+ const parseStart = Date.now();
126
+ const json = yield this.parser.parse(chmed, this.sds, lang);
127
+ this.logger.info(`Parsing took ${Date.now() - parseStart} ms`);
128
+ return json;
129
+ } catch (error) {
130
+ this.logger.error("Parsing failed:", error);
131
+ throw new AppError(
132
+ 400,
133
+ "Failed to parse CHMED data. Please check the input format.",
134
+ "PARSE_ERROR",
135
+ [
136
+ {
137
+ path: "chmed",
138
+ message: "Invalid CHMED format or structure",
139
+ code: "invalid_format"
140
+ }
141
+ ]
142
+ );
143
+ }
144
+ });
145
+ }
146
+ generatePdfResponse(json, res) {
147
+ return __async(this, null, function* () {
148
+ const pdfStart = Date.now();
149
+ try {
150
+ const pdf = new PDFLib(this.logger);
151
+ const pdfBuffer = yield pdf.generateBuffer(json);
152
+ res.setHeader("Content-Type", "application/pdf");
153
+ res.setHeader("Content-Disposition", 'inline; filename="chmed16a.pdf"');
154
+ res.send(pdfBuffer);
155
+ this.logger.info(`PDF generation took ${Date.now() - pdfStart} ms`);
156
+ } catch (error) {
157
+ this.logger.error("PDF generation failed:", error);
158
+ throw new AppError(
159
+ 500,
160
+ "Failed to generate PDF. The parsed data may be invalid.",
161
+ "PDF_GENERATION_ERROR",
162
+ [
163
+ {
164
+ path: "pdf_generation",
165
+ message: "PDF creation failed due to invalid or corrupted data",
166
+ code: "generation_failed"
167
+ }
168
+ ]
169
+ );
170
+ }
171
+ });
42
172
  }
43
173
  };
44
174
 
@@ -46,23 +176,65 @@ var CHMED16AController = class {
46
176
  import { ZodError } from "zod";
47
177
 
48
178
  // src/chmed16/request.ts
49
- import z from "zod";
50
- var bodySchema = z.object({
51
- chmed: z.string().min(1, "string is required"),
52
- lang: z.string().min(1, "lang is required")
53
- });
179
+ import * as z2 from "zod/v4";
180
+ var chmedPattern = /^CHMED[0-9A-Za-z]+/;
181
+ var bodySchema = z2.object({
182
+ chmed: z2.string().min(5, "string is required").refine(
183
+ (val) => {
184
+ if (chmedPattern.test(val)) return true;
185
+ try {
186
+ const url = new URL(val);
187
+ const hash = url.hash.slice(1);
188
+ return chmedPattern.test(hash);
189
+ } catch (e) {
190
+ return false;
191
+ }
192
+ },
193
+ { message: "chmed must be a valid CHMED string" }
194
+ ).meta({
195
+ description: "A valid CHMED string",
196
+ example: "CHMED16A1H4sIAAAAAAAACr2WX2/iOBDAv4rl1wtZ/yEh8LTlaHuVll1Eu620qz4YGIiV4CDHqbbb49v0M9zTvfHFbhwaWvpvV6eqgIMzGTv+zYzHc0NHymkwjvZu6NFntQTao4PCKhrQT3e3x1ZNwKKgP0A1yrvtqMV5S0gUHYOZ4bOeCOipswBeoQ/WlM6qsgTidb7pFUolYwxv/tTu+k7Hv8Is8GYG2B2lhfFvY50uSZIEG/5QfjhUOvdKuKhwUS/l4wR0udKQh9MUNU5mJe19v6Fn1yuoV3Ku/AAeyjAOedjGJriMO6FgHC9hFCby7+HB8fBwwBin68uADmHmDTD4VLohGBzsF2RhSXssoONpPf0JqrTXwbYToRilMY8uG5HYiqJOeyfid6JuZyeSTSdu1PnuWWe7FPUQhzc4XW/Kr0ajhYUf8Jg2jnbP+fpyva6h9BRdaNx2Qv8KKqSUIo5rs22nkIhabDUG7sgWCE0FE6zFui3hJx24s2JrkhMzhrK2yQD1WcgCjs3/Y7v0q98uAGMhQ/WzbDEuvTVPi9nEgjFo2oAerFYnPj5QfqYmOTgHBK5cHhJtSFakOZhyc1tZmFvQsCQXPpAsUdU83/xTggkJTjIuFI4fffFdWNXWPK0mfm2NMSlnIolrqzSk7f9L+oDyF6QDWFkoS108Ih0WdoFcpDJqkm5uzUIvCG4cMlRp/hO0Q6x9qic0URRLlryF336fpp9XLi2m6cxW0+zDX2B/7lN9fZmGPIsT0M+TkZpmD/YBJoZIdCL5lmT8l2QnZg6Ze+KmV4CeCTrZcPEdjOi2u51Evq+bRspm2pSFIR8IzuByjMAcFvto54VFpCU5naa5moMJfj8Wa1j+3A6LuiwRb5VLmsZfhR1WZQb5FdhypYyp8ADZ32jqh16qnEjyg7jN7SLX05SAxtyTLl/YZPw+KsWOLZEJa0dv5cemve7Hc4RyxWr+BKoPmvRhpuw8JDwiLSIZGWpToavI1Z1fDzFL4rZr+Dm5wI0LRKHH3ebfafaiER7lz3tj3DtaRDxm3fh9t6hPNza9dumyKh/5WLuHyHKLakiuzILwFmthC8hMGYW+v4BpWkLuz4/6GX6fDW/xNL7buJfbbfa+2C+dlZzsTkuOLq2w7tr3/b2D6et4+OrREVZPddnkbzAlwNLrozYWDTvU2ghYttWf1jOX5oPjDvCkQO1OzDgWVTIWss6C30q/duzUpSOaivvSkYkzHvek6MnkD8Z79QTjJYYc7WsP6LPRXV2KNYHKcyC+mMnqYgaMB1UelCwqR8Bm+eYWTebdisVQ39eXdP0fL5lXGtoKAAA"
197
+ }),
198
+ lang: z2.enum(["en", "de", "fr", "it"]).optional().meta({
199
+ description: "Language code",
200
+ example: "en"
201
+ })
202
+ }).meta({ description: "Request body for CHMED endpoint" });
54
203
 
55
204
  // src/chmed16/middleware.ts
56
- var validateBody = (req, res, next) => {
57
- try {
58
- req.body = bodySchema.parse(req.body);
59
- next();
60
- } catch (error) {
61
- if (error instanceof ZodError) {
62
- return res.status(400).json({ error: error.issues.map((e) => e.message).join(", ") });
205
+ var validateBody = (logger) => {
206
+ return (req, res, next) => {
207
+ try {
208
+ req.body = bodySchema.parse(req.body);
209
+ next();
210
+ } catch (e) {
211
+ logger.debug("Body validation failed", { error: e });
212
+ if (e instanceof ZodError) {
213
+ const details = e.issues.map((err) => ({
214
+ path: err.path.join("."),
215
+ message: err.message,
216
+ code: err.code
217
+ }));
218
+ logger.warn("Validation failed", { details });
219
+ return res.status(400).json(
220
+ new AppError(
221
+ 400,
222
+ "Invalid request body",
223
+ "BAD_REQUEST",
224
+ details
225
+ ).toJSON()
226
+ );
227
+ }
228
+ logger.error("Unexpected error during body validation", { error: e });
229
+ return res.status(500).json(
230
+ new AppError(
231
+ 500,
232
+ "Internal Server Error",
233
+ "INTERNAL_SERVER_ERROR"
234
+ ).toJSON()
235
+ );
63
236
  }
64
- return res.status(500).json({ error: "Internal Server Error" });
65
- }
237
+ };
66
238
  };
67
239
 
68
240
  // src/chmed16/router.ts
@@ -70,12 +242,17 @@ var CHMED16ARouter = class {
70
242
  static getRouter(logger) {
71
243
  const router = express2.Router();
72
244
  const controller = new CHMED16AController(logger);
73
- router.post("/chmed", validateBody, controller.process);
245
+ router.post(
246
+ "/chmed",
247
+ validateBody(logger),
248
+ controller.process.bind(controller)
249
+ );
74
250
  return router;
75
251
  }
76
252
  };
77
253
 
78
254
  // src/index.ts
255
+ import { ZodError as ZodError2 } from "zod";
79
256
  var App = class {
80
257
  constructor(logger, appVersion) {
81
258
  this.logger = logger;
@@ -93,14 +270,53 @@ var App = class {
93
270
  });
94
271
  }
95
272
  initializeRoutes() {
96
- this.app.use("/v1", HealthRouter.getRouter(this.logger, this.appVersion));
273
+ this.app.use("/", HealthRouter.getRouter(this.logger, this.appVersion));
97
274
  this.app.use("/v1", CHMED16ARouter.getRouter(this.logger));
98
275
  }
276
+ /**
277
+ * General error handler for all errors, so we can throw from anywhere in the project
278
+ */
99
279
  initializeErrorHandling() {
100
- this.app.use((err, _, res) => {
101
- this.logger.error(err.stack);
102
- res.status(500).json({ error: "Internal Server Error" });
103
- });
280
+ this.app.use(
281
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
282
+ (e, req, res, next) => {
283
+ this.logger.debug("General Error Handler");
284
+ this.logger.debug(e);
285
+ if (e instanceof ZodError2) {
286
+ const details = e.issues.map((err) => ({
287
+ path: err.path.join("."),
288
+ message: err.message,
289
+ code: err.code
290
+ }));
291
+ return res.status(400).json(
292
+ new AppError(
293
+ 400,
294
+ "Invalid request body",
295
+ "BAD_REQUEST",
296
+ details
297
+ ).toJSON()
298
+ );
299
+ }
300
+ if (e instanceof AppError) {
301
+ if (e.code === "PARSE_ERROR") {
302
+ this.logger.warn(`Parse error: ${e.message}`);
303
+ } else if (e.code === "PDF_GENERATION_ERROR") {
304
+ this.logger.error(`PDF generation error: ${e.message}`);
305
+ } else {
306
+ this.logger.error(`App error: ${e.message}`);
307
+ }
308
+ return res.status(e.status).json(e.toJSON());
309
+ }
310
+ this.logger.error("Unexpected error:", e);
311
+ return res.status(500).json(
312
+ new AppError(
313
+ 500,
314
+ "Internal Server Error",
315
+ "INTERNAL_SERVER_ERROR"
316
+ ).toJSON()
317
+ );
318
+ }
319
+ );
104
320
  }
105
321
  listen(port) {
106
322
  this.app.listen(port, () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vereign/core",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "core api library for e prescription project",
5
5
  "repository": {
6
6
  "type": "git",
@@ -25,7 +25,9 @@
25
25
  "lint": "pnpm eslint",
26
26
  "test": "vitest run --coverage",
27
27
  "test:watch": "vitest --coverage",
28
- "semantic-release": "semantic-release"
28
+ "semantic-release": "semantic-release",
29
+ "dev": "ts-node-dev ./src/dev-server.ts",
30
+ "generate:swagger": "npx ts-node ./src/generate-swagger.ts"
29
31
  },
30
32
  "devDependencies": {
31
33
  "@commitlint/cli": "^19.8.1",
@@ -50,13 +52,16 @@
50
52
  "prettier": "^3.6.2",
51
53
  "semantic-release": "^24.2.7",
52
54
  "supertest": "^7.1.4",
55
+ "ts-node-dev": "^2.0.0",
53
56
  "tsup": "^8.5.0",
54
57
  "typescript": "^5.8.3",
55
58
  "typescript-eslint": "^8.37.0",
56
59
  "vitest": "^3.2.4",
57
- "zod-to-openapi": "^0.2.1"
60
+ "zod-openapi": "^5.3.1"
58
61
  },
59
62
  "dependencies": {
63
+ "@vereign/chmed-parser": "^1.4.6",
64
+ "@vereign/pdf-generator": "^1.2.1",
60
65
  "express": "^5.1.0",
61
66
  "pdfkit": "^0.17.1",
62
67
  "winston": "^3.17.0",