facturas 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createPadronService
3
- } from "./chunk-NUV5RZPZ.mjs";
3
+ } from "./chunk-7LEKQ4P7.mjs";
4
4
  import {
5
5
  buildFacturaB,
6
6
  buildFacturaC,
@@ -10,7 +10,7 @@ import {
10
10
  normalizeWsfeDateInput,
11
11
  normalizeWsfeVoucherInput,
12
12
  serializeArcaExchangeRate
13
- } from "./chunk-TOVSOJ3G.mjs";
13
+ } from "./chunk-QHHLHSBW.mjs";
14
14
  import {
15
15
  ARCA_CURRENCY_IDS,
16
16
  ARCA_DOCUMENT_TYPES,
@@ -22,8 +22,8 @@ import {
22
22
  } from "./chunk-76WU5BVI.mjs";
23
23
  import {
24
24
  createWsmtxcaService
25
- } from "./chunk-ZX4OOCML.mjs";
26
- import "./chunk-PPYGVNFA.mjs";
25
+ } from "./chunk-ALH5IPSY.mjs";
26
+ import "./chunk-UDX6HLAU.mjs";
27
27
  import {
28
28
  ArcaAuthenticationError,
29
29
  ArcaConfigurationError,
@@ -37,7 +37,7 @@ import {
37
37
  createSafeErrorDiagnostic,
38
38
  isArcaAuthenticationError,
39
39
  toArcaSafeErrorMetadata
40
- } from "./chunk-HUT3PFKF.mjs";
40
+ } from "./chunk-ZOSNASXB.mjs";
41
41
 
42
42
  // src/config.ts
43
43
  var ARCA_ENVIRONMENTS = ["production", "test"];
@@ -88,17 +88,7 @@ function assertArcaClientConfig(config) {
88
88
  "Encrypted private keys are not supported. Provide an unencrypted PKCS#8 or RSA private key PEM."
89
89
  );
90
90
  }
91
- if (!/^\d{11}$/.test(normalized.taxId)) {
92
- invalidFields.push("taxId");
93
- }
94
- if (!normalized.certificatePem.startsWith("-----BEGIN CERTIFICATE-----")) {
95
- invalidFields.push("certificatePem");
96
- }
97
- if (!PRIVATE_KEY_PEM_PREFIXES.some(
98
- (prefix) => normalized.privateKeyPem.startsWith(prefix)
99
- )) {
100
- invalidFields.push("privateKeyPem");
101
- }
91
+ invalidFields.push(...invalidCredentialFields(normalized));
102
92
  if (!ARCA_ENVIRONMENTS.includes(normalized.environment)) {
103
93
  invalidFields.push("environment");
104
94
  }
@@ -210,10 +200,11 @@ function normalizeArcaClientConfig(config) {
210
200
  const normalizedEnvironment = normalizeEnvironmentValue(String(config.environment)) ?? config.environment;
211
201
  const normalizedLoggerLevel = normalizeLogLevelValue(config.logger?.level);
212
202
  return {
213
- taxId: config.taxId.trim(),
214
- certificatePem: config.certificatePem.trim(),
215
- privateKeyPem: config.privateKeyPem.trim(),
216
- environment: normalizedEnvironment,
203
+ taxId: config.taxId?.trim() ?? "",
204
+ certificatePem: config.certificatePem?.trim() ?? "",
205
+ privateKeyPem: config.privateKeyPem?.trim() ?? "",
206
+ environment: normalizedEnvironment ?? "test",
207
+ ...config.store === void 0 ? {} : { store: config.store },
217
208
  timeout: config.timeout ?? DEFAULT_ARCA_TIMEOUT_MS,
218
209
  retries: config.retries ?? DEFAULT_ARCA_RETRIES,
219
210
  retryDelay: config.retryDelay ?? DEFAULT_ARCA_RETRY_DELAY_MS,
@@ -249,6 +240,40 @@ function normalizeLogLevelValue(value) {
249
240
  }
250
241
  return value;
251
242
  }
243
+ function discoverArcaClientConfig(config) {
244
+ const environment = config.environment ?? readEnv(process.env, ARCA_ENV_VARIABLES.environment);
245
+ if (environment === void 0) {
246
+ throw new ArcaConfigurationError(
247
+ `environment is required: pass environment or set ${ARCA_ENV_VARIABLES.environment} to "test" or "production".`
248
+ );
249
+ }
250
+ return {
251
+ ...config,
252
+ taxId: config.taxId ?? readEnv(process.env, ARCA_ENV_VARIABLES.taxId) ?? "",
253
+ certificatePem: config.certificatePem ?? readEnv(process.env, ARCA_ENV_VARIABLES.certificatePem) ?? "",
254
+ privateKeyPem: config.privateKeyPem ?? readEnv(process.env, ARCA_ENV_VARIABLES.privateKeyPem) ?? "",
255
+ environment
256
+ };
257
+ }
258
+ function invalidCredentialFields(normalized) {
259
+ const invalidFields = [];
260
+ if (!/^\d{11}$/.test(normalized.taxId)) {
261
+ invalidFields.push(normalized.taxId ? "taxId" : "taxId (ARCA_TAX_ID)");
262
+ }
263
+ if (!normalized.certificatePem.startsWith("-----BEGIN CERTIFICATE-----")) {
264
+ invalidFields.push(
265
+ normalized.certificatePem ? "certificatePem" : "certificatePem (ARCA_CERTIFICATE_PEM)"
266
+ );
267
+ }
268
+ if (!PRIVATE_KEY_PEM_PREFIXES.some(
269
+ (prefix) => normalized.privateKeyPem.startsWith(prefix)
270
+ )) {
271
+ invalidFields.push(
272
+ normalized.privateKeyPem ? "privateKeyPem" : "privateKeyPem (ARCA_PRIVATE_KEY_PEM)"
273
+ );
274
+ }
275
+ return invalidFields;
276
+ }
252
277
 
253
278
  // src/internal/logger.ts
254
279
  var ARCA_LOG_LEVELS = ["debug", "info", "warn", "error"];
@@ -301,6 +326,36 @@ function defaultArcaLog(level, message, ...args) {
301
326
  method(message, ...args);
302
327
  }
303
328
 
329
+ // src/store/types.ts
330
+ import { createHash } from "crypto";
331
+ function attemptKey(environment, taxId, key) {
332
+ return `arca:v1:attempt:${environment}:${taxId}:${key}`;
333
+ }
334
+ function canonicalHash(input) {
335
+ return createHash("sha256").update(JSON.stringify(canonical(input))).digest("hex");
336
+ }
337
+ function canonical(value) {
338
+ if (Array.isArray(value)) {
339
+ return value.map(canonical);
340
+ }
341
+ if (value instanceof Date) {
342
+ return value.toJSON();
343
+ }
344
+ if (value !== null && typeof value === "object") {
345
+ return Object.fromEntries(
346
+ Object.entries(value).filter(([, item]) => item !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, item]) => [key, canonical(item)])
347
+ );
348
+ }
349
+ return value;
350
+ }
351
+ async function storeCall(fn) {
352
+ try {
353
+ return await fn();
354
+ } catch (cause) {
355
+ throw new ArcaConfigurationError("ARCA store operation failed.", { cause });
356
+ }
357
+ }
358
+
304
359
  // src/services/wsfe-derive.ts
