@dyrected/core 2.5.59 → 2.5.60

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.cjs CHANGED
@@ -50,6 +50,7 @@ __export(index_exports, {
50
50
  defineRichTextField: () => defineRichTextField,
51
51
  defineRowField: () => defineRowField,
52
52
  defineSelectField: () => defineSelectField,
53
+ defineTab: () => defineTab,
53
54
  defineTextField: () => defineTextField,
54
55
  defineTextareaField: () => defineTextareaField,
55
56
  defineTimeField: () => defineTimeField,
@@ -419,6 +420,7 @@ var AUDIT_COLLECTION = {
419
420
  { name: "timestamp", type: "date", label: "Timestamp", required: true },
420
421
  { name: "changes", type: "json", label: "Changes" }
421
422
  ],
423
+ access: { read: () => false, create: () => false, update: () => false, delete: () => false },
422
424
  admin: { hidden: true }
423
425
  };
424
426
  var WORKFLOW_HISTORY_COLLECTION_CONFIG = {
@@ -523,6 +525,12 @@ function normalizeConfig(config) {
523
525
  if (field.name === "email") {
524
526
  return {
525
527
  ...field,
528
+ // Email is the login identifier. Keep the integrity constraints
529
+ // auth relies on even when the field is explicitly redefined, so a
530
+ // custom `email` field can never silently drop uniqueness.
531
+ required: true,
532
+ unique: true,
533
+ promoted: true,
526
534
  access: {
527
535
  ...field.access || {},
528
536
  update: "!id"
@@ -532,6 +540,9 @@ function normalizeConfig(config) {
532
540
  if (field.name === "password") {
533
541
  return {
534
542
  ...field,
543
+ // Password is required to authenticate; enforce it regardless of
544
+ // how the field was declared.
545
+ required: true,
535
546
  admin: { ...field.admin || {} },
536
547
  access: {
537
548
  ...field.access || {},
@@ -947,6 +958,15 @@ async function executeFieldAfterRead(fields, doc, user, db) {
947
958
  }
948
959
 
949
960
  // src/utils/openapi.ts
961
+ function getCollectionLabels(collection) {
962
+ return collection.labels || { singular: collection.slug, plural: collection.slug };
963
+ }
964
+ function getCollectionTag(collection) {
965
+ return `Collection: ${getCollectionLabels(collection).plural}`;
966
+ }
967
+ function getGlobalTag(global) {
968
+ return `Global: ${global.label || global.slug}`;
969
+ }
950
970
  function generateOpenApi(config) {
951
971
  const spec = {
952
972
  openapi: "3.0.0",
@@ -978,15 +998,7 @@ function generateOpenApi(config) {
978
998
  },
979
999
  WorkflowHistoryEntry: {
980
1000
  type: "object",
981
- required: [
982
- "collection",
983
- "documentId",
984
- "transition",
985
- "from",
986
- "to",
987
- "revision",
988
- "createdAt"
989
- ],
1001
+ required: ["collection", "documentId", "transition", "from", "to", "revision", "createdAt"],
990
1002
  properties: {
991
1003
  id: { type: "string" },
992
1004
  collection: { type: "string" },
@@ -1000,6 +1012,21 @@ function generateOpenApi(config) {
1000
1012
  createdAt: { type: "string", format: "date-time" }
1001
1013
  }
1002
1014
  },
1015
+ AuditEntry: {
1016
+ type: "object",
1017
+ required: ["collection", "operation", "timestamp"],
1018
+ properties: {
1019
+ id: { type: "string" },
1020
+ collection: { type: "string" },
1021
+ documentId: { type: "string", nullable: true },
1022
+ operation: { type: "string" },
1023
+ user: { type: "string", nullable: true },
1024
+ timestamp: { type: "string", format: "date-time" },
1025
+ changes: {
1026
+ oneOf: [{ type: "string" }, { type: "object", additionalProperties: true }, { type: "null" }]
1027
+ }
1028
+ }
1029
+ },
1003
1030
  Error: {
1004
1031
  type: "object",
1005
1032
  properties: {
@@ -1088,17 +1115,13 @@ function generateOpenApi(config) {
1088
1115
  get: {
1089
1116
  tags: ["Preferences"],
1090
1117
  summary: "Get an authenticated user preference",
1091
- parameters: [
1092
- { name: "key", in: "path", required: true, schema: { type: "string" } }
1093
- ],
1118
+ parameters: [{ name: "key", in: "path", required: true, schema: { type: "string" } }],
1094
1119
  responses: { 200: { description: "Preference value" } }
1095
1120
  },
1096
1121
  put: {
1097
1122
  tags: ["Preferences"],
1098
1123
  summary: "Set an authenticated user preference",
1099
- parameters: [
1100
- { name: "key", in: "path", required: true, schema: { type: "string" } }
1101
- ],
1124
+ parameters: [{ name: "key", in: "path", required: true, schema: { type: "string" } }],
1102
1125
  requestBody: {
1103
1126
  required: true,
1104
1127
  content: { "application/json": { schema: { type: "object" } } }
@@ -1129,6 +1152,39 @@ function generateOpenApi(config) {
1129
1152
  responses: { 200: { description: "Preview document data" } }
1130
1153
  }
1131
1154
  };
1155
+ spec.paths["/api/audit"] = {
1156
+ get: {
1157
+ tags: ["Audit"],
1158
+ summary: "Get audit entries across all readable audited collections",
1159
+ parameters: [
1160
+ { name: "limit", in: "query", schema: { type: "integer", default: 50, maximum: 100 } },
1161
+ { name: "page", in: "query", schema: { type: "integer", default: 1 } },
1162
+ { name: "where", in: "query", schema: { type: "string" }, description: "JSON filter" },
1163
+ { name: "sort", in: "query", schema: { type: "string", default: "-timestamp" } }
1164
+ ],
1165
+ responses: {
1166
+ 200: {
1167
+ description: "Audit entries",
1168
+ content: {
1169
+ "application/json": {
1170
+ schema: {
1171
+ type: "object",
1172
+ properties: {
1173
+ docs: {
1174
+ type: "array",
1175
+ items: { $ref: "#/components/schemas/AuditEntry" }
1176
+ },
1177
+ total: { type: "integer" },
1178
+ limit: { type: "integer" },
1179
+ page: { type: "integer" }
1180
+ }
1181
+ }
1182
+ }
1183
+ }
1184
+ }
1185
+ }
1186
+ }
1187
+ };
1132
1188
  for (const collection of config.collections) {
1133
1189
  spec.components.schemas[collection.slug] = collectionToSchema(collection);
1134
1190
  }
@@ -1138,10 +1194,11 @@ function generateOpenApi(config) {
1138
1194
  for (const collection of config.collections) {
1139
1195
  const slug = collection.slug;
1140
1196
  const path = `/api/collections/${slug}`;
1141
- const labels = collection.labels || { singular: slug, plural: `${slug}s` };
1197
+ const labels = getCollectionLabels(collection);
1198
+ const collectionTag = getCollectionTag(collection);
1142
1199
  spec.paths[path] = {
1143
1200
  get: {
1144
- tags: ["Collections"],
1201
+ tags: [collectionTag],
1145
1202
  summary: `Find ${labels.plural}`,
1146
1203
  parameters: [
1147
1204
  {
@@ -1190,7 +1247,7 @@ function generateOpenApi(config) {
1190
1247
  }
1191
1248
  },
1192
1249
  post: {
1193
- tags: ["Collections"],
1250
+ tags: [collectionTag],
1194
1251
  summary: `Create ${labels.singular}`,
1195
1252
  requestBody: {
1196
1253
  required: true,
@@ -1214,7 +1271,7 @@ function generateOpenApi(config) {
1214
1271
  };
1215
1272
  spec.paths[`${path}/{id}`] = {
1216
1273
  get: {
1217
- tags: ["Collections"],
1274
+ tags: [collectionTag],
1218
1275
  summary: `Get a single ${labels.singular}`,
1219
1276
  parameters: [
1220
1277
  {
@@ -1236,7 +1293,7 @@ function generateOpenApi(config) {
1236
1293
  }
1237
1294
  },
1238
1295
  patch: {
1239
- tags: ["Collections"],
1296
+ tags: [collectionTag],
1240
1297
  summary: `Update ${labels.singular}`,
1241
1298
  parameters: [
1242
1299
  {
@@ -1266,7 +1323,7 @@ function generateOpenApi(config) {
1266
1323
  }
1267
1324
  },
1268
1325
  delete: {
1269
- tags: ["Collections"],
1326
+ tags: [collectionTag],
1270
1327
  summary: `Delete ${labels.singular}`,
1271
1328
  parameters: [
1272
1329
  {
@@ -1283,7 +1340,7 @@ function generateOpenApi(config) {
1283
1340
  };
1284
1341
  spec.paths[`${path}/delete-many`] = {
1285
1342
  delete: {
1286
- tags: ["Collections"],
1343
+ tags: [collectionTag],
1287
1344
  summary: `Delete multiple ${labels.plural}`,
1288
1345
  requestBody: {
1289
1346
  required: true,
@@ -1302,9 +1359,46 @@ function generateOpenApi(config) {
1302
1359
  responses: { 200: { description: "Deleted and failed document IDs" } }
1303
1360
  }
1304
1361
  };
1362
+ if (collection.audit) {
1363
+ spec.paths[`${path}/__audit`] = {
1364
+ get: {
1365
+ tags: [collectionTag],
1366
+ summary: `Get ${labels.singular} audit entries`,
1367
+ parameters: [
1368
+ { name: "limit", in: "query", schema: { type: "integer", default: 50, maximum: 100 } },
1369
+ { name: "page", in: "query", schema: { type: "integer", default: 1 } },
1370
+ { name: "where", in: "query", schema: { type: "string" }, description: "JSON filter" },
1371
+ { name: "sort", in: "query", schema: { type: "string", default: "-timestamp" } }
1372
+ ],
1373
+ responses: {
1374
+ 200: {
1375
+ description: "Audit entries for this collection",
1376
+ content: {
1377
+ "application/json": {
1378
+ schema: {
1379
+ type: "object",
1380
+ properties: {
1381
+ docs: {
1382
+ type: "array",
1383
+ items: { $ref: "#/components/schemas/AuditEntry" }
1384
+ },
1385
+ total: { type: "integer" },
1386
+ limit: { type: "integer" },
1387
+ page: { type: "integer" }
1388
+ }
1389
+ }
1390
+ }
1391
+ }
1392
+ },
1393
+ 403: { description: "Collection read access denied" },
1394
+ 404: { description: "Audit is not enabled for this collection" }
1395
+ }
1396
+ }
1397
+ };
1398
+ }
1305
1399
  spec.paths[`${path}/seed`] = {
1306
1400
  post: {
1307
- tags: ["Collections"],
1401
+ tags: [collectionTag],
1308
1402
  summary: `Seed initial ${labels.plural}`,
1309
1403
  responses: { 200: { description: "Seeded documents" } }
1310
1404
  }
@@ -1312,12 +1406,12 @@ function generateOpenApi(config) {
1312
1406
  if (collection.upload) {
1313
1407
  spec.paths[`${path}/media`] = {
1314
1408
  get: {
1315
- tags: ["Media"],
1409
+ tags: [collectionTag],
1316
1410
  summary: `List ${labels.plural}`,
1317
1411
  responses: { 200: { description: "Paginated media documents" } }
1318
1412
  },
1319
1413
  post: {
1320
- tags: ["Media"],
1414
+ tags: [collectionTag],
1321
1415
  summary: `Upload ${labels.singular}`,
1322
1416
  requestBody: {
1323
1417
  required: true,
@@ -1336,7 +1430,7 @@ function generateOpenApi(config) {
1336
1430
  };
1337
1431
  spec.paths[`${path}/media/{filename}`] = {
1338
1432
  get: {
1339
- tags: ["Media"],
1433
+ tags: [collectionTag],
1340
1434
  summary: `Serve ${labels.singular} bytes`,
1341
1435
  parameters: [
1342
1436
  {
@@ -1356,7 +1450,7 @@ function generateOpenApi(config) {
1356
1450
  }
1357
1451
  if (collection.auth) {
1358
1452
  const publicAuthPost = (summary) => ({
1359
- tags: ["Authentication"],
1453
+ tags: [collectionTag],
1360
1454
  summary,
1361
1455
  security: [],
1362
1456
  requestBody: {
@@ -1398,14 +1492,14 @@ function generateOpenApi(config) {
1398
1492
  };
1399
1493
  spec.paths[`${path}/logout`] = {
1400
1494
  post: {
1401
- tags: ["Authentication"],
1495
+ tags: [collectionTag],
1402
1496
  summary: `Log out of ${labels.plural}`,
1403
1497
  responses: { 200: { description: "Logged out" } }
1404
1498
  }
1405
1499
  };
1406
1500
  spec.paths[`${path}/init`] = {
1407
1501
  get: {
1408
- tags: ["Authentication"],
1502
+ tags: [collectionTag],
1409
1503
  summary: `Get ${labels.plural} initialization state`,
1410
1504
  security: [],
1411
1505
  responses: { 200: { description: "Initialization state" } }
@@ -1416,14 +1510,14 @@ function generateOpenApi(config) {
1416
1510
  };
1417
1511
  spec.paths[`${path}/me`] = {
1418
1512
  get: {
1419
- tags: ["Authentication"],
1513
+ tags: [collectionTag],
1420
1514
  summary: `Get the current ${labels.singular}`,
1421
1515
  responses: { 200: { description: "Authenticated user" } }
1422
1516
  }
1423
1517
  };
1424
1518
  spec.paths[`${path}/refresh-token`] = {
1425
1519
  post: {
1426
- tags: ["Authentication"],
1520
+ tags: [collectionTag],
1427
1521
  summary: "Refresh an authentication token",
1428
1522
  responses: { 200: { description: "Refreshed token" } }
1429
1523
  }
@@ -1436,7 +1530,7 @@ function generateOpenApi(config) {
1436
1530
  };
1437
1531
  spec.paths[`${path}/invite`] = {
1438
1532
  post: {
1439
- tags: ["Authentication"],
1533
+ tags: [collectionTag],
1440
1534
  summary: `Invite a ${labels.singular}`,
1441
1535
  responses: { 200: { description: "Invitation sent" } }
1442
1536
  }
@@ -1446,7 +1540,7 @@ function generateOpenApi(config) {
1446
1540
  };
1447
1541
  spec.paths[`${path}/{id}/change-password`] = {
1448
1542
  post: {
1449
- tags: ["Authentication"],
1543
+ tags: [collectionTag],
1450
1544
  summary: `Change a ${labels.singular} password`,
1451
1545
  parameters: [
1452
1546
  {
@@ -1463,7 +1557,7 @@ function generateOpenApi(config) {
1463
1557
  if (collection.workflow) {
1464
1558
  spec.paths[`${path}/{id}/transitions/{transition}`] = {
1465
1559
  post: {
1466
- tags: ["Workflows"],
1560
+ tags: [collectionTag],
1467
1561
  summary: `Transition ${labels.singular} workflow`,
1468
1562
  parameters: [
1469
1563
  {
@@ -1508,7 +1602,7 @@ function generateOpenApi(config) {
1508
1602
  };
1509
1603
  spec.paths[`${path}/{id}/workflow-history`] = {
1510
1604
  get: {
1511
- tags: ["Workflows"],
1605
+ tags: [collectionTag],
1512
1606
  summary: `Get ${labels.singular} workflow history`,
1513
1607
  parameters: [
1514
1608
  {
@@ -1554,9 +1648,10 @@ function generateOpenApi(config) {
1554
1648
  for (const global of config.globals) {
1555
1649
  const slug = global.slug;
1556
1650
  const path = `/api/globals/${slug}`;
1651
+ const globalTag = getGlobalTag(global);
1557
1652
  spec.paths[path] = {
1558
1653
  get: {
1559
- tags: ["Globals"],
1654
+ tags: [globalTag],
1560
1655
  summary: `Get ${global.label || slug}`,
1561
1656
  responses: {
1562
1657
  200: {
@@ -1570,7 +1665,7 @@ function generateOpenApi(config) {
1570
1665
  }
1571
1666
  },
1572
1667
  patch: {
1573
- tags: ["Globals"],
1668
+ tags: [globalTag],
1574
1669
  summary: `Update ${global.label || slug}`,
1575
1670
  requestBody: {
1576
1671
  required: true,
@@ -1594,7 +1689,7 @@ function generateOpenApi(config) {
1594
1689
  };
1595
1690
  spec.paths[`${path}/seed`] = {
1596
1691
  post: {
1597
- tags: ["Globals"],
1692
+ tags: [globalTag],
1598
1693
  summary: `Seed ${global.label || slug}`,
1599
1694
  responses: { 200: { description: "Seeded global" } }
1600
1695
  }
@@ -1673,10 +1768,7 @@ function fieldToSchema(field) {
1673
1768
  break;
1674
1769
  case "url":
1675
1770
  schema = {
1676
- oneOf: [
1677
- { type: "string" },
1678
- { type: "object", additionalProperties: true }
1679
- ]
1771
+ oneOf: [{ type: "string" }, { type: "object", additionalProperties: true }]
1680
1772
  };
1681
1773
  break;
1682
1774
  case "icon":
@@ -1812,6 +1904,12 @@ var defineUrlField = createFieldDefiner("url");
1812
1904
  var defineIconField = createFieldDefiner("icon");
1813
1905
  var defineJoinField = createFieldDefiner("join");
1814
1906
  var defineRowField = createFieldDefiner("row");
1907
+ function defineTab(args) {
1908
+ return args.fields.map((field) => ({
1909
+ ...field,
1910
+ admin: { ...field.admin ?? {}, tab: args.label }
1911
+ }));
1912
+ }
1815
1913
  // Annotate the CommonJS export names for ESM import in node:
1816
1914
  0 && (module.exports = {
1817
1915
  LIFECYCLE_EVENTS_COLLECTION,
@@ -1844,6 +1942,7 @@ var defineRowField = createFieldDefiner("row");
1844
1942
  defineRichTextField,
1845
1943
  defineRowField,
1846
1944
  defineSelectField,
1945
+ defineTab,
1847
1946
  defineTextField,
1848
1947
  defineTextareaField,
1849
1948
  defineTimeField,
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { F as Field, B as Block, D as DyrectedConfig, C as CollectionConfig, A as AdminAuthConfig, P as PublicAdminAuthConfig, W as WorkflowConfig, a as AuthenticatedUser, b as WorkflowTransition, L as LifecycleEventName, c as LifecycleEvent, d as BaseDocument, H as HookRequestContext, e as ArrayField, f as BlocksField, g as BooleanField, h as DateField, i as DateTimeField, E as EmailField, G as GlobalConfig, I as IconField, j as ImageField, J as JoinField, k as JsonField, M as MultiSelectField, N as NumberField, O as ObjectField, R as RadioField, l as RelationshipField, m as RichTextField, n as RowField, S as SelectField, T as TextField, o as TextareaField, p as TimeField, U as UrlField } from './app-config-C3X5BAyK.cjs';
2
- export { q as AccessFunction, r as AccessFunctionArgs, s as AccessPolicyResolver, t as AccessResult, u as AccessRule, v as AdminAuthAccessResolution, w as AdminAuthClaimMapping, x as AdminAuthMember, y as AdminAuthProvider, z as AdminAuthProviderMembersConfig, K as AdminAuthProvisioningMode, Q as AdminAuthResolveAccessArgs, V as AdminAuthResolvedIdentity, X as AdminConfig, Y as AdminDashboardComponentSlots, Z as AdminIconName, _ as BaseAdminAuthProvider, $ as BaseFieldAdmin, a0 as BlockVariant, a1 as BooleanFieldAdmin, a2 as CharacterLimitFieldAdmin, a3 as CharacterLimitFieldConfig, a4 as CollectionAfterChangeHook, a5 as CollectionAfterDeleteHook, a6 as CollectionAfterReadHook, a7 as CollectionAfterTransitionHook, a8 as CollectionBeforeChangeHook, a9 as CollectionBeforeDeleteHook, aa as CollectionBeforeReadHook, ab as CollectionBeforeTransitionHook, ac as CollectionListComponentSlots, ad as CustomAdminAuthProvider, ae as DatabaseAdapter, af as DynamicOptionItem, ag as DynamicOptionsConfig, ah as DynamicOptionsResolver, ai as DynamicOptionsResolverArgs, aj as EmailFieldAdmin, ak as FieldAdminHooks, al as FieldAdminOnChangeHook, am as FieldAdminOnChangeHookArgs, an as FieldAdminOptionsHook, ao as FieldAdminOptionsHookArgs, ap as FieldAdminOptionsHookResult, aq as FieldAfterReadHook, ar as FieldAfterReadHookArgs, as as FieldBase, at as FieldBeforeChangeHook, au as FieldBeforeChangeHookArgs, av as FieldHook, aw as FieldHooks, ax as FieldType, ay as FileData, az as GlobalAfterChangeHook, aA as GlobalAfterReadHook, aB as GlobalBeforeChangeHook, aC as GlobalBeforeReadHook, aD as HeadingLevel, aE as HookFunction, aF as IconFieldAdmin, aG as ImageService, aH as LIFECYCLE_EVENT_NAMES, aI as LifecycleEventHandler, aJ as MultiSelectFieldAdmin, aK as NamedAccessPolicy, aL as OIDCAdminAuthProvider, aM as PaginatedResult, aN as PublicAdminAuthProvider, aO as RadioFieldAdmin, aP as ReadonlyDatabaseAdapter, aQ as RichTextFeature, aR as RichTextFieldConfig, aS as SelectFieldAdmin, aT as StorageAdapter, aU as TextFieldAdmin, aV as TextareaFieldAdmin, aW as TypedField, aX as UploadConfig, aY as UrlFieldAdmin, aZ as WordLimitFieldAdmin, a_ as WordLimitFieldConfig, a$ as WorkflowMetadata, b0 as WorkflowRole, b1 as WorkflowState, b2 as WorkflowTransitionContext } from './app-config-C3X5BAyK.cjs';
1
+ import { F as Field, B as Block, D as DyrectedConfig, C as CollectionConfig, A as AdminAuthConfig, P as PublicAdminAuthConfig, W as WorkflowConfig, a as AuthenticatedUser, b as WorkflowTransition, L as LifecycleEventName, c as LifecycleEvent, d as BaseDocument, H as HookRequestContext, e as ArrayField, f as BlocksField, g as BooleanField, h as DateField, i as DateTimeField, E as EmailField, G as GlobalConfig, I as IconField, j as ImageField, J as JoinField, k as JsonField, M as MultiSelectField, N as NumberField, O as ObjectField, R as RadioField, l as RelationshipField, m as RichTextField, n as RowField, S as SelectField, T as TextField, o as TextareaField, p as TimeField, U as UrlField } from './app-config-C0twGIaF.cjs';
2
+ export { q as AccessFunction, r as AccessFunctionArgs, s as AccessPolicyResolver, t as AccessResult, u as AccessRule, v as AdminAuthAccessResolution, w as AdminAuthClaimMapping, x as AdminAuthMember, y as AdminAuthProvider, z as AdminAuthProviderMembersConfig, K as AdminAuthProvisioningMode, Q as AdminAuthResolveAccessArgs, V as AdminAuthResolvedIdentity, X as AdminConfig, Y as AdminDashboardComponentSlots, Z as AdminIconName, _ as BaseAdminAuthProvider, $ as BaseFieldAdmin, a0 as BlockVariant, a1 as BooleanFieldAdmin, a2 as CharacterLimitFieldAdmin, a3 as CharacterLimitFieldConfig, a4 as CollectionAfterChangeHook, a5 as CollectionAfterDeleteHook, a6 as CollectionAfterReadHook, a7 as CollectionAfterTransitionHook, a8 as CollectionBeforeChangeHook, a9 as CollectionBeforeDeleteHook, aa as CollectionBeforeReadHook, ab as CollectionBeforeTransitionHook, ac as CollectionListComponentSlots, ad as CustomAdminAuthProvider, ae as DatabaseAdapter, af as DynamicOptionItem, ag as DynamicOptionsConfig, ah as DynamicOptionsResolver, ai as DynamicOptionsResolverArgs, aj as EmailFieldAdmin, ak as FieldAdminHooks, al as FieldAdminOnChangeHook, am as FieldAdminOnChangeHookArgs, an as FieldAdminOptionsHook, ao as FieldAdminOptionsHookArgs, ap as FieldAdminOptionsHookResult, aq as FieldAfterReadHook, ar as FieldAfterReadHookArgs, as as FieldBase, at as FieldBeforeChangeHook, au as FieldBeforeChangeHookArgs, av as FieldHook, aw as FieldHooks, ax as FieldType, ay as FileData, az as GlobalAfterChangeHook, aA as GlobalAfterReadHook, aB as GlobalBeforeChangeHook, aC as GlobalBeforeReadHook, aD as HeadingLevel, aE as HookFunction, aF as IconFieldAdmin, aG as ImageService, aH as LIFECYCLE_EVENT_NAMES, aI as LifecycleEventHandler, aJ as MultiSelectFieldAdmin, aK as NamedAccessPolicy, aL as NumberFieldAdmin, aM as NumberLimitFieldAdmin, aN as NumberLimitFieldConfig, aO as OIDCAdminAuthProvider, aP as PaginatedResult, aQ as PublicAdminAuthProvider, aR as RadioFieldAdmin, aS as ReadonlyDatabaseAdapter, aT as RichTextFeature, aU as RichTextFieldConfig, aV as SelectFieldAdmin, aW as StorageAdapter, aX as TextFieldAdmin, aY as TextareaFieldAdmin, aZ as TypedField, a_ as UploadConfig, a$ as UrlFieldAdmin, b0 as UrlLinkValue, b1 as WordLimitFieldAdmin, b2 as WordLimitFieldConfig, b3 as WorkflowMetadata, b4 as WorkflowRole, b5 as WorkflowState, b6 as WorkflowTransitionContext } from './app-config-C0twGIaF.cjs';
3
3
  import 'lucide-react';
4
4
 
5
5
  type Prettify<T> = {
@@ -463,5 +463,38 @@ declare const defineJoinField: <const T extends Omit<JoinField, "type">>(field:
463
463
  declare const defineRowField: <const T extends Omit<RowField, "type">>(field: T) => T & {
464
464
  type: "row";
465
465
  };
466
+ /**
467
+ * Group fields under a named tab in the Admin edit form. Tabs are presentational
468
+ * only: `defineTab` returns the given fields unchanged except that each one's
469
+ * `admin.tab` is set to `label`, and the Admin panel renders fields that share a
470
+ * tab name together under that tab. Spread the result into a collection, global,
471
+ * or group `fields` array, and add one `defineTab` call per tab. Fields left
472
+ * without a tab are collected into a leading tab named after the collection.
473
+ *
474
+ * ```ts
475
+ * defineCollection({
476
+ * slug: 'pages',
477
+ * fields: [
478
+ * ...defineTab({
479
+ * label: 'Content',
480
+ * fields: [
481
+ * defineTextField({ name: 'title', required: true }),
482
+ * defineRichTextField({ name: 'body' }),
483
+ * ],
484
+ * }),
485
+ * ...defineTab({
486
+ * label: 'SEO',
487
+ * fields: [defineTextField({ name: 'metaTitle' })],
488
+ * }),
489
+ * ],
490
+ * })
491
+ * ```
492
+ */
493
+ declare function defineTab<const T extends readonly Field[]>(args: {
494
+ /** Tab name shown in the Admin edit form's tab bar. */
495
+ label: string;
496
+ /** Fields to place under this tab; each field's `admin.tab` is set to `label`. */
497
+ fields: T;
498
+ }): T;
466
499
 
467
- export { AdminAuthConfig, ArrayField, type AuthDocFields, AuthenticatedUser, BaseDocument, Block, BlocksField, BooleanField, CollectionConfig, DateField, DateTimeField, DyrectedConfig, EmailField, Field, GlobalConfig, HookRequestContext, IconField, ImageField, type InferDocShape, JoinField, JsonField, LIFECYCLE_EVENTS_COLLECTION, LifecycleEvent, LifecycleEventName, MultiSelectField, NumberField, ObjectField, type Prettify, PublicAdminAuthConfig, RadioField, RelationshipField, RichTextField, RowField, SelectField, type SortClause, type SortDirection, type SqlWhereResult, type SystemDocFields, TextField, TextareaField, TimeField, type UploadDocFields, UrlField, WORKFLOW_HISTORY_COLLECTION, type WhereClause, type WhereOperator, type WhereOperatorName, WorkflowConfig, WorkflowTransition, availableWorkflowTransitions, canViewWorkflowDraft, createLifecycleEvent, createWorkflowDocument, defineArrayField, defineBlock, defineBlocksField, defineBooleanField, defineCollection, defineConfig, defineDateField, defineDateTimeField, defineEmailField, defineField, defineGlobal, defineIconField, defineImageField, defineJoinField, defineJsonField, defineMultiSelectField, defineNumberField, defineObjectField, defineRadioField, defineRelationshipField, defineRichTextField, defineRowField, defineSelectField, defineTextField, defineTextareaField, defineTimeField, defineUrlField, dispatchLifecycleEvent, dispatchPendingLifecycleEvents, executeFieldAfterRead, executeFieldBeforeChange, generateOpenApi, getAdminAuthCollection, getPublicAdminAuthConfig, initializeWorkflowDocument, materializeWorkflowDocument, normalizeConfig, parseMongoWhere, parseSort, parseSqlWhere, publishingWorkflow, resolveAdminAuthCollection, runCollectionHooks, saveWorkflowDraft, transitionWorkflow, workflowCapabilities };
500
+ export { AdminAuthConfig, ArrayField, type AuthDocFields, AuthenticatedUser, BaseDocument, Block, BlocksField, BooleanField, CollectionConfig, DateField, DateTimeField, DyrectedConfig, EmailField, Field, GlobalConfig, HookRequestContext, IconField, ImageField, type InferDocShape, JoinField, JsonField, LIFECYCLE_EVENTS_COLLECTION, LifecycleEvent, LifecycleEventName, MultiSelectField, NumberField, ObjectField, type Prettify, PublicAdminAuthConfig, RadioField, RelationshipField, RichTextField, RowField, SelectField, type SortClause, type SortDirection, type SqlWhereResult, type SystemDocFields, TextField, TextareaField, TimeField, type UploadDocFields, UrlField, WORKFLOW_HISTORY_COLLECTION, type WhereClause, type WhereOperator, type WhereOperatorName, WorkflowConfig, WorkflowTransition, availableWorkflowTransitions, canViewWorkflowDraft, createLifecycleEvent, createWorkflowDocument, defineArrayField, defineBlock, defineBlocksField, defineBooleanField, defineCollection, defineConfig, defineDateField, defineDateTimeField, defineEmailField, defineField, defineGlobal, defineIconField, defineImageField, defineJoinField, defineJsonField, defineMultiSelectField, defineNumberField, defineObjectField, defineRadioField, defineRelationshipField, defineRichTextField, defineRowField, defineSelectField, defineTab, defineTextField, defineTextareaField, defineTimeField, defineUrlField, dispatchLifecycleEvent, dispatchPendingLifecycleEvents, executeFieldAfterRead, executeFieldBeforeChange, generateOpenApi, getAdminAuthCollection, getPublicAdminAuthConfig, initializeWorkflowDocument, materializeWorkflowDocument, normalizeConfig, parseMongoWhere, parseSort, parseSqlWhere, publishingWorkflow, resolveAdminAuthCollection, runCollectionHooks, saveWorkflowDraft, transitionWorkflow, workflowCapabilities };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { F as Field, B as Block, D as DyrectedConfig, C as CollectionConfig, A as AdminAuthConfig, P as PublicAdminAuthConfig, W as WorkflowConfig, a as AuthenticatedUser, b as WorkflowTransition, L as LifecycleEventName, c as LifecycleEvent, d as BaseDocument, H as HookRequestContext, e as ArrayField, f as BlocksField, g as BooleanField, h as DateField, i as DateTimeField, E as EmailField, G as GlobalConfig, I as IconField, j as ImageField, J as JoinField, k as JsonField, M as MultiSelectField, N as NumberField, O as ObjectField, R as RadioField, l as RelationshipField, m as RichTextField, n as RowField, S as SelectField, T as TextField, o as TextareaField, p as TimeField, U as UrlField } from './app-config-C3X5BAyK.js';
2
- export { q as AccessFunction, r as AccessFunctionArgs, s as AccessPolicyResolver, t as AccessResult, u as AccessRule, v as AdminAuthAccessResolution, w as AdminAuthClaimMapping, x as AdminAuthMember, y as AdminAuthProvider, z as AdminAuthProviderMembersConfig, K as AdminAuthProvisioningMode, Q as AdminAuthResolveAccessArgs, V as AdminAuthResolvedIdentity, X as AdminConfig, Y as AdminDashboardComponentSlots, Z as AdminIconName, _ as BaseAdminAuthProvider, $ as BaseFieldAdmin, a0 as BlockVariant, a1 as BooleanFieldAdmin, a2 as CharacterLimitFieldAdmin, a3 as CharacterLimitFieldConfig, a4 as CollectionAfterChangeHook, a5 as CollectionAfterDeleteHook, a6 as CollectionAfterReadHook, a7 as CollectionAfterTransitionHook, a8 as CollectionBeforeChangeHook, a9 as CollectionBeforeDeleteHook, aa as CollectionBeforeReadHook, ab as CollectionBeforeTransitionHook, ac as CollectionListComponentSlots, ad as CustomAdminAuthProvider, ae as DatabaseAdapter, af as DynamicOptionItem, ag as DynamicOptionsConfig, ah as DynamicOptionsResolver, ai as DynamicOptionsResolverArgs, aj as EmailFieldAdmin, ak as FieldAdminHooks, al as FieldAdminOnChangeHook, am as FieldAdminOnChangeHookArgs, an as FieldAdminOptionsHook, ao as FieldAdminOptionsHookArgs, ap as FieldAdminOptionsHookResult, aq as FieldAfterReadHook, ar as FieldAfterReadHookArgs, as as FieldBase, at as FieldBeforeChangeHook, au as FieldBeforeChangeHookArgs, av as FieldHook, aw as FieldHooks, ax as FieldType, ay as FileData, az as GlobalAfterChangeHook, aA as GlobalAfterReadHook, aB as GlobalBeforeChangeHook, aC as GlobalBeforeReadHook, aD as HeadingLevel, aE as HookFunction, aF as IconFieldAdmin, aG as ImageService, aH as LIFECYCLE_EVENT_NAMES, aI as LifecycleEventHandler, aJ as MultiSelectFieldAdmin, aK as NamedAccessPolicy, aL as OIDCAdminAuthProvider, aM as PaginatedResult, aN as PublicAdminAuthProvider, aO as RadioFieldAdmin, aP as ReadonlyDatabaseAdapter, aQ as RichTextFeature, aR as RichTextFieldConfig, aS as SelectFieldAdmin, aT as StorageAdapter, aU as TextFieldAdmin, aV as TextareaFieldAdmin, aW as TypedField, aX as UploadConfig, aY as UrlFieldAdmin, aZ as WordLimitFieldAdmin, a_ as WordLimitFieldConfig, a$ as WorkflowMetadata, b0 as WorkflowRole, b1 as WorkflowState, b2 as WorkflowTransitionContext } from './app-config-C3X5BAyK.js';
1
+ import { F as Field, B as Block, D as DyrectedConfig, C as CollectionConfig, A as AdminAuthConfig, P as PublicAdminAuthConfig, W as WorkflowConfig, a as AuthenticatedUser, b as WorkflowTransition, L as LifecycleEventName, c as LifecycleEvent, d as BaseDocument, H as HookRequestContext, e as ArrayField, f as BlocksField, g as BooleanField, h as DateField, i as DateTimeField, E as EmailField, G as GlobalConfig, I as IconField, j as ImageField, J as JoinField, k as JsonField, M as MultiSelectField, N as NumberField, O as ObjectField, R as RadioField, l as RelationshipField, m as RichTextField, n as RowField, S as SelectField, T as TextField, o as TextareaField, p as TimeField, U as UrlField } from './app-config-C0twGIaF.js';
2
+ export { q as AccessFunction, r as AccessFunctionArgs, s as AccessPolicyResolver, t as AccessResult, u as AccessRule, v as AdminAuthAccessResolution, w as AdminAuthClaimMapping, x as AdminAuthMember, y as AdminAuthProvider, z as AdminAuthProviderMembersConfig, K as AdminAuthProvisioningMode, Q as AdminAuthResolveAccessArgs, V as AdminAuthResolvedIdentity, X as AdminConfig, Y as AdminDashboardComponentSlots, Z as AdminIconName, _ as BaseAdminAuthProvider, $ as BaseFieldAdmin, a0 as BlockVariant, a1 as BooleanFieldAdmin, a2 as CharacterLimitFieldAdmin, a3 as CharacterLimitFieldConfig, a4 as CollectionAfterChangeHook, a5 as CollectionAfterDeleteHook, a6 as CollectionAfterReadHook, a7 as CollectionAfterTransitionHook, a8 as CollectionBeforeChangeHook, a9 as CollectionBeforeDeleteHook, aa as CollectionBeforeReadHook, ab as CollectionBeforeTransitionHook, ac as CollectionListComponentSlots, ad as CustomAdminAuthProvider, ae as DatabaseAdapter, af as DynamicOptionItem, ag as DynamicOptionsConfig, ah as DynamicOptionsResolver, ai as DynamicOptionsResolverArgs, aj as EmailFieldAdmin, ak as FieldAdminHooks, al as FieldAdminOnChangeHook, am as FieldAdminOnChangeHookArgs, an as FieldAdminOptionsHook, ao as FieldAdminOptionsHookArgs, ap as FieldAdminOptionsHookResult, aq as FieldAfterReadHook, ar as FieldAfterReadHookArgs, as as FieldBase, at as FieldBeforeChangeHook, au as FieldBeforeChangeHookArgs, av as FieldHook, aw as FieldHooks, ax as FieldType, ay as FileData, az as GlobalAfterChangeHook, aA as GlobalAfterReadHook, aB as GlobalBeforeChangeHook, aC as GlobalBeforeReadHook, aD as HeadingLevel, aE as HookFunction, aF as IconFieldAdmin, aG as ImageService, aH as LIFECYCLE_EVENT_NAMES, aI as LifecycleEventHandler, aJ as MultiSelectFieldAdmin, aK as NamedAccessPolicy, aL as NumberFieldAdmin, aM as NumberLimitFieldAdmin, aN as NumberLimitFieldConfig, aO as OIDCAdminAuthProvider, aP as PaginatedResult, aQ as PublicAdminAuthProvider, aR as RadioFieldAdmin, aS as ReadonlyDatabaseAdapter, aT as RichTextFeature, aU as RichTextFieldConfig, aV as SelectFieldAdmin, aW as StorageAdapter, aX as TextFieldAdmin, aY as TextareaFieldAdmin, aZ as TypedField, a_ as UploadConfig, a$ as UrlFieldAdmin, b0 as UrlLinkValue, b1 as WordLimitFieldAdmin, b2 as WordLimitFieldConfig, b3 as WorkflowMetadata, b4 as WorkflowRole, b5 as WorkflowState, b6 as WorkflowTransitionContext } from './app-config-C0twGIaF.js';
3
3
  import 'lucide-react';
4
4
 
5
5
  type Prettify<T> = {
@@ -463,5 +463,38 @@ declare const defineJoinField: <const T extends Omit<JoinField, "type">>(field:
463
463
  declare const defineRowField: <const T extends Omit<RowField, "type">>(field: T) => T & {
464
464
  type: "row";
465
465
  };
466
+ /**
467
+ * Group fields under a named tab in the Admin edit form. Tabs are presentational
468
+ * only: `defineTab` returns the given fields unchanged except that each one's
469
+ * `admin.tab` is set to `label`, and the Admin panel renders fields that share a
470
+ * tab name together under that tab. Spread the result into a collection, global,
471
+ * or group `fields` array, and add one `defineTab` call per tab. Fields left
472
+ * without a tab are collected into a leading tab named after the collection.
473
+ *
474
+ * ```ts
475
+ * defineCollection({
476
+ * slug: 'pages',
477
+ * fields: [
478
+ * ...defineTab({
479
+ * label: 'Content',
480
+ * fields: [
481
+ * defineTextField({ name: 'title', required: true }),
482
+ * defineRichTextField({ name: 'body' }),
483
+ * ],
484
+ * }),
485
+ * ...defineTab({
486
+ * label: 'SEO',
487
+ * fields: [defineTextField({ name: 'metaTitle' })],
488
+ * }),
489
+ * ],
490
+ * })
491
+ * ```
492
+ */
493
+ declare function defineTab<const T extends readonly Field[]>(args: {
494
+ /** Tab name shown in the Admin edit form's tab bar. */
495
+ label: string;
496
+ /** Fields to place under this tab; each field's `admin.tab` is set to `label`. */
497
+ fields: T;
498
+ }): T;
466
499
 
467
- export { AdminAuthConfig, ArrayField, type AuthDocFields, AuthenticatedUser, BaseDocument, Block, BlocksField, BooleanField, CollectionConfig, DateField, DateTimeField, DyrectedConfig, EmailField, Field, GlobalConfig, HookRequestContext, IconField, ImageField, type InferDocShape, JoinField, JsonField, LIFECYCLE_EVENTS_COLLECTION, LifecycleEvent, LifecycleEventName, MultiSelectField, NumberField, ObjectField, type Prettify, PublicAdminAuthConfig, RadioField, RelationshipField, RichTextField, RowField, SelectField, type SortClause, type SortDirection, type SqlWhereResult, type SystemDocFields, TextField, TextareaField, TimeField, type UploadDocFields, UrlField, WORKFLOW_HISTORY_COLLECTION, type WhereClause, type WhereOperator, type WhereOperatorName, WorkflowConfig, WorkflowTransition, availableWorkflowTransitions, canViewWorkflowDraft, createLifecycleEvent, createWorkflowDocument, defineArrayField, defineBlock, defineBlocksField, defineBooleanField, defineCollection, defineConfig, defineDateField, defineDateTimeField, defineEmailField, defineField, defineGlobal, defineIconField, defineImageField, defineJoinField, defineJsonField, defineMultiSelectField, defineNumberField, defineObjectField, defineRadioField, defineRelationshipField, defineRichTextField, defineRowField, defineSelectField, defineTextField, defineTextareaField, defineTimeField, defineUrlField, dispatchLifecycleEvent, dispatchPendingLifecycleEvents, executeFieldAfterRead, executeFieldBeforeChange, generateOpenApi, getAdminAuthCollection, getPublicAdminAuthConfig, initializeWorkflowDocument, materializeWorkflowDocument, normalizeConfig, parseMongoWhere, parseSort, parseSqlWhere, publishingWorkflow, resolveAdminAuthCollection, runCollectionHooks, saveWorkflowDraft, transitionWorkflow, workflowCapabilities };
500
+ export { AdminAuthConfig, ArrayField, type AuthDocFields, AuthenticatedUser, BaseDocument, Block, BlocksField, BooleanField, CollectionConfig, DateField, DateTimeField, DyrectedConfig, EmailField, Field, GlobalConfig, HookRequestContext, IconField, ImageField, type InferDocShape, JoinField, JsonField, LIFECYCLE_EVENTS_COLLECTION, LifecycleEvent, LifecycleEventName, MultiSelectField, NumberField, ObjectField, type Prettify, PublicAdminAuthConfig, RadioField, RelationshipField, RichTextField, RowField, SelectField, type SortClause, type SortDirection, type SqlWhereResult, type SystemDocFields, TextField, TextareaField, TimeField, type UploadDocFields, UrlField, WORKFLOW_HISTORY_COLLECTION, type WhereClause, type WhereOperator, type WhereOperatorName, WorkflowConfig, WorkflowTransition, availableWorkflowTransitions, canViewWorkflowDraft, createLifecycleEvent, createWorkflowDocument, defineArrayField, defineBlock, defineBlocksField, defineBooleanField, defineCollection, defineConfig, defineDateField, defineDateTimeField, defineEmailField, defineField, defineGlobal, defineIconField, defineImageField, defineJoinField, defineJsonField, defineMultiSelectField, defineNumberField, defineObjectField, defineRadioField, defineRelationshipField, defineRichTextField, defineRowField, defineSelectField, defineTab, defineTextField, defineTextareaField, defineTimeField, defineUrlField, dispatchLifecycleEvent, dispatchPendingLifecycleEvents, executeFieldAfterRead, executeFieldBeforeChange, generateOpenApi, getAdminAuthCollection, getPublicAdminAuthConfig, initializeWorkflowDocument, materializeWorkflowDocument, normalizeConfig, parseMongoWhere, parseSort, parseSqlWhere, publishingWorkflow, resolveAdminAuthCollection, runCollectionHooks, saveWorkflowDraft, transitionWorkflow, workflowCapabilities };
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  saveWorkflowDraft,
22
22
  transitionWorkflow,
23
23
  workflowCapabilities
24
- } from "./chunk-L65PQYDP.js";
24
+ } from "./chunk-2WODKOUB.js";
25
25
 
26
26
  // src/types/workflows.ts
27
27
  var LIFECYCLE_EVENT_NAMES = [
@@ -247,6 +247,12 @@ var defineUrlField = createFieldDefiner("url");
247
247
  var defineIconField = createFieldDefiner("icon");
248
248
  var defineJoinField = createFieldDefiner("join");
249
249
  var defineRowField = createFieldDefiner("row");
250
+ function defineTab(args) {
251
+ return args.fields.map((field) => ({
252
+ ...field,
253
+ admin: { ...field.admin ?? {}, tab: args.label }
254
+ }));
255
+ }
250
256
  export {
251
257
  LIFECYCLE_EVENTS_COLLECTION,
252
258
  LIFECYCLE_EVENT_NAMES,
@@ -278,6 +284,7 @@ export {
278
284
  defineRichTextField,
279
285
  defineRowField,
280
286
  defineSelectField,
287
+ defineTab,
281
288
  defineTextField,
282
289
  defineTextareaField,
283
290
  defineTimeField,