@quazardous/qdadm 2.4.4 → 2.5.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.
@@ -9,6 +9,7 @@
9
9
 
10
10
  import { BaseConnector, type ConnectorOptions, type ParseResult, type ParseWarning } from './BaseConnector'
11
11
  import { getDefaultType, type CustomMappings } from '../FieldMapper'
12
+ import { humanizeFieldName } from '../../utils/humanize'
12
13
  import type { UnifiedEntitySchema, UnifiedFieldSchema, UnifiedFieldType } from '../schema'
13
14
 
14
15
  /**
@@ -48,6 +49,19 @@ export interface OpenAPIConnectorOptions extends ConnectorOptions {
48
49
  operationFilter?: OperationFilter | null
49
50
  /** Custom type mappings for FieldMapper */
50
51
  customMappings?: CustomMappings
52
+ /**
53
+ * Infer a label from the field name when the schema has no `description`
54
+ * (#1255): 'humanize' turns `botUuid` into "Bot Uuid". Opt-in — it changes
55
+ * generated output. Default: off (label only from `description`).
56
+ */
57
+ inferLabels?: 'humanize' | false
58
+ /**
59
+ * Infer `readOnly` from operation shape (#1255): a field that appears in
60
+ * responses but in no request-body schema is marked `readOnly: true`
61
+ * (an entity with no write operations gets all fields readOnly). Opt-in —
62
+ * it changes generated output. Schema-declared `readOnly` always wins.
63
+ */
64
+ inferReadOnly?: boolean
51
65
  }
52
66
 
53
67
  /**
@@ -64,6 +78,8 @@ interface EntityWorkingData {
64
78
  rawSchema: OpenAPISchema | null
65
79
  /** Extracted fields */
66
80
  fields: Map<string, UnifiedFieldSchema>
81
+ /** Field names seen in request-body schemas (for readOnly inference, #1255) */
82
+ requestFields: Set<string>
67
83
  }
68
84
 
69
85
  /**
@@ -173,6 +189,12 @@ export class OpenAPIConnector extends BaseConnector {
173
189
  /** Custom type mappings */
174
190
  customMappings: CustomMappings
175
191
 
192
+ /** Label inference mode (#1255) */
193
+ inferLabels: 'humanize' | false
194
+
195
+ /** Whether to infer readOnly from operation shape (#1255) */
196
+ inferReadOnly: boolean
197
+
176
198
  /**
177
199
  * Create a new OpenAPI connector
178
200
  *
@@ -185,6 +207,8 @@ export class OpenAPIConnector extends BaseConnector {
185
207
  this.dataWrapper = options.dataWrapper ?? 'data'
186
208
  this.operationFilter = options.operationFilter || null
187
209
  this.customMappings = options.customMappings || {}
210
+ this.inferLabels = options.inferLabels ?? false
211
+ this.inferReadOnly = options.inferReadOnly ?? false
188
212
  }
189
213
 
190
214
  /**
@@ -201,10 +225,11 @@ export class OpenAPIConnector extends BaseConnector {
201
225
  * })
202
226
  */
203
227
  parse(source: unknown): UnifiedEntitySchema[] {
204
- const spec = source as OpenAPISpec
205
- this._validateSource(spec)
206
- const entities = this._extractEntities(spec)
207
- return Array.from(entities.values())
228
+ const { schemas, warnings } = this.parseWithWarnings(source)
229
+ for (const warning of warnings) {
230
+ console.warn(`⚠ [${this.name}] ${warning.path}: ${warning.message}`)
231
+ }
232
+ return schemas
208
233
  }
209
234
 
