create-aziz-app 2.0.14 → 2.0.15

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.
@@ -0,0 +1,36 @@
1
+ // ============================================================
2
+ // AzizApp Framework Boot
3
+ // Loads layout.js + page.js for the current route
4
+ // Handles client-side navigation like Next.js App Router
5
+ // ============================================================
6
+
7
+ import { h, render, Fragment } from '/_framework/runtime.js';
8
+ import { initHooks, resetHooks } from '/_framework/hooks.js';
9
+ import { router } from '/_framework/router.js';
10
+
11
+ // Make h and Fragment globally available for JSX
12
+ window.h = h;
13
+ window.Fragment = Fragment;
14
+
15
+ // Boot the app
16
+ async function boot() {
17
+ console.log('⚡ AzizApp booting...');
18
+
19
+ // Initialize hooks system
20
+ initHooks();
21
+
22
+ // Start the router
23
+ await router.init();
24
+
25
+ console.log('✅ AzizApp ready');
26
+ }
27
+
28
+ boot().catch(err => {
29
+ console.error('❌ Boot failed:', err);
30
+ document.getElementById('root').innerHTML = `
31
+ <div style="padding:40px;font-family:monospace;color:red">
32
+ <h2>Boot Error</h2>
33
+ <pre>${err.message}</pre>
34
+ </div>
35
+ `;
36
+ });
@@ -0,0 +1,207 @@
1
+ // ============================================================
2
+ // AzizApp Hooks
3
+ // React-identical hooks: useState, useEffect, useMemo,
4
+ // useCallback, useRef, useReducer, useContext
5
+ // ============================================================
6
+
7
+ let currentFiber = null; // current component being rendered
8
+ let hookIndex = 0; // current hook slot
9
+
10
+ // Called before rendering a component
11
+ export function prepareHooks(fiber) {
12
+ currentFiber = fiber;
13
+ hookIndex = 0;
14
+ }
15
+
16
+ // Called after rendering a component
17
+ export function finishHooks() {
18
+ currentFiber = null;
19
+ hookIndex = 0;
20
+ }
21
+
22
+ // Global reset (called before full page re-render)
23
+ export function resetHooks() {
24
+ currentFiber = null;
25
+ hookIndex = 0;
26
+ }
27
+
28
+ export function initHooks() {
29
+ // Expose all hooks globally (no import needed in pages)
30
+ window.useState = useState;
31
+ window.useEffect = useEffect;
32
+ window.useMemo = useMemo;
33
+ window.useCallback = useCallback;
34
+ window.useRef = useRef;
35
+ window.useReducer = useReducer;
36
+ window.useContext = useContext;
37
+ window.createContext = createContext;
38
+ window.useLocalStorage = useLocalStorage;
39
+ window.useFetch = useFetch;
40
+ window.resetHooks = resetHooks;
41
+ window.prepareHooks = prepareHooks;
42
+ window.finishHooks = finishHooks;
43
+ }
44
+
45
+ // ─── useState ────────────────────────────────────────────────
46
+ export function useState(initialValue) {
47
+ const fiber = currentFiber;
48
+ const idx = hookIndex++;
49
+
50
+ if (!fiber) throw new Error('useState called outside component');
51
+
52
+ if (fiber.hooks[idx] === undefined) {
53
+ fiber.hooks[idx] = {
54
+ value: typeof initialValue === 'function' ? initialValue() : initialValue
55
+ };
56
+ }
57
+
58
+ const hook = fiber.hooks[idx];
59
+
60
+ const setState = (next) => {
61
+ const nextVal = typeof next === 'function' ? next(hook.value) : next;
62
+ if (nextVal !== hook.value) {
63
+ hook.value = nextVal;
64
+ scheduleRender();
65
+ }
66
+ };
67
+
68
+ return [hook.value, setState];
69
+ }
70
+
71
+ // ─── useReducer ──────────────────────────────────────────────
72
+ export function useReducer(reducer, initialState, init) {
73
+ const [state, setState] = useState(init ? init(initialState) : initialState);
74
+ const dispatch = (action) => setState(prev => reducer(prev, action));
75
+ return [state, dispatch];
76
+ }
77
+
78
+ // ─── useEffect ───────────────────────────────────────────────
79
+ export function useEffect(callback, deps) {
80
+ const fiber = currentFiber;
81
+ const idx = hookIndex++;
82
+
83
+ if (!fiber) throw new Error('useEffect called outside component');
84
+
85
+ const prev = fiber.hooks[idx];
86
+ const changed = !prev || !deps || deps.some((d, i) => d !== prev.deps?.[i]);
87
+
88
+ if (changed) {
89
+ // Queue cleanup + effect after render
90
+ queueMicrotask(() => {
91
+ if (prev?.cleanup) prev.cleanup();
92
+ const cleanup = callback();
93
+ if (fiber.hooks[idx]) {
94
+ fiber.hooks[idx].cleanup = typeof cleanup === 'function' ? cleanup : undefined;
95
+ }
96
+ });
97
+ }
98
+
99
+ fiber.hooks[idx] = { deps, cleanup: prev?.cleanup };
100
+ }
101
+
102
+ // ─── useMemo ─────────────────────────────────────────────────
103
+ export function useMemo(factory, deps) {
104
+ const fiber = currentFiber;
105
+ const idx = hookIndex++;
106
+
107
+ if (!fiber) throw new Error('useMemo called outside component');
108
+
109
+ const prev = fiber.hooks[idx];
110
+ const changed = !prev || deps.some((d, i) => d !== prev.deps?.[i]);
111
+
112
+ if (changed) {
113
+ fiber.hooks[idx] = { value: factory(), deps };
114
+ }
115
+
116
+ return fiber.hooks[idx].value;
117
+ }
118
+
119
+ // ─── useCallback ─────────────────────────────────────────────
120
+ export function useCallback(fn, deps) {
121
+ return useMemo(() => fn, deps);
122
+ }
123
+
124
+ // ─── useRef ──────────────────────────────────────────────────
125
+ export function useRef(initial) {
126
+ const fiber = currentFiber;
127
+ const idx = hookIndex++;
128
+
129
+ if (!fiber) throw new Error('useRef called outside component');
130
+
131
+ if (!fiber.hooks[idx]) {
132
+ fiber.hooks[idx] = { current: initial };
133
+ }
134
+
135
+ return fiber.hooks[idx];
136
+ }
137
+
138
+ // ─── useContext ───────────────────────────────────────────────
139
+ const contexts = new Map();
140
+
141
+ export function createContext(defaultValue) {
142
+ const ctx = {
143
+ _value: defaultValue,
144
+ Provider({ value, children }) {
145
+ ctx._value = value;
146
+ return children;
147
+ }
148
+ };
149
+ return ctx;
150
+ }
151
+
152
+ export function useContext(ctx) {
153
+ return ctx._value;
154
+ }
155
+
156
+ // ─── useLocalStorage ─────────────────────────────────────────
157
+ export function useLocalStorage(key, initial) {
158
+ const [value, setValue] = useState(() => {
159
+ try {
160
+ const stored = localStorage.getItem(key);
161
+ return stored !== null ? JSON.parse(stored) : initial;
162
+ } catch { return initial; }
163
+ });
164
+
165
+ const set = (next) => {
166
+ const val = typeof next === 'function' ? next(value) : next;
167
+ setValue(val);
168
+ try { localStorage.setItem(key, JSON.stringify(val)); } catch {}
169
+ };
170
+
171
+ return [value, set];
172
+ }
173
+
174
+ // ─── useFetch ────────────────────────────────────────────────
175
+ export function useFetch(url, options = {}) {
176
+ const [data, setData] = useState(null);
177
+ const [loading, setLoading] = useState(true);
178
+ const [error, setError] = useState(null);
179
+
180
+ useEffect(() => {
181
+ let cancelled = false;
182
+ setLoading(true);
183
+
184
+ fetch(url, options)
185
+ .then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
186
+ .then(d => { if (!cancelled) { setData(d); setLoading(false); } })
187
+ .catch(e => { if (!cancelled) { setError(e.message); setLoading(false); } });
188
+
189
+ return () => { cancelled = true; };
190
+ }, [url]);
191
+
192
+ return { data, loading, error };
193
+ }
194
+
195
+ // ─── Render scheduler ────────────────────────────────────────
196
+ let renderScheduled = false;
197
+
198
+ function scheduleRender() {
199
+ if (renderScheduled) return;
200
+ renderScheduled = true;
201
+ requestAnimationFrame(() => {
202
+ renderScheduled = false;
203
+ if (typeof window.__rerenderPage === 'function') {
204
+ window.__rerenderPage();
205
+ }
206
+ });
207
+ }
@@ -0,0 +1,279 @@
1
+ // ============================================================
2
+ // AzizApp Router
3
+ // File-based routing like Next.js App Router
4
+ //
5
+ // app/
6
+ // layout.js → root layout (wraps everything)
7
+ // page.js → /
8
+ // about/
9
+ // page.js → /about
10
+ // layout.js → layout for /about and children
11
+ // blog/
12
+ // [slug]/
13
+ // page.js → /blog/:slug
14
+ // ============================================================
15
+
16
+ import { h, render } from '/_framework/runtime.js';
17
+ import { prepareHooks, finishHooks, resetHooks } from '/_framework/hooks.js';
18
+
19
+ // All registered routes (populated by dev server scan)
20
+ const routes = {};
21
+
22
+ // Current fiber tree for hooks
23
+ const fibers = new Map();
24
+
25
+ function getFiber(id) {
26
+ if (!fibers.has(id)) {
27
+ fibers.set(id, { hooks: [] });
28
+ }
29
+ return fibers.get(id);
30
+ }
31
+
32
+ // Wrap a component so hooks work correctly
33
+ function withHooks(id, Component) {
34
+ return function WrappedComponent(props) {
35
+ const fiber = getFiber(id);
36
+ prepareHooks(fiber);
37
+ const result = Component(props);
38
+ finishHooks();
39
+ return result;
40
+ };
41
+ }
42
+
43
+ // Navigate to a path
44
+ export function navigate(path) {
45
+ history.pushState(null, '', path);
46
+ router.render();
47
+ }
48
+
49
+ // Link component (like Next.js <Link>)
50
+ export function Link({ href, children, className, style }) {
51
+ return h('a', {
52
+ href,
53
+ class: className,
54
+ style,
55
+ onClick: (e) => {
56
+ e.preventDefault();
57
+ navigate(href);
58
+ }
59
+ }, ...(Array.isArray(children) ? children : [children]));
60
+ }
61
+
62
+ // useRouter hook
63
+ export function useRouter() {
64
+ return {
65
+ pathname: window.location.pathname,
66
+ push: navigate,
67
+ back: () => history.back(),
68
+ forward: () => history.forward(),
69
+ params: router.currentParams || {}
70
+ };
71
+ }
72
+
73
+ // useParams hook
74
+ export function useParams() {
75
+ return router.currentParams || {};
76
+ }
77
+
78
+ // useSearchParams hook
79
+ export function useSearchParams() {
80
+ const params = new URLSearchParams(window.location.search);
81
+ return {
82
+ get: (key) => params.get(key),
83
+ getAll: (key) => params.getAll(key),
84
+ has: (key) => params.has(key),
85
+ toString: () => params.toString()
86
+ };
87
+ }
88
+
89
+ // Match a route pattern to a pathname
90
+ // Supports: /about, /blog/[slug], /blog/[...slug]
91
+ function matchRoute(pattern, pathname) {
92
+ const patternParts = pattern.split('/').filter(Boolean);
93
+ const pathParts = pathname.split('/').filter(Boolean);
94
+ const params = {};
95
+
96
+ // Catch-all: /blog/[...slug]
97
+ const catchAll = patternParts.findIndex(p => p.startsWith('[...'));
98
+ if (catchAll !== -1) {
99
+ if (pathParts.length < catchAll) return null;
100
+ const paramName = patternParts[catchAll].slice(4, -1);
101
+ params[paramName] = pathParts.slice(catchAll);
102
+ return params;
103
+ }
104
+
105
+ if (patternParts.length !== pathParts.length) return null;
106
+
107
+ for (let i = 0; i < patternParts.length; i++) {
108
+ if (patternParts[i].startsWith('[') && patternParts[i].endsWith(']')) {
109
+ // Dynamic segment
110
+ params[patternParts[i].slice(1, -1)] = pathParts[i];
111
+ } else if (patternParts[i] !== pathParts[i]) {
112
+ return null;
113
+ }
114
+ }
115
+
116
+ return params;
117
+ }
118
+
119
+ // Find the best matching route for a pathname
120
+ function resolveRoute(pathname) {
121
+ // Exact match first
122
+ if (routes[pathname]) {
123
+ return { route: routes[pathname], params: {} };
124
+ }
125
+
126
+ // Pattern match
127
+ for (const pattern in routes) {
128
+ if (pattern.includes('[')) {
129
+ const params = matchRoute(pattern, pathname);
130
+ if (params !== null) {
131
+ return { route: routes[pattern], params };
132
+ }
133
+ }
134
+ }
135
+
136
+ return null;
137
+ }
138
+
139
+ // Collect layout chain for a path
140
+ // /about/team → [app/layout.js, app/about/layout.js]
141
+ function getLayoutChain(pathname) {
142
+ return router.layouts.filter(({ path }) => {
143
+ if (path === '/') return true;
144
+ return pathname === path || pathname.startsWith(path + '/');
145
+ }).sort((a, b) => a.path.length - b.path.length);
146
+ }
147
+
148
+ export const router = {
149
+ layouts: [],
150
+ currentParams: {},
151
+
152
+ // Register a route (called by dev server scan result)
153
+ register(pathname, module) {
154
+ routes[pathname] = module;
155
+ },
156
+
157
+ // Register a layout
158
+ registerLayout(pathname, module) {
159
+ this.layouts.push({ path: pathname, module });
160
+ },
161
+
162
+ async init() {
163
+ // Expose globals
164
+ window.navigate = navigate;
165
+ window.useRouter = useRouter;
166
+ window.useParams = useParams;
167
+ window.useSearchParams = useSearchParams;
168
+ window.Link = Link;
169
+ window.__rerenderPage = () => this.render();
170
+
171
+ // Load routes from server
172
+ await this.loadRoutes();
173
+
174
+ // Handle browser back/forward
175
+ window.addEventListener('popstate', () => this.render());
176
+
177
+ // Initial render
178
+ await this.render();
179
+ },
180
+
181
+ async loadRoutes() {
182
+ try {
183
+ // Dev server exposes the route manifest
184
+ const res = await fetch('/_framework/routes.json');
185
+ const manifest = await res.json();
186
+
187
+ // Dynamically import each page and layout
188
+ for (const { pathname, filePath, type } of manifest) {
189
+ try {
190
+ const mod = await import(filePath + '?t=' + Date.now());
191
+ if (type === 'page') {
192
+ this.register(pathname, mod);
193
+ } else if (type === 'layout') {
194
+ this.registerLayout(pathname, mod);
195
+ }
196
+ } catch (err) {
197
+ console.error(`Failed to load ${filePath}:`, err);
198
+ }
199
+ }
200
+
201
+ console.log(`📄 Loaded ${Object.keys(routes).length} routes`);
202
+ } catch (err) {
203
+ console.error('Failed to load routes:', err);
204
+ }
205
+ },
206
+
207
+ async render() {
208
+ const pathname = window.location.pathname;
209
+ const root = document.getElementById('root');
210
+
211
+ // Find matching route
212
+ const match = resolveRoute(pathname);
213
+
214
+ if (!match) {
215
+ root.innerHTML = '';
216
+ const notFound = h('div', { style: { padding: '40px', textAlign: 'center' } },
217
+ h('h1', { style: { fontSize: '80px', color: '#61dafb' } }, '404'),
218
+ h('p', {}, 'Page not found'),
219
+ h('button', {
220
+ style: { padding: '10px 24px', cursor: 'pointer' },
221
+ onClick: () => navigate('/')
222
+ }, 'Go Home')
223
+ );
224
+ root.appendChild(notFound);
225
+ return;
226
+ }
227
+
228
+ this.currentParams = match.params;
229
+
230
+ const { route } = match;
231
+ const PageComponent = route.default;
232
+
233
+ if (!PageComponent) {
234
+ console.error(`Route has no default export: ${pathname}`);
235
+ return;
236
+ }
237
+
238
+ // Get layout chain
239
+ const layoutChain = getLayoutChain(pathname);
240
+
241
+ // Wrap page in layouts (innermost first, outermost last)
242
+ let content = () => {
243
+ const fiber = getFiber('page:' + pathname);
244
+ prepareHooks(fiber);
245
+ const result = PageComponent({});
246
+ finishHooks();
247
+ return result;
248
+ };
249
+
250
+ // Apply layouts from innermost to outermost
251
+ for (let i = layoutChain.length - 1; i >= 0; i--) {
252
+ const { module: layoutMod, path: layoutPath } = layoutChain[i];
253
+ const LayoutComponent = layoutMod.default;
254
+ if (!LayoutComponent) continue;
255
+
256
+ const innerContent = content;
257
+ content = () => {
258
+ const fiber = getFiber('layout:' + layoutPath);
259
+ prepareHooks(fiber);
260
+ const result = LayoutComponent({ children: innerContent() });
261
+ finishHooks();
262
+ return result;
263
+ };
264
+ }
265
+
266
+ // Render
267
+ root.innerHTML = '';
268
+ const tree = content();
269
+ if (Array.isArray(tree)) {
270
+ tree.forEach(node => {
271
+ if (node instanceof Node) root.appendChild(node);
272
+ });
273
+ } else if (tree instanceof Node) {
274
+ root.appendChild(tree);
275
+ }
276
+ }
277
+ };
278
+
279
+ export default router;
@@ -0,0 +1,86 @@
1
+ // ============================================================
2
+ // AzizApp Runtime
3
+ // h() — JSX factory (like React.createElement)
4
+ // render() — mount component tree to DOM
5
+ // ============================================================
6
+
7
+ export const Fragment = Symbol('Fragment');
8
+
9
+ // JSX factory — Babel compiles <div> → h("div", props, ...children)
10
+ export function h(tag, props, ...children) {
11
+ // Fragment: just return children
12
+ if (tag === Fragment) {
13
+ return flatChildren(children);
14
+ }
15
+
16
+ // Component function
17
+ if (typeof tag === 'function') {
18
+ return tag({ ...(props || {}), children: flatChildren(children) });
19
+ }
20
+
21
+ const el = document.createElement(tag);
22
+
23
+ if (props) {
24
+ for (const key in props) {
25
+ if (key === 'children') continue;
26
+
27
+ if (key === 'ref' && props[key] && typeof props[key] === 'object') {
28
+ // useRef support
29
+ props[key].current = el;
30
+ } else if (key.startsWith('on')) {
31
+ // onClick → click
32
+ el.addEventListener(key.slice(2).toLowerCase(), props[key]);
33
+ } else if (key === 'className' || key === 'class') {
34
+ el.className = props[key];
35
+ } else if (key === 'htmlFor') {
36
+ el.setAttribute('for', props[key]);
37
+ } else if (key === 'style' && typeof props[key] === 'object') {
38
+ Object.assign(el.style, props[key]);
39
+ } else if (key === 'dangerouslySetInnerHTML') {
40
+ el.innerHTML = props[key].__html;
41
+ } else if (key === 'value' && (tag === 'input' || tag === 'textarea' || tag === 'select')) {
42
+ el.value = props[key];
43
+ } else if (key === 'checked' && tag === 'input') {
44
+ el.checked = props[key];
45
+ } else if (key === 'disabled') {
46
+ if (props[key]) el.setAttribute('disabled', '');
47
+ } else if (key === 'key') {
48
+ // ignore reconciliation key for now
49
+ } else {
50
+ el.setAttribute(key, props[key]);
51
+ }
52
+ }
53
+ }
54
+
55
+ appendChildren(el, flatChildren(children));
56
+ return el;
57
+ }
58
+
59
+ function flatChildren(children) {
60
+ return children.flat(Infinity).filter(c => c !== null && c !== undefined && c !== false && c !== true);
61
+ }
62
+
63
+ function appendChildren(parent, children) {
64
+ for (const child of children) {
65
+ if (child instanceof Node) {
66
+ parent.appendChild(child);
67
+ } else if (Array.isArray(child)) {
68
+ appendChildren(parent, child);
69
+ } else {
70
+ parent.appendChild(document.createTextNode(String(child)));
71
+ }
72
+ }
73
+ }
74
+
75
+ // Mount a component tree into a DOM element
76
+ export function render(component, container) {
77
+ container.innerHTML = '';
78
+ const result = typeof component === 'function' ? component() : component;
79
+ if (Array.isArray(result)) {
80
+ appendChildren(container, result);
81
+ } else if (result instanceof Node) {
82
+ container.appendChild(result);
83
+ }
84
+ }
85
+
86
+ export default { h, render, Fragment };
@@ -1,26 +1,17 @@
1
1
  // API Route: /api/hello
2
- // Simple Next.js-style API route
2
+ // Next.js App Router style
3
3
 
4
- export default async function handler(req, res) {
5
- const { method, query } = req;
4
+ const { NextResponse } = require('../modules/next-response');
6
5
 
7
- if (method !== 'GET') {
8
- res.setHeader('Allow', ['GET']);
9
- return res.status(405).json({
10
- error: 'Method not allowed',
11
- message: `Method ${method} is not allowed. Use GET instead.`
12
- });
13
- }
14
-
15
- const name = query.name || 'World';
6
+ async function GET(request) {
7
+ const { searchParams } = new URL(request.url);
8
+ const name = searchParams.get('name') || 'World';
16
9
 
17
- return res.status(200).json({
10
+ return NextResponse.json({
18
11
  message: `Hello, ${name}!`,
19
12
  timestamp: new Date().toISOString(),
20
- method: method,
21
- query: query
13
+ method: request.method
22
14
  });
23
15
  }
24
16
 
25
- // CommonJS export for Node.js compatibility
26
- module.exports = handler;
17
+ module.exports = { GET };