tables.X.create(record) single-argument form silently escapes the caller's request transaction

Open Beginner friendly
#2,453 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
88/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
javascript, node.js
Domain
backend, databases

Research direction

Start in resources/Resource.ts at static create(), specifically the default loadAsInstance !== false two-argument shift branch, and then read resources/transaction.ts to understand how context determines transaction ownership. Preserve the caller's ambient request transaction for the single-argument form while retaining the empty-context fallback; verify that a write followed by a throw rolls back rather than persisting.

Written by the indexing model from the issue text.

Description

tables.X.create(record) — the single-argument form — silently runs outside the caller's request
transaction. A write followed by a throw therefore persists, while the same write issued as
tables.X.create(record, this.getContext()) correctly rolls back. Verified in both directions in one
run.

Mechanism (harper, origin/main)

resources/Resource.ts, static create(), the default (loadAsInstance !== false) "two argument
form, shift" branch:

context = record || {};   // single-arg call: record is undefined -> context = {}
record = idPrefix;

{} is truthy, so the subsequent if (context) { … } else { context = contextStorage.getStore() ?? {}; }
takes the truthy branch and the ambient-context lookup is never reached.

resources/transaction.ts's transaction(context, cb) then sees a context with no .transaction
property, so isJoinableScope is false and it allocates a fresh DatabaseTransaction({ scopeOwned: true })
that commits itself via onComplete / doneWriting: true, independent of the caller's
request-scoped transaction.

Why it matters

It only manifests on the error path, which is exactly where atomicity is supposed to hold: the
caller's transaction rolls back, the escaped write stays committed. Nothing logs, and the
single-argument form reads like a supported convenience shape.

Fix shape

In that shift branch, only fabricate {} when there is genuinely no ambient store — mirroring the
fallback the very next lines already contain:

context = record ?? contextStorage.getStore() ?? {};

Confined to the loadAsInstance !== false default-Table path.

Dominant language
JavaScript
Stars
89
Forks
10
Avg merge
1d 15h
Merged PRs (30d)
196

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from HarperFast/harper

All issues in HarperFast/harper

Similar issues

More JavaScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.