@ripwords/myinvois-client 0.2.41 → 0.3.1

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 (107) hide show
  1. package/dist/api/documentManagement.d.ts +2 -2
  2. package/dist/api/documentSubmission.d.ts +2 -2
  3. package/dist/api/documentSubmission.js +2 -2
  4. package/dist/api/documentTypeManagement.d.ts +2 -2
  5. package/dist/api/notificationManagement.d.ts +2 -2
  6. package/dist/api/platformLogin.d.ts +2 -2
  7. package/dist/api/taxpayerValidation.d.ts +2 -2
  8. package/dist/apiQueue-B6Q644Bz.js +201 -0
  9. package/dist/apiQueue-DgKWaQDS.cjs +220 -0
  10. package/dist/apiQueue-DgKWaQDS.cjs.map +1 -0
  11. package/dist/{document-DLFdGSK1.js → document-D4O7JY0G.js} +9 -3
  12. package/dist/{document-CCza2JPL.cjs → document-DoQEvmcK.cjs} +10 -4
  13. package/dist/document-DoQEvmcK.cjs.map +1 -0
  14. package/dist/{documentSubmission-M4UlirJ7.cjs → documentSubmission-DUsjqWhR.cjs} +2 -2
  15. package/dist/{documentSubmission-M4UlirJ7.cjs.map → documentSubmission-DUsjqWhR.cjs.map} +1 -1
  16. package/dist/{documentSubmission-ZAgXsd3X.js → documentSubmission-uJ7yPWub.js} +1 -1
  17. package/dist/{documents-DCZ3Ffya.d.cts → documents-BECak3KN.d.cts} +7 -1
  18. package/dist/{documents-DzZA3NHj.d.ts → documents-Dp19RgNX.d.ts} +6 -0
  19. package/dist/index.cjs +3 -3
  20. package/dist/index.d.ts +2 -2
  21. package/dist/index.js +3 -3
  22. package/dist/index10.cjs +24 -4
  23. package/dist/index10.cjs.map +1 -0
  24. package/dist/index11.cjs +0 -22
  25. package/dist/index12.cjs +33 -2
  26. package/dist/index12.cjs.map +1 -0
  27. package/dist/index13.cjs +23 -2
  28. package/dist/index13.cjs.map +1 -0
  29. package/dist/index14.cjs +0 -330
  30. package/dist/index15.cjs +0 -193
  31. package/dist/index16.cjs +0 -62
  32. package/dist/index17.cjs +4 -531
  33. package/dist/index18.cjs +6 -195
  34. package/dist/index19.cjs +5 -0
  35. package/dist/index2.cjs +61 -4
  36. package/dist/index2.cjs.map +1 -0
  37. package/dist/index20.cjs +2 -24
  38. package/dist/index21.cjs +3 -0
  39. package/dist/index22.cjs +6 -0
  40. package/dist/index23.cjs +203 -24
  41. package/dist/index23.cjs.map +1 -1
  42. package/dist/index24.cjs +104 -20
  43. package/dist/index24.cjs.map +1 -1
  44. package/dist/index25.cjs +137 -0
  45. package/dist/{index33.cjs.map → index25.cjs.map} +1 -1
  46. package/dist/index26.cjs +59 -29
  47. package/dist/index26.cjs.map +1 -1
  48. package/dist/index27.cjs +262 -19
  49. package/dist/index27.cjs.map +1 -1
  50. package/dist/index28.cjs +79 -0
  51. package/dist/{index36.cjs.map → index28.cjs.map} +1 -1
  52. package/dist/index29.cjs +107 -0
  53. package/dist/{index37.cjs.map → index29.cjs.map} +1 -1
  54. package/dist/index3.cjs +531 -6
  55. package/dist/index3.cjs.map +1 -0
  56. package/dist/index30.cjs +73 -0
  57. package/dist/{index38.cjs.map → index30.cjs.map} +1 -1
  58. package/dist/index31.cjs +107 -203
  59. package/dist/index31.cjs.map +1 -1
  60. package/dist/index32.cjs +95 -104
  61. package/dist/index32.cjs.map +1 -1
  62. package/dist/index33.cjs +4 -136
  63. package/dist/index34.cjs +9 -60
  64. package/dist/index34.cjs.map +1 -1
  65. package/dist/index35.cjs +4 -266
  66. package/dist/index36.cjs +21 -78
  67. package/dist/index37.cjs +2 -106
  68. package/dist/index38.cjs +2 -72
  69. package/dist/index39.cjs +326 -108
  70. package/dist/index39.cjs.map +1 -1
  71. package/dist/index4.cjs +195 -4
  72. package/dist/index4.cjs.map +1 -0
  73. package/dist/index40.cjs +189 -96
  74. package/dist/index40.cjs.map +1 -1
  75. package/dist/index5.cjs +0 -3
  76. package/dist/index6.cjs +24 -2
  77. package/dist/index6.cjs.map +1 -0
  78. package/dist/index68.cts.map +1 -1
  79. package/dist/index7.cjs +0 -6
  80. package/dist/index71.cts.map +1 -1
  81. package/dist/index8.cjs +0 -4
  82. package/dist/index9.cjs +25 -9
  83. package/dist/index9.cjs.map +1 -1
  84. package/dist/{taxpayer-DmHW0m7o.d.ts → taxpayer-BdvCGHHC.d.ts} +1 -1
  85. package/dist/{taxpayer-Pm90MrPj.d.cts → taxpayer-CaDfslWB.d.cts} +2 -2
  86. package/dist/types/documents.d.ts +1 -1
  87. package/dist/types/index.d.ts +2 -2
  88. package/dist/types/taxpayer.d.ts +2 -2
  89. package/dist/utils/apiQueue.d.ts +11 -3
  90. package/dist/utils/apiQueue.js +2 -2
  91. package/dist/utils/document.d.ts +4 -3
  92. package/dist/utils/document.js +1 -1
  93. package/dist/utils/signature-diagnostics.d.ts +2 -2
  94. package/dist/utils/signature-diagnostics.js +1 -1
  95. package/dist/utils/validation.d.ts +2 -2
  96. package/package.json +1 -1
  97. package/dist/apiQueue-CCrZMnMu.js +0 -182
  98. package/dist/apiQueue-Djd7WlnV.cjs +0 -195
  99. package/dist/apiQueue-Djd7WlnV.cjs.map +0 -1
  100. package/dist/document-CCza2JPL.cjs.map +0 -1
  101. package/dist/index14.cjs.map +0 -1
  102. package/dist/index15.cjs.map +0 -1
  103. package/dist/index16.cjs.map +0 -1
  104. package/dist/index17.cjs.map +0 -1
  105. package/dist/index18.cjs.map +0 -1
  106. package/dist/index20.cjs.map +0 -1
  107. package/dist/index35.cjs.map +0 -1
@@ -51,11 +51,11 @@ import "../YX-F34sJ7Ik.js";
51
51
  import "../ZX-CDQOfsHh.js";
52
52
  import "../XX-DOA-10JW.js";
53
53
  import "../unit-types-VgYXIwTT.js";
54
- import { DocumentStatus, DocumentSummary, DocumentValidationResult, DocumentValidationStepResult } from "../documents-DzZA3NHj.js";
54
+ import { DocumentStatus, DocumentSummary, DocumentValidationResult, DocumentValidationStepResult } from "../documents-Dp19RgNX.js";
55
55
  import "../payment-modes-g3DzLmWb.js";
56
56
  import "../signatures-CerHUrj3.js";
57
57
  import "../notifications-sFhgh3rJ.js";
58
- import "../taxpayer-DmHW0m7o.js";
58
+ import "../taxpayer-BdvCGHHC.js";
59
59
  import { Fetch } from "../utils-C4FoVKLq.js";
60
60
  import "../index-CygwSf0x.js";
61
61
 
@@ -51,11 +51,11 @@ import "../YX-F34sJ7Ik.js";
51
51
  import "../ZX-CDQOfsHh.js";
52
52
  import "../XX-DOA-10JW.js";
53
53
  import "../unit-types-VgYXIwTT.js";
54
- import { AllDocumentsV1_1, DocumentSummary, SigningCredentials, StandardError, SubmissionResponse, SubmissionStatus } from "../documents-DzZA3NHj.js";
54
+ import { AllDocumentsV1_1, DocumentSummary, SigningCredentials, StandardError, SubmissionResponse, SubmissionStatus } from "../documents-Dp19RgNX.js";
55
55
  import "../payment-modes-g3DzLmWb.js";
56
56
  import "../signatures-CerHUrj3.js";
57
57
  import "../notifications-sFhgh3rJ.js";
58
- import "../taxpayer-DmHW0m7o.js";
58
+ import "../taxpayer-BdvCGHHC.js";
59
59
  import { Fetch } from "../utils-C4FoVKLq.js";
60
60
  import "../index-CygwSf0x.js";
61
61
 
@@ -1,5 +1,5 @@
1
1
  import "../formatIdValue-qTxJqj9o.js";
2
- import "../document-DLFdGSK1.js";
3
- import { getSubmissionStatus, performDocumentAction, submitDocument } from "../documentSubmission-ZAgXsd3X.js";
2
+ import "../document-D4O7JY0G.js";
3
+ import { getSubmissionStatus, performDocumentAction, submitDocument } from "../documentSubmission-uJ7yPWub.js";
4
4
 
5
5
  export { getSubmissionStatus, performDocumentAction, submitDocument };
@@ -51,11 +51,11 @@ import "../YX-F34sJ7Ik.js";
51
51
  import "../ZX-CDQOfsHh.js";
52
52
  import "../XX-DOA-10JW.js";
53
53
  import "../unit-types-VgYXIwTT.js";
54
- import { DocumentTypeResponse, DocumentTypeVersionResponse, DocumentTypesResponse } from "../documents-DzZA3NHj.js";
54
+ import { DocumentTypeResponse, DocumentTypeVersionResponse, DocumentTypesResponse } from "../documents-Dp19RgNX.js";
55
55
  import "../payment-modes-g3DzLmWb.js";
56
56
  import "../signatures-CerHUrj3.js";
57
57
  import "../notifications-sFhgh3rJ.js";
58
- import "../taxpayer-DmHW0m7o.js";
58
+ import "../taxpayer-BdvCGHHC.js";
59
59
  import { Fetch } from "../utils-C4FoVKLq.js";
60
60
  import "../index-CygwSf0x.js";
61
61
 
@@ -51,11 +51,11 @@ import "../YX-F34sJ7Ik.js";
51
51
  import "../ZX-CDQOfsHh.js";
52
52
  import "../XX-DOA-10JW.js";
53
53
  import "../unit-types-VgYXIwTT.js";
54
- import "../documents-DzZA3NHj.js";
54
+ import "../documents-Dp19RgNX.js";
55
55
  import "../payment-modes-g3DzLmWb.js";
56
56
  import "../signatures-CerHUrj3.js";
57
57
  import { NotificationResponse, NotificationSearchParams } from "../notifications-sFhgh3rJ.js";
58
- import "../taxpayer-DmHW0m7o.js";
58
+ import "../taxpayer-BdvCGHHC.js";
59
59
  import { Fetch } from "../utils-C4FoVKLq.js";
60
60
  import "../index-CygwSf0x.js";
61
61
 
@@ -51,11 +51,11 @@ import "../YX-F34sJ7Ik.js";
51
51
  import "../ZX-CDQOfsHh.js";
52
52
  import "../XX-DOA-10JW.js";
53
53
  import "../unit-types-VgYXIwTT.js";
