facturas 0.11.0 → 0.12.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.
@@ -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,102 @@ 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);
2961
- }
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
- );
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
+ async function claim(reserved) {
3039
+ const number = options.number ?? reserved ?? await nextNumber(wsfe, prepared.data, options);
3040
+ const service = options.service ?? "wsfe";
3041
+ const versioned = service === "wsmtxca" || prepared.data.details !== void 0;
3042
+ const record = {
3043
+ v: versioned ? 2 : 1,
3044
+ operation,
3045
+ ...versioned ? { service } : {},
3046
+ representedTaxId,
3047
+ inputHash,
3048
+ number,
3049
+ salesPoint: prepared.data.salesPoint,
3050
+ voucherType: prepared.data.voucherType,
3051
+ sent: prepared.data,
3052
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
3053
+ };
3054
+ if (await storeCall(() => store.add(key, JSON.stringify(record)))) {
3055
+ const claimed = {
3056
+ v: 1,
3057
+ key: idempotencyKey,
3058
+ number,
3059
+ claimedAt: (/* @__PURE__ */ new Date()).toISOString()
3060
+ };
3061
+ const coordinated = store.withLock !== void 0;
3062
+ if (coordinated) {
3063
+ await storeCall(
3064
+ () => store.set(sequenceRecord, JSON.stringify(claimed))
3065
+ );
3066
+ }
3067
+ const outcome = await settle(
3068
+ runAuthorization(wsfe, prepared, options, number)
3069
+ );
3070
+ if (coordinated && outcome.kind !== "indeterminate") {
3071
+ await storeCall(
3072
+ () => store.set(
3073
+ sequenceRecord,
3074
+ JSON.stringify({ ...claimed, resolvedAt: (/* @__PURE__ */ new Date()).toISOString() })
3075
+ )
3076
+ );
3077
+ }
3078
+ return outcome;
3079
+ }
3080
+ const winner = await storeCall(() => store.get(key));
3081
+ if (winner === null) {
3082
+ throw new ArcaConfigurationError(
3083
+ "ARCA reservation disappeared after atomic creation lost."
3084
+ );
3085
+ }
3086
+ return await replay(winner, true);
2967
3087
  }
