@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.
- package/AFRICODE_FRAMEWORK_GUIDE.md +707 -0
- package/LICENSE +623 -0
- package/README.md +442 -0
- package/bin/africode.js +73 -0
- package/bin/africode.js.1758507140 +343 -0
- package/bin/cli.ts +83 -0
- package/bin/create-africode.js +158 -0
- package/bin/scaffold.ts +219 -0
- package/components/accordion.js +183 -0
- package/components/alert.js +131 -0
- package/components/auth.js +172 -0
- package/components/avatar.js +117 -0
- package/components/badge.js +104 -0
- package/components/base.d.ts +139 -0
- package/components/base.js +184 -0
- package/components/button.js +164 -0
- package/components/card.js +137 -0
- package/components/cultural-card.js +243 -0
- package/components/divider.js +83 -0
- package/components/dropdown.js +171 -0
- package/components/error-boundary.js +155 -0
- package/components/form.js +131 -0
- package/components/grid.js +273 -0
- package/components/hero.js +138 -0
- package/components/icon.js +36 -0
- package/components/index.js +57 -0
- package/components/input.js +256 -0
- package/components/kanga-card.js +185 -0
- package/components/language-switcher.js +108 -0
- package/components/loader.js +80 -0
- package/components/modal.js +262 -0
- package/components/motion.js +84 -0
- package/components/navbar.js +236 -0
- package/components/pattern-showcase.js +225 -0
- package/components/progress.js +134 -0
- package/components/react.js +111 -0
- package/components/section.js +54 -0
- package/components/select.js +322 -0
- package/components/sidebar.js +180 -0
- package/components/skeleton.js +85 -0
- package/components/table.js +181 -0
- package/components/tabs.js +202 -0
- package/components/theme-toggle.js +82 -0
- package/components/toast.js +139 -0
- package/components/tooltip.js +167 -0
- package/core/a2ui-schema-manager.js +344 -0
- package/core/a2ui.js +431 -0
- package/core/bun-runtime.js +799 -0
- package/core/cli/commands/add.js +23 -0
- package/core/cli/commands/audit.js +58 -0
- package/core/cli/commands/build.js +137 -0
- package/core/cli/commands/create-plugin.js +241 -0
- package/core/cli/commands/dev.js +228 -0
- package/core/cli/commands/lint.js +23 -0
- package/core/cli/commands/test.js +34 -0
- package/core/cli/migrator.js +71 -0
- package/core/cli/ui.js +46 -0
- package/core/compliance.js +628 -0
- package/core/config.js +263 -0
- package/core/db-advanced.js +481 -0
- package/core/db.js +284 -0
- package/core/enhanced-hmr.js +404 -0
- package/core/errors.js +222 -0
- package/core/file-router.js +290 -0
- package/core/heartbeat.js +64 -0
- package/core/hmr-client.js +204 -0
- package/core/hmr.js +196 -0
- package/core/html.d.ts +116 -0
- package/core/html.js +160 -0
- package/core/hydration.js +52 -0
- package/core/lipa-namba-journey.js +572 -0
- package/core/motion.js +106 -0
- package/core/nida-cig-middleware.js +455 -0
- package/core/patterns.d.ts +124 -0
- package/core/patterns.js +833 -0
- package/core/plugins/index.js +312 -0
- package/core/router.js +387 -0
- package/core/sdk-client.js +62 -0
- package/core/sdk.d.ts +133 -0
- package/core/sdk.js +123 -0
- package/core/seo.js +76 -0
- package/core/server/auth-endpoints.js +339 -0
- package/core/server/auth.js +180 -0
- package/core/server/csrf.js +206 -0
- package/core/server/db.js +39 -0
- package/core/server/middleware.js +324 -0
- package/core/server/rate-limit.js +238 -0
- package/core/server/render.js +69 -0
- package/core/server/router.js +120 -0
- package/core/shim.js +28 -0
- package/core/state.d.ts +86 -0
- package/core/state.js +242 -0
- package/core/store.d.ts +122 -0
- package/core/store.js +61 -0
- package/core/validation.d.ts +233 -0
- package/core/validation.js +590 -0
- package/core/websocket.js +639 -0
- package/dist/africode.js +2905 -0
- package/dist/africode.js.map +61 -0
- package/dist/build-info.json +23 -0
- package/dist/components.js +2888 -0
- package/dist/components.js.map +58 -0
- package/dist/styles/africanity.css +322 -0
- package/dist/styles/typography.css +141 -0
- package/docs/IDE-Guide.md +50 -0
- package/package.json +110 -0
- package/src/index.ts +196 -0
- package/styles/africanity.css +322 -0
- package/styles/typography.css +141 -0
- package/templates/starter/.env.example +15 -0
- package/templates/starter/africode.config.js +40 -0
- package/templates/starter/package.json +14 -0
- package/templates/starter/src/pages/index.html +46 -0
- package/templates/starter/src/pages/index.js +32 -0
- package/templates/starter/src/styles/main.css +4 -0
- package/templates/starter-3d/.env.example +7 -0
- package/templates/starter-3d/africode.config.js +29 -0
- package/templates/starter-3d/components/af-model-viewer.js +125 -0
- package/templates/starter-3d/package.json +15 -0
- package/templates/starter-3d/src/pages/index.html +46 -0
- package/templates/starter-3d/src/pages/index.js +50 -0
- package/templates/starter-3d/src/styles/main.css +4 -0
- package/templates/starter-react/.env.example +15 -0
- package/templates/starter-react/africode.config.js +40 -0
- package/templates/starter-react/package.json +16 -0
- package/templates/starter-react/src/pages/index.html +46 -0
- package/templates/starter-react/src/pages/index.js +68 -0
- package/templates/starter-react/src/styles/main.css +4 -0
- package/templates/starter-tailwind/.env.example +15 -0
- package/templates/starter-tailwind/africode.config.js +40 -0
- package/templates/starter-tailwind/package.json +20 -0
- package/templates/starter-tailwind/src/pages/index.html +46 -0
- package/templates/starter-tailwind/src/pages/index.js +37 -0
- package/templates/starter-tailwind/src/styles/main.css +4 -0
- package/templates/starter-tailwind/src/styles/tailwind.css +1 -0
- 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
|
+
};
|
package/core/store.d.ts
ADDED
|
@@ -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;
|