210
235
  /**
@@ -267,6 +292,7 @@ export class OpenAPIConnector extends BaseConnector {
267
292
  idField: null,
268
293
  rawSchema: null,
269
294
  fields: new Map(),
295
+ requestFields: new Set(),
270
296
  })
271
297
  }
272
298
 
@@ -371,6 +397,8 @@ export class OpenAPIConnector extends BaseConnector {
371
397
  const requestSchema = this._extractRequestSchema(op, spec)
372
398
  if (requestSchema) {
373
399
  this._extractFields(requestSchema, entity.fields, spec, warnings, path)
400
+ // Track writable field names for readOnly inference (#1255)
401
+ this._collectFieldNames(requestSchema, spec, entity.requestFields)
374
402
  }
375
403
  }
376
404
  }
@@ -565,6 +593,20 @@ export class OpenAPIConnector extends BaseConnector {
565
593
  continue
566
594
  }
567
595
 
596
+ // Bare object schema: no contract at all. fast-json-stringify (Fastify)
597
+ // serializes such a field to `{}` at runtime, stripping every key (#1240).
598
+ if (this._isBareObjectSchema(resolvedSchema)) {
599
+ warnings.push({
600
+ path: `${sourcePath}#${fieldName}`,
601
+ message:
602
+ `object schema has no \`properties\` and \`additionalProperties\` is not set. ` +
603
+ `Under Fastify (fast-json-stringify) this serializes to \`{}\` at runtime ` +
604
+ `and the generated field type is unusable. ` +
605
+ `For a free-form JSON blob, declare \`additionalProperties: true\`.`,
606
+ code: 'EMPTY_OBJECT_SCHEMA',
607
+ })
608
+ }
609
+
568
610
  // Use FieldMapper for type conversion
569
611
  const type = getDefaultType(resolvedSchema, this.customMappings)
570
612
 
@@ -578,6 +620,8 @@ export class OpenAPIConnector extends BaseConnector {
578
620
  // Add optional properties
579
621
  if (resolvedSchema.description) {
580
622
  field.label = resolvedSchema.description
623
+ } else if (this.inferLabels === 'humanize') {
624
+ field.label = humanizeFieldName(fieldName)
581
625
  }
582
626
  if (resolvedSchema.format) {
583
627
  field.format = resolvedSchema.format
@@ -604,6 +648,57 @@ export class OpenAPIConnector extends BaseConnector {
604
648
  }
605
649
  }
606
650
 
651
+ /**
652
+ * Collect field names from a schema the same way _extractFields names them
653
+ * (top-level + one-level-nested dotted keys). Used to track which fields
654
+ * appear in request bodies, for readOnly inference (#1255).
655
+ *
656
+ * @private
657
+ * @param schema - JSON Schema object
658
+ * @param spec - Full spec for ref resolution
659
+ * @param into - Set collecting the names
660
+ * @param prefix - Prefix for nested field names
661
+ */
662
+ private _collectFieldNames(
663
+ schema: OpenAPISchema,
664
+ spec: OpenAPISpec,
665
+ into: Set<string>,
666
+ prefix: string = ''
667
+ ): void {
668
+ if (!schema || !schema.properties) return
669
+
670
+ for (const [name, propSchema] of Object.entries(schema.properties)) {
671
+ const fieldName = prefix ? `${prefix}.${name}` : name
672
+ into.add(fieldName)
673
+
674
+ const resolvedSchema = propSchema.$ref ? this._resolveRef(propSchema.$ref, spec) : propSchema
675
+ if (resolvedSchema?.type === 'object' && resolvedSchema.properties && !prefix) {
676
+ this._collectFieldNames(resolvedSchema, spec, into, fieldName)
677
+ }
678
+ }
679
+ }
680
+
681
+ /**
682
+ * Detect a "bare" object schema: type object with no properties, no
683
+ * additionalProperties contract, and no composition/reference that could
684
+ * supply one. Such a schema is the quasi-always-footgun case (#1240) —
685
+ * objects with `properties`, a sub-schema `additionalProperties`, `$ref`
686
+ * or oneOf/anyOf/allOf are deliberately excluded.
687
+ *
688
+ * @private
689
+ * @param schema - Resolved property schema
690
+ * @returns True if the schema is a contract-less object
691
+ */
692
+ private _isBareObjectSchema(schema: OpenAPISchema): boolean {
693
+ if (schema.type !== 'object') return false
694
+ if (schema.properties && Object.keys(schema.properties).length > 0) return false
695
+ const additionalProperties = schema.additionalProperties
696
+ if (additionalProperties === true) return false
697
+ if (additionalProperties && typeof additionalProperties === 'object') return false
698
+ if (schema.$ref || schema.oneOf || schema.anyOf || schema.allOf) return false
699
+ return true
700
+ }
701
+
607
702
  /**
608
703
  * Merge field info from secondary source into primary
609
704
  *
@@ -667,6 +762,17 @@ export class OpenAPIConnector extends BaseConnector {
667
762
  const schemas: Map<string, UnifiedEntitySchema> = new Map()
668
763
 
669
764
  for (const [name, data] of entityData) {
765
+ // readOnly inference (#1255): a field never seen in a request body is
766
+ // not writable through this API. Entities without write operations end
767
+ // up with every field readOnly. Schema-declared readOnly is preserved.
768
+ if (this.inferReadOnly) {
769
+ for (const field of data.fields.values()) {
770
+ if (!field.readOnly && !data.requestFields.has(field.name)) {
771
+ field.readOnly = true
772
+ }
773
+ }
774
+ }
775
+
670
776
  const schema: UnifiedEntitySchema = {
671
777
  name: data.name,
672
778
  endpoint: data.endpoint,
@@ -436,6 +436,84 @@ describe('generateManagers', () => {
436
436
  })
437
437
  })
438
438
 
439
+ describe('baseClass option (#1253)', () => {
440
+ function entityConfig(extra = {}) {
441
+ return {
442
+ schema: {
443
+ name: 'users',
444
+ endpoint: '/users',
445
+ fields: {
446
+ id: { name: 'id', type: 'number' },
447
+ name: { name: 'name', type: 'text', required: true }
448
+ }
449
+ },
450
+ endpoint: '/users',
451
+ storageImport: '@quazardous/qdadm',
452
+ storageClass: 'ApiStorage',
453
+ ...extra
454
+ }
455
+ }
456
+
457
+ it('class mode extends the global baseClass and imports it', async () => {
458
+ await generateManagers({
459
+ output: testOutputDir,
460
+ classMode: true,
461
+ baseClass: { import: '@/managers/SkyEntityManager', name: 'SkyEntityManager' },
462
+ entities: { users: entityConfig() }
463
+ })
464
+
465
+ const content = await readFile(join(testOutputDir, 'users.ts'), 'utf-8')
466
+ expect(content).toContain("import { SkyEntityManager } from '@/managers/SkyEntityManager'")
467
+ expect(content).toContain('extends SkyEntityManager<UsersEntity>')
468
+ expect(content).not.toContain("import { EntityManager } from '@quazardous/qdadm'")
469
+ // Options/record types still come from qdadm
470
+ expect(content).toContain("import type { EntityRecord, EntityManagerOptions } from '@quazardous/qdadm'")
471
+ })
472
+
473
+ it('instance mode instantiates the baseClass', async () => {
474
+ await generateManagers({
475
+ output: testOutputDir,
476
+ baseClass: { import: './base', name: 'MyBase' },
477
+ entities: { users: entityConfig() }
478
+ })
479
+
480
+ const content = await readFile(join(testOutputDir, 'usersManager.ts'), 'utf-8')
481
+ expect(content).toContain("import { MyBase } from './base'")
482
+ expect(content).toContain('new MyBase<UsersEntity>(')
483
+ expect(content).not.toContain('new EntityManager<UsersEntity>(')
484
+ })
485
+
486
+ it('per-entity baseClass overrides the global one', async () => {
487
+ await generateManagers({
488
+ output: testOutputDir,
489
+ classMode: true,
490
+ baseClass: { import: './globalBase', name: 'GlobalBase' },
491
+ entities: {
492
+ users: entityConfig({
493
+ baseClass: { import: './localBase', name: 'LocalBase' }
494
+ })
495
+ }
496
+ })
497
+
498
+ const content = await readFile(join(testOutputDir, 'users.ts'), 'utf-8')
499
+ expect(content).toContain("import { LocalBase } from './localBase'")
500
+ expect(content).toContain('extends LocalBase<UsersEntity>')
501
+ expect(content).not.toContain('GlobalBase')
502
+ })
503
+
504
+ it('defaults to EntityManager when no baseClass is given', async () => {
505
+ await generateManagers({
506
+ output: testOutputDir,
507
+ classMode: true,
508
+ entities: { users: entityConfig() }
509
+ })
510
+
511
+ const content = await readFile(join(testOutputDir, 'users.ts'), 'utf-8')
512
+ expect(content).toContain("import { EntityManager } from '@quazardous/qdadm'")
513
+ expect(content).toContain('extends EntityManager<UsersEntity>')
514
+ })
515
+ })
516
+
439
517
  describe('default output directory', () => {
440
518
  it('uses default output when not specified', async () => {
441
519
  // This test would write to actual src/generated/managers/
@@ -22,6 +22,21 @@ import type { EntityDecorator } from './decorators'
22
22
  */
