litecms 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 (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1387 -0
  3. package/dist/admin/CmsAdminLayout.d.ts +27 -0
  4. package/dist/admin/CmsAdminLayout.d.ts.map +1 -0
  5. package/dist/admin/CmsAdminPage.d.ts +31 -0
  6. package/dist/admin/CmsAdminPage.d.ts.map +1 -0
  7. package/dist/admin/config.d.ts +83 -0
  8. package/dist/admin/config.d.ts.map +1 -0
  9. package/dist/admin/config.js +53 -0
  10. package/dist/admin/exports.d.ts +7 -0
  11. package/dist/admin/exports.d.ts.map +1 -0
  12. package/dist/admin/exports.js +452 -0
  13. package/dist/admin/index.d.ts +147 -0
  14. package/dist/admin/index.d.ts.map +1 -0
  15. package/dist/components/CmsAutoForm.d.ts +73 -0
  16. package/dist/components/CmsAutoForm.d.ts.map +1 -0
  17. package/dist/components/CmsField.d.ts +50 -0
  18. package/dist/components/CmsField.d.ts.map +1 -0
  19. package/dist/components/CmsForm.d.ts +74 -0
  20. package/dist/components/CmsForm.d.ts.map +1 -0
  21. package/dist/components/CmsImageField.d.ts +33 -0
  22. package/dist/components/CmsImageField.d.ts.map +1 -0
  23. package/dist/components/CmsNavSection.d.ts +7 -0
  24. package/dist/components/CmsNavSection.d.ts.map +1 -0
  25. package/dist/components/CmsSimpleForm.d.ts +54 -0
  26. package/dist/components/CmsSimpleForm.d.ts.map +1 -0
  27. package/dist/components/index.d.ts +43 -0
  28. package/dist/components/index.d.ts.map +1 -0
  29. package/dist/components/index.js +619 -0
  30. package/dist/domain/index.d.ts +1 -0
  31. package/dist/domain/index.d.ts.map +1 -0
  32. package/dist/index-8zcd33mx.js +39 -0
  33. package/dist/index-pmb5m3ek.js +4135 -0
  34. package/dist/index.d.ts +4 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +32 -0
  37. package/dist/schema/index.d.ts +80 -0
  38. package/dist/schema/index.d.ts.map +1 -0
  39. package/dist/schema/index.js +46 -0
  40. package/dist/server/index.d.ts +79 -0
  41. package/dist/server/index.d.ts.map +1 -0
  42. package/dist/server/index.js +117 -0
  43. package/dist/shared/utils.d.ts +23 -0
  44. package/dist/shared/utils.d.ts.map +1 -0
  45. package/dist/storage/index.d.ts +86 -0
  46. package/dist/storage/index.d.ts.map +1 -0
  47. package/dist/storage/index.js +86 -0
  48. package/dist/stores/index.d.ts +1 -0
  49. package/dist/stores/index.d.ts.map +1 -0
  50. package/package.json +90 -0
@@ -0,0 +1,4 @@
1
+ export * from './components';
2
+ export * from './schema';
3
+ export type { ActionState, ActionOptions } from './server';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AAEzB,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,32 @@
1
+ import {
2
+ CmsAutoForm,
3
+ CmsForm,
4
+ CmsFormError,
5
+ CmsFormSuccess,
6
+ CmsSubmitButton,
7
+ useCmsForm
8
+ } from "./components/index.js";
9
+ import {
10
+ CmsCheckbox,
11
+ CmsField,
12
+ CmsHiddenField,
13
+ CmsImageField
14
+ } from "./index-pmb5m3ek.js";
15
+ import {
16
+ defineSchema,
17
+ getEditableFields
18
+ } from "./schema/index.js";
19
+ export {
20
+ useCmsForm,
21
+ getEditableFields,
22
+ defineSchema,
23
+ CmsSubmitButton,
24
+ CmsImageField,
25
+ CmsHiddenField,
26
+ CmsFormSuccess,
27
+ CmsFormError,
28
+ CmsForm,
29
+ CmsField,
30
+ CmsCheckbox,
31
+ CmsAutoForm
32
+ };
@@ -0,0 +1,80 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Field UI metadata for auto-generating forms
4
+ */
5
+ export type FieldMeta = {
6
+ /** Display label for the field */
7
+ label: string;
8
+ /** Whether the field is editable in the admin panel (default: true) */
9
+ editable?: boolean;
10
+ /** Input type override */
11
+ type?: 'text' | 'textarea' | 'number' | 'email' | 'url' | 'select' | 'checkbox' | 'image';
12
+ /** Placeholder text */
13
+ placeholder?: string;
14
+ /** Help text shown below the input */
15
+ helpText?: string;
16
+ /** Options for select fields */
17
+ options?: Array<{
18
+ value: string;
19
+ label: string;
20
+ }>;
21
+ /** Number of rows for textarea (default: 3) */
22
+ rows?: number;
23
+ /** Order in the form (lower = higher) */
24
+ order?: number;
25
+ /** Group name for organizing fields */
26
+ group?: string;
27
+ /** Accepted file types for image fields (default: 'image/*') */
28
+ accept?: string;
29
+ };
30
+ /**
31
+ * Schema definition with field metadata
32
+ */
33
+ export type SchemaDefinition<T extends z.ZodRawShape> = {
34
+ /** The Zod schema */
35
+ schema: z.ZodObject<T>;
36
+ /** Field metadata keyed by field name */
37
+ fields: {
38
+ [K in keyof T]?: FieldMeta;
39
+ };
40
+ /** Default values */
41
+ defaults: z.infer<z.ZodObject<T>>;
42
+ };
43
+ /**
44
+ * Define a CMS schema with field metadata for auto-generating forms.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * const HomePageDef = defineSchema({
49
+ * schema: z.object({
50
+ * heroTitle: z.string().min(1),
51
+ * heroSubtitle: z.string().optional(),
52
+ * internalNote: z.string().optional(),
53
+ * }),
54
+ * fields: {
55
+ * heroTitle: { label: 'Hero Title', placeholder: 'Welcome...' },
56
+ * heroSubtitle: { label: 'Subtitle', type: 'textarea', rows: 3 },
57
+ * internalNote: { label: 'Internal Note', editable: false },
58
+ * },
59
+ * defaults: {
60
+ * heroTitle: 'Welcome',
61
+ * heroSubtitle: '',
62
+ * internalNote: '',
63
+ * },
64
+ * });
65
+ * ```
66
+ */
67
+ export declare function defineSchema<T extends z.ZodRawShape>(definition: SchemaDefinition<T>): SchemaDefinition<T>;
68
+ /**
69
+ * Field info extracted from schema
70
+ */
71
+ export type FieldInfo = {
72
+ name: string;
73
+ meta: FieldMeta;
74
+ required: boolean;
75
+ };
76
+ /**
77
+ * Get editable fields from a schema definition
78
+ */
79
+ export declare function getEditableFields<T extends z.ZodRawShape>(definition: SchemaDefinition<T>): FieldInfo[];
80
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schema/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG;IACpB,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,0BAA0B;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;IAC1F,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gCAAgC;IAChC,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClD,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,CAAC,CAAC,WAAW,IAAI;IACpD,qBAAqB;IACrB,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IACvB,yCAAyC;IACzC,MAAM,EAAE;SAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,SAAS;KAAE,CAAC;IACvC,qBAAqB;IACrB,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;CACrC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,CAAC,CAAC,WAAW,EAChD,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAChC,gBAAgB,CAAC,CAAC,CAAC,CAErB;AAED;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,CAAC,CAAC,WAAW,EACrD,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAChC,SAAS,EAAE,CA+Bb"}
@@ -0,0 +1,46 @@
1
+ // src/schema/index.ts
2
+ function defineSchema(definition) {
3
+ return definition;
4
+ }
5
+ function getEditableFields(definition) {
6
+ const shape = definition.schema.shape;
7
+ const fields = [];
8
+ for (const name of Object.keys(shape)) {
9
+ const meta = definition.fields[name];
10
+ if (meta?.editable === false)
11
+ continue;
12
+ const fieldMeta = {
13
+ label: meta?.label ?? formatLabel(name),
14
+ editable: meta?.editable ?? true,
15
+ type: meta?.type ?? "text",
16
+ placeholder: meta?.placeholder,
17
+ helpText: meta?.helpText,
18
+ options: meta?.options,
19
+ rows: meta?.rows,
20
+ order: meta?.order ?? 999,
21
+ group: meta?.group
22
+ };
23
+ const isRequired = !isOptionalField(shape[name]);
24
+ fields.push({
25
+ name,
26
+ meta: fieldMeta,
27
+ required: isRequired
28
+ });
29
+ }
30
+ return fields.sort((a, b) => (a.meta.order ?? 999) - (b.meta.order ?? 999));
31
+ }
32
+ function isOptionalField(zodType) {
33
+ const typeDef = zodType;
34
+ const typeName = typeDef?._def?.typeName;
35
+ if (typeName === "ZodOptional" || typeName === "ZodNullable" || typeName === "ZodDefault") {
36
+ return true;
37
+ }
38
+ return false;
39
+ }
40
+ function formatLabel(name) {
41
+ return name.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase()).trim();
42
+ }
43
+ export {
44
+ getEditableFields,
45
+ defineSchema
46
+ };
@@ -0,0 +1,79 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Action state returned from server actions
4
+ */
5
+ export type ActionState<T = unknown> = {
6
+ success: boolean;
7
+ data?: T;
8
+ errors?: {
9
+ fieldErrors?: Record<string, string[]>;
10
+ formError?: string;
11
+ };
12
+ };
13
+ /**
14
+ * Options for createSaveAction
15
+ */
16
+ export type ActionOptions<TData> = {
17
+ /** Callback to save data (e.g., Drizzle upsert) */
18
+ save: (data: TData) => Promise<void>;
19
+ /** Path to revalidate after successful save (passed to onRevalidate) */
20
+ revalidatePath?: string;
21
+ /**
22
+ * Callback to revalidate the path after successful save.
23
+ * Import revalidatePath from next/cache and pass it here.
24
+ * @example
25
+ * ```ts
26
+ * import { revalidatePath } from 'next/cache';
27
+ * createSaveAction(schema, { onRevalidate: revalidatePath, revalidatePath: '/' });
28
+ * ```
29
+ */
30
+ onRevalidate?: (path: string) => void;
31
+ /**
32
+ * Optional authentication check. Return true if authorized, false otherwise.
33
+ * If not provided, no auth check is performed.
34
+ * @example
35
+ * ```ts
36
+ * createSaveAction(schema, {
37
+ * checkAuth: async () => {
38
+ * const session = await getSession();
39
+ * return !!session;
40
+ * }
41
+ * });
42
+ * ```
43
+ */
44
+ checkAuth?: () => Promise<boolean>;
45
+ };
46
+ export declare const isDevelopmentEnv: boolean;
47
+ /**
48
+ * Creates a type-safe server action for form submission.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * import { revalidatePath } from 'next/cache';
53
+ *
54
+ * const saveHome = createSaveAction(HomePageSchema, {
55
+ * save: async (data) => {
56
+ * await db.upsert(cmsDocuments).values({ key: "home", data });
57
+ * },
58
+ * revalidatePath: "/",
59
+ * onRevalidate: revalidatePath,
60
+ * });
61
+ * ```
62
+ */
63
+ export declare function createSaveAction<TSchema extends z.ZodType>(schema: TSchema, options: ActionOptions<z.infer<TSchema>>): (_prevState: ActionState<z.infer<TSchema>>, formData: FormData) => Promise<ActionState<z.infer<TSchema>>>;
64
+ /**
65
+ * Parse FormData into a plain object with type coercion.
66
+ * Handles:
67
+ * - Empty strings as undefined (for optional fields)
68
+ * - Number coercion for fields ending with _number or containing numeric values
69
+ * - Boolean coercion for checkbox fields
70
+ * - Nested objects using dot notation (e.g., "address.city")
71
+ * - Arrays using bracket notation (e.g., "tags[0]", "tags[1]")
72
+ * - Arrays of objects (e.g., "navLinks[0].label", "navLinks[0].href")
73
+ */
74
+ export declare function parseFormDataToObject(formData: FormData): Record<string, unknown>;
75
+ /**
76
+ * Helper to parse FormData with a specific schema
77
+ */
78
+ export declare function parseFormData<T extends z.ZodType>(schema: T, formData: FormData): z.infer<T> | null;
79
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;GAEG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,GAAG,OAAO,IAAI;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,MAAM,CAAC,EAAE;QACL,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QACvC,SAAS,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACL,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,aAAa,CAAC,KAAK,IAAI;IAC/B,mDAAmD;IACnD,IAAI,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC;;;;;;;;;;;;OAYG;IACH,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;CACtC,CAAC;AAEF,eAAO,MAAM,gBAAgB,SAIY,CAAC;AAE1C;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,SAAS,CAAC,CAAC,OAAO,EACtD,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAGpC,YAAY,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,EACzC,UAAU,QAAQ,KACnB,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAmE5C;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACjC,QAAQ,EAAE,QAAQ,GACnB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAyCzB;AAmDD;;GAEG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAC7C,MAAM,EAAE,CAAC,EACT,QAAQ,EAAE,QAAQ,GACnB,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAInB"}
@@ -0,0 +1,117 @@
1
+ // src/server/index.ts
2
+ var isDevelopmentEnv = typeof globalThis !== "undefined" && "process" in globalThis && globalThis.process?.env?.NODE_ENV === "development";
3
+ function createSaveAction(schema, options) {
4
+ return async (_prevState, formData) => {
5
+ try {
6
+ if (options.checkAuth) {
7
+ const isAuthorized = await options.checkAuth();
8
+ if (!isAuthorized) {
9
+ return {
10
+ success: false,
11
+ errors: { formError: "Unauthorized" }
12
+ };
13
+ }
14
+ }
15
+ const rawData = parseFormDataToObject(formData);
16
+ if (isDevelopmentEnv) {
17
+ console.log("[litecms] Parsed form data:", JSON.stringify(rawData, null, 2));
18
+ }
19
+ const result = schema.safeParse(rawData);
20
+ if (!result.success) {
21
+ const fieldErrors = {};
22
+ for (const issue of result.error.issues) {
23
+ const path = issue.path.join(".");
24
+ if (!fieldErrors[path]) {
25
+ fieldErrors[path] = [];
26
+ }
27
+ fieldErrors[path].push(issue.message);
28
+ }
29
+ if (isDevelopmentEnv) {
30
+ console.log("[litecms] Validation failed:", result.error.issues);
31
+ }
32
+ return {
33
+ success: false,
34
+ errors: {
35
+ fieldErrors,
36
+ formError: `Validation failed: ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ")}`
37
+ }
38
+ };
39
+ }
40
+ await options.save(result.data);
41
+ if (options.revalidatePath && options.onRevalidate) {
42
+ options.onRevalidate(options.revalidatePath);
43
+ }
44
+ return {
45
+ success: true,
46
+ data: result.data
47
+ };
48
+ } catch (error) {
49
+ console.error("[litecms] Save action failed:", error);
50
+ const message = error instanceof Error ? error.message : "An unexpected error occurred";
51
+ return {
52
+ success: false,
53
+ errors: { formError: `Save failed: ${message}` }
54
+ };
55
+ }
56
+ };
57
+ }
58
+ function parseFormDataToObject(formData) {
59
+ const result = {};
60
+ for (const [key, value] of formData.entries()) {
61
+ if (value instanceof File)
62
+ continue;
63
+ const stringValue = value.toString();
64
+ let parsedValue = stringValue;
65
+ if (stringValue === "") {
66
+ parsedValue = undefined;
67
+ } else if (stringValue === "true" || stringValue === "on") {
68
+ parsedValue = true;
69
+ } else if (stringValue === "false") {
70
+ parsedValue = false;
71
+ } else if (key.endsWith("_number") || /^-?\d+(\.\d+)?$/.test(stringValue) && !key.toLowerCase().includes("href") && !key.toLowerCase().includes("url") && !key.toLowerCase().includes("link") && !key.toLowerCase().includes("value") && !key.toLowerCase().includes("label") && !key.toLowerCase().includes("title") && !key.toLowerCase().includes("name") && !key.toLowerCase().includes("text")) {
72
+ const num = Number(stringValue);
73
+ if (!isNaN(num)) {
74
+ parsedValue = num;
75
+ }
76
+ }
77
+ setValueByPath(result, key, parsedValue);
78
+ }
79
+ return result;
80
+ }
81
+ function setValueByPath(obj, path, value) {
82
+ const segments = path.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
83
+ let current = obj;
84
+ for (let i = 0;i < segments.length - 1; i++) {
85
+ const segment = segments[i];
86
+ const nextSegment = segments[i + 1];
87
+ const isNextArray = /^\d+$/.test(nextSegment);
88
+ if (!(segment in current)) {
89
+ current[segment] = isNextArray ? [] : {};
90
+ }
91
+ const currentValue = current[segment];
92
+ if (isNextArray && !Array.isArray(currentValue)) {
93
+ current[segment] = [];
94
+ } else if (!isNextArray && (Array.isArray(currentValue) || typeof currentValue !== "object")) {
95
+ current[segment] = {};
96
+ }
97
+ current = current[segment];
98
+ }
99
+ const lastSegment = segments[segments.length - 1];
100
+ if (/^\d+$/.test(lastSegment)) {
101
+ const index = parseInt(lastSegment, 10);
102
+ current[index] = value;
103
+ } else {
104
+ current[lastSegment] = value;
105
+ }
106
+ }
107
+ function parseFormData(schema, formData) {
108
+ const rawData = parseFormDataToObject(formData);
109
+ const result = schema.safeParse(rawData);
110
+ return result.success ? result.data : null;
111
+ }
112
+ export {
113
+ parseFormDataToObject,
114
+ parseFormData,
115
+ isDevelopmentEnv,
116
+ createSaveAction
117
+ };
@@ -0,0 +1,23 @@
1
+ import type { PageGroup } from '../admin';
2
+ import type { FieldGroupData } from '../components';
3
+ import type { FieldInfo } from '../schema';
4
+ import { type ClassValue } from 'clsx';
5
+ export declare function cn(...inputs: ClassValue[]): string;
6
+ export declare function groupPages(pages: Array<{
7
+ slug: string;
8
+ title: string;
9
+ group?: string;
10
+ icon?: string;
11
+ }>): PageGroup[];
12
+ /**
13
+ * Get nested error from react-hook-form errors object
14
+ */
15
+ export declare function getNestedError(errors: Record<string, unknown>, path: string): {
16
+ message?: string;
17
+ } | undefined;
18
+ /**
19
+ * Flatten a value into form field entries using bracket/dot notation.
20
+ */
21
+ export declare function flattenValue(key: string, value: unknown, result?: Array<[string, string]>): Array<[string, string]>;
22
+ export declare function groupFields(fields: FieldInfo[]): FieldGroupData[];
23
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/shared/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAG7C,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC;AAED,wBAAgB,UAAU,CACtB,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAC7E,SAAS,EAAE,CAoBb;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,EAAE,MAAM,GACb;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAalC;AAED;;GAEG;AACH,wBAAgB,YAAY,CACxB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,OAAO,EACd,MAAM,GAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,GACrC,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAkBzB;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,cAAc,EAAE,CAgBjE"}
@@ -0,0 +1,86 @@
1
+ import { S3Client, PutObjectCommand, ListObjectsV2Command, DeleteObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
2
+ /**
3
+ * Configuration for storage client
4
+ */
5
+ export type StorageConfig = {
6
+ /** S3-compatible endpoint URL (e.g., Minio endpoint) */
7
+ endpoint: string;
8
+ /** AWS region (defaults to 'us-east-1') */
9
+ region?: string;
10
+ /** Access key ID */
11
+ accessKeyId: string;
12
+ /** Secret access key */
13
+ secretAccessKey: string;
14
+ /** Bucket name */
15
+ bucket: string;
16
+ /** Use path-style URLs (required for Minio, defaults to true) */
17
+ forcePathStyle?: boolean;
18
+ /** Base URL path for serving files (defaults to '/api/storage/images') */
19
+ publicUrlBase?: string;
20
+ };
21
+ /**
22
+ * File info returned from list operations
23
+ */
24
+ export type FileInfo = {
25
+ key: string;
26
+ url: string;
27
+ lastModified?: Date;
28
+ size?: number;
29
+ };
30
+ /**
31
+ * Upload result
32
+ */
33
+ export type UploadResult = {
34
+ url: string;
35
+ key: string;
36
+ };
37
+ /**
38
+ * Storage client instance with all operations
39
+ */
40
+ export type StorageClient = {
41
+ /** Upload a file to storage */
42
+ uploadFile: (file: File, path?: string) => Promise<UploadResult>;
43
+ /** List files in storage */
44
+ listFiles: (prefix?: string) => Promise<FileInfo[]>;
45
+ /** Delete a file from storage */
46
+ deleteFile: (key: string) => Promise<void>;
47
+ /** Get file content as buffer */
48
+ getFile: (key: string) => Promise<{
49
+ buffer: ArrayBuffer;
50
+ contentType: string | undefined;
51
+ }>;
52
+ /** The underlying S3 client */
53
+ s3Client: S3Client;
54
+ /** The bucket name */
55
+ bucket: string;
56
+ /** The public URL base */
57
+ publicUrlBase: string;
58
+ };
59
+ /**
60
+ * Create a storage client with the given configuration.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * // In your app's lib/storage.ts
65
+ * import { createStorageClient } from 'litecms/storage';
66
+ *
67
+ * export const storage = createStorageClient({
68
+ * endpoint: process.env.STORAGE_URL!,
69
+ * accessKeyId: process.env.STORAGE_ACCESS_KEY!,
70
+ * secretAccessKey: process.env.STORAGE_SECRET_KEY!,
71
+ * bucket: process.env.STORAGE_BUCKET || 'mybucket',
72
+ * publicUrlBase: '/api/storage/images',
73
+ * });
74
+ *
75
+ * // Then use it
76
+ * const { url, key } = await storage.uploadFile(file);
77
+ * const files = await storage.listFiles('images/');
78
+ * await storage.deleteFile(key);
79
+ * ```
80
+ */
81
+ export declare function createStorageClient(config: StorageConfig): StorageClient;
82
+ /**
83
+ * Re-export S3 client and commands for advanced usage
84
+ */
85
+ export { S3Client, PutObjectCommand, ListObjectsV2Command, DeleteObjectCommand, GetObjectCommand };
86
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE7H;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IACxB,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oBAAoB;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,wBAAwB;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,IAAI,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IACxB,+BAA+B;IAC/B,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IACjE,4BAA4B;IAC5B,SAAS,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IACpD,iCAAiC;IACjC,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,iCAAiC;IACjC,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,MAAM,EAAE,WAAW,CAAC;QAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC,CAAC;IAC5F,+BAA+B;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,0BAA0B;IAC1B,aAAa,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,CAyGxE;AAED;;GAEG;AACH,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,CAAC"}
@@ -0,0 +1,86 @@
1
+ // src/storage/index.ts
2
+ import { S3Client, PutObjectCommand, ListObjectsV2Command, DeleteObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
3
+ function createStorageClient(config) {
4
+ const {
5
+ endpoint,
6
+ region = "us-east-1",
7
+ accessKeyId,
8
+ secretAccessKey,
9
+ bucket,
10
+ forcePathStyle = true,
11
+ publicUrlBase = "/api/storage/images"
12
+ } = config;
13
+ const s3Client = new S3Client({
14
+ endpoint,
15
+ region,
16
+ credentials: {
17
+ accessKeyId,
18
+ secretAccessKey
19
+ },
20
+ forcePathStyle
21
+ });
22
+ async function uploadFile(file, path) {
23
+ const buffer = new Uint8Array(await file.arrayBuffer());
24
+ const timestamp = Date.now();
25
+ const sanitizedName = file.name.replace(/[^a-zA-Z0-9.-]/g, "_");
26
+ const key = path || `images/${timestamp}-${sanitizedName}`;
27
+ const command = new PutObjectCommand({
28
+ Bucket: bucket,
29
+ Key: key,
30
+ Body: buffer,
31
+ ContentType: file.type
32
+ });
33
+ await s3Client.send(command);
34
+ const url = `${publicUrlBase}/${key}`;
35
+ return { url, key };
36
+ }
37
+ async function listFiles(prefix = "images/") {
38
+ const command = new ListObjectsV2Command({
39
+ Bucket: bucket,
40
+ Prefix: prefix
41
+ });
42
+ const response = await s3Client.send(command);
43
+ return (response.Contents || []).map((item) => ({
44
+ key: item.Key,
45
+ url: `${publicUrlBase}/${item.Key}`,
46
+ lastModified: item.LastModified,
47
+ size: item.Size
48
+ }));
49
+ }
50
+ async function deleteFile(key) {
51
+ const command = new DeleteObjectCommand({
52
+ Bucket: bucket,
53
+ Key: key
54
+ });
55
+ await s3Client.send(command);
56
+ }
57
+ async function getFile(key) {
58
+ const command = new GetObjectCommand({
59
+ Bucket: bucket,
60
+ Key: key
61
+ });
62
+ const response = await s3Client.send(command);
63
+ const buffer = new Uint8Array(await response.Body.transformToByteArray());
64
+ return {
65
+ buffer: buffer.buffer,
66
+ contentType: response.ContentType
67
+ };
68
+ }
69
+ return {
70
+ uploadFile,
71
+ listFiles,
72
+ deleteFile,
73
+ getFile,
74
+ s3Client,
75
+ bucket,
76
+ publicUrlBase
77
+ };
78
+ }
79
+ export {
80
+ createStorageClient,
81
+ S3Client,
82
+ PutObjectCommand,
83
+ ListObjectsV2Command,
84
+ GetObjectCommand,
85
+ DeleteObjectCommand
86
+ };
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/stores/index.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,90 @@
1
+ {
2
+ "name": "litecms",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "author": "Timo Weiss",
7
+ "description": "A super lightweight CMS for Next.js",
8
+ "keywords": [
9
+ "cms",
10
+ "nextjs",
11
+ "react",
12
+ "admin",
13
+ "zod",
14
+ "forms",
15
+ "typescript"
16
+ ],
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/exddc/litecms"
20
+ },
21
+ "homepage": "https://github.com/exddc/litecms",
22
+ "bugs": {
23
+ "url": "https://github.com/exddc/litecms/issues"
24
+ },
25
+ "files": ["dist"],
26
+ "main": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "default": "./dist/index.js"
32
+ },
33
+ "./server": {
34
+ "types": "./dist/server/index.d.ts",
35
+ "default": "./dist/server/index.js"
36
+ },
37
+ "./components": {
38
+ "types": "./dist/components/index.d.ts",
39
+ "default": "./dist/components/index.js"
40
+ },
41
+ "./schema": {
42
+ "types": "./dist/schema/index.d.ts",
43
+ "default": "./dist/schema/index.js"
44
+ },
45
+ "./admin": {
46
+ "types": "./dist/admin/exports.d.ts",
47
+ "default": "./dist/admin/exports.js"
48
+ },
49
+ "./admin/config": {
50
+ "types": "./dist/admin/config.d.ts",
51
+ "default": "./dist/admin/config.js"
52
+ },
53
+ "./storage": {
54
+ "types": "./dist/storage/index.d.ts",
55
+ "default": "./dist/storage/index.js"
56
+ }
57
+ },
58
+ "peerDependencies": {
59
+ "react": ">=18",
60
+ "react-dom": ">=18",
61
+ "next": ">=14",
62
+ "@aws-sdk/client-s3": ">=3"
63
+ },
64
+ "peerDependenciesMeta": {
65
+ "@aws-sdk/client-s3": {
66
+ "optional": true
67
+ }
68
+ },
69
+ "dependencies": {
70
+ "@hookform/resolvers": "^5.2.2",
71
+ "clsx": "^2.1.1",
72
+ "react-hook-form": "^7.71.0",
73
+ "tailwind-merge": "^3.4.0",
74
+ "zod": "^4.3.5"
75
+ },
76
+ "devDependencies": {
77
+ "@types/node": "^20",
78
+ "@types/react": "^18",
79
+ "@types/react-dom": "^18",
80
+ "typescript": "^5"
81
+ },
82
+ "scripts": {
83
+ "build": "bun run build:js && bun run build:types && bun run build:fix",
84
+ "build:js": "bun build src/index.ts src/server/index.ts src/components/index.ts src/schema/index.ts src/admin/exports.ts src/admin/config.ts src/storage/index.ts --outdir dist --target node --format esm --external react --external react-dom --external next --external @aws-sdk/client-s3 --splitting",
85
+ "build:types": "tsc -p tsconfig.build.json",
86
+ "build:fix": "node scripts/fix-build.cjs",
87
+ "dev": "bun run build --watch",
88
+ "test": "bun test"
89
+ }
90
+ }