@aipricinglab/sdk 0.1.0 → 0.2.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.
- package/README.md +27 -2
- package/dist/index.d.mts +12 -5
- package/dist/index.d.ts +12 -5
- package/dist/index.js +25 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +25 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -122,7 +122,7 @@ await apl.track('user_abc123', 'video.runway-gen3', 8);
|
|
|
122
122
|
## Check without charging
|
|
123
123
|
|
|
124
124
|
```ts
|
|
125
|
-
const { allowed, reasons } = await apl.canUse('user_abc123', 'image.flux-pro');
|
|
125
|
+
const { allowed, matched, reasons } = await apl.canUse('user_abc123', 'image.flux-pro');
|
|
126
126
|
|
|
127
127
|
if (!allowed) {
|
|
128
128
|
return showUpgradePrompt(reasons);
|
|
@@ -137,6 +137,17 @@ if (await apl.can('user_abc123', 'image.flux-pro')) {
|
|
|
137
137
|
}
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
+
### Fail-closed by default
|
|
141
|
+
|
|
142
|
+
`canUse` and `reserve` return `{ allowed: false, matched: false }` when:
|
|
143
|
+
|
|
144
|
+
- The `event` string doesn't match any limit group on the user's plan → `reasons: ['unmatched_event']` (typo, missing limit group, or mistyped event name).
|
|
145
|
+
- The user has no active subscription on file → `reasons: ['no_subscription']` (you forgot to call `upsertSubscription`).
|
|
146
|
+
|
|
147
|
+
This is intentional — silent fail-open is a footgun. To make typos and missing setup obvious during development, the SDK also `console.warn`s once per call when `matched === false` and `process.env.NODE_ENV !== 'production'`. Production stays silent.
|
|
148
|
+
|
|
149
|
+
Every event sent to `track()` is *recorded regardless* (even unmatched / no-subscription) so you can inspect them in the dashboard's **Events** activity feed, which shows status badges (Counted / Not matched / Limit reached / No plan) and "did you mean?" suggestions for typos.
|
|
150
|
+
|
|
140
151
|
## Variant-gated plan rows
|
|
141
152
|
|
|
142
153
|
Some plans gate a model by output tier — e.g. *"100 standard images, 10 4K images"*. Pass the `variant` key in `metadata` so the right bucket is incremented:
|
|
@@ -183,6 +194,20 @@ await apl.upsertSubscription({
|
|
|
183
194
|
});
|
|
184
195
|
```
|
|
185
196
|
|
|
197
|
+
Calling `upsertSubscription` with the **same** `planId` the user is already on is a no-op: counters keep ticking, `started_at` is preserved, periods don't reset. Safe to call on every login or webhook retry.
|
|
198
|
+
|
|
199
|
+
### Plan-change semantics
|
|
200
|
+
|
|
201
|
+
When you move a user onto a *different* plan mid-period, each limit group on the new plan picks one of three behaviors (configured per-plan in the dashboard's **Advanced** section):
|
|
202
|
+
|
|
203
|
+
| Mode | What happens to counters at switch time |
|
|
204
|
+
|---|---|
|
|
205
|
+
| **`carry`** *(default)* | Existing counters with the same limit-group ID continue. Brand-new groups start at 0. |
|
|
206
|
+
| **`reset`** | Counters for the new plan's groups are wiped — the user starts the period from 0. |
|
|
207
|
+
| **`block`** | Counters are pre-filled to quota; `canUse` / `reserve` return `limit_reached` until the next period rollover. |
|
|
208
|
+
|
|
209
|
+
This lets you choose the right cancellation/upgrade UX — generous (`carry`), clean-slate (`reset`), or anti-abuse (`block`, which closes the "free→pro→cancel→fresh free quota" cycling exploit).
|
|
210
|
+
|
|
186
211
|
## Error handling
|
|
187
212
|
|
|
188
213
|
Every method throws a typed `AiPricingLabError` on failure:
|
|
@@ -205,7 +230,7 @@ try {
|
|
|
205
230
|
| Method | Purpose |
|
|
206
231
|
|---|---|
|
|
207
232
|
| `track(userId, event, quantity?, metadata?)` | Record consumption. |
|
|
208
|
-
| `canUse(userId, event, quantity?, metadata?)` | Check if allowed; returns `{ allowed, reasons, details }`. |
|
|
233
|
+
| `canUse(userId, event, quantity?, metadata?)` | Check if allowed; returns `{ allowed, matched, reasons, details }`. |
|
|
209
234
|
| `can(userId, event, quantity?, metadata?)` | Boolean shorthand for `canUse`. |
|
|
210
235
|
| `reserve(userId, event, quantity?, metadata?)` | Atomically hold quota; returns a `reservationId`. |
|
|
211
236
|
| `commit(reservationId)` | Confirm a reservation. |
|
package/dist/index.d.mts
CHANGED
|
@@ -3,12 +3,14 @@ interface MatchRule {
|
|
|
3
3
|
event: string;
|
|
4
4
|
metadata?: Record<string, string> | undefined;
|
|
5
5
|
}
|
|
6
|
+
type OnPlanChange = 'carry' | 'reset' | 'block';
|
|
6
7
|
interface LimitGroup {
|
|
7
8
|
id: string;
|
|
8
9
|
label: string;
|
|
9
10
|
unit: LimitUnit;
|
|
10
11
|
quota: number;
|
|
11
12
|
matches: MatchRule[];
|
|
13
|
+
onPlanChange?: OnPlanChange;
|
|
12
14
|
}
|
|
13
15
|
interface PlanLimits {
|
|
14
16
|
groups: LimitGroup[];
|
|
@@ -16,8 +18,11 @@ interface PlanLimits {
|
|
|
16
18
|
type PeriodType = 'daily' | 'weekly' | 'monthly' | 'lifetime';
|
|
17
19
|
type PeriodAnchor = 'subscription_start' | 'calendar';
|
|
18
20
|
type EventMetadata = Record<string, string>;
|
|
21
|
+
type MatchStatus = 'matched' | 'unmatched' | 'blocked' | 'no_subscription';
|
|
19
22
|
interface TrackResponseData {
|
|
20
23
|
eventId: string;
|
|
24
|
+
matchStatus: MatchStatus;
|
|
25
|
+
matchedGroupIds: string[];
|
|
21
26
|
counters: CounterSummary[];
|
|
22
27
|
}
|
|
23
28
|
interface CounterSummary {
|
|
@@ -25,7 +30,7 @@ interface CounterSummary {
|
|
|
25
30
|
count: number;
|
|
26
31
|
costCents: number;
|
|
27
32
|
}
|
|
28
|
-
type ErrorCode = 'not_found' | 'invalid_key' | 'requires_secret_key' | 'limit_reached' | 'workspace_limit_reached' | 'invalid_request' | 'reservation_expired' | 'reservation_not_pending' | 'internal_error' | 'not_implemented';
|
|
33
|
+
type ErrorCode = 'not_found' | 'invalid_key' | 'requires_secret_key' | 'limit_reached' | 'unmatched_event' | 'no_subscription' | 'workspace_limit_reached' | 'invalid_request' | 'reservation_expired' | 'reservation_not_pending' | 'internal_error' | 'not_implemented';
|
|
29
34
|
interface ApiError {
|
|
30
35
|
code: ErrorCode;
|
|
31
36
|
message: string;
|
|
@@ -41,6 +46,7 @@ interface ApiFail {
|
|
|
41
46
|
type ApiResponse<T> = ApiOk<T> | ApiFail;
|
|
42
47
|
interface CanUseResponseData {
|
|
43
48
|
allowed: boolean;
|
|
49
|
+
matched: boolean;
|
|
44
50
|
reasons: string[];
|
|
45
51
|
details: {
|
|
46
52
|
groupId: string;
|
|
@@ -51,6 +57,7 @@ interface CanUseResponseData {
|
|
|
51
57
|
}
|
|
52
58
|
interface ReserveResponseData {
|
|
53
59
|
allowed: boolean;
|
|
60
|
+
matched: boolean;
|
|
54
61
|
reservationId?: string;
|
|
55
62
|
expiresAt?: string;
|
|
56
63
|
reasons?: string[];
|
|
@@ -88,7 +95,7 @@ declare class AiPricingLabClient {
|
|
|
88
95
|
* Record that a user consumed something.
|
|
89
96
|
*
|
|
90
97
|
* `metadata` is matched against the `metadata` filters on plan match rules.
|
|
91
|
-
* The dashboard uses one reserved key
|
|
98
|
+
* The dashboard uses one reserved key - **`variant`** - when a plan row gates
|
|
92
99
|
* a model to a specific output tier (e.g. image quality `1k`/`2k`/`4k`,
|
|
93
100
|
* video tier `standard`/`fast`/`lite`). Pass it through so variant-gated
|
|
94
101
|
* buckets count the event:
|
|
@@ -102,7 +109,7 @@ declare class AiPricingLabClient {
|
|
|
102
109
|
* Use `.can()` if you only need the boolean.
|
|
103
110
|
*
|
|
104
111
|
* Pass `metadata.variant` (e.g. `'4k'`, `'standard'`) when a plan row gates
|
|
105
|
-
* the model to a specific output tier
|
|
112
|
+
* the model to a specific output tier - see {@link AiPricingLabClient.track}.
|
|
106
113
|
*/
|
|
107
114
|
canUse(userId: string, event: string, quantity?: number, metadata?: EventMetadata): Promise<CanUseResponseData>;
|
|
108
115
|
/** Shorthand: returns `true` if allowed, `false` if the user hit a limit. */
|
|
@@ -112,7 +119,7 @@ declare class AiPricingLabClient {
|
|
|
112
119
|
* On success call `commit(reservationId)`, on failure call `release(reservationId)`.
|
|
113
120
|
*
|
|
114
121
|
* Pass `metadata.variant` (e.g. `'4k'`, `'standard'`) when a plan row gates
|
|
115
|
-
* the model to a specific output tier
|
|
122
|
+
* the model to a specific output tier - see {@link AiPricingLabClient.track}.
|
|
116
123
|
*/
|
|
117
124
|
reserve(userId: string, event: string, quantity?: number, metadata?: EventMetadata): Promise<ReserveResponseData>;
|
|
118
125
|
/** Confirm a reservation after a successful AI call. */
|
|
@@ -122,7 +129,7 @@ declare class AiPricingLabClient {
|
|
|
122
129
|
/**
|
|
123
130
|
* Get current usage counters for a user.
|
|
124
131
|
*
|
|
125
|
-
* Routes to `/api/v1/usage/me` for public (`pk_live_`) keys
|
|
132
|
+
* Routes to `/api/v1/usage/me` for public (`pk_live_`) keys - that endpoint
|
|
126
133
|
* is the only one that accepts public keys. Secret keys hit `/api/v1/usage`.
|
|
127
134
|
*/
|
|
128
135
|
usage(userId: string, event?: string): Promise<UsageResponseData>;
|
package/dist/index.d.ts
CHANGED
|
@@ -3,12 +3,14 @@ interface MatchRule {
|
|
|
3
3
|
event: string;
|
|
4
4
|
metadata?: Record<string, string> | undefined;
|
|
5
5
|
}
|
|
6
|
+
type OnPlanChange = 'carry' | 'reset' | 'block';
|
|
6
7
|
interface LimitGroup {
|
|
7
8
|
id: string;
|
|
8
9
|
label: string;
|
|
9
10
|
unit: LimitUnit;
|
|
10
11
|
quota: number;
|
|
11
12
|
matches: MatchRule[];
|
|
13
|
+
onPlanChange?: OnPlanChange;
|
|
12
14
|
}
|
|
13
15
|
interface PlanLimits {
|
|
14
16
|
groups: LimitGroup[];
|
|
@@ -16,8 +18,11 @@ interface PlanLimits {
|
|
|
16
18
|
type PeriodType = 'daily' | 'weekly' | 'monthly' | 'lifetime';
|
|
17
19
|
type PeriodAnchor = 'subscription_start' | 'calendar';
|
|
18
20
|
type EventMetadata = Record<string, string>;
|
|
21
|
+
type MatchStatus = 'matched' | 'unmatched' | 'blocked' | 'no_subscription';
|
|
19
22
|
interface TrackResponseData {
|
|
20
23
|
eventId: string;
|
|
24
|
+
matchStatus: MatchStatus;
|
|
25
|
+
matchedGroupIds: string[];
|
|
21
26
|
counters: CounterSummary[];
|
|
22
27
|
}
|
|
23
28
|
interface CounterSummary {
|
|
@@ -25,7 +30,7 @@ interface CounterSummary {
|
|
|
25
30
|
count: number;
|
|
26
31
|
costCents: number;
|
|
27
32
|
}
|
|
28
|
-
type ErrorCode = 'not_found' | 'invalid_key' | 'requires_secret_key' | 'limit_reached' | 'workspace_limit_reached' | 'invalid_request' | 'reservation_expired' | 'reservation_not_pending' | 'internal_error' | 'not_implemented';
|
|
33
|
+
type ErrorCode = 'not_found' | 'invalid_key' | 'requires_secret_key' | 'limit_reached' | 'unmatched_event' | 'no_subscription' | 'workspace_limit_reached' | 'invalid_request' | 'reservation_expired' | 'reservation_not_pending' | 'internal_error' | 'not_implemented';
|
|
29
34
|
interface ApiError {
|
|
30
35
|
code: ErrorCode;
|
|
31
36
|
message: string;
|
|
@@ -41,6 +46,7 @@ interface ApiFail {
|
|
|
41
46
|
type ApiResponse<T> = ApiOk<T> | ApiFail;
|
|
42
47
|
interface CanUseResponseData {
|
|
43
48
|
allowed: boolean;
|
|
49
|
+
matched: boolean;
|
|
44
50
|
reasons: string[];
|
|
45
51
|
details: {
|
|
46
52
|
groupId: string;
|
|
@@ -51,6 +57,7 @@ interface CanUseResponseData {
|
|
|
51
57
|
}
|
|
52
58
|
interface ReserveResponseData {
|
|
53
59
|
allowed: boolean;
|
|
60
|
+
matched: boolean;
|
|
54
61
|
reservationId?: string;
|
|
55
62
|
expiresAt?: string;
|
|
56
63
|
reasons?: string[];
|
|
@@ -88,7 +95,7 @@ declare class AiPricingLabClient {
|
|
|
88
95
|
* Record that a user consumed something.
|
|
89
96
|
*
|
|
90
97
|
* `metadata` is matched against the `metadata` filters on plan match rules.
|
|
91
|
-
* The dashboard uses one reserved key
|
|
98
|
+
* The dashboard uses one reserved key - **`variant`** - when a plan row gates
|
|
92
99
|
* a model to a specific output tier (e.g. image quality `1k`/`2k`/`4k`,
|
|
93
100
|
* video tier `standard`/`fast`/`lite`). Pass it through so variant-gated
|
|
94
101
|
* buckets count the event:
|
|
@@ -102,7 +109,7 @@ declare class AiPricingLabClient {
|
|
|
102
109
|
* Use `.can()` if you only need the boolean.
|
|
103
110
|
*
|
|
104
111
|
* Pass `metadata.variant` (e.g. `'4k'`, `'standard'`) when a plan row gates
|
|
105
|
-
* the model to a specific output tier
|
|
112
|
+
* the model to a specific output tier - see {@link AiPricingLabClient.track}.
|
|
106
113
|
*/
|
|
107
114
|
canUse(userId: string, event: string, quantity?: number, metadata?: EventMetadata): Promise<CanUseResponseData>;
|
|
108
115
|
/** Shorthand: returns `true` if allowed, `false` if the user hit a limit. */
|
|
@@ -112,7 +119,7 @@ declare class AiPricingLabClient {
|
|
|
112
119
|
* On success call `commit(reservationId)`, on failure call `release(reservationId)`.
|
|
113
120
|
*
|
|
114
121
|
* Pass `metadata.variant` (e.g. `'4k'`, `'standard'`) when a plan row gates
|
|
115
|
-
* the model to a specific output tier
|
|
122
|
+
* the model to a specific output tier - see {@link AiPricingLabClient.track}.
|
|
116
123
|
*/
|
|
117
124
|
reserve(userId: string, event: string, quantity?: number, metadata?: EventMetadata): Promise<ReserveResponseData>;
|
|
118
125
|
/** Confirm a reservation after a successful AI call. */
|
|
@@ -122,7 +129,7 @@ declare class AiPricingLabClient {
|
|
|
122
129
|
/**
|
|
123
130
|
* Get current usage counters for a user.
|
|
124
131
|
*
|
|
125
|
-
* Routes to `/api/v1/usage/me` for public (`pk_live_`) keys
|
|
132
|
+
* Routes to `/api/v1/usage/me` for public (`pk_live_`) keys - that endpoint
|
|
126
133
|
* is the only one that accepts public keys. Secret keys hit `/api/v1/usage`.
|
|
127
134
|
*/
|
|
128
135
|
usage(userId: string, event?: string): Promise<UsageResponseData>;
|
package/dist/index.js
CHANGED
|
@@ -72,7 +72,7 @@ var AiPricingLabClient = class {
|
|
|
72
72
|
* Record that a user consumed something.
|
|
73
73
|
*
|
|
74
74
|
* `metadata` is matched against the `metadata` filters on plan match rules.
|
|
75
|
-
* The dashboard uses one reserved key
|
|
75
|
+
* The dashboard uses one reserved key - **`variant`** - when a plan row gates
|
|
76
76
|
* a model to a specific output tier (e.g. image quality `1k`/`2k`/`4k`,
|
|
77
77
|
* video tier `standard`/`fast`/`lite`). Pass it through so variant-gated
|
|
78
78
|
* buckets count the event:
|
|
@@ -93,15 +93,17 @@ var AiPricingLabClient = class {
|
|
|
93
93
|
* Use `.can()` if you only need the boolean.
|
|
94
94
|
*
|
|
95
95
|
* Pass `metadata.variant` (e.g. `'4k'`, `'standard'`) when a plan row gates
|
|
96
|
-
* the model to a specific output tier
|
|
96
|
+
* the model to a specific output tier - see {@link AiPricingLabClient.track}.
|
|
97
97
|
*/
|
|
98
98
|
async canUse(userId, event, quantity = 1, metadata) {
|
|
99
|
-
|
|
99
|
+
const result = await request(this.baseUrl, this.apiKey, "POST", "/api/v1/can-use", {
|
|
100
100
|
userId,
|
|
101
101
|
event,
|
|
102
102
|
quantity,
|
|
103
103
|
metadata
|
|
104
104
|
});
|
|
105
|
+
warnIfUnmatched("canUse", event, result);
|
|
106
|
+
return result;
|
|
105
107
|
}
|
|
106
108
|
/** Shorthand: returns `true` if allowed, `false` if the user hit a limit. */
|
|
107
109
|
async can(userId, event, quantity = 1, metadata) {
|
|
@@ -113,15 +115,17 @@ var AiPricingLabClient = class {
|
|
|
113
115
|
* On success call `commit(reservationId)`, on failure call `release(reservationId)`.
|
|
114
116
|
*
|
|
115
117
|
* Pass `metadata.variant` (e.g. `'4k'`, `'standard'`) when a plan row gates
|
|
116
|
-
* the model to a specific output tier
|
|
118
|
+
* the model to a specific output tier - see {@link AiPricingLabClient.track}.
|
|
117
119
|
*/
|
|
118
120
|
async reserve(userId, event, quantity = 1, metadata) {
|
|
119
|
-
|
|
121
|
+
const result = await request(this.baseUrl, this.apiKey, "POST", "/api/v1/reserve", {
|
|
120
122
|
userId,
|
|
121
123
|
event,
|
|
122
124
|
quantity,
|
|
123
125
|
metadata
|
|
124
126
|
});
|
|
127
|
+
warnIfUnmatched("reserve", event, result);
|
|
128
|
+
return result;
|
|
125
129
|
}
|
|
126
130
|
/** Confirm a reservation after a successful AI call. */
|
|
127
131
|
async commit(reservationId) {
|
|
@@ -138,7 +142,7 @@ var AiPricingLabClient = class {
|
|
|
138
142
|
/**
|
|
139
143
|
* Get current usage counters for a user.
|
|
140
144
|
*
|
|
141
|
-
* Routes to `/api/v1/usage/me` for public (`pk_live_`) keys
|
|
145
|
+
* Routes to `/api/v1/usage/me` for public (`pk_live_`) keys - that endpoint
|
|
142
146
|
* is the only one that accepts public keys. Secret keys hit `/api/v1/usage`.
|
|
143
147
|
*/
|
|
144
148
|
async usage(userId, event) {
|
|
@@ -161,6 +165,21 @@ var AiPricingLabClient = class {
|
|
|
161
165
|
function createClient(opts) {
|
|
162
166
|
return new AiPricingLabClient(opts);
|
|
163
167
|
}
|
|
168
|
+
function warnIfUnmatched(method, event, result) {
|
|
169
|
+
const proc = globalThis.process;
|
|
170
|
+
if (!proc || proc.env?.["NODE_ENV"] === "production") return;
|
|
171
|
+
if (result.matched !== false) return;
|
|
172
|
+
const reason = result.reasons?.[0];
|
|
173
|
+
if (reason === "unmatched_event") {
|
|
174
|
+
console.warn(
|
|
175
|
+
`[AiPricingLab] ${method}(${JSON.stringify(event)}) returned matched: false (unmatched_event). No limit group on this user's plan covers this event_type. Check for typos or define a matching limit group in the dashboard.`
|
|
176
|
+
);
|
|
177
|
+
} else if (reason === "no_subscription") {
|
|
178
|
+
console.warn(
|
|
179
|
+
`[AiPricingLab] ${method}(${JSON.stringify(event)}) returned matched: false (no_subscription). This user has no active subscription. Call upsertSubscription({ userId, planId }) before metering.`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
164
183
|
// Annotate the CommonJS export names for ESM import in node:
|
|
165
184
|
0 && (module.exports = {
|
|
166
185
|
AiPricingLabClient,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/http.ts","../src/client.ts"],"sourcesContent":["export { AiPricingLabClient, createClient } from './client';\nexport type { ClientOptions } from './client';\nexport { AiPricingLabError } from './errors';\nexport type {\n EventMetadata,\n TrackResponseData,\n CounterSummary,\n CanUseResponseData,\n ReserveResponseData,\n UsageResponseData,\n UpsertSubscriptionRequest,\n UpsertSubscriptionResponseData,\n LimitGroup,\n LimitUnit,\n MatchRule,\n PlanLimits,\n PeriodType,\n PeriodAnchor,\n ErrorCode,\n ApiError,\n ApiResponse,\n} from './types';\n","import type { ErrorCode } from './types';\n\nexport class AiPricingLabError extends Error {\n readonly code: ErrorCode;\n readonly status: number;\n\n constructor(opts: { code: ErrorCode; message: string; status: number }) {\n super(opts.message);\n this.name = 'AiPricingLabError';\n this.code = opts.code;\n this.status = opts.status;\n }\n}\n","// Phase 9: internal fetch wrapper\nimport type { ApiResponse, ErrorCode } from './types';\nimport { AiPricingLabError } from './errors';\n\nexport async function request<T>(\n baseUrl: string,\n apiKey: string,\n method: string,\n path: string,\n body?: unknown,\n): Promise<T> {\n const url = `${baseUrl.replace(/\\/$/, '')}${path}`;\n const res = await fetch(url, {\n method,\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n });\n\n const json = (await res.json()) as ApiResponse<T>;\n\n if (!json.ok) {\n throw new AiPricingLabError({\n code: json.error.code as ErrorCode,\n message: json.error.message,\n status: res.status,\n });\n }\n\n return json.data;\n}\n","import type {\n TrackResponseData,\n CanUseResponseData,\n ReserveResponseData,\n UsageResponseData,\n UpsertSubscriptionRequest,\n UpsertSubscriptionResponseData,\n EventMetadata,\n} from './types';\nimport { request } from './http';\n\nexport interface ClientOptions {\n apiKey: string;\n baseUrl?: string;\n}\n\nexport class AiPricingLabClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n\n constructor(opts: ClientOptions) {\n this.apiKey = opts.apiKey;\n this.baseUrl = opts.baseUrl ?? 'https://www.aipricinglab.space';\n }\n\n /**\n * Record that a user consumed something.\n *\n * `metadata` is matched against the `metadata` filters on plan match rules.\n * The dashboard uses one reserved key — **`variant`** — when a plan row gates\n * a model to a specific output tier (e.g. image quality `1k`/`2k`/`4k`,\n * video tier `standard`/`fast`/`lite`). Pass it through so variant-gated\n * buckets count the event:\n *\n * await apl.track('user_123', 'image.gemini-3.1-image', 1, { variant: '4k' });\n */\n async track(\n userId: string,\n event: string,\n quantity = 1,\n metadata?: EventMetadata,\n ): Promise<TrackResponseData> {\n return request<TrackResponseData>(this.baseUrl, this.apiKey, 'POST', '/api/v1/track', {\n userId,\n event,\n quantity,\n metadata,\n });\n }\n\n /**\n * Check if a user is allowed to perform an action.\n * Returns `{ allowed, reasons, details }`.\n * Use `.can()` if you only need the boolean.\n *\n * Pass `metadata.variant` (e.g. `'4k'`, `'standard'`) when a plan row gates\n * the model to a specific output tier — see {@link AiPricingLabClient.track}.\n */\n async canUse(\n userId: string,\n event: string,\n quantity = 1,\n metadata?: EventMetadata,\n ): Promise<CanUseResponseData> {\n return request<CanUseResponseData>(this.baseUrl, this.apiKey, 'POST', '/api/v1/can-use', {\n userId,\n event,\n quantity,\n metadata,\n });\n }\n\n /** Shorthand: returns `true` if allowed, `false` if the user hit a limit. */\n async can(userId: string, event: string, quantity = 1, metadata?: EventMetadata): Promise<boolean> {\n const { allowed } = await this.canUse(userId, event, quantity, metadata);\n return allowed;\n }\n\n /**\n * Atomically reserve quota before doing the AI call.\n * On success call `commit(reservationId)`, on failure call `release(reservationId)`.\n *\n * Pass `metadata.variant` (e.g. `'4k'`, `'standard'`) when a plan row gates\n * the model to a specific output tier — see {@link AiPricingLabClient.track}.\n */\n async reserve(\n userId: string,\n event: string,\n quantity = 1,\n metadata?: EventMetadata,\n ): Promise<ReserveResponseData> {\n return request<ReserveResponseData>(this.baseUrl, this.apiKey, 'POST', '/api/v1/reserve', {\n userId,\n event,\n quantity,\n metadata,\n });\n }\n\n /** Confirm a reservation after a successful AI call. */\n async commit(reservationId: string): Promise<void> {\n await request<Record<string, never>>(this.baseUrl, this.apiKey, 'POST', '/api/v1/commit', {\n reservationId,\n });\n }\n\n /** Cancel a reservation after a failed AI call so quota is returned. */\n async release(reservationId: string): Promise<void> {\n await request<Record<string, never>>(this.baseUrl, this.apiKey, 'POST', '/api/v1/release', {\n reservationId,\n });\n }\n\n /**\n * Get current usage counters for a user.\n *\n * Routes to `/api/v1/usage/me` for public (`pk_live_`) keys — that endpoint\n * is the only one that accepts public keys. Secret keys hit `/api/v1/usage`.\n */\n async usage(userId: string, event?: string): Promise<UsageResponseData> {\n const qs = new URLSearchParams({ userId });\n if (event) qs.set('event', event);\n const path = this.apiKey.startsWith('pk_live_')\n ? `/api/v1/usage/me?${qs}`\n : `/api/v1/usage?${qs}`;\n return request<UsageResponseData>(this.baseUrl, this.apiKey, 'GET', path);\n }\n\n /** Assign or update a user's plan subscription. */\n async upsertSubscription(\n params: UpsertSubscriptionRequest,\n ): Promise<UpsertSubscriptionResponseData> {\n return request<UpsertSubscriptionResponseData>(\n this.baseUrl,\n this.apiKey,\n 'POST',\n '/api/v1/subscriptions',\n params,\n );\n }\n}\n\nexport function createClient(opts: ClientOptions): AiPricingLabClient {\n return new AiPricingLabClient(opts);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/http.ts","../src/client.ts"],"sourcesContent":["export { AiPricingLabClient, createClient } from './client';\nexport type { ClientOptions } from './client';\nexport { AiPricingLabError } from './errors';\nexport type {\n EventMetadata,\n TrackResponseData,\n CounterSummary,\n CanUseResponseData,\n ReserveResponseData,\n UsageResponseData,\n UpsertSubscriptionRequest,\n UpsertSubscriptionResponseData,\n LimitGroup,\n LimitUnit,\n MatchRule,\n PlanLimits,\n PeriodType,\n PeriodAnchor,\n ErrorCode,\n ApiError,\n ApiResponse,\n} from './types';\n","import type { ErrorCode } from './types';\n\nexport class AiPricingLabError extends Error {\n readonly code: ErrorCode;\n readonly status: number;\n\n constructor(opts: { code: ErrorCode; message: string; status: number }) {\n super(opts.message);\n this.name = 'AiPricingLabError';\n this.code = opts.code;\n this.status = opts.status;\n }\n}\n","// Phase 9: internal fetch wrapper\nimport type { ApiResponse, ErrorCode } from './types';\nimport { AiPricingLabError } from './errors';\n\nexport async function request<T>(\n baseUrl: string,\n apiKey: string,\n method: string,\n path: string,\n body?: unknown,\n): Promise<T> {\n const url = `${baseUrl.replace(/\\/$/, '')}${path}`;\n const res = await fetch(url, {\n method,\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n });\n\n const json = (await res.json()) as ApiResponse<T>;\n\n if (!json.ok) {\n throw new AiPricingLabError({\n code: json.error.code as ErrorCode,\n message: json.error.message,\n status: res.status,\n });\n }\n\n return json.data;\n}\n","import type {\n TrackResponseData,\n CanUseResponseData,\n ReserveResponseData,\n UsageResponseData,\n UpsertSubscriptionRequest,\n UpsertSubscriptionResponseData,\n EventMetadata,\n} from './types';\nimport { request } from './http';\n\nexport interface ClientOptions {\n apiKey: string;\n baseUrl?: string;\n}\n\nexport class AiPricingLabClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n\n constructor(opts: ClientOptions) {\n this.apiKey = opts.apiKey;\n this.baseUrl = opts.baseUrl ?? 'https://www.aipricinglab.space';\n }\n\n /**\n * Record that a user consumed something.\n *\n * `metadata` is matched against the `metadata` filters on plan match rules.\n * The dashboard uses one reserved key - **`variant`** - when a plan row gates\n * a model to a specific output tier (e.g. image quality `1k`/`2k`/`4k`,\n * video tier `standard`/`fast`/`lite`). Pass it through so variant-gated\n * buckets count the event:\n *\n * await apl.track('user_123', 'image.gemini-3.1-image', 1, { variant: '4k' });\n */\n async track(\n userId: string,\n event: string,\n quantity = 1,\n metadata?: EventMetadata,\n ): Promise<TrackResponseData> {\n return request<TrackResponseData>(this.baseUrl, this.apiKey, 'POST', '/api/v1/track', {\n userId,\n event,\n quantity,\n metadata,\n });\n }\n\n /**\n * Check if a user is allowed to perform an action.\n * Returns `{ allowed, reasons, details }`.\n * Use `.can()` if you only need the boolean.\n *\n * Pass `metadata.variant` (e.g. `'4k'`, `'standard'`) when a plan row gates\n * the model to a specific output tier - see {@link AiPricingLabClient.track}.\n */\n async canUse(\n userId: string,\n event: string,\n quantity = 1,\n metadata?: EventMetadata,\n ): Promise<CanUseResponseData> {\n const result = await request<CanUseResponseData>(this.baseUrl, this.apiKey, 'POST', '/api/v1/can-use', {\n userId,\n event,\n quantity,\n metadata,\n });\n warnIfUnmatched('canUse', event, result);\n return result;\n }\n\n /** Shorthand: returns `true` if allowed, `false` if the user hit a limit. */\n async can(userId: string, event: string, quantity = 1, metadata?: EventMetadata): Promise<boolean> {\n const { allowed } = await this.canUse(userId, event, quantity, metadata);\n return allowed;\n }\n\n /**\n * Atomically reserve quota before doing the AI call.\n * On success call `commit(reservationId)`, on failure call `release(reservationId)`.\n *\n * Pass `metadata.variant` (e.g. `'4k'`, `'standard'`) when a plan row gates\n * the model to a specific output tier - see {@link AiPricingLabClient.track}.\n */\n async reserve(\n userId: string,\n event: string,\n quantity = 1,\n metadata?: EventMetadata,\n ): Promise<ReserveResponseData> {\n const result = await request<ReserveResponseData>(this.baseUrl, this.apiKey, 'POST', '/api/v1/reserve', {\n userId,\n event,\n quantity,\n metadata,\n });\n warnIfUnmatched('reserve', event, result);\n return result;\n }\n\n /** Confirm a reservation after a successful AI call. */\n async commit(reservationId: string): Promise<void> {\n await request<Record<string, never>>(this.baseUrl, this.apiKey, 'POST', '/api/v1/commit', {\n reservationId,\n });\n }\n\n /** Cancel a reservation after a failed AI call so quota is returned. */\n async release(reservationId: string): Promise<void> {\n await request<Record<string, never>>(this.baseUrl, this.apiKey, 'POST', '/api/v1/release', {\n reservationId,\n });\n }\n\n /**\n * Get current usage counters for a user.\n *\n * Routes to `/api/v1/usage/me` for public (`pk_live_`) keys - that endpoint\n * is the only one that accepts public keys. Secret keys hit `/api/v1/usage`.\n */\n async usage(userId: string, event?: string): Promise<UsageResponseData> {\n const qs = new URLSearchParams({ userId });\n if (event) qs.set('event', event);\n const path = this.apiKey.startsWith('pk_live_')\n ? `/api/v1/usage/me?${qs}`\n : `/api/v1/usage?${qs}`;\n return request<UsageResponseData>(this.baseUrl, this.apiKey, 'GET', path);\n }\n\n /** Assign or update a user's plan subscription. */\n async upsertSubscription(\n params: UpsertSubscriptionRequest,\n ): Promise<UpsertSubscriptionResponseData> {\n return request<UpsertSubscriptionResponseData>(\n this.baseUrl,\n this.apiKey,\n 'POST',\n '/api/v1/subscriptions',\n params,\n );\n }\n}\n\nexport function createClient(opts: ClientOptions): AiPricingLabClient {\n return new AiPricingLabClient(opts);\n}\n\n// Surfaces typos and missing limit groups during development. Silent in\n// production so server logs aren't polluted on every legitimate \"user has\n// no subscription yet\" call.\nfunction warnIfUnmatched(\n method: 'canUse' | 'reserve',\n event: string,\n result: { matched?: boolean; reasons?: string[] },\n): void {\n // SDK ships with zero runtime deps and no @types/node, so feel our way\n // through globalThis instead of referencing `process` directly. In the\n // browser there's no process and the warn is silent — that's intentional.\n const proc = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process;\n if (!proc || proc.env?.['NODE_ENV'] === 'production') return;\n if (result.matched !== false) return;\n const reason = result.reasons?.[0];\n if (reason === 'unmatched_event') {\n // eslint-disable-next-line no-console\n console.warn(\n `[AiPricingLab] ${method}(${JSON.stringify(event)}) returned matched: false (unmatched_event). ` +\n `No limit group on this user's plan covers this event_type. ` +\n `Check for typos or define a matching limit group in the dashboard.`,\n );\n } else if (reason === 'no_subscription') {\n // eslint-disable-next-line no-console\n console.warn(\n `[AiPricingLab] ${method}(${JSON.stringify(event)}) returned matched: false (no_subscription). ` +\n `This user has no active subscription. Call upsertSubscription({ userId, planId }) before metering.`,\n );\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -44,7 +44,7 @@ var AiPricingLabClient = class {
|
|
|
44
44
|
* Record that a user consumed something.
|
|
45
45
|
*
|
|
46
46
|
* `metadata` is matched against the `metadata` filters on plan match rules.
|
|
47
|
-
* The dashboard uses one reserved key
|
|
47
|
+
* The dashboard uses one reserved key - **`variant`** - when a plan row gates
|
|
48
48
|
* a model to a specific output tier (e.g. image quality `1k`/`2k`/`4k`,
|
|
49
49
|
* video tier `standard`/`fast`/`lite`). Pass it through so variant-gated
|
|
50
50
|
* buckets count the event:
|
|
@@ -65,15 +65,17 @@ var AiPricingLabClient = class {
|
|
|
65
65
|
* Use `.can()` if you only need the boolean.
|
|
66
66
|
*
|
|
67
67
|
* Pass `metadata.variant` (e.g. `'4k'`, `'standard'`) when a plan row gates
|
|
68
|
-
* the model to a specific output tier
|
|
68
|
+
* the model to a specific output tier - see {@link AiPricingLabClient.track}.
|
|
69
69
|
*/
|
|
70
70
|
async canUse(userId, event, quantity = 1, metadata) {
|
|
71
|
-
|
|
71
|
+
const result = await request(this.baseUrl, this.apiKey, "POST", "/api/v1/can-use", {
|
|
72
72
|
userId,
|
|
73
73
|
event,
|
|
74
74
|
quantity,
|
|
75
75
|
metadata
|
|
76
76
|
});
|
|
77
|
+
warnIfUnmatched("canUse", event, result);
|
|
78
|
+
return result;
|
|
77
79
|
}
|
|
78
80
|
/** Shorthand: returns `true` if allowed, `false` if the user hit a limit. */
|
|
79
81
|
async can(userId, event, quantity = 1, metadata) {
|
|
@@ -85,15 +87,17 @@ var AiPricingLabClient = class {
|
|
|
85
87
|
* On success call `commit(reservationId)`, on failure call `release(reservationId)`.
|
|
86
88
|
*
|
|
87
89
|
* Pass `metadata.variant` (e.g. `'4k'`, `'standard'`) when a plan row gates
|
|
88
|
-
* the model to a specific output tier
|
|
90
|
+
* the model to a specific output tier - see {@link AiPricingLabClient.track}.
|
|
89
91
|
*/
|
|
90
92
|
async reserve(userId, event, quantity = 1, metadata) {
|
|
91
|
-
|
|
93
|
+
const result = await request(this.baseUrl, this.apiKey, "POST", "/api/v1/reserve", {
|
|
92
94
|
userId,
|
|
93
95
|
event,
|
|
94
96
|
quantity,
|
|
95
97
|
metadata
|
|
96
98
|
});
|
|
99
|
+
warnIfUnmatched("reserve", event, result);
|
|
100
|
+
return result;
|
|
97
101
|
}
|
|
98
102
|
/** Confirm a reservation after a successful AI call. */
|
|
99
103
|
async commit(reservationId) {
|
|
@@ -110,7 +114,7 @@ var AiPricingLabClient = class {
|
|
|
110
114
|
/**
|
|
111
115
|
* Get current usage counters for a user.
|
|
112
116
|
*
|
|
113
|
-
* Routes to `/api/v1/usage/me` for public (`pk_live_`) keys
|
|
117
|
+
* Routes to `/api/v1/usage/me` for public (`pk_live_`) keys - that endpoint
|
|
114
118
|
* is the only one that accepts public keys. Secret keys hit `/api/v1/usage`.
|
|
115
119
|
*/
|
|
116
120
|
async usage(userId, event) {
|
|
@@ -133,6 +137,21 @@ var AiPricingLabClient = class {
|
|
|
133
137
|
function createClient(opts) {
|
|
134
138
|
return new AiPricingLabClient(opts);
|
|
135
139
|
}
|
|
140
|
+
function warnIfUnmatched(method, event, result) {
|
|
141
|
+
const proc = globalThis.process;
|
|
142
|
+
if (!proc || proc.env?.["NODE_ENV"] === "production") return;
|
|
143
|
+
if (result.matched !== false) return;
|
|
144
|
+
const reason = result.reasons?.[0];
|
|
145
|
+
if (reason === "unmatched_event") {
|
|
146
|
+
console.warn(
|
|
147
|
+
`[AiPricingLab] ${method}(${JSON.stringify(event)}) returned matched: false (unmatched_event). No limit group on this user's plan covers this event_type. Check for typos or define a matching limit group in the dashboard.`
|
|
148
|
+
);
|
|
149
|
+
} else if (reason === "no_subscription") {
|
|
150
|
+
console.warn(
|
|
151
|
+
`[AiPricingLab] ${method}(${JSON.stringify(event)}) returned matched: false (no_subscription). This user has no active subscription. Call upsertSubscription({ userId, planId }) before metering.`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
136
155
|
export {
|
|
137
156
|
AiPricingLabClient,
|
|
138
157
|
AiPricingLabError,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/client.ts"],"sourcesContent":["import type { ErrorCode } from './types';\n\nexport class AiPricingLabError extends Error {\n readonly code: ErrorCode;\n readonly status: number;\n\n constructor(opts: { code: ErrorCode; message: string; status: number }) {\n super(opts.message);\n this.name = 'AiPricingLabError';\n this.code = opts.code;\n this.status = opts.status;\n }\n}\n","// Phase 9: internal fetch wrapper\nimport type { ApiResponse, ErrorCode } from './types';\nimport { AiPricingLabError } from './errors';\n\nexport async function request<T>(\n baseUrl: string,\n apiKey: string,\n method: string,\n path: string,\n body?: unknown,\n): Promise<T> {\n const url = `${baseUrl.replace(/\\/$/, '')}${path}`;\n const res = await fetch(url, {\n method,\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n });\n\n const json = (await res.json()) as ApiResponse<T>;\n\n if (!json.ok) {\n throw new AiPricingLabError({\n code: json.error.code as ErrorCode,\n message: json.error.message,\n status: res.status,\n });\n }\n\n return json.data;\n}\n","import type {\n TrackResponseData,\n CanUseResponseData,\n ReserveResponseData,\n UsageResponseData,\n UpsertSubscriptionRequest,\n UpsertSubscriptionResponseData,\n EventMetadata,\n} from './types';\nimport { request } from './http';\n\nexport interface ClientOptions {\n apiKey: string;\n baseUrl?: string;\n}\n\nexport class AiPricingLabClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n\n constructor(opts: ClientOptions) {\n this.apiKey = opts.apiKey;\n this.baseUrl = opts.baseUrl ?? 'https://www.aipricinglab.space';\n }\n\n /**\n * Record that a user consumed something.\n *\n * `metadata` is matched against the `metadata` filters on plan match rules.\n * The dashboard uses one reserved key — **`variant`** — when a plan row gates\n * a model to a specific output tier (e.g. image quality `1k`/`2k`/`4k`,\n * video tier `standard`/`fast`/`lite`). Pass it through so variant-gated\n * buckets count the event:\n *\n * await apl.track('user_123', 'image.gemini-3.1-image', 1, { variant: '4k' });\n */\n async track(\n userId: string,\n event: string,\n quantity = 1,\n metadata?: EventMetadata,\n ): Promise<TrackResponseData> {\n return request<TrackResponseData>(this.baseUrl, this.apiKey, 'POST', '/api/v1/track', {\n userId,\n event,\n quantity,\n metadata,\n });\n }\n\n /**\n * Check if a user is allowed to perform an action.\n * Returns `{ allowed, reasons, details }`.\n * Use `.can()` if you only need the boolean.\n *\n * Pass `metadata.variant` (e.g. `'4k'`, `'standard'`) when a plan row gates\n * the model to a specific output tier — see {@link AiPricingLabClient.track}.\n */\n async canUse(\n userId: string,\n event: string,\n quantity = 1,\n metadata?: EventMetadata,\n ): Promise<CanUseResponseData> {\n return request<CanUseResponseData>(this.baseUrl, this.apiKey, 'POST', '/api/v1/can-use', {\n userId,\n event,\n quantity,\n metadata,\n });\n }\n\n /** Shorthand: returns `true` if allowed, `false` if the user hit a limit. */\n async can(userId: string, event: string, quantity = 1, metadata?: EventMetadata): Promise<boolean> {\n const { allowed } = await this.canUse(userId, event, quantity, metadata);\n return allowed;\n }\n\n /**\n * Atomically reserve quota before doing the AI call.\n * On success call `commit(reservationId)`, on failure call `release(reservationId)`.\n *\n * Pass `metadata.variant` (e.g. `'4k'`, `'standard'`) when a plan row gates\n * the model to a specific output tier — see {@link AiPricingLabClient.track}.\n */\n async reserve(\n userId: string,\n event: string,\n quantity = 1,\n metadata?: EventMetadata,\n ): Promise<ReserveResponseData> {\n return request<ReserveResponseData>(this.baseUrl, this.apiKey, 'POST', '/api/v1/reserve', {\n userId,\n event,\n quantity,\n metadata,\n });\n }\n\n /** Confirm a reservation after a successful AI call. */\n async commit(reservationId: string): Promise<void> {\n await request<Record<string, never>>(this.baseUrl, this.apiKey, 'POST', '/api/v1/commit', {\n reservationId,\n });\n }\n\n /** Cancel a reservation after a failed AI call so quota is returned. */\n async release(reservationId: string): Promise<void> {\n await request<Record<string, never>>(this.baseUrl, this.apiKey, 'POST', '/api/v1/release', {\n reservationId,\n });\n }\n\n /**\n * Get current usage counters for a user.\n *\n * Routes to `/api/v1/usage/me` for public (`pk_live_`) keys — that endpoint\n * is the only one that accepts public keys. Secret keys hit `/api/v1/usage`.\n */\n async usage(userId: string, event?: string): Promise<UsageResponseData> {\n const qs = new URLSearchParams({ userId });\n if (event) qs.set('event', event);\n const path = this.apiKey.startsWith('pk_live_')\n ? `/api/v1/usage/me?${qs}`\n : `/api/v1/usage?${qs}`;\n return request<UsageResponseData>(this.baseUrl, this.apiKey, 'GET', path);\n }\n\n /** Assign or update a user's plan subscription. */\n async upsertSubscription(\n params: UpsertSubscriptionRequest,\n ): Promise<UpsertSubscriptionResponseData> {\n return request<UpsertSubscriptionResponseData>(\n this.baseUrl,\n this.apiKey,\n 'POST',\n '/api/v1/subscriptions',\n params,\n );\n }\n}\n\nexport function createClient(opts: ClientOptions): AiPricingLabClient {\n return new AiPricingLabClient(opts);\n}\n"],"mappings":";AAEO,IAAM,oBAAN,cAAgC,MAAM;AAAA,EAClC;AAAA,EACA;AAAA,EAET,YAAY,MAA4D;AACtE,UAAM,KAAK,OAAO;AAClB,SAAK,OAAO;AACZ,SAAK,OAAO,KAAK;AACjB,SAAK,SAAS,KAAK;AAAA,EACrB;AACF;;;ACRA,eAAsB,QACpB,SACA,QACA,QACA,MACA,MACY;AACZ,QAAM,MAAM,GAAG,QAAQ,QAAQ,OAAO,EAAE,CAAC,GAAG,IAAI;AAChD,QAAM,MAAM,MAAM,MAAM,KAAK;AAAA,IAC3B;AAAA,IACA,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,eAAe,UAAU,MAAM;AAAA,IACjC;AAAA,IACA,GAAI,SAAS,SAAY,EAAE,MAAM,KAAK,UAAU,IAAI,EAAE,IAAI,CAAC;AAAA,EAC7D,CAAC;AAED,QAAM,OAAQ,MAAM,IAAI,KAAK;AAE7B,MAAI,CAAC,KAAK,IAAI;AACZ,UAAM,IAAI,kBAAkB;AAAA,MAC1B,MAAM,KAAK,MAAM;AAAA,MACjB,SAAS,KAAK,MAAM;AAAA,MACpB,QAAQ,IAAI;AAAA,IACd,CAAC;AAAA,EACH;AAEA,SAAO,KAAK;AACd;;;AChBO,IAAM,qBAAN,MAAyB;AAAA,EACb;AAAA,EACA;AAAA,EAEjB,YAAY,MAAqB;AAC/B,SAAK,SAAS,KAAK;AACnB,SAAK,UAAU,KAAK,WAAW;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,MACJ,QACA,OACA,WAAW,GACX,UAC4B;AAC5B,WAAO,QAA2B,KAAK,SAAS,KAAK,QAAQ,QAAQ,iBAAiB;AAAA,MACpF;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,OACJ,QACA,OACA,WAAW,GACX,UAC6B;AAC7B,WAAO,QAA4B,KAAK,SAAS,KAAK,QAAQ,QAAQ,mBAAmB;AAAA,MACvF;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,IAAI,QAAgB,OAAe,WAAW,GAAG,UAA4C;AACjG,UAAM,EAAE,QAAQ,IAAI,MAAM,KAAK,OAAO,QAAQ,OAAO,UAAU,QAAQ;AACvE,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,QACJ,QACA,OACA,WAAW,GACX,UAC8B;AAC9B,WAAO,QAA6B,KAAK,SAAS,KAAK,QAAQ,QAAQ,mBAAmB;AAAA,MACxF;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,OAAO,eAAsC;AACjD,UAAM,QAA+B,KAAK,SAAS,KAAK,QAAQ,QAAQ,kBAAkB;AAAA,MACxF;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,QAAQ,eAAsC;AAClD,UAAM,QAA+B,KAAK,SAAS,KAAK,QAAQ,QAAQ,mBAAmB;AAAA,MACzF;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,MAAM,QAAgB,OAA4C;AACtE,UAAM,KAAK,IAAI,gBAAgB,EAAE,OAAO,CAAC;AACzC,QAAI,MAAO,IAAG,IAAI,SAAS,KAAK;AAChC,UAAM,OAAO,KAAK,OAAO,WAAW,UAAU,IAC1C,oBAAoB,EAAE,KACtB,iBAAiB,EAAE;AACvB,WAAO,QAA2B,KAAK,SAAS,KAAK,QAAQ,OAAO,IAAI;AAAA,EAC1E;AAAA;AAAA,EAGA,MAAM,mBACJ,QACyC;AACzC,WAAO;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,aAAa,MAAyC;AACpE,SAAO,IAAI,mBAAmB,IAAI;AACpC;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/client.ts"],"sourcesContent":["import type { ErrorCode } from './types';\n\nexport class AiPricingLabError extends Error {\n readonly code: ErrorCode;\n readonly status: number;\n\n constructor(opts: { code: ErrorCode; message: string; status: number }) {\n super(opts.message);\n this.name = 'AiPricingLabError';\n this.code = opts.code;\n this.status = opts.status;\n }\n}\n","// Phase 9: internal fetch wrapper\nimport type { ApiResponse, ErrorCode } from './types';\nimport { AiPricingLabError } from './errors';\n\nexport async function request<T>(\n baseUrl: string,\n apiKey: string,\n method: string,\n path: string,\n body?: unknown,\n): Promise<T> {\n const url = `${baseUrl.replace(/\\/$/, '')}${path}`;\n const res = await fetch(url, {\n method,\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n });\n\n const json = (await res.json()) as ApiResponse<T>;\n\n if (!json.ok) {\n throw new AiPricingLabError({\n code: json.error.code as ErrorCode,\n message: json.error.message,\n status: res.status,\n });\n }\n\n return json.data;\n}\n","import type {\n TrackResponseData,\n CanUseResponseData,\n ReserveResponseData,\n UsageResponseData,\n UpsertSubscriptionRequest,\n UpsertSubscriptionResponseData,\n EventMetadata,\n} from './types';\nimport { request } from './http';\n\nexport interface ClientOptions {\n apiKey: string;\n baseUrl?: string;\n}\n\nexport class AiPricingLabClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n\n constructor(opts: ClientOptions) {\n this.apiKey = opts.apiKey;\n this.baseUrl = opts.baseUrl ?? 'https://www.aipricinglab.space';\n }\n\n /**\n * Record that a user consumed something.\n *\n * `metadata` is matched against the `metadata` filters on plan match rules.\n * The dashboard uses one reserved key - **`variant`** - when a plan row gates\n * a model to a specific output tier (e.g. image quality `1k`/`2k`/`4k`,\n * video tier `standard`/`fast`/`lite`). Pass it through so variant-gated\n * buckets count the event:\n *\n * await apl.track('user_123', 'image.gemini-3.1-image', 1, { variant: '4k' });\n */\n async track(\n userId: string,\n event: string,\n quantity = 1,\n metadata?: EventMetadata,\n ): Promise<TrackResponseData> {\n return request<TrackResponseData>(this.baseUrl, this.apiKey, 'POST', '/api/v1/track', {\n userId,\n event,\n quantity,\n metadata,\n });\n }\n\n /**\n * Check if a user is allowed to perform an action.\n * Returns `{ allowed, reasons, details }`.\n * Use `.can()` if you only need the boolean.\n *\n * Pass `metadata.variant` (e.g. `'4k'`, `'standard'`) when a plan row gates\n * the model to a specific output tier - see {@link AiPricingLabClient.track}.\n */\n async canUse(\n userId: string,\n event: string,\n quantity = 1,\n metadata?: EventMetadata,\n ): Promise<CanUseResponseData> {\n const result = await request<CanUseResponseData>(this.baseUrl, this.apiKey, 'POST', '/api/v1/can-use', {\n userId,\n event,\n quantity,\n metadata,\n });\n warnIfUnmatched('canUse', event, result);\n return result;\n }\n\n /** Shorthand: returns `true` if allowed, `false` if the user hit a limit. */\n async can(userId: string, event: string, quantity = 1, metadata?: EventMetadata): Promise<boolean> {\n const { allowed } = await this.canUse(userId, event, quantity, metadata);\n return allowed;\n }\n\n /**\n * Atomically reserve quota before doing the AI call.\n * On success call `commit(reservationId)`, on failure call `release(reservationId)`.\n *\n * Pass `metadata.variant` (e.g. `'4k'`, `'standard'`) when a plan row gates\n * the model to a specific output tier - see {@link AiPricingLabClient.track}.\n */\n async reserve(\n userId: string,\n event: string,\n quantity = 1,\n metadata?: EventMetadata,\n ): Promise<ReserveResponseData> {\n const result = await request<ReserveResponseData>(this.baseUrl, this.apiKey, 'POST', '/api/v1/reserve', {\n userId,\n event,\n quantity,\n metadata,\n });\n warnIfUnmatched('reserve', event, result);\n return result;\n }\n\n /** Confirm a reservation after a successful AI call. */\n async commit(reservationId: string): Promise<void> {\n await request<Record<string, never>>(this.baseUrl, this.apiKey, 'POST', '/api/v1/commit', {\n reservationId,\n });\n }\n\n /** Cancel a reservation after a failed AI call so quota is returned. */\n async release(reservationId: string): Promise<void> {\n await request<Record<string, never>>(this.baseUrl, this.apiKey, 'POST', '/api/v1/release', {\n reservationId,\n });\n }\n\n /**\n * Get current usage counters for a user.\n *\n * Routes to `/api/v1/usage/me` for public (`pk_live_`) keys - that endpoint\n * is the only one that accepts public keys. Secret keys hit `/api/v1/usage`.\n */\n async usage(userId: string, event?: string): Promise<UsageResponseData> {\n const qs = new URLSearchParams({ userId });\n if (event) qs.set('event', event);\n const path = this.apiKey.startsWith('pk_live_')\n ? `/api/v1/usage/me?${qs}`\n : `/api/v1/usage?${qs}`;\n return request<UsageResponseData>(this.baseUrl, this.apiKey, 'GET', path);\n }\n\n /** Assign or update a user's plan subscription. */\n async upsertSubscription(\n params: UpsertSubscriptionRequest,\n ): Promise<UpsertSubscriptionResponseData> {\n return request<UpsertSubscriptionResponseData>(\n this.baseUrl,\n this.apiKey,\n 'POST',\n '/api/v1/subscriptions',\n params,\n );\n }\n}\n\nexport function createClient(opts: ClientOptions): AiPricingLabClient {\n return new AiPricingLabClient(opts);\n}\n\n// Surfaces typos and missing limit groups during development. Silent in\n// production so server logs aren't polluted on every legitimate \"user has\n// no subscription yet\" call.\nfunction warnIfUnmatched(\n method: 'canUse' | 'reserve',\n event: string,\n result: { matched?: boolean; reasons?: string[] },\n): void {\n // SDK ships with zero runtime deps and no @types/node, so feel our way\n // through globalThis instead of referencing `process` directly. In the\n // browser there's no process and the warn is silent — that's intentional.\n const proc = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process;\n if (!proc || proc.env?.['NODE_ENV'] === 'production') return;\n if (result.matched !== false) return;\n const reason = result.reasons?.[0];\n if (reason === 'unmatched_event') {\n // eslint-disable-next-line no-console\n console.warn(\n `[AiPricingLab] ${method}(${JSON.stringify(event)}) returned matched: false (unmatched_event). ` +\n `No limit group on this user's plan covers this event_type. ` +\n `Check for typos or define a matching limit group in the dashboard.`,\n );\n } else if (reason === 'no_subscription') {\n // eslint-disable-next-line no-console\n console.warn(\n `[AiPricingLab] ${method}(${JSON.stringify(event)}) returned matched: false (no_subscription). ` +\n `This user has no active subscription. Call upsertSubscription({ userId, planId }) before metering.`,\n );\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aipricinglab/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Usage metering and limits SDK for AI-powered apps. Track LLM tokens, image generations, video seconds, and any other AI consumption — provider-agnostic.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|