kassza 0.13.0 → 0.15.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 (164) hide show
  1. package/README.en.md +48 -0
  2. package/README.md +152 -22
  3. package/agents/README.md +6 -1
  4. package/agents/api.md +64 -12
  5. package/agents/pitfalls.md +12 -1
  6. package/agents/recipes.md +67 -8
  7. package/agents/skills/kassza/SKILL.md +3 -0
  8. package/agents/skills/migrate-from-szamlazz-js/SKILL.md +39 -0
  9. package/dist/batch/index.cjs +346 -0
  10. package/dist/batch/index.d.cts +91 -0
  11. package/dist/batch/index.d.ts +91 -0
  12. package/dist/batch/index.js +339 -0
  13. package/dist/cli.cjs +105 -14
  14. package/dist/cli.js +104 -13
  15. package/dist/{client-C-PdfL_P.cjs → client-CLFvJCVW.cjs} +358 -181
  16. package/dist/{client-CfaS1-Jx.js → client-Cvf-hTz_.js} +311 -152
  17. package/dist/{client-6MkeuFqD.d.ts → client-DeWWQ-RZ.d.ts} +99 -26
  18. package/dist/{client-B5FryKxJ.d.cts → client-SVZu25MB.d.cts} +99 -26
  19. package/dist/{context-DXNbTPwz.d.cts → context-CFNKzStE.d.cts} +82 -58
  20. package/dist/{context-BxQlIlug.d.ts → context-kb33TgzE.d.ts} +82 -58
  21. package/dist/cookie-stores/index.cjs +21 -150
  22. package/dist/cookie-stores/index.d.cts +4 -63
  23. package/dist/cookie-stores/index.d.ts +4 -63
  24. package/dist/cookie-stores/index.js +3 -144
  25. package/dist/create-CflXrPip.js +204 -0
  26. package/dist/create-CvUvLUaT.cjs +257 -0
  27. package/dist/{create-once-DgMtKKcG.cjs → create-once-9yxR_Er_.cjs} +285 -33
  28. package/dist/create-once-Bh6fOWkW.d.cts +20 -0
  29. package/dist/{create-once-Cz621l8r.js → create-once-Ca2gMk1h.js} +250 -34
  30. package/dist/create-once-VKp0tVow.d.ts +20 -0
  31. package/dist/data-link/index.cjs +4 -4
  32. package/dist/data-link/index.d.cts +2 -52
  33. package/dist/data-link/index.d.ts +2 -52
  34. package/dist/data-link/index.js +4 -4
  35. package/dist/delegation/index.cjs +4 -4
  36. package/dist/delegation/index.d.cts +2 -2
  37. package/dist/delegation/index.d.ts +2 -2
  38. package/dist/delegation/index.js +4 -4
  39. package/dist/{errors-DLrb0g64.js → errors-DtN4g5Z9.js} +9 -1
  40. package/dist/{errors-DxeuTslP.cjs → errors-EE96_6D_.cjs} +20 -0
  41. package/dist/{get-Csm2n0rZ.cjs → get-BdMTseh4.cjs} +9 -183
  42. package/dist/{get-DT3JEgQ6.js → get-CZEShCcj.js} +4 -124
  43. package/dist/index-BhUPY_EH.d.cts +109 -0
  44. package/dist/index-BqGgQ4t3.d.ts +109 -0
  45. package/dist/{index-CYAGCdKJ.d.cts → index-C-7yWV7L.d.cts} +24 -1
  46. package/dist/{index-CYAGCdKJ.d.ts → index-C-7yWV7L.d.ts} +24 -1
  47. package/dist/index.cjs +19 -5
  48. package/dist/index.d.cts +10 -20
  49. package/dist/index.d.ts +10 -20
  50. package/dist/index.js +6 -6
  51. package/dist/ipn/index.cjs +26 -5
  52. package/dist/ipn/index.d.cts +12 -1
  53. package/dist/ipn/index.d.ts +12 -1
  54. package/dist/ipn/index.js +26 -6
  55. package/dist/{issue-AGRohSm7.cjs → issue-Bin4tKD_.cjs} +416 -342
  56. package/dist/{issue-DtSLpVdJ.js → issue-DMA1Nbr1.js} +394 -266
  57. package/dist/journal/index.cjs +454 -0
  58. package/dist/journal/index.d.cts +18 -0
  59. package/dist/journal/index.d.ts +18 -0
  60. package/dist/journal/index.js +445 -0
  61. package/dist/journal-DvI81Kt7.d.cts +114 -0
  62. package/dist/journal-Y9qrIB8G.d.ts +114 -0
  63. package/dist/mcp/index.cjs +1 -1
  64. package/dist/mcp/index.d.cts +1 -1
  65. package/dist/mcp/index.d.ts +1 -1
  66. package/dist/mcp/index.js +1 -1
  67. package/dist/money/index.cjs +2 -1
  68. package/dist/money/index.d.cts +2 -2
  69. package/dist/money/index.d.ts +2 -2
  70. package/dist/money/index.js +2 -2
  71. package/dist/{money-t7uy1n91.js → money-Cw457SD6.js} +95 -3
  72. package/dist/{money-BxytJW6f.cjs → money-K9j0wPit.cjs} +99 -1
  73. package/dist/nav/index.cjs +2 -2
  74. package/dist/nav/index.d.cts +1 -1
  75. package/dist/nav/index.d.ts +1 -1
  76. package/dist/nav/index.js +2 -2
  77. package/dist/{nav-BEwcX7-N.cjs → nav-BNbc8U0R.cjs} +1 -1
  78. package/dist/{nav-CK1WgYo7.js → nav-BcCerwWS.js} +1 -1
  79. package/dist/node/index.cjs +124 -0
  80. package/dist/node/index.d.cts +35 -0
  81. package/dist/node/index.d.ts +35 -0
  82. package/dist/node/index.js +120 -0
  83. package/dist/observe/index.cjs +311 -0
  84. package/dist/observe/index.d.cts +53 -0
  85. package/dist/observe/index.d.ts +53 -0
  86. package/dist/observe/index.js +303 -0
  87. package/dist/once-BN_3B5OF.cjs +103 -0
  88. package/dist/once-CqqZxsXG.js +74 -0
  89. package/dist/{parse-C608ehqM.js → parse-Cr7yQ3_z.js} +2 -2
  90. package/dist/{parse-BJj-aqKy.cjs → parse-DOA2XQ3e.cjs} +2 -2
  91. package/dist/payments/barion.cjs +26 -2
  92. package/dist/payments/barion.d.cts +2 -2
  93. package/dist/payments/barion.d.ts +2 -2
  94. package/dist/payments/barion.js +26 -2
  95. package/dist/payments/index.cjs +7 -2
  96. package/dist/payments/index.d.cts +4 -4
  97. package/dist/payments/index.d.ts +4 -4
  98. package/dist/payments/index.js +3 -3
  99. package/dist/payments/paypal.cjs +16 -2
  100. package/dist/payments/paypal.d.cts +2 -2
  101. package/dist/payments/paypal.d.ts +2 -2
  102. package/dist/payments/paypal.js +16 -2
  103. package/dist/payments/revolut.cjs +16 -2
  104. package/dist/payments/revolut.d.cts +2 -2
  105. package/dist/payments/revolut.d.ts +2 -2
  106. package/dist/payments/revolut.js +16 -2
  107. package/dist/payments/simplepay.cjs +36 -4
  108. package/dist/payments/simplepay.d.cts +2 -2
  109. package/dist/payments/simplepay.d.ts +2 -2
  110. package/dist/payments/simplepay.js +36 -4
  111. package/dist/payments/stripe.cjs +19 -2
  112. package/dist/payments/stripe.d.cts +2 -2
  113. package/dist/payments/stripe.d.ts +2 -2
  114. package/dist/payments/stripe.js +19 -2
  115. package/dist/push-BXj0Sf0Z.d.ts +52 -0
  116. package/dist/push-D4nNbCNV.d.cts +52 -0
  117. package/dist/reference-B5TATqb5.js +124 -0
  118. package/dist/reference-BIdYMK0t.cjs +183 -0
  119. package/dist/reports/index.cjs +1 -1
  120. package/dist/reports/index.d.cts +1 -1
  121. package/dist/reports/index.d.ts +1 -1
  122. package/dist/reports/index.js +1 -1
  123. package/dist/{serialize-DSE_o9RK.js → serialize-B2eFh3D-.js} +5 -1
  124. package/dist/{serialize-BXOww1Uq.cjs → serialize-CquIsxA-.cjs} +5 -1
  125. package/dist/{server-Cd8HB661.js → server-CqqECkMp.js} +8 -7
  126. package/dist/{server-C6PoNkdD.cjs → server-DENsDDW9.cjs} +17 -16
  127. package/dist/session-Ckl5eJhV.d.ts +7 -0
  128. package/dist/{session-CKOuE6Jc.cjs → session-DeM5vjvC.cjs} +69 -13
  129. package/dist/{session-rMKaJcmq.js → session-SaEEXu2N.js} +58 -14
  130. package/dist/session-YbChChx7.d.cts +7 -0
  131. package/dist/storage/fs.cjs +1 -1
  132. package/dist/storage/fs.js +1 -1
  133. package/dist/storage/index.cjs +1 -1
  134. package/dist/storage/index.js +1 -1
  135. package/dist/store-C1rHd9DB.d.cts +19 -0
  136. package/dist/store-C1rHd9DB.d.ts +19 -0
  137. package/dist/stores/index.cjs +24 -0
  138. package/dist/stores/index.d.cts +4 -0
  139. package/dist/stores/index.d.ts +4 -0
  140. package/dist/stores/index.js +3 -0
  141. package/dist/stores-BNrS0dWl.cjs +506 -0
  142. package/dist/stores-DhjGBrl_.js +441 -0
  143. package/dist/testing/index.cjs +28 -16
  144. package/dist/testing/index.d.cts +6 -3
  145. package/dist/testing/index.d.ts +6 -3
  146. package/dist/testing/index.js +26 -14
  147. package/dist/{types-BVeR7KyU.d.ts → types-B1A8mUIE.d.cts} +13 -1
  148. package/dist/{types-BVeR7KyU.d.cts → types-ByAEvGsZ.d.ts} +13 -1
  149. package/dist/{types-k--zHf4l.d.ts → types-D1zDOH_F.d.ts} +1 -1
  150. package/dist/{types-DH8QoNZK.d.cts → types-lnKkQdot.d.cts} +1 -1
  151. package/dist/warnings-B3DXnveC.d.cts +31 -0
  152. package/dist/warnings-B3DXnveC.d.ts +31 -0
  153. package/dist/warnings-DeIw5RFj.cjs +20 -0
  154. package/dist/warnings-DwTl5J-2.js +15 -0
  155. package/dist/{webhook-CrEGohbg.js → webhook-CQVShAfI.js} +44 -1
  156. package/dist/{webhook-81giRMdQ.cjs → webhook-Ckj-_hPD.cjs} +67 -0
  157. package/dist/webhook-CmdhGiH5.d.cts +11 -0
  158. package/dist/webhook-D6lEejob.d.ts +11 -0
  159. package/llms.txt +4 -3
  160. package/package.json +92 -4
  161. package/dist/session-Dm_45x8K.d.cts +0 -11
  162. package/dist/session-Dm_45x8K.d.ts +0 -11
  163. package/dist/webhook-CpCMHhn2.d.ts +0 -6
  164. package/dist/webhook-CpMJuP6c.d.cts +0 -6
