@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/README.md +294 -0
- package/dist/index.d.mts +1019 -0
- package/dist/index.d.ts +1019 -0
- package/dist/index.js +990 -0
- package/dist/index.mjs +957 -0
- package/package.json +65 -0
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
|
+
});
|