Read the source and its declared rule
The original node batch lists A→B then rootB under an immediate self-FK. B exists in the complete input but not yet in the target whenA is first written. The approved two-pass load allocates nullable-link rows, then updates references and commits only the completed transaction.
| Record | Input JSON |
|---|---|
| Base input | {"nodes":[{"id":"A","parent":"B"},{"id":"B","parent":null}]} |
Compare the actual paths
Correct target contains A→B and rootB. If B is absent from the batch, the later UPDATE actually fails 787 and rollback removes the provisional allocations too.
| Path | Output JSON |
|---|---|
| Misleading path | {"error":{"code":787},"nodes":[]} |
| Corrected path | {"error":null,"nodes":[{"id":"A","parent_id":"B"},{"id":"B","parent_id":null}]} |
import {DatabaseSync} from 'node:sqlite';
import {TextDecoder} from 'node:util';
import {inflateSync} from 'node:zlib';
import {posix} from 'node:path';
import {createHash} from 'node:crypto';
const rows=(db,sql)=>db.prepare(sql).all().map(r=>({...r}));
const failed=(action)=>{try{action();return null;}catch(e){return {code:e.errcode??e.code??e.name};}};
import assert from 'node:assert/strict';
const run=function run(i,corrected=true){
const db=new DatabaseSync(':memory:');try{db.exec('PRAGMA foreign_keys=ON;CREATE TABLE node(id TEXT PRIMARY KEY,parent_id TEXT REFERENCES node(id));BEGIN;');
const error=failed(()=>{for(const n of i.nodes)db.prepare('INSERT INTO node VALUES(?,?)').run(n.id,corrected?null:n.parent);
if(corrected)for(const n of i.nodes)db.prepare('UPDATE node SET parent_id=? WHERE id=?').run(n.parent,n.id);
db.exec('COMMIT;');});
if(error)db.exec('ROLLBACK;');return {error,nodes:rows(db,'SELECT * FROM node ORDER BY id')};
}finally{db.close();}
};
const cases=[{"label":"misleading base","input":{"nodes":[{"id":"A","parent":"B"},{"id":"B","parent":null}]},"expected":{"error":{"code":787},"nodes":[]},"corrected":false},{"label":"corrected base","input":{"nodes":[{"id":"A","parent":"B"},{"id":"B","parent":null}]},"expected":{"error":null,"nodes":[{"id":"A","parent_id":"B"},{"id":"B","parent_id":null}]},"corrected":true},{"label":"missing later reference rolls back allocations","input":{"nodes":[{"id":"A","parent":"B"}]},"expected":{"error":{"code":787},"nodes":[]},"corrected":true},{"label":"independent root","input":{"nodes":[{"id":"A","parent":null}]},"expected":{"error":null,"nodes":[{"id":"A","parent_id":null}]},"corrected":true}];
for(const test of cases){
const actual=run(test.input,test.corrected);
assert.deepEqual(actual,test.expected,test.label);
console.log(test.label,actual);
}
Check boundaries before the next load
MissingB leaves an empty committed target. An independent rootA commits with its null parent.
| Case | Input JSON | Expected output JSON |
|---|---|---|
| Missing later parent | {"nodes":[{"id":"A","parent":"B"}]} | {"error":{"code":787},"nodes":[]} |
| Independent root record | {"nodes":[{"id":"A","parent":null}]} | {"error":null,"nodes":[{"id":"A","parent_id":null}]} |
Can provisional allocations be called a completed load before references pass?
Show the worked answer
No. In the missingB case the reference update fails and all provisional rows roll back. Complete links before commit.