Skip to content

[BUG]: ScopedGenerator overrides other class methods #3213

@karlrobeck

Description

@karlrobeck

Hi. im just wondering if this is a intended design or a bug. im building a sqlite library as a test project using napi-rs and rusqlite and for the most part it works fine on my end. there is this section of my code that uses ScopedGenerator to iterate rows and convert them into JS object. i've created another method in my RowIterator called toJSON so that i can integrate it with JSON.stringify according to MDN docs.

If the value has a toJSON() method, it's responsible to define what data will be serialized. Instead of the object being serialized, the value returned by the toJSON() method when called will be serialized. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify

here is the code that i have implemented

RowIterator

#[napi(iterator)]
pub struct RowIterator<'a> {
  pub(crate) rows: rusqlite::Rows<'a>,
}

#[napi]
impl<'a> ScopedGenerator<'a> for RowIterator<'a> {
  type Next = ();
  type Return = ();
  type Yield = Unknown<'a>;

  fn next(&mut self, env: &'a napi::Env, _value: Option<Self::Next>) -> Option<Self::Yield> {
    let next_row = self.rows.next().ok().unwrap_or_default()?;

    let mut value_map = HashMap::new();

    let columns = next_row.as_ref().columns();

    for column in &columns {
      let raw_value = next_row.get_ref(column.name()).ok()?;
      value_map.insert(column.name(), ValueRef(raw_value));
    }

    env.to_js_value(&value_map).ok()
  }
}

#[napi]
impl RowIterator<'_> {
  #[napi(js_name = "toJSON")]
  pub fn to_json(&mut self, env: Env) -> napi::Result<Unknown<'_>> {
    let mut rows = vec![];

    while let Some(row) = self.rows.next().map_err(NodeRusqliteError::from)? {
      let mut value_map = HashMap::new();

      let columns = row.as_ref().columns();

      for column in columns {
        let raw_value = row
          .get_ref(column.name())
          .map_err(NodeRusqliteError::from)?;

        let value = serde_json::to_value(ValueRef(raw_value))
          .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))
          .map_err(NodeRusqliteError::from)?;
        value_map.insert(column.name().to_string(), value);
      }
      rows.push(value_map);
    }

    env.to_js_value(&rows)
  }
}

when i run napi build (under bun run build:debug)

here is the generated typescript definition

binding.d.ts

/**
 * This type extends JavaScript's `Iterator`, and so has the iterator helper
 * methods. It may extend the upcoming TypeScript `Iterator` class in the future.
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator#iterator_helper_methods
 * @see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-6.html#iterator-helper-methods
 */
export declare class RowIterator extends Iterator<unknown, void, void> {
  toJSON(): unknown
  next(value?: void): IteratorResult<unknown, void>
}

here is the code that i have as a basic test.

test.ts

import { Connection } from "../bindings/binding.js";

let conn = Connection.openInMemory();

conn.prepare("select ? as name",(statement) => {
  const result = statement.query(["john doe"])
  console.log("array result -> ", Array.from(result))
  console.log("json.stringify result ->", JSON.stringify(result))
  console.log("toJSON result -> ", result.toJSON())
})

and here is the output for bun runtime.

array result ->  [
  {
    name: "john doe",
  }
]
json.stringify result -> {}
4 | 
5 | conn.prepare("select ? as name",(statement) => {
6 |   const result = statement.query(["john doe"])
7 |   console.log("array result -> ", Array.from(result))
8 |   console.log("json.stringify result ->", JSON.stringify(result))
9 |   console.log("toJSON result -> ", result.toJSON())
                                              ^
TypeError: result.toJSON is not a function. (In 'result.toJSON()', 'result.toJSON' is undefined)
      at <anonymous> (/home/robeckk/opensource/node-rusqlite/examples/test.ts:9:43)
      at /home/robeckk/opensource/node-rusqlite/examples/test.ts:5:6
      at loadAndEvaluateModule (2:1)

Bun v1.3.11 (Linux x64)

and here is the output for nodejs

(node:184965) [MODULE_TYPELESS_PACKAGE_JSON] Warning: Module type of file:///home/robeckk/opensource/node-rusqlite/examples/test.ts is not specified and it doesn't parse as CommonJS.
Reparsing as ES module because module syntax was detected. This incurs a performance overhead.
To eliminate this warning, add "type": "module" to /home/robeckk/opensource/node-rusqlite/package.json.
(Use `node --trace-warnings ...` to show where the warning was created)
array result ->  [ { name: 'john doe' } ]
json.stringify result -> {}
file:///home/robeckk/opensource/node-rusqlite/examples/test.ts:9
  console.log("toJSON result -> ", result.toJSON())
                                          ^

TypeError: result.toJSON is not a function
    at file:///home/robeckk/opensource/node-rusqlite/examples/test.ts:9:43
    at file:///home/robeckk/opensource/node-rusqlite/examples/test.ts:5:6
    at ModuleJob.run (node:internal/modules/esm/module_job:413:25)
    at process.processTicksAndRejections (node:internal/process/task_queues:103:5)
    at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:660:26)
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)

Node.js v24.13.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions