269 lines
8.3 KiB
TypeScript
269 lines
8.3 KiB
TypeScript
/*
|
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
|
*/
|
|
|
|
import type { Schema, ObjectTypeObject, ObjectTypeSingleton, Fields, Form, List } from '@/types/schema';
|
|
|
|
export interface ResolvedObject {
|
|
viewName: string;
|
|
objectName: string;
|
|
objectType: ObjectTypeObject | ObjectTypeSingleton;
|
|
permissionPrefix: string;
|
|
enterprise: boolean;
|
|
}
|
|
|
|
export function resolveObject(schema: Schema, viewName: string): ResolvedObject | null {
|
|
const entry = schema.objects[viewName];
|
|
if (!entry) return null;
|
|
|
|
if (entry.type === 'view') {
|
|
const parent = schema.objects[entry.objectName];
|
|
if (!parent || parent.type === 'view') return null;
|
|
return {
|
|
viewName,
|
|
objectName: entry.objectName,
|
|
objectType: parent,
|
|
permissionPrefix: parent.permissionPrefix,
|
|
enterprise: parent.enterprise ?? false,
|
|
};
|
|
}
|
|
|
|
return {
|
|
viewName,
|
|
objectName: viewName,
|
|
objectType: entry,
|
|
permissionPrefix: entry.permissionPrefix,
|
|
enterprise: entry.enterprise ?? false,
|
|
};
|
|
}
|
|
|
|
export interface ResolvedSingleSchema {
|
|
type: 'single';
|
|
schemaName: string;
|
|
fields: Fields;
|
|
}
|
|
|
|
export interface ResolvedMultipleSchema {
|
|
type: 'multiple';
|
|
variants: {
|
|
name: string;
|
|
label: string;
|
|
schemaName?: string;
|
|
fields: Fields | null;
|
|
}[];
|
|
}
|
|
|
|
export type ResolvedSchema = ResolvedSingleSchema | ResolvedMultipleSchema;
|
|
|
|
export function resolveSchema(schema: Schema, objectName: string): ResolvedSchema | null {
|
|
const schemaEntry = schema.schemas[objectName];
|
|
if (!schemaEntry) return null;
|
|
|
|
if (schemaEntry.type === 'single') {
|
|
const fields = schema.fields[schemaEntry.schemaName];
|
|
if (!fields) return null;
|
|
return { type: 'single', schemaName: schemaEntry.schemaName, fields };
|
|
}
|
|
|
|
return {
|
|
type: 'multiple',
|
|
variants: schemaEntry.variants.map((v) => ({
|
|
name: v.name,
|
|
label: v.label,
|
|
schemaName: v.schemaName,
|
|
fields: v.schemaName ? (schema.fields[v.schemaName] ?? null) : null,
|
|
})),
|
|
};
|
|
}
|
|
|
|
export function resolveList(schema: Schema, viewName: string, objectName: string): List | null {
|
|
return schema.lists[viewName] ?? schema.lists[objectName] ?? null;
|
|
}
|
|
|
|
export function resolveForm(schema: Schema, viewName: string, objectName: string, schemaName: string): Form | null {
|
|
return schema.forms[viewName] ?? schema.forms[objectName] ?? schema.forms[schemaName] ?? null;
|
|
}
|
|
|
|
export function resolveVariantForm(
|
|
schema: Schema,
|
|
_viewName: string,
|
|
_objectName: string,
|
|
variantSchemaName?: string,
|
|
): Form | null {
|
|
if (variantSchemaName && schema.forms[variantSchemaName]) {
|
|
return schema.forms[variantSchemaName];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function deepMerge(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown> {
|
|
const result = { ...target };
|
|
for (const key of Object.keys(source)) {
|
|
const sourceVal = source[key];
|
|
const targetVal = result[key];
|
|
if (
|
|
sourceVal &&
|
|
typeof sourceVal === 'object' &&
|
|
!Array.isArray(sourceVal) &&
|
|
targetVal &&
|
|
typeof targetVal === 'object' &&
|
|
!Array.isArray(targetVal)
|
|
) {
|
|
result[key] = deepMerge(targetVal as Record<string, unknown>, sourceVal as Record<string, unknown>);
|
|
} else {
|
|
result[key] = sourceVal;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export function buildCreateDefaults(
|
|
schema: Schema,
|
|
resolvedObject: ResolvedObject,
|
|
resolvedSchema: ResolvedSchema,
|
|
variantName?: string,
|
|
filtersStatic?: Record<string, unknown>,
|
|
): Record<string, unknown> {
|
|
let defaults: Record<string, unknown> = {};
|
|
let activeFields: Fields | null = null;
|
|
|
|
if (resolvedSchema.type === 'single') {
|
|
activeFields = resolvedSchema.fields;
|
|
const childDefaults = resolvedSchema.fields.defaults ?? {};
|
|
defaults = deepMerge(defaults, childDefaults);
|
|
} else if (resolvedSchema.type === 'multiple' && variantName) {
|
|
const variant = resolvedSchema.variants.find((v) => v.name === variantName);
|
|
activeFields = variant?.fields ?? null;
|
|
if (variant?.fields?.defaults) {
|
|
defaults = deepMerge(defaults, variant.fields.defaults);
|
|
}
|
|
defaults['@type'] = variantName;
|
|
}
|
|
|
|
const parentSchemaEntry = schema.schemas[resolvedObject.objectName];
|
|
if (parentSchemaEntry?.type === 'single') {
|
|
const parentFields = schema.fields[parentSchemaEntry.schemaName];
|
|
if (parentFields?.defaults) {
|
|
defaults = deepMerge(defaults, parentFields.defaults);
|
|
}
|
|
}
|
|
|
|
if (activeFields) {
|
|
for (const [propName, propDef] of Object.entries(activeFields.properties)) {
|
|
const t = propDef.type;
|
|
if (t.type !== 'object') continue;
|
|
|
|
if (defaults[propName] === null) continue;
|
|
|
|
const parentChildOverride =
|
|
defaults[propName] && typeof defaults[propName] === 'object' && !Array.isArray(defaults[propName])
|
|
? (defaults[propName] as Record<string, unknown>)
|
|
: {};
|
|
|
|
const nestedSchemaEntry = schema.schemas[t.objectName];
|
|
let nestedVariant: string | undefined;
|
|
if (nestedSchemaEntry?.type === 'multiple') {
|
|
nestedVariant = (parentChildOverride['@type'] as string | undefined) ?? nestedSchemaEntry.variants[0]?.name;
|
|
}
|
|
|
|
const nested = buildEmbeddedDefaults(schema, t.objectName, parentChildOverride, nestedVariant);
|
|
|
|
if (Object.keys(nested).length > 0) {
|
|
defaults[propName] = nested;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (filtersStatic && Object.keys(filtersStatic).length > 0) {
|
|
const validKeys = new Set<string>(['@type']);
|
|
if (activeFields) {
|
|
for (const k of Object.keys(activeFields.properties)) validKeys.add(k);
|
|
}
|
|
if (resolvedSchema.type === 'multiple') {
|
|
const parentSchemaName =
|
|
parentSchemaEntry?.type === 'single' ? parentSchemaEntry.schemaName : resolvedObject.objectName;
|
|
const parentProps = schema.fields[parentSchemaName]?.properties;
|
|
if (parentProps) {
|
|
for (const k of Object.keys(parentProps)) validKeys.add(k);
|
|
}
|
|
}
|
|
|
|
const filtered: Record<string, unknown> = {};
|
|
for (const [k, v] of Object.entries(filtersStatic)) {
|
|
if (validKeys.has(k)) filtered[k] = v;
|
|
}
|
|
if (Object.keys(filtered).length > 0) {
|
|
defaults = deepMerge(defaults, filtered);
|
|
}
|
|
}
|
|
|
|
return defaults;
|
|
}
|
|
|
|
export function buildEmbeddedDefaults(
|
|
schema: Schema,
|
|
objectName: string,
|
|
parentOverrides: Record<string, unknown> = {},
|
|
variantName?: string,
|
|
): Record<string, unknown> {
|
|
const schemaEntry = schema.schemas[objectName];
|
|
if (!schemaEntry) return { ...parentOverrides };
|
|
|
|
let fields: Fields | null = null;
|
|
let result: Record<string, unknown> = {};
|
|
|
|
if (schemaEntry.type === 'single') {
|
|
fields = schema.fields[schemaEntry.schemaName] ?? null;
|
|
} else {
|
|
const variant = variantName ? schemaEntry.variants.find((v) => v.name === variantName) : schemaEntry.variants[0];
|
|
if (!variant) return { '@type': variantName ?? '', ...parentOverrides };
|
|
result['@type'] = variant.name;
|
|
if (variant.schemaName) {
|
|
fields = schema.fields[variant.schemaName] ?? null;
|
|
}
|
|
}
|
|
|
|
if (fields) {
|
|
if (fields.defaults) {
|
|
result = deepMerge(result, fields.defaults);
|
|
}
|
|
|
|
for (const [propName, propDef] of Object.entries(fields.properties)) {
|
|
const t = propDef.type;
|
|
if (t.type !== 'object') continue;
|
|
|
|
const parentChildOverride =
|
|
result[propName] && typeof result[propName] === 'object' && !Array.isArray(result[propName])
|
|
? (result[propName] as Record<string, unknown>)
|
|
: {};
|
|
|
|
const nestedSchemaEntry = schema.schemas[t.objectName];
|
|
let nestedVariant: string | undefined;
|
|
if (nestedSchemaEntry?.type === 'multiple') {
|
|
nestedVariant = (parentChildOverride['@type'] as string | undefined) ?? nestedSchemaEntry.variants[0]?.name;
|
|
}
|
|
|
|
const nestedDefaults = buildEmbeddedDefaults(schema, t.objectName, parentChildOverride, nestedVariant);
|
|
|
|
if (Object.keys(nestedDefaults).length > 0) {
|
|
result[propName] = nestedDefaults;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Object.keys(parentOverrides).length > 0) {
|
|
result = deepMerge(result, parentOverrides);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
export function getDisplayProperty(schema: Schema, objectName: string): string {
|
|
const list = schema.lists[objectName];
|
|
if (list?.labelProperty) return list.labelProperty;
|
|
if (list?.columns?.[0]) return list.columns[0].name;
|
|
return 'name';
|
|
}
|