@nehorai/credits 1.6.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +226 -2
  3. package/dist/adapters/index.cjs +298 -40
  4. package/dist/adapters/index.cjs.map +1 -1
  5. package/dist/adapters/index.d.cts +2 -2
  6. package/dist/adapters/index.d.ts +2 -2
  7. package/dist/adapters/index.js +298 -40
  8. package/dist/adapters/index.js.map +1 -1
  9. package/dist/core/index.cjs +696 -74
  10. package/dist/core/index.cjs.map +1 -1
  11. package/dist/core/index.d.cts +583 -5
  12. package/dist/core/index.d.ts +583 -5
  13. package/dist/core/index.js +648 -74
  14. package/dist/core/index.js.map +1 -1
  15. package/dist/index.cjs +1779 -364
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +41 -6
  18. package/dist/index.d.ts +41 -6
  19. package/dist/index.js +1723 -364
  20. package/dist/index.js.map +1 -1
  21. package/dist/repository/index.cjs +1413 -181
  22. package/dist/repository/index.cjs.map +1 -1
  23. package/dist/repository/index.d.cts +142 -16
  24. package/dist/repository/index.d.ts +142 -16
  25. package/dist/repository/index.js +1406 -181
  26. package/dist/repository/index.js.map +1 -1
  27. package/dist/sdk/index.cjs +23 -1
  28. package/dist/sdk/index.cjs.map +1 -1
  29. package/dist/sdk/index.d.cts +2 -2
  30. package/dist/sdk/index.d.ts +2 -2
  31. package/dist/sdk/index.js +23 -1
  32. package/dist/sdk/index.js.map +1 -1
  33. package/dist/service/index.cjs +483 -105
  34. package/dist/service/index.cjs.map +1 -1
  35. package/dist/service/index.d.cts +70 -10
  36. package/dist/service/index.d.ts +70 -10
  37. package/dist/service/index.js +483 -105
  38. package/dist/service/index.js.map +1 -1
  39. package/dist/types-BmG5Omzj.d.ts +646 -0
  40. package/dist/types-C8hZMe8a.d.cts +646 -0
  41. package/dist/{types-CjLj3GW5.d.cts → types-DJ8yqaVr.d.cts} +39 -1
  42. package/dist/{types-CjLj3GW5.d.ts → types-DJ8yqaVr.d.ts} +39 -1
  43. package/package.json +9 -10
  44. package/dist/types-CZNv1U3e.d.cts +0 -305
  45. package/dist/types-G4WK0rM-.d.ts +0 -305
@@ -1,5 +1,5 @@
1
- import { b as PortableUserCredits, g as CreditCheckResult, a as CreditOperationType, c as PortableReservation, d as PortableUsageLog, D as DeductCreditsResult, S as SubscriptionTier, j as UsageHistoryResponse, f as PortableJournalEntry } from '../types-CjLj3GW5.cjs';
2
- import { I as ICreditRepository, J as JournalEntryQuery } from '../types-CZNv1U3e.cjs';
1
+ import { c as PortableUserCredits, g as CreditCheckResult, C as CreditOperationType, a as PortableReservation, d as PortableUsageLog, D as DeductCreditsResult, S as SubscriptionTier, j as UsageHistoryResponse, f as PortableJournalEntry } from '../types-DJ8yqaVr.cjs';
2
+ import { I as ICreditRepository, n as ReserveOutcome, l as ReservationTransitionOptions, C as CommitOutcome, R as ReleaseOutcome, A as AddCreditsAtomicOptions, a as AddCreditsOutcome, J as JournalEntryQuery } from '../types-C8hZMe8a.cjs';
3
3
 
4
4
  /**
5
5
  * Notification callback type for low balance notifications
@@ -22,6 +22,20 @@ interface ReserveCreditsOptions {
22
22
  * legitimately in flight.
23
23
  */
24
24
  ttlMs?: number;