305
360
  var INVOICE_TYPES = {
306
361
  A: ARCA_VOUCHER_TYPES.FACTURA_A,
@@ -324,18 +379,15 @@ function deriveWsfeInvoice(input, now = /* @__PURE__ */ new Date()) {
324
379
  ],
325
380
  "input"
326
381
  );
327
- if (typeof input.issuer !== "string" || !Object.hasOwn(ARCA_ISSUER_CONDITION_IDS, input.issuer)) {
328
- invalid(
329
- "issuer",
330
- "responsable_inscripto, monotributo, exento, or no_alcanzado"
331
- );
332
- }
333
- if (!Number.isSafeInteger(input.salesPoint) || input.salesPoint < 1 || input.salesPoint > 99999) {
334
- invalid("salesPoint", "an integer from 1 through 99999");
335
- }
382
+ assertIssuerCondition(input.issuer);
383
+ assertSalesPoint(input.salesPoint);
336
384
  const receiver = deriveReceiver(input.to);
337
- const voucherClass = ARCA_INVOICE_CLASS_BY_ISSUER[input.issuer][input.to.condition];
338
- const { data: amountsData, amounts } = calculateWsfeAmounts(input);
385
+ const voucherClass = resolveInvoiceClass(input.issuer, input.to.condition);
386
+ const { data: amountsData, amounts } = calculateWsfeAmounts({
387
+ voucherClass,
388
+ items: input.items,
389
+ total: input.total
390
+ });
339
391
  const currency = deriveCurrency(input);
340
392
  if (input.to.condition === "consumidor_final" && receiver.documentType === ARCA_DOCUMENT_TYPES.CONSUMIDOR_FINAL) {
341
393
  const [whole, fraction = ""] = currency.exchangeRate.split(".");
@@ -375,6 +427,22 @@ function deriveWsfeInvoice(input, now = /* @__PURE__ */ new Date()) {
375
427
  }
376
428
  return { data, voucherClass, amounts };
377
429
  }
430
+ function assertIssuerCondition(issuer) {
431
+ if (typeof issuer !== "string" || !Object.hasOwn(ARCA_ISSUER_CONDITION_IDS, issuer)) {
432
+ invalid(
433
+ "issuer",
434
+ "responsable_inscripto, monotributo, exento, or no_alcanzado"
435
+ );
436
+ }
437
+ }
438
+ function assertSalesPoint(salesPoint) {
439
+ if (!Number.isSafeInteger(salesPoint) || salesPoint < 1 || salesPoint > 99999) {
440
+ invalid("salesPoint", "an integer from 1 through 99999");
441
+ }
442
+ }
443
+ function resolveInvoiceClass(issuer, condition) {
444
+ return ARCA_INVOICE_CLASS_BY_ISSUER[issuer][condition];
445
+ }
378
446
  function deriveReceiver(to) {
379
447
  assertIssueObject(to, "to");
380
448
  assertIssueKeys(to, ["condition", "cuit", "dni"], "to");
@@ -489,18 +557,15 @@ function assertIssueObject(value, field) {
489
557
  invalid(field, "an object");
490
558
  }
491
559
  }
492
- function assertIssueKeys(value, keys, prefix) {
560
+ function assertIssueKeys(value, keys, prefix, method = "issue()") {
493
561
  for (const key of Object.keys(value)) {
494
562
  if (!keys.includes(key)) {
495
563
  const field = prefix === "input" ? key : `${prefix}.${key}`;
496
- throw new ArcaInputError(
497
- `${field} is not supported by vouchers.issue().`,
498
- {
499
- code: "ARCA_INPUT_RESERVED_FIELD",
500
- field,
501
- expected: "a supported facade field; use the exact API for other fiscal fields"
502
- }
503
- );
564
+ throw new ArcaInputError(`${field} is not supported by ${method}.`, {
565
+ code: "ARCA_INPUT_RESERVED_FIELD",
566
+ field,
567
+ expected: "a supported facade field; use the exact API for other fiscal fields"
568
+ });
504
569
  }
505
570
  }
506
571
  }
@@ -512,6 +577,286 @@ function invalid(field, expected) {
512
577
  });
513
578
  }
514
579
 
