@airdraft/core 0.1.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/dist/adapters/GitHubAdapter.d.ts +69 -0
  3. package/dist/adapters/GitHubAdapter.d.ts.map +1 -0
  4. package/dist/adapters/GitHubAdapter.js +278 -0
  5. package/dist/adapters/GitHubAdapter.js.map +1 -0
  6. package/dist/adapters/LocalAdapter.d.ts +21 -0
  7. package/dist/adapters/LocalAdapter.d.ts.map +1 -0
  8. package/dist/adapters/LocalAdapter.js +109 -0
  9. package/dist/adapters/LocalAdapter.js.map +1 -0
  10. package/dist/config.d.ts +21 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +34 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/engine.d.ts +26 -0
  15. package/dist/engine.d.ts.map +1 -0
  16. package/dist/engine.js +379 -0
  17. package/dist/engine.js.map +1 -0
  18. package/dist/errors.d.ts +45 -0
  19. package/dist/errors.d.ts.map +1 -0
  20. package/dist/errors.js +80 -0
  21. package/dist/errors.js.map +1 -0
  22. package/dist/fields.d.ts +23 -0
  23. package/dist/fields.d.ts.map +1 -0
  24. package/dist/fields.js +181 -0
  25. package/dist/fields.js.map +1 -0
  26. package/dist/index.d.ts +10 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +11 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/publish.d.ts +20 -0
  31. package/dist/publish.d.ts.map +1 -0
  32. package/dist/publish.js +25 -0
  33. package/dist/publish.js.map +1 -0
  34. package/dist/slug.d.ts +21 -0
  35. package/dist/slug.d.ts.map +1 -0
  36. package/dist/slug.js +39 -0
  37. package/dist/slug.js.map +1 -0
  38. package/dist/types.d.ts +326 -0
  39. package/dist/types.d.ts.map +1 -0
  40. package/dist/types.js +3 -0
  41. package/dist/types.js.map +1 -0
  42. package/package.json +41 -0
