@ripwords/myinvois-client 0.2.41 → 0.3.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.
Files changed (153) hide show
  1. package/dist/apiQueue-B6Q644Bz.js +201 -0
  2. package/dist/apiQueue-DgKWaQDS.cjs +220 -0
  3. package/dist/apiQueue-DgKWaQDS.cjs.map +1 -0
  4. package/dist/index.cjs +1 -1
  5. package/dist/index.js +1 -1
  6. package/dist/index10.cjs +24 -4
  7. package/dist/index10.cjs.map +1 -0
  8. package/dist/index11.cjs +0 -22
  9. package/dist/index12.cjs +33 -2
  10. package/dist/index12.cjs.map +1 -0
  11. package/dist/index13.cjs +23 -2
  12. package/dist/index13.cjs.map +1 -0
  13. package/dist/index14.cjs +0 -330
  14. package/dist/index15.cjs +0 -193
  15. package/dist/index16.cjs +0 -62
  16. package/dist/index17.cjs +4 -531
  17. package/dist/index18.cjs +6 -195
  18. package/dist/index19.cjs +5 -0
  19. package/dist/index2.cjs +61 -4
  20. package/dist/index2.cjs.map +1 -0
  21. package/dist/index20.cjs +2 -24
  22. package/dist/index21.cjs +3 -0
  23. package/dist/index22.cjs +6 -0
  24. package/dist/index23.cjs +203 -24
  25. package/dist/index23.cjs.map +1 -1
  26. package/dist/index24.cjs +104 -20
  27. package/dist/index24.cjs.map +1 -1
  28. package/dist/index25.cjs +137 -0
  29. package/dist/index25.cjs.map +1 -0
  30. package/dist/index26.cjs +59 -29
  31. package/dist/index26.cjs.map +1 -1
  32. package/dist/index27.cjs +262 -19
  33. package/dist/index27.cjs.map +1 -1
  34. package/dist/index28.cjs +79 -0
  35. package/dist/index28.cjs.map +1 -0
  36. package/dist/index29.cjs +107 -0
  37. package/dist/index29.cjs.map +1 -0
  38. package/dist/index3.cjs +531 -6
  39. package/dist/index3.cjs.map +1 -0
  40. package/dist/index30.cjs +73 -0
  41. package/dist/index30.cjs.map +1 -0
  42. package/dist/index31.cjs +107 -203
  43. package/dist/index31.cjs.map +1 -1
  44. package/dist/index32.cjs +95 -104
  45. package/dist/index32.cjs.map +1 -1
  46. package/dist/index33.cjs +13 -132
  47. package/dist/index33.cjs.map +1 -1
  48. package/dist/index34.cjs +33 -59
  49. package/dist/index34.cjs.map +1 -1
  50. package/dist/index35.cjs +14 -262
  51. package/dist/index35.cjs.map +1 -1
  52. package/dist/index36.cjs +24 -74
  53. package/dist/index36.cjs.map +1 -1
  54. package/dist/index37.cjs +15 -102
  55. package/dist/index37.cjs.map +1 -1
  56. package/dist/index38.cjs +9 -68
  57. package/dist/index38.cjs.map +1 -1
  58. package/dist/index39.cjs +9 -107
  59. package/dist/index39.cjs.map +1 -1
  60. package/dist/index4.cjs +195 -4
  61. package/dist/index4.cjs.map +1 -0
  62. package/dist/index40.cjs +12 -95
  63. package/dist/index40.cjs.map +1 -1
  64. package/dist/index41.cjs +6 -11
  65. package/dist/index41.cjs.map +1 -1
  66. package/dist/index42.cjs +101 -31
  67. package/dist/index42.cjs.map +1 -1
  68. package/dist/index43.cjs +106 -12
  69. package/dist/index43.cjs.map +1 -1
  70. package/dist/index44.cjs +119 -22
  71. package/dist/index44.cjs.map +1 -1
  72. package/dist/index45.cjs +106 -13
  73. package/dist/index45.cjs.map +1 -1
  74. package/dist/index46.cjs +98 -7
  75. package/dist/index46.cjs.map +1 -1
  76. package/dist/index47.cjs +118 -7
  77. package/dist/index47.cjs.map +1 -1
  78. package/dist/index48.cjs +127 -10
  79. package/dist/index48.cjs.map +1 -1
  80. package/dist/index49.cjs +117 -6
  81. package/dist/index49.cjs.map +1 -1
  82. package/dist/index5.cjs +0 -3
  83. package/dist/index50.cjs +14 -101
  84. package/dist/index50.cjs.map +1 -1
  85. package/dist/index51.cjs +95 -106
  86. package/dist/index51.cjs.map +1 -1
  87. package/dist/index52.cjs +142 -119
  88. package/dist/index52.cjs.map +1 -1
  89. package/dist/index53.cjs +114 -106
  90. package/dist/index53.cjs.map +1 -1
  91. package/dist/index54.cjs +144 -98
  92. package/dist/index54.cjs.map +1 -1
  93. package/dist/index55.cjs +113 -118
  94. package/dist/index55.cjs.map +1 -1
  95. package/dist/index56.cjs +17 -127
  96. package/dist/index56.cjs.map +1 -1
  97. package/dist/index57.cjs +112 -117
  98. package/dist/index57.cjs.map +1 -1
  99. package/dist/index58.cjs +47 -14
  100. package/dist/index58.cjs.map +1 -1
  101. package/dist/index59.cjs +14 -95
  102. package/dist/index59.cjs.map +1 -1
  103. package/dist/index6.cjs +24 -2
  104. package/dist/index6.cjs.map +1 -0
  105. package/dist/index60.cjs +28 -142
  106. package/dist/index60.cjs.map +1 -1
  107. package/dist/index61.cjs +22 -114
  108. package/dist/index61.cjs.map +1 -1
  109. package/dist/index62.cjs +9 -144
  110. package/dist/index62.cjs.map +1 -1
  111. package/dist/index63.cjs +8 -113
  112. package/dist/index63.cjs.map +1 -1
  113. package/dist/index64.cjs +17 -17
  114. package/dist/index64.cjs.map +1 -1
  115. package/dist/index65.cjs +412 -112
  116. package/dist/index65.cjs.map +1 -1
  117. package/dist/index66.cjs +8 -47
  118. package/dist/index66.cjs.map +1 -1
  119. package/dist/index67.cjs +9 -14
  120. package/dist/index67.cjs.map +1 -1
  121. package/dist/index68.cjs +4 -34
  122. package/dist/index68.cts.map +1 -1
  123. package/dist/index69.cjs +9 -25
  124. package/dist/index69.cjs.map +1 -1
  125. package/dist/index7.cjs +0 -6
  126. package/dist/index70.cjs +4 -15
  127. package/dist/index71.cjs +21 -14
  128. package/dist/index72.cjs +2 -23
  129. package/dist/index73.cjs +2 -418
  130. package/dist/index74.cjs +324 -9
  131. package/dist/index74.cjs.map +1 -1
  132. package/dist/index75.cjs +187 -10
  133. package/dist/index75.cjs.map +1 -1
  134. package/dist/index8.cjs +0 -4
  135. package/dist/index9.cjs +25 -9
  136. package/dist/index9.cjs.map +1 -1
  137. package/dist/utils/apiQueue.d.ts +11 -3
  138. package/dist/utils/apiQueue.js +2 -2
  139. package/package.json +1 -1
  140. package/dist/apiQueue-CCrZMnMu.js +0 -182
  141. package/dist/apiQueue-Djd7WlnV.cjs +0 -195
  142. package/dist/apiQueue-Djd7WlnV.cjs.map +0 -1
  143. package/dist/index14.cjs.map +0 -1
  144. package/dist/index15.cjs.map +0 -1
  145. package/dist/index16.cjs.map +0 -1
  146. package/dist/index17.cjs.map +0 -1
  147. package/dist/index18.cjs.map +0 -1
  148. package/dist/index20.cjs.map +0 -1
  149. package/dist/index68.cjs.map +0 -1
  150. package/dist/index70.cjs.map +0 -1
  151. package/dist/index71.cjs.map +0 -1
  152. package/dist/index72.cjs.map +0 -1
  153. package/dist/index73.cjs.map +0 -1
package/dist/index9.cjs CHANGED
@@ -1,13 +1,29 @@
1
1
 
2
- //#region src/utils/base64.ts
3
- function encodeToBase64(content) {
4
- return Buffer.from(content).toString("base64");
5
- }
6
- function decodeFromBase64(content) {
7
- return Buffer.from(content, "base64").toString("utf-8");
8
- }
2
+ //#region src/types/notifications.d.ts
3
+ let NotificationTypeEnum = /* @__PURE__ */ function(NotificationTypeEnum$1) {
4
+ NotificationTypeEnum$1[NotificationTypeEnum$1["Profile data validation"] = 3] = "Profile data validation";
5
+ NotificationTypeEnum$1[NotificationTypeEnum$1["Document received"] = 6] = "Document received";
6
+ NotificationTypeEnum$1[NotificationTypeEnum$1["Document validated"] = 7] = "Document validated";
7
+ NotificationTypeEnum$1[NotificationTypeEnum$1["Document cancelled"] = 8] = "Document cancelled";
8
+ NotificationTypeEnum$1[NotificationTypeEnum$1["User profile changed"] = 10] = "User profile changed";
9
+ NotificationTypeEnum$1[NotificationTypeEnum$1["Taxpayer profile changed"] = 11] = "Taxpayer profile changed";
10
+ NotificationTypeEnum$1[NotificationTypeEnum$1["Document rejection initiated"] = 15] = "Document rejection initiated";
11
+ NotificationTypeEnum$1[NotificationTypeEnum$1["ERP data validation"] = 26] = "ERP data validation";
12
+ NotificationTypeEnum$1[NotificationTypeEnum$1["Documents processing summary"] = 33] = "Documents processing summary";
13
+ NotificationTypeEnum$1[NotificationTypeEnum$1["Document Template Published"] = 34] = "Document Template Published";
14
+ NotificationTypeEnum$1[NotificationTypeEnum$1["Document Template Deletion"] = 35] = "Document Template Deletion";
15
+ return NotificationTypeEnum$1;
16
+ }({});
17
+ let NotificationStatusEnum = /* @__PURE__ */ function(NotificationStatusEnum$1) {
18
+ NotificationStatusEnum$1[NotificationStatusEnum$1["New"] = 1] = "New";
19
+ NotificationStatusEnum$1[NotificationStatusEnum$1["Pending"] = 2] = "Pending";
20
+ NotificationStatusEnum$1[NotificationStatusEnum$1["Batched"] = 3] = "Batched";
21
+ NotificationStatusEnum$1[NotificationStatusEnum$1["Delivered"] = 4] = "Delivered";
22
+ NotificationStatusEnum$1[NotificationStatusEnum$1["Error"] = 5] = "Error";
23
+ return NotificationStatusEnum$1;
24
+ }({});
9
25
 
