@aipricinglab/sdk 0.1.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 ADDED
@@ -0,0 +1,231 @@
1
+ # @aipricinglab/sdk
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@aipricinglab/sdk.svg)](https://www.npmjs.com/package/@aipricinglab/sdk)
4
+
5
+ The official TypeScript SDK for **[AiPricingLab](https://aipricinglab.space)** — drop-in usage metering, limits, and plan enforcement for AI-powered apps.
6
+
7
+ Stop building your own meter for LLM tokens, image generations, video seconds, or any other AI consumption. Define plans in the dashboard, install this SDK, and ship.
8
+
9
+ - 🌐 Website: <https://aipricinglab.space>
10
+ - 📚 Docs: <https://aipricinglab.space/docs>
11
+ - 🐛 Issues: <https://aipricinglab.space/support>
12
+
13
+ ---
14
+
15
+ ## Features
16
+
17
+ - **Provider-agnostic** — works with OpenAI, Anthropic, Replicate, Fal, your own models, anything you bill against.
18
+ - **Atomic reservations** — `reserve` / `commit` / `release` prevents parallel requests from blowing past limits.
19
+ - **Zero runtime dependencies** — uses the platform `fetch`. Tiny bundle, works in Node, Bun, Deno, Edge, and the browser.
20
+ - **Typed errors** — every failure mode is a typed `AiPricingLabError` with a stable `code`.
21
+ - **Public + secret keys** — read a user's own usage from the browser with `pk_live_...`, do everything else from the backend with `sk_live_...`.
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ npm install @aipricinglab/sdk
27
+ # or
28
+ pnpm add @aipricinglab/sdk
29
+ # or
30
+ yarn add @aipricinglab/sdk
31
+ ```
32
+
33
+ Requires Node 18+ (or any runtime with global `fetch`).
34
+
35
+ ## Quick start
36
+
37
+ Grab a secret key from your app's **API keys** tab at <https://aipricinglab.space>, then:
38
+
39
+ ```ts
40
+ import { createClient } from '@aipricinglab/sdk';
41
+
42
+ const apl = createClient({ apiKey: process.env.AIPRICINGLAB_KEY! });
43
+
44
+ // Just record consumption — fire-and-forget.
45
+ await apl.track('user_abc123', 'image.flux-pro', 1);
46
+ ```
47
+
48
+ That's it. The event is now visible in your AiPricingLab dashboard, counted against any matching plan limits.
49
+
50
+ ### Event naming convention
51
+
52
+ The plan builder generates match rules in the form **`<category>.<modelId>`**, where `category` is one of `image`, `llm`, `video`, `audio`. Use the same shape in your `track` / `canUse` / `reserve` calls so events count toward plan rows automatically:
53
+
54
+ | Category | Example event name |
55
+ |---|---|
56
+ | Image generation | `image.flux-pro`, `image.gemini-3.1-image` |
57
+ | Language model | `llm.gpt-4o`, `llm.claude-sonnet-4-6` |
58
+ | Video generation | `video.runway-gen3`, `video.veo-2` |
59
+ | Audio generation | `audio.elevenlabs-v2`, `audio.whisper` |
60
+
61
+ A plan can also have a **category total** (e.g. *"30 images of any kind per month"*). Those use the wildcard pattern `<category>.*`, which matches any event that starts with that prefix — you don't need to emit anything special.
62
+
63
+ You're free to use any other event names too — they just won't match the structural plan rows; they'll only fire if you've added a matching custom group.
64
+
65
+ ## Reserve / commit / release (recommended)
66
+
67
+ For strict enforcement, use the three-step pattern. It atomically increments the counter *before* the AI call so two parallel requests can't both squeeze through under the limit:
68
+
69
+ ```ts
70
+ import { createClient, AiPricingLabError } from '@aipricinglab/sdk';
71
+
72
+ const apl = createClient({ apiKey: process.env.AIPRICINGLAB_KEY! });
73
+
74
+ async function generateImage(userId: string, prompt: string) {
75
+ // 1. Reserve quota before the AI call.
76
+ const reservation = await apl.reserve(userId, 'image.flux-pro', 1);
77
+
78
+ if (!reservation.allowed) {
79
+ throw new Error(`Limit reached: ${reservation.reasons?.join(', ')}`);
80
+ }
81
+
82
+ try {
83
+ const image = await callFluxPro(prompt);
84
+
85
+ // 2. Commit on success — the charge is final.
86
+ await apl.commit(reservation.reservationId!);
87
+ return image;
88
+ } catch (err) {
89
+ // 3. Release on failure — the user is not charged.
90
+ await apl.release(reservation.reservationId!);
91
+ throw err;
92
+ }
93
+ }
94
+ ```
95
+
96
+ Reservations auto-expire after 60 seconds if you forget to commit or release, so a crashed server won't permanently lock quota.
97
+
98
+ ## Track (fire-and-forget)
99
+
100
+ Simpler, no atomicity guarantee. Use it when slight over-counting is acceptable.
101
+
102
+ ### `quantity` semantics
103
+
104
+ `quantity` is **the number of units the event consumed** in whatever unit the matching plan limit uses:
105
+
106
+ - For an `image` plan with unit `count` → `quantity = 1` per generation.
107
+ - For an `llm` plan with unit `tokens` → `quantity = totalInputTokens + totalOutputTokens`.
108
+ - For an `llm` plan with unit `cents` → `quantity = costInCents` (integer cents).
109
+ - For a `video` plan with unit `seconds` → `quantity = clipLengthInSeconds`.
110
+
111
+ ```ts
112
+ // LLM call — pass total tokens consumed
113
+ await apl.track('user_abc123', 'llm.gpt-4o', 1842, {
114
+ inputTokens: '920',
115
+ outputTokens: '922',
116
+ });
117
+
118
+ // Video clip — pass duration in seconds
119
+ await apl.track('user_abc123', 'video.runway-gen3', 8);
120
+ ```
121
+
122
+ ## Check without charging
123
+
124
+ ```ts
125
+ const { allowed, reasons } = await apl.canUse('user_abc123', 'image.flux-pro');
126
+
127
+ if (!allowed) {
128
+ return showUpgradePrompt(reasons);
129
+ }
130
+ ```
131
+
132
+ Or the boolean shorthand:
133
+
134
+ ```ts
135
+ if (await apl.can('user_abc123', 'image.flux-pro')) {
136
+ // proceed
137
+ }
138
+ ```
139
+
140
+ ## Variant-gated plan rows
141
+
142
+ 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:
143
+
144
+ ```ts
145
+ await apl.track('user_abc123', 'image.gemini-3.1-image', 1, { variant: '4k' });
146
+ ```
147
+
148
+ `variant` is the only reserved metadata key — everything else is yours.
149
+
150
+ ### Metadata is string-only
151
+
152
+ Metadata values must be strings (`Record<string, string>`). The API rejects numbers and booleans, so coerce them on your side:
153
+
154
+ ```ts
155
+ await apl.track('user_abc123', 'llm.gpt-4o', 1842, {
156
+ inputTokens: String(920), // ✅
157
+ cached: String(true), // ✅
158
+ // outputTokens: 922, // ❌ rejected
159
+ });
160
+ ```
161
+
162
+ ## Read a user's own usage (browser-safe)
163
+
164
+ Use a `pk_live_...` key in client-side code. Public keys can only read usage; they cannot track, reserve, or modify subscriptions. The SDK auto-routes public keys to the public-safe endpoint:
165
+
166
+ ```ts
167
+ const apl = createClient({ apiKey: 'pk_live_...' });
168
+
169
+ const usage = await apl.usage(currentUserId);
170
+ // → { userId, period: { start, end } | null, counters: [{ groupId, count, costCents }, ...] }
171
+ ```
172
+
173
+ `period` is `null` when the user has no active subscription on the app.
174
+
175
+ ## Manage subscriptions
176
+
177
+ Move a user onto a plan (or off it) from your backend:
178
+
179
+ ```ts
180
+ await apl.upsertSubscription({
181
+ userId: 'user_abc123',
182
+ planId: 'plan_pro_monthly',
183
+ });
184
+ ```
185
+
186
+ ## Error handling
187
+
188
+ Every method throws a typed `AiPricingLabError` on failure:
189
+
190
+ ```ts
191
+ import { AiPricingLabError } from '@aipricinglab/sdk';
192
+
193
+ try {
194
+ await apl.track('user_abc123', 'image.flux-pro');
195
+ } catch (err) {
196
+ if (err instanceof AiPricingLabError) {
197
+ console.error(err.code, err.status, err.message);
198
+ // err.code: 'limit_reached' | 'invalid_key' | 'not_found' | 'workspace_limit_reached' | ...
199
+ }
200
+ }
201
+ ```
202
+
203
+ ## API reference
204
+
205
+ | Method | Purpose |
206
+ |---|---|
207
+ | `track(userId, event, quantity?, metadata?)` | Record consumption. |
208
+ | `canUse(userId, event, quantity?, metadata?)` | Check if allowed; returns `{ allowed, reasons, details }`. |
209
+ | `can(userId, event, quantity?, metadata?)` | Boolean shorthand for `canUse`. |
210
+ | `reserve(userId, event, quantity?, metadata?)` | Atomically hold quota; returns a `reservationId`. |
211
+ | `commit(reservationId)` | Confirm a reservation. |
212
+ | `release(reservationId)` | Cancel a reservation; quota returned. |
213
+ | `usage(userId, event?)` | Read a user's current counters. |
214
+ | `upsertSubscription({ userId, planId })` | Assign or change a user's plan. |
215
+
216
+ Full request/response types are exported from the package — your editor will pick them up.
217
+
218
+ ## Configuration
219
+
220
+ ```ts
221
+ createClient({
222
+ apiKey: 'sk_live_...', // required
223
+ baseUrl: 'https://www.aipricinglab.space', // optional, defaults to production
224
+ });
225
+ ```
226
+
227
+ Set `baseUrl` only if you're running a self-hosted instance.
228
+
229
+ ## License
230
+
231
+ MIT © [AiPricingLab](https://aipricinglab.space)
@@ -0,0 +1,144 @@
1
+ type LimitUnit = 'count' | 'tokens' | 'seconds' | 'cents';
2
+ interface MatchRule {
3
+ event: string;
4
+ metadata?: Record<string, string> | undefined;
5
+ }
6
+ interface LimitGroup {
7
+ id: string;
8
+ label: string;
9
+ unit: LimitUnit;
10
+ quota: number;
11
+ matches: MatchRule[];
12
+ }
13
+ interface PlanLimits {
14
+ groups: LimitGroup[];
15
+ }
16
+ type PeriodType = 'daily' | 'weekly' | 'monthly' | 'lifetime';
17
+ type PeriodAnchor = 'subscription_start' | 'calendar';
18
+ type EventMetadata = Record<string, string>;
19
+ interface TrackResponseData {
20
+ eventId: string;
21
+ counters: CounterSummary[];
22
+ }
23
+ interface CounterSummary {
24
+ groupId: string;
25
+ count: number;
26
+ costCents: number;
27
+ }
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';
29
+ interface ApiError {
30
+ code: ErrorCode;
31
+ message: string;
32
+ }
33
+ interface ApiOk<T> {
34
+ ok: true;
35
+ data: T;
36
+ }
37
+ interface ApiFail {
38
+ ok: false;
39
+ error: ApiError;
40
+ }
41
+ type ApiResponse<T> = ApiOk<T> | ApiFail;
42
+ interface CanUseResponseData {
43
+ allowed: boolean;
44
+ reasons: string[];
45
+ details: {
46
+ groupId: string;
47
+ current: number;
48
+ quota: number;
49
+ resetsAt: string | null;
50
+ }[];
51
+ }
52
+ interface ReserveResponseData {
53
+ allowed: boolean;
54
+ reservationId?: string;
55
+ expiresAt?: string;
56
+ reasons?: string[];
57
+ }
58
+ interface UsageResponseData {
59
+ userId: string;
60
+ period: {
61
+ start: string;
62
+ end: string | null;
63
+ } | null;
64
+ counters: CounterSummary[];
65
+ }
66
+ interface UpsertSubscriptionRequest {
67
+ userId: string;
68
+ planId: string;
69
+ customLimits?: PlanLimits;
70
+ endsAt?: string;
71
+ }
72
+ interface UpsertSubscriptionResponseData {
73
+ subscriptionId: string;
74
+ userId: string;
75
+ planId: string;
76
+ startedAt: string;
77
+ }
78
+
79
+ interface ClientOptions {
80
+ apiKey: string;
81
+ baseUrl?: string;
82
+ }
83
+ declare class AiPricingLabClient {
84
+ private readonly apiKey;
85
+ private readonly baseUrl;
86
+ constructor(opts: ClientOptions);
87
+ /**
88
+ * Record that a user consumed something.
89
+ *
90
+ * `metadata` is matched against the `metadata` filters on plan match rules.
91
+ * The dashboard uses one reserved key — **`variant`** — when a plan row gates
92
+ * a model to a specific output tier (e.g. image quality `1k`/`2k`/`4k`,
93
+ * video tier `standard`/`fast`/`lite`). Pass it through so variant-gated
94
+ * buckets count the event:
95
+ *
96
+ * await apl.track('user_123', 'image.gemini-3.1-image', 1, { variant: '4k' });
97
+ */
98
+ track(userId: string, event: string, quantity?: number, metadata?: EventMetadata): Promise<TrackResponseData>;
99
+ /**
100
+ * Check if a user is allowed to perform an action.
101
+ * Returns `{ allowed, reasons, details }`.
102
+ * Use `.can()` if you only need the boolean.
103
+ *
104
+ * Pass `metadata.variant` (e.g. `'4k'`, `'standard'`) when a plan row gates
105
+ * the model to a specific output tier — see {@link AiPricingLabClient.track}.
106
+ */
107
+ canUse(userId: string, event: string, quantity?: number, metadata?: EventMetadata): Promise<CanUseResponseData>;
108
+ /** Shorthand: returns `true` if allowed, `false` if the user hit a limit. */
109
+ can(userId: string, event: string, quantity?: number, metadata?: EventMetadata): Promise<boolean>;
110
+ /**
111
+ * Atomically reserve quota before doing the AI call.
112
+ * On success call `commit(reservationId)`, on failure call `release(reservationId)`.
113
+ *
114
+ * Pass `metadata.variant` (e.g. `'4k'`, `'standard'`) when a plan row gates
115
+ * the model to a specific output tier — see {@link AiPricingLabClient.track}.
116
+ */
117
+ reserve(userId: string, event: string, quantity?: number, metadata?: EventMetadata): Promise<ReserveResponseData>;
118
+ /** Confirm a reservation after a successful AI call. */
119
+ commit(reservationId: string): Promise<void>;
120
+ /** Cancel a reservation after a failed AI call so quota is returned. */
121
+ release(reservationId: string): Promise<void>;
122
+ /**
123
+ * Get current usage counters for a user.
124
+ *
125
+ * Routes to `/api/v1/usage/me` for public (`pk_live_`) keys — that endpoint
126
+ * is the only one that accepts public keys. Secret keys hit `/api/v1/usage`.
127
+ */
128
+ usage(userId: string, event?: string): Promise<UsageResponseData>;
129
+ /** Assign or update a user's plan subscription. */
130
+ upsertSubscription(params: UpsertSubscriptionRequest): Promise<UpsertSubscriptionResponseData>;
131
+ }
132
+ declare function createClient(opts: ClientOptions): AiPricingLabClient;
133
+
134
+ declare class AiPricingLabError extends Error {
135
+ readonly code: ErrorCode;
136
+ readonly status: number;
137
+ constructor(opts: {
138
+ code: ErrorCode;
139
+ message: string;
140
+ status: number;
141
+ });
142
+ }
143
+
144
+ export { AiPricingLabClient, AiPricingLabError, type ApiError, type ApiResponse, type CanUseResponseData, type ClientOptions, type CounterSummary, type ErrorCode, type EventMetadata, type LimitGroup, type LimitUnit, type MatchRule, type PeriodAnchor, type PeriodType, type PlanLimits, type ReserveResponseData, type TrackResponseData, type UpsertSubscriptionRequest, type UpsertSubscriptionResponseData, type UsageResponseData, createClient };
@@ -0,0 +1,144 @@
1
+ type LimitUnit = 'count' | 'tokens' | 'seconds' | 'cents';
2
+ interface MatchRule {
3
+ event: string;
4
+ metadata?: Record<string, string> | undefined;
5
+ }
6
+ interface LimitGroup {
7
+ id: string;
8
+ label: string;
9
+ unit: LimitUnit;
10
+ quota: number;
11
+ matches: MatchRule[];
12
+ }
13
+ interface PlanLimits {
14
+ groups: LimitGroup[];
15
+ }
16
+ type PeriodType = 'daily' | 'weekly' | 'monthly' | 'lifetime';
17
+ type PeriodAnchor = 'subscription_start' | 'calendar';
18
+ type EventMetadata = Record<string, string>;
19
+ interface TrackResponseData {
20
+ eventId: string;
21
+ counters: CounterSummary[];
22
+ }
23
+ interface CounterSummary {
24
+ groupId: string;
25
+ count: number;
26
+ costCents: number;
27
+ }
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';
29
+ interface ApiError {
30
+ code: ErrorCode;
31
+ message: string;
32
+ }
33
+ interface ApiOk<T> {
34
+ ok: true;
35
+ data: T;
36
+ }
37
+ interface ApiFail {
38
+ ok: false;
39
+ error: ApiError;
40
+ }
41
+ type ApiResponse<T> = ApiOk<T> | ApiFail;
42
+ interface CanUseResponseData {
43
+ allowed: boolean;
44
+ reasons: string[];
45
+ details: {
46
+ groupId: string;
47
+ current: number;
48
+ quota: number;
49
+ resetsAt: string | null;
50
+ }[];
51
+ }
52
+ interface ReserveResponseData {
53
+ allowed: boolean;
54
+ reservationId?: string;
55
+ expiresAt?: string;
56
+ reasons?: string[];
57
+ }
58
+ interface UsageResponseData {
59
+ userId: string;
60
+ period: {
61
+ start: string;
62
+ end: string | null;
63
+ } | null;
64
+ counters: CounterSummary[];
65
+ }
66
+ interface UpsertSubscriptionRequest {
67
+ userId: string;
68
+ planId: string;
69
+ customLimits?: PlanLimits;
70
+ endsAt?: string;
71
+ }
72
+ interface UpsertSubscriptionResponseData {
73
+ subscriptionId: string;
74
+ userId: string;
75
+ planId: string;
76
+ startedAt: string;
77
+ }
78
+
79
+ interface ClientOptions {
80
+ apiKey: string;
81
+ baseUrl?: string;
82
+ }
83
+ declare class AiPricingLabClient {
84
+ private readonly apiKey;
85
+ private readonly baseUrl;
86
+ constructor(opts: ClientOptions);
87
+ /**
88
+ * Record that a user consumed something.
89
+ *
90
+ * `metadata` is matched against the `metadata` filters on plan match rules.
91
+ * The dashboard uses one reserved key — **`variant`** — when a plan row gates
92
+ * a model to a specific output tier (e.g. image quality `1k`/`2k`/`4k`,
93
+ * video tier `standard`/`fast`/`lite`). Pass it through so variant-gated
94
+ * buckets count the event:
95
+ *
96
+ * await apl.track('user_123', 'image.gemini-3.1-image', 1, { variant: '4k' });
97
+ */
98
+ track(userId: string, event: string, quantity?: number, metadata?: EventMetadata): Promise<TrackResponseData>;
99
+ /**
100
+ * Check if a user is allowed to perform an action.
101
+ * Returns `{ allowed, reasons, details }`.
102
+ * Use `.can()` if you only need the boolean.
103
+ *
104
+ * Pass `metadata.variant` (e.g. `'4k'`, `'standard'`) when a plan row gates
105
+ * the model to a specific output tier — see {@link AiPricingLabClient.track}.
106
+ */
107
+ canUse(userId: string, event: string, quantity?: number, metadata?: EventMetadata): Promise<CanUseResponseData>;
108
+ /** Shorthand: returns `true` if allowed, `false` if the user hit a limit. */
109
+ can(userId: string, event: string, quantity?: number, metadata?: EventMetadata): Promise<boolean>;
110
+ /**
111
+ * Atomically reserve quota before doing the AI call.
112
+ * On success call `commit(reservationId)`, on failure call `release(reservationId)`.
113
+ *
114
+ * Pass `metadata.variant` (e.g. `'4k'`, `'standard'`) when a plan row gates
115
+ * the model to a specific output tier — see {@link AiPricingLabClient.track}.
116
+ */
117
+ reserve(userId: string, event: string, quantity?: number, metadata?: EventMetadata): Promise<ReserveResponseData>;
118
+ /** Confirm a reservation after a successful AI call. */
119
+ commit(reservationId: string): Promise<void>;
120
+ /** Cancel a reservation after a failed AI call so quota is returned. */
121
+ release(reservationId: string): Promise<void>;
122
+ /**
123
+ * Get current usage counters for a user.
124
+ *
125
+ * Routes to `/api/v1/usage/me` for public (`pk_live_`) keys — that endpoint
126
+ * is the only one that accepts public keys. Secret keys hit `/api/v1/usage`.
127
+ */
128
+ usage(userId: string, event?: string): Promise<UsageResponseData>;
129
+ /** Assign or update a user's plan subscription. */
130
+ upsertSubscription(params: UpsertSubscriptionRequest): Promise<UpsertSubscriptionResponseData>;
131
+ }
132
+ declare function createClient(opts: ClientOptions): AiPricingLabClient;
133
+
134
+ declare class AiPricingLabError extends Error {
135
+ readonly code: ErrorCode;
136
+ readonly status: number;
137
+ constructor(opts: {
138
+ code: ErrorCode;
139
+ message: string;
140
+ status: number;
141
+ });
142
+ }
143
+
144
+ export { AiPricingLabClient, AiPricingLabError, type ApiError, type ApiResponse, type CanUseResponseData, type ClientOptions, type CounterSummary, type ErrorCode, type EventMetadata, type LimitGroup, type LimitUnit, type MatchRule, type PeriodAnchor, type PeriodType, type PlanLimits, type ReserveResponseData, type TrackResponseData, type UpsertSubscriptionRequest, type UpsertSubscriptionResponseData, type UsageResponseData, createClient };
package/dist/index.js ADDED
@@ -0,0 +1,170 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AiPricingLabClient: () => AiPricingLabClient,
24
+ AiPricingLabError: () => AiPricingLabError,
25
+ createClient: () => createClient
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/errors.ts
30
+ var AiPricingLabError = class extends Error {
31
+ code;
32
+ status;
33
+ constructor(opts) {
34
+ super(opts.message);
35
+ this.name = "AiPricingLabError";
36
+ this.code = opts.code;
37
+ this.status = opts.status;
38
+ }
39
+ };
40
+
41
+ // src/http.ts
42
+ async function request(baseUrl, apiKey, method, path, body) {
43
+ const url = `${baseUrl.replace(/\/$/, "")}${path}`;
44
+ const res = await fetch(url, {
45
+ method,
46
+ headers: {
47
+ "Content-Type": "application/json",
48
+ Authorization: `Bearer ${apiKey}`
49
+ },
50
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
51
+ });
52
+ const json = await res.json();
53
+ if (!json.ok) {
54
+ throw new AiPricingLabError({
55
+ code: json.error.code,
56
+ message: json.error.message,
57
+ status: res.status
58
+ });
59
+ }
60
+ return json.data;
61
+ }
62
+
63
+ // src/client.ts
64
+ var AiPricingLabClient = class {
65
+ apiKey;
66
+ baseUrl;
67
+ constructor(opts) {
68
+ this.apiKey = opts.apiKey;
69
+ this.baseUrl = opts.baseUrl ?? "https://www.aipricinglab.space";
70
+ }
71
+ /**
72
+ * Record that a user consumed something.
73
+ *
74
+ * `metadata` is matched against the `metadata` filters on plan match rules.
75
+ * The dashboard uses one reserved key — **`variant`** — when a plan row gates
76
+ * a model to a specific output tier (e.g. image quality `1k`/`2k`/`4k`,
77
+ * video tier `standard`/`fast`/`lite`). Pass it through so variant-gated
78
+ * buckets count the event:
79
+ *
80
+ * await apl.track('user_123', 'image.gemini-3.1-image', 1, { variant: '4k' });
81
+ */
82
+ async track(userId, event, quantity = 1, metadata) {
83
+ return request(this.baseUrl, this.apiKey, "POST", "/api/v1/track", {
84
+ userId,
85
+ event,
86
+ quantity,
87
+ metadata
88
+ });
89
+ }
90
+ /**
91
+ * Check if a user is allowed to perform an action.
92
+ * Returns `{ allowed, reasons, details }`.
93
+ * Use `.can()` if you only need the boolean.
94
+ *
95
+ * Pass `metadata.variant` (e.g. `'4k'`, `'standard'`) when a plan row gates
96
+ * the model to a specific output tier — see {@link AiPricingLabClient.track}.
97
+ */
98
+ async canUse(userId, event, quantity = 1, metadata) {
99
+ return request(this.baseUrl, this.apiKey, "POST", "/api/v1/can-use", {
100
+ userId,
101
+ event,
102
+ quantity,
103
+ metadata
104
+ });
105
+ }
106
+ /** Shorthand: returns `true` if allowed, `false` if the user hit a limit. */
107
+ async can(userId, event, quantity = 1, metadata) {
108
+ const { allowed } = await this.canUse(userId, event, quantity, metadata);
109
+ return allowed;
110
+ }
111
+ /**
112
+ * Atomically reserve quota before doing the AI call.
113
+ * On success call `commit(reservationId)`, on failure call `release(reservationId)`.
114
+ *
115
+ * Pass `metadata.variant` (e.g. `'4k'`, `'standard'`) when a plan row gates
116
+ * the model to a specific output tier — see {@link AiPricingLabClient.track}.
117
+ */
118
+ async reserve(userId, event, quantity = 1, metadata) {
119
+ return request(this.baseUrl, this.apiKey, "POST", "/api/v1/reserve", {
120
+ userId,
121
+ event,
122
+ quantity,
123
+ metadata
124
+ });
125
+ }
126
+ /** Confirm a reservation after a successful AI call. */
127
+ async commit(reservationId) {
128
+ await request(this.baseUrl, this.apiKey, "POST", "/api/v1/commit", {
129
+ reservationId
130
+ });
131
+ }
132
+ /** Cancel a reservation after a failed AI call so quota is returned. */
133
+ async release(reservationId) {
134
+ await request(this.baseUrl, this.apiKey, "POST", "/api/v1/release", {
135
+ reservationId
136
+ });
137
+ }
138
+ /**
139
+ * Get current usage counters for a user.
140
+ *
141
+ * Routes to `/api/v1/usage/me` for public (`pk_live_`) keys — that endpoint
142
+ * is the only one that accepts public keys. Secret keys hit `/api/v1/usage`.
143
+ */
144
+ async usage(userId, event) {
145
+ const qs = new URLSearchParams({ userId });
146
+ if (event) qs.set("event", event);
147
+ const path = this.apiKey.startsWith("pk_live_") ? `/api/v1/usage/me?${qs}` : `/api/v1/usage?${qs}`;
148
+ return request(this.baseUrl, this.apiKey, "GET", path);
149
+ }
150
+ /** Assign or update a user's plan subscription. */
151
+ async upsertSubscription(params) {
152
+ return request(
153
+ this.baseUrl,
154
+ this.apiKey,
155
+ "POST",
156
+ "/api/v1/subscriptions",
157
+ params
158
+ );
159
+ }
160
+ };
161
+ function createClient(opts) {
162
+ return new AiPricingLabClient(opts);
163
+ }
164
+ // Annotate the CommonJS export names for ESM import in node:
165
+ 0 && (module.exports = {
166
+ AiPricingLabClient,
167
+ AiPricingLabError,
168
+ createClient
169
+ });
170
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,141 @@
1
+ // src/errors.ts
2
+ var AiPricingLabError = class extends Error {
3
+ code;
4
+ status;
5
+ constructor(opts) {
6
+ super(opts.message);
7
+ this.name = "AiPricingLabError";
8
+ this.code = opts.code;
9
+ this.status = opts.status;
10
+ }
11
+ };
12
+
13
+ // src/http.ts
14
+ async function request(baseUrl, apiKey, method, path, body) {
15
+ const url = `${baseUrl.replace(/\/$/, "")}${path}`;
16
+ const res = await fetch(url, {
17
+ method,
18
+ headers: {
19
+ "Content-Type": "application/json",
20
+ Authorization: `Bearer ${apiKey}`
21
+ },
22
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
23
+ });
24
+ const json = await res.json();
25
+ if (!json.ok) {
26
+ throw new AiPricingLabError({
27
+ code: json.error.code,
28
+ message: json.error.message,
29
+ status: res.status
30
+ });
31
+ }
32
+ return json.data;
33
+ }
34
+
35
+ // src/client.ts
36
+ var AiPricingLabClient = class {
37
+ apiKey;
38
+ baseUrl;
39
+ constructor(opts) {
40
+ this.apiKey = opts.apiKey;
41
+ this.baseUrl = opts.baseUrl ?? "https://www.aipricinglab.space";
42
+ }
43
+ /**
44
+ * Record that a user consumed something.
45
+ *
46
+ * `metadata` is matched against the `metadata` filters on plan match rules.
47
+ * The dashboard uses one reserved key — **`variant`** — when a plan row gates
48
+ * a model to a specific output tier (e.g. image quality `1k`/`2k`/`4k`,
49
+ * video tier `standard`/`fast`/`lite`). Pass it through so variant-gated
50
+ * buckets count the event:
51
+ *
52
+ * await apl.track('user_123', 'image.gemini-3.1-image', 1, { variant: '4k' });
53
+ */
54
+ async track(userId, event, quantity = 1, metadata) {
55
+ return request(this.baseUrl, this.apiKey, "POST", "/api/v1/track", {
56
+ userId,
57
+ event,
58
+ quantity,
59
+ metadata
60
+ });
61
+ }
62
+ /**
63
+ * Check if a user is allowed to perform an action.
64
+ * Returns `{ allowed, reasons, details }`.
65
+ * Use `.can()` if you only need the boolean.
66
+ *
67
+ * Pass `metadata.variant` (e.g. `'4k'`, `'standard'`) when a plan row gates
68
+ * the model to a specific output tier — see {@link AiPricingLabClient.track}.
69
+ */
70
+ async canUse(userId, event, quantity = 1, metadata) {
71
+ return request(this.baseUrl, this.apiKey, "POST", "/api/v1/can-use", {
72
+ userId,
73
+ event,
74
+ quantity,
75
+ metadata
76
+ });
77
+ }
78
+ /** Shorthand: returns `true` if allowed, `false` if the user hit a limit. */
79
+ async can(userId, event, quantity = 1, metadata) {
80
+ const { allowed } = await this.canUse(userId, event, quantity, metadata);
81
+ return allowed;
82
+ }
83
+ /**
84
+ * Atomically reserve quota before doing the AI call.
85
+ * On success call `commit(reservationId)`, on failure call `release(reservationId)`.
86
+ *
87
+ * Pass `metadata.variant` (e.g. `'4k'`, `'standard'`) when a plan row gates
88
+ * the model to a specific output tier — see {@link AiPricingLabClient.track}.
89
+ */
90
+ async reserve(userId, event, quantity = 1, metadata) {
91
+ return request(this.baseUrl, this.apiKey, "POST", "/api/v1/reserve", {
92
+ userId,
93
+ event,
94
+ quantity,
95
+ metadata
96
+ });
97
+ }
98
+ /** Confirm a reservation after a successful AI call. */
99
+ async commit(reservationId) {
100
+ await request(this.baseUrl, this.apiKey, "POST", "/api/v1/commit", {
101
+ reservationId
102
+ });
103
+ }
104
+ /** Cancel a reservation after a failed AI call so quota is returned. */
105
+ async release(reservationId) {
106
+ await request(this.baseUrl, this.apiKey, "POST", "/api/v1/release", {
107
+ reservationId
108
+ });
109
+ }
110
+ /**
111
+ * Get current usage counters for a user.
112
+ *
113
+ * Routes to `/api/v1/usage/me` for public (`pk_live_`) keys — that endpoint
114
+ * is the only one that accepts public keys. Secret keys hit `/api/v1/usage`.
115
+ */
116
+ async usage(userId, event) {
117
+ const qs = new URLSearchParams({ userId });
118
+ if (event) qs.set("event", event);
119
+ const path = this.apiKey.startsWith("pk_live_") ? `/api/v1/usage/me?${qs}` : `/api/v1/usage?${qs}`;
120
+ return request(this.baseUrl, this.apiKey, "GET", path);
121
+ }
122
+ /** Assign or update a user's plan subscription. */
123
+ async upsertSubscription(params) {
124
+ return request(
125
+ this.baseUrl,
126
+ this.apiKey,
127
+ "POST",
128
+ "/api/v1/subscriptions",
129
+ params
130
+ );
131
+ }
132
+ };
133
+ function createClient(opts) {
134
+ return new AiPricingLabClient(opts);
135
+ }
136
+ export {
137
+ AiPricingLabClient,
138
+ AiPricingLabError,
139
+ createClient
140
+ };
141
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +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":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@aipricinglab/sdk",
3
+ "version": "0.1.0",
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
+ "keywords": [
6
+ "ai",
7
+ "metering",
8
+ "usage",
9
+ "limits",
10
+ "billing",
11
+ "rate-limiting",
12
+ "llm",
13
+ "tokens",
14
+ "openai",
15
+ "anthropic",
16
+ "stripe",
17
+ "saas"
18
+ ],
19
+ "homepage": "https://aipricinglab.space",
20
+ "bugs": {
21
+ "url": "https://aipricinglab.space/support"
22
+ },
23
+ "license": "MIT",
24
+ "author": "AiPricingLab",
25
+ "main": "./dist/index.js",
26
+ "module": "./dist/index.mjs",
27
+ "types": "./dist/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.mjs",
32
+ "require": "./dist/index.js"
33
+ }
34
+ },
35
+ "files": ["dist", "README.md"],
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "scripts": {
40
+ "build": "tsup",
41
+ "dev": "tsup --watch",
42
+ "typecheck": "tsc --noEmit",
43
+ "prepublishOnly": "pnpm build"
44
+ },
45
+ "devDependencies": {
46
+ "tsup": "^8.5.0",
47
+ "typescript": "^5.8.3"
48
+ }
49
+ }