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,219 +1,219 @@
1
- // ============================================================
2
- // Aziz.js API Client
3
- // A tiny, dependency-free fetch wrapper for talking to
4
- // /api/** route handlers (or any REST backend).
5
- //
6
- // const users = await api.get('/api/users');
7
- // const created = await api.post('/api/users', { name: 'A' });
8
- // await api.put('/api/users?id=1', { name: 'B' });
9
- // await api.delete('/api/users?id=1');
10
- //
11
- // Features:
12
- // - baseURL + default headers
13
- // - automatic JSON encode / decode
14
- // - request timeouts (AbortController)
15
- // - external AbortSignal support
16
- // - retries with backoff for 5xx / network errors
17
- // - typed ApiError with { status, data }
18
- // - query string helper
19
- // ============================================================
20
-
21
- export class ApiError extends Error {
22
- constructor(message, { status = 0, data = null, url = null } = {}) {
23
- super(message);
24
- this.name = 'ApiError';
25
- this.status = status;
26
- this.data = data;
27
- this.url = url;
28
- }
29
- }
30
-
31
- const defaults = {
32
- baseURL: '',
33
- timeout: 15000,
34
- retries: 0,
35
- headers: {},
36
- };
37
-
38
- function buildURL(path, query) {
39
- let url = /^https?:\/\//i.test(path)
40
- ? path
41
- : defaults.baseURL
42
- ? defaults.baseURL.replace(/\/+$/, '') + (path.startsWith('/') ? path : '/' + path)
43
- : path;
44
-
45
- if (query && typeof query === 'object') {
46
- const qs = new URLSearchParams();
47
- for (const [key, value] of Object.entries(query)) {
48
- if (value === undefined || value === null) continue;
49
- if (Array.isArray(value)) value.forEach(v => qs.append(key, v));
50
- else qs.append(key, value);
51
- }
52
- const s = qs.toString();
53
- if (s) url += (url.includes('?') ? '&' : '?') + s;
54
- }
55
-
56
- return url;
57
- }
58
-
59
- function sleep(ms) {
60
- return new Promise(resolve => setTimeout(resolve, ms));
61
- }
62
-
63
- async function parseResponse(res, raw) {
64
- if (raw) return res;
65
-
66
- const contentType = (res.headers.get('content-type') || '').toLowerCase();
67
- if (res.status === 204 || res.status === 205) return null;
68
-
69
- if (contentType.includes('application/json')) {
70
- return res.json().catch(() => null);
71
- }
72
- if (contentType.startsWith('text/') || contentType.includes('html') || contentType.includes('xml')) {
73
- return res.text();
74
- }
75
- return res.blob();
76
- }
77
-
78
- function extractMessage(data, status) {
79
- if (data && typeof data === 'object') {
80
- const raw = data.error ?? data.message ?? data.detail;
81
- if (typeof raw === 'string') return raw;
82
- if (raw && typeof raw === 'object') return raw.message || JSON.stringify(raw);
83
- }
84
- if (typeof data === 'string' && data.trim()) return data.trim().slice(0, 200);
85
- return `Request failed with status ${status}`;
86
- }
87
-
88
- async function request(path, options = {}) {
89
- const {
90
- method = 'GET',
91
- body,
92
- headers = {},
93
- query,
94
- timeout = defaults.timeout,
95
- retries = defaults.retries,
96
- signal,
97
- raw = false,
98
- } = options;
99
-
100
- const url = buildURL(path, query);
101
-
102
- const finalHeaders = { 'Accept': 'application/json', ...defaults.headers, ...headers };
103
- let payload = body;
104
-
105
- const isFormData = typeof FormData !== 'undefined' && body instanceof FormData;
106
- const isBlob = typeof Blob !== 'undefined' && body instanceof Blob;
107
-
108
- if (body !== undefined && body !== null && !isFormData && !isBlob && typeof body !== 'string') {
109
- payload = JSON.stringify(body);
110
- if (!finalHeaders['Content-Type']) finalHeaders['Content-Type'] = 'application/json';
111
- }
112
-
113
- let lastError;
114
-
115
- for (let attempt = 0; attempt <= retries; attempt++) {
116
- const controller = new AbortController();
117
- let timedOut = false;
118
-
119
- const timer = timeout > 0
120
- ? setTimeout(() => { timedOut = true; controller.abort(); }, timeout)
121
- : null;
122
-
123
- const onExternalAbort = () => controller.abort();
124
- if (signal) {
125
- if (signal.aborted) controller.abort();
126
- else signal.addEventListener('abort', onExternalAbort, { once: true });
127
- }
128
-
129
- try {
130
- const res = await fetch(url, {
131
- method,
132
- headers: finalHeaders,
133
- body: payload,
134
- signal: controller.signal,
135
- });
136
-
137
- const data = await parseResponse(res, raw);
138
-
139
- if (!res.ok) {
140
- const err = new ApiError(extractMessage(data, res.status), {
141
- status: res.status,
142
- data: raw ? null : data,
143
- url,
144
- });
145
- // Only retry server errors / rate limits
146
- if (res.status >= 500 || res.status === 429) {
147
- lastError = err;
148
- } else {
149
- throw err;
150
- }
151
- } else {
152
- return data;
153
- }
154
- } catch (err) {
155
- if (err instanceof ApiError && !(err.status >= 500 || err.status === 429)) {
156
- throw err; // client error — never retry
157
- }
158
- if (err.name === 'AbortError') {
159
- if (signal && signal.aborted) throw err; // caller aborted
160
- lastError = new ApiError(
161
- timedOut ? `Request timed out after ${timeout}ms` : 'Request aborted',
162
- { status: 0, url }
163
- );
164
- } else if (err instanceof ApiError) {
165
- lastError = err;
166
- } else {
167
- lastError = new ApiError(err.message || 'Network error', { status: 0, url });
168
- }
169
- } finally {
170
- if (timer) clearTimeout(timer);
171
- if (signal) signal.removeEventListener('abort', onExternalAbort);
172
- }
173
-
174
- if (attempt < retries) {
175
- await sleep(200 * Math.pow(2, attempt)); // 200ms, 400ms, 800ms…
176
- }
177
- }
178
-
179
- throw lastError || new ApiError('Request failed', { status: 0, url });
180
- }
181
-
182
- export const api = {
183
- ApiError,
184
-
185
- /** Set a base URL applied to every relative path. */
186
- setBaseURL(baseURL) { defaults.baseURL = baseURL || ''; return api; },
187
-
188
- /** Merge default headers (e.g. Authorization). */
189
- setHeaders(headers) { Object.assign(defaults.headers, headers || {}); return api; },
190
-
191
- /** Configure timeout / retries / baseURL / headers at once. */
192
- configure(opts = {}) {
193
- if ('baseURL' in opts) defaults.baseURL = opts.baseURL || '';
194
- if ('timeout' in opts) defaults.timeout = opts.timeout;
195
- if ('retries' in opts) defaults.retries = opts.retries;
196
- if ('headers' in opts) Object.assign(defaults.headers, opts.headers || {});
197
- return api;
198
- },
199
-
200
- get defaults() { return { ...defaults, headers: { ...defaults.headers } }; },
201
-
202
- request,
203
-
204
- get: (path, options) => request(path, { ...options, method: 'GET' }),
205
- post: (path, body, options) => request(path, { ...options, method: 'POST', body }),
206
- put: (path, body, options) => request(path, { ...options, method: 'PUT', body }),
207
- patch: (path, body, options) => request(path, { ...options, method: 'PATCH', body }),
208
- delete: (path, options) => request(path, { ...options, method: 'DELETE' }),
209
- del: (path, options) => request(path, { ...options, method: 'DELETE' }),
210
- head: (path, options) => request(path, { ...options, method: 'HEAD', raw: true }),
211
-
212
- /** Send multipart/form-data. */
213
- form: (path, formData, options) => request(path, { ...options, method: 'POST', body: formData }),
214
-
215
- /** Low-level: return the raw Response instead of parsed data. */
216
- raw: (path, options) => request(path, { ...options, raw: true }),
217
- };
218
-
219
- export default api;
1
+ // ============================================================
2
+ // Aziz.js API Client
3
+ // A tiny, dependency-free fetch wrapper for talking to
4
+ // /api/** route handlers (or any REST backend).
5
+ //
6
+ // const users = await api.get('/api/users');
7
+ // const created = await api.post('/api/users', { name: 'A' });
8
+ // await api.put('/api/users?id=1', { name: 'B' });
9
+ // await api.delete('/api/users?id=1');
10
+ //
11
+ // Features:
12
+ // - baseURL + default headers
13
+ // - automatic JSON encode / decode
14
+ // - request timeouts (AbortController)
15
+ // - external AbortSignal support
16
+ // - retries with backoff for 5xx / network errors
17
+ // - typed ApiError with { status, data }
18
+ // - query string helper
19
+ // ============================================================
20
+
21
+ export class ApiError extends Error {
22
+ constructor(message, { status = 0, data = null, url = null } = {}) {
23
+ super(message);
24
+ this.name = 'ApiError';
25
+ this.status = status;
26
+ this.data = data;
27
+ this.url = url;
28
+ }
29
+ }
30
+
31
+ const defaults = {
32
+ baseURL: '',
33
+ timeout: 15000,
34
+ retries: 0,
35
+ headers: {},
36
+ };
37
+
38
+ function buildURL(path, query) {
39
+ let url = /^https?:\/\//i.test(path)
40
+ ? path
41
+ : defaults.baseURL
42
+ ? defaults.baseURL.replace(/\/+$/, '') + (path.startsWith('/') ? path : '/' + path)
43
+ : path;
44
+
45
+ if (query && typeof query === 'object') {
46
+ const qs = new URLSearchParams();
47
+ for (const [key, value] of Object.entries(query)) {
48
+ if (value === undefined || value === null) continue;
49
+ if (Array.isArray(value)) value.forEach(v => qs.append(key, v));
50
+ else qs.append(key, value);
51
+ }
52
+ const s = qs.toString();
53
+ if (s) url += (url.includes('?') ? '&' : '?') + s;
54
+ }
55
+
56
+ return url;
57
+ }
58
+
59
+ function sleep(ms) {
60
+ return new Promise(resolve => setTimeout(resolve, ms));
61
+ }
62
+
63
+ async function parseResponse(res, raw) {
64
+ if (raw) return res;
65
+
66
+ const contentType = (res.headers.get('content-type') || '').toLowerCase();
67
+ if (res.status === 204 || res.status === 205) return null;
68
+
69
+ if (contentType.includes('application/json')) {
70
+ return res.json().catch(() => null);
71
+ }
72
+ if (contentType.startsWith('text/') || contentType.includes('html') || contentType.includes('xml')) {
73
+ return res.text();
74
+ }
75
+ return res.blob();
76
+ }
77
+
78
+ function extractMessage(data, status) {
79
+ if (data && typeof data === 'object') {
80
+ const raw = data.error ?? data.message ?? data.detail;
81
+ if (typeof raw === 'string') return raw;
82
+ if (raw && typeof raw === 'object') return raw.message || JSON.stringify(raw);
83
+ }
84
+ if (typeof data === 'string' && data.trim()) return data.trim().slice(0, 200);
85
+ return `Request failed with status ${status}`;
86
+ }
87
+
88
+ async function request(path, options = {}) {
89
+ const {
90
+ method = 'GET',
91
+ body,
92
+ headers = {},
93
+ query,
94
+ timeout = defaults.timeout,
95
+ retries = defaults.retries,
96
+ signal,
97
+ raw = false,
98
+ } = options;
99
+
100
+ const url = buildURL(path, query);
101
+
102
+ const finalHeaders = { 'Accept': 'application/json', ...defaults.headers, ...headers };
103
+ let payload = body;
104
+
105
+ const isFormData = typeof FormData !== 'undefined' && body instanceof FormData;
106
+ const isBlob = typeof Blob !== 'undefined' && body instanceof Blob;
107
+
108
+ if (body !== undefined && body !== null && !isFormData && !isBlob && typeof body !== 'string') {
109
+ payload = JSON.stringify(body);
110
+ if (!finalHeaders['Content-Type']) finalHeaders['Content-Type'] = 'application/json';
111
+ }
112
+
113
+ let lastError;
114
+
115
+ for (let attempt = 0; attempt <= retries; attempt++) {
116
+ const controller = new AbortController();
117
+ let timedOut = false;
118
+
119
+ const timer = timeout > 0
120
+ ? setTimeout(() => { timedOut = true; controller.abort(); }, timeout)
121
+ : null;
122
+
123
+ const onExternalAbort = () => controller.abort();
124
+ if (signal) {
125
+ if (signal.aborted) controller.abort();
126
+ else signal.addEventListener('abort', onExternalAbort, { once: true });
127
+ }
128
+
129
+ try {
130
+ const res = await fetch(url, {
131
+ method,
132
+ headers: finalHeaders,
133
+ body: payload,
134
+ signal: controller.signal,
135
+ });
136
+
137
+ const data = await parseResponse(res, raw);
138
+
139
+ if (!res.ok) {
140
+ const err = new ApiError(extractMessage(data, res.status), {
141
+ status: res.status,
142
+ data: raw ? null : data,
143
+ url,
144
+ });
145
+ // Only retry server errors / rate limits
146
+ if (res.status >= 500 || res.status === 429) {
147
+ lastError = err;
148
+ } else {
149
+ throw err;
150
+ }
151
+ } else {
152
+ return data;
153
+ }
154
+ } catch (err) {
155
+ if (err instanceof ApiError && !(err.status >= 500 || err.status === 429)) {
156
+ throw err; // client error — never retry
157
+ }
158
+ if (err.name === 'AbortError') {
159
+ if (signal && signal.aborted) throw err; // caller aborted
160
+ lastError = new ApiError(
161
+ timedOut ? `Request timed out after ${timeout}ms` : 'Request aborted',
162
+ { status: 0, url }
163
+ );
164
+ } else if (err instanceof ApiError) {
165
+ lastError = err;
166
+ } else {
167
+ lastError = new ApiError(err.message || 'Network error', { status: 0, url });
168
+ }
169
+ } finally {
170
+ if (timer) clearTimeout(timer);
171
+ if (signal) signal.removeEventListener('abort', onExternalAbort);
172
+ }
173
+
174
+ if (attempt < retries) {
175
+ await sleep(200 * Math.pow(2, attempt)); // 200ms, 400ms, 800ms…
176
+ }
177
+ }
178
+
179
+ throw lastError || new ApiError('Request failed', { status: 0, url });
180
+ }
181
+
182
+ export const api = {
183
+ ApiError,
184
+
185
+ /** Set a base URL applied to every relative path. */
186
+ setBaseURL(baseURL) { defaults.baseURL = baseURL || ''; return api; },
187
+
188
+ /** Merge default headers (e.g. Authorization). */
189
+ setHeaders(headers) { Object.assign(defaults.headers, headers || {}); return api; },
190
+
191
+ /** Configure timeout / retries / baseURL / headers at once. */
192
+ configure(opts = {}) {
193
+ if ('baseURL' in opts) defaults.baseURL = opts.baseURL || '';
194
+ if ('timeout' in opts) defaults.timeout = opts.timeout;
195
+ if ('retries' in opts) defaults.retries = opts.retries;
196
+ if ('headers' in opts) Object.assign(defaults.headers, opts.headers || {});
197
+ return api;
198
+ },
199
+
200
+ get defaults() { return { ...defaults, headers: { ...defaults.headers } }; },
201
+
202
+ request,
203
+
204
+ get: (path, options) => request(path, { ...options, method: 'GET' }),
205
+ post: (path, body, options) => request(path, { ...options, method: 'POST', body }),
206
+ put: (path, body, options) => request(path, { ...options, method: 'PUT', body }),
207
+ patch: (path, body, options) => request(path, { ...options, method: 'PATCH', body }),
208
+ delete: (path, options) => request(path, { ...options, method: 'DELETE' }),
209
+ del: (path, options) => request(path, { ...options, method: 'DELETE' }),
210
+ head: (path, options) => request(path, { ...options, method: 'HEAD', raw: true }),
211
+
212
+ /** Send multipart/form-data. */
213
+ form: (path, formData, options) => request(path, { ...options, method: 'POST', body: formData }),
214
+
215
+ /** Low-level: return the raw Response instead of parsed data. */
216
+ raw: (path, options) => request(path, { ...options, raw: true }),
217
+ };
218
+
219
+ export default api;
@@ -1,33 +1,33 @@
1
- // ============================================================
2
- // Aziz.js Framework Boot
3
- // ============================================================
4
-
5
- import { h, Fragment } from '/_framework/runtime.js';
6
- import { initHooks } from '/_framework/hooks.js';
7
- import { router } from '/_framework/router.js';
8
- import api from '/_framework/api.js';
9
-
10
- // Make h and Fragment globally available for JSX in pages
11
- window.h = h;
12
- window.Fragment = Fragment;
13
-
14
- // Global API client: api.get('/api/users'), api.post(...), etc.
15
- window.api = api;
16
-
17
- async function boot() {
18
- initHooks();
19
- await router.init();
20
- window.__azizReady = true;
21
- }
22
-
23
- boot().catch(err => {
24
- console.error('❌ Aziz.js boot failed:', err);
25
- const root = document.getElementById('root');
26
- if (root) {
27
- root.innerHTML = `<div style="padding:40px;font-family:monospace;color:#b00;background:#fff1f0;border:1px solid #e88;border-radius:8px;margin:40px">
28
- <h2>⚡ Aziz.js Boot Error</h2>
29
- <pre style="white-space:pre-wrap">${String(err && err.message || err)}</pre>
30
- <p style="color:#666;font-size:13px">Check the browser console for details.</p>
31
- </div>`;
32
- }
33
- });
1
+ // ============================================================
2
+ // Aziz.js Framework Boot
3
+ // ============================================================
4
+
5
+ import { h, Fragment } from '/_framework/runtime.js';
6
+ import { initHooks } from '/_framework/hooks.js';
7
+ import { router } from '/_framework/router.js';
8
+ import api from '/_framework/api.js';
9
+
10
+ // Make h and Fragment globally available for JSX in pages
11
+ window.h = h;
12
+ window.Fragment = Fragment;
13
+
14
+ // Global API client: api.get('/api/users'), api.post(...), etc.
15
+ window.api = api;
16
+
17
+ async function boot() {
18
+ initHooks();
19
+ await router.init();
20
+ window.__azizReady = true;
21
+ }
22
+
23
+ boot().catch(err => {
24
+ console.error('❌ Aziz.js boot failed:', err);
25
+ const root = document.getElementById('root');
26
+ if (root) {
27
+ root.innerHTML = `<div style="padding:40px;font-family:monospace;color:#b00;background:#fff1f0;border:1px solid #e88;border-radius:8px;margin:40px">
28
+ <h2>⚡ Aziz.js Boot Error</h2>
29
+ <pre style="white-space:pre-wrap">${String(err && err.message || err)}</pre>
30
+ <p style="color:#666;font-size:13px">Check the browser console for details.</p>
31
+ </div>`;
32
+ }
33
+ });