@nest-yalc-2/omnikernel-module 2.2.2 → 2.2.4
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 +17 -15
- package/src/__tests__/omni-collection.entity.spec.ts +49 -49
- package/src/__tests__/omni-collection.service.spec.ts +83 -83
- package/src/__tests__/omni-document.entity.spec.ts +55 -55
- package/src/__tests__/omni-document.service.spec.ts +83 -83
- package/src/__tests__/omni-extension-projection-constraints.spec.ts +518 -518
- package/src/__tests__/omni-extension-projection.registration.spec.ts +947 -946
- package/src/__tests__/omni-extension-projection.service.spec.ts +806 -806
- package/src/__tests__/omni-external-ref.entity.spec.ts +36 -36
- package/src/__tests__/omni-external-ref.service.spec.ts +252 -252
- package/src/__tests__/omni-named.entity.spec.ts +33 -33
- package/src/__tests__/omni-record.entity.spec.ts +32 -32
- package/src/__tests__/omni-record.service.spec.ts +107 -107
- package/src/__tests__/omni-relation-semantics.spec.ts +102 -102
- package/src/__tests__/omni-relation.entity.spec.ts +38 -38
- package/src/__tests__/omni-scope-substrate.spec.ts +81 -81
- package/src/__tests__/omni-scoped.backend.spec.ts +115 -115
- package/src/__tests__/omni-scoped.service.spec.ts +354 -354
- package/src/__tests__/omnikernel.diagnostics.spec.ts +94 -94
- package/src/__tests__/omnikernel.module.spec.ts +38 -40
- package/src/__tests__/omnikernel.persistence.spec.ts +154 -154
- package/src/__tests__/omnikernel.public-api.spec.ts +238 -238
- package/src/__tests__/omnikernel.query.service.spec.ts +155 -155
- package/src/base/omni-base.entity.ts +41 -41
- package/src/base/omni-external-ref.entity.ts +53 -53
- package/src/base/omni-named.entity.ts +16 -16
- package/src/base/omni-record.entity.ts +51 -51
- package/src/base/omni-relation.entity.ts +72 -72
- package/src/index.ts +49 -49
- package/src/omni-collection-kind.enum.ts +10 -10
- package/src/omni-collection.backend.ts +16 -16
- package/src/omni-collection.dto.ts +170 -170
- package/src/omni-collection.entity.ts +21 -21
- package/src/omni-collection.service.ts +75 -75
- package/src/omni-document-kind.enum.ts +12 -12
- package/src/omni-document.backend.ts +16 -16
- package/src/omni-document.dto.ts +198 -198
- package/src/omni-document.entity.ts +30 -30
- package/src/omni-document.service.ts +75 -75
- package/src/omni-dto.helpers.ts +21 -21
- package/src/omni-extension-projection.definition.ts +294 -294
- package/src/omni-extension-projection.resource.ts +274 -274
- package/src/omni-extension-projection.service.ts +452 -452
- package/src/omni-external-ref-binding.validator.ts +53 -53
- package/src/omni-external-ref-internal-type.enum.ts +11 -11
- package/src/omni-external-ref.backend.ts +25 -25
- package/src/omni-external-ref.dto.ts +128 -128
- package/src/omni-external-ref.service.ts +255 -255
- package/src/omni-migration.ts +365 -365
- package/src/omni-named.backend.ts +11 -11
- package/src/omni-named.dto.ts +86 -86
- package/src/omni-projection.catalog.ts +345 -345
- package/src/omni-projection.lifecycle.ts +53 -53
- package/src/omni-record-status.enum.ts +11 -11
- package/src/omni-record.backend.ts +19 -19
- package/src/omni-record.dto.ts +157 -157
- package/src/omni-record.service.ts +113 -113
- package/src/omni-relation-kind.contract.ts +49 -49
- package/src/omni-relation-kind.enum.ts +12 -12
- package/src/omni-relation-projection.definition.ts +166 -166
- package/src/omni-relation-projection.resource.ts +360 -360
- package/src/omni-relation-projection.service.ts +610 -610
- package/src/omni-relation-semantics.ts +52 -52
- package/src/omni-relation-status.enum.ts +11 -11
- package/src/omni-relation.backend.ts +21 -21
- package/src/omni-relation.dto.ts +142 -142
- package/src/omni-relation.service.ts +159 -159
- package/src/omni-scope.ts +161 -161
- package/src/omni-scoped.backend.ts +110 -110
- package/src/omni-scoped.repository.ts +34 -34
- package/src/omni-scoped.service.ts +338 -338
- package/src/omnikernel.diagnostics.ts +83 -83
- package/src/omnikernel.module.ts +97 -97
- package/src/omnikernel.query.service.ts +108 -108
|
@@ -1,360 +1,360 @@
|
|
|
1
|
-
import { Scope, type InjectionToken, type Provider } from '@nestjs/common';
|
|
2
|
-
import { InputType, Int, ObjectType } from '@nestjs/graphql';
|
|
3
|
-
import { getDataSourceToken } from '@nestjs/typeorm';
|
|
4
|
-
import {
|
|
5
|
-
CrudGenResourceFactory,
|
|
6
|
-
FilterOptionType,
|
|
7
|
-
ModelField,
|
|
8
|
-
ModelObject,
|
|
9
|
-
getServiceToken,
|
|
10
|
-
type ICrudGenResourceFactoryResult,
|
|
11
|
-
} from '@nest-yalc-2/crud-gen';
|
|
12
|
-
import {
|
|
13
|
-
GQLDataLoader,
|
|
14
|
-
getDataloaderToken,
|
|
15
|
-
getFn,
|
|
16
|
-
} from '@nest-yalc-2/data-loader';
|
|
17
|
-
import { UUIDScalar } from '@nest-yalc-2/graphql/scalars/uuid.scalar.js';
|
|
18
|
-
import type { ClassType } from '@node-yalc/types/globals.js';
|
|
19
|
-
import returnValue from '@node-yalc/utils/returnValue.js';
|
|
20
|
-
import { Exclude, Expose } from 'class-transformer';
|
|
21
|
-
import { GraphQLJSON } from 'graphql-type-json';
|
|
22
|
-
import type { DataSource, ObjectLiteral } from 'typeorm';
|
|
23
|
-
import { OmniRecordEntity } from './base/omni-record.entity.js';
|
|
24
|
-
import { OmniRelationEntity } from './base/omni-relation.entity.js';
|
|
25
|
-
import type {
|
|
26
|
-
OmniProjectionReaderCatalogProvider,
|
|
27
|
-
OmniRelationProjectionReaderRegistration,
|
|
28
|
-
} from './omni-projection.catalog.js';
|
|
29
|
-
import {
|
|
30
|
-
getOmniRelationProjectionAliases,
|
|
31
|
-
getOmniRelationProjectionAllowedKinds,
|
|
32
|
-
type OmniRelationProjectionDefinition,
|
|
33
|
-
} from './omni-relation-projection.definition.js';
|
|
34
|
-
import type { OmniProjectionLifecycleProvider } from './omni-projection.lifecycle.js';
|
|
35
|
-
import { OmniRelationProjectionService } from './omni-relation-projection.service.js';
|
|
36
|
-
import { createOmniRelationKindContract } from './omni-relation-kind.contract.js';
|
|
37
|
-
import { OMNI_KERNEL_OPTIONS, OmniScopeContext } from './omni-scope.js';
|
|
38
|
-
|
|
39
|
-
type RelationGraphqlClass = new () => Record<string, unknown>;
|
|
40
|
-
|
|
41
|
-
export interface OmniRelationProjectionGraphqlNames {
|
|
42
|
-
object: string;
|
|
43
|
-
create: string;
|
|
44
|
-
patch: string;
|
|
45
|
-
conditions: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface OmniRelationProjectionRegistrationOptions<
|
|
49
|
-
Api extends ObjectLiteral,
|
|
50
|
-
> {
|
|
51
|
-
/** API-only model used by CrudGen to derive operation names and tokens. */
|
|
52
|
-
apiModel: ClassType<Api>;
|
|
53
|
-
definition: OmniRelationProjectionDefinition;
|
|
54
|
-
dbConnection: string;
|
|
55
|
-
/** Shared catalog provider, normally built from all returned registrations. */
|
|
56
|
-
catalog?: OmniProjectionReaderCatalogProvider;
|
|
57
|
-
/** Request-scoped app policy injected into the generated service factory. */
|
|
58
|
-
lifecycle?: OmniProjectionLifecycleProvider<
|
|
59
|
-
OmniRelationProjectionDefinition,
|
|
60
|
-
OmniRelationEntity
|
|
61
|
-
>;
|
|
62
|
-
rest?: {
|
|
63
|
-
path?: string;
|
|
64
|
-
};
|
|
65
|
-
graphql?: {
|
|
66
|
-
prefix?: string;
|
|
67
|
-
names?: OmniRelationProjectionGraphqlNames;
|
|
68
|
-
};
|
|
69
|
-
/** Optional consumer-owned ModuleRef token for generated GraphQL DI. */
|
|
70
|
-
moduleRefToken?: InjectionToken;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export interface OmniRelationProjectionGraphqlTypes {
|
|
74
|
-
object: RelationGraphqlClass;
|
|
75
|
-
create: RelationGraphqlClass;
|
|
76
|
-
patch: RelationGraphqlClass;
|
|
77
|
-
conditions: RelationGraphqlClass;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export interface OmniRelationProjectionRegistration<Api extends ObjectLiteral> {
|
|
81
|
-
/** omni-relation already belongs to OmniKernelModule; no new table exists. */
|
|
82
|
-
entities: readonly [];
|
|
83
|
-
/** Include these in OmniKernelModule.register({ relationKinds }). */
|
|
84
|
-
relationKinds: readonly string[];
|
|
85
|
-
definition: OmniRelationProjectionDefinition;
|
|
86
|
-
reader: OmniRelationProjectionReaderRegistration;
|
|
87
|
-
graphqlTypes: OmniRelationProjectionGraphqlTypes;
|
|
88
|
-
resource: ICrudGenResourceFactoryResult<Api>;
|
|
89
|
-
controllers: readonly ClassType[];
|
|
90
|
-
providers: readonly Provider[];
|
|
91
|
-
serviceToken: string;
|
|
92
|
-
dataLoaderToken: string;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function namedClass(
|
|
96
|
-
name: string,
|
|
97
|
-
outputFields?: readonly (readonly [string, string])[],
|
|
98
|
-
): RelationGraphqlClass {
|
|
99
|
-
return {
|
|
100
|
-
[name]: class {
|
|
101
|
-
constructor(data?: Record<string, unknown>) {
|
|
102
|
-
if (!outputFields) {
|
|
103
|
-
Object.assign(this, data);
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
for (const [publicName, destination] of outputFields) {
|
|
107
|
-
if (data && Object.prototype.hasOwnProperty.call(data, destination)) {
|
|
108
|
-
(this as Record<string, unknown>)[publicName] = data[destination];
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
},
|
|
113
|
-
}[name] as RelationGraphqlClass;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function addField(
|
|
117
|
-
target: RelationGraphqlClass,
|
|
118
|
-
name: string,
|
|
119
|
-
destination: string,
|
|
120
|
-
type: unknown,
|
|
121
|
-
nullable: boolean,
|
|
122
|
-
): void {
|
|
123
|
-
ModelField({
|
|
124
|
-
dst: destination,
|
|
125
|
-
gqlType: returnValue(type),
|
|
126
|
-
gqlOptions: { nullable },
|
|
127
|
-
})(target.prototype, name);
|
|
128
|
-
Expose()(target.prototype, name);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function defaultGraphqlNames(
|
|
132
|
-
apiModel: ClassType,
|
|
133
|
-
): OmniRelationProjectionGraphqlNames {
|
|
134
|
-
return {
|
|
135
|
-
object: apiModel.name,
|
|
136
|
-
create: `${apiModel.name}CreateInput`,
|
|
137
|
-
patch: `${apiModel.name}UpdateInput`,
|
|
138
|
-
conditions: `${apiModel.name}Condition`,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/** Builds aliased public types while retaining native fields inside the store. */
|
|
143
|
-
export function createOmniRelationProjectionGraphqlTypes(
|
|
144
|
-
definition: OmniRelationProjectionDefinition,
|
|
145
|
-
names: OmniRelationProjectionGraphqlNames,
|
|
146
|
-
): OmniRelationProjectionGraphqlTypes {
|
|
147
|
-
const aliases = getOmniRelationProjectionAliases(definition);
|
|
148
|
-
const multipleKinds =
|
|
149
|
-
getOmniRelationProjectionAllowedKinds(definition).length > 1;
|
|
150
|
-
const objectFields: Array<readonly [string, string]> = [
|
|
151
|
-
['guid', 'guid'],
|
|
152
|
-
[aliases.source, 'sourceRecordId'],
|
|
153
|
-
[aliases.target, 'targetRecordId'],
|
|
154
|
-
['revision', 'revision'],
|
|
155
|
-
[aliases.payload, 'payload'],
|
|
156
|
-
];
|
|
157
|
-
if (multipleKinds) objectFields.splice(1, 0, [aliases.kind, 'kind']);
|
|
158
|
-
const object = namedClass(names.object, objectFields);
|
|
159
|
-
ObjectType(names.object)(object);
|
|
160
|
-
ModelObject({
|
|
161
|
-
filters: { type: FilterOptionType.INCLUDE, fields: ['guid'] },
|
|
162
|
-
})(object);
|
|
163
|
-
Exclude()(object);
|
|
164
|
-
addField(object, 'guid', 'guid', UUIDScalar, false);
|
|
165
|
-
if (multipleKinds) {
|
|
166
|
-
addField(object, aliases.kind, 'kind', String, false);
|
|
167
|
-
}
|
|
168
|
-
addField(object, aliases.source, 'sourceRecordId', UUIDScalar, false);
|
|
169
|
-
addField(object, aliases.target, 'targetRecordId', UUIDScalar, false);
|
|
170
|
-
addField(object, 'revision', 'revision', Int, false);
|
|
171
|
-
addField(object, aliases.payload, 'payload', GraphQLJSON, true);
|
|
172
|
-
|
|
173
|
-
const create = namedClass(names.create);
|
|
174
|
-
InputType(names.create)(create);
|
|
175
|
-
ModelObject()(create);
|
|
176
|
-
addField(create, 'guid', 'guid', UUIDScalar, false);
|
|
177
|
-
if (multipleKinds) {
|
|
178
|
-
addField(create, aliases.kind, 'kind', String, false);
|
|
179
|
-
}
|
|
180
|
-
addField(create, aliases.source, 'sourceRecordId', UUIDScalar, false);
|
|
181
|
-
addField(create, aliases.target, 'targetRecordId', UUIDScalar, false);
|
|
182
|
-
addField(create, aliases.payload, 'payload', GraphQLJSON, true);
|
|
183
|
-
|
|
184
|
-
const patch = namedClass(names.patch);
|
|
185
|
-
InputType(names.patch)(patch);
|
|
186
|
-
ModelObject()(patch);
|
|
187
|
-
addField(patch, aliases.payload, 'payload', GraphQLJSON, true);
|
|
188
|
-
addField(patch, 'expectedRevision', 'expectedRevision', Int, false);
|
|
189
|
-
|
|
190
|
-
const conditions = namedClass(names.conditions);
|
|
191
|
-
InputType(names.conditions)(conditions);
|
|
192
|
-
ModelObject()(conditions);
|
|
193
|
-
addField(conditions, 'guid', 'guid', UUIDScalar, false);
|
|
194
|
-
|
|
195
|
-
return { object, create, patch, conditions };
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Composes one generated REST/GraphQL/dataloader surface over omni-relation.
|
|
200
|
-
* It never creates a relation table or a handwritten transport adapter.
|
|
201
|
-
*/
|
|
202
|
-
export function createOmniRelationProjectionRegistration<
|
|
203
|
-
Api extends ObjectLiteral,
|
|
204
|
-
>(
|
|
205
|
-
options: OmniRelationProjectionRegistrationOptions<Api>,
|
|
206
|
-
): OmniRelationProjectionRegistration<Api> {
|
|
207
|
-
const graphqlTypes = createOmniRelationProjectionGraphqlTypes(
|
|
208
|
-
options.definition,
|
|
209
|
-
options.graphql?.names ?? defaultGraphqlNames(options.apiModel),
|
|
210
|
-
);
|
|
211
|
-
const serviceToken = getServiceToken(options.apiModel);
|
|
212
|
-
const dataLoaderToken = getDataloaderToken(options.apiModel);
|
|
213
|
-
const resource = CrudGenResourceFactory<Api>({
|
|
214
|
-
entityModel: options.apiModel,
|
|
215
|
-
backend: false,
|
|
216
|
-
graphql: {
|
|
217
|
-
resolver: {
|
|
218
|
-
dto: graphqlTypes.object as ClassType,
|
|
219
|
-
input: {
|
|
220
|
-
create: graphqlTypes.create as ClassType,
|
|
221
|
-
update: graphqlTypes.patch as ClassType,
|
|
222
|
-
conditions: graphqlTypes.conditions as ClassType,
|
|
223
|
-
},
|
|
224
|
-
...(options.graphql?.prefix ? { prefix: options.graphql.prefix } : {}),
|
|
225
|
-
...(options.moduleRefToken !== undefined
|
|
226
|
-
? { moduleRefToken: options.moduleRefToken }
|
|
227
|
-
: {}),
|
|
228
|
-
queries: { getResource: { idName: 'guid' } },
|
|
229
|
-
},
|
|
230
|
-
serviceToken,
|
|
231
|
-
dataLoaderToken,
|
|
232
|
-
},
|
|
233
|
-
rest: {
|
|
234
|
-
dto: graphqlTypes.object as ClassType,
|
|
235
|
-
serialize: true,
|
|
236
|
-
...(options.rest?.path ? { path: options.rest.path } : {}),
|
|
237
|
-
idField: 'guid' as keyof Api & string,
|
|
238
|
-
serviceToken,
|
|
239
|
-
},
|
|
240
|
-
});
|
|
241
|
-
const relationKinds = Object.freeze([
|
|
242
|
-
...getOmniRelationProjectionAllowedKinds(options.definition),
|
|
243
|
-
]);
|
|
244
|
-
const reader = Object.freeze({
|
|
245
|
-
type: 'relation' as const,
|
|
246
|
-
id: options.definition.id,
|
|
247
|
-
definition: options.definition,
|
|
248
|
-
});
|
|
249
|
-
const serviceProvider: Provider =
|
|
250
|
-
options.lifecycle && options.catalog
|
|
251
|
-
? {
|
|
252
|
-
provide: serviceToken,
|
|
253
|
-
scope: Scope.REQUEST,
|
|
254
|
-
useFactory: (
|
|
255
|
-
dataSource: DataSource,
|
|
256
|
-
scope: OmniScopeContext,
|
|
257
|
-
omniOptions: { deletion: { relation: 'hard' | 'tombstone' } },
|
|
258
|
-
lifecycle: any,
|
|
259
|
-
catalog: any,
|
|
260
|
-
) =>
|
|
261
|
-
new OmniRelationProjectionService(
|
|
262
|
-
dataSource.getRepository(OmniRelationEntity),
|
|
263
|
-
scope,
|
|
264
|
-
omniOptions.deletion.relation,
|
|
265
|
-
dataSource.getRepository(OmniRecordEntity),
|
|
266
|
-
createOmniRelationKindContract(relationKinds),
|
|
267
|
-
options.definition,
|
|
268
|
-
dataSource,
|
|
269
|
-
lifecycle,
|
|
270
|
-
catalog,
|
|
271
|
-
),
|
|
272
|
-
inject: [
|
|
273
|
-
getDataSourceToken(options.dbConnection),
|
|
274
|
-
OmniScopeContext,
|
|
275
|
-
OMNI_KERNEL_OPTIONS,
|
|
276
|
-
options.lifecycle.token,
|
|
277
|
-
options.catalog.token,
|
|
278
|
-
],
|
|
279
|
-
}
|
|
280
|
-
: options.lifecycle
|
|
281
|
-
? {
|
|
282
|
-
provide: serviceToken,
|
|
283
|
-
scope: Scope.REQUEST,
|
|
284
|
-
useFactory: (
|
|
285
|
-
dataSource: DataSource,
|
|
286
|
-
scope: OmniScopeContext,
|
|
287
|
-
omniOptions: { deletion: { relation: 'hard' | 'tombstone' } },
|
|
288
|
-
lifecycle: any,
|
|
289
|
-
) =>
|
|
290
|
-
new OmniRelationProjectionService(
|
|
291
|
-
dataSource.getRepository(OmniRelationEntity),
|
|
292
|
-
scope,
|
|
293
|
-
omniOptions.deletion.relation,
|
|
294
|
-
dataSource.getRepository(OmniRecordEntity),
|
|
295
|
-
createOmniRelationKindContract(relationKinds),
|
|
296
|
-
options.definition,
|
|
297
|
-
dataSource,
|
|
298
|
-
lifecycle,
|
|
299
|
-
),
|
|
300
|
-
inject: [
|
|
301
|
-
getDataSourceToken(options.dbConnection),
|
|
302
|
-
OmniScopeContext,
|
|
303
|
-
OMNI_KERNEL_OPTIONS,
|
|
304
|
-
options.lifecycle.token,
|
|
305
|
-
],
|
|
306
|
-
}
|
|
307
|
-
: {
|
|
308
|
-
provide: serviceToken,
|
|
309
|
-
scope: Scope.REQUEST,
|
|
310
|
-
useFactory: (
|
|
311
|
-
dataSource: DataSource,
|
|
312
|
-
scope: OmniScopeContext,
|
|
313
|
-
omniOptions: { deletion: { relation: 'hard' | 'tombstone' } },
|
|
314
|
-
) =>
|
|
315
|
-
new OmniRelationProjectionService(
|
|
316
|
-
dataSource.getRepository(OmniRelationEntity),
|
|
317
|
-
scope,
|
|
318
|
-
omniOptions.deletion.relation,
|
|
319
|
-
dataSource.getRepository(OmniRecordEntity),
|
|
320
|
-
createOmniRelationKindContract(relationKinds),
|
|
321
|
-
options.definition,
|
|
322
|
-
dataSource,
|
|
323
|
-
undefined,
|
|
324
|
-
),
|
|
325
|
-
inject: [
|
|
326
|
-
getDataSourceToken(options.dbConnection),
|
|
327
|
-
OmniScopeContext,
|
|
328
|
-
OMNI_KERNEL_OPTIONS,
|
|
329
|
-
],
|
|
330
|
-
};
|
|
331
|
-
const providers: Provider[] = [
|
|
332
|
-
serviceProvider,
|
|
333
|
-
{
|
|
334
|
-
provide: dataLoaderToken,
|
|
335
|
-
scope: Scope.REQUEST,
|
|
336
|
-
useFactory: (
|
|
337
|
-
service: OmniRelationProjectionService,
|
|
338
|
-
scope: OmniScopeContext,
|
|
339
|
-
) =>
|
|
340
|
-
new GQLDataLoader(getFn(service as never), 'guid', undefined, {
|
|
341
|
-
cacheKeyFn: (key) => scope.cacheKey(key),
|
|
342
|
-
}),
|
|
343
|
-
inject: [serviceToken, OmniScopeContext],
|
|
344
|
-
},
|
|
345
|
-
...resource.providers,
|
|
346
|
-
];
|
|
347
|
-
|
|
348
|
-
return Object.freeze({
|
|
349
|
-
entities: Object.freeze([]) as readonly [],
|
|
350
|
-
relationKinds,
|
|
351
|
-
definition: options.definition,
|
|
352
|
-
reader,
|
|
353
|
-
graphqlTypes: Object.freeze(graphqlTypes),
|
|
354
|
-
resource,
|
|
355
|
-
controllers: Object.freeze([...resource.controllers]),
|
|
356
|
-
providers: Object.freeze(providers),
|
|
357
|
-
serviceToken,
|
|
358
|
-
dataLoaderToken,
|
|
359
|
-
});
|
|
360
|
-
}
|
|
1
|
+
import { Scope, type InjectionToken, type Provider } from '@nestjs/common';
|
|
2
|
+
import { InputType, Int, ObjectType } from '@nestjs/graphql';
|
|
3
|
+
import { getDataSourceToken } from '@nestjs/typeorm';
|
|
4
|
+
import {
|
|
5
|
+
CrudGenResourceFactory,
|
|
6
|
+
FilterOptionType,
|
|
7
|
+
ModelField,
|
|
8
|
+
ModelObject,
|
|
9
|
+
getServiceToken,
|
|
10
|
+
type ICrudGenResourceFactoryResult,
|
|
11
|
+
} from '@nest-yalc-2/crud-gen';
|
|
12
|
+
import {
|
|
13
|
+
GQLDataLoader,
|
|
14
|
+
getDataloaderToken,
|
|
15
|
+
getFn,
|
|
16
|
+
} from '@nest-yalc-2/data-loader';
|
|
17
|
+
import { UUIDScalar } from '@nest-yalc-2/graphql/scalars/uuid.scalar.js';
|
|
18
|
+
import type { ClassType } from '@node-yalc/types/globals.js';
|
|
19
|
+
import returnValue from '@node-yalc/utils/returnValue.js';
|
|
20
|
+
import { Exclude, Expose } from 'class-transformer';
|
|
21
|
+
import { GraphQLJSON } from 'graphql-type-json';
|
|
22
|
+
import type { DataSource, ObjectLiteral } from 'typeorm';
|
|
23
|
+
import { OmniRecordEntity } from './base/omni-record.entity.js';
|
|
24
|
+
import { OmniRelationEntity } from './base/omni-relation.entity.js';
|
|
25
|
+
import type {
|
|
26
|
+
OmniProjectionReaderCatalogProvider,
|
|
27
|
+
OmniRelationProjectionReaderRegistration,
|
|
28
|
+
} from './omni-projection.catalog.js';
|
|
29
|
+
import {
|
|
30
|
+
getOmniRelationProjectionAliases,
|
|
31
|
+
getOmniRelationProjectionAllowedKinds,
|
|
32
|
+
type OmniRelationProjectionDefinition,
|
|
33
|
+
} from './omni-relation-projection.definition.js';
|
|
34
|
+
import type { OmniProjectionLifecycleProvider } from './omni-projection.lifecycle.js';
|
|
35
|
+
import { OmniRelationProjectionService } from './omni-relation-projection.service.js';
|
|
36
|
+
import { createOmniRelationKindContract } from './omni-relation-kind.contract.js';
|
|
37
|
+
import { OMNI_KERNEL_OPTIONS, OmniScopeContext } from './omni-scope.js';
|
|
38
|
+
|
|
39
|
+
type RelationGraphqlClass = new () => Record<string, unknown>;
|
|
40
|
+
|
|
41
|
+
export interface OmniRelationProjectionGraphqlNames {
|
|
42
|
+
object: string;
|
|
43
|
+
create: string;
|
|
44
|
+
patch: string;
|
|
45
|
+
conditions: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface OmniRelationProjectionRegistrationOptions<
|
|
49
|
+
Api extends ObjectLiteral,
|
|
50
|
+
> {
|
|
51
|
+
/** API-only model used by CrudGen to derive operation names and tokens. */
|
|
52
|
+
apiModel: ClassType<Api>;
|
|
53
|
+
definition: OmniRelationProjectionDefinition;
|
|
54
|
+
dbConnection: string;
|
|
55
|
+
/** Shared catalog provider, normally built from all returned registrations. */
|
|
56
|
+
catalog?: OmniProjectionReaderCatalogProvider;
|
|
57
|
+
/** Request-scoped app policy injected into the generated service factory. */
|
|
58
|
+
lifecycle?: OmniProjectionLifecycleProvider<
|
|
59
|
+
OmniRelationProjectionDefinition,
|
|
60
|
+
OmniRelationEntity
|
|
61
|
+
>;
|
|
62
|
+
rest?: {
|
|
63
|
+
path?: string;
|
|
64
|
+
};
|
|
65
|
+
graphql?: {
|
|
66
|
+
prefix?: string;
|
|
67
|
+
names?: OmniRelationProjectionGraphqlNames;
|
|
68
|
+
};
|
|
69
|
+
/** Optional consumer-owned ModuleRef token for generated GraphQL DI. */
|
|
70
|
+
moduleRefToken?: InjectionToken;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface OmniRelationProjectionGraphqlTypes {
|
|
74
|
+
object: RelationGraphqlClass;
|
|
75
|
+
create: RelationGraphqlClass;
|
|
76
|
+
patch: RelationGraphqlClass;
|
|
77
|
+
conditions: RelationGraphqlClass;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface OmniRelationProjectionRegistration<Api extends ObjectLiteral> {
|
|
81
|
+
/** omni-relation already belongs to OmniKernelModule; no new table exists. */
|
|
82
|
+
entities: readonly [];
|
|
83
|
+
/** Include these in OmniKernelModule.register({ relationKinds }). */
|
|
84
|
+
relationKinds: readonly string[];
|
|
85
|
+
definition: OmniRelationProjectionDefinition;
|
|
86
|
+
reader: OmniRelationProjectionReaderRegistration;
|
|
87
|
+
graphqlTypes: OmniRelationProjectionGraphqlTypes;
|
|
88
|
+
resource: ICrudGenResourceFactoryResult<Api>;
|
|
89
|
+
controllers: readonly ClassType[];
|
|
90
|
+
providers: readonly Provider[];
|
|
91
|
+
serviceToken: string;
|
|
92
|
+
dataLoaderToken: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function namedClass(
|
|
96
|
+
name: string,
|
|
97
|
+
outputFields?: readonly (readonly [string, string])[],
|
|
98
|
+
): RelationGraphqlClass {
|
|
99
|
+
return {
|
|
100
|
+
[name]: class {
|
|
101
|
+
constructor(data?: Record<string, unknown>) {
|
|
102
|
+
if (!outputFields) {
|
|
103
|
+
Object.assign(this, data);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
for (const [publicName, destination] of outputFields) {
|
|
107
|
+
if (data && Object.prototype.hasOwnProperty.call(data, destination)) {
|
|
108
|
+
(this as Record<string, unknown>)[publicName] = data[destination];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
}[name] as RelationGraphqlClass;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function addField(
|
|
117
|
+
target: RelationGraphqlClass,
|
|
118
|
+
name: string,
|
|
119
|
+
destination: string,
|
|
120
|
+
type: unknown,
|
|
121
|
+
nullable: boolean,
|
|
122
|
+
): void {
|
|
123
|
+
ModelField({
|
|
124
|
+
dst: destination,
|
|
125
|
+
gqlType: returnValue(type),
|
|
126
|
+
gqlOptions: { nullable },
|
|
127
|
+
})(target.prototype, name);
|
|
128
|
+
Expose()(target.prototype, name);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function defaultGraphqlNames(
|
|
132
|
+
apiModel: ClassType,
|
|
133
|
+
): OmniRelationProjectionGraphqlNames {
|
|
134
|
+
return {
|
|
135
|
+
object: apiModel.name,
|
|
136
|
+
create: `${apiModel.name}CreateInput`,
|
|
137
|
+
patch: `${apiModel.name}UpdateInput`,
|
|
138
|
+
conditions: `${apiModel.name}Condition`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Builds aliased public types while retaining native fields inside the store. */
|
|
143
|
+
export function createOmniRelationProjectionGraphqlTypes(
|
|
144
|
+
definition: OmniRelationProjectionDefinition,
|
|
145
|
+
names: OmniRelationProjectionGraphqlNames,
|
|
146
|
+
): OmniRelationProjectionGraphqlTypes {
|
|
147
|
+
const aliases = getOmniRelationProjectionAliases(definition);
|
|
148
|
+
const multipleKinds =
|
|
149
|
+
getOmniRelationProjectionAllowedKinds(definition).length > 1;
|
|
150
|
+
const objectFields: Array<readonly [string, string]> = [
|
|
151
|
+
['guid', 'guid'],
|
|
152
|
+
[aliases.source, 'sourceRecordId'],
|
|
153
|
+
[aliases.target, 'targetRecordId'],
|
|
154
|
+
['revision', 'revision'],
|
|
155
|
+
[aliases.payload, 'payload'],
|
|
156
|
+
];
|
|
157
|
+
if (multipleKinds) objectFields.splice(1, 0, [aliases.kind, 'kind']);
|
|
158
|
+
const object = namedClass(names.object, objectFields);
|
|
159
|
+
ObjectType(names.object)(object);
|
|
160
|
+
ModelObject({
|
|
161
|
+
filters: { type: FilterOptionType.INCLUDE, fields: ['guid'] },
|
|
162
|
+
})(object);
|
|
163
|
+
Exclude()(object);
|
|
164
|
+
addField(object, 'guid', 'guid', UUIDScalar, false);
|
|
165
|
+
if (multipleKinds) {
|
|
166
|
+
addField(object, aliases.kind, 'kind', String, false);
|
|
167
|
+
}
|
|
168
|
+
addField(object, aliases.source, 'sourceRecordId', UUIDScalar, false);
|
|
169
|
+
addField(object, aliases.target, 'targetRecordId', UUIDScalar, false);
|
|
170
|
+
addField(object, 'revision', 'revision', Int, false);
|
|
171
|
+
addField(object, aliases.payload, 'payload', GraphQLJSON, true);
|
|
172
|
+
|
|
173
|
+
const create = namedClass(names.create);
|
|
174
|
+
InputType(names.create)(create);
|
|
175
|
+
ModelObject()(create);
|
|
176
|
+
addField(create, 'guid', 'guid', UUIDScalar, false);
|
|
177
|
+
if (multipleKinds) {
|
|
178
|
+
addField(create, aliases.kind, 'kind', String, false);
|
|
179
|
+
}
|
|
180
|
+
addField(create, aliases.source, 'sourceRecordId', UUIDScalar, false);
|
|
181
|
+
addField(create, aliases.target, 'targetRecordId', UUIDScalar, false);
|
|
182
|
+
addField(create, aliases.payload, 'payload', GraphQLJSON, true);
|
|
183
|
+
|
|
184
|
+
const patch = namedClass(names.patch);
|
|
185
|
+
InputType(names.patch)(patch);
|
|
186
|
+
ModelObject()(patch);
|
|
187
|
+
addField(patch, aliases.payload, 'payload', GraphQLJSON, true);
|
|
188
|
+
addField(patch, 'expectedRevision', 'expectedRevision', Int, false);
|
|
189
|
+
|
|
190
|
+
const conditions = namedClass(names.conditions);
|
|
191
|
+
InputType(names.conditions)(conditions);
|
|
192
|
+
ModelObject()(conditions);
|
|
193
|
+
addField(conditions, 'guid', 'guid', UUIDScalar, false);
|
|
194
|
+
|
|
195
|
+
return { object, create, patch, conditions };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Composes one generated REST/GraphQL/dataloader surface over omni-relation.
|
|
200
|
+
* It never creates a relation table or a handwritten transport adapter.
|
|
201
|
+
*/
|
|
202
|
+
export function createOmniRelationProjectionRegistration<
|
|
203
|
+
Api extends ObjectLiteral,
|
|
204
|
+
>(
|
|
205
|
+
options: OmniRelationProjectionRegistrationOptions<Api>,
|
|
206
|
+
): OmniRelationProjectionRegistration<Api> {
|
|
207
|
+
const graphqlTypes = createOmniRelationProjectionGraphqlTypes(
|
|
208
|
+
options.definition,
|
|
209
|
+
options.graphql?.names ?? defaultGraphqlNames(options.apiModel),
|
|
210
|
+
);
|
|
211
|
+
const serviceToken = getServiceToken(options.apiModel);
|
|
212
|
+
const dataLoaderToken = getDataloaderToken(options.apiModel);
|
|
213
|
+
const resource = CrudGenResourceFactory<Api>({
|
|
214
|
+
entityModel: options.apiModel,
|
|
215
|
+
backend: false,
|
|
216
|
+
graphql: {
|
|
217
|
+
resolver: {
|
|
218
|
+
dto: graphqlTypes.object as ClassType,
|
|
219
|
+
input: {
|
|
220
|
+
create: graphqlTypes.create as ClassType,
|
|
221
|
+
update: graphqlTypes.patch as ClassType,
|
|
222
|
+
conditions: graphqlTypes.conditions as ClassType,
|
|
223
|
+
},
|
|
224
|
+
...(options.graphql?.prefix ? { prefix: options.graphql.prefix } : {}),
|
|
225
|
+
...(options.moduleRefToken !== undefined
|
|
226
|
+
? { moduleRefToken: options.moduleRefToken }
|
|
227
|
+
: {}),
|
|
228
|
+
queries: { getResource: { idName: 'guid' } },
|
|
229
|
+
},
|
|
230
|
+
serviceToken,
|
|
231
|
+
dataLoaderToken,
|
|
232
|
+
},
|
|
233
|
+
rest: {
|
|
234
|
+
dto: graphqlTypes.object as ClassType,
|
|
235
|
+
serialize: true,
|
|
236
|
+
...(options.rest?.path ? { path: options.rest.path } : {}),
|
|
237
|
+
idField: 'guid' as keyof Api & string,
|
|
238
|
+
serviceToken,
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
const relationKinds = Object.freeze([
|
|
242
|
+
...getOmniRelationProjectionAllowedKinds(options.definition),
|
|
243
|
+
]);
|
|
244
|
+
const reader = Object.freeze({
|
|
245
|
+
type: 'relation' as const,
|
|
246
|
+
id: options.definition.id,
|
|
247
|
+
definition: options.definition,
|
|
248
|
+
});
|
|
249
|
+
const serviceProvider: Provider =
|
|
250
|
+
options.lifecycle && options.catalog
|
|
251
|
+
? {
|
|
252
|
+
provide: serviceToken,
|
|
253
|
+
scope: Scope.REQUEST,
|
|
254
|
+
useFactory: (
|
|
255
|
+
dataSource: DataSource,
|
|
256
|
+
scope: OmniScopeContext,
|
|
257
|
+
omniOptions: { deletion: { relation: 'hard' | 'tombstone' } },
|
|
258
|
+
lifecycle: any,
|
|
259
|
+
catalog: any,
|
|
260
|
+
) =>
|
|
261
|
+
new OmniRelationProjectionService(
|
|
262
|
+
dataSource.getRepository(OmniRelationEntity),
|
|
263
|
+
scope,
|
|
264
|
+
omniOptions.deletion.relation,
|
|
265
|
+
dataSource.getRepository(OmniRecordEntity),
|
|
266
|
+
createOmniRelationKindContract(relationKinds),
|
|
267
|
+
options.definition,
|
|
268
|
+
dataSource,
|
|
269
|
+
lifecycle,
|
|
270
|
+
catalog,
|
|
271
|
+
),
|
|
272
|
+
inject: [
|
|
273
|
+
getDataSourceToken(options.dbConnection),
|
|
274
|
+
OmniScopeContext,
|
|
275
|
+
OMNI_KERNEL_OPTIONS,
|
|
276
|
+
options.lifecycle.token,
|
|
277
|
+
options.catalog.token,
|
|
278
|
+
],
|
|
279
|
+
}
|
|
280
|
+
: options.lifecycle
|
|
281
|
+
? {
|
|
282
|
+
provide: serviceToken,
|
|
283
|
+
scope: Scope.REQUEST,
|
|
284
|
+
useFactory: (
|
|
285
|
+
dataSource: DataSource,
|
|
286
|
+
scope: OmniScopeContext,
|
|
287
|
+
omniOptions: { deletion: { relation: 'hard' | 'tombstone' } },
|
|
288
|
+
lifecycle: any,
|
|
289
|
+
) =>
|
|
290
|
+
new OmniRelationProjectionService(
|
|
291
|
+
dataSource.getRepository(OmniRelationEntity),
|
|
292
|
+
scope,
|
|
293
|
+
omniOptions.deletion.relation,
|
|
294
|
+
dataSource.getRepository(OmniRecordEntity),
|
|
295
|
+
createOmniRelationKindContract(relationKinds),
|
|
296
|
+
options.definition,
|
|
297
|
+
dataSource,
|
|
298
|
+
lifecycle,
|
|
299
|
+
),
|
|
300
|
+
inject: [
|
|
301
|
+
getDataSourceToken(options.dbConnection),
|
|
302
|
+
OmniScopeContext,
|
|
303
|
+
OMNI_KERNEL_OPTIONS,
|
|
304
|
+
options.lifecycle.token,
|
|
305
|
+
],
|
|
306
|
+
}
|
|
307
|
+
: {
|
|
308
|
+
provide: serviceToken,
|
|
309
|
+
scope: Scope.REQUEST,
|
|
310
|
+
useFactory: (
|
|
311
|
+
dataSource: DataSource,
|
|
312
|
+
scope: OmniScopeContext,
|
|
313
|
+
omniOptions: { deletion: { relation: 'hard' | 'tombstone' } },
|
|
314
|
+
) =>
|
|
315
|
+
new OmniRelationProjectionService(
|
|
316
|
+
dataSource.getRepository(OmniRelationEntity),
|
|
317
|
+
scope,
|
|
318
|
+
omniOptions.deletion.relation,
|
|
319
|
+
dataSource.getRepository(OmniRecordEntity),
|
|
320
|
+
createOmniRelationKindContract(relationKinds),
|
|
321
|
+
options.definition,
|
|
322
|
+
dataSource,
|
|
323
|
+
undefined,
|
|
324
|
+
),
|
|
325
|
+
inject: [
|
|
326
|
+
getDataSourceToken(options.dbConnection),
|
|
327
|
+
OmniScopeContext,
|
|
328
|
+
OMNI_KERNEL_OPTIONS,
|
|
329
|
+
],
|
|
330
|
+
};
|
|
331
|
+
const providers: Provider[] = [
|
|
332
|
+
serviceProvider,
|
|
333
|
+
{
|
|
334
|
+
provide: dataLoaderToken,
|
|
335
|
+
scope: Scope.REQUEST,
|
|
336
|
+
useFactory: (
|
|
337
|
+
service: OmniRelationProjectionService,
|
|
338
|
+
scope: OmniScopeContext,
|
|
339
|
+
) =>
|
|
340
|
+
new GQLDataLoader(getFn(service as never), 'guid', undefined, {
|
|
341
|
+
cacheKeyFn: (key) => scope.cacheKey(key),
|
|
342
|
+
}),
|
|
343
|
+
inject: [serviceToken, OmniScopeContext],
|
|
344
|
+
},
|
|
345
|
+
...resource.providers,
|
|
346
|
+
];
|
|
347
|
+
|
|
348
|
+
return Object.freeze({
|
|
349
|
+
entities: Object.freeze([]) as readonly [],
|
|
350
|
+
relationKinds,
|
|
351
|
+
definition: options.definition,
|
|
352
|
+
reader,
|
|
353
|
+
graphqlTypes: Object.freeze(graphqlTypes),
|
|
354
|
+
resource,
|
|
355
|
+
controllers: Object.freeze([...resource.controllers]),
|
|
356
|
+
providers: Object.freeze(providers),
|
|
357
|
+
serviceToken,
|
|
358
|
+
dataLoaderToken,
|
|
359
|
+
});
|
|
360
|
+
}
|