@salefony/api-sdk 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 +675 -0
- package/dist/index.d.mts +1862 -0
- package/dist/index.d.ts +1862 -0
- package/dist/index.js +2310 -0
- package/dist/index.mjs +2246 -0
- package/package.json +64 -0
package/README.md
ADDED
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
# Salefony API SDK
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
Salefony API SDK - Official SDK for Salefony Backend API. Full TypeScript support, SSR-ready, with column autocomplete and Cloudflare-style API design.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Table of Contents
|
|
11
|
+
|
|
12
|
+
- [Features](#-features)
|
|
13
|
+
- [Installation](#-installation)
|
|
14
|
+
- [Quick Start](#-quick-start)
|
|
15
|
+
- [Configuration](#-configuration)
|
|
16
|
+
- [Authentication](#-authentication)
|
|
17
|
+
- [Core API Methods](#-core-api-methods)
|
|
18
|
+
- [Columns & Autocomplete](#-columns--autocomplete)
|
|
19
|
+
- [Filtering & Pagination](#-filtering--pagination)
|
|
20
|
+
- [LINQ-style Fluent API](#-linq-style-fluent-api)
|
|
21
|
+
- [Response Helpers](#-response-helpers)
|
|
22
|
+
- [Admin vs Frontstore](#-admin-vs-frontstore)
|
|
23
|
+
- [SSR Usage](#-ssr-usage)
|
|
24
|
+
- [Error Handling](#-error-handling)
|
|
25
|
+
- [API Reference](#-api-reference)
|
|
26
|
+
- [Troubleshooting](#-troubleshooting)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## ✨ Features
|
|
31
|
+
|
|
32
|
+
| Feature | Description |
|
|
33
|
+
|---------|-------------|
|
|
34
|
+
| **Full Type Safety** | TypeScript support for models, queries, and column autocomplete |
|
|
35
|
+
| **SSR Optimized** | Compatible with Next.js Server Components, Actions, and Nuxt |
|
|
36
|
+
| **Cloudflare-style API** | `get`, `list`, `create`, `edit`, `deleteById` syntax |
|
|
37
|
+
| **Column Autocomplete** | Typed columns for each resource (e.g. `translations.*`, `parent.id`) |
|
|
38
|
+
| **LINQ-style Queries** | Fluent chain: `.where().orderBy().take().toList()` |
|
|
39
|
+
| **Response Helpers** | `unwrap()`, `unwrapList()`, `unwrapMeta()` for easy data extraction |
|
|
40
|
+
| **Env Fallback** | `createSDKFromEnv()` for automatic config from environment variables |
|
|
41
|
+
| **Built-in Retry** | Automatic retry for network and 5xx errors |
|
|
42
|
+
| **Debug Mode** | Request/response logging in development |
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 📦 Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Using bun
|
|
50
|
+
bun add @salefony/api-sdk
|
|
51
|
+
|
|
52
|
+
# Using npm
|
|
53
|
+
npm install @salefony/api-sdk
|
|
54
|
+
|
|
55
|
+
# Using pnpm
|
|
56
|
+
pnpm add @salefony/api-sdk
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 🚀 Quick Start
|
|
62
|
+
|
|
63
|
+
### 1. Create SDK instance
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { createSDK, createSDKFromEnv } from '@salefony/api-sdk';
|
|
67
|
+
|
|
68
|
+
// Option A: Manual configuration
|
|
69
|
+
const sdk = createSDK({
|
|
70
|
+
baseUrl: 'https://api.yourdomain.com',
|
|
71
|
+
authToken: 'your-jwt-token',
|
|
72
|
+
debug: true,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Option B: From environment variables (recommended for Next.js)
|
|
76
|
+
// Uses: NEXT_PUBLIC_BACKEND_API_URL or BACKEND_API_URL, NODE_ENV
|
|
77
|
+
const sdk = createSDKFromEnv({ authToken: process.env.JWT });
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 2. Basic CRUD operations
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// Get single record by ID
|
|
84
|
+
const collection = await sdk.admin.collections.get('collection-id-123');
|
|
85
|
+
|
|
86
|
+
// List records with pagination
|
|
87
|
+
const result = await sdk.admin.collections.list({
|
|
88
|
+
filter: { isActive: true },
|
|
89
|
+
paginate: { page: 1, limit: 20 },
|
|
90
|
+
sort: 'order:asc',
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Create new record
|
|
94
|
+
const created = await sdk.admin.collections.create({
|
|
95
|
+
slug: 'my-collection',
|
|
96
|
+
typeId: 'type-id',
|
|
97
|
+
translations: [{ languageCode: 'en', title: 'My Collection' }],
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Update record
|
|
101
|
+
await sdk.admin.collections.edit('collection-id', { isActive: false });
|
|
102
|
+
|
|
103
|
+
// Delete record
|
|
104
|
+
await sdk.admin.collections.deleteById('collection-id');
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 3. Extract data from response
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { unwrap, unwrapList, unwrapMeta } from '@salefony/api-sdk';
|
|
111
|
+
|
|
112
|
+
const res = await sdk.admin.collections.list({ filter: { isActive: true } });
|
|
113
|
+
|
|
114
|
+
const items = unwrapList(res); // res.data ?? [] (always array)
|
|
115
|
+
const first = unwrap(res); // res.data ?? null
|
|
116
|
+
const meta = unwrapMeta(res); // res.meta (pagination info)
|
|
117
|
+
const total = meta?.total ?? 0; // total record count
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## ⚙️ Configuration
|
|
123
|
+
|
|
124
|
+
### SDK Config Options
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
interface SDKConfig {
|
|
128
|
+
baseUrl: string; // Required: API base URL
|
|
129
|
+
authToken?: string; // Bearer token for JWT auth
|
|
130
|
+
vendorKey?: string; // API key for vendor/marketplace
|
|
131
|
+
debug?: boolean; // Log requests/responses (default: NODE_ENV === 'development')
|
|
132
|
+
timeout?: number; // Request timeout in ms
|
|
133
|
+
retries?: number; // Retry count for failed requests
|
|
134
|
+
headers?: Record<string, string>; // Custom headers
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Method chaining
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
sdk
|
|
142
|
+
.setAuthToken('jwt-token')
|
|
143
|
+
.setVendorKey('vnd_xxx')
|
|
144
|
+
.setHeaders({ 'X-Custom-Header': 'value' });
|
|
145
|
+
|
|
146
|
+
// Clear auth
|
|
147
|
+
sdk.logout();
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 🔐 Authentication
|
|
153
|
+
|
|
154
|
+
### Admin login (Better Auth)
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// Sign in with email/password
|
|
158
|
+
const { data } = await sdk.admin.auth.mobileLogin({
|
|
159
|
+
email: 'admin@example.com',
|
|
160
|
+
password: 'password',
|
|
161
|
+
provider: 'email',
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Set token for subsequent requests
|
|
165
|
+
sdk.setAuthToken(data.accessToken);
|
|
166
|
+
|
|
167
|
+
// Check session
|
|
168
|
+
const session = await sdk.admin.auth.getSession();
|
|
169
|
+
|
|
170
|
+
// Sign out
|
|
171
|
+
await sdk.admin.auth.signOut();
|
|
172
|
+
sdk.logout();
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Passing auth in Next.js
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
// In Server Component or API route
|
|
179
|
+
import { cookies } from 'next/headers';
|
|
180
|
+
|
|
181
|
+
const cookieStore = await cookies();
|
|
182
|
+
const token = cookieStore.get('auth-token')?.value;
|
|
183
|
+
|
|
184
|
+
const sdk = createSDKFromEnv();
|
|
185
|
+
if (token) sdk.setAuthToken(token);
|
|
186
|
+
|
|
187
|
+
const data = await sdk.admin.collections.list();
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 📋 Core API Methods
|
|
193
|
+
|
|
194
|
+
Every CRUD resource supports these methods:
|
|
195
|
+
|
|
196
|
+
| Method | Description | Example |
|
|
197
|
+
|--------|-------------|---------|
|
|
198
|
+
| `get(id \| params)` | Get single record | `get('id')` or `get({ id, columns })` |
|
|
199
|
+
| `list(params?)` | List with filter, paginate, sort | `list({ filter, paginate, sort })` |
|
|
200
|
+
| `create(data)` | Create new record | `create({ slug, name, ... })` |
|
|
201
|
+
| `edit(id, data)` | Update record | `edit('id', { name: 'New' })` |
|
|
202
|
+
| `deleteById(id)` | Delete record | `deleteById('id')` |
|
|
203
|
+
|
|
204
|
+
### get() – Single record
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
// Simple: get by ID (returns all fields)
|
|
208
|
+
const col = await sdk.admin.collections.get('xyz');
|
|
209
|
+
|
|
210
|
+
// With columns: select specific fields (recommended)
|
|
211
|
+
const col = await sdk.admin.collections.get({
|
|
212
|
+
id: 'xyz',
|
|
213
|
+
columns: ['id', 'slug', 'translations.*', 'parent.id', 'type.*'],
|
|
214
|
+
language: 'en', // Optional: for translations
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Response: { data: T, meta?: ListMeta }
|
|
218
|
+
const data = col.data;
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### list() – List records
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
const res = await sdk.admin.collections.list({
|
|
225
|
+
columns: ['id', 'slug', 'order', 'translations.*', 'parent.id'],
|
|
226
|
+
filter: { isActive: true, typeId: 'category' },
|
|
227
|
+
paginate: { page: 1, limit: 20 },
|
|
228
|
+
sort: 'order:asc',
|
|
229
|
+
search: 'keyword', // Optional: full-text search
|
|
230
|
+
language: 'en', // Optional: for translations
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Response: { data: T[], meta: { total, page, limit, totalPages, ... } }
|
|
234
|
+
const items = res.data;
|
|
235
|
+
const total = res.meta?.total;
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### create() – Create record
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
const created = await sdk.admin.collections.create({
|
|
242
|
+
slug: 'new-collection',
|
|
243
|
+
typeId: 'content-type-id',
|
|
244
|
+
parentId: null,
|
|
245
|
+
order: 0,
|
|
246
|
+
isActive: true,
|
|
247
|
+
translations: [
|
|
248
|
+
{ languageCode: 'en', title: 'New Collection', description: 'Desc' },
|
|
249
|
+
],
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### edit() – Update record
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
await sdk.admin.collections.edit('collection-id', {
|
|
257
|
+
isActive: false,
|
|
258
|
+
order: 5,
|
|
259
|
+
translations: [
|
|
260
|
+
{ languageCode: 'en', title: 'Updated Title' },
|
|
261
|
+
],
|
|
262
|
+
});
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### deleteById() – Delete record
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
await sdk.admin.collections.deleteById('collection-id');
|
|
269
|
+
// Returns void (204 No Content)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## 📌 Columns & Autocomplete
|
|
275
|
+
|
|
276
|
+
Columns use **dot-notation** and are typed per resource for IDE autocomplete.
|
|
277
|
+
|
|
278
|
+
### Using column helpers (recommended)
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
import {
|
|
282
|
+
collectionColumns,
|
|
283
|
+
contentColumns,
|
|
284
|
+
layoutColumns,
|
|
285
|
+
navigationColumns,
|
|
286
|
+
languageColumns,
|
|
287
|
+
projectColumns,
|
|
288
|
+
} from '@salefony/api-sdk';
|
|
289
|
+
|
|
290
|
+
// TypeScript suggests valid columns
|
|
291
|
+
const cols = collectionColumns(
|
|
292
|
+
'id',
|
|
293
|
+
'slug',
|
|
294
|
+
'order',
|
|
295
|
+
'translations.*',
|
|
296
|
+
'parent.id',
|
|
297
|
+
'parent.translations.*',
|
|
298
|
+
'type.id',
|
|
299
|
+
'type.slug',
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
const res = await sdk.admin.collections.list({
|
|
303
|
+
columns: cols,
|
|
304
|
+
filter: { isActive: true },
|
|
305
|
+
});
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Available column helpers
|
|
309
|
+
|
|
310
|
+
| Resource | Helper | Example columns |
|
|
311
|
+
|----------|--------|-----------------|
|
|
312
|
+
| Collections | `collectionColumns` | `id`, `slug`, `translations.*`, `parent.id`, `children.*`, `contents.*` |
|
|
313
|
+
| Contents | `contentColumns` | `id`, `slug`, `translations.*`, `collection.*`, `type.*`, `attributes.*` |
|
|
314
|
+
| Layouts | `layoutColumns` | `id`, `name`, `slug`, `LayoutTranslation.*`, `layoutData` |
|
|
315
|
+
| Navigations | `navigationColumns` | `id`, `name`, `slug`, `items.*`, `translations.*` |
|
|
316
|
+
| Languages | `languageColumns` | `id`, `name`, `code`, `isDefault`, `isActive` |
|
|
317
|
+
| Projects | `projectColumns` | `id`, `slug`, `sector.*`, `translations.*` |
|
|
318
|
+
| Datasource | `datasourceColumns` | `id`, `slug`, `title`, `translations.*`, `Content.*` |
|
|
319
|
+
| Metadata | `metadataColumns` | `id`, `slug`, `title`, `type.*`, `entries.*` |
|
|
320
|
+
| Vendors | `vendorColumns` | `id`, `name`, `slug`, `collections.*` |
|
|
321
|
+
| Store | `storeColumns` | `id`, `name`, `slug`, `defaultLanguage.*` |
|
|
322
|
+
| Section | `sectionColumns` | `id`, `name`, `layout.*` |
|
|
323
|
+
| Sector | `sectorColumns` | `id`, `slug`, `translations.*` |
|
|
324
|
+
| ApiKey | `apiKeyColumns` | `id`, `name`, `key`, `store.*` |
|
|
325
|
+
|
|
326
|
+
### Generic columns helper
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
import { columns } from '@salefony/api-sdk';
|
|
330
|
+
import type { CollectionColumn } from '@salefony/api-sdk';
|
|
331
|
+
|
|
332
|
+
const cols = columns<CollectionColumn>('id', 'slug', 'translations.*');
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Column syntax
|
|
336
|
+
|
|
337
|
+
| Pattern | Meaning | Example |
|
|
338
|
+
|--------|---------|---------|
|
|
339
|
+
| `field` | Single scalar field | `id`, `slug`, `name` |
|
|
340
|
+
| `relation.field` | Field from relation | `parent.id`, `type.slug` |
|
|
341
|
+
| `relation.*` | All fields of relation | `translations.*`, `parent.*` |
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## 🔍 Filtering & Pagination
|
|
346
|
+
|
|
347
|
+
### filter
|
|
348
|
+
|
|
349
|
+
Simple key-value filter (merged into Prisma `where`):
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
filter: {
|
|
353
|
+
isActive: true,
|
|
354
|
+
typeId: 'category-id',
|
|
355
|
+
parentId: null,
|
|
356
|
+
// Nested
|
|
357
|
+
type: { slug: 'blog' },
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### paginate
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
paginate: {
|
|
365
|
+
page: 1, // 1-based
|
|
366
|
+
limit: 20, // items per page
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### sort
|
|
371
|
+
|
|
372
|
+
Format: `"field:asc"` or `"field:desc"`:
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
sort: 'order:asc'
|
|
376
|
+
sort: 'createdAt:desc'
|
|
377
|
+
sort: 'name:asc'
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### search
|
|
381
|
+
|
|
382
|
+
Full-text search (backend-dependent):
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
list({ search: 'keyword', language: 'en' })
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## ⛓️ LINQ-style Fluent API
|
|
391
|
+
|
|
392
|
+
Chain methods for readable queries:
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
// Basic chain
|
|
396
|
+
const items = await sdk.admin.collections
|
|
397
|
+
.where({ isActive: true })
|
|
398
|
+
.columns('id', 'slug', 'translations.*')
|
|
399
|
+
.sort('order:asc')
|
|
400
|
+
.take(10)
|
|
401
|
+
.skip(0)
|
|
402
|
+
.toList();
|
|
403
|
+
|
|
404
|
+
// Start with query()
|
|
405
|
+
const items = await sdk.admin.collections
|
|
406
|
+
.query()
|
|
407
|
+
.where({ isActive: true })
|
|
408
|
+
.orderBy({ order: 'asc' })
|
|
409
|
+
.orderByDescending('createdAt')
|
|
410
|
+
.take(20)
|
|
411
|
+
.toList();
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### LINQ methods
|
|
415
|
+
|
|
416
|
+
| Method | Description | Example |
|
|
417
|
+
|--------|-------------|---------|
|
|
418
|
+
| `where(filter)` | Add filter | `.where({ isActive: true })` |
|
|
419
|
+
| `columns(...cols)` | Add columns | `.columns('id', 'translations.*')` |
|
|
420
|
+
| `filter(obj)` | Alias for where | `.filter({ typeId: 'x' })` |
|
|
421
|
+
| `sort(str)` | Set sort | `.sort('order:asc')` |
|
|
422
|
+
| `orderBy(obj)` | Ascending | `.orderBy({ order: 'asc' })` |
|
|
423
|
+
| `orderByDescending(field)` | Descending | `.orderByDescending('createdAt')` |
|
|
424
|
+
| `take(n)` | Limit | `.take(10)` |
|
|
425
|
+
| `skip(n)` | Offset | `.skip(20)` |
|
|
426
|
+
| `toList()` | Execute, return list | `.toList()` |
|
|
427
|
+
| `execute()` | Alias for toList | `.execute()` |
|
|
428
|
+
| `first()` | First item, throws if empty | `.first()` |
|
|
429
|
+
| `firstOrDefault()` | First or null | `.firstOrDefault()` |
|
|
430
|
+
| `single()` | Single item, throws if not 1 | `.single()` |
|
|
431
|
+
| `singleOrDefault()` | Single or null | `.singleOrDefault()` |
|
|
432
|
+
| `count()` | Count | `.count()` |
|
|
433
|
+
| `any()` | Exists? | `.any()` |
|
|
434
|
+
|
|
435
|
+
### Examples
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
// First matching record
|
|
439
|
+
const first = await sdk.admin.collections
|
|
440
|
+
.where({ slug: 'blog' })
|
|
441
|
+
.first();
|
|
442
|
+
|
|
443
|
+
// Check existence
|
|
444
|
+
const exists = await sdk.admin.collections
|
|
445
|
+
.where({ typeId: 'x' })
|
|
446
|
+
.any();
|
|
447
|
+
|
|
448
|
+
// Count
|
|
449
|
+
const count = await sdk.admin.collections
|
|
450
|
+
.where({ isActive: true })
|
|
451
|
+
.count();
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
## 📤 Response Helpers
|
|
457
|
+
|
|
458
|
+
```typescript
|
|
459
|
+
import { unwrap, unwrapList, unwrapMeta } from '@salefony/api-sdk';
|
|
460
|
+
|
|
461
|
+
const res = await sdk.admin.collections.list();
|
|
462
|
+
|
|
463
|
+
// Extract data with fallback
|
|
464
|
+
const data = unwrap(res); // res.data ?? res ?? null
|
|
465
|
+
const items = unwrapList(res); // res.data ?? [] (always array)
|
|
466
|
+
const meta = unwrapMeta(res); // res.meta
|
|
467
|
+
|
|
468
|
+
// Pagination
|
|
469
|
+
const { total, page, limit, totalPages, hasNextPage } = meta ?? {};
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
## 🏢 Admin vs Frontstore
|
|
475
|
+
|
|
476
|
+
| Namespace | Use case | Example |
|
|
477
|
+
|-----------|----------|---------|
|
|
478
|
+
| `sdk.admin` | Admin panel, CMS, management | `sdk.admin.collections`, `sdk.admin.contents` |
|
|
479
|
+
| `sdk.frontstore` | Public storefront, site | `sdk.frontstore.content`, `sdk.frontstore.collection` |
|
|
480
|
+
|
|
481
|
+
### Admin resources
|
|
482
|
+
|
|
483
|
+
- `collections`, `contents`, `layouts`, `navigations`
|
|
484
|
+
- `languages`, `datasource`, `metadata`
|
|
485
|
+
- `vendors`, `stores`, `apiKeys`
|
|
486
|
+
- `settings`, `seo`, `media`
|
|
487
|
+
- `projects`, `sectors`, `sections`
|
|
488
|
+
- `cloudflare`, `google`, `themes`
|
|
489
|
+
- `subscriptions`, `purchases`, `customData`
|
|
490
|
+
- `auth`, `reviews`
|
|
491
|
+
|
|
492
|
+
### Frontstore resources
|
|
493
|
+
|
|
494
|
+
- `content`, `collection` – public content/collections
|
|
495
|
+
- `navigation`, `media`, `language`
|
|
496
|
+
- `store`, `user`, `shop`
|
|
497
|
+
- `utility` (settings, sitemap)
|
|
498
|
+
- `slugs`, `favorite`, `review`
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
## 🛠️ SSR Usage
|
|
503
|
+
|
|
504
|
+
### Next.js Server Component
|
|
505
|
+
|
|
506
|
+
```typescript
|
|
507
|
+
import { createSDKFromEnv, unwrapList } from '@salefony/api-sdk';
|
|
508
|
+
import { cookies } from 'next/headers';
|
|
509
|
+
|
|
510
|
+
export default async function Page() {
|
|
511
|
+
const sdk = createSDKFromEnv();
|
|
512
|
+
const cookieStore = await cookies();
|
|
513
|
+
const token = cookieStore.get('auth-token')?.value;
|
|
514
|
+
if (token) sdk.setAuthToken(token);
|
|
515
|
+
|
|
516
|
+
const res = await sdk.admin.collections.list({
|
|
517
|
+
columns: ['id', 'slug', 'translations.*'],
|
|
518
|
+
filter: { isActive: true },
|
|
519
|
+
sort: 'order:asc',
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
const items = unwrapList(res);
|
|
523
|
+
return <ul>{items.map((i) => <li key={i.id}>{i.slug}</li>)}</ul>;
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### Next.js API Route
|
|
528
|
+
|
|
529
|
+
```typescript
|
|
530
|
+
// app/api/collections/route.ts
|
|
531
|
+
import { NextResponse } from 'next/server';
|
|
532
|
+
import { createSDKFromEnv, unwrapList } from '@salefony/api-sdk';
|
|
533
|
+
import { getAdminSession } from '@/lib/admin-session';
|
|
534
|
+
|
|
535
|
+
export async function GET() {
|
|
536
|
+
const session = await getAdminSession();
|
|
537
|
+
if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
538
|
+
|
|
539
|
+
const sdk = createSDKFromEnv();
|
|
540
|
+
sdk.setAuthToken(session.accessToken);
|
|
541
|
+
|
|
542
|
+
const res = await sdk.admin.collections.list({
|
|
543
|
+
columns: ['id', 'slug', 'order', 'translations.*', 'parent.id'],
|
|
544
|
+
filter: { isActive: true },
|
|
545
|
+
sort: 'order:asc',
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
return NextResponse.json(unwrapList(res));
|
|
549
|
+
}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### Passing headers
|
|
553
|
+
|
|
554
|
+
```typescript
|
|
555
|
+
import { headers } from 'next/headers';
|
|
556
|
+
|
|
557
|
+
const h = await headers();
|
|
558
|
+
sdk.setHeaders({
|
|
559
|
+
cookie: h.get('cookie') ?? '',
|
|
560
|
+
'user-agent': h.get('user-agent') ?? '',
|
|
561
|
+
});
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
---
|
|
565
|
+
|
|
566
|
+
## 🛑 Error Handling
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
import {
|
|
570
|
+
SalefonyError,
|
|
571
|
+
SalefonyAuthError,
|
|
572
|
+
SalefonyNotFoundError,
|
|
573
|
+
SalefonyValidationError,
|
|
574
|
+
} from '@salefony/api-sdk';
|
|
575
|
+
|
|
576
|
+
try {
|
|
577
|
+
await sdk.admin.collections.get('invalid-id');
|
|
578
|
+
} catch (error) {
|
|
579
|
+
if (error instanceof SalefonyNotFoundError) {
|
|
580
|
+
console.error(error.message);
|
|
581
|
+
console.error(error.requestContext); // { method, url }
|
|
582
|
+
}
|
|
583
|
+
if (error instanceof SalefonyAuthError) {
|
|
584
|
+
// 401, 403
|
|
585
|
+
}
|
|
586
|
+
if (error instanceof SalefonyValidationError) {
|
|
587
|
+
console.error(error.details); // validation errors
|
|
588
|
+
}
|
|
589
|
+
if (error instanceof SalefonyError) {
|
|
590
|
+
console.error(error.statusCode, error.isClientError, error.isServerError);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
---
|
|
596
|
+
|
|
597
|
+
## 📂 API Reference
|
|
598
|
+
|
|
599
|
+
### CRUD methods
|
|
600
|
+
|
|
601
|
+
| Method | Signature | Returns |
|
|
602
|
+
|--------|-----------|---------|
|
|
603
|
+
| `get` | `(id \| GetParams) => Promise<ApiResponse<T>>` | Single record |
|
|
604
|
+
| `list` | `(ListParams?) => Promise<ApiResponse<T[]>>` | List + meta |
|
|
605
|
+
| `create` | `(data) => Promise<ApiResponse<T>>` | Created record |
|
|
606
|
+
| `edit` | `(id, data) => Promise<ApiResponse<T>>` | Updated record |
|
|
607
|
+
| `deleteById` | `(id) => Promise<void>` | void |
|
|
608
|
+
|
|
609
|
+
### GetParams
|
|
610
|
+
|
|
611
|
+
```typescript
|
|
612
|
+
interface GetParams {
|
|
613
|
+
id: string;
|
|
614
|
+
columns?: Column | Column[];
|
|
615
|
+
language?: string;
|
|
616
|
+
}
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
### ListParams
|
|
620
|
+
|
|
621
|
+
```typescript
|
|
622
|
+
interface ListParams {
|
|
623
|
+
columns?: Column | Column[];
|
|
624
|
+
filter?: Record<string, unknown>;
|
|
625
|
+
paginate?: { page?: number; limit?: number };
|
|
626
|
+
sort?: string;
|
|
627
|
+
search?: string;
|
|
628
|
+
language?: string;
|
|
629
|
+
take?: number; // Legacy
|
|
630
|
+
skip?: number; // Legacy
|
|
631
|
+
}
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
### ListMeta (pagination)
|
|
635
|
+
|
|
636
|
+
```typescript
|
|
637
|
+
interface ListMeta {
|
|
638
|
+
total?: number;
|
|
639
|
+
page?: number;
|
|
640
|
+
limit?: number;
|
|
641
|
+
totalPages?: number;
|
|
642
|
+
hasNextPage?: boolean;
|
|
643
|
+
hasPreviousPage?: boolean;
|
|
644
|
+
}
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
---
|
|
648
|
+
|
|
649
|
+
## 🔧 Troubleshooting
|
|
650
|
+
|
|
651
|
+
### "Record not found"
|
|
652
|
+
|
|
653
|
+
- Check that the ID exists and belongs to the current store.
|
|
654
|
+
- Verify auth token and store context.
|
|
655
|
+
|
|
656
|
+
### Columns not working
|
|
657
|
+
|
|
658
|
+
- Ensure backend supports `columns` (findMany/findUnique).
|
|
659
|
+
- Use correct dot-notation: `translations.*`, `parent.id`.
|
|
660
|
+
|
|
661
|
+
### CORS / network errors
|
|
662
|
+
|
|
663
|
+
- Ensure `baseUrl` is correct and reachable.
|
|
664
|
+
- SSR: use server-side SDK; do not call from client with sensitive tokens.
|
|
665
|
+
|
|
666
|
+
### Type errors with columns
|
|
667
|
+
|
|
668
|
+
- Use resource column helpers: `collectionColumns('id', 'slug', ...)`.
|
|
669
|
+
- Or `columns<CollectionColumn>('id', 'slug', ...)`.
|
|
670
|
+
|
|
671
|
+
---
|
|
672
|
+
|
|
673
|
+
## 📝 License
|
|
674
|
+
|
|
675
|
+
This project is proprietary and licensed for commercial use.
|