@quazardous/qdadm 2.1.0 → 2.2.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/package.json
CHANGED
|
@@ -192,6 +192,26 @@ export interface KernelOptions {
|
|
|
192
192
|
* wiring. Typically the same client your ApiStorages use.
|
|
193
193
|
*/
|
|
194
194
|
apiClient?: ApiClientSource
|
|
195
|
+
/**
|
|
196
|
+
* Naming policy for the URL param carrying the parent id in child route
|
|
197
|
+
* families (#1201). `auto` (default): list-only families keep the bare
|
|
198
|
+
* `idField` param; families with show/edit routes namespace the parent
|
|
199
|
+
* (`jobs/:jobId/tasks/:id`) so the principal (child) id stays `:id`.
|
|
200
|
+
* `always`: namespaced everywhere. `bare`: legacy `idField` param, with
|
|
201
|
+
* an explicit registration error on collision.
|
|
202
|
+
*/
|
|
203
|
+
parentParamMode?: 'auto' | 'always' | 'bare'
|
|
204
|
+
/**
|
|
205
|
+
* Global mapping service for parent URL param names (#1201). Consulted
|
|
206
|
+
* before `parentParamMode`; a per-call `parentParam` still wins. Return
|
|
207
|
+
* undefined to fall through to the mode-based default.
|
|
208
|
+
*/
|
|
209
|
+
routeParamResolver?: (ctx: {
|
|
210
|
+
parentEntity: string
|
|
211
|
+
parentRouteName: string
|
|
212
|
+
childEntity: string | null
|
|
213
|
+
parentIdField: string
|
|
214
|
+
}) => string | undefined
|
|
195
215
|
/**
|
|
196
216
|
* Existing Vue app to install qdadm onto. When provided, the Kernel
|
|
197
217
|
* skips its own `createApp(WrappedRoot)` and reuses the supplied
|
|
@@ -45,6 +45,89 @@ function capitalize(str: string): string {
|
|
|
45
45
|
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/** Context handed to a kernel-level routeParamResolver (#1201). */
|
|
49
|
+
export interface RouteParamResolverContext {
|
|
50
|
+
parentEntity: string
|
|
51
|
+
parentRouteName: string
|
|
52
|
+
/** Child entity name, or null for a non-entity childPage. */
|
|
53
|
+
childEntity: string | null
|
|
54
|
+
parentIdField: string
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Resolve the URL param name carrying the parent id in a child route
|
|
59
|
+
* family (#1201). Precedence: per-call `parentParam` > kernel
|
|
60
|
+
* `routeParamResolver` > `parentParamMode`:
|
|
61
|
+
*
|
|
62
|
+
* - `auto` (default): families whose routes never carry the child's own
|
|
63
|
+
* id (list-only, childPage) keep the bare parent `idField` param —
|
|
64
|
+
* unchanged URLs. Families with show/edit routes namespace the parent
|
|
65
|
+
* (`:jobId`) so the principal (child) id stays `:id`.
|
|
66
|
+
* - `always`: namespaced parent param everywhere.
|
|
67
|
+
* - `bare`: legacy `idField` param everywhere; a would-be collision
|
|
68
|
+
* throws at registration instead of silently shadowing the parent.
|
|
69
|
+
*/
|
|
70
|
+
function resolveParentParam(opts: {
|
|
71
|
+
kernelOptions?: {
|
|
72
|
+
parentParamMode?: 'auto' | 'always' | 'bare'
|
|
73
|
+
routeParamResolver?: (ctx: RouteParamResolverContext) => string | undefined
|
|
74
|
+
}
|
|
75
|
+
explicit?: string
|
|
76
|
+
parentEntity: string
|
|
77
|
+
parentRouteName: string
|
|
78
|
+
childEntity: string | null
|
|
79
|
+
parentIdField: string
|
|
80
|
+
childIdParam: string
|
|
81
|
+
hasOwnIdParam: boolean
|
|
82
|
+
}): string {
|
|
83
|
+
const {
|
|
84
|
+
kernelOptions,
|
|
85
|
+
explicit,
|
|
86
|
+
parentEntity,
|
|
87
|
+
parentRouteName,
|
|
88
|
+
childEntity,
|
|
89
|
+
parentIdField,
|
|
90
|
+
childIdParam,
|
|
91
|
+
hasOwnIdParam,
|
|
92
|
+
} = opts
|
|
93
|
+
|
|
94
|
+
if (explicit) return explicit
|
|
95
|
+
|
|
96
|
+
const resolved = kernelOptions?.routeParamResolver?.({
|
|
97
|
+
parentEntity,
|
|
98
|
+
parentRouteName,
|
|
99
|
+
childEntity,
|
|
100
|
+
parentIdField,
|
|
101
|
+
})
|
|
102
|
+
if (resolved) return resolved
|
|
103
|
+
|
|
104
|
+
const mode = kernelOptions?.parentParamMode || 'auto'
|
|
105
|
+
const namespaced = `${singularize(parentEntity)}Id`
|
|
106
|
+
|
|
107
|
+
if (mode === 'always') return namespaced
|
|
108
|
+
|
|
109
|
+
if (mode === 'bare') {
|
|
110
|
+
if (hasOwnIdParam && parentIdField === childIdParam) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`[qdadm] crud('${childEntity}', { parentRoute: '${parentRouteName}' }): parent and child ` +
|
|
113
|
+
`both use the URL param ':${childIdParam}' — the child id would shadow the parent id. ` +
|
|
114
|
+
`Switch parentParamMode to 'auto', or set an explicit parentParam.`
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
return parentIdField
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// mode === 'auto'
|
|
121
|
+
if (!hasOwnIdParam) return parentIdField
|
|
122
|
+
if (namespaced === childIdParam) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
`[qdadm] crud('${childEntity}', { parentRoute: '${parentRouteName}' }): the namespaced ` +
|
|
125
|
+
`parent param ':${namespaced}' collides with the child idField — set an explicit parentParam.`
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
return namespaced
|
|
129
|
+
}
|
|
130
|
+
|
|
48
131
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
49
132
|
export function applyRoutingMethods(KernelContextClass: { prototype: any }): void {
|
|
50
133
|
const proto = KernelContextClass.prototype as Self
|
|
@@ -103,7 +186,20 @@ export function applyRoutingMethods(KernelContextClass: { prototype: any }): voi
|
|
|
103
186
|
const parentManager = parentEntityName
|
|
104
187
|
? this._kernel.orchestrator?.get(parentEntityName)
|
|
105
188
|
: null
|
|
106
|
-
const
|
|
189
|
+
const parentIdField = parentManager?.idField || 'id'
|
|
190
|
+
// Families with show/edit routes carry the child's own :id — the
|
|
191
|
+
// parent param must not collide with it (#1201).
|
|
192
|
+
const hasOwnIdParam = !!(pages.show || pages.form || pages.edit)
|
|
193
|
+
const parentIdParam = resolveParentParam({
|
|
194
|
+
kernelOptions: this._kernel.options,
|
|
195
|
+
explicit: options.parentParam,
|
|
196
|
+
parentEntity: parentEntityName || parentRouteName,
|
|
197
|
+
parentRouteName,
|
|
198
|
+
childEntity: entity,
|
|
199
|
+
parentIdField,
|
|
200
|
+
childIdParam: idParam,
|
|
201
|
+
hasOwnIdParam,
|
|
202
|
+
})
|
|
107
203
|
|
|
108
204
|
// Build base path: parentPath/:parentId/entity (e.g., books/:bookId/loans)
|
|
109
205
|
const parentBasePath =
|
|
@@ -238,7 +334,19 @@ export function applyRoutingMethods(KernelContextClass: { prototype: any }): voi
|
|
|
238
334
|
const parentManager = parentEntityName
|
|
239
335
|
? this._kernel.orchestrator?.get(parentEntityName)
|
|
240
336
|
: null
|
|
241
|
-
const
|
|
337
|
+
const parentIdField = parentManager?.idField || 'id'
|
|
338
|
+
// childPage has no own :id (single-id family) — auto mode keeps the
|
|
339
|
+
// bare param; explicit parentParam / resolver / 'always' still apply.
|
|
340
|
+
const parentIdParam = resolveParentParam({
|
|
341
|
+
kernelOptions: this._kernel.options,
|
|
342
|
+
explicit: options.parentParam,
|
|
343
|
+
parentEntity: parentEntityName || parentRouteName,
|
|
344
|
+
parentRouteName,
|
|
345
|
+
childEntity: null,
|
|
346
|
+
parentIdField,
|
|
347
|
+
childIdParam: 'id',
|
|
348
|
+
hasOwnIdParam: false,
|
|
349
|
+
})
|
|
242
350
|
|
|
243
351
|
const parentBasePath =
|
|
244
352
|
parentRoute.path.replace(/\/(create|:.*)?$/, '') || parentEntityName
|
|
@@ -148,6 +148,12 @@ export interface CrudOptions {
|
|
|
148
148
|
parentRoute?: string
|
|
149
149
|
foreignKey?: string
|
|
150
150
|
label?: string
|
|
151
|
+
/**
|
|
152
|
+
* Explicit URL param name carrying the parent id in child routes
|
|
153
|
+
* (e.g. 'jobUuid' → `jobs/:jobUuid/tasks/:id`). Overrides the kernel
|
|
154
|
+
* `routeParamResolver` and `parentParamMode` (#1201).
|
|
155
|
+
*/
|
|
156
|
+
parentParam?: string
|
|
151
157
|
}
|
|
152
158
|
|
|
153
159
|
/** Child page options for non-entity child routes */
|
|
@@ -156,6 +162,8 @@ export interface ChildPageOptions {
|
|
|
156
162
|
label?: string
|
|
157
163
|
icon?: string
|
|
158
164
|
meta?: Record<string, unknown>
|
|
165
|
+
/** Explicit URL param name for the parent id (see CrudOptions.parentParam). */
|
|
166
|
+
parentParam?: string
|
|
159
167
|
}
|
|
160
168
|
|
|
161
169
|
/** User entity options */
|