54
- import "../documents-DzZA3NHj.js";
54
+ import "../documents-Dp19RgNX.js";
55
55
  import "../payment-modes-g3DzLmWb.js";
56
56
  import "../signatures-CerHUrj3.js";
57
57
  import "../notifications-sFhgh3rJ.js";
58
- import "../taxpayer-DmHW0m7o.js";
58
+ import "../taxpayer-BdvCGHHC.js";
59
59
  import "../utils-C4FoVKLq.js";
60
60
  import { ClientCredentials } from "../index-CygwSf0x.js";
61
61
 
@@ -51,11 +51,11 @@ import "../YX-F34sJ7Ik.js";
51
51
  import "../ZX-CDQOfsHh.js";
52
52
  import "../XX-DOA-10JW.js";
53
53
  import "../unit-types-VgYXIwTT.js";
54
- import { RegistrationType } from "../documents-DzZA3NHj.js";
54
+ import { RegistrationType } from "../documents-Dp19RgNX.js";
55
55
  import "../payment-modes-g3DzLmWb.js";
56
56
  import "../signatures-CerHUrj3.js";
57
57
  import "../notifications-sFhgh3rJ.js";
58
- import { TaxpayerQRCodeResponse, TinSearchParams, TinSearchResponse } from "../taxpayer-DmHW0m7o.js";
58
+ import { TaxpayerQRCodeResponse, TinSearchParams, TinSearchResponse } from "../taxpayer-BdvCGHHC.js";
59
59
  import { Fetch } from "../utils-C4FoVKLq.js";
60
60
  import "../index-CygwSf0x.js";
61
61
 
