juxscript 1.0.59 → 1.0.61

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.
package/README.md CHANGED
@@ -1,4 +1,35 @@
1
- # The web needs a higher level of abstraction: Meet **JUX**
1
+ # JUX - JavaScript UX Authorship Platform
2
+
3
+ > Say goodbye to markup `</</>>>`. Build reactive UIs with pure JavaScript.
4
+
5
+ ## 🚀 Quick Start
6
+
7
+ ### Option 1: Create New Project (Recommended)
8
+
9
+ ```bash
10
+ npx jux create my-app
11
+ cd my-app
12
+ npm run dev
13
+ ```
14
+
15
+ ### Option 2: Add to Existing Project
16
+
17
+ ```bash
18
+ # Install juxscript first
19
+ npm install juxscript
20
+
21
+ # Then initialize
22
+ npx jux init
23
+
24
+ # Start dev server
25
+ npx jux serve
26
+ ```
27
+
28
+ ## Why Install First?
29
+
30
+ JUX uses `esbuild`, `express`, and other tools that must be installed before the CLI can run. When you run `npm install juxscript`, these dependencies are automatically installed.
31
+
32
+ ## The web needs a higher level of abstraction: Meet **JUX**
2
33
 
3
34
 
4
35
  ```diff
package/bin/cli.js CHANGED
@@ -215,6 +215,7 @@ async function buildProject(isServe = false, wsPort = 3001) {
215
215
  fs.mkdirSync(PATHS.frontendDist, { recursive: true });
216
216
 
217
217
  // Step 1: Generate documentation
218
+ /*
218
219
  const docsStartTime = performance.now();
219
220
  let docsTime = 0; // ✅ Declare with default value
220
221
  console.log('📚 Generating documentation...');
@@ -226,6 +227,7 @@ async function buildProject(isServe = false, wsPort = 3001) {
226
227
  docsTime = performance.now() - docsStartTime; // ✅ Still calculate time even on error
227
228
  console.warn(`⚠️ Failed to generate docs (${docsTime.toFixed(0)}ms):`, error.message);
228
229
  }
230
+ */
229
231
 
230
232
  // Step 2: Copy jux lib to frontend dist
231
233
  const libStartTime = performance.now();
@@ -262,47 +264,24 @@ async function buildProject(isServe = false, wsPort = 3001) {
262
264
 
263
265
  // ✅ Bundle and get the generated filename
264
266
  const bundleStartTime = performance.now();
265
- const mainJsFilename = await bundleJuxFilesToRouter(PATHS.juxSource, PATHS.frontendDist, {
267
+ const bundleResult = await bundleJuxFilesToRouter(PATHS.juxSource, PATHS.frontendDist, {
266
268
  routePrefix: ''
267
269
  });
268
270
  const bundleTime = performance.now() - bundleStartTime;
269
271
 
270
- // Generate routes for index.html
271
- const routes = projectJuxFiles.map(juxFile => {
272
- const relativePath = path.relative(PATHS.juxSource, juxFile);
273
- const parsedPath = path.parse(relativePath);
274
-
275
- const rawFunctionName = parsedPath.dir
276
- ? `${parsedPath.dir.replace(/\//g, '_')}_${parsedPath.name}`
277
- : parsedPath.name;
278
-
279
- const functionName = rawFunctionName
280
- .replace(/[-_]/g, ' ')
281
- .split(' ')
282
- .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
283
- .join('');
284
-
285
- const routePath = '/' + (parsedPath.dir ? `${parsedPath.dir}/` : '') + parsedPath.name;
286
-
287
- return {
288
- path: routePath.replace(/\/+/g, '/'),
289
- functionName
290
- };
291
- });
292
-
293
272
  // ✅ Generate unified index.html
294
273
  const indexStartTime = performance.now();
295
- generateIndexHtml(PATHS.frontendDist, routes, mainJsFilename);
274
+ generateIndexHtml(PATHS.frontendDist, bundleResult);
296
275
  const indexTime = performance.now() - indexStartTime;
297
276
 
298
277
  const totalBuildTime = performance.now() - buildStartTime;
299
278
 
300
- console.log(`\nBundled ${projectJuxFiles.length} page(s) ${PATHS.frontendDist}/${mainJsFilename}\n`);
279
+ // FIX: Use bundleResult.mainJsFilename instead of bare mainJsFilename
280
+ console.log(`\n✅ Bundled ${projectJuxFiles.length} page(s) → ${PATHS.frontendDist}/${bundleResult.mainJsFilename}\n`);
301
281
 
302
282
  // ✅ Build summary with timing breakdown
303
283
  console.log(`📊 Build Summary:`);