2968
- return replay(winner);
2969
- function replay(json) {
3088
+ async function replay(json, locked = false) {
2970
3089
  const stored = readRecord(json);
2971
3090
  if ((stored.service ?? "wsfe") !== (options.service ?? "wsfe") || stored.operation !== operation || stored.inputHash !== inputHash || stored.representedTaxId !== representedTaxId) {
2972
3091
  throw new ArcaInputError(
@@ -2977,17 +3096,107 @@ async function runOperation(wsfe, operation, input, prepare, options, context, r
2977
3096
  }
2978
3097
  );
2979
3098
  }
2980
- return runAuthorization(
2981
- wsfe,
2982
- {
2983
- ...preparedFromRecord(stored),
2984
- ...replayAmounts ? { amounts: replayAmounts } : {}
2985
- },
2986
- options,
2987
- stored.number,
2988
- true
3099
+ const consult = async () => {
3100
+ const recorded = await storeCall(() => store.get(settled));
3101
+ if (recorded !== null) {
3102
+ return await settledOutcome(
3103
+ select ?? (() => wsfe),
3104
+ stored,
3105
+ readSettledRecord(recorded),
3106
+ options
3107
+ );
3108
+ }
3109
+ return await settle(
3110
+ runAuthorization(
3111
+ wsfe,
3112
+ {
3113
+ ...preparedFromRecord(stored),
3114
+ ...replayAmounts ? { amounts: replayAmounts } : {}
3115
+ },
3116
+ options,
3117
+ stored.number,
3118
+ true
3119
+ )
3120
+ );
3121
+ };
3122
+ if (locked || !store.withLock) {
3123
+ return await consult();
3124
+ }
3125
+ return await store.withLock(
3126
+ sequenceLockKey(
3127
+ environment,
3128
+ stored.representedTaxId ?? taxId,
3129
+ stored.salesPoint,
3130
+ stored.voucherType
3131
+ ),
3132
+ consult
2989
3133
  );
2990
3134
  }
3135
+ async function settle(running) {
3136
+ return await recordConflict(store, settled, await running);
3137
+ }
3138
+ }
3139
+ async function recordConflict(store, key, outcome) {
3140
+ if (outcome.kind !== "conflict") {
3141
+ return outcome;
3142
+ }
3143
+ const record = {
3144
+ v: 1,
3145
+ kind: "conflict",
3146
+ number: outcome.attempted.number,
3147
+ found: Object.fromEntries(
3148
+ Object.entries(outcome.found).filter(([field]) => field !== "raw")
3149
+ ),
3150
+ settledAt: (/* @__PURE__ */ new Date()).toISOString()
3151
+ };
3152
+ await storeCall(() => store.add(key, JSON.stringify(record)));
3153
+ return outcome;
3154
+ }
3155
+ function readSettledRecord(json) {
3156
+ try {
3157
+ const record = JSON.parse(json);
3158
+ 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")) {
3159
+ throw new Error("Invalid settled structure");
3160
+ }
3161
+ return record;
3162
+ } catch (cause) {
3163
+ throw new ArcaConfigurationError(
3164
+ "Invalid ARCA settled record; preserve it for reconciliation.",
3165
+ { cause }
3166
+ );
3167
+ }
3168
+ }
3169
+ async function settledOutcome(select, reservation, settled, options) {
3170
+ if (settled.kind === "conflict") {
3171
+ return settledConflict(settled, reservation);
3172
+ }
3173
+ const outcome = await consultReservation(select, reservation, options, true);
3174
+ if (outcome.kind === "indeterminate" && outcome.lookup.kind === "not_found") {
3175
+ return {
3176
+ kind: "indeterminate",
3177
+ attempted: {
3178
+ salesPoint: reservation.salesPoint,
3179
+ voucherType: reservation.voucherType,
3180
+ number: settled.number
3181
+ },
3182
+ attempt: replayEvidence(reservation.service),
3183
+ lookup: { kind: "superseded", by: settled.by }
3184
+ };
3185
+ }
3186
+ return outcome;
3187
+ }
3188
+ function settledConflict(settled, reservation) {
3189
+ return {
3190
+ kind: "conflict",
3191
+ attempted: {
3192
+ salesPoint: reservation.salesPoint,
3193
+ voucherType: reservation.voucherType,
3194
+ number: settled.number
3195
+ },
3196
+ attempt: replayEvidence(reservation.service),
3197
+ found: settled.found,
3198
+ reason: "This key already recorded another voucher at the reserved number. Reconcile before issuing under a new key."
3199
+ };
2991
3200
  }
