zero-query 0.9.7 → 0.9.8

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 (56) hide show
  1. package/README.md +31 -3
  2. package/cli/commands/build.js +50 -3
  3. package/cli/commands/create.js +22 -9
  4. package/cli/help.js +2 -0
  5. package/cli/scaffold/default/app/app.js +211 -0
  6. package/cli/scaffold/default/app/components/about.js +201 -0
  7. package/cli/scaffold/default/app/components/api-demo.js +143 -0
  8. package/cli/scaffold/default/app/components/contact-card.js +231 -0
  9. package/cli/scaffold/default/app/components/contacts/contacts.css +706 -0
  10. package/cli/scaffold/default/app/components/contacts/contacts.html +200 -0
  11. package/cli/scaffold/default/app/components/contacts/contacts.js +196 -0
  12. package/cli/scaffold/default/app/components/counter.js +127 -0
  13. package/cli/scaffold/default/app/components/home.js +249 -0
  14. package/cli/scaffold/{app → default/app}/components/not-found.js +2 -2
  15. package/cli/scaffold/default/app/components/playground/playground.css +116 -0
  16. package/cli/scaffold/default/app/components/playground/playground.html +162 -0
  17. package/cli/scaffold/default/app/components/playground/playground.js +117 -0
  18. package/cli/scaffold/default/app/components/todos.js +225 -0
  19. package/cli/scaffold/default/app/components/toolkit/toolkit.css +97 -0
  20. package/cli/scaffold/default/app/components/toolkit/toolkit.html +146 -0
  21. package/cli/scaffold/default/app/components/toolkit/toolkit.js +280 -0
  22. package/cli/scaffold/default/app/routes.js +15 -0
  23. package/cli/scaffold/{app → default/app}/store.js +15 -10
  24. package/cli/scaffold/{global.css → default/global.css} +238 -252
  25. package/cli/scaffold/{index.html → default/index.html} +35 -0
  26. package/cli/scaffold/{app → minimal/app}/app.js +37 -39
  27. package/cli/scaffold/minimal/app/components/about.js +68 -0
  28. package/cli/scaffold/minimal/app/components/counter.js +122 -0
  29. package/cli/scaffold/minimal/app/components/home.js +68 -0
  30. package/cli/scaffold/minimal/app/components/not-found.js +16 -0
  31. package/cli/scaffold/minimal/app/routes.js +9 -0
  32. package/cli/scaffold/minimal/app/store.js +36 -0
  33. package/cli/scaffold/minimal/assets/.gitkeep +0 -0
  34. package/cli/scaffold/minimal/global.css +291 -0
  35. package/cli/scaffold/minimal/index.html +44 -0
  36. package/dist/zquery.dist.zip +0 -0
  37. package/dist/zquery.js +1942 -1925
  38. package/dist/zquery.min.js +2 -2
  39. package/index.d.ts +10 -1
  40. package/index.js +4 -3
  41. package/package.json +1 -1
  42. package/src/component.js +6 -3
  43. package/src/diff.js +15 -2
  44. package/tests/cli.test.js +304 -0
  45. package/cli/scaffold/app/components/about.js +0 -131
  46. package/cli/scaffold/app/components/api-demo.js +0 -103
  47. package/cli/scaffold/app/components/contacts/contacts.css +0 -246
  48. package/cli/scaffold/app/components/contacts/contacts.html +0 -140
  49. package/cli/scaffold/app/components/contacts/contacts.js +0 -153
  50. package/cli/scaffold/app/components/counter.js +0 -85
  51. package/cli/scaffold/app/components/home.js +0 -137
  52. package/cli/scaffold/app/components/todos.js +0 -131
  53. package/cli/scaffold/app/routes.js +0 -13
  54. /package/cli/scaffold/{LICENSE → default/LICENSE} +0 -0
  55. /package/cli/scaffold/{assets → default/assets}/.gitkeep +0 -0
  56. /package/cli/scaffold/{favicon.ico → default/favicon.ico} +0 -0