304
284
  console.log(` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
305
- console.log(` Documentation: ${docsTime.toFixed(0)}ms`);
306
285
  console.log(` Library copy: ${libTime.toFixed(0)}ms`);
307
286
  console.log(` Presets copy: ${presetsTime.toFixed(0)}ms`);
308
287
  console.log(` Assets copy: ${assetsTime.toFixed(0)}ms`);
@@ -320,7 +299,8 @@ async function buildProject(isServe = false, wsPort = 3001) {
320
299
  console.log(` FastAPI: app.mount("/", StaticFiles(directory="jux-dist"), name="static")`);
321
300
  console.log('');
322
301
  console.log('📍 Available routes:');
323
- routes.forEach(r => {
302
+ // ✅ FIX: Use bundleResult.routes instead of local routes variable
303
+ bundleResult.routes.forEach(r => {
324
304
  console.log(` ${r.path}`);
325
305
  });
326
306
  console.log('');
@@ -350,10 +330,10 @@ async function buildProject(isServe = false, wsPort = 3001) {
350
330
 
351
331
  // ✅ Copy presets/default/ directly to jux/ (no presets subfolder!)
352
332
  const defaultPresetSrc = path.join(PATHS.packageRoot, 'presets', 'default');
353
-
333
+
354
334
  if (fs.existsSync(defaultPresetSrc)) {
355
335
  console.log('📦 Copying default preset boilerplate...');
356
-
336
+
357
337
  const entries = fs.readdirSync(defaultPresetSrc, { withFileTypes: true });
358
338
  let copiedCount = 0;
359
339
 
@@ -401,7 +381,7 @@ jux.paragraph('counter')
401
381
  const pkgPath = path.join(PATHS.projectRoot, 'package.json');
402
382
  if (!fs.existsSync(pkgPath)) {
403
383
  const projectName = path.basename(PATHS.projectRoot).toLowerCase().replace(/[^a-z0-9-]/g, '-');
404
-
384
+
405
385
  const pkgContent = {
406
386
  "name": projectName,
407
387
  "version": "0.1.0",
@@ -429,14 +409,14 @@ jux.paragraph('counter')
429
409
 
430
410
  // ✅ Copy juxconfig to root
431
411
  const configExampleSrc = path.join(PATHS.packageRoot, 'juxconfig.example.js');
432
-
412
+
433
413
  if (fs.existsSync(configExampleSrc)) {
434
414
  const configDest = path.join(PATHS.projectRoot, 'juxconfig.js');
435
415
  if (!fs.existsSync(configDest)) {
436
416
  fs.copyFileSync(configExampleSrc, configDest);
437
417
  console.log('+ Created juxconfig.js');
438
418
  }
439
-
419
+
440
420
  // ❌ REMOVE: Don't copy example - it's available in node_modules
441
421
  // const configExampleDest = path.join(PATHS.projectRoot, 'juxconfig.example.js');
442
422
  // if (!fs.existsSync(configExampleDest)) {
package/docs/grid.png ADDED
Binary file
@@ -16,12 +16,21 @@ export default {
16
16
  // Application settings
17
17
  appName: 'My JUX App',
18
18
  autoRoutes: true,
19
-
20
19
  // Directories
21
20
  sourceDir: 'jux',
22
21
  distDir: '.jux-dist',
23
22
 
24
- // External services
23
+ // External services - used by jux.fetch and jux.fetch.serviceClient()
24
+ // Each service becomes available as a baseUrl for fetch requests
25
+ //
26
+ // Usage in components:
27
+ // const db = jux.fetch.serviceClient('database');
28
+ // const { data } = await db.fetch('/users').send();
29
+ //
30
+ // // Or directly:
31
+ // const { data } = await jux.fetch('/users')
32
+ // .header('Authorization', 'Bearer token')
33
+ // .send();
25
34
  services: {
26
35
  database: 'http://localhost:4000/api/db',
27
36
  auth: 'http://localhost:4000/api/auth',
@@ -34,6 +43,10 @@ export default {
34
43
  ws: 3001
35
44
  },
36
45
 
46
+ // Fetch configuration
47
+ fetchTimeout: 30000, // milliseconds
48
+ fetchLog: false, // true, false, or 'errors' for error-only logging
49
+
37
50
  // Build options
38
51
  build: {
39
52
  minify: false,
@@ -41,9 +54,18 @@ export default {
41
54
  },
42
55
 
43
56
  // Bootstrap functions (run on startup)
57
+ // Perfect place to initialize services and auth
44
58
  bootstrap: [
59
+ // Example: Setup fetch with service configuration
60
+ // async function initFetch() {
61
+ // await jux.fetch.setupConfig();
62
+ // console.log('✅ Fetch configured');
63
+ // },
64
+
65
+ // Example: Initialize authentication
45
66
  // async function initAuth() {
46
67
  // console.log('🔐 Initializing auth...');
47
68
  // },
48
69
  ]
49
70
  };
71
+
@@ -394,4 +394,24 @@ export abstract class BaseComponent<TState extends Record<string, any>> {
394
394
  }
395
395
  return this.render(`#${juxComponent._id}`);
396
396
  }
