@cereusdb/minimal 0.3.0 → 0.4.0
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 +84 -2
- package/dist/external.d.ts +199 -4
- package/dist/external.js +219 -4
- package/dist/index.d.ts +199 -4
- package/dist/index.js +219 -4
- package/dist/storage.d.ts +64 -0
- package/dist/storage.js +211 -0
- package/dist/wasm/cereusdb-external.d.ts +68 -1
- package/dist/wasm/cereusdb-external.js +359 -3
- package/dist/wasm/cereusdb.d.ts +68 -1
- package/dist/wasm/cereusdb.js +359 -3
- package/dist/wasm/cereusdb_bg.wasm +0 -0
- package/dist/wasm/cereusdb_bg.wasm.d.ts +12 -0
- package/package.json +1 -1
package/dist/storage.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
function splitPath(path) {
|
|
2
|
+
return path.split('/').filter((segment) => segment.length > 0);
|
|
3
|
+
}
|
|
4
|
+
function isNotFound(error) {
|
|
5
|
+
const name = error?.name;
|
|
6
|
+
return name === 'NotFoundError' || name === 'TypeMismatchError';
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Keeps files in memory. Useful for tests, Node.js, and environments without
|
|
10
|
+
* OPFS. Share one instance between CereusDB instances to simulate a reload.
|
|
11
|
+
*/
|
|
12
|
+
export class MemoryStorageBackend {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.files = new Map();
|
|
15
|
+
this.locks = new Set();
|
|
16
|
+
}
|
|
17
|
+
async readFile(path) {
|
|
18
|
+
const data = this.files.get(splitPath(path).join('/'));
|
|
19
|
+
return data ? data.slice() : null;
|
|
20
|
+
}
|
|
21
|
+
async writeFile(path, data) {
|
|
22
|
+
this.files.set(splitPath(path).join('/'), data.slice());
|
|
23
|
+
}
|
|
24
|
+
async remove(path) {
|
|
25
|
+
const key = splitPath(path).join('/');
|
|
26
|
+
for (const existing of [...this.files.keys()]) {
|
|
27
|
+
if (key === '' || existing === key || existing.startsWith(`${key}/`)) {
|
|
28
|
+
this.files.delete(existing);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async list(path) {
|
|
33
|
+
const prefix = splitPath(path).join('/');
|
|
34
|
+
const entries = new Set();
|
|
35
|
+
for (const key of this.files.keys()) {
|
|
36
|
+
if (prefix !== '' && !key.startsWith(`${prefix}/`)) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const rest = prefix === '' ? key : key.slice(prefix.length + 1);
|
|
40
|
+
const [head, ...tail] = rest.split('/');
|
|
41
|
+
entries.add(tail.length > 0 ? `${head}/` : head);
|
|
42
|
+
}
|
|
43
|
+
return [...entries].sort();
|
|
44
|
+
}
|
|
45
|
+
async lock(name) {
|
|
46
|
+
if (this.locks.has(name)) {
|
|
47
|
+
throw new Error('it is already open in another CereusDB instance');
|
|
48
|
+
}
|
|
49
|
+
this.locks.add(name);
|
|
50
|
+
}
|
|
51
|
+
async unlock(name) {
|
|
52
|
+
this.locks.delete(name);
|
|
53
|
+
}
|
|
54
|
+
/** All stored file paths (for inspection and tests). */
|
|
55
|
+
paths() {
|
|
56
|
+
return [...this.files.keys()].sort();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Stores databases in the Origin Private File System (OPFS). Used for
|
|
61
|
+
* `opfs://` locations by default when the browser supports OPFS.
|
|
62
|
+
*
|
|
63
|
+
* Opening a database takes a Web Lock, so a database can be open in only one
|
|
64
|
+
* tab or worker at a time.
|
|
65
|
+
*/
|
|
66
|
+
export class OPFSStorageBackend {
|
|
67
|
+
constructor(options = {}) {
|
|
68
|
+
this.releases = new Map();
|
|
69
|
+
this.directory = options.directory ?? 'cereusdb';
|
|
70
|
+
}
|
|
71
|
+
/** Whether OPFS is available in the current environment. */
|
|
72
|
+
static isSupported() {
|
|
73
|
+
return (typeof navigator !== 'undefined' &&
|
|
74
|
+
typeof navigator.storage?.getDirectory === 'function');
|
|
75
|
+
}
|
|
76
|
+
root() {
|
|
77
|
+
this.rootPromise ?? (this.rootPromise = navigator.storage
|
|
78
|
+
.getDirectory()
|
|
79
|
+
.then((root) => root.getDirectoryHandle(this.directory, { create: true })));
|
|
80
|
+
return this.rootPromise;
|
|
81
|
+
}
|
|
82
|
+
async directoryHandle(segments, create) {
|
|
83
|
+
let directory = await this.root();
|
|
84
|
+
for (const segment of segments) {
|
|
85
|
+
try {
|
|
86
|
+
directory = await directory.getDirectoryHandle(segment, { create });
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (isNotFound(error)) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return directory;
|
|
96
|
+
}
|
|
97
|
+
async readFile(path) {
|
|
98
|
+
const segments = splitPath(path);
|
|
99
|
+
const name = segments.pop();
|
|
100
|
+
if (name === undefined) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
const directory = await this.directoryHandle(segments, false);
|
|
104
|
+
if (directory === null) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const file = await (await directory.getFileHandle(name)).getFile();
|
|
109
|
+
return new Uint8Array(await file.arrayBuffer());
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (isNotFound(error)) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async writeFile(path, data) {
|
|
119
|
+
const segments = splitPath(path);
|
|
120
|
+
const name = segments.pop();
|
|
121
|
+
if (name === undefined) {
|
|
122
|
+
throw new Error('writeFile requires a file path');
|
|
123
|
+
}
|
|
124
|
+
const directory = await this.directoryHandle(segments, true);
|
|
125
|
+
if (directory === null) {
|
|
126
|
+
throw new Error(`cannot create directory for ${path}`);
|
|
127
|
+
}
|
|
128
|
+
const handle = await directory.getFileHandle(name, { create: true });
|
|
129
|
+
if (typeof handle.createWritable === 'function') {
|
|
130
|
+
// Writes go to a swap file that replaces the original on close().
|
|
131
|
+
const writable = await handle.createWritable();
|
|
132
|
+
try {
|
|
133
|
+
await writable.write(data);
|
|
134
|
+
await writable.close();
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
await writable.abort().catch(() => undefined);
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
// Older Safari only supports synchronous access handles (in workers).
|
|
143
|
+
const createSyncAccessHandle = handle.createSyncAccessHandle;
|
|
144
|
+
if (typeof createSyncAccessHandle !== 'function') {
|
|
145
|
+
throw new Error('this browser cannot write OPFS files outside a worker');
|
|
146
|
+
}
|
|
147
|
+
const access = await createSyncAccessHandle.call(handle);
|
|
148
|
+
try {
|
|
149
|
+
access.truncate(0);
|
|
150
|
+
access.write(data, { at: 0 });
|
|
151
|
+
access.flush();
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
access.close();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async remove(path) {
|
|
158
|
+
const segments = splitPath(path);
|
|
159
|
+
const name = segments.pop();
|
|
160
|
+
if (name === undefined) {
|
|
161
|
+
throw new Error('remove requires a path');
|
|
162
|
+
}
|
|
163
|
+
const directory = await this.directoryHandle(segments, false);
|
|
164
|
+
if (directory === null) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
await directory.removeEntry(name, { recursive: true });
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
if (!isNotFound(error)) {
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async list(path) {
|
|
177
|
+
const directory = await this.directoryHandle(splitPath(path), false);
|
|
178
|
+
if (directory === null) {
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
const entries = [];
|
|
182
|
+
const iterable = directory;
|
|
183
|
+
for await (const [name, handle] of iterable.entries()) {
|
|
184
|
+
entries.push(handle.kind === 'directory' ? `${name}/` : name);
|
|
185
|
+
}
|
|
186
|
+
return entries.sort();
|
|
187
|
+
}
|
|
188
|
+
async lock(name) {
|
|
189
|
+
if (typeof navigator === 'undefined' || navigator.locks === undefined) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
await new Promise((resolve, reject) => {
|
|
193
|
+
navigator.locks
|
|
194
|
+
.request(name, { ifAvailable: true }, (lock) => {
|
|
195
|
+
if (lock === null) {
|
|
196
|
+
reject(new Error('it is open in another tab or worker'));
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
resolve();
|
|
200
|
+
// Hold the lock until unlock() resolves this promise.
|
|
201
|
+
return new Promise((release) => this.releases.set(name, release));
|
|
202
|
+
})
|
|
203
|
+
.catch(reject);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
async unlock(name) {
|
|
207
|
+
const release = this.releases.get(name);
|
|
208
|
+
this.releases.delete(name);
|
|
209
|
+
release?.();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -9,15 +9,65 @@ export class CereusDB {
|
|
|
9
9
|
private constructor();
|
|
10
10
|
free(): void;
|
|
11
11
|
[Symbol.dispose](): void;
|
|
12
|
+
/**
|
|
13
|
+
* Create (`create = true`) or attach a persistent database. Returns the
|
|
14
|
+
* database (catalog) name.
|
|
15
|
+
*/
|
|
16
|
+
attach_database(location: string, name: string | null | undefined, create: boolean, if_not_exists: boolean): Promise<string>;
|
|
17
|
+
/**
|
|
18
|
+
* Describe all databases, schemas, tables and views as a JSON string.
|
|
19
|
+
*/
|
|
20
|
+
catalog_json(): Promise<string>;
|
|
21
|
+
/**
|
|
22
|
+
* Rewrite every table of a persistent database into a single segment.
|
|
23
|
+
*/
|
|
24
|
+
compact_database(name: string): Promise<void>;
|
|
12
25
|
/**
|
|
13
26
|
* Create a new CereusDB instance.
|
|
14
27
|
* Initializes DataFusion context and registers all spatial functions.
|
|
15
28
|
*/
|
|
16
29
|
static create(): CereusDB;
|
|
17
30
|
/**
|
|
18
|
-
*
|
|
31
|
+
* Detach a persistent database after writing pending changes.
|
|
32
|
+
*/
|
|
33
|
+
detach_database(name: string, if_exists: boolean): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Drop a database by name or location URL. Persistent databases are
|
|
36
|
+
* deleted from storage.
|
|
37
|
+
*/
|
|
38
|
+
drop_database(target: string, if_exists: boolean): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Drop a registered table. Call `flush()` to write the change when the
|
|
41
|
+
* table belongs to a persistent database.
|
|
19
42
|
*/
|
|
20
43
|
drop_table(table_name: string): void;
|
|
44
|
+
/**
|
|
45
|
+
* Run a query and encode the result as GeoParquet. `options` may set
|
|
46
|
+
* `compression` and `rowGroupSize`.
|
|
47
|
+
*/
|
|
48
|
+
export_geoparquet(query: string, options: any): Promise<Uint8Array>;
|
|
49
|
+
/**
|
|
50
|
+
* Write all pending changes of attached persistent databases.
|
|
51
|
+
*/
|
|
52
|
+
flush(): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Insert Arrow IPC data (stream or file format) into a table, matching
|
|
55
|
+
* columns by name. Returns the number of inserted rows.
|
|
56
|
+
*/
|
|
57
|
+
insert_arrow(table_name: string, data: Uint8Array): Promise<number>;
|
|
58
|
+
/**
|
|
59
|
+
* Database locations stored in the backend for `scheme`, attached or not.
|
|
60
|
+
*/
|
|
61
|
+
list_database_locations(scheme: string): Promise<any>;
|
|
62
|
+
/**
|
|
63
|
+
* List all tables and views as `catalog.schema.table`.
|
|
64
|
+
*/
|
|
65
|
+
qualified_tables(): any;
|
|
66
|
+
/**
|
|
67
|
+
* Set the function `(filename, bytes, mimeType)` that receives files
|
|
68
|
+
* written by `COPY ... TO`. `null` removes it.
|
|
69
|
+
*/
|
|
70
|
+
register_export_handler(handler: any): void;
|
|
21
71
|
/**
|
|
22
72
|
* Register a GeoJSON string as a named table.
|
|
23
73
|
*/
|
|
@@ -51,6 +101,11 @@ export class CereusDB {
|
|
|
51
101
|
* The server must support CORS.
|
|
52
102
|
*/
|
|
53
103
|
register_remote_parquet(table_name: string, url: string): Promise<void>;
|
|
104
|
+
/**
|
|
105
|
+
* Register the storage backend used for database locations with the given
|
|
106
|
+
* URL scheme (for example `opfs`).
|
|
107
|
+
*/
|
|
108
|
+
register_storage_backend(scheme: string, backend: any): void;
|
|
54
109
|
/**
|
|
55
110
|
* Execute a SQL query.
|
|
56
111
|
* Returns results as Arrow IPC bytes (Uint8Array).
|
|
@@ -88,8 +143,19 @@ export interface InitOutput {
|
|
|
88
143
|
readonly malloc: (a: number) => number;
|
|
89
144
|
readonly __wbg_cereusdb_free: (a: number, b: number) => void;
|
|
90
145
|
readonly _abort_js: () => void;
|
|
146
|
+
readonly cereusdb_attach_database: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => any;
|
|
147
|
+
readonly cereusdb_catalog_json: (a: number) => any;
|
|
148
|
+
readonly cereusdb_compact_database: (a: number, b: number, c: number) => any;
|
|
91
149
|
readonly cereusdb_create: () => [number, number, number];
|
|
150
|
+
readonly cereusdb_detach_database: (a: number, b: number, c: number, d: number) => any;
|
|
151
|
+
readonly cereusdb_drop_database: (a: number, b: number, c: number, d: number) => any;
|
|
92
152
|
readonly cereusdb_drop_table: (a: number, b: number, c: number) => [number, number];
|
|
153
|
+
readonly cereusdb_export_geoparquet: (a: number, b: number, c: number, d: any) => any;
|
|
154
|
+
readonly cereusdb_flush: (a: number) => any;
|
|
155
|
+
readonly cereusdb_insert_arrow: (a: number, b: number, c: number, d: number, e: number) => any;
|
|
156
|
+
readonly cereusdb_list_database_locations: (a: number, b: number, c: number) => any;
|
|
157
|
+
readonly cereusdb_qualified_tables: (a: number) => [number, number, number];
|
|
158
|
+
readonly cereusdb_register_export_handler: (a: number, b: any) => [number, number];
|
|
93
159
|
readonly cereusdb_register_geojson: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
|
94
160
|
readonly cereusdb_register_geotiff_buffer: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
|
95
161
|
readonly cereusdb_register_object_stores: (a: number, b: any) => [number, number];
|
|
@@ -97,6 +163,7 @@ export interface InitOutput {
|
|
|
97
163
|
readonly cereusdb_register_parquet_table: (a: number, b: number, c: number, d: number, e: number, f: any) => any;
|
|
98
164
|
readonly cereusdb_register_raster_buffer: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => [number, number];
|
|
99
165
|
readonly cereusdb_register_remote_parquet: (a: number, b: number, c: number, d: number, e: number) => any;
|
|
166
|
+
readonly cereusdb_register_storage_backend: (a: number, b: number, c: number, d: any) => [number, number];
|
|
100
167
|
readonly cereusdb_sql: (a: number, b: number, c: number) => any;
|
|
101
168
|
readonly cereusdb_sql_json: (a: number, b: number, c: number) => any;
|
|
102
169
|
readonly cereusdb_tables: (a: number) => [number, number, number];
|