@puzzle-section/sdk-typescript 1.0.0 → 1.0.2

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.
Files changed (2) hide show
  1. package/README.md +223 -186
  2. package/package.json +3 -3
package/README.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  Official TypeScript SDK for the Puzzle Section Platform API.
4
4
 
5
+ The SDK provides access to:
6
+ - **Puzzle content** — daily puzzles, by type, by ID, solution validation
7
+ - **Admin tools** — puzzle and variant management, content moderation
8
+ - **Health monitoring** — API status and connectivity checks
9
+
10
+ User management and progress tracking are tenant responsibilities. Implement your own backend for user-specific features.
11
+
5
12
  ## Installation
6
13
 
7
14
  ```bash
@@ -12,219 +19,196 @@ yarn add @puzzle-section/sdk-typescript
12
19
  pnpm add @puzzle-section/sdk-typescript
13
20
  ```
14
21
 
22
+ **Requirements:** Node.js 18+, TypeScript 5.0+ (recommended), or any modern browser with ES2020+ support.
23
+
15
24
  ## Quick Start
16
25
 
17
26
  ```typescript
18
27
  import { PuzzleSectionClient } from '@puzzle-section/sdk-typescript';
19
28
 
20
- // Initialize the client
21
29
  const client = new PuzzleSectionClient({
22
30
  apiKey: 'ps_live_xxxxxxxxxxxx',
23
31
  });
24
32
 
25
- // Get today's puzzles
26
- const response = await client.puzzles.getDaily();
27
- console.log(response.data);
28
-
29
- // Get a specific puzzle
30
- const puzzle = await client.puzzles.getById('puzzle-id');
31
- console.log(puzzle.data);
33
+ const { data: puzzles } = await client.puzzles.getDaily();
32
34
  ```
33
35
 
34
36
  ## Configuration
35
37
 
36
38
  ```typescript
37
39
  const client = new PuzzleSectionClient({
38
- // Required: Your API key
39
- apiKey: 'ps_live_xxxxxxxxxxxx',
40
+ apiKey: 'ps_live_xxxxxxxxxxxx', // Required tenant API key
41
+ baseUrl: 'https://api.puzzlesection.app', // Optional — default shown
42
+ timeout: 30000, // Optional — request timeout in ms
43
+ retryCount: 3, // Optional — max retries on 5xx / 429
44
+ fetch: customFetch, // Optional — custom fetch implementation
45
+ });
46
+ ```
40
47
 
41
- // Optional: Override base URL (for sandbox or self-hosted)
42
- baseUrl: 'https://sandbox.puzzlesection.app',
48
+ | Option | Type | Default | Description |
49
+ |--------|------|---------|-------------|
50
+ | `apiKey` | string | — | Tenant API key (required). Sent as `X-API-Key`. |
51
+ | `baseUrl` | string | `https://api.puzzlesection.app` | API base URL. All requests go to `{baseUrl}/api/v1{path}`. |
52
+ | `timeout` | number | `30000` | Request timeout in milliseconds. |
53
+ | `retryCount` | number | `3` | Max retries for 5xx and 429 responses. |
54
+ | `fetch` | typeof fetch | `globalThis.fetch` | Custom fetch for environments without native fetch. |
43
55
 
44
- // Optional: Request timeout in milliseconds
45
- timeout: 30000,
56
+ ## Response Envelope
46
57
 
47
- // Optional: Number of retry attempts for failed requests
48
- retryCount: 3,
58
+ Every SDK method returns `Promise<ResponseWithRateLimit<T>>`:
49
59
 
50
- // Optional: End-user token for user-specific operations
51
- userToken: 'usr_xxxxxxxxxxxx',
52
- });
60
+ ```typescript
61
+ interface ResponseWithRateLimit<T> {
62
+ data: T;
63
+ rateLimit: RateLimitInfo;
64
+ }
65
+
66
+ interface RateLimitInfo {
67
+ limit: number; // Max requests per window
68
+ remaining: number; // Requests remaining
69
+ reset: number; // Unix timestamp when window resets
70
+ }
71
+
72
+ const { data, rateLimit } = await client.puzzles.getDaily();
73
+ console.log(rateLimit.remaining); // 999
53
74
  ```
54
75
 
55
76
  ## API Reference
56
77
 
57
- ### Puzzles
78
+ ### client.puzzles
58
79
 
