@crombolo/delivery-client 0.0.0-stage → 0.22.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kineco
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE ADDED
@@ -0,0 +1,69 @@
1
+ crombolo
2
+ Copyright (c) 2026 Kineco
3
+
4
+ This product includes third-party software. crombolo is an independent project.
5
+ It is not affiliated with, endorsed by, or sponsored by Umbraco A/S, which owns
6
+ the Umbraco trademark.
7
+
8
+ There are three places third-party material is redistributed, rather than merely
9
+ depended on:
10
+
11
+ @crombolo/backoffice-dist the built Umbraco backoffice and the browser
12
+ packages bundled into it, produced by
13
+ `bun run vendor:backoffice`
14
+ @crombolo/backoffice-dist three CSS files copied unmodified from the Umbraco
15
+ client source tree
16
+ @crombolo/contracts the Umbraco Management API OpenAPI document,
17
+ copied unmodified
18
+
19
+ No Umbraco trademark is redistributed. The Umbraco wordmark, the "U" roundel
20
+ favicon and the installer illustration are deliberately not carried in this
21
+ repository: MIT grants copyright permission and says nothing about trademarks, so
22
+ crombolo's own marks are served at those paths instead. See `BRANDED_ASSETS` in
23
+ `@crombolo/backoffice-host`.
24
+
25
+ The Umbraco backoffice
26
+ ----------------------
27
+ @umbraco-cms/backoffice 18.2.0 — MIT — https://github.com/umbraco/Umbraco-CMS
28
+ @umbraco-ui/uui 2.0.2 — MIT — https://github.com/umbraco/Umbraco.UI
29
+
30
+ Static files from the Umbraco client source tree
31
+ ------------------------------------------------
32
+ css/umb-css.css, css/rte-content.css, css/umbraco-blockgridlayout.css
33
+ — copied unmodified from Umbraco-CMS release-18.2.0 — MIT
34
+ — https://github.com/umbraco/Umbraco-CMS
35
+ Shipped in @crombolo/backoffice-dist under `upstream-static/`.
36
+
37
+ `rte-content.css` itself carries Tiptap's default editor styles, copied from
38
+ tiptap v2.11.7 (MIT); the file records this in its own header.
39
+
40
+ The Umbraco Management API contract
41
+ -----------------------------------
42
+ OpenApi.json — copied unmodified from Umbraco-CMS release-18.2.0
43
+ (src/Umbraco.Cms.Api.Management/OpenApi.json) — MIT
44
+ — https://github.com/umbraco/Umbraco-CMS
45
+ Shipped in @crombolo/contracts, which also ships the TypeScript types generated
46
+ from it.
47
+
48
+ Browser runtime dependencies bundled into the backoffice build
49
+ --------------------------------------------------------------
50
+ @heximal/expressions 0.1.5 — BSD-3-Clause
51
+ @microsoft/signalr 10.0.11 — MIT
52
+ @tiptap/* 3.31.3 — MIT
53
+ diff 9.0.0 — BSD-3-Clause
54
+ dompurify 3.4.16 — Apache-2.0, elected from "MPL-2.0 OR Apache-2.0"
55
+ element-internals-polyfill 3.0.2 — MIT
56
+ lit 3.3.3 — BSD-3-Clause
57
+ luxon 3.7.2 — MIT
58
+ marked 18.0.14 — MIT
59
+ monaco-editor 0.57.0 — MIT
60
+ rxjs 7.8.2 — Apache-2.0
61
+ uuid 14.0.2 — MIT
62
+
63
+ dompurify is offered under either licence; Apache-2.0 is elected here, so no
64
+ MPL-2.0 obligation attaches to the copy bundled into the backoffice build. Of the
65
+ Apache-2.0 packages above, none ships a NOTICE file of its own, so Apache-2.0
66
+ section 4(d) adds nothing to this file.
67
+
68
+ Each package's own licence text ships inside its npm distribution and applies
69
+ unchanged to the copies bundled here.
package/README.md CHANGED
@@ -1,3 +1,38 @@
1
- # Temporary Holding Version
1
+ # @crombolo/delivery-client
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A typed client for a [crombolo](https://github.com/kineco-au/crombolo-cms) site's
4
+ Delivery API, for an app in the browser, Node or Bun. No dependencies.
5
+
6
+ ```sh
7
+ npm install @crombolo/delivery-client
8
+ # in the site: write its types into the app
9
+ bunx crombolo generate --delivery --out ../app/src/cms-types.ts
10
+ ```
11
+
12
+ ```ts
13
+ import { createDeliveryClient, isExpanded } from '@crombolo/delivery-client'
14
+ import type { DeliveryTypes } from './cms-types'
15
+
16
+ const cms = createDeliveryClient<DeliveryTypes>({ baseUrl: 'https://cms.example.com' })
17
+
18
+ const page = await cms.item('/news/story', { expand: 'properties[related]' })
19
+ if (page?.contentType === 'article') {
20
+ console.log(page.properties.title)
21
+ const related = page.properties.related
22
+ if (related && isExpanded(related)) console.log(related.properties)
23
+ }
24
+
25
+ const latest = await cms.content({ fetch: 'children:/news', sort: 'createDate:desc', take: 5 })
26
+ const hits = await cms.search('harbour')
27
+ ```
28
+
29
+ - `item`, `items`, `content`, `mediaItem`, `mediaItems`, `media` and `search`,
30
+ with the API's own `expand`, `fields`, `fetch`, `filter` and `sort` grammar.
31
+ - `apiKey`, `culture`, `startItem` and `memberToken` options, each overridable
32
+ per request. Keep an API key out of code that runs in a browser.
33
+ - An item that is not there is `undefined`; anything else refused throws a
34
+ `DeliveryError` with its `status` and `body`.
35
+ - A browser app on another origin needs the site to allow it:
36
+ `deliveryApi.cors.allowedOrigins`.
37
+
38
+ The site's docs describe the API and the generated types: `docs/delivery-api.md`.
@@ -0,0 +1,294 @@
1
+ /**
2
+ * A typed client for a crombolo site's Delivery API (`/umbraco/delivery/api/v2`).
3
+ * The shapes are the JSON the API writes; `crombolo generate --delivery` writes
4
+ * a site's content types as a `DeliveryTypes` map to pass in as the generic.
5
+ * docs/delivery-api.md, "Typed apps".
6
+ *
7
+ * One file and no dependencies, because it is built to plain JavaScript for apps
8
+ * on any toolchain, unlike the rest of crombolo.
9
+ */
10
+ /** Where a page lives: its path below its root, and the root itself. */
11
+ export interface ApiRoute {
12
+ path: string;
13
+ queryString: string | null;
14
+ startItem: {
15
+ id: string;
16
+ path: string;
17
+ };
18
+ }
19
+ /** What a picker's target carries when it was not expanded. */
20
+ export type NotExpanded = Record<string, never>;
21
+ export interface ApiContent<Alias extends string = string, Properties = Record<string, unknown>> {
22
+ contentType: Alias;
23
+ name: string;
24
+ createDate: string;
25
+ updateDate: string;
26
+ route: ApiRoute;
27
+ id: string;
28
+ properties: Properties;
29
+ }
30
+ /** A page as a picker or a link holds it: its properties are there only when expanded. */
31
+ export type ApiContentReference<Alias extends string = string, Properties = Record<string, unknown>> = ApiContent<Alias, Properties | NotExpanded>;
32
+ /** A page as the item, items, query and search endpoints answer it. */
33
+ export interface ApiContentResponse<Alias extends string = string, Properties = Record<string, unknown>> extends ApiContent<Alias, Properties> {
34
+ /** Its route in each culture; empty for a page that does not vary. */
35
+ cultures: Record<string, ApiRoute>;
36
+ }
37
+ /** A block or picked element: its properties are always there. */
38
+ export interface ApiElement<Alias extends string = string, Properties = Record<string, unknown>> {
39
+ contentType: Alias;
40
+ id: string;
41
+ name: string;
42
+ properties: Properties;
43
+ }
44
+ export interface ApiFocalPoint {
45
+ left: number;
46
+ top: number;
47
+ }
48
+ export interface ApiCrop {
49
+ alias: string;
50
+ width: number;
51
+ height: number;
52
+ coordinates: {
53
+ x1: number;
54
+ y1: number;
55
+ x2: number;
56
+ y2: number;
57
+ } | null;
58
+ }
59
+ export interface ApiMedia<Alias extends string = string, Properties = Record<string, unknown>> {
60
+ id: string;
61
+ name: string;
62
+ mediaType: Alias;
63
+ url: string;
64
+ extension: string | null;
65
+ width: number | null;
66
+ height: number | null;
67
+ bytes: number | null;
68
+ properties: Properties;
69
+ }
70
+ /** Media as a picker holds it: its own properties are there only when expanded. */
71
+ export type ApiMediaReference<Alias extends string = string, Properties = Record<string, unknown>> = ApiMedia<Alias, Properties | NotExpanded>;
72
+ /** Media picked with the media picker: its crops and focal point, the pick's own first. */
73
+ export type ApiMediaWithCrops<Alias extends string = string, Properties = Record<string, unknown>> = ApiMediaReference<Alias, Properties> & {
74
+ focalPoint: ApiFocalPoint | null;
75
+ crops: ApiCrop[];
76
+ };
77
+ /** Media as the media item and query endpoints answer it. */
78
+ export type ApiMediaResponse<Alias extends string = string, Properties = Record<string, unknown>> = ApiMedia<Alias, Properties> & {
79
+ /** Names from the root down, lower case, with a trailing slash. */
80
+ path: string;
81
+ createDate: string;
82
+ updateDate: string;
83
+ focalPoint: ApiFocalPoint | null;
84
+ crops: ApiCrop[] | null;
85
+ };
86
+ export interface ApiLink {
87
+ url: string | null;
88
+ queryString: string | null;
89
+ title: string | null;
90
+ target: string | null;
91
+ destinationId: string | null;
92
+ destinationType: string | null;
93
+ route: ApiRoute | null;
94
+ linkType: 'Content' | 'Media' | 'External';
95
+ culture: string | null;
96
+ }
97
+ export interface ApiBlockListItem<Content = ApiElement, Settings = ApiElement> {
98
+ content: Content | null;
99
+ settings: Settings | null;
100
+ }
101
+ export interface ApiBlockList<Content = ApiElement, Settings = ApiElement> {
102
+ items: ApiBlockListItem<Content, Settings>[];
103
+ }
104
+ export interface ApiBlockGridArea<Content = ApiElement, Settings = ApiElement> {
105
+ alias: string;
106
+ rowSpan: number;
107
+ columnSpan: number;
108
+ items: ApiBlockGridItem<Content, Settings>[];
109
+ }
110
+ export interface ApiBlockGridItem<Content = ApiElement, Settings = ApiElement> extends ApiBlockListItem<Content, Settings> {
111
+ rowSpan: number;
112
+ columnSpan: number;
113
+ areaGridColumns: number;
114
+ areas: ApiBlockGridArea<Content, Settings>[];
115
+ }
116
+ export interface ApiBlockGrid<Content = ApiElement, Settings = ApiElement> {
117
+ gridColumns: number;
118
+ items: ApiBlockGridItem<Content, Settings>[];
119
+ }
120
+ /** Rich text: its markup, with links already this site's URLs. */
121
+ export interface ApiRichText {
122
+ markup: string;
123
+ blocks: unknown[];
124
+ }
125
+ export interface ApiColor {
126
+ value: string;
127
+ label: string;
128
+ }
129
+ export interface ApiImageCropper {
130
+ url: string | null;
131
+ focalPoint: ApiFocalPoint | null;
132
+ crops: ApiCrop[];
133
+ }
134
+ /** A site's types, as `crombolo generate --delivery` writes them: each alias's properties. */
135
+ export interface DeliverySchema {
136
+ content: object;
137
+ media: object;
138
+ }
139
+ type Aliases<Map> = Extract<keyof Map, string>;
140
+ /** Any page the site serves, told apart by `contentType`. */
141
+ export type ContentItem<Schema extends DeliverySchema = DeliverySchema> = DeliverySchema extends Schema ? ApiContentResponse : {
142
+ [Alias in Aliases<Schema['content']>]: ApiContentResponse<Alias, Schema['content'][Alias]>;
143
+ }[Aliases<Schema['content']>];
144
+ /** Any media item, told apart by `mediaType`. */
145
+ export type MediaItem<Schema extends DeliverySchema = DeliverySchema> = DeliverySchema extends Schema ? ApiMediaResponse : {
146
+ [Alias in Aliases<Schema['media']>]: ApiMediaResponse<Alias, Schema['media'][Alias]>;
147
+ }[Aliases<Schema['media']>];
148
+ /** A search result: the page, how well it matched, and an excerpt with matches in `<mark>`. */
149
+ export type SearchHit<Schema extends DeliverySchema = DeliverySchema> = ContentItem<Schema> & {
150
+ score: number;
151
+ /** HTML: everything escaped but the `<mark>` around each matching word. */
152
+ excerpt: string;
153
+ };
154
+ /** What the site answers at an address: a page, a redirect, or not found. */
155
+ export type RouteAnswer<Schema extends DeliverySchema = DeliverySchema> = {
156
+ kind: 'page';
157
+ culture: string | null;
158
+ item: ContentItem<Schema>;
159
+ } | {
160
+ kind: 'redirect';
161
+ location: string;
162
+ statusCode: number;
163
+ permanent: boolean;
164
+ }
165
+ /** With the site's not-found page, when it has one. */
166
+ | {
167
+ kind: 'notFound';
168
+ item: ContentItem<Schema> | null;
169
+ };
170
+ /** A menu's entry: a page, media item, address or label, with the entries below it. */
171
+ export interface ApiMenuItem {
172
+ label: string;
173
+ kind: 'page' | 'media' | 'external' | 'label';
174
+ /** The page or media item it links to. */
175
+ id: string | null;
176
+ /** An address for media and external links; a page has its `route` instead. */
177
+ url: string | null;
178
+ route: ApiRoute | null;
179
+ target: string | null;
180
+ children: ApiMenuItem[];
181
+ }
182
+ export interface ApiMenu {
183
+ alias: string;
184
+ items: ApiMenuItem[];
185
+ }
186
+ export interface Paged<Item> {
187
+ total: number;
188
+ items: Item[];
189
+ }
190
+ /**
191
+ * Whether a picker's target came back expanded. An expanded target carries every
192
+ * property, empty ones as `null`; an unexpanded one carries none.
193
+ */
194
+ export declare function isExpanded<Reference extends {
195
+ properties: object;
196
+ }>(reference: Reference): reference is Reference & {
197
+ properties: Exclude<Reference['properties'], NotExpanded>;
198
+ };
199
+ /** `fields` narrows the properties a response carries, so any may be missing. */
200
+ type Narrowed<Item> = Item extends {
201
+ properties: infer Properties;
202
+ } ? Omit<Item, 'properties'> & {
203
+ properties: Partial<Properties>;
204
+ } : Item;
205
+ type Fielded<Item, Options> = Options extends {
206
+ fields: string;
207
+ } ? Narrowed<Item> : Item;
208
+ export interface RequestOptions {
209
+ /** A culture name, such as `en-US`; otherwise the client's, then the site's default. */
210
+ culture?: string;
211
+ /** A root's id or path, to resolve paths and queries under it. */
212
+ startItem?: string;
213
+ /** Which pickers to expand: `properties[$all]`, `properties[related[properties[$all]]]`. */
214
+ expand?: string;
215
+ /** Which properties to include: `properties[title,image]`. */
216
+ fields?: string;
217
+ signal?: AbortSignal;
218
+ }
219
+ export interface ContentQuery extends RequestOptions {
220
+ /** `children:<id or path>`, `descendants:…` or `ancestors:…`; everything when left out. */
221
+ fetch?: string;
222
+ /** `contentType:page`, `name:news`, `createDate>2026-01-01`; filters combine with AND. */
223
+ filter?: string | string[];
224
+ /** `name:asc`, `createDate:desc`, `sortOrder:asc`, `level:asc`, `updateDate:desc`. */
225
+ sort?: string | string[];
226
+ skip?: number;
227
+ take?: number;
228
+ }
229
+ export interface MediaQuery extends Omit<RequestOptions, 'culture' | 'startItem'> {
230
+ /** Required: `children:/` for the top level, or `children:<id or path>`. */
231
+ fetch: string;
232
+ filter?: string | string[];
233
+ sort?: string | string[];
234
+ skip?: number;
235
+ take?: number;
236
+ }
237
+ export interface SearchOptions {
238
+ culture?: string;
239
+ /** Keep to these page types. */
240
+ contentType?: string | string[];
241
+ skip?: number;
242
+ /** At most 100. */
243
+ take?: number;
244
+ signal?: AbortSignal;
245
+ }
246
+ export interface DeliveryClientOptions {
247
+ /** The site's address, such as `https://cms.example.com`. */
248
+ baseUrl: string;
249
+ /** Sent as `Api-Key`, for a site whose API is not public. Keep it out of browser code. */
250
+ apiKey?: string;
251
+ /** The culture every request asks for unless it names its own. */
252
+ culture?: string;
253
+ /** The root every request resolves under unless it names its own. */
254
+ startItem?: string;
255
+ /** A signed-in member's access token, for members-only pages. Asked for on every request. */
256
+ memberToken?: () => string | undefined | Promise<string | undefined>;
257
+ /** Headers sent with every request. */
258
+ headers?: Record<string, string>;
259
+ /** Defaults to the global `fetch`. */
260
+ fetch?: (input: string, init: RequestInit) => Promise<Response>;
261
+ }
262
+ /** A refusal or failure from the API: its status, and its body when it had one. */
263
+ export declare class DeliveryError extends Error {
264
+ readonly status: number;
265
+ readonly body: unknown;
266
+ constructor(status: number, body: unknown, url: string);
267
+ }
268
+ export interface DeliveryClient<Schema extends DeliverySchema = DeliverySchema> {
269
+ /** One page by id or path; undefined when there is none. */
270
+ item<Options extends RequestOptions>(idOrPath: string, options?: Options): Promise<Fielded<ContentItem<Schema>, Options> | undefined>;
271
+ /** Pages by id, in the order asked, leaving out any that do not exist. */
272
+ items<Options extends RequestOptions>(ids: readonly string[], options?: Options): Promise<Fielded<ContentItem<Schema>, Options>[]>;
273
+ /** A page of pages, filtered and sorted. */
274
+ content<Options extends ContentQuery>(query?: Options): Promise<Paged<Fielded<ContentItem<Schema>, Options>>>;
275
+ /** One media item by id or path; undefined when there is none. */
276
+ mediaItem<Options extends RequestOptions>(idOrPath: string, options?: Options): Promise<Fielded<MediaItem<Schema>, Options> | undefined>;
277
+ mediaItems<Options extends RequestOptions>(ids: readonly string[], options?: Options): Promise<Fielded<MediaItem<Schema>, Options>[]>;
278
+ /** A page of media below a folder. */
279
+ media<Options extends MediaQuery>(query: Options): Promise<Paged<Fielded<MediaItem<Schema>, Options>>>;
280
+ /** Published pages matching every word of the term, best first. */
281
+ search(term: string, options?: SearchOptions): Promise<Paged<SearchHit<Schema>>>;
282
+ /**
283
+ * What the site would answer at a visitor's address — the page, a redirect, or
284
+ * not found — for an app's router. `host` picks the root and culture as the
285
+ * site's domains do.
286
+ */
287
+ /** A menu by its alias, in the culture asked for; undefined when there is none. */
288
+ menu(alias: string, options?: Pick<RequestOptions, 'culture' | 'signal'>): Promise<ApiMenu | undefined>;
289
+ route(path: string, options?: Omit<RequestOptions, 'culture' | 'fields'> & {
290
+ host?: string;
291
+ }): Promise<RouteAnswer<Schema>>;
292
+ }
293
+ export declare function createDeliveryClient<Schema extends DeliverySchema = DeliverySchema>(options: DeliveryClientOptions): DeliveryClient<Schema>;
294
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,86 @@
1
+ // packages/delivery-client/src/index.ts
2
+ function isExpanded(reference) {
3
+ return Object.keys(reference.properties).length > 0;
4
+ }
5
+
6
+ class DeliveryError extends Error {
7
+ status;
8
+ body;
9
+ constructor(status, body, url) {
10
+ super(`The Delivery API answered ${status} for ${url}`);
11
+ this.name = "DeliveryError";
12
+ this.status = status;
13
+ this.body = body;
14
+ }
15
+ }
16
+ var API_PATH = "/umbraco/delivery/api/v2";
17
+ var list = (value) => value === undefined ? [] : Array.isArray(value) ? value : [value];
18
+ var itemPath = (idOrPath) => idOrPath.replace(/^\/+/, "").split("/").map((segment) => encodeURIComponent(segment)).join("/");
19
+ function createDeliveryClient(options) {
20
+ const base = `${options.baseUrl.replace(/\/+$/, "")}${API_PATH}`;
21
+ const send = options.fetch ?? ((input, init) => fetch(input, init));
22
+ async function get(path, params, request, missing) {
23
+ const query = new URLSearchParams(params).toString();
24
+ const url = `${base}/${path}${query ? `?${query}` : ""}`;
25
+ const headers = { accept: "application/json", ...options.headers };
26
+ if (options.apiKey)
27
+ headers["api-key"] = options.apiKey;
28
+ const culture = request.culture ?? options.culture;
29
+ if (culture)
30
+ headers["accept-language"] = culture;
31
+ const startItem = request.startItem ?? options.startItem;
32
+ if (startItem)
33
+ headers["start-item"] = startItem;
34
+ const token = await options.memberToken?.();
35
+ if (token)
36
+ headers.authorization = `Bearer ${token}`;
37
+ const response = await send(url, { headers, signal: request.signal });
38
+ if (response.status === 404 && missing === "undefined")
39
+ return;
40
+ const text = await response.text();
41
+ const body = text ? JSON.parse(text) : null;
42
+ if (response.status === 404 && missing === "body")
43
+ return body;
44
+ if (!response.ok)
45
+ throw new DeliveryError(response.status, body, url);
46
+ return body;
47
+ }
48
+ const shaping = (request) => [
49
+ ...request.expand ? [["expand", request.expand]] : [],
50
+ ...request.fields ? [["fields", request.fields]] : []
51
+ ];
52
+ const paging = (query) => [
53
+ ...list(query.filter).map((filter) => ["filter", filter]),
54
+ ...list(query.sort).map((sort) => ["sort", sort]),
55
+ ...query.skip !== undefined ? [["skip", String(query.skip)]] : [],
56
+ ...query.take !== undefined ? [["take", String(query.take)]] : []
57
+ ];
58
+ const ids = (keys) => keys.map((id) => ["id", id]);
59
+ return {
60
+ item: (idOrPath, request) => get(`content/item/${itemPath(idOrPath)}`, shaping(request ?? {}), request ?? {}, "undefined"),
61
+ items: (keys, request) => get("content/items", [...ids(keys), ...shaping(request ?? {})], request ?? {}, "throw"),
62
+ content: (query) => {
63
+ const q = query ?? {};
64
+ return get("content", [...q.fetch ? [["fetch", q.fetch]] : [], ...paging(q), ...shaping(q)], q, "throw");
65
+ },
66
+ mediaItem: (idOrPath, request) => get(`media/item/${itemPath(idOrPath)}`, shaping(request ?? {}), request ?? {}, "undefined"),
67
+ mediaItems: (keys, request) => get("media/items", [...ids(keys), ...shaping(request ?? {})], request ?? {}, "throw"),
68
+ media: (query) => get("media", [["fetch", query.fetch], ...paging(query), ...shaping(query)], query, "throw"),
69
+ menu: (alias, request = {}) => get(`menus/${encodeURIComponent(alias)}`, [], request, "undefined"),
70
+ route: (path, request = {}) => get("route", [
71
+ ["path", path],
72
+ ...request.host ? [["host", request.host]] : [],
73
+ ...shaping(request)
74
+ ], request, "body"),
75
+ search: (term, search = {}) => get("search", [
76
+ ["term", term],
77
+ ...list(search.contentType).map((type) => ["contentType", type]),
78
+ ...paging(search)
79
+ ], search, "throw")
80
+ };
81
+ }
82
+ export {
83
+ DeliveryError,
84
+ createDeliveryClient,
85
+ isExpanded
86
+ };
package/package.json CHANGED
@@ -1,6 +1,39 @@
1
1
  {
2
2
  "name": "@crombolo/delivery-client",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.22.0",
4
+ "description": "A typed client for a crombolo site's Delivery API, for apps in the browser, Node or Bun. No dependencies.",
5
+ "license": "MIT",
6
+ "author": "Crombolo",
7
+ "homepage": "https://github.com/kineco-au/crombolo-cms#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/kineco-au/crombolo-cms.git",
11
+ "directory": "packages/delivery-client"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/kineco-au/crombolo-cms/issues"
15
+ },
16
+ "keywords": [
17
+ "crombolo",
18
+ "headless",
19
+ "delivery-api"
20
+ ],
21
+ "engines": {
22
+ "bun": ">=1.3.0"
23
+ },
24
+ "type": "module",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "import": "./dist/index.js",
29
+ "default": "./dist/index.js"
30
+ }
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "NOTICE"
35
+ ],
36
+ "publishConfig": {
37
+ "access": "public"
38
+ }
39
+ }