25
+ /**
26
+ * Caller-supplied idempotency key, unique per user.
27
+ *
28
+ * Retrying a reserve with the same key and the same amount/operation returns
29
+ * the original reservation instead of placing a second hold — the fix for a
30
+ * webhook or job runner delivering the same request twice. Reusing a key
31
+ * with a *different* amount or operation is a
32
+ * `CreditErrorCode.IDEMPOTENCY_CONFLICT`.
33
+ *
34
+ * Requires a repository implementing the V2 boundary (see
35
+ * `supportsCreditsV2`). A legacy adapter has no unique index to enforce the
36
+ * key, so it is ignored there rather than silently promised.
37
+ */
38
+ idempotencyKey?: string;
25
39
  }
26
40
  /**
27
41
  * Options for a one-shot atomic credit deduction via `deductCredits`.
@@ -103,16 +117,55 @@ declare class CreditsService {
103
117
  */
104
118
  reserveCredits(userId: string, amount: number, operationType: CreditOperationType, options?: ReserveCreditsOptions): Promise<PortableReservation>;
105
119
  /**
106
- * Commit a reservation (phase 2 of two-phase commit - success)
107
- * Deducts credits and marks reservation as committed
108
- * Also triggers low balance notifications if balance drops below threshold
120
+ * Reserve credits and get the typed outcome instead of an exception.
121
+ *
122
+ * Prefer this over {@link reserveCredits} in async callers: it distinguishes
123
+ * a fresh hold from an idempotent replay, and reports insufficient funds as
124
+ * a value rather than as control flow.
125
+ */
126
+ reserveCreditsDetailed(userId: string, amount: number, operationType: CreditOperationType, options?: ReserveCreditsOptions): Promise<ReserveOutcome>;
127
+ /**
128
+ * Commit a reservation (phase 2 of two-phase commit - success).
129
+ *
130
+ * Idempotent: re-delivering a commit for an already-committed reservation is
131
+ * a no-op. Committing one that was released or expired throws, because the
132
+ * credits are no longer held and pretending otherwise would hide a real bug.
109
133
  */
110
- commitCredits(userId: string, reservationId: string): Promise<void>;
134
+ commitCredits(userId: string, reservationId: string, options?: ReservationTransitionOptions): Promise<void>;
111
135
  /**
112
- * Release a reservation (phase 2 of two-phase commit - failure)
113
- * Returns reserved credits and marks reservation as released
136
+ * Commit a reservation and get the typed outcome.
137
+ *
138
+ * Exactly one concurrent caller sees `committed`; that call moved the balance
139
+ * and wrote the single journal entry. On a V2 repository the journal is
140
+ * written inside the same transaction, so this method adds none of its own —
141
+ * the duplicate entry the previous implementation wrote is gone.
142
+ *
143
+ * The low-balance notification fires only for the winner, and only after the
144
+ * transaction has committed, so no callback runs inside a database
145
+ * transaction.
146
+ */
147
+ commitCreditsDetailed(userId: string, reservationId: string, options?: ReservationTransitionOptions): Promise<CommitOutcome>;
148
+ /**
149
+ * Release a reservation (phase 2 of two-phase commit - failure).
150
+ *
151
+ * Throws `RESERVATION_NOT_FOUND` for an unknown reservation, matching the
152
+ * pre-V2 behaviour — a release naming a reservation that does not exist is a
153
+ * caller bug, and swallowing it hides the bug while the credits stay held.
154
+ *
155
+ * Releasing one that is already terminal — `released`, `expired`, or
156
+ * `committed` — is a no-op, which is the behaviour this method has always
157
+ * had. A release that loses a race to a concurrent commit is the common
158
+ * case in a retry-heavy caller, and callers written against the pre-V2
159
+ * contract handle it by not handling it.
160
+ *
161
+ * That does mean this method cannot tell you that the credits were spent
162
+ * rather than returned. Use {@link releaseCreditsDetailed}, which reports
163
+ * `already_terminal` with `terminalStatus: 'committed'`, when you need to
164
+ * branch on it.
114
165
  */
