@setzkasten-cms/core 0.4.2
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 +37 -0
- package/dist/chunk-IL2PWN4R.js +142 -0
- package/dist/define-config-bJ65Zaui.d.ts +209 -0
- package/dist/index.d.ts +261 -0
- package/dist/index.js +314 -0
- package/dist/testing.d.ts +28 -0
- package/dist/testing.js +69 -0
- package/package.json +32 -0
- package/src/commands/command.test.ts +85 -0
- package/src/commands/command.ts +65 -0
- package/src/errors/errors.ts +112 -0
- package/src/events/content-event-bus.test.ts +59 -0
- package/src/events/content-event-bus.ts +55 -0
- package/src/fields/factories.test.ts +168 -0
- package/src/fields/factories.ts +166 -0
- package/src/fields/field-definition.ts +215 -0
- package/src/index.ts +85 -0
- package/src/ports/asset-store.ts +36 -0
- package/src/ports/auth-provider.ts +32 -0
- package/src/ports/content-repository.ts +59 -0
- package/src/schema/define-config.ts +96 -0
- package/src/serialization/json-serializer.ts +87 -0
- package/src/serialization/serializer.ts +65 -0
- package/src/testing/index.ts +72 -0
- package/src/validation/schema-to-zod.test.ts +133 -0
- package/src/validation/schema-to-zod.ts +126 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Setzkasten Community License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lilapixel
|
|
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 use,
|
|
7
|
+
copy, modify, merge, publish, and distribute the Software, subject to the
|
|
8
|
+
following conditions:
|
|
9
|
+
|
|
10
|
+
1. The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
2. The Software may not be used for commercial purposes without a separate
|
|
14
|
+
commercial license from the copyright holder. "Commercial purposes" means
|
|
15
|
+
any use of the Software that is primarily intended for or directed toward
|
|
16
|
+
commercial advantage or monetary compensation. This includes, but is not
|
|
17
|
+
limited to:
|
|
18
|
+
- Using the Software to manage content for a commercial website or product
|
|
19
|
+
- Offering the Software as part of a paid service
|
|
20
|
+
- Using the Software within a for-profit organization
|
|
21
|
+
|
|
22
|
+
3. Non-commercial use is permitted without restriction. This includes:
|
|
23
|
+
- Personal projects
|
|
24
|
+
- Open source projects
|
|
25
|
+
- Educational and academic use
|
|
26
|
+
- Non-profit organizations
|
|
27
|
+
|
|
28
|
+
4. A commercial license ("Enterprise License") may be obtained by contacting
|
|
29
|
+
Lilapixel at hello@lilapixel.de.
|
|
30
|
+
|
|
31
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
32
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
33
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
34
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
35
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
36
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
37
|
+
SOFTWARE.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// src/schema/define-config.ts
|
|
2
|
+
function defineSection(options) {
|
|
3
|
+
return Object.freeze({
|
|
4
|
+
label: options.label,
|
|
5
|
+
icon: options.icon,
|
|
6
|
+
fields: options.fields
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
function defineCollection(options) {
|
|
10
|
+
return Object.freeze({
|
|
11
|
+
label: options.label,
|
|
12
|
+
path: options.path,
|
|
13
|
+
slugField: options.slugField,
|
|
14
|
+
fields: options.fields
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
function defineConfig(config) {
|
|
18
|
+
return Object.freeze(config);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/fields/factories.ts
|
|
22
|
+
var f = {
|
|
23
|
+
text(options) {
|
|
24
|
+
return Object.freeze({
|
|
25
|
+
type: "text",
|
|
26
|
+
label: options.label,
|
|
27
|
+
description: options.description,
|
|
28
|
+
defaultValue: options.defaultValue ?? "",
|
|
29
|
+
required: options.required ?? false,
|
|
30
|
+
multiline: options.multiline ?? false,
|
|
31
|
+
placeholder: options.placeholder,
|
|
32
|
+
maxLength: options.maxLength,
|
|
33
|
+
pattern: options.pattern,
|
|
34
|
+
formatting: options.formatting ?? false
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
number(options) {
|
|
38
|
+
return Object.freeze({
|
|
39
|
+
type: "number",
|
|
40
|
+
label: options.label,
|
|
41
|
+
description: options.description,
|
|
42
|
+
defaultValue: options.defaultValue ?? 0,
|
|
43
|
+
required: options.required ?? false,
|
|
44
|
+
min: options.min,
|
|
45
|
+
max: options.max,
|
|
46
|
+
step: options.step,
|
|
47
|
+
slider: options.slider ?? false
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
boolean(options) {
|
|
51
|
+
return Object.freeze({
|
|
52
|
+
type: "boolean",
|
|
53
|
+
label: options.label,
|
|
54
|
+
description: options.description,
|
|
55
|
+
defaultValue: options.defaultValue ?? false
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
select(options) {
|
|
59
|
+
return Object.freeze({
|
|
60
|
+
type: "select",
|
|
61
|
+
label: options.label,
|
|
62
|
+
description: options.description,
|
|
63
|
+
defaultValue: options.defaultValue ?? options.options[0]?.value ?? "",
|
|
64
|
+
required: options.required ?? false,
|
|
65
|
+
options: Object.freeze(options.options),
|
|
66
|
+
multi: options.multi ?? false
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
icon(options) {
|
|
70
|
+
return Object.freeze({
|
|
71
|
+
type: "icon",
|
|
72
|
+
label: options.label,
|
|
73
|
+
description: options.description,
|
|
74
|
+
defaultValue: options.defaultValue ?? "",
|
|
75
|
+
required: options.required ?? false
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
image(options) {
|
|
79
|
+
return Object.freeze({
|
|
80
|
+
type: "image",
|
|
81
|
+
label: options.label,
|
|
82
|
+
description: options.description,
|
|
83
|
+
required: options.required ?? false,
|
|
84
|
+
directory: options.directory,
|
|
85
|
+
accept: Object.freeze(options.accept ?? ["webp", "jpg", "png"]),
|
|
86
|
+
maxSize: options.maxSize,
|
|
87
|
+
filenameStrategy: options.filenameStrategy ?? "original"
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
array(itemField, options) {
|
|
91
|
+
return Object.freeze({
|
|
92
|
+
type: "array",
|
|
93
|
+
label: options.label,
|
|
94
|
+
description: options.description,
|
|
95
|
+
required: options.required ?? false,
|
|
96
|
+
itemField,
|
|
97
|
+
minItems: options.minItems,
|
|
98
|
+
maxItems: options.maxItems,
|
|
99
|
+
itemLabel: options.itemLabel
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
object(fields, options) {
|
|
103
|
+
return Object.freeze({
|
|
104
|
+
type: "object",
|
|
105
|
+
label: options.label,
|
|
106
|
+
description: options.description,
|
|
107
|
+
required: options.required ?? false,
|
|
108
|
+
fields,
|
|
109
|
+
collapsible: options.collapsible ?? false
|
|
110
|
+
});
|
|
111
|
+
},
|
|
112
|
+
color(options) {
|
|
113
|
+
return Object.freeze({
|
|
114
|
+
type: "color",
|
|
115
|
+
label: options.label,
|
|
116
|
+
description: options.description,
|
|
117
|
+
defaultValue: options.defaultValue ?? "#000000",
|
|
118
|
+
presets: Object.freeze(options.presets ?? [])
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
/**
|
|
122
|
+
* Override field – wraps a set of fields with an "active" checkbox.
|
|
123
|
+
* When inactive, the override fields are hidden and don't affect output.
|
|
124
|
+
* This replaces Keystatic's DOM-hack approach with a native schema concept.
|
|
125
|
+
*/
|
|
126
|
+
override(fields, options) {
|
|
127
|
+
return Object.freeze({
|
|
128
|
+
type: "override",
|
|
129
|
+
label: options.label,
|
|
130
|
+
description: options.description,
|
|
131
|
+
defaultValue: { active: false },
|
|
132
|
+
fields
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export {
|
|
138
|
+
defineSection,
|
|
139
|
+
defineCollection,
|
|
140
|
+
defineConfig,
|
|
141
|
+
f
|
|
142
|
+
};
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/** Discriminator for all field types */
|
|
2
|
+
type FieldType = 'text' | 'number' | 'boolean' | 'select' | 'icon' | 'image' | 'array' | 'object' | 'conditional' | 'color' | 'override';
|
|
3
|
+
/** Path to a field in a nested schema (e.g. ['items', 0, 'title']) */
|
|
4
|
+
type FieldPath = ReadonlyArray<string | number>;
|
|
5
|
+
/**
|
|
6
|
+
* Base field definition. The phantom type TValue carries the runtime value type
|
|
7
|
+
* through the type system without ever being assigned at runtime.
|
|
8
|
+
*/
|
|
9
|
+
interface FieldDefinition<TType extends FieldType = FieldType, TValue = unknown> {
|
|
10
|
+
readonly type: TType;
|
|
11
|
+
readonly label: string;
|
|
12
|
+
readonly description?: string;
|
|
13
|
+
readonly defaultValue?: TValue;
|
|
14
|
+
readonly required?: boolean;
|
|
15
|
+
readonly _phantom?: TValue;
|
|
16
|
+
}
|
|
17
|
+
interface TextFieldOptions {
|
|
18
|
+
label: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
defaultValue?: string;
|
|
21
|
+
required?: boolean;
|
|
22
|
+
multiline?: boolean;
|
|
23
|
+
placeholder?: string;
|
|
24
|
+
maxLength?: number;
|
|
25
|
+
pattern?: RegExp;
|
|
26
|
+
/** Enable inline formatting (bold, italic, strike, links). Output is HTML. */
|
|
27
|
+
formatting?: boolean;
|
|
28
|
+
}
|
|
29
|
+
interface TextFieldDef extends FieldDefinition<'text', string> {
|
|
30
|
+
readonly multiline: boolean;
|
|
31
|
+
readonly placeholder?: string;
|
|
32
|
+
readonly maxLength?: number;
|
|
33
|
+
readonly pattern?: RegExp;
|
|
34
|
+
readonly formatting: boolean;
|
|
35
|
+
}
|
|
36
|
+
interface NumberFieldOptions {
|
|
37
|
+
label: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
defaultValue?: number;
|
|
40
|
+
required?: boolean;
|
|
41
|
+
min?: number;
|
|
42
|
+
max?: number;
|
|
43
|
+
step?: number;
|
|
44
|
+
slider?: boolean;
|
|
45
|
+
}
|
|
46
|
+
interface NumberFieldDef extends FieldDefinition<'number', number> {
|
|
47
|
+
readonly min?: number;
|
|
48
|
+
readonly max?: number;
|
|
49
|
+
readonly step?: number;
|
|
50
|
+
readonly slider: boolean;
|
|
51
|
+
}
|
|
52
|
+
interface BooleanFieldOptions {
|
|
53
|
+
label: string;
|
|
54
|
+
description?: string;
|
|
55
|
+
defaultValue?: boolean;
|
|
56
|
+
}
|
|
57
|
+
interface BooleanFieldDef extends FieldDefinition<'boolean', boolean> {
|
|
58
|
+
}
|
|
59
|
+
interface SelectOption {
|
|
60
|
+
readonly label: string;
|
|
61
|
+
readonly value: string;
|
|
62
|
+
readonly icon?: string;
|
|
63
|
+
}
|
|
64
|
+
interface SelectFieldOptions {
|
|
65
|
+
label: string;
|
|
66
|
+
description?: string;
|
|
67
|
+
defaultValue?: string;
|
|
68
|
+
required?: boolean;
|
|
69
|
+
options: readonly SelectOption[];
|
|
70
|
+
multi?: boolean;
|
|
71
|
+
}
|
|
72
|
+
interface SelectFieldDef extends FieldDefinition<'select', string> {
|
|
73
|
+
readonly options: readonly SelectOption[];
|
|
74
|
+
readonly multi: boolean;
|
|
75
|
+
}
|
|
76
|
+
interface IconFieldOptions {
|
|
77
|
+
label: string;
|
|
78
|
+
description?: string;
|
|
79
|
+
defaultValue?: string;
|
|
80
|
+
required?: boolean;
|
|
81
|
+
}
|
|
82
|
+
interface IconFieldDef extends FieldDefinition<'icon', string> {
|
|
83
|
+
}
|
|
84
|
+
interface ImageFieldOptions {
|
|
85
|
+
label: string;
|
|
86
|
+
description?: string;
|
|
87
|
+
required?: boolean;
|
|
88
|
+
directory: string;
|
|
89
|
+
accept?: readonly string[];
|
|
90
|
+
maxSize?: string;
|
|
91
|
+
filenameStrategy?: 'original' | 'slugified' | 'custom';
|
|
92
|
+
}
|
|
93
|
+
interface ImageValue {
|
|
94
|
+
readonly path: string;
|
|
95
|
+
readonly alt?: string;
|
|
96
|
+
}
|
|
97
|
+
interface ImageFieldDef extends FieldDefinition<'image', ImageValue> {
|
|
98
|
+
readonly directory: string;
|
|
99
|
+
readonly accept: readonly string[];
|
|
100
|
+
readonly maxSize?: string;
|
|
101
|
+
readonly filenameStrategy: 'original' | 'slugified' | 'custom';
|
|
102
|
+
}
|
|
103
|
+
interface ArrayFieldOptions {
|
|
104
|
+
label: string;
|
|
105
|
+
description?: string;
|
|
106
|
+
required?: boolean;
|
|
107
|
+
minItems?: number;
|
|
108
|
+
maxItems?: number;
|
|
109
|
+
itemLabel?: (item: unknown, index: number) => string;
|
|
110
|
+
}
|
|
111
|
+
interface ArrayFieldDef<TItem extends FieldDefinition = FieldDefinition> extends FieldDefinition<'array', InferFieldValue<TItem>[]> {
|
|
112
|
+
readonly itemField: TItem;
|
|
113
|
+
readonly minItems?: number;
|
|
114
|
+
readonly maxItems?: number;
|
|
115
|
+
readonly itemLabel?: (item: unknown, index: number) => string;
|
|
116
|
+
}
|
|
117
|
+
interface ObjectFieldOptions {
|
|
118
|
+
label: string;
|
|
119
|
+
description?: string;
|
|
120
|
+
required?: boolean;
|
|
121
|
+
collapsible?: boolean;
|
|
122
|
+
}
|
|
123
|
+
type FieldRecord = Record<string, FieldDefinition>;
|
|
124
|
+
interface ObjectFieldDef<TFields extends FieldRecord = FieldRecord> extends FieldDefinition<'object', InferSchemaValues<TFields>> {
|
|
125
|
+
readonly fields: TFields;
|
|
126
|
+
readonly collapsible: boolean;
|
|
127
|
+
}
|
|
128
|
+
interface ColorFieldOptions {
|
|
129
|
+
label: string;
|
|
130
|
+
description?: string;
|
|
131
|
+
defaultValue?: string;
|
|
132
|
+
presets?: readonly string[];
|
|
133
|
+
}
|
|
134
|
+
interface ColorFieldDef extends FieldDefinition<'color', string> {
|
|
135
|
+
readonly presets: readonly string[];
|
|
136
|
+
}
|
|
137
|
+
interface OverrideFieldOptions {
|
|
138
|
+
label: string;
|
|
139
|
+
description?: string;
|
|
140
|
+
}
|
|
141
|
+
interface OverrideFieldDef<TFields extends FieldRecord = FieldRecord> extends FieldDefinition<'override', {
|
|
142
|
+
active: boolean;
|
|
143
|
+
} & Partial<InferSchemaValues<TFields>>> {
|
|
144
|
+
readonly fields: TFields;
|
|
145
|
+
}
|
|
146
|
+
/** Extract the runtime value type from a FieldDefinition */
|
|
147
|
+
type InferFieldValue<F> = F extends FieldDefinition<infer _T, infer V> ? V : never;
|
|
148
|
+
/** Infer all values from a schema record */
|
|
149
|
+
type InferSchemaValues<S extends FieldRecord> = {
|
|
150
|
+
[K in keyof S]: InferFieldValue<S[K]>;
|
|
151
|
+
};
|
|
152
|
+
/** Union of all concrete field definitions */
|
|
153
|
+
type AnyFieldDef = TextFieldDef | NumberFieldDef | BooleanFieldDef | SelectFieldDef | IconFieldDef | ImageFieldDef | ArrayFieldDef | ObjectFieldDef | ColorFieldDef | OverrideFieldDef;
|
|
154
|
+
|
|
155
|
+
interface SectionDefinition<TFields extends FieldRecord = FieldRecord> {
|
|
156
|
+
readonly label: string;
|
|
157
|
+
readonly icon?: string;
|
|
158
|
+
readonly fields: TFields;
|
|
159
|
+
}
|
|
160
|
+
declare function defineSection<TFields extends FieldRecord>(options: {
|
|
161
|
+
label: string;
|
|
162
|
+
icon?: string;
|
|
163
|
+
fields: TFields;
|
|
164
|
+
}): SectionDefinition<TFields>;
|
|
165
|
+
interface ProductDefinition {
|
|
166
|
+
readonly label: string;
|
|
167
|
+
readonly sections: Record<string, SectionDefinition>;
|
|
168
|
+
}
|
|
169
|
+
interface CollectionDefinition<TFields extends FieldRecord = FieldRecord> {
|
|
170
|
+
readonly label: string;
|
|
171
|
+
readonly path: string;
|
|
172
|
+
readonly slugField: string;
|
|
173
|
+
readonly fields: TFields;
|
|
174
|
+
}
|
|
175
|
+
declare function defineCollection<TFields extends FieldRecord>(options: {
|
|
176
|
+
label: string;
|
|
177
|
+
path: string;
|
|
178
|
+
slugField: string;
|
|
179
|
+
fields: TFields;
|
|
180
|
+
}): CollectionDefinition<TFields>;
|
|
181
|
+
interface StorageConfig {
|
|
182
|
+
readonly kind: 'github' | 'local';
|
|
183
|
+
readonly repo?: string;
|
|
184
|
+
readonly branch?: string;
|
|
185
|
+
}
|
|
186
|
+
interface AuthConfig {
|
|
187
|
+
readonly providers: readonly ('github' | 'google' | 'email')[];
|
|
188
|
+
readonly allowedEmails?: readonly string[];
|
|
189
|
+
}
|
|
190
|
+
interface ThemeConfig {
|
|
191
|
+
readonly primaryColor: string;
|
|
192
|
+
readonly logo?: string;
|
|
193
|
+
readonly brandName?: string;
|
|
194
|
+
}
|
|
195
|
+
interface IconsConfig {
|
|
196
|
+
/** Icon sets to enable. Default: ['lucide'] */
|
|
197
|
+
readonly sets: readonly string[];
|
|
198
|
+
}
|
|
199
|
+
interface SetzKastenConfig {
|
|
200
|
+
readonly storage: StorageConfig;
|
|
201
|
+
readonly auth: AuthConfig;
|
|
202
|
+
readonly theme?: ThemeConfig;
|
|
203
|
+
readonly icons?: IconsConfig;
|
|
204
|
+
readonly products: Record<string, ProductDefinition>;
|
|
205
|
+
readonly collections?: Record<string, CollectionDefinition>;
|
|
206
|
+
}
|
|
207
|
+
declare function defineConfig(config: SetzKastenConfig): SetzKastenConfig;
|
|
208
|
+
|
|
209
|
+
export { type ArrayFieldOptions as A, type BooleanFieldOptions as B, type ColorFieldOptions as C, type ThemeConfig as D, defineCollection as E, type FieldDefinition as F, defineConfig as G, defineSection as H, type IconFieldOptions as I, type NumberFieldOptions as N, type ObjectFieldOptions as O, type ProductDefinition as P, type SelectFieldOptions as S, type TextFieldOptions as T, type TextFieldDef as a, type NumberFieldDef as b, type BooleanFieldDef as c, type SelectFieldDef as d, type IconFieldDef as e, type ImageFieldOptions as f, type ImageFieldDef as g, type ArrayFieldDef as h, type FieldRecord as i, type ObjectFieldDef as j, type ColorFieldDef as k, type OverrideFieldOptions as l, type OverrideFieldDef as m, type FieldPath as n, type AnyFieldDef as o, type AuthConfig as p, type CollectionDefinition as q, type FieldType as r, type IconsConfig as s, type ImageValue as t, type InferFieldValue as u, type InferSchemaValues as v, type SectionDefinition as w, type SelectOption as x, type SetzKastenConfig as y, type StorageConfig as z };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { T as TextFieldOptions, a as TextFieldDef, N as NumberFieldOptions, b as NumberFieldDef, B as BooleanFieldOptions, c as BooleanFieldDef, S as SelectFieldOptions, d as SelectFieldDef, I as IconFieldOptions, e as IconFieldDef, f as ImageFieldOptions, g as ImageFieldDef, F as FieldDefinition, A as ArrayFieldOptions, h as ArrayFieldDef, i as FieldRecord, O as ObjectFieldOptions, j as ObjectFieldDef, C as ColorFieldOptions, k as ColorFieldDef, l as OverrideFieldOptions, m as OverrideFieldDef, n as FieldPath } from './define-config-bJ65Zaui.js';
|
|
2
|
+
export { o as AnyFieldDef, p as AuthConfig, q as CollectionDefinition, r as FieldType, s as IconsConfig, t as ImageValue, u as InferFieldValue, v as InferSchemaValues, P as ProductDefinition, w as SectionDefinition, x as SelectOption, y as SetzKastenConfig, z as StorageConfig, D as ThemeConfig, E as defineCollection, G as defineConfig, H as defineSection } from './define-config-bJ65Zaui.js';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Field factories – the public API for defining content schemas.
|
|
7
|
+
*
|
|
8
|
+
* Each factory returns a frozen FieldDefinition object with a discriminated
|
|
9
|
+
* `type` property. The phantom type parameter carries the runtime value type
|
|
10
|
+
* through the type system for end-to-end inference.
|
|
11
|
+
*/
|
|
12
|
+
declare const f: {
|
|
13
|
+
readonly text: (options: TextFieldOptions) => TextFieldDef;
|
|
14
|
+
readonly number: (options: NumberFieldOptions) => NumberFieldDef;
|
|
15
|
+
readonly boolean: (options: BooleanFieldOptions) => BooleanFieldDef;
|
|
16
|
+
readonly select: (options: SelectFieldOptions) => SelectFieldDef;
|
|
17
|
+
readonly icon: (options: IconFieldOptions) => IconFieldDef;
|
|
18
|
+
readonly image: (options: ImageFieldOptions) => ImageFieldDef;
|
|
19
|
+
readonly array: <TItem extends FieldDefinition>(itemField: TItem, options: ArrayFieldOptions) => ArrayFieldDef<TItem>;
|
|
20
|
+
readonly object: <TFields extends FieldRecord>(fields: TFields, options: ObjectFieldOptions) => ObjectFieldDef<TFields>;
|
|
21
|
+
readonly color: (options: ColorFieldOptions) => ColorFieldDef;
|
|
22
|
+
/**
|
|
23
|
+
* Override field – wraps a set of fields with an "active" checkbox.
|
|
24
|
+
* When inactive, the override fields are hidden and don't affect output.
|
|
25
|
+
* This replaces Keystatic's DOM-hack approach with a native schema concept.
|
|
26
|
+
*/
|
|
27
|
+
readonly override: <TFields extends FieldRecord>(fields: TFields, options: OverrideFieldOptions) => OverrideFieldDef<TFields>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Generates a Zod schema from a FieldDefinition.
|
|
32
|
+
* This is the single source of truth for validation – Zod schemas are derived
|
|
33
|
+
* from field definitions, never written by hand.
|
|
34
|
+
*/
|
|
35
|
+
declare function fieldToZod(field: FieldDefinition): z.ZodType;
|
|
36
|
+
/**
|
|
37
|
+
* Convert an entire field record (schema) to a Zod object schema.
|
|
38
|
+
*/
|
|
39
|
+
declare function schemaToZod(fields: FieldRecord): z.ZodObject<Record<string, z.ZodType>>;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Strategy interface for field serialization.
|
|
43
|
+
* Each field type defines how it converts between editor values and stored format.
|
|
44
|
+
*/
|
|
45
|
+
interface FieldSerializer<TValue = unknown, TSerialized = unknown> {
|
|
46
|
+
serialize(value: TValue): TSerialized;
|
|
47
|
+
deserialize(raw: TSerialized): TValue;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Registry of serializers keyed by field type.
|
|
51
|
+
*/
|
|
52
|
+
type SerializerRegistry = Record<string, FieldSerializer>;
|
|
53
|
+
/**
|
|
54
|
+
* Serialize an entire schema's values to a flat JSON object.
|
|
55
|
+
*/
|
|
56
|
+
declare function serializeEntry(fields: FieldRecord, values: Record<string, unknown>, registry: SerializerRegistry): Record<string, unknown>;
|
|
57
|
+
/**
|
|
58
|
+
* Deserialize a stored JSON object back to editor values.
|
|
59
|
+
*/
|
|
60
|
+
declare function deserializeEntry(fields: FieldRecord, raw: Record<string, unknown>, registry: SerializerRegistry): Record<string, unknown>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Default JSON serializer registry.
|
|
64
|
+
* Compatible with Keystatic's JSON output format.
|
|
65
|
+
*/
|
|
66
|
+
declare const jsonSerializerRegistry: SerializerRegistry;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Command pattern – all mutations go through commands.
|
|
70
|
+
* This gives undo/redo for free.
|
|
71
|
+
*/
|
|
72
|
+
interface Command {
|
|
73
|
+
execute(): void;
|
|
74
|
+
undo(): void;
|
|
75
|
+
describe(): string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Command history – manages undo/redo stacks.
|
|
79
|
+
*/
|
|
80
|
+
interface CommandHistory {
|
|
81
|
+
execute(command: Command): void;
|
|
82
|
+
undo(): void;
|
|
83
|
+
redo(): void;
|
|
84
|
+
canUndo(): boolean;
|
|
85
|
+
canRedo(): boolean;
|
|
86
|
+
clear(): void;
|
|
87
|
+
}
|
|
88
|
+
declare function createCommandHistory(): CommandHistory;
|
|
89
|
+
|
|
90
|
+
type ContentEvent = {
|
|
91
|
+
readonly type: 'field-changed';
|
|
92
|
+
readonly path: FieldPath;
|
|
93
|
+
readonly value: unknown;
|
|
94
|
+
} | {
|
|
95
|
+
readonly type: 'entry-saved';
|
|
96
|
+
readonly collection: string;
|
|
97
|
+
readonly slug: string;
|
|
98
|
+
} | {
|
|
99
|
+
readonly type: 'entry-deleted';
|
|
100
|
+
readonly collection: string;
|
|
101
|
+
readonly slug: string;
|
|
102
|
+
} | {
|
|
103
|
+
readonly type: 'entry-loaded';
|
|
104
|
+
readonly collection: string;
|
|
105
|
+
readonly slug: string;
|
|
106
|
+
};
|
|
107
|
+
type ContentEventType = ContentEvent['type'];
|
|
108
|
+
type Unsubscribe = () => void;
|
|
109
|
+
/**
|
|
110
|
+
* Observer pattern – decoupled communication between form state and preview engine.
|
|
111
|
+
*/
|
|
112
|
+
interface ContentEventBus {
|
|
113
|
+
emit(event: ContentEvent): void;
|
|
114
|
+
on<T extends ContentEventType>(type: T, handler: (event: Extract<ContentEvent, {
|
|
115
|
+
type: T;
|
|
116
|
+
}>) => void): Unsubscribe;
|
|
117
|
+
}
|
|
118
|
+
declare function createEventBus(): ContentEventBus;
|
|
119
|
+
|
|
120
|
+
type SetzKastenError = ValidationError | ConflictError | RateLimitError | AuthError | NetworkError | NotFoundError | SerializationError;
|
|
121
|
+
interface BaseError {
|
|
122
|
+
readonly message: string;
|
|
123
|
+
readonly cause?: unknown;
|
|
124
|
+
readonly timestamp: number;
|
|
125
|
+
}
|
|
126
|
+
interface ValidationError extends BaseError {
|
|
127
|
+
readonly type: 'validation';
|
|
128
|
+
readonly fieldPath: FieldPath;
|
|
129
|
+
readonly rule: string;
|
|
130
|
+
}
|
|
131
|
+
interface ConflictError extends BaseError {
|
|
132
|
+
readonly type: 'conflict';
|
|
133
|
+
}
|
|
134
|
+
interface RateLimitError extends BaseError {
|
|
135
|
+
readonly type: 'rate-limit';
|
|
136
|
+
readonly retryAfter: number;
|
|
137
|
+
readonly remaining: number;
|
|
138
|
+
}
|
|
139
|
+
interface AuthError extends BaseError {
|
|
140
|
+
readonly type: 'auth';
|
|
141
|
+
}
|
|
142
|
+
interface NetworkError extends BaseError {
|
|
143
|
+
readonly type: 'network';
|
|
144
|
+
}
|
|
145
|
+
interface NotFoundError extends BaseError {
|
|
146
|
+
readonly type: 'not-found';
|
|
147
|
+
readonly path: string;
|
|
148
|
+
}
|
|
149
|
+
interface SerializationError extends BaseError {
|
|
150
|
+
readonly type: 'serialization';
|
|
151
|
+
readonly path?: string;
|
|
152
|
+
}
|
|
153
|
+
type Result<T, E = SetzKastenError> = {
|
|
154
|
+
readonly ok: true;
|
|
155
|
+
readonly value: T;
|
|
156
|
+
} | {
|
|
157
|
+
readonly ok: false;
|
|
158
|
+
readonly error: E;
|
|
159
|
+
};
|
|
160
|
+
declare function ok<T>(value: T): Result<T, never>;
|
|
161
|
+
declare function err<E>(error: E): Result<never, E>;
|
|
162
|
+
declare function validationError(fieldPath: FieldPath, rule: string, message: string): ValidationError;
|
|
163
|
+
declare function conflictError(message: string): ConflictError;
|
|
164
|
+
declare function rateLimitError(retryAfter: number, remaining: number): RateLimitError;
|
|
165
|
+
declare function authError(message: string): AuthError;
|
|
166
|
+
declare function networkError(message: string, cause?: unknown): NetworkError;
|
|
167
|
+
declare function notFoundError(path: string): NotFoundError;
|
|
168
|
+
declare function serializationError(message: string, path?: string): SerializationError;
|
|
169
|
+
|
|
170
|
+
interface EntryData {
|
|
171
|
+
readonly content: Record<string, unknown>;
|
|
172
|
+
readonly sha?: string;
|
|
173
|
+
}
|
|
174
|
+
interface Asset {
|
|
175
|
+
readonly path: string;
|
|
176
|
+
readonly content: Uint8Array;
|
|
177
|
+
readonly mimeType: string;
|
|
178
|
+
}
|
|
179
|
+
interface CommitResult {
|
|
180
|
+
readonly sha: string;
|
|
181
|
+
readonly message: string;
|
|
182
|
+
readonly url?: string;
|
|
183
|
+
}
|
|
184
|
+
interface TreeNode {
|
|
185
|
+
readonly path: string;
|
|
186
|
+
readonly type: 'file' | 'dir';
|
|
187
|
+
readonly sha?: string;
|
|
188
|
+
}
|
|
189
|
+
interface EntryListItem {
|
|
190
|
+
readonly slug: string;
|
|
191
|
+
readonly name: string;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Repository pattern: abstracts content storage.
|
|
195
|
+
* Implemented by github-adapter (and potentially local-filesystem, GitLab, etc.)
|
|
196
|
+
*/
|
|
197
|
+
interface ContentRepository {
|
|
198
|
+
/** List all entries in a collection/singleton directory */
|
|
199
|
+
listEntries(collection: string): Promise<Result<EntryListItem[]>>;
|
|
200
|
+
/** Read a single entry's content */
|
|
201
|
+
getEntry(collection: string, slug: string): Promise<Result<EntryData>>;
|
|
202
|
+
/** Save an entry with optional assets (images). Creates a single atomic commit. */
|
|
203
|
+
saveEntry(collection: string, slug: string, data: EntryData, assets?: Asset[]): Promise<Result<CommitResult>>;
|
|
204
|
+
/** Delete an entry */
|
|
205
|
+
deleteEntry(collection: string, slug: string): Promise<Result<CommitResult>>;
|
|
206
|
+
/** Get the full repository tree (for navigation) */
|
|
207
|
+
getTree(ref?: string): Promise<Result<TreeNode[]>>;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
interface AuthUser {
|
|
211
|
+
readonly id: string;
|
|
212
|
+
readonly email: string;
|
|
213
|
+
readonly name?: string;
|
|
214
|
+
readonly avatarUrl?: string;
|
|
215
|
+
readonly provider: 'github' | 'google' | 'email';
|
|
216
|
+
}
|
|
217
|
+
interface AuthSession {
|
|
218
|
+
readonly user: AuthUser;
|
|
219
|
+
readonly expiresAt: number;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Auth provider port – abstracts authentication.
|
|
223
|
+
* Determines WHO can access the CMS, separate from HOW content is committed.
|
|
224
|
+
*/
|
|
225
|
+
interface AuthProvider {
|
|
226
|
+
/** Get current session (null if not authenticated) */
|
|
227
|
+
getSession(): Promise<Result<AuthSession | null>>;
|
|
228
|
+
/** Get the OAuth redirect URL for login */
|
|
229
|
+
getLoginUrl(provider: 'github' | 'google' | 'email'): string;
|
|
230
|
+
/** Exchange auth callback for session */
|
|
231
|
+
handleCallback(code: string, provider: string): Promise<Result<AuthSession>>;
|
|
232
|
+
/** End session */
|
|
233
|
+
logout(): Promise<void>;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
interface AssetMetadata {
|
|
237
|
+
readonly path: string;
|
|
238
|
+
readonly size: number;
|
|
239
|
+
readonly mimeType: string;
|
|
240
|
+
readonly width?: number;
|
|
241
|
+
readonly height?: number;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Asset store port – abstracts image/file storage.
|
|
245
|
+
* Default implementation uses GitHub Contents API.
|
|
246
|
+
* Could be swapped for S3, Cloudinary, etc.
|
|
247
|
+
*/
|
|
248
|
+
interface AssetStore {
|
|
249
|
+
/** Upload an asset, preserving the original filename */
|
|
250
|
+
upload(directory: string, filename: string, content: Uint8Array, mimeType: string): Promise<Result<AssetMetadata>>;
|
|
251
|
+
/** Delete an asset */
|
|
252
|
+
delete(path: string): Promise<Result<void>>;
|
|
253
|
+
/** List assets in a directory */
|
|
254
|
+
list(directory: string): Promise<Result<AssetMetadata[]>>;
|
|
255
|
+
/** Get a public URL for an asset (for the deployed website) */
|
|
256
|
+
getUrl(path: string): string;
|
|
257
|
+
/** Get a preview URL that works immediately (e.g. raw GitHub URL) */
|
|
258
|
+
getPreviewUrl?(path: string): string;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export { ArrayFieldDef, type Asset, type AssetMetadata, type AssetStore, type AuthError, type AuthProvider, type AuthSession, type AuthUser, BooleanFieldDef, ColorFieldDef, type Command, type CommandHistory, type CommitResult, type ConflictError, type ContentEvent, type ContentEventBus, type ContentEventType, type ContentRepository, type EntryData, type EntryListItem, FieldDefinition, FieldPath, FieldRecord, type FieldSerializer, IconFieldDef, ImageFieldDef, type NetworkError, type NotFoundError, NumberFieldDef, ObjectFieldDef, OverrideFieldDef, type RateLimitError, type Result, SelectFieldDef, type SerializationError, type SerializerRegistry, type SetzKastenError, TextFieldDef, type TreeNode, type Unsubscribe, type ValidationError, authError, conflictError, createCommandHistory, createEventBus, deserializeEntry, err, f, fieldToZod, jsonSerializerRegistry, networkError, notFoundError, ok, rateLimitError, schemaToZod, serializationError, serializeEntry, validationError };
|