juxscript 1.0.89 → 1.0.90

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.
@@ -0,0 +1,255 @@
1
+ import express from 'express';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ import http from 'http';
5
+ import { fileURLToPath } from 'url';
6
+ import { WebSocketServer } from 'ws';
7
+ import { createWatcher } from './watcher.js';
8
+ import { JuxCompiler } from './compiler3.js';
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+
13
+ // ═══════════════════════════════════════════════════════════════
14
+ // CONFIGURATION
15
+ // ═══════════════════════════════════════════════════════════════
16
+ const args = process.argv.slice(2);
17
+ const HOT_RELOAD = args.includes('--hot') || args.includes('-h') || args.includes('--watch');
18
+
19
+ // Extract port from args (e.g., --port=3000 or -p 3000)
20
+ function getArgValue(flag, shortFlag, defaultValue) {
21
+ for (let i = 0; i < args.length; i++) {
22
+ if (args[i].startsWith(`${flag}=`)) return args[i].split('=')[1];
23
+ if (args[i].startsWith(`${shortFlag}=`)) return args[i].split('=')[1];
24
+ if ((args[i] === flag || args[i] === shortFlag) && args[i + 1]) return args[i + 1];
25
+ }
26
+ return defaultValue;
27
+ }
28
+
29
+ const PORT = parseInt(getArgValue('--port', '-p', process.env.PORT || '3000'));
30
+ const WS_PORT = parseInt(getArgValue('--ws-port', '-w', process.env.WS_PORT || String(PORT + 1)));
31
+
32
+ // Resolve paths relative to CWD (user's project)
33
+ const PROJECT_ROOT = process.cwd();
34
+ const DIST_DIR = path.resolve(PROJECT_ROOT, '.jux-dist');
35
+ const SRC_DIR = path.resolve(PROJECT_ROOT, 'jux');
36
+
37
+ const app = express();
38
+ let lastBuildResult = { success: true, errors: [] };
39
+
40
+ // Check if dist directory exists - if not, try to build first
41
+ if (!fs.existsSync(DIST_DIR) || !fs.existsSync(path.join(DIST_DIR, 'index.html'))) {
42
+ console.log('⚠️ Dist directory not found. Running initial build...');
43
+
44
+ const compiler = new JuxCompiler({
45
+ srcDir: SRC_DIR,
46
+ distDir: DIST_DIR
47
+ });
48
+
49
+ try {
50
+ const result = await compiler.build();
51
+ lastBuildResult = result;
52
+
53
+ if (!result.success) {
54
+ console.log('\n❌ Initial build failed. Starting server to show error overlay.\n');
55
+ }
56
+ } catch (err) {
57
+ console.error('❌ Initial build failed:', err.message);
58
+ lastBuildResult = { success: false, errors: [{ message: err.message }] };
59
+ }
60
+ }
61
+
62
+ if (!fs.existsSync(DIST_DIR)) {
63
+ fs.mkdirSync(DIST_DIR, { recursive: true });
64
+ }
65
+
66
+ app.use((req, res, next) => {
67
+ res.setHeader('Cache-Control', 'no-store');
68
+ next();
69
+ });
70
+
71
+ app.use((req, res, next) => {
72
+ if (req.path.endsWith('.js')) {
73
+ res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
74
+ }
75
+ next();
76
+ });
77
+
78
+ app.get('/favicon.ico', (req, res) => res.status(204).end());
79
+ app.get('/favicon.png', (req, res) => res.status(204).end());
80
+
81
+ app.get('/__jux_sources.json', (req, res) => {
82
+ const snapshotPath = path.join(DIST_DIR, '__jux_sources.json');
83
+ if (fs.existsSync(snapshotPath)) {
84
+ res.setHeader('Content-Type', 'application/json');
85
+ res.sendFile(snapshotPath);
86
+ } else {
87
+ res.json({});
88
+ }
89
+ });
90
+
91
+ const hotReloadScript = `
92
+ <script>
93
+ (function() {
94
+ var ws = new WebSocket('ws://localhost:${WS_PORT}');
95
+ ws.onopen = function() { console.log('🔥 Hot reload connected'); };
96
+ ws.onmessage = function(e) {
97
+ var data = JSON.parse(e.data);
98
+ if (data.type === 'reload') location.reload();
99
+ else if (data.type === 'build-error') console.error('❌ Build error:', data.errors);
100
+ };
101
+ ws.onclose = function() { setTimeout(function() { location.reload(); }, 2000); };
102
+ })();
103
+ </script>
104
+ `;
105
+
106
+ function getErrorPageHtml(errors) {
107
+ return `<!DOCTYPE html>
108
+ <html lang="en">
109
+ <head>
110
+ <meta charset="UTF-8">
111
+ <title>JUX Build Error</title>
112
+ <style>
113
+ * { margin: 0; padding: 0; box-sizing: border-box; }
114
+ body { font-family: monospace; background: #1a1a2e; color: #eee; min-height: 100vh; padding: 40px; }
115
+ .container { max-width: 800px; margin: 0 auto; }
116
+ h1 { color: #ff6b6b; margin-bottom: 8px; }
117
+ .subtitle { color: #888; margin-bottom: 30px; }
118
+ .error-card { background: #16213e; padding: 16px 20px; border-radius: 8px; margin-bottom: 12px; border-left: 4px solid #ff6b6b; }
119
+ .error-header { color: #ff6b6b; font-weight: bold; margin-bottom: 8px; }
120
+ .error-code { background: #0f0f23; padding: 12px; border-radius: 4px; font-size: 13px; color: #888; }
121
+ </style>
122
+ </head>
123
+ <body>
124
+ <div class="container">
125
+ <h1>🛑 Build Failed</h1>
126
+ <p class="subtitle">Fix the errors below and save to rebuild.</p>
127
+ ${errors.map(err => `
128
+ <div class="error-card">
129
+ <div class="error-header">${err.view ? `[${err.view}] ` : ''}${err.message}</div>
130
+ ${err.code ? `<pre class="error-code">${err.code}</pre>` : ''}
131
+ </div>
132
+ `).join('')}
133
+ </div>
134
+ ${HOT_RELOAD ? `<script>
135
+ var ws = new WebSocket('ws://localhost:${WS_PORT}');
136
+ ws.onmessage = function(e) { if (JSON.parse(e.data).type === 'reload') location.reload(); };
137
+ </script>` : ''}
138
+ </body>
139
+ </html>`;
140
+ }
141
+
142
+ if (HOT_RELOAD) {
143
+ app.get(['/', '/index.html'], (req, res) => {
144
+ const indexPath = path.join(DIST_DIR, 'index.html');
145
+
146
+ if (!fs.existsSync(indexPath) || !lastBuildResult.success) {
147
+ return res.send(getErrorPageHtml(lastBuildResult.errors));
148
+ }
149
+
150
+ let html = fs.readFileSync(indexPath, 'utf8');
151
+ html = html.replace('</body>', hotReloadScript + '</body>');
152
+ res.setHeader('Content-Type', 'text/html');
153
+ res.send(html);
154
+ });
155
+ }
156
+
157
+ app.use(express.static(DIST_DIR));
158
+
159
+ app.get('*', (req, res) => {
160
+ if (path.extname(req.path) && req.path !== '/') {
161
+ return res.status(404).send('Not found');
162
+ }
163
+
164
+ const indexPath = path.join(DIST_DIR, 'index.html');
165
+
166
+ if (!fs.existsSync(indexPath) || !lastBuildResult.success) {
167
+ return res.send(getErrorPageHtml(lastBuildResult.errors));
168
+ }
169
+
170
+ if (HOT_RELOAD) {
171
+ let html = fs.readFileSync(indexPath, 'utf8');
172
+ html = html.replace('</body>', hotReloadScript + '</body>');
173
+ res.setHeader('Content-Type', 'text/html');
174
+ res.send(html);
175
+ } else {
176
+ res.sendFile(indexPath);
177
+ }
178
+ });
179
+
180
+ const server = http.createServer(app);
181
+ let wss = null;
182
+ let watcher = null;
183
+
184
+ if (HOT_RELOAD) {
185
+ wss = new WebSocketServer({ port: WS_PORT });
186
+ const clients = new Set();
187
+
188
+ wss.on('connection', (ws) => {
189
+ clients.add(ws);
190
+ ws.on('close', () => clients.delete(ws));
191
+ });
192
+
193
+ const broadcast = (message) => {
194
+ const data = JSON.stringify(message);
195
+ clients.forEach(client => {
196
+ if (client.readyState === 1) client.send(data);
197
+ });
198
+ };
199
+
200
+ const compiler = new JuxCompiler({
201
+ srcDir: SRC_DIR,
202
+ distDir: DIST_DIR
203
+ });
204
+
205
+ watcher = createWatcher(SRC_DIR, {
206
+ onChange: async (changedFiles) => {
207
+ console.log('🔄 Rebuilding...');
208
+
209
+ try {
210
+ const result = await compiler.build();
211
+ lastBuildResult = result;
212
+
213
+ if (!result.success) {
214
+ console.error('❌ Rebuild failed');
215
+ broadcast({ type: 'build-error', errors: result.errors });
216
+ return;
217
+ }
218
+
219
+ console.log('✅ Rebuild complete');
220
+ broadcast({ type: 'reload', files: changedFiles });
221
+ } catch (err) {
222
+ console.error('❌ Rebuild failed:', err.message);
223
+ lastBuildResult = { success: false, errors: [{ message: err.message }] };
224
+ broadcast({ type: 'build-error', errors: [{ message: err.message }] });
225
+ }
226
+ },
227
+ onReady: () => {
228
+ console.log(`👀 Watching: ${SRC_DIR}`);
229
+ }
230
+ });
231
+ }
232
+
233
+ server.listen(PORT, () => {
234
+ console.log(`\n🚀 JUX Server running at http://localhost:${PORT}`);
235
+ if (HOT_RELOAD) {
236
+ console.log(`🔥 Hot reload: ws://localhost:${WS_PORT}`);
237
+ }
238
+ if (!lastBuildResult.success) {
239
+ console.log(`⚠️ Build has errors - fix them and save\n`);
240
+ }
241
+ });
242
+
243
+ const shutdown = () => {
244
+ console.log('\n👋 Shutting down...');
245
+ if (watcher) watcher.close();
246
+ if (wss) {
247
+ wss.clients.forEach(client => client.terminate());
248
+ wss.close();
249
+ }
250
+ server.close(() => process.exit(0));
251
+ setTimeout(() => process.exit(0), 2000);
252
+ };
253
+
254
+ process.on('SIGINT', shutdown);
255
+ process.on('SIGTERM', shutdown);
@@ -1,70 +1,59 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { bundleJuxFilesToRouter, generateIndexHtml } from './compiler.js';
4
- import { verifyStaticBuild } from './verifier.js';
5
-
6
- let isRebuilding = false;
7
- let rebuildQueued = false;
8
3
 
