@puzzle-section/sdk-typescript 1.0.0 → 1.0.1
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 +223 -186
- package/package.json +1 -1
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
|
-
|
|
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
|
|
39
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
45
|
-
timeout: 30000,
|
|
56
|
+
## Response Envelope
|
|
46
57
|
|
|
47
|
-
|
|
48
|
-
retryCount: 3,
|
|
58
|
+
Every SDK method returns `Promise<ResponseWithRateLimit<T>>`:
|
|
49
59
|
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
###
|
|
78
|
+
### client.puzzles
|
|
58
79
|
|
|
59
80
|
```typescript
|
|
60
|
-
//
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
//
|
|
74
|
-
const puzzle = await client.puzzles.getById('
|
|
92
|
+
// Fetch a single puzzle by UUID
|
|
93
|
+
const { data: puzzle } = await client.puzzles.getById('550e8400-e29b-...');
|
|
75
94
|
|
|
76
|
-
//
|
|
77
|
-
const
|
|
78
|
-
difficulty: '
|
|
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
|
-
//
|
|
84
|
-
const
|
|
103
|
+
// Specific puzzle for a date and type
|
|
104
|
+
const { data } = await client.puzzles.getByDate('2026-03-05', 'sudoku', 'medium');
|
|
85
105
|
|
|
86
|
-
//
|
|
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
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
###
|
|
117
|
+
### client.health
|
|
99
118
|
|
|
100
119
|
```typescript
|
|
101
|
-
//
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
//
|
|
111
|
-
const
|
|
112
|
-
|
|
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
|
-
###
|
|
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
|
-
//
|
|
125
|
-
await client.
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
//
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
//
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
//
|
|
161
|
-
await client.
|
|
162
|
-
```
|
|
162
|
+
// Get a single puzzle
|
|
163
|
+
const { data: puzzle } = await client.admin.getPuzzle('puzzle-id');
|
|
163
164
|
|
|
164
|
-
|
|
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
|
-
|
|
167
|
-
|
|
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
|
-
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
187
|
+
// Delete a variant
|
|
188
|
+
await client.admin.deleteVariant('puzzle-id', 'variant-id');
|
|
194
189
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
|
224
|
+
const { data } = await client.puzzles.getById('nonexistent');
|
|
240
225
|
} catch (error) {
|
|
241
|
-
if (error instanceof
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
console.log(`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
##
|
|
302
|
+
## Available Puzzle Types
|
|
281
303
|
|
|
282
|
-
|
|
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 (
|
|
322
|
+
// ES Modules (browser / Node.js)
|
|
286
323
|
import { PuzzleSectionClient } from '@puzzle-section/sdk-typescript';
|
|
287
324
|
|
|
288
325
|
// CommonJS (Node.js)
|