@unchainedshop/api 3.0.0-rc9 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/lib/api-index.d.ts +3 -2
  2. package/lib/api-index.d.ts.map +1 -1
  3. package/lib/api-index.js +3 -2
  4. package/lib/api-index.js.map +1 -1
  5. package/lib/context.d.ts +16 -21
  6. package/lib/context.d.ts.map +1 -1
  7. package/lib/context.js +2 -2
  8. package/lib/context.js.map +1 -1
  9. package/lib/errors.d.ts +1 -0
  10. package/lib/errors.d.ts.map +1 -1
  11. package/lib/errors.js +1 -0
  12. package/lib/errors.js.map +1 -1
  13. package/lib/express/createBulkImportMiddleware.d.ts.map +1 -1
  14. package/lib/express/createBulkImportMiddleware.js +3 -3
  15. package/lib/express/createBulkImportMiddleware.js.map +1 -1
  16. package/lib/express/createERCMetadataMiddleware.d.ts.map +1 -1
  17. package/lib/express/createERCMetadataMiddleware.js +9 -8
  18. package/lib/express/createERCMetadataMiddleware.js.map +1 -1
  19. package/lib/express/index.d.ts +2 -0
  20. package/lib/express/index.d.ts.map +1 -1
  21. package/lib/express/index.js +11 -2
  22. package/lib/express/index.js.map +1 -1
  23. package/lib/fastify/ercMetadataHandler.d.ts.map +1 -1
  24. package/lib/fastify/ercMetadataHandler.js +2 -1
  25. package/lib/fastify/ercMetadataHandler.js.map +1 -1
  26. package/lib/fastify/index.d.ts +4 -1
  27. package/lib/fastify/index.d.ts.map +1 -1
  28. package/lib/fastify/index.js +47 -15
  29. package/lib/fastify/index.js.map +1 -1
  30. package/lib/loaders/index.d.ts +103 -59
  31. package/lib/loaders/index.d.ts.map +1 -1
  32. package/lib/loaders/index.js +173 -111
  33. package/lib/loaders/index.js.map +1 -1
  34. package/lib/locale-context.d.ts +1 -6
  35. package/lib/locale-context.d.ts.map +1 -1
  36. package/lib/locale-context.js +2 -4
  37. package/lib/locale-context.js.map +1 -1
  38. package/lib/resolvers/mutations/accounts/createUser.js +1 -1
  39. package/lib/resolvers/mutations/accounts/createUser.js.map +1 -1
  40. package/lib/resolvers/mutations/accounts/impersonate.d.ts +9 -0
  41. package/lib/resolvers/mutations/accounts/impersonate.d.ts.map +1 -0
  42. package/lib/resolvers/mutations/accounts/impersonate.js +17 -0
  43. package/lib/resolvers/mutations/accounts/impersonate.js.map +1 -0
  44. package/lib/resolvers/mutations/accounts/loginAsGuest.js +1 -1
  45. package/lib/resolvers/mutations/accounts/loginAsGuest.js.map +1 -1
  46. package/lib/resolvers/mutations/accounts/loginWithPassword.js +1 -1
  47. package/lib/resolvers/mutations/accounts/loginWithPassword.js.map +1 -1
  48. package/lib/resolvers/mutations/accounts/loginWithWebAuthn.js +1 -1
  49. package/lib/resolvers/mutations/accounts/loginWithWebAuthn.js.map +1 -1
  50. package/lib/resolvers/mutations/accounts/resetPassword.js +1 -1
  51. package/lib/resolvers/mutations/accounts/resetPassword.js.map +1 -1
  52. package/lib/resolvers/mutations/accounts/stopImpersonation.d.ts +6 -0
  53. package/lib/resolvers/mutations/accounts/stopImpersonation.d.ts.map +1 -0
  54. package/lib/resolvers/mutations/accounts/stopImpersonation.js +14 -0
  55. package/lib/resolvers/mutations/accounts/stopImpersonation.js.map +1 -0
  56. package/lib/resolvers/mutations/accounts/verifyEmail.js +1 -1
  57. package/lib/resolvers/mutations/accounts/verifyEmail.js.map +1 -1
  58. package/lib/resolvers/mutations/assortments/removeAssortmentProduct.js +1 -1
  59. package/lib/resolvers/mutations/assortments/removeAssortmentProduct.js.map +1 -1
  60. package/lib/resolvers/mutations/index.d.ts +2 -0
  61. package/lib/resolvers/mutations/index.d.ts.map +1 -1
  62. package/lib/resolvers/mutations/index.js +5 -1
  63. package/lib/resolvers/mutations/index.js.map +1 -1
  64. package/lib/resolvers/mutations/products/removeProductReviewVote.d.ts +1 -1
  65. package/lib/resolvers/mutations/products/removeProductReviewVote.d.ts.map +1 -1
  66. package/lib/resolvers/mutations/users/addPushSubscription.d.ts.map +1 -1
  67. package/lib/resolvers/mutations/users/addPushSubscription.js +2 -1
  68. package/lib/resolvers/mutations/users/addPushSubscription.js.map +1 -1
  69. package/lib/resolvers/mutations/users/heartbeat.d.ts +1 -1
  70. package/lib/resolvers/mutations/users/heartbeat.d.ts.map +1 -1
  71. package/lib/resolvers/mutations/users/heartbeat.js +2 -2
  72. package/lib/resolvers/mutations/users/heartbeat.js.map +1 -1
  73. package/lib/resolvers/mutations/users/removePushSubscription.d.ts.map +1 -1
  74. package/lib/resolvers/mutations/users/removePushSubscription.js +2 -1
  75. package/lib/resolvers/mutations/users/removePushSubscription.js.map +1 -1
  76. package/lib/resolvers/queries/index.d.ts +3 -0
  77. package/lib/resolvers/queries/index.d.ts.map +1 -1
  78. package/lib/resolvers/queries/index.js +4 -0
  79. package/lib/resolvers/queries/index.js.map +1 -1
  80. package/lib/resolvers/queries/users/impersonator.d.ts +3 -0
  81. package/lib/resolvers/queries/users/impersonator.d.ts.map +1 -0
  82. package/lib/resolvers/queries/users/impersonator.js +8 -0
  83. package/lib/resolvers/queries/users/impersonator.js.map +1 -0
  84. package/lib/resolvers/queries/warehousing/tokens.d.ts +2 -1
  85. package/lib/resolvers/queries/warehousing/tokens.d.ts.map +1 -1
  86. package/lib/resolvers/queries/warehousing/tokens.js +2 -2
  87. package/lib/resolvers/queries/warehousing/tokens.js.map +1 -1
  88. package/lib/resolvers/queries/warehousing/tokensCount.d.ts +5 -0
  89. package/lib/resolvers/queries/warehousing/tokensCount.d.ts.map +1 -0
  90. package/lib/resolvers/queries/warehousing/tokensCount.js +6 -0
  91. package/lib/resolvers/queries/warehousing/tokensCount.js.map +1 -0
  92. package/lib/resolvers/type/assortment/assortment-path-link-types.d.ts +7 -9
  93. package/lib/resolvers/type/assortment/assortment-path-link-types.d.ts.map +1 -1
  94. package/lib/resolvers/type/assortment/assortment-path-link-types.js +9 -7
  95. package/lib/resolvers/type/assortment/assortment-path-link-types.js.map +1 -1
  96. package/lib/resolvers/type/assortment/assortment-types.d.ts +1 -1
  97. package/lib/resolvers/type/assortment/assortment-types.d.ts.map +1 -1
  98. package/lib/resolvers/type/assortment/assortment-types.js +9 -2
  99. package/lib/resolvers/type/assortment/assortment-types.js.map +1 -1
  100. package/lib/resolvers/type/index.d.ts +14 -8
  101. package/lib/resolvers/type/index.d.ts.map +1 -1
  102. package/lib/resolvers/type/product/product-bundle-types.d.ts +1 -1
  103. package/lib/resolvers/type/product/product-bundle-types.d.ts.map +1 -1
  104. package/lib/resolvers/type/product/product-configurable-types.d.ts +1 -1
  105. package/lib/resolvers/type/product/product-configurable-types.d.ts.map +1 -1
  106. package/lib/resolvers/type/product/product-plan-types.d.ts +1 -1
  107. package/lib/resolvers/type/product/product-plan-types.d.ts.map +1 -1
  108. package/lib/resolvers/type/product/product-simple-types.d.ts +1 -1
  109. package/lib/resolvers/type/product/product-simple-types.d.ts.map +1 -1
  110. package/lib/resolvers/type/product/product-tokenized-types.d.ts +1 -1
  111. package/lib/resolvers/type/product/product-tokenized-types.d.ts.map +1 -1
  112. package/lib/resolvers/type/product/product-types.d.ts +1 -1
  113. package/lib/resolvers/type/product/product-types.d.ts.map +1 -1
  114. package/lib/resolvers/type/product/product-types.js +9 -2
  115. package/lib/resolvers/type/product/product-types.js.map +1 -1
  116. package/lib/resolvers/type/product/product-variation-assignment-vector.d.ts.map +1 -1
  117. package/lib/resolvers/type/product/product-variation-assignment-vector.js +4 -1
  118. package/lib/resolvers/type/product/product-variation-assignment-vector.js.map +1 -1
  119. package/lib/resolvers/type/product/product-variation-types.d.ts.map +1 -1
  120. package/lib/resolvers/type/product/product-variation-types.js +5 -2
  121. package/lib/resolvers/type/product/product-variation-types.js.map +1 -1
  122. package/lib/roles/all.d.ts.map +1 -1
  123. package/lib/roles/all.js +2 -0
  124. package/lib/roles/all.js.map +1 -1
  125. package/lib/roles/index.d.ts.map +1 -1
  126. package/lib/roles/index.js +2 -0
  127. package/lib/roles/index.js.map +1 -1
  128. package/lib/schema/mutation.d.ts.map +1 -1
  129. package/lib/schema/mutation.js +10 -0
  130. package/lib/schema/mutation.js.map +1 -1
  131. package/lib/schema/query.d.ts.map +1 -1
  132. package/lib/schema/query.js +11 -1
  133. package/lib/schema/query.js.map +1 -1
  134. package/lib/schema/types/user.js +1 -1
  135. package/package.json +20 -20
  136. package/src/api-index.ts +5 -4
  137. package/src/context.ts +31 -17
  138. package/src/errors.ts +5 -0
  139. package/src/express/createBulkImportMiddleware.ts +3 -3
  140. package/src/express/createERCMetadataMiddleware.ts +10 -8
  141. package/src/express/index.ts +29 -13
  142. package/src/fastify/ercMetadataHandler.ts +3 -1
  143. package/src/fastify/index.ts +67 -23
  144. package/src/loaders/index.ts +273 -228
  145. package/src/locale-context.ts +1 -10
  146. package/src/resolvers/mutations/accounts/createUser.ts +1 -1
  147. package/src/resolvers/mutations/accounts/impersonate.ts +23 -0
  148. package/src/resolvers/mutations/accounts/loginAsGuest.ts +1 -1
  149. package/src/resolvers/mutations/accounts/loginWithPassword.ts +1 -1
  150. package/src/resolvers/mutations/accounts/loginWithWebAuthn.ts +1 -1
  151. package/src/resolvers/mutations/accounts/resetPassword.ts +1 -1
  152. package/src/resolvers/mutations/accounts/stopImpersonation.ts +19 -0
  153. package/src/resolvers/mutations/accounts/verifyEmail.ts +1 -1
  154. package/src/resolvers/mutations/assortments/removeAssortmentProduct.ts +1 -1
  155. package/src/resolvers/mutations/index.ts +6 -1
  156. package/src/resolvers/mutations/users/addPushSubscription.ts +2 -1
  157. package/src/resolvers/mutations/users/heartbeat.ts +2 -2
  158. package/src/resolvers/mutations/users/removePushSubscription.ts +3 -1
  159. package/src/resolvers/queries/index.ts +4 -0
  160. package/src/resolvers/queries/users/impersonator.ts +13 -0
  161. package/src/resolvers/queries/warehousing/tokens.ts +3 -1
  162. package/src/resolvers/queries/warehousing/tokensCount.ts +12 -0
  163. package/src/resolvers/type/assortment/assortment-path-link-types.ts +14 -19
  164. package/src/resolvers/type/assortment/assortment-types.ts +17 -5
  165. package/src/resolvers/type/product/product-types.ts +17 -5
  166. package/src/resolvers/type/product/product-variation-assignment-vector.ts +4 -1
  167. package/src/resolvers/type/product/product-variation-types.ts +5 -2
  168. package/src/roles/all.ts +2 -0
  169. package/src/roles/index.ts +2 -0
  170. package/src/schema/mutation.ts +10 -0
  171. package/src/schema/query.ts +11 -1
  172. package/src/schema/types/user.ts +1 -1
@@ -12,75 +12,22 @@ import {
12
12
  AssortmentMediaText,
13
13
  } from '@unchainedshop/core-assortments';
14
14
  import { File } from '@unchainedshop/core-files';
15
- export interface UnchainedLoaders {
16
- loaders: {
17
- productLoader: InstanceType<typeof DataLoader<{ productId: string }, Product>>;
18
- productLoaderBySKU: InstanceType<typeof DataLoader<{ sku: string }, Product>>;
19
- productTextLoader: InstanceType<
20
- typeof DataLoader<{ productId: string; locale: string }, ProductText>
21
- >;
22
- productMediaTextLoader: InstanceType<
23
- typeof DataLoader<{ productMediaId: string; locale: string }, ProductMediaText>
24
- >;
25
-
26
- fileLoader: InstanceType<typeof DataLoader<{ fileId: string }, File>>;
27
-
28
- filterLoader: InstanceType<typeof DataLoader<{ filterId: string }, Filter>>;
29
- filterTextLoader: InstanceType<
30
- typeof DataLoader<{ filterId: string; filterOptionValue?: string; locale: string }, FilterText>
31
- >;
32
-
33
- assortmentLoader: InstanceType<typeof DataLoader<{ assortmentId: string }, Assortment>>;
34
- assortmentTextLoader: InstanceType<
35
- typeof DataLoader<{ assortmentId: string; locale: string }, AssortmentText>
36
- >;
37
- assortmentLinkLoader: InstanceType<
38
- typeof DataLoader<{ parentAssortmentId: string; childAssortmentId: string }, AssortmentLink>
39
- >;
40
- assortmentLinksLoader: InstanceType<
41
- typeof DataLoader<{ parentAssortmentId?: string; assortmentId?: string }, AssortmentLink[]>
42
- >;
43
- assortmentProductLoader: InstanceType<
44
- typeof DataLoader<{ assortmentId: string; productId: string }, AssortmentProduct>
45
- >;
46
- assortmentMediaTextLoader: InstanceType<
47
- typeof DataLoader<{ assortmentMediaId: string; locale: string }, AssortmentMediaText>
48
- >;
49
-
50
- productMediasLoader: InstanceType<typeof DataLoader<{ productId?: string }, ProductMedia[]>>;
51
- assortmentMediasLoader: InstanceType<
52
- typeof DataLoader<{ assortmentId?: string }, AssortmentMediaType[]>
53
- >;
54
- };
55
- }
56
-
57
- function getLocaleStrings(localeObj: Intl.Locale) {
58
- return [localeObj.baseName, localeObj.language, systemLocale.baseName, systemLocale.language];
59
- }
60
15
 
