@storyblok/api-client 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/generated/datasource_entries/sdk.gen.cjs +2 -2
- package/dist/generated/datasource_entries/sdk.gen.cjs.map +1 -1
- package/dist/generated/datasource_entries/sdk.gen.mjs +2 -2
- package/dist/generated/datasource_entries/sdk.gen.mjs.map +1 -1
- package/dist/generated/datasource_entries/types.gen.d.cts +3 -3
- package/dist/generated/datasource_entries/types.gen.d.mts +3 -3
- package/dist/generated/datasources/sdk.gen.cjs +2 -2
- package/dist/generated/datasources/sdk.gen.cjs.map +1 -1
- package/dist/generated/datasources/sdk.gen.mjs +2 -2
- package/dist/generated/datasources/sdk.gen.mjs.map +1 -1
- package/dist/generated/datasources/types.gen.d.cts +3 -3
- package/dist/generated/datasources/types.gen.d.mts +3 -3
- package/dist/generated/links/sdk.gen.cjs +2 -2
- package/dist/generated/links/sdk.gen.cjs.map +1 -1
- package/dist/generated/links/sdk.gen.mjs +2 -2
- package/dist/generated/links/sdk.gen.mjs.map +1 -1
- package/dist/generated/links/types.gen.d.cts +3 -3
- package/dist/generated/links/types.gen.d.mts +3 -3
- package/dist/generated/stories/sdk.gen.cjs +2 -2
- package/dist/generated/stories/sdk.gen.cjs.map +1 -1
- package/dist/generated/stories/sdk.gen.mjs +2 -2
- package/dist/generated/stories/sdk.gen.mjs.map +1 -1
- package/dist/generated/stories/types.gen.d.cts +6 -6
- package/dist/generated/stories/types.gen.d.mts +6 -6
- package/dist/generated/tags/sdk.gen.cjs +2 -2
- package/dist/generated/tags/sdk.gen.cjs.map +1 -1
- package/dist/generated/tags/sdk.gen.mjs +2 -2
- package/dist/generated/tags/sdk.gen.mjs.map +1 -1
- package/dist/generated/tags/types.gen.d.cts +3 -3
- package/dist/generated/tags/types.gen.d.mts +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -20
- package/dist/index.d.mts +20 -20
- package/dist/index.mjs.map +1 -1
- package/dist/resources/datasource-entries.cjs +2 -2
- package/dist/resources/datasource-entries.cjs.map +1 -1
- package/dist/resources/datasource-entries.mjs +3 -3
- package/dist/resources/datasource-entries.mjs.map +1 -1
- package/dist/resources/datasources.cjs +2 -2
- package/dist/resources/datasources.cjs.map +1 -1
- package/dist/resources/datasources.mjs +3 -3
- package/dist/resources/datasources.mjs.map +1 -1
- package/dist/resources/links.cjs +2 -2
- package/dist/resources/links.cjs.map +1 -1
- package/dist/resources/links.mjs +3 -3
- package/dist/resources/links.mjs.map +1 -1
- package/dist/resources/stories.cjs +2 -2
- package/dist/resources/stories.cjs.map +1 -1
- package/dist/resources/stories.mjs +3 -3
- package/dist/resources/stories.mjs.map +1 -1
- package/dist/resources/tags.cjs +2 -2
- package/dist/resources/tags.cjs.map +1 -1
- package/dist/resources/tags.mjs +3 -3
- package/dist/resources/tags.mjs.map +1 -1
- package/dist/utils/fetch-rel-uuids.cjs +1 -1
- package/dist/utils/fetch-rel-uuids.cjs.map +1 -1
- package/dist/utils/fetch-rel-uuids.mjs +2 -2
- package/dist/utils/fetch-rel-uuids.mjs.map +1 -1
- package/dist/utils/rate-limit.cjs +4 -4
- package/dist/utils/rate-limit.cjs.map +1 -1
- package/dist/utils/rate-limit.d.cts +1 -1
- package/dist/utils/rate-limit.d.mts +1 -1
- package/dist/utils/rate-limit.mjs +4 -4
- package/dist/utils/rate-limit.mjs.map +1 -1
- package/package.json +3 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stories.cjs","names":["get","resolveRelationMap","inlineStoryContent","
|
|
1
|
+
{"version":3,"file":"stories.cjs","names":["get","resolveRelationMap","inlineStoryContent","list","inlineStoriesContent"],"sources":["../../src/resources/stories.ts"],"sourcesContent":["import { get, list } from '../generated/stories/sdk.gen';\nimport type { GetData, GetResponses, ListData, ListResponses } from '../generated/stories/types.gen';\nimport type {\n AssetField,\n MultilinkField,\n PluginField,\n RichtextField,\n StoryCapi,\n StoryContent,\n TableField,\n} from '../generated/stories';\nimport { inlineStoriesContent, inlineStoryContent, resolveRelationMap } from '../utils/inline-relations';\nimport type { ApiResponse, FetchOptions, ResourceDeps } from '../types';\n\ntype InlinedStoryContentField =\n | string\n | number\n | boolean\n | Array<string | AssetField | StoryContent | StoryWithInlinedRelations>\n | AssetField\n | MultilinkField\n | TableField\n | RichtextField\n | PluginField\n | StoryWithInlinedRelations\n | undefined;\n\ninterface InlinedStoryContent {\n _uid: string;\n component: string;\n _editable?: string;\n [key: string]: InlinedStoryContentField;\n}\n\nexport type StoryWithInlinedRelations = Omit<StoryCapi, 'content'> & {\n content: InlinedStoryContent;\n};\n\ntype StoryResult<InlineRelations extends boolean> = InlineRelations extends true\n ? StoryWithInlinedRelations\n : StoryCapi;\n\ntype GetResponse<InlineRelations extends boolean> = Omit<GetResponses[200], 'story'> & {\n story: StoryResult<InlineRelations>;\n};\ntype ListResponse<InlineRelations extends boolean> = Omit<ListResponses[200], 'stories'> & {\n stories: Array<StoryResult<InlineRelations>>;\n};\n\n/** Pre-resolved to avoid TypeScript emitting deep indexed-access chains that trip up DTS bundlers. */\ntype StoryIdentifier = GetData['path']['identifier'];\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nexport interface StoriesResourceDeps extends ResourceDeps {\n inlineRelations: boolean;\n}\n\nexport function createStoriesResource<InlineRelations extends boolean>(\n deps: StoriesResourceDeps,\n) {\n const { client, requestWithCache, asApiResponse, inlineRelations, throttleManager } = deps;\n\n return {\n get: async <ThrowOnError extends boolean = false>(\n identifier: StoryIdentifier,\n options: { query?: GetData['query']; signal?: AbortSignal; throwOnError?: ThrowOnError; fetchOptions?: FetchOptions } = {},\n ): Promise<ApiResponse<GetResponse<InlineRelations>, ThrowOnError>> => {\n const { query = {}, signal, throwOnError, fetchOptions } = options;\n const resolvedQuery = typeof identifier === 'string' && UUID_RE.test(identifier) && !query.find_by\n ? { ...query, find_by: 'uuid' as const }\n : query;\n const requestPath = `/v2/cdn/stories/${identifier}`;\n type Res = ApiResponse<GetResponse<InlineRelations>, ThrowOnError>;\n return requestWithCache<GetResponse<InlineRelations>, ThrowOnError>('GET', requestPath, resolvedQuery, async (requestQuery: Record<string, unknown>): Promise<Res> => {\n const response = await throttleManager.execute(requestPath, requestQuery, () =>\n asApiResponse<GetResponse<InlineRelations>, ThrowOnError>(get({\n client,\n path: { identifier },\n query: requestQuery,\n signal,\n ...(throwOnError === undefined ? {} : { throwOnError }),\n ...(fetchOptions ? { kyOptions: { ...client.getConfig().kyOptions, ...fetchOptions } } : {}),\n })));\n\n if (!inlineRelations || response.data === undefined) {\n return response;\n }\n\n const resolved = await resolveRelationMap(response.data, requestQuery, { client, throttleManager });\n if (!resolved) {\n return response;\n }\n\n return {\n ...response,\n data: {\n ...response.data,\n story: inlineStoryContent(response.data.story, resolved.relationPaths, resolved.relationMap),\n },\n };\n }, inlineRelations ? { cacheKeyPrefix: 'inline' } : undefined);\n },\n\n list: async <ThrowOnError extends boolean = false>(\n options: { query?: ListData['query']; signal?: AbortSignal; throwOnError?: ThrowOnError; fetchOptions?: FetchOptions } = {},\n ): Promise<ApiResponse<ListResponse<InlineRelations>, ThrowOnError>> => {\n const { query = {}, signal, throwOnError, fetchOptions } = options;\n const requestPath = '/v2/cdn/stories';\n type ResAll = ApiResponse<ListResponse<InlineRelations>, ThrowOnError>;\n return requestWithCache<ListResponse<InlineRelations>, ThrowOnError>('GET', requestPath, query, async (requestQuery: Record<string, unknown>): Promise<ResAll> => {\n const response = await throttleManager.execute(requestPath, requestQuery, () =>\n asApiResponse<ListResponse<InlineRelations>, ThrowOnError>(list({\n client,\n query: requestQuery,\n signal,\n ...(throwOnError === undefined ? {} : { throwOnError }),\n ...(fetchOptions ? { kyOptions: { ...client.getConfig().kyOptions, ...fetchOptions } } : {}),\n })));\n\n if (!inlineRelations || response.data === undefined) {\n return response;\n }\n\n const resolved = await resolveRelationMap(response.data, requestQuery, { client, throttleManager });\n if (!resolved) {\n return response;\n }\n\n return {\n ...response,\n data: {\n ...response.data,\n stories: inlineStoriesContent(response.data.stories, resolved.relationPaths, resolved.relationMap),\n },\n };\n }, inlineRelations ? { cacheKeyPrefix: 'inline' } : undefined);\n },\n };\n}\n"],"mappings":";;;;AAoDA,MAAM,UAAU;AAMhB,SAAgB,sBACd,MACA;CACA,MAAM,EAAE,QAAQ,kBAAkB,eAAe,iBAAiB,oBAAoB;AAEtF,QAAO;EACL,KAAK,OACH,YACA,UAAwH,EAAE,KACrD;GACrE,MAAM,EAAE,QAAQ,EAAE,EAAE,QAAQ,cAAc,iBAAiB;GAC3D,MAAM,gBAAgB,OAAO,eAAe,YAAY,QAAQ,KAAK,WAAW,IAAI,CAAC,MAAM,UACvF;IAAE,GAAG;IAAO,SAAS;IAAiB,GACtC;GACJ,MAAM,cAAc,mBAAmB;AAEvC,UAAO,iBAA6D,OAAO,aAAa,eAAe,OAAO,iBAAwD;IACpK,MAAM,WAAW,MAAM,gBAAgB,QAAQ,aAAa,oBAC1D,cAA0DA,oBAAI;KAC5D;KACA,MAAM,EAAE,YAAY;KACpB,OAAO;KACP;KACA,GAAI,iBAAiB,SAAY,EAAE,GAAG,EAAE,cAAc;KACtD,GAAI,eAAe,EAAE,WAAW;MAAE,GAAG,OAAO,WAAW,CAAC;MAAW,GAAG;MAAc,EAAE,GAAG,EAAE;KAC5F,CAAC,CAAC,CAAC;AAEN,QAAI,CAAC,mBAAmB,SAAS,SAAS,OACxC,QAAO;IAGT,MAAM,WAAW,MAAMC,4CAAmB,SAAS,MAAM,cAAc;KAAE;KAAQ;KAAiB,CAAC;AACnG,QAAI,CAAC,SACH,QAAO;AAGT,WAAO;KACL,GAAG;KACH,MAAM;MACJ,GAAG,SAAS;MACZ,OAAOC,4CAAmB,SAAS,KAAK,OAAO,SAAS,eAAe,SAAS,YAAY;MAC7F;KACF;MACA,kBAAkB,EAAE,gBAAgB,UAAU,GAAG,OAAU;;EAGhE,MAAM,OACJ,UAAyH,EAAE,KACrD;GACtE,MAAM,EAAE,QAAQ,EAAE,EAAE,QAAQ,cAAc,iBAAiB;GAC3D,MAAM,cAAc;AAEpB,UAAO,iBAA8D,OAAO,aAAa,OAAO,OAAO,iBAA2D;IAChK,MAAM,WAAW,MAAM,gBAAgB,QAAQ,aAAa,oBAC1D,cAA2DC,qBAAK;KAC9D;KACA,OAAO;KACP;KACA,GAAI,iBAAiB,SAAY,EAAE,GAAG,EAAE,cAAc;KACtD,GAAI,eAAe,EAAE,WAAW;MAAE,GAAG,OAAO,WAAW,CAAC;MAAW,GAAG;MAAc,EAAE,GAAG,EAAE;KAC5F,CAAC,CAAC,CAAC;AAEN,QAAI,CAAC,mBAAmB,SAAS,SAAS,OACxC,QAAO;IAGT,MAAM,WAAW,MAAMF,4CAAmB,SAAS,MAAM,cAAc;KAAE;KAAQ;KAAiB,CAAC;AACnG,QAAI,CAAC,SACH,QAAO;AAGT,WAAO;KACL,GAAG;KACH,MAAM;MACJ,GAAG,SAAS;MACZ,SAASG,8CAAqB,SAAS,KAAK,SAAS,SAAS,eAAe,SAAS,YAAY;MACnG;KACF;MACA,kBAAkB,EAAE,gBAAgB,UAAU,GAAG,OAAU;;EAEjE"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { get,
|
|
1
|
+
import { get, list } from "../generated/stories/sdk.gen.mjs";
|
|
2
2
|
import { inlineStoriesContent, inlineStoryContent, resolveRelationMap } from "../utils/inline-relations.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/resources/stories.ts
|
|
@@ -40,11 +40,11 @@ function createStoriesResource(deps) {
|
|
|
40
40
|
};
|
|
41
41
|
}, inlineRelations ? { cacheKeyPrefix: "inline" } : void 0);
|
|
42
42
|
},
|
|
43
|
-
|
|
43
|
+
list: async (options = {}) => {
|
|
44
44
|
const { query = {}, signal, throwOnError, fetchOptions } = options;
|
|
45
45
|
const requestPath = "/v2/cdn/stories";
|
|
46
46
|
return requestWithCache("GET", requestPath, query, async (requestQuery) => {
|
|
47
|
-
const response = await throttleManager.execute(requestPath, requestQuery, () => asApiResponse(
|
|
47
|
+
const response = await throttleManager.execute(requestPath, requestQuery, () => asApiResponse(list({
|
|
48
48
|
client,
|
|
49
49
|
query: requestQuery,
|
|
50
50
|
signal,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stories.mjs","names":[],"sources":["../../src/resources/stories.ts"],"sourcesContent":["import { get,
|
|
1
|
+
{"version":3,"file":"stories.mjs","names":[],"sources":["../../src/resources/stories.ts"],"sourcesContent":["import { get, list } from '../generated/stories/sdk.gen';\nimport type { GetData, GetResponses, ListData, ListResponses } from '../generated/stories/types.gen';\nimport type {\n AssetField,\n MultilinkField,\n PluginField,\n RichtextField,\n StoryCapi,\n StoryContent,\n TableField,\n} from '../generated/stories';\nimport { inlineStoriesContent, inlineStoryContent, resolveRelationMap } from '../utils/inline-relations';\nimport type { ApiResponse, FetchOptions, ResourceDeps } from '../types';\n\ntype InlinedStoryContentField =\n | string\n | number\n | boolean\n | Array<string | AssetField | StoryContent | StoryWithInlinedRelations>\n | AssetField\n | MultilinkField\n | TableField\n | RichtextField\n | PluginField\n | StoryWithInlinedRelations\n | undefined;\n\ninterface InlinedStoryContent {\n _uid: string;\n component: string;\n _editable?: string;\n [key: string]: InlinedStoryContentField;\n}\n\nexport type StoryWithInlinedRelations = Omit<StoryCapi, 'content'> & {\n content: InlinedStoryContent;\n};\n\ntype StoryResult<InlineRelations extends boolean> = InlineRelations extends true\n ? StoryWithInlinedRelations\n : StoryCapi;\n\ntype GetResponse<InlineRelations extends boolean> = Omit<GetResponses[200], 'story'> & {\n story: StoryResult<InlineRelations>;\n};\ntype ListResponse<InlineRelations extends boolean> = Omit<ListResponses[200], 'stories'> & {\n stories: Array<StoryResult<InlineRelations>>;\n};\n\n/** Pre-resolved to avoid TypeScript emitting deep indexed-access chains that trip up DTS bundlers. */\ntype StoryIdentifier = GetData['path']['identifier'];\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nexport interface StoriesResourceDeps extends ResourceDeps {\n inlineRelations: boolean;\n}\n\nexport function createStoriesResource<InlineRelations extends boolean>(\n deps: StoriesResourceDeps,\n) {\n const { client, requestWithCache, asApiResponse, inlineRelations, throttleManager } = deps;\n\n return {\n get: async <ThrowOnError extends boolean = false>(\n identifier: StoryIdentifier,\n options: { query?: GetData['query']; signal?: AbortSignal; throwOnError?: ThrowOnError; fetchOptions?: FetchOptions } = {},\n ): Promise<ApiResponse<GetResponse<InlineRelations>, ThrowOnError>> => {\n const { query = {}, signal, throwOnError, fetchOptions } = options;\n const resolvedQuery = typeof identifier === 'string' && UUID_RE.test(identifier) && !query.find_by\n ? { ...query, find_by: 'uuid' as const }\n : query;\n const requestPath = `/v2/cdn/stories/${identifier}`;\n type Res = ApiResponse<GetResponse<InlineRelations>, ThrowOnError>;\n return requestWithCache<GetResponse<InlineRelations>, ThrowOnError>('GET', requestPath, resolvedQuery, async (requestQuery: Record<string, unknown>): Promise<Res> => {\n const response = await throttleManager.execute(requestPath, requestQuery, () =>\n asApiResponse<GetResponse<InlineRelations>, ThrowOnError>(get({\n client,\n path: { identifier },\n query: requestQuery,\n signal,\n ...(throwOnError === undefined ? {} : { throwOnError }),\n ...(fetchOptions ? { kyOptions: { ...client.getConfig().kyOptions, ...fetchOptions } } : {}),\n })));\n\n if (!inlineRelations || response.data === undefined) {\n return response;\n }\n\n const resolved = await resolveRelationMap(response.data, requestQuery, { client, throttleManager });\n if (!resolved) {\n return response;\n }\n\n return {\n ...response,\n data: {\n ...response.data,\n story: inlineStoryContent(response.data.story, resolved.relationPaths, resolved.relationMap),\n },\n };\n }, inlineRelations ? { cacheKeyPrefix: 'inline' } : undefined);\n },\n\n list: async <ThrowOnError extends boolean = false>(\n options: { query?: ListData['query']; signal?: AbortSignal; throwOnError?: ThrowOnError; fetchOptions?: FetchOptions } = {},\n ): Promise<ApiResponse<ListResponse<InlineRelations>, ThrowOnError>> => {\n const { query = {}, signal, throwOnError, fetchOptions } = options;\n const requestPath = '/v2/cdn/stories';\n type ResAll = ApiResponse<ListResponse<InlineRelations>, ThrowOnError>;\n return requestWithCache<ListResponse<InlineRelations>, ThrowOnError>('GET', requestPath, query, async (requestQuery: Record<string, unknown>): Promise<ResAll> => {\n const response = await throttleManager.execute(requestPath, requestQuery, () =>\n asApiResponse<ListResponse<InlineRelations>, ThrowOnError>(list({\n client,\n query: requestQuery,\n signal,\n ...(throwOnError === undefined ? {} : { throwOnError }),\n ...(fetchOptions ? { kyOptions: { ...client.getConfig().kyOptions, ...fetchOptions } } : {}),\n })));\n\n if (!inlineRelations || response.data === undefined) {\n return response;\n }\n\n const resolved = await resolveRelationMap(response.data, requestQuery, { client, throttleManager });\n if (!resolved) {\n return response;\n }\n\n return {\n ...response,\n data: {\n ...response.data,\n stories: inlineStoriesContent(response.data.stories, resolved.relationPaths, resolved.relationMap),\n },\n };\n }, inlineRelations ? { cacheKeyPrefix: 'inline' } : undefined);\n },\n };\n}\n"],"mappings":";;;;AAoDA,MAAM,UAAU;AAMhB,SAAgB,sBACd,MACA;CACA,MAAM,EAAE,QAAQ,kBAAkB,eAAe,iBAAiB,oBAAoB;AAEtF,QAAO;EACL,KAAK,OACH,YACA,UAAwH,EAAE,KACrD;GACrE,MAAM,EAAE,QAAQ,EAAE,EAAE,QAAQ,cAAc,iBAAiB;GAC3D,MAAM,gBAAgB,OAAO,eAAe,YAAY,QAAQ,KAAK,WAAW,IAAI,CAAC,MAAM,UACvF;IAAE,GAAG;IAAO,SAAS;IAAiB,GACtC;GACJ,MAAM,cAAc,mBAAmB;AAEvC,UAAO,iBAA6D,OAAO,aAAa,eAAe,OAAO,iBAAwD;IACpK,MAAM,WAAW,MAAM,gBAAgB,QAAQ,aAAa,oBAC1D,cAA0D,IAAI;KAC5D;KACA,MAAM,EAAE,YAAY;KACpB,OAAO;KACP;KACA,GAAI,iBAAiB,SAAY,EAAE,GAAG,EAAE,cAAc;KACtD,GAAI,eAAe,EAAE,WAAW;MAAE,GAAG,OAAO,WAAW,CAAC;MAAW,GAAG;MAAc,EAAE,GAAG,EAAE;KAC5F,CAAC,CAAC,CAAC;AAEN,QAAI,CAAC,mBAAmB,SAAS,SAAS,OACxC,QAAO;IAGT,MAAM,WAAW,MAAM,mBAAmB,SAAS,MAAM,cAAc;KAAE;KAAQ;KAAiB,CAAC;AACnG,QAAI,CAAC,SACH,QAAO;AAGT,WAAO;KACL,GAAG;KACH,MAAM;MACJ,GAAG,SAAS;MACZ,OAAO,mBAAmB,SAAS,KAAK,OAAO,SAAS,eAAe,SAAS,YAAY;MAC7F;KACF;MACA,kBAAkB,EAAE,gBAAgB,UAAU,GAAG,OAAU;;EAGhE,MAAM,OACJ,UAAyH,EAAE,KACrD;GACtE,MAAM,EAAE,QAAQ,EAAE,EAAE,QAAQ,cAAc,iBAAiB;GAC3D,MAAM,cAAc;AAEpB,UAAO,iBAA8D,OAAO,aAAa,OAAO,OAAO,iBAA2D;IAChK,MAAM,WAAW,MAAM,gBAAgB,QAAQ,aAAa,oBAC1D,cAA2D,KAAK;KAC9D;KACA,OAAO;KACP;KACA,GAAI,iBAAiB,SAAY,EAAE,GAAG,EAAE,cAAc;KACtD,GAAI,eAAe,EAAE,WAAW;MAAE,GAAG,OAAO,WAAW,CAAC;MAAW,GAAG;MAAc,EAAE,GAAG,EAAE;KAC5F,CAAC,CAAC,CAAC;AAEN,QAAI,CAAC,mBAAmB,SAAS,SAAS,OACxC,QAAO;IAGT,MAAM,WAAW,MAAM,mBAAmB,SAAS,MAAM,cAAc;KAAE;KAAQ;KAAiB,CAAC;AACnG,QAAI,CAAC,SACH,QAAO;AAGT,WAAO;KACL,GAAG;KACH,MAAM;MACJ,GAAG,SAAS;MACZ,SAAS,qBAAqB,SAAS,KAAK,SAAS,SAAS,eAAe,SAAS,YAAY;MACnG;KACF;MACA,kBAAkB,EAAE,gBAAgB,UAAU,GAAG,OAAU;;EAEjE"}
|
package/dist/resources/tags.cjs
CHANGED
|
@@ -3,11 +3,11 @@ const require_sdk_gen = require('../generated/tags/sdk.gen.cjs');
|
|
|
3
3
|
//#region src/resources/tags.ts
|
|
4
4
|
function createTagsResource(deps) {
|
|
5
5
|
const { client, requestWithCache, asApiResponse, throttleManager } = deps;
|
|
6
|
-
return {
|
|
6
|
+
return { list: async (options = {}) => {
|
|
7
7
|
const { query = {}, signal, throwOnError, fetchOptions } = options;
|
|
8
8
|
const requestPath = "/v2/cdn/tags";
|
|
9
9
|
return requestWithCache("GET", requestPath, query, (requestQuery) => {
|
|
10
|
-
return throttleManager.execute(requestPath, requestQuery, () => asApiResponse(require_sdk_gen.
|
|
10
|
+
return throttleManager.execute(requestPath, requestQuery, () => asApiResponse(require_sdk_gen.list({
|
|
11
11
|
client,
|
|
12
12
|
query: requestQuery,
|
|
13
13
|
signal,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tags.cjs","names":["
|
|
1
|
+
{"version":3,"file":"tags.cjs","names":["listTagsApi"],"sources":["../../src/resources/tags.ts"],"sourcesContent":["import { list as listTagsApi } from '../generated/tags/sdk.gen';\nimport type { ListData as TagsListData, ListResponses as TagsListResponses } from '../generated/tags/types.gen';\nimport type { ApiResponse, FetchOptions, ResourceDeps } from '../types';\n\nexport function createTagsResource(deps: ResourceDeps) {\n const { client, requestWithCache, asApiResponse, throttleManager } = deps;\n\n return {\n list: async <ThrowOnError extends boolean = false>(\n options: { query?: TagsListData['query']; signal?: AbortSignal; throwOnError?: ThrowOnError; fetchOptions?: FetchOptions } = {},\n ): Promise<ApiResponse<TagsListResponses[200], ThrowOnError>> => {\n const { query = {}, signal, throwOnError, fetchOptions } = options;\n const requestPath = '/v2/cdn/tags';\n return requestWithCache<TagsListResponses[200], ThrowOnError>('GET', requestPath, query, (requestQuery: Record<string, unknown>) => {\n return throttleManager.execute(requestPath, requestQuery, () =>\n asApiResponse<TagsListResponses[200], ThrowOnError>(listTagsApi({\n client,\n query: requestQuery,\n signal,\n ...(throwOnError === undefined ? {} : { throwOnError }),\n ...(fetchOptions ? { kyOptions: { ...client.getConfig().kyOptions, ...fetchOptions } } : {}),\n })));\n });\n },\n };\n}\n"],"mappings":";;;AAIA,SAAgB,mBAAmB,MAAoB;CACrD,MAAM,EAAE,QAAQ,kBAAkB,eAAe,oBAAoB;AAErE,QAAO,EACL,MAAM,OACJ,UAA6H,EAAE,KAChE;EAC/D,MAAM,EAAE,QAAQ,EAAE,EAAE,QAAQ,cAAc,iBAAiB;EAC3D,MAAM,cAAc;AACpB,SAAO,iBAAuD,OAAO,aAAa,QAAQ,iBAA0C;AAClI,UAAO,gBAAgB,QAAQ,aAAa,oBAC1C,cAAoDA,qBAAY;IAC9D;IACA,OAAO;IACP;IACA,GAAI,iBAAiB,SAAY,EAAE,GAAG,EAAE,cAAc;IACtD,GAAI,eAAe,EAAE,WAAW;KAAE,GAAG,OAAO,WAAW,CAAC;KAAW,GAAG;KAAc,EAAE,GAAG,EAAE;IAC5F,CAAC,CAAC,CAAC;IACN;IAEL"}
|
package/dist/resources/tags.mjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { list } from "../generated/tags/sdk.gen.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/resources/tags.ts
|
|
4
4
|
function createTagsResource(deps) {
|
|
5
5
|
const { client, requestWithCache, asApiResponse, throttleManager } = deps;
|
|
6
|
-
return {
|
|
6
|
+
return { list: async (options = {}) => {
|
|
7
7
|
const { query = {}, signal, throwOnError, fetchOptions } = options;
|
|
8
8
|
const requestPath = "/v2/cdn/tags";
|
|
9
9
|
return requestWithCache("GET", requestPath, query, (requestQuery) => {
|
|
10
|
-
return throttleManager.execute(requestPath, requestQuery, () => asApiResponse(
|
|
10
|
+
return throttleManager.execute(requestPath, requestQuery, () => asApiResponse(list({
|
|
11
11
|
client,
|
|
12
12
|
query: requestQuery,
|
|
13
13
|
signal,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tags.mjs","names":["
|
|
1
|
+
{"version":3,"file":"tags.mjs","names":["listTagsApi"],"sources":["../../src/resources/tags.ts"],"sourcesContent":["import { list as listTagsApi } from '../generated/tags/sdk.gen';\nimport type { ListData as TagsListData, ListResponses as TagsListResponses } from '../generated/tags/types.gen';\nimport type { ApiResponse, FetchOptions, ResourceDeps } from '../types';\n\nexport function createTagsResource(deps: ResourceDeps) {\n const { client, requestWithCache, asApiResponse, throttleManager } = deps;\n\n return {\n list: async <ThrowOnError extends boolean = false>(\n options: { query?: TagsListData['query']; signal?: AbortSignal; throwOnError?: ThrowOnError; fetchOptions?: FetchOptions } = {},\n ): Promise<ApiResponse<TagsListResponses[200], ThrowOnError>> => {\n const { query = {}, signal, throwOnError, fetchOptions } = options;\n const requestPath = '/v2/cdn/tags';\n return requestWithCache<TagsListResponses[200], ThrowOnError>('GET', requestPath, query, (requestQuery: Record<string, unknown>) => {\n return throttleManager.execute(requestPath, requestQuery, () =>\n asApiResponse<TagsListResponses[200], ThrowOnError>(listTagsApi({\n client,\n query: requestQuery,\n signal,\n ...(throwOnError === undefined ? {} : { throwOnError }),\n ...(fetchOptions ? { kyOptions: { ...client.getConfig().kyOptions, ...fetchOptions } } : {}),\n })));\n });\n },\n };\n}\n"],"mappings":";;;AAIA,SAAgB,mBAAmB,MAAoB;CACrD,MAAM,EAAE,QAAQ,kBAAkB,eAAe,oBAAoB;AAErE,QAAO,EACL,MAAM,OACJ,UAA6H,EAAE,KAChE;EAC/D,MAAM,EAAE,QAAQ,EAAE,EAAE,QAAQ,cAAc,iBAAiB;EAC3D,MAAM,cAAc;AACpB,SAAO,iBAAuD,OAAO,aAAa,QAAQ,iBAA0C;AAClI,UAAO,gBAAgB,QAAQ,aAAa,oBAC1C,cAAoDA,KAAY;IAC9D;IACA,OAAO;IACP;IACA,GAAI,iBAAiB,SAAY,EAAE,GAAG,EAAE,cAAc;IACtD,GAAI,eAAe,EAAE,WAAW;KAAE,GAAG,OAAO,WAAW,CAAC;KAAW,GAAG;KAAc,EAAE,GAAG,EAAE;IAC5F,CAAC,CAAC,CAAC;IACN;IAEL"}
|
|
@@ -25,7 +25,7 @@ const fetchMissingRelations = async ({ client, uuids, baseQuery, throttleManager
|
|
|
25
25
|
};
|
|
26
26
|
const chunks = require_array.chunkArray(uuids, UUID_CHUNK_SIZE);
|
|
27
27
|
return (await Promise.all(chunks.map((chunk) => throttleManager.execute("/v2/cdn/stories", queryContext, async () => {
|
|
28
|
-
const response = await require_sdk_gen.
|
|
28
|
+
const response = await require_sdk_gen.list({
|
|
29
29
|
client,
|
|
30
30
|
query: {
|
|
31
31
|
...queryContext,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-rel-uuids.cjs","names":["chunkArray","
|
|
1
|
+
{"version":3,"file":"fetch-rel-uuids.cjs","names":["chunkArray","list"],"sources":["../../src/utils/fetch-rel-uuids.ts"],"sourcesContent":["import type { Client } from '../generated/shared/client';\nimport { list } from '../generated/stories/sdk.gen';\nimport type { StoryCapi } from '../generated/stories/types.gen';\nimport type { ThrottleManager } from './rate-limit';\nimport { chunkArray } from './array';\n\nconst UUID_CHUNK_SIZE = 50;\nconst QUERY_CONTEXT_KEYS = new Set([\n 'cv',\n 'fallback_lang',\n 'from_release',\n 'language',\n 'resolve_assets',\n 'resolve_links',\n 'resolve_links_level',\n 'version',\n]);\n\nconst pickQueryContext = (baseQuery: Record<string, unknown>): Record<string, unknown> => {\n const query: Record<string, unknown> = {};\n for (const key of QUERY_CONTEXT_KEYS) {\n if (baseQuery[key] !== undefined) {\n query[key] = baseQuery[key];\n }\n }\n return query;\n};\n\ninterface FetchMissingRelationsOptions {\n client: Client;\n uuids: string[];\n baseQuery: Record<string, unknown>;\n throttleManager: ThrottleManager;\n}\n\nexport const fetchMissingRelations = async ({\n client,\n uuids,\n baseQuery,\n throttleManager,\n}: FetchMissingRelationsOptions): Promise<StoryCapi[]> => {\n const queryContext = { ...pickQueryContext(baseQuery), per_page: UUID_CHUNK_SIZE };\n const chunks = chunkArray(uuids, UUID_CHUNK_SIZE);\n\n const results = await Promise.all(\n chunks.map(chunk =>\n throttleManager.execute('/v2/cdn/stories', queryContext, async () => {\n const response = await list({\n client,\n query: {\n ...queryContext,\n by_uuids: chunk.join(','),\n per_page: UUID_CHUNK_SIZE,\n },\n });\n\n throttleManager.adaptToResponse(response.response);\n\n if (response.error !== undefined) {\n throw response.error;\n }\n\n if (response.data === undefined) {\n throw new Error('Failed to fetch missing relations.');\n }\n\n return response.data.stories;\n }),\n ),\n );\n\n return results.flat();\n};\n"],"mappings":";;;;AAMA,MAAM,kBAAkB;AACxB,MAAM,qBAAqB,IAAI,IAAI;CACjC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,MAAM,oBAAoB,cAAgE;CACxF,MAAM,QAAiC,EAAE;AACzC,MAAK,MAAM,OAAO,mBAChB,KAAI,UAAU,SAAS,OACrB,OAAM,OAAO,UAAU;AAG3B,QAAO;;AAUT,MAAa,wBAAwB,OAAO,EAC1C,QACA,OACA,WACA,sBACwD;CACxD,MAAM,eAAe;EAAE,GAAG,iBAAiB,UAAU;EAAE,UAAU;EAAiB;CAClF,MAAM,SAASA,yBAAW,OAAO,gBAAgB;AA6BjD,SA3BgB,MAAM,QAAQ,IAC5B,OAAO,KAAI,UACT,gBAAgB,QAAQ,mBAAmB,cAAc,YAAY;EACnE,MAAM,WAAW,MAAMC,qBAAK;GAC1B;GACA,OAAO;IACL,GAAG;IACH,UAAU,MAAM,KAAK,IAAI;IACzB,UAAU;IACX;GACF,CAAC;AAEF,kBAAgB,gBAAgB,SAAS,SAAS;AAElD,MAAI,SAAS,UAAU,OACrB,OAAM,SAAS;AAGjB,MAAI,SAAS,SAAS,OACpB,OAAM,IAAI,MAAM,qCAAqC;AAGvD,SAAO,SAAS,KAAK;GACrB,CACH,CACF,EAEc,MAAM"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { list } from "../generated/stories/sdk.gen.mjs";
|
|
2
2
|
import { chunkArray } from "./array.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/utils/fetch-rel-uuids.ts
|
|
@@ -25,7 +25,7 @@ const fetchMissingRelations = async ({ client, uuids, baseQuery, throttleManager
|
|
|
25
25
|
};
|
|
26
26
|
const chunks = chunkArray(uuids, UUID_CHUNK_SIZE);
|
|
27
27
|
return (await Promise.all(chunks.map((chunk) => throttleManager.execute("/v2/cdn/stories", queryContext, async () => {
|
|
28
|
-
const response = await
|
|
28
|
+
const response = await list({
|
|
29
29
|
client,
|
|
30
30
|
query: {
|
|
31
31
|
...queryContext,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-rel-uuids.mjs","names":[],"sources":["../../src/utils/fetch-rel-uuids.ts"],"sourcesContent":["import type { Client } from '../generated/shared/client';\nimport {
|
|
1
|
+
{"version":3,"file":"fetch-rel-uuids.mjs","names":[],"sources":["../../src/utils/fetch-rel-uuids.ts"],"sourcesContent":["import type { Client } from '../generated/shared/client';\nimport { list } from '../generated/stories/sdk.gen';\nimport type { StoryCapi } from '../generated/stories/types.gen';\nimport type { ThrottleManager } from './rate-limit';\nimport { chunkArray } from './array';\n\nconst UUID_CHUNK_SIZE = 50;\nconst QUERY_CONTEXT_KEYS = new Set([\n 'cv',\n 'fallback_lang',\n 'from_release',\n 'language',\n 'resolve_assets',\n 'resolve_links',\n 'resolve_links_level',\n 'version',\n]);\n\nconst pickQueryContext = (baseQuery: Record<string, unknown>): Record<string, unknown> => {\n const query: Record<string, unknown> = {};\n for (const key of QUERY_CONTEXT_KEYS) {\n if (baseQuery[key] !== undefined) {\n query[key] = baseQuery[key];\n }\n }\n return query;\n};\n\ninterface FetchMissingRelationsOptions {\n client: Client;\n uuids: string[];\n baseQuery: Record<string, unknown>;\n throttleManager: ThrottleManager;\n}\n\nexport const fetchMissingRelations = async ({\n client,\n uuids,\n baseQuery,\n throttleManager,\n}: FetchMissingRelationsOptions): Promise<StoryCapi[]> => {\n const queryContext = { ...pickQueryContext(baseQuery), per_page: UUID_CHUNK_SIZE };\n const chunks = chunkArray(uuids, UUID_CHUNK_SIZE);\n\n const results = await Promise.all(\n chunks.map(chunk =>\n throttleManager.execute('/v2/cdn/stories', queryContext, async () => {\n const response = await list({\n client,\n query: {\n ...queryContext,\n by_uuids: chunk.join(','),\n per_page: UUID_CHUNK_SIZE,\n },\n });\n\n throttleManager.adaptToResponse(response.response);\n\n if (response.error !== undefined) {\n throw response.error;\n }\n\n if (response.data === undefined) {\n throw new Error('Failed to fetch missing relations.');\n }\n\n return response.data.stories;\n }),\n ),\n );\n\n return results.flat();\n};\n"],"mappings":";;;;AAMA,MAAM,kBAAkB;AACxB,MAAM,qBAAqB,IAAI,IAAI;CACjC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,MAAM,oBAAoB,cAAgE;CACxF,MAAM,QAAiC,EAAE;AACzC,MAAK,MAAM,OAAO,mBAChB,KAAI,UAAU,SAAS,OACrB,OAAM,OAAO,UAAU;AAG3B,QAAO;;AAUT,MAAa,wBAAwB,OAAO,EAC1C,QACA,OACA,WACA,sBACwD;CACxD,MAAM,eAAe;EAAE,GAAG,iBAAiB,UAAU;EAAE,UAAU;EAAiB;CAClF,MAAM,SAAS,WAAW,OAAO,gBAAgB;AA6BjD,SA3BgB,MAAM,QAAQ,IAC5B,OAAO,KAAI,UACT,gBAAgB,QAAQ,mBAAmB,cAAc,YAAY;EACnE,MAAM,WAAW,MAAM,KAAK;GAC1B;GACA,OAAO;IACL,GAAG;IACH,UAAU,MAAM,KAAK,IAAI;IACzB,UAAU;IACX;GACF,CAAC;AAEF,kBAAgB,gBAAgB,SAAS,SAAS;AAElD,MAAI,SAAS,UAAU,OACrB,OAAM,SAAS;AAGjB,MAAI,SAAS,SAAS,OACpB,OAAM,IAAI,MAAM,qCAAqC;AAGvD,SAAO,SAAS,KAAK;GACrB,CACH,CACF,EAEc,MAAM"}
|
|
@@ -94,7 +94,7 @@ function parseRateLimitPolicyHeader(response) {
|
|
|
94
94
|
*
|
|
95
95
|
* - `false` → no throttling (passthrough)
|
|
96
96
|
* - `number` → fixed single queue at that limit
|
|
97
|
-
* - `{
|
|
97
|
+
* - `{ maxConcurrency: n }` → fixed single queue at n req/s
|
|
98
98
|
* - `{}` / `undefined` (default)→ auto-detect tier from path + per_page
|
|
99
99
|
*/
|
|
100
100
|
function createThrottleManager(config) {
|
|
@@ -102,9 +102,9 @@ function createThrottleManager(config) {
|
|
|
102
102
|
execute: (_path, _query, fn) => fn(),
|
|
103
103
|
adaptToResponse: () => {}
|
|
104
104
|
};
|
|
105
|
-
const {
|
|
106
|
-
if (
|
|
107
|
-
const cappedLimit = Math.min(
|
|
105
|
+
const { maxConcurrency, adaptToServerHeaders = true } = typeof config === "number" ? { maxConcurrency: config } : config;
|
|
106
|
+
if (maxConcurrency !== void 0) {
|
|
107
|
+
const cappedLimit = Math.min(maxConcurrency, MAX_RATE_LIMIT);
|
|
108
108
|
const throttle = createThrottle(cappedLimit);
|
|
109
109
|
return {
|
|
110
110
|
execute: (_path, _query, fn) => throttle.execute(fn),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.cjs","names":[],"sources":["../../src/utils/rate-limit.ts"],"sourcesContent":["/**\n * Rate limiting for the Content API client.\n *\n * Provides both a simple token-bucket throttle and a tier-aware manager\n * that automatically selects the right concurrency limit based on request\n * type (single story vs. listing) and the per_page query parameter — mirroring\n * the tiers enforced server-side by the Storyblok CDN.\n */\n\nconst TIER_LIMITS = {\n SINGLE_OR_SMALL: 50, // single story fetch or per_page ≤ 25\n MEDIUM: 15, // per_page 26–50\n LARGE: 10, // per_page 51–75\n VERY_LARGE: 6, // per_page 76–100\n} as const;\n\ntype TierName = keyof typeof TIER_LIMITS;\n\nconst PER_PAGE_THRESHOLDS = {\n SMALL: 25,\n MEDIUM: 50,\n LARGE: 75,\n} as const;\n\nconst DEFAULT_PER_PAGE = 25;\nconst MAX_RATE_LIMIT = 1_000;\n\nexport interface RateLimitConfig {\n /**\n * Fixed maximum number of concurrent requests per second.\n * When set, disables automatic per_page tier detection and all requests\n * share a single queue at this limit. Capped at 1000.\n */\n
|
|
1
|
+
{"version":3,"file":"rate-limit.cjs","names":[],"sources":["../../src/utils/rate-limit.ts"],"sourcesContent":["/**\n * Rate limiting for the Content API client.\n *\n * Provides both a simple token-bucket throttle and a tier-aware manager\n * that automatically selects the right concurrency limit based on request\n * type (single story vs. listing) and the per_page query parameter — mirroring\n * the tiers enforced server-side by the Storyblok CDN.\n */\n\nconst TIER_LIMITS = {\n SINGLE_OR_SMALL: 50, // single story fetch or per_page ≤ 25\n MEDIUM: 15, // per_page 26–50\n LARGE: 10, // per_page 51–75\n VERY_LARGE: 6, // per_page 76–100\n} as const;\n\ntype TierName = keyof typeof TIER_LIMITS;\n\nconst PER_PAGE_THRESHOLDS = {\n SMALL: 25,\n MEDIUM: 50,\n LARGE: 75,\n} as const;\n\nconst DEFAULT_PER_PAGE = 25;\nconst MAX_RATE_LIMIT = 1_000;\n\nexport interface RateLimitConfig {\n /**\n * Fixed maximum number of concurrent requests per second.\n * When set, disables automatic per_page tier detection and all requests\n * share a single queue at this limit. Capped at 1000.\n */\n maxConcurrency?: number;\n /**\n * Dynamically adjust the rate limit based on the `X-RateLimit-Policy`\n * response header returned by the Storyblok API.\n * @default true\n */\n adaptToServerHeaders?: boolean;\n}\n\nexport interface ThrottleManager {\n execute: <T>(path: string, query: Record<string, unknown>, fn: () => Promise<T>) => Promise<T>;\n adaptToResponse: (response: Response | undefined) => void;\n}\n\ninterface Throttle {\n execute: <T>(fn: () => Promise<T>) => Promise<T>;\n setLimit: (n: number) => void;\n}\n\n/**\n * Concurrency limiter: allows up to `initialLimit` requests to be in-flight\n * at the same time. A slot is freed as soon as the request's promise settles\n * (resolves or rejects), so throughput scales with how quickly requests\n * complete rather than being artificially capped at N per second.\n */\nexport function createThrottle(initialLimit: number): Throttle {\n let limit = initialLimit;\n let activeCount = 0;\n const queue: Array<() => void> = [];\n\n const tryNext = () => {\n while (queue.length > 0 && activeCount < limit) {\n activeCount++;\n const run = queue.shift()!;\n run();\n }\n };\n\n const execute = <T>(fn: () => Promise<T>): Promise<T> => {\n return new Promise<T>((resolve, reject) => {\n queue.push(() => {\n fn().then(\n (value) => {\n activeCount--;\n tryNext();\n resolve(value);\n },\n (error) => {\n activeCount--;\n tryNext();\n reject(error);\n },\n );\n });\n tryNext();\n });\n };\n\n const setLimit = (n: number) => {\n limit = n;\n // If the limit increased, unblock any waiting requests.\n tryNext();\n };\n\n return { execute, setLimit };\n}\n\n// Matches /v2/cdn/stories/<identifier> — a single story fetch (including nested slugs).\nconst SINGLE_STORY_PATH_RE = /\\/v2\\/cdn\\/stories\\/.+$/;\n\n/**\n * Maps a request path + query to one of the four rate-limit tiers.\n * Used by the auto-detection mode of `createThrottleManager`.\n */\nexport function determineTier(path: string, query: Record<string, unknown>): TierName {\n if (SINGLE_STORY_PATH_RE.test(path)) {\n return 'SINGLE_OR_SMALL';\n }\n\n const rawPerPage = query.per_page;\n const perPage\n = typeof rawPerPage === 'number'\n ? rawPerPage\n : typeof rawPerPage === 'string'\n ? Number.parseInt(rawPerPage, 10) || DEFAULT_PER_PAGE\n : DEFAULT_PER_PAGE;\n\n if (perPage <= PER_PAGE_THRESHOLDS.SMALL) {\n return 'SINGLE_OR_SMALL';\n }\n if (perPage <= PER_PAGE_THRESHOLDS.MEDIUM) {\n return 'MEDIUM';\n }\n if (perPage <= PER_PAGE_THRESHOLDS.LARGE) {\n return 'LARGE';\n }\n return 'VERY_LARGE';\n}\n\n/**\n * Extracts the quota (`q=`) value from the `X-RateLimit-Policy` response header.\n * Returns `undefined` if the header is absent or unparseable.\n *\n * Example header: `\"concurrent-requests\";q=30`\n */\nexport function parseRateLimitPolicyHeader(response: Response): number | undefined {\n const policy = response.headers.get('x-ratelimit-policy');\n if (!policy) {\n return undefined;\n }\n const match = policy.match(/q=(\\d+)/);\n if (!match) {\n return undefined;\n }\n return Math.min(Number.parseInt(match[1], 10), MAX_RATE_LIMIT);\n}\n\n/**\n * Creates a `ThrottleManager` from the user-supplied `rateLimit` config.\n *\n * - `false` → no throttling (passthrough)\n * - `number` → fixed single queue at that limit\n * - `{ maxConcurrency: n }` → fixed single queue at n req/s\n * - `{}` / `undefined` (default)→ auto-detect tier from path + per_page\n */\nexport function createThrottleManager(config: RateLimitConfig | number | false): ThrottleManager {\n // Disabled — every request goes straight through.\n if (config === false) {\n return {\n execute: (_path, _query, fn) => fn(),\n adaptToResponse: () => {},\n };\n }\n\n const resolvedConfig: RateLimitConfig = typeof config === 'number' ? { maxConcurrency: config } : config;\n const { maxConcurrency, adaptToServerHeaders = true } = resolvedConfig;\n\n // Fixed-limit mode — single queue, optional server-header adaptation.\n if (maxConcurrency !== undefined) {\n const cappedLimit = Math.min(maxConcurrency, MAX_RATE_LIMIT);\n const throttle = createThrottle(cappedLimit);\n\n return {\n execute: (_path, _query, fn) => throttle.execute(fn),\n adaptToResponse: (response) => {\n if (!adaptToServerHeaders || response === undefined) {\n return;\n }\n const serverLimit = parseRateLimitPolicyHeader(response);\n if (serverLimit !== undefined) {\n // Never exceed the user-configured ceiling.\n throttle.setLimit(Math.min(cappedLimit, serverLimit));\n }\n },\n };\n }\n\n // Auto-detect mode — one throttle per tier, tier chosen per request.\n const throttles: Record<TierName, Throttle> = {\n SINGLE_OR_SMALL: createThrottle(TIER_LIMITS.SINGLE_OR_SMALL),\n MEDIUM: createThrottle(TIER_LIMITS.MEDIUM),\n LARGE: createThrottle(TIER_LIMITS.LARGE),\n VERY_LARGE: createThrottle(TIER_LIMITS.VERY_LARGE),\n };\n\n return {\n execute: (path, query, fn) => {\n const tier = determineTier(path, query);\n return throttles[tier].execute(fn);\n },\n adaptToResponse: (response) => {\n if (!adaptToServerHeaders || response === undefined) {\n return;\n }\n const serverLimit = parseRateLimitPolicyHeader(response);\n if (serverLimit !== undefined) {\n // The SINGLE_OR_SMALL tier is the most common; adapting it covers the\n // majority of requests. Other tiers are already conservatively limited.\n throttles.SINGLE_OR_SMALL.setLimit(Math.min(TIER_LIMITS.SINGLE_OR_SMALL, serverLimit));\n }\n },\n };\n}\n"],"mappings":";;;;;;;;;;AASA,MAAM,cAAc;CAClB,iBAAiB;CACjB,QAAQ;CACR,OAAO;CACP,YAAY;CACb;AAID,MAAM,sBAAsB;CAC1B,OAAO;CACP,QAAQ;CACR,OAAO;CACR;AAED,MAAM,mBAAmB;AACzB,MAAM,iBAAiB;;;;;;;AAiCvB,SAAgB,eAAe,cAAgC;CAC7D,IAAI,QAAQ;CACZ,IAAI,cAAc;CAClB,MAAM,QAA2B,EAAE;CAEnC,MAAM,gBAAgB;AACpB,SAAO,MAAM,SAAS,KAAK,cAAc,OAAO;AAC9C;AAEA,GADY,MAAM,OAAO,EACpB;;;CAIT,MAAM,WAAc,OAAqC;AACvD,SAAO,IAAI,SAAY,SAAS,WAAW;AACzC,SAAM,WAAW;AACf,QAAI,CAAC,MACF,UAAU;AACT;AACA,cAAS;AACT,aAAQ,MAAM;QAEf,UAAU;AACT;AACA,cAAS;AACT,YAAO,MAAM;MAEhB;KACD;AACF,YAAS;IACT;;CAGJ,MAAM,YAAY,MAAc;AAC9B,UAAQ;AAER,WAAS;;AAGX,QAAO;EAAE;EAAS;EAAU;;AAI9B,MAAM,uBAAuB;;;;;AAM7B,SAAgB,cAAc,MAAc,OAA0C;AACpF,KAAI,qBAAqB,KAAK,KAAK,CACjC,QAAO;CAGT,MAAM,aAAa,MAAM;CACzB,MAAM,UACF,OAAO,eAAe,WACpB,aACA,OAAO,eAAe,WACpB,OAAO,SAAS,YAAY,GAAG,IAAI,mBACnC;AAER,KAAI,WAAW,oBAAoB,MACjC,QAAO;AAET,KAAI,WAAW,oBAAoB,OACjC,QAAO;AAET,KAAI,WAAW,oBAAoB,MACjC,QAAO;AAET,QAAO;;;;;;;;AAST,SAAgB,2BAA2B,UAAwC;CACjF,MAAM,SAAS,SAAS,QAAQ,IAAI,qBAAqB;AACzD,KAAI,CAAC,OACH;CAEF,MAAM,QAAQ,OAAO,MAAM,UAAU;AACrC,KAAI,CAAC,MACH;AAEF,QAAO,KAAK,IAAI,OAAO,SAAS,MAAM,IAAI,GAAG,EAAE,eAAe;;;;;;;;;;AAWhE,SAAgB,sBAAsB,QAA2D;AAE/F,KAAI,WAAW,MACb,QAAO;EACL,UAAU,OAAO,QAAQ,OAAO,IAAI;EACpC,uBAAuB;EACxB;CAIH,MAAM,EAAE,gBAAgB,uBAAuB,SADP,OAAO,WAAW,WAAW,EAAE,gBAAgB,QAAQ,GAAG;AAIlG,KAAI,mBAAmB,QAAW;EAChC,MAAM,cAAc,KAAK,IAAI,gBAAgB,eAAe;EAC5D,MAAM,WAAW,eAAe,YAAY;AAE5C,SAAO;GACL,UAAU,OAAO,QAAQ,OAAO,SAAS,QAAQ,GAAG;GACpD,kBAAkB,aAAa;AAC7B,QAAI,CAAC,wBAAwB,aAAa,OACxC;IAEF,MAAM,cAAc,2BAA2B,SAAS;AACxD,QAAI,gBAAgB,OAElB,UAAS,SAAS,KAAK,IAAI,aAAa,YAAY,CAAC;;GAG1D;;CAIH,MAAM,YAAwC;EAC5C,iBAAiB,eAAe,YAAY,gBAAgB;EAC5D,QAAQ,eAAe,YAAY,OAAO;EAC1C,OAAO,eAAe,YAAY,MAAM;EACxC,YAAY,eAAe,YAAY,WAAW;EACnD;AAED,QAAO;EACL,UAAU,MAAM,OAAO,OAAO;AAE5B,UAAO,UADM,cAAc,MAAM,MAAM,EAChB,QAAQ,GAAG;;EAEpC,kBAAkB,aAAa;AAC7B,OAAI,CAAC,wBAAwB,aAAa,OACxC;GAEF,MAAM,cAAc,2BAA2B,SAAS;AACxD,OAAI,gBAAgB,OAGlB,WAAU,gBAAgB,SAAS,KAAK,IAAI,YAAY,iBAAiB,YAAY,CAAC;;EAG3F"}
|
|
@@ -5,7 +5,7 @@ interface RateLimitConfig {
|
|
|
5
5
|
* When set, disables automatic per_page tier detection and all requests
|
|
6
6
|
* share a single queue at this limit. Capped at 1000.
|
|
7
7
|
*/
|
|
8
|
-
|
|
8
|
+
maxConcurrency?: number;
|
|
9
9
|
/**
|
|
10
10
|
* Dynamically adjust the rate limit based on the `X-RateLimit-Policy`
|
|
11
11
|
* response header returned by the Storyblok API.
|
|
@@ -5,7 +5,7 @@ interface RateLimitConfig {
|
|
|
5
5
|
* When set, disables automatic per_page tier detection and all requests
|
|
6
6
|
* share a single queue at this limit. Capped at 1000.
|
|
7
7
|
*/
|
|
8
|
-
|
|
8
|
+
maxConcurrency?: number;
|
|
9
9
|
/**
|
|
10
10
|
* Dynamically adjust the rate limit based on the `X-RateLimit-Policy`
|
|
11
11
|
* response header returned by the Storyblok API.
|
|
@@ -93,7 +93,7 @@ function parseRateLimitPolicyHeader(response) {
|
|
|
93
93
|
*
|
|
94
94
|
* - `false` → no throttling (passthrough)
|
|
95
95
|
* - `number` → fixed single queue at that limit
|
|
96
|
-
* - `{
|
|
96
|
+
* - `{ maxConcurrency: n }` → fixed single queue at n req/s
|
|
97
97
|
* - `{}` / `undefined` (default)→ auto-detect tier from path + per_page
|
|
98
98
|
*/
|
|
99
99
|
function createThrottleManager(config) {
|
|
@@ -101,9 +101,9 @@ function createThrottleManager(config) {
|
|
|
101
101
|
execute: (_path, _query, fn) => fn(),
|
|
102
102
|
adaptToResponse: () => {}
|
|
103
103
|
};
|
|
104
|
-
const {
|
|
105
|
-
if (
|
|
106
|
-
const cappedLimit = Math.min(
|
|
104
|
+
const { maxConcurrency, adaptToServerHeaders = true } = typeof config === "number" ? { maxConcurrency: config } : config;
|
|
105
|
+
if (maxConcurrency !== void 0) {
|
|
106
|
+
const cappedLimit = Math.min(maxConcurrency, MAX_RATE_LIMIT);
|
|
107
107
|
const throttle = createThrottle(cappedLimit);
|
|
108
108
|
return {
|
|
109
109
|
execute: (_path, _query, fn) => throttle.execute(fn),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.mjs","names":[],"sources":["../../src/utils/rate-limit.ts"],"sourcesContent":["/**\n * Rate limiting for the Content API client.\n *\n * Provides both a simple token-bucket throttle and a tier-aware manager\n * that automatically selects the right concurrency limit based on request\n * type (single story vs. listing) and the per_page query parameter — mirroring\n * the tiers enforced server-side by the Storyblok CDN.\n */\n\nconst TIER_LIMITS = {\n SINGLE_OR_SMALL: 50, // single story fetch or per_page ≤ 25\n MEDIUM: 15, // per_page 26–50\n LARGE: 10, // per_page 51–75\n VERY_LARGE: 6, // per_page 76–100\n} as const;\n\ntype TierName = keyof typeof TIER_LIMITS;\n\nconst PER_PAGE_THRESHOLDS = {\n SMALL: 25,\n MEDIUM: 50,\n LARGE: 75,\n} as const;\n\nconst DEFAULT_PER_PAGE = 25;\nconst MAX_RATE_LIMIT = 1_000;\n\nexport interface RateLimitConfig {\n /**\n * Fixed maximum number of concurrent requests per second.\n * When set, disables automatic per_page tier detection and all requests\n * share a single queue at this limit. Capped at 1000.\n */\n
|
|
1
|
+
{"version":3,"file":"rate-limit.mjs","names":[],"sources":["../../src/utils/rate-limit.ts"],"sourcesContent":["/**\n * Rate limiting for the Content API client.\n *\n * Provides both a simple token-bucket throttle and a tier-aware manager\n * that automatically selects the right concurrency limit based on request\n * type (single story vs. listing) and the per_page query parameter — mirroring\n * the tiers enforced server-side by the Storyblok CDN.\n */\n\nconst TIER_LIMITS = {\n SINGLE_OR_SMALL: 50, // single story fetch or per_page ≤ 25\n MEDIUM: 15, // per_page 26–50\n LARGE: 10, // per_page 51–75\n VERY_LARGE: 6, // per_page 76–100\n} as const;\n\ntype TierName = keyof typeof TIER_LIMITS;\n\nconst PER_PAGE_THRESHOLDS = {\n SMALL: 25,\n MEDIUM: 50,\n LARGE: 75,\n} as const;\n\nconst DEFAULT_PER_PAGE = 25;\nconst MAX_RATE_LIMIT = 1_000;\n\nexport interface RateLimitConfig {\n /**\n * Fixed maximum number of concurrent requests per second.\n * When set, disables automatic per_page tier detection and all requests\n * share a single queue at this limit. Capped at 1000.\n */\n maxConcurrency?: number;\n /**\n * Dynamically adjust the rate limit based on the `X-RateLimit-Policy`\n * response header returned by the Storyblok API.\n * @default true\n */\n adaptToServerHeaders?: boolean;\n}\n\nexport interface ThrottleManager {\n execute: <T>(path: string, query: Record<string, unknown>, fn: () => Promise<T>) => Promise<T>;\n adaptToResponse: (response: Response | undefined) => void;\n}\n\ninterface Throttle {\n execute: <T>(fn: () => Promise<T>) => Promise<T>;\n setLimit: (n: number) => void;\n}\n\n/**\n * Concurrency limiter: allows up to `initialLimit` requests to be in-flight\n * at the same time. A slot is freed as soon as the request's promise settles\n * (resolves or rejects), so throughput scales with how quickly requests\n * complete rather than being artificially capped at N per second.\n */\nexport function createThrottle(initialLimit: number): Throttle {\n let limit = initialLimit;\n let activeCount = 0;\n const queue: Array<() => void> = [];\n\n const tryNext = () => {\n while (queue.length > 0 && activeCount < limit) {\n activeCount++;\n const run = queue.shift()!;\n run();\n }\n };\n\n const execute = <T>(fn: () => Promise<T>): Promise<T> => {\n return new Promise<T>((resolve, reject) => {\n queue.push(() => {\n fn().then(\n (value) => {\n activeCount--;\n tryNext();\n resolve(value);\n },\n (error) => {\n activeCount--;\n tryNext();\n reject(error);\n },\n );\n });\n tryNext();\n });\n };\n\n const setLimit = (n: number) => {\n limit = n;\n // If the limit increased, unblock any waiting requests.\n tryNext();\n };\n\n return { execute, setLimit };\n}\n\n// Matches /v2/cdn/stories/<identifier> — a single story fetch (including nested slugs).\nconst SINGLE_STORY_PATH_RE = /\\/v2\\/cdn\\/stories\\/.+$/;\n\n/**\n * Maps a request path + query to one of the four rate-limit tiers.\n * Used by the auto-detection mode of `createThrottleManager`.\n */\nexport function determineTier(path: string, query: Record<string, unknown>): TierName {\n if (SINGLE_STORY_PATH_RE.test(path)) {\n return 'SINGLE_OR_SMALL';\n }\n\n const rawPerPage = query.per_page;\n const perPage\n = typeof rawPerPage === 'number'\n ? rawPerPage\n : typeof rawPerPage === 'string'\n ? Number.parseInt(rawPerPage, 10) || DEFAULT_PER_PAGE\n : DEFAULT_PER_PAGE;\n\n if (perPage <= PER_PAGE_THRESHOLDS.SMALL) {\n return 'SINGLE_OR_SMALL';\n }\n if (perPage <= PER_PAGE_THRESHOLDS.MEDIUM) {\n return 'MEDIUM';\n }\n if (perPage <= PER_PAGE_THRESHOLDS.LARGE) {\n return 'LARGE';\n }\n return 'VERY_LARGE';\n}\n\n/**\n * Extracts the quota (`q=`) value from the `X-RateLimit-Policy` response header.\n * Returns `undefined` if the header is absent or unparseable.\n *\n * Example header: `\"concurrent-requests\";q=30`\n */\nexport function parseRateLimitPolicyHeader(response: Response): number | undefined {\n const policy = response.headers.get('x-ratelimit-policy');\n if (!policy) {\n return undefined;\n }\n const match = policy.match(/q=(\\d+)/);\n if (!match) {\n return undefined;\n }\n return Math.min(Number.parseInt(match[1], 10), MAX_RATE_LIMIT);\n}\n\n/**\n * Creates a `ThrottleManager` from the user-supplied `rateLimit` config.\n *\n * - `false` → no throttling (passthrough)\n * - `number` → fixed single queue at that limit\n * - `{ maxConcurrency: n }` → fixed single queue at n req/s\n * - `{}` / `undefined` (default)→ auto-detect tier from path + per_page\n */\nexport function createThrottleManager(config: RateLimitConfig | number | false): ThrottleManager {\n // Disabled — every request goes straight through.\n if (config === false) {\n return {\n execute: (_path, _query, fn) => fn(),\n adaptToResponse: () => {},\n };\n }\n\n const resolvedConfig: RateLimitConfig = typeof config === 'number' ? { maxConcurrency: config } : config;\n const { maxConcurrency, adaptToServerHeaders = true } = resolvedConfig;\n\n // Fixed-limit mode — single queue, optional server-header adaptation.\n if (maxConcurrency !== undefined) {\n const cappedLimit = Math.min(maxConcurrency, MAX_RATE_LIMIT);\n const throttle = createThrottle(cappedLimit);\n\n return {\n execute: (_path, _query, fn) => throttle.execute(fn),\n adaptToResponse: (response) => {\n if (!adaptToServerHeaders || response === undefined) {\n return;\n }\n const serverLimit = parseRateLimitPolicyHeader(response);\n if (serverLimit !== undefined) {\n // Never exceed the user-configured ceiling.\n throttle.setLimit(Math.min(cappedLimit, serverLimit));\n }\n },\n };\n }\n\n // Auto-detect mode — one throttle per tier, tier chosen per request.\n const throttles: Record<TierName, Throttle> = {\n SINGLE_OR_SMALL: createThrottle(TIER_LIMITS.SINGLE_OR_SMALL),\n MEDIUM: createThrottle(TIER_LIMITS.MEDIUM),\n LARGE: createThrottle(TIER_LIMITS.LARGE),\n VERY_LARGE: createThrottle(TIER_LIMITS.VERY_LARGE),\n };\n\n return {\n execute: (path, query, fn) => {\n const tier = determineTier(path, query);\n return throttles[tier].execute(fn);\n },\n adaptToResponse: (response) => {\n if (!adaptToServerHeaders || response === undefined) {\n return;\n }\n const serverLimit = parseRateLimitPolicyHeader(response);\n if (serverLimit !== undefined) {\n // The SINGLE_OR_SMALL tier is the most common; adapting it covers the\n // majority of requests. Other tiers are already conservatively limited.\n throttles.SINGLE_OR_SMALL.setLimit(Math.min(TIER_LIMITS.SINGLE_OR_SMALL, serverLimit));\n }\n },\n };\n}\n"],"mappings":";;;;;;;;;AASA,MAAM,cAAc;CAClB,iBAAiB;CACjB,QAAQ;CACR,OAAO;CACP,YAAY;CACb;AAID,MAAM,sBAAsB;CAC1B,OAAO;CACP,QAAQ;CACR,OAAO;CACR;AAED,MAAM,mBAAmB;AACzB,MAAM,iBAAiB;;;;;;;AAiCvB,SAAgB,eAAe,cAAgC;CAC7D,IAAI,QAAQ;CACZ,IAAI,cAAc;CAClB,MAAM,QAA2B,EAAE;CAEnC,MAAM,gBAAgB;AACpB,SAAO,MAAM,SAAS,KAAK,cAAc,OAAO;AAC9C;AAEA,GADY,MAAM,OAAO,EACpB;;;CAIT,MAAM,WAAc,OAAqC;AACvD,SAAO,IAAI,SAAY,SAAS,WAAW;AACzC,SAAM,WAAW;AACf,QAAI,CAAC,MACF,UAAU;AACT;AACA,cAAS;AACT,aAAQ,MAAM;QAEf,UAAU;AACT;AACA,cAAS;AACT,YAAO,MAAM;MAEhB;KACD;AACF,YAAS;IACT;;CAGJ,MAAM,YAAY,MAAc;AAC9B,UAAQ;AAER,WAAS;;AAGX,QAAO;EAAE;EAAS;EAAU;;AAI9B,MAAM,uBAAuB;;;;;AAM7B,SAAgB,cAAc,MAAc,OAA0C;AACpF,KAAI,qBAAqB,KAAK,KAAK,CACjC,QAAO;CAGT,MAAM,aAAa,MAAM;CACzB,MAAM,UACF,OAAO,eAAe,WACpB,aACA,OAAO,eAAe,WACpB,OAAO,SAAS,YAAY,GAAG,IAAI,mBACnC;AAER,KAAI,WAAW,oBAAoB,MACjC,QAAO;AAET,KAAI,WAAW,oBAAoB,OACjC,QAAO;AAET,KAAI,WAAW,oBAAoB,MACjC,QAAO;AAET,QAAO;;;;;;;;AAST,SAAgB,2BAA2B,UAAwC;CACjF,MAAM,SAAS,SAAS,QAAQ,IAAI,qBAAqB;AACzD,KAAI,CAAC,OACH;CAEF,MAAM,QAAQ,OAAO,MAAM,UAAU;AACrC,KAAI,CAAC,MACH;AAEF,QAAO,KAAK,IAAI,OAAO,SAAS,MAAM,IAAI,GAAG,EAAE,eAAe;;;;;;;;;;AAWhE,SAAgB,sBAAsB,QAA2D;AAE/F,KAAI,WAAW,MACb,QAAO;EACL,UAAU,OAAO,QAAQ,OAAO,IAAI;EACpC,uBAAuB;EACxB;CAIH,MAAM,EAAE,gBAAgB,uBAAuB,SADP,OAAO,WAAW,WAAW,EAAE,gBAAgB,QAAQ,GAAG;AAIlG,KAAI,mBAAmB,QAAW;EAChC,MAAM,cAAc,KAAK,IAAI,gBAAgB,eAAe;EAC5D,MAAM,WAAW,eAAe,YAAY;AAE5C,SAAO;GACL,UAAU,OAAO,QAAQ,OAAO,SAAS,QAAQ,GAAG;GACpD,kBAAkB,aAAa;AAC7B,QAAI,CAAC,wBAAwB,aAAa,OACxC;IAEF,MAAM,cAAc,2BAA2B,SAAS;AACxD,QAAI,gBAAgB,OAElB,UAAS,SAAS,KAAK,IAAI,aAAa,YAAY,CAAC;;GAG1D;;CAIH,MAAM,YAAwC;EAC5C,iBAAiB,eAAe,YAAY,gBAAgB;EAC5D,QAAQ,eAAe,YAAY,OAAO;EAC1C,OAAO,eAAe,YAAY,MAAM;EACxC,YAAY,eAAe,YAAY,WAAW;EACnD;AAED,QAAO;EACL,UAAU,MAAM,OAAO,OAAO;AAE5B,UAAO,UADM,cAAc,MAAM,MAAM,EAChB,QAAQ,GAAG;;EAEpC,kBAAkB,aAAa;AAC7B,OAAI,CAAC,wBAAwB,aAAa,OACxC;GAEF,MAAM,cAAc,2BAA2B,SAAS;AACxD,OAAI,gBAAgB,OAGlB,WAAU,gBAAgB,SAAS,KAAK,IAAI,YAAY,iBAAiB,YAAY,CAAC;;EAG3F"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@storyblok/api-client",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.2",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Storyblok Content Delivery API Client",
|
|
7
7
|
"author": "",
|
|
@@ -40,11 +40,12 @@
|
|
|
40
40
|
"eslint": "^9.39.2",
|
|
41
41
|
"glob": "^13.0.6",
|
|
42
42
|
"msw": "^2.12.9",
|
|
43
|
+
"pathe": "^2.0.3",
|
|
43
44
|
"tsdown": "^0.20.3",
|
|
44
45
|
"tsx": "^4.21.0",
|
|
45
46
|
"vitest": "^4.0.18",
|
|
46
47
|
"@storyblok/eslint-config": "0.5.0",
|
|
47
|
-
"@storyblok/openapi": "
|
|
48
|
+
"@storyblok/openapi": "2.0.0"
|
|
48
49
|
},
|
|
49
50
|
"nx": {
|
|
50
51
|
"targets": {
|