@@ -0,0 +1,280 @@
1
+ // toolkit.js — HTTP, utilities, and advanced store features
2
+ //
3
+ // Features used:
4
+ // $.http / $.post / $.put / $.delete — HTTP client + interceptors
5
+ // $.pipe / $.memoize / $.retry — functional utilities
6
+ // $.groupBy / $.chunk / $.unique — collection helpers
7
+ // store.use / snapshot / history — middleware & time-travel
8
+ // templateUrl / styleUrl — external template & styles
9
+
10
+ $.component('toolkit-page', {
11
+ templateUrl: 'toolkit.html',
12
+ styleUrl: 'toolkit.css',
13
+
14
+ state: () => ({
15
+ activeTab: 'http',
16
+ httpLog: [],
17
+ httpMethod: '',
18
+ httpEndpoint: '',
19
+ httpStatusCode: null,
20
+ httpOutput: '',
21
+ searchQuery: '',
22
+ searchResults: [],
23
+ searchLoading: false,
24
+ activeUtil: '',
25
+ utilOutput: '',
26
+ storeLog: [],
27
+ storeSnap: null,
28
+ storeHistory: null,
29
+ // Derived state for template expressions
30
+ methodClass: 'get',
31
+ statusOk: false,
32
+ actionCount: 0,
33
+ }),
34
+
35
+ // init() lifecycle — runs before the first render
36
+ init() {
37
+ this._prevBaseURL = $.http.getConfig().baseURL || '';
38
+ $.http.configure({ baseURL: 'https://jsonplaceholder.typicode.com' });
39
+
40
+ this._unsubReq = $.http.onRequest((opts, url) => {
41
+ this._pushLog('→ ' + (opts.method || 'GET') + ' ' + url);
42
+ });
43
+ this._unsubRes = $.http.onResponse((result) => {
44
+ this._pushLog('← ' + result.status + ' ' + (result.statusText || 'OK'));
45
+ });
46
+
47
+ this._mwActive = true;
48
+ $.getStore('main').use((action, args) => {
49
+ if (!this._mwActive) return;
50
+ const raw = this.state.storeLog.__raw || this.state.storeLog;
51
+ this.state.storeLog = [...raw, {
52
+ id: Date.now(), action,
53
+ args: JSON.stringify(args).slice(0, 60),
54
+ time: new Date().toLocaleTimeString(),
55
+ }].slice(-8);
56
+ });
57
+
58
+ // Keep actionCount in sync with store history
59
+ this._syncActionCount();
60
+ this._storeSub = $.getStore('main').subscribe(() => this._syncActionCount());
61
+ },
62
+
63
+ destroyed() {
64
+ this._mwActive = false;
65
+ if (this._unsubReq) this._unsubReq();
66
+ if (this._unsubRes) this._unsubRes();
67
+ if (this._abortCtrl) this._abortCtrl.abort();
68
+ if (this._storeSub) this._storeSub();
69
+ $.http.configure({ baseURL: this._prevBaseURL });
70
+ },
71
+
72
+ _pushLog(msg) {
73
+ const raw = this.state.httpLog.__raw || this.state.httpLog;
74
+ this.state.httpLog = [...raw, { id: Date.now(), msg }].slice(-10);
75
+ },
76
+
77
+ _syncActionCount() {
78
+ const store = $.getStore('main');
79
+ this.state.actionCount = store.history ? store.history.length : 0;
80
+ },
81
+
82
+ _updateHttpMeta(method, endpoint) {
83
+ this.state.httpMethod = method;
84
+ this.state.httpEndpoint = endpoint;
85
+ this.state.httpOutput = '';
86
+ this.state.httpStatusCode = null;
87
+ this.state.methodClass = method === 'DELETE' ? 'del' : (method || 'get').toLowerCase();
88
+ this.state.statusOk = false;
89
+ },
90
+
91
+ _setHttpResult(status, output) {
92
+ this.state.httpStatusCode = status;
93
+ this.state.statusOk = status >= 200 && status < 400;
94
+ this.state.httpOutput = output;
95
+ },
96
+
97
+ setTab(tab) {
98
+ this.state.activeTab = tab;
99
+ this.state.utilOutput = '';
100
+ this.state.activeUtil = '';
101
+ this.state.httpOutput = '';
102
+ this.state.httpMethod = '';
103
+ this.state.httpStatusCode = null;
104
+ this.state.storeSnap = null;
105
+ this.state.storeHistory = null;
106
+ },
107
+
108
+ /* ── HTTP demos ───────────────────────────────────────────── */
109
+
110
+ async doGet() {
111
+ this._updateHttpMeta('GET', '/posts/1');
112
+ try {
113
+ const { data, status } = await $.get('/posts/1');
114
+ this._setHttpResult(status, JSON.stringify(data, null, 2));
115
+ } catch (e) { this._setHttpResult(0, 'Error: ' + e.message); }
116
+ },
117
+
118
+ async doPost() {
119
+ this._updateHttpMeta('POST', '/posts');
120
+ try {
121
+ const { data, status } = await $.post('/posts', {
122
+ title: 'Created with zQuery', body: 'Sent via $.post()', userId: 1,
123
+ });
124
+ this._setHttpResult(status, JSON.stringify(data, null, 2));
125
+ $.bus.emit('toast', { message: 'POST successful!', type: 'success' });
126
+ } catch (e) { this._setHttpResult(0, 'Error: ' + e.message); }
127
+ },
128
+
129
+ async doPut() {
130
+ this._updateHttpMeta('PUT', '/posts/1');
131
+ try {
132
+ const { data, status } = await $.put('/posts/1', {
133
+ id: 1, title: 'Updated via $.put()', body: 'Full resource replacement', userId: 1,
134
+ });
135
+ this._setHttpResult(status, JSON.stringify(data, null, 2));
136
+ $.bus.emit('toast', { message: 'PUT successful!', type: 'success' });
137
+ } catch (e) { this._setHttpResult(0, 'Error: ' + e.message); }
138
+ },
139
+
140
+ async doDelete() {
141
+ this._updateHttpMeta('DELETE', '/posts/1');
142
+ try {
143
+ const { status } = await $.delete('/posts/1');
144
+ this._setHttpResult(status, 'Resource deleted successfully.');
145
+ $.bus.emit('toast', { message: 'DELETE sent', type: 'info' });
146
+ } catch (e) { this._setHttpResult(0, 'Error: ' + e.message); }
147
+ },
148
+
149
+ async doSearch(e) {
150
+ const q = e.target.value.trim().toLowerCase();
151
+ this.state.searchQuery = e.target.value;
152
+ if (!q) { this.state.searchResults = []; return; }
153
+
154
+ if (this._abortCtrl) this._abortCtrl.abort();
155
+ this._abortCtrl = $.http.createAbort();
156
+ this.state.searchLoading = true;
157
+
158
+ try {
159
+ const { data } = await $.get('/users', null, { signal: this._abortCtrl.signal });
160
+ this.state.searchResults = data.filter(u =>
161
+ u.name.toLowerCase().includes(q) || u.email.toLowerCase().includes(q)
162
+ ).slice(0, 5);
163
+ } catch (err) {
164
+ if (err.name !== 'AbortError') this.state.searchResults = [];
165
+ } finally { this.state.searchLoading = false; }
166
+ },
167
+
168
+ /* ── Utility demos ─────────────────────────────────────────── */
169
+
170
+ runUtil(name) {
171
+ this.state.activeUtil = name;
172
+ const demos = {
173
+ pipe: () => {
174
+ const slugify = $.pipe(
175
+ s => s.trim(), s => s.toLowerCase(),
176
+ s => s.replace(/[^a-z0-9]+/g, '-'), s => s.replace(/^-|-$/g, ''),
177
+ );
178
+ const input = ' Hello World! Creating Slugs ';
179
+ return `$.pipe(trim, lower, replace, strip)\n\nInput: "${input}"\nOutput: "${slugify(input)}"`;
180
+ },
181
+ memoize: () => {
182
+ const slowFib = (n) => n <= 1 ? n : slowFib(n - 1) + slowFib(n - 2);
183
+ const t0 = performance.now();
184
+ const result = slowFib(35);
185
+ const slowTime = performance.now() - t0;
186
+
187
+ const memoFib = $.memoize((n) => n <= 1 ? n : memoFib(n - 1) + memoFib(n - 2));
188
+ const t1 = performance.now();
189
+ memoFib(35);
190
+ const fastTime = performance.now() - t1;
191
+
192
+ const t2 = performance.now();
193
+ memoFib(35);
194
+ const cachedTime = performance.now() - t2;
195
+
196
+ return [
197
+ `$.memoize(fibonacci)`,
198
+ ``,
199
+ `fib(35) = ${result}`,
200
+ `────────────────────────────`,
201
+ `No memoize: ${slowTime.toFixed(1)} ms`,
202
+ `Memoized: ${fastTime.toFixed(3)} ms`,
203
+ `Cached: ${cachedTime.toFixed(4)} ms ⚡`,
204
+ ``,
205
+ `Speedup: ${(slowTime / Math.max(fastTime, 0.001)).toFixed(0)}× faster`,
206
+ ].join('\n');
207
+ },
208
+ retry: async () => {
209
+ this.state.utilOutput = '⏳ Running with exponential backoff…';
210
+ const log = [];
211
+ try {
212
+ await $.retry(async (attempt) => {
213
+ log.push(` #${attempt} ${attempt < 3 ? '✗ failed' : '✓ succeeded!'}`);
214
+ if (attempt < 3) throw new Error('Simulated failure');
215
+ return 'ok';
216
+ }, { attempts: 3, delay: 400, backoff: 2 });
217
+ } catch (e) { log.push(' All attempts failed.'); }
218
+ this.state.utilOutput = '$.retry({ attempts: 3, delay: 400, backoff: 2 })\n\n' + log.join('\n');
219
+ },
220
+ arrays: () => {
221
+ const data = [
222
+ { name: 'Alice', dept: 'eng' }, { name: 'Bob', dept: 'qa' },
223
+ { name: 'Carol', dept: 'eng' }, { name: 'Dave', dept: 'design' },
224
+ ];
225
+ return [
226
+ '$.groupBy(people, p => p.dept)',
227
+ JSON.stringify($.groupBy(data, p => p.dept), null, 2),
228
+ '',
229
+ '$.chunk($.range(1,9), 3) → ' + JSON.stringify($.chunk($.range(1, 9), 3)),
230
+ '$.unique([3,1,4,1,5,9]) → ' + JSON.stringify($.unique([3, 1, 4, 1, 5, 9, 2, 5])),
231
+ '$.range(0, 10, 2) → ' + JSON.stringify($.range(0, 10, 2)),
232
+ ].join('\n');
233
+ },
234
+ objects: () => {
235
+ const obj = { name: 'Tony', email: 'tony@dev.io', role: 'admin', secret: 'abc' };
236
+ const a = { x: 1, nested: { y: 2 } };
237
+ return [
238
+ `$.pick(obj, ["name","email"]) → ${JSON.stringify($.pick(obj, ['name', 'email']))}`,
239
+ `$.omit(obj, ["secret"]) → ${JSON.stringify($.omit(obj, ['secret']))}`,
240
+ `$.isEqual(a, b) → ${$.isEqual(a, { x: 1, nested: { y: 2 } })}`,
241
+ ``,
242
+ `$.deepMerge({}, a, { nested: { z: 3 } })`,
243
+ JSON.stringify($.deepMerge({}, a, { nested: { z: 3 } }), null, 2),
244
+ ].join('\n');
245
+ },
246
+ strings: () => [
247
+ `$.capitalize('hello') → "${$.capitalize('hello')}"`,
248
+ `$.truncate('Hello World!', 8) → "${$.truncate('Hello World!', 8)}"`,
249
+ `$.camelCase('my-component') → "${$.camelCase('my-component')}"`,
250
+ `$.kebabCase('myComponent') → "${$.kebabCase('myComponent')}"`,
251
+ ``,
252
+ `$.param({ q: 'test', page: 2 })`,
253
+ ` → "${$.param({ q: 'test', page: 2 })}"`,
254
+ ``,
255
+ `$.parseQuery('?q=test&page=2')`,
256
+ ` → ${JSON.stringify($.parseQuery('?q=test&page=2'))}`,
257
+ ].join('\n'),
258
+ };
259
+
260
+ const fn = demos[name];
261
+ if (!fn) return;
262
+ const result = fn();
263
+ if (result && typeof result.then === 'function') return;
264
+ this.state.utilOutput = result;
265
+ },
266
+
267
+ /* ── Store demos ──────────────────────────────────────────── */
268
+
269
+ takeSnapshot() {
270
+ this.state.storeSnap = JSON.stringify($.getStore('main').snapshot(), null, 2);
271
+ this.state.storeHistory = null;
272
+ $.bus.emit('toast', { message: 'Snapshot captured!', type: 'info' });
273
+ },
274
+
275
+ viewHistory() {
276
+ const h = $.getStore('main').history.slice(-6);
277
+ this.state.storeHistory = JSON.stringify(h, null, 2);
278
+ this.state.storeSnap = null;
279
+ },
280
+ });
@@ -0,0 +1,15 @@
1
+ // routes.js — Route definitions
2
+ //
3
+ // Maps URL paths to component tag names.
4
+ // Also supports :params, wildcards, and lazy loading via `load`.
5
+
6
+ export const routes = [
7
+ { path: '/', component: 'home-page' },
8
+ { path: '/counter', component: 'counter-page' },
9
+ { path: '/todos', component: 'todos-page' },
10
+ { path: '/contacts', component: 'contacts-page' },
11
+ { path: '/api', component: 'api-demo' },
12
+ { path: '/playground', component: 'playground-page' },
13
+ { path: '/toolkit', component: 'toolkit-page' },
14
+ { path: '/about', component: 'about-page' },
15
+ ];
@@ -1,8 +1,8 @@
1
- // app/store.js — global state management
1
+ // store.js — Global state management
2
2
  //
