@dyrected/core 2.22.0 → 2.24.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/server.js CHANGED
@@ -1782,6 +1782,243 @@ var require_Jexl = __commonJS({
1782
1782
  }
1783
1783
  });
1784
1784
 
1785
+ // src/types/access.ts
1786
+ var init_access = __esm({
1787
+ "src/types/access.ts"() {
1788
+ "use strict";
1789
+ }
1790
+ });
1791
+
1792
+ // src/types/adapters.ts
1793
+ var init_adapters = __esm({
1794
+ "src/types/adapters.ts"() {
1795
+ "use strict";
1796
+ }
1797
+ });
1798
+
1799
+ // src/types/aggregate.ts
1800
+ var init_aggregate = __esm({
1801
+ "src/types/aggregate.ts"() {
1802
+ "use strict";
1803
+ }
1804
+ });
1805
+
1806
+ // src/types/admin.ts
1807
+ var FORM_SHELLS;
1808
+ var init_admin = __esm({
1809
+ "src/types/admin.ts"() {
1810
+ "use strict";
1811
+ FORM_SHELLS = ["full", "header", "none"];
1812
+ }
1813
+ });
1814
+
1815
+ // src/types/admin-auth.ts
1816
+ var init_admin_auth = __esm({
1817
+ "src/types/admin-auth.ts"() {
1818
+ "use strict";
1819
+ }
1820
+ });
1821
+
1822
+ // src/types/app-config.ts
1823
+ var init_app_config = __esm({
1824
+ "src/types/app-config.ts"() {
1825
+ "use strict";
1826
+ }
1827
+ });
1828
+
1829
+ // src/types/documents.ts
1830
+ var init_documents = __esm({
1831
+ "src/types/documents.ts"() {
1832
+ "use strict";
1833
+ }
1834
+ });
1835
+
1836
+ // src/types/hooks.ts
1837
+ var init_hooks = __esm({
1838
+ "src/types/hooks.ts"() {
1839
+ "use strict";
1840
+ }
1841
+ });
1842
+
1843
+ // src/types/request.ts
1844
+ var init_request = __esm({
1845
+ "src/types/request.ts"() {
1846
+ "use strict";
1847
+ }
1848
+ });
1849
+
1850
+ // src/types/schema-config.ts
1851
+ var init_schema_config = __esm({
1852
+ "src/types/schema-config.ts"() {
1853
+ "use strict";
1854
+ }
1855
+ });
1856
+
1857
+ // src/types/schema-core.ts
1858
+ var init_schema_core = __esm({
1859
+ "src/types/schema-core.ts"() {
1860
+ "use strict";
1861
+ }
1862
+ });
1863
+
1864
+ // src/types/schema-inference.ts
1865
+ var init_schema_inference = __esm({
1866
+ "src/types/schema-inference.ts"() {
1867
+ "use strict";
1868
+ }
1869
+ });
1870
+
1871
+ // src/types/workflows.ts
1872
+ var init_workflows = __esm({
1873
+ "src/types/workflows.ts"() {
1874
+ "use strict";
1875
+ }
1876
+ });
1877
+
1878
+ // src/types/tasks.ts
1879
+ var init_tasks = __esm({
1880
+ "src/types/tasks.ts"() {
1881
+ "use strict";
1882
+ }
1883
+ });
1884
+
1885
+ // src/types/detail.ts
1886
+ var init_detail = __esm({
1887
+ "src/types/detail.ts"() {
1888
+ "use strict";
1889
+ }
1890
+ });
1891
+
1892
+ // src/types/views.ts
1893
+ var init_views = __esm({
1894
+ "src/types/views.ts"() {
1895
+ "use strict";
1896
+ }
1897
+ });
1898
+
1899
+ // src/types/ai.ts
1900
+ var ai_exports = {};
1901
+ __export(ai_exports, {
1902
+ AI_ACTIONS_COLLECTION: () => AI_ACTIONS_COLLECTION,
1903
+ AI_AUDIT_COLLECTION: () => AI_AUDIT_COLLECTION,
1904
+ AI_CHUNKS_COLLECTION: () => AI_CHUNKS_COLLECTION,
1905
+ AI_MESSAGES_COLLECTION: () => AI_MESSAGES_COLLECTION,
1906
+ AI_THREADS_COLLECTION: () => AI_THREADS_COLLECTION,
1907
+ isAICollection: () => isAICollection
1908
+ });
1909
+ function isAICollection(slug) {
1910
+ return slug === AI_THREADS_COLLECTION || slug === AI_MESSAGES_COLLECTION || slug === AI_CHUNKS_COLLECTION || slug === AI_ACTIONS_COLLECTION || slug === AI_AUDIT_COLLECTION;
1911
+ }
1912
+ var AI_THREADS_COLLECTION, AI_MESSAGES_COLLECTION, AI_CHUNKS_COLLECTION, AI_ACTIONS_COLLECTION, AI_AUDIT_COLLECTION;
1913
+ var init_ai = __esm({
1914
+ "src/types/ai.ts"() {
1915
+ "use strict";
1916
+ AI_THREADS_COLLECTION = "_dyrected_ai_threads";
1917
+ AI_MESSAGES_COLLECTION = "_dyrected_ai_messages";
1918
+ AI_CHUNKS_COLLECTION = "_dyrected_ai_chunks";
1919
+ AI_ACTIONS_COLLECTION = "_dyrected_ai_actions";
1920
+ AI_AUDIT_COLLECTION = "_dyrected_ai_audit";
1921
+ }
1922
+ });
1923
+
1924
+ // src/types/ai-errors.ts
1925
+ var DyrectedAIError;
1926
+ var init_ai_errors = __esm({
1927
+ "src/types/ai-errors.ts"() {
1928
+ "use strict";
1929
+ DyrectedAIError = class _DyrectedAIError extends Error {
1930
+ code;
1931
+ status;
1932
+ details;
1933
+ constructor(code, message, status = 500, details) {
1934
+ super(message);
1935
+ this.name = "DyrectedAIError";
1936
+ this.code = code;
1937
+ this.status = status;
1938
+ this.details = details;
1939
+ Object.setPrototypeOf(this, _DyrectedAIError.prototype);
1940
+ }
1941
+ toJSON() {
1942
+ return {
1943
+ error: true,
1944
+ code: this.code,
1945
+ message: this.message,
1946
+ status: this.status,
1947
+ details: this.details
1948
+ };
1949
+ }
1950
+ };
1951
+ }
1952
+ });
1953
+
1954
+ // src/types/readonly-fix.ts
1955
+ var init_readonly_fix = __esm({
1956
+ "src/types/readonly-fix.ts"() {
1957
+ "use strict";
1958
+ }
1959
+ });
1960
+
1961
+ // src/types/navigation.ts
1962
+ var CURRENT_NAV_PREFERENCES_VERSION, DEFAULT_USER_NAV_PREFERENCES;
1963
+ var init_navigation = __esm({
1964
+ "src/types/navigation.ts"() {
1965
+ "use strict";
1966
+ CURRENT_NAV_PREFERENCES_VERSION = 1;
1967
+ DEFAULT_USER_NAV_PREFERENCES = {
1968
+ _version: CURRENT_NAV_PREFERENCES_VERSION,
1969
+ pinned: [],
1970
+ hidden: [],
1971
+ groupOrder: [],
1972
+ itemOrder: {},
1973
+ groups: [],
1974
+ items: []
1975
+ };
1976
+ }
1977
+ });
1978
+
1979
+ // src/types/email.ts
1980
+ var init_email = __esm({
1981
+ "src/types/email.ts"() {
1982
+ "use strict";
1983
+ }
1984
+ });
1985
+
1986
+ // src/types/pages.ts
1987
+ var init_pages = __esm({
1988
+ "src/types/pages.ts"() {
1989
+ "use strict";
1990
+ }
1991
+ });
1992
+
1993
+ // src/types/index.ts
1994
+ var init_types = __esm({
1995
+ "src/types/index.ts"() {
1996
+ "use strict";
1997
+ init_access();
1998
+ init_adapters();
1999
+ init_aggregate();
2000
+ init_admin();
2001
+ init_admin_auth();
2002
+ init_app_config();
2003
+ init_documents();
2004
+ init_hooks();
2005
+ init_request();
2006
+ init_schema_config();
2007
+ init_schema_core();
2008
+ init_schema_inference();
2009
+ init_workflows();
2010
+ init_tasks();
2011
+ init_detail();
2012
+ init_views();
2013
+ init_ai();
2014
+ init_ai_errors();
2015
+ init_readonly_fix();
2016
+ init_navigation();
2017
+ init_email();
2018
+ init_pages();
2019
+ }
2020
+ });
2021
+
1785
2022
  // src/utils/jexl-helpers.ts