@@ -1,14 +1,16 @@
1
- const require_create_once = require("./create-once-DgMtKKcG.cjs");
2
- const require_errors = require("./errors-DxeuTslP.cjs");
1
+ const require_create_once = require("./create-once-9yxR_Er_.cjs");
2
+ const require_errors = require("./errors-EE96_6D_.cjs");
3
3
  const require_binary = require("./binary-B89HM03_.cjs");
4
- const require_serialize = require("./serialize-BXOww1Uq.cjs");
5
- const require_parse = require("./parse-BJj-aqKy.cjs");
6
- const require_session = require("./session-CKOuE6Jc.cjs");
7
- const require_dates = require("./dates-CPgBodkP.cjs");
4
+ const require_serialize = require("./serialize-CquIsxA-.cjs");
5
+ const require_parse = require("./parse-DOA2XQ3e.cjs");
6
+ const require_session = require("./session-DeM5vjvC.cjs");
7
+ const require_warnings = require("./warnings-DeIw5RFj.cjs");
8
+ const require_issue = require("./issue-Bin4tKD_.cjs");
8
9
  const require_vat = require("./vat-g35srbNG.cjs");
9
- const require_money = require("./money-BxytJW6f.cjs");
10
- const require_issue = require("./issue-AGRohSm7.cjs");
11
- const require_get = require("./get-Csm2n0rZ.cjs");
10
+ const require_money = require("./money-K9j0wPit.cjs");
11
+ const require_reference = require("./reference-BIdYMK0t.cjs");
12
+ const require_get = require("./get-BdMTseh4.cjs");
13
+ const require_create = require("./create-CvUvLUaT.cjs");
12
14
  //#region src/core/attempt-ledger.ts
13
15
  const ATTEMPT_LEDGER_TTL_SECONDS = 86400;
14
16
  const ATTEMPT_LEDGER_KEY_PREFIX = "szamlazz:attempts:";
@@ -38,17 +40,63 @@ function parseCount(value) {
38
40
  const parsed = Number.parseInt(value, 10);
39
41
  return Number.isInteger(parsed) && parsed > 0 ? parsed : 0;
40
42
  }
