@nestledjs/data-browser 0.1.15 → 0.1.17
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/README.md +0 -2
- package/index.d.ts +1 -0
- package/index.js +2 -0
- package/lib/components/RelationFieldWrapper.d.ts +2 -1
- package/lib/hooks/useAdminList.js +2 -0
- package/lib/hooks/useRelationData.js +0 -3
- package/lib/layouts/AdminDataLayout.js +1 -1
- package/lib/pages/AdminDataCreatePage.js +38 -42
- package/lib/pages/AdminDataEditPage.js +98 -119
- package/lib/pages/AdminDataIndexPage.js +1 -1
- package/lib/pages/AdminDataListPage.js +111 -125
- package/lib/types/index.d.ts +3 -0
- package/lib/utils/get-plural-names.d.ts +21 -0
- package/lib/utils/get-plural-names.js +41 -0
- package/lib/utils/graphql-utils.js +1 -16
- package/lib/utils/secure-storage.js +0 -13
- package/package.json +2 -3
package/lib/types/index.d.ts
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluralizes a word using the pluralize library, with special handling for words
|
|
3
|
+
* where the singular and plural forms are the same (like "data", "sheep").
|
|
4
|
+
* In those cases, appends "List" to make it clear it's a collection.
|
|
5
|
+
* Also includes overrides for known uncountable nouns that the pluralize library
|
|
6
|
+
* incorrectly pluralizes.
|
|
7
|
+
*
|
|
8
|
+
* @param name - The word to pluralize
|
|
9
|
+
* @returns The pluralized form, or the word + "List" if singular equals plural or is uncountable
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* getPluralName('user') // 'users'
|
|
14
|
+
* getPluralName('category') // 'categories'
|
|
15
|
+
* getPluralName('data') // 'dataList' (because 'data' plural is also 'data')
|
|
16
|
+
* getPluralName('sheep') // 'sheepList' (because 'sheep' plural is also 'sheep')
|
|
17
|
+
* getPluralName('luggage') // 'luggageList' (override: pluralize incorrectly returns 'luggages')
|
|
18
|
+
* getPluralName('furniture') // 'furnitureList' (override: pluralize incorrectly returns 'furnitures')
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function getPluralName(name: string): string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import pluralize from "pluralize";
|
|
2
|
+
const UNCOUNTABLE_OVERRIDES = /* @__PURE__ */ new Set([
|
|
3
|
+
"advice",
|
|
4
|
+
"anger",
|
|
5
|
+
"art",
|
|
6
|
+
"beauty",
|
|
7
|
+
"courage",
|
|
8
|
+
"evidence",
|
|
9
|
+
"feedback",
|
|
10
|
+
"furniture",
|
|
11
|
+
"happiness",
|
|
12
|
+
"hardware",
|
|
13
|
+
"homework",
|
|
14
|
+
"housework",
|
|
15
|
+
"knowledge",
|
|
16
|
+
"love",
|
|
17
|
+
"luggage",
|
|
18
|
+
"music",
|
|
19
|
+
"news",
|
|
20
|
+
"progress",
|
|
21
|
+
"research",
|
|
22
|
+
"software",
|
|
23
|
+
"traffic",
|
|
24
|
+
"weather",
|
|
25
|
+
"wisdom"
|
|
26
|
+
]);
|
|
27
|
+
function getPluralName(name) {
|
|
28
|
+
if (!name || typeof name !== "string") {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`getPluralName: name must be a non-empty string, received type: ${typeof name}, value: ${String(name)}`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
if (UNCOUNTABLE_OVERRIDES.has(name.toLowerCase())) {
|
|
34
|
+
return name + "List";
|
|
35
|
+
}
|
|
36
|
+
const plural = pluralize(name);
|
|
37
|
+
return plural === name ? name + "List" : plural;
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
getPluralName
|
|
41
|
+
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Link } from "react-router";
|
|
3
3
|
import { FormFieldClass } from "@nestledjs/forms";
|
|
4
|
-
import { getPluralName } from "@nestledjs/helpers";
|
|
5
4
|
import { RelationFieldWrapper } from "../components/RelationFieldWrapper.js";
|
|
5
|
+
import { getPluralName } from "./get-plural-names.js";
|
|
6
6
|
function getEnumValues(sdk, enumType) {
|
|
7
7
|
try {
|
|
8
8
|
const enumObject = sdk[enumType];
|
|
@@ -16,7 +16,6 @@ function getEnumValues(sdk, enumType) {
|
|
|
16
16
|
}
|
|
17
17
|
return values;
|
|
18
18
|
} catch (error) {
|
|
19
|
-
console.warn(`Failed to get enum values for type ${enumType}:`, error);
|
|
20
19
|
return null;
|
|
21
20
|
}
|
|
22
21
|
}
|
|
@@ -53,15 +52,6 @@ function getAdminDocuments(sdk, model) {
|
|
|
53
52
|
if (!documents.update) missingDocuments.push(updateDocumentName);
|
|
54
53
|
if (!documents.delete) missingDocuments.push(deleteDocumentName);
|
|
55
54
|
if (missingDocuments.length > 0) {
|
|
56
|
-
console.error(`[GraphQL Documents] Missing documents for model "${model.name}":`, {
|
|
57
|
-
model: model.name,
|
|
58
|
-
normalizedModelName,
|
|
59
|
-
normalizedPluralName,
|
|
60
|
-
missingDocuments,
|
|
61
|
-
availableDocuments: Object.keys(sdk).filter(
|
|
62
|
-
(key) => key.includes("Admin") && key.includes("Document")
|
|
63
|
-
)
|
|
64
|
-
});
|
|
65
55
|
throw new Error(
|
|
66
56
|
`Missing GraphQL documents for model "${model.name}": ${missingDocuments.join(", ")}. Please ensure the API server is running and the GraphQL schema is up to date.`
|
|
67
57
|
);
|
|
@@ -125,7 +115,6 @@ function buildFormFields(sdk, model, operation, currentItem, isSubmitting, baseP
|
|
|
125
115
|
initialValue = isoString.substring(0, 16);
|
|
126
116
|
}
|
|
127
117
|
} catch (e) {
|
|
128
|
-
console.warn(`Failed to convert date value for field ${field.name}:`, e);
|
|
129
118
|
initialValue = "";
|
|
130
119
|
}
|
|
131
120
|
}
|
|
@@ -273,9 +262,6 @@ function buildFormFields(sdk, model, operation, currentItem, isSubmitting, baseP
|
|
|
273
262
|
});
|
|
274
263
|
break;
|
|
275
264
|
} else {
|
|
276
|
-
console.warn(
|
|
277
|
-
`GraphQL document ${adminDocumentName} or ${regularDocumentName} not found for relation ${field.type}. Using text input instead.`
|
|
278
|
-
);
|
|
279
265
|
formField = FormFieldClass.text(relationFieldName, {
|
|
280
266
|
label: `${label} ID`,
|
|
281
267
|
required: options.required,
|
|
@@ -397,7 +383,6 @@ function convertStringValue(value, field) {
|
|
|
397
383
|
const date = new Date(value);
|
|
398
384
|
return date.toISOString();
|
|
399
385
|
} catch (e) {
|
|
400
|
-
console.warn(`Failed to convert datetime-local value: ${value}`);
|
|
401
386
|
return value;
|
|
402
387
|
}
|
|
403
388
|
}
|
|
@@ -47,19 +47,16 @@ const SecureAdminLocalStorage = {
|
|
|
47
47
|
return { version: ADMIN_CONFIG_VERSION, models: {} };
|
|
48
48
|
}
|
|
49
49
|
if (stored.length > MAX_CONFIG_SIZE) {
|
|
50
|
-
console.warn("[AdminLocalStorage] Config exceeds size limit, resetting");
|
|
51
50
|
localStorage.removeItem(ADMIN_CONFIG_KEY);
|
|
52
51
|
return { version: ADMIN_CONFIG_VERSION, models: {} };
|
|
53
52
|
}
|
|
54
53
|
const parsed = JSON.parse(stored);
|
|
55
54
|
if (!validateAdminConfig(parsed)) {
|
|
56
|
-
console.warn("[AdminLocalStorage] Invalid config detected, resetting");
|
|
57
55
|
localStorage.removeItem(ADMIN_CONFIG_KEY);
|
|
58
56
|
return { version: ADMIN_CONFIG_VERSION, models: {} };
|
|
59
57
|
}
|
|
60
58
|
return parsed;
|
|
61
59
|
} catch (error) {
|
|
62
|
-
console.warn("[AdminLocalStorage] Failed to load config:", error);
|
|
63
60
|
try {
|
|
64
61
|
localStorage.removeItem(ADMIN_CONFIG_KEY);
|
|
65
62
|
} catch {
|
|
@@ -71,18 +68,15 @@ const SecureAdminLocalStorage = {
|
|
|
71
68
|
setConfig: (config) => {
|
|
72
69
|
try {
|
|
73
70
|
if (!validateAdminConfig(config)) {
|
|
74
|
-
console.warn("[AdminLocalStorage] Invalid config provided");
|
|
75
71
|
return false;
|
|
76
72
|
}
|
|
77
73
|
const serialized = JSON.stringify(config);
|
|
78
74
|
if (serialized.length > MAX_CONFIG_SIZE) {
|
|
79
|
-
console.warn("[AdminLocalStorage] Config too large to store");
|
|
80
75
|
return false;
|
|
81
76
|
}
|
|
82
77
|
localStorage.setItem(ADMIN_CONFIG_KEY, serialized);
|
|
83
78
|
return true;
|
|
84
79
|
} catch (error) {
|
|
85
|
-
console.warn("[AdminLocalStorage] Failed to save config:", error);
|
|
86
80
|
return false;
|
|
87
81
|
}
|
|
88
82
|
},
|
|
@@ -154,12 +148,10 @@ const SecureAdminLocalStorage = {
|
|
|
154
148
|
try {
|
|
155
149
|
const config = SecureAdminLocalStorage.getConfig();
|
|
156
150
|
if (!validateAdminConfig(config)) {
|
|
157
|
-
console.warn("[AdminLocalStorage] Cannot export invalid config");
|
|
158
151
|
return null;
|
|
159
152
|
}
|
|
160
153
|
return JSON.stringify(config, null, 2);
|
|
161
154
|
} catch (error) {
|
|
162
|
-
console.warn("[AdminLocalStorage] Failed to export config:", error);
|
|
163
155
|
return null;
|
|
164
156
|
}
|
|
165
157
|
},
|
|
@@ -168,12 +160,10 @@ const SecureAdminLocalStorage = {
|
|
|
168
160
|
try {
|
|
169
161
|
const sanitizedJson = sanitizeString(configJson);
|
|
170
162
|
if (!sanitizedJson || sanitizedJson.length > MAX_CONFIG_SIZE) {
|
|
171
|
-
console.warn("[AdminLocalStorage] Invalid or oversized config JSON");
|
|
172
163
|
return false;
|
|
173
164
|
}
|
|
174
165
|
const config = JSON.parse(sanitizedJson);
|
|
175
166
|
if (!validateAdminConfig(config)) {
|
|
176
|
-
console.warn("[AdminLocalStorage] Failed config validation during import");
|
|
177
167
|
return false;
|
|
178
168
|
}
|
|
179
169
|
const serialized = JSON.stringify(config);
|
|
@@ -186,13 +176,11 @@ const SecureAdminLocalStorage = {
|
|
|
186
176
|
];
|
|
187
177
|
for (const pattern of suspiciousPatterns) {
|
|
188
178
|
if (pattern.test(serialized)) {
|
|
189
|
-
console.warn("[AdminLocalStorage] Suspicious content detected in config");
|
|
190
179
|
return false;
|
|
191
180
|
}
|
|
192
181
|
}
|
|
193
182
|
return SecureAdminLocalStorage.setConfig(config);
|
|
194
183
|
} catch (error) {
|
|
195
|
-
console.warn("[AdminLocalStorage] Failed to import config:", error);
|
|
196
184
|
return false;
|
|
197
185
|
}
|
|
198
186
|
},
|
|
@@ -202,7 +190,6 @@ const SecureAdminLocalStorage = {
|
|
|
202
190
|
localStorage.removeItem(ADMIN_CONFIG_KEY);
|
|
203
191
|
return true;
|
|
204
192
|
} catch (error) {
|
|
205
|
-
console.warn("[AdminLocalStorage] Failed to clear config:", error);
|
|
206
193
|
return false;
|
|
207
194
|
}
|
|
208
195
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nestledjs/data-browser",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
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",
|
|
@@ -38,8 +38,7 @@
|
|
|
38
38
|
"@apollo/client": "^4.0.0",
|
|
39
39
|
"@heroicons/react": "^2.0.0",
|
|
40
40
|
"@nestledjs/forms": "^0.5.0",
|
|
41
|
-
"@nestledjs/
|
|
42
|
-
"@nestledjs/shared-components": "^0.1.15",
|
|
41
|
+
"@nestledjs/shared-components": "^0.1.17",
|
|
43
42
|
"react": "^19.0.0",
|
|
44
43
|
"react-router": "^7.0.0"
|
|
45
44
|
},
|