10
26
  //#endregion
11
- exports.decodeFromBase64 = decodeFromBase64;
12
- exports.encodeToBase64 = encodeToBase64;
27
+ exports.NotificationStatusEnum = NotificationStatusEnum;
28
+ exports.NotificationTypeEnum = NotificationTypeEnum;
13
29
  //# sourceMappingURL=index9.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index9.cjs","names":["content: string"],"sources":["../src/utils/base64.ts"],"sourcesContent":["export function encodeToBase64(content: string): string {\n return Buffer.from(content).toString('base64')\n}\n\nexport function decodeFromBase64(content: string): string {\n return Buffer.from(content, 'base64').toString('utf-8')\n}\n"],"mappings":";;AAAA,SAAgB,eAAeA,SAAyB;AACtD,QAAO,OAAO,KAAK,QAAQ,CAAC,SAAS,SAAS;AAC/C;AAED,SAAgB,iBAAiBA,SAAyB;AACxD,QAAO,OAAO,KAAK,SAAS,SAAS,CAAC,SAAS,QAAQ;AACxD"}
1
+ {"version":3,"file":"index9.cjs","names":[],"sources":["../src/types/notifications.d.ts"],"sourcesContent":["export type NotificationType = 3 | 6 | 7 | 8 | 10 | 11 | 15 | 26 | 33 | 34 | 35\n\nexport enum NotificationTypeEnum {\n 'Profile data validation' = 3,\n 'Document received' = 6,\n 'Document validated' = 7,\n 'Document cancelled' = 8,\n 'User profile changed' = 10,\n 'Taxpayer profile changed' = 11,\n 'Document rejection initiated' = 15,\n 'ERP data validation' = 26,\n 'Documents processing summary' = 33,\n 'Document Template Published' = 34,\n 'Document Template Deletion' = 35,\n}\n\nexport type NotificationStatus = 1 | 2 | 3 | 4 | 5\n\nexport enum NotificationStatusEnum {\n 'New' = 1,\n 'Pending' = 2,\n 'Batched' = 3,\n 'Delivered' = 4,\n 'Error' = 5,\n}\n\nexport type NotificationDeliveryAttempt = {\n attemptDateTime: string\n status: string\n statusDetails: string\n}\n\nexport type NotificationMetadata = {\n hasNext: boolean\n}\n\nexport type Notification = {\n notificationId: string\n receiverNName: string\n notificationDeliveryId: string\n creationDateTime: string\n receivedDateTime: string\n notificationSubject: string\n deliveredDateTime: string\n typeId: string\n typeName: string\n finalMessage: string\n address: string\n language: string\n status: string\n deliveryAttempts: NotificationDeliveryAttempt[]\n}\n\nexport type NotificationResponse = {\n notifications: Notification[]\n metadata: NotificationMetadata\n}\n\nexport type NotificationSearchParams = {\n dateFrom?: string\n dateTo?: string\n type?: NotificationType\n language?: string\n status?: NotificationStatus\n pageNo?: number\n pageSize?: number\n}\n"],"mappings":";;AAEA,IAAY,wEAAL;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AACD;AAID,IAAY,4EAAL;AACL;AACA;AACA;AACA;AACA;;AACD"}
@@ -1,5 +1,5 @@
1
1
  //#region src/utils/apiQueue.d.ts
2
- // A very small utility that provides per-endpoint request queuing with fixed-window rate-limits.
2
+ // A very small utility that provides per-endpoint request queuing with rate-limits.
3
3
  // The goal is to make sure that we never exceed the vendor-defined limits while also ensuring
4
4
  // that every request is eventually executed.
5
5
  //
@@ -22,16 +22,24 @@ Search Taxpayer's TIN : 60
22
22
  Taxpayer's QR Code : 60
23
23
  */
24
24
  type ApiCategory = "loginTaxpayer" | "loginIntermediary" | "submitDocuments" | "getSubmission" | "cancelDocument" | "rejectDocument" | "getDocument" | "getDocumentDetails" | "getRecentDocuments" | "searchDocuments" | "searchTin" | "taxpayerQr" | "default";
25
+ type Task<T> = () => Promise<T>;
25
26
  /**
26
27
  * Public helper to schedule a request according to the category's limits.
27
28
  * Rate limits are enforced per clientId, so multiple instances with the same
28
29
  * clientId will share rate limiters, while different clientIds get separate limiters.
30
+ *
31
+ * This implementation uses a sliding window to track all requests within the time window.
29
32
  */
30
- declare function queueRequest<T>(clientId: string, category: ApiCategory, fn: () => Promise<T>, debug?: boolean): Promise<T>;
33
+ declare function queueRequest<T>(clientId: string, category: ApiCategory, task: Task<T>, debug?: boolean): Promise<T>;
34
+ /**
35
+ * Cleanup function to clear all queues and timers for a specific client.
36
+ * Useful for testing or cleanup on application shutdown.
37
+ */
38
+ declare function clearQueue(clientId: string, category?: ApiCategory): void;
31
39
  /**
32
40
  * Very naive path-based category detection. If no matcher fits, the `default` category
33
41
  * (effectively unlimited) is returned. Adjust these heuristics as your API surface evolves.
34
42
  */
35
43
  declare function categorizeRequest(path: string, method?: string): ApiCategory;
36
44
  //#endregion
37
- export { ApiCategory, categorizeRequest, queueRequest };
45
+ export { ApiCategory, categorizeRequest, clearQueue, queueRequest };
@@ -1,3 +1,3 @@
1
- import { categorizeRequest, queueRequest } from "../apiQueue-CCrZMnMu.js";
1
+ import { categorizeRequest, clearQueue, queueRequest } from "../apiQueue-B6Q644Bz.js";
2
2
 
3
- export { categorizeRequest, queueRequest };
3
+ export { categorizeRequest, clearQueue, queueRequest };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ripwords/myinvois-client",
3
- "version": "0.2.41",
3
+ "version": "0.3.0",
4
4
  "description": "MyInvois client",