59
80
  ```typescript
60
- // Get daily puzzles
61
- const dailyPuzzles = await client.puzzles.getDaily();
62
-
63
- // Get daily puzzles for a specific date
64
- const puzzles = await client.puzzles.getDaily({ date: '2026-01-15' });
65
-
66
- // Get daily puzzles filtered by type and difficulty
67
- const filtered = await client.puzzles.getDaily({
68
- date: '2026-01-15',
69
- types: ['sudoku', 'kakuro'],
70
- difficulties: ['medium', 'hard'],
81
+ // All daily puzzles
82
+ const { data } = await client.puzzles.getDaily();
83
+ // data: Puzzle[]
84
+
85
+ // Filtered by type, difficulty, and date
86
+ const { data } = await client.puzzles.getDaily({
87
+ date: '2026-03-05', // YYYY-MM-DD (default: today)
88
+ types: ['sudoku', 'kakuro'], // PuzzleType[]
89
+ difficulties: ['medium', 'hard'], // PuzzleDifficulty[]
71
90
  });
72
91
 
73
- // Get puzzle by ID
74
- const puzzle = await client.puzzles.getById('puzzle-id');
92
+ // Fetch a single puzzle by UUID
93
+ const { data: puzzle } = await client.puzzles.getById('550e8400-e29b-...');
75
94
 
76
- // Get puzzles by type with pagination
77
- const sudokus = await client.puzzles.getByType('sudoku', {
78
- difficulty: 'medium',
95
+ // Paginated list by type
96
+ const { data } = await client.puzzles.getByType('sudoku', {
97
+ difficulty: 'hard',
79
98
  limit: 10,
80
99
  page: 1,
81
100
  });
101
+ // data.data: Puzzle[], data.pagination: { page, limit, total, totalPages }
82
102
 
83
- // Get puzzle by date and type
84
- const datePuzzle = await client.puzzles.getByDate('2026-01-15', 'sudoku', 'medium');
103
+ // Specific puzzle for a date and type
104
+ const { data } = await client.puzzles.getByDate('2026-03-05', 'sudoku', 'medium');
85
105
 
86
- // Get available puzzle types
87
- const types = await client.puzzles.getTypes();
106
+ // All available puzzle types
107
+ const { data: types } = await client.puzzles.getTypes();
88
108
 
89
- // Validate a solution before submitting
90
- const validation = await client.puzzles.validateSolution('puzzle-id', {
91
- grid: [[5, 3, 4, ...], ...],
92
- });
93
- if (validation.data.valid) {
94
- // Solution is correct
95
- }
109
+ // Validate a solution
110
+ const { data } = await client.puzzles.validateSolution(
111
+ '550e8400-...',
112
+ { grid: [[5, 3, 4, 6, 7, 8, 9, 1, 2], ...] }
113
+ );
114
+ // data: { valid: boolean, errors?: string[] }
96
115
  ```
97
116
 
98
- ### Users
117
+ ### client.health
99
118
 
100
119
  ```typescript
101
- // Get current user profile (requires user token)
102
- const user = await client.users.getCurrent();
103
-
104
- // Get user statistics
105
- const stats = await client.users.getStats();
106
-
107
- // Get another user by ID (public fields only)
108
- const otherUser = await client.users.getById('user-id');
109
-
110
- // Update current user profile
111
- const updated = await client.users.update({
112
- username: 'NewUsername',
113
- avatar: 'https://example.com/avatar.png',
114
- preferred_locale: 'en',
115
- });
116
-
117
- // Get user achievements
118
- const achievements = await client.users.getAchievements();
120
+ // Full health check
121
+ const { data } = await client.health.check();
122
+ // data: {
123
+ // status: 'healthy' | 'degraded' | 'unhealthy',
124
+ // version: '1.0.0',
125
+ // timestamp: '2026-03-05T12:00:00Z',
126
+ // checks: { database: 'up' | 'down', redis: 'up' | 'down' }
127
+ // }
128
+
129
+ // Simple connectivity ping
130
+ const { data } = await client.health.ping();
131
+ // data: { pong: true, timestamp: '2026-03-05T12:00:00Z' }
119
132
  ```
120
133
 
121
- ### Progress
134
+ ### client.admin
135
+
136
+ Admin methods are scoped to your tenant and are intended for puzzle content management — typically used by internal tools and the dashboard, not end-user applications.
122
137
 
