@storyblok/management-api-client 0.1.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/LICENSE +8 -0
- package/README.md +241 -0
- package/dist/_virtual/rolldown_runtime.js +25 -0
- package/dist/client/client.d.mts +7 -0
- package/dist/client/client.d.ts +7 -0
- package/dist/client/client.js +160 -0
- package/dist/client/client.js.map +1 -0
- package/dist/client/client.mjs +159 -0
- package/dist/client/client.mjs.map +1 -0
- package/dist/client/index.js +4 -0
- package/dist/client/index.mjs +4 -0
- package/dist/client/types.d.mts +116 -0
- package/dist/client/types.d.ts +116 -0
- package/dist/client/utils.d.mts +23 -0
- package/dist/client/utils.d.ts +23 -0
- package/dist/client/utils.js +242 -0
- package/dist/client/utils.js.map +1 -0
- package/dist/client/utils.mjs +236 -0
- package/dist/client/utils.mjs.map +1 -0
- package/dist/core/auth.d.mts +21 -0
- package/dist/core/auth.d.ts +21 -0
- package/dist/core/auth.js +13 -0
- package/dist/core/auth.js.map +1 -0
- package/dist/core/auth.mjs +12 -0
- package/dist/core/auth.mjs.map +1 -0
- package/dist/core/bodySerializer.d.mts +13 -0
- package/dist/core/bodySerializer.d.ts +13 -0
- package/dist/core/bodySerializer.js +7 -0
- package/dist/core/bodySerializer.js.map +1 -0
- package/dist/core/bodySerializer.mjs +6 -0
- package/dist/core/bodySerializer.mjs.map +1 -0
- package/dist/core/params.js +12 -0
- package/dist/core/params.js.map +1 -0
- package/dist/core/params.mjs +11 -0
- package/dist/core/params.mjs.map +1 -0
- package/dist/core/pathSerializer.d.mts +14 -0
- package/dist/core/pathSerializer.d.ts +14 -0
- package/dist/core/pathSerializer.js +85 -0
- package/dist/core/pathSerializer.js.map +1 -0
- package/dist/core/pathSerializer.mjs +82 -0
- package/dist/core/pathSerializer.mjs.map +1 -0
- package/dist/core/types.d.mts +78 -0
- package/dist/core/types.d.ts +78 -0
- package/dist/index.d.mts +50 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +84 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +82 -0
- package/dist/index.mjs.map +1 -0
- package/dist/sdk-registry.generated.d.mts +28 -0
- package/dist/sdk-registry.generated.d.ts +28 -0
- package/dist/sdk-registry.generated.js +26 -0
- package/dist/sdk-registry.generated.js.map +1 -0
- package/dist/sdk-registry.generated.mjs +26 -0
- package/dist/sdk-registry.generated.mjs.map +1 -0
- package/examples/index.ts +32 -0
- package/generate.ts +125 -0
- package/package.json +79 -0
- package/src/__tests__/integration.test.ts +143 -0
- package/src/__tests__/region-resolution.test.ts +89 -0
- package/src/client/client.ts +246 -0
- package/src/client/index.ts +22 -0
- package/src/client/types.ts +222 -0
- package/src/client/utils.ts +417 -0
- package/src/core/auth.ts +40 -0
- package/src/core/bodySerializer.ts +88 -0
- package/src/core/params.ts +151 -0
- package/src/core/pathSerializer.ts +179 -0
- package/src/core/types.ts +118 -0
- package/src/index.ts +129 -0
- package/src/shared-client.ts +13 -0
- package/tsconfig.json +25 -0
- package/tsdown.config.ts +21 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
Copyright © 2025 Storyblok GmbH
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
5
|
+
|
|
6
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
7
|
+
|
|
8
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# @storyblok/management-api-client
|
|
2
|
+
|
|
3
|
+
A comprehensive TypeScript SDK for the Storyblok Management API with automatic region resolution and built-in retry logic.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Type-Safe**: Generated from OpenAPI specifications with full TypeScript support
|
|
8
|
+
- **Region Resolution**: Automatic regional endpoint selection based on space ID
|
|
9
|
+
- **Retry Logic**: Built-in retry with exponential backoff and `retry-after` header support
|
|
10
|
+
- **Multi-Resource**: Unified client for many MAPI resources (stories, datasources, components, etc.)
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @storyblok/management-api-client
|
|
16
|
+
# or
|
|
17
|
+
pnpm add @storyblok/management-api-client
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { ManagementApiClient } from '@storyblok/management-api-client';
|
|
24
|
+
|
|
25
|
+
const client = new ManagementApiClient({
|
|
26
|
+
token: { accessToken: 'your-personal-access-token' },
|
|
27
|
+
// Optional configuration
|
|
28
|
+
region: 'us', // 'eu' | 'us' | 'ap' | 'ca' | 'cn'
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Get stories with full type safety
|
|
32
|
+
const stories = await client.stories.list({
|
|
33
|
+
path: { space_id: 123456 },
|
|
34
|
+
query: { per_page: 10 }
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Create a datasource
|
|
38
|
+
const datasource = await client.datasources.create({
|
|
39
|
+
path: { space_id: 123456 },
|
|
40
|
+
body: {
|
|
41
|
+
datasource: {
|
|
42
|
+
name: 'My Datasource',
|
|
43
|
+
slug: 'my-datasource'
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Authentication
|
|
50
|
+
|
|
51
|
+
### Personal Access Token
|
|
52
|
+
```typescript
|
|
53
|
+
const client = new ManagementApiClient({
|
|
54
|
+
token: { accessToken: 'your-personal-access-token' }
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### OAuth Token
|
|
59
|
+
```typescript
|
|
60
|
+
const client = new ManagementApiClient({
|
|
61
|
+
token: { oauthToken: 'your-oauth-token' }
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Configuration
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
interface ManagementApiClientConfig {
|
|
69
|
+
token: { accessToken: string } | { oauthToken: string };
|
|
70
|
+
region?: 'eu' | 'us' | 'ap' | 'ca' | 'cn'; // Auto-detected from space_id if not provided
|
|
71
|
+
baseUrl?: string; // Override automatic region resolution
|
|
72
|
+
headers?: Record<string, string>; // Additional headers
|
|
73
|
+
throwOnError?: boolean; // Throw on HTTP errors (default: false)
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Available Resources
|
|
78
|
+
|
|
79
|
+
The client provides access to all Storyblok Management API resources:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// Stories
|
|
83
|
+
await client.stories.list({ path: { space_id } });
|
|
84
|
+
await client.stories.get({ path: { space_id, story_id } });
|
|
85
|
+
await client.stories.create({ path: { space_id }, body: { story: {...} } });
|
|
86
|
+
await client.stories.updateStory({ path: { space_id, story_id }, body: { story: {...} } });
|
|
87
|
+
await client.stories.delete({ path: { space_id, story_id } });
|
|
88
|
+
|
|
89
|
+
// Datasources
|
|
90
|
+
await client.datasources.list({ path: { space_id } });
|
|
91
|
+
await client.datasources.create({ path: { space_id }, body: { datasource: {...} } });
|
|
92
|
+
|
|
93
|
+
// Components
|
|
94
|
+
await client.components.list({ path: { space_id } });
|
|
95
|
+
await client.components.create({ path: { space_id }, body: { component: {...} } });
|
|
96
|
+
|
|
97
|
+
// Spaces
|
|
98
|
+
await client.spaces.list({});
|
|
99
|
+
await client.spaces.get({ path: { space_id } });
|
|
100
|
+
|
|
101
|
+
// Datasource Entries
|
|
102
|
+
await client.datasourceEntries.list({ path: { space_id, datasource_id } });
|
|
103
|
+
|
|
104
|
+
// Internal Tags
|
|
105
|
+
await client.internalTags.list({ path: { space_id } });
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Region Resolution
|
|
109
|
+
|
|
110
|
+
The client automatically determines the correct regional endpoint based on your space ID:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// These will automatically route to the correct regional endpoints:
|
|
114
|
+
await client.stories.list({ path: { space_id: 564469716905585 } });
|
|
115
|
+
// → https://api-us.storyblok.com
|
|
116
|
+
|
|
117
|
+
await client.stories.list({ path: { space_id: 845944693616241 } });
|
|
118
|
+
// → https://api-ca.storyblok.com
|
|
119
|
+
|
|
120
|
+
await client.stories.list({ path: { space_id: 1127419670326897 } });
|
|
121
|
+
// → https://api-ap.storyblok.com
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Override Region Resolution
|
|
125
|
+
```typescript
|
|
126
|
+
const client = new ManagementApiClient({
|
|
127
|
+
token: { accessToken: 'your-token' },
|
|
128
|
+
baseUrl: 'https://custom-api.example.com' // Bypasses automatic region detection
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Retry Logic
|
|
133
|
+
|
|
134
|
+
The client includes built-in retry handling for rate limits and network errors:
|
|
135
|
+
|
|
136
|
+
- **429 Retry Handling**: Automatically retries on rate limit responses
|
|
137
|
+
- **Retry-After Support**: Respects `retry-after` headers from the API
|
|
138
|
+
- **Exponential Backoff**: Smart retry delays to avoid overwhelming the API
|
|
139
|
+
- **Network Error Retry**: Retries on network failures
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// The client automatically handles retries with these defaults:
|
|
143
|
+
// - maxRetries: 3
|
|
144
|
+
// - retryDelay: 1000ms
|
|
145
|
+
// - Respects retry-after headers from 429 responses
|
|
146
|
+
|
|
147
|
+
const stories = await client.stories.list({
|
|
148
|
+
path: { space_id: 123456 },
|
|
149
|
+
query: { per_page: 10 }
|
|
150
|
+
});
|
|
151
|
+
// If rate limited, will automatically retry up to 3 times
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Runtime Configuration
|
|
155
|
+
|
|
156
|
+
Update client configuration at runtime:
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// Update region/baseUrl
|
|
160
|
+
client.setConfig({
|
|
161
|
+
region: 'us',
|
|
162
|
+
headers: { 'Custom-Header': 'value' }
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Update authentication
|
|
166
|
+
client.setToken({ accessToken: 'new-token' });
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Error Handling
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
try {
|
|
173
|
+
const story = await client.stories.get({
|
|
174
|
+
path: { space_id: 123456, story_id: 'non-existent' }
|
|
175
|
+
});
|
|
176
|
+
} catch (error) {
|
|
177
|
+
if (error.status === 404) {
|
|
178
|
+
console.log('Story not found');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Or configure to not throw errors
|
|
183
|
+
const client = new ManagementApiClient({
|
|
184
|
+
token: { accessToken: 'your-token' },
|
|
185
|
+
throwOnError: false
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const result = await client.stories.get({
|
|
189
|
+
path: { space_id: 123456, story_id: 'non-existent' }
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
if (result.error) {
|
|
193
|
+
console.log('Error:', result.error);
|
|
194
|
+
} else {
|
|
195
|
+
console.log('Story:', result.data);
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## TypeScript Support
|
|
200
|
+
|
|
201
|
+
The client provides full TypeScript support with generated types:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
import type {
|
|
205
|
+
StoriesTypes,
|
|
206
|
+
DatasourcesTypes,
|
|
207
|
+
ComponentsTypes
|
|
208
|
+
} from '@storyblok/management-api-client';
|
|
209
|
+
|
|
210
|
+
// Full type safety for request/response data
|
|
211
|
+
const createStory = async (storyData: StoriesTypes.CreateRequestBody) => {
|
|
212
|
+
const response = await client.stories.create({
|
|
213
|
+
path: { space_id: 123456 },
|
|
214
|
+
body: storyData
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Response is fully typed
|
|
218
|
+
return response.data; // StoriesTypes.Story
|
|
219
|
+
};
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Development
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
# Generate SDKs from OpenAPI specs
|
|
226
|
+
pnpm generate
|
|
227
|
+
|
|
228
|
+
# Build the package
|
|
229
|
+
pnpm build
|
|
230
|
+
|
|
231
|
+
# Run tests
|
|
232
|
+
pnpm test
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Contributing
|
|
236
|
+
|
|
237
|
+
This package is generated from OpenAPI specifications in `packages/openapi/`. To add new endpoints or modify existing ones, update the OpenAPI specs and regenerate the client.
|
|
238
|
+
|
|
239
|
+
## License
|
|
240
|
+
|
|
241
|
+
MIT
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
|
|
25
|
+
exports.__toESM = __toESM;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.js');
|
|
2
|
+
const require_utils = require('./utils.js');
|
|
3
|
+
const __storyblok_region_helper = require_rolldown_runtime.__toESM(require("@storyblok/region-helper"));
|
|
4
|
+
|
|
5
|
+
//#region src/client/client.ts
|
|
6
|
+
const createClient = (config = {}) => {
|
|
7
|
+
let _config = require_utils.mergeConfigs(require_utils.createConfig(), config);
|
|
8
|
+
const getConfig = () => ({ ..._config });
|
|
9
|
+
const setConfig = (config$1) => {
|
|
10
|
+
_config = require_utils.mergeConfigs(_config, config$1);
|
|
11
|
+
return getConfig();
|
|
12
|
+
};
|
|
13
|
+
const interceptors = require_utils.createInterceptors();
|
|
14
|
+
const request = async (options) => {
|
|
15
|
+
const opts = {
|
|
16
|
+
..._config,
|
|
17
|
+
...options,
|
|
18
|
+
fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
|
|
19
|
+
headers: require_utils.mergeHeaders(_config.headers, options.headers)
|
|
20
|
+
};
|
|
21
|
+
if (!_config.baseUrl && options.path?.space_id) {
|
|
22
|
+
const region = (0, __storyblok_region_helper.getRegion)(options.path.space_id);
|
|
23
|
+
if (region) opts.baseUrl = (0, __storyblok_region_helper.getManagementBaseUrl)(region, "https");
|
|
24
|
+
}
|
|
25
|
+
if (opts.security) await require_utils.setAuthParams({
|
|
26
|
+
...opts,
|
|
27
|
+
security: opts.security
|
|
28
|
+
});
|
|
29
|
+
if (opts.requestValidator) await opts.requestValidator(opts);
|
|
30
|
+
if (opts.body && opts.bodySerializer) opts.body = opts.bodySerializer(opts.body);
|
|
31
|
+
if (opts.body === void 0 || opts.body === "") opts.headers.delete("Content-Type");
|
|
32
|
+
const url = require_utils.buildUrl(opts);
|
|
33
|
+
const requestInit = {
|
|
34
|
+
redirect: "follow",
|
|
35
|
+
...opts
|
|
36
|
+
};
|
|
37
|
+
let request$1 = new Request(url, requestInit);
|
|
38
|
+
for (const fn of interceptors.request._fns) if (fn) request$1 = await fn(request$1, opts);
|
|
39
|
+
const _fetch = opts.fetch;
|
|
40
|
+
let response = await executeWithRetry(_fetch, url, requestInit, {
|
|
41
|
+
maxRetries: 3,
|
|
42
|
+
retryDelay: 1e3
|
|
43
|
+
});
|
|
44
|
+
for (const fn of interceptors.response._fns) if (fn) response = await fn(response, request$1, opts);
|
|
45
|
+
const result = {
|
|
46
|
+
request: request$1,
|
|
47
|
+
response
|
|
48
|
+
};
|
|
49
|
+
if (response.ok) {
|
|
50
|
+
if (response.status === 204 || response.headers.get("Content-Length") === "0") return opts.responseStyle === "data" ? {} : {
|
|
51
|
+
data: {},
|
|
52
|
+
...result
|
|
53
|
+
};
|
|
54
|
+
const parseAs = (opts.parseAs === "auto" ? require_utils.getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json";
|
|
55
|
+
let data;
|
|
56
|
+
switch (parseAs) {
|
|
57
|
+
case "arrayBuffer":
|
|
58
|
+
case "blob":
|
|
59
|
+
case "formData":
|
|
60
|
+
case "json":
|
|
61
|
+
case "text":
|
|
62
|
+
data = await response[parseAs]();
|
|
63
|
+
break;
|
|
64
|
+
case "stream": return opts.responseStyle === "data" ? response.body : {
|
|
65
|
+
data: response.body,
|
|
66
|
+
...result
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (parseAs === "json") {
|
|
70
|
+
if (opts.responseValidator) await opts.responseValidator(data);
|
|
71
|
+
if (opts.responseTransformer) data = await opts.responseTransformer(data);
|
|
72
|
+
}
|
|
73
|
+
return opts.responseStyle === "data" ? data : {
|
|
74
|
+
data,
|
|
75
|
+
...result
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const textError = await response.text();
|
|
79
|
+
let jsonError;
|
|
80
|
+
try {
|
|
81
|
+
jsonError = JSON.parse(textError);
|
|
82
|
+
} catch {}
|
|
83
|
+
const error = jsonError ?? textError;
|
|
84
|
+
let finalError = error;
|
|
85
|
+
for (const fn of interceptors.error._fns) if (fn) finalError = await fn(error, response, request$1, opts);
|
|
86
|
+
finalError = finalError || {};
|
|
87
|
+
if (opts.throwOnError) throw finalError;
|
|
88
|
+
return opts.responseStyle === "data" ? void 0 : {
|
|
89
|
+
error: finalError,
|
|
90
|
+
...result
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
async function executeWithRetry(fetchFn, url, requestInit, retryConfig, attempt = 0) {
|
|
94
|
+
try {
|
|
95
|
+
const request$1 = new Request(url, requestInit);
|
|
96
|
+
const response = await fetchFn(request$1);
|
|
97
|
+
if (response.status === 429 && attempt < retryConfig.maxRetries) {
|
|
98
|
+
const retryAfter = response.headers.get("retry-after");
|
|
99
|
+
const delay = retryAfter ? parseInt(retryAfter) * 1e3 : retryConfig.retryDelay;
|
|
100
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
101
|
+
return executeWithRetry(fetchFn, url, requestInit, retryConfig, attempt + 1);
|
|
102
|
+
}
|
|
103
|
+
return response;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
if (attempt < retryConfig.maxRetries) {
|
|
106
|
+
const delay = retryConfig.retryDelay;
|
|
107
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
108
|
+
return executeWithRetry(fetchFn, url, requestInit, retryConfig, attempt + 1);
|
|
109
|
+
}
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
buildUrl: require_utils.buildUrl,
|
|
115
|
+
connect: (options) => request({
|
|
116
|
+
...options,
|
|
117
|
+
method: "CONNECT"
|
|
118
|
+
}),
|
|
119
|
+
delete: (options) => request({
|
|
120
|
+
...options,
|
|
121
|
+
method: "DELETE"
|
|
122
|
+
}),
|
|
123
|
+
get: (options) => request({
|
|
124
|
+
...options,
|
|
125
|
+
method: "GET"
|
|
126
|
+
}),
|
|
127
|
+
getConfig,
|
|
128
|
+
head: (options) => request({
|
|
129
|
+
...options,
|
|
130
|
+
method: "HEAD"
|
|
131
|
+
}),
|
|
132
|
+
interceptors,
|
|
133
|
+
options: (options) => request({
|
|
134
|
+
...options,
|
|
135
|
+
method: "OPTIONS"
|
|
136
|
+
}),
|
|
137
|
+
patch: (options) => request({
|
|
138
|
+
...options,
|
|
139
|
+
method: "PATCH"
|
|
140
|
+
}),
|
|
141
|
+
post: (options) => request({
|
|
142
|
+
...options,
|
|
143
|
+
method: "POST"
|
|
144
|
+
}),
|
|
145
|
+
put: (options) => request({
|
|
146
|
+
...options,
|
|
147
|
+
method: "PUT"
|
|
148
|
+
}),
|
|
149
|
+
request,
|
|
150
|
+
setConfig,
|
|
151
|
+
trace: (options) => request({
|
|
152
|
+
...options,
|
|
153
|
+
method: "TRACE"
|
|
154
|
+
})
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
//#endregion
|
|
159
|
+
exports.createClient = createClient;
|
|
160
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","names":["config: Config","mergeConfigs","createConfig","config","createInterceptors","request: Client['request']","mergeHeaders","setAuthParams","buildUrl","requestInit: ReqInit","request","getParseAs","data: any","jsonError: unknown","fetchFn: any","url: string","retryConfig: { maxRetries: number; retryDelay: number }","attempt: number"],"sources":["../../src/client/client.ts"],"sourcesContent":["import { getManagementBaseUrl, getRegion } from '@storyblok/region-helper';\nimport type { Client, Config, RequestOptions } from './types';\nimport {\n buildUrl,\n createConfig,\n createInterceptors,\n getParseAs,\n mergeConfigs,\n mergeHeaders,\n setAuthParams,\n} from './utils';\n\ntype ReqInit = Omit<RequestInit, 'body' | 'headers'> & {\n body?: any;\n headers: ReturnType<typeof mergeHeaders>;\n};\n\nexport const createClient = (config: Config = {}): Client => {\n let _config = mergeConfigs(createConfig(), config);\n\n const getConfig = (): Config => ({ ..._config });\n\n const setConfig = (config: Config): Config => {\n _config = mergeConfigs(_config, config);\n return getConfig();\n };\n\n const interceptors = createInterceptors<\n Request,\n Response,\n unknown,\n RequestOptions\n >();\n\n const request: Client['request'] = async (options) => {\n const opts = {\n ..._config,\n ...options,\n fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,\n headers: mergeHeaders(_config.headers, options.headers),\n };\n\n // If the baseUrl is not set and we have a space_id, we can attempt toinfer the region\n if (!_config.baseUrl && options.path?.space_id) {\n const region = getRegion(options.path.space_id as number);\n if (region) {\n opts.baseUrl = getManagementBaseUrl(region, 'https');\n }\n }\n\n if (opts.security) {\n await setAuthParams({\n ...opts,\n security: opts.security,\n });\n }\n\n if (opts.requestValidator) {\n await opts.requestValidator(opts);\n }\n\n if (opts.body && opts.bodySerializer) {\n opts.body = opts.bodySerializer(opts.body);\n }\n\n // remove Content-Type header if body is empty to avoid sending invalid requests\n if (opts.body === undefined || opts.body === '') {\n opts.headers.delete('Content-Type');\n }\n\n const url = buildUrl(opts);\n const requestInit: ReqInit = {\n redirect: 'follow',\n ...opts,\n };\n\n let request = new Request(url, requestInit);\n\n for (const fn of interceptors.request._fns) {\n if (fn) {\n request = await fn(request, opts);\n }\n }\n\n // fetch must be assigned here, otherwise it would throw the error:\n // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation\n const _fetch = opts.fetch!;\n \n // Execute with retry logic by recreating the request for each attempt\n let response = await executeWithRetry(_fetch, url, requestInit, {\n maxRetries: 3,\n retryDelay: 1000\n });\n\n for (const fn of interceptors.response._fns) {\n if (fn) {\n response = await fn(response, request, opts);\n }\n }\n\n const result = {\n request,\n response,\n };\n\n if (response.ok) {\n if (\n response.status === 204 ||\n response.headers.get('Content-Length') === '0'\n ) {\n return opts.responseStyle === 'data'\n ? {}\n : {\n data: {},\n ...result,\n };\n }\n\n const parseAs =\n (opts.parseAs === 'auto'\n ? getParseAs(response.headers.get('Content-Type'))\n : opts.parseAs) ?? 'json';\n\n let data: any;\n switch (parseAs) {\n case 'arrayBuffer':\n case 'blob':\n case 'formData':\n case 'json':\n case 'text':\n data = await response[parseAs]();\n break;\n case 'stream':\n return opts.responseStyle === 'data'\n ? response.body\n : {\n data: response.body,\n ...result,\n };\n }\n\n if (parseAs === 'json') {\n if (opts.responseValidator) {\n await opts.responseValidator(data);\n }\n\n if (opts.responseTransformer) {\n data = await opts.responseTransformer(data);\n }\n }\n\n return opts.responseStyle === 'data'\n ? data\n : {\n data,\n ...result,\n };\n }\n\n const textError = await response.text();\n let jsonError: unknown;\n\n try {\n jsonError = JSON.parse(textError);\n } catch {\n // noop\n }\n\n const error = jsonError ?? textError;\n let finalError = error;\n\n for (const fn of interceptors.error._fns) {\n if (fn) {\n finalError = (await fn(error, response, request, opts)) as string;\n }\n }\n\n finalError = finalError || ({} as string);\n\n if (opts.throwOnError) {\n throw finalError;\n }\n\n // TODO: we probably want to return error and improve types\n return opts.responseStyle === 'data'\n ? undefined\n : {\n error: finalError,\n ...result,\n };\n };\n\n // Helper function to execute fetch with retry logic\n async function executeWithRetry(\n fetchFn: any,\n url: string,\n requestInit: ReqInit,\n retryConfig: { maxRetries: number; retryDelay: number },\n attempt: number = 0\n ): Promise<Response> {\n try {\n const request = new Request(url, requestInit);\n const response = await fetchFn(request);\n \n if (response.status === 429 && attempt < retryConfig.maxRetries) {\n const retryAfter = response.headers.get('retry-after');\n const delay = retryAfter ? parseInt(retryAfter) * 1000 : retryConfig.retryDelay;\n \n await new Promise(resolve => setTimeout(resolve, delay));\n \n // Use the original unconsumed request for retry\n return executeWithRetry(fetchFn, url, requestInit, retryConfig, attempt + 1);\n }\n \n return response;\n } catch (error) {\n // If it's a network error and we haven't exceeded retries, try again\n if (attempt < retryConfig.maxRetries) {\n const delay = retryConfig.retryDelay;\n await new Promise(resolve => setTimeout(resolve, delay));\n \n // Use the original unconsumed request for retry\n return executeWithRetry(fetchFn, url, requestInit, retryConfig, attempt + 1);\n }\n \n throw error;\n }\n }\n\n return {\n buildUrl,\n connect: (options) => request({ ...options, method: 'CONNECT' }),\n delete: (options) => request({ ...options, method: 'DELETE' }),\n get: (options) => request({ ...options, method: 'GET' }),\n getConfig,\n head: (options) => request({ ...options, method: 'HEAD' }),\n interceptors,\n options: (options) => request({ ...options, method: 'OPTIONS' }),\n patch: (options) => request({ ...options, method: 'PATCH' }),\n post: (options) => request({ ...options, method: 'POST' }),\n put: (options) => request({ ...options, method: 'PUT' }),\n request,\n setConfig,\n trace: (options) => request({ ...options, method: 'TRACE' }),\n };\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { buildUrl, createConfig, createInterceptors, getParseAs, mergeConfigs, mergeHeaders, setAuthParams } from "./utils.mjs";
|
|
2
|
+
import { getManagementBaseUrl, getRegion } from "@storyblok/region-helper";
|
|
3
|
+
|
|
4
|
+
//#region src/client/client.ts
|
|
5
|
+
const createClient = (config = {}) => {
|
|
6
|
+
let _config = mergeConfigs(createConfig(), config);
|
|
7
|
+
const getConfig = () => ({ ..._config });
|
|
8
|
+
const setConfig = (config$1) => {
|
|
9
|
+
_config = mergeConfigs(_config, config$1);
|
|
10
|
+
return getConfig();
|
|
11
|
+
};
|
|
12
|
+
const interceptors = createInterceptors();
|
|
13
|
+
const request = async (options) => {
|
|
14
|
+
const opts = {
|
|
15
|
+
..._config,
|
|
16
|
+
...options,
|
|
17
|
+
fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
|
|
18
|
+
headers: mergeHeaders(_config.headers, options.headers)
|
|
19
|
+
};
|
|
20
|
+
if (!_config.baseUrl && options.path?.space_id) {
|
|
21
|
+
const region = getRegion(options.path.space_id);
|
|
22
|
+
if (region) opts.baseUrl = getManagementBaseUrl(region, "https");
|
|
23
|
+
}
|
|
24
|
+
if (opts.security) await setAuthParams({
|
|
25
|
+
...opts,
|
|
26
|
+
security: opts.security
|
|
27
|
+
});
|
|
28
|
+
if (opts.requestValidator) await opts.requestValidator(opts);
|
|
29
|
+
if (opts.body && opts.bodySerializer) opts.body = opts.bodySerializer(opts.body);
|
|
30
|
+
if (opts.body === void 0 || opts.body === "") opts.headers.delete("Content-Type");
|
|
31
|
+
const url = buildUrl(opts);
|
|
32
|
+
const requestInit = {
|
|
33
|
+
redirect: "follow",
|
|
34
|
+
...opts
|
|
35
|
+
};
|
|
36
|
+
let request$1 = new Request(url, requestInit);
|
|
37
|
+
for (const fn of interceptors.request._fns) if (fn) request$1 = await fn(request$1, opts);
|
|
38
|
+
const _fetch = opts.fetch;
|
|
39
|
+
let response = await executeWithRetry(_fetch, url, requestInit, {
|
|
40
|
+
maxRetries: 3,
|
|
41
|
+
retryDelay: 1e3
|
|
42
|
+
});
|
|
43
|
+
for (const fn of interceptors.response._fns) if (fn) response = await fn(response, request$1, opts);
|
|
44
|
+
const result = {
|
|
45
|
+
request: request$1,
|
|
46
|
+
response
|
|
47
|
+
};
|
|
48
|
+
if (response.ok) {
|
|
49
|
+
if (response.status === 204 || response.headers.get("Content-Length") === "0") return opts.responseStyle === "data" ? {} : {
|
|
50
|
+
data: {},
|
|
51
|
+
...result
|
|
52
|
+
};
|
|
53
|
+
const parseAs = (opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json";
|
|
54
|
+
let data;
|
|
55
|
+
switch (parseAs) {
|
|
56
|
+
case "arrayBuffer":
|
|
57
|
+
case "blob":
|
|
58
|
+
case "formData":
|
|
59
|
+
case "json":
|
|
60
|
+
case "text":
|
|
61
|
+
data = await response[parseAs]();
|
|
62
|
+
break;
|
|
63
|
+
case "stream": return opts.responseStyle === "data" ? response.body : {
|
|
64
|
+
data: response.body,
|
|
65
|
+
...result
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (parseAs === "json") {
|
|
69
|
+
if (opts.responseValidator) await opts.responseValidator(data);
|
|
70
|
+
if (opts.responseTransformer) data = await opts.responseTransformer(data);
|
|
71
|
+
}
|
|
72
|
+
return opts.responseStyle === "data" ? data : {
|
|
73
|
+
data,
|
|
74
|
+
...result
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const textError = await response.text();
|
|
78
|
+
let jsonError;
|
|
79
|
+
try {
|
|
80
|
+
jsonError = JSON.parse(textError);
|
|
81
|
+
} catch {}
|
|
82
|
+
const error = jsonError ?? textError;
|
|
83
|
+
let finalError = error;
|
|
84
|
+
for (const fn of interceptors.error._fns) if (fn) finalError = await fn(error, response, request$1, opts);
|
|
85
|
+
finalError = finalError || {};
|
|
86
|
+
if (opts.throwOnError) throw finalError;
|
|
87
|
+
return opts.responseStyle === "data" ? void 0 : {
|
|
88
|
+
error: finalError,
|
|
89
|
+
...result
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
async function executeWithRetry(fetchFn, url, requestInit, retryConfig, attempt = 0) {
|
|
93
|
+
try {
|
|
94
|
+
const request$1 = new Request(url, requestInit);
|
|
95
|
+
const response = await fetchFn(request$1);
|
|
96
|
+
if (response.status === 429 && attempt < retryConfig.maxRetries) {
|
|
97
|
+
const retryAfter = response.headers.get("retry-after");
|
|
98
|
+
const delay = retryAfter ? parseInt(retryAfter) * 1e3 : retryConfig.retryDelay;
|
|
99
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
100
|
+
return executeWithRetry(fetchFn, url, requestInit, retryConfig, attempt + 1);
|
|
101
|
+
}
|
|
102
|
+
return response;
|
|
103
|
+
} catch (error) {
|
|
104
|
+
if (attempt < retryConfig.maxRetries) {
|
|
105
|
+
const delay = retryConfig.retryDelay;
|
|
106
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
107
|
+
return executeWithRetry(fetchFn, url, requestInit, retryConfig, attempt + 1);
|
|
108
|
+
}
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
buildUrl,
|
|
114
|
+
connect: (options) => request({
|
|
115
|
+
...options,
|
|
116
|
+
method: "CONNECT"
|
|
117
|
+
}),
|
|
118
|
+
delete: (options) => request({
|
|
119
|
+
...options,
|
|
120
|
+
method: "DELETE"
|
|
121
|
+
}),
|
|
122
|
+
get: (options) => request({
|
|
123
|
+
...options,
|
|
124
|
+
method: "GET"
|
|
125
|
+
}),
|
|
126
|
+
getConfig,
|
|
127
|
+
head: (options) => request({
|
|
128
|
+
...options,
|
|
129
|
+
method: "HEAD"
|
|
130
|
+
}),
|
|
131
|
+
interceptors,
|
|
132
|
+
options: (options) => request({
|
|
133
|
+
...options,
|
|
134
|
+
method: "OPTIONS"
|
|
135
|
+
}),
|
|
136
|
+
patch: (options) => request({
|
|
137
|
+
...options,
|
|
138
|
+
method: "PATCH"
|
|
139
|
+
}),
|
|
140
|
+
post: (options) => request({
|
|
141
|
+
...options,
|
|
142
|
+
method: "POST"
|
|
143
|
+
}),
|
|
144
|
+
put: (options) => request({
|
|
145
|
+
...options,
|
|
146
|
+
method: "PUT"
|
|
147
|
+
}),
|
|
148
|
+
request,
|
|
149
|
+
setConfig,
|
|
150
|
+
trace: (options) => request({
|
|
151
|
+
...options,
|
|
152
|
+
method: "TRACE"
|
|
153
|
+
})
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
//#endregion
|
|
158
|
+
export { createClient };
|
|
159
|
+
//# sourceMappingURL=client.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.mjs","names":["config: Config","config","request: Client['request']","requestInit: ReqInit","request","data: any","jsonError: unknown","fetchFn: any","url: string","retryConfig: { maxRetries: number; retryDelay: number }","attempt: number"],"sources":["../../src/client/client.ts"],"sourcesContent":["import { getManagementBaseUrl, getRegion } from '@storyblok/region-helper';\nimport type { Client, Config, RequestOptions } from './types';\nimport {\n buildUrl,\n createConfig,\n createInterceptors,\n getParseAs,\n mergeConfigs,\n mergeHeaders,\n setAuthParams,\n} from './utils';\n\ntype ReqInit = Omit<RequestInit, 'body' | 'headers'> & {\n body?: any;\n headers: ReturnType<typeof mergeHeaders>;\n};\n\nexport const createClient = (config: Config = {}): Client => {\n let _config = mergeConfigs(createConfig(), config);\n\n const getConfig = (): Config => ({ ..._config });\n\n const setConfig = (config: Config): Config => {\n _config = mergeConfigs(_config, config);\n return getConfig();\n };\n\n const interceptors = createInterceptors<\n Request,\n Response,\n unknown,\n RequestOptions\n >();\n\n const request: Client['request'] = async (options) => {\n const opts = {\n ..._config,\n ...options,\n fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,\n headers: mergeHeaders(_config.headers, options.headers),\n };\n\n // If the baseUrl is not set and we have a space_id, we can attempt toinfer the region\n if (!_config.baseUrl && options.path?.space_id) {\n const region = getRegion(options.path.space_id as number);\n if (region) {\n opts.baseUrl = getManagementBaseUrl(region, 'https');\n }\n }\n\n if (opts.security) {\n await setAuthParams({\n ...opts,\n security: opts.security,\n });\n }\n\n if (opts.requestValidator) {\n await opts.requestValidator(opts);\n }\n\n if (opts.body && opts.bodySerializer) {\n opts.body = opts.bodySerializer(opts.body);\n }\n\n // remove Content-Type header if body is empty to avoid sending invalid requests\n if (opts.body === undefined || opts.body === '') {\n opts.headers.delete('Content-Type');\n }\n\n const url = buildUrl(opts);\n const requestInit: ReqInit = {\n redirect: 'follow',\n ...opts,\n };\n\n let request = new Request(url, requestInit);\n\n for (const fn of interceptors.request._fns) {\n if (fn) {\n request = await fn(request, opts);\n }\n }\n\n // fetch must be assigned here, otherwise it would throw the error:\n // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation\n const _fetch = opts.fetch!;\n \n // Execute with retry logic by recreating the request for each attempt\n let response = await executeWithRetry(_fetch, url, requestInit, {\n maxRetries: 3,\n retryDelay: 1000\n });\n\n for (const fn of interceptors.response._fns) {\n if (fn) {\n response = await fn(response, request, opts);\n }\n }\n\n const result = {\n request,\n response,\n };\n\n if (response.ok) {\n if (\n response.status === 204 ||\n response.headers.get('Content-Length') === '0'\n ) {\n return opts.responseStyle === 'data'\n ? {}\n : {\n data: {},\n ...result,\n };\n }\n\n const parseAs =\n (opts.parseAs === 'auto'\n ? getParseAs(response.headers.get('Content-Type'))\n : opts.parseAs) ?? 'json';\n\n let data: any;\n switch (parseAs) {\n case 'arrayBuffer':\n case 'blob':\n case 'formData':\n case 'json':\n case 'text':\n data = await response[parseAs]();\n break;\n case 'stream':\n return opts.responseStyle === 'data'\n ? response.body\n : {\n data: response.body,\n ...result,\n };\n }\n\n if (parseAs === 'json') {\n if (opts.responseValidator) {\n await opts.responseValidator(data);\n }\n\n if (opts.responseTransformer) {\n data = await opts.responseTransformer(data);\n }\n }\n\n return opts.responseStyle === 'data'\n ? data\n : {\n data,\n ...result,\n };\n }\n\n const textError = await response.text();\n let jsonError: unknown;\n\n try {\n jsonError = JSON.parse(textError);\n } catch {\n // noop\n }\n\n const error = jsonError ?? textError;\n let finalError = error;\n\n for (const fn of interceptors.error._fns) {\n if (fn) {\n finalError = (await fn(error, response, request, opts)) as string;\n }\n }\n\n finalError = finalError || ({} as string);\n\n if (opts.throwOnError) {\n throw finalError;\n }\n\n // TODO: we probably want to return error and improve types\n return opts.responseStyle === 'data'\n ? undefined\n : {\n error: finalError,\n ...result,\n };\n };\n\n // Helper function to execute fetch with retry logic\n async function executeWithRetry(\n fetchFn: any,\n url: string,\n requestInit: ReqInit,\n retryConfig: { maxRetries: number; retryDelay: number },\n attempt: number = 0\n ): Promise<Response> {\n try {\n const request = new Request(url, requestInit);\n const response = await fetchFn(request);\n \n if (response.status === 429 && attempt < retryConfig.maxRetries) {\n const retryAfter = response.headers.get('retry-after');\n const delay = retryAfter ? parseInt(retryAfter) * 1000 : retryConfig.retryDelay;\n \n await new Promise(resolve => setTimeout(resolve, delay));\n \n // Use the original unconsumed request for retry\n return executeWithRetry(fetchFn, url, requestInit, retryConfig, attempt + 1);\n }\n \n return response;\n } catch (error) {\n // If it's a network error and we haven't exceeded retries, try again\n if (attempt < retryConfig.maxRetries) {\n const delay = retryConfig.retryDelay;\n await new Promise(resolve => setTimeout(resolve, delay));\n \n // Use the original unconsumed request for retry\n return executeWithRetry(fetchFn, url, requestInit, retryConfig, attempt + 1);\n }\n \n throw error;\n }\n }\n\n return {\n buildUrl,\n connect: (options) => request({ ...options, method: 'CONNECT' }),\n delete: (options) => request({ ...options, method: 'DELETE' }),\n get: (options) => request({ ...options, method: 'GET' }),\n getConfig,\n head: (options) => request({ ...options, method: 'HEAD' }),\n interceptors,\n options: (options) => request({ ...options, method: 'OPTIONS' }),\n patch: (options) => request({ ...options, method: 'PATCH' }),\n post: (options) => request({ ...options, method: 'POST' }),\n put: (options) => request({ ...options, method: 'PUT' }),\n request,\n setConfig,\n trace: (options) => request({ ...options, method: 'TRACE' }),\n };\n};\n"],"mappings":"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"}
|