diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index da41006..4f13949 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -88,11 +88,11 @@ functionality. (These steps can only be executed by maintainers.) -1. Manually trigger the [GitHub Actions workflow](../../actions/workflows/build-wasm.yml). By - default, it uses the latest SQLite tag. This pull request will contain the latest `sqlite3.wasm` - and related bindings. - -2. Once the above pull request is validated and merged, update the version number in `package.json`, - reflecting the current [SQLite version number](https://sqlite.org/download.html) and add a build - identifier suffix like `-build1`. The complete version number should read something like - `3.41.2-build1`. +1. Run the [Build SQLite Wasm workflow](../../actions/workflows/build-wasm.yml) with a full SQLite + commit SHA as `sqlite_ref`. Review the generated PR and sync types with the + [upstream API docs](https://sqlite.org/wasm/doc/trunk/api-index.md). + +2. Merge the PR and update `package.json` and `package-lock.json` to the SQLite version plus a build + suffix, e.g. `3.55.0-build1`. + +3. Run the [Publish package workflow](../../actions/workflows/publish.yml) from the default branch. diff --git a/package.json b/package.json index 5d68d9d..be825d6 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,12 @@ "main": "./dist/index.mjs", "node": "./dist/node.mjs", "types": "./dist/index.d.mts", + "sideEffects": [ + "./dist/index.mjs", + "./dist/node.mjs", + "./dist/sqlite3-worker1.mjs", + "./dist/sqlite3-opfs-async-proxy.js" + ], "exports": { ".": { "types": "./dist/index.d.mts", diff --git a/scripts/build-sqlite3-wasm.sh b/scripts/build-sqlite3-wasm.sh index 3bdb457..fb387fb 100644 --- a/scripts/build-sqlite3-wasm.sh +++ b/scripts/build-sqlite3-wasm.sh @@ -6,7 +6,6 @@ SQLITE_REF="${SQLITE_REF:-master}" SRC_DIR="${WORKDIR:-/build/sqlite-src}" HOST_UID="${HOST_UID:-}" HOST_GID="${HOST_GID:-}" -WASM_OPT_WRAPPER="" EXTRACT_DIR="" log() { printf '\033[1;34m[build]\033[0m %s\n' "$*"; } @@ -23,7 +22,6 @@ mkdir -p /src/bin cleanup() { local status=$? trap - EXIT - if [ -n "$WASM_OPT_WRAPPER" ]; then rm -f "$WASM_OPT_WRAPPER"; fi if [ -n "$EXTRACT_DIR" ]; then rm -rf "$EXTRACT_DIR"; fi if [ -n "$HOST_UID" ] && [ -n "$HOST_GID" ]; then log "Fixing ownership for /src/bin to $HOST_UID:$HOST_GID" @@ -60,14 +58,6 @@ log "make sqlite3.c" make sqlite3.c cd ext/wasm -# SQLite invokes wasm-opt with --all-features. Newer Binaryen versions then -# emit compact imports, which are not supported by our browser/Node targets. -# Append the compatibility flag after SQLite's flags, preserving its passes. -WASM_OPT_WRAPPER="$(mktemp /tmp/sqlite-wasm-opt.XXXXXX)" -printf '#!/usr/bin/env bash\nexec %q "$@" --disable-compact-imports\n' \ - "$(command -v wasm-opt)" > "$WASM_OPT_WRAPPER" -chmod +x "$WASM_OPT_WRAPPER" - # Check the generated defaults before overriding the per-loader flags. Refuse # to silently discard additional flags introduced by an upstream SQLite update. make .wasmbuilds.make WASM_CUSTOM_INSTANTIATE=0 @@ -86,8 +76,7 @@ log "Running make -j4 npm with Cross-Origin Storage for browser loaders (excludi # SQLite's custom instantiateWasm bypasses Emscripten's COS loading path. make -j4 npm WASM_CUSTOM_INSTANTIATE=0 \ 'emcc.flags.bundler=-sCROSS_ORIGIN_STORAGE' \ - 'emcc.flags.esm=-sCROSS_ORIGIN_STORAGE' \ - "bin.wasm-opt=$WASM_OPT_WRAPPER" + 'emcc.flags.esm=-sCROSS_ORIGIN_STORAGE' # Extract to staging and verify before replacing the mounted output. log "Extracting npm-bundle.zip" diff --git a/src/index.d.ts b/src/index.d.ts index f2b03ec..6117cbb 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -226,6 +226,10 @@ export class PreparedStatement { * non-0 because this function is effectively a destructor and "destructors do * not throw." This function will throw if it is called while the statement is * in active use via a {@link Database#exec} callback. + * + * Since SQLite 3.55.0, native disposal via `capi.sqlite3_finalize()` also + * disconnects this OO1 object and shared wrappers. That low-level call + * bypasses this method's statement-locking checks. */ finalize(): number | undefined; @@ -1045,6 +1049,10 @@ export class Database { * Note that garbage collection of a db handle, if it happens at all, will * never trigger `close()`, so {@link onclose} handlers are not a reliable way * to implement close-time cleanup or maintenance of a db. + * + * Since SQLite 3.55.0, native disposal via `capi.sqlite3_close_v2()` also + * disconnects this OO1 object and shared wrappers, without calling `onclose` + * handlers. Calling `close()` after native disposal is a no-op. */ close(): void; // ✓ @@ -3017,33 +3025,31 @@ export type WASM_API = { peek64f: (addr: WasmPointer) => Sqlite3Result; /** - * Requires `n` to be one of: + * Requires n to be one of: * - * - Integer 8, 16, or 32. - * - A integer-type TypedArray constructor: `Int8Array`, `Int16Array`, - * `Int32Array`, or their `Uint` counterparts. + * - integer 8, 16, or 32. + * - A integer-type TypedArray constructor: Int8Array, Int16Array, + * Int32Array, or their Uint counterparts. * - * If `BigInt` support is enabled, it also accepts the value 64 or a - * `BigInt64Array`/`BigUint64Array`, else it throws if passed 64 or one of - * those constructors. + * If this.bigIntEnabled is true, it also accepts the value 64 or a + * BigInt64Array/BigUint64Array, else it throws if passed 64 or one + * of those constructors. * - * Returns an integer-based `TypedArray` view of the WASM heap memory buffer - * associated with the given block size. If passed an integer as the first - * argument and unsigned is truthy then the `"U"` (unsigned) variant of that - * view is returned, else the signed variant is returned. If passed a - * `TypedArray` value, the 2nd argument is ignored. Note that `Float32Array` - * and `Float64Array` views are not supported by this function. + * Returns an integer-based TypedArray view of the WASM heap memory + * buffer associated with the given block size. If passed an integer + * as the first argument and unsigned is truthy then the "U" + * (unsigned) variant of that view is returned, else the signed + * variant is returned. If passed a TypedArray value, the 2nd + * argument is ignored. * - * Be aware that growth of the heap may invalidate any references to this - * heap, so do not hold a reference longer than needed and do not use a - * reference after any operation which may allocate. Instead, re-fetch the - * reference by calling this function again, which automatically refreshes the - * view if needed. + * Growth of the heap will invalidate any references to this heap, + * so do not hold a reference longer than needed and do not use a + * reference after any operation which may allocate. Instead, + * re-fetch the reference by calling this function again. * - * Throws if passed an invalid `n`. + * Throws if passed an invalid n. * - * Use of this function in client code is very rare. In practice, one of the - * (faster) convenience forms is used. + * @see https://sqlite.org/wasm/doc/trunk/api-wasm.md */ heapForSize(n: 8, unsigned: false | undefined): Int8Array; heapForSize(n: 8, unsigned: true): Uint8Array; @@ -3059,8 +3065,10 @@ export type WASM_API = { heapForSize(n: typeof Uint32Array): Uint32Array; heapForSize(n: 64, unsigned: false | undefined): BigInt64Array; heapForSize(n: 64, unsigned: true): BigUint64Array; - heapForSize(n: typeof Int32Array): BigInt64Array; - heapForSize(n: typeof Uint32Array): BigUint64Array; + heapForSize(n: typeof BigInt64Array): BigInt64Array; + heapForSize(n: typeof BigUint64Array): BigUint64Array; + heapForSize(n: typeof Float32Array): Float32Array; + heapForSize(n: typeof Float64Array): Float64Array; /** Faster convenience method to get a Int8Array view of the WASM heap. */ heap8: () => Int8Array; @@ -3086,6 +3094,18 @@ export type WASM_API = { /** Faster convenience method to get a BigUint64Array view of the WASM heap. */ heap64u: () => BigUint64Array; + /** + * Convenience (and faster) equivalent of + * this.heapForSize(Float32Array). + */ + heap32f: () => Float32Array; + + /** + * Convenience (and faster) equivalent of + * this.heapForSize(Float64Array). + */ + heap64f: () => Float64Array; + /** * Fetches the `heapForSize()` for the given representation then writes the * given numeric value to it. Only numbers may be written this way, and @@ -3153,11 +3173,14 @@ export type WASM_API = { cstrToJs: (ptr: WasmPointer) => string | null; /** - * Expects its argument to be a pointer into the WASM heap memory which refers - * to a NUL-terminated C-style string encoded as UTF-8. Returns the length, in - * bytes, of the string, as for `strlen(3)`. As a special case, if the - * argument is falsy, then it returns null. Throws if the argument is out of - * range for `wasm.heap8u()`. + * Expects ptr to be a pointer into the WASM heap memory which + * refers to a NUL-terminated C-style string encoded as UTF-8. + * Returns the length, in bytes, of the string, as for `strlen(3)`. + * As a special case, if !ptr or if it's not a pointer then it + * returns `null`. Results are undefined if no NUL is found. Throws + * if ptr is out of range for target.heap8u(). + * + * @see https://sqlite.org/wasm/doc/trunk/api-wasm.md */ cstrlen: (ptr: WasmPointer) => number | null; @@ -3643,17 +3666,25 @@ export type WASM_API = { */ /** - * Returns true if its value is a WASM pointer type. That is, it's a a 32-bit - * integer greater than or equal to zero. + * isPtr() is an alias for isPtr32() or isPtr64(), depending on the + * value of target.ptr.size. * - * Sidebar: `isPtr()` is an alias for `isPtr32()`. If/when 64-bit WASM pointer - * support becomes widespread, it will become an alias for either `isPtr32()` - * or the as-yet-hypothetical `isPtr64()`, depending on a configuration - * option. + * @see https://sqlite.org/wasm/doc/trunk/api-wasm.md */ isPtr: (v: unknown) => v is WasmPointer; - /** See {@link WASM_API#isPtr} */ + /** + * Returns true if the given value appears to be legal for use as a + * WASM pointer value. Its _range_ of values is not (cannot be) + * validated except to ensure that it is a 32-bit integer with a + * value of 0 or greater. Likewise, it cannot verify whether the + * value actually refers to allocated memory in the WASM heap. + * + * Whether or not null or undefined are legal are context-dependent. + * They generally are legal but this function does not treat them as + * such because they're not strictly legal for passing as-is as WASM + * integer arguments. + */ isPtr32: (v: unknown) => v is WasmPointer; /** Size of a WASM pointer in bytes. */ @@ -4478,7 +4509,16 @@ export type CAPI = { /** SQLITE_LIMIT_SCHEMA: maximum number of schema objects (since 3.54). */ | 13 /** SQLITE_LIMIT_TRIGGER_STEPS: maximum statements per trigger (since 3.54). */ - | 14, + | 14 + /** + * The maximum number of common table expressions (CTEs) allowed + * in a single SQL statements. This limit is used to limit possible + * exponential growth in the number of required cursors required for + * nested joins of CTEs. The limit is an absolute upper bound, and + * SQLite may refuse to compile a statement if the number of CTEs simply + * approaches this limit. Set this limit to 0 to disable the restriction. + */ + | 15, newVal: number, ) => number; @@ -4658,7 +4698,7 @@ export type CAPI = { sqlite3_bind_blob: ( stmt: StmtPtr, idx: number, - blob: WasmPointer | string | readonly string[] | Int8Array | Uint8Array | ArrayBuffer, + blob: WasmPointer | null | string | readonly string[] | Int8Array | Uint8Array | ArrayBuffer, n: number, dtor: DtorType, ) => Sqlite3Result; @@ -4734,7 +4774,7 @@ export type CAPI = { sqlite3_bind_text: ( stmt: StmtPtr, idx: number, - text: string | WasmPointer | readonly string[] | Int8Array | Uint8Array | ArrayBuffer, + text: string | WasmPointer | null | readonly string[] | Int8Array | Uint8Array | ArrayBuffer, n: number, dtor: DtorType, ) => Sqlite3Result; @@ -5435,7 +5475,7 @@ export type CAPI = { ctx: WasmPointer, n: number, pAux: WasmPointer, - xDelete: (() => void) | WasmPointer, + xDelete: WasmPointer, ) => void; /** @@ -8014,6 +8054,118 @@ export type CAPI = { */ sqlite3_js_db_uses_vfs: (db: DbPtr, vfsName: string, dbName: string) => boolean; + /** + * Sets up the given callback to be called when the given WASM + * pointer is finalized by the library. The purposes of this is to + * provide client code with a way to map JS state to native + * pointers in such a way that the state can be cleaned up + * regardless of how the natives are finalized. + * + * typeName describes what data type ptr is. It must be one of: + * + * "sqlite3*", "sqlite3_stmt*" + * + * and the ptr argument must be a non-null pointer to that type. + * + * The callback must be either a Function or a WASM function + * pointer[^1] to a function with the signature `void(*)(void*)`, + * a.k.a. "v(p)" in the library's function-binding parlance. + * + * It is legal to call this multiple times for a given combination + * of pointer and callback. Each callback will be added to the + * queue only once for each distinct callback reference, with the + * caveat noted below regarding callback references. That is, + * calling it N times with the same ptr/callback combination will + * only install the callback once, but N calls with N distinct + * callbacks will install all N. + * + * When used in loops or client functions which may see the same + * pointer multiple times, it is generally important to **not pass + * function literals**, as each such literal is a different + * instance, so an identical callback may be added N times for a + * given pointer. Functions should be assigned to symbols and + * those symbols should be passed in so that callback + * deduplication works: + * + * ``` + * // NO: + * sqlite3_js_on_pointer_dispose(..., (x)=>...); + * // YES: + * const myFunc = (x)=>...; + * ... + * sqlite3_js_on_pointer_dispose(..., myFunc); + * // Also YES: + * let myFunc; + * ... + * sqlite3_js_on_pointer_dispose(..., (myFunc ??= (x)=>...)); + * ``` + * + * For single-call cases, e.g. when passing a newly-initialized db + * or statement, a function literal is perfectly fine. Within + * loops and client-defined functions which are called more than + * once per native instance, function literals will cause grief in + * the form of potentially registering a separate callback for + * each invocation of the SQL function. + * + * On success it returns an unspecified truthy value. On error + * (argument validation issues) it returns an unspecified falsy + * value and has no side-effects. It does not throw because it's + * intended to be usable from cases where throwing is not + * permitted, but it only fails for argument validation. + * + * When SQLite is about to free that pointer, all callbacks + * queued for it will be passed that pointer and then the + * callback queue for that pointer is cleared. + * + * Because the callback is called during the native-level + * finalization process, it must observe certain limitations: + * + * - It absolutely must not finalize the object, or results are + * well and truly undefined. Best case, a WASM crash. + * + * - Because multiple on-dispose listeners may be installed for + * any given pointer, the callback must not leave the object in an + * unusable state. + * + * - It should not perform "real work" with the pointer. e.g. a + * finalizing statement must not be stepped nor its columns + * fetched. A finalizing db should probably not run new + * queries. + * + * - Any exceptions are ignored but may trigger an alert via + * sqlite3.config.error(). + * + * Ideally, such callbacks do nothing more than remove any state + * which they have mapped to the given pointer value, without + * actually trying to use the object referred to by that pointer. + * Sometimes, however, cleanup work may be necessary which + * involves judicious use of that type's methods. + * + * Added in 3.55.0. + * + * Design note: ideally we could support `sqlite3_context*`, as + * that would neatly tie in cleanup for user-defined SQL + * functions. However, the C APIs do not guaranty stability of + * those pointers across calls. It currently provides + * _unguaranteed_ stability in two cases but does not for any + * aggregate-specific cases, making it unsuitable for the problem + * this API addresses (pun not intended). + * + * [^1]: Though callback may be a WASM function pointer, that is + * actually less efficient than passing a JS function. The + * on-dispose dispatching is called from native code, invoking a + * JS function which the library connects to it during + * bootstrapping. sqlite3_js_on_pointer_dispose() receives the + * notifications "second hand" through that mechanism. If + * callback is a WASM function, invoking it adds one more step + * from JS-space into C-space and back. + */ + sqlite3_js_on_pointer_dispose: ( + typeName: 'sqlite3*' | 'sqlite3_stmt*', + ptr: WasmPointer, + callback: ((ptr: WasmPointer) => void) | WasmPointer, + ) => boolean | null | undefined; + /** * Calls a synchronous callback up to maxTimes total attempts. Retries when * it returns SQLITE_BUSY or throws SQLite3Error with that resultCode. diff --git a/tsdown.config.ts b/tsdown.config.ts index 8c3e8b9..7c49d9e 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -8,6 +8,7 @@ const tsdownConfig: UserConfig[] = [ index: 'src/browser.ts', node: 'src/node.ts', 'sqlite3-worker1': 'src/bin/sqlite3-worker1.mjs', + 'sqlite3-worker1-promiser': 'src/bin/sqlite3-worker1-promiser.mjs', }, format: ['esm'], dts: false,