@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/ai.d.ts
CHANGED
|
@@ -36,14 +36,12 @@ const models = {
|
|
|
36
36
|
text : 'gemini-2.5-flash'
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
// Values mirror LiteLLM's published pricing at the time of writing. Model
|
|
43
|
-
// providers update pricing periodically — verify against
|
|
39
|
+
// Raw per-model provider rates in cents per 1,000,000 tokens. This is what
|
|
40
|
+
// Drawbridge pays the provider — the wholesale cost. Sync from LiteLLM:
|
|
44
41
|
// https://github.com/BerriAI/litellm/blob/main/litellm/model_prices_and_context_window_backup.json
|
|
45
|
-
//
|
|
46
|
-
|
|
42
|
+
// Never apply markup here; the MARKUP constant below derives the retail
|
|
43
|
+
// `pricing` table.
|
|
44
|
+
const cost = {
|
|
47
45
|
'gemini-2.5-flash' : {
|
|
48
46
|
cached : 3,
|
|
49
47
|
input : 30,
|
|
@@ -56,9 +54,37 @@ const pricing = {
|
|
|
56
54
|
}
|
|
57
55
|
};
|
|
58
56
|
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
57
|
+
// Markup multiplier applied to every model's cost to produce the retail
|
|
58
|
+
// price. 1.3 = 30% margin. Change one number, all pricing updates. Mirrors
|
|
59
|
+
// the per-customer markup Stripe rate cards used to provide; keeping it
|
|
60
|
+
// global (rather than per-model or per-plan) for simplicity — easy to
|
|
61
|
+
// refactor later if tier-based pricing becomes a real need.
|
|
62
|
+
const MARKUP = 1.3;
|
|
63
|
+
|
|
64
|
+
// Derived retail pricing in cents per 1,000,000 tokens — what users pay.
|
|
65
|
+
// Rounded so the per-1M rate is a whole-cent value; per-request rounding
|
|
66
|
+
// happens in priceForRequest via Math.ceil so we never under-bill.
|
|
67
|
+
const pricing = Object.fromEntries(
|
|
68
|
+
Object.entries( cost ).map( ( [ model, rates ] ) => [
|
|
69
|
+
model,
|
|
70
|
+
{
|
|
71
|
+
cached : Math.round( rates.cached * MARKUP ),
|
|
72
|
+
input : Math.round( rates.input * MARKUP ),
|
|
73
|
+
output : Math.round( rates.output * MARKUP )
|
|
74
|
+
}
|
|
75
|
+
] )
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const sumCents = ( rates, tokens ) => Math.ceil(
|
|
79
|
+
(
|
|
80
|
+
Number( tokens?.cached || 0 ) * rates.cached +
|
|
81
|
+
Number( tokens?.input || 0 ) * rates.input +
|
|
82
|
+
Number( tokens?.output || 0 ) * rates.output
|
|
83
|
+
) / 1_000_000
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// What we charge the user, rounded up so we never under-bill. Throws on an
|
|
87
|
+
// unknown model so a typo doesn't silently fall back to a wrong price.
|
|
62
88
|
const priceForRequest = ( model, usage ) => {
|
|
63
89
|
|
|
64
90
|
const rates = pricing[ model ];
|
|
@@ -69,19 +95,23 @@ const priceForRequest = ( model, usage ) => {
|
|
|
69
95
|
|
|
70
96
|
}
|
|
71
97
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
output : Number( usage?.output ) || 0
|
|
76
|
-
};
|
|
98
|
+
return sumCents( rates, usage );
|
|
99
|
+
|
|
100
|
+
};
|
|
77
101
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
tokens.output * rates.output
|
|
82
|
-
);
|
|
102
|
+
// What we pay the provider. Recorded on each transaction row so margin is
|
|
103
|
+
// computable per request without backfilling from git history.
|
|
104
|
+
const costForRequest = ( model, usage ) => {
|
|
83
105
|
|
|
84
|
-
|
|
106
|
+
const rates = cost[ model ];
|
|
107
|
+
|
|
108
|
+
if( !rates ){
|
|
109
|
+
|
|
110
|
+
throw new Error( `Unknown model for cost: ${ model }` );
|
|
111
|
+
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return sumCents( rates, usage );
|
|
85
115
|
|
|
86
116
|
};
|
|
87
117
|
|
|
@@ -92,30 +122,41 @@ const tokensFromMetadata = ( metadata ) => ({
|
|
|
92
122
|
output : Number( metadata?.candidatesTokenCount ) || 0
|
|
93
123
|
});
|
|
94
124
|
|
|
95
|
-
// Compute cost from response usage and atomically debit the user.
|
|
96
|
-
//
|
|
125
|
+
// Compute gross + cost from response usage and atomically debit the user.
|
|
126
|
+
// Records the full income-statement line on the transaction row's `ai`
|
|
127
|
+
// sub-doc: wholesale + retail per-1M rate snapshots, plus rolled-up cost,
|
|
128
|
+
// net, and gross totals. Margin per request is `ai.net`; margin per query
|
|
129
|
+
// is a single $sum: '$ai.net'.
|
|
97
130
|
const billRequest = async ({
|
|
98
131
|
db,
|
|
99
|
-
|
|
100
|
-
userId,
|
|
132
|
+
user,
|
|
101
133
|
model,
|
|
102
134
|
usage
|
|
103
135
|
}) => {
|
|
104
136
|
|
|
105
|
-
const
|
|
137
|
+
const gross = priceForRequest( model, usage );
|
|
138
|
+
const cogs = costForRequest( model, usage );
|
|
106
139
|
|
|
107
140
|
await debit({
|
|
108
141
|
db,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
142
|
+
user,
|
|
143
|
+
amount : gross,
|
|
144
|
+
type : 'ai',
|
|
112
145
|
category : 'usage',
|
|
113
|
-
|
|
114
|
-
|
|
146
|
+
source : 'user',
|
|
147
|
+
ai : {
|
|
115
148
|
name : 'google',
|
|
116
149
|
model,
|
|
117
150
|
tokens : usage,
|
|
118
|
-
|
|
151
|
+
rates : {
|
|
152
|
+
wholesale : cost[ model ],
|
|
153
|
+
retail : pricing[ model ]
|
|
154
|
+
},
|
|
155
|
+
totals : {
|
|
156
|
+
cost : cogs,
|
|
157
|
+
net : gross - cogs,
|
|
158
|
+
gross
|
|
159
|
+
}
|
|
119
160
|
}
|
|
120
161
|
});
|
|
121
162
|
|
|
@@ -131,8 +172,7 @@ const google = {
|
|
|
131
172
|
},
|
|
132
173
|
{
|
|
133
174
|
db,
|
|
134
|
-
|
|
135
|
-
userId
|
|
175
|
+
user
|
|
136
176
|
}
|
|
137
177
|
) => {
|
|
138
178
|
|
|
@@ -143,45 +183,36 @@ const google = {
|
|
|
143
183
|
{ text : prompt.join( '\n' ) }
|
|
144
184
|
];
|
|
145
185
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
...config,
|
|
153
|
-
responseModalities : [ 'Image' ]
|
|
154
|
-
}
|
|
155
|
-
}) );
|
|
156
|
-
|
|
157
|
-
const content = response?.candidates?.[ 0 ]?.content?.parts?.[ 0 ]?.inlineData?.data;
|
|
158
|
-
|
|
159
|
-
if( !content ){
|
|
160
|
-
|
|
161
|
-
throw new Error( 'Could not generate from prompt. Please try again with a different prompt.' );
|
|
162
|
-
|
|
186
|
+
const response = await googleCircuit( () => google_client.models.generateContent({
|
|
187
|
+
model,
|
|
188
|
+
contents : { parts },
|
|
189
|
+
config : {
|
|
190
|
+
...config,
|
|
191
|
+
responseModalities : [ 'Image' ]
|
|
163
192
|
}
|
|
193
|
+
}) );
|
|
164
194
|
|
|
165
|
-
|
|
195
|
+
const content = response?.candidates?.[ 0 ]?.content?.parts?.[ 0 ]?.inlineData?.data;
|
|
166
196
|
|
|
167
|
-
|
|
168
|
-
db,
|
|
169
|
-
authenticated,
|
|
170
|
-
userId,
|
|
171
|
-
model,
|
|
172
|
-
usage : tokensFromMetadata( response?.usageMetadata )
|
|
173
|
-
});
|
|
197
|
+
if( !content ){
|
|
174
198
|
|
|
175
|
-
|
|
199
|
+
throw new Error( 'Could not generate from prompt. Please try again with a different prompt.' );
|
|
176
200
|
|
|
177
|
-
|
|
201
|
+
}
|
|
178
202
|
|
|
179
|
-
|
|
203
|
+
if( user?.id ){
|
|
180
204
|
|
|
181
|
-
|
|
205
|
+
await billRequest({
|
|
206
|
+
db,
|
|
207
|
+
user,
|
|
208
|
+
model,
|
|
209
|
+
usage : tokensFromMetadata( response?.usageMetadata )
|
|
210
|
+
});
|
|
182
211
|
|
|
183
212
|
}
|
|
184
213
|
|
|
214
|
+
return { content };
|
|
215
|
+
|
|
185
216
|
},
|
|
186
217
|
|
|
187
218
|
text : async (
|
|
@@ -191,54 +222,44 @@ const google = {
|
|
|
191
222
|
},
|
|
192
223
|
{
|
|
193
224
|
db,
|
|
194
|
-
|
|
195
|
-
userId
|
|
225
|
+
user
|
|
196
226
|
}
|
|
197
227
|
) => {
|
|
198
228
|
|
|
199
229
|
const model = models.text;
|
|
200
230
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
model
|
|
210
|
-
}) );
|
|
211
|
-
|
|
212
|
-
const content = response?.text;
|
|
231
|
+
const response = await googleCircuit( () => google_client.models.generateContent({
|
|
232
|
+
config : {
|
|
233
|
+
...config,
|
|
234
|
+
responseModalities : [ 'Text' ]
|
|
235
|
+
},
|
|
236
|
+
contents : prompt.join( '\n' ),
|
|
237
|
+
model
|
|
238
|
+
}) );
|
|
213
239
|
|
|
214
|
-
|
|
240
|
+
const content = response?.text;
|
|
215
241
|
|
|
216
|
-
|
|
242
|
+
if( !content ){
|
|
217
243
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if( userId ){
|
|
221
|
-
|
|
222
|
-
await billRequest({
|
|
223
|
-
db,
|
|
224
|
-
authenticated,
|
|
225
|
-
userId,
|
|
226
|
-
model,
|
|
227
|
-
usage : tokensFromMetadata( response?.usageMetadata )
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
}
|
|
244
|
+
throw new Error( 'Could not generate from prompt. Please try again with a different prompt.' );
|
|
231
245
|
|
|
232
|
-
|
|
246
|
+
}
|
|
233
247
|
|
|
234
|
-
|
|
248
|
+
if( user?.id ){
|
|
235
249
|
|
|
236
|
-
|
|
250
|
+
await billRequest({
|
|
251
|
+
db,
|
|
252
|
+
user,
|
|
253
|
+
model,
|
|
254
|
+
usage : tokensFromMetadata( response?.usageMetadata )
|
|
255
|
+
});
|
|
237
256
|
|
|
238
257
|
}
|
|
239
258
|
|
|
259
|
+
return { content };
|
|
260
|
+
|
|
240
261
|
}
|
|
241
262
|
|
|
242
263
|
};
|
|
243
264
|
|
|
244
|
-
export { google, priceForRequest, pricing };
|
|
265
|
+
export { MARKUP, cost, costForRequest, google, priceForRequest, pricing };
|
package/dist/ai.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
debit
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-RW2FJVIV.js";
|
|
4
4
|
import {
|
|
5
5
|
circuit
|
|
6
6
|
} from "./chunk-6HH36OK6.js";
|
|
@@ -19,7 +19,7 @@ var models = {
|
|
|
19
19
|
image: "gemini-2.5-flash-image",
|
|
20
20
|
text: "gemini-2.5-flash"
|
|
21
21
|
};
|
|
22
|
-
var
|
|
22
|
+
var cost = {
|
|
23
23
|
"gemini-2.5-flash": {
|
|
24
24
|
cached: 3,
|
|
25
25
|
input: 30,
|
|
@@ -31,18 +31,33 @@ var pricing = {
|
|
|
31
31
|
output: 250
|
|
32
32
|
}
|
|
33
33
|
};
|
|
34
|
+
var MARKUP = 1.3;
|
|
35
|
+
var pricing = Object.fromEntries(
|
|
36
|
+
Object.entries(cost).map(([model, rates]) => [
|
|
37
|
+
model,
|
|
38
|
+
{
|
|
39
|
+
cached: Math.round(rates.cached * MARKUP),
|
|
40
|
+
input: Math.round(rates.input * MARKUP),
|
|
41
|
+
output: Math.round(rates.output * MARKUP)
|
|
42
|
+
}
|
|
43
|
+
])
|
|
44
|
+
);
|
|
45
|
+
var sumCents = (rates, tokens) => Math.ceil(
|
|
46
|
+
(Number((tokens == null ? void 0 : tokens.cached) || 0) * rates.cached + Number((tokens == null ? void 0 : tokens.input) || 0) * rates.input + Number((tokens == null ? void 0 : tokens.output) || 0) * rates.output) / 1e6
|
|
47
|
+
);
|
|
34
48
|
var priceForRequest = (model, usage) => {
|
|
35
49
|
const rates = pricing[model];
|
|
36
50
|
if (!rates) {
|
|
37
51
|
throw new Error(`Unknown model for pricing: ${model}`);
|
|
38
52
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
53
|
+
return sumCents(rates, usage);
|
|
54
|
+
};
|
|
55
|
+
var costForRequest = (model, usage) => {
|
|
56
|
+
const rates = cost[model];
|
|
57
|
+
if (!rates) {
|
|
58
|
+
throw new Error(`Unknown model for cost: ${model}`);
|
|
59
|
+
}
|
|
60
|
+
return sumCents(rates, usage);
|
|
46
61
|
};
|
|
47
62
|
var tokensFromMetadata = (metadata) => ({
|
|
48
63
|
cached: Number(metadata == null ? void 0 : metadata.cachedContentTokenCount) || 0,
|
|
@@ -51,24 +66,32 @@ var tokensFromMetadata = (metadata) => ({
|
|
|
51
66
|
});
|
|
52
67
|
var billRequest = async ({
|
|
53
68
|
db,
|
|
54
|
-
|
|
55
|
-
userId,
|
|
69
|
+
user,
|
|
56
70
|
model,
|
|
57
71
|
usage
|
|
58
72
|
}) => {
|
|
59
|
-
const
|
|
73
|
+
const gross = priceForRequest(model, usage);
|
|
74
|
+
const cogs = costForRequest(model, usage);
|
|
60
75
|
await debit({
|
|
61
76
|
db,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
77
|
+
user,
|
|
78
|
+
amount: gross,
|
|
79
|
+
type: "ai",
|
|
65
80
|
category: "usage",
|
|
66
|
-
|
|
67
|
-
|
|
81
|
+
source: "user",
|
|
82
|
+
ai: {
|
|
68
83
|
name: "google",
|
|
69
84
|
model,
|
|
70
85
|
tokens: usage,
|
|
71
|
-
|
|
86
|
+
rates: {
|
|
87
|
+
wholesale: cost[model],
|
|
88
|
+
retail: pricing[model]
|
|
89
|
+
},
|
|
90
|
+
totals: {
|
|
91
|
+
cost: cogs,
|
|
92
|
+
net: gross - cogs,
|
|
93
|
+
gross
|
|
94
|
+
}
|
|
72
95
|
}
|
|
73
96
|
});
|
|
74
97
|
};
|
|
@@ -79,8 +102,7 @@ var google = {
|
|
|
79
102
|
reference = null
|
|
80
103
|
}, {
|
|
81
104
|
db,
|
|
82
|
-
|
|
83
|
-
userId
|
|
105
|
+
user
|
|
84
106
|
}) => {
|
|
85
107
|
var _a, _b, _c, _d, _e, _f;
|
|
86
108
|
const model = models.image;
|
|
@@ -88,71 +110,63 @@ var google = {
|
|
|
88
110
|
...reference ? [{ inlineData: { data: reference.data, mimeType: reference.mimeType } }] : [],
|
|
89
111
|
{ text: prompt.join("\n") }
|
|
90
112
|
];
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
config
|
|
96
|
-
|
|
97
|
-
responseModalities: ["Image"]
|
|
98
|
-
}
|
|
99
|
-
}));
|
|
100
|
-
const content = (_f = (_e = (_d = (_c = (_b = (_a = response == null ? void 0 : response.candidates) == null ? void 0 : _a[0]) == null ? void 0 : _b.content) == null ? void 0 : _c.parts) == null ? void 0 : _d[0]) == null ? void 0 : _e.inlineData) == null ? void 0 : _f.data;
|
|
101
|
-
if (!content) {
|
|
102
|
-
throw new Error("Could not generate from prompt. Please try again with a different prompt.");
|
|
103
|
-
}
|
|
104
|
-
if (userId) {
|
|
105
|
-
await billRequest({
|
|
106
|
-
db,
|
|
107
|
-
authenticated,
|
|
108
|
-
userId,
|
|
109
|
-
model,
|
|
110
|
-
usage: tokensFromMetadata(response == null ? void 0 : response.usageMetadata)
|
|
111
|
-
});
|
|
113
|
+
const response = await googleCircuit(() => google_client.models.generateContent({
|
|
114
|
+
model,
|
|
115
|
+
contents: { parts },
|
|
116
|
+
config: {
|
|
117
|
+
...config,
|
|
118
|
+
responseModalities: ["Image"]
|
|
112
119
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
120
|
+
}));
|
|
121
|
+
const content = (_f = (_e = (_d = (_c = (_b = (_a = response == null ? void 0 : response.candidates) == null ? void 0 : _a[0]) == null ? void 0 : _b.content) == null ? void 0 : _c.parts) == null ? void 0 : _d[0]) == null ? void 0 : _e.inlineData) == null ? void 0 : _f.data;
|
|
122
|
+
if (!content) {
|
|
123
|
+
throw new Error("Could not generate from prompt. Please try again with a different prompt.");
|
|
124
|
+
}
|
|
125
|
+
if (user == null ? void 0 : user.id) {
|
|
126
|
+
await billRequest({
|
|
127
|
+
db,
|
|
128
|
+
user,
|
|
129
|
+
model,
|
|
130
|
+
usage: tokensFromMetadata(response == null ? void 0 : response.usageMetadata)
|
|
131
|
+
});
|
|
116
132
|
}
|
|
133
|
+
return { content };
|
|
117
134
|
},
|
|
118
135
|
text: async ({
|
|
119
136
|
config = {},
|
|
120
137
|
prompt = []
|
|
121
138
|
}, {
|
|
122
139
|
db,
|
|
123
|
-
|
|
124
|
-
userId
|
|
140
|
+
user
|
|
125
141
|
}) => {
|
|
126
142
|
const model = models.text;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
config
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
usage: tokensFromMetadata(response == null ? void 0 : response.usageMetadata)
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
return { content };
|
|
150
|
-
} catch (error) {
|
|
151
|
-
throw new Error((error == null ? void 0 : error.message) || "AI error, please try again");
|
|
143
|
+
const response = await googleCircuit(() => google_client.models.generateContent({
|
|
144
|
+
config: {
|
|
145
|
+
...config,
|
|
146
|
+
responseModalities: ["Text"]
|
|
147
|
+
},
|
|
148
|
+
contents: prompt.join("\n"),
|
|
149
|
+
model
|
|
150
|
+
}));
|
|
151
|
+
const content = response == null ? void 0 : response.text;
|
|
152
|
+
if (!content) {
|
|
153
|
+
throw new Error("Could not generate from prompt. Please try again with a different prompt.");
|
|
154
|
+
}
|
|
155
|
+
if (user == null ? void 0 : user.id) {
|
|
156
|
+
await billRequest({
|
|
157
|
+
db,
|
|
158
|
+
user,
|
|
159
|
+
model,
|
|
160
|
+
usage: tokensFromMetadata(response == null ? void 0 : response.usageMetadata)
|
|
161
|
+
});
|
|
152
162
|
}
|
|
163
|
+
return { content };
|
|
153
164
|
}
|
|
154
165
|
};
|
|
155
166
|
export {
|
|
167
|
+
MARKUP,
|
|
168
|
+
cost,
|
|
169
|
+
costForRequest,
|
|
156
170
|
google,
|
|
157
171
|
priceForRequest,
|
|
158
172
|
pricing
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// transactions.js
|
|
2
|
+
var insertTransaction = async ({
|
|
3
|
+
db,
|
|
4
|
+
user,
|
|
5
|
+
type,
|
|
6
|
+
category,
|
|
7
|
+
source,
|
|
8
|
+
amount,
|
|
9
|
+
_id,
|
|
10
|
+
stripeInvoiceId,
|
|
11
|
+
stripeEventId,
|
|
12
|
+
session,
|
|
13
|
+
...rest
|
|
14
|
+
}) => {
|
|
15
|
+
var _a;
|
|
16
|
+
const beforeRaw = (_a = user == null ? void 0 : user.balance) == null ? void 0 : _a[type];
|
|
17
|
+
const balance = typeof beforeRaw === "number" ? { before: beforeRaw, after: beforeRaw + amount } : void 0;
|
|
18
|
+
await db.create({
|
|
19
|
+
authenticated: user,
|
|
20
|
+
collection: "transaction",
|
|
21
|
+
data: {
|
|
22
|
+
..._id && { _id },
|
|
23
|
+
userId: user == null ? void 0 : user.id,
|
|
24
|
+
type,
|
|
25
|
+
category,
|
|
26
|
+
source,
|
|
27
|
+
amount,
|
|
28
|
+
...balance && { balance },
|
|
29
|
+
...rest,
|
|
30
|
+
...stripeInvoiceId && { stripeInvoiceId },
|
|
31
|
+
...stripeEventId && { stripeEventId }
|
|
32
|
+
},
|
|
33
|
+
...session && { options: { session } }
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
var credit = async ({
|
|
37
|
+
db,
|
|
38
|
+
user,
|
|
39
|
+
amount,
|
|
40
|
+
type,
|
|
41
|
+
category,
|
|
42
|
+
source,
|
|
43
|
+
_id,
|
|
44
|
+
stripeInvoiceId,
|
|
45
|
+
stripeEventId,
|
|
46
|
+
session,
|
|
47
|
+
...rest
|
|
48
|
+
}) => {
|
|
49
|
+
if (!Number.isInteger(amount) || amount <= 0) {
|
|
50
|
+
throw new Error(`credit() requires a positive integer amount, got ${amount}`);
|
|
51
|
+
}
|
|
52
|
+
if (!type) {
|
|
53
|
+
throw new Error('credit() requires a type (e.g., "ai")');
|
|
54
|
+
}
|
|
55
|
+
if (!source) {
|
|
56
|
+
throw new Error('credit() requires a source ("system" | "admin" | "user")');
|
|
57
|
+
}
|
|
58
|
+
await insertTransaction({
|
|
59
|
+
db,
|
|
60
|
+
user,
|
|
61
|
+
type,
|
|
62
|
+
category,
|
|
63
|
+
source,
|
|
64
|
+
amount,
|
|
65
|
+
_id,
|
|
66
|
+
stripeInvoiceId,
|
|
67
|
+
stripeEventId,
|
|
68
|
+
session,
|
|
69
|
+
...rest
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
var debit = async ({
|
|
73
|
+
db,
|
|
74
|
+
user,
|
|
75
|
+
amount,
|
|
76
|
+
type,
|
|
77
|
+
category,
|
|
78
|
+
source,
|
|
79
|
+
stripeInvoiceId,
|
|
80
|
+
stripeEventId,
|
|
81
|
+
session,
|
|
82
|
+
...rest
|
|
83
|
+
}) => {
|
|
84
|
+
if (!Number.isInteger(amount) || amount <= 0) {
|
|
85
|
+
throw new Error(`debit() requires a positive integer amount, got ${amount}`);
|
|
86
|
+
}
|
|
87
|
+
if (!type) {
|
|
88
|
+
throw new Error('debit() requires a type (e.g., "ai")');
|
|
89
|
+
}
|
|
90
|
+
if (!source) {
|
|
91
|
+
throw new Error('debit() requires a source ("system" | "admin" | "user")');
|
|
92
|
+
}
|
|
93
|
+
await insertTransaction({
|
|
94
|
+
db,
|
|
95
|
+
user,
|
|
96
|
+
type,
|
|
97
|
+
category,
|
|
98
|
+
source,
|
|
99
|
+
amount: -amount,
|
|
100
|
+
stripeInvoiceId,
|
|
101
|
+
stripeEventId,
|
|
102
|
+
session,
|
|
103
|
+
...rest
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export {
|
|
108
|
+
credit,
|
|
109
|
+
debit
|
|
110
|
+
};
|