Skip to content

sqlite3 command always fails — dist/commands/sqlite3/worker.js missing from published package #165

@subsetpark

Description

@subsetpark

Bug

sqlite3 :memory: "SELECT 1+1;" returns exit code 1 with stderr sqlite3: Expected ArrayBuffer for the first argument. on every invocation. Affects at least 2.13.1 and 2.14.0.

Reproducer

import { Bash } from 'just-bash'
const bash = new Bash({ files: {}, cwd: '/' })
const result = await bash.exec("sqlite3 :memory: 'SELECT 1+1;'")
// stdout:   ''
// stderr:   'sqlite3: Expected ArrayBuffer for the first argument.\n'
// exitCode: 1

Root cause

The sqlite3 command resolves its worker file via a function in the bundled chunk (paraphrased):

function resolveWorkerPath() {
  const dir = dirname(fileURLToPath(import.meta.url)) // dist/bundle/chunks/
  const p1 = join(dir, '../../commands/sqlite3/worker.js')  // dist/commands/sqlite3/worker.js
  if (existsSync(p1)) return p1
  const p2 = join(dir, 'worker.js')                         // dist/bundle/chunks/worker.js  ← WRONG
  if (existsSync(p2)) return p2
  const p3 = join(dir, '../../../dist/commands/sqlite3/worker.js')
  if (existsSync(p3)) return p3
  throw new Error("sqlite3 worker not found. Run 'pnpm build' to compile the worker.")
}

p1 (dist/commands/sqlite3/worker.js) does not exist in the published package — only dist/commands/sqlite3/worker.d.ts is present. So the resolver falls through to p2, which is dist/bundle/chunks/worker.js — the Python3 worker.

The Python3 worker's SyncBackend constructor immediately does new Int32Array(sharedBuffer) where sharedBuffer is undefined for sqlite3 inputs (sqlite3 passes dbBuffer, not sharedBuffer). This throws the "Expected ArrayBuffer" error, which is caught and surfaced as { success: false, error: "Expected ArrayBuffer for the first argument." }.

Smoking gun

dist/commands/sqlite3/
  worker.d.ts   ✅ present in published package
  worker.js     ❌ missing from published package

The type declaration exists, confirming worker.js was intentional — it was accidentally omitted from the build or publish step.

Workaround

Copy the following to dist/commands/sqlite3/worker.js after install (a minimal implementation using the already-bundled sql.js):

import { workerData, parentPort } from 'node:worker_threads'
import { createRequire } from 'node:module'
const require = createRequire(import.meta.url)

async function main() {
  const { protocolToken, dbBuffer, sql, options } = workerData
  const SQL = await require('sql.js')()
  const db = dbBuffer ? new SQL.Database(dbBuffer) : new SQL.Database()
  const results = []
  let hasModifications = false
  try {
    for (const r of db.exec(sql))
      results.push({ columns: r.columns, rows: r.values })
    hasModifications = db.getRowsModified() > 0
  } catch (e) {
    if (options?.bail) {
      db.close()
      return parentPort.postMessage({ protocolToken, success: false, error: e.message })
    }
    results.push({ type: 'error', error: e.message })
  }
  const updatedBuffer = (hasModifications && dbBuffer) ? db.export() : null
  db.close()
  parentPort.postMessage({ protocolToken, success: true, results, hasModifications, dbBuffer: updatedBuffer })
}

main().catch(e =>
  parentPort.postMessage({ protocolToken: workerData?.protocolToken, success: false, error: e.message })
)

Note: this workaround omits any security sandbox that the real worker likely has — the Python3 worker uses WorkerDefenseInDepth, so the real sqlite3 worker probably does too.

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