@sanity/schema 5.14.0-next.9 → 5.14.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.
package/lib/_internal.js CHANGED
@@ -14,6 +14,7 @@ import isPlainObject from "lodash-es/isPlainObject.js";
14
14
  import omit from "lodash-es/omit.js";
15
15
  import leven from "leven";
16
16
  import inspect from "object-inspect";
17
+ import startCase from "lodash-es/startCase.js";
17
18
  import { createReferenceTypeNode } from "groq-js";
18
19
  const MAX_IDLE_WORK = 8.333333333333334;
19
20
  class IdleScheduler {
@@ -1821,6 +1822,14 @@ function validateField(field, visitorContext) {
1821
1822
  `The type "${field.type}" is a document type and should not be used as a field type directly. Use a "reference" if you want to create a link to the document, or use "object" if you want to embed fields inline.`,
1822
1823
  HELP_IDS.FIELD_TYPE_IS_DOCUMENT
1823
1824
  )
1825
+ ), field.title && typeof field.title != "string" && problems.push(
1826
+ warning(
1827
+ `Field "${field.name}" has a non-string title. This is known to cause problems and will not be supported in future versions. Please use a string instead.`
1828
+ )
1829
+ ), field.description && typeof field.description != "string" && problems.push(
1830
+ warning(
1831
+ `Field "${field.name}" has a non-string description. This is known to cause problems and will not be supported in future versions. Please use a string instead.`
1832
+ )
1824
1833
  ), problems;
1825
1834
  }
