@nestledjs/data-browser 1.0.14 → 1.0.16

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.
Files changed (34) hide show
  1. package/README.md +43 -39
  2. package/index.js +6 -1
  3. package/lib/components/ExportButton.d.ts +14 -0
  4. package/lib/components/ExportButton.js +281 -0
  5. package/lib/components/FilterField.js +1 -2
  6. package/lib/components/RelationFieldWrapper.d.ts +1 -1
  7. package/lib/components/RelationFieldWrapper.js +2 -2
  8. package/lib/components/filters/DateRangeFilter.d.ts +1 -1
  9. package/lib/components/filters/DateRangeFilter.js +2 -2
  10. package/lib/components/filters/EnumFilter.d.ts +1 -1
  11. package/lib/components/filters/NumberRangeFilter.d.ts +1 -1
  12. package/lib/components/filters/NumberRangeFilter.js +2 -2
  13. package/lib/components/filters/RelationComponents.d.ts +5 -5
  14. package/lib/components/filters/RelationComponents.js +40 -13
  15. package/lib/components/filters/RelationFilterField.d.ts +1 -1
  16. package/lib/components/filters/RelationFilterField.js +18 -10
  17. package/lib/components/index.d.ts +1 -0
  18. package/lib/components/shared/AdminBreadcrumbs.js +2 -17
  19. package/lib/components/shared/AdminErrorStates.js +6 -1
  20. package/lib/components/shared/AdminStatusDisplay.js +7 -11
  21. package/lib/context/AdminDataContext.d.ts +2 -2
  22. package/lib/hooks/useAdminList.js +1 -1
  23. package/lib/hooks/useClickOutside.js +1 -1
  24. package/lib/hooks/useRelationData.js +2 -1
  25. package/lib/layouts/AdminDataLayout.js +87 -14
  26. package/lib/pages/AdminDataCreatePage.d.ts +1 -1
  27. package/lib/pages/AdminDataCreatePage.js +68 -45
  28. package/lib/pages/AdminDataEditPage.js +71 -38
  29. package/lib/pages/AdminDataListPage.js +116 -85
  30. package/lib/utils/graphql-utils.d.ts +7 -1
  31. package/lib/utils/graphql-utils.js +343 -331
  32. package/lib/utils/secure-storage.js +26 -20
  33. package/lib/utils/string-utils.js +31 -8
  34. package/package.json +2 -2
@@ -3,7 +3,7 @@ const ADMIN_CONFIG_VERSION = "1.0";
3
3
  const MAX_CONFIG_SIZE = 5e4;
4
4
  const sanitizeString = (value) => {
5
5
  if (typeof value !== "string") return "";
6
- return value.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "").replace(/javascript:/gi, "").replace(/on\w+\s*=/gi, "").trim().substring(0, 1e3);
6
+ return value.replaceAll(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "").replaceAll(/javascript:/gi, "").replaceAll(/on\w+\s*=/gi, "").trim().substring(0, 1e3);
7
7
  };
8
8
  const sanitizeArray = (value) => {
9
9
  if (!Array.isArray(value)) return [];
@@ -15,9 +15,21 @@ const sanitizeSortPreference = (value) => {
15
15
  const orderBy = sanitizeString(obj.orderBy);
16
16
  const orderDirection = sanitizeString(obj.orderDirection);
17
17
  if (!["asc", "desc"].includes(orderDirection)) return null;
18
- if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(orderBy)) return null;
18
+ if (!/^[a-zA-Z_]\w*$/.test(orderBy)) return null;
19
19
  return { orderBy, orderDirection };
20
20
  };
