Hi 👋🏻, Alberto from @prisma here.
We are users of the `serde-json` feature in [23fdc5965b/Cargo.toml (L55-L60)).
## The Problem
While investigating a [user-reported Prisma issue](https://github.com/prisma/prisma/issues/22294), we noticed that napi.rs treats `null` values in `Object` like `undefined` ones.
However, `null` and `undefined` are semantically different in JavaScript:
- `undefined` indicates that a value is not set
- `null` indicates that a value has been explicitly set
This PR, which we tested internally, fixes the user's issue on Prisma, and hopefully provides value to the `napi-rs` project as a whole.
## Scenario
Consider [this scenario](dcb8cb9817/query-engine/driver-adapters/src/napi/result.rs (L32-L33)), effectively equivalent to:
```rust
let napi_env: napi::sys::napi_env = /* ... */;
let value: JsUnknown = object.get_named_property("value")?;
// napi.rs implements the `FromNapiValue` trait for `serde_json` if
// the `serde_json` feature is enabled (which we use already).
let json = serde_json::Value::from_napi_value(napi_env, value.raw())?;
Ok(Self::Ok(json.into()));
```
By looking at `napi.rs`' [`serde.rs` source file](https://github.com/napi-rs/napi-rs/blob/napi%402.12.4/crates/napi/src/bindgen_runtime/js_values/serde.rs), we [can see](https://github.com/napi-rs/napi-rs/blob/napi%402.12.4/crates/napi/src/bindgen_runtime/js_values/serde.rs#L104-L108) that "JSON subobjects" (`Map<String, serde_json::Value>`) are populated only if the value returned by `JsObject::get` is not `None`.
This implies that, given `napi-rs`' [`Object::get` definition](https://github.com/napi-rs/napi-rs/blob/napi%402.12.4/crates/napi/src/bindgen_runtime/js_values/object.rs#L24-L44), `Object::get("key")` returns `None` both when the original object is
```js
{
// "key" is `undefined`
}
```
and when the object is
```js
{
"key": null
}
```
It just so happens that `prisma-engines` explicitly set a few `null` values explicitly for the features it offers, but these values are stripped away when we use the [napi-flavoured Prisma Query Engine](https://github.com/prisma/prisma-engines/tree/main/query-engine/query-engine-node-api).
---
I am available for further comments, clarifications, and refactorings. Have a good day!
[![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)
This PR contains the following updates:
| Update | Change |
|---|---|
| lockFileMaintenance | All locks refreshed |
🔧 This Pull Request updates lock files to use the latest dependency versions.
---
### Configuration
📅 **Schedule**: Branch creation - "before 4am on the first day of the month" (UTC), Automerge - At any time (no schedule defined).
🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.
♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://togithub.com/renovatebot/renovate/discussions) if that's undesired.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box
---
This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/napi-rs/napi-rs).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy4xNTMuMiIsInVwZGF0ZWRJblZlciI6IjM3LjE1My4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiJ9-->
[![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [npm-run-all](https://togithub.com/mysticatea/npm-run-all) | devDependencies | replacement | [`^4.1.5` -> `^5.0.0`](https://renovatebot.com/diffs/npm/npm-run-all/4.1.5/) |
This is a special PR that replaces `npm-run-all` with the community suggested minimal stable replacement version.
---
### Configuration
📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).
🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.
♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box
---
This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/napi-rs/napi-rs).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy4xNTMuMiIsInVwZGF0ZWRJblZlciI6IjM3LjE1My4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiJ9-->
This is the experimental feature that provides a all new design `Function` API in NAPI-RS.
The main motivation to design a new `Function` instead of improving the old `JsFunction` is there are some fundamental problems in the old `JsFunction` API.
1. The old `JsFunction` doesn't contains a lifetime, which means you can send it to a outlive scope and call it later, which would cause a `napi_invalid_arg` error in the underlying `napi_call_function` API. This design issue also happens in the `JsObject`/`JsBuffer` and all other non-primitive types APIs.
2. It's not possible to generate correct TypeScript type definitions for the old `JsFunction` API.
3. The arguments of the old `JsFunction` API must be the same type, which is makes it really unfriendly to use.
Expect that, we also have a high level and modern Function exists in the `NAPI-RS` which is the Generic type style `Fn(Args) -> Return`.
This API is pretty nice to use, and more importantly, it's sound.
But there are some limitations to use it, like create a reference to it to outlive the scope of the JavaScript function under the hood. And you can't use it create a `ThreadsafeFunction`.
So there is the new design `Function` API, there are some core features:
1. It's a generic typed API, which means you can get more accurate Rust type information and generate correct TypeScript type definitions.
2. It's sound, which means you can't send it to a outlive scope and call it later, if you want do that, you must create a reference to it or create a `ThreadsafeFunction`.
3. It's friendly to use, you can use different types of arguments and return types, and it can covert the Rust tuple type to JavaScript arguments automatically.
Here is some examples to show how to use it:
```rust
use napi::bindgen_prelude::*;
use napi_derive::napi;
#[napi]
pub fn callback_javascript_callback(add_one: Function<u32, u32>) -> Result<u32> {
add_one.call(100)
}
```
⬇️⬇️⬇️
```typescript
export function callbackJavascriptCallback(add_one: (arg0: number) => number): number;
```
⬇️⬇️⬇️
```javascript
callbackJavascriptCallback((arg0) => arg0 + 1);
// 101
```
If you define a tuple as the `Function` arguments, it will be converted to JavaScript arguments automatically.
```rust
use napi::bindgen_prelude::*;
use napi_derive::napi;
#[napi]
pub fn callback_javascript_callback(add: Function<(u32, u32), u32>) -> Result<u32> {
add.call((100, 200))
}
```
⬇️⬇️⬇️
```typescript
export function callbackJavascriptCallback(add: (arg0: number, arg1: number) => number): number;
```
⬇️⬇️⬇️
```javascript
callbackJavascriptCallback((arg0, arg1) => arg0 + arg1);
// 300
```
If you are trying to send it into a outlive scope, you will get a compile error.
For example, if you are trying to send a callback to `git2-rs` `RemoteCallbacks::credentials` API:
```rust
use napi::bindgen_prelude::*;
use napi_derive::napi;
#[napi]
pub fn build_credential(on_credential: Function<(String, Option<String>, CredentialType), ClassInstance<Cred>>) -> Result<()> {
let mut callbacks = git2::RemoteCallbacks::new();
callbacks.credentials(move |url, username_from_url, allowed_types| {
on_credential.call((url.to_string(), username_from_url.map(|s| s.to_string()), allowed_types.into()))
.map(...)
.map_error(...)
});
}
```
You will get a compile error:
```text
error[E0597]: `on_credential` does not live long enough
```
To fix this issue, you can create a reference to it:
```rust
use napi::bindgen_prelude::*;
use napi_derive::napi;
#[napi]
pub fn build_credential(env: Env. on_credential: Function<(String, Option<String>, CredentialType), ClassInstance<Cred>>) -> Result<()> {
let mut callbacks = git2::RemoteCallbacks::new();
let on_credential_ref = on_credential.create_ref()?;
callbacks.credentials(move |url, username_from_url, allowed_types| {
let on_credential = on_credential_ref.borrow_back(&env)?;
on_credential.call((url.to_string(), username_from_url.map(|s| s.to_string()), allowed_types.into()))
.map(...)
.map_error(...)
});
}
```