@andesine/extensions 0.0.0-stage → 0.5.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/CHANGELOG.md +42 -0
- package/LICENSE +21 -0
- package/README.md +84 -3
- package/dist/index.d.ts +295 -0
- package/dist/index.js +177 -0
- package/dist/index.js.map +1 -0
- package/dist/solid.d.ts +356 -0
- package/dist/solid.js +555 -0
- package/dist/solid.js.map +1 -0
- package/package.json +60 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
- First release, versioned with `@andesine/sdk` and the `andesine` CLI.
|
|
6
|
+
- Add the root entry for `andesine.config.ts`: `defineExtension` (the manifest; a Zod
|
|
7
|
+
`configuration` becomes its JSON Schema subset) and `defineBuild` (the frontend module
|
|
8
|
+
and custom icon sets for `andesine extensions build`).
|
|
9
|
+
- Add `@andesine/extensions/solid`: the worker runtime for extension frontends
|
|
10
|
+
(`startExtension`), a `solid-js/universal` renderer that sends serialized view
|
|
11
|
+
patches to Andesine, and the host components: layout (`Stack`, `Grid`, `Box`,
|
|
12
|
+
`Divider`, `ScrollArea`), text (`Text`, `Heading`, `Code`, `Link`, `Badge`,
|
|
13
|
+
`Icon`), controls (`Button`, `IconButton`, `Input`, `Textarea`, `Select`,
|
|
14
|
+
`Combobox`, `Checkbox`, `Toggle`, `ToggleGroup`, `ColorInput`, `Tooltip`),
|
|
15
|
+
settings (`Setting`, `List`), and data display (`Card`, `Tree`, `Tabs`, `Tab`,
|
|
16
|
+
`Disclosure`, `Callout`, `Steps`, `Step`, `Figure`, `CodeBlock`, `TimeAgo`,
|
|
17
|
+
`Spinner`, `Skeleton`).
|
|
18
|
+
- Layout and text components (`Stack`, `Grid`, `Box`, `Text`, `Heading`) accept
|
|
19
|
+
`class` with utility classes of the Andesine UnoCSS preset. Icons are icon
|
|
20
|
+
classes (`i-lucide:rocket`; `?bg` for colored icons) from the default Lucide
|
|
21
|
+
and Tabler sets, installed Iconify sets, or custom sets.
|
|
22
|
+
- Add `ContentSlot` for element views: it shows the element's editable content,
|
|
23
|
+
which Andesine owns. Element views receive `{ element, props }` with the
|
|
24
|
+
element name and its props.
|
|
25
|
+
- Add host actions: `copyText`, `downloadFile`, and `openURL`. They run shortly
|
|
26
|
+
after a user interaction in the extension's view and reject with
|
|
27
|
+
`ExtensionRequestError` (`code`) otherwise.
|
|
28
|
+
- Add `useEditor()` for views: block action views can `replace` the selected
|
|
29
|
+
blocks, `insertAfter` them, and `close`; element views can `setElementProps`.
|
|
30
|
+
Andesine validates each edit and applies it as the member's edit.
|
|
31
|
+
- Add `usePermissions()` and `hasPermission()` for the member's effective
|
|
32
|
+
permissions, `apiFetch` and `EXTENSION_API_URL` to use the Andesine SDK from
|
|
33
|
+
the frontend (requests go through Andesine with the member's session), and
|
|
34
|
+
`backend.fetch` for the extension's own backend with a session token.
|
|
35
|
+
- Add `useConfiguration()` for the configuration's non-secret values, with
|
|
36
|
+
defaults for unset fields. It updates when a manager saves the configuration.
|
|
37
|
+
- Add `storage` for the extension's JSON key-value storage, which its backend
|
|
38
|
+
shares: `get`, `set`, `delete`, and `list` (in key order, with an optional
|
|
39
|
+
prefix).
|
|
40
|
+
- `backend.fetch` also sends the `Andesine-Extension` and `Andesine-Instance`
|
|
41
|
+
headers, which `verifySession` from `@andesine/sdk/extensions` needs. The
|
|
42
|
+
backend's CORS configuration must allow them with `Authorization`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vrite, Inc.
|
|
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/README.md
CHANGED
|
@@ -1,4 +1,85 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @andesine/extensions
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Authoring tools for Andesine extensions. Extension frontends are Solid components
|
|
4
|
+
that run in a sandboxed worker. Andesine renders its own components from the
|
|
5
|
+
serialized view tree; extension code has no DOM access.
|
|
6
|
+
|
|
7
|
+
## Solid entry
|
|
8
|
+
|
|
9
|
+
Compile extension JSX for the universal renderer:
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
// babel-preset-solid options
|
|
13
|
+
{ generate: "universal", moduleName: "@andesine/extensions/solid" }
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Bundle for the `browser` export condition (Solid's `worker` condition selects its
|
|
17
|
+
non-reactive server build). Then register the views that the manifest names:
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { createSignal } from "solid-js";
|
|
21
|
+
import { Button, Stack, Text, startExtension } from "@andesine/extensions/solid";
|
|
22
|
+
|
|
23
|
+
const Counter = () => {
|
|
24
|
+
const [count, setCount] = createSignal(0);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Stack direction="row" gap="small">
|
|
28
|
+
<Text>{count()}</Text>
|
|
29
|
+
<Button onClick={() => setCount(count() + 1)}>Add</Button>
|
|
30
|
+
</Stack>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
startExtension({ Counter });
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Only the exported host components render. Props must be JSON values; functions
|
|
38
|
+
become callbacks that the host calls with plain values.
|
|
39
|
+
|
|
40
|
+
## Element views
|
|
41
|
+
|
|
42
|
+
An element view renders a document element (for example `<Accordion>`) and
|
|
43
|
+
receives `{ element, props }`. `ContentSlot` shows the element's editable
|
|
44
|
+
content; Andesine keeps that content when the slot is hidden. A view's CSS does
|
|
45
|
+
not apply inside its slot.
|
|
46
|
+
|
|
47
|
+
## Host actions
|
|
48
|
+
|
|
49
|
+
`copyText(text)`, `downloadFile(name, text, type?)`, and `openURL(url)` ask
|
|
50
|
+
Andesine to act for the extension. Call them from an interaction, for example a
|
|
51
|
+
button's `onClick`; otherwise they reject with `ExtensionRequestError` and the
|
|
52
|
+
code `user_interaction_required`. `openURL` asks the member to confirm URLs
|
|
53
|
+
outside the manifest's declared URLs.
|
|
54
|
+
|
|
55
|
+
## Block actions and edits
|
|
56
|
+
|
|
57
|
+
A block action view receives `{ blocks }`: the selected blocks as editor JSON.
|
|
58
|
+
`useEditor()` returns `replace(content)`, `insertAfter(content)`, and `close()`
|
|
59
|
+
for block action views, and `setElementProps(props)` for element views (call it
|
|
60
|
+
from an interaction). Content must be blocks that are allowed where the
|
|
61
|
+
selection is; edits fail when the selected blocks changed meanwhile.
|
|
62
|
+
|
|
63
|
+
## API, permissions, and backend
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { createClient } from "@andesine/sdk";
|
|
67
|
+
import { EXTENSION_API_URL, apiFetch, backend, hasPermission } from "@andesine/extensions/solid";
|
|
68
|
+
|
|
69
|
+
const client = createClient({ baseURL: EXTENSION_API_URL, fetch: apiFetch });
|
|
70
|
+
const entries = hasPermission("read:entries") ? await client.entries.list({}) : [];
|
|
71
|
+
const response = await backend.fetch("/publish", { method: "POST" });
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
API requests use the member's session and are limited to the member's
|
|
75
|
+
permissions that the extension was granted (`usePermissions()`); other
|
|
76
|
+
operations answer with HTTP 403. `backend.fetch` calls paths of the manifest's
|
|
77
|
+
backend URL with a short-lived session token in `Authorization: Bearer`, which
|
|
78
|
+
the backend verifies with Andesine.
|
|
79
|
+
|
|
80
|
+
## Examples
|
|
81
|
+
|
|
82
|
+
See the extension examples in this repository (`packages/public/extension-examples`): element
|
|
83
|
+
views, a block action that calls its backend, a webhook-driven backend, and a secret
|
|
84
|
+
configuration field. `npx andesine extensions init` creates a project with samples of every
|
|
85
|
+
feature.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
//#region ../../private/contracts/src/extensions/configuration.d.ts
|
|
3
|
+
type ExtensionConfiguration = z.output<typeof extensionConfigurationType>;
|
|
4
|
+
/** Flat JSON Schema (draft 2020-12) subset that the host validates without running extension code. */
|
|
5
|
+
declare const extensionConfigurationType: z.ZodObject<{
|
|
6
|
+
$schema: z.ZodOptional<z.ZodLiteral<"https://json-schema.org/draft/2020-12/schema">>;
|
|
7
|
+
type: z.ZodLiteral<"object">;
|
|
8
|
+
properties: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
|
|
9
|
+
type: z.ZodLiteral<"string">;
|
|
10
|
+
minLength: z.ZodOptional<z.ZodInt>;
|
|
11
|
+
maxLength: z.ZodOptional<z.ZodInt>;
|
|
12
|
+
pattern: z.ZodOptional<z.ZodString>;
|
|
13
|
+
format: z.ZodOptional<z.ZodEnum<{
|
|
14
|
+
uri: "uri";
|
|
15
|
+
email: "email";
|
|
16
|
+
}>>;
|
|
17
|
+
default: z.ZodOptional<z.ZodString>;
|
|
18
|
+
secret: z.ZodOptional<z.ZodLiteral<true>>;
|
|
19
|
+
control: z.ZodOptional<z.ZodLiteral<"textarea">>;
|
|
20
|
+
title: z.ZodOptional<z.ZodString>;
|
|
21
|
+
description: z.ZodOptional<z.ZodString>;
|
|
22
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
23
|
+
type: z.ZodLiteral<"string">;
|
|
24
|
+
enum: z.ZodArray<z.ZodString>;
|
|
25
|
+
default: z.ZodOptional<z.ZodString>;
|
|
26
|
+
title: z.ZodOptional<z.ZodString>;
|
|
27
|
+
description: z.ZodOptional<z.ZodString>;
|
|
28
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
29
|
+
type: z.ZodEnum<{
|
|
30
|
+
number: "number";
|
|
31
|
+
integer: "integer";
|
|
32
|
+
}>;
|
|
33
|
+
minimum: z.ZodOptional<z.ZodNumber>;
|
|
34
|
+
maximum: z.ZodOptional<z.ZodNumber>;
|
|
35
|
+
exclusiveMinimum: z.ZodOptional<z.ZodNumber>;
|
|
36
|
+
exclusiveMaximum: z.ZodOptional<z.ZodNumber>;
|
|
37
|
+
multipleOf: z.ZodOptional<z.ZodNumber>;
|
|
38
|
+
default: z.ZodOptional<z.ZodNumber>;
|
|
39
|
+
title: z.ZodOptional<z.ZodString>;
|
|
40
|
+
description: z.ZodOptional<z.ZodString>;
|
|
41
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
42
|
+
type: z.ZodLiteral<"boolean">;
|
|
43
|
+
default: z.ZodOptional<z.ZodBoolean>;
|
|
44
|
+
title: z.ZodOptional<z.ZodString>;
|
|
45
|
+
description: z.ZodOptional<z.ZodString>;
|
|
46
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
47
|
+
type: z.ZodLiteral<"array">;
|
|
48
|
+
items: z.ZodUnion<readonly [z.ZodObject<{
|
|
49
|
+
type: z.ZodLiteral<"string">;
|
|
50
|
+
minLength: z.ZodOptional<z.ZodInt>;
|
|
51
|
+
maxLength: z.ZodOptional<z.ZodInt>;
|
|
52
|
+
pattern: z.ZodOptional<z.ZodString>;
|
|
53
|
+
format: z.ZodOptional<z.ZodEnum<{
|
|
54
|
+
uri: "uri";
|
|
55
|
+
email: "email";
|
|
56
|
+
}>>;
|
|
57
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
58
|
+
type: z.ZodLiteral<"string">;
|
|
59
|
+
enum: z.ZodArray<z.ZodString>;
|
|
60
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
61
|
+
type: z.ZodEnum<{
|
|
62
|
+
number: "number";
|
|
63
|
+
integer: "integer";
|
|
64
|
+
}>;
|
|
65
|
+
minimum: z.ZodOptional<z.ZodNumber>;
|
|
66
|
+
maximum: z.ZodOptional<z.ZodNumber>;
|
|
67
|
+
exclusiveMinimum: z.ZodOptional<z.ZodNumber>;
|
|
68
|
+
exclusiveMaximum: z.ZodOptional<z.ZodNumber>;
|
|
69
|
+
multipleOf: z.ZodOptional<z.ZodNumber>;
|
|
70
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
71
|
+
type: z.ZodLiteral<"boolean">;
|
|
72
|
+
}, z.core.$strict>]>;
|
|
73
|
+
minItems: z.ZodOptional<z.ZodInt>;
|
|
74
|
+
maxItems: z.ZodOptional<z.ZodInt>;
|
|
75
|
+
uniqueItems: z.ZodOptional<z.ZodBoolean>;
|
|
76
|
+
default: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
77
|
+
title: z.ZodOptional<z.ZodString>;
|
|
78
|
+
description: z.ZodOptional<z.ZodString>;
|
|
79
|
+
}, z.core.$strict>]>>;
|
|
80
|
+
required: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
81
|
+
additionalProperties: z.ZodLiteral<false>;
|
|
82
|
+
}, z.core.$strict>;
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region ../../private/contracts/src/extensions/manifest.d.ts
|
|
85
|
+
declare const extensionManifestType: z.ZodObject<{
|
|
86
|
+
elementViews: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
87
|
+
id: z.ZodString;
|
|
88
|
+
name: z.ZodString;
|
|
89
|
+
element: z.ZodString;
|
|
90
|
+
entry: z.ZodString;
|
|
91
|
+
descendants: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
92
|
+
element: z.ZodString;
|
|
93
|
+
entry: z.ZodString;
|
|
94
|
+
}, z.core.$strict>>>;
|
|
95
|
+
}, z.core.$strict>>>;
|
|
96
|
+
blockActions: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
97
|
+
id: z.ZodString;
|
|
98
|
+
label: z.ZodString;
|
|
99
|
+
icon: z.ZodOptional<z.ZodString>;
|
|
100
|
+
blocks: z.ZodArray<z.ZodString>;
|
|
101
|
+
entry: z.ZodString;
|
|
102
|
+
}, z.core.$strict>>>;
|
|
103
|
+
panels: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
104
|
+
side: z.ZodLiteral<"left">;
|
|
105
|
+
entry: z.ZodString;
|
|
106
|
+
id: z.ZodString;
|
|
107
|
+
name: z.ZodString;
|
|
108
|
+
icon: z.ZodString;
|
|
109
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
110
|
+
side: z.ZodLiteral<"right">;
|
|
111
|
+
context: z.ZodEnum<{
|
|
112
|
+
entry: "entry";
|
|
113
|
+
workspace: "workspace";
|
|
114
|
+
collection: "collection";
|
|
115
|
+
}>;
|
|
116
|
+
entry: z.ZodString;
|
|
117
|
+
id: z.ZodString;
|
|
118
|
+
name: z.ZodString;
|
|
119
|
+
icon: z.ZodString;
|
|
120
|
+
}, z.core.$strict>], "side">>>;
|
|
121
|
+
settingsSections: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
122
|
+
id: z.ZodString;
|
|
123
|
+
heading: z.ZodString;
|
|
124
|
+
entry: z.ZodString;
|
|
125
|
+
}, z.core.$strict>>>;
|
|
126
|
+
name: z.ZodString;
|
|
127
|
+
version: z.ZodString;
|
|
128
|
+
displayName: z.ZodString;
|
|
129
|
+
description: z.ZodString;
|
|
130
|
+
icon: z.ZodOptional<z.ZodString>;
|
|
131
|
+
apiVersion: z.ZodLiteral<1>;
|
|
132
|
+
permissions: z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
133
|
+
"read:restricted_collections": "read:restricted_collections";
|
|
134
|
+
entries: "entries";
|
|
135
|
+
"read:entries": "read:entries";
|
|
136
|
+
versions: "versions";
|
|
137
|
+
"read:versions": "read:versions";
|
|
138
|
+
publishing: "publishing";
|
|
139
|
+
"read:publishing": "read:publishing";
|
|
140
|
+
collections: "collections";
|
|
141
|
+
"read:collections": "read:collections";
|
|
142
|
+
memberships: "memberships";
|
|
143
|
+
"read:memberships": "read:memberships";
|
|
144
|
+
roles: "roles";
|
|
145
|
+
"read:roles": "read:roles";
|
|
146
|
+
webhooks: "webhooks";
|
|
147
|
+
"read:webhooks": "read:webhooks";
|
|
148
|
+
"ai-answers": "ai-answers";
|
|
149
|
+
}>>>;
|
|
150
|
+
requests: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
151
|
+
backend: z.ZodOptional<z.ZodObject<{
|
|
152
|
+
url: z.ZodString;
|
|
153
|
+
keys: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
154
|
+
kid: z.ZodString;
|
|
155
|
+
kty: z.ZodLiteral<"OKP">;
|
|
156
|
+
alg: z.ZodLiteral<"EdDSA">;
|
|
157
|
+
crv: z.ZodLiteral<"Ed25519">;
|
|
158
|
+
x: z.ZodString;
|
|
159
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
160
|
+
kid: z.ZodString;
|
|
161
|
+
kty: z.ZodLiteral<"EC">;
|
|
162
|
+
alg: z.ZodLiteral<"ES256">;
|
|
163
|
+
crv: z.ZodLiteral<"P-256">;
|
|
164
|
+
x: z.ZodString;
|
|
165
|
+
y: z.ZodString;
|
|
166
|
+
}, z.core.$strict>], "alg">>;
|
|
167
|
+
}, z.core.$strict>>;
|
|
168
|
+
webhooks: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
169
|
+
events: z.ZodArray<z.ZodEnum<{
|
|
170
|
+
"entry.created": "entry.created";
|
|
171
|
+
"entry.updated": "entry.updated";
|
|
172
|
+
"entry.content_saved": "entry.content_saved";
|
|
173
|
+
"entry.moved": "entry.moved";
|
|
174
|
+
"entry.deleted": "entry.deleted";
|
|
175
|
+
"entry.restored": "entry.restored";
|
|
176
|
+
"collection.created": "collection.created";
|
|
177
|
+
"collection.updated": "collection.updated";
|
|
178
|
+
"collection.moved": "collection.moved";
|
|
179
|
+
"collection.deleted": "collection.deleted";
|
|
180
|
+
"collection.restored": "collection.restored";
|
|
181
|
+
"publishing.channel_advanced": "publishing.channel_advanced";
|
|
182
|
+
"publishing.channel_created": "publishing.channel_created";
|
|
183
|
+
"publishing.channel_deleted": "publishing.channel_deleted";
|
|
184
|
+
"extension.installed": "extension.installed";
|
|
185
|
+
"extension.enabled": "extension.enabled";
|
|
186
|
+
"extension.disabled": "extension.disabled";
|
|
187
|
+
"extension.updated": "extension.updated";
|
|
188
|
+
"extension.configured": "extension.configured";
|
|
189
|
+
"extension.uninstalled": "extension.uninstalled";
|
|
190
|
+
}>>;
|
|
191
|
+
path: z.ZodString;
|
|
192
|
+
channels: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
193
|
+
}, z.core.$strict>>>;
|
|
194
|
+
configuration: z.ZodOptional<z.ZodObject<{
|
|
195
|
+
$schema: z.ZodOptional<z.ZodLiteral<"https://json-schema.org/draft/2020-12/schema">>;
|
|
196
|
+
type: z.ZodLiteral<"object">;
|
|
197
|
+
properties: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
|
|
198
|
+
type: z.ZodLiteral<"string">;
|
|
199
|
+
minLength: z.ZodOptional<z.ZodInt>;
|
|
200
|
+
maxLength: z.ZodOptional<z.ZodInt>;
|
|
201
|
+
pattern: z.ZodOptional<z.ZodString>;
|
|
202
|
+
format: z.ZodOptional<z.ZodEnum<{
|
|
203
|
+
uri: "uri";
|
|
204
|
+
email: "email";
|
|
205
|
+
}>>;
|
|
206
|
+
default: z.ZodOptional<z.ZodString>;
|
|
207
|
+
secret: z.ZodOptional<z.ZodLiteral<true>>;
|
|
208
|
+
control: z.ZodOptional<z.ZodLiteral<"textarea">>;
|
|
209
|
+
title: z.ZodOptional<z.ZodString>;
|
|
210
|
+
description: z.ZodOptional<z.ZodString>;
|
|
211
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
212
|
+
type: z.ZodLiteral<"string">;
|
|
213
|
+
enum: z.ZodArray<z.ZodString>;
|
|
214
|
+
default: z.ZodOptional<z.ZodString>;
|
|
215
|
+
title: z.ZodOptional<z.ZodString>;
|
|
216
|
+
description: z.ZodOptional<z.ZodString>;
|
|
217
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
218
|
+
type: z.ZodEnum<{
|
|
219
|
+
number: "number";
|
|
220
|
+
integer: "integer";
|
|
221
|
+
}>;
|
|
222
|
+
minimum: z.ZodOptional<z.ZodNumber>;
|
|
223
|
+
maximum: z.ZodOptional<z.ZodNumber>;
|
|
224
|
+
exclusiveMinimum: z.ZodOptional<z.ZodNumber>;
|
|
225
|
+
exclusiveMaximum: z.ZodOptional<z.ZodNumber>;
|
|
226
|
+
multipleOf: z.ZodOptional<z.ZodNumber>;
|
|
227
|
+
default: z.ZodOptional<z.ZodNumber>;
|
|
228
|
+
title: z.ZodOptional<z.ZodString>;
|
|
229
|
+
description: z.ZodOptional<z.ZodString>;
|
|
230
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
231
|
+
type: z.ZodLiteral<"boolean">;
|
|
232
|
+
default: z.ZodOptional<z.ZodBoolean>;
|
|
233
|
+
title: z.ZodOptional<z.ZodString>;
|
|
234
|
+
description: z.ZodOptional<z.ZodString>;
|
|
235
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
236
|
+
type: z.ZodLiteral<"array">;
|
|
237
|
+
items: z.ZodUnion<readonly [z.ZodObject<{
|
|
238
|
+
type: z.ZodLiteral<"string">;
|
|
239
|
+
minLength: z.ZodOptional<z.ZodInt>;
|
|
240
|
+
maxLength: z.ZodOptional<z.ZodInt>;
|
|
241
|
+
pattern: z.ZodOptional<z.ZodString>;
|
|
242
|
+
format: z.ZodOptional<z.ZodEnum<{
|
|
243
|
+
uri: "uri";
|
|
244
|
+
email: "email";
|
|
245
|
+
}>>;
|
|
246
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
247
|
+
type: z.ZodLiteral<"string">;
|
|
248
|
+
enum: z.ZodArray<z.ZodString>;
|
|
249
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
250
|
+
type: z.ZodEnum<{
|
|
251
|
+
number: "number";
|
|
252
|
+
integer: "integer";
|
|
253
|
+
}>;
|
|
254
|
+
minimum: z.ZodOptional<z.ZodNumber>;
|
|
255
|
+
maximum: z.ZodOptional<z.ZodNumber>;
|
|
256
|
+
exclusiveMinimum: z.ZodOptional<z.ZodNumber>;
|
|
257
|
+
exclusiveMaximum: z.ZodOptional<z.ZodNumber>;
|
|
258
|
+
multipleOf: z.ZodOptional<z.ZodNumber>;
|
|
259
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
260
|
+
type: z.ZodLiteral<"boolean">;
|
|
261
|
+
}, z.core.$strict>]>;
|
|
262
|
+
minItems: z.ZodOptional<z.ZodInt>;
|
|
263
|
+
maxItems: z.ZodOptional<z.ZodInt>;
|
|
264
|
+
uniqueItems: z.ZodOptional<z.ZodBoolean>;
|
|
265
|
+
default: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
266
|
+
title: z.ZodOptional<z.ZodString>;
|
|
267
|
+
description: z.ZodOptional<z.ZodString>;
|
|
268
|
+
}, z.core.$strict>]>>;
|
|
269
|
+
required: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
270
|
+
additionalProperties: z.ZodLiteral<false>;
|
|
271
|
+
}, z.core.$strict>>;
|
|
272
|
+
}, z.core.$strict>;
|
|
273
|
+
//#endregion
|
|
274
|
+
//#region src/index.d.ts
|
|
275
|
+
interface ExtensionDefinition extends Omit<ExtensionManifestInput, "configuration"> {
|
|
276
|
+
/** A Zod object of flat fields, or the configuration schema it converts to. */
|
|
277
|
+
configuration?: z.ZodObject | ExtensionConfiguration;
|
|
278
|
+
}
|
|
279
|
+
/** Options of `andesine extensions build`, exported from the config file as `build`. */
|
|
280
|
+
interface ExtensionBuildOptions {
|
|
281
|
+
/** The frontend module that exports the manifest's view entries (default: `src/frontend/index.tsx`). */
|
|
282
|
+
frontend?: string;
|
|
283
|
+
/** Custom icon sets: a set prefix and a directory of SVG files, e.g. `{ acme: "./icons" }`. */
|
|
284
|
+
icons?: Record<string, string>;
|
|
285
|
+
}
|
|
286
|
+
type ExtensionManifestInput = z.input<typeof extensionManifestType>;
|
|
287
|
+
/**
|
|
288
|
+
* Defines the extension manifest, the default export of `andesine.config.ts`. A Zod
|
|
289
|
+
* configuration becomes its JSON Schema subset; `andesine extensions build` validates the rest.
|
|
290
|
+
*/
|
|
291
|
+
declare const defineExtension: (definition: ExtensionDefinition) => ExtensionManifestInput;
|
|
292
|
+
declare const defineBuild: (options: ExtensionBuildOptions) => ExtensionBuildOptions;
|
|
293
|
+
//#endregion
|
|
294
|
+
export { type ExtensionBuildOptions, type ExtensionDefinition, type ExtensionManifestInput, defineBuild, defineExtension };
|
|
295
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
//#region ../../private/contracts/src/primitives/unique.ts
|
|
3
|
+
/** Whether every value is distinct. */
|
|
4
|
+
const uniqueItems = (values) => new Set(values).size === values.length;
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region ../../private/contracts/src/extensions/configuration.ts
|
|
7
|
+
const JSON_SCHEMA_DIALECT = "https://json-schema.org/draft/2020-12/schema";
|
|
8
|
+
const MAX_CONFIGURATION_STRING_LENGTH = 1e4;
|
|
9
|
+
const MAX_CONFIGURATION_ARRAY_ITEMS = 100;
|
|
10
|
+
const MAX_CONFIGURATION_ENUM_VALUES = 100;
|
|
11
|
+
const MAX_CONFIGURATION_PATTERN_LENGTH = 500;
|
|
12
|
+
const isValidPattern = (pattern) => {
|
|
13
|
+
try {
|
|
14
|
+
new RegExp(pattern);
|
|
15
|
+
return true;
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const fieldKeyType = z.string().regex(/^[A-Za-z][A-Za-z0-9_]{0,63}$/, "Use a letter, then letters, digits, or underscores");
|
|
21
|
+
const lengthType = z.int().min(0).max(MAX_CONFIGURATION_STRING_LENGTH);
|
|
22
|
+
const metadata = {
|
|
23
|
+
title: z.string().min(1).max(100).optional(),
|
|
24
|
+
description: z.string().min(1).max(500).optional()
|
|
25
|
+
};
|
|
26
|
+
const stringItemType = z.strictObject({
|
|
27
|
+
type: z.literal("string"),
|
|
28
|
+
minLength: lengthType.optional(),
|
|
29
|
+
maxLength: lengthType.optional(),
|
|
30
|
+
pattern: z.string().min(1).max(MAX_CONFIGURATION_PATTERN_LENGTH).refine(isValidPattern, "Use a valid regular expression").optional(),
|
|
31
|
+
format: z.enum(["uri", "email"]).optional()
|
|
32
|
+
});
|
|
33
|
+
const enumItemType = z.strictObject({
|
|
34
|
+
type: z.literal("string"),
|
|
35
|
+
enum: z.array(z.string().min(1).max(200)).min(1).max(MAX_CONFIGURATION_ENUM_VALUES).refine(uniqueItems, "Options must be unique")
|
|
36
|
+
});
|
|
37
|
+
const numberItemType = z.strictObject({
|
|
38
|
+
type: z.enum(["number", "integer"]),
|
|
39
|
+
minimum: z.number().optional(),
|
|
40
|
+
maximum: z.number().optional(),
|
|
41
|
+
exclusiveMinimum: z.number().optional(),
|
|
42
|
+
exclusiveMaximum: z.number().optional(),
|
|
43
|
+
multipleOf: z.number().positive().optional()
|
|
44
|
+
});
|
|
45
|
+
const booleanItemType = z.strictObject({ type: z.literal("boolean") });
|
|
46
|
+
const stringFormatTypes = {
|
|
47
|
+
uri: z.url(),
|
|
48
|
+
email: z.email()
|
|
49
|
+
};
|
|
50
|
+
const itemType = z.union([
|
|
51
|
+
stringItemType,
|
|
52
|
+
enumItemType,
|
|
53
|
+
numberItemType,
|
|
54
|
+
booleanItemType
|
|
55
|
+
]);
|
|
56
|
+
const fieldType = z.union([
|
|
57
|
+
stringItemType.extend({
|
|
58
|
+
...metadata,
|
|
59
|
+
default: z.string().optional(),
|
|
60
|
+
secret: z.literal(true).optional(),
|
|
61
|
+
control: z.literal("textarea").optional()
|
|
62
|
+
}),
|
|
63
|
+
enumItemType.extend({
|
|
64
|
+
...metadata,
|
|
65
|
+
default: z.string().optional()
|
|
66
|
+
}),
|
|
67
|
+
numberItemType.extend({
|
|
68
|
+
...metadata,
|
|
69
|
+
default: z.number().optional()
|
|
70
|
+
}),
|
|
71
|
+
booleanItemType.extend({
|
|
72
|
+
...metadata,
|
|
73
|
+
default: z.boolean().optional()
|
|
74
|
+
}),
|
|
75
|
+
z.strictObject({
|
|
76
|
+
...metadata,
|
|
77
|
+
type: z.literal("array"),
|
|
78
|
+
items: itemType,
|
|
79
|
+
minItems: z.int().min(0).max(MAX_CONFIGURATION_ARRAY_ITEMS).optional(),
|
|
80
|
+
maxItems: z.int().min(0).max(MAX_CONFIGURATION_ARRAY_ITEMS).optional(),
|
|
81
|
+
uniqueItems: z.boolean().optional(),
|
|
82
|
+
default: z.array(z.union([
|
|
83
|
+
z.string(),
|
|
84
|
+
z.number(),
|
|
85
|
+
z.boolean()
|
|
86
|
+
])).optional()
|
|
87
|
+
})
|
|
88
|
+
]);
|
|
89
|
+
/** Flat JSON Schema (draft 2020-12) subset that the host validates without running extension code. */
|
|
90
|
+
const extensionConfigurationType = z.strictObject({
|
|
91
|
+
$schema: z.literal(JSON_SCHEMA_DIALECT).optional(),
|
|
92
|
+
type: z.literal("object"),
|
|
93
|
+
properties: z.record(fieldKeyType, fieldType).refine((properties) => Object.keys(properties).length <= 50, `Use at most 50 fields`),
|
|
94
|
+
required: z.array(fieldKeyType).refine(uniqueItems, "Fields must be unique").optional(),
|
|
95
|
+
additionalProperties: z.literal(false)
|
|
96
|
+
}).superRefine((configuration, context) => {
|
|
97
|
+
for (const [index, key] of (configuration.required || []).entries()) if (!configuration.properties[key]) context.addIssue({
|
|
98
|
+
code: "custom",
|
|
99
|
+
path: ["required", index],
|
|
100
|
+
message: "Unknown field"
|
|
101
|
+
});
|
|
102
|
+
for (const [key, field] of Object.entries(configuration.properties)) if ("secret" in field && field.secret && field.default !== void 0) context.addIssue({
|
|
103
|
+
code: "custom",
|
|
104
|
+
path: [
|
|
105
|
+
"properties",
|
|
106
|
+
key,
|
|
107
|
+
"default"
|
|
108
|
+
],
|
|
109
|
+
message: "Secret fields cannot have defaults"
|
|
110
|
+
});
|
|
111
|
+
else if (field.default !== void 0) {
|
|
112
|
+
if (!createConfigurationValueType(field).safeParse(field.default).success) context.addIssue({
|
|
113
|
+
code: "custom",
|
|
114
|
+
path: [
|
|
115
|
+
"properties",
|
|
116
|
+
key,
|
|
117
|
+
"default"
|
|
118
|
+
],
|
|
119
|
+
message: "The default does not match the field"
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
const createConfigurationValueType = (field) => {
|
|
124
|
+
if (field.type === "boolean") return z.boolean();
|
|
125
|
+
if (field.type === "array") {
|
|
126
|
+
const maxItems = Math.min(field.maxItems ?? MAX_CONFIGURATION_ARRAY_ITEMS, MAX_CONFIGURATION_ARRAY_ITEMS);
|
|
127
|
+
const items = z.array(createConfigurationValueType(field.items)).min(field.minItems ?? 0).max(maxItems);
|
|
128
|
+
return field.uniqueItems ? items.refine(uniqueItems, "Values must be unique") : items;
|
|
129
|
+
}
|
|
130
|
+
if ("enum" in field) return z.enum(field.enum);
|
|
131
|
+
if (field.type === "string") {
|
|
132
|
+
const maxLength = Math.min(field.maxLength ?? MAX_CONFIGURATION_STRING_LENGTH, MAX_CONFIGURATION_STRING_LENGTH);
|
|
133
|
+
const value = (field.format ? stringFormatTypes[field.format] : z.string()).min(field.minLength ?? 0).max(maxLength);
|
|
134
|
+
return field.pattern ? value.regex(new RegExp(field.pattern)) : value;
|
|
135
|
+
}
|
|
136
|
+
let value = field.type === "integer" ? z.int() : z.number();
|
|
137
|
+
if (field.minimum !== void 0) value = value.gte(field.minimum);
|
|
138
|
+
if (field.maximum !== void 0) value = value.lte(field.maximum);
|
|
139
|
+
if (field.exclusiveMinimum !== void 0) value = value.gt(field.exclusiveMinimum);
|
|
140
|
+
if (field.exclusiveMaximum !== void 0) value = value.lt(field.exclusiveMaximum);
|
|
141
|
+
if (field.multipleOf !== void 0) value = value.multipleOf(field.multipleOf);
|
|
142
|
+
return value;
|
|
143
|
+
};
|
|
144
|
+
/** Converts an authoring Zod object to the configuration subset; unsupported constructs throw. */
|
|
145
|
+
const toExtensionConfiguration = (schema) => {
|
|
146
|
+
const jsonSchema = z.toJSONSchema(schema, {
|
|
147
|
+
io: "input",
|
|
148
|
+
target: "draft-2020-12",
|
|
149
|
+
unrepresentable: "throw"
|
|
150
|
+
});
|
|
151
|
+
const result = extensionConfigurationType.safeParse({
|
|
152
|
+
additionalProperties: false,
|
|
153
|
+
...jsonSchema
|
|
154
|
+
});
|
|
155
|
+
if (!result.success) throw new Error(`Unsupported configuration schema:\n${z.prettifyError(result.error)}`);
|
|
156
|
+
return result.data;
|
|
157
|
+
};
|
|
158
|
+
//#endregion
|
|
159
|
+
//#region src/index.ts
|
|
160
|
+
const isZodObject = (value) => "_zod" in value;
|
|
161
|
+
/**
|
|
162
|
+
* Defines the extension manifest, the default export of `andesine.config.ts`. A Zod
|
|
163
|
+
* configuration becomes its JSON Schema subset; `andesine extensions build` validates the rest.
|
|
164
|
+
*/
|
|
165
|
+
const defineExtension = (definition) => {
|
|
166
|
+
const { configuration, ...manifest } = definition;
|
|
167
|
+
if (!configuration) return manifest;
|
|
168
|
+
return {
|
|
169
|
+
...manifest,
|
|
170
|
+
configuration: isZodObject(configuration) ? toExtensionConfiguration(configuration) : configuration
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
const defineBuild = (options) => options;
|
|
174
|
+
//#endregion
|
|
175
|
+
export { defineBuild, defineExtension };
|
|
176
|
+
|
|
177
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../private/contracts/src/primitives/unique.ts","../../../private/contracts/src/extensions/configuration.ts","../src/index.ts"],"sourcesContent":["/** Whether every value is distinct. */\nconst uniqueItems = <T>(values: T[]): boolean => new Set(values).size === values.length;\n\nexport { uniqueItems };\n","import * as z from \"zod\";\nimport { uniqueItems } from \"../primitives/unique\";\n\ntype ExtensionConfiguration = z.output<typeof extensionConfigurationType>;\ntype ExtensionConfigurationField = ExtensionConfiguration[\"properties\"][string];\ntype ExtensionConfigurationItem = Exclude<ExtensionConfigurationField, { type: \"array\" }>;\ntype ExtensionConfigurationPrimitive = string | number | boolean;\ntype ExtensionConfigurationValue =\n ExtensionConfigurationPrimitive | ExtensionConfigurationPrimitive[];\n\nconst JSON_SCHEMA_DIALECT = \"https://json-schema.org/draft/2020-12/schema\";\nconst MAX_CONFIGURATION_FIELDS = 50;\nconst MAX_CONFIGURATION_STRING_LENGTH = 10_000;\nconst MAX_CONFIGURATION_ARRAY_ITEMS = 100;\nconst MAX_CONFIGURATION_ENUM_VALUES = 100;\nconst MAX_CONFIGURATION_PATTERN_LENGTH = 500;\nconst isValidPattern = (pattern: string): boolean => {\n try {\n new RegExp(pattern);\n\n return true;\n } catch {\n return false;\n }\n};\nconst fieldKeyType = z\n .string()\n .regex(/^[A-Za-z][A-Za-z0-9_]{0,63}$/, \"Use a letter, then letters, digits, or underscores\");\nconst lengthType = z.int().min(0).max(MAX_CONFIGURATION_STRING_LENGTH);\nconst metadata = {\n title: z.string().min(1).max(100).optional(),\n description: z.string().min(1).max(500).optional()\n};\nconst stringItemType = z.strictObject({\n type: z.literal(\"string\"),\n minLength: lengthType.optional(),\n maxLength: lengthType.optional(),\n pattern: z\n .string()\n .min(1)\n .max(MAX_CONFIGURATION_PATTERN_LENGTH)\n .refine(isValidPattern, \"Use a valid regular expression\")\n .optional(),\n format: z.enum([\"uri\", \"email\"]).optional()\n});\nconst enumItemType = z.strictObject({\n type: z.literal(\"string\"),\n enum: z\n .array(z.string().min(1).max(200))\n .min(1)\n .max(MAX_CONFIGURATION_ENUM_VALUES)\n .refine(uniqueItems, \"Options must be unique\")\n});\nconst numberItemType = z.strictObject({\n type: z.enum([\"number\", \"integer\"]),\n minimum: z.number().optional(),\n maximum: z.number().optional(),\n exclusiveMinimum: z.number().optional(),\n exclusiveMaximum: z.number().optional(),\n multipleOf: z.number().positive().optional()\n});\nconst booleanItemType = z.strictObject({ type: z.literal(\"boolean\") });\nconst stringFormatTypes = { uri: z.url(), email: z.email() };\nconst itemType = z.union([stringItemType, enumItemType, numberItemType, booleanItemType]);\nconst fieldType = z.union([\n stringItemType.extend({\n ...metadata,\n default: z.string().optional(),\n // Secret values are write-only and readable only by the extension backend.\n secret: z.literal(true).optional(),\n control: z.literal(\"textarea\").optional()\n }),\n enumItemType.extend({ ...metadata, default: z.string().optional() }),\n numberItemType.extend({ ...metadata, default: z.number().optional() }),\n booleanItemType.extend({ ...metadata, default: z.boolean().optional() }),\n z.strictObject({\n ...metadata,\n type: z.literal(\"array\"),\n items: itemType,\n minItems: z.int().min(0).max(MAX_CONFIGURATION_ARRAY_ITEMS).optional(),\n maxItems: z.int().min(0).max(MAX_CONFIGURATION_ARRAY_ITEMS).optional(),\n uniqueItems: z.boolean().optional(),\n default: z.array(z.union([z.string(), z.number(), z.boolean()])).optional()\n })\n]);\n/** Flat JSON Schema (draft 2020-12) subset that the host validates without running extension code. */\nconst extensionConfigurationType = z\n .strictObject({\n $schema: z.literal(JSON_SCHEMA_DIALECT).optional(),\n type: z.literal(\"object\"),\n properties: z\n .record(fieldKeyType, fieldType)\n .refine(\n (properties) => Object.keys(properties).length <= MAX_CONFIGURATION_FIELDS,\n `Use at most ${MAX_CONFIGURATION_FIELDS} fields`\n ),\n required: z.array(fieldKeyType).refine(uniqueItems, \"Fields must be unique\").optional(),\n additionalProperties: z.literal(false)\n })\n .superRefine((configuration, context) => {\n for (const [index, key] of (configuration.required || []).entries()) {\n if (!configuration.properties[key]) {\n context.addIssue({ code: \"custom\", path: [\"required\", index], message: \"Unknown field\" });\n }\n }\n\n for (const [key, field] of Object.entries(configuration.properties)) {\n const isSecretWithDefault = \"secret\" in field && field.secret && field.default !== undefined;\n\n if (isSecretWithDefault) {\n context.addIssue({\n code: \"custom\",\n path: [\"properties\", key, \"default\"],\n message: \"Secret fields cannot have defaults\"\n });\n } else if (field.default !== undefined) {\n const result = createConfigurationValueType(field).safeParse(field.default);\n\n if (!result.success) {\n context.addIssue({\n code: \"custom\",\n path: [\"properties\", key, \"default\"],\n message: \"The default does not match the field\"\n });\n }\n }\n }\n });\nconst createConfigurationValueType = (\n field: ExtensionConfigurationField | ExtensionConfigurationItem\n): z.ZodType<ExtensionConfigurationValue> => {\n if (field.type === \"boolean\") return z.boolean();\n\n if (field.type === \"array\") {\n const maxItems = Math.min(\n field.maxItems ?? MAX_CONFIGURATION_ARRAY_ITEMS,\n MAX_CONFIGURATION_ARRAY_ITEMS\n );\n const items = z\n .array(\n createConfigurationValueType(field.items) as z.ZodType<ExtensionConfigurationPrimitive>\n )\n .min(field.minItems ?? 0)\n .max(maxItems);\n\n return field.uniqueItems ? items.refine(uniqueItems, \"Values must be unique\") : items;\n }\n\n if (\"enum\" in field) return z.enum(field.enum as [string, ...string[]]);\n\n if (field.type === \"string\") {\n const maxLength = Math.min(\n field.maxLength ?? MAX_CONFIGURATION_STRING_LENGTH,\n MAX_CONFIGURATION_STRING_LENGTH\n );\n const value = (field.format ? stringFormatTypes[field.format] : z.string())\n .min(field.minLength ?? 0)\n .max(maxLength);\n\n return field.pattern ? value.regex(new RegExp(field.pattern)) : value;\n }\n\n let value = field.type === \"integer\" ? z.int() : z.number();\n\n if (field.minimum !== undefined) value = value.gte(field.minimum);\n\n if (field.maximum !== undefined) value = value.lte(field.maximum);\n\n if (field.exclusiveMinimum !== undefined) value = value.gt(field.exclusiveMinimum);\n\n if (field.exclusiveMaximum !== undefined) value = value.lt(field.exclusiveMaximum);\n\n if (field.multipleOf !== undefined) value = value.multipleOf(field.multipleOf);\n\n return value;\n};\n/** Validates stored values: known fields only; missing required fields are a state, not an error. */\nconst createConfigurationValuesType = (\n configuration: ExtensionConfiguration\n): z.ZodType<Record<string, ExtensionConfigurationValue>> => {\n const shape = Object.fromEntries(\n Object.entries(configuration.properties).map(([key, field]) => {\n return [key, createConfigurationValueType(field).optional()];\n })\n );\n\n return z.strictObject(shape) as z.ZodType<Record<string, ExtensionConfigurationValue>>;\n};\n/** Converts an authoring Zod object to the configuration subset; unsupported constructs throw. */\nconst toExtensionConfiguration = (schema: z.ZodObject): ExtensionConfiguration => {\n const jsonSchema = z.toJSONSchema(schema, {\n io: \"input\",\n target: \"draft-2020-12\",\n unrepresentable: \"throw\"\n });\n const result = extensionConfigurationType.safeParse({\n additionalProperties: false,\n ...jsonSchema\n });\n\n if (!result.success) {\n throw new Error(`Unsupported configuration schema:\\n${z.prettifyError(result.error)}`);\n }\n\n return result.data;\n};\n\nexport {\n MAX_CONFIGURATION_FIELDS,\n extensionConfigurationType,\n createConfigurationValueType,\n createConfigurationValuesType,\n toExtensionConfiguration\n};\nexport type { ExtensionConfiguration, ExtensionConfigurationField, ExtensionConfigurationValue };\n","import { type extensionManifestType } from \"@andesine/contracts/extensions\";\n// The configuration module alone, so the bundle carries no other contracts code.\nimport {\n type ExtensionConfiguration,\n toExtensionConfiguration\n} from \"@andesine/contracts/extensions/configuration\";\nimport type * as z from \"zod\";\n\ninterface ExtensionDefinition extends Omit<ExtensionManifestInput, \"configuration\"> {\n /** A Zod object of flat fields, or the configuration schema it converts to. */\n configuration?: z.ZodObject | ExtensionConfiguration;\n}\n/** Options of `andesine extensions build`, exported from the config file as `build`. */\ninterface ExtensionBuildOptions {\n /** The frontend module that exports the manifest's view entries (default: `src/frontend/index.tsx`). */\n frontend?: string;\n /** Custom icon sets: a set prefix and a directory of SVG files, e.g. `{ acme: \"./icons\" }`. */\n icons?: Record<string, string>;\n}\n\ntype ExtensionManifestInput = z.input<typeof extensionManifestType>;\n\nconst isZodObject = (value: object): value is z.ZodObject => \"_zod\" in value;\n/**\n * Defines the extension manifest, the default export of `andesine.config.ts`. A Zod\n * configuration becomes its JSON Schema subset; `andesine extensions build` validates the rest.\n */\nconst defineExtension = (definition: ExtensionDefinition): ExtensionManifestInput => {\n const { configuration, ...manifest } = definition;\n\n if (!configuration) return manifest;\n\n return {\n ...manifest,\n configuration: isZodObject(configuration)\n ? toExtensionConfiguration(configuration)\n : configuration\n };\n};\nconst defineBuild = (options: ExtensionBuildOptions): ExtensionBuildOptions => options;\n\nexport { defineExtension, defineBuild };\nexport type { ExtensionDefinition, ExtensionBuildOptions, ExtensionManifestInput };\n"],"mappings":"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"}
|