adminizer 5.0.0-build.12 → 5.0.0-build.13

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 (38) hide show
  1. package/assets/{add-BhTWhSNx.js → add-B_hn__Ba.js} +1 -1
  2. package/assets/{add-group-BEOEkMGK.js → add-group-0SdYgiXZ.js} +1 -1
  3. package/assets/{add-user-MW-fSILU.js → add-user-Bh1iqSem.js} +1 -1
  4. package/assets/ai-assistant/agent.es.js +3948 -3889
  5. package/assets/app.js +32 -32
  6. package/assets/{catalog-D-lFVV9U.js → catalog-DjK8r0Mn.js} +1 -1
  7. package/assets/controls/handsontable.es.js +11637 -11632
  8. package/assets/controls/jsoneditor.es.js +6308 -6297
  9. package/assets/controls/toast-ui.es.js +18 -17
  10. package/assets/{dashboard-Cmj4Df_C.js → dashboard-CbJIK7ka.js} +1 -1
  11. package/assets/{history-BXfeZHTD.js → history-CZAJgVXP.js} +1 -1
  12. package/assets/{list-Dp8F_hor.js → list-C6w_xDK2.js} +1 -1
  13. package/assets/manifest.json +24 -24
  14. package/assets/{module-Q-1AzZH9.js → module-DvPjKyF_.js} +1 -1
  15. package/assets/{notification-CSeMEsPn.js → notification-N_b-N0Zu.js} +1 -1
  16. package/assets/{user-filters-list-Do6Dis5D.js → user-filters-list-xbJoY_5_.js} +1 -1
  17. package/assets/{welcome-D3dg746Y.js → welcome-BUkdkKa5.js} +1 -1
  18. package/assets/{with-app-layout-Bjo8B9zO.js → with-app-layout-WhRyO5WK.js} +1 -1
  19. package/controllers/view.js +1 -1
  20. package/helpers/configHelper.js +12 -17
  21. package/helpers/controllerHelper.d.ts +1 -0
  22. package/helpers/controllerHelper.js +25 -15
  23. package/helpers/fieldsHelper.js +3 -3
  24. package/helpers/inertiaAddHelper.js +1 -1
  25. package/helpers/modelResourceHelper.d.ts +1 -1
  26. package/helpers/modelResourceHelper.js +8 -4
  27. package/interfaces/adminpanelConfig.d.ts +6 -1
  28. package/lib/DataAccessor.d.ts +1 -0
  29. package/lib/DataAccessor.js +45 -47
  30. package/lib/filters/FilterService.js +4 -3
  31. package/lib/history-actions/AbstractHistoryAdapter.js +11 -9
  32. package/lib/model/AbstractModel.d.ts +2 -0
  33. package/lib/model/ModelHandler.d.ts +47 -2
  34. package/lib/model/ModelHandler.js +107 -8
  35. package/package.json +1 -1
  36. package/system/bindAccessRights.js +2 -3
  37. package/system/buildInternalModelAccess.js +5 -2
  38. package/system/validateSystemModels.js +29 -9
