vona-module-a-orm 5.1.39 → 5.1.41

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 (61) hide show
  1. package/dist/.metadata/index.d.ts +18 -18
  2. package/dist/.metadata/index.d.ts.map +1 -1
  3. package/dist/index.js +459 -58
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/aggregateResult.d.ts +7 -0
  6. package/dist/lib/aggregateResult.d.ts.map +1 -0
  7. package/dist/lib/analysis/controls.d.ts +27 -0
  8. package/dist/lib/analysis/controls.d.ts.map +1 -0
  9. package/dist/lib/analysis/execute.d.ts +6 -0
  10. package/dist/lib/analysis/execute.d.ts.map +1 -0
  11. package/dist/lib/analysis/index.d.ts +4 -0
  12. package/dist/lib/analysis/index.d.ts.map +1 -0
  13. package/dist/lib/analysis/policy.d.ts +6 -0
  14. package/dist/lib/analysis/policy.d.ts.map +1 -0
  15. package/dist/lib/bean.model/bean.model_cache.d.ts +4 -1
  16. package/dist/lib/bean.model/bean.model_cache.d.ts.map +1 -1
  17. package/dist/lib/bean.model/bean.model_crud_inner.d.ts +1 -1
  18. package/dist/lib/bean.model/bean.model_crud_inner.d.ts.map +1 -1
  19. package/dist/lib/dto/dto.d.ts +3 -0
  20. package/dist/lib/dto/dto.d.ts.map +1 -1
  21. package/dist/lib/dto/dtoAggregate.d.ts.map +1 -1
  22. package/dist/lib/dto/dtoAnalysis.d.ts +6 -0
  23. package/dist/lib/dto/dtoAnalysis.d.ts.map +1 -0
  24. package/dist/lib/dto/dtoCreate.d.ts +2 -2
  25. package/dist/lib/dto/dtoCreate.d.ts.map +1 -1
  26. package/dist/lib/dto/dtoGet.d.ts.map +1 -1
  27. package/dist/lib/dto/dtoMutate.d.ts +2 -2
  28. package/dist/lib/dto/dtoMutate.d.ts.map +1 -1
  29. package/dist/lib/dto/dtoUpdate.d.ts +2 -2
  30. package/dist/lib/dto/dtoUpdate.d.ts.map +1 -1
  31. package/dist/lib/index.d.ts +1 -0
  32. package/dist/lib/index.d.ts.map +1 -1
  33. package/dist/types/analysis.d.ts +105 -0
  34. package/dist/types/analysis.d.ts.map +1 -0
  35. package/dist/types/dto/dtoMutate.d.ts +28 -11
  36. package/dist/types/dto/dtoMutate.d.ts.map +1 -1
  37. package/dist/types/index.d.ts +1 -0
  38. package/dist/types/index.d.ts.map +1 -1
  39. package/dist/types/relationsAggr.d.ts +1 -1
  40. package/dist/types/relationsAggr.d.ts.map +1 -1
  41. package/package.json +2 -2
  42. package/src/.metadata/index.ts +26 -26
  43. package/src/lib/aggregateResult.ts +53 -0
  44. package/src/lib/analysis/controls.ts +163 -0
  45. package/src/lib/analysis/execute.ts +49 -0
  46. package/src/lib/analysis/index.ts +3 -0
  47. package/src/lib/analysis/policy.ts +140 -0
  48. package/src/lib/bean.model/bean.model_cache.ts +96 -12
  49. package/src/lib/bean.model/bean.model_crud_inner.ts +8 -3
  50. package/src/lib/dto/dto.ts +3 -0
  51. package/src/lib/dto/dtoAggregate.ts +4 -1
  52. package/src/lib/dto/dtoAnalysis.ts +34 -0
  53. package/src/lib/dto/dtoCreate.ts +7 -9
  54. package/src/lib/dto/dtoGet.ts +27 -3
  55. package/src/lib/dto/dtoMutate.ts +2 -2
  56. package/src/lib/dto/dtoUpdate.ts +7 -9
  57. package/src/lib/index.ts +1 -0
  58. package/src/types/analysis.ts +137 -0
  59. package/src/types/dto/dtoMutate.ts +124 -87
  60. package/src/types/index.ts +1 -0
  61. package/src/types/relationsAggr.ts +1 -1
@@ -18,6 +18,7 @@ import type {
18
18
  TypeModelWhere,
19
19
  } from '../../types/index.ts';
20
20
 
21
+ import { normalizeAggregateRows, normalizeAggregateValue } from '../aggregateResult.ts';
21
22
  import { BeanModelView } from './bean.model_view.ts';
