-
Notifications
You must be signed in to change notification settings - Fork 60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Missing typing for sqlite3Worker1Promiser #53
Comments
Hey there! Firstly, thanks for shipping this package in the first place! :) I understand that having this typed as "any" is far from ideal, and even saw the ongoing PR to add the type definitions to it. Not an easy job! However, it would make a potential developer less confused when using the package with Typescript if the type was shipped by default instead of having to declare it manually. Especially since the recommended approach uses "sqlite3Worker1Promiser". Maybe we could add a JSDoc noting that the type is under development and keep it as something along the lines of: (...args: unknown[]) => Promise<unknown> I know there's nothing more permanent than a temporary solution, so I understand if we don't follow with this approach too 😅 |
@filipe-freire I'm not opposed, but am unsure where you'd add this, the caveat from #53 (comment) still applies 🫣. To the thin wrapper code part of this repo (if so, please file a quick PR), or the underlying SQLite code (if so, that's something to raise with @sgbeal)? |
In the upstream project we use only vanilla JS and avoid all tooling-specific extensions (because none of us use them, so we can't be relied upon to maintain such pieces properly long-term). |
Never mind my comment above, after analyzing the PR more thoroughly the work there is already way beyond what I suggested. Apologies for that 😅 The work would be done in this wrapper though. I unfortunately don't have the insight needed to complete that PR. However, an option is to merge it as it stands right now, which would still provide a better DX to someone just starting out. The only thing needed would be to change the Docs to reflect the types added: import {
sqlite3Worker1Promiser,
type Promiser,
type PromiserResponseSuccess
} from '@sqlite.org/sqlite-wasm';
const log = console.log;
const error = console.error;
const initializeSQLite = async () => {
try {
log('Loading and initializing SQLite3 module...');
const promiser = (await new Promise((resolve) => {
const _promiser = sqlite3Worker1Promiser({
onready: () => resolve(_promiser)
});
})) satisfies Promiser;
log('Done initializing. Running demo...');
const configResponse = (await promiser(
'config-get', {}
)) as PromiserResponseSuccess<'config-get'>;
log('Running SQLite3 version', configResponse.result.version.libVersion);
const openResponse = (await promiser('open', {
filename: 'file:mydb.sqlite3?vfs=opfs'
})) as PromiserResponseSuccess<'open' >;
const {
dbId
} = openResponse;
log(
'OPFS is available, created persisted database at',
openResponse.result.filename.replace(/^file:(.*?)\?vfs=opfs$/, '$1')
);
// Your SQLite code here.
} catch (err) {
if (!(err instanceof Error)) {
err = new Error(err.result.message);
}
error(err.name, err.message);
}
};
initializeSQLite(); Type casting is not an ideal solution, yet it might be a good enough compromise while this work is ongoing. I'll defer the decision to you! ✌🏻 |
I don't know how merge-ready #54 is. Do people here generally approve of a not-perfect-but-good-enough merger? I think for the documentation, we should keep it vanilla. Those wanting to use the types will know how to include them, whereas people unfamiliar with TypeScript might be wondering what the to them weird |
How about simply adding a Typescript version for its users out there? Having them figure it out by themselves is not the best solution here imo... As for the approval of "not-perfect-but-good-enough" PR's, I'll defer that to y'all 😁 |
Any update on this? I cannot follow the recommended "wrapped worker" approach in my Vite React + TypeScript project due to |
You can inspire yourself from the pending PR (#54) and create a declare module "@sqlite.org/sqlite-wasm" {
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
type TODO = any;
/**
* A function to be called when the SQLite3 module and worker APIs are done
* loading asynchronously. This is the only way of knowing that the loading
* has completed.
*
* @since V3.46: Is passed the function which gets returned by
* `sqlite3Worker1Promiser()`, as accessing it from this callback is more
* convenient for certain usage patterns. The promiser v2 interface obviates
* the need for this callback.
*/
type OnreadyFunction = () => void;
type Sqlite3Worker1PromiserConfig = {
onready?: OnreadyFunction;
/**
* A worker instance which loads `sqlite3-worker1.js`, or a functional
* equivalent. Note that the promiser factory replaces the
* `worker.onmessage` property. This config option may alternately be a
* function, in which case this function is called to instantiate the
* worker.
*/
worker?: Worker | (() => Worker);
/** Function to generate unique message IDs */
generateMessageId?: (messageObject: TODO) => string;
/**
* A `console.debug()` style function for logging information about Worker
* messages.
*/
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
debug?: (...args: any[]) => void;
/**
* A callback function that is called when a `message` event is received
* from the worker, and the event is not handled by the proxy.
*
* @note This *should* ideally never happen, as the proxy aims to handle
* all known message types.
*/
onunhandled?: (event: MessageEvent) => void;
};
/**
* A db identifier string (returned by 'open') which tells the operation which
* database instance to work on. If not provided, the first-opened db is
* used.
*
* @warning This is an "opaque" value, with no inherently useful syntax
* or information. Its value is subject to change with any given build
* of this API and cannot be used as a basis for anything useful beyond
* its one intended purpose.
*/
type DbId = string | undefined;
type Sqlite3Version = {
libVersion: string;
sourceId: string;
libVersionNumber: number;
downloadVersion: number;
};
// Message types and their corresponding arguments and results. Should be able to get better types for some of these (open, exec and stack) from the existing types, although the Promiser verions have minor differences
type PromiserMethods = {
/** @link https://sqlite.org/wasm/doc/trunk/api-worker1.md#method-open */
open: {
args: Partial<
{
/**
* The db filename. [=":memory:" or "" (unspecified)]: TODO: See the
* sqlite3.oo1.DB constructor for peculiarities and transformations
*/
filename?: string;
} & {
/**
* Sqlite3_vfs name. Ignored if filename is ":memory:" or "". This may
* change how the given filename is resolved. The VFS may optionally
* be provided via a URL-style filename argument: filename:
* "file:foo.db?vfs=...". By default it uses a transient database,
* created anew on each request.
*
* If both this argument and a URI-style argument are provided, which
* one has precedence is unspecified.
*/
vfs?: string;
}
>;
result: {
dbId: DbId;
/** Db filename, possibly differing from the input */
filename: string;
/**
* Indicates if the given filename resides in the known-persistent
* storage
*/
persistent: boolean;
/** Name of the underlying VFS */
vfs: "string";
};
/** @link https://sqlite.org/wasm/doc/trunk/api-worker1.md#method-close */
};
close: {
args: { dbId?: DbId };
result: {
/** Filename of closed db, or undefined if no db was closed */
filename: string | undefined;
};
/** @link https://sqlite.org/wasm/doc/trunk/api-worker1.md#method-config-get */
};
"config-get": {
args: unknown;
result: {
dbID: DbId;
version: Sqlite3Version;
/** Indicates if BigInt support is enabled */
bigIntEnabled: boolean;
/** Indicates if opfs support is enabled */
opfsEnabled: boolean; //not documented on sqlie.org?
/** Result of sqlite3.capi.sqlite3_js_vfs_list() */
vfsList: string[]; // is there a full list somewhere I can use?
};
};
/**
* Interface for running arbitrary SQL. Wraps`oo1.DB.exec()` methods. And
* supports most of its features as defined in
* https://sqlite.org/wasm/doc/trunk/api-oo1.md#db-exec. There are a few
* limitations imposed by the state having to cross thread boundaries.
*
* @link https://sqlite.org/wasm/doc/trunk/api-worker1.md#method-exec
*/
exec: {
args: {
sql: string;
dbId?: DbId;
/**
* At the end of the result set, the same event is fired with
* (row=undefined, rowNumber=null) to indicate that the end of the
* result set has been reached. Note that the rows arrive via
* worker-posted messages, with all the implications of that.
*/
callback?: (result: {
/**
* Internally-synthesized message type string used temporarily for
* worker message dispatching.
*/
type: string;
/** Sqlilte3 VALUE */
row: TODO;
/** 1-based index */
rowNumber: number;
columnNames: string[];
}) => void;
/**
* A single value valid as an argument for Stmt.bind(). This is only
* applied to the first non-empty statement in the SQL which has any
* bindable parameters. (Empty statements are skipped entirely.)
*/
bind?: Exclude<TODO, null>;
[key: string]: TODO; //
};
result: { [key: string]: TODO };
};
};
type PromiserResponseSuccess<T extends keyof PromiserMethods> = {
/** Type of the inbound message */
type: T;
/** Operation dependent result */
result: PromiserMethods[T]["result"];
/** Same value, if any, provided by the inbound message */
messageId: string;
/**
* The id of the db which was operated on, if any, as returned by the
* corresponding 'open' operation.
*/
dbId: DbId;
// possibly other metadata ...
/*
WorkerReceivedTime: number
WorkerRespondTime: number
departureTime: number
*/
};
type PromiserResponseError = {
type: "error";
/** Operation independent object */
result: {
/** Type of the triggereing operation */
operation: string;
/** Error Message */
message: string;
/** The ErrorClass.name property from the thrown exception */
errorClass: string;
/** The message object which triggered the error */
input: object;
/** _if available_ a stack trace array */
stack: TODO[];
};
/** Same value, if any, provided by the inbound message */
messageId: string;
dbId: DbId;
};
type PromiserResponse<T extends keyof PromiserMethods> =
| PromiserResponseSuccess<T>
| PromiserResponseError;
type Promiser = {
<T extends keyof PromiserMethods>(
/** The type of the message */
messageType: T,
/** The arguments for the message type */
messageArguments: PromiserMethods[T]["args"],
): Promise<PromiserResponse<T>>;
<T extends keyof PromiserMethods>(message: {
/** The type of the message */
type: T;
/** The arguments for the message type */
args: PromiserMethods[T]["args"];
}): Promise<PromiserResponse<T>>;
};
/** Factory for creating promiser instances. */
const sqlite3Worker1Promiser: {
/**
* Promiser v1
*
* @example
* const factory = sqlite3Worker1Promiser({
* onready: () => {
* promiser('open', { filename: 'my_database.sqlite' })
* .then((msg) => {
* // ...
* })
* .catch((e) => {
* console.error(e);
* });
* },
* });
*
* @link https://sqlite.org/wasm/doc/trunk/api-worker1.md#promiser
*/
(config?: Sqlite3Worker1PromiserConfig | OnreadyFunction): Promiser;
/**
* Promiser v2
*
* @since 3.46:
* @example
* const factoryPromise = sqlite3Worker1Promiser.v2(config);
* const factory = await factoryPromise;
*
* @link https://sqlite.org/wasm/doc/trunk/api-worker1.md#promiser.v2
*/
v2: (
config?: Sqlite3Worker1PromiserConfig | OnreadyFunction,
) => Promise<Promiser>;
defaultConfig: Sqlite3Worker1PromiserConfig;
};
} |
In order to follow the recommended "wrapped worker" approach from the readme I had to add:
immediately below the import statement, because
sqlite3Worker1Promiser
is not included in theindex.d.ts
The text was updated successfully, but these errors were encountered: