Skip to content

Feature request: getOrCreateUnique (or option on upsertUnique) #2994

@joeinnes

Description

@joeinnes

Problem

Right now upsertUnique always mutates, meaning user modifications will be overwritten if the record exists.

There are common cases where the intended semantics are instead: create the record if it doesn’t exist, but if it does exist, just return it unchanged.


Option 1: Separate method

const learnJazzTask = await Task.getOrCreateUnique({
  value: {
    text: "Let's learn some Jazz!",
  },
  unique: "learning-jazz",
  owner: project.$jazz.owner,
});

Pros

  • Very explicit about semantics.
  • Matches naming conventions in other ORMs (getOrCreate, findOrCreate).
  • Keeps upsertUnique narrowly focused.

Cons

  • Duplicates a lot of upsertUnique implementation.
  • Naming is a bit unwieldy (getOrCreateUnique).

Option 2: Extend upsertUnique with a flag

const learnJazzTask = await Task.upsertUnique({
  value: {
    text: "Let's learn some Jazz!",
  },
  unique: "learning-jazz",
  owner: project.$jazz.owner,
  ifExists: "overwrite", // "overwrite" | "return" - defautlt 'overwrite'
});

Pros

  • Reuses existing API surface — no new top-level method.
  • Reduces code duplication internally.
  • Easier to document and maintain one concept instead of two.

Cons

  • Semantics are less immediately clear: upsertUnique is now doing two quite different things depending on an option.
  • Risk of confusion between “update-or-insert” vs. “insert-or-return.”

Option 3: Provide a thin alias (getOrCreateUnique)

const learnJazzTask = await Task.getOrCreate({
  value: {
    text: "Let's learn some Jazz!",
  },
  unique: "learning-jazz",
  owner: project.$jazz.owner,
});

Under the hood this would simply call:

Task.upsertUnique({ ..., ifExists: "return" });

Pros

  • Clearer semantics than Option 2.
  • Minimal implementation cost (just syntactic sugar).
  • Avoids code duplication (unlike Option 1).

Cons

  • Adds another method name to the API surface, which can cause slight cognitive overhead.
  • Still means implementing Option 2, but 'hiding it' behind a different API.

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions