@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 CHANGED
@@ -1,11 +1,18 @@
1
1
  # @viglet/shio-client
2
2
 
3
- Framework-agnostic TypeScript client for the **Shio CMS Content Delivery API (CDA)**.
3
+ [![npm](https://img.shields.io/npm/v/@viglet/shio-client?logo=npm)](https://www.npmjs.com/package/@viglet/shio-client)
4
+ [![License](https://img.shields.io/npm/l/@viglet/shio-client)](https://github.com/openviglet/shio/blob/HEAD/LICENSE)
5
+ [![Website](https://img.shields.io/badge/website-0ea5e9?logo=googlechrome&logoColor=white)](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
- GPL-3.0
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
- /** Get a published post by id (attributes expanded), or `null` if not found / not published. */
30
- getPost(postId: string, opts?: ShioRequestOptions): Promise<ShioPost | null>;
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
- getPostByUrl(siteId: string, url: string, opts?: ShioRequestOptions): Promise<ShioPost | null>;
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 type { ShioClientConfig, ShioRequestOptions, ShioSite, ShioFolder, ShioPostSummary, ShioPost, ShioListing, ShioPath, ShioQueryParams, ShioPageParams, } from "./types";
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";
@@ -1,113 +1,162 @@
1
- class o extends Error {
2
- constructor(e, t, i) {
3
- super(`Shio CDA ${e} for ${t}`), this.name = "ShioHttpError", this.status = e, this.url = t, this.body = i, Object.setPrototypeOf(this, o.prototype);
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
- const u = "/api/v2/cda";
7
- class c {
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 d(s) {
90
- return new c(s);
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 n(s) {
93
- return encodeURIComponent(s);
101
+ async function o(e) {
102
+ try {
103
+ return await e.text();
104
+ } catch {
105
+ return;
106
+ }
94
107
  }
95
- function h(s) {
96
- const e = new URLSearchParams();
97
- for (const [i, r] of Object.entries(s))
98
- r != null && r !== "" && e.append(i, String(r));
99
- const t = e.toString();
100
- return t ? `?${t}` : "";
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
- async function l(s) {
103
- try {
104
- return await s.text();
105
- } catch {
106
- return;
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
- export {
110
- c as ShioClient,
111
- o as ShioHttpError,
112
- d as createShioClient
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 };
@@ -1 +1,6 @@
1
- (function(s,n){typeof exports=="object"&&typeof module<"u"?n(exports):typeof define=="function"&&define.amd?define(["exports"],n):(s=typeof globalThis<"u"?globalThis:s||self,n(s.ShioClient={}))})(this,(function(s){"use strict";class n extends Error{constructor(e,t,i){super(`Shio CDA ${e} for ${t}`),this.name="ShioHttpError",this.status=e,this.url=t,this.body=i,Object.setPrototypeOf(this,n.prototype)}}const d="/api/v2/cda";class u{constructor(e){if(!(e!=null&&e.baseUrl))throw new Error("ShioClient: `baseUrl` is required");this.baseUrl=e.baseUrl.replace(/\/+$/,""),this.headers={Accept:"application/json",...e.headers??{}},e.apiToken&&(this.headers.Key=e.apiToken);const t=e.fetch??globalThis.fetch;if(typeof t!="function")throw new 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/${l(e)}`,t)}listChildren(e,t,i){const o=h({page:t==null?void 0:t.page,size:t==null?void 0:t.size});return this.requestOrNull(`/object/${l(e)}/list${o}`,i)}getPath(e,t){return this.requestOrNull(`/object/${l(e)}/path`,t)}getPost(e,t){return this.requestOrNull(`/post/${l(e)}`,t)}getPostByUrl(e,t,i){const o=h({siteId:e,url:t});return this.requestOrNull(`/post/by-url${o}`,i)}query(e,t){const i=h({siteId:e==null?void 0:e.siteId,folderId:e==null?void 0:e.folderId,postType:e==null?void 0:e.postType,page:e==null?void 0:e.page,size:e==null?void 0:e.size});return this.request(`/query${i}`,t)}async send(e,t){return this.fetchFn(`${this.baseUrl}${d}${e}`,{method:"GET",headers:this.headers,signal:t==null?void 0:t.signal,...this.credentials?{credentials:this.credentials}:{}})}async request(e,t){const i=await this.send(e,t);if(!i.ok)throw new n(i.status,e,await c(i));return await i.json()}async requestOrNull(e,t){const i=await this.send(e,t);if(i.status===404)return null;if(!i.ok)throw new n(i.status,e,await c(i));return await i.json()}}function f(r){return new u(r)}function l(r){return encodeURIComponent(r)}function h(r){const e=new URLSearchParams;for(const[i,o]of Object.entries(r))o!=null&&o!==""&&e.append(i,String(o));const t=e.toString();return t?`?${t}`:""}async function c(r){try{return await r.text()}catch{return}}s.ShioClient=u,s.ShioHttpError=n,s.createShioClient=f,Object.defineProperty(s,Symbol.toStringTag,{value:"Module"})}));
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: string | null;
39
- url: string | null;
40
- rootFolderId: string | null;
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: string | null;
52
- summary: string | null;
53
- furl: string | null;
54
- postType: string | null;
55
- date: string | null;
56
- modifiedDate: string | null;
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: string | null;
62
- summary: string | null;
63
- furl: string | null;
64
- postType: string | null;
65
- siteId: string | null;
66
- folderId: string | null;
126
+ title?: string;
127
+ summary?: string;
128
+ furl?: string;
129
+ postType?: string;
130
+ siteId?: string;
131
+ folderId?: string;
67
132
  published: boolean;
68
- date: string | null;
69
- modifiedDate: string | null;
70
- publicationDate: string | null;
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: string | null;
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: ShioSite | null;
229
+ site?: ShioSite;
86
230
  /** Ordered root → ... → current. */
87
231
  breadcrumb: ShioFolder[];
88
- currentFolder: ShioFolder | null;
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.2.1",
4
- "description": "Framework-agnostic TypeScript client for the Shio CMS Content Delivery API (CDA). Zero runtime dependencies — works in Node, the browser, edge runtimes, Next.js server components, Astro, Vue, Svelte and plain scripts.",
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": "^6.3.1"
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": "Viglet Team",
34
- "license": "GPL-3.0",
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
  }