UNPKG

@elsikora/nestjs-crud-automator

Version:

A library for automating the creation of CRUD operations in NestJS.

251 lines (247 loc) 13.7 kB
'use strict'; var node_async_hooks = require('node:async_hooks'); var scopeWhereContract_class = require('./scope-where-contract.class.js'); var securitySnapshot_class = require('./security-snapshot.class.js'); var scopeMergeWhere_utility = require('../../../../utility/authorization/scope-merge-where.utility.js'); var exception_utility = require('../../../../utility/error/exception.utility.js'); var objectFindPropertyDescriptor_utility = require('../../../../utility/object-find-property-descriptor.utility.js'); var typeorm = require('typeorm'); /** * Preserves pagination-critical GET_MANY options owned by a generated cursor route. * Direct service calls retain the ordinary subscriber contract. */ class ApiControllerGeneratedGetManyContract { static STORAGE = new node_async_hooks.AsyncLocalStorage(); static createSnapshot(properties) { const snapshot = { order: properties.order, take: properties.take, }; for (const key of ["join", "lock", "withDeleted"]) { this.copyDataProperty(properties, snapshot, key); } return securitySnapshot_class.ApiControllerGeneratedSecuritySnapshot.detach(snapshot); } static hasActiveSession() { return this.STORAGE.getStore() !== undefined; } static protect(properties, snapshot) { const session = this.STORAGE.getStore(); const protectedProperties = Object.create(Object.getPrototypeOf(properties)); for (const key of Reflect.ownKeys(properties)) { if (key === "cache" || key === "join" || key === "lock" || key === "order" || key === "select" || key === "skip" || key === "take" || key === "withDeleted") { continue; } const descriptor = Object.getOwnPropertyDescriptor(properties, key); if (descriptor) { Object.defineProperty(protectedProperties, key, descriptor); } } for (const key of ["select", "skip"]) { Object.defineProperty(protectedProperties, key, { // eslint-disable-next-line @elsikora/typescript/naming-convention configurable: true, // eslint-disable-next-line @elsikora/typescript/naming-convention enumerable: true, value: undefined, // eslint-disable-next-line @elsikora/typescript/naming-convention writable: true, }); } this.defineValue(protectedProperties, "cache", false); for (const key of ["order", "take"]) { const descriptor = Object.getOwnPropertyDescriptor(snapshot, key); if (descriptor) { Object.defineProperty(protectedProperties, key, descriptor); } } if (session) { for (const key of ["join", "lock"]) { this.defineValue(protectedProperties, key); } this.copyDataProperty(properties, protectedProperties, "withDeleted"); this.defineValue(protectedProperties, "where", this.resolveScopedWindowWhere(session)); } else { for (const key of ["join", "lock", "withDeleted"]) { const descriptor = Object.getOwnPropertyDescriptor(snapshot, key); if (descriptor) { Object.defineProperty(protectedProperties, key, descriptor); } } } return protectedProperties; } static async resolveBefore(properties, callback) { const session = this.STORAGE.getStore(); if (!session) { return (await callback(properties)) ?? properties; } if (session.state.isPrepared) { return this.applyCandidateProperties(properties, session.state.candidateProperties); } session.state.isPrepared = true; const beforeProperties = securitySnapshot_class.ApiControllerGeneratedSecuritySnapshot.detach(session.baseProperties); const subscriberProperties = securitySnapshot_class.ApiControllerGeneratedSecuritySnapshot.detach((await callback(beforeProperties)) ?? beforeProperties); this.assertSupportedCandidateProperties(subscriberProperties); session.state.candidateProperties = this.createCandidateProperties(subscriberProperties); this.assertStableCandidateWhere(session.state.candidateProperties.where); session.state.isCandidateWhereScoped = scopeWhereContract_class.ApiControllerGeneratedScopeWhereContract.contains(session.state.candidateProperties.where, session.baseProperties.where); return this.applyCandidateProperties(subscriberProperties, session.state.candidateProperties); } static run(baseProperties, callback) { this.assertStableCandidateWhere(baseProperties.where); return this.STORAGE.run({ baseProperties: securitySnapshot_class.ApiControllerGeneratedSecuritySnapshot.detach(baseProperties), state: { isPrepared: false }, }, callback); } static runWindow(windowWhere, callback) { const session = this.STORAGE.getStore(); if (!session) { return callback(); } return this.STORAGE.run({ ...session, windowWhere: securitySnapshot_class.ApiControllerGeneratedSecuritySnapshot.detach(windowWhere), }, callback); } static applyCandidateProperties(properties, candidateProperties) { const result = securitySnapshot_class.ApiControllerGeneratedSecuritySnapshot.detach(properties); if (candidateProperties) { for (const key of ["where", "withDeleted"]) { const descriptor = Object.getOwnPropertyDescriptor(candidateProperties, key); if (descriptor) { Object.defineProperty(result, key, securitySnapshot_class.ApiControllerGeneratedSecuritySnapshot.detach(descriptor)); } } } return result; } static assertCanonicalFindOperator(value, isEqualOperator) { const expectedKeys = ["@instanceof", "_getSql", "_multipleParameters", "_objectLiteralParameters", "_type", "_useParameter", "_value"]; const actualKeys = Reflect.ownKeys(value); if (actualKeys.length !== expectedKeys.length || actualKeys.some((key) => typeof key !== "string" || !expectedKeys.includes(key))) { throw exception_utility.ErrorException("CURSOR GET_LIST candidate WHERE contains a non-canonical TypeORM operator"); } for (const key of expectedKeys) { const descriptor = Object.getOwnPropertyDescriptor(value, key); if (!descriptor || !("value" in descriptor) || !descriptor.configurable || !descriptor.enumerable || !descriptor.writable) { throw exception_utility.ErrorException("CURSOR GET_LIST candidate WHERE contains a non-canonical TypeORM operator"); } } const expectedMarker = Symbol.for(isEqualOperator ? "EqualOperator" : "FindOperator"); const marker = Object.getOwnPropertyDescriptor(value, "@instanceof")?.value; const type = Object.getOwnPropertyDescriptor(value, "_type")?.value; const getSql = Object.getOwnPropertyDescriptor(value, "_getSql")?.value; const objectLiteralParameters = Object.getOwnPropertyDescriptor(value, "_objectLiteralParameters")?.value; if (marker !== expectedMarker || typeof type !== "string") { throw exception_utility.ErrorException("CURSOR GET_LIST candidate WHERE contains a non-canonical TypeORM operator"); } if (type === "raw") { throw exception_utility.ErrorException("CURSOR GET_LIST candidate WHERE cannot contain raw SQL operators"); } if (getSql !== undefined || objectLiteralParameters !== undefined) { throw exception_utility.ErrorException("CURSOR GET_LIST candidate WHERE contains a non-canonical TypeORM operator"); } } static assertStableCandidateWhere(value, visited = new WeakSet()) { if (typeof value === "function") { throw exception_utility.ErrorException("CURSOR GET_LIST candidate WHERE cannot contain function-backed values"); } if (!value || typeof value !== "object" || visited.has(value) || value instanceof ArrayBuffer || value instanceof Date || value instanceof RegExp) { return; } if (ArrayBuffer.isView(value)) { if (typeof SharedArrayBuffer !== "undefined" && value.buffer instanceof SharedArrayBuffer) { throw exception_utility.ErrorException("CURSOR GET_LIST candidate WHERE cannot contain shared-memory values"); } return; } visited.add(value); const prototype = Object.getPrototypeOf(value); const isFindOperator = typeorm.InstanceChecker.isFindOperator(value); if (isFindOperator) { if (prototype !== typeorm.EqualOperator.prototype && prototype !== typeorm.FindOperator.prototype) { throw exception_utility.ErrorException("CURSOR GET_LIST candidate WHERE contains an unsupported executable value"); } this.assertCanonicalFindOperator(value, prototype === typeorm.EqualOperator.prototype); } else if (!Array.isArray(value) && prototype !== null && prototype !== Object.prototype) { throw exception_utility.ErrorException("CURSOR GET_LIST candidate WHERE contains an unsupported executable value"); } for (const key of Reflect.ownKeys(value)) { if (Array.isArray(value) && key === "length") { continue; } const descriptor = Object.getOwnPropertyDescriptor(value, key); if (!descriptor || !("value" in descriptor)) { throw exception_utility.ErrorException("CURSOR GET_LIST candidate WHERE must contain data properties only"); } this.assertStableCandidateWhere(descriptor.value, visited); } } static assertSupportedCandidateProperties(properties) { for (const key of ["join", "lock"]) { const descriptor = objectFindPropertyDescriptor_utility.ObjectFindPropertyDescriptor(properties, key); if (descriptor && (!this.isDataProperty(descriptor) || descriptor.value !== undefined)) { throw exception_utility.ErrorException(`CURSOR GET_LIST function subscriber cannot set ${key}`); } } const withDeletedDescriptor = objectFindPropertyDescriptor_utility.ObjectFindPropertyDescriptor(properties, "withDeleted"); if (withDeletedDescriptor && (!this.isDataProperty(withDeletedDescriptor) || (withDeletedDescriptor.value !== undefined && typeof withDeletedDescriptor.value !== "boolean"))) { throw exception_utility.ErrorException("CURSOR GET_LIST function subscriber withDeleted must be a boolean data property"); } } static copyDataProperty(source, target, key) { const descriptor = objectFindPropertyDescriptor_utility.ObjectFindPropertyDescriptor(source, key); if (!descriptor) { this.defineValue(target, key); return; } if (!this.isDataProperty(descriptor)) { throw exception_utility.ErrorException(`Generated GET_MANY option ${String(key)} must be a data property`); } Object.defineProperty(target, key, securitySnapshot_class.ApiControllerGeneratedSecuritySnapshot.detach(descriptor)); } static createCandidateProperties(properties) { const candidateProperties = {}; for (const key of ["where", "withDeleted"]) { this.copyDataProperty(properties, candidateProperties, key); } return securitySnapshot_class.ApiControllerGeneratedSecuritySnapshot.detach(candidateProperties); } static defineValue(target, key, value) { Object.defineProperty(target, key, { // eslint-disable-next-line @elsikora/typescript/naming-convention configurable: true, // eslint-disable-next-line @elsikora/typescript/naming-convention enumerable: true, value, // eslint-disable-next-line @elsikora/typescript/naming-convention writable: true, }); } static isDataProperty(descriptor) { return "value" in descriptor; } static readWhere(properties) { const descriptor = objectFindPropertyDescriptor_utility.ObjectFindPropertyDescriptor(properties, "where"); if (!descriptor) { return undefined; } if (!this.isDataProperty(descriptor)) { throw exception_utility.ErrorException("Generated GET_MANY option where must be a data property"); } const value = descriptor.value; return value; } static resolveScopedWindowWhere(session) { const baseWhere = this.readWhere(session.baseProperties); const candidateWhere = session.state.candidateProperties ? this.readWhere(session.state.candidateProperties) : undefined; const candidateScope = session.state.isCandidateWhereScoped ? candidateWhere : scopeMergeWhere_utility.AuthorizationScopeMergeWhere(candidateWhere, baseWhere); return scopeMergeWhere_utility.AuthorizationScopeMergeWhere(securitySnapshot_class.ApiControllerGeneratedSecuritySnapshot.detach(candidateScope), securitySnapshot_class.ApiControllerGeneratedSecuritySnapshot.detach(session.windowWhere)); } } exports.ApiControllerGeneratedGetManyContract = ApiControllerGeneratedGetManyContract; //# sourceMappingURL=get-many-contract.class.js.map