@scarif/scarif-js 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 +451 -0
- package/dist/client.d.ts +33 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +145 -0
- package/dist/constants.d.ts +25 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +26 -0
- package/dist/index.d.ts +119 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +133 -0
- package/dist/types/api.d.ts +37 -0
- package/dist/types/api.d.ts.map +1 -0
- package/dist/types/api.js +20 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +27 -0
- package/dist/types/queries.d.ts +53 -0
- package/dist/types/queries.d.ts.map +1 -0
- package/dist/types/queries.js +5 -0
- package/dist/types/tables.d.ts +13 -0
- package/dist/types/tables.d.ts.map +1 -0
- package/dist/types/tables.js +6 -0
- package/dist/types/types.d.ts +22 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/types.js +12 -0
- package/dist/types.d.ts +22 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +12 -0
- package/dist/utils/query-builder.d.ts +84 -0
- package/dist/utils/query-builder.d.ts.map +1 -0
- package/dist/utils/query-builder.js +336 -0
- package/dist/utils/select-parser.d.ts +28 -0
- package/dist/utils/select-parser.d.ts.map +1 -0
- package/dist/utils/select-parser.js +84 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
# Stoorplek Data API SDK
|
|
2
|
+
|
|
3
|
+
Simple, type-safe SDK for accessing data from your API service with powerful chainable querying capabilities. Built with Supabase-style select syntax for intuitive querying.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install stoorplek-data-api
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
### Option 1: True One-Liner (Environment Variable)
|
|
14
|
+
|
|
15
|
+
Set your API key in `.env`:
|
|
16
|
+
```env
|
|
17
|
+
MY_API_KEY=sk_live_your_api_key_here
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Then use the one-liner:
|
|
21
|
+
```javascript
|
|
22
|
+
import { stoorplek } from 'stoorplek-data-api';
|
|
23
|
+
|
|
24
|
+
// That's it! One import, one call
|
|
25
|
+
const { data, error } = await stoorplek.from('countries').select('*');
|
|
26
|
+
console.log(data);
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Option 2: Configure Once, Use Everywhere
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
import { stoorplek } from 'stoorplek-data-api';
|
|
33
|
+
|
|
34
|
+
// Configure once at app startup
|
|
35
|
+
stoorplek.configure({
|
|
36
|
+
apiKey: 'sk_live_your_api_key_here'
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Then use anywhere in your app
|
|
40
|
+
const { data: countries } = await stoorplek.from('countries').select('*');
|
|
41
|
+
const { data: cities } = await stoorplek.from('cities').select('*');
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Option 3: Traditional Client (More Control)
|
|
45
|
+
|
|
46
|
+
```javascript
|
|
47
|
+
import { createClient } from 'stoorplek-data-api';
|
|
48
|
+
|
|
49
|
+
const client = createClient({
|
|
50
|
+
apiKey: 'sk_live_your_api_key_here',
|
|
51
|
+
baseUrl: 'https://api.yourservice.com', // optional
|
|
52
|
+
timeout: 30000 // optional, defaults to 30 seconds
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const { data, error } = await client.from('countries').select('*');
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Features
|
|
59
|
+
|
|
60
|
+
✨ **Type-Safe** - Full TypeScript support with automatic type inference
|
|
61
|
+
🔍 **Chainable API** - Supabase-like query builder with fluent interface
|
|
62
|
+
📦 **Zero Config** - Works with environment variables out of the box
|
|
63
|
+
🎯 **One-Liner Ready** - Import and use in a single line
|
|
64
|
+
⚡ **Lightweight** - Minimal dependencies, maximum performance
|
|
65
|
+
🔗 **Relations** - Easy eager loading of related data
|
|
66
|
+
|
|
67
|
+
## Querying & Filtering
|
|
68
|
+
|
|
69
|
+
### Basic Queries
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
import { stoorplek } from 'stoorplek-data-api';
|
|
73
|
+
|
|
74
|
+
// Get all records (select is required)
|
|
75
|
+
const { data: all } = await stoorplek.from('countries').select('*');
|
|
76
|
+
|
|
77
|
+
// Select specific columns
|
|
78
|
+
const { data: names } = await stoorplek.from('countries')
|
|
79
|
+
.select('name, iso');
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Relations (Eager Loading)
|
|
83
|
+
|
|
84
|
+
Supabase-style nested relations in the select string:
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
// Load relations (all columns)
|
|
88
|
+
const { data: withCities } = await stoorplek.from('countries')
|
|
89
|
+
.select('*, cities(*)');
|
|
90
|
+
|
|
91
|
+
// Load relations with specific columns
|
|
92
|
+
const { data: custom } = await stoorplek.from('countries')
|
|
93
|
+
.select('name, iso, cities(id, name, population)');
|
|
94
|
+
|
|
95
|
+
// Multiple relations
|
|
96
|
+
const { data: multiple } = await stoorplek.from('countries')
|
|
97
|
+
.select('*, cities(*), languages(*)');
|
|
98
|
+
|
|
99
|
+
// Multiple relations with column selection
|
|
100
|
+
const { data: complex } = await stoorplek.from('countries')
|
|
101
|
+
.select('id, name, cities(id, name), languages(*)');
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Filtering
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
// Equal to
|
|
108
|
+
const { data: usa } = await stoorplek.from('countries')
|
|
109
|
+
.select('*')
|
|
110
|
+
.eq('iso', 'US');
|
|
111
|
+
|
|
112
|
+
// Not equal to
|
|
113
|
+
const { data: notUSA } = await stoorplek.from('countries')
|
|
114
|
+
.select('*')
|
|
115
|
+
.neq('iso', 'US');
|
|
116
|
+
|
|
117
|
+
// Greater than / Less than
|
|
118
|
+
const { data: highIds } = await stoorplek.from('countries')
|
|
119
|
+
.select('*')
|
|
120
|
+
.gt('id', 100);
|
|
121
|
+
|
|
122
|
+
const { data: lowIds } = await stoorplek.from('countries')
|
|
123
|
+
.select('*')
|
|
124
|
+
.lt('id', 50);
|
|
125
|
+
|
|
126
|
+
// Pattern matching (case-insensitive)
|
|
127
|
+
const { data: unitedCountries } = await stoorplek.from('countries')
|
|
128
|
+
.select('*')
|
|
129
|
+
.ilike('name', '%united%');
|
|
130
|
+
|
|
131
|
+
// Pattern matching (case-sensitive)
|
|
132
|
+
const { data: exactMatch } = await stoorplek.from('countries')
|
|
133
|
+
.select('*')
|
|
134
|
+
.like('name', 'United%');
|
|
135
|
+
|
|
136
|
+
// In array
|
|
137
|
+
const { data: specific } = await stoorplek.from('countries')
|
|
138
|
+
.select('*')
|
|
139
|
+
.in('iso', ['US', 'CA', 'MX']);
|
|
140
|
+
|
|
141
|
+
// Not in array
|
|
142
|
+
const { data: excluded } = await stoorplek.from('countries')
|
|
143
|
+
.select('*')
|
|
144
|
+
.notIn('iso', ['US', 'CA']);
|
|
145
|
+
|
|
146
|
+
// Is null / Is not null
|
|
147
|
+
const { data: withNull } = await stoorplek.from('countries')
|
|
148
|
+
.select('*')
|
|
149
|
+
.isNull('deleted_at');
|
|
150
|
+
|
|
151
|
+
const { data: withoutNull } = await stoorplek.from('countries')
|
|
152
|
+
.select('*')
|
|
153
|
+
.isNotNull('email');
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Sorting
|
|
157
|
+
|
|
158
|
+
```javascript
|
|
159
|
+
// Single column sort (ascending by default)
|
|
160
|
+
const { data: sorted } = await stoorplek.from('countries')
|
|
161
|
+
.select('*')
|
|
162
|
+
.order('name');
|
|
163
|
+
|
|
164
|
+
// Descending order
|
|
165
|
+
const { data: sortedDesc } = await stoorplek.from('countries')
|
|
166
|
+
.select('*')
|
|
167
|
+
.order('name', 'desc');
|
|
168
|
+
|
|
169
|
+
// Multiple columns (chain multiple order calls)
|
|
170
|
+
const { data: multiSort } = await stoorplek.from('countries')
|
|
171
|
+
.select('*')
|
|
172
|
+
.order('name', 'asc')
|
|
173
|
+
.order('id', 'desc');
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Pagination
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
// Limit results
|
|
180
|
+
const { data: limited } = await stoorplek.from('countries')
|
|
181
|
+
.select('*')
|
|
182
|
+
.limit(10);
|
|
183
|
+
|
|
184
|
+
// Offset for pagination
|
|
185
|
+
const { data: page2 } = await stoorplek.from('countries')
|
|
186
|
+
.select('*')
|
|
187
|
+
.limit(10)
|
|
188
|
+
.offset(10);
|
|
189
|
+
|
|
190
|
+
// Combined with sorting
|
|
191
|
+
const { data: paginatedSorted } = await stoorplek.from('countries')
|
|
192
|
+
.select('*')
|
|
193
|
+
.order('name')
|
|
194
|
+
.limit(20)
|
|
195
|
+
.offset(0);
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Complex Queries
|
|
199
|
+
|
|
200
|
+
Combine multiple filters for powerful queries:
|
|
201
|
+
|
|
202
|
+
```javascript
|
|
203
|
+
const { data, error } = await stoorplek.from('countries')
|
|
204
|
+
.select('name, iso, cities(id, name), languages(*)')
|
|
205
|
+
.ilike('name', '%united%')
|
|
206
|
+
.order('name', 'asc')
|
|
207
|
+
.limit(10);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Getting Single Result
|
|
211
|
+
|
|
212
|
+
```javascript
|
|
213
|
+
// Get single result (automatically limits to 1)
|
|
214
|
+
const { data: usa, error } = await stoorplek.from('countries')
|
|
215
|
+
.select('*')
|
|
216
|
+
.eq('iso', 'US')
|
|
217
|
+
.single();
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## TypeScript Support
|
|
221
|
+
|
|
222
|
+
The SDK provides full type safety with automatic type inference:
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
import { stoorplek } from 'stoorplek-data-api';
|
|
226
|
+
|
|
227
|
+
// TypeScript automatically knows this returns ApiResponse<Country[]>
|
|
228
|
+
const { data, error } = await stoorplek.from('countries').select('*');
|
|
229
|
+
|
|
230
|
+
// Full autocomplete and type checking
|
|
231
|
+
data?.forEach(country => {
|
|
232
|
+
console.log(country.name); // ✅ TypeScript knows 'name' exists
|
|
233
|
+
console.log(country.iso); // ✅ TypeScript knows 'iso' exists
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Custom Types
|
|
238
|
+
|
|
239
|
+
For dynamic tables or custom type definitions:
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
interface CustomType {
|
|
243
|
+
id: number;
|
|
244
|
+
customField: string;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const { data, error } = await stoorplek.from<CustomType>('custom_table').select('*');
|
|
248
|
+
// data is typed as CustomType[] | null
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Error Handling
|
|
252
|
+
|
|
253
|
+
```javascript
|
|
254
|
+
import { stoorplek } from 'stoorplek-data-api';
|
|
255
|
+
|
|
256
|
+
const { data, error } = await stoorplek.from('countries')
|
|
257
|
+
.select('*')
|
|
258
|
+
.eq('iso', 'US');
|
|
259
|
+
|
|
260
|
+
if (error) {
|
|
261
|
+
console.error('API Error:', error.message);
|
|
262
|
+
console.error('Error Code:', error.code);
|
|
263
|
+
} else {
|
|
264
|
+
console.log('Data:', data);
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## API Reference
|
|
269
|
+
|
|
270
|
+
### `createClient(options)`
|
|
271
|
+
|
|
272
|
+
Creates a new API client instance.
|
|
273
|
+
|
|
274
|
+
**Parameters:**
|
|
275
|
+
- `options.apiKey` (required): Your API key
|
|
276
|
+
- `options.baseUrl` (optional): Base URL for the API (defaults to http://localhost:3000)
|
|
277
|
+
- `options.timeout` (optional): Request timeout in milliseconds (defaults to 30000)
|
|
278
|
+
|
|
279
|
+
**Returns:** `ApiClient` instance
|
|
280
|
+
|
|
281
|
+
**Example:**
|
|
282
|
+
```javascript
|
|
283
|
+
const client = createClient({
|
|
284
|
+
apiKey: 'sk_live_your_api_key',
|
|
285
|
+
baseUrl: 'https://api.yourservice.com',
|
|
286
|
+
timeout: 30000
|
|
287
|
+
});
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### `configure(options)`
|
|
291
|
+
|
|
292
|
+
Configure the default client for one-liner usage.
|
|
293
|
+
|
|
294
|
+
**Parameters:**
|
|
295
|
+
- `options.apiKey` (required): Your API key
|
|
296
|
+
- `options.baseUrl` (optional): Base URL for the API
|
|
297
|
+
- `options.timeout` (optional): Request timeout in milliseconds
|
|
298
|
+
|
|
299
|
+
**Example:**
|
|
300
|
+
```javascript
|
|
301
|
+
import { configure } from 'stoorplek-data-api';
|
|
302
|
+
|
|
303
|
+
configure({
|
|
304
|
+
apiKey: process.env.MY_API_KEY,
|
|
305
|
+
timeout: 30000
|
|
306
|
+
});
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### `from(table)` / `query(table)`
|
|
310
|
+
|
|
311
|
+
Start a chainable query for a specific table.
|
|
312
|
+
|
|
313
|
+
**Parameters:**
|
|
314
|
+
- `table`: Name of the table to query
|
|
315
|
+
|
|
316
|
+
**Returns:** `QueryBuilder<T>` instance with chainable methods
|
|
317
|
+
|
|
318
|
+
**Chainable Methods:**
|
|
319
|
+
- `.select(query)` - **Required.** Select columns and relations using Supabase-style syntax (e.g., `'id, name, cities(id, name)'`)
|
|
320
|
+
- **Filters:** `.eq()`, `.neq()`, `.gt()`, `.gte()`, `.lt()`, `.lte()`, `.like()`, `.ilike()`, `.in()`, `.notIn()`, `.isNull()`, `.isNotNull()` - See [Filtering](#filtering) section for examples
|
|
321
|
+
- `.order(column, direction?)` - Sort (direction: 'asc' | 'desc', defaults to 'asc')
|
|
322
|
+
- `.limit(count)` - Limit results
|
|
323
|
+
- `.offset(count)` - Pagination offset
|
|
324
|
+
- `.range(from, to)` - Set offset and limit together
|
|
325
|
+
- `.single()` - Execute query and return single result (returns object instead of array)
|
|
326
|
+
|
|
327
|
+
**Example:**
|
|
328
|
+
```javascript
|
|
329
|
+
const { data, error } = await stoorplek.from('countries')
|
|
330
|
+
.select('name, iso, cities(id, name)')
|
|
331
|
+
.ilike('name', '%united%')
|
|
332
|
+
.order('name', 'asc')
|
|
333
|
+
.limit(10);
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### `health()`
|
|
337
|
+
|
|
338
|
+
Check if the API is healthy.
|
|
339
|
+
|
|
340
|
+
**Returns:** `Promise<ApiResponse>`
|
|
341
|
+
|
|
342
|
+
**Example:**
|
|
343
|
+
```javascript
|
|
344
|
+
import { health } from 'stoorplek-data-api';
|
|
345
|
+
|
|
346
|
+
const status = await health();
|
|
347
|
+
console.log(status);
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### `stoorplek`
|
|
351
|
+
|
|
352
|
+
Default export object for easy importing and usage.
|
|
353
|
+
|
|
354
|
+
**Example:**
|
|
355
|
+
```javascript
|
|
356
|
+
const { stoorplek } = require('stoorplek-data-api');
|
|
357
|
+
|
|
358
|
+
// Configure (or use MY_API_KEY env var)
|
|
359
|
+
stoorplek.configure({ apiKey: 'your-key' });
|
|
360
|
+
|
|
361
|
+
// Use in queries
|
|
362
|
+
const { data, error } = await stoorplek.from('countries').select('*');
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
## Response Format
|
|
366
|
+
|
|
367
|
+
All API responses follow this structure:
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
interface ApiResponse<T> {
|
|
371
|
+
data: T;
|
|
372
|
+
error: {
|
|
373
|
+
message: string;
|
|
374
|
+
code?: string;
|
|
375
|
+
details?: any;
|
|
376
|
+
} | null;
|
|
377
|
+
status: number;
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
**Example success response:**
|
|
382
|
+
```json
|
|
383
|
+
{
|
|
384
|
+
"data": [
|
|
385
|
+
{ "id": 1, "name": "United States", "iso": "US" },
|
|
386
|
+
{ "id": 2, "name": "Canada", "iso": "CA" }
|
|
387
|
+
],
|
|
388
|
+
"error": null,
|
|
389
|
+
"status": 200
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
**Example error response:**
|
|
394
|
+
```json
|
|
395
|
+
{
|
|
396
|
+
"data": [],
|
|
397
|
+
"error": {
|
|
398
|
+
"message": "Invalid table name",
|
|
399
|
+
"code": "INVALID_TABLE",
|
|
400
|
+
"details": {}
|
|
401
|
+
},
|
|
402
|
+
"status": 400
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
## Advanced Usage
|
|
407
|
+
|
|
408
|
+
### Using with the Client Instance
|
|
409
|
+
|
|
410
|
+
```javascript
|
|
411
|
+
import { createClient } from 'stoorplek-data-api';
|
|
412
|
+
|
|
413
|
+
const client = createClient({
|
|
414
|
+
apiKey: process.env.API_KEY
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// All the same chainable methods work
|
|
418
|
+
const { data, error } = await client
|
|
419
|
+
.from('countries')
|
|
420
|
+
.select('name, iso')
|
|
421
|
+
.ilike('name', '%united%');
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### Environment Variables
|
|
425
|
+
|
|
426
|
+
The SDK automatically looks for `MY_API_KEY` in your environment:
|
|
427
|
+
|
|
428
|
+
```env
|
|
429
|
+
# .env file
|
|
430
|
+
MY_API_KEY=sk_live_your_api_key_here
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Then just import and use:
|
|
434
|
+
```javascript
|
|
435
|
+
import { stoorplek } from 'stoorplek-data-api';
|
|
436
|
+
|
|
437
|
+
const { data, error } = await stoorplek.from('countries').select('*'); // No configuration needed!
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
## Available Tables
|
|
441
|
+
|
|
442
|
+
Currently supported tables:
|
|
443
|
+
- `countries` - Country data with relations to `cities` and `languages`
|
|
444
|
+
- `cities` - City data with relation to `country`
|
|
445
|
+
- `languages` - Language data with relation to `countries`
|
|
446
|
+
|
|
447
|
+
More tables will be added as the API expands.
|
|
448
|
+
|
|
449
|
+
## License
|
|
450
|
+
|
|
451
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ApiClientOptions, ApiResponse, QueryBuilder, TableTypes } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Main API client class
|
|
4
|
+
*/
|
|
5
|
+
export declare class ApiClient {
|
|
6
|
+
private apiKey;
|
|
7
|
+
private baseUrl;
|
|
8
|
+
private timeout;
|
|
9
|
+
constructor(options: ApiClientOptions);
|
|
10
|
+
/**
|
|
11
|
+
* Start a chainable query for a table
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const countries = await client
|
|
16
|
+
* .from('countries')
|
|
17
|
+
* .select('name, iso, cities(id, name, population)')
|
|
18
|
+
* .ilike('name', '%united%')
|
|
19
|
+
* .order('name', 'asc')
|
|
20
|
+
* .limit(10);
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
from<T extends keyof TableTypes>(table: T): QueryBuilder<TableTypes[T]>;
|
|
24
|
+
from<T = any>(table: string): QueryBuilder<T>;
|
|
25
|
+
/**
|
|
26
|
+
* Handles common error cases and returns appropriate ApiResponse
|
|
27
|
+
*/
|
|
28
|
+
private handleError;
|
|
29
|
+
private executeQuery;
|
|
30
|
+
health(): Promise<ApiResponse>;
|
|
31
|
+
private validateTable;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAc,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAK9F;;GAEG;AACH,qBAAa,SAAS;IAClB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,gBAAgB;IAcrC;;;;;;;;;;;;OAYG;IACH,IAAI,CAAC,CAAC,SAAS,MAAM,UAAU,EAAE,KAAK,EAAE,CAAC,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IACvE,IAAI,CAAC,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC;IAa7C;;OAEG;IACH,OAAO,CAAC,WAAW;YA4BL,YAAY;IA6DpB,MAAM,IAAI,OAAO,CAAC,WAAW,CAAC;IAsCpC,OAAO,CAAC,aAAa;CAOxB"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ApiClient = void 0;
|
|
4
|
+
const api_1 = require("./types/api");
|
|
5
|
+
const constants_1 = require("./constants");
|
|
6
|
+
const query_builder_1 = require("./utils/query-builder");
|
|
7
|
+
/**
|
|
8
|
+
* Main API client class
|
|
9
|
+
*/
|
|
10
|
+
class ApiClient {
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.apiKey = options.apiKey;
|
|
13
|
+
this.baseUrl = options.baseUrl || constants_1.DEFAULT_BASE_URL;
|
|
14
|
+
this.timeout = options.timeout || constants_1.DEFAULT_TIMEOUT;
|
|
15
|
+
if (!this.apiKey) {
|
|
16
|
+
throw new Error('API key is required');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
from(table) {
|
|
20
|
+
this.validateTable(table);
|
|
21
|
+
return new query_builder_1.QueryBuilderImpl(table, (t, state) => this.executeQuery(t, state));
|
|
22
|
+
}
|
|
23
|
+
// ===========================================
|
|
24
|
+
// ERROR HANDLING
|
|
25
|
+
// ===========================================
|
|
26
|
+
/**
|
|
27
|
+
* Handles common error cases and returns appropriate ApiResponse
|
|
28
|
+
*/
|
|
29
|
+
handleError(error, defaultMessage, defaultData) {
|
|
30
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
31
|
+
return {
|
|
32
|
+
data: defaultData,
|
|
33
|
+
error: {
|
|
34
|
+
message: `Request timeout after ${this.timeout}ms`,
|
|
35
|
+
code: 'TIMEOUT',
|
|
36
|
+
},
|
|
37
|
+
status: 408,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const errorMessage = error instanceof Error ? error.message : defaultMessage;
|
|
41
|
+
return {
|
|
42
|
+
data: defaultData,
|
|
43
|
+
error: {
|
|
44
|
+
message: errorMessage,
|
|
45
|
+
code: 'FETCH_ERROR',
|
|
46
|
+
details: error,
|
|
47
|
+
},
|
|
48
|
+
status: 0,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
// ===========================================
|
|
52
|
+
// DIRECT EXECUTION (Internal)
|
|
53
|
+
// ===========================================
|
|
54
|
+
async executeQuery(table, state) {
|
|
55
|
+
const queryString = (0, query_builder_1.buildQueryString)(state);
|
|
56
|
+
const url = `${this.baseUrl}/data/${table}${queryString}`;
|
|
57
|
+
try {
|
|
58
|
+
const controller = new AbortController();
|
|
59
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
60
|
+
const response = await fetch(url, {
|
|
61
|
+
method: 'GET',
|
|
62
|
+
headers: {
|
|
63
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
64
|
+
'Content-Type': 'application/json',
|
|
65
|
+
},
|
|
66
|
+
signal: controller.signal,
|
|
67
|
+
});
|
|
68
|
+
clearTimeout(timeoutId);
|
|
69
|
+
const jsonData = await response.json();
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
const errorData = (0, api_1.isApiErrorResponse)(jsonData) ? jsonData : {};
|
|
72
|
+
return {
|
|
73
|
+
data: [],
|
|
74
|
+
error: {
|
|
75
|
+
message: errorData.message || 'Request failed',
|
|
76
|
+
code: errorData.code,
|
|
77
|
+
details: errorData.details,
|
|
78
|
+
},
|
|
79
|
+
status: response.status,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if ((0, api_1.isApiSuccessResponse)(jsonData)) {
|
|
83
|
+
return {
|
|
84
|
+
data: jsonData.data,
|
|
85
|
+
error: null,
|
|
86
|
+
status: response.status,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// Fallback for unexpected response structure
|
|
90
|
+
return {
|
|
91
|
+
data: [],
|
|
92
|
+
error: {
|
|
93
|
+
message: 'Unexpected response format',
|
|
94
|
+
code: 'INVALID_RESPONSE',
|
|
95
|
+
details: jsonData,
|
|
96
|
+
},
|
|
97
|
+
status: response.status,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
return this.handleError(error, 'Unknown error occurred', []);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// ===========================================
|
|
105
|
+
// HEALTH CHECK
|
|
106
|
+
// ===========================================
|
|
107
|
+
async health() {
|
|
108
|
+
const url = `${this.baseUrl}/health`;
|
|
109
|
+
try {
|
|
110
|
+
const controller = new AbortController();
|
|
111
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
112
|
+
const response = await fetch(url, {
|
|
113
|
+
method: 'GET',
|
|
114
|
+
headers: {
|
|
115
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
116
|
+
},
|
|
117
|
+
signal: controller.signal,
|
|
118
|
+
});
|
|
119
|
+
clearTimeout(timeoutId);
|
|
120
|
+
const data = await response.json();
|
|
121
|
+
return {
|
|
122
|
+
data,
|
|
123
|
+
error: null,
|
|
124
|
+
status: response.status,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
const response = this.handleError(error, 'Health check failed', null);
|
|
129
|
+
// Override error code for health check
|
|
130
|
+
return {
|
|
131
|
+
...response,
|
|
132
|
+
error: response.error ? { ...response.error, code: 'HEALTH_CHECK_ERROR' } : null,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// ===========================================
|
|
137
|
+
// VALIDATION
|
|
138
|
+
// ===========================================
|
|
139
|
+
validateTable(table) {
|
|
140
|
+
if (!constants_1.ALLOWED_TABLES[table]) {
|
|
141
|
+
throw new Error(`Invalid table: "${table}". Allowed tables: ${Object.keys(constants_1.ALLOWED_TABLES).join(', ')}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
exports.ApiClient = ApiClient;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package constants and configuration
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* List of allowed table names
|
|
6
|
+
* This should match your server's whitelist
|
|
7
|
+
*/
|
|
8
|
+
export interface TableDefinition {
|
|
9
|
+
relations?: string[];
|
|
10
|
+
}
|
|
11
|
+
export declare const ALLOWED_TABLES: Record<string, TableDefinition>;
|
|
12
|
+
/**
|
|
13
|
+
* Type for allowed table names
|
|
14
|
+
*/
|
|
15
|
+
export type AllowedTable = keyof typeof ALLOWED_TABLES;
|
|
16
|
+
/**
|
|
17
|
+
* Default API base URL
|
|
18
|
+
*/
|
|
19
|
+
export declare const DEFAULT_BASE_URL = "http://localhost:3000";
|
|
20
|
+
/**
|
|
21
|
+
* Environment variable name for API key
|
|
22
|
+
*/
|
|
23
|
+
export declare const API_KEY_ENV_VAR = "MY_API_KEY";
|
|
24
|
+
export declare const DEFAULT_TIMEOUT = 30000;
|
|
25
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC5B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAU1D,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,cAAc,CAAC;AAEvD;;GAEG;AACH,eAAO,MAAM,gBAAgB,0BAA0B,CAAC;AAExD;;GAEG;AACH,eAAO,MAAM,eAAe,eAAe,CAAC;AAE5C,eAAO,MAAM,eAAe,QAAQ,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Package constants and configuration
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DEFAULT_TIMEOUT = exports.API_KEY_ENV_VAR = exports.DEFAULT_BASE_URL = exports.ALLOWED_TABLES = void 0;
|
|
7
|
+
exports.ALLOWED_TABLES = {
|
|
8
|
+
countries: {
|
|
9
|
+
relations: ['cities', 'languages']
|
|
10
|
+
},
|
|
11
|
+
cities: {
|
|
12
|
+
relations: ['country']
|
|
13
|
+
},
|
|
14
|
+
languages: {
|
|
15
|
+
relations: ['countries']
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Default API base URL
|
|
20
|
+
*/
|
|
21
|
+
exports.DEFAULT_BASE_URL = 'http://localhost:3000';
|
|
22
|
+
/**
|
|
23
|
+
* Environment variable name for API key
|
|
24
|
+
*/
|
|
25
|
+
exports.API_KEY_ENV_VAR = 'MY_API_KEY';
|
|
26
|
+
exports.DEFAULT_TIMEOUT = 30000; // 30 seconds
|