Skip to content

Commit

Permalink
Merge pull request #78 from prisma-idb/74-upsert
Browse files Browse the repository at this point in the history
`upsert()`
  • Loading branch information
WhyAsh5114 authored Dec 10, 2024
2 parents 09e1a6a + 60a340f commit 969fbd9
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { addApplyOrderByClause } from "./utils/_applyOrderByClause";
import { addResolveOrderByKey } from "./utils/_resolveOrderByKey";
import { addResolveSortOrder } from "./utils/_resolveSortOrder";
import { addPreprocessListFields } from "./utils/_preprocessListFields";
import { addUpsertMethod } from "./api/upsert";

export function addIDBModelClass(file: SourceFile, model: Model, models: readonly Model[]) {
const modelClass = file.addClass({
Expand Down Expand Up @@ -59,4 +60,5 @@ export function addIDBModelClass(file: SourceFile, model: Model, models: readonl
addDeleteManyMethod(modelClass, model, models);

addUpdateMethod(modelClass, model);
addUpsertMethod(modelClass, model);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function addUpdateMethod(modelClass: ClassDeclaration, model: Model) {
],
returnType: `Promise<Prisma.Result<Prisma.${model.name}Delegate, Q, 'update'>>`,
statements: (writer) => {
addGetRecord(writer);
addGetRecord(writer, model);
addStringUpdateHandling(writer, model);
addDateTimeUpdateHandling(writer, model);
addBooleanUpdateHandling(writer, model);
Expand All @@ -26,23 +26,37 @@ export function addUpdateMethod(modelClass: ClassDeclaration, model: Model) {
});
}

function addGetRecord(writer: CodeBlockWriter) {
function addGetRecord(writer: CodeBlockWriter, model: Model) {
const pk = JSON.parse(getUniqueIdentifiers(model)[0].keyPath) as string[];
writer
.writeLine(`tx = tx ?? this.client._db.transaction(Array.from(this._getNeededStoresForFind(query)), "readwrite");`)
.writeLine(`const record = await this.findUnique({ where: query.where }, tx);`)
.writeLine(`if (record === null)`)
.block(() => {
writer.writeLine(`tx.abort();`).writeLine(`throw new Error("Record not found");`);
});
})
.writeLine(
`const startKeyPath: PrismaIDBSchema["${model.name}"]["key"] = [${pk.map((field) => `record.${field}, `)}];`,
);
}

function addPutAndReturn(writer: CodeBlockWriter, model: Model) {
const pk = getUniqueIdentifiers(model)[0];
const pk = JSON.parse(getUniqueIdentifiers(model)[0].keyPath) as string[];
writer
.writeLine(
`const endKeyPath: PrismaIDBSchema["${model.name}"]["key"] = [${pk.map((field) => `record.${field}, `)}];`,
)
.writeLine(`for (let i = 0; i < startKeyPath.length; i++)`)
.block(() => {
writer.writeLine(`if (startKeyPath[i] !== endKeyPath[i])`).block(() => {
writer.writeLine(`await tx.objectStore("${model.name}").delete(startKeyPath);`).writeLine(`break;`);
});
})
.writeLine(`const keyPath = await tx.objectStore("${model.name}").put(record);`)
.writeLine(`const recordWithRelations = (await this.findUnique(`)
.block(() => {
writer.writeLine(`...query, where: { ${JSON.parse(pk.keyPath)[0]}: keyPath[0] },`);
// TODO: composite keys
writer.writeLine(`...query, where: { ${pk[0]}: keyPath[0] },`);
})
.writeLine(`, tx))!;`)
.writeLine(`return recordWithRelations as Prisma.Result<Prisma.${model.name}Delegate, Q, "update">;`);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { getUniqueIdentifiers } from "../../../../../helpers/utils";
import { Model } from "../../../../../fileCreators/types";
import { ClassDeclaration, CodeBlockWriter } from "ts-morph";

export function addUpsertMethod(modelClass: ClassDeclaration, model: Model) {
modelClass.addMethod({
name: "upsert",
typeParameters: [{ name: "Q", constraint: `Prisma.Args<Prisma.${model.name}Delegate, "upsert">` }],
isAsync: true,
parameters: [
{ name: "query", type: "Q" },
{ name: "tx", hasQuestionToken: true, type: "IDBUtils.ReadwriteTransactionType" },
],
returnType: `Promise<Prisma.Result<Prisma.${model.name}Delegate, Q, "upsert">>`,
statements: (writer) => {
addGetAndUpsertRecord(writer);
addRefetchAndReturnRecord(writer, model);
},
});
}

function addGetAndUpsertRecord(writer: CodeBlockWriter) {
// TODO: add nested query things to the tx as well (nested writes to other records)
writer
.writeLine(`tx = tx ?? this.client._db.transaction(Array.from(this._getNeededStoresForFind(query)), "readwrite");`)
.writeLine(`let record = await this.findUnique({ where: query.where }, tx);`)
.writeLine(`if (!record) record = await this.create({ data: query.create }, tx);`)
.writeLine(`else record = await this.update({ where: query.where, data: query.update }, tx);`);
}

function addRefetchAndReturnRecord(writer: CodeBlockWriter, model: Model) {
// TODO: composite keys
const pk = JSON.parse(getUniqueIdentifiers(model)[0].keyPath)[0];
const hasRelations = model.fields.some(({ kind }) => kind === "object");

let recordFindQuery = `record = await this.findUniqueOrThrow({ where: { ${pk}: record.${pk} }, select: query.select`;
if (hasRelations) recordFindQuery += ", include: query.include";
recordFindQuery += "});";

writer
.writeLine(recordFindQuery)
.writeLine(`return record as Prisma.Result<Prisma.${model.name}Delegate, Q, "upsert">;`);
}
100 changes: 100 additions & 0 deletions packages/usage/src/prisma/prisma-idb/prisma-idb-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,7 @@ class UserIDBClass extends BaseIDBModelClass {
tx.abort();
throw new Error("Record not found");
}
const startKeyPath: PrismaIDBSchema["User"]["key"] = [record.id];
const stringFields = ["name"] as const;
for (const field of stringFields) {
IDBUtils.handleStringUpdateField(record, field, query.data[field]);
Expand All @@ -778,6 +779,13 @@ class UserIDBClass extends BaseIDBModelClass {
for (const field of intFields) {
IDBUtils.handleIntUpdateField(record, field, query.data[field]);
}
const endKeyPath: PrismaIDBSchema["User"]["key"] = [record.id];
for (let i = 0; i < startKeyPath.length; i++) {
if (startKeyPath[i] !== endKeyPath[i]) {
await tx.objectStore("User").delete(startKeyPath);
break;
}
}
const keyPath = await tx.objectStore("User").put(record);
const recordWithRelations = (await this.findUnique(
{
Expand All @@ -788,6 +796,18 @@ class UserIDBClass extends BaseIDBModelClass {
))!;
return recordWithRelations as Prisma.Result<Prisma.UserDelegate, Q, "update">;
}

async upsert<Q extends Prisma.Args<Prisma.UserDelegate, "upsert">>(
query: Q,
tx?: IDBUtils.ReadwriteTransactionType,
): Promise<Prisma.Result<Prisma.UserDelegate, Q, "upsert">> {
tx = tx ?? this.client._db.transaction(Array.from(this._getNeededStoresForFind(query)), "readwrite");
let record = await this.findUnique({ where: query.where }, tx);
if (!record) record = await this.create({ data: query.create }, tx);
else record = await this.update({ where: query.where, data: query.update }, tx);
record = await this.findUniqueOrThrow({ where: { id: record.id }, select: query.select, include: query.include });
return record as Prisma.Result<Prisma.UserDelegate, Q, "upsert">;
}
}

class ProfileIDBClass extends BaseIDBModelClass {
Expand Down Expand Up @@ -1236,6 +1256,7 @@ class ProfileIDBClass extends BaseIDBModelClass {
tx.abort();
throw new Error("Record not found");
}
const startKeyPath: PrismaIDBSchema["Profile"]["key"] = [record.id];
const stringFields = ["bio"] as const;
for (const field of stringFields) {
IDBUtils.handleStringUpdateField(record, field, query.data[field]);
Expand All @@ -1244,6 +1265,13 @@ class ProfileIDBClass extends BaseIDBModelClass {
for (const field of intFields) {
IDBUtils.handleIntUpdateField(record, field, query.data[field]);
}
const endKeyPath: PrismaIDBSchema["Profile"]["key"] = [record.id];
for (let i = 0; i < startKeyPath.length; i++) {
if (startKeyPath[i] !== endKeyPath[i]) {
await tx.objectStore("Profile").delete(startKeyPath);
break;
}
}
const keyPath = await tx.objectStore("Profile").put(record);
const recordWithRelations = (await this.findUnique(
{
Expand All @@ -1254,6 +1282,18 @@ class ProfileIDBClass extends BaseIDBModelClass {
))!;
return recordWithRelations as Prisma.Result<Prisma.ProfileDelegate, Q, "update">;
}

async upsert<Q extends Prisma.Args<Prisma.ProfileDelegate, "upsert">>(
query: Q,
tx?: IDBUtils.ReadwriteTransactionType,
): Promise<Prisma.Result<Prisma.ProfileDelegate, Q, "upsert">> {
tx = tx ?? this.client._db.transaction(Array.from(this._getNeededStoresForFind(query)), "readwrite");
let record = await this.findUnique({ where: query.where }, tx);
if (!record) record = await this.create({ data: query.create }, tx);
else record = await this.update({ where: query.where, data: query.update }, tx);
record = await this.findUniqueOrThrow({ where: { id: record.id }, select: query.select, include: query.include });
return record as Prisma.Result<Prisma.ProfileDelegate, Q, "upsert">;
}
}

class PostIDBClass extends BaseIDBModelClass {
Expand Down Expand Up @@ -1875,6 +1915,7 @@ class PostIDBClass extends BaseIDBModelClass {
tx.abort();
throw new Error("Record not found");
}
const startKeyPath: PrismaIDBSchema["Post"]["key"] = [record.id];
const stringFields = ["title"] as const;
for (const field of stringFields) {
IDBUtils.handleStringUpdateField(record, field, query.data[field]);
Expand All @@ -1887,6 +1928,13 @@ class PostIDBClass extends BaseIDBModelClass {
for (const field of listFields) {
IDBUtils.handleScalarListUpdateField(record, field, query.data[field]);
}
const endKeyPath: PrismaIDBSchema["Post"]["key"] = [record.id];
for (let i = 0; i < startKeyPath.length; i++) {
if (startKeyPath[i] !== endKeyPath[i]) {
await tx.objectStore("Post").delete(startKeyPath);
break;
}
}
const keyPath = await tx.objectStore("Post").put(record);
const recordWithRelations = (await this.findUnique(
{
Expand All @@ -1897,6 +1945,18 @@ class PostIDBClass extends BaseIDBModelClass {
))!;
return recordWithRelations as Prisma.Result<Prisma.PostDelegate, Q, "update">;
}

async upsert<Q extends Prisma.Args<Prisma.PostDelegate, "upsert">>(
query: Q,
tx?: IDBUtils.ReadwriteTransactionType,
): Promise<Prisma.Result<Prisma.PostDelegate, Q, "upsert">> {
tx = tx ?? this.client._db.transaction(Array.from(this._getNeededStoresForFind(query)), "readwrite");
let record = await this.findUnique({ where: query.where }, tx);
if (!record) record = await this.create({ data: query.create }, tx);
else record = await this.update({ where: query.where, data: query.update }, tx);
record = await this.findUniqueOrThrow({ where: { id: record.id }, select: query.select, include: query.include });
return record as Prisma.Result<Prisma.PostDelegate, Q, "upsert">;
}
}

class CommentIDBClass extends BaseIDBModelClass {
Expand Down Expand Up @@ -2442,6 +2502,7 @@ class CommentIDBClass extends BaseIDBModelClass {
tx.abort();
throw new Error("Record not found");
}
const startKeyPath: PrismaIDBSchema["Comment"]["key"] = [record.id];
const stringFields = ["id", "text"] as const;
for (const field of stringFields) {
IDBUtils.handleStringUpdateField(record, field, query.data[field]);
Expand All @@ -2450,6 +2511,13 @@ class CommentIDBClass extends BaseIDBModelClass {
for (const field of intFields) {
IDBUtils.handleIntUpdateField(record, field, query.data[field]);
}
const endKeyPath: PrismaIDBSchema["Comment"]["key"] = [record.id];
for (let i = 0; i < startKeyPath.length; i++) {
if (startKeyPath[i] !== endKeyPath[i]) {
await tx.objectStore("Comment").delete(startKeyPath);
break;
}
}
const keyPath = await tx.objectStore("Comment").put(record);
const recordWithRelations = (await this.findUnique(
{
Expand All @@ -2460,6 +2528,18 @@ class CommentIDBClass extends BaseIDBModelClass {
))!;
return recordWithRelations as Prisma.Result<Prisma.CommentDelegate, Q, "update">;
}

async upsert<Q extends Prisma.Args<Prisma.CommentDelegate, "upsert">>(
query: Q,
tx?: IDBUtils.ReadwriteTransactionType,
): Promise<Prisma.Result<Prisma.CommentDelegate, Q, "upsert">> {
tx = tx ?? this.client._db.transaction(Array.from(this._getNeededStoresForFind(query)), "readwrite");
let record = await this.findUnique({ where: query.where }, tx);
if (!record) record = await this.create({ data: query.create }, tx);
else record = await this.update({ where: query.where, data: query.update }, tx);
record = await this.findUniqueOrThrow({ where: { id: record.id }, select: query.select, include: query.include });
return record as Prisma.Result<Prisma.CommentDelegate, Q, "upsert">;
}
}

class AllFieldScalarTypesIDBClass extends BaseIDBModelClass {
Expand Down Expand Up @@ -2954,6 +3034,7 @@ class AllFieldScalarTypesIDBClass extends BaseIDBModelClass {
tx.abort();
throw new Error("Record not found");
}
const startKeyPath: PrismaIDBSchema["AllFieldScalarTypes"]["key"] = [record.id];
const stringFields = ["string"] as const;
for (const field of stringFields) {
IDBUtils.handleStringUpdateField(record, field, query.data[field]);
Expand All @@ -2978,6 +3059,13 @@ class AllFieldScalarTypesIDBClass extends BaseIDBModelClass {
for (const field of listFields) {
IDBUtils.handleScalarListUpdateField(record, field, query.data[field]);
}
const endKeyPath: PrismaIDBSchema["AllFieldScalarTypes"]["key"] = [record.id];
for (let i = 0; i < startKeyPath.length; i++) {
if (startKeyPath[i] !== endKeyPath[i]) {
await tx.objectStore("AllFieldScalarTypes").delete(startKeyPath);
break;
}
}
const keyPath = await tx.objectStore("AllFieldScalarTypes").put(record);
const recordWithRelations = (await this.findUnique(
{
Expand All @@ -2988,4 +3076,16 @@ class AllFieldScalarTypesIDBClass extends BaseIDBModelClass {
))!;
return recordWithRelations as Prisma.Result<Prisma.AllFieldScalarTypesDelegate, Q, "update">;
}

async upsert<Q extends Prisma.Args<Prisma.AllFieldScalarTypesDelegate, "upsert">>(
query: Q,
tx?: IDBUtils.ReadwriteTransactionType,
): Promise<Prisma.Result<Prisma.AllFieldScalarTypesDelegate, Q, "upsert">> {
tx = tx ?? this.client._db.transaction(Array.from(this._getNeededStoresForFind(query)), "readwrite");
let record = await this.findUnique({ where: query.where }, tx);
if (!record) record = await this.create({ data: query.create }, tx);
else record = await this.update({ where: query.where, data: query.update }, tx);
record = await this.findUniqueOrThrow({ where: { id: record.id }, select: query.select });
return record as Prisma.Result<Prisma.AllFieldScalarTypesDelegate, Q, "upsert">;
}
}
24 changes: 24 additions & 0 deletions packages/usage/tests/modelQueries/upsert.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { test } from "../fixtures";
import { expectQueryToSucceed } from "../queryRunnerHelper";

test("upsert_ChangeId_SuccessfullyUpdatesRecord", async ({ page }) => {
await expectQueryToSucceed({
page,
model: "user",
operation: "upsert",
query: { where: { id: 1 }, create: { id: 1, name: "John" }, update: { name: "Alice" } },
});

await expectQueryToSucceed({
page,
model: "user",
operation: "upsert",
query: { where: { id: 1 }, create: { name: "Alice" }, update: { id: 3 } },
});

await expectQueryToSucceed({
page,
model: "user",
operation: "findMany",
});
});

0 comments on commit 969fbd9

Please sign in to comment.