Package not found. Please check the package name and try again.

@closeloop/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/nextjs.js ADDED
@@ -0,0 +1,955 @@
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/nextjs.ts
21
+ var nextjs_exports = {};
22
+ __export(nextjs_exports, {
23
+ AuthenticationError: () => AuthenticationError,
24
+ CloseLoop: () => CloseLoop,
25
+ CloseLoopError: () => CloseLoopError,
26
+ CreditsExpiredError: () => CreditsExpiredError,
27
+ InsufficientCreditsError: () => InsufficientCreditsError,
28
+ NetworkError: () => NetworkError,
29
+ NotFoundError: () => NotFoundError,
30
+ RateLimitError: () => RateLimitError,
31
+ ValidationError: () => ValidationError,
32
+ consumeCreditsAfterRequest: () => consumeCreditsAfterRequest,
33
+ creditGate: () => creditGate
34
+ });
35
+ module.exports = __toCommonJS(nextjs_exports);
36
+ var import_server = require("next/server");
37
+
38
+ // src/schemas/validation.ts
39
+ var import_zod = require("zod");
40
+ var walletAddressSchema = import_zod.z.string().regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum wallet address");
41
+ var planIdSchema = import_zod.z.string().min(1, "Plan ID is required").max(100, "Plan ID too long");
42
+ var balanceIdSchema = import_zod.z.string().min(1, "Balance ID is required").max(100, "Balance ID too long");
43
+ var creditAmountSchema = import_zod.z.number().int("Amount must be an integer").positive("Amount must be positive").max(1e9, "Amount exceeds maximum");
44
+ var paginationLimitSchema = import_zod.z.number().int().positive().max(100, "Limit cannot exceed 100");
45
+ var transactionTypeSchema = import_zod.z.enum([
46
+ "PURCHASE",
47
+ "CONSUMPTION",
48
+ "REFUND",
49
+ "EXPIRATION"
50
+ ]);
51
+ var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
52
+ function sanitizeMetadata(metadata) {
53
+ if (!metadata) return void 0;
54
+ const clean = /* @__PURE__ */ Object.create(null);
55
+ for (const [key, value] of Object.entries(metadata)) {
56
+ if (DANGEROUS_KEYS.has(key)) continue;
57
+ clean[key] = value;
58
+ }
59
+ return Object.freeze(clean);
60
+ }
61
+ var verifyCreditsSchema = import_zod.z.object({
62
+ walletAddress: walletAddressSchema,
63
+ planId: planIdSchema,
64
+ amount: creditAmountSchema
65
+ });
66
+ var consumeCreditsSchema = import_zod.z.object({
67
+ walletAddress: walletAddressSchema,
68
+ planId: planIdSchema,
69
+ amount: creditAmountSchema,
70
+ consumedBy: import_zod.z.string().max(100).optional(),
71
+ metadata: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
72
+ idempotencyKey: import_zod.z.string().max(100).optional()
73
+ });
74
+ var batchConsumeSchema = import_zod.z.object({
75
+ operations: import_zod.z.array(
76
+ import_zod.z.object({
77
+ walletAddress: walletAddressSchema,
78
+ planId: planIdSchema,
79
+ amount: creditAmountSchema,
80
+ consumedBy: import_zod.z.string().max(100).optional(),
81
+ metadata: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
82
+ })
83
+ ).min(1, "At least one operation required").max(100, "Maximum 100 operations per batch"),
84
+ atomic: import_zod.z.boolean().optional()
85
+ });
86
+ var getBalanceSchema = import_zod.z.object({
87
+ walletAddress: walletAddressSchema,
88
+ planId: planIdSchema
89
+ });
90
+ var listBalancesSchema = import_zod.z.object({
91
+ walletAddress: walletAddressSchema,
92
+ activeOnly: import_zod.z.boolean().optional(),
93
+ limit: paginationLimitSchema.optional(),
94
+ cursor: import_zod.z.string().max(200).optional()
95
+ });
96
+ var listTransactionsSchema = import_zod.z.object({
97
+ balanceId: balanceIdSchema,
98
+ type: transactionTypeSchema.optional(),
99
+ limit: paginationLimitSchema.optional(),
100
+ cursor: import_zod.z.string().max(200).optional()
101
+ });
102
+ var webhookEventTypeSchema = import_zod.z.enum([
103
+ "payment.success",
104
+ "credits.low",
105
+ "credits.expired"
106
+ ]);
107
+ var webhookEventSchema = import_zod.z.object({
108
+ type: webhookEventTypeSchema,
109
+ id: import_zod.z.string().min(1),
110
+ createdAt: import_zod.z.string(),
111
+ data: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())
112
+ });
113
+ var paymentSuccessPayloadSchema = import_zod.z.object({
114
+ transactionId: import_zod.z.string(),
115
+ planId: import_zod.z.string(),
116
+ planName: import_zod.z.string(),
117
+ amount: import_zod.z.number(),
118
+ requests: import_zod.z.number().nullable(),
119
+ creditAmount: import_zod.z.number().nullable(),
120
+ transactionHash: import_zod.z.string(),
121
+ payerAddress: import_zod.z.string(),
122
+ accessType: import_zod.z.string()
123
+ });
124
+ var creditsLowPayloadSchema = import_zod.z.object({
125
+ walletAddress: import_zod.z.string(),
126
+ planId: import_zod.z.string(),
127
+ remainingCredits: import_zod.z.number(),
128
+ threshold: import_zod.z.number()
129
+ });
130
+ var creditsExpiredPayloadSchema = import_zod.z.object({
131
+ walletAddress: import_zod.z.string(),
132
+ planId: import_zod.z.string(),
133
+ expiredCredits: import_zod.z.number(),
134
+ expiresAt: import_zod.z.string()
135
+ });
136
+
137
+ // src/utils/errors.ts
138
+ var CloseLoopError = class extends Error {
139
+ constructor(message, code, statusCode, details) {
140
+ super(message);
141
+ this.code = code;
142
+ this.statusCode = statusCode;
143
+ this.details = details;
144
+ this.name = "CloseLoopError";
145
+ }
146
+ };
147
+ var InsufficientCreditsError = class extends CloseLoopError {
148
+ constructor(remainingCredits, requiredCredits) {
149
+ super(
150
+ `Insufficient credits: ${remainingCredits} available, ${requiredCredits} required`,
151
+ "INSUFFICIENT_CREDITS",
152
+ 402
153
+ );
154
+ this.remainingCredits = remainingCredits;
155
+ this.requiredCredits = requiredCredits;
156
+ this.name = "InsufficientCreditsError";
157
+ }
158
+ };
159
+ var CreditsExpiredError = class extends CloseLoopError {
160
+ constructor(expiresAt) {
161
+ super(
162
+ `Credits expired at ${expiresAt.toISOString()}`,
163
+ "CREDITS_EXPIRED",
164
+ 410
165
+ );
166
+ this.expiresAt = expiresAt;
167
+ this.name = "CreditsExpiredError";
168
+ }
169
+ };
170
+ var AuthenticationError = class extends CloseLoopError {
171
+ constructor(message = "Invalid API key") {
172
+ super(message, "AUTHENTICATION_ERROR", 401);
173
+ this.name = "AuthenticationError";
174
+ }
175
+ };
176
+ var RateLimitError = class extends CloseLoopError {
177
+ constructor(retryAfter) {
178
+ super("Rate limit exceeded", "RATE_LIMIT", 429);
179
+ this.retryAfter = retryAfter;
180
+ this.name = "RateLimitError";
181
+ }
182
+ };
183
+ var NetworkError = class extends CloseLoopError {
184
+ constructor(message, cause) {
185
+ super(message, "NETWORK_ERROR");
186
+ this.cause = cause;
187
+ this.name = "NetworkError";
188
+ }
189
+ };
190
+ var NotFoundError = class extends CloseLoopError {
191
+ constructor(resource) {
192
+ super(`${resource} not found`, "NOT_FOUND", 404);
193
+ this.name = "NotFoundError";
194
+ }
195
+ };
196
+ var ValidationError = class extends CloseLoopError {
197
+ constructor(message, field) {
198
+ super(message, "VALIDATION_ERROR", 400);
199
+ this.field = field;
200
+ this.name = "ValidationError";
201
+ }
202
+ };
203
+
204
+ // src/constants.ts
205
+ var DEFAULT_BASE_URL = "https://closeloop.app";
206
+ var DEFAULT_TIMEOUT = 3e4;
207
+ var SDK_VERSION = "0.1.0";
208
+ var USER_AGENT = `closeloop-node/${SDK_VERSION}`;
209
+
210
+ // src/utils/validation.ts
211
+ function validateInput(schema, data) {
212
+ const result = schema.safeParse(data);
213
+ if (!result.success) {
214
+ throw new CloseLoopError(
215
+ `Validation error: ${result.error?.message || "Invalid input"}`,
216
+ "VALIDATION_ERROR",
217
+ 400
218
+ );
219
+ }
220
+ return result.data;
221
+ }
222
+
223
+ // src/resources/balances.ts
224
+ var ENDPOINTS = {
225
+ BALANCE: "/api/credit/balance",
226
+ BALANCES: "/api/credit/balances",
227
+ STATS: "/api/credit/stats"
228
+ };
229
+ var Balances = class {
230
+ constructor(http) {
231
+ this.http = http;
232
+ }
233
+ /**
234
+ * Get a specific credit balance for a wallet and plan.
235
+ *
236
+ * @example
237
+ * ```typescript
238
+ * const balance = await client.balances.get({
239
+ * walletAddress: "0x1234...",
240
+ * planId: "plan_abc123"
241
+ * })
242
+ *
243
+ * if (balance) {
244
+ * console.log(`Credits: ${balance.remainingCredits}/${balance.totalCredits}`)
245
+ * }
246
+ * ```
247
+ *
248
+ * @returns The credit balance, or null if not found
249
+ * @throws {CloseLoopError} When input validation fails
250
+ */
251
+ async get(params) {
252
+ const validated = validateInput(getBalanceSchema, params);
253
+ const query = this.buildQueryString({
254
+ wallet: validated.walletAddress,
255
+ plan: validated.planId
256
+ });
257
+ try {
258
+ return await this.http.request({
259
+ method: "GET",
260
+ path: `${ENDPOINTS.BALANCE}?${query}`
261
+ });
262
+ } catch (error) {
263
+ if (error instanceof NotFoundError) {
264
+ return null;
265
+ }
266
+ throw error;
267
+ }
268
+ }
269
+ /**
270
+ * List all credit balances for a wallet.
271
+ *
272
+ * @example
273
+ * ```typescript
274
+ * const { balances, nextCursor } = await client.balances.list({
275
+ * walletAddress: "0x1234...",
276
+ * activeOnly: true,
277
+ * limit: 10
278
+ * })
279
+ *
280
+ * for (const balance of balances) {
281
+ * console.log(`${balance.planName}: ${balance.remainingCredits} credits`)
282
+ * }
283
+ *
284
+ * // Paginate if needed
285
+ * if (nextCursor) {
286
+ * const nextPage = await client.balances.list({
287
+ * walletAddress: "0x1234...",
288
+ * cursor: nextCursor
289
+ * })
290
+ * }
291
+ * ```
292
+ *
293
+ * @throws {CloseLoopError} When input validation fails
294
+ */
295
+ async list(params) {
296
+ const validated = validateInput(listBalancesSchema, params);
297
+ const query = this.buildQueryString({
298
+ wallet: validated.walletAddress,
299
+ activeOnly: validated.activeOnly,
300
+ limit: validated.limit,
301
+ cursor: validated.cursor
302
+ });
303
+ return this.http.request({
304
+ method: "GET",
305
+ path: `${ENDPOINTS.BALANCES}?${query}`
306
+ });
307
+ }
308
+ /**
309
+ * Get transaction history for a balance.
310
+ *
311
+ * @example
312
+ * ```typescript
313
+ * const { transactions } = await client.balances.transactions({
314
+ * balanceId: "bal_xyz",
315
+ * type: "CONSUMPTION",
316
+ * limit: 50
317
+ * })
318
+ *
319
+ * for (const tx of transactions) {
320
+ * console.log(`${tx.type}: ${tx.amount} - ${tx.description}`)
321
+ * }
322
+ * ```
323
+ *
324
+ * @throws {CloseLoopError} When input validation fails
325
+ */
326
+ async transactions(params) {
327
+ const validated = validateInput(listTransactionsSchema, params);
328
+ const query = this.buildQueryString({
329
+ type: validated.type,
330
+ limit: validated.limit,
331
+ cursor: validated.cursor
332
+ });
333
+ const basePath = `${ENDPOINTS.BALANCE}/${encodeURIComponent(validated.balanceId)}/transactions`;
334
+ const path = query ? `${basePath}?${query}` : basePath;
335
+ return this.http.request({
336
+ method: "GET",
337
+ path
338
+ });
339
+ }
340
+ /**
341
+ * Get aggregated stats for a wallet's credits.
342
+ *
343
+ * @example
344
+ * ```typescript
345
+ * const stats = await client.balances.stats("0x1234...")
346
+ * console.log(`Total: ${stats.totalCredits}, Used: ${stats.totalUsed}`)
347
+ * ```
348
+ *
349
+ * @throws {CloseLoopError} When input validation fails
350
+ */
351
+ async stats(walletAddress) {
352
+ validateInput(walletAddressSchema, walletAddress);
353
+ const query = this.buildQueryString({ wallet: walletAddress });
354
+ return this.http.request({
355
+ method: "GET",
356
+ path: `${ENDPOINTS.STATS}?${query}`
357
+ });
358
+ }
359
+ // ==========================================================================
360
+ // Private Helpers
361
+ // ==========================================================================
362
+ /**
363
+ * Build URL query string from params, filtering out undefined values
364
+ */
365
+ buildQueryString(params) {
366
+ const query = new URLSearchParams();
367
+ for (const [key, value] of Object.entries(params)) {
368
+ if (value !== void 0) {
369
+ query.set(key, String(value));
370
+ }
371
+ }
372
+ return query.toString();
373
+ }
374
+ };
375
+
376
+ // src/resources/credits.ts
377
+ var ENDPOINTS2 = {
378
+ VERIFY: "/api/credit/verify",
379
+ CONSUME: "/api/credit/consume",
380
+ BATCH_CONSUME: "/api/credit/consume/batch"
381
+ };
382
+ var Credits = class {
383
+ constructor(http) {
384
+ this.http = http;
385
+ }
386
+ /**
387
+ * Verify if a user has enough credits without consuming them.
388
+ *
389
+ * @example
390
+ * ```typescript
391
+ * const result = await client.credits.verify({
392
+ * walletAddress: "0x1234...",
393
+ * planId: "plan_abc123",
394
+ * amount: 10
395
+ * })
396
+ *
397
+ * if (result.hasEnoughCredits) {
398
+ * // Proceed with service
399
+ * }
400
+ * ```
401
+ *
402
+ * @throws {CloseLoopError} When input validation fails
403
+ */
404
+ async verify(params) {
405
+ const validated = validateInput(verifyCreditsSchema, params);
406
+ return this.http.request({
407
+ method: "POST",
408
+ path: ENDPOINTS2.VERIFY,
409
+ body: validated
410
+ });
411
+ }
412
+ /**
413
+ * Consume credits from a user's balance.
414
+ *
415
+ * @example
416
+ * ```typescript
417
+ * const result = await client.credits.consume({
418
+ * walletAddress: "0x1234...",
419
+ * planId: "plan_abc123",
420
+ * amount: 1,
421
+ * consumedBy: "ai-generation",
422
+ * metadata: { requestId: "req_xyz" }
423
+ * })
424
+ *
425
+ * console.log(`Remaining: ${result.remainingCredits}`)
426
+ * ```
427
+ *
428
+ * @throws {InsufficientCreditsError} When user doesn't have enough credits
429
+ * @throws {CreditsExpiredError} When credits have expired
430
+ * @throws {CloseLoopError} When input validation fails
431
+ */
432
+ async consume(params) {
433
+ const validated = validateInput(consumeCreditsSchema, params);
434
+ return this.http.request({
435
+ method: "POST",
436
+ path: ENDPOINTS2.CONSUME,
437
+ body: this.buildConsumeBody(validated),
438
+ headers: this.buildIdempotencyHeaders(validated.idempotencyKey)
439
+ });
440
+ }
441
+ /**
442
+ * Verify and consume credits in a single atomic operation.
443
+ * This is useful when you want to ensure credits are available
444
+ * before consuming them, without race conditions.
445
+ *
446
+ * @example
447
+ * ```typescript
448
+ * try {
449
+ * const result = await client.credits.verifyAndConsume({
450
+ * walletAddress: "0x1234...",
451
+ * planId: "plan_abc123",
452
+ * amount: 5,
453
+ * consumedBy: "batch-processing"
454
+ * })
455
+ * // Credits were verified and consumed
456
+ * } catch (error) {
457
+ * if (error instanceof InsufficientCreditsError) {
458
+ * // Handle insufficient credits
459
+ * }
460
+ * }
461
+ * ```
462
+ *
463
+ * @throws {InsufficientCreditsError} When user doesn't have enough credits
464
+ * @throws {CreditsExpiredError} When credits have expired
465
+ * @throws {CloseLoopError} When input validation fails
466
+ */
467
+ async verifyAndConsume(params) {
468
+ const validated = validateInput(consumeCreditsSchema, params);
469
+ const verification = await this.http.request({
470
+ method: "POST",
471
+ path: ENDPOINTS2.VERIFY,
472
+ body: {
473
+ walletAddress: validated.walletAddress,
474
+ planId: validated.planId,
475
+ amount: validated.amount
476
+ }
477
+ });
478
+ this.assertCreditsAvailable(verification, validated.amount);
479
+ return this.http.request({
480
+ method: "POST",
481
+ path: ENDPOINTS2.CONSUME,
482
+ body: this.buildConsumeBody(validated),
483
+ headers: this.buildIdempotencyHeaders(validated.idempotencyKey)
484
+ });
485
+ }
486
+ /**
487
+ * Consume credits in batch.
488
+ *
489
+ * @example
490
+ * ```typescript
491
+ * const result = await client.credits.batchConsume({
492
+ * operations: [
493
+ * { walletAddress: "0x1234...", planId: "plan_a", amount: 1 },
494
+ * { walletAddress: "0x5678...", planId: "plan_b", amount: 2 }
495
+ * ],
496
+ * atomic: false // Allow partial success
497
+ * })
498
+ *
499
+ * console.log(`Success: ${result.successCount}, Failed: ${result.failedCount}`)
500
+ * ```
501
+ *
502
+ * @throws {CloseLoopError} When input validation fails
503
+ */
504
+ async batchConsume(params) {
505
+ const validated = validateInput(batchConsumeSchema, params);
506
+ const sanitizedOperations = validated.operations.map((op) => ({
507
+ ...op,
508
+ metadata: sanitizeMetadata(op.metadata)
509
+ }));
510
+ return this.http.request({
511
+ method: "POST",
512
+ path: ENDPOINTS2.BATCH_CONSUME,
513
+ body: {
514
+ operations: sanitizedOperations,
515
+ atomic: validated.atomic
516
+ }
517
+ });
518
+ }
519
+ // ==========================================================================
520
+ // Private Helpers
521
+ // ==========================================================================
522
+ /**
523
+ * Build consumption request body with sanitized metadata
524
+ */
525
+ buildConsumeBody(validated) {
526
+ return {
527
+ walletAddress: validated.walletAddress,
528
+ planId: validated.planId,
529
+ amount: validated.amount,
530
+ consumedBy: validated.consumedBy,
531
+ metadata: sanitizeMetadata(validated.metadata)
532
+ };
533
+ }
534
+ /**
535
+ * Build idempotency headers if key is provided
536
+ */
537
+ buildIdempotencyHeaders(idempotencyKey) {
538
+ if (!idempotencyKey) return {};
539
+ return { "Idempotency-Key": idempotencyKey };
540
+ }
541
+ /**
542
+ * Assert credits are available, throw typed errors if not
543
+ */
544
+ assertCreditsAvailable(verification, requiredAmount) {
545
+ if (!verification.hasEnoughCredits) {
546
+ throw new InsufficientCreditsError(
547
+ verification.remainingCredits,
548
+ requiredAmount
549
+ );
550
+ }
551
+ if (verification.expiresAt) {
552
+ const expirationDate = new Date(verification.expiresAt);
553
+ if (expirationDate < /* @__PURE__ */ new Date()) {
554
+ throw new CreditsExpiredError(expirationDate);
555
+ }
556
+ }
557
+ }
558
+ };
559
+
560
+ // src/utils/crypto.ts
561
+ var import_crypto = require("crypto");
562
+ function generateSignature(payload, secret) {
563
+ return (0, import_crypto.createHmac)("sha256", secret).update(payload).digest("hex");
564
+ }
565
+ function verifySignature(payload, signature, secret) {
566
+ const expectedSignature = generateSignature(payload, secret);
567
+ if (signature.length !== expectedSignature.length) {
568
+ return false;
569
+ }
570
+ return (0, import_crypto.timingSafeEqual)(Buffer.from(signature), Buffer.from(expectedSignature));
571
+ }
572
+
573
+ // src/resources/webhooks.ts
574
+ var ERROR_CODES = {
575
+ VALIDATION: "VALIDATION_ERROR",
576
+ INVALID_SIGNATURE: "INVALID_SIGNATURE",
577
+ INVALID_PAYLOAD: "INVALID_PAYLOAD"
578
+ };
579
+ var Webhooks = class {
580
+ /**
581
+ * Verify a webhook signature and parse the event.
582
+ *
583
+ * @example
584
+ * ```typescript
585
+ * // Express handler
586
+ * app.post("/webhook", (req, res) => {
587
+ * try {
588
+ * const event = client.webhooks.verify({
589
+ * payload: req.body,
590
+ * signature: req.headers["x-closeloop-signature"],
591
+ * secret: process.env.WEBHOOK_SECRET!
592
+ * })
593
+ *
594
+ * if (client.webhooks.isPaymentSuccess(event)) {
595
+ * const { creditAmount, payerAddress } = event.data
596
+ * // Handle credit purchase
597
+ * }
598
+ *
599
+ * res.json({ received: true })
600
+ * } catch (error) {
601
+ * res.status(400).json({ error: "Invalid signature" })
602
+ * }
603
+ * })
604
+ * ```
605
+ *
606
+ * @example
607
+ * ```typescript
608
+ * // Next.js App Router
609
+ * export async function POST(request: Request) {
610
+ * const payload = await request.text()
611
+ * const signature = request.headers.get("x-closeloop-signature")!
612
+ *
613
+ * const event = client.webhooks.verify({
614
+ * payload,
615
+ * signature,
616
+ * secret: process.env.WEBHOOK_SECRET!
617
+ * })
618
+ *
619
+ * // Handle event...
620
+ * return Response.json({ received: true })
621
+ * }
622
+ * ```
623
+ *
624
+ * @throws {CloseLoopError} When signature is invalid or payload is malformed
625
+ */
626
+ verify(params) {
627
+ this.validateRequiredParams(params);
628
+ const payloadString = this.normalizePayload(params.payload);
629
+ this.verifySignatureOrThrow(payloadString, params.signature, params.secret);
630
+ const parsed = this.parseJsonOrThrow(payloadString);
631
+ return this.validateEventStructure(parsed);
632
+ }
633
+ /**
634
+ * Type guard for payment.success events
635
+ * Also validates the payload structure
636
+ *
637
+ * @example
638
+ * ```typescript
639
+ * if (client.webhooks.isPaymentSuccess(event)) {
640
+ * // TypeScript knows event.data is PaymentSuccessPayload
641
+ * console.log(event.data.creditAmount)
642
+ * }
643
+ * ```
644
+ */
645
+ isPaymentSuccess(event) {
646
+ return event.type === "payment.success" && paymentSuccessPayloadSchema.safeParse(event.data).success;
647
+ }
648
+ /**
649
+ * Type guard for credits.low events
650
+ * Also validates the payload structure
651
+ *
652
+ * @example
653
+ * ```typescript
654
+ * if (client.webhooks.isCreditsLow(event)) {
655
+ * // TypeScript knows event.data is CreditsLowPayload
656
+ * console.log(`Low balance: ${event.data.remainingCredits}`)
657
+ * }
658
+ * ```
659
+ */
660
+ isCreditsLow(event) {
661
+ return event.type === "credits.low" && creditsLowPayloadSchema.safeParse(event.data).success;
662
+ }
663
+ /**
664
+ * Type guard for credits.expired events
665
+ * Also validates the payload structure
666
+ *
667
+ * @example
668
+ * ```typescript
669
+ * if (client.webhooks.isCreditsExpired(event)) {
670
+ * // TypeScript knows event.data is CreditsExpiredPayload
671
+ * console.log(`Expired: ${event.data.expiredCredits} credits`)
672
+ * }
673
+ * ```
674
+ */
675
+ isCreditsExpired(event) {
676
+ return event.type === "credits.expired" && creditsExpiredPayloadSchema.safeParse(event.data).success;
677
+ }
678
+ // ==========================================================================
679
+ // Private Helpers
680
+ // ==========================================================================
681
+ /**
682
+ * Validate that all required parameters are present
683
+ */
684
+ validateRequiredParams(params) {
685
+ if (!params.payload) {
686
+ throw new CloseLoopError(
687
+ "Webhook payload is required",
688
+ ERROR_CODES.VALIDATION,
689
+ 400
690
+ );
691
+ }
692
+ if (!params.signature) {
693
+ throw new CloseLoopError(
694
+ "Webhook signature is required",
695
+ ERROR_CODES.VALIDATION,
696
+ 400
697
+ );
698
+ }
699
+ if (!params.secret) {
700
+ throw new CloseLoopError(
701
+ "Webhook secret is required",
702
+ ERROR_CODES.VALIDATION,
703
+ 400
704
+ );
705
+ }
706
+ }
707
+ /**
708
+ * Convert payload to string regardless of input type
709
+ */
710
+ normalizePayload(payload) {
711
+ return typeof payload === "string" ? payload : payload.toString("utf8");
712
+ }
713
+ /**
714
+ * Verify HMAC signature or throw error
715
+ */
716
+ verifySignatureOrThrow(payload, signature, secret) {
717
+ if (!verifySignature(payload, signature, secret)) {
718
+ throw new CloseLoopError(
719
+ "Invalid webhook signature",
720
+ ERROR_CODES.INVALID_SIGNATURE,
721
+ 401
722
+ );
723
+ }
724
+ }
725
+ /**
726
+ * Parse JSON payload or throw error
727
+ */
728
+ parseJsonOrThrow(payload) {
729
+ try {
730
+ return JSON.parse(payload);
731
+ } catch {
732
+ throw new CloseLoopError(
733
+ "Invalid webhook payload: could not parse JSON",
734
+ ERROR_CODES.INVALID_PAYLOAD,
735
+ 400
736
+ );
737
+ }
738
+ }
739
+ /**
740
+ * Validate event structure using Zod schema
741
+ */
742
+ validateEventStructure(parsed) {
743
+ const result = webhookEventSchema.safeParse(parsed);
744
+ if (!result.success) {
745
+ throw new CloseLoopError(
746
+ "Invalid webhook payload structure",
747
+ ERROR_CODES.INVALID_PAYLOAD,
748
+ 400
749
+ );
750
+ }
751
+ return result.data;
752
+ }
753
+ };
754
+
755
+ // src/utils/http.ts
756
+ var HttpClient = class {
757
+ baseUrl;
758
+ apiKey;
759
+ timeout;
760
+ constructor(options) {
761
+ this.baseUrl = options.baseUrl.replace(/\/$/, "");
762
+ this.apiKey = options.apiKey;
763
+ this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
764
+ }
765
+ /**
766
+ * Make an HTTP request to the CloseLoop API
767
+ */
768
+ async request(options) {
769
+ const url = `${this.baseUrl}${options.path}`;
770
+ const controller = new AbortController();
771
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
772
+ try {
773
+ const response = await fetch(url, {
774
+ method: options.method,
775
+ headers: {
776
+ "Content-Type": "application/json",
777
+ "User-Agent": USER_AGENT,
778
+ "X-API-Key": this.apiKey,
779
+ ...options.headers
780
+ },
781
+ body: options.body ? JSON.stringify(options.body) : void 0,
782
+ signal: controller.signal
783
+ });
784
+ clearTimeout(timeoutId);
785
+ if (!response.ok) {
786
+ await this.handleErrorResponse(response);
787
+ }
788
+ if (response.status === 204) {
789
+ return {};
790
+ }
791
+ return await response.json();
792
+ } catch (error) {
793
+ clearTimeout(timeoutId);
794
+ if (error instanceof CloseLoopError) {
795
+ throw error;
796
+ }
797
+ if (error instanceof Error && error.name === "AbortError") {
798
+ throw new NetworkError("Request timeout");
799
+ }
800
+ throw new NetworkError(
801
+ error instanceof Error ? error.message : "Network request failed",
802
+ error instanceof Error ? error : void 0
803
+ );
804
+ }
805
+ }
806
+ async handleErrorResponse(response) {
807
+ let errorData = {};
808
+ try {
809
+ const json = await response.json();
810
+ errorData = json;
811
+ } catch {
812
+ }
813
+ const message = errorData.message || `Request failed with status ${response.status}`;
814
+ switch (response.status) {
815
+ case 401:
816
+ throw new AuthenticationError(message);
817
+ case 404:
818
+ throw new NotFoundError(message);
819
+ case 429: {
820
+ const retryAfter = response.headers.get("Retry-After");
821
+ throw new RateLimitError(
822
+ retryAfter ? parseInt(retryAfter, 10) : void 0
823
+ );
824
+ }
825
+ default:
826
+ throw new CloseLoopError(
827
+ message,
828
+ errorData.code || "API_ERROR",
829
+ response.status,
830
+ errorData.details
831
+ );
832
+ }
833
+ }
834
+ };
835
+
836
+ // src/client.ts
837
+ var CloseLoop = class {
838
+ httpClient;
839
+ /**
840
+ * Credit consumption and verification operations
841
+ */
842
+ credits;
843
+ /**
844
+ * Credit balance queries
845
+ */
846
+ balances;
847
+ /**
848
+ * Webhook signature verification utilities
849
+ */
850
+ webhooks;
851
+ constructor(options) {
852
+ if (!options.apiKey) {
853
+ throw new Error("CloseLoop API key is required");
854
+ }
855
+ const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
856
+ this.httpClient = new HttpClient({
857
+ baseUrl,
858
+ apiKey: options.apiKey,
859
+ timeout: options.timeout
860
+ });
861
+ this.credits = new Credits(this.httpClient);
862
+ this.balances = new Balances(this.httpClient);
863
+ this.webhooks = new Webhooks();
864
+ }
865
+ };
866
+
867
+ // src/nextjs.ts
868
+ function errorResponse(error, status, extra) {
869
+ return import_server.NextResponse.json({ error, ...extra }, { status });
870
+ }
871
+ function handleCreditError(error) {
872
+ if (error instanceof InsufficientCreditsError) {
873
+ return errorResponse("Insufficient credits", 402, {
874
+ required: error.requiredCredits,
875
+ remaining: error.remainingCredits
876
+ });
877
+ }
878
+ if (error instanceof CreditsExpiredError) {
879
+ return errorResponse("Credits expired", 410, {
880
+ expiredAt: error.expiresAt.toISOString()
881
+ });
882
+ }
883
+ if (error instanceof CloseLoopError) {
884
+ return errorResponse(error.message, error.statusCode || 500);
885
+ }
886
+ return null;
887
+ }
888
+ function creditGate(options) {
889
+ validateOptions(options);
890
+ const creditsRequired = options.creditsPerRequest ?? 1;
891
+ return async function middleware(request) {
892
+ const walletAddress = await options.getWalletAddress(request);
893
+ if (!walletAddress) {
894
+ return errorResponse("Wallet address required", 401);
895
+ }
896
+ if (!walletAddressSchema.safeParse(walletAddress).success) {
897
+ return errorResponse("Invalid wallet address format", 400);
898
+ }
899
+ try {
900
+ const verification = await options.client.credits.verify({
901
+ walletAddress,
902
+ planId: options.planId,
903
+ amount: creditsRequired
904
+ });
905
+ if (!verification.hasEnoughCredits) {
906
+ return errorResponse("Insufficient credits", 402, {
907
+ required: creditsRequired,
908
+ remaining: verification.remainingCredits
909
+ });
910
+ }
911
+ return createSuccessResponse(verification);
912
+ } catch (error) {
913
+ const handledResponse = handleCreditError(error);
914
+ if (handledResponse) return handledResponse;
915
+ throw error;
916
+ }
917
+ };
918
+ }
919
+ function validateOptions(options) {
920
+ if (!options.planId?.length) {
921
+ throw new Error("creditGate: planId is required");
922
+ }
923
+ if (!options.client) {
924
+ throw new Error("creditGate: CloseLoop client is required");
925
+ }
926
+ if (!options.getWalletAddress) {
927
+ throw new Error("creditGate: getWalletAddress function is required");
928
+ }
929
+ }
930
+ function createSuccessResponse(verification) {
931
+ const response = import_server.NextResponse.next();
932
+ response.headers.set(
933
+ "X-Credits-Remaining",
934
+ String(verification.remainingCredits)
935
+ );
936
+ return response;
937
+ }
938
+ async function consumeCreditsAfterRequest(client, params) {
939
+ await client.credits.consume(params);
940
+ }
941
+ // Annotate the CommonJS export names for ESM import in node:
942
+ 0 && (module.exports = {
943
+ AuthenticationError,
944
+ CloseLoop,
945
+ CloseLoopError,
946
+ CreditsExpiredError,
947
+ InsufficientCreditsError,
948
+ NetworkError,
949
+ NotFoundError,
950
+ RateLimitError,
951
+ ValidationError,
952
+ consumeCreditsAfterRequest,
953
+ creditGate
954
+ });
955
+ //# sourceMappingURL=nextjs.js.map