@@ -0,0 +1,201 @@
1
+ //#region src/utils/apiQueue.ts
2
+ const queues = {};
3
+ const LIMITS = {
4
+ loginTaxpayer: {
5
+ max: 12,
6
+ perMs: 6e4
7
+ },
8
+ loginIntermediary: {
9
+ max: 12,
10
+ perMs: 6e4
11
+ },
12
+ submitDocuments: {
13
+ max: 100,
14
+ perMs: 6e4
15
+ },
16
+ getSubmission: {
17
+ max: 300,
18
+ perMs: 6e4
19
+ },
20
+ cancelDocument: {
21
+ max: 12,
22
+ perMs: 6e4
23
+ },
24
+ rejectDocument: {
25
+ max: 12,
26
+ perMs: 6e4
27
+ },
28
+ getDocument: {
29
+ max: 60,
30
+ perMs: 6e4
31
+ },
32
+ getDocumentDetails: {
33
+ max: 125,
34
+ perMs: 6e4
35
+ },
36
+ getRecentDocuments: {
37
+ max: 12,
38
+ perMs: 6e4
39
+ },
40
+ searchDocuments: {
41
+ max: 12,
42
+ perMs: 6e4
43
+ },
44
+ searchTin: {
45
+ max: 60,
46
+ perMs: 6e4
47
+ },
48
+ taxpayerQr: {
49
+ max: 60,
50
+ perMs: 6e4
51
+ },
52
+ default: {
53
+ max: 12,
54
+ perMs: 6e4
55
+ }
56
+ };
57
+ /**
58
+ * Clean up old timestamps outside the sliding window
59
+ */
60
+ function cleanupTimestamps(timestamps, windowMs, now) {
61
+ return timestamps.filter((ts) => now - ts < windowMs);
62
+ }
63
+ /**
64
+ * Calculate when we can make the next request without exceeding the rate limit
65
+ */
66
+ function getNextAvailableTime(timestamps, max, windowMs, now) {
67
+ if (timestamps.length < max) return now;
68
+ const oldestTimestamp = timestamps[0];
69
+ if (!oldestTimestamp) return now;
70
+ return oldestTimestamp + windowMs;
71
+ }
72
+ /**
73
+ * Public helper to schedule a request according to the category's limits.
74
+ * Rate limits are enforced per clientId, so multiple instances with the same
75
+ * clientId will share rate limiters, while different clientIds get separate limiters.
76
+ *
77
+ * This implementation uses a sliding window to track all requests within the time window.
78
+ */
79
+ function queueRequest(clientId, category, task, debug = false) {
80
+ const key = `${clientId}:${category}`;
81
+ if (!queues[key]) queues[key] = {
82
+ running: 0,
83
+ queue: [],
84
+ requestTimestamps: [],
85
+ nextTimer: null
86
+ };
87
+ const queue = queues[key];
88
+ const { max, perMs } = LIMITS[category] ?? LIMITS.default;
89
+ return new Promise((resolve, reject) => {
90
+ const run = () => {
91
+ const now$1 = Date.now();
92
+ queue.requestTimestamps = cleanupTimestamps(queue.requestTimestamps, perMs, now$1);
93
+ if (queue.requestTimestamps.length >= max) {
94
+ const nextAvailable = getNextAvailableTime(queue.requestTimestamps, max, perMs, now$1);
95
+ const waitTime = nextAvailable - now$1;
96
+ if (debug) console.log(`[apiQueue] 🚫 Rate limit reached (${queue.requestTimestamps.length}/${max} in last ${perMs}ms). Queuing request. Need to wait ${waitTime.toFixed(0)}ms. Queue size: ${queue.queue.length + 1}`);
97
+ queue.queue.push({
98
+ run,
99
+ addedAt: now$1
100
+ });
101
+ return;
102
+ }
103
+ queue.requestTimestamps.push(now$1);
104
+ queue.running++;
105
+ if (debug) console.log(`[apiQueue] ▶️ Executing request (${category}). Requests in window: ${queue.requestTimestamps.length}/${max}. Running: ${queue.running}. Queue size: ${queue.queue.length}`);
106
+ task().then(resolve).catch(reject).finally(() => {
107
+ queue.running--;
108
+ if (debug) console.log(`[apiQueue] ✅ Request completed (${category}). Requests in window: ${queue.requestTimestamps.length}/${max}. Running: ${queue.running}. Queue size: ${queue.queue.length}`);
109
+ processQueue(key, debug, category);
110
+ });
111
+ };
112
+ const now = Date.now();
113
+ queue.requestTimestamps = cleanupTimestamps(queue.requestTimestamps, perMs, now);
114
+ if (queue.queue.length > 0 || queue.requestTimestamps.length >= max) {
115
+ queue.queue.push({
116
+ run,
117
+ addedAt: now
118
+ });
119
+ if (debug) console.log(`[apiQueue] ⏳ Queued request (${category}). Requests in window: ${queue.requestTimestamps.length}/${max}. Queue size: ${queue.queue.length}`);
120
+ processQueue(key, debug, category);
121
+ } else run();
122
+ });
123
+ }
124
+ function processQueue(key, debug, category) {
125
+ const queue = queues[key];
126
+ if (!queue || queue.queue.length === 0) return;
127
+ const { max, perMs } = LIMITS[category] ?? LIMITS.default;
128
+ const now = Date.now();
129
+ queue.requestTimestamps = cleanupTimestamps(queue.requestTimestamps, perMs, now);
130
+ if (queue.requestTimestamps.length < max) {
131
+ const next = queue.queue.shift();
132
+ if (next) {
133
+ if (debug) {
134
+ const waitTime = Date.now() - next.addedAt;
135
+ console.log(`[apiQueue] 🚀 Processing queued request (${category}). Waited: ${waitTime.toFixed(0)}ms. Queue size: ${queue.queue.length}`);
136
+ }
137
+ next.run();
138
+ }
139
+ if (queue.queue.length > 0) processQueue(key, debug, category);
140
+ } else {
141
+ const nextAvailable = getNextAvailableTime(queue.requestTimestamps, max, perMs, now);
142
+ const delay = Math.max(0, nextAvailable - now + 50);
143
+ if (debug) console.log(`[apiQueue] ⏸️ Delaying queue processing (${category}). Will retry in ${delay.toFixed(0)}ms. Queue size: ${queue.queue.length}`);
144
+ if (!queue.nextTimer) queue.nextTimer = setTimeout(() => {
145
+ queue.nextTimer = null;
146
+ processQueue(key, debug, category);
147
+ }, delay);
148
+ }
149
+ }
150
+ /**
151
+ * Cleanup function to clear all queues and timers for a specific client.
152
+ * Useful for testing or cleanup on application shutdown.
153
+ */
154
+ function clearQueue(clientId, category) {
155
+ if (category) {
156
+ const key = `${clientId}:${category}`;
157
+ const queue = queues[key];
158
+ if (queue) {
159
+ if (queue.nextTimer) {
160
+ clearTimeout(queue.nextTimer);
161
+ queue.nextTimer = null;
162
+ }
163
+ queue.queue = [];
164
+ queue.requestTimestamps = [];
165
+ queue.running = 0;
166
+ }
167
+ } else Object.keys(queues).forEach((key) => {
168
+ if (key.startsWith(`${clientId}:`)) {
169
+ const queue = queues[key];
170
+ if (queue?.nextTimer) {
171
+ clearTimeout(queue.nextTimer);
172
+ queue.nextTimer = null;
173
+ }
174
+ delete queues[key];
175
+ }
176
+ });
177
+ }
178
+ /**
179
+ * Very naive path-based category detection. If no matcher fits, the `default` category
180
+ * (effectively unlimited) is returned. Adjust these heuristics as your API surface evolves.
181
+ */
182
+ function categorizeRequest(path, method = "GET") {
183
+ const cleanPath = path.toLowerCase();
184
+ const isPost = method?.toUpperCase() === "POST";
185
+ if (cleanPath.includes("/documentsubmissions")) return isPost ? "submitDocuments" : "getSubmission";
186
+ if (cleanPath.includes("/documents/recent")) return "getRecentDocuments";
187
+ if (cleanPath.includes("/documents/search")) return "searchDocuments";
188
+ if (cleanPath.includes("/documents/state/") && cleanPath.endsWith("/state")) return "cancelDocument";
189
+ if (/\/documents\/[^/]+\/raw$/.test(cleanPath)) return "getDocument";
190
+ if (/\/documents\/[^/]+\/details$/.test(cleanPath)) return "getDocumentDetails";
191
+ if (cleanPath.includes("/taxpayer/search/tin")) return "searchTin";
192
+ if (cleanPath.includes("/taxpayer/validate/")) return "searchTin";
193
+ if (cleanPath.includes("/taxpayer/qrcode")) return "taxpayerQr";
194
+ if (cleanPath.includes("/searchtin")) return "searchTin";
195
+ if (cleanPath.includes("/qrcode")) return "taxpayerQr";
196
+ if (cleanPath.includes("/connect/token")) return "loginTaxpayer";
197
+ return "default";
198
+ }
199
+
200
+ //#endregion
201
+ export { categorizeRequest, clearQueue, queueRequest };
@@ -0,0 +1,220 @@
1
+
2
+ //#region src/utils/apiQueue.ts
3
+ const queues = {};
4
+ const LIMITS = {
5
+ loginTaxpayer: {
6
+ max: 12,
7
+ perMs: 6e4
8
+ },
9
+ loginIntermediary: {
10
+ max: 12,
11
+ perMs: 6e4
12
+ },
13
+ submitDocuments: {
14
+ max: 100,
15
+ perMs: 6e4
16
+ },
17
+ getSubmission: {
18
+ max: 300,
19
+ perMs: 6e4
20
+ },
21
+ cancelDocument: {
22
+ max: 12,
23
+ perMs: 6e4
24
+ },
25
+ rejectDocument: {
26
+ max: 12,
27
+ perMs: 6e4
28
+ },
29
+ getDocument: {
30
+ max: 60,
31
+ perMs: 6e4
32
+ },
33
+ getDocumentDetails: {
34
+ max: 125,
35
+ perMs: 6e4
36
+ },
37
+ getRecentDocuments: {
38
+ max: 12,
39
+ perMs: 6e4
40
+ },
41
+ searchDocuments: {
42
+ max: 12,
43
+ perMs: 6e4
44
+ },
45
+ searchTin: {
46
+ max: 60,
47
+ perMs: 6e4
48
+ },
49
+ taxpayerQr: {
50
+ max: 60,
51
+ perMs: 6e4
52
+ },
53
+ default: {
54
+ max: 12,
55
+ perMs: 6e4
56
+ }
57
+ };
58
+ /**
59
+ * Clean up old timestamps outside the sliding window
60
+ */
61
+ function cleanupTimestamps(timestamps, windowMs, now) {
62
+ return timestamps.filter((ts) => now - ts < windowMs);
63
+ }
64
+ /**
65
+ * Calculate when we can make the next request without exceeding the rate limit
66
+ */
67
+ function getNextAvailableTime(timestamps, max, windowMs, now) {
68
+ if (timestamps.length < max) return now;
69
+ const oldestTimestamp = timestamps[0];
70
+ if (!oldestTimestamp) return now;
71
+ return oldestTimestamp + windowMs;
72
+ }
73
+ /**
74
+ * Public helper to schedule a request according to the category's limits.
75
+ * Rate limits are enforced per clientId, so multiple instances with the same
76
+ * clientId will share rate limiters, while different clientIds get separate limiters.
77
+ *
78
+ * This implementation uses a sliding window to track all requests within the time window.
79
+ */
80
+ function queueRequest(clientId, category, task, debug = false) {
81
+ const key = `${clientId}:${category}`;
82
+ if (!queues[key]) queues[key] = {
83
+ running: 0,
84
+ queue: [],
85
+ requestTimestamps: [],
86
+ nextTimer: null
87
+ };
88
+ const queue = queues[key];
89
+ const { max, perMs } = LIMITS[category] ?? LIMITS.default;
90
+ return new Promise((resolve, reject) => {
91
+ const run = () => {
92
+ const now$1 = Date.now();
93
+ queue.requestTimestamps = cleanupTimestamps(queue.requestTimestamps, perMs, now$1);
94
+ if (queue.requestTimestamps.length >= max) {
95
+ const nextAvailable = getNextAvailableTime(queue.requestTimestamps, max, perMs, now$1);
96
+ const waitTime = nextAvailable - now$1;
97
+ if (debug) console.log(`[apiQueue] 🚫 Rate limit reached (${queue.requestTimestamps.length}/${max} in last ${perMs}ms). Queuing request. Need to wait ${waitTime.toFixed(0)}ms. Queue size: ${queue.queue.length + 1}`);
98
+ queue.queue.push({
99
+ run,
100
+ addedAt: now$1
101
+ });
102
+ return;
103
+ }
104
+ queue.requestTimestamps.push(now$1);
105
+ queue.running++;
106
+ if (debug) console.log(`[apiQueue] ▶️ Executing request (${category}). Requests in window: ${queue.requestTimestamps.length}/${max}. Running: ${queue.running}. Queue size: ${queue.queue.length}`);
107
+ task().then(resolve).catch(reject).finally(() => {
108
+ queue.running--;
109
+ if (debug) console.log(`[apiQueue] ✅ Request completed (${category}). Requests in window: ${queue.requestTimestamps.length}/${max}. Running: ${queue.running}. Queue size: ${queue.queue.length}`);
110
+ processQueue(key, debug, category);
111
+ });
112
+ };
113
+ const now = Date.now();
114
+ queue.requestTimestamps = cleanupTimestamps(queue.requestTimestamps, perMs, now);
115
+ if (queue.queue.length > 0 || queue.requestTimestamps.length >= max) {
116
+ queue.queue.push({
117
+ run,
118
+ addedAt: now
119
+ });
120
+ if (debug) console.log(`[apiQueue] ⏳ Queued request (${category}). Requests in window: ${queue.requestTimestamps.length}/${max}. Queue size: ${queue.queue.length}`);
121
+ processQueue(key, debug, category);
122
+ } else run();
123
+ });
124
+ }
125
+ function processQueue(key, debug, category) {
126
+ const queue = queues[key];
127
+ if (!queue || queue.queue.length === 0) return;
128
+ const { max, perMs } = LIMITS[category] ?? LIMITS.default;
129
+ const now = Date.now();
130
+ queue.requestTimestamps = cleanupTimestamps(queue.requestTimestamps, perMs, now);
131
+ if (queue.requestTimestamps.length < max) {
132
+ const next = queue.queue.shift();
133
+ if (next) {
134
+ if (debug) {
135
+ const waitTime = Date.now() - next.addedAt;
136
+ console.log(`[apiQueue] 🚀 Processing queued request (${category}). Waited: ${waitTime.toFixed(0)}ms. Queue size: ${queue.queue.length}`);
137
+ }
138
+ next.run();
139
+ }
140
+ if (queue.queue.length > 0) processQueue(key, debug, category);
141
+ } else {
142
+ const nextAvailable = getNextAvailableTime(queue.requestTimestamps, max, perMs, now);
143
+ const delay = Math.max(0, nextAvailable - now + 50);
144
+ if (debug) console.log(`[apiQueue] ⏸️ Delaying queue processing (${category}). Will retry in ${delay.toFixed(0)}ms. Queue size: ${queue.queue.length}`);
145
+ if (!queue.nextTimer) queue.nextTimer = setTimeout(() => {
146
+ queue.nextTimer = null;
147
+ processQueue(key, debug, category);
148
+ }, delay);
149
+ }
150
+ }
151
+ /**
152
+ * Cleanup function to clear all queues and timers for a specific client.
153
+ * Useful for testing or cleanup on application shutdown.
154
+ */
155
+ function clearQueue(clientId, category) {
156
+ if (category) {
157
+ const key = `${clientId}:${category}`;
158
+ const queue = queues[key];
159
+ if (queue) {
160
+ if (queue.nextTimer) {
161
+ clearTimeout(queue.nextTimer);
162
+ queue.nextTimer = null;
163
+ }
164
+ queue.queue = [];
165
+ queue.requestTimestamps = [];
166
+ queue.running = 0;
167
+ }
168
+ } else Object.keys(queues).forEach((key) => {
169
+ if (key.startsWith(`${clientId}:`)) {
170
+ const queue = queues[key];
171
+ if (queue?.nextTimer) {
172
+ clearTimeout(queue.nextTimer);
173
+ queue.nextTimer = null;
174
+ }
175
+ delete queues[key];
176
+ }
177
+ });
178
+ }
179
+ /**
180
+ * Very naive path-based category detection. If no matcher fits, the `default` category
181
+ * (effectively unlimited) is returned. Adjust these heuristics as your API surface evolves.
182
+ */
183
+ function categorizeRequest(path, method = "GET") {
184
+ const cleanPath = path.toLowerCase();
185
+ const isPost = method?.toUpperCase() === "POST";
186
+ if (cleanPath.includes("/documentsubmissions")) return isPost ? "submitDocuments" : "getSubmission";
187
+ if (cleanPath.includes("/documents/recent")) return "getRecentDocuments";
188
+ if (cleanPath.includes("/documents/search")) return "searchDocuments";
189
+ if (cleanPath.includes("/documents/state/") && cleanPath.endsWith("/state")) return "cancelDocument";
190
+ if (/\/documents\/[^/]+\/raw$/.test(cleanPath)) return "getDocument";
191
+ if (/\/documents\/[^/]+\/details$/.test(cleanPath)) return "getDocumentDetails";
192
+ if (cleanPath.includes("/taxpayer/search/tin")) return "searchTin";
193
+ if (cleanPath.includes("/taxpayer/validate/")) return "searchTin";
194
+ if (cleanPath.includes("/taxpayer/qrcode")) return "taxpayerQr";
195
+ if (cleanPath.includes("/searchtin")) return "searchTin";
196
+ if (cleanPath.includes("/qrcode")) return "taxpayerQr";
197
+ if (cleanPath.includes("/connect/token")) return "loginTaxpayer";
198
+ return "default";
199
+ }
200
+
201
+ //#endregion
202
+ Object.defineProperty(exports, 'categorizeRequest', {
203
+ enumerable: true,
204
+ get: function () {
205
+ return categorizeRequest;
206
+ }
207
+ });
208
+ Object.defineProperty(exports, 'clearQueue', {
209
+ enumerable: true,
210
+ get: function () {
211
+ return clearQueue;
212
+ }
213
+ });
214
+ Object.defineProperty(exports, 'queueRequest', {
215
+ enumerable: true,
216
+ get: function () {
217
+ return queueRequest;
218
+ }
219
+ });
220
+ //# sourceMappingURL=apiQueue-DgKWaQDS.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apiQueue-DgKWaQDS.cjs","names":["queues: Record<string, Queue>","LIMITS: Record<ApiCategory, { max: number; perMs: number }>","timestamps: number[]","windowMs: number","now: number","max: number","clientId: string","category: ApiCategory","task: Task<T>","now","key: string","debug: boolean","category?: ApiCategory","path: string","method: string"],"sources":["../src/utils/apiQueue.ts"],"sourcesContent":["// A very small utility that provides per-endpoint request queuing with 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\ntype Task<T> = () => Promise<T>\n\ninterface Queue {\n running: number\n queue: Array<{ run: () => void; addedAt: number }>\n requestTimestamps: number[] // Track all request timestamps in the sliding window\n nextTimer: NodeJS.Timeout | null // Track scheduled timer to avoid pile-up\n}\n\nconst queues: Record<string, Queue> = {}\n\n// Rate limits: max requests per time window (in ms)\nconst LIMITS: Record<ApiCategory, { max: number; perMs: number }> = {\n loginTaxpayer: { max: 12, perMs: 60000 }, // 12 req/60s\n loginIntermediary: { max: 12, perMs: 60000 }, // 12 req/60s\n submitDocuments: { max: 100, perMs: 60000 }, // 100 req/60s\n getSubmission: { max: 300, perMs: 60000 }, // 300 req/60s\n cancelDocument: { max: 12, perMs: 60000 }, // 12 req/60s\n rejectDocument: { max: 12, perMs: 60000 }, // 12 req/60s\n getDocument: { max: 60, perMs: 60000 }, // 60 req/60s\n getDocumentDetails: { max: 125, perMs: 60000 }, // 125 req/60s\n getRecentDocuments: { max: 12, perMs: 60000 }, // 12 req/60s\n searchDocuments: { max: 12, perMs: 60000 }, // 12 req/60s\n searchTin: { max: 60, perMs: 60000 }, // 60 req/60s\n taxpayerQr: { max: 60, perMs: 60000 }, // 60 req/60s\n default: { max: 12, perMs: 60000 }, // 12 req/60s (minimum limit)\n}\n\n/**\n * Clean up old timestamps outside the sliding window\n */\nfunction cleanupTimestamps(\n timestamps: number[],\n windowMs: number,\n now: number,\n): number[] {\n return timestamps.filter(ts => now - ts < windowMs)\n}\n\n/**\n * Calculate when we can make the next request without exceeding the rate limit\n */\nfunction getNextAvailableTime(\n timestamps: number[],\n max: number,\n windowMs: number,\n now: number,\n): number {\n if (timestamps.length < max) {\n return now // Can execute immediately\n }\n\n // We're at the limit. Find when the oldest request will expire\n const oldestTimestamp = timestamps[0]\n if (!oldestTimestamp) {\n return now // Shouldn't happen, but fallback to now\n }\n return oldestTimestamp + windowMs\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 *\n * This implementation uses a sliding window to track all requests within the time window.\n */\nexport function queueRequest<T>(\n clientId: string,\n category: ApiCategory,\n task: Task<T>,\n debug = false,\n): Promise<T> {\n const key = `${clientId}:${category}`\n if (!queues[key]) {\n queues[key] = {\n running: 0,\n queue: [],\n requestTimestamps: [],\n nextTimer: null,\n }\n }\n\n const queue = queues[key]!\n const { max, perMs } = LIMITS[category] ?? LIMITS.default\n\n return new Promise<T>((resolve, reject) => {\n const run = () => {\n const now = Date.now()\n\n // Clean up old timestamps before checking\n queue.requestTimestamps = cleanupTimestamps(\n queue.requestTimestamps,\n perMs,\n now,\n )\n\n // Check if we can execute now\n if (queue.requestTimestamps.length >= max) {\n // We've hit the rate limit, need to wait\n const nextAvailable = getNextAvailableTime(\n queue.requestTimestamps,\n max,\n perMs,\n now,\n )\n const waitTime = nextAvailable - now\n\n if (debug) {\n console.log(\n `[apiQueue] 🚫 Rate limit reached (${queue.requestTimestamps.length}/${max} in last ${perMs}ms). Queuing request. Need to wait ${waitTime.toFixed(0)}ms. Queue size: ${queue.queue.length + 1}`,\n )\n }\n\n // Re-queue this request to try again later\n queue.queue.push({ run, addedAt: now })\n // Don't call processQueue here - let the scheduled timer handle it\n // to avoid potential requeue loops\n return\n }\n\n // Record this request timestamp\n queue.requestTimestamps.push(now)\n queue.running++\n\n if (debug) {\n console.log(\n `[apiQueue] ▶️ Executing request (${category}). Requests in window: ${queue.requestTimestamps.length}/${max}. Running: ${queue.running}. Queue size: ${queue.queue.length}`,\n )\n }\n\n task()\n .then(resolve)\n .catch(reject)\n .finally(() => {\n queue.running--\n if (debug) {\n console.log(\n `[apiQueue] ✅ Request completed (${category}). Requests in window: ${queue.requestTimestamps.length}/${max}. Running: ${queue.running}. Queue size: ${queue.queue.length}`,\n )\n }\n processQueue(key, debug, category)\n })\n }\n\n // Add to queue or run immediately\n const now = Date.now()\n queue.requestTimestamps = cleanupTimestamps(\n queue.requestTimestamps,\n perMs,\n now,\n )\n\n if (queue.queue.length > 0 || queue.requestTimestamps.length >= max) {\n // Either there's already a queue, or we're at the rate limit\n queue.queue.push({ run, addedAt: now })\n if (debug) {\n console.log(\n `[apiQueue] ⏳ Queued request (${category}). Requests in window: ${queue.requestTimestamps.length}/${max}. Queue size: ${queue.queue.length}`,\n )\n }\n processQueue(key, debug, category)\n } else {\n // Can run immediately\n run()\n }\n })\n}\n\nfunction processQueue(key: string, debug: boolean, category: ApiCategory) {\n const queue = queues[key]\n if (!queue || queue.queue.length === 0) return\n\n const { max, perMs } = LIMITS[category] ?? LIMITS.default\n const now = Date.now()\n\n // Clean up old timestamps\n queue.requestTimestamps = cleanupTimestamps(\n queue.requestTimestamps,\n perMs,\n now,\n )\n\n // Check if we can process the next request\n if (queue.requestTimestamps.length < max) {\n // We have capacity, process immediately\n const next = queue.queue.shift()\n if (next) {\n if (debug) {\n const waitTime = Date.now() - next.addedAt\n console.log(\n `[apiQueue] 🚀 Processing queued request (${category}). Waited: ${waitTime.toFixed(0)}ms. Queue size: ${queue.queue.length}`,\n )\n }\n next.run()\n }\n // After one runs, immediately try again (in case there's more space)\n if (queue.queue.length > 0) {\n processQueue(key, debug, category)\n }\n } else {\n // We're at capacity, schedule for when the oldest request expires\n const nextAvailable = getNextAvailableTime(\n queue.requestTimestamps,\n max,\n perMs,\n now,\n )\n const delay = Math.max(0, nextAvailable - now + 50) // +50ms buffer for timer precision\n\n if (debug) {\n console.log(\n `[apiQueue] ⏸️ Delaying queue processing (${category}). Will retry in ${delay.toFixed(0)}ms. Queue size: ${queue.queue.length}`,\n )\n }\n\n // Only schedule a timer if one isn't already scheduled\n // This prevents timer pile-up during bursts\n if (!queue.nextTimer) {\n queue.nextTimer = setTimeout(() => {\n queue.nextTimer = null\n processQueue(key, debug, category)\n }, delay)\n }\n }\n}\n\n/**\n * Cleanup function to clear all queues and timers for a specific client.\n * Useful for testing or cleanup on application shutdown.\n */\nexport function clearQueue(clientId: string, category?: ApiCategory): void {\n if (category) {\n const key = `${clientId}:${category}`\n const queue = queues[key]\n if (queue) {\n if (queue.nextTimer) {\n clearTimeout(queue.nextTimer)\n queue.nextTimer = null\n }\n queue.queue = []\n queue.requestTimestamps = []\n queue.running = 0\n }\n } else {\n // Clear all queues for this client\n Object.keys(queues).forEach(key => {\n if (key.startsWith(`${clientId}:`)) {\n const queue = queues[key]\n if (queue?.nextTimer) {\n clearTimeout(queue.nextTimer)\n queue.nextTimer = null\n }\n delete queues[key]\n }\n })\n }\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":"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"}
@@ -173,6 +173,10 @@ const generateCleanInvoiceObject = (invoice) => {
173
173
  _: invoice.legalMonetaryTotal.taxInclusiveAmount,
174
174
  currencyID: invoice.invoiceCurrencyCode
175
175
  }],
176
+ PayableRoundingAmount: [{
177
+ _: invoice.legalMonetaryTotal.payableRoundingAmount,
178
+ currencyID: invoice.invoiceCurrencyCode
179
+ }],
176
180
  PayableAmount: [{
177
181
  _: invoice.legalMonetaryTotal.payableAmount,
178
182
  currencyID: invoice.invoiceCurrencyCode
@@ -545,7 +549,7 @@ const generateCompleteDocument = (invoices, signingCredentials) => {
545
549
  * Creates a line item with percentage-based taxation (e.g., SST, GST)
546
550
  */
547
551
  const createPercentageTaxLineItem = (params) => {
548
- const quantity = params.quantity || 1;
552
+ const quantity = params.quantity ? params.quantity <= 0 ? 1 : params.quantity ?? 1 : 1;
549
553
  const preDiscountAmount = params.unitPrice * quantity;
550
554
  const hasDiscount = params.discountAmount !== void 0 || params.discountRate !== void 0;
551
555
  const computedDiscountByRate = hasDiscount && params.discountRate !== void 0 ? preDiscountAmount * params.discountRate : 0;
@@ -594,15 +598,17 @@ const createFixedRateTaxLineItem = (params) => {
594
598
  /**
595
599
  * Calculates invoice totals from line items
596
600
  */
597
- const calculateInvoiceTotals = (lineItems) => {
601
+ const calculateInvoiceTotals = (lineItems, payableRoundingAmount = 0) => {
598
602
  const taxExclusiveAmount = lineItems.reduce((sum, item) => sum + item.totalTaxableAmountPerLine, 0);
599
603
  const totalTaxAmount = lineItems.reduce((sum, item) => sum + item.taxAmount, 0);
600
604
  const taxInclusiveAmount = taxExclusiveAmount + totalTaxAmount;
605
+ const payableAmount = taxInclusiveAmount + payableRoundingAmount;
601
606
  return {
602
607
  legalMonetaryTotal: {
603
608
  taxExclusiveAmount: Math.round(taxExclusiveAmount * 100) / 100,
604
609
  taxInclusiveAmount: Math.round(taxInclusiveAmount * 100) / 100,
605
- payableAmount: Math.round(taxInclusiveAmount * 100) / 100
610
+ payableRoundingAmount: Math.round(payableRoundingAmount * 100) / 100,
611
+ payableAmount: Math.round(payableAmount * 100) / 100
606
612
  },
607
613
  taxTotal: { taxAmount: Math.round(totalTaxAmount * 100) / 100 }
608
614
  };
@@ -174,6 +174,10 @@ const generateCleanInvoiceObject = (invoice) => {
174
174
  _: invoice.legalMonetaryTotal.taxInclusiveAmount,
175
175
  currencyID: invoice.invoiceCurrencyCode
176
176
  }],
177
+ PayableRoundingAmount: [{
178
+ _: invoice.legalMonetaryTotal.payableRoundingAmount,
179
+ currencyID: invoice.invoiceCurrencyCode
180
+ }],
177
181
  PayableAmount: [{
178
182
  _: invoice.legalMonetaryTotal.payableAmount,
179
183
  currencyID: invoice.invoiceCurrencyCode
@@ -546,7 +550,7 @@ const generateCompleteDocument = (invoices, signingCredentials) => {
546
550
  * Creates a line item with percentage-based taxation (e.g., SST, GST)
547
551
  */
548
552
  const createPercentageTaxLineItem = (params) => {
549
- const quantity = params.quantity || 1;
553
+ const quantity = params.quantity ? params.quantity <= 0 ? 1 : params.quantity ?? 1 : 1;
550
554
  const preDiscountAmount = params.unitPrice * quantity;
551
555
  const hasDiscount = params.discountAmount !== void 0 || params.discountRate !== void 0;
552
556
  const computedDiscountByRate = hasDiscount && params.discountRate !== void 0 ? preDiscountAmount * params.discountRate : 0;
@@ -595,15 +599,17 @@ const createFixedRateTaxLineItem = (params) => {
595
599
  /**
596
600
  * Calculates invoice totals from line items
597
601
  */
598
- const calculateInvoiceTotals = (lineItems) => {
602
+ const calculateInvoiceTotals = (lineItems, payableRoundingAmount = 0) => {
599
603
  const taxExclusiveAmount = lineItems.reduce((sum, item) => sum + item.totalTaxableAmountPerLine, 0);
600
604
  const totalTaxAmount = lineItems.reduce((sum, item) => sum + item.taxAmount, 0);
601
605
  const taxInclusiveAmount = taxExclusiveAmount + totalTaxAmount;
606
+ const payableAmount = taxInclusiveAmount + payableRoundingAmount;
602
607
  return {
603
608
  legalMonetaryTotal: {
604
609
  taxExclusiveAmount: Math.round(taxExclusiveAmount * 100) / 100,
605
610
  taxInclusiveAmount: Math.round(taxInclusiveAmount * 100) / 100,
606
- payableAmount: Math.round(taxInclusiveAmount * 100) / 100
611
+ payableRoundingAmount: Math.round(payableRoundingAmount * 100) / 100,
612
+ payableAmount: Math.round(payableAmount * 100) / 100
607
613
  },
608
614
  taxTotal: { taxAmount: Math.round(totalTaxAmount * 100) / 100 }
609
615
  };
@@ -724,4 +730,4 @@ Object.defineProperty(exports, 'transformDocumentForHashing', {
724
730
  return transformDocumentForHashing;
725
731
  }
726
732
  });
727
- //# sourceMappingURL=document-CCza2JPL.cjs.map
733
+ //# sourceMappingURL=document-DoQEvmcK.cjs.map