mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-19 16:01:44 +00:00
feat: changed backend-pg to backend
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
import { Knex } from "knex";
|
||||
import { Tables } from "knex/types/tables";
|
||||
|
||||
import { DatabaseError } from "../errors";
|
||||
|
||||
export * from "./join";
|
||||
export * from "./select";
|
||||
|
||||
export const withTransaction = <K extends object>(db: Knex, dal: K) => ({
|
||||
transaction: async <T>(cb: (tx: Knex) => Promise<T>) =>
|
||||
db.transaction(async (trx) => {
|
||||
const res = await cb(trx);
|
||||
return res;
|
||||
}),
|
||||
...dal
|
||||
});
|
||||
|
||||
export type TFindFilter<R extends {} = any> = Partial<R> & {
|
||||
$in?: Partial<{ [k in keyof R]: R[k][] }>;
|
||||
};
|
||||
export const buildFindFilter =
|
||||
<R extends {} = any>({ $in, ...filter }: TFindFilter<R>) =>
|
||||
(bd: Knex.QueryBuilder<R, R>) => {
|
||||
bd.where(filter);
|
||||
if ($in) {
|
||||
Object.entries($in).forEach(([key, val]) => {
|
||||
bd.whereIn(key as any, val as any);
|
||||
});
|
||||
}
|
||||
return bd;
|
||||
};
|
||||
|
||||
export type TFindOpt<R extends {} = any> = {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
sort?: Array<[keyof R, "asc" | "desc"] | [keyof R, "asc" | "desc", "first" | "last"]>;
|
||||
tx?: Knex;
|
||||
};
|
||||
|
||||
// What is ormify
|
||||
// It is to inject typical operations like find, findOne, update, delete, create
|
||||
// This will avoid writing most common ones each time
|
||||
export const ormify = <DbOps extends object, Tname extends keyof Tables>(
|
||||
db: Knex,
|
||||
tableName: Tname,
|
||||
dal?: DbOps
|
||||
) => ({
|
||||
transaction: async <T>(cb: (tx: Knex) => Promise<T>) =>
|
||||
db.transaction(async (trx) => {
|
||||
const res = await cb(trx);
|
||||
return res;
|
||||
}),
|
||||
findById: async (id: string, tx?: Knex) => {
|
||||
try {
|
||||
const result = await (tx || db)(tableName)
|
||||
.where({ id } as any)
|
||||
.first("*");
|
||||
return result;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find by id" });
|
||||
}
|
||||
},
|
||||
findOne: async (filter: Partial<Tables[Tname]["base"]>, tx?: Knex) => {
|
||||
try {
|
||||
const res = await (tx || db)(tableName).where(filter).first("*");
|
||||
return res;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find one" });
|
||||
}
|
||||
},
|
||||
find: async (
|
||||
filter: TFindFilter<Tables[Tname]["base"]>,
|
||||
{ offset, limit, sort, tx }: TFindOpt<Tables[Tname]["base"]> = {}
|
||||
) => {
|
||||
try {
|
||||
const query = (tx || db)(tableName).where(buildFindFilter(filter));
|
||||
if (limit) query.limit(limit);
|
||||
if (offset) query.offset(offset);
|
||||
if (sort) {
|
||||
query.orderBy(
|
||||
sort.map(([column, order, nulls]) => ({ column: column as string, order, nulls }))
|
||||
);
|
||||
}
|
||||
const res = await query;
|
||||
return res;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find one" });
|
||||
}
|
||||
},
|
||||
create: async (data: Tables[Tname]["insert"], tx?: Knex) => {
|
||||
try {
|
||||
const [res] = await (tx || db)(tableName)
|
||||
.insert(data as any)
|
||||
.returning("*");
|
||||
return res;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Create" });
|
||||
}
|
||||
},
|
||||
insertMany: async (data: readonly Tables[Tname]["insert"][], tx?: Knex) => {
|
||||
try {
|
||||
if (!data.length) return [];
|
||||
const res = await (tx || db)(tableName)
|
||||
.insert(data as any)
|
||||
.returning("*");
|
||||
return res;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Create" });
|
||||
}
|
||||
},
|
||||
updateById: async (id: string, data: Tables[Tname]["update"], tx?: Knex) => {
|
||||
try {
|
||||
const [res] = await (tx || db)(tableName)
|
||||
.where({ id } as any)
|
||||
.update(data as any)
|
||||
.returning("*");
|
||||
return res;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Update by id" });
|
||||
}
|
||||
},
|
||||
update: async (
|
||||
filter: TFindFilter<Tables[Tname]["base"]>,
|
||||
data: Tables[Tname]["update"],
|
||||
tx?: Knex
|
||||
) => {
|
||||
try {
|
||||
const res = await (tx || db)(tableName)
|
||||
.where(buildFindFilter(filter))
|
||||
.update(data as any)
|
||||
.returning("*");
|
||||
return res;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Update" });
|
||||
}
|
||||
},
|
||||
deleteById: async (id: string, tx?: Knex) => {
|
||||
try {
|
||||
const [res] = await (tx || db)(tableName)
|
||||
.where({ id } as any)
|
||||
.delete()
|
||||
.returning("*");
|
||||
return res;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Delete by id" });
|
||||
}
|
||||
},
|
||||
delete: async (filter: TFindFilter<Tables[Tname]["base"]>, tx?: Knex) => {
|
||||
try {
|
||||
const res = await (tx || db)(tableName)
|
||||
.where(buildFindFilter(filter))
|
||||
.delete()
|
||||
.returning("*");
|
||||
return res;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Delete" });
|
||||
}
|
||||
},
|
||||
...(dal || {})
|
||||
});
|
||||
@@ -0,0 +1,129 @@
|
||||
export const mergeOneToManyRelation = <
|
||||
T extends Record<string, any>,
|
||||
Pk extends keyof T,
|
||||
P extends Record<string, any>,
|
||||
C extends any,
|
||||
Ck extends string = "child"
|
||||
>(
|
||||
data: T[],
|
||||
key: Pk,
|
||||
parentMapper: (arg: T) => P,
|
||||
childMapper: (arg: T) => C,
|
||||
childKey: Ck
|
||||
) => {
|
||||
let prevPkIndex = -1;
|
||||
let prevPkId: null | string = null;
|
||||
|
||||
const groupedRecord: (P & Record<Ck, C[]>)[] = [];
|
||||
for (let i = 0; i < data.length; i += 1) {
|
||||
const pk = data[i][key];
|
||||
const row = data[i];
|
||||
if (pk !== prevPkId) {
|
||||
const parent = parentMapper(row) as any;
|
||||
parent[childKey] = [];
|
||||
groupedRecord.push(parent);
|
||||
prevPkId = pk;
|
||||
prevPkIndex += 1;
|
||||
}
|
||||
groupedRecord[prevPkIndex][childKey].push(childMapper(row));
|
||||
}
|
||||
return groupedRecord;
|
||||
};
|
||||
|
||||
export type TSqlPackRelationships<
|
||||
T extends Record<string, any>,
|
||||
P extends Record<string, any>,
|
||||
C extends TChildMapper<T>[]
|
||||
> = {
|
||||
data: T[];
|
||||
key: keyof T;
|
||||
parentMapper: (arg: T) => P;
|
||||
childrenMapper: C;
|
||||
};
|
||||
|
||||
export type TChildMapper<T extends {}, U extends string = string, R extends unknown = unknown> = {
|
||||
key: keyof T;
|
||||
label: U;
|
||||
mapper: (arg: T) => R;
|
||||
childrenMapper?: TChildMapper<T, U, R>[];
|
||||
};
|
||||
|
||||
type MappedRecord<T extends TChildMapper<any>> = {
|
||||
[K in T["label"]]: Array<
|
||||
Exclude<ReturnType<Extract<T, { label: K }>["mapper"]>, null | undefined> &
|
||||
(Extract<T, { label: K }>["childrenMapper"] extends Array<infer TChild>
|
||||
? TChild extends TChildMapper<any>
|
||||
? MappedRecord<TChild>
|
||||
: {}
|
||||
: {})
|
||||
>;
|
||||
};
|
||||
|
||||
const sqlChildMapper = <
|
||||
T extends Record<string, any> = {},
|
||||
P extends Record<string, any> = {},
|
||||
C extends TChildMapper<T>[] = TChildMapper<T>[]
|
||||
>(
|
||||
doc: T,
|
||||
docsByPk: P,
|
||||
lookupTable: Set<string>,
|
||||
pk: keyof P,
|
||||
prefix: string,
|
||||
childrenMapper: C
|
||||
) => {
|
||||
if (!docsByPk) return;
|
||||
|
||||
type Cm = MappedRecord<(typeof childrenMapper)[number]>;
|
||||
childrenMapper.forEach(({ label, mapper, key: childPk, childrenMapper: nestedMappers }) => {
|
||||
// eslint-disable-next-line
|
||||
if (!docsByPk?.[pk as keyof P]?.[label]) docsByPk[pk as keyof P][label as keyof Cm] = [] as any;
|
||||
|
||||
if (doc?.[childPk] !== null && typeof doc?.[childPk] !== "undefined") {
|
||||
const ck = `${prefix}-${label}-${doc[childPk]}`;
|
||||
const val = mapper(doc);
|
||||
if (!lookupTable.has(ck)) {
|
||||
if (typeof val !== "undefined" && val !== null) docsByPk[pk as keyof P][label].push(val);
|
||||
lookupTable.add(ck);
|
||||
}
|
||||
if (nestedMappers && val) {
|
||||
sqlChildMapper(
|
||||
doc,
|
||||
docsByPk[pk][label as keyof P],
|
||||
lookupTable,
|
||||
docsByPk[pk][label as keyof P].length - 1,
|
||||
ck,
|
||||
nestedMappers
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const sqlNestRelationships = <
|
||||
T extends Record<string, any> = {},
|
||||
P extends Record<string, any> = {},
|
||||
C extends TChildMapper<T>[] = TChildMapper<T>[]
|
||||
>({
|
||||
data,
|
||||
key,
|
||||
parentMapper,
|
||||
childrenMapper
|
||||
}: TSqlPackRelationships<T, P, C>) => {
|
||||
const lookupTable = new Set<string>();
|
||||
const recordsOrder: string[] = [];
|
||||
|
||||
type Cm = MappedRecord<(typeof childrenMapper)[number]>;
|
||||
|
||||
const recordsGroupedByPk: Record<string, P & Cm> = {};
|
||||
data.forEach((doc) => {
|
||||
const pk = doc[key];
|
||||
if (!lookupTable.has(pk)) {
|
||||
recordsGroupedByPk[pk] = parentMapper(doc) as P & Cm;
|
||||
recordsOrder.push(pk);
|
||||
lookupTable.add(pk);
|
||||
}
|
||||
|
||||
sqlChildMapper(doc, recordsGroupedByPk, lookupTable, pk, pk, childrenMapper);
|
||||
});
|
||||
return recordsOrder.map((pkId) => recordsGroupedByPk[pkId]);
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Tables } from "knex/types/tables";
|
||||
|
||||
export const selectAllTableCols = <Tname extends keyof Tables>(tableName: Tname) =>
|
||||
`${tableName}.*` as keyof Tables[Tname]["base"];
|
||||
|
||||
export const stripUndefinedInWhere = <T extends object>(val: T): Exclude<T, undefined> => {
|
||||
const copy = val;
|
||||
Object.entries(copy).forEach(([key, value]) => {
|
||||
if (typeof value === "undefined") {
|
||||
delete copy[key as keyof T];
|
||||
}
|
||||
});
|
||||
return copy as Exclude<T, undefined>;
|
||||
};
|
||||
Reference in New Issue
Block a user