strapi-plugin-meilisearch 0.16.0 → 0.16.2

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.
@@ -89,10 +89,320 @@ const bootstrap = async ({ strapi: strapi2 }) => {
89
89
  };
90
90
  const destroy = () => {
91
91
  };
92
+ function isObject(elem) {
93
+ return typeof elem === "object" && !Array.isArray(elem) && elem !== null;
94
+ }
95
+ const normalizeEntryLocale = (locale) => {
96
+ if (locale === "all") return "*";
97
+ return locale;
98
+ };
99
+ const normalizeEntryScope = (options = {}) => {
100
+ const { filters = {}, status = "published", locale } = options;
101
+ return {
102
+ filters,
103
+ status,
104
+ locale: normalizeEntryLocale(locale)
105
+ };
106
+ };
107
+ const isWildcardLocale = (locale) => normalizeEntryLocale(locale) === "*";
108
+ const aborted = ({ contentType: contentType2, action }) => {
109
+ strapi.log.error(
110
+ `Indexing of ${contentType2} aborted as the data could not be ${action}`
111
+ );
112
+ return [];
113
+ };
114
+ const configurationService = ({ strapi: strapi2 }) => {
115
+ const meilisearchConfig = strapi2.config.get("plugin::meilisearch") || {};
116
+ const contentTypeService2 = strapi2.plugin("meilisearch").service("contentType");
117
+ return {
118
+ /**
119
+ * Get the names of the indexes from Meilisearch in which the contentType content is added.
120
+ *
121
+ * @param {object} options
122
+ * @param {string} options.contentType - ContentType name.
123
+ *
124
+ * @return {String[]} - Index names
125
+ */
126
+ getIndexNamesOfContentType: function({ contentType: contentType2 }) {
127
+ const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
128
+ const contentTypeConfig = meilisearchConfig[collection] || {};
129
+ let indexNames = [contentTypeConfig.indexName].flat(2).filter((index2) => index2);
130
+ return indexNames.length > 0 ? indexNames : [collection];
131
+ },
132
+ /**
133
+ * Get the entries query rule of a content-type that are applied when fetching entries in the Strapi database.
134
+ *
135
+ * @param {object} options
136
+ * @param {string} options.contentType - ContentType name.
137
+ *
138
+ * @return {String} - EntriesQuery rules.
139
+ */
140
+ entriesQuery: function({ contentType: contentType2 }) {
141
+ const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
142
+ const contentTypeConfig = meilisearchConfig[collection] || {};
143
+ return contentTypeConfig.entriesQuery || {};
144
+ },
145
+ /**
146
+ * Transform contentTypes entries before indexation in Meilisearch.
147
+ *
148
+ * @param {object} options
149
+ * @param {string} options.contentType - ContentType name.
150
+ * @param {Array<Object>} options.entries - The data to convert. Conversion will use
151
+ * the static method `toSearchIndex` defined in the model definition
152
+ *
153
+ * @return {Promise<Array<Object>>} - Converted or mapped data
154
+ */
155
+ transformEntries: async function({ contentType: contentType2, entries = [] }) {
156
+ const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
157
+ const contentTypeConfig = meilisearchConfig[collection] || {};
158
+ try {
159
+ if (Array.isArray(entries) && typeof contentTypeConfig?.transformEntry === "function") {
160
+ const transformed = await Promise.all(
161
+ entries.map(
162
+ async (entry) => await contentTypeConfig.transformEntry({
163
+ entry,
164
+ contentType: contentType2
165
+ })
166
+ )
167
+ );
168
+ if (transformed.length > 0 && !isObject(transformed[0])) {
169
+ return aborted({ contentType: contentType2, action: "transformed" });
170
+ }
171
+ return transformed;
172
+ }
173
+ } catch (e) {
174
+ strapi2.log.error(e);
175
+ return aborted({ contentType: contentType2, action: "transformed" });
176
+ }
177
+ return entries;
178
+ },
179
+ /**
180
+ * Filter contentTypes entries before indexation in Meilisearch.
181
+ *
182
+ * @param {object} options
183
+ * @param {string} options.contentType - ContentType name.
184
+ * @param {Array<Object>} options.entries - The data to convert. Conversion will use
185
+ * the static method `toSearchIndex` defined in the model definition
186
+ *
187
+ * @return {Promise<Array<Object>>} - Converted or mapped data
188
+ */
189
+ filterEntries: async function({ contentType: contentType2, entries = [] }) {
190
+ const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
191
+ const contentTypeConfig = meilisearchConfig[collection] || {};
192
+ try {
193
+ if (Array.isArray(entries) && typeof contentTypeConfig?.filterEntry === "function") {
194
+ const filtered = await entries.reduce(
195
+ async (filteredEntries, entry) => {
196
+ const isValid = await contentTypeConfig.filterEntry({
197
+ entry,
198
+ contentType: contentType2
199
+ });
200
+ if (!isValid) return filteredEntries;
201
+ const syncFilteredEntries = await filteredEntries;
202
+ return [...syncFilteredEntries, entry];
203
+ },
204
+ []
205
+ );
206
+ return filtered;
207
+ }
208
+ } catch (e) {
209
+ strapi2.log.error(e);
210
+ return aborted({ contentType: contentType2, action: "filtered" });
211
+ }
212
+ return entries;
213
+ },
214
+ /**
215
+ * Returns Meilisearch index settings from model definition.
216
+ *
217
+ * @param {object} options
218
+ * @param {string} options.contentType - ContentType name.
219
+ * @param {Array<Object>} [options.entries] - The data to convert. Conversion will use
220
+
221
+ * @typedef Settings
222
+ * @type {import('meilisearch').Settings}
223
+ * @return {Settings} - Meilisearch index settings
224
+ */
225
+ getSettings: function({ contentType: contentType2 }) {
226
+ const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
227
+ const contentTypeConfig = meilisearchConfig[collection] || {};
228
+ const settings = contentTypeConfig.settings || {};
229
+ return settings;
230
+ },
231
+ /**
232
+ * Return all contentTypes having the provided indexName setting.
233
+ *
234
+ * @param {object} options
235
+ * @param {string} options.indexName - Index in Meilisearch.
236
+ *
237
+ * @returns {string[]} List of contentTypes storing its data in the provided indexName
238
+ */
239
+ listContentTypesWithCustomIndexName: function({ indexName }) {
240
+ const contentTypes = strapi2.plugin("meilisearch").service("contentType").getContentTypesUid() || [];
241
+ const collectionNames = contentTypes.map(
242
+ (contentType2) => contentTypeService2.getCollectionName({ contentType: contentType2 })
243
+ );
244
+ const contentTypeWithIndexName = collectionNames.filter((contentType2) => {
245
+ const names = this.getIndexNamesOfContentType({
246
+ contentType: contentType2
247
+ });
248
+ return names.includes(indexName);
249
+ });
250
+ return contentTypeWithIndexName;
251
+ },
252
+ /**
253
+ * Remove sensitive fields (password, author, etc, ..) from entry.
254
+ *
255
+ * @param {object} options
256
+ * @param {Array<Object>} options.entries - The entries to sanitize
257
+ *
258
+ *
259
+ * @return {Array<Object>} - Entries
260
+ */
261
+ removeSensitiveFields: function({ contentType: contentType2, entries }) {
262
+ const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
263
+ const contentTypeConfig = meilisearchConfig[collection] || {};
264
+ const noSanitizePrivateFields = contentTypeConfig.noSanitizePrivateFields || [];
265
+ if (noSanitizePrivateFields.includes("*")) {
266
+ return entries;
267
+ }
268
+ const attrs = strapi2.contentTypes[contentType2].attributes;
269
+ const privateFields = Object.entries(attrs).map(
270
+ ([field, schema]) => schema.private && !noSanitizePrivateFields.includes(field) ? field : false
271
+ );
272
+ return entries.map((entry) => {
273
+ privateFields.forEach((attr) => delete entry[attr]);
274
+ return entry;
275
+ });
276
+ },
277
+ /**
278
+ * Remove unpublished entries from array of entries
279
+ * unless `status` is set to 'draft'.
280
+ *
281
+ * @param {object} options
282
+ * @param {Array<Object>} options.entries - The entries to filter.
283
+ * @param {string} options.contentType - ContentType name.
284
+ *
285
+ * @return {Array<Object>} - Published entries.
286
+ */
287
+ removeUnpublishedArticles: function({ entries, contentType: contentType2 }) {
288
+ const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
289
+ const contentTypeConfig = meilisearchConfig[collection] || {};
290
+ const entriesQuery = contentTypeConfig.entriesQuery || {};
291
+ if (entriesQuery.status === "draft") {
292
+ return entries;
293
+ } else {
294
+ return entries.filter(
295
+ (entry) => !(entry?.publishedAt === void 0 || entry?.publishedAt === null)
296
+ );
297
+ }
298
+ },
299
+ /**
300
+ * Remove language entries.
301
+ * In the plugin entriesQuery, if `locale` is set and not equal to
302
+ * `all` (used with the i18n plugin for Strapi) or `*` (used with Strapi 5
303
+ * native localization), all entries that do not have the specified
304
+ * language are removed.
305
+ *
306
+ * @param {object} options
307
+ * @param {Array<Object>} options.entries - The entries to filter.
308
+ * @param {string} options.contentType - ContentType name.
309
+ *
310
+ * @return {Array<Object>} - Published entries.
311
+ */
312
+ removeLocaleEntries: function({ entries, contentType: contentType2 }) {
313
+ const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
314
+ const contentTypeConfig = meilisearchConfig[collection] || {};
315
+ const entriesQuery = contentTypeConfig.entriesQuery || {};
316
+ if (!entriesQuery.locale || isWildcardLocale(entriesQuery.locale)) {
317
+ return entries;
318
+ } else {
319
+ return entries.filter((entry) => entry.locale === entriesQuery.locale);
320
+ }
321
+ }
322
+ };
323
+ };
92
324
  async function registerDocumentMiddleware({ strapi: strapi2 }) {
93
325
  if (!strapi2?.documents || typeof strapi2.documents.use !== "function") {
94
326
  return;
95
327
  }
328
+ const extractEntryCandidates = (result) => {
329
+ if (result == null) return [];
330
+ const candidates = [];
331
+ const appendCandidate = (data, source) => {
332
+ if (data != null && typeof data === "object") {
333
+ candidates.push({ data, source });
334
+ }
335
+ };
336
+ if (Array.isArray(result)) {
337
+ result.forEach((data) => appendCandidate(data, "root"));
338
+ return candidates;
339
+ }
340
+ appendCandidate(result, "root");
341
+ if (Array.isArray(result.versions)) {
342
+ result.versions.forEach((data) => appendCandidate(data, "versions"));
343
+ }
344
+ if (Array.isArray(result.entries)) {
345
+ result.entries.forEach((data) => appendCandidate(data, "entries"));
346
+ }
347
+ if (result.entry != null) {
348
+ appendCandidate(result.entry, "entry");
349
+ }
350
+ return candidates;
351
+ };
352
+ const isPublishedEntry = (entry) => !(entry?.publishedAt === void 0 || entry?.publishedAt === null);
353
+ const rankEntryCandidates = (candidates) => {
354
+ return [...candidates].sort((a, b) => {
355
+ const aHasPrimaryKey = a.data?.id != null;
356
+ const bHasPrimaryKey = b.data?.id != null;
357
+ if (aHasPrimaryKey && !bHasPrimaryKey) return -1;
358
+ if (!aHasPrimaryKey && bHasPrimaryKey) return 1;
359
+ const aIsRoot = a.source === "root";
360
+ const bIsRoot = b.source === "root";
361
+ if (!aIsRoot && bIsRoot) return -1;
362
+ if (aIsRoot && !bIsRoot) return 1;
363
+ return 0;
364
+ });
365
+ };
366
+ const getEntryFromResult = ({
367
+ resultCandidates,
368
+ documentId,
369
+ entriesQuery
370
+ }) => {
371
+ const documentCandidates = (resultCandidates || []).filter(
372
+ (candidate) => candidate?.data?.documentId === documentId
373
+ );
374
+ if (documentCandidates.length === 0) return null;
375
+ const rankedCandidates = rankEntryCandidates(documentCandidates);
376
+ if (entriesQuery?.status === "draft") {
377
+ const draftCandidate = rankedCandidates.find(
378
+ (candidate) => !isPublishedEntry(candidate.data)
379
+ );
380
+ return draftCandidate?.data || rankedCandidates[0]?.data || null;
381
+ }
382
+ const publishedCandidate = rankedCandidates.find(
383
+ (candidate) => isPublishedEntry(candidate.data)
384
+ );
385
+ return publishedCandidate?.data || null;
386
+ };
387
+ const getEntryOutsideTransaction = ({
388
+ contentTypeService: contentTypeService2,
389
+ contentType: contentType2,
390
+ documentId,
391
+ entriesQuery
392
+ }) => new Promise((resolve, reject) => {
393
+ setImmediate(async () => {
394
+ try {
395
+ const entry = await contentTypeService2.getEntry({
396
+ contentType: contentType2,
397
+ documentId,
398
+ entriesQuery: { ...entriesQuery }
399
+ });
400
+ resolve(entry);
401
+ } catch (error2) {
402
+ reject(error2);
403
+ }
404
+ });
405
+ });
96
406
  strapi2.documents.use(async (ctx, next) => {
97
407
  let result;
98
408
  try {
@@ -115,7 +425,7 @@ async function registerDocumentMiddleware({ strapi: strapi2 }) {
115
425
  "discardDraft"
116
426
  ];
117
427
  const entriesQuery = meilisearch2.entriesQuery({ contentType: contentType2 });
118
- const shouldDeleteByLocale = entriesQuery.locale === "*" || entriesQuery.locale === "all";
428
+ const shouldDeleteByLocale = isWildcardLocale(entriesQuery.locale);
119
429
  const { status } = entriesQuery || {};
120
430
  const statusFilter = typeof status === "string" && status.length > 0 ? { status } : {};
121
431
  const preDeleteDocumentId = deleteActions.includes(ctx.action) && ctx?.params?.documentId ? ctx.params.documentId : null;
@@ -147,24 +457,39 @@ async function registerDocumentMiddleware({ strapi: strapi2 }) {
147
457
  }
148
458
  }
149
459
  result = await next();
150
- const documentId = result?.documentId ?? preDeleteEntry?.documentId ?? preDeleteDocumentId ?? null;
460
+ const contextDocumentId = typeof ctx?.params?.documentId === "string" && ctx.params.documentId.length > 0 ? ctx.params.documentId : null;
461
+ const documentId = contextDocumentId ?? result?.documentId ?? preDeleteEntry?.documentId ?? preDeleteDocumentId ?? null;
151
462
  if (updateActions.includes(ctx.action) && documentId != null) {
152
- const entry = await contentTypeService2.getEntry({
153
- contentType: contentType2,
463
+ const resultCandidates = extractEntryCandidates(result);
464
+ let entry = getEntryFromResult({
465
+ resultCandidates,
154
466
  documentId,
155
- entriesQuery: { ...entriesQuery }
467
+ entriesQuery
156
468
  });
469
+ if (!entry) {
470
+ entry = await getEntryOutsideTransaction({
471
+ contentTypeService: contentTypeService2,
472
+ contentType: contentType2,
473
+ documentId,
474
+ entriesQuery
475
+ });
476
+ }
157
477
  if (entry) {
478
+ const normalizedEntry = entry.documentId === documentId ? entry : { ...entry, documentId };
158
479
  await meilisearch2.updateEntriesInMeilisearch({
159
480
  contentType: contentType2,
160
- entries: [entry]
481
+ entries: [normalizedEntry]
161
482
  });
162
- } else {
483
+ } else if (ctx.action === "create" || ctx.action === "publish") {
163
484
  await meilisearch2.deleteEntriesFromMeiliSearch({
164
485
  contentType: contentType2,
165
486
  documentIds: [documentId],
166
487
  entriesQuery
167
488
  });
489
+ } else {
490
+ strapi2.log.info(
491
+ `Meilisearch document middleware skipped indexing ${contentType2} documentId=${documentId} for action ${ctx.action}: no indexable entry in result payload`
492
+ );
168
493
  }
169
494
  } else if (deleteActions.includes(ctx.action)) {
170
495
  if (documentId != null) {
@@ -195,9 +520,6 @@ async function registerDocumentMiddleware({ strapi: strapi2 }) {
195
520
  const register = async ({ strapi: strapi2 }) => {
196
521
  await registerDocumentMiddleware({ strapi: strapi2 });
197
522
  };
198
- function isObject(elem) {
199
- return typeof elem === "object" && !Array.isArray(elem) && elem !== null;
200
- }
201
523
  function EntriesQuery({ configuration, collectionName }) {
202
524
  const log = strapi.log;
203
525
  const {
@@ -774,22 +1096,39 @@ const contentTypeService = ({ strapi: strapi2 }) => ({
774
1096
  *
775
1097
  * @param {object} options
776
1098
  * @param {string} options.contentType - Name of the contentType.
777
- * @param {object} [options.where] - Filter condition
1099
+ * @param {object} [options.filters] - Filter condition.
1100
+ * @param {string} [options.status='published'] - Publication state.
1101
+ * @param {string} [options.locale] - Locale to query.
778
1102
  *
779
1103
  * @returns {Promise<number>} number of entries in the content type.
780
1104
  */
781
1105
  numberOfEntries: async function({
782
1106
  contentType: contentType2,
783
1107
  filters = {},
784
- status = "published"
1108
+ status = "published",
1109
+ locale
785
1110
  }) {
786
1111
  const contentTypeUid = this.getContentTypeUid({ contentType: contentType2 });
787
1112
  if (contentTypeUid === void 0) return 0;
1113
+ const queryOptions = normalizeEntryScope({
1114
+ filters,
1115
+ status,
1116
+ locale
1117
+ });
788
1118
  try {
789
- const count = await strapi2.documents(contentTypeUid).count({
790
- filters,
791
- status
792
- });
1119
+ if (queryOptions.locale === "*") {
1120
+ const batchCounts = await this.actionInBatches({
1121
+ contentType: contentType2,
1122
+ entriesQuery: {
1123
+ ...queryOptions,
1124
+ fields: ["documentId"],
1125
+ populate: {}
1126
+ },
1127
+ callback: ({ entries }) => entries.length
1128
+ });
1129
+ return batchCounts.reduce((total, count2) => total + count2, 0);
1130
+ }
1131
+ const count = await strapi2.documents(contentTypeUid).count(queryOptions);
793
1132
  return count;
794
1133
  } catch (e) {
795
1134
  strapi2.log.warn(e);
@@ -801,18 +1140,29 @@ const contentTypeService = ({ strapi: strapi2 }) => ({
801
1140
  *
802
1141
  * @param {object} options
803
1142
  * @param {string[]} options.contentTypes - Names of the contentType.
804
- * @param {object} [options.where] - Filter condition
1143
+ * @param {object} [options.filters] - Filter condition.
1144
+ * @param {string} [options.status='published'] - Publication state.
1145
+ * @param {string} [options.locale] - Locale to query.
805
1146
  *
806
1147
  * @returns {Promise<number>} Total entries number of the content types.
807
1148
  */
808
1149
  totalNumberOfEntries: async function({
809
1150
  contentTypes,
810
1151
  filters = {},
811
- status = "published"
1152
+ status = "published",
1153
+ locale
812
1154
  }) {
1155
+ const normalizedEntryScope = normalizeEntryScope({
1156
+ filters,
1157
+ status,
1158
+ locale
1159
+ });
813
1160
  let numberOfEntries = await Promise.all(
814
1161
  contentTypes.map(
815
- async (contentType2) => this.numberOfEntries({ contentType: contentType2, filters, status })
1162
+ async (contentType2) => this.numberOfEntries({
1163
+ contentType: contentType2,
1164
+ ...normalizedEntryScope
1165
+ })
816
1166
  )
817
1167
  );
818
1168
  const entriesSum = numberOfEntries.reduce((acc, curr) => acc + curr, 0);
@@ -840,7 +1190,13 @@ const contentTypeService = ({ strapi: strapi2 }) => ({
840
1190
  status = "published",
841
1191
  locale
842
1192
  } = entriesQuery;
843
- const queryOptions = { documentId, fields, populate, status, locale };
1193
+ const queryOptions = {
1194
+ documentId,
1195
+ fields,
1196
+ populate,
1197
+ status,
1198
+ locale: normalizeEntryLocale(locale)
1199
+ };
844
1200
  const contentTypeUid = this.getContentTypeUid({ contentType: contentType2 });
845
1201
  if (contentTypeUid === void 0) return null;
846
1202
  const entry = await strapi2.documents(contentTypeUid).findOne(queryOptions);
@@ -882,15 +1238,20 @@ const contentTypeService = ({ strapi: strapi2 }) => ({
882
1238
  }) {
883
1239
  const contentTypeUid = this.getContentTypeUid({ contentType: contentType2 });
884
1240
  if (contentTypeUid === void 0) return [];
1241
+ const normalizedEntryScope = normalizeEntryScope({
1242
+ filters,
1243
+ status,
1244
+ locale
1245
+ });
885
1246
  const queryOptions = {
886
1247
  fields: fields || "*",
887
1248
  start,
888
1249
  limit,
889
- filters,
1250
+ filters: normalizedEntryScope.filters,
890
1251
  sort,
891
1252
  populate,
892
- status,
893
- locale
1253
+ status: normalizedEntryScope.status,
1254
+ locale: normalizedEntryScope.locale
894
1255
  };
895
1256
  const entries = await strapi2.documents(contentTypeUid).findMany(queryOptions);
896
1257
  if (entries && !Array.isArray(entries)) return [entries];
@@ -912,23 +1273,37 @@ const contentTypeService = ({ strapi: strapi2 }) => ({
912
1273
  },
913
1274
  entriesQuery = {}
914
1275
  }) {
915
- const batchSize = entriesQuery.limit || 500;
916
- const entries_count = await this.numberOfEntries({
917
- contentType: contentType2,
918
- ...entriesQuery
1276
+ const normalizedEntryScope = normalizeEntryScope({
1277
+ filters: entriesQuery.filters,
1278
+ status: entriesQuery.status,
1279
+ locale: entriesQuery.locale
919
1280
  });
1281
+ const normalizedEntriesQuery = {
1282
+ ...entriesQuery,
1283
+ ...normalizedEntryScope
1284
+ };
1285
+ const {
1286
+ start: initialStart,
1287
+ limit: initialLimit,
1288
+ ...baseQuery
1289
+ } = normalizedEntriesQuery;
1290
+ const batchSize = initialLimit ?? 500;
1291
+ const shouldIterateUntilEmpty = baseQuery.locale === "*";
1292
+ let start = initialStart ?? 0;
920
1293
  const cbResponse = [];
921
- for (let index2 = 0; index2 < entries_count; index2 += batchSize) {
1294
+ while (true) {
922
1295
  const entries = await this.getEntries({
923
- start: index2,
1296
+ start,
1297
+ limit: batchSize,
924
1298
  contentType: contentType2,
925
- ...entriesQuery
1299
+ ...baseQuery
926
1300
  }) || [];
927
- if (entries.length > 0) {
928
- const info = await callback({ entries, contentType: contentType2 });
929
- if (Array.isArray(info)) cbResponse.push(...info);
930
- else if (info) cbResponse.push(info);
931
- }
1301
+ if (entries.length === 0) break;
1302
+ const info = await callback({ entries, contentType: contentType2 });
1303
+ if (Array.isArray(info)) cbResponse.push(...info);
1304
+ else if (info) cbResponse.push(info);
1305
+ if (!shouldIterateUntilEmpty && entries.length < batchSize) break;
1306
+ start += batchSize;
932
1307
  }
933
1308
  return cbResponse;
934
1309
  }
@@ -1260,224 +1635,7 @@ const store = ({ strapi: strapi2 }) => {
1260
1635
  ...createStoreConnector({ strapi: strapi2 })
1261
1636
  };
1262
1637
  };
1263
- const isWildcardLocale = (locale) => locale === "all" || locale === "*";
1264
- const aborted = ({ contentType: contentType2, action }) => {
1265
- strapi.log.error(
1266
- `Indexing of ${contentType2} aborted as the data could not be ${action}`
1267
- );
1268
- return [];
1269
- };
1270
- const configurationService = ({ strapi: strapi2 }) => {
1271
- const meilisearchConfig = strapi2.config.get("plugin::meilisearch") || {};
1272
- const contentTypeService2 = strapi2.plugin("meilisearch").service("contentType");
1273
- return {
1274
- /**
1275
- * Get the names of the indexes from Meilisearch in which the contentType content is added.
1276
- *
1277
- * @param {object} options
1278
- * @param {string} options.contentType - ContentType name.
1279
- *
1280
- * @return {String[]} - Index names
1281
- */
1282
- getIndexNamesOfContentType: function({ contentType: contentType2 }) {
1283
- const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
1284
- const contentTypeConfig = meilisearchConfig[collection] || {};
1285
- let indexNames = [contentTypeConfig.indexName].flat(2).filter((index2) => index2);
1286
- return indexNames.length > 0 ? indexNames : [collection];
1287
- },
1288
- /**
1289
- * Get the entries query rule of a content-type that are applied when fetching entries in the Strapi database.
1290
- *
1291
- * @param {object} options
1292
- * @param {string} options.contentType - ContentType name.
1293
- *
1294
- * @return {String} - EntriesQuery rules.
1295
- */
1296
- entriesQuery: function({ contentType: contentType2 }) {
1297
- const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
1298
- const contentTypeConfig = meilisearchConfig[collection] || {};
1299
- return contentTypeConfig.entriesQuery || {};
1300
- },
1301
- /**
1302
- * Transform contentTypes entries before indexation in Meilisearch.
1303
- *
1304
- * @param {object} options
1305
- * @param {string} options.contentType - ContentType name.
1306
- * @param {Array<Object>} options.entries - The data to convert. Conversion will use
1307
- * the static method `toSearchIndex` defined in the model definition
1308
- *
1309
- * @return {Promise<Array<Object>>} - Converted or mapped data
1310
- */
1311
- transformEntries: async function({ contentType: contentType2, entries = [] }) {
1312
- const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
1313
- const contentTypeConfig = meilisearchConfig[collection] || {};
1314
- try {
1315
- if (Array.isArray(entries) && typeof contentTypeConfig?.transformEntry === "function") {
1316
- const transformed = await Promise.all(
1317
- entries.map(
1318
- async (entry) => await contentTypeConfig.transformEntry({
1319
- entry,
1320
- contentType: contentType2
1321
- })
1322
- )
1323
- );
1324
- if (transformed.length > 0 && !isObject(transformed[0])) {
1325
- return aborted({ contentType: contentType2, action: "transformed" });
1326
- }
1327
- return transformed;
1328
- }
1329
- } catch (e) {
1330
- strapi2.log.error(e);
1331
- return aborted({ contentType: contentType2, action: "transformed" });
1332
- }
1333
- return entries;
1334
- },
1335
- /**
1336
- * Filter contentTypes entries before indexation in Meilisearch.
1337
- *
1338
- * @param {object} options
1339
- * @param {string} options.contentType - ContentType name.
1340
- * @param {Array<Object>} options.entries - The data to convert. Conversion will use
1341
- * the static method `toSearchIndex` defined in the model definition
1342
- *
1343
- * @return {Promise<Array<Object>>} - Converted or mapped data
1344
- */
1345
- filterEntries: async function({ contentType: contentType2, entries = [] }) {
1346
- const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
1347
- const contentTypeConfig = meilisearchConfig[collection] || {};
1348
- try {
1349
- if (Array.isArray(entries) && typeof contentTypeConfig?.filterEntry === "function") {
1350
- const filtered = await entries.reduce(
1351
- async (filteredEntries, entry) => {
1352
- const isValid = await contentTypeConfig.filterEntry({
1353
- entry,
1354
- contentType: contentType2
1355
- });
1356
- if (!isValid) return filteredEntries;
1357
- const syncFilteredEntries = await filteredEntries;
1358
- return [...syncFilteredEntries, entry];
1359
- },
1360
- []
1361
- );
1362
- return filtered;
1363
- }
1364
- } catch (e) {
1365
- strapi2.log.error(e);
1366
- return aborted({ contentType: contentType2, action: "filtered" });
1367
- }
1368
- return entries;
1369
- },
1370
- /**
1371
- * Returns Meilisearch index settings from model definition.
1372
- *
1373
- * @param {object} options
1374
- * @param {string} options.contentType - ContentType name.
1375
- * @param {Array<Object>} [options.entries] - The data to convert. Conversion will use
1376
-
1377
- * @typedef Settings
1378
- * @type {import('meilisearch').Settings}
1379
- * @return {Settings} - Meilisearch index settings
1380
- */
1381
- getSettings: function({ contentType: contentType2 }) {
1382
- const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
1383
- const contentTypeConfig = meilisearchConfig[collection] || {};
1384
- const settings = contentTypeConfig.settings || {};
1385
- return settings;
1386
- },
1387
- /**
1388
- * Return all contentTypes having the provided indexName setting.
1389
- *
1390
- * @param {object} options
1391
- * @param {string} options.indexName - Index in Meilisearch.
1392
- *
1393
- * @returns {string[]} List of contentTypes storing its data in the provided indexName
1394
- */
1395
- listContentTypesWithCustomIndexName: function({ indexName }) {
1396
- const contentTypes = strapi2.plugin("meilisearch").service("contentType").getContentTypesUid() || [];
1397
- const collectionNames = contentTypes.map(
1398
- (contentType2) => contentTypeService2.getCollectionName({ contentType: contentType2 })
1399
- );
1400
- const contentTypeWithIndexName = collectionNames.filter((contentType2) => {
1401
- const names = this.getIndexNamesOfContentType({
1402
- contentType: contentType2
1403
- });
1404
- return names.includes(indexName);
1405
- });
1406
- return contentTypeWithIndexName;
1407
- },
1408
- /**
1409
- * Remove sensitive fields (password, author, etc, ..) from entry.
1410
- *
1411
- * @param {object} options
1412
- * @param {Array<Object>} options.entries - The entries to sanitize
1413
- *
1414
- *
1415
- * @return {Array<Object>} - Entries
1416
- */
1417
- removeSensitiveFields: function({ contentType: contentType2, entries }) {
1418
- const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
1419
- const contentTypeConfig = meilisearchConfig[collection] || {};
1420
- const noSanitizePrivateFields = contentTypeConfig.noSanitizePrivateFields || [];
1421
- if (noSanitizePrivateFields.includes("*")) {
1422
- return entries;
1423
- }
1424
- const attrs = strapi2.contentTypes[contentType2].attributes;
1425
- const privateFields = Object.entries(attrs).map(
1426
- ([field, schema]) => schema.private && !noSanitizePrivateFields.includes(field) ? field : false
1427
- );
1428
- return entries.map((entry) => {
1429
- privateFields.forEach((attr) => delete entry[attr]);
1430
- return entry;
1431
- });
1432
- },
1433
- /**
1434
- * Remove unpublished entries from array of entries
1435
- * unless `status` is set to 'draft'.
1436
- *
1437
- * @param {object} options
1438
- * @param {Array<Object>} options.entries - The entries to filter.
1439
- * @param {string} options.contentType - ContentType name.
1440
- *
1441
- * @return {Array<Object>} - Published entries.
1442
- */
1443
- removeUnpublishedArticles: function({ entries, contentType: contentType2 }) {
1444
- const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
1445
- const contentTypeConfig = meilisearchConfig[collection] || {};
1446
- const entriesQuery = contentTypeConfig.entriesQuery || {};
1447
- if (entriesQuery.status === "draft") {
1448
- return entries;
1449
- } else {
1450
- return entries.filter(
1451
- (entry) => !(entry?.publishedAt === void 0 || entry?.publishedAt === null)
1452
- );
1453
- }
1454
- },
1455
- /**
1456
- * Remove language entries.
1457
- * In the plugin entriesQuery, if `locale` is set and not equal to
1458
- * `all` (used with the i18n plugin for Strapi) or `*` (used with Strapi 5
1459
- * native localization), all entries that do not have the specified
1460
- * language are removed.
1461
- *
1462
- * @param {object} options
1463
- * @param {Array<Object>} options.entries - The entries to filter.
1464
- * @param {string} options.contentType - ContentType name.
1465
- *
1466
- * @return {Array<Object>} - Published entries.
1467
- */
1468
- removeLocaleEntries: function({ entries, contentType: contentType2 }) {
1469
- const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
1470
- const contentTypeConfig = meilisearchConfig[collection] || {};
1471
- const entriesQuery = contentTypeConfig.entriesQuery || {};
1472
- if (!entriesQuery.locale || isWildcardLocale(entriesQuery.locale)) {
1473
- return entries;
1474
- } else {
1475
- return entries.filter((entry) => entry.locale === entriesQuery.locale);
1476
- }
1477
- }
1478
- };
1479
- };
1480
- const version = "0.16.0";
1638
+ const version = "0.16.2";
1481
1639
  const Meilisearch = (config2) => {
1482
1640
  return new MeiliSearch({
1483
1641
  ...config2,