create-aziz-app 3.1.2 → 3.1.3

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 (33) hide show
  1. package/README.md +658 -207
  2. package/bin/create-aziz-app.js +302 -302
  3. package/package.json +1 -1
  4. package/template/README.md +402 -402
  5. package/template/_framework/api.js +219 -219
  6. package/template/_framework/boot.js +33 -33
  7. package/template/_framework/hooks.js +367 -291
  8. package/template/_framework/next-response.js +128 -128
  9. package/template/_framework/route-match.js +145 -145
  10. package/template/_framework/router.js +465 -259
  11. package/template/_framework/runtime.js +264 -264
  12. package/template/app/layout.js +18 -18
  13. package/template/app/page.js +33 -33
  14. package/template/docs/01-getting-started.md +151 -151
  15. package/template/docs/02-project-structure.md +109 -109
  16. package/template/docs/03-pages-and-layouts.md +140 -140
  17. package/template/docs/04-routing.md +120 -120
  18. package/template/docs/05-hooks.md +169 -169
  19. package/template/docs/06-api-routes.md +210 -210
  20. package/template/docs/07-api-client.md +151 -151
  21. package/template/docs/08-styling.md +94 -94
  22. package/template/docs/09-imports-and-modules.md +97 -97
  23. package/template/docs/10-database-and-models.md +154 -154
  24. package/template/docs/11-authentication.md +171 -171
  25. package/template/docs/12-production-and-deployment.md +121 -121
  26. package/template/docs/13-internals.md +179 -179
  27. package/template/docs/README.md +67 -67
  28. package/template/index.html +20 -20
  29. package/template/package.json +16 -16
  30. package/template/scripts/dev-server.js +493 -381
  31. package/template/scripts/lib/api-router.js +525 -525
  32. package/template/scripts/lib/js-transform.js +213 -195
  33. package/template/scripts/lib/module-resolver.js +132 -132
