@nest-yalc-2/omnikernel-module 1.1.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/LICENSE +661 -0
- package/README.md +193 -0
- package/package.json +52 -0
- package/src/__tests__/omni-collection.entity.spec.ts +49 -0
- package/src/__tests__/omni-collection.service.spec.ts +83 -0
- package/src/__tests__/omni-document.entity.spec.ts +55 -0
- package/src/__tests__/omni-document.service.spec.ts +83 -0
- package/src/__tests__/omni-extension-projection-constraints.spec.ts +518 -0
- package/src/__tests__/omni-extension-projection.registration.spec.ts +946 -0
- package/src/__tests__/omni-extension-projection.service.spec.ts +806 -0
- package/src/__tests__/omni-external-ref.entity.spec.ts +36 -0
- package/src/__tests__/omni-external-ref.service.spec.ts +252 -0
- package/src/__tests__/omni-named.entity.spec.ts +33 -0
- package/src/__tests__/omni-record.entity.spec.ts +32 -0
- package/src/__tests__/omni-record.service.spec.ts +107 -0
- package/src/__tests__/omni-relation-semantics.spec.ts +102 -0
- package/src/__tests__/omni-relation.entity.spec.ts +38 -0
- package/src/__tests__/omni-scope-substrate.spec.ts +81 -0
- package/src/__tests__/omni-scoped.backend.spec.ts +115 -0
- package/src/__tests__/omni-scoped.service.spec.ts +354 -0
- package/src/__tests__/omnikernel.diagnostics.spec.ts +94 -0
- package/src/__tests__/omnikernel.module.spec.ts +40 -0
- package/src/__tests__/omnikernel.persistence.spec.ts +154 -0
- package/src/__tests__/omnikernel.public-api.spec.ts +238 -0
- package/src/__tests__/omnikernel.query.service.spec.ts +155 -0
- package/src/__tests__/tsconfig.json +6 -0
- package/src/base/omni-base.entity.d.ts +9 -0
- package/src/base/omni-base.entity.js +53 -0
- package/src/base/omni-base.entity.js.map +1 -0
- package/src/base/omni-base.entity.ts +41 -0
- package/src/base/omni-external-ref.entity.d.ts +13 -0
- package/src/base/omni-external-ref.entity.js +72 -0
- package/src/base/omni-external-ref.entity.js.map +1 -0
- package/src/base/omni-external-ref.entity.ts +53 -0
- package/src/base/omni-named.entity.d.ts +6 -0
- package/src/base/omni-named.entity.js +35 -0
- package/src/base/omni-named.entity.js.map +1 -0
- package/src/base/omni-named.entity.ts +16 -0
- package/src/base/omni-record.entity.d.ts +13 -0
- package/src/base/omni-record.entity.js +71 -0
- package/src/base/omni-record.entity.js.map +1 -0
- package/src/base/omni-record.entity.ts +51 -0
- package/src/base/omni-relation.entity.d.ts +15 -0
- package/src/base/omni-relation.entity.js +91 -0
- package/src/base/omni-relation.entity.js.map +1 -0
- package/src/base/omni-relation.entity.ts +72 -0
- package/src/index.d.ts +49 -0
- package/src/index.js +66 -0
- package/src/index.js.map +1 -0
- package/src/index.ts +49 -0
- package/src/omni-collection-kind.enum.d.ts +4 -0
- package/src/omni-collection-kind.enum.js +13 -0
- package/src/omni-collection-kind.enum.js.map +1 -0
- package/src/omni-collection-kind.enum.ts +10 -0
- package/src/omni-collection.backend.d.ts +2 -0
- package/src/omni-collection.backend.js +15 -0
- package/src/omni-collection.backend.js.map +1 -0
- package/src/omni-collection.backend.ts +16 -0
- package/src/omni-collection.dto.d.ts +32 -0
- package/src/omni-collection.dto.js +184 -0
- package/src/omni-collection.dto.js.map +1 -0
- package/src/omni-collection.dto.ts +170 -0
- package/src/omni-collection.entity.d.ts +7 -0
- package/src/omni-collection.entity.js +41 -0
- package/src/omni-collection.entity.js.map +1 -0
- package/src/omni-collection.entity.ts +21 -0
- package/src/omni-collection.service.d.ts +14 -0
- package/src/omni-collection.service.js +24 -0
- package/src/omni-collection.service.js.map +1 -0
- package/src/omni-collection.service.ts +75 -0
- package/src/omni-document-kind.enum.d.ts +6 -0
- package/src/omni-document-kind.enum.js +15 -0
- package/src/omni-document-kind.enum.js.map +1 -0
- package/src/omni-document-kind.enum.ts +12 -0
- package/src/omni-document.backend.d.ts +2 -0
- package/src/omni-document.backend.js +15 -0
- package/src/omni-document.backend.js.map +1 -0
- package/src/omni-document.backend.ts +16 -0
- package/src/omni-document.dto.d.ts +35 -0
- package/src/omni-document.dto.js +213 -0
- package/src/omni-document.dto.js.map +1 -0
- package/src/omni-document.dto.ts +198 -0
- package/src/omni-document.entity.d.ts +10 -0
- package/src/omni-document.entity.js +53 -0
- package/src/omni-document.entity.js.map +1 -0
- package/src/omni-document.entity.ts +30 -0
- package/src/omni-document.service.d.ts +14 -0
- package/src/omni-document.service.js +24 -0
- package/src/omni-document.service.js.map +1 -0
- package/src/omni-document.service.ts +75 -0
- package/src/omni-dto.helpers.d.ts +1 -0
- package/src/omni-dto.helpers.js +10 -0
- package/src/omni-dto.helpers.js.map +1 -0
- package/src/omni-dto.helpers.ts +21 -0
- package/src/omni-extension-projection.definition.d.ts +17 -0
- package/src/omni-extension-projection.definition.js +160 -0
- package/src/omni-extension-projection.definition.js.map +1 -0
- package/src/omni-extension-projection.definition.ts +294 -0
- package/src/omni-extension-projection.resource.d.ts +42 -0
- package/src/omni-extension-projection.resource.js +127 -0
- package/src/omni-extension-projection.resource.js.map +1 -0
- package/src/omni-extension-projection.resource.ts +274 -0
- package/src/omni-extension-projection.service.d.ts +33 -0
- package/src/omni-extension-projection.service.js +332 -0
- package/src/omni-extension-projection.service.js.map +1 -0
- package/src/omni-extension-projection.service.ts +452 -0
- package/src/omni-external-ref-binding.validator.d.ts +14 -0
- package/src/omni-external-ref-binding.validator.js +34 -0
- package/src/omni-external-ref-binding.validator.js.map +1 -0
- package/src/omni-external-ref-binding.validator.ts +53 -0
- package/src/omni-external-ref-internal-type.enum.d.ts +5 -0
- package/src/omni-external-ref-internal-type.enum.js +14 -0
- package/src/omni-external-ref-internal-type.enum.js.map +1 -0
- package/src/omni-external-ref-internal-type.enum.ts +11 -0
- package/src/omni-external-ref.backend.d.ts +2 -0
- package/src/omni-external-ref.backend.js +19 -0
- package/src/omni-external-ref.backend.js.map +1 -0
- package/src/omni-external-ref.backend.ts +25 -0
- package/src/omni-external-ref.dto.d.ts +26 -0
- package/src/omni-external-ref.dto.js +140 -0
- package/src/omni-external-ref.dto.js.map +1 -0
- package/src/omni-external-ref.dto.ts +128 -0
- package/src/omni-external-ref.service.d.ts +33 -0
- package/src/omni-external-ref.service.js +133 -0
- package/src/omni-external-ref.service.js.map +1 -0
- package/src/omni-external-ref.service.ts +255 -0
- package/src/omni-migration.d.ts +28 -0
- package/src/omni-migration.js +239 -0
- package/src/omni-migration.js.map +1 -0
- package/src/omni-migration.ts +365 -0
- package/src/omni-named.backend.d.ts +2 -0
- package/src/omni-named.backend.js +13 -0
- package/src/omni-named.backend.js.map +1 -0
- package/src/omni-named.backend.ts +11 -0
- package/src/omni-named.dto.d.ts +19 -0
- package/src/omni-named.dto.js +94 -0
- package/src/omni-named.dto.js.map +1 -0
- package/src/omni-named.dto.ts +86 -0
- package/src/omni-projection.catalog.d.ts +49 -0
- package/src/omni-projection.catalog.js +177 -0
- package/src/omni-projection.catalog.js.map +1 -0
- package/src/omni-projection.catalog.ts +345 -0
- package/src/omni-projection.lifecycle.d.ts +20 -0
- package/src/omni-projection.lifecycle.js +3 -0
- package/src/omni-projection.lifecycle.js.map +1 -0
- package/src/omni-projection.lifecycle.ts +53 -0
- package/src/omni-record-status.enum.d.ts +5 -0
- package/src/omni-record-status.enum.js +14 -0
- package/src/omni-record-status.enum.js.map +1 -0
- package/src/omni-record-status.enum.ts +11 -0
- package/src/omni-record.backend.d.ts +2 -0
- package/src/omni-record.backend.js +13 -0
- package/src/omni-record.backend.js.map +1 -0
- package/src/omni-record.backend.ts +19 -0
- package/src/omni-record.dto.d.ts +29 -0
- package/src/omni-record.dto.js +169 -0
- package/src/omni-record.dto.js.map +1 -0
- package/src/omni-record.dto.ts +157 -0
- package/src/omni-record.service.d.ts +18 -0
- package/src/omni-record.service.js +45 -0
- package/src/omni-record.service.js.map +1 -0
- package/src/omni-record.service.ts +113 -0
- package/src/omni-relation-kind.contract.d.ts +9 -0
- package/src/omni-relation-kind.contract.js +33 -0
- package/src/omni-relation-kind.contract.js.map +1 -0
- package/src/omni-relation-kind.contract.ts +49 -0
- package/src/omni-relation-kind.enum.d.ts +6 -0
- package/src/omni-relation-kind.enum.js +15 -0
- package/src/omni-relation-kind.enum.js.map +1 -0
- package/src/omni-relation-kind.enum.ts +12 -0
- package/src/omni-relation-projection.definition.d.ts +25 -0
- package/src/omni-relation-projection.definition.js +83 -0
- package/src/omni-relation-projection.definition.js.map +1 -0
- package/src/omni-relation-projection.definition.ts +166 -0
- package/src/omni-relation-projection.resource.d.ts +51 -0
- package/src/omni-relation-projection.resource.js +203 -0
- package/src/omni-relation-projection.resource.js.map +1 -0
- package/src/omni-relation-projection.resource.ts +360 -0
- package/src/omni-relation-projection.service.d.ts +47 -0
- package/src/omni-relation-projection.service.js +385 -0
- package/src/omni-relation-projection.service.js.map +1 -0
- package/src/omni-relation-projection.service.ts +610 -0
- package/src/omni-relation-semantics.d.ts +11 -0
- package/src/omni-relation-semantics.js +34 -0
- package/src/omni-relation-semantics.js.map +1 -0
- package/src/omni-relation-semantics.ts +52 -0
- package/src/omni-relation-status.enum.d.ts +5 -0
- package/src/omni-relation-status.enum.js +14 -0
- package/src/omni-relation-status.enum.js.map +1 -0
- package/src/omni-relation-status.enum.ts +11 -0
- package/src/omni-relation.backend.d.ts +2 -0
- package/src/omni-relation.backend.js +17 -0
- package/src/omni-relation.backend.js.map +1 -0
- package/src/omni-relation.backend.ts +21 -0
- package/src/omni-relation.dto.d.ts +28 -0
- package/src/omni-relation.dto.js +153 -0
- package/src/omni-relation.dto.js.map +1 -0
- package/src/omni-relation.dto.ts +142 -0
- package/src/omni-relation.service.d.ts +19 -0
- package/src/omni-relation.service.js +76 -0
- package/src/omni-relation.service.js.map +1 -0
- package/src/omni-relation.service.ts +159 -0
- package/src/omni-scope.d.ts +33 -0
- package/src/omni-scope.js +91 -0
- package/src/omni-scope.js.map +1 -0
- package/src/omni-scope.ts +161 -0
- package/src/omni-scoped.backend.d.ts +21 -0
- package/src/omni-scoped.backend.js +47 -0
- package/src/omni-scoped.backend.js.map +1 -0
- package/src/omni-scoped.backend.ts +110 -0
- package/src/omni-scoped.repository.d.ts +10 -0
- package/src/omni-scoped.repository.js +26 -0
- package/src/omni-scoped.repository.js.map +1 -0
- package/src/omni-scoped.repository.ts +34 -0
- package/src/omni-scoped.service.d.ts +36 -0
- package/src/omni-scoped.service.js +171 -0
- package/src/omni-scoped.service.js.map +1 -0
- package/src/omni-scoped.service.ts +338 -0
- package/src/omnikernel.diagnostics.d.ts +16 -0
- package/src/omnikernel.diagnostics.js +50 -0
- package/src/omnikernel.diagnostics.js.map +1 -0
- package/src/omnikernel.diagnostics.ts +83 -0
- package/src/omnikernel.module.d.ts +5 -0
- package/src/omnikernel.module.js +87 -0
- package/src/omnikernel.module.js.map +1 -0
- package/src/omnikernel.module.ts +97 -0
- package/src/omnikernel.query.service.d.ts +17 -0
- package/src/omnikernel.query.service.js +85 -0
- package/src/omnikernel.query.service.js.map +1 -0
- package/src/omnikernel.query.service.ts +108 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"omni-scoped.repository.js","sourceRoot":"","sources":["omni-scoped.repository.ts"],"names":[],"mappings":";;;AAQA,MAAa,oBAAoB;IAC/B,YACmB,UAA8B,EAC9B,KAAgB;QADhB,eAAU,GAAV,UAAU,CAAoB;QAC9B,UAAK,GAAL,KAAK,CAAW;IAChC,CAAC;IAEJ,KAAK,CACH,QAAkC,EAA8B;QAEhE,IAAI,SAAS,IAAK,KAAgB,EAAE,CAAC;YACnC,MAAM,IAAI,SAAS,CAAC,8CAA8C,CAAC,CAAC;QACtE,CAAC;QACD,OAAO;YACL,GAAI,KAAgB;YACpB,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO;SACW,CAAC;IAC3C,CAAC;IAED,aAAa,CAAC,IAAY;QACxB,OAAO,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,EAAW,CAAC,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED,IAAI,CAAC,QAAkC,EAA8B;QACnE,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC5D,CAAC;CACF;AAzBD,oDAyBC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { FindOptionsWhere, ObjectLiteral, Repository } from 'typeorm';
|
|
2
|
+
import type { OmniScope } from './omni-scope.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Small repository facade for normal Omni lookups. It mechanically adds the
|
|
6
|
+
* current scope and is the supported lower-level alternative to passing raw
|
|
7
|
+
* repositories between application layers.
|
|
8
|
+
*/
|
|
9
|
+
export class OmniScopedRepository<Entity extends ObjectLiteral> {
|
|
10
|
+
constructor(
|
|
11
|
+
private readonly repository: Repository<Entity>,
|
|
12
|
+
private readonly scope: OmniScope,
|
|
13
|
+
) {}
|
|
14
|
+
|
|
15
|
+
where(
|
|
16
|
+
where: FindOptionsWhere<Entity> = {} as FindOptionsWhere<Entity>,
|
|
17
|
+
): FindOptionsWhere<Entity> {
|
|
18
|
+
if ('scopeId' in (where as object)) {
|
|
19
|
+
throw new TypeError('Omni scopeId is derived from server context.');
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
...(where as object),
|
|
23
|
+
scopeId: this.scope.scopeId,
|
|
24
|
+
} as unknown as FindOptionsWhere<Entity>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
findOneByGuid(guid: string): Promise<Entity | null> {
|
|
28
|
+
return this.repository.findOne({ where: this.where({ guid } as never) });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
find(where: FindOptionsWhere<Entity> = {} as FindOptionsWhere<Entity>) {
|
|
32
|
+
return this.repository.find({ where: this.where(where) });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { CrudGenFindManyOptions } from '@nest-yalc-2/crud-gen/api-graphql/crud-gen-gql.interface.js';
|
|
2
|
+
import { GenericService } from '@nest-yalc-2/crud-gen/typeorm/generic.service.js';
|
|
3
|
+
import type { DeepPartial, FindOptionsWhere, ObjectLiteral } from 'typeorm';
|
|
4
|
+
import type { OmniDeletePolicy, OmniScope } from './omni-scope.js';
|
|
5
|
+
type ScopedEntity = ObjectLiteral & {
|
|
6
|
+
scopeId: string;
|
|
7
|
+
deletedAt?: Date | null;
|
|
8
|
+
};
|
|
9
|
+
export declare class OmniScopedService<Entity extends ScopedEntity> extends GenericService<Entity> {
|
|
10
|
+
private readonly deletion;
|
|
11
|
+
constructor(repository: any, scopeOrRepositoryWrite?: OmniScope | any, deletion?: OmniDeletePolicy);
|
|
12
|
+
protected readonly scope: OmniScope;
|
|
13
|
+
protected get scopeId(): string;
|
|
14
|
+
protected scopedConditions(conditions: FindOptionsWhere<Entity>): FindOptionsWhere<Entity>;
|
|
15
|
+
getEntity(conditions: FindOptionsWhere<Entity>[] | FindOptionsWhere<Entity> | ObjectLiteral | string, fields?: (keyof Entity)[], relations?: string[], databaseName?: string, options?: {
|
|
16
|
+
failOnNull: false;
|
|
17
|
+
}): Promise<Entity | undefined>;
|
|
18
|
+
getEntity(conditions: FindOptionsWhere<Entity>[] | FindOptionsWhere<Entity> | ObjectLiteral | string, fields?: (keyof Entity)[], relations?: string[], databaseName?: string, options?: {
|
|
19
|
+
failOnNull?: boolean;
|
|
20
|
+
}): Promise<Entity>;
|
|
21
|
+
getEntityListExtended(findOptions: CrudGenFindManyOptions<Entity>, withCount?: false, relations?: string[], databaseName?: string): Promise<Entity[]>;
|
|
22
|
+
getEntityListExtended(findOptions: CrudGenFindManyOptions<Entity>, withCount: true, relations?: string[], databaseName?: string): Promise<[Entity[], number]>;
|
|
23
|
+
private scopeFindOptions;
|
|
24
|
+
createEntity(input: DeepPartial<Entity>, findOptions?: CrudGenFindManyOptions<Entity>, returnEntity?: true): Promise<Entity>;
|
|
25
|
+
createEntity(input: DeepPartial<Entity>, findOptions?: CrudGenFindManyOptions<Entity>, returnEntity?: boolean): Promise<Entity | boolean>;
|
|
26
|
+
updateEntity(conditions: FindOptionsWhere<Entity>, input: DeepPartial<Entity>, findOptions?: CrudGenFindManyOptions<Entity>, returnEntity?: true): Promise<Entity>;
|
|
27
|
+
updateEntity(conditions: FindOptionsWhere<Entity>, input: DeepPartial<Entity>, findOptions?: CrudGenFindManyOptions<Entity>, returnEntity?: boolean): Promise<Entity | boolean>;
|
|
28
|
+
deleteEntity(conditions: FindOptionsWhere<Entity>): Promise<boolean>;
|
|
29
|
+
protected rejectServerFields(input: object): void;
|
|
30
|
+
protected rejectClientScope(input: object): void;
|
|
31
|
+
private validatePayloadContract;
|
|
32
|
+
protected notFound(): never;
|
|
33
|
+
private scopedWhere;
|
|
34
|
+
private rejectScopeInWhere;
|
|
35
|
+
}
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OmniScopedService = void 0;
|
|
4
|
+
const common_1 = require("@nestjs/common");
|
|
5
|
+
const crud_gen_enum_js_1 = require("@nest-yalc-2/crud-gen/crud-gen.enum.js");
|
|
6
|
+
const generic_service_js_1 = require("@nest-yalc-2/crud-gen/typeorm/generic.service.js");
|
|
7
|
+
const typeorm_1 = require("typeorm");
|
|
8
|
+
function hasOwn(input, key) {
|
|
9
|
+
return Object.prototype.hasOwnProperty.call(input, key);
|
|
10
|
+
}
|
|
11
|
+
class OmniScopedService extends generic_service_js_1.GenericService {
|
|
12
|
+
constructor(repository, scopeOrRepositoryWrite, deletion = 'hard') {
|
|
13
|
+
const scope = isOmniScope(scopeOrRepositoryWrite)
|
|
14
|
+
? scopeOrRepositoryWrite
|
|
15
|
+
: defaultOmniScope;
|
|
16
|
+
super(repository, isOmniScope(scopeOrRepositoryWrite) ? undefined : scopeOrRepositoryWrite);
|
|
17
|
+
this.deletion = deletion;
|
|
18
|
+
this.scope = scope;
|
|
19
|
+
}
|
|
20
|
+
get scopeId() {
|
|
21
|
+
return this.scope.scopeId;
|
|
22
|
+
}
|
|
23
|
+
scopedConditions(conditions) {
|
|
24
|
+
this.rejectClientScope(conditions);
|
|
25
|
+
return {
|
|
26
|
+
...conditions,
|
|
27
|
+
scopeId: this.scopeId,
|
|
28
|
+
...(this.deletion === 'tombstone' ? { deletedAt: (0, typeorm_1.IsNull)() } : {}),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
async getEntity(conditions, fields, relations, databaseName, options) {
|
|
32
|
+
const entity = await super.getEntity(this.scopedWhere(conditions), fields, relations, databaseName, { failOnNull: false });
|
|
33
|
+
if (!entity && options?.failOnNull)
|
|
34
|
+
this.notFound();
|
|
35
|
+
return entity;
|
|
36
|
+
}
|
|
37
|
+
async getEntityListExtended(findOptions = {}, withCount = false, relations, databaseName) {
|
|
38
|
+
const scopedOptions = this.scopeFindOptions(findOptions);
|
|
39
|
+
if (withCount) {
|
|
40
|
+
return super.getEntityListExtended(scopedOptions, true, relations, databaseName);
|
|
41
|
+
}
|
|
42
|
+
return super.getEntityListExtended(scopedOptions, false, relations, databaseName);
|
|
43
|
+
}
|
|
44
|
+
scopeFindOptions(findOptions) {
|
|
45
|
+
this.rejectScopeInWhere(findOptions.where);
|
|
46
|
+
const userWhere = findOptions.where;
|
|
47
|
+
const filters = {};
|
|
48
|
+
filters.scopeId = this.scopeId;
|
|
49
|
+
if (this.deletion === 'tombstone')
|
|
50
|
+
filters.deletedAt = (0, typeorm_1.IsNull)();
|
|
51
|
+
const where = {
|
|
52
|
+
operator: crud_gen_enum_js_1.Operators.AND,
|
|
53
|
+
filters,
|
|
54
|
+
...(userWhere ? { childExpressions: [userWhere] } : {}),
|
|
55
|
+
};
|
|
56
|
+
return {
|
|
57
|
+
...findOptions,
|
|
58
|
+
where,
|
|
59
|
+
...(findOptions.subQueryFilters
|
|
60
|
+
? {
|
|
61
|
+
subQueryFilters: this.scopeFindOptions(findOptions.subQueryFilters),
|
|
62
|
+
}
|
|
63
|
+
: {}),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
async createEntity(input, findOptions, returnEntity = true) {
|
|
67
|
+
this.rejectServerFields(input);
|
|
68
|
+
this.validatePayloadContract(input);
|
|
69
|
+
return super.createEntity({ ...input, scopeId: this.scopeId }, findOptions, returnEntity);
|
|
70
|
+
}
|
|
71
|
+
async updateEntity(conditions, input, findOptions, returnEntity = true) {
|
|
72
|
+
this.rejectServerFields(input);
|
|
73
|
+
this.validatePayloadContract(input);
|
|
74
|
+
if (hasOwn(input, 'guid')) {
|
|
75
|
+
throw new common_1.BadRequestException('guid is immutable.');
|
|
76
|
+
}
|
|
77
|
+
return super.updateEntity(this.scopedConditions(conditions), { ...input, scopeId: this.scopeId }, findOptions, returnEntity);
|
|
78
|
+
}
|
|
79
|
+
async deleteEntity(conditions) {
|
|
80
|
+
const scoped = this.scopedConditions(conditions);
|
|
81
|
+
if (this.deletion === 'hard') {
|
|
82
|
+
return super.deleteEntity(scoped);
|
|
83
|
+
}
|
|
84
|
+
const result = await this.getRepository().update(scoped, {
|
|
85
|
+
deletedAt: new Date(),
|
|
86
|
+
});
|
|
87
|
+
if (!result.affected)
|
|
88
|
+
this.notFound();
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
rejectServerFields(input) {
|
|
92
|
+
for (const field of ['scopeId', 'revision', 'deletedAt']) {
|
|
93
|
+
if (hasOwn(input, field)) {
|
|
94
|
+
throw new common_1.BadRequestException(`${field} is server-owned.`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
rejectClientScope(input) {
|
|
99
|
+
if (hasOwn(input, 'scopeId')) {
|
|
100
|
+
throw new common_1.BadRequestException('scopeId is derived from server context.');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
validatePayloadContract(input) {
|
|
104
|
+
const candidate = input;
|
|
105
|
+
if (hasOwn(candidate, 'payload') &&
|
|
106
|
+
candidate.payload !== null &&
|
|
107
|
+
(typeof candidate.payload !== 'object' ||
|
|
108
|
+
Array.isArray(candidate.payload))) {
|
|
109
|
+
throw new common_1.BadRequestException('payload must be a JSON object or null.');
|
|
110
|
+
}
|
|
111
|
+
const hasSchemaId = hasOwn(candidate, 'payloadSchemaId');
|
|
112
|
+
const hasSchemaVersion = hasOwn(candidate, 'payloadSchemaVersion');
|
|
113
|
+
if (hasSchemaId !== hasSchemaVersion) {
|
|
114
|
+
throw new common_1.BadRequestException('payloadSchemaId and payloadSchemaVersion must be supplied together.');
|
|
115
|
+
}
|
|
116
|
+
if (!hasSchemaId)
|
|
117
|
+
return;
|
|
118
|
+
if (typeof candidate.payloadSchemaId !== 'string' ||
|
|
119
|
+
candidate.payloadSchemaId.trim().length === 0 ||
|
|
120
|
+
candidate.payloadSchemaId.length > 128) {
|
|
121
|
+
throw new common_1.BadRequestException('payloadSchemaId must be a non-empty string.');
|
|
122
|
+
}
|
|
123
|
+
if (typeof candidate.payloadSchemaVersion !== 'number' ||
|
|
124
|
+
!Number.isInteger(candidate.payloadSchemaVersion) ||
|
|
125
|
+
candidate.payloadSchemaVersion < 1 ||
|
|
126
|
+
candidate.payloadSchemaVersion > 2_147_483_647) {
|
|
127
|
+
throw new common_1.BadRequestException('payloadSchemaVersion must be a positive signed 32-bit integer.');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
notFound() {
|
|
131
|
+
throw new common_1.NotFoundException('Omni resource was not found in this scope.');
|
|
132
|
+
}
|
|
133
|
+
scopedWhere(where) {
|
|
134
|
+
if (typeof where === 'string') {
|
|
135
|
+
throw new common_1.BadRequestException('String where clauses are not supported for scoped Omni resources.');
|
|
136
|
+
}
|
|
137
|
+
if (Array.isArray(where)) {
|
|
138
|
+
return where.map((condition) => this.scopedConditions(condition));
|
|
139
|
+
}
|
|
140
|
+
return this.scopedConditions(where);
|
|
141
|
+
}
|
|
142
|
+
rejectScopeInWhere(where) {
|
|
143
|
+
if (!where || typeof where !== 'object')
|
|
144
|
+
return;
|
|
145
|
+
if (Array.isArray(where)) {
|
|
146
|
+
where.forEach((item) => this.rejectScopeInWhere(item));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const candidate = where;
|
|
150
|
+
if (hasOwn(candidate, 'scopeId'))
|
|
151
|
+
this.rejectClientScope(candidate);
|
|
152
|
+
if (candidate.filters && typeof candidate.filters === 'object') {
|
|
153
|
+
this.rejectScopeInWhere(candidate.filters);
|
|
154
|
+
}
|
|
155
|
+
if (Array.isArray(candidate.childExpressions)) {
|
|
156
|
+
candidate.childExpressions.forEach((item) => this.rejectScopeInWhere(item));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
exports.OmniScopedService = OmniScopedService;
|
|
161
|
+
function isOmniScope(value) {
|
|
162
|
+
return (!!value &&
|
|
163
|
+
typeof value === 'object' &&
|
|
164
|
+
typeof value.scopeId === 'string' &&
|
|
165
|
+
typeof value.cacheKey === 'function');
|
|
166
|
+
}
|
|
167
|
+
const defaultOmniScope = {
|
|
168
|
+
scopeId: 'default',
|
|
169
|
+
cacheKey: (key) => `default:${key}`,
|
|
170
|
+
};
|
|
171
|
+
//# sourceMappingURL=omni-scoped.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"omni-scoped.service.js","sourceRoot":"","sources":["omni-scoped.service.ts"],"names":[],"mappings":";;;AAAA,2CAAwE;AAGxE,6EAAmE;AACnE,yFAAkF;AAElF,qCAAiC;AAQjC,SAAS,MAAM,CAAC,KAAa,EAAE,GAAW;IACxC,OAAO,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AAC1D,CAAC;AAMD,MAAa,iBAEX,SAAQ,mCAAsB;IAC9B,YACE,UAAe,EACf,sBAAwC,EACvB,WAA6B,MAAM;QAEpD,MAAM,KAAK,GAAG,WAAW,CAAC,sBAAsB,CAAC;YAC/C,CAAC,CAAC,sBAAsB;YACxB,CAAC,CAAC,gBAAgB,CAAC;QACrB,KAAK,CACH,UAAU,EACV,WAAW,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,sBAAsB,CACzE,CAAC;QARe,aAAQ,GAAR,QAAQ,CAA2B;QASpD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAID,IAAc,OAAO;QACnB,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;IAC5B,CAAC;IAES,gBAAgB,CACxB,UAAoC;QAEpC,IAAI,CAAC,iBAAiB,CAAC,UAAoB,CAAC,CAAC;QAC7C,OAAO;YACL,GAAG,UAAU;YACb,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,GAAG,CAAC,IAAI,CAAC,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAA,gBAAM,GAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC3B,CAAC;IAC3C,CAAC;IAwBQ,KAAK,CAAC,SAAS,CACtB,UAIU,EACV,MAAyB,EACzB,SAAoB,EACpB,YAAqB,EACrB,OAAkC;QAElC,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,SAAS,CAClC,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,EAC5B,MAAM,EACN,SAAS,EACT,YAAY,EACZ,EAAE,UAAU,EAAE,KAAK,EAAE,CACtB,CAAC;QACF,IAAI,CAAC,MAAM,IAAI,OAAO,EAAE,UAAU;YAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;QACpD,OAAO,MAAM,CAAC;IAChB,CAAC;IAcQ,KAAK,CAAC,qBAAqB,CAClC,cAA8C,EAAE,EAChD,SAAS,GAAG,KAAK,EACjB,SAAoB,EACpB,YAAqB;QAErB,MAAM,aAAa,GAAG,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC;QACzD,IAAI,SAAS,EAAE,CAAC;YACd,OAAO,KAAK,CAAC,qBAAqB,CAChC,aAAa,EACb,IAAI,EACJ,SAAS,EACT,YAAY,CACb,CAAC;QACJ,CAAC;QACD,OAAO,KAAK,CAAC,qBAAqB,CAChC,aAAa,EACb,KAAK,EACL,SAAS,EACT,YAAY,CACb,CAAC;IACJ,CAAC;IAEO,gBAAgB,CACtB,WAA2C;QAE3C,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAC3C,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC;QACpC,MAAM,OAAO,GAAuC,EAAE,CAAC;QACvD,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC/B,IAAI,IAAI,CAAC,QAAQ,KAAK,WAAW;YAAE,OAAO,CAAC,SAAS,GAAG,IAAA,gBAAM,GAAE,CAAC;QAChE,MAAM,KAAK,GAA4B;YACrC,QAAQ,EAAE,4BAAS,CAAC,GAAG;YACvB,OAAO;YACP,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxD,CAAC;QACF,OAAO;YACL,GAAG,WAAW;YACd,KAAK;YACL,GAAG,CAAC,WAAW,CAAC,eAAe;gBAC7B,CAAC,CAAC;oBACE,eAAe,EAAE,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,eAAe,CAAC;iBACpE;gBACH,CAAC,CAAC,EAAE,CAAC;SACR,CAAC;IACJ,CAAC;IAYQ,KAAK,CAAC,YAAY,CACzB,KAA0B,EAC1B,WAA4C,EAC5C,YAAY,GAAG,IAAI;QAEnB,IAAI,CAAC,kBAAkB,CAAC,KAAe,CAAC,CAAC;QACzC,IAAI,CAAC,uBAAuB,CAAC,KAAe,CAAC,CAAC;QAC9C,OAAO,KAAK,CAAC,YAAY,CACvB,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAyB,EAC1D,WAAW,EACX,YAAY,CACb,CAAC;IACJ,CAAC;IAcQ,KAAK,CAAC,YAAY,CACzB,UAAoC,EACpC,KAA0B,EAC1B,WAA4C,EAC5C,YAAY,GAAG,IAAI;QAEnB,IAAI,CAAC,kBAAkB,CAAC,KAAe,CAAC,CAAC;QACzC,IAAI,CAAC,uBAAuB,CAAC,KAAe,CAAC,CAAC;QAC9C,IAAI,MAAM,CAAC,KAAe,EAAE,MAAM,CAAC,EAAE,CAAC;YACpC,MAAM,IAAI,4BAAmB,CAAC,oBAAoB,CAAC,CAAC;QACtD,CAAC;QACD,OAAO,KAAK,CAAC,YAAY,CACvB,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,EACjC,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAyB,EAC1D,WAAW,EACX,YAAY,CACb,CAAC;IACJ,CAAC;IAEQ,KAAK,CAAC,YAAY,CACzB,UAAoC;QAEpC,MAAM,MAAM,GAAG,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;QACjD,IAAI,IAAI,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;YAC7B,OAAO,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QACpC,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE;YACvD,SAAS,EAAE,IAAI,IAAI,EAAE;SACC,CAAC,CAAC;QAC1B,IAAI,CAAC,MAAM,CAAC,QAAQ;YAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;QACtC,OAAO,IAAI,CAAC;IACd,CAAC;IAES,kBAAkB,CAAC,KAAa;QACxC,KAAK,MAAM,KAAK,IAAI,CAAC,SAAS,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE,CAAC;YACzD,IAAI,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC;gBACzB,MAAM,IAAI,4BAAmB,CAAC,GAAG,KAAK,mBAAmB,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC;IACH,CAAC;IAES,iBAAiB,CAAC,KAAa;QACvC,IAAI,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,4BAAmB,CAAC,yCAAyC,CAAC,CAAC;QAC3E,CAAC;IACH,CAAC;IAEO,uBAAuB,CAAC,KAAa;QAC3C,MAAM,SAAS,GAAG,KAAgC,CAAC;QACnD,IACE,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC;YAC5B,SAAS,CAAC,OAAO,KAAK,IAAI;YAC1B,CAAC,OAAO,SAAS,CAAC,OAAO,KAAK,QAAQ;gBACpC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,EACnC,CAAC;YACD,MAAM,IAAI,4BAAmB,CAAC,wCAAwC,CAAC,CAAC;QAC1E,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;QACzD,MAAM,gBAAgB,GAAG,MAAM,CAAC,SAAS,EAAE,sBAAsB,CAAC,CAAC;QACnE,IAAI,WAAW,KAAK,gBAAgB,EAAE,CAAC;YACrC,MAAM,IAAI,4BAAmB,CAC3B,qEAAqE,CACtE,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,WAAW;YAAE,OAAO;QAEzB,IACE,OAAO,SAAS,CAAC,eAAe,KAAK,QAAQ;YAC7C,SAAS,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;YAC7C,SAAS,CAAC,eAAe,CAAC,MAAM,GAAG,GAAG,EACtC,CAAC;YACD,MAAM,IAAI,4BAAmB,CAC3B,6CAA6C,CAC9C,CAAC;QACJ,CAAC;QACD,IACE,OAAO,SAAS,CAAC,oBAAoB,KAAK,QAAQ;YAClD,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,oBAAoB,CAAC;YACjD,SAAS,CAAC,oBAAoB,GAAG,CAAC;YAClC,SAAS,CAAC,oBAAoB,GAAG,aAAa,EAC9C,CAAC;YACD,MAAM,IAAI,4BAAmB,CAC3B,gEAAgE,CACjE,CAAC;QACJ,CAAC;IACH,CAAC;IAES,QAAQ;QAChB,MAAM,IAAI,0BAAiB,CAAC,4CAA4C,CAAC,CAAC;IAC5E,CAAC;IAEO,WAAW,CACjB,KAIU;QAEV,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,MAAM,IAAI,4BAAmB,CAC3B,mEAAmE,CACpE,CAAC;QACJ,CAAC;QACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC;QACpE,CAAC;QACD,OAAO,IAAI,CAAC,gBAAgB,CAAC,KAAiC,CAAC,CAAC;IAClE,CAAC;IAEO,kBAAkB,CAAC,KAAc;QACvC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO;QAChD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;YACvD,OAAO;QACT,CAAC;QACD,MAAM,SAAS,GAAG,KAAgC,CAAC;QACnD,IAAI,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC;YAAE,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;QACpE,IAAI,SAAS,CAAC,OAAO,IAAI,OAAO,SAAS,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;YAC/D,IAAI,CAAC,kBAAkB,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAC7C,CAAC;QACD,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,gBAAgB,CAAC,EAAE,CAAC;YAC9C,SAAS,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAC1C,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAC9B,CAAC;QACJ,CAAC;IACH,CAAC;CACF;AA7SD,8CA6SC;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,OAAO,CACL,CAAC,CAAC,KAAK;QACP,OAAO,KAAK,KAAK,QAAQ;QACzB,OAAQ,KAAmB,CAAC,OAAO,KAAK,QAAQ;QAChD,OAAQ,KAAmB,CAAC,QAAQ,KAAK,UAAU,CACpD,CAAC;AACJ,CAAC;AAED,MAAM,gBAAgB,GAAc;IAClC,OAAO,EAAE,SAAS;IAClB,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,WAAW,GAAG,EAAE;CACpC,CAAC"}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { BadRequestException, NotFoundException } from '@nestjs/common';
|
|
2
|
+
import type { CrudGenFindManyOptions } from '@nest-yalc-2/crud-gen/api-graphql/crud-gen-gql.interface.js';
|
|
3
|
+
import type { IWhereCondition } from '@nest-yalc-2/crud-gen/api-graphql/crud-gen-gql.type.js';
|
|
4
|
+
import { Operators } from '@nest-yalc-2/crud-gen/crud-gen.enum.js';
|
|
5
|
+
import { GenericService } from '@nest-yalc-2/crud-gen/typeorm/generic.service.js';
|
|
6
|
+
import type { DeepPartial, FindOptionsWhere, ObjectLiteral } from 'typeorm';
|
|
7
|
+
import { IsNull } from 'typeorm';
|
|
8
|
+
import type { OmniDeletePolicy, OmniScope } from './omni-scope.js';
|
|
9
|
+
|
|
10
|
+
type ScopedEntity = ObjectLiteral & {
|
|
11
|
+
scopeId: string;
|
|
12
|
+
deletedAt?: Date | null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function hasOwn(input: object, key: string): boolean {
|
|
16
|
+
return Object.prototype.hasOwnProperty.call(input, key);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The shared service boundary for scoped Omni CRUD. Every lookup, mutation,
|
|
21
|
+
* and generated grid is scoped before it reaches the generic CrudGen service.
|
|
22
|
+
*/
|
|
23
|
+
export class OmniScopedService<
|
|
24
|
+
Entity extends ScopedEntity,
|
|
25
|
+
> extends GenericService<Entity> {
|
|
26
|
+
constructor(
|
|
27
|
+
repository: any,
|
|
28
|
+
scopeOrRepositoryWrite?: OmniScope | any,
|
|
29
|
+
private readonly deletion: OmniDeletePolicy = 'hard',
|
|
30
|
+
) {
|
|
31
|
+
const scope = isOmniScope(scopeOrRepositoryWrite)
|
|
32
|
+
? scopeOrRepositoryWrite
|
|
33
|
+
: defaultOmniScope;
|
|
34
|
+
super(
|
|
35
|
+
repository,
|
|
36
|
+
isOmniScope(scopeOrRepositoryWrite) ? undefined : scopeOrRepositoryWrite,
|
|
37
|
+
);
|
|
38
|
+
this.scope = scope;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
protected readonly scope: OmniScope;
|
|
42
|
+
|
|
43
|
+
protected get scopeId(): string {
|
|
44
|
+
return this.scope.scopeId;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
protected scopedConditions(
|
|
48
|
+
conditions: FindOptionsWhere<Entity>,
|
|
49
|
+
): FindOptionsWhere<Entity> {
|
|
50
|
+
this.rejectClientScope(conditions as object);
|
|
51
|
+
return {
|
|
52
|
+
...conditions,
|
|
53
|
+
scopeId: this.scopeId,
|
|
54
|
+
...(this.deletion === 'tombstone' ? { deletedAt: IsNull() } : {}),
|
|
55
|
+
} as unknown as FindOptionsWhere<Entity>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
override async getEntity(
|
|
59
|
+
conditions:
|
|
60
|
+
| FindOptionsWhere<Entity>[]
|
|
61
|
+
| FindOptionsWhere<Entity>
|
|
62
|
+
| ObjectLiteral
|
|
63
|
+
| string,
|
|
64
|
+
fields?: (keyof Entity)[],
|
|
65
|
+
relations?: string[],
|
|
66
|
+
databaseName?: string,
|
|
67
|
+
options?: { failOnNull: false },
|
|
68
|
+
): Promise<Entity | undefined>;
|
|
69
|
+
override async getEntity(
|
|
70
|
+
conditions:
|
|
71
|
+
| FindOptionsWhere<Entity>[]
|
|
72
|
+
| FindOptionsWhere<Entity>
|
|
73
|
+
| ObjectLiteral
|
|
74
|
+
| string,
|
|
75
|
+
fields?: (keyof Entity)[],
|
|
76
|
+
relations?: string[],
|
|
77
|
+
databaseName?: string,
|
|
78
|
+
options?: { failOnNull?: boolean },
|
|
79
|
+
): Promise<Entity>;
|
|
80
|
+
override async getEntity(
|
|
81
|
+
conditions:
|
|
82
|
+
| FindOptionsWhere<Entity>[]
|
|
83
|
+
| FindOptionsWhere<Entity>
|
|
84
|
+
| ObjectLiteral
|
|
85
|
+
| string,
|
|
86
|
+
fields?: (keyof Entity)[],
|
|
87
|
+
relations?: string[],
|
|
88
|
+
databaseName?: string,
|
|
89
|
+
options?: { failOnNull?: boolean },
|
|
90
|
+
): Promise<Entity | undefined> {
|
|
91
|
+
const entity = await super.getEntity(
|
|
92
|
+
this.scopedWhere(conditions),
|
|
93
|
+
fields,
|
|
94
|
+
relations,
|
|
95
|
+
databaseName,
|
|
96
|
+
{ failOnNull: false },
|
|
97
|
+
);
|
|
98
|
+
if (!entity && options?.failOnNull) this.notFound();
|
|
99
|
+
return entity;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
override async getEntityListExtended(
|
|
103
|
+
findOptions: CrudGenFindManyOptions<Entity>,
|
|
104
|
+
withCount?: false,
|
|
105
|
+
relations?: string[],
|
|
106
|
+
databaseName?: string,
|
|
107
|
+
): Promise<Entity[]>;
|
|
108
|
+
override async getEntityListExtended(
|
|
109
|
+
findOptions: CrudGenFindManyOptions<Entity>,
|
|
110
|
+
withCount: true,
|
|
111
|
+
relations?: string[],
|
|
112
|
+
databaseName?: string,
|
|
113
|
+
): Promise<[Entity[], number]>;
|
|
114
|
+
override async getEntityListExtended(
|
|
115
|
+
findOptions: CrudGenFindManyOptions<Entity> = {},
|
|
116
|
+
withCount = false,
|
|
117
|
+
relations?: string[],
|
|
118
|
+
databaseName?: string,
|
|
119
|
+
): Promise<Entity[] | [Entity[], number]> {
|
|
120
|
+
const scopedOptions = this.scopeFindOptions(findOptions);
|
|
121
|
+
if (withCount) {
|
|
122
|
+
return super.getEntityListExtended(
|
|
123
|
+
scopedOptions,
|
|
124
|
+
true,
|
|
125
|
+
relations,
|
|
126
|
+
databaseName,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
return super.getEntityListExtended(
|
|
130
|
+
scopedOptions,
|
|
131
|
+
false,
|
|
132
|
+
relations,
|
|
133
|
+
databaseName,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private scopeFindOptions(
|
|
138
|
+
findOptions: CrudGenFindManyOptions<Entity>,
|
|
139
|
+
): CrudGenFindManyOptions<Entity> {
|
|
140
|
+
this.rejectScopeInWhere(findOptions.where);
|
|
141
|
+
const userWhere = findOptions.where;
|
|
142
|
+
const filters: IWhereCondition<Entity>['filters'] = {};
|
|
143
|
+
filters.scopeId = this.scopeId;
|
|
144
|
+
if (this.deletion === 'tombstone') filters.deletedAt = IsNull();
|
|
145
|
+
const where: IWhereCondition<Entity> = {
|
|
146
|
+
operator: Operators.AND,
|
|
147
|
+
filters,
|
|
148
|
+
...(userWhere ? { childExpressions: [userWhere] } : {}),
|
|
149
|
+
};
|
|
150
|
+
return {
|
|
151
|
+
...findOptions,
|
|
152
|
+
where,
|
|
153
|
+
...(findOptions.subQueryFilters
|
|
154
|
+
? {
|
|
155
|
+
subQueryFilters: this.scopeFindOptions(findOptions.subQueryFilters),
|
|
156
|
+
}
|
|
157
|
+
: {}),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
override async createEntity(
|
|
162
|
+
input: DeepPartial<Entity>,
|
|
163
|
+
findOptions?: CrudGenFindManyOptions<Entity>,
|
|
164
|
+
returnEntity?: true,
|
|
165
|
+
): Promise<Entity>;
|
|
166
|
+
override async createEntity(
|
|
167
|
+
input: DeepPartial<Entity>,
|
|
168
|
+
findOptions?: CrudGenFindManyOptions<Entity>,
|
|
169
|
+
returnEntity?: boolean,
|
|
170
|
+
): Promise<Entity | boolean>;
|
|
171
|
+
override async createEntity(
|
|
172
|
+
input: DeepPartial<Entity>,
|
|
173
|
+
findOptions?: CrudGenFindManyOptions<Entity>,
|
|
174
|
+
returnEntity = true,
|
|
175
|
+
): Promise<Entity | boolean> {
|
|
176
|
+
this.rejectServerFields(input as object);
|
|
177
|
+
this.validatePayloadContract(input as object);
|
|
178
|
+
return super.createEntity(
|
|
179
|
+
{ ...input, scopeId: this.scopeId } as DeepPartial<Entity>,
|
|
180
|
+
findOptions,
|
|
181
|
+
returnEntity,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
override async updateEntity(
|
|
186
|
+
conditions: FindOptionsWhere<Entity>,
|
|
187
|
+
input: DeepPartial<Entity>,
|
|
188
|
+
findOptions?: CrudGenFindManyOptions<Entity>,
|
|
189
|
+
returnEntity?: true,
|
|
190
|
+
): Promise<Entity>;
|
|
191
|
+
override async updateEntity(
|
|
192
|
+
conditions: FindOptionsWhere<Entity>,
|
|
193
|
+
input: DeepPartial<Entity>,
|
|
194
|
+
findOptions?: CrudGenFindManyOptions<Entity>,
|
|
195
|
+
returnEntity?: boolean,
|
|
196
|
+
): Promise<Entity | boolean>;
|
|
197
|
+
override async updateEntity(
|
|
198
|
+
conditions: FindOptionsWhere<Entity>,
|
|
199
|
+
input: DeepPartial<Entity>,
|
|
200
|
+
findOptions?: CrudGenFindManyOptions<Entity>,
|
|
201
|
+
returnEntity = true,
|
|
202
|
+
): Promise<Entity | boolean> {
|
|
203
|
+
this.rejectServerFields(input as object);
|
|
204
|
+
this.validatePayloadContract(input as object);
|
|
205
|
+
if (hasOwn(input as object, 'guid')) {
|
|
206
|
+
throw new BadRequestException('guid is immutable.');
|
|
207
|
+
}
|
|
208
|
+
return super.updateEntity(
|
|
209
|
+
this.scopedConditions(conditions),
|
|
210
|
+
{ ...input, scopeId: this.scopeId } as DeepPartial<Entity>,
|
|
211
|
+
findOptions,
|
|
212
|
+
returnEntity,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
override async deleteEntity(
|
|
217
|
+
conditions: FindOptionsWhere<Entity>,
|
|
218
|
+
): Promise<boolean> {
|
|
219
|
+
const scoped = this.scopedConditions(conditions);
|
|
220
|
+
if (this.deletion === 'hard') {
|
|
221
|
+
return super.deleteEntity(scoped);
|
|
222
|
+
}
|
|
223
|
+
const result = await this.getRepository().update(scoped, {
|
|
224
|
+
deletedAt: new Date(),
|
|
225
|
+
} as DeepPartial<Entity>);
|
|
226
|
+
if (!result.affected) this.notFound();
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
protected rejectServerFields(input: object): void {
|
|
231
|
+
for (const field of ['scopeId', 'revision', 'deletedAt']) {
|
|
232
|
+
if (hasOwn(input, field)) {
|
|
233
|
+
throw new BadRequestException(`${field} is server-owned.`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
protected rejectClientScope(input: object): void {
|
|
239
|
+
if (hasOwn(input, 'scopeId')) {
|
|
240
|
+
throw new BadRequestException('scopeId is derived from server context.');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private validatePayloadContract(input: object): void {
|
|
245
|
+
const candidate = input as Record<string, unknown>;
|
|
246
|
+
if (
|
|
247
|
+
hasOwn(candidate, 'payload') &&
|
|
248
|
+
candidate.payload !== null &&
|
|
249
|
+
(typeof candidate.payload !== 'object' ||
|
|
250
|
+
Array.isArray(candidate.payload))
|
|
251
|
+
) {
|
|
252
|
+
throw new BadRequestException('payload must be a JSON object or null.');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const hasSchemaId = hasOwn(candidate, 'payloadSchemaId');
|
|
256
|
+
const hasSchemaVersion = hasOwn(candidate, 'payloadSchemaVersion');
|
|
257
|
+
if (hasSchemaId !== hasSchemaVersion) {
|
|
258
|
+
throw new BadRequestException(
|
|
259
|
+
'payloadSchemaId and payloadSchemaVersion must be supplied together.',
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
if (!hasSchemaId) return;
|
|
263
|
+
|
|
264
|
+
if (
|
|
265
|
+
typeof candidate.payloadSchemaId !== 'string' ||
|
|
266
|
+
candidate.payloadSchemaId.trim().length === 0 ||
|
|
267
|
+
candidate.payloadSchemaId.length > 128
|
|
268
|
+
) {
|
|
269
|
+
throw new BadRequestException(
|
|
270
|
+
'payloadSchemaId must be a non-empty string.',
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
if (
|
|
274
|
+
typeof candidate.payloadSchemaVersion !== 'number' ||
|
|
275
|
+
!Number.isInteger(candidate.payloadSchemaVersion) ||
|
|
276
|
+
candidate.payloadSchemaVersion < 1 ||
|
|
277
|
+
candidate.payloadSchemaVersion > 2_147_483_647
|
|
278
|
+
) {
|
|
279
|
+
throw new BadRequestException(
|
|
280
|
+
'payloadSchemaVersion must be a positive signed 32-bit integer.',
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
protected notFound(): never {
|
|
286
|
+
throw new NotFoundException('Omni resource was not found in this scope.');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private scopedWhere(
|
|
290
|
+
where:
|
|
291
|
+
| FindOptionsWhere<Entity>[]
|
|
292
|
+
| FindOptionsWhere<Entity>
|
|
293
|
+
| ObjectLiteral
|
|
294
|
+
| string,
|
|
295
|
+
): FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[] {
|
|
296
|
+
if (typeof where === 'string') {
|
|
297
|
+
throw new BadRequestException(
|
|
298
|
+
'String where clauses are not supported for scoped Omni resources.',
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
if (Array.isArray(where)) {
|
|
302
|
+
return where.map((condition) => this.scopedConditions(condition));
|
|
303
|
+
}
|
|
304
|
+
return this.scopedConditions(where as FindOptionsWhere<Entity>);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private rejectScopeInWhere(where: unknown): void {
|
|
308
|
+
if (!where || typeof where !== 'object') return;
|
|
309
|
+
if (Array.isArray(where)) {
|
|
310
|
+
where.forEach((item) => this.rejectScopeInWhere(item));
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const candidate = where as Record<string, unknown>;
|
|
314
|
+
if (hasOwn(candidate, 'scopeId')) this.rejectClientScope(candidate);
|
|
315
|
+
if (candidate.filters && typeof candidate.filters === 'object') {
|
|
316
|
+
this.rejectScopeInWhere(candidate.filters);
|
|
317
|
+
}
|
|
318
|
+
if (Array.isArray(candidate.childExpressions)) {
|
|
319
|
+
candidate.childExpressions.forEach((item) =>
|
|
320
|
+
this.rejectScopeInWhere(item),
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function isOmniScope(value: unknown): value is OmniScope {
|
|
327
|
+
return (
|
|
328
|
+
!!value &&
|
|
329
|
+
typeof value === 'object' &&
|
|
330
|
+
typeof (value as OmniScope).scopeId === 'string' &&
|
|
331
|
+
typeof (value as OmniScope).cacheKey === 'function'
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const defaultOmniScope: OmniScope = {
|
|
336
|
+
scopeId: 'default',
|
|
337
|
+
cacheKey: (key) => `default:${key}`,
|
|
338
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DataSource } from 'typeorm';
|
|
2
|
+
export interface OmniKernelQueryPlanEvidence {
|
|
3
|
+
dialect: 'sqlite' | 'postgres';
|
|
4
|
+
recordGridPlan: readonly string[];
|
|
5
|
+
relationSourcePlan: readonly string[];
|
|
6
|
+
usesRecordGridIndex: boolean;
|
|
7
|
+
usesRelationSourceIndex: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function collectOmniKernelQueryPlanEvidence(dataSource: DataSource, sample: {
|
|
10
|
+
scopeId: string;
|
|
11
|
+
recordKind: string;
|
|
12
|
+
recordStatus: string;
|
|
13
|
+
sourceRecordId: string;
|
|
14
|
+
relationKind: string;
|
|
15
|
+
relationStatus: string;
|
|
16
|
+
}): Promise<OmniKernelQueryPlanEvidence>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.collectOmniKernelQueryPlanEvidence = collectOmniKernelQueryPlanEvidence;
|
|
4
|
+
const recordGridIndex = 'omni_record_scope_kind_status_guid_idx';
|
|
5
|
+
const relationSourceIndex = 'omni_relation_scope_source_kind_status_created_guid_idx';
|
|
6
|
+
async function collectOmniKernelQueryPlanEvidence(dataSource, sample) {
|
|
7
|
+
const dialect = dataSource.options.type;
|
|
8
|
+
if (dialect !== 'sqlite' && dialect !== 'postgres') {
|
|
9
|
+
throw new TypeError('OmniKernel diagnostics support sqlite and postgres only.');
|
|
10
|
+
}
|
|
11
|
+
const statements = dialect === 'sqlite'
|
|
12
|
+
? {
|
|
13
|
+
record: `EXPLAIN QUERY PLAN SELECT "guid" FROM "omni-record" WHERE "scopeId" = ? AND "kind" = ? AND "status" = ? AND "deletedAt" IS NULL ORDER BY "guid"`,
|
|
14
|
+
relation: `EXPLAIN QUERY PLAN SELECT "guid" FROM "omni-relation" WHERE "scopeId" = ? AND "sourceRecordId" = ? AND "kind" = ? AND "status" = ? ORDER BY "createdAt", "guid"`,
|
|
15
|
+
}
|
|
16
|
+
: {
|
|
17
|
+
record: `EXPLAIN (FORMAT JSON) SELECT "guid" FROM "omni-record" WHERE "scopeId" = $1 AND "kind" = $2 AND "status" = $3 AND "deletedAt" IS NULL ORDER BY "guid"`,
|
|
18
|
+
relation: `EXPLAIN (FORMAT JSON) SELECT "guid" FROM "omni-relation" WHERE "scopeId" = $1 AND "sourceRecordId" = $2 AND "kind" = $3 AND "status" = $4 ORDER BY "createdAt", "guid"`,
|
|
19
|
+
};
|
|
20
|
+
const recordResult = await dataSource.query(statements.record, [
|
|
21
|
+
sample.scopeId,
|
|
22
|
+
sample.recordKind,
|
|
23
|
+
sample.recordStatus,
|
|
24
|
+
]);
|
|
25
|
+
const relationResult = await dataSource.query(statements.relation, [
|
|
26
|
+
sample.scopeId,
|
|
27
|
+
sample.sourceRecordId,
|
|
28
|
+
sample.relationKind,
|
|
29
|
+
sample.relationStatus,
|
|
30
|
+
]);
|
|
31
|
+
const recordGridPlan = flattenPlan(recordResult);
|
|
32
|
+
const relationSourcePlan = flattenPlan(relationResult);
|
|
33
|
+
return {
|
|
34
|
+
dialect,
|
|
35
|
+
recordGridPlan,
|
|
36
|
+
relationSourcePlan,
|
|
37
|
+
usesRecordGridIndex: containsIndex(recordGridPlan, recordGridIndex),
|
|
38
|
+
usesRelationSourceIndex: containsIndex(relationSourcePlan, relationSourceIndex),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function flattenPlan(result) {
|
|
42
|
+
return JSON.stringify(result)
|
|
43
|
+
.replaceAll('\\n', ' ')
|
|
44
|
+
.split(/(?<=\}),|\n/)
|
|
45
|
+
.filter((line) => line.length > 0);
|
|
46
|
+
}
|
|
47
|
+
function containsIndex(plan, indexName) {
|
|
48
|
+
return plan.some((line) => line.includes(indexName));
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=omnikernel.diagnostics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"omnikernel.diagnostics.js","sourceRoot":"","sources":["omnikernel.diagnostics.ts"],"names":[],"mappings":";;AAkBA,gFAqDC;AA7DD,MAAM,eAAe,GAAG,wCAAwC,CAAC;AACjE,MAAM,mBAAmB,GACvB,yDAAyD,CAAC;AAMrD,KAAK,UAAU,kCAAkC,CACtD,UAAsB,EACtB,MAOC;IAED,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC;IACxC,IAAI,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,UAAU,EAAE,CAAC;QACnD,MAAM,IAAI,SAAS,CACjB,0DAA0D,CAC3D,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GACd,OAAO,KAAK,QAAQ;QAClB,CAAC,CAAC;YACE,MAAM,EAAE,iJAAiJ;YACzJ,QAAQ,EAAE,iKAAiK;SAC5K;QACH,CAAC,CAAC;YACE,MAAM,EAAE,uJAAuJ;YAC/J,QAAQ,EAAE,wKAAwK;SACnL,CAAC;IAER,MAAM,YAAY,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,EAAE;QAC7D,MAAM,CAAC,OAAO;QACd,MAAM,CAAC,UAAU;QACjB,MAAM,CAAC,YAAY;KACpB,CAAC,CAAC;IACH,MAAM,cAAc,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,QAAQ,EAAE;QACjE,MAAM,CAAC,OAAO;QACd,MAAM,CAAC,cAAc;QACrB,MAAM,CAAC,YAAY;QACnB,MAAM,CAAC,cAAc;KACtB,CAAC,CAAC;IACH,MAAM,cAAc,GAAG,WAAW,CAAC,YAAY,CAAC,CAAC;IACjD,MAAM,kBAAkB,GAAG,WAAW,CAAC,cAAc,CAAC,CAAC;IAEvD,OAAO;QACL,OAAO;QACP,cAAc;QACd,kBAAkB;QAClB,mBAAmB,EAAE,aAAa,CAAC,cAAc,EAAE,eAAe,CAAC;QACnE,uBAAuB,EAAE,aAAa,CACpC,kBAAkB,EAClB,mBAAmB,CACpB;KACF,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,MAAe;IAClC,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;SAC1B,UAAU,CAAC,KAAK,EAAE,GAAG,CAAC;SACtB,KAAK,CAAC,aAAa,CAAC;SACpB,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACvC,CAAC;AAED,SAAS,aAAa,CAAC,IAAuB,EAAE,SAAiB;IAC/D,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;AACvD,CAAC"}
|