2992
3201
  function validateKeyStore(options, context) {
2993
3202
  if (options.idempotencyKey === void 0) {
@@ -3043,6 +3252,7 @@ async function nextNumber(wsfe, data, options) {
3043
3252
  const number = await wsfe.getNextVoucherNumber({
3044
3253
  representedTaxId: options.representedTaxId,
3045
3254
  forceRefresh: options.forceRefresh,
3255
+ ...options.signal === void 0 ? {} : { signal: options.signal },
3046
3256
  salesPoint: data.salesPoint,
3047
3257
  voucherType: data.voucherType
3048
3258
  });
@@ -3060,7 +3270,8 @@ async function nextNumber(wsfe, data, options) {
3060
3270
  async function runAuthorization(wsfe, { data, voucherClass, amounts }, options, reservedNumber, replay = false) {
3061
3271
  const auth = {
3062
3272
  representedTaxId: options.representedTaxId,
3063
- forceRefresh: options.forceRefresh
3273
+ forceRefresh: options.forceRefresh,
3274
+ ...options.signal === void 0 ? {} : { signal: options.signal }
3064
3275
  };
3065
3276
  const includeRaw = options.include?.raw === true;
3066
3277
  const includeExact = options.include?.exactInput === true;
@@ -3099,7 +3310,7 @@ async function runAuthorization(wsfe, { data, voucherClass, amounts }, options,
3099
3310
  kind: "indeterminate",
3100
3311
  attempted,
3101
3312
  attempt: attempt2,
3102
- lookup: { kind: "failed", error: toArcaSafeErrorMetadata(error) }
3313
+ lookup: options.signal?.aborted ? { kind: "aborted" } : { kind: "failed", error: toArcaSafeErrorMetadata(error) }
3103
3314
  };
3104
3315
  }
3105
3316
  if (lookup.kind === "found") {
@@ -3121,32 +3332,13 @@ async function runAuthorization(wsfe, { data, voucherClass, amounts }, options,
3121
3332
  };
3122
3333
  }
3123
3334
  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
- };
3335
+ return await resolveRejection(
3336
+ authorization,
3337
+ recovery,
3338
+ options,
3339
+ includeRaw,
3340
+ replay
3341
+ );
3150
3342
  }
3151
3343
  const uncertain = authorization.kind === "indeterminate" ? authorization : {
3152
3344
  ...authorization,
@@ -3166,6 +3358,33 @@ async function runAuthorization(wsfe, { data, voucherClass, amounts }, options,
3166
3358
  voucher
3167
3359
  });
3168
3360
  }
3361
+ async function resolveRejection(authorization, recovery, options, includeRaw, replay) {
3362
+ const issues = [...authorization.errors, ...authorization.observations];
3363
+ const consult = options.idempotencyKey !== void 0 && (options.service === "wsmtxca" || issues.some((issue) => issue.code === "10016"));
3364
+ if (consult) {
3365
+ const recovered = await recoverInvoice({
3366
+ ...recovery,
3367
+ attempt: projectEvidence(
3368
+ {
3369
+ ...authorization,
3370
+ kind: "indeterminate",
3371
+ reason: "contradictory_response"
3372
+ },
3373
+ includeRaw
3374
+ ),
3375
+ strangerAtNumber: !replay
3376
+ });
3377
+ if (recovered.kind === "authorized" || recovered.kind === "conflict") {
3378
+ return recovered;
3379
+ }
3380
+ }
3381
+ return {
3382
+ kind: "rejected",
3383
+ attempted: recovery.attempted,
3384
+ issues: issues.map(projectIssue),
3385
+ authorization: projectEvidence(authorization, includeRaw)
3386
+ };
3387
+ }
3169
3388
  async function recoverInvoice({
3170
3389
  wsfe,
3171
3390
  auth,
@@ -3176,8 +3395,17 @@ async function recoverInvoice({
3176
3395
  exact,
3177
3396
  voucher,
3178
3397
  lookup: suppliedLookup,
3179
- service
3398
+ service,
3399
+ strangerAtNumber = false
3180
3400
  }) {
3401
+ if (suppliedLookup === void 0 && auth.signal?.aborted) {
3402
+ return {
3403
+ kind: "indeterminate",
3404
+ attempted,
3405
+ attempt,
3406
+ lookup: { kind: "aborted" }
3407
+ };
3408
+ }
3181
3409
  let lookup;
3182
3410
  try {
3183
3411
  lookup = suppliedLookup ?? await wsfe.lookupVoucher({ ...auth, ...attempted });
@@ -3186,7 +3414,7 @@ async function recoverInvoice({
3186
3414
  kind: "indeterminate",
3187
3415
  attempted,
3188
3416
  attempt,
3189
- lookup: { kind: "failed", error: toArcaSafeErrorMetadata(error) }
3417
+ lookup: auth.signal?.aborted ? { kind: "aborted" } : { kind: "failed", error: toArcaSafeErrorMetadata(error) }
3190
3418
  };
3191
3419
  }
3192
3420
  const raw = includeRaw ? { raw: lookup.raw } : {};
@@ -3198,6 +3426,15 @@ async function recoverInvoice({
3198
3426
  lookup: { kind: "not_found", ...raw }
3199
3427
  };
3200
3428
  }
3429
+ if (strangerAtNumber) {
3430
+ return {
3431
+ kind: "conflict",
3432
+ attempted,
3433
+ attempt,
3434
+ found: { ...toVoucherSummary(lookup.voucher), ...raw },
3435
+ reason: "ARCA refused the number this call reserved and another voucher occupies it"
3436
+ };
3437
+ }
3201
3438
  const detailsMatch = service === "wsmtxca" ? matchWsmtxcaDetails(data, attempted.number, lookup.voucher.raw) : void 0;
3202
3439
  const matched = detailsMatch === void 0 ? matchWsfeVoucherIdentity(data, attempted.number, lookup.voucher) : detailsMatch === "match" && lookup.voucher.cae && lookup.voucher.caeExpiry ? { matches: true } : {
3203
3440
  matches: false,
@@ -3281,6 +3518,14 @@ function projectEvidence(evidence, includeRaw) {
3281
3518
  }
3282
3519
  return projected;
3283
3520
  }
3521
+ function cloneOptions(options) {
3522
+ assertIssueObject(options, "options");
3523
+ const { signal, ...rest } = options;
3524
+ return {
3525
+ ...structuredClone(rest),
3526
+ ...signal === void 0 ? {} : { signal }
3527
+ };
3528
+ }
3284
3529
  function validateOptions(options) {
3285
3530
  assertIssueObject(options, "options");
3286
3531
  assertIssueKeys(
@@ -3291,10 +3536,17 @@ function validateOptions(options) {
3291
3536
  "include",
3292
3537
  "idempotencyKey",
3293
3538
  "service",
3294
- "number"
3539
+ "number",
3540
+ "signal"
3295
3541
  ],
3296
3542
  "options"
3297
3543
  );
3544
+ if (options.signal !== void 0 && (typeof options.signal !== "object" || options.signal === null || typeof options.signal.aborted !== "boolean" || typeof options.signal.addEventListener !== "function")) {
3545
+ throw new ArcaInputError("options.signal must be an AbortSignal.", {
3546
+ code: "ARCA_INPUT_INVALID_VALUE",
3547
+ field: "options.signal"
3548
+ });
3549
+ }
3298
3550
  if (options.service !== void 0 && options.service !== "wsfe" && options.service !== "wsmtxca") {
3299
3551
  throw new ArcaInputError("Unknown fiscal service", {
3300
3552
  code: "ARCA_INPUT_INVALID_VALUE",
@@ -3348,8 +3600,8 @@ function replayEvidence(service = "wsfe") {
3348
3600
  observations: []
3349
3601
  };
3350
3602
  }
3351
- async function issueCreditNote(wsfe, input, inputOptions, context, kind = "creditNote") {
3352
- const options = structuredClone(inputOptions);
3603
+ async function issueCreditNote(wsfe, input, inputOptions, context, kind = "creditNote", select) {
3604
+ const options = cloneOptions(inputOptions);
3353
3605
  validateOptions(options);
3354
3606
  validateKeyStore(options, context);
3355
3607
  assertIssueObject(input, "input");
@@ -3366,7 +3618,9 @@ async function issueCreditNote(wsfe, input, inputOptions, context, kind = "credi
3366
3618
  note,
3367
3619
  () => prepareNote(wsfe, note, options, context, kind),
3368
3620
  options,
3369
- context
3621
+ context,
3622
+ void 0,
3623
+ select
3370
3624
  );
3371
3625
  }
3372
3626
  async function previewNote(wsfe, input, inputOptions, context, kind) {
@@ -3467,11 +3721,11 @@ function exactEvidence(data, number, options, include) {
3467
3721
  } : {};
3468
3722
  }
3469
3723
  async function recoverOperation(select, key, inputOptions, context) {
3470
- const options = structuredClone(inputOptions);
3724
+ const options = cloneOptions(inputOptions);
3471
3725
  assertIssueObject(options, "options");
3472
3726
  assertIssueKeys(
3473
3727
  options,
3474
- ["representedTaxId", "forceRefresh", "include"],
3728
+ ["representedTaxId", "forceRefresh", "include", "signal"],
3475
3729
  "options"
3476
3730
  );
3477
3731
  validateOptions(options);
@@ -3492,28 +3746,52 @@ async function recoverOperation(select, key, inputOptions, context) {
3492
3746
  });
3493
3747
  }
3494
3748
  const record = readRecord(json);
3749
+ const store = context.store;
3750
+ const settled = settledKey(
3751
+ context.environment,
3752
+ context.taxId,
3753
+ key
3754
+ );
3495
3755
  if (options.representedTaxId !== void 0 && String(options.representedTaxId) !== (record.representedTaxId ?? context?.taxId)) {
3496
3756
  throw new ArcaInputError(
3497
3757
  "Reservation belongs to another represented taxpayer",
3498
3758
  { code: "ARCA_INPUT_IDEMPOTENCY_MISMATCH" }
3499
3759
  );
3500
3760
  }
3761
+ const recorded = await storeCall(() => store.get(settled));
3762
+ if (recorded !== null) {
3763
+ return await settledOutcome(
3764
+ select,
3765
+ record,
3766
+ readSettledRecord(recorded),
3767
+ options
3768
+ );
3769
+ }
3770
+ return await recordConflict(
3771
+ store,
3772
+ settled,
3773
+ await consultReservation(select, record, options)
3774
+ );
3775
+ }
3776
+ function consultReservation(select, record, options, strangerAtNumber = false) {
3501
3777
  const storedOptions = {
3502
3778
  ...options,
3503
3779
  service: record.service ?? "wsfe",
3504
3780
  representedTaxId: record.representedTaxId
3505
3781
  };
3506
3782
  const prepared = preparedFromRecord(record);
3783
+ const attempted = {
3784
+ salesPoint: record.salesPoint,
3785
+ voucherType: record.voucherType,
3786
+ number: record.number
3787
+ };
3507
3788
  return recoverInvoice({
3508
3789
  wsfe: select(storedOptions),
3509
3790
  service: storedOptions.service,
3510
3791
  auth: storedOptions,
3511
3792
  data: prepared.data,
3512
- attempted: {
3513
- salesPoint: record.salesPoint,
3514
- voucherType: record.voucherType,
3515
- number: record.number
3516
- },
3793
+ strangerAtNumber,
3794
+ attempted,
3517
3795
  attempt: replayEvidence(storedOptions.service),
3518
3796
  includeRaw: options.include?.raw === true,
3519
3797
  exact: exactEvidence(
@@ -3523,9 +3801,7 @@ async function recoverOperation(select, key, inputOptions, context) {
3523
3801
  options.include?.exactInput === true
3524
3802
  ),
3525
3803
  voucher: (cae, caeExpiry) => ({
3526
- salesPoint: record.salesPoint,
3527
- voucherType: record.voucherType,
3528
- number: record.number,
3804
+ ...attempted,
3529
3805
  voucherClass: prepared.voucherClass,
3530
3806
  date: prepared.data.voucherDate,
3531
3807
  amounts: prepared.amounts,
@@ -3534,6 +3810,89 @@ async function recoverOperation(select, key, inputOptions, context) {
3534
3810
  })
3535
3811
  });
3536
3812
  }
3813
+ async function runSequenceBarrier({
3814
+ store,
3815
+ environment,
3816
+ taxId,
3817
+ sequence,
3818
+ coordinates,
3819
+ select,
3820
+ options,
3821
+ supersededBy,
3822
+ readNext
3823
+ }) {
3824
+ const json = await storeCall(() => store.get(sequence));
3825
+ if (json === null) {
3826
+ return {};
3827
+ }
3828
+ const claimed = readSequenceRecord(json);
3829
+ if (claimed.resolvedAt !== void 0) {
3830
+ return {};
3831
+ }
3832
+ const settled = settledKey(environment, taxId, claimed.key);
3833
+ if (await storeCall(() => store.get(settled)) !== null) {
3834
+ return {};
3835
+ }
3836
+ const reservation = await storeCall(
3837
+ () => store.get(attemptKey(environment, taxId, claimed.key))
3838
+ );
3839
+ if (reservation === null) {
3840
+ return {};
3841
+ }
3842
+ const blocked = {
3843
+ blocked: {
3844
+ kind: "indeterminate",
3845
+ attempted: { ...coordinates, number: claimed.number },
3846
+ attempt: replayEvidence(options.service),
3847
+ lookup: { kind: "blocked", by: claimed.key }
3848
+ }
3849
+ };
3850
+ const outcome = await recordConflict(
3851
+ store,
3852
+ settled,
3853
+ await consultReservation(select, readRecord(reservation), {
3854
+ forceRefresh: options.forceRefresh,
3855
+ ...options.signal === void 0 ? {} : { signal: options.signal }
3856
+ })
3857
+ );
3858
+ if (outcome.kind === "authorized" || outcome.kind === "conflict") {
3859
+ return {};
3860
+ }
3861
+ if (outcome.kind !== "indeterminate" || outcome.lookup.kind !== "not_found") {
3862
+ return blocked;
3863
+ }
3864
+ const next = await readNext();
3865
+ if (next !== claimed.number) {
3866
+ return blocked;
3867
+ }
3868
+ await storeCall(
3869
+ () => store.add(
3870
+ settled,
3871
+ JSON.stringify({
3872
+ v: 1,
3873
+ kind: "superseded",
3874
+ number: claimed.number,
3875
+ by: supersededBy,
3876
+ settledAt: (/* @__PURE__ */ new Date()).toISOString()
3877
+ })
3878
+ )
3879
+ );
3880
+ return { reserved: next };
3881
+ }
3882
+ function readSequenceRecord(json) {
3883
+ try {
3884
+ const record = JSON.parse(json);
3885
+ if (!record || record.v !== 1 || typeof record.key !== "string" || !Number.isSafeInteger(record.number)) {
3886
+ throw new Error("Invalid sequence structure");
3887
+ }
3888
+ return record;
3889
+ } catch (cause) {
3890
+ throw new ArcaConfigurationError(
3891
+ "Invalid ARCA sequence record; delete the sequence key to resume.",
3892
+ { cause }
3893
+ );
3894
+ }
3895
+ }
3537
3896
 
3538
3897
  // src/soap/index.ts
3539
3898
  function createSoapTransport(options) {
@@ -3572,7 +3931,8 @@ function createSoapTransport(options) {
3572
3931
  retryDelay: options.config.retryDelay,
3573
3932
  logger: options.logger,
3574
3933
  service: request.service,
3575
- operation: request.operation
3934
+ operation: request.operation,
3935
+ signal: request.signal
3576
3936
  });
3577
3937
  options.logger?.debug("Received ARCA SOAP response", {
3578
3938
  service: request.service,
@@ -3688,9 +4048,56 @@ function createArcaClient(config = {}) {
3688
4048
  }
3689
4049
 
3690
4050
  // src/store/file.ts
3691
- import { createHash as createHash3, randomUUID } from "crypto";
3692
- import { mkdir, readFile, rename, unlink, writeFile } from "fs/promises";
4051
+ import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
4052
+ import {
4053
+ mkdir,
4054
+ readFile,
4055
+ rename,
4056
+ rm,
4057
+ stat,
4058
+ unlink,
4059
+ writeFile
4060
+ } from "fs/promises";
3693
4061
  import { join } from "path";
4062
+
4063
+ // src/store/lock.ts
4064
+ import { randomUUID } from "crypto";
4065
+ var ARCA_LEASE_MS = 6e4;
4066
+ var RENEW_MS = 2e4;
4067
+ var POLL_MS = 50;
4068
+ var MAX_WAIT_MS = 2 * ARCA_LEASE_MS;
4069
+ async function withLease(key, driver, fn) {
4070
+ const owner = randomUUID();
4071
+ const deadline = Date.now() + MAX_WAIT_MS;
4072
+ let held = await driver.acquire(owner);
4073
+ while (!held) {
4074
+ if (Date.now() >= deadline) {
4075
+ throw new ArcaConfigurationError(
4076
+ `ARCA store lock ${key} stayed held; no work was attempted.`
4077
+ );
4078
+ }
4079
+ await delay2(POLL_MS + Math.floor(Math.random() * POLL_MS));
4080
+ held = await driver.acquire(owner);
4081
+ }
4082
+ const renewal = setInterval(() => {
4083
+ driver.renew(owner).catch(() => void 0);
4084
+ }, RENEW_MS);
4085
+ renewal.unref?.();
4086
+ try {
4087
+ return await fn();
4088
+ } finally {
4089
+ clearInterval(renewal);
4090
+ await driver.release(owner).catch(() => void 0);
4091
+ }
4092
+ }
4093
+ function delay2(ms) {
4094
+ return new Promise((resolve) => {
4095
+ const timer = setTimeout(resolve, ms);
4096
+ timer.unref?.();
4097
+ });
4098
+ }
4099
+
4100
+ // src/store/file.ts
3694
4101
  function createFileStore(directory) {
3695
4102
  const path = (key) => join(directory, createHash3("sha256").update(key).digest("hex"));
3696
4103
  const ensure = () => mkdir(directory, { recursive: true, mode: 448 });
@@ -3707,7 +4114,7 @@ function createFileStore(directory) {
3707
4114
  }),
3708
4115
  set: (key, value) => storeCall(async () => {
3709
4116
  await ensure();
3710
- const temporary = `${path(key)}.${randomUUID()}.tmp`;
4117
+ const temporary = `${path(key)}.${randomUUID2()}.tmp`;
3711
4118
  try {
3712
4119
  await writeFile(temporary, value, { mode: 384, flag: "wx" });
3713
4120
  await rename(temporary, path(key));
@@ -3735,9 +4142,85 @@ function createFileStore(directory) {
3735
4142
  throw error;
3736
4143
  }
3737
4144
  }
4145
+ }),
4146
+ withLock: (key, fn) => (
4147
+ // A lock directory: mkdir is the atomic claim on every POSIX filesystem
4148
+ // and on Windows, and the holder file inside carries the lease.
4149
+ withLease(key, fileLease(`${path(key)}.lock`, ensure), fn)
4150
+ )
4151
+ };
4152
+ }
4153
+ function fileLease(directory, ensure) {
4154
+ const holder = join(directory, "holder");
4155
+ const claim = (owner) => JSON.stringify({
4156
+ owner,
4157
+ expiresAt: new Date(Date.now() + ARCA_LEASE_MS).toISOString()
4158
+ });
4159
+ const read = async () => {
4160
+ try {
4161
+ return JSON.parse(await readFile(holder, "utf8"));
4162
+ } catch {
4163
+ return null;
4164
+ }
4165
+ };
4166
+ return {
4167
+ acquire: (owner) => storeCall(async () => {
4168
+ await ensure();
4169
+ if (!await createDirectory(directory)) {
4170
+ if (await stillHeld(directory, await read())) {
4171
+ return false;
4172
+ }
4173
+ await rm(directory, { recursive: true, force: true });
4174
+ if (!await createDirectory(directory)) {
4175
+ return false;
4176
+ }
4177
+ }
4178
+ return await writeHolder(holder, claim(owner));
4179
+ }),
4180
+ renew: (owner) => storeCall(async () => {
4181
+ if ((await read())?.owner === owner) {
4182
+ await writeFile(holder, claim(owner), { mode: 384 });
4183
+ }
4184
+ }),
4185
+ release: (owner) => storeCall(async () => {
4186
+ if ((await read())?.owner === owner) {
4187
+ await rm(directory, { recursive: true, force: true });
4188
+ }
3738
4189
  })
3739
4190
  };
3740
4191
  }
4192
+ async function createDirectory(directory) {
4193
+ try {
4194
+ await mkdir(directory);
4195
+ return true;
4196
+ } catch (error) {
4197
+ if (error.code === "EEXIST") {
4198
+ return false;
4199
+ }
4200
+ throw error;
4201
+ }
4202
+ }
4203
+ async function writeHolder(holder, value) {
4204
+ try {
4205
+ await writeFile(holder, value, { mode: 384, flag: "wx" });
4206
+ return true;
4207
+ } catch (error) {
4208
+ if (error.code === "EEXIST") {
4209
+ return false;
4210
+ }
4211
+ throw error;
4212
+ }
4213
+ }
4214
+ async function stillHeld(directory, holder) {
4215
+ if (holder) {
4216
+ return Date.parse(holder.expiresAt) > Date.now();
4217
+ }
4218
+ try {
4219
+ return (await stat(directory)).mtimeMs + ARCA_LEASE_MS > Date.now();
4220
+ } catch {
4221
+ return false;
4222
+ }
4223
+ }
3741
4224
 
3742
4225
  export {
3743
4226
  ARCA_ENVIRONMENTS,
@@ -3753,6 +4236,8 @@ export {
3753
4236
  createWsaaAuthModule,
3754
4237
  createWsaaStoreAdapter,
3755
4238
  createArcaClient,
4239
+ ARCA_LEASE_MS,
4240
+ withLease,
3756
4241
  createFileStore
3757
4242
  };
3758
- //# sourceMappingURL=chunk-LOWV7IF6.mjs.map
4243
+ //# sourceMappingURL=chunk-QEJCLJDS.mjs.map