/* eslint-disable @typescript-eslint/no-explicit-any */ import { Nullable } from '@speckle/shared' import { SchemaConfig, MetaSchemaConfig } from '@/modules/core/dbSchema' import { camelCase, isString } from 'lodash-es' import { Knex } from 'knex' /** * All meta records must follow this interface */ export interface BaseMetaRecord { key: string value: V createdAt: Date updatedAt: Date } /** * Helpers that simplify working with a DB table's associated meta table, if one exists */ export function metaHelpers< R extends BaseMetaRecord, S extends SchemaConfig> >(table: S, knex: Knex) { const db = () => knex(table.meta.name) return { /** * Get a single value */ get: async ( id: string, key: keyof S['meta']['metaKey'] ): Promise> => { const q = db() .where(table.meta.col.key, key) .andWhere(table.meta.parentIdentityCol, id) .first() const res = (await q) as Nullable return res }, /** * Get multiple values at once, keyed by ID * E.g.: { * "1234": { * "foo": ..., * "bar": ..., * } * } */ getMultiple: async ( requests: Array<{ id: string; key: keyof S['meta']['metaKey'] }> ) => { const meta = table.meta.withoutTablePrefix const q = db() .select>('*') .whereIn( table.meta.col.key, requests.map((r) => r.key) ) .whereIn( table.meta.parentIdentityCol, requests.map((r) => r.id) ) const results = await q const ret: Record> = {} for (const result of results) { const resultId = (result as Record)[meta.parentIdentityCol] if (!ret[resultId]) { ret[resultId] = {} } const identityValues = ret[resultId] identityValues[result.key] = result as RR } return ret }, /** * Set a value */ set: async ( id: string, key: keyof S['meta']['metaKey'], val: any ) => { const meta = table.meta.withoutTablePrefix const q = db() .insert({ [meta.parentIdentityCol]: id, [meta.col.key]: key, [meta.col.value]: isString(val) ? JSON.stringify(val) : val, [meta.col.updatedAt]: new Date() }) .onConflict([meta.parentIdentityCol, meta.col.key]) .merge([meta.col.value, meta.col.updatedAt]) .returning('*') const [newEntry] = (await q) as RR[] return newEntry }, /** * Delete meta entry entirely */ delete: async (id: string, key: keyof S['meta']['metaKey']) => { const q = db() .where(table.meta.col.key, key) .andWhere(table.meta.parentIdentityCol, id) .del() const res = await q return res > 0 }, /** * Get unique GQL ID for the meta record */ getGraphqlId: (record: R) => { const metaName = camelCase(table.meta.name) const entityId = (record as Record)[ table.meta.parentIdentityCol ] as string return `MetaValue/${metaName}/${entityId}/${record.key}` } } }