@puzzle-section/sdk-typescript 1.0.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.js ADDED
@@ -0,0 +1,990 @@
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
+ ApiError: () => ApiError,
24
+ AuthenticationError: () => AuthenticationError,
25
+ NotFoundError: () => NotFoundError,
26
+ PuzzleSectionClient: () => PuzzleSectionClient,
27
+ RateLimitError: () => RateLimitError,
28
+ ServerError: () => ServerError,
29
+ ValidationError: () => ValidationError
30
+ });
31
+ module.exports = __toCommonJS(index_exports);
32
+
33
+ // src/errors.ts
34
+ var ApiError = class extends Error {
35
+ code;
36
+ statusCode;
37
+ details;
38
+ constructor(message, code, statusCode, details) {
39
+ super(message);
40
+ this.name = "ApiError";
41
+ this.code = code;
42
+ this.statusCode = statusCode;
43
+ this.details = details;
44
+ if (Error.captureStackTrace) {
45
+ Error.captureStackTrace(this, this.constructor);
46
+ }
47
+ }
48
+ };
49
+ var AuthenticationError = class extends ApiError {
50
+ constructor(message = "Invalid or missing API key") {
51
+ super(message, "AUTHENTICATION_ERROR", 401);
52
+ this.name = "AuthenticationError";
53
+ }
54
+ };
55
+ var RateLimitError = class extends ApiError {
56
+ retryAfter;
57
+ limit;
58
+ remaining;
59
+ reset;
60
+ constructor(message, retryAfter, limit, remaining, reset) {
61
+ super(message, "RATE_LIMIT_EXCEEDED", 429, {
62
+ retryAfter,
63
+ limit,
64
+ remaining,
65
+ reset
66
+ });
67
+ this.name = "RateLimitError";
68
+ this.retryAfter = retryAfter;
69
+ this.limit = limit;
70
+ this.remaining = remaining;
71
+ this.reset = reset;
72
+ }
73
+ };
74
+ var NotFoundError = class extends ApiError {
75
+ resourceType;
76
+ resourceId;
77
+ constructor(resourceType, resourceId) {
78
+ super(
79
+ `${resourceType} with ID '${resourceId}' not found`,
80
+ "NOT_FOUND",
81
+ 404,
82
+ { resourceType, resourceId }
83
+ );
84
+ this.name = "NotFoundError";
85
+ this.resourceType = resourceType;
86
+ this.resourceId = resourceId;
87
+ }
88
+ };
89
+ var ValidationError = class extends ApiError {
90
+ validationErrors;
91
+ constructor(message, validationErrors) {
92
+ super(message, "VALIDATION_ERROR", 400, { validationErrors });
93
+ this.name = "ValidationError";
94
+ this.validationErrors = validationErrors;
95
+ }
96
+ };
97
+ var ServerError = class extends ApiError {
98
+ constructor(message = "An unexpected server error occurred") {
99
+ super(message, "SERVER_ERROR", 500);
100
+ this.name = "ServerError";
101
+ }
102
+ };
103
+ function createErrorFromResponse(statusCode, body, headers) {
104
+ const errorBody = body.error || { code: "UNKNOWN", message: "Unknown error" };
105
+ switch (statusCode) {
106
+ case 401:
107
+ return new AuthenticationError(errorBody.message);
108
+ case 404:
109
+ return new NotFoundError(
110
+ errorBody.details?.resourceType || "Resource",
111
+ errorBody.details?.resourceId || "unknown"
112
+ );
113
+ case 429: {
114
+ const retryAfter = parseInt(headers?.get("Retry-After") || "60", 10);
115
+ const limit = parseInt(headers?.get("X-RateLimit-Limit") || "0", 10);
116
+ const remaining = parseInt(
117
+ headers?.get("X-RateLimit-Remaining") || "0",
118
+ 10
119
+ );
120
+ const reset = parseInt(headers?.get("X-RateLimit-Reset") || "0", 10);
121
+ return new RateLimitError(
122
+ errorBody.message,
123
+ retryAfter,
124
+ limit,
125
+ remaining,
126
+ reset
127
+ );
128
+ }
129
+ case 400:
130
+ return new ValidationError(
131
+ errorBody.message,
132
+ errorBody.details || {}
133
+ );
134
+ case 500:
135
+ case 502:
136
+ case 503:
137
+ case 504:
138
+ return new ServerError(errorBody.message);
139
+ default:
140
+ return new ApiError(
141
+ errorBody.message,
142
+ errorBody.code,
143
+ statusCode,
144
+ errorBody.details
145
+ );
146
+ }
147
+ }
148
+
149
+ // src/api/puzzles.ts
150
+ var PuzzlesApi = class {
151
+ constructor(request) {
152
+ this.request = request;
153
+ }
154
+ /**
155
+ * Get daily puzzles
156
+ *
157
+ * @param params - Optional parameters
158
+ * @returns List of daily puzzles
159
+ *
160
+ * @example
161
+ * ```typescript
162
+ * // Get today's puzzles
163
+ * const puzzles = await client.puzzles.getDaily();
164
+ *
165
+ * // Get puzzles for a specific date
166
+ * const puzzles = await client.puzzles.getDaily({ date: '2024-01-15' });
167
+ * ```
168
+ */
169
+ async getDaily(params) {
170
+ return this.request({
171
+ method: "GET",
172
+ path: "/puzzles/daily",
173
+ query: {
174
+ date: params?.date,
175
+ types: params?.types?.join(","),
176
+ difficulties: params?.difficulties?.join(",")
177
+ }
178
+ });
179
+ }
180
+ /**
181
+ * Get a specific puzzle by ID
182
+ *
183
+ * @param id - Puzzle ID
184
+ * @returns Puzzle details
185
+ *
186
+ * @example
187
+ * ```typescript
188
+ * const puzzle = await client.puzzles.getById('puzzle-id');
189
+ * ```
190
+ */
191
+ async getById(id) {
192
+ return this.request({
193
+ method: "GET",
194
+ path: `/puzzles/${id}`
195
+ });
196
+ }
197
+ /**
198
+ * Get puzzles by type
199
+ *
200
+ * @param type - Puzzle type (sudoku, wordsearch, etc.)
201
+ * @param params - Optional filtering parameters
202
+ * @returns Paginated list of puzzles
203
+ *
204
+ * @example
205
+ * ```typescript
206
+ * const sudokus = await client.puzzles.getByType('sudoku', {
207
+ * difficulty: 'medium',
208
+ * limit: 10,
209
+ * });
210
+ * ```
211
+ */
212
+ async getByType(type, params) {
213
+ return this.request({
214
+ method: "GET",
215
+ path: `/puzzles/type/${type}`,
216
+ query: {
217
+ difficulty: params?.difficulty,
218
+ limit: params?.limit,
219
+ page: params?.page
220
+ }
221
+ });
222
+ }
223
+ /**
224
+ * Get available puzzle types
225
+ *
226
+ * @returns List of available puzzle types
227
+ */
228
+ async getTypes() {
229
+ return this.request({
230
+ method: "GET",
231
+ path: "/puzzles/types"
232
+ });
233
+ }
234
+ /**
235
+ * Get puzzle by date and type
236
+ *
237
+ * @param date - Date in YYYY-MM-DD format
238
+ * @param type - Puzzle type
239
+ * @param difficulty - Optional difficulty filter
240
+ * @returns Puzzle for the specified date
241
+ */
242
+ async getByDate(date, type, difficulty) {
243
+ return this.request({
244
+ method: "GET",
245
+ path: `/puzzles/date/${date}/${type}`,
246
+ query: { difficulty }
247
+ });
248
+ }
249
+ /**
250
+ * Validate puzzle solution
251
+ *
252
+ * @param id - Puzzle ID
253
+ * @param solution - User's solution
254
+ * @returns Validation result
255
+ */
256
+ async validateSolution(id, solution) {
257
+ return this.request({
258
+ method: "POST",
259
+ path: `/puzzles/${id}/validate`,
260
+ body: { solution }
261
+ });
262
+ }
263
+ };
264
+
265
+ // src/api/users.ts
266
+ var UsersApi = class {
267
+ constructor(request) {
268
+ this.request = request;
269
+ }
270
+ /**
271
+ * Get current user profile
272
+ *
273
+ * Requires a user token to be set.
274
+ *
275
+ * @returns Current user profile
276
+ *
277
+ * @example
278
+ * ```typescript
279
+ * client.setUserToken('usr_xxxxxxxxxxxx');
280
+ * const user = await client.users.getCurrent();
281
+ * ```
282
+ */
283
+ async getCurrent() {
284
+ return this.request({
285
+ method: "GET",
286
+ path: "/users/me"
287
+ });
288
+ }
289
+ /**
290
+ * Get user statistics
291
+ *
292
+ * Requires a user token to be set.
293
+ *
294
+ * @returns User statistics
295
+ */
296
+ async getStats() {
297
+ return this.request({
298
+ method: "GET",
299
+ path: "/users/me/stats"
300
+ });
301
+ }
302
+ /**
303
+ * Get user by ID
304
+ *
305
+ * @param id - User ID
306
+ * @returns User profile (public fields only)
307
+ */
308
+ async getById(id) {
309
+ return this.request({
310
+ method: "GET",
311
+ path: `/users/${id}`
312
+ });
313
+ }
314
+ /**
315
+ * Update current user profile
316
+ *
317
+ * Requires a user token to be set.
318
+ *
319
+ * @param updates - Profile updates
320
+ * @returns Updated user profile
321
+ */
322
+ async update(updates) {
323
+ return this.request({
324
+ method: "PATCH",
325
+ path: "/users/me",
326
+ body: updates
327
+ });
328
+ }
329
+ /**
330
+ * Get user achievements
331
+ *
332
+ * Requires a user token to be set.
333
+ *
334
+ * @returns List of user achievements
335
+ */
336
+ async getAchievements() {
337
+ return this.request({
338
+ method: "GET",
339
+ path: "/users/me/achievements"
340
+ });
341
+ }
342
+ };
343
+
344
+ // src/api/progress.ts
345
+ var ProgressApi = class {
346
+ constructor(request) {
347
+ this.request = request;
348
+ }
349
+ /**
350
+ * Get saved progress for a puzzle
351
+ *
352
+ * @param puzzleId - Puzzle ID
353
+ * @returns Saved progress or null if not found
354
+ *
355
+ * @example
356
+ * ```typescript
357
+ * const progress = await client.progress.get('puzzle-id');
358
+ * if (progress.data) {
359
+ * console.log(`Elapsed time: ${progress.data.elapsed_time}s`);
360
+ * }
361
+ * ```
362
+ */
363
+ async get(puzzleId) {
364
+ return this.request({
365
+ method: "GET",
366
+ path: `/progress/${puzzleId}`
367
+ });
368
+ }
369
+ /**
370
+ * Save puzzle progress
371
+ *
372
+ * @param input - Progress data to save
373
+ * @returns Saved progress
374
+ *
375
+ * @example
376
+ * ```typescript
377
+ * await client.progress.save({
378
+ * puzzleId: 'puzzle-id',
379
+ * elapsedTime: 120,
380
+ * state: {
381
+ * grid: [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
382
+ * },
383
+ * });
384
+ * ```
385
+ */
386
+ async save(input) {
387
+ return this.request({
388
+ method: "POST",
389
+ path: `/progress/${input.puzzleId}`,
390
+ body: {
391
+ elapsed_time: input.elapsedTime,
392
+ state_data: input.state,
393
+ is_paused: input.isPaused ?? false
394
+ }
395
+ });
396
+ }
397
+ /**
398
+ * Mark puzzle as complete
399
+ *
400
+ * @param input - Completion data
401
+ * @returns Completion record with score
402
+ *
403
+ * @example
404
+ * ```typescript
405
+ * const completion = await client.progress.complete({
406
+ * puzzleId: 'puzzle-id',
407
+ * elapsedTime: 300,
408
+ * hintsUsed: 2,
409
+ * });
410
+ * console.log(`Score: ${completion.data.score}`);
411
+ * ```
412
+ */
413
+ async complete(input) {
414
+ return this.request({
415
+ method: "POST",
416
+ path: `/progress/${input.puzzleId}/complete`,
417
+ body: {
418
+ elapsed_time: input.elapsedTime,
419
+ hints_used: input.hintsUsed ?? 0
420
+ }
421
+ });
422
+ }
423
+ /**
424
+ * Delete saved progress
425
+ *
426
+ * @param puzzleId - Puzzle ID
427
+ */
428
+ async delete(puzzleId) {
429
+ return this.request({
430
+ method: "DELETE",
431
+ path: `/progress/${puzzleId}`
432
+ });
433
+ }
434
+ /**
435
+ * Get all active progress for current user
436
+ *
437
+ * @returns List of in-progress puzzles
438
+ */
439
+ async getAll() {
440
+ return this.request({
441
+ method: "GET",
442
+ path: "/progress"
443
+ });
444
+ }
445
+ /**
446
+ * Get completion history for current user
447
+ *
448
+ * @param params - Optional pagination params
449
+ * @returns List of completed puzzles
450
+ */
451
+ async getCompletions(params) {
452
+ return this.request({
453
+ method: "GET",
454
+ path: "/progress/completions",
455
+ query: params
456
+ });
457
+ }
458
+ };
459
+
460
+ // src/api/health.ts
461
+ var HealthApi = class {
462
+ constructor(request) {
463
+ this.request = request;
464
+ }
465
+ /**
466
+ * Check API health
467
+ *
468
+ * @returns Health status
469
+ *
470
+ * @example
471
+ * ```typescript
472
+ * const health = await client.health.check();
473
+ * console.log(health.data.status); // 'healthy'
474
+ * ```
475
+ */
476
+ async check() {
477
+ return this.request({
478
+ method: "GET",
479
+ path: "/health"
480
+ });
481
+ }
482
+ /**
483
+ * Simple ping check
484
+ *
485
+ * @returns Ping response
486
+ */
487
+ async ping() {
488
+ return this.request({
489
+ method: "GET",
490
+ path: "/health/ping"
491
+ });
492
+ }
493
+ };
494
+
495
+ // src/api/admin.ts
496
+ var AdminApi = class {
497
+ constructor(request) {
498
+ this.request = request;
499
+ }
500
+ /**
501
+ * List admin puzzles
502
+ *
503
+ * @param params - Optional filter parameters
504
+ * @returns Paginated list of admin puzzles
505
+ *
506
+ * @example
507
+ * ```typescript
508
+ * const result = await client.admin.listPuzzles({
509
+ * type: 'nonogram',
510
+ * status: 'published',
511
+ * page: 1,
512
+ * pageSize: 20,
513
+ * });
514
+ * ```
515
+ */
516
+ async listPuzzles(params) {
517
+ return this.request({
518
+ method: "GET",
519
+ path: "/admin/puzzles",
520
+ query: {
521
+ type: params?.type,
522
+ status: params?.status,
523
+ search: params?.search,
524
+ page: params?.page,
525
+ pageSize: params?.pageSize
526
+ }
527
+ });
528
+ }
529
+ /**
530
+ * List deleted admin puzzles (recycle bin)
531
+ *
532
+ * @param params - Optional filter parameters
533
+ * @returns Paginated list of deleted admin puzzles
534
+ *
535
+ * @example
536
+ * ```typescript
537
+ * const result = await client.admin.listDeletedPuzzles();
538
+ * ```
539
+ */
540
+ async listDeletedPuzzles(params) {
541
+ return this.request({
542
+ method: "GET",
543
+ path: "/admin/puzzles/deleted",
544
+ query: {
545
+ type: params?.type,
546
+ search: params?.search,
547
+ page: params?.page,
548
+ pageSize: params?.pageSize
549
+ }
550
+ });
551
+ }
552
+ /**
553
+ * Get a single admin puzzle by ID
554
+ *
555
+ * @param id - Puzzle ID
556
+ * @returns Admin puzzle with variants
557
+ *
558
+ * @example
559
+ * ```typescript
560
+ * const puzzle = await client.admin.getPuzzle('puzzle-id');
561
+ * ```
562
+ */
563
+ async getPuzzle(id) {
564
+ return this.request({
565
+ method: "GET",
566
+ path: `/admin/puzzles/${id}`
567
+ });
568
+ }
569
+ /**
570
+ * Create a new admin puzzle
571
+ *
572
+ * @param data - Puzzle creation data
573
+ * @returns Created puzzle
574
+ *
575
+ * @example
576
+ * ```typescript
577
+ * const puzzle = await client.admin.createPuzzle({
578
+ * title: 'My Nonogram',
579
+ * type: 'nonogram',
580
+ * });
581
+ * ```
582
+ */
583
+ async createPuzzle(data) {
584
+ return this.request({
585
+ method: "POST",
586
+ path: "/admin/puzzles",
587
+ body: data
588
+ });
589
+ }
590
+ /**
591
+ * Update an admin puzzle
592
+ *
593
+ * @param id - Puzzle ID
594
+ * @param data - Update data
595
+ * @returns Updated puzzle
596
+ *
597
+ * @example
598
+ * ```typescript
599
+ * const puzzle = await client.admin.updatePuzzle('puzzle-id', {
600
+ * title: 'Updated Title',
601
+ * status: 'published',
602
+ * });
603
+ * ```
604
+ */
605
+ async updatePuzzle(id, data) {
606
+ return this.request({
607
+ method: "PUT",
608
+ path: `/admin/puzzles/${id}`,
609
+ body: data
610
+ });
611
+ }
612
+ /**
613
+ * Soft delete an admin puzzle (move to recycle bin)
614
+ *
615
+ * @param id - Puzzle ID
616
+ * @returns Success message
617
+ *
618
+ * @example
619
+ * ```typescript
620
+ * await client.admin.deletePuzzle('puzzle-id');
621
+ * ```
622
+ */
623
+ async deletePuzzle(id) {
624
+ return this.request({
625
+ method: "DELETE",
626
+ path: `/admin/puzzles/${id}`
627
+ });
628
+ }
629
+ /**
630
+ * Remove a platform puzzle from the tenant's library.
631
+ * Deactivates all calendar assignments for this puzzle. Only applies to platform puzzles
632
+ * (tenantId=null). For tenant-owned puzzles, use deletePuzzle instead.
633
+ * Requires tenant API key authentication.
634
+ *
635
+ * @param id - Admin puzzle ID
636
+ * @returns Result with deactivatedCount
637
+ *
638
+ * @example
639
+ * ```typescript
640
+ * const result = await client.admin.removeFromLibrary('puzzle-id');
641
+ * console.log(`Deactivated ${result.data.deactivatedCount} calendar assignments`);
642
+ * ```
643
+ */
644
+ async removeFromLibrary(id) {
645
+ return this.request({
646
+ method: "POST",
647
+ path: `/admin/puzzles/${id}/remove-from-library`
648
+ });
649
+ }
650
+ /**
651
+ * Restore a soft-deleted puzzle from recycle bin
652
+ *
653
+ * @param id - Puzzle ID
654
+ * @returns Restored puzzle
655
+ *
656
+ * @example
657
+ * ```typescript
658
+ * const puzzle = await client.admin.restorePuzzle('puzzle-id');
659
+ * ```
660
+ */
661
+ async restorePuzzle(id) {
662
+ return this.request({
663
+ method: "POST",
664
+ path: `/admin/puzzles/${id}/restore`
665
+ });
666
+ }
667
+ /**
668
+ * Permanently delete a puzzle (cannot be undone)
669
+ *
670
+ * @param id - Puzzle ID
671
+ * @returns Success message
672
+ *
673
+ * @example
674
+ * ```typescript
675
+ * await client.admin.permanentlyDeletePuzzle('puzzle-id');
676
+ * ```
677
+ */
678
+ async permanentlyDeletePuzzle(id) {
679
+ return this.request({
680
+ method: "DELETE",
681
+ path: `/admin/puzzles/${id}/permanent`
682
+ });
683
+ }
684
+ /**
685
+ * Publish an admin puzzle
686
+ *
687
+ * @param id - Puzzle ID
688
+ * @returns Published puzzle
689
+ *
690
+ * @example
691
+ * ```typescript
692
+ * const puzzle = await client.admin.publishPuzzle('puzzle-id');
693
+ * ```
694
+ */
695
+ async publishPuzzle(id) {
696
+ return this.request({
697
+ method: "POST",
698
+ path: `/admin/puzzles/${id}/publish`
699
+ });
700
+ }
701
+ /**
702
+ * Unpublish an admin puzzle
703
+ *
704
+ * @param id - Puzzle ID
705
+ * @returns Unpublished puzzle
706
+ *
707
+ * @example
708
+ * ```typescript
709
+ * const puzzle = await client.admin.unpublishPuzzle('puzzle-id');
710
+ * ```
711
+ */
712
+ async unpublishPuzzle(id) {
713
+ return this.request({
714
+ method: "POST",
715
+ path: `/admin/puzzles/${id}/unpublish`
716
+ });
717
+ }
718
+ /**
719
+ * Create or update a puzzle variant
720
+ *
721
+ * @param puzzleId - Puzzle ID
722
+ * @param data - Variant data
723
+ * @returns Created/updated variant
724
+ *
725
+ * @example
726
+ * ```typescript
727
+ * const variant = await client.admin.upsertVariant('puzzle-id', {
728
+ * difficulty: 'medium',
729
+ * enabled: true,
730
+ * width: 15,
731
+ * height: 15,
732
+ * grid_data: [[0, 1, 0], ...],
733
+ * });
734
+ * ```
735
+ */
736
+ async upsertVariant(puzzleId, data) {
737
+ return this.request({
738
+ method: "PUT",
739
+ path: `/admin/puzzles/${puzzleId}/variants`,
740
+ body: data
741
+ });
742
+ }
743
+ /**
744
+ * Delete a puzzle variant
745
+ *
746
+ * @param puzzleId - Puzzle ID
747
+ * @param variantId - Variant ID
748
+ * @returns Success message
749
+ *
750
+ * @example
751
+ * ```typescript
752
+ * await client.admin.deleteVariant('puzzle-id', 'variant-id');
753
+ * ```
754
+ */
755
+ async deleteVariant(puzzleId, variantId) {
756
+ return this.request({
757
+ method: "DELETE",
758
+ path: `/admin/puzzles/${puzzleId}/variants/${variantId}`
759
+ });
760
+ }
761
+ /**
762
+ * Evaluate fun factor of a nonogram puzzle
763
+ *
764
+ * @param data - Grid data and puzzle metadata
765
+ * @returns Fun factor evaluation result
766
+ *
767
+ * @example
768
+ * ```typescript
769
+ * const result = await client.admin.evaluateFunFactor({
770
+ * grid_data: [[0, 1, 0], ...],
771
+ * difficulty: 'medium',
772
+ * width: 15,
773
+ * height: 15,
774
+ * });
775
+ * ```
776
+ */
777
+ async evaluateFunFactor(data) {
778
+ return this.request({
779
+ method: "POST",
780
+ path: "/admin/puzzles/evaluate-fun-factor",
781
+ body: data
782
+ });
783
+ }
784
+ /**
785
+ * Filter a word from a wordsearch puzzle
786
+ *
787
+ * Adds the word to the tenant's filtered word list and removes it from
788
+ * the specified puzzle. The word will not appear in future puzzle generations
789
+ * and is immediately removed from the current puzzle for all users.
790
+ *
791
+ * @param puzzleId - ID of the wordsearch puzzle
792
+ * @param data - Word to filter and optional audit metadata
793
+ * @returns Updated puzzle data with the word removed
794
+ *
795
+ * @example
796
+ * ```typescript
797
+ * const result = await client.admin.filterWordFromPuzzle('puzzle-id', {
798
+ * word: 'offensive',
799
+ * reason: 'Inappropriate content',
800
+ * addedByName: 'Admin User',
801
+ * addedByEmail: 'admin@example.com',
802
+ * });
803
+ * ```
804
+ */
805
+ async filterWordFromPuzzle(puzzleId, data) {
806
+ return this.request({
807
+ method: "POST",
808
+ path: `/v1/puzzles/${puzzleId}/filter-word`,
809
+ body: data
810
+ });
811
+ }
812
+ };
813
+
814
+ // src/client.ts
815
+ var PuzzleSectionClient = class {
816
+ config;
817
+ /**
818
+ * Puzzles API
819
+ */
820
+ puzzles;
821
+ /**
822
+ * Users API
823
+ */
824
+ users;
825
+ /**
826
+ * Progress API
827
+ */
828
+ progress;
829
+ /**
830
+ * Health API
831
+ */
832
+ health;
833
+ /**
834
+ * Admin API
835
+ */
836
+ admin;
837
+ constructor(config) {
838
+ if (!config.apiKey) {
839
+ throw new Error("API key is required");
840
+ }
841
+ this.config = {
842
+ apiKey: config.apiKey,
843
+ baseUrl: config.baseUrl || "https://api.puzzlesection.app",
844
+ timeout: config.timeout || 3e4,
845
+ retryCount: config.retryCount || 3,
846
+ userToken: config.userToken,
847
+ fetch: config.fetch || globalThis.fetch.bind(globalThis)
848
+ };
849
+ this.puzzles = new PuzzlesApi(this.request.bind(this));
850
+ this.users = new UsersApi(this.request.bind(this));
851
+ this.progress = new ProgressApi(this.request.bind(this));
852
+ this.health = new HealthApi(this.request.bind(this));
853
+ this.admin = new AdminApi(this.request.bind(this));
854
+ }
855
+ /**
856
+ * Set the user token for user-specific operations
857
+ */
858
+ setUserToken(token) {
859
+ this.config.userToken = token;
860
+ }
861
+ /**
862
+ * Make an API request
863
+ */
864
+ async request(options) {
865
+ const { method, path, body, query, headers = {} } = options;
866
+ const url = new URL(`${this.config.baseUrl}/api/v1${path}`);
867
+ if (query) {
868
+ for (const [key, value] of Object.entries(query)) {
869
+ if (value !== void 0) {
870
+ url.searchParams.set(key, String(value));
871
+ }
872
+ }
873
+ }
874
+ const requestHeaders = {
875
+ "Content-Type": "application/json",
876
+ "X-API-Key": this.config.apiKey,
877
+ ...headers
878
+ };
879
+ if (this.config.userToken) {
880
+ requestHeaders["X-User-Token"] = this.config.userToken;
881
+ }
882
+ const requestInit = {
883
+ method,
884
+ headers: requestHeaders
885
+ };
886
+ if (body && method !== "GET") {
887
+ requestInit.body = JSON.stringify(body);
888
+ }
889
+ let lastError;
890
+ for (let attempt = 0; attempt <= this.config.retryCount; attempt++) {
891
+ try {
892
+ const response = await this.executeRequest(url.toString(), requestInit);
893
+ return response;
894
+ } catch (error) {
895
+ lastError = error;
896
+ if (error instanceof ApiError) {
897
+ if (error.statusCode >= 400 && error.statusCode < 500 && error.statusCode !== 429) {
898
+ throw error;
899
+ }
900
+ if (error.statusCode === 429 && attempt < this.config.retryCount) {
901
+ const retryAfter = error.retryAfter || Math.pow(2, attempt);
902
+ await this.sleep(retryAfter * 1e3);
903
+ continue;
904
+ }
905
+ }
906
+ if (attempt < this.config.retryCount) {
907
+ await this.sleep(Math.pow(2, attempt) * 1e3);
908
+ }
909
+ }
910
+ }
911
+ throw lastError;
912
+ }
913
+ /**
914
+ * Execute a single request
915
+ */
916
+ async executeRequest(url, init) {
917
+ const controller = new AbortController();
918
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
919
+ try {
920
+ const response = await this.config.fetch(url, {
921
+ ...init,
922
+ signal: controller.signal
923
+ });
924
+ clearTimeout(timeoutId);
925
+ const rateLimit = {
926
+ limit: parseInt(response.headers.get("X-RateLimit-Limit") || "0", 10),
927
+ remaining: parseInt(response.headers.get("X-RateLimit-Remaining") || "0", 10),
928
+ reset: parseInt(response.headers.get("X-RateLimit-Reset") || "0", 10)
929
+ };
930
+ let body;
931
+ const contentType = response.headers.get("content-type") || "";
932
+ if (contentType && !contentType.includes("application/json")) {
933
+ throw new ApiError(
934
+ `Server returned non-JSON response (HTTP ${response.status})`,
935
+ "INVALID_RESPONSE",
936
+ response.status
937
+ );
938
+ }
939
+ try {
940
+ body = await response.json();
941
+ } catch {
942
+ throw new ApiError(
943
+ `Failed to parse server response (HTTP ${response.status})`,
944
+ "INVALID_RESPONSE",
945
+ response.status
946
+ );
947
+ }
948
+ if (!response.ok || !body.success) {
949
+ throw createErrorFromResponse(
950
+ response.status,
951
+ body,
952
+ response.headers
953
+ );
954
+ }
955
+ return {
956
+ data: body.data,
957
+ rateLimit
958
+ };
959
+ } catch (error) {
960
+ clearTimeout(timeoutId);
961
+ if (error instanceof ApiError) {
962
+ throw error;
963
+ }
964
+ if (error.name === "AbortError") {
965
+ throw new ApiError("Request timed out", "TIMEOUT", 408);
966
+ }
967
+ throw new ApiError(
968
+ `Network error: ${error.message}`,
969
+ "NETWORK_ERROR",
970
+ 0
971
+ );
972
+ }
973
+ }
974
+ /**
975
+ * Sleep for a given duration
976
+ */
977
+ sleep(ms) {
978
+ return new Promise((resolve) => setTimeout(resolve, ms));
979
+ }
980
+ };
981
+ // Annotate the CommonJS export names for ESM import in node:
982
+ 0 && (module.exports = {
983
+ ApiError,
984
+ AuthenticationError,
985
+ NotFoundError,
986
+ PuzzleSectionClient,
987
+ RateLimitError,
988
+ ServerError,
989
+ ValidationError
990
+ });