juxscript 1.1.159 → 1.1.161

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 (48) hide show
  1. package/index.d.ts +4 -0
  2. package/index.d.ts.map +1 -1
  3. package/index.js +5 -0
  4. package/lib/components/checkbox.js +4 -4
  5. package/lib/components/checkbox.ts +5 -5
  6. package/lib/components/datepicker.d.ts.map +1 -1
  7. package/lib/components/datepicker.js +5 -4
  8. package/lib/components/datepicker.ts +5 -4
  9. package/lib/components/dialog.d.ts.map +1 -1
  10. package/lib/components/dialog.js +2 -0
  11. package/lib/components/dialog.ts +2 -0
  12. package/lib/components/dropdown.d.ts +1 -0
  13. package/lib/components/dropdown.d.ts.map +1 -1
  14. package/lib/components/dropdown.js +5 -6
  15. package/lib/components/dropdown.ts +6 -9
  16. package/lib/components/fileupload.d.ts +6 -0
  17. package/lib/components/fileupload.d.ts.map +1 -1
  18. package/lib/components/fileupload.js +31 -61
  19. package/lib/components/fileupload.ts +38 -67
  20. package/lib/components/input.js +6 -6
  21. package/lib/components/input.ts +6 -6
  22. package/lib/components/modal.d.ts.map +1 -1
  23. package/lib/components/modal.js +2 -0
  24. package/lib/components/modal.ts +2 -0
  25. package/lib/components/select.d.ts.map +1 -1
  26. package/lib/components/select.js +5 -0
  27. package/lib/components/select.ts +6 -0
  28. package/lib/components/switch.js +4 -4
  29. package/lib/components/switch.ts +5 -5
  30. package/lib/components/tabs.js +4 -4
  31. package/lib/components/tabs.ts +4 -4
  32. package/lib/storage/DataFrame.d.ts +59 -0
  33. package/lib/storage/DataFrame.d.ts.map +1 -0
  34. package/lib/storage/DataFrame.js +443 -0
  35. package/lib/storage/DataFrame.ts +472 -0
  36. package/lib/storage/FileStorage.d.ts +53 -0
  37. package/lib/storage/FileStorage.d.ts.map +1 -0
  38. package/lib/storage/FileStorage.js +80 -0
  39. package/lib/storage/FileStorage.ts +95 -0
  40. package/lib/storage/IndexedDBDriver.d.ts +75 -0
  41. package/lib/storage/IndexedDBDriver.d.ts.map +1 -0
  42. package/lib/storage/IndexedDBDriver.js +177 -0
  43. package/lib/storage/IndexedDBDriver.ts +226 -0
  44. package/lib/storage/TabularDriver.d.ts +75 -0
  45. package/lib/storage/TabularDriver.d.ts.map +1 -0
  46. package/lib/storage/TabularDriver.js +399 -0
  47. package/lib/storage/TabularDriver.ts +491 -0
  48. package/package.json +1 -1
