facturas 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,7 +11,7 @@ import {
11
11
  normalizeWsfeDateInput,
12
12
  normalizeWsfeVoucherInput,
13
13
  serializeArcaExchangeRate
14
- } from "./chunk-5KCTRQJX.mjs";
14
+ } from "./chunk-QHYJ424J.mjs";
15
15
  import {
16
16
  ARCA_CURRENCY_IDS,
17
17
  ARCA_DOCUMENT_TYPES,
@@ -23,7 +23,7 @@ import {
23
23
  } from "./chunk-GX5ERXT6.mjs";
24
24
  import {
25
25
  createWsmtxcaService
26
- } from "./chunk-ALH5IPSY.mjs";
26
+ } from "./chunk-D5ZWXTCV.mjs";
27
27
  import {
28
28
  ArcaConfigurationError,
29
29
  ArcaError,
@@ -278,6 +278,15 @@ import { createHash } from "crypto";
278
278
  function attemptKey(environment, taxId, key) {
279
279
  return `arca:v1:attempt:${environment}:${taxId}:${key}`;
280
280
  }
281
+ function sequenceKey(environment, taxId, salesPoint, voucherType) {
282
+ return `arca:v1:sequence:${environment}:${taxId}:${salesPoint}:${voucherType}`;
283
+ }
284
+ function sequenceLockKey(environment, taxId, salesPoint, voucherType) {
285
+ return `arca:v1:lock:sequence:${environment}:${taxId}:${salesPoint}:${voucherType}`;
286
+ }
287
+ function settledKey(environment, taxId, key) {
288
+ return `arca:v1:settled:${environment}:${taxId}:${key}`;
289
+ }
281
290
  function canonicalHash(input) {
282
291
  return createHash("sha256").update(JSON.stringify(canonical(input))).digest("hex");
283
292
  }
@@ -723,6 +732,27 @@ function isWsaaCredentialValid(credentials) {
723
732
  import { createHash as createHash2 } from "crypto";
724
733
  import forge from "node-forge";
725
734
 
735
+ // src/internal/abort.ts
736
+ function abortedError(signal) {
737
+ return new ArcaTransportError("The ARCA call was aborted", {
738
+ cause: signal?.reason
739
+ });
740
+ }
741
+ function abortable(promise, signal) {
742
+ if (!signal) {
743
+ return promise;
744
+ }
745
+ if (signal.aborted) {
746
+ promise.catch(() => void 0);
747
+ return Promise.reject(abortedError(signal));
748
+ }
749
+ return new Promise((resolve, reject) => {
750
+ const onAbort = () => reject(abortedError(signal));
751
+ signal.addEventListener("abort", onAbort, { once: true });
752
+ promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
753
+ });
754
+ }
755
+
726
756
  // src/internal/http.ts
727
757
  import https from "https";
728
758
  var defaultAgent = new https.Agent({
@@ -743,7 +773,8 @@ async function postXmlWithMetadata({
743
773
  retryDelay = 500,
744
774
  logger,
745
775
  service,
746
- operation
776
+ operation,
777
+ signal
747
778
  }) {
748
779
  const totalAttempts = retries + 1;
749
780
  for (let attempt = 1; attempt <= totalAttempts; attempt += 1) {
@@ -754,13 +785,14 @@ async function postXmlWithMetadata({
754
785
  contentType,
755
786
  soapAction,
756
787
  useLegacyTlsSecurityLevel0,
757
- timeout
788
+ timeout,
789
+ signal
758
790
  });
759
791
  } catch (error) {
760
792
  if (!(error instanceof ArcaTransportError)) {
761
793
  throw error;
762
794
  }
763
- if (attempt >= totalAttempts) {
795
+ if (attempt >= totalAttempts || signal?.aborted) {
764
796
  logger?.error("ARCA transport request failed", {
765
797
  service,
766
798
  operation,
@@ -794,10 +826,16 @@ async function postXmlOnce({
794
826
  contentType,
795
827
  soapAction,
796
828
  useLegacyTlsSecurityLevel0,
797
- timeout
829
+ timeout,
830
+ signal
798
831
  }) {
799
832
  const endpoint = new URL(url);
800
833
  const requestBody = Buffer.from(body, "utf8");
834
+ if (signal?.aborted) {
835
+ throw new ArcaTransportError("ARCA HTTP request was aborted", {
836
+ cause: signal.reason
837
+ });
838
+ }
801
839
  return await new Promise((resolve, reject) => {
802
840
  let settled = false;
803
841
  const settleResolve = (response) => {
@@ -908,6 +946,15 @@ async function postXmlOnce({
908
946
  })
909
947
  );
910
948
  });
949
+ const abort = () => {
950
+ const cause = new Error("ARCA HTTP request was aborted");
951
+ settleReject(
952
+ new ArcaTransportError("ARCA HTTP request was aborted", { cause })
953
+ );
954
+ request.destroy(cause);
955
+ };
956
+ signal?.addEventListener("abort", abort, { once: true });
957
+ request.on("close", () => signal?.removeEventListener("abort", abort));
911
958
  request.write(requestBody);
912
959
  request.end();
913
960
  });
@@ -1143,34 +1190,37 @@ function createWsaaAuthModule(options) {
1143
1190
  }
1144
1191
  return {
1145
1192
  login(service, authOptions = {}) {
1146
- const sessionKey = buildWsaaSessionKey(options.config, service);
1147
- const cacheKey = serializeWsaaSessionKey(sessionKey);
1148
- if (authOptions.forceRefresh) {
1149
- const runningForced2 = forcedInFlight.get(cacheKey);
1150
- if (runningForced2) {
1151
- return runningForced2;
1152
- }
1153
- const runningOrdinary2 = ordinaryInFlight.get(cacheKey);
1154
- return trackLogin(forcedInFlight, cacheKey, async () => {
1155
- await runningOrdinary2?.catch(() => void 0);
1156
- return await performLogin(service, sessionKey, cacheKey, true);
1157
- });
1158
- }
1159
- const runningOrdinary = ordinaryInFlight.get(cacheKey);
1160
- if (runningOrdinary) {
1161
- return runningOrdinary;
1162
- }
1163
- const runningForced = forcedInFlight.get(cacheKey);
1164
- if (runningForced) {
1165
- return runningForced;
1166
- }
1167
- return trackLogin(
1168
- ordinaryInFlight,
1169
- cacheKey,
1170
- () => performLogin(service, sessionKey, cacheKey, false)
1171
- );
1193
+ return abortable(runLogin(service, authOptions), authOptions.signal);
1172
1194
  }
1173
1195
  };
1196
+ function runLogin(service, authOptions) {
1197
+ const sessionKey = buildWsaaSessionKey(options.config, service);
1198
+ const cacheKey = serializeWsaaSessionKey(sessionKey);
1199
+ if (authOptions.forceRefresh) {
1200
+ const runningForced2 = forcedInFlight.get(cacheKey);
1201
+ if (runningForced2) {
1202
+ return runningForced2;
1203
+ }
1204
+ const runningOrdinary2 = ordinaryInFlight.get(cacheKey);
1205
+ return trackLogin(forcedInFlight, cacheKey, async () => {
1206
+ await runningOrdinary2?.catch(() => void 0);
1207
+ return await performLogin(service, sessionKey, cacheKey, true);
1208
+ });
1209
+ }
1210
+ const runningOrdinary = ordinaryInFlight.get(cacheKey);
1211
+ if (runningOrdinary) {
1212
+ return runningOrdinary;
1213
+ }
1214
+ const runningForced = forcedInFlight.get(cacheKey);
1215
+ if (runningForced) {
1216
+ return runningForced;
1217
+ }
1218
+ return trackLogin(
1219
+ ordinaryInFlight,
1220
+ cacheKey,
1221
+ () => performLogin(service, sessionKey, cacheKey, false)
1222
+ );
1223
+ }
1174
1224
  }
1175
1225
  async function requestCredentials(config, service, options) {
1176
1226
  const loginTicketRequestXml = buildLoginTicketRequest(service);
@@ -2094,6 +2144,7 @@ function createWsmtxcaIssuanceService(wsmtxca) {
2094
2144
  issue: (input) => wsmtxca.issue({
2095
2145
  representedTaxId: input.representedTaxId,
2096
2146
  forceRefresh: input.forceRefresh,
2147
+ signal: input.signal,
2097
2148
  data: wsmtxcaRequest(input.data, input.voucherNumber)
2098
2149
  }),
2099
2150
  lookupVoucher: async (input) => {
@@ -2821,7 +2872,8 @@ function createVouchersService(wsfe, context, wsmtxca) {
2821
2872
  input,
2822
2873
  options ?? {},
2823
2874
  context,
2824
- "debitNote"
2875
+ "debitNote",
2876
+ select
2825
2877
  ),
2826
2878
  previewCreditNote: async (input, options) => previewNote(
2827
2879
  select(options),
@@ -2842,7 +2894,9 @@ function createVouchersService(wsfe, context, wsmtxca) {
2842
2894
  select(options),
2843
2895
  input,
2844
2896
  options === void 0 ? {} : options,
2845
- context
2897
+ context,
2898
+ "creditNote",
2899
+ select
2846
2900
  );
2847
2901
  return result;
2848
2902
  },
@@ -2851,7 +2905,8 @@ function createVouchersService(wsfe, context, wsmtxca) {
2851
2905
  select(options),
2852
2906
  input,
2853
2907
  options === void 0 ? {} : options,
2854
- context
2908
+ context,
2909
+ select
2855
2910
  );
2856
2911
  return result;
2857
2912
  },
@@ -2904,8 +2959,8 @@ function toPreview(prepared, options) {
2904
2959
  ...options.service === "wsmtxca" ? { service: "wsmtxca" } : {}
2905
2960
  };
2906
2961
  }
2907
- async function issueInvoice(wsfe, input, inputOptions, context) {
2908
- const options = structuredClone(inputOptions);
2962
+ async function issueInvoice(wsfe, input, inputOptions, context, select) {
2963
+ const options = cloneOptions(inputOptions);
2909
2964
  validateOptions(options);
2910
2965
  validateKeyStore(options, context);
2911
2966
  const prepared = prepareInvoice(input, options);
@@ -2916,19 +2971,18 @@ async function issueInvoice(wsfe, input, inputOptions, context) {
2916
2971
  () => Promise.resolve(prepared),
2917
2972
  options,
2918
2973
  context,
2919
- prepared.amounts
2974
+ prepared.amounts,
2975
+ select
2920
2976
  );
2921
2977
  }
2922
- async function runOperation(wsfe, operation, input, prepare, options, context, replayAmounts) {
2923
- const store = context?.store;
2924
- if (options.idempotencyKey === void 0 || !store || !context) {
2978
+ async function runOperation(wsfe, operation, input, prepare, options, context, replayAmounts, select) {
2979
+ if (options.idempotencyKey === void 0 || !context?.store) {
2925
2980
  return runAuthorization(wsfe, await prepare(), options);
2926
2981
  }
2927
- const key = attemptKey(
2928
- context.environment,
2929
- context.taxId,
2930
- options.idempotencyKey
2931
- );
2982
+ const store = context.store;
2983
+ const { environment, taxId } = context;
2984
+ const idempotencyKey = options.idempotencyKey;
2985
+ const key = attemptKey(environment, taxId, idempotencyKey);
2932
2986
  const representedTaxId = options.representedTaxId === void 0 ? void 0 : String(options.representedTaxId);
2933
2987
  const inputHash = canonicalHash({
2934
2988
  input,
@@ -2936,37 +2990,107 @@ async function runOperation(wsfe, operation, input, prepare, options, context, r
2936
2990
  ...options.service === "wsmtxca" ? { service: "wsmtxca" } : {},
2937
2991
  ...options.number === void 0 ? {} : { number: options.number }
2938
2992
  });
2993
+ const settled = settledKey(environment, taxId, idempotencyKey);
2939
2994
  const existing = await storeCall(() => store.get(key));
2940
2995
  if (existing !== null) {
2941
- return replay(existing);
2996
+ return await replay(existing);
2942
2997
  }
2943
2998
  const prepared = await prepare();
2944
- const number = await nextNumber(wsfe, prepared.data, options);
2945
- const service = options.service ?? "wsfe";
2946
- const versioned = service === "wsmtxca" || prepared.data.details !== void 0;
2947
- const record = {
2948
- v: versioned ? 2 : 1,
2949
- operation,
2950
- ...versioned ? { service } : {},
2951
- representedTaxId,
2952
- inputHash,
2953
- number,
2954
- salesPoint: prepared.data.salesPoint,
2955
- voucherType: prepared.data.voucherType,
2956
- sent: prepared.data,
2957
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
2999
+ const sequence = {
3000
+ coordinates: {
3001
+ salesPoint: prepared.data.salesPoint,
3002
+ voucherType: prepared.data.voucherType
3003
+ },
3004
+ // The sequence belongs to the taxpayer whose numbering ARCA advances.
3005
+ taxId: String(options.representedTaxId ?? context.taxId)
2958
3006
  };
2959
- if (await storeCall(() => store.add(key, JSON.stringify(record)))) {
2960
- return runAuthorization(wsfe, prepared, options, number);
3007
+ const sequenceRecord = sequenceKey(
3008
+ environment,
3009
+ sequence.taxId,
3010
+ sequence.coordinates.salesPoint,
3011
+ sequence.coordinates.voucherType
3012
+ );
3013
+ if (!store.withLock) {
3014
+ return await claim();
3015
+ }
3016
+ return await store.withLock(
3017
+ sequenceLockKey(
3018
+ environment,
3019
+ sequence.taxId,
3020
+ sequence.coordinates.salesPoint,
3021
+ sequence.coordinates.voucherType
3022
+ ),
3023
+ async () => {
3024
+ const barrier = await runSequenceBarrier({
3025
+ store,
3026
+ environment,
3027
+ taxId,
3028
+ sequence: sequenceRecord,
3029
+ coordinates: sequence.coordinates,
3030
+ select: select ?? (() => wsfe),
3031
+ options,
3032
+ supersededBy: idempotencyKey,
3033
+ readNext: () => nextNumber(wsfe, prepared.data, options)
3034
+ });
3035
+ return "blocked" in barrier ? barrier.blocked : await claim(barrier.reserved);
3036
+ }
3037
+ );
3038
+ function reservation(number) {
3039
+ const service = options.service ?? "wsfe";
3040
+ const versioned = service === "wsmtxca" || prepared.data.details !== void 0;
3041
+ return {
3042
+ v: versioned ? 2 : 1,
3043
+ operation,
3044
+ ...versioned ? { service } : {},
3045
+ representedTaxId,
3046
+ inputHash,
3047
+ number,
3048
+ salesPoint: prepared.data.salesPoint,
3049
+ voucherType: prepared.data.voucherType,
3050
+ sent: prepared.data,
3051
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
3052
+ };
2961
3053
  }
2962
- const winner = await storeCall(() => store.get(key));
2963
- if (winner === null) {
2964
- throw new ArcaConfigurationError(
2965
- "ARCA reservation disappeared after atomic creation lost."
2966
- );
3054
+ async function claim(reserved) {
3055
+ const coordinated = store.withLock !== void 0;
3056
+ const prior = coordinated ? await storeCall(() => store.get(key)) : null;
3057
+ if (prior !== null) {
3058
+ return await replay(prior, true);
3059
+ }
3060
+ const number = options.number ?? reserved ?? await nextNumber(wsfe, prepared.data, options);
3061
+ const record = reservation(number);
3062
+ const claimed = {
3063
+ v: 1,
3064
+ key: idempotencyKey,
3065
+ number,
3066
+ claimedAt: (/* @__PURE__ */ new Date()).toISOString()
3067
+ };
3068
+ if (coordinated) {
3069
+ await storeCall(() => store.set(sequenceRecord, JSON.stringify(claimed)));
3070
+ }
3071
+ if (await storeCall(() => store.add(key, JSON.stringify(record)))) {
3072
+ const outcome = await settle(
3073
+ runAuthorization(wsfe, prepared, options, number)
3074
+ );
3075
+ if (coordinated && outcome.kind !== "indeterminate") {
3076
+ await storeCall(
3077
+ () => store.set(
3078
+ sequenceRecord,
3079
+ JSON.stringify({ ...claimed, resolvedAt: (/* @__PURE__ */ new Date()).toISOString() })
3080
+ )
3081
+ );
3082
+ }
3083
+ return outcome;
3084
+ }
3085
+ const winner = await storeCall(() => store.get(key));
3086
+ if (winner === null) {
3087
+ throw new ArcaConfigurationError(
3088
+ "ARCA reservation disappeared after atomic creation lost."
3089
+ );
3090
+ }
3091
+ return await replay(winner, true);
2967
3092
  }
2968
- return replay(winner);
2969
- function replay(json) {
3093
+ async function replay(json, locked = false) {
2970
3094
  const stored = readRecord(json);
2971
3095
  if ((stored.service ?? "wsfe") !== (options.service ?? "wsfe") || stored.operation !== operation || stored.inputHash !== inputHash || stored.representedTaxId !== representedTaxId) {
2972
3096
  throw new ArcaInputError(
@@ -2977,17 +3101,107 @@ async function runOperation(wsfe, operation, input, prepare, options, context, r
2977
3101
  }
2978
3102
  );
2979
3103
  }
2980
- return runAuthorization(
2981
- wsfe,
2982
- {
2983
- ...preparedFromRecord(stored),
2984
- ...replayAmounts ? { amounts: replayAmounts } : {}
2985
- },
2986
- options,
2987
- stored.number,
2988
- true
3104
+ const consult = async () => {
3105
+ const recorded = await storeCall(() => store.get(settled));
3106
+ if (recorded !== null) {
3107
+ return await settledOutcome(
3108
+ select ?? (() => wsfe),
3109
+ stored,
3110
+ readSettledRecord(recorded),
3111
+ options
3112
+ );
3113
+ }
3114
+ return await settle(
3115
+ runAuthorization(
3116
+ wsfe,
3117
+ {
3118
+ ...preparedFromRecord(stored),
3119
+ ...replayAmounts ? { amounts: replayAmounts } : {}
3120
+ },
3121
+ options,
3122
+ stored.number,
3123
+ true
3124
+ )
3125
+ );
3126
+ };
3127
+ if (locked || !store.withLock) {
3128
+ return await consult();
3129
+ }
3130
+ return await store.withLock(
3131
+ sequenceLockKey(
3132
+ environment,
3133
+ stored.representedTaxId ?? taxId,
3134
+ stored.salesPoint,
3135
+ stored.voucherType
3136
+ ),
3137
+ consult
2989
3138
  );
2990
3139
  }
3140
+ async function settle(running) {
3141
+ return await recordConflict(store, settled, await running);
3142
+ }
3143
+ }
3144
+ async function recordConflict(store, key, outcome) {
3145
+ if (outcome.kind !== "conflict") {
3146
+ return outcome;
3147
+ }
3148
+ const record = {
3149
+ v: 1,
3150
+ kind: "conflict",
3151
+ number: outcome.attempted.number,
3152
+ found: Object.fromEntries(
3153
+ Object.entries(outcome.found).filter(([field]) => field !== "raw")
3154
+ ),
3155
+ settledAt: (/* @__PURE__ */ new Date()).toISOString()
3156
+ };
3157
+ await storeCall(() => store.add(key, JSON.stringify(record)));
3158
+ return outcome;
3159
+ }
3160
+ function readSettledRecord(json) {
3161
+ try {
3162
+ const record = JSON.parse(json);
3163
+ if (!record || record.v !== 1 || !Number.isSafeInteger(record.number) || (record.kind === "conflict" ? !record.found || typeof record.found !== "object" : record.kind !== "superseded" || typeof record.by !== "string")) {
3164
+ throw new Error("Invalid settled structure");
3165
+ }
3166
+ return record;
3167
+ } catch (cause) {
3168
+ throw new ArcaConfigurationError(
3169
+ "Invalid ARCA settled record; preserve it for reconciliation.",
3170
+ { cause }
3171
+ );
3172
+ }
3173
+ }
3174
+ async function settledOutcome(select, reservation, settled, options) {
3175
+ if (settled.kind === "conflict") {
3176
+ return settledConflict(settled, reservation);
3177
+ }
3178
+ const outcome = await consultReservation(select, reservation, options, true);
3179
+ if (outcome.kind === "indeterminate" && outcome.lookup.kind === "not_found") {
3180
+ return {
3181
+ kind: "indeterminate",
3182
+ attempted: {
3183
+ salesPoint: reservation.salesPoint,
3184
+ voucherType: reservation.voucherType,
3185
+ number: settled.number
3186
+ },
3187
+ attempt: replayEvidence(reservation.service),
3188
+ lookup: { kind: "superseded", by: settled.by }
3189
+ };
3190
+ }
3191
+ return outcome;
3192
+ }
3193
+ function settledConflict(settled, reservation) {
3194
+ return {
3195
+ kind: "conflict",
3196
+ attempted: {
3197
+ salesPoint: reservation.salesPoint,
3198
+ voucherType: reservation.voucherType,
3199
+ number: settled.number
3200
+ },
3201
+ attempt: replayEvidence(reservation.service),
3202
+ found: settled.found,
3203
+ reason: "This key already recorded another voucher at the reserved number. Reconcile before issuing under a new key."
3204
+ };
2991
3205
  }
2992
3206
  function validateKeyStore(options, context) {
2993
3207
  if (options.idempotencyKey === void 0) {
@@ -3043,6 +3257,7 @@ async function nextNumber(wsfe, data, options) {
3043
3257
  const number = await wsfe.getNextVoucherNumber({
3044
3258
  representedTaxId: options.representedTaxId,
3045
3259
  forceRefresh: options.forceRefresh,
3260
+ ...options.signal === void 0 ? {} : { signal: options.signal },
3046
3261
  salesPoint: data.salesPoint,
3047
3262
  voucherType: data.voucherType
3048
3263
  });
@@ -3060,7 +3275,8 @@ async function nextNumber(wsfe, data, options) {
3060
3275
  async function runAuthorization(wsfe, { data, voucherClass, amounts }, options, reservedNumber, replay = false) {
3061
3276
  const auth = {
3062
3277
  representedTaxId: options.representedTaxId,
3063
- forceRefresh: options.forceRefresh
3278
+ forceRefresh: options.forceRefresh,
3279
+ ...options.signal === void 0 ? {} : { signal: options.signal }
3064
3280
  };
3065
3281
  const includeRaw = options.include?.raw === true;
3066
3282
  const includeExact = options.include?.exactInput === true;
@@ -3099,7 +3315,7 @@ async function runAuthorization(wsfe, { data, voucherClass, amounts }, options,
3099
3315
  kind: "indeterminate",
3100
3316
  attempted,
3101
3317
  attempt: attempt2,
3102
- lookup: { kind: "failed", error: toArcaSafeErrorMetadata(error) }
3318
+ lookup: options.signal?.aborted ? { kind: "aborted" } : { kind: "failed", error: toArcaSafeErrorMetadata(error) }
3103
3319
  };
3104
3320
  }
3105
3321
  if (lookup.kind === "found") {
@@ -3121,32 +3337,13 @@ async function runAuthorization(wsfe, { data, voucherClass, amounts }, options,
3121
3337
  };
3122
3338
  }
3123
3339
  if (authorization.kind === "rejected") {
3124
- if (options.idempotencyKey !== void 0 && (options.service === "wsmtxca" || [...authorization.errors, ...authorization.observations].some(
3125
- (issue) => issue.code === "10016"
3126
- ))) {
3127
- const recovered = await recoverInvoice({
3128
- ...recovery,
3129
- attempt: projectEvidence(
3130
- {
3131
- ...authorization,
3132
- kind: "indeterminate",
3133
- reason: "contradictory_response"
3134
- },
3135
- includeRaw
3136
- )
3137
- });
3138
- if (recovered.kind === "authorized" || recovered.kind === "conflict") {
3139
- return recovered;
3140
- }
3141
- }
3142
- return {
3143
- kind: "rejected",
3144
- attempted,
3145
- issues: [...authorization.errors, ...authorization.observations].map(
3146
- projectIssue
3147
- ),
3148
- authorization: projectEvidence(authorization, includeRaw)
3149
- };
3340
+ return await resolveRejection(
3341
+ authorization,
3342
+ recovery,
3343
+ options,
3344
+ includeRaw,
3345
+ replay
3346
+ );
3150
3347
  }
3151
3348
  const uncertain = authorization.kind === "indeterminate" ? authorization : {
3152
3349
  ...authorization,
@@ -3166,6 +3363,33 @@ async function runAuthorization(wsfe, { data, voucherClass, amounts }, options,
3166
3363
  voucher
3167
3364
  });
3168
3365
  }
3366
+ async function resolveRejection(authorization, recovery, options, includeRaw, replay) {
3367
+ const issues = [...authorization.errors, ...authorization.observations];
3368
+ const consult = options.idempotencyKey !== void 0 && (options.service === "wsmtxca" || issues.some((issue) => issue.code === "10016"));
3369
+ if (consult) {
3370
+ const recovered = await recoverInvoice({
3371
+ ...recovery,
3372
+ attempt: projectEvidence(
3373
+ {
3374
+ ...authorization,
3375
+ kind: "indeterminate",
3376
+ reason: "contradictory_response"
3377
+ },
3378
+ includeRaw
3379
+ ),
3380
+ strangerAtNumber: !replay
3381
+ });
3382
+ if (recovered.kind === "authorized" || recovered.kind === "conflict") {
3383
+ return recovered;
3384
+ }
3385
+ }
3386
+ return {
3387
+ kind: "rejected",
3388
+ attempted: recovery.attempted,
3389
+ issues: issues.map(projectIssue),
3390
+ authorization: projectEvidence(authorization, includeRaw)
3391
+ };
3392
+ }
3169
3393
  async function recoverInvoice({
3170
3394
  wsfe,
3171
3395
  auth,
@@ -3176,8 +3400,17 @@ async function recoverInvoice({
3176
3400
  exact,
3177
3401
  voucher,
3178
3402
  lookup: suppliedLookup,
3179
- service
3403
+ service,
3404
+ strangerAtNumber = false
3180
3405
  }) {
3406
+ if (suppliedLookup === void 0 && auth.signal?.aborted) {
3407
+ return {
3408
+ kind: "indeterminate",
3409
+ attempted,
3410
+ attempt,
3411
+ lookup: { kind: "aborted" }
3412
+ };
3413
+ }
3181
3414
  let lookup;
3182
3415
  try {
3183
3416
  lookup = suppliedLookup ?? await wsfe.lookupVoucher({ ...auth, ...attempted });
@@ -3186,7 +3419,7 @@ async function recoverInvoice({
3186
3419
  kind: "indeterminate",
3187
3420
  attempted,
3188
3421
  attempt,
3189
- lookup: { kind: "failed", error: toArcaSafeErrorMetadata(error) }
3422
+ lookup: auth.signal?.aborted ? { kind: "aborted" } : { kind: "failed", error: toArcaSafeErrorMetadata(error) }
3190
3423
  };
3191
3424
  }
3192
3425
  const raw = includeRaw ? { raw: lookup.raw } : {};
@@ -3198,6 +3431,15 @@ async function recoverInvoice({
3198
3431
  lookup: { kind: "not_found", ...raw }
3199
3432
  };
3200
3433
  }
3434
+ if (strangerAtNumber) {
3435
+ return {
3436
+ kind: "conflict",
3437
+ attempted,
3438
+ attempt,
3439
+ found: { ...toVoucherSummary(lookup.voucher), ...raw },
3440
+ reason: "ARCA refused the number this call reserved and another voucher occupies it"
3441
+ };
3442
+ }
3201
3443
  const detailsMatch = service === "wsmtxca" ? matchWsmtxcaDetails(data, attempted.number, lookup.voucher.raw) : void 0;
3202
3444
  const matched = detailsMatch === void 0 ? matchWsfeVoucherIdentity(data, attempted.number, lookup.voucher) : detailsMatch === "match" && lookup.voucher.cae && lookup.voucher.caeExpiry ? { matches: true } : {
3203
3445
  matches: false,
@@ -3281,6 +3523,14 @@ function projectEvidence(evidence, includeRaw) {
3281
3523
  }
3282
3524
  return projected;
3283
3525
  }
3526
+ function cloneOptions(options) {
3527
+ assertIssueObject(options, "options");
3528
+ const { signal, ...rest } = options;
3529
+ return {
3530
+ ...structuredClone(rest),
3531
+ ...signal === void 0 ? {} : { signal }
3532
+ };
3533
+ }
3284
3534
  function validateOptions(options) {
3285
3535
  assertIssueObject(options, "options");
3286
3536
  assertIssueKeys(
@@ -3291,10 +3541,17 @@ function validateOptions(options) {
3291
3541
  "include",
3292
3542
  "idempotencyKey",
3293
3543
  "service",
3294
- "number"
3544
+ "number",
3545
+ "signal"
3295
3546
  ],
3296
3547
  "options"
3297
3548
  );
3549
+ if (options.signal !== void 0 && (typeof options.signal !== "object" || options.signal === null || typeof options.signal.aborted !== "boolean" || typeof options.signal.addEventListener !== "function")) {
3550
+ throw new ArcaInputError("options.signal must be an AbortSignal.", {
3551
+ code: "ARCA_INPUT_INVALID_VALUE",
3552
+ field: "options.signal"
3553
+ });
3554
+ }
3298
3555
  if (options.service !== void 0 && options.service !== "wsfe" && options.service !== "wsmtxca") {
3299
3556
  throw new ArcaInputError("Unknown fiscal service", {
3300
3557
  code: "ARCA_INPUT_INVALID_VALUE",
@@ -3348,8 +3605,8 @@ function replayEvidence(service = "wsfe") {
3348
3605
  observations: []
3349
3606
  };
3350
3607
  }
3351
- async function issueCreditNote(wsfe, input, inputOptions, context, kind = "creditNote") {
3352
- const options = structuredClone(inputOptions);
3608
+ async function issueCreditNote(wsfe, input, inputOptions, context, kind = "creditNote", select) {
3609
+ const options = cloneOptions(inputOptions);
3353
3610
  validateOptions(options);
3354
3611
  validateKeyStore(options, context);
3355
3612
  assertIssueObject(input, "input");
@@ -3366,7 +3623,9 @@ async function issueCreditNote(wsfe, input, inputOptions, context, kind = "credi
3366
3623
  note,
3367
3624
  () => prepareNote(wsfe, note, options, context, kind),
3368
3625
  options,
3369
- context
3626
+ context,
3627
+ void 0,
3628
+ select
3370
3629
  );
3371
3630
  }
3372
3631
  async function previewNote(wsfe, input, inputOptions, context, kind) {
@@ -3467,11 +3726,11 @@ function exactEvidence(data, number, options, include) {
3467
3726
  } : {};
3468
3727
  }
3469
3728
  async function recoverOperation(select, key, inputOptions, context) {
3470
- const options = structuredClone(inputOptions);
3729
+ const options = cloneOptions(inputOptions);
3471
3730
  assertIssueObject(options, "options");
3472
3731
  assertIssueKeys(
3473
3732
  options,
3474
- ["representedTaxId", "forceRefresh", "include"],
3733
+ ["representedTaxId", "forceRefresh", "include", "signal"],
3475
3734
  "options"
3476
3735
  );
3477
3736
  validateOptions(options);
@@ -3492,28 +3751,52 @@ async function recoverOperation(select, key, inputOptions, context) {
3492
3751
  });
3493
3752
  }
3494
3753
  const record = readRecord(json);
3754
+ const store = context.store;
3755
+ const settled = settledKey(
3756
+ context.environment,
3757
+ context.taxId,
3758
+ key
3759
+ );
3495
3760
  if (options.representedTaxId !== void 0 && String(options.representedTaxId) !== (record.representedTaxId ?? context?.taxId)) {
3496
3761
  throw new ArcaInputError(
3497
3762
  "Reservation belongs to another represented taxpayer",
3498
3763
  { code: "ARCA_INPUT_IDEMPOTENCY_MISMATCH" }
3499
3764
  );
3500
3765
  }
3766
+ const recorded = await storeCall(() => store.get(settled));
3767
+ if (recorded !== null) {
3768
+ return await settledOutcome(
3769
+ select,
3770
+ record,
3771
+ readSettledRecord(recorded),
3772
+ options
3773
+ );
3774
+ }
3775
+ return await recordConflict(
3776
+ store,
3777
+ settled,
3778
+ await consultReservation(select, record, options)
3779
+ );
3780
+ }
3781
+ function consultReservation(select, record, options, strangerAtNumber = false) {
3501
3782
  const storedOptions = {
3502
3783
  ...options,
3503
3784
  service: record.service ?? "wsfe",
3504
3785
  representedTaxId: record.representedTaxId
3505
3786
  };
3506
3787
  const prepared = preparedFromRecord(record);
3788
+ const attempted = {
3789
+ salesPoint: record.salesPoint,
3790
+ voucherType: record.voucherType,
3791
+ number: record.number
3792
+ };
3507
3793
  return recoverInvoice({
3508
3794
  wsfe: select(storedOptions),
3509
3795
  service: storedOptions.service,
3510
3796
  auth: storedOptions,
3511
3797
  data: prepared.data,
3512
- attempted: {
3513
- salesPoint: record.salesPoint,
3514
- voucherType: record.voucherType,
3515
- number: record.number
3516
- },
3798
+ strangerAtNumber,
3799
+ attempted,
3517
3800
  attempt: replayEvidence(storedOptions.service),
3518
3801
  includeRaw: options.include?.raw === true,
3519
3802
  exact: exactEvidence(
@@ -3523,9 +3806,7 @@ async function recoverOperation(select, key, inputOptions, context) {
3523
3806
  options.include?.exactInput === true
3524
3807
  ),
3525
3808
  voucher: (cae, caeExpiry) => ({
3526
- salesPoint: record.salesPoint,
3527
- voucherType: record.voucherType,
3528
- number: record.number,
3809
+ ...attempted,
3529
3810
  voucherClass: prepared.voucherClass,
3530
3811
  date: prepared.data.voucherDate,
3531
3812
  amounts: prepared.amounts,
@@ -3534,6 +3815,90 @@ async function recoverOperation(select, key, inputOptions, context) {
3534
3815
  })
3535
3816
  });
3536
3817
  }
3818
+ async function runSequenceBarrier({
3819
+ store,
3820
+ environment,
3821
+ taxId,
3822
+ sequence,
3823
+ coordinates,
3824
+ select,
3825
+ options,
3826
+ supersededBy,
3827
+ readNext
3828
+ }) {
3829
+ const json = await storeCall(() => store.get(sequence));
3830
+ if (json === null) {
3831
+ return {};
3832
+ }
3833
+ const claimed = readSequenceRecord(json);
3834
+ if (claimed.resolvedAt !== void 0) {
3835
+ return {};
3836
+ }
3837
+ const settled = settledKey(environment, taxId, claimed.key);
3838
+ if (await storeCall(() => store.get(settled)) !== null) {
3839
+ return {};
3840
+ }
3841
+ const reservation = await storeCall(
3842
+ () => store.get(attemptKey(environment, taxId, claimed.key))
3843
+ );
3844
+ if (reservation === null) {
3845
+ return {};
3846
+ }
3847
+ const record = readRecord(reservation);
3848
+ const blocked = {
3849
+ blocked: {
3850
+ kind: "indeterminate",
3851
+ attempted: { ...coordinates, number: record.number },
3852
+ attempt: replayEvidence(options.service),
3853
+ lookup: { kind: "blocked", by: claimed.key }
3854
+ }
3855
+ };
3856
+ const outcome = await recordConflict(
3857
+ store,
3858
+ settled,
3859
+ await consultReservation(select, record, {
3860
+ forceRefresh: options.forceRefresh,
3861
+ ...options.signal === void 0 ? {} : { signal: options.signal }
3862
+ })
3863
+ );
3864
+ if (outcome.kind === "authorized" || outcome.kind === "conflict") {
3865
+ return {};
3866
+ }
3867
+ if (outcome.kind !== "indeterminate" || outcome.lookup.kind !== "not_found") {
3868
+ return blocked;
3869
+ }
3870
+ const next = await readNext();
3871
+ if (next !== record.number) {
3872
+ return blocked;
3873
+ }
3874
+ await storeCall(
3875
+ () => store.add(
3876
+ settled,
3877
+ JSON.stringify({
3878
+ v: 1,
3879
+ kind: "superseded",
3880
+ number: record.number,
3881
+ by: supersededBy,
3882
+ settledAt: (/* @__PURE__ */ new Date()).toISOString()
3883
+ })
3884
+ )
3885
+ );
3886
+ return { reserved: next };
3887
+ }
3888
+ function readSequenceRecord(json) {
3889
+ try {
3890
+ const record = JSON.parse(json);
3891
+ if (!record || record.v !== 1 || typeof record.key !== "string" || !Number.isSafeInteger(record.number)) {
3892
+ throw new Error("Invalid sequence structure");
3893
+ }
3894
+ return record;
3895
+ } catch (cause) {
3896
+ throw new ArcaConfigurationError(
3897
+ "Invalid ARCA sequence record; delete the sequence key to resume.",
3898
+ { cause }
3899
+ );
3900
+ }
3901
+ }
3537
3902
 
3538
3903
  // src/soap/index.ts
3539
3904
  function createSoapTransport(options) {
@@ -3572,7 +3937,8 @@ function createSoapTransport(options) {
3572
3937
  retryDelay: options.config.retryDelay,
3573
3938
  logger: options.logger,
3574
3939
  service: request.service,
3575
- operation: request.operation
3940
+ operation: request.operation,
3941
+ signal: request.signal
3576
3942
  });
3577
3943
  options.logger?.debug("Received ARCA SOAP response", {
3578
3944
  service: request.service,
@@ -3688,9 +4054,56 @@ function createArcaClient(config = {}) {
3688
4054
  }
3689
4055
 
3690
4056
  // src/store/file.ts
3691
- import { createHash as createHash3, randomUUID } from "crypto";
3692
- import { mkdir, readFile, rename, unlink, writeFile } from "fs/promises";
4057
+ import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
4058
+ import {
4059
+ mkdir,
4060
+ readFile,
4061
+ rename,
4062
+ rm,
4063
+ stat,
4064
+ unlink,
4065
+ writeFile
4066
+ } from "fs/promises";
3693
4067
  import { join } from "path";
4068
+
4069
+ // src/store/lock.ts
4070
+ import { randomUUID } from "crypto";
4071
+ var ARCA_LEASE_MS = 6e4;
4072
+ var RENEW_MS = 2e4;
4073
+ var POLL_MS = 50;
4074
+ var MAX_WAIT_MS = 2 * ARCA_LEASE_MS;
4075
+ async function withLease(key, driver, fn) {
4076
+ const owner = randomUUID();
4077
+ const deadline = Date.now() + MAX_WAIT_MS;
4078
+ let held = await driver.acquire(owner);
4079
+ while (!held) {
4080
+ if (Date.now() >= deadline) {
4081
+ throw new ArcaConfigurationError(
4082
+ `ARCA store lock ${key} stayed held; no work was attempted.`
4083
+ );
4084
+ }
4085
+ await delay2(POLL_MS + Math.floor(Math.random() * POLL_MS));
4086
+ held = await driver.acquire(owner);
4087
+ }
4088
+ const renewal = setInterval(() => {
4089
+ driver.renew(owner).catch(() => void 0);
4090
+ }, RENEW_MS);
4091
+ renewal.unref?.();
4092
+ try {
4093
+ return await fn();
4094
+ } finally {
4095
+ clearInterval(renewal);
4096
+ await driver.release(owner).catch(() => void 0);
4097
+ }
4098
+ }
4099
+ function delay2(ms) {
4100
+ return new Promise((resolve) => {
4101
+ const timer = setTimeout(resolve, ms);
4102
+ timer.unref?.();
4103
+ });
4104
+ }
4105
+
4106
+ // src/store/file.ts
3694
4107
  function createFileStore(directory) {
3695
4108
  const path = (key) => join(directory, createHash3("sha256").update(key).digest("hex"));
3696
4109
  const ensure = () => mkdir(directory, { recursive: true, mode: 448 });
@@ -3707,7 +4120,7 @@ function createFileStore(directory) {
3707
4120
  }),
3708
4121
  set: (key, value) => storeCall(async () => {
3709
4122
  await ensure();
3710
- const temporary = `${path(key)}.${randomUUID()}.tmp`;
4123
+ const temporary = `${path(key)}.${randomUUID2()}.tmp`;
3711
4124
  try {
3712
4125
  await writeFile(temporary, value, { mode: 384, flag: "wx" });
3713
4126
  await rename(temporary, path(key));
@@ -3735,9 +4148,85 @@ function createFileStore(directory) {
3735
4148
  throw error;
3736
4149
  }
3737
4150
  }
4151
+ }),
4152
+ withLock: (key, fn) => (
4153
+ // A lock directory: mkdir is the atomic claim on every POSIX filesystem
4154
+ // and on Windows, and the holder file inside carries the lease.
4155
+ withLease(key, fileLease(`${path(key)}.lock`, ensure), fn)
4156
+ )
4157
+ };
4158
+ }
4159
+ function fileLease(directory, ensure) {
4160
+ const holder = join(directory, "holder");
4161
+ const claim = (owner) => JSON.stringify({
4162
+ owner,
4163
+ expiresAt: new Date(Date.now() + ARCA_LEASE_MS).toISOString()
4164
+ });
4165
+ const read = async () => {
4166
+ try {
4167
+ return JSON.parse(await readFile(holder, "utf8"));
4168
+ } catch {
4169
+ return null;
4170
+ }
4171
+ };
4172
+ return {
4173
+ acquire: (owner) => storeCall(async () => {
4174
+ await ensure();
4175
+ if (!await createDirectory(directory)) {
4176
+ if (await stillHeld(directory, await read())) {
4177
+ return false;
4178
+ }
4179
+ await rm(directory, { recursive: true, force: true });
4180
+ if (!await createDirectory(directory)) {
4181
+ return false;
4182
+ }
4183
+ }
4184
+ return await writeHolder(holder, claim(owner));
4185
+ }),
4186
+ renew: (owner) => storeCall(async () => {
4187
+ if ((await read())?.owner === owner) {
4188
+ await writeFile(holder, claim(owner), { mode: 384 });
4189
+ }
4190
+ }),
4191
+ release: (owner) => storeCall(async () => {
4192
+ if ((await read())?.owner === owner) {
4193
+ await rm(directory, { recursive: true, force: true });
4194
+ }
3738
4195
  })
3739
4196
  };
3740
4197
  }
4198
+ async function createDirectory(directory) {
4199
+ try {
4200
+ await mkdir(directory);
4201
+ return true;
4202
+ } catch (error) {
4203
+ if (error.code === "EEXIST") {
4204
+ return false;
4205
+ }
4206
+ throw error;
4207
+ }
4208
+ }
4209
+ async function writeHolder(holder, value) {
4210
+ try {
4211
+ await writeFile(holder, value, { mode: 384, flag: "wx" });
4212
+ return true;
4213
+ } catch (error) {
4214
+ if (error.code === "EEXIST") {
4215
+ return false;
4216
+ }
4217
+ throw error;
4218
+ }
4219
+ }
4220
+ async function stillHeld(directory, holder) {
4221
+ if (holder) {
4222
+ return Date.parse(holder.expiresAt) > Date.now();
4223
+ }
4224
+ try {
4225
+ return (await stat(directory)).mtimeMs + ARCA_LEASE_MS > Date.now();
4226
+ } catch {
4227
+ return false;
4228
+ }
4229
+ }
3741
4230
 
3742
4231
  export {
3743
4232
  ARCA_ENVIRONMENTS,
@@ -3753,6 +4242,8 @@ export {
3753
4242
  createWsaaAuthModule,
3754
4243
  createWsaaStoreAdapter,
3755
4244
  createArcaClient,
4245
+ ARCA_LEASE_MS,
4246
+ withLease,
3756
4247
  createFileStore
3757
4248
  };
3758
- //# sourceMappingURL=chunk-LOWV7IF6.mjs.map
4249
+ //# sourceMappingURL=chunk-2PYF5JFG.mjs.map