22
23
 
23
24
  export class BeanModelCrudInner<TRecord extends {}> extends BeanModelView<TRecord> {
@@ -66,7 +67,8 @@ export class BeanModelCrudInner<TRecord extends {}> extends BeanModelView<TRecor
66
67
  }
67
68
 
68
69
  protected async _select<
69
- T extends IModelSelectParams<TRecord> | IBuildModelSelectParams<TRecord, undefined, never>,
70
+ T extends (IModelSelectParams<TRecord> | IBuildModelSelectParams<TRecord, undefined, never>) &
71
+ Pick<IModelSelectGeneralParams<TRecord>, 'aggrs'>,
70
72
  >(
71
73
  table?: keyof ITableRecord,
72
74
  params?: T,
@@ -89,7 +91,10 @@ export class BeanModelCrudInner<TRecord extends {}> extends BeanModelView<TRecor
89
91
  }
90
92
  return `model.select: ${tip}, ${builder.toQuery()}`;
91
93
  });
92
- return (await this.dialect.select(builder, datas, () => this.columns(table))) as TRecord[];
94
+ const rows = (await this.dialect.select(builder, datas, () =>
95
+ this.columns(table),
96
+ )) as TRecord[];
97
+ return normalizeAggregateRows(rows, params?.aggrs);
93
98
  }
94
99
 
95
100
  protected _select_buildParams<T extends IModelSelectGeneralParams<TRecord>>(
@@ -164,7 +169,7 @@ export class BeanModelCrudInner<TRecord extends {}> extends BeanModelView<TRecor
164
169
  // ready
165
170
  this.$loggerChild('model').debug(() => `model.count: ${builder.toQuery()}`);
166
171
  const res = await builder;
167
- return this.extractFirstNumber(res);
172
+ return normalizeAggregateValue(this.extractFirstValue(res), 'count');
168
173
  }
169
174
 
