facturas 0.8.0 → 0.9.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/README.md +797 -37
- package/dist/{chunk-PPYGVNFA.mjs → chunk-4QVFXT35.mjs} +2 -2
- package/dist/{chunk-NUV5RZPZ.mjs → chunk-7LEKQ4P7.mjs} +2 -2
- package/dist/{chunk-TOVSOJ3G.mjs → chunk-RCM4NAAG.mjs} +25 -6
- package/dist/chunk-RCM4NAAG.mjs.map +1 -0
- package/dist/{chunk-ZX4OOCML.mjs → chunk-XNBD5NVL.mjs} +32 -31
- package/dist/chunk-XNBD5NVL.mjs.map +1 -0
- package/dist/{chunk-HUT3PFKF.mjs → chunk-ZOSNASXB.mjs} +1 -1
- package/dist/chunk-ZOSNASXB.mjs.map +1 -0
- package/dist/errors.d.ts +2 -2
- package/dist/errors.mjs +1 -1
- package/dist/{errors-B0uouRzR.d.ts → fiscal-evidence-DBzakqBE.d.ts} +77 -77
- package/dist/{index-CVpygFb0.d.ts → index-CVGQyyYM.d.ts} +1 -1
- package/dist/index.d.ts +57 -22
- package/dist/index.mjs +672 -61
- package/dist/index.mjs.map +1 -1
- package/dist/padron.d.ts +2 -2
- package/dist/padron.mjs +2 -2
- package/dist/{types-BXq0da71.d.ts → types-BiznC1-X.d.ts} +12 -1
- package/dist/types.d.ts +1 -1
- package/dist/{wsfe-CG_erq7V.d.ts → wsfe-DF0jAE2k.d.ts} +18 -7
- package/dist/wsfe.d.ts +4 -4
- package/dist/wsfe.mjs +3 -3
- package/dist/wsmtxca.d.ts +14 -5
- package/dist/wsmtxca.mjs +3 -3
- package/package.json +1 -1
- package/dist/chunk-HUT3PFKF.mjs.map +0 -1
- package/dist/chunk-TOVSOJ3G.mjs.map +0 -1
- package/dist/chunk-ZX4OOCML.mjs.map +0 -1
- /package/dist/{chunk-PPYGVNFA.mjs.map → chunk-4QVFXT35.mjs.map} +0 -0
- /package/dist/{chunk-NUV5RZPZ.mjs.map → chunk-7LEKQ4P7.mjs.map} +0 -0
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createPadronService
|
|
3
|
-
} from "./chunk-
|
|
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-
|
|
13
|
+
} from "./chunk-RCM4NAAG.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-
|
|
26
|
-
import "./chunk-
|
|
25
|
+
} from "./chunk-XNBD5NVL.mjs";
|
|
26
|
+
import "./chunk-4QVFXT35.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-
|
|
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
|
-
|
|
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
|
|
214
|
-
certificatePem: config.certificatePem
|
|
215
|
-
privateKeyPem: config.privateKeyPem
|
|
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,
|
|
@@ -493,14 +548,11 @@ function assertIssueKeys(value, keys, prefix) {
|
|
|
493
548
|
for (const key of Object.keys(value)) {
|
|
494
549
|
if (!keys.includes(key)) {
|
|
495
550
|
const field = prefix === "input" ? key : `${prefix}.${key}`;
|
|
496
|
-
throw new ArcaInputError(
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
expected: "a supported facade field; use the exact API for other fiscal fields"
|
|
502
|
-
}
|
|
503
|
-
);
|
|
551
|
+
throw new ArcaInputError(`${field} is not supported by issue().`, {
|
|
552
|
+
code: "ARCA_INPUT_RESERVED_FIELD",
|
|
553
|
+
field,
|
|
554
|
+
expected: "a supported facade field; use the exact API for other fiscal fields"
|
|
555
|
+
});
|
|
504
556
|
}
|
|
505
557
|
}
|
|
506
558
|
}
|
|
@@ -512,6 +564,129 @@ function invalid(field, expected) {
|
|
|
512
564
|
});
|
|
513
565
|
}
|
|
514
566
|
|
|
567
|
+
// src/services/wsfe-cancel.ts
|
|
568
|
+
var NOTES = {
|
|
569
|
+
1: { voucherType: 3, voucherClass: "A" },
|
|
570
|
+
6: { voucherType: 8, voucherClass: "B" },
|
|
571
|
+
11: { voucherType: 13, voucherClass: "C" }
|
|
572
|
+
};
|
|
573
|
+
function invalid2(reason) {
|
|
574
|
+
throw new ArcaInputError(
|
|
575
|
+
`Cannot cancel: ${reason}. Use wsfe.issue() for exact control.`,
|
|
576
|
+
{ code: "ARCA_INPUT_INVALID_VALUE" }
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
function required(value, field) {
|
|
580
|
+
if (value === void 0 || value === null) {
|
|
581
|
+
invalid2(`original is missing ${field}`);
|
|
582
|
+
}
|
|
583
|
+
return value;
|
|
584
|
+
}
|
|
585
|
+
function deriveWsfeCancellation(original, date, now = /* @__PURE__ */ new Date()) {
|
|
586
|
+
const note = NOTES[original.voucherType ?? 0];
|
|
587
|
+
if (!note) {
|
|
588
|
+
invalid2("original must be an invoice of type 1, 6 or 11");
|
|
589
|
+
}
|
|
590
|
+
if (!(["A", "O"].includes(original.result ?? "") && original.cae?.trim() && original.caeExpiry?.trim())) {
|
|
591
|
+
invalid2("original is not authorized");
|
|
592
|
+
}
|
|
593
|
+
rejectExtensions(original);
|
|
594
|
+
if (original.currencyId !== "PES" && original.currencyId !== "DOL") {
|
|
595
|
+
invalid2("unsupported currency; only ARS and USD are supported");
|
|
596
|
+
}
|
|
597
|
+
const voucherDate = normalizeWsfeDateInput(
|
|
598
|
+
date ?? buenosAiresDate(now),
|
|
599
|
+
"date"
|
|
600
|
+
);
|
|
601
|
+
const originalDate = normalizeWsfeDateInput(
|
|
602
|
+
required(original.voucherDate, "voucherDate"),
|
|
603
|
+
"original.voucherDate"
|
|
604
|
+
);
|
|
605
|
+
if (originalDate > voucherDate && originalDate.slice(0, 6) !== voucherDate.slice(0, 6)) {
|
|
606
|
+
invalid2(
|
|
607
|
+
"original date is later than the note and outside its month (10210)"
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
const data = {
|
|
611
|
+
salesPoint: required(original.salesPoint, "salesPoint"),
|
|
612
|
+
voucherType: note.voucherType,
|
|
613
|
+
concept: required(original.concept, "concept"),
|
|
614
|
+
documentType: required(original.documentType, "documentType"),
|
|
615
|
+
documentNumber: Number(required(original.documentNumber, "documentNumber")),
|
|
616
|
+
receiverVatConditionId: required(
|
|
617
|
+
original.receiverVatConditionId,
|
|
618
|
+
"receiverVatConditionId"
|
|
619
|
+
),
|
|
620
|
+
currencyId: original.currencyId,
|
|
621
|
+
exchangeRate: required(original.exchangeRate, "exchangeRate"),
|
|
622
|
+
voucherDate,
|
|
623
|
+
totalAmount: required(original.totalAmount, "totalAmount"),
|
|
624
|
+
netAmount: required(original.netAmount, "netAmount"),
|
|
625
|
+
vatAmount: required(original.vatAmount, "vatAmount"),
|
|
626
|
+
exemptAmount: required(original.exemptAmount, "exemptAmount"),
|
|
627
|
+
nonTaxableAmount: required(original.nonTaxableAmount, "nonTaxableAmount"),
|
|
628
|
+
taxAmount: required(original.taxAmount, "taxAmount"),
|
|
629
|
+
...original.vatRates === void 0 ? {} : { vatRates: original.vatRates.map((rate) => ({ ...rate })) },
|
|
630
|
+
associatedVouchers: [
|
|
631
|
+
{
|
|
632
|
+
type: required(original.voucherType, "voucherType"),
|
|
633
|
+
salesPoint: required(original.salesPoint, "salesPoint"),
|
|
634
|
+
number: original.voucherNumber,
|
|
635
|
+
voucherDate: originalDate
|
|
636
|
+
}
|
|
637
|
+
]
|
|
638
|
+
};
|
|
639
|
+
copyServiceDates(original, data);
|
|
640
|
+
normalizeWsfeVoucherInput(data);
|
|
641
|
+
const total = Number(
|
|
642
|
+
normalizeArcaAmountToMinorUnits(data.totalAmount, "totalAmount")
|
|
643
|
+
);
|
|
644
|
+
return {
|
|
645
|
+
data,
|
|
646
|
+
voucherClass: note.voucherClass,
|
|
647
|
+
amounts: { computedTotal: total, sentTotal: total, vatAdjustment: 0 }
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
function rejectExtensions(original) {
|
|
651
|
+
for (const [field, rawField] of [
|
|
652
|
+
["taxes", "Tributos"],
|
|
653
|
+
["optionalFields", "Opcionales"],
|
|
654
|
+
["buyers", "Compradores"],
|
|
655
|
+
["activities", "Actividades"],
|
|
656
|
+
["associatedPeriod", "PeriodoAsoc"]
|
|
657
|
+
]) {
|
|
658
|
+
const value = original[field];
|
|
659
|
+
const raw = original.raw[rawField];
|
|
660
|
+
if (present(value) || present(raw)) {
|
|
661
|
+
invalid2(`unsupported original ${field}`);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if ((original.taxAmount ?? 0) !== 0) {
|
|
665
|
+
invalid2("unsupported original taxes");
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
function present(value) {
|
|
669
|
+
return value !== void 0 && value !== null && value !== "" && (!Array.isArray(value) || value.length > 0);
|
|
670
|
+
}
|
|
671
|
+
function copyServiceDates(original, data) {
|
|
672
|
+
if (data.concept !== 2 && data.concept !== 3) {
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
data.serviceStartDate = required(
|
|
676
|
+
original.serviceStartDate,
|
|
677
|
+
"serviceStartDate"
|
|
678
|
+
);
|
|
679
|
+
data.serviceEndDate = required(
|
|
680
|
+
original.serviceEndDate,
|
|
681
|
+
"serviceEndDate"
|
|
682
|
+
);
|
|
683
|
+
const due = normalizeWsfeDateInput(
|
|
684
|
+
required(original.paymentDueDate, "paymentDueDate"),
|
|
685
|
+
"original.paymentDueDate"
|
|
686
|
+
);
|
|
687
|
+
data.paymentDueDate = due < data.voucherDate ? data.voucherDate : due;
|
|
688
|
+
}
|
|
689
|
+
|
|
515
690
|
// src/services/wsfe-identity.ts
|
|
516
691
|
function matchWsfeVoucherIdentity(sent, number, found) {
|
|
517
692
|
let missing;
|
|
@@ -581,7 +756,7 @@ function matchWsfeVoucherIdentity(sent, number, found) {
|
|
|
581
756
|
(value) => normalizeArcaAmountToMinorUnits(value, field)
|
|
582
757
|
]);
|
|
583
758
|
}
|
|
584
|
-
if (sent.concept === 2) {
|
|
759
|
+
if (sent.concept === 2 || sent.concept === 3) {
|
|
585
760
|
for (const field of [
|
|
586
761
|
"serviceStartDate",
|
|
587
762
|
"serviceEndDate",
|
|
@@ -608,6 +783,17 @@ function matchWsfeVoucherIdentity(sent, number, found) {
|
|
|
608
783
|
}
|
|
609
784
|
missing ??= rates.reason;
|
|
610
785
|
}
|
|
786
|
+
if (sent.associatedVouchers?.length) {
|
|
787
|
+
if (!found.associatedVouchers) {
|
|
788
|
+
missing ??= "associatedVouchers";
|
|
789
|
+
} else if (associationIdentity(sent.associatedVouchers) !== associationIdentity(found.associatedVouchers)) {
|
|
790
|
+
return {
|
|
791
|
+
matches: false,
|
|
792
|
+
evidence: "conflict",
|
|
793
|
+
reason: "associatedVouchers differ from the sent input"
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
}
|
|
611
797
|
missing ??= incompleteAuthorization(sent, found);
|
|
612
798
|
return missing ? {
|
|
613
799
|
matches: false,
|
|
@@ -617,12 +803,11 @@ function matchWsfeVoucherIdentity(sent, number, found) {
|
|
|
617
803
|
}
|
|
618
804
|
function incompleteAuthorization(sent, found) {
|
|
619
805
|
let missing;
|
|
620
|
-
if (sent.concept !== 1 && sent.concept !== 2) {
|
|
806
|
+
if (sent.concept !== 1 && sent.concept !== 2 && sent.concept !== 3) {
|
|
621
807
|
missing ??= "unsupported concept";
|
|
622
808
|
}
|
|
623
809
|
for (const field of [
|
|
624
810
|
"taxes",
|
|
625
|
-
"associatedVouchers",
|
|
626
811
|
"associatedPeriod",
|
|
627
812
|
"optionalFields",
|
|
628
813
|
"buyers",
|
|
@@ -738,55 +923,176 @@ function toVoucherSummary(found) {
|
|
|
738
923
|
}
|
|
739
924
|
return summary;
|
|
740
925
|
}
|
|
926
|
+
function associationIdentity(vouchers) {
|
|
927
|
+
return JSON.stringify(
|
|
928
|
+
vouchers.map(({ type, salesPoint, number }) => `${type}:${salesPoint}:${number}`).sort()
|
|
929
|
+
);
|
|
930
|
+
}
|
|
741
931
|
|
|
742
932
|
// src/services/vouchers.ts
|
|
743
|
-
|
|
744
|
-
function createVouchersService(wsfe) {
|
|
933
|
+
function createVouchersService(wsfe, context) {
|
|
745
934
|
return {
|
|
935
|
+
cancel: async (target, options) => {
|
|
936
|
+
const result = await cancelInvoice(wsfe, target, options ?? {}, context);
|
|
937
|
+
return result;
|
|
938
|
+
},
|
|
746
939
|
issue: async (input, options) => {
|
|
747
940
|
const result = await issueInvoice(
|
|
748
941
|
wsfe,
|
|
749
942
|
input,
|
|
750
|
-
options === void 0 ? {} : options
|
|
943
|
+
options === void 0 ? {} : options,
|
|
944
|
+
context
|
|
751
945
|
);
|
|
752
946
|
return result;
|
|
753
947
|
}
|
|
754
948
|
};
|
|
755
949
|
}
|
|
756
|
-
async function issueInvoice(wsfe, input, options) {
|
|
950
|
+
async function issueInvoice(wsfe, input, options, context) {
|
|
757
951
|
validateOptions(options);
|
|
758
|
-
|
|
759
|
-
const
|
|
760
|
-
|
|
761
|
-
|
|
952
|
+
validateKeyStore(options, context);
|
|
953
|
+
const prepared = deriveWsfeInvoice(input);
|
|
954
|
+
return await runOperation(
|
|
955
|
+
wsfe,
|
|
956
|
+
"issue",
|
|
957
|
+
input,
|
|
958
|
+
() => Promise.resolve(prepared),
|
|
959
|
+
options,
|
|
960
|
+
context,
|
|
961
|
+
prepared.amounts
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
async function runOperation(wsfe, operation, input, prepare, options, context, replayAmounts) {
|
|
965
|
+
const store = context?.store;
|
|
966
|
+
if (options.idempotencyKey === void 0 || !store || !context) {
|
|
967
|
+
return runAuthorization(wsfe, await prepare(), options);
|
|
968
|
+
}
|
|
969
|
+
const key = attemptKey(
|
|
970
|
+
context.environment,
|
|
971
|
+
context.taxId,
|
|
972
|
+
options.idempotencyKey
|
|
973
|
+
);
|
|
974
|
+
const representedTaxId = options.representedTaxId === void 0 ? void 0 : String(options.representedTaxId);
|
|
975
|
+
const inputHash = canonicalHash({ input, representedTaxId });
|
|
976
|
+
const existing = await storeCall(() => store.get(key));
|
|
977
|
+
if (existing !== null) {
|
|
978
|
+
return replay(existing);
|
|
979
|
+
}
|
|
980
|
+
const prepared = await prepare();
|
|
981
|
+
const number = await nextNumber(wsfe, prepared.data, options);
|
|
982
|
+
const record = {
|
|
983
|
+
v: 1,
|
|
984
|
+
operation,
|
|
985
|
+
representedTaxId,
|
|
986
|
+
inputHash,
|
|
987
|
+
number,
|
|
988
|
+
salesPoint: prepared.data.salesPoint,
|
|
989
|
+
voucherType: prepared.data.voucherType,
|
|
990
|
+
sent: prepared.data,
|
|
991
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
762
992
|
};
|
|
763
|
-
|
|
764
|
-
|
|
993
|
+
if (await storeCall(() => store.add(key, JSON.stringify(record)))) {
|
|
994
|
+
return runAuthorization(wsfe, prepared, options, number);
|
|
995
|
+
}
|
|
996
|
+
const winner = await storeCall(() => store.get(key));
|
|
997
|
+
if (winner === null) {
|
|
998
|
+
throw new ArcaConfigurationError(
|
|
999
|
+
"ARCA reservation disappeared after atomic creation lost."
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
return replay(winner);
|
|
1003
|
+
function replay(json) {
|
|
1004
|
+
const stored = readRecord(json);
|
|
1005
|
+
if (stored.operation !== operation || stored.inputHash !== inputHash || stored.representedTaxId !== representedTaxId) {
|
|
1006
|
+
throw new ArcaInputError(
|
|
1007
|
+
"The idempotency key was already used with different input or operation.",
|
|
1008
|
+
{
|
|
1009
|
+
code: "ARCA_INPUT_IDEMPOTENCY_MISMATCH",
|
|
1010
|
+
field: "options.idempotencyKey"
|
|
1011
|
+
}
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
return runAuthorization(
|
|
1015
|
+
wsfe,
|
|
1016
|
+
{
|
|
1017
|
+
...preparedFromRecord(stored),
|
|
1018
|
+
...replayAmounts ? { amounts: replayAmounts } : {}
|
|
1019
|
+
},
|
|
1020
|
+
options,
|
|
1021
|
+
stored.number,
|
|
1022
|
+
true
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
function validateKeyStore(options, context) {
|
|
1027
|
+
if (options.idempotencyKey === void 0) {
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
if (typeof options.idempotencyKey !== "string" || options.idempotencyKey.length < 1 || options.idempotencyKey.length > 255) {
|
|
1031
|
+
throw new ArcaInputError(
|
|
1032
|
+
"idempotencyKey must contain 1 to 255 characters.",
|
|
1033
|
+
{ code: "ARCA_INPUT_INVALID_VALUE", field: "options.idempotencyKey" }
|
|
1034
|
+
);
|
|
1035
|
+
}
|
|
1036
|
+
if (!context?.store) {
|
|
1037
|
+
throw new ArcaConfigurationError(
|
|
1038
|
+
'idempotencyKey requires a store. Add import { createPostgresStore } from "facturas"; and store: createPostgresStore({ query }) to createArcaClient().'
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
function readRecord(json) {
|
|
1043
|
+
try {
|
|
1044
|
+
const record = JSON.parse(json);
|
|
1045
|
+
if (!record || record.v !== 1 || !["issue", "cancel"].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) {
|
|
1046
|
+
throw new Error("Invalid reservation structure");
|
|
1047
|
+
}
|
|
1048
|
+
normalizeWsfeVoucherInput(record.sent);
|
|
1049
|
+
return record;
|
|
1050
|
+
} catch (cause) {
|
|
1051
|
+
throw new ArcaConfigurationError(
|
|
1052
|
+
"Invalid ARCA reservation record; preserve it for reconciliation.",
|
|
1053
|
+
{ cause }
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
function preparedFromRecord(record) {
|
|
1058
|
+
const sentTotal = Number(
|
|
1059
|
+
normalizeArcaAmountToMinorUnits(record.sent.totalAmount, "totalAmount")
|
|
1060
|
+
);
|
|
1061
|
+
return {
|
|
1062
|
+
data: record.sent,
|
|
1063
|
+
voucherClass: [1, 3].includes(record.voucherType) ? "A" : [6, 8].includes(record.voucherType) ? "B" : "C",
|
|
1064
|
+
amounts: { computedTotal: sentTotal, sentTotal, vatAdjustment: 0 }
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
async function nextNumber(wsfe, data, options) {
|
|
765
1068
|
const number = await wsfe.getNextVoucherNumber({
|
|
766
|
-
|
|
1069
|
+
representedTaxId: options.representedTaxId,
|
|
1070
|
+
forceRefresh: options.forceRefresh,
|
|
767
1071
|
salesPoint: data.salesPoint,
|
|
768
1072
|
voucherType: data.voucherType
|
|
769
1073
|
});
|
|
770
1074
|
if (!Number.isSafeInteger(number) || number < 1 || number > 99999999) {
|
|
771
1075
|
throw new ArcaServiceError(
|
|
772
1076
|
"WSFE returned an invalid next voucher number.",
|
|
773
|
-
{
|
|
774
|
-
service: "wsfe",
|
|
775
|
-
operation: "FECompUltimoAutorizado"
|
|
776
|
-
}
|
|
1077
|
+
{ service: "wsfe", operation: "FECompUltimoAutorizado" }
|
|
777
1078
|
);
|
|
778
1079
|
}
|
|
1080
|
+
return number;
|
|
1081
|
+
}
|
|
1082
|
+
async function runAuthorization(wsfe, { data, voucherClass, amounts }, options, reservedNumber, replay = false) {
|
|
1083
|
+
const auth = {
|
|
1084
|
+
representedTaxId: options.representedTaxId,
|
|
1085
|
+
forceRefresh: options.forceRefresh
|
|
1086
|
+
};
|
|
1087
|
+
const includeRaw = options.include?.raw === true;
|
|
1088
|
+
const includeExact = options.include?.exactInput === true;
|
|
1089
|
+
const number = reservedNumber ?? await nextNumber(wsfe, data, options);
|
|
779
1090
|
const attempted = {
|
|
780
1091
|
salesPoint: data.salesPoint,
|
|
781
1092
|
voucherType: data.voucherType,
|
|
782
1093
|
number
|
|
783
1094
|
};
|
|
784
1095
|
const exact = includeExact ? { sent: data } : {};
|
|
785
|
-
const authorization = await wsfe.authorizeVoucherOutcome({
|
|
786
|
-
...auth,
|
|
787
|
-
data,
|
|
788
|
-
voucherNumber: number
|
|
789
|
-
});
|
|
790
1096
|
const voucher = (cae, caeExpiry) => ({
|
|
791
1097
|
...attempted,
|
|
792
1098
|
voucherClass,
|
|
@@ -795,6 +1101,29 @@ async function issueInvoice(wsfe, input, options) {
|
|
|
795
1101
|
caeExpiry,
|
|
796
1102
|
amounts
|
|
797
1103
|
});
|
|
1104
|
+
const recovery = { wsfe, auth, data, attempted, includeRaw, exact, voucher };
|
|
1105
|
+
if (replay) {
|
|
1106
|
+
const attempt2 = replayEvidence();
|
|
1107
|
+
let lookup;
|
|
1108
|
+
try {
|
|
1109
|
+
lookup = await wsfe.lookupVoucher({ ...auth, ...attempted });
|
|
1110
|
+
} catch (error) {
|
|
1111
|
+
return {
|
|
1112
|
+
kind: "indeterminate",
|
|
1113
|
+
attempted,
|
|
1114
|
+
attempt: attempt2,
|
|
1115
|
+
lookup: { kind: "failed", error: toArcaSafeErrorMetadata(error) }
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
if (lookup.kind === "found") {
|
|
1119
|
+
return recoverInvoice({ ...recovery, attempt: attempt2, lookup });
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
const authorization = await wsfe.issue({
|
|
1123
|
+
...auth,
|
|
1124
|
+
data,
|
|
1125
|
+
voucherNumber: number
|
|
1126
|
+
});
|
|
798
1127
|
if (authorization.kind === "authorized" && authorization.caeExpiry && authorization.voucherNumber === number) {
|
|
799
1128
|
return {
|
|
800
1129
|
kind: "authorized",
|
|
@@ -805,6 +1134,24 @@ async function issueInvoice(wsfe, input, options) {
|
|
|
805
1134
|
};
|
|
806
1135
|
}
|
|
807
1136
|
if (authorization.kind === "rejected") {
|
|
1137
|
+
if (options.idempotencyKey !== void 0 && [...authorization.errors, ...authorization.observations].some(
|
|
1138
|
+
(issue) => issue.code === "10016"
|
|
1139
|
+
)) {
|
|
1140
|
+
const recovered = await recoverInvoice({
|
|
1141
|
+
...recovery,
|
|
1142
|
+
attempt: projectEvidence(
|
|
1143
|
+
{
|
|
1144
|
+
...authorization,
|
|
1145
|
+
kind: "indeterminate",
|
|
1146
|
+
reason: "contradictory_response"
|
|
1147
|
+
},
|
|
1148
|
+
includeRaw
|
|
1149
|
+
)
|
|
1150
|
+
});
|
|
1151
|
+
if (recovered.kind === "authorized" || recovered.kind === "conflict") {
|
|
1152
|
+
return recovered;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
808
1155
|
return {
|
|
809
1156
|
kind: "rejected",
|
|
810
1157
|
attempted,
|
|
@@ -839,11 +1186,12 @@ async function recoverInvoice({
|
|
|
839
1186
|
attempt,
|
|
840
1187
|
includeRaw,
|
|
841
1188
|
exact,
|
|
842
|
-
voucher
|
|
1189
|
+
voucher,
|
|
1190
|
+
lookup: suppliedLookup
|
|
843
1191
|
}) {
|
|
844
1192
|
let lookup;
|
|
845
1193
|
try {
|
|
846
|
-
lookup = await wsfe.lookupVoucher({ ...auth, ...attempted });
|
|
1194
|
+
lookup = suppliedLookup ?? await wsfe.lookupVoucher({ ...auth, ...attempted });
|
|
847
1195
|
} catch (error) {
|
|
848
1196
|
return {
|
|
849
1197
|
kind: "indeterminate",
|
|
@@ -873,7 +1221,7 @@ async function recoverInvoice({
|
|
|
873
1221
|
attempted,
|
|
874
1222
|
attempt,
|
|
875
1223
|
found: { ...toVoucherSummary(lookup.voucher), ...raw },
|
|
876
|
-
reason: `${matched.reason}.
|
|
1224
|
+
reason: `${matched.reason}. Configure a store and pass idempotencyKey for retries.`
|
|
877
1225
|
};
|
|
878
1226
|
}
|
|
879
1227
|
return {
|
|
@@ -947,7 +1295,7 @@ function validateOptions(options) {
|
|
|
947
1295
|
assertIssueObject(options, "options");
|
|
948
1296
|
assertIssueKeys(
|
|
949
1297
|
options,
|
|
950
|
-
["representedTaxId", "forceRefresh", "include"],
|
|
1298
|
+
["representedTaxId", "forceRefresh", "include", "idempotencyKey"],
|
|
951
1299
|
"options"
|
|
952
1300
|
);
|
|
953
1301
|
if (options.representedTaxId !== void 0) {
|
|
@@ -980,6 +1328,73 @@ function validateOptions(options) {
|
|
|
980
1328
|
}
|
|
981
1329
|
}
|
|
982
1330
|
}
|
|
1331
|
+
function replayEvidence() {
|
|
1332
|
+
return {
|
|
1333
|
+
kind: "indeterminate",
|
|
1334
|
+
service: "wsfe",
|
|
1335
|
+
operation: "FECAESolicitar",
|
|
1336
|
+
reason: "incomplete_response",
|
|
1337
|
+
results: {},
|
|
1338
|
+
errors: [],
|
|
1339
|
+
observations: []
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
async function cancelInvoice(wsfe, target, options, context) {
|
|
1343
|
+
assertIssueObject(options, "options");
|
|
1344
|
+
const { date, ...issueOptions } = options;
|
|
1345
|
+
validateOptions(issueOptions);
|
|
1346
|
+
validateKeyStore(issueOptions, context);
|
|
1347
|
+
assertIssueObject(target, "target");
|
|
1348
|
+
for (const [field, max] of [
|
|
1349
|
+
["salesPoint", 99999],
|
|
1350
|
+
["voucherType", 999],
|
|
1351
|
+
["number", 99999999]
|
|
1352
|
+
]) {
|
|
1353
|
+
if (!Number.isSafeInteger(target[field]) || target[field] < 1 || target[field] > max) {
|
|
1354
|
+
throw new ArcaInputError(`Invalid cancel target ${field}.`, {
|
|
1355
|
+
code: "ARCA_INPUT_INVALID_VALUE",
|
|
1356
|
+
field
|
|
1357
|
+
});
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
if (![1, 6, 11].includes(target.voucherType)) {
|
|
1361
|
+
throw new ArcaInputError(
|
|
1362
|
+
"cancel requires an invoice of type 1, 6 or 11; use wsfe.issue() for exact control.",
|
|
1363
|
+
{ code: "ARCA_INPUT_INVALID_VALUE" }
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
if (date !== void 0) {
|
|
1367
|
+
normalizeWsfeDateInput(date, "date");
|
|
1368
|
+
}
|
|
1369
|
+
return await runOperation(
|
|
1370
|
+
wsfe,
|
|
1371
|
+
"cancel",
|
|
1372
|
+
{ target, date },
|
|
1373
|
+
async () => {
|
|
1374
|
+
const original = await wsfe.lookupVoucher({
|
|
1375
|
+
representedTaxId: issueOptions.representedTaxId,
|
|
1376
|
+
forceRefresh: issueOptions.forceRefresh,
|
|
1377
|
+
salesPoint: target.salesPoint,
|
|
1378
|
+
voucherType: target.voucherType,
|
|
1379
|
+
number: target.number
|
|
1380
|
+
});
|
|
1381
|
+
if (original.kind === "not_found") {
|
|
1382
|
+
throw new ArcaInputError("original voucher not found", {
|
|
1383
|
+
code: "ARCA_INPUT_INVALID_VALUE"
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
if (original.voucher.salesPoint !== target.salesPoint || original.voucher.voucherType !== target.voucherType || original.voucher.voucherNumber !== target.number) {
|
|
1387
|
+
throw new ArcaInputError(
|
|
1388
|
+
"Original lookup coordinates do not match the cancel target.",
|
|
1389
|
+
{ code: "ARCA_INPUT_INVALID_VALUE" }
|
|
1390
|
+
);
|
|
1391
|
+
}
|
|
1392
|
+
return deriveWsfeCancellation(original.voucher, date);
|
|
1393
|
+
},
|
|
1394
|
+
issueOptions,
|
|
1395
|
+
context
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
983
1398
|
|
|
984
1399
|
// src/internal/http.ts
|
|
985
1400
|
import https from "https";
|
|
@@ -1389,7 +1804,7 @@ function createSoapTransport(options) {
|
|
|
1389
1804
|
}
|
|
1390
1805
|
|
|
1391
1806
|
// src/wsaa/index.ts
|
|
1392
|
-
import { createHash } from "crypto";
|
|
1807
|
+
import { createHash as createHash2 } from "crypto";
|
|
1393
1808
|
import forge from "node-forge";
|
|
1394
1809
|
|
|
1395
1810
|
// src/wsaa/session-store.ts
|
|
@@ -1623,7 +2038,7 @@ function buildWsaaSessionKey(config, service) {
|
|
|
1623
2038
|
};
|
|
1624
2039
|
}
|
|
1625
2040
|
function getCertificateFingerprint(config) {
|
|
1626
|
-
return
|
|
2041
|
+
return createHash2("sha256").update(config.certificatePem).digest("hex");
|
|
1627
2042
|
}
|
|
1628
2043
|
function getCachedCredentials(cache, cacheKey) {
|
|
1629
2044
|
const localCached = cache.get(cacheKey);
|
|
@@ -1814,10 +2229,40 @@ function parseLoginTicketResponse(xml) {
|
|
|
1814
2229
|
};
|
|
1815
2230
|
}
|
|
1816
2231
|
|
|
2232
|
+
// src/wsaa/store-adapter.ts
|
|
2233
|
+
function createWsaaStoreAdapter(store) {
|
|
2234
|
+
const key = (value) => `arca:v1:wsaa:${serializeWsaaSessionKey(value)}`;
|
|
2235
|
+
const remove = store.delete?.bind(store);
|
|
2236
|
+
const lock = store.withLock?.bind(store);
|
|
2237
|
+
return {
|
|
2238
|
+
get: (value) => storeCall(async () => {
|
|
2239
|
+
const json = await store.get(key(value));
|
|
2240
|
+
if (json === null) {
|
|
2241
|
+
return null;
|
|
2242
|
+
}
|
|
2243
|
+
const credentials = JSON.parse(json);
|
|
2244
|
+
return credentials && typeof credentials.token === "string" && typeof credentials.sign === "string" && isWsaaCredentialValid(credentials) ? credentials : null;
|
|
2245
|
+
}),
|
|
2246
|
+
set: (value, credentials) => storeCall(() => store.set(key(value), JSON.stringify(credentials))),
|
|
2247
|
+
...remove ? {
|
|
2248
|
+
delete: (value) => storeCall(() => remove(key(value)))
|
|
2249
|
+
} : {},
|
|
2250
|
+
...lock ? {
|
|
2251
|
+
withLock: (value, fn) => lock(key(value), fn)
|
|
2252
|
+
} : {}
|
|
2253
|
+
};
|
|
2254
|
+
}
|
|
2255
|
+
|
|
1817
2256
|
// src/client.ts
|
|
1818
|
-
function createArcaClient(config) {
|
|
1819
|
-
|
|
1820
|
-
|
|
2257
|
+
function createArcaClient(config = {}) {
|
|
2258
|
+
const discovered = discoverArcaClientConfig(config);
|
|
2259
|
+
assertArcaClientConfig(discovered);
|
|
2260
|
+
const normalizedConfig = normalizeArcaClientConfig(discovered);
|
|
2261
|
+
if (normalizedConfig.store && !normalizedConfig.wsaaSessionStore) {
|
|
2262
|
+
normalizedConfig.wsaaSessionStore = createWsaaStoreAdapter(
|
|
2263
|
+
normalizedConfig.store
|
|
2264
|
+
);
|
|
2265
|
+
}
|
|
1821
2266
|
const logger = createArcaLogger(normalizedConfig.logger);
|
|
1822
2267
|
const auth = createWsaaAuthModule({ config: normalizedConfig, logger });
|
|
1823
2268
|
const soap = createSoapTransport({ config: normalizedConfig, logger });
|
|
@@ -1829,14 +2274,176 @@ function createArcaClient(config) {
|
|
|
1829
2274
|
retryDelay: normalizedConfig.retryDelay
|
|
1830
2275
|
});
|
|
1831
2276
|
const wsfe = createWsfeService({ config: normalizedConfig, auth, soap });
|
|
2277
|
+
const vouchers = createVouchersService(wsfe, normalizedConfig);
|
|
1832
2278
|
return {
|
|
1833
2279
|
config: publicConfig,
|
|
2280
|
+
issue: vouchers.issue,
|
|
2281
|
+
cancel: vouchers.cancel,
|
|
1834
2282
|
wsfe,
|
|
1835
|
-
vouchers: createVouchersService(wsfe),
|
|
1836
2283
|
wsmtxca: createWsmtxcaService({ config: normalizedConfig, auth, soap }),
|
|
1837
2284
|
padron: createPadronService({ config: normalizedConfig, auth, soap })
|
|
1838
2285
|
};
|
|
1839
2286
|
}
|
|
2287
|
+
|
|
2288
|
+
// src/store/file.ts
|
|
2289
|
+
import { createHash as createHash3, randomUUID } from "crypto";
|
|
2290
|
+
import { mkdir, readFile, rename, unlink, writeFile } from "fs/promises";
|
|
2291
|
+
import { join } from "path";
|
|
2292
|
+
function createFileStore(directory) {
|
|
2293
|
+
const path = (key) => join(directory, createHash3("sha256").update(key).digest("hex"));
|
|
2294
|
+
const ensure = () => mkdir(directory, { recursive: true, mode: 448 });
|
|
2295
|
+
return {
|
|
2296
|
+
get: (key) => storeCall(async () => {
|
|
2297
|
+
try {
|
|
2298
|
+
return await readFile(path(key), "utf8");
|
|
2299
|
+
} catch (error) {
|
|
2300
|
+
if (error.code === "ENOENT") {
|
|
2301
|
+
return null;
|
|
2302
|
+
}
|
|
2303
|
+
throw error;
|
|
2304
|
+
}
|
|
2305
|
+
}),
|
|
2306
|
+
set: (key, value) => storeCall(async () => {
|
|
2307
|
+
await ensure();
|
|
2308
|
+
const temporary = `${path(key)}.${randomUUID()}.tmp`;
|
|
2309
|
+
try {
|
|
2310
|
+
await writeFile(temporary, value, { mode: 384, flag: "wx" });
|
|
2311
|
+
await rename(temporary, path(key));
|
|
2312
|
+
} finally {
|
|
2313
|
+
await unlink(temporary).catch(() => void 0);
|
|
2314
|
+
}
|
|
2315
|
+
}),
|
|
2316
|
+
add: (key, value) => storeCall(async () => {
|
|
2317
|
+
await ensure();
|
|
2318
|
+
try {
|
|
2319
|
+
await writeFile(path(key), value, { flag: "wx", mode: 384 });
|
|
2320
|
+
return true;
|
|
2321
|
+
} catch (error) {
|
|
2322
|
+
if (error.code === "EEXIST") {
|
|
2323
|
+
return false;
|
|
2324
|
+
}
|
|
2325
|
+
throw error;
|
|
2326
|
+
}
|
|
2327
|
+
}),
|
|
2328
|
+
delete: (key) => storeCall(async () => {
|
|
2329
|
+
try {
|
|
2330
|
+
await unlink(path(key));
|
|
2331
|
+
} catch (error) {
|
|
2332
|
+
if (error.code !== "ENOENT") {
|
|
2333
|
+
throw error;
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
})
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
// src/store/memory.ts
|
|
2341
|
+
function createMemoryStore() {
|
|
2342
|
+
const values = /* @__PURE__ */ new Map();
|
|
2343
|
+
const locks = /* @__PURE__ */ new Map();
|
|
2344
|
+
return {
|
|
2345
|
+
get: (key) => Promise.resolve(values.get(key) ?? null),
|
|
2346
|
+
set(key, value) {
|
|
2347
|
+
values.set(key, value);
|
|
2348
|
+
return Promise.resolve();
|
|
2349
|
+
},
|
|
2350
|
+
add(key, value) {
|
|
2351
|
+
if (values.has(key)) {
|
|
2352
|
+
return Promise.resolve(false);
|
|
2353
|
+
}
|
|
2354
|
+
values.set(key, value);
|
|
2355
|
+
return Promise.resolve(true);
|
|
2356
|
+
},
|
|
2357
|
+
delete(key) {
|
|
2358
|
+
values.delete(key);
|
|
2359
|
+
return Promise.resolve();
|
|
2360
|
+
},
|
|
2361
|
+
async withLock(key, fn) {
|
|
2362
|
+
const previous = locks.get(key) ?? Promise.resolve();
|
|
2363
|
+
let release = () => void 0;
|
|
2364
|
+
const current = new Promise((resolve) => {
|
|
2365
|
+
release = resolve;
|
|
2366
|
+
});
|
|
2367
|
+
const queued = previous.catch(() => void 0).then(() => current);
|
|
2368
|
+
locks.set(key, queued);
|
|
2369
|
+
await previous.catch(() => void 0);
|
|
2370
|
+
try {
|
|
2371
|
+
return await fn();
|
|
2372
|
+
} finally {
|
|
2373
|
+
release();
|
|
2374
|
+
if (locks.get(key) === queued) {
|
|
2375
|
+
locks.delete(key);
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
};
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2382
|
+
// src/store/postgres.ts
|
|
2383
|
+
function createPostgresStore({
|
|
2384
|
+
query,
|
|
2385
|
+
table = "arca_store"
|
|
2386
|
+
}) {
|
|
2387
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(table)) {
|
|
2388
|
+
throw new ArcaConfigurationError("Invalid ARCA store table identifier.");
|
|
2389
|
+
}
|
|
2390
|
+
const name = `"${table}"`;
|
|
2391
|
+
const run = (text, params) => storeCall(async () => {
|
|
2392
|
+
const result = await query(text, params);
|
|
2393
|
+
return Array.isArray(result) ? result : result.rows;
|
|
2394
|
+
});
|
|
2395
|
+
return {
|
|
2396
|
+
async get(key) {
|
|
2397
|
+
return (await run(`SELECT value FROM ${name} WHERE key = $1`, [key]))[0]?.value ?? null;
|
|
2398
|
+
},
|
|
2399
|
+
async set(key, value) {
|
|
2400
|
+
await run(
|
|
2401
|
+
`INSERT INTO ${name} (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = now()`,
|
|
2402
|
+
[key, value]
|
|
2403
|
+
);
|
|
2404
|
+
},
|
|
2405
|
+
async add(key, value) {
|
|
2406
|
+
return (await run(
|
|
2407
|
+
`INSERT INTO ${name} (key, value) VALUES ($1, $2) ON CONFLICT DO NOTHING RETURNING key`,
|
|
2408
|
+
[key, value]
|
|
2409
|
+
)).length > 0;
|
|
2410
|
+
},
|
|
2411
|
+
async delete(key) {
|
|
2412
|
+
await run(`DELETE FROM ${name} WHERE key = $1`, [key]);
|
|
2413
|
+
}
|
|
2414
|
+
};
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
// src/store/redis.ts
|
|
2418
|
+
function createRedisStore(client, options = {}) {
|
|
2419
|
+
const flavor = options.flavor ?? (typeof client.call === "function" ? "ioredis" : "upstash");
|
|
2420
|
+
if (flavor === "ioredis" && !client.call) {
|
|
2421
|
+
throw new ArcaConfigurationError(
|
|
2422
|
+
"ARCA Redis ioredis flavor requires client.call."
|
|
2423
|
+
);
|
|
2424
|
+
}
|
|
2425
|
+
const upstashSet = client.set;
|
|
2426
|
+
return {
|
|
2427
|
+
get: (key) => storeCall(async () => {
|
|
2428
|
+
const value = await client.get(key);
|
|
2429
|
+
if (value === null) {
|
|
2430
|
+
return null;
|
|
2431
|
+
}
|
|
2432
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
2433
|
+
}),
|
|
2434
|
+
set: (key, value) => storeCall(async () => {
|
|
2435
|
+
await client.set(key, value);
|
|
2436
|
+
}),
|
|
2437
|
+
add: (key, value) => storeCall(
|
|
2438
|
+
async () => (flavor === "ioredis" ? await client.call?.("SET", key, value, "NX") : await upstashSet.call(client, key, value, { nx: true })) === "OK"
|
|
2439
|
+
),
|
|
2440
|
+
...client.del ? {
|
|
2441
|
+
delete: (key) => storeCall(async () => {
|
|
2442
|
+
await client.del?.(key);
|
|
2443
|
+
})
|
|
2444
|
+
} : {}
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
1840
2447
|
export {
|
|
1841
2448
|
ARCA_ENVIRONMENTS,
|
|
1842
2449
|
ARCA_ENV_VARIABLES,
|
|
@@ -1857,8 +2464,12 @@ export {
|
|
|
1857
2464
|
buildFacturaC,
|
|
1858
2465
|
createArcaClient,
|
|
1859
2466
|
createArcaClientConfigFromEnv,
|
|
2467
|
+
createFileStore,
|
|
2468
|
+
createMemoryStore,
|
|
1860
2469
|
createMemoryWsaaSessionStore,
|
|
1861
2470
|
createPadronService,
|
|
2471
|
+
createPostgresStore,
|
|
2472
|
+
createRedisStore,
|
|
1862
2473
|
createWsfeService,
|
|
1863
2474
|
createWsmtxcaService,
|
|
1864
2475
|
isArcaAuthenticationError,
|