@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.
@@ -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
- * Drop a registered table.
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];