mirror of
https://github.com/logto-io/logto.git
synced 2025-02-10 21:58:23 -05:00
89 lines
3.1 KiB
TypeScript
89 lines
3.1 KiB
TypeScript
import type { SchemaValuePrimitive, SchemaValue } from '@logto/schemas';
|
|
import type { Falsy } from '@silverhand/essentials';
|
|
import { notFalsy } from '@silverhand/essentials';
|
|
import type { SqlSqlToken, SqlToken, QueryResult, IdentifierSqlToken } from 'slonik';
|
|
import { sql } from 'slonik';
|
|
|
|
import type { FieldIdentifiers, Table } from './types.js';
|
|
|
|
export const conditionalSql = <T>(value: T, buildSql: (value: Exclude<T, Falsy>) => SqlSqlToken) =>
|
|
notFalsy(value) ? buildSql(value) : sql``;
|
|
export const conditionalArraySql = <T>(
|
|
value: T[],
|
|
buildSql: (value: Exclude<T[], Falsy>) => SqlSqlToken
|
|
) => (value.length > 0 ? buildSql(value) : sql``);
|
|
|
|
export const autoSetFields = Object.freeze(['createdAt', 'updatedAt'] as const);
|
|
export type OmitAutoSetFields<T> = Omit<T, typeof autoSetFields[number]>;
|
|
export type ExcludeAutoSetFields<T> = Exclude<T, typeof autoSetFields[number]>;
|
|
export const excludeAutoSetFields = <T extends string>(fields: readonly T[]) =>
|
|
Object.freeze(
|
|
fields.filter(
|
|
(field): field is ExcludeAutoSetFields<T> =>
|
|
// Read only string arrays
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
!(autoSetFields as readonly string[]).includes(field)
|
|
)
|
|
);
|
|
|
|
/**
|
|
* Note `undefined` is removed from the acceptable list,
|
|
* since you should NOT call this function if ignoring the field is the desired behavior.
|
|
* Calling this function with `null` means an explicit `null` setting in database is expected.
|
|
* @param key The key of value. Will treat as `timestamp` if it ends with `_at` or 'At' AND value is a number;
|
|
* @param value The value to convert.
|
|
* @returns A primitive that can be saved into database.
|
|
*/
|
|
// eslint-disable-next-line complexity
|
|
export const convertToPrimitiveOrSql = (
|
|
key: string,
|
|
value: NonNullable<SchemaValue> | null
|
|
): NonNullable<SchemaValuePrimitive> | SqlToken | null => {
|
|
if (value === null) {
|
|
return null;
|
|
}
|
|
|
|
if (typeof value === 'object') {
|
|
return JSON.stringify(value);
|
|
}
|
|
|
|
if (['_at', 'At'].some((value) => key.endsWith(value)) && typeof value === 'number') {
|
|
return sql`to_timestamp(${value}::double precision / 1000)`;
|
|
}
|
|
|
|
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
return value;
|
|
}
|
|
|
|
if (typeof value === 'string') {
|
|
if (value === '') {
|
|
return null;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
throw new Error(`Cannot convert ${key} to primitive`);
|
|
};
|
|
|
|
export const convertToIdentifiers = <T extends Table>({ table, fields }: T, withPrefix = false) => {
|
|
const fieldsIdentifiers = Object.entries<string>(fields).map<
|
|
[keyof T['fields'], IdentifierSqlToken]
|
|
>(([key, value]) => [key, sql.identifier(withPrefix ? [table, value] : [value])]);
|
|
|
|
return {
|
|
table: sql.identifier([table]),
|
|
// Key value inferred from the original fields directly
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
fields: Object.fromEntries(fieldsIdentifiers) as FieldIdentifiers<keyof T['fields']>,
|
|
};
|
|
};
|
|
|
|
export const convertToTimestamp = (time = new Date()) =>
|
|
sql`to_timestamp(${time.valueOf() / 1000})`;
|
|
|
|
export const manyRows = async <T>(query: Promise<QueryResult<T>>): Promise<readonly T[]> => {
|
|
const { rows } = await query;
|
|
|
|
return rows;
|
|
};
|