Make device row writes atomic
This commit is contained in:
@@ -44,6 +44,8 @@ For insertion, a circuit-level cell creates a reserve circuit directly after the
|
|||||||
|
|
||||||
For deletion, a circuit-level cell targets the complete circuit and a device-level cell targets only the device row. Deleting the last device requires an explicit choice between keeping the empty circuit as reserve, deleting the complete circuit, or cancelling. Delete commands are undoable and never renumber remaining circuits.
|
For deletion, a circuit-level cell targets the complete circuit and a device-level cell targets only the device row. Deleting the last device requires an explicit choice between keeping the empty circuit as reserve, deleting the complete circuit, or cancelling. Delete commands are undoable and never renumber remaining circuits.
|
||||||
|
|
||||||
|
Creating or deleting a device row updates the row and the circuit reserve state in one SQLite transaction.
|
||||||
|
|
||||||
## Equipment Identifier Validation
|
## Equipment Identifier Validation
|
||||||
|
|
||||||
Equipment identifiers are compared case-insensitively after trimming whitespace. A conflicting edit is highlighted before submission and cannot be committed. Existing conflicts are highlighted in the grid. Validation errors remain dismissible above the editor instead of replacing the complete workspace.
|
Equipment identifiers are compared case-insensitively after trimming whitespace. A conflicting edit is highlighted before submission and cannot be committed. Existing conflicts are highlighted in the grid. Validation errors remain dismissible above the editor instead of replacing the complete workspace.
|
||||||
|
|||||||
@@ -26,6 +26,13 @@ export interface CircuitDeviceRowUpdateInput {
|
|||||||
overriddenFields?: string;
|
overriddenFields?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CircuitDeviceRowCreateInput extends CircuitDeviceRowUpdateInput {
|
||||||
|
circuitId: string;
|
||||||
|
linkedProjectDeviceId?: string;
|
||||||
|
legacyConsumerId?: string;
|
||||||
|
sortOrder: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CircuitDeviceRowsBulkMoveInput {
|
export interface CircuitDeviceRowsBulkMoveInput {
|
||||||
rows: Array<{ id: string; expectedCircuitId: string }>;
|
rows: Array<{ id: string; expectedCircuitId: string }>;
|
||||||
targetCircuitId?: string;
|
targetCircuitId?: string;
|
||||||
@@ -60,6 +67,16 @@ function toUpdateValues(input: CircuitDeviceRowUpdateInput) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toCreateValues(id: string, input: CircuitDeviceRowCreateInput) {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
circuitId: input.circuitId,
|
||||||
|
legacyConsumerId: input.legacyConsumerId ?? null,
|
||||||
|
sortOrder: input.sortOrder,
|
||||||
|
...toUpdateValues(input),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export class CircuitDeviceRowRepository {
|
export class CircuitDeviceRowRepository {
|
||||||
async findById(rowId: string) {
|
async findById(rowId: string) {
|
||||||
const [row] = await db.select().from(circuitDeviceRows).where(eq(circuitDeviceRows.id, rowId)).limit(1);
|
const [row] = await db.select().from(circuitDeviceRows).where(eq(circuitDeviceRows.id, rowId)).limit(1);
|
||||||
@@ -141,51 +158,43 @@ export class CircuitDeviceRowRepository {
|
|||||||
.where(and(eq(circuitLists.projectId, projectId), inArray(circuitDeviceRows.id, rowIds)));
|
.where(and(eq(circuitLists.projectId, projectId), inArray(circuitDeviceRows.id, rowIds)));
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(input: {
|
async create(input: CircuitDeviceRowCreateInput) {
|
||||||
circuitId: string;
|
|
||||||
linkedProjectDeviceId?: string;
|
|
||||||
legacyConsumerId?: string;
|
|
||||||
sortOrder: number;
|
|
||||||
name: string;
|
|
||||||
displayName: string;
|
|
||||||
phaseType?: string;
|
|
||||||
connectionKind?: string;
|
|
||||||
costGroup?: string;
|
|
||||||
category?: string;
|
|
||||||
level?: string;
|
|
||||||
roomId?: string;
|
|
||||||
roomNumberSnapshot?: string;
|
|
||||||
roomNameSnapshot?: string;
|
|
||||||
quantity: number;
|
|
||||||
powerPerUnit: number;
|
|
||||||
simultaneityFactor: number;
|
|
||||||
cosPhi?: number;
|
|
||||||
remark?: string;
|
|
||||||
overriddenFields?: string;
|
|
||||||
}) {
|
|
||||||
const id = crypto.randomUUID();
|
const id = crypto.randomUUID();
|
||||||
await db.insert(circuitDeviceRows).values({
|
await db.insert(circuitDeviceRows).values(toCreateValues(id, input));
|
||||||
id,
|
return id;
|
||||||
circuitId: input.circuitId,
|
}
|
||||||
linkedProjectDeviceId: input.linkedProjectDeviceId ?? null,
|
|
||||||
legacyConsumerId: input.legacyConsumerId ?? null,
|
createInCircuitTransactional(
|
||||||
sortOrder: input.sortOrder,
|
input: Omit<CircuitDeviceRowCreateInput, "sortOrder"> & { sortOrder?: number }
|
||||||
name: input.name,
|
) {
|
||||||
displayName: input.displayName,
|
const id = crypto.randomUUID();
|
||||||
phaseType: input.phaseType ?? null,
|
db.transaction((tx) => {
|
||||||
connectionKind: input.connectionKind ?? null,
|
const [circuit] = tx
|
||||||
costGroup: input.costGroup ?? null,
|
.select({ id: circuits.id })
|
||||||
category: input.category ?? null,
|
.from(circuits)
|
||||||
level: input.level ?? null,
|
.where(eq(circuits.id, input.circuitId))
|
||||||
roomId: input.roomId ?? null,
|
.limit(1)
|
||||||
roomNumberSnapshot: input.roomNumberSnapshot ?? null,
|
.all();
|
||||||
roomNameSnapshot: input.roomNameSnapshot ?? null,
|
if (!circuit) {
|
||||||
quantity: input.quantity,
|
throw new Error("Der Stromkreis ist ungültig.");
|
||||||
powerPerUnit: input.powerPerUnit,
|
}
|
||||||
simultaneityFactor: input.simultaneityFactor,
|
|
||||||
cosPhi: input.cosPhi ?? null,
|
const existingRows = tx
|
||||||
remark: input.remark ?? null,
|
.select({ sortOrder: circuitDeviceRows.sortOrder })
|
||||||
overriddenFields: input.overriddenFields ?? null,
|
.from(circuitDeviceRows)
|
||||||
|
.where(eq(circuitDeviceRows.circuitId, input.circuitId))
|
||||||
|
.all();
|
||||||
|
const lastSortOrder = existingRows.reduce(
|
||||||
|
(highest, row) => Math.max(highest, row.sortOrder),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const sortOrder = input.sortOrder ?? lastSortOrder + 10;
|
||||||
|
|
||||||
|
tx
|
||||||
|
.insert(circuitDeviceRows)
|
||||||
|
.values(toCreateValues(id, { ...input, sortOrder }))
|
||||||
|
.run();
|
||||||
|
tx.update(circuits).set({ isReserve: 0 }).where(eq(circuits.id, input.circuitId)).run();
|
||||||
});
|
});
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@@ -262,6 +271,44 @@ export class CircuitDeviceRowRepository {
|
|||||||
await db.delete(circuitDeviceRows).where(eq(circuitDeviceRows.id, rowId));
|
await db.delete(circuitDeviceRows).where(eq(circuitDeviceRows.id, rowId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteFromCircuitTransactional(rowId: string, expectedCircuitId: string) {
|
||||||
|
db.transaction((tx) => {
|
||||||
|
const [row] = tx
|
||||||
|
.select({ id: circuitDeviceRows.id, circuitId: circuitDeviceRows.circuitId })
|
||||||
|
.from(circuitDeviceRows)
|
||||||
|
.where(eq(circuitDeviceRows.id, rowId))
|
||||||
|
.limit(1)
|
||||||
|
.all();
|
||||||
|
if (!row || row.circuitId !== expectedCircuitId) {
|
||||||
|
throw new Error("Die Gerätezeile wurde vor dem Löschen verändert.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = tx
|
||||||
|
.delete(circuitDeviceRows)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(circuitDeviceRows.id, rowId),
|
||||||
|
eq(circuitDeviceRows.circuitId, expectedCircuitId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.run();
|
||||||
|
if (result.changes !== 1) {
|
||||||
|
throw new Error("Die Gerätezeile konnte nicht gelöscht werden.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const remainingRows = tx
|
||||||
|
.select({ id: circuitDeviceRows.id })
|
||||||
|
.from(circuitDeviceRows)
|
||||||
|
.where(eq(circuitDeviceRows.circuitId, expectedCircuitId))
|
||||||
|
.all();
|
||||||
|
tx
|
||||||
|
.update(circuits)
|
||||||
|
.set({ isReserve: remainingRows.length === 0 ? 1 : 0 })
|
||||||
|
.where(eq(circuits.id, expectedCircuitId))
|
||||||
|
.run();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async moveToCircuit(rowId: string, targetCircuitId: string, sortOrder: number) {
|
async moveToCircuit(rowId: string, targetCircuitId: string, sortOrder: number) {
|
||||||
await db
|
await db
|
||||||
.update(circuitDeviceRows)
|
.update(circuitDeviceRows)
|
||||||
|
|||||||
@@ -171,11 +171,10 @@ export class CircuitWriteService {
|
|||||||
}
|
}
|
||||||
await this.assertValidLinkedProjectDevice(circuitId, input.linkedProjectDeviceId);
|
await this.assertValidLinkedProjectDevice(circuitId, input.linkedProjectDeviceId);
|
||||||
|
|
||||||
const existingRows = await this.deviceRowRepository.countByCircuit(circuitId);
|
const rowId = this.deviceRowRepository.createInCircuitTransactional({
|
||||||
const rowId = await this.deviceRowRepository.create({
|
|
||||||
circuitId,
|
circuitId,
|
||||||
linkedProjectDeviceId: input.linkedProjectDeviceId,
|
linkedProjectDeviceId: input.linkedProjectDeviceId,
|
||||||
sortOrder: input.sortOrder ?? (existingRows + 1) * 10,
|
sortOrder: input.sortOrder,
|
||||||
name: input.name,
|
name: input.name,
|
||||||
displayName: input.displayName,
|
displayName: input.displayName,
|
||||||
phaseType: input.phaseType,
|
phaseType: input.phaseType,
|
||||||
@@ -194,29 +193,6 @@ export class CircuitWriteService {
|
|||||||
overriddenFields: input.overriddenFields,
|
overriddenFields: input.overriddenFields,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reserve circuits become active as soon as at least one device row exists.
|
|
||||||
if (Boolean(circuit.isReserve)) {
|
|
||||||
await this.circuitRepository.update(circuit.id, {
|
|
||||||
sectionId: circuit.sectionId,
|
|
||||||
equipmentIdentifier: circuit.equipmentIdentifier,
|
|
||||||
displayName: circuit.displayName ?? undefined,
|
|
||||||
sortOrder: circuit.sortOrder,
|
|
||||||
protectionType: circuit.protectionType ?? undefined,
|
|
||||||
protectionRatedCurrent: circuit.protectionRatedCurrent ?? undefined,
|
|
||||||
protectionCharacteristic: circuit.protectionCharacteristic ?? undefined,
|
|
||||||
cableType: circuit.cableType ?? undefined,
|
|
||||||
cableCrossSection: circuit.cableCrossSection ?? undefined,
|
|
||||||
cableLength: circuit.cableLength ?? undefined,
|
|
||||||
rcdAssignment: circuit.rcdAssignment ?? undefined,
|
|
||||||
terminalDesignation: circuit.terminalDesignation ?? undefined,
|
|
||||||
voltage: circuit.voltage ?? undefined,
|
|
||||||
controlRequirement: circuit.controlRequirement ?? undefined,
|
|
||||||
status: circuit.status ?? undefined,
|
|
||||||
isReserve: false,
|
|
||||||
remark: circuit.remark ?? undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.deviceRowRepository.findById(rowId);
|
return this.deviceRowRepository.findById(rowId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,30 +248,7 @@ export class CircuitWriteService {
|
|||||||
if (!circuit) {
|
if (!circuit) {
|
||||||
throw new Error("Invalid circuit id.");
|
throw new Error("Invalid circuit id.");
|
||||||
}
|
}
|
||||||
await this.deviceRowRepository.delete(rowId);
|
this.deviceRowRepository.deleteFromCircuitTransactional(rowId, circuit.id);
|
||||||
const remaining = await this.deviceRowRepository.countByCircuit(current.circuitId);
|
|
||||||
// When last row is removed, keep circuit and mark it reserve instead of deleting it.
|
|
||||||
if (remaining === 0) {
|
|
||||||
await this.circuitRepository.update(circuit.id, {
|
|
||||||
sectionId: circuit.sectionId,
|
|
||||||
equipmentIdentifier: circuit.equipmentIdentifier,
|
|
||||||
displayName: circuit.displayName ?? undefined,
|
|
||||||
sortOrder: circuit.sortOrder,
|
|
||||||
protectionType: circuit.protectionType ?? undefined,
|
|
||||||
protectionRatedCurrent: circuit.protectionRatedCurrent ?? undefined,
|
|
||||||
protectionCharacteristic: circuit.protectionCharacteristic ?? undefined,
|
|
||||||
cableType: circuit.cableType ?? undefined,
|
|
||||||
cableCrossSection: circuit.cableCrossSection ?? undefined,
|
|
||||||
cableLength: circuit.cableLength ?? undefined,
|
|
||||||
rcdAssignment: circuit.rcdAssignment ?? undefined,
|
|
||||||
terminalDesignation: circuit.terminalDesignation ?? undefined,
|
|
||||||
voltage: circuit.voltage ?? undefined,
|
|
||||||
controlRequirement: circuit.controlRequirement ?? undefined,
|
|
||||||
status: circuit.status ?? undefined,
|
|
||||||
isReserve: true,
|
|
||||||
remark: circuit.remark ?? undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async moveDeviceRow(rowId: string, input: MoveCircuitDeviceRowInput) {
|
async moveDeviceRow(rowId: string, input: MoveCircuitDeviceRowInput) {
|
||||||
|
|||||||
@@ -117,17 +117,14 @@ describe("circuit write service rules", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("deleting last device row keeps circuit and sets reserve", async () => {
|
it("deleting last device row keeps circuit and sets reserve", async () => {
|
||||||
let reserveFlag = false;
|
let transactionalDelete: { rowId: string; circuitId: string } | undefined;
|
||||||
const service = new CircuitWriteService({
|
const service = new CircuitWriteService({
|
||||||
deviceRowRepository: {
|
deviceRowRepository: {
|
||||||
async findById() {
|
async findById() {
|
||||||
return { id: "r1", circuitId: "c1" } as never;
|
return { id: "r1", circuitId: "c1" } as never;
|
||||||
},
|
},
|
||||||
async delete() {
|
deleteFromCircuitTransactional(rowId: string, circuitId: string) {
|
||||||
return;
|
transactionalDelete = { rowId, circuitId };
|
||||||
},
|
|
||||||
async countByCircuit() {
|
|
||||||
return 0;
|
|
||||||
},
|
},
|
||||||
} as never,
|
} as never,
|
||||||
circuitRepository: {
|
circuitRepository: {
|
||||||
@@ -141,18 +138,22 @@ describe("circuit write service rules", () => {
|
|||||||
isReserve: 0,
|
isReserve: 0,
|
||||||
} as never;
|
} as never;
|
||||||
},
|
},
|
||||||
async update(_id: string, payload: { isReserve: boolean }) {
|
|
||||||
reserveFlag = payload.isReserve;
|
|
||||||
},
|
|
||||||
} as never,
|
} as never,
|
||||||
});
|
});
|
||||||
|
|
||||||
await service.deleteDeviceRow("r1");
|
await service.deleteDeviceRow("r1");
|
||||||
assert.equal(reserveFlag, true);
|
assert.deepEqual(transactionalDelete, { rowId: "r1", circuitId: "c1" });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("creating device row in reserve circuit clears reserve status", async () => {
|
it("creating device row in reserve circuit clears reserve status", async () => {
|
||||||
let reserveFlag = true;
|
let transactionalCreate:
|
||||||
|
| {
|
||||||
|
circuitId: string;
|
||||||
|
sortOrder?: number;
|
||||||
|
name: string;
|
||||||
|
displayName: string;
|
||||||
|
}
|
||||||
|
| undefined;
|
||||||
const service = new CircuitWriteService({
|
const service = new CircuitWriteService({
|
||||||
circuitRepository: {
|
circuitRepository: {
|
||||||
async findById() {
|
async findById() {
|
||||||
@@ -165,15 +166,10 @@ describe("circuit write service rules", () => {
|
|||||||
isReserve: 1,
|
isReserve: 1,
|
||||||
} as never;
|
} as never;
|
||||||
},
|
},
|
||||||
async update(_id: string, payload: { isReserve: boolean }) {
|
|
||||||
reserveFlag = payload.isReserve;
|
|
||||||
},
|
|
||||||
} as never,
|
} as never,
|
||||||
deviceRowRepository: {
|
deviceRowRepository: {
|
||||||
async countByCircuit() {
|
createInCircuitTransactional(input: typeof transactionalCreate) {
|
||||||
return 0;
|
transactionalCreate = input;
|
||||||
},
|
|
||||||
async create() {
|
|
||||||
return "row1";
|
return "row1";
|
||||||
},
|
},
|
||||||
async findById() {
|
async findById() {
|
||||||
@@ -196,7 +192,27 @@ describe("circuit write service rules", () => {
|
|||||||
powerPerUnit: 1,
|
powerPerUnit: 1,
|
||||||
simultaneityFactor: 1,
|
simultaneityFactor: 1,
|
||||||
});
|
});
|
||||||
assert.equal(reserveFlag, false);
|
assert.deepEqual(transactionalCreate, {
|
||||||
|
circuitId: "c1",
|
||||||
|
linkedProjectDeviceId: undefined,
|
||||||
|
sortOrder: undefined,
|
||||||
|
name: "Load",
|
||||||
|
displayName: "Load",
|
||||||
|
phaseType: undefined,
|
||||||
|
connectionKind: undefined,
|
||||||
|
costGroup: undefined,
|
||||||
|
category: undefined,
|
||||||
|
level: undefined,
|
||||||
|
roomId: undefined,
|
||||||
|
roomNumberSnapshot: undefined,
|
||||||
|
roomNameSnapshot: undefined,
|
||||||
|
quantity: 1,
|
||||||
|
powerPerUnit: 1,
|
||||||
|
simultaneityFactor: 1,
|
||||||
|
cosPhi: undefined,
|
||||||
|
remark: undefined,
|
||||||
|
overriddenFields: undefined,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renumber uses safe bulk identifier update for swapped identifiers", async () => {
|
it("renumber uses safe bulk identifier update for swapped identifiers", async () => {
|
||||||
@@ -770,6 +786,163 @@ describe("circuit write service rules", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("device row creation and reserve activation share one synchronous transaction", () => {
|
||||||
|
const repository = new CircuitDeviceRowRepository();
|
||||||
|
const originalTransaction = (db as unknown as { transaction: unknown }).transaction;
|
||||||
|
|
||||||
|
let callbackReturnedPromise = false;
|
||||||
|
let insertedValues: { id?: string; circuitId?: string; sortOrder?: number } | undefined;
|
||||||
|
let reserveValue: number | undefined;
|
||||||
|
let selectCall = 0;
|
||||||
|
|
||||||
|
(db as unknown as { transaction: (cb: (tx: unknown) => unknown) => void }).transaction = (cb) => {
|
||||||
|
const fakeTx = {
|
||||||
|
select() {
|
||||||
|
const rows = selectCall++ === 0 ? [{ id: "c1" }] : [{ sortOrder: 20 }];
|
||||||
|
const query = {
|
||||||
|
from() {
|
||||||
|
return query;
|
||||||
|
},
|
||||||
|
where() {
|
||||||
|
return query;
|
||||||
|
},
|
||||||
|
limit() {
|
||||||
|
return query;
|
||||||
|
},
|
||||||
|
all() {
|
||||||
|
return rows;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return query;
|
||||||
|
},
|
||||||
|
insert() {
|
||||||
|
return {
|
||||||
|
values(values: typeof insertedValues) {
|
||||||
|
insertedValues = values;
|
||||||
|
return {
|
||||||
|
run() {
|
||||||
|
return { changes: 1 };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
update() {
|
||||||
|
return {
|
||||||
|
set(values: { isReserve: number }) {
|
||||||
|
reserveValue = values.isReserve;
|
||||||
|
return {
|
||||||
|
where() {
|
||||||
|
return {
|
||||||
|
run() {
|
||||||
|
return { changes: 1 };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const callbackResult = cb(fakeTx);
|
||||||
|
callbackReturnedPromise = Boolean(
|
||||||
|
callbackResult && typeof (callbackResult as Promise<unknown>).then === "function"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const rowId = repository.createInCircuitTransactional({
|
||||||
|
circuitId: "c1",
|
||||||
|
name: "Load",
|
||||||
|
displayName: "Load",
|
||||||
|
quantity: 1,
|
||||||
|
powerPerUnit: 1,
|
||||||
|
simultaneityFactor: 1,
|
||||||
|
});
|
||||||
|
assert.equal(callbackReturnedPromise, false);
|
||||||
|
assert.equal(insertedValues?.id, rowId);
|
||||||
|
assert.equal(insertedValues?.circuitId, "c1");
|
||||||
|
assert.equal(insertedValues?.sortOrder, 30);
|
||||||
|
assert.equal(reserveValue, 0);
|
||||||
|
} finally {
|
||||||
|
(db as unknown as { transaction: unknown }).transaction = originalTransaction;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("device row deletion and reserve update share one synchronous transaction", () => {
|
||||||
|
const repository = new CircuitDeviceRowRepository();
|
||||||
|
const originalTransaction = (db as unknown as { transaction: unknown }).transaction;
|
||||||
|
|
||||||
|
let callbackReturnedPromise = false;
|
||||||
|
let deleteCount = 0;
|
||||||
|
let reserveValue: number | undefined;
|
||||||
|
let selectCall = 0;
|
||||||
|
|
||||||
|
(db as unknown as { transaction: (cb: (tx: unknown) => unknown) => void }).transaction = (cb) => {
|
||||||
|
const fakeTx = {
|
||||||
|
select() {
|
||||||
|
const rows = selectCall++ === 0 ? [{ id: "r1", circuitId: "c1" }] : [];
|
||||||
|
const query = {
|
||||||
|
from() {
|
||||||
|
return query;
|
||||||
|
},
|
||||||
|
where() {
|
||||||
|
return query;
|
||||||
|
},
|
||||||
|
limit() {
|
||||||
|
return query;
|
||||||
|
},
|
||||||
|
all() {
|
||||||
|
return rows;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return query;
|
||||||
|
},
|
||||||
|
delete() {
|
||||||
|
return {
|
||||||
|
where() {
|
||||||
|
return {
|
||||||
|
run() {
|
||||||
|
deleteCount += 1;
|
||||||
|
return { changes: 1 };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
update() {
|
||||||
|
return {
|
||||||
|
set(values: { isReserve: number }) {
|
||||||
|
reserveValue = values.isReserve;
|
||||||
|
return {
|
||||||
|
where() {
|
||||||
|
return {
|
||||||
|
run() {
|
||||||
|
return { changes: 1 };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const callbackResult = cb(fakeTx);
|
||||||
|
callbackReturnedPromise = Boolean(
|
||||||
|
callbackResult && typeof (callbackResult as Promise<unknown>).then === "function"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
repository.deleteFromCircuitTransactional("r1", "c1");
|
||||||
|
assert.equal(callbackReturnedPromise, false);
|
||||||
|
assert.equal(deleteCount, 1);
|
||||||
|
assert.equal(reserveValue, 1);
|
||||||
|
} finally {
|
||||||
|
(db as unknown as { transaction: unknown }).transaction = originalTransaction;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("tracks local edits on linked device rows as overridden fields", async () => {
|
it("tracks local edits on linked device rows as overridden fields", async () => {
|
||||||
let savedOverrides: string | undefined;
|
let savedOverrides: string | undefined;
|
||||||
const current = {
|
const current = {
|
||||||
|
|||||||
Reference in New Issue
Block a user