1786
2023
  function slugify(val) {
1787
2024
  if (val === void 0 || val === null) return "";
@@ -2585,6 +2822,52 @@ function collectPreviewUrlValidationIssues(config) {
2585
2822
  });
2586
2823
  return issues;
2587
2824
  }
2825
+ function collectFormComponentIssues(components, path, surface, keys, issues) {
2826
+ if (!isPlainObject(components)) return;
2827
+ for (const key of keys) {
2828
+ const value = components[key];
2829
+ if (value === void 0) continue;
2830
+ if (typeof value !== "string" || value.length === 0) {
2831
+ issues.push({
2832
+ path: `${path}.admin.components.${key}`,
2833
+ surface,
2834
+ message: `\`${key}\` must be the registry key of a component, such as "check_ins.scanForm".`,
2835
+ expression: String(value)
2836
+ });
2837
+ }
2838
+ }
2839
+ const shell = components.formShell;
2840
+ if (shell !== void 0 && !FORM_SHELLS.includes(shell)) {
2841
+ issues.push({
2842
+ path: `${path}.admin.components.formShell`,
2843
+ surface,
2844
+ message: `Unknown formShell "${String(shell)}". Use ${FORM_SHELLS.map((value) => `"${value}"`).join(", ")}.`,
2845
+ expression: String(shell)
2846
+ });
2847
+ }
2848
+ }
2849
+ function collectAdminComponentsValidationIssues(config) {
2850
+ const issues = [];
2851
+ config.collections.forEach((collection, index) => {
2852
+ collectFormComponentIssues(
2853
+ collection.admin?.components,
2854
+ collectionSegment(collection, index),
2855
+ "collection.admin.components",
2856
+ FORM_COMPONENT_KEYS,
2857
+ issues
2858
+ );
2859
+ });
2860
+ config.globals?.forEach((global, index) => {
2861
+ collectFormComponentIssues(
2862
+ global.admin?.components,
2863
+ globalSegment(global, index),
2864
+ "global.admin.components",
2865
+ GLOBAL_FORM_COMPONENT_KEYS,
2866
+ issues
2867
+ );
2868
+ });
2869
+ return issues;
2870
+ }
2588
2871
  function assertValidAdminConditionsInConfig(config, source = "config") {
2589
2872
  const diagnostics = collectAdminConditionValidationIssues(config).map((issue) => ({
2590
2873
  severity: "error",
@@ -2642,7 +2925,15 @@ function collectConfigDiagnostics(config) {
2642
2925
  message: issue.message,
2643
2926
  expression: issue.expression
2644
2927
  }));
2645
- return [...hookIssues, ...accessIssues, ...adminConditionIssues, ...previewUrlIssues];
2928
+ const adminComponentIssues = collectAdminComponentsValidationIssues(config).map((issue) => ({
2929
+ severity: "error",
2930
+ source: "adminComponents",
2931
+ path: issue.path,
2932
+ surface: issue.surface,
2933
+ message: issue.message,
2934
+ expression: issue.expression
2935
+ }));
2936
+ return [...hookIssues, ...accessIssues, ...adminConditionIssues, ...previewUrlIssues, ...adminComponentIssues];
2646
2937
  }
2647
2938
  function formatConfigDiagnostics(source, diagnostics, options = {}) {
2648
2939
  const color = options.color ?? false;
@@ -2682,11 +2973,12 @@ function assertValidDeclarativeAccessInConfig(config, source = "config") {
2682
2973
  if (diagnostics.length === 0) return;
2683
2974
  throw new ConfigValidationError(source, diagnostics);
2684
2975
  }
2685
- var import_jexl3, SURFACE_ROOTS, ACCESS_ROOTS, ADMIN_CONDITION_RESERVED_ROOTS, PREVIEW_URL_RESERVED_ROOTS, COMPILE_CACHE, ConfigValidationError;
2976
+ var import_jexl3, SURFACE_ROOTS, ACCESS_ROOTS, ADMIN_CONDITION_RESERVED_ROOTS, PREVIEW_URL_RESERVED_ROOTS, COMPILE_CACHE, FORM_COMPONENT_KEYS, GLOBAL_FORM_COMPONENT_KEYS, ConfigValidationError;
2686
2977
  var init_declarative_hooks = __esm({
2687
2978
  "src/utils/declarative-hooks.ts"() {
2688
2979
  "use strict";
2689
2980
  import_jexl3 = __toESM(require_Jexl(), 1);
2981
+ init_types();
2690
2982
  init_jexl_helpers();
2691
2983
  registerJexlHelpers(import_jexl3.default);
2692
2984
  SURFACE_ROOTS = {
@@ -2703,6 +2995,8 @@ var init_declarative_hooks = __esm({
2703
2995
  ADMIN_CONDITION_RESERVED_ROOTS = ["data", "siblingData", "user", "id", ...BUILTIN_JEXL_HELPERS];
2704
2996
  PREVIEW_URL_RESERVED_ROOTS = ["siteUrl", "id", ...BUILTIN_JEXL_HELPERS];
2705
2997
  COMPILE_CACHE = /* @__PURE__ */ new Map();
2998
+ FORM_COMPONENT_KEYS = ["createForm", "editForm", "form"];
2999
+ GLOBAL_FORM_COMPONENT_KEYS = ["editForm", "form"];
2706
3000
  ConfigValidationError = class extends Error {
2707
3001
  diagnostics;
2708
3002
  source;
@@ -2915,7 +3209,7 @@ async function executeFieldAfterRead(fields, doc, user, db) {
2915
3209
  return result;
2916
3210
  }
2917
3211
  var import_jexl4;
2918
- var init_hooks = __esm({
3212
+ var init_hooks2 = __esm({
2919
3213
  "src/utils/hooks.ts"() {
2920
3214
  "use strict";
2921
3215
  import_jexl4 = __toESM(require_Jexl(), 1);
@@ -2992,7 +3286,7 @@ async function isAccessAllowed(config, access, args) {
2992
3286
  if (result === void 0) return true;
2993
3287
  return typeof result === "boolean" ? result : !!result;
2994
3288
  }
2995
- var init_access = __esm({
3289
+ var init_access2 = __esm({
2996
3290
  "src/auth/access.ts"() {
2997
3291
  "use strict";
2998
3292
  init_jexl();
@@ -3172,7 +3466,7 @@ var TRASH_COLLECTION, INTERNAL_SYSTEM_COLLECTIONS, TRASH_COLLECTION_CONFIG;
3172
3466
  var init_trash = __esm({
3173
3467
  "src/trash.ts"() {
3174
3468
  "use strict";
3175
- init_hooks();
3469
+ init_hooks2();
3176
3470
  TRASH_COLLECTION = "__trash";
3177
3471
  INTERNAL_SYSTEM_COLLECTIONS = /* @__PURE__ */ new Set([
3178
3472
  "__audit",
@@ -3398,32 +3692,7 @@ function mergeWhereConstraint(where, constraint) {
3398
3692
  var init_access_control = __esm({
3399
3693
  "src/utils/access-control.ts"() {
3400
3694
  "use strict";
3401
- init_access();
3402
- }
3403
- });
3404
-
3405
- // src/types/ai.ts
3406
- var ai_exports = {};
3407
- __export(ai_exports, {
3408
- AI_ACTIONS_COLLECTION: () => AI_ACTIONS_COLLECTION,
3409
- AI_AUDIT_COLLECTION: () => AI_AUDIT_COLLECTION,
3410
- AI_CHUNKS_COLLECTION: () => AI_CHUNKS_COLLECTION,
3411
- AI_MESSAGES_COLLECTION: () => AI_MESSAGES_COLLECTION,
3412
- AI_THREADS_COLLECTION: () => AI_THREADS_COLLECTION,
3413
- isAICollection: () => isAICollection
3414
- });
3415
- function isAICollection(slug) {
3416
- return slug === AI_THREADS_COLLECTION || slug === AI_MESSAGES_COLLECTION || slug === AI_CHUNKS_COLLECTION || slug === AI_ACTIONS_COLLECTION || slug === AI_AUDIT_COLLECTION;
3417
- }
3418
- var AI_THREADS_COLLECTION, AI_MESSAGES_COLLECTION, AI_CHUNKS_COLLECTION, AI_ACTIONS_COLLECTION, AI_AUDIT_COLLECTION;
3419
- var init_ai = __esm({
3420
- "src/types/ai.ts"() {
3421
- "use strict";
3422
- AI_THREADS_COLLECTION = "_dyrected_ai_threads";
3423
- AI_MESSAGES_COLLECTION = "_dyrected_ai_messages";
3424
- AI_CHUNKS_COLLECTION = "_dyrected_ai_chunks";
3425
- AI_ACTIONS_COLLECTION = "_dyrected_ai_actions";
3426
- AI_AUDIT_COLLECTION = "_dyrected_ai_audit";
3695
+ init_access2();
3427
3696
  }
3428
3697
  });
3429
3698
 
@@ -3972,7 +4241,7 @@ var init_rag_service = __esm({
3972
4241
  "src/services/rag/rag.service.ts"() {
3973
4242
  "use strict";
3974
4243
  init_ai();
3975
- init_access();
4244
+ init_access2();
3976
4245
  init_normalizer();
3977
4246
  init_chunker();
3978
4247
  init_embedding_service();
@@ -5409,7 +5678,7 @@ var init_ai_tools = __esm({
5409
5678
  "src/services/ai-tools.ts"() {
5410
5679
  "use strict";
5411
5680
  init_ai();
5412
- init_access();
5681
+ init_access2();
5413
5682
  init_access_control();
5414
5683
  init_rag_service();
5415
5684
  init_ai_pii();
@@ -6441,7 +6710,7 @@ function createSessionId() {
6441
6710
  }
6442
6711
 
6443
6712
  // src/controllers/collection.controller.ts
6444
- init_hooks();
6713
+ init_hooks2();
6445
6714
 
6446
6715
  // src/utils/readonly-db.ts
6447
6716
  var WRITE_METHODS = ["create", "update", "delete", "updateGlobal", "execute"];
@@ -6569,7 +6838,7 @@ function generateUniqueUploadFilename(filename) {
6569
6838
  }
6570
6839
 
6571
6840
  // src/controllers/collection.controller.ts
6572
- init_access();
6841
+ init_access2();
6573
6842
 
6574
6843
  // src/utils/admin-auth.ts
6575
6844
  function getAdminAuthCollection(config) {
@@ -6643,20 +6912,12 @@ function isUserAdmin(user, collection) {
6643
6912
  }
6644
6913
 
6645
6914
  // src/utils/collection-search.ts
6646
- var DEFAULT_SEARCHABLE_NAMES = /* @__PURE__ */ new Set([
6647
- "title",
6648
- "name",
6649
- "label",
6650
- "slug",
6651
- "email",
6652
- "caption",
6653
- "description"
6654
- ]);
6655
6915
  var MAX_RELATION_MATCHES = 50;
6916
+ var MAX_DEFAULT_SEARCH_FIELDS = 8;
6656
6917
  var MAX_RELATION_TITLE_DEPTH = 8;
6657
6918
  var TITLE_FALLBACK_NAMES = ["title", "name", "label", "heading", "email", "subject"];
6658
6919
  function isScalarSearchField(field) {
6659
- return field.type === "text" || field.type === "textarea" || field.type === "richText" || field.type === "email";
6920
+ return field.type === "text" || field.type === "textarea" || field.type === "richText" || field.type === "email" || field.type === "url";
6660
6921
  }
6661
6922
  function findFieldByName(collection, fieldName) {
6662
6923
  if (!fieldName) return void 0;
@@ -6671,10 +6932,20 @@ function resolveCollectionTitleField(collection) {
6671
6932
  }
6672
6933
  return collection.fields.find((field) => !!field.name && field.type !== "join" && field.type !== "row");
6673
6934
  }
6935
+ function flattenNamedFields(fields) {
6936
+ const result = [];
6937
+ for (const field of fields) {
6938
+ if (field.type === "row" && field.fields) {
6939
+ result.push(...flattenNamedFields(field.fields));
6940
+ continue;
6941
+ }
6942
+ if (field.name) result.push(field);
6943
+ }
6944
+ return result;
6945
+ }
6674
6946
  function getCollectionSearchableFields(collection) {
6675
- const fieldByName = new Map(
6676
- collection.fields.filter((field) => !!field.name).map((field) => [field.name, field])
6677
- );
6947
+ const namedFields = flattenNamedFields(collection.fields);
6948
+ const fieldByName = new Map(namedFields.map((field) => [field.name, field]));
6678
6949
  const configured = collection.admin?.searchableFields?.map((fieldName) => fieldByName.get(fieldName)).filter((field) => !!field);
6679
6950
  if (configured && configured.length > 0) {
6680
6951
  return configured;
@@ -6685,12 +6956,12 @@ function getCollectionSearchableFields(collection) {
6685
6956
  if (results.some((item) => item.name === field.name)) return;
6686
6957
  results.push(field);
6687
6958
  };
6688
- pushUnique(findFieldByName(collection, collection.admin?.useAsTitle));
6689
- for (const field of collection.fields) {
6690
- if (!field.name) continue;
6691
- if (isScalarSearchField(field) && DEFAULT_SEARCHABLE_NAMES.has(field.name)) {
6692
- pushUnique(field);
6693
- }
6959
+ const titleField = collection.admin?.useAsTitle ? fieldByName.get(collection.admin.useAsTitle) : void 0;
6960
+ pushUnique(titleField);
6961
+ for (const field of namedFields) {
6962
+ if (results.length >= MAX_DEFAULT_SEARCH_FIELDS) break;
6963
+ if (field.admin?.hidden || !isScalarSearchField(field)) continue;
6964
+ pushUnique(field);
6694
6965
  }
6695
6966
  return results;
6696
6967
  }
@@ -6830,19 +7101,8 @@ async function buildCollectionSearchWhere(args) {
6830
7101
  // src/utils/navigation.ts
6831
7102
  init_trash();
6832
7103
 
6833
- // src/types/navigation.ts
6834
- var CURRENT_NAV_PREFERENCES_VERSION = 1;
6835
- var DEFAULT_USER_NAV_PREFERENCES = {
6836
- _version: CURRENT_NAV_PREFERENCES_VERSION,
6837
- pinned: [],
6838
- hidden: [],
6839
- groupOrder: [],
6840
- itemOrder: {},
6841
- groups: [],
6842
- items: []
6843
- };
6844
-
6845
7104
  // src/utils/navigation-reconciler.ts
7105
+ init_navigation();
6846
7106
  var normalizeKey = (val) => (val || "").toLowerCase().replace(/[^a-z0-9]/g, "-");
6847
7107
  function reconcileNavigation(baseTree, prefs, schemas, options) {
6848
7108
  if (!baseTree) {
@@ -7115,11 +7375,32 @@ var RESERVED_NAVIGATION_SLUGS = /* @__PURE__ */ new Set([
7115
7375
  "admin",
7116
7376
  "preferences",
7117
7377
  "auth",
7118
- "trash"
7378
+ "trash",
7379
+ "pages"
7119
7380
  ]);
7120
7381
  function assertValidNavigationSlugs(navigation) {
7382
+ const seenWorkspaceSlugs = /* @__PURE__ */ new Set();
7383
+ const seenPageSlugs = /* @__PURE__ */ new Set();
7121
7384
  for (const item of navigation) {
7122
7385
  if (item.dashboard || item.trash) continue;
7386
+ if (item.page) {
7387
+ const pageSlug = typeof item.page === "string" ? item.slug || item.page : item.slug || item.page.slug;
7388
+ if (pageSlug) {
7389
+ const normalized = pageSlug.toLowerCase().trim().replace(/^\//, "");
7390
+ if (RESERVED_NAVIGATION_SLUGS.has(normalized)) {
7391
+ throw new Error(
7392
+ `[Dyrected Config] The slug '${pageSlug}' in custom page '${item.label || pageSlug}' is a reserved system path and cannot be used as a page slug.`
7393
+ );
7394
+ }
7395
+ if (seenPageSlugs.has(normalized)) {
7396
+ throw new Error(
7397
+ `[Dyrected Config] Duplicate page slug '${pageSlug}' in navigation. Page slugs must be unique.`
7398
+ );
7399
+ }
7400
+ seenPageSlugs.add(normalized);
7401
+ }
7402
+ continue;
7403
+ }
7123
7404
  if (item.slug) {
7124
7405
  const normalizedSlug = item.slug.toLowerCase().trim().replace(/^\//, "");
7125
7406
  if (RESERVED_NAVIGATION_SLUGS.has(normalizedSlug)) {
@@ -7127,6 +7408,12 @@ function assertValidNavigationSlugs(navigation) {
7127
7408
  `[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.`
7128
7409
  );
7129
7410
  }
7411
+ if (seenWorkspaceSlugs.has(normalizedSlug)) {
7412
+ throw new Error(
7413
+ `[Dyrected Config] Duplicate workspace slug '${item.slug}' in navigation. Workspace slugs must be unique.`
7414
+ );
7415
+ }
7416
+ seenWorkspaceSlugs.add(normalizedSlug);
7130
7417
  }
7131
7418
  }
7132
7419
  }
@@ -7269,6 +7556,43 @@ function compileNavigation(config) {
7269
7556
  });
7270
7557
  continue;
7271
7558
  }
7559
+ if (item.page) {
7560
+ const pageConfig = typeof item.page === "string" ? {
7561
+ slug: item.slug || item.page,
7562
+ label: item.label || item.page,
7563
+ icon: item.icon,
7564
+ component: item.page,
7565
+ group: typeof item.group === "string" ? item.group : item.group?.name,
7566
+ order: item.order,
7567
+ badge: item.badge,
7568
+ access: item.access
7569
+ } : {
7570
+ ...item.page,
7571
+ slug: item.slug || item.page.slug || "page",
7572
+ label: item.label || item.page.label || item.slug || "Page",
7573
+ icon: item.icon || item.page.icon,
7574
+ group: item.group ? typeof item.group === "string" ? item.group : item.group?.name : item.page.group,
7575
+ order: item.order ?? item.page.order,
7576
+ badge: item.badge || item.page.badge,
7577
+ access: item.access || (item.page.access && typeof item.page.access === "object" && "read" in item.page.access ? item.page.access.read : item.page.access)
7578
+ };
7579
+ const pageSlug = pageConfig.slug || "page";
7580
+ registerItem(groupName, {
7581
+ id: `page:${pageSlug}`,
7582
+ type: "page",
7583
+ slug: pageSlug,
7584
+ label: pageConfig.label || pageSlug,
7585
+ icon: pageConfig.icon || "FileText",
7586
+ href: `/pages/${pageSlug}`,
7587
+ group: groupName,
7588
+ order: pageConfig.order ?? 100,
7589
+ views: [],
7590
+ badge: pageConfig.badge,
7591
+ access: pageConfig.access && typeof pageConfig.access === "object" && "read" in pageConfig.access ? pageConfig.access.read : pageConfig.access,
7592
+ page: pageConfig
7593
+ });
7594
+ continue;
7595
+ }
7272
7596
  const workspaceSlug = item.slug || "workspace";
7273
7597
  registerItem(groupName, {
7274
7598
  id: `workspace_${workspaceSlug}`,
@@ -10548,7 +10872,7 @@ var CollectionController = class _CollectionController {
10548
10872
  };
10549
10873
 
10550
10874
  // src/controllers/global.controller.ts
10551
- init_hooks();
10875
+ init_hooks2();
10552
10876
  init_access_control();
10553
10877
  import { HTTPException as HTTPException3 } from "hono/http-exception";
10554
10878
  var GlobalController = class {
@@ -14725,7 +15049,7 @@ var AIController = class {
14725
15049
  const dynamicConfig = await activeConfig.onSchemaFetch(projectId);
14726
15050
  activeConfig = mergeDynamicConfig(activeConfig, dynamicConfig);
14727
15051
  }
14728
- const { isAccessAllowed: isAccessAllowed2 } = await Promise.resolve().then(() => (init_access(), access_exports));
15052
+ const { isAccessAllowed: isAccessAllowed2 } = await Promise.resolve().then(() => (init_access2(), access_exports));
14729
15053
  const { validatePayloadAgainstFields: validatePayloadAgainstFields2 } = await Promise.resolve().then(() => (init_ai_tools(), ai_tools_exports));
14730
15054
  if (action.targetCollection) {
14731
15055
  const col = activeConfig.collections?.find((c2) => c2.slug === action.targetCollection);
@@ -15100,31 +15424,8 @@ function optionalAuth(config) {
15100
15424
  };
15101
15425
  }
15102
15426
 
15103
- // src/types/ai-errors.ts
15104
- var DyrectedAIError = class _DyrectedAIError extends Error {
15105
- code;
15106
- status;
15107
- details;
15108
- constructor(code, message, status = 500, details) {
15109
- super(message);
15110
- this.name = "DyrectedAIError";
15111
- this.code = code;
15112
- this.status = status;
15113
- this.details = details;
15114
- Object.setPrototypeOf(this, _DyrectedAIError.prototype);
15115
- }
15116
- toJSON() {
15117
- return {
15118
- error: true,
15119
- code: this.code,
15120
- message: this.message,
15121
- status: this.status,
15122
- details: this.details
15123
- };
15124
- }
15125
- };
15126
-
15127
15427
  // src/middleware/ai-rate-limit.ts
15428
+ init_ai_errors();
15128
15429
  var InMemoryRateLimitStore = class {
15129
15430
  windows = /* @__PURE__ */ new Map();
15130
15431
  constructor() {
@@ -16776,6 +17077,9 @@ async function serializeViewForApi(view, serializeAccess) {
16776
17077
  dateField: view.dateField,
16777
17078
  startDateField: view.startDateField,
16778
17079
  endDateField: view.endDateField,
17080
+ component: view.layout === "custom" ? view.component : void 0,
17081
+ options: view.layout === "custom" ? view.options : void 0,
17082
+ data: view.layout === "custom" ? view.data : void 0,
16779
17083
  columns: view.columns,
16780
17084
  sort: view.sort,
16781
17085
  metrics: view.metrics,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dyrected/core",
3
- "version": "2.22.0",
3
+ "version": "2.24.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",