123
138
  ```typescript
124
- // Save puzzle progress (auto-save)
125
- await client.progress.save({
126
- puzzleId: 'puzzle-id',
127
- elapsedTime: 120000, // milliseconds
128
- state: {
129
- grid: [[5, 3, 0, ...], ...],
130
- notes: [[[], [], [1, 2], ...], ...],
131
- },
132
- isPaused: false,
139
+ // List puzzles with filters
140
+ const { data } = await client.admin.listPuzzles({
141
+ type: 'nonogram',
142
+ status: 'draft',
143
+ search: 'cat',
144
+ page: 1,
145
+ pageSize: 20,
133
146
  });
147
+ // data: PaginatedResponse<AdminPuzzle>
134
148
 
135
- // Get saved progress for a puzzle
136
- const progress = await client.progress.get('puzzle-id');
137
- if (progress.data) {
138
- console.log(`Elapsed time: ${progress.data.elapsedTime}ms`);
139
- console.log(`Last saved: ${progress.data.lastSavedAt}`);
140
- }
141
-
142
- // Get all in-progress puzzles
143
- const allProgress = await client.progress.getAll();
144
-
145
- // Complete a puzzle
146
- const completion = await client.progress.complete({
147
- puzzleId: 'puzzle-id',
148
- elapsedTime: 300000, // milliseconds
149
- hintsUsed: 0,
149
+ // Create a puzzle
150
+ const { data: puzzle } = await client.admin.createPuzzle({
151
+ title: 'Cat Nonogram',
152
+ type: 'nonogram',
150
153
  });
151
- console.log(`Score: ${completion.data.score}`);
152
- console.log(`Rank: ${completion.data.rank}`);
153
154
 
154
- // Get completion history
155
- const completions = await client.progress.getCompletions({
156
- limit: 20,
157
- page: 1,
155
+ // Update puzzle metadata
156
+ const { data: updated } = await client.admin.updatePuzzle('puzzle-id', {
157
+ title: 'Updated Title',
158
+ status: 'published',
159
+ event_tags: ['spring-2026'],
158
160
  });
159
161
 
160
- // Delete saved progress (start fresh)
161
- await client.progress.delete('puzzle-id');
162
- ```
162
+ // Get a single puzzle
163
+ const { data: puzzle } = await client.admin.getPuzzle('puzzle-id');
163
164
 
164
- ### Health
165
+ // Lifecycle
166
+ await client.admin.publishPuzzle('puzzle-id');
167
+ await client.admin.unpublishPuzzle('puzzle-id');
168
+ await client.admin.deletePuzzle('puzzle-id'); // soft delete
169
+ await client.admin.restorePuzzle('puzzle-id'); // restore from bin
170
+ await client.admin.permanentlyDeletePuzzle('puzzle-id');
171
+ await client.admin.removeFromLibrary('puzzle-id'); // deactivate all variants
165
172
 
166
- ```typescript
167
- // Check API health
168
- const health = await client.health.check();
169
- console.log(health.data.status); // 'healthy' | 'degraded' | 'unhealthy'
170
- ```
171
-
172
- ## Response Format
173
-
174
- All API methods return a response object with `data` and `rateLimit`:
173
+ // List deleted puzzles
174
+ const { data } = await client.admin.listDeletedPuzzles();
175
175
 
176
- ```typescript
177
- const response = await client.puzzles.getById('puzzle-id');
178
-
179
- // The actual data
180
- const puzzle = response.data;
181
- console.log(puzzle.type); // 'sudoku'
182
- console.log(puzzle.difficulty); // 'medium'
183
- console.log(puzzle.data); // Puzzle-specific data (grid, clues, etc.)
184
-
185
- // Rate limit information
186
- console.log(response.rateLimit.limit); // Max requests per window
187
- console.log(response.rateLimit.remaining); // Remaining requests
188
- console.log(response.rateLimit.reset); // Unix timestamp when window resets
189
- ```
190
-
191
- ## Puzzle Object
176
+ // Create or update a puzzle variant
177
+ const { data: variant } = await client.admin.upsertVariant('puzzle-id', {
178
+ difficulty: 'medium',
179
+ enabled: true,
180
+ width: 15,
181
+ height: 15,
182
+ grid_data: [[0, 1, 1, ...], ...],
183
+ color_grid: [[0, 1, 2, ...], ...], // for color nonograms
184
+ palette: ['#000', '#ff0000', '#0000ff'],
185
+ });
192
186
 
193
- The `Puzzle` object contains:
187
+ // Delete a variant
188
+ await client.admin.deleteVariant('puzzle-id', 'variant-id');
194
189
 
195
- ```typescript
196
- interface Puzzle {
197
- id: string; // UUID
198
- type: PuzzleType; // 'sudoku', 'kakuro', 'crossword', etc.
199
- difficulty: PuzzleDifficulty; // 'easy', 'medium', 'hard', 'expert'
200
- date: string; // 'YYYY-MM-DD'
201
- estimatedTime: number; // Estimated completion time in seconds
202
- isPremium: boolean; // Whether premium access is required
203
- data: PuzzleData; // Type-specific puzzle data
204
- createdAt: string; // ISO timestamp
205
- updatedAt: string; // ISO timestamp
206
- }
190
+ // Evaluate nonogram fun factor
191
+ const { data: result } = await client.admin.evaluateFunFactor({
192
+ grid_data: [[0, 1, 1, ...], ...],
193
+ difficulty: 'medium',
194
+ width: 15,
195
+ height: 15,
196
+ });
197
+ // result.funScore, result.meetsThreshold, result.recommendation, ...
198
+
199
+ // Filter an offensive word from a wordsearch puzzle
200
+ await client.admin.filterWordFromPuzzle('puzzle-id', {
201
+ word: 'offensive',
202
+ reason: 'inappropriate',
203
+ addedByName: 'Content Moderator',
204
+ addedByEmail: 'mod@example.com',
205
+ });
207
206
  ```
208
207
 
209
- ## Available Puzzle Types
210
-
211
- - `sudoku` - Classic 9x9 Sudoku
212
- - `kakuro` - Cross-sum puzzles
213
- - `crossmath` - Arithmetic crossword
214
- - `slitherlink` - Loop-drawing puzzle
215
- - `hashi` - Bridge-building puzzle
216
- - `breadcrumb` - Path-following puzzle
217
- - `crossword` - Word crossword
218
- - `wordsearch` - Find hidden words
219
- - `wordladder` - Transform words
220
- - `wordpath` - Connect letters
221
- - `nonogram` - Picture logic (black/white)
222
- - `colornonogram` - Picture logic (color)
223
- - `mosaic` - Fill-in picture puzzle
224
- - `picturepath` - Draw path puzzle
225
-
226
208
  ## Error Handling
227
209
 
210
+ The SDK throws typed error classes that extend `ApiError`. Use `instanceof` to branch:
211
+
228
212
  ```typescript
229
213
  import {
230
214
  PuzzleSectionClient,
@@ -233,56 +217,109 @@ import {
233
217
  RateLimitError,
234
218
  NotFoundError,
235
219
  ValidationError,
220
+ ServerError,
236
221
  } from '@puzzle-section/sdk-typescript';
237
222
 
238
223
  try {
239
- const puzzle = await client.puzzles.getById('invalid-id');
224
+ const { data } = await client.puzzles.getById('nonexistent');
240
225
  } catch (error) {
241
- if (error instanceof NotFoundError) {
242
- console.log('Puzzle not found');
243
- } else if (error instanceof RateLimitError) {
244
- console.log(`Rate limited. Retry after ${error.retryAfter} seconds`);
226
+ if (error instanceof RateLimitError) {
227
+ // 429 — SDK retries automatically; this fires when retries are exhausted
228
+ console.log(`Retry after ${error.retryAfter}s`);
229
+ console.log(`Limit: ${error.limit}, Remaining: ${error.remaining}`);
230
+
245
231
  } else if (error instanceof AuthenticationError) {
246
- console.log('Invalid API key');
232
+ // 401 — invalid or missing API key
233
+ console.error('Check your API key configuration');
234
+
235
+ } else if (error instanceof NotFoundError) {
236
+ // 404 — resource does not exist
237
+ console.log(error.message);
238
+
247
239
  } else if (error instanceof ValidationError) {
248
- console.log(`Validation error: ${error.message}`);
240
+ // 400 — request validation failed
241
+ console.log(error.details); // Record<string, string[]>
242
+
243
+ } else if (error instanceof ServerError) {
244
+ // 500–504 — thrown after retries are exhausted
245
+ console.error('Server error');
246
+
249
247
  } else if (error instanceof ApiError) {
250
- console.log(`API error: ${error.code} - ${error.message}`);
248
+ // Catch-all for any other API error
249
+ console.error(`[${error.code}] ${error.message} (HTTP ${error.statusCode})`);
251
250
  }
252
251
  }
253
252
  ```
254
253
 
255
- ## TypeScript Support
254
+ | Class | HTTP Status | Extra Properties |
255
+ |-------|-------------|-----------------|
256
+ | `ApiError` | any | `message`, `code`, `statusCode`, `details?` |
257
+ | `AuthenticationError` | 401 | — |
258
+ | `ValidationError` | 400 | `details: Record<string, string[]>` |
259
+ | `NotFoundError` | 404 | — |
260
+ | `RateLimitError` | 429 | `retryAfter`, `limit`, `remaining`, `reset` |
261
+ | `ServerError` | 500–504 | — |
256
262
 
