@africode/core 5.0.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 (136) hide show
  1. package/AFRICODE_FRAMEWORK_GUIDE.md +707 -0
  2. package/LICENSE +623 -0
  3. package/README.md +442 -0
  4. package/bin/africode.js +73 -0
  5. package/bin/africode.js.1758507140 +343 -0
  6. package/bin/cli.ts +83 -0
  7. package/bin/create-africode.js +158 -0
  8. package/bin/scaffold.ts +219 -0
  9. package/components/accordion.js +183 -0
  10. package/components/alert.js +131 -0
  11. package/components/auth.js +172 -0
  12. package/components/avatar.js +117 -0
  13. package/components/badge.js +104 -0
  14. package/components/base.d.ts +139 -0
  15. package/components/base.js +184 -0
  16. package/components/button.js +164 -0
  17. package/components/card.js +137 -0
  18. package/components/cultural-card.js +243 -0
  19. package/components/divider.js +83 -0
  20. package/components/dropdown.js +171 -0
  21. package/components/error-boundary.js +155 -0
  22. package/components/form.js +131 -0
  23. package/components/grid.js +273 -0
  24. package/components/hero.js +138 -0
  25. package/components/icon.js +36 -0
  26. package/components/index.js +57 -0
  27. package/components/input.js +256 -0
  28. package/components/kanga-card.js +185 -0
  29. package/components/language-switcher.js +108 -0
  30. package/components/loader.js +80 -0
  31. package/components/modal.js +262 -0
  32. package/components/motion.js +84 -0
  33. package/components/navbar.js +236 -0
  34. package/components/pattern-showcase.js +225 -0
  35. package/components/progress.js +134 -0
  36. package/components/react.js +111 -0
  37. package/components/section.js +54 -0
  38. package/components/select.js +322 -0
  39. package/components/sidebar.js +180 -0
  40. package/components/skeleton.js +85 -0
  41. package/components/table.js +181 -0
  42. package/components/tabs.js +202 -0
  43. package/components/theme-toggle.js +82 -0
  44. package/components/toast.js +139 -0
  45. package/components/tooltip.js +167 -0
  46. package/core/a2ui-schema-manager.js +344 -0
  47. package/core/a2ui.js +431 -0
  48. package/core/bun-runtime.js +799 -0
  49. package/core/cli/commands/add.js +23 -0
  50. package/core/cli/commands/audit.js +58 -0
  51. package/core/cli/commands/build.js +137 -0
  52. package/core/cli/commands/create-plugin.js +241 -0
  53. package/core/cli/commands/dev.js +228 -0
  54. package/core/cli/commands/lint.js +23 -0
  55. package/core/cli/commands/test.js +34 -0
  56. package/core/cli/migrator.js +71 -0
  57. package/core/cli/ui.js +46 -0
  58. package/core/compliance.js +628 -0
  59. package/core/config.js +263 -0
  60. package/core/db-advanced.js +481 -0
  61. package/core/db.js +284 -0
  62. package/core/enhanced-hmr.js +404 -0
  63. package/core/errors.js +222 -0
  64. package/core/file-router.js +290 -0
  65. package/core/heartbeat.js +64 -0
  66. package/core/hmr-client.js +204 -0
  67. package/core/hmr.js +196 -0
  68. package/core/html.d.ts +116 -0
  69. package/core/html.js +160 -0
  70. package/core/hydration.js +52 -0
  71. package/core/lipa-namba-journey.js +572 -0
  72. package/core/motion.js +106 -0
  73. package/core/nida-cig-middleware.js +455 -0
  74. package/core/patterns.d.ts +124 -0
  75. package/core/patterns.js +833 -0
  76. package/core/plugins/index.js +312 -0
  77. package/core/router.js +387 -0
  78. package/core/sdk-client.js +62 -0
  79. package/core/sdk.d.ts +133 -0
  80. package/core/sdk.js +123 -0
  81. package/core/seo.js +76 -0
  82. package/core/server/auth-endpoints.js +339 -0
  83. package/core/server/auth.js +180 -0
  84. package/core/server/csrf.js +206 -0
  85. package/core/server/db.js +39 -0
  86. package/core/server/middleware.js +324 -0
  87. package/core/server/rate-limit.js +238 -0
  88. package/core/server/render.js +69 -0
  89. package/core/server/router.js +120 -0
  90. package/core/shim.js +28 -0
  91. package/core/state.d.ts +86 -0
  92. package/core/state.js +242 -0
  93. package/core/store.d.ts +122 -0
  94. package/core/store.js +61 -0
  95. package/core/validation.d.ts +233 -0
  96. package/core/validation.js +590 -0
  97. package/core/websocket.js +639 -0
  98. package/dist/africode.js +2905 -0
  99. package/dist/africode.js.map +61 -0
  100. package/dist/build-info.json +23 -0
  101. package/dist/components.js +2888 -0
  102. package/dist/components.js.map +58 -0
  103. package/dist/styles/africanity.css +322 -0
  104. package/dist/styles/typography.css +141 -0
  105. package/docs/IDE-Guide.md +50 -0
  106. package/package.json +110 -0
  107. package/src/index.ts +196 -0
  108. package/styles/africanity.css +322 -0
  109. package/styles/typography.css +141 -0
  110. package/templates/starter/.env.example +15 -0
  111. package/templates/starter/africode.config.js +40 -0
  112. package/templates/starter/package.json +14 -0
  113. package/templates/starter/src/pages/index.html +46 -0
  114. package/templates/starter/src/pages/index.js +32 -0
  115. package/templates/starter/src/styles/main.css +4 -0
  116. package/templates/starter-3d/.env.example +7 -0
  117. package/templates/starter-3d/africode.config.js +29 -0
  118. package/templates/starter-3d/components/af-model-viewer.js +125 -0
  119. package/templates/starter-3d/package.json +15 -0
  120. package/templates/starter-3d/src/pages/index.html +46 -0
  121. package/templates/starter-3d/src/pages/index.js +50 -0
  122. package/templates/starter-3d/src/styles/main.css +4 -0
  123. package/templates/starter-react/.env.example +15 -0
  124. package/templates/starter-react/africode.config.js +40 -0
  125. package/templates/starter-react/package.json +16 -0
  126. package/templates/starter-react/src/pages/index.html +46 -0
  127. package/templates/starter-react/src/pages/index.js +68 -0
  128. package/templates/starter-react/src/styles/main.css +4 -0
  129. package/templates/starter-tailwind/.env.example +15 -0
  130. package/templates/starter-tailwind/africode.config.js +40 -0
  131. package/templates/starter-tailwind/package.json +20 -0
  132. package/templates/starter-tailwind/src/pages/index.html +46 -0
  133. package/templates/starter-tailwind/src/pages/index.js +37 -0
  134. package/templates/starter-tailwind/src/styles/main.css +4 -0
  135. package/templates/starter-tailwind/src/styles/tailwind.css +1 -0
  136. package/templates/starter-tailwind/src/tailwind-loader.js +30 -0
