@closeloop/sdk 0.1.7 → 0.1.8

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