43
+ function createLocalCounter() {
44
+ const counts = /* @__PURE__ */ new Map();
45
+ const get = (key) => {
46
+ const entry = counts.get(key);
47
+ if (!entry) return 0;
48
+ if (entry.expiresAt <= Date.now()) {
49
+ counts.delete(key);
50
+ return 0;
51
+ }
52
+ return entry.count;
53
+ };
54
+ return {
55
+ get,
56
+ bump(key) {
57
+ const count = get(key) + 1;
58
+ counts.delete(key);
59
+ counts.set(key, {
60
+ count,
61
+ expiresAt: Date.now() + ATTEMPT_LEDGER_TTL_SECONDS * 1e3
62
+ });
63
+ if (counts.size > 1e3) {
64
+ const oldest = counts.keys().next().value;
65
+ if (oldest !== void 0) counts.delete(oldest);
66
+ }
67
+ },
68
+ clear(key) {
69
+ counts.delete(key);
70
+ }
71
+ };
72
+ }
41
73
  function createAttemptLedger(store) {
74
+ const local = createLocalCounter();
75
+ const unpersisted = createLocalCounter();
42
76
  return {
43
77
  async failures(key) {
44
- return parseCount(await store.get(key));
78
+ return parseCount(await store.get(key)) + unpersisted.get(key);
45
79
  },
46
- async recordFailure(key, previous) {
47
- const next = previous + 1;
48
- await store.set(key, String(next), ATTEMPT_LEDGER_TTL_SECONDS);
49
- return next;
80
+ fallbackFailures(key) {
81
+ return local.get(key);
82
+ },
83
+ async recordFailure(key) {
84
+ local.bump(key);
85
+ try {
86
+ if (store.increment) {
87
+ await store.increment(key, ATTEMPT_LEDGER_TTL_SECONDS);
88
+ return;
89
+ }
90
+ const current = parseCount(await store.get(key));
91
+ await store.set(key, String(current + 1), ATTEMPT_LEDGER_TTL_SECONDS);
92
+ } catch (error) {
93
+ unpersisted.bump(key);
94
+ throw error;
95
+ }
50
96
  },
51
97
  async reset(key) {
98
+ local.clear(key);
99
+ unpersisted.clear(key);
52
100
  await store.delete(key);
53
101
  }
54
102
  };
@@ -63,6 +111,7 @@ function attemptLimitError(action, key, failures) {
63
111
  }
64
112
  const DEFAULT_SAFE_ATTEMPTS = 3;
65
113
  const DEFAULT_RETRY_DELAY_MS = 1e3;
114
+ const RETRY_JITTER = .2;
66
115
  function readEnv(name) {
67
116
  const value = (globalThis.process?.env)?.[name]?.trim();
68
117
  return value ? value : void 0;
@@ -112,22 +161,49 @@ function sleep(ms, signal) {
112
161
  signal?.addEventListener("abort", onAbort, { once: true });
113
162
  });
114
163
  }
115
- async function ignoreStoreFailure(operation) {
116
- try {
117
- return await operation();
118
- } catch {
119
- return;
120
- }
164
+ function hookWarning(name, action, error) {
165
+ return {
166
+ kind: "hook",
167
+ message: `A(z) ${name} hook hibát dobott, a kassza figyelmen kívül hagyta.`,
168
+ error,
169
+ action,
170
+ operation: name
171
+ };
121
172
  }
122
- function callHook(hook, event) {
173
+ function callHook(hooks, name, event) {
174
+ const hook = hooks[name];
123
175
  if (!hook) return;
124
176
  try {
125
177
  const result = hook(event);
126
- if (result instanceof Promise) result.catch(() => void 0);
127
- } catch {
128
- return;
178
+ if (result instanceof Promise) result.catch((error) => require_warnings.emitWarning(hooks.onWarning, hookWarning(name, event.action, error)));
179
+ } catch (error) {
180
+ require_warnings.emitWarning(hooks.onWarning, hookWarning(name, event.action, error));
129
181
  }
130
182
  }
183
+ function parseRetryAfter(value, now) {
184
+ if (value === null) return void 0;
185
+ const trimmed = value.trim();
186
+ if (/^\d+$/.test(trimmed)) return Number(trimmed) * 1e3;
187
+ const date = Date.parse(trimmed);
188
+ if (Number.isNaN(date)) return void 0;
189
+ return Math.max(0, date - now);
190
+ }
191
+ function retryDelay(baseMs, attempt, random = Math.random()) {
192
+ const backoff = baseMs * 2 ** (attempt - 1);
193
+ return Math.round(backoff * (.8 + 2 * RETRY_JITTER * random));
194
+ }
195
+ function resolveLedgerMode(value) {
196
+ if (value === void 0 || value === "fail-open" || value === "fail-closed") return value ?? "fail-open";
197
+ throw configurationError(`Az attemptLedgerMode értéke 'fail-open' vagy 'fail-closed' lehet, kapott: ${String(value)}`);
198
+ }
199
+ function storeUnavailableError(action, error) {
200
+ return new require_errors.SzamlazzError("A próbálkozás-napló tárolója nem érhető el, ezért a kassza nem küldte el a kérést.", {
201
+ category: "store_unavailable",
202
+ action,
203
+ cause: error,
204
+ hint: "Az attemptLedgerMode: 'fail-closed' beállítás miatt a kassza nem küld kérést, amíg a napló (például a Redis) nem elérhető. Állítsd helyre a tárolót, vagy válts 'fail-open' módra."
205
+ });
206
+ }
131
207
  function clampAttempts(value) {
132
208
  if (value === void 0) return DEFAULT_SAFE_ATTEMPTS;
133
209
  if (!Number.isInteger(value) || value < 1) throw configurationError(`A maxAttempts értéke legalább 1 egész szám legyen, kapott: ${value}`);
@@ -167,11 +243,27 @@ function createAgentContext(options = {}) {
167
243
  return sessionKeyPromise;
168
244
  };
169
245
  const hooks = options.hooks ?? {};
246
+ const warn = (warning) => require_warnings.emitWarning(hooks.onWarning, warning);
170
247
  const ledger = options.attemptLedger ? createAttemptLedger(options.attemptLedger) : void 0;
248
+ const ledgerMode = resolveLedgerMode(options.attemptLedgerMode);
249
+ async function ignoreStoreFailure(operation, action, run) {
250
+ try {
251
+ return await run();
252
+ } catch (error) {
253
+ warn({
254
+ kind: "session",
255
+ message: `A session cookie tároló "${operation}" művelete sikertelen, a kérés session nélkül folytatódik.`,
256
+ error,
257
+ action,
258
+ operation
259
+ });
260
+ return;
261
+ }
262
+ }
171
263
  const cooldownMs = resolveCooldown(options.maintenanceCooldownMs);
172
264
  let blockedUntil = 0;
173
265
  async function send(request) {
174
- const cookie = cookieStore ? await ignoreStoreFailure(async () => cookieStore.get(await resolveSessionKey())) : void 0;
266
+ const cookie = cookieStore ? await ignoreStoreFailure("get", request.action, async () => cookieStore.get(await resolveSessionKey())) : void 0;
175
267
  request.signal?.throwIfAborted();
176
268
  const headers = new Headers({ Accept: "application/xml, application/pdf, text/plain, */*" });
177
269
  if (cookie) headers.set("Cookie", cookie);
@@ -202,12 +294,15 @@ function createAgentContext(options = {}) {
202
294
  }
203
295
  if (cookieStore) {
204
296
  const merged = require_session.mergeSetCookies(cookie, response.headers);
205
- if (merged) await ignoreStoreFailure(async () => cookieStore.set(await resolveSessionKey(), merged, require_session.SESSION_TTL_SECONDS));
297
+ if (merged) await ignoreStoreFailure("set", request.action, async () => cookieStore.set(await resolveSessionKey(), merged, require_session.SESSION_TTL_SECONDS));
206
298
  }
207
- return require_parse.createAgentResponse(request.action, response.status, response.headers, body);
299
+ return {
300
+ response: require_parse.createAgentResponse(request.action, response.status, response.headers, body),
301
+ sessionReused: Boolean(cookie)
302
+ };
208
303
  }
209
304
  async function resetSession() {
210
- if (cookieStore) await ignoreStoreFailure(async () => cookieStore.delete(await resolveSessionKey()));
305
+ if (cookieStore) await ignoreStoreFailure("delete", void 0, async () => cookieStore.delete(await resolveSessionKey()));
211
306
  }
212
307
  async function resetAttempts(key) {
213
308
  if (ledger) await ledger.reset(key);
@@ -223,6 +318,41 @@ function createAgentContext(options = {}) {
223
318
  }))
224
319
  });
225
320
  }