170
175
  protected async _increment(
@@ -1,4 +1,5 @@
1
1
  import { DtoAggregate } from './dtoAggregate.ts';
2
+ import { DtoAnalysisAggregate, DtoAnalysisGroup } from './dtoAnalysis.ts';
2
3
  import { DtoCreate } from './dtoCreate.ts';
3
4
  import { DtoGet } from './dtoGet.ts';
4
5
  import { DtoGroup } from './dtoGroup.ts';
@@ -15,6 +16,8 @@ export const $Dto = {
15
16
  mutate: DtoMutate,
16
17
  get: DtoGet,
17
18
  aggregate: DtoAggregate,
19
+ analysisGroup: DtoAnalysisGroup,
20
+ analysisAggregate: DtoAnalysisAggregate,
18
21
  group: DtoGroup,
19
22
  query: DtoQuery,
20
23
  queryPage: DtoQueryPage,
@@ -56,7 +56,10 @@ export function _DtoAggregate_inner<
56
56
  if (!columns) continue;
57
57
  for (const column of columns) {
58
58
  const column2 = `${key}_${column === '*' ? 'all' : column}`;
59
- Api.field(v.optional(), z.string())(classTarget.prototype, column2);
59
+ Api.field(v.optional(), key === 'count' ? z.string() : z.string().nullable())(
60
+ classTarget.prototype,
61
+ column2,
62
+ );
60
63
  }
61
64
  }
62
65
  return classTarget as any;
@@ -0,0 +1,34 @@
1
+ import type { Constructable } from 'vona';
2
+
3
+ import { $Class } from 'vona';
4
+
5
+ import type {
6
+ IAnalysisPolicy,
7
+ TypeAnalysisAggregateResult,
8
+ TypeAnalysisGroupResult,
9
+ } from '../../types/analysis.ts';
10
+
11
+ import { _DtoAggregate_inner } from './dtoAggregate.ts';
12
+ import { DtoGroup } from './dtoGroup.ts';
13
+
14
+ /** A finite optional superset: absent fields are unrequested, null retains SQL meaning. */
15
+ export function DtoAnalysisGroup<Policy extends IAnalysisPolicy>(
16
+ policy: Policy,
17
+ ): Constructable<TypeAnalysisGroupResult<Policy>> {
18
+ if (!policy.defaults.group) throw new TypeError('Analysis group action is not enabled');
19
+ const base = DtoGroup(policy.modelLike as any, [...policy.groups] as any, policy.aggrs as any);
20
+ return $Class.partial(base) as Constructable<TypeAnalysisGroupResult<Policy>>;
21
+ }
22
+
23
+ export function DtoAnalysisAggregate<Policy extends IAnalysisPolicy>(
24
+ policy: Policy,
25
+ ): Constructable<TypeAnalysisAggregateResult<Policy>> {
26
+ if (!policy.defaults.aggregate) throw new TypeError('Analysis aggregate action is not enabled');
27
+ abstract class TargetClass {}
28
+ // Aggregate aliases deliberately do not copy field validators/renderers from source columns.
29
+ return _DtoAggregate_inner(
30
+ TargetClass as any,
31
+ policy.modelLike as any,
32
+ policy.aggrs as any,
33
+ ) as Constructable<TypeAnalysisAggregateResult<Policy>>;
34
+ }
@@ -2,7 +2,7 @@ import type { Constructable } from 'vona';
2
2
 
3
3
  import { mergeTargetDecoratorDtoOpenapi } from 'vona-module-a-openapiutils';
4
4
 
5
- import type { IDtoMutateParams, TypeDtoMutateResult } from '../../types/dto/dtoMutate.ts';
5
+ import type { IDtoMutateParams, TypeDtoMutateClass } from '../../types/dto/dtoMutate.ts';
6
6
  import type { IModelClassRecord } from '../../types/onion/model.ts';
7
7
  import type { BeanModelMeta } from '../bean.model/bean.model_meta.ts';
8
8
 
@@ -16,14 +16,12 @@ export function DtoCreate<
16
16
  ? (() => Constructable<ModelLike>) | Constructable<ModelLike>
17
17
  : ModelLike,
18
18
  params?: T,
19
- ): Constructable<
20
- TypeDtoMutateResult<
21
- ModelLike,
22
- T,
23
- 'create',
24
- 'id' | 'iid' | 'deleted' | 'createdAt' | 'updatedAt',
25
- true
26
- >
19
+ ): TypeDtoMutateClass<
20
+ ModelLike,
21
+ T,
22
+ 'create',
23
+ 'id' | 'iid' | 'deleted' | 'createdAt' | 'updatedAt',
24
+ true
27
25
  > {
28
26
  const dtoClass = _DtoMutate_raw(
29
27
  modelLike,
@@ -1,7 +1,7 @@
1
1
  import type { Constructable } from 'vona';
2
2
 
3
3
  import { ensureArray, hashkey } from '@cabloy/utils';
4
- import { $Class, appResource } from 'vona';
4
+ import { $Class, appMetadata, appResource, registerMappedClassMetadataKey } from 'vona';
5
5
  import {
6
6
  addSchemaDynamic,
7
7
  Api,
@@ -24,6 +24,7 @@ import {
24
24
  prepareClassModel,
25
25
  prepareColumns,
26
26
  } from '../../common/utils.ts';
27
+ import { SymbolDtoMutateRelations } from '../../types/dto/dtoMutate.ts';
27
28
  import { handleRelationsCollection } from '../utils.ts';
28
29
  import { DtoAggregate } from './dtoAggregate.ts';
29
30
  import { DtoGroup } from './dtoGroup.ts';
@@ -63,10 +64,33 @@ export function _DtoGet_relations<TRecord extends {}, TModel extends BeanModelMe
63
64
  includeWrapper?: IModelRelationIncludeWrapper,
64
65
  mutateTypeTopLevel?: TypeDtoMutateType,
65
66
  ) {
66
- // relations
67
67
  const relations = _DtoGet_relations_collection(modelClass, includeWrapper);
68
- if (!relations) return;
68
+ // Mapped-class metadata follows the dtoClass field projection, including pick/omit.
69
+ registerMappedClassMetadataKey(entityClass.prototype, SymbolDtoMutateRelations, {});
70
+ const retained = appMetadata.getOwnMetadataMap<string, IRelationItem>(
71
+ true,
72
+ SymbolDtoMutateRelations,
73
+ entityClass.prototype,
74
+ );
75
+ if (mutateTypeTopLevel) {
76
+ const overridden = new Set(relations.map(relation => relation[0]));
77
+ for (const name in retained) {
78
+ if (!overridden.has(name)) {
79
+ _DtoGet_relation_handle(entityClass, retained[name], mutateTypeTopLevel);
80
+ }
81
+ }
82
+ }
69
83
  for (const relation of relations) {
84
+ const [name, definition] = relation;
85
+ if (
86
+ mutateTypeTopLevel &&
87
+ definition !== false &&
88
+ (definition.type === 'hasOne' || definition.type === 'hasMany')
89
+ ) {
90
+ retained[name] = relation;
91
+ } else {
92
+ delete retained[name];
93
+ }
70
94
  _DtoGet_relation_handle(entityClass, relation, mutateTypeTopLevel);
71
95
  }
72
96
  }
@@ -6,7 +6,7 @@ import { getTargetDecoratorRuleColumns } from 'vona-module-a-openapiutils';
6
6
 
7
7
  import type {
8
8
  IDtoMutateParams,
9
- TypeDtoMutateResult,
9
+ TypeDtoMutateClass,
10
10
  TypeDtoMutateType,
11
11
  } from '../../types/dto/dtoMutate.ts';
12
12
  import type { TypeModelColumnsStrict } from '../../types/modelWhere.ts';
@@ -30,7 +30,7 @@ export function DtoMutate<
30
30
  ? (() => Constructable<ModelLike>) | Constructable<ModelLike>
31
31
  : ModelLike,
32
32
  params?: T,
33
- ): Constructable<TypeDtoMutateResult<ModelLike, T, 'mutate', undefined, true>> {
33
+ ): TypeDtoMutateClass<ModelLike, T, 'mutate', undefined, true> {
34
34
  return _DtoMutate_raw(modelLike, params, 'mutate', undefined, true, undefined);
35
35
  }
36
36
 
@@ -2,7 +2,7 @@ import type { Constructable } from 'vona';
2
2
 
3
3
  import { mergeTargetDecoratorDtoOpenapi } from 'vona-module-a-openapiutils';
4
4
 
5
- import type { IDtoMutateParams, TypeDtoMutateResult } from '../../types/dto/dtoMutate.ts';
5
+ import type { IDtoMutateParams, TypeDtoMutateClass } from '../../types/dto/dtoMutate.ts';
6
6
  import type { IModelClassRecord } from '../../types/onion/model.ts';
7
7
  import type { BeanModelMeta } from '../bean.model/bean.model_meta.ts';
8
8
 
@@ -16,14 +16,12 @@ export function DtoUpdate<
16
16
  ? (() => Constructable<ModelLike>) | Constructable<ModelLike>
17
17
  : ModelLike,
18
18
  params?: T,
19
- ): Constructable<
20
- TypeDtoMutateResult<
21
- ModelLike,
22
- T,
23
- 'update',
24
- 'id' | 'iid' | 'deleted' | 'createdAt' | 'updatedAt',
25
- true
26
- >
19
+ ): TypeDtoMutateClass<
20
+ ModelLike,
21
+ T,
22
+ 'update',
23
+ 'id' | 'iid' | 'deleted' | 'createdAt' | 'updatedAt',
24
+ true
27
25
  > {
28
26
  const dtoClass = _DtoMutate_raw(
29
27
  modelLike,
package/src/lib/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './analysis/index.ts';
1
2
  export * from './const.ts';
2
3
  export * from './databaseDialect.ts';
3
4
  export * from './dto/index.ts';
@@ -0,0 +1,137 @@
1
+ import type { Constructable } from 'vona';
2
+
3
+ import type { BeanModelMeta } from '../lib/bean.model/bean.model_meta.ts';
4
+ import type { TypeModelWhere } from './modelWhere.ts';
5
+ import type { IModelClassRecord } from './onion/model.ts';
6
+ import type { TypeModelOfModelLike, TypeSymbolKeyEntity } from './relations.ts';
7
+
8
+ export type TypeAnalysisMode = 'group' | 'aggregate';
9
+ export type TypeAnalysisFunction = 'count' | 'sum' | 'avg' | 'min' | 'max';
10
+ export type TypeAnalysisModelLike =
11
+ | Constructable<BeanModelMeta>
12
+ | (() => Constructable<BeanModelMeta>)
13
+ | keyof IModelClassRecord;
14
+ export type TypeAnalysisEntity<ModelLike extends TypeAnalysisModelLike> =
15
+ TypeModelOfModelLike<ModelLike>[TypeSymbolKeyEntity];
16
+ export type TypeAnalysisField<Entity> = Extract<keyof Entity, string>;
17
+ export type TypeAnalysisNumericField<Entity> = Exclude<
18
+ {
19
+ [K in keyof Entity]-?: NonNullable<Entity[K]> extends number ? K : never;
20
+ }[keyof Entity],
21
+ 'id' | 'iid'
22
+ > &
23
+ string;
24
+
25
+ export type TypeAnalysisAggrs = Partial<Record<TypeAnalysisFunction, readonly string[]>>;
26
+ export type TypeAnalysisAllowedAggrs<Entity> = {
27
+ readonly count?: readonly ('*' | TypeAnalysisField<Entity>)[];
28
+ } & {
29
+ readonly [
30
+ K in Exclude<TypeAnalysisFunction, 'count'>
31
+ ]?: readonly TypeAnalysisNumericField<Entity>[];
32
+ };
33
+ export type TypeAnalysisSelectionAggrs<Aggrs extends TypeAnalysisAggrs> = {
34
+ readonly [K in keyof Aggrs]?: NonNullable<Aggrs[K]> extends readonly string[]
35
+ ? readonly NonNullable<Aggrs[K]>[number][]
36
+ : never;
37
+ };
38
+ export interface IAnalysisLimits {
39
+ readonly maxGroups: number;
40
+ readonly maxMetrics: number;
41
+ readonly maxGroupRows: number;
42
+ }
43
+ export interface IAnalysisSelection {
44
+ readonly groups?: readonly string[];
45
+ readonly aggrs: TypeAnalysisAggrs;
46
+ }
47
+
48
+ /** Structural server-owned policy surface; model references must not be serialized. */
49
+ export interface IAnalysisPolicy {
50
+ readonly modelLike: TypeAnalysisModelLike;
51
+ readonly modelClass: Constructable<BeanModelMeta>;
52
+ readonly groups: readonly string[];
53
+ readonly aggrs: TypeAnalysisAggrs;
54
+ readonly defaults: {
55
+ readonly group?: IAnalysisSelection & { readonly groups: readonly string[] };
56
+ readonly aggregate?: { readonly aggrs: TypeAnalysisAggrs };
57
+ };
58
+ readonly limits: IAnalysisLimits;
59
+ }
60
+ export type TypeAnalysisPolicy<
61
+ ModelLike extends TypeAnalysisModelLike,
62
+ Groups extends readonly string[],
63
+ Aggrs extends TypeAnalysisAggrs,
64
+ > = Omit<IAnalysisPolicy, 'modelLike' | 'groups' | 'aggrs'> & {
65
+ readonly modelLike: ModelLike;
66
+ readonly groups: Groups;
67
+ readonly aggrs: Aggrs;
68
+ };
69
+ export interface IAnalysisConfig<
70
+ Groups extends readonly string[],
71
+ Aggrs extends TypeAnalysisAggrs,
72
+ > {
73
+ readonly groups: Groups;
74
+ readonly aggrs: Aggrs;
75
+ readonly defaults: {
76
+ readonly group?: {
77
+ readonly groups: readonly NoInfer<Groups[number]>[];
78
+ readonly aggrs: TypeAnalysisSelectionAggrs<NoInfer<Aggrs>>;
79
+ };
80
+ readonly aggregate?: { readonly aggrs: TypeAnalysisSelectionAggrs<NoInfer<Aggrs>> };
81
+ };
82
+ readonly limits?: Partial<IAnalysisLimits>;
83
+ }
84
+
85
+ interface IAnalysisParams<Policy extends IAnalysisPolicy> {
86
+ where?: TypeModelWhere<TypeAnalysisEntity<Policy['modelLike']>>;
87
+ aggrs?: TypeAnalysisSelectionAggrs<Policy['aggrs']>;
88
+ // Existing list controls remain accepted but never drive analysis execution.
89
+ columns?: unknown;
90
+ orders?: unknown;
91
+ limit?: number;
92
+ offset?: number;
93
+ pageNo?: number;
94
+ pageSize?: number;
95
+ joins?: never;
96
+ having?: never;
97
+ distinct?: never;
98
+ include?: never;
99
+ with?: never;
100
+ }
101
+ export type TypeAnalysisGroupParams<Policy extends IAnalysisPolicy> = IAnalysisParams<Policy> & {
102
+ groups?: readonly Policy['groups'][number][];
103
+ };
104
+ export type TypeAnalysisAggregateParams<Policy extends IAnalysisPolicy> =
105
+ IAnalysisParams<Policy> & {
106
+ groups?: never;
107
+ };
108
+
109
+ type TypeAnalysisAlias<Column extends string> = Column extends '*' ? 'all' : Column;
110
+ export type TypeAnalysisAggregateResult<Policy extends IAnalysisPolicy> = {
111
+ -readonly [
112
+ Fn in keyof Policy['aggrs'] &
113
+ TypeAnalysisFunction as Policy['aggrs'][Fn] extends readonly string[]
114
+ ? `${Fn}_${TypeAnalysisAlias<Policy['aggrs'][Fn][number]>}`
115
+ : never
116
+ ]?: Fn extends 'count' ? string : string | null;
117
+ };
118
+ export type TypeAnalysisGroupResult<Policy extends IAnalysisPolicy> = Partial<
119
+ Pick<
120
+ TypeAnalysisEntity<Policy['modelLike']>,
121
+ Extract<Policy['groups'][number], keyof TypeAnalysisEntity<Policy['modelLike']>>
122
+ >
123
+ > &
124
+ TypeAnalysisAggregateResult<Policy>;
125
+
126
+ export interface IAnalysisCapabilities {
127
+ readonly mode: TypeAnalysisMode;
128
+ readonly groups: readonly string[];
129
+ readonly aggrs: TypeAnalysisAggrs;
130
+ readonly defaults: IAnalysisSelection;
131
+ readonly limits: IAnalysisLimits;
132
+ }
133
+
134
+ export interface IResolvedAnalysis {
135
+ groups?: string[];
136
+ aggrs: Partial<Record<TypeAnalysisFunction, string[]>>;
137
+ }
@@ -25,8 +25,33 @@ import type {
25
25
  TypeUtilPrepareColumns,
26
26
  } from '../relations.ts';
27
27
 
28
+ export const SymbolDtoMutateRelations = Symbol('SymbolDtoMutateRelations');
29
+
28
30
  export type TypeDtoMutateType = 'create' | 'update' | 'mutate';
29
31
 
32
+ // Static phantom metadata survives ordinary subclassing without becoming a DTO field.
33
+ // Runtime stores the corresponding resolved relation plans as mapped-class metadata.
34
+ export type TypeDtoMutateClass<
35
+ ModelLike extends BeanModelMeta | keyof IModelClassRecord,
36
+ TOptionsRelation,
37
+ TMutateTypeTopLevel extends TypeDtoMutateType,
38
+ TColumnsOmitDefault extends string | string[] | undefined = undefined,
39
+ TTopLevel extends boolean | undefined = undefined,
40
+ > = Constructable<
41
+ TypeDtoMutateResult<
42
+ ModelLike,
43
+ TOptionsRelation,
44
+ TMutateTypeTopLevel,
45
+ TColumnsOmitDefault,
46
+ TTopLevel
47
+ >
48
+ > & {
49
+ [SymbolDtoMutateRelations]?: TypeDtoMutateOwnedRelations<
50
+ TypeModelOfModelLike<ModelLike>,
51
+ TOptionsRelation
52
+ >;
53
+ };
54
+
30
55
  export type IDtoMutateParams<ModelLike extends BeanModelMeta | keyof IModelClassRecord> =
31
56
  IBuildDtoMutateParams<
32
57
  TypeModelOfModelLike<ModelLike>[TypeSymbolKeyEntity],
@@ -65,100 +90,112 @@ export type TypeDtoMutateRelationResult<
65
90
  TTopLevel extends boolean | undefined = undefined,
66
91
  TColumns = undefined,
67
92
  REFKEY = undefined,
68
- > = (TOptionsRelation extends { dtoClass: Constructable<infer TDto> }
69
- ? TypeDtoMutateRelationResultDto<TDto, TMutateTypeTopLevel, TTopLevel, REFKEY>
70
- : TypeDtoMutateRelationResultEntity<
71
- TRecord,
72
- TColumns extends string | string[] ? TColumns : TypeUtilGetParamsColumns<TOptionsRelation>,
73
- TMutateTypeTopLevel,
74
- TColumnsOmitDefault,
75
- TTopLevel,
76
- REFKEY
77
- >) &
78
- (TModel extends BeanModelMeta
79
- ? OmitNever<
80
- TypeDtoMutateRelationResultMergeInclude<
81
- TMutateTypeTopLevel,
82
- TypeUtilGetModelOptions<TModel>,
83
- TypeUtilGetParamsInlcude<TOptionsRelation>
84
- >
85
- > &
86
- OmitNever<
87
- TypeDtoMutateModelRelationResultMergeWith<
88
- TMutateTypeTopLevel,
89
- TypeUtilGetParamsWith<TOptionsRelation>
90
- >
91
- >
92
- : {});
93
-
94
- type TypeDtoMutateRelationResultMergeInclude<
95
- TMutateTypeTopLevel extends TypeDtoMutateType,
96
- TModelOptions extends IDecoratorModelOptions,
97
- TInclude extends {} | undefined | unknown,
98
- > = {
99
- [RelationName in keyof TModelOptions['relations']]: TInclude extends {}
100
- ? TInclude[RelationName] extends {} | boolean
101
- ? TypeDtoMutateRelationResultMergeIncludeWrapper<
102
- TMutateTypeTopLevel,
103
- TModelOptions['relations'][RelationName],
104
- TInclude[RelationName]
105
- >
106
- : TypeDtoMutateRelationResultMergeAutoload<
107
- TMutateTypeTopLevel,
108
- TModelOptions['relations'][RelationName]
109
- >
110
- : TypeDtoMutateRelationResultMergeAutoload<
93
+ > = TypeDtoMutateMergeRelations<
94
+ TOptionsRelation extends { dtoClass: Constructable<infer TDto> }
95
+ ? TypeDtoMutateRelationResultDto<TDto, TMutateTypeTopLevel, TTopLevel, REFKEY>
96
+ : TypeDtoMutateRelationResultEntity<
97
+ TRecord,
98
+ TColumns extends string | string[] ? TColumns : TypeUtilGetParamsColumns<TOptionsRelation>,
111
99
  TMutateTypeTopLevel,
112
- TModelOptions['relations'][RelationName]
113
- >;
114
- };
100
+ TColumnsOmitDefault,
101
+ TTopLevel,
102
+ REFKEY
103
+ >,
104
+ TypeDtoMutateRelationPlans<TModel, TOptionsRelation>,
105
+ TMutateTypeTopLevel
106
+ >;
115
107
 
116
- type TypeDtoMutateModelRelationResultMergeWith<
117
- TMutateTypeTopLevel extends TypeDtoMutateType,
118
- TWith extends {} | undefined | unknown,
119
- > = TWith extends {}
120
- ? {
121
- [RelationName in keyof TWith]: TypeDtoMutateRelationResultMergeWithRelation<
122
- TMutateTypeTopLevel,
123
- TWith[RelationName]
124
- >;
125
- }
108
+ // Plans describe only declared relations, never arbitrary object/array schema fields.
109
+ // A fresh include/with/autoload selection replaces the whole retained plan of that name.
110
+ type TypeDtoMutateIncludePlans<TModelOptions extends IDecoratorModelOptions, TInclude> = OmitNever<{
111
+ [Name in keyof TModelOptions['relations']]: TInclude extends {}
112
+ ? TInclude[Name] extends false
113
+ ? false
114
+ : TInclude[Name] extends true
115
+ ? [TModelOptions['relations'][Name], undefined]
116
+ : TInclude[Name] extends {}
117
+ ? [TModelOptions['relations'][Name], TInclude[Name]]
118
+ : TypeDtoMutateAutoloadPlan<TModelOptions['relations'][Name]>
119
+ : TypeDtoMutateAutoloadPlan<TModelOptions['relations'][Name]>;
120
+ }>;
121
+
122
+ type TypeDtoMutateAutoloadPlan<Relation> =
123
+ TypeUtilGetRelationOptionsAutoload<Relation> extends true ? [Relation, undefined] : never;
124
+
125
+ type TypeDtoMutateWithPlans<TWith> = TWith extends {}
126
+ ? OmitNever<{
127
+ [Name in keyof TWith]: TWith[Name] extends boolean
128
+ ? never
129
+ : TWith[Name] extends {}
130
+ ? [TWith[Name], TypeUtilGetRelationOptions<TWith[Name]>]
131
+ : never;
132
+ }>
126
133
  : {};
127
134
 
128
- type TypeDtoMutateRelationResultMergeIncludeWrapper<
129
- TMutateTypeTopLevel extends TypeDtoMutateType,
130
- Relation,
131
- IncludeWrapper,
132
- > = IncludeWrapper extends false
133
- ? never
134
- : IncludeWrapper extends true
135
- ? TypeUtilGetDtoMutateRelationEntityByType<TMutateTypeTopLevel, Relation, undefined>
136
- : IncludeWrapper extends {}
137
- ? TypeUtilGetDtoMutateRelationEntityByType<TMutateTypeTopLevel, Relation, IncludeWrapper>
138
- : never;
135
+ type TypeDtoMutateFreshPlans<
136
+ TModel extends BeanModelMeta | undefined,
137
+ TOptionsRelation,
138
+ TWith = TypeDtoMutateWithPlans<TypeUtilGetParamsWith<TOptionsRelation>>,
139
+ > = Omit<
140
+ TModel extends BeanModelMeta
141
+ ? TypeDtoMutateIncludePlans<
142
+ TypeUtilGetModelOptions<TModel>,
143
+ TypeUtilGetParamsInlcude<TOptionsRelation>
144
+ >
145
+ : {},
146
+ keyof TWith
147
+ > &
148
+ TWith;
139
149
 
140
- type TypeDtoMutateRelationResultMergeAutoload<
141
- TMutateTypeTopLevel extends TypeDtoMutateType,
142
- Relation,
143
- > =
144
- TypeUtilGetRelationOptionsAutoload<Relation> extends true
145
- ? TypeUtilGetDtoMutateRelationEntityByType<TMutateTypeTopLevel, Relation, undefined>
150
+ type TypeDtoMutateRetainedPlans<TOptionsRelation> = TOptionsRelation extends {
151
+ dtoClass: infer TClass extends Constructable<unknown>;
152
+ }
153
+ ? typeof SymbolDtoMutateRelations extends keyof TClass
154
+ ? Pick<
155
+ NonNullable<TClass[typeof SymbolDtoMutateRelations]>,
156
+ Extract<
157
+ keyof NonNullable<TClass[typeof SymbolDtoMutateRelations]>,
158
+ keyof InstanceType<TClass>
159
+ >
160
+ >
161
+ : {}
162
+ : {};
163
+
164
+ type TypeDtoMutateRelationPlans<
165
+ TModel extends BeanModelMeta | undefined,
166
+ TOptionsRelation,
167
+ TFresh = TypeDtoMutateFreshPlans<TModel, TOptionsRelation>,
168
+ > = Omit<TypeDtoMutateRetainedPlans<TOptionsRelation>, keyof TFresh> & TFresh;
169
+
170
+ type TypeDtoMutateOwnedRelations<
171
+ TModel extends BeanModelMeta | undefined,
172
+ TOptionsRelation,
173
+ TPlans = TypeDtoMutateRelationPlans<TModel, TOptionsRelation>,
174
+ > = OmitNever<{
175
+ [Name in keyof TPlans]: TPlans[Name] extends [infer Relation, unknown]
176
+ ? TypeUtilGetRelationType<Relation> extends 'hasOne' | 'hasMany'
177
+ ? TPlans[Name]
178
+ : never
146
179
  : never;
180
+ }>;
147
181
 
148
- type TypeDtoMutateRelationResultMergeWithRelation<
149
- TMutateTypeTopLevel extends TypeDtoMutateType,
150
- WithRelation,
151
- > = WithRelation extends false
152
- ? never
153
- : WithRelation extends true
154
- ? never
155
- : WithRelation extends {}
156
- ? TypeUtilGetDtoMutateRelationEntityByType<
157
- TMutateTypeTopLevel,
158
- WithRelation,
159
- TypeUtilGetRelationOptions<WithRelation>
160
- >
161
- : never;
182
+ type TypeDtoMutateProjectRelations<TPlans, TMutateType extends TypeDtoMutateType> = OmitNever<{
183
+ [Name in keyof TPlans]: TPlans[Name] extends [infer Relation, infer IncludeWrapper]
184
+ ? TypeUtilGetDtoMutateRelationEntityByType<TMutateType, Relation, IncludeWrapper>
185
+ : never;
186
+ }>;
187
+
188
+ type TypeDtoMutateDisabledRelations<TPlans> = {
189
+ [Name in keyof TPlans]: TPlans[Name] extends false ? Name : never;
190
+ }[keyof TPlans];
191
+
192
+ type TypeDtoMutateMergeRelations<
193
+ TBase,
194
+ TPlans,
195
+ TMutateType extends TypeDtoMutateType,
196
+ TRelations = TypeDtoMutateProjectRelations<TPlans, TMutateType>,
197
+ TDisabled extends keyof TBase = Extract<keyof TBase, TypeDtoMutateDisabledRelations<TPlans>>,
198
+ > = Omit<TBase, keyof TRelations | TDisabled> & Partial<Pick<TBase, TDisabled>> & TRelations;
162
199
 
163
200
  // Relation options supply the default dtoClass; an include wrapper may override it.
164
201
  type TypeUtilGetDtoMutateRelationParams<Relation, IncludeWrapper> = IncludeWrapper extends {
@@ -1,3 +1,4 @@
1
+ export * from './analysis.ts';
1
2
  export * from './columns.ts';
2
3
  export * from './config.ts';
3
4
  export * from './database.ts';
@@ -27,7 +27,7 @@ export type TypeUtilAggrPrepareColumns<TColumns> = TColumns extends string[]
27
27
  : undefined;
28
28
  export type TypeRecordAggrsValues<TRecord extends Record<string, any>> = TRecord[keyof TRecord];
29
29
  export type TypeRecordAggrsValuesToObject<AggrValues extends string> = {
30
- [K in AggrValues]: string | undefined;
30
+ [K in AggrValues]: K extends `count_${string}` ? string | undefined : string | null | undefined;
31
31
  };
32
32
 
33
33
  export type TypeUtilGetAggrsFromRelationAndIncludeWrapper<