21
+ const isValidModelName = (name) => /^[a-zA-Z_]\w*$/.test(name);
22
+ const isValidModelConfig = (modelConfig) => {
23
+ if (!modelConfig || typeof modelConfig !== "object") return true;
24
+ const cfg = modelConfig;
25
+ if (cfg.visibleColumns !== void 0 && !Array.isArray(cfg.visibleColumns)) return false;
26
+ if (cfg.sortPreference !== void 0) {
27
+ const sortPref = sanitizeSortPreference(cfg.sortPreference);
28
+ if (cfg.sortPreference !== null && sortPref === null) return false;
29
+ }
30
+ if (cfg.searchFields !== void 0 && !Array.isArray(cfg.searchFields)) return false;
31
+ return true;
32
+ };
21
33
  const validateAdminConfig = (config) => {
22
34
  if (!config || typeof config !== "object") return false;
23
35
  const configObj = config;
@@ -25,16 +37,8 @@ const validateAdminConfig = (config) => {
25
37
  if (!configObj.models || typeof configObj.models !== "object") return false;
26
38
  const models = configObj.models;
27
39
  for (const [modelName, modelConfig] of Object.entries(models)) {
28
- if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(modelName)) return false;
29
- if (modelConfig && typeof modelConfig === "object") {
30
- const config2 = modelConfig;
31
- if (config2.visibleColumns !== void 0 && !Array.isArray(config2.visibleColumns)) return false;
32
- if (config2.sortPreference !== void 0) {
33
- const sortPref = sanitizeSortPreference(config2.sortPreference);
34
- if (config2.sortPreference !== null && sortPref === null) return false;
35
- }
36
- if (config2.searchFields !== void 0 && !Array.isArray(config2.searchFields)) return false;
37
- }
40
+ if (!isValidModelName(modelName)) return false;
41
+ if (!isValidModelConfig(modelConfig)) return false;
38
42
  }
39
43
  return true;
40
44
  };
@@ -56,10 +60,11 @@ const SecureAdminLocalStorage = {
56
60
  return { version: ADMIN_CONFIG_VERSION, models: {} };
57
61
  }
58
62
  return parsed;
59
- } catch (error) {
63
+ } catch {
60
64
  try {
61
65
  localStorage.removeItem(ADMIN_CONFIG_KEY);
62
- } catch {
66
+ } catch (cleanupError) {
67
+ console.error("Unexpected error:", cleanupError);
63
68
  }
64
69
  return { version: ADMIN_CONFIG_VERSION, models: {} };
65
70
  }
@@ -77,16 +82,16 @@ const SecureAdminLocalStorage = {
77
82
  localStorage.setItem(ADMIN_CONFIG_KEY, serialized);
78
83
  return true;
79
84
  } catch (error) {
85
+ console.error("Unexpected error:", error);
80
86
  return false;
81
87
  }
82
88
  },
83
89
  // Get visible columns for a specific model with sanitization
84
90
  getColumnVisibility: (modelName) => {
85
- var _a;
86
91
  const sanitizedModelName = sanitizeString(modelName);
87
92
  if (!sanitizedModelName) return null;
88
93
  const config = SecureAdminLocalStorage.getConfig();
89
- const columns = (_a = config.models[sanitizedModelName]) == null ? void 0 : _a.visibleColumns;
94
+ const columns = config.models[sanitizedModelName]?.visibleColumns;
90
95
  return columns ? sanitizeArray(columns) : null;
91
96
  },
92
97
  // Set visible columns for a specific model with validation
@@ -103,11 +108,10 @@ const SecureAdminLocalStorage = {
103
108
  },
104
109
  // Get sort preference for a specific model with validation
105
110
  getSortPreference: (modelName) => {
106
- var _a;
107
111
  const sanitizedModelName = sanitizeString(modelName);
108
112
  if (!sanitizedModelName) return null;
109
113
  const config = SecureAdminLocalStorage.getConfig();
110
- const sortPref = (_a = config.models[sanitizedModelName]) == null ? void 0 : _a.sortPreference;
114
+ const sortPref = config.models[sanitizedModelName]?.sortPreference;
111
115
  return sortPref ? sanitizeSortPreference(sortPref) : null;
112
116
  },
113
117
  // Set sort preference for a specific model with validation
@@ -124,11 +128,10 @@ const SecureAdminLocalStorage = {
124
128
  },
125
129
  // Get search fields for a specific model with sanitization
126
130
  getSearchFields: (modelName) => {
127
- var _a;
128
131
  const sanitizedModelName = sanitizeString(modelName);
129
132
  if (!sanitizedModelName) return null;
130
133
  const config = SecureAdminLocalStorage.getConfig();
131
- const searchFields = (_a = config.models[sanitizedModelName]) == null ? void 0 : _a.searchFields;
134
+ const searchFields = config.models[sanitizedModelName]?.searchFields;
132
135
  return searchFields ? sanitizeArray(searchFields) : null;
133
136
  },
134
137
  // Set search fields for a specific model with validation