23
23
  const DEFAULT_OUTPUT_DIR = 'src/generated/managers/'
24
24
 
25
+ /**
26
+ * Custom base class for generated managers (#1253).
27
+ *
28
+ * The class MUST extend EntityManager, stay generic over the entity type
29
+ * and pass constructor options through — the generated code does
30
+ * `class Generated<X>Manager extends <name><XEntity>` and calls
31
+ * `super({ ...schema options, storage, ...options })`.
32
+ */
33
+ export interface GenerateManagersBaseClass {
34
+ /** Import specifier (e.g. '@/managers/SkyEntityManager') */
35
+ import: string
36
+ /** Exported class name (e.g. 'SkyEntityManager') */
37
+ name: string
38
+ }
39
+
25
40
  /**
26
41
  * Entity configuration for generation
27
42
  */
@@ -40,6 +55,8 @@ export interface GenerateManagersEntityConfig {
40
55
  decorators?: EntityDecorator
41
56
  /** Generate class instead of instance (for extension) */
42
57
  classMode?: boolean
58
+ /** Base class for this entity's generated manager (overrides global) */
59
+ baseClass?: GenerateManagersBaseClass
43
60
  }
44
61
 
45
62
  /**
@@ -50,6 +67,8 @@ export interface GenerateManagersConfig {
50
67
  output?: string
51
68
  /** Generate classes instead of instances (for extension pattern) */
