@viglet/shio-client 2026.2.1 → 2026.3.3
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 +52 -6
- package/bin/shio-types.mjs +99 -0
- package/dist/client.d.ts +31 -4
- package/dist/codegen.d.ts +30 -0
- package/dist/index.d.ts +4 -1
- package/dist/shio-client.js +156 -107
- package/dist/shio-client.umd.cjs +6 -1
- package/dist/types.d.ts +210 -21
- package/package.json +39 -10
package/README.md
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
# @viglet/shio-client
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@viglet/shio-client)
|
|
4
|
+
[](https://github.com/openviglet/shio/blob/HEAD/LICENSE)
|
|
5
|
+
[](https://openviglet.github.io/shio/)
|
|
6
|
+
|
|
7
|
+
Framework-agnostic **TypeScript client** for the Content Delivery API (CDA) of
|
|
8
|
+
**[Viglet Shio](https://openviglet.github.io/shio/)**, the open source, **agent-ready CMS
|
|
9
|
+
for coding agents**.
|
|
4
10
|
|
|
5
11
|
- **Zero runtime dependencies** — just `fetch`.
|
|
6
12
|
- Runs in **Node, the browser, edge runtimes, Next.js server components, Astro, Vue, Svelte** and plain scripts.
|
|
7
13
|
- Targets the stable, read-only CDA contract (`/api/v2/cda/**`) — no Java, no legacy console API.
|
|
8
|
-
- This is the **base** SDK; `@viglet/shio-react-sdk` is one consumer of it.
|
|
14
|
+
- This is the **base** SDK; [`@viglet/shio-react-sdk`](https://www.npmjs.com/package/@viglet/shio-react-sdk) is one consumer of it.
|
|
15
|
+
- Pair it with [`@viglet/shio`](https://www.npmjs.com/package/@viglet/shio) — `shio types` generates a `.d.ts` from your live content model, so `post.attrs.*` autocompletes.
|
|
9
16
|
|
|
10
17
|
## Install
|
|
11
18
|
|
|
@@ -34,16 +41,41 @@ console.log(post?.attrs.title);
|
|
|
34
41
|
|---|---|---|
|
|
35
42
|
| `getSites()` | `GET /site` | `ShioSite[]` |
|
|
36
43
|
| `getSite(siteId)` | `GET /site/{id}` | `ShioSite \| null` |
|
|
37
|
-
| `listChildren(folderId, {page,size})` | `GET /object/{id}/list` | `ShioListing \| null` |
|
|
44
|
+
| `listChildren(folderId, {page,size,locale})` | `GET /object/{id}/list` | `ShioListing \| null` |
|
|
38
45
|
| `getPath(objectId)` | `GET /object/{id}/path` | `ShioPath \| null` |
|
|
39
46
|
| `getPost(postId)` | `GET /post/{id}` | `ShioPost \| null` |
|
|
40
|
-
| `getPostByUrl(siteId, url)` | `GET /post/by-url` | `ShioPost \| null` |
|
|
41
|
-
| `query({siteId,folderId,postType,page,size})` | `GET /query` | `ShioListing` |
|
|
47
|
+
| `getPostByUrl(siteId, url, locale?)` | `GET /post/by-url` | `ShioPost \| null` |
|
|
48
|
+
| `query({siteId,folderId,postType,page,size,locale})` | `GET /query` | `ShioListing` |
|
|
42
49
|
|
|
43
50
|
- Single-resource getters return `null` on `404`.
|
|
44
51
|
- Any other non-OK status throws `ShioHttpError` (`{ status, url, body }`).
|
|
45
52
|
- Every method accepts a final `{ signal }` option for cancellation.
|
|
46
53
|
|
|
54
|
+
## A language switcher
|
|
55
|
+
|
|
56
|
+
A site's `locales` says which languages it offers; a post's `availableLocales` says which of
|
|
57
|
+
them this page exists in. Together they are a switcher, with no call beyond the two you
|
|
58
|
+
already make:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const site = await shio.getSite(siteId);
|
|
62
|
+
// null when the post at `url` is in another language; its availableLocales say which exist
|
|
63
|
+
const post = await shio.getPostByUrl(siteId, url, locale) ?? await shio.getPostByUrl(siteId, url);
|
|
64
|
+
|
|
65
|
+
const offered = site?.locales?.enabled ?? [];
|
|
66
|
+
const links = offered.map((tag) => ({
|
|
67
|
+
tag,
|
|
68
|
+
// a language this page is missing in shows the site's fallback for it, else its default
|
|
69
|
+
target: post?.availableLocales.includes(tag)
|
|
70
|
+
? tag
|
|
71
|
+
: site?.locales?.fallback[tag] ?? site?.locales?.defaultLocale,
|
|
72
|
+
}));
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`locales.declared` is `false` when nobody has chosen the site's languages yet. The list is
|
|
76
|
+
then the languages its published pages are already written in, which is a description of the
|
|
77
|
+
content so far rather than a promise about it.
|
|
78
|
+
|
|
47
79
|
## Next.js App Router — catch-all page
|
|
48
80
|
|
|
49
81
|
The `getPostByUrl` call is built for `[...slug]` routing:
|
|
@@ -95,6 +127,20 @@ npm run typecheck # tsc --noEmit
|
|
|
95
127
|
npm test # node:test, no network (injected fetch)
|
|
96
128
|
```
|
|
97
129
|
|
|
130
|
+
## Related packages
|
|
131
|
+
|
|
132
|
+
| Package | For |
|
|
133
|
+
|---|---|
|
|
134
|
+
| [`@viglet/shio`](https://www.npmjs.com/package/@viglet/shio) | The Shio CLI — content as files, type generation, and the MCP bridge for [Claude Code](https://claude.com/claude-code) |
|
|
135
|
+
| [`@viglet/shio-react-sdk`](https://www.npmjs.com/package/@viglet/shio-react-sdk) | React hooks and render components built on this client |
|
|
136
|
+
| [`@viglet/shio-sections`](https://www.npmjs.com/package/@viglet/shio-sections) | The page-section vocabulary |
|
|
137
|
+
| [`@viglet/shio-model`](https://www.npmjs.com/package/@viglet/shio-model) | Author the content model as typed code |
|
|
138
|
+
| [`@viglet/shio-editor-cors`](https://www.npmjs.com/package/@viglet/shio-editor-cors) | Inline visual editing with the Universal Editor |
|
|
139
|
+
| [`@viglet/create-shio-app`](https://www.npmjs.com/package/@viglet/create-shio-app) | Next.js starter generator |
|
|
140
|
+
|
|
98
141
|
## License
|
|
99
142
|
|
|
100
|
-
|
|
143
|
+
Apache-2.0 — [Viglet Shio](https://openviglet.github.io/shio/) ·
|
|
144
|
+
[github.com/openviglet/shio](https://github.com/openviglet/shio) ·
|
|
145
|
+
[docs.viglet.org/shio](https://docs.viglet.org/shio/) ·
|
|
146
|
+
part of the [Viglet](https://www.viglet.org) open source family.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (C) 2016-2026 the original author or authors.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0.
|
|
6
|
+
*
|
|
7
|
+
* shio-types — generate a TypeScript `.d.ts` from a Shio instance's post-types
|
|
8
|
+
* (SH11). Fetches `/api/v2/cda/post-type` and writes typed `Post` interfaces so
|
|
9
|
+
* page code gets autocomplete on `post.attrs.*`.
|
|
10
|
+
*
|
|
11
|
+
* npx shio-types --url http://localhost:2710 --token <CDA token> --out lib/shio-types.d.ts
|
|
12
|
+
*
|
|
13
|
+
* Env fallbacks: SHIO_URL, SHIO_CDA_TOKEN.
|
|
14
|
+
*/
|
|
15
|
+
import { writeFile } from "node:fs/promises";
|
|
16
|
+
import { argv, env, exit, stdout } from "node:process";
|
|
17
|
+
|
|
18
|
+
import { ShioClient, generateShioTypes } from "../dist/shio-client.js";
|
|
19
|
+
|
|
20
|
+
const HELP = `shio-types — generate TypeScript types from Shio post-types
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
shio-types [options]
|
|
24
|
+
|
|
25
|
+
Options:
|
|
26
|
+
--url <baseUrl> Shio base URL (env SHIO_URL, default http://localhost:2710)
|
|
27
|
+
--token <token> CDA token (env SHIO_CDA_TOKEN)
|
|
28
|
+
--out <file> Output .d.ts path (default shio-types.d.ts; "-" for stdout)
|
|
29
|
+
--import-from <mod> Module to import ShioPost from (default @viglet/shio-client)
|
|
30
|
+
-h, --help Show this help
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
function parseArgs(args) {
|
|
34
|
+
const opts = {};
|
|
35
|
+
for (let i = 0; i < args.length; i++) {
|
|
36
|
+
const a = args[i];
|
|
37
|
+
switch (a) {
|
|
38
|
+
case "-h":
|
|
39
|
+
case "--help":
|
|
40
|
+
opts.help = true;
|
|
41
|
+
break;
|
|
42
|
+
case "--url":
|
|
43
|
+
opts.url = args[++i];
|
|
44
|
+
break;
|
|
45
|
+
case "--token":
|
|
46
|
+
opts.token = args[++i];
|
|
47
|
+
break;
|
|
48
|
+
case "--out":
|
|
49
|
+
opts.out = args[++i];
|
|
50
|
+
break;
|
|
51
|
+
case "--import-from":
|
|
52
|
+
opts.importFrom = args[++i];
|
|
53
|
+
break;
|
|
54
|
+
default:
|
|
55
|
+
fail(`Unknown argument: ${a}\n\n${HELP}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return opts;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function fail(message) {
|
|
62
|
+
process.stderr.write(`${message}\n`);
|
|
63
|
+
exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function main() {
|
|
67
|
+
const opts = parseArgs(argv.slice(2));
|
|
68
|
+
if (opts.help) {
|
|
69
|
+
stdout.write(HELP);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const baseUrl = opts.url ?? env.SHIO_URL ?? "http://localhost:2710";
|
|
74
|
+
const apiToken = opts.token ?? env.SHIO_CDA_TOKEN;
|
|
75
|
+
const out = opts.out ?? "shio-types.d.ts";
|
|
76
|
+
|
|
77
|
+
const client = new ShioClient({ baseUrl, apiToken });
|
|
78
|
+
|
|
79
|
+
let schemas;
|
|
80
|
+
try {
|
|
81
|
+
schemas = await client.getPostTypes();
|
|
82
|
+
} catch (err) {
|
|
83
|
+
fail(`shio-types: failed to fetch post-types from ${baseUrl}\n ${err?.message ?? err}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const source = generateShioTypes(schemas, {
|
|
87
|
+
importFrom: opts.importFrom,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (out === "-") {
|
|
91
|
+
stdout.write(source);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
await writeFile(out, source, "utf8");
|
|
96
|
+
stdout.write(`shio-types: wrote ${schemas.length} post-type(s) to ${out}\n`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
main().catch((err) => fail(`shio-types: ${err?.stack ?? err}`));
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ShioClientConfig, ShioListing, ShioPageParams, ShioPath, ShioPost, ShioQueryParams, ShioRequestOptions, ShioSite } from "./types";
|
|
1
|
+
import type { ShioClientConfig, ShioListing, ShioPageParams, ShioPath, ShioPost, ShioPostTypeSchema, ShioQueryParams, ShioReadOptions, ShioRedirect, ShioRequestOptions, ShioSite } from "./types";
|
|
2
2
|
/**
|
|
3
3
|
* Framework-agnostic client for the Shio Content Delivery API (CDA, SH1).
|
|
4
4
|
*
|
|
@@ -22,17 +22,44 @@ export declare class ShioClient {
|
|
|
22
22
|
getSites(opts?: ShioRequestOptions): Promise<ShioSite[]>;
|
|
23
23
|
/** Get one site (with its root folder id), or `null` if not found. */
|
|
24
24
|
getSite(siteId: string, opts?: ShioRequestOptions): Promise<ShioSite | null>;
|
|
25
|
+
/**
|
|
26
|
+
* A site's redirect table (SH1133), ordered by `from`, or `null` if the site is unknown.
|
|
27
|
+
*
|
|
28
|
+
* Small and site-scoped, so a front end can hold it and consult it before a 404 rather than
|
|
29
|
+
* asking per request. A production token sees published redirects only.
|
|
30
|
+
*/
|
|
31
|
+
getRedirects(siteId: string, opts?: ShioRequestOptions): Promise<ShioRedirect[] | null>;
|
|
25
32
|
/** List a folder's children (subfolders + published posts), or `null` if the folder is unknown. */
|
|
26
33
|
listChildren(folderId: string, params?: ShioPageParams, opts?: ShioRequestOptions): Promise<ShioListing | null>;
|
|
27
34
|
/** Get the breadcrumb path for a folder or post, or `null` if unknown. */
|
|
28
35
|
getPath(objectId: string, opts?: ShioRequestOptions): Promise<ShioPath | null>;
|
|
29
|
-
/**
|
|
30
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Get a published post by id (attributes expanded), or `null` if not found / not published.
|
|
38
|
+
*
|
|
39
|
+
* SH1081 — `opts.state` asks for another version: `"draft"` or `"rev:<n>"` with a preview token.
|
|
40
|
+
*/
|
|
41
|
+
getPost(postId: string, opts?: ShioReadOptions): Promise<ShioPost | null>;
|
|
31
42
|
/**
|
|
32
43
|
* Resolve a published post by site + friendly URL — the call behind catch-all
|
|
33
44
|
* routing (e.g. Next.js `[...slug]`). Returns `null` if unresolved.
|
|
45
|
+
*
|
|
46
|
+
* SH1057 — pass a `locale` as the third argument to resolve only the translation
|
|
47
|
+
* in that language: `getPostByUrl(siteId, "/about", "pt-br")` is `null` when the
|
|
48
|
+
* post at `/about` is in another language, and its `availableLocales` says which.
|
|
49
|
+
*
|
|
50
|
+
* SH1081 — `opts.state` asks for another version, as on {@link getPost}: a draft preview of
|
|
51
|
+
* one page is `getPostByUrl(siteId, "/about", { state: "draft" })` with a preview token.
|
|
52
|
+
*/
|
|
53
|
+
getPostByUrl(siteId: string, url: string, opts?: ShioReadOptions): Promise<ShioPost | null>;
|
|
54
|
+
getPostByUrl(siteId: string, url: string, locale: string, opts?: ShioReadOptions): Promise<ShioPost | null>;
|
|
55
|
+
/**
|
|
56
|
+
* List every post-type's content-model schema — the input to
|
|
57
|
+
* {@link generateShioTypes}. The schema is global (not site-scoped), so a
|
|
58
|
+
* per-site token sees every post-type.
|
|
34
59
|
*/
|
|
35
|
-
|
|
60
|
+
getPostTypes(opts?: ShioRequestOptions): Promise<ShioPostTypeSchema[]>;
|
|
61
|
+
/** Get one post-type's content-model schema by its API name, or `null` if unknown. */
|
|
62
|
+
getPostType(name: string, opts?: ShioRequestOptions): Promise<ShioPostTypeSchema | null>;
|
|
36
63
|
/** Query published posts by site / folder / post type (always returns a listing). */
|
|
37
64
|
query(params?: ShioQueryParams, opts?: ShioRequestOptions): Promise<ShioListing>;
|
|
38
65
|
private send;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript code generation from Shio post-type schemas (SH11).
|
|
3
|
+
*
|
|
4
|
+
* Pure and dependency-free: it turns the {@link ShioPostTypeSchema}[] returned by
|
|
5
|
+
* {@link ShioClient.getPostTypes} into a `.d.ts` source string. The `shio-types`
|
|
6
|
+
* CLI ({@link file:../bin/shio-types.mjs}) wires the fetch + file write around it;
|
|
7
|
+
* keeping the transform pure makes it testable without a network or a filesystem.
|
|
8
|
+
*/
|
|
9
|
+
import type { ShioPostTypeSchema } from "./types";
|
|
10
|
+
/** Options for {@link generateShioTypes}. */
|
|
11
|
+
export interface GenerateTypesOptions {
|
|
12
|
+
/**
|
|
13
|
+
* The module the generated `Post` interfaces import `ShioPost` from. Defaults
|
|
14
|
+
* to the published package name; set to a relative path when generating into
|
|
15
|
+
* the SDK's own source tree.
|
|
16
|
+
*/
|
|
17
|
+
importFrom?: string;
|
|
18
|
+
/** Override the file banner comment (e.g. to pin a version). */
|
|
19
|
+
banner?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Generate `.d.ts` source declaring one typed `Post` interface per post-type,
|
|
23
|
+
* plus a `ShioPostTypes` name→type map. Page code then gets autocomplete on
|
|
24
|
+
* `post.attrs.*`.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const schemas = await client.getPostTypes();
|
|
28
|
+
* await writeFile("shio-types.d.ts", generateShioTypes(schemas));
|
|
29
|
+
*/
|
|
30
|
+
export declare function generateShioTypes(schemas: ShioPostTypeSchema[], options?: GenerateTypesOptions): string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
export { ShioClient, createShioClient } from "./client";
|
|
2
2
|
export { ShioHttpError } from "./errors";
|
|
3
|
-
export
|
|
3
|
+
export { generateShioTypes } from "./codegen";
|
|
4
|
+
export type { GenerateTypesOptions } from "./codegen";
|
|
5
|
+
export type { ShioClientConfig, ShioRequestOptions, ShioContentState, ShioReadOptions, ShioSite, ShioSiteLocales, ShioRedirect, ShioFolder, ShioPostSummary, ShioPost, ShioListing, ShioPath, ShioQueryParams, ShioPageParams, ShioFieldSchema, ShioPostTypeSchema, ShioBlock, ShioBlockMark, } from "./types";
|
|
6
|
+
export { SHIO_BLOCK_TYPES, shioBlocks } from "./types";
|
package/dist/shio-client.js
CHANGED
|
@@ -1,113 +1,162 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var e = class e extends Error {
|
|
3
|
+
constructor(t, n, r) {
|
|
4
|
+
super(`Shio CDA ${t} for ${n}`), this.name = "ShioHttpError", this.status = t, this.url = n, this.body = r, Object.setPrototypeOf(this, e.prototype);
|
|
5
|
+
}
|
|
6
|
+
}, t = "/api/v2/cda", n = class {
|
|
7
|
+
constructor(e) {
|
|
8
|
+
if (!e?.baseUrl) throw Error("ShioClient: `baseUrl` is required");
|
|
9
|
+
this.baseUrl = e.baseUrl.replace(/\/+$/, ""), this.headers = {
|
|
10
|
+
Accept: "application/json",
|
|
11
|
+
...e.headers ?? {}
|
|
12
|
+
}, e.apiToken && (this.headers.Key = e.apiToken);
|
|
13
|
+
let t = e.fetch ?? globalThis.fetch;
|
|
14
|
+
if (typeof t != "function") throw Error("ShioClient: no global `fetch` available — pass `fetch` in the config");
|
|
15
|
+
this.fetchFn = t.bind(globalThis), this.credentials = e.credentials;
|
|
16
|
+
}
|
|
17
|
+
getSites(e) {
|
|
18
|
+
return this.request("/site", e);
|
|
19
|
+
}
|
|
20
|
+
getSite(e, t) {
|
|
21
|
+
return this.requestOrNull(`/site/${i(e)}`, t);
|
|
22
|
+
}
|
|
23
|
+
getRedirects(e, t) {
|
|
24
|
+
return this.requestOrNull(`/site/${i(e)}/redirects`, t);
|
|
25
|
+
}
|
|
26
|
+
listChildren(e, t, n) {
|
|
27
|
+
let r = a({
|
|
28
|
+
page: t?.page,
|
|
29
|
+
size: t?.size,
|
|
30
|
+
locale: t?.locale,
|
|
31
|
+
state: t?.state
|
|
32
|
+
});
|
|
33
|
+
return this.requestOrNull(`/object/${i(e)}/list${r}`, n);
|
|
34
|
+
}
|
|
35
|
+
getPath(e, t) {
|
|
36
|
+
return this.requestOrNull(`/object/${i(e)}/path`, t);
|
|
37
|
+
}
|
|
38
|
+
getPost(e, t) {
|
|
39
|
+
let n = a({ state: t?.state });
|
|
40
|
+
return this.requestOrNull(`/post/${i(e)}${n}`, t);
|
|
41
|
+
}
|
|
42
|
+
getPostByUrl(e, t, n, r) {
|
|
43
|
+
let i = typeof n == "string" ? n : void 0, o = typeof n == "string" ? r : n, s = a({
|
|
44
|
+
siteId: e,
|
|
45
|
+
url: t,
|
|
46
|
+
locale: i,
|
|
47
|
+
state: o?.state
|
|
48
|
+
});
|
|
49
|
+
return this.requestOrNull(`/post/by-url${s}`, o);
|
|
50
|
+
}
|
|
51
|
+
getPostTypes(e) {
|
|
52
|
+
return this.request("/post-type", e);
|
|
53
|
+
}
|
|
54
|
+
getPostType(e, t) {
|
|
55
|
+
return this.requestOrNull(`/post-type/${i(e)}`, t);
|
|
56
|
+
}
|
|
57
|
+
query(e, t) {
|
|
58
|
+
let n = a({
|
|
59
|
+
siteId: e?.siteId,
|
|
60
|
+
folderId: e?.folderId,
|
|
61
|
+
postType: e?.postType,
|
|
62
|
+
page: e?.page,
|
|
63
|
+
size: e?.size,
|
|
64
|
+
locale: e?.locale,
|
|
65
|
+
state: e?.state
|
|
66
|
+
});
|
|
67
|
+
return this.request(`/query${n}`, t);
|
|
68
|
+
}
|
|
69
|
+
async send(e, n) {
|
|
70
|
+
return this.fetchFn(`${this.baseUrl}${t}${e}`, {
|
|
71
|
+
method: "GET",
|
|
72
|
+
headers: this.headers,
|
|
73
|
+
signal: n?.signal,
|
|
74
|
+
...this.credentials ? { credentials: this.credentials } : {}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
async request(t, n) {
|
|
78
|
+
let r = await this.send(t, n);
|
|
79
|
+
if (!r.ok) throw new e(r.status, t, await o(r));
|
|
80
|
+
return await r.json();
|
|
81
|
+
}
|
|
82
|
+
async requestOrNull(t, n) {
|
|
83
|
+
let r = await this.send(t, n);
|
|
84
|
+
if (r.status === 404) return null;
|
|
85
|
+
if (!r.ok) throw new e(r.status, t, await o(r));
|
|
86
|
+
return await r.json();
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
function r(e) {
|
|
90
|
+
return new n(e);
|
|
5
91
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
constructor(e) {
|
|
9
|
-
if (!(e != null && e.baseUrl))
|
|
10
|
-
throw new Error("ShioClient: `baseUrl` is required");
|
|
11
|
-
this.baseUrl = e.baseUrl.replace(/\/+$/, ""), this.headers = { Accept: "application/json", ...e.headers ?? {} }, e.apiToken && (this.headers.Key = e.apiToken);
|
|
12
|
-
const t = e.fetch ?? globalThis.fetch;
|
|
13
|
-
if (typeof t != "function")
|
|
14
|
-
throw new Error(
|
|
15
|
-
"ShioClient: no global `fetch` available — pass `fetch` in the config"
|
|
16
|
-
);
|
|
17
|
-
this.fetchFn = t.bind(globalThis), this.credentials = e.credentials;
|
|
18
|
-
}
|
|
19
|
-
// --- Sites ---
|
|
20
|
-
/** List all published sites. */
|
|
21
|
-
getSites(e) {
|
|
22
|
-
return this.request("/site", e);
|
|
23
|
-
}
|
|
24
|
-
/** Get one site (with its root folder id), or `null` if not found. */
|
|
25
|
-
getSite(e, t) {
|
|
26
|
-
return this.requestOrNull(`/site/${n(e)}`, t);
|
|
27
|
-
}
|
|
28
|
-
// --- Navigation ---
|
|
29
|
-
/** List a folder's children (subfolders + published posts), or `null` if the folder is unknown. */
|
|
30
|
-
listChildren(e, t, i) {
|
|
31
|
-
const r = h({ page: t == null ? void 0 : t.page, size: t == null ? void 0 : t.size });
|
|
32
|
-
return this.requestOrNull(
|
|
33
|
-
`/object/${n(e)}/list${r}`,
|
|
34
|
-
i
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
/** Get the breadcrumb path for a folder or post, or `null` if unknown. */
|
|
38
|
-
getPath(e, t) {
|
|
39
|
-
return this.requestOrNull(`/object/${n(e)}/path`, t);
|
|
40
|
-
}
|
|
41
|
-
// --- Posts ---
|
|
42
|
-
/** Get a published post by id (attributes expanded), or `null` if not found / not published. */
|
|
43
|
-
getPost(e, t) {
|
|
44
|
-
return this.requestOrNull(`/post/${n(e)}`, t);
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Resolve a published post by site + friendly URL — the call behind catch-all
|
|
48
|
-
* routing (e.g. Next.js `[...slug]`). Returns `null` if unresolved.
|
|
49
|
-
*/
|
|
50
|
-
getPostByUrl(e, t, i) {
|
|
51
|
-
const r = h({ siteId: e, url: t });
|
|
52
|
-
return this.requestOrNull(`/post/by-url${r}`, i);
|
|
53
|
-
}
|
|
54
|
-
/** Query published posts by site / folder / post type (always returns a listing). */
|
|
55
|
-
query(e, t) {
|
|
56
|
-
const i = h({
|
|
57
|
-
siteId: e == null ? void 0 : e.siteId,
|
|
58
|
-
folderId: e == null ? void 0 : e.folderId,
|
|
59
|
-
postType: e == null ? void 0 : e.postType,
|
|
60
|
-
page: e == null ? void 0 : e.page,
|
|
61
|
-
size: e == null ? void 0 : e.size
|
|
62
|
-
});
|
|
63
|
-
return this.request(`/query${i}`, t);
|
|
64
|
-
}
|
|
65
|
-
// --- internals ---
|
|
66
|
-
async send(e, t) {
|
|
67
|
-
return this.fetchFn(`${this.baseUrl}${u}${e}`, {
|
|
68
|
-
method: "GET",
|
|
69
|
-
headers: this.headers,
|
|
70
|
-
signal: t == null ? void 0 : t.signal,
|
|
71
|
-
...this.credentials ? { credentials: this.credentials } : {}
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
async request(e, t) {
|
|
75
|
-
const i = await this.send(e, t);
|
|
76
|
-
if (!i.ok)
|
|
77
|
-
throw new o(i.status, e, await l(i));
|
|
78
|
-
return await i.json();
|
|
79
|
-
}
|
|
80
|
-
async requestOrNull(e, t) {
|
|
81
|
-
const i = await this.send(e, t);
|
|
82
|
-
if (i.status === 404)
|
|
83
|
-
return null;
|
|
84
|
-
if (!i.ok)
|
|
85
|
-
throw new o(i.status, e, await l(i));
|
|
86
|
-
return await i.json();
|
|
87
|
-
}
|
|
92
|
+
function i(e) {
|
|
93
|
+
return encodeURIComponent(e);
|
|
88
94
|
}
|
|
89
|
-
function
|
|
90
|
-
|
|
95
|
+
function a(e) {
|
|
96
|
+
let t = new URLSearchParams();
|
|
97
|
+
for (let [n, r] of Object.entries(e)) r != null && r !== "" && t.append(n, String(r));
|
|
98
|
+
let n = t.toString();
|
|
99
|
+
return n ? `?${n}` : "";
|
|
91
100
|
}
|
|
92
|
-
function
|
|
93
|
-
|
|
101
|
+
async function o(e) {
|
|
102
|
+
try {
|
|
103
|
+
return await e.text();
|
|
104
|
+
} catch {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
94
107
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
// src/codegen.ts
|
|
109
|
+
var s = "@viglet/shio-client", c = "// AUTO-GENERATED by @viglet/shio-client (shio-types). DO NOT EDIT BY HAND.\n// Re-run `shio-types` after changing a post-type.";
|
|
110
|
+
function l(e, t = {}) {
|
|
111
|
+
let n = t.importFrom ?? s, r = t.banner ?? c, i = [...e].sort((e, t) => e.name.localeCompare(t.name)), a = /* @__PURE__ */ new Map(), o = /* @__PURE__ */ new Set();
|
|
112
|
+
for (let e of i) {
|
|
113
|
+
let t = m(e.name) || "PostType", n = t, r = 2;
|
|
114
|
+
for (; o.has(n);) n = `${t}${r++}`;
|
|
115
|
+
o.add(n), a.set(e.name, n);
|
|
116
|
+
}
|
|
117
|
+
let l = [];
|
|
118
|
+
for (let e of i) {
|
|
119
|
+
let t = a.get(e.name), n = f(e.title ?? e.description, 0);
|
|
120
|
+
l.push(`${n}export interface ${t}Attrs {\n${u(e.fields, 1)}}\n\n/** A \`${e.name}\` post with typed attributes. */\nexport type ${t}Post = ShioPost & {\n postType: ${JSON.stringify(e.name)};\n attrs: ${t}Attrs;\n};`);
|
|
121
|
+
}
|
|
122
|
+
let d = `/** Maps each post-type's API name to its typed post interface. */
|
|
123
|
+
export interface ShioPostTypes {\n${i.map((e) => ` ${p(e.name)}: ${a.get(e.name)}Post;`).join("\n") || ""}\n}\n\n/** Union of all known post-type API names. */
|
|
124
|
+
export type ShioPostTypeName = keyof ShioPostTypes;`;
|
|
125
|
+
return `${r}\n\nimport type { ShioPost } from ${JSON.stringify(n)};\n\n` + (l.length ? `${l.join("\n\n")}\n\n` : "") + `${d}\n`;
|
|
101
126
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
127
|
+
function u(e, t) {
|
|
128
|
+
let n = " ".repeat(t);
|
|
129
|
+
return [...e].sort((e, t) => e.ordinal - t.ordinal).map((e) => {
|
|
130
|
+
let r = f(e.label ?? e.description, t), i = e.required ? "" : "?";
|
|
131
|
+
return `${r}${n}${p(e.name)}${i}: ${d(e, t)};\n`;
|
|
132
|
+
}).join("");
|
|
108
133
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
134
|
+
function d(e, t) {
|
|
135
|
+
return e.fields && e.fields.length > 0 ? `Array<{\n${u(e.fields, t + 1)}${" ".repeat(t)}}>` : e.multiple ? "string[]" : "string";
|
|
136
|
+
}
|
|
137
|
+
function f(e, t) {
|
|
138
|
+
return !e || !e.trim() ? "" : `${" ".repeat(t)}/** ${e.trim().replace(/\*\//g, "*\\/")} */\n`;
|
|
139
|
+
}
|
|
140
|
+
function p(e) {
|
|
141
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e) ? e : JSON.stringify(e);
|
|
142
|
+
}
|
|
143
|
+
function m(e) {
|
|
144
|
+
let t = (e ?? "").split(/[^A-Za-z0-9]+/).filter(Boolean).map((e) => e.charAt(0).toUpperCase() + e.slice(1)).join("");
|
|
145
|
+
return /^[0-9]/.test(t) && (t = `T${t}`), t;
|
|
146
|
+
}
|
|
147
|
+
// src/types.ts
|
|
148
|
+
var h = [
|
|
149
|
+
"heading",
|
|
150
|
+
"paragraph",
|
|
151
|
+
"quote",
|
|
152
|
+
"list",
|
|
153
|
+
"code",
|
|
154
|
+
"image",
|
|
155
|
+
"embed",
|
|
156
|
+
"reference"
|
|
157
|
+
];
|
|
158
|
+
function g(e, t) {
|
|
159
|
+
let n = e.attrs?.[t];
|
|
160
|
+
return Array.isArray(n) ? n : null;
|
|
161
|
+
}
|
|
162
|
+
export { h as SHIO_BLOCK_TYPES, n as ShioClient, e as ShioHttpError, r as createShioClient, l as generateShioTypes, g as shioBlocks };
|
package/dist/shio-client.umd.cjs
CHANGED
|
@@ -1 +1,6 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.ShioClient={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=class e extends Error{constructor(t,n,r){super(`Shio CDA ${t} for ${n}`),this.name=`ShioHttpError`,this.status=t,this.url=n,this.body=r,Object.setPrototypeOf(this,e.prototype)}},n=`/api/v2/cda`,r=class{constructor(e){if(!e?.baseUrl)throw Error("ShioClient: `baseUrl` is required");this.baseUrl=e.baseUrl.replace(/\/+$/,``),this.headers={Accept:`application/json`,...e.headers??{}},e.apiToken&&(this.headers.Key=e.apiToken);let t=e.fetch??globalThis.fetch;if(typeof t!=`function`)throw Error("ShioClient: no global `fetch` available — pass `fetch` in the config");this.fetchFn=t.bind(globalThis),this.credentials=e.credentials}getSites(e){return this.request(`/site`,e)}getSite(e,t){return this.requestOrNull(`/site/${a(e)}`,t)}getRedirects(e,t){return this.requestOrNull(`/site/${a(e)}/redirects`,t)}listChildren(e,t,n){let r=o({page:t?.page,size:t?.size,locale:t?.locale,state:t?.state});return this.requestOrNull(`/object/${a(e)}/list${r}`,n)}getPath(e,t){return this.requestOrNull(`/object/${a(e)}/path`,t)}getPost(e,t){let n=o({state:t?.state});return this.requestOrNull(`/post/${a(e)}${n}`,t)}getPostByUrl(e,t,n,r){let i=typeof n==`string`?n:void 0,a=typeof n==`string`?r:n,s=o({siteId:e,url:t,locale:i,state:a?.state});return this.requestOrNull(`/post/by-url${s}`,a)}getPostTypes(e){return this.request(`/post-type`,e)}getPostType(e,t){return this.requestOrNull(`/post-type/${a(e)}`,t)}query(e,t){let n=o({siteId:e?.siteId,folderId:e?.folderId,postType:e?.postType,page:e?.page,size:e?.size,locale:e?.locale,state:e?.state});return this.request(`/query${n}`,t)}async send(e,t){return this.fetchFn(`${this.baseUrl}${n}${e}`,{method:`GET`,headers:this.headers,signal:t?.signal,...this.credentials?{credentials:this.credentials}:{}})}async request(e,n){let r=await this.send(e,n);if(!r.ok)throw new t(r.status,e,await s(r));return await r.json()}async requestOrNull(e,n){let r=await this.send(e,n);if(r.status===404)return null;if(!r.ok)throw new t(r.status,e,await s(r));return await r.json()}};function i(e){return new r(e)}function a(e){return encodeURIComponent(e)}function o(e){let t=new URLSearchParams;for(let[n,r]of Object.entries(e))r!=null&&r!==``&&t.append(n,String(r));let n=t.toString();return n?`?${n}`:``}async function s(e){try{return await e.text()}catch{return}}var c=`@viglet/shio-client`,l="// AUTO-GENERATED by @viglet/shio-client (shio-types). DO NOT EDIT BY HAND.\n// Re-run `shio-types` after changing a post-type.";function u(e,t={}){let n=t.importFrom??c,r=t.banner??l,i=[...e].sort((e,t)=>e.name.localeCompare(t.name)),a=new Map,o=new Set;for(let e of i){let t=h(e.name)||`PostType`,n=t,r=2;for(;o.has(n);)n=`${t}${r++}`;o.add(n),a.set(e.name,n)}let s=[];for(let e of i){let t=a.get(e.name),n=p(e.title??e.description,0);s.push(`${n}export interface ${t}Attrs {\n${d(e.fields,1)}}\n\n/** A \`${e.name}\` post with typed attributes. */\nexport type ${t}Post = ShioPost & {\n postType: ${JSON.stringify(e.name)};\n attrs: ${t}Attrs;\n};`)}let u=`/** Maps each post-type's API name to its typed post interface. */
|
|
2
|
+
export interface ShioPostTypes {\n${i.map(e=>` ${m(e.name)}: ${a.get(e.name)}Post;`).join(`
|
|
3
|
+
`)||``}\n}\n\n/** Union of all known post-type API names. */
|
|
4
|
+
export type ShioPostTypeName = keyof ShioPostTypes;`;return`${r}\n\nimport type { ShioPost } from ${JSON.stringify(n)};\n\n`+(s.length?`${s.join(`
|
|
5
|
+
|
|
6
|
+
`)}\n\n`:``)+`${u}\n`}function d(e,t){let n=` `.repeat(t);return[...e].sort((e,t)=>e.ordinal-t.ordinal).map(e=>{let r=p(e.label??e.description,t),i=e.required?``:`?`;return`${r}${n}${m(e.name)}${i}: ${f(e,t)};\n`}).join(``)}function f(e,t){return e.fields&&e.fields.length>0?`Array<{\n${d(e.fields,t+1)}${` `.repeat(t)}}>`:e.multiple?`string[]`:`string`}function p(e,t){return!e||!e.trim()?``:`${` `.repeat(t)}/** ${e.trim().replace(/\*\//g,`*\\/`)} */\n`}function m(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)?e:JSON.stringify(e)}function h(e){let t=(e??``).split(/[^A-Za-z0-9]+/).filter(Boolean).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(``);return/^[0-9]/.test(t)&&(t=`T${t}`),t}var g=[`heading`,`paragraph`,`quote`,`list`,`code`,`image`,`embed`,`reference`];function _(e,t){let n=e.attrs?.[t];return Array.isArray(n)?n:null}e.SHIO_BLOCK_TYPES=g,e.ShioClient=r,e.ShioHttpError=t,e.createShioClient=i,e.generateShioTypes=u,e.shioBlocks=_});
|
package/dist/types.d.ts
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
* `/api/v2/cda/**`). Field names track the backend DTO records
|
|
4
4
|
* (`com.viglet.shio.api.cda.dto`) one-for-one — they are a frozen public
|
|
5
5
|
* contract; do not rename.
|
|
6
|
+
*
|
|
7
|
+
* **Optional, not nullable (SH127).** A field the CMS has no value for is *absent*
|
|
8
|
+
* from the JSON rather than present as `null`, so it is declared `field?: T` here.
|
|
9
|
+
* These two halves shipped together and had to: the backend omits the null because
|
|
10
|
+
* `"url": null` costs bytes and tokens to say nothing, and this file declares it
|
|
11
|
+
* optional because a consumer written against `string | null` would otherwise start
|
|
12
|
+
* receiving `undefined` from a comparison it believed was exhaustive. Narrow with a
|
|
13
|
+
* falsy check (`if (post.title)`) rather than `=== null`.
|
|
6
14
|
*/
|
|
7
15
|
/** Configuration for {@link ShioClient}. */
|
|
8
16
|
export interface ShioClientConfig {
|
|
@@ -31,13 +39,68 @@ export interface ShioRequestOptions {
|
|
|
31
39
|
/** Abort signal for cancellation (e.g. React effect cleanup). */
|
|
32
40
|
signal?: AbortSignal;
|
|
33
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* The version of content a CDA read asks for (SH1081, over SH990's parameter).
|
|
44
|
+
*
|
|
45
|
+
* `"published"` is the default and any token may read it. `"draft"` and `"rev:<n>"` need a
|
|
46
|
+
* preview token, and without one the read throws {@link ShioHttpError} with status 403 and the
|
|
47
|
+
* problem slug `state-needs-preview-token`. A string rather than a union, so the server stays the
|
|
48
|
+
* one place that says which values exist.
|
|
49
|
+
*/
|
|
50
|
+
export type ShioContentState = string;
|
|
51
|
+
/** Per-request options for a single-post read: {@link ShioRequestOptions} plus a state. */
|
|
52
|
+
export interface ShioReadOptions extends ShioRequestOptions {
|
|
53
|
+
/** See {@link ShioContentState}; omitted, the published version. */
|
|
54
|
+
state?: ShioContentState;
|
|
55
|
+
}
|
|
34
56
|
/** A published site. */
|
|
35
57
|
export interface ShioSite {
|
|
36
58
|
id: string;
|
|
37
59
|
name: string;
|
|
38
|
-
description
|
|
39
|
-
url
|
|
40
|
-
rootFolderId
|
|
60
|
+
description?: string;
|
|
61
|
+
url?: string;
|
|
62
|
+
rootFolderId?: string;
|
|
63
|
+
/**
|
|
64
|
+
* The languages this site is in (SH1123) — what a language switcher is built from.
|
|
65
|
+
* Present on {@link ShioClient.getSite} and {@link ShioClient.getSites}; absent on the
|
|
66
|
+
* `site` a {@link ShioPath} carries.
|
|
67
|
+
*/
|
|
68
|
+
locales?: ShioSiteLocales;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* A site's locale policy (SH1123). Pair it with {@link ShioPost.availableLocales}: the
|
|
72
|
+
* site says which languages it offers, the post says which of them this page exists in.
|
|
73
|
+
*/
|
|
74
|
+
export interface ShioSiteLocales {
|
|
75
|
+
/** The locale the site is written in — always one of {@link enabled}. */
|
|
76
|
+
defaultLocale: string;
|
|
77
|
+
/** Every locale the site offers, in the order a switcher lists them. */
|
|
78
|
+
enabled: string[];
|
|
79
|
+
/**
|
|
80
|
+
* Locale to show instead when a page is missing in the one asked for, e.g.
|
|
81
|
+
* `{ "pt-br": "pt" }`. A locale with no entry falls back to {@link defaultLocale}.
|
|
82
|
+
*/
|
|
83
|
+
fallback: Record<string, string>;
|
|
84
|
+
/**
|
|
85
|
+
* `true` when somebody chose this policy. `false` when it was read off the published
|
|
86
|
+
* content, in which case {@link enabled} lists what has been translated so far rather
|
|
87
|
+
* than what the site promises.
|
|
88
|
+
*/
|
|
89
|
+
declared: boolean;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* One row of a site's redirect table (SH1133), from {@link ShioClient.getRedirects}.
|
|
93
|
+
*
|
|
94
|
+
* A front end that serves the site's URLs itself consults this before answering 404: the
|
|
95
|
+
* instance's own renderer honours these, and a headless site never reaches it.
|
|
96
|
+
*/
|
|
97
|
+
export interface ShioRedirect {
|
|
98
|
+
/** The site-relative path that moved, e.g. `/pricing`. */
|
|
99
|
+
from: string;
|
|
100
|
+
/** Where it moved: a site-relative path, or an absolute URL as written. */
|
|
101
|
+
to: string;
|
|
102
|
+
/** `true` for a 301, `false` for a 302 — the default, because a wrong 301 is cached for ever. */
|
|
103
|
+
permanent: boolean;
|
|
41
104
|
}
|
|
42
105
|
/** A folder (listing item / breadcrumb node). */
|
|
43
106
|
export interface ShioFolder {
|
|
@@ -48,32 +111,113 @@ export interface ShioFolder {
|
|
|
48
111
|
/** A post listing item — no heavy attribute payload. */
|
|
49
112
|
export interface ShioPostSummary {
|
|
50
113
|
id: string;
|
|
51
|
-
title
|
|
52
|
-
summary
|
|
53
|
-
furl
|
|
54
|
-
postType
|
|
55
|
-
|
|
56
|
-
|
|
114
|
+
title?: string;
|
|
115
|
+
summary?: string;
|
|
116
|
+
furl?: string;
|
|
117
|
+
postType?: string;
|
|
118
|
+
/** BCP-47 locale of this row (SH52), absent on single-language content. */
|
|
119
|
+
locale?: string;
|
|
120
|
+
date?: string;
|
|
121
|
+
modifiedDate?: string;
|
|
57
122
|
}
|
|
58
123
|
/** A full post with its post-type attributes expanded into {@link attrs}. */
|
|
59
124
|
export interface ShioPost {
|
|
60
125
|
id: string;
|
|
61
|
-
title
|
|
62
|
-
summary
|
|
63
|
-
furl
|
|
64
|
-
postType
|
|
65
|
-
siteId
|
|
66
|
-
folderId
|
|
126
|
+
title?: string;
|
|
127
|
+
summary?: string;
|
|
128
|
+
furl?: string;
|
|
129
|
+
postType?: string;
|
|
130
|
+
siteId?: string;
|
|
131
|
+
folderId?: string;
|
|
67
132
|
published: boolean;
|
|
68
|
-
date
|
|
69
|
-
modifiedDate
|
|
70
|
-
publicationDate
|
|
133
|
+
date?: string;
|
|
134
|
+
modifiedDate?: string;
|
|
135
|
+
publicationDate?: string;
|
|
136
|
+
/** BCP-47 locale of this post (SH52), absent on single-language content. */
|
|
137
|
+
locale?: string;
|
|
138
|
+
/**
|
|
139
|
+
* Every locale this post is translated into, including {@link locale} — the
|
|
140
|
+
* list a language switcher renders from (SH52). Always present; `[]` on a
|
|
141
|
+
* post with no translation group.
|
|
142
|
+
*/
|
|
143
|
+
availableLocales: string[];
|
|
71
144
|
/** Post-type attributes as a JSON object, keyed by attribute name. */
|
|
72
145
|
attrs: Record<string, unknown>;
|
|
146
|
+
/**
|
|
147
|
+
* SH1016 — field name to rendered HTML, for this post's `Blocks` fields.
|
|
148
|
+
*
|
|
149
|
+
* Beside {@link attrs}, never instead of it: a site that maps block types to its own
|
|
150
|
+
* components reads the array out of `attrs`, and one that has been dropping a body into a
|
|
151
|
+
* div reads this. Absent for a post with no block field, which is every post written
|
|
152
|
+
* before the widget existed.
|
|
153
|
+
*/
|
|
154
|
+
rendered?: Record<string, string>;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* SH1016 — one block of a `Blocks` field, discriminated by {@link ShioBlock.type}.
|
|
158
|
+
*
|
|
159
|
+
* TypeScript narrows on `type`, so `switch (block.type)` gives each case its own fields with
|
|
160
|
+
* no cast — which is the whole reason the body is an array of these rather than a string.
|
|
161
|
+
* A `key` is the block's identity across a reorder, not its position.
|
|
162
|
+
*
|
|
163
|
+
* Marks are ranges over the block's own `text`, not nested spans: `<b><i>x</i></b>` and
|
|
164
|
+
* `<i><b>x</b></i>` are one content with two spellings, and a range has one.
|
|
165
|
+
*/
|
|
166
|
+
export interface ShioBlockMark {
|
|
167
|
+
from: number;
|
|
168
|
+
to: number;
|
|
169
|
+
type: "strong" | "em" | "code" | "link" | "strike";
|
|
170
|
+
/** Present on a `link` mark. */
|
|
171
|
+
href?: string;
|
|
172
|
+
}
|
|
173
|
+
interface ShioBlockBase {
|
|
174
|
+
key: string;
|
|
73
175
|
}
|
|
176
|
+
export type ShioBlock = (ShioBlockBase & {
|
|
177
|
+
type: "heading";
|
|
178
|
+
level: number;
|
|
179
|
+
text?: string;
|
|
180
|
+
marks?: ShioBlockMark[];
|
|
181
|
+
}) | (ShioBlockBase & {
|
|
182
|
+
type: "paragraph";
|
|
183
|
+
text?: string;
|
|
184
|
+
marks?: ShioBlockMark[];
|
|
185
|
+
}) | (ShioBlockBase & {
|
|
186
|
+
type: "quote";
|
|
187
|
+
text?: string;
|
|
188
|
+
marks?: ShioBlockMark[];
|
|
189
|
+
}) | (ShioBlockBase & {
|
|
190
|
+
type: "list";
|
|
191
|
+
ordered?: boolean;
|
|
192
|
+
items?: string[];
|
|
193
|
+
}) | (ShioBlockBase & {
|
|
194
|
+
type: "code";
|
|
195
|
+
language?: string;
|
|
196
|
+
text?: string;
|
|
197
|
+
}) | (ShioBlockBase & {
|
|
198
|
+
type: "image";
|
|
199
|
+
src?: string;
|
|
200
|
+
alt?: string;
|
|
201
|
+
}) | (ShioBlockBase & {
|
|
202
|
+
type: "embed";
|
|
203
|
+
url?: string;
|
|
204
|
+
}) | (ShioBlockBase & {
|
|
205
|
+
type: "reference";
|
|
206
|
+
address?: string;
|
|
207
|
+
});
|
|
208
|
+
/** The registered block types, as the server refuses anything outside them. */
|
|
209
|
+
export declare const SHIO_BLOCK_TYPES: readonly ["heading", "paragraph", "quote", "list", "code", "image", "embed", "reference"];
|
|
210
|
+
/**
|
|
211
|
+
* Reads a post's block field as blocks, or `null` when it is not one.
|
|
212
|
+
*
|
|
213
|
+
* A helper rather than a cast, because the honest answer for a field that holds a string is
|
|
214
|
+
* "this is not a blocks field" — and a consumer that casts finds that out by rendering
|
|
215
|
+
* nothing.
|
|
216
|
+
*/
|
|
217
|
+
export declare function shioBlocks(post: ShioPost, field: string): ShioBlock[] | null;
|
|
74
218
|
/** Folder children: subfolders plus a paged slice of published posts. */
|
|
75
219
|
export interface ShioListing {
|
|
76
|
-
folderId
|
|
220
|
+
folderId?: string;
|
|
77
221
|
folders: ShioFolder[];
|
|
78
222
|
posts: ShioPostSummary[];
|
|
79
223
|
page: number;
|
|
@@ -82,10 +226,46 @@ export interface ShioListing {
|
|
|
82
226
|
}
|
|
83
227
|
/** Breadcrumb path for a folder or post. */
|
|
84
228
|
export interface ShioPath {
|
|
85
|
-
site
|
|
229
|
+
site?: ShioSite;
|
|
86
230
|
/** Ordered root → ... → current. */
|
|
87
231
|
breadcrumb: ShioFolder[];
|
|
88
|
-
currentFolder
|
|
232
|
+
currentFolder?: ShioFolder;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* One field of a post-type's content model (SH11). Recursive: a Relator field
|
|
236
|
+
* carries its item sub-fields in {@link fields}. Mirrors the backend
|
|
237
|
+
* `ShCdaSchemaField` record one-for-one.
|
|
238
|
+
*/
|
|
239
|
+
export interface ShioFieldSchema {
|
|
240
|
+
name: string;
|
|
241
|
+
label?: string;
|
|
242
|
+
description?: string;
|
|
243
|
+
/** The Shio widget name (e.g. `"Text"`, `"Relator"`), or `null`. */
|
|
244
|
+
widget?: string;
|
|
245
|
+
required: boolean;
|
|
246
|
+
/**
|
|
247
|
+
* The delivered value is an array — a `Check Box` / `Multi Select` value, or a
|
|
248
|
+
* Relator's item list (in which case {@link fields} is non-empty).
|
|
249
|
+
*/
|
|
250
|
+
multiple: boolean;
|
|
251
|
+
isTitle: boolean;
|
|
252
|
+
isSummary: boolean;
|
|
253
|
+
ordinal: number;
|
|
254
|
+
/** Sub-fields, for a Relator; empty for scalar fields. */
|
|
255
|
+
fields: ShioFieldSchema[];
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* A post-type's content model (SH11): its API {@link name} plus the ordered
|
|
259
|
+
* fields a post of this type exposes under {@link ShioPost.attrs}. Mirrors the
|
|
260
|
+
* backend `ShCdaPostTypeSchema` record. Feed an array of these to
|
|
261
|
+
* {@link generateShioTypes} to emit `.d.ts` declarations.
|
|
262
|
+
*/
|
|
263
|
+
export interface ShioPostTypeSchema {
|
|
264
|
+
name: string;
|
|
265
|
+
title?: string;
|
|
266
|
+
namePlural?: string;
|
|
267
|
+
description?: string;
|
|
268
|
+
fields: ShioFieldSchema[];
|
|
89
269
|
}
|
|
90
270
|
/** Parameters for {@link ShioClient.query}. */
|
|
91
271
|
export interface ShioQueryParams {
|
|
@@ -94,9 +274,18 @@ export interface ShioQueryParams {
|
|
|
94
274
|
postType?: string;
|
|
95
275
|
page?: number;
|
|
96
276
|
size?: number;
|
|
277
|
+
/** Only posts written in this locale (SH1057), e.g. `"pt-br"`; omitted, every locale. */
|
|
278
|
+
locale?: string;
|
|
279
|
+
/** SH1081 — which version of each post; see {@link ShioContentState}. */
|
|
280
|
+
state?: ShioContentState;
|
|
97
281
|
}
|
|
98
282
|
/** Paging parameters for listings. */
|
|
99
283
|
export interface ShioPageParams {
|
|
100
284
|
page?: number;
|
|
101
285
|
size?: number;
|
|
286
|
+
/** Only posts written in this locale (SH1057); a folder's subfolders are listed whatever it is. */
|
|
287
|
+
locale?: string;
|
|
288
|
+
/** SH1081 — which version of each post; see {@link ShioContentState}. */
|
|
289
|
+
state?: ShioContentState;
|
|
102
290
|
}
|
|
291
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@viglet/shio-client",
|
|
3
|
-
"version": "2026.
|
|
4
|
-
"description": "Framework-agnostic TypeScript client for the Shio
|
|
3
|
+
"version": "2026.3.3",
|
|
4
|
+
"description": "Framework-agnostic TypeScript client for the Viglet Shio Content Delivery API (CDA). Zero runtime dependencies — works in Node, the browser, edge runtimes, Next.js server components, Astro, Vue, Svelte and plain scripts.",
|
|
5
|
+
"homepage": "https://openviglet.github.io/shio/",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/openviglet/shio.git",
|
|
9
|
+
"directory": "shio-client"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/openviglet/shio/issues"
|
|
13
|
+
},
|
|
5
14
|
"type": "module",
|
|
6
15
|
"main": "dist/shio-client.umd.cjs",
|
|
7
16
|
"module": "dist/shio-client.js",
|
|
@@ -11,31 +20,51 @@
|
|
|
11
20
|
"types": "./dist/index.d.ts",
|
|
12
21
|
"import": "./dist/shio-client.js",
|
|
13
22
|
"require": "./dist/shio-client.umd.cjs"
|
|
14
|
-
}
|
|
23
|
+
},
|
|
24
|
+
"./package.json": "./package.json"
|
|
25
|
+
},
|
|
26
|
+
"bin": {
|
|
27
|
+
"shio-types": "bin/shio-types.mjs"
|
|
15
28
|
},
|
|
16
29
|
"files": [
|
|
17
|
-
"dist"
|
|
30
|
+
"dist",
|
|
31
|
+
"bin"
|
|
18
32
|
],
|
|
19
33
|
"sideEffects": false,
|
|
20
34
|
"devDependencies": {
|
|
21
35
|
"typescript": "^5.8.3",
|
|
22
|
-
"vite": "^
|
|
36
|
+
"vite": "^8.3.5"
|
|
23
37
|
},
|
|
24
38
|
"keywords": [
|
|
25
39
|
"shio",
|
|
40
|
+
"viglet",
|
|
26
41
|
"cms",
|
|
27
|
-
"headless",
|
|
42
|
+
"headless-cms",
|
|
43
|
+
"content-management-system",
|
|
44
|
+
"agent",
|
|
45
|
+
"ai",
|
|
46
|
+
"mcp",
|
|
47
|
+
"model-context-protocol",
|
|
48
|
+
"claude-code",
|
|
28
49
|
"cda",
|
|
29
50
|
"content-delivery",
|
|
30
51
|
"typescript",
|
|
31
|
-
"sdk"
|
|
52
|
+
"sdk",
|
|
53
|
+
"api-client",
|
|
54
|
+
"nextjs",
|
|
55
|
+
"astro",
|
|
56
|
+
"edge",
|
|
57
|
+
"headless"
|
|
32
58
|
],
|
|
33
|
-
"author":
|
|
34
|
-
|
|
59
|
+
"author": {
|
|
60
|
+
"name": "Viglet Team",
|
|
61
|
+
"url": "https://www.viglet.org"
|
|
62
|
+
},
|
|
63
|
+
"license": "Apache-2.0",
|
|
35
64
|
"scripts": {
|
|
36
65
|
"dev": "vite build --watch",
|
|
37
66
|
"build": "vite build && tsc --emitDeclarationOnly",
|
|
38
67
|
"typecheck": "tsc --noEmit",
|
|
39
|
-
"test": "node --test \"test/*.test.mjs\""
|
|
68
|
+
"test": "node --import ../scripts/test-watchdog.mjs --test \"test/*.test.mjs\""
|
|
40
69
|
}
|
|
41
70
|
}
|