@dyrected/core 2.15.0 → 2.17.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/dist/index.cjs CHANGED
@@ -1789,9 +1789,14 @@ __export(index_exports, {
1789
1789
  AI_CHUNKS_COLLECTION: () => AI_CHUNKS_COLLECTION,
1790
1790
  AI_MESSAGES_COLLECTION: () => AI_MESSAGES_COLLECTION,
1791
1791
  AI_THREADS_COLLECTION: () => AI_THREADS_COLLECTION,
1792
+ BASE62_ALPHABET: () => BASE62_ALPHABET,
1792
1793
  BUILTIN_JEXL_HELPERS: () => BUILTIN_JEXL_HELPERS,
1794
+ CROCKFORD_BASE32: () => CROCKFORD_BASE32,
1795
+ CURRENT_NAV_PREFERENCES_VERSION: () => CURRENT_NAV_PREFERENCES_VERSION,
1793
1796
  ConfigValidationError: () => ConfigValidationError,
1794
1797
  ConflictError: () => ConflictError,
1798
+ DEFAULT_USER_NAV_PREFERENCES: () => DEFAULT_USER_NAV_PREFERENCES,
1799
+ DuplicateKeyError: () => DuplicateKeyError,
1795
1800
  DyrectedAIError: () => DyrectedAIError,
1796
1801
  DyrectedError: () => DyrectedError,
1797
1802
  FieldConditionBuilder: () => FieldConditionBuilder,
@@ -1799,9 +1804,11 @@ __export(index_exports, {
1799
1804
  LIFECYCLE_EVENTS_COLLECTION: () => LIFECYCLE_EVENTS_COLLECTION,
1800
1805
  LIFECYCLE_EVENT_NAMES: () => LIFECYCLE_EVENT_NAMES,
1801
1806
  MatchBuilder: () => MatchBuilder,
1807
+ NANOID_ALPHABET: () => NANOID_ALPHABET,
1802
1808
  NON_NEGOTIABLE_CREDENTIAL_FIELDS: () => NON_NEGOTIABLE_CREDENTIAL_FIELDS,
1803
1809
  NotFoundError: () => NotFoundError,
1804
1810
  PII_REGEX_PATTERNS: () => PII_REGEX_PATTERNS,
1811
+ RESERVED_NAVIGATION_SLUGS: () => RESERVED_NAVIGATION_SLUGS,
1805
1812
  ValidationError: () => ValidationError,
1806
1813
  WORKFLOW_HISTORY_COLLECTION: () => WORKFLOW_HISTORY_COLLECTION,
1807
1814
  abs: () => abs,
@@ -1812,6 +1819,7 @@ __export(index_exports, {
1812
1819
  assertValidDeclarativeHookExpression: () => assertValidDeclarativeHookExpression,
1813
1820
  assertValidDeclarativeHookResult: () => assertValidDeclarativeHookResult,
1814
1821
  assertValidDeclarativeHooksInConfig: () => assertValidDeclarativeHooksInConfig,
1822
+ assertValidNavigationSlugs: () => assertValidNavigationSlugs,
1815
1823
  assertValidPreviewUrlsInConfig: () => assertValidPreviewUrlsInConfig,
1816
1824
  availableWorkflowTransitions: () => availableWorkflowTransitions,
1817
1825
  buildSqlOrderBy: () => buildSqlOrderBy,
@@ -1826,6 +1834,7 @@ __export(index_exports, {
1826
1834
  collectDeclarativeHookValidationIssues: () => collectDeclarativeHookValidationIssues,
1827
1835
  collectPreviewUrlValidationIssues: () => collectPreviewUrlValidationIssues,
1828
1836
  compact: () => compact,
1837
+ compileNavigation: () => compileNavigation,
1829
1838
  count: () => count,
1830
1839
  createLifecycleEvent: () => createLifecycleEvent,
1831
1840
  createWorkflowDocument: () => createWorkflowDocument,
@@ -1866,6 +1875,7 @@ __export(index_exports, {
1866
1875
  defineTimeField: () => defineTimeField,
1867
1876
  defineUrlField: () => defineUrlField,
1868
1877
  defineView: () => defineView,
1878
+ defineWorkspace: () => defineWorkspace,
1869
1879
  diffDays: () => diffDays,
1870
1880
  dispatchLifecycleEvent: () => dispatchLifecycleEvent,
1871
1881
  dispatchPendingLifecycleEvents: () => dispatchPendingLifecycleEvents,
@@ -1894,6 +1904,8 @@ __export(index_exports, {
1894
1904
  formatDate: () => formatDate,
1895
1905
  formatJexlValue: () => formatJexlValue,
1896
1906
  generateDefaultDetailSchema: () => generateDefaultDetailSchema,
1907
+ generateDocumentId: () => generateDocumentId,
1908
+ generateId: () => generateId,
1897
1909
  generateOpenApi: () => generateOpenApi,
1898
1910
  get: () => get,
1899
1911
  getAdminAuthCollection: () => getAdminAuthCollection,
@@ -1920,6 +1932,7 @@ __export(index_exports, {
1920
1932
  materializeWorkflowDocument: () => materializeWorkflowDocument,
1921
1933
  max: () => max,
1922
1934
  min: () => min,
1935
+ nanoid: () => nanoid,
1923
1936
  normalizeConfig: () => normalizeConfig,
1924
1937
  normalizeDetailItem: () => normalizeDetailItem,
1925
1938
  now: () => now,
@@ -1928,13 +1941,17 @@ __export(index_exports, {
1928
1941
  parseSort: () => parseSort,
1929
1942
  parseSqlWhere: () => parseSqlWhere,
1930
1943
  pow: () => pow,
1944
+ prefixedId: () => prefixedId,
1945
+ pruneNavigationForUser: () => pruneNavigationForUser,
1931
1946
  publishedStateName: () => publishedStateName,
1932
1947
  publishingWorkflow: () => publishingWorkflow,
1933
1948
  readingTime: () => readingTime,
1949
+ reconcileNavigation: () => reconcileNavigation,
1934
1950
  registerJexlHelpers: () => registerJexlHelpers,
1935
1951
  replace: () => replace,
1936
1952
  resolveAdminAuthCollection: () => resolveAdminAuthCollection,
1937
1953
  resolveAuthorizedSiteId: () => resolveAuthorizedSiteId,
1954
+ resolvePrefix: () => resolvePrefix,
1938
1955
  round: () => round,
1939
1956
  runCollectionHooks: () => runCollectionHooks,
1940
1957
  sanitizeDocForAI: () => sanitizeDocForAI,
@@ -1948,8 +1965,10 @@ __export(index_exports, {
1948
1965
  transitionWorkflow: () => transitionWorkflow,
1949
1966
  trim: () => trim,
1950
1967
  truncate: () => truncate,
1968
+ ulid: () => ulid,
1951
1969
  unique: () => unique,
1952
1970
  upper: () => upper,
1971
+ uuid: () => uuid,
1953
1972
  validateAdminConditionExpression: () => validateAdminConditionExpression,
1954
1973
  validateDeclarativeAccessExpression: () => validateDeclarativeAccessExpression,
1955
1974
  validateDeclarativeHookExpression: () => validateDeclarativeHookExpression,
@@ -2031,6 +2050,21 @@ var DyrectedAIError = class _DyrectedAIError extends Error {
2031
2050
  }
2032
2051
  };
2033
2052
 
2053
+ // src/types/navigation.ts
2054
+ function defineWorkspace(options) {
2055
+ return options;
2056
+ }
2057
+ var CURRENT_NAV_PREFERENCES_VERSION = 1;
2058
+ var DEFAULT_USER_NAV_PREFERENCES = {
2059
+ _version: CURRENT_NAV_PREFERENCES_VERSION,
2060
+ pinned: [],
2061
+ hidden: [],
2062
+ groupOrder: [],
2063
+ itemOrder: {},
2064
+ groups: [],
2065
+ items: []
2066
+ };
2067
+
2034
2068
  // src/auth/token.ts
2035
2069
  var import_jose = require("jose");
2036
2070
 
@@ -3549,6 +3583,134 @@ function assertValidDeclarativeAccessInConfig(config, source = "config") {
3549
3583
  throw new ConfigValidationError(source, diagnostics);
3550
3584
  }
3551
3585
 
3586
+ // src/utils/id.ts
3587
+ var import_node_crypto = require("crypto");
3588
+ var NANOID_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-";
3589
+ var BASE62_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
3590
+ var CROCKFORD_BASE32 = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
3591
+ var COMMON_SLUG_PREFIXES = {
3592
+ users: "usr",
3593
+ members: "mem",
3594
+ accounts: "acc",
3595
+ applications: "app",
3596
+ customers: "cus",
3597
+ products: "prd",
3598
+ orders: "ord",
3599
+ reports: "rpt",
3600
+ sessions: "ses",
3601
+ transactions: "txn",
3602
+ wallets: "wal",
3603
+ media: "med",
3604
+ files: "fil",
3605
+ posts: "post",
3606
+ pages: "page",
3607
+ tags: "tag",
3608
+ categories: "cat",
3609
+ comments: "com"
3610
+ };
3611
+ function nanoid(size = 21, alphabet = NANOID_ALPHABET) {
3612
+ const mask = (2 << 31 - Math.clz32(alphabet.length - 1 | 1)) - 1;
3613
+ const step = Math.ceil(1.6 * mask * size / alphabet.length);
3614
+ let id = "";
3615
+ while (true) {
3616
+ const bytes = (0, import_node_crypto.randomBytes)(step);
3617
+ for (let i = 0; i < step; i++) {
3618
+ const byte = bytes[i] & mask;
3619
+ if (alphabet[byte]) {
3620
+ id += alphabet[byte];
3621
+ if (id.length === size) return id;
3622
+ }
3623
+ }
3624
+ }
3625
+ }
3626
+ function encodeUlidTime(now2, len = 10) {
3627
+ let str = "";
3628
+ let time = Math.floor(now2);
3629
+ for (let i = len - 1; i >= 0; i--) {
3630
+ const mod = time % 32;
3631
+ str = CROCKFORD_BASE32[mod] + str;
3632
+ time = (time - mod) / 32;
3633
+ }
3634
+ return str;
3635
+ }
3636
+ function encodeUlidRandom(bytes) {
3637
+ let str = "";
3638
+ let bitBuffer = 0;
3639
+ let bitsInBuffer = 0;
3640
+ for (let i = 0; i < bytes.length; i++) {
3641
+ bitBuffer = bitBuffer << 8 | bytes[i];
3642
+ bitsInBuffer += 8;
3643
+ while (bitsInBuffer >= 5) {
3644
+ bitsInBuffer -= 5;
3645
+ const index = bitBuffer >> bitsInBuffer & 31;
3646
+ str += CROCKFORD_BASE32[index];
3647
+ }
3648
+ }
3649
+ return str;
3650
+ }
3651
+ function ulid(seedTime = Date.now()) {
3652
+ const timePart = encodeUlidTime(seedTime);
3653
+ const randPart = encodeUlidRandom((0, import_node_crypto.randomBytes)(10));
3654
+ return timePart + randPart;
3655
+ }
3656
+ function uuid() {
3657
+ return (0, import_node_crypto.randomUUID)();
3658
+ }
3659
+ function resolvePrefix(slug, explicitPrefix) {
3660
+ if (explicitPrefix && typeof explicitPrefix === "string" && explicitPrefix.trim().length > 0) {
3661
+ const cleaned = explicitPrefix.trim().toLowerCase().replace(/[^a-z0-9_]/g, "");
3662
+ if (cleaned.length > 0) return cleaned;
3663
+ }
3664
+ if (!slug || typeof slug !== "string") return "col";
3665
+ const s = slug.trim().toLowerCase();
3666
+ if (COMMON_SLUG_PREFIXES[s]) return COMMON_SLUG_PREFIXES[s];
3667
+ const pieces = s.split(/[-_]/).filter(Boolean);
3668
+ if (pieces.length > 1 && pieces[0].length >= 2) {
3669
+ const first2 = pieces[0].replace(/[^a-z0-9]/g, "");
3670
+ if (first2.length >= 2) return first2.slice(0, 4);
3671
+ }
3672
+ const cleanWord = s.replace(/[^a-z0-9]/g, "");
3673
+ if (cleanWord.length <= 4) return cleanWord || "col";
3674
+ return cleanWord.slice(0, 3) || "col";
3675
+ }
3676
+ function prefixedId(prefix = "col", size = 16) {
3677
+ const cleanPrefix = (prefix || "col").toLowerCase().replace(/[^a-z0-9_]/g, "") || "col";
3678
+ return `${cleanPrefix}_${nanoid(size, BASE62_ALPHABET)}`;
3679
+ }
3680
+ function generateId(options) {
3681
+ if (typeof options === "string") {
3682
+ return prefixedId(resolvePrefix(options));
3683
+ }
3684
+ const opts = options || {};
3685
+ const type = opts.type || "prefixed-nanoid";
3686
+ switch (type) {
3687
+ case "uuid":
3688
+ return uuid();
3689
+ case "ulid":
3690
+ return ulid();
3691
+ case "nanoid":
3692
+ return nanoid(opts.size || 21);
3693
+ case "prefixed-nanoid":
3694
+ default:
3695
+ return prefixedId(opts.prefix || "col", opts.size || 16);
3696
+ }
3697
+ }
3698
+ function generateDocumentId(collectionOrSlug) {
3699
+ if (!collectionOrSlug) {
3700
+ return prefixedId("col");
3701
+ }
3702
+ if (typeof collectionOrSlug === "string") {
3703
+ const prefix2 = resolvePrefix(collectionOrSlug);
3704
+ return prefixedId(prefix2);
3705
+ }
3706
+ if (typeof collectionOrSlug.idGenerator === "function") {
3707
+ return collectionOrSlug.idGenerator(collectionOrSlug.slug);
3708
+ }
3709
+ const type = collectionOrSlug.idType || "prefixed-nanoid";
3710
+ const prefix = resolvePrefix(collectionOrSlug.slug, collectionOrSlug.idPrefix);
3711
+ return generateId({ prefix, type });
3712
+ }
3713
+
3552
3714
  // src/utils/config.ts
3553
3715
  var AUDIT_COLLECTION_SLUG = "__audit";
3554
3716
  var SYSTEM_FIELDS = [
@@ -3779,7 +3941,33 @@ function normalizeConfig(config) {
3779
3941
  }
3780
3942
  ];
3781
3943
  }
3944
+ if (!existingFieldNames.has("__preferences")) {
3945
+ fields = [
3946
+ ...fields,
3947
+ {
3948
+ name: "__preferences",
3949
+ type: "json",
3950
+ label: "Preferences",
3951
+ promoted: true,
3952
+ admin: {
3953
+ hidden: true,
3954
+ readOnly: true
3955
+ }
3956
+ }
3957
+ ];
3958
+ }
3782
3959
  fields = fields.map((field) => {
3960
+ if (field.name === "__preferences") {
3961
+ return {
3962
+ ...field,
3963
+ promoted: true,
3964
+ admin: {
3965
+ ...field.admin || {},
3966
+ hidden: true,
3967
+ readOnly: true
3968
+ }
3969
+ };
3970
+ }
3783
3971
  if (field.name === "email") {
3784
3972
  return {
3785
3973
  ...field,
@@ -3870,11 +4058,31 @@ function normalizeConfig(config) {
3870
4058
  }
3871
4059
  const updatedFieldNames = new Set(fields.map((f) => f.name));
3872
4060
  const fieldsToInject = SYSTEM_FIELDS.filter((f) => !updatedFieldNames.has(f.name));
4061
+ const indexedFieldNames = /* @__PURE__ */ new Set();
4062
+ if (col.indexes) {
4063
+ for (const idx of col.indexes) {
4064
+ if (Array.isArray(idx.fields)) {
4065
+ for (const f of idx.fields) {
4066
+ indexedFieldNames.add(f);
4067
+ }
4068
+ }
4069
+ }
4070
+ }
4071
+ const allFields = [...fields, ...fieldsToInject].map((field) => {
4072
+ if (field.unique || field.name && indexedFieldNames.has(field.name)) {
4073
+ return { ...field, promoted: true };
4074
+ }
4075
+ return field;
4076
+ });
3873
4077
  const workflow = col.workflow || (col.drafts ? simplePublishingWorkflow() : void 0);
4078
+ const idPrefix = col.idPrefix || resolvePrefix(col.slug);
4079
+ const idType = col.idType || "prefixed-nanoid";
3874
4080
  return {
3875
4081
  ...col,
4082
+ idPrefix,
4083
+ idType,
3876
4084
  workflow,
3877
- fields: [...fields, ...fieldsToInject]
4085
+ fields: allFields
3878
4086
  };
3879
4087
  });
3880
4088
  const hasAuditCollection = normalizedCollections.some((col) => col.slug === AUDIT_COLLECTION_SLUG);
@@ -3934,6 +4142,17 @@ var ConflictError = class _ConflictError extends DyrectedError {
3934
4142
  Object.setPrototypeOf(this, _ConflictError.prototype);
3935
4143
  }
3936
4144
  };
4145
+ var DuplicateKeyError = class _DuplicateKeyError extends ConflictError {
4146
+ field;
4147
+ value;
4148
+ constructor(message, options) {
4149
+ super(message);
4150
+ this.name = "DuplicateKeyError";
4151
+ this.field = options?.field;
4152
+ this.value = options?.value;
4153
+ Object.setPrototypeOf(this, _DuplicateKeyError.prototype);
4154
+ }
4155
+ };
3937
4156
 
3938
4157
  // src/utils/parse-where.ts
3939
4158
  function assertNever(op, context) {
@@ -6555,6 +6774,525 @@ function sanitizeDocForAI(options) {
6555
6774
  return result;
6556
6775
  }
6557
6776
 
6777
+ // src/utils/navigation-reconciler.ts
6778
+ var normalizeKey = (val) => (val || "").toLowerCase().replace(/[^a-z0-9]/g, "-");
6779
+ function reconcileNavigation(baseTree, prefs, schemas, options) {
6780
+ if (!baseTree) {
6781
+ return {
6782
+ groups: [],
6783
+ ungrouped: [],
6784
+ pinnedItems: []
6785
+ };
6786
+ }
6787
+ const includeHidden = options?.includeHidden ?? false;
6788
+ const effectivePrefs = prefs || DEFAULT_USER_NAV_PREFERENCES;
6789
+ const hiddenSet = new Set(effectivePrefs.hidden || []);
6790
+ const knownCollectionSlugs = new Set((schemas?.collections || []).map((c) => c.slug));
6791
+ const knownGlobalSlugs = new Set((schemas?.globals || []).map((g) => g.slug));
6792
+ const groupsList = [];
6793
+ const findGroup = (query) => {
6794
+ const qNorm = normalizeKey(query);
6795
+ return groupsList.find(
6796
+ (g) => g.id === query || g.slug === query || g.name === query || normalizeKey(g.id) === qNorm || normalizeKey(g.slug) === qNorm || normalizeKey(g.name) === qNorm
6797
+ );
6798
+ };
6799
+ for (const g of baseTree.groups || []) {
6800
+ groupsList.push({
6801
+ ...g,
6802
+ items: (g.items || []).map((item) => ({ ...item }))
6803
+ });
6804
+ }
6805
+ for (const ug of effectivePrefs.groups || []) {
6806
+ const existing = findGroup(ug.slug || ug.name);
6807
+ if (!existing) {
6808
+ groupsList.push({
6809
+ id: ug.slug ? `group_${ug.slug}` : `group_${normalizeKey(ug.name)}`,
6810
+ name: ug.name,
6811
+ slug: ug.slug || normalizeKey(ug.name),
6812
+ icon: ug.icon || "Folder",
6813
+ defaultExpanded: ug.defaultExpanded ?? true,
6814
+ order: ug.order ?? 100,
6815
+ items: []
6816
+ });
6817
+ }
6818
+ }
6819
+ for (const itemOpt of effectivePrefs.items || []) {
6820
+ const targetGroup = typeof itemOpt.group === "object" ? itemOpt.group?.name : itemOpt.group;
6821
+ const groupKey = targetGroup || "Custom";
6822
+ let group = findGroup(groupKey);
6823
+ if (!group) {
6824
+ group = {
6825
+ id: `group_${normalizeKey(groupKey)}`,
6826
+ name: groupKey,
6827
+ slug: normalizeKey(groupKey),
6828
+ icon: "Folder",
6829
+ defaultExpanded: true,
6830
+ order: 99,
6831
+ items: []
6832
+ };
6833
+ groupsList.push(group);
6834
+ }
6835
+ const itemId = itemOpt.slug ? `workspace_${itemOpt.slug}` : itemOpt.collection ? `collection_${itemOpt.collection}` : `custom_${Math.random()}`;
6836
+ for (const otherGroup of groupsList) {
6837
+ if (otherGroup !== group) {
6838
+ otherGroup.items = otherGroup.items.filter((i) => i.slug !== itemOpt.slug && i.id !== itemId);
6839
+ }
6840
+ }
6841
+ const existingIndex = group.items.findIndex((i) => i.id === itemId || i.slug === itemOpt.slug);
6842
+ const existingItem = existingIndex >= 0 ? group.items[existingIndex] : void 0;
6843
+ const col = schemas?.collections?.find((c) => c.slug === itemOpt.collection);
6844
+ const colViews = col?.views || [];
6845
+ const baseViews = existingItem?.views || [];
6846
+ const userViews = itemOpt.views || [];
6847
+ const userViewSlugs = new Set(userViews.map((v) => v.slug));
6848
+ let mergedViews = [
6849
+ ...baseViews.filter((v) => !userViewSlugs.has(v.slug)),
6850
+ ...userViews
6851
+ ];
6852
+ if (mergedViews.length === 0 && itemOpt.collection) {
6853
+ if (colViews.length > 0) {
6854
+ mergedViews = [...colViews];
6855
+ } else {
6856
+ mergedViews = [
6857
+ {
6858
+ slug: "default",
6859
+ label: col?.label || col?.labels?.plural || itemOpt.label || "All Records",
6860
+ layout: "table",
6861
+ collection: itemOpt.collection
6862
+ }
6863
+ ];
6864
+ }
6865
+ }
6866
+ mergedViews = mergedViews.map((v) => ({
6867
+ ...v,
6868
+ collection: v.collection || itemOpt.collection || existingItem?.collection
6869
+ }));
6870
+ const compiledItem = {
6871
+ id: itemId,
6872
+ type: itemOpt.slug ? "workspace" : itemOpt.collection ? "collection" : itemOpt.global ? "global" : "workspace",
6873
+ slug: itemOpt.slug || itemOpt.collection || itemOpt.global || "item",
6874
+ label: itemOpt.label || itemOpt.slug || "Item",
6875
+ icon: itemOpt.icon || existingItem?.icon || col?.admin?.icon || "Briefcase",
6876
+ group: group.name,
6877
+ order: itemOpt.order ?? existingItem?.order ?? 100,
6878
+ views: mergedViews,
6879
+ collection: itemOpt.collection || existingItem?.collection,
6880
+ global: itemOpt.global || existingItem?.global,
6881
+ badge: itemOpt.badge || existingItem?.badge,
6882
+ access: itemOpt.access || existingItem?.access
6883
+ };
6884
+ if (existingIndex >= 0) {
6885
+ group.items[existingIndex] = compiledItem;
6886
+ } else {
6887
+ group.items.push(compiledItem);
6888
+ }
6889
+ }
6890
+ const allAvailableItems = [];
6891
+ const reconciledGroups = [];
6892
+ for (const group of groupsList) {
6893
+ const isGroupHidden = hiddenSet.has(group.id) || !!group.slug && hiddenSet.has(group.slug) || hiddenSet.has(group.name);
6894
+ if (!includeHidden && isGroupHidden) {
6895
+ continue;
6896
+ }
6897
+ const visibleItems = [];
6898
+ for (const item of group.items) {
6899
+ const isItemHidden = hiddenSet.has(item.id) || !!item.slug && hiddenSet.has(item.slug);
6900
+ if (!includeHidden && isItemHidden) {
6901
+ continue;
6902
+ }
6903
+ const allViews = item.views || [];
6904
+ const visibleViews = includeHidden ? allViews : allViews.filter((v) => {
6905
+ const compoundKey = `${item.slug}_${v.slug}`;
6906
+ return !hiddenSet.has(compoundKey) && !hiddenSet.has(v.slug);
6907
+ });
6908
+ let isTombstone = false;
6909
+ if (schemas && schemas.collections && schemas.collections.length > 0) {
6910
+ if (item.collection && !knownCollectionSlugs.has(item.collection)) {
6911
+ isTombstone = true;
6912
+ }
6913
+ }
6914
+ if (schemas && schemas.globals && schemas.globals.length > 0) {
6915
+ if (item.global && !knownGlobalSlugs.has(item.global)) {
6916
+ isTombstone = true;
6917
+ }
6918
+ }
6919
+ const processedItem = {
6920
+ ...item,
6921
+ views: visibleViews,
6922
+ ...isTombstone ? { isTombstone: true } : {}
6923
+ };
6924
+ visibleItems.push(processedItem);
6925
+ allAvailableItems.push(processedItem);
6926
+ }
6927
+ const groupKey = group.slug || group.name;
6928
+ const itemOrderList = effectivePrefs.itemOrder?.[group.id] || effectivePrefs.itemOrder?.[groupKey];
6929
+ if (itemOrderList && itemOrderList.length > 0) {
6930
+ visibleItems.sort((a, b) => {
6931
+ const aIdx = itemOrderList.indexOf(a.id) >= 0 ? itemOrderList.indexOf(a.id) : a.slug ? itemOrderList.indexOf(a.slug) : -1;
6932
+ const bIdx = itemOrderList.indexOf(b.id) >= 0 ? itemOrderList.indexOf(b.id) : b.slug ? itemOrderList.indexOf(b.slug) : -1;
6933
+ if (aIdx >= 0 && bIdx >= 0) return aIdx - bIdx;
6934
+ if (aIdx >= 0) return -1;
6935
+ if (bIdx >= 0) return 1;
6936
+ return (a.order ?? 100) - (b.order ?? 100);
6937
+ });
6938
+ } else {
6939
+ visibleItems.sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
6940
+ }
6941
+ reconciledGroups.push({
6942
+ ...group,
6943
+ items: visibleItems
6944
+ });
6945
+ }
6946
+ if (effectivePrefs.groupOrder && effectivePrefs.groupOrder.length > 0) {
6947
+ reconciledGroups.sort((a, b) => {
6948
+ const aIdx = effectivePrefs.groupOrder.indexOf(a.id) >= 0 ? effectivePrefs.groupOrder.indexOf(a.id) : a.slug ? effectivePrefs.groupOrder.indexOf(a.slug) : effectivePrefs.groupOrder.indexOf(a.name);
6949
+ const bIdx = effectivePrefs.groupOrder.indexOf(b.id) >= 0 ? effectivePrefs.groupOrder.indexOf(b.id) : b.slug ? effectivePrefs.groupOrder.indexOf(b.slug) : effectivePrefs.groupOrder.indexOf(b.name);
6950
+ if (aIdx >= 0 && bIdx >= 0) return aIdx - bIdx;
6951
+ if (aIdx >= 0) return -1;
6952
+ if (bIdx >= 0) return 1;
6953
+ return (a.order ?? 100) - (b.order ?? 100);
6954
+ });
6955
+ } else {
6956
+ reconciledGroups.sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
6957
+ }
6958
+ const reconciledUngrouped = (baseTree.ungrouped || []).filter((item) => {
6959
+ const isItemHidden = hiddenSet.has(item.id) || !!item.slug && hiddenSet.has(item.slug);
6960
+ return includeHidden || !isItemHidden;
6961
+ }).map((item) => {
6962
+ const allViews = item.views || [];
6963
+ const visibleViews = includeHidden ? allViews : allViews.filter((v) => {
6964
+ const compoundKey = `${item.slug}_${v.slug}`;
6965
+ return !hiddenSet.has(compoundKey) && !hiddenSet.has(v.slug);
6966
+ });
6967
+ const processed = { ...item, views: visibleViews };
6968
+ allAvailableItems.push(processed);
6969
+ return processed;
6970
+ });
6971
+ const pinnedItems = [];
6972
+ for (const pin of effectivePrefs.pinned || []) {
6973
+ const match = allAvailableItems.find((i) => i.slug === pin.slug || i.id === pin.slug);
6974
+ if (match) {
6975
+ pinnedItems.push({
6976
+ ...match,
6977
+ id: `pin_${match.id}`,
6978
+ label: pin.label || match.label,
6979
+ icon: pin.icon || match.icon
6980
+ });
6981
+ } else {
6982
+ pinnedItems.push({
6983
+ id: `pin_${pin.slug}`,
6984
+ type: pin.type,
6985
+ slug: pin.slug,
6986
+ label: pin.label || pin.slug,
6987
+ icon: pin.icon || "Star",
6988
+ order: 0,
6989
+ views: []
6990
+ });
6991
+ }
6992
+ }
6993
+ return {
6994
+ groups: reconciledGroups,
6995
+ ungrouped: reconciledUngrouped,
6996
+ pinnedItems
6997
+ };
6998
+ }
6999
+
7000
+ // src/utils/navigation.ts
7001
+ var RESERVED_NAVIGATION_SLUGS = /* @__PURE__ */ new Set([
7002
+ "collections",
7003
+ "globals",
7004
+ "setup",
7005
+ "api",
7006
+ "login",
7007
+ "logout",
7008
+ "settings",
7009
+ "admin",
7010
+ "preferences",
7011
+ "auth"
7012
+ ]);
7013
+ function assertValidNavigationSlugs(navigation) {
7014
+ for (const item of navigation) {
7015
+ if (item.slug) {
7016
+ const normalizedSlug = item.slug.toLowerCase().trim().replace(/^\//, "");
7017
+ if (RESERVED_NAVIGATION_SLUGS.has(normalizedSlug)) {
7018
+ throw new Error(
7019
+ `[Dyrected Config] The slug '${item.slug}' in navigation item '${item.label || item.slug}' is a reserved system path and cannot be used as an operational workspace slug.`
7020
+ );
7021
+ }
7022
+ }
7023
+ }
7024
+ }
7025
+ function resolveGroupMeta(groupProp, defaultGroupName = "Collections") {
7026
+ if (!groupProp) {
7027
+ return { name: defaultGroupName };
7028
+ }
7029
+ if (typeof groupProp === "string") {
7030
+ return { name: groupProp };
7031
+ }
7032
+ return { name: groupProp.name, meta: groupProp };
7033
+ }
7034
+ function compileNavigation(config) {
7035
+ const collections = config.collections || [];
7036
+ const globals = config.globals || [];
7037
+ const explicitNav = config.admin?.navigation || [];
7038
+ assertValidNavigationSlugs(explicitNav);
7039
+ const collectionsBySlug = /* @__PURE__ */ new Map();
7040
+ for (const col of collections) {
7041
+ collectionsBySlug.set(col.slug, col);
7042
+ }
7043
+ const globalsBySlug = /* @__PURE__ */ new Map();
7044
+ for (const gl of globals) {
7045
+ globalsBySlug.set(gl.slug, gl);
7046
+ }
7047
+ const mentionedCollections = /* @__PURE__ */ new Set();
7048
+ const mentionedGlobals = /* @__PURE__ */ new Set();
7049
+ const injectedViewsByCollection = /* @__PURE__ */ new Map();
7050
+ for (const item of explicitNav) {
7051
+ if (item.addToCollection) {
7052
+ const existing = injectedViewsByCollection.get(item.addToCollection) || [];
7053
+ if (item.views && item.views.length > 0) {
7054
+ injectedViewsByCollection.set(item.addToCollection, [...existing, ...item.views]);
7055
+ }
7056
+ }
7057
+ if (item.collection) mentionedCollections.add(item.collection);
7058
+ if (item.global) mentionedGlobals.add(item.global);
7059
+ }
7060
+ const groupMetadataMap = /* @__PURE__ */ new Map();
7061
+ const itemsByGroup = /* @__PURE__ */ new Map();
7062
+ const ungroupedItems = [];
7063
+ const registerItem = (groupName, item) => {
7064
+ if (!groupName) {
7065
+ ungroupedItems.push(item);
7066
+ } else {
7067
+ const list = itemsByGroup.get(groupName) || [];
7068
+ list.push(item);
7069
+ itemsByGroup.set(groupName, list);
7070
+ }
7071
+ };
7072
+ for (const item of explicitNav) {
7073
+ if (item.addToCollection && !item.slug && !item.collection && !item.global && !item.dashboard && !item.href) {
7074
+ continue;
7075
+ }
7076
+ const { name: groupName, meta: groupMeta } = resolveGroupMeta(item.group, void 0);
7077
+ if (groupName && groupMeta) {
7078
+ groupMetadataMap.set(groupName, { ...groupMetadataMap.get(groupName) || {}, ...groupMeta });
7079
+ }
7080
+ if (item.dashboard) {
7081
+ registerItem(groupName, {
7082
+ id: "dashboard",
7083
+ type: "dashboard",
7084
+ slug: "dashboard",
7085
+ label: item.label || "Dashboard",
7086
+ icon: item.icon || "LayoutDashboard",
7087
+ group: groupName,
7088
+ order: item.order ?? 0,
7089
+ views: [],
7090
+ access: item.access
7091
+ });
7092
+ continue;
7093
+ }
7094
+ if (item.collection) {
7095
+ const col = collectionsBySlug.get(item.collection);
7096
+ const combinedViews = [
7097
+ ...col?.views || [],
7098
+ ...injectedViewsByCollection.get(item.collection) || [],
7099
+ ...item.views || []
7100
+ ];
7101
+ registerItem(groupName, {
7102
+ id: item.slug || `collection_${item.collection}`,
7103
+ type: item.slug ? "workspace" : "collection",
7104
+ slug: item.slug || item.collection,
7105
+ collection: item.collection,
7106
+ label: item.label || col?.labels?.plural || item.collection,
7107
+ icon: item.icon || col?.admin?.icon || "Folder",
7108
+ group: groupName,
7109
+ order: item.order ?? col?.admin?.order ?? 100,
7110
+ views: combinedViews,
7111
+ badge: item.badge,
7112
+ access: item.access || col?.access?.read
7113
+ });
7114
+ continue;
7115
+ }
7116
+ if (item.global) {
7117
+ const gl = globalsBySlug.get(item.global);
7118
+ registerItem(groupName, {
7119
+ id: item.slug || `global_${item.global}`,
7120
+ type: "global",
7121
+ slug: item.slug || item.global,
7122
+ global: item.global,
7123
+ label: item.label || gl?.label || item.global,
7124
+ icon: item.icon || "Settings",
7125
+ group: groupName,
7126
+ order: item.order ?? 100,
7127
+ views: [],
7128
+ badge: item.badge,
7129
+ access: item.access
7130
+ });
7131
+ continue;
7132
+ }
7133
+ if (item.href) {
7134
+ registerItem(groupName, {
7135
+ id: `link_${item.slug || item.href}`,
7136
+ type: "link",
7137
+ slug: item.slug || "link",
7138
+ label: item.label || "Link",
7139
+ href: item.href,
7140
+ icon: item.icon || "ExternalLink",
7141
+ group: groupName,
7142
+ order: item.order ?? 100,
7143
+ views: [],
7144
+ badge: item.badge,
7145
+ access: item.access
7146
+ });
7147
+ continue;
7148
+ }
7149
+ const workspaceSlug = item.slug || "workspace";
7150
+ registerItem(groupName, {
7151
+ id: `workspace_${workspaceSlug}`,
7152
+ type: "workspace",
7153
+ slug: workspaceSlug,
7154
+ label: item.label || workspaceSlug,
7155
+ icon: item.icon || "Briefcase",
7156
+ group: groupName,
7157
+ order: item.order ?? 100,
7158
+ views: item.views || [],
7159
+ badge: item.badge,
7160
+ access: item.access
7161
+ });
7162
+ }
7163
+ for (const col of collections) {
7164
+ if (mentionedCollections.has(col.slug)) continue;
7165
+ if (col.admin?.hidden) continue;
7166
+ const groupProp = col.admin?.group;
7167
+ const { name: groupName, meta: groupMeta } = resolveGroupMeta(groupProp, "Collections");
7168
+ if (groupMeta) {
7169
+ groupMetadataMap.set(groupName, { ...groupMetadataMap.get(groupName) || {}, ...groupMeta });
7170
+ }
7171
+ const combinedViews = [
7172
+ ...col.views || [],
7173
+ ...injectedViewsByCollection.get(col.slug) || []
7174
+ ];
7175
+ registerItem(groupName, {
7176
+ id: `collection_${col.slug}`,
7177
+ type: "collection",
7178
+ slug: col.slug,
7179
+ collection: col.slug,
7180
+ label: col.labels?.plural || col.slug,
7181
+ icon: col.admin?.icon || "Folder",
7182
+ group: groupName,
7183
+ order: col?.admin?.order ?? 100,
7184
+ views: combinedViews,
7185
+ access: col.access?.read
7186
+ });
7187
+ }
7188
+ for (const gl of globals) {
7189
+ if (mentionedGlobals.has(gl.slug)) continue;
7190
+ const groupName = "Configuration";
7191
+ registerItem(groupName, {
7192
+ id: `global_${gl.slug}`,
7193
+ type: "global",
7194
+ slug: gl.slug,
7195
+ global: gl.slug,
7196
+ label: gl.label || gl.slug,
7197
+ icon: "Settings",
7198
+ group: groupName,
7199
+ order: 100,
7200
+ views: []
7201
+ });
7202
+ }
7203
+ const sortAndSpliceItems = (items) => {
7204
+ const sorted = [...items].sort((a, b) => a.order - b.order);
7205
+ const explicitMap = /* @__PURE__ */ new Map();
7206
+ for (const opt of explicitNav) {
7207
+ if (opt.slug) explicitMap.set(opt.slug, opt);
7208
+ if (opt.collection) explicitMap.set(opt.collection, opt);
7209
+ if (opt.global) explicitMap.set(opt.global, opt);
7210
+ }
7211
+ for (const opt of explicitNav) {
7212
+ const itemKey = opt.slug || opt.collection || opt.global;
7213
+ if (!itemKey) continue;
7214
+ if (opt.after) {
7215
+ const itemIdx = sorted.findIndex((i) => i.slug === itemKey);
7216
+ const targetIdx = sorted.findIndex((i) => i.slug === opt.after);
7217
+ if (itemIdx !== -1 && targetIdx !== -1 && itemIdx !== targetIdx) {
7218
+ const [moved] = sorted.splice(itemIdx, 1);
7219
+ const newTargetIdx = sorted.findIndex((i) => i.slug === opt.after);
7220
+ sorted.splice(newTargetIdx + 1, 0, moved);
7221
+ }
7222
+ } else if (opt.before) {
7223
+ const itemIdx = sorted.findIndex((i) => i.slug === itemKey);
7224
+ const targetIdx = sorted.findIndex((i) => i.slug === opt.before);
7225
+ if (itemIdx !== -1 && targetIdx !== -1 && itemIdx !== targetIdx) {
7226
+ const [moved] = sorted.splice(itemIdx, 1);
7227
+ const newTargetIdx = sorted.findIndex((i) => i.slug === opt.before);
7228
+ sorted.splice(newTargetIdx, 0, moved);
7229
+ }
7230
+ } else if (opt.position === "first") {
7231
+ const itemIdx = sorted.findIndex((i) => i.slug === itemKey);
7232
+ if (itemIdx > 0) {
7233
+ const [moved] = sorted.splice(itemIdx, 1);
7234
+ sorted.unshift(moved);
7235
+ }
7236
+ } else if (opt.position === "last") {
7237
+ const itemIdx = sorted.findIndex((i) => i.slug === itemKey);
7238
+ if (itemIdx !== -1 && itemIdx < sorted.length - 1) {
7239
+ const [moved] = sorted.splice(itemIdx, 1);
7240
+ sorted.push(moved);
7241
+ }
7242
+ }
7243
+ }
7244
+ return sorted;
7245
+ };
7246
+ const compiledGroups = [];
7247
+ for (const [groupName, rawItems] of itemsByGroup.entries()) {
7248
+ const meta = groupMetadataMap.get(groupName);
7249
+ const sortedItems = sortAndSpliceItems(rawItems);
7250
+ compiledGroups.push({
7251
+ id: `group_${groupName.toLowerCase().replace(/\s+/g, "_")}`,
7252
+ name: groupName,
7253
+ slug: meta?.slug || groupName.toLowerCase().replace(/\s+/g, "-"),
7254
+ icon: meta?.icon,
7255
+ defaultExpanded: meta?.defaultExpanded ?? true,
7256
+ order: meta?.order ?? 100,
7257
+ items: sortedItems
7258
+ });
7259
+ }
7260
+ compiledGroups.sort((a, b) => a.order - b.order);
7261
+ return {
7262
+ groups: compiledGroups,
7263
+ ungrouped: sortAndSpliceItems(ungroupedItems)
7264
+ };
7265
+ }
7266
+ function pruneNavigationForUser(tree, user) {
7267
+ const userRoles = /* @__PURE__ */ new Set();
7268
+ if (user?.role) userRoles.add(user.role);
7269
+ if (Array.isArray(user?.roles)) {
7270
+ for (const r of user.roles) userRoles.add(r);
7271
+ }
7272
+ const isAllowed = (item) => {
7273
+ if (!item.access) return true;
7274
+ if (Array.isArray(item.access)) {
7275
+ if (item.access.length === 0) return true;
7276
+ return item.access.some((role) => userRoles.has(role));
7277
+ }
7278
+ return true;
7279
+ };
7280
+ const prunedGroups = [];
7281
+ for (const group of tree.groups) {
7282
+ const visibleItems = group.items.filter(isAllowed);
7283
+ if (visibleItems.length > 0) {
7284
+ prunedGroups.push({
7285
+ ...group,
7286
+ items: visibleItems
7287
+ });
7288
+ }
7289
+ }
7290
+ return {
7291
+ groups: prunedGroups,
7292
+ ungrouped: tree.ungrouped.filter(isAllowed)
7293
+ };
7294
+ }
7295
+
6558
7296
  // src/index.ts
6559
7297
  var import_zod = require("zod");
6560
7298
  function defineCollection(config) {
@@ -6619,9 +7357,14 @@ function defineTab(args) {
6619
7357
  AI_CHUNKS_COLLECTION,
6620
7358
  AI_MESSAGES_COLLECTION,
6621
7359
  AI_THREADS_COLLECTION,
7360
+ BASE62_ALPHABET,
6622
7361
  BUILTIN_JEXL_HELPERS,
7362
+ CROCKFORD_BASE32,
7363
+ CURRENT_NAV_PREFERENCES_VERSION,
6623
7364
  ConfigValidationError,
6624
7365
  ConflictError,
7366
+ DEFAULT_USER_NAV_PREFERENCES,
7367
+ DuplicateKeyError,
6625
7368
  DyrectedAIError,
6626
7369
  DyrectedError,
6627
7370
  FieldConditionBuilder,
@@ -6629,9 +7372,11 @@ function defineTab(args) {
6629
7372
  LIFECYCLE_EVENTS_COLLECTION,
6630
7373
  LIFECYCLE_EVENT_NAMES,
6631
7374
  MatchBuilder,
7375
+ NANOID_ALPHABET,
6632
7376
  NON_NEGOTIABLE_CREDENTIAL_FIELDS,
6633
7377
  NotFoundError,
6634
7378
  PII_REGEX_PATTERNS,
7379
+ RESERVED_NAVIGATION_SLUGS,
6635
7380
  ValidationError,
6636
7381
  WORKFLOW_HISTORY_COLLECTION,
6637
7382
  abs,
@@ -6642,6 +7387,7 @@ function defineTab(args) {
6642
7387
  assertValidDeclarativeHookExpression,
6643
7388
  assertValidDeclarativeHookResult,
6644
7389
  assertValidDeclarativeHooksInConfig,
7390
+ assertValidNavigationSlugs,
6645
7391
  assertValidPreviewUrlsInConfig,
6646
7392
  availableWorkflowTransitions,
6647
7393
  buildSqlOrderBy,
@@ -6656,6 +7402,7 @@ function defineTab(args) {
6656
7402
  collectDeclarativeHookValidationIssues,
6657
7403
  collectPreviewUrlValidationIssues,
6658
7404
  compact,
7405
+ compileNavigation,
6659
7406
  count,
6660
7407
  createLifecycleEvent,
6661
7408
  createWorkflowDocument,
@@ -6696,6 +7443,7 @@ function defineTab(args) {
6696
7443
  defineTimeField,
6697
7444
  defineUrlField,
6698
7445
  defineView,
7446
+ defineWorkspace,
6699
7447
  diffDays,
6700
7448
  dispatchLifecycleEvent,
6701
7449
  dispatchPendingLifecycleEvents,
@@ -6724,6 +7472,8 @@ function defineTab(args) {
6724
7472
  formatDate,
6725
7473
  formatJexlValue,
6726
7474
  generateDefaultDetailSchema,
7475
+ generateDocumentId,
7476
+ generateId,
6727
7477
  generateOpenApi,
6728
7478
  get,
6729
7479
  getAdminAuthCollection,
@@ -6750,6 +7500,7 @@ function defineTab(args) {
6750
7500
  materializeWorkflowDocument,
6751
7501
  max,
6752
7502
  min,
7503
+ nanoid,
6753
7504
  normalizeConfig,
6754
7505
  normalizeDetailItem,
6755
7506
  now,
@@ -6758,13 +7509,17 @@ function defineTab(args) {
6758
7509
  parseSort,
6759
7510
  parseSqlWhere,
6760
7511
  pow,
7512
+ prefixedId,
7513
+ pruneNavigationForUser,
6761
7514
  publishedStateName,
6762
7515
  publishingWorkflow,
6763
7516
  readingTime,
7517
+ reconcileNavigation,
6764
7518
  registerJexlHelpers,
6765
7519
  replace,
6766
7520
  resolveAdminAuthCollection,
6767
7521
  resolveAuthorizedSiteId,
7522
+ resolvePrefix,
6768
7523
  round,
6769
7524
  runCollectionHooks,
6770
7525
  sanitizeDocForAI,
@@ -6778,8 +7533,10 @@ function defineTab(args) {
6778
7533
  transitionWorkflow,
6779
7534
  trim,
6780
7535
  truncate,
7536
+ ulid,
6781
7537
  unique,
6782
7538
  upper,
7539
+ uuid,
6783
7540
  validateAdminConditionExpression,
6784
7541
  validateDeclarativeAccessExpression,
6785
7542
  validateDeclarativeHookExpression,