Skip to content

Commit

Permalink
More extension api
Browse files Browse the repository at this point in the history
  • Loading branch information
samwillis committed Jun 18, 2024
1 parent acb9a6b commit 0f5ae1a
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 11 deletions.
27 changes: 20 additions & 7 deletions packages/pglite/src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,19 @@ export interface ExecProtocolOptions {
syncToFs?: boolean;
}

export interface Extension<T = any> {
name: string;
setup: (mod: any, pg: PGliteInterface) => T;
init: () => void;
export interface ExtensionSetupResult {
emscriptenOpts?: any;
namespaceObj?: any;
init?: () => Promise<void>;
close?: () => Promise<void>;
}

export interface Extension {
name?: string;
setup: (
pg: PGliteInterface,
emscriptenOpts: any
) => Promise<ExtensionSetupResult>;
}

export type Extensions = {
Expand Down Expand Up @@ -62,9 +71,13 @@ export type PGliteInterface = {

export type PGliteInterfaceExtensions<E> = E extends Extensions
? {
[K in keyof E]: ReturnType<E[K]["setup"]> extends undefined | null | void
? never
: ReturnType<E[K]["setup"]>;
[K in keyof E]: Awaited<
ReturnType<E[K]["setup"]>
>["namespaceObj"] extends infer N
? N extends undefined | null | void
? never
: N
: never;
}
: {};

Expand Down
45 changes: 41 additions & 4 deletions packages/pglite/src/pglite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import type {
QueryOptions,
ExecProtocolOptions,
PGliteInterfaceExtensions,
Extensions,
Extension,
} from "./interface.js";

// Importing the source as the built version is not ESM compatible
Expand All @@ -30,13 +32,15 @@ export class PGlite implements PGliteInterface {
fs?: Filesystem;
protected emp?: any;

#extensions: Extensions;
#initStarted = false;
#ready = false;
#eventTarget: EventTarget;
#closing = false;
#closed = false;
#inTransaction = false;
#relaxedDurability = false;
#extensionsClose: Array<() => Promise<void>> = [];

#resultAccumulator: Uint8Array[] = [];

Expand Down Expand Up @@ -98,6 +102,9 @@ export class PGlite implements PGliteInterface {
this.#resultAccumulator.push(e.detail);
});

// Save the extensions for later use
this.#extensions = options.extensions ?? {};

// Initialize the database, and store the promise so we can wait for it to be ready
this.waitReady = this.#init(options ?? {});
}
Expand All @@ -114,6 +121,7 @@ export class PGlite implements PGliteInterface {
this.fs = await loadFs(dataDir, fsType);
}

const extensionInitFns: Array<() => Promise<void>> = [];
let firstRun = false;
await new Promise<void>(async (resolve, reject) => {
if (this.#initStarted) {
Expand Down Expand Up @@ -157,6 +165,23 @@ export class PGlite implements PGliteInterface {
Event: PGEvent,
};

// Setup extensions
for (const [extName, ext] of Object.entries(this.#extensions)) {
const extRet = await ext.setup(this, emscriptenOpts);
if (extRet.emscriptenOpts) {
emscriptenOpts = extRet.emscriptenOpts;
}
if (extRet.namespaceObj) {
(this as any)[extName] = extRet.namespaceObj;
}
if (extRet.init) {
extensionInitFns.push(extRet.init);
}
if (extRet.close) {
this.#extensionsClose.push(extRet.close);
}
}

emscriptenOpts = await this.fs!.emscriptenOpts(emscriptenOpts);
const emp = await EmPostgresFactory(emscriptenOpts);
this.emp = emp;
Expand All @@ -168,6 +193,11 @@ export class PGlite implements PGliteInterface {
await this.#runExec(`
SET search_path TO public;
`);

// Init extensions
for (const initFn of extensionInitFns) {
await initFn();
}
}

/**
Expand Down Expand Up @@ -220,6 +250,13 @@ export class PGlite implements PGliteInterface {
async close() {
await this.#checkReady();
this.#closing = true;

// Close all extensions
for (const closeFn of this.#extensionsClose) {
await closeFn();
}

// Close the database
await new Promise<void>(async (resolve, reject) => {
try {
await this.execProtocol(serialize.end());
Expand Down Expand Up @@ -519,18 +556,18 @@ export class PGlite implements PGliteInterface {
}

/**
* Create a new PGlite instance with extensions on the interface
* (The main constructor does enable extensions, but does not add them to the interface)
* Create a new PGlite instance with extensions on the Typescript interface
* (The main constructor does enable extensions, however due to the limitations
* of Typescript, the extensions are not available on the instance interface)
* @param dataDir The directory to store the database files
* Prefix with idb:// to use indexeddb filesystem in the browser
* Use memory:// to use in-memory filesystem
* @param options Optional options
* @returns A new PGlite instance with extensions
*/
static withExtensions<O extends PGliteOptions>(
dataDir?: string,
options?: O
): PGlite & PGliteInterfaceExtensions<O["extensions"]> {
return new PGlite(dataDir, options) as any;
return new PGlite(options) as any;
}
}

0 comments on commit 0f5ae1a

Please sign in to comment.