@vereign/core 1.1.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/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 });
@@ -86,50 +100,172 @@ var import_express2 = __toESM(require("express"));
86
100
  // src/chmed16/controller.ts
87
101
  var import_chmed_parser = require("@vereign/chmed-parser");
88
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
+
134
+ // src/chmed16/controller.ts
89
135
  var CHMED16AController = class {
90
136
  constructor(logger) {
91
- this.process = (req, res) => __async(this, null, function* () {
137
+ this.process = (req, res, next) => __async(this, null, function* () {
92
138
  const { chmed, lang } = req.body;
93
139
  const start = Date.now();
94
- this.logger.info(`Processing started`);
95
- const parseStart = Date.now();
96
- const json = yield this.parser.parse(chmed, lang, this.sds);
97
- this.logger.info(`Parsing took ${Date.now() - parseStart} ms`);
98
- res.setHeader("Content-Type", "application/pdf");
99
- res.setHeader("Content-Disposition", 'inline; filename="chmed16a.pdf"');
100
- const pdfStart = Date.now();
101
- const pdf = new import_pdf_generator.PDFLib(this.logger);
102
- pdf.generate(json, res);
103
- this.logger.info(`PDF generation took ${Date.now() - pdfStart} ms`);
104
- this.logger.info(`Total request time: ${Date.now() - start} ms`);
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
+ }
105
148
  });
106
149
  this.logger = logger;
107
150
  this.sds = new import_chmed_parser.SDS("https://sds-test.hin.ch", logger);
108
151
  this.parser = new import_chmed_parser.CHMEDParser(logger);
109
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
+ });
203
+ }
110
204
  };
111
205
 
112
206
  // src/chmed16/middleware.ts
113
- var import_zod2 = require("zod");
207
+ var import_zod = require("zod");
114
208
 
115
209
  // src/chmed16/request.ts
116
- var import_zod = __toESM(require("zod"));
117
- var bodySchema = import_zod.default.object({
118
- chmed: import_zod.default.string().min(1, "string is required"),
119
- lang: import_zod.default.string().min(1, "lang is required")
120
- });
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" });
121
234
 
122
235
  // src/chmed16/middleware.ts
123
- var validateBody = (req, res, next) => {
124
- try {
125
- req.body = bodySchema.parse(req.body);
126
- next();
127
- } catch (error) {
128
- if (error instanceof import_zod2.ZodError) {
129
- 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
+ );
130
267
  }
131
- return res.status(500).json({ error: "Internal Server Error" });
132
- }
268
+ };
133
269
  };
134
270
 
135
271
  // src/chmed16/router.ts
@@ -137,12 +273,17 @@ var CHMED16ARouter = class {
137
273
  static getRouter(logger) {
138
274
  const router = import_express2.default.Router();
139
275
  const controller = new CHMED16AController(logger);
140
- router.post("/chmed", validateBody, controller.process);
276
+ router.post(
277
+ "/chmed",
278
+ validateBody(logger),
279
+ controller.process.bind(controller)
280
+ );
141
281
  return router;
142
282
  }
143
283
  };
144
284
 
145
285
  // src/index.ts
286
+ var import_zod2 = require("zod");
146
287
  var App = class {
147
288
  constructor(logger, appVersion) {
148
289
  this.logger = logger;
@@ -160,15 +301,51 @@ var App = class {
160
301
  });
161
302
  }
162
303
  initializeRoutes() {
163
- this.app.use("/v1", HealthRouter.getRouter(this.logger, this.appVersion));
304
+ this.app.use("/", HealthRouter.getRouter(this.logger, this.appVersion));
164
305
  this.app.use("/v1", CHMED16ARouter.getRouter(this.logger));
165
306
  }
307
+ /**
308
+ * General error handler for all errors, so we can throw from anywhere in the project
309
+ */
166
310
  initializeErrorHandling() {
167
311
  this.app.use(
168
- //eslint-disable-next-line
169
- (err, req, res, next) => {
170
- this.logger.error(err.stack);
171
- res.status(500).json({ error: "Internal Server Error" });
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
+ );
172
349
  }
173
350
  );
174
351
  }
