mdoTomofumi Chiba
1/13/2024, 12:51:38 PM

Deno で Node 互換の未実装部分を補完するワークアラウンド

Deno Advent Calendar 2023 何日目かの記事です。

ニッチで将来的には意味がなくなりそうなネタですが、Deno で Node 互換の未実装部分を補完するワークアラウンド を紹介したいと思います。
その方法を知った経緯から書きます。

みなさん Deno で Oracle Database に接続したいですよね。分かります。
Oracle から thin モード接続に対応した npm モジュールが登場したので試してみました。
試したコードは、以下です。

// @deno-types="npm:@types/oracledb"
import oracledb from "oracledb";

const conn = await oracledb.getConnection({connectString: "localhost:1521/XEPDB1", user: "test", password: "test"});
const result = await conn.execute("select table_name from tabs");
console.log(result);
conn.close();

実行してみると以下のようにエラー。

$ deno --version
deno 1.37.2 (release, x86_64-unknown-linux-gnu)
v8 11.8.172.13
typescript 5.2.2
$ deno task start
Task start deno run --allow-sys --allow-env --allow-net main.ts
error: Uncaught (in promise) TypeError: The "string" argument must be of type string or an instance of Buffer or ArrayBuffer. Received undefined
    at Function.byteLength (ext:deno_node/internal/buffer.mjs:359:11)
    at WritePacket.writeKeyValue (file:///home/chiba/.cache/deno/npm/registry.npmjs.org/oracledb/6.2.0/lib/thin/protocol/packet.js:514:32)
(省略)

絶賛、互換性を上げてるところだから、とりあえず気長に待とうかと思いました。その後、試しに競合 Bun で動かしてみると、普通に動いてしまいました。
「なんか悔しい」と思い、Issue を作成しました。
Issue 作成後、自分でなんとかできないかと思い、デバッグしてみると process.argv0 の実装にバグがある事に気づき報告し、直して頂きました(まだちょっとNodeとは挙動が違うので完全ではない)。
その後、修正バージョンで試すと、次は以下のようなエラーに変わりました。

$ deno task start
Task start deno run --allow-sys --allow-env --allow-net --reload main.ts
error: Uncaught (in promise) TypeError: Unknown cipher
    at new Decipheriv (ext:deno_node/internal/crypto/cipher.ts:140:13)
    at Object.createDecipheriv (node:crypto:28:10)
    at EncryptDecrypt._decrypt (file:///home/chiba/.cache/deno/npm/registry.npmjs.org/oracledb/6.2.0/lib/thin/protocol/encryptDecrypt.js:45:29)
(省略)

これもデバッグしてみると crypto の aes-256-cbc, aes-192-cbc の未実装、crypto.Decipheriv.prototype.setAutoPadding の未実装が関係してそうという所までは分かり、実装に貢献する能力も時間もなかったため、しばらく待とうかと思いました。

ここからが、本題です。。
その後、これを解決するためのワークアラウンドを教えてくれるコメントがありました。
内容としましては、ブラウザ用の Node API の実装(Browserify)に置き換えるという方法でした。
import map の設定で、依存ライブラリが使用する Node API の実装をピンポイントで置き換えることができるようです。

まずは、以下のような polyfills.ts ファイルを作成します。

import browserify from "https://esm.sh/[email protected]";
import crypto from "node:crypto";

crypto.createDecipheriv = browserify.createDecipheriv;
crypto.createCipheriv = browserify.createCipheriv;

export default crypto;

そして、以下のように deno.json で設定します。

{
  "imports": {
    "oracledb": "https://esm.sh/v135/[email protected]"
  },
  "scopes": {
    "https://esm.sh/v135/[email protected]/": {
      "node:crypto": "./polyfills.ts"
    }
  }
}

そして、試してみると Oracle Database に接続できる事が確認できました!
Deno がブラウザの API を実装してくれているからできる技だと思いますが、こんな事ができるのかと驚きでした。

おまけ

npm:oracledb の thick モード(ネイティブライブラリを利用する方)を試したら、普通に動きました。ネイティブライブラリをインストールし、以下のコードを追加する感じです。

oracledb.initOracleClient();

ネイティブライブラリをインストールするのが面倒なので thin モードの方が良いですよね。。

以上です。

TweetLike