1826
1835
  function getDuplicateFields(array2) {
@@ -2350,6 +2359,405 @@ function stringToRegExp(str) {
2350
2359
  const match = str.match(/^\/(.*)\/([gimuy]*)$/);
2351
2360
  return match ? new RegExp(match[1], match[2]) : new RegExp(str);
2352
2361
  }
2362
+ const DEFAULT_IMAGE_FIELDS = ["asset", "hotspot", "crop", "media"], DEFAULT_FILE_FIELDS = ["asset", "media"], DEFAULT_GEOPOINT_FIELDS = ["lat", "lng", "alt"], DEFAULT_SLUG_FIELDS = ["current", "source"];
2363
+ function getCustomFields(type) {
2364
+ const fields = type.fieldsets ? type.fieldsets.flatMap((fs) => fs.single ? fs.field : fs.fields.map((field) => ({
2365
+ ...field,
2366
+ fieldset: fs.name
2367
+ }))) : type.fields;
2368
+ return isType$1(type, "block") ? [] : isType$1(type, "slug") ? fields.filter((f) => !DEFAULT_SLUG_FIELDS.includes(f.name)) : isType$1(type, "geopoint") ? fields.filter((f) => !DEFAULT_GEOPOINT_FIELDS.includes(f.name)) : isType$1(type, "image") ? fields.filter((f) => !DEFAULT_IMAGE_FIELDS.includes(f.name)) : isType$1(type, "file") ? fields.filter((f) => !DEFAULT_FILE_FIELDS.includes(f.name)) : fields;
2369
+ }
2370
+ function isReference(type) {
2371
+ return isType$1(type, "reference");
2372
+ }
2373
+ function isCrossDatasetReference(type) {
2374
+ return isType$1(type, "crossDatasetReference");
2375
+ }
2376
+ function isGlobalDocumentReference(type) {
2377
+ return isType$1(type, "globalDocumentReference");
2378
+ }
2379
+ function isObjectField(maybeOjectField) {
2380
+ return typeof maybeOjectField == "object" && maybeOjectField !== null && "name" in maybeOjectField;
2381
+ }
2382
+ function isCustomized(maybeCustomized) {
2383
+ const internalOwnProps = getSchemaTypeInternalOwnProps(maybeCustomized);
2384
+ return isObjectField(maybeCustomized) && !isReference(maybeCustomized) && !isCrossDatasetReference(maybeCustomized) && !isGlobalDocumentReference(maybeCustomized) && "fields" in maybeCustomized && Array.isArray(maybeCustomized.fields) && // needed to differentiate inline, named array object types from globally defined types
2385
+ // we only consider it customized if the _definition_ has fields declared
2386
+ // this holds for all customizable object-like types: object, document, image and file
2387
+ internalOwnProps?.fields ? !!getCustomFields(maybeCustomized).length : !1;
2388
+ }
2389
+ function isType$1(schemaType, typeName) {
2390
+ return schemaType.name === typeName ? !0 : schemaType.type ? isType$1(schemaType.type, typeName) : !1;
2391
+ }
2392
+ function isDefined(value) {
2393
+ return value != null;
2394
+ }
2395
+ function isRecord(value) {
2396
+ return !!value && typeof value == "object";
2397
+ }
2398
+ function isPrimitive(value) {
2399
+ return isString(value) || isBoolean(value) || isNumber(value);
2400
+ }
2401
+ function isString(value) {
2402
+ return typeof value == "string";
2403
+ }
2404
+ function isNumber(value) {
2405
+ return typeof value == "number";
2406
+ }
2407
+ function isBoolean(value) {
2408
+ return typeof value == "boolean";
2409
+ }
2410
+ function getSchemaTypeInternalOwnProps(type) {
2411
+ return type?._internal_ownProps;
2412
+ }
2413
+ function getDefinedTypeName(type) {
2414
+ return getSchemaTypeInternalOwnProps(type)?.type;
2415
+ }
2416
+ const MAX_CUSTOM_PROPERTY_DEPTH = 5, noopIconResolver = () => {
2417
+ };
2418
+ async function extractCreateWorkspaceManifest(workspace, iconResolver = noopIconResolver) {
2419
+ const serializedSchema = extractManifestSchemaTypes(workspace.schema), serializedTools = await extractManifestTools(workspace.tools, iconResolver);
2420
+ return {
2421
+ name: workspace.name,
2422
+ title: workspace.title,
2423
+ subtitle: workspace.subtitle,
2424
+ basePath: workspace.basePath,
2425
+ projectId: workspace.projectId,
2426
+ dataset: workspace.dataset,
2427
+ icon: await iconResolver({
2428
+ icon: workspace.icon,
2429
+ title: workspace.title ?? workspace.name,
2430
+ subtitle: workspace.subtitle
2431
+ }),
2432
+ mediaLibrary: workspace.mediaLibrary,
2433
+ schema: serializedSchema,
2434
+ tools: serializedTools
2435
+ };
2436
+ }
2437
+ function extractManifestSchemaTypes(schema) {
2438
+ const typeNames = schema.getTypeNames(), studioDefaultTypeNames = Schema.compile({
2439
+ name: "default",
2440
+ types: builtinTypes
2441
+ }).getTypeNames();
2442
+ return typeNames.filter((typeName) => !studioDefaultTypeNames.includes(typeName)).map((typeName) => schema.get(typeName)).filter((type) => typeof type < "u").map((type) => transformType(type));
2443
+ }
2444
+ function transformCommonTypeFields(type, typeName, context) {
2445
+ const arrayProps = typeName === "array" && type.jsonType === "array" ? transformArrayMember(type) : {}, referenceProps = isReference(type) ? transformReference(type) : {}, crossDatasetRefProps = isCrossDatasetReference(type) ? transformCrossDatasetReference(type) : {}, globalRefProps = isGlobalDocumentReference(type) ? transformGlobalDocumentReference(type) : {}, objectFields = type.jsonType === "object" && type.type && isCustomized(type) ? {
2446
+ fields: getCustomFields(type).map((objectField) => transformField(objectField))
2447
+ } : {};
2448
+ return {
2449
+ ...retainCustomTypeProps(type),
2450
+ ...transformValidation(type.validation),
2451
+ ...ensureString("description", type.description),
2452
+ ...objectFields,
2453
+ ...arrayProps,
2454
+ ...referenceProps,
2455
+ ...crossDatasetRefProps,
2456
+ ...globalRefProps,
2457
+ ...ensureConditional("readOnly", type.readOnly),
2458
+ ...ensureConditional("hidden", type.hidden),
2459
+ ...transformFieldsets(type),
2460
+ // fieldset prop gets instrumented via getCustomFields
2461
+ ...ensureString("fieldset", type.fieldset),
2462
+ ...transformBlockType(type)
2463
+ };
2464
+ }
2465
+ function transformFieldsets(type) {
2466
+ if (type.jsonType !== "object")
2467
+ return {};
2468
+ const fieldsets = type.fieldsets?.filter((fs) => !fs.single).map((fs) => {
2469
+ const options = isRecord(fs.options) ? { options: retainSerializableProps(fs.options) } : {};
2470
+ return {
2471
+ name: fs.name,
2472
+ ...ensureCustomTitle(fs.name, fs.title),
2473
+ ...ensureString("description", fs.description),
2474
+ ...ensureConditional("readOnly", fs.readOnly),
2475
+ ...ensureConditional("hidden", fs.hidden),
2476
+ ...options
2477
+ };
2478
+ });
2479
+ return fieldsets?.length ? { fieldsets } : {};
2480
+ }
2481
+ function transformType(type, context) {
2482
+ const typeName = type.type ? type.type.name : type.jsonType;
2483
+ return {
2484
+ ...transformCommonTypeFields(type, typeName),
2485
+ name: type.name,
2486
+ type: typeName,
2487
+ ...ensureCustomTitle(type.name, type.title)
2488
+ };
2489
+ }
2490
+ function retainCustomTypeProps(type) {
2491
+ const manuallySerializedFields = [
2492
+ //explicitly added
2493
+ "name",
2494
+ "title",
2495
+ "description",
2496
+ "readOnly",
2497
+ "hidden",
2498
+ "validation",
2499
+ "fieldsets",
2500
+ "fields",
2501
+ "to",
2502
+ "of",
2503
+ // not serialized
2504
+ "type",
2505
+ "jsonType",
2506
+ "__experimental_actions",
2507
+ "__experimental_formPreviewTitle",
2508
+ "__experimental_omnisearch_visibility",
2509
+ "__experimental_search",
2510
+ "components",
2511
+ "icon",
2512
+ "orderings",
2513
+ "preview",
2514
+ "groups",
2515
+ //only exists on fields
2516
+ "group"
2517
+ // we know about these, but let them be generically handled
2518
+ // deprecated
2519
+ // rows (from text)
2520
+ // initialValue
2521
+ // options
2522
+ // crossDatasetReference props
2523
+ ], typeWithoutManuallyHandledFields = Object.fromEntries(
2524
+ Object.entries(type).filter(
2525
+ ([key]) => !manuallySerializedFields.includes(key)
2526
+ )
2527
+ );
2528
+ return retainSerializableProps(typeWithoutManuallyHandledFields);
2529
+ }
2530
+ function retainSerializableProps(maybeSerializable, depth = 0) {
2531
+ if (!(depth > MAX_CUSTOM_PROPERTY_DEPTH) && isDefined(maybeSerializable)) {
2532
+ if (isPrimitive(maybeSerializable))
2533
+ return maybeSerializable === "" ? void 0 : maybeSerializable;
2534
+ if (maybeSerializable instanceof RegExp)
2535
+ return maybeSerializable.toString();
2536
+ if (Array.isArray(maybeSerializable)) {
2537
+ const arrayItems = maybeSerializable.map((item) => retainSerializableProps(item, depth + 1)).filter((item) => isDefined(item));
2538
+ return arrayItems.length ? arrayItems : void 0;
2539
+ }
2540
+ if (isRecord(maybeSerializable)) {
2541
+ const serializableEntries = Object.entries(maybeSerializable).map(([key, value]) => [key, retainSerializableProps(value, depth + 1)]).filter(([, value]) => isDefined(value));
2542
+ return serializableEntries.length ? Object.fromEntries(serializableEntries) : void 0;
2543
+ }
2544
+ }
2545
+ }
2546
+ function transformField(field, context) {
2547
+ const fieldType = field.type, typeName = getDefinedTypeName(fieldType) ?? fieldType.name;
2548
+ return {
2549
+ ...transformCommonTypeFields(fieldType, typeName),
2550
+ name: field.name,
2551
+ type: typeName,
2552
+ ...ensureCustomTitle(field.name, fieldType.title),
2553
+ // this prop gets added synthetically via getCustomFields
2554
+ ...ensureString("fieldset", field.fieldset)
2555
+ };
2556
+ }
2557
+ function transformArrayMember(arrayMember, context) {
2558
+ return {
2559
+ of: arrayMember.of.map((type) => {
2560
+ const typeName = getDefinedTypeName(type) ?? type.name;
2561
+ return {
2562
+ ...transformCommonTypeFields(type, typeName),
2563
+ type: typeName,
2564
+ ...typeName === type.name ? {} : { name: type.name },
2565
+ ...ensureCustomTitle(type.name, type.title)
2566
+ };
2567
+ })
2568
+ };
2569
+ }
2570
+ function transformReference(reference2) {
2571
+ return {
2572
+ to: (reference2.to ?? []).map((type) => ({
2573
+ ...retainCustomTypeProps(type),
2574
+ type: type.name
2575
+ }))
2576
+ };
2577
+ }
2578
+ function transformCrossDatasetReference(reference2) {
2579
+ return {
2580
+ to: (reference2.to ?? []).map((crossDataset) => {
2581
+ const preview = crossDataset.preview?.select ? { preview: { select: crossDataset.preview.select } } : {};
2582
+ return {
2583
+ type: crossDataset.type,
2584
+ ...ensureCustomTitle(crossDataset.type, crossDataset.title),
2585
+ ...preview
2586
+ };
2587
+ })
2588
+ };
2589
+ }
2590
+ function transformGlobalDocumentReference(reference2) {
2591
+ return {
2592
+ to: (reference2.to ?? []).map((crossDataset) => {
2593
+ const preview = crossDataset.preview?.select ? { preview: { select: crossDataset.preview.select } } : {};
2594
+ return {
2595
+ type: crossDataset.type,
2596
+ ...ensureCustomTitle(crossDataset.type, crossDataset.title),
2597
+ ...preview
2598
+ };
2599
+ })
2600
+ };
2601
+ }
2602
+ const transformTypeValidationRule = (rule) => ({
2603
+ ...rule,
2604
+ constraint: "constraint" in rule && (typeof rule.constraint == "string" ? rule.constraint.toLowerCase() : retainSerializableProps(rule.constraint))
2605
+ }), validationRuleTransformers = {
2606
+ type: transformTypeValidationRule
2607
+ };
2608
+ function transformValidation(validation) {
2609
+ const validationArray = (Array.isArray(validation) ? validation : [validation]).filter(
2610
+ (value) => typeof value == "object" && "_type" in value
2611
+ ), disallowedFlags = ["type"], disallowedConstraintTypes = [Rule.FIELD_REF], serializedValidation = validationArray.map(({ _rules, _message, _level }) => {
2612
+ const message = typeof _message == "string" ? { message: _message } : {};
2613
+ return {
2614
+ rules: _rules.filter((rule) => {
2615
+ if (!("constraint" in rule))
2616
+ return !1;
2617
+ const { flag, constraint } = rule;
2618
+ return disallowedFlags.includes(flag) ? !1 : !(typeof constraint == "object" && "type" in constraint && disallowedConstraintTypes.includes(constraint.type));
2619
+ }).reduce((rules, rule) => {
2620
+ const transformedRule = (validationRuleTransformers[rule.flag] ?? ((spec) => retainSerializableProps(spec)))(rule);
2621
+ return transformedRule ? [...rules, transformedRule] : rules;
2622
+ }, []),
2623
+ level: _level,
2624
+ ...message
2625
+ };
2626
+ }).filter((group) => !!group.rules.length);
2627
+ return serializedValidation.length ? { validation: serializedValidation } : {};
2628
+ }
2629
+ function ensureCustomTitle(typeName, value) {
2630
+ const titleObject = ensureString("title", value), defaultTitle = startCase(typeName);
2631
+ return titleObject.title === defaultTitle ? {} : titleObject;
2632
+ }
2633
+ function ensureString(key, value) {
2634
+ return typeof value == "string" ? {
2635
+ [key]: value
2636
+ } : {};
2637
+ }
2638
+ function ensureConditional(key, value) {
2639
+ return typeof value == "boolean" ? {
2640
+ [key]: value
2641
+ } : typeof value == "function" ? {
2642
+ [key]: "conditional"
2643
+ } : {};
2644
+ }
2645
+ function transformBlockType(blockType, context) {
2646
+ if (blockType.jsonType !== "object" || !isType$1(blockType, "block"))
2647
+ return {};
2648
+ const childrenField = blockType.fields?.find((field) => field.name === "children");
2649
+ if (!childrenField)
2650
+ return {};
2651
+ const ofType = childrenField.type.of;
2652
+ if (!ofType)
2653
+ return {};
2654
+ const spanType = ofType.find((memberType) => memberType.name === "span");
2655
+ if (!spanType)
2656
+ return {};
2657
+ const inlineObjectTypes = ofType.filter((memberType) => memberType.name !== "span") || [];
2658
+ return {
2659
+ marks: {
2660
+ annotations: spanType.annotations.map((t) => transformType(t)),
2661
+ decorators: resolveEnabledDecorators(spanType)
2662
+ },
2663
+ lists: resolveEnabledListItems(blockType),
2664
+ styles: resolveEnabledStyles(blockType),
2665
+ of: inlineObjectTypes.map((t) => transformType(t))
2666
+ };
2667
+ }
2668
+ function resolveEnabledStyles(blockType) {
2669
+ const styleField = blockType.fields?.find((btField) => btField.name === "style");
2670
+ return resolveTitleValueArray(styleField?.type?.options?.list);
2671
+ }
2672
+ function resolveEnabledDecorators(spanType) {
2673
+ return "decorators" in spanType ? resolveTitleValueArray(spanType.decorators) : void 0;
2674
+ }
2675
+ function resolveEnabledListItems(blockType) {
2676
+ const listField = blockType.fields?.find((btField) => btField.name === "listItem");
2677
+ return resolveTitleValueArray(listField?.type?.options?.list);
2678
+ }
2679
+ function resolveTitleValueArray(possibleArray) {
2680
+ if (!possibleArray || !Array.isArray(possibleArray))
2681
+ return;
2682
+ const titledValues = possibleArray.filter(
2683
+ (d) => isRecord(d) && !!d.value && isString(d.value)
2684
+ ).map((item) => ({
2685
+ value: item.value,
2686
+ ...ensureString("title", item.title)
2687
+ }));
2688
+ if (titledValues?.length)
2689
+ return titledValues;
2690
+ }
2691
+ const extractManifestTools = async (tools, iconResolver = noopIconResolver) => Promise.all(
2692
+ tools.map(async (tool) => {
2693
+ const { title, name, icon, __internalApplicationType: type } = tool;
2694
+ return {
2695
+ title,
2696
+ name,
2697
+ type: type || null,
2698
+ icon: await iconResolver({
2699
+ icon,
2700
+ title
2701
+ })
2702
+ };
2703
+ })
2704
+ ), SANITY_WORKSPACE_SCHEMA_ID_PREFIX = "_.schemas", CURRENT_WORKSPACE_SCHEMA_VERSION = "2025-05-01", validForNamesChars = "a-zA-Z0-9_-", validForNamesPattern = new RegExp(`^[${validForNamesChars}]+$`, "g"), validForIdChars = "a-zA-Z0-9._-", validForIdPattern = new RegExp(`^[${validForIdChars}]+$`, "g"), requiredInId = SANITY_WORKSPACE_SCHEMA_ID_PREFIX.replace(/[.]/g, "\\."), idPatternString = `^${requiredInId}\\.([${validForNamesChars}]+)`, baseIdPattern = new RegExp(`${idPatternString}$`), taggedIdPattern = new RegExp(`${idPatternString}\\.tag\\.([${validForNamesChars}]+)$`);
2705
+ function getWorkspaceSchemaId(args) {
2706
+ const { workspaceName: rawWorkspaceName, tag } = args;
2707
+ let workspaceName = rawWorkspaceName, idWarning;
2708
+ workspaceName.match(validForNamesPattern) || (workspaceName = workspaceName.replace(new RegExp(`[^${validForNamesChars}]`, "g"), "_"), idWarning = [
2709
+ `Workspace "${rawWorkspaceName}" contains characters unsupported by schema _id [${validForNamesChars}], they will be replaced with _.`,
2710
+ "This could lead duplicate schema ids: consider renaming your workspace."
2711
+ ].join(`
2712
+ `));
2713
+ const safeBaseId = `${SANITY_WORKSPACE_SCHEMA_ID_PREFIX}.${workspaceName}`;
2714
+ return {
2715
+ safeBaseId,
2716
+ safeTaggedId: `${safeBaseId}${tag ? `.tag.${tag}` : ""}`,
2717
+ idWarning
2718
+ };
2719
+ }
2720
+ function parseWorkspaceSchemaId(id, errors) {
2721
+ const trimmedId = id.trim();
2722
+ if (!trimmedId.match(validForIdPattern)) {
2723
+ errors.push(`id can only contain characters in [${validForIdChars}] but found: "${trimmedId}"`);
2724
+ return;
2725
+ }
2726
+ if (trimmedId.startsWith("-")) {
2727
+ errors.push(`id cannot start with - (dash) but found: "${trimmedId}"`);
2728
+ return;
2729
+ }
2730
+ if (trimmedId.match(/\.\./g)) {
2731
+ errors.push(`id cannot have consecutive . (period) characters, but found: "${trimmedId}"`);
2732
+ return;
2733
+ }
2734
+ const [, workspace] = trimmedId.match(taggedIdPattern) ?? trimmedId.match(baseIdPattern) ?? [];
2735
+ if (!workspace) {
2736
+ errors.push(
2737
+ [
2738
+ `id must either match ${SANITY_WORKSPACE_SCHEMA_ID_PREFIX}.<workspaceName> `,
2739
+ `or ${SANITY_WORKSPACE_SCHEMA_ID_PREFIX}.<workspaceName>.tag.<tag> but found: "${trimmedId}". `,
2740
+ `Note that workspace name characters not in [${validForNamesChars}] has to be replaced with _ for schema id.`
2741
+ ].join("")
2742
+ );
2743
+ return;
2744
+ }
2745
+ return {
2746
+ schemaId: trimmedId,
2747
+ workspace
2748
+ };
2749
+ }
2750
+ function createStoredWorkspaceSchemaPayload(args) {
2751
+ return {
2752
+ version: CURRENT_WORKSPACE_SCHEMA_VERSION,
2753
+ tag: args.tag,
2754
+ workspace: {
2755
+ name: args.workspace.name,
2756
+ title: args.workspace.title
2757
+ },
2758
+ schema: args.schema
2759
+ };
2760
+ }
2353
2761
  const documentDefaultFields = (typeName) => ({
2354
2762
  _id: {
2355
2763
  type: "objectAttribute",
@@ -2950,12 +3358,19 @@ export {
2950
3358
  ValidationError,
2951
3359
  builtinTypes,
2952
3360
  createSchemaFromManifestTypes,
3361
+ createStoredWorkspaceSchemaPayload,
3362
+ extractCreateWorkspaceManifest,
3363
+ extractManifestSchemaTypes,
2953
3364
  extractSchema,
3365
+ getWorkspaceSchemaId,
2954
3366
  groupProblems,
2955
3367
  isActionEnabled,
3368
+ parseWorkspaceSchemaId,
2956
3369
  processSchemaSynchronization,
2957
3370
  resolveSearchConfig,
2958
3371
  resolveSearchConfigForBaseFieldPaths,
3372
+ validForNamesChars,
3373
+ validForNamesPattern,
2959
3374
  validateMediaLibraryAssetAspect,
2960
3375
  validateSchema
2961
3376
  };