package/core/state.js ADDED
@@ -0,0 +1,242 @@
1
+ /**
2
+ * AfriCode Reactive State System
3
+ *
4
+ * A lightweight, Proxy-based reactive state management system
5
+ * following the African Fractal philosophy - simple at the core,
6
+ * powerful through composition.
7
+ *
8
+ * @module core/state
9
+ */
10
+
11
+ // Store for all subscribers per state
12
+ const stateSubscribers = new WeakMap();
13
+
14
+ // Track nested proxies to avoid duplicates
15
+ const proxyCache = new WeakMap();
16
+
17
+ /**
18
+ * Creates a deeply reactive state object using Proxy
19
+ * State changes automatically notify all subscribers
20
+ *
21
+ * @param {Object} initialState - The initial state object
22
+ * @returns {Proxy} A reactive proxy of the state
23
+ *
24
+ * @example
25
+ * const state = createReactiveState({ count: 0, user: { name: 'Amara' } });
26
+ * subscribe(() => console.log('State changed!'));
27
+ * state.count++; // Logs: "State changed!"
28
+ */
29
+ export function createReactiveState(initialState) {
30
+ // Return cached proxy if already created
31
+ if (proxyCache.has(initialState)) {
32
+ return proxyCache.get(initialState);
33
+ }
34
+
35
+ const handler = {
36
+ get(target, property, receiver) {
37
+ const value = Reflect.get(target, property, receiver);
38
+
39
+ // Recursively wrap nested objects
40
+ if (value !== null && typeof value === 'object') {
41
+ return createReactiveState(value);
42
+ }
43
+
44
+ return value;
45
+ },
46
+
47
+ set(target, property, value, receiver) {
48
+ const oldValue = Reflect.get(target, property, receiver);
49
+
50
+ // Only notify if value actually changed
51
+ if (oldValue !== value) {
52
+ const result = Reflect.set(target, property, value, receiver);
53
+
54
+ if (result) {
55
+ notifySubscribers(receiver, value, oldValue, property);
56
+ }
57
+
58
+ return result;
59
+ }
60
+
61
+ return true;
62
+ },
63
+
64
+ deleteProperty(target, property) {
65
+ const oldValue = Reflect.get(target, property);
66
+ const result = Reflect.deleteProperty(target, property);
67
+
68
+ if (result) {
69
+ const proxy = proxyCache.get(target);
70
+ notifySubscribers(proxy, undefined, oldValue, property);
71
+ }
72
+
73
+ return result;
74
+ }
75
+ };
76
+
77
+ const proxy = new Proxy(initialState, handler);
78
+ proxyCache.set(initialState, proxy);
79
+
80
+ return proxy;
81
+ }
82
+
83
+ /**
84
+ * Subscribe to state changes
85
+ *
86
+ * @param {Proxy} state - The reactive state to subscribe to
87
+ * @param {Function} callback - Function to call when state changes
88
+ * @returns {Function} Unsubscribe function
89
+ *
90
+ * @example
91
+ * const state = createReactiveState({ count: 0 });
92
+ * const unsubscribe = subscribe(state, () => updateUI());
93
+ * // Later: unsubscribe();
94
+ */
95
+ export function subscribe(state, callback) {
96
+ if (!stateSubscribers.has(state)) {
97
+ stateSubscribers.set(state, new Set());
98
+ }
99
+
100
+ const subscribers = stateSubscribers.get(state);
101
+ subscribers.add(callback);
102
+
103
+ // Return unsubscribe function
104
+ return () => {
105
+ subscribers.delete(callback);
106
+ };
107
+ }
108
+
109
+ /**
110
+ * Notify all subscribers of state change
111
+ * Uses microtask queue for batched updates
112
+ */
113
+ function notifySubscribers(state, newValue, oldValue, property) {
114
+ // For testing, make synchronous. In production, use microtask
115
+ const subscribers = stateSubscribers.get(state);
116
+ if (!subscribers) {return;}
117
+
118
+ subscribers.forEach(callback => {
119
+ try {
120
+ callback(newValue, oldValue, property);
121
+ } catch (error) {
122
+ console.error('[AfriCode State] Subscriber error:', error);
123
+ }
124
+ });
125
+ }
126
+
127
+ /**
128
+ * Create a computed value that updates when dependencies change
129
+ *
130
+ * @param {Function} computeFn - Function that computes the derived value
131
+ * @returns {Function} Getter function for the computed value
132
+ *
133
+ * @example
134
+ * const state = createReactiveState({ price: 100, quantity: 2 });
135
+ * const total = computed(() => state.price * state.quantity);
136
+ * console.log(total()); // 200
137
+ */
138
+ export function computed(computeFn) {
139
+ let cachedValue;
140
+ let isDirty = true;
141
+
142
+ subscribe(() => {
143
+ isDirty = true;
144
+ });
145
+
146
+ return () => {
147
+ if (isDirty) {
148
+ cachedValue = computeFn();
149
+ isDirty = false;
150
+ }
151
+ return cachedValue;
152
+ };
153
+ }
154
+
155
+ /**
156
+ * Batch multiple state changes into a single notification
157
+ *
158
+ * @param {Function} updateFn - Function containing multiple state updates
159
+ *
160
+ * @example
161
+ * batch(() => {
162
+ * state.name = 'Kwame';
163
+ * state.age = 25;
164
+ * state.country = 'Ghana';
165
+ * }); // Only one notification
166
+ */
167
+ export function batch(state, updateFn) {
168
+ const subscribers = stateSubscribers.get(state) || new Set();
169
+ const originalSubscribers = new Set(subscribers);
170
+ subscribers.clear();
171
+
172
+ try {
173
+ updateFn();
174
+ } finally {
175
+ originalSubscribers.forEach(sub => subscribers.add(sub));
176
+ notifySubscribers(state);
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Create a simple signal (getter/setter pair)
182
+ *
183
+ * @param {*} initialValue - The initial value
184
+ * @returns {[Function, Function]} [getter, setter]
185
+ *
186
+ * @example
187
+ * const [count, setCount] = createSignal(0);
188
+ * console.log(count()); // 0
189
+ * setCount(5);
190
+ * console.log(count()); // 5
191
+ */
192
+ export function createSignal(initialValue) {
193
+ let value = initialValue;
194
+ const signalSubscribers = new Set();
195
+
196
+ const getter = () => {
197
+ return value;
198
+ };
199
+
200
+ const setter = (newValue) => {
201
+ if (value !== newValue) {
202
+ value = typeof newValue === 'function' ? newValue(value) : newValue;
203
+ signalSubscribers.forEach(fn => fn());
204
+ notifySubscribers();
205
+ }
206
+ };
207
+
208
+ // Allow effects to subscribe to this signal
209
+ getter.subscribe = (fn) => {
210
+ signalSubscribers.add(fn);
211
+ return () => signalSubscribers.delete(fn);
212
+ };
213
+
214
+ return [getter, setter];
215
+ }
216
+
217
+ /**
218
+ * Create an effect that runs when reactive dependencies change
219
+ *
220
+ * @param {Function} effectFn - The effect function to run
221
+ * @returns {Function} Cleanup function
222
+ *
223
+ * @example
224
+ * const [count, setCount] = createSignal(0);
225
+ * createEffect(() => console.log('Count is:', count()));
226
+ */
227
+ export function createEffect(effectFn) {
228
+ // Run immediately
229
+ effectFn();
230
+ // Subscribe to future changes
231
+ return subscribe(effectFn);
232
+ }
233
+
234
+ // Default export for convenience
235
+ export default {
236
+ createReactiveState,
237
+ subscribe,
238
+ computed,
239
+ batch,
240
+ createSignal,
241
+ createEffect
242
+ };
@@ -0,0 +1,122 @@
1
+ /**
2
+ * AfriCode Store (Global State) - TypeScript Definitions
3
+ */
4
+
5
+ /**
6
+ * Global store interface
7
+ */
8
+ export interface GlobalStore {
9
+ /** Current theme (light/dark/custom) */
10
+ theme: string;
11
+
12
+ /** Current language code (en/sw/fr/ar/am/yo) */
13
+ language: string;
14
+
15
+ /** Current user (if authenticated) */
16
+ user?: {
17
+ id: string;
18
+ name: string;
19
+ email: string;
20
+ roles?: string[];
21
+ };
22
+
23
+ /** App-level notifications */
24
+ notifications?: Array<{
25
+ id: string;
26
+ message: string;
27
+ type: 'info' | 'success' | 'warning' | 'error';
28
+ timestamp: number;
29
+ }>;
30
+
31
+ /** Global loading state */
32
+ isLoading?: boolean;
33
+
34
+ /** Global error state */
35
+ error?: Error | null;
36
+
37
+ /** Additional properties */
38
+ [key: string]: any;
39
+ }
40
+
41
+ /**
42
+ * Store actions interface
43
+ */
44
+ export interface StoreActions {
45
+ /** Set theme */
46
+ setTheme(theme: string): void;
47
+
48
+ /** Set language */
49
+ setLanguage(language: string): void;
50
+
51
+ /** Set user */
52
+ setUser(user: GlobalStore['user']): void;
53
+
54
+ /** Clear user (logout) */
55
+ clearUser(): void;
56
+
57
+ /** Show notification */
58
+ notify(message: string, type?: string): void;
59
+
60
+ /** Set loading state */
61
+ setLoading(isLoading: boolean): void;
62
+
63
+ /** Set error */
64
+ setError(error: Error | null): void;
65
+
66
+ /** Clear error */
67
+ clearError(): void;
68
+
69
+ /** Additional actions */
70
+ [key: string]: any;
71
+ }
72
+
73
+ /**
74
+ * Global reactive store object
75
+ */
76
+ export const store: GlobalStore;
77
+
78
+ /**
79
+ * Store action methods
80
+ */
81
+ export const actions: StoreActions;
82
+
83
+ /**
84
+ * Subscribe to store changes
85
+ */
86
+ export function watchStore<K extends keyof GlobalStore>(
87
+ key: K,
88
+ callback: (value: GlobalStore[K]) => void
89
+ ): () => void; // Unsubscribe function
90
+
91
+ /**
92
+ * Watch for user changes
93
+ */
94
+ export function watchUser(
95
+ callback: (user: GlobalStore['user']) => void
96
+ ): () => void;
97
+
98
+ /**
99
+ * Watch for theme changes
100
+ */
101
+ export function watchTheme(
102
+ callback: (theme: string) => void
103
+ ): () => void;
104
+
105
+ /**
106
+ * Watch for language changes
107
+ */
108
+ export function watchLanguage(
109
+ callback: (lang: string) => void
110
+ ): () => void;
111
+
112
+ /**
113
+ * Reset store to initial state
114
+ */
115
+ export function resetStore(): void;
116
+
117
+ /**
118
+ * Get store snapshot
119
+ */
120
+ export function getStoreSnapshot(): Readonly<GlobalStore>;
121
+
122
+ export default store;
package/core/store.js ADDED
@@ -0,0 +1,61 @@
1
+ /**
2
+ * AfriCode Global Store
3
+ *
4
+ * Centralized state container for the application UI.
5
+ * Powered by the core reactive engine.
6
+ *
7
+ * @module core/store
8
+ */
9
+
10
+ import { createReactiveState } from './state.js';
11
+
12
+ // Initial default state
13
+ const initialState = {
14
+ theme: 'light', // Top-level theme
15
+ language: 'en', // Top-level language
16
+ ui: {
17
+ sidebarCollapsed: false,
18
+ theme: 'dark', // 'dark', 'light', 'system'
19
+ activePage: 'home'
20
+ },
21
+ user: {
22
+ name: 'Guest',
23
+ permissions: []
24
+ },
25
+ config: {
26
+ animationsEnabled: true,
27
+ soundEnabled: false
28
+ }
29
+ };
30
+
31
+ // Create the global store
32
+ export const store = createReactiveState(initialState);
33
+
34
+ // Actions (Helper functions to modify state)
35
+ export const actions = {
36
+ setTheme(theme) {
37
+ if (['dark', 'light', 'system'].includes(theme)) {
38
+ store.theme = theme;
39
+ // Also update ui.theme for consistency
40
+ store.ui.theme = theme;
41
+ }
42
+ },
43
+
44
+ setLanguage(language) {
45
+ store.language = language;
46
+ },
47
+
48
+ toggleSidebar(collapsed) {
49
+ if (typeof collapsed === 'boolean') {
50
+ store.ui.sidebarCollapsed = collapsed;
51
+ } else {
52
+ store.ui.sidebarCollapsed = !store.ui.sidebarCollapsed;
53
+ }
54
+ },
55
+
56
+ setActivePage(pageName) {
57
+ store.ui.activePage = pageName;
58
+ }
59
+ };
60
+
61
+ export default { store, actions };
@@ -0,0 +1,233 @@
1
+ /**
2
+ * AfriCode Validation System Type Definitions
3
+ *
4
+ * Philosophy: Strict by default. Convenient by composition. Secure early.
5
+ *
6
+ * TypeScript definitions for Zod-based validation schemas, AfriFieldBuilder,
7
+ * afri namespace, and normalizeInput utility.
8
+ */
9
+
10
+ import { z } from 'zod';
11
+
12
+ // ─────────────────────────────────────────────────────────────
13
+ // Result Types
14
+ // ─────────────────────────────────────────────────────────────
15
+
16
+ export interface ValidationResult<T = any> {
17
+ success: boolean;
18
+ data?: T;
19
+ errors?: Record<string, string>;
20
+ }
21
+
22
+ export interface FieldValidationResult {
23
+ success: boolean;
24
+ error?: string;
25
+ }
26
+
27
+ // ─────────────────────────────────────────────────────────────
28
+ // Schemas
29
+ // ─────────────────────────────────────────────────────────────
30
+
31
+ export interface ValidationSchemas {
32
+ login: z.ZodObject<{
33
+ email: z.ZodString;
34
+ password: z.ZodString;
35
+ }>;
36
+
37
+ register: z.ZodObject<{
38
+ name: z.ZodString;
39
+ email: z.ZodString;
40
+ password: z.ZodString;
41
+ confirmPassword: z.ZodString;
42
+ }>;
43
+
44
+ contact: z.ZodObject<{
45
+ name: z.ZodString;
46
+ email: z.ZodString;
47
+ message: z.ZodString;
48
+ }>;
49
+
50
+ /** User profile update — avatar_url is a strict URL, nullable */
51
+ userProfile: z.ZodObject<{
52
+ full_name: z.ZodOptional<z.ZodString>;
53
+ bio: z.ZodOptional<z.ZodNullable<z.ZodString>>;
54
+ avatar_url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
55
+ theme: z.ZodOptional<z.ZodEnum<['africanity', 'dark', 'light', 'system']>>;
56
+ language: z.ZodOptional<z.ZodString>;
57
+ }>;
58
+
59
+ /** Project creation/update — repository_url, demo_url are strict URLs, nullable */
60
+ project: z.ZodObject<{
61
+ name: z.ZodString;
62
+ description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
63
+ repository_url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
64
+ demo_url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
65
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
66
+ is_public: z.ZodOptional<z.ZodBoolean>;
67
+ }>;
68
+
69
+ email: z.ZodString;
70
+ password: z.ZodString;
71
+ phone: z.ZodString;
72
+ url: z.ZodString;
73
+ required: z.ZodString;
74
+ positiveNumber: z.ZodNumber;
75
+ integer: z.ZodNumber;
76
+ futureDate: z.ZodDate;
77
+ imageFile: z.ZodType<File>;
78
+ }
79
+
80
+ // ─────────────────────────────────────────────────────────────
81
+ // Validation Class
82
+ // ─────────────────────────────────────────────────────────────
83
+
84
+ export declare class Validation {
85
+ static validate<T>(
86
+ schema: z.ZodSchema<T>,
87
+ data: unknown
88
+ ): ValidationResult<T>;
89
+
90
+ static validateField(
91
+ schema: z.ZodSchema,
92
+ value: unknown
93
+ ): FieldValidationResult;
94
+
95
+ static getFieldError(
96
+ validationResult: ValidationResult,
97
+ fieldPath: string
98
+ ): string | null;
99
+
100
+ static hasFieldError(
101
+ validationResult: ValidationResult,
102
+ fieldPath: string
103
+ ): boolean;
104
+ }
105
+
106
+ // ─────────────────────────────────────────────────────────────
107
+ // Rules
108
+ // ─────────────────────────────────────────────────────────────
109
+
110
+ export interface ValidationRules {
111
+ required: (message?: string) => z.ZodString;
112
+ email: (message?: string) => z.ZodString;
113
+ minLength: (min: number, message?: string) => z.ZodString;
114
+ maxLength: (max: number, message?: string) => z.ZodString;
115
+ pattern: (regex: RegExp, message: string) => z.ZodString;
116
+ numeric: (message?: string) => z.ZodString;
117
+ phone: (message?: string) => z.ZodString;
118
+ url: (message?: string) => z.ZodString;
119
+ }
120
+
121
+ // ─────────────────────────────────────────────────────────────
122
+ // AfriFieldBuilder — Chainable field schema builder
123
+ //
124
+ // Strict by default. Convenient by composition.
125
+ // ─────────────────────────────────────────────────────────────
126
+
127
+ export interface AfriFieldMeta {
128
+ fieldType: string;
129
+ isNullable: boolean;
130
+ isOptional: boolean;
131
+ emptyAsNull: boolean;
132
+ }
133
+
134
+ /**
135
+ * Chainable field builder for AfriCode validation schemas.
136
+ * Wraps Zod with framework-level semantics.
137
+ *
138
+ * .emptyAsNull() does NOT alter the Zod schema itself.
139
+ * It marks the field for preprocessing via normalizeInput().
140
+ */
141
+ export declare class AfriFieldBuilder {
142
+ constructor(baseSchema: z.ZodSchema, fieldType?: string);
143
+
144
+ /** Allow null as a valid value */
145
+ nullable(): this;
146
+
147
+ /** Allow undefined (field can be omitted) */
148
+ optional(): this;
149
+
150
+ /**
151
+ * Mark this field for empty-string-to-null conversion.
152
+ * Requires .nullable() to have been called first.
153
+ * The conversion happens in normalizeInput(), not in the Zod schema.
154
+ */
155
+ emptyAsNull(): this;
156
+
157
+ /** Set a custom error message */
158
+ message(message: string): this;
159
+
160
+ /** Build the final Zod schema */
161
+ build(): z.ZodSchema;
162
+
163
+ /** Get the metadata for this field (used by normalizeInput) */
164
+ getMeta(): AfriFieldMeta;
165
+ }
166
+
167
+ // ─────────────────────────────────────────────────────────────
168
+ // afri namespace — Field factory
169
+ // ─────────────────────────────────────────────────────────────
170
+
171
+ export interface AfriNamespace {
172
+ /** Create a strict URL field builder. Empty strings always rejected at core. */
173
+ url(message?: string): AfriFieldBuilder;
174
+
175
+ /** Create a strict email field builder. */
176
+ email(message?: string): AfriFieldBuilder;
177
+
178
+ /** Create a string field builder. */
179
+ string(opts?: { min?: number; max?: number; message?: string }): AfriFieldBuilder;
180
+
181
+ /** Create a number field builder. */
182
+ number(opts?: { min?: number; max?: number; int?: boolean }): AfriFieldBuilder;
183
+
184
+ /** Create a phone field builder. */
185
+ phone(message?: string): AfriFieldBuilder;
186
+ }
187
+
188
+ export declare const afri: AfriNamespace;
189
+
190
+ // ─────────────────────────────────────────────────────────────
191
+ // Preprocessing Utilities
192
+ // ─────────────────────────────────────────────────────────────
193
+
194
+ /**
195
+ * Normalize input data before validation.
196
+ * Examines field metadata from AfriFieldBuilder instances and applies
197
+ * transformations like converting empty strings to null for .emptyAsNull() fields.
198
+ *
199
+ * Separation of concerns:
200
+ * - normalizeInput() = input adapter (flexible)
201
+ * - Validation.validate() = core validation (strict)
202
+ */
203
+ export declare function normalizeInput(
204
+ fieldBuilders: Record<string, AfriFieldBuilder>,
205
+ data: Record<string, unknown>
206
+ ): Record<string, unknown>;
207
+
208
+ /**
209
+ * Build a Zod object schema from a map of AfriFieldBuilder instances.
210
+ * Convenience method to go from builders → Zod schema in one step.
211
+ */
212
+ export declare function buildSchema(
213
+ fieldBuilders: Record<string, AfriFieldBuilder | z.ZodSchema>
214
+ ): z.ZodObject<any>;
215
+
216
+ // ─────────────────────────────────────────────────────────────
217
+ // Exports
218
+ // ─────────────────────────────────────────────────────────────
219
+
220
+ export declare const schemas: ValidationSchemas;
221
+ export declare const rules: ValidationRules;
222
+
223
+ declare const validation: {
224
+ schemas: ValidationSchemas;
225
+ Validation: typeof Validation;
226
+ rules: ValidationRules;
227
+ afri: AfriNamespace;
228
+ AfriFieldBuilder: typeof AfriFieldBuilder;
229
+ normalizeInput: typeof normalizeInput;
230
+ buildSchema: typeof buildSchema;
231
+ };
232
+
233
+ export default validation;