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