9
4
  /**
10
- * Find all .jux files in a directory
5
+ * Watch a directory for changes and trigger callbacks
11
6
  */
12
- function findJuxFiles(dir, fileList = []) {
13
- if (!fs.existsSync(dir)) return fileList;
14
- fs.readdirSync(dir).forEach(file => {
15
- const filePath = path.join(dir, file);
16
- if (fs.statSync(filePath).isDirectory()) {
17
- if (!['node_modules', 'jux-dist', '.git', 'server'].includes(file)) findJuxFiles(filePath, fileList);
18
- } else if (file.endsWith('.jux')) {
19
- fileList.push(filePath);
20
- }
21
- });
22
- return fileList;
7
+ export function createWatcher(srcDir, options = {}) {
8
+ const {
9
+ onChange = () => {},
10
+ onReady = () => {},
11
+ debounceMs = 100,
12
+ extensions = ['.jux', '.js', '.css']
13
+ } = options;
14
+
15
+ const absoluteSrcDir = path.resolve(srcDir);
16
+ let debounceTimer = null;
17
+ let pendingChanges = new Set();
18
+
19
+ console.log(`👀 Watching: ${absoluteSrcDir}`);
20
+
21
+ // Debounced change handler
22
+ const handleChange = (eventType, filename) => {
23
+ if (!filename) return;
24
+
25
+ // Filter by extension
26
+ const ext = path.extname(filename);
27
+ if (!extensions.includes(ext)) return;
28
+
29
+ pendingChanges.add(filename);
30
+
31
+ // Debounce rapid changes
32
+ if (debounceTimer) clearTimeout(debounceTimer);
33
+ debounceTimer = setTimeout(() => {
34
+ const changes = [...pendingChanges];
35
+ pendingChanges.clear();
36
+
37
+ console.log(`📝 Changed: ${changes.join(', ')}`);
38
+ onChange(changes);
39
+ }, debounceMs);
40
+ };
41
+
42
+ // Start watching
43
+ const watcher = fs.watch(absoluteSrcDir, { recursive: true }, handleChange);
44
+
45
+ watcher.on('error', (err) => {
46
+ console.error('❌ Watcher error:', err);
47
+ });
48
+
49
+ onReady();
50
+
51
+ // Return control object
52
+ return {
53
+ close: () => {
54
+ if (debounceTimer) clearTimeout(debounceTimer);
55
+ watcher.close();
56
+ console.log('👋 Watcher closed');
57
+ }
58
+ };
23
59
  }
24
-
25
- /**
26
- * Full rebuild of the entire bundle
27
- */
28
- async function fullRebuild(juxSource, distDir, config, wsPort) {
29
- try {
30
- const bundleResult = await bundleJuxFilesToRouter(juxSource, distDir, { routePrefix: '', config });
31
- generateIndexHtml(distDir, bundleResult, { isDev: true, wsPort: wsPort || 3001 });
32
- if (!verifyStaticBuild(distDir)) return false;
33
- return true;
34
- } catch (err) {
35
- console.error(`❌ Rebuild failed:`, err.message);
36
- return false;
37
- }
38
- }
39
-
40
- /**
41
- * Start watching for file changes and rebuild on change
42
- */
43
- export function startWatcher(juxSource, distDir, wsClients, config, wsPort) {
44
- console.log(`👀 Watching for changes...`);
45
-
46
- const watcher = fs.watch(juxSource, { recursive: true }, async (eventType, filename) => {
47
- if (isRebuilding) { rebuildQueued = true; return; }
48
- isRebuilding = true;
49
-
50
- process.stdout.write(`\n📝 Change: ${filename} -> Rebuilding...`);
51
- const success = await fullRebuild(juxSource, distDir, config, wsPort);
52
- isRebuilding = false;
53
-
54
- if (success && wsClients && wsClients.length > 0) {
55
- console.log(' ✅ Reloaded.');
56
- setTimeout(() => {
57
- wsClients.forEach(client => { if (client.readyState === 1) client.send(JSON.stringify({ type: 'reload' })); });
58
- }, 100);
59
- } else if (!success) {
60
- console.log(' ❌ Failed.');
61
- }
62
-
63
- if (rebuildQueued) {
64
- rebuildQueued = false;
65
- setTimeout(() => watcher.emit('change', 'change', filename), 500);
66
- }
67
- });
68
-
69
- return watcher;
70
- }
package/package.json CHANGED
@@ -1,23 +1,29 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.0.89",
3
+ "version": "1.0.90",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
- "types": "lib/globals.d.ts",
6
+ "main": "./index.js",
7
+ "module": "./index.js",
8
+ "types": "./lib/globals.d.ts",
7
9
  "access": "public",
8
10
  "repository": {
9
11
  "type": "git",
10
12
  "url": "git+https://github.com/jux/juxscript.git"
11
13
  },
12
14
  "exports": {
15
+ ".": {
16
+ "import": "./index.js",
17
+ "types": "./lib/globals.d.ts"
18
+ },
13
19
  "./package.json": "./package.json"
14
20
  },
15
21
  "files": [
22
+ "index.js",
16
23
  "bin",
17
24
  "create",
18
25
  "lib",
19
26
  "machinery",
20
- "tests",
21
27
  "types",
22
28
  "juxconfig.example.js",
23
29
  "README.md",
@@ -51,6 +57,8 @@
51
57
  },
52
58
  "dependencies": {
53
59
  "acorn": "^8.15.0",
60
+ "astray": "^1.1.1",
61
+ "magic-string": "^0.30.21",
54
62
  "axios": "^1.6.0",
55
63
  "better-sqlite3": "^12.6.2",
56
64
  "chart.js": "^4.5.1",
@@ -1,39 +0,0 @@
1
- import { List } from './list/component.js';
2
- import { Grid } from './grid/component.js';
3
- import { Input } from './input/component.js';
4
- import { Element } from './element/component.js';
5
- export { List, Grid, Input, Element };
6
- export declare const tree: () => Record<string, any>;
7
- export declare const use: (query: string) => any;
8
- export declare const engines: () => string[];
9
- export declare const log: () => void;
10
- /**
11
- * juxDef: Global Introspection & Debugging Tool
12
- * Exposed to window.juxDef for console access.
13
- */
14
- export declare const juxDef: {
15
- tree: () => Record<string, any>;
16
- use: (query: string) => any;
17
- engines: () => string[];
18
- log: () => void;
19
- };
20
- export declare const juxV2: {
21
- List: typeof List;
22
- Grid: typeof Grid;
23
- Input: typeof Input;
24
- Element: typeof Element;
25
- reflection: {
26
- tree: () => Record<string, any>;
27
- use: (query: string) => any;
28
- engines: () => string[];
29
- log: () => void;
30
- };
31
- events: import("./base/GlobalBus.js").JuxGlobalBus;
32
- plugins: {
33
- ServerSQLitePlugin: (config: import("./plugins/ServerSQLitePlugin.js").ServerSQLiteConfig) => import("./base/BaseEngine.js").JuxServiceContract<import("./base/BaseEngine.js").BaseEngine<any>>;
34
- LocalStoragePlugin: (config: import("./plugins/LocalStoragePlugin.js").LocalStorageConfig) => import("./base/BaseEngine.js").JuxServiceContract<import("./base/BaseEngine.js").BaseEngine<any>>;
35
- IndexedDBPlugin: (config: import("./plugins/IndexedDBPlugin.js").IndexedDBConfig) => import("./base/BaseEngine.js").JuxServiceContract<import("./base/BaseEngine.js").BaseEngine<any>>;
36
- ClientSQLitePlugin: (config: import("./plugins/ClientSQLitePlugin.js").ClientSQLiteConfig) => import("./base/BaseEngine.js").JuxServiceContract<import("./base/BaseEngine.js").BaseEngine<any>>;
37
- };
38
- };
39
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAE7C,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAGjD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAwCtC,eAAO,MAAM,IAAI,2BAmGhB,CAAC;AAEF,eAAO,MAAM,GAAG,GAAI,OAAO,MAAM,QA8BhC,CAAC;AAEF,eAAO,MAAM,OAAO,gBAOnB,CAAC;AAEF,eAAO,MAAM,GAAG,YAsBf,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,MAAM;;iBArEQ,MAAM;;;CA0EhC,CAAC;AASF,eAAO,MAAM,KAAK;;;;;;;qBAnFS,MAAM;;;;;;;;;;;CAgGhC,CAAC"}
@@ -1,221 +0,0 @@
1
- import { GlobalBus } from './base/GlobalBus.js';
2
- import { ServerSQLitePlugin } from './plugins/ServerSQLitePlugin.js'; // The Real Server Deal
3
- import { LocalStoragePlugin } from './plugins/LocalStoragePlugin.js';
4
- import { IndexedDBPlugin } from './plugins/IndexedDBPlugin.js';
5
- import { ClientSQLitePlugin } from './plugins/ClientSQLitePlugin.js';
6
- import { List } from './list/component.js';
7
- import { Grid } from './grid/component.js';
8
- import { Input } from './input/component.js';
9
- import { Element } from './element/component.js';
10
- // Export individually
11
- export { List, Grid, Input, Element };
12
- /**
13
- * Curated documentation for core framework capabilities.
14
- * These strings are appended to the reflection data in juxDef.tree.
15
- */
16
- const STATIC_DOCS = {
17
- // BaseEngine / Lifecycle
18
- 'rollback': 'Reverts state to previous Snapshot (Time Travel Undo).',
19
- 'rollforward': 'Redoes a previously rolled-back state change.',
20
- 'dispose': 'Cleans up global listeners to prevent memory leaks.',
21
- 'on': 'Subscribes to a local event on this component instance.',
22
- 'off': 'Unsubscribes from a local event.',
23
- 'listenTo': 'Subscribes to the Global "Neighborhood" Event Bus.',
24
- 'render': 'Mounts the component visual (Skin) to the DOM.',
25
- 'addPlugin': 'Injects external services/drivers (DI) into the engine.',
26
- // Plugin Mixins
27
- 'query': 'Executes a raw SQL query against the attached database driver (via SQLitePlugin).',
28
- 'sqlRun': 'Executes a write operation against Client-Side WASM SQLite.',
29
- 'sqlExec': 'Executes a read query against Client-Side WASM SQLite.',
30
- // List Config / Knobs
31
- 'knobs': 'Configures multiple UI features at once (add, edit, move, etc).',
32
- 'enableNoItems': 'Sets a custom message for empty states.',
33
- 'enableSearch': 'Shows a toolbar input for real-time text filtering.',
34
- // Data Mutation
35
- 'addItems': 'Bulk insert optimization (single render cycle).',
36
- 'addItem': 'Adds a single item object or string.',
37
- 'updateItem': 'Patches properties of an item at specific index.',
38
- 'removeItem': 'Deletes item at index.',
39
- 'moveItem': 'Reorders item from index A to B (Drag & Drop).',
40
- // Input Config
41
- 'setValue': 'Programmatically sets the input value.',
42
- 'setError': 'Displays validation error message below input.',
43
- 'setLabel': 'Updates the field label.'
44
- };
45
- export const tree = () => {
46
- // Internal access to engines (private to this scope/getter)
47
- const engines = (typeof window !== 'undefined' ? window.juxEngines : {}) || {};
48
- const root = {};
49
- // Reflection Helper: Inspects function source to gather docs
50
- const inspectStart = (name, fn) => {
51
- try {
52
- const source = fn.toString();
53
- // 1. Signature / Params
54
- // Matches "methodName(a, b) {" or "(a, b) =>"
55
- let params = '';
56
- const paramMatch = source.match(/^[^(]*\(([^)]*)\)/);
57
- if (paramMatch) {
58
- // Start from arg list
59
- params = paramMatch[1].replace(/\s+/g, ' ').trim();
60
- }
61
- // 2. Returns this? (Heuristic)
62
- // Checks for "return this" or explicit arrow return of instance
63
- const returnsThis = /return\s+this/.test(source) || /=>\s*this/.test(source);
64
- // 3. Emissions (Heuristic)
65
- // Scans for this.emit('event-name', ...)
66
- const emissions = new Set();
67
- const emitRegex = /this\.emit\(\s*['"]([^'"]+)['"]/g;
68
- let match;
69
- while ((match = emitRegex.exec(source)) !== null) {
70
- emissions.add(match[1]);
71
- }
72
- // Format Output
73
- let doc = `${name}(${params})`;
74
- const extras = [];
75
- if (returnsThis)
76
- extras.push('returns this');
77
- if (emissions.size > 0)
78
- extras.push(`emits: ${Array.from(emissions).join(', ')}`);
79
- if (extras.length > 0) {
80
- doc += ` -> ${extras.join(' | ')}`;
81
- }
82
- // 4. Append Static Docs if available
83
- if (STATIC_DOCS[name]) {
84
- doc += ` // ${STATIC_DOCS[name]}`;
85
- }
86
- return doc;
87
- }
88
- catch (e) {
89
- return `${name}(?)`;
90
- }
91
- };
92
- Object.keys(engines).forEach(id => {
93
- const engine = engines[id];
94
- // Collect all discoverable methods (Instance + Inherited)
95
- const capabilities = [];
96
- const processed = new Set();
97
- // Safe interaction helper
98
- const addMethod = (k) => {
99
- if (processed.has(k))
100
- return;
101
- if (k === 'constructor')
102
- return;
103
- try {
104
- // FIX: Always access via instance to ensure 'this' context is correct for getters
105
- // and to avoid invoking methods on the prototype object which lacks private state.
106
- const val = engine[k];
107
- if (typeof val === 'function') {
108
- processed.add(k);
109
- capabilities.push(inspectStart(k, val));
110
- }
111
- }
112
- catch (e) {
113
- // Ignore properties that cannot be accessed (e.g. strict mode violations, private getters)
114
- }
115
- };
116
- // 1. Instance (Factory-added methods like .render, .knobs)
117
- Object.getOwnPropertyNames(engine).forEach(k => addMethod(k));
118
- // 2. Prototype Chain (Class Inheritance)
119
- let proto = Object.getPrototypeOf(engine);
120
- while (proto && proto !== Object.prototype) {
121
- Object.getOwnPropertyNames(proto).forEach(k => addMethod(k));
122
- proto = Object.getPrototypeOf(proto);
123
- }
124
- // Safe snapshot
125
- root[id] = {
126
- type: engine.constructor.name,
127
- state: { ...engine.state },
128
- listeners: engine.eventRegistry || {},
129
- capabilities: capabilities.sort()
130
- };
131
- });
132
- return root;
133
- };
134
- export const use = (query) => {
135
- const engines = (typeof window !== 'undefined' ? window.juxEngines : {}) || {};
136
- const keys = Object.keys(engines);
137
- // 1. Exact Match
138
- if (engines[query]) {
139
- window.jux = engines[query];
140
- console.log(`🎯 Active Engine set to: %c${query}`, 'font-weight:bold; color:#00ff00');
141
- console.log(` You can now call methods on %cwindow.jux`, 'font-style:italic');
142
- return engines[query];
143
- }
144
- // 2. Fuzzy/Smart Match
145
- const candidates = keys.filter(k => k.toLowerCase().includes(query.toLowerCase()));
146
- if (candidates.length === 1) {
147
- const match = candidates[0];
148
- window.jux = engines[match];
149
- console.log(`🎯 Smart Match! Active Engine set to: %c${match}`, 'font-weight:bold; color:#00ff00');
150
- console.log(` You can now call methods on %cwindow.jux`, 'font-style:italic');
151
- return engines[match];
152
- }
153
- // 3. Ambiguous or No Match
154
- if (candidates.length > 1) {
155
- console.warn(`⚠️ Ambiguous match for "${query}". Did you mean:`);
156
- console.table(candidates);
157
- }
158
- else {
159
- console.warn(`❌ Engine "${query}" not found. Available IDs:`, keys);
160
- }
161
- };
162
- export const engines = () => {
163
- const engines = (typeof window !== 'undefined' ? window.juxEngines : {}) || {};
164
- const keys = Object.keys(engines);
165
- console.group('🔑 Available Engines');
166
- keys.forEach(k => console.log(`"${k}"`));
167
- console.groupEnd();
168
- return keys;
169
- };
170
- export const log = () => {
171
- console.group('JUX Runtime Inspector');
172
- console.groupCollapsed('📦 Component Tree (Active Instances)');
173
- const treeData = tree();
174
- if (Object.keys(treeData).length === 0) {
175
- console.log("No active components found.");
176
- }
177
- else {
178
- console.table(Object.keys(treeData).map(k => ({
179
- ID: k,
180
- Type: treeData[k].type,
181
- Items: (treeData[k].state.items?.length) ?? 'N/A'
182
- })));
183
- console.log(treeData);
184
- }
185
- console.groupEnd();
186
- console.groupCollapsed('📡 Global Event Bus');
187
- console.log(GlobalBus.registry);
188
- console.groupEnd();
189
- console.groupEnd();
190
- };
191
- /**
192
- * juxDef: Global Introspection & Debugging Tool
193
- * Exposed to window.juxDef for console access.
194
- */
195
- export const juxDef = {
196
- tree,
197
- use,
198
- engines,
199
- log
200
- };
201
- // Automatically attach to window for DevTables/Console access
202
- if (typeof window !== 'undefined') {
203
- // @ts-ignore
204
- window.juxDef = juxDef;
205
- }
206
- // Export as a namespace object
207
- export const juxV2 = {
208
- List,
209
- Grid,
210
- Input,
211
- Element,
212
- reflection: juxDef,
213
- events: GlobalBus,
214
- plugins: {
215
- ServerSQLitePlugin,
216
- LocalStoragePlugin,
217
- IndexedDBPlugin,
218
- ClientSQLitePlugin
219
- }
220
- };
221
- //# sourceMappingURL=index.js.map