What happened?
Noticed this while validating batch workflows for automation: both batch paths stop on the first failing record, but earlier records are already committed.
observation.addMany inserts earlier observations before throwing on a later invalid record (src/core/observation.ts:94)
observation.updateMany applies earlier updates before throwing on a later missing/invalid id (src/core/observation.ts:318)
That means a failed batch operation leaves the database partially changed, which is surprising for import/update pipelines that expect all-or-nothing behavior.
How to reproduce
I reproduced this with the published API from dist/index.mjs.
addMany partial commit:
node --input-type=module -e "import { mkdtempSync, rmSync } from 'node:fs'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { createObsxa } from './dist/index.mjs'; const dir = mkdtempSync(join(tmpdir(), 'obsxa-audit-')); const db = join(dir, 'audit.db'); const obsxa = await createObsxa({ db }); try { await obsxa.project.add({ id: 'p1', name: 'P1' }); try { await obsxa.observation.addMany([{ projectId: 'p1', title: 'ok1', source: 's1' }, { projectId: 'missing', title: 'bad', source: 's2' }, { projectId: 'p1', title: 'ok2', source: 's3' }]); } catch (e) { console.log('addMany error:', e.message); } const rows = await obsxa.observation.list('p1'); console.log('rows_in_p1', rows.length); console.log('titles', rows.map(r => r.title).join(',')); } finally { await obsxa.close(); rmSync(dir, { recursive: true, force: true }); }"
Observed output:
addMany error: Project \"missing\" not found
rows_in_p1 1
titles ok1
updateMany partial commit:
node --input-type=module -e "import { mkdtempSync, rmSync } from 'node:fs'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { createObsxa } from './dist/index.mjs'; const dir = mkdtempSync(join(tmpdir(), 'obsxa-audit-')); const db = join(dir, 'audit.db'); const obsxa = await createObsxa({ db }); try { await obsxa.project.add({ id: 'p1', name: 'P1' }); const a = await obsxa.observation.add({ projectId: 'p1', title: 'a', source: 's1' }); const b = await obsxa.observation.add({ projectId: 'p1', title: 'b', source: 's2' }); try { await obsxa.observation.updateMany([{ id: a.id, title: 'a-updated' }, { id: 999999, title: 'missing' }, { id: b.id, title: 'b-updated' }]); } catch (e) { console.log('updateMany error:', e.message); } const rows = await obsxa.observation.list('p1'); console.log('titles_after', rows.map(r => r.id + ':' + r.title).join(',')); } finally { await obsxa.close(); rmSync(dir, { recursive: true, force: true }); }"
Observed output:
updateMany error: Observation #999999 not found
titles_after 1:a-updated,2:b
Anything else?
This looks fixable by wrapping each batch method in a single transaction, so either all records are applied or none are.
Related tests that currently cover happy path only: test/index.test.ts:520.
What happened?
Noticed this while validating batch workflows for automation: both batch paths stop on the first failing record, but earlier records are already committed.
observation.addManyinserts earlier observations before throwing on a later invalid record (src/core/observation.ts:94)observation.updateManyapplies earlier updates before throwing on a later missing/invalid id (src/core/observation.ts:318)That means a failed batch operation leaves the database partially changed, which is surprising for import/update pipelines that expect all-or-nothing behavior.
How to reproduce
I reproduced this with the published API from
dist/index.mjs.addManypartial commit:node --input-type=module -e "import { mkdtempSync, rmSync } from 'node:fs'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { createObsxa } from './dist/index.mjs'; const dir = mkdtempSync(join(tmpdir(), 'obsxa-audit-')); const db = join(dir, 'audit.db'); const obsxa = await createObsxa({ db }); try { await obsxa.project.add({ id: 'p1', name: 'P1' }); try { await obsxa.observation.addMany([{ projectId: 'p1', title: 'ok1', source: 's1' }, { projectId: 'missing', title: 'bad', source: 's2' }, { projectId: 'p1', title: 'ok2', source: 's3' }]); } catch (e) { console.log('addMany error:', e.message); } const rows = await obsxa.observation.list('p1'); console.log('rows_in_p1', rows.length); console.log('titles', rows.map(r => r.title).join(',')); } finally { await obsxa.close(); rmSync(dir, { recursive: true, force: true }); }"Observed output:
addMany error: Project \"missing\" not foundrows_in_p1 1titles ok1updateManypartial commit:node --input-type=module -e "import { mkdtempSync, rmSync } from 'node:fs'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { createObsxa } from './dist/index.mjs'; const dir = mkdtempSync(join(tmpdir(), 'obsxa-audit-')); const db = join(dir, 'audit.db'); const obsxa = await createObsxa({ db }); try { await obsxa.project.add({ id: 'p1', name: 'P1' }); const a = await obsxa.observation.add({ projectId: 'p1', title: 'a', source: 's1' }); const b = await obsxa.observation.add({ projectId: 'p1', title: 'b', source: 's2' }); try { await obsxa.observation.updateMany([{ id: a.id, title: 'a-updated' }, { id: 999999, title: 'missing' }, { id: b.id, title: 'b-updated' }]); } catch (e) { console.log('updateMany error:', e.message); } const rows = await obsxa.observation.list('p1'); console.log('titles_after', rows.map(r => r.id + ':' + r.title).join(',')); } finally { await obsxa.close(); rmSync(dir, { recursive: true, force: true }); }"Observed output:
updateMany error: Observation #999999 not foundtitles_after 1:a-updated,2:bAnything else?
This looks fixable by wrapping each batch method in a single transaction, so either all records are applied or none are.
Related tests that currently cover happy path only:
test/index.test.ts:520.