61
- function findMatchingText(texts, localeStrings) {
62
- return localeStrings.reduce((acc, localeString) => {
63
- if (acc) return acc;
64
- return texts.find((p) => p.locale.toLowerCase() === localeString.toLowerCase());
65
- }, null);
16
+ function getLocaleStrings(locale: string) {
17
+ const localeObj = new Intl.Locale(locale);
18
+ return [
19
+ ...new Set([
20
+ localeObj.baseName.toLowerCase(),
21
+ localeObj.language.toLowerCase(),
22
+ systemLocale.baseName.toLowerCase(),
23
+ systemLocale.language.toLowerCase(),
24
+ ]),
25
+ ];
66
26
  }
67
27
 
68
- function getFilteredQueries({ queries, texts, filterFn }) {
69
- return queries.map(({ locale, ...queryParams }) => {
70
- const localeObj = new Intl.Locale(locale);
71
-
72
- const filteredTexts = texts.filter(filterFn(queryParams));
73
- if (!filteredTexts.length) return null;
74
-
75
- const localeStrings = getLocaleStrings(localeObj);
76
- const filterText = findMatchingText(filteredTexts, localeStrings);
77
- return filterText || filteredTexts[0];
78
- });
79
- }
80
-
81
- const loaders = async (unchainedAPI: UnchainedCore): Promise<UnchainedLoaders['loaders']> => {
28
+ const loaders = async (unchainedAPI: UnchainedCore) => {
82
29
  return {
83
- assortmentLoader: new DataLoader(async (queries) => {
30
+ assortmentLoader: new DataLoader<{ assortmentId: string }, Assortment>(async (queries) => {
84
31
  const assortmentIds = [...new Set(queries.map((q) => q.assortmentId).filter(Boolean))];
85
32
 
86
33
  const assortments = await unchainedAPI.modules.assortments.findAssortments({
@@ -89,35 +36,45 @@ const loaders = async (unchainedAPI: UnchainedCore): Promise<UnchainedLoaders['l
89
36
  includeLeaves: true,
90
37
  });
91
38
 
92
- return queries.map(({ assortmentId }) => {
93
- return assortments.find((assortment) => {
94
- if (assortment._id !== assortmentId) return false;
95
- return true;
96
- });
97
- });
39
+ const assortmentMap = {};
40
+ for (const assortment of assortments) {
41
+ assortmentMap[assortment._id] = assortment;
42
+ }
43
+ return queries.map((q) => assortmentMap[q.assortmentId]);
98
44
  }),
99
45
 
100
- assortmentTextLoader: new DataLoader(async (queries) => {
101
- const assortmentIds = [...new Set(queries.map((q) => q.assortmentId).filter(Boolean))];
46
+ assortmentTextLoader: new DataLoader<{ assortmentId: string; locale: string }, AssortmentText>(
47
+ async (queries) => {
48
+ const assortmentIds = [...new Set(queries.map((q) => q.assortmentId).filter(Boolean))];
102
49
 
103
- const texts = await unchainedAPI.modules.assortments.texts.findTexts(
104
- { assortmentId: { $in: assortmentIds } },
105
- {
106
- sort: {
107
- assortmentId: 1,
50
+ const texts = await unchainedAPI.modules.assortments.texts.findTexts(
51
+ { assortmentId: { $in: assortmentIds } },
52
+ {
53
+ sort: {
54
+ assortmentId: 1,
55
+ },
108
56
  },
109
- },
110
- );
111
-
112
- const filterFn =
113
- ({ assortmentId }) =>
114
- (text) =>
115
- text.assortmentId === assortmentId;
116
-
117
- return getFilteredQueries({ queries, texts, filterFn });
118
- }),
119
-
120
- assortmentMediaTextLoader: new DataLoader(async (queries) => {
57
+ );
58
+
59
+ const queryLocales = [...new Set(queries.map((q) => q.locale.toLowerCase()))];
60
+ const textsMap = {};
61
+ const localeMap = Object.fromEntries(
62
+ queryLocales.flatMap((queryLocale) => {
63
+ return getLocaleStrings(queryLocale).map((locale) => [locale, queryLocale]);
64
+ }),
65
+ );
66
+ for (const text of texts) {
67
+ const locale = localeMap[text.locale.toLowerCase()];
68
+ textsMap[locale + text.assortmentId] = text;
69
+ }
70
+ return queries.map((q) => textsMap[q.locale.toLowerCase() + q.assortmentId]);
71
+ },
72
+ ),
73
+
74
+ assortmentMediaTextLoader: new DataLoader<
75
+ { assortmentMediaId: string; locale: string },
76
+ AssortmentMediaText
77
+ >(async (queries) => {
121
78
  const assortmentMediaIds = [...new Set(queries.map((q) => q.assortmentMediaId).filter(Boolean))];
122
79
 
123
80
  const texts = await unchainedAPI.modules.assortments.media.texts.findMediaTexts(
@@ -129,93 +86,150 @@ const loaders = async (unchainedAPI: UnchainedCore): Promise<UnchainedLoaders['l
129
86
  },
130
87
  );
131
88
 
132
- const filterFn =
133
- ({ assortmentMediaId }) =>
134
- (text) =>
135
- text.assortmentMediaId === assortmentMediaId;
136
-
137
- return getFilteredQueries({ queries, texts, filterFn });
89
+ const queryLocales = [...new Set(queries.map((q) => q.locale.toLowerCase()))];
90
+ const textsMap = {};
91
+ const localeMap = Object.fromEntries(
92
+ queryLocales.flatMap((queryLocale) => {
93
+ return getLocaleStrings(queryLocale).map((locale) => [locale, queryLocale]);
94
+ }),
95
+ );
96
+ for (const text of texts) {
97
+ const locale = localeMap[text.locale.toLowerCase()];
98
+ textsMap[locale + text.assortmentMediaId] = text;
99
+ }
100
+ return queries.map((q) => textsMap[q.locale.toLowerCase() + q.assortmentMediaId]);
138
101
  }),
139
102
 
140
- assortmentMediasLoader: new DataLoader(async (queries) => {
141
- const assortmentIds = [...new Set(queries.map((q) => q.assortmentId).filter(Boolean))];
142
- const assortmentMediaItems = await unchainedAPI.modules.assortments.media.findAssortmentMedias({
143
- assortmentId: { $in: assortmentIds },
144
- });
145
-
146
- return queries.map((q) => {
147
- return assortmentMediaItems.filter((i) => {
148
- return i.assortmentId === q.assortmentId;
103
+ assortmentMediasLoader: new DataLoader<{ assortmentId?: string }, AssortmentMediaType[]>(
104
+ async (queries) => {
105
+ const assortmentIds = [...new Set(queries.map((q) => q.assortmentId).filter(Boolean))];
106
+ const assortmentMediaItems = await unchainedAPI.modules.assortments.media.findAssortmentMedias({
107
+ assortmentId: { $in: assortmentIds },
149
108
  });
150
- });
151
- }),
152
109
 
153
- assortmentLinkLoader: new DataLoader(async (queries) => {
110
+ const assortmentMediaMap = {};
111
+ for (const assortmentMedia of assortmentMediaItems) {
112
+ if (!assortmentMediaMap[assortmentMedia.assortmentId]) {
113
+ assortmentMediaMap[assortmentMedia.assortmentId] = [assortmentMedia];
114
+ } else {
115
+ assortmentMediaMap[assortmentMedia.assortmentId].push(assortmentMedia);
116
+ }
117
+ }
118
+ return queries.map((q) => assortmentMediaMap[q.assortmentId] || []);
119
+ },
120
+ ),
121
+
122
+ assortmentLinkLoader: new DataLoader<
123
+ { parentAssortmentId: string; childAssortmentId: string },
124
+ AssortmentLink
125
+ >(async (queries) => {
154
126
  const parentAssortmentIds = [...new Set(queries.map((q) => q.parentAssortmentId).filter(Boolean))];
155
127
 
156
128
  const links = await unchainedAPI.modules.assortments.links.findLinks({
157
129
  parentAssortmentIds,
158
130
  });
159
131
 
160
- return queries.map(({ parentAssortmentId, childAssortmentId }) => {
161
- return links.find((link) => {
162
- if (link.parentAssortmentId !== parentAssortmentId) return false;
163
- if (childAssortmentId && link.childAssortmentId !== childAssortmentId) return false;
164
- return true;
165
- });
132
+ const assortmentLinkMap = {};
133
+ for (const link of links) {
134
+ if (!assortmentLinkMap[link.parentAssortmentId]) {
135
+ assortmentLinkMap[link.parentAssortmentId] = [link];
136
+ } else {
137
+ assortmentLinkMap[link.parentAssortmentId].push(link);
138
+ }
139
+ }
140
+
141
+ return queries.map((q) => {
142
+ if (q.childAssortmentId) {
143
+ return assortmentLinkMap[q.parentAssortmentId].find(
144
+ (link) => link.childAssortmentId === q.childAssortmentId,
145
+ );
146
+ }
147
+ return assortmentLinkMap[q.parentAssortmentId][0];
166
148
  });
167
149
  }),
168
150
 
169
- assortmentLinksLoader: new DataLoader(async (queries) => {
170
- const parentAssortmentIds = [
171
- ...new Set(queries.flatMap((q) => q.parentAssortmentId).filter(Boolean)),
172
- ];
173
- const assortmentIds = [...new Set(queries.flatMap((q) => q.assortmentId).filter(Boolean))];
174
-
175
- const linksByParentAssortmentId =
176
- parentAssortmentIds?.length &&
177
- (await unchainedAPI.modules.assortments.links.findLinks({
178
- parentAssortmentIds,
179
- }));
180
- const linksByAssortmentId =
181
- assortmentIds?.length &&
182
- (await unchainedAPI.modules.assortments.links.findLinks({
183
- assortmentIds,
184
- }));
185
-
186
- return queries.map(({ parentAssortmentId, assortmentId }) => {
187
- if (parentAssortmentId) {
188
- return linksByParentAssortmentId.filter(
189
- (link) => link.parentAssortmentId === parentAssortmentId,
190
- );
151
+ assortmentLinksLoader: new DataLoader<
152
+ { parentAssortmentId?: string; childAssortmentId?: string; assortmentId?: string },
153
+ AssortmentLink[]
154
+ >(async (queries) => {
155
+ const parentAssortmentIds = queries.flatMap((q) => q.parentAssortmentId).filter(Boolean);
156
+ const childAssortmentIds = queries.flatMap((q) => q.childAssortmentId).filter(Boolean);
157
+ const assortmentIds = queries.flatMap((q) => q.assortmentId).filter(Boolean);
158
+
159
+ const allLinks = await unchainedAPI.modules.assortments.links.findLinks({
160
+ assortmentIds: [...new Set([...parentAssortmentIds, ...childAssortmentIds, ...assortmentIds])],
161
+ });
162
+
163
+ const parentAssortmentLinkMap = {};
164
+ const childAssortmentLinkMap = {};
165
+
166
+ for (const link of allLinks) {
167
+ if (!parentAssortmentLinkMap[link.parentAssortmentId]) {
168
+ parentAssortmentLinkMap[link.parentAssortmentId] = [link];
169
+ } else {
170
+ parentAssortmentLinkMap[link.parentAssortmentId].push(link);
191
171
  }
192
- if (assortmentId) {
193
- return linksByAssortmentId.filter(
194
- (link) =>
195
- link.parentAssortmentId === assortmentId || link.childAssortmentId === assortmentId,
196
- );
172
+
173
+ if (!childAssortmentLinkMap[link.childAssortmentId]) {
174
+ childAssortmentLinkMap[link.childAssortmentId] = [link];
175
+ } else {
176
+ childAssortmentLinkMap[link.childAssortmentId].push(link);
177
+ }
178
+ }
179
+
180
+ return queries.map((q) => {
181
+ if (q.parentAssortmentId) {
182
+ return parentAssortmentLinkMap[q.parentAssortmentId] || [];
183
+ } else if (q.childAssortmentId) {
184
+ return childAssortmentLinkMap[q.childAssortmentId] || [];
197
185
  }
198
- return [];
186
+ return [
187
+ ...(parentAssortmentLinkMap[q.assortmentId] || []),
188
+ ...(childAssortmentLinkMap[q.assortmentId] || []),
189
+ ];
199
190
  });
200
191
  }),
201
192
 
202
- assortmentProductLoader: new DataLoader(async (queries) => {
193
+ assortmentProductLoader: new DataLoader<
194
+ { assortmentId: string; productId: string },
195
+ AssortmentProduct
196
+ >(async (queries) => {
203
197
  const assortmentIds = [...new Set(queries.map((q) => q.assortmentId).filter(Boolean))];
204
198
 
205
- const assortmentProducts = await unchainedAPI.modules.assortments.products.findProducts({
199
+ const assortmentProducts = await unchainedAPI.modules.assortments.products.findAssortmentProducts({
206
200
  assortmentIds,
207
201
  });
208
202
 
209
- return queries.map(({ assortmentId, productId }) => {
210
- return assortmentProducts.find((assortmentProduct) => {
211
- if (assortmentProduct.assortmentId !== assortmentId) return false;
212
- if (assortmentProduct.productId !== productId) return false;
213
- return true;
214
- });
215
- });
203
+ const assortmentProductMap = {};
204
+ for (const assortmentProduct of assortmentProducts) {
205
+ assortmentProductMap[assortmentProduct.assortmentId + assortmentProduct.productId] =
206
+ assortmentProduct;
207
+ }
208
+ return queries.map((q) => assortmentProductMap[q.assortmentId + q.productId]);
216
209
  }),
217
210
 
218
- filterLoader: new DataLoader(async (queries) => {
211
+ assortmentProductsLoader: new DataLoader<{ productId: string }, AssortmentProduct[]>(
212
+ async (queries) => {
213
+ const productIds = [...new Set(queries.map((q) => q.productId).filter(Boolean))];
214
+
215
+ const assortmentProducts =
216
+ await unchainedAPI.modules.assortments.products.findAssortmentProducts({
217
+ productIds,
218
+ });
219
+
220
+ const assortmentProductsMap = {};
221
+ for (const assortmentProduct of assortmentProducts) {
222
+ if (!assortmentProductsMap[assortmentProduct.productId]) {
223
+ assortmentProductsMap[assortmentProduct.productId] = [assortmentProduct];
224
+ } else {
225
+ assortmentProductsMap[assortmentProduct.productId].push(assortmentProduct);
226
+ }
227
+ }
228
+ return queries.map((q) => assortmentProductsMap[q.productId] || []);
229
+ },
230
+ ),
231
+
232
+ filterLoader: new DataLoader<{ filterId: string }, Filter>(async (queries) => {
219
233
  const filterIds = [...new Set(queries.map((q) => q.filterId).filter(Boolean))];
220
234
 
221
235
  const filters = await unchainedAPI.modules.filters.findFilters({
@@ -223,15 +237,18 @@ const loaders = async (unchainedAPI: UnchainedCore): Promise<UnchainedLoaders['l
223
237
  includeInactive: true,
224
238
  });
225
239
 
226
- return queries.map(({ filterId }) => {
227
- return filters.find((product) => {
228
- if (product._id !== filterId) return false;
229
- return true;
230
- });
231
- });
240
+ const filterMap = {};
241
+ for (const filter of filters) {
242
+ filterMap[filter._id] = filter;
243
+ }
244
+
245
+ return queries.map((q) => filterMap[q.filterId]);
232
246
  }),
233
247
 
234
- filterTextLoader: new DataLoader(async (queries) => {
248
+ filterTextLoader: new DataLoader<
249
+ { filterId: string; filterOptionValue?: string; locale: string },
250
+ FilterText
251
+ >(async (queries) => {
235
252
  const filterIds = [...new Set(queries.map((q) => q.filterId).filter(Boolean))];
236
253
 
237
254
  const texts = await unchainedAPI.modules.filters.texts.findTexts(
@@ -243,14 +260,21 @@ const loaders = async (unchainedAPI: UnchainedCore): Promise<UnchainedLoaders['l
243
260
  },
244
261
  );
245
262
 
246
- const filterFn =
247
- ({ filterId, filterOptionValue }) =>
248
- (text) =>
249
- text.filterId === filterId && text.filterOptionValue === filterOptionValue;
250
- return getFilteredQueries({ queries, texts, filterFn });
263
+ const queryLocales = [...new Set(queries.map((q) => q.locale.toLowerCase()))];
264
+ const textsMap = {};
265
+ const localeMap = Object.fromEntries(
266
+ queryLocales.flatMap((queryLocale) => {
267
+ return getLocaleStrings(queryLocale).map((locale) => [locale, queryLocale]);
268
+ }),
269
+ );
270
+ for (const text of texts) {
271
+ const locale = localeMap[text.locale.toLowerCase()];
272
+ textsMap[locale + text.filterId + text.filterOptionValue] = text;
273
+ }
274
+ return queries.map((q) => textsMap[q.locale.toLowerCase() + q.filterId + q.filterOptionValue]);
251
275
  }),
252
276
 
253
- productLoader: new DataLoader(async (queries) => {
277
+ productLoader: new DataLoader<{ productId: string }, Product>(async (queries) => {
254
278
  const productIds = [...new Set(queries.map((q) => q.productId).filter(Boolean))]; // you don't need lodash, _.unique my ass
255
279
 
256
280
  const products = await unchainedAPI.modules.products.findProducts({
@@ -260,15 +284,15 @@ const loaders = async (unchainedAPI: UnchainedCore): Promise<UnchainedLoaders['l
260
284
  },
261
285
  });
262
286
 
263
- return queries.map(({ productId }) => {
264
- return products.find((product) => {
265
- if (product._id !== productId) return false;
266
- return true;
267
- });
268
- });
287
+ const productMap = {};
288
+ for (const product of products) {
289
+ productMap[product._id] = product;
290
+ }
291
+
292
+ return queries.map((q) => productMap[q.productId]);
269
293
  }),
270
294
 
271
- productLoaderBySKU: new DataLoader(async (queries) => {
295
+ productLoaderBySKU: new DataLoader<{ sku: string }, Product>(async (queries) => {
272
296
  const skus = [...new Set(queries.map((q) => q.sku).filter(Boolean))]; // you don't need lodash, _.unique my ass
273
297
 
274
298
  const products = await unchainedAPI.modules.products.findProducts({
@@ -278,81 +302,102 @@ const loaders = async (unchainedAPI: UnchainedCore): Promise<UnchainedLoaders['l
278
302
  },
279
303
  });
280
304
 
281
- return queries.map(({ sku }) => {
282
- return products.find((product) => {
283
- if (product.warehousing?.sku !== sku) return false;
284
- return true;
285
- });
286
- });
287
- }),
288
-
289
- productTextLoader: new DataLoader(async (queries) => {
290
- const productIds = [...new Set(queries.map((q) => q.productId).filter(Boolean))];
291
-
292
- const texts = await unchainedAPI.modules.products.texts.findTexts(
293
- { productId: { $in: productIds } },
294
- {
295
- sort: {
296
- productId: 1,
297
- },
298
- },
299
- );
300
-
301
- const filterFn =
302
- ({ productId }) =>
303
- (text) =>
304
- text.productId === productId;
305
+ const productMap = {};
306
+ for (const product of products) {
307
+ productMap[product.warehousing!.sku!] = product;
308
+ }
305
309
 
306
- return getFilteredQueries({ queries, texts, filterFn });
310
+ return queries.map((q) => productMap[q.sku]);
307
311
  }),
308
312
 
309
- productMediaTextLoader: new DataLoader(async (queries) => {
310
- const productMediaIds = [...new Set(queries.map((q) => q.productMediaId).filter(Boolean))];
311
-
312
- const texts = await unchainedAPI.modules.products.media.texts.findMediaTexts(
313
- { productMediaId: { $in: productMediaIds } },
314
- {
315
- sort: {
316
- productMediaId: 1,
313
+ productTextLoader: new DataLoader<{ productId: string; locale: string }, ProductText>(
314
+ async (queries) => {
315
+ const productIds = [...new Set(queries.map((q) => q.productId))].filter(Boolean);
316
+ const texts = await unchainedAPI.modules.products.texts.findTexts(
317
+ { productId: { $in: productIds } },
318
+ {
319
+ sort: {
320
+ productId: 1,
321
+ },
317
322
  },
318
- },
319
- );
320
-
321
- const filterFn =
322
- ({ productMediaId }) =>
323
- (text) =>
324
- text.productMediaId === productMediaId;
325
-
326
- return getFilteredQueries({ queries, texts, filterFn });
327
- }),
323
+ );
324
+ const queryLocales = [...new Set(queries.map((q) => q.locale.toLowerCase()))];
325
+ const textsMap = {};
326
+ const localeMap = Object.fromEntries(
327
+ queryLocales.flatMap((queryLocale) => {
328
+ return getLocaleStrings(queryLocale).map((locale) => [locale, queryLocale]);
329
+ }),
330
+ );
331
+ for (const text of texts) {
332
+ const locale = localeMap[text.locale.toLowerCase()];
333
+ textsMap[locale + text.productId] = text;
334
+ }
335
+ return queries.map((q) => textsMap[q.locale.toLowerCase() + q.productId]);
336
+ },
337
+ ),
338
+
339
+ productMediaTextLoader: new DataLoader<{ productMediaId: string; locale: string }, ProductMediaText>(
340
+ async (queries) => {
341
+ const productMediaIds = [...new Set(queries.map((q) => q.productMediaId).filter(Boolean))];
342
+
343
+ const texts = await unchainedAPI.modules.products.media.texts.findMediaTexts(
344
+ { productMediaId: { $in: productMediaIds } },
345
+ {
346
+ sort: {
347
+ productMediaId: 1,
348
+ },
349
+ },
350
+ );
351
+
352
+ const queryLocales = [...new Set(queries.map((q) => q.locale.toLowerCase()))];
353
+ const textsMap = {};
354
+ const localeMap = Object.fromEntries(
355
+ queryLocales.flatMap((queryLocale) => {
356
+ return getLocaleStrings(queryLocale).map((locale) => [locale, queryLocale]);
357
+ }),
358
+ );
359
+ for (const text of texts) {
360
+ const locale = localeMap[text.locale.toLowerCase()];
361
+ textsMap[locale + text.productMediaId] = text;
362
+ }
363
+ return queries.map((q) => textsMap[q.locale.toLowerCase() + q.productMediaId]);
364
+ },
365
+ ),
328
366
 
329
- productMediasLoader: new DataLoader(async (queries) => {
367
+ productMediasLoader: new DataLoader<{ productId?: string }, ProductMedia[]>(async (queries) => {
330
368
  const productIds = [...new Set(queries.map((q) => q.productId).filter(Boolean))];
331
369
  const productMediaItems = await unchainedAPI.modules.products.media.findProductMedias({
332
370
  productId: { $in: productIds },
333
371
  });
334
- return queries.map((q) => {
335
- return productMediaItems.filter((i) => {
336
- return i.productId === q.productId;
337
- });
338
- });
372
+
373
+ const productMediaMap = {};
374
+ for (const productMedia of productMediaItems) {
375
+ if (!productMediaMap[productMedia.productId]) {
376
+ productMediaMap[productMedia.productId] = [productMedia];
377
+ } else {
378
+ productMediaMap[productMedia.productId].push(productMedia);
379
+ }
380
+ }
381
+ return queries.map((q) => productMediaMap[q.productId] || []);
339
382
  }),
340
383
 
341
- fileLoader: new DataLoader(async (queries) => {
384
+ fileLoader: new DataLoader<{ fileId: string }, File>(async (queries) => {
342
385
  const fileIds = [...new Set(queries.map((q) => q.fileId).filter(Boolean))]; // you don't need lodash, _.unique my ass
343
386
 
344
387
  const files = await unchainedAPI.modules.files.findFiles({
345
388
  _id: { $in: fileIds },
346
389
  });
347
390
 
348
- return queries.map(({ fileId }) => {
349
- return files.find((file) => {
350
- if (file._id !== fileId) return false;
351
- return true;
352
- });
353
- });
391
+ const fileMap = {};
392
+ for (const file of files) {
393
+ fileMap[file._id] = file;
394
+ }
395
+
396
+ return queries.map((q) => fileMap[q.fileId]);
354
397
  }),
355
398
  };
356
399
  };
357
400
 
401
+ export type UnchainedLoaders = Awaited<ReturnType<typeof loaders>>;
402
+
358
403
  export default loaders;
@@ -15,9 +15,6 @@ export interface UnchainedLocaleContext {
15
15
  countryContext: string;
16
16
  localeContext: Intl.Locale;
17
17
  currencyContext: string;
18
- remoteAddress?: string;
19
- remotePort?: string;
20
- userAgent?: string;
21
18
  }
22
19
  const { NODE_ENV } = process.env;
23
20
 
@@ -76,20 +73,14 @@ export const resolveDefaultContext = pMemoize(
76
73
 
77
74
  export const getLocaleContext = async (
78
75
  {
79
- remoteAddress,
80
- remotePort,
81
76
  getHeader,
82
77
  }: {
83
- remoteAddress: string;
84
- remotePort: number | string;
85
78
  getHeader: UnchainedHTTPServerContext['getHeader'];
86
79
  },
87
80
  unchainedAPI: UnchainedCore,
88
81
  ): Promise<UnchainedLocaleContext> => {
89
- const userAgent = getHeader('user-agent');
90
- const context = await resolveDefaultContext(
82
+ return resolveDefaultContext(
91
83
  { acceptLang: getHeader('accept-language'), acceptCountry: getHeader('x-shop-country') },
92
84
  unchainedAPI,
93
85
  );
94
- return { remoteAddress, remotePort: String(remotePort), userAgent, ...context };
95
86
  };
@@ -37,7 +37,7 @@ export default async function createUser(root: never, params: UserData, context:
37
37
  user = await context.modules.users.updateHeartbeat(user._id, {
38
38
  remoteAddress: context.remoteAddress,
39
39
  remotePort: context.remotePort,
40
- userAgent: context.userAgent,
40
+ userAgent: context.getHeader('user-agent'),
41
41
  locale: context.localeContext.baseName,
42
42
  countryCode: context.countryContext,
43
43
  });