@@ -152,6 +155,7 @@ const SecureAdminLocalStorage = {
152
155
  }
153
156
  return JSON.stringify(config, null, 2);
154
157
  } catch (error) {
158
+ console.error("Unexpected error:", error);
155
159
  return null;
156
160
  }
157
161
  },
@@ -181,6 +185,7 @@ const SecureAdminLocalStorage = {
181
185
  }
182
186
  return SecureAdminLocalStorage.setConfig(config);
183
187
  } catch (error) {
188
+ console.error("Unexpected error:", error);
184
189
  return false;
185
190
  }
186
191
  },
@@ -190,6 +195,7 @@ const SecureAdminLocalStorage = {
190
195
  localStorage.removeItem(ADMIN_CONFIG_KEY);
191
196
  return true;
192
197
  } catch (error) {
198
+ console.error("Unexpected error:", error);
193
199
  return false;
194
200
  }
195
201
  }
@@ -4,6 +4,15 @@ function kebabCase(name) {
4
4
  function spacedWords(name) {
5
5
  return name.replaceAll(/([a-z])([A-Z])/g, "$1 $2").replaceAll(/([A-Z])([A-Z][a-z])/g, "$1 $2");
6
6
  }
7
+ function isUppercaseLetter(char) {
8
+ return char >= "A" && char <= "Z";
9
+ }
10
+ function isLowercaseLetter(char) {
11
+ return char !== void 0 && char >= "a" && char <= "z";
12
+ }
13
+ function titleCaseAcronym(acronym) {
14
+ return acronym.charAt(0).toUpperCase() + acronym.slice(1).toLowerCase();
15
+ }
7
16
  function formatFieldName(fieldName) {
8
17
  if (fieldName.includes(".")) {
9
18
  return fieldName.split(".").map((part) => {
@@ -22,15 +31,29 @@ function formatFieldName(fieldName) {
22
31
  return fieldName.replaceAll(/([a-z])([A-Z])/g, "$1 $2").replace(/^./, (str) => str.toUpperCase());
23
32
  }
24
33
  function normalizeModelNameForDocument(modelName) {
25
- return modelName.replace(/([A-Z]{2,})([A-Z][a-z])/g, (_, acronym, rest) => {
26
- if (acronym.length >= 2) {
27
- return acronym.charAt(0).toUpperCase() + acronym.slice(1).toLowerCase() + rest;
34
+ let normalized = "";
35
+ let index = 0;
36
+ while (index < modelName.length) {
37
+ if (!isUppercaseLetter(modelName[index])) {
38
+ normalized += modelName[index];
39
+ index += 1;
40
+ continue;
28
41
  }
29
- return acronym + rest;
30
- }).replace(
31
- /[A-Z]{3,}$/g,
32
- (match) => match.charAt(0).toUpperCase() + match.slice(1).toLowerCase()
33
- );
42
+ const runStart = index;
43
+ while (index < modelName.length && isUppercaseLetter(modelName[index])) {
44
+ index += 1;
45
+ }
46
+ const run = modelName.slice(runStart, index);
47
+ const nextIsLowercase = isLowercaseLetter(modelName[index]);
48
+ if (run.length >= 4 && nextIsLowercase) {
49
+ normalized += titleCaseAcronym(run.slice(0, -1)) + run[run.length - 1];
50
+ } else if (run.length >= 3 && index === modelName.length) {
51
+ normalized += titleCaseAcronym(run);
52
+ } else {
53
+ normalized += run;
54
+ }
55
+ }
56
+ return normalized;
34
57
  }
35
58
  function getItemDisplayName(item) {
36
59
  if (item.name) return item.name;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nestledjs/data-browser",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "Universal admin data browser for Nestled framework projects with full CRUD operations",
5
5
  "main": "./index.js",
6
6
  "module": "./index.js",
@@ -37,7 +37,7 @@
37
37
  "peerDependencies": {
38
38
  "@apollo/client": "^4.0.0",
39
39
  "@heroicons/react": "^2.0.0",
40
- "@nestledjs/forms": "^0.6.3",
40
+ "@nestledjs/forms": "0.7.7",
41
41
  "@nestledjs/shared-components": "^1.0.14",
42
42
  "react": "^19.0.0",
43
43
  "react-router": "^7.0.0"