@drawbridge/drawbridge-utils 0.0.29 → 0.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai.cjs +120 -135
- package/dist/ai.d.cts +117 -96
- package/dist/ai.d.ts +117 -96
- package/dist/ai.js +85 -71
- package/dist/chunk-RW2FJVIV.js +110 -0
- package/dist/transactions.cjs +51 -104
- package/dist/transactions.d.cts +93 -139
- package/dist/transactions.d.ts +93 -139
- package/dist/transactions.js +1 -3
- package/package.json +1 -1
- package/dist/chunk-H735KDYS.js +0 -162
package/dist/transactions.cjs
CHANGED
|
@@ -19,37 +19,38 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
// transactions.js
|
|
20
20
|
var transactions_exports = {};
|
|
21
21
|
__export(transactions_exports, {
|
|
22
|
-
InsufficientCreditsError: () => InsufficientCreditsError,
|
|
23
22
|
credit: () => credit,
|
|
24
23
|
debit: () => debit
|
|
25
24
|
});
|
|
26
25
|
module.exports = __toCommonJS(transactions_exports);
|
|
27
|
-
var
|
|
26
|
+
var insertTransaction = async ({
|
|
28
27
|
db,
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
user,
|
|
29
|
+
type,
|
|
31
30
|
category,
|
|
32
|
-
|
|
31
|
+
source,
|
|
33
32
|
amount,
|
|
34
|
-
|
|
35
|
-
provider,
|
|
33
|
+
_id,
|
|
36
34
|
stripeInvoiceId,
|
|
37
35
|
stripeEventId,
|
|
38
|
-
session
|
|
36
|
+
session,
|
|
37
|
+
...rest
|
|
39
38
|
}) => {
|
|
39
|
+
var _a;
|
|
40
|
+
const beforeRaw = (_a = user == null ? void 0 : user.balance) == null ? void 0 : _a[type];
|
|
41
|
+
const balance = typeof beforeRaw === "number" ? { before: beforeRaw, after: beforeRaw + amount } : void 0;
|
|
40
42
|
await db.create({
|
|
41
|
-
authenticated,
|
|
43
|
+
authenticated: user,
|
|
42
44
|
collection: "transaction",
|
|
43
45
|
data: {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
},
|
|
46
|
+
..._id && { _id },
|
|
47
|
+
userId: user == null ? void 0 : user.id,
|
|
48
|
+
type,
|
|
48
49
|
category,
|
|
49
|
-
|
|
50
|
+
source,
|
|
50
51
|
amount,
|
|
51
|
-
balance,
|
|
52
|
-
...
|
|
52
|
+
...balance && { balance },
|
|
53
|
+
...rest,
|
|
53
54
|
...stripeInvoiceId && { stripeInvoiceId },
|
|
54
55
|
...stripeEventId && { stripeEventId }
|
|
55
56
|
},
|
|
@@ -58,130 +59,76 @@ var recordTransaction = async ({
|
|
|
58
59
|
};
|
|
59
60
|
var credit = async ({
|
|
60
61
|
db,
|
|
61
|
-
|
|
62
|
-
userId,
|
|
62
|
+
user,
|
|
63
63
|
amount,
|
|
64
|
+
type,
|
|
64
65
|
category,
|
|
65
|
-
|
|
66
|
+
source,
|
|
67
|
+
_id,
|
|
66
68
|
stripeInvoiceId,
|
|
67
69
|
stripeEventId,
|
|
68
|
-
session
|
|
70
|
+
session,
|
|
71
|
+
...rest
|
|
69
72
|
}) => {
|
|
70
|
-
var _a, _b, _c;
|
|
71
73
|
if (!Number.isInteger(amount) || amount <= 0) {
|
|
72
74
|
throw new Error(`credit() requires a positive integer amount, got ${amount}`);
|
|
73
75
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
collection: "user",
|
|
77
|
-
query: {
|
|
78
|
-
id: userId
|
|
79
|
-
},
|
|
80
|
-
data: {
|
|
81
|
-
$inc: {
|
|
82
|
-
"balance.ai": amount
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
...session && { options: { session } }
|
|
86
|
-
});
|
|
87
|
-
const after = ((_b = (_a = result == null ? void 0 : result.value) == null ? void 0 : _a.balance) == null ? void 0 : _b.ai) ?? ((_c = result == null ? void 0 : result.balance) == null ? void 0 : _c.ai);
|
|
88
|
-
if (typeof after !== "number") {
|
|
89
|
-
throw new Error(`credit() could not read updated balance for user ${userId}`);
|
|
76
|
+
if (!type) {
|
|
77
|
+
throw new Error('credit() requires a type (e.g., "ai")');
|
|
90
78
|
}
|
|
91
|
-
|
|
92
|
-
|
|
79
|
+
if (!source) {
|
|
80
|
+
throw new Error('credit() requires a source ("system" | "admin" | "user")');
|
|
81
|
+
}
|
|
82
|
+
await insertTransaction({
|
|
93
83
|
db,
|
|
94
|
-
|
|
95
|
-
|
|
84
|
+
user,
|
|
85
|
+
type,
|
|
96
86
|
category,
|
|
97
|
-
|
|
87
|
+
source,
|
|
98
88
|
amount,
|
|
99
|
-
|
|
100
|
-
before,
|
|
101
|
-
after
|
|
102
|
-
},
|
|
89
|
+
_id,
|
|
103
90
|
stripeInvoiceId,
|
|
104
91
|
stripeEventId,
|
|
105
|
-
session
|
|
92
|
+
session,
|
|
93
|
+
...rest
|
|
106
94
|
});
|
|
107
|
-
return {
|
|
108
|
-
balance: {
|
|
109
|
-
before,
|
|
110
|
-
after
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
};
|
|
114
|
-
var InsufficientCreditsError = class extends Error {
|
|
115
|
-
constructor(message = "Insufficient AI credits") {
|
|
116
|
-
super(message);
|
|
117
|
-
this.name = "InsufficientCreditsError";
|
|
118
|
-
this.status = 402;
|
|
119
|
-
}
|
|
120
95
|
};
|
|
121
96
|
var debit = async ({
|
|
122
97
|
db,
|
|
123
|
-
|
|
124
|
-
userId,
|
|
98
|
+
user,
|
|
125
99
|
amount,
|
|
100
|
+
type,
|
|
126
101
|
category,
|
|
127
|
-
|
|
128
|
-
provider,
|
|
102
|
+
source,
|
|
129
103
|
stripeInvoiceId,
|
|
130
104
|
stripeEventId,
|
|
131
|
-
session
|
|
105
|
+
session,
|
|
106
|
+
...rest
|
|
132
107
|
}) => {
|
|
133
|
-
var _a, _b, _c;
|
|
134
108
|
if (!Number.isInteger(amount) || amount <= 0) {
|
|
135
109
|
throw new Error(`debit() requires a positive integer amount, got ${amount}`);
|
|
136
110
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
"balance.ai": {
|
|
143
|
-
$gte: amount
|
|
144
|
-
}
|
|
145
|
-
},
|
|
146
|
-
data: {
|
|
147
|
-
$inc: {
|
|
148
|
-
"balance.ai": -amount
|
|
149
|
-
}
|
|
150
|
-
},
|
|
151
|
-
...session && { options: { session } }
|
|
152
|
-
});
|
|
153
|
-
const after = ((_b = (_a = result == null ? void 0 : result.value) == null ? void 0 : _a.balance) == null ? void 0 : _b.ai) ?? ((_c = result == null ? void 0 : result.balance) == null ? void 0 : _c.ai);
|
|
154
|
-
if (typeof after !== "number") {
|
|
155
|
-
throw new InsufficientCreditsError();
|
|
111
|
+
if (!type) {
|
|
112
|
+
throw new Error('debit() requires a type (e.g., "ai")');
|
|
113
|
+
}
|
|
114
|
+
if (!source) {
|
|
115
|
+
throw new Error('debit() requires a source ("system" | "admin" | "user")');
|
|
156
116
|
}
|
|
157
|
-
|
|
158
|
-
await recordTransaction({
|
|
117
|
+
await insertTransaction({
|
|
159
118
|
db,
|
|
160
|
-
|
|
161
|
-
|
|
119
|
+
user,
|
|
120
|
+
type,
|
|
162
121
|
category,
|
|
163
|
-
|
|
122
|
+
source,
|
|
164
123
|
amount: -amount,
|
|
165
|
-
balance: {
|
|
166
|
-
before,
|
|
167
|
-
after
|
|
168
|
-
},
|
|
169
|
-
provider,
|
|
170
124
|
stripeInvoiceId,
|
|
171
125
|
stripeEventId,
|
|
172
|
-
session
|
|
126
|
+
session,
|
|
127
|
+
...rest
|
|
173
128
|
});
|
|
174
|
-
return {
|
|
175
|
-
amount: -amount,
|
|
176
|
-
balance: {
|
|
177
|
-
before,
|
|
178
|
-
after
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
129
|
};
|
|
182
130
|
// Annotate the CommonJS export names for ESM import in node:
|
|
183
131
|
0 && (module.exports = {
|
|
184
|
-
InsufficientCreditsError,
|
|
185
132
|
credit,
|
|
186
133
|
debit
|
|
187
134
|
});
|
package/dist/transactions.d.cts
CHANGED
|
@@ -1,56 +1,62 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// Ledger transaction insert helpers — no balance writes happen here.
|
|
2
|
+
//
|
|
3
|
+
// User-balance updates are the exclusive job of drawbridge-sync's
|
|
4
|
+
// stream/transaction.js change-stream listener: every insert into the
|
|
5
|
+
// `transaction` collection triggers an atomic $inc on user.balance.<type>
|
|
6
|
+
// (guarded against going negative). This decouples the API surface from
|
|
7
|
+
// the balance arithmetic and makes the `transaction` collection the single
|
|
8
|
+
// source of truth for both audit history and balance derivation.
|
|
5
9
|
//
|
|
6
10
|
// import { credit, debit } from '@drawbridge/drawbridge-utils/transactions';
|
|
7
11
|
//
|
|
8
|
-
// await credit({ db,
|
|
9
|
-
// category: 'promotional',
|
|
12
|
+
// await credit({ db, user, amount: 500,
|
|
13
|
+
// type: 'ai', category: 'promotional', source: 'system' });
|
|
10
14
|
//
|
|
11
|
-
// await debit({ db,
|
|
12
|
-
//
|
|
13
|
-
//
|
|
15
|
+
// await debit({ db, user, amount: 6,
|
|
16
|
+
// type: 'ai', category: 'usage', source: 'user',
|
|
17
|
+
// ai: { name, model, tokens, rates, totals } });
|
|
14
18
|
//
|
|
15
|
-
// `db` is the wrapper returned by @drawbridge/mongodb — credit/debit
|
|
16
|
-
// against its `.create
|
|
19
|
+
// `db` is the wrapper returned by @drawbridge/mongodb — credit/debit
|
|
20
|
+
// duck-type against its `.create` interface so this module has no hard dep.
|
|
17
21
|
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
// the
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// contract.
|
|
27
|
-
const recordTransaction = async ({
|
|
22
|
+
// `user` is the user the transaction is being recorded against. The helper
|
|
23
|
+
// reads `user.balance.<type>` to stamp `balance: { before, after }` on the
|
|
24
|
+
// row as an audit snapshot of what the actor saw at action time — useful
|
|
25
|
+
// for support / dispute resolution. When `balance.<type>` isn't on the user
|
|
26
|
+
// (fresh signup, webhook-initiated row), the snapshot is omitted, which is
|
|
27
|
+
// the honest representation of "no prior view to record".
|
|
28
|
+
|
|
29
|
+
const insertTransaction = async ({
|
|
28
30
|
db,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
user,
|
|
32
|
+
type,
|
|
31
33
|
category,
|
|
32
|
-
|
|
34
|
+
source,
|
|
33
35
|
amount,
|
|
34
|
-
|
|
35
|
-
provider,
|
|
36
|
+
_id,
|
|
36
37
|
stripeInvoiceId,
|
|
37
38
|
stripeEventId,
|
|
38
|
-
session
|
|
39
|
+
session,
|
|
40
|
+
...rest
|
|
39
41
|
}) => {
|
|
40
42
|
|
|
43
|
+
const beforeRaw = user?.balance?.[ type ];
|
|
44
|
+
const balance = typeof beforeRaw === 'number'
|
|
45
|
+
? { before : beforeRaw, after : beforeRaw + amount }
|
|
46
|
+
: undefined;
|
|
47
|
+
|
|
41
48
|
await db.create({
|
|
42
|
-
authenticated,
|
|
49
|
+
authenticated : user,
|
|
43
50
|
collection : 'transaction',
|
|
44
51
|
data : {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
},
|
|
52
|
+
...( _id && { _id }),
|
|
53
|
+
userId : user?.id,
|
|
54
|
+
type,
|
|
49
55
|
category,
|
|
50
|
-
|
|
56
|
+
source,
|
|
51
57
|
amount,
|
|
52
|
-
balance,
|
|
53
|
-
...
|
|
58
|
+
...( balance && { balance }),
|
|
59
|
+
...rest,
|
|
54
60
|
...( stripeInvoiceId && { stripeInvoiceId }),
|
|
55
61
|
...( stripeEventId && { stripeEventId })
|
|
56
62
|
},
|
|
@@ -59,20 +65,22 @@ const recordTransaction = async ({
|
|
|
59
65
|
|
|
60
66
|
};
|
|
61
67
|
|
|
62
|
-
// Additive:
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
68
|
+
// Additive: record a credit. The stream handler does the $inc separately.
|
|
69
|
+
// Optional `_id` lets callers pre-generate the transaction's ObjectId — the
|
|
70
|
+
// top-up route stamps it onto the Stripe invoice metadata so Stripe-side and
|
|
71
|
+
// Mongo-side IDs match for cross-system reconciliation.
|
|
66
72
|
const credit = async ({
|
|
67
73
|
db,
|
|
68
|
-
|
|
69
|
-
userId,
|
|
74
|
+
user,
|
|
70
75
|
amount,
|
|
76
|
+
type,
|
|
71
77
|
category,
|
|
72
|
-
|
|
78
|
+
source,
|
|
79
|
+
_id,
|
|
73
80
|
stripeInvoiceId,
|
|
74
81
|
stripeEventId,
|
|
75
|
-
session
|
|
82
|
+
session,
|
|
83
|
+
...rest
|
|
76
84
|
}) => {
|
|
77
85
|
|
|
78
86
|
if( !Number.isInteger( amount ) || amount <= 0 ){
|
|
@@ -81,83 +89,56 @@ const credit = async ({
|
|
|
81
89
|
|
|
82
90
|
}
|
|
83
91
|
|
|
84
|
-
|
|
85
|
-
authenticated,
|
|
86
|
-
collection : 'user',
|
|
87
|
-
query : {
|
|
88
|
-
id : userId
|
|
89
|
-
},
|
|
90
|
-
data : {
|
|
91
|
-
$inc : {
|
|
92
|
-
'balance.ai' : amount
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
...( session && { options : { session } })
|
|
96
|
-
});
|
|
92
|
+
if( !type ){
|
|
97
93
|
|
|
98
|
-
|
|
94
|
+
throw new Error( 'credit() requires a type (e.g., "ai")' );
|
|
99
95
|
|
|
100
|
-
|
|
96
|
+
}
|
|
101
97
|
|
|
102
|
-
|
|
98
|
+
if( !source ){
|
|
103
99
|
|
|
104
|
-
|
|
100
|
+
throw new Error( 'credit() requires a source ("system" | "admin" | "user")' );
|
|
105
101
|
|
|
106
|
-
|
|
102
|
+
}
|
|
107
103
|
|
|
108
|
-
await
|
|
104
|
+
await insertTransaction({
|
|
109
105
|
db,
|
|
110
|
-
|
|
111
|
-
|
|
106
|
+
user,
|
|
107
|
+
type,
|
|
112
108
|
category,
|
|
113
|
-
|
|
109
|
+
source,
|
|
114
110
|
amount,
|
|
115
|
-
|
|
116
|
-
before,
|
|
117
|
-
after
|
|
118
|
-
},
|
|
111
|
+
_id,
|
|
119
112
|
stripeInvoiceId,
|
|
120
113
|
stripeEventId,
|
|
121
|
-
session
|
|
114
|
+
session,
|
|
115
|
+
...rest
|
|
122
116
|
});
|
|
123
117
|
|
|
124
|
-
return {
|
|
125
|
-
balance : {
|
|
126
|
-
before,
|
|
127
|
-
after
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
|
|
131
118
|
};
|
|
132
119
|
|
|
133
|
-
// Subtractive:
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
this.status = 402;
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
|
|
120
|
+
// Subtractive: record a debit (negative amount). The stream handler's $inc
|
|
121
|
+
// is guarded against negative balance — if the resulting balance would be
|
|
122
|
+
// less than zero, the $inc is skipped and the row stays as audit-only.
|
|
123
|
+
// Callers (e.g., ai.js) pre-compute `amount` from their domain-specific
|
|
124
|
+
// pricing.
|
|
125
|
+
//
|
|
126
|
+
// Note: there is no synchronous gate here. The atomic gate-and-decrement
|
|
127
|
+
// pattern moves to the AI middleware (`balance.<type> >= MAX_REQUEST_CENTS`)
|
|
128
|
+
// plus the stream-side $inc guard. Concurrent debits with low balance can
|
|
129
|
+
// race past the middleware check but the stream guard prevents the actual
|
|
130
|
+
// balance from going negative.
|
|
150
131
|
const debit = async ({
|
|
151
132
|
db,
|
|
152
|
-
|
|
153
|
-
userId,
|
|
133
|
+
user,
|
|
154
134
|
amount,
|
|
135
|
+
type,
|
|
155
136
|
category,
|
|
156
|
-
|
|
157
|
-
provider,
|
|
137
|
+
source,
|
|
158
138
|
stripeInvoiceId,
|
|
159
139
|
stripeEventId,
|
|
160
|
-
session
|
|
140
|
+
session,
|
|
141
|
+
...rest
|
|
161
142
|
}) => {
|
|
162
143
|
|
|
163
144
|
if( !Number.isInteger( amount ) || amount <= 0 ){
|
|
@@ -166,58 +147,31 @@ const debit = async ({
|
|
|
166
147
|
|
|
167
148
|
}
|
|
168
149
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
query : {
|
|
173
|
-
id : userId,
|
|
174
|
-
'balance.ai' : {
|
|
175
|
-
$gte : amount
|
|
176
|
-
}
|
|
177
|
-
},
|
|
178
|
-
data : {
|
|
179
|
-
$inc : {
|
|
180
|
-
'balance.ai' : -amount
|
|
181
|
-
}
|
|
182
|
-
},
|
|
183
|
-
...( session && { options : { session } })
|
|
184
|
-
});
|
|
150
|
+
if( !type ){
|
|
151
|
+
|
|
152
|
+
throw new Error( 'debit() requires a type (e.g., "ai")' );
|
|
185
153
|
|
|
186
|
-
|
|
154
|
+
}
|
|
187
155
|
|
|
188
|
-
if(
|
|
156
|
+
if( !source ){
|
|
189
157
|
|
|
190
|
-
throw new
|
|
158
|
+
throw new Error( 'debit() requires a source ("system" | "admin" | "user")' );
|
|
191
159
|
|
|
192
160
|
}
|
|
193
161
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
await recordTransaction({
|
|
162
|
+
await insertTransaction({
|
|
197
163
|
db,
|
|
198
|
-
|
|
199
|
-
|
|
164
|
+
user,
|
|
165
|
+
type,
|
|
200
166
|
category,
|
|
201
|
-
|
|
167
|
+
source,
|
|
202
168
|
amount : -amount,
|
|
203
|
-
balance : {
|
|
204
|
-
before,
|
|
205
|
-
after
|
|
206
|
-
},
|
|
207
|
-
provider,
|
|
208
169
|
stripeInvoiceId,
|
|
209
170
|
stripeEventId,
|
|
210
|
-
session
|
|
171
|
+
session,
|
|
172
|
+
...rest
|
|
211
173
|
});
|
|
212
174
|
|
|
213
|
-
return {
|
|
214
|
-
amount : -amount,
|
|
215
|
-
balance : {
|
|
216
|
-
before,
|
|
217
|
-
after
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
|
-
|
|
221
175
|
};
|
|
222
176
|
|
|
223
|
-
export {
|
|
177
|
+
export { credit, debit };
|