@crombolo/api-delivery 0.0.0-stage → 0.13.1

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/package.json CHANGED
@@ -1,6 +1,35 @@
1
1
  {
2
2
  "name": "@crombolo/api-delivery",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.13.1",
4
+ "description": "Umbraco's Content Delivery API for crombolo: published content and media as JSON, served from the published cache.",
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/api-delivery"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/kineco-au/crombolo-cms/issues"
15
+ },
16
+ "engines": {
17
+ "bun": ">=1.3.0"
18
+ },
19
+ "type": "module",
20
+ "exports": {
21
+ ".": "./src/index.ts"
22
+ },
23
+ "files": [
24
+ "src",
25
+ "NOTICE"
26
+ ],
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "dependencies": {
31
+ "@crombolo/core": "0.13.1",
32
+ "@crombolo/contracts": "0.13.1",
33
+ "@crombolo/render": "0.13.1"
34
+ }
35
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * How much of the Delivery API contract is served, found by asking: each
3
+ * operation is sent to the handler with placeholder parameters, and counts
4
+ * when the handler answers it at all rather than passing it on.
5
+ */
6
+ import { listOperations, loadDeliverySpec, type OperationInfo } from '@crombolo/contracts'
7
+ import type { DeliveryApi } from './handler.ts'
8
+
9
+ /** A request for an operation, its path parameters filled with placeholders. Pure. */
10
+ export function probeFor(op: OperationInfo, origin = 'http://localhost'): Request {
11
+ const path = op.path
12
+ .replace('{id}', '00000000-0000-4000-8000-000000000001')
13
+ .replace('{path}', 'probe')
14
+ return new Request(`${origin}${path}`, { method: op.method.toUpperCase() })
15
+ }
16
+
17
+ export async function deliveryCoverage(
18
+ api: DeliveryApi,
19
+ ): Promise<{ operations: OperationInfo[]; served: Set<string> }> {
20
+ const operations = listOperations(loadDeliverySpec())
21
+ const served = new Set<string>()
22
+ for (const op of operations) {
23
+ const response = await api.handle(probeFor(op))
24
+ if (response && response.status !== 501) served.add(op.operationId)
25
+ }
26
+ return { operations, served }
27
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * `expand` and `fields` (v2): which pickers are expanded, and which properties
3
+ * are kept, as Umbraco's `ElementOnlyOutputExpansionStrategy` reads them.
4
+ * `properties[a,b[properties[c]]]` is a tree; `$all` matches every alias.
5
+ */
6
+
7
+ export interface ExpansionNode {
8
+ key: string
9
+ children: ExpansionNode[]
10
+ }
11
+
12
+ export class ExpansionSyntaxError extends Error {}
13
+
14
+ /** `properties[a,b[properties[c]]]` as a tree under an unnamed root. Pure. */
15
+ export function parseExpansion(value: string): ExpansionNode {
16
+ const root: ExpansionNode = { key: '', children: [] }
17
+ const text = value.trim()
18
+ if (!text) return root
19
+ const opens = [...text].filter((c) => c === '[').length
20
+ const closes = [...text].filter((c) => c === ']').length
21
+ if (opens !== closes)
22
+ throw new ExpansionSyntaxError(
23
+ 'Value did not contain an equal number of start and end brackets',
24
+ )
25
+ if (text.startsWith('[')) throw new ExpansionSyntaxError('Value cannot start with a bracket')
26
+ if (text.includes('[]')) throw new ExpansionSyntaxError('Value cannot contain empty brackets')
27
+
28
+ const stack: ExpansionNode[] = [root]
29
+ let current: ExpansionNode | undefined
30
+ let name = ''
31
+ const flush = () => {
32
+ if (name.trim() && !current) {
33
+ current = { key: name.trim(), children: [] }
34
+ ;(stack[stack.length - 1] as ExpansionNode).children.push(current)
35
+ } else if (name.trim() && current) current.key += name.trim()
36
+ name = ''
37
+ }
38
+ for (const c of text) {
39
+ if (c === '[') {
40
+ flush()
41
+ if (!current) throw new ExpansionSyntaxError('Value cannot contain empty brackets')
42
+ stack.push(current)
43
+ current = undefined
44
+ } else if (c === ']') {
45
+ flush()
46
+ current = stack.pop()
47
+ } else if (c === ',') {
48
+ flush()
49
+ current = undefined
50
+ } else name += c
51
+ }
52
+ flush()
53
+ return root
54
+ }
55
+
56
+ const child = (node: ExpansionNode | undefined, key: string) =>
57
+ node?.children.find((c) => c.key === key)
58
+
59
+ /**
60
+ * The node governing a property at this level: `$all` here, else
61
+ * `properties[$all]` or `properties[<alias>]`. Pure.
62
+ */
63
+ export function nextNode(
64
+ node: ExpansionNode | undefined,
65
+ alias: string,
66
+ ): ExpansionNode | undefined {
67
+ if (!node) return undefined
68
+ const all = child(node, '$all')
69
+ if (all) return all
70
+ const properties = child(node, 'properties')
71
+ return child(properties, '$all') ?? child(properties, alias)
72
+ }
73
+
74
+ /** Whether `fields` keeps a property: a node with no children keeps everything beneath it. Pure. */
75
+ export function keeps(include: ExpansionNode | undefined, alias: string): boolean {
76
+ if (!include || include.children.length === 0) return true
77
+ return nextNode(include, alias) !== undefined
78
+ }
79
+
80
+ /** The default for an absent `fields`: everything. */
81
+ export const ALL_FIELDS: ExpansionNode = { key: '', children: [{ key: '$all', children: [] }] }