@@ -1,291 +1,367 @@
1
- // ============================================================
2
- // Aziz.js Hooks
3
- // React-identical hooks: useState, useEffect, useMemo,
4
- // useCallback, useRef, useReducer, useContext, useFetch
5
- // ============================================================
6
-
7
- let currentFiber = null; // component instance currently rendering
8
- let hookIndex = 0; // current hook slot
9
-
10
- // Called by the runtime around each component render.
11
- export function prepareHooks(fiber) {
12
- currentFiber = fiber;
13
- hookIndex = 0;
14
- }
15
-
16
- export function finishHooks() {
17
- currentFiber = null;
18
- hookIndex = 0;
19
- }
20
-
21
- export function resetHooks() {
22
- currentFiber = null;
23
- hookIndex = 0;
24
- }
25
-
26
- // ─── Global registration ─────────────────────────────────────
27
-
28
- export function initHooks() {
29
- // Expose all hooks globally (no imports 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.useApi = useFetch;
41
- window.resetHooks = resetHooks;
42
- window.prepareHooks = prepareHooks;
43
- window.finishHooks = finishHooks;
44
- }
45
-
46
- // ─── useState ────────────────────────────────────────────────
47
-
48
- export function useState(initialValue) {
49
- const fiber = currentFiber;
50
- const idx = hookIndex++;
51
-
52
- if (!fiber) throw new Error('useState called outside component');
53
-
54
- if (fiber.hooks[idx] === undefined) {
55
- fiber.hooks[idx] = {
56
- value: typeof initialValue === 'function' ? initialValue() : initialValue
57
- };
58
- }
59
-
60
- const hook = fiber.hooks[idx];
61
-
62
- const setState = (next) => {
63
- if (fiber.mounted === false) return; // component unmounted
64
- const nextVal = typeof next === 'function' ? next(hook.value) : next;
65
- if (nextVal !== hook.value) {
66
- hook.value = nextVal;
67
- scheduleRender();
68
- }
69
- };
70
-
71
- return [hook.value, setState];
72
- }
73
-
74
- // ─── useReducer ──────────────────────────────────────────────
75
-
76
- export function useReducer(reducer, initialState, init) {
77
- const [state, setState] = useState(init ? init(initialState) : initialState);
78
- const dispatch = (action) => setState(prev => reducer(prev, action));
79
- return [state, dispatch];
80
- }
81
-
82
- // ─── useEffect ───────────────────────────────────────────────
83
-
84
- export function useEffect(callback, deps) {
85
- const fiber = currentFiber;
86
- const idx = hookIndex++;
87
-
88
- if (!fiber) throw new Error('useEffect called outside component');
89
-
90
- const prev = fiber.hooks[idx];
91
- const changed = !prev || !deps || deps.some((d, i) => d !== prev.deps?.[i]);
92
-
93
- if (changed) {
94
- queueMicrotask(() => {
95
- if (fiber.mounted === false) return; // unmounted before flush
96
- if (prev && typeof prev.cleanup === 'function') prev.cleanup();
97
- const cleanup = callback();
98
- if (fiber.mounted !== false) {
99
- fiber.hooks[idx] = fiber.hooks[idx] || {};
100
- fiber.hooks[idx].cleanup = typeof cleanup === 'function' ? cleanup : undefined;
101
- }
102
- });
103
- }
104
-
105
- if (prev) {
106
- // Preserve deps/cleanup slots on re-render
107
- prev.deps = deps;
108
- fiber.hooks[idx] = prev;
109
- } else {
110
- fiber.hooks[idx] = { deps, cleanup: undefined };
111
- }
112
- }
113
-
114
- // ─── useMemo / useCallback ───────────────────────────────────
115
-
116
- export function useMemo(factory, deps) {
117
- const fiber = currentFiber;
118
- const idx = hookIndex++;
119
-
120
- if (!fiber) throw new Error('useMemo called outside component');
121
-
122
- const prev = fiber.hooks[idx];
123
- const changed = !prev || !deps || deps.some((d, i) => d !== prev.deps?.[i]);
124
-
125
- if (changed) {
126
- fiber.hooks[idx] = { value: factory(), deps };
127
- }
128
-
129
- return fiber.hooks[idx].value;
130
- }
131
-
132
- export function useCallback(fn, deps) {
133
- return useMemo(() => fn, deps);
134
- }
135
-
136
- // ─── useRef ──────────────────────────────────────────────────
137
-
138
- export function useRef(initial) {
139
- const fiber = currentFiber;
140
- const idx = hookIndex++;
141
-
142
- if (!fiber) throw new Error('useRef called outside component');
143
-
144
- if (!fiber.hooks[idx]) {
145
- fiber.hooks[idx] = { current: initial };
146
- }
147
-
148
- return fiber.hooks[idx];
149
- }
150
-
151
- // ─── Context ─────────────────────────────────────────────────
152
-
153
- const ctxStack = []; // [{ ctx, value }] — active during provider children render
154
-
155
- // Injected by runtime.js to materialize provider children inside the provider scope.
156
- let materializeChildren = (children) => children;
157
-
158
- export function setMaterializer(fn) {
159
- materializeChildren = fn;
160
- }
161
-
162
- export function createContext(defaultValue) {
163
- const ctx = { _defaultValue: defaultValue, Provider: null };
164
-
165
- function ContextProvider(props) {
166
- ctxStack.push({ ctx, value: props.value });
167
- try {
168
- return materializeChildren(props.children);
169
- } finally {
170
- ctxStack.pop();
171
- }
172
- }
173
- ContextProvider.displayName = 'Context.Provider';
174
-
175
- ctx.Provider = ContextProvider;
176
- return ctx;
177
- }
178
-
179
- export function useContext(ctx) {
180
- for (let i = ctxStack.length - 1; i >= 0; i--) {
181
- if (ctxStack[i].ctx === ctx) return ctxStack[i].value;
182
- }
183
- return ctx._defaultValue;
184
- }
185
-
186
- // ─── useLocalStorage ─────────────────────────────────────────
187
-
188
- export function useLocalStorage(key, initial) {
189
- const [value, setValue] = useState(() => {
190
- try {
191
- const stored = localStorage.getItem(key);
192
- return stored !== null ? JSON.parse(stored) : initial;
193
- } catch { return initial; }
194
- });
195
-
196
- // Keep multiple tabs in sync
197
- useEffect(() => {
198
- function onStorage(e) {
199
- if (e.key !== key || e.newValue === null) return;
200
- try { setValue(JSON.parse(e.newValue)); } catch { /* ignore */ }
201
- }
202
- window.addEventListener('storage', onStorage);
203
- return () => window.removeEventListener('storage', onStorage);
204
- }, [key]);
205
-
206
- const set = (next) => {
207
- const val = typeof next === 'function' ? next(value) : next;
208
- setValue(val);
209
- try { localStorage.setItem(key, JSON.stringify(val)); } catch { /* quota */ }
210
- };
211
-
212
- return [value, set];
213
- }
214
-
215
- // ─── useFetch ────────────────────────────────────────────────
216
-
217
- export function useFetch(url, options = {}) {
218
- const [state, setState] = useState({ data: null, loading: !!url, error: null, status: null });
219
- const abortRef = useRef(null);
220
- const [nonce, setNonce] = useState(0);
221
-
222
- useEffect(() => {
223
- if (!url) {
224
- setState({ data: null, loading: false, error: null, status: null });
225
- return;
226
- }
227
-
228
- // Cancel any in-flight request from a previous render
229
- if (abortRef.current) abortRef.current.abort();
230
- const controller = new AbortController();
231
- abortRef.current = controller;
232
-
233
- setState(prev => ({ ...prev, loading: true, error: null }));
234
-
235
- fetch(url, { ...options, signal: controller.signal })
236
- .then(async (res) => {
237
- const contentType = (res.headers.get('content-type') || '').toLowerCase();
238
- let data = null;
239
- if (res.status !== 204) {
240
- if (contentType.includes('application/json')) {
241
- data = await res.json().catch(() => null);
242
- } else {
243
- data = await res.text();
244
- }
245
- }
246
- if (!res.ok) {
247
- const message = (data && (data.error || data.message)) || `HTTP ${res.status}`;
248
- const err = new Error(typeof message === 'string' ? message : JSON.stringify(message));
249
- err.status = res.status;
250
- err.data = data;
251
- throw err;
252
- }
253
- setState({ data, loading: false, error: null, status: res.status });
254
- })
255
- .catch((err) => {
256
- if (err.name === 'AbortError') return;
257
- setState(prev => ({ ...prev, loading: false, error: err.message, status: err.status || null }));
258
- });
259
-
260
- return () => controller.abort();
261
- }, [url, nonce]);
262
-
263
- const refetch = useCallback(() => setNonce(n => n + 1), []);
264
-
265
- return { ...state, refetch };
266
- }
267
-
268
- // ─── Render scheduler ────────────────────────────────────────
269
-
270
- let renderScheduled = false;
271
-
272
- function scheduleRender() {
273
- if (renderScheduled) return;
274
- renderScheduled = true;
275
-
276
- const run = () => {
277
- if (!renderScheduled) return;
278
- renderScheduled = false;
279
- if (typeof window.__rerenderPage === 'function') {
280
- window.__rerenderPage();
281
- }
282
- };
283
-
284
- if (typeof requestAnimationFrame === 'function') {
285
- requestAnimationFrame(run);
286
- // Fallback: rAF is paused in background tabs
287
- setTimeout(run, 120);
288
- } else {
289
- setTimeout(run, 0);
290
- }
291
- }
1
+ // ============================================================
2
+ // Aziz.js Hooks
3
+ // React-identical hooks: useState, useEffect, useMemo,
4
+ // useCallback, useRef, useReducer, useContext, useFetch
5
+ // ============================================================
6
+
7
+ let currentFiber = null; // component instance currently rendering
8
+ let hookIndex = 0; // current hook slot
9
+
10
+ // Called by the runtime around each component render.
11
+ export function prepareHooks(fiber) {
12
+ currentFiber = fiber;
13
+ hookIndex = 0;
14
+ }
15
+
16
+ export function finishHooks() {
17
+ currentFiber = null;
18
+ hookIndex = 0;
19
+ }
20
+
21
+ export function resetHooks() {
22
+ currentFiber = null;
23
+ hookIndex = 0;
24
+ }
25
+
26
+ // ─── Global registration ─────────────────────────────────────
27
+
28
+ export function initHooks() {
29
+ // Expose all hooks globally (no imports 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.useApi = useFetch;
41
+ window.onLoadState = onLoadState;
42
+ window.getPendingLoads = getPendingLoads;
43
+ window.clearFetchCache = clearFetchCache;
44
+ window.resetHooks = resetHooks;
45
+ window.prepareHooks = prepareHooks;
46
+ window.finishHooks = finishHooks;
47
+ }
48
+
49
+ // ─── useState ────────────────────────────────────────────────
50
+
51
+ export function useState(initialValue) {
52
+ const fiber = currentFiber;
53
+ const idx = hookIndex++;
54
+
55
+ if (!fiber) throw new Error('useState called outside component');
56
+
57
+ if (fiber.hooks[idx] === undefined) {
58
+ fiber.hooks[idx] = {
59
+ value: typeof initialValue === 'function' ? initialValue() : initialValue
60
+ };
61
+ }
62
+
63
+ const hook = fiber.hooks[idx];
64
+
65
+ const setState = (next) => {
66
+ if (fiber.mounted === false) return; // component unmounted
67
+ const nextVal = typeof next === 'function' ? next(hook.value) : next;
68
+ if (nextVal !== hook.value) {
69
+ hook.value = nextVal;
70
+ scheduleRender();
71
+ }
72
+ };
73
+
74
+ return [hook.value, setState];
75
+ }
76
+
77
+ // ─── useReducer ──────────────────────────────────────────────
78
+
79
+ export function useReducer(reducer, initialState, init) {
80
+ const [state, setState] = useState(init ? init(initialState) : initialState);
81
+ const dispatch = (action) => setState(prev => reducer(prev, action));
82
+ return [state, dispatch];
83
+ }
84
+
85
+ // ─── useEffect ───────────────────────────────────────────────
86
+
87
+ export function useEffect(callback, deps) {
88
+ const fiber = currentFiber;
89
+ const idx = hookIndex++;
90
+
91
+ if (!fiber) throw new Error('useEffect called outside component');
92
+
93
+ const prev = fiber.hooks[idx];
94
+ const changed = !prev || !deps || deps.some((d, i) => d !== prev.deps?.[i]);
95
+
96
+ if (changed) {
97
+ queueMicrotask(() => {
98
+ if (fiber.mounted === false) return; // unmounted before flush
99
+ if (prev && typeof prev.cleanup === 'function') prev.cleanup();
100
+ const cleanup = callback();
101
+ if (fiber.mounted !== false) {
102
+ fiber.hooks[idx] = fiber.hooks[idx] || {};
103
+ fiber.hooks[idx].cleanup = typeof cleanup === 'function' ? cleanup : undefined;
104
+ }
105
+ });
106
+ }
107
+
108
+ if (prev) {
109
+ // Preserve deps/cleanup slots on re-render
110
+ prev.deps = deps;
111
+ fiber.hooks[idx] = prev;
112
+ } else {
113
+ fiber.hooks[idx] = { deps, cleanup: undefined };
114
+ }
115
+ }
116
+
117
+ // ─── useMemo / useCallback ───────────────────────────────────
118
+
119
+ export function useMemo(factory, deps) {
120
+ const fiber = currentFiber;
121
+ const idx = hookIndex++;
122
+
123
+ if (!fiber) throw new Error('useMemo called outside component');
124
+
125
+ const prev = fiber.hooks[idx];
126
+ const changed = !prev || !deps || deps.some((d, i) => d !== prev.deps?.[i]);
127
+
128
+ if (changed) {
129
+ fiber.hooks[idx] = { value: factory(), deps };
130
+ }
131
+
132
+ return fiber.hooks[idx].value;
133
+ }
134
+
135
+ export function useCallback(fn, deps) {
136
+ return useMemo(() => fn, deps);
137
+ }
138
+
139
+ // ─── useRef ──────────────────────────────────────────────────
140
+
141
+ export function useRef(initial) {
142
+ const fiber = currentFiber;
143
+ const idx = hookIndex++;
144
+
145
+ if (!fiber) throw new Error('useRef called outside component');
146
+
147
+ if (!fiber.hooks[idx]) {
148
+ fiber.hooks[idx] = { current: initial };
149
+ }
150
+
151
+ return fiber.hooks[idx];
152
+ }
153
+
154
+ // ─── Context ─────────────────────────────────────────────────
155
+
156
+ const ctxStack = []; // [{ ctx, value }] — active during provider children render
157
+
158
+ // Injected by runtime.js to materialize provider children inside the provider scope.
159
+ let materializeChildren = (children) => children;
160
+
161
+ export function setMaterializer(fn) {
162
+ materializeChildren = fn;
163
+ }
164
+
165
+ export function createContext(defaultValue) {
166
+ const ctx = { _defaultValue: defaultValue, Provider: null };
167
+
168
+ function ContextProvider(props) {
169
+ ctxStack.push({ ctx, value: props.value });
170
+ try {
171
+ return materializeChildren(props.children);
172
+ } finally {
173
+ ctxStack.pop();
174
+ }
175
+ }
176
+ ContextProvider.displayName = 'Context.Provider';
177
+
178
+ ctx.Provider = ContextProvider;
179
+ return ctx;
180
+ }
181
+
182
+ export function useContext(ctx) {
183
+ for (let i = ctxStack.length - 1; i >= 0; i--) {
184
+ if (ctxStack[i].ctx === ctx) return ctxStack[i].value;
185
+ }
186
+ return ctx._defaultValue;
187
+ }
188
+
189
+ // ─── useLocalStorage ─────────────────────────────────────────
190
+
191
+ export function useLocalStorage(key, initial) {
192
+ const [value, setValue] = useState(() => {
193
+ try {
194
+ const stored = localStorage.getItem(key);
195
+ return stored !== null ? JSON.parse(stored) : initial;
196
+ } catch { return initial; }
197
+ });
198
+
199
+ // Keep multiple tabs in sync
200
+ useEffect(() => {
201
+ function onStorage(e) {
202
+ if (e.key !== key || e.newValue === null) return;
203
+ try { setValue(JSON.parse(e.newValue)); } catch { /* ignore */ }
204
+ }
205
+ window.addEventListener('storage', onStorage);
206
+ return () => window.removeEventListener('storage', onStorage);
207
+ }, [key]);
208
+
209
+ const set = (next) => {
210
+ const val = typeof next === 'function' ? next(value) : next;
211
+ setValue(val);
212
+ try { localStorage.setItem(key, JSON.stringify(val)); } catch { /* quota */ }
213
+ };
214
+
215
+ return [value, set];
216
+ }
217
+
218
+ // ─── Load tracking ───────────────────────────────────────────
219
+ //
220
+ // A tiny registry of in-flight requests so the router can tell when a route
221
+ // has settled. This is what lets app/<route>/loading.js work without a real
222
+ // Suspense renderer: after boot every page module is already imported, so
223
+ // data fetching is the only genuinely async part of a navigation.
224
+
225
+ let pendingLoads = 0;
226
+ const loadListeners = new Set();
227
+
228
+ function emitLoadState() {
229
+ for (const fn of loadListeners) {
230
+ try { fn(pendingLoads); } catch { /* listener errors must not break fetching */ }
231
+ }
232
+ }
233
+
234
+ export function beginLoad() {
235
+ pendingLoads++;
236
+ emitLoadState();
237
+ }
238
+
239
+ export function endLoad() {
240
+ pendingLoads = Math.max(0, pendingLoads - 1);
241
+ emitLoadState();
242
+ }
243
+
244
+ export function getPendingLoads() {
245
+ return pendingLoads;
246
+ }
247
+
248
+ export function onLoadState(fn) {
249
+ loadListeners.add(fn);
250
+ fn(pendingLoads);
251
+ return () => loadListeners.delete(fn);
252
+ }
253
+
254
+ // ─── Response cache ──────────────────────────────────────────
255
+ //
256
+ // Re-mounting a page (for example swapping loading.js out for it) must not
257
+ // re-hit the network. Entries are keyed by URL and expire quickly.
258
+
259
+ const responseCache = new Map();
260
+ const CACHE_TTL = 5000;
261
+
262
+ export function clearFetchCache() {
263
+ responseCache.clear();
264
+ }
265
+
266
+ function cacheKey(url, options) {
267
+ const method = (options.method || 'GET').toUpperCase();
268
+ return method === 'GET' ? url : url + '|' + JSON.stringify(options.body || null);
269
+ }
270
+
271
+ // ─── useFetch ────────────────────────────────────────────────
272
+
273
+ export function useFetch(url, options = {}) {
274
+ const [state, setState] = useState({ data: null, loading: !!url, error: null, status: null });
275
+ const abortRef = useRef(null);
276
+ const [nonce, setNonce] = useState(0);
277
+
278
+ useEffect(() => {
279
+ if (!url) {
280
+ setState({ data: null, loading: false, error: null, status: null });
281
+ return;
282
+ }
283
+
284
+ // Cancel any in-flight request from a previous render
285
+ if (abortRef.current) abortRef.current.abort();
286
+ const controller = new AbortController();
287
+ abortRef.current = controller;
288
+
289
+ // Serve a fresh cache entry immediately instead of refetching. This is
290
+ // what makes swapping loading.js out for the page cheap.
291
+ const key = cacheKey(url, options);
292
+ const cached = responseCache.get(key);
293
+ if (cached && Date.now() - cached.at < CACHE_TTL && (options.force || cached.data !== undefined)) {
294
+ setState({ data: cached.data, loading: false, error: null, status: cached.status });
295
+ return;
296
+ }
297
+
298
+ setState(prev => ({ ...prev, loading: true, error: null }));
299
+
300
+ beginLoad();
301
+ let counted = true;
302
+ const settle = () => {
303
+ if (!counted) return;
304
+ counted = false;
305
+ endLoad();
306
+ };
307
+
308
+ fetch(url, { ...options, signal: controller.signal })
309
+ .then(async (res) => {
310
+ const contentType = (res.headers.get('content-type') || '').toLowerCase();
311
+ let data = null;
312
+ if (res.status !== 204) {
313
+ if (contentType.includes('application/json')) {
314
+ data = await res.json().catch(() => null);
315
+ } else {
316
+ data = await res.text();
317
+ }
318
+ }
319
+ if (!res.ok) {
320
+ const message = (data && (data.error || data.message)) || `HTTP ${res.status}`;
321
+ const err = new Error(typeof message === 'string' ? message : JSON.stringify(message));
322
+ err.status = res.status;
323
+ err.data = data;
324
+ throw err;
325
+ }
326
+ responseCache.set(key, { data, status: res.status, at: Date.now() });
327
+ setState({ data, loading: false, error: null, status: res.status });
328
+ settle();
329
+ })
330
+ .catch((err) => {
331
+ if (err.name === 'AbortError') { settle(); return; }
332
+ setState(prev => ({ ...prev, loading: false, error: err.message, status: err.status || null }));
333
+ settle();
334
+ });
335
+
336
+ return () => { controller.abort(); settle(); };
337
+ }, [url, nonce]);
338
+
339
+ const refetch = useCallback(() => setNonce(n => n + 1), []);
340
+
341
+ return { ...state, refetch };
342
+ }
343
+
344
+ // ─── Render scheduler ────────────────────────────────────────
345
+
346
+ let renderScheduled = false;
347
+
348
+ function scheduleRender() {
349
+ if (renderScheduled) return;
350
+ renderScheduled = true;
351
+
352
+ const run = () => {
353
+ if (!renderScheduled) return;
354
+ renderScheduled = false;
355
+ if (typeof window.__rerenderPage === 'function') {
356
+ window.__rerenderPage();
357
+ }
358
+ };
359
+
360
+ if (typeof requestAnimationFrame === 'function') {
361
+ requestAnimationFrame(run);
362
+ // Fallback: rAF is paused in background tabs
363
+ setTimeout(run, 120);
364
+ } else {
365
+ setTimeout(run, 0);
366
+ }
367
+ }