@vexcms/core 0.0.8 → 0.0.10

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.js CHANGED
@@ -178,32 +178,41 @@ function resolveMediaCollection(props) {
178
178
  };
179
179
  }
180
180
  function defineConfig(vexConfig) {
181
- const { media: mediaInput, ...restInput } = vexConfig;
181
+ let resolvedInput = vexConfig;
182
+ if (vexConfig.plugins && vexConfig.plugins.length > 0) {
183
+ for (const plugin of vexConfig.plugins) {
184
+ resolvedInput = plugin(resolvedInput);
185
+ }
186
+ }
187
+ const { plugins: _plugins, ...inputWithoutPlugins } = resolvedInput;
188
+ const { media: mediaInput, ...restInput } = inputWithoutPlugins;
189
+ const resolved = inputWithoutPlugins;
182
190
  const config = {
183
191
  ...BASE_VEX_CONFIG,
184
192
  ...restInput,
185
193
  admin: {
186
194
  ...BASE_VEX_CONFIG.admin,
187
- ...vexConfig.admin,
195
+ ...resolved.admin,
188
196
  meta: {
189
197
  ...BASE_VEX_CONFIG.admin.meta,
190
- ...vexConfig.admin?.meta
198
+ ...resolved.admin?.meta
191
199
  },
192
200
  sidebar: {
193
201
  ...BASE_VEX_CONFIG.admin.sidebar,
194
- ...vexConfig.admin?.sidebar
202
+ ...resolved.admin?.sidebar
195
203
  },
196
204
  onboarding: {
197
205
  ...BASE_VEX_CONFIG.admin.onboarding,
198
- ...vexConfig.admin?.onboarding
206
+ ...resolved.admin?.onboarding
199
207
  },
200
- livePreview: vexConfig.admin?.livePreview
208
+ livePreview: resolved.admin?.livePreview
201
209
  },
202
210
  schema: {
203
211
  ...BASE_VEX_CONFIG.schema,
204
- ...vexConfig.schema
212
+ ...resolved.schema
205
213
  },
206
- access: vexConfig.access
214
+ access: resolved.access,
215
+ breakpoints: resolved.breakpoints
207
216
  };
208
217
  if (mediaInput) {
209
218
  if (mediaInput.collections.length === 0) {
@@ -272,6 +281,11 @@ function defineMediaCollection(props) {
272
281
  return props;
273
282
  }
274
283
 
284
+ // src/config/defineGlobal.ts
285
+ function defineGlobal(props) {
286
+ return props;
287
+ }
288
+
275
289
  // src/access/defineAccess.ts
276
290
  function defineAccess(props) {
277
291
  if (props.orgCollection && !props.userOrgField) {
@@ -461,6 +475,51 @@ function hasPermission(props) {
461
475
  return merged;
462
476
  }
463
477
 
478
+ // src/access/checkAdminAccess.ts
479
+ function checkAdminAccess(props) {
480
+ if (props.access === void 0) {
481
+ return false;
482
+ }
483
+ if (props.userRoles.length === 0) {
484
+ return false;
485
+ }
486
+ const knownRolesSet = new Set(props.access.roles);
487
+ for (const role of props.userRoles) {
488
+ if (!knownRolesSet.has(role)) continue;
489
+ const rolePerms = props.access.permissions[role];
490
+ if (rolePerms === void 0) continue;
491
+ const adminPerm = rolePerms.admin;
492
+ if (adminPerm === void 0) continue;
493
+ if (typeof adminPerm === "boolean") {
494
+ if (adminPerm) return true;
495
+ continue;
496
+ }
497
+ if (typeof adminPerm === "function") {
498
+ const callbackProps = props.organization !== void 0 ? { user: props.user, organization: props.organization } : { user: props.user };
499
+ if (adminPerm(callbackProps)) return true;
500
+ }
501
+ }
502
+ return false;
503
+ }
504
+
505
+ // src/metadata/buildSiteMetadata.ts
506
+ function buildSiteMetadata(props) {
507
+ const title = props.page?.metaTitle || props.page?.title || props.site.metaTitle || props.site.name || "Untitled";
508
+ const description = props.page?.metaDescription || props.site.metaDescription || props.site.description || "";
509
+ const ogImage = props.page?.ogImage || props.site.ogImage || void 0;
510
+ const twitterHandle = props.site.twitterHandle || void 0;
511
+ let finalTitle = title;
512
+ if (props.titleSuffix && !title.endsWith(props.titleSuffix) && title !== props.site.name) {
513
+ finalTitle = title + props.titleSuffix;
514
+ }
515
+ return {
516
+ title: finalTitle,
517
+ description,
518
+ ogImage,
519
+ twitterHandle
520
+ };
521
+ }
522
+
464
523
  // src/config/sanitizeConfig.ts
465
524
  function sanitizeConfigForClient(config) {
466
525
  const { media, ...rest } = config;
@@ -920,6 +979,50 @@ function jsonToValueTypeString(props) {
920
979
  });
921
980
  }
922
981
 
982
+ // src/fields/object/config.ts
983
+ function object(options) {
984
+ return { type: "object", ...options };
985
+ }
986
+
987
+ // src/fields/object/schemaValueType.ts
988
+ function objectToValueTypeString(props) {
989
+ const fieldEntries = [];
990
+ for (const [subFieldName, subField] of Object.entries(props.field.fields)) {
991
+ const valueType = props.resolveInnerField({
992
+ field: subField,
993
+ collectionSlug: props.collectionSlug,
994
+ fieldName: `${props.fieldName}.${subFieldName}`
995
+ });
996
+ fieldEntries.push(`${subFieldName}: ${valueType}`);
997
+ }
998
+ const objectType = fieldEntries.length <= 2 ? `v.object({ ${fieldEntries.join(", ")} })` : `v.object({
999
+ ${fieldEntries.map((e) => ` ${e},`).join("\n")}
1000
+ })`;
1001
+ return processFieldValueTypeOptions({
1002
+ field: props.field,
1003
+ collectionSlug: props.collectionSlug,
1004
+ fieldName: props.fieldName,
1005
+ expectedType: "object",
1006
+ valueType: objectType,
1007
+ skipDefaultValidation: true
1008
+ });
1009
+ }
1010
+
1011
+ // src/fields/object/columnDef.ts
1012
+ function objectColumnDef(props) {
1013
+ return {
1014
+ accessorKey: props.fieldKey,
1015
+ header: props.field.label ?? toTitleCase(props.fieldKey),
1016
+ meta: { align: props.field.admin?.cellAlignment ?? "left" },
1017
+ cell: (info) => {
1018
+ const value = info.getValue();
1019
+ if (value == null) return "";
1020
+ const str = JSON.stringify(value);
1021
+ return str.length > 50 ? str.slice(0, 50) + "..." : str;
1022
+ }
1023
+ };
1024
+ }
1025
+
923
1026
  // src/fields/richtext/config.ts
924
1027
  function richtext(options) {
925
1028
  return {
@@ -997,11 +1100,17 @@ function arrayColumnDef(props) {
997
1100
  // src/fields/array/schemaValueType.ts
998
1101
  function arrayToValueTypeString(props) {
999
1102
  const innerValueType = props.resolveInnerField({
1000
- field: props.field.field,
1103
+ field: props.field.items,
1001
1104
  collectionSlug: props.collectionSlug,
1002
1105
  fieldName: `${props.fieldName}[]`
1003
1106
  });
1004
- const unwrapped = innerValueType.replace(/^v\.optional\((.+)\)$/, "$1");
1107
+ let unwrapped = innerValueType;
1108
+ if (unwrapped.startsWith("v.optional(")) {
1109
+ const inner = unwrapped.slice("v.optional(".length);
1110
+ if (inner.endsWith(")")) {
1111
+ unwrapped = inner.slice(0, -1);
1112
+ }
1113
+ }
1005
1114
  const arrayType = `v.array(${unwrapped})`;
1006
1115
  return processFieldValueTypeOptions({
1007
1116
  field: props.field,
@@ -1064,6 +1173,7 @@ function blocksToValueTypeString(props) {
1064
1173
  const fieldEntries = [
1065
1174
  `blockType: v.literal("${block.slug}")`,
1066
1175
  `blockName: v.optional(v.string())`,
1176
+ `blockStyles: v.optional(v.string())`,
1067
1177
  `_key: v.string()`
1068
1178
  ];
1069
1179
  for (const [fieldName, field] of Object.entries(block.fields)) {
@@ -1075,10 +1185,18 @@ function blocksToValueTypeString(props) {
1075
1185
  });
1076
1186
  fieldEntries.push(`${fieldName}: ${valueType}`);
1077
1187
  }
1078
- objectTypes.push(`v.object({${fieldEntries.join(", ")}})`);
1188
+ objectTypes.push(
1189
+ `v.object({
1190
+ ${fieldEntries.map((e) => ` ${e},`).join("\n")}
1191
+ })`
1192
+ );
1079
1193
  }
1080
- const innerType = objectTypes.length === 1 ? objectTypes[0] : `v.union(${objectTypes.join(", ")})`;
1081
- const arrayType = `v.array(${innerType})`;
1194
+ const innerType = objectTypes.length === 1 ? objectTypes[0] : `v.union(
1195
+ ${objectTypes.map((o) => ` ${o},`).join("\n")}
1196
+ )`;
1197
+ const arrayType = `v.array(
1198
+ ${innerType}
1199
+ )`;
1082
1200
  return processFieldValueTypeOptions({
1083
1201
  field: props.field,
1084
1202
  collectionSlug: props.collectionSlug,
@@ -1106,6 +1224,42 @@ function blocksColumnDef(props) {
1106
1224
  };
1107
1225
  }
1108
1226
 
1227
+ // src/fields/color/config.ts
1228
+ function color(props) {
1229
+ return {
1230
+ ...props,
1231
+ type: "color"
1232
+ };
1233
+ }
1234
+
1235
+ // src/fields/color/schemaValueType.ts
1236
+ function colorToValueTypeString(props) {
1237
+ return processFieldValueTypeOptions({
1238
+ field: props.field,
1239
+ collectionSlug: props.collectionSlug,
1240
+ fieldName: props.fieldName,
1241
+ expectedType: "string",
1242
+ valueType: TEXT_VALUETYPE
1243
+ });
1244
+ }
1245
+
1246
+ // src/fields/color/columnDef.ts
1247
+ function colorColumnDef(props) {
1248
+ return {
1249
+ accessorKey: props.fieldKey,
1250
+ header: props.field.label ?? props.fieldKey,
1251
+ size: 120,
1252
+ cell: (info) => {
1253
+ const value = info.getValue();
1254
+ if (!value) return "";
1255
+ return value;
1256
+ },
1257
+ meta: {
1258
+ type: "color"
1259
+ }
1260
+ };
1261
+ }
1262
+
1109
1263
  // src/valueTypes/extract.ts
1110
1264
  function fieldToValueType(props) {
1111
1265
  const { field, collectionSlug, fieldName } = props;
@@ -1128,6 +1282,13 @@ function fieldToValueType(props) {
1128
1282
  return uploadToValueTypeString({ field, collectionSlug, fieldName });
1129
1283
  case "json":
1130
1284
  return jsonToValueTypeString({ field, collectionSlug, fieldName });
1285
+ case "object":
1286
+ return objectToValueTypeString({
1287
+ field,
1288
+ collectionSlug,
1289
+ fieldName,
1290
+ resolveInnerField: fieldToValueType
1291
+ });
1131
1292
  case "richtext":
1132
1293
  return richtextToValueTypeString({ field, collectionSlug, fieldName });
1133
1294
  case "array":
@@ -1150,6 +1311,10 @@ function fieldToValueType(props) {
1150
1311
  }),
1151
1312
  visitedBlockSlugs: props.visitedBlockSlugs
1152
1313
  });
1314
+ case "color":
1315
+ return colorToValueTypeString({ field, collectionSlug, fieldName });
1316
+ case "tabs":
1317
+ return "v.any()";
1153
1318
  case "ui":
1154
1319
  throw new VexFieldValidationError(
1155
1320
  collectionSlug,
@@ -1390,6 +1555,43 @@ function buildSlugRegistry(props) {
1390
1555
  }
1391
1556
 
1392
1557
  // src/valueTypes/generate.ts
1558
+ function expandFields(props) {
1559
+ const result = [];
1560
+ for (const [fieldName, field] of Object.entries(props.fields)) {
1561
+ if (field.type === "ui") continue;
1562
+ if (field.type === "tabs") {
1563
+ const tabsField = field;
1564
+ for (const tab of tabsField.tabs) {
1565
+ const innerFields = [];
1566
+ for (const [innerName, innerField] of Object.entries(tab.fields)) {
1567
+ if (innerField.type === "ui") continue;
1568
+ const innerValueType = fieldToValueType({
1569
+ field: innerField,
1570
+ collectionSlug: props.collectionSlug,
1571
+ fieldName: innerName
1572
+ });
1573
+ innerFields.push(`${innerName}: ${innerValueType}`);
1574
+ }
1575
+ if (innerFields.length > 0) {
1576
+ result.push({
1577
+ name: tab.slug,
1578
+ valueType: `v.optional(v.object({ ${innerFields.join(", ")} }))`
1579
+ });
1580
+ }
1581
+ }
1582
+ } else {
1583
+ result.push({
1584
+ name: fieldName,
1585
+ valueType: fieldToValueType({
1586
+ fieldName,
1587
+ field,
1588
+ collectionSlug: props.collectionSlug
1589
+ })
1590
+ });
1591
+ }
1592
+ }
1593
+ return result;
1594
+ }
1393
1595
  function generateVexSchema(props) {
1394
1596
  const config = props.config;
1395
1597
  buildSlugRegistry({ config });
@@ -1440,19 +1642,10 @@ function generateVexSchema(props) {
1440
1642
  searchIndexes.push(si);
1441
1643
  }
1442
1644
  } else {
1443
- for (const [fieldName, field] of Object.entries(
1444
- collection.fields
1445
- )) {
1446
- if (field.type === "ui") continue;
1447
- fields.push({
1448
- name: fieldName,
1449
- valueType: fieldToValueType({
1450
- fieldName,
1451
- field,
1452
- collectionSlug: collection.slug
1453
- })
1454
- });
1455
- }
1645
+ fields.push(...expandFields({
1646
+ fields: collection.fields,
1647
+ collectionSlug: collection.slug
1648
+ }));
1456
1649
  }
1457
1650
  lines.push(
1458
1651
  "",
@@ -1575,6 +1768,11 @@ function generateVexSchema(props) {
1575
1768
  for (const f of fields) {
1576
1769
  lines.push(` ${f.name}: ${f.valueType},`);
1577
1770
  }
1771
+ lines.push(` vex_status: v.optional(v.union(v.literal("draft"), v.literal("published"))),`);
1772
+ if (global.versions?.drafts) {
1773
+ lines.push(` vex_version: v.optional(v.number()),`);
1774
+ lines.push(` vex_publishedAt: v.optional(v.number()),`);
1775
+ }
1578
1776
  lines.push("})");
1579
1777
  for (const i of indexes) {
1580
1778
  const fieldList = i.fields.map((f) => `"${f}"`).join(", ");
@@ -1588,7 +1786,12 @@ function generateVexSchema(props) {
1588
1786
  lines.push(" collection: v.string(),");
1589
1787
  lines.push(" documentId: v.string(),");
1590
1788
  lines.push(" version: v.number(),");
1591
- lines.push(` status: v.union(v.literal("draft"), v.literal("published"), v.literal("autosave"), v.literal("previewSnapshot")),`);
1789
+ lines.push(" status: v.union(");
1790
+ lines.push(` v.literal("draft"),`);
1791
+ lines.push(` v.literal("published"),`);
1792
+ lines.push(` v.literal("autosave"),`);
1793
+ lines.push(` v.literal("previewSnapshot"),`);
1794
+ lines.push(" ),");
1592
1795
  lines.push(" snapshot: v.any(),");
1593
1796
  lines.push(" createdAt: v.number(),");
1594
1797
  lines.push(" createdBy: v.optional(v.string()),");
@@ -1683,7 +1886,7 @@ function generateColumns(props) {
1683
1886
  continue;
1684
1887
  }
1685
1888
  if (field.admin?.hidden) continue;
1686
- if (field.type === "ui") continue;
1889
+ if (field.type === "ui" || field.type === "tabs") continue;
1687
1890
  const col = buildColumnDef(fieldKey, field);
1688
1891
  if (useAsTitle && fieldKey === useAsTitle) {
1689
1892
  col.meta = { ...col.meta, isTitle: true };
@@ -1706,7 +1909,7 @@ function generateColumns(props) {
1706
1909
  for (const fieldKey of allFieldKeys) {
1707
1910
  const field = fields[fieldKey] ?? authFields[fieldKey];
1708
1911
  if (field.admin?.hidden) continue;
1709
- if (field.type === "ui") continue;
1912
+ if (field.type === "ui" || field.type === "tabs") continue;
1710
1913
  const col = buildColumnDef(fieldKey, field);
1711
1914
  if (useAsTitle && fieldKey === useAsTitle) {
1712
1915
  col.meta = { ...col.meta, isTitle: true };
@@ -1741,6 +1944,8 @@ function buildColumnDef(fieldKey, field) {
1741
1944
  return relationshipColumnDef({ fieldKey, field });
1742
1945
  case "json":
1743
1946
  return jsonColumnDef({ fieldKey, field });
1947
+ case "object":
1948
+ return objectColumnDef({ fieldKey, field });
1744
1949
  case "richtext":
1745
1950
  return richtextColumnDef({ fieldKey, field });
1746
1951
  case "array":
@@ -1749,6 +1954,8 @@ function buildColumnDef(fieldKey, field) {
1749
1954
  return uploadColumnDef({ fieldKey, field });
1750
1955
  case "blocks":
1751
1956
  return blocksColumnDef({ fieldKey, field });
1957
+ case "color":
1958
+ return colorColumnDef({ fieldKey, field });
1752
1959
  default:
1753
1960
  return {
1754
1961
  accessorKey: fieldKey,
@@ -1764,6 +1971,19 @@ function generateFormSchema(props) {
1764
1971
  for (const [fieldName, field] of Object.entries(props.fields)) {
1765
1972
  if (field.admin?.hidden) continue;
1766
1973
  if (field.type === "ui") continue;
1974
+ if (field.type === "tabs") {
1975
+ for (const tab of field.tabs) {
1976
+ const tabFields = {};
1977
+ for (const [subName, subField] of Object.entries(tab.fields)) {
1978
+ if (subField.type === "ui") continue;
1979
+ let subValidator = fieldMetaToZod({ field: subField });
1980
+ if (!subField.required) subValidator = subValidator.optional();
1981
+ tabFields[subName] = subValidator;
1982
+ }
1983
+ shape[tab.slug] = z.object(tabFields).optional();
1984
+ }
1985
+ continue;
1986
+ }
1767
1987
  let validator = fieldMetaToZod({ field });
1768
1988
  if (!field.required) {
1769
1989
  validator = validator.optional();
@@ -1815,10 +2035,23 @@ function fieldMetaToZod(props) {
1815
2035
  }
1816
2036
  case "json":
1817
2037
  return z.any();
2038
+ case "object": {
2039
+ const shape = {};
2040
+ for (const [subName, subField] of Object.entries(props.field.fields)) {
2041
+ let subValidator = fieldMetaToZod({ field: subField });
2042
+ if (!subField.required) subValidator = subValidator.optional();
2043
+ shape[subName] = subValidator;
2044
+ }
2045
+ return z.object(shape);
2046
+ }
1818
2047
  case "richtext":
1819
2048
  return z.any();
2049
+ case "color":
2050
+ return z.string();
2051
+ case "tabs":
2052
+ return z.any();
1820
2053
  case "array": {
1821
- let schema = z.array(fieldMetaToZod({ field: props.field.field }));
2054
+ let schema = z.array(fieldMetaToZod({ field: props.field.items }));
1822
2055
  if (props.field.min != null) schema = schema.min(props.field.min);
1823
2056
  if (props.field.max != null) schema = schema.max(props.field.max);
1824
2057
  return schema;
@@ -1878,15 +2111,26 @@ function getFormDefaultValue(props) {
1878
2111
  case "imageUrl":
1879
2112
  return props.field.defaultValue ?? "";
1880
2113
  case "relationship":
1881
- return props.field.hasMany ? [] : "";
2114
+ return props.field.hasMany ? [] : void 0;
1882
2115
  case "upload":
1883
- return props.field.hasMany ? [] : "";
2116
+ return props.field.hasMany ? [] : void 0;
1884
2117
  case "json":
1885
2118
  return {};
2119
+ case "object": {
2120
+ const defaults = {};
2121
+ for (const [subName, subField] of Object.entries(props.field.fields)) {
2122
+ defaults[subName] = getFormDefaultValue({ field: subField });
2123
+ }
2124
+ return defaults;
2125
+ }
1886
2126
  case "richtext":
1887
2127
  return [];
2128
+ case "color":
2129
+ return props.field.defaultValue ?? "";
2130
+ case "tabs":
2131
+ return {};
1888
2132
  case "array":
1889
- return [];
2133
+ return props.field.defaultValue ?? [];
1890
2134
  case "blocks":
1891
2135
  return [];
1892
2136
  case "ui":
@@ -1900,6 +2144,17 @@ function generateFormDefaultValues(props) {
1900
2144
  for (const [fieldName, field] of Object.entries(props.fields)) {
1901
2145
  if (field.admin?.hidden) continue;
1902
2146
  if (field.type === "ui") continue;
2147
+ if (field.type === "tabs") {
2148
+ for (const tab of field.tabs) {
2149
+ const tabDefaults = {};
2150
+ for (const [subName, subField] of Object.entries(tab.fields)) {
2151
+ if (subField.type === "ui") continue;
2152
+ tabDefaults[subName] = getFormDefaultValue({ field: subField });
2153
+ }
2154
+ result[tab.slug] = tabDefaults;
2155
+ }
2156
+ continue;
2157
+ }
1903
2158
  result[fieldName] = getFormDefaultValue({ field });
1904
2159
  }
1905
2160
  return result;
@@ -1915,8 +2170,16 @@ function ui(props) {
1915
2170
  };
1916
2171
  }
1917
2172
 
2173
+ // src/fields/tabs/config.ts
2174
+ function tabs(props) {
2175
+ return {
2176
+ ...props,
2177
+ type: "tabs"
2178
+ };
2179
+ }
2180
+
1918
2181
  // src/types/fields.ts
1919
- var RESERVED_BLOCK_FIELD_NAMES = ["blockType", "blockName", "_key"];
2182
+ var RESERVED_BLOCK_FIELD_NAMES = ["blockType", "blockName", "_key", "blockStyles"];
1920
2183
 
1921
2184
  // src/blocks/defineBlock.ts
1922
2185
  function defineBlock(props) {
@@ -1934,6 +2197,23 @@ function defineBlock(props) {
1934
2197
  );
1935
2198
  }
1936
2199
  }
2200
+ const VALID_STYLE_TIERS = ["container", "text", "layout", "media"];
2201
+ if (props.admin?.blockStyles && props.admin.blockStyles !== true) {
2202
+ if (!Array.isArray(props.admin.blockStyles)) {
2203
+ throw new VexBlockValidationError(
2204
+ props.slug,
2205
+ `admin.blockStyles must be true or an array of style tier names. Got: ${typeof props.admin.blockStyles}`
2206
+ );
2207
+ }
2208
+ for (const tier of props.admin.blockStyles) {
2209
+ if (!VALID_STYLE_TIERS.includes(tier)) {
2210
+ throw new VexBlockValidationError(
2211
+ props.slug,
2212
+ `Invalid style tier "${tier}" in admin.blockStyles. Valid tiers: ${VALID_STYLE_TIERS.join(", ")}`
2213
+ );
2214
+ }
2215
+ }
2216
+ }
1937
2217
  return {
1938
2218
  slug: props.slug,
1939
2219
  label: props.label,
@@ -1943,6 +2223,237 @@ function defineBlock(props) {
1943
2223
  };
1944
2224
  }
1945
2225
 
2226
+ // src/styles/presets.ts
2227
+ var SPACING_PRESETS = [
2228
+ { value: "0", label: "0", hint: "0px" },
2229
+ { value: "0.5", label: "0.5", hint: "2px / 0.125rem" },
2230
+ { value: "1", label: "1", hint: "4px / 0.25rem" },
2231
+ { value: "1.5", label: "1.5", hint: "6px / 0.375rem" },
2232
+ { value: "2", label: "2", hint: "8px / 0.5rem" },
2233
+ { value: "3", label: "3", hint: "12px / 0.75rem" },
2234
+ { value: "4", label: "4", hint: "16px / 1rem" },
2235
+ { value: "5", label: "5", hint: "20px / 1.25rem" },
2236
+ { value: "6", label: "6", hint: "24px / 1.5rem" },
2237
+ { value: "8", label: "8", hint: "32px / 2rem" },
2238
+ { value: "10", label: "10", hint: "40px / 2.5rem" },
2239
+ { value: "12", label: "12", hint: "48px / 3rem" },
2240
+ { value: "16", label: "16", hint: "64px / 4rem" },
2241
+ { value: "20", label: "20", hint: "80px / 5rem" },
2242
+ { value: "24", label: "24", hint: "96px / 6rem" }
2243
+ ];
2244
+ var FONT_SIZE_PRESETS = [
2245
+ { value: "xs", label: "XS", hint: "12px / 0.75rem" },
2246
+ { value: "sm", label: "SM", hint: "14px / 0.875rem" },
2247
+ { value: "base", label: "Base", hint: "16px / 1rem" },
2248
+ { value: "lg", label: "LG", hint: "18px / 1.125rem" },
2249
+ { value: "xl", label: "XL", hint: "20px / 1.25rem" },
2250
+ { value: "2xl", label: "2XL", hint: "24px / 1.5rem" },
2251
+ { value: "3xl", label: "3XL", hint: "30px / 1.875rem" },
2252
+ { value: "4xl", label: "4XL", hint: "36px / 2.25rem" },
2253
+ { value: "5xl", label: "5XL", hint: "48px / 3rem" }
2254
+ ];
2255
+ var FONT_WEIGHT_PRESETS = [
2256
+ { value: "thin", label: "Thin", hint: "100" },
2257
+ { value: "light", label: "Light", hint: "300" },
2258
+ { value: "normal", label: "Normal", hint: "400" },
2259
+ { value: "medium", label: "Medium", hint: "500" },
2260
+ { value: "semibold", label: "Semibold", hint: "600" },
2261
+ { value: "bold", label: "Bold", hint: "700" },
2262
+ { value: "extrabold", label: "Extra Bold", hint: "800" }
2263
+ ];
2264
+ var BORDER_RADIUS_PRESETS = [
2265
+ { value: "none", label: "None", hint: "0px" },
2266
+ { value: "sm", label: "SM", hint: "2px / 0.125rem" },
2267
+ { value: "DEFAULT", label: "Default", hint: "4px / 0.25rem" },
2268
+ { value: "md", label: "MD", hint: "6px / 0.375rem" },
2269
+ { value: "lg", label: "LG", hint: "8px / 0.5rem" },
2270
+ { value: "xl", label: "XL", hint: "12px / 0.75rem" },
2271
+ { value: "2xl", label: "2XL", hint: "16px / 1rem" },
2272
+ { value: "3xl", label: "3XL", hint: "24px / 1.5rem" },
2273
+ { value: "full", label: "Full", hint: "9999px" }
2274
+ ];
2275
+ var BOX_SHADOW_PRESETS = [
2276
+ { value: "none", label: "None", hint: "no shadow" },
2277
+ { value: "sm", label: "SM", hint: "small" },
2278
+ { value: "DEFAULT", label: "Default", hint: "medium" },
2279
+ { value: "md", label: "MD", hint: "medium" },
2280
+ { value: "lg", label: "LG", hint: "large" },
2281
+ { value: "xl", label: "XL", hint: "extra large" },
2282
+ { value: "2xl", label: "2XL", hint: "xx-large" }
2283
+ ];
2284
+ var OPACITY_PRESETS = [
2285
+ { value: "0", label: "0%", hint: "invisible" },
2286
+ { value: "5", label: "5%", hint: "" },
2287
+ { value: "10", label: "10%", hint: "" },
2288
+ { value: "25", label: "25%", hint: "" },
2289
+ { value: "50", label: "50%", hint: "" },
2290
+ { value: "75", label: "75%", hint: "" },
2291
+ { value: "100", label: "100%", hint: "fully visible" }
2292
+ ];
2293
+ var WIDTH_PRESETS = [
2294
+ { value: "auto", label: "Auto", hint: "auto" },
2295
+ { value: "full", label: "Full", hint: "100%" },
2296
+ { value: "screen", label: "Screen", hint: "100vw" },
2297
+ { value: "1/2", label: "1/2", hint: "50%" },
2298
+ { value: "1/3", label: "1/3", hint: "33.33%" },
2299
+ { value: "2/3", label: "2/3", hint: "66.67%" },
2300
+ { value: "1/4", label: "1/4", hint: "25%" },
2301
+ { value: "3/4", label: "3/4", hint: "75%" },
2302
+ { value: "max", label: "Max Content", hint: "max-content" },
2303
+ { value: "fit", label: "Fit Content", hint: "fit-content" }
2304
+ ];
2305
+ var MAX_WIDTH_PRESETS = [
2306
+ { value: "none", label: "None", hint: "no limit" },
2307
+ { value: "xs", label: "XS", hint: "320px / 20rem" },
2308
+ { value: "sm", label: "SM", hint: "384px / 24rem" },
2309
+ { value: "md", label: "MD", hint: "448px / 28rem" },
2310
+ { value: "lg", label: "LG", hint: "512px / 32rem" },
2311
+ { value: "xl", label: "XL", hint: "576px / 36rem" },
2312
+ { value: "2xl", label: "2XL", hint: "672px / 42rem" },
2313
+ { value: "3xl", label: "3XL", hint: "768px / 48rem" },
2314
+ { value: "4xl", label: "4XL", hint: "896px / 56rem" },
2315
+ { value: "5xl", label: "5XL", hint: "1024px / 64rem" },
2316
+ { value: "6xl", label: "6XL", hint: "1152px / 72rem" },
2317
+ { value: "7xl", label: "7XL", hint: "1280px / 80rem" },
2318
+ { value: "full", label: "Full", hint: "100%" },
2319
+ { value: "screen", label: "Screen", hint: "100vw" }
2320
+ ];
2321
+ var LINE_HEIGHT_PRESETS = [
2322
+ { value: "none", label: "None", hint: "1" },
2323
+ { value: "tight", label: "Tight", hint: "1.25" },
2324
+ { value: "snug", label: "Snug", hint: "1.375" },
2325
+ { value: "normal", label: "Normal", hint: "1.5" },
2326
+ { value: "relaxed", label: "Relaxed", hint: "1.625" },
2327
+ { value: "loose", label: "Loose", hint: "2" }
2328
+ ];
2329
+ var LETTER_SPACING_PRESETS = [
2330
+ { value: "tighter", label: "Tighter", hint: "-0.05em" },
2331
+ { value: "tight", label: "Tight", hint: "-0.025em" },
2332
+ { value: "normal", label: "Normal", hint: "0em" },
2333
+ { value: "wide", label: "Wide", hint: "0.025em" },
2334
+ { value: "wider", label: "Wider", hint: "0.05em" },
2335
+ { value: "widest", label: "Widest", hint: "0.1em" }
2336
+ ];
2337
+ var BORDER_WIDTH_PRESETS = [
2338
+ { value: "0", label: "None", hint: "0px" },
2339
+ { value: "DEFAULT", label: "Default", hint: "1px" },
2340
+ { value: "2", label: "2", hint: "2px" },
2341
+ { value: "4", label: "4", hint: "4px" },
2342
+ { value: "8", label: "8", hint: "8px" }
2343
+ ];
2344
+ var ASPECT_RATIO_PRESETS = [
2345
+ { value: "auto", label: "Auto", hint: "auto" },
2346
+ { value: "square", label: "Square", hint: "1 / 1" },
2347
+ { value: "video", label: "Video", hint: "16 / 9" },
2348
+ { value: "4/3", label: "4:3", hint: "4 / 3" },
2349
+ { value: "3/2", label: "3:2", hint: "3 / 2" }
2350
+ ];
2351
+
2352
+ // src/styles/blockStylesToTailwind.ts
2353
+ var PROPERTY_CLASS_MAP = {
2354
+ // Container — spacing
2355
+ margin: (v2) => `m-${v2}`,
2356
+ marginTop: (v2) => `mt-${v2}`,
2357
+ marginRight: (v2) => `mr-${v2}`,
2358
+ marginBottom: (v2) => `mb-${v2}`,
2359
+ marginLeft: (v2) => `ml-${v2}`,
2360
+ padding: (v2) => `p-${v2}`,
2361
+ paddingTop: (v2) => `pt-${v2}`,
2362
+ paddingRight: (v2) => `pr-${v2}`,
2363
+ paddingBottom: (v2) => `pb-${v2}`,
2364
+ paddingLeft: (v2) => `pl-${v2}`,
2365
+ // Container — sizing
2366
+ width: (v2) => `w-${v2}`,
2367
+ maxWidth: (v2) => `max-w-${v2}`,
2368
+ // Container — background
2369
+ backgroundColor: (v2) => {
2370
+ if (v2.startsWith("var(") || v2.startsWith("#") || v2.startsWith("rgb") || v2.startsWith("hsl") || v2.startsWith("oklch")) {
2371
+ return `bg-[${v2}]`;
2372
+ }
2373
+ return `bg-${v2}`;
2374
+ },
2375
+ // Container — border
2376
+ borderWidth: (v2) => v2 === "DEFAULT" ? "border" : `border-${v2}`,
2377
+ borderColor: (v2) => {
2378
+ if (v2.startsWith("var(") || v2.startsWith("#") || v2.startsWith("rgb") || v2.startsWith("hsl") || v2.startsWith("oklch")) {
2379
+ return `border-[${v2}]`;
2380
+ }
2381
+ return `border-${v2}`;
2382
+ },
2383
+ borderStyle: (v2) => `border-${v2}`,
2384
+ borderRadius: (v2) => v2 === "DEFAULT" ? "rounded" : `rounded-${v2}`,
2385
+ // Container — effects
2386
+ boxShadow: (v2) => v2 === "DEFAULT" ? "shadow" : `shadow-${v2}`,
2387
+ opacity: (v2) => `opacity-${v2}`,
2388
+ // Container — display
2389
+ display: (v2) => {
2390
+ if (v2 === "inline-flex") return "inline-flex";
2391
+ return v2;
2392
+ },
2393
+ overflow: (v2) => `overflow-${v2}`,
2394
+ // Text
2395
+ textAlign: (v2) => `text-${v2}`,
2396
+ fontSize: (v2) => `text-${v2}`,
2397
+ fontWeight: (v2) => `font-${v2}`,
2398
+ color: (v2) => {
2399
+ if (v2.startsWith("var(") || v2.startsWith("#") || v2.startsWith("rgb") || v2.startsWith("hsl") || v2.startsWith("oklch")) {
2400
+ return `text-[${v2}]`;
2401
+ }
2402
+ return `text-${v2}`;
2403
+ },
2404
+ lineHeight: (v2) => `leading-${v2}`,
2405
+ letterSpacing: (v2) => `tracking-${v2}`,
2406
+ // Layout
2407
+ gap: (v2) => `gap-${v2}`,
2408
+ flexDirection: (v2) => {
2409
+ const map = {
2410
+ row: "flex-row",
2411
+ column: "flex-col",
2412
+ "row-reverse": "flex-row-reverse",
2413
+ "column-reverse": "flex-col-reverse"
2414
+ };
2415
+ return map[v2] ?? `flex-${v2}`;
2416
+ },
2417
+ alignItems: (v2) => `items-${v2}`,
2418
+ justifyContent: (v2) => `justify-${v2}`,
2419
+ flexWrap: (v2) => `flex-${v2}`,
2420
+ // Media
2421
+ objectFit: (v2) => `object-${v2}`,
2422
+ aspectRatio: (v2) => `aspect-${v2}`,
2423
+ objectPosition: (v2) => `object-${v2}`
2424
+ };
2425
+ function stylesToClasses(props) {
2426
+ const classes = [];
2427
+ for (const [key, value] of Object.entries(props.styles)) {
2428
+ if (value === void 0 || value === null || value === "") continue;
2429
+ const mapper = PROPERTY_CLASS_MAP[key];
2430
+ if (!mapper) continue;
2431
+ classes.push(mapper(String(value)));
2432
+ }
2433
+ return classes;
2434
+ }
2435
+ function blockStylesToTailwind(props) {
2436
+ if (!props.blockStylesJson) return "";
2437
+ let data;
2438
+ try {
2439
+ data = JSON.parse(props.blockStylesJson);
2440
+ } catch {
2441
+ return "";
2442
+ }
2443
+ const result = [];
2444
+ if (data.base) {
2445
+ result.push(...stylesToClasses({ styles: data.base }));
2446
+ }
2447
+ const breakpointKeys = Object.keys(data).filter((k) => k !== "base").sort();
2448
+ for (const bp of breakpointKeys) {
2449
+ const classes = stylesToClasses({ styles: data[bp] });
2450
+ for (const cls of classes) {
2451
+ result.push(`${bp}:${cls}`);
2452
+ }
2453
+ }
2454
+ return result.join(" ");
2455
+ }
2456
+
1946
2457
  // src/typeGen/slugToInterfaceName.ts
1947
2458
  function slugToInterfaceName(props) {
1948
2459
  return props.slug.replace(/[-_]+/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").split(/\s+/).filter(Boolean).map((seg) => seg.charAt(0).toUpperCase() + seg.slice(1).toLowerCase()).join("");
@@ -1963,8 +2474,14 @@ function fieldToTypeString(props) {
1963
2474
  return "string";
1964
2475
  case "json":
1965
2476
  return "Record<string, unknown>";
2477
+ case "object":
2478
+ return "Record<string, unknown>";
1966
2479
  case "richtext":
1967
2480
  return "RichTextDocument";
2481
+ case "color":
2482
+ return "string";
2483
+ case "tabs":
2484
+ return "Record<string, unknown>";
1968
2485
  case "ui":
1969
2486
  return "never";
1970
2487
  case "select": {
@@ -1986,7 +2503,7 @@ function fieldToTypeString(props) {
1986
2503
  }
1987
2504
  case "array": {
1988
2505
  const inner = fieldToTypeString({
1989
- field: props.field.field,
2506
+ field: props.field.items,
1990
2507
  blockInterfaceNames: props.blockInterfaceNames
1991
2508
  });
1992
2509
  const needsParens = inner.includes("|");
@@ -2023,6 +2540,10 @@ function generateVexTypes(props) {
2023
2540
  collectBlocks(block.fields);
2024
2541
  }
2025
2542
  }
2543
+ } else if (field.type === "tabs") {
2544
+ for (const tab of field.tabs) {
2545
+ collectBlocks(tab.fields);
2546
+ }
2026
2547
  }
2027
2548
  }
2028
2549
  }
@@ -2093,6 +2614,10 @@ function generateVexTypes(props) {
2093
2614
  for (const field of Object.values(fields)) {
2094
2615
  if (field.type === "relationship" || field.type === "upload") {
2095
2616
  needsIdImport = true;
2617
+ } else if (field.type === "tabs") {
2618
+ for (const tab of field.tabs) {
2619
+ checkForIdFields(tab.fields);
2620
+ }
2096
2621
  }
2097
2622
  }
2098
2623
  }
@@ -2118,6 +2643,10 @@ function generateVexTypes(props) {
2118
2643
  for (const field of Object.values(fields)) {
2119
2644
  if (field.type === "richtext") {
2120
2645
  needsRichTextImport = true;
2646
+ } else if (field.type === "tabs") {
2647
+ for (const tab of field.tabs) {
2648
+ checkForRichTextFields(tab.fields);
2649
+ }
2121
2650
  }
2122
2651
  }
2123
2652
  }
@@ -2212,6 +2741,7 @@ function generateVexTypes(props) {
2212
2741
  generateGlobalInterface({
2213
2742
  name,
2214
2743
  slug: g.slug,
2744
+ tableName: g.tableName ?? g.slug,
2215
2745
  fields: g.fields,
2216
2746
  blockInterfaceNames
2217
2747
  })
@@ -2240,18 +2770,7 @@ function generateBlockInterface(props) {
2240
2770
  lines.push(` blockType: '${props.block.slug}';`);
2241
2771
  lines.push(` blockName?: string;`);
2242
2772
  lines.push(` _key: string;`);
2243
- for (const [fieldName, field] of Object.entries(props.block.fields)) {
2244
- const f = field;
2245
- if (f.type === "ui") continue;
2246
- const label = f.label;
2247
- if (label) lines.push(` /** ${label} */`);
2248
- const optional = f.required ? "" : "?";
2249
- const typeStr = fieldToTypeString({
2250
- field: f,
2251
- blockInterfaceNames: props.blockInterfaceNames
2252
- });
2253
- lines.push(` ${fieldName}${optional}: ${typeStr};`);
2254
- }
2773
+ lines.push(...generateFieldLines({ fields: props.block.fields, blockInterfaceNames: props.blockInterfaceNames }));
2255
2774
  lines.push("}");
2256
2775
  return lines.join("\n");
2257
2776
  }
@@ -2265,18 +2784,7 @@ function generateCollectionInterface(props) {
2265
2784
  lines.push(` vex_version?: number;`);
2266
2785
  lines.push(` vex_publishedAt?: number;`);
2267
2786
  }
2268
- for (const [fieldName, field] of Object.entries(props.fields)) {
2269
- const f = field;
2270
- if (f.type === "ui") continue;
2271
- const label = f.label;
2272
- if (label) lines.push(` /** ${label} */`);
2273
- const optional = f.required ? "" : "?";
2274
- const typeStr = fieldToTypeString({
2275
- field: f,
2276
- blockInterfaceNames: props.blockInterfaceNames
2277
- });
2278
- lines.push(` ${fieldName}${optional}: ${typeStr};`);
2279
- }
2787
+ lines.push(...generateFieldLines({ fields: props.fields, blockInterfaceNames: props.blockInterfaceNames }));
2280
2788
  lines.push("}");
2281
2789
  return lines.join("\n");
2282
2790
  }
@@ -2293,42 +2801,58 @@ function generateMediaCollectionInterface(props) {
2293
2801
  lines.push(` width?: number;`);
2294
2802
  lines.push(` height?: number;`);
2295
2803
  const lockedSet = /* @__PURE__ */ new Set([...LOCKED_MEDIA_FIELDS, ...OVERRIDABLE_MEDIA_FIELDS]);
2804
+ const filteredFields = {};
2296
2805
  for (const [fieldName, field] of Object.entries(props.userFields)) {
2297
2806
  if (lockedSet.has(fieldName)) continue;
2298
- const f = field;
2299
- if (f.type === "ui") continue;
2300
- const label = f.label;
2301
- if (label) lines.push(` /** ${label} */`);
2302
- const optional = f.required ? "" : "?";
2303
- const typeStr = fieldToTypeString({
2304
- field: f,
2305
- blockInterfaceNames: props.blockInterfaceNames
2306
- });
2307
- lines.push(` ${fieldName}${optional}: ${typeStr};`);
2807
+ filteredFields[fieldName] = field;
2308
2808
  }
2809
+ lines.push(...generateFieldLines({ fields: filteredFields, blockInterfaceNames: props.blockInterfaceNames }));
2309
2810
  lines.push("}");
2310
2811
  return lines.join("\n");
2311
2812
  }
2312
2813
  function generateGlobalInterface(props) {
2313
2814
  const lines = [];
2314
2815
  lines.push(`export interface ${props.name} {`);
2315
- lines.push(` _id: Id<'vex_globals'>;`);
2816
+ lines.push(` _id: Id<'${props.tableName}'>;`);
2316
2817
  lines.push(` _creationTime: number;`);
2317
- lines.push(` vexGlobalSlug: '${props.slug}';`);
2818
+ lines.push(...generateFieldLines({ fields: props.fields, blockInterfaceNames: props.blockInterfaceNames }));
2819
+ lines.push("}");
2820
+ return lines.join("\n");
2821
+ }
2822
+ function generateFieldLines(props) {
2823
+ const lines = [];
2318
2824
  for (const [fieldName, field] of Object.entries(props.fields)) {
2319
2825
  const f = field;
2320
2826
  if (f.type === "ui") continue;
2321
- const label = f.label;
2322
- if (label) lines.push(` /** ${label} */`);
2323
- const optional = f.required ? "" : "?";
2324
- const typeStr = fieldToTypeString({
2325
- field: f,
2326
- blockInterfaceNames: props.blockInterfaceNames
2327
- });
2328
- lines.push(` ${fieldName}${optional}: ${typeStr};`);
2827
+ if (f.type === "tabs") {
2828
+ const tabsField = f;
2829
+ for (const tab of tabsField.tabs) {
2830
+ const innerProps = [];
2831
+ for (const [innerName, innerField] of Object.entries(tab.fields)) {
2832
+ if (innerField.type === "ui") continue;
2833
+ const innerOptional = innerField.required ? "" : "?";
2834
+ const innerTypeStr = fieldToTypeString({
2835
+ field: innerField,
2836
+ blockInterfaceNames: props.blockInterfaceNames
2837
+ });
2838
+ innerProps.push(`${innerName}${innerOptional}: ${innerTypeStr}`);
2839
+ }
2840
+ if (innerProps.length > 0) {
2841
+ lines.push(` ${tab.slug}?: { ${innerProps.join("; ")} };`);
2842
+ }
2843
+ }
2844
+ } else {
2845
+ const label = f.label;
2846
+ if (label) lines.push(` /** ${label} */`);
2847
+ const optional = f.required ? "" : "?";
2848
+ const typeStr = fieldToTypeString({
2849
+ field: f,
2850
+ blockInterfaceNames: props.blockInterfaceNames
2851
+ });
2852
+ lines.push(` ${fieldName}${optional}: ${typeStr};`);
2853
+ }
2329
2854
  }
2330
- lines.push("}");
2331
- return lines.join("\n");
2855
+ return lines;
2332
2856
  }
2333
2857
 
2334
2858
  // src/versioning/constants.ts
@@ -2621,6 +3145,25 @@ function generateCollectionQueries(props) {
2621
3145
  slugs.push(collection.slug);
2622
3146
  }
2623
3147
  }
3148
+ if (config.globals) {
3149
+ for (const global of config.globals) {
3150
+ if (slugs.includes(global.slug)) continue;
3151
+ const collection = {
3152
+ slug: global.slug,
3153
+ tableName: global.tableName ?? global.slug,
3154
+ fields: global.fields,
3155
+ searchIndexes: void 0
3156
+ };
3157
+ const { apiFile, modelFile } = generateCollectionPair({
3158
+ collection,
3159
+ isMedia: false,
3160
+ imports
3161
+ });
3162
+ result[`api/${global.slug}.ts`] = apiFile;
3163
+ result[`model/api/${global.slug}.ts`] = modelFile;
3164
+ slugs.push(global.slug);
3165
+ }
3166
+ }
2624
3167
  if (config.auth?.collections) {
2625
3168
  for (const collection of config.auth.collections) {
2626
3169
  if (!collection.generateApi) continue;
@@ -3156,13 +3699,24 @@ function planMigration(props) {
3156
3699
  }
3157
3700
  export {
3158
3701
  ALL_SYSTEM_FIELDS,
3702
+ ASPECT_RATIO_PRESETS,
3703
+ BORDER_RADIUS_PRESETS,
3704
+ BORDER_WIDTH_PRESETS,
3705
+ BOX_SHADOW_PRESETS,
3159
3706
  DEFAULT_AUTOSAVE_INTERVAL,
3160
3707
  DEFAULT_BREAKPOINTS,
3161
3708
  DEFAULT_MAX_VERSIONS_PER_DOC,
3709
+ FONT_SIZE_PRESETS,
3710
+ FONT_WEIGHT_PRESETS,
3162
3711
  GENERATED_HEADER,
3712
+ LETTER_SPACING_PRESETS,
3713
+ LINE_HEIGHT_PRESETS,
3163
3714
  LOCKED_MEDIA_FIELDS,
3715
+ MAX_WIDTH_PRESETS,
3716
+ OPACITY_PRESETS,
3164
3717
  OVERRIDABLE_MEDIA_FIELDS,
3165
3718
  PREVIEW_SNAPSHOT_DEBOUNCE_MS,
3719
+ SPACING_PRESETS,
3166
3720
  VERSION_SYSTEM_FIELDS,
3167
3721
  VexAccessConfigError,
3168
3722
  VexAccessError,
@@ -3172,10 +3726,15 @@ export {
3172
3726
  VexFieldValidationError,
3173
3727
  VexMediaConfigError,
3174
3728
  VexSlugConflictError,
3729
+ WIDTH_PRESETS,
3175
3730
  addRemovedFieldsAsOptional,
3176
3731
  array,
3732
+ blockStylesToTailwind,
3177
3733
  blocks,
3734
+ buildSiteMetadata,
3735
+ checkAdminAccess,
3178
3736
  checkbox,
3737
+ color,
3179
3738
  createDocument,
3180
3739
  createVexQuery,
3181
3740
  date,
@@ -3183,6 +3742,7 @@ export {
3183
3742
  defineBlock,
3184
3743
  defineCollection,
3185
3744
  defineConfig,
3745
+ defineGlobal,
3186
3746
  defineMediaCollection,
3187
3747
  deleteDocument,
3188
3748
  deletePreviewSnapshot,
@@ -3210,6 +3770,7 @@ export {
3210
3770
  makeFieldsOptional,
3211
3771
  mergeAuthCollectionWithUserCollection,
3212
3772
  number,
3773
+ object,
3213
3774
  planMigration,
3214
3775
  relationship,
3215
3776
  resolvePreviewURL,
@@ -3219,6 +3780,7 @@ export {
3219
3780
  select,
3220
3781
  shouldReloadURL,
3221
3782
  slugToInterfaceName,
3783
+ tabs,
3222
3784
  text,
3223
3785
  toTitleCase,
3224
3786
  ui,