@@ -0,0 +1,491 @@
1
+ import { DataFrame } from './DataFrame.js';
2
+
3
+ export interface StoredTable {
4
+ id: string;
5
+ name: string;
6
+ columns: string[];
7
+ rows: any[][];
8
+ rowCount: number;
9
+ delimiter: string;
10
+ timestamp: number;
11
+ metadata?: Record<string, any>;
12
+ }
13
+
14
+ export interface ParseOptions {
15
+ delimiter?: string;
16
+ hasHeader?: boolean;
17
+ chunkSize?: number;
18
+ onProgress?: (parsed: number, total: number | null) => void;
19
+ maxRows?: number;
20
+ skipRows?: number;
21
+ columns?: string[];
22
+ }
23
+
24
+ export class TabularDriver {
25
+ private _dbName: string;
26
+ private _storeName: string;
27
+ private _db: IDBDatabase | null = null;
28
+
29
+ constructor(dbName: string = 'jux-tabular', storeName: string = 'tables') {
30
+ this._dbName = dbName;
31
+ this._storeName = storeName;
32
+ }
33
+
34
+ async open(): Promise<IDBDatabase> {
35
+ if (this._db) return this._db;
36
+
37
+ return new Promise((resolve, reject) => {
38
+ const request = indexedDB.open(this._dbName, 1);
39
+
40
+ request.onupgradeneeded = (e) => {
41
+ const db = (e.target as IDBOpenDBRequest).result;
42
+ if (!db.objectStoreNames.contains(this._storeName)) {
43
+ const store = db.createObjectStore(this._storeName, { keyPath: 'id' });
44
+ store.createIndex('name', 'name', { unique: false });
45
+ store.createIndex('timestamp', 'timestamp', { unique: false });
46
+ }
47
+ };
48
+
49
+ request.onsuccess = (e) => {
50
+ this._db = (e.target as IDBOpenDBRequest).result;
51
+ resolve(this._db);
52
+ };
53
+
54
+ request.onerror = () => reject(request.error);
55
+ });
56
+ }
57
+
58
+ /* ═══════════════════════════════════════════════════
59
+ * CSV / TSV PARSING
60
+ * ═══════════════════════════════════════════════════ */
61
+
62
+ /**
63
+ * Parse a CSV/TSV string into a DataFrame
64
+ */
65
+ parseCSV(text: string, options: ParseOptions = {}): DataFrame {
66
+ const {
67
+ delimiter = ',',
68
+ hasHeader = true,
69
+ maxRows,
70
+ skipRows = 0,
71
+ columns: selectCols
72
+ } = options;
73
+
74
+ const lines = this._splitLines(text);
75
+ let startIdx = skipRows;
76
+ let headers: string[];
77
+
78
+ if (hasHeader && lines.length > startIdx) {
79
+ headers = this._parseLine(lines[startIdx], delimiter);
80
+ startIdx++;
81
+ } else {
82
+ const firstLine = this._parseLine(lines[startIdx] || '', delimiter);
83
+ headers = firstLine.map((_, i) => `col_${i}`);
84
+ }
85
+
86
+ const rows: Record<string, any>[] = [];
87
+ const end = maxRows ? Math.min(startIdx + maxRows, lines.length) : lines.length;
88
+
89
+ for (let i = startIdx; i < end; i++) {
90
+ if (!lines[i].trim()) continue;
91
+ const values = this._parseLine(lines[i], delimiter);
92
+ const row: Record<string, any> = {};
93
+
94
+ headers.forEach((h, j) => {
95
+ row[h] = this._autoType(values[j]);
96
+ });
97
+
98
+ rows.push(row);
99
+ }
100
+
101
+ let df = new DataFrame(rows);
102
+
103
+ if (selectCols) {
104
+ df = df.select(...selectCols);
105
+ }
106
+
107
+ return df;
108
+ }
109
+
110
+ /**
111
+ * Stream-parse a File into a DataFrame, with progress callback
112
+ */
113
+ async streamFile(file: File, options: ParseOptions = {}): Promise<DataFrame> {
114
+ const {
115
+ delimiter = file.name.endsWith('.tsv') ? '\t' : ',',
116
+ hasHeader = true,
117
+ chunkSize = 64 * 1024,
118
+ onProgress,
119
+ maxRows,
120
+ skipRows = 0,
121
+ columns: selectCols
122
+ } = options;
123
+
124
+ const totalSize = file.size;
125
+ let bytesRead = 0;
126
+ let headers: string[] | null = null;
127
+ let lineBuffer = '';
128
+ let rowCount = 0;
129
+ let skipped = 0;
130
+ const rows: Record<string, any>[] = [];
131
+
132
+ const reader = file.stream().getReader();
133
+ const decoder = new TextDecoder();
134
+
135
+ while (true) {
136
+ const { done, value } = await reader.read();
137
+ if (done) break;
138
+
139
+ bytesRead += value.byteLength;
140
+ lineBuffer += decoder.decode(value, { stream: !done });
141
+
142
+ const lines = lineBuffer.split('\n');
143
+ lineBuffer = lines.pop() || '';
144
+
145
+ for (const line of lines) {
146
+ if (!line.trim()) continue;
147
+
148
+ if (!headers && hasHeader) {
149
+ headers = this._parseLine(line, delimiter);
150
+ continue;
151
+ }
152
+
153
+ if (!headers) {
154
+ const vals = this._parseLine(line, delimiter);
155
+ headers = vals.map((_, i) => `col_${i}`);
156
+ }
157
+
158
+ if (skipped < skipRows) {
159
+ skipped++;
160
+ continue;
161
+ }
162
+
163
+ if (maxRows && rowCount >= maxRows) break;
164
+
165
+ const values = this._parseLine(line, delimiter);
166
+ const row: Record<string, any> = {};
167
+ headers.forEach((h, j) => {
168
+ row[h] = this._autoType(values[j]);
169
+ });
170
+ rows.push(row);
171
+ rowCount++;
172
+ }
173
+
174
+ if (onProgress) {
175
+ onProgress(bytesRead, totalSize);
176
+ }
177
+
178
+ if (maxRows && rowCount >= maxRows) break;
179
+ }
180
+
181
+ // Handle remaining buffer
182
+ if (lineBuffer.trim() && (!maxRows || rowCount < maxRows)) {
183
+ if (!headers) {
184
+ headers = this._parseLine(lineBuffer, delimiter).map((_, i) => `col_${i}`);
185
+ } else {
186
+ const values = this._parseLine(lineBuffer, delimiter);
187
+ const row: Record<string, any> = {};
188
+ headers.forEach((h, j) => {
189
+ row[h] = this._autoType(values[j]);
190
+ });
191
+ rows.push(row);
192
+ }
193
+ }
194
+
195
+ if (onProgress) onProgress(totalSize, totalSize);
196
+
197
+ let df = new DataFrame(rows);
198
+ if (selectCols) df = df.select(...selectCols);
199
+
200
+ return df;
201
+ }
202
+
203
+ /* ═══════════════════════════════════════════════════
204
+ * INDEXEDDB PERSISTENCE
205
+ * ═══════════════════════════════════════════════════ */
206
+
207
+ /**
208
+ * Store a DataFrame to IndexedDB
209
+ */
210
+ async store(name: string, df: DataFrame, metadata?: Record<string, any>): Promise<string> {
211
+ const db = await this.open();
212
+
213
+ const table: StoredTable = {
214
+ id: `${name}-${Date.now()}`,
215
+ name,
216
+ columns: df.columns,
217
+ rows: df.toRows().map(row => df.columns.map(c => row[c])),
218
+ rowCount: df.height,
219
+ delimiter: ',',
220
+ timestamp: Date.now(),
221
+ metadata
222
+ };
223
+
224
+ return new Promise((resolve, reject) => {
225
+ const tx = db.transaction(this._storeName, 'readwrite');
226
+ const store = tx.objectStore(this._storeName);
227
+ const request = store.put(table);
228
+ request.onsuccess = () => resolve(table.id);
229
+ request.onerror = () => reject(request.error);
230
+ });
231
+ }
232
+
233
+ /**
234
+ * Load a DataFrame from IndexedDB by ID
235
+ */
236
+ async load(id: string): Promise<DataFrame | null> {
237
+ const db = await this.open();
238
+
239
+ return new Promise((resolve, reject) => {
240
+ const tx = db.transaction(this._storeName, 'readonly');
241
+ const store = tx.objectStore(this._storeName);
242
+ const request = store.get(id);
243
+
244
+ request.onsuccess = () => {
245
+ const table: StoredTable = request.result;
246
+ if (!table) return resolve(null);
247
+
248
+ const rows = table.rows.map(row => {
249
+ const obj: Record<string, any> = {};
250
+ table.columns.forEach((col, i) => { obj[col] = row[i]; });
251
+ return obj;
252
+ });
253
+
254
+ resolve(new DataFrame(rows));
255
+ };
256
+ request.onerror = () => reject(request.error);
257
+ });
258
+ }
259
+
260
+ /**
261
+ * Load the most recent table by name
262
+ */
263
+ async loadByName(name: string): Promise<DataFrame | null> {
264
+ const db = await this.open();
265
+
266
+ return new Promise((resolve, reject) => {
267
+ const tx = db.transaction(this._storeName, 'readonly');
268
+ const store = tx.objectStore(this._storeName);
269
+ const index = store.index('name');
270
+ const request = index.getAll(name);
271
+
272
+ request.onsuccess = () => {
273
+ const tables: StoredTable[] = request.result || [];
274
+ if (tables.length === 0) return resolve(null);
275
+
276
+ tables.sort((a, b) => b.timestamp - a.timestamp);
277
+ const table = tables[0];
278
+
279
+ const rows = table.rows.map(row => {
280
+ const obj: Record<string, any> = {};
281
+ table.columns.forEach((col, i) => { obj[col] = row[i]; });
282
+ return obj;
283
+ });
284
+
285
+ resolve(new DataFrame(rows));
286
+ };
287
+ request.onerror = () => reject(request.error);
288
+ });
289
+ }
290
+
291
+ /**
292
+ * List all stored tables (metadata only)
293
+ */
294
+ async list(): Promise<Array<{ id: string; name: string; columns: string[]; rowCount: number; timestamp: number }>> {
295
+ const db = await this.open();
296
+
297
+ return new Promise((resolve, reject) => {
298
+ const tx = db.transaction(this._storeName, 'readonly');
299
+ const store = tx.objectStore(this._storeName);
300
+ const request = store.getAll();
301
+
302
+ request.onsuccess = () => {
303
+ resolve((request.result || []).map((t: StoredTable) => ({
304
+ id: t.id,
305
+ name: t.name,
306
+ columns: t.columns,
307
+ rowCount: t.rowCount,
308
+ timestamp: t.timestamp
309
+ })));
310
+ };
311
+ request.onerror = () => reject(request.error);
312
+ });
313
+ }
314
+
315
+ /**
316
+ * Delete a stored table
317
+ */
318
+ async delete(id: string): Promise<void> {
319
+ const db = await this.open();
320
+
321
+ return new Promise((resolve, reject) => {
322
+ const tx = db.transaction(this._storeName, 'readwrite');
323
+ const store = tx.objectStore(this._storeName);
324
+ const request = store.delete(id);
325
+ request.onsuccess = () => resolve();
326
+ request.onerror = () => reject(request.error);
327
+ });
328
+ }
329
+
330
+ async clear(): Promise<void> {
331
+ const db = await this.open();
332
+
333
+ return new Promise((resolve, reject) => {
334
+ const tx = db.transaction(this._storeName, 'readwrite');
335
+ const store = tx.objectStore(this._storeName);
336
+ const request = store.clear();
337
+ request.onsuccess = () => resolve();
338
+ request.onerror = () => reject(request.error);
339
+ });
340
+ }
341
+
342
+ /* ═══════════════════════════════════════════════════
343
+ * STREAM FROM URL
344
+ * ═══════════════════════════════════════════════════ */
345
+
346
+ /**
347
+ * Fetch and stream-parse a remote CSV/TSV file
348
+ */
349
+ async fetch(url: string, options: ParseOptions = {}): Promise<DataFrame> {
350
+ const response = await globalThis.fetch(url);
351
+ if (!response.ok) throw new Error(`Failed to fetch: ${response.status}`);
352
+ if (!response.body) throw new Error('No response body');
353
+
354
+ const contentLength = Number(response.headers.get('content-length')) || null;
355
+ const ext = url.split('.').pop()?.toLowerCase();
356
+ const delimiter = options.delimiter ?? (ext === 'tsv' ? '\t' : ',');
357
+
358
+ const { hasHeader = true, chunkSize, onProgress, maxRows, skipRows = 0, columns: selectCols } = options;
359
+
360
+ const reader = response.body.getReader();
361
+ const decoder = new TextDecoder();
362
+
363
+ let headers: string[] | null = null;
364
+ let lineBuffer = '';
365
+ let rowCount = 0;
366
+ let skipped = 0;
367
+ let bytesRead = 0;
368
+ const rows: Record<string, any>[] = [];
369
+
370
+ while (true) {
371
+ const { done, value } = await reader.read();
372
+ if (done) break;
373
+
374
+ bytesRead += value.byteLength;
375
+ lineBuffer += decoder.decode(value, { stream: true });
376
+
377
+ const lines = lineBuffer.split('\n');
378
+ lineBuffer = lines.pop() || '';
379
+
380
+ for (const line of lines) {
381
+ if (!line.trim()) continue;
382
+
383
+ if (!headers && hasHeader) {
384
+ headers = this._parseLine(line, delimiter);
385
+ continue;
386
+ }
387
+
388
+ if (!headers) {
389
+ const vals = this._parseLine(line, delimiter);
390
+ headers = vals.map((_, i) => `col_${i}`);
391
+ }
392
+
393
+ if (skipped < skipRows) { skipped++; continue; }
394
+ if (maxRows && rowCount >= maxRows) break;
395
+
396
+ const values = this._parseLine(line, delimiter);
397
+ const row: Record<string, any> = {};
398
+ headers.forEach((h, j) => { row[h] = this._autoType(values[j]); });
399
+ rows.push(row);
400
+ rowCount++;
401
+ }
402
+
403
+ if (onProgress) onProgress(bytesRead, contentLength);
404
+ if (maxRows && rowCount >= maxRows) break;
405
+ }
406
+
407
+ if (lineBuffer.trim() && (!maxRows || rowCount < maxRows)) {
408
+ if (headers) {
409
+ const values = this._parseLine(lineBuffer, delimiter);
410
+ const row: Record<string, any> = {};
411
+ headers.forEach((h, j) => { row[h] = this._autoType(values[j]); });
412
+ rows.push(row);
413
+ }
414
+ }
415
+
416
+ if (onProgress) onProgress(bytesRead, bytesRead);
417
+
418
+ let df = new DataFrame(rows);
419
+ if (selectCols) df = df.select(...selectCols);
420
+ return df;
421
+ }
422
+
423
+ /* ═══════════════════════════════════════════════════
424
+ * INTERNAL PARSING HELPERS
425
+ * ═══════════════════════════════════════════════════ */
426
+
427
+ private _splitLines(text: string): string[] {
428
+ return text.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
429
+ }
430
+
431
+ private _parseLine(line: string, delimiter: string): string[] {
432
+ const result: string[] = [];
433
+ let current = '';
434
+ let inQuotes = false;
435
+
436
+ for (let i = 0; i < line.length; i++) {
437
+ const ch = line[i];
438
+
439
+ if (inQuotes) {
440
+ if (ch === '"') {
441
+ if (i + 1 < line.length && line[i + 1] === '"') {
442
+ current += '"';
443
+ i++;
444
+ } else {
445
+ inQuotes = false;
446
+ }
447
+ } else {
448
+ current += ch;
449
+ }
450
+ } else {
451
+ if (ch === '"') {
452
+ inQuotes = true;
453
+ } else if (ch === delimiter) {
454
+ result.push(current.trim());
455
+ current = '';
456
+ } else {
457
+ current += ch;
458
+ }
459
+ }
460
+ }
461
+
462
+ result.push(current.trim());
463
+ return result;
464
+ }
465
+
466
+ private _autoType(value: string | undefined): any {
467
+ if (value === undefined || value === '' || value === null) return null;
468
+
469
+ if (value === 'true') return true;
470
+ if (value === 'false') return false;
471
+
472
+ const num = Number(value);
473
+ if (!isNaN(num) && value.trim() !== '') return num;
474
+
475
+ return value;
476
+ }
477
+
478
+ close(): void {
479
+ if (this._db) {
480
+ this._db.close();
481
+ this._db = null;
482
+ }
483
+ }
484
+ }
485
+
486
+ /**
487
+ * Factory
488
+ */
489
+ export function tabularDriver(dbName?: string, storeName?: string): TabularDriver {
490
+ return new TabularDriver(dbName, storeName);
491
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.1.159",
3
+ "version": "1.1.161",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "index.js",