115
- releaseCredits(userId: string, reservationId: string): Promise<void>;
166
+ releaseCredits(userId: string, reservationId: string, options?: ReservationTransitionOptions): Promise<void>;
167
+ /** Release a reservation and get the typed outcome. */
168
+ releaseCreditsDetailed(userId: string, reservationId: string, options?: ReservationTransitionOptions): Promise<ReleaseOutcome>;
116
169
  /**
117
170
  * Log usage for audit trail
118
171
  * @param log - Usage log data
@@ -124,8 +177,15 @@ declare class CreditsService {
124
177
  * @param amount - Credits to add
125
178
  * @param description - Transaction description
126
179
  * @param paymentRef - Optional payment reference
180
+ * @param options - Optional journal source / reference type / extra metadata
181
+ * for the credit journal entry (e.g. revenue-attribution fields).
182
+ * @returns Whether this delivery credited the account (`created`), repeated an
183
+ * earlier one (`replayed`), or reused the reference for a different credit
184
+ * event (`conflict`, which credited nothing). On a repository without
185
+ * `addCreditsV2` the result is always `created` and carries no
186
+ * deduplication guarantee — see {@link addCreditsThroughRepository}.
127
187
  */
128
- addCredits(userId: string, amount: number, description: string, paymentRef?: string): Promise<void>;
188
+ addCredits(userId: string, amount: number, description: string, paymentRef?: string, options?: AddCreditsAtomicOptions): Promise<AddCreditsOutcome>;
129
189
  /**
130
190
  * One-shot atomic "deduct-if-sufficient" credit charge.
131
191
  *
@@ -1,5 +1,5 @@
1
- import { b as PortableUserCredits, g as CreditCheckResult, a as CreditOperationType, c as PortableReservation, d as PortableUsageLog, D as DeductCreditsResult, S as SubscriptionTier, j as UsageHistoryResponse, f as PortableJournalEntry } from '../types-CjLj3GW5.js';
2
- import { I as ICreditRepository, J as JournalEntryQuery } from '../types-G4WK0rM-.js';
1
+ import { c as PortableUserCredits, g as CreditCheckResult, C as CreditOperationType, a as PortableReservation, d as PortableUsageLog, D as DeductCreditsResult, S as SubscriptionTier, j as UsageHistoryResponse, f as PortableJournalEntry } from '../types-DJ8yqaVr.js';
2
+ import { I as ICreditRepository, n as ReserveOutcome, l as ReservationTransitionOptions, C as CommitOutcome, R as ReleaseOutcome, A as AddCreditsAtomicOptions, a as AddCreditsOutcome, J as JournalEntryQuery } from '../types-BmG5Omzj.js';
3
3
 
4
4
  /**
5
5
  * Notification callback type for low balance notifications
@@ -22,6 +22,20 @@ interface ReserveCreditsOptions {
22
22
  * legitimately in flight.
23
23
  */
24
24
  ttlMs?: number;
25
+ /**
26
+ * Caller-supplied idempotency key, unique per user.
27
+ *
28
+ * Retrying a reserve with the same key and the same amount/operation returns
29
+ * the original reservation instead of placing a second hold — the fix for a
30
+ * webhook or job runner delivering the same request twice. Reusing a key
31
+ * with a *different* amount or operation is a
32
+ * `CreditErrorCode.IDEMPOTENCY_CONFLICT`.
33
+ *
34
+ * Requires a repository implementing the V2 boundary (see
35
+ * `supportsCreditsV2`). A legacy adapter has no unique index to enforce the
36
+ * key, so it is ignored there rather than silently promised.
37
+ */
38
+ idempotencyKey?: string;
25
39
  }
26
40
  /**
27
41
  * Options for a one-shot atomic credit deduction via `deductCredits`.
@@ -103,16 +117,55 @@ declare class CreditsService {
103
117
  */
104
118
  reserveCredits(userId: string, amount: number, operationType: CreditOperationType, options?: ReserveCreditsOptions): Promise<PortableReservation>;
