Skip to content

Commit

Permalink
Improve performance for breadcrubs by an order of magnitude
Browse files Browse the repository at this point in the history
  • Loading branch information
pozylon committed Jan 1, 2025
1 parent 1d8a5f2 commit 47e8026
Show file tree
Hide file tree
Showing 13 changed files with 427 additions and 393 deletions.
2 changes: 1 addition & 1 deletion packages/api/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ export type Context = UnchainedCore & {
version?: string;
roles?: any;
adminUiConfig?: AdminUiConfig;
loaders: UnchainedLoaders;
} & UnchainedUserContext &
UnchainedLocaleContext &
UnchainedLoaders &
UnchainedHTTPServerContext;

let context;
Expand Down
380 changes: 198 additions & 182 deletions packages/api/src/loaders/index.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default async function removeAssortmentProduct(

if (!assortmentProductId) throw new InvalidIdError({ assortmentProductId });

const assortmentProduct = await modules.assortments.products.findProduct({
const assortmentProduct = await modules.assortments.products.findAssortmentProduct({
assortmentProductId,
});
if (!assortmentProduct) throw new AssortmentProductNotFoundError({ assortmentProductId });
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
import { AssortmentLink } from '@unchainedshop/core-assortments';
import { Context } from '../../../context.js';
import {
AssortmentPathLink as AssortmentPathLinkType,
AssortmentLink as AssortmentLinkType,
AssortmentText,
} from '@unchainedshop/core-assortments';

type HelperType<P, T> = (assortmentPathLink: AssortmentPathLinkType, params: P, context: Context) => T;

export interface AssortmentPathLinkHelperTypes {
link: HelperType<never, Promise<AssortmentLinkType>>;
assortmentTexts: HelperType<{ forceLocale?: string }, Promise<AssortmentText>>;
}
export const AssortmentPathLink = {
link(linkObj) {
if (linkObj._id) return linkObj;
return null;
},

export const AssortmentPathLink: AssortmentPathLinkHelperTypes = {
link: async ({ assortmentId, childAssortmentId }, _, { loaders }) => {
return loaders.assortmentLinkLoader.load({
parentAssortmentId: assortmentId,
childAssortmentId,
});
assortmentId(linkObj) {
return linkObj.childAssortmentId;
},

assortmentTexts: async ({ assortmentId }, params, { loaders, localeContext }) => {
assortmentTexts: async (
{ childAssortmentId }: AssortmentLink,
params: { forceLocale?: string },
{ loaders, localeContext }: Context,
) => {
const text = await loaders.assortmentTextLoader.load({
assortmentId,
assortmentId: childAssortmentId,
locale: params.forceLocale || localeContext.baseName,
});
return text;
Expand Down
22 changes: 17 additions & 5 deletions packages/api/src/resolvers/type/assortment/assortment-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,22 @@ import { Assortment } from '@unchainedshop/core-assortments';
import { SearchFilterQuery } from '@unchainedshop/core-filters';

export const AssortmentTypes = {
assortmentPaths: (obj: Assortment, _, { modules }: Context) => {
return modules.assortments.breadcrumbs({
assortmentId: obj._id,
});
assortmentPaths(obj: Assortment, _, { modules, loaders }: Context) {
return modules.assortments.breadcrumbs(
{
assortmentId: obj._id,
},
{
resolveAssortmentProducts: async (productId) =>
loaders.assortmentProductsLoader.load({
productId,
}),
resolveAssortmentLinks: async (childAssortmentId) =>
loaders.assortmentLinksLoader.load({
childAssortmentId,
}),
},
);
},

children: async (
Expand Down Expand Up @@ -79,7 +91,7 @@ export const AssortmentTypes = {

async productAssignments(obj: Assortment, _, { modules }: Context) {
// TODO: Loader & move default sort to core module
return modules.assortments.products.findProducts(
return modules.assortments.products.findAssortmentProducts(
{
assortmentId: obj._id,
},
Expand Down
22 changes: 17 additions & 5 deletions packages/api/src/resolvers/type/product/product-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,27 @@ export const Product = {
params: {
forceLocale?: string;
},
{ modules }: Context,
{ modules, loaders }: Context,
): Promise<
Array<{
links: Array<AssortmentPathLink>;
}>
> {
return modules.assortments.breadcrumbs({
productId: product._id,
});
return modules.assortments.breadcrumbs(
{
productId: product._id,
},
{
resolveAssortmentProducts: async (productId) =>
loaders.assortmentProductsLoader.load({
productId,
}),
resolveAssortmentLinks: async (childAssortmentId) =>
loaders.assortmentLinksLoader.load({
childAssortmentId,
}),
},
);
},

// TODO: Use a loader!
Expand Down Expand Up @@ -103,7 +115,7 @@ export const Product = {

if (!assortmentIds.length) return [];

const productIds = await modules.assortments.products.findProductSiblings({
const productIds = await modules.assortments.products.findSiblings({
productId,
assortmentIds,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { emit, registerEvents } from '@unchainedshop/events';
import { generateDbFilterById, generateDbObjectId, mongodb } from '@unchainedshop/mongodb';
import { walkUpFromAssortment } from '../utils/breadcrumbs/build-paths.js';
import { resolveAssortmentLinkFromDatabase } from '../utils/breadcrumbs/resolveAssortmentLinkFromDatabase.js';
import { AssortmentLink, InvalidateCacheFn } from '../db/AssortmentsCollection.js';

const ASSORTMENT_LINK_EVENTS = [
Expand Down Expand Up @@ -110,33 +109,36 @@ export const configureAssortmentLinksModule = ({
create: async (doc, options) => {
const { _id: assortmentLinkId, parentAssortmentId, childAssortmentId, sortKey, ...rest } = doc;

const selector = {
...(assortmentLinkId ? generateDbFilterById(assortmentLinkId) : {}),
parentAssortmentId,
childAssortmentId,
};
const assortmentLinksPath = await walkUpFromAssortment({
resolveAssortmentLinks: async (id: string) => {
return AssortmentLinks.find(
{ childAssortmentId: id },
{
projection: { _id: 1, childAssortmentId: 1, parentAssortmentId: 1 },
sort: { sortKey: 1, parentAssortmentId: 1 },
},
).toArray();
},
assortmentId: parentAssortmentId,
});
const assortmentIdAlreadyPartOfGraphParents = assortmentLinksPath.some((path) =>
path.links?.some(
(l) => l.parentAssortmentId === childAssortmentId || l.childAssortmentId === childAssortmentId,
),
);
if (assortmentIdAlreadyPartOfGraphParents) throw Error('CyclicGraphNotSupported');

const $set: any = {
const $set: mongodb.UpdateFilter<AssortmentLink> = {
updated: new Date(),
...rest,
};
const $setOnInsert: any = {
const $setOnInsert: mongodb.UpdateFilter<AssortmentLink> = {
_id: assortmentLinkId || generateDbObjectId(),
parentAssortmentId,
childAssortmentId,
created: new Date(),
};

const assortmentLinksPath = await walkUpFromAssortment({
resolveAssortmentLink: resolveAssortmentLinkFromDatabase(AssortmentLinks),
assortmentId: parentAssortmentId,
});
assortmentLinksPath
.flatMap(({ links }) => links)
.forEach(({ parentIds }) => {
if (parentIds.includes(childAssortmentId)) throw Error('CyclicGraphNotSupported');
});

if (sortKey === undefined || sortKey === null) {
// Get next sort key
const lastAssortmentLink = (await AssortmentLinks.findOne(
Expand All @@ -149,7 +151,11 @@ export const configureAssortmentLinksModule = ({
}

const assortmentLink = await AssortmentLinks.findOneAndUpdate(
selector,
{
...(assortmentLinkId ? generateDbFilterById(assortmentLinkId) : {}),
parentAssortmentId,
childAssortmentId,
},
{
$set,
$setOnInsert,
Expand Down
Loading

0 comments on commit 47e8026

Please sign in to comment.