package/dist/index.mjs CHANGED
@@ -1,3 +1,19 @@
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
+ };
1
17
  var __async = (__this, __arguments, generator) => {
2
18
  return new Promise((resolve, reject) => {
3
19
  var fulfilled = (value) => {
@@ -53,50 +69,172 @@ import express2 from "express";
53
69
  // src/chmed16/controller.ts
54
70
  import { CHMEDParser, SDS } from "@vereign/chmed-parser";
55
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
+
103
+ // src/chmed16/controller.ts
56
104
  var CHMED16AController = class {
57
105
  constructor(logger) {
58
- this.process = (req, res) => __async(this, null, function* () {
106
+ this.process = (req, res, next) => __async(this, null, function* () {
59
107
  const { chmed, lang } = req.body;
60
108
  const start = Date.now();
61
- this.logger.info(`Processing started`);
62
- const parseStart = Date.now();
63
- const json = yield this.parser.parse(chmed, lang, this.sds);
64
- this.logger.info(`Parsing took ${Date.now() - parseStart} ms`);
65
- res.setHeader("Content-Type", "application/pdf");
66
- res.setHeader("Content-Disposition", 'inline; filename="chmed16a.pdf"');
67
- const pdfStart = Date.now();
68
- const pdf = new PDFLib(this.logger);
69
- pdf.generate(json, res);
70
- this.logger.info(`PDF generation took ${Date.now() - pdfStart} ms`);
71
- this.logger.info(`Total request time: ${Date.now() - start} ms`);
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
+ }
72
117
  });
73
118
  this.logger = logger;
74
119
  this.sds = new SDS("https://sds-test.hin.ch", logger);
75
120
  this.parser = new CHMEDParser(logger);
76
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
+ });
172
+ }
77
173
  };
78
174
 
79
175
  // src/chmed16/middleware.ts
80
176
  import { ZodError } from "zod";
81
177
 
82
178
  // src/chmed16/request.ts
83
- import z from "zod";
84
- var bodySchema = z.object({
85
- chmed: z.string().min(1, "string is required"),
86
- lang: z.string().min(1, "lang is required")
87
- });
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" });
88
203
 
89
204
  // 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 ZodError) {
96
- 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
+ );
97
236
  }
98
- return res.status(500).json({ error: "Internal Server Error" });
99
- }
237
+ };
100
238
  };
101
239
 
102
240
  // src/chmed16/router.ts
@@ -104,12 +242,17 @@ var CHMED16ARouter = class {
104
242
  static getRouter(logger) {
105
243
  const router = express2.Router();
106
244
  const controller = new CHMED16AController(logger);
107
- router.post("/chmed", validateBody, controller.process);
245
+ router.post(
246
+ "/chmed",
247
+ validateBody(logger),
248
+ controller.process.bind(controller)
249
+ );
108
250
  return router;
109
251
  }
110
252
  };
111
253
 
112
254
  // src/index.ts
255
+ import { ZodError as ZodError2 } from "zod";
113
256
  var App = class {
114
257
  constructor(logger, appVersion) {
115
258
  this.logger = logger;
@@ -127,15 +270,51 @@ var App = class {
127
270
  });
128
271
  }
129
272
  initializeRoutes() {
130
- this.app.use("/v1", HealthRouter.getRouter(this.logger, this.appVersion));
273
+ this.app.use("/", HealthRouter.getRouter(this.logger, this.appVersion));
131
274
  this.app.use("/v1", CHMED16ARouter.getRouter(this.logger));
132
275
  }
276
+ /**
277
+ * General error handler for all errors, so we can throw from anywhere in the project
278
+ */
133
279
  initializeErrorHandling() {
134
280
  this.app.use(
135
- //eslint-disable-next-line
136
- (err, req, res, next) => {
137
- this.logger.error(err.stack);
138
- res.status(500).json({ error: "Internal Server Error" });
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
+ );
139
318
  }
140
319
  );
141
320
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vereign/core",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "core api library for e prescription project",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,7 +26,8 @@
26
26
  "test": "vitest run --coverage",
27
27
  "test:watch": "vitest --coverage",
28
28
  "semantic-release": "semantic-release",
29
- "dev": "ts-node-dev ./src/dev-server.ts"
29
+ "dev": "ts-node-dev ./src/dev-server.ts",
30
+ "generate:swagger": "npx ts-node ./src/generate-swagger.ts"
30
31
  },
31
32
  "devDependencies": {
32
33
  "@commitlint/cli": "^19.8.1",
@@ -56,11 +57,11 @@
56
57
  "typescript": "^5.8.3",
57
58
  "typescript-eslint": "^8.37.0",
58
59
  "vitest": "^3.2.4",
59
- "zod-to-openapi": "^0.2.1"
60
+ "zod-openapi": "^5.3.1"
60
61
  },
61
62
  "dependencies": {
62
- "@vereign/chmed-parser": "^1.4.1",
63
- "@vereign/pdf-generator": "^1.1.7",
63
+ "@vereign/chmed-parser": "^1.4.6",
64
+ "@vereign/pdf-generator": "^1.2.1",
64
65
  "express": "^5.1.0",
65
66
  "pdfkit": "^0.17.1",
66
67
  "winston": "^3.17.0",