5
5
  "scripts": {
6
6
  "build": "tsdown",
@@ -1,182 +0,0 @@
1
- //#region src/utils/apiQueue.ts
2
- const WINDOW = 6e4;
3
- const RATE_LIMITS = {
4
- loginTaxpayer: {
5
- limit: 12,
6
- windowMs: WINDOW
7
- },
8
- loginIntermediary: {
9
- limit: 12,
10
- windowMs: WINDOW
11
- },
12
- submitDocuments: {
13
- limit: 100,
14
- windowMs: WINDOW
15
- },
16
- getSubmission: {
17
- limit: 300,
18
- windowMs: WINDOW
19
- },
20
- cancelDocument: {
21
- limit: 12,
22
- windowMs: WINDOW
23
- },
24
- rejectDocument: {
25
- limit: 12,
26
- windowMs: WINDOW
27
- },
28
- getDocument: {
29
- limit: 60,
30
- windowMs: WINDOW
31
- },
32
- getDocumentDetails: {
33
- limit: 125,
34
- windowMs: WINDOW
35
- },
36
- getRecentDocuments: {
37
- limit: 12,
38
- windowMs: WINDOW
39
- },
40
- searchDocuments: {
41
- limit: 12,
42
- windowMs: WINDOW
43
- },
44
- searchTin: {
45
- limit: 60,
46
- windowMs: WINDOW
47
- },
48
- taxpayerQr: {
49
- limit: 60,
50
- windowMs: WINDOW
51
- },
52
- default: {
53
- limit: 12,
54
- windowMs: WINDOW
55
- }
56
- };
57
- /**
58
- * A token-bucket style rate-limiter with queuing.
59
- * Uses a sliding window approach to allow bursts while respecting overall limits.
60
- * Each category gets its own instance so limits remain isolated.
61
- */
62
- var RateLimiter = class {
63
- limit;
64
- windowMs;
65
- minInterval;
66
- queue = [];
67
- nextAvailable = 0;
68
- timer = null;
69
- requestTimes = [];
70
- isProcessing = false;
71
- constructor(config) {
72
- this.limit = config.limit;
73
- this.windowMs = config.windowMs;
74
- this.minInterval = Math.ceil(this.windowMs / this.limit * .5);
75
- }
76
- drainQueue() {
77
- if (this.isProcessing || this.queue.length === 0) return;
78
- this.isProcessing = true;
79
- try {
80
- const now = Date.now();
81
- this.requestTimes = this.requestTimes.filter((time) => now - time < this.windowMs);
82
- if (this.requestTimes.length >= this.limit) {
83
- const oldestRequest = Math.min(...this.requestTimes);
84
- const nextAvailable = oldestRequest + this.windowMs;
85
- this.scheduleNextDrain(nextAvailable - now);
86
- return;
87
- }
88
- if (now < this.nextAvailable) {
89
- this.scheduleNextDrain(this.nextAvailable - now);
90
- return;
91
- }
92
- const next = this.queue.shift();
93
- const requestStartTime = Date.now();
94
- this.requestTimes.push(requestStartTime);
95
- this.nextAvailable = requestStartTime + this.minInterval;
96
- next();
97
- } finally {
98
- this.isProcessing = false;
99
- }
100
- if (this.queue.length > 0) {
101
- const now = Date.now();
102
- const delay = Math.max(1, this.nextAvailable - now);
103
- this.scheduleNextDrain(delay);
104
- }
105
- }
106
- scheduleNextDrain(delay) {
107
- if (this.timer) clearTimeout(this.timer);
108
- this.timer = setTimeout(() => {
109
- this.timer = null;
110
- this.drainQueue();
111
- }, Math.max(0, delay));
112
- }
113
- get queueSize() {
114
- return this.queue.length;
115
- }
116
- cleanup() {
117
- if (this.timer) {
118
- clearTimeout(this.timer);
119
- this.timer = null;
120
- }
121
- this.queue = [];
122
- this.requestTimes = [];
123
- this.isProcessing = false;
124
- }
125
- schedule(fn, debug = false, category) {
126
- return new Promise((resolve, reject) => {
127
- const execute = () => {
128
- if (debug && category) console.log(`[apiQueue] ▶️ Executing request (${category}). Remaining queue: ${this.queue.length}`);
129
- try {
130
- const result = fn();
131
- if (result && typeof result.then === "function") result.then(resolve).catch(reject);
132
- else resolve(result);
133
- } catch (err) {
134
- reject(err);
135
- }
136
- };
137
- if (debug && category) console.log(`[apiQueue] ⏳ Queued request (${category}). Queue length before push: ${this.queue.length}`);
138
- this.queue.push(execute);
139
- this.drainQueue();
140
- });
141
- }
142
- };
143
- const limiterRegistry = /* @__PURE__ */ new Map();
144
- function getLimiter(clientId, category) {
145
- if (!limiterRegistry.has(clientId)) limiterRegistry.set(clientId, /* @__PURE__ */ new Map());
146
- const clientLimiters = limiterRegistry.get(clientId);
147
- if (!clientLimiters.has(category)) clientLimiters.set(category, new RateLimiter(RATE_LIMITS[category]));
148
- return clientLimiters.get(category);
149
- }
150
- /**
151
- * Public helper to schedule a request according to the category's limits.
152
- * Rate limits are enforced per clientId, so multiple instances with the same
153
- * clientId will share rate limiters, while different clientIds get separate limiters.
154
- */
155
- function queueRequest(clientId, category, fn, debug = false) {
156
- const limiter = getLimiter(clientId, category);
157
- return limiter.schedule(fn, debug, category);
158
- }
159
- /**
160
- * Very naive path-based category detection. If no matcher fits, the `default` category
161
- * (effectively unlimited) is returned. Adjust these heuristics as your API surface evolves.
162
- */
163
- function categorizeRequest(path, method = "GET") {
164
- const cleanPath = path.toLowerCase();
165
- const isPost = method?.toUpperCase() === "POST";
166
- if (cleanPath.includes("/documentsubmissions")) return isPost ? "submitDocuments" : "getSubmission";
167
- if (cleanPath.includes("/documents/recent")) return "getRecentDocuments";
168
- if (cleanPath.includes("/documents/search")) return "searchDocuments";
169
- if (cleanPath.includes("/documents/state/") && cleanPath.endsWith("/state")) return "cancelDocument";
170
- if (/\/documents\/[^/]+\/raw$/.test(cleanPath)) return "getDocument";
171
- if (/\/documents\/[^/]+\/details$/.test(cleanPath)) return "getDocumentDetails";
172
- if (cleanPath.includes("/taxpayer/search/tin")) return "searchTin";
173
- if (cleanPath.includes("/taxpayer/validate/")) return "searchTin";
174
- if (cleanPath.includes("/taxpayer/qrcode")) return "taxpayerQr";
175
- if (cleanPath.includes("/searchtin")) return "searchTin";
176
- if (cleanPath.includes("/qrcode")) return "taxpayerQr";
177
- if (cleanPath.includes("/connect/token")) return "loginTaxpayer";
178
- return "default";
179
- }
180
-
181
- //#endregion
182
- export { categorizeRequest, queueRequest };
@@ -1,195 +0,0 @@
1
-
2
- //#region src/utils/apiQueue.ts
3
- const WINDOW = 6e4;
4
- const RATE_LIMITS = {
5
- loginTaxpayer: {
6
- limit: 12,
7
- windowMs: WINDOW
8
- },
9
- loginIntermediary: {
10
- limit: 12,
11
- windowMs: WINDOW
12
- },
13
- submitDocuments: {
14
- limit: 100,
15
- windowMs: WINDOW
16
- },
17
- getSubmission: {
18
- limit: 300,
19
- windowMs: WINDOW
20
- },
21
- cancelDocument: {
22
- limit: 12,
23
- windowMs: WINDOW
24
- },
25
- rejectDocument: {
26
- limit: 12,
27
- windowMs: WINDOW
28
- },
29
- getDocument: {
30
- limit: 60,
31
- windowMs: WINDOW
32
- },
33
- getDocumentDetails: {
34
- limit: 125,
35
- windowMs: WINDOW
36
- },
37
- getRecentDocuments: {
38
- limit: 12,
39
- windowMs: WINDOW
40
- },
41
- searchDocuments: {
42
- limit: 12,
43
- windowMs: WINDOW
44
- },
45
- searchTin: {
46
- limit: 60,
47
- windowMs: WINDOW
48
- },
49
- taxpayerQr: {
50
- limit: 60,
51
- windowMs: WINDOW
52
- },
53
- default: {
54
- limit: 12,
55
- windowMs: WINDOW
56
- }
57
- };
58
- /**
59
- * A token-bucket style rate-limiter with queuing.
60
- * Uses a sliding window approach to allow bursts while respecting overall limits.
61
- * Each category gets its own instance so limits remain isolated.
62
- */
63
- var RateLimiter = class {
64
- limit;
65
- windowMs;
66
- minInterval;
67
- queue = [];
68
- nextAvailable = 0;
69
- timer = null;
70
- requestTimes = [];
71
- isProcessing = false;
72
- constructor(config) {
73
- this.limit = config.limit;
74
- this.windowMs = config.windowMs;
75
- this.minInterval = Math.ceil(this.windowMs / this.limit * .5);
76
- }
77
- drainQueue() {
78
- if (this.isProcessing || this.queue.length === 0) return;
79
- this.isProcessing = true;
80
- try {
81
- const now = Date.now();
82
- this.requestTimes = this.requestTimes.filter((time) => now - time < this.windowMs);
83
- if (this.requestTimes.length >= this.limit) {
84
- const oldestRequest = Math.min(...this.requestTimes);
85
- const nextAvailable = oldestRequest + this.windowMs;
86
- this.scheduleNextDrain(nextAvailable - now);
87
- return;
88
- }
89
- if (now < this.nextAvailable) {
90
- this.scheduleNextDrain(this.nextAvailable - now);
91
- return;
92
- }
93
- const next = this.queue.shift();
94
- const requestStartTime = Date.now();
95
- this.requestTimes.push(requestStartTime);
96
- this.nextAvailable = requestStartTime + this.minInterval;
97
- next();
98
- } finally {
99
- this.isProcessing = false;
100
- }
101
- if (this.queue.length > 0) {
102
- const now = Date.now();
103
- const delay = Math.max(1, this.nextAvailable - now);
104
- this.scheduleNextDrain(delay);
105
- }
106
- }
107
- scheduleNextDrain(delay) {
108
- if (this.timer) clearTimeout(this.timer);
109
- this.timer = setTimeout(() => {
110
- this.timer = null;
111
- this.drainQueue();
112
- }, Math.max(0, delay));
113
- }
114
- get queueSize() {
115
- return this.queue.length;
116
- }
117
- cleanup() {
118
- if (this.timer) {
119
- clearTimeout(this.timer);
120
- this.timer = null;
121
- }
122
- this.queue = [];
123
- this.requestTimes = [];
124
- this.isProcessing = false;
125
- }
126
- schedule(fn, debug = false, category) {
127
- return new Promise((resolve, reject) => {
128
- const execute = () => {
129
- if (debug && category) console.log(`[apiQueue] ▶️ Executing request (${category}). Remaining queue: ${this.queue.length}`);
130
- try {
131
- const result = fn();
132
- if (result && typeof result.then === "function") result.then(resolve).catch(reject);
133
- else resolve(result);
134
- } catch (err) {
135
- reject(err);
136
- }
137
- };
138
- if (debug && category) console.log(`[apiQueue] ⏳ Queued request (${category}). Queue length before push: ${this.queue.length}`);
139
- this.queue.push(execute);
140
- this.drainQueue();
141
- });
142
- }
143
- };
144
- const limiterRegistry = /* @__PURE__ */ new Map();
145
- function getLimiter(clientId, category) {
146
- if (!limiterRegistry.has(clientId)) limiterRegistry.set(clientId, /* @__PURE__ */ new Map());
147
- const clientLimiters = limiterRegistry.get(clientId);
148
- if (!clientLimiters.has(category)) clientLimiters.set(category, new RateLimiter(RATE_LIMITS[category]));
149
- return clientLimiters.get(category);
150
- }
151
- /**
152
- * Public helper to schedule a request according to the category's limits.
153
- * Rate limits are enforced per clientId, so multiple instances with the same
154
- * clientId will share rate limiters, while different clientIds get separate limiters.
155
- */
156
- function queueRequest(clientId, category, fn, debug = false) {
157
- const limiter = getLimiter(clientId, category);
158
- return limiter.schedule(fn, debug, category);
159
- }
160
- /**
161
- * Very naive path-based category detection. If no matcher fits, the `default` category
162
- * (effectively unlimited) is returned. Adjust these heuristics as your API surface evolves.
163
- */
164
- function categorizeRequest(path, method = "GET") {
165
- const cleanPath = path.toLowerCase();
166
- const isPost = method?.toUpperCase() === "POST";
167
- if (cleanPath.includes("/documentsubmissions")) return isPost ? "submitDocuments" : "getSubmission";
168
- if (cleanPath.includes("/documents/recent")) return "getRecentDocuments";
169
- if (cleanPath.includes("/documents/search")) return "searchDocuments";
170
- if (cleanPath.includes("/documents/state/") && cleanPath.endsWith("/state")) return "cancelDocument";
171
- if (/\/documents\/[^/]+\/raw$/.test(cleanPath)) return "getDocument";
172
- if (/\/documents\/[^/]+\/details$/.test(cleanPath)) return "getDocumentDetails";
173
- if (cleanPath.includes("/taxpayer/search/tin")) return "searchTin";
174
- if (cleanPath.includes("/taxpayer/validate/")) return "searchTin";
175
- if (cleanPath.includes("/taxpayer/qrcode")) return "taxpayerQr";
176
- if (cleanPath.includes("/searchtin")) return "searchTin";
177
- if (cleanPath.includes("/qrcode")) return "taxpayerQr";
178
- if (cleanPath.includes("/connect/token")) return "loginTaxpayer";
179
- return "default";
180
- }
181
-
182
- //#endregion
183
- Object.defineProperty(exports, 'categorizeRequest', {
184
- enumerable: true,
185
- get: function () {
186
- return categorizeRequest;
187
- }
188
- });
189
- Object.defineProperty(exports, 'queueRequest', {
190
- enumerable: true,
191
- get: function () {
192
- return queueRequest;
193
- }
194
- });
195
- //# sourceMappingURL=apiQueue-Djd7WlnV.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"apiQueue-Djd7WlnV.cjs","names":["RATE_LIMITS: Record<ApiCategory, RateLimitConfig>","config: RateLimitConfig","delay: number","fn: () => Promise<T>","debug: boolean","category?: ApiCategory","clientId: string","category: ApiCategory","path: string","method: string"],"sources":["../src/utils/apiQueue.ts"],"sourcesContent":["// A very small utility that provides per-endpoint request queuing with fixed-window rate-limits.\n// The goal is to make sure that we never exceed the vendor-defined limits while also ensuring\n// that every request is eventually executed.\n//\n// NOTE: This is intentionally minimal – no external dependencies are introduced.\n// If you need more advanced features (persistence, jitter, etc.) consider a library such as `bottleneck`.\n\n/*\nRate-limit specification (per 60-second window)\n----------------------------------------------\nLogin as Taxpayer System : 12\nLogin as Intermediary System : 12\nSubmit Documents : 100\nGet Submission : 300\nCancel Document : 12\nReject Document : 12\nGet Document : 60\nGet Document Details : 125\nGet Recent Documents : 12\nSearch Documents : 12\nSearch Taxpayer's TIN : 60\nTaxpayer's QR Code : 60\n*/\n\nexport type ApiCategory =\n | 'loginTaxpayer'\n | 'loginIntermediary'\n | 'submitDocuments'\n | 'getSubmission'\n | 'cancelDocument'\n | 'rejectDocument'\n | 'getDocument'\n | 'getDocumentDetails'\n | 'getRecentDocuments'\n | 'searchDocuments'\n | 'searchTin'\n | 'taxpayerQr'\n | 'default'\n\ninterface RateLimitConfig {\n limit: number\n windowMs: number\n}\n\nconst WINDOW = 60_000 // 60 seconds\n\n// Hard-coded limits based on the specification above.\nconst RATE_LIMITS: Record<ApiCategory, RateLimitConfig> = {\n loginTaxpayer: { limit: 12, windowMs: WINDOW },\n loginIntermediary: { limit: 12, windowMs: WINDOW },\n submitDocuments: { limit: 100, windowMs: WINDOW },\n getSubmission: { limit: 300, windowMs: WINDOW },\n cancelDocument: { limit: 12, windowMs: WINDOW },\n rejectDocument: { limit: 12, windowMs: WINDOW },\n getDocument: { limit: 60, windowMs: WINDOW },\n getDocumentDetails: { limit: 125, windowMs: WINDOW },\n getRecentDocuments: { limit: 12, windowMs: WINDOW },\n searchDocuments: { limit: 12, windowMs: WINDOW },\n searchTin: { limit: 60, windowMs: WINDOW },\n taxpayerQr: { limit: 60, windowMs: WINDOW },\n default: { limit: 12, windowMs: WINDOW }, // minimum limit\n}\n\n/**\n * A token-bucket style rate-limiter with queuing.\n * Uses a sliding window approach to allow bursts while respecting overall limits.\n * Each category gets its own instance so limits remain isolated.\n */\nclass RateLimiter {\n private readonly limit: number\n private readonly windowMs: number\n private readonly minInterval: number\n\n private queue: Array<() => void> = []\n private nextAvailable = 0 // timestamp (ms) when the next request can be executed\n private timer: NodeJS.Timeout | null = null\n private requestTimes: number[] = [] // Track request timestamps for sliding window\n private isProcessing = false // Prevent race conditions in drainQueue\n\n constructor(config: RateLimitConfig) {\n this.limit = config.limit\n this.windowMs = config.windowMs\n // Use a more reasonable interval that allows bursts while preventing 429s\n // Allow bursts up to 50% of the limit, then space out remaining requests\n this.minInterval = Math.ceil((this.windowMs / this.limit) * 0.5) // 50% of even spacing\n }\n\n private drainQueue() {\n // Prevent race conditions by ensuring only one drainQueue runs at a time\n if (this.isProcessing || this.queue.length === 0) {\n return\n }\n\n this.isProcessing = true\n\n try {\n const now = Date.now()\n\n // Clean up old request times outside the window\n this.requestTimes = this.requestTimes.filter(\n time => now - time < this.windowMs,\n )\n\n // Check if we can make another request within the rate limit\n if (this.requestTimes.length >= this.limit) {\n // We've hit the limit, schedule for when the oldest request expires\n const oldestRequest = Math.min(...this.requestTimes)\n const nextAvailable = oldestRequest + this.windowMs\n\n this.scheduleNextDrain(nextAvailable - now)\n return\n }\n\n // Check minimum interval constraint\n if (now < this.nextAvailable) {\n // Too early – schedule when we're allowed to execute next\n this.scheduleNextDrain(this.nextAvailable - now)\n return\n }\n\n // Execute the next queued task\n const next = this.queue.shift()!\n const requestStartTime = Date.now()\n this.requestTimes.push(requestStartTime)\n this.nextAvailable = requestStartTime + this.minInterval\n\n // Execute the request immediately\n next()\n } finally {\n this.isProcessing = false\n }\n\n // After resetting isProcessing, check if there are more requests\n // and schedule the next drain with appropriate delay\n if (this.queue.length > 0) {\n // Calculate delay until we can process the next request\n const now = Date.now()\n const delay = Math.max(1, this.nextAvailable - now)\n\n // Use scheduleNextDrain to ensure only one timer is active\n this.scheduleNextDrain(delay)\n }\n }\n\n private scheduleNextDrain(delay: number) {\n if (this.timer) {\n clearTimeout(this.timer)\n }\n\n this.timer = setTimeout(\n () => {\n this.timer = null\n this.drainQueue()\n },\n Math.max(0, delay),\n )\n }\n\n get queueSize() {\n return this.queue.length\n }\n\n // Cleanup method to prevent memory leaks\n cleanup() {\n if (this.timer) {\n clearTimeout(this.timer)\n this.timer = null\n }\n this.queue = []\n this.requestTimes = []\n this.isProcessing = false\n }\n\n schedule<T>(\n fn: () => Promise<T>,\n debug: boolean = false,\n category?: ApiCategory,\n ): Promise<T> {\n return new Promise((resolve, reject) => {\n const execute = () => {\n if (debug && category) {\n console.log(\n `[apiQueue] ▶️ Executing request (${category}). Remaining queue: ${this.queue.length}`,\n )\n }\n try {\n const result = fn()\n if (result && typeof (result as any).then === 'function') {\n ;(result as Promise<T>).then(resolve).catch(reject)\n } else {\n resolve(result as T)\n }\n } catch (err) {\n reject(err)\n }\n }\n\n if (debug && category) {\n console.log(\n `[apiQueue] ⏳ Queued request (${category}). Queue length before push: ${this.queue.length}`,\n )\n }\n\n this.queue.push(execute)\n this.drainQueue()\n })\n }\n}\n\n// A shared registry of limiters keyed by clientId, then by category\n// This ensures that different clientIds get separate rate limiters,\n// while multiple instances with the same clientId share the same limiters.\nconst limiterRegistry = new Map<string, Map<ApiCategory, RateLimiter>>()\n\nfunction getLimiter(clientId: string, category: ApiCategory): RateLimiter {\n if (!limiterRegistry.has(clientId)) {\n limiterRegistry.set(clientId, new Map<ApiCategory, RateLimiter>())\n }\n\n const clientLimiters = limiterRegistry.get(clientId)!\n\n if (!clientLimiters.has(category)) {\n clientLimiters.set(category, new RateLimiter(RATE_LIMITS[category]))\n }\n\n return clientLimiters.get(category)!\n}\n\n/**\n * Public helper to schedule a request according to the category's limits.\n * Rate limits are enforced per clientId, so multiple instances with the same\n * clientId will share rate limiters, while different clientIds get separate limiters.\n */\nexport function queueRequest<T>(\n clientId: string,\n category: ApiCategory,\n fn: () => Promise<T>,\n debug: boolean = false,\n): Promise<T> {\n const limiter = getLimiter(clientId, category)\n return limiter.schedule(fn, debug, category)\n}\n\n/**\n * Very naive path-based category detection. If no matcher fits, the `default` category\n * (effectively unlimited) is returned. Adjust these heuristics as your API surface evolves.\n */\nexport function categorizeRequest(\n path: string,\n method: string = 'GET',\n): ApiCategory {\n const cleanPath = path.toLowerCase()\n const isPost = method?.toUpperCase() === 'POST'\n\n if (cleanPath.includes('/documentsubmissions')) {\n return isPost ? 'submitDocuments' : 'getSubmission'\n }\n\n // -----------------------------\n // v1.0 API endpoint matchers\n // -----------------------------\n\n // Get Recent Documents - /api/v1.0/documents/recent\n if (cleanPath.includes('/documents/recent')) {\n return 'getRecentDocuments'\n }\n\n // Search Documents - /api/v1.0/documents/search\n if (cleanPath.includes('/documents/search')) {\n return 'searchDocuments'\n }\n\n // Document state actions (cancel/reject) - PUT /api/v1.0/documents/state/{uuid}/state\n // Both cancel and reject use the same endpoint, differentiated only by request body\n if (cleanPath.includes('/documents/state/') && cleanPath.endsWith('/state')) {\n // Both cancelDocument and rejectDocument share the same rate limit (12 RPM)\n // Use cancelDocument category for both since they share the same bucket\n return 'cancelDocument'\n }\n\n // Document raw content - /api/v1.0/documents/{uuid}/raw\n if (/\\/documents\\/[^/]+\\/raw$/.test(cleanPath)) {\n return 'getDocument'\n }\n\n // Document details - /api/v1.0/documents/{uuid}/details\n if (/\\/documents\\/[^/]+\\/details$/.test(cleanPath)) {\n return 'getDocumentDetails'\n }\n\n // Taxpayer TIN search & validation share same limit bucket\n if (cleanPath.includes('/taxpayer/search/tin')) return 'searchTin'\n if (cleanPath.includes('/taxpayer/validate/')) return 'searchTin'\n\n // Taxpayer QR code info\n if (cleanPath.includes('/taxpayer/qrcode')) return 'taxpayerQr'\n\n // Legacy matchers (kept for backward compatibility)\n if (cleanPath.includes('/searchtin')) return 'searchTin'\n if (cleanPath.includes('/qrcode')) return 'taxpayerQr'\n if (cleanPath.includes('/connect/token')) {\n return 'loginTaxpayer'\n }\n\n return 'default'\n}\n"],"mappings":";;AA4CA,MAAM,SAAS;AAGf,MAAMA,cAAoD;CACxD,eAAe;EAAE,OAAO;EAAI,UAAU;CAAQ;CAC9C,mBAAmB;EAAE,OAAO;EAAI,UAAU;CAAQ;CAClD,iBAAiB;EAAE,OAAO;EAAK,UAAU;CAAQ;CACjD,eAAe;EAAE,OAAO;EAAK,UAAU;CAAQ;CAC/C,gBAAgB;EAAE,OAAO;EAAI,UAAU;CAAQ;CAC/C,gBAAgB;EAAE,OAAO;EAAI,UAAU;CAAQ;CAC/C,aAAa;EAAE,OAAO;EAAI,UAAU;CAAQ;CAC5C,oBAAoB;EAAE,OAAO;EAAK,UAAU;CAAQ;CACpD,oBAAoB;EAAE,OAAO;EAAI,UAAU;CAAQ;CACnD,iBAAiB;EAAE,OAAO;EAAI,UAAU;CAAQ;CAChD,WAAW;EAAE,OAAO;EAAI,UAAU;CAAQ;CAC1C,YAAY;EAAE,OAAO;EAAI,UAAU;CAAQ;CAC3C,SAAS;EAAE,OAAO;EAAI,UAAU;CAAQ;AACzC;;;;;;AAOD,IAAM,cAAN,MAAkB;CAChB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CAEjB,AAAQ,QAA2B,CAAE;CACrC,AAAQ,gBAAgB;CACxB,AAAQ,QAA+B;CACvC,AAAQ,eAAyB,CAAE;CACnC,AAAQ,eAAe;CAEvB,YAAYC,QAAyB;AACnC,OAAK,QAAQ,OAAO;AACpB,OAAK,WAAW,OAAO;AAGvB,OAAK,cAAc,KAAK,KAAM,KAAK,WAAW,KAAK,QAAS,GAAI;CACjE;CAED,AAAQ,aAAa;AAEnB,MAAI,KAAK,gBAAgB,KAAK,MAAM,WAAW,EAC7C;AAGF,OAAK,eAAe;AAEpB,MAAI;GACF,MAAM,MAAM,KAAK,KAAK;AAGtB,QAAK,eAAe,KAAK,aAAa,OACpC,UAAQ,MAAM,OAAO,KAAK,SAC3B;AAGD,OAAI,KAAK,aAAa,UAAU,KAAK,OAAO;IAE1C,MAAM,gBAAgB,KAAK,IAAI,GAAG,KAAK,aAAa;IACpD,MAAM,gBAAgB,gBAAgB,KAAK;AAE3C,SAAK,kBAAkB,gBAAgB,IAAI;AAC3C;GACD;AAGD,OAAI,MAAM,KAAK,eAAe;AAE5B,SAAK,kBAAkB,KAAK,gBAAgB,IAAI;AAChD;GACD;GAGD,MAAM,OAAO,KAAK,MAAM,OAAO;GAC/B,MAAM,mBAAmB,KAAK,KAAK;AACnC,QAAK,aAAa,KAAK,iBAAiB;AACxC,QAAK,gBAAgB,mBAAmB,KAAK;AAG7C,SAAM;EACP,UAAS;AACR,QAAK,eAAe;EACrB;AAID,MAAI,KAAK,MAAM,SAAS,GAAG;GAEzB,MAAM,MAAM,KAAK,KAAK;GACtB,MAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,gBAAgB,IAAI;AAGnD,QAAK,kBAAkB,MAAM;EAC9B;CACF;CAED,AAAQ,kBAAkBC,OAAe;AACvC,MAAI,KAAK,MACP,cAAa,KAAK,MAAM;AAG1B,OAAK,QAAQ,WACX,MAAM;AACJ,QAAK,QAAQ;AACb,QAAK,YAAY;EAClB,GACD,KAAK,IAAI,GAAG,MAAM,CACnB;CACF;CAED,IAAI,YAAY;AACd,SAAO,KAAK,MAAM;CACnB;CAGD,UAAU;AACR,MAAI,KAAK,OAAO;AACd,gBAAa,KAAK,MAAM;AACxB,QAAK,QAAQ;EACd;AACD,OAAK,QAAQ,CAAE;AACf,OAAK,eAAe,CAAE;AACtB,OAAK,eAAe;CACrB;CAED,SACEC,IACAC,QAAiB,OACjBC,UACY;AACZ,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;GACtC,MAAM,UAAU,MAAM;AACpB,QAAI,SAAS,SACX,SAAQ,KACL,oCAAoC,SAAS,sBAAsB,KAAK,MAAM,OAAO,EACvF;AAEH,QAAI;KACF,MAAM,SAAS,IAAI;AACnB,SAAI,iBAAkB,OAAe,SAAS,WAC3C,CAAC,OAAsB,KAAK,QAAQ,CAAC,MAAM,OAAO;SAEnD,SAAQ,OAAY;IAEvB,SAAQ,KAAK;AACZ,YAAO,IAAI;IACZ;GACF;AAED,OAAI,SAAS,SACX,SAAQ,KACL,+BAA+B,SAAS,+BAA+B,KAAK,MAAM,OAAO,EAC3F;AAGH,QAAK,MAAM,KAAK,QAAQ;AACxB,QAAK,YAAY;EAClB;CACF;AACF;AAKD,MAAM,kCAAkB,IAAI;AAE5B,SAAS,WAAWC,UAAkBC,UAAoC;AACxE,MAAK,gBAAgB,IAAI,SAAS,CAChC,iBAAgB,IAAI,0BAAU,IAAI,MAAgC;CAGpE,MAAM,iBAAiB,gBAAgB,IAAI,SAAS;AAEpD,MAAK,eAAe,IAAI,SAAS,CAC/B,gBAAe,IAAI,UAAU,IAAI,YAAY,YAAY,WAAW;AAGtE,QAAO,eAAe,IAAI,SAAS;AACpC;;;;;;AAOD,SAAgB,aACdD,UACAC,UACAJ,IACAC,QAAiB,OACL;CACZ,MAAM,UAAU,WAAW,UAAU,SAAS;AAC9C,QAAO,QAAQ,SAAS,IAAI,OAAO,SAAS;AAC7C;;;;;AAMD,SAAgB,kBACdI,MACAC,SAAiB,OACJ;CACb,MAAM,YAAY,KAAK,aAAa;CACpC,MAAM,SAAS,QAAQ,aAAa,KAAK;AAEzC,KAAI,UAAU,SAAS,uBAAuB,CAC5C,QAAO,SAAS,oBAAoB;AAQtC,KAAI,UAAU,SAAS,oBAAoB,CACzC,QAAO;AAIT,KAAI,UAAU,SAAS,oBAAoB,CACzC,QAAO;AAKT,KAAI,UAAU,SAAS,oBAAoB,IAAI,UAAU,SAAS,SAAS,CAGzE,QAAO;AAIT,KAAI,2BAA2B,KAAK,UAAU,CAC5C,QAAO;AAIT,KAAI,+BAA+B,KAAK,UAAU,CAChD,QAAO;AAIT,KAAI,UAAU,SAAS,uBAAuB,CAAE,QAAO;AACvD,KAAI,UAAU,SAAS,sBAAsB,CAAE,QAAO;AAGtD,KAAI,UAAU,SAAS,mBAAmB,CAAE,QAAO;AAGnD,KAAI,UAAU,SAAS,aAAa,CAAE,QAAO;AAC7C,KAAI,UAAU,SAAS,UAAU,CAAE,QAAO;AAC1C,KAAI,UAAU,SAAS,iBAAiB,CACtC,QAAO;AAGT,QAAO;AACR"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index14.cjs","names":["certificatePem: string","issues: string[]","recommendations: string[]","dn: string","fields: Record<string, string>","invoices: InvoiceV1_1[]","str: string","result: DiagnosticResult"],"sources":["../src/utils/signature-diagnostics.ts"],"sourcesContent":["import crypto from 'crypto'\nimport type { InvoiceV1_1 } from '../types'\nimport {\n extractCertificateInfo,\n calculateDocumentDigest,\n calculateSignedPropertiesDigest,\n createSignedProperties,\n calculateCertificateDigest,\n} from './document'\n\nexport interface CertificateAnalysisResult {\n organizationIdentifier?: string\n serialNumber?: string\n issuerName: string\n subjectName: string\n issues: string[]\n recommendations: string[]\n}\n\nexport interface SignatureAnalysisResult {\n documentDigest: string\n certificateDigest: string\n signedPropertiesDigest: string\n issues: string[]\n recommendations: string[]\n}\n\nexport interface DiagnosticResult {\n certificateAnalysis: CertificateAnalysisResult\n signatureAnalysis: SignatureAnalysisResult\n summary: {\n totalIssues: number\n certificateIssues: number\n signatureIssues: number\n }\n}\n\n/**\n * Analyzes certificate for MyInvois compatibility issues\n */\nfunction analyzeCertificateForDiagnostics(\n certificatePem: string,\n): CertificateAnalysisResult {\n const issues: string[] = []\n const recommendations: string[] = []\n\n try {\n const cert = new crypto.X509Certificate(certificatePem)\n const certInfo = extractCertificateInfo(certificatePem)\n\n // Parse subject fields for MyInvois analysis\n const parseSubjectFields = (dn: string) => {\n const fields: Record<string, string> = {}\n dn.split('\\n').forEach(line => {\n const trimmed = line.trim()\n if (trimmed.includes('=')) {\n const [key, ...valueParts] = trimmed.split('=')\n if (key) {\n fields[key.trim()] = valueParts.join('=').trim()\n }\n }\n })\n return fields\n }\n\n const subjectFields = parseSubjectFields(cert.subject)\n const organizationIdentifier =\n subjectFields['organizationIdentifier'] || subjectFields['2.5.4.97']\n const serialNumber = subjectFields['serialNumber']\n\n // DS311 - TIN Mismatch Analysis\n if (!organizationIdentifier) {\n issues.push(\n 'DS311: Certificate missing organizationIdentifier field (TIN)',\n )\n recommendations.push(\n 'CRITICAL: Generate new certificate with organizationIdentifier matching your MyInvois TIN',\n )\n recommendations.push(\n 'Portal Error: \"Signer of invoice doesn\\'t match the submitter of document. TIN doesn\\'t match with the OI.\"',\n )\n } else {\n // Additional TIN format validation\n if (organizationIdentifier.length < 10) {\n issues.push(\n 'DS311: OrganizationIdentifier (TIN) appears too short - may cause submission rejection',\n )\n recommendations.push(\n 'Verify TIN format matches exactly what is registered in MyInvois',\n )\n }\n }\n\n // DS312 - Registration Number Analysis\n if (!serialNumber) {\n issues.push(\n 'DS312: Certificate missing serialNumber field (business registration)',\n )\n recommendations.push(\n 'CRITICAL: Generate new certificate with serialNumber matching your business registration',\n )\n recommendations.push(\n 'Portal Error: \"Submitter registration/identity number doesn\\'t match with the certificate SERIALNUMBER.\"',\n )\n }\n\n // DS329 - Certificate Trust Analysis\n if (cert.issuer === cert.subject) {\n issues.push(\n 'DS329: Self-signed certificate detected - will fail chain of trust validation',\n )\n recommendations.push(\n 'BLOCKING: Obtain certificate from MyInvois-approved CA:',\n )\n recommendations.push('• MSC Trustgate Sdn Bhd')\n recommendations.push('• DigiCert Sdn Bhd')\n recommendations.push('• Cybersign Asia Sdn Bhd')\n recommendations.push(\n 'Portal Error: \"Certificate is not valid according to the chain of trust validation or has been issued by an untrusted certificate authority.\"',\n )\n } else {\n // Check if issuer looks like a known CA\n const issuerName = cert.issuer.toLowerCase()\n const approvedCAs = ['msc trustgate', 'digicert', 'cybersign']\n const isFromApprovedCA = approvedCAs.some(ca => issuerName.includes(ca))\n\n if (!isFromApprovedCA) {\n issues.push('DS329: Certificate may not be from MyInvois-approved CA')\n recommendations.push(\n 'Verify certificate was issued by an approved CA for MyInvois',\n )\n }\n }\n\n // DS326 - Issuer Name Format Analysis (Enhanced)\n const rawIssuer = cert.issuer\n const normalizedIssuer = certInfo.issuerName\n\n // Check for issues in the NORMALIZED issuer (these are actual problems)\n const normalizedIssuerIssues = [\n {\n check: normalizedIssuer.includes('\\n'),\n issue: 'Normalized issuer still contains newlines',\n },\n {\n check: normalizedIssuer.includes(' '),\n issue: 'Normalized issuer contains double spaces',\n },\n {\n check: /=\\s+/.test(normalizedIssuer),\n issue: 'Normalized issuer has spaces after equals',\n },\n {\n check: /\\s+=/.test(normalizedIssuer),\n issue: 'Normalized issuer has spaces before equals',\n },\n {\n check: normalizedIssuer.includes('\\r'),\n issue: 'Normalized issuer contains carriage returns',\n },\n ]\n\n // Only report actual issues in the normalized version that will cause portal errors\n const hasActualFormatIssues = normalizedIssuerIssues.some(\n ({ check, issue }) => {\n if (check) {\n issues.push(`DS326: ${issue} - will cause X509IssuerName mismatch`)\n return true\n }\n return false\n },\n )\n\n // Check if raw issuer has issues but normalized version is OK (informational)\n const hasRawIssuesButNormalizedOk =\n rawIssuer.includes('\\n') && !normalizedIssuer.includes('\\n')\n\n if (hasActualFormatIssues) {\n recommendations.push(\n 'CRITICAL: Fix issuer name normalization in signature generation',\n )\n recommendations.push(\n 'Portal Error: \"Certificate X509IssuerName doesn\\'t match the X509IssuerName value provided in the signed properties section.\"',\n )\n recommendations.push(\n 'The normalization function is not properly formatting the issuer name',\n )\n recommendations.push(\n 'Debug: Check document.ts extractCertificateInfo() normalization logic',\n )\n } else if (hasRawIssuesButNormalizedOk) {\n // This is informational - normalization is working correctly\n console.log(\n 'ℹ️ Note: Raw certificate issuer has newlines but normalization is handling them correctly',\n )\n }\n\n // Additional certificate validity checks\n const now = new Date()\n const validFrom = new Date(cert.validFrom)\n const validTo = new Date(cert.validTo)\n\n if (now < validFrom) {\n issues.push('DS329: Certificate not yet valid (future start date)')\n recommendations.push('Wait until certificate validity period begins')\n }\n\n if (now > validTo) {\n issues.push('DS329: Certificate has expired')\n recommendations.push(\n 'BLOCKING: Renew certificate - expired certificates are rejected',\n )\n }\n\n // Check certificate key usage (if available)\n try {\n if (cert.keyUsage && !cert.keyUsage.includes('digital signature')) {\n issues.push('DS333: Certificate lacks digitalSignature key usage')\n recommendations.push(\n 'Generate new certificate with digitalSignature key usage enabled',\n )\n }\n } catch {\n // Key usage might not be available in all certificates\n console.log('Note: Could not check key usage extensions')\n }\n\n return {\n organizationIdentifier,\n serialNumber,\n issuerName: certInfo.issuerName,\n subjectName: certInfo.subjectName,\n issues,\n recommendations,\n }\n } catch (error) {\n issues.push(`Certificate parsing failed: ${error}`)\n recommendations.push('Verify certificate format and validity')\n\n return {\n issuerName: '',\n subjectName: '',\n issues,\n recommendations,\n }\n }\n}\n\n/**\n * Analyzes signature generation for potential issues\n */\nfunction analyzeSignatureForDiagnostics(\n invoices: InvoiceV1_1[],\n certificatePem: string,\n): SignatureAnalysisResult {\n const issues: string[] = []\n const recommendations: string[] = []\n\n try {\n // Step 1: Document digest\n const documentDigest = calculateDocumentDigest(invoices)\n\n // Step 2: Certificate digest\n const certificateDigest = calculateCertificateDigest(certificatePem)\n\n // Step 3: Extract certificate info\n const certInfo = extractCertificateInfo(certificatePem)\n const signingTime = new Date().toISOString()\n\n // Step 4: Create signed properties\n const signedProperties = createSignedProperties(\n certificateDigest,\n signingTime,\n certInfo.issuerName,\n certInfo.serialNumber,\n )\n\n // Step 5: Signed properties digest\n const signedPropertiesDigest =\n calculateSignedPropertiesDigest(signedProperties)\n\n // DS333 - Document Signature Validation\n if (documentDigest.length === 0) {\n issues.push('DS333: Document digest generation failed')\n recommendations.push(\n 'CRITICAL: Verify document serialization excludes UBLExtensions/Signature',\n )\n recommendations.push(\n 'Portal Error: \"Document signature value is not a valid signature of the document digest using the public key of the certificate provided.\"',\n )\n }\n\n if (certificateDigest.length === 0) {\n issues.push('DS333: Certificate digest generation failed')\n recommendations.push('CRITICAL: Verify certificate format and encoding')\n recommendations.push(\n 'Certificate must be properly base64 encoded without headers/footers',\n )\n }\n\n if (signedPropertiesDigest.length === 0) {\n issues.push('DS333: Signed properties digest generation failed')\n recommendations.push(\n 'CRITICAL: Verify signed properties structure and canonicalization',\n )\n recommendations.push(\n 'Check XML canonicalization (C14N) is applied correctly',\n )\n }\n\n // Additional DS333 checks\n try {\n const cert = new crypto.X509Certificate(certificatePem)\n\n // Verify the certificate has the required algorithms for MyInvois\n const publicKey = cert.publicKey\n const keyDetails = publicKey.asymmetricKeyDetails\n\n if (keyDetails) {\n // Check if key size is adequate for RSA (minimum 2048 bits)\n if (\n publicKey.asymmetricKeyType === 'rsa' &&\n keyDetails.modulusLength &&\n keyDetails.modulusLength < 2048\n ) {\n issues.push(\n 'DS333: RSA key size too small (minimum 2048 bits required)',\n )\n recommendations.push(\n 'CRITICAL: Generate new certificate with RSA 2048+ bits',\n )\n }\n\n // Check supported key types\n const supportedKeyTypes = ['rsa', 'ec']\n if (!supportedKeyTypes.includes(publicKey.asymmetricKeyType || '')) {\n issues.push(\n `DS333: Unsupported key type: ${publicKey.asymmetricKeyType}`,\n )\n recommendations.push(\n 'CRITICAL: Use RSA or EC key types for MyInvois compatibility',\n )\n }\n }\n\n // Test certificate format validity\n const certBuffer = Buffer.from(\n certificatePem.replace(/-----[^-]+-----/g, '').replace(/\\s/g, ''),\n 'base64',\n )\n if (certBuffer.length === 0) {\n issues.push('DS333: Certificate encoding appears invalid')\n recommendations.push(\n 'CRITICAL: Verify certificate is properly PEM encoded',\n )\n }\n } catch (error) {\n issues.push(`DS333: Certificate validation failed - ${error}`)\n recommendations.push(\n 'CRITICAL: Verify certificate format and structure are valid',\n )\n }\n\n // Validate digest formats (should be base64)\n const isValidBase64 = (str: string) => {\n try {\n return Buffer.from(str, 'base64').toString('base64') === str\n } catch {\n return false\n }\n }\n\n if (documentDigest && !isValidBase64(documentDigest)) {\n issues.push('DS333: Document digest is not valid base64 format')\n recommendations.push('Ensure digest is properly base64 encoded')\n }\n\n if (certificateDigest && !isValidBase64(certificateDigest)) {\n issues.push('DS333: Certificate digest is not valid base64 format')\n recommendations.push(\n 'Ensure certificate digest is properly base64 encoded',\n )\n }\n\n if (signedPropertiesDigest && !isValidBase64(signedPropertiesDigest)) {\n issues.push('DS333: Signed properties digest is not valid base64 format')\n recommendations.push(\n 'Ensure signed properties digest is properly base64 encoded',\n )\n }\n\n return {\n documentDigest,\n certificateDigest,\n signedPropertiesDigest,\n issues,\n recommendations,\n }\n } catch (error) {\n issues.push(`Signature analysis failed: ${error}`)\n recommendations.push('Review signature generation implementation')\n\n return {\n documentDigest: '',\n certificateDigest: '',\n signedPropertiesDigest: '',\n issues,\n recommendations,\n }\n }\n}\n\n/**\n * Comprehensive signature diagnostics\n */\nexport function diagnoseSignatureIssues(\n invoices: InvoiceV1_1[],\n certificatePem: string,\n): DiagnosticResult {\n const certificateAnalysis = analyzeCertificateForDiagnostics(certificatePem)\n const signatureAnalysis = analyzeSignatureForDiagnostics(\n invoices,\n certificatePem,\n )\n\n const certificateIssues = certificateAnalysis.issues.length\n const signatureIssues = signatureAnalysis.issues.length\n\n return {\n certificateAnalysis,\n signatureAnalysis,\n summary: {\n totalIssues: certificateIssues + signatureIssues,\n certificateIssues,\n signatureIssues,\n },\n }\n}\n\n/**\n * Prints diagnostic results in a formatted way\n */\nexport function printDiagnostics(result: DiagnosticResult): void {\n console.log('\\n🔍 MyInvois Signature Diagnostics Report')\n console.log('='.repeat(60))\n\n // Certificate Analysis\n console.log('\\n📜 CERTIFICATE ANALYSIS')\n console.log('-'.repeat(30))\n\n console.log(` Issuer: ${result.certificateAnalysis.issuerName}`)\n console.log(` Subject: ${result.certificateAnalysis.subjectName}`)\n\n if (result.certificateAnalysis.organizationIdentifier) {\n console.log(\n ` Organization ID (TIN): ${result.certificateAnalysis.organizationIdentifier}`,\n )\n }\n\n if (result.certificateAnalysis.serialNumber) {\n console.log(` Serial Number: ${result.certificateAnalysis.serialNumber}`)\n }\n\n if (result.certificateAnalysis.issues.length > 0) {\n console.log('\\n 🚨 Certificate Issues:')\n result.certificateAnalysis.issues.forEach((issue, index) => {\n console.log(` ${index + 1}. ${issue}`)\n })\n }\n\n if (result.certificateAnalysis.recommendations.length > 0) {\n console.log('\\n 💡 Certificate Recommendations:')\n result.certificateAnalysis.recommendations.forEach((rec, index) => {\n console.log(` ${index + 1}. ${rec}`)\n })\n }\n\n // Signature Analysis\n console.log('\\n🔐 SIGNATURE ANALYSIS')\n console.log('-'.repeat(30))\n\n console.log(\n ` Document Digest: ${result.signatureAnalysis.documentDigest.substring(0, 32)}...`,\n )\n console.log(\n ` Certificate Digest: ${result.signatureAnalysis.certificateDigest.substring(0, 32)}...`,\n )\n console.log(\n ` Signed Properties Digest: ${result.signatureAnalysis.signedPropertiesDigest.substring(0, 32)}...`,\n )\n\n if (result.signatureAnalysis.issues.length > 0) {\n console.log('\\n 🚨 Signature Issues:')\n result.signatureAnalysis.issues.forEach((issue, index) => {\n console.log(` ${index + 1}. ${issue}`)\n })\n }\n\n if (result.signatureAnalysis.recommendations.length > 0) {\n console.log('\\n 💡 Signature Recommendations:')\n result.signatureAnalysis.recommendations.forEach((rec, index) => {\n console.log(` ${index + 1}. ${rec}`)\n })\n }\n\n // Summary\n console.log('\\n📊 SUMMARY')\n console.log('-'.repeat(30))\n console.log(` Total Issues Found: ${result.summary.totalIssues}`)\n console.log(` Certificate Issues: ${result.summary.certificateIssues}`)\n console.log(` Signature Issues: ${result.summary.signatureIssues}`)\n\n if (result.summary.totalIssues === 0) {\n console.log('\\n ✅ No issues detected in current analysis')\n console.log(' 🎉 Certificate and signature implementation appear valid')\n } else {\n console.log('\\n ⚠️ Issues detected - review recommendations above')\n\n // Check for specific portal errors\n const hasDS311 = result.certificateAnalysis.issues.some(issue =>\n issue.includes('DS311'),\n )\n const hasDS312 = result.certificateAnalysis.issues.some(issue =>\n issue.includes('DS312'),\n )\n const hasDS326 = result.certificateAnalysis.issues.some(issue =>\n issue.includes('DS326'),\n )\n const hasDS329 = result.certificateAnalysis.issues.some(issue =>\n issue.includes('DS329'),\n )\n const hasDS333 = result.signatureAnalysis.issues.some(issue =>\n issue.includes('DS333'),\n )\n\n console.log('\\n 🎯 MYINVOIS PORTAL ERROR ANALYSIS:')\n\n if (hasDS311) {\n console.log(\n ' ❌ DS311 - TIN mismatch between certificate and submitter',\n )\n }\n\n if (hasDS312) {\n console.log(\n ' ❌ DS312 - Registration number mismatch with certificate',\n )\n }\n\n if (hasDS326) {\n console.log(' ❌ DS326 - X509IssuerName format inconsistency')\n }\n\n if (hasDS329) {\n console.log(' ❌ DS329 - Certificate trust chain validation failure')\n }\n\n if (hasDS333) {\n console.log(' ❌ DS333 - Document signature validation failure')\n }\n\n if (result.summary.certificateIssues > 0) {\n console.log('\\n 🚨 PRIMARY ACTION REQUIRED:')\n console.log(' Certificate issues must be resolved first')\n console.log(\n ' Self-generated certificates cannot pass MyInvois validation',\n )\n }\n\n if (result.summary.signatureIssues > 0) {\n console.log('\\n ⚙️ SECONDARY ACTION:')\n console.log(' Review and optimize signature implementation')\n }\n\n console.log('\\n 📋 NEXT STEPS:')\n console.log(' 1. Address BLOCKING/CRITICAL issues first')\n console.log(' 2. Test with updated certificate/implementation')\n console.log(' 3. Re-run diagnostics to verify fixes')\n console.log(' 4. Submit test document to MyInvois portal')\n }\n\n console.log('\\n' + '='.repeat(60))\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index15.cjs","names":["tin: string","registrationType?: string","errors: ValidationError[]","contactNumber: string","amount: number","fieldName: string","item: InvoiceLineItem","index: number","invoice: InvoiceV1_1","allErrors: ValidationError[]"],"sources":["../src/utils/validation.ts"],"sourcesContent":["import type { InvoiceV1_1, InvoiceLineItem } from '../types'\n\n/**\n * MyInvois Invoice Validation Utilities\n *\n * Provides comprehensive validation for invoice data before document generation\n * and submission to ensure compliance with MyInvois business rules and format requirements.\n */\n\nexport interface ValidationResult {\n isValid: boolean\n errors: ValidationError[]\n warnings: ValidationWarning[]\n}\n\nexport interface ValidationError {\n field: string\n code: string\n message: string\n severity: 'error' | 'warning'\n}\n\nexport interface ValidationWarning extends ValidationError {\n severity: 'warning'\n}\n\n/**\n * Validates TIN format based on registration type\n */\nexport const validateTIN = (\n tin: string,\n registrationType?: string,\n): ValidationError[] => {\n const errors: ValidationError[] = []\n\n if (!tin) {\n errors.push({\n field: 'tin',\n code: 'TIN_REQUIRED',\n message: 'TIN is required',\n severity: 'error',\n })\n return errors\n }\n\n // TIN format validation based on type\n if (registrationType === 'BRN' && !tin.startsWith('C')) {\n errors.push({\n field: 'tin',\n code: 'TIN_FORMAT_INVALID',\n message: 'Company TIN should start with \"C\" for BRN registration',\n severity: 'warning',\n })\n }\n\n if (registrationType === 'NRIC' && !tin.startsWith('IG')) {\n errors.push({\n field: 'tin',\n code: 'TIN_FORMAT_INVALID',\n message: 'Individual TIN should start with \"IG\" for NRIC registration',\n severity: 'warning',\n })\n }\n\n // Length validation\n if (tin.length > 14) {\n errors.push({\n field: 'tin',\n code: 'TIN_LENGTH_INVALID',\n message: 'TIN cannot exceed 14 characters',\n severity: 'error',\n })\n }\n\n return errors\n}\n\n/**\n * Validates contact number format (E.164 standard)\n */\nexport const validateContactNumber = (\n contactNumber: string,\n): ValidationError[] => {\n const errors: ValidationError[] = []\n\n if (!contactNumber || contactNumber === 'NA') {\n return errors // Allow NA for consolidated e-invoices\n }\n\n // E.164 format validation\n const e164Regex = /^\\+[1-9]\\d{1,14}$/\n if (!e164Regex.test(contactNumber)) {\n errors.push({\n field: 'contactNumber',\n code: 'CONTACT_FORMAT_INVALID',\n message: 'Contact number must be in E.164 format (e.g., +60123456789)',\n severity: 'error',\n })\n }\n\n if (contactNumber.length < 8) {\n errors.push({\n field: 'contactNumber',\n code: 'CONTACT_LENGTH_INVALID',\n message: 'Contact number must be at least 8 characters',\n severity: 'error',\n })\n }\n\n return errors\n}\n\n/**\n * Validates monetary amounts\n */\nexport const validateMonetaryAmount = (\n amount: number,\n fieldName: string,\n maxDigits = 18,\n maxDecimals = 2,\n): ValidationError[] => {\n const errors: ValidationError[] = []\n\n if (amount < 0) {\n errors.push({\n field: fieldName,\n code: 'AMOUNT_NEGATIVE',\n message: `${fieldName} cannot be negative`,\n severity: 'error',\n })\n }\n\n // Check total digits\n const amountStr = amount.toString()\n const [integerPart, decimalPart] = amountStr.split('.')\n\n if (integerPart && integerPart.length > maxDigits - maxDecimals) {\n errors.push({\n field: fieldName,\n code: 'AMOUNT_DIGITS_EXCEEDED',\n message: `${fieldName} exceeds maximum ${maxDigits} digits`,\n severity: 'error',\n })\n }\n\n if (decimalPart && decimalPart.length > maxDecimals) {\n errors.push({\n field: fieldName,\n code: 'AMOUNT_DECIMALS_EXCEEDED',\n message: `${fieldName} exceeds maximum ${maxDecimals} decimal places`,\n severity: 'error',\n })\n }\n\n return errors\n}\n\n/**\n * Validates line item tax calculation consistency for both fixed rate and percentage taxation\n */\nexport const validateLineItemTax = (\n item: InvoiceLineItem,\n index: number,\n): ValidationError[] => {\n const errors: ValidationError[] = []\n const tolerance = 0.01\n\n // Check if tax calculation method is specified\n const hasFixedRate =\n item.taxPerUnitAmount !== undefined && item.baseUnitMeasure !== undefined\n const hasPercentageRate = item.taxRate !== undefined\n\n if (!hasFixedRate && !hasPercentageRate) {\n errors.push({\n field: `lineItem[${index}]`,\n code: 'TAX_METHOD_MISSING',\n message: `Line item ${index + 1} must specify either taxRate (for percentage) or taxPerUnitAmount + baseUnitMeasure (for fixed rate)`,\n severity: 'error',\n })\n return errors\n }\n\n if (hasFixedRate && hasPercentageRate) {\n errors.push({\n field: `lineItem[${index}]`,\n code: 'TAX_METHOD_CONFLICT',\n message: `Line item ${index + 1} cannot have both percentage and fixed rate tax methods`,\n severity: 'error',\n })\n }\n\n // Validate fixed rate tax calculation\n if (hasFixedRate) {\n if (item.baseUnitMeasureCode === undefined) {\n errors.push({\n field: `lineItem[${index}].baseUnitMeasureCode`,\n code: 'UNIT_CODE_MISSING',\n message: `Line item ${index + 1} with fixed rate tax must specify baseUnitMeasureCode`,\n severity: 'error',\n })\n }\n\n const expectedTaxAmount = item.taxPerUnitAmount! * item.baseUnitMeasure!\n if (Math.abs(item.taxAmount - expectedTaxAmount) > tolerance) {\n errors.push({\n field: `lineItem[${index}].taxAmount`,\n code: 'FIXED_TAX_CALCULATION_MISMATCH',\n message: `Line item ${index + 1} tax amount (${item.taxAmount}) doesn't match fixed rate calculation (${item.taxPerUnitAmount} × ${item.baseUnitMeasure} = ${expectedTaxAmount})`,\n severity: 'error',\n })\n }\n }\n\n // Validate percentage tax calculation\n if (hasPercentageRate && !hasFixedRate) {\n const expectedTaxAmount =\n (item.totalTaxableAmountPerLine * item.taxRate!) / 100\n if (Math.abs(item.taxAmount - expectedTaxAmount) > tolerance) {\n errors.push({\n field: `lineItem[${index}].taxAmount`,\n code: 'PERCENTAGE_TAX_CALCULATION_MISMATCH',\n message: `Line item ${index + 1} tax amount (${item.taxAmount}) doesn't match percentage calculation (${item.totalTaxableAmountPerLine} × ${item.taxRate}% = ${expectedTaxAmount})`,\n severity: 'error',\n })\n }\n }\n\n return errors\n}\n\n/**\n * Validates tax calculation consistency\n */\nexport const validateTaxCalculations = (\n invoice: InvoiceV1_1,\n): ValidationError[] => {\n const errors: ValidationError[] = []\n\n // Validate individual line item tax calculations\n invoice.invoiceLineItems.forEach((item, index) => {\n errors.push(...validateLineItemTax(item, index))\n })\n\n // Calculate expected totals from line items\n const expectedTaxExclusive = invoice.invoiceLineItems.reduce(\n (sum, item) => sum + item.totalTaxableAmountPerLine,\n 0,\n )\n const expectedTaxAmount = invoice.invoiceLineItems.reduce(\n (sum, item) => sum + item.taxAmount,\n 0,\n )\n\n // Allow small rounding differences (0.01)\n const tolerance = 0.01\n\n if (\n Math.abs(\n invoice.legalMonetaryTotal.taxExclusiveAmount - expectedTaxExclusive,\n ) > tolerance\n ) {\n errors.push({\n field: 'legalMonetaryTotal.taxExclusiveAmount',\n code: 'TAX_EXCLUSIVE_MISMATCH',\n message: `Tax exclusive amount (${invoice.legalMonetaryTotal.taxExclusiveAmount}) doesn't match sum of line items (${expectedTaxExclusive})`,\n severity: 'error',\n })\n }\n\n if (Math.abs(invoice.taxTotal.taxAmount - expectedTaxAmount) > tolerance) {\n errors.push({\n field: 'taxTotal.taxAmount',\n code: 'TAX_AMOUNT_MISMATCH',\n message: `Tax amount (${invoice.taxTotal.taxAmount}) doesn't match sum of line item taxes (${expectedTaxAmount})`,\n severity: 'error',\n })\n }\n\n return errors\n}\n\n/**\n * Main validation function for complete invoice\n */\nexport const validateInvoice = (invoice: InvoiceV1_1): ValidationResult => {\n const allErrors: ValidationError[] = []\n\n // Core field validations\n allErrors.push(\n ...validateTIN(invoice.supplier.tin, invoice.supplier.registrationType),\n )\n allErrors.push(\n ...validateTIN(invoice.buyer.tin, invoice.buyer.registrationType),\n )\n\n allErrors.push(...validateContactNumber(invoice.supplier.contactNumber))\n allErrors.push(...validateContactNumber(invoice.buyer.contactNumber))\n\n // Monetary validations\n allErrors.push(\n ...validateMonetaryAmount(\n invoice.legalMonetaryTotal.taxExclusiveAmount,\n 'taxExclusiveAmount',\n ),\n )\n allErrors.push(\n ...validateMonetaryAmount(\n invoice.legalMonetaryTotal.payableAmount,\n 'payableAmount',\n ),\n )\n allErrors.push(\n ...validateMonetaryAmount(invoice.taxTotal.taxAmount, 'taxAmount'),\n )\n\n // Line item validations\n invoice.invoiceLineItems.forEach((item, index) => {\n allErrors.push(\n ...validateMonetaryAmount(item.unitPrice, `lineItem[${index}].unitPrice`),\n )\n allErrors.push(\n ...validateMonetaryAmount(item.taxAmount, `lineItem[${index}].taxAmount`),\n )\n allErrors.push(\n ...validateMonetaryAmount(\n item.totalTaxableAmountPerLine,\n `lineItem[${index}].totalTaxableAmountPerLine`,\n ),\n )\n })\n\n // Business rule validations\n allErrors.push(...validateTaxCalculations(invoice))\n\n // Separate errors and warnings\n const errors = allErrors.filter(e => e.severity === 'error')\n const warnings = allErrors.filter(\n e => e.severity === 'warning',\n ) as ValidationWarning[]\n\n return {\n isValid: errors.length === 0,\n errors,\n warnings,\n }\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index16.cjs","names":[],"sources":["../src/types/classification-codes.d.ts"],"sourcesContent":["/**\n * Represents the allowed classification codes for e-Invoice items.\n * Based on the documentation: https://sdk.myinvois.hasil.gov.my/codes/classification-codes/\n */\nexport type ClassificationCode =\n | '001' // Breastfeeding equipment\n | '002' // Child care centres and kindergartens fees\n | '003' // Computer, smartphone or tablet\n | '004' // Consolidated e-Invoice\n | '005' // Construction materials (as specified under Fourth Schedule of the Lembaga Pembangunan Industri Pembinaan Malaysia Act 1994)\n | '006' // Disbursement\n | '007' // Donation\n | '008' // e-Commerce - e-Invoice to buyer / purchaser\n | '009' // e-Commerce - Self-billed e-Invoice to seller, logistics, etc.\n | '010' // Education fees\n | '011' // Goods on consignment (Consignor)\n | '012' // Goods on consignment (Consignee)\n | '013' // Gym membership\n | '014' // Insurance - Education and medical benefits\n | '015' // Insurance - Takaful or life insurance\n | '016' // Interest and financing expenses\n | '017' // Internet subscription\n | '018' // Land and building\n | '019' // Medical examination for learning disabilities and early intervention or rehabilitation treatments of learning disabilities\n | '020' // Medical examination or vaccination expenses\n | '021' // Medical expenses for serious diseases\n | '022' // Others\n | '023' // Petroleum operations (as defined in Petroleum (Income Tax) Act 1967)\n | '024' // Private retirement scheme or deferred annuity scheme\n | '025' // Motor vehicle\n | '026' // Subscription of books / journals / magazines / newspapers / other similar publications\n | '027' // Reimbursement\n | '028' // Rental of motor vehicle\n | '029' // EV charging facilities (Installation, rental, sale / purchase or subscription fees)\n | '030' // Repair and maintenance\n | '031' // Research and development\n | '032' // Foreign income\n | '033' // Self-billed - Betting and gaming\n | '034' // Self-billed - Importation of goods\n | '035' // Self-billed - Importation of services\n | '036' // Self-billed - Others\n | '037' // Self-billed - Monetary payment to agents, dealers or distributors\n | '038' // Sports equipment, rental / entry fees for sports facilities, registration in sports competition or sports training fees imposed by associations / sports clubs / companies registered with the Sports Commissioner or Companies Commission of Malaysia and carrying out sports activities as listed under the Sports Development Act 1997\n | '039' // Supporting equipment for disabled person\n | '040' // Voluntary contribution to approved provident fund\n | '041' // Dental examination or treatment\n | '042' // Fertility treatment\n | '043' // Treatment and home care nursing, daycare centres and residential care centers\n | '044' // Vouchers, gift cards, loyalty points, etc\n | '045' // Self-billed - Non-monetary payment to agents, dealers or distributors\n\n/**\n * Enum representing the allowed classification codes with descriptive names.\n * Provides a more readable way to reference classification codes.\n *\n * @example\n * const code = ClassificationCodeEnum.ComputerSmartphoneOrTablet;\n * console.log(code); // Output: \"003\"\n */\nexport enum ClassificationCodeEnum {\n BreastfeedingEquipment = '001',\n ChildCareCentresAndKindergartensFees = '002',\n ComputerSmartphoneOrTablet = '003',\n ConsolidatedEInvoice = '004',\n ConstructionMaterials = '005',\n Disbursement = '006',\n Donation = '007',\n ECommerceEInvoiceToBuyer = '008',\n ECommerceSelfBilledToSellerLogistics = '009',\n EducationFees = '010',\n GoodsOnConsignmentConsignor = '011',\n GoodsOnConsignmentConsignee = '012',\n GymMembership = '013',\n InsuranceEducationMedicalBenefits = '014',\n InsuranceTakafulLife = '015',\n InterestFinancingExpenses = '016',\n InternetSubscription = '017',\n LandAndBuilding = '018',\n MedicalExamLearningDisabilities = '019',\n MedicalExamVaccination = '020',\n MedicalExpensesSeriousDiseases = '021',\n Others = '022',\n PetroleumOperations = '023',\n PrivateRetirementSchemeDeferredAnnuity = '024',\n MotorVehicle = '025',\n SubscriptionBooksJournalsEtc = '026',\n Reimbursement = '027',\n RentalOfMotorVehicle = '028',\n EVChargingFacilities = '029',\n RepairAndMaintenance = '030',\n ResearchAndDevelopment = '031',\n ForeignIncome = '032',\n SelfBilledBettingGaming = '033',\n SelfBilledImportationGoods = '034',\n SelfBilledImportationServices = '035',\n SelfBilledOthers = '036',\n SelfBilledMonetaryPaymentToAgents = '037',\n SportsEquipmentRentalFeesEtc = '038',\n SupportingEquipmentDisabledPerson = '039',\n VoluntaryContributionProvidentFund = '040',\n DentalExamTreatment = '041',\n FertilityTreatment = '042',\n TreatmentHomeCareNursingEtc = '043',\n VouchersGiftCardsLoyaltyPoints = '044',\n SelfBilledNonMonetaryPaymentToAgents = '045',\n}\n\n/**\n * Interface representing a classification code entry.\n * Contains the code and its corresponding description.\n */\nexport interface Classification {\n code: ClassificationCode\n description: string\n}\n"],"mappings":";;;;;;;;;;AA2DA,IAAY,4EAAL;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AACD"}