Feature request: `getOrCreateUnique` (or option on `upsertUnique`)
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
upsertUniquenarrowly focused.
Cons
- Duplicates a lot of
upsertUniqueimplementation. - 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:
upsertUniqueis 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.
After some intensive use of loadUnique we found that it is possible to get into some nasty race condition situations.
We should provide a loadOrCreateUnique API that:
- doesn't accept a
resolveparam, as the failure on loading a child could lead to a double createUnique - handle race conditions when running multiple
loadOrCreateUniquein parallel
We should also deprecate, and remove upsertUnique because it is better to focus in one safe API.
I prefer load as prefix instead of get because it hints more on the async nature of the API.