# Introduction In this PR we've migrated `twenty-shared` from a `vite` app [libary-mode](https://vite.dev/guide/build#library-mode) to a [preconstruct](https://preconstruct.tools/) "atomic" application ( in the future would like to introduce preconstruct to handle of all our atomic dependencies such as `twenty-emails` `twenty-ui` etc it will be integrated at the monorepo's root directly, would be to invasive in the first, starting incremental via `twenty-shared`) For more information regarding the motivations please refer to nor: - https://github.com/twentyhq/core-team-issues/issues/587 - https://github.com/twentyhq/core-team-issues/issues/281#issuecomment-2630949682 close https://github.com/twentyhq/core-team-issues/issues/589 close https://github.com/twentyhq/core-team-issues/issues/590 ## How to test In order to ease the review this PR will ship all the codegen at the very end, the actual meaning full diff is `+2,411 −114` In order to migrate existing dependent packages to `twenty-shared` multi barrel new arch you need to run in local: ```sh yarn tsx packages/twenty-shared/scripts/migrateFromSingleToMultiBarrelImport.ts && \ npx nx run-many -t lint --fix -p twenty-front twenty-ui twenty-server twenty-emails twenty-shared twenty-zapier ``` Note that `migrateFromSingleToMultiBarrelImport` is idempotent, it's atm included in the PR but should not be merged. ( such as codegen will be added before merging this script will be removed ) ## Misc - related opened issue preconstruct https://github.com/preconstruct/preconstruct/issues/617 ## Closed related PR - https://github.com/twentyhq/twenty/pull/11028 - https://github.com/twentyhq/twenty/pull/10993 - https://github.com/twentyhq/twenty/pull/10960 ## Upcoming enhancement: ( in others dedicated PRs ) - 1/ refactor generate barrel to export atomic module instead of `*` - 2/ generate barrel own package with several files and tests - 3/ Migration twenty-ui the same way - 4/ Use `preconstruct` at monorepo global level ## Conclusion As always any suggestions are welcomed !
167 lines
4.5 KiB
TypeScript
167 lines
4.5 KiB
TypeScript
import {
|
|
isArray,
|
|
isNonEmptyString,
|
|
isNumber,
|
|
isObject,
|
|
isString,
|
|
} from '@sniptt/guards';
|
|
import { GraphQLVariables } from 'msw';
|
|
import { isDefined } from 'twenty-shared/utils';
|
|
|
|
type StringFilter = {
|
|
equals?: string;
|
|
contains?: string;
|
|
in?: Array<string>;
|
|
notIn?: Array<string>;
|
|
};
|
|
|
|
const filterData = <DataT>(
|
|
data: Array<DataT>,
|
|
where: Record<string, any>,
|
|
): Array<DataT> =>
|
|
data.filter((item) => {
|
|
// { firstName: {contains: '%string%' }}
|
|
// { lastName: {equals: 'string' }}
|
|
// { is: { company: { equals: 'string' }}}
|
|
let isMatch: boolean = (
|
|
Object.keys(where) as Array<keyof typeof where>
|
|
).every((key) => {
|
|
if (!['OR', 'AND', 'NOT'].includes(key)) {
|
|
const filterElement = where[key] as StringFilter & { is?: object };
|
|
|
|
if (isDefined(filterElement.is)) {
|
|
const nestedKey = Object.keys(filterElement.is)[0] as string;
|
|
if (
|
|
item[key as keyof typeof item] &&
|
|
isObject(item[key as keyof typeof item])
|
|
) {
|
|
const nestedItem = item[key as keyof typeof item];
|
|
return (
|
|
nestedItem[nestedKey as keyof typeof nestedItem] ===
|
|
(
|
|
filterElement.is[
|
|
nestedKey as keyof typeof filterElement.is
|
|
] as StringFilter
|
|
).equals
|
|
);
|
|
}
|
|
}
|
|
if (isNonEmptyString(filterElement.equals)) {
|
|
return item[key as keyof typeof item] === filterElement.equals;
|
|
}
|
|
if (isNonEmptyString(filterElement.contains)) {
|
|
return (item[key as keyof typeof item] as string)
|
|
.toLocaleLowerCase()
|
|
.includes(
|
|
filterElement.contains.replaceAll('%', '').toLocaleLowerCase(),
|
|
);
|
|
}
|
|
if (isArray(filterElement.in)) {
|
|
const itemValue = item[key as keyof typeof item] as string;
|
|
|
|
return filterElement.in.includes(itemValue);
|
|
}
|
|
if (isArray(filterElement.notIn)) {
|
|
const itemValue = item[key as keyof typeof item] as string;
|
|
|
|
if (filterElement.notIn.length === 0) return true;
|
|
|
|
return !filterElement.notIn.includes(itemValue);
|
|
}
|
|
}
|
|
return false;
|
|
});
|
|
|
|
// { OR: [{ firstName: filter }, { lastName: filter }]
|
|
if (isArray(where.OR)) {
|
|
isMatch =
|
|
isMatch ||
|
|
where.OR.some((orFilter: any) =>
|
|
filterData<DataT>(data, orFilter).includes(item),
|
|
);
|
|
}
|
|
|
|
if (isArray(where.AND)) {
|
|
isMatch =
|
|
isMatch ||
|
|
where.AND.every((andFilter: any) =>
|
|
filterData<DataT>(data, andFilter).includes(item),
|
|
);
|
|
}
|
|
|
|
return isMatch;
|
|
});
|
|
|
|
export const filterAndSortData = <DataT>(
|
|
data: Array<DataT>,
|
|
where?: Record<string, any>,
|
|
orderBy?: Array<any>,
|
|
limit?: number,
|
|
): Array<DataT> => {
|
|
let filteredData = data;
|
|
|
|
if (isDefined(where)) {
|
|
filteredData = filterData<DataT>(data, where);
|
|
}
|
|
|
|
if (isArray(orderBy) && orderBy.length > 0 && isDefined(orderBy[0])) {
|
|
const firstOrderBy = orderBy?.[0];
|
|
|
|
const key = Object.keys(firstOrderBy)[0];
|
|
|
|
filteredData.sort((itemA, itemB) => {
|
|
const itemAValue = itemA[key as unknown as keyof typeof itemA];
|
|
const itemBValue = itemB[key as unknown as keyof typeof itemB];
|
|
if (!itemAValue || !itemBValue) {
|
|
return 0;
|
|
}
|
|
|
|
const sortDirection =
|
|
firstOrderBy[key as unknown as keyof typeof firstOrderBy];
|
|
if (isString(itemAValue) && isString(itemBValue)) {
|
|
return sortDirection === 'desc'
|
|
? itemBValue.localeCompare(itemAValue)
|
|
: -itemBValue.localeCompare(itemAValue);
|
|
}
|
|
return 0;
|
|
});
|
|
}
|
|
|
|
if (isNumber(limit) && limit > 0) {
|
|
filteredData = filteredData.slice(0, limit);
|
|
}
|
|
|
|
return filteredData;
|
|
};
|
|
|
|
export const fetchOneFromData = <DataT extends { id: string }>(
|
|
data: Array<DataT>,
|
|
id: string,
|
|
): DataT | undefined => {
|
|
if (!isDefined(id)) {
|
|
throw new Error(
|
|
`id is not defined in updateOneFromData, check that you provided where.id if needed.`,
|
|
);
|
|
}
|
|
|
|
return data.filter((item) => item.id === id)[0];
|
|
};
|
|
|
|
export const updateOneFromData = <DataT extends { id: string }>(
|
|
data: Array<DataT>,
|
|
id: string | undefined,
|
|
payload: GraphQLVariables,
|
|
): DataT | undefined => {
|
|
if (!isDefined(id)) {
|
|
throw new Error(
|
|
`id is not defined in updateOneFromData, check that you provided where.id if needed.`,
|
|
);
|
|
}
|
|
|
|
const object = data.filter((item) => item.id === id)[0];
|
|
|
|
const newObject = Object.assign(object, payload);
|
|
|
|
return newObject;
|
|
};
|