first commit
This commit is contained in:
34
packages/core/src/fields/boolean.ts
Normal file
34
packages/core/src/fields/boolean.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { z } from "astro/zod";
|
||||
|
||||
import type { FieldDefinition, FieldUIHints } from "./types.js";
|
||||
|
||||
export interface BooleanOptions {
|
||||
default?: boolean;
|
||||
label?: string;
|
||||
helpText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Boolean field - checkbox/toggle
|
||||
*/
|
||||
export function boolean(options: BooleanOptions = {}): FieldDefinition<boolean> {
|
||||
const boolSchema = z.boolean();
|
||||
|
||||
// Apply default
|
||||
const schema: z.ZodTypeAny =
|
||||
options.default !== undefined ? boolSchema.default(options.default) : boolSchema;
|
||||
|
||||
const ui: FieldUIHints = {
|
||||
widget: "boolean",
|
||||
label: options.label,
|
||||
helpText: options.helpText,
|
||||
};
|
||||
|
||||
return {
|
||||
type: "boolean",
|
||||
columnType: "INTEGER",
|
||||
schema,
|
||||
options,
|
||||
ui,
|
||||
};
|
||||
}
|
||||
44
packages/core/src/fields/datetime.ts
Normal file
44
packages/core/src/fields/datetime.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { z } from "astro/zod";
|
||||
|
||||
import type { FieldDefinition, FieldUIHints } from "./types.js";
|
||||
|
||||
export interface DatetimeOptions {
|
||||
required?: boolean;
|
||||
min?: Date;
|
||||
max?: Date;
|
||||
helpText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Datetime field - date and time picker
|
||||
*/
|
||||
export function datetime(options: DatetimeOptions = {}): FieldDefinition<Date> {
|
||||
let dateSchema = z.date();
|
||||
|
||||
// Apply constraints
|
||||
if (options.min !== undefined) {
|
||||
dateSchema = dateSchema.min(options.min, "Date is too early");
|
||||
}
|
||||
|
||||
if (options.max !== undefined) {
|
||||
dateSchema = dateSchema.max(options.max, "Date is too late");
|
||||
}
|
||||
|
||||
// Optional vs required
|
||||
const schema: z.ZodTypeAny = options.required ? dateSchema : dateSchema.optional();
|
||||
|
||||
const ui: FieldUIHints = {
|
||||
widget: "datetime",
|
||||
helpText: options.helpText,
|
||||
min: options.min?.toISOString(),
|
||||
max: options.max?.toISOString(),
|
||||
};
|
||||
|
||||
return {
|
||||
type: "datetime",
|
||||
columnType: "TEXT",
|
||||
schema,
|
||||
options,
|
||||
ui,
|
||||
};
|
||||
}
|
||||
41
packages/core/src/fields/file.ts
Normal file
41
packages/core/src/fields/file.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { z } from "astro/zod";
|
||||
|
||||
import type { FieldDefinition, FieldUIHints, FileValue } from "./types.js";
|
||||
|
||||
export interface FileOptions {
|
||||
required?: boolean;
|
||||
maxSize?: number; // In bytes
|
||||
allowedTypes?: string[]; // MIME types
|
||||
helpText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* File field - file upload
|
||||
*/
|
||||
export function file(options: FileOptions = {}): FieldDefinition<FileValue> {
|
||||
const fileObjSchema = z.object({
|
||||
id: z.string(),
|
||||
url: z.string(),
|
||||
filename: z.string(),
|
||||
mimeType: z.string(),
|
||||
size: z.number(),
|
||||
});
|
||||
|
||||
// Optional vs required
|
||||
const schema: z.ZodTypeAny = options.required ? fileObjSchema : fileObjSchema.optional();
|
||||
|
||||
const ui: FieldUIHints = {
|
||||
widget: "file",
|
||||
helpText: options.helpText,
|
||||
maxSize: options.maxSize,
|
||||
allowedTypes: options.allowedTypes,
|
||||
};
|
||||
|
||||
return {
|
||||
type: "file",
|
||||
columnType: "TEXT",
|
||||
schema,
|
||||
options,
|
||||
ui,
|
||||
};
|
||||
}
|
||||
34
packages/core/src/fields/image.ts
Normal file
34
packages/core/src/fields/image.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { z } from "astro/zod";
|
||||
|
||||
import type { FieldDefinition, ImageValue } from "./types.js";
|
||||
|
||||
/**
|
||||
* Image field schema
|
||||
*/
|
||||
const imageSchema = z.object({
|
||||
id: z.string(),
|
||||
src: z.string(),
|
||||
alt: z.string().optional(),
|
||||
width: z.number().optional(),
|
||||
height: z.number().optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Image field
|
||||
* References media items from the media library
|
||||
*/
|
||||
export function image(options?: {
|
||||
required?: boolean;
|
||||
maxSize?: number; // in bytes
|
||||
allowedTypes?: string[]; // MIME types
|
||||
}): FieldDefinition<ImageValue | undefined> {
|
||||
return {
|
||||
type: "image",
|
||||
columnType: "TEXT",
|
||||
schema: options?.required === false ? imageSchema.optional() : imageSchema,
|
||||
options,
|
||||
ui: {
|
||||
widget: "image",
|
||||
},
|
||||
};
|
||||
}
|
||||
42
packages/core/src/fields/index.ts
Normal file
42
packages/core/src/fields/index.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
// Field type exports
|
||||
export { text } from "./text.js";
|
||||
export { textarea } from "./textarea.js";
|
||||
export { number } from "./number.js";
|
||||
export { integer } from "./integer.js";
|
||||
export { boolean } from "./boolean.js";
|
||||
export { select } from "./select.js";
|
||||
export { multiSelect } from "./multiselect.js";
|
||||
export { datetime } from "./datetime.js";
|
||||
export { slug } from "./slug.js";
|
||||
export { image } from "./image.js";
|
||||
export { file } from "./file.js";
|
||||
export { reference } from "./reference.js";
|
||||
export { json } from "./json.js";
|
||||
export { richText } from "./richtext.js";
|
||||
export { portableText } from "./portable-text.js";
|
||||
|
||||
// Type exports
|
||||
export type {
|
||||
FieldDefinition,
|
||||
FieldUIHints,
|
||||
ColumnType,
|
||||
PortableTextBlock,
|
||||
ImageValue,
|
||||
FileValue,
|
||||
} from "./types.js";
|
||||
|
||||
// MediaValue is canonical in media/types.ts but re-exported here for convenience
|
||||
export type { MediaValue } from "../media/types.js";
|
||||
|
||||
export type { TextOptions } from "./text.js";
|
||||
export type { TextareaOptions } from "./textarea.js";
|
||||
export type { NumberOptions } from "./number.js";
|
||||
export type { IntegerOptions } from "./integer.js";
|
||||
export type { BooleanOptions } from "./boolean.js";
|
||||
export type { SelectOptions } from "./select.js";
|
||||
export type { MultiSelectOptions } from "./multiselect.js";
|
||||
export type { DatetimeOptions } from "./datetime.js";
|
||||
export type { SlugOptions } from "./slug.js";
|
||||
export type { FileOptions } from "./file.js";
|
||||
export type { JsonOptions } from "./json.js";
|
||||
export type { RichTextOptions } from "./richtext.js";
|
||||
50
packages/core/src/fields/integer.ts
Normal file
50
packages/core/src/fields/integer.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { z } from "astro/zod";
|
||||
|
||||
import type { FieldDefinition, FieldUIHints } from "./types.js";
|
||||
|
||||
export interface IntegerOptions {
|
||||
required?: boolean;
|
||||
min?: number;
|
||||
max?: number;
|
||||
placeholder?: string;
|
||||
helpText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Integer field - whole number input
|
||||
*
|
||||
* Unlike the `number` field which stores as REAL (floating point),
|
||||
* this field stores as INTEGER for whole numbers.
|
||||
*/
|
||||
export function integer(options: IntegerOptions = {}): FieldDefinition<number> {
|
||||
let intSchema = z.number().int("Must be a whole number");
|
||||
|
||||
// Range constraints
|
||||
if (options.min !== undefined) {
|
||||
intSchema = intSchema.min(options.min, `Must be at least ${options.min}`);
|
||||
}
|
||||
|
||||
if (options.max !== undefined) {
|
||||
intSchema = intSchema.max(options.max, `Must be at most ${options.max}`);
|
||||
}
|
||||
|
||||
// Optional vs required
|
||||
const schema: z.ZodTypeAny = options.required ? intSchema : intSchema.optional();
|
||||
|
||||
const ui: FieldUIHints = {
|
||||
widget: "number",
|
||||
placeholder: options.placeholder,
|
||||
helpText: options.helpText,
|
||||
min: options.min,
|
||||
max: options.max,
|
||||
step: 1, // Indicate whole numbers
|
||||
};
|
||||
|
||||
return {
|
||||
type: "integer",
|
||||
columnType: "INTEGER",
|
||||
schema,
|
||||
options,
|
||||
ui,
|
||||
};
|
||||
}
|
||||
37
packages/core/src/fields/json.ts
Normal file
37
packages/core/src/fields/json.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { z } from "astro/zod";
|
||||
|
||||
import type { FieldDefinition, FieldUIHints } from "./types.js";
|
||||
|
||||
export interface JsonOptions<T = unknown> {
|
||||
required?: boolean;
|
||||
schema?: z.ZodType<T>; // Optional custom schema for validation
|
||||
helpText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON field - arbitrary JSON data
|
||||
*/
|
||||
export function json<T = unknown>(options: JsonOptions<T> = {}): FieldDefinition<T> {
|
||||
// When T = unknown (default), z.unknown() is already z.ZodType<unknown>.
|
||||
// When a custom schema is provided, it carries the correct generic.
|
||||
// The generic constraint ensures type safety for callers.
|
||||
let schema: z.ZodTypeAny = options.schema ?? z.unknown();
|
||||
|
||||
// Optional vs required
|
||||
if (!options.required && !options.schema) {
|
||||
schema = z.unknown().optional();
|
||||
}
|
||||
|
||||
const ui: FieldUIHints = {
|
||||
widget: "json",
|
||||
helpText: options.helpText || "JSON data",
|
||||
};
|
||||
|
||||
return {
|
||||
type: "json",
|
||||
columnType: "JSON",
|
||||
schema,
|
||||
options,
|
||||
ui,
|
||||
};
|
||||
}
|
||||
48
packages/core/src/fields/multiselect.ts
Normal file
48
packages/core/src/fields/multiselect.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { z } from "astro/zod";
|
||||
|
||||
import type { FieldDefinition, FieldUIHints } from "./types.js";
|
||||
|
||||
export interface MultiSelectOptions<T extends readonly [string, ...string[]]> {
|
||||
options: T;
|
||||
required?: boolean;
|
||||
min?: number;
|
||||
max?: number;
|
||||
helpText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* MultiSelect field - multiple choices from predefined options
|
||||
*/
|
||||
export function multiSelect<T extends readonly [string, ...string[]]>(
|
||||
msOptions: MultiSelectOptions<T>,
|
||||
): FieldDefinition<T[number][]> {
|
||||
let arraySchema = z.array(z.enum(msOptions.options));
|
||||
|
||||
// Apply constraints
|
||||
if (msOptions.min !== undefined) {
|
||||
arraySchema = arraySchema.min(msOptions.min, `Must select at least ${msOptions.min}`);
|
||||
}
|
||||
|
||||
if (msOptions.max !== undefined) {
|
||||
arraySchema = arraySchema.max(msOptions.max, `Must select at most ${msOptions.max}`);
|
||||
}
|
||||
|
||||
// Optional vs required
|
||||
const schema: z.ZodTypeAny = msOptions.required ? arraySchema : arraySchema.optional();
|
||||
|
||||
const ui: FieldUIHints = {
|
||||
widget: "multiSelect",
|
||||
helpText: msOptions.helpText,
|
||||
options: msOptions.options,
|
||||
min: msOptions.min,
|
||||
max: msOptions.max,
|
||||
};
|
||||
|
||||
return {
|
||||
type: "multiSelect",
|
||||
columnType: "JSON",
|
||||
schema,
|
||||
options: msOptions,
|
||||
ui,
|
||||
};
|
||||
}
|
||||
52
packages/core/src/fields/number.ts
Normal file
52
packages/core/src/fields/number.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { z } from "astro/zod";
|
||||
|
||||
import type { FieldDefinition, FieldUIHints } from "./types.js";
|
||||
|
||||
export interface NumberOptions {
|
||||
required?: boolean;
|
||||
min?: number;
|
||||
max?: number;
|
||||
integer?: boolean;
|
||||
placeholder?: string;
|
||||
helpText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number field - numeric input
|
||||
*/
|
||||
export function number(options: NumberOptions = {}): FieldDefinition<number> {
|
||||
let numberSchema = z.number();
|
||||
|
||||
// Integer constraint
|
||||
if (options.integer) {
|
||||
numberSchema = numberSchema.int("Must be an integer");
|
||||
}
|
||||
|
||||
// Range constraints
|
||||
if (options.min !== undefined) {
|
||||
numberSchema = numberSchema.min(options.min, `Must be at least ${options.min}`);
|
||||
}
|
||||
|
||||
if (options.max !== undefined) {
|
||||
numberSchema = numberSchema.max(options.max, `Must be at most ${options.max}`);
|
||||
}
|
||||
|
||||
// Optional vs required
|
||||
const schema: z.ZodTypeAny = options.required ? numberSchema : numberSchema.optional();
|
||||
|
||||
const ui: FieldUIHints = {
|
||||
widget: "number",
|
||||
placeholder: options.placeholder,
|
||||
helpText: options.helpText,
|
||||
min: options.min,
|
||||
max: options.max,
|
||||
};
|
||||
|
||||
return {
|
||||
type: "number",
|
||||
columnType: "REAL",
|
||||
schema,
|
||||
options,
|
||||
ui,
|
||||
};
|
||||
}
|
||||
33
packages/core/src/fields/portable-text.ts
Normal file
33
packages/core/src/fields/portable-text.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { z } from "astro/zod";
|
||||
|
||||
import type { FieldDefinition, PortableTextBlock } from "./types.js";
|
||||
|
||||
/**
|
||||
* Portable Text block schema
|
||||
*/
|
||||
const portableTextBlockSchema: z.ZodType<PortableTextBlock> = z
|
||||
.object({
|
||||
_type: z.string(),
|
||||
_key: z.string(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
/**
|
||||
* Portable Text field
|
||||
* Stores structured content in Portable Text format
|
||||
*/
|
||||
export function portableText(options?: {
|
||||
required?: boolean;
|
||||
}): FieldDefinition<PortableTextBlock[] | undefined> {
|
||||
const schema = z.array(portableTextBlockSchema);
|
||||
|
||||
return {
|
||||
type: "portableText",
|
||||
columnType: "JSON",
|
||||
schema: options?.required === false ? schema.optional() : schema,
|
||||
options,
|
||||
ui: {
|
||||
widget: "portableText",
|
||||
},
|
||||
};
|
||||
}
|
||||
29
packages/core/src/fields/reference.ts
Normal file
29
packages/core/src/fields/reference.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { z } from "astro/zod";
|
||||
|
||||
import type { FieldDefinition } from "./types.js";
|
||||
|
||||
/**
|
||||
* Reference field
|
||||
* References another content item by ID
|
||||
*/
|
||||
export function reference(
|
||||
collection: string,
|
||||
options?: {
|
||||
required?: boolean;
|
||||
},
|
||||
): FieldDefinition<string | undefined> {
|
||||
const schema = z.string();
|
||||
|
||||
return {
|
||||
type: "reference",
|
||||
columnType: "TEXT",
|
||||
schema: options?.required === false ? schema.optional() : schema,
|
||||
options: {
|
||||
...options,
|
||||
collection,
|
||||
},
|
||||
ui: {
|
||||
widget: "reference",
|
||||
},
|
||||
};
|
||||
}
|
||||
31
packages/core/src/fields/richtext.ts
Normal file
31
packages/core/src/fields/richtext.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { z } from "astro/zod";
|
||||
|
||||
import type { FieldDefinition, FieldUIHints } from "./types.js";
|
||||
|
||||
export interface RichTextOptions {
|
||||
required?: boolean;
|
||||
helpText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rich text field - Markdown content
|
||||
*/
|
||||
export function richText(options: RichTextOptions = {}): FieldDefinition<string> {
|
||||
const stringSchema = z.string();
|
||||
|
||||
// Optional vs required
|
||||
const schema: z.ZodTypeAny = options.required ? stringSchema : stringSchema.optional();
|
||||
|
||||
const ui: FieldUIHints = {
|
||||
widget: "richText",
|
||||
helpText: options.helpText || "Markdown formatted text",
|
||||
};
|
||||
|
||||
return {
|
||||
type: "richText",
|
||||
columnType: "TEXT",
|
||||
schema,
|
||||
options,
|
||||
ui,
|
||||
};
|
||||
}
|
||||
46
packages/core/src/fields/select.ts
Normal file
46
packages/core/src/fields/select.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { z } from "astro/zod";
|
||||
|
||||
import type { FieldDefinition, FieldUIHints } from "./types.js";
|
||||
|
||||
export interface SelectOptions<T extends readonly [string, ...string[]]> {
|
||||
options: T;
|
||||
required?: boolean;
|
||||
default?: T[number];
|
||||
placeholder?: string;
|
||||
helpText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select field - single choice from predefined options
|
||||
*/
|
||||
export function select<T extends readonly [string, ...string[]]>(
|
||||
selectOptions: SelectOptions<T>,
|
||||
): FieldDefinition<T[number]> {
|
||||
const enumSchema = z.enum(selectOptions.options);
|
||||
|
||||
// Apply default first, then optional
|
||||
let schema: z.ZodTypeAny;
|
||||
if (selectOptions.default !== undefined) {
|
||||
schema = enumSchema.default(selectOptions.default);
|
||||
} else if (!selectOptions.required) {
|
||||
// Only make it optional if no default is provided
|
||||
schema = enumSchema.optional();
|
||||
} else {
|
||||
schema = enumSchema;
|
||||
}
|
||||
|
||||
const ui: FieldUIHints = {
|
||||
widget: "select",
|
||||
placeholder: selectOptions.placeholder,
|
||||
helpText: selectOptions.helpText,
|
||||
options: selectOptions.options,
|
||||
};
|
||||
|
||||
return {
|
||||
type: "select",
|
||||
columnType: "TEXT",
|
||||
schema,
|
||||
options: selectOptions,
|
||||
ui,
|
||||
};
|
||||
}
|
||||
38
packages/core/src/fields/slug.ts
Normal file
38
packages/core/src/fields/slug.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { z } from "astro/zod";
|
||||
|
||||
import type { FieldDefinition, FieldUIHints } from "./types.js";
|
||||
|
||||
export interface SlugOptions {
|
||||
required?: boolean;
|
||||
from?: string; // Field name to generate slug from
|
||||
pattern?: RegExp;
|
||||
helpText?: string;
|
||||
}
|
||||
|
||||
// Default slug pattern: lowercase alphanumeric + hyphens
|
||||
const DEFAULT_SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
||||
|
||||
/**
|
||||
* Slug field - URL-safe identifier
|
||||
*/
|
||||
export function slug(options: SlugOptions = {}): FieldDefinition<string> {
|
||||
const pattern = options.pattern || DEFAULT_SLUG_PATTERN;
|
||||
const stringSchema = z.string().regex(pattern, "Invalid slug format");
|
||||
|
||||
// Optional vs required
|
||||
const schema: z.ZodTypeAny = options.required ? stringSchema : stringSchema.optional();
|
||||
|
||||
const ui: FieldUIHints = {
|
||||
widget: "slug",
|
||||
helpText: options.helpText || "URL-safe identifier (lowercase, hyphens only)",
|
||||
from: options.from,
|
||||
};
|
||||
|
||||
return {
|
||||
type: "slug",
|
||||
columnType: "TEXT",
|
||||
schema,
|
||||
options,
|
||||
ui,
|
||||
};
|
||||
}
|
||||
55
packages/core/src/fields/text.ts
Normal file
55
packages/core/src/fields/text.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { z } from "astro/zod";
|
||||
|
||||
import type { FieldDefinition, FieldUIHints } from "./types.js";
|
||||
|
||||
export interface TextOptions {
|
||||
required?: boolean;
|
||||
minLength?: number;
|
||||
maxLength?: number;
|
||||
pattern?: RegExp;
|
||||
placeholder?: string;
|
||||
helpText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Text field - single line text input
|
||||
*/
|
||||
export function text(options: TextOptions = {}): FieldDefinition<string> {
|
||||
let stringSchema = z.string();
|
||||
|
||||
// Apply constraints
|
||||
if (options.minLength !== undefined) {
|
||||
stringSchema = stringSchema.min(
|
||||
options.minLength,
|
||||
`Must be at least ${options.minLength} characters`,
|
||||
);
|
||||
}
|
||||
|
||||
if (options.maxLength !== undefined) {
|
||||
stringSchema = stringSchema.max(
|
||||
options.maxLength,
|
||||
`Must be at most ${options.maxLength} characters`,
|
||||
);
|
||||
}
|
||||
|
||||
if (options.pattern) {
|
||||
stringSchema = stringSchema.regex(options.pattern, "Invalid format");
|
||||
}
|
||||
|
||||
// Optional vs required
|
||||
const schema: z.ZodTypeAny = options.required ? stringSchema : stringSchema.optional();
|
||||
|
||||
const ui: FieldUIHints = {
|
||||
widget: "text",
|
||||
placeholder: options.placeholder,
|
||||
helpText: options.helpText,
|
||||
};
|
||||
|
||||
return {
|
||||
type: "text",
|
||||
columnType: "TEXT",
|
||||
schema,
|
||||
options,
|
||||
ui,
|
||||
};
|
||||
}
|
||||
52
packages/core/src/fields/textarea.ts
Normal file
52
packages/core/src/fields/textarea.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { z } from "astro/zod";
|
||||
|
||||
import type { FieldDefinition, FieldUIHints } from "./types.js";
|
||||
|
||||
export interface TextareaOptions {
|
||||
required?: boolean;
|
||||
minLength?: number;
|
||||
maxLength?: number;
|
||||
rows?: number;
|
||||
placeholder?: string;
|
||||
helpText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Textarea field - multi-line text input
|
||||
*/
|
||||
export function textarea(options: TextareaOptions = {}): FieldDefinition<string> {
|
||||
let stringSchema = z.string();
|
||||
|
||||
// Apply constraints
|
||||
if (options.minLength !== undefined) {
|
||||
stringSchema = stringSchema.min(
|
||||
options.minLength,
|
||||
`Must be at least ${options.minLength} characters`,
|
||||
);
|
||||
}
|
||||
|
||||
if (options.maxLength !== undefined) {
|
||||
stringSchema = stringSchema.max(
|
||||
options.maxLength,
|
||||
`Must be at most ${options.maxLength} characters`,
|
||||
);
|
||||
}
|
||||
|
||||
// Optional vs required
|
||||
const schema: z.ZodTypeAny = options.required ? stringSchema : stringSchema.optional();
|
||||
|
||||
const ui: FieldUIHints = {
|
||||
widget: "textarea",
|
||||
placeholder: options.placeholder,
|
||||
helpText: options.helpText,
|
||||
rows: options.rows || 6,
|
||||
};
|
||||
|
||||
return {
|
||||
type: "textarea",
|
||||
columnType: "TEXT",
|
||||
schema,
|
||||
options,
|
||||
ui,
|
||||
};
|
||||
}
|
||||
64
packages/core/src/fields/types.ts
Normal file
64
packages/core/src/fields/types.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import type { z } from "astro/zod";
|
||||
|
||||
/**
|
||||
* SQLite column types that map from field types
|
||||
*/
|
||||
export type ColumnType = "TEXT" | "REAL" | "INTEGER" | "JSON";
|
||||
|
||||
/**
|
||||
* Base field definition
|
||||
*
|
||||
* Note: schema uses z.ZodTypeAny to accommodate optional/default wrappers
|
||||
*/
|
||||
export interface FieldDefinition<_T = unknown> {
|
||||
type: string;
|
||||
/**
|
||||
* The SQLite column type to use when storing this field
|
||||
*/
|
||||
columnType: ColumnType;
|
||||
schema: z.ZodTypeAny;
|
||||
options?: unknown;
|
||||
ui?: FieldUIHints;
|
||||
}
|
||||
|
||||
/**
|
||||
* UI hints for admin rendering
|
||||
*/
|
||||
export interface FieldUIHints {
|
||||
widget?: string;
|
||||
placeholder?: string;
|
||||
helpText?: string;
|
||||
rows?: number; // For textarea
|
||||
min?: number | string;
|
||||
max?: number | string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Portable Text block structure
|
||||
*/
|
||||
export interface PortableTextBlock {
|
||||
_type: string;
|
||||
_key: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
// Re-export MediaValue from media/types.ts (canonical location)
|
||||
export type { MediaValue } from "../media/types.js";
|
||||
import type { MediaValue } from "../media/types.js";
|
||||
|
||||
/**
|
||||
* @deprecated Use MediaValue instead. ImageValue is an alias for backwards compatibility.
|
||||
*/
|
||||
export type ImageValue = MediaValue;
|
||||
|
||||
/**
|
||||
* File field value
|
||||
*/
|
||||
export interface FileValue {
|
||||
id: string;
|
||||
url: string;
|
||||
filename: string;
|
||||
mimeType: string;
|
||||
size: number;
|
||||
}
|
||||
Reference in New Issue
Block a user