@storyblok/api-client 0.3.1 → 1.0.0-alpha.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 +3 -1
- package/dist/client.cjs +179 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +567 -0
- package/dist/client.d.mts +567 -0
- package/dist/client.mjs +178 -0
- package/dist/client.mjs.map +1 -0
- package/dist/error.cjs.map +1 -1
- package/dist/error.d.cts +13 -2
- package/dist/error.d.mts +13 -2
- package/dist/error.mjs.map +1 -1
- package/dist/generated/capi/_internal.gen.d.cts +120 -0
- package/dist/generated/capi/_internal.gen.d.mts +120 -0
- package/dist/generated/{shared → capi}/client/client.gen.cjs +1 -1
- package/dist/generated/capi/client/client.gen.cjs.map +1 -0
- package/dist/generated/capi/client/client.gen.d.mts +1 -0
- package/dist/generated/{shared → capi}/client/client.gen.mjs +1 -1
- package/dist/generated/capi/client/client.gen.mjs.map +1 -0
- package/dist/generated/capi/client/index.d.mts +3 -0
- package/dist/generated/{shared → capi}/client/types.gen.d.cts +40 -4
- package/dist/generated/{shared → capi}/client/types.gen.d.mts +40 -4
- package/dist/generated/{shared → capi}/client/utils.gen.cjs +1 -1
- package/dist/generated/capi/client/utils.gen.cjs.map +1 -0
- package/dist/generated/{shared → capi}/client/utils.gen.d.cts +1 -1
- package/dist/generated/{shared → capi}/client/utils.gen.d.mts +1 -1
- package/dist/generated/{shared → capi}/client/utils.gen.mjs +1 -1
- package/dist/generated/capi/client/utils.gen.mjs.map +1 -0
- package/dist/generated/capi/client.gen.cjs +10 -0
- package/dist/generated/capi/client.gen.cjs.map +1 -0
- package/dist/generated/capi/client.gen.mjs +10 -0
- package/dist/generated/capi/client.gen.mjs.map +1 -0
- package/dist/generated/{shared → capi}/core/auth.gen.cjs +1 -1
- package/dist/generated/capi/core/auth.gen.cjs.map +1 -0
- package/dist/generated/{shared → capi}/core/auth.gen.d.cts +1 -1
- package/dist/generated/{shared → capi}/core/auth.gen.d.mts +1 -1
- package/dist/generated/{shared → capi}/core/auth.gen.mjs +1 -1
- package/dist/generated/capi/core/auth.gen.mjs.map +1 -0
- package/dist/generated/{shared → capi}/core/bodySerializer.gen.cjs +1 -1
- package/dist/generated/capi/core/bodySerializer.gen.cjs.map +1 -0
- package/dist/generated/{shared → capi}/core/bodySerializer.gen.d.cts +1 -1
- package/dist/generated/{shared → capi}/core/bodySerializer.gen.d.mts +1 -1
- package/dist/generated/{shared → capi}/core/bodySerializer.gen.mjs +1 -1
- package/dist/generated/capi/core/bodySerializer.gen.mjs.map +1 -0
- package/dist/generated/{shared → capi}/core/params.gen.cjs +1 -1
- package/dist/generated/capi/core/params.gen.cjs.map +1 -0
- package/dist/generated/{shared → capi}/core/params.gen.mjs +1 -1
- package/dist/generated/capi/core/params.gen.mjs.map +1 -0
- package/dist/generated/{shared → capi}/core/pathSerializer.gen.cjs +1 -1
- package/dist/generated/capi/core/pathSerializer.gen.cjs.map +1 -0
- package/dist/generated/{shared → capi}/core/pathSerializer.gen.d.cts +1 -1
- package/dist/generated/{shared → capi}/core/pathSerializer.gen.d.mts +1 -1
- package/dist/generated/{shared → capi}/core/pathSerializer.gen.mjs +1 -1
- package/dist/generated/capi/core/pathSerializer.gen.mjs.map +1 -0
- package/dist/generated/{shared → capi}/core/serverSentEvents.gen.cjs +1 -1
- package/dist/generated/capi/core/serverSentEvents.gen.cjs.map +1 -0
- package/dist/generated/{shared → capi}/core/serverSentEvents.gen.d.cts +5 -2
- package/dist/generated/{shared → capi}/core/serverSentEvents.gen.d.mts +5 -2
- package/dist/generated/{shared → capi}/core/serverSentEvents.gen.mjs +1 -1
- package/dist/generated/capi/core/serverSentEvents.gen.mjs.map +1 -0
- package/dist/generated/{shared → capi}/core/types.gen.d.cts +15 -2
- package/dist/generated/{shared → capi}/core/types.gen.d.mts +15 -2
- package/dist/generated/{shared → capi}/core/utils.gen.cjs +1 -1
- package/dist/generated/capi/core/utils.gen.cjs.map +1 -0
- package/dist/generated/{shared → capi}/core/utils.gen.mjs +1 -1
- package/dist/generated/capi/core/utils.gen.mjs.map +1 -0
- package/dist/generated/capi/sdk.gen.cjs +141 -0
- package/dist/generated/capi/sdk.gen.cjs.map +1 -0
- package/dist/generated/capi/sdk.gen.mjs +133 -0
- package/dist/generated/capi/sdk.gen.mjs.map +1 -0
- package/dist/generated/capi/types-aliased.gen.d.cts +181 -0
- package/dist/generated/capi/types-aliased.gen.d.mts +181 -0
- package/dist/generated/capi/types.gen.d.cts +919 -0
- package/dist/generated/capi/types.gen.d.mts +919 -0
- package/dist/generated/mapi/_internal.gen.d.cts +146 -0
- package/dist/generated/mapi/_internal.gen.d.mts +146 -0
- package/dist/generated/mapi/types.gen.d.cts +293 -0
- package/dist/generated/mapi/types.gen.d.mts +293 -0
- package/dist/generated/overlay/_internal.gen.d.cts +831 -0
- package/dist/generated/overlay/_internal.gen.d.mts +831 -0
- package/dist/generated/types/_utils.d.cts +7 -0
- package/dist/generated/types/_utils.d.mts +7 -0
- package/dist/generated/types/block.d.cts +29 -0
- package/dist/generated/types/block.d.mts +29 -0
- package/dist/generated/types/field.d.cts +64 -0
- package/dist/generated/types/field.d.mts +64 -0
- package/dist/generated/types/story.d.cts +19 -0
- package/dist/generated/types/story.d.mts +19 -0
- package/dist/index.cjs +2 -154
- package/dist/index.d.cts +8 -184
- package/dist/index.d.mts +8 -184
- package/dist/index.mjs +2 -151
- package/dist/resources/datasource-entries.cjs +2 -2
- package/dist/resources/datasource-entries.cjs.map +1 -1
- package/dist/resources/datasource-entries.mjs +2 -2
- package/dist/resources/datasource-entries.mjs.map +1 -1
- package/dist/resources/datasources.cjs +4 -4
- package/dist/resources/datasources.cjs.map +1 -1
- package/dist/resources/datasources.mjs +4 -4
- package/dist/resources/datasources.mjs.map +1 -1
- package/dist/resources/experiments.cjs +26 -0
- package/dist/resources/experiments.cjs.map +1 -0
- package/dist/resources/experiments.mjs +26 -0
- package/dist/resources/experiments.mjs.map +1 -0
- package/dist/resources/links.cjs +2 -2
- package/dist/resources/links.cjs.map +1 -1
- package/dist/resources/links.mjs +2 -2
- package/dist/resources/links.mjs.map +1 -1
- package/dist/resources/spaces.cjs +4 -4
- package/dist/resources/spaces.cjs.map +1 -1
- package/dist/resources/spaces.mjs +4 -4
- package/dist/resources/spaces.mjs.map +1 -1
- package/dist/resources/stories.cjs +16 -12
- package/dist/resources/stories.cjs.map +1 -1
- package/dist/resources/stories.d.cts +82 -4
- package/dist/resources/stories.d.mts +82 -4
- package/dist/resources/stories.mjs +16 -12
- 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 +2 -2
- package/dist/resources/tags.mjs.map +1 -1
- package/dist/utils/fetch-rel-uuids.cjs +2 -2
- 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/inline-relations.cjs.map +1 -1
- package/dist/utils/inline-relations.mjs.map +1 -1
- package/dist/utils/rate-limit.cjs +9 -5
- package/dist/utils/rate-limit.cjs.map +1 -1
- package/dist/utils/rate-limit.d.cts +4 -18
- package/dist/utils/rate-limit.d.mts +4 -18
- package/dist/utils/rate-limit.mjs +10 -4
- package/dist/utils/rate-limit.mjs.map +1 -1
- package/package.json +16 -12
- package/dist/generated/datasource_entries/client.gen.cjs +0 -10
- package/dist/generated/datasource_entries/client.gen.cjs.map +0 -1
- package/dist/generated/datasource_entries/client.gen.mjs +0 -10
- package/dist/generated/datasource_entries/client.gen.mjs.map +0 -1
- package/dist/generated/datasource_entries/sdk.gen.cjs +0 -21
- package/dist/generated/datasource_entries/sdk.gen.cjs.map +0 -1
- package/dist/generated/datasource_entries/sdk.gen.mjs +0 -21
- package/dist/generated/datasource_entries/sdk.gen.mjs.map +0 -1
- package/dist/generated/datasource_entries/types.gen.d.cts +0 -67
- package/dist/generated/datasource_entries/types.gen.d.mts +0 -67
- package/dist/generated/datasources/client.gen.cjs +0 -10
- package/dist/generated/datasources/client.gen.cjs.map +0 -1
- package/dist/generated/datasources/client.gen.mjs +0 -10
- package/dist/generated/datasources/client.gen.mjs.map +0 -1
- package/dist/generated/datasources/sdk.gen.cjs +0 -36
- package/dist/generated/datasources/sdk.gen.cjs.map +0 -1
- package/dist/generated/datasources/sdk.gen.mjs +0 -35
- package/dist/generated/datasources/sdk.gen.mjs.map +0 -1
- package/dist/generated/datasources/types.gen.d.cts +0 -109
- package/dist/generated/datasources/types.gen.d.mts +0 -109
- package/dist/generated/links/client.gen.cjs +0 -10
- package/dist/generated/links/client.gen.cjs.map +0 -1
- package/dist/generated/links/client.gen.mjs +0 -10
- package/dist/generated/links/client.gen.mjs.map +0 -1
- package/dist/generated/links/sdk.gen.cjs +0 -21
- package/dist/generated/links/sdk.gen.cjs.map +0 -1
- package/dist/generated/links/sdk.gen.mjs +0 -21
- package/dist/generated/links/sdk.gen.mjs.map +0 -1
- package/dist/generated/links/types.gen.d.cts +0 -142
- package/dist/generated/links/types.gen.d.mts +0 -142
- package/dist/generated/shared/client/client.gen.cjs.map +0 -1
- package/dist/generated/shared/client/client.gen.mjs.map +0 -1
- package/dist/generated/shared/client/utils.gen.cjs.map +0 -1
- package/dist/generated/shared/client/utils.gen.mjs.map +0 -1
- package/dist/generated/shared/core/auth.gen.cjs.map +0 -1
- package/dist/generated/shared/core/auth.gen.mjs.map +0 -1
- package/dist/generated/shared/core/bodySerializer.gen.cjs.map +0 -1
- package/dist/generated/shared/core/bodySerializer.gen.mjs.map +0 -1
- package/dist/generated/shared/core/params.gen.cjs.map +0 -1
- package/dist/generated/shared/core/params.gen.mjs.map +0 -1
- package/dist/generated/shared/core/pathSerializer.gen.cjs.map +0 -1
- package/dist/generated/shared/core/pathSerializer.gen.mjs.map +0 -1
- package/dist/generated/shared/core/serverSentEvents.gen.cjs.map +0 -1
- package/dist/generated/shared/core/serverSentEvents.gen.mjs.map +0 -1
- package/dist/generated/shared/core/utils.gen.cjs.map +0 -1
- package/dist/generated/shared/core/utils.gen.mjs.map +0 -1
- package/dist/generated/spaces/client.gen.cjs +0 -10
- package/dist/generated/spaces/client.gen.cjs.map +0 -1
- package/dist/generated/spaces/client.gen.mjs +0 -10
- package/dist/generated/spaces/client.gen.mjs.map +0 -1
- package/dist/generated/spaces/sdk.gen.cjs +0 -21
- package/dist/generated/spaces/sdk.gen.cjs.map +0 -1
- package/dist/generated/spaces/sdk.gen.mjs +0 -21
- package/dist/generated/spaces/sdk.gen.mjs.map +0 -1
- package/dist/generated/spaces/types.gen.d.cts +0 -34
- package/dist/generated/spaces/types.gen.d.mts +0 -34
- package/dist/generated/stories/client.gen.cjs +0 -10
- package/dist/generated/stories/client.gen.cjs.map +0 -1
- package/dist/generated/stories/client.gen.mjs +0 -10
- package/dist/generated/stories/client.gen.mjs.map +0 -1
- package/dist/generated/stories/sdk.gen.cjs +0 -36
- package/dist/generated/stories/sdk.gen.cjs.map +0 -1
- package/dist/generated/stories/sdk.gen.mjs +0 -35
- package/dist/generated/stories/sdk.gen.mjs.map +0 -1
- package/dist/generated/stories/types.gen.d.cts +0 -544
- package/dist/generated/stories/types.gen.d.mts +0 -544
- package/dist/generated/tags/client.gen.cjs +0 -10
- package/dist/generated/tags/client.gen.cjs.map +0 -1
- package/dist/generated/tags/client.gen.mjs +0 -10
- package/dist/generated/tags/client.gen.mjs.map +0 -1
- package/dist/generated/tags/sdk.gen.cjs +0 -21
- package/dist/generated/tags/sdk.gen.cjs.map +0 -1
- package/dist/generated/tags/sdk.gen.mjs +0 -21
- package/dist/generated/tags/sdk.gen.mjs.map +0 -1
- package/dist/generated/tags/types.gen.d.cts +0 -44
- package/dist/generated/tags/types.gen.d.mts +0 -44
- package/dist/index.cjs.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/types.d.cts +0 -37
- package/dist/types.d.mts +0 -37
- /package/dist/generated/{shared → capi}/client/index.cjs +0 -0
- /package/dist/generated/{shared → capi}/client/index.mjs +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-rel-uuids.cjs","names":["chunkArray","
|
|
1
|
+
{"version":3,"file":"fetch-rel-uuids.cjs","names":["chunkArray","listStories"],"sources":["../../src/utils/fetch-rel-uuids.ts"],"sourcesContent":["import type { Client } from '../generated/capi/client';\nimport { listStories } from '../generated/capi/sdk.gen';\nimport type { Story } from '../generated/types/story';\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<Story[]> => {\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 listStories({\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 // SDK return shape is the raw `DraftStory | PublishedStory` union; cast\n // to the wrapper `Story` (public type) so callers don't have to know\n // about the spec-level discriminated form and get the benefits of the\n // generic `Story` with schema support.\n return response.data.stories as unknown as Story[];\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,sBACoD;CACpD,MAAM,eAAe;EAAE,GAAG,iBAAiB,UAAU;EAAE,UAAU;EAAiB;CAClF,MAAM,SAASA,yBAAW,OAAO,gBAAgB;AAiCjD,SA/BgB,MAAM,QAAQ,IAC5B,OAAO,KAAI,UACT,gBAAgB,QAAQ,mBAAmB,cAAc,YAAY;EACnE,MAAM,WAAW,MAAMC,4BAAY;GACjC;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;AAOvD,SAAO,SAAS,KAAK;GACrB,CACH,CACF,EAEc,MAAM"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { listStories } from "../generated/capi/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 listStories({
|
|
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/
|
|
1
|
+
{"version":3,"file":"fetch-rel-uuids.mjs","names":[],"sources":["../../src/utils/fetch-rel-uuids.ts"],"sourcesContent":["import type { Client } from '../generated/capi/client';\nimport { listStories } from '../generated/capi/sdk.gen';\nimport type { Story } from '../generated/types/story';\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<Story[]> => {\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 listStories({\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 // SDK return shape is the raw `DraftStory | PublishedStory` union; cast\n // to the wrapper `Story` (public type) so callers don't have to know\n // about the spec-level discriminated form and get the benefits of the\n // generic `Story` with schema support.\n return response.data.stories as unknown as Story[];\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,sBACoD;CACpD,MAAM,eAAe;EAAE,GAAG,iBAAiB,UAAU;EAAE,UAAU;EAAiB;CAClF,MAAM,SAAS,WAAW,OAAO,gBAAgB;AAiCjD,SA/BgB,MAAM,QAAQ,IAC5B,OAAO,KAAI,UACT,gBAAgB,QAAQ,mBAAmB,cAAc,YAAY;EACnE,MAAM,WAAW,MAAM,YAAY;GACjC;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;AAOvD,SAAO,SAAS,KAAK;GACrB,CACH,CACF,EAEc,MAAM"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inline-relations.cjs","names":["fetchMissingRelations"],"sources":["../../src/utils/inline-relations.ts"],"sourcesContent":["import type { Client } from '../generated/
|
|
1
|
+
{"version":3,"file":"inline-relations.cjs","names":["fetchMissingRelations"],"sources":["../../src/utils/inline-relations.ts"],"sourcesContent":["import type { Client } from '../generated/capi/client';\nimport type { Story } from '../generated/types/story';\nimport type { StoryWithInlinedRelations } from '../resources/stories';\nimport { fetchMissingRelations } from './fetch-rel-uuids';\nimport type { ThrottleManager } from './rate-limit';\n\ntype RelationPath = `${string}.${string}`;\n\ninterface ComponentNode {\n _uid: string;\n component: string;\n [key: string]: unknown;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n value !== null && typeof value === 'object' && !Array.isArray(value);\n\nconst isComponentNode = (value: Record<string, unknown>): value is ComponentNode =>\n typeof value.component === 'string' && typeof value._uid === 'string';\n\n/**\n * Decodes a string if it appears to be URL-encoded.\n * Detects common encoded characters (%2C for comma, %20 for space, etc.)\n */\nconst decodeIfEncoded = (value: string): string => {\n // Check if the string contains URL-encoded characters (% followed by hex digits)\n if (/%[0-9A-F]{2}/i.test(value)) {\n try {\n return decodeURIComponent(value);\n }\n catch {\n // If decoding fails (malformed encoding), return original\n return value;\n }\n }\n return value;\n};\n\nconst inlineStoryContentInternal = <TStory extends Story | StoryWithInlinedRelations>(\n story: TStory,\n relationPaths: ReadonlySet<RelationPath>,\n relationMap: ReadonlyMap<string, TStory>,\n resolved: Map<string, TStory>,\n): TStory => {\n const existingStory = resolved.get(story.uuid);\n if (existingStory) {\n return existingStory;\n }\n\n const clonedStory = structuredClone(story);\n resolved.set(story.uuid, clonedStory);\n // resolveNode returns `unknown` to handle arbitrary JSON trees; shape is preserved at runtime.\n clonedStory.content = resolveNode(clonedStory.content, relationMap, relationPaths, resolved) as Story['content'];\n return clonedStory;\n};\n\nfunction resolveNode<TStory extends Story | StoryWithInlinedRelations>(\n value: unknown,\n relationMap: ReadonlyMap<string, TStory>,\n relationPaths: ReadonlySet<RelationPath>,\n resolved: Map<string, TStory>,\n): unknown {\n if (Array.isArray(value)) {\n return value.map(item => resolveNode(item, relationMap, relationPaths, resolved));\n }\n\n if (!isRecord(value)) {\n return value;\n }\n\n if (isComponentNode(value)) {\n for (const [fieldName, fieldValue] of Object.entries(value)) {\n if (fieldName === 'component' || fieldName === '_uid') {\n continue;\n }\n\n const relationPath: RelationPath = `${value.component}.${fieldName}`;\n value[fieldName] = relationPaths.has(relationPath)\n ? resolveFieldValue(fieldValue, relationMap, relationPaths, resolved)\n : resolveNode(fieldValue, relationMap, relationPaths, resolved);\n }\n\n return value;\n }\n\n for (const [fieldName, fieldValue] of Object.entries(value)) {\n value[fieldName] = resolveNode(fieldValue, relationMap, relationPaths, resolved);\n }\n\n return value;\n}\n\nexport const parseResolveRelations = (query: Record<string, unknown>): RelationPath[] => {\n if (typeof query.resolve_relations !== 'string') {\n return [];\n }\n\n // Decode URL-encoded strings to handle pre-encoded input\n const resolveRelations = decodeIfEncoded(query.resolve_relations);\n\n return resolveRelations\n .split(',')\n .map(path => path.trim())\n .filter((path): path is RelationPath => {\n const [component = '', field = '', ...rest] = path.split('.');\n return component.length > 0 && field.length > 0 && rest.length === 0;\n });\n};\n\nexport const buildRelationMap = (rels: Array<Story> | undefined): Map<string, Story> => {\n const relationMap = new Map<string, Story>();\n\n for (const story of rels ?? []) {\n relationMap.set(story.uuid, story);\n }\n\n return relationMap;\n};\n\nfunction resolveFieldValue<TStory extends Story | StoryWithInlinedRelations>(\n value: unknown,\n relationMap: ReadonlyMap<string, TStory>,\n relationPaths: ReadonlySet<RelationPath>,\n resolved: Map<string, TStory>,\n): unknown {\n if (typeof value === 'string') {\n const relatedStory = relationMap.get(value);\n if (!relatedStory) {\n return value;\n }\n\n return inlineStoryContentInternal(relatedStory, relationPaths, relationMap, resolved);\n }\n\n if (Array.isArray(value)) {\n return value.map(item => resolveFieldValue(item, relationMap, relationPaths, resolved));\n }\n\n return resolveNode(value, relationMap, relationPaths, resolved);\n}\n\nexport const inlineStoryContent = <TStory extends Story | StoryWithInlinedRelations>(\n story: TStory,\n relationPaths: RelationPath[],\n relationMap: ReadonlyMap<string, TStory>,\n): TStory => {\n const normalizedPaths = new Set(relationPaths);\n const resolved = new Map<string, TStory>();\n return inlineStoryContentInternal(story, normalizedPaths, relationMap, resolved);\n};\n\nexport const inlineStoriesContent = <TStory extends Story | StoryWithInlinedRelations>(\n stories: Array<TStory>,\n relationPaths: RelationPath[],\n relationMap: ReadonlyMap<string, TStory>,\n): Array<TStory> => {\n const normalizedPaths = new Set(relationPaths);\n const resolved = new Map<string, TStory>();\n return stories.map(story => inlineStoryContentInternal(story, normalizedPaths, relationMap, resolved));\n};\n\ninterface ResolveRelationMapOptions {\n client: Client;\n throttleManager: ThrottleManager;\n}\n\nexport interface ResolvedRelations {\n relationPaths: RelationPath[];\n relationMap: Map<string, Story>;\n}\n\n/**\n * Parses relation paths from the request query, builds a relation map from the\n * response's `rels`, and fetches any additional relations referenced by `rel_uuids`.\n *\n * Returns `null` when there is nothing to inline (no `resolve_relations` in the query).\n */\nexport const resolveRelationMap = async (\n responseData: { rels?: Story[]; rel_uuids?: string[] },\n requestQuery: Record<string, unknown>,\n { client, throttleManager }: ResolveRelationMapOptions,\n): Promise<ResolvedRelations | null> => {\n const relationPaths = parseResolveRelations(requestQuery);\n if (relationPaths.length === 0) {\n return null;\n }\n\n const relationMap = buildRelationMap(responseData.rels);\n if (responseData.rel_uuids?.length) {\n const missingUuids = responseData.rel_uuids.filter(uuid => !relationMap.has(uuid));\n if (missingUuids.length > 0) {\n const fetchedRelations = await fetchMissingRelations({\n client,\n uuids: missingUuids,\n baseQuery: requestQuery,\n throttleManager,\n });\n for (const relationStory of fetchedRelations) {\n relationMap.set(relationStory.uuid, relationStory);\n }\n }\n }\n\n return { relationPaths, relationMap };\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inline-relations.mjs","names":[],"sources":["../../src/utils/inline-relations.ts"],"sourcesContent":["import type { Client } from '../generated/
|
|
1
|
+
{"version":3,"file":"inline-relations.mjs","names":[],"sources":["../../src/utils/inline-relations.ts"],"sourcesContent":["import type { Client } from '../generated/capi/client';\nimport type { Story } from '../generated/types/story';\nimport type { StoryWithInlinedRelations } from '../resources/stories';\nimport { fetchMissingRelations } from './fetch-rel-uuids';\nimport type { ThrottleManager } from './rate-limit';\n\ntype RelationPath = `${string}.${string}`;\n\ninterface ComponentNode {\n _uid: string;\n component: string;\n [key: string]: unknown;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n value !== null && typeof value === 'object' && !Array.isArray(value);\n\nconst isComponentNode = (value: Record<string, unknown>): value is ComponentNode =>\n typeof value.component === 'string' && typeof value._uid === 'string';\n\n/**\n * Decodes a string if it appears to be URL-encoded.\n * Detects common encoded characters (%2C for comma, %20 for space, etc.)\n */\nconst decodeIfEncoded = (value: string): string => {\n // Check if the string contains URL-encoded characters (% followed by hex digits)\n if (/%[0-9A-F]{2}/i.test(value)) {\n try {\n return decodeURIComponent(value);\n }\n catch {\n // If decoding fails (malformed encoding), return original\n return value;\n }\n }\n return value;\n};\n\nconst inlineStoryContentInternal = <TStory extends Story | StoryWithInlinedRelations>(\n story: TStory,\n relationPaths: ReadonlySet<RelationPath>,\n relationMap: ReadonlyMap<string, TStory>,\n resolved: Map<string, TStory>,\n): TStory => {\n const existingStory = resolved.get(story.uuid);\n if (existingStory) {\n return existingStory;\n }\n\n const clonedStory = structuredClone(story);\n resolved.set(story.uuid, clonedStory);\n // resolveNode returns `unknown` to handle arbitrary JSON trees; shape is preserved at runtime.\n clonedStory.content = resolveNode(clonedStory.content, relationMap, relationPaths, resolved) as Story['content'];\n return clonedStory;\n};\n\nfunction resolveNode<TStory extends Story | StoryWithInlinedRelations>(\n value: unknown,\n relationMap: ReadonlyMap<string, TStory>,\n relationPaths: ReadonlySet<RelationPath>,\n resolved: Map<string, TStory>,\n): unknown {\n if (Array.isArray(value)) {\n return value.map(item => resolveNode(item, relationMap, relationPaths, resolved));\n }\n\n if (!isRecord(value)) {\n return value;\n }\n\n if (isComponentNode(value)) {\n for (const [fieldName, fieldValue] of Object.entries(value)) {\n if (fieldName === 'component' || fieldName === '_uid') {\n continue;\n }\n\n const relationPath: RelationPath = `${value.component}.${fieldName}`;\n value[fieldName] = relationPaths.has(relationPath)\n ? resolveFieldValue(fieldValue, relationMap, relationPaths, resolved)\n : resolveNode(fieldValue, relationMap, relationPaths, resolved);\n }\n\n return value;\n }\n\n for (const [fieldName, fieldValue] of Object.entries(value)) {\n value[fieldName] = resolveNode(fieldValue, relationMap, relationPaths, resolved);\n }\n\n return value;\n}\n\nexport const parseResolveRelations = (query: Record<string, unknown>): RelationPath[] => {\n if (typeof query.resolve_relations !== 'string') {\n return [];\n }\n\n // Decode URL-encoded strings to handle pre-encoded input\n const resolveRelations = decodeIfEncoded(query.resolve_relations);\n\n return resolveRelations\n .split(',')\n .map(path => path.trim())\n .filter((path): path is RelationPath => {\n const [component = '', field = '', ...rest] = path.split('.');\n return component.length > 0 && field.length > 0 && rest.length === 0;\n });\n};\n\nexport const buildRelationMap = (rels: Array<Story> | undefined): Map<string, Story> => {\n const relationMap = new Map<string, Story>();\n\n for (const story of rels ?? []) {\n relationMap.set(story.uuid, story);\n }\n\n return relationMap;\n};\n\nfunction resolveFieldValue<TStory extends Story | StoryWithInlinedRelations>(\n value: unknown,\n relationMap: ReadonlyMap<string, TStory>,\n relationPaths: ReadonlySet<RelationPath>,\n resolved: Map<string, TStory>,\n): unknown {\n if (typeof value === 'string') {\n const relatedStory = relationMap.get(value);\n if (!relatedStory) {\n return value;\n }\n\n return inlineStoryContentInternal(relatedStory, relationPaths, relationMap, resolved);\n }\n\n if (Array.isArray(value)) {\n return value.map(item => resolveFieldValue(item, relationMap, relationPaths, resolved));\n }\n\n return resolveNode(value, relationMap, relationPaths, resolved);\n}\n\nexport const inlineStoryContent = <TStory extends Story | StoryWithInlinedRelations>(\n story: TStory,\n relationPaths: RelationPath[],\n relationMap: ReadonlyMap<string, TStory>,\n): TStory => {\n const normalizedPaths = new Set(relationPaths);\n const resolved = new Map<string, TStory>();\n return inlineStoryContentInternal(story, normalizedPaths, relationMap, resolved);\n};\n\nexport const inlineStoriesContent = <TStory extends Story | StoryWithInlinedRelations>(\n stories: Array<TStory>,\n relationPaths: RelationPath[],\n relationMap: ReadonlyMap<string, TStory>,\n): Array<TStory> => {\n const normalizedPaths = new Set(relationPaths);\n const resolved = new Map<string, TStory>();\n return stories.map(story => inlineStoryContentInternal(story, normalizedPaths, relationMap, resolved));\n};\n\ninterface ResolveRelationMapOptions {\n client: Client;\n throttleManager: ThrottleManager;\n}\n\nexport interface ResolvedRelations {\n relationPaths: RelationPath[];\n relationMap: Map<string, Story>;\n}\n\n/**\n * Parses relation paths from the request query, builds a relation map from the\n * response's `rels`, and fetches any additional relations referenced by `rel_uuids`.\n *\n * Returns `null` when there is nothing to inline (no `resolve_relations` in the query).\n */\nexport const resolveRelationMap = async (\n responseData: { rels?: Story[]; rel_uuids?: string[] },\n requestQuery: Record<string, unknown>,\n { client, throttleManager }: ResolveRelationMapOptions,\n): Promise<ResolvedRelations | null> => {\n const relationPaths = parseResolveRelations(requestQuery);\n if (relationPaths.length === 0) {\n return null;\n }\n\n const relationMap = buildRelationMap(responseData.rels);\n if (responseData.rel_uuids?.length) {\n const missingUuids = responseData.rel_uuids.filter(uuid => !relationMap.has(uuid));\n if (missingUuids.length > 0) {\n const fetchedRelations = await fetchMissingRelations({\n client,\n uuids: missingUuids,\n baseQuery: requestQuery,\n throttleManager,\n });\n for (const relationStory of fetchedRelations) {\n relationMap.set(relationStory.uuid, relationStory);\n }\n }\n }\n\n return { relationPaths, relationMap };\n};\n"],"mappings":"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"}
|
|
@@ -77,14 +77,20 @@ function determineTier(path, query) {
|
|
|
77
77
|
return "VERY_LARGE";
|
|
78
78
|
}
|
|
79
79
|
/**
|
|
80
|
-
* Extracts the quota (`q=`) value from the `X-RateLimit-Policy` response header
|
|
81
|
-
*
|
|
80
|
+
* Extracts the quota (`q=`) value from the `X-RateLimit-Policy` response header,
|
|
81
|
+
* but only when the policy describes a rate limit — not a concurrency limit.
|
|
82
82
|
*
|
|
83
|
-
*
|
|
83
|
+
* The Storyblok CDN currently returns `"concurrent-requests";q=30` which is a
|
|
84
|
+
* concurrency limit (always ~30), not a rate limit. Applying that value to the
|
|
85
|
+
* tier-based rate limiter would incorrectly cap throughput below what the API
|
|
86
|
+
* allows. This function therefore ignores concurrency policies and only returns
|
|
87
|
+
* a value for rate-limit policies (e.g. `"rate-limit";q=50`), which the API
|
|
88
|
+
* does not send yet but may in the future.
|
|
84
89
|
*/
|
|
85
90
|
function parseRateLimitPolicyHeader(response) {
|
|
86
91
|
const policy = response.headers.get("x-ratelimit-policy");
|
|
87
92
|
if (!policy) return;
|
|
93
|
+
if (policy.includes("\"concurrent-requests\"")) return;
|
|
88
94
|
const match = policy.match(/q=(\d+)/);
|
|
89
95
|
if (!match) return;
|
|
90
96
|
return Math.min(Number.parseInt(match[1], 10), MAX_RATE_LIMIT);
|
|
@@ -134,7 +140,5 @@ function createThrottleManager(config) {
|
|
|
134
140
|
}
|
|
135
141
|
|
|
136
142
|
//#endregion
|
|
137
|
-
exports.createThrottle = createThrottle;
|
|
138
143
|
exports.createThrottleManager = createThrottleManager;
|
|
139
|
-
exports.parseRateLimitPolicyHeader = parseRateLimitPolicyHeader;
|
|
140
144
|
//# sourceMappingURL=rate-limit.cjs.map
|
|
@@ -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 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
|
|
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 * but only when the policy describes a rate limit — not a concurrency limit.\n *\n * The Storyblok CDN currently returns `\"concurrent-requests\";q=30` which is a\n * concurrency limit (always ~30), not a rate limit. Applying that value to the\n * tier-based rate limiter would incorrectly cap throughput below what the API\n * allows. This function therefore ignores concurrency policies and only returns\n * a value for rate-limit policies (e.g. `\"rate-limit\";q=50`), which the API\n * does not send yet but may in the future.\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 // Only act on rate-limit policies — skip concurrency limits like\n // \"concurrent-requests\";q=30 which represent a different constraint.\n if (policy.includes('\"concurrent-requests\"')) {\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;;;;;;;;;;;;;AAcT,SAAgB,2BAA2B,UAAwC;CACjF,MAAM,SAAS,SAAS,QAAQ,IAAI,qBAAqB;AACzD,KAAI,CAAC,OACH;AAIF,KAAI,OAAO,SAAS,0BAAwB,CAC1C;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"}
|
|
@@ -13,24 +13,10 @@ interface RateLimitConfig {
|
|
|
13
13
|
*/
|
|
14
14
|
adaptToServerHeaders?: boolean;
|
|
15
15
|
}
|
|
16
|
-
interface
|
|
17
|
-
execute: <T>(fn: () => Promise<T>) => Promise<T>;
|
|
18
|
-
|
|
16
|
+
interface ThrottleManager {
|
|
17
|
+
execute: <T>(path: string, query: Record<string, unknown>, fn: () => Promise<T>) => Promise<T>;
|
|
18
|
+
adaptToResponse: (response: Response | undefined) => void;
|
|
19
19
|
}
|
|
20
|
-
/**
|
|
21
|
-
* Concurrency limiter: allows up to `initialLimit` requests to be in-flight
|
|
22
|
-
* at the same time. A slot is freed as soon as the request's promise settles
|
|
23
|
-
* (resolves or rejects), so throughput scales with how quickly requests
|
|
24
|
-
* complete rather than being artificially capped at N per second.
|
|
25
|
-
*/
|
|
26
|
-
declare function createThrottle(initialLimit: number): Throttle;
|
|
27
|
-
/**
|
|
28
|
-
* Extracts the quota (`q=`) value from the `X-RateLimit-Policy` response header.
|
|
29
|
-
* Returns `undefined` if the header is absent or unparseable.
|
|
30
|
-
*
|
|
31
|
-
* Example header: `"concurrent-requests";q=30`
|
|
32
|
-
*/
|
|
33
|
-
declare function parseRateLimitPolicyHeader(response: Response): number | undefined;
|
|
34
20
|
//#endregion
|
|
35
|
-
export { RateLimitConfig,
|
|
21
|
+
export { RateLimitConfig, ThrottleManager };
|
|
36
22
|
//# sourceMappingURL=rate-limit.d.cts.map
|
|
@@ -13,24 +13,10 @@ interface RateLimitConfig {
|
|
|
13
13
|
*/
|
|
14
14
|
adaptToServerHeaders?: boolean;
|
|
15
15
|
}
|
|
16
|
-
interface
|
|
17
|
-
execute: <T>(fn: () => Promise<T>) => Promise<T>;
|
|
18
|
-
|
|
16
|
+
interface ThrottleManager {
|
|
17
|
+
execute: <T>(path: string, query: Record<string, unknown>, fn: () => Promise<T>) => Promise<T>;
|
|
18
|
+
adaptToResponse: (response: Response | undefined) => void;
|
|
19
19
|
}
|
|
20
|
-
/**
|
|
21
|
-
* Concurrency limiter: allows up to `initialLimit` requests to be in-flight
|
|
22
|
-
* at the same time. A slot is freed as soon as the request's promise settles
|
|
23
|
-
* (resolves or rejects), so throughput scales with how quickly requests
|
|
24
|
-
* complete rather than being artificially capped at N per second.
|
|
25
|
-
*/
|
|
26
|
-
declare function createThrottle(initialLimit: number): Throttle;
|
|
27
|
-
/**
|
|
28
|
-
* Extracts the quota (`q=`) value from the `X-RateLimit-Policy` response header.
|
|
29
|
-
* Returns `undefined` if the header is absent or unparseable.
|
|
30
|
-
*
|
|
31
|
-
* Example header: `"concurrent-requests";q=30`
|
|
32
|
-
*/
|
|
33
|
-
declare function parseRateLimitPolicyHeader(response: Response): number | undefined;
|
|
34
20
|
//#endregion
|
|
35
|
-
export { RateLimitConfig,
|
|
21
|
+
export { RateLimitConfig, ThrottleManager };
|
|
36
22
|
//# sourceMappingURL=rate-limit.d.mts.map
|
|
@@ -76,14 +76,20 @@ function determineTier(path, query) {
|
|
|
76
76
|
return "VERY_LARGE";
|
|
77
77
|
}
|
|
78
78
|
/**
|
|
79
|
-
* Extracts the quota (`q=`) value from the `X-RateLimit-Policy` response header
|
|
80
|
-
*
|
|
79
|
+
* Extracts the quota (`q=`) value from the `X-RateLimit-Policy` response header,
|
|
80
|
+
* but only when the policy describes a rate limit — not a concurrency limit.
|
|
81
81
|
*
|
|
82
|
-
*
|
|
82
|
+
* The Storyblok CDN currently returns `"concurrent-requests";q=30` which is a
|
|
83
|
+
* concurrency limit (always ~30), not a rate limit. Applying that value to the
|
|
84
|
+
* tier-based rate limiter would incorrectly cap throughput below what the API
|
|
85
|
+
* allows. This function therefore ignores concurrency policies and only returns
|
|
86
|
+
* a value for rate-limit policies (e.g. `"rate-limit";q=50`), which the API
|
|
87
|
+
* does not send yet but may in the future.
|
|
83
88
|
*/
|
|
84
89
|
function parseRateLimitPolicyHeader(response) {
|
|
85
90
|
const policy = response.headers.get("x-ratelimit-policy");
|
|
86
91
|
if (!policy) return;
|
|
92
|
+
if (policy.includes("\"concurrent-requests\"")) return;
|
|
87
93
|
const match = policy.match(/q=(\d+)/);
|
|
88
94
|
if (!match) return;
|
|
89
95
|
return Math.min(Number.parseInt(match[1], 10), MAX_RATE_LIMIT);
|
|
@@ -133,5 +139,5 @@ function createThrottleManager(config) {
|
|
|
133
139
|
}
|
|
134
140
|
|
|
135
141
|
//#endregion
|
|
136
|
-
export {
|
|
142
|
+
export { createThrottleManager };
|
|
137
143
|
//# sourceMappingURL=rate-limit.mjs.map
|
|
@@ -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 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
|
|
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 * but only when the policy describes a rate limit — not a concurrency limit.\n *\n * The Storyblok CDN currently returns `\"concurrent-requests\";q=30` which is a\n * concurrency limit (always ~30), not a rate limit. Applying that value to the\n * tier-based rate limiter would incorrectly cap throughput below what the API\n * allows. This function therefore ignores concurrency policies and only returns\n * a value for rate-limit policies (e.g. `\"rate-limit\";q=50`), which the API\n * does not send yet but may in the future.\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 // Only act on rate-limit policies — skip concurrency limits like\n // \"concurrent-requests\";q=30 which represent a different constraint.\n if (policy.includes('\"concurrent-requests\"')) {\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;;;;;;;;;;;;;AAcT,SAAgB,2BAA2B,UAAwC;CACjF,MAAM,SAAS,SAAS,QAAQ,IAAI,qBAAqB;AACzD,KAAI,CAAC,OACH;AAIF,KAAI,OAAO,SAAS,0BAAwB,CAC1C;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.
|
|
4
|
+
"version": "1.0.0-alpha.2",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Storyblok Content Delivery API Client",
|
|
7
7
|
"author": "",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"homepage": "https://github.com/storyblok/monoblok/tree/main/packages/capi-client#readme",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
|
-
"url": "https://github.com/storyblok/monoblok.git",
|
|
12
|
+
"url": "git+https://github.com/storyblok/monoblok.git",
|
|
13
13
|
"directory": "packages/capi-client"
|
|
14
14
|
},
|
|
15
15
|
"bugs": {
|
|
@@ -34,32 +34,35 @@
|
|
|
34
34
|
"@storyblok/region-helper": "1.4.1"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@hey-api/openapi-ts": "^0.92.3",
|
|
38
|
-
"@msw/source": "^0.6.1",
|
|
39
37
|
"@types/node": "^24.11.0",
|
|
40
38
|
"eslint": "^9.39.2",
|
|
41
|
-
"glob": "^13.0.6",
|
|
42
39
|
"msw": "^2.12.9",
|
|
43
40
|
"pathe": "^2.0.3",
|
|
44
41
|
"tsdown": "^0.20.3",
|
|
45
42
|
"tsx": "^4.21.0",
|
|
46
|
-
"vitest": "^4.
|
|
43
|
+
"vitest": "^4.1.3",
|
|
44
|
+
"@storyblok/management-api-client": "1.0.0-alpha.2",
|
|
47
45
|
"@storyblok/eslint-config": "0.5.1",
|
|
48
|
-
"@storyblok/openapi": "
|
|
46
|
+
"@storyblok/openapi-codegen": "0.0.1"
|
|
49
47
|
},
|
|
50
48
|
"nx": {
|
|
51
49
|
"targets": {
|
|
52
50
|
"generate": {
|
|
53
|
-
"
|
|
54
|
-
"
|
|
51
|
+
"inputs": [
|
|
52
|
+
"{projectRoot}/scripts/generate.ts",
|
|
53
|
+
"{projectRoot}/package.json",
|
|
54
|
+
"openapiCodegen"
|
|
55
55
|
],
|
|
56
56
|
"outputs": [
|
|
57
57
|
"{projectRoot}/src/generated/**"
|
|
58
|
+
],
|
|
59
|
+
"cache": true,
|
|
60
|
+
"dependsOn": [
|
|
61
|
+
"@storyblok/openapi-codegen:build"
|
|
58
62
|
]
|
|
59
63
|
},
|
|
60
64
|
"build": {
|
|
61
65
|
"dependsOn": [
|
|
62
|
-
"generate",
|
|
63
66
|
"^build"
|
|
64
67
|
],
|
|
65
68
|
"outputs": [
|
|
@@ -72,8 +75,9 @@
|
|
|
72
75
|
"generate": "tsx scripts/generate.ts",
|
|
73
76
|
"build": "tsdown",
|
|
74
77
|
"test": "vitest run",
|
|
78
|
+
"test:types": "tsc --noEmit --skipLibCheck",
|
|
79
|
+
"coverage": "vitest run --coverage",
|
|
75
80
|
"lint": "eslint .",
|
|
76
|
-
"lint:fix": "eslint . --fix"
|
|
77
|
-
"typecheck": "tsc --noEmit"
|
|
81
|
+
"lint:fix": "eslint . --fix"
|
|
78
82
|
}
|
|
79
83
|
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
const require_utils_gen = require('../shared/client/utils.gen.cjs');
|
|
2
|
-
const require_client_gen = require('../shared/client/client.gen.cjs');
|
|
3
|
-
require('../shared/client/index.cjs');
|
|
4
|
-
|
|
5
|
-
//#region src/generated/datasource_entries/client.gen.ts
|
|
6
|
-
const client = require_client_gen.createClient(require_utils_gen.createConfig({ baseUrl: "https://api.storyblok.com" }));
|
|
7
|
-
|
|
8
|
-
//#endregion
|
|
9
|
-
exports.client = client;
|
|
10
|
-
//# sourceMappingURL=client.gen.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client.gen.cjs","names":["createClient","createConfig"],"sources":["../../../src/generated/datasource_entries/client.gen.ts"],"sourcesContent":["// This file is auto-generated by @hey-api/openapi-ts\n\nimport { type ClientOptions, type Config, createClient, createConfig } from '../shared/client';\nimport type { ClientOptions as ClientOptions2 } from './types.gen';\n\n/**\n * The `createClientConfig()` function will be called on client initialization\n * and the returned object will become the client's initial configuration.\n *\n * You may want to initialize your client this way instead of calling\n * `setConfig()`. This is useful for example if you're using Next.js\n * to ensure your client always has the correct values.\n */\nexport type CreateClientConfig<T extends ClientOptions = ClientOptions2> = (override?: Config<ClientOptions & T>) => Config<Required<ClientOptions> & T>;\n\nexport const client = createClient(createConfig<ClientOptions2>({ baseUrl: 'https://api.storyblok.com' }));\n"],"mappings":";;;;;AAeA,MAAa,SAASA,gCAAaC,+BAA6B,EAAE,SAAS,6BAA6B,CAAC,CAAC"}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { createConfig } from "../shared/client/utils.gen.mjs";
|
|
2
|
-
import { createClient } from "../shared/client/client.gen.mjs";
|
|
3
|
-
import "../shared/client/index.mjs";
|
|
4
|
-
|
|
5
|
-
//#region src/generated/datasource_entries/client.gen.ts
|
|
6
|
-
const client = createClient(createConfig({ baseUrl: "https://api.storyblok.com" }));
|
|
7
|
-
|
|
8
|
-
//#endregion
|
|
9
|
-
export { client };
|
|
10
|
-
//# sourceMappingURL=client.gen.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client.gen.mjs","names":[],"sources":["../../../src/generated/datasource_entries/client.gen.ts"],"sourcesContent":["// This file is auto-generated by @hey-api/openapi-ts\n\nimport { type ClientOptions, type Config, createClient, createConfig } from '../shared/client';\nimport type { ClientOptions as ClientOptions2 } from './types.gen';\n\n/**\n * The `createClientConfig()` function will be called on client initialization\n * and the returned object will become the client's initial configuration.\n *\n * You may want to initialize your client this way instead of calling\n * `setConfig()`. This is useful for example if you're using Next.js\n * to ensure your client always has the correct values.\n */\nexport type CreateClientConfig<T extends ClientOptions = ClientOptions2> = (override?: Config<ClientOptions & T>) => Config<Required<ClientOptions> & T>;\n\nexport const client = createClient(createConfig<ClientOptions2>({ baseUrl: 'https://api.storyblok.com' }));\n"],"mappings":";;;;;AAeA,MAAa,SAAS,aAAa,aAA6B,EAAE,SAAS,6BAA6B,CAAC,CAAC"}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
const require_client_gen = require('./client.gen.cjs');
|
|
2
|
-
|
|
3
|
-
//#region src/generated/datasource_entries/sdk.gen.ts
|
|
4
|
-
/**
|
|
5
|
-
* Retrieve Multiple Datasource Entries
|
|
6
|
-
*
|
|
7
|
-
* Returns an array of all datasource entries. The API response can be filtered using the query parameters.
|
|
8
|
-
*/
|
|
9
|
-
const list = (options) => (options?.client ?? require_client_gen.client).get({
|
|
10
|
-
security: [{
|
|
11
|
-
in: "query",
|
|
12
|
-
name: "token",
|
|
13
|
-
type: "apiKey"
|
|
14
|
-
}],
|
|
15
|
-
url: "/v2/cdn/datasource_entries",
|
|
16
|
-
...options
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
//#endregion
|
|
20
|
-
exports.list = list;
|
|
21
|
-
//# sourceMappingURL=sdk.gen.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sdk.gen.cjs","names":["client"],"sources":["../../../src/generated/datasource_entries/sdk.gen.ts"],"sourcesContent":["// This file is auto-generated by @hey-api/openapi-ts\n\nimport type { Client, Options as Options2, TDataShape } from '../shared/client';\nimport { client } from './client.gen';\nimport type { ListData, ListErrors, ListResponses } from './types.gen';\n\nexport type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = Options2<TData, ThrowOnError> & {\n /**\n * You can provide a client instance returned by `createClient()` instead of\n * individual options. This might be also useful if you want to implement a\n * custom client.\n */\n client?: Client;\n /**\n * You can pass arbitrary values through the `meta` object. This can be\n * used to access values that aren't defined as part of the SDK function.\n */\n meta?: Record<string, unknown>;\n};\n\n/**\n * Retrieve Multiple Datasource Entries\n *\n * Returns an array of all datasource entries. The API response can be filtered using the query parameters.\n */\nexport const list = <ThrowOnError extends boolean = false>(options?: Options<ListData, ThrowOnError>) => (options?.client ?? client).get<ListResponses, ListErrors, ThrowOnError>({\n security: [{\n in: 'query',\n name: 'token',\n type: 'apiKey'\n }],\n url: '/v2/cdn/datasource_entries',\n ...options\n});\n"],"mappings":";;;;;;;;AAyBA,MAAa,QAA8C,aAA+C,SAAS,UAAUA,2BAAQ,IAA6C;CAC9K,UAAU,CAAC;EACH,IAAI;EACJ,MAAM;EACN,MAAM;EACT,CAAC;CACN,KAAK;CACL,GAAG;CACN,CAAC"}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { client } from "./client.gen.mjs";
|
|
2
|
-
|
|
3
|
-
//#region src/generated/datasource_entries/sdk.gen.ts
|
|
4
|
-
/**
|
|
5
|
-
* Retrieve Multiple Datasource Entries
|
|
6
|
-
*
|
|
7
|
-
* Returns an array of all datasource entries. The API response can be filtered using the query parameters.
|
|
8
|
-
*/
|
|
9
|
-
const list = (options) => (options?.client ?? client).get({
|
|
10
|
-
security: [{
|
|
11
|
-
in: "query",
|
|
12
|
-
name: "token",
|
|
13
|
-
type: "apiKey"
|
|
14
|
-
}],
|
|
15
|
-
url: "/v2/cdn/datasource_entries",
|
|
16
|
-
...options
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
//#endregion
|
|
20
|
-
export { list };
|
|
21
|
-
//# sourceMappingURL=sdk.gen.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sdk.gen.mjs","names":[],"sources":["../../../src/generated/datasource_entries/sdk.gen.ts"],"sourcesContent":["// This file is auto-generated by @hey-api/openapi-ts\n\nimport type { Client, Options as Options2, TDataShape } from '../shared/client';\nimport { client } from './client.gen';\nimport type { ListData, ListErrors, ListResponses } from './types.gen';\n\nexport type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = Options2<TData, ThrowOnError> & {\n /**\n * You can provide a client instance returned by `createClient()` instead of\n * individual options. This might be also useful if you want to implement a\n * custom client.\n */\n client?: Client;\n /**\n * You can pass arbitrary values through the `meta` object. This can be\n * used to access values that aren't defined as part of the SDK function.\n */\n meta?: Record<string, unknown>;\n};\n\n/**\n * Retrieve Multiple Datasource Entries\n *\n * Returns an array of all datasource entries. The API response can be filtered using the query parameters.\n */\nexport const list = <ThrowOnError extends boolean = false>(options?: Options<ListData, ThrowOnError>) => (options?.client ?? client).get<ListResponses, ListErrors, ThrowOnError>({\n security: [{\n in: 'query',\n name: 'token',\n type: 'apiKey'\n }],\n url: '/v2/cdn/datasource_entries',\n ...options\n});\n"],"mappings":";;;;;;;;AAyBA,MAAa,QAA8C,aAA+C,SAAS,UAAU,QAAQ,IAA6C;CAC9K,UAAU,CAAC;EACH,IAAI;EACJ,MAAM;EACN,MAAM;EACT,CAAC;CACN,KAAK;CACL,GAAG;CACN,CAAC"}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
//#region src/generated/datasource_entries/types.gen.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Storyblok datasource entry object for the Content Delivery API
|
|
4
|
-
*/
|
|
5
|
-
type DatasourceEntryCapi = {
|
|
6
|
-
/**
|
|
7
|
-
* Unique identifier of the datasource entry
|
|
8
|
-
*/
|
|
9
|
-
id: number;
|
|
10
|
-
/**
|
|
11
|
-
* Display name of the datasource entry
|
|
12
|
-
*/
|
|
13
|
-
name: string;
|
|
14
|
-
/**
|
|
15
|
-
* Value of the datasource entry
|
|
16
|
-
*/
|
|
17
|
-
value: string;
|
|
18
|
-
/**
|
|
19
|
-
* Dimension value of the datasource entry (null if no dimension is specified)
|
|
20
|
-
*/
|
|
21
|
-
dimension_value?: string | null;
|
|
22
|
-
};
|
|
23
|
-
type ListData = {
|
|
24
|
-
body?: never;
|
|
25
|
-
path?: never;
|
|
26
|
-
query?: {
|
|
27
|
-
/**
|
|
28
|
-
* Slug of the datasource to filter by
|
|
29
|
-
*/
|
|
30
|
-
datasource?: string;
|
|
31
|
-
/**
|
|
32
|
-
* Datasource dimension
|
|
33
|
-
*/
|
|
34
|
-
dimension?: string;
|
|
35
|
-
/**
|
|
36
|
-
* Page number for pagination
|
|
37
|
-
*/
|
|
38
|
-
page?: number;
|
|
39
|
-
/**
|
|
40
|
-
* Number of entries per page (default 25, max 1000)
|
|
41
|
-
*/
|
|
42
|
-
per_page?: number;
|
|
43
|
-
/**
|
|
44
|
-
* Cached version Unix timestamp for cache invalidation
|
|
45
|
-
*/
|
|
46
|
-
cv?: number;
|
|
47
|
-
};
|
|
48
|
-
url: '/v2/cdn/datasource_entries';
|
|
49
|
-
};
|
|
50
|
-
type ListResponses = {
|
|
51
|
-
/**
|
|
52
|
-
* Array of datasource entries
|
|
53
|
-
*/
|
|
54
|
-
200: {
|
|
55
|
-
/**
|
|
56
|
-
* Array of datasource entry objects
|
|
57
|
-
*/
|
|
58
|
-
datasource_entries: Array<DatasourceEntryCapi>;
|
|
59
|
-
/**
|
|
60
|
-
* Cache version Unix timestamp
|
|
61
|
-
*/
|
|
62
|
-
cv?: number | null;
|
|
63
|
-
};
|
|
64
|
-
};
|
|
65
|
-
//#endregion
|
|
66
|
-
export { DatasourceEntryCapi, ListData, ListResponses };
|
|
67
|
-
//# sourceMappingURL=types.gen.d.cts.map
|