580
+ // src/services/wsfe-credit-note.ts
581
+ var NOTES = {
582
+ 1: { voucherType: 3, voucherClass: "A" },
583
+ 6: { voucherType: 8, voucherClass: "B" },
584
+ 11: { voucherType: 13, voucherClass: "C" }
585
+ };
586
+ function invalid2(reason) {
587
+ throw new ArcaInputError(
588
+ `issueCreditNote cannot proceed: ${reason}. Use wsfe.issue() for exact control.`,
589
+ { code: "ARCA_INPUT_INVALID_VALUE" }
590
+ );
591
+ }
592
+ function required(value, field) {
593
+ if (value === void 0 || value === null) {
594
+ invalid2(`original is missing ${field}`);
595
+ }
596
+ return value;
597
+ }
598
+ function deriveWsfeFullCreditNote(original, input, now = /* @__PURE__ */ new Date()) {
599
+ const { note, header } = prepareCreditNote(original, input, now);
600
+ const data = {
601
+ ...header,
602
+ totalAmount: required(original.totalAmount, "totalAmount"),
603
+ netAmount: required(original.netAmount, "netAmount"),
604
+ vatAmount: required(original.vatAmount, "vatAmount"),
605
+ exemptAmount: required(original.exemptAmount, "exemptAmount"),
606
+ nonTaxableAmount: required(original.nonTaxableAmount, "nonTaxableAmount"),
607
+ taxAmount: required(original.taxAmount, "taxAmount"),
608
+ ...original.vatRates === void 0 ? {} : { vatRates: original.vatRates.map((rate) => ({ ...rate })) }
609
+ };
610
+ normalizeWsfeVoucherInput(data);
611
+ const total = Number(
612
+ normalizeArcaAmountToMinorUnits(data.totalAmount, "totalAmount")
613
+ );
614
+ return {
615
+ data,
616
+ voucherClass: note.voucherClass,
617
+ amounts: { computedTotal: total, sentTotal: total, vatAdjustment: 0 }
618
+ };
619
+ }
620
+ function deriveWsfePartialCreditNote(original, input, now = /* @__PURE__ */ new Date()) {
621
+ if (input.items === void 0) {
622
+ invalid2("items is required to credit chosen lines");
623
+ }
624
+ const { note, header } = prepareCreditNote(original, input, now);
625
+ const { data: amountsData, amounts } = calculateWsfeAmounts({
626
+ voucherClass: note.voucherClass,
627
+ items: input.items,
628
+ total: input.total
629
+ });
630
+ const originalTotal = normalizeArcaAmountToMinorUnits(
631
+ required(original.totalAmount, "totalAmount"),
632
+ "totalAmount"
633
+ );
634
+ if (BigInt(amounts.sentTotal) > originalTotal) {
635
+ invalid2(
636
+ "the note total is greater than the original; the SDK does not track earlier notes against an original"
637
+ );
638
+ }
639
+ const data = { ...header, ...amountsData };
640
+ normalizeWsfeVoucherInput(data);
641
+ return { data, voucherClass: note.voucherClass, amounts };
642
+ }
643
+ function prepareCreditNote(original, input, now) {
644
+ const note = NOTES[original.voucherType ?? 0];
645
+ if (!note) {
646
+ invalid2("original must be an invoice of type 1, 6 or 11");
647
+ }
648
+ if (!(["A", "O"].includes(original.result ?? "") && original.cae?.trim() && original.caeExpiry?.trim())) {
649
+ invalid2("original is not authorized");
650
+ }
651
+ rejectExtensions(original);
652
+ if (original.currencyId !== "PES" && original.currencyId !== "DOL") {
653
+ invalid2("unsupported currency; only ARS and USD are supported");
654
+ }
655
+ const voucherDate = normalizeWsfeDateInput(
656
+ input.date ?? buenosAiresDate(now),
657
+ "date"
658
+ );
659
+ const originalDate = normalizeWsfeDateInput(
660
+ required(original.voucherDate, "voucherDate"),
661
+ "original.voucherDate"
662
+ );
663
+ if (originalDate > voucherDate && originalDate.slice(0, 6) !== voucherDate.slice(0, 6)) {
664
+ invalid2(
665
+ "original date is later than the note and outside its month (10210)"
666
+ );
667
+ }
668
+ const header = {
669
+ salesPoint: input.salesPoint ?? required(original.salesPoint, "salesPoint"),
670
+ voucherType: note.voucherType,
671
+ concept: required(original.concept, "concept"),
672
+ documentType: required(original.documentType, "documentType"),
673
+ documentNumber: Number(required(original.documentNumber, "documentNumber")),
674
+ receiverVatConditionId: required(
675
+ original.receiverVatConditionId,
676
+ "receiverVatConditionId"
677
+ ),
678
+ currencyId: original.currencyId,
679
+ exchangeRate: required(original.exchangeRate, "exchangeRate"),
680
+ voucherDate,
681
+ associatedVouchers: [
682
+ {
683
+ type: required(original.voucherType, "voucherType"),
684
+ salesPoint: required(original.salesPoint, "salesPoint"),
685
+ number: original.voucherNumber,
686
+ voucherDate: originalDate
687
+ }
688
+ ]
689
+ };
690
+ copyServiceDates(original, header);
691
+ return { note, header };
692
+ }
693
+ function rejectExtensions(original) {
694
+ for (const [field, rawField] of [
695
+ ["taxes", "Tributos"],
696
+ ["optionalFields", "Opcionales"],
697
+ ["buyers", "Compradores"],
698
+ ["activities", "Actividades"],
699
+ ["associatedPeriod", "PeriodoAsoc"]
700
+ ]) {
701
+ const value = original[field];
702
+ const raw = original.raw[rawField];
703
+ if (present(value) || present(raw)) {
704
+ invalid2(`unsupported original ${field}`);
705
+ }
706
+ }
707
+ if ((original.taxAmount ?? 0) !== 0) {
708
+ invalid2("unsupported original taxes");
709
+ }
710
+ }
711
+ function present(value) {
712
+ return value !== void 0 && value !== null && value !== "" && (!Array.isArray(value) || value.length > 0);
713
+ }
714
+ function copyServiceDates(original, header) {
715
+ if (header.concept !== 2 && header.concept !== 3) {
716
+ return;
717
+ }
718
+ header.serviceStartDate = required(
719
+ original.serviceStartDate,
720
+ "serviceStartDate"
721
+ );
722
+ header.serviceEndDate = required(
723
+ original.serviceEndDate,
724
+ "serviceEndDate"
725
+ );
726
+ const due = normalizeWsfeDateInput(
727
+ required(original.paymentDueDate, "paymentDueDate"),
728
+ "original.paymentDueDate"
729
+ );
730
+ header.paymentDueDate = due < header.voucherDate ? header.voucherDate : due;
731
+ }
732
+ var CREDIT_NOTE_KEYS = ["for", "salesPoint", "date", "items", "total", "all"];
733
+ var TARGET_BOUNDS = [
734
+ ["salesPoint", 99999],
735
+ ["voucherType", 999],
736
+ ["number", 99999999]
737
+ ];
738
+ function assertCreditNoteInput(input) {
739
+ assertIssueObject(input, "input");
740
+ if ("associatedPeriod" in input) {
741
+ throw new ArcaInputError(
742
+ "issueCreditNote does not support associatedPeriod; a note against a period is exact-layer work. Use wsfe.issue() for exact control.",
743
+ {
744
+ code: "ARCA_INPUT_RESERVED_FIELD",
745
+ field: "associatedPeriod",
746
+ expected: "an associated invoice through for"
747
+ }
748
+ );
749
+ }
750
+ assertIssueKeys(input, CREDIT_NOTE_KEYS, "input", "issueCreditNote()");
751
+ const target = assertCreditNoteTarget(input.for);
752
+ if (input.salesPoint !== void 0) {
753
+ assertCreditNoteBound(input.salesPoint, 99999, "salesPoint");
754
+ }
755
+ const date = input.date === void 0 ? void 0 : normalizeWsfeDateInput(input.date, "date");
756
+ const common = {
757
+ for: target,
758
+ ...input.salesPoint === void 0 ? {} : { salesPoint: input.salesPoint },
759
+ ...date === void 0 ? {} : { date }
760
+ };
761
+ if (input.items === void 0 === (input.all === void 0)) {
762
+ throw new ArcaInputError(
763
+ "issueCreditNote needs exactly one mode: items for the credited lines, or all: true for the whole original.",
764
+ {
765
+ code: "ARCA_INPUT_INVALID_VALUE",
766
+ field: input.items === void 0 ? "input.items" : "input.all",
767
+ expected: "either items or all: true, never both and never neither"
768
+ }
769
+ );
770
+ }
771
+ if (input.all !== void 0) {
772
+ if (input.all !== true) {
773
+ throw new ArcaInputError(
774
+ "issueCreditNote accepts only all: true; pass items to credit chosen lines.",
775
+ {
776
+ code: "ARCA_INPUT_INVALID_VALUE",
777
+ field: "input.all",
778
+ expected: "the literal true"
779
+ }
780
+ );
781
+ }
782
+ if (input.total !== void 0) {
783
+ throw new ArcaInputError(
784
+ "issueCreditNote takes total only with items; all: true credits the original's own total.",
785
+ {
786
+ code: "ARCA_INPUT_INVALID_VALUE",
787
+ field: "input.total",
788
+ expected: "no total when all is true"
789
+ }
790
+ );
791
+ }
792
+ return { ...common, all: true };
793
+ }
794
+ return {
795
+ ...common,
796
+ items: copyCreditNoteItems(input.items),
797
+ ...input.total === void 0 ? {} : { total: input.total }
798
+ };
799
+ }
800
+ function assertCreditNoteTarget(value) {
801
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
802
+ throw new ArcaInputError(
803
+ "issueCreditNote requires for: the coordinates of the authorized invoice the note corrects.",
804
+ {
805
+ code: "ARCA_INPUT_MISSING_FIELD",
806
+ field: "input.for",
807
+ expected: "{ salesPoint, voucherType, number }"
808
+ }
809
+ );
810
+ }
811
+ assertIssueKeys(
812
+ value,
813
+ ["salesPoint", "voucherType", "number"],
814
+ "input.for",
815
+ "issueCreditNote()"
816
+ );
817
+ for (const [field, max] of TARGET_BOUNDS) {
818
+ assertCreditNoteBound(value[field], max, `for.${field}`);
819
+ }
820
+ if (![1, 6, 11].includes(value.voucherType)) {
821
+ throw new ArcaInputError(
822
+ "issueCreditNote requires an authorized invoice of type 1, 6 or 11 in for.voucherType. Use wsfe.issue() for exact control.",
823
+ {
824
+ code: "ARCA_INPUT_INVALID_VALUE",
825
+ field: "input.for.voucherType",
826
+ expected: "1, 6 or 11"
827
+ }
828
+ );
829
+ }
830
+ return {
831
+ salesPoint: value.salesPoint,
832
+ voucherType: value.voucherType,
833
+ number: value.number
834
+ };
835
+ }
836
+ function assertCreditNoteBound(value, max, path) {
837
+ if (!Number.isSafeInteger(value) || value < 1 || value > max) {
838
+ throw new ArcaInputError(
839
+ `issueCreditNote requires input.${path} to be an integer from 1 through ${max}.`,
840
+ { code: "ARCA_INPUT_INVALID_VALUE", field: `input.${path}` }
841
+ );
842
+ }
843
+ }
844
+ function copyCreditNoteItems(items) {
845
+ if (!Array.isArray(items)) {
846
+ throw new ArcaInputError(
847
+ "issueCreditNote requires items to be a non-empty array of items.",
848
+ {
849
+ code: "ARCA_INPUT_INVALID_VALUE",
850
+ field: "input.items",
851
+ expected: "a non-empty array of items"
852
+ }
853
+ );
854
+ }
855
+ return items.map(
856
+ (item) => item === null || typeof item !== "object" ? item : { ...item }
857
+ );
858
+ }
859
+
515
860
  // src/services/wsfe-identity.ts
