UNPKG

prostgles-types

Version:

Shared TypeScript object definitions for prostgles-client and prostgles-server

484 lines (448 loc) 14.4 kB
import type { JSONSchema7, JSONSchema7Definition, JSONSchema7TypeName } from "json-schema"; import { AnyObject } from "../filters"; import { getKeys, getObjectEntries, isObject, StrictUnion } from "../util"; export const PrimitiveTypes = [ "boolean", "number", "integer", "string", "Date", "time", "timestamp", "any", ] as const; export const PrimitiveArrayTypes = PrimitiveTypes.map((v) => `${v}[]` as `${typeof v}[]`); export const DATA_TYPES = [...PrimitiveTypes, ...PrimitiveArrayTypes] as const; type DataType = (typeof DATA_TYPES)[number]; export namespace JSONB { export type BaseOptions = { /** * False by default */ optional?: boolean; /** * False by default */ nullable?: boolean; description?: string; title?: string; }; export type Lookup = BaseOptions & { type?: "Lookup" | "Lookup[]"; lookup: | { type: | "data" /** * This is used as edit-mode (to generate lookup of type data) */ | "data-def"; table: string; column: string; filter?: AnyObject; isArray?: boolean; isFullRow?: { /** * Columns used to display the selected row in the dropdown */ displayColumns?: string[]; }; /** * Columns used to search */ searchColumns?: string[]; /** * If true then a button will be shown * in the row card footer to access this action */ showInRowCard?: { /** * Action button text. Defaults to the method name */ actionLabel?: string; actionColor?: "danger" | "warn" | "action"; actionStyle?: AnyObject; actionClass?: string; }; } | { type: "schema"; isArray?: boolean; object: "column" | "table"; filter?: { table?: string; tsDataType?: string; udt_name?: string }; }; allowedValues?: undefined; oneOf?: undefined; oneOfType?: undefined; arrayOf?: undefined; arrayOfType?: undefined; enum?: undefined; }; export type BasicType = BaseOptions & { type: DataType; allowedValues?: readonly any[] | any[]; oneOf?: undefined; oneOfType?: undefined; arrayOf?: undefined; arrayOfType?: undefined; enum?: undefined; }; export type ObjectType = BaseOptions & { type: ObjectSchema; allowedValues?: undefined; oneOf?: undefined; oneOfType?: undefined; arrayOf?: undefined; arrayOfType?: undefined; enum?: undefined; }; export type EnumType = BaseOptions & { type?: undefined; enum: readonly any[]; oneOf?: undefined; oneOfType?: undefined; arrayOf?: undefined; arrayOfType?: undefined; allowedValues?: undefined; }; export type OneOf = BaseOptions & { type?: undefined; arrayOf?: undefined; arrayOfType?: undefined; allowedValues?: undefined; enum?: undefined; } & ( | { oneOf?: undefined; oneOfType: readonly ObjectSchema[]; } | { oneOf: FieldType[]; oneOfType?: undefined; } ); export type ArrayOf = BaseOptions & { type?: undefined; allowedValues?: undefined; oneOf?: undefined; oneOfType?: undefined; enum?: undefined; } & ( | { arrayOf?: undefined; arrayOfType: ObjectSchema; } | { arrayOf: FieldType; arrayOfType?: undefined; } ); export type RecordType = BaseOptions & { type?: undefined; allowedValues?: undefined; oneOf?: undefined; oneOfType?: undefined; arrayOf?: undefined; arrayOfType?: undefined; enum?: undefined; record: { keysEnum?: readonly string[]; values?: FieldType; partial?: boolean; }; }; export type FieldTypeObj = StrictUnion< BasicType | ObjectType | EnumType | OneOf | ArrayOf | RecordType | Lookup >; export type FieldType = DataType | FieldTypeObj; export type GetType<T extends FieldType | Omit<FieldTypeObj, "optional">> = GetWNullType< T extends DataType ? { type: T } : T >; type GetWNullType<T extends FieldTypeObj | Omit<FieldTypeObj, "optional">> = T extends { nullable: true } ? null | _GetType<T> : _GetType<T>; type GetAllowedValues<T extends FieldTypeObj | Omit<FieldTypeObj, "optional">, TType> = T extends { allowedValues: readonly any[] } ? T["allowedValues"][number] : TType; // type _GetType<T extends FieldTypeObj | Omit<FieldTypeObj, "optional">> = // T extends { type: ObjectSchema } ? GetObjectType<T["type"]> // : T extends { type: "number" } ? GetAllowedValues<T, number> // : T extends { type: "boolean" } ? GetAllowedValues<T, boolean> // : T extends { type: "integer" } ? GetAllowedValues<T, number> // : T extends { type: "string" } ? GetAllowedValues<T, string> // : T extends { type: "time" } ? GetAllowedValues<T, string> // : T extends { type: "timestamp" } ? GetAllowedValues<T, string> // : T extends { type: "Date" } ? GetAllowedValues<T, string> // : T extends { type: "any" } ? GetAllowedValues<T, any> // : T extends { type: "number[]" } ? GetAllowedValues<T, number>[] // : T extends { type: "boolean[]" } ? GetAllowedValues<T, boolean>[] // : T extends { type: "integer[]" } ? GetAllowedValues<T, number>[] // : T extends { type: "time[]" } ? GetAllowedValues<T, string>[] // : T extends { type: "timestamp[]" } ? GetAllowedValues<T, string>[] // : T extends { type: "Date[]" } ? GetAllowedValues<T, string>[] // : T extends { type: "string[]" } ? GetAllowedValues<T, string>[] // : T extends { type: "any[]" } ? GetAllowedValues<T, any>[] // : T extends { enum: readonly any[] | any[] } ? T["enum"][number] // : T extends { record: RecordType["record"] } ? // Record< // T["record"] extends { keysEnum: readonly string[] } ? T["record"]["keysEnum"][number] // : string, // T["record"] extends { values: FieldType } ? GetType<T["record"]["values"]> : any // > // : T extends { oneOf: readonly FieldType[] | FieldType[] } ? GetType<T["oneOf"][number]> // : T extends { oneOfType: readonly ObjectSchema[] | ObjectSchema[] } ? // GetObjectType<T["oneOfType"][number]> // : T extends { arrayOf: FieldType } ? GetType<T["arrayOf"]>[] // : T extends { arrayOfType: ObjectSchema } ? GetObjectType<T["arrayOfType"]>[] // : any; type _GetType<T extends FieldTypeObj | Omit<FieldTypeObj, "optional">> = // Handle objects first (most common case) T extends { type: infer U } ? U extends ObjectSchema ? GetObjectType<U> : U extends DataType ? GetPrimitiveType<T, U> : never : // Handle other patterns T extends { enum: readonly any[] } ? T["enum"][number] : T extends { arrayOfType: infer U } ? U extends ObjectSchema ? GetObjectType<U>[] : never : T extends { arrayOf: infer U } ? U extends FieldType ? GetType<U>[] : never : T extends { oneOf: readonly (infer U)[] } ? U extends FieldType ? GetType<U> : never : T extends { oneOfType: readonly (infer U)[] } ? U extends ObjectSchema ? GetObjectType<U> : never : T extends { record: infer R } ? R extends RecordType["record"] ? Record< R extends { keysEnum: readonly string[] } ? R["keysEnum"][number] : string, R extends { values: infer V } ? V extends FieldType ? GetType<V> : any : any > : never : any; type GetPrimitiveType< T extends JSONB.FieldTypeObj | Omit<JSONB.FieldTypeObj, "optional">, U extends DataType, > = U extends "number" ? GetAllowedValues<T, number> : U extends "boolean" ? GetAllowedValues<T, boolean> : U extends "integer" ? GetAllowedValues<T, number> : U extends "string" ? GetAllowedValues<T, string> : U extends "time" | "timestamp" | "Date" ? GetAllowedValues<T, string> : U extends "any" ? GetAllowedValues<T, any> : U extends `${infer P}[]` ? P extends "number" | "integer" ? GetAllowedValues<T, number>[] : P extends "boolean" ? GetAllowedValues<T, boolean>[] : P extends "string" | "time" | "timestamp" | "Date" ? GetAllowedValues<T, string>[] : P extends "any" ? GetAllowedValues<T, any>[] : never : never; type IsOptional<F extends FieldType> = F extends DataType ? false : F extends { optional: true } ? true : false; type ObjectSchema = Record<string, FieldType>; export type JSONBSchema = Omit<FieldTypeObj, "optional"> & { defaultValue?: any; }; export type GetObjectType<S extends ObjectSchema> = { [K in keyof S as IsOptional<S[K]> extends true ? K : never]?: GetType<S[K]>; } & { [K in keyof S as IsOptional<S[K]> extends true ? never : K]: GetType<S[K]>; }; // export type GetObjectType<S extends ObjectSchema> = { // [K in keyof S]: S[K] extends { optional: true } ? GetType<S[K]> | undefined : GetType<S[K]>; // }; export type GetSchemaType<S extends JSONBSchema> = S["nullable"] extends true ? null | GetType<S> : GetType<S>; } /** tests */ const t: JSONB.GetType<{ arrayOfType: { a: "number" } }> = [{ a: 2 }]; /** StrictUnion was removed because it doesn't work with object | string */ const _oneOf: JSONB.GetType<{ nullable: true; oneOf: [ { enum: ["n"] }, { type: { a: "number" } }, { type: { a: { type: "string"; allowedValues: ["a"] } } }, ]; }> = { a: "a", }; const _a: JSONB.GetType<{ type: { a: "number" } }> = { a: 2, }; const _r: JSONB.GetType<{ record: { keysEnum: ["a", "b"]; values: "integer[]" } }> = { a: [2], b: [221], }; const _dd: JSONB.JSONBSchema = { enum: [1], type: "any", }; const s = { type: { a: { type: "boolean" }, c: { type: { c1: { type: "string" } } }, arr: { arrayOfType: { d: "string" } }, o: { oneOfType: [{ z: { type: "integer" } }, { z1: { type: "integer" } }], }, }, } as const; // satisfies JSONB.JSONBSchema; const _ss: JSONB.GetType<typeof s> = { a: true, arr: [{ d: "" }], c: { c1: "", }, o: { z1: 23 }, }; const getJSONSchemaType = ( rawType: JSONB.BasicType["type"] | JSONB.Lookup["type"] | undefined ): { type: JSONSchema7TypeName | undefined; isArray: boolean } | undefined => { if (!rawType) return; const type: (typeof PrimitiveTypes)[number] | "Lookup" = rawType.endsWith("[]") ? (rawType.slice(0, -2) as any) : rawType; return { type: type === "integer" ? "integer" : type === "boolean" ? "boolean" : type === "number" ? "number" : type === "any" ? undefined : type === "Lookup" ? undefined : "string", isArray: rawType.endsWith("[]"), }; }; export const getJSONSchemaObject = ( rawType: JSONB.FieldType | JSONB.JSONBSchema, rootInfo?: { id: string } ): JSONSchema7 => { const { type, arrayOf, arrayOfType, description, nullable, oneOf, oneOfType, title, record, ...t } = typeof rawType === "string" ? ({ type: rawType } as JSONB.FieldTypeObj) : rawType; let result: JSONSchema7 = {}; const partialProps: Partial<JSONSchema7> = { ...((t.enum || (t.allowedValues?.length && (typeof type !== "string" || !type.endsWith("[]")))) && { enum: t.allowedValues?.slice(0) ?? t.enum!.slice(0), }), ...(!!description && { description }), ...(!!title && { title }), }; if (t.enum?.length) { const firstElemType = typeof t.enum[0]; partialProps.type = firstElemType === "number" ? "number" : firstElemType === "boolean" ? "boolean" : "string"; } if (typeof type === "string" || arrayOf || arrayOfType) { /** ARRAY */ if (type && typeof type !== "string") { throw "Not expected"; } if (arrayOf || arrayOfType || type?.endsWith("[]")) { const arrayItems = arrayOf || arrayOfType ? getJSONSchemaObject(arrayOf || { type: arrayOfType }) : type?.startsWith("any") ? { type: undefined } : ({ type: getJSONSchemaType(type)?.type, ...(t.allowedValues && { enum: t.allowedValues.slice(0) }), } satisfies JSONSchema7Definition); result = { type: "array", items: arrayItems, }; /** PRIMITIVES */ } else { result = { type: getJSONSchemaType(type)?.type, }; } /** OBJECT */ } else if (isObject(type)) { result = { type: "object", required: getKeys(type).filter((k) => { const t = type[k]!; return typeof t === "string" || !t.optional; }), properties: getObjectEntries(type).reduce((a, [k, v]) => { return { ...a, [k]: getJSONSchemaObject(v), }; }, {}), }; } else if (oneOf || oneOfType) { const _oneOf = oneOf || oneOfType!.map((type) => ({ type })); result = { oneOf: _oneOf.map((t) => getJSONSchemaObject(t)), }; } else if (record) { result = { type: "object", ...(record.values && !record.keysEnum && { additionalProperties: getJSONSchemaObject(record.values) }), ...(record.keysEnum && { properties: record.keysEnum.reduce( (a, v) => ({ ...a, [v]: !record.values ? { type: {} } : getJSONSchemaObject(record.values), }), {} ), }), }; } if (nullable) { const nullDef = { type: "null" } as const; if (result.oneOf) { result.oneOf.push(nullDef); } else if (result.enum && !result.enum.includes(null)) { result.enum.push(null); } else result = { oneOf: [result, nullDef], }; } const rootSchema: JSONSchema7 | undefined = !rootInfo ? undefined : ( { $id: rootInfo?.id, $schema: "https://json-schema.org/draft/2020-12/schema", } ); return { ...rootSchema, ...partialProps, ...result, }; }; export function getJSONBSchemaAsJSONSchema( tableName: string, colName: string, schema: JSONB.JSONBSchema ): JSONSchema7 { return getJSONSchemaObject(schema, { id: `${tableName}.${colName}` }); }