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