-
-
Notifications
You must be signed in to change notification settings - Fork 158
feat(sqlite): add bun:sqlite compatibility #8525
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| ### Added | ||
|
|
||
| - Add a native `bun:sqlite` compatibility facade backed by the same rusqlite | ||
| engine as Perry's `node:sqlite` implementation. `Database` construction, | ||
| prepared statements, positional and named parameters, object and array row | ||
| modes, blobs, safe integers, transactions, change metadata, serialization, | ||
| extension loading, and handle lifetime operations now support OpenCode's Bun | ||
| SQLite adapter without leaving an unresolved `bun:` import in the graph. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -229,6 +229,26 @@ pub(crate) const API_MANIFEST_PART_1: &[ApiEntry] = &[ | |
| .stub_note("stage ≥2 — not yet implemented, throws at runtime (#6562)"), | ||
| method("bun:ffi", "read", false, None) | ||
| .stub_note("stage ≥2 — not yet implemented, throws at runtime (#6562)"), | ||
| // bun:sqlite (#8510) shares node:sqlite's rusqlite handles while keeping | ||
| // Bun's public constructor and statement vocabulary. | ||
| class("bun:sqlite", "Database"), | ||
| class("bun:sqlite", "Statement"), | ||
| method("bun:sqlite", "Database", false, None), | ||
| method("bun:sqlite", "query", true, Some("Database")), | ||
| method("bun:sqlite", "prepare", true, Some("Database")), | ||
| method("bun:sqlite", "run", true, Some("Database")), | ||
| method("bun:sqlite", "close", true, Some("Database")), | ||
| method("bun:sqlite", "serialize", true, Some("Database")), | ||
| method("bun:sqlite", "loadExtension", true, Some("Database")), | ||
| method("bun:sqlite", "transaction", true, Some("Database")), | ||
| property("bun:sqlite", "filename"), | ||
| property("bun:sqlite", "inTransaction"), | ||
| method("bun:sqlite", "run", true, Some("Statement")), | ||
| method("bun:sqlite", "get", true, Some("Statement")), | ||
| method("bun:sqlite", "all", true, Some("Statement")), | ||
| method("bun:sqlite", "values", true, Some("Statement")), | ||
| method("bun:sqlite", "safeIntegers", true, Some("Statement")), | ||
| method("bun:sqlite", "finalize", true, Some("Statement")), | ||
|
Comment on lines
+236
to
+251
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🌐 Web query:
💡 Result: Yes, in the Citations:
🏁 Script executed: #!/bin/bash
set -eu
file=$(fd -t f '^part_1\.rs$' . | head -n 1)
printf '%s\n' "FILE=$file"
sed -n '210,265p' "$file"
printf '%s\n' '--- manifest helper definitions and relevant entries ---'
rg -n -C 4 'fn (method|property)|macro_rules! (method|property)|method\("bun:sqlite"|Database|Statement' crates/perry-api-manifest crates | head -n 240Repository: PerryTS/perry Length of output: 22781 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- method entry construction ---'
sed -n '270,410p' crates/perry-api-manifest/src/entries.rs
printf '%s\n' '--- bun sqlite implementation and dispatch symbols ---'
rg -n -C 3 'bun_sqlite|sqlite.*run|sqlite.*exec|Database.*run|Database.*exec|js_bun_sqlite' crates runtime src 2>/dev/null | head -n 300
printf '%s\n' '--- all exact bun:sqlite exec references ---'
rg -n -F '"bun:sqlite"' . | rg -n 'exec|run|sqlite' | head -n 200Repository: PerryTS/perry Length of output: 36371 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- Bun SQLite native table ---'
sed -n '710,855p' crates/perry-codegen/src/lower_call/native_table/databases.rs
printf '%s\n' '--- Bun SQLite runtime dispatch ---'
fd -t f . crates/perry-stdlib crates/perry-codegen | xargs rg -n -C 5 'bun_sqlite_database_(run|query|call)|BunSqliteDatabase|method_name|class_filter'
printf '%s\n' '--- generated API declaration ---'
sed -n '330,390p' docs/api/perry.d.ts
printf '%s\n' '--- Bun SQLite tests and fixtures ---'
rg -n -C 4 'bun:sqlite|Database.*(run|exec)|\.exec\(' crates test-files docs | head -n 260Repository: PerryTS/perry Length of output: 50370 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- Bun runtime implementation ---'
rg -n -C 12 'js_bun_sqlite_database_run|pub .*bun.*sqlite.*run|bun_sqlite_database_run' \
crates/perry-stdlib crates/perry-runtime crates/perry-codegen
printf '%s\n' '--- Bun API declaration ---'
sed -n '335,385p' docs/api/perry.d.ts
printf '%s\n' '--- focused manifest/native-table verifier ---'
python3 - <<'PY'
from pathlib import Path
import re
manifest = Path("crates/perry-api-manifest/src/entries/part_1.rs").read_text()
native = Path("crates/perry-codegen/src/lower_call/native_table/databases.rs").read_text()
manifest_rows = re.findall(
r'method\("bun:sqlite",\s*"([^"]+)",\s*(true|false),\s*(Some\("([^"]+)"\)|None)\)',
manifest,
)
native_section = native.split('// ========== bun:sqlite ==========', 1)[1].split(
'// ========== node:sqlite ==========', 1
)[0]
native_rows = re.findall(
r'NativeModSig\s*\{\s*module:\s*"bun:sqlite",\s*'
r'has_receiver:\s*(true|false),\s*method:\s*"([^"]+)",\s*'
r'class_filter:\s*(Some\("([^"]+)"\)|None),\s*'
r'runtime:\s*"([^"]+)"',
native_section,
re.S,
)
print("manifest Database methods:",
[(name, cls or None) for name, recv, _, cls in manifest_rows
if cls == "Database"])
print("native Database methods:",
[(name, cls or None, runtime) for recv, name, _, cls, runtime in native_rows
if cls == "Database"])
print("manifest has Database.exec:",
any(name == "exec" and cls == "Database" for name, recv, _, cls in manifest_rows))
print("native has Database.exec:",
any(name == "exec" and cls == "Database" for recv, name, _, cls, runtime in native_rows))
print("native Database.run runtime:",
[runtime for recv, name, _, cls, runtime in native_rows
if name == "run" and cls == "Database"])
PYRepository: PerryTS/perry Length of output: 12197 Add the documented Add 🤖 Prompt for AI Agents |
||
| class("sqlite", "DatabaseSync"), | ||
| class("sqlite", "Session"), | ||
| class("sqlite", "SQLTagStore"), | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -637,6 +637,29 @@ pub(super) fn lower_builtin_new<'a>( | |
| let handle = blk.call(I64, "js_pg_pool_new", &[(DOUBLE, &config_val)]); | ||
| Ok(Some(nanbox_pointer_inline(blk, &handle))) | ||
| } | ||
| // bun:sqlite Database — distinct internal name avoids colliding with | ||
| // better-sqlite3's exported `Database` while preserving full JS values | ||
| // for Bun's optional filename and flags object. | ||
| "BunSqliteDatabase" => { | ||
| let path_idx = adopt_optional_arg(ctx, args, 0, group)?; | ||
| let options_idx = adopt_optional_arg(ctx, args, 1, group)?; | ||
| let undef = || double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)); | ||
| let path_value = match path_idx { | ||
| Some(i) => group.reread(ctx, i)?, | ||
| None => undef(), | ||
| }; | ||
| let options_value = match options_idx { | ||
| Some(i) => group.reread(ctx, i)?, | ||
| None => undef(), | ||
| }; | ||
| let blk = ctx.block(); | ||
| let handle = blk.call( | ||
| I64, | ||
| "js_bun_sqlite_database_new", | ||
| &[(DOUBLE, &path_value), (DOUBLE, &options_value)], | ||
| ); | ||
| Ok(Some(nanbox_pointer_inline(blk, &handle))) | ||
|
Comment on lines
+643
to
+661
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Evaluate constructor arguments after This branch lowers only arguments zero and one. Lower and discard 🤖 Prompt for AI Agents🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🌐 Web query:
💡 Result: Yes, for the versions of Bun that support Citations:
🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- matching runtime definitions and call sites ---'
rg -n -C 8 'js_bun_sqlite_database_new|validate_optional_object|BunSqliteDatabase' crates
printf '%s\n' '--- relevant builtin lowering ---'
cat -n crates/perry-codegen/src/lower_call/builtin.rs | sed -n '630,670p'
printf '%s\n' '--- exact nanbox helper definition ---'
cat -n crates/perry-codegen/src/nanbox.rs | sed -n '80,115p'Repository: PerryTS/perry Length of output: 21519 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- Bun constructor implementation ---'
cat -n crates/perry-stdlib/src/sqlite/bun.rs | sed -n '1,90p'
printf '%s\n' '--- options validation and value-shape helpers ---'
cat -n crates/perry-stdlib/src/sqlite/options.rs | sed -n '1,45p;185,220p'
rg -n -C 6 'fn is_object_like|is_object_like\(' crates/perry-stdlib crates/perry-common crates
printf '%s\n' '--- read-only behavioral verifier ---'
python3 - <<'PY'
from pathlib import Path
bun = Path("crates/perry-stdlib/src/sqlite/bun.rs").read_text()
options = Path("crates/perry-stdlib/src/sqlite/options.rs").read_text()
# Verify the constructor validates every defined options value before reading
# readonly/readwrite/create, and that validation rejects non-object values.
constructor = bun[bun.index("pub unsafe extern \"C\" fn js_bun_sqlite_database_new"):bun.index("pub unsafe extern \"C\" fn js_bun_sqlite_database_new") + 1800]
assert 'let explicit_options = !value_from_f64(options_value).is_undefined();' in constructor
assert 'validate_optional_object(options_value);' in constructor
validation = options[options.index("pub(crate) unsafe fn validate_optional_object"):options.index("pub(crate) unsafe fn validate_optional_object") + 450]
assert 'if js.is_null() || !is_object_like(options_value)' in validation
validate_at = constructor.index("validate_optional_object(options_value)")
read_at = constructor.index('options.read_only = bool_option')
assert validate_at < read_at
# A defined numeric options value is not undefined, null, or object-like.
# This models the branch shown in validate_optional_object; it therefore
# reaches the type error before flag decoding because no numeric branch exists.
def current_outcome(value_kind):
explicit = value_kind != "undefined"
if not explicit:
return "default options"
if value_kind == "object":
return "object options"
return 'throw_type: The "options" argument must be an object.'
assert current_outcome("number") == 'throw_type: The "options" argument must be an object.'
assert current_outcome("object") == "object options"
assert current_outcome("undefined") == "default options"
print("numeric options: rejected by current validation path")
print("object options: accepted by current validation path")
print("undefined options: default options")
PYRepository: PerryTS/perry Length of output: 50369 🏁 Script executed: #!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
bun = Path("crates/perry-stdlib/src/sqlite/bun.rs").read_text()
options = Path("crates/perry-stdlib/src/sqlite/options.rs").read_text()
start = bun.index('pub unsafe extern "C" fn js_bun_sqlite_database_new')
body = bun[start:]
validate = body.index("validate_optional_object(options_value)")
flag_read = body.index('options.read_only = bool_option')
assert validate < flag_read
assert 'let explicit_options = !value_from_f64(options_value).is_undefined();' in body
start = options.index("pub(crate) fn is_object_like")
is_object_like = options[start:options.index("pub(crate) unsafe fn object_field", start)]
validation = options[options.index("pub(crate) unsafe fn validate_optional_object"):]
assert "value_from_f64(value).is_pointer()" in is_object_like
assert "js.is_null() || !is_object_like(options_value)" in validation
def outcome(kind):
if kind == "undefined":
return "defaults"
if kind == "object":
return "object-options"
return "type-error"
assert outcome("number") == "type-error"
assert outcome("object") == "object-options"
assert outcome("undefined") == "defaults"
print("constructor validates defined options before flag reads")
print("numeric options are rejected because numbers are not pointer-backed objects")
print("object and undefined options follow their existing paths")
PYRepository: PerryTS/perry Length of output: 340 Accept numeric SQLite open flags. If 🤖 Prompt for AI Agents |
||
| } | ||
| // better-sqlite3 Database — `new Database(filename)` opens a SQLite | ||
| // connection. Without this, `new Database(...)` falls into lower_new's | ||
| // empty-object placeholder, so `db` is a generic ObjectHeader pointer | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -717,6 +717,133 @@ pub(super) const DATABASES_ROWS: &[NativeModSig] = &[ | |
| args: &[], | ||
| ret: NR_VOID, | ||
| }, | ||
| // ========== bun:sqlite ========== | ||
| NativeModSig { | ||
| module: "bun:sqlite", | ||
| has_receiver: false, | ||
| method: "Database", | ||
| class_filter: None, | ||
| runtime: "js_bun_sqlite_database_call", | ||
| args: &[NA_F64, NA_F64], | ||
| ret: NR_PTR, | ||
| }, | ||
| NativeModSig { | ||
| module: "bun:sqlite", | ||
| has_receiver: true, | ||
| method: "query", | ||
| class_filter: Some("Database"), | ||
| runtime: "js_bun_sqlite_database_query", | ||
| args: &[NA_F64], | ||
| ret: NR_PTR, | ||
| }, | ||
| NativeModSig { | ||
| module: "bun:sqlite", | ||
| has_receiver: true, | ||
| method: "prepare", | ||
| class_filter: Some("Database"), | ||
| runtime: "js_bun_sqlite_database_query", | ||
| args: &[NA_F64], | ||
| ret: NR_PTR, | ||
|
Comment on lines
+739
to
+746
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🌐 Web query:
💡 Result: Yes, that is correct. In bun:sqlite, the Database.query method caches the compiled SQL statement on the Database instance [1][2], while the Database.prepare method does not cache the compiled query [3][4]. When you use Database.query, Bun stores the resulting Statement object in an internal cache (up to a default limit of 20 queries) [2][5]. If you call query again with the same SQL string, Bun returns the existing, cached Statement object rather than recompiling the SQL [1][2]. This caching mechanism refers to the compiled SQL bytecode (the prepared statement itself), not the results of the query; it is safe to reuse cached statements with different parameter values [2]. In contrast, Database.prepare creates a fresh Statement instance every time it is called [1][2]. This is intended for scenarios where you want to avoid filling the cache with one-off or dynamically generated queries [1][2]. Citations:
🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- native table entry ---'
sed -n '700,770p' crates/perry-codegen/src/lower_call/native_table/databases.rs
printf '%s\n' '--- runtime symbol definitions and call sites ---'
rg -n -C 5 'js_bun_sqlite_database_query|bun_sqlite_database_(prepare|query)|Database\.prepare|Database\.query' cratesRepository: PerryTS/perry Length of output: 9343 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- Bun SQLite implementation ---'
cat -n crates/perry-stdlib/src/sqlite/bun.rs
printf '%s\n' '--- shared database prepare implementation ---'
rg -n -C 8 'js_node_sqlite_database_sync_prepare|register_node_sqlite_database|Statement.*cache|cache' crates/perry-stdlib/src/sqlite
printf '%s\n' '--- Bun SQLite tests and documentation references ---'
rg -n -i -C 3 'bun:sqlite|Database\.query|Database\.prepare|cached|uncached' crates tests README.md 2>/dev/null | head -240Repository: PerryTS/perry Length of output: 45157 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- shared prepare body ---'
sed -n '340,430p' crates/perry-stdlib/src/sqlite/node_db.rs
printf '%s\n' '--- database and statement handle fields ---'
rg -n -C 5 'struct NodeSqlite(Db|Stmt)Handle|type NodeSqlite|register_handle|finalize_node_sqlite_statement_handle' crates/perry-stdlib/src/sqlite
printf '%s\n' '--- native dispatch construction ---'
sed -n '80,135p' crates/perry-stdlib/src/sqlite/dispatch.rsRepository: PerryTS/perry Length of output: 19433 Implement separate Bun SQLite statement paths.
🤖 Prompt for AI Agents |
||
| }, | ||
| NativeModSig { | ||
| module: "bun:sqlite", | ||
| has_receiver: true, | ||
| method: "run", | ||
| class_filter: Some("Database"), | ||
| runtime: "js_bun_sqlite_database_run", | ||
| args: &[NA_F64, NA_VARARGS], | ||
| ret: NR_PTR, | ||
| }, | ||
| NativeModSig { | ||
| module: "bun:sqlite", | ||
| has_receiver: true, | ||
| method: "close", | ||
| class_filter: Some("Database"), | ||
| runtime: "js_node_sqlite_database_sync_close", | ||
| args: &[], | ||
| ret: NR_I32, | ||
| }, | ||
|
Comment on lines
+757
to
+765
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🌐 Web query:
💡 Result: In the Citations:
🏁 Script executed: # Inspect the signature table, the native runtime helpers, and all callers of the
# bun:sqlite close entry to determine whether the boolean is dropped and whether
# the selected helper implements Bun's close contract.
printf '%s\n' '--- target rows ---'
sed -n '700,790p' crates/perry-codegen/src/lower_call/native_table/databases.rs
printf '%s\n' '--- related signatures and helpers ---'
rg -n -C 5 'js_node_sqlite_database_sync_close|sqlite_database_sync_close|bun:sqlite|throwOnError|Database.*close' crates
printf '%s\n' '--- tracked candidate files ---'
git ls-files | rg 'sqlite|database|native_table'Repository: PerryTS/perry Length of output: 48277 🏁 Script executed: # Read the close implementation and the native-call lowering path to establish
# the actual argument ABI. Also inspect Bun-specific tests and documentation
# comments for the intended close behavior.
printf '%s\n' '--- node close implementation ---'
sed -n '220,290p' crates/perry-stdlib/src/sqlite/node_db.rs
printf '%s\n' '--- Bun SQLite implementation ---'
sed -n '1,90p' crates/perry-stdlib/src/sqlite/bun.rs
printf '%s\n' '--- native-call lowering definitions ---'
rg -n -C 8 'struct NativeModSig|enum NativeArg|NA_F64|NA_VARARGS|args:.*NativeModSig|runtime.*NativeModSig' crates/perry-codegen/src/lower_call
printf '%s\n' '--- close-related tests and docs ---'
rg -n -C 8 'close\(|throwOnError|prepared.*close|close.*prepared|bun:sqlite' test-files test-parity docs changelog.d/8525-bun-sqlite.mdRepository: PerryTS/perry Length of output: 50370 🏁 Script executed: # Narrow the inspection to the relevant files and avoid unrelated `.close()`
# matches from the full test suite.
printf '%s\n' '--- close implementation ---'
sed -n '245,285p' crates/perry-stdlib/src/sqlite/node_db.rs
printf '%s\n' '--- Bun implementation ---'
sed -n '1,85p' crates/perry-stdlib/src/sqlite/bun.rs
printf '%s\n' '--- signature type and argument lowering ---'
rg -n -C 12 'pub struct NativeModSig|struct NativeModSig|enum NativeArg|NA_F64|NA_VARARGS' crates/perry-codegen/src/lower_call/native_table crates/perry-codegen/src/lower_call
printf '%s\n' '--- close references in focused files ---'
rg -n -C 6 'close\(|throwOnError|prepared.*close|close.*prepared' \
crates/perry-stdlib/src/sqlite \
crates/perry-codegen/src/lower_call/native_table/databases.rs \
test-files/test_gap_bun_sqlite_8510.ts \
changelog.d/8525-bun-sqlite.mdRepository: PerryTS/perry Length of output: 50370 🏁 Script executed: # The previous search still matched every native-table row because the argument
# constants are shared across the table. Use exact symbol searches and bounded
# source reads.
printf '%s\n' '--- close implementation ---'
sed -n '250,280p' crates/perry-stdlib/src/sqlite/node_db.rs
printf '%s\n' '--- Bun implementation ---'
sed -n '1,82p' crates/perry-stdlib/src/sqlite/bun.rs
printf '%s\n' '--- NativeModSig declaration ---'
rg -n -C 10 'struct NativeModSig|pub struct NativeModSig' crates/perry-codegen/src
printf '%s\n' '--- argument-kind declarations ---'
rg -n -C 10 'enum NativeArg|NA_F64|NA_VARARGS' crates/perry-codegen/src/lower_call/native_table/mod.rs
printf '%s\n' '--- close references in SQLite sources ---'
rg -n -C 5 'js_node_sqlite_database_sync_close|throwOnError|prepared' crates/perry-stdlib/src/sqlite
printf '%s\n' '--- focused Bun test/changelog references ---'
rg -n -C 5 'close|throwOnError|prepared' test-files/test_gap_bun_sqlite_8510.ts changelog.d/8525-bun-sqlite.mdRepository: PerryTS/perry Length of output: 20562 🏁 Script executed: # Inspect the remainder of the close helper, the statement-finalization helper,
# and the exact native-call emission code. This distinguishes an omitted Boolean
# from a helper that already finalizes statements and reports failures.
printf '%s\n' '--- remainder of close helper ---'
sed -n '257,325p' crates/perry-stdlib/src/sqlite/node_db.rs
printf '%s\n' '--- finalization helpers and database handle fields ---'
rg -n -C 12 'struct NodeSqliteDbHandle|finalize_node_sqlite_statements|delete_node_sqlite_sessions|fn finalize_node_sqlite' crates/perry-stdlib/src/sqlite/node_db.rs
printf '%s\n' '--- exact native-call emission references ---'
rg -n -C 14 'sig\.args|row\.args|native_sig\.args|NativeArgKind::VarArgsAsArray|NativeArgKind::F64' crates/perry-codegen/src/lower_call
printf '%s\n' '--- relevant Bun close documentation ---'Repository: PerryTS/perry Length of output: 50369 🌐 Web query:
💡 Result: In the context of the Bun runtime's SQLite implementation, the Database.close(throwOnError) method manages the behavior of active prepared statements and the database connection itself [1]. When you call db.close(false) (the default behavior), the database connection is closed, but any statements created using.prepare are allowed to persist until they are finalized or garbage-collected [1]. The underlying database connection and file handle remain open until all outstanding prepared statements are finalized [1]. When you call db.close(true), the system attempts to finalize every outstanding prepared statement immediately and releases the connection [1]. If SQLite encounters an error during this process, the method will throw an error [1]. Once a database is closed, attempting to use any statement that was finalized by the close call will result in a "Database has closed" error [1]. There are two exceptions to this: calling toString on a closed statement returns an empty string, and calling finalize on a closed statement remains safe [1]. If a Database instance is garbage-collected without an explicit call to close, Bun will automatically release the connection only after all statements associated with it have been finalized or collected [1]. Citations: 🏁 Script executed: # Inspect the remainder of the close helper, the statement-finalization helper,
# and the exact native-call emission code. This distinguishes an omitted Boolean
# from a helper that already finalizes statements and reports failures.
printf '%s\n' '--- remainder of close helper ---'
sed -n '257,325p' crates/perry-stdlib/src/sqlite/node_db.rs
printf '%s\n' '--- finalization helpers and database handle fields ---'
rg -n -C 12 'struct NodeSqliteDbHandle|finalize_node_sqlite_statements|delete_node_sqlite_sessions|fn finalize_node_sqlite' crates/perry-stdlib/src/sqlite/node_db.rs
printf '%s\n' '--- exact native-call emission references ---'
rg -n -C 14 'sig\.args|row\.args|native_sig\.args|NativeArgKind::VarArgsAsArray|NativeArgKind::F64' crates/perry-codegen/src/lower_callRepository: PerryTS/perry Length of output: 50369 🏁 Script executed: # Verify the two remaining semantic points with small, read-only source probes:
# (1) the native call emits an extra F64 argument when the row has no declared
# slot, and (2) statement finalization discards any close-time error state.
python3 - <<'PY'
from pathlib import Path
p = Path("crates/perry-codegen/src/lower_call/native_module_dispatch.rs").read_text()
start = p.index(" // Coerce each arg per the sig's coercion rules.")
end = p.index(" // If fewer args than sig expects", start)
print(p[start:end])
PY
printf '%s\n' '--- finalization definition ---'
rg -n -C 20 'fn finalize_node_sqlite_statements|finalize_node_sqlite_statements\(' \
crates/perry-stdlib/src/sqlite/node_db.rs \
crates/perry-stdlib/src/sqlite/*.rs
printf '%s\n' '--- close-time SQLite error handling ---'
rg -n -C 8 'throw_sqlite_error|close.*Err|finalize.*Err|finalize.*unwrap|Statement.*final' crates/perry-stdlib/src/sqlite/node_db.rs crates/perry-stdlib/src/sqlite/node_stmt_session.rsRepository: PerryTS/perry Length of output: 39349 Honor Bun’s The native helper always finalizes tracked statements and ignores 🤖 Prompt for AI Agents |
||
| NativeModSig { | ||
| module: "bun:sqlite", | ||
| has_receiver: true, | ||
| method: "serialize", | ||
| class_filter: Some("Database"), | ||
| runtime: "js_node_sqlite_database_sync_serialize", | ||
| args: &[NA_F64], | ||
| ret: NR_PTR, | ||
| }, | ||
| NativeModSig { | ||
| module: "bun:sqlite", | ||
| has_receiver: true, | ||
| method: "loadExtension", | ||
| class_filter: Some("Database"), | ||
| runtime: "js_node_sqlite_database_sync_load_extension", | ||
| args: &[NA_F64], | ||
| ret: NR_I32, | ||
| }, | ||
| NativeModSig { | ||
| module: "bun:sqlite", | ||
| has_receiver: true, | ||
| method: "transaction", | ||
| class_filter: Some("Database"), | ||
| runtime: "js_bun_sqlite_database_transaction", | ||
| args: &[NA_F64], | ||
| ret: NR_PTR, | ||
| }, | ||
| NativeModSig { | ||
| module: "bun:sqlite", | ||
| has_receiver: true, | ||
| method: "run", | ||
| class_filter: Some("Statement"), | ||
| runtime: "js_node_sqlite_statement_sync_run", | ||
| args: &[NA_VARARGS], | ||
| ret: NR_PTR, | ||
| }, | ||
| NativeModSig { | ||
| module: "bun:sqlite", | ||
| has_receiver: true, | ||
| method: "get", | ||
| class_filter: Some("Statement"), | ||
| runtime: "js_node_sqlite_statement_sync_get", | ||
| args: &[NA_VARARGS], | ||
| ret: NR_F64, | ||
| }, | ||
| NativeModSig { | ||
| module: "bun:sqlite", | ||
| has_receiver: true, | ||
| method: "all", | ||
| class_filter: Some("Statement"), | ||
| runtime: "js_node_sqlite_statement_sync_all", | ||
| args: &[NA_VARARGS], | ||
| ret: NR_PTR, | ||
| }, | ||
| NativeModSig { | ||
| module: "bun:sqlite", | ||
| has_receiver: true, | ||
| method: "values", | ||
| class_filter: Some("Statement"), | ||
| runtime: "js_bun_sqlite_statement_values", | ||
| args: &[NA_VARARGS], | ||
| ret: NR_PTR, | ||
| }, | ||
| NativeModSig { | ||
| module: "bun:sqlite", | ||
| has_receiver: true, | ||
| method: "safeIntegers", | ||
| class_filter: Some("Statement"), | ||
| runtime: "js_bun_sqlite_statement_safe_integers", | ||
| args: &[NA_F64], | ||
| ret: NR_F64, | ||
| }, | ||
| NativeModSig { | ||
| module: "bun:sqlite", | ||
| has_receiver: true, | ||
| method: "finalize", | ||
| class_filter: Some("Statement"), | ||
| runtime: "js_bun_sqlite_statement_finalize", | ||
| args: &[], | ||
| ret: NR_VOID, | ||
| }, | ||
| // ========== node:sqlite ========== | ||
| NativeModSig { | ||
| module: "sqlite", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -73,6 +73,20 @@ pub(super) fn lower_new(ctx: &mut LoweringContext, new_expr: &ast::NewExpr) -> R | |
| } | ||
|
|
||
| if let ast::Expr::Ident(callee_ident) = callee_expr { | ||
| // Keep Bun's `Database` distinct from better-sqlite3's same-named | ||
| // constructor while still allocating the shared native SQLite handle. | ||
| if matches!( | ||
| ctx.lookup_native_module(callee_ident.sym.as_ref()), | ||
| Some(("bun:sqlite", Some("Database"))) | ||
| ) { | ||
| return Ok(Expr::New { | ||
| class_name: "BunSqliteDatabase".to_string(), | ||
| args: lower_optional_args(ctx, new_expr.args.as_deref())?, | ||
| type_args: Vec::new(), | ||
| byte_offset: new_byte_offset, | ||
| cap_args_appended: 0, | ||
| }); | ||
| } | ||
|
Comment on lines
+78
to
+89
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Preserve lexical shadowing before Bun Database lowering. Both constructor paths can use a stale native-module binding after a nested binding shadows the import.
📍 Affects 2 files
🤖 Prompt for AI Agents |
||
| let module_constructor = ctx | ||
| .lookup_native_module(callee_ident.sym.as_ref()) | ||
| .map(|(module_name, method)| { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Register Database properties as receiver methods.
property()creates a module export. It cannot representdb.filenameordb.inTransaction.Register these as zero-argument
Databasemethods and add matching native-table rows. The current declarations can generate incorrect module-level API entries and do not create instance dispatch.🤖 Prompt for AI Agents