@nestjs-odata/typeorm 1.0.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/README.md +58 -0
- package/dist/index.cjs +2258 -0
- package/dist/index.d.cts +705 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +705 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2208 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/batch/batch-response-builder.ts","../src/deriver/typeorm-type-mapper.ts","../src/deriver/typeorm-edm-deriver.ts","../src/translator/filter-visitor.ts","../src/translator/select-visitor.ts","../src/translator/orderby-visitor.ts","../src/translator/pagination-visitor.ts","../src/translator/expand-visitor.ts","../src/translator/apply-visitor.ts","../src/translator/expand-pagination.ts","../src/translator/typeorm-query-translator.ts","../src/translator/typeorm-auto-handler.ts","../src/etag/typeorm-etag.provider.ts","../src/translator/search-provider.ts","../src/odata-typeorm.module.ts","../src/batch/batch-controller.ts","../src/index.ts"],"sourcesContent":["/**\n * OData v4 $batch response builder.\n *\n * Builds a multipart/mixed HTTP response body from per-operation results.\n * Each operation gets its own HTTP status code and body (D-03).\n *\n * Per T-05-05: failed operation responses use OData error format,\n * no stack traces or internal details.\n *\n * Wire format per OData v4 Part 1 section 11:\n * --{boundary}\\r\\n\n * Content-Type: application/http\\r\\n\n * Content-Transfer-Encoding: binary\\r\\n\n * \\r\\n\n * HTTP/1.1 {status} {statusText}\\r\\n\n * Content-Type: application/json\\r\\n\n * \\r\\n\n * {body}\\r\\n\n * --{boundary}--\\r\\n\n */\n\nimport { randomUUID } from 'crypto'\nimport type { BatchResponsePart } from '@nestjs-odata/core'\n\n/**\n * Map of HTTP status codes to reason phrases.\n */\nconst STATUS_TEXTS: Record<number, string> = {\n 200: 'OK',\n 201: 'Created',\n 204: 'No Content',\n 400: 'Bad Request',\n 401: 'Unauthorized',\n 403: 'Forbidden',\n 404: 'Not Found',\n 405: 'Method Not Allowed',\n 409: 'Conflict',\n 422: 'Unprocessable Entity',\n 429: 'Too Many Requests',\n 500: 'Internal Server Error',\n 501: 'Not Implemented',\n 503: 'Service Unavailable',\n}\n\n/**\n * Build a multipart/mixed batch response from individual operation results.\n *\n * @param parts - Array of per-operation response descriptors\n * @returns Object with `contentType` (for Content-Type header) and `body` (response body string)\n */\nexport function buildBatchResponse(parts: BatchResponsePart[]): {\n contentType: string\n body: string\n} {\n const boundary = `batch_${randomUUID().replace(/-/g, '').slice(0, 16)}`\n const lines: string[] = []\n\n for (const part of parts) {\n const statusText = STATUS_TEXTS[part.statusCode] ?? 'Unknown'\n lines.push(`--${boundary}`)\n lines.push('Content-Type: application/http')\n lines.push('Content-Transfer-Encoding: binary')\n if (part.contentId !== undefined) {\n lines.push(`Content-ID: ${part.contentId}`)\n }\n lines.push('')\n lines.push(`HTTP/1.1 ${part.statusCode} ${statusText}`)\n\n if (part.body !== undefined) {\n lines.push('Content-Type: application/json')\n lines.push(`Content-Length: ${Buffer.byteLength(part.body, 'utf-8')}`)\n }\n lines.push('')\n\n if (part.body !== undefined) {\n lines.push(part.body)\n }\n }\n\n lines.push(`--${boundary}--`)\n lines.push('')\n\n const body = lines.join('\\r\\n')\n const contentType = `multipart/mixed; boundary=${boundary}`\n\n return { contentType, body }\n}\n","import type { EdmPrimitiveType, UnmappedTypeStrategy } from '@nestjs-odata/core'\n\n/**\n * Complete mapping from TypeORM column types to OData v4 EDM primitive types.\n * Note: Edm.DateTime does NOT exist in OData v4 — use Edm.DateTimeOffset.\n * Source: TypeORM ColumnTypes + OData v4 OASIS spec.\n */\nconst COLUMN_TYPE_MAP: Readonly<Record<string, EdmPrimitiveType>> = {\n // Int32 group\n int: 'Edm.Int32',\n int2: 'Edm.Int32',\n int4: 'Edm.Int32',\n integer: 'Edm.Int32',\n tinyint: 'Edm.Int32',\n smallint: 'Edm.Int32',\n mediumint: 'Edm.Int32',\n\n // Int64 group\n bigint: 'Edm.Int64',\n int8: 'Edm.Int64',\n int64: 'Edm.Int64',\n 'unsigned big int': 'Edm.Int64',\n\n // Double group\n float: 'Edm.Double',\n float4: 'Edm.Double',\n float8: 'Edm.Double',\n float64: 'Edm.Double',\n double: 'Edm.Double',\n real: 'Edm.Double',\n 'double precision': 'Edm.Double',\n\n // Decimal group\n decimal: 'Edm.Decimal',\n numeric: 'Edm.Decimal',\n money: 'Edm.Decimal',\n smallmoney: 'Edm.Decimal',\n\n // String group\n varchar: 'Edm.String',\n nvarchar: 'Edm.String',\n char: 'Edm.String',\n nchar: 'Edm.String',\n text: 'Edm.String',\n ntext: 'Edm.String',\n tinytext: 'Edm.String',\n mediumtext: 'Edm.String',\n longtext: 'Edm.String',\n clob: 'Edm.String',\n nclob: 'Edm.String',\n citext: 'Edm.String',\n shorttext: 'Edm.String',\n alphanum: 'Edm.String',\n long: 'Edm.String',\n\n // Boolean group\n boolean: 'Edm.Boolean',\n bool: 'Edm.Boolean',\n\n // Date (date-only, not datetime)\n date: 'Edm.Date',\n\n // DateTimeOffset (NEVER Edm.DateTime — removed in OData v4)\n datetime: 'Edm.DateTimeOffset',\n datetime2: 'Edm.DateTimeOffset',\n datetimeoffset: 'Edm.DateTimeOffset',\n timestamp: 'Edm.DateTimeOffset',\n timestamptz: 'Edm.DateTimeOffset',\n 'timestamp with local time zone': 'Edm.DateTimeOffset',\n smalldatetime: 'Edm.DateTimeOffset',\n\n // TimeOfDay\n time: 'Edm.TimeOfDay',\n timetz: 'Edm.TimeOfDay',\n\n // Guid\n uuid: 'Edm.Guid',\n uniqueidentifier: 'Edm.Guid',\n\n // Binary\n blob: 'Edm.Binary',\n tinyblob: 'Edm.Binary',\n mediumblob: 'Edm.Binary',\n longblob: 'Edm.Binary',\n bytea: 'Edm.Binary',\n bytes: 'Edm.Binary',\n binary: 'Edm.Binary',\n varbinary: 'Edm.Binary',\n image: 'Edm.Binary',\n bfile: 'Edm.Binary',\n raw: 'Edm.Binary',\n 'long raw': 'Edm.Binary',\n}\n\n/**\n * Unmapped column types — require strategy handling rather than direct mapping.\n * These types cannot be automatically represented as an OData v4 primitive type.\n */\nconst UNMAPPED_COLUMN_TYPES = new Set([\n 'json',\n 'jsonb',\n 'simple-json',\n 'simple-array',\n 'simple-enum',\n 'enum',\n 'set',\n])\n\n/**\n * Map a TypeORM column type to an OData v4 EDM primitive type.\n *\n * Resolution order:\n * 1. Direct lookup in the column type map\n * 2. Unmapped type — apply unmappedTypeStrategy\n * 3. JS design type fallback (Number, String, Boolean, Date)\n * 4. Unknown type — apply unmappedTypeStrategy\n *\n * CRITICAL: Edm.DateTime does NOT exist in OData v4. All datetime-like\n * types produce Edm.DateTimeOffset per the OASIS OData v4 specification.\n *\n * @param columnType - TypeORM column type string (e.g., 'varchar', 'int', 'datetime')\n * @param designType - JavaScript design-time type constructor (Number, String, Boolean, Date)\n * @param unmappedTypeStrategy - how to handle types not in the mapping table\n */\nexport function mapColumnTypeToEdm(\n columnType: string,\n designType: (new (...args: unknown[]) => unknown) | undefined,\n unmappedTypeStrategy: UnmappedTypeStrategy,\n): EdmPrimitiveType | undefined {\n const normalized = columnType.toLowerCase()\n\n // 1. Direct lookup\n const mapped = COLUMN_TYPE_MAP[normalized]\n if (mapped !== undefined) {\n return mapped\n }\n\n // 2. Explicitly unmapped types — apply strategy directly\n if (UNMAPPED_COLUMN_TYPES.has(normalized)) {\n return applyUnmappedStrategy(columnType, unmappedTypeStrategy)\n }\n\n // 3. JS design type fallback\n if (designType === Number) return 'Edm.Int32'\n if (designType === String) return 'Edm.String'\n if (designType === Boolean) return 'Edm.Boolean'\n if (designType === Date) return 'Edm.DateTimeOffset'\n\n // 4. Completely unknown type — apply strategy\n return applyUnmappedStrategy(columnType, unmappedTypeStrategy)\n}\n\nfunction applyUnmappedStrategy(\n columnType: string,\n strategy: UnmappedTypeStrategy,\n): EdmPrimitiveType | undefined {\n if (strategy === 'skip') {\n return undefined\n }\n if (strategy === 'string-fallback') {\n return 'Edm.String'\n }\n throw new Error(\n `Column type '${columnType}' cannot be mapped to an OData v4 EDM primitive type. ` +\n `Use @EdmType() decorator to specify the type explicitly, or change the unmappedTypeStrategy.`,\n )\n}\n","import type { EntityMetadata } from 'typeorm'\nimport type {\n EdmEntityConfig,\n EdmNavigationProperty,\n EdmPrimitiveType,\n EdmProperty,\n UnmappedTypeStrategy,\n} from '@nestjs-odata/core'\nimport {\n getEdmTypeOverrides,\n getEntitySetName,\n getExcludedProperties,\n pluralizeEntityName,\n} from '@nestjs-odata/core'\nimport type { IEdmDeriver, EntityClass } from '@nestjs-odata/core'\nimport { mapColumnTypeToEdm } from './typeorm-type-mapper.js'\n\n/**\n * TypeOrmEdmDeriver — derives OData v4 EdmEntityConfig[] from TypeORM EntityMetadata.\n *\n * Respects OData decorators: @EdmType, @ODataExclude, @ODataEntitySet.\n * Detects ViewEntity and marks them read-only.\n * Navigation property types are always namespace-qualified (e.g., 'Default.Category').\n *\n * CRITICAL: Never produces Edm.DateTime — all datetime-like types produce Edm.DateTimeOffset.\n *\n * Usage (runtime):\n * const deriver = new TypeOrmEdmDeriver('Default', 'skip')\n * // When called by the module, metadatas come from the registered DataSource\n *\n * Usage (testing):\n * deriver.deriveEntityTypes([Product], dataSource.entityMetadatas)\n */\nexport class TypeOrmEdmDeriver implements IEdmDeriver {\n constructor(\n private readonly namespace: string,\n private readonly unmappedTypeStrategy: UnmappedTypeStrategy,\n ) {}\n\n /**\n * Implement IEdmDeriver — called by the core module.\n * When entity metadatas are not provided, returns empty (requires integration with DataSource).\n */\n deriveEntityTypes(entityClasses: EntityClass[]): EdmEntityConfig[]\n /**\n * Overload for direct use with TypeORM EntityMetadata (used in tests and adapter integration).\n */\n deriveEntityTypes(\n entityClasses: EntityClass[],\n entityMetadatas: EntityMetadata[],\n ): EdmEntityConfig[]\n deriveEntityTypes(\n entityClasses: EntityClass[],\n entityMetadatas: EntityMetadata[] = [],\n ): EdmEntityConfig[] {\n return entityClasses\n .map((entityClass) => this.deriveEntity(entityClass, entityMetadatas))\n .filter((config): config is EdmEntityConfig => config !== null)\n }\n\n private deriveEntity(\n entityClass: EntityClass,\n entityMetadatas: EntityMetadata[],\n ): EdmEntityConfig | null {\n const meta = entityMetadatas.find((m) => m.target === entityClass)\n if (!meta) return null\n\n const excludedProps = getExcludedProperties(entityClass)\n const edmTypeOverrides = getEdmTypeOverrides(entityClass)\n const customSetName = getEntitySetName(entityClass)\n\n const properties = this.deriveProperties(meta, excludedProps, edmTypeOverrides)\n const navigationProperties = this.deriveNavigationProperties(meta)\n const keyProperties = meta.primaryColumns.map((c) => c.propertyName)\n const entitySetName = customSetName ?? pluralizeEntityName(meta.name)\n const isReadOnly = meta.tableType === 'view'\n\n return {\n entityTypeName: meta.name,\n entitySetName,\n properties,\n navigationProperties,\n keyProperties,\n isReadOnly,\n }\n }\n\n private deriveProperties(\n meta: EntityMetadata,\n excludedProps: Set<string>,\n edmTypeOverrides: Record<\n string,\n { type: string; precision?: number; scale?: number; maxLength?: number }\n >,\n ): readonly EdmProperty[] {\n return meta.columns\n .filter((col) => !excludedProps.has(col.propertyName))\n .reduce<EdmProperty[]>((acc, col) => {\n const override = edmTypeOverrides[col.propertyName]\n\n if (override) {\n acc.push({\n name: col.propertyName,\n type: override.type as EdmPrimitiveType,\n nullable: col.isNullable,\n ...(override.precision !== undefined ? { precision: override.precision } : {}),\n ...(override.scale !== undefined ? { scale: override.scale } : {}),\n ...(override.maxLength !== undefined ? { maxLength: override.maxLength } : {}),\n })\n return acc\n }\n\n const isConstructor = typeof col.type === 'function'\n const columnType = typeof col.type === 'string' ? col.type : ''\n const designType = isConstructor\n ? (col.type as new (...args: unknown[]) => unknown)\n : undefined\n\n const edmType = mapColumnTypeToEdm(columnType, designType, this.unmappedTypeStrategy)\n if (edmType === undefined) {\n // strategy is 'skip' — omit this property\n return acc\n }\n\n acc.push({\n name: col.propertyName,\n type: edmType,\n nullable: col.isNullable,\n ...(col.precision !== undefined && col.precision !== null\n ? { precision: col.precision }\n : {}),\n ...(col.scale !== undefined && col.scale !== null ? { scale: col.scale } : {}),\n ...(col.length\n ? {\n maxLength: typeof col.length === 'string' ? parseInt(col.length, 10) : col.length,\n }\n : {}),\n })\n return acc\n }, [])\n }\n\n private deriveNavigationProperties(meta: EntityMetadata): readonly EdmNavigationProperty[] {\n return meta.relations.map((rel) => {\n const isCollection = rel.isOneToMany || rel.isManyToMany\n const targetName =\n typeof rel.type === 'string'\n ? rel.type\n : ((rel.type as { name?: string }).name ?? String(rel.type))\n\n const type = isCollection\n ? `Collection(${this.namespace}.${targetName})`\n : `${this.namespace}.${targetName}`\n\n return {\n name: rel.propertyName,\n type,\n nullable: !isCollection,\n isCollection,\n }\n })\n }\n}\n","import { Brackets, type SelectQueryBuilder, type ObjectLiteral, type Repository } from 'typeorm'\nimport type {\n BinaryExprNode,\n FunctionCallNode,\n LambdaExprNode,\n LiteralNode,\n PropertyAccessNode,\n UnaryExprNode,\n FilterNode,\n FilterVisitor,\n EdmEntityType,\n} from '@nestjs-odata/core'\nimport { acceptVisitor, ODataValidationError } from '@nestjs-odata/core'\n\n/** Map of OData comparison operators to SQL operators */\nconst COMPARISON_OPS: Record<string, string> = {\n eq: '=',\n ne: '!=',\n lt: '<',\n le: '<=',\n gt: '>',\n ge: '>=',\n}\n\n/** Map of OData scalar function names to SQL function names */\nconst SCALAR_FUNCTIONS: Record<string, string> = {\n length: 'LENGTH',\n tolower: 'LOWER',\n toupper: 'UPPER',\n trim: 'TRIM',\n}\n\n/** Map of OData arithmetic operators to SQL operators */\nconst ARITH_OPS: Record<string, string> = {\n add: '+',\n sub: '-',\n mul: '*',\n div: '/',\n divby: '/',\n mod: '%',\n}\n\n/** SQLite strftime format strings for date/time functions */\nconst DATE_STRFTIME: Record<string, string> = {\n year: '%Y',\n month: '%m',\n day: '%d',\n hour: '%H',\n minute: '%M',\n second: '%S',\n}\n\n/** ANSI/Postgres EXTRACT field names for date/time functions */\nconst DATE_EXTRACT: Record<string, string> = {\n year: 'YEAR',\n month: 'MONTH',\n day: 'DAY',\n hour: 'HOUR',\n minute: 'MINUTE',\n second: 'SECOND',\n}\n\n/**\n * TypeOrmFilterVisitor — translates an OData filter AST into TypeORM\n * SelectQueryBuilder.andWhere() calls with named parameters.\n *\n * All literal values are bound via named parameters (:p1, :p2, ...) — zero\n * string interpolation in WHERE clauses (T-03-04 mitigation).\n *\n * LIKE special characters (%, _) are escaped before use in contains/startswith/endswith\n * to prevent wildcard injection (T-03-05 mitigation).\n *\n * Per SEC-04: Tracks filter AST nesting depth and throws ODataValidationError when\n * depth exceeds maxFilterDepth. Prevents pathological filter expressions.\n */\nexport class TypeOrmFilterVisitor implements FilterVisitor<void> {\n /** Shared param counter — passed between sibling visitors for unique names */\n paramCount = 0\n /** Current nesting depth — propagated to sub-visitors for or branches */\n currentDepth = 0\n /** Accumulated params from resolveExpression (e.g. arithmetic operands) to merge into andWhere */\n private pendingParams: Record<string, unknown> = {}\n\n constructor(\n private readonly qb: SelectQueryBuilder<ObjectLiteral>,\n private readonly alias: string,\n private readonly entityType: EdmEntityType,\n private readonly maxFilterDepth: number = 10,\n private readonly dialect: 'sqlite' | 'postgres' | 'ansi' = 'ansi',\n private readonly repo?: Repository<ObjectLiteral>,\n ) {}\n\n /** Entry point: dispatch the root FilterNode to the appropriate visit method */\n visit(node: FilterNode): void {\n acceptVisitor(node, this)\n }\n\n visitBinaryExpr(node: BinaryExprNode): void {\n this.currentDepth++\n if (this.currentDepth > this.maxFilterDepth) {\n throw new ODataValidationError(\n `$filter nesting depth ${this.currentDepth} exceeds maximum of ${this.maxFilterDepth}`,\n this.entityType.name,\n '$filter',\n )\n }\n const { operator, left, right } = node\n\n if (operator === 'and') {\n acceptVisitor(left, this)\n acceptVisitor(right, this)\n this.currentDepth--\n return\n }\n\n if (operator === 'or') {\n const depthAtEntry = this.currentDepth\n this.qb.andWhere(\n new Brackets((qb) => {\n const leftVisitor = new TypeOrmFilterVisitor(\n qb as SelectQueryBuilder<ObjectLiteral>,\n this.alias,\n this.entityType,\n this.maxFilterDepth,\n this.dialect,\n this.repo,\n )\n leftVisitor.paramCount = this.paramCount\n leftVisitor.currentDepth = depthAtEntry\n acceptVisitor(left, leftVisitor)\n this.paramCount = leftVisitor.paramCount\n\n const rightVisitor = new TypeOrmFilterVisitor(\n qb as SelectQueryBuilder<ObjectLiteral>,\n this.alias,\n this.entityType,\n this.maxFilterDepth,\n this.dialect,\n this.repo,\n )\n rightVisitor.paramCount = this.paramCount\n rightVisitor.currentDepth = depthAtEntry\n acceptVisitor(right, rightVisitor)\n this.paramCount = rightVisitor.paramCount\n }),\n )\n this.currentDepth--\n return\n }\n\n const sqlOp = COMPARISON_OPS[operator]\n if (sqlOp) {\n this.pendingParams = {}\n const leftExpr = this.resolveExpression(left)\n const paramName = this.nextParam()\n const literalValue = this.extractLiteralValue(right)\n const allParams = { ...this.pendingParams, [paramName]: literalValue }\n this.pendingParams = {}\n this.qb.andWhere(`${leftExpr} ${sqlOp} :${paramName}`, allParams)\n this.currentDepth--\n return\n }\n\n // Unsupported operator — skip (arithmetic ops would need different handling)\n this.currentDepth--\n }\n\n visitUnaryExpr(node: UnaryExprNode): void {\n if (node.operator === 'not') {\n const innerVisitor = new InnerFilterExprBuilder(\n this.alias,\n this.entityType,\n this.paramCount,\n this.dialect,\n )\n const { expr, params, finalCount } = innerVisitor.build(node.operand)\n this.paramCount = finalCount\n this.qb.andWhere(`NOT (${expr})`, params)\n }\n // 'neg' (arithmetic negation) not needed for WHERE clause use cases\n }\n\n visitFunctionCall(node: FunctionCallNode): void {\n const name = node.name.toLowerCase()\n\n // String matching functions\n if (name === 'contains' || name === 'startswith' || name === 'endswith') {\n this.applyLikeFunction(name, node)\n return\n }\n\n // Scalar functions used as expressions (length, tolower, etc.) are resolved\n // via resolveExpression when used as the left side of a comparison.\n // Direct FunctionCall at the top level is not a WHERE condition — skip.\n }\n\n visitLambdaExpr(node: LambdaExprNode): void {\n if (!this.repo) return // no metadata available — skip (test isolation fallback)\n\n const relation = this.repo.metadata.relations.find(\n (r) => r.propertyName.toLowerCase() === node.collection.toLowerCase(),\n )\n if (!relation) return // unknown relation — skip silently\n\n if (relation.relationType === 'many-to-many') {\n throw new ODataValidationError(\n `Lambda expressions on ManyToMany relations are not supported`,\n this.entityType.name,\n '$filter',\n )\n }\n\n if (node.operator === 'all' && !node.predicate) {\n // vacuous truth — add no condition\n return\n }\n\n this.paramCount++ // reserve one count for alias suffix to prevent collision\n const subAlias = `${node.variable ?? 'sub'}_lambda_${this.paramCount}`\n const targetTable = relation.inverseEntityMetadata.tableName\n // For OneToMany: FK lives on related side — use inverseRelation.joinColumns\n const fkCol =\n relation.inverseRelation?.joinColumns[0]?.databaseName ??\n relation.joinColumns[0]?.databaseName ??\n 'id'\n const pkCol = this.repo.metadata.primaryColumns[0]?.databaseName ?? 'id'\n\n if (node.operator === 'any') {\n this.buildAnyClause(node, subAlias, targetTable, fkCol, pkCol)\n } else {\n this.buildAllClause(node, subAlias, targetTable, fkCol, pkCol)\n }\n }\n\n private buildAnyClause(\n node: LambdaExprNode,\n subAlias: string,\n targetTable: string,\n fkCol: string,\n pkCol: string,\n ): void {\n const correlate = `${subAlias}.${fkCol} = ${this.alias}.${pkCol}`\n\n if (!node.predicate) {\n // any() — check collection is non-empty\n this.qb.andWhere(`EXISTS (SELECT 1 FROM \"${targetTable}\" ${subAlias} WHERE ${correlate})`)\n return\n }\n\n const builder = new InnerFilterExprBuilder(\n subAlias,\n this.entityType,\n this.paramCount,\n this.dialect,\n )\n const result = builder.build(node.predicate)\n this.paramCount = result.finalCount\n\n this.qb.andWhere(\n `EXISTS (SELECT 1 FROM \"${targetTable}\" ${subAlias} WHERE ${correlate} AND ${result.expr})`,\n result.params,\n )\n }\n\n private buildAllClause(\n node: LambdaExprNode,\n subAlias: string,\n targetTable: string,\n fkCol: string,\n pkCol: string,\n ): void {\n // node.predicate is guaranteed non-null here (vacuous truth handled above)\n const builder = new InnerFilterExprBuilder(\n subAlias,\n this.entityType,\n this.paramCount,\n this.dialect,\n )\n const result = builder.build(node.predicate!)\n this.paramCount = result.finalCount\n\n const correlate = `${subAlias}.${fkCol} = ${this.alias}.${pkCol}`\n this.qb.andWhere(\n `NOT EXISTS (SELECT 1 FROM \"${targetTable}\" ${subAlias} WHERE ${correlate} AND NOT (${result.expr}))`,\n result.params,\n )\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n visitPropertyAccess(_node: PropertyAccessNode): void {\n // PropertyAccess at the root filter level has no SQL meaning without an operator — skip\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n visitLiteral(_node: LiteralNode): void {\n // Literal at root filter level has no SQL meaning — skip\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private nextParam(): string {\n this.paramCount++\n return `p${this.paramCount}`\n }\n\n /**\n * Resolve a FilterNode to an SQL expression string (left side of comparison).\n * Handles PropertyAccessNode (column ref) and scalar FunctionCallNode (LENGTH, LOWER, etc.).\n */\n private resolveExpression(node: FilterNode): string {\n if (node.kind === 'PropertyAccess') {\n return `${this.alias}.${node.path[0]}`\n }\n if (node.kind === 'BinaryExpr') {\n const sqlArith = ARITH_OPS[node.operator]\n if (sqlArith) {\n const left = this.resolveExpression(node.left)\n const right = this.resolveArithOperand(node.right)\n return `(${left} ${sqlArith} ${right})`\n }\n }\n if (node.kind === 'FunctionCall') {\n const fnName = node.name.toLowerCase()\n const dateResult = this.resolveDateFunction(fnName, node)\n if (dateResult !== null) return dateResult\n const strResult = this.resolveStringFunction(fnName, node)\n if (strResult !== null) return strResult\n const sqlFn = SCALAR_FUNCTIONS[fnName]\n if (sqlFn && node.args.length >= 1) {\n const inner = this.resolveExpression(node.args[0])\n return `${sqlFn}(${inner})`\n }\n }\n if (node.kind === 'Literal') {\n const paramName = this.nextParam()\n this.pendingParams[paramName] = node.value\n return `:${paramName}`\n }\n return ''\n }\n\n /** Resolve date/time function to dialect-correct SQL. Returns null if not a date function. */\n private resolveDateFunction(\n fnName: string,\n node: FilterNode & { kind: 'FunctionCall' },\n ): string | null {\n const strftimeFmt = DATE_STRFTIME[fnName]\n const extractField = DATE_EXTRACT[fnName]\n if (!strftimeFmt || !extractField) return null\n const inner = this.resolveExpression(node.args[0])\n if (this.dialect === 'sqlite') {\n return `CAST(strftime('${strftimeFmt}', ${inner}) AS INTEGER)`\n }\n return `EXTRACT(${extractField} FROM ${inner})`\n }\n\n /** Bind an arithmetic operand: Literal nodes become named params; others recurse. */\n private resolveArithOperand(node: FilterNode): string {\n if (node.kind === 'Literal') {\n const paramName = this.nextParam()\n this.pendingParams[paramName] = node.value\n return `:${paramName}`\n }\n return this.resolveExpression(node)\n }\n\n /** Resolve indexof/substring/concat to parameterized SQL. Returns null if not a string fn. */\n private resolveStringFunction(\n fnName: string,\n node: FilterNode & { kind: 'FunctionCall' },\n ): string | null {\n if (fnName === 'indexof' && node.args.length >= 2) {\n const str = this.resolveExpression(node.args[0])\n const subParam = this.nextParam()\n this.pendingParams[subParam] = (node.args[1] as LiteralNode).value\n const sqlFn = this.dialect === 'sqlite' ? 'INSTR' : 'STRPOS'\n return `${sqlFn}(${str}, :${subParam}) - 1`\n }\n if (fnName === 'substring' && node.args.length >= 2) {\n const str = this.resolveExpression(node.args[0])\n const startParam = this.nextParam()\n this.pendingParams[startParam] = ((node.args[1] as LiteralNode).value as number) + 1\n if (node.args.length >= 3) {\n const lenParam = this.nextParam()\n this.pendingParams[lenParam] = (node.args[2] as LiteralNode).value\n return `SUBSTR(${str}, :${startParam}, :${lenParam})`\n }\n return `SUBSTR(${str}, :${startParam})`\n }\n if (fnName === 'concat' && node.args.length >= 2) {\n const left = this.resolveExpression(node.args[0])\n const right = this.resolveExpression(node.args[1])\n return `${left} || ${right}`\n }\n return null\n }\n\n /** Extract the raw JS value from a LiteralNode */\n private extractLiteralValue(node: FilterNode): string | number | boolean | null {\n if (node.kind === 'Literal') {\n return node.value\n }\n return null\n }\n\n /** Apply a LIKE function (contains, startswith, endswith) with escaped value */\n private applyLikeFunction(name: string, node: FunctionCallNode): void {\n const prop = this.resolveExpression(node.args[0])\n const rawValue = String(this.extractLiteralValue(node.args[1]) ?? '')\n const escaped = this.escapeLike(rawValue)\n\n let pattern: string\n if (name === 'contains') {\n pattern = `%${escaped}%`\n } else if (name === 'startswith') {\n pattern = `${escaped}%`\n } else {\n // endswith\n pattern = `%${escaped}`\n }\n\n const paramName = this.nextParam()\n this.qb.andWhere(`${prop} LIKE :${paramName}`, { [paramName]: pattern })\n }\n\n /**\n * Escape LIKE special characters to prevent wildcard injection (T-03-05).\n * % -> \\%, _ -> \\_\n */\n private escapeLike(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/%/g, '\\\\%').replace(/_/g, '\\\\_')\n }\n}\n\n/**\n * Helper class that builds an SQL expression string from a FilterNode subtree\n * without directly calling qb.andWhere(). Used for NOT(...) wrapping.\n */\nclass InnerFilterExprBuilder {\n private paramCount: number\n private readonly params: Record<string, unknown> = {}\n\n constructor(\n private readonly alias: string,\n private readonly entityType: EdmEntityType,\n startCount: number,\n private readonly dialect: 'sqlite' | 'postgres' | 'ansi' = 'ansi',\n ) {\n this.paramCount = startCount\n }\n\n build(node: FilterNode): { expr: string; params: Record<string, unknown>; finalCount: number } {\n const expr = this.buildExpr(node)\n return { expr, params: this.params, finalCount: this.paramCount }\n }\n\n private buildExpr(node: FilterNode): string {\n if (node.kind === 'BinaryExpr') {\n const sqlArith = ARITH_OPS[node.operator]\n if (sqlArith) {\n const left = this.buildExpr(node.left)\n const right = this.resolveArithOperand(node.right)\n return `(${left} ${sqlArith} ${right})`\n }\n const sqlOp = COMPARISON_OPS[node.operator]\n if (sqlOp) {\n const left = this.buildExpr(node.left)\n const right = this.buildExpr(node.right)\n return `${left} ${sqlOp} ${right}`\n }\n }\n if (node.kind === 'FunctionCall') {\n const name = node.name.toLowerCase()\n if (name === 'contains' || name === 'startswith' || name === 'endswith') {\n const prop = this.buildExpr(node.args[0])\n const rawValue = String(node.args[1]?.kind === 'Literal' ? node.args[1].value : '')\n const escaped = rawValue.replace(/\\\\/g, '\\\\\\\\').replace(/%/g, '\\\\%').replace(/_/g, '\\\\_')\n let pattern: string\n if (name === 'contains') {\n pattern = `%${escaped}%`\n } else if (name === 'startswith') {\n pattern = `${escaped}%`\n } else {\n pattern = `%${escaped}`\n }\n this.paramCount++\n const paramName = `p${this.paramCount}`\n this.params[paramName] = pattern\n return `${prop} LIKE :${paramName}`\n }\n const dateResult = this.resolveDateFunction(name, node)\n if (dateResult !== null) return dateResult\n const strResult = this.resolveStringFunction(name, node)\n if (strResult !== null) return strResult\n const sqlFn = SCALAR_FUNCTIONS[name]\n if (sqlFn && node.args.length >= 1) {\n const inner = this.buildExpr(node.args[0])\n return `${sqlFn}(${inner})`\n }\n }\n if (node.kind === 'PropertyAccess') {\n return `${this.alias}.${node.path[0]}`\n }\n if (node.kind === 'Literal') {\n this.paramCount++\n const paramName = `p${this.paramCount}`\n this.params[paramName] = node.value\n return `:${paramName}`\n }\n return ''\n }\n\n /** Resolve date/time function to dialect-correct SQL. Returns null if not a date function. */\n private resolveDateFunction(\n fnName: string,\n node: FilterNode & { kind: 'FunctionCall' },\n ): string | null {\n const strftimeFmt = DATE_STRFTIME[fnName]\n const extractField = DATE_EXTRACT[fnName]\n if (!strftimeFmt || !extractField) return null\n const inner = this.buildExpr(node.args[0])\n if (this.dialect === 'sqlite') {\n return `CAST(strftime('${strftimeFmt}', ${inner}) AS INTEGER)`\n }\n return `EXTRACT(${extractField} FROM ${inner})`\n }\n\n /** Resolve indexof/substring/concat to parameterized SQL. Returns null if not a string fn. */\n private resolveStringFunction(\n fnName: string,\n node: FilterNode & { kind: 'FunctionCall' },\n ): string | null {\n if (fnName === 'indexof' && node.args.length >= 2) {\n const str = this.buildExpr(node.args[0])\n this.paramCount++\n const subParam = `p${this.paramCount}`\n this.params[subParam] = (node.args[1] as LiteralNode).value\n const sqlFn = this.dialect === 'sqlite' ? 'INSTR' : 'STRPOS'\n return `${sqlFn}(${str}, :${subParam}) - 1`\n }\n if (fnName === 'substring' && node.args.length >= 2) {\n const str = this.buildExpr(node.args[0])\n this.paramCount++\n const startParam = `p${this.paramCount}`\n this.params[startParam] = ((node.args[1] as LiteralNode).value as number) + 1\n if (node.args.length >= 3) {\n this.paramCount++\n const lenParam = `p${this.paramCount}`\n this.params[lenParam] = (node.args[2] as LiteralNode).value\n return `SUBSTR(${str}, :${startParam}, :${lenParam})`\n }\n return `SUBSTR(${str}, :${startParam})`\n }\n if (fnName === 'concat' && node.args.length >= 2) {\n const left = this.buildExpr(node.args[0])\n const right = this.buildExpr(node.args[1])\n return `${left} || ${right}`\n }\n return null\n }\n\n /** Bind an arithmetic operand: Literal nodes become named params; others recurse via buildExpr. */\n private resolveArithOperand(node: FilterNode): string {\n if (node.kind === 'Literal') {\n this.paramCount++\n const paramName = `p${this.paramCount}`\n this.params[paramName] = node.value\n return `:${paramName}`\n }\n return this.buildExpr(node)\n }\n}\n","import type { SelectQueryBuilder, ObjectLiteral } from 'typeorm'\nimport type { EdmEntityType, SelectNode } from '@nestjs-odata/core'\n\n/**\n * TypeOrmSelectVisitor — translates an OData $select node into a TypeORM\n * SelectQueryBuilder.select() call.\n *\n * Key properties (primary keys) are always included in the projection even if\n * not in the $select list (T-03-06 mitigation — avoids identity column gaps).\n */\nexport class TypeOrmSelectVisitor {\n constructor(\n private readonly qb: SelectQueryBuilder<ObjectLiteral>,\n private readonly alias: string,\n private readonly entityType: EdmEntityType,\n ) {}\n\n /**\n * Apply the $select projection to the QueryBuilder.\n *\n * - If select.all is true or select.items is absent/empty, do nothing (return all columns).\n * - Otherwise, build a deduplicated column list and call qb.select().\n */\n apply(select: SelectNode): void {\n if (select.all || !select.items?.length) {\n return\n }\n\n const columns = select.items.map((item) => `${this.alias}.${item.path[0]}`)\n\n // Always include key properties — ensures entity identity is always available\n for (const key of this.entityType.keyProperties) {\n columns.push(`${this.alias}.${key}`)\n }\n\n // Deduplicate while preserving order\n const seen = new Set<string>()\n const deduplicated = columns.filter((col) => {\n if (seen.has(col)) return false\n seen.add(col)\n return true\n })\n\n this.qb.select(deduplicated)\n }\n}\n","import type { SelectQueryBuilder, ObjectLiteral } from 'typeorm'\nimport type { OrderByItem } from '@nestjs-odata/core'\n\n/**\n * TypeOrmOrderByVisitor — translates OData $orderby items into TypeORM\n * QueryBuilder.orderBy() / addOrderBy() calls.\n */\nexport class TypeOrmOrderByVisitor {\n constructor(\n private readonly qb: SelectQueryBuilder<ObjectLiteral>,\n private readonly alias: string,\n ) {}\n\n /**\n * Apply $orderby items to the QueryBuilder.\n * First item uses orderBy(), subsequent items use addOrderBy().\n */\n apply(items: OrderByItem[]): void {\n if (!items.length) return\n\n for (let i = 0; i < items.length; i++) {\n const item = items[i]\n const propertyName = this.resolvePropertyName(item)\n const direction = item.direction.toUpperCase() as 'ASC' | 'DESC'\n const column = `${this.alias}.${propertyName}`\n\n if (i === 0) {\n this.qb.orderBy(column, direction)\n } else {\n this.qb.addOrderBy(column, direction)\n }\n }\n }\n\n private resolvePropertyName(item: OrderByItem): string {\n const expr = item.expression\n if (expr.kind === 'PropertyAccess') {\n return expr.path[0]\n }\n return ''\n }\n}\n","import type { SelectQueryBuilder, ObjectLiteral } from 'typeorm'\n\n/**\n * TypeOrmPaginationVisitor — applies OData $top/$skip pagination to a TypeORM\n * SelectQueryBuilder via take() and skip().\n */\nexport class TypeOrmPaginationVisitor {\n constructor(private readonly qb: SelectQueryBuilder<ObjectLiteral>) {}\n\n /**\n * Apply pagination to the QueryBuilder.\n *\n * - top=0 is valid (returns empty result set) — applies take(0)\n * - undefined values are skipped (no call made)\n */\n paginate(top: number | undefined, skip: number | undefined): void {\n if (top !== undefined) {\n this.qb.take(top)\n }\n if (skip !== undefined) {\n this.qb.skip(skip)\n }\n }\n}\n","import type { SelectQueryBuilder, ObjectLiteral } from 'typeorm'\nimport type { ExpandNode, ExpandItem, EdmEntityType } from '@nestjs-odata/core'\nimport { ODataValidationError } from '@nestjs-odata/core'\nimport type { EdmRegistry } from '@nestjs-odata/core'\nimport { TypeOrmFilterVisitor } from './filter-visitor.js'\nimport { TypeOrmSelectVisitor } from './select-visitor.js'\nimport { TypeOrmOrderByVisitor } from './orderby-visitor.js'\n\n/**\n * TypeOrmExpandVisitor — translates $expand AST nodes into TypeORM\n * leftJoinAndSelect calls. Supports nested expand with depth tracking.\n *\n * Per D-09: Uses JOINs, NOT lazy loading — one SQL query.\n * Per D-07: Enforces maxExpandDepth.\n * Per D-10: Validates navigation property names against EDM.\n * Per D-13: Records $top/$skip per expand item in expandPaginationMap for\n * post-JOIN in-memory slicing by TypeOrmQueryTranslator after getMany().\n *\n * Alias convention: `{parentAlias}_{navigationProperty}` — unique per level\n * to prevent TypeORM alias collision when the same nav prop appears at\n * different tree paths.\n */\nexport class TypeOrmExpandVisitor {\n /**\n * Records per-navigation-property $top/$skip values for post-JOIN slicing.\n * Populated during apply() calls. Consumed by TypeOrmQueryTranslator after\n * getMany() via applyExpandPagination().\n */\n readonly expandPaginationMap = new Map<string, { skip?: number; top?: number }>()\n\n constructor(\n private readonly qb: SelectQueryBuilder<ObjectLiteral>,\n private readonly edmRegistry: EdmRegistry,\n private readonly maxExpandDepth: number,\n ) {}\n\n /**\n * Apply an ExpandNode to the SelectQueryBuilder starting from parentAlias.\n *\n * @param expandNode - The $expand AST node to process\n * @param parentAlias - The QueryBuilder alias for the parent entity (e.g. 'entity')\n * @param entityType - EDM entity type of the parent (for validation)\n * @param currentDepth - Current recursion depth (0 = top-level expand)\n */\n apply(\n expandNode: ExpandNode,\n parentAlias: string,\n entityType: EdmEntityType,\n currentDepth: number,\n ): void {\n if (currentDepth >= this.maxExpandDepth) {\n throw new ODataValidationError(\n `$expand depth limit of ${this.maxExpandDepth} exceeded`,\n entityType.name,\n '$expand',\n )\n }\n\n for (const item of expandNode.items) {\n this.applyExpandItem(item, parentAlias, entityType, currentDepth)\n }\n }\n\n private applyExpandItem(\n item: ExpandItem,\n parentAlias: string,\n entityType: EdmEntityType,\n currentDepth: number,\n ): void {\n // Validate against EDM navigation properties (D-10, T-04-09)\n const navProp = entityType.navigationProperties.find(\n (np) => np.name === item.navigationProperty,\n )\n if (!navProp) {\n throw new ODataValidationError(\n `'${item.navigationProperty}' is not a navigation property of '${entityType.name}'`,\n entityType.name,\n item.navigationProperty,\n )\n }\n\n // Build unique alias: parentAlias_navigationProperty\n const joinAlias = `${parentAlias}_${item.navigationProperty}`\n this.qb.leftJoinAndSelect(`${parentAlias}.${item.navigationProperty}`, joinAlias)\n\n // Resolve the target entity type for nested query options\n const targetEntityType = this.edmRegistry.getEntityType(navProp.type)\n\n // Apply nested query options on the joined relation\n if (item.filter && targetEntityType) {\n new TypeOrmFilterVisitor(this.qb, joinAlias, targetEntityType).visit(item.filter)\n }\n if (item.select && targetEntityType) {\n new TypeOrmSelectVisitor(this.qb, joinAlias, targetEntityType).apply(item.select)\n }\n if (item.orderBy?.length) {\n new TypeOrmOrderByVisitor(this.qb, joinAlias).apply(item.orderBy)\n }\n\n // Per D-13: Record $top/$skip for post-JOIN in-memory slicing after getMany().\n // Only collections can be paginated — single-valued nav props are not arrays.\n if ((item.top !== undefined || item.skip !== undefined) && navProp.isCollection) {\n this.expandPaginationMap.set(item.navigationProperty, {\n skip: item.skip,\n top: item.top,\n })\n }\n\n // Recursive nested expand (D-07)\n if (item.expand && targetEntityType) {\n this.apply(item.expand, joinAlias, targetEntityType, currentDepth + 1)\n }\n }\n}\n","import type { SelectQueryBuilder, ObjectLiteral, Repository } from 'typeorm'\nimport type {\n EdmEntityType,\n ApplyNode,\n ApplyStep,\n AggregateExpression,\n FilterNode,\n} from '@nestjs-odata/core'\nimport { TypeOrmFilterVisitor } from './filter-visitor.js'\n\n/**\n * TypeOrmApplyVisitor — translates an OData $apply pipeline AST into\n * TypeORM SelectQueryBuilder GROUP BY / aggregate SQL.\n *\n * Handles three step types in pipeline order:\n * - ApplyFilter: delegates to TypeOrmFilterVisitor (adds WHERE clause)\n * - ApplyGroupBy: adds GROUP BY columns + aggregate SELECT expressions\n * - ApplyAggregate: adds aggregate SELECT expressions (no GROUP BY)\n *\n * Security: aggregate property names validated against AggregateExpression.method\n * enum (constrained by parser); aliases validated by parser regex before SQL use.\n * No user input is directly interpolated into SQL — property/alias names come\n * from the validated AST (T-11-05 mitigation).\n *\n * Returns projected column names (for @odata.context URL construction).\n */\nexport class TypeOrmApplyVisitor {\n constructor(\n private readonly qb: SelectQueryBuilder<ObjectLiteral>,\n private readonly alias: string,\n private readonly entityType: EdmEntityType,\n private readonly maxFilterDepth: number = 10,\n private readonly dialect: 'sqlite' | 'postgres' | 'ansi' = 'ansi',\n private readonly repo: Repository<ObjectLiteral>,\n ) {}\n\n /**\n * Process all steps in an $apply pipeline in order.\n * Returns all projected column names for @odata.context construction.\n */\n apply(applyNode: ApplyNode): string[] {\n const projectedColumns: string[] = []\n\n for (const step of applyNode.steps) {\n const stepColumns = this.applyStep(step)\n projectedColumns.push(...stepColumns)\n }\n\n return projectedColumns\n }\n\n // ── Private step handlers ───────────────────────────────────────────────────\n\n private applyStep(step: ApplyStep): string[] {\n switch (step.kind) {\n case 'ApplyFilter':\n return this.applyFilterStep(step.filter)\n case 'ApplyGroupBy':\n return this.applyGroupByStep(step.properties, step.aggregate)\n case 'ApplyAggregate':\n return this.applyAggregateStep(step.expressions)\n }\n }\n\n /**\n * ApplyFilter step — delegates to TypeOrmFilterVisitor.\n * Adds WHERE clause to the query. Returns no projected columns.\n */\n private applyFilterStep(filter: FilterNode): string[] {\n const visitor = new TypeOrmFilterVisitor(\n this.qb,\n this.alias,\n this.entityType,\n this.maxFilterDepth,\n this.dialect,\n this.repo,\n )\n visitor.visit(filter)\n return []\n }\n\n /**\n * ApplyGroupBy step — adds GROUP BY columns and optional aggregate expressions.\n * Clears existing SELECT first (replaces entity.* with explicit projections).\n */\n private applyGroupByStep(\n properties: string[],\n aggregate: readonly AggregateExpression[] | undefined,\n ): string[] {\n const projectedColumns: string[] = []\n\n // Clear entity.* default select\n this.qb.select([])\n\n // Add GROUP BY columns\n for (const prop of properties) {\n this.qb.addSelect(`${this.alias}.${prop}`, prop)\n this.qb.addGroupBy(`${this.alias}.${prop}`)\n projectedColumns.push(prop)\n }\n\n // Add aggregate expressions\n if (aggregate) {\n for (const expr of aggregate) {\n const sqlExpr = this.buildAggregateSql(expr)\n this.qb.addSelect(sqlExpr, expr.alias)\n projectedColumns.push(expr.alias)\n }\n }\n\n return projectedColumns\n }\n\n /**\n * ApplyAggregate step (no GROUP BY) — adds aggregate SELECT expressions.\n * Clears existing SELECT first (replaces entity.* with explicit projections).\n */\n private applyAggregateStep(expressions: readonly AggregateExpression[]): string[] {\n const projectedColumns: string[] = []\n\n // Clear entity.* default select\n this.qb.select([])\n\n for (const expr of expressions) {\n const sqlExpr = this.buildAggregateSql(expr)\n this.qb.addSelect(sqlExpr, expr.alias)\n projectedColumns.push(expr.alias)\n }\n\n return projectedColumns\n }\n\n /**\n * Build the SQL aggregate function expression for a single AggregateExpression.\n *\n * Method mapping:\n * - count with $count property => COUNT(*)\n * - count with field => COUNT(alias.field)\n * - sum => SUM(alias.field)\n * - avg => AVG(alias.field)\n * - min => MIN(alias.field)\n * - max => MAX(alias.field)\n * - countdistinct => COUNT(DISTINCT alias.field)\n */\n private buildAggregateSql(expr: AggregateExpression): string {\n const { property, method } = expr\n\n switch (method) {\n case 'count':\n return property === '$count' ? 'COUNT(*)' : `COUNT(${this.alias}.${property})`\n case 'sum':\n return `SUM(${this.alias}.${property})`\n case 'avg':\n return `AVG(${this.alias}.${property})`\n case 'min':\n return `MIN(${this.alias}.${property})`\n case 'max':\n return `MAX(${this.alias}.${property})`\n case 'countdistinct':\n return `COUNT(DISTINCT ${this.alias}.${property})`\n }\n }\n}\n","import type { ObjectLiteral } from 'typeorm'\n\n/**\n * Apply post-JOIN pagination to expanded collections.\n *\n * TypeORM hydrates relations as JavaScript arrays. After getMany(),\n * this function slices each expanded collection by the per-expand-item\n * $top/$skip values recorded during ExpandVisitor traversal.\n *\n * Per D-13: In-memory slicing is the v1 approach. Acceptable for\n * single-level expand pagination. Total result set is already bounded\n * by maxTop and maxExpandDepth before slicing occurs (T-05-10 accept).\n *\n * Mutates items in-place — the hydrated array is replaced with the sliced\n * version. This avoids allocating a new top-level array.\n */\nexport function applyExpandPagination(\n items: ObjectLiteral[],\n paginationMap: ReadonlyMap<string, { readonly skip?: number; readonly top?: number }>,\n): void {\n for (const [navProp, { skip = 0, top }] of paginationMap.entries()) {\n for (const item of items) {\n const related = item[navProp]\n if (Array.isArray(related)) {\n item[navProp] = related.slice(skip, top !== undefined ? skip + top : undefined)\n }\n }\n }\n}\n","import { Inject, Injectable, Optional } from '@nestjs/common'\nimport type { Repository, SelectQueryBuilder, ObjectLiteral } from 'typeorm'\nimport type {\n IQueryTranslator,\n ODataQuery,\n ODataQueryResult,\n EdmEntityType,\n ODataModuleResolvedOptions,\n ISearchProvider,\n} from '@nestjs-odata/core'\nimport { EdmRegistry, ODATA_MODULE_OPTIONS, SEARCH_PROVIDER } from '@nestjs-odata/core'\nimport { TypeOrmFilterVisitor } from './filter-visitor.js'\nimport { TypeOrmSelectVisitor } from './select-visitor.js'\nimport { TypeOrmOrderByVisitor } from './orderby-visitor.js'\nimport { TypeOrmPaginationVisitor } from './pagination-visitor.js'\nimport { TypeOrmExpandVisitor } from './expand-visitor.js'\nimport { TypeOrmApplyVisitor } from './apply-visitor.js'\nimport { applyExpandPagination } from './expand-pagination.js'\n\n/**\n * Translation result from translate() — includes the QueryBuilder and the\n * expandPaginationMap needed for post-JOIN in-memory slicing (D-13).\n * When $apply is present, applyProperties contains the projected column names.\n */\nexport interface TranslateResult {\n readonly qb: SelectQueryBuilder<ObjectLiteral>\n readonly expandPaginationMap: ReadonlyMap<string, { skip?: number; top?: number }>\n readonly applyProperties?: string[]\n}\n\n/**\n * TypeOrmQueryTranslator — orchestrates all visitor classes to translate\n * an ODataQuery into a TypeORM SelectQueryBuilder, then executes it.\n *\n * Implements IQueryTranslator<SelectQueryBuilder<ObjectLiteral>> — the adapter seam\n * between @nestjs-odata/core query types and TypeORM query building.\n *\n * Visitor application order (deterministic):\n * 1. filter — WHERE clause (must be first, affects result set)\n * 1.5 search — $search LIKE conditions (additional WHERE)\n * 2. apply — $apply aggregation pipeline (skips select/orderby/expand)\n * 3. select — column projection (skipped when $apply present)\n * 4. orderby — sorting (skipped when $apply present)\n * 5. pagination — take/skip\n * 6. expand — JOINs for navigation properties (skipped when $apply present)\n */\n@Injectable()\nexport class TypeOrmQueryTranslator implements IQueryTranslator<TranslateResult> {\n constructor(\n private readonly repo: Repository<ObjectLiteral>,\n private readonly edmRegistry: EdmRegistry,\n @Inject(ODATA_MODULE_OPTIONS) private readonly options: ODataModuleResolvedOptions,\n @Optional() @Inject(SEARCH_PROVIDER) private readonly searchProvider?: ISearchProvider,\n ) {}\n\n /**\n * Translate an ODataQuery AST into a TypeORM SelectQueryBuilder.\n * Also returns the expandPaginationMap for post-JOIN slicing (D-13).\n * Does not execute the query — call execute() for DB access.\n */\n translate(query: ODataQuery, entityType: EdmEntityType): TranslateResult {\n const alias = 'entity'\n const qb = this.repo.createQueryBuilder(alias)\n\n const dbType = this.repo.manager.connection.options.type as 'sqlite' | 'postgres' | 'ansi'\n const dialect: 'sqlite' | 'postgres' | 'ansi' =\n dbType === 'sqlite' || dbType === 'postgres' ? dbType : 'ansi'\n\n // 1. Filter\n if (query.filter) {\n new TypeOrmFilterVisitor(\n qb,\n alias,\n entityType,\n this.options.maxFilterDepth,\n dialect,\n this.repo,\n ).visit(query.filter)\n }\n\n // 1.5 Search ($search free-text) — applies as additional WHERE clause\n if (query.search && this.searchProvider) {\n const searchResult = this.searchProvider.buildSearchCondition(\n query.search,\n query.entitySetName,\n alias,\n )\n if (searchResult) {\n qb.andWhere(searchResult.condition, searchResult.params)\n }\n }\n\n // 2. Apply ($apply aggregation pipeline)\n // When $apply is present, skip $select, $orderby, $expand per OData spec\n let applyProperties: string[] | undefined\n if (query.apply) {\n const applyVisitor = new TypeOrmApplyVisitor(\n qb,\n alias,\n entityType,\n this.options.maxFilterDepth,\n dialect,\n this.repo,\n )\n applyProperties = applyVisitor.apply(query.apply)\n }\n\n if (!query.apply) {\n // 3. Select (skip when $apply present)\n if (query.select) {\n new TypeOrmSelectVisitor(qb, alias, entityType).apply(query.select)\n }\n\n // 4. OrderBy (skip when $apply present)\n if (query.orderBy?.length) {\n new TypeOrmOrderByVisitor(qb, alias).apply(query.orderBy)\n }\n }\n\n // 5. Pagination (always applies — works on aggregated results too)\n new TypeOrmPaginationVisitor(qb).paginate(query.top, query.skip)\n\n // 6. Expand (JOINs — skip when $apply present)\n let expandPaginationMap: ReadonlyMap<string, { skip?: number; top?: number }> = new Map()\n if (!query.apply && query.expand) {\n const expandVisitor = new TypeOrmExpandVisitor(\n qb,\n this.edmRegistry,\n this.options.maxExpandDepth,\n )\n expandVisitor.apply(query.expand, alias, entityType, 0)\n expandPaginationMap = expandVisitor.expandPaginationMap\n }\n\n return { qb, expandPaginationMap, applyProperties }\n }\n\n /**\n * Execute the translated SelectQueryBuilder and return structured results.\n * Applies post-JOIN in-memory expand pagination after getMany() (D-13).\n * When $apply is present, uses getRawMany() to preserve aggregate aliases.\n *\n * @param translateResult - The result from translate() (qb + expandPaginationMap)\n * @param includeCount - If true, uses getManyAndCount() to include total count\n */\n async execute(\n translateResult: TranslateResult | SelectQueryBuilder<ObjectLiteral>,\n includeCount: boolean,\n ): Promise<ODataQueryResult> {\n // Support legacy callers that pass a raw SelectQueryBuilder (backwards compat)\n let qb: SelectQueryBuilder<ObjectLiteral>\n let expandPaginationMap: ReadonlyMap<string, { skip?: number; top?: number }>\n let applyProperties: string[] | undefined\n\n if ('qb' in translateResult && 'expandPaginationMap' in translateResult) {\n qb = translateResult.qb\n expandPaginationMap = translateResult.expandPaginationMap\n applyProperties = translateResult.applyProperties\n } else {\n qb = translateResult\n expandPaginationMap = new Map()\n }\n\n // When $apply is present, use getRawMany() — getMany() drops aggregate aliases\n // (Pitfall 1 from RESEARCH: TypeORM entity hydration strips unknown columns)\n const isAggregated = applyProperties !== undefined\n\n if (isAggregated) {\n if (includeCount) {\n const items = await qb.getRawMany()\n return { items, count: items.length, isAggregated: true, applyProperties }\n }\n const items = await qb.getRawMany()\n return { items, isAggregated: true, applyProperties }\n }\n\n if (includeCount) {\n const [items, count] = await qb.getManyAndCount()\n applyExpandPagination(items, expandPaginationMap)\n return { items, count }\n }\n const items = await qb.getMany()\n applyExpandPagination(items, expandPaginationMap)\n return { items }\n }\n}\n","import { HttpException, Inject, Injectable, NotFoundException, Optional } from '@nestjs/common'\nimport type { ODataQuery, ODataQueryResult, EdmEntityType, IETagProvider } from '@nestjs-odata/core'\nimport {\n EdmRegistry,\n ETAG_PROVIDER,\n ODATA_MODULE_OPTIONS,\n parseODataKey,\n type ODataModuleResolvedOptions,\n} from '@nestjs-odata/core'\nimport type { Repository, ObjectLiteral, EntityManager, DataSource } from 'typeorm'\nimport { TypeOrmQueryTranslator } from './typeorm-query-translator.js'\n\n/**\n * TypeOrmAutoHandler — handles OData GET and $count requests automatically\n * using the TypeORM repository and the query translator.\n *\n * Provides zero-boilerplate OData endpoint handling:\n * - handleGet(): translate + execute OData query, detect next page via top+1 trick\n * - handleCount(): strip pagination/select/orderby, return total count for filter\n *\n * Per D-13: effectiveTop is clamped by maxTop (T-03-11).\n * Per D-15: handleCount() strips $top/$skip to avoid Pitfall 3.\n * Per Pitfall 2: fetches top+1 items to detect next page without a separate count query.\n *\n * The EdmEntityType is resolved at request time via EdmRegistry so the provider\n * can be registered during module compile time before EDM derivation runs at onModuleInit.\n */\n@Injectable()\nexport class TypeOrmAutoHandler {\n constructor(\n private readonly translator: TypeOrmQueryTranslator,\n private readonly edmRegistry: EdmRegistry,\n @Inject(ODATA_MODULE_OPTIONS) private readonly options: ODataModuleResolvedOptions,\n private readonly repo: Repository<ObjectLiteral>,\n @Optional() @Inject(ETAG_PROVIDER) private readonly etagProvider?: IETagProvider,\n private readonly dataSource?: DataSource,\n ) {}\n\n /**\n * Resolve EdmEntityType from the registry at request time.\n * Throws if the entity set is not registered.\n */\n private resolveEntityType(entitySetName: string): EdmEntityType {\n const entitySet = this.edmRegistry.getEntitySet(entitySetName)\n if (!entitySet) {\n throw new Error(`ODataAutoHandler: entity set '${entitySetName}' not found in EdmRegistry`)\n }\n const entityType = this.edmRegistry.getEntityType(entitySet.entityTypeName)\n if (!entityType) {\n throw new Error(\n `ODataAutoHandler: entity type '${entitySet.entityTypeName}' not found in EdmRegistry`,\n )\n }\n return entityType\n }\n\n /**\n * Handle an OData GET collection request.\n *\n * Strategy:\n * 1. Resolve EdmEntityType via EdmRegistry\n * 2. Determine effectiveTop (query.top ?? maxTop), clamped to maxTop\n * 3. Build a modified query with top=effectiveTop+1 to detect next page\n * 4. Translate to SelectQueryBuilder\n * 5. Execute (with or without count per query.count)\n * 6. Check if items.length > effectiveTop (has more pages)\n * 7. If yes: slice to effectiveTop, build nextLink; otherwise: no nextLink\n *\n * @param query - Validated ODataQuery from ODataQueryPipe\n * @param requestUrl - Base URL of the request for nextLink construction\n */\n async handleGet(query: ODataQuery, requestUrl: string): Promise<ODataQueryResult> {\n const entityType = this.resolveEntityType(query.entitySetName)\n const maxTop = this.options.maxTop ?? 1000\n const effectiveTop = Math.min(query.top ?? maxTop, maxTop)\n\n // Build the query with top+1 for next page detection (Pitfall 2)\n const fetchQuery: ODataQuery = {\n ...query,\n top: effectiveTop + 1,\n }\n\n const translateResult = this.translator.translate(fetchQuery, entityType)\n\n const includeCount = query.count === true\n const rawResult = await this.translator.execute(translateResult, includeCount)\n\n const items = rawResult.items\n const hasMore = items.length > effectiveTop\n\n const slicedItems = hasMore ? items.slice(0, effectiveTop) : items\n\n let nextLink: string | undefined\n if (hasMore) {\n const currentSkip = query.skip ?? 0\n nextLink = this.buildNextLink(requestUrl, currentSkip + effectiveTop, effectiveTop)\n }\n\n return {\n items: slicedItems,\n count: rawResult.count,\n nextLink,\n select: query.select,\n isAggregated: rawResult.isAggregated,\n applyProperties: rawResult.applyProperties,\n }\n }\n\n /**\n * Handle an OData $count route.\n *\n * Per Pitfall 3: strips $top/$skip/$orderby/$select from the query so count\n * returns total matching rows for the filter, not just the current page count.\n *\n * @param query - Validated ODataQuery from ODataQueryPipe\n */\n async handleCount(query: ODataQuery): Promise<number> {\n const entityType = this.resolveEntityType(query.entitySetName)\n\n // Strip everything except filter and entitySetName (Pitfall 3 — T-03-12)\n const countQuery: ODataQuery = {\n entitySetName: query.entitySetName,\n filter: query.filter,\n }\n\n const { qb } = this.translator.translate(countQuery, entityType)\n return qb.getCount()\n }\n\n /**\n * Build an OData nextLink URL with updated $skip and $top parameters.\n *\n * @param requestUrl - The current request URL (may contain existing query params)\n * @param newSkip - The new $skip value for the next page\n * @param top - The page size ($top value)\n */\n buildNextLink(requestUrl: string, newSkip: number, top: number): string {\n // Parse the URL to extract existing query params\n let baseUrl: string\n let searchStr: string\n\n const qIndex = requestUrl.indexOf('?')\n if (qIndex !== -1) {\n baseUrl = requestUrl.slice(0, qIndex)\n searchStr = requestUrl.slice(qIndex + 1)\n } else {\n baseUrl = requestUrl\n searchStr = ''\n }\n\n // Parse existing params and update $skip/$top\n const params = new Map<string, string>()\n if (searchStr) {\n for (const part of searchStr.split('&')) {\n const eqIdx = part.indexOf('=')\n if (eqIdx !== -1) {\n params.set(\n decodeURIComponent(part.slice(0, eqIdx)),\n decodeURIComponent(part.slice(eqIdx + 1)),\n )\n }\n }\n }\n\n params.set('$skip', String(newSkip))\n params.set('$top', String(top))\n\n const queryParts: string[] = []\n for (const [key, val] of params.entries()) {\n // Do not encode OData system query option prefix ($) — OData URLs use $ unencoded\n queryParts.push(`${key}=${encodeURIComponent(val)}`)\n }\n\n return `${baseUrl}?${queryParts.join('&')}`\n }\n\n /**\n * Handle OData GET by key — single entity retrieval.\n *\n * Per D-05: returns single entity, throws NotFoundException if not found.\n * Per D-02: key is parsed from parenthetical format.\n * Per T-04-08: parseODataKey returns typed values used in parameterized where clause.\n * Per T-09-05: If-None-Match header supported — returns { __notModified, etag } signal when matched.\n */\n async handleGetByKey(\n keyStr: string,\n entitySetName: string,\n ifNoneMatchHeader?: string,\n ): Promise<unknown> {\n const entityType = this.resolveEntityType(entitySetName)\n const where = parseODataKey(keyStr, entityType.keyProperties)\n const entity = await this.repo.findOne({ where })\n if (!entity) {\n throw new NotFoundException(`Entity '${entitySetName}' with key '${keyStr}' not found`)\n }\n\n // ETag support: check If-None-Match for cache validation\n if (this.etagProvider) {\n const etagColumn = this.etagProvider.getETagColumn(entitySetName)\n if (etagColumn) {\n const currentEtag = this.etagProvider.computeETag(\n entity as Record<string, unknown>,\n etagColumn,\n )\n // Attach ETag to entity for interceptor to pick up\n const entityWithEtag = { ...(entity as Record<string, unknown>), __etag: currentEtag }\n\n // If-None-Match: return 304 signal when ETag matches (resource not modified)\n if (\n ifNoneMatchHeader &&\n this.etagProvider.validateIfMatch(\n ifNoneMatchHeader,\n entity as Record<string, unknown>,\n etagColumn,\n )\n ) {\n return { __notModified: true, etag: currentEtag }\n }\n\n return entityWithEtag\n }\n }\n\n return entity\n }\n\n /**\n * Handle OData POST — create entity.\n *\n * Per D-03: returns 201 + Location header + created entity.\n * Returns { entity, locationUrl } for the interceptor to set the Location header.\n * Per T-04-07: TypeORM repo.create() only maps declared columns — unknown fields ignored.\n * Per T-04-11: entity class acts as whitelist, preventing mass assignment.\n */\n async handleCreate(\n body: Record<string, unknown>,\n entitySetName: string,\n ): Promise<{ entity: unknown; locationUrl: string }> {\n const entityType = this.resolveEntityType(entitySetName)\n const created = this.repo.create(body as ObjectLiteral)\n const saved = await this.repo.save(created)\n\n // Build key string for Location URL\n const keyParts = entityType.keyProperties.map((kp) => {\n const val = (saved as Record<string, unknown>)[kp]\n return entityType.keyProperties.length === 1 ? String(val) : `${kp}=${String(val)}`\n })\n const keyStr = keyParts.join(',')\n const locationUrl = `${this.options.serviceRoot}/${entitySetName}(${keyStr})`\n\n return { entity: saved, locationUrl }\n }\n\n /**\n * Handle OData POST with deep insert — create parent and nested children atomically.\n *\n * Per D-02 (deep insert): navigation property keys in the body identify child collections.\n * All saves occur within the provided EntityManager (transaction scope).\n *\n * Per T-10-05: depth is checked before recursion — throws 400 if depth >= maxDeepInsertDepth.\n * Per T-10-06: TypeORM repo.create() only maps declared columns — mass assignment safe.\n *\n * @param body - Request body (scalar fields + optional navigation property arrays)\n * @param entitySetName - OData entity set name (e.g. 'Orders')\n * @param manager - Transaction-scoped EntityManager\n * @param depth - Current recursion depth (default 0)\n */\n async handleDeepCreate(\n body: Record<string, unknown>,\n entitySetName: string,\n manager: EntityManager,\n depth = 0,\n ): Promise<{ entity: unknown; locationUrl: string }> {\n const maxDepth = this.options.maxDeepInsertDepth ?? 5\n if (depth >= maxDepth) {\n throw new HttpException(\n {\n error: {\n code: '400',\n message: `Deep insert exceeds maxDeepInsertDepth (${maxDepth})`,\n },\n },\n 400,\n )\n }\n\n const entityType = this.resolveEntityType(entitySetName)\n\n // Separate scalar props from navigation props\n const navPropNames = new Set(entityType.navigationProperties.map((p) => p.name))\n const scalarBody: Record<string, unknown> = {}\n const navData: Record<string, unknown[]> = {}\n\n for (const [key, value] of Object.entries(body)) {\n if (navPropNames.has(key) && Array.isArray(value)) {\n navData[key] = value as unknown[]\n } else if (!navPropNames.has(key)) {\n scalarBody[key] = value\n }\n }\n\n // Save parent entity using this handler's repo (entity class for this entity set)\n const parentRepo = manager.getRepository(this.repo.target as new () => ObjectLiteral)\n const parentCreated = parentRepo.create(scalarBody as ObjectLiteral)\n const savedParent = (await parentRepo.save(parentCreated)) as Record<string, unknown>\n\n // Get parent PK value\n const parentKeyProp = entityType.keyProperties[0] ?? 'id'\n const parentKeyValue = savedParent[parentKeyProp]\n\n // Process navigation properties (child collections)\n for (const [navPropName, childItems] of Object.entries(navData)) {\n const navProp = entityType.navigationProperties.find((p) => p.name === navPropName)\n if (!navProp) continue\n\n // Resolve child entity type name.\n // Collection nav props use format: \"Collection(Default.OrderItem)\" -> \"OrderItem\"\n // Singular nav props use format: \"Default.OrderItem\" -> \"OrderItem\"\n let rawType = navProp.type\n if (rawType.startsWith('Collection(') && rawType.endsWith(')')) {\n rawType = rawType.slice('Collection('.length, -1)\n }\n const childTypeName = rawType.split('.').pop() ?? rawType\n\n // Find child entity set by entity type name\n const childEntitySet = [...this.edmRegistry.getEntitySets().values()].find(\n (es) => es.entityTypeName === childTypeName,\n )\n if (!childEntitySet) {\n throw new HttpException(\n {\n error: {\n code: '400',\n message: `Deep insert: cannot resolve entity set for navigation property '${navPropName}' (type '${childTypeName}')`,\n },\n },\n 400,\n )\n }\n\n // Resolve FK column name from TypeORM relation metadata\n // Look at the child entity class's ManyToOne relation to find the join column\n let fkColumnName: string | undefined\n if (this.dataSource) {\n try {\n const parentMeta = this.dataSource.getMetadata(this.repo.target)\n const relation = parentMeta.relations.find((r) => r.propertyName === navPropName)\n if (relation?.inverseRelation?.joinColumns?.[0]?.propertyName) {\n fkColumnName = relation.inverseRelation.joinColumns[0].propertyName\n }\n } catch {\n // fallback below\n }\n }\n\n // Fallback: derive FK column name from parent entity set name (e.g. 'Orders' -> 'orderId')\n if (!fkColumnName) {\n const singularParent = entitySetName.endsWith('s')\n ? entitySetName.slice(0, -1)\n : entitySetName\n fkColumnName = `${singularParent.charAt(0).toLowerCase()}${singularParent.slice(1)}Id`\n }\n\n // Resolve the child entity class via DataSource metadata\n const childEntityClass = this.dataSource\n ? this.resolveEntityClassFromDataSource(childTypeName)\n : undefined\n\n for (let index = 0; index < childItems.length; index++) {\n const childItem = childItems[index] as Record<string, unknown>\n // Inject FK from parent's PK — overrides any user-supplied value (T-10-09)\n const childBody = { ...childItem, [fkColumnName]: parentKeyValue }\n\n try {\n if (childEntityClass) {\n // Use a fresh repo for the child entity class\n const childRepo = manager.getRepository(childEntityClass as new () => ObjectLiteral)\n const childCreated = childRepo.create(childBody as ObjectLiteral)\n await childRepo.save(childCreated)\n } else {\n // Fallback: use the child entity set name to resolve via handleDeepCreate recursion\n // This won't work well without a child repo, but we handle the error gracefully\n throw new Error(\n `Cannot resolve entity class for '${childTypeName}' — DataSource not available`,\n )\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n throw new HttpException(\n {\n error: {\n code: '400',\n message: `Deep insert failed at '${navPropName}[${index}]': ${message}`,\n },\n },\n 400,\n )\n }\n }\n }\n\n // Build location URL from saved parent's key\n const keyParts = entityType.keyProperties.map((kp) => {\n const val = savedParent[kp]\n return entityType.keyProperties.length === 1 ? String(val) : `${kp}=${String(val)}`\n })\n const keyStr = keyParts.join(',')\n const locationUrl = `${this.options.serviceRoot}/${entitySetName}(${keyStr})`\n\n return { entity: savedParent, locationUrl }\n }\n\n /**\n * Resolve a TypeORM entity class from an EDM entity type name.\n * Requires DataSource to be provided.\n */\n private resolveEntityClassFromDataSource(entityTypeName: string): (new () => object) | undefined {\n if (!this.dataSource) return undefined\n for (const meta of this.dataSource.entityMetadatas) {\n if (meta.name === entityTypeName) {\n return meta.target as new () => object\n }\n }\n return undefined\n }\n\n /**\n * Handle OData PATCH — partial update (merge-patch semantics).\n *\n * Per D-01: send only changed fields, server merges with existing entity.\n * Per Pitfall 4: checks preload result for undefined (entity not found case).\n * Per T-04-08: parseODataKey returns typed values for safe parameterized queries.\n * Per T-09-04: If-Match header enforces optimistic concurrency — 412 on stale ETag.\n */\n async handleUpdate(\n keyStr: string,\n body: Record<string, unknown>,\n entitySetName: string,\n ifMatchHeader?: string,\n ): Promise<unknown> {\n const entityType = this.resolveEntityType(entitySetName)\n const where = parseODataKey(keyStr, entityType.keyProperties)\n\n // ETag If-Match validation before update\n if (this.etagProvider && ifMatchHeader) {\n const etagColumn = this.etagProvider.getETagColumn(entitySetName)\n if (etagColumn) {\n const current = await this.repo.findOne({ where })\n if (!current) {\n throw new NotFoundException(`Entity '${entitySetName}' with key '${keyStr}' not found`)\n }\n if (\n !this.etagProvider.validateIfMatch(\n ifMatchHeader,\n current as Record<string, unknown>,\n etagColumn,\n )\n ) {\n throw new HttpException(\n {\n error: {\n code: '412',\n message: 'The ETag value does not match the current version of the entity',\n },\n },\n 412,\n )\n }\n }\n }\n\n const merged = { ...where, ...body }\n const preloaded = await this.repo.preload(merged as ObjectLiteral)\n if (!preloaded) {\n throw new NotFoundException(`Entity '${entitySetName}' with key '${keyStr}' not found`)\n }\n return this.repo.save(preloaded)\n }\n\n /**\n * Handle OData PUT — full entity replacement.\n *\n * Per D-01 (OData v4 spec): PUT replaces ALL entity properties.\n * Unspecified fields reset to column defaults (null for nullable, default value otherwise).\n * This is distinct from PATCH merge semantics.\n *\n * Implementation:\n * 1. Parse key from URL\n * 2. Validate body key matches URL key (reject 400 on mismatch)\n * 3. Find existing entity — throw 404 if not found\n * 4. Validate ETag If-Match (412 on mismatch)\n * 5. Build full replacement using repo.metadata.columns:\n * - Skip isPrimary, isCreateDate, isUpdateDate, isVersion columns\n * - body value > column default > null (if nullable) > absent\n * 6. Save and return\n *\n * Per T-10-01: body key mismatch rejected before DB write.\n * Per T-10-02: repo.create() only maps declared entity columns — mass assignment safe.\n * Per T-10-03: managed columns (PK, timestamps, version) skipped.\n * Per T-10-04: ETag If-Match enforcement identical to handleUpdate().\n */\n async handleReplace(\n keyStr: string,\n body: Record<string, unknown>,\n entitySetName: string,\n ifMatchHeader?: string,\n ): Promise<unknown> {\n const entityType = this.resolveEntityType(entitySetName)\n const where = parseODataKey(keyStr, entityType.keyProperties)\n\n // Validate key in body matches URL key (D-01 spec compliance — T-10-01)\n for (const kp of entityType.keyProperties) {\n const bodyKeyValue = body[kp]\n const urlKeyValue = where[kp]\n if (bodyKeyValue !== undefined && bodyKeyValue !== urlKeyValue) {\n throw new HttpException(\n { error: { code: '400', message: 'Key in body does not match URL key' } },\n 400,\n )\n }\n }\n\n // Find existing entity to confirm it exists\n const existing = await this.repo.findOne({ where })\n if (!existing) {\n throw new NotFoundException(`Entity '${entitySetName}' with key '${keyStr}' not found`)\n }\n\n // ETag If-Match validation (identical block to handleUpdate — T-10-04)\n if (this.etagProvider && ifMatchHeader) {\n const etagColumn = this.etagProvider.getETagColumn(entitySetName)\n if (etagColumn) {\n if (\n !this.etagProvider.validateIfMatch(\n ifMatchHeader,\n existing as Record<string, unknown>,\n etagColumn,\n )\n ) {\n throw new HttpException(\n {\n error: {\n code: '412',\n message: 'The ETag value does not match the current version of the entity',\n },\n },\n 412,\n )\n }\n }\n }\n\n // Build full replacement entity using column metadata (Pattern 1 from RESEARCH)\n // repo.metadata.columns gives us all column metadata without needing DataSource injection\n const replacement: Record<string, unknown> = {}\n\n // Set key values from URL\n for (const kp of entityType.keyProperties) {\n replacement[kp] = where[kp]\n }\n\n for (const col of this.repo.metadata.columns) {\n if (col.isPrimary) continue // key already set from URL\n if (col.isCreateDate || col.isUpdateDate || col.isVersion) continue // TypeORM-managed\n\n const propName = col.propertyName\n if (Object.prototype.hasOwnProperty.call(body, propName)) {\n replacement[propName] = body[propName] // use body value\n } else if (col.default !== undefined) {\n replacement[propName] = col.default // reset to column default\n } else if (col.isNullable) {\n replacement[propName] = null // reset nullable to null\n }\n // else: omit — DB-managed default (e.g., CURRENT_TIMESTAMP)\n }\n\n const entity = this.repo.create(replacement as ObjectLiteral)\n return this.repo.save(entity)\n }\n\n /**\n * Handle OData DELETE — remove entity.\n *\n * Per D-04: returns 204 No Content (void return value).\n * Per T-04-08: parseODataKey returns typed values for safe parameterized queries.\n * Per T-09-04: If-Match header enforces optimistic concurrency — 412 on stale ETag.\n */\n async handleDelete(keyStr: string, entitySetName: string, ifMatchHeader?: string): Promise<void> {\n const entityType = this.resolveEntityType(entitySetName)\n const where = parseODataKey(keyStr, entityType.keyProperties)\n\n // ETag If-Match validation before delete\n if (this.etagProvider && ifMatchHeader) {\n const etagColumn = this.etagProvider.getETagColumn(entitySetName)\n if (etagColumn) {\n const current = await this.repo.findOne({ where })\n if (!current) {\n throw new NotFoundException(`Entity '${entitySetName}' with key '${keyStr}' not found`)\n }\n if (\n !this.etagProvider.validateIfMatch(\n ifMatchHeader,\n current as Record<string, unknown>,\n etagColumn,\n )\n ) {\n throw new HttpException(\n {\n error: {\n code: '412',\n message: 'The ETag value does not match the current version of the entity',\n },\n },\n 412,\n )\n }\n }\n }\n\n const result = await this.repo.delete(where)\n if (result.affected === 0) {\n throw new NotFoundException(`Entity '${entitySetName}' with key '${keyStr}' not found`)\n }\n }\n}\n","import { Injectable } from '@nestjs/common'\nimport type { DataSource } from 'typeorm'\nimport { EdmRegistry } from '@nestjs-odata/core'\nimport { getETagProperty } from '@nestjs-odata/core'\nimport type { IETagProvider } from '@nestjs-odata/core'\n\n/**\n * TypeORM implementation of IETagProvider.\n *\n * Discovers the ETag source column per entity set in priority order:\n * 1. @ODataETag() decorator (explicit opt-in takes precedence)\n * 2. TypeORM @UpdateDateColumn (automatic fallback via isUpdateDate)\n *\n * ETag format: W/\"<base64-encoded column value>\"\n * This is deterministic and changes whenever the column value changes.\n *\n * Caches column name resolution per entity set for performance.\n */\n@Injectable()\nexport class TypeOrmETagProvider implements IETagProvider {\n private readonly cache = new Map<string, string | null>()\n\n constructor(\n private readonly dataSource: DataSource,\n private readonly edmRegistry: EdmRegistry,\n ) {}\n\n /**\n * Get the ETag column name for an entity set.\n * Returns undefined if neither @ODataETag nor @UpdateDateColumn is found.\n * Results are cached to avoid repeated metadata reflection.\n */\n getETagColumn(entitySetName: string): string | undefined {\n if (this.cache.has(entitySetName)) {\n const cached = this.cache.get(entitySetName)\n return cached ?? undefined\n }\n\n const column = this.resolveETagColumn(entitySetName)\n this.cache.set(entitySetName, column ?? null)\n return column\n }\n\n /**\n * Compute a weak ETag string from an entity's ETag column value.\n * Format: W/\"<base64-encoded string value>\"\n */\n computeETag(entity: Record<string, unknown>, etagColumn: string): string {\n const value = entity[etagColumn]\n let stringValue: string\n if (value instanceof Date) {\n stringValue = value.toISOString()\n } else {\n stringValue = String(value)\n }\n const encoded = Buffer.from(stringValue).toString('base64')\n return `W/\"${encoded}\"`\n }\n\n /**\n * Validate an If-Match header value against the current entity.\n * Returns true if the ETag matches (safe to proceed with mutation).\n */\n validateIfMatch(\n ifMatchValue: string,\n entity: Record<string, unknown>,\n etagColumn: string,\n ): boolean {\n const current = this.computeETag(entity, etagColumn)\n // Normalize: strip surrounding quotes/whitespace for robust comparison\n const normalize = (s: string): string =>\n s\n .trim()\n .replace(/^W\\//, '')\n .replace(/^\"(.*)\"$/, '$1')\n return normalize(current) === normalize(ifMatchValue)\n }\n\n // ── Private ────────────────────────────────────────────────────────────────\n\n private resolveETagColumn(entitySetName: string): string | undefined {\n // Step 1: resolve entity type name from registry\n const entitySet = this.edmRegistry.getEntitySet(entitySetName)\n if (!entitySet) return undefined\n\n const entityTypeName = entitySet.entityTypeName\n\n // Step 2: find the TypeORM metadata for this entity type\n const meta = this.dataSource.entityMetadatas.find((m) => m.name === entityTypeName)\n if (!meta) return undefined\n\n const entityClass = meta.target as new (...args: unknown[]) => unknown\n if (typeof entityClass !== 'function') return undefined\n\n // Step 3: check @ODataETag decorator first (explicit opt-in takes precedence)\n const explicitProperty = getETagProperty(entityClass)\n if (explicitProperty) return explicitProperty\n\n // Step 4: fall back to TypeORM @UpdateDateColumn\n const updateDateCol = meta.columns.find((c) => c.isUpdateDate)\n if (updateDateCol) return updateDateCol.propertyName\n\n return undefined\n }\n}\n","import { Injectable } from '@nestjs/common'\nimport type { DataSource } from 'typeorm'\nimport { EdmRegistry, ODataValidationError, getSearchableProperties } from '@nestjs-odata/core'\nimport type {\n ISearchProvider,\n SearchNode,\n SearchTermNode,\n SearchBinaryNode,\n} from '@nestjs-odata/core'\n\n/**\n * TypeORM implementation of ISearchProvider.\n *\n * Builds parameterized LIKE conditions for $search queries.\n * Uses @ODataSearchable()-decorated properties to determine which fields to search.\n *\n * Search terms are parameterized (never interpolated) and LIKE special characters\n * (%, _) are escaped before use — mitigates T-11-04 SQL injection.\n *\n * If no @ODataSearchable fields are found, throws ODataValidationError with a\n * clear message instructing the developer to add the decorator.\n */\n@Injectable()\nexport class TypeOrmSearchProvider implements ISearchProvider {\n constructor(\n private readonly dataSource: DataSource,\n private readonly edmRegistry: EdmRegistry,\n ) {}\n\n /**\n * Build a parameterized WHERE condition from a parsed $search AST node.\n *\n * @param searchNode - Parsed $search expression\n * @param entitySetName - OData entity set name being searched\n * @param alias - TypeORM QueryBuilder alias for the entity table\n * @returns Condition string + named params, or null if unable to build\n */\n buildSearchCondition(\n searchNode: SearchNode,\n entitySetName: string,\n alias: string,\n ): { condition: string; params: Record<string, unknown> } | null {\n const entityClass = this.resolveEntityClass(entitySetName)\n const searchableFields = entityClass ? getSearchableProperties(entityClass) : []\n\n if (searchableFields.length === 0) {\n throw new ODataValidationError(\n `No searchable fields configured for entity set '${entitySetName}'. Use @ODataSearchable() decorator on entity properties.`,\n entitySetName,\n '$search',\n )\n }\n\n const params: Record<string, unknown> = {}\n let paramCounter = 0\n\n const buildCondition = (node: SearchNode): string => {\n if (node.kind === 'SearchTerm') {\n return this.buildTermCondition(node, searchableFields, alias, params, paramCounter++)\n }\n // node is SearchBinaryNode here (discriminated union narrowing)\n return this.buildBinaryCondition(node, buildCondition)\n }\n\n const condition = buildCondition(searchNode)\n return { condition, params }\n }\n\n // ── Private helpers ─────────────────────────────────────────────────────────\n\n /**\n * Resolve the entity class from the EdmRegistry and DataSource metadata.\n */\n private resolveEntityClass(entitySetName: string): (new (...args: unknown[]) => unknown) | null {\n const entitySet = this.edmRegistry.getEntitySet(entitySetName)\n if (!entitySet) return null\n\n const meta = this.dataSource.entityMetadatas.find((m) => m.name === entitySet.entityTypeName)\n if (!meta) return null\n\n const entityClass = meta.target\n if (typeof entityClass !== 'function') return null\n\n return entityClass as new (...args: unknown[]) => unknown\n }\n\n /**\n * Build a single LIKE condition for a search term across all searchable fields.\n * Fields are OR'd together: (alias.f1 LIKE :p OR alias.f2 LIKE :p)\n * Negated terms are wrapped: NOT (...)\n */\n private buildTermCondition(\n node: SearchTermNode,\n searchableFields: string[],\n alias: string,\n params: Record<string, unknown>,\n index: number,\n ): string {\n const paramName = `search_${index}`\n const escaped = this.escapeLike(node.value)\n params[paramName] = `%${escaped}%`\n\n const likeParts = searchableFields.map((field) => `${alias}.${field} LIKE :${paramName}`)\n const inner = `(${likeParts.join(' OR ')})`\n\n return node.negated ? `NOT ${inner}` : inner\n }\n\n /**\n * Build a binary AND/OR condition combining two recursively-built conditions.\n */\n private buildBinaryCondition(\n node: SearchBinaryNode,\n buildCondition: (n: SearchNode) => string,\n ): string {\n const leftCond = buildCondition(node.left)\n const rightCond = buildCondition(node.right)\n return `(${leftCond} ${node.operator} ${rightCond})`\n }\n\n /**\n * Escape LIKE special characters to prevent wildcard injection (T-11-04).\n * % -> \\%, _ -> \\_\n */\n private escapeLike(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/%/g, '\\\\%').replace(/_/g, '\\\\_')\n }\n}\n","import { DynamicModule, Inject, Injectable, Module, OnModuleInit } from '@nestjs/common'\nimport { PATH_METADATA } from '@nestjs/common/constants.js'\nimport { TypeOrmModule } from '@nestjs/typeorm'\nimport { DataSource, type ObjectLiteral } from 'typeorm'\nimport type { EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-class-or-schema.type.js'\nimport type {\n EdmEntityConfig,\n EdmEntitySet,\n EdmEntityType,\n EntityClass,\n ISearchProvider,\n ODataModuleResolvedOptions,\n} from '@nestjs-odata/core'\nimport {\n EdmRegistry,\n ETAG_PROVIDER,\n ODataModule,\n ODATA_MODULE_OPTIONS,\n SEARCH_PROVIDER,\n type ODataModuleOptions,\n} from '@nestjs-odata/core'\nimport { TypeOrmEdmDeriver } from './deriver/typeorm-edm-deriver.js'\nimport { TypeOrmQueryTranslator } from './translator/typeorm-query-translator.js'\nimport { TypeOrmAutoHandler } from './translator/typeorm-auto-handler.js'\nimport { TypeOrmETagProvider } from './etag/typeorm-etag.provider.js'\nimport { TypeOrmSearchProvider } from './translator/search-provider.js'\nimport { BatchController } from './batch/batch-controller.js'\n\n/**\n * DI injection token for the array of TypeORM entity classes\n * registered via ODataTypeOrmModule.forFeature().\n */\nexport const TYPEORM_ODATA_ENTITIES = Symbol('TYPEORM_ODATA_ENTITIES')\n\n/**\n * TypeOrmEdmInitializer — runs at module init to derive EDM from TypeORM entities\n * and register them in the EdmRegistry.\n *\n * Per D-06, D-16, EDM-06: EDM derivation happens after DataSource is ready (onModuleInit).\n */\n@Injectable()\nexport class TypeOrmEdmInitializer implements OnModuleInit {\n constructor(\n private readonly dataSource: DataSource,\n private readonly edmRegistry: EdmRegistry,\n @Inject(ODATA_MODULE_OPTIONS) private readonly options: ODataModuleOptions,\n @Inject(TYPEORM_ODATA_ENTITIES) private readonly entityClasses: EntityClass[],\n ) {}\n\n onModuleInit(): void {\n const deriver = new TypeOrmEdmDeriver(\n this.options.namespace ?? 'Default',\n this.options.unmappedTypeStrategy ?? 'skip',\n )\n const metadatas = this.entityClasses.map((cls) => this.dataSource.getMetadata(cls))\n const configs: EdmEntityConfig[] = deriver.deriveEntityTypes(this.entityClasses, metadatas)\n\n for (const config of configs) {\n const namespace = this.options.namespace ?? 'Default'\n const entityType: EdmEntityType = {\n name: config.entityTypeName,\n namespace,\n properties: config.properties,\n navigationProperties: config.navigationProperties,\n keyProperties: config.keyProperties,\n isReadOnly: config.isReadOnly,\n }\n const entitySet: EdmEntitySet = {\n name: config.entitySetName,\n entityTypeName: config.entityTypeName,\n namespace,\n isReadOnly: config.isReadOnly,\n }\n this.edmRegistry.register(entityType, entitySet)\n }\n }\n}\n\n/**\n * TypeORM adapter module for @nestjs-odata/core.\n *\n * Wraps TypeOrmModule.forFeature() and registers the entity classes\n * under the TYPEORM_ODATA_ENTITIES token. TypeOrmEdmInitializer runs\n * at onModuleInit to derive and register OData EDM metadata from TypeORM.\n *\n * ODataModule.forRoot() must be imported in the application root module —\n * EdmRegistry is @Global() so it is available here without an explicit import.\n *\n * Usage:\n * ODataTypeOrmModule.forFeature([Product, Category])\n */\n@Module({})\nexport class ODataTypeOrmModule {\n /**\n * Register TypeORM entity classes for OData auto-derivation.\n *\n * @param entities - Array of TypeORM entity classes (decorated with @Entity())\n * @returns DynamicModule that imports TypeOrmModule.forFeature, provides TYPEORM_ODATA_ENTITIES,\n * and wires TypeOrmEdmInitializer to populate the EdmRegistry at onModuleInit\n */\n static forFeature(\n entities: EntityClassOrSchema[],\n options?: { serviceRoot?: string },\n ): DynamicModule {\n // Patch BatchController's PATH_METADATA to include the serviceRoot so\n // POST {serviceRoot}/$batch is registered correctly.\n // Per D-07, D-08: inherit serviceRoot from ODataModule.registeredServiceRoot\n // when not explicitly provided. Single source of truth from forRoot().\n const serviceRoot: string = options?.serviceRoot ?? ODataModule.registeredServiceRoot ?? 'odata'\n const root = serviceRoot.startsWith('/') ? serviceRoot.slice(1) : serviceRoot\n Reflect.defineMetadata(PATH_METADATA, root, BatchController)\n\n return {\n module: ODataTypeOrmModule,\n imports: [TypeOrmModule.forFeature(entities)],\n controllers: [BatchController],\n providers: [\n {\n provide: TYPEORM_ODATA_ENTITIES,\n useValue: entities,\n },\n TypeOrmEdmInitializer,\n {\n provide: TypeOrmSearchProvider,\n useFactory: (dataSource: DataSource, edmRegistry: EdmRegistry): TypeOrmSearchProvider => {\n return new TypeOrmSearchProvider(dataSource, edmRegistry)\n },\n inject: [DataSource, EdmRegistry],\n },\n {\n provide: SEARCH_PROVIDER,\n useExisting: TypeOrmSearchProvider,\n },\n {\n provide: TypeOrmQueryTranslator,\n useFactory: (\n dataSource: DataSource,\n edmRegistry: EdmRegistry,\n options: ODataModuleResolvedOptions,\n searchProvider: ISearchProvider,\n ): TypeOrmQueryTranslator => {\n // Use a shared repository for query builder creation.\n // TypeOrmQueryTranslator uses repo.createQueryBuilder() which accepts any entity target.\n // The DataSource repository is typed as ObjectLiteral which satisfies the translator.\n const firstEntity = entities[0]\n if (!firstEntity) {\n throw new Error('ODataTypeOrmModule.forFeature() requires at least one entity class')\n }\n const repo = dataSource.getRepository(firstEntity as new () => ObjectLiteral)\n return new TypeOrmQueryTranslator(repo, edmRegistry, options, searchProvider)\n },\n inject: [\n DataSource,\n EdmRegistry,\n ODATA_MODULE_OPTIONS,\n { token: SEARCH_PROVIDER, optional: true },\n ],\n },\n {\n provide: TypeOrmETagProvider,\n useFactory: (dataSource: DataSource, edmRegistry: EdmRegistry): TypeOrmETagProvider => {\n return new TypeOrmETagProvider(dataSource, edmRegistry)\n },\n inject: [DataSource, EdmRegistry],\n },\n {\n provide: ETAG_PROVIDER,\n useExisting: TypeOrmETagProvider,\n },\n {\n provide: TypeOrmAutoHandler,\n useFactory: (\n translator: TypeOrmQueryTranslator,\n edmRegistry: EdmRegistry,\n options: ODataModuleResolvedOptions,\n dataSource: DataSource,\n etagProvider: TypeOrmETagProvider,\n ): TypeOrmAutoHandler => {\n const firstEntity = entities[0]\n if (!firstEntity) {\n throw new Error('ODataTypeOrmModule.forFeature() requires at least one entity class')\n }\n const repo = dataSource.getRepository(firstEntity as new () => ObjectLiteral)\n return new TypeOrmAutoHandler(\n translator,\n edmRegistry,\n options,\n repo,\n etagProvider,\n dataSource,\n )\n },\n inject: [\n TypeOrmQueryTranslator,\n EdmRegistry,\n ODATA_MODULE_OPTIONS,\n DataSource,\n TypeOrmETagProvider,\n ],\n },\n ],\n exports: [\n TYPEORM_ODATA_ENTITIES,\n TypeOrmQueryTranslator,\n TypeOrmAutoHandler,\n TypeOrmETagProvider,\n ETAG_PROVIDER,\n TypeOrmSearchProvider,\n SEARCH_PROVIDER,\n ],\n }\n }\n}\n","/**\n * OData v4 $batch endpoint controller.\n *\n * Accepts POST /$batch with a multipart/mixed body containing multiple\n * OData operations. Dispatches each sub-request to the appropriate CRUD\n * handler. Wraps changesets in TypeORM QueryRunner transactions for\n * atomicity (D-02, BATCH-02).\n *\n * Security:\n * T-05-01: Boundary validation delegated to parseBatchBody (throws 400).\n * T-05-02: MAX_BATCH_OPERATIONS limit enforced by parseBatchBody.\n * T-05-03: Sub-request URLs parsed via regex — never passed to shell/eval.\n * T-05-05: Error responses use OData error format, no stack traces.\n */\n\nimport { Controller, Post, Req, Res, Inject, HttpStatus } from '@nestjs/common'\nimport type { IncomingMessage } from 'http'\nimport { DataSource, type EntityManager, type ObjectLiteral } from 'typeorm'\n\n/**\n * Minimal interface for the subset of Express Request used by BatchController.\n * Avoids a direct dependency on @types/express in the typeorm adapter package.\n */\nexport interface BatchRequest extends IncomingMessage {\n body?: unknown\n rawBody?: Buffer | string\n headers: Record<string, string | string[] | undefined>\n}\n\n/**\n * Minimal interface for the subset of Express Response used by BatchController.\n */\nexport interface BatchResponse {\n status(code: number): this\n set(field: string, value: string): this\n send(body: string): this\n json(body: unknown): this\n}\nimport {\n EdmRegistry,\n ODATA_MODULE_OPTIONS,\n parseODataKey,\n parseBatchBody,\n extractBoundary,\n type ODataModuleResolvedOptions,\n type BatchResponsePart,\n type BatchRequestPart,\n type EdmEntityType,\n} from '@nestjs-odata/core'\nimport { buildBatchResponse } from './batch-response-builder.js'\nimport { TYPEORM_ODATA_ENTITIES } from '../odata-typeorm.module.js'\nimport type { EntityClass } from '@nestjs-odata/core'\n\n/**\n * Internal error used to signal a failed changeset operation so the\n * QueryRunner transaction can be rolled back.\n * Carries the status code and serialized body from the failed operation.\n */\nclass ChangesetOperationError extends Error {\n constructor(\n public readonly statusCode: number,\n public readonly body: string,\n ) {\n super(`Changeset operation failed with status ${statusCode}`)\n this.name = 'ChangesetOperationError'\n Object.setPrototypeOf(this, new.target.prototype)\n }\n}\n\n/**\n * Build an OData error response body (T-05-05).\n * Never exposes stack traces or internal server details.\n */\nfunction buildODataError(code: string, message: string): string {\n return JSON.stringify({\n error: {\n code,\n message,\n details: [],\n },\n })\n}\n\n/**\n * Map HTTP status to OData error code string.\n */\nfunction statusToCode(status: number): string {\n const codes: Record<number, string> = {\n 400: 'BadRequest',\n 401: 'Unauthorized',\n 403: 'Forbidden',\n 404: 'NotFound',\n 409: 'Conflict',\n 500: 'InternalServerError',\n }\n return codes[status] ?? 'Error'\n}\n\n/**\n * Parse entity set name and optional key from a sub-request URL.\n *\n * Strategy:\n * 1. Strip query string first.\n * 2. Try to match last segment with key: /EntitySet(key) at path end.\n * 3. Fall back to matching last path segment (no key).\n *\n * Per T-05-03: URL parsing is regex-based, never used for shell/eval.\n *\n * Returns null if the URL cannot be parsed.\n */\nfunction parseEntityUrl(url: string): { entitySetName: string; key?: string } | null {\n // Strip query string\n const pathOnly = url.split('?')[0]\n\n // Match segment with key: /EntitySetName(key) at end of path\n const withKeyMatch = /\\/([A-Za-z][A-Za-z0-9_]*)\\(([^)]*)\\)$/.exec(pathOnly)\n if (withKeyMatch) {\n return { entitySetName: withKeyMatch[1] ?? '', key: withKeyMatch[2] }\n }\n\n // Match last path segment without key\n const withoutKeyMatch = /\\/([A-Za-z][A-Za-z0-9_]*)$/.exec(pathOnly)\n if (withoutKeyMatch) {\n return { entitySetName: withoutKeyMatch[1] ?? '', key: undefined }\n }\n\n return null\n}\n\n/**\n * BatchController handles POST /$batch requests.\n *\n * The service root prefix is set dynamically when registering this controller\n * in ODataTypeOrmModule.forFeature().\n */\n@Controller()\nexport class BatchController {\n constructor(\n private readonly dataSource: DataSource,\n private readonly edmRegistry: EdmRegistry,\n @Inject(ODATA_MODULE_OPTIONS) private readonly options: ODataModuleResolvedOptions,\n @Inject(TYPEORM_ODATA_ENTITIES) private readonly entityClasses: EntityClass[],\n ) {}\n\n /**\n * POST {serviceRoot}/$batch\n *\n * Reads raw body (must be pre-configured with body-parser for text/plain or rawBody),\n * parses multipart/mixed, dispatches sub-requests, builds multipart response.\n */\n @Post('$batch')\n async handleBatch(@Req() req: BatchRequest, @Res() res: BatchResponse): Promise<void> {\n let rawBody: string\n try {\n rawBody = await this.extractRawBody(req)\n } catch (err) {\n res\n .status(HttpStatus.BAD_REQUEST)\n .json({ error: { code: 'BadRequest', message: String(err), details: [] } })\n return\n }\n\n const contentTypeHeader = req.headers['content-type']\n const contentType =\n (Array.isArray(contentTypeHeader) ? contentTypeHeader[0] : contentTypeHeader) ?? ''\n\n let boundary: string\n try {\n boundary = extractBoundary(contentType)\n } catch {\n res.status(HttpStatus.BAD_REQUEST).json({\n error: {\n code: 'BadRequest',\n message: 'Missing or invalid boundary in Content-Type for $batch request',\n details: [],\n },\n })\n return\n }\n\n let parsed\n try {\n parsed = parseBatchBody(rawBody, boundary)\n } catch (err) {\n const msg = err instanceof Error ? err.message : 'Malformed batch body'\n res\n .status(HttpStatus.BAD_REQUEST)\n .json({ error: { code: 'BadRequest', message: msg, details: [] } })\n return\n }\n\n const responseParts: BatchResponsePart[] = []\n\n for (const part of parsed.parts) {\n if (part.kind === 'request') {\n const responsePart = await this.dispatchIndividualRequest(part)\n responseParts.push(responsePart)\n } else if (part.kind === 'changeset') {\n const changesetParts = await this.executeChangeset(part.parts)\n responseParts.push(...changesetParts)\n }\n }\n\n const { contentType: responseContentType, body } = buildBatchResponse(responseParts)\n\n res.status(HttpStatus.OK).set('Content-Type', responseContentType).send(body)\n }\n\n /**\n * Extract the raw body string from the request.\n *\n * Tries multiple strategies in order:\n * 1. req.body as string (if a text/plain body parser ran first)\n * 2. req.rawBody Buffer (NestJS rawBody: true option)\n * 3. Read directly from the Node.js request stream (multipart/mixed fallback)\n * This works because $batch sends multipart/mixed which no standard body\n * parser processes, leaving the stream unconsumed.\n */\n private extractRawBody(req: BatchRequest): Promise<string> | string {\n // When body is already a string (text/plain body parser)\n if (typeof req.body === 'string' && req.body.length > 0) {\n return req.body\n }\n // When rawBody is available (NestJS rawBody: true)\n const rawBodyProp = req.rawBody\n if (rawBodyProp !== undefined) {\n return typeof rawBodyProp === 'string' ? rawBodyProp : rawBodyProp.toString('utf-8')\n }\n // Fall back: read from the Node.js IncomingMessage stream.\n // multipart/mixed is not handled by standard body parsers, so the stream is unconsumed.\n return new Promise<string>((resolve, reject) => {\n const chunks: Buffer[] = []\n req.on('data', (chunk: Buffer) => chunks.push(chunk))\n req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')))\n req.on('error', reject)\n })\n }\n\n /**\n * Dispatch a single individual request (outside a changeset).\n * Failures are caught and returned as per-operation error responses (BATCH-03).\n */\n private async dispatchIndividualRequest(part: BatchRequestPart): Promise<BatchResponsePart> {\n try {\n return await this.dispatchWithManager(part, this.dataSource.manager)\n } catch (err) {\n return this.buildErrorResponse(err, part.contentId)\n }\n }\n\n /**\n * Execute all operations in a changeset within a single QueryRunner transaction.\n * If any operation fails, all are rolled back (BATCH-02).\n *\n * Per D-02: full changeset rollback on any failure.\n * Per WRITE-03 / T-10-08: contentIdMap is local to each changeset call — never leaks across changesets.\n */\n private async executeChangeset(parts: readonly BatchRequestPart[]): Promise<BatchResponsePart[]> {\n const queryRunner = this.dataSource.createQueryRunner()\n await queryRunner.connect()\n await queryRunner.startTransaction()\n\n // Per T-10-08: declared inside executeChangeset() so each changeset gets a fresh Map.\n const contentIdMap = new Map<string, string>()\n\n try {\n const results: BatchResponsePart[] = []\n\n for (const part of parts) {\n // Resolve any $N references in URL and body before dispatching (WRITE-03)\n const resolvedPart = this.resolveContentIdReferences(part, contentIdMap)\n const result = await this.dispatchWithManager(resolvedPart, queryRunner.manager)\n results.push(result)\n\n // Per D-02: if any changeset operation fails (status >= 400), roll back\n // the entire changeset — do not commit partial changes.\n if (result.statusCode >= 400) {\n throw new ChangesetOperationError(result.statusCode, result.body ?? 'Operation failed')\n }\n\n // Store location URL in contentIdMap after a successful 201 with Content-ID (WRITE-03)\n const location = result.headers['location']\n if (result.statusCode === 201 && location && part.contentId !== undefined) {\n contentIdMap.set(part.contentId, location)\n }\n }\n\n await queryRunner.commitTransaction()\n return results\n } catch (err) {\n await queryRunner.rollbackTransaction()\n\n // All changeset parts get the same error response (BATCH-02 atomicity)\n const errorResponse =\n err instanceof ChangesetOperationError\n ? {\n statusCode: err.statusCode,\n headers: {} as Record<string, string>,\n body: err.body,\n }\n : this.buildErrorResponse(err)\n return parts.map((p) => ({ ...errorResponse, contentId: p.contentId }))\n } finally {\n await queryRunner.release()\n }\n }\n\n /**\n * Resolve Content-ID references ($N) in a BatchRequestPart's URL and body.\n *\n * Per WRITE-03: $N in a URL or body is substituted with the location URL recorded\n * for content ID N from a prior 201 response in the same changeset.\n *\n * Fast path: returns the original part unchanged when contentIdMap is empty or\n * no $N patterns match (avoids unnecessary object allocation).\n *\n * Per immutability rules: never mutates the readonly BatchRequestPart — returns a new object.\n * Per T-10-07: substitution uses only URLs already in contentIdMap (populated by our own\n * 201 responses) — no code evaluation, no cross-changeset leak.\n */\n private resolveContentIdReferences(\n part: BatchRequestPart,\n contentIdMap: ReadonlyMap<string, string>,\n ): BatchRequestPart {\n if (contentIdMap.size === 0) return part // fast path: nothing to resolve\n\n let url = part.url\n let body = part.body\n\n for (const [id, resolvedUrl] of contentIdMap) {\n // URL pattern: $N followed by /, ?, #, or end of string (standard URL segments)\n const urlPattern = new RegExp(`\\\\$${id}(?=[/?#]|$)`, 'g')\n url = url.replace(urlPattern, resolvedUrl)\n if (body) {\n // Body pattern: $N followed by a non-digit or end of string.\n // Handles JSON bodies where $1 appears inside quoted strings (e.g., \"$1\" -> resolved URL).\n const bodyPattern = new RegExp(`\\\\$${id}(?=\\\\D|$)`, 'g')\n body = body.replace(bodyPattern, resolvedUrl)\n }\n }\n\n // Return original if nothing changed (reference equality fast path)\n if (url === part.url && body === part.body) return part\n\n return { ...part, url, ...(body !== part.body ? { body } : {}) }\n }\n\n /**\n * Dispatch a parsed sub-request to the appropriate handler using the given EntityManager.\n * Uses the EntityManager so changesets can use a transaction-scoped manager.\n *\n * Per T-05-03: URL parsing is regex-based, entity operations go through TypeORM parameterized queries.\n */\n private async dispatchWithManager(\n part: BatchRequestPart,\n manager: EntityManager,\n ): Promise<BatchResponsePart> {\n const parsed = parseEntityUrl(part.url)\n if (!parsed) {\n return {\n contentId: part.contentId,\n statusCode: 400,\n headers: {},\n body: buildODataError('BadRequest', `Cannot parse entity URL: ${part.url}`),\n }\n }\n\n const { entitySetName, key } = parsed\n\n // Resolve entity type from registry\n const entitySet = this.edmRegistry.getEntitySet(entitySetName)\n if (!entitySet) {\n return {\n contentId: part.contentId,\n statusCode: 404,\n headers: {},\n body: buildODataError('NotFound', `Entity set '${entitySetName}' not found`),\n }\n }\n\n const entityType = this.edmRegistry.getEntityType(entitySet.entityTypeName)\n if (!entityType) {\n return {\n contentId: part.contentId,\n statusCode: 404,\n headers: {},\n body: buildODataError('NotFound', `Entity type '${entitySet.entityTypeName}' not found`),\n }\n }\n\n // Resolve the TypeORM entity class for this entity set\n const entityClass = this.resolveEntityClass(entityType.name)\n if (!entityClass) {\n return {\n contentId: part.contentId,\n statusCode: 500,\n headers: {},\n body: buildODataError(\n 'InternalServerError',\n `Entity class not found for '${entityType.name}'`,\n ),\n }\n }\n\n const method = part.method.toUpperCase()\n\n try {\n if (method === 'GET') {\n if (key !== undefined) {\n return await this.dispatchGetByKey(\n key,\n entitySetName,\n entityType.keyProperties,\n entityClass,\n manager,\n part.contentId,\n )\n }\n // Collection GET — not supported in batch (read-only outside changeset)\n return await this.dispatchGetCollection(entityClass, manager, part.contentId)\n } else if (method === 'POST') {\n return await this.dispatchCreate(\n part,\n entitySetName,\n entityType.keyProperties,\n entityClass,\n manager,\n )\n } else if (method === 'PATCH') {\n if (key === undefined) {\n return {\n contentId: part.contentId,\n statusCode: 400,\n headers: {},\n body: buildODataError('BadRequest', `PATCH requires a key: ${part.url}`),\n }\n }\n return await this.dispatchUpdate(\n key,\n part,\n entitySetName,\n entityType.keyProperties,\n entityClass,\n manager,\n )\n } else if (method === 'PUT') {\n if (key === undefined) {\n return {\n contentId: part.contentId,\n statusCode: 400,\n headers: {},\n body: buildODataError('BadRequest', `PUT requires a key: ${part.url}`),\n }\n }\n return await this.dispatchReplace(\n key,\n part,\n entitySetName,\n entityType,\n entityClass,\n manager,\n )\n } else if (method === 'DELETE') {\n if (key === undefined) {\n return {\n contentId: part.contentId,\n statusCode: 400,\n headers: {},\n body: buildODataError('BadRequest', `DELETE requires a key: ${part.url}`),\n }\n }\n return await this.dispatchDelete(\n key,\n entitySetName,\n entityType.keyProperties,\n entityClass,\n manager,\n part.contentId,\n )\n }\n\n return {\n contentId: part.contentId,\n statusCode: 405,\n headers: {},\n body: buildODataError('MethodNotAllowed', `Method '${method}' not supported in $batch`),\n }\n } catch (err) {\n return this.buildErrorResponse(err, part.contentId)\n }\n }\n\n /** GET /EntitySet(key) */\n private async dispatchGetByKey(\n keyStr: string,\n entitySetName: string,\n keyProperties: readonly string[],\n entityClass: EntityClass,\n manager: EntityManager,\n contentId?: string,\n ): Promise<BatchResponsePart> {\n const where = parseODataKey(keyStr, keyProperties)\n const entity = await manager.findOne(entityClass as new () => ObjectLiteral, { where })\n if (!entity) {\n return {\n contentId,\n statusCode: 404,\n headers: {},\n body: buildODataError(\n 'NotFound',\n `Entity '${entitySetName}' with key '${keyStr}' not found`,\n ),\n }\n }\n return {\n contentId,\n statusCode: 200,\n headers: {},\n body: JSON.stringify(entity),\n }\n }\n\n /** GET /EntitySet (collection — basic support) */\n private async dispatchGetCollection(\n entityClass: EntityClass,\n manager: EntityManager,\n contentId?: string,\n ): Promise<BatchResponsePart> {\n const items = await manager.find(entityClass as new () => ObjectLiteral)\n return {\n contentId,\n statusCode: 200,\n headers: {},\n body: JSON.stringify({ value: items }),\n }\n }\n\n /** POST /EntitySet */\n private async dispatchCreate(\n part: BatchRequestPart,\n entitySetName: string,\n keyProperties: readonly string[],\n entityClass: EntityClass,\n manager: EntityManager,\n ): Promise<BatchResponsePart> {\n const body = part.body ? (JSON.parse(part.body) as Record<string, unknown>) : {}\n const repo = manager.getRepository(entityClass as new () => ObjectLiteral)\n const created = repo.create(body as ObjectLiteral)\n const saved = (await repo.save(created)) as Record<string, unknown>\n\n // Build Location URL\n const keyParts = keyProperties.map((kp) => {\n const val = saved[kp]\n return keyProperties.length === 1 ? String(val) : `${kp}=${String(val)}`\n })\n const keyStr = keyParts.join(',')\n const locationUrl = `${this.options.serviceRoot}/${entitySetName}(${keyStr})`\n\n return {\n contentId: part.contentId,\n statusCode: 201,\n headers: { location: locationUrl },\n body: JSON.stringify(saved),\n }\n }\n\n /** PATCH /EntitySet(key) */\n private async dispatchUpdate(\n keyStr: string,\n part: BatchRequestPart,\n entitySetName: string,\n keyProperties: readonly string[],\n entityClass: EntityClass,\n manager: EntityManager,\n ): Promise<BatchResponsePart> {\n const body = part.body ? (JSON.parse(part.body) as Record<string, unknown>) : {}\n const where = parseODataKey(keyStr, keyProperties)\n const EntityCls = entityClass as new () => ObjectLiteral\n const existing = await manager.findOne(EntityCls, { where })\n if (!existing) {\n return {\n contentId: part.contentId,\n statusCode: 404,\n headers: {},\n body: buildODataError(\n 'NotFound',\n `Entity '${entitySetName}' with key '${keyStr}' not found`,\n ),\n }\n }\n // Merge patch fields onto the existing entity, then save\n const merged = manager.merge(EntityCls, existing, body as ObjectLiteral)\n const saved = await manager.save(EntityCls, merged)\n return {\n contentId: part.contentId,\n statusCode: 200,\n headers: {},\n body: JSON.stringify(saved),\n }\n }\n\n /**\n * PUT /EntitySet(key) — full entity replacement.\n *\n * Per OData v4 spec (D-01): all entity properties replaced.\n * Unspecified fields reset to column defaults (null for nullable, default value otherwise).\n * Distinct from PATCH merge semantics used by dispatchUpdate().\n *\n * Uses repo.metadata.columns for column introspection — same pattern as handleReplace().\n */\n private async dispatchReplace(\n keyStr: string,\n part: BatchRequestPart,\n entitySetName: string,\n entityType: EdmEntityType,\n entityClass: EntityClass,\n manager: EntityManager,\n ): Promise<BatchResponsePart> {\n const body = part.body ? (JSON.parse(part.body) as Record<string, unknown>) : {}\n const where = parseODataKey(keyStr, entityType.keyProperties)\n const EntityCls = entityClass as new () => ObjectLiteral\n\n // Validate key in body matches URL key (T-10-01)\n for (const kp of entityType.keyProperties) {\n const bodyKeyValue = body[kp]\n const urlKeyValue = where[kp]\n if (bodyKeyValue !== undefined && bodyKeyValue !== urlKeyValue) {\n return {\n contentId: part.contentId,\n statusCode: 400,\n headers: {},\n body: buildODataError('BadRequest', 'Key in body does not match URL key'),\n }\n }\n }\n\n const existing = await manager.findOne(EntityCls, { where })\n if (!existing) {\n return {\n contentId: part.contentId,\n statusCode: 404,\n headers: {},\n body: buildODataError(\n 'NotFound',\n `Entity '${entitySetName}' with key '${keyStr}' not found`,\n ),\n }\n }\n\n // Build full replacement using column metadata — same logic as handleReplace()\n const meta = this.dataSource.getMetadata(entityClass)\n const replacement: Record<string, unknown> = {}\n\n // Set key values from URL\n for (const kp of entityType.keyProperties) {\n replacement[kp] = where[kp]\n }\n\n for (const col of meta.columns) {\n if (col.isPrimary) continue\n if (col.isCreateDate || col.isUpdateDate || col.isVersion) continue\n\n const propName = col.propertyName\n if (Object.prototype.hasOwnProperty.call(body, propName)) {\n replacement[propName] = body[propName]\n } else if (col.default !== undefined) {\n replacement[propName] = col.default\n } else if (col.isNullable) {\n replacement[propName] = null\n }\n }\n\n const repo = manager.getRepository(EntityCls)\n const entity = repo.create(replacement as ObjectLiteral)\n const saved = await manager.save(EntityCls, entity)\n\n return {\n contentId: part.contentId,\n statusCode: 200,\n headers: {},\n body: JSON.stringify(saved),\n }\n }\n\n /** DELETE /EntitySet(key) */\n private async dispatchDelete(\n keyStr: string,\n entitySetName: string,\n keyProperties: readonly string[],\n entityClass: EntityClass,\n manager: EntityManager,\n contentId?: string,\n ): Promise<BatchResponsePart> {\n const where = parseODataKey(keyStr, keyProperties)\n const result = await manager.delete(entityClass as new () => ObjectLiteral, where)\n if (result.affected === 0) {\n return {\n contentId,\n statusCode: 404,\n headers: {},\n body: buildODataError(\n 'NotFound',\n `Entity '${entitySetName}' with key '${keyStr}' not found`,\n ),\n }\n }\n return {\n contentId,\n statusCode: 204,\n headers: {},\n body: undefined,\n }\n }\n\n /**\n * Build an OData error response part from an exception.\n * Never exposes stack traces (T-05-05).\n */\n private buildErrorResponse(err: unknown, contentId?: string): BatchResponsePart {\n if (err instanceof Error) {\n // Check for NestJS NotFoundException\n if (\n err.constructor.name === 'NotFoundException' ||\n (err as { status?: number }).status === 404\n ) {\n return {\n contentId,\n statusCode: 404,\n headers: {},\n body: buildODataError('NotFound', err.message),\n }\n }\n // Check for bad request / validation errors\n if (\n err.constructor.name === 'ODataValidationError' ||\n err.constructor.name === 'ODataParseError' ||\n (err as { status?: number }).status === 400\n ) {\n return {\n contentId,\n statusCode: 400,\n headers: {},\n body: buildODataError('BadRequest', err.message),\n }\n }\n }\n\n const status = (err as { status?: number })?.status\n if (typeof status === 'number' && status >= 400 && status < 500) {\n return {\n contentId,\n statusCode: status,\n headers: {},\n body: buildODataError(statusToCode(status), (err as Error).message ?? 'Request error'),\n }\n }\n\n return {\n contentId,\n statusCode: 500,\n headers: {},\n body: buildODataError('InternalServerError', 'An unexpected error occurred.'),\n }\n }\n\n /**\n * Resolve a TypeORM entity class from an EDM entity type name.\n * Matches by checking DataSource metadata for each registered entity class.\n */\n private resolveEntityClass(entityTypeName: string): EntityClass | undefined {\n for (const cls of this.entityClasses) {\n try {\n const meta = this.dataSource.getMetadata(cls)\n if (meta.name === entityTypeName) {\n return cls\n }\n } catch {\n // Entity not found in this DataSource — skip\n }\n }\n return undefined\n }\n}\n","export const VERSION = '0.0.1'\n\nexport * from './batch/index.js'\nexport * from './odata-typeorm.module.js'\nexport * from './deriver/typeorm-edm-deriver.js'\nexport * from './translator/index.js'\nexport * from './etag/typeorm-etag.provider.js'\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nestjs-odata/typeorm",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "TypeORM adapter for @nestjs-odata/core — auto-derives OData EDM from TypeORM entity metadata",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/cberd1509/nestjs-odata.git",
|
|
9
|
+
"directory": "packages/typeorm"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/cberd1509/nestjs-odata#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/cberd1509/nestjs-odata/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"nestjs",
|
|
17
|
+
"odata",
|
|
18
|
+
"odata-v4",
|
|
19
|
+
"typeorm",
|
|
20
|
+
"rest",
|
|
21
|
+
"api",
|
|
22
|
+
"edm",
|
|
23
|
+
"orm-adapter"
|
|
24
|
+
],
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"main": "./dist/index.cjs",
|
|
27
|
+
"module": "./dist/index.mjs",
|
|
28
|
+
"types": "./dist/index.d.mts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"import": {
|
|
32
|
+
"types": "./dist/index.d.mts",
|
|
33
|
+
"default": "./dist/index.mjs"
|
|
34
|
+
},
|
|
35
|
+
"require": {
|
|
36
|
+
"types": "./dist/index.d.cts",
|
|
37
|
+
"default": "./dist/index.cjs"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"README.md"
|
|
44
|
+
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsdown",
|
|
47
|
+
"test": "vitest run --passWithNoTests",
|
|
48
|
+
"typecheck": "tsc --noEmit",
|
|
49
|
+
"lint": "eslint ."
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"@nestjs-odata/core": ">=1.0.0",
|
|
53
|
+
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
|
54
|
+
"@nestjs/typeorm": "^10.0.0 || ^11.0.0",
|
|
55
|
+
"reflect-metadata": "^0.1.13 || ^0.2.0",
|
|
56
|
+
"rxjs": "^7.0.0",
|
|
57
|
+
"typeorm": "^0.3.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@nestjs-odata/core": "workspace:*",
|
|
61
|
+
"@nestjs/common": "^11.0.0",
|
|
62
|
+
"@nestjs/core": "^11.0.0",
|
|
63
|
+
"@nestjs/testing": "^11.0.0",
|
|
64
|
+
"@nestjs/typeorm": "^11.0.0",
|
|
65
|
+
"@swc/core": "^1.15.24",
|
|
66
|
+
"@types/better-sqlite3": "^7.6.0",
|
|
67
|
+
"@types/node": "24",
|
|
68
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
69
|
+
"better-sqlite3": "^12.8.0",
|
|
70
|
+
"reflect-metadata": "^0.2.2",
|
|
71
|
+
"rxjs": "^7.0.0",
|
|
72
|
+
"tsdown": "^0.21.7",
|
|
73
|
+
"typeorm": "^0.3.28",
|
|
74
|
+
"typescript": "^5.9.3",
|
|
75
|
+
"unplugin-swc": "^1.5.9",
|
|
76
|
+
"vitest": "^3.2.4"
|
|
77
|
+
}
|
|
78
|
+
}
|