@nestjs-odata/core 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 +55 -0
- package/dist/index.cjs +2688 -0
- package/dist/index.d.cts +1402 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +1402 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2565 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +74 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["splitTopLevelCommas"],"sources":["../src/query/odata-validation.error.ts","../src/batch/batch-parser.ts","../src/parser/visitor.ts","../src/parser/errors.ts","../src/parser/lexer.ts","../src/parser/parser.ts","../src/parser/search-parser.ts","../src/parser/apply-parser.ts","../src/edm/edm-registry.ts","../src/edm/pluralize.ts","../src/interfaces/edm-deriver.interface.ts","../src/interfaces/query-translator.interface.ts","../src/interfaces/etag.interface.ts","../src/interfaces/search.interface.ts","../src/tokens.ts","../src/query/odata-query.pipe.ts","../src/decorators/metadata-keys.ts","../src/decorators/odata-etag.decorator.ts","../src/decorators/odata-searchable.decorator.ts","../src/decorators/edm-type.decorator.ts","../src/decorators/odata-exclude.decorator.ts","../src/decorators/odata-entity-set.decorator.ts","../src/decorators/odata-key.decorator.ts","../src/decorators/odata-view.decorator.ts","../src/response/odata-context-url.builder.ts","../src/response/odata-annotation.builder.ts","../src/response/odata-response.interceptor.ts","../src/response/odata-exception.filter.ts","../src/decorators/odata-get.decorator.ts","../src/decorators/odata-query.decorator.ts","../src/decorators/odata-post.decorator.ts","../src/decorators/odata-patch.decorator.ts","../src/decorators/odata-put.decorator.ts","../src/decorators/odata-delete.decorator.ts","../src/decorators/odata-get-by-key.decorator.ts","../src/decorators/odata-controller.decorator.ts","../src/edm/edm-feature-initializer.ts","../src/metadata/csdl-builder.ts","../src/metadata/service-document-builder.ts","../src/metadata/metadata.controller.ts","../src/odata.module.ts","../src/utils/odata-key-parser.ts","../src/index.ts"],"sourcesContent":["/**\n * OData validation error types.\n * ODataValidationError is distinct from ODataParseError — it represents\n * semantic validation failures (unknown properties, type mismatches) rather\n * than syntax errors.\n *\n * Per threat model T-03-02: includes property name and entity type name\n * (user's own query input) but never stack traces or internal paths.\n */\n\n/**\n * Thrown when field name validation fails against the EdmRegistry.\n *\n * The `entityTypeName` and `propertyName` fields provide diagnostic context\n * for the user's own query (not internal server data).\n */\nexport class ODataValidationError extends Error {\n constructor(\n message: string,\n public readonly entityTypeName: string,\n public readonly propertyName: string,\n public readonly availableProperties?: readonly string[],\n ) {\n const enrichedMessage =\n availableProperties && availableProperties.length > 0\n ? `${message}. Available properties: ${availableProperties.join(', ')}`\n : message\n super(enrichedMessage)\n this.name = 'ODataValidationError'\n // Ensure instanceof works correctly when transpiled to ES5\n Object.setPrototypeOf(this, new.target.prototype)\n }\n}\n","/**\n * OData v4 $batch multipart/mixed body parser.\n *\n * Custom parser with zero external dependencies per D-01.\n * Implements OData v4 Part 1 section 11 wire format.\n *\n * Security (T-05-01): validates boundary format, throws ODataValidationError\n * on malformed input — never passes invalid data through.\n */\n\nimport { ODataValidationError } from '../query/odata-validation.error.js'\nimport type { BatchChangesetPart, BatchPart, BatchRequestPart, ParsedBatch } from './batch-types.js'\n\n/**\n * Maximum number of operations allowed in a single batch request (T-05-02).\n * Prevents unbounded transaction size and DoS via large batches.\n */\nexport const MAX_BATCH_OPERATIONS = 100\n\n/**\n * Extract the boundary value from a Content-Type header.\n *\n * Handles both quoted (boundary=\"abc\") and unquoted (boundary=abc) formats.\n * Throws ODataValidationError if the boundary parameter is missing.\n *\n * @param contentType - The Content-Type header value (e.g., 'multipart/mixed; boundary=batch_abc123')\n * @returns The boundary string value\n * @throws ODataValidationError if boundary parameter is missing\n */\nexport function extractBoundary(contentType: string): string {\n // Match both quoted: boundary=\"value\" and unquoted: boundary=value\n const match = /boundary=(?:\"([^\"]+)\"|([^\\s;]+))/i.exec(contentType)\n if (!match) {\n throw new ODataValidationError(\n 'Missing boundary parameter in Content-Type header for $batch request',\n '$batch',\n 'boundary',\n )\n }\n // match[1] is quoted form, match[2] is unquoted form\n return match[1] ?? match[2] ?? ''\n}\n\n/**\n * Parse a multipart/mixed batch body into structured BatchPart objects.\n *\n * Handles:\n * - Individual request parts (kind: 'request')\n * - Changesets (kind: 'changeset') containing sub-request parts\n * - Both CRLF (\\r\\n) and LF (\\n) line endings\n * - Content-ID headers for changeset sub-parts\n * - Embedded HTTP request parsing (METHOD URL HTTP/1.1, headers, body)\n *\n * Per T-05-02: rejects batches exceeding MAX_BATCH_OPERATIONS operations.\n *\n * @param body - The raw request body string\n * @param boundary - The multipart boundary (from extractBoundary())\n * @returns ParsedBatch with all parsed parts\n * @throws ODataValidationError if body is malformed or exceeds limits\n */\nexport function parseBatchBody(body: string, boundary: string): ParsedBatch {\n if (!body || body.trim() === '') {\n return { boundary, parts: [] }\n }\n\n // Normalize line endings to \\n for consistent parsing\n const normalized = body.replace(/\\r\\n/g, '\\n')\n\n const parts = splitByBoundary(normalized, boundary)\n let totalOperations = 0\n\n const batchParts: BatchPart[] = []\n for (const part of parts) {\n const trimmed = part.trim()\n if (!trimmed) continue\n\n const parsed = parsePart(trimmed)\n if (parsed) {\n // Count operations for limit enforcement (T-05-02)\n if (parsed.kind === 'request') {\n totalOperations++\n } else if (parsed.kind === 'changeset') {\n totalOperations += parsed.parts.length\n }\n\n if (totalOperations > MAX_BATCH_OPERATIONS) {\n throw new ODataValidationError(\n `Batch request exceeds maximum of ${MAX_BATCH_OPERATIONS} operations`,\n '$batch',\n 'operations',\n )\n }\n\n batchParts.push(parsed)\n }\n }\n\n return { boundary, parts: batchParts }\n}\n\n/**\n * Split a normalized (LF-only) body by the multipart boundary.\n * Returns the content segments between boundary markers.\n */\nfunction splitByBoundary(body: string, boundary: string): string[] {\n const delimiter = `--${boundary}`\n const terminator = `--${boundary}--`\n\n const lines = body.split('\\n')\n const segments: string[] = []\n let currentSegment: string[] = []\n let inSegment = false\n\n for (const line of lines) {\n const trimmedLine = line.trimEnd()\n\n if (trimmedLine === terminator) {\n // End of multipart body\n if (inSegment && currentSegment.length > 0) {\n segments.push(currentSegment.join('\\n'))\n }\n // Reset so the post-loop guard does not push the same segment again\n inSegment = false\n currentSegment = []\n break\n } else if (trimmedLine === delimiter) {\n // Start of a new part\n if (inSegment && currentSegment.length > 0) {\n segments.push(currentSegment.join('\\n'))\n currentSegment = []\n }\n inSegment = true\n } else if (inSegment) {\n currentSegment.push(line)\n }\n }\n\n // Handle case where there's no terminator\n if (inSegment && currentSegment.length > 0) {\n segments.push(currentSegment.join('\\n'))\n }\n\n return segments\n}\n\n/**\n * Parse a single multipart segment into a BatchPart.\n * Detects whether it's a changeset or an individual request.\n */\nfunction parsePart(segment: string): BatchPart | null {\n // A segment has MIME headers followed by blank line followed by content\n const blankLineIdx = segment.indexOf('\\n\\n')\n if (blankLineIdx === -1) {\n throw new ODataValidationError(\n 'Malformed batch part: missing blank line separator between headers and content',\n '$batch',\n 'part',\n )\n }\n\n const headerSection = segment.slice(0, blankLineIdx)\n const content = segment.slice(blankLineIdx + 2)\n\n const partHeaders = parseMimeHeaders(headerSection)\n const contentType = getHeaderValue(partHeaders, 'content-type')\n\n if (contentType && contentType.toLowerCase().startsWith('multipart/mixed')) {\n // This part is a changeset\n return parseChangeset(content, contentType)\n } else {\n // This part is an individual request (application/http)\n return parseRequestPart(content, partHeaders)\n }\n}\n\n/**\n * Parse a changeset part (kind: 'changeset') by recursively parsing its sub-parts.\n */\nfunction parseChangeset(content: string, changesetContentType: string): BatchChangesetPart {\n const changesetBoundary = extractBoundary(changesetContentType)\n const subSegments = splitByBoundary(content.trim(), changesetBoundary)\n const subParts: BatchRequestPart[] = []\n\n for (const subSegment of subSegments) {\n const trimmed = subSegment.trim()\n if (!trimmed) continue\n\n const blankLineIdx = trimmed.indexOf('\\n\\n')\n if (blankLineIdx === -1) {\n throw new ODataValidationError(\n 'Malformed changeset sub-part: missing blank line separator',\n '$batch',\n 'changeset-part',\n )\n }\n\n const subHeaderSection = trimmed.slice(0, blankLineIdx)\n const subContent = trimmed.slice(blankLineIdx + 2)\n const subHeaders = parseMimeHeaders(subHeaderSection)\n\n const requestPart = parseRequestPart(subContent, subHeaders)\n if (requestPart) {\n subParts.push(requestPart)\n }\n }\n\n return { kind: 'changeset', parts: subParts }\n}\n\n/**\n * Parse an individual HTTP request embedded in a batch part.\n *\n * The content is an embedded HTTP request:\n * METHOD URL HTTP/1.1\\n\n * Header-Name: value\\n\n * \\n\n * [optional body]\n */\nfunction parseRequestPart(content: string, mimeHeaders: Record<string, string>): BatchRequestPart {\n const trimmedContent = content.trim()\n const lines = trimmedContent.split('\\n')\n\n if (lines.length === 0 || !lines[0]) {\n throw new ODataValidationError(\n 'Malformed batch sub-request: empty content',\n '$batch',\n 'request',\n )\n }\n\n // First line: METHOD URL HTTP/1.1\n const requestLine = lines[0].trimEnd()\n const requestLineMatch = /^(\\S+)\\s+(\\S+)(?:\\s+HTTP\\/\\S+)?$/.exec(requestLine)\n if (!requestLineMatch) {\n throw new ODataValidationError(\n `Malformed batch sub-request line: '${requestLine}'`,\n '$batch',\n 'request-line',\n )\n }\n\n const method = requestLineMatch[1] ?? ''\n const url = requestLineMatch[2] ?? ''\n\n // Parse HTTP headers until blank line\n const httpHeaders: Record<string, string> = {}\n let bodyStartIdx = 1\n\n for (let i = 1; i < lines.length; i++) {\n const line = lines[i].trimEnd()\n if (line === '') {\n bodyStartIdx = i + 1\n break\n }\n const colonIdx = line.indexOf(':')\n if (colonIdx !== -1) {\n const headerName = line.slice(0, colonIdx).trim().toLowerCase()\n const headerValue = line.slice(colonIdx + 1).trim()\n httpHeaders[headerName] = headerValue\n }\n bodyStartIdx = i + 1\n }\n\n // Extract body if present\n const bodyLines = lines.slice(bodyStartIdx)\n const bodyStr = bodyLines.join('\\n').trim()\n const body = bodyStr || undefined\n\n // Extract Content-ID from MIME headers (present in changeset sub-parts)\n const contentId = getHeaderValue(mimeHeaders, 'content-id')\n\n return {\n kind: 'request',\n contentId: contentId !== undefined ? contentId.replace(/^<|>$/g, '') : undefined,\n method: method.toUpperCase(),\n url,\n headers: httpHeaders,\n body,\n }\n}\n\n/**\n * Parse MIME-style headers from a header section string.\n * Returns a map of lowercase header names to values.\n */\nfunction parseMimeHeaders(headerSection: string): Record<string, string> {\n const headers: Record<string, string> = {}\n const lines = headerSection.split('\\n')\n\n for (const line of lines) {\n const trimmedLine = line.trimEnd()\n if (!trimmedLine) continue\n\n const colonIdx = trimmedLine.indexOf(':')\n if (colonIdx !== -1) {\n const name = trimmedLine.slice(0, colonIdx).trim().toLowerCase()\n const value = trimmedLine.slice(colonIdx + 1).trim()\n headers[name] = value\n }\n }\n\n return headers\n}\n\n/**\n * Get a header value by name (case-insensitive lookup).\n * Returns undefined if not found.\n */\nfunction getHeaderValue(headers: Record<string, string>, name: string): string | undefined {\n return headers[name.toLowerCase()]\n}\n","/**\n * Visitor interface for OData v4 filter AST traversal.\n * One visit method per FilterNode variant, using the discriminated union `kind` field.\n *\n * Usage:\n * ```typescript\n * class SqlVisitor implements FilterVisitor<string> {\n * visitBinaryExpr(node: BinaryExprNode): string {\n * return `${this.visit(node.left)} ${node.operator.toUpperCase()} ${this.visit(node.right)}`\n * }\n * // ... other visit methods\n * visit(node: FilterNode): string {\n * switch (node.kind) {\n * case 'BinaryExpr': return this.visitBinaryExpr(node)\n * // ...\n * }\n * }\n * }\n * ```\n */\n\nimport type {\n BinaryExprNode,\n FilterNode,\n FunctionCallNode,\n LambdaExprNode,\n LiteralNode,\n PropertyAccessNode,\n UnaryExprNode,\n} from './ast.js'\n\n/**\n * Generic visitor interface for FilterNode AST traversal.\n * Implement this interface to transform or analyze OData filter expressions.\n *\n * @template T - The return type of each visit method\n */\nexport interface FilterVisitor<T> {\n visitBinaryExpr(node: BinaryExprNode): T\n visitUnaryExpr(node: UnaryExprNode): T\n visitFunctionCall(node: FunctionCallNode): T\n visitLambdaExpr(node: LambdaExprNode): T\n visitPropertyAccess(node: PropertyAccessNode): T\n visitLiteral(node: LiteralNode): T\n}\n\n/**\n * Dispatch a FilterNode to the appropriate visitor method.\n * Convenience function to avoid writing switch statements in every visitor.\n */\nexport function acceptVisitor<T>(node: FilterNode, visitor: FilterVisitor<T>): T {\n switch (node.kind) {\n case 'BinaryExpr':\n return visitor.visitBinaryExpr(node)\n case 'UnaryExpr':\n return visitor.visitUnaryExpr(node)\n case 'FunctionCall':\n return visitor.visitFunctionCall(node)\n case 'LambdaExpr':\n return visitor.visitLambdaExpr(node)\n case 'PropertyAccess':\n return visitor.visitPropertyAccess(node)\n case 'Literal':\n return visitor.visitLiteral(node)\n }\n}\n","/**\n * OData parser error types.\n * ODataParseError carries position information for diagnostic messages.\n */\n\n/**\n * Thrown when the lexer or parser encounters malformed OData query syntax.\n *\n * The `position` field indicates the character offset in the input string\n * where the error was detected. The `token` field (if available) holds\n * the token that triggered the error.\n *\n * Note: position info is intentionally included — the input is the user's own\n * query string, not server-internal data, so there is no information disclosure risk.\n */\nexport class ODataParseError extends Error {\n constructor(\n message: string,\n public readonly position: number,\n public readonly token: unknown = null,\n public readonly queryContext?: string,\n ) {\n super(message)\n this.name = 'ODataParseError'\n // Ensure instanceof works correctly when transpiled to ES5\n Object.setPrototypeOf(this, new.target.prototype)\n }\n\n /**\n * Create an ODataParseError with a context snippet extracted from the query string.\n * Extracts ~20 characters before and after the error position for diagnostic context.\n *\n * Per threat model T-12-05: context snippet shows only the user's own query input\n * (already known to them) — never includes stack traces or internal paths.\n *\n * @param message - Base error message\n * @param position - Character offset where the error was detected\n * @param queryString - The full query string for context extraction\n * @param token - Optional token that triggered the error\n */\n static withContext(\n message: string,\n position: number,\n queryString: string,\n token: unknown = null,\n ): ODataParseError {\n const contextRadius = 20\n const start = Math.max(0, position - contextRadius)\n const end = Math.min(queryString.length, position + contextRadius)\n\n const prefix = start > 0 ? '...' : ''\n const suffix = end < queryString.length ? '...' : ''\n const snippet = `${prefix}${queryString.slice(start, end)}${suffix}`\n\n const enrichedMessage = `${message} at position ${position}: ${snippet}`\n return new ODataParseError(enrichedMessage, position, token, snippet)\n }\n}\n","/**\n * OData v4 Lexer (tokenizer).\n * Converts an OData query expression string into a token stream.\n *\n * Character-by-character scanning with keyword disambiguation.\n * OData keyword recognition per Part 2 Section 5.1.1 ABNF grammar.\n */\n\nimport { ODataParseError } from './errors.js'\n\n/** All token kinds produced by the OData lexer */\nexport enum TokenKind {\n // Literals\n STRING_LITERAL = 'STRING_LITERAL',\n INT_LITERAL = 'INT_LITERAL',\n DECIMAL_LITERAL = 'DECIMAL_LITERAL',\n BOOL_LITERAL = 'BOOL_LITERAL',\n NULL_LITERAL = 'NULL_LITERAL',\n GUID_LITERAL = 'GUID_LITERAL',\n DATETIME_LITERAL = 'DATETIME_LITERAL',\n\n // Identifiers\n IDENTIFIER = 'IDENTIFIER',\n\n // Punctuation\n OPEN_PAREN = 'OPEN_PAREN',\n CLOSE_PAREN = 'CLOSE_PAREN',\n COMMA = 'COMMA',\n SLASH = 'SLASH',\n COLON = 'COLON',\n STAR = 'STAR',\n\n // Logical operators\n AND = 'AND',\n OR = 'OR',\n NOT = 'NOT',\n\n // Comparison operators\n EQ = 'EQ',\n NE = 'NE',\n LT = 'LT',\n LE = 'LE',\n GT = 'GT',\n GE = 'GE',\n HAS = 'HAS',\n IN = 'IN',\n\n // Arithmetic operators\n ADD = 'ADD',\n SUB = 'SUB',\n MUL = 'MUL',\n DIV = 'DIV',\n DIVBY = 'DIVBY',\n MOD = 'MOD',\n\n // End of input\n EOF = 'EOF',\n}\n\n/** Mapping of OData keyword strings to their TokenKind */\nconst KEYWORDS: Readonly<Record<string, TokenKind>> = {\n and: TokenKind.AND,\n or: TokenKind.OR,\n not: TokenKind.NOT,\n eq: TokenKind.EQ,\n ne: TokenKind.NE,\n lt: TokenKind.LT,\n le: TokenKind.LE,\n gt: TokenKind.GT,\n ge: TokenKind.GE,\n has: TokenKind.HAS,\n in: TokenKind.IN,\n add: TokenKind.ADD,\n sub: TokenKind.SUB,\n mul: TokenKind.MUL,\n div: TokenKind.DIV,\n divby: TokenKind.DIVBY,\n mod: TokenKind.MOD,\n}\n\n/** GUID pattern: 8-4-4-4-12 hex chars */\nconst GUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/\n\n/** A single lexer token with kind, value, and source position */\nexport interface Token {\n readonly kind: TokenKind\n readonly value: string | number | boolean | null\n readonly position: number\n}\n\nfunction isDigit(ch: string): boolean {\n return ch >= '0' && ch <= '9'\n}\n\nfunction isAlpha(ch: string): boolean {\n return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch === '_'\n}\n\nfunction isAlphaNum(ch: string): boolean {\n return isAlpha(ch) || isDigit(ch)\n}\n\n/**\n * Tokenize an OData query expression string into a token array.\n * The last token is always EOF.\n *\n * @param input - Raw OData expression string (e.g., \"Price gt 5 and Active eq true\")\n * @returns Array of tokens ending with EOF\n * @throws ODataParseError on unrecognized characters\n */\nexport function tokenize(input: string): Token[] {\n const tokens: Token[] = []\n let pos = 0\n\n while (pos < input.length) {\n // Skip whitespace\n if (input[pos] === ' ' || input[pos] === '\\t') {\n pos++\n continue\n }\n\n const start = pos\n const ch = input[pos]\n\n // Single-character punctuation\n if (ch === '(') {\n tokens.push({ kind: TokenKind.OPEN_PAREN, value: '(', position: start })\n pos++\n continue\n }\n if (ch === ')') {\n tokens.push({ kind: TokenKind.CLOSE_PAREN, value: ')', position: start })\n pos++\n continue\n }\n if (ch === ',') {\n tokens.push({ kind: TokenKind.COMMA, value: ',', position: start })\n pos++\n continue\n }\n if (ch === '/') {\n tokens.push({ kind: TokenKind.SLASH, value: '/', position: start })\n pos++\n continue\n }\n if (ch === ':') {\n tokens.push({ kind: TokenKind.COLON, value: ':', position: start })\n pos++\n continue\n }\n if (ch === '*') {\n tokens.push({ kind: TokenKind.STAR, value: '*', position: start })\n pos++\n continue\n }\n\n // String literal: starts with single quote\n if (ch === \"'\") {\n pos++ // consume opening quote\n let value = ''\n while (pos < input.length) {\n if (input[pos] === \"'\") {\n // Check for escaped quote: ''\n if (pos + 1 < input.length && input[pos + 1] === \"'\") {\n value += \"'\"\n pos += 2\n } else {\n pos++ // consume closing quote\n break\n }\n } else {\n value += input[pos]\n pos++\n }\n }\n tokens.push({ kind: TokenKind.STRING_LITERAL, value, position: start })\n continue\n }\n\n // Numeric literal: digit or negative number\n if (isDigit(ch)) {\n let numStr = ''\n while (pos < input.length && isDigit(input[pos])) {\n numStr += input[pos++]\n }\n // Check for decimal\n if (\n pos < input.length &&\n input[pos] === '.' &&\n pos + 1 < input.length &&\n isDigit(input[pos + 1])\n ) {\n numStr += '.'\n pos++\n while (pos < input.length && isDigit(input[pos])) {\n numStr += input[pos++]\n }\n tokens.push({ kind: TokenKind.DECIMAL_LITERAL, value: parseFloat(numStr), position: start })\n } else {\n // Could be start of GUID — check if followed by hex chars and dashes\n // e.g., 12345678-1234-...\n const remaining = input.slice(start)\n const guidMatch = GUID_RE.exec(remaining)\n if (guidMatch && remaining.length >= 36) {\n const guidStr = remaining.slice(0, 36)\n if (GUID_RE.test(guidStr)) {\n tokens.push({ kind: TokenKind.GUID_LITERAL, value: guidStr, position: start })\n pos = start + 36\n continue\n }\n }\n tokens.push({ kind: TokenKind.INT_LITERAL, value: parseInt(numStr, 10), position: start })\n }\n continue\n }\n\n // Identifier or keyword: starts with alpha or underscore\n if (isAlpha(ch)) {\n let word = ''\n // Collect alphanumeric + underscore + dash (for GUID-like patterns starting with letter)\n while (pos < input.length && (isAlphaNum(input[pos]) || input[pos] === '-')) {\n // Peek: if we see a dash, check if it looks like a GUID pattern\n if (input[pos] === '-') {\n // Only include dash if it looks like it's part of a GUID\n const soFar = word + input.slice(start, pos)\n void soFar\n // Check remaining from start\n const remaining = input.slice(start)\n if (GUID_RE.test(remaining.slice(0, 36))) {\n const guidStr = remaining.slice(0, 36)\n tokens.push({ kind: TokenKind.GUID_LITERAL, value: guidStr, position: start })\n pos = start + 36\n word = '' // signal we handled it\n break\n }\n // Not a GUID dash — stop consuming\n break\n }\n word += input[pos++]\n }\n\n // If word is empty, we consumed a GUID above — continue\n if (word === '') continue\n\n // Check for DateTimeOffset: word looks like date part \"2024-01-15T...\"\n // Actually DATETIME will start with digits, handled above. Skip here.\n\n // Boolean literals\n if (word === 'true') {\n tokens.push({ kind: TokenKind.BOOL_LITERAL, value: true, position: start })\n continue\n }\n if (word === 'false') {\n tokens.push({ kind: TokenKind.BOOL_LITERAL, value: false, position: start })\n continue\n }\n // Null literal\n if (word === 'null') {\n tokens.push({ kind: TokenKind.NULL_LITERAL, value: null, position: start })\n continue\n }\n\n // Keywords\n if (word in KEYWORDS) {\n tokens.push({ kind: KEYWORDS[word], value: word, position: start })\n continue\n }\n\n // Plain identifier\n tokens.push({ kind: TokenKind.IDENTIFIER, value: word, position: start })\n continue\n }\n\n // Unrecognized character\n throw new ODataParseError(`Unexpected character '${ch}' at position ${pos}`, pos)\n }\n\n tokens.push({ kind: TokenKind.EOF, value: null, position: pos })\n return tokens\n}\n","/**\n * OData v4 recursive descent parser with Pratt/precedence-climbing for binary operators.\n *\n * Architecture:\n * tokenize(input) -> Token[] -> Parser -> FilterNode AST\n *\n * Precedence table (lowest to highest binding power):\n * 1 — or (left-associative)\n * 2 — and (left-associative)\n * 3 — not (prefix, handled in parsePrimary)\n * 4 — eq, ne, lt, le, gt, ge, has, in (non-associative, treated as left)\n * 5 — add, sub (left-associative)\n * 6 — mul, div, divby, mod (left-associative)\n * 7 — neg unary minus (prefix, handled in parsePrimary)\n *\n * Security: Max nesting depth of 50 prevents stack overflow from deeply nested\n * parenthesized expressions per threat model T-01-09.\n */\n\nimport type {\n BinaryExprNode,\n BinaryOperator,\n ExpandItem,\n ExpandNode,\n FilterNode,\n FunctionCallNode,\n LambdaExprNode,\n LiteralNode,\n OrderByItem,\n PropertyAccessNode,\n QueryOptions,\n SelectItem,\n SelectNode,\n UnaryExprNode,\n} from './ast.js'\nimport { ODataParseError } from './errors.js'\nimport { tokenize, Token, TokenKind } from './lexer.js'\n\n/** Maximum nesting depth for parenthesized expressions (DoS protection, T-01-09) */\nconst MAX_NESTING_DEPTH = 50\n\n/**\n * Operator precedence levels.\n * Higher number = binds tighter.\n */\nconst PRECEDENCE: Partial<Record<TokenKind, number>> = {\n [TokenKind.OR]: 1,\n [TokenKind.AND]: 2,\n [TokenKind.EQ]: 4,\n [TokenKind.NE]: 4,\n [TokenKind.LT]: 4,\n [TokenKind.LE]: 4,\n [TokenKind.GT]: 4,\n [TokenKind.GE]: 4,\n [TokenKind.HAS]: 4,\n [TokenKind.IN]: 4,\n [TokenKind.ADD]: 5,\n [TokenKind.SUB]: 5,\n [TokenKind.MUL]: 6,\n [TokenKind.DIV]: 6,\n [TokenKind.DIVBY]: 6,\n [TokenKind.MOD]: 6,\n}\n\n/** Map from TokenKind to BinaryOperator string */\nconst BINARY_OP_MAP: Partial<Record<TokenKind, BinaryOperator>> = {\n [TokenKind.EQ]: 'eq',\n [TokenKind.NE]: 'ne',\n [TokenKind.LT]: 'lt',\n [TokenKind.LE]: 'le',\n [TokenKind.GT]: 'gt',\n [TokenKind.GE]: 'ge',\n [TokenKind.HAS]: 'has',\n [TokenKind.IN]: 'in',\n [TokenKind.AND]: 'and',\n [TokenKind.OR]: 'or',\n [TokenKind.ADD]: 'add',\n [TokenKind.SUB]: 'sub',\n [TokenKind.MUL]: 'mul',\n [TokenKind.DIV]: 'div',\n [TokenKind.DIVBY]: 'divby',\n [TokenKind.MOD]: 'mod',\n}\n\n/**\n * Known OData v4 built-in functions that the parser validates.\n * Prevents arbitrary function names from being silently accepted.\n */\nconst KNOWN_FUNCTIONS = new Set([\n 'startswith',\n 'endswith',\n 'contains',\n 'indexof',\n 'substring',\n 'length',\n 'tolower',\n 'toupper',\n 'trim',\n 'concat',\n 'matchesPattern',\n 'year',\n 'month',\n 'day',\n 'hour',\n 'minute',\n 'second',\n 'fractionalseconds',\n 'totaloffsetminutes',\n 'date',\n 'time',\n 'now',\n 'mindatetime',\n 'maxdatetime',\n 'totalseconds',\n 'round',\n 'floor',\n 'ceiling',\n 'cast',\n 'isof',\n 'geo.distance',\n 'geo.intersects',\n 'geo.length',\n])\n\n/** Lambda operators */\nconst LAMBDA_OPS = new Set(['any', 'all'])\n\n// ---------------------------------------------------------------------------\n// Parser class\n// ---------------------------------------------------------------------------\n\nclass Parser {\n private readonly tokens: Token[]\n private pos: number = 0\n private nestingDepth: number = 0\n\n constructor(tokens: Token[]) {\n this.tokens = tokens\n }\n\n private peek(): Token {\n return this.tokens[this.pos] ?? { kind: TokenKind.EOF, value: null, position: -1 }\n }\n\n private advance(): Token {\n const tok = this.tokens[this.pos]\n if (tok === undefined) {\n return { kind: TokenKind.EOF, value: null, position: -1 }\n }\n this.pos++\n return tok\n }\n\n private expect(kind: TokenKind): Token {\n const tok = this.peek()\n if (tok.kind !== kind) {\n throw new ODataParseError(\n `Expected ${kind} but got ${tok.kind} ('${String(tok.value)}') at position ${tok.position}`,\n tok.position,\n tok,\n )\n }\n return this.advance()\n }\n\n private isBinaryOperator(token: Token): boolean {\n return token.kind in PRECEDENCE\n }\n\n private getPrecedence(token: Token): number {\n return PRECEDENCE[token.kind] ?? 0\n }\n\n /**\n * Parse a filter expression using Pratt/precedence-climbing.\n * @param minPrecedence - Minimum binding power for the current operator level\n */\n parseExpression(minPrecedence: number = 0): FilterNode {\n let left = this.parsePrimary()\n\n while (this.isBinaryOperator(this.peek()) && this.getPrecedence(this.peek()) > minPrecedence) {\n const opToken = this.advance()\n const prec = this.getPrecedence(opToken)\n // All supported binary operators are left-associative → next min = same prec\n // (right-assoc would use prec - 1)\n const right = this.parseExpression(prec)\n const operator = BINARY_OP_MAP[opToken.kind]\n if (operator === undefined) {\n throw new ODataParseError(\n `Unknown binary operator token: ${opToken.kind}`,\n opToken.position,\n opToken,\n )\n }\n const node: BinaryExprNode = { kind: 'BinaryExpr', operator, left, right }\n left = node\n }\n\n return left\n }\n\n /**\n * Parse a primary expression (literals, identifiers, function calls, lambdas, parens, unary).\n */\n parsePrimary(): FilterNode {\n const token = this.peek()\n\n // Parenthesized expression\n if (token.kind === TokenKind.OPEN_PAREN) {\n this.advance()\n this.nestingDepth++\n if (this.nestingDepth > MAX_NESTING_DEPTH) {\n throw new ODataParseError(\n `Maximum nesting depth of ${MAX_NESTING_DEPTH} exceeded`,\n token.position,\n token,\n )\n }\n const expr = this.parseExpression(0)\n this.nestingDepth--\n this.expect(TokenKind.CLOSE_PAREN)\n return expr\n }\n\n // NOT prefix\n if (token.kind === TokenKind.NOT) {\n this.advance()\n // NOT precedence = 3, parse at level 3 so it captures comparisons\n const operand = this.parseExpression(3)\n const node: UnaryExprNode = { kind: 'UnaryExpr', operator: 'not', operand }\n return node\n }\n\n // Unary minus (neg)\n if (token.kind === TokenKind.SUB) {\n this.advance()\n const operand = this.parsePrimary()\n // If the operand is a literal number, negate it inline\n if (operand.kind === 'Literal' && operand.literalKind === 'number') {\n const lit: LiteralNode = {\n kind: 'Literal',\n literalKind: operand.literalKind,\n value: -(operand.value as number),\n }\n return lit\n }\n const node: UnaryExprNode = { kind: 'UnaryExpr', operator: 'neg', operand }\n return node\n }\n\n // Literals\n if (\n token.kind === TokenKind.STRING_LITERAL ||\n token.kind === TokenKind.INT_LITERAL ||\n token.kind === TokenKind.DECIMAL_LITERAL\n ) {\n this.advance()\n const kind = token.kind === TokenKind.STRING_LITERAL ? 'string' : 'number'\n const lit: LiteralNode = { kind: 'Literal', literalKind: kind, value: token.value }\n return lit\n }\n if (token.kind === TokenKind.BOOL_LITERAL) {\n this.advance()\n const lit: LiteralNode = { kind: 'Literal', literalKind: 'boolean', value: token.value }\n return lit\n }\n if (token.kind === TokenKind.NULL_LITERAL) {\n this.advance()\n const lit: LiteralNode = { kind: 'Literal', literalKind: 'null', value: null }\n return lit\n }\n if (token.kind === TokenKind.GUID_LITERAL) {\n this.advance()\n const lit: LiteralNode = {\n kind: 'Literal',\n literalKind: 'guid',\n value: token.value,\n }\n return lit\n }\n if (token.kind === TokenKind.DATETIME_LITERAL) {\n this.advance()\n const lit: LiteralNode = {\n kind: 'Literal',\n literalKind: 'dateTimeOffset',\n value: token.value,\n }\n return lit\n }\n\n // Identifier — could be property, function call, or lambda\n if (token.kind === TokenKind.IDENTIFIER) {\n return this.parseIdentifierExpression()\n }\n\n throw new ODataParseError(\n `Unexpected token ${token.kind} ('${String(token.value)}') at position ${token.position}`,\n token.position,\n token,\n )\n }\n\n /**\n * Parse an identifier-started expression:\n * 1. identifier( → function call\n * 2. identifier/identifier( → lambda (any/all)\n * 3. identifier/identifier/.../identifier → property access\n * 4. identifier → single-segment property access\n */\n private parseIdentifierExpression(): FilterNode {\n const firstToken = this.advance() // consume first identifier\n const firstName = firstToken.value as string\n\n // Check for function call: identifier(\n if (this.peek().kind === TokenKind.OPEN_PAREN) {\n return this.parseFunctionCall(firstName, firstToken.position)\n }\n\n // Check for slash — could be navigation property or lambda\n if (this.peek().kind === TokenKind.SLASH) {\n this.advance() // consume /\n const nextToken = this.peek()\n\n if (nextToken.kind !== TokenKind.IDENTIFIER) {\n throw new ODataParseError(\n `Expected identifier after '/' but got ${nextToken.kind} at position ${nextToken.position}`,\n nextToken.position,\n nextToken,\n )\n }\n\n const secondName = nextToken.value as string\n\n // Peek ahead: if next after identifier is '(' and it's a lambda op → lambda\n if (LAMBDA_OPS.has(secondName)) {\n // Check if this is lambda: secondName(\n const savedPos = this.pos\n this.advance() // consume secondName\n if (this.peek().kind === TokenKind.OPEN_PAREN) {\n return this.parseLambdaExpr(firstName, secondName, firstToken.position)\n }\n // Not lambda — restore and treat as navigation\n this.pos = savedPos\n }\n\n // Navigation property path: collect all /identifier segments\n this.advance() // consume secondName\n const path = [firstName, secondName]\n\n while (this.peek().kind === TokenKind.SLASH) {\n this.advance() // consume /\n const segToken = this.peek()\n if (segToken.kind !== TokenKind.IDENTIFIER) {\n throw new ODataParseError(\n `Expected identifier after '/' but got ${segToken.kind} at position ${segToken.position}`,\n segToken.position,\n segToken,\n )\n }\n path.push(segToken.value as string)\n this.advance()\n }\n\n const node: PropertyAccessNode = { kind: 'PropertyAccess', path }\n return node\n }\n\n // Plain single-segment property access\n const node: PropertyAccessNode = { kind: 'PropertyAccess', path: [firstName] }\n return node\n }\n\n /**\n * Parse a function call: name(arg1, arg2, ...)\n * Validates against the known built-in function list.\n */\n private parseFunctionCall(name: string, position: number): FilterNode {\n if (!KNOWN_FUNCTIONS.has(name)) {\n throw new ODataParseError(\n `Unknown function '${name}' at position ${position}. ` +\n `Known functions: ${Array.from(KNOWN_FUNCTIONS).join(', ')}`,\n position,\n )\n }\n\n this.expect(TokenKind.OPEN_PAREN)\n const args: FilterNode[] = []\n\n if (this.peek().kind !== TokenKind.CLOSE_PAREN) {\n args.push(this.parseExpression(0))\n while (this.peek().kind === TokenKind.COMMA) {\n this.advance() // consume comma\n args.push(this.parseExpression(0))\n }\n }\n\n this.expect(TokenKind.CLOSE_PAREN)\n const node: FunctionCallNode = { kind: 'FunctionCall', name, args }\n return node\n }\n\n /**\n * Parse a lambda expression: collection/operator(variable:predicate)\n * e.g., Tags/any(t:t/Name eq 'electronics')\n * e.g., Tags/any() — no-predicate variant\n */\n private parseLambdaExpr(collection: string, operator: string, position: number): FilterNode {\n if (!LAMBDA_OPS.has(operator)) {\n throw new ODataParseError(\n `Unknown lambda operator '${operator}' at position ${position}`,\n position,\n )\n }\n\n this.expect(TokenKind.OPEN_PAREN)\n\n // Check for no-predicate variant: collection/any()\n if (this.peek().kind === TokenKind.CLOSE_PAREN) {\n this.advance()\n const node: LambdaExprNode = {\n kind: 'LambdaExpr',\n operator: operator as 'any' | 'all',\n collection,\n variable: null,\n predicate: null,\n }\n return node\n }\n\n // Expect: variable:predicate\n const varToken = this.expect(TokenKind.IDENTIFIER)\n const variable = varToken.value as string\n this.expect(TokenKind.COLON)\n const predicate = this.parseExpression(0)\n this.expect(TokenKind.CLOSE_PAREN)\n\n const node: LambdaExprNode = {\n kind: 'LambdaExpr',\n operator: operator as 'any' | 'all',\n collection,\n variable,\n predicate,\n }\n return node\n }\n\n /**\n * Ensure we have consumed all tokens (no trailing garbage).\n */\n expectEOF(): void {\n const tok = this.peek()\n if (tok.kind !== TokenKind.EOF) {\n throw new ODataParseError(\n `Unexpected token ${tok.kind} ('${String(tok.value)}') at position ${tok.position} — expected end of expression`,\n tok.position,\n tok,\n )\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a standalone OData $filter expression string into a FilterNode AST.\n *\n * @param input - Raw filter expression (e.g., \"Price gt 5 and Active eq true\")\n * @returns Root FilterNode of the parsed expression\n * @throws ODataParseError on syntax errors\n */\nexport function parseFilter(input: string): FilterNode {\n if (!input.trim()) {\n throw new ODataParseError('Filter expression must not be empty', 0)\n }\n const tokens = tokenize(input)\n const parser = new Parser(tokens)\n const node = parser.parseExpression(0)\n parser.expectEOF()\n return node\n}\n\n/**\n * Parse an OData query string into a QueryOptions object.\n * Handles $filter, $orderby, $select, $top, $skip.\n * Unknown query options are silently ignored.\n *\n * @param queryString - Raw query string, with or without leading '?'\n * @returns Parsed QueryOptions (fields are optional — only present when found in query)\n * @throws ODataParseError on syntax errors\n */\nexport function parseQuery(queryString: string): QueryOptions {\n // Strip leading '?' if present\n const qs = queryString.startsWith('?') ? queryString.slice(1) : queryString\n\n if (!qs.trim()) return {}\n\n const parts = qs.split('&')\n let filter: FilterNode | undefined\n let orderBy: OrderByItem[] | undefined\n let select: SelectNode | undefined\n let top: number | undefined\n let skip: number | undefined\n let expand: ExpandNode | undefined\n\n for (const part of parts) {\n const lpart = part.toLowerCase()\n\n if (lpart.startsWith('$filter=')) {\n const val = part.slice('$filter='.length)\n filter = parseFilter(val)\n } else if (lpart.startsWith('$orderby=')) {\n const val = part.slice('$orderby='.length)\n orderBy = parseOrderBy(val)\n } else if (lpart.startsWith('$select=')) {\n const val = part.slice('$select='.length)\n select = parseSelect(val)\n } else if (lpart.startsWith('$top=')) {\n const val = part.slice('$top='.length)\n top = parseNonNegativeInt(val, '$top', 0)\n } else if (lpart.startsWith('$skip=')) {\n const val = part.slice('$skip='.length)\n skip = parseNonNegativeInt(val, '$skip', 0)\n } else if (lpart.startsWith('$expand=')) {\n const val = part.slice('$expand='.length)\n expand = parseExpand(val)\n }\n // Unknown options are silently ignored\n }\n\n return { filter, orderBy, select, top, skip, expand }\n}\n\n/**\n * Parse the value of a $orderby query option.\n * Format: expr [asc|desc] (, expr [asc|desc])*\n */\nfunction parseOrderBy(value: string): OrderByItem[] {\n if (!value.trim()) return []\n\n const items = value.split(',')\n return items.map((item) => {\n const trimmed = item.trim()\n // Check for trailing 'asc' or 'desc' keyword\n let exprStr = trimmed\n let direction: 'asc' | 'desc' = 'asc'\n\n // The direction keyword is at the end, separated by whitespace.\n // Use atomic-style match: find last space, check suffix — avoids \\s+ backtracking (ReDoS).\n const lower = trimmed.toLowerCase()\n if (/ desc$/i.test(lower)) {\n direction = 'desc'\n exprStr = trimmed.slice(0, -5).trimEnd()\n } else if (/ asc$/i.test(lower)) {\n direction = 'asc'\n exprStr = trimmed.slice(0, -4).trimEnd()\n }\n\n const tokens = tokenize(exprStr)\n const parser = new Parser(tokens)\n const expression = parser.parseExpression(0)\n parser.expectEOF()\n\n return { expression, direction }\n })\n}\n\n/**\n * Parse the value of a $select query option.\n * Format: * | path (, path)* where path is a slash-separated property name list\n */\nfunction parseSelect(value: string): SelectNode {\n if (value.trim() === '*') {\n return { all: true }\n }\n\n const paths = value.split(',')\n const items: SelectItem[] = paths.map((p) => {\n const path = p.trim().split('/').filter(Boolean)\n return { path }\n })\n\n return { items }\n}\n\n/**\n * Split an $expand value string by top-level commas only (not commas inside parentheses).\n * Tracks parenthesis depth to avoid splitting nested options like Items($filter=x),Customer.\n */\nfunction splitTopLevelCommas(value: string): string[] {\n const segments: string[] = []\n let depth = 0\n let start = 0\n\n for (let i = 0; i < value.length; i++) {\n const ch = value[i]\n if (ch === '(') {\n depth++\n } else if (ch === ')') {\n depth--\n } else if (ch === ',' && depth === 0) {\n segments.push(value.slice(start, i))\n start = i + 1\n }\n }\n\n segments.push(value.slice(start))\n return segments\n}\n\n/**\n * Parse the value of a $expand query option into an ExpandNode.\n * Handles simple (Customer), multi (Customer,Items), nested options (Items($filter=...)),\n * and recursive nested expand (Items($expand=Product)).\n *\n * Per D-07: Full nested $expand support.\n * Per D-08: Nested query options use semicolons as separators.\n * Per T-04-01: Inherits max nesting depth from recursive parseQuery calls.\n */\nfunction parseExpand(value: string): ExpandNode {\n const trimmed = value.trim()\n if (!trimmed) {\n return { items: [] }\n }\n\n const segments = splitTopLevelCommas(trimmed)\n const items: ExpandItem[] = segments\n .map((seg) => seg.trim())\n .filter((seg) => seg.length > 0)\n .map((seg) => {\n const parenIdx = seg.indexOf('(')\n if (parenIdx === -1) {\n // Simple: just a navigation property name\n return { navigationProperty: seg.trim() } satisfies ExpandItem\n }\n\n // Has nested options: NavProp(options)\n const navigationProperty = seg.slice(0, parenIdx).trim()\n // Extract content between outer parens — last char should be ')'\n const innerContent = seg.slice(parenIdx + 1, seg.length - 1)\n\n // Replace semicolons with & to convert nested OData options to query string format\n const nestedQuery = innerContent.replace(/;/g, '&')\n\n // Recursively parse nested query options\n const nested = parseQuery(nestedQuery)\n\n const item: ExpandItem = {\n navigationProperty,\n ...(nested.filter !== undefined && { filter: nested.filter }),\n ...(nested.select !== undefined && { select: nested.select }),\n ...(nested.orderBy !== undefined && { orderBy: nested.orderBy }),\n ...(nested.top !== undefined && { top: nested.top }),\n ...(nested.skip !== undefined && { skip: nested.skip }),\n ...(nested.expand !== undefined && { expand: nested.expand }),\n }\n return item\n })\n\n return { items }\n}\n\n/**\n * Parse and validate a non-negative integer value for $top or $skip.\n * @throws ODataParseError if the value is not a valid non-negative integer\n */\nfunction parseNonNegativeInt(value: string, paramName: string, position: number): number {\n const trimmed = value.trim()\n // Must be digits only (no decimals, no negative sign)\n if (!/^\\d+$/.test(trimmed)) {\n throw new ODataParseError(\n `${paramName} must be a non-negative integer, got '${trimmed}'`,\n position,\n )\n }\n const n = parseInt(trimmed, 10)\n if (n < 0) {\n throw new ODataParseError(`${paramName} must be a non-negative integer, got ${n}`, position)\n }\n return n\n}\n","/**\n * OData $search expression parser.\n *\n * Grammar (simplified OData v4 Part 2 Section 4.6):\n * searchExpr = searchTerm | searchBinary | searchUnary\n * searchBinary = searchExpr ('AND' | 'OR') searchExpr\n * searchUnary = 'NOT' searchTerm\n * searchTerm = DQUOTE searchPhrase DQUOTE | searchWord\n * searchPhrase = <any sequence of chars except DQUOTE>\n * searchWord = <sequence of non-whitespace chars, not AND/OR/NOT>\n *\n * Operator precedence (lowest to highest):\n * 1 — OR\n * 2 — AND (explicit and implicit)\n * 3 — NOT (prefix)\n *\n * Security: MAX_SEARCH_DEPTH prevents deeply nested AND/OR trees from\n * causing stack overflow (threat T-11-02).\n */\n\nimport type { SearchNode, SearchTermNode, SearchBinaryNode } from './ast.js'\nimport { ODataParseError } from './errors.js'\n\n/** Maximum number of binary operations allowed in a search expression (DoS protection). */\nconst MAX_SEARCH_DEPTH = 20\n\n// ---------------------------------------------------------------------------\n// Tokenizer\n// ---------------------------------------------------------------------------\n\ntype SearchToken =\n | { type: 'TERM'; value: string; negated: boolean }\n | { type: 'AND' }\n | { type: 'OR' }\n\n/**\n * Tokenizes a $search query string into a flat list of tokens.\n * Handles double-quoted phrases and keyword recognition.\n */\nfunction tokenizeSearch(input: string): SearchToken[] {\n const tokens: SearchToken[] = []\n let i = 0\n const trimmed = input.trim()\n\n while (i < trimmed.length) {\n // Skip whitespace\n while (i < trimmed.length && trimmed[i] === ' ') i++\n if (i >= trimmed.length) break\n\n // Double-quoted phrase\n if (trimmed[i] === '\"') {\n i++ // skip opening quote\n let phrase = ''\n while (i < trimmed.length && trimmed[i] !== '\"') {\n phrase += trimmed[i++]\n }\n if (i < trimmed.length) i++ // skip closing quote\n tokens.push({ type: 'TERM', value: phrase, negated: false })\n continue\n }\n\n // Read a word (until whitespace)\n let word = ''\n while (i < trimmed.length && trimmed[i] !== ' ') {\n word += trimmed[i++]\n }\n\n if (word === 'AND') {\n tokens.push({ type: 'AND' })\n } else if (word === 'OR') {\n tokens.push({ type: 'OR' })\n } else if (word === 'NOT') {\n // Peek ahead: skip whitespace, then read the next term\n while (i < trimmed.length && trimmed[i] === ' ') i++\n if (i >= trimmed.length) {\n throw new ODataParseError('NOT operator must be followed by a search term', i)\n }\n\n if (trimmed[i] === '\"') {\n // Quoted phrase after NOT\n i++ // skip opening quote\n let phrase = ''\n while (i < trimmed.length && trimmed[i] !== '\"') {\n phrase += trimmed[i++]\n }\n if (i < trimmed.length) i++ // skip closing quote\n tokens.push({ type: 'TERM', value: phrase, negated: true })\n } else {\n // Bare word after NOT\n let nextWord = ''\n while (i < trimmed.length && trimmed[i] !== ' ') {\n nextWord += trimmed[i++]\n }\n tokens.push({ type: 'TERM', value: nextWord, negated: true })\n }\n } else {\n tokens.push({ type: 'TERM', value: word, negated: false })\n }\n }\n\n return tokens\n}\n\n// ---------------------------------------------------------------------------\n// Parser — converts token list to SearchNode AST\n// ---------------------------------------------------------------------------\n\n/**\n * Build a SearchNode from a flat token list using a recursive-descent approach.\n * OR has lower precedence than AND (AND binds tighter).\n */\nfunction buildSearchNode(tokens: SearchToken[], depth: number): SearchNode {\n if (depth > MAX_SEARCH_DEPTH) {\n throw new ODataParseError(\n `$search expression exceeds maximum depth of ${MAX_SEARCH_DEPTH} — expression too complex`,\n 0,\n )\n }\n\n if (tokens.length === 0) {\n throw new ODataParseError('Empty $search expression — at least one search term is required', 0)\n }\n\n // Split on top-level OR (lowest precedence)\n const orIdx = findTopLevelOperator(tokens, 'OR')\n if (orIdx !== -1) {\n const left = buildSearchNode(tokens.slice(0, orIdx), depth + 1)\n const right = buildSearchNode(tokens.slice(orIdx + 1), depth + 1)\n const node: SearchBinaryNode = { kind: 'SearchBinary', operator: 'OR', left, right }\n return node\n }\n\n // Split on top-level AND (explicit)\n const andIdx = findTopLevelOperator(tokens, 'AND')\n if (andIdx !== -1) {\n const left = buildSearchNode(tokens.slice(0, andIdx), depth + 1)\n const right = buildSearchNode(tokens.slice(andIdx + 1), depth + 1)\n const node: SearchBinaryNode = { kind: 'SearchBinary', operator: 'AND', left, right }\n return node\n }\n\n // No explicit AND/OR — multiple TERM tokens means implicit AND (left-associative)\n const termTokens = tokens.filter((t) => t.type === 'TERM')\n if (termTokens.length > 1) {\n // Build left-associative tree: ((a AND b) AND c)\n let left: SearchNode = termToNode(\n termTokens[0] as { type: 'TERM'; value: string; negated: boolean },\n )\n for (let i = 1; i < termTokens.length; i++) {\n const right = termToNode(termTokens[i] as { type: 'TERM'; value: string; negated: boolean })\n if (depth + i > MAX_SEARCH_DEPTH) {\n throw new ODataParseError(\n `$search expression exceeds maximum depth of ${MAX_SEARCH_DEPTH}`,\n 0,\n )\n }\n const node: SearchBinaryNode = { kind: 'SearchBinary', operator: 'AND', left, right }\n left = node\n }\n return left\n }\n\n // Single TERM\n if (tokens.length === 1 && tokens[0].type === 'TERM') {\n return termToNode(tokens[0] as { type: 'TERM'; value: string; negated: boolean })\n }\n\n throw new ODataParseError(`Unexpected token sequence in $search expression`, 0)\n}\n\nfunction termToNode(token: { type: 'TERM'; value: string; negated: boolean }): SearchTermNode {\n const node: SearchTermNode = token.negated\n ? { kind: 'SearchTerm', value: token.value, negated: true }\n : { kind: 'SearchTerm', value: token.value }\n return node\n}\n\n/**\n * Find the index of the last top-level occurrence of the given operator type.\n * Returns -1 if not found.\n * \"Top-level\" means outside of any grouping (search doesn't use parens, so all are top-level).\n */\nfunction findTopLevelOperator(tokens: SearchToken[], opType: 'AND' | 'OR'): number {\n // Find the rightmost occurrence (for left-associativity, we want the last one at top level)\n // Actually for left-associativity we want the last operator at this level so the right operand\n // is a single term while the left operand gets recursively built.\n // But to handle precedence correctly: OR has lower precedence, so we split on OR first.\n // Within the OR split, we want to keep left-associativity.\n // So: find the LAST occurrence of the operator (rightmost split gives left-associative tree).\n let lastIdx = -1\n for (let i = 0; i < tokens.length; i++) {\n if (tokens[i].type === opType) {\n lastIdx = i\n }\n }\n return lastIdx\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Parse an OData $search query string into a SearchNode AST.\n *\n * @param input - The raw $search query string value (without the $search= prefix)\n * @throws ODataParseError if the input is empty, malformed, or exceeds MAX_SEARCH_DEPTH\n */\nexport function parseSearch(input: string): SearchNode {\n const trimmed = input.trim()\n if (!trimmed) {\n throw new ODataParseError('Empty $search expression — at least one search term is required', 0)\n }\n\n const tokens = tokenizeSearch(trimmed)\n\n if (tokens.length === 0) {\n throw new ODataParseError('Empty $search expression — at least one search term is required', 0)\n }\n\n // Count total binary operators to detect DoS early (each AND/OR creates one tree level)\n const operatorCount = tokens.filter((t) => t.type === 'AND' || t.type === 'OR').length\n // Implicit AND operators = TERM count - 1 when there are no explicit operators\n const termCount = tokens.filter((t) => t.type === 'TERM').length\n const explicitOps = operatorCount\n const implicitAnds = explicitOps === 0 && termCount > 1 ? termCount - 1 : 0\n const totalOps = explicitOps + implicitAnds\n\n if (totalOps >= MAX_SEARCH_DEPTH) {\n throw new ODataParseError(\n `$search expression exceeds maximum depth of ${MAX_SEARCH_DEPTH} — expression too complex`,\n 0,\n )\n }\n\n return buildSearchNode(tokens, 0)\n}\n","/**\n * OData $apply transformation pipeline parser.\n *\n * Supports the following transformation steps (OData v4 Aggregation Extension):\n * - aggregate(...) — aggregate expressions over the current set\n * - groupby((...), aggregate(...)) — partition and aggregate\n * - filter(...) — filter the current set (only BEFORE aggregate/groupby steps)\n *\n * Security:\n * - Aggregate aliases are validated against /^[A-Za-z_][A-Za-z0-9_]*$/ to prevent\n * SQL injection when aliases are interpolated into query strings (T-11-01).\n * - Post-groupby filter steps are rejected since HAVING translation is not supported (A5).\n */\n\nimport type {\n ApplyNode,\n ApplyStep,\n ApplyFilterStep,\n ApplyGroupByStep,\n ApplyAggregateStep,\n AggregateExpression,\n} from './ast.js'\nimport { ODataParseError } from './errors.js'\nimport { parseFilter } from './parser.js'\n\n/** Regex for valid SQL-safe identifiers (prevents SQL injection via aliases). */\nconst ALIAS_REGEX = /^[A-Za-z_][A-Za-z0-9_]*$/\n\n// ---------------------------------------------------------------------------\n// Pipeline splitter\n// ---------------------------------------------------------------------------\n\n/**\n * Split a $apply string on '/' characters that are at parenthesis depth 0.\n * This correctly handles filter(a/b gt 0) without splitting inside the parens.\n */\nexport function splitApplyPipeline(value: string): string[] {\n const steps: string[] = []\n let depth = 0\n let current = ''\n\n for (let i = 0; i < value.length; i++) {\n const ch = value[i]\n if (ch === '(') {\n depth++\n current += ch\n } else if (ch === ')') {\n depth--\n current += ch\n } else if (ch === '/' && depth === 0) {\n steps.push(current.trim())\n current = ''\n } else {\n current += ch\n }\n }\n\n if (current.trim()) {\n steps.push(current.trim())\n }\n\n return steps\n}\n\n// ---------------------------------------------------------------------------\n// Aggregate expression parser\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a comma-separated list of aggregate expressions.\n * Handles both:\n * - `property with method as alias`\n * - `$count as alias` (shorthand for count of records)\n */\nfunction parseAggregateExpressions(inner: string): AggregateExpression[] {\n const parts = splitTopLevelCommas(inner)\n return parts.map((part) => parseOneAggregateExpression(part.trim()))\n}\n\nfunction parseOneAggregateExpression(expr: string): AggregateExpression {\n // Handle $count as alias\n const countMatch = /^\\$count\\s+as\\s+(\\S+)$/.exec(expr)\n if (countMatch) {\n const alias = countMatch[1]\n validateAlias(alias)\n return { property: '$count', method: 'count', alias }\n }\n\n // Handle property with method as alias\n const withMatch = /^(\\S+)\\s+with\\s+(\\w+)\\s+as\\s+(\\S+)$/.exec(expr)\n if (withMatch) {\n const [, property, methodRaw, alias] = withMatch\n const method = methodRaw.toLowerCase()\n if (!isValidMethod(method)) {\n throw new ODataParseError(\n `Unknown aggregate method '${method}'. Valid methods: sum, count, avg, min, max, countdistinct`,\n 0,\n )\n }\n validateAlias(alias)\n return { property, method, alias }\n }\n\n throw new ODataParseError(\n `Invalid aggregate expression '${expr}'. Expected 'property with method as alias' or '$count as alias'`,\n 0,\n )\n}\n\nfunction isValidMethod(method: string): method is AggregateExpression['method'] {\n return ['sum', 'count', 'avg', 'min', 'max', 'countdistinct'].includes(method)\n}\n\nfunction validateAlias(alias: string): void {\n if (!ALIAS_REGEX.test(alias)) {\n throw new ODataParseError(\n `Invalid aggregate alias '${alias}'. Aliases must match /^[A-Za-z_][A-Za-z0-9_]*$/ to prevent SQL injection`,\n 0,\n )\n }\n}\n\n// ---------------------------------------------------------------------------\n// Step parsers\n// ---------------------------------------------------------------------------\n\nfunction parseFilterStep(inner: string): ApplyFilterStep {\n const filter = parseFilter(inner)\n return { kind: 'ApplyFilter', filter }\n}\n\nfunction parseAggregateStep(inner: string): ApplyAggregateStep {\n const expressions = parseAggregateExpressions(inner)\n return { kind: 'ApplyAggregate', expressions }\n}\n\nfunction parseGroupByStep(inner: string): ApplyGroupByStep {\n // groupby((prop1,prop2,...),aggregate(...))\n // or groupby((prop1,prop2,...))\n //\n // Extract first parenthesized group (properties), then optionally the aggregate part.\n if (!inner.startsWith('(')) {\n throw new ODataParseError(\n `Invalid groupby() syntax: expected '(' to start property list, got '${inner[0]}'`,\n 0,\n )\n }\n\n // Find the matching closing paren for the property list\n let depth = 0\n let propEnd = -1\n for (let i = 0; i < inner.length; i++) {\n if (inner[i] === '(') depth++\n else if (inner[i] === ')') {\n depth--\n if (depth === 0) {\n propEnd = i\n break\n }\n }\n }\n\n if (propEnd === -1) {\n throw new ODataParseError('Unclosed parenthesis in groupby() property list', 0)\n }\n\n const propList = inner.slice(1, propEnd) // contents inside the first (...)\n const properties = propList\n .split(',')\n .map((p) => p.trim())\n .filter(Boolean)\n\n // Check for optional aggregate part\n const rest = inner.slice(propEnd + 1).trim()\n let aggregate: readonly AggregateExpression[] | undefined\n\n if (rest.startsWith(',aggregate(')) {\n const aggInner = rest.slice(',aggregate('.length, rest.length - 1)\n aggregate = parseAggregateExpressions(aggInner)\n } else if (rest.startsWith(',')) {\n throw new ODataParseError(`Unexpected content after groupby() property list: '${rest}'`, 0)\n }\n\n return { kind: 'ApplyGroupBy', properties, aggregate }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Split a string on ',' characters at parenthesis depth 0.\n * Reusable utility — same algorithm as pipeline splitter but for commas.\n */\nfunction splitTopLevelCommas(value: string): string[] {\n const parts: string[] = []\n let depth = 0\n let current = ''\n\n for (let i = 0; i < value.length; i++) {\n const ch = value[i]\n if (ch === '(') {\n depth++\n current += ch\n } else if (ch === ')') {\n depth--\n current += ch\n } else if (ch === ',' && depth === 0) {\n parts.push(current.trim())\n current = ''\n } else {\n current += ch\n }\n }\n\n if (current.trim()) {\n parts.push(current.trim())\n }\n\n return parts\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Parse an OData $apply transformation pipeline into an ApplyNode AST.\n *\n * Steps are separated by '/' at depth 0. Each step is one of:\n * - filter(...)\n * - aggregate(...)\n * - groupby((...),aggregate(...))\n *\n * @param input - The raw $apply query string value (without the $apply= prefix)\n * @throws ODataParseError for empty input, unrecognized steps, invalid aliases,\n * or post-groupby filter steps (HAVING not supported)\n */\nexport function parseApply(input: string): ApplyNode {\n const trimmed = input.trim()\n if (!trimmed) {\n throw new ODataParseError(\n 'Empty $apply expression — at least one transformation step is required',\n 0,\n )\n }\n\n const stepStrings = splitApplyPipeline(trimmed)\n const steps: ApplyStep[] = []\n let seenAggregation = false // track if we've seen aggregate() or groupby()\n\n for (const stepStr of stepStrings) {\n // Determine step type by prefix\n if (stepStr.startsWith('filter(') && stepStr.endsWith(')')) {\n if (seenAggregation) {\n throw new ODataParseError(\n 'Post-groupby filter (HAVING) is not supported in this version. Place filter() steps before groupby()',\n 0,\n )\n }\n const inner = stepStr.slice('filter('.length, stepStr.length - 1)\n steps.push(parseFilterStep(inner))\n } else if (stepStr.startsWith('aggregate(') && stepStr.endsWith(')')) {\n seenAggregation = true\n const inner = stepStr.slice('aggregate('.length, stepStr.length - 1)\n steps.push(parseAggregateStep(inner))\n } else if (stepStr.startsWith('groupby(') && stepStr.endsWith(')')) {\n seenAggregation = true\n const inner = stepStr.slice('groupby('.length, stepStr.length - 1)\n steps.push(parseGroupByStep(inner))\n } else {\n // Extract the step name for error message\n const nameMatch = /^(\\w+)\\(/.exec(stepStr)\n const stepName = nameMatch ? nameMatch[1] : stepStr\n throw new ODataParseError(\n `Unrecognized $apply transformation step '${stepName}'. Supported steps: filter, aggregate, groupby`,\n 0,\n )\n }\n }\n\n return { steps }\n}\n","import { Injectable } from '@nestjs/common'\nimport type { EdmEntityType } from './edm-entity-type.js'\nimport type { EdmEntitySet } from './edm-entity-set.js'\nimport type { ODataEntitySecurityOptions } from '../query/odata-query.types.js'\n\n/**\n * EdmRegistry — NestJS injectable singleton that stores all registered\n * OData entity types and entity sets.\n *\n * Per threat model T-02-01: throws on duplicate registration to prevent\n * silent override of entity types (Tampering mitigation).\n */\n@Injectable()\nexport class EdmRegistry {\n private readonly entityTypes = new Map<string, EdmEntityType>()\n private readonly entitySets = new Map<string, EdmEntitySet>()\n /** Per-entity security option overrides keyed by entitySetName (per D-07) */\n private readonly entitySecurityOptions = new Map<string, ODataEntitySecurityOptions>()\n\n /**\n * Register an entity type and its corresponding entity set.\n *\n * Idempotent: if the same entity type name is already registered (e.g. from multiple\n * ODataTypeOrmModule.forFeature() calls in different feature modules), the registration\n * is silently skipped. This supports the common pattern of importing forFeature() in\n * both a root AppModule and a feature module for the same entity.\n *\n * Throws only if a DIFFERENT entity type is registered under the same name (name collision).\n */\n register(entityType: EdmEntityType, entitySet: EdmEntitySet): void {\n if (this.entityTypes.has(entityType.name)) {\n // Idempotent: same name already registered — skip silently\n return\n }\n this.entityTypes.set(entityType.name, entityType)\n this.entitySets.set(entitySet.name, entitySet)\n }\n\n /** Retrieve a registered entity type by name. Returns undefined if not found. */\n getEntityType(name: string): EdmEntityType | undefined {\n return this.entityTypes.get(name)\n }\n\n /** Retrieve a registered entity set by name. Returns undefined if not found. */\n getEntitySet(name: string): EdmEntitySet | undefined {\n return this.entitySets.get(name)\n }\n\n /** All registered entity types as a read-only map. */\n getEntityTypes(): ReadonlyMap<string, EdmEntityType> {\n return this.entityTypes\n }\n\n /** All registered entity sets as a read-only map. */\n getEntitySets(): ReadonlyMap<string, EdmEntitySet> {\n return this.entitySets\n }\n\n /**\n * Store per-entity security option overrides for a given entity set name.\n * These override global ODataModuleResolvedOptions values (per D-07).\n */\n setEntitySecurityOptions(entitySetName: string, options: ODataEntitySecurityOptions): void {\n this.entitySecurityOptions.set(entitySetName, options)\n }\n\n /**\n * Retrieve per-entity security options for a given entity set name.\n * Returns undefined if no overrides have been set.\n */\n getEntitySecurityOptions(entitySetName: string): ODataEntitySecurityOptions | undefined {\n return this.entitySecurityOptions.get(entitySetName)\n }\n}\n","import pluralize from 'pluralize'\n\n/**\n * Pluralize an entity class name to produce an EntitySet name.\n * Uses the `pluralize` library which handles irregular forms (Person → People,\n * Category → Categories, Index → Indices, etc.).\n *\n * @param name - Entity class name in PascalCase (e.g., 'Product', 'OrderItem')\n * @returns Pluralized entity set name (e.g., 'Products', 'OrderItems')\n */\nexport function pluralizeEntityName(name: string): string {\n return pluralize(name)\n}\n","import type { EdmEntityConfig } from '../edm/edm-entity-set.js'\n\n/** A generic constructor type representing an entity class */\nexport type EntityClass = new (...args: unknown[]) => unknown\n\n/**\n * IEdmDeriver — adapter seam interface for ORM-specific EDM derivation.\n * Implementations live in adapter packages (e.g., @nestjs-odata/typeorm).\n * Core has zero ORM dependencies — adapters inject their implementation\n * via the EDM_DERIVER token.\n */\nexport interface IEdmDeriver {\n deriveEntityTypes(entityClasses: EntityClass[]): EdmEntityConfig[]\n}\n\n/** NestJS injection token for IEdmDeriver */\nexport const EDM_DERIVER = Symbol('EDM_DERIVER')\n","import type { EdmEntityType } from '../edm/edm-entity-type.js'\nimport type { ODataQuery, ODataQueryResult } from '../query/odata-query.types.js'\n\n/**\n * IQueryTranslator — adapter seam interface for ORM-specific query translation.\n *\n * Implementations live in adapter packages (e.g., @nestjs-odata/typeorm).\n * The two-method design (translate + execute) separates AST-to-query translation\n * from query execution, enabling independent unit testing of each step.\n *\n * Generic parameter TQuery is the ORM-specific query type (e.g., TypeORM SelectQueryBuilder).\n */\nexport interface IQueryTranslator<TQuery = unknown> {\n /** Translate a typed OData query AST into an ORM-specific query object */\n translate(query: ODataQuery, entityType: EdmEntityType): TQuery\n /** Execute the translated query and return structured results */\n execute(translatedQuery: TQuery, includeCount: boolean): Promise<ODataQueryResult>\n}\n\n/** NestJS injection token for IQueryTranslator */\nexport const QUERY_TRANSLATOR = Symbol('QUERY_TRANSLATOR')\n","/**\n * Injection token for IETagProvider.\n * Use this token when registering or injecting the ETag provider via NestJS DI.\n */\nexport const ETAG_PROVIDER = Symbol('ETAG_PROVIDER')\n\n/**\n * Interface for computing and validating ETags for OData entities.\n *\n * Implementations are adapter-specific (e.g., TypeOrmETagProvider).\n * Core uses this interface without importing any ORM code.\n */\nexport interface IETagProvider {\n /**\n * Get the ETag column name for an entity set.\n * Returns undefined if the entity set is not ETag-enabled\n * (no @UpdateDateColumn or @ODataETag decorator found).\n */\n getETagColumn(entitySetName: string): string | undefined\n\n /**\n * Compute a weak ETag string from an entity's ETag column value.\n * Format: W/\"<value>\"\n */\n computeETag(entity: Record<string, unknown>, etagColumn: string): string\n\n /**\n * Validate an If-Match header value against the current entity state.\n * Returns true if the ETag matches (proceed with mutation),\n * false if the ETag is stale (return 412).\n */\n validateIfMatch(\n ifMatchValue: string,\n entity: Record<string, unknown>,\n etagColumn: string,\n ): boolean\n}\n","import type { SearchNode } from '../parser/ast.js'\n\n/**\n * Injection token for ISearchProvider.\n * Use this token when registering or injecting the search provider via NestJS DI.\n */\nexport const SEARCH_PROVIDER = Symbol('SEARCH_PROVIDER')\n\n/**\n * Interface for translating a parsed $search expression into a SQL condition.\n *\n * Implementations are adapter-specific (e.g., TypeOrmSearchProvider).\n * Core uses this interface without importing any ORM code.\n *\n * Returns null if the search cannot be applied (e.g., no searchable fields defined).\n */\nexport interface ISearchProvider {\n /**\n * Build a SQL WHERE condition fragment from a parsed $search expression.\n *\n * @param searchNode - The parsed $search AST node\n * @param entitySetName - The entity set being queried\n * @param alias - The query builder alias for the entity table\n * @returns An object with the SQL condition string and named parameters,\n * or null if search cannot be applied\n */\n buildSearchCondition(\n searchNode: SearchNode,\n entitySetName: string,\n alias: string,\n ): { condition: string; params: Record<string, unknown> } | null\n}\n","/**\n * DI injection token for the array of EdmEntityConfig objects\n * registered via ODataModule.forFeature().\n */\nexport const EDM_ENTITY_CONFIGS = Symbol('EDM_ENTITY_CONFIGS')\n\n/**\n * DI injection token for the fully-resolved ODataModuleOptions (with defaults applied).\n * Defined here (not in odata.module.ts) to avoid circular imports with metadata builders.\n */\nexport const ODATA_MODULE_OPTIONS = Symbol('ODATA_MODULE_OPTIONS')\n","import { Inject, Injectable } from '@nestjs/common'\nimport type { ArgumentMetadata, PipeTransform } from '@nestjs/common'\nimport { parseQuery } from '../parser/parser.js'\nimport { parseSearch } from '../parser/search-parser.js'\nimport { parseApply } from '../parser/apply-parser.js'\nimport { EdmRegistry } from '../edm/edm-registry.js'\nimport { ODATA_MODULE_OPTIONS } from '../tokens.js'\nimport type { ODataModuleResolvedOptions } from '../odata.module.js'\nimport type { ODataQuery } from './odata-query.types.js'\nimport type { EdmEntityType } from '../edm/edm-entity-type.js'\nimport type { FilterNode, SelectItem, ExpandNode, SearchNode, ApplyNode } from '../parser/ast.js'\nimport { ODataValidationError } from './odata-validation.error.js'\n\n/**\n * ODataQueryPipe — NestJS PipeTransform that converts raw Express query params\n * (req.query as Record<string, string>) into a typed, validated ODataQuery.\n *\n * Per threat model T-03-01: validates all $filter/$select/$orderby property\n * references against EdmRegistry. Unknown properties throw ODataValidationError.\n *\n * Per threat model T-03-03: clamps $top to maxTop from ODataModuleResolvedOptions.\n *\n * Zero TypeORM imports — per PKG-01 architecture constraint.\n */\n@Injectable()\nexport class ODataQueryPipe implements PipeTransform<Record<string, string>, ODataQuery> {\n constructor(\n private readonly edmRegistry: EdmRegistry,\n @Inject(ODATA_MODULE_OPTIONS) private readonly options: ODataModuleResolvedOptions,\n ) {}\n\n transform(value: Record<string, string>, metadata: ArgumentMetadata): ODataQuery {\n // metadata.data carries the entitySetName passed by @ODataQuery(entitySetName)\n const entitySetName = metadata.data ?? ''\n\n // Reconstruct query string from the Record entries (handling $count, $search, $apply separately)\n const parts: string[] = []\n let count = false\n let search: SearchNode | undefined\n let apply: ApplyNode | undefined\n\n for (const [key, val] of Object.entries(value)) {\n const lkey = key.toLowerCase()\n if (lkey === '$count') {\n count = val === 'true'\n } else if (lkey === '$search') {\n search = parseSearch(decodeURIComponent(val))\n } else if (lkey === '$apply') {\n apply = parseApply(decodeURIComponent(val))\n } else {\n parts.push(`${key}=${val}`)\n }\n }\n\n const queryString = parts.join('&')\n\n // Parse into QueryOptions (filter AST, select, orderBy, top, skip)\n const parsed = parseQuery(queryString)\n\n // Enforce maxTop: reject (HTTP 400) when $top exceeds the limit (per D-05, SEC-01)\n // Per-entity override takes precedence over global options (per D-07)\n const entitySecOpts = this.edmRegistry.getEntitySecurityOptions(entitySetName)\n const effectiveMaxTop = entitySecOpts?.maxTop ?? this.options.maxTop\n const top = parsed.top\n if (top !== undefined && top > effectiveMaxTop) {\n throw new ODataValidationError(\n `$top value ${top} exceeds maximum of ${effectiveMaxTop}`,\n entitySetName,\n '$top',\n )\n }\n\n // Validate field names against EdmRegistry when we have an entity set\n if (entitySetName) {\n const entitySet = this.edmRegistry.getEntitySet(entitySetName)\n if (entitySet) {\n const entityType = this.edmRegistry.getEntityType(entitySet.entityTypeName)\n if (entityType) {\n // Validate $filter property references\n if (parsed.filter) {\n this.validateFilterNode(parsed.filter, entityType)\n }\n\n // Validate $select field names (per T-03-01)\n if (parsed.select?.items) {\n for (const item of parsed.select.items) {\n this.validateSelectItem(item, entityType)\n }\n }\n\n // Validate $orderby expression property references\n if (parsed.orderBy) {\n for (const orderItem of parsed.orderBy) {\n this.validateFilterNode(orderItem.expression, entityType)\n }\n }\n\n // Validate $expand navigation property names (T-04-09, D-10)\n if (parsed.expand?.items) {\n this.validateExpandNode(parsed.expand, entityType)\n }\n }\n }\n }\n\n return {\n filter: parsed.filter,\n select: parsed.select,\n orderBy: parsed.orderBy,\n top,\n skip: parsed.skip,\n count: count || undefined,\n entitySetName,\n expand: parsed.expand,\n search,\n apply,\n }\n }\n\n /**\n * Recursively walk a FilterNode tree and throw ODataValidationError for any\n * PropertyAccessNode whose path[0] is not in entityType.properties.\n *\n * Per D-10: validates property references before any DB query is constructed.\n * Per T-03-01: unknown properties throw ODataValidationError (not a generic error).\n */\n private validateFilterNode(node: FilterNode, entityType: EdmEntityType): void {\n switch (node.kind) {\n case 'PropertyAccess': {\n const propertyName = node.path[0]\n if (!propertyName) break\n const knownNames = entityType.properties.map((p) => p.name)\n if (!knownNames.includes(propertyName)) {\n throw new ODataValidationError(\n `Property '${propertyName}' not found on entity '${entityType.name}'`,\n entityType.name,\n propertyName,\n knownNames,\n )\n }\n break\n }\n case 'BinaryExpr':\n this.validateFilterNode(node.left, entityType)\n this.validateFilterNode(node.right, entityType)\n break\n case 'UnaryExpr':\n this.validateFilterNode(node.operand, entityType)\n break\n case 'FunctionCall':\n for (const arg of node.args) {\n this.validateFilterNode(arg, entityType)\n }\n break\n case 'LambdaExpr':\n if (node.predicate) {\n this.validateFilterNode(node.predicate, entityType)\n }\n break\n case 'Literal':\n // Literals have no property references to validate\n break\n }\n }\n\n /**\n * Validate $expand navigation property names against the entity type's navigationProperties.\n * Validates top-level navigation properties only — nested expand validation is handled\n * by TypeOrmExpandVisitor at translation time with full EDM registry access.\n *\n * Per T-04-09: prevents users from expanding properties not declared as navigation properties.\n * Per D-10: validates property references before any DB query is constructed.\n */\n private validateExpandNode(expandNode: ExpandNode, entityType: EdmEntityType): void {\n for (const item of expandNode.items) {\n const navNames = entityType.navigationProperties.map((np) => np.name)\n if (!navNames.includes(item.navigationProperty)) {\n throw new ODataValidationError(\n `Navigation property '${item.navigationProperty}' not found on entity '${entityType.name}'`,\n entityType.name,\n item.navigationProperty,\n navNames,\n )\n }\n }\n }\n\n /**\n * Validate a $select item path[0] against entityType.properties.\n * Throws ODataValidationError for unknown property names.\n */\n private validateSelectItem(item: SelectItem, entityType: EdmEntityType): void {\n const propertyName = item.path[0]\n if (!propertyName) return\n\n const knownNames = entityType.properties.map((p) => p.name)\n if (!knownNames.includes(propertyName)) {\n throw new ODataValidationError(\n `Property '${propertyName}' not found on entity '${entityType.name}'`,\n entityType.name,\n propertyName,\n knownNames,\n )\n }\n }\n}\n","/** Metadata key for @EdmType() property decorator — stores EdmTypeOptions per property */\nexport const EDM_TYPE_KEY = Symbol('nestjs-odata:edm-type')\n\n/** Metadata key for @ODataExclude() property decorator — stores Set<string> of excluded property names */\nexport const ODATA_EXCLUDE_KEY = Symbol('nestjs-odata:odata-exclude')\n\n/** Metadata key for @ODataEntitySet() class decorator — stores the entity set name string */\nexport const ODATA_ENTITY_SET_KEY = Symbol('nestjs-odata:entity-set')\n\n/** Metadata key for @ODataKey() property decorator — stores string[] of key property names */\nexport const ODATA_KEY_KEY = Symbol('nestjs-odata:odata-key')\n\n/** Metadata key for @ODataView() class decorator — stores ODataViewOptions */\nexport const ODATA_VIEW_KEY = Symbol('nestjs-odata:odata-view')\n\n/** Metadata key for @ODataGet() method decorator — marks a route as an OData route */\nexport const ODATA_ROUTE_KEY = Symbol('ODATA_ROUTE')\n\n/** Metadata key for @ODataController() class decorator — stores the entity set name string */\nexport const ODATA_CONTROLLER_KEY = Symbol('nestjs-odata:odata-controller')\n\n/** Metadata key for @ODataETag() property decorator — stores the ETag source property name */\nexport const ODATA_ETAG_KEY = Symbol('nestjs-odata:odata-etag')\n\n/** Metadata key for @ODataSearchable() property decorator — stores string[] of searchable property names */\nexport const ODATA_SEARCHABLE_KEY = Symbol('nestjs-odata:odata-searchable')\n","import 'reflect-metadata'\nimport { ODATA_ETAG_KEY } from './metadata-keys.js'\n\n/**\n * Marks a property as the ETag source for concurrency control.\n * Per D-03: opt-in per entity. Only entities with @ODataETag or @UpdateDateColumn get ETags.\n *\n * Usage:\n * class Product {\n * @ODataETag()\n * version: number\n * }\n */\nexport function ODataETag(): PropertyDecorator {\n return (target: object, propertyKey: string | symbol): void => {\n Reflect.defineMetadata(\n ODATA_ETAG_KEY,\n String(propertyKey),\n (target as { constructor: new (...args: unknown[]) => unknown }).constructor,\n )\n }\n}\n\n/**\n * Get the ETag property name for a given entity class.\n * Returns undefined if the entity does not have @ODataETag.\n */\nexport function getETagProperty(target: new (...args: unknown[]) => unknown): string | undefined {\n return Reflect.getMetadata(ODATA_ETAG_KEY, target) as string | undefined\n}\n","import 'reflect-metadata'\nimport { ODATA_SEARCHABLE_KEY } from './metadata-keys.js'\n\n/**\n * Marks a property as searchable via OData $search.\n * Multiple properties on the same entity can be decorated — all are stored.\n *\n * Usage:\n * class Product {\n * @ODataSearchable()\n * name: string\n *\n * @ODataSearchable()\n * description: string\n * }\n */\nexport function ODataSearchable(): PropertyDecorator {\n return (target: object, propertyKey: string | symbol): void => {\n const ctor = (target as { constructor: new (...args: unknown[]) => unknown }).constructor\n const existing: string[] =\n (Reflect.getMetadata(ODATA_SEARCHABLE_KEY, ctor) as string[] | undefined) ?? []\n Reflect.defineMetadata(ODATA_SEARCHABLE_KEY, [...existing, String(propertyKey)], ctor)\n }\n}\n\n/**\n * Get the list of searchable property names for a given entity class.\n * Returns an empty array if no properties are decorated with @ODataSearchable().\n */\nexport function getSearchableProperties(target: new (...args: unknown[]) => unknown): string[] {\n return (Reflect.getMetadata(ODATA_SEARCHABLE_KEY, target) as string[] | undefined) ?? []\n}\n","import 'reflect-metadata'\nimport { EDM_TYPE_KEY } from './metadata-keys.js'\n\n/** Options for the @EdmType property decorator */\nexport interface EdmTypeOptions {\n readonly type: string\n readonly precision?: number\n readonly scale?: number\n readonly maxLength?: number\n}\n\n/**\n * @EdmType() — override the EDM primitive type for a property.\n * Use when the TypeScript type cannot be automatically mapped, or when\n * precision/scale/maxLength constraints are needed (e.g., Edm.Decimal).\n *\n * Zero imports from typeorm, @nestjs/common, or any ORM. Pure reflect-metadata.\n */\nexport function EdmType(options: EdmTypeOptions): PropertyDecorator {\n return (target: object, propertyKey: string | symbol): void => {\n const constructor = (target as { constructor: object }).constructor\n const existing: Record<string | symbol, EdmTypeOptions> =\n (Reflect.getMetadata(EDM_TYPE_KEY, constructor) as Record<string | symbol, EdmTypeOptions>) ??\n {}\n Reflect.defineMetadata(EDM_TYPE_KEY, { ...existing, [propertyKey]: options }, constructor)\n }\n}\n\n/** Read all @EdmType overrides registered on a class */\nexport function getEdmTypeOverrides(target: object): Record<string, EdmTypeOptions> {\n return (Reflect.getMetadata(EDM_TYPE_KEY, target) as Record<string, EdmTypeOptions>) ?? {}\n}\n","import 'reflect-metadata'\nimport { ODATA_EXCLUDE_KEY } from './metadata-keys.js'\n\n/**\n * @ODataExclude() — marks a property to be excluded from the OData EDM.\n * The property will not appear in $metadata and will be stripped from responses.\n *\n * Zero imports from typeorm, @nestjs/common, or any ORM. Pure reflect-metadata.\n */\nexport function ODataExclude(): PropertyDecorator {\n return (target: object, propertyKey: string | symbol): void => {\n const constructor = (target as { constructor: object }).constructor\n const existing: Set<string | symbol> =\n (Reflect.getMetadata(ODATA_EXCLUDE_KEY, constructor) as Set<string | symbol>) ?? new Set()\n const updated = new Set(existing)\n updated.add(propertyKey)\n Reflect.defineMetadata(ODATA_EXCLUDE_KEY, updated, constructor)\n }\n}\n\n/** Read all property names excluded via @ODataExclude() on a class */\nexport function getExcludedProperties(target: object): Set<string> {\n const meta = Reflect.getMetadata(ODATA_EXCLUDE_KEY, target) as Set<string | symbol> | undefined\n if (!meta) return new Set<string>()\n const result = new Set<string>()\n for (const key of meta) {\n if (typeof key === 'string') result.add(key)\n }\n return result\n}\n","import 'reflect-metadata'\nimport { ODATA_ENTITY_SET_KEY } from './metadata-keys.js'\n\n/**\n * @ODataEntitySet(name) — override the entity set name for an entity class.\n * Without this decorator, the adapter will auto-pluralize the class name.\n *\n * Zero imports from typeorm, @nestjs/common, or any ORM. Pure reflect-metadata.\n */\nexport function ODataEntitySet(name: string): ClassDecorator {\n return (target: object): void => {\n Reflect.defineMetadata(ODATA_ENTITY_SET_KEY, name, target)\n }\n}\n\n/** Read the entity set name set via @ODataEntitySet(), or undefined if not set */\nexport function getEntitySetName(target: object): string | undefined {\n return Reflect.getMetadata(ODATA_ENTITY_SET_KEY, target) as string | undefined\n}\n","import 'reflect-metadata'\nimport { ODATA_KEY_KEY } from './metadata-keys.js'\n\n/**\n * @ODataKey() — marks a property as part of the entity key.\n * Can be applied to multiple properties for composite keys.\n *\n * Zero imports from typeorm, @nestjs/common, or any ORM. Pure reflect-metadata.\n */\nexport function ODataKey(): PropertyDecorator {\n return (target: object, propertyKey: string | symbol): void => {\n const constructor = (target as { constructor: object }).constructor\n const existing: string[] = (Reflect.getMetadata(ODATA_KEY_KEY, constructor) as string[]) ?? []\n if (typeof propertyKey === 'string') {\n Reflect.defineMetadata(ODATA_KEY_KEY, [...existing, propertyKey], constructor)\n }\n }\n}\n\n/** Read all key property names registered via @ODataKey() on a class */\nexport function getKeyProperties(target: object): string[] {\n return (Reflect.getMetadata(ODATA_KEY_KEY, target) as string[]) ?? []\n}\n","import 'reflect-metadata'\nimport { ODATA_VIEW_KEY } from './metadata-keys.js'\n\n/** Options for the @ODataView class decorator */\nexport interface ODataViewOptions {\n readonly sourceEntity: string\n readonly exposedProperties: string[]\n readonly entitySetName?: string\n}\n\n/**\n * @ODataView() — marks a class as a virtual OData view.\n * A virtual view projects a subset of an existing entity type's properties\n * as its own EntitySet without duplicating entity registration. Per D-22, D-23.\n *\n * Zero imports from typeorm, @nestjs/common, or any ORM. Pure reflect-metadata.\n */\nexport function ODataView(options: ODataViewOptions): ClassDecorator {\n return (target: object): void => {\n Reflect.defineMetadata(ODATA_VIEW_KEY, options, target)\n }\n}\n\n/** Read the ODataView options from a class, or undefined if not decorated */\nexport function getODataViewOptions(target: object): ODataViewOptions | undefined {\n return Reflect.getMetadata(ODATA_VIEW_KEY, target) as ODataViewOptions | undefined\n}\n","import type { SelectNode } from '../parser/ast.js'\n\n/**\n * Builds the OData v4 @odata.context URL per spec section 10.\n *\n * Format: {serviceRoot}/$metadata#{entitySetName}[(field1,field2,...)]\n * Single-entity format: {serviceRoot}/$metadata#{entitySetName}/$entity\n *\n * Per D-07: when $select has specific items (not all, not undefined),\n * a select projection suffix is appended.\n * Per D-05: when isSingleEntity is true, /$entity suffix is appended.\n *\n * @param serviceRoot - The OData service root path (e.g. '/odata')\n * @param entitySetName - The name of the entity set (e.g. 'Products')\n * @param select - Optional SelectNode from the parsed query\n * @param isSingleEntity - When true, appends /$entity suffix for single-entity responses\n */\nexport function buildContextUrl(\n serviceRoot: string,\n entitySetName: string,\n select?: SelectNode,\n isSingleEntity?: boolean,\n): string {\n // Normalize: strip trailing slash from serviceRoot\n const root = serviceRoot.endsWith('/') ? serviceRoot.slice(0, -1) : serviceRoot\n const base = `${root}/$metadata#${entitySetName}`\n\n // Per D-05: single-entity responses use /$entity suffix\n if (isSingleEntity) {\n return `${base}/$entity`\n }\n\n // Per D-07: if select has specific items (not all, not undefined), append (Field1,Field2)\n if (select?.items?.length) {\n const fields = select.items.map((item) => item.path.join('/')).join(',')\n return `${base}(${fields})`\n }\n\n return base\n}\n","import type { EdmEntityType } from '../edm/edm-entity-type.js'\n\n/**\n * Context needed to annotate an entity with OData metadata annotations.\n */\nexport interface AnnotationContext {\n /** The OData service root path (e.g. '/odata') */\n readonly serviceRoot: string\n /** The OData entity set name (e.g. 'Products') */\n readonly entitySetName: string\n /** The EDM entity type descriptor */\n readonly entityType: EdmEntityType\n /** The EDM namespace (e.g. 'Default') */\n readonly namespace: string\n}\n\n/**\n * Build the canonical key string for a given entity and its key properties.\n *\n * - Single key, integer value: (1)\n * - Single key, string value: ('abc')\n * - Composite key: (orderId=1,productId=2)\n *\n * @param entity - The entity object\n * @param keyProperties - The key property names from the EDM entity type\n */\nfunction buildKeyString(\n entity: Record<string, unknown>,\n keyProperties: readonly string[],\n): string {\n if (keyProperties.length === 1) {\n const keyName = keyProperties[0]\n const keyValue = entity[keyName]\n if (typeof keyValue === 'string') {\n return `('${keyValue}')`\n }\n return `(${String(keyValue)})`\n }\n\n // Composite key\n const parts = keyProperties.map((keyName) => {\n const keyValue = entity[keyName]\n if (typeof keyValue === 'string') {\n return `${keyName}='${keyValue}'`\n }\n return `${keyName}=${String(keyValue)}`\n })\n return `(${parts.join(',')})`\n}\n\n/**\n * Annotate a single entity object with OData v4 metadata annotations.\n *\n * Adds:\n * - @odata.id — canonical URL for the entity (RESP-04)\n * - @odata.type — qualified type name with namespace prefix (RESP-05)\n * - {navProp}@odata.navigationLink — for each navigation property (RESP-06)\n *\n * Returns the entity unchanged if it is null, undefined, or not an object.\n *\n * This function is pure — it never mutates the input entity.\n *\n * @param entity - The entity object to annotate\n * @param ctx - The annotation context (service root, entity set name, entity type, namespace)\n */\nexport function annotateEntity(\n entity: Record<string, unknown>,\n ctx: AnnotationContext,\n): Record<string, unknown> {\n if (entity === null || entity === undefined || typeof entity !== 'object') {\n return entity\n }\n\n const { serviceRoot, entitySetName, entityType, namespace } = ctx\n\n // Normalize serviceRoot: strip trailing slash\n const root = serviceRoot.endsWith('/') ? serviceRoot.slice(0, -1) : serviceRoot\n\n const keyStr = buildKeyString(entity, entityType.keyProperties)\n const odataId = `${root}/${entitySetName}${keyStr}`\n const odataType = `#${namespace}.${entityType.name}`\n\n // Build navigation link entries\n const navLinks: Record<string, string> = {}\n for (const navProp of entityType.navigationProperties) {\n navLinks[`${navProp.name}@odata.navigationLink`] = `${odataId}/${navProp.name}`\n }\n\n // Return new object — immutable (never mutate input)\n return {\n ...entity,\n '@odata.id': odataId,\n '@odata.type': odataType,\n ...navLinks,\n }\n}\n\n/**\n * Annotate an array of entity objects with OData v4 metadata annotations.\n *\n * Applies `annotateEntity` to every item in the array and returns a new array.\n * Does not mutate the input array or any items.\n *\n * @param items - The array of entity objects to annotate\n * @param ctx - The annotation context\n */\nexport function annotateEntities(\n items: Record<string, unknown>[],\n ctx: AnnotationContext,\n): Record<string, unknown>[] {\n return items.map((item) => annotateEntity(item, ctx))\n}\n","import { CallHandler, ExecutionContext, Inject, Injectable, NestInterceptor } from '@nestjs/common'\nimport { Reflector } from '@nestjs/core'\nimport { Observable } from 'rxjs'\nimport { map } from 'rxjs/operators'\nimport { ODATA_MODULE_OPTIONS } from '../tokens.js'\nimport type { ODataModuleResolvedOptions } from '../odata.module.js'\nimport type { ODataQueryResult } from '../query/odata-query.types.js'\nimport type { SelectNode } from '../parser/ast.js'\nimport { buildContextUrl } from './odata-context-url.builder.js'\nimport { annotateEntity, annotateEntities } from './odata-annotation.builder.js'\nimport type { AnnotationContext } from './odata-annotation.builder.js'\nimport { ODATA_ROUTE_KEY } from '../decorators/metadata-keys.js'\nimport { EdmRegistry } from '../edm/edm-registry.js'\n\n/** Metadata value shape set by @ODataGet() and CRUD decorators */\ninterface ODataRouteMetadata {\n readonly entitySetName: string\n readonly operation?: string\n readonly isSingleEntity?: boolean\n}\n\n/**\n * NestJS interceptor that wraps ODataQueryResult into an OData v4 JSON envelope.\n *\n * Per D-05: only activates on routes that have ODATA_ROUTE_KEY metadata (set by @ODataGet()).\n * Non-OData routes pass through completely unchanged (T-03-09).\n *\n * Response format:\n * {\n * '@odata.context': '/odata/$metadata#EntitySet',\n * '@odata.id': '/odata/EntitySet(key)', (per RESP-04)\n * '@odata.type': '#Namespace.EntityType', (per RESP-05)\n * '{nav}@odata.navigationLink': '...', (per RESP-06)\n * 'value': [...],\n * '@odata.count': N, (only when present)\n * '@odata.nextLink': '...' (only when present)\n * }\n *\n * Per D-08: undefined keys are omitted entirely — not set to null or undefined.\n *\n * Zero TypeORM imports — per PKG-01 architecture constraint.\n */\n@Injectable()\nexport class ODataResponseInterceptor implements NestInterceptor {\n constructor(\n private readonly reflector: Reflector,\n @Inject(ODATA_MODULE_OPTIONS) private readonly options: ODataModuleResolvedOptions,\n private readonly edmRegistry: EdmRegistry,\n ) {}\n\n /**\n * Resolve the AnnotationContext for an entity set name.\n * Returns null when the entity set or its type is not registered (graceful degradation).\n */\n private resolveAnnotationContext(entitySetName: string): AnnotationContext | null {\n const entitySet = this.edmRegistry.getEntitySet(entitySetName)\n if (!entitySet) return null\n const entityType = this.edmRegistry.getEntityType(entitySet.entityTypeName)\n if (!entityType) return null\n return {\n serviceRoot: this.options.serviceRoot,\n entitySetName,\n entityType,\n namespace: this.options.namespace,\n }\n }\n\n intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {\n // Check if this route has OData metadata (set by @ODataGet())\n const metadata = this.reflector.get<ODataRouteMetadata | undefined>(\n ODATA_ROUTE_KEY,\n context.getHandler(),\n )\n\n // If not an OData route, pass through unchanged (D-05, T-03-09)\n if (!metadata) {\n return next.handle()\n }\n\n const { entitySetName, operation, isSingleEntity } = metadata\n\n return next.handle().pipe(\n map((result: unknown) => {\n // Handle POST create: set Location header and return entity with $entity context URL\n if (operation === 'create' && result !== null && typeof result === 'object') {\n const createResult = result as { entity?: unknown; locationUrl?: string }\n if (createResult.locationUrl) {\n const httpResponse = context\n .switchToHttp()\n .getResponse<{ setHeader: (k: string, v: string) => void }>()\n httpResponse.setHeader('Location', createResult.locationUrl)\n }\n const entity = (createResult.entity ?? result) as Record<string, unknown>\n const contextUrl = buildContextUrl(\n this.options.serviceRoot,\n entitySetName,\n undefined,\n true,\n )\n\n // Annotate the created entity (RESP-04, RESP-05, RESP-06)\n const annotationCtx = this.resolveAnnotationContext(entitySetName)\n const annotatedEntity = annotationCtx ? annotateEntity(entity, annotationCtx) : entity\n\n return {\n '@odata.context': contextUrl,\n ...annotatedEntity,\n }\n }\n\n // Handle single-entity response (GET by key, PATCH update) — D-05\n if (isSingleEntity) {\n const entityResult = result as Record<string, unknown>\n\n // Handle 304 Not Modified — entity unchanged (If-None-Match matched)\n if (entityResult['__notModified'] === true) {\n const etag = entityResult['etag'] as string\n const httpResponse = context.switchToHttp().getResponse<{\n setHeader: (k: string, v: string) => void\n status: (code: number) => { end: () => void }\n }>()\n httpResponse.setHeader('ETag', etag)\n // Set 304 status and end the response directly — no body for 304\n httpResponse.status(304).end()\n return null\n }\n\n const contextUrl = buildContextUrl(\n this.options.serviceRoot,\n entitySetName,\n undefined,\n true,\n )\n\n // Annotate the single entity (RESP-04, RESP-05, RESP-06)\n const annotationCtx = this.resolveAnnotationContext(entitySetName)\n\n // Handle __etag internal property — set ETag header and add @odata.etag to body\n let entityToAnnotate = entityResult\n if (entityResult['__etag']) {\n const etag = entityResult['__etag'] as string\n const httpResponse = context\n .switchToHttp()\n .getResponse<{ setHeader: (k: string, v: string) => void }>()\n httpResponse.setHeader('ETag', etag)\n // Remove internal property and add @odata.etag\n const rest: Record<string, unknown> = {}\n for (const key of Object.keys(entityResult)) {\n if (key !== '__etag') {\n rest[key] = entityResult[key]\n }\n }\n entityToAnnotate = { '@odata.etag': etag, ...rest }\n }\n\n const annotatedEntity = annotationCtx\n ? annotateEntity(entityToAnnotate, annotationCtx)\n : entityToAnnotate\n\n return {\n '@odata.context': contextUrl,\n ...annotatedEntity,\n }\n }\n\n // Cast once — shared by aggregated and collection branches\n const queryResult = result as ODataQueryResult\n\n // Aggregated response ($apply) — no entity annotations, projection context URL\n if (queryResult.isAggregated) {\n const projectedSelect: SelectNode | undefined = queryResult.applyProperties?.length\n ? { items: queryResult.applyProperties.map((p) => ({ path: [p] })) }\n : undefined\n const aggContextUrl = buildContextUrl(\n this.options.serviceRoot,\n entitySetName,\n projectedSelect,\n )\n const aggResponse: Record<string, unknown> = {\n '@odata.context': aggContextUrl,\n value: queryResult.items,\n }\n if (queryResult.count !== undefined) {\n aggResponse['@odata.count'] = queryResult.count\n }\n return aggResponse\n }\n\n // Collection response (default) — wrap in OData envelope\n const contextUrl = buildContextUrl(\n this.options.serviceRoot,\n entitySetName,\n queryResult.select,\n )\n\n // Annotate each item in the collection (RESP-04, RESP-05, RESP-06)\n const annotationCtx = this.resolveAnnotationContext(entitySetName)\n const items = annotationCtx\n ? annotateEntities(queryResult.items as Record<string, unknown>[], annotationCtx)\n : queryResult.items\n\n // Build response: only include optional keys when they have values (D-06, D-08)\n const response: Record<string, unknown> = {\n '@odata.context': contextUrl,\n value: items,\n }\n\n if (queryResult.count !== undefined) {\n response['@odata.count'] = queryResult.count\n }\n\n if (queryResult.nextLink !== undefined) {\n response['@odata.nextLink'] = queryResult.nextLink\n }\n\n return response\n }),\n )\n }\n}\n","import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus } from '@nestjs/common'\nimport { ODataParseError } from '../parser/errors.js'\nimport { ODataValidationError } from '../query/odata-validation.error.js'\n\n/** Minimal shape of an HTTP response object (Express or Fastify compatible) */\ninterface HttpResponse {\n status(code: number): this\n json(body: unknown): this\n}\n\n/**\n * OData v4 error response body shape (per OData v4 Part 1 section 9.4).\n */\ninterface ODataErrorBody {\n error: {\n code: string\n message: string\n details: unknown[]\n }\n}\n\n/**\n * NestJS exception filter that converts all caught exceptions into OData v4 error format.\n *\n * Per D-09: all errors are formatted as { error: { code, message, details } }.\n * Per D-11, T-03-08: never includes stack traces or internal server details.\n *\n * Handled exception types:\n * - ODataParseError -> HTTP 400, code 'BadRequest', user message\n * - ODataValidationError -> HTTP 400, code 'BadRequest', user message\n * - HttpException -> uses exception's HTTP status code, generic OData code\n * - All others -> HTTP 500, code 'InternalServerError', generic message (no details leaked)\n *\n * Zero TypeORM imports — per PKG-01 architecture constraint.\n */\n@Catch()\nexport class ODataExceptionFilter implements ExceptionFilter {\n catch(exception: unknown, host: ArgumentsHost): void {\n const ctx = host.switchToHttp()\n const response = ctx.getResponse<HttpResponse>()\n\n let status: number\n let body: ODataErrorBody\n\n if (exception instanceof ODataParseError) {\n status = HttpStatus.BAD_REQUEST\n const details: unknown[] = exception.queryContext ? [{ target: exception.queryContext }] : []\n body = {\n error: {\n code: 'BadRequest',\n message: exception.message,\n details,\n },\n }\n } else if (exception instanceof ODataValidationError) {\n status = HttpStatus.BAD_REQUEST\n const details: unknown[] =\n exception.availableProperties && exception.availableProperties.length > 0\n ? [{ target: 'availableProperties', value: [...exception.availableProperties] }]\n : []\n body = {\n error: {\n code: 'BadRequest',\n message: exception.message,\n details,\n },\n }\n } else if (exception instanceof HttpException) {\n status = exception.getStatus()\n const code = httpStatusToODataCode(status)\n body = {\n error: {\n code,\n message: exception.message,\n details: [],\n },\n }\n } else {\n // Generic error — never leak internal details (T-03-08)\n status = HttpStatus.INTERNAL_SERVER_ERROR\n body = {\n error: {\n code: 'InternalServerError',\n message: 'An unexpected error occurred.',\n details: [],\n },\n }\n }\n\n response.status(status).json(body)\n }\n}\n\n/**\n * Map HTTP status code to an OData error code string.\n * Only covers the most common HTTP status codes.\n */\nfunction httpStatusToODataCode(status: number): string {\n const codes: Record<number, string> = {\n 400: 'BadRequest',\n 401: 'Unauthorized',\n 403: 'Forbidden',\n 404: 'NotFound',\n 405: 'MethodNotAllowed',\n 409: 'Conflict',\n 410: 'Gone',\n 422: 'UnprocessableEntity',\n 429: 'TooManyRequests',\n 500: 'InternalServerError',\n 501: 'NotImplemented',\n 503: 'ServiceUnavailable',\n }\n return codes[status] ?? 'Error'\n}\n","import { applyDecorators, Get, SetMetadata, UseFilters, UseInterceptors } from '@nestjs/common'\nimport { ODATA_ROUTE_KEY } from './metadata-keys.js'\nimport { ODataResponseInterceptor } from '../response/odata-response.interceptor.js'\nimport { ODataExceptionFilter } from '../response/odata-exception.filter.js'\n\n/**\n * Options for the @ODataGet() decorator.\n */\nexport interface ODataGetOptions {\n /** Custom route path. Defaults to '' (empty, NestJS will use the controller prefix). */\n path?: string\n /**\n * When true, signals the auto-handler mechanism (wired in Plan 04 by the typeorm module)\n * to replace the decorated method body with a generated handler.\n * Default: false\n */\n autoHandler?: boolean\n}\n\n/**\n * Composite method decorator for OData GET endpoints.\n *\n * Composes:\n * 1. Get(path) — NestJS route decorator\n * 2. SetMetadata(ODATA_ROUTE_KEY, { entitySetName }) — marks the route as OData\n * 3. UseInterceptors(ODataResponseInterceptor) — wraps result in OData JSON envelope\n * 4. UseFilters(ODataExceptionFilter) — formats errors as OData v4 error bodies\n *\n * @param entitySetName - The OData entity set name (e.g. 'Products'). Used to build\n * the @odata.context URL and for response formatting.\n * @param options - Optional configuration\n *\n * Per D-12, D-13, RESEARCH.md Pattern 5.\n * Zero TypeORM imports — per PKG-01 architecture constraint.\n */\nexport function ODataGet(entitySetName: string, options?: ODataGetOptions): MethodDecorator {\n return applyDecorators(\n Get(options?.path ?? entitySetName),\n SetMetadata(ODATA_ROUTE_KEY, {\n entitySetName,\n autoHandler: options?.autoHandler ?? false,\n }),\n UseInterceptors(ODataResponseInterceptor),\n UseFilters(ODataExceptionFilter),\n )\n}\n","import { createParamDecorator } from '@nestjs/common'\nimport type { ExecutionContext } from '@nestjs/common'\nimport { ODataQueryPipe } from '../query/odata-query.pipe.js'\n\n/**\n * Internal param decorator that extracts req.query from the HTTP execution context.\n * Not exported — consumers use the ODataQueryParam wrapper which auto-attaches ODataQueryPipe.\n */\nconst RawQuery = createParamDecorator((_data: string | undefined, ctx: ExecutionContext) => {\n const request = ctx.switchToHttp().getRequest<{ query: Record<string, string> }>()\n return request.query\n})\n\n/**\n * Parameter decorator that extracts req.query and auto-applies ODataQueryPipe.\n *\n * Usage:\n * @Get()\n * async getProducts(@ODataQueryParam('Products') query: ODataQuery) { ... }\n *\n * The decorator passes the entitySetName as `data` so that the ODataQueryPipe\n * can inject it into the query object for context URL construction and field validation.\n *\n * Per D-04: auto-applies ODataQueryPipe — no @UsePipes(ODataQueryPipe) needed.\n * Per D-05: ODataQueryPipe remains exported for advanced use cases.\n * Per D-06: eliminates silent validation bypass when forgetting @UsePipes.\n *\n * NestJS resolves ODataQueryPipe from DI automatically when a class reference\n * is passed as the pipe argument to a createParamDecorator result.\n *\n * Zero TypeORM imports — per PKG-01 architecture constraint.\n */\nexport const ODataQueryParam = (entitySetName?: string): ParameterDecorator =>\n RawQuery(entitySetName, ODataQueryPipe)\n","import {\n applyDecorators,\n HttpCode,\n Post,\n SetMetadata,\n UseFilters,\n UseInterceptors,\n} from '@nestjs/common'\nimport { ODATA_ROUTE_KEY } from './metadata-keys.js'\nimport { ODataResponseInterceptor } from '../response/odata-response.interceptor.js'\nimport { ODataExceptionFilter } from '../response/odata-exception.filter.js'\n\nexport interface ODataPostOptions {\n path?: string\n}\n\n/**\n * Composite method decorator for OData POST (create) endpoints.\n *\n * Composes:\n * 1. Post(path) — NestJS route decorator\n * 2. HttpCode(201) — POST returns 201 Created per D-03\n * 3. SetMetadata(ODATA_ROUTE_KEY, { entitySetName, operation: 'create' })\n * 4. UseInterceptors(ODataResponseInterceptor) — wraps result in OData JSON envelope\n * 5. UseFilters(ODataExceptionFilter) — formats errors as OData v4 error bodies\n *\n * Per D-03: POST returns 201. Per D-12: explicit opt-in per operation.\n * Zero TypeORM imports — per PKG-01 architecture constraint.\n */\nexport function ODataPost(entitySetName: string, options?: ODataPostOptions): MethodDecorator {\n return applyDecorators(\n Post(options?.path ?? ''),\n HttpCode(201),\n SetMetadata(ODATA_ROUTE_KEY, {\n entitySetName,\n operation: 'create',\n }),\n UseInterceptors(ODataResponseInterceptor),\n UseFilters(ODataExceptionFilter),\n )\n}\n","import { applyDecorators, Patch, SetMetadata, UseFilters, UseInterceptors } from '@nestjs/common'\nimport { ODATA_ROUTE_KEY } from './metadata-keys.js'\nimport { ODataResponseInterceptor } from '../response/odata-response.interceptor.js'\nimport { ODataExceptionFilter } from '../response/odata-exception.filter.js'\n\nexport interface ODataPatchOptions {\n path?: string\n}\n\n/**\n * Composite method decorator for OData PATCH (update) endpoints.\n *\n * Composes:\n * 1. Patch(path) — NestJS route decorator, defaults to ':key'\n * 2. SetMetadata(ODATA_ROUTE_KEY, { entitySetName, operation: 'update', isSingleEntity: true })\n * 3. UseInterceptors(ODataResponseInterceptor) — wraps result in single-entity OData envelope\n * 4. UseFilters(ODataExceptionFilter) — formats errors as OData v4 error bodies\n *\n * Per D-01: merge-patch semantics. Per D-02: parenthetical key via ':key' param.\n * Zero TypeORM imports — per PKG-01 architecture constraint.\n */\nexport function ODataPatch(entitySetName: string, options?: ODataPatchOptions): MethodDecorator {\n return applyDecorators(\n Patch(options?.path ?? ':key'),\n SetMetadata(ODATA_ROUTE_KEY, {\n entitySetName,\n operation: 'update',\n isSingleEntity: true,\n }),\n UseInterceptors(ODataResponseInterceptor),\n UseFilters(ODataExceptionFilter),\n )\n}\n","import { applyDecorators, Put, SetMetadata, UseFilters, UseInterceptors } from '@nestjs/common'\nimport { ODATA_ROUTE_KEY } from './metadata-keys.js'\nimport { ODataResponseInterceptor } from '../response/odata-response.interceptor.js'\nimport { ODataExceptionFilter } from '../response/odata-exception.filter.js'\n\nexport interface ODataPutOptions {\n path?: string\n}\n\n/**\n * Composite method decorator for OData PUT (full entity replacement) endpoints.\n *\n * Composes:\n * 1. Put(path) — NestJS route decorator, defaults to ':key'\n * 2. SetMetadata(ODATA_ROUTE_KEY, { entitySetName, operation: 'replace', isSingleEntity: true })\n * 3. UseInterceptors(ODataResponseInterceptor) — wraps result in single-entity OData envelope\n * 4. UseFilters(ODataExceptionFilter) — formats errors as OData v4 error bodies\n *\n * Per D-01: PUT semantics — full entity replacement. All unspecified fields reset to\n * column defaults (null for nullable, default value for columns with defaults).\n * Per D-02: parenthetical key via ':key' param.\n * Zero TypeORM imports — per PKG-01 architecture constraint.\n */\nexport function ODataPut(entitySetName: string, options?: ODataPutOptions): MethodDecorator {\n return applyDecorators(\n Put(options?.path ?? ':key'),\n SetMetadata(ODATA_ROUTE_KEY, {\n entitySetName,\n operation: 'replace',\n isSingleEntity: true,\n }),\n UseInterceptors(ODataResponseInterceptor),\n UseFilters(ODataExceptionFilter),\n )\n}\n","import { applyDecorators, Delete, HttpCode, SetMetadata, UseFilters } from '@nestjs/common'\nimport { ODATA_ROUTE_KEY } from './metadata-keys.js'\nimport { ODataExceptionFilter } from '../response/odata-exception.filter.js'\n\nexport interface ODataDeleteOptions {\n path?: string\n}\n\n/**\n * Composite method decorator for OData DELETE endpoints.\n *\n * Composes:\n * 1. Delete(path) — NestJS route decorator, defaults to ':key'\n * 2. HttpCode(204) — DELETE returns 204 No Content per D-04\n * 3. SetMetadata(ODATA_ROUTE_KEY, { entitySetName, operation: 'delete' })\n * 4. UseFilters(ODataExceptionFilter) — formats errors as OData v4 error bodies\n *\n * Per D-04: DELETE returns 204 with no body. No ODataResponseInterceptor — no body.\n * Zero TypeORM imports — per PKG-01 architecture constraint.\n */\nexport function ODataDelete(entitySetName: string, options?: ODataDeleteOptions): MethodDecorator {\n return applyDecorators(\n Delete(options?.path ?? ':key'),\n HttpCode(204),\n SetMetadata(ODATA_ROUTE_KEY, {\n entitySetName,\n operation: 'delete',\n }),\n UseFilters(ODataExceptionFilter),\n )\n}\n","import { applyDecorators, Get, SetMetadata, UseFilters, UseInterceptors } from '@nestjs/common'\nimport { ODATA_ROUTE_KEY } from './metadata-keys.js'\nimport { ODataResponseInterceptor } from '../response/odata-response.interceptor.js'\nimport { ODataExceptionFilter } from '../response/odata-exception.filter.js'\n\nexport interface ODataGetByKeyOptions {\n path?: string\n}\n\n/**\n * Composite method decorator for OData GET-by-key (single entity) endpoints.\n *\n * Composes:\n * 1. Get(path) — NestJS route decorator, defaults to ':key'\n * 2. SetMetadata(ODATA_ROUTE_KEY, { entitySetName, operation: 'getByKey', isSingleEntity: true })\n * 3. UseInterceptors(ODataResponseInterceptor) — returns entity directly (not wrapped in value array)\n * 4. UseFilters(ODataExceptionFilter) — formats errors as OData v4 error bodies\n *\n * Per D-05: Returns single entity, not wrapped in value array.\n * Context URL uses /$entity suffix for single-entity responses.\n * Zero TypeORM imports — per PKG-01 architecture constraint.\n */\nexport function ODataGetByKey(\n entitySetName: string,\n options?: ODataGetByKeyOptions,\n): MethodDecorator {\n return applyDecorators(\n Get(options?.path ?? ':key'),\n SetMetadata(ODATA_ROUTE_KEY, {\n entitySetName,\n operation: 'getByKey',\n isSingleEntity: true,\n }),\n UseInterceptors(ODataResponseInterceptor),\n UseFilters(ODataExceptionFilter),\n )\n}\n","import { applyDecorators, Controller, SetMetadata } from '@nestjs/common'\nimport { ODATA_CONTROLLER_KEY } from './metadata-keys.js'\n\nexport interface ODataControllerOptions {\n /** Override the route path. Defaults to entitySetName. */\n path?: string\n}\n\n/**\n * Class decorator for OData controllers.\n *\n * Composes:\n * 1. Controller(path) — NestJS route prefix, defaults to entitySetName\n * 2. SetMetadata(ODATA_CONTROLLER_KEY, entitySetName) — marks as OData controller\n * (used by ODataModule.forRoot() to patch PATH_METADATA with serviceRoot)\n *\n * Per D-11: Separate from @Controller() — sets entity context and route prefix.\n * Per D-17: The service root prefix is applied by ODataModule.forRoot({ controllers })\n * via Reflect.defineMetadata(PATH_METADATA) — the same pattern used by MetadataController.\n * The controller is initially registered with just the entitySetName as path;\n * the module prepends serviceRoot during forRoot().\n *\n * Note: ODataResponseInterceptor and ODataExceptionFilter are NOT applied at class level.\n * Each CRUD method decorator (@ODataGet, @ODataPost, @ODataPatch, @ODataDelete, @ODataGetByKey)\n * applies these per-method. This avoids double-wrapping when method and class decorators\n * are both used.\n *\n * @param entitySetName - The OData entity set name (e.g. 'Products')\n * @param options - Optional configuration\n *\n * Zero TypeORM imports — per PKG-01 architecture constraint.\n */\nexport function ODataController(\n entitySetName: string,\n options?: ODataControllerOptions,\n): ClassDecorator {\n return applyDecorators(\n Controller(options?.path ?? entitySetName),\n SetMetadata(ODATA_CONTROLLER_KEY, entitySetName),\n ) as ClassDecorator\n}\n","import { Inject, Injectable, OnModuleInit } from '@nestjs/common'\nimport { EdmRegistry } from './edm-registry.js'\nimport type { EdmEntityConfig, EdmEntitySet } from './edm-entity-set.js'\nimport type { EdmEntityType } from './edm-entity-type.js'\nimport type { ODataModuleResolvedOptions } from '../odata.module.js'\nimport { EDM_ENTITY_CONFIGS, ODATA_MODULE_OPTIONS } from '../tokens.js'\n\n/**\n * EdmFeatureInitializer — consumes pre-built EdmEntityConfig[] from the\n * EDM_ENTITY_CONFIGS token and registers them into EdmRegistry at module init.\n *\n * This closes the MOD-02 gap: ODataModule.forFeature() provides the token but\n * nothing consumed it, making core-only entity registration inert.\n *\n * Pattern mirrors TypeOrmEdmInitializer in @nestjs-odata/typeorm but operates\n * on already-derived EdmEntityConfig[] (no TypeORM derivation step needed).\n */\n@Injectable()\nexport class EdmFeatureInitializer implements OnModuleInit {\n constructor(\n private readonly edmRegistry: EdmRegistry,\n @Inject(ODATA_MODULE_OPTIONS) private readonly options: ODataModuleResolvedOptions,\n @Inject(EDM_ENTITY_CONFIGS) private readonly entityConfigs: EdmEntityConfig[],\n ) {}\n\n onModuleInit(): void {\n const namespace = this.options.namespace\n for (const config of this.entityConfigs) {\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","import { Injectable, Inject } from '@nestjs/common'\nimport { EdmRegistry } from '../edm/edm-registry.js'\nimport type { EdmEntityType } from '../edm/edm-entity-type.js'\nimport type { EdmEntitySet } from '../edm/edm-entity-set.js'\nimport type { EdmProperty, EdmNavigationProperty } from '../edm/edm-types.js'\nimport { ODATA_MODULE_OPTIONS } from '../tokens.js'\nimport type { ODataModuleOptions } from '../odata.module.js'\n\n/**\n * CsdlBuilder — builds OData v4 CSDL XML from the EdmRegistry.\n *\n * Per D-17: XML is built once at init time and cached.\n * Per D-08: Default namespace is 'Default'.\n * Per D-09: Container name is 'Container'.\n * Per T-02-08: Caching mitigates DoS risk from repeated CSDL requests.\n */\n@Injectable()\nexport class CsdlBuilder {\n private cachedXml: string | null = null\n\n constructor(\n private readonly edmRegistry: EdmRegistry,\n @Inject(ODATA_MODULE_OPTIONS) private readonly options: ODataModuleOptions,\n ) {}\n\n /**\n * Build and return the CSDL XML string.\n * Builds once on first call; returns cached string on subsequent calls.\n */\n buildCsdlXml(): string {\n if (this.cachedXml !== null) {\n return this.cachedXml\n }\n this.cachedXml = this.buildXml()\n return this.cachedXml\n }\n\n private buildXml(): string {\n const namespace = this.options.namespace ?? 'Default'\n const entityTypes = Array.from(this.edmRegistry.getEntityTypes().values())\n const entitySets = Array.from(this.edmRegistry.getEntitySets().values())\n\n const parts: string[] = []\n parts.push('<?xml version=\"1.0\" encoding=\"UTF-8\"?>')\n parts.push('<edmx:Edmx xmlns:edmx=\"http://docs.oasis-open.org/odata/ns/edmx\" Version=\"4.0\">')\n parts.push(' <edmx:DataServices>')\n parts.push(\n ` <Schema xmlns=\"http://docs.oasis-open.org/odata/ns/edm\" Namespace=\"${namespace}\">`,\n )\n\n for (const entityType of entityTypes) {\n parts.push(this.buildEntityTypeXml(entityType))\n }\n\n parts.push(' <EntityContainer Name=\"Container\">')\n for (const entitySet of entitySets) {\n parts.push(this.buildEntitySetXml(entitySet, namespace))\n }\n parts.push(' </EntityContainer>')\n parts.push(' </Schema>')\n parts.push(' </edmx:DataServices>')\n parts.push('</edmx:Edmx>')\n\n return parts.join('\\n')\n }\n\n private buildEntityTypeXml(entityType: EdmEntityType): string {\n const lines: string[] = []\n lines.push(` <EntityType Name=\"${entityType.name}\">`)\n\n if (entityType.keyProperties.length > 0) {\n lines.push(' <Key>')\n for (const keyProp of entityType.keyProperties) {\n lines.push(` <PropertyRef Name=\"${keyProp}\"/>`)\n }\n lines.push(' </Key>')\n }\n\n for (const prop of entityType.properties) {\n lines.push(this.buildPropertyXml(prop))\n }\n\n for (const nav of entityType.navigationProperties) {\n lines.push(this.buildNavigationPropertyXml(nav))\n }\n\n lines.push(' </EntityType>')\n return lines.join('\\n')\n }\n\n private buildPropertyXml(prop: EdmProperty): string {\n let xml = ` <Property Name=\"${prop.name}\" Type=\"${prop.type}\" Nullable=\"${prop.nullable}\"`\n if (prop.precision !== undefined) {\n xml += ` Precision=\"${prop.precision}\"`\n }\n if (prop.scale !== undefined) {\n xml += ` Scale=\"${prop.scale}\"`\n }\n if (prop.maxLength !== undefined) {\n xml += ` MaxLength=\"${prop.maxLength}\"`\n }\n xml += '/>'\n return xml\n }\n\n private buildNavigationPropertyXml(nav: EdmNavigationProperty): string {\n if (nav.isCollection) {\n // Collection navigation properties do not have Nullable attribute per OData spec\n return ` <NavigationProperty Name=\"${nav.name}\" Type=\"${nav.type}\"/>`\n }\n return ` <NavigationProperty Name=\"${nav.name}\" Type=\"${nav.type}\" Nullable=\"${nav.nullable}\"/>`\n }\n\n private buildEntitySetXml(entitySet: EdmEntitySet, namespace: string): string {\n return ` <EntitySet Name=\"${entitySet.name}\" EntityType=\"${namespace}.${entitySet.entityTypeName}\"/>`\n }\n}\n","import { Injectable, Inject } from '@nestjs/common'\nimport { EdmRegistry } from '../edm/edm-registry.js'\nimport { ODATA_MODULE_OPTIONS } from '../tokens.js'\nimport type { ODataModuleOptions } from '../odata.module.js'\n\n/** A single entity set entry in the OData service document */\nexport interface ServiceDocumentEntry {\n readonly name: string\n readonly url: string\n readonly kind: 'EntitySet'\n}\n\n/** OData service document response shape */\nexport interface ServiceDocument {\n readonly '@odata.context': string\n readonly value: readonly ServiceDocumentEntry[]\n}\n\n/**\n * ServiceDocumentBuilder — builds the OData service document (root URL response).\n *\n * Per D-24: Service document lists all registered EntitySets with name, url, and kind.\n */\n@Injectable()\nexport class ServiceDocumentBuilder {\n constructor(\n private readonly edmRegistry: EdmRegistry,\n @Inject(ODATA_MODULE_OPTIONS) private readonly options: ODataModuleOptions,\n ) {}\n\n /**\n * Build the OData service document JSON object.\n * Returns @odata.context pointing to $metadata and a value array with all EntitySets.\n */\n buildServiceDocument(): ServiceDocument {\n const serviceRoot = this.options.serviceRoot\n const entitySets = Array.from(this.edmRegistry.getEntitySets().values())\n\n const value: ServiceDocumentEntry[] = entitySets.map((es) => ({\n name: es.name,\n url: es.name,\n kind: 'EntitySet',\n }))\n\n return {\n '@odata.context': `${serviceRoot}/$metadata`,\n value,\n }\n }\n}\n","import { Controller, Get, Header, Inject } from '@nestjs/common'\nimport { CsdlBuilder } from './csdl-builder.js'\nimport { ServiceDocumentBuilder } from './service-document-builder.js'\nimport { ODATA_MODULE_OPTIONS } from '../tokens.js'\nimport type { ODataModuleOptions } from '../odata.module.js'\n\n/**\n * MetadataController — serves the $metadata CSDL XML and OData service document.\n *\n * Routes (relative to serviceRoot, e.g. '/odata'):\n * GET /odata/$metadata → CSDL XML (Content-Type: application/xml)\n * GET /odata → Service document JSON (Content-Type: application/json)\n *\n * The controller uses @Controller() with no prefix because consumers mount it\n * via ODataModule which sets the serviceRoot path. The routes use the serviceRoot\n * path directly to ensure correct routing when integrated into a NestJS app.\n *\n * Implementation uses a fixed serviceRoot-relative routing strategy:\n * The @Controller() prefix is set to the serviceRoot and endpoints are\n * '/$metadata' and '/' respectively. This is the most NestJS-idiomatic approach.\n */\n@Controller()\nexport class MetadataController {\n constructor(\n private readonly csdlBuilder: CsdlBuilder,\n private readonly serviceDocumentBuilder: ServiceDocumentBuilder,\n @Inject(ODATA_MODULE_OPTIONS) readonly options: ODataModuleOptions,\n ) {}\n\n /**\n * GET {serviceRoot}/$metadata\n * Returns OData v4 CSDL XML document.\n * Content-Type: application/xml per OData spec.\n */\n @Get('$metadata')\n @Header('Content-Type', 'application/xml')\n getMetadata(): string {\n return this.csdlBuilder.buildCsdlXml()\n }\n\n /**\n * GET {serviceRoot}/ (service document)\n * Returns OData service document listing all available EntitySets.\n * Content-Type: application/json per OData spec.\n */\n @Get('')\n @Header('Content-Type', 'application/json')\n getServiceDocument(): object {\n return this.serviceDocumentBuilder.buildServiceDocument()\n }\n}\n","import {\n ConfigurableModuleBuilder,\n ConfigurableModuleAsyncOptions,\n DynamicModule,\n Global,\n Module,\n} from '@nestjs/common'\nimport { PATH_METADATA } from '@nestjs/common/constants.js'\nimport type { UnmappedTypeStrategy } from './edm/edm-types.js'\nimport type { EdmEntityConfig } from './edm/edm-entity-set.js'\nimport { EdmRegistry } from './edm/edm-registry.js'\nimport { EdmFeatureInitializer } from './edm/edm-feature-initializer.js'\nimport { EDM_ENTITY_CONFIGS, ODATA_MODULE_OPTIONS } from './tokens.js'\nimport { CsdlBuilder } from './metadata/csdl-builder.js'\nimport { ServiceDocumentBuilder } from './metadata/service-document-builder.js'\nimport { MetadataController } from './metadata/metadata.controller.js'\nimport { ODATA_CONTROLLER_KEY } from './decorators/metadata-keys.js'\n\n// Re-export ODATA_MODULE_OPTIONS so consumers can import it from this module\nexport { ODATA_MODULE_OPTIONS } from './tokens.js'\n\n/**\n * Configuration options for ODataModule.forRoot().\n *\n * Per threat model T-02-03: serviceRoot must be a non-empty string.\n * Per threat model T-02-04: maxTop and maxExpandDepth have safe defaults.\n */\nexport interface ODataModuleOptions {\n /** Base path for the OData service, e.g. '/odata' */\n serviceRoot: string\n /** EDM namespace for all entity types. Default: 'Default' (per D-08) */\n namespace?: string\n /** Maximum number of entities returned per query. Default: 1000 */\n maxTop?: number\n /** Maximum depth of $expand nesting. Default: 2 */\n maxExpandDepth?: number\n /** Maximum nesting depth of $filter expressions. Default: 10 (per SEC-04) */\n maxFilterDepth?: number\n /** Strategy when a TypeScript type cannot be mapped to an EDM primitive. Default: 'skip' (per D-10) */\n unmappedTypeStrategy?: UnmappedTypeStrategy\n /**\n * Maximum nesting depth for deep insert POST bodies. Default: 5.\n * Per T-10-05: prevents denial-of-service via unbounded recursion.\n */\n maxDeepInsertDepth?: number\n /**\n * OData controller classes decorated with @ODataController().\n * Per D-17: forRoot() patches each controller's PATH_METADATA to prepend serviceRoot synchronously,\n * before NestJS compiles the module. Controllers not listed here will NOT have serviceRoot applied.\n */\n\n controllers?: (new (...args: any[]) => any)[]\n}\n\n/** Resolved options with all defaults applied */\nexport interface ODataModuleResolvedOptions {\n serviceRoot: string\n namespace: string\n maxTop: number\n maxExpandDepth: number\n /** Maximum nesting depth of $filter expressions (SEC-04). Default: 10 */\n maxFilterDepth: number\n unmappedTypeStrategy: UnmappedTypeStrategy\n /** Maximum nesting depth for deep insert POST bodies. Default: 5. */\n maxDeepInsertDepth: number\n}\n\nconst DEFAULT_OPTIONS: Omit<ODataModuleResolvedOptions, 'serviceRoot'> = {\n namespace: 'Default',\n maxTop: 1000,\n maxExpandDepth: 2,\n maxFilterDepth: 10,\n unmappedTypeStrategy: 'skip',\n maxDeepInsertDepth: 5,\n}\n\nconst { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =\n new ConfigurableModuleBuilder<ODataModuleOptions>()\n .setClassMethodName('forRoot')\n .setFactoryMethodName('create')\n .build()\n\n/** Internal raw options token from ConfigurableModuleBuilder — not exported publicly. */\nconst RAW_OPTIONS_TOKEN = MODULE_OPTIONS_TOKEN\n\n/** Provider that applies defaults to the raw options and exposes them under ODATA_MODULE_OPTIONS */\nconst resolvedOptionsProvider = {\n provide: ODATA_MODULE_OPTIONS,\n useFactory: (raw: ODataModuleOptions): ODataModuleResolvedOptions => ({\n ...DEFAULT_OPTIONS,\n ...raw,\n }),\n inject: [RAW_OPTIONS_TOKEN],\n}\n\n/**\n * Patch the MetadataController's PATH_METADATA to use the given serviceRoot.\n * This allows the controller to serve routes under the correct path without\n * hardcoding the serviceRoot.\n *\n * Uses Reflect.defineMetadata to set the NestJS controller path at module\n * registration time (before the DI container compiles the module).\n */\nfunction createMetadataControllerWithPath(serviceRoot: string): typeof MetadataController {\n // Normalize: remove leading slash to match NestJS path convention\n const path = serviceRoot.startsWith('/') ? serviceRoot.slice(1) : serviceRoot\n Reflect.defineMetadata(PATH_METADATA, path, MetadataController)\n return MetadataController\n}\n\n/**\n * Core OData module — ORM-agnostic.\n *\n * Usage:\n * // Root module:\n * ODataModule.forRoot({ serviceRoot: '/odata' })\n * ODataModule.forRootAsync({ useFactory: () => ({ serviceRoot: '/odata' }) })\n *\n * // Feature modules:\n * ODataModule.forFeature([myEdmEntityConfig])\n *\n * Zero TypeORM imports — per PKG-01 architecture constraint.\n */\n@Global()\n@Module({\n providers: [EdmRegistry],\n exports: [EdmRegistry],\n})\nexport class ODataModule extends ConfigurableModuleClass {\n /** Static storage for serviceRoot, set by forRoot() and read by forFeature() adapters. */\n private static _serviceRoot: string = ''\n\n /** Returns the serviceRoot registered via forRoot(). Used by adapter modules (e.g. ODataTypeOrmModule). */\n static get registeredServiceRoot(): string {\n return ODataModule._serviceRoot\n }\n\n /** Override forRoot to inject the resolved-options provider into the dynamic module. */\n static override forRoot(options: ODataModuleOptions): DynamicModule {\n // Per T-12-01: validate serviceRoot is a non-empty string\n if (!options.serviceRoot || options.serviceRoot.trim() === '') {\n throw new Error('ODataModule.forRoot(): serviceRoot must be a non-empty string')\n }\n\n // Store serviceRoot for adapter modules to read synchronously\n ODataModule._serviceRoot = options.serviceRoot\n\n const parent = super.forRoot(options)\n const metadataController = createMetadataControllerWithPath(options.serviceRoot)\n\n // Per D-17: patch @ODataController paths with serviceRoot synchronously before module compilation.\n // NestJS reads PATH_METADATA at compile time, so patching must happen here in forRoot().\n const odataControllers = options.controllers ?? []\n if (odataControllers.length > 0) {\n const root = options.serviceRoot.startsWith('/')\n ? options.serviceRoot.slice(1)\n : options.serviceRoot\n for (const ctrl of odataControllers) {\n const entitySetName = Reflect.getMetadata(ODATA_CONTROLLER_KEY, ctrl) as string | undefined\n if (entitySetName) {\n const fullPath = root ? `${root}/${entitySetName}` : entitySetName\n Reflect.defineMetadata(PATH_METADATA, fullPath, ctrl)\n }\n }\n }\n\n return {\n ...parent,\n providers: [\n ...(parent.providers ?? []),\n resolvedOptionsProvider,\n CsdlBuilder,\n ServiceDocumentBuilder,\n ],\n controllers: [...(parent.controllers ?? []), metadataController],\n exports: [...(parent.exports ?? []), ODATA_MODULE_OPTIONS, CsdlBuilder],\n }\n }\n\n /** Override forRootAsync to inject the resolved-options provider into the dynamic module. */\n static override forRootAsync(\n options: ConfigurableModuleAsyncOptions<ODataModuleOptions>,\n ): DynamicModule {\n const parent = super.forRootAsync(options)\n // For async, we can't know the serviceRoot at this point.\n // Default to empty prefix; consumers should use forRoot for synchronous config.\n const controller = MetadataController\n return {\n ...parent,\n providers: [\n ...(parent.providers ?? []),\n resolvedOptionsProvider,\n CsdlBuilder,\n ServiceDocumentBuilder,\n ],\n controllers: [...(parent.controllers ?? []), controller],\n exports: [...(parent.exports ?? []), ODATA_MODULE_OPTIONS, CsdlBuilder],\n }\n }\n\n /**\n * Register EDM entity configurations from a feature module.\n * Entities provided here are available via the EDM_ENTITY_CONFIGS injection token.\n */\n static forFeature(entityConfigs: EdmEntityConfig[]): DynamicModule {\n return {\n module: ODataModule,\n providers: [\n {\n provide: EDM_ENTITY_CONFIGS,\n useValue: entityConfigs,\n },\n EdmFeatureInitializer,\n ],\n exports: [EDM_ENTITY_CONFIGS],\n }\n }\n}\n","/**\n * Parse an OData parenthetical key string into a Record of key-value pairs.\n *\n * Formats per D-02:\n * Simple: '42' -> { Id: 42 }\n * String: \"'hello'\" -> { Name: 'hello' }\n * Composite: 'OrderId=1,ItemId=3' -> { OrderId: 1, ItemId: 3 }\n *\n * Per T-04-02: Key values are returned as typed JS values (number, string, boolean)\n * — never interpolated into SQL. Safe for parameterized ORM queries.\n */\n\n/**\n * Coerce an OData key value string to the appropriate JS type.\n * - Strips surrounding single quotes (OData string literal)\n * - Parses 'true'/'false' as boolean\n * - Parses numeric strings as numbers\n * - Otherwise returns as string\n */\nfunction coerceKeyValue(val: string): unknown {\n const trimmed = val.trim()\n\n // OData string literal: surrounded by single quotes\n if (trimmed.startsWith(\"'\") && trimmed.endsWith(\"'\")) {\n return trimmed.slice(1, -1)\n }\n\n // Boolean literals\n if (trimmed === 'true') return true\n if (trimmed === 'false') return false\n\n // Numeric: integer or decimal\n if (/^-?\\d+(\\.\\d+)?$/.test(trimmed)) {\n return Number(trimmed)\n }\n\n // Default: return as string (e.g., unquoted identifiers, GUIDs)\n return trimmed\n}\n\n/**\n * Parse an OData parenthetical key string into a Record of key-value pairs.\n *\n * @param keyStr - The key string extracted from parentheses (e.g., '42', \"'hello'\", 'OrderId=1,ItemId=3')\n * @param keyProperties - Ordered list of key property names (used for simple keys)\n * @returns Record mapping property names to coerced values\n * @throws Error if keyStr is empty\n */\nexport function parseODataKey(\n keyStr: string,\n keyProperties: readonly string[],\n): Record<string, unknown> {\n if (!keyStr) {\n throw new Error('OData key cannot be empty')\n }\n\n // Composite key: contains '=' (name=value pairs)\n if (keyStr.includes('=')) {\n const pairs = keyStr.split(',')\n const result: Record<string, unknown> = {}\n for (const pair of pairs) {\n const eqIdx = pair.indexOf('=')\n const name = pair.slice(0, eqIdx).trim()\n const value = pair.slice(eqIdx + 1).trim()\n result[name] = coerceKeyValue(value)\n }\n return result\n }\n\n // Simple key: single value, use first key property name\n const keyName = keyProperties[0]\n return { [keyName]: coerceKeyValue(keyStr) }\n}\n","export const VERSION = '0.0.1'\n\nexport * from './batch/index.js'\nexport * from './parser/index.js'\nexport * from './edm/index.js'\nexport * from './interfaces/index.js'\nexport * from './query/index.js'\nexport * from './decorators/index.js'\nexport * from './response/index.js'\nexport * from './odata.module.js'\nexport * from './tokens.js'\nexport * from './metadata/index.js'\nexport * from './utils/index.js'\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nestjs-odata/core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Core OData v4 library for NestJS — parser, EDM, decorators, module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/cberd1509/nestjs-odata.git",
|
|
9
|
+
"directory": "packages/core"
|
|
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
|
+
"rest",
|
|
20
|
+
"api",
|
|
21
|
+
"edm",
|
|
22
|
+
"metadata",
|
|
23
|
+
"nestjs-module"
|
|
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",
|
|
48
|
+
"typecheck": "tsc --noEmit",
|
|
49
|
+
"lint": "eslint ."
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
|
53
|
+
"reflect-metadata": "^0.1.13 || ^0.2.0",
|
|
54
|
+
"rxjs": "^7.0.0"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@nestjs/common": "^11.0.0",
|
|
58
|
+
"@nestjs/core": "^11.0.0",
|
|
59
|
+
"@nestjs/testing": "^11.0.0",
|
|
60
|
+
"@swc/core": "^1.15.24",
|
|
61
|
+
"@types/node": "24",
|
|
62
|
+
"@types/pluralize": "^0.0.33",
|
|
63
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
64
|
+
"reflect-metadata": "^0.2.2",
|
|
65
|
+
"rxjs": "^7.0.0",
|
|
66
|
+
"tsdown": "^0.21.7",
|
|
67
|
+
"typescript": "^5.9.3",
|
|
68
|
+
"unplugin-swc": "^1.5.9",
|
|
69
|
+
"vitest": "^3.2.4"
|
|
70
|
+
},
|
|
71
|
+
"dependencies": {
|
|
72
|
+
"pluralize": "^8.0.0"
|
|
73
|
+
}
|
|
74
|
+
}
|