@strapi/utils 5.0.0-beta.6 → 5.0.0-beta.7

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.
package/dist/index.mjs CHANGED
@@ -283,6 +283,15 @@ const isVisibleAttribute = (model, attributeName) => {
283
283
  const getOptions = (model) => ___default.assign({ draftAndPublish: false }, ___default.get(model, "options", {}));
284
284
  const hasDraftAndPublish = (model) => ___default.get(model, "options.draftAndPublish", false) === true;
285
285
  const isDraft = (data, model) => hasDraftAndPublish(model) && ___default.get(data, PUBLISHED_AT_ATTRIBUTE$1) === null;
286
+ const isSchema = (data) => {
287
+ return typeof data === "object" && data !== null && "modelType" in data && typeof data.modelType === "string" && ["component", "contentType"].includes(data.modelType);
288
+ };
289
+ const isComponentSchema = (data) => {
290
+ return isSchema(data) && data.modelType === "component";
291
+ };
292
+ const isContentTypeSchema = (data) => {
293
+ return isSchema(data) && data.modelType === "contentType";
294
+ };
286
295
  const isSingleType = ({ kind = COLLECTION_TYPE }) => kind === SINGLE_TYPE;
287
296
  const isCollectionType = ({ kind = COLLECTION_TYPE }) => kind === COLLECTION_TYPE;
288
297
  const isKind = (kind) => (model) => model.kind === kind;
@@ -303,14 +312,16 @@ const isPrivateAttribute = (model, attributeName) => {
303
312
  return getStoredPrivateAttributes(model).includes(attributeName);
304
313
  };
305
314
  const isScalarAttribute = (attribute) => {
306
- return !["media", "component", "relation", "dynamiczone"].includes(attribute?.type);
315
+ return attribute && !["media", "component", "relation", "dynamiczone"].includes(attribute.type);
307
316
  };
308
317
  const isMediaAttribute = (attribute) => attribute?.type === "media";
309
318
  const isRelationalAttribute = (attribute) => attribute?.type === "relation";
319
+ const HAS_RELATION_REORDERING = ["manyToMany", "manyToOne", "oneToMany"];
320
+ const hasRelationReordering = (attribute) => isRelationalAttribute(attribute) && HAS_RELATION_REORDERING.includes(attribute.relation);
310
321
  const isComponentAttribute = (attribute) => ["component", "dynamiczone"].includes(attribute?.type);
311
- const isDynamicZoneAttribute = (attribute) => attribute?.type === "dynamiczone";
322
+ const isDynamicZoneAttribute = (attribute) => !!attribute && attribute.type === "dynamiczone";
312
323
  const isMorphToRelationalAttribute = (attribute) => {
313
- return isRelationalAttribute(attribute) && attribute?.relation?.startsWith?.("morphTo");
324
+ return !!attribute && isRelationalAttribute(attribute) && attribute.relation?.startsWith?.("morphTo");
314
325
  };
315
326
  const getComponentAttributes = (schema) => {
316
327
  return ___default.reduce(
@@ -367,8 +378,11 @@ const contentTypes = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.define
367
378
  getVisibleAttributes,
368
379
  getWritableAttributes,
369
380
  hasDraftAndPublish,
381
+ hasRelationReordering,
370
382
  isCollectionType,
371
383
  isComponentAttribute,
384
+ isComponentSchema,
385
+ isContentTypeSchema,
372
386
  isDraft,
373
387
  isDynamicZoneAttribute,
374
388
  isKind,
@@ -377,6 +391,7 @@ const contentTypes = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.define
377
391
  isPrivateAttribute,
378
392
  isRelationalAttribute,
379
393
  isScalarAttribute,
394
+ isSchema,
380
395
  isSingleType,
381
396
  isTypedAttribute,
382
397
  isVisibleAttribute,
@@ -531,28 +546,29 @@ const providerFactory = (options = {}) => {
531
546
  };
532
547
  const traverseEntity = async (visitor2, options, entity) => {
533
548
  const { path = { raw: null, attribute: null }, schema, getModel } = options;
549
+ let parent = options.parent;
534
550
  const traverseMorphRelationTarget = async (visitor22, path2, entry) => {
535
551
  const targetSchema = getModel(entry.__type);
536
- const traverseOptions = { schema: targetSchema, path: path2, getModel };
552
+ const traverseOptions = { schema: targetSchema, path: path2, getModel, parent };
537
553
  return traverseEntity(visitor22, traverseOptions, entry);
538
554
  };
539
555
  const traverseRelationTarget = (schema2) => async (visitor22, path2, entry) => {
540
- const traverseOptions = { schema: schema2, path: path2, getModel };
556
+ const traverseOptions = { schema: schema2, path: path2, getModel, parent };
541
557
  return traverseEntity(visitor22, traverseOptions, entry);
542
558
  };
543
559
  const traverseMediaTarget = async (visitor22, path2, entry) => {
544
560
  const targetSchemaUID = "plugin::upload.file";
545
561
  const targetSchema = getModel(targetSchemaUID);
546
- const traverseOptions = { schema: targetSchema, path: path2, getModel };
562
+ const traverseOptions = { schema: targetSchema, path: path2, getModel, parent };
547
563
  return traverseEntity(visitor22, traverseOptions, entry);
548
564
  };
549
565
  const traverseComponent = async (visitor22, path2, schema2, entry) => {
550
- const traverseOptions = { schema: schema2, path: path2, getModel };
566
+ const traverseOptions = { schema: schema2, path: path2, getModel, parent };
551
567
  return traverseEntity(visitor22, traverseOptions, entry);
552
568
  };
553
569
  const visitDynamicZoneEntry = async (visitor22, path2, entry) => {
554
570
  const targetSchema = getModel(entry.__component);
555
- const traverseOptions = { schema: targetSchema, path: path2, getModel };
571
+ const traverseOptions = { schema: targetSchema, path: path2, getModel, parent };
556
572
  return traverseEntity(visitor22, traverseOptions, entry);
557
573
  };
558
574
  if (!isObject(entity) || isNil(schema)) {
@@ -564,9 +580,6 @@ const traverseEntity = async (visitor2, options, entity) => {
564
580
  for (let i = 0; i < keys.length; i += 1) {
565
581
  const key = keys[i];
566
582
  const attribute = schema.attributes[key];
567
- if (isNil(attribute)) {
568
- continue;
569
- }
570
583
  const newPath = { ...path };
571
584
  newPath.raw = isNil(path.raw) ? key : `${path.raw}.${key}`;
572
585
  if (!isNil(attribute)) {
@@ -579,13 +592,15 @@ const traverseEntity = async (visitor2, options, entity) => {
579
592
  value: copy[key],
580
593
  attribute,
581
594
  path: newPath,
582
- getModel
595
+ getModel,
596
+ parent
583
597
  };
584
598
  await visitor2(visitorOptions, visitorUtils);
585
599
  const value = copy[key];
586
- if (isNil(value)) {
600
+ if (isNil(value) || isNil(attribute)) {
587
601
  continue;
588
602
  }
603
+ parent = { schema, key, attribute, path: newPath };
589
604
  if (isRelationalAttribute(attribute)) {
590
605
  const isMorphRelation = attribute.relation.toLowerCase().startsWith("morph");
591
606
  const method = isMorphRelation ? traverseMorphRelationTarget : traverseRelationTarget(getModel(attribute.target));
@@ -2153,13 +2168,16 @@ const index$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
2153
2168
  sanitizers,
2154
2169
  visitors: index$4
2155
2170
  }, Symbol.toStringTag, { value: "Module" }));
2156
- const throwInvalidParam = ({ key, path }) => {
2157
- const msg = path && path !== key ? `Invalid parameter ${key} at ${path}` : `Invalid parameter ${key}`;
2158
- throw new ValidationError(msg);
2171
+ const throwInvalidKey = ({ key, path }) => {
2172
+ const msg = path && path !== key ? `Invalid key ${key} at ${path}` : `Invalid key ${key}`;
2173
+ throw new ValidationError(msg, {
2174
+ key,
2175
+ path
2176
+ });
2159
2177
  };
2160
2178
  const visitor$3 = ({ key, attribute, path }) => {
2161
2179
  if (attribute?.type === "password") {
2162
- throwInvalidParam({ key, path: path.attribute });
2180
+ throwInvalidKey({ key, path: path.attribute });
2163
2181
  }
2164
2182
  };
2165
2183
  const visitor$2 = ({ schema, key, attribute, path }) => {
@@ -2168,7 +2186,7 @@ const visitor$2 = ({ schema, key, attribute, path }) => {
2168
2186
  }
2169
2187
  const isPrivate = attribute.private === true || isPrivateAttribute(schema, key);
2170
2188
  if (isPrivate) {
2171
- throwInvalidParam({ key, path: path.attribute });
2189
+ throwInvalidKey({ key, path: path.attribute });
2172
2190
  }
2173
2191
  };
2174
2192
  const ACTIONS_TO_VERIFY = ["find"];
@@ -2186,7 +2204,7 @@ const throwRestrictedRelations = (auth) => async ({ data, key, attribute, schema
2186
2204
  const scopes = ACTIONS_TO_VERIFY.map((action) => `${element.__type}.${action}`);
2187
2205
  const isAllowed = await hasAccessToSomeScopes(scopes, auth);
2188
2206
  if (!isAllowed) {
2189
- throwInvalidParam({ key, path: path.attribute });
2207
+ throwInvalidKey({ key, path: path.attribute });
2190
2208
  }
2191
2209
  }
2192
2210
  };
@@ -2194,7 +2212,7 @@ const throwRestrictedRelations = (auth) => async ({ data, key, attribute, schema
2194
2212
  const scopes = ACTIONS_TO_VERIFY.map((action) => `${attribute.target}.${action}`);
2195
2213
  const isAllowed = await hasAccessToSomeScopes(scopes, auth);
2196
2214
  if (!isAllowed) {
2197
- throwInvalidParam({ key, path: path.attribute });
2215
+ throwInvalidKey({ key, path: path.attribute });
2198
2216
  }
2199
2217
  };
2200
2218
  const isCreatorRelation = [CREATED_BY_ATTRIBUTE, UPDATED_BY_ATTRIBUTE].includes(key);
@@ -2220,12 +2238,12 @@ const hasAccessToSomeScopes = async (scopes, auth) => {
2220
2238
  };
2221
2239
  const visitor$1 = ({ key, attribute, path }) => {
2222
2240
  if (isMorphToRelationalAttribute(attribute)) {
2223
- throwInvalidParam({ key, path: path.attribute });
2241
+ throwInvalidKey({ key, path: path.attribute });
2224
2242
  }
2225
2243
  };
2226
2244
  const visitor = ({ key, attribute, path }) => {
2227
2245
  if (isDynamicZoneAttribute(attribute)) {
2228
- throwInvalidParam({ key, path: path.attribute });
2246
+ throwInvalidKey({ key, path: path.attribute });
2229
2247
  }
2230
2248
  };
2231
2249
  const throwDisallowedFields = (allowedFields = null) => ({ key, path: { attribute: path } }) => {
@@ -2247,7 +2265,7 @@ const throwDisallowedFields = (allowedFields = null) => ({ key, path: { attribut
2247
2265
  if (isPathAllowed) {
2248
2266
  return;
2249
2267
  }
2250
- throwInvalidParam({ key, path });
2268
+ throwInvalidKey({ key, path });
2251
2269
  };
2252
2270
  const getContainedPaths = (path) => {
2253
2271
  const parts = toPath(path);
@@ -2257,7 +2275,7 @@ const getContainedPaths = (path) => {
2257
2275
  };
2258
2276
  const throwRestrictedFields = (restrictedFields = null) => ({ key, path: { attribute: path } }) => {
2259
2277
  if (restrictedFields === null) {
2260
- throwInvalidParam({ key, path });
2278
+ throwInvalidKey({ key, path });
2261
2279
  }
2262
2280
  if (!(isArray(restrictedFields) && restrictedFields.every(isString))) {
2263
2281
  throw new TypeError(
@@ -2265,15 +2283,45 @@ const throwRestrictedFields = (restrictedFields = null) => ({ key, path: { attri
2265
2283
  );
2266
2284
  }
2267
2285
  if (restrictedFields.includes(path)) {
2268
- throwInvalidParam({ key, path });
2286
+ throwInvalidKey({ key, path });
2269
2287
  }
2270
2288
  const isRestrictedNested = restrictedFields.some(
2271
2289
  (allowedPath) => path?.toString().startsWith(`${allowedPath}.`)
2272
2290
  );
2273
2291
  if (isRestrictedNested) {
2274
- throwInvalidParam({ key, path });
2292
+ throwInvalidKey({ key, path });
2275
2293
  }
2276
2294
  };
2295
+ const ID_FIELDS = [constants$1.DOC_ID_ATTRIBUTE, constants$1.DOC_ID_ATTRIBUTE];
2296
+ const ALLOWED_ROOT_LEVEL_FIELDS = [...ID_FIELDS];
2297
+ const MORPH_TO_ALLOWED_FIELDS = ["__type"];
2298
+ const DYNAMIC_ZONE_ALLOWED_FIELDS = ["__component"];
2299
+ const RELATION_REORDERING_FIELDS = ["connect", "disconnect", "set", "options"];
2300
+ const throwUnrecognizedFields = ({ key, attribute, path, schema, parent }) => {
2301
+ if (attribute) {
2302
+ return;
2303
+ }
2304
+ if (path.attribute === null) {
2305
+ if (ALLOWED_ROOT_LEVEL_FIELDS.includes(key)) {
2306
+ return;
2307
+ }
2308
+ return throwInvalidKey({ key, path: attribute });
2309
+ }
2310
+ if (isMorphToRelationalAttribute(parent?.attribute) && MORPH_TO_ALLOWED_FIELDS.includes(key)) {
2311
+ return;
2312
+ }
2313
+ if (isComponentSchema(schema) && isDynamicZoneAttribute(parent?.attribute) && DYNAMIC_ZONE_ALLOWED_FIELDS.includes(key)) {
2314
+ return;
2315
+ }
2316
+ if (hasRelationReordering(parent?.attribute) && RELATION_REORDERING_FIELDS.includes(key)) {
2317
+ return;
2318
+ }
2319
+ const canUseID = isRelationalAttribute(parent?.attribute) || isMediaAttribute(parent?.attribute);
2320
+ if (canUseID && !ID_FIELDS.includes(key)) {
2321
+ return;
2322
+ }
2323
+ throwInvalidKey({ key, path: attribute });
2324
+ };
2277
2325
  const index$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
2278
2326
  __proto__: null,
2279
2327
  throwDisallowedFields,
@@ -2282,7 +2330,8 @@ const index$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
2282
2330
  throwPassword: visitor$3,
2283
2331
  throwPrivate: visitor$2,
2284
2332
  throwRestrictedFields,
2285
- throwRestrictedRelations
2333
+ throwRestrictedRelations,
2334
+ throwUnrecognizedFields
2286
2335
  }, Symbol.toStringTag, { value: "Module" }));
2287
2336
  const { ID_ATTRIBUTE: ID_ATTRIBUTE$1, DOC_ID_ATTRIBUTE: DOC_ID_ATTRIBUTE$1 } = constants$1;
2288
2337
  const throwPasswords = (ctx) => async (entity) => {
@@ -2303,7 +2352,7 @@ const defaultValidateFilters = curry((ctx, filters2) => {
2303
2352
  }
2304
2353
  const isAttribute = !!attribute;
2305
2354
  if (!isAttribute && !isOperator(key)) {
2306
- throwInvalidParam({ key, path: path.attribute });
2355
+ throwInvalidKey({ key, path: path.attribute });
2307
2356
  }
2308
2357
  }, ctx),
2309
2358
  // dynamic zones from filters
@@ -2328,7 +2377,7 @@ const defaultValidateSort = curry((ctx, sort2) => {
2328
2377
  return;
2329
2378
  }
2330
2379
  if (!attribute) {
2331
- throwInvalidParam({ key, path: path.attribute });
2380
+ throwInvalidKey({ key, path: path.attribute });
2332
2381
  }
2333
2382
  }, ctx),
2334
2383
  // dynamic zones from sort
@@ -2345,7 +2394,7 @@ const defaultValidateSort = curry((ctx, sort2) => {
2345
2394
  return;
2346
2395
  }
2347
2396
  if (!isScalarAttribute(attribute) && isEmpty(value)) {
2348
- throwInvalidParam({ key, path: path.attribute });
2397
+ throwInvalidKey({ key, path: path.attribute });
2349
2398
  }
2350
2399
  }, ctx)
2351
2400
  )(sort2);
@@ -2361,7 +2410,7 @@ const defaultValidateFields = curry((ctx, fields2) => {
2361
2410
  return;
2362
2411
  }
2363
2412
  if (isNil(attribute) || !isScalarAttribute(attribute)) {
2364
- throwInvalidParam({ key, path: path.attribute });
2413
+ throwInvalidKey({ key, path: path.attribute });
2365
2414
  }
2366
2415
  }, ctx),
2367
2416
  // private fields
@@ -2456,15 +2505,18 @@ const createAPIValidators = (opts) => {
2456
2505
  (data2) => {
2457
2506
  if (isObject(data2)) {
2458
2507
  if (ID_ATTRIBUTE in data2) {
2459
- throwInvalidParam({ key: ID_ATTRIBUTE });
2508
+ throwInvalidKey({ key: ID_ATTRIBUTE });
2460
2509
  }
2461
2510
  if (DOC_ID_ATTRIBUTE in data2) {
2462
- throwInvalidParam({ key: DOC_ID_ATTRIBUTE });
2511
+ throwInvalidKey({ key: DOC_ID_ATTRIBUTE });
2463
2512
  }
2464
2513
  }
2514
+ return data2;
2465
2515
  },
2466
2516
  // non-writable attributes
2467
- traverseEntity$1(throwRestrictedFields(nonWritableAttributes), { schema, getModel })
2517
+ traverseEntity$1(throwRestrictedFields(nonWritableAttributes), { schema, getModel }),
2518
+ // unrecognized attributes
2519
+ traverseEntity$1(throwUnrecognizedFields, { schema, getModel })
2468
2520
  ];
2469
2521
  if (auth) {
2470
2522
  transforms.push(
@@ -2475,7 +2527,14 @@ const createAPIValidators = (opts) => {
2475
2527
  );
2476
2528
  }
2477
2529
  opts?.validators?.input?.forEach((validator) => transforms.push(validator(schema)));
2478
- await pipe(...transforms)(data);
2530
+ try {
2531
+ await pipe(...transforms)(data);
2532
+ } catch (e) {
2533
+ if (e instanceof ValidationError) {
2534
+ e.details.source = "body";
2535
+ }
2536
+ throw e;
2537
+ }
2479
2538
  };
2480
2539
  const validateQuery = async (query, schema, { auth } = {}) => {
2481
2540
  if (!schema) {
@@ -2512,7 +2571,15 @@ const createAPIValidators = (opts) => {
2512
2571
  })
2513
2572
  );
2514
2573
  }
2515
- await pipe(...transforms)(filters2);
2574
+ try {
2575
+ await pipe(...transforms)(filters2);
2576
+ } catch (e) {
2577
+ if (e instanceof ValidationError) {
2578
+ e.details.source = "query";
2579
+ e.details.param = "filters";
2580
+ }
2581
+ throw e;
2582
+ }
2516
2583
  };
2517
2584
  const validateSort = async (sort2, schema, { auth } = {}) => {
2518
2585
  if (!schema) {
@@ -2527,14 +2594,30 @@ const createAPIValidators = (opts) => {
2527
2594
  })
2528
2595
  );
2529
2596
  }
2530
- await pipe(...transforms)(sort2);
2597
+ try {
2598
+ await pipe(...transforms)(sort2);
2599
+ } catch (e) {
2600
+ if (e instanceof ValidationError) {
2601
+ e.details.source = "query";
2602
+ e.details.param = "sort";
2603
+ }
2604
+ throw e;
2605
+ }
2531
2606
  };
2532
2607
  const validateFields = async (fields2, schema) => {
2533
2608
  if (!schema) {
2534
2609
  throw new Error("Missing schema in validateFields");
2535
2610
  }
2536
2611
  const transforms = [defaultValidateFields({ schema, getModel })];
2537
- await pipe(...transforms)(fields2);
2612
+ try {
2613
+ await pipe(...transforms)(fields2);
2614
+ } catch (e) {
2615
+ if (e instanceof ValidationError) {
2616
+ e.details.source = "query";
2617
+ e.details.param = "fields";
2618
+ }
2619
+ throw e;
2620
+ }
2538
2621
  };
2539
2622
  const validatePopulate = async (populate2, schema, { auth } = {}) => {
2540
2623
  if (!schema) {
@@ -2549,7 +2632,15 @@ const createAPIValidators = (opts) => {
2549
2632
  })
2550
2633
  );
2551
2634
  }
2552
- await pipe(...transforms)(populate2);
2635
+ try {
2636
+ await pipe(...transforms)(populate2);
2637
+ } catch (e) {
2638
+ if (e instanceof ValidationError) {
2639
+ e.details.source = "query";
2640
+ e.details.param = "populate";
2641
+ }
2642
+ throw e;
2643
+ }
2553
2644
  };
2554
2645
  return {
2555
2646
  input: validateInput,
@@ -2633,8 +2724,57 @@ const withDefaultPagination = (args, { defaults: defaults2 = {}, maxLimit = -1 }
2633
2724
  );
2634
2725
  return replacePaginationAttributes(args);
2635
2726
  };
2727
+ const transformPagedPaginationInfo = (paginationInfo, total) => {
2728
+ if (!isNil(paginationInfo.page)) {
2729
+ const page = paginationInfo.page;
2730
+ const pageSize = paginationInfo.pageSize ?? total;
2731
+ return {
2732
+ page,
2733
+ pageSize,
2734
+ pageCount: pageSize > 0 ? Math.ceil(total / pageSize) : 0,
2735
+ total
2736
+ };
2737
+ }
2738
+ if (!isNil(paginationInfo.start)) {
2739
+ const start = paginationInfo.start;
2740
+ const limit = paginationInfo.limit ?? total;
2741
+ return {
2742
+ page: Math.floor(start / limit) + 1,
2743
+ pageSize: limit,
2744
+ pageCount: limit > 0 ? Math.ceil(total / limit) : 0,
2745
+ total
2746
+ };
2747
+ }
2748
+ return {
2749
+ ...paginationInfo,
2750
+ page: 1,
2751
+ pageSize: 10,
2752
+ pageCount: 1,
2753
+ total
2754
+ };
2755
+ };
2756
+ const transformOffsetPaginationInfo = (paginationInfo, total) => {
2757
+ if (!isNil(paginationInfo.page)) {
2758
+ const limit = paginationInfo.pageSize ?? total;
2759
+ const start = (paginationInfo.page - 1) * limit;
2760
+ return { start, limit, total };
2761
+ }
2762
+ if (!isNil(paginationInfo.start)) {
2763
+ const start = paginationInfo.start;
2764
+ const limit = paginationInfo.limit ?? total;
2765
+ return { start, limit, total };
2766
+ }
2767
+ return {
2768
+ ...paginationInfo,
2769
+ start: 0,
2770
+ limit: 10,
2771
+ total
2772
+ };
2773
+ };
2636
2774
  const pagination = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
2637
2775
  __proto__: null,
2776
+ transformOffsetPaginationInfo,
2777
+ transformPagedPaginationInfo,
2638
2778
  withDefaultPagination
2639
2779
  }, Symbol.toStringTag, { value: "Module" }));
2640
2780
  const SUPPORTED_PACKAGE_MANAGERS = ["npm", "yarn"];