package/dist/fields.js ADDED
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Validate a single field value against its FieldConfig.
3
+ * Returns an array of error messages (empty = valid).
4
+ */
5
+ export function validateField(fieldName, value, config) {
6
+ const errors = [];
7
+ if (config.required && (value === undefined || value === null || value === '')) {
8
+ errors.push({ field: fieldName, message: 'This field is required.' });
9
+ return errors;
10
+ }
11
+ if (value === undefined || value === null)
12
+ return errors;
13
+ switch (config.type) {
14
+ case 'string':
15
+ case 'text':
16
+ case 'image':
17
+ case 'relation': {
18
+ if (typeof value !== 'string') {
19
+ errors.push({ field: fieldName, message: `Expected a string.` });
20
+ break;
21
+ }
22
+ if (config.pattern) {
23
+ const re = new RegExp(config.pattern);
24
+ if (!re.test(value)) {
25
+ errors.push({ field: fieldName, message: `Does not match pattern "${config.pattern}".` });
26
+ }
27
+ }
28
+ break;
29
+ }
30
+ case 'rich-text': {
31
+ if (typeof value !== 'string') {
32
+ errors.push({ field: fieldName, message: `Expected a string (MDX body).` });
33
+ }
34
+ break;
35
+ }
36
+ case 'number': {
37
+ if (typeof value !== 'number') {
38
+ errors.push({ field: fieldName, message: `Expected a number.` });
39
+ break;
40
+ }
41
+ if (config.min !== undefined && value < config.min) {
42
+ errors.push({ field: fieldName, message: `Must be ≥ ${config.min}.` });
43
+ }
44
+ if (config.max !== undefined && value > config.max) {
45
+ errors.push({ field: fieldName, message: `Must be ≤ ${config.max}.` });
46
+ }
47
+ break;
48
+ }
49
+ case 'boolean': {
50
+ if (typeof value !== 'boolean') {
51
+ errors.push({ field: fieldName, message: `Expected a boolean.` });
52
+ }
53
+ break;
54
+ }
55
+ case 'date':
56
+ case 'datetime': {
57
+ if (typeof value !== 'string' || isNaN(Date.parse(value))) {
58
+ errors.push({ field: fieldName, message: `Expected a valid ISO date string.` });
59
+ }
60
+ break;
61
+ }
62
+ case 'select': {
63
+ if (typeof value !== 'string') {
64
+ errors.push({ field: fieldName, message: `Expected a string.` });
65
+ break;
66
+ }
67
+ if (config.options && !config.options.includes(value)) {
68
+ errors.push({ field: fieldName, message: `Must be one of: ${config.options.join(', ')}.` });
69
+ }
70
+ break;
71
+ }
72
+ case 'multiselect': {
73
+ if (!Array.isArray(value)) {
74
+ errors.push({ field: fieldName, message: `Expected an array.` });
75
+ break;
76
+ }
77
+ if (config.options) {
78
+ for (const item of value) {
79
+ if (typeof item !== 'string' || !config.options.includes(item)) {
80
+ errors.push({
81
+ field: fieldName,
82
+ message: `All items must be one of: ${config.options.join(', ')}.`,
83
+ });
84
+ break;
85
+ }
86
+ }
87
+ }
88
+ break;
89
+ }
90
+ case 'list': {
91
+ if (!Array.isArray(value)) {
92
+ errors.push({ field: fieldName, message: `Expected an array.` });
93
+ break;
94
+ }
95
+ if (config.min !== undefined && value.length < config.min) {
96
+ errors.push({ field: fieldName, message: `Must have at least ${config.min} items.` });
97
+ }
98
+ if (config.max !== undefined && value.length > config.max) {
99
+ errors.push({ field: fieldName, message: `Must have at most ${config.max} items.` });
100
+ }
101
+ break;
102
+ }
103
+ case 'relations': {
104
+ if (!Array.isArray(value)) {
105
+ errors.push({ field: fieldName, message: `Expected an array.` });
106
+ break;
107
+ }
108
+ for (const item of value) {
109
+ if (typeof item !== 'string' || item.trim() === '') {
110
+ errors.push({ field: fieldName, message: `Each item must be a non-empty string.` });
111
+ break;
112
+ }
113
+ }
114
+ if (config.min !== undefined && value.length < config.min) {
115
+ errors.push({ field: fieldName, message: `Must have at least ${config.min} items.` });
116
+ }
117
+ if (config.max !== undefined && value.length > config.max) {
118
+ errors.push({ field: fieldName, message: `Must have at most ${config.max} items.` });
119
+ }
120
+ break;
121
+ }
122
+ case 'object': {
123
+ if (typeof value !== 'object' || Array.isArray(value)) {
124
+ errors.push({ field: fieldName, message: `Expected an object.` });
125
+ break;
126
+ }
127
+ if (config.fields) {
128
+ const nested = value;
129
+ for (const [subName, subConfig] of Object.entries(config.fields)) {
130
+ const subErrors = validateField(`${fieldName}.${subName}`, nested[subName], subConfig);
131
+ errors.push(...subErrors);
132
+ }
133
+ }
134
+ break;
135
+ }
136
+ }
137
+ return errors;
138
+ }
139
+ /**
140
+ * Validate all fields in an entry payload against a collection's field schema.
141
+ * Returns all collected errors.
142
+ */
143
+ export function validateFields(data, fields) {
144
+ const errors = [];
145
+ for (const [name, config] of Object.entries(fields)) {
146
+ errors.push(...validateField(name, data[name], config));
147
+ }
148
+ return errors;
149
+ }
150
+ /**
151
+ * Incompatible field/format checks.
152
+ * Returns an error message if the collection config has an inconsistency, otherwise null.
153
+ */
154
+ export function checkFormatCompatibility(format, fields) {
155
+ if (format !== 'mdx') {
156
+ for (const [name, config] of Object.entries(fields)) {
157
+ if (config.type === 'rich-text') {
158
+ return `Field "${name}" uses type "rich-text" which is only compatible with format "mdx".`;
159
+ }
160
+ }
161
+ }
162
+ return null;
163
+ }
164
+ /** Build a Zod-compatible FieldType enum for schema export. */
165
+ export const FIELD_TYPES = [
166
+ 'string',
167
+ 'text',
168
+ 'number',
169
+ 'boolean',
170
+ 'date',
171
+ 'datetime',
172
+ 'list',
173
+ 'select',
174
+ 'multiselect',
175
+ 'rich-text',
176
+ 'relation',
177
+ 'relations',
178
+ 'object',
179
+ 'image',
180
+ ];
181
+ //# sourceMappingURL=fields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fields.js","sourceRoot":"","sources":["../src/fields.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export * from './types.js';
2
+ export * from './config.js';
3
+ export * from './engine.js';
4
+ export * from './fields.js';
5
+ export * from './slug.js';
6
+ export * from './publish.js';
7
+ export * from './errors.js';
8
+ export * from './adapters/LocalAdapter.js';
9
+ export * from './adapters/GitHubAdapter.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,4BAA4B,CAAA;AAC1C,cAAc,6BAA6B,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ // @airdraft/core — public surface
2
+ export * from './types.js';
3
+ export * from './config.js';
4
+ export * from './engine.js';
5
+ export * from './fields.js';
6
+ export * from './slug.js';
7
+ export * from './publish.js';
8
+ export * from './errors.js';
9
+ export * from './adapters/LocalAdapter.js';
10
+ export * from './adapters/GitHubAdapter.js';
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAClC,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,4BAA4B,CAAA;AAC1C,cAAc,6BAA6B,CAAA"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Liveness rule for publish-workflow collections:
3
+ *
4
+ * An entry is LIVE iff:
5
+ * published === true
6
+ * AND (publishedAt === null OR publishedAt <= now)
7
+ *
8
+ * Liveness is evaluated at read/filter time.
9
+ * No background state mutation occurs.
10
+ */
11
+ export declare function isLive(published: boolean, publishedAt: string | null | undefined): boolean;
12
+ /**
13
+ * Build the initial publish fields for a newly created entry.
14
+ * New entries always start as drafts.
15
+ */
16
+ export declare function initialPublishFields(): {
17
+ published: boolean;
18
+ publishedAt: null;
19
+ };
20
+ //# sourceMappingURL=publish.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../src/publish.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CAAC,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAI1F;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,IAAI,CAAA;CAAE,CAEhF"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Liveness rule for publish-workflow collections:
3
+ *
4
+ * An entry is LIVE iff:
5
+ * published === true
6
+ * AND (publishedAt === null OR publishedAt <= now)
7
+ *
8
+ * Liveness is evaluated at read/filter time.
9
+ * No background state mutation occurs.
10
+ */
11
+ export function isLive(published, publishedAt) {
12
+ if (!published)
13
+ return false;
14
+ if (publishedAt == null)
15
+ return true;
16
+ return new Date(publishedAt).getTime() <= Date.now();
17
+ }
18
+ /**
19
+ * Build the initial publish fields for a newly created entry.
20
+ * New entries always start as drafts.
21
+ */
22
+ export function initialPublishFields() {
23
+ return { published: false, publishedAt: null };
24
+ }
25
+ //# sourceMappingURL=publish.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish.js","sourceRoot":"","sources":["../src/publish.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,UAAU,MAAM,CAAC,SAAkB,EAAE,WAAsC;IAC/E,IAAI,CAAC,SAAS;QAAE,OAAO,KAAK,CAAA;IAC5B,IAAI,WAAW,IAAI,IAAI;QAAE,OAAO,IAAI,CAAA;IACpC,OAAO,IAAI,IAAI,CAAC,WAAW,CAAC,CAAC,OAAO,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,CAAA;AACtD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB;IAClC,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,CAAA;AAChD,CAAC"}
package/dist/slug.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ /** Slug regex: lowercase alphanumeric, hyphen-separated. */
2
+ export declare const SLUG_REGEX: RegExp;
3
+ /**
4
+ * Validate a slug string against the Airdraft slug format.
5
+ * Returns the slug unchanged if valid, throws otherwise.
6
+ */
7
+ export declare function validateSlug(slug: string): string;
8
+ /**
9
+ * Convert an arbitrary string into a valid Airdraft slug.
10
+ * - Lowercases
11
+ * - Converts accented characters to ASCII equivalents
12
+ * - Replaces non-alphanumeric sequences with a single hyphen
13
+ * - Strips leading/trailing hyphens
14
+ */
15
+ export declare function slugify(input: string): string;
16
+ /**
17
+ * Derive a slug from an entry's data object, using the given source field.
18
+ * Throws if the resolved value is not a non-empty string.
19
+ */
20
+ export declare function deriveSlug(data: Record<string, unknown>, slugSource?: string): string;
21
+ //# sourceMappingURL=slug.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slug.d.ts","sourceRoot":"","sources":["../src/slug.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,eAAO,MAAM,UAAU,QAA+B,CAAA;AAEtD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOjD;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAO7C;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,UAAU,GAAE,MAAgB,GAC3B,MAAM,CAQR"}
package/dist/slug.js ADDED
@@ -0,0 +1,39 @@
1
+ /** Slug regex: lowercase alphanumeric, hyphen-separated. */
2
+ export const SLUG_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
3
+ /**
4
+ * Validate a slug string against the Airdraft slug format.
5
+ * Returns the slug unchanged if valid, throws otherwise.
6
+ */
7
+ export function validateSlug(slug) {
8
+ if (!SLUG_REGEX.test(slug)) {
9
+ throw new Error(`Invalid slug "${slug}". Slugs must match /^[a-z0-9]+(?:-[a-z0-9]+)*$/`);
10
+ }
11
+ return slug;
12
+ }
13
+ /**
14
+ * Convert an arbitrary string into a valid Airdraft slug.
15
+ * - Lowercases
16
+ * - Converts accented characters to ASCII equivalents
17
+ * - Replaces non-alphanumeric sequences with a single hyphen
18
+ * - Strips leading/trailing hyphens
19
+ */
20
+ export function slugify(input) {
21
+ return input
22
+ .normalize('NFD')
23
+ .replace(/[\u0300-\u036f]/g, '')
24
+ .toLowerCase()
25
+ .replace(/[^a-z0-9]+/g, '-')
26
+ .replace(/^-+|-+$/g, '');
27
+ }
28
+ /**
29
+ * Derive a slug from an entry's data object, using the given source field.
30
+ * Throws if the resolved value is not a non-empty string.
31
+ */
32
+ export function deriveSlug(data, slugSource = 'title') {
33
+ const raw = data[slugSource];
34
+ if (typeof raw !== 'string' || raw.trim() === '') {
35
+ throw new Error(`slugSource field "${slugSource}" must be a non-empty string to auto-generate a slug`);
36
+ }
37
+ return slugify(raw);
38
+ }
39
+ //# sourceMappingURL=slug.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slug.js","sourceRoot":"","sources":["../src/slug.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,MAAM,CAAC,MAAM,UAAU,GAAG,4BAA4B,CAAA;AAEtD;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,iBAAiB,IAAI,kDAAkD,CACxE,CAAA;IACH,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,OAAO,KAAK;SACT,SAAS,CAAC,KAAK,CAAC;SAChB,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC;SAC/B,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAA;AAC5B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CACxB,IAA6B,EAC7B,aAAqB,OAAO;IAE5B,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,CAAA;IAC5B,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACjD,MAAM,IAAI,KAAK,CACb,qBAAqB,UAAU,sDAAsD,CACtF,CAAA;IACH,CAAC;IACD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAA;AACrB,CAAC"}
@@ -0,0 +1,326 @@
1
+ /**
2
+ * Maps a FieldConfig to its runtime TypeScript primitive.
3
+ * Works best when CollectionConfig is defined inline with `as const`.
4
+ *
5
+ * @example
6
+ * const postsCollection = {
7
+ * fields: {
8
+ * title: { type: 'string', required: true },
9
+ * tags: { type: 'list' },
10
+ * }
11
+ * } satisfies CollectionConfig
12
+ *
13
+ * type PostData = InferCollectionData<typeof postsCollection>
14
+ * // => { title: string; tags?: string[] }
15
+ */
16
+ type FieldToPrimitive<F extends FieldConfig> = F['type'] extends 'string' | 'text' | 'rich-text' | 'select' | 'relation' | 'image' ? string : F['type'] extends 'number' ? number : F['type'] extends 'boolean' ? boolean : F['type'] extends 'date' | 'datetime' ? string : F['type'] extends 'list' | 'multiselect' | 'relations' ? string[] : F['type'] extends 'object' ? Record<string, unknown> : unknown;
17
+ /** Picks required fields and makes them non-optional. */
18
+ type RequiredFields<Fields extends Record<string, FieldConfig>> = {
19
+ [K in keyof Fields as Fields[K]['required'] extends true ? K : never]: FieldToPrimitive<Fields[K]>;
20
+ };
21
+ /** Picks optional (non-required) fields. */
22
+ type OptionalFields<Fields extends Record<string, FieldConfig>> = {
23
+ [K in keyof Fields as Fields[K]['required'] extends true ? never : K]?: FieldToPrimitive<Fields[K]>;
24
+ };
25
+ /**
26
+ * For every `image` field named `foo`, the server stores two extra fields:
27
+ * `foo_url` (the resolved CDN URL) and `foo_media` (the full MediaItem object).
28
+ */
29
+ type ImageAuxFields<Fields extends Record<string, FieldConfig>> = {
30
+ [K in keyof Fields as Fields[K]['type'] extends 'image' ? `${K & string}_url` : never]?: string;
31
+ } & {
32
+ [K in keyof Fields as Fields[K]['type'] extends 'image' ? `${K & string}_media` : never]?: MediaItem;
33
+ };
34
+ /** Publish-workflow fields injected when `collection.publish === true`. */
35
+ interface PublishFields {
36
+ published?: boolean;
37
+ publishedAt?: string | null;
38
+ }
39
+ /**
40
+ * Infer the `data` shape of entries for a given CollectionConfig.
41
+ * Requires the collection to be defined inline (not loaded from a JSON file).
42
+ */
43
+ export type InferCollectionData<C extends CollectionConfig> = RequiredFields<C['fields']> & OptionalFields<C['fields']> & ImageAuxFields<C['fields']> & PublishFields;
44
+ export type FieldType = 'string' | 'text' | 'number' | 'boolean' | 'date' | 'datetime' | 'list' | 'select' | 'multiselect' | 'rich-text' | 'relation' | 'relations' | 'object' | 'image';
45
+ export interface FieldConfig {
46
+ type: FieldType;
47
+ label?: string;
48
+ required?: boolean;
49
+ default?: unknown;
50
+ hidden?: boolean;
51
+ of?: FieldType;
52
+ options?: string[];
53
+ collection?: string;
54
+ min?: number;
55
+ max?: number;
56
+ pattern?: string;
57
+ fields?: Record<string, FieldConfig>;
58
+ }
59
+ export interface CollectionConfig {
60
+ /** File path template; {slug} is replaced with the entry slug. */
61
+ path: string;
62
+ label?: string;
63
+ fields: Record<string, FieldConfig>;
64
+ defaultSort?: {
65
+ field: string;
66
+ order: 'asc' | 'desc';
67
+ };
68
+ /**
69
+ * Source field for server-side slug generation when create payload omits `slug`.
70
+ * Must resolve to a top-level string field. Default: 'title'.
71
+ */
72
+ slugSource?: string;
73
+ previewUrl?: string;
74
+ format: 'mdx' | 'json' | 'yaml';
75
+ /**
76
+ * Enable draft/publish workflow. When true, injects reserved system fields:
77
+ * - `published: boolean`
78
+ * - `publishedAt: datetime | null`
79
+ */
80
+ publish?: boolean;
81
+ }
82
+ export type CollectionMap = Record<string, CollectionConfig>;
83
+ export interface EntryMeta {
84
+ /** GitHub blob SHA; required for optimistic concurrency on PUT/DELETE. */
85
+ sha: string;
86
+ }
87
+ export interface Entry {
88
+ slug: string;
89
+ meta: EntryMeta;
90
+ /** Field values keyed by field name. */
91
+ data: Record<string, unknown>;
92
+ published?: boolean;
93
+ publishedAt?: string | null;
94
+ }
95
+ export interface FileResult {
96
+ content: string;
97
+ sha: string;
98
+ path: string;
99
+ }
100
+ export interface WriteOptions {
101
+ message: string;
102
+ sha?: string;
103
+ branch?: string;
104
+ }
105
+ export interface DeleteOptions {
106
+ message: string;
107
+ sha: string;
108
+ branch?: string;
109
+ }
110
+ export interface FileListItem {
111
+ path: string;
112
+ sha: string;
113
+ }
114
+ export interface StorageAdapter {
115
+ read(path: string): Promise<FileResult | null>;
116
+ write(path: string, content: string, options: WriteOptions): Promise<void>;
117
+ delete(path: string, options: DeleteOptions): Promise<void>;
118
+ list(glob: string): Promise<FileListItem[]>;
119
+ }
120
+ export interface SchemaAdapter {
121
+ read(): Promise<CollectionMap>;
122
+ write(collections: CollectionMap): Promise<void>;
123
+ }
124
+ export interface ReadContext {
125
+ collection: string;
126
+ slug?: string;
127
+ adapter: StorageAdapter;
128
+ }
129
+ export interface WriteContext {
130
+ collection: string;
131
+ slug: string;
132
+ data: Record<string, unknown>;
133
+ adapter: StorageAdapter;
134
+ next: () => Promise<void>;
135
+ }
136
+ export interface DeleteContext {
137
+ collection: string;
138
+ slug: string;
139
+ sha: string;
140
+ adapter: StorageAdapter;
141
+ next: () => Promise<void>;
142
+ }
143
+ /** Context injected into plugin route handlers by the HTTP adapter. */
144
+ export interface PluginRouteContext {
145
+ adapter: StorageAdapter;
146
+ config: CmsConfig;
147
+ }
148
+ export type RouteHandler = (req: Request, ctx: PluginRouteContext) => Promise<Response>;
149
+ export interface FieldHandler {
150
+ serialize: (value: unknown) => string;
151
+ deserialize: (raw: string) => unknown;
152
+ validate: (value: unknown, config: FieldConfig) => string | null;
153
+ }
154
+ export interface Plugin {
155
+ name: string;
156
+ fields?: Record<string, FieldHandler>;
157
+ routes?: Record<string, RouteHandler>;
158
+ /**
159
+ * Called by HTTP-layer adapters (e.g. @airdraft/next) before routing.
160
+ * Throw `UnauthorizedError` (401) or `ForbiddenError` (403) to block the request.
161
+ */
162
+ middleware?: (request: Request) => Promise<void> | void;
163
+ hooks?: {
164
+ beforeRead?: (ctx: ReadContext) => Promise<void> | void;
165
+ afterRead?: (ctx: ReadContext) => Promise<void> | void;
166
+ beforeWrite?: (ctx: WriteContext) => Promise<void> | void;
167
+ afterWrite?: (ctx: WriteContext) => Promise<void> | void;
168
+ beforeDelete?: (ctx: DeleteContext) => Promise<void> | void;
169
+ afterDelete?: (ctx: DeleteContext) => Promise<void> | void;
170
+ /**
171
+ * Called after an entry is read from the storage adapter. Receives the entry
172
+ * (or the entry returned by the previous plugin in the chain) and must return
173
+ * the (optionally mutated) entry. Used by plugin-media to inject `{field}_url`
174
+ * companion fields for image-type fields.
175
+ */
176
+ transformEntry?: (entry: Entry, collection: CollectionConfig) => Promise<Entry> | Entry;
177
+ /**
178
+ * Called once per CMS request, at completion, with a wide audit event
179
+ * containing method, path, action, status code, duration, and error info.
180
+ * Implement this to persist, forward, or react to every operation.
181
+ */
182
+ onAuditEvent?: (event: AuditEvent) => void | Promise<void>;
183
+ };
184
+ schema?: (schema: CmsSchema) => CmsSchema;
185
+ }
186
+ export type UploadBody = File | Blob | ReadableStream | ArrayBuffer | string;
187
+ export interface UploadOptions {
188
+ mimeType?: string;
189
+ }
190
+ export interface UrlOptions {
191
+ /** Signed URL lifetime in seconds. Absent = permanent/provider-default. */
192
+ expiresIn?: number;
193
+ }
194
+ export interface MediaListOptions {
195
+ prefix?: string;
196
+ limit?: number;
197
+ cursor?: string;
198
+ }
199
+ export interface MediaUploadResult {
200
+ key: string;
201
+ url: string;
202
+ size: number;
203
+ mimeType: string;
204
+ name: string;
205
+ width?: number;
206
+ height?: number;
207
+ meta?: Record<string, string>;
208
+ }
209
+ export interface MediaListItem {
210
+ key: string;
211
+ url: string;
212
+ size: number;
213
+ mimeType: string;
214
+ name: string;
215
+ lastModified?: string;
216
+ }
217
+ export interface MediaListResult {
218
+ items: MediaListItem[];
219
+ cursor?: string;
220
+ }
221
+ /**
222
+ * A fully enriched media item combining adapter-level data with sidecar
223
+ * metadata stored via the content StorageAdapter (`_media/{key}.json`).
224
+ * Returned by all library-mode HTTP endpoints and @airdraft/client media methods.
225
+ */
226
+ export interface MediaItem {
227
+ key: string;
228
+ name: string;
229
+ mimeType: string;
230
+ size: number;
231
+ uploadedAt: string;
232
+ url: string;
233
+ isIndexed: boolean;
234
+ alt?: string;
235
+ caption?: string;
236
+ title?: string;
237
+ tags?: string[];
238
+ width?: number;
239
+ height?: number;
240
+ uploadedBy?: string;
241
+ }
242
+ export interface MediaLibraryResult {
243
+ items: MediaItem[];
244
+ cursor?: string;
245
+ }
246
+ export interface MediaAdapter {
247
+ upload(key: string, body: UploadBody, options?: UploadOptions): Promise<MediaUploadResult>;
248
+ url(key: string, options?: UrlOptions): Promise<string>;
249
+ list(options?: MediaListOptions): Promise<MediaListResult>;
250
+ delete(key: string): Promise<void>;
251
+ }
252
+ /**
253
+ * A wide audit event emitted once per CMS request, at completion.
254
+ * Follows the canonical log-line pattern: one rich, structured event per request.
255
+ *
256
+ * action values:
257
+ * collection.list | collection.get | collection.create | collection.update | collection.delete
258
+ * plugin.media.upload | plugin.media.list | plugin.media.*.url | plugin.media.*
259
+ * schema.get | unknown
260
+ */
261
+ export interface AuditEvent {
262
+ /** Unique identifier for this request. */
263
+ requestId: string;
264
+ /** ISO 8601 timestamp at the start of the request. */
265
+ timestamp: string;
266
+ /** Wall-clock duration of the full operation, in milliseconds. */
267
+ durationMs: number;
268
+ /** HTTP method (GET, POST, PUT, DELETE). */
269
+ method: string;
270
+ /** Full request path (e.g. /api/cms/posts/hello). */
271
+ path: string;
272
+ /** Classified CMS action (e.g. collection.create, plugin.media.upload). */
273
+ action: string;
274
+ /** Collection name when the operation targets a collection. */
275
+ collection?: string;
276
+ /** Entry slug when the operation targets a single entry. */
277
+ slug?: string;
278
+ /** HTTP status code returned to the client. */
279
+ statusCode: number;
280
+ /**
281
+ * Identity of the caller — the raw API key or bearer token value.
282
+ * Redact or hash this before persisting to external systems.
283
+ */
284
+ actor?: string;
285
+ /** Structured request context: query params and any other relevant request fields. */
286
+ request?: Record<string, unknown>;
287
+ /** Structured response context: entry count, created slug, media key, etc. */
288
+ response?: Record<string, unknown>;
289
+ /** Error details when the operation resulted in a 4xx or 5xx response. */
290
+ error?: {
291
+ message: string;
292
+ code?: string;
293
+ };
294
+ }
295
+ export interface CmsSchema {
296
+ collections: CollectionMap;
297
+ }
298
+ export interface ListOptions {
299
+ filter?: Record<string, unknown>;
300
+ sort?: {
301
+ field: string;
302
+ order: 'asc' | 'desc';
303
+ };
304
+ limit?: number;
305
+ offset?: number;
306
+ /** default: 'all'. Silently ignored on non-publish collections. */
307
+ status?: 'published' | 'draft' | 'all';
308
+ /** Field names to expand from slug(s) to full entry objects. Depth limited to 1. */
309
+ expand?: string[];
310
+ }
311
+ export interface GetOptions {
312
+ /** Field names to expand from slug(s) to full entry objects. Depth limited to 1. */
313
+ expand?: string[];
314
+ }
315
+ export interface CmsConfig {
316
+ adapter: StorageAdapter;
317
+ collections: CollectionMap;
318
+ plugins: Plugin[];
319
+ basePath: string;
320
+ defaultLocale?: string;
321
+ revalidation?: Record<string, (entry: Entry) => string[]>;
322
+ /** Path to the airdraft.schema.json file, when using file-based schema. */
323
+ schemaPath?: string;
324
+ }
325
+ export {};
326
+ //# sourceMappingURL=types.d.ts.map