@dream-api/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/dist/index.mjs ADDED
@@ -0,0 +1,571 @@
1
+ // src/types.ts
2
+ var DreamAPIException = class extends Error {
3
+ constructor(error, status = 500) {
4
+ super(error.message);
5
+ this.name = "DreamAPIException";
6
+ this.status = status;
7
+ this.code = error.error;
8
+ }
9
+ };
10
+
11
+ // src/client.ts
12
+ var DEFAULT_BASE_URL = "https://api-multi.k-c-sheffield012376.workers.dev";
13
+ var DEFAULT_SIGNUP_URL = "https://sign-up.k-c-sheffield012376.workers.dev";
14
+ var DEFAULT_CLERK_URL = "https://composed-blowfish-76.accounts.dev";
15
+ var DreamClient = class {
16
+ constructor(config) {
17
+ this.userToken = null;
18
+ this.tokenRefresher = null;
19
+ if (!config.secretKey) {
20
+ throw new Error("DreamAPI: secretKey is required");
21
+ }
22
+ this.secretKey = config.secretKey;
23
+ this.publishableKey = config.publishableKey;
24
+ this.baseUrl = config.baseUrl || DEFAULT_BASE_URL;
25
+ this.signupUrl = config.signupUrl || DEFAULT_SIGNUP_URL;
26
+ this.clerkUrl = config.clerkBaseUrl || DEFAULT_CLERK_URL;
27
+ }
28
+ /**
29
+ * Set the end-user JWT token for user-specific operations.
30
+ * Call this after the user signs in via Clerk.
31
+ */
32
+ setUserToken(token) {
33
+ this.userToken = token;
34
+ }
35
+ /**
36
+ * Clear the current user token (on sign out)
37
+ */
38
+ clearUserToken() {
39
+ this.userToken = null;
40
+ }
41
+ /**
42
+ * Set a function to refresh the token before API calls
43
+ */
44
+ setTokenRefresher(refresher) {
45
+ this.tokenRefresher = refresher;
46
+ }
47
+ /**
48
+ * Refresh token if we have a refresher set
49
+ */
50
+ async ensureFreshToken() {
51
+ if (this.tokenRefresher) {
52
+ const newToken = await this.tokenRefresher();
53
+ if (newToken) {
54
+ this.userToken = newToken;
55
+ }
56
+ }
57
+ }
58
+ /**
59
+ * Get the publishable key
60
+ */
61
+ getPublishableKey() {
62
+ return this.publishableKey;
63
+ }
64
+ /**
65
+ * Get the sign-up URL base
66
+ */
67
+ getSignupBaseUrl() {
68
+ return this.signupUrl;
69
+ }
70
+ /**
71
+ * Get the Clerk base URL for hosted auth pages
72
+ */
73
+ getClerkBaseUrl() {
74
+ return this.clerkUrl;
75
+ }
76
+ /**
77
+ * Make an authenticated request to the API
78
+ */
79
+ async request(method, endpoint, options = {}) {
80
+ const { body, requiresUserToken = false } = options;
81
+ const headers = {
82
+ "Authorization": `Bearer ${this.secretKey}`,
83
+ "Content-Type": "application/json"
84
+ };
85
+ if (this.publishableKey) {
86
+ headers["X-Publishable-Key"] = this.publishableKey;
87
+ }
88
+ if (requiresUserToken) {
89
+ if (!this.userToken) {
90
+ throw new DreamAPIException(
91
+ { error: "auth_required", message: "User token required. Call setUserToken() first." },
92
+ 401
93
+ );
94
+ }
95
+ headers["X-Clerk-Token"] = this.userToken;
96
+ } else if (this.userToken) {
97
+ headers["X-Clerk-Token"] = this.userToken;
98
+ }
99
+ const url = `${this.baseUrl}${endpoint}`;
100
+ const response = await fetch(url, {
101
+ method,
102
+ headers,
103
+ body: body ? JSON.stringify(body) : void 0
104
+ });
105
+ const data = await response.json();
106
+ if (!response.ok) {
107
+ const error = data;
108
+ throw new DreamAPIException(error, response.status);
109
+ }
110
+ return data;
111
+ }
112
+ /**
113
+ * GET request
114
+ */
115
+ async get(endpoint, requiresUserToken = false) {
116
+ return this.request("GET", endpoint, { requiresUserToken });
117
+ }
118
+ /**
119
+ * POST request
120
+ */
121
+ async post(endpoint, body, requiresUserToken = false) {
122
+ return this.request("POST", endpoint, { body, requiresUserToken });
123
+ }
124
+ /**
125
+ * PATCH request
126
+ */
127
+ async patch(endpoint, body, requiresUserToken = false) {
128
+ return this.request("PATCH", endpoint, { body, requiresUserToken });
129
+ }
130
+ /**
131
+ * DELETE request
132
+ */
133
+ async delete(endpoint, requiresUserToken = false) {
134
+ return this.request("DELETE", endpoint, { requiresUserToken });
135
+ }
136
+ };
137
+
138
+ // src/clerk.ts
139
+ var CLERK_PUBLISHABLE_KEY = "pk_test_Y29tcG9zZWQtYmxvd2Zpc2gtNzYuY2xlcmsuYWNjb3VudHMuZGV2JA";
140
+ var CLERK_CDN_URL = "https://cdn.jsdelivr.net/npm/@clerk/clerk-js@5/dist/clerk.browser.js";
141
+ var JWT_TEMPLATE = "end-user-api";
142
+ function getClerk() {
143
+ return window.Clerk;
144
+ }
145
+ var ClerkManager = class {
146
+ constructor(onTokenChange) {
147
+ this.loaded = false;
148
+ this.token = null;
149
+ this.onTokenChange = onTokenChange;
150
+ }
151
+ /**
152
+ * Load Clerk SDK (call once on page load)
153
+ */
154
+ async load() {
155
+ if (this.loaded || typeof window === "undefined") return;
156
+ const existingClerk = getClerk();
157
+ if (existingClerk) {
158
+ this.loaded = true;
159
+ await this.checkSession();
160
+ return;
161
+ }
162
+ await new Promise((resolve, reject) => {
163
+ const script = document.createElement("script");
164
+ script.src = CLERK_CDN_URL;
165
+ script.async = true;
166
+ script.crossOrigin = "anonymous";
167
+ script.setAttribute("data-clerk-publishable-key", CLERK_PUBLISHABLE_KEY);
168
+ script.onload = () => resolve();
169
+ script.onerror = () => reject(new Error("Failed to load Clerk SDK"));
170
+ document.head.appendChild(script);
171
+ });
172
+ const clerk = getClerk();
173
+ if (clerk) {
174
+ await clerk.load();
175
+ }
176
+ this.loaded = true;
177
+ await this.checkSession();
178
+ }
179
+ /**
180
+ * Check if returning from auth and grab token
181
+ */
182
+ async checkSession() {
183
+ const clerk = getClerk();
184
+ if (!clerk?.session || !clerk?.user) {
185
+ return;
186
+ }
187
+ try {
188
+ this.token = await clerk.session.getToken({ template: JWT_TEMPLATE });
189
+ this.onTokenChange?.(this.token);
190
+ } catch (err) {
191
+ console.error("Failed to get Clerk token:", err);
192
+ }
193
+ }
194
+ /**
195
+ * Check if user is signed in
196
+ */
197
+ isSignedIn() {
198
+ const clerk = getClerk();
199
+ return !!clerk?.user && !!clerk?.session;
200
+ }
201
+ /**
202
+ * Get current user info
203
+ */
204
+ getUser() {
205
+ const clerk = getClerk();
206
+ if (!clerk?.user) return null;
207
+ const user = clerk.user;
208
+ const metadata = user.publicMetadata || {};
209
+ return {
210
+ id: user.id,
211
+ email: user.primaryEmailAddress?.emailAddress || "",
212
+ plan: metadata.plan || "free",
213
+ publishableKey: metadata.publishableKey || ""
214
+ };
215
+ }
216
+ /**
217
+ * Get current token
218
+ */
219
+ getToken() {
220
+ return this.token;
221
+ }
222
+ /**
223
+ * Refresh token (call before API requests)
224
+ */
225
+ async refreshToken() {
226
+ const clerk = getClerk();
227
+ if (!clerk?.session) return null;
228
+ try {
229
+ this.token = await clerk.session.getToken({ template: JWT_TEMPLATE });
230
+ this.onTokenChange?.(this.token);
231
+ return this.token;
232
+ } catch (err) {
233
+ console.error("Failed to refresh token:", err);
234
+ return null;
235
+ }
236
+ }
237
+ /**
238
+ * Sign out
239
+ */
240
+ async signOut() {
241
+ const clerk = getClerk();
242
+ if (!clerk) return;
243
+ await clerk.signOut();
244
+ this.token = null;
245
+ this.onTokenChange?.(null);
246
+ }
247
+ /**
248
+ * Check if we're returning from auth (has clerk params/cookies)
249
+ */
250
+ static hasAuthParams() {
251
+ if (typeof window === "undefined") return false;
252
+ return window.location.search.includes("__clerk") || document.cookie.includes("__clerk") || document.cookie.includes("__session");
253
+ }
254
+ };
255
+
256
+ // src/auth.ts
257
+ var AuthHelpers = class {
258
+ constructor(client) {
259
+ this.initialized = false;
260
+ this.client = client;
261
+ this.clerk = new ClerkManager((token) => {
262
+ if (token) {
263
+ this.client.setUserToken(token);
264
+ } else {
265
+ this.client.clearUserToken();
266
+ }
267
+ });
268
+ }
269
+ /**
270
+ * Initialize auth (loads Clerk internally).
271
+ * Call this on page load to detect existing sessions.
272
+ *
273
+ * @example
274
+ * ```typescript
275
+ * await api.auth.init();
276
+ * if (api.auth.isSignedIn()) {
277
+ * // User is signed in, token already set
278
+ * await api.usage.track();
279
+ * }
280
+ * ```
281
+ */
282
+ async init() {
283
+ if (this.initialized) return;
284
+ await this.clerk.load();
285
+ this.initialized = true;
286
+ }
287
+ /**
288
+ * Check if user is signed in
289
+ */
290
+ isSignedIn() {
291
+ return this.clerk.isSignedIn();
292
+ }
293
+ /**
294
+ * Get current user info
295
+ */
296
+ getUser() {
297
+ return this.clerk.getUser();
298
+ }
299
+ /**
300
+ * Sign out the current user
301
+ */
302
+ async signOut() {
303
+ await this.clerk.signOut();
304
+ }
305
+ /**
306
+ * Refresh the auth token (call before long sessions)
307
+ */
308
+ async refreshToken() {
309
+ await this.clerk.refreshToken();
310
+ }
311
+ /**
312
+ * Check if returning from auth redirect
313
+ */
314
+ static hasAuthParams() {
315
+ return ClerkManager.hasAuthParams();
316
+ }
317
+ /**
318
+ * Get the sign-up URL for new users.
319
+ *
320
+ * Redirects to the sign-up worker which handles user creation
321
+ * and sets the required metadata (publishableKey, plan).
322
+ *
323
+ * @example
324
+ * ```typescript
325
+ * const signupUrl = api.auth.getSignUpUrl({ redirect: '/dashboard' });
326
+ * // Returns: https://sign-up.../signup?pk=pk_xxx&redirect=...
327
+ * window.location.href = signupUrl;
328
+ * ```
329
+ */
330
+ getSignUpUrl(options) {
331
+ const pk = this.client.getPublishableKey();
332
+ if (!pk) {
333
+ throw new Error("DreamAPI: publishableKey required for auth URLs");
334
+ }
335
+ const baseUrl = this.client.getSignupBaseUrl();
336
+ const params = new URLSearchParams({
337
+ pk,
338
+ redirect: options.redirect
339
+ });
340
+ return `${baseUrl}/signup?${params.toString()}`;
341
+ }
342
+ /**
343
+ * Get the sign-in URL for returning users.
344
+ *
345
+ * Redirects to Clerk's hosted sign-in page. After sign-in,
346
+ * users are redirected to your specified URL.
347
+ *
348
+ * @example
349
+ * ```typescript
350
+ * const signinUrl = api.auth.getSignInUrl({ redirect: '/dashboard' });
351
+ * window.location.href = signinUrl;
352
+ * ```
353
+ */
354
+ getSignInUrl(options) {
355
+ const clerkBaseUrl = this.client.getClerkBaseUrl();
356
+ return `${clerkBaseUrl}/sign-in?redirect_url=${encodeURIComponent(options.redirect)}`;
357
+ }
358
+ /**
359
+ * Get the customer portal URL for account management.
360
+ *
361
+ * Redirects to Clerk's hosted account page where users can
362
+ * manage their profile, password, and security settings.
363
+ *
364
+ * Note: This is separate from billing management.
365
+ * For billing, use api.billing.openPortal().
366
+ *
367
+ * @example
368
+ * ```typescript
369
+ * const portalUrl = api.auth.getCustomerPortalUrl();
370
+ * window.location.href = portalUrl;
371
+ * ```
372
+ */
373
+ getCustomerPortalUrl() {
374
+ const clerkBaseUrl = this.client.getClerkBaseUrl();
375
+ return `${clerkBaseUrl}/user`;
376
+ }
377
+ };
378
+
379
+ // src/index.ts
380
+ var DreamAPI = class {
381
+ constructor(config) {
382
+ this.client = new DreamClient(config);
383
+ this.auth = new AuthHelpers(this.client);
384
+ this.customers = new CustomerAPI(this.client);
385
+ this.usage = new UsageAPI(this.client);
386
+ this.billing = new BillingAPI(this.client);
387
+ this.products = new ProductAPI(this.client);
388
+ this.dashboard = new DashboardAPI(this.client);
389
+ }
390
+ /**
391
+ * Set the end-user JWT token.
392
+ * Required for user-specific operations (usage, billing).
393
+ *
394
+ * @example
395
+ * ```typescript
396
+ * // After user signs in via Clerk
397
+ * const token = await clerk.session.getToken();
398
+ * api.setUserToken(token);
399
+ * ```
400
+ */
401
+ setUserToken(token) {
402
+ this.client.setUserToken(token);
403
+ }
404
+ /**
405
+ * Clear the user token (on sign out)
406
+ */
407
+ clearUserToken() {
408
+ this.client.clearUserToken();
409
+ }
410
+ };
411
+ var CustomerAPI = class {
412
+ constructor(client) {
413
+ this.client = client;
414
+ }
415
+ /**
416
+ * Create a new customer
417
+ *
418
+ * @example
419
+ * ```typescript
420
+ * const { customer } = await api.customers.create({
421
+ * email: 'user@example.com',
422
+ * firstName: 'John',
423
+ * plan: 'free',
424
+ * });
425
+ * ```
426
+ */
427
+ async create(params) {
428
+ return this.client.post("/api/customers", params);
429
+ }
430
+ /**
431
+ * Get a customer by ID
432
+ */
433
+ async get(customerId) {
434
+ return this.client.get(`/api/customers/${customerId}`);
435
+ }
436
+ /**
437
+ * Update a customer's plan
438
+ */
439
+ async update(customerId, params) {
440
+ return this.client.patch(`/api/customers/${customerId}`, params);
441
+ }
442
+ /**
443
+ * Delete a customer
444
+ */
445
+ async delete(customerId) {
446
+ return this.client.delete(`/api/customers/${customerId}`);
447
+ }
448
+ };
449
+ var UsageAPI = class {
450
+ constructor(client) {
451
+ this.client = client;
452
+ }
453
+ /**
454
+ * Track a usage event.
455
+ * Increments the user's usage counter for the current period.
456
+ *
457
+ * @example
458
+ * ```typescript
459
+ * const { usage } = await api.usage.track();
460
+ * console.log(`Used ${usage.usageCount} of ${usage.limit}`);
461
+ * ```
462
+ */
463
+ async track() {
464
+ const response = await this.client.post("/api/data", null, true);
465
+ if ("success" in response) {
466
+ return response;
467
+ }
468
+ return { success: true, usage: response };
469
+ }
470
+ /**
471
+ * Check current usage without incrementing.
472
+ *
473
+ * @example
474
+ * ```typescript
475
+ * const usage = await api.usage.check();
476
+ * if (usage.remaining <= 0) {
477
+ * // Show upgrade prompt
478
+ * }
479
+ * ```
480
+ */
481
+ async check() {
482
+ const response = await this.client.get("/api/usage", true);
483
+ return response.usage || response;
484
+ }
485
+ };
486
+ var BillingAPI = class {
487
+ constructor(client) {
488
+ this.client = client;
489
+ }
490
+ /**
491
+ * Create a checkout session for subscription upgrade.
492
+ *
493
+ * @example
494
+ * ```typescript
495
+ * const { url } = await api.billing.createCheckout({
496
+ * tier: 'pro',
497
+ * successUrl: '/success',
498
+ * cancelUrl: '/pricing',
499
+ * });
500
+ * window.location.href = url;
501
+ * ```
502
+ */
503
+ async createCheckout(params) {
504
+ return this.client.post("/api/create-checkout", params, true);
505
+ }
506
+ /**
507
+ * Open the Stripe customer portal for billing management.
508
+ *
509
+ * @example
510
+ * ```typescript
511
+ * const { url } = await api.billing.openPortal({ returnUrl: '/dashboard' });
512
+ * window.location.href = url;
513
+ * ```
514
+ */
515
+ async openPortal(params) {
516
+ return this.client.post("/api/customer-portal", params, true);
517
+ }
518
+ };
519
+ var ProductAPI = class {
520
+ constructor(client) {
521
+ this.client = client;
522
+ }
523
+ /**
524
+ * List subscription tiers
525
+ */
526
+ async listTiers() {
527
+ return this.client.get("/api/tiers");
528
+ }
529
+ /**
530
+ * List products (for store projects)
531
+ */
532
+ async list() {
533
+ return this.client.get("/api/products");
534
+ }
535
+ /**
536
+ * Create a cart checkout (guest checkout for store)
537
+ */
538
+ async cartCheckout(params) {
539
+ return this.client.post("/api/cart/checkout", params);
540
+ }
541
+ };
542
+ var DashboardAPI = class {
543
+ constructor(client) {
544
+ this.client = client;
545
+ }
546
+ /**
547
+ * Get dashboard metrics
548
+ *
549
+ * @example
550
+ * ```typescript
551
+ * const dashboard = await api.dashboard.get();
552
+ * console.log(`MRR: $${dashboard.mrr}`);
553
+ * console.log(`Active subs: ${dashboard.activeSubscriptions}`);
554
+ * ```
555
+ */
556
+ async get() {
557
+ return this.client.get("/api/dashboard");
558
+ }
559
+ /**
560
+ * Get aggregate totals across all projects
561
+ */
562
+ async getTotals() {
563
+ return this.client.get("/api/dashboard/totals");
564
+ }
565
+ };
566
+ var index_default = DreamAPI;
567
+ export {
568
+ DreamAPI,
569
+ DreamAPIException,
570
+ index_default as default
571
+ };
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@dream-api/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Official SDK for Dream API - Auth, billing, and usage tracking in one API",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
20
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
21
+ "typecheck": "tsc --noEmit",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "keywords": [
25
+ "api",
26
+ "billing",
27
+ "usage-tracking",
28
+ "saas",
29
+ "stripe",
30
+ "clerk",
31
+ "authentication"
32
+ ],
33
+ "author": "Dream API",
34
+ "license": "MIT",
35
+ "devDependencies": {
36
+ "tsup": "^8.0.0",
37
+ "typescript": "^5.0.0"
38
+ },
39
+ "peerDependencies": {},
40
+ "engines": {
41
+ "node": ">=18"
42
+ }
43
+ }