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,264 +1,264 @@
1
- // ============================================================
2
- // Aziz.js Runtime
3
- //
4
- // h() — JSX factory (like React.createElement)
5
- // materialize — turn a node tree into real DOM
6
- // mountPage — mount a page + its layout chain
7
- //
8
- // Rendering model
9
- // ---------------
10
- // Function (component) tags are LAZY: h(Component) returns a
11
- // lightweight descriptor. Descriptors are executed top-down when
12
- // they are attached to the DOM, which gives every component
13
- // instance its own hook "fiber" (keyed by its position in the
14
- // tree) and makes context propagation work correctly.
15
- // ============================================================
16
-
17
- import { prepareHooks, finishHooks, setMaterializer } from './hooks.js';
18
-
19
- export const Fragment = Symbol('Fragment');
20
-
21
- const VNODE = Symbol('aziz.vnode');
22
-
23
- // ─── Fibers ──────────────────────────────────────────────────
24
- // One fiber per component instance, keyed by a stable tree path.
25
-
26
- const fibers = new Map();
27
-
28
- export function getFiber(id) {
29
- let fiber = fibers.get(id);
30
- if (!fiber) {
31
- fiber = { id, hooks: [], mounted: true };
32
- fibers.set(id, fiber);
33
- } else {
34
- fiber.mounted = true;
35
- }
36
- return fiber;
37
- }
38
-
39
- export function fiberCount() {
40
- return fibers.size;
41
- }
42
-
43
- // Unmount everything: run pending effect cleanups, drop state.
44
- export function disposeFibers() {
45
- for (const fiber of fibers.values()) {
46
- fiber.mounted = false;
47
- for (const hook of fiber.hooks) {
48
- if (hook && typeof hook.cleanup === 'function') {
49
- try { hook.cleanup(); } catch (err) { console.error('Error in effect cleanup:', err); }
50
- hook.cleanup = null;
51
- }
52
- }
53
- }
54
- fibers.clear();
55
- }
56
-
57
- // ─── Render context ──────────────────────────────────────────
58
-
59
- const pathStack = []; // component segments, outermost first
60
- const counters = []; // per-component occurrence counters
61
- let rootCounter = 0;
62
- let baseId = '';
63
-
64
- // Begin a fresh render pass rooted at `base` (e.g. "page:/about").
65
- export function beginRender(base) {
66
- baseId = base || '';
67
- pathStack.length = 0;
68
- counters.length = 0;
69
- rootCounter = 0;
70
- }
71
-
72
- function nextKey(props) {
73
- if (props && props.key !== undefined && props.key !== null) return 'k:' + props.key;
74
- if (counters.length === 0) return 'i:' + (++rootCounter);
75
- const top = counters.length - 1;
76
- return 'i:' + (++counters[top]);
77
- }
78
-
79
- function componentName(fn) {
80
- return fn.displayName || fn.name || 'Anonymous';
81
- }
82
-
83
- // ─── JSX factory ─────────────────────────────────────────────
84
-
85
- export function h(tag, props, ...children) {
86
- // Everything is a lazy descriptor. Elements are only turned into real
87
- // DOM during materialization, so nested components are always executed
88
- // top-down (giving correct context + hook scoping).
89
- let kids = flatChildren(children);
90
- if (kids.length === 0 && props && props.children !== undefined && props.children !== null) {
91
- kids = flatChildren(Array.isArray(props.children) ? props.children : [props.children]);
92
- }
93
- return { [VNODE]: true, type: tag, props: props || null, children: kids };
94
- }
95
-
96
- function flatChildren(children) {
97
- return children
98
- .flat(Infinity)
99
- .filter(c => c !== null && c !== undefined && c !== false && c !== true);
100
- }
101
-
102
- // ─── DOM element creation (eager) ────────────────────────────
103
-
104
- function createElement(tag, props, kids) {
105
- const el = document.createElement(tag);
106
- applyProps(el, tag, props);
107
- appendNodes(el, materialize(kids));
108
- return el;
109
- }
110
-
111
- const EVENT_ALIASES = {
112
- doubleclick: 'dblclick',
113
- };
114
-
115
- function eventNameFromProp(key) {
116
- const name = key.slice(2).toLowerCase();
117
- return EVENT_ALIASES[name] || name;
118
- }
119
-
120
- function applyProps(el, tag, props) {
121
- if (!props) return;
122
-
123
- for (const key in props) {
124
- if (key === 'children' || key === 'key') continue;
125
- const value = props[key];
126
-
127
- if (key === 'ref') {
128
- if (typeof value === 'function') value(el);
129
- else if (value && typeof value === 'object') value.current = el;
130
- } else if (key === 'className' || key === 'class') {
131
- if (value != null) el.className = value;
132
- } else if (key === 'htmlFor') {
133
- if (value != null) el.setAttribute('for', value);
134
- } else if (key === 'style') {
135
- if (value && typeof value === 'object') Object.assign(el.style, value);
136
- else if (value != null) el.setAttribute('style', value);
137
- } else if (key === 'dangerouslySetInnerHTML') {
138
- if (value && value.__html != null) el.innerHTML = value.__html;
139
- } else if (key === 'value' && (tag === 'input' || tag === 'textarea' || tag === 'select')) {
140
- el.value = value == null ? '' : value;
141
- } else if (key === 'checked' && tag === 'input') {
142
- el.checked = !!value;
143
- } else if (key.startsWith('on') && typeof value === 'function') {
144
- el.addEventListener(eventNameFromProp(key), value);
145
- } else if (typeof value === 'boolean') {
146
- if (value) el.setAttribute(key, '');
147
- else el.removeAttribute(key);
148
- } else if (value != null) {
149
- el.setAttribute(key, value);
150
- }
151
- }
152
- }
153
-
154
- // ─── Materialization ─────────────────────────────────────────
155
-
156
- // Turn any node value into real DOM node(s).
157
- // Returns: Node | Node[] | null
158
- export function materialize(node) {
159
- if (node === null || node === undefined || node === false || node === true) return null;
160
-
161
- if (Array.isArray(node)) {
162
- const out = [];
163
- for (const child of node) {
164
- const m = materialize(child);
165
- if (Array.isArray(m)) {
166
- for (const n of m) if (n !== null) out.push(n);
167
- } else if (m !== null) {
168
- out.push(m);
169
- }
170
- }
171
- return out;
172
- }
173
-
174
- if (typeof node === 'object' && node[VNODE]) {
175
- return materializeVNode(node);
176
- }
177
-
178
- if (typeof Node !== 'undefined' && node instanceof Node) return node;
179
-
180
- return document.createTextNode(String(node));
181
- }
182
-
183
- function materializeVNode(vnode) {
184
- const { type, props, children } = vnode;
185
-
186
- if (type === Fragment) return materialize(children);
187
- if (typeof type === 'function') return materializeComponent(type, props, children);
188
-
189
- return createElement(type, props, children);
190
- }
191
-
192
- function materializeComponent(type, props, children) {
193
- const segment = componentName(type) + '@' + nextKey(props);
194
- pathStack.push(segment);
195
- counters.push(0);
196
-
197
- const id = (baseId ? baseId + '>' : '') + pathStack.join('>');
198
- const fiber = getFiber(id);
199
-
200
- prepareHooks(fiber);
201
-
202
- let output;
203
- try {
204
- output = type({ ...(props || {}), children });
205
- finishHooks();
206
- } catch (err) {
207
- finishHooks();
208
- counters.pop();
209
- pathStack.pop();
210
- throw err;
211
- }
212
-
213
- // Materialize the component's output while its scope is still on the
214
- // stack so child fibers nest under this component (stable keys).
215
- const nodes = materialize(output);
216
- counters.pop();
217
- pathStack.pop();
218
- return nodes;
219
- }
220
-
221
- export function appendNodes(container, nodes) {
222
- if (nodes === null || nodes === undefined) return;
223
- if (Array.isArray(nodes)) {
224
- for (const n of nodes) {
225
- if (n !== null && n !== undefined) container.appendChild(n);
226
- }
227
- } else {
228
- container.appendChild(nodes);
229
- }
230
- }
231
-
232
- // ─── Mounting ────────────────────────────────────────────────
233
-
234
- // Mount a page wrapped in its layout chain.
235
- // `layouts` must be ordered outermost → innermost.
236
- export function mountPage(PageComponent, layouts, container, base) {
237
- beginRender(base || 'page');
238
-
239
- let tree = h(PageComponent, {});
240
- for (let i = layouts.length - 1; i >= 0; i--) {
241
- tree = h(layouts[i], { children: tree });
242
- }
243
-
244
- const nodes = materialize(tree);
245
- container.innerHTML = '';
246
- appendNodes(container, nodes);
247
-
248
- return container;
249
- }
250
-
251
- // Simple mount helper (kept for backwards compatibility).
252
- export function render(component, container) {
253
- beginRender('root');
254
- const tree = typeof component === 'function' ? h(component, {}) : component;
255
- const nodes = materialize(tree);
256
- container.innerHTML = '';
257
- appendNodes(container, nodes);
258
- return container;
259
- }
260
-
261
- // Let hooks.js materialize context-provider children in scope.
262
- setMaterializer(materialize);
263
-
264
- export default { h, render, materialize, Fragment, mountPage, disposeFibers, getFiber };
1
+ // ============================================================
2
+ // Aziz.js Runtime
3
+ //
4
+ // h() — JSX factory (like React.createElement)
5
+ // materialize — turn a node tree into real DOM
6
+ // mountPage — mount a page + its layout chain
7
+ //
8
+ // Rendering model
9
+ // ---------------
10
+ // Function (component) tags are LAZY: h(Component) returns a
11
+ // lightweight descriptor. Descriptors are executed top-down when
12
+ // they are attached to the DOM, which gives every component
13
+ // instance its own hook "fiber" (keyed by its position in the
14
+ // tree) and makes context propagation work correctly.
15
+ // ============================================================
16
+
17
+ import { prepareHooks, finishHooks, setMaterializer } from './hooks.js';
18
+
19
+ export const Fragment = Symbol('Fragment');
20
+
21
+ const VNODE = Symbol('aziz.vnode');
22
+
23
+ // ─── Fibers ──────────────────────────────────────────────────
24
+ // One fiber per component instance, keyed by a stable tree path.
25
+
26
+ const fibers = new Map();
27
+
28
+ export function getFiber(id) {
29
+ let fiber = fibers.get(id);
30
+ if (!fiber) {
31
+ fiber = { id, hooks: [], mounted: true };
32
+ fibers.set(id, fiber);
33
+ } else {
34
+ fiber.mounted = true;
35
+ }
36
+ return fiber;
37
+ }
38
+
39
+ export function fiberCount() {
40
+ return fibers.size;
41
+ }
42
+
43
+ // Unmount everything: run pending effect cleanups, drop state.
44
+ export function disposeFibers() {
45
+ for (const fiber of fibers.values()) {
46
+ fiber.mounted = false;
47
+ for (const hook of fiber.hooks) {
48
+ if (hook && typeof hook.cleanup === 'function') {
49
+ try { hook.cleanup(); } catch (err) { console.error('Error in effect cleanup:', err); }
50
+ hook.cleanup = null;
51
+ }
52
+ }
53
+ }
54
+ fibers.clear();
55
+ }
56
+
57
+ // ─── Render context ──────────────────────────────────────────
58
+
59
+ const pathStack = []; // component segments, outermost first
60
+ const counters = []; // per-component occurrence counters
61
+ let rootCounter = 0;
62
+ let baseId = '';
63
+
64
+ // Begin a fresh render pass rooted at `base` (e.g. "page:/about").
65
+ export function beginRender(base) {
66
+ baseId = base || '';
67
+ pathStack.length = 0;
68
+ counters.length = 0;
69
+ rootCounter = 0;
70
+ }
71
+
72
+ function nextKey(props) {
73
+ if (props && props.key !== undefined && props.key !== null) return 'k:' + props.key;
74
+ if (counters.length === 0) return 'i:' + (++rootCounter);
75
+ const top = counters.length - 1;
76
+ return 'i:' + (++counters[top]);
77
+ }
78
+
79
+ function componentName(fn) {
80
+ return fn.displayName || fn.name || 'Anonymous';
81
+ }
82
+
83
+ // ─── JSX factory ─────────────────────────────────────────────
84
+
85
+ export function h(tag, props, ...children) {
86
+ // Everything is a lazy descriptor. Elements are only turned into real
87
+ // DOM during materialization, so nested components are always executed
88
+ // top-down (giving correct context + hook scoping).
89
+ let kids = flatChildren(children);
90
+ if (kids.length === 0 && props && props.children !== undefined && props.children !== null) {
91
+ kids = flatChildren(Array.isArray(props.children) ? props.children : [props.children]);
92
+ }
93
+ return { [VNODE]: true, type: tag, props: props || null, children: kids };
94
+ }
95
+
96
+ function flatChildren(children) {
97
+ return children
98
+ .flat(Infinity)
99
+ .filter(c => c !== null && c !== undefined && c !== false && c !== true);
100
+ }
101
+
102
+ // ─── DOM element creation (eager) ────────────────────────────
103
+
104
+ function createElement(tag, props, kids) {
105
+ const el = document.createElement(tag);
106
+ applyProps(el, tag, props);
107
+ appendNodes(el, materialize(kids));
108
+ return el;
109
+ }
110
+
111
+ const EVENT_ALIASES = {
112
+ doubleclick: 'dblclick',
113
+ };
114
+
115
+ function eventNameFromProp(key) {
116
+ const name = key.slice(2).toLowerCase();
117
+ return EVENT_ALIASES[name] || name;
118
+ }
119
+
120
+ function applyProps(el, tag, props) {
121
+ if (!props) return;
122
+
123
+ for (const key in props) {
124
+ if (key === 'children' || key === 'key') continue;
125
+ const value = props[key];
126
+
127
+ if (key === 'ref') {
128
+ if (typeof value === 'function') value(el);
129
+ else if (value && typeof value === 'object') value.current = el;
130
+ } else if (key === 'className' || key === 'class') {
131
+ if (value != null) el.className = value;
132
+ } else if (key === 'htmlFor') {
133
+ if (value != null) el.setAttribute('for', value);
134
+ } else if (key === 'style') {
135
+ if (value && typeof value === 'object') Object.assign(el.style, value);
136
+ else if (value != null) el.setAttribute('style', value);
137
+ } else if (key === 'dangerouslySetInnerHTML') {
138
+ if (value && value.__html != null) el.innerHTML = value.__html;
139
+ } else if (key === 'value' && (tag === 'input' || tag === 'textarea' || tag === 'select')) {
140
+ el.value = value == null ? '' : value;
141
+ } else if (key === 'checked' && tag === 'input') {
142
+ el.checked = !!value;
143
+ } else if (key.startsWith('on') && typeof value === 'function') {
144
+ el.addEventListener(eventNameFromProp(key), value);
145
+ } else if (typeof value === 'boolean') {
146
+ if (value) el.setAttribute(key, '');
147
+ else el.removeAttribute(key);
148
+ } else if (value != null) {
149
+ el.setAttribute(key, value);
150
+ }
151
+ }
152
+ }
153
+
154
+ // ─── Materialization ─────────────────────────────────────────
155
+
156
+ // Turn any node value into real DOM node(s).
157
+ // Returns: Node | Node[] | null
158
+ export function materialize(node) {
159
+ if (node === null || node === undefined || node === false || node === true) return null;
160
+
161
+ if (Array.isArray(node)) {
162
+ const out = [];
163
+ for (const child of node) {
164
+ const m = materialize(child);
165
+ if (Array.isArray(m)) {
166
+ for (const n of m) if (n !== null) out.push(n);
167
+ } else if (m !== null) {
168
+ out.push(m);
169
+ }
170
+ }
171
+ return out;
172
+ }
173
+
174
+ if (typeof node === 'object' && node[VNODE]) {
175
+ return materializeVNode(node);
176
+ }
177
+
178
+ if (typeof Node !== 'undefined' && node instanceof Node) return node;
179
+
180
+ return document.createTextNode(String(node));
181
+ }
182
+
183
+ function materializeVNode(vnode) {
184
+ const { type, props, children } = vnode;
185
+
186
+ if (type === Fragment) return materialize(children);
187
+ if (typeof type === 'function') return materializeComponent(type, props, children);
188
+
189
+ return createElement(type, props, children);
190
+ }
191
+
192
+ function materializeComponent(type, props, children) {
193
+ const segment = componentName(type) + '@' + nextKey(props);
194
+ pathStack.push(segment);
195
+ counters.push(0);
196
+
197
+ const id = (baseId ? baseId + '>' : '') + pathStack.join('>');
198
+ const fiber = getFiber(id);
199
+
200
+ prepareHooks(fiber);
201
+
202
+ let output;
203
+ try {
204
+ output = type({ ...(props || {}), children });
205
+ finishHooks();
206
+ } catch (err) {
207
+ finishHooks();
208
+ counters.pop();
209
+ pathStack.pop();
210
+ throw err;
211
+ }
212
+
213
+ // Materialize the component's output while its scope is still on the
214
+ // stack so child fibers nest under this component (stable keys).
215
+ const nodes = materialize(output);
216
+ counters.pop();
217
+ pathStack.pop();
218
+ return nodes;
219
+ }
220
+
221
+ export function appendNodes(container, nodes) {
222
+ if (nodes === null || nodes === undefined) return;
223
+ if (Array.isArray(nodes)) {
224
+ for (const n of nodes) {
225
+ if (n !== null && n !== undefined) container.appendChild(n);
226
+ }
227
+ } else {
228
+ container.appendChild(nodes);
229
+ }
230
+ }
231
+
232
+ // ─── Mounting ────────────────────────────────────────────────
233
+
234
+ // Mount a page wrapped in its layout chain.
235
+ // `layouts` must be ordered outermost → innermost.
236
+ export function mountPage(PageComponent, layouts, container, base, props) {
237
+ beginRender(base || 'page');
238
+
239
+ let tree = h(PageComponent, props || {});
240
+ for (let i = layouts.length - 1; i >= 0; i--) {
241
+ tree = h(layouts[i], { children: tree });
242
+ }
243
+
244
+ const nodes = materialize(tree);
245
+ container.innerHTML = '';
246
+ appendNodes(container, nodes);
247
+
248
+ return container;
249
+ }
250
+
251
+ // Simple mount helper (kept for backwards compatibility).
252
+ export function render(component, container) {
253
+ beginRender('root');
254
+ const tree = typeof component === 'function' ? h(component, {}) : component;
255
+ const nodes = materialize(tree);
256
+ container.innerHTML = '';
257
+ appendNodes(container, nodes);
258
+ return container;
259
+ }
260
+
261
+ // Let hooks.js materialize context-provider children in scope.
262
+ setMaterializer(materialize);
263
+
264
+ export default { h, render, materialize, Fragment, mountPage, disposeFibers, getFiber };
@@ -1,18 +1,18 @@
1
- // Root layout — wraps every page (like Next.js app/layout.js)
2
- export default function Layout({ children }) {
3
- return (
4
- <div style={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
5
- <main style={{ flex: 1 }}>
6
- {children}
7
- </main>
8
- <footer>
9
- Built with Aziz.js
10
- </footer>
11
- </div>
12
- );
13
- }
14
-
15
- export const metadata = {
16
- title: 'My App',
17
- description: 'Built with Aziz.js'
18
- };
1
+ // Root layout — wraps every page (like Next.js app/layout.js)
2
+ export default function Layout({ children }) {
3
+ return (
4
+ <div style={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
5
+ <main style={{ flex: 1 }}>
6
+ {children}
7
+ </main>
8
+ <footer>
9
+ Built with Aziz.js
10
+ </footer>
11
+ </div>
12
+ );
13
+ }
14
+
15
+ export const metadata = {
16
+ title: 'My App',
17
+ description: 'Built with Aziz.js'
18
+ };
@@ -1,33 +1,33 @@
1
- // Home page — route: /
2
- export default function Page() {
3
- const [count, setCount] = useState(0);
4
-
5
- return (
6
- <div style={{ padding: '48px 32px', maxWidth: '720px', margin: '0 auto' }}>
7
- <h1>Welcome to Aziz.js</h1>
8
- <p>
9
- Your app is running. Edit <code>app/page.js</code> and save —
10
- the browser refreshes automatically.
11
- </p>
12
-
13
- <div class="card">
14
- <h2>Interactive state</h2>
15
- <p>A tiny slice of state, exactly like React's <code>useState</code>.</p>
16
- <div class="count-display">{count}</div>
17
- <div class="counter-actions">
18
- <button class="btn btn-danger" onClick={() => setCount(c => c - 1)}>−</button>
19
- <button class="btn btn-primary" onClick={() => setCount(c => c + 1)}>+</button>
20
- <button class="btn btn-grey" onClick={() => setCount(0)}>Reset</button>
21
- </div>
22
- </div>
23
-
24
- <div class="card">
25
- <h2>Add a page</h2>
26
- <p>
27
- Each <code>page.js</code> inside <code>app/</code> is a route.
28
- Create <code>app/about/page.js</code> and it becomes <code>/about</code>.
29
- </p>
30
- </div>
31
- </div>
32
- );
33
- }
1
+ // Home page — route: /
2
+ export default function Page() {
3
+ const [count, setCount] = useState(0);
4
+
5
+ return (
6
+ <div style={{ padding: '48px 32px', maxWidth: '720px', margin: '0 auto' }}>
7
+ <h1>Welcome to Aziz.js</h1>
8
+ <p>
9
+ Your app is running. Edit <code>app/page.js</code> and save —
10
+ the browser refreshes automatically.
11
+ </p>
12
+
13
+ <div class="card">
14
+ <h2>Interactive state</h2>
15
+ <p>A tiny slice of state, exactly like React's <code>useState</code>.</p>
16
+ <div class="count-display">{count}</div>
17
+ <div class="counter-actions">
18
+ <button class="btn btn-danger" onClick={() => setCount(c => c - 1)}>−</button>
19
+ <button class="btn btn-primary" onClick={() => setCount(c => c + 1)}>+</button>
20
+ <button class="btn btn-grey" onClick={() => setCount(0)}>Reset</button>
21
+ </div>
22
+ </div>
23
+
24
+ <div class="card">
25
+ <h2>Add a page</h2>
26
+ <p>
27
+ Each <code>page.js</code> inside <code>app/</code> is a route.
28
+ Create <code>app/about/page.js</code> and it becomes <code>/about</code>.
29
+ </p>
30
+ </div>
31
+ </div>
32
+ );
33
+ }