257
- This SDK is written in TypeScript and provides full type definitions:
263
+ **Retry behaviour:**
264
+ - **5xx errors** are retried up to `retryCount` times with exponential backoff (2<sup>attempt</sup> seconds).
265
+ - **429 errors** are retried using the `Retry-After` header value when present.
266
+ - **4xx errors** (except 429) are not retried.
267
+ - **Timeouts** throw `ApiError` with code `TIMEOUT` (status 408).
268
+ - **Network failures** throw `ApiError` with code `NETWORK_ERROR` (status 0).
258
269
 
259
- ```typescript
260
- import type {
261
- Puzzle,
262
- PuzzleType,
263
- PuzzleDifficulty,
264
- User,
265
- UserStats,
266
- PuzzleProgress,
267
- PuzzleCompletion,
268
- } from '@puzzle-section/sdk-typescript';
270
+ ## TypeScript Types
269
271
 
270
- // Type-safe puzzle handling
271
- const response = await client.puzzles.getById('id');
272
- const puzzle: Puzzle = response.data;
272
+ All types are exported from the package:
273
273
 
274
- if (puzzle.type === 'sudoku') {
275
- // Access puzzle-specific data
276
- const grid = puzzle.data.grid as number[][];
277
- }
274
+ ```typescript
275
+ import type {
276
+ // Puzzle types
277
+ Puzzle,
278
+ PuzzleType, // 'sudoku' | 'wordsearch' | 'crossword' | ... (14 types)
279
+ PuzzleDifficulty, // 'easy' | 'medium' | 'hard' | 'expert'
280
+ PuzzleData,
281
+ PuzzleSolution,
282
+
283
+ // Response types
284
+ RateLimitInfo,
285
+ HealthStatus,
286
+
287
+ // Admin types
288
+ AdminPuzzle,
289
+ AdminPuzzleVariant,
290
+ AdminPuzzleType,
291
+ AdminPuzzleStatus,
292
+ CreateAdminPuzzleRequest,
293
+ UpdateAdminPuzzleRequest,
294
+ UpsertPuzzleVariantRequest,
295
+ FunFactorResult,
296
+
297
+ // Config
298
+ ClientConfig,
299
+ } from '@puzzle-section/sdk-typescript';
278
300
  ```
279
301
 
280
- ## Browser Support
302
+ ## Available Puzzle Types
281
303
 
282
- The SDK works in both Node.js and browser environments:
304
+ - `sudoku` Classic 9×9 Sudoku
305
+ - `kakuro` — Cross-sum puzzles
306
+ - `crossmath` — Arithmetic crossword
307
+ - `slitherlink` — Loop-drawing puzzle
308
+ - `hashi` — Bridge-building puzzle
309
+ - `breadcrumb` — Path-following puzzle
310
+ - `crossword` — Word crossword
311
+ - `wordsearch` — Find hidden words
312
+ - `wordladder` — Transform words step by step
313
+ - `wordpath` — Connect letters into a path
314
+ - `nonogram` — Picture logic (black and white)
315
+ - `colornonogram` — Picture logic (colour)
316
+ - `mosaic` — Fill-in picture puzzle
317
+ - `picturepath` — Draw a path through a picture
318
+
319
+ ## Browser and Module Support
283
320
 
284
321
  ```typescript
285
- // ES Modules (Browser/Node.js)
322
+ // ES Modules (browser / Node.js)
286
323
  import { PuzzleSectionClient } from '@puzzle-section/sdk-typescript';
287
324
 
288
325
  // CommonJS (Node.js)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@puzzle-section/sdk-typescript",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "TypeScript SDK for the Puzzle Section Platform API",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -29,7 +29,7 @@
29
29
  },
30
30
  "repository": {
31
31
  "type": "git",
32
- "url": "https://docs.puzzlesection.app/sdk/typescript",
32
+ "url": "https://docs.puzzlesection.app/sdks",
33
33
  "directory": "sdks/typescript"
34
34
  },
35
35
  "keywords": [
@@ -47,7 +47,7 @@
47
47
  "bugs": {
48
48
  "email": "support@puzzlesection.app"
49
49
  },
50
- "homepage": "https://docs.puzzlesection.app/sdk/typescript",
50
+ "homepage": "https://docs.puzzlesection.app/sdks",
51
51
  "devDependencies": {
52
52
  "@openapitools/openapi-generator-cli": "^2.15.3",
53
53
  "@types/node": "^22.14.0",