516
861
  function matchWsfeVoucherIdentity(sent, number, found) {
517
862
  let missing;
@@ -581,7 +926,7 @@ function matchWsfeVoucherIdentity(sent, number, found) {
581
926
  (value) => normalizeArcaAmountToMinorUnits(value, field)
582
927
  ]);
583
928
  }
584
- if (sent.concept === 2) {
929
+ if (sent.concept === 2 || sent.concept === 3) {
585
930
  for (const field of [
586
931
  "serviceStartDate",
587
932
  "serviceEndDate",
@@ -608,6 +953,17 @@ function matchWsfeVoucherIdentity(sent, number, found) {
608
953
  }
609
954
  missing ??= rates.reason;
610
955
  }
956
+ if (sent.associatedVouchers?.length) {
957
+ if (!found.associatedVouchers) {
958
+ missing ??= "associatedVouchers";
959
+ } else if (associationIdentity(sent.associatedVouchers) !== associationIdentity(found.associatedVouchers)) {
960
+ return {
961
+ matches: false,
962
+ evidence: "conflict",
963
+ reason: "associatedVouchers differ from the sent input"
964
+ };
965
+ }
966
+ }
611
967
  missing ??= incompleteAuthorization(sent, found);
612
968
  return missing ? {
613
969
  matches: false,
@@ -617,12 +973,11 @@ function matchWsfeVoucherIdentity(sent, number, found) {
617
973
  }
618
974
  function incompleteAuthorization(sent, found) {
619
975
  let missing;
620
- if (sent.concept !== 1 && sent.concept !== 2) {
976
+ if (sent.concept !== 1 && sent.concept !== 2 && sent.concept !== 3) {
621
977
  missing ??= "unsupported concept";
622
978
  }
623
979
  for (const field of [
624
980
  "taxes",
625
- "associatedVouchers",
626
981
  "associatedPeriod",
627
982
  "optionalFields",
628
983
  "buyers",
@@ -738,55 +1093,201 @@ function toVoucherSummary(found) {
738
1093
  }
739
1094
  return summary;
740
1095
  }
1096
+ function associationIdentity(vouchers) {
1097
+ return JSON.stringify(
1098
+ vouchers.map(({ type, salesPoint, number }) => `${type}:${salesPoint}:${number}`).sort()
1099
+ );
1100
+ }
741
1101
 
742
1102
  // src/services/vouchers.ts
743
- var SERIALIZATION_NOTICE = "Serialize calls per (representedTaxId, salesPoint, voucherType). The SDK does not coordinate writers; uncoordinated calls collide on 10016. Servers and queues must persist attempts and use wsfe.authorizeVoucherOutcome().";
744
- function createVouchersService(wsfe) {
1103
+ function createVouchersService(wsfe, context) {
745
1104
  return {
1105
+ issueCreditNote: async (input, options) => {
1106
+ const result = await issueCreditNote(
1107
+ wsfe,
1108
+ input,
1109
+ options === void 0 ? {} : options,
1110
+ context
1111
+ );
1112
+ return result;
1113
+ },
746
1114
  issue: async (input, options) => {
747
1115
  const result = await issueInvoice(
748
1116
  wsfe,
749
1117
  input,
750
- options === void 0 ? {} : options
1118
+ options === void 0 ? {} : options,
1119
+ context
751
1120
  );
752
1121
  return result;
753
- }
1122
+ },
1123
+ preview: (input, options) => previewInvoice(input, options === void 0 ? {} : options)
754
1124
  };
755
1125
  }
756
- async function issueInvoice(wsfe, input, options) {
757
- validateOptions(options);
1126
+ function previewInvoice(input, options) {
1127
+ assertIssueObject(options, "options");
1128
+ assertIssueKeys(options, ["representedTaxId"], "options");
1129
+ if (options.representedTaxId !== void 0) {
1130
+ issueDocumentNumber(
1131
+ options.representedTaxId,
1132
+ "options.representedTaxId",
1133
+ 11,
1134
+ 11
1135
+ );
1136
+ }
758
1137
  const { data, voucherClass, amounts } = deriveWsfeInvoice(input);
759
- const auth = {
760
- representedTaxId: options.representedTaxId,
761
- forceRefresh: options.forceRefresh
1138
+ return {
1139
+ voucherClass,
1140
+ voucherType: data.voucherType,
1141
+ amounts,
1142
+ request: data
762
1143
  };
763
- const includeRaw = options.include?.raw === true;
764
- const includeExact = options.include?.exactInput === true;
1144
+ }
1145
+ async function issueInvoice(wsfe, input, options, context) {
1146
+ validateOptions(options);
1147
+ validateKeyStore(options, context);
1148
+ const prepared = deriveWsfeInvoice(input);
1149
+ return await runOperation(
1150
+ wsfe,
1151
+ "issue",
1152
+ input,
1153
+ () => Promise.resolve(prepared),
1154
+ options,
1155
+ context,
1156
+ prepared.amounts
1157
+ );
1158
+ }
1159
+ async function runOperation(wsfe, operation, input, prepare, options, context, replayAmounts) {
1160
+ const store = context?.store;
1161
+ if (options.idempotencyKey === void 0 || !store || !context) {
1162
+ return runAuthorization(wsfe, await prepare(), options);
1163
+ }
1164
+ const key = attemptKey(
1165
+ context.environment,
1166
+ context.taxId,
1167
+ options.idempotencyKey
1168
+ );
1169
+ const representedTaxId = options.representedTaxId === void 0 ? void 0 : String(options.representedTaxId);
1170
+ const inputHash = canonicalHash({ input, representedTaxId });
1171
+ const existing = await storeCall(() => store.get(key));
1172
+ if (existing !== null) {
1173
+ return replay(existing);
1174
+ }
1175
+ const prepared = await prepare();
1176
+ const number = await nextNumber(wsfe, prepared.data, options);
1177
+ const record = {
1178
+ v: 1,
1179
+ operation,
1180
+ representedTaxId,
1181
+ inputHash,
1182
+ number,
1183
+ salesPoint: prepared.data.salesPoint,
1184
+ voucherType: prepared.data.voucherType,
1185
+ sent: prepared.data,
1186
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1187
+ };
1188
+ if (await storeCall(() => store.add(key, JSON.stringify(record)))) {
1189
+ return runAuthorization(wsfe, prepared, options, number);
1190
+ }
1191
+ const winner = await storeCall(() => store.get(key));
1192
+ if (winner === null) {
1193
+ throw new ArcaConfigurationError(
1194
+ "ARCA reservation disappeared after atomic creation lost."
1195
+ );
1196
+ }
1197
+ return replay(winner);
1198
+ function replay(json) {
1199
+ const stored = readRecord(json);
1200
+ if (stored.operation !== operation || stored.inputHash !== inputHash || stored.representedTaxId !== representedTaxId) {
1201
+ throw new ArcaInputError(
1202
+ "The idempotency key was already used with different input or operation.",
1203
+ {
1204
+ code: "ARCA_INPUT_IDEMPOTENCY_MISMATCH",
1205
+ field: "options.idempotencyKey"
1206
+ }
1207
+ );
1208
+ }
1209
+ return runAuthorization(
1210
+ wsfe,
1211
+ {
1212
+ ...preparedFromRecord(stored),
1213
+ ...replayAmounts ? { amounts: replayAmounts } : {}
1214
+ },
1215
+ options,
1216
+ stored.number,
1217
+ true
1218
+ );
1219
+ }
1220
+ }
1221
+ function validateKeyStore(options, context) {
1222
+ if (options.idempotencyKey === void 0) {
1223
+ return;
1224
+ }
1225
+ if (typeof options.idempotencyKey !== "string" || options.idempotencyKey.length < 1 || options.idempotencyKey.length > 255) {
1226
+ throw new ArcaInputError(
1227
+ "idempotencyKey must contain 1 to 255 characters.",
1228
+ { code: "ARCA_INPUT_INVALID_VALUE", field: "options.idempotencyKey" }
1229
+ );
1230
+ }
1231
+ if (!context?.store) {
1232
+ throw new ArcaConfigurationError(
1233
+ 'idempotencyKey requires a store. Add import { createPostgresStore } from "facturas"; and store: createPostgresStore({ query }) to createArcaClient().'
1234
+ );
1235
+ }
1236
+ }
1237
+ function readRecord(json) {
1238
+ try {
1239
+ const record = JSON.parse(json);
1240
+ if (!record || record.v !== 1 || !["issue", "creditNote"].includes(record.operation) || typeof record.inputHash !== "string" || !record.sent || !Number.isSafeInteger(record.number) || record.number < 1 || record.number > 99999999 || record.sent.salesPoint !== record.salesPoint || record.sent.voucherType !== record.voucherType) {
1241
+ throw new Error("Invalid reservation structure");
1242
+ }
1243
+ normalizeWsfeVoucherInput(record.sent);
1244
+ return record;
1245
+ } catch (cause) {
1246
+ throw new ArcaConfigurationError(
1247
+ "Invalid ARCA reservation record; preserve it for reconciliation.",
1248
+ { cause }
1249
+ );
1250
+ }
1251
+ }
1252
+ function preparedFromRecord(record) {
1253
+ const sentTotal = Number(
1254
+ normalizeArcaAmountToMinorUnits(record.sent.totalAmount, "totalAmount")
1255
+ );
1256
+ return {
1257
+ data: record.sent,
1258
+ voucherClass: [1, 3].includes(record.voucherType) ? "A" : [6, 8].includes(record.voucherType) ? "B" : "C",
1259
+ amounts: { computedTotal: sentTotal, sentTotal, vatAdjustment: 0 }
1260
+ };
1261
+ }
1262
+ async function nextNumber(wsfe, data, options) {
765
1263
  const number = await wsfe.getNextVoucherNumber({
766
- ...auth,
1264
+ representedTaxId: options.representedTaxId,
1265
+ forceRefresh: options.forceRefresh,
767
1266
  salesPoint: data.salesPoint,
768
1267
  voucherType: data.voucherType
769
1268
  });
770
1269
  if (!Number.isSafeInteger(number) || number < 1 || number > 99999999) {
771
1270
  throw new ArcaServiceError(
772
1271
  "WSFE returned an invalid next voucher number.",
773
- {
774
- service: "wsfe",
775
- operation: "FECompUltimoAutorizado"
776
- }
1272
+ { service: "wsfe", operation: "FECompUltimoAutorizado" }
777
1273
  );
778
1274
  }
1275
+ return number;
1276
+ }
1277
+ async function runAuthorization(wsfe, { data, voucherClass, amounts }, options, reservedNumber, replay = false) {
1278
+ const auth = {
1279
+ representedTaxId: options.representedTaxId,
1280
+ forceRefresh: options.forceRefresh
1281
+ };
1282
+ const includeRaw = options.include?.raw === true;
1283
+ const includeExact = options.include?.exactInput === true;
1284
+ const number = reservedNumber ?? await nextNumber(wsfe, data, options);
779
1285
  const attempted = {
780
1286
  salesPoint: data.salesPoint,
781
1287
  voucherType: data.voucherType,
782
1288
  number
783
1289
  };
784
1290
  const exact = includeExact ? { sent: data } : {};
785
- const authorization = await wsfe.authorizeVoucherOutcome({
786
- ...auth,
787
- data,
788
- voucherNumber: number
789
- });
790
1291
  const voucher = (cae, caeExpiry) => ({
791
1292
  ...attempted,
792
1293
  voucherClass,
@@ -795,6 +1296,29 @@ async function issueInvoice(wsfe, input, options) {
795
1296
  caeExpiry,
796
1297
  amounts
797
1298
  });
1299
+ const recovery = { wsfe, auth, data, attempted, includeRaw, exact, voucher };
1300
+ if (replay) {
1301
+ const attempt2 = replayEvidence();
1302
+ let lookup;
1303
+ try {
1304
+ lookup = await wsfe.lookupVoucher({ ...auth, ...attempted });
1305
+ } catch (error) {
1306
+ return {
1307
+ kind: "indeterminate",
1308
+ attempted,
1309
+ attempt: attempt2,
1310
+ lookup: { kind: "failed", error: toArcaSafeErrorMetadata(error) }
1311
+ };
1312
+ }
1313
+ if (lookup.kind === "found") {
1314
+ return recoverInvoice({ ...recovery, attempt: attempt2, lookup });
1315
+ }
1316
+ }
1317
+ const authorization = await wsfe.issue({
1318
+ ...auth,
1319
+ data,
1320
+ voucherNumber: number
1321
+ });
798
1322
  if (authorization.kind === "authorized" && authorization.caeExpiry && authorization.voucherNumber === number) {
799
1323
  return {
800
1324
  kind: "authorized",
@@ -805,6 +1329,24 @@ async function issueInvoice(wsfe, input, options) {
805
1329
  };
806
1330
  }
807
1331
  if (authorization.kind === "rejected") {
1332
+ if (options.idempotencyKey !== void 0 && [...authorization.errors, ...authorization.observations].some(
1333
+ (issue) => issue.code === "10016"
1334
+ )) {
1335
+ const recovered = await recoverInvoice({
1336
+ ...recovery,
1337
+ attempt: projectEvidence(
1338
+ {
1339
+ ...authorization,
1340
+ kind: "indeterminate",
1341
+ reason: "contradictory_response"
1342
+ },
1343
+ includeRaw
1344
+ )
1345
+ });
1346
+ if (recovered.kind === "authorized" || recovered.kind === "conflict") {
1347
+ return recovered;
1348
+ }
1349
+ }
808
1350
  return {
809
1351
  kind: "rejected",
810
1352
  attempted,
@@ -839,11 +1381,12 @@ async function recoverInvoice({
839
1381
  attempt,
840
1382
  includeRaw,
841
1383
  exact,
842
- voucher
1384
+ voucher,
1385
+ lookup: suppliedLookup
843
1386
  }) {
844
1387
  let lookup;
845
1388
  try {
846
- lookup = await wsfe.lookupVoucher({ ...auth, ...attempted });
1389
+ lookup = suppliedLookup ?? await wsfe.lookupVoucher({ ...auth, ...attempted });
847
1390
  } catch (error) {
848
1391
  return {
849
1392
  kind: "indeterminate",
@@ -873,7 +1416,7 @@ async function recoverInvoice({
873
1416
  attempted,
874
1417
  attempt,
875
1418
  found: { ...toVoucherSummary(lookup.voucher), ...raw },
876
- reason: `${matched.reason}. ${SERIALIZATION_NOTICE}`
1419
+ reason: `${matched.reason}. Configure a store and pass idempotencyKey for retries.`
877
1420
  };
878
1421
  }
879
1422
  return {
@@ -947,7 +1490,7 @@ function validateOptions(options) {
947
1490
  assertIssueObject(options, "options");
948
1491
  assertIssueKeys(
949
1492
  options,
950
- ["representedTaxId", "forceRefresh", "include"],
1493
+ ["representedTaxId", "forceRefresh", "include", "idempotencyKey"],
951
1494
  "options"
952
1495
  );
953
1496
  if (options.representedTaxId !== void 0) {
@@ -980,6 +1523,55 @@ function validateOptions(options) {
980
1523
  }
981
1524
  }
982
1525
  }
1526
+ function replayEvidence() {
1527
+ return {
1528
+ kind: "indeterminate",
1529
+ service: "wsfe",
1530
+ operation: "FECAESolicitar",
1531
+ reason: "incomplete_response",
1532
+ results: {},
1533
+ errors: [],
1534
+ observations: []
1535
+ };
1536
+ }
1537
+ async function issueCreditNote(wsfe, input, options, context) {
1538
+ validateOptions(options);
1539
+ validateKeyStore(options, context);
1540
+ const note = assertCreditNoteInput(input);
1541
+ const target = note.for;
1542
+ return await runOperation(
1543
+ wsfe,
1544
+ "creditNote",
1545
+ note,
1546
+ async () => {
1547
+ const original = await wsfe.lookupVoucher({
1548
+ representedTaxId: options.representedTaxId,
1549
+ forceRefresh: options.forceRefresh,
1550
+ salesPoint: target.salesPoint,
1551
+ voucherType: target.voucherType,
1552
+ number: target.number
1553
+ });
1554
+ if (original.kind === "not_found") {
1555
+ throw new ArcaInputError(
1556
+ "issueCreditNote failed: original voucher not found.",
1557
+ {
1558
+ code: "ARCA_INPUT_INVALID_VALUE",
1559
+ field: "input.for"
1560
+ }
1561
+ );
1562
+ }
1563
+ if (original.voucher.salesPoint !== target.salesPoint || original.voucher.voucherType !== target.voucherType || original.voucher.voucherNumber !== target.number) {
1564
+ throw new ArcaInputError(
1565
+ "issueCreditNote failed: the original lookup coordinates do not match input.for.",
1566
+ { code: "ARCA_INPUT_INVALID_VALUE", field: "input.for" }
1567
+ );
1568
+ }
1569
+ return note.all === true ? deriveWsfeFullCreditNote(original.voucher, note) : deriveWsfePartialCreditNote(original.voucher, note);
1570
+ },
1571
+ options,
1572
+ context
1573
+ );
1574
+ }
983
1575
 
984
1576
  // src/internal/http.ts
985
1577
  import https from "https";
@@ -1389,7 +1981,7 @@ function createSoapTransport(options) {
1389
1981
  }
1390
1982
 
1391
1983
  // src/wsaa/index.ts
1392
- import { createHash } from "crypto";
1984
+ import { createHash as createHash2 } from "crypto";
1393
1985
  import forge from "node-forge";
1394
1986
 
1395
1987
  // src/wsaa/session-store.ts
@@ -1623,7 +2215,7 @@ function buildWsaaSessionKey(config, service) {
1623
2215
  };
1624
2216
  }
1625
2217
  function getCertificateFingerprint(config) {
1626
- return createHash("sha256").update(config.certificatePem).digest("hex");
2218
+ return createHash2("sha256").update(config.certificatePem).digest("hex");
1627
2219
  }
1628
2220
  function getCachedCredentials(cache, cacheKey) {
1629
2221
  const localCached = cache.get(cacheKey);
@@ -1814,10 +2406,40 @@ function parseLoginTicketResponse(xml) {
1814
2406
  };
1815
2407
  }
1816
2408
 
2409
+ // src/wsaa/store-adapter.ts
2410
+ function createWsaaStoreAdapter(store) {
2411
+ const key = (value) => `arca:v1:wsaa:${serializeWsaaSessionKey(value)}`;
2412
+ const remove = store.delete?.bind(store);
2413
+ const lock = store.withLock?.bind(store);
2414
+ return {
2415
+ get: (value) => storeCall(async () => {
2416
+ const json = await store.get(key(value));
2417
+ if (json === null) {
2418
+ return null;
2419
+ }
2420
+ const credentials = JSON.parse(json);
2421
+ return credentials && typeof credentials.token === "string" && typeof credentials.sign === "string" && isWsaaCredentialValid(credentials) ? credentials : null;
2422
+ }),
2423
+ set: (value, credentials) => storeCall(() => store.set(key(value), JSON.stringify(credentials))),
2424
+ ...remove ? {
2425
+ delete: (value) => storeCall(() => remove(key(value)))
2426
+ } : {},
2427
+ ...lock ? {
2428
+ withLock: (value, fn) => lock(key(value), fn)
2429
+ } : {}
2430
+ };
2431
+ }
2432
+
1817
2433
  // src/client.ts
1818
- function createArcaClient(config) {
1819
- assertArcaClientConfig(config);
1820
- const normalizedConfig = normalizeArcaClientConfig(config);
2434
+ function createArcaClient(config = {}) {
2435
+ const discovered = discoverArcaClientConfig(config);
2436
+ assertArcaClientConfig(discovered);
2437
+ const normalizedConfig = normalizeArcaClientConfig(discovered);
2438
+ if (normalizedConfig.store && !normalizedConfig.wsaaSessionStore) {
2439
+ normalizedConfig.wsaaSessionStore = createWsaaStoreAdapter(
2440
+ normalizedConfig.store
2441
+ );
2442
+ }
1821
2443
  const logger = createArcaLogger(normalizedConfig.logger);
1822
2444
  const auth = createWsaaAuthModule({ config: normalizedConfig, logger });
1823
2445
  const soap = createSoapTransport({ config: normalizedConfig, logger });
@@ -1829,14 +2451,177 @@ function createArcaClient(config) {
1829
2451
  retryDelay: normalizedConfig.retryDelay
1830
2452
  });
1831
2453
  const wsfe = createWsfeService({ config: normalizedConfig, auth, soap });
2454
+ const vouchers = createVouchersService(wsfe, normalizedConfig);
1832
2455
  return {
1833
2456
  config: publicConfig,
2457
+ issue: vouchers.issue,
2458
+ preview: vouchers.preview,
2459
+ issueCreditNote: vouchers.issueCreditNote,
1834
2460
  wsfe,
1835
- vouchers: createVouchersService(wsfe),
1836
2461
  wsmtxca: createWsmtxcaService({ config: normalizedConfig, auth, soap }),
1837
2462
  padron: createPadronService({ config: normalizedConfig, auth, soap })
1838
2463
  };
1839
2464
  }
2465
+
2466
+ // src/store/file.ts
2467
+ import { createHash as createHash3, randomUUID } from "crypto";
2468
+ import { mkdir, readFile, rename, unlink, writeFile } from "fs/promises";
2469
+ import { join } from "path";
2470
+ function createFileStore(directory) {
2471
+ const path = (key) => join(directory, createHash3("sha256").update(key).digest("hex"));
2472
+ const ensure = () => mkdir(directory, { recursive: true, mode: 448 });
2473
+ return {
2474
+ get: (key) => storeCall(async () => {
2475
+ try {
2476
+ return await readFile(path(key), "utf8");
2477
+ } catch (error) {
2478
+ if (error.code === "ENOENT") {
2479
+ return null;
2480
+ }
2481
+ throw error;
2482
+ }
2483
+ }),
2484
+ set: (key, value) => storeCall(async () => {
2485
+ await ensure();
2486
+ const temporary = `${path(key)}.${randomUUID()}.tmp`;
2487
+ try {
2488
+ await writeFile(temporary, value, { mode: 384, flag: "wx" });
2489
+ await rename(temporary, path(key));
2490
+ } finally {
2491
+ await unlink(temporary).catch(() => void 0);
2492
+ }
2493
+ }),
2494
+ add: (key, value) => storeCall(async () => {
2495
+ await ensure();
2496
+ try {
2497
+ await writeFile(path(key), value, { flag: "wx", mode: 384 });
2498
+ return true;
2499
+ } catch (error) {
2500
+ if (error.code === "EEXIST") {
2501
+ return false;
2502
+ }
2503
+ throw error;
2504
+ }
2505
+ }),
2506
+ delete: (key) => storeCall(async () => {
2507
+ try {
2508
+ await unlink(path(key));
2509
+ } catch (error) {
2510
+ if (error.code !== "ENOENT") {
2511
+ throw error;
2512
+ }
2513
+ }
2514
+ })
2515
+ };
2516
+ }
2517
+
2518
+ // src/store/memory.ts
2519
+ function createMemoryStore() {
2520
+ const values = /* @__PURE__ */ new Map();
2521
+ const locks = /* @__PURE__ */ new Map();
2522
+ return {
2523
+ get: (key) => Promise.resolve(values.get(key) ?? null),
2524
+ set(key, value) {
2525
+ values.set(key, value);
2526
+ return Promise.resolve();
2527
+ },
2528
+ add(key, value) {
2529
+ if (values.has(key)) {
2530
+ return Promise.resolve(false);
2531
+ }
2532
+ values.set(key, value);
2533
+ return Promise.resolve(true);
2534
+ },
2535
+ delete(key) {
2536
+ values.delete(key);
2537
+ return Promise.resolve();
2538
+ },
2539
+ async withLock(key, fn) {
2540
+ const previous = locks.get(key) ?? Promise.resolve();
2541
+ let release = () => void 0;
2542
+ const current = new Promise((resolve) => {
2543
+ release = resolve;
2544
+ });
2545
+ const queued = previous.catch(() => void 0).then(() => current);
2546
+ locks.set(key, queued);
2547
+ await previous.catch(() => void 0);
2548
+ try {
2549
+ return await fn();
2550
+ } finally {
2551
+ release();
2552
+ if (locks.get(key) === queued) {
2553
+ locks.delete(key);
2554
+ }
2555
+ }
2556
+ }
2557
+ };
2558
+ }
2559
+
2560
+ // src/store/postgres.ts
2561
+ function createPostgresStore({
2562
+ query,
2563
+ table = "arca_store"
2564
+ }) {
2565
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(table)) {
2566
+ throw new ArcaConfigurationError("Invalid ARCA store table identifier.");
2567
+ }
2568
+ const name = `"${table}"`;
2569
+ const run = (text, params) => storeCall(async () => {
2570
+ const result = await query(text, params);
2571
+ return Array.isArray(result) ? result : result.rows;
2572
+ });
2573
+ return {
2574
+ async get(key) {
2575
+ return (await run(`SELECT value FROM ${name} WHERE key = $1`, [key]))[0]?.value ?? null;
2576
+ },
2577
+ async set(key, value) {
2578
+ await run(
2579
+ `INSERT INTO ${name} (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = now()`,
2580
+ [key, value]
2581
+ );
2582
+ },
2583
+ async add(key, value) {
2584
+ return (await run(
2585
+ `INSERT INTO ${name} (key, value) VALUES ($1, $2) ON CONFLICT DO NOTHING RETURNING key`,
2586
+ [key, value]
2587
+ )).length > 0;
2588
+ },
2589
+ async delete(key) {
2590
+ await run(`DELETE FROM ${name} WHERE key = $1`, [key]);
2591
+ }
2592
+ };
2593
+ }
2594
+
2595
+ // src/store/redis.ts
2596
+ function createRedisStore(client, options = {}) {
2597
+ const flavor = options.flavor ?? (typeof client.call === "function" ? "ioredis" : "upstash");
2598
+ if (flavor === "ioredis" && !client.call) {
2599
+ throw new ArcaConfigurationError(
2600
+ "ARCA Redis ioredis flavor requires client.call."
2601
+ );
2602
+ }
2603
+ const upstashSet = client.set;
2604
+ return {
2605
+ get: (key) => storeCall(async () => {
2606
+ const value = await client.get(key);
2607
+ if (value === null) {
2608
+ return null;
2609
+ }
2610
+ return typeof value === "string" ? value : JSON.stringify(value);
2611
+ }),
2612
+ set: (key, value) => storeCall(async () => {
2613
+ await client.set(key, value);
2614
+ }),
2615
+ add: (key, value) => storeCall(
2616
+ async () => (flavor === "ioredis" ? await client.call?.("SET", key, value, "NX") : await upstashSet.call(client, key, value, { nx: true })) === "OK"
2617
+ ),
2618
+ ...client.del ? {
2619
+ delete: (key) => storeCall(async () => {
2620
+ await client.del?.(key);
2621
+ })
2622
+ } : {}
2623
+ };
2624
+ }
1840
2625
  export {
1841
2626
  ARCA_ENVIRONMENTS,
1842
2627
  ARCA_ENV_VARIABLES,
@@ -1857,8 +2642,12 @@ export {
1857
2642
  buildFacturaC,
1858
2643
  createArcaClient,
1859
2644
  createArcaClientConfigFromEnv,
2645
+ createFileStore,
2646
+ createMemoryStore,
1860
2647
  createMemoryWsaaSessionStore,
1861
2648
  createPadronService,
2649
+ createPostgresStore,
2650
+ createRedisStore,
1862
2651
  createWsfeService,
1863
2652
  createWsmtxcaService,
1864
2653
  isArcaAuthenticationError,