hyperdb-wasm 0.0.26862-dev.wasm.g89537caa → 0.0.27000-dev.wasm.g0bde3882

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,7 +2,7 @@ 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>;
@@ -13,10 +13,8 @@ export interface HyperDBClient {
13
13
  insertArrowIPCFromPath(connection: number, path: string, name: string, schema: string | null, create: boolean, internal?: boolean): Promise<WasmResult>;
14
14
  createTemporaryFile(bytes: Uint8Array): Promise<WasmResult>;
15
15
  removeFile(path: string): Promise<WasmResult>;
16
- registerExternalFile(path: string): Promise<WasmResult>;
17
- registerExternalFile(name: string, url: string, size: number): Promise<WasmResult>;
18
- registerExternalDirectory(handle: unknown, mode: 'read' | 'readwrite'): Promise<WasmResult>;
19
- removeExternalRegistration(path: string): Promise<WasmResult>;
16
+ mount(source: MountSource, path: string, mode: MountAccessMode): Promise<WasmResult>;
17
+ unmount(path: string): Promise<WasmResult>;
20
18
  pollQuery(query: number): Promise<WasmResult>;
21
19
  cancelQuery(query: number): Promise<WasmResult>;
22
20
  finishQuery(query: number): Promise<WasmResult>;
@@ -28,18 +26,16 @@ export interface ArrowInsertOptions {
28
26
  readonly schema?: string;
29
27
  readonly create?: boolean;
30
28
  }
31
- export interface ExternalFileRegistration {
32
- readonly name: string;
33
- readonly url: string;
34
- readonly size: number;
29
+ export type MountAccessMode = 'read' | 'readwrite';
30
+ export interface MountOptions {
31
+ readonly path: string;
32
+ readonly mode?: MountAccessMode;
35
33
  }
36
- export type ExternalDirectoryMode = 'read' | 'readwrite';
37
- export interface ExternalDirectoryRegistrationOptions {
38
- readonly mode?: ExternalDirectoryMode;
34
+ export interface MountedResource {
35
+ readonly path: string;
36
+ close(): Promise<void>;
39
37
  }
40
- type BrowserExternalFileArguments = readonly [name: string, url: string, size: number];
41
- type NodeExternalFileArguments = readonly [path: string];
42
- export type ExternalFileArguments = BrowserExternalFileArguments | NodeExternalFileArguments;
38
+ export declare function validateMountPath(path: unknown): asserts path is string;
43
39
  export type HyperDBSettingValue = string | number | boolean | null | readonly HyperDBSettingValue[] | {
44
40
  readonly [key: string]: HyperDBSettingValue;
45
41
  };
@@ -88,22 +84,22 @@ export declare class HyperDBError extends Error {
88
84
  constructor(message: string, sqlState: string, errorSource: HyperDBErrorSource, detail?: string, hint?: string);
89
85
  }
90
86
  declare function initializeHyperDB(client: HyperDBClient, settings: HyperDBSettings | undefined): Promise<void>;
91
- export declare class HyperDB<ExternalFileArgs extends ExternalFileArguments = BrowserExternalFileArguments> {
87
+ export declare class HyperDB {
92
88
  protected readonly client: HyperDBClient;
93
89
  private terminated;
94
90
  private termination;
95
91
  private activeDatabaseOperations;
96
92
  private readonly connections;
93
+ private readonly mountedResources;
97
94
  constructor(client: HyperDBClient);
98
95
  static create(options?: BrowserCreateOptions): Promise<HyperDB>;
99
96
  ready(): Promise<void>;
100
97
  connect(): Promise<HyperDBConnection>;
101
- registerExternalFile(...args: ExternalFileArgs): Promise<string>;
102
- removeExternalRegistration(path: string): Promise<void>;
103
- protected registerExternalDirectorySource(source: unknown, mode: ExternalDirectoryMode): Promise<string>;
98
+ protected mountSource(source: MountSource, path: string, mode: MountAccessMode, cleanup?: () => void): Promise<MountedResource>;
104
99
  terminate(): Promise<void>;
105
100
  private ensureRunning;
106
101
  protected runDatabaseOperation<Result>(operation: () => Promise<Result>): Promise<Result>;
102
+ private unmount;
107
103
  }
108
104
  export declare class HyperDBConnection {
109
105
  private readonly client;
package/dist/browser.js CHANGED
@@ -122,14 +122,11 @@ var init_client = __esm({
122
122
  removeFile(path) {
123
123
  return this.request({ id: this.nextId++, type: "removeFile", path });
124
124
  }
125
- registerExternalFile(pathOrName, url, size) {
126
- 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 });
125
+ mount(source, path, mode) {
126
+ return this.request({ id: this.nextId++, type: "mount", source, path, mode });
127
127
  }
128
- registerExternalDirectory(source, mode) {
129
- return typeof source === "string" ? this.request({ id: this.nextId++, type: "registerExternalDirectory", path: source, mode }) : this.request({ id: this.nextId++, type: "registerExternalDirectory", handle: source, mode });
130
- }
131
- removeExternalRegistration(path) {
132
- return this.request({ id: this.nextId++, type: "removeExternalRegistration", path });
128
+ unmount(path) {
129
+ return this.request({ id: this.nextId++, type: "unmount", path });
133
130
  }
134
131
  pollQuery(query) {
135
132
  return this.request({ id: this.nextId++, type: "pollQuery", query });
@@ -12126,6 +12123,15 @@ RecordBatchFileWriter["throughDOM"] = recordBatchWriterThroughDOMStream;
12126
12123
  RecordBatchStreamWriter["throughDOM"] = recordBatchWriterThroughDOMStream;
12127
12124
 
12128
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
+ }
12129
12135
  var HyperDBError = class extends Error {
12130
12136
  sqlState;
12131
12137
  errorSource;
@@ -12238,6 +12244,43 @@ async function initializeHyperDB(client, settings) {
12238
12244
  function yieldToEventLoop() {
12239
12245
  return new Promise((resolve) => setTimeout(resolve, 0));
12240
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
+ };
12241
12284
  var HyperDB = class _HyperDB {
12242
12285
  constructor(client) {
12243
12286
  this.client = client;
@@ -12247,6 +12290,7 @@ var HyperDB = class _HyperDB {
12247
12290
  termination;
12248
12291
  activeDatabaseOperations = 0;
12249
12292
  connections = /* @__PURE__ */ new Set();
12293
+ mountedResources = /* @__PURE__ */ new Set();
12250
12294
  static async create(options = {}) {
12251
12295
  const { createBrowserClient: createBrowserClient2 } = await Promise.resolve().then(() => (init_browser(), browser_exports));
12252
12296
  const client = createBrowserClient2(options);
@@ -12274,55 +12318,15 @@ var HyperDB = class _HyperDB {
12274
12318
  this.connections.add(result);
12275
12319
  return result;
12276
12320
  }
12277
- async registerExternalFile(...args) {
12278
- if (args.length === 1) {
12279
- const [sourcePath] = args;
12280
- if (!sourcePath) throw new Error("External file path must not be empty");
12281
- if (sourcePath.includes("\0")) throw new Error("External file path must not contain NUL");
12282
- return this.runDatabaseOperation(async () => {
12283
- const result = await this.client.registerExternalFile(sourcePath);
12284
- expectOk(result, "register external file");
12285
- const path = new TextDecoder("utf-8", { fatal: true }).decode(result.payload);
12286
- if (!/^\/mnt\/external\/[^/]+\/[^/]+$/.test(path)) throw new Error("register external file returned an invalid path");
12287
- return path;
12288
- });
12289
- }
12290
- const [name, url, size] = args;
12291
- if (!name) throw new Error("External file name must not be empty");
12292
- if (name.includes("\0")) throw new Error("External file name must not contain NUL");
12293
- if (name === "." || name === ".." || name.includes("/") || name.includes("\\")) {
12294
- throw new Error("External file name must be a basename without path separators");
12295
- }
12296
- if (!Number.isSafeInteger(size) || size < 0) throw new Error("External file size must be a nonnegative safe integer");
12297
- let scheme;
12298
- try {
12299
- scheme = new URL(url).protocol;
12300
- } catch {
12301
- throw new Error("External file URL must use the blob scheme");
12302
- }
12303
- if (scheme !== "blob:") throw new Error("External file URL must use the blob scheme");
12321
+ mountSource(source, path, mode, cleanup = () => void 0) {
12304
12322
  return this.runDatabaseOperation(async () => {
12305
- const result = await this.client.registerExternalFile(name, url, size);
12306
- expectOk(result, "register external file");
12307
- const path = new TextDecoder("utf-8", { fatal: true }).decode(result.payload);
12308
- if (!/^\/mnt\/external\/[^/]+\/[^/]+$/.test(path)) throw new Error("register external file returned an invalid path");
12309
- return path;
12310
- });
12311
- }
12312
- async removeExternalRegistration(path) {
12313
- if (!path) throw new Error("External registration path must not be empty");
12314
- if (path.includes("\0")) throw new Error("External registration path must not contain null bytes");
12315
- await this.runDatabaseOperation(async () => {
12316
- expectOk(await this.client.removeExternalRegistration(path), "remove external registration");
12317
- });
12318
- }
12319
- registerExternalDirectorySource(source, mode) {
12320
- return this.runDatabaseOperation(async () => {
12321
- const result = await this.client.registerExternalDirectory(source, mode);
12322
- expectOk(result, "register external directory");
12323
- const path = new TextDecoder("utf-8", { fatal: true }).decode(result.payload);
12324
- if (!/^\/mnt\/external\/[^/]+\/[^/]+$/.test(path)) throw new Error("register external directory returned an invalid path");
12325
- return path;
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;
12326
12330
  });
12327
12331
  }
12328
12332
  async terminate() {
@@ -12342,6 +12346,8 @@ var HyperDB = class _HyperDB {
12342
12346
  this.termination = void 0;
12343
12347
  throw error;
12344
12348
  }
12349
+ for (const resource of this.mountedResources) resource.releaseAfterShutdown();
12350
+ this.mountedResources.clear();
12345
12351
  try {
12346
12352
  await this.client.terminate();
12347
12353
  this.terminated = true;
@@ -12366,6 +12372,12 @@ var HyperDB = class _HyperDB {
12366
12372
  --this.activeDatabaseOperations;
12367
12373
  }
12368
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
+ }
12369
12381
  };
12370
12382
  var HyperDBConnection = class {
12371
12383
  constructor(client, connection, onClose = () => void 0) {
@@ -12590,16 +12602,35 @@ var HyperDB2 = class _HyperDB extends HyperDB {
12590
12602
  throw error;
12591
12603
  }
12592
12604
  }
12593
- 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;
12594
12625
  const mode = options.mode ?? "read";
12595
- 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)}`);
12596
12627
  if (!handle || handle.kind !== "directory" || typeof handle.queryPermission !== "function" || typeof handle.requestPermission !== "function") {
12597
- throw new Error("External directory handle must be a FileSystemDirectoryHandle");
12628
+ throw new Error("Mount source must be a File or FileSystemDirectoryHandle");
12598
12629
  }
12599
12630
  let permission = await handle.queryPermission({ mode });
12600
12631
  if (permission !== "granted") permission = await handle.requestPermission({ mode });
12601
- if (permission !== "granted") throw new Error(`External directory ${mode} permission was not granted`);
12602
- 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);
12603
12634
  }
12604
12635
  };
12605
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;