@nestledjs/data-browser 0.1.13 → 0.1.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.
@@ -47,6 +47,9 @@ export type AdminListAction = {
47
47
  payload: Record<string, unknown>;
48
48
  } | {
49
49
  type: 'TOGGLE_FILTERS';
50
+ } | {
51
+ type: 'SET_SHOW_FILTERS';
52
+ payload: boolean;
50
53
  } | {
51
54
  type: 'RESET_PAGINATION';
52
55
  } | {
@@ -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
+ };
@@ -17,7 +17,7 @@ export declare function getMutationName(model: DatabaseModel, operation: 'create
17
17
  /**
18
18
  * Build form fields for a model and operation
19
19
  */
20
- export declare function buildFormFields(sdk: any, model: DatabaseModel, operation: 'create' | 'update', currentItem?: any, isSubmitting?: boolean, basePath?: string): FormField[];
20
+ export declare function buildFormFields(sdk: any, model: DatabaseModel, operation: 'create' | 'update', currentItem?: any, isSubmitting?: boolean, basePath?: string, databaseModels?: DatabaseModel[]): FormField[];
21
21
  /**
22
22
  * Clean form input data for GraphQL mutations
23
23
  * Removes Apollo metadata and system fields
@@ -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
  );
@@ -88,7 +78,19 @@ function toLowerCamelCase(str) {
88
78
  if (!str) return "";
89
79
  return str.charAt(0).toLowerCase() + str.slice(1);
90
80
  }
91
- function buildFormFields(sdk, model, operation, currentItem, isSubmitting, basePath = "/admin/data") {
81
+ function findForeignKeyFieldName(relatedModel, currentModelName, relationName) {
82
+ var _a;
83
+ if (!relatedModel) return null;
84
+ const foreignKeyField = relatedModel.fields.find((f) => {
85
+ if (f.type !== currentModelName) return false;
86
+ if (!f.relationName) return false;
87
+ if (f.isList) return false;
88
+ if (relationName && f.relationName !== relationName) return false;
89
+ return true;
90
+ });
91
+ return ((_a = foreignKeyField == null ? void 0 : foreignKeyField.relationFromFields) == null ? void 0 : _a[0]) || null;
92
+ }
93
+ function buildFormFields(sdk, model, operation, currentItem, isSubmitting, basePath = "/admin/data", databaseModels) {
92
94
  const editableFields = model.fields.filter((field) => {
93
95
  if (operation === "create" && field.isId) return false;
94
96
  if (field.isReadOnly || field.isGenerated) return false;
@@ -113,7 +115,6 @@ function buildFormFields(sdk, model, operation, currentItem, isSubmitting, baseP
113
115
  initialValue = isoString.substring(0, 16);
114
116
  }
115
117
  } catch (e) {
116
- console.warn(`Failed to convert date value for field ${field.name}:`, e);
117
118
  initialValue = "";
118
119
  }
119
120
  }
@@ -261,9 +262,6 @@ function buildFormFields(sdk, model, operation, currentItem, isSubmitting, baseP
261
262
  });
262
263
  break;
263
264
  } else {
264
- console.warn(
265
- `GraphQL document ${adminDocumentName} or ${regularDocumentName} not found for relation ${field.type}. Using text input instead.`
266
- );
267
265
  formField = FormFieldClass.text(relationFieldName, {
268
266
  label: `${label} ID`,
269
267
  required: options.required,
@@ -291,16 +289,17 @@ function buildFormFields(sdk, model, operation, currentItem, isSubmitting, baseP
291
289
  formFields.push(formField);
292
290
  });
293
291
  listRelationFields.forEach((field) => {
294
- var _a, _b;
292
+ var _a;
295
293
  const label = field.name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^./, (str) => str.toUpperCase());
296
294
  if (operation === "update" && currentItem) {
297
295
  const pluralModelName = getPluralName(field.type);
298
296
  const relatedModelKebab = toKebabCase(pluralModelName);
299
297
  const displayName = field.type.replace(/([a-z])([A-Z])/g, "$1 $2");
300
298
  const pluralDisplayName = getPluralName(displayName);
301
- const foreignKeyFieldName = ((_a = field.relationToFields) == null ? void 0 : _a[0]) || `${toLowerCamelCase(model.name)}Id`;
299
+ const relatedModel = databaseModels == null ? void 0 : databaseModels.find((m) => m.name === field.type);
300
+ const foreignKeyFieldName = findForeignKeyFieldName(relatedModel, model.name, field.relationName) || `${toLowerCamelCase(model.name)}Id`;
302
301
  const filterUrl = `${basePath}/${relatedModelKebab}?${foreignKeyFieldName}=${currentItem.id}`;
303
- const countData = (_b = currentItem._count) == null ? void 0 : _b[field.name];
302
+ const countData = (_a = currentItem._count) == null ? void 0 : _a[field.name];
304
303
  const countText = countData !== void 0 ? ` (${countData})` : "";
305
304
  const formField = FormFieldClass.content(field.name, {
306
305
  content: React.createElement("div", { className: "py-2" }, [
@@ -384,7 +383,6 @@ function convertStringValue(value, field) {
384
383
  const date = new Date(value);
385
384
  return date.toISOString();
386
385
  } catch (e) {
387
- console.warn(`Failed to convert datetime-local value: ${value}`);
388
386
  return value;
389
387
  }
390
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.13",
3
+ "version": "0.1.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",
@@ -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/helpers": "^0.1.0",
42
- "@nestledjs/shared-components": "^0.1.12",
41
+ "@nestledjs/shared-components": "^0.1.16",
43
42
  "react": "^19.0.0",
44
43
  "react-router": "^7.0.0"
45
44
  },