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.
- package/index.d.ts +4 -0
- package/index.d.ts.map +1 -1
- package/index.js +5 -0
- package/lib/components/checkbox.js +4 -4
- package/lib/components/checkbox.ts +5 -5
- package/lib/components/datepicker.d.ts.map +1 -1
- package/lib/components/datepicker.js +5 -4
- package/lib/components/datepicker.ts +5 -4
- package/lib/components/dialog.d.ts.map +1 -1
- package/lib/components/dialog.js +2 -0
- package/lib/components/dialog.ts +2 -0
- package/lib/components/dropdown.d.ts +1 -0
- package/lib/components/dropdown.d.ts.map +1 -1
- package/lib/components/dropdown.js +5 -6
- package/lib/components/dropdown.ts +6 -9
- package/lib/components/fileupload.d.ts +6 -0
- package/lib/components/fileupload.d.ts.map +1 -1
- package/lib/components/fileupload.js +31 -61
- package/lib/components/fileupload.ts +38 -67
- package/lib/components/input.js +6 -6
- package/lib/components/input.ts +6 -6
- package/lib/components/modal.d.ts.map +1 -1
- package/lib/components/modal.js +2 -0
- package/lib/components/modal.ts +2 -0
- package/lib/components/select.d.ts.map +1 -1
- package/lib/components/select.js +5 -0
- package/lib/components/select.ts +6 -0
- package/lib/components/switch.js +4 -4
- package/lib/components/switch.ts +5 -5
- package/lib/components/tabs.js +4 -4
- package/lib/components/tabs.ts +4 -4
- package/lib/storage/DataFrame.d.ts +59 -0
- package/lib/storage/DataFrame.d.ts.map +1 -0
- package/lib/storage/DataFrame.js +443 -0
- package/lib/storage/DataFrame.ts +472 -0
- package/lib/storage/FileStorage.d.ts +53 -0
- package/lib/storage/FileStorage.d.ts.map +1 -0
- package/lib/storage/FileStorage.js +80 -0
- package/lib/storage/FileStorage.ts +95 -0
- package/lib/storage/IndexedDBDriver.d.ts +75 -0
- package/lib/storage/IndexedDBDriver.d.ts.map +1 -0
- package/lib/storage/IndexedDBDriver.js +177 -0
- package/lib/storage/IndexedDBDriver.ts +226 -0
- package/lib/storage/TabularDriver.d.ts +75 -0
- package/lib/storage/TabularDriver.d.ts.map +1 -0
- package/lib/storage/TabularDriver.js +399 -0
- package/lib/storage/TabularDriver.ts +491 -0
- package/package.json +1 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { State } from '../reactivity/state.js';
|
|
2
|
+
import { IndexedDBDriver, StoredFile, FileQuery } from './IndexedDBDriver.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Reactive file storage — wraps IndexedDBDriver with a State<StoredFile[]>
|
|
6
|
+
* so components can subscribe to file list changes.
|
|
7
|
+
*/
|
|
8
|
+
export class FileStorage {
|
|
9
|
+
private _driver: IndexedDBDriver;
|
|
10
|
+
private _state: State<StoredFile[]>;
|
|
11
|
+
|
|
12
|
+
constructor(dbName?: string, storeName?: string) {
|
|
13
|
+
this._driver = new IndexedDBDriver(dbName, storeName);
|
|
14
|
+
this._state = new State<StoredFile[]>([]);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Reactive state — subscribe to file list changes */
|
|
18
|
+
get state(): State<StoredFile[]> {
|
|
19
|
+
return this._state;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Current file list */
|
|
23
|
+
get files(): StoredFile[] {
|
|
24
|
+
return this._state.value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The underlying driver for advanced operations */
|
|
28
|
+
get driver(): IndexedDBDriver {
|
|
29
|
+
return this._driver;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Load file list from IndexedDB and update state
|
|
34
|
+
*/
|
|
35
|
+
async refresh(query?: FileQuery): Promise<StoredFile[]> {
|
|
36
|
+
const files = await this._driver.list(query);
|
|
37
|
+
this._state.set(files);
|
|
38
|
+
return files;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Store a file and update state
|
|
43
|
+
*/
|
|
44
|
+
async store(file: File, metadata?: Record<string, any>): Promise<StoredFile> {
|
|
45
|
+
const stored = await this._driver.store(file, metadata);
|
|
46
|
+
await this.refresh();
|
|
47
|
+
return stored;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Store multiple files and update state
|
|
52
|
+
*/
|
|
53
|
+
async storeAll(files: File[], metadata?: Record<string, any>): Promise<StoredFile[]> {
|
|
54
|
+
const stored = await this._driver.storeAll(files, metadata);
|
|
55
|
+
await this.refresh();
|
|
56
|
+
return stored;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Delete a file and update state
|
|
61
|
+
*/
|
|
62
|
+
async delete(id: string): Promise<void> {
|
|
63
|
+
await this._driver.delete(id);
|
|
64
|
+
await this.refresh();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Clear all files and update state
|
|
69
|
+
*/
|
|
70
|
+
async clear(): Promise<void> {
|
|
71
|
+
await this._driver.clear();
|
|
72
|
+
this._state.set([]);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get a stored file by ID (does not affect state)
|
|
77
|
+
*/
|
|
78
|
+
async get(id: string): Promise<StoredFile | null> {
|
|
79
|
+
return this._driver.get(id);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get storage usage
|
|
84
|
+
*/
|
|
85
|
+
async usage(): Promise<{ count: number; bytes: number }> {
|
|
86
|
+
return this._driver.usage();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Factory function
|
|
92
|
+
*/
|
|
93
|
+
export function fileStorage(dbName?: string, storeName?: string): FileStorage {
|
|
94
|
+
return new FileStorage(dbName, storeName);
|
|
95
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export interface StoredFile {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
size: number;
|
|
5
|
+
type: string;
|
|
6
|
+
data: ArrayBuffer;
|
|
7
|
+
timestamp: number;
|
|
8
|
+
metadata?: Record<string, any>;
|
|
9
|
+
}
|
|
10
|
+
export interface FileQuery {
|
|
11
|
+
type?: string;
|
|
12
|
+
minSize?: number;
|
|
13
|
+
maxSize?: number;
|
|
14
|
+
since?: number;
|
|
15
|
+
limit?: number;
|
|
16
|
+
}
|
|
17
|
+
export declare class IndexedDBDriver {
|
|
18
|
+
private _dbName;
|
|
19
|
+
private _storeName;
|
|
20
|
+
private _version;
|
|
21
|
+
private _db;
|
|
22
|
+
constructor(dbName?: string, storeName?: string, version?: number);
|
|
23
|
+
/**
|
|
24
|
+
* Open the database connection
|
|
25
|
+
*/
|
|
26
|
+
open(): Promise<IDBDatabase>;
|
|
27
|
+
/**
|
|
28
|
+
* Store a File object
|
|
29
|
+
*/
|
|
30
|
+
store(file: File, metadata?: Record<string, any>): Promise<StoredFile>;
|
|
31
|
+
/**
|
|
32
|
+
* Store multiple files
|
|
33
|
+
*/
|
|
34
|
+
storeAll(files: File[], metadata?: Record<string, any>): Promise<StoredFile[]>;
|
|
35
|
+
/**
|
|
36
|
+
* Retrieve a stored file by ID
|
|
37
|
+
*/
|
|
38
|
+
get(id: string): Promise<StoredFile | null>;
|
|
39
|
+
/**
|
|
40
|
+
* Get all stored files (metadata only, no data by default)
|
|
41
|
+
*/
|
|
42
|
+
list(query?: FileQuery): Promise<StoredFile[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Delete a stored file by ID
|
|
45
|
+
*/
|
|
46
|
+
delete(id: string): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Delete all stored files
|
|
49
|
+
*/
|
|
50
|
+
clear(): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Get total storage usage in bytes
|
|
53
|
+
*/
|
|
54
|
+
usage(): Promise<{
|
|
55
|
+
count: number;
|
|
56
|
+
bytes: number;
|
|
57
|
+
}>;
|
|
58
|
+
/**
|
|
59
|
+
* Convert a StoredFile back to a File object
|
|
60
|
+
*/
|
|
61
|
+
toFile(stored: StoredFile): File;
|
|
62
|
+
/**
|
|
63
|
+
* Convert a StoredFile to a blob URL for display/download
|
|
64
|
+
*/
|
|
65
|
+
toBlobUrl(stored: StoredFile): string;
|
|
66
|
+
/**
|
|
67
|
+
* Close the database connection
|
|
68
|
+
*/
|
|
69
|
+
close(): void;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Factory function
|
|
73
|
+
*/
|
|
74
|
+
export declare function indexedDBDriver(dbName?: string, storeName?: string): IndexedDBDriver;
|
|
75
|
+
//# sourceMappingURL=IndexedDBDriver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IndexedDBDriver.d.ts","sourceRoot":"","sources":["IndexedDBDriver.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,WAAW,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,SAAS;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,eAAe;IACxB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,GAAG,CAA4B;gBAE3B,MAAM,GAAE,MAAoB,EAAE,SAAS,GAAE,MAAgB,EAAE,OAAO,GAAE,MAAU;IAM1F;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC;IA0BlC;;OAEG;IACG,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC;IAuB5E;;OAEG;IACG,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAQpF;;OAEG;IACG,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAYjD;;OAEG;IACG,IAAI,CAAC,KAAK,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAuCpD;;OAEG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYvC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAY5B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAQxD;;OAEG;IACH,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAIhC;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM;IAKrC;;OAEG;IACH,KAAK,IAAI,IAAI;CAMhB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,eAAe,CAEpF"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
export class IndexedDBDriver {
|
|
2
|
+
constructor(dbName = 'jux-files', storeName = 'files', version = 1) {
|
|
3
|
+
this._db = null;
|
|
4
|
+
this._dbName = dbName;
|
|
5
|
+
this._storeName = storeName;
|
|
6
|
+
this._version = version;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Open the database connection
|
|
10
|
+
*/
|
|
11
|
+
async open() {
|
|
12
|
+
if (this._db)
|
|
13
|
+
return this._db;
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const request = indexedDB.open(this._dbName, this._version);
|
|
16
|
+
request.onupgradeneeded = (e) => {
|
|
17
|
+
const db = e.target.result;
|
|
18
|
+
if (!db.objectStoreNames.contains(this._storeName)) {
|
|
19
|
+
const store = db.createObjectStore(this._storeName, { keyPath: 'id' });
|
|
20
|
+
store.createIndex('name', 'name', { unique: false });
|
|
21
|
+
store.createIndex('type', 'type', { unique: false });
|
|
22
|
+
store.createIndex('timestamp', 'timestamp', { unique: false });
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
request.onsuccess = (e) => {
|
|
26
|
+
this._db = e.target.result;
|
|
27
|
+
resolve(this._db);
|
|
28
|
+
};
|
|
29
|
+
request.onerror = () => reject(request.error);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Store a File object
|
|
34
|
+
*/
|
|
35
|
+
async store(file, metadata) {
|
|
36
|
+
const db = await this.open();
|
|
37
|
+
const data = await file.arrayBuffer();
|
|
38
|
+
const stored = {
|
|
39
|
+
id: `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
40
|
+
name: file.name,
|
|
41
|
+
size: file.size,
|
|
42
|
+
type: file.type,
|
|
43
|
+
data,
|
|
44
|
+
timestamp: Date.now(),
|
|
45
|
+
metadata
|
|
46
|
+
};
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const tx = db.transaction(this._storeName, 'readwrite');
|
|
49
|
+
const store = tx.objectStore(this._storeName);
|
|
50
|
+
const request = store.put(stored);
|
|
51
|
+
request.onsuccess = () => resolve(stored);
|
|
52
|
+
request.onerror = () => reject(request.error);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Store multiple files
|
|
57
|
+
*/
|
|
58
|
+
async storeAll(files, metadata) {
|
|
59
|
+
const results = [];
|
|
60
|
+
for (const file of files) {
|
|
61
|
+
results.push(await this.store(file, metadata));
|
|
62
|
+
}
|
|
63
|
+
return results;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Retrieve a stored file by ID
|
|
67
|
+
*/
|
|
68
|
+
async get(id) {
|
|
69
|
+
const db = await this.open();
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
const tx = db.transaction(this._storeName, 'readonly');
|
|
72
|
+
const store = tx.objectStore(this._storeName);
|
|
73
|
+
const request = store.get(id);
|
|
74
|
+
request.onsuccess = () => resolve(request.result || null);
|
|
75
|
+
request.onerror = () => reject(request.error);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get all stored files (metadata only, no data by default)
|
|
80
|
+
*/
|
|
81
|
+
async list(query) {
|
|
82
|
+
const db = await this.open();
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
const tx = db.transaction(this._storeName, 'readonly');
|
|
85
|
+
const store = tx.objectStore(this._storeName);
|
|
86
|
+
const request = store.getAll();
|
|
87
|
+
request.onsuccess = () => {
|
|
88
|
+
let results = request.result || [];
|
|
89
|
+
if (query) {
|
|
90
|
+
if (query.type) {
|
|
91
|
+
results = results.filter(f => f.type.startsWith(query.type));
|
|
92
|
+
}
|
|
93
|
+
if (query.minSize !== undefined) {
|
|
94
|
+
results = results.filter(f => f.size >= query.minSize);
|
|
95
|
+
}
|
|
96
|
+
if (query.maxSize !== undefined) {
|
|
97
|
+
results = results.filter(f => f.size <= query.maxSize);
|
|
98
|
+
}
|
|
99
|
+
if (query.since !== undefined) {
|
|
100
|
+
results = results.filter(f => f.timestamp >= query.since);
|
|
101
|
+
}
|
|
102
|
+
// Sort by timestamp descending
|
|
103
|
+
results.sort((a, b) => b.timestamp - a.timestamp);
|
|
104
|
+
if (query.limit !== undefined) {
|
|
105
|
+
results = results.slice(0, query.limit);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
resolve(results);
|
|
109
|
+
};
|
|
110
|
+
request.onerror = () => reject(request.error);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Delete a stored file by ID
|
|
115
|
+
*/
|
|
116
|
+
async delete(id) {
|
|
117
|
+
const db = await this.open();
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
const tx = db.transaction(this._storeName, 'readwrite');
|
|
120
|
+
const store = tx.objectStore(this._storeName);
|
|
121
|
+
const request = store.delete(id);
|
|
122
|
+
request.onsuccess = () => resolve();
|
|
123
|
+
request.onerror = () => reject(request.error);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Delete all stored files
|
|
128
|
+
*/
|
|
129
|
+
async clear() {
|
|
130
|
+
const db = await this.open();
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
const tx = db.transaction(this._storeName, 'readwrite');
|
|
133
|
+
const store = tx.objectStore(this._storeName);
|
|
134
|
+
const request = store.clear();
|
|
135
|
+
request.onsuccess = () => resolve();
|
|
136
|
+
request.onerror = () => reject(request.error);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Get total storage usage in bytes
|
|
141
|
+
*/
|
|
142
|
+
async usage() {
|
|
143
|
+
const files = await this.list();
|
|
144
|
+
return {
|
|
145
|
+
count: files.length,
|
|
146
|
+
bytes: files.reduce((sum, f) => sum + f.size, 0)
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Convert a StoredFile back to a File object
|
|
151
|
+
*/
|
|
152
|
+
toFile(stored) {
|
|
153
|
+
return new File([stored.data], stored.name, { type: stored.type });
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Convert a StoredFile to a blob URL for display/download
|
|
157
|
+
*/
|
|
158
|
+
toBlobUrl(stored) {
|
|
159
|
+
const blob = new Blob([stored.data], { type: stored.type });
|
|
160
|
+
return URL.createObjectURL(blob);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Close the database connection
|
|
164
|
+
*/
|
|
165
|
+
close() {
|
|
166
|
+
if (this._db) {
|
|
167
|
+
this._db.close();
|
|
168
|
+
this._db = null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Factory function
|
|
174
|
+
*/
|
|
175
|
+
export function indexedDBDriver(dbName, storeName) {
|
|
176
|
+
return new IndexedDBDriver(dbName, storeName);
|
|
177
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
export interface StoredFile {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
size: number;
|
|
5
|
+
type: string;
|
|
6
|
+
data: ArrayBuffer;
|
|
7
|
+
timestamp: number;
|
|
8
|
+
metadata?: Record<string, any>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface FileQuery {
|
|
12
|
+
type?: string;
|
|
13
|
+
minSize?: number;
|
|
14
|
+
maxSize?: number;
|
|
15
|
+
since?: number;
|
|
16
|
+
limit?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class IndexedDBDriver {
|
|
20
|
+
private _dbName: string;
|
|
21
|
+
private _storeName: string;
|
|
22
|
+
private _version: number;
|
|
23
|
+
private _db: IDBDatabase | null = null;
|
|
24
|
+
|
|
25
|
+
constructor(dbName: string = 'jux-files', storeName: string = 'files', version: number = 1) {
|
|
26
|
+
this._dbName = dbName;
|
|
27
|
+
this._storeName = storeName;
|
|
28
|
+
this._version = version;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Open the database connection
|
|
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, this._version);
|
|
39
|
+
|
|
40
|
+
request.onupgradeneeded = (e) => {
|
|
41
|
+
const db = (e.target as IDBOpenDBRequest).result;
|
|
42
|
+
|
|
43
|
+
if (!db.objectStoreNames.contains(this._storeName)) {
|
|
44
|
+
const store = db.createObjectStore(this._storeName, { keyPath: 'id' });
|
|
45
|
+
store.createIndex('name', 'name', { unique: false });
|
|
46
|
+
store.createIndex('type', 'type', { unique: false });
|
|
47
|
+
store.createIndex('timestamp', 'timestamp', { unique: false });
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
request.onsuccess = (e) => {
|
|
52
|
+
this._db = (e.target as IDBOpenDBRequest).result;
|
|
53
|
+
resolve(this._db);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
request.onerror = () => reject(request.error);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Store a File object
|
|
62
|
+
*/
|
|
63
|
+
async store(file: File, metadata?: Record<string, any>): Promise<StoredFile> {
|
|
64
|
+
const db = await this.open();
|
|
65
|
+
const data = await file.arrayBuffer();
|
|
66
|
+
|
|
67
|
+
const stored: StoredFile = {
|
|
68
|
+
id: `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
69
|
+
name: file.name,
|
|
70
|
+
size: file.size,
|
|
71
|
+
type: file.type,
|
|
72
|
+
data,
|
|
73
|
+
timestamp: Date.now(),
|
|
74
|
+
metadata
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
const tx = db.transaction(this._storeName, 'readwrite');
|
|
79
|
+
const store = tx.objectStore(this._storeName);
|
|
80
|
+
const request = store.put(stored);
|
|
81
|
+
request.onsuccess = () => resolve(stored);
|
|
82
|
+
request.onerror = () => reject(request.error);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Store multiple files
|
|
88
|
+
*/
|
|
89
|
+
async storeAll(files: File[], metadata?: Record<string, any>): Promise<StoredFile[]> {
|
|
90
|
+
const results: StoredFile[] = [];
|
|
91
|
+
for (const file of files) {
|
|
92
|
+
results.push(await this.store(file, metadata));
|
|
93
|
+
}
|
|
94
|
+
return results;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Retrieve a stored file by ID
|
|
99
|
+
*/
|
|
100
|
+
async get(id: string): Promise<StoredFile | null> {
|
|
101
|
+
const db = await this.open();
|
|
102
|
+
|
|
103
|
+
return new Promise((resolve, reject) => {
|
|
104
|
+
const tx = db.transaction(this._storeName, 'readonly');
|
|
105
|
+
const store = tx.objectStore(this._storeName);
|
|
106
|
+
const request = store.get(id);
|
|
107
|
+
request.onsuccess = () => resolve(request.result || null);
|
|
108
|
+
request.onerror = () => reject(request.error);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get all stored files (metadata only, no data by default)
|
|
114
|
+
*/
|
|
115
|
+
async list(query?: FileQuery): Promise<StoredFile[]> {
|
|
116
|
+
const db = await this.open();
|
|
117
|
+
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
const tx = db.transaction(this._storeName, 'readonly');
|
|
120
|
+
const store = tx.objectStore(this._storeName);
|
|
121
|
+
const request = store.getAll();
|
|
122
|
+
|
|
123
|
+
request.onsuccess = () => {
|
|
124
|
+
let results: StoredFile[] = request.result || [];
|
|
125
|
+
|
|
126
|
+
if (query) {
|
|
127
|
+
if (query.type) {
|
|
128
|
+
results = results.filter(f => f.type.startsWith(query.type!));
|
|
129
|
+
}
|
|
130
|
+
if (query.minSize !== undefined) {
|
|
131
|
+
results = results.filter(f => f.size >= query.minSize!);
|
|
132
|
+
}
|
|
133
|
+
if (query.maxSize !== undefined) {
|
|
134
|
+
results = results.filter(f => f.size <= query.maxSize!);
|
|
135
|
+
}
|
|
136
|
+
if (query.since !== undefined) {
|
|
137
|
+
results = results.filter(f => f.timestamp >= query.since!);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Sort by timestamp descending
|
|
141
|
+
results.sort((a, b) => b.timestamp - a.timestamp);
|
|
142
|
+
|
|
143
|
+
if (query.limit !== undefined) {
|
|
144
|
+
results = results.slice(0, query.limit);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
resolve(results);
|
|
149
|
+
};
|
|
150
|
+
request.onerror = () => reject(request.error);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Delete a stored file by ID
|
|
156
|
+
*/
|
|
157
|
+
async delete(id: string): Promise<void> {
|
|
158
|
+
const db = await this.open();
|
|
159
|
+
|
|
160
|
+
return new Promise((resolve, reject) => {
|
|
161
|
+
const tx = db.transaction(this._storeName, 'readwrite');
|
|
162
|
+
const store = tx.objectStore(this._storeName);
|
|
163
|
+
const request = store.delete(id);
|
|
164
|
+
request.onsuccess = () => resolve();
|
|
165
|
+
request.onerror = () => reject(request.error);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Delete all stored files
|
|
171
|
+
*/
|
|
172
|
+
async clear(): Promise<void> {
|
|
173
|
+
const db = await this.open();
|
|
174
|
+
|
|
175
|
+
return new Promise((resolve, reject) => {
|
|
176
|
+
const tx = db.transaction(this._storeName, 'readwrite');
|
|
177
|
+
const store = tx.objectStore(this._storeName);
|
|
178
|
+
const request = store.clear();
|
|
179
|
+
request.onsuccess = () => resolve();
|
|
180
|
+
request.onerror = () => reject(request.error);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get total storage usage in bytes
|
|
186
|
+
*/
|
|
187
|
+
async usage(): Promise<{ count: number; bytes: number }> {
|
|
188
|
+
const files = await this.list();
|
|
189
|
+
return {
|
|
190
|
+
count: files.length,
|
|
191
|
+
bytes: files.reduce((sum, f) => sum + f.size, 0)
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Convert a StoredFile back to a File object
|
|
197
|
+
*/
|
|
198
|
+
toFile(stored: StoredFile): File {
|
|
199
|
+
return new File([stored.data], stored.name, { type: stored.type });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Convert a StoredFile to a blob URL for display/download
|
|
204
|
+
*/
|
|
205
|
+
toBlobUrl(stored: StoredFile): string {
|
|
206
|
+
const blob = new Blob([stored.data], { type: stored.type });
|
|
207
|
+
return URL.createObjectURL(blob);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Close the database connection
|
|
212
|
+
*/
|
|
213
|
+
close(): void {
|
|
214
|
+
if (this._db) {
|
|
215
|
+
this._db.close();
|
|
216
|
+
this._db = null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Factory function
|
|
223
|
+
*/
|
|
224
|
+
export function indexedDBDriver(dbName?: string, storeName?: string): IndexedDBDriver {
|
|
225
|
+
return new IndexedDBDriver(dbName, storeName);
|
|
226
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { DataFrame } from './DataFrame.js';
|
|
2
|
+
export interface StoredTable {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
columns: string[];
|
|
6
|
+
rows: any[][];
|
|
7
|
+
rowCount: number;
|
|
8
|
+
delimiter: string;
|
|
9
|
+
timestamp: number;
|
|
10
|
+
metadata?: Record<string, any>;
|
|
11
|
+
}
|
|
12
|
+
export interface ParseOptions {
|
|
13
|
+
delimiter?: string;
|
|
14
|
+
hasHeader?: boolean;
|
|
15
|
+
chunkSize?: number;
|
|
16
|
+
onProgress?: (parsed: number, total: number | null) => void;
|
|
17
|
+
maxRows?: number;
|
|
18
|
+
skipRows?: number;
|
|
19
|
+
columns?: string[];
|
|
20
|
+
}
|
|
21
|
+
export declare class TabularDriver {
|
|
22
|
+
private _dbName;
|
|
23
|
+
private _storeName;
|
|
24
|
+
private _db;
|
|
25
|
+
constructor(dbName?: string, storeName?: string);
|
|
26
|
+
open(): Promise<IDBDatabase>;
|
|
27
|
+
/**
|
|
28
|
+
* Parse a CSV/TSV string into a DataFrame
|
|
29
|
+
*/
|
|
30
|
+
parseCSV(text: string, options?: ParseOptions): DataFrame;
|
|
31
|
+
/**
|
|
32
|
+
* Stream-parse a File into a DataFrame, with progress callback
|
|
33
|
+
*/
|
|
34
|
+
streamFile(file: File, options?: ParseOptions): Promise<DataFrame>;
|
|
35
|
+
/**
|
|
36
|
+
* Store a DataFrame to IndexedDB
|
|
37
|
+
*/
|
|
38
|
+
store(name: string, df: DataFrame, metadata?: Record<string, any>): Promise<string>;
|
|
39
|
+
/**
|
|
40
|
+
* Load a DataFrame from IndexedDB by ID
|
|
41
|
+
*/
|
|
42
|
+
load(id: string): Promise<DataFrame | null>;
|
|
43
|
+
/**
|
|
44
|
+
* Load the most recent table by name
|
|
45
|
+
*/
|
|
46
|
+
loadByName(name: string): Promise<DataFrame | null>;
|
|
47
|
+
/**
|
|
48
|
+
* List all stored tables (metadata only)
|
|
49
|
+
*/
|
|
50
|
+
list(): Promise<Array<{
|
|
51
|
+
id: string;
|
|
52
|
+
name: string;
|
|
53
|
+
columns: string[];
|
|
54
|
+
rowCount: number;
|
|
55
|
+
timestamp: number;
|
|
56
|
+
}>>;
|
|
57
|
+
/**
|
|
58
|
+
* Delete a stored table
|
|
59
|
+
*/
|
|
60
|
+
delete(id: string): Promise<void>;
|
|
61
|
+
clear(): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Fetch and stream-parse a remote CSV/TSV file
|
|
64
|
+
*/
|
|
65
|
+
fetch(url: string, options?: ParseOptions): Promise<DataFrame>;
|
|
66
|
+
private _splitLines;
|
|
67
|
+
private _parseLine;
|
|
68
|
+
private _autoType;
|
|
69
|
+
close(): void;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Factory
|
|
73
|
+
*/
|
|
74
|
+
export declare function tabularDriver(dbName?: string, storeName?: string): TabularDriver;
|
|
75
|
+
//# sourceMappingURL=TabularDriver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TabularDriver.d.ts","sourceRoot":"","sources":["TabularDriver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,YAAY;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,qBAAa,aAAa;IACtB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,GAAG,CAA4B;gBAE3B,MAAM,GAAE,MAAsB,EAAE,SAAS,GAAE,MAAiB;IAKlE,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC;IA4BlC;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,SAAS;IA6C7D;;OAEG;IACG,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,SAAS,CAAC;IA8F5E;;OAEG;IACG,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAuBzF;;OAEG;IACG,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAwBjD;;OAEG;IACG,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IA4BzD;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAqBlH;;OAEG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB5B;;OAEG;IACG,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,SAAS,CAAC;IA8ExE,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,UAAU;IAmClB,OAAO,CAAC,SAAS;IAYjB,KAAK,IAAI,IAAI;CAMhB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,aAAa,CAEhF"}
|