397
+
398
+ /* ═════════════════════════════════════════════════════════════════
399
+ * PROPS ACCESSOR - Read-only access to component state
400
+ * ═════════════════════════════════════════════════════════════════ */
401
+
402
+ /**
403
+ * ✅ Read-only accessor for component state
404
+ * Provides clear separation between setters (fluent methods) and getters
405
+ *
406
+ * @example
407
+ * const myCard = card('example')
408
+ * .title('Hello') // ✅ SETTER (fluent)
409
+ * .content('World'); // ✅ SETTER (fluent)
410
+ *
411
+ * console.log(myCard.props.title); // ✅ GETTER: 'Hello'
412
+ * console.log(myCard.props.content); // ✅ GETTER: 'World'
413
+ */
414
+ get props(): Readonly<TState> {
415
+ return this.state as Readonly<TState>;
416
+ }
397
417
  }
@@ -0,0 +1,231 @@
1
+ import { BaseComponent } from './base/BaseComponent.js';
2
+ import { Chart, ChartConfiguration, ChartType, ChartOptions, ChartData } from 'chart.js/auto';
3
+
4
+ // filepath: /Users/timkerr/newprojects2025/compile-sqljs/packages/jux/lib/components/chart.ts
5
+
6
+ export interface ChartState {
7
+ type: ChartType;
8
+ data: ChartData;
9
+ options: ChartOptions;
10
+ style?: string;
11
+ class?: string;
12
+ visible?: boolean;
13
+ disabled?: boolean;
14
+ loading?: boolean;
15
+ }
16
+
17
+ /**
18
+ * Chart component using Chart.js
19
+ * Provides fluent API for creating and configuring charts
20
+ */
21
+ export class ChartComponent extends BaseComponent<ChartState> {
22
+ private static readonly TRIGGER_EVENTS: readonly string[] = [];
23
+ private static readonly CALLBACK_EVENTS: readonly string[] = ['render', 'update', 'destroy'];
24
+ private chartInstance: Chart | null = null;
25
+
26
+ constructor(id: string, type: ChartType = 'bar', options?: Partial<ChartState>) {
27
+ super(id, {
28
+ type,
29
+ data: { datasets: [] },
30
+ options: {},
31
+ ...options
32
+ });
33
+ }
34
+
35
+ protected getTriggerEvents(): readonly string[] {
36
+ return ChartComponent.TRIGGER_EVENTS;
37
+ }
38
+
39
+ protected getCallbackEvents(): readonly string[] {
40
+ return ChartComponent.CALLBACK_EVENTS;
41
+ }
42
+
43
+ /* ═════════════════════════════════════════════════════════════════
44
+ * CHART TYPE
45
+ * ═════════════════════════════════════════════════════════════════ */
46
+
47
+ type(value: ChartType): this {
48
+ this.state.type = value;
49
+ if (this.chartInstance) {
50
+ // Destroy and recreate chart with new type
51
+ const canvas = this.chartInstance.canvas;
52
+ this.chartInstance.destroy();
53
+ const config: ChartConfiguration = {
54
+ type: this.state.type,
55
+ data: this.state.data,
56
+ options: this.state.options
57
+ };
58
+ this.chartInstance = new Chart(canvas, config);
59
+ }
60
+ return this;
61
+ }
62
+
63
+ /* ═════════════════════════════════════════════════════════════════
64
+ * DATA CONFIGURATION
65
+ * ═════════════════════════════════════════════════════════════════ */
66
+
67
+ data(value: ChartData): this {
68
+ this.state.data = value;
69
+ if (this.chartInstance) {
70
+ this.chartInstance.data = value;
71
+ this.chartInstance.update();
72
+ }
73
+ return this;
74
+ }
75
+
76
+ labels(value: string[]): this {
77
+ this.state.data.labels = value;
78
+ if (this.chartInstance) {
79
+ this.chartInstance.data.labels = value;
80
+ this.chartInstance.update();
81
+ }
82
+ return this;
83
+ }
84
+
85
+ datasets(value: any[]): this {
86
+ this.state.data.datasets = value;
87
+ if (this.chartInstance) {
88
+ this.chartInstance.data.datasets = value;
89
+ this.chartInstance.update();
90
+ }
91
+ return this;
92
+ }
93
+
94
+ addDataset(dataset: any): this {
95
+ this.state.data.datasets.push(dataset);
96
+ if (this.chartInstance) {
97
+ this.chartInstance.data.datasets.push(dataset);
98
+ this.chartInstance.update();
99
+ }
100
+ return this;
101
+ }
102
+
103
+ /* ═════════════════════════════════════════════════════════════════
104
+ * OPTIONS CONFIGURATION
105
+ * ═════════════════════════════════════════════════════════════════ */
106
+
107
+ options(value: ChartOptions): this {
108
+ this.state.options = { ...this.state.options, ...value };
109
+ if (this.chartInstance) {
110
+ this.chartInstance.options = this.state.options;
111
+ this.chartInstance.update();
112
+ }
113
+ return this;
114
+ }
115
+
116
+ responsive(value: boolean = true): this {
117
+ return this.options({ responsive: value });
118
+ }
119
+
120
+ maintainAspectRatio(value: boolean = true): this {
121
+ return this.options({ maintainAspectRatio: value });
122
+ }
123
+
124
+ title(text: string, display: boolean = true): this {
125
+ return this.options({
126
+ plugins: {
127
+ ...this.state.options.plugins,
128
+ title: { display, text }
129
+ }
130
+ });
131
+ }
132
+
133
+ legend(display: boolean = true, position: 'top' | 'bottom' | 'left' | 'right' = 'top'): this {
134
+ return this.options({
135
+ plugins: {
136
+ ...this.state.options.plugins,
137
+ legend: { display, position }
138
+ }
139
+ });
140
+ }
141
+
142
+ tooltip(enabled: boolean = true): this {
143
+ return this.options({
144
+ plugins: {
145
+ ...this.state.options.plugins,
146
+ tooltip: { enabled }
147
+ }
148
+ });
149
+ }
150
+
151
+ /* ═════════════════════════════════════════════════════════════════
152
+ * CHART OPERATIONS
153
+ * ═════════════════════════════════════════════════════════════════ */
154
+
155
+ update(mode?: 'resize' | 'reset' | 'none' | 'hide' | 'show' | 'default'): this {
156
+ if (this.chartInstance) {
157
+ this.chartInstance.update(mode);
158
+ this._triggerCallback('update', this.chartInstance);
159
+ }
160
+ return this;
161
+ }
162
+
163
+ reset(): this {
164
+ if (this.chartInstance) {
165
+ this.chartInstance.reset();
166
+ }
167
+ return this;
168
+ }
169
+
170
+ destroy(): this {
171
+ if (this.chartInstance) {
172
+ this.chartInstance.destroy();
173
+ this.chartInstance = null;
174
+ this._triggerCallback('destroy');
175
+ }
176
+ return this;
177
+ }
178
+
179
+ getChart(): Chart | null {
180
+ return this.chartInstance;
181
+ }
182
+
183
+ /* ═════════════════════════════════════════════════════════════════
184
+ * RENDER
185
+ * ═════════════════════════════════════════════════════════════════ */
186
+
187
+ render(targetId?: string): this {
188
+ const container = this._setupContainer(targetId);
189
+
190
+ // Destroy existing chart
191
+ if (this.chartInstance) {
192
+ this.chartInstance.destroy();
193
+ }
194
+
195
+ // Clear container and create canvas
196
+ container.innerHTML = '';
197
+ const canvas = document.createElement('canvas');
198
+ canvas.id = `${this._id}-canvas`;
199
+
200
+ // Apply styles and classes
201
+ if (this.state.style) container.setAttribute('style', this.state.style);
202
+ if (this.state.class) container.className = this.state.class;
203
+ if (this.state.visible === false) container.style.display = 'none';
204
+
205
+ container.appendChild(canvas);
206
+
207
+ // Create Chart.js instance
208
+ const config: ChartConfiguration = {
209
+ type: this.state.type,
210
+ data: this.state.data,
211
+ options: this.state.options
212
+ };
213
+
214
+ this.chartInstance = new Chart(canvas, config);
215
+
216
+ // Wire events and syncs
217
+ this._wireStandardEvents(container);
218
+ this._wireAllSyncs();
219
+
220
+ this._triggerCallback('render', this.chartInstance);
221
+
222
+ return this;
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Factory function for creating chart components
228
+ */
229
+ export function chart(id: string, type: ChartType = 'bar', options?: Partial<ChartState>): ChartComponent {
230
+ return new ChartComponent(id, type, options);
231
+ }