storyloco 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/package.json +1 -1
- package/packages/vite/dist/schema/plugin.js +5 -0
- package/packages/vite/dist/server.d.ts +29 -10
- package/packages/vite/dist/server.js +28 -17
- package/packages/vite/dist/storyblok.svelte.d.ts +40 -0
- package/packages/vite/dist/storyblok.svelte.js +13 -0
package/README.md
CHANGED
|
@@ -176,10 +176,19 @@ await storyblok.story('layout')
|
|
|
176
176
|
// A list. `total` is the unpaged count, for "showing n of m"
|
|
177
177
|
const { stories, total } = await storyblok.stories({ content_type: 'blog', per_page: 2 })
|
|
178
178
|
|
|
179
|
+
// Everything that matches, paged through the API's 100-per-page cap
|
|
180
|
+
const products = await storyblok.all({ content_type: 'product' })
|
|
181
|
+
|
|
179
182
|
// The link tree, flattened — Storyblok returns it keyed by uuid
|
|
180
183
|
const links = await storyblok.links({ starts_with: 'blog/' })
|
|
181
184
|
```
|
|
182
185
|
|
|
186
|
+
Reads with a `content_type` are typed from your generated schema: the plugin registers
|
|
187
|
+
the space's content types with storyloco, so `stories({ content_type: 'product' })` yields
|
|
188
|
+
product stories and `content_type: 'product,category'` a union of both — no type argument
|
|
189
|
+
needed. Until a schema has been generated, or for a name it doesn't know, the content falls
|
|
190
|
+
back to the SDK's permissive type. An explicit type argument always wins.
|
|
191
|
+
|
|
183
192
|
Your params are spread last, so any default can be overridden:
|
|
184
193
|
|
|
185
194
|
```typescript
|
|
@@ -194,6 +203,18 @@ const product = await storyblok.find<Product>(`products/${slug}`)
|
|
|
194
203
|
if (!product) error(404, 'Product not found')
|
|
195
204
|
```
|
|
196
205
|
|
|
206
|
+
The content type can be a union. Each member becomes its own story type, so `is_component`
|
|
207
|
+
can narrow the result — checking `story.content.component` yourself narrows only `content`,
|
|
208
|
+
not the story:
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
import { is_component } from 'storyloco'
|
|
212
|
+
|
|
213
|
+
const story = await storyblok.find<Product | Category>(`products/${slug}`)
|
|
214
|
+
if (is_component(story, 'product')) story.content.sku // ISbStoryData<Product>
|
|
215
|
+
if (is_component(story, 'category')) story.content.description // ISbStoryData<Category>
|
|
216
|
+
```
|
|
217
|
+
|
|
197
218
|
It's callable too, for anything the methods don't cover:
|
|
198
219
|
|
|
199
220
|
```typescript
|
package/package.json
CHANGED
|
@@ -218,6 +218,11 @@ import type { ISbStoryData } from '@storyblok/js';`, "");
|
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
export type ContentType = ContentTypes[keyof ContentTypes]
|
|
221
|
+
|
|
222
|
+
/** Lets storyloco type reads by \`content_type\` without a type argument. */
|
|
223
|
+
declare module "storyloco" {
|
|
224
|
+
interface Registry extends ContentTypes {}
|
|
225
|
+
}
|
|
221
226
|
`;
|
|
222
227
|
let content = component_types.replace(`// This file was generated by the storyblok CLI.
|
|
223
228
|
// DO NOT MODIFY THIS FILE BY HAND.`, header);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ISbStoriesParams,
|
|
1
|
+
import type { ISbStoriesParams, ISbStoryParams } from "@storyblok/svelte";
|
|
2
2
|
import type { ISbLink, ISbLinksParams } from "storyblok-js-client";
|
|
3
|
-
import { type StoryblokClient, type Version } from "./storyblok.svelte.js";
|
|
3
|
+
import { type ContentFor, type Story, type StoryblokClient, type Version } from "./storyblok.svelte.js";
|
|
4
4
|
/** The content version for the current request, as set by the `version` hook. */
|
|
5
5
|
export declare function version(): Version;
|
|
6
6
|
/**
|
|
@@ -20,7 +20,9 @@ export declare function version(): Version;
|
|
|
20
20
|
* // anywhere on the server
|
|
21
21
|
* await storyblok.story("layout")
|
|
22
22
|
* await storyblok.find<Product>(`products/${slug}`) // null when missing
|
|
23
|
-
* await storyblok.
|
|
23
|
+
* await storyblok.find<Product | Category>(slug) // a union of stories; narrow with is_component
|
|
24
|
+
* await storyblok.stories({ content_type: "blog", per_page: 2 }) // typed from the generated schema
|
|
25
|
+
* await storyblok.all<Product>({ content_type: "product" }) // every page
|
|
24
26
|
*
|
|
25
27
|
* const { client, version } = storyblok()
|
|
26
28
|
* await client.get("cdn/links", { version })
|
|
@@ -32,18 +34,35 @@ export declare function setup(client: StoryblokClient): (() => {
|
|
|
32
34
|
}) & {
|
|
33
35
|
story: <T = import("storyblok-js-client").ISbComponentType<string> & {
|
|
34
36
|
[index: string]: any;
|
|
35
|
-
}>(slug: string, params?: ISbStoryParams) => Promise<
|
|
37
|
+
}>(slug: string, params?: ISbStoryParams) => Promise<Story<T>>;
|
|
36
38
|
find: <T = import("storyblok-js-client").ISbComponentType<string> & {
|
|
37
39
|
[index: string]: any;
|
|
38
|
-
}>(slug: string, params?: ISbStoryParams) => Promise<
|
|
40
|
+
}>(slug: string, params?: ISbStoryParams) => Promise<Story<T> | null>;
|
|
41
|
+
stories: {
|
|
42
|
+
<K extends string>(params: ISbStoriesParams & {
|
|
43
|
+
content_type: K;
|
|
44
|
+
}): Promise<{
|
|
45
|
+
stories: Story<ContentFor<K>>[];
|
|
46
|
+
total: number;
|
|
47
|
+
}>;
|
|
48
|
+
<T = import("storyblok-js-client").ISbComponentType<string> & {
|
|
49
|
+
[index: string]: any;
|
|
50
|
+
}>(params?: ISbStoriesParams): Promise<{
|
|
51
|
+
stories: Story<T>[];
|
|
52
|
+
total: number;
|
|
53
|
+
}>;
|
|
54
|
+
};
|
|
55
|
+
all: {
|
|
56
|
+
<K extends string>(params: ISbStoriesParams & {
|
|
57
|
+
content_type: K;
|
|
58
|
+
}): Promise<Array<Story<ContentFor<K>>>>;
|
|
59
|
+
<T = import("storyblok-js-client").ISbComponentType<string> & {
|
|
60
|
+
[index: string]: any;
|
|
61
|
+
}>(params?: ISbStoriesParams): Promise<Array<Story<T>>>;
|
|
62
|
+
};
|
|
39
63
|
/**
|
|
40
64
|
* The link tree as a flat array — Storyblok returns it keyed by uuid, which is
|
|
41
65
|
* almost never what you want. Filtering and ordering are left to the caller.
|
|
42
66
|
*/
|
|
43
67
|
links(params?: ISbLinksParams): Promise<ISbLink[]>;
|
|
44
|
-
/** A list of stories. `total` is the unpaged count, for "showing n of m". */
|
|
45
|
-
stories<T>(params?: ISbStoriesParams): Promise<{
|
|
46
|
-
stories: Array<ISbStoryData<T>>;
|
|
47
|
-
total: number;
|
|
48
|
-
}>;
|
|
49
68
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getRequestEvent } from "$app/server";
|
|
2
2
|
import { error } from "@sveltejs/kit";
|
|
3
|
-
import { handle_error } from "./storyblok.svelte.js";
|
|
3
|
+
import { handle_error, } from "./storyblok.svelte.js";
|
|
4
4
|
function is_not_found(err) {
|
|
5
5
|
return typeof err === "object" && err !== null && "status" in err && err.status === 404;
|
|
6
6
|
}
|
|
@@ -25,7 +25,9 @@ export function version() {
|
|
|
25
25
|
* // anywhere on the server
|
|
26
26
|
* await storyblok.story("layout")
|
|
27
27
|
* await storyblok.find<Product>(`products/${slug}`) // null when missing
|
|
28
|
-
* await storyblok.
|
|
28
|
+
* await storyblok.find<Product | Category>(slug) // a union of stories; narrow with is_component
|
|
29
|
+
* await storyblok.stories({ content_type: "blog", per_page: 2 }) // typed from the generated schema
|
|
30
|
+
* await storyblok.all<Product>({ content_type: "product" }) // every page
|
|
29
31
|
*
|
|
30
32
|
* const { client, version } = storyblok()
|
|
31
33
|
* await client.get("cdn/links", { version })
|
|
@@ -61,9 +63,33 @@ export function setup(client) {
|
|
|
61
63
|
error(404, "Story not found");
|
|
62
64
|
return found;
|
|
63
65
|
}
|
|
66
|
+
async function stories(params = {}) {
|
|
67
|
+
const response = await client
|
|
68
|
+
.get("cdn/stories", {
|
|
69
|
+
version: version(),
|
|
70
|
+
resolve_links: "url",
|
|
71
|
+
resolve_assets: 1,
|
|
72
|
+
...params,
|
|
73
|
+
})
|
|
74
|
+
.catch(handle_error);
|
|
75
|
+
return { stories: response.data.stories, total: response.total ?? 0 };
|
|
76
|
+
}
|
|
77
|
+
async function all(params = {}) {
|
|
78
|
+
return client
|
|
79
|
+
.getAll("cdn/stories", {
|
|
80
|
+
version: version(),
|
|
81
|
+
resolve_links: "url",
|
|
82
|
+
resolve_assets: 1,
|
|
83
|
+
per_page: 100,
|
|
84
|
+
...params,
|
|
85
|
+
})
|
|
86
|
+
.catch(handle_error);
|
|
87
|
+
}
|
|
64
88
|
return Object.assign(context, {
|
|
65
89
|
story,
|
|
66
90
|
find,
|
|
91
|
+
stories,
|
|
92
|
+
all,
|
|
67
93
|
/**
|
|
68
94
|
* The link tree as a flat array — Storyblok returns it keyed by uuid, which is
|
|
69
95
|
* almost never what you want. Filtering and ordering are left to the caller.
|
|
@@ -74,20 +100,5 @@ export function setup(client) {
|
|
|
74
100
|
.catch(handle_error);
|
|
75
101
|
return Object.values(response.data.links ?? {});
|
|
76
102
|
},
|
|
77
|
-
/** A list of stories. `total` is the unpaged count, for "showing n of m". */
|
|
78
|
-
async stories(params = {}) {
|
|
79
|
-
const response = await client
|
|
80
|
-
.get("cdn/stories", {
|
|
81
|
-
version: version(),
|
|
82
|
-
resolve_links: "url",
|
|
83
|
-
resolve_assets: 1,
|
|
84
|
-
...params,
|
|
85
|
-
})
|
|
86
|
-
.catch(handle_error);
|
|
87
|
-
return {
|
|
88
|
-
stories: response.data.stories,
|
|
89
|
-
total: response.total ?? 0,
|
|
90
|
-
};
|
|
91
|
-
},
|
|
92
103
|
});
|
|
93
104
|
}
|
|
@@ -140,6 +140,46 @@ export interface Richtext<Blocks = unknown> {
|
|
|
140
140
|
content?: Array<Richtext<Blocks>>;
|
|
141
141
|
marks?: Array<Richtext<Blocks>>;
|
|
142
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Content types by component name, for narrowing reads by `content_type`. Empty here;
|
|
145
|
+
* the schema plugin's generated file merges the space's `ContentTypes` into it, so
|
|
146
|
+
* `storyblok.stories({ content_type: "product" })` is typed as products without a type
|
|
147
|
+
* argument. Until a schema is generated every name falls back to the SDK's content type.
|
|
148
|
+
*/
|
|
149
|
+
export interface Registry {
|
|
150
|
+
}
|
|
151
|
+
/** The SDK's permissive content type: any component, any fields. */
|
|
152
|
+
export type Content = ISbStoryData["content"];
|
|
153
|
+
/** Each name in a comma-separated `content_type`. */
|
|
154
|
+
type Names<S extends string> = S extends `${infer A},${infer B}` ? A | Names<B> : S;
|
|
155
|
+
/**
|
|
156
|
+
* The content behind a `content_type` param. Registered names map to their generated
|
|
157
|
+
* types (a list to their union); anything else is the permissive `Content`.
|
|
158
|
+
*/
|
|
159
|
+
export type ContentFor<K extends string> = [Names<K>] extends [keyof Registry] ? Registry[Names<K>] : Content;
|
|
160
|
+
/**
|
|
161
|
+
* A story per member of `T`, so a union of content types is a union of stories —
|
|
162
|
+
* `Story<Product | Category>` is `ISbStoryData<Product> | ISbStoryData<Category>`, which
|
|
163
|
+
* `is_component` can narrow. `ISbStoryData<Product | Category>` can't be.
|
|
164
|
+
*/
|
|
165
|
+
export type Story<T> = T extends unknown ? ISbStoryData<T> : never;
|
|
166
|
+
/**
|
|
167
|
+
* Narrow a story (or a union of them) by its content's `component`. TypeScript won't
|
|
168
|
+
* narrow the story through the nested discriminant on its own.
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```ts
|
|
172
|
+
* const story = await storyblok.find<Product | Category>(`products/${slug}`)
|
|
173
|
+
* if (is_component(story, "product")) story.content.sku // ISbStoryData<Product>
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
export declare function is_component<S extends ISbStoryData<{
|
|
177
|
+
component?: string;
|
|
178
|
+
}>, K extends string>(story: S | null | undefined, component: K): story is Extract<S, {
|
|
179
|
+
content: {
|
|
180
|
+
component?: K;
|
|
181
|
+
};
|
|
182
|
+
}>;
|
|
143
183
|
/** Adds the `story.url` that `resolve_links: "url"` injects but the CLI doesn't declare. */
|
|
144
184
|
export type Resolved<T extends {
|
|
145
185
|
story?: unknown;
|
|
@@ -137,6 +137,19 @@ export class StoryblokClient {
|
|
|
137
137
|
};
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Narrow a story (or a union of them) by its content's `component`. TypeScript won't
|
|
142
|
+
* narrow the story through the nested discriminant on its own.
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```ts
|
|
146
|
+
* const story = await storyblok.find<Product | Category>(`products/${slug}`)
|
|
147
|
+
* if (is_component(story, "product")) story.content.sku // ISbStoryData<Product>
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export function is_component(story, component) {
|
|
151
|
+
return story?.content.component === component;
|
|
152
|
+
}
|
|
140
153
|
/**
|
|
141
154
|
* Resolve a multilink to an href. Story links become root-relative, everything else passes
|
|
142
155
|
* through. Prefers `story.url` over `cached_url`, which goes stale when a target moves —
|