@@ -40,18 +40,27 @@ export class AppModelAccess {
40
40
  export class ModelHandler {
41
41
  models = new Map();
42
42
  modelAliases = new Map();
43
+ hostModelResources = new Map();
43
44
  internalAccess;
44
45
  appAccessRecords = new Map();
45
- add(modelName, modelInstance, aliases = []) {
46
- const modelname = modelName.toLowerCase();
47
- this.models.set(modelname, {
48
- id: modelname,
49
- name: modelName,
46
+ add(resourceName, modelInstance, options = {}) {
47
+ const resourceId = normalizeName(resourceName);
48
+ if (this.models.has(resourceId)) {
49
+ throw new Error(`Model resource "${resourceName}" is already registered`);
50
+ }
51
+ const normalizedOptions = Array.isArray(options) ? { aliases: options } : options;
52
+ const hostModelName = normalizedOptions.hostModelName ?? resourceName;
53
+ this.registerHostModel(resourceName, hostModelName, true);
54
+ this.models.set(resourceId, {
55
+ id: resourceId,
56
+ name: resourceName,
57
+ hostModelName,
58
+ primaryHostModel: normalizedOptions.primary === true,
50
59
  model: modelInstance,
51
60
  enabled: true,
52
61
  });
53
- this.registerAliases(modelName, aliases);
54
- Adminizer.log.debug(`Model with name [${modelname}] was registered`);
62
+ this.registerAliases(resourceName, normalizedOptions.aliases ?? []);
63
+ Adminizer.log.debug(`Model resource [${resourceName}] was registered for host model [${hostModelName}]`);
55
64
  }
56
65
  register(appName, modelName, modelInstance) {
57
66
  const normalizedModelName = normalizeName(modelName);
@@ -61,10 +70,13 @@ export class ModelHandler {
61
70
  this.models.set(normalizedModelName, {
62
71
  id: normalizedModelName,
63
72
  name: modelName,
73
+ hostModelName: modelName,
74
+ primaryHostModel: true,
64
75
  model: modelInstance,
65
76
  ownerApp: appName,
66
77
  enabled: true,
67
78
  });
79
+ this.registerHostModel(modelName, modelName, false);
68
80
  Adminizer.log.debug(`Model with name [${normalizedModelName}] was registered by app [${appName}]`);
69
81
  return normalizedModelName;
70
82
  }
@@ -74,6 +86,7 @@ export class ModelHandler {
74
86
  return;
75
87
  }
76
88
  this.models.delete(normalizeName(id));
89
+ this.unregisterHostModel(record.hostModelName, record.name);
77
90
  }
78
91
  disable(id) {
79
92
  const record = this.models.get(normalizeName(id));
@@ -118,7 +131,10 @@ export class ModelHandler {
118
131
  return new AppModelAccess((modelName) => this.model.get(modelName), appName, () => this.getAllowedAppModels(appName));
119
132
  }
120
133
  // TODO: 'hot reload' need add method for delete model & unbind
121
- /** Improved model getter with case-insensitive lookup and configured alias support. */
134
+ /**
135
+ * Improved model getter with case-insensitive lookup and configured alias support.
136
+ * @deprecated New code should use getResource() and getByHostModel() explicitly.
137
+ */
122
138
  get model() {
123
139
  return {
124
140
  get: (modelName) => this.models.get(this.resolveName(modelName))?.enabled
@@ -130,6 +146,65 @@ export class ModelHandler {
130
146
  values: () => this.enabledModelValues(),
131
147
  };
132
148
  }
149
+ /**
150
+ * Returns a model by its Adminizer resource name or an explicit deprecated compatibility alias.
151
+ * New code must use canonical resource names.
152
+ */
153
+ getResource(resourceName) {
154
+ return this.model.get(resourceName);
155
+ }
156
+ getResourceRecord(resourceName) {
157
+ const record = this.models.get(this.resolveName(resourceName));
158
+ return record?.enabled ? record : undefined;
159
+ }
160
+ /**
161
+ * Resolves a physical ORM model name to its Adminizer resource.
162
+ * This lookup is deliberately separate from getResource(): a host model may have the
163
+ * same name as a system resource while being exposed under a project resource alias.
164
+ */
165
+ resolveResourceByHostModel(hostModelName) {
166
+ const resourceIds = this.hostModelResources.get(normalizeName(hostModelName));
167
+ const records = Array.from(resourceIds ?? [])
168
+ .map((resourceId) => this.models.get(resourceId))
169
+ .filter((record) => Boolean(record?.enabled));
170
+ if (records.length === 1) {
171
+ return records[0].name;
172
+ }
173
+ const primaryRecords = records.filter((record) => record.primaryHostModel);
174
+ if (primaryRecords.length === 1) {
175
+ return primaryRecords[0].name;
176
+ }
177
+ if (records.length > 1) {
178
+ throw new Error(this.getSharedHostModelError(hostModelName, records));
179
+ }
180
+ return undefined;
181
+ }
182
+ getByHostModel(hostModelName) {
183
+ const resourceName = this.resolveResourceByHostModel(hostModelName);
184
+ return resourceName ? this.getResource(resourceName) : undefined;
185
+ }
186
+ /**
187
+ * Resolves an ORM association target. Adapter-provided host model names take precedence;
188
+ * system-model relations may pass their explicit canonical resource name instead.
189
+ */
190
+ resolveAssociationResource(targetName, resourceName) {
191
+ if (resourceName) {
192
+ return this.getResourceRecord(resourceName)?.name;
193
+ }
194
+ return this.resolveResourceByHostModel(targetName)
195
+ ?? this.getResourceRecord(targetName)?.name;
196
+ }
197
+ /** Validates that every host ORM model used by multiple resources has one primary resource. */
198
+ validateHostModelMappings() {
199
+ for (const [normalizedHostModelName, resourceIds] of this.hostModelResources) {
200
+ const records = Array.from(resourceIds)
201
+ .map((resourceId) => this.models.get(resourceId))
202
+ .filter((record) => Boolean(record?.enabled));
203
+ if (records.length > 1 && records.filter((record) => record.primaryHostModel).length !== 1) {
204
+ throw new Error(this.getSharedHostModelError(records[0]?.hostModelName ?? normalizedHostModelName, records));
205
+ }
206
+ }
207
+ }
133
208
  resolveModelName(modelName) {
134
209
  const resolvedName = this.resolveName(modelName);
135
210
  return this.models.get(resolvedName)?.name ?? modelName;
@@ -184,6 +259,30 @@ export class ModelHandler {
184
259
  this.modelAliases.set(normalizedAlias, normalizedModelName);
185
260
  }
186
261
  }
262
+ registerHostModel(resourceName, hostModelName, allowMultipleResources) {
263
+ const normalizedHostModelName = normalizeName(hostModelName);
264
+ const resourceId = normalizeName(resourceName);
265
+ const resourceIds = this.hostModelResources.get(normalizedHostModelName) ?? new Set();
266
+ if (!allowMultipleResources && resourceIds.size && !resourceIds.has(resourceId)) {
267
+ const existingResourceId = resourceIds.values().next().value;
268
+ const existingResource = this.models.get(existingResourceId)?.name ?? existingResourceId;
269
+ throw new Error(`Host model "${hostModelName}" is already registered for Adminizer resource "${existingResource}"`);
270
+ }
271
+ resourceIds.add(resourceId);
272
+ this.hostModelResources.set(normalizedHostModelName, resourceIds);
273
+ }
274
+ unregisterHostModel(hostModelName, resourceName) {
275
+ const normalizedHostModelName = normalizeName(hostModelName);
276
+ const resourceIds = this.hostModelResources.get(normalizedHostModelName);
277
+ resourceIds?.delete(normalizeName(resourceName));
278
+ if (!resourceIds?.size) {
279
+ this.hostModelResources.delete(normalizedHostModelName);
280
+ }
281
+ }
282
+ getSharedHostModelError(hostModelName, records) {
283
+ return (`Host model "${hostModelName}" is mapped to multiple Adminizer resources: ` +
284
+ `${records.map((record) => record.name).join(", ")}. Configure exactly one with primary: true.`);
285
+ }
187
286
  resolveName(modelName) {
188
287
  const normalizedModelName = normalizeName(modelName);
189
288
  return this.modelAliases.get(normalizedModelName) ?? normalizedModelName;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "adminizer",
3
3
  "type": "module",
4
- "version": "5.0.0-build.12",
4
+ "version": "5.0.0-build.13",
5
5
  "main": "index.js",
6
6
  "exports": {
7
7
  ".": "./index.js",
@@ -2,10 +2,9 @@ import { GROUP_FILTER_VISIBILITY_TOKEN } from "../policies/permissionResolvers.j
2
2
  export default async function bindAccessRights(adminizer) {
3
3
  if (adminizer.config.models) {
4
4
  let models = adminizer.config.models;
5
- for (let key of Object.keys(models)) {
6
- const model = models[key];
5
+ for (const [resourceName, model] of Object.entries(models)) {
7
6
  if (typeof model !== "boolean") {
8
- adminizer.accessRightsHelper.registerModelTokens(model.model);
7
+ adminizer.accessRightsHelper.registerModelTokens(resourceName);
9
8
  }
10
9
  }
11
10
  }
@@ -30,10 +30,13 @@ function getDataAccessorInternalModels(config, modelHandler) {
30
30
  if (!userAccessRelation || typeof userAccessRelation !== "object" || Array.isArray(userAccessRelation)) {
31
31
  continue;
32
32
  }
33
- const model = modelHandler.model.get(modelConfig.model ?? modelName);
33
+ const model = modelHandler.getResource(modelName);
34
34
  const intermediateModelName = model?.attributes?.[userAccessRelation.field]?.model;
35
35
  if (intermediateModelName) {
36
- models.push(modelHandler.resolveModelName(intermediateModelName));
36
+ const resourceName = modelHandler.resolveAssociationResource(intermediateModelName, model?.attributes?.[userAccessRelation.field]?.resourceName);
37
+ if (resourceName) {
38
+ models.push(resourceName);
39
+ }
37
40
  }
38
41
  }
39
42
  return Array.from(new Set(models));
@@ -10,13 +10,11 @@ export function validateSystemModels(adminizer) {
10
10
  }
11
11
  const systemAdapter = getAdapter(adminizer, defaultOrmAdapter);
12
12
  const bindings = buildSystemModelBindings(systemAdapter);
13
- const systemModels = new Set();
13
+ const systemResourceNames = new Set(SYSTEM_MODEL_CONTRACTS.map((contract) => contract.name));
14
14
  for (const contract of SYSTEM_MODEL_CONTRACTS) {
15
15
  const binding = bindings.get(contract.name.toLowerCase()) ?? {
16
16
  model: contract.name,
17
17
  };
18
- systemModels.add(contract.name.toLowerCase());
19
- systemModels.add(binding.model.toLowerCase());
20
18
  const registeredModel = systemAdapter.getModel(binding.model);
21
19
  if (!registeredModel) {
22
20
  throw new Error(`System model "${contract.name}" was not provided by adapter "${systemAdapter.ormType}" as host model "${binding.model}". ` +
@@ -27,7 +25,11 @@ export function validateSystemModels(adminizer) {
27
25
  resolveRelationTarget: (target) => resolveSystemRelationTarget(target, bindings),
28
26
  });
29
27
  normalizeSystemModelRelations(model, bindings);
30
- adminizer.modelHandler.add(contract.name, model, [binding.model]);
28
+ adminizer.modelHandler.add(contract.name, model, {
29
+ hostModelName: binding.model,
30
+ // @deprecated Compatibility lookup for modules that still request UserAP/GroupAP.
31
+ aliases: [binding.model],
32
+ });
31
33
  }
32
34
  const modelsFromConfig = Object.entries(adminizer.config.models ?? {});
33
35
  Adminizer.log.debug(`Bind models > Models from config: ${modelsFromConfig.map(([name]) => name)}`);
@@ -35,7 +37,10 @@ export function validateSystemModels(adminizer) {
35
37
  const modelName = modelConfig && typeof modelConfig !== "boolean"
36
38
  ? modelConfig.model
37
39
  : configName;
38
- if (systemModels.has(modelName.toLowerCase())) {
40
+ // System resources are already registered above. Do not compare host model names
41
+ // here: a project resource may intentionally map its own Adminizer name to a
42
+ // host model named "User" or "Group".
43
+ if (systemResourceNames.has(configName)) {
39
44
  continue;
40
45
  }
41
46
  const adapterName = modelConfig && typeof modelConfig !== "boolean"
@@ -46,8 +51,12 @@ export function validateSystemModels(adminizer) {
46
51
  if (!registeredModel) {
47
52
  throw new Error(`Bind models > Model not found: ${modelName}`);
48
53
  }
49
- adminizer.modelHandler.add(modelName, new ormAdapter.Model(modelName, registeredModel));
54
+ adminizer.modelHandler.add(configName, new ormAdapter.Model(modelName, registeredModel), {
55
+ hostModelName: modelName,
56
+ primary: modelConfig && typeof modelConfig !== "boolean" && modelConfig.primary === true,
57
+ });
50
58
  }
59
+ adminizer.modelHandler.validateHostModelMappings();
51
60
  Adminizer.log.info("Models loaded");
52
61
  }
53
62
  function getAdapter(adminizer, adapterName) {
@@ -79,6 +88,9 @@ function normalizeSystemModelBinding(binding) {
79
88
  return binding;
80
89
  }
81
90
  function resolveSystemRelationTarget(target, bindings) {
91
+ return getSystemRelationResource(target, bindings) ?? target;
92
+ }
93
+ function getSystemRelationResource(target, bindings) {
82
94
  const normalizedTarget = target.toLowerCase();
83
95
  for (const contract of SYSTEM_MODEL_CONTRACTS) {
84
96
  const binding = bindings.get(contract.name.toLowerCase());
@@ -87,15 +99,23 @@ function resolveSystemRelationTarget(target, bindings) {
87
99
  return contract.name;
88
100
  }
89
101
  }
90
- return target;
102
+ return undefined;
91
103
  }
92
104
  function normalizeSystemModelRelations(model, bindings) {
93
105
  for (const attribute of Object.values(model.attributes)) {
94
106
  if (attribute.model) {
95
- attribute.model = resolveSystemRelationTarget(attribute.model, bindings);
107
+ const resourceName = getSystemRelationResource(attribute.model, bindings);
108
+ if (resourceName) {
109
+ attribute.resourceName = resourceName;
110
+ attribute.model = resourceName;
111
+ }
96
112
  }
97
113
  if (attribute.collection) {
98
- attribute.collection = resolveSystemRelationTarget(attribute.collection, bindings);
114
+ const resourceName = getSystemRelationResource(attribute.collection, bindings);
115
+ if (resourceName) {
116
+ attribute.resourceName = resourceName;
117
+ attribute.collection = resourceName;
118
+ }
99
119
  }
100
120
  }
101
121
  }