52
69
  classMode?: boolean
70
+ /** Base class for all generated managers (default: EntityManager) */
71
+ baseClass?: GenerateManagersBaseClass
53
72
  /** Entity configurations keyed by entity name */
54
73
  entities: Record<string, GenerateManagersEntityConfig>
55
74
  }
@@ -175,12 +194,19 @@ function serializeValue(value: unknown, indent: number = 0): string {
175
194
  function generateManagerSource(
176
195
  entityName: string,
177
196
  entityConfig: GenerateManagersEntityConfig,
178
- globalOptions: { classMode?: boolean } = {}
197
+ globalOptions: { classMode?: boolean; baseClass?: GenerateManagersBaseClass } = {}
179
198
  ): string {
180
199
  const { schema, endpoint, storageImport, storageClass, storageOptions = {}, decorators } = entityConfig
181
200
  const classMode = entityConfig.classMode ?? globalOptions.classMode ?? false
182
201
  const className = toPascalCase(entityName)
183
202
 
203
+ // Custom base class (#1253): per-entity wins over global, default EntityManager.
204
+ const baseClass = entityConfig.baseClass ?? globalOptions.baseClass
205
+ const baseName = baseClass?.name ?? 'EntityManager'
206
+ const baseImport = baseClass
207
+ ? `import { ${baseClass.name} } from '${baseClass.import}'`
208
+ : `import { EntityManager } from '@quazardous/qdadm'`
209
+
184
210
  // Build storage options object
185
211
  const fullStorageOptions: Record<string, unknown> = {
186
212
  endpoint,
@@ -240,7 +266,7 @@ function generateManagerSource(
240
266
  * Endpoint: ${endpoint}
241
267
  */
242
268
 
243
- import { EntityManager } from '@quazardous/qdadm'
269
+ ${baseImport}
244
270
  import type { EntityRecord, EntityManagerOptions } from '@quazardous/qdadm'
245
271
  import { ${storageClass} } from '${storageImport}'
246
272
 
@@ -249,9 +275,9 @@ ${entityInterface}
249
275
  /**
250
276
  * Generated base manager for ${entityName}
251
277
  *
252
- * @extends EntityManager
278
+ * @extends ${baseName}
253
279
  */
254
- export class Generated${className}Manager extends EntityManager<${className}Entity> {
280
+ export class Generated${className}Manager extends ${baseName}<${className}Entity> {
255
281
  constructor(options: Partial<EntityManagerOptions<${className}Entity>> = {}) {
256
282
  super({
257
283
  ...${serializeValue(managerOptions, 2)},
@@ -274,7 +300,7 @@ export class Generated${className}Manager extends EntityManager<${className}Enti
274
300
  * Endpoint: ${endpoint}
275
301
  */
276
302
 
277
- import { EntityManager } from '@quazardous/qdadm'
303
+ ${baseImport}
278
304
  import type { EntityRecord } from '@quazardous/qdadm'
279
305
  import { ${storageClass} } from '${storageImport}'
280
306
 
@@ -295,7 +321,7 @@ const storageOptions = ${serializeValue(fullStorageOptions, 0)}
295
321
  *
296
322
  * Provides CRUD operations for ${entityName} entity.
297
323
  */
298
- export const ${entityName}Manager = new EntityManager<${className}Entity>({
324
+ export const ${entityName}Manager = new ${baseName}<${className}Entity>({
299
325
  ...${serializeValue(managerOptions, 0)},
300
326
  storage: new ${storageClass}<${className}Entity>(storageOptions)
301
327
  })
@@ -349,7 +375,7 @@ export async function generateManagers(config: GenerateManagersConfig): Promise<
349
375
  }
350
376
 
351
377
  const outputDir = config.output || DEFAULT_OUTPUT_DIR
352
- const globalOptions = { classMode: config.classMode ?? false }
378
+ const globalOptions = { classMode: config.classMode ?? false, baseClass: config.baseClass }
353
379
  const generatedFiles: string[] = []
354
380
 
355
381
  // Validate each entity config
package/src/gen/index.ts CHANGED
@@ -63,4 +63,5 @@ export {
63
63
  generateEntityInterface,
64
64
  type GenerateManagersConfig,
65
65
  type GenerateManagersEntityConfig,
66
+ type GenerateManagersBaseClass,
66
67
  } from './generateManagers'
package/src/index.ts CHANGED
@@ -96,6 +96,8 @@ export {
96
96
  type SeverityMapValue,
97
97
  type SeverityMap,
98
98
  type RoutingContext,
99
+ type StorageResolution,
100
+ type ResolvedStorage,
99
101
  type PresaveContext,
100
102
  type PostsaveContext,
101
103
  type PredeleteContext,
@@ -150,6 +152,30 @@ export type {
150
152
  // ════════════════════════════════════════════════════════════════════════════
151
153
  export * from './orchestrator/index'
152
154
 
155
+ /**
156
+ * Typed manager registry (#1253) — augment it from your app to make
157
+ * `getManager()` / `useEntity()` return your concrete EntityManager
158
+ * subclasses instead of `EntityManager<EntityRecord>`:
159
+ *
160
+ * ```ts
161
+ * declare module '@quazardous/qdadm' {
162
+ * interface QdadmManagerRegistry {
163
+ * bots: BotsManager
164
+ * botPools: BotPoolsManager
165
+ * }
166
+ * }
167
+ * ```
168
+ *
169
+ * Entity names not declared here keep the current fallback behavior.
170
+ *
171
+ * NOTE: deliberately declared in this file (not re-exported from a
172
+ * sub-module) — TypeScript module augmentation does not merge through
173
+ * re-export aliases, and consumers augment '@quazardous/qdadm', which
174
+ * resolves here.
175
+ */
176
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
177
+ export interface QdadmManagerRegistry {}
178
+
153
179
  // ════════════════════════════════════════════════════════════════════════════
154
180
  // COMPOSABLES (excluding types that conflict with other modules)
155
181
  // ════════════════════════════════════════════════════════════════════════════
@@ -1,14 +1,14 @@
1
1
  import type { AxiosLikeClient, AxiosError } from './Kernel.types'
2
+ import type { Kernel } from './Kernel'
2
3
 
3
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
4
- type Self = any
4
+ // #1196 Phase B — this-typing against the real Kernel shape (was Self = any)
5
+ type Self = Kernel
5
6
 
6
7
  /**
7
8
  * Patch Kernel prototype with API client methods.
8
9
  */
9
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
- export function applyApiMethods(KernelClass: { prototype: any }): void {
11
- const proto = KernelClass.prototype as Self
10
+ export function applyApiMethods(KernelClass: { prototype: Kernel }): void {
11
+ const proto = KernelClass.prototype
12
12
 
13
13
  /**
14
14
  * Setup an axios client with automatic auth and error handling
@@ -1,14 +1,14 @@
1
1
  import { authFactory, CompositeAuthAdapter } from '../entity/auth'
2
+ import type { Kernel } from './Kernel'
2
3
 
3
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
4
- type Self = any
4
+ // #1196 Phase B — this-typing against the real Kernel shape (was Self = any)
5
+ type Self = Kernel
5
6
 
6
7
  /**
7
8
  * Patch Kernel prototype with auth-related methods.
8
9
  */
9
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
- export function applyAuthMethods(KernelClass: { prototype: any }): void {
11
- const proto = KernelClass.prototype as Self
10
+ export function applyAuthMethods(KernelClass: { prototype: Kernel }): void {
11
+ const proto = KernelClass.prototype
12
12
 
13
13
  /**
14
14
  * Resolve entityAuthAdapter through authFactory
@@ -9,13 +9,13 @@
9
9
 
10
10
  import { I18n } from '../i18n/I18n'
11
11
  import type { I18nOptions } from '../i18n/types'
12
+ import type { Kernel } from './Kernel'
12
13
 
13
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
14
- type Self = any
14
+ // #1196 Phase B — this-typing against the real Kernel shape (was Self = any)
15
+ type Self = Kernel
15
16
 
16
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
- export function applyI18nMethods(KernelClass: { prototype: any }): void {
18
- const proto = KernelClass.prototype as Self
17
+ export function applyI18nMethods(KernelClass: { prototype: Kernel }): void {
18
+ const proto = KernelClass.prototype
19
19
 
20
20
  proto._createI18n = function (this: Self): void {
21
21
  const opts = (this.options.i18n as I18nOptions | undefined) ?? {}
@@ -1,15 +1,15 @@
1
1
  import { initModules, setSectionOrder } from '../module/moduleRegistry'
2
2
  import { createModuleLoader, type ModuleLike } from './ModuleLoader'
3
3
  import { createKernelContext, type KernelContext } from './KernelContext'
4
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
- type Self = any
4
+ import type { Kernel } from './Kernel'
5
+ // #1196 Phase B — this-typing against the real Kernel shape (was Self = any)
6
+ type Self = Kernel
6
7
 
7
8
  /**
8
9
  * Patch Kernel prototype with module-related methods.
9
10
  */
10
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
- export function applyModuleMethods(KernelClass: { prototype: any }): void {
12
- const proto = KernelClass.prototype as Self
11
+ export function applyModuleMethods(KernelClass: { prototype: Kernel }): void {
12
+ const proto = KernelClass.prototype
13
13
 
14
14
  /**
15
15
  * Fire entity cache warmups
@@ -13,15 +13,15 @@ import { ActiveStack } from '../chain/ActiveStack.js'
13
13
  import { StackHydrator } from '../chain/StackHydrator.js'
14
14
  import { Orchestrator } from '../orchestrator/Orchestrator'
15
15
  import type { EntityAuthAdapter } from '../entity/auth/EntityAuthAdapter'
16
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
- type Self = any
16
+ import type { Kernel } from './Kernel'
17
+ // #1196 Phase B — this-typing against the real Kernel shape (was Self = any)
18
+ type Self = Kernel
18
19
 
19
20
  /**
20
21
  * Patch Kernel prototype with registry-related methods.
21
22
  */
22
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
- export function applyRegistryMethods(KernelClass: { prototype: any }): void {
24
- const proto = KernelClass.prototype as Self
23
+ export function applyRegistryMethods(KernelClass: { prototype: Kernel }): void {
24
+ const proto = KernelClass.prototype
25
25
 
26
26
  /**
27
27
  * Create signal bus for event-driven communication.
@@ -8,15 +8,15 @@ import {
8
8
  type RouteRecordNormalized,
9
9
  } from 'vue-router'
10
10
  import { getRoutes } from '../module/moduleRegistry'
11
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
- type Self = any
11
+ import type { Kernel } from './Kernel'
12
+ // #1196 Phase B — this-typing against the real Kernel shape (was Self = any)
13
+ type Self = Kernel
13
14
 
14
15
  /**
15
16
  * Patch Kernel prototype with routing-related methods.
16
17
  */
17
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
- export function applyRoutingMethods(KernelClass: { prototype: any }): void {
19
- const proto = KernelClass.prototype as Self
18
+ export function applyRoutingMethods(KernelClass: { prototype: Kernel }): void {
19
+ const proto = KernelClass.prototype
20
20
 
21
21
  /**
22
22
  * Create Vue Router.
@@ -16,8 +16,9 @@ import ToastListener from '../toast/ToastListener.vue'
16
16
  import { createQdadm } from '../plugin.js'
17
17
  import { createNotificationStore, NOTIFICATION_KEY } from '../notifications/NotificationStore'
18
18
  import { I18N_INJECTION_KEY } from '../i18n/useI18n'
19
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
- type Self = any
19
+ import type { Kernel } from './Kernel'
20
+ // #1196 Phase B — this-typing against the real Kernel shape (was Self = any)
21
+ type Self = Kernel
21
22
 
22
23
  /**
23
24
  * Reactive reference to the debug bar component, set by the Kernel
@@ -57,9 +58,8 @@ export function setQdadmDebugBar(component: Component | null): void {
57
58
  /**
58
59
  * Patch Kernel prototype with Vue app creation methods.
59
60
  */
60
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
- export function applyVueMethods(KernelClass: { prototype: any }): void {
62
- const proto = KernelClass.prototype as Self
61
+ export function applyVueMethods(KernelClass: { prototype: Kernel }): void {
62
+ const proto = KernelClass.prototype
63
63
 
64
64
  /**
65
65
  * Create layout components map for useLayoutResolver
@@ -8,13 +8,13 @@ import { managerFactory, type ManagerFactoryContext } from '../entity/factory.js
8
8
  import type { EntityManager } from '../entity/EntityManager'
9
9
  import { UsersManager } from '../security/UsersManager'
10
10
  import type { UserEntityOptions } from './KernelContext.types'
11
+ import type { KernelContext } from './KernelContext'
11
12
 
12
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
- type Self = any
13
+ // #1196 Phase B — this-typing against the real KernelContext shape (was Self = any)
14
+ type Self = KernelContext
14
15
 
15
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
- export function applyEntityMethods(KernelContextClass: { prototype: any }): void {
17
- const proto = KernelContextClass.prototype as Self
16
+ export function applyEntityMethods(KernelContextClass: { prototype: KernelContext }): void {
17
+ const proto = KernelContextClass.prototype
18
18
 
19
19
  /**
20
20
  * Register an entity manager.
@@ -12,13 +12,13 @@ import type {
12
12
  HookHandler,
13
13
  SignalHandler,
14
14
  } from './KernelContext.types'
15
+ import type { KernelContext } from './KernelContext'
15
16
 
16
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
- type Self = any
17
+ // #1196 Phase B — this-typing against the real KernelContext shape (was Self = any)
18
+ type Self = KernelContext
18
19
 
19
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
- export function applyEventMethods(KernelContextClass: { prototype: any }): void {
21
- const proto = KernelContextClass.prototype as Self
20
+ export function applyEventMethods(KernelContextClass: { prototype: KernelContext }): void {
21
+ const proto = KernelContextClass.prototype
22
22
 
23
23
  proto.on = function (
24
24
  this: Self,
@@ -6,13 +6,13 @@
6
6
  */
7
7
 
8
8
  import type { AliasPattern, MessagesBundle, TranslationProvider } from '../i18n/types'
9
+ import type { KernelContext } from './KernelContext'
9
10
 
10
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
- type Self = any
11
+ // #1196 Phase B — this-typing against the real KernelContext shape (was Self = any)
12
+ type Self = KernelContext
12
13
 
13
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
14
- export function applyI18nMethods(KernelContextClass: { prototype: any }): void {
15
- const proto = KernelContextClass.prototype as Self
14
+ export function applyI18nMethods(KernelContextClass: { prototype: KernelContext }): void {
15
+ const proto = KernelContextClass.prototype
16
16
 
17
17
  proto.messages = function (this: Self, locale: string, bundle: MessagesBundle): Self {
18
18
  if (this._kernel.i18nInstance) {
@@ -5,13 +5,13 @@
5
5
  */
6
6
 
7
7
  import type { PermissionMeta, PermissionOptions } from './KernelContext.types'
8
+ import type { KernelContext } from './KernelContext'
8
9
 
9
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
- type Self = any
10
+ // #1196 Phase B — this-typing against the real KernelContext shape (was Self = any)
11
+ type Self = KernelContext
11
12
 
12
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
- export function applyPermissionMethods(KernelContextClass: { prototype: any }): void {
14
- const proto = KernelContextClass.prototype as Self
13
+ export function applyPermissionMethods(KernelContextClass: { prototype: KernelContext }): void {
14
+ const proto = KernelContextClass.prototype
15
15
 
16
16
  proto.permissions = function (
17
17
  this: Self,