3
- // $.store() creates a centralized store with state, actions, and getters.
4
- // Components can dispatch actions and subscribe to changes.
5
- // The store is accessible anywhere via $.getStore('main').
3
+ // Defines a centralized store with state, actions, and getters.
4
+ // Any component can access it via $.getStore('main').
5
+ // Dispatch actions to update state; subscribe to react to changes.
6
6
 
7
7
  export const store = $.store('main', {
8
8
  state: {
@@ -11,11 +11,11 @@ export const store = $.store('main', {
11
11
 
12
12
  // Contacts
13
13
  contacts: [
14
- { id: 1, name: 'Tony Wiedman', email: 'tony@z-query.com', role: 'Developer', status: 'online', favorite: true },
15
- { id: 2, name: 'Robert Baratheon', email: 'robert@stormlands.io', role: 'Manager', status: 'offline', favorite: false },
16
- { id: 3, name: 'Terry A. Davis', email: 'terry@templeos.net', role: 'Developer', status: 'online', favorite: true },
17
- { id: 4, name: 'Trevor Moore', email: 'trevor@wkuk.tv', role: 'Designer', status: 'away', favorite: false },
18
- { id: 5, name: 'Carlo Acutis', email: 'carlo@vatican.va', role: 'Developer', status: 'online', favorite: false },
14
+ { id: 1, name: 'Tony Wiedman', email: 'tony@z-query.com', role: 'Developer', status: 'online', favorite: true, phone: '+1 (555) 201-0017', location: 'Philadelphia, PA', joined: '2024-01-15', bio: 'Full-stack developer & creator of zQuery.' },
15
+ { id: 2, name: 'Robert Baratheon', email: 'robert@stormlands.io', role: 'Manager', status: 'offline', favorite: false, phone: '+1 (555) 783-1042', location: "Storm's End, Westeros", joined: '2024-03-22', bio: 'Former king turned project manager.' },
16
+ { id: 3, name: 'Terry A. Davis', email: 'terry@templeos.net', role: 'Developer', status: 'online', favorite: true, phone: '+1 (555) 640-8086', location: 'Las Vegas, NV', joined: '2024-02-10', bio: 'Built an entire OS from scratch. Legendary.' },
17
+ { id: 4, name: 'Trevor Moore', email: 'trevor@wkuk.tv', role: 'Designer', status: 'away', favorite: false, phone: '+1 (555) 994-2287', location: 'Los Angeles, CA', joined: '2024-05-08', bio: 'Comedian, writer, and creative visionary.' },
18
+ { id: 5, name: 'Carlo Acutis', email: 'carlo@vatican.va', role: 'Developer', status: 'online', favorite: false, phone: '+39 (02) 555-1430', location: 'Milan, Italy', joined: '2024-04-01', bio: 'Patron saint of the internet.' },
19
19
  ],
20
20
  contactsAdded: 0,
21
21
  },
@@ -54,7 +54,8 @@ export const store = $.store('main', {
54
54
 
55
55
  // -- Contact actions --
56
56
 
57
- addContact(state, { name, email, role }) {
57
+ addContact(state, { name, email, role, phone, location, bio }) {
58
+ const cities = ['New York, NY', 'San Francisco, CA', 'Austin, TX', 'Seattle, WA', 'Chicago, IL', 'Denver, CO', 'Portland, OR', 'Boston, MA'];
58
59
  state.contacts.push({
59
60
  id: Date.now(),
60
61
  name,
@@ -62,6 +63,10 @@ export const store = $.store('main', {
62
63
  role,
63
64
  status: 'offline',
64
65
  favorite: false,
66
+ phone: phone || '',
67
+ location: location || cities[Math.floor(Math.random() * cities.length)],
68
+ joined: new Date().toISOString().slice(0, 10),
69
+ bio: bio || '',
65
70
  });
66
71
  state.contactsAdded++;
67
72
  },