105
119
  /**
106
- * Commit a reservation (phase 2 of two-phase commit - success)
107
- * Deducts credits and marks reservation as committed
108
- * Also triggers low balance notifications if balance drops below threshold
120
+ * Reserve credits and get the typed outcome instead of an exception.
121
+ *
122
+ * Prefer this over {@link reserveCredits} in async callers: it distinguishes
123
+ * a fresh hold from an idempotent replay, and reports insufficient funds as
124
+ * a value rather than as control flow.
125
+ */
126
+ reserveCreditsDetailed(userId: string, amount: number, operationType: CreditOperationType, options?: ReserveCreditsOptions): Promise<ReserveOutcome>;
127
+ /**
128
+ * Commit a reservation (phase 2 of two-phase commit - success).
129
+ *
130
+ * Idempotent: re-delivering a commit for an already-committed reservation is
131
+ * a no-op. Committing one that was released or expired throws, because the
132
+ * credits are no longer held and pretending otherwise would hide a real bug.
109
133
  */
110
- commitCredits(userId: string, reservationId: string): Promise<void>;
134
+ commitCredits(userId: string, reservationId: string, options?: ReservationTransitionOptions): Promise<void>;
111
135
  /**
112
- * Release a reservation (phase 2 of two-phase commit - failure)
113
- * Returns reserved credits and marks reservation as released
136
+ * Commit a reservation and get the typed outcome.
137
+ *
138
+ * Exactly one concurrent caller sees `committed`; that call moved the balance
139
+ * and wrote the single journal entry. On a V2 repository the journal is
140
+ * written inside the same transaction, so this method adds none of its own —
141
+ * the duplicate entry the previous implementation wrote is gone.
142
+ *
143
+ * The low-balance notification fires only for the winner, and only after the
144
+ * transaction has committed, so no callback runs inside a database
145
+ * transaction.
146
+ */
147
+ commitCreditsDetailed(userId: string, reservationId: string, options?: ReservationTransitionOptions): Promise<CommitOutcome>;
148
+ /**
149
+ * Release a reservation (phase 2 of two-phase commit - failure).
150
+ *
151
+ * Throws `RESERVATION_NOT_FOUND` for an unknown reservation, matching the
152
+ * pre-V2 behaviour — a release naming a reservation that does not exist is a
153
+ * caller bug, and swallowing it hides the bug while the credits stay held.
154
+ *
155
+ * Releasing one that is already terminal — `released`, `expired`, or
156
+ * `committed` — is a no-op, which is the behaviour this method has always
157
+ * had. A release that loses a race to a concurrent commit is the common
158
+ * case in a retry-heavy caller, and callers written against the pre-V2
159
+ * contract handle it by not handling it.
160
+ *
161
+ * That does mean this method cannot tell you that the credits were spent
162
+ * rather than returned. Use {@link releaseCreditsDetailed}, which reports
163
+ * `already_terminal` with `terminalStatus: 'committed'`, when you need to
164
+ * branch on it.
114
165
  */
115
- releaseCredits(userId: string, reservationId: string): Promise<void>;
166
+ releaseCredits(userId: string, reservationId: string, options?: ReservationTransitionOptions): Promise<void>;
167
+ /** Release a reservation and get the typed outcome. */
168
+ releaseCreditsDetailed(userId: string, reservationId: string, options?: ReservationTransitionOptions): Promise<ReleaseOutcome>;
116
169
  /**
117
170
  * Log usage for audit trail
118
171
  * @param log - Usage log data
@@ -124,8 +177,15 @@ declare class CreditsService {
124
177
  * @param amount - Credits to add
125
178
  * @param description - Transaction description
126
179
  * @param paymentRef - Optional payment reference
180
+ * @param options - Optional journal source / reference type / extra metadata
181
+ * for the credit journal entry (e.g. revenue-attribution fields).
182
+ * @returns Whether this delivery credited the account (`created`), repeated an
183
+ * earlier one (`replayed`), or reused the reference for a different credit
184
+ * event (`conflict`, which credited nothing). On a repository without
185
+ * `addCreditsV2` the result is always `created` and carries no
186
+ * deduplication guarantee — see {@link addCreditsThroughRepository}.
127
187
  */
128
- addCredits(userId: string, amount: number, description: string, paymentRef?: string): Promise<void>;
188
+ addCredits(userId: string, amount: number, description: string, paymentRef?: string, options?: AddCreditsAtomicOptions): Promise<AddCreditsOutcome>;
129
189
  /**
130
190
  * One-shot atomic "deduct-if-sufficient" credit charge.
131
191
  *