321
+ function ledgerWarning(operation, action, error) {
322
+ warn({
323
+ kind: "ledger",
324
+ message: `A próbálkozás-napló tárolójának "${operation}" művelete sikertelen. A kassza a folyamaton belüli számlálót használja, a folyamatok közötti védelem most nem él.`,
325
+ error,
326
+ action,
327
+ operation
328
+ });
329
+ }
330
+ async function readFailures(action, key) {
331
+ if (!ledger || key === void 0) return 0;
332
+ try {
333
+ return await ledger.failures(key);
334
+ } catch (error) {
335
+ if (ledgerMode === "fail-closed") throw storeUnavailableError(action, error);
336
+ ledgerWarning("get", action, error);
337
+ return ledger.fallbackFailures(key);
338
+ }
339
+ }
340
+ async function recordFailure(action, key) {
341
+ if (!ledger || key === void 0) return;
342
+ try {
343
+ await ledger.recordFailure(key);
344
+ } catch (error) {
345
+ ledgerWarning("increment", action, error);
346
+ }
347
+ }
348
+ async function clearFailures(action, key) {
349
+ if (!ledger || key === void 0) return;
350
+ try {
351
+ await ledger.reset(key);
352
+ } catch (error) {
353
+ ledgerWarning("delete", action, error);
354
+ }
355
+ }
226
356
  function assertNoCooldown(action) {
227
357
  const remainingMs = blockedUntil - Date.now();
228
358
  if (remainingMs > 0) throw maintenanceCooldownError(action, remainingMs);
@@ -230,45 +360,72 @@ function createAgentContext(options = {}) {
230
360
  async function execute(request, parse) {
231
361
  const maxAttempts = request.safeToRetry ? safeAttempts : 1;
232
362
  const ledgerKey = await ledgerKeyFor(request);
363
+ const requestId = crypto.randomUUID();
233
364
  for (let attempt = 1;; attempt++) {
234
365
  assertNoCooldown(request.action);
235
- const failures = ledger && ledgerKey !== void 0 ? await ignoreStoreFailure(() => ledger.failures(ledgerKey)) ?? 0 : 0;
366
+ const failures = await readFailures(request.action, ledgerKey);
236
367
  if (ledgerKey !== void 0 && failures >= 5) throw attemptLimitError(request.action, ledgerKey, failures);
237
368
  const startedAt = Date.now();
238
- callHook(hooks.onRequest, {
369
+ let retryAfterMs;
370
+ const base = {
239
371
  action: request.action,
240
- attempt
241
- });
372
+ attempt,
373
+ requestId
374
+ };
375
+ callHook(hooks, "onRequest", base);
242
376
  try {
243
- const response = await send(request);
244
- callHook(hooks.onResponse, {
245
- action: request.action,
246
- attempt,
377
+ const { response, sessionReused } = await send(request);
378
+ retryAfterMs = parseRetryAfter(response.headers.get("retry-after"), Date.now());
379
+ callHook(hooks, "onResponse", {
380
+ ...base,
247
381
  status: response.status,
248
- durationMs: Date.now() - startedAt
382
+ durationMs: Date.now() - startedAt,
383
+ responseBytes: response.body.byteLength,
384
+ sessionReused
249
385
  });
250
386
  const result = await parse(response);
251
- if (ledger && ledgerKey !== void 0 && failures > 0) await ignoreStoreFailure(() => ledger.reset(ledgerKey));
387
+ if (failures > 0) await clearFailures(request.action, ledgerKey);
388
+ callHook(hooks, "onComplete", {
389
+ ...base,
390
+ outcome: "success",
391
+ durationMs: Date.now() - startedAt,
392
+ willRetry: false
393
+ });
252
394
  return result;
253
395
  } catch (error) {
254
- if (!(error instanceof require_errors.SzamlazzError)) throw error;
255
- if (ledger && ledgerKey !== void 0 && countsAsFailedAttempt(error)) await ignoreStoreFailure(() => ledger.recordFailure(ledgerKey, failures));
396
+ if (!(error instanceof require_errors.SzamlazzError)) {
397
+ callHook(hooks, "onComplete", {
398
+ ...base,
399
+ outcome: "error",
400
+ durationMs: Date.now() - startedAt,
401
+ willRetry: false
402
+ });
403
+ throw error;
404
+ }
405
+ if (countsAsFailedAttempt(error)) await recordFailure(request.action, ledgerKey);
256
406
  if (error.category === "maintenance" && cooldownMs > 0) blockedUntil = Date.now() + cooldownMs;
257
- const willRetry = error.retryable && attempt < maxAttempts && !(error.category === "maintenance" && cooldownMs > 0);
258
- callHook(hooks.onError, {
259
- action: request.action,
260
- attempt,
407
+ const willRetry = error.retryable && attempt < maxAttempts && !(error.category === "maintenance" && cooldownMs > 0) && (retryAfterMs === void 0 || retryAfterMs <= 6e4);
408
+ callHook(hooks, "onError", {
409
+ ...base,
410
+ error,
411
+ willRetry
412
+ });
413
+ callHook(hooks, "onComplete", {
414
+ ...base,
415
+ outcome: "error",
416
+ durationMs: Date.now() - startedAt,
261
417
  error,
262
418
  willRetry
263
419
  });
264
420
  if (error.category === "auth") await resetSession();
265
421
  if (!willRetry) throw error;
266
- await sleep(retryDelayMs * 2 ** (attempt - 1), request.signal);
422
+ await sleep(Math.max(retryDelay(retryDelayMs, attempt), retryAfterMs ?? 0), request.signal);
267
423
  }
268
424
  }
269
425
  }
270
426
  return {
271
427
  credentials,
428
+ warn,
272
429
  execute,
273
430
  resetSession,
274
431
  resetAttempts
@@ -555,100 +712,25 @@ async function previewInvoice(ctx, defaults, input, options = {}) {
555
712
  const items = itemsOf(invoice);
556
713
  return ctx.execute(buildRequest(ctx, invoice, true, options), (response) => parseInvoicePreviewResponse(response, items));
557
714
  }
558
- function normalizeRegisterInput(input) {
559
- const payments = "payments" in input ? input.payments : [{
560
- amount: input.amount,
561
- method: input.method ?? "átutalás",
562
- date: input.date,
563
- description: input.description
564
- }];
565
- if (!Array.isArray(payments) || payments.length === 0) throw require_get.validationError("Adj meg legalább egy befizetést (payments). A korábbi befizetések törléséhez a clearPayments függvényt használd.");
566
- if (payments.length > 5) throw require_get.validationError(`Egy kérésben legfeljebb 5 befizetés rögzíthető, kapott: ${payments.length}.`);
567
- return {
568
- invoiceNumber: input.invoiceNumber,
569
- taxNumber: input.taxNumber,
570
- additive: input.additive ?? true,
571
- payments
572
- };
573
- }
574
- function normalizeClearInput(input) {
575
- const target = typeof input === "string" ? { invoiceNumber: input } : input;
576
- return {
577
- invoiceNumber: target.invoiceNumber,
578
- taxNumber: target.taxNumber,
579
- additive: false,
580
- payments: []
581
- };
582
- }
583
- function paymentNode(payment, index) {
584
- const position = index + 1;
585
- if (typeof payment.amount !== "number" || !Number.isFinite(payment.amount)) throw require_get.validationError(`A(z) ${position}. befizetés összege (amount) nem véges szám.`);
586
- return require_serialize.el("kifizetes", [
587
- require_serialize.el("datum", payment.date === void 0 ? require_dates.todayInBudapest() : require_get.agentDate(payment.date, "date")),
588
- require_serialize.el("jogcim", require_get.requireText(payment.method, `Add meg a(z) ${position}. befizetés módját (method).`)),
589
- require_serialize.el("osszeg", payment.amount),
590
- require_serialize.optionalEl("leiras", require_get.optionalText(payment.description))
591
- ]);
592
- }
593
- function buildPaymentXml(credentials, request) {
594
- const invoiceNumber = require_get.requireText(request.invoiceNumber, "Add meg a számla számát (invoiceNumber), amelyhez a befizetés tartozik.");
595
- return require_serialize.buildXmlDocument({
596
- root: "xmlszamlakifiz",
597
- namespace: "http://www.szamlazz.hu/xmlszamlakifiz",
598
- schemaLocation: `${require_get.AGENT_XSD_BASE_URL}agentkifiz/xmlszamlakifiz.xsd`,
599
- children: [require_serialize.el("beallitasok", [
600
- ...credentials,
601
- require_serialize.el("szamlaszam", invoiceNumber),
602
- require_serialize.optionalEl("adoszam", require_get.optionalText(request.taxNumber)),
603
- require_serialize.el("additiv", request.additive),
604
- require_serialize.el("valaszVerzio", 2)
605
- ]), ...request.payments.map(paymentNode)]
606
- });
607
- }
608
- function parseRegisterPaymentResponse(response, invoiceNumber) {
609
- const result = require_get.parseInvoiceResult(response);
610
- return {
611
- invoiceNumber: result.number ?? invoiceNumber.trim(),
612
- netTotal: result.netTotal,
613
- grossTotal: result.grossTotal,
614
- outstanding: result.outstanding,
615
- buyerAccountUrl: result.buyerAccountUrl
616
- };
617
- }
618
- function sendPaymentRequest(ctx, request, options) {
619
- const xml = buildPaymentXml(ctx.credentials, request);
620
- return ctx.execute({
621
- action: "registerPayment",
622
- xml,
623
- signal: options.signal,
624
- safeToRetry: !request.additive
625
- }, (response) => parseRegisterPaymentResponse(response, request.invoiceNumber));
626
- }
627
- async function registerPayment(ctx, input, options = {}) {
628
- return sendPaymentRequest(ctx, normalizeRegisterInput(input), options);
629
- }
630
- async function clearPayments(ctx, input, options = {}) {
631
- return sendPaymentRequest(ctx, normalizeClearInput(input), options);
632
- }
633
715
  //#endregion
634
716
  //#region src/invoices/pdf.ts
635
717
  function buildInvoicePdfXml(credentials, reference) {
636
- const resolved = require_get.resolveInvoiceReference(reference);
718
+ const resolved = require_reference.resolveInvoiceReference(reference);
637
719
  return require_serialize.buildXmlDocument({
638
720
  root: "xmlszamlapdf",
639
721
  namespace: "http://www.szamlazz.hu/xmlszamlapdf",
640
- schemaLocation: `${require_get.AGENT_XSD_BASE_URL}agentpdf/xmlszamlapdf.xsd`,
722
+ schemaLocation: `${require_reference.AGENT_XSD_BASE_URL}agentpdf/xmlszamlapdf.xsd`,
641
723
  children: [
642
724
  ...credentials,
643
- require_serialize.optionalEl("szamlaszam", require_get.referenceValue(resolved, "invoiceNumber")),
644
- require_serialize.optionalEl("rendelesSzam", require_get.referenceValue(resolved, "orderNumber")),
725
+ require_serialize.optionalEl("szamlaszam", require_reference.referenceValue(resolved, "invoiceNumber")),
726
+ require_serialize.optionalEl("rendelesSzam", require_reference.referenceValue(resolved, "orderNumber")),
645
727
  require_serialize.el("valaszVerzio", 2),
646
- require_serialize.optionalEl("szamlaKulsoAzon", require_get.referenceValue(resolved, "externalId"))
728
+ require_serialize.optionalEl("szamlaKulsoAzon", require_reference.referenceValue(resolved, "externalId"))
647
729
  ]
648
730
  });
649
731
  }
650
732
  function parseInvoicePdfResponse(response) {
651
- const { pdf, ...rest } = require_get.parseInvoiceResult(response);
733
+ const { pdf, ...rest } = require_reference.parseInvoiceResult(response);
652
734
  if (pdf === void 0 || pdf.length === 0) throw require_parse.unexpectedResponse(response, "A válasz nem tartalmaz PDF-et.");
653
735
  return {
654
736
  ...rest,
@@ -667,12 +749,12 @@ async function getInvoicePdf(ctx, reference, options = {}) {
667
749
  const RESPONSE_ROOTS = /* @__PURE__ */ new Set(["xmlszamladbkdelvalasz", "xmlszamlavalasz"]);
668
750
  const REFERENCE_HINT = "Add meg a díjbekérő számát szövegként, vagy pontosan egyet ezek közül: { proformaNumber }, { orderNumber }.";
669
751
  function resolveProformaReference(reference) {
670
- if (typeof reference === "string") return { proformaNumber: require_get.requireText(reference, `Üres díjbekérőszám. ${REFERENCE_HINT}`) };
671
- if (typeof reference !== "object" || reference === null) throw require_get.validationError(`Érvénytelen díjbekérő-hivatkozás. ${REFERENCE_HINT}`);
752
+ if (typeof reference === "string") return { proformaNumber: require_reference.requireText(reference, `Üres díjbekérőszám. ${REFERENCE_HINT}`) };
753
+ if (typeof reference !== "object" || reference === null) throw require_reference.validationError(`Érvénytelen díjbekérő-hivatkozás. ${REFERENCE_HINT}`);
672
754
  const record = reference;
673
755
  const hasNumber = record.proformaNumber !== void 0;
674
- if (hasNumber === (record.orderNumber !== void 0)) throw require_get.validationError(`Érvénytelen díjbekérő-hivatkozás. ${REFERENCE_HINT}`);
675
- return hasNumber ? { proformaNumber: require_get.requireText(record.proformaNumber, `Üres proformaNumber. ${REFERENCE_HINT}`) } : { orderNumber: require_get.requireText(record.orderNumber, `Üres orderNumber. ${REFERENCE_HINT}`) };
756
+ if (hasNumber === (record.orderNumber !== void 0)) throw require_reference.validationError(`Érvénytelen díjbekérő-hivatkozás. ${REFERENCE_HINT}`);
757
+ return hasNumber ? { proformaNumber: require_reference.requireText(record.proformaNumber, `Üres proformaNumber. ${REFERENCE_HINT}`) } : { orderNumber: require_reference.requireText(record.orderNumber, `Üres orderNumber. ${REFERENCE_HINT}`) };
676
758
  }
677
759
  function buildDeleteProformaXml(credentials, reference) {
678
760
  const resolved = resolveProformaReference(reference);
@@ -721,48 +803,48 @@ function normalizeInput$2(input) {
721
803
  }
722
804
  function validateTemplate(template) {
723
805
  if (template === void 0 || templates.has(template)) return template;
724
- throw require_get.validationError(`Ismeretlen számlasablon: ${String(template)}. Lehetséges értékek: ${require_create_once.INVOICE_TEMPLATES.join(", ")}.`);
806
+ throw require_reference.validationError(`Ismeretlen számlasablon: ${String(template)}. Lehetséges értékek: ${require_create_once.INVOICE_TEMPLATES.join(", ")}.`);
725
807
  }
726
808
  function buildReverseInvoiceXml(credentials, input) {
727
809
  const options = normalizeInput$2(input);
728
- const invoiceNumber = require_get.requireText(options.invoiceNumber, "Add meg a sztornózandó számla számát (invoiceNumber).");
810
+ const invoiceNumber = require_reference.requireText(options.invoiceNumber, "Add meg a sztornózandó számla számát (invoiceNumber).");
729
811
  const email = options.email;
730
812
  const buyer = options.buyer;
731
813
  return require_serialize.buildXmlDocument({
732
814
  root: "xmlszamlast",
733
815
  namespace: "http://www.szamlazz.hu/xmlszamlast",
734
- schemaLocation: `${require_get.AGENT_XSD_BASE_URL}agentst/xmlszamlast.xsd`,
816
+ schemaLocation: `${require_reference.AGENT_XSD_BASE_URL}agentst/xmlszamlast.xsd`,
735
817
  children: [
736
818
  require_serialize.el("beallitasok", [
737
819
  ...credentials,
738
820
  require_serialize.el("eszamla", options.eInvoice ?? false),
739
821
  require_serialize.el("szamlaLetoltes", options.downloadPdf ?? true),
740
822
  require_serialize.el("valaszVerzio", 2),
741
- require_serialize.optionalEl("szamlaKulsoAzon", require_get.optionalText(options.externalId))
823
+ require_serialize.optionalEl("szamlaKulsoAzon", require_reference.optionalText(options.externalId))
742
824
  ]),
743
825
  require_serialize.el("fejlec", [
744
826
  require_serialize.el("szamlaszam", invoiceNumber),
745
- require_serialize.optionalEl("keltDatum", require_get.optionalAgentDate(options.issueDate, "issueDate")),
746
- require_serialize.optionalEl("teljesitesDatum", require_get.optionalAgentDate(options.fulfillmentDate, "fulfillmentDate")),
747
- require_serialize.optionalEl("megjegyzes", require_get.optionalText(options.comment)),
827
+ require_serialize.optionalEl("keltDatum", require_reference.optionalAgentDate(options.issueDate, "issueDate")),
828
+ require_serialize.optionalEl("teljesitesDatum", require_reference.optionalAgentDate(options.fulfillmentDate, "fulfillmentDate")),
829
+ require_serialize.optionalEl("megjegyzes", require_reference.optionalText(options.comment)),
748
830
  require_serialize.el("tipus", "SS"),
749
831
  require_serialize.optionalEl("szamlaSablon", validateTemplate(options.template))
750
832
  ]),
751
833
  require_serialize.optionalEl("elado", [
752
- require_serialize.optionalEl("emailReplyto", require_get.optionalText(email?.replyTo)),
753
- require_serialize.optionalEl("emailTargy", require_get.optionalText(email?.subject)),
754
- require_serialize.optionalEl("emailSzoveg", require_get.optionalText(email?.text))
834
+ require_serialize.optionalEl("emailReplyto", require_reference.optionalText(email?.replyTo)),
835
+ require_serialize.optionalEl("emailTargy", require_reference.optionalText(email?.subject)),
836
+ require_serialize.optionalEl("emailSzoveg", require_reference.optionalText(email?.text))
755
837
  ]),
756
838
  require_serialize.optionalEl("vevo", [
757
- require_serialize.optionalEl("email", require_get.optionalText(buyer?.email)),
758
- require_serialize.optionalEl("adoszam", require_get.optionalText(buyer?.taxNumber)),
759
- require_serialize.optionalEl("adoszamEU", require_get.optionalText(buyer?.euTaxNumber))
839
+ require_serialize.optionalEl("email", require_reference.optionalText(buyer?.email)),
840
+ require_serialize.optionalEl("adoszam", require_reference.optionalText(buyer?.taxNumber)),
841
+ require_serialize.optionalEl("adoszamEU", require_reference.optionalText(buyer?.euTaxNumber))
760
842
  ])
761
843
  ]
762
844
  });
763
845
  }
764
846
  function parseReverseInvoiceResponse(response, originalInvoiceNumber) {
765
- const result = require_get.parseInvoiceResult(response);
847
+ const result = require_reference.parseInvoiceResult(response);
766
848
  if (result.number === void 0) throw require_parse.unexpectedResponse(response, "A válaszban nincs sztornó számlaszám.");
767
849
  if (originalInvoiceNumber !== void 0 && result.number === originalInvoiceNumber.trim()) throw new require_errors.SzamlazzError(`A(z) ${result.number} bizonylat nem lett sztornózva: a Számlázz.hu az eredeti bizonylatot adta vissza.`, {
768
850
  category: "validation",
@@ -791,10 +873,10 @@ function normalizeInput$1(input) {
791
873
  return typeof input === "string" ? { receiptNumber: input } : input;
792
874
  }
793
875
  function resolveIdentifier(input) {
794
- const receiptNumber = require_issue.optionalText(input.receiptNumber);
795
- const orderNumber = require_issue.optionalText(input.orderNumber);
796
- if (receiptNumber === void 0 && orderNumber === void 0) throw require_issue.receiptValidationError("A nyugta lekérdezéséhez add meg a nyugtaszámot (receiptNumber) vagy a rendelésszámot (orderNumber).");
797
- if (receiptNumber !== void 0 && orderNumber !== void 0) throw require_issue.receiptValidationError("A nyugta lekérdezéséhez a nyugtaszám (receiptNumber) és a rendelésszám (orderNumber) közül csak az egyiket add meg.");
876
+ const receiptNumber = require_create.optionalText(input.receiptNumber);
877
+ const orderNumber = require_create.optionalText(input.orderNumber);
878
+ if (receiptNumber === void 0 && orderNumber === void 0) throw require_create.receiptValidationError("A nyugta lekérdezéséhez add meg a nyugtaszámot (receiptNumber) vagy a rendelésszámot (orderNumber).");
879
+ if (receiptNumber !== void 0 && orderNumber !== void 0) throw require_create.receiptValidationError("A nyugta lekérdezéséhez a nyugtaszám (receiptNumber) és a rendelésszám (orderNumber) közül csak az egyiket add meg.");
798
880
  return {
799
881
  receiptNumber,
800
882
  orderNumber
@@ -806,12 +888,12 @@ function buildGetReceiptXml(credentials, input) {
806
888
  return require_serialize.buildXmlDocument({
807
889
  root: "xmlnyugtaget",
808
890
  namespace: GET_RECEIPT_NAMESPACE,
809
- schemaLocation: `${require_issue.RECEIPT_XSD_BASE_URL}nyugtaget/xmlnyugtaget.xsd`,
891
+ schemaLocation: `${require_create.RECEIPT_XSD_BASE_URL}nyugtaget/xmlnyugtaget.xsd`,
810
892
  children: [require_serialize.el("beallitasok", [...credentials, require_serialize.el("pdfLetoltes", normalized.downloadPdf ?? true)]), require_serialize.el("fejlec", [
811
893
  require_serialize.optionalEl("nyugtaszam", receiptNumber),
812
894
  require_serialize.optionalEl("rendelesSzam", orderNumber),
813
- require_serialize.optionalEl("hivasAzonosito", require_issue.optionalText(normalized.callId)),
814
- require_serialize.optionalEl("pdfSablon", require_issue.validateTemplate(normalized.template))
895
+ require_serialize.optionalEl("hivasAzonosito", require_create.optionalText(normalized.callId)),
896
+ require_serialize.optionalEl("pdfSablon", require_create.validateTemplate(normalized.template))
815
897
  ])]
816
898
  });
817
899
  }
@@ -835,11 +917,11 @@ function buildReverseReceiptXml(credentials, input) {
835
917
  return require_serialize.buildXmlDocument({
836
918
  root: "xmlnyugtast",
837
919
  namespace: REVERSE_RECEIPT_NAMESPACE,
838
- schemaLocation: `${require_issue.RECEIPT_XSD_BASE_URL}nyugtast/xmlnyugtast.xsd`,
920
+ schemaLocation: `${require_create.RECEIPT_XSD_BASE_URL}nyugtast/xmlnyugtast.xsd`,
839
921
  children: [require_serialize.el("beallitasok", [...credentials, require_serialize.el("pdfLetoltes", normalized.downloadPdf ?? true)]), require_serialize.el("fejlec", [
840
- require_serialize.el("nyugtaszam", require_issue.requireReceiptNumber(normalized.receiptNumber)),
841
- require_serialize.optionalEl("pdfSablon", require_issue.validateTemplate(normalized.template)),
842
- require_serialize.optionalEl("hivasAzonosito", require_issue.optionalText(normalized.callId))
922
+ require_serialize.el("nyugtaszam", require_create.requireReceiptNumber(normalized.receiptNumber)),
923
+ require_serialize.optionalEl("pdfSablon", require_create.validateTemplate(normalized.template)),
924
+ require_serialize.optionalEl("hivasAzonosito", require_create.optionalText(normalized.callId))
843
925
  ])]
844
926
  });
845
927
  }
@@ -850,7 +932,7 @@ function reverseReceipt(ctx, input, options = {}) {
850
932
  action: "reverseReceipt",
851
933
  xml,
852
934
  signal: options.signal,
853
- safeToRetry: require_issue.optionalText(normalized.callId) !== void 0
935
+ safeToRetry: require_create.optionalText(normalized.callId) !== void 0
854
936
  }, require_parse.parseReceiptResponse);
855
937
  }
856
938
  //#endregion
@@ -861,30 +943,30 @@ function isValidEmail(value) {
861
943
  return EMAIL_PATTERN.test(value);
862
944
  }
863
945
  function assertEmail(value, field) {
864
- if (!isValidEmail(value)) throw require_issue.receiptValidationError(`Érvénytelen e-mail cím a(z) ${field} mezőben: ${value}`);
946
+ if (!isValidEmail(value)) throw require_create.receiptValidationError(`Érvénytelen e-mail cím a(z) ${field} mezőben: ${value}`);
865
947
  return value;
866
948
  }
867
949
  function normalizeEmails(emails) {
868
950
  const list = (typeof emails === "string" ? [emails] : [...emails]).flatMap((entry) => entry.split(/[,;]/)).map((entry) => entry.trim()).filter((entry) => entry !== "");
869
- if (list.length === 0) throw require_issue.receiptValidationError("Adj meg legalább egy címzett e-mail címet (emails).");
951
+ if (list.length === 0) throw require_create.receiptValidationError("Adj meg legalább egy címzett e-mail címet (emails).");
870
952
  return list.map((email) => assertEmail(email, "emails"));
871
953
  }
872
954
  function buildSendReceiptXml(credentials, input) {
873
- const receiptNumber = require_issue.requireReceiptNumber(input.receiptNumber);
955
+ const receiptNumber = require_create.requireReceiptNumber(input.receiptNumber);
874
956
  const emails = normalizeEmails(input.emails);
875
- const replyTo = require_issue.optionalText(input.replyTo);
957
+ const replyTo = require_create.optionalText(input.replyTo);
876
958
  if (replyTo !== void 0) assertEmail(replyTo, "replyTo");
877
959
  return require_serialize.buildXmlDocument({
878
960
  root: "xmlnyugtasend",
879
961
  namespace: SEND_RECEIPT_NAMESPACE,
880
- schemaLocation: `${require_issue.RECEIPT_XSD_BASE_URL}nyugtasend/xmlnyugtasend.xsd`,
962
+ schemaLocation: `${require_create.RECEIPT_XSD_BASE_URL}nyugtasend/xmlnyugtasend.xsd`,
881
963
  children: [
882
964
  require_serialize.el("beallitasok", credentials),
883
965
  require_serialize.el("fejlec", [require_serialize.el("nyugtaszam", receiptNumber)]),
884
966
  require_serialize.el("emailKuldes", [
885
967
  require_serialize.el("email", emails.join(",")),
886
968
  require_serialize.optionalEl("emailReplyto", replyTo),
887
- require_serialize.el("emailTargy", require_issue.optionalText(input.subject) ?? `Nyugta ${receiptNumber}`),
969
+ require_serialize.el("emailTargy", require_create.optionalText(input.subject) ?? `Nyugta ${receiptNumber}`),
888
970
  require_serialize.optionalEl("emailSzoveg", input.text?.trim() ? input.text : void 0)
889
971
  ])
890
972
  ]
@@ -1087,6 +1169,54 @@ async function queryTaxpayer(ctx, taxNumber, options = {}) {
1087
1169
  }, parseQueryTaxpayerResponse);
1088
1170
  }
1089
1171
  //#endregion
1172
+ //#region src/taxpayer/cache.ts
1173
+ const TAXPAYER_CACHE_KEY_PREFIX = "szamlazz:taxpayer:";
1174
+ const DEFAULT_TAXPAYER_TTL_SECONDS = 86400;
1175
+ const DEFAULT_TAXPAYER_INVALID_TTL_SECONDS = 600;
1176
+ function positiveSeconds(value, fallback, field) {
1177
+ if (value === void 0) return fallback;
1178
+ if (!Number.isFinite(value) || value < 1) throw new require_errors.SzamlazzError(`A(z) ${field} legalább 1 másodperc legyen, kapott: ${String(value)}`, { category: "configuration" });
1179
+ return Math.ceil(value);
1180
+ }
1181
+ function parseCached(value) {
1182
+ if (typeof value !== "string" || value === "") return void 0;
1183
+ try {
1184
+ const parsed = JSON.parse(value);
1185
+ if (typeof parsed !== "object" || parsed === null) return void 0;
1186
+ const info = parsed;
1187
+ return typeof info.valid === "boolean" && Array.isArray(info.addresses) ? info : void 0;
1188
+ } catch {
1189
+ return;
1190
+ }
1191
+ }
1192
+ function cachedTaxpayerQuery(query, options, warn) {
1193
+ const ttl = positiveSeconds(options.ttlSeconds, DEFAULT_TAXPAYER_TTL_SECONDS, "ttlSeconds");
1194
+ const invalidTtl = positiveSeconds(options.invalidTtlSeconds, 600, "invalidTtlSeconds");
1195
+ const report = (operation, error) => warn?.({
1196
+ kind: "cache",
1197
+ message: `Az adószám-gyorsítótár "${operation}" művelete sikertelen, a kassza a Számlázz.hu-tól kérdez.`,
1198
+ error,
1199
+ action: "queryTaxpayer",
1200
+ operation
1201
+ });
1202
+ return async (taxNumber, requestOptions) => {
1203
+ const key = `${TAXPAYER_CACHE_KEY_PREFIX}${toTaxpayerId(taxNumber)}`;
1204
+ try {
1205
+ const cached = parseCached(await options.store.get(key));
1206
+ if (cached) return cached;
1207
+ } catch (error) {
1208
+ report("get", error);
1209
+ }
1210
+ const info = await query(taxNumber, requestOptions);
1211
+ try {
1212
+ await options.store.set(key, JSON.stringify(info), info.valid ? ttl : invalidTtl);
1213
+ } catch (error) {
1214
+ report("set", error);
1215
+ }
1216
+ return info;
1217
+ };
1218
+ }
1219
+ //#endregion
1090
1220
  //#region src/client.ts
1091
1221
  const CREDENTIAL_PROBE_INVOICE_NUMBER = "KASSZA-CREDENTIAL-PROBE-0";
1092
1222
  async function nullIfNotFound(promise) {
@@ -1109,11 +1239,14 @@ function invoiceNumberOf(input) {
1109
1239
  function receiptNumberOf(input) {
1110
1240
  return typeof input === "string" ? input : input.receiptNumber;
1111
1241
  }
1112
- function createInvoicesApi(ctx, defaults, onDocument) {
1242
+ function createDocumentEmitter(hook, options) {
1243
+ return { emit: (event) => require_create_once.emitDocumentEvent(hook, event, options) };
1244
+ }
1245
+ function createInvoicesApi(ctx, defaults, events, guard) {
1113
1246
  const api = {
1114
1247
  async create(input, options) {
1115
1248
  const document = await createInvoice(ctx, defaults, input, options);
1116
- await require_create_once.emitDocumentEvent(onDocument, {
1249
+ await events.emit({
1117
1250
  kind: "invoice",
1118
1251
  action: "created",
1119
1252
  number: document.number,
@@ -1122,11 +1255,11 @@ function createInvoicesApi(ctx, defaults, onDocument) {
1122
1255
  });
1123
1256
  return document;
1124
1257
  },
1125
- createOnce: (input, options) => require_issue.createInvoiceOnce(api, input, options),
1258
+ createOnce: (input, options) => require_issue.createInvoiceOnce(api, input, options, guard),
1126
1259
  preview: (input, options) => previewInvoice(ctx, defaults, input, options),
1127
1260
  async reverse(input, options) {
1128
1261
  const document = await reverseInvoice(ctx, input, options);
1129
- await require_create_once.emitDocumentEvent(onDocument, {
1262
+ await events.emit({
1130
1263
  kind: "invoice",
1131
1264
  action: "reversed",
1132
1265
  number: document.number,
@@ -1136,8 +1269,8 @@ function createInvoicesApi(ctx, defaults, onDocument) {
1136
1269
  return document;
1137
1270
  },
1138
1271
  async registerPayment(input, options) {
1139
- const document = await registerPayment(ctx, input, options);
1140
- await require_create_once.emitDocumentEvent(onDocument, {
1272
+ const document = await require_create_once.registerPayment(ctx, input, options);
1273
+ await events.emit({
1141
1274
  kind: "invoice",
1142
1275
  action: "payment",
1143
1276
  number: document.invoiceNumber,
@@ -1145,9 +1278,10 @@ function createInvoicesApi(ctx, defaults, onDocument) {
1145
1278
  });
1146
1279
  return document;
1147
1280
  },
1281
+ registerPaymentOnce: (input, options) => require_create_once.registerPaymentOnce(api, input, options, guard),
1148
1282
  async clearPayments(input, options) {
1149
- const document = await clearPayments(ctx, input, options);
1150
- await require_create_once.emitDocumentEvent(onDocument, {
1283
+ const document = await require_create_once.clearPayments(ctx, input, options);
1284
+ await events.emit({
1151
1285
  kind: "invoice",
1152
1286
  action: "payment",
1153
1287
  number: document.invoiceNumber,
@@ -1162,11 +1296,20 @@ function createInvoicesApi(ctx, defaults, onDocument) {
1162
1296
  };
1163
1297
  return api;
1164
1298
  }
1165
- function createReceiptsApi(ctx, defaults, invoices, onDocument) {
1299
+ function convertedJoin(result) {
1300
+ return {
1301
+ receipt: result.receipt,
1302
+ invoice: {
1303
+ ...result.invoice,
1304
+ created: false
1305
+ }
1306
+ };
1307
+ }
1308
+ function createReceiptsApi(ctx, defaults, invoices, events, guard) {
1166
1309
  const api = {
1167
1310
  async create(input, options) {
1168
- const document = await require_issue.createReceipt(ctx, defaults, input, options);
1169
- await require_create_once.emitDocumentEvent(onDocument, {
1311
+ const document = await require_create.createReceipt(ctx, defaults, input, options);
1312
+ await events.emit({
1170
1313
  kind: "receipt",
1171
1314
  action: "created",
1172
1315
  number: document.number,
@@ -1174,10 +1317,10 @@ function createReceiptsApi(ctx, defaults, invoices, onDocument) {
1174
1317
  });
1175
1318
  return document;
1176
1319
  },
1177
- createOnce: (input, options) => require_create_once.createReceiptOnce(api, input, options),
1320
+ createOnce: (input, options) => require_create_once.createReceiptOnce(api, input, options, guard),
1178
1321
  async reverse(input, options) {
1179
1322
  const document = await reverseReceipt(ctx, input, options);
1180
- await require_create_once.emitDocumentEvent(onDocument, {
1323
+ await events.emit({
1181
1324
  kind: "receipt",
1182
1325
  action: "reversed",
1183
1326
  number: document.number,
@@ -1189,22 +1332,38 @@ function createReceiptsApi(ctx, defaults, invoices, onDocument) {
1189
1332
  get: (input, options) => getReceipt(ctx, input, options),
1190
1333
  find: (input, options) => nullIfNotFound(getReceipt(ctx, input, options)),
1191
1334
  send: (input, options) => sendReceipt(ctx, input, options),
1192
- convertToInvoice: (input, options) => require_issue.convertReceiptToInvoice({
1335
+ convertToInvoice: (input, options = {}) => require_issue.guardOnce(guard, `convert:${input.receiptNumber?.trim() ?? ""}`, {
1336
+ ...options,
1337
+ lock: false
1338
+ }, () => require_issue.convertReceiptToInvoice({
1193
1339
  receipts: api,
1194
1340
  invoices
1195
- }, input, options)
1341
+ }, input, options), convertedJoin)
1196
1342
  };
1197
1343
  return api;
1198
1344
  }
1345
+ function onceScopeOf(ctx) {
1346
+ return ctx.credentials.map((node) => `${node.name}=${String(node.content)}`).join("\n");
1347
+ }
1199
1348
  function createKassza(options = {}) {
1200
1349
  const ctx = createAgentContext(options);
1201
- const onDocument = options.hooks?.onDocument;
1202
- const invoices = createInvoicesApi(ctx, options.defaults?.invoice ?? {}, onDocument);
1203
- const receipts = createReceiptsApi(ctx, options.defaults?.receipt ?? {}, invoices, onDocument);
1350
+ const events = createDocumentEmitter(options.hooks?.onDocument, {
1351
+ mode: options.hooks?.onDocumentError,
1352
+ onWarning: options.hooks?.onWarning
1353
+ });
1354
+ const guard = {
1355
+ scope: onceScopeOf(ctx),
1356
+ lock: options.createOnceLock,
1357
+ warn: ctx.warn
1358
+ };
1359
+ const directQuery = (taxNumber, requestOptions) => queryTaxpayer(ctx, taxNumber, requestOptions);
1360
+ const taxpayerQuery = options.taxpayerCache ? cachedTaxpayerQuery(directQuery, options.taxpayerCache, ctx.warn) : directQuery;
1361
+ const invoices = createInvoicesApi(ctx, options.defaults?.invoice ?? {}, events, guard);
1362
+ const receipts = createReceiptsApi(ctx, options.defaults?.receipt ?? {}, invoices, events, guard);
1204
1363
  return {
1205
1364
  invoices,
1206
1365
  receipts,
1207
- taxpayer: { query: (taxNumber, requestOptions) => queryTaxpayer(ctx, taxNumber, requestOptions) },
1366
+ taxpayer: { query: taxpayerQuery },
1208
1367
  async verifyCredentials(requestOptions) {
1209
1368
  try {
1210
1369
  await getInvoicePdf(ctx, CREDENTIAL_PROBE_INVOICE_NUMBER, requestOptions);
@@ -1237,6 +1396,24 @@ Object.defineProperty(exports, "ATTEMPT_LEDGER_TTL_SECONDS", {
1237
1396
  return ATTEMPT_LEDGER_TTL_SECONDS;
1238
1397
  }
1239
1398
  });
1399
+ Object.defineProperty(exports, "DEFAULT_TAXPAYER_INVALID_TTL_SECONDS", {
1400
+ enumerable: true,
1401
+ get: function() {
1402
+ return DEFAULT_TAXPAYER_INVALID_TTL_SECONDS;
1403
+ }
1404
+ });
1405
+ Object.defineProperty(exports, "DEFAULT_TAXPAYER_TTL_SECONDS", {
1406
+ enumerable: true,
1407
+ get: function() {
1408
+ return DEFAULT_TAXPAYER_TTL_SECONDS;
1409
+ }
1410
+ });
1411
+ Object.defineProperty(exports, "TAXPAYER_CACHE_KEY_PREFIX", {
1412
+ enumerable: true,
1413
+ get: function() {
1414
+ return TAXPAYER_CACHE_KEY_PREFIX;
1415
+ }
1416
+ });
1240
1417
  Object.defineProperty(exports, "attemptLedgerKey", {
1241
1418
  enumerable: true,
1242
1419
  get: function() {