/*
  2022-05-22

  The author disclaims copyright to this source code.  In place of a
  legal notice, here is a blessing:

  *   May you do good and not evil.
  *   May you find forgiveness for yourself and forgive others.
  *   May you share freely, never taking more than you give.

  ***********************************************************************

  This file is intended to be combined at build-time with other
  related code, most notably a header and footer which wraps this
  whole file into a single callback which can be run after Emscripten
  loads the corresponding WASM module. The sqlite3 JS API has no hard
  requirements on Emscripten and does not expose any Emscripten APIs
  to clients. It is structured such that its build can be tweaked to
  include it in arbitrary WASM environments which can supply the
  necessary underlying features (e.g. a POSIX file I/O layer).

  Main project home page: https://sqlite.org

  Documentation home page: https://sqlite.org/wasm
*/

/**
   sqlite3ApiBootstrap() is the only global symbol persistently
   exposed by this API. It is intended to be called one time at the
   end of the API amalgamation process and passed configuration details
   for the current environment.

   This function is not intended for client-level use. It is intended
   for use in creating bundles configured for specific WASM
   environments. That said, the "sqlite3-api.js" intermediary build
   file aims to be suitable for dropping in to custom builds, and it
   exposes only this function.

   This function expects a configuration object, intended to abstract
   away details specific to any given WASM environment, primarily so
   that it can be used without any direct dependency on
   Emscripten. (Note the default values for the config object!) The
   config object is only honored the first time this is
   called. Subsequent calls ignore the argument and return the same
   (configured) object which gets initialized by the first call.  This
   function will throw if any of the required config options are
   missing.

   The config object properties include:

   - `exports`[^1]: the "exports" object for the current WASM
     environment. In an Emscripten-based build, this should be set to
     `Module['asm']` (versions <=3.1.43) or `Module['wasmExports']`
     (versions >=3.1.44).

   - `memory`[^1]: optional WebAssembly.Memory object, defaulting to
     `exports.memory`. In Emscripten environments this should be set
     to `Module.wasmMemory` if the build uses `-sIMPORTED_MEMORY`, or be
     left undefined/falsy to default to `exports.memory` when using
     WASM-exported memory.

   - `functionTable`[^1]: optional WebAssembly.Table object holding
     the indirect function table. If not set then the table is assumed
     to be in `exports.__indirect_function_table`.

   - `bigIntEnabled`: true if BigInt support is enabled. Defaults to
     true if `globalThis.BigInt64Array` is available, else false. Some APIs
     will throw exceptions if called without BigInt support, as BigInt
     is required for marshalling C-side int64 into and out of JS.
     (Sidebar: it is technically possible to add int64 support via
     marshalling of int32 pairs, but doing so is unduly invasive.)

   - `allocExportName`: the name of the function, in `exports`, of the
     `malloc(3)`-compatible routine for the WASM environment. Defaults
     to `"sqlite3_malloc"`. Beware that using any allocator other than
     sqlite3_malloc() may require care in certain client-side code
     regarding which allocator is uses. Notably, sqlite3_deserialize()
     and sqlite3_serialize() can only safely use memory from different
     allocators under very specific conditions. The canonical builds
     of this API guaranty that `sqlite3_malloc()` is the JS-side
     allocator implementation.

   - `deallocExportName`: the name of the function, in `exports`, of
     the `free(3)`-compatible routine for the WASM
     environment. Defaults to `"sqlite3_free"`.

   - `reallocExportName`: the name of the function, in `exports`, of
     the `realloc(3)`-compatible routine for the WASM
     environment. Defaults to `"sqlite3_realloc"`.

   - `debug`, `log`, `warn`, and `error` may be functions equivalent
     to the like-named methods of the global `console` object. By
     default, these map directly to their `console` counterparts, but
     can be replaced with (e.g.) empty functions to squelch all such
     output.

   - `wasmfsOpfsDir`[^1]: Specifies the "mount point" of the
     OPFS-backed filesystem in WASMFS-capable builds. This is only
     used in WASMFS-capable builds of the library (which the canonical
     builds do not include).

     - `disable` (as of 3.53.0) may be an object with the following
     properties:
       - `vfs`, an object, may contain a map of VFS names to booleans.
       Any mapping to falsy are disabled. The supported names
       are: "kvvfs", "opfs", "opfs-sahpool", "opfs-wl".
       - Other disabling options may be added in the future.

   [^1] = This property may optionally be a function, in which case
          this function calls that function to fetch the value,
          enabling delayed evaluation.

   The returned object is the top-level sqlite3 namespace object.


   Client code may optionally assign sqlite3ApiBootstrap.defaultConfig
   an object-type value before calling sqlite3ApiBootstrap() (without
   arguments) in order to tell that call to use this object as its
   default config value. The intention of this is to provide
   downstream clients with a reasonably flexible approach for plugging
   in an environment-suitable configuration without having to define a
   new global-scope symbol.

   However, because clients who access this library via an
   Emscripten-hosted module will not have an opportunity to call
   sqlite3ApiBootstrap() themselves, nor to access it before it is
   called, an alternative option for setting the configuration is to
   define globalThis.sqlite3ApiConfig to an object. If it is set, it
   is used instead of sqlite3ApiBootstrap.defaultConfig if
   sqlite3ApiBootstrap() is called without arguments. Setting the
   `exports` and `memory` parts require already having loaded the WASM
   module, though.

   Both sqlite3ApiBootstrap.defaultConfig and
   globalThis.sqlite3ApiConfig get deleted by sqlite3ApiBootstrap()
   because any changes to them made after that point would have no
   useful effect. This function also deletes itself from globalThis
   when it's called.

   This function returns a Promise to the sqlite3 namespace object,
   which resolves after the async pieces of the library init are
   complete.
*/
'use strict';
globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
  apiConfig = (globalThis.sqlite3ApiConfig || sqlite3ApiBootstrap.defaultConfig)
){
  if(sqlite3ApiBootstrap.sqlite3){ /* already initialized */
    (sqlite3ApiBootstrap.sqlite3.config || console).warn(
      "sqlite3ApiBootstrap() called multiple times.",
      "Config and external initializers are ignored on calls after the first."
    );
    return sqlite3ApiBootstrap.sqlite3;
  }
  const nu = (...obj)=>Object.assign(Object.create(null),...obj);
  const config = nu({
    exports: undefined,
    memory: undefined,
    bigIntEnabled: !!globalThis.BigInt64Array,
    debug: console.debug.bind(console),
    warn: console.warn.bind(console),
    error: console.error.bind(console),
    log: console.log.bind(console),
    wasmfsOpfsDir: '/opfs',
    /**
       useStdAlloc is just for testing allocator discrepancies. The
       docs guarantee that this is false in the canonical builds. For
       99% of purposes it doesn't matter which allocators we use, but
       it becomes significant with, e.g., sqlite3_deserialize() and
       certain wasm.xWrap.resultAdapter()s.
    */
    useStdAlloc: false
  }, apiConfig);

  Object.assign(config, {
    allocExportName: config.useStdAlloc ? 'malloc' : 'sqlite3_malloc',
    deallocExportName: config.useStdAlloc ? 'free' : 'sqlite3_free',
    reallocExportName: config.useStdAlloc ? 'realloc' : 'sqlite3_realloc'
  });

  [
    // If any of these config options are functions, replace them with
    // the result of calling that function. They must not be async.
    'exports', 'memory', 'functionTable', 'wasmfsOpfsDir'
  ].forEach((k)=>{
    if('function' === typeof config[k]){
      config[k] = config[k]();
    }
  });

  /**
      The main sqlite3 binding API gets installed into this object,
      mimicking the C API as closely as we can. The numerous members
      names with prefixes 'sqlite3_' and 'SQLITE_' behave, insofar as
      possible, identically to the C-native counterparts, as documented at:

      https://sqlite.org/c3ref/intro.html

      A very few exceptions require an additional level of proxy
      function or may otherwise require special attention in the WASM
      environment, and all such cases are documented somewhere below
      in this file or in sqlite3-api-glue.js. capi members which are
      not documented are installed as 1-to-1 proxies for their
      C-side counterparts.
  */
  const capi = nu();
  /**
     Holds state which are specific to the WASM-related
     infrastructure and glue code.

     Note that a number of members of this object are injected
     dynamically after the api object is fully constructed, so
     not all are documented in this file.
  */
  const wasm = nu();

  /** Internal helper for SQLite3Error ctor. */
  const __rcStr = (rc)=>{
    return (capi.sqlite3_js_rc_str && capi.sqlite3_js_rc_str(rc))
           || ("Unknown result code #"+rc);
  };

  /** Internal helper for SQLite3Error ctor. */
  const isInt32 = (n)=>
        'number'===typeof n
        && n===(n | 0)
        && n<=2147483647 && n>=-2147483648;

  /**
     An Error subclass specifically for reporting DB-level errors and
     enabling clients to unambiguously identify such exceptions.
     The C-level APIs never throw, but some of the higher-level
     C-style APIs do and the object-oriented APIs use exceptions
     exclusively to report errors.
  */
  class SQLite3Error extends Error {
    /**
       Constructs this object with a message depending on its arguments:

       If its first argument is an integer, it is assumed to be
       an SQLITE_... result code and it is passed to
       sqlite3.capi.sqlite3_js_rc_str() to stringify it.

       If called with exactly 2 arguments and the 2nd is an object,
       that object is treated as the 2nd argument to the parent
       constructor.

       The exception's message is created by concatenating its
       arguments with a space between each, except for the
       two-args-with-an-object form and that the first argument will
       get coerced to a string, as described above, if it's an
       integer.

       If passed an integer first argument, the error object's
       `resultCode` member will be set to the given integer value,
       else it will be set to capi.SQLITE_ERROR.
    */
    constructor(...args){
      let rc;
      if(args.length){
        if(isInt32(args[0])){
          rc = args[0];
          if(1===args.length){
            super(__rcStr(args[0]));
          }else{
            const rcStr = __rcStr(rc);
            if('object'===typeof args[1]){
              super(rcStr,args[1]);
            }else{
              args[0] = rcStr+':';
              super(args.join(' '));
            }
          }
        }else{
          if(2===args.length && 'object'===typeof args[1]){
            super(...args);
          }else{
            super(args.join(' '));
          }
        }
      }
      this.resultCode = rc || capi.SQLITE_ERROR;
      this.name = 'SQLite3Error';
    }
  };

  /**
     Functionally equivalent to the SQLite3Error constructor but may
     be used as part of an expression, e.g.:

     ```
     return someFunction(x) || SQLite3Error.toss(...);
     ```
  */
  SQLite3Error.toss = (...args)=>{
    throw new SQLite3Error(...args);
  };
  const toss3 = SQLite3Error.toss;

  if(config.wasmfsOpfsDir && !/^\/[^/]+$/.test(config.wasmfsOpfsDir)){
    toss3("config.wasmfsOpfsDir must be falsy or in the form '/dir-name'.");
  }

  /**
     Returns true if the given BigInt value is small enough to fit
     into an int64 value, else false.
  */
  const bigIntFits64 = function f(b){
    if(!f._max){
      f._max = BigInt("0x7fffffffffffffff");
      f._min = ~f._max;
    }
    return b >= f._min && b <= f._max;
  };

  /**
     Returns true if the given BigInt value is small enough to fit
     into an int32, else false.
  */
  const bigIntFits32 = (b)=>(b >= (-0x7fffffffn - 1n) && b <= 0x7fffffffn);

  /**
     Returns true if the given BigInt value is small enough to fit
     into a double value without loss of precision, else false.
  */
  const bigIntFitsDouble = function f(b){
    if(!f._min){
      f._min = Number.MIN_SAFE_INTEGER;
      f._max = Number.MAX_SAFE_INTEGER;
    }
    return b >= f._min && b <= f._max;
  };

  /** Returns v if v appears to be a TypedArray, else false. */
  const isTypedArray = (v)=>{
    return (v && v.constructor && isInt32(v.constructor.BYTES_PER_ELEMENT)) ? v : false;
  };

  /**
     Returns true if v appears to be one of our bind()-able TypedArray
     types: Uint8Array or Int8Array or ArrayBuffer. Support for
     TypedArrays with element sizes >1 is a potential TODO just
     waiting on a use case to justify them. Until then, their `buffer`
     property can be used to pass them as an ArrayBuffer. If it's not
     a bindable array type, a falsy value is returned.
  */
  const isBindableTypedArray = (v)=>
        v && (v instanceof Uint8Array
              || v instanceof Int8Array
              || v instanceof ArrayBuffer);

  /**
     Returns true if v appears to be one of the TypedArray types
     which is legal for holding SQL code (as opposed to binary blobs).

     Currently this is the same as isBindableTypedArray() but it
     seems likely that we'll eventually want to add Uint32Array
     and friends to the isBindableTypedArray() list but not to the
     isSQLableTypedArray() list.
  */
  const isSQLableTypedArray = (v)=>
        v && (v instanceof Uint8Array
              || v instanceof Int8Array
              || v instanceof ArrayBuffer);

  /** Returns true if isBindableTypedArray(v) does, else throws with a message
      that v is not a supported TypedArray value. */
  const affirmBindableTypedArray = (v)=>
        isBindableTypedArray(v)
        || toss3("Value is not of a supported TypedArray type.");

  /**
     If v is-a Array, its join("") result is returned.  If
     isSQLableTypedArray(v) is true then wasm.typedArrayToString(v) is
     returned. If it looks like a WASM pointer, wasm.cstrToJs(v) is
     returned. Else v is returned as-is.

     Reminder to self: the "return as-is" instead of returning ''+v is
     arguably a design mistake but changing it is risky at this point.
  */
  const flexibleString = function(v){
    if(isSQLableTypedArray(v)){
      return wasm.typedArrayToString(
        (v instanceof ArrayBuffer) ? new Uint8Array(v) : v,
        0, v.length
      );
    }
    else if(Array.isArray(v)) return v.join("");
    else if(wasm.isPtr(v)) v = wasm.cstrToJs(v);
    return v;
  };

  /**
     An Error subclass specifically for reporting Wasm-level malloc()
     failure and enabling clients to unambiguously identify such
     exceptions.
  */
  class WasmAllocError extends Error {
    /**
       If called with 2 arguments and the 2nd one is an object, it
       behaves like the Error constructor, else it concatenates all
       arguments together with a single space between each to
       construct an error message string. As a special case, if
       called with no arguments then it uses a default error
       message.
    */
    constructor(...args){
      if(2===args.length && 'object'===typeof args[1]){
        super(...args);
      }else if(args.length){
        super(args.join(' '));
      }else{
        super("Allocation failed.");
      }
      this.resultCode = capi.SQLITE_NOMEM;
      this.name = 'WasmAllocError';
    }
  };
  /**
     Functionally equivalent to the WasmAllocError constructor but may
     be used as part of an expression, e.g.:

     ```
     return someAllocatingFunction(x) || WasmAllocError.toss(...);
     ```
  */
  WasmAllocError.toss = (...args)=>{
    throw new WasmAllocError(...args);
  };

  Object.assign(capi, {
    /**
       sqlite3_bind_blob() works exactly like its C counterpart unless
       its 3rd argument is one of:

       - JS string: the 3rd argument is converted to a C string, the
         4th argument is ignored, and the C-string's length is used
         in its place.

       - Array: converted to a string as defined for "flexible
         strings" and then it's treated as a JS string.

       - Int8Array or Uint8Array: wasm.allocFromTypedArray() is used to
         conver the memory to the WASM heap. If the 4th argument is
         0 or greater, it is used as-is, otherwise the array's byteLength
         value is used. This is an exception to the C API's undefined
         behavior for a negative 4th argument, but results are undefined
         if the given 4th argument value is greater than the byteLength
         of the input array.

       - If it's an ArrayBuffer, it gets wrapped in a Uint8Array and
         treated as that type.

       In all of those cases, the final argument (destructor) is
       ignored and capi.SQLITE_WASM_DEALLOC is assumed.

       A 3rd argument of `null` is treated as if it were a WASM pointer
       of 0.

       If the 3rd argument is neither a WASM pointer nor one of the
       above-described types, capi.SQLITE_MISUSE is returned.

       The first argument may be either an `sqlite3_stmt*` WASM
       pointer or an sqlite3.oo1.Stmt instance.

       For consistency with the C API, it requires the same number of
       arguments. It returns capi.SQLITE_MISUSE if passed any other
       argument count.
    */
    sqlite3_bind_blob: undefined/*installed later*/,

    /**
       sqlite3_bind_text() works exactly like its C counterpart unless
       its 3rd argument is one of:

       - JS string: the 3rd argument is converted to a C string, the
         4th argument is ignored, and the C-string's length is used
         in its place.

       - Array: converted to a string as defined for "flexible
         strings". The 4th argument is ignored and a value of -1
         is assumed.

       - Int8Array or Uint8Array: is assumed to contain UTF-8 text, is
         converted to a string. The 4th argument is ignored, replaced
         by the array's byteLength value.

       - If it's an ArrayBuffer, it gets wrapped in a Uint8Array and
         treated as that type.

       In each of those cases, the final argument (text destructor) is
       ignored and capi.SQLITE_WASM_DEALLOC is assumed.

       A 3rd argument of `null` is treated as if it were a WASM pointer
       of 0.

       If the 3rd argument is neither a WASM pointer nor one of the
       above-described types, capi.SQLITE_MISUSE is returned.

       The first argument may be either an `sqlite3_stmt*` WASM
       pointer or an sqlite3.oo1.Stmt instance.

       For consistency with the C API, it requires the same number of
       arguments. It returns capi.SQLITE_MISUSE if passed any other
       argument count.

       If client code needs to bind partial strings, it needs to
       either parcel the string up before passing it in here or it
       must pass in a WASM pointer for the 3rd argument and a valid
       4th-argument value, taking care not to pass a value which
       truncates a multi-byte UTF-8 character. When passing
       WASM-format strings, it is important that the final argument be
       valid or unexpected content can result, or WASM may crash if
       the application reads past the WASM heap bounds.
    */
    sqlite3_bind_text: undefined/*installed later*/,

    /**
       sqlite3_create_function_v2() differs from its native
       counterpart only in the following ways:

       1) The fourth argument (`eTextRep`) argument must not specify
       any encoding other than sqlite3.SQLITE_UTF8. The JS API does not
       currently support any other encoding and likely never
       will. This function does not replace that argument on its own
       because it may contain other flags. As a special case, if
       the bottom 4 bits of that argument are 0, SQLITE_UTF8 is
       assumed.

       2) Any of the four final arguments may be either WASM pointers
       (assumed to be function pointers) or JS Functions. In the
       latter case, each gets bound to WASM using
       sqlite3.capi.wasm.installFunction() and that wrapper is passed
       on to the native implementation.

       For consistency with the C API, it requires the same number of
       arguments. It returns capi.SQLITE_MISUSE if passed any other
       argument count.

       The semantics of JS functions are:

       xFunc: is passed `(pCtx, ...values)`. Its return value becomes
       the new SQL function's result.

       xStep: is passed `(pCtx, ...values)`. Its return value is
       ignored.

       xFinal: is passed `(pCtx)`. Its return value becomes the new
       aggregate SQL function's result.

       xDestroy: is passed `(void*)`. Its return value is ignored. The
       pointer passed to it is the one from the 5th argument to
       sqlite3_create_function_v2().

       Note that:

       - `pCtx` in the above descriptions is a `sqlite3_context*`. At
         least 99 times out of a hundred, that initial argument will
         be irrelevant for JS UDF bindings, but it needs to be there
         so that the cases where it _is_ relevant, in particular with
         window and aggregate functions, have full access to the
         lower-level sqlite3 APIs.

       - When wrapping JS functions, the remaining arguments are passd
         to them as positional arguments, not as an array of
         arguments, because that allows callback definitions to be
         more JS-idiomatic than C-like. For example `(pCtx,a,b)=>a+b`
         is more intuitive and legible than
         `(pCtx,args)=>args[0]+args[1]`. For cases where an array of
         arguments would be more convenient, the callbacks simply need
         to be declared like `(pCtx,...args)=>{...}`, in which case
         `args` will be an array.

       - If a JS wrapper throws, it gets translated to
         sqlite3_result_error() or sqlite3_result_error_nomem(),
         depending on whether the exception is an
         sqlite3.WasmAllocError object or not.

       - When passing on WASM function pointers, arguments are _not_
         converted or reformulated. They are passed on as-is in raw
         pointer form using their native C signatures. Only JS
         functions passed in to this routine, and thus wrapped by this
         routine, get automatic conversions of arguments and result
         values. The routines which perform those conversions are
         exposed for client-side use as sqlite3_values_to_js(),
         sqlite3_result_js(), and sqlite3_result_error_js().

       For xFunc(), xStep(), and xFinal():

       - When called from SQL, arguments to the UDF, and its result,
         will be converted between JS and SQL with as much fidelity as
         is feasible, triggering an exception if a type conversion
         cannot be determined. Some freedom is afforded to numeric
         conversions due to friction between the JS and C worlds:
         integers which are larger than 32 bits may be treated as
         doubles or BigInts.

       If any JS-side bound functions throw, those exceptions are
       intercepted and converted to database-side errors with the
       exception of xDestroy(): any exception from it is ignored,
       possibly generating a console.error() message.  Destructors
       must not throw.

       Automatically-converted JS-to-WASM functions will be cleaned up
       either when (A) this function is called again with the same
       name, arity, and encoding, but null/0 values for the functions,
       or (B) when pDb is passed to sqlite3_close_v2(). If this factor
       is relevant for a given client, they can create WASM-bound JS
       functions themselves, hold on to their pointers, and pass the
       pointers in to here. Later on, they can free those pointers
       (using `wasm.uninstallFunction()` or equivalent).

       C reference: https://sqlite.org/c3ref/create_function.html

       Maintenance reminder: the ability to add new
       WASM-accessible functions to the runtime requires that the
       WASM build is compiled with emcc's `-sALLOW_TABLE_GROWTH`
       flag.
    */
    sqlite3_create_function_v2: (
      pDb, funcName, nArg, eTextRep, pApp,
      xFunc, xStep, xFinal, xDestroy
    )=>{/*installed later*/},
    /**
       Equivalent to passing the same arguments to
       sqlite3_create_function_v2(), with 0 as the final argument.
    */
    sqlite3_create_function: (
      pDb, funcName, nArg, eTextRep, pApp,
      xFunc, xStep, xFinal
    )=>{/*installed later*/},
    /**
       The sqlite3_create_window_function() JS wrapper differs from
       its native implementation in the exact same way that
       sqlite3_create_function_v2() does. The additional function,
       xInverse(), is treated identically to xStep() by the wrapping
       layer.
    */
    sqlite3_create_window_function: (
      pDb, funcName, nArg, eTextRep, pApp,
      xStep, xFinal, xValue, xInverse, xDestroy
    )=>{/*installed later*/},
    /**
       The sqlite3_prepare_v3() binding handles two different uses
       with differing JS/WASM semantics:

       1) sqlite3_prepare_v3(pDb, sqlString, -1, prepFlags, ppStmt , null)

       2) sqlite3_prepare_v3(pDb, sqlPointer, sqlByteLen, prepFlags, ppStmt, sqlPointerToPointer)

       The SQL length argument (the 3rd argument) must, for usage (1),
       always be negative because it must be a byte length and that
       value is expensive to calculate from JS (where only the
       character length of strings is readily available). It is
       retained in this API's interface for code/documentation
       compatibility reasons but is currently _always_ ignored. With
       usage (2), the 3rd argument is used as-is but is is still
       critical that the C-style input string (2nd argument) be
       terminated with a 0 byte.

       In usage (1), the 2nd argument must be of type string,
       Uint8Array, Int8Array, or ArrayBuffer (all of which are assumed
       to hold SQL). If it is, this function assumes case (1) and
       calls the underyling C function with the equivalent of:

       (pDb, sqlAsString, -1, prepFlags, ppStmt, null)

       The `pzTail` argument is ignored in this case because its
       result is meaningless when a string-type value is passed
       through: the string goes through another level of internal
       conversion for WASM's sake and the result pointer would refer
       to that transient conversion's memory, not the passed-in
       string.

       If the sql argument is not a string, it must be a _pointer_ to
       a NUL-terminated string which was allocated in the WASM memory
       (e.g. using capi.wasm.alloc() or equivalent). In that case,
       the final argument may be 0/null/undefined or must be a pointer
       to which the "tail" of the compiled SQL is written, as
       documented for the C-side sqlite3_prepare_v3().

       In case (2), the underlying C function is called with the
       equivalent of:

       (pDb, sqlAsPointer, sqlByteLen, prepFlags, ppStmt, pzTail)

       It returns its result and compiled statement as documented in
       the C API. Fetching the output pointers (5th and 6th
       parameters) requires using `capi.wasm.peek()` (or
       equivalent) and the `pzTail` will point to an address relative to
       the `sqlAsPointer` value.

       If passed an invalid 2nd argument type, this function will
       return SQLITE_MISUSE and sqlite3_errmsg() will contain a string
       describing the problem.

       Side-note: if given an empty string, or one which contains only
       comments or an empty SQL expression, 0 is returned but the result
       output pointer will be NULL.
    */
    sqlite3_prepare_v3: (dbPtr, sql, sqlByteLen, prepFlags,
                         stmtPtrPtr, strPtrPtr)=>{}/*installed later*/,

    /**
       Equivalent to calling sqlite3_prapare_v3() with 0 as its 4th argument.
    */
    sqlite3_prepare_v2: (dbPtr, sql, sqlByteLen,
                         stmtPtrPtr,strPtrPtr)=>{}/*installed later*/,

    /**
       This binding enables the callback argument to be a JavaScript.

       If the callback is a function, then for the duration of the
       sqlite3_exec() call, it installs a WASM-bound function which
       acts as a proxy for the given callback. That proxy will also
       perform a conversion of the callback's arguments from
       `(char**)` to JS arrays of strings. However, for API
       consistency's sake it will still honor the C-level callback
       parameter order and will call it like:

       `callback(pVoid, colCount, listOfValues, listOfColNames)`

       If the callback is not a JS function then this binding performs
       no translation of the callback, but the sql argument is still
       converted to a WASM string for the call using the
       "string:flexible" argument converter.
    */
    sqlite3_exec: (pDb, sql, callback, pVoid, pErrMsg)=>{}/*installed later*/,

    /**
       If passed a single argument which appears to be a byte-oriented
       TypedArray (Int8Array or Uint8Array), this function treats that
       TypedArray as an output target, fetches `theArray.byteLength`
       bytes of randomness, and populates the whole array with it. As
       a special case, if the array's length is 0, this function
       behaves as if it were passed (0,0). When called this way, it
       returns its argument, else it returns the `undefined` value.

       If called with any other arguments, they are passed on as-is
       to the C API. Results are undefined if passed any incompatible
       values.
     */
    sqlite3_randomness: (n, outPtr)=>{/*installed later*/},
  }/*capi*/);

  /**
     Various internal-use utilities are added here as needed. They
     are bound to an object only so that we have access to them in
     the differently-scoped steps of the API bootstrapping
     process. At the end of the API setup process, this object gets
     removed. These are NOT part of the public API.
  */
  const util = {
    affirmBindableTypedArray, flexibleString,
    bigIntFits32, bigIntFits64, bigIntFitsDouble,
    isBindableTypedArray,
    isInt32, isSQLableTypedArray, isTypedArray,
    isUIThread: ()=>(globalThis.window===globalThis && !!globalThis.document),
    // is this true for ESM?: 'undefined'===typeof WorkerGlobalScope
    toss: function(...args){throw new Error(args.join(' '))},
    toss3,
    typedArrayPart: wasm.typedArrayPart,
    nu,
    assert: function(arg,msg){
      if( !arg ){
        util.toss("Assertion failed:",msg);
      }
    },
    /**
       Given a byte array or ArrayBuffer, this function throws if the
       lead bytes of that buffer do not hold a SQLite3 database header,
       else it returns without side effects.

       Added in 3.44.
    */
    affirmDbHeader: function(bytes){
      if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes);
      const header = "SQLite format 3";
      if( header.length > bytes.byteLength ){
        toss3("Input does not contain an SQLite3 database header.");
      }
      for(let i = 0; i < header.length; ++i){
        if( header.charCodeAt(i) !== bytes[i] ){
          toss3("Input does not contain an SQLite3 database header.");
        }
      }
    },
    /**
       Given a byte array or ArrayBuffer, this function throws if the
       database does not, at a cursory glance, appear to be an SQLite3
       database. It only examines the size and header, but further
       checks may be added in the future.

       Added in 3.44.
    */
    affirmIsDb: function(bytes){
      if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes);
      const n = bytes.byteLength;
      if(n<512 || n%512!==0) {
        toss3("Byte array size",n,"is invalid for an SQLite3 db.");
      }
      util.affirmDbHeader(bytes);
    }
  }/*util*/;

  /**
     wasm.X properties which are used for configuring the wasm
     environment via whwashutil.js. This object gets fleshed out with
     a number of WASM-specific utilities, in sqlite3-api-glue.c-pp.js.
  */
  Object.assign(wasm, {

    /**
       The symbols exported by the WASM environment.
    */
    exports: config.exports
      || toss3("Missing API config.exports (WASM module exports)."),

    /**
       When Emscripten compiles with `-sIMPORTED_MEMORY`, it
       initializes the heap and imports it into wasm, as opposed to
       the other way around. In this case, the memory is not available
       via this.exports.memory so the client must pass it in via
       config.memory.
    */
    memory: config.memory
      || config.exports['memory']
      || toss3("API config object requires a WebAssembly.Memory object",
              "in either config.exports.memory (exported)",
              "or config.memory (imported)."),

    /**
       The WASM pointer size. If set then it MUST be one of 4 or 8 and
       it MUST correspond to the WASM environment's pointer size. We
       figure out the size by calling some un-JS-wrapped WASM function
       which returns a pointer-type value. If that value is a BigInt,
       it's 64-bit, else it's 32-bit. The pieces which populate
       sqlite3.wasm (whwasmutil.js) can figure this out _if_ they can
       allocate, but we have a chicken/egg situation there which makes
       it illegal for that code to invoke wasm.dealloc() at the time
       it would be needed. So we need to configure it ahead of time
       (here) instead.
    */
    pointerSize: ('number'===typeof config.exports.sqlite3_libversion()) ? 4 : 8,

    /**
       True if BigInt support was enabled via (e.g.) the
       Emscripten -sWASM_BIGINT flag, else false. When
       enabled, certain 64-bit sqlite3 APIs are enabled which
       are not otherwise enabled due to JS/WASM int64
       impedance mismatches.
    */
    bigIntEnabled: !!config.bigIntEnabled,

    /**
       WebAssembly.Table object holding the indirect function call
       table. Defaults to exports.__indirect_function_table.
    */
    functionTable: config.functionTable,

    /**
       The API's primary point of access to the WASM-side memory
       allocator.  Works like sqlite3_malloc() but throws a
       WasmAllocError if allocation fails. It is important that any
       code which might pass through the sqlite3 C API NOT throw and
       must instead return SQLITE_NOMEM (or equivalent, depending on
       the context).

       Very few cases in the sqlite3 JS APIs can result in
       client-defined functions propagating exceptions via the C-style
       API. Most notably, this applies to WASM-bound JS functions
       which are created directly by clients and passed on _as WASM
       function pointers_ to functions such as
       sqlite3_create_function_v2(). Such bindings created
       transparently by this API will automatically use wrappers which
       catch exceptions and convert them to appropriate error codes.

       For cases where non-throwing allocation is required, use
       this.alloc.impl(), which is the unadulterated WASM-exported
       counterpart of this wrapper.

       Design note: this function is not named "malloc" primarily
       because Emscripten uses that name and we wanted to avoid any
       confusion early on in this code's development, when it still
       had close ties to Emscripten's glue code.
    */
    alloc: undefined/*installed later*/,

    /**
       Rarely necessary in JS code, this routine works like
       sqlite3_realloc(M,N), where M is either NULL or a pointer
       obtained from this function or this.alloc() and N is the number
       of bytes to reallocate the block to. Returns a pointer to the
       reallocated block or 0 if allocation fails.

       If M is NULL and N is positive, this behaves like
       this.alloc(N). If N is 0, it behaves like this.dealloc().
       Results are undefined if N is negative (sqlite3_realloc()
       treats that as 0, but if this code is built with a different
       allocator it may misbehave with negative values).

       Like this.alloc.impl(), this.realloc.impl() is a direct binding
       to the underlying realloc() implementation which does not throw
       exceptions, instead returning 0 (or 0n) on allocation error.
    */
    realloc: undefined/*installed later*/,

    /**
       The API's primary point of access to the WASM-side memory
       deallocator. Works like sqlite3_free().

       Design note: this function is not named "free" for the same
       reason that this.alloc() is not called this.malloc().
    */
    dealloc: undefined/*installed later*/

    /* Many more wasm-related APIs get installed later on. */
  }/*wasm*/);

  /**
     wasm.alloc()'s srcTypedArray.byteLength bytes,
     populates them with the values from the source
     TypedArray, and returns the pointer to that memory. The
     returned pointer must eventually be passed to
     wasm.dealloc() to clean it up.

     The argument may be a Uint8Array, Int8Array, or ArrayBuffer,
     and it throws if passed any other type.

     As a special case, to avoid further special cases where
     this is used, if srcTypedArray.byteLength is 0, it
     allocates a single byte and sets it to the value
     0. Even in such cases, calls must behave as if the
     allocated memory has exactly srcTypedArray.byteLength
     bytes.
  */
  wasm.allocFromTypedArray = function(srcTypedArray){
    if(srcTypedArray instanceof ArrayBuffer){
      srcTypedArray = new Uint8Array(srcTypedArray);
    }
    affirmBindableTypedArray(srcTypedArray);
    const pRet = wasm.alloc(srcTypedArray.byteLength || 1);
    wasm.heapForSize(srcTypedArray.constructor)
      .set(srcTypedArray.byteLength ? srcTypedArray : [0], Number(pRet))
    /* Maintenance note: the order of alloc() and heapForSize() calls
       is significant: https://sqlite.org/forum/forumpost/05b77273be104532 */;
    return pRet;
  };

  {
    // Set up allocators...
    const keyAlloc = config.allocExportName,
          keyDealloc = config.deallocExportName,
          keyRealloc = config.reallocExportName;
    for(const key of [keyAlloc, keyDealloc, keyRealloc]){
      const f = wasm.exports[key];
      if(!(f instanceof Function)) toss3("Missing required exports[",key,"] function.");
    }

    wasm.alloc = function f(n){
      return f.impl(n) || WasmAllocError.toss("Failed to allocate",n," bytes.");
    };
    wasm.alloc.impl = wasm.exports[keyAlloc];
    wasm.realloc = function f(m,n){
      const m2 = f.impl(wasm.ptr.coerce(m)/*tag:64bit*/,n);
      return n ? (m2 || WasmAllocError.toss("Failed to reallocate",n," bytes.")) : wasm.ptr.null;
    };
    wasm.realloc.impl = wasm.exports[keyRealloc];
    wasm.dealloc = function f(m){
      f.impl(wasm.ptr.coerce(m)/*tag:64bit*/)
      /* This coerce() is the reason we have to set wasm.pointerSize before
         calling WhWasmUtilInstaller(). If we don't, that code will call
         into this very early in its init, before wasm.ptr has been set up,
         resulting in a null deref here. */;
    };
    wasm.dealloc.impl = wasm.exports[keyDealloc];
  }

  /**
     Reports info about compile-time options using
     sqlite3_compileoption_get() and sqlite3_compileoption_used(). It
     has several distinct uses:

     If optName is an array then it is expected to be a list of
     compilation options and this function returns an object
     which maps each such option to true or false, indicating
     whether or not the given option was included in this
     build. That object is returned.

     If optName is an object, its keys are expected to be compilation
     options and this function sets each entry to true or false,
     indicating whether the compilation option was used or not. That
     object is returned.

     If passed no arguments then it returns an object mapping
     all known compilation options to their compile-time values,
     or boolean true if they are defined with no value. This
     result, which is relatively expensive to compute, is cached
     and returned for future no-argument calls.

     In all other cases it returns true if the given option was
     active when when compiling the sqlite3 module, else false.

     Compile-time option names may optionally include their
     "SQLITE_" prefix. When it returns an object of all options,
     the prefix is elided.
  */
  wasm.compileOptionUsed = function f(optName){
    if(!arguments.length){
      if(f._result) return f._result;
      else if(!f._opt){
        f._rx = /^([^=]+)=(.+)/;
        f._rxInt = /^-?\d+$/;
        f._opt = function(opt, rv){
          const m = f._rx.exec(opt);
          rv[0] = (m ? m[1] : opt);
          rv[1] = m ? (f._rxInt.test(m[2]) ? +m[2] : m[2]) : true;
        };
      }
      const rc = nu(), ov = [0,0];
      let i = 0, k;
      while((k = capi.sqlite3_compileoption_get(i++))){
        f._opt(k,ov);
        rc[ov[0]] = ov[1];
      }
      return f._result = rc;
    }else if(Array.isArray(optName)){
      const rc = nu();
      optName.forEach((v)=>{
        rc[v] = capi.sqlite3_compileoption_used(v);
      });
      return rc;
    }else if('object' === typeof optName){
      Object.keys(optName).forEach((k)=> {
        optName[k] = capi.sqlite3_compileoption_used(k);
      });
      return optName;
    }
    return (
      'string'===typeof optName
    ) ? !!capi.sqlite3_compileoption_used(optName) : false;
  }/*compileOptionUsed()*/;

  /**
     sqlite3.wasm.pstack (pseudo-stack) holds a special-case allocator
     intended solely for short-lived, small data. In practice, it's
     primarily used to allocate output pointers. It must not be used
     for any memory which needs to outlive the scope in which it's
     obtained from pstack.

     The library guarantees only that a minimum of 2kb are available
     in this allocator, and it may provide more (it's a build-time
     value). pstack.quota and pstack.remaining can be used to get the
     total resp. remaining amount of memory.

     It has only a single intended usage pattern:

     ```
     const stackPos = pstack.pointer;
     try{
       const ptr = pstack.alloc(8);
       // ==> pstack.pointer === ptr
       const otherPtr = pstack.alloc(8);
       // ==> pstack.pointer === otherPtr
       ...
     }finally{
       pstack.restore(stackPos);
       // ==> pstack.pointer === stackPos
     }
     ```

     This allocator is much faster than a general-purpose one but is
     limited to usage patterns like the one shown above (which are
     pretty common when using sqlite3.capi).

     The memory lives in the WASM heap and can be used with routines
     such as wasm.poke() and wasm.heap8u().slice().
  */
  wasm.pstack = nu({
    /**
       Sets the current pstack position to the given pointer. Results
       are undefined if the passed-in value did not come from
       this.pointer.

       In debug builds this may trigger an assert() in the WASM
       environment if passed an illegal value.
    */
    restore: wasm.exports.sqlite3__wasm_pstack_restore,

    /**
       Attempts to allocate the given number of bytes from the
       pstack. On success, it zeroes out a block of memory of the
       given size, adjusts the pstack pointer, and returns a pointer
       to the memory. On error, throws a WasmAllocError. The
       memory must eventually be released using restore().

       If n is a string, it must be a WASM "IR" value in the set
       accepted by wasm.sizeofIR(), which is mapped to the size of
       that data type. If passed a string not in that set, it throws a
       WasmAllocError.

       This method always adjusts the given value to be a multiple
       of 8 bytes because failing to do so can lead to incorrect
       results when reading and writing 64-bit values from/to the WASM
       heap. Similarly, the returned address is always 8-byte aligned.
    */
    alloc: function(n){
      if('string'===typeof n && !(n = wasm.sizeofIR(n))){
        WasmAllocError.toss("Invalid value for pstack.alloc(",arguments[0],")");
      }
      return wasm.exports.sqlite3__wasm_pstack_alloc(n)
        || WasmAllocError.toss("Could not allocate",n,
                               "bytes from the pstack.");
    },

    /**
       alloc()'s n chunks, each sz bytes, as a single memory block and
       returns the addresses as an array of n element, each holding
       the address of one chunk.

       sz may optionally be an IR string accepted by wasm.sizeofIR().

       Throws a WasmAllocError if allocation fails.

       Example:

       ```
       const [p1, p2, p3] = wasm.pstack.allocChunks(3, wasm.ptr.size);
       ```
    */
    allocChunks: function(n,sz){
      if('string'===typeof sz && !(sz = wasm.sizeofIR(sz))){
        WasmAllocError.toss("Invalid size value for allocChunks(",arguments[1],")");
      }
      const mem = wasm.pstack.alloc(n * sz);
      const rc = [mem];
      let i = 1, offset = sz;
      for(; i < n; ++i, offset += sz) rc.push(wasm.ptr.add(mem, offset));
      return rc;
    },

    /**
       A convenience wrapper for allocChunks() which sizes each chunk
       as either 8 bytes (safePtrSize is truthy) or wasm.ptr.size (if
       safePtrSize is falsy).

       How it returns its result differs depending on its first
       argument: if it's 1, it returns a single pointer value. If it's
       more than 1, it returns the same as allocChunks().

       When a returned pointer will refer to a 64-bit value, e.g. a
       double or int64, and that value must be written or fetched,
       e.g. using wasm.poke() or wasm.peek(), it is
       important that the pointer in question be aligned to an 8-byte
       boundary or else it will not be fetched or written properly and
       will corrupt or read neighboring memory.

       However, when all pointers involved point to "small" data, it
       is safe to pass a falsy value to save a tiny bit of memory.
    */
    allocPtr: (n=1,safePtrSize=true)=>{
      return 1===n
        ? wasm.pstack.alloc(safePtrSize ? 8 : wasm.ptr.size)
        : wasm.pstack.allocChunks(n, safePtrSize ? 8 : wasm.ptr.size);
    },

    /**
       Records the current pstack position, calls the given function,
       passing it the sqlite3 object, then restores the pstack
       regardless of whether the function throws. Returns the result
       of the call or propagates an exception on error.

       Added in 3.44.
    */
    call: function(f){
      const stackPos = wasm.pstack.pointer;
      try{ return f(sqlite3) }
      finally{ wasm.pstack.restore(stackPos); }
    }

  })/*wasm.pstack*/;

  Object.defineProperties(wasm.pstack, {
    /**
       Resolves to the current pstack position pointer either as a
       Number (32-bit WASM) or BigInt (64-bit WASM). This value is
       intended _only_ to be saved for passing to restore(). Writing
       to this memory, without first reserving it via
       wasm.pstack.alloc() and friends, leads to undefined results.
    */
    pointer: {
      configurable: false, iterable: true, writeable: false,
      get: wasm.exports.sqlite3__wasm_pstack_ptr
      //Whether or not a setter as an alternative to restore() is
      //clearer or would just lead to confusion or misuse is unclear.
      //set: wasm.exports.sqlite3__wasm_pstack_restore
    },

    /**
       Resolves to the total number of bytes available in the pstack
       allocator, including any space which is currently
       allocated. This value is a compile-time constant.
    */
    quota: {
      configurable: false, iterable: true, writeable: false,
      get: wasm.exports.sqlite3__wasm_pstack_quota
    },

    /**
       Resolves to the number of bytes remaining in the pstack
       allocator.
    */
    remaining: {
      configurable: false, iterable: true, writeable: false,
      get: wasm.exports.sqlite3__wasm_pstack_remaining
    }
  })/*wasm.pstack properties*/;

  /**
     Docs: https://sqlite.org/wasm/doc/trunk/api-c-style.md#sqlite3_randomness
  */
  capi.sqlite3_randomness = (...args)=>{
    if(1===args.length
       && util.isTypedArray(args[0])
       && 1===args[0].BYTES_PER_ELEMENT){
      const ta = args[0];
      if(0===ta.byteLength){
        wasm.exports.sqlite3_randomness(0,wasm.ptr.null);
        return ta;
      }
      const stack = wasm.pstack.pointer;
      try {
        let n = ta.byteLength, offset = 0;
        const r = wasm.exports.sqlite3_randomness;
        const heap = wasm.heap8u();
        const nAlloc = n < 512 ? n : 512;
        const ptr = wasm.pstack.alloc(nAlloc);
        do{
          const j = (n>nAlloc ? nAlloc : n);
          r(j, ptr);
          ta.set(wasm.typedArrayPart(heap, ptr, wasm.ptr.add(ptr,j)), offset);
          n -= j;
          offset += j;
        } while(n > 0);
      }catch(e){
        config.error("Highly unexpected (and ignored!) "+
                     "exception in sqlite3_randomness():",e);
      }finally{
        wasm.pstack.restore(stack);
      }
      return ta;
    }
    wasm.exports.sqlite3_randomness(...args);
  };

  /**
     If the wasm environment has a WASMFS/OPFS-backed persistent
     storage directory, its path is returned by this function. If it
     does not then it returns "" (noting that "" is a falsy value).

     The first time this is called, this function inspects the current
     environment to determine whether WASMFS persistence support is
     available and, if it is, enables it (if needed). After the first
     call it always returns the cached result.

     If the returned string is not empty, any files stored under the
     returned path (recursively) are housed in OPFS storage. If the
     returned string is empty, this particular persistent storage
     option is not available on the client.

     Though the mount point name returned by this function is intended
     to remain stable, clients should not hard-coded it anywhere.
     Always call this function to get the path.

     This function is a no-op in most builds of this library, as the
     WASMFS capability requires a custom build.
  */
  capi.sqlite3_wasmfs_opfs_dir = function(){
    if(undefined !== this.dir) return this.dir;
    // If we have no OPFS, there is no persistent dir
    const pdir = config.wasmfsOpfsDir;
    if(!pdir
       || !globalThis.FileSystemHandle
       || !globalThis.FileSystemDirectoryHandle
       || !globalThis.FileSystemFileHandle
       || !wasm.exports.sqlite3__wasm_init_wasmfs){
      return this.dir = "";
    }
    try{
      if(pdir && 0===wasm.xCallWrapped(
        'sqlite3__wasm_init_wasmfs', 'i32', ['string'], pdir
      )){
        return this.dir = pdir;
      }else{
        return this.dir = "";
      }
    }catch(e){
      // sqlite3__wasm_init_wasmfs() is not available
      return this.dir = "";
    }
  }.bind(nu());

  /**
     Returns true if sqlite3.capi.sqlite3_wasmfs_opfs_dir() is a
     non-empty string and the given name starts with (that string +
     '/'), else returns false.
  */
  capi.sqlite3_wasmfs_filename_is_persistent = function(name){
    const p = capi.sqlite3_wasmfs_opfs_dir();
    return (p && name) ? name.startsWith(p+'/') : false;
  };

  /**
     Given an `sqlite3*`, an sqlite3_vfs name, and an optional db name
     (defaulting to "main"), returns a truthy value (see below) if
     that db uses that VFS, else returns false. If pDb is falsy then
     the 3rd argument is ignored and this function returns a truthy
     value if the default VFS name matches that of the 2nd argument.
     Results are undefined if pDb is truthy but refers to an invalid
     pointer. The 3rd argument specifies the database name of the
     given database connection to check, defaulting to the main db.

     The 2nd and 3rd arguments may either be a JS string or a WASM
     C-string. If the 2nd argument is a NULL WASM pointer, the default
     VFS is assumed. If the 3rd is a NULL WASM pointer, "main" is
     assumed.

     The truthy value it returns is a pointer to the `sqlite3_vfs`
     object.

     To permit safe use of this function from APIs which may be called
     via C (like SQL UDFs), this function does not throw: if bad
     arguments cause a conversion error when passing into wasm-space,
     false is returned.
  */
  capi.sqlite3_js_db_uses_vfs = function(pDb,vfsName,dbName=0){
    try{
      const pK = capi.sqlite3_vfs_find(vfsName);
      if(!pK) return false;
      else if(!pDb){
        return pK===capi.sqlite3_vfs_find(0) ? pK : false;
      }else{
        return pK===capi.sqlite3_js_db_vfs(pDb,dbName) ? pK : false;
      }
    }catch(e){
      /* Ignore - probably bad args to a wasm-bound function. */
      return false;
    }
  };

  /**
     Returns an array of the names of all currently-registered sqlite3
     VFSes.
  */
  capi.sqlite3_js_vfs_list = function(){
    const rc = [];
    let pVfs = capi.sqlite3_vfs_find(wasm.ptr.null);
    while(pVfs){
      const oVfs = new capi.sqlite3_vfs(pVfs);
      rc.push(wasm.cstrToJs(oVfs.$zName));
      pVfs = oVfs.$pNext;
      oVfs.dispose();
    }
    return rc;
  };

  /**
     A convenience wrapper around sqlite3_serialize() which serializes
     the given `sqlite3*` pointer to a Uint8Array. The first argument
     may be either an `sqlite3*` or an sqlite3.oo1.DB instance.

     On success it returns a Uint8Array. If the schema is empty, an
     empty array is returned.

     `schema` is the schema to serialize. It may be a WASM C-string
     pointer or a JS string. If it is falsy, it defaults to `"main"`.

     On error it throws with a description of the problem.
  */
  capi.sqlite3_js_db_export = function(pDb, schema=0){
    pDb = wasm.xWrap.testConvertArg('sqlite3*', pDb);
    if(!pDb) toss3('Invalid sqlite3* argument.');
    if(!wasm.bigIntEnabled) toss3('BigInt support is not enabled.');
    const scope = wasm.scopedAllocPush();
    let pOut;
    try{
      const pSize = wasm.scopedAlloc(8/*i64*/ + wasm.ptr.size);
      const ppOut = wasm.ptr.add(pSize, 8);
      /**
         Maintenance reminder, since this cost a full hour of grief
         and confusion: if the order of pSize/ppOut are reversed in
         that memory block, fetching the value of pSize after the
         export reads a garbage size because it's not on an 8-byte
         memory boundary!
      */
      const zSchema = schema
            ? (wasm.isPtr(schema) ? schema : wasm.scopedAllocCString(''+schema))
            : wasm.ptr.null;
      let rc = wasm.exports.sqlite3__wasm_db_serialize(
        pDb, zSchema, ppOut, pSize, 0
      );
      if(rc){
        toss3("Database serialization failed with code",
             sqlite3.capi.sqlite3_js_rc_str(rc));
      }
      pOut = wasm.peekPtr(ppOut);
      const nOut = wasm.peek(pSize, 'i64');
      rc = nOut
        ? wasm.heap8u().slice(Number(pOut), Number(pOut) + Number(nOut))
        : new Uint8Array();
      return rc;
    }finally{
      if(pOut) wasm.exports.sqlite3_free(pOut);
      wasm.scopedAllocPop(scope);
    }
  };

  /**
     Given a `sqlite3*` and a database name (JS string or WASM
     C-string pointer, which may be 0), returns a pointer to the
     sqlite3_vfs responsible for it. If the given db name is null/0,
     or not provided, then "main" is assumed.
  */
  capi.sqlite3_js_db_vfs =
    (dbPointer, dbName=wasm.ptr.null)=>util.sqlite3__wasm_db_vfs(dbPointer, dbName);

  /**
     A thin wrapper around capi.sqlite3_aggregate_context() which
     behaves the same except that it throws a WasmAllocError if that
     function returns 0. As a special case, if n is falsy it does
     _not_ throw if that function returns 0. That special case is
     intended for use with xFinal() implementations.
  */
  capi.sqlite3_js_aggregate_context = (pCtx, n)=>{
    return capi.sqlite3_aggregate_context(pCtx, n)
      || (n ? WasmAllocError.toss("Cannot allocate",n,
                                  "bytes for sqlite3_aggregate_context()")
          : 0);
  };

  /**
     If the current environment supports the POSIX file APIs, this routine
     creates (or overwrites) the given file using those APIs. This is
     primarily intended for use in Emscripten-based builds where the POSIX
     APIs are transparently proxied by an in-memory virtual filesystem.
     It may behave differently in other environments.

     The first argument must be either a JS string or WASM C-string
     holding the filename. This routine does _not_ create intermediary
     directories if the filename has a directory part.

     The 2nd argument may either a valid WASM memory pointer, an
     ArrayBuffer, or a Uint8Array. The 3rd must be the length, in
     bytes, of the data array to copy. If the 2nd argument is an
     ArrayBuffer or Uint8Array and the 3rd is not a positive integer
     then the 3rd defaults to the array's byteLength value.

     Results are undefined if data is a WASM pointer and dataLen is
     exceeds data's bounds.

     Throws if any arguments are invalid or if creating or writing to
     the file fails.

     Added in 3.43 as an alternative for the deprecated
     sqlite3_js_vfs_create_file().
  */
  capi.sqlite3_js_posix_create_file = function(filename, data, dataLen){
    let pData;
    if(data && wasm.isPtr(data)){
      pData = data;
    }else if(data instanceof ArrayBuffer || data instanceof Uint8Array){
      pData = wasm.allocFromTypedArray(data);
      if(arguments.length<3 || !util.isInt32(dataLen) || dataLen<0){
        dataLen = data.byteLength;
      }
    }else{
      SQLite3Error.toss("Invalid 2nd argument for sqlite3_js_posix_create_file().");
    }
    try{
      if(!util.isInt32(dataLen) || dataLen<0){
        SQLite3Error.toss("Invalid 3rd argument for sqlite3_js_posix_create_file().");
      }
      const rc = util.sqlite3__wasm_posix_create_file(filename, pData, dataLen);
      if(rc) SQLite3Error.toss("Creation of file failed with sqlite3 result code",
                               capi.sqlite3_js_rc_str(rc));
    }finally{
      if( pData && pData!==data ) wasm.dealloc(pData);
    }
  };

  /**
     Deprecation warning: this function does not work properly in
     debug builds of sqlite3 because its out-of-scope use of the
     sqlite3_vfs API triggers assertions in the core library.  That
     was unfortunately not discovered until 2023-08-11. This function
     is now deprecated. It should not be used in new code and should
     be removed from existing code.

     Alternative options:

     - The "unix" VFS and its variants can get equivalent
       functionality with sqlite3_js_posix_create_file().

     - OPFS: use either sqlite3.oo1.OpfsDb.importDb(), for the "opfs"
       VFS, or the importDb() method of the PoolUtil object provided
       by the "opfs-sahpool" OPFS (noting that its VFS name may differ
       depending on client-side configuration). We cannot proxy those
       from here because the former is necessarily asynchronous and
       the latter requires information not available to this function.

     Historical (deprecated) behaviour:

     Creates a file using the storage appropriate for the given
     sqlite3_vfs.  The first argument may be a VFS name (JS string
     only, NOT a WASM C-string), WASM-managed `sqlite3_vfs*`, or
     a capi.sqlite3_vfs instance. Pass 0 (a NULL pointer) to use the
     default VFS. If passed a string which does not resolve using
     sqlite3_vfs_find(), an exception is thrown. (Note that a WASM
     C-string is not accepted because it is impossible to
     distinguish from a C-level `sqlite3_vfs*`.)

     The second argument, the filename, must be a JS or WASM C-string.

     The 3rd may either be falsy, a valid WASM memory pointer, an
     ArrayBuffer, or a Uint8Array. The 4th must be the length, in
     bytes, of the data array to copy. If the 3rd argument is an
     ArrayBuffer or Uint8Array and the 4th is not a positive integer
     then the 4th defaults to the array's byteLength value.

     If data is falsy then a file is created with dataLen bytes filled
     with uninitialized data (whatever truncate() leaves there). If
     data is not falsy then a file is created or truncated and it is
     filled with the first dataLen bytes of the data source.

     Throws if any arguments are invalid or if creating or writing to
     the file fails.

     Note that most VFSes do _not_ automatically create directory
     parts of filenames, nor do all VFSes have a concept of
     directories.  If the given filename is not valid for the given
     VFS, an exception will be thrown. This function exists primarily
     to assist in implementing file-upload capability, with the caveat
     that clients must have some idea of the VFS into which they want
     to upload and that VFS must support the operation.

     VFS-specific notes:

     - "memdb": results are undefined.

     - "kvvfs": will fail with an I/O error due to strict internal
       requirements of that VFS's xTruncate().

     - "unix" and related: will use the WASM build's equivalent of the
       POSIX I/O APIs. This will work so long as neither a specific
       VFS nor the WASM environment imposes requirements which break
       it.  (Much later: it turns out that debug builds of the library
       impose such requirements, in that they assert() that dataLen is
       an even multiple of a valid db page size.)

     - "opfs": uses OPFS storage and creates directory parts of the
       filename. It can only be used to import an SQLite3 database
       file and will fail if given anything else.
  */
  capi.sqlite3_js_vfs_create_file = function(vfs, filename, data, dataLen){
    config.warn("sqlite3_js_vfs_create_file() is deprecated and",
                "should be avoided because it can lead to C-level crashes.",
                "See its documentation for alternatives.");
    let pData;
    if(data){
      if( wasm.isPtr(data) ){
        pData = data;
      }else{
        if( data instanceof ArrayBuffer ){
          data = new Uint8Array(data);
        }
        if( data instanceof Uint8Array ){
          pData = wasm.allocFromTypedArray(data);
          if(arguments.length<4 || !util.isInt32(dataLen) || dataLen<0){
            dataLen = data.byteLength;
          }
        }else{
          SQLite3Error.toss("Invalid 3rd argument type for sqlite3_js_vfs_create_file().");
        }
      }
    }else{
       pData = 0;
    }
    if(!util.isInt32(dataLen) || dataLen<0){
      if( pData && pData!==data ) wasm.dealloc(pData);
      SQLite3Error.toss("Invalid 4th argument for sqlite3_js_vfs_create_file().");
    }
    try{
      const rc = util.sqlite3__wasm_vfs_create_file(vfs, filename, pData, dataLen);
      if(rc) SQLite3Error.toss("Creation of file failed with sqlite3 result code",
                               capi.sqlite3_js_rc_str(rc));
    }finally{
       if( pData && pData!==data ) wasm.dealloc(pData);
    }
  };

  /**
     Converts SQL input from a variety of convenient formats
     to plain strings.

     If v is a string, it is returned as-is. If it is-a Array, its
     join("") result is returned.  If is is a Uint8Array, Int8Array,
     or ArrayBuffer, it is assumed to hold UTF-8-encoded text and is
     decoded to a string. If it looks like a WASM pointer,
     wasm.cstrToJs(sql) is returned. Else undefined is returned.

     Added in 3.44
  */
  capi.sqlite3_js_sql_to_string = (sql)=>{
    if('string' === typeof sql){
      return sql;
    }
    const x = flexibleString(v);
    return x===v ? undefined : x;
  }

  /**
     Wraps all known variants of the C-side variadic
     sqlite3_db_config().

     Full docs: https://sqlite.org/c3ref/db_config.html

     Returns capi.SQLITE_MISUSE if op is not a valid operation ID.

     The variants which take `(int, int*)` arguments treat a
     missing or falsy pointer argument as 0.
  */
  capi.sqlite3_db_config = function(pDb, op, ...args){
    switch(op){
      case capi.SQLITE_DBCONFIG_ENABLE_FKEY:
      case capi.SQLITE_DBCONFIG_ENABLE_TRIGGER:
      case capi.SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER:
      case capi.SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION:
      case capi.SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE:
      case capi.SQLITE_DBCONFIG_ENABLE_QPSG:
      case capi.SQLITE_DBCONFIG_TRIGGER_EQP:
      case capi.SQLITE_DBCONFIG_RESET_DATABASE:
      case capi.SQLITE_DBCONFIG_DEFENSIVE:
      case capi.SQLITE_DBCONFIG_WRITABLE_SCHEMA:
      case capi.SQLITE_DBCONFIG_LEGACY_ALTER_TABLE:
      case capi.SQLITE_DBCONFIG_DQS_DML:
      case capi.SQLITE_DBCONFIG_DQS_DDL:
      case capi.SQLITE_DBCONFIG_ENABLE_VIEW:
      case capi.SQLITE_DBCONFIG_LEGACY_FILE_FORMAT:
      case capi.SQLITE_DBCONFIG_TRUSTED_SCHEMA:
      case capi.SQLITE_DBCONFIG_STMT_SCANSTATUS:
      case capi.SQLITE_DBCONFIG_REVERSE_SCANORDER:
      case capi.SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE:
      case capi.SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE:
      case capi.SQLITE_DBCONFIG_ENABLE_COMMENTS:
      case capi.SQLITE_DBCONFIG_FP_DIGITS:
        if( !this.ip ){
          this.ip = wasm.xWrap('sqlite3__wasm_db_config_ip','int',
                               ['sqlite3*', 'int', 'int', '*']);
        }
        return this.ip(pDb, op, args[0], args[1] || 0);
      case capi.SQLITE_DBCONFIG_LOOKASIDE:
        if( !this.pii ){
          this.pii = wasm.xWrap('sqlite3__wasm_db_config_pii', 'int',
                                ['sqlite3*', 'int', '*', 'int', 'int']);
        }
        return this.pii(pDb, op, args[0], args[1], args[2]);
      case capi.SQLITE_DBCONFIG_MAINDBNAME:
        if(!this.s){
          this.s = wasm.xWrap('sqlite3__wasm_db_config_s','int',
                              ['sqlite3*', 'int', 'string:static']
                              /* MAINDBNAME requires a static string */);
        }
        return this.s(pDb, op, args[0]);
      default:
        return capi.SQLITE_MISUSE;
    }
  }.bind(nu());

  /**
     Given a (sqlite3_value*), this function attempts to convert it
     to an equivalent JS value with as much fidelity as feasible and
     return it.

     By default it throws if it cannot determine any sensible
     conversion. If passed a falsy second argument, it instead returns
     `undefined` if no suitable conversion is found.  Note that there
     is no conversion from SQL to JS which results in the `undefined`
     value, so `undefined` has an unambiguous meaning here.  It will
     always throw a WasmAllocError if allocating memory for a
     conversion fails.

     Caveats:

     - It does not support sqlite3_value_to_pointer() conversions
       because those require a type name string which this function
       does not have and cannot sensibly be given at the level of the
       API where this is used (e.g. automatically converting UDF
       arguments). Clients using sqlite3_value_to_pointer(), and its
       related APIs, will need to manage those themselves.
  */
  capi.sqlite3_value_to_js = function(pVal,throwIfCannotConvert=true){
    let arg;
    const valType = capi.sqlite3_value_type(pVal);
    switch(valType){
        case capi.SQLITE_INTEGER:
          if(wasm.bigIntEnabled){
            arg = capi.sqlite3_value_int64(pVal);
            if(util.bigIntFitsDouble(arg)) arg = Number(arg);
          }
          else arg = capi.sqlite3_value_double(pVal)/*yes, double, for larger integers*/;
          break;
        case capi.SQLITE_FLOAT:
          arg = capi.sqlite3_value_double(pVal);
          break;
        case capi.SQLITE_TEXT:
          arg = capi.sqlite3_value_text(pVal);
          break;
        case capi.SQLITE_BLOB:{
          const n = capi.sqlite3_value_bytes(pVal);
          const pBlob = capi.sqlite3_value_blob(pVal);
          if(n && !pBlob) sqlite3.WasmAllocError.toss(
            "Cannot allocate memory for blob argument of",n,"byte(s)"
          );
          arg = n
            ? wasm.heap8u().slice(Number(pBlob), Number(pBlob) + Number(n))
            : null;
          break;
        }
        case capi.SQLITE_NULL:
          arg = null; break;
        default:
          if(throwIfCannotConvert){
            toss3(capi.SQLITE_MISMATCH,
                  "Unhandled sqlite3_value_type():",valType);
          }
          arg = undefined;
    }
    return arg;
  };

  /**
     Requires a C-style array of `sqlite3_value*` objects and the
     number of entries in that array. Returns a JS array containing
     the results of passing each C array entry to
     sqlite3_value_to_js(). The 3rd argument to this function is
     passed on as the 2nd argument to that one.
  */
  capi.sqlite3_values_to_js = function(argc,pArgv,throwIfCannotConvert=true){
    let i;
    const tgt = [];
    for(i = 0; i < argc; ++i){
      /**
         Curiously: despite ostensibly requiring 8-byte
         alignment, the pArgv array is parcelled into chunks of
         4 bytes (1 pointer each). The values those point to
         have 8-byte alignment but the individual argv entries
         do not.
      */
      tgt.push(capi.sqlite3_value_to_js(
        wasm.peekPtr(wasm.ptr.add(pArgv, wasm.ptr.size * i)),
        throwIfCannotConvert
      ));
    }
    return tgt;
  };

  /**
     Calls either sqlite3_result_error_nomem(), if e is-a
     WasmAllocError, or sqlite3_result_error(). In the latter case,
     the second argument is coerced to a string to create the error
     message.

     The first argument is a (sqlite3_context*). Returns void.
     Does not throw.
  */
  capi.sqlite3_result_error_js = function(pCtx,e){
    if(e instanceof WasmAllocError){
      capi.sqlite3_result_error_nomem(pCtx);
    }else{
      /* Maintenance reminder: ''+e, rather than e.message,
         will prefix e.message with e.name, so it includes
         the exception's type name in the result. */;
      capi.sqlite3_result_error(pCtx, ''+e, -1);
    }
  };

  /**
     This function passes its 2nd argument to one of the
     sqlite3_result_xyz() routines, depending on the type of that
     argument:

     - If (val instanceof Error), this function passes it to
       sqlite3_result_error_js().
     - `null`: `sqlite3_result_null()`
     - `boolean`: `sqlite3_result_int()` with a value of 0 or 1.
     - `number`: `sqlite3_result_int()`, `sqlite3_result_int64()`, or
       `sqlite3_result_double()`, depending on the range of the number
       and whether or not int64 support is enabled.
     - `bigint`: similar to `number` but will trigger an error if the
       value is too big to store in an int64.
     - `string`: `sqlite3_result_text()`
     - Uint8Array or Int8Array or ArrayBuffer: `sqlite3_result_blob()`
     - `undefined`: is a no-op provided to simplify certain use cases.

     Anything else triggers `sqlite3_result_error()` with a
     description of the problem.

     The first argument to this function is a `(sqlite3_context*)`.
     Returns void. Does not throw.
  */
  capi.sqlite3_result_js = function(pCtx,val){
    if(val instanceof Error){
      capi.sqlite3_result_error_js(pCtx, val);
      return;
    }
    try{
      switch(typeof val) {
          case 'undefined':
            /* This is a no-op. This routine originated in the create_function()
               family of APIs and in that context, passing in undefined indicated
               that the caller was responsible for calling sqlite3_result_xxx()
               (if needed). */
            break;
          case 'boolean':
            capi.sqlite3_result_int(pCtx, val ? 1 : 0);
            break;
          case 'bigint':
            if(util.bigIntFits32(val)){
              capi.sqlite3_result_int(pCtx, Number(val));
            }else if(util.bigIntFitsDouble(val)){
              capi.sqlite3_result_double(pCtx, Number(val));
            }else if(wasm.bigIntEnabled){
              if(util.bigIntFits64(val)) capi.sqlite3_result_int64(pCtx, val);
              else toss3("BigInt value",val.toString(),"is too BigInt for int64.");
            }else{
              toss3("BigInt value",val.toString(),"is too BigInt.");
            }
            break;
          case 'number': {
            let f;
            if(util.isInt32(val)){
              f = capi.sqlite3_result_int;
            }else if(wasm.bigIntEnabled
                     && Number.isInteger(val)
                     && util.bigIntFits64(BigInt(val))){
              f = capi.sqlite3_result_int64;
            }else{
              f = capi.sqlite3_result_double;
            }
            f(pCtx, val);
            break;
          }
          case 'string': {
            const [p, n] = wasm.allocCString(val,true);
            capi.sqlite3_result_text(pCtx, p, n, capi.SQLITE_WASM_DEALLOC);
            break;
          }
          case 'object':
            if(null===val/*yes, typeof null === 'object'*/) {
              capi.sqlite3_result_null(pCtx);
              break;
            }else if(util.isBindableTypedArray(val)){
              const pBlob = wasm.allocFromTypedArray(val);
              capi.sqlite3_result_blob(
                pCtx, pBlob, val.byteLength,
                capi.SQLITE_WASM_DEALLOC
              );
              break;
            }
            // else fall through
          default:
            toss3("Don't not how to handle this UDF result value:",(typeof val), val);
      }
    }catch(e){
      capi.sqlite3_result_error_js(pCtx, e);
    }
  };

  /**
     Returns the result sqlite3_column_value(pStmt,iCol) passed to
     sqlite3_value_to_js(). The 3rd argument of this function is
     ignored by this function except to pass it on as the second
     argument of sqlite3_value_to_js(). If the sqlite3_column_value()
     returns NULL (e.g. because the column index is out of range),
     this function returns `undefined`, regardless of the 3rd
     argument. If the 3rd argument is falsy and conversion fails,
     `undefined` will be returned.

     Note that sqlite3_column_value() returns an "unprotected" value
     object, but in a single-threaded environment (like this one)
     there is no distinction between protected and unprotected values.
  */
  capi.sqlite3_column_js = function(pStmt, iCol, throwIfCannotConvert=true){
    const v = capi.sqlite3_column_value(pStmt, iCol);
    return (0===v) ? undefined : capi.sqlite3_value_to_js(v, throwIfCannotConvert);
  };

  if( true ){ /* changeset/preupdate additions... */
    /**
       Internal impl of sqlite3_preupdate_new/old_js() and
       sqlite3changeset_new/old_js().
    */
    const __newOldValue = function(pObj, iCol, impl){
      impl = capi[impl];
      if(!this.ptr) this.ptr = wasm.allocPtr();
      else wasm.pokePtr(this.ptr, 0);
      const rc = impl(pObj, iCol, this.ptr);
      if(rc) return SQLite3Error.toss(rc,arguments[2]+"() failed with code "+rc);
      const pv = wasm.peekPtr(this.ptr);
      return pv ? capi.sqlite3_value_to_js( pv, true ) : undefined;
    }.bind(nu());

    /**
       A wrapper around sqlite3_preupdate_new() which fetches the
       sqlite3_value at the given index and returns the result of
       passing it to sqlite3_value_to_js(). Throws on error.
    */
    capi.sqlite3_preupdate_new_js =
      (pDb, iCol)=>__newOldValue(pDb, iCol, 'sqlite3_preupdate_new');

    /**
       The sqlite3_preupdate_old() counterpart of
       sqlite3_preupdate_new_js(), with an identical interface.
    */
    capi.sqlite3_preupdate_old_js =
      (pDb, iCol)=>__newOldValue(pDb, iCol, 'sqlite3_preupdate_old');

    /**
       A wrapper around sqlite3changeset_new() which fetches the
       sqlite3_value at the given index and returns the result of
       passing it to sqlite3_value_to_js(). Throws on error.

       If sqlite3changeset_new() succeeds but has no value to report,
       this function returns the undefined value, noting that
       undefined is not a valid conversion from an `sqlite3_value`, so
       is unambiguous.
    */
    capi.sqlite3changeset_new_js =
      (pChangesetIter, iCol) => __newOldValue(pChangesetIter, iCol,
                                              'sqlite3changeset_new');

    /**
       The sqlite3changeset_old() counterpart of
       sqlite3changeset_new_js(), with an identical interface.
    */
    capi.sqlite3changeset_old_js =
      (pChangesetIter, iCol)=>__newOldValue(pChangesetIter, iCol,
                                            'sqlite3changeset_old');
  }/*changeset/preupdate additions*/

  /**
     sqlite3_js_retry_busy(maxTimes,callback[,beforeRetry])

     Calls the given _synchronous_ callback function. If that function
     returns sqlite3.capi.SQLITE_BUSY _or_ throws an SQLite3Error
     with a resultCode property of that value then it will suppress
     that error and try again, up to the given maximum number of
     times. If the callback returns any other value than that,
     it is returned. If the maximum number of retries has been
     reached, an SQLite3Error with a resultCode value of
     sqlite3.capi.SQLITE_BUSY is thrown. If the callback throws any
     exception other than the aforementioned BUSY exception, it is
     propagated. If it throws a BUSY exception on its final attempt,
     that is propagated as well.

     If the beforeRetry argument is given, it must be a _synchronous_
     function.  It is called immediately before each retry of the
     callback (not for the initial call), passed the attempt number
     (so it starts with 2, not 1). If it throws, the exception is
     handled as described above. Its result value is ignored.

     To effectively retry "forever", pass a huge maxTimes value such
     as Number.MAX_SAFE_INTEGER, with the caveat that there is no
     recovery from that unless the beforeRetry() can figure out when
     to throw.

     Added in 3.53.0.

     TODO?: an async variant of this.
  */
  capi.sqlite3_js_retry_busy = function(maxTimes, callback, beforeRetry){
    for(let n = 1; n <= maxTimes; ++n){
      try{
        if( beforeRetry && n>1 ) beforeRetry(n);
        const rc = callback();
        if( capi.SQLITE_BUSY===rc ){
          if( n===maxTimes ){
            throw new SQLite3Error(rc, [
              "sqlite3_js_retry_busy() max retry attempts (",
              maxTimes,
              ") reached."
            ].join(''));
          }
          continue;
        }
        return rc;
      }catch(e){
        if( n<maxTimes
            && (e instanceof SQLite3Error)
            && e.resultCode===capi.SQLITE_BUSY ){
          continue;
        }
        throw e;
      }
    }
  };

  /* The remainder of the API will be set up in later steps. */
  const sqlite3 = {
    WasmAllocError: WasmAllocError,
    SQLite3Error: SQLite3Error,
    capi,
    util /* internal: will get removed after library init */,
    wasm,
    config,
    /**
       Holds the version info of the sqlite3 source tree from which
       the generated sqlite3-api.js gets built. Its version may well
       differ from that reported by sqlite3_libversion(), but that
       should be considered a source file mismatch, as the JS and WASM
       files are intended to be built and distributed together.

       This object is initially a placeholder which gets replaced by a
       build-generated object.
    */
    version: nu(),

    /**
       The library reserves the 'client' property for client-side use
       and promises to never define a property with this name nor to
       ever rely on specific contents of it. It makes no such guarantees
       for other properties.
    */
    client: undefined,

    /**
       This function is not part of the public interface, but a
       piece of internal bootstrapping infrastructure.

       Performs any optional asynchronous library-level initialization
       which might be required. This function returns a Promise which
       resolves to the sqlite3 namespace object. Any error in the
       async init will be fatal to the init as a whole, but init
       routines are themselves welcome to install dummy catch()
       handlers which are not fatal if their failure should be
       considered non-fatal.

       Ideally this function is called as part of the Promise chain
       which handles the loading and bootstrapping of the API.  If not
       then it must be called by client-level code, which must not use
       the library until the returned Promise resolves.

       If called multiple times it will return the same Promise on
       subsequent calls. The current build setup precludes that
       possibility, so it's only a hypothetical problem if/when this
       function ever needs to be invoked by clients.

       In Emscripten-based builds, this function is called
       automatically and deleted from this object.
    */
    asyncPostInit: async function ff(){
      if(ff.isReady instanceof Promise) return ff.isReady;
      let lia = this.initializersAsync;
      delete this.initializersAsync;
      const postInit = async ()=>{
        if(!sqlite3.__isUnderTest){
          /* Delete references to internal-only APIs which are used by
             some initializers. Retain them when running in test mode
             so that we can add tests for them. */
          delete sqlite3.util;
          delete sqlite3.StructBinder;
          delete sqlite3.opfs;
        }
        return sqlite3;
      };
      const catcher = (e)=>{
        config.error("an async sqlite3 initializer failed:",e);
        throw e;
      };
      if(!lia || !lia.length){
        return ff.isReady = postInit().catch(catcher);
      }
      lia = lia.map((f)=>{
        return (f instanceof Function) ? async x=>f(sqlite3) : f;
      });
      lia.push(postInit);
      let p = Promise.resolve(sqlite3);
      while(lia.length) p = p.then(lia.shift());
      return ff.isReady = p.catch(catcher);
    }.bind(sqlite3ApiBootstrap),
    /**
       scriptInfo holds information about the currenty-loading script
       so that we can locate the WASM file if it's somewhere other
       than the build-time-defined directory. It ideally gets injected
       into this object by the infrastructure which assembles the
       JS/WASM module. It contains state which must be collected
       before sqlite3ApiBootstrap() can be declared. It is not
       necessarily available to any sqlite3ApiBootstrap.initializers
       but "should" be in place (if it's added at all) by the time
       that sqlite3ApiBootstrap.initializersAsync is processed.

       This state is not part of the public API, only intended for use
       with the sqlite3 API bootstrapping and wasm-loading process.
    */
    scriptInfo: undefined
  };
  if( 'undefined'!==typeof sqlite3IsUnderTest/* from post-js-header.js */ ){
    sqlite3.__isUnderTest = !!sqlite3IsUnderTest;
  }
  try{
    sqlite3ApiBootstrap.initializers.forEach((f)=>{
      f(sqlite3);
    });
  }catch(e){
    /* If we don't report this here, it can get completely swallowed
       up and disappear into the abyss of Promises and Workers. */
    console.error("sqlite3 bootstrap initializer threw:",e);
    throw e;
  }
  delete sqlite3ApiBootstrap.initializers;
  sqlite3ApiBootstrap.sqlite3 = sqlite3;
  if( 'undefined'!==typeof sqlite3InitScriptInfo/* from post-js-header.js */ ){
    sqlite3InitScriptInfo.debugModule(
      "sqlite3ApiBootstrap() complete", sqlite3
    );
    sqlite3.scriptInfo
    /* Used by some async init code. As of 2025-11-15 this is still
       in use by the OPFS VFS for locating its worker. In non-Emscripten
       builds, this would need to be injected in somewhere to get
       that VFS loading. */ = sqlite3InitScriptInfo;
  }
  if( sqlite3.__isUnderTest ){
    if( 'undefined'!==typeof EmscriptenModule ){
      sqlite3.config.emscripten = EmscriptenModule;
    }
    /*
      The problem with exposing these pieces (in non-testing runs) via
      sqlite3.wasm is that it exposes non-SQLite pieces to the
      clients, who may come to expect it to remain. _We_ only have
      these data because we've overridden Emscripten's wasm file
      loader, and if we lose that capability for some reason then
      we'll lose access to this metadata.

      These data are interesting for exploring how the wasm/JS pieces
      connect, e.g. for exploring exactly what Emscripten imports into
      WASM from its JS glue, but it's not SQLite-related and is not
      required for the library to work.
    */
    const iw = sqlite3.scriptInfo?.instantiateWasm;
    if( iw ){
      /* Metadata injected by the custom Module.instantiateWasm()
         in pre-js.c-pp.js. */
      sqlite3.wasm.module = iw.module;
      sqlite3.wasm.instance = iw.instance;
      sqlite3.wasm.imports = iw.imports;
    }
  }

  /**
     Eliminate any confusion about whether these config objects may
     be used after library initialization by eliminating the outward-facing
     objects...
  */
  delete globalThis.sqlite3ApiConfig;
  delete globalThis.sqlite3ApiBootstrap;
  delete sqlite3ApiBootstrap.defaultConfig;
  return sqlite3.asyncPostInit().then((s)=>{
    if( 'undefined'!==typeof sqlite3InitScriptInfo/* from post-js-header.js */ ){
      sqlite3InitScriptInfo.debugModule(
        "sqlite3.asyncPostInit() complete", s
      );
    }
    delete s.asyncPostInit;
    delete s.scriptInfo;
    delete s.emscripten;
    return s;
  });
}/*sqlite3ApiBootstrap()*/;

/**
  globalThis.sqlite3ApiBootstrap.initializers is an internal detail
  used by the various pieces of the sqlite3 API's amalgamation
  process. It must not be modified by client code except when plugging
  such code into the amalgamation process.

  Each component of the amalgamation is expected to append a function
  to this array. When sqlite3ApiBootstrap() is called for the first
  time, each such function will be called (in their appended order)
  and passed the sqlite3 namespace object, into which they can install
  their features. At the end of that process, this array is deleted.

  The order of insertion into this array is significant for
  some pieces. e.g. sqlite3.capi and sqlite3.wasm cannot be fully
  utilized until the whwasmutil.js part is plugged in via
  sqlite3-api-glue.js.
*/
globalThis.sqlite3ApiBootstrap.initializers = [];

/**
  globalThis.sqlite3ApiBootstrap.initializersAsync is an internal detail
  used by the sqlite3 API's amalgamation process. It must not be
  modified by client code except when plugging such code into the
  amalgamation process.

  The counterpart of globalThis.sqlite3ApiBootstrap.initializers,
  specifically for initializers which are asynchronous. All entries in
  this list must be either async functions, non-async functions which
  return a Promise, or a Promise. Each function in the list is called
  with the sqlite3 object as its only argument.

  The resolved value of any Promise is ignored and rejection will kill
  the asyncPostInit() process (at an indeterminate point because all
  of them are run asynchronously in parallel).

  This list is not processed until the client calls
  sqlite3.asyncPostInit(). This means, for example, that intializers
  added to globalThis.sqlite3ApiBootstrap.initializers may push entries to
  this list.
*/
globalThis.sqlite3ApiBootstrap.initializersAsync = [];

/**
   Client code may assign sqlite3ApiBootstrap.defaultConfig an
   object-type value before calling sqlite3ApiBootstrap() (without
   arguments) in order to tell that call to use this object as its
   default config value. The intention of this is to provide
   downstream clients with a reasonably flexible approach for plugging in
   an environment-suitable configuration without having to define a new
   global-scope symbol.
*/
globalThis.sqlite3ApiBootstrap.defaultConfig = Object.create(null);

/**
   Placeholder: gets installed by the first call to
   globalThis.sqlite3ApiBootstrap(). However, it is recommended that the
   caller of sqlite3ApiBootstrap() capture its return value and delete
   globalThis.sqlite3ApiBootstrap after calling it. It returns the same
   value which will be stored here.
*/
globalThis.sqlite3ApiBootstrap.sqlite3 = undefined;
