hyperdb-wasm 0.0.26862-dev.wasm.g8c0876f6 → 0.0.27000-dev.wasm.ge6287b4f

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/dist/api.d.ts CHANGED
@@ -2,28 +2,19 @@ import { DataType, Table } from 'apache-arrow';
2
2
  import type { AsyncRecordBatchStreamReader, TypeMap } from 'apache-arrow';
3
3
  import type { HyperDBEngineClient } from './client.js';
4
4
  import type { BrowserClientOptions } from './browser.js';
5
- import type { WasmResult } from './protocol.js';
5
+ import type { MountSource, WasmResult } from './protocol.js';
6
6
  import type { HyperDBErrorSource } from './protocol.js';
7
7
  export interface HyperDBClient {
8
8
  ready(): Promise<void>;
9
9
  initialize(settings: string): Promise<WasmResult>;
10
10
  connect(): Promise<WasmResult>;
11
11
  disconnect(connection: number): Promise<WasmResult>;
12
- createDatabase(databaseName: string, persistent: boolean): Promise<WasmResult>;
13
- openDatabase(databaseName: string): Promise<WasmResult>;
14
- listDatabases(): Promise<WasmResult>;
15
- checkpointDatabase(databaseName: string): Promise<WasmResult>;
16
- dropDatabase(databaseName: string): Promise<WasmResult>;
17
- attachDatabase(connection: number, databaseName: string, alias: string): Promise<WasmResult>;
18
- detachDatabase(connection: number, alias: string): Promise<WasmResult>;
19
12
  startQuery(connection: number, sql: string): Promise<WasmResult>;
20
13
  insertArrowIPCFromPath(connection: number, path: string, name: string, schema: string | null, create: boolean, internal?: boolean): Promise<WasmResult>;
21
14
  createTemporaryFile(bytes: Uint8Array): Promise<WasmResult>;
22
15
  removeFile(path: string): Promise<WasmResult>;
23
- registerExternalFile(path: string): Promise<WasmResult>;
24
- registerExternalFile(name: string, url: string, size: number): Promise<WasmResult>;
25
- registerExternalDirectory(handle: unknown, mode: 'read' | 'readwrite'): Promise<WasmResult>;
26
- removeExternalRegistration(path: string): Promise<WasmResult>;
16
+ mount(source: MountSource, path: string, mode: MountAccessMode): Promise<WasmResult>;
17
+ unmount(path: string): Promise<WasmResult>;
27
18
  pollQuery(query: number): Promise<WasmResult>;
28
19
  cancelQuery(query: number): Promise<WasmResult>;
29
20
  finishQuery(query: number): Promise<WasmResult>;
@@ -35,21 +26,16 @@ export interface ArrowInsertOptions {
35
26
  readonly schema?: string;
36
27
  readonly create?: boolean;
37
28
  }
38
- export interface DatabaseCreateOptions {
39
- readonly storage?: 'transient' | 'persistent';
29
+ export type MountAccessMode = 'read' | 'readwrite';
30
+ export interface MountOptions {
31
+ readonly path: string;
32
+ readonly mode?: MountAccessMode;
40
33
  }
41
- export interface ExternalFileRegistration {
42
- readonly name: string;
43
- readonly url: string;
44
- readonly size: number;
45
- }
46
- export type ExternalDirectoryMode = 'read' | 'readwrite';
47
- export interface ExternalDirectoryRegistrationOptions {
48
- readonly mode?: ExternalDirectoryMode;
34
+ export interface MountedResource {
35
+ readonly path: string;
36
+ close(): Promise<void>;
49
37
  }
50
- type BrowserExternalFileArguments = readonly [name: string, url: string, size: number];
51
- type NodeExternalFileArguments = readonly [path: string];
52
- export type ExternalFileArguments = BrowserExternalFileArguments | NodeExternalFileArguments;
38
+ export declare function validateMountPath(path: unknown): asserts path is string;
53
39
  export type HyperDBSettingValue = string | number | boolean | null | readonly HyperDBSettingValue[] | {
54
40
  readonly [key: string]: HyperDBSettingValue;
55
41
  };
@@ -58,10 +44,6 @@ export interface HyperDBCreateOptions {
58
44
  readonly settings?: HyperDBSettings;
59
45
  }
60
46
  export type BrowserCreateOptions = BrowserClientOptions & HyperDBCreateOptions;
61
- export interface DatabaseMetadata {
62
- readonly name: string;
63
- readonly storage: 'transient' | 'persistent';
64
- }
65
47
  export type QueryMetricValue = null | boolean | number | string | readonly QueryMetricValue[] | {
66
48
  readonly [key: string]: QueryMetricValue;
67
49
  };
@@ -102,27 +84,22 @@ export declare class HyperDBError extends Error {
102
84
  constructor(message: string, sqlState: string, errorSource: HyperDBErrorSource, detail?: string, hint?: string);
103
85
  }
104
86
  declare function initializeHyperDB(client: HyperDBClient, settings: HyperDBSettings | undefined): Promise<void>;
105
- export declare class HyperDB<ExternalFileArgs extends ExternalFileArguments = BrowserExternalFileArguments> {
87
+ export declare class HyperDB {
106
88
  protected readonly client: HyperDBClient;
107
89
  private terminated;
108
90
  private termination;
109
91
  private activeDatabaseOperations;
110
92
  private readonly connections;
93
+ private readonly mountedResources;
111
94
  constructor(client: HyperDBClient);
112
95
  static create(options?: BrowserCreateOptions): Promise<HyperDB>;
113
96
  ready(): Promise<void>;
114
97
  connect(): Promise<HyperDBConnection>;
115
- createDatabase(databaseName: string, options?: DatabaseCreateOptions): Promise<void>;
116
- openDatabase(databaseName: string): Promise<void>;
117
- listDatabases(): Promise<DatabaseMetadata[]>;
118
- registerExternalFile(...args: ExternalFileArgs): Promise<string>;
119
- removeExternalRegistration(path: string): Promise<void>;
120
- protected registerExternalDirectorySource(source: unknown, mode: ExternalDirectoryMode): Promise<string>;
121
- checkpointDatabase(databaseName: string): Promise<void>;
122
- dropDatabase(databaseName: string): Promise<void>;
98
+ protected mountSource(source: MountSource, path: string, mode: MountAccessMode, cleanup?: () => void): Promise<MountedResource>;
123
99
  terminate(): Promise<void>;
124
100
  private ensureRunning;
125
101
  protected runDatabaseOperation<Result>(operation: () => Promise<Result>): Promise<Result>;
102
+ private unmount;
126
103
  }
127
104
  export declare class HyperDBConnection {
128
105
  private readonly client;
@@ -133,8 +110,6 @@ export declare class HyperDBConnection {
133
110
  private closing;
134
111
  constructor(client: HyperDBClient, connection: number, onClose?: () => void);
135
112
  close(): Promise<void>;
136
- attachDatabase(databaseName: string, alias?: string): Promise<void>;
137
- detachDatabase(alias: string): Promise<void>;
138
113
  sendIPC(sql: string): QueryResult<AsyncIterable<Uint8Array>>;
139
114
  queryIPC(sql: string): QueryResult<Promise<Uint8Array>>;
140
115
  send<T extends TypeMap = {
package/dist/browser.d.ts CHANGED
@@ -11,6 +11,8 @@ export interface BrowserClientOptions {
11
11
  readonly workerUrl?: string | URL;
12
12
  readonly engineUrl?: string | URL;
13
13
  readonly wasmUrl?: string | URL;
14
+ /** OPFS-relative directory mounted as the durable filesystem root. */
15
+ readonly storagePath?: string;
14
16
  readonly onSetupProgress?: (progress: SetupProgress) => void;
15
17
  }
16
18
  type BrowserEnvironment = {
@@ -36,6 +38,6 @@ export declare class BrowserWorker implements WorkerTransport {
36
38
  onClose(): () => void;
37
39
  terminate(): void;
38
40
  }
39
- export declare function resolveBrowserWorkerUrl(workerUrl: string | URL, engineUrl: string | URL, wasmUrl?: string | URL): URL;
41
+ export declare function resolveBrowserWorkerUrl(workerUrl: string | URL, engineUrl: string | URL, wasmUrl?: string | URL, storagePath?: string): URL;
40
42
  export declare function createBrowserClient(options?: BrowserClientOptions): HyperDBEngineClient;
41
43
  export {};
package/dist/browser.js CHANGED
@@ -106,27 +106,6 @@ var init_client = __esm({
106
106
  disconnect(connection) {
107
107
  return this.request({ id: this.nextId++, type: "disconnect", connection });
108
108
  }
109
- createDatabase(databaseName, persistent) {
110
- return this.request({ id: this.nextId++, type: "createDatabase", databaseName, persistent });
111
- }
112
- openDatabase(databaseName) {
113
- return this.request({ id: this.nextId++, type: "openDatabase", databaseName });
114
- }
115
- listDatabases() {
116
- return this.request({ id: this.nextId++, type: "listDatabases" });
117
- }
118
- checkpointDatabase(databaseName) {
119
- return this.request({ id: this.nextId++, type: "checkpointDatabase", databaseName });
120
- }
121
- dropDatabase(databaseName) {
122
- return this.request({ id: this.nextId++, type: "dropDatabase", databaseName });
123
- }
124
- attachDatabase(connection, databaseName, alias) {
125
- return this.request({ id: this.nextId++, type: "attachDatabase", connection, databaseName, alias });
126
- }
127
- detachDatabase(connection, alias) {
128
- return this.request({ id: this.nextId++, type: "detachDatabase", connection, alias });
129
- }
130
109
  shutdown() {
131
110
  return this.request({ id: this.nextId++, type: "shutdown" });
132
111
  }
@@ -143,14 +122,11 @@ var init_client = __esm({
143
122
  removeFile(path) {
144
123
  return this.request({ id: this.nextId++, type: "removeFile", path });
145
124
  }
146
- registerExternalFile(pathOrName, url, size) {
147
- return url === void 0 || size === void 0 ? this.request({ id: this.nextId++, type: "registerExternalFile", path: pathOrName }) : this.request({ id: this.nextId++, type: "registerExternalFile", name: pathOrName, url, size });
148
- }
149
- registerExternalDirectory(source, mode) {
150
- return typeof source === "string" ? this.request({ id: this.nextId++, type: "registerExternalDirectory", path: source, mode }) : this.request({ id: this.nextId++, type: "registerExternalDirectory", handle: source, mode });
125
+ mount(source, path, mode) {
126
+ return this.request({ id: this.nextId++, type: "mount", source, path, mode });
151
127
  }
152
- removeExternalRegistration(path) {
153
- return this.request({ id: this.nextId++, type: "removeExternalRegistration", path });
128
+ unmount(path) {
129
+ return this.request({ id: this.nextId++, type: "unmount", path });
154
130
  }
155
131
  pollQuery(query) {
156
132
  return this.request({ id: this.nextId++, type: "pollQuery", query });
@@ -252,10 +228,11 @@ function assertBrowserSupport(environment = globalThis) {
252
228
  throw new Error("HyperDB cannot initialize because this browser does not support the origin-private file system");
253
229
  }
254
230
  }
255
- function resolveBrowserWorkerUrl(workerUrl, engineUrl, wasmUrl) {
231
+ function resolveBrowserWorkerUrl(workerUrl, engineUrl, wasmUrl, storagePath) {
256
232
  const resolved = new URL(workerUrl);
257
233
  resolved.searchParams.set("engineUrl", new URL(engineUrl).href);
258
234
  if (wasmUrl != null) resolved.searchParams.set("wasmUrl", new URL(wasmUrl).href);
235
+ if (storagePath != null) resolved.searchParams.set("storagePath", storagePath);
259
236
  return resolved;
260
237
  }
261
238
  function createBrowserClient(options = {}) {
@@ -263,7 +240,7 @@ function createBrowserClient(options = {}) {
263
240
  const workerUrl = options.workerUrl ?? new URL("./browser_worker.js", import.meta.url);
264
241
  const engineUrl = options.engineUrl ?? new URL("./hyperdb-wasm.js", import.meta.url);
265
242
  return new HyperDBEngineClient(
266
- new BrowserWorker(new Worker(resolveBrowserWorkerUrl(workerUrl, engineUrl, options.wasmUrl), { type: "classic" })),
243
+ new BrowserWorker(new Worker(resolveBrowserWorkerUrl(workerUrl, engineUrl, options.wasmUrl, options.storagePath), { type: "classic" })),
267
244
  options.onSetupProgress
268
245
  );
269
246
  }
@@ -12146,6 +12123,15 @@ RecordBatchFileWriter["throughDOM"] = recordBatchWriterThroughDOMStream;
12146
12123
  RecordBatchStreamWriter["throughDOM"] = recordBatchWriterThroughDOMStream;
12147
12124
 
12148
12125
  // src/api.ts
12126
+ function validateMountPath(path) {
12127
+ if (typeof path !== "string" || !path.startsWith("/mnt/external/")) {
12128
+ throw new Error("Mount path must be an absolute path below /mnt/external");
12129
+ }
12130
+ const relativePath = path.slice("/mnt/external/".length);
12131
+ if (new TextEncoder().encode(relativePath).byteLength === 0 || new TextEncoder().encode(relativePath).byteLength > 200 || relativePath.split("/").some((component) => !component || component === "." || component === ".." || !/^[A-Za-z0-9._-]+$/.test(component))) {
12132
+ throw new Error("Mount path below /mnt/external must be 1-200 bytes of slash-separated components containing ASCII letters, digits, '.', '-', or '_'");
12133
+ }
12134
+ }
12149
12135
  var HyperDBError = class extends Error {
12150
12136
  sqlState;
12151
12137
  errorSource;
@@ -12160,7 +12146,6 @@ var HyperDBError = class extends Error {
12160
12146
  this.hint = hint;
12161
12147
  }
12162
12148
  };
12163
- var maxPersistentDatabaseNameLength = 200;
12164
12149
  function deferred() {
12165
12150
  let resolve;
12166
12151
  let reject;
@@ -12216,26 +12201,6 @@ function normalizeArrowInsertOptions(options) {
12216
12201
  if (!options.name) throw new Error("Arrow insert table name must not be empty");
12217
12202
  return { name: options.name, schema: options.schema ?? null, create: options.create ?? true };
12218
12203
  }
12219
- function validateDatabaseName(databaseName) {
12220
- if (!databaseName) throw new Error("Database name must not be empty");
12221
- if (databaseName.includes("\0")) throw new Error("Database name must not contain NUL");
12222
- }
12223
- function validatePersistentDatabaseName(databaseName) {
12224
- validateDatabaseName(databaseName);
12225
- if (databaseName.length > maxPersistentDatabaseNameLength) {
12226
- throw new Error(`Persistent database name must be at most ${maxPersistentDatabaseNameLength} characters`);
12227
- }
12228
- if (!/^[a-z0-9][a-z0-9_-]*$/.test(databaseName)) {
12229
- throw new Error("Persistent database name must start with a lowercase ASCII letter or digit and contain only lowercase ASCII letters, digits, hyphens, and underscores");
12230
- }
12231
- if (/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])$/.test(databaseName)) {
12232
- throw new Error("Persistent database name is reserved by Windows");
12233
- }
12234
- }
12235
- function validateDatabaseAlias(alias) {
12236
- if (!alias) throw new Error("Database alias must not be empty");
12237
- if (alias.includes("\0")) throw new Error("Database alias must not contain NUL");
12238
- }
12239
12204
  function materializeDictionaryColumns(table) {
12240
12205
  const dictionaryColumns = table.schema.fields.map((field, index) => DataType.isDictionary(field.type) ? index : -1).filter((index) => index !== -1);
12241
12206
  if (dictionaryColumns.length === 0) return table;
@@ -12276,37 +12241,46 @@ async function initializeHyperDB(client, settings) {
12276
12241
  if (serialized === void 0) throw new Error("HyperDB settings must be JSON-serializable");
12277
12242
  expectOk(await client.initialize(serialized), "initialize HyperDB settings");
12278
12243
  }
12279
- function readDatabasesFromResult(result) {
12280
- expectOk(result, "list databases");
12281
- const invalidResult = () => new Error("list databases returned invalid metadata");
12282
- if (result.payload.byteLength < 4) throw invalidResult();
12283
- const view = new DataView(result.payload.buffer, result.payload.byteOffset, result.payload.byteLength);
12284
- const count = view.getUint32(0, true);
12285
- let offset = 4;
12286
- const databases = [];
12287
- const decoder2 = new TextDecoder("utf-8", { fatal: true });
12288
- for (let index = 0; index < count; ++index) {
12289
- if (offset + 8 > result.payload.byteLength) throw invalidResult();
12290
- const rawStorage = view.getUint32(offset, true);
12291
- const nameLength = view.getUint32(offset + 4, true);
12292
- offset += 8;
12293
- if (nameLength > result.payload.byteLength - offset) throw invalidResult();
12294
- let name;
12295
- try {
12296
- name = decoder2.decode(result.payload.subarray(offset, offset + nameLength));
12297
- } catch {
12298
- throw invalidResult();
12299
- }
12300
- if (!name || name.includes("\0") || rawStorage !== 0 && rawStorage !== 1) throw invalidResult();
12301
- databases.push({ name, storage: rawStorage === 0 ? "transient" : "persistent" });
12302
- offset += nameLength;
12303
- }
12304
- if (offset !== result.payload.byteLength) throw invalidResult();
12305
- return databases;
12306
- }
12307
12244
  function yieldToEventLoop() {
12308
12245
  return new Promise((resolve) => setTimeout(resolve, 0));
12309
12246
  }
12247
+ var MountedResourceImpl = class {
12248
+ constructor(path, unmount, cleanup) {
12249
+ this.path = path;
12250
+ this.unmount = unmount;
12251
+ this.cleanup = cleanup;
12252
+ }
12253
+ path;
12254
+ unmount;
12255
+ cleanup;
12256
+ closed = false;
12257
+ closing;
12258
+ cleaned = false;
12259
+ async close() {
12260
+ if (this.closed) return;
12261
+ if (!this.closing) {
12262
+ this.closing = (async () => {
12263
+ try {
12264
+ await this.unmount(this);
12265
+ } catch (error) {
12266
+ this.closing = void 0;
12267
+ throw error;
12268
+ }
12269
+ this.release();
12270
+ })();
12271
+ }
12272
+ await this.closing;
12273
+ }
12274
+ releaseAfterShutdown() {
12275
+ this.release();
12276
+ }
12277
+ release() {
12278
+ this.closed = true;
12279
+ if (this.cleaned) return;
12280
+ this.cleaned = true;
12281
+ this.cleanup();
12282
+ }
12283
+ };
12310
12284
  var HyperDB = class _HyperDB {
12311
12285
  constructor(client) {
12312
12286
  this.client = client;
@@ -12316,6 +12290,7 @@ var HyperDB = class _HyperDB {
12316
12290
  termination;
12317
12291
  activeDatabaseOperations = 0;
12318
12292
  connections = /* @__PURE__ */ new Set();
12293
+ mountedResources = /* @__PURE__ */ new Set();
12319
12294
  static async create(options = {}) {
12320
12295
  const { createBrowserClient: createBrowserClient2 } = await Promise.resolve().then(() => (init_browser(), browser_exports));
12321
12296
  const client = createBrowserClient2(options);
@@ -12343,86 +12318,15 @@ var HyperDB = class _HyperDB {
12343
12318
  this.connections.add(result);
12344
12319
  return result;
12345
12320
  }
12346
- async createDatabase(databaseName, options = {}) {
12347
- validateDatabaseName(databaseName);
12348
- if (options.storage !== void 0 && options.storage !== "transient" && options.storage !== "persistent") {
12349
- throw new Error(`Unsupported database storage: ${String(options.storage)}`);
12350
- }
12351
- if (options.storage === "persistent") validatePersistentDatabaseName(databaseName);
12352
- await this.runDatabaseOperation(async () => {
12353
- expectOk(await this.client.createDatabase(databaseName, options.storage === "persistent"), "create database");
12354
- });
12355
- }
12356
- async openDatabase(databaseName) {
12357
- validatePersistentDatabaseName(databaseName);
12358
- await this.runDatabaseOperation(async () => {
12359
- expectOk(await this.client.openDatabase(databaseName), "open database");
12360
- });
12361
- }
12362
- listDatabases() {
12363
- return this.runDatabaseOperation(async () => readDatabasesFromResult(await this.client.listDatabases()));
12364
- }
12365
- async registerExternalFile(...args) {
12366
- if (args.length === 1) {
12367
- const [sourcePath] = args;
12368
- if (!sourcePath) throw new Error("External file path must not be empty");
12369
- if (sourcePath.includes("\0")) throw new Error("External file path must not contain NUL");
12370
- return this.runDatabaseOperation(async () => {
12371
- const result = await this.client.registerExternalFile(sourcePath);
12372
- expectOk(result, "register external file");
12373
- const path = new TextDecoder("utf-8", { fatal: true }).decode(result.payload);
12374
- if (!/^\/mnt\/external\/[^/]+\/[^/]+$/.test(path)) throw new Error("register external file returned an invalid path");
12375
- return path;
12376
- });
12377
- }
12378
- const [name, url, size] = args;
12379
- if (!name) throw new Error("External file name must not be empty");
12380
- if (name.includes("\0")) throw new Error("External file name must not contain NUL");
12381
- if (name === "." || name === ".." || name.includes("/") || name.includes("\\")) {
12382
- throw new Error("External file name must be a basename without path separators");
12383
- }
12384
- if (!Number.isSafeInteger(size) || size < 0) throw new Error("External file size must be a nonnegative safe integer");
12385
- let scheme;
12386
- try {
12387
- scheme = new URL(url).protocol;
12388
- } catch {
12389
- throw new Error("External file URL must use the blob scheme");
12390
- }
12391
- if (scheme !== "blob:") throw new Error("External file URL must use the blob scheme");
12321
+ mountSource(source, path, mode, cleanup = () => void 0) {
12392
12322
  return this.runDatabaseOperation(async () => {
12393
- const result = await this.client.registerExternalFile(name, url, size);
12394
- expectOk(result, "register external file");
12395
- const path = new TextDecoder("utf-8", { fatal: true }).decode(result.payload);
12396
- if (!/^\/mnt\/external\/[^/]+\/[^/]+$/.test(path)) throw new Error("register external file returned an invalid path");
12397
- return path;
12398
- });
12399
- }
12400
- async removeExternalRegistration(path) {
12401
- if (!path) throw new Error("External registration path must not be empty");
12402
- if (path.includes("\0")) throw new Error("External registration path must not contain null bytes");
12403
- await this.runDatabaseOperation(async () => {
12404
- expectOk(await this.client.removeExternalRegistration(path), "remove external registration");
12405
- });
12406
- }
12407
- registerExternalDirectorySource(source, mode) {
12408
- return this.runDatabaseOperation(async () => {
12409
- const result = await this.client.registerExternalDirectory(source, mode);
12410
- expectOk(result, "register external directory");
12411
- const path = new TextDecoder("utf-8", { fatal: true }).decode(result.payload);
12412
- if (!/^\/mnt\/external\/[^/]+\/[^/]+$/.test(path)) throw new Error("register external directory returned an invalid path");
12413
- return path;
12414
- });
12415
- }
12416
- async checkpointDatabase(databaseName) {
12417
- validateDatabaseName(databaseName);
12418
- await this.runDatabaseOperation(async () => {
12419
- expectOk(await this.client.checkpointDatabase(databaseName), "checkpoint database");
12420
- });
12421
- }
12422
- async dropDatabase(databaseName) {
12423
- validateDatabaseName(databaseName);
12424
- await this.runDatabaseOperation(async () => {
12425
- expectOk(await this.client.dropDatabase(databaseName), "drop database");
12323
+ const result = await this.client.mount(source, path, mode);
12324
+ expectOk(result, "mount");
12325
+ const mountedPath = new TextDecoder("utf-8", { fatal: true }).decode(result.payload);
12326
+ if (mountedPath !== path) throw new Error("mount returned an invalid path");
12327
+ const resource = new MountedResourceImpl(mountedPath, (mounted) => this.unmount(mounted), cleanup);
12328
+ this.mountedResources.add(resource);
12329
+ return resource;
12426
12330
  });
12427
12331
  }
12428
12332
  async terminate() {
@@ -12442,6 +12346,8 @@ var HyperDB = class _HyperDB {
12442
12346
  this.termination = void 0;
12443
12347
  throw error;
12444
12348
  }
12349
+ for (const resource of this.mountedResources) resource.releaseAfterShutdown();
12350
+ this.mountedResources.clear();
12445
12351
  try {
12446
12352
  await this.client.terminate();
12447
12353
  this.terminated = true;
@@ -12466,6 +12372,12 @@ var HyperDB = class _HyperDB {
12466
12372
  --this.activeDatabaseOperations;
12467
12373
  }
12468
12374
  }
12375
+ async unmount(resource) {
12376
+ await this.runDatabaseOperation(async () => {
12377
+ expectOk(await this.client.unmount(resource.path), "unmount");
12378
+ this.mountedResources.delete(resource);
12379
+ });
12380
+ }
12469
12381
  };
12470
12382
  var HyperDBConnection = class {
12471
12383
  constructor(client, connection, onClose = () => void 0) {
@@ -12496,25 +12408,6 @@ var HyperDBConnection = class {
12496
12408
  }
12497
12409
  await this.closing;
12498
12410
  }
12499
- async attachDatabase(databaseName, alias = databaseName) {
12500
- validateDatabaseName(databaseName);
12501
- validateDatabaseAlias(alias);
12502
- this.ensureAvailable();
12503
- try {
12504
- expectOk(await this.client.attachDatabase(this.connection, databaseName, alias), "attach database");
12505
- } finally {
12506
- this.active = false;
12507
- }
12508
- }
12509
- async detachDatabase(alias) {
12510
- validateDatabaseAlias(alias);
12511
- this.ensureAvailable();
12512
- try {
12513
- expectOk(await this.client.detachDatabase(this.connection, alias), "detach database");
12514
- } finally {
12515
- this.active = false;
12516
- }
12517
- }
12518
12411
  sendIPC(sql) {
12519
12412
  const connection = this;
12520
12413
  const queryInfo = deferred();
@@ -12709,16 +12602,35 @@ var HyperDB2 = class _HyperDB extends HyperDB {
12709
12602
  throw error;
12710
12603
  }
12711
12604
  }
12712
- async registerExternalDirectory(handle, options = {}) {
12605
+ async mount(source, options) {
12606
+ const path = options?.path;
12607
+ validateMountPath(path);
12608
+ if (typeof File !== "undefined" && source instanceof File) {
12609
+ if (options.mode !== void 0 && options.mode !== "read") throw new Error("File mounts are always read-only");
12610
+ if (!source.name) throw new Error("Mounted file name must not be empty");
12611
+ if (source.name.includes("\0")) throw new Error("Mounted file name must not contain NUL");
12612
+ if (source.name === "." || source.name === ".." || source.name.includes("/") || source.name.includes("\\")) {
12613
+ throw new Error("Mounted file name must be a basename without path separators");
12614
+ }
12615
+ if (!Number.isSafeInteger(source.size) || source.size < 0) throw new Error("Mounted file size must be a nonnegative safe integer");
12616
+ const url = URL.createObjectURL(source);
12617
+ try {
12618
+ return await this.mountSource({ kind: "browser-file", name: source.name, url, size: source.size }, path, "read", () => URL.revokeObjectURL(url));
12619
+ } catch (error) {
12620
+ URL.revokeObjectURL(url);
12621
+ throw error;
12622
+ }
12623
+ }
12624
+ const handle = source;
12713
12625
  const mode = options.mode ?? "read";
12714
- if (mode !== "read" && mode !== "readwrite") throw new Error(`Unsupported external directory mode: ${String(mode)}`);
12626
+ if (mode !== "read" && mode !== "readwrite") throw new Error(`Unsupported mount mode: ${String(mode)}`);
12715
12627
  if (!handle || handle.kind !== "directory" || typeof handle.queryPermission !== "function" || typeof handle.requestPermission !== "function") {
12716
- throw new Error("External directory handle must be a FileSystemDirectoryHandle");
12628
+ throw new Error("Mount source must be a File or FileSystemDirectoryHandle");
12717
12629
  }
12718
12630
  let permission = await handle.queryPermission({ mode });
12719
12631
  if (permission !== "granted") permission = await handle.requestPermission({ mode });
12720
- if (permission !== "granted") throw new Error(`External directory ${mode} permission was not granted`);
12721
- return this.registerExternalDirectorySource(handle, mode);
12632
+ if (permission !== "granted") throw new Error(`Directory ${mode} permission was not granted`);
12633
+ return this.mountSource({ kind: "browser-directory", handle }, path, mode);
12722
12634
  }
12723
12635
  };
12724
12636
  export {
@@ -1,4 +1,8 @@
1
1
  import type { ExternalDirectoryBackend } from './wasm_bindings.js';
2
+ export type BrowserDirectoryEntry = {
3
+ readonly name: string;
4
+ readonly kind: 'file' | 'directory';
5
+ };
2
6
  export declare class BrowserExternalDirectoryBackend implements ExternalDirectoryBackend {
3
7
  private readonly directories;
4
8
  private readonly paths;
@@ -11,6 +15,14 @@ export declare class BrowserExternalDirectoryBackend implements ExternalDirector
11
15
  registered(path: string, locator: string): void;
12
16
  removed(pathOrLocator: string): void;
13
17
  list(locator: string): Promise<readonly string[]>;
18
+ listEntries(locator: string, path: string): Promise<readonly BrowserDirectoryEntry[]>;
19
+ lookup(locator: string, path: string): Promise<{
20
+ readonly kind: 'directory';
21
+ } | {
22
+ readonly kind: 'file';
23
+ readonly locator: string;
24
+ readonly size: number;
25
+ } | undefined>;
14
26
  open(locator: string, path: string): Promise<{
15
27
  readonly locator: string;
16
28
  readonly size: number;
@@ -24,6 +36,7 @@ export declare class BrowserExternalDirectoryBackend implements ExternalDirector
24
36
  shutdown(): void;
25
37
  private getDirectory;
26
38
  private getWritableDirectory;
39
+ private openDirectory;
27
40
  private getOpenWriter;
28
41
  private cleanupWriter;
29
42
  private enqueue;