-
Notifications
You must be signed in to change notification settings - Fork 56
Open
Description
This relates mostly to deleting 1 to 1 linked documents & files, but would be nice to have type safe helpers like these ones:
/**
* Reusable helper type: keys in T whose values are assignable to V
* @example
* type A = KeysOfType<{ a: string; b: number }, string>; // 'a'
* type B = KeysOfType<{ a: string; b: number }, number>; // 'b'
* type C = KeysOfType<{ a: string; b: number }, string | number>; // 'a' | 'b'
*/
export type KeysOfType<T, V> = {
[K in keyof T]-?: T[K] extends V ? K : never;
}[keyof T];
export async function deleteRemovedReferences<
T extends Doc<TableNames>,
K extends KeysOfType<T, Id<TableNames> | undefined>,
>(ctx: MutationCtx, oldDoc: T, newDoc: Partial<T>, idFields: K[]) {
for (const field of idFields) {
const oldId = oldDoc?.[field] as Id<TableNames> | undefined;
const newId = newDoc?.[field] as Id<TableNames> | undefined;
if (oldId && !newId) {
await ctx.db.delete(oldId);
}
}
}
export async function deleteRemovedFileReferences<
T extends Doc<TableNames>,
K extends KeysOfType<T, Id<'files'> | undefined>,
>(ctx: MutationCtx, oldDoc: T, newDoc: Partial<T>, fileFields: K[]) {
const idsToRemove: Id<'files'>[] = fileFields
.filter((field) => oldDoc?.[field] && !newDoc?.[field])
.map((field) => oldDoc?.[field] as Id<'files'>);
if (idsToRemove.length > 0) {
await ctx.runMutation(api.files.remove, {
ids: idsToRemove,
});
}
}
And here's how these are used:
export const upsertDoctorProfile = mutation({
args: { payload: v.any() },
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error('Not authenticated');
const parsed = DoctorProfileSchema.pipe(
DoctorProfileFilesNullToUndefinedTransform,
).parse(args.payload);
const existing = await ctx.db
.query('doctorProfiles')
.withIndex('by_user', (q) => q.eq('userId', identity.subject))
.unique();
if (!existing) {
await ctx.db.insert(
'doctorProfiles',
addUpdatedAt({ userId: identity.subject, ...parsed }),
);
return true;
}
// Determine files to remove before patching
await deleteRemovedFileReferences(ctx, existing, parsed, [
'idCardFileId',
'diplomaFileId',
'graduationCertificateFileId',
'rethusCertificateFileId',
'homologationCertificateFileId',
'habilitationBadgeFileId',
]);
await ctx.db.patch(existing._id, addUpdatedAt({ ...parsed }));
return true;
},
});
Metadata
Metadata
Assignees
Labels
No labels