@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.
@@ -0,0 +1,1402 @@
1
+ import * as _$_nestjs_common0 from "@nestjs/common";
2
+ import { ArgumentMetadata, ArgumentsHost, CallHandler, ConfigurableModuleAsyncOptions, DynamicModule, ExceptionFilter, ExecutionContext, NestInterceptor, PipeTransform } from "@nestjs/common";
3
+ import { Reflector } from "@nestjs/core";
4
+ import { Observable } from "rxjs";
5
+
6
+ //#region src/batch/batch-types.d.ts
7
+ /**
8
+ * OData v4 $batch type definitions.
9
+ *
10
+ * Per OData v4 Part 1 section 11, a batch request contains multiple
11
+ * operations in a multipart/mixed body. Operations can be:
12
+ * - Individual requests: GET, DELETE, or data modification operations
13
+ * - Changesets: groups of data modification operations that must succeed or fail atomically
14
+ *
15
+ * These types form the contract between batch-parser.ts and batch-controller.ts.
16
+ */
17
+ /**
18
+ * Represents a single parsed sub-request from a batch body.
19
+ * Produced by parseBatchBody() for individual requests and changeset sub-parts.
20
+ */
21
+ interface BatchRequestPart {
22
+ readonly kind: 'request';
23
+ /** Content-ID header value for referencing the result within a changeset */
24
+ readonly contentId?: string;
25
+ /** HTTP method: GET, POST, PATCH, PUT, DELETE */
26
+ readonly method: string;
27
+ /** The URL from the embedded HTTP request line */
28
+ readonly url: string;
29
+ /** Parsed headers from the embedded HTTP request */
30
+ readonly headers: Readonly<Record<string, string>>;
31
+ /** Raw body string for POST/PATCH/PUT requests, undefined for GET/DELETE */
32
+ readonly body?: string;
33
+ }
34
+ /**
35
+ * Represents a changeset — a group of data modification operations
36
+ * that must be executed atomically (all succeed or all roll back).
37
+ *
38
+ * Per OData v4 Part 1 section 11.7: changesets cannot contain GET requests.
39
+ */
40
+ interface BatchChangesetPart {
41
+ readonly kind: 'changeset';
42
+ /** The individual sub-requests within this changeset */
43
+ readonly parts: readonly BatchRequestPart[];
44
+ }
45
+ /**
46
+ * A part in a batch body — either an individual request or a changeset.
47
+ */
48
+ type BatchPart = BatchRequestPart | BatchChangesetPart;
49
+ /**
50
+ * The fully parsed result of a multipart/mixed batch body.
51
+ */
52
+ interface ParsedBatch {
53
+ readonly boundary: string;
54
+ readonly parts: readonly BatchPart[];
55
+ }
56
+ /**
57
+ * Represents the HTTP response for a single operation within a batch response.
58
+ */
59
+ interface BatchResponsePart {
60
+ /** Content-ID from the corresponding request part, if any */
61
+ readonly contentId?: string;
62
+ /** HTTP status code for this operation */
63
+ readonly statusCode: number;
64
+ /** Response headers for this operation */
65
+ readonly headers: Readonly<Record<string, string>>;
66
+ /** Serialized response body, if any */
67
+ readonly body?: string;
68
+ }
69
+ /**
70
+ * The complete batch response, ready to be serialized to multipart/mixed.
71
+ */
72
+ interface BatchResponse {
73
+ readonly boundary: string;
74
+ readonly parts: readonly BatchResponsePart[];
75
+ }
76
+ //#endregion
77
+ //#region src/batch/batch-parser.d.ts
78
+ /**
79
+ * Maximum number of operations allowed in a single batch request (T-05-02).
80
+ * Prevents unbounded transaction size and DoS via large batches.
81
+ */
82
+ declare const MAX_BATCH_OPERATIONS = 100;
83
+ /**
84
+ * Extract the boundary value from a Content-Type header.
85
+ *
86
+ * Handles both quoted (boundary="abc") and unquoted (boundary=abc) formats.
87
+ * Throws ODataValidationError if the boundary parameter is missing.
88
+ *
89
+ * @param contentType - The Content-Type header value (e.g., 'multipart/mixed; boundary=batch_abc123')
90
+ * @returns The boundary string value
91
+ * @throws ODataValidationError if boundary parameter is missing
92
+ */
93
+ declare function extractBoundary(contentType: string): string;
94
+ /**
95
+ * Parse a multipart/mixed batch body into structured BatchPart objects.
96
+ *
97
+ * Handles:
98
+ * - Individual request parts (kind: 'request')
99
+ * - Changesets (kind: 'changeset') containing sub-request parts
100
+ * - Both CRLF (\r\n) and LF (\n) line endings
101
+ * - Content-ID headers for changeset sub-parts
102
+ * - Embedded HTTP request parsing (METHOD URL HTTP/1.1, headers, body)
103
+ *
104
+ * Per T-05-02: rejects batches exceeding MAX_BATCH_OPERATIONS operations.
105
+ *
106
+ * @param body - The raw request body string
107
+ * @param boundary - The multipart boundary (from extractBoundary())
108
+ * @returns ParsedBatch with all parsed parts
109
+ * @throws ODataValidationError if body is malformed or exceeds limits
110
+ */
111
+ declare function parseBatchBody(body: string, boundary: string): ParsedBatch;
112
+ //#endregion
113
+ //#region src/parser/ast.d.ts
114
+ /**
115
+ * OData v4 AST (Abstract Syntax Tree) node types.
116
+ * All nodes use discriminated unions with a `kind` field as the discriminant.
117
+ *
118
+ * Per OData v4 Part 2 Section 5.1.1 — URL Conventions: $filter expressions.
119
+ */
120
+ /** Binary operator union — all OData v4 binary operators */
121
+ type BinaryOperator = 'eq' | 'ne' | 'lt' | 'le' | 'gt' | 'ge' | 'has' | 'in' | 'and' | 'or' | 'add' | 'sub' | 'mul' | 'div' | 'divby' | 'mod';
122
+ /** Unary operator union */
123
+ type UnaryOperator = 'not' | 'neg';
124
+ /** Lambda operator — collection traversal */
125
+ type LambdaOperator = 'any' | 'all';
126
+ /** Literal kind discriminant */
127
+ type LiteralKind = 'string' | 'number' | 'boolean' | 'null' | 'guid' | 'dateTimeOffset';
128
+ /** Binary expression: left op right (e.g., Price gt 5) */
129
+ interface BinaryExprNode {
130
+ readonly kind: 'BinaryExpr';
131
+ readonly operator: BinaryOperator;
132
+ readonly left: FilterNode;
133
+ readonly right: FilterNode;
134
+ }
135
+ /** Unary expression: op operand (e.g., not Active) */
136
+ interface UnaryExprNode {
137
+ readonly kind: 'UnaryExpr';
138
+ readonly operator: UnaryOperator;
139
+ readonly operand: FilterNode;
140
+ }
141
+ /** Function call: name(args...) (e.g., contains(Name,'widget')) */
142
+ interface FunctionCallNode {
143
+ readonly kind: 'FunctionCall';
144
+ readonly name: string;
145
+ readonly args: FilterNode[];
146
+ }
147
+ /** Property access: path segments (e.g., Category/Name) */
148
+ interface PropertyAccessNode {
149
+ readonly kind: 'PropertyAccess';
150
+ readonly path: string[];
151
+ }
152
+ /** Literal value: string, number, boolean, null, guid, dateTimeOffset */
153
+ interface LiteralNode {
154
+ readonly kind: 'Literal';
155
+ readonly literalKind: LiteralKind;
156
+ readonly value: string | number | boolean | null;
157
+ }
158
+ /**
159
+ * Lambda expression: collection/lambda(variable:predicate)
160
+ * e.g., Tags/any(t:t/Name eq 'electronics')
161
+ * When predicate is null, represents Tags/any() (no predicate variant).
162
+ */
163
+ interface LambdaExprNode {
164
+ readonly kind: 'LambdaExpr';
165
+ readonly operator: LambdaOperator;
166
+ /** Collection property path (e.g., 'Tags') */
167
+ readonly collection: string;
168
+ /** Lambda variable (e.g., 't'), null for zero-arg any() */
169
+ readonly variable: string | null;
170
+ /** Lambda predicate expression, null for zero-arg any() */
171
+ readonly predicate: FilterNode | null;
172
+ }
173
+ /**
174
+ * Union of all filter expression AST node types.
175
+ * Use the `kind` discriminant for type narrowing.
176
+ */
177
+ type FilterNode = BinaryExprNode | UnaryExprNode | FunctionCallNode | PropertyAccessNode | LiteralNode | LambdaExprNode;
178
+ /** Sort direction */
179
+ type OrderByDirection = 'asc' | 'desc';
180
+ /** Single $orderby clause item */
181
+ interface OrderByItem {
182
+ readonly expression: FilterNode;
183
+ readonly direction: OrderByDirection;
184
+ }
185
+ /** Parsed $orderby value */
186
+ interface OrderByNode {
187
+ readonly items: OrderByItem[];
188
+ }
189
+ /** Single $select path item (e.g., Name or Category/Name) */
190
+ interface SelectItem {
191
+ readonly path: string[];
192
+ }
193
+ /** Parsed $select value */
194
+ interface SelectNode {
195
+ /** Individual property paths — present when not SelectAll */
196
+ readonly items?: SelectItem[];
197
+ /** True when $select=* */
198
+ readonly all?: boolean;
199
+ }
200
+ /** Single $expand item — one navigation property with optional nested query options */
201
+ interface ExpandItem {
202
+ readonly navigationProperty: string;
203
+ readonly filter?: FilterNode;
204
+ readonly select?: SelectNode;
205
+ readonly orderBy?: OrderByItem[];
206
+ readonly top?: number;
207
+ readonly skip?: number;
208
+ readonly expand?: ExpandNode;
209
+ }
210
+ /** Parsed $expand value — list of expand items */
211
+ interface ExpandNode {
212
+ readonly items: readonly ExpandItem[];
213
+ }
214
+ /**
215
+ * Aggregate of parsed OData query option values.
216
+ * Each field is optional — only present when the query string contains it.
217
+ */
218
+ interface QueryOptions {
219
+ readonly filter?: FilterNode;
220
+ readonly orderBy?: OrderByItem[];
221
+ readonly select?: SelectNode;
222
+ readonly top?: number;
223
+ readonly skip?: number;
224
+ readonly expand?: ExpandNode;
225
+ }
226
+ /** A single search term, optionally negated. Quoted phrases are stored as a single value. */
227
+ interface SearchTermNode {
228
+ readonly kind: 'SearchTerm';
229
+ readonly value: string;
230
+ readonly negated?: boolean;
231
+ }
232
+ /** Binary search expression combining two search nodes with AND or OR. */
233
+ interface SearchBinaryNode {
234
+ readonly kind: 'SearchBinary';
235
+ readonly operator: 'AND' | 'OR';
236
+ readonly left: SearchNode;
237
+ readonly right: SearchNode;
238
+ }
239
+ /** Union of all $search expression node types. */
240
+ type SearchNode = SearchTermNode | SearchBinaryNode;
241
+ /**
242
+ * A single aggregate method expression: `property with method as alias`
243
+ * or `$count as alias` (property='$count', method='count').
244
+ */
245
+ interface AggregateExpression {
246
+ readonly property: string;
247
+ readonly method: 'sum' | 'count' | 'avg' | 'min' | 'max' | 'countdistinct';
248
+ readonly alias: string;
249
+ }
250
+ /** filter() transformation step — applies a filter expression to the current set. */
251
+ interface ApplyFilterStep {
252
+ readonly kind: 'ApplyFilter';
253
+ readonly filter: FilterNode;
254
+ }
255
+ /**
256
+ * groupby() transformation step — partitions by property list with optional
257
+ * aggregate expressions inside.
258
+ */
259
+ interface ApplyGroupByStep {
260
+ readonly kind: 'ApplyGroupBy';
261
+ readonly properties: string[];
262
+ readonly aggregate?: readonly AggregateExpression[];
263
+ }
264
+ /** aggregate() transformation step — computes aggregate values across the set. */
265
+ interface ApplyAggregateStep {
266
+ readonly kind: 'ApplyAggregate';
267
+ readonly expressions: readonly AggregateExpression[];
268
+ }
269
+ /** Union of all $apply transformation step types. */
270
+ type ApplyStep = ApplyFilterStep | ApplyGroupByStep | ApplyAggregateStep;
271
+ /** Root $apply node — an ordered pipeline of transformation steps. */
272
+ interface ApplyNode {
273
+ readonly steps: readonly ApplyStep[];
274
+ }
275
+ //#endregion
276
+ //#region src/parser/visitor.d.ts
277
+ /**
278
+ * Generic visitor interface for FilterNode AST traversal.
279
+ * Implement this interface to transform or analyze OData filter expressions.
280
+ *
281
+ * @template T - The return type of each visit method
282
+ */
283
+ interface FilterVisitor<T> {
284
+ visitBinaryExpr(node: BinaryExprNode): T;
285
+ visitUnaryExpr(node: UnaryExprNode): T;
286
+ visitFunctionCall(node: FunctionCallNode): T;
287
+ visitLambdaExpr(node: LambdaExprNode): T;
288
+ visitPropertyAccess(node: PropertyAccessNode): T;
289
+ visitLiteral(node: LiteralNode): T;
290
+ }
291
+ /**
292
+ * Dispatch a FilterNode to the appropriate visitor method.
293
+ * Convenience function to avoid writing switch statements in every visitor.
294
+ */
295
+ declare function acceptVisitor<T>(node: FilterNode, visitor: FilterVisitor<T>): T;
296
+ //#endregion
297
+ //#region src/parser/errors.d.ts
298
+ /**
299
+ * OData parser error types.
300
+ * ODataParseError carries position information for diagnostic messages.
301
+ */
302
+ /**
303
+ * Thrown when the lexer or parser encounters malformed OData query syntax.
304
+ *
305
+ * The `position` field indicates the character offset in the input string
306
+ * where the error was detected. The `token` field (if available) holds
307
+ * the token that triggered the error.
308
+ *
309
+ * Note: position info is intentionally included — the input is the user's own
310
+ * query string, not server-internal data, so there is no information disclosure risk.
311
+ */
312
+ declare class ODataParseError extends Error {
313
+ readonly position: number;
314
+ readonly token: unknown;
315
+ readonly queryContext?: string | undefined;
316
+ constructor(message: string, position: number, token?: unknown, queryContext?: string | undefined);
317
+ /**
318
+ * Create an ODataParseError with a context snippet extracted from the query string.
319
+ * Extracts ~20 characters before and after the error position for diagnostic context.
320
+ *
321
+ * Per threat model T-12-05: context snippet shows only the user's own query input
322
+ * (already known to them) — never includes stack traces or internal paths.
323
+ *
324
+ * @param message - Base error message
325
+ * @param position - Character offset where the error was detected
326
+ * @param queryString - The full query string for context extraction
327
+ * @param token - Optional token that triggered the error
328
+ */
329
+ static withContext(message: string, position: number, queryString: string, token?: unknown): ODataParseError;
330
+ }
331
+ //#endregion
332
+ //#region src/parser/lexer.d.ts
333
+ /**
334
+ * OData v4 Lexer (tokenizer).
335
+ * Converts an OData query expression string into a token stream.
336
+ *
337
+ * Character-by-character scanning with keyword disambiguation.
338
+ * OData keyword recognition per Part 2 Section 5.1.1 ABNF grammar.
339
+ */
340
+ /** All token kinds produced by the OData lexer */
341
+ declare enum TokenKind {
342
+ STRING_LITERAL = "STRING_LITERAL",
343
+ INT_LITERAL = "INT_LITERAL",
344
+ DECIMAL_LITERAL = "DECIMAL_LITERAL",
345
+ BOOL_LITERAL = "BOOL_LITERAL",
346
+ NULL_LITERAL = "NULL_LITERAL",
347
+ GUID_LITERAL = "GUID_LITERAL",
348
+ DATETIME_LITERAL = "DATETIME_LITERAL",
349
+ IDENTIFIER = "IDENTIFIER",
350
+ OPEN_PAREN = "OPEN_PAREN",
351
+ CLOSE_PAREN = "CLOSE_PAREN",
352
+ COMMA = "COMMA",
353
+ SLASH = "SLASH",
354
+ COLON = "COLON",
355
+ STAR = "STAR",
356
+ AND = "AND",
357
+ OR = "OR",
358
+ NOT = "NOT",
359
+ EQ = "EQ",
360
+ NE = "NE",
361
+ LT = "LT",
362
+ LE = "LE",
363
+ GT = "GT",
364
+ GE = "GE",
365
+ HAS = "HAS",
366
+ IN = "IN",
367
+ ADD = "ADD",
368
+ SUB = "SUB",
369
+ MUL = "MUL",
370
+ DIV = "DIV",
371
+ DIVBY = "DIVBY",
372
+ MOD = "MOD",
373
+ EOF = "EOF"
374
+ }
375
+ /** A single lexer token with kind, value, and source position */
376
+ interface Token {
377
+ readonly kind: TokenKind;
378
+ readonly value: string | number | boolean | null;
379
+ readonly position: number;
380
+ }
381
+ /**
382
+ * Tokenize an OData query expression string into a token array.
383
+ * The last token is always EOF.
384
+ *
385
+ * @param input - Raw OData expression string (e.g., "Price gt 5 and Active eq true")
386
+ * @returns Array of tokens ending with EOF
387
+ * @throws ODataParseError on unrecognized characters
388
+ */
389
+ declare function tokenize(input: string): Token[];
390
+ //#endregion
391
+ //#region src/parser/parser.d.ts
392
+ /**
393
+ * Parse a standalone OData $filter expression string into a FilterNode AST.
394
+ *
395
+ * @param input - Raw filter expression (e.g., "Price gt 5 and Active eq true")
396
+ * @returns Root FilterNode of the parsed expression
397
+ * @throws ODataParseError on syntax errors
398
+ */
399
+ declare function parseFilter(input: string): FilterNode;
400
+ /**
401
+ * Parse an OData query string into a QueryOptions object.
402
+ * Handles $filter, $orderby, $select, $top, $skip.
403
+ * Unknown query options are silently ignored.
404
+ *
405
+ * @param queryString - Raw query string, with or without leading '?'
406
+ * @returns Parsed QueryOptions (fields are optional — only present when found in query)
407
+ * @throws ODataParseError on syntax errors
408
+ */
409
+ declare function parseQuery(queryString: string): QueryOptions;
410
+ //#endregion
411
+ //#region src/parser/search-parser.d.ts
412
+ /**
413
+ * Parse an OData $search query string into a SearchNode AST.
414
+ *
415
+ * @param input - The raw $search query string value (without the $search= prefix)
416
+ * @throws ODataParseError if the input is empty, malformed, or exceeds MAX_SEARCH_DEPTH
417
+ */
418
+ declare function parseSearch(input: string): SearchNode;
419
+ //#endregion
420
+ //#region src/parser/apply-parser.d.ts
421
+ /**
422
+ * Parse an OData $apply transformation pipeline into an ApplyNode AST.
423
+ *
424
+ * Steps are separated by '/' at depth 0. Each step is one of:
425
+ * - filter(...)
426
+ * - aggregate(...)
427
+ * - groupby((...),aggregate(...))
428
+ *
429
+ * @param input - The raw $apply query string value (without the $apply= prefix)
430
+ * @throws ODataParseError for empty input, unrecognized steps, invalid aliases,
431
+ * or post-groupby filter steps (HAVING not supported)
432
+ */
433
+ declare function parseApply(input: string): ApplyNode;
434
+ //#endregion
435
+ //#region src/edm/edm-types.d.ts
436
+ /**
437
+ * OData v4 EDM (Entity Data Model) primitive type system.
438
+ * All 15 OData v4 primitive types per the OASIS OData v4 specification.
439
+ * Note: Edm.DateTime is NOT included — use Edm.DateTimeOffset per OData v4.
440
+ */
441
+ /** All 15 OData v4 EDM primitive types */
442
+ type EdmPrimitiveType = 'Edm.Binary' | 'Edm.Boolean' | 'Edm.Byte' | 'Edm.Date' | 'Edm.DateTimeOffset' | 'Edm.Decimal' | 'Edm.Double' | 'Edm.Guid' | 'Edm.Int16' | 'Edm.Int32' | 'Edm.Int64' | 'Edm.SByte' | 'Edm.Single' | 'Edm.String' | 'Edm.TimeOfDay';
443
+ /** A structural property of an EDM entity type */
444
+ interface EdmProperty {
445
+ readonly name: string;
446
+ readonly type: EdmPrimitiveType;
447
+ readonly nullable: boolean;
448
+ readonly precision?: number;
449
+ readonly scale?: number;
450
+ readonly maxLength?: number;
451
+ }
452
+ /** A navigation property linking two EDM entity types */
453
+ interface EdmNavigationProperty {
454
+ readonly name: string;
455
+ readonly type: string;
456
+ readonly nullable: boolean;
457
+ readonly isCollection: boolean;
458
+ readonly partner?: string;
459
+ }
460
+ /** Strategy when a TypeScript type cannot be mapped to an EDM primitive type */
461
+ type UnmappedTypeStrategy = 'skip' | 'string-fallback' | 'error';
462
+ //#endregion
463
+ //#region src/edm/edm-entity-type.d.ts
464
+ /**
465
+ * Represents an OData v4 EntityType in the Entity Data Model.
466
+ * Immutable — always create new instances rather than mutating existing ones.
467
+ */
468
+ interface EdmEntityType {
469
+ readonly name: string;
470
+ readonly namespace: string;
471
+ readonly properties: readonly EdmProperty[];
472
+ readonly navigationProperties: readonly EdmNavigationProperty[];
473
+ readonly keyProperties: readonly string[];
474
+ readonly isReadOnly: boolean;
475
+ }
476
+ //#endregion
477
+ //#region src/edm/edm-entity-set.d.ts
478
+ /**
479
+ * Represents an OData v4 EntitySet in the Entity Data Model.
480
+ * An EntitySet is a named collection of entities of a given type.
481
+ */
482
+ interface EdmEntitySet {
483
+ readonly name: string;
484
+ readonly entityTypeName: string;
485
+ readonly namespace: string;
486
+ readonly isReadOnly: boolean;
487
+ }
488
+ /**
489
+ * Full entity configuration combining type and set metadata.
490
+ * Used by IEdmDeriver to pass complete entity information to the registry.
491
+ */
492
+ interface EdmEntityConfig {
493
+ readonly entityTypeName: string;
494
+ readonly entitySetName: string;
495
+ readonly properties: readonly EdmProperty[];
496
+ readonly navigationProperties: readonly EdmNavigationProperty[];
497
+ readonly keyProperties: readonly string[];
498
+ readonly isReadOnly: boolean;
499
+ }
500
+ /**
501
+ * Virtual view — a projection of an existing entity type exposing only
502
+ * a subset of properties as its own EntitySet. Per design decisions D-22, D-23.
503
+ */
504
+ interface EdmVirtualView {
505
+ readonly name: string;
506
+ readonly sourceEntityTypeName: string;
507
+ readonly exposedProperties: readonly string[];
508
+ readonly entitySetName: string;
509
+ }
510
+ //#endregion
511
+ //#region src/query/odata-query.types.d.ts
512
+ /**
513
+ * Per-entity security option overrides.
514
+ * When set via EdmRegistry, these take precedence over global ODataModuleOptions values.
515
+ *
516
+ * Per D-07: allows individual entity sets to have tighter (or looser) limits than the
517
+ * global defaults without modifying the global config.
518
+ */
519
+ interface ODataEntitySecurityOptions {
520
+ readonly maxTop?: number;
521
+ readonly maxExpandDepth?: number;
522
+ readonly maxFilterDepth?: number;
523
+ }
524
+ /**
525
+ * Typed OData query object produced by ODataQueryPipe.
526
+ * All optional fields are only present when the corresponding query option
527
+ * was supplied in the request.
528
+ *
529
+ * Per D-14: entitySetName is required for context URL and validation.
530
+ */
531
+ interface ODataQuery {
532
+ readonly filter?: FilterNode;
533
+ readonly select?: SelectNode;
534
+ readonly orderBy?: OrderByItem[];
535
+ readonly top?: number;
536
+ readonly skip?: number;
537
+ /** True when $count=true was present in the query string */
538
+ readonly count?: boolean;
539
+ /** Name of the entity set — required for context URL construction and field validation */
540
+ readonly entitySetName: string;
541
+ /** Parsed $expand value — navigation properties to expand */
542
+ readonly expand?: ExpandNode;
543
+ /** Parsed $search value — full-text search expression */
544
+ readonly search?: SearchNode;
545
+ /** Parsed $apply value — aggregation/transformation pipeline */
546
+ readonly apply?: ApplyNode;
547
+ }
548
+ /**
549
+ * Result of executing a translated OData query.
550
+ * Used by IQueryTranslator.execute() to return structured data to the controller.
551
+ *
552
+ * select is included so the controller can build the correct @odata.context URL.
553
+ */
554
+ interface ODataQueryResult<T = unknown> {
555
+ readonly items: T[];
556
+ readonly count?: number;
557
+ readonly nextLink?: string;
558
+ readonly select?: SelectNode;
559
+ /** True when the result was produced by a $apply aggregation pipeline */
560
+ readonly isAggregated?: boolean;
561
+ /** Property names present in aggregation output (for context URL construction) */
562
+ readonly applyProperties?: string[];
563
+ }
564
+ //#endregion
565
+ //#region src/edm/edm-registry.d.ts
566
+ /**
567
+ * EdmRegistry — NestJS injectable singleton that stores all registered
568
+ * OData entity types and entity sets.
569
+ *
570
+ * Per threat model T-02-01: throws on duplicate registration to prevent
571
+ * silent override of entity types (Tampering mitigation).
572
+ */
573
+ declare class EdmRegistry {
574
+ private readonly entityTypes;
575
+ private readonly entitySets;
576
+ /** Per-entity security option overrides keyed by entitySetName (per D-07) */
577
+ private readonly entitySecurityOptions;
578
+ /**
579
+ * Register an entity type and its corresponding entity set.
580
+ *
581
+ * Idempotent: if the same entity type name is already registered (e.g. from multiple
582
+ * ODataTypeOrmModule.forFeature() calls in different feature modules), the registration
583
+ * is silently skipped. This supports the common pattern of importing forFeature() in
584
+ * both a root AppModule and a feature module for the same entity.
585
+ *
586
+ * Throws only if a DIFFERENT entity type is registered under the same name (name collision).
587
+ */
588
+ register(entityType: EdmEntityType, entitySet: EdmEntitySet): void;
589
+ /** Retrieve a registered entity type by name. Returns undefined if not found. */
590
+ getEntityType(name: string): EdmEntityType | undefined;
591
+ /** Retrieve a registered entity set by name. Returns undefined if not found. */
592
+ getEntitySet(name: string): EdmEntitySet | undefined;
593
+ /** All registered entity types as a read-only map. */
594
+ getEntityTypes(): ReadonlyMap<string, EdmEntityType>;
595
+ /** All registered entity sets as a read-only map. */
596
+ getEntitySets(): ReadonlyMap<string, EdmEntitySet>;
597
+ /**
598
+ * Store per-entity security option overrides for a given entity set name.
599
+ * These override global ODataModuleResolvedOptions values (per D-07).
600
+ */
601
+ setEntitySecurityOptions(entitySetName: string, options: ODataEntitySecurityOptions): void;
602
+ /**
603
+ * Retrieve per-entity security options for a given entity set name.
604
+ * Returns undefined if no overrides have been set.
605
+ */
606
+ getEntitySecurityOptions(entitySetName: string): ODataEntitySecurityOptions | undefined;
607
+ }
608
+ //#endregion
609
+ //#region src/edm/pluralize.d.ts
610
+ /**
611
+ * Pluralize an entity class name to produce an EntitySet name.
612
+ * Uses the `pluralize` library which handles irregular forms (Person → People,
613
+ * Category → Categories, Index → Indices, etc.).
614
+ *
615
+ * @param name - Entity class name in PascalCase (e.g., 'Product', 'OrderItem')
616
+ * @returns Pluralized entity set name (e.g., 'Products', 'OrderItems')
617
+ */
618
+ declare function pluralizeEntityName(name: string): string;
619
+ //#endregion
620
+ //#region src/interfaces/edm-deriver.interface.d.ts
621
+ /** A generic constructor type representing an entity class */
622
+ type EntityClass = new (...args: unknown[]) => unknown;
623
+ /**
624
+ * IEdmDeriver — adapter seam interface for ORM-specific EDM derivation.
625
+ * Implementations live in adapter packages (e.g., @nestjs-odata/typeorm).
626
+ * Core has zero ORM dependencies — adapters inject their implementation
627
+ * via the EDM_DERIVER token.
628
+ */
629
+ interface IEdmDeriver {
630
+ deriveEntityTypes(entityClasses: EntityClass[]): EdmEntityConfig[];
631
+ }
632
+ /** NestJS injection token for IEdmDeriver */
633
+ declare const EDM_DERIVER: unique symbol;
634
+ //#endregion
635
+ //#region src/interfaces/query-translator.interface.d.ts
636
+ /**
637
+ * IQueryTranslator — adapter seam interface for ORM-specific query translation.
638
+ *
639
+ * Implementations live in adapter packages (e.g., @nestjs-odata/typeorm).
640
+ * The two-method design (translate + execute) separates AST-to-query translation
641
+ * from query execution, enabling independent unit testing of each step.
642
+ *
643
+ * Generic parameter TQuery is the ORM-specific query type (e.g., TypeORM SelectQueryBuilder).
644
+ */
645
+ interface IQueryTranslator<TQuery = unknown> {
646
+ /** Translate a typed OData query AST into an ORM-specific query object */
647
+ translate(query: ODataQuery, entityType: EdmEntityType): TQuery;
648
+ /** Execute the translated query and return structured results */
649
+ execute(translatedQuery: TQuery, includeCount: boolean): Promise<ODataQueryResult>;
650
+ }
651
+ /** NestJS injection token for IQueryTranslator */
652
+ declare const QUERY_TRANSLATOR: unique symbol;
653
+ //#endregion
654
+ //#region src/interfaces/etag.interface.d.ts
655
+ /**
656
+ * Injection token for IETagProvider.
657
+ * Use this token when registering or injecting the ETag provider via NestJS DI.
658
+ */
659
+ declare const ETAG_PROVIDER: unique symbol;
660
+ /**
661
+ * Interface for computing and validating ETags for OData entities.
662
+ *
663
+ * Implementations are adapter-specific (e.g., TypeOrmETagProvider).
664
+ * Core uses this interface without importing any ORM code.
665
+ */
666
+ interface IETagProvider {
667
+ /**
668
+ * Get the ETag column name for an entity set.
669
+ * Returns undefined if the entity set is not ETag-enabled
670
+ * (no @UpdateDateColumn or @ODataETag decorator found).
671
+ */
672
+ getETagColumn(entitySetName: string): string | undefined;
673
+ /**
674
+ * Compute a weak ETag string from an entity's ETag column value.
675
+ * Format: W/"<value>"
676
+ */
677
+ computeETag(entity: Record<string, unknown>, etagColumn: string): string;
678
+ /**
679
+ * Validate an If-Match header value against the current entity state.
680
+ * Returns true if the ETag matches (proceed with mutation),
681
+ * false if the ETag is stale (return 412).
682
+ */
683
+ validateIfMatch(ifMatchValue: string, entity: Record<string, unknown>, etagColumn: string): boolean;
684
+ }
685
+ //#endregion
686
+ //#region src/interfaces/search.interface.d.ts
687
+ /**
688
+ * Injection token for ISearchProvider.
689
+ * Use this token when registering or injecting the search provider via NestJS DI.
690
+ */
691
+ declare const SEARCH_PROVIDER: unique symbol;
692
+ /**
693
+ * Interface for translating a parsed $search expression into a SQL condition.
694
+ *
695
+ * Implementations are adapter-specific (e.g., TypeOrmSearchProvider).
696
+ * Core uses this interface without importing any ORM code.
697
+ *
698
+ * Returns null if the search cannot be applied (e.g., no searchable fields defined).
699
+ */
700
+ interface ISearchProvider {
701
+ /**
702
+ * Build a SQL WHERE condition fragment from a parsed $search expression.
703
+ *
704
+ * @param searchNode - The parsed $search AST node
705
+ * @param entitySetName - The entity set being queried
706
+ * @param alias - The query builder alias for the entity table
707
+ * @returns An object with the SQL condition string and named parameters,
708
+ * or null if search cannot be applied
709
+ */
710
+ buildSearchCondition(searchNode: SearchNode, entitySetName: string, alias: string): {
711
+ condition: string;
712
+ params: Record<string, unknown>;
713
+ } | null;
714
+ }
715
+ //#endregion
716
+ //#region src/query/odata-validation.error.d.ts
717
+ /**
718
+ * OData validation error types.
719
+ * ODataValidationError is distinct from ODataParseError — it represents
720
+ * semantic validation failures (unknown properties, type mismatches) rather
721
+ * than syntax errors.
722
+ *
723
+ * Per threat model T-03-02: includes property name and entity type name
724
+ * (user's own query input) but never stack traces or internal paths.
725
+ */
726
+ /**
727
+ * Thrown when field name validation fails against the EdmRegistry.
728
+ *
729
+ * The `entityTypeName` and `propertyName` fields provide diagnostic context
730
+ * for the user's own query (not internal server data).
731
+ */
732
+ declare class ODataValidationError extends Error {
733
+ readonly entityTypeName: string;
734
+ readonly propertyName: string;
735
+ readonly availableProperties?: readonly string[] | undefined;
736
+ constructor(message: string, entityTypeName: string, propertyName: string, availableProperties?: readonly string[] | undefined);
737
+ }
738
+ //#endregion
739
+ //#region src/tokens.d.ts
740
+ /**
741
+ * DI injection token for the array of EdmEntityConfig objects
742
+ * registered via ODataModule.forFeature().
743
+ */
744
+ declare const EDM_ENTITY_CONFIGS: unique symbol;
745
+ /**
746
+ * DI injection token for the fully-resolved ODataModuleOptions (with defaults applied).
747
+ * Defined here (not in odata.module.ts) to avoid circular imports with metadata builders.
748
+ */
749
+ declare const ODATA_MODULE_OPTIONS: unique symbol;
750
+ //#endregion
751
+ //#region src/odata.module.d.ts
752
+ /**
753
+ * Configuration options for ODataModule.forRoot().
754
+ *
755
+ * Per threat model T-02-03: serviceRoot must be a non-empty string.
756
+ * Per threat model T-02-04: maxTop and maxExpandDepth have safe defaults.
757
+ */
758
+ interface ODataModuleOptions {
759
+ /** Base path for the OData service, e.g. '/odata' */
760
+ serviceRoot: string;
761
+ /** EDM namespace for all entity types. Default: 'Default' (per D-08) */
762
+ namespace?: string;
763
+ /** Maximum number of entities returned per query. Default: 1000 */
764
+ maxTop?: number;
765
+ /** Maximum depth of $expand nesting. Default: 2 */
766
+ maxExpandDepth?: number;
767
+ /** Maximum nesting depth of $filter expressions. Default: 10 (per SEC-04) */
768
+ maxFilterDepth?: number;
769
+ /** Strategy when a TypeScript type cannot be mapped to an EDM primitive. Default: 'skip' (per D-10) */
770
+ unmappedTypeStrategy?: UnmappedTypeStrategy;
771
+ /**
772
+ * Maximum nesting depth for deep insert POST bodies. Default: 5.
773
+ * Per T-10-05: prevents denial-of-service via unbounded recursion.
774
+ */
775
+ maxDeepInsertDepth?: number;
776
+ /**
777
+ * OData controller classes decorated with @ODataController().
778
+ * Per D-17: forRoot() patches each controller's PATH_METADATA to prepend serviceRoot synchronously,
779
+ * before NestJS compiles the module. Controllers not listed here will NOT have serviceRoot applied.
780
+ */
781
+ controllers?: (new (...args: any[]) => any)[];
782
+ }
783
+ /** Resolved options with all defaults applied */
784
+ interface ODataModuleResolvedOptions {
785
+ serviceRoot: string;
786
+ namespace: string;
787
+ maxTop: number;
788
+ maxExpandDepth: number;
789
+ /** Maximum nesting depth of $filter expressions (SEC-04). Default: 10 */
790
+ maxFilterDepth: number;
791
+ unmappedTypeStrategy: UnmappedTypeStrategy;
792
+ /** Maximum nesting depth for deep insert POST bodies. Default: 5. */
793
+ maxDeepInsertDepth: number;
794
+ }
795
+ declare const ConfigurableModuleClass: _$_nestjs_common0.ConfigurableModuleCls<ODataModuleOptions, "forRoot", "create", {}>;
796
+ /**
797
+ * Core OData module — ORM-agnostic.
798
+ *
799
+ * Usage:
800
+ * // Root module:
801
+ * ODataModule.forRoot({ serviceRoot: '/odata' })
802
+ * ODataModule.forRootAsync({ useFactory: () => ({ serviceRoot: '/odata' }) })
803
+ *
804
+ * // Feature modules:
805
+ * ODataModule.forFeature([myEdmEntityConfig])
806
+ *
807
+ * Zero TypeORM imports — per PKG-01 architecture constraint.
808
+ */
809
+ declare class ODataModule extends ConfigurableModuleClass {
810
+ /** Static storage for serviceRoot, set by forRoot() and read by forFeature() adapters. */
811
+ private static _serviceRoot;
812
+ /** Returns the serviceRoot registered via forRoot(). Used by adapter modules (e.g. ODataTypeOrmModule). */
813
+ static get registeredServiceRoot(): string;
814
+ /** Override forRoot to inject the resolved-options provider into the dynamic module. */
815
+ static forRoot(options: ODataModuleOptions): DynamicModule;
816
+ /** Override forRootAsync to inject the resolved-options provider into the dynamic module. */
817
+ static forRootAsync(options: ConfigurableModuleAsyncOptions<ODataModuleOptions>): DynamicModule;
818
+ /**
819
+ * Register EDM entity configurations from a feature module.
820
+ * Entities provided here are available via the EDM_ENTITY_CONFIGS injection token.
821
+ */
822
+ static forFeature(entityConfigs: EdmEntityConfig[]): DynamicModule;
823
+ }
824
+ //#endregion
825
+ //#region src/query/odata-query.pipe.d.ts
826
+ /**
827
+ * ODataQueryPipe — NestJS PipeTransform that converts raw Express query params
828
+ * (req.query as Record<string, string>) into a typed, validated ODataQuery.
829
+ *
830
+ * Per threat model T-03-01: validates all $filter/$select/$orderby property
831
+ * references against EdmRegistry. Unknown properties throw ODataValidationError.
832
+ *
833
+ * Per threat model T-03-03: clamps $top to maxTop from ODataModuleResolvedOptions.
834
+ *
835
+ * Zero TypeORM imports — per PKG-01 architecture constraint.
836
+ */
837
+ declare class ODataQueryPipe implements PipeTransform<Record<string, string>, ODataQuery> {
838
+ private readonly edmRegistry;
839
+ private readonly options;
840
+ constructor(edmRegistry: EdmRegistry, options: ODataModuleResolvedOptions);
841
+ transform(value: Record<string, string>, metadata: ArgumentMetadata): ODataQuery;
842
+ /**
843
+ * Recursively walk a FilterNode tree and throw ODataValidationError for any
844
+ * PropertyAccessNode whose path[0] is not in entityType.properties.
845
+ *
846
+ * Per D-10: validates property references before any DB query is constructed.
847
+ * Per T-03-01: unknown properties throw ODataValidationError (not a generic error).
848
+ */
849
+ private validateFilterNode;
850
+ /**
851
+ * Validate $expand navigation property names against the entity type's navigationProperties.
852
+ * Validates top-level navigation properties only — nested expand validation is handled
853
+ * by TypeOrmExpandVisitor at translation time with full EDM registry access.
854
+ *
855
+ * Per T-04-09: prevents users from expanding properties not declared as navigation properties.
856
+ * Per D-10: validates property references before any DB query is constructed.
857
+ */
858
+ private validateExpandNode;
859
+ /**
860
+ * Validate a $select item path[0] against entityType.properties.
861
+ * Throws ODataValidationError for unknown property names.
862
+ */
863
+ private validateSelectItem;
864
+ }
865
+ //#endregion
866
+ //#region src/decorators/metadata-keys.d.ts
867
+ /** Metadata key for @EdmType() property decorator — stores EdmTypeOptions per property */
868
+ declare const EDM_TYPE_KEY: unique symbol;
869
+ /** Metadata key for @ODataExclude() property decorator — stores Set<string> of excluded property names */
870
+ declare const ODATA_EXCLUDE_KEY: unique symbol;
871
+ /** Metadata key for @ODataEntitySet() class decorator — stores the entity set name string */
872
+ declare const ODATA_ENTITY_SET_KEY: unique symbol;
873
+ /** Metadata key for @ODataKey() property decorator — stores string[] of key property names */
874
+ declare const ODATA_KEY_KEY: unique symbol;
875
+ /** Metadata key for @ODataView() class decorator — stores ODataViewOptions */
876
+ declare const ODATA_VIEW_KEY: unique symbol;
877
+ /** Metadata key for @ODataGet() method decorator — marks a route as an OData route */
878
+ declare const ODATA_ROUTE_KEY: unique symbol;
879
+ /** Metadata key for @ODataController() class decorator — stores the entity set name string */
880
+ declare const ODATA_CONTROLLER_KEY: unique symbol;
881
+ /** Metadata key for @ODataETag() property decorator — stores the ETag source property name */
882
+ declare const ODATA_ETAG_KEY: unique symbol;
883
+ /** Metadata key for @ODataSearchable() property decorator — stores string[] of searchable property names */
884
+ declare const ODATA_SEARCHABLE_KEY: unique symbol;
885
+ //#endregion
886
+ //#region src/decorators/odata-etag.decorator.d.ts
887
+ /**
888
+ * Marks a property as the ETag source for concurrency control.
889
+ * Per D-03: opt-in per entity. Only entities with @ODataETag or @UpdateDateColumn get ETags.
890
+ *
891
+ * Usage:
892
+ * class Product {
893
+ * @ODataETag()
894
+ * version: number
895
+ * }
896
+ */
897
+ declare function ODataETag(): PropertyDecorator;
898
+ /**
899
+ * Get the ETag property name for a given entity class.
900
+ * Returns undefined if the entity does not have @ODataETag.
901
+ */
902
+ declare function getETagProperty(target: new (...args: unknown[]) => unknown): string | undefined;
903
+ //#endregion
904
+ //#region src/decorators/odata-searchable.decorator.d.ts
905
+ /**
906
+ * Marks a property as searchable via OData $search.
907
+ * Multiple properties on the same entity can be decorated — all are stored.
908
+ *
909
+ * Usage:
910
+ * class Product {
911
+ * @ODataSearchable()
912
+ * name: string
913
+ *
914
+ * @ODataSearchable()
915
+ * description: string
916
+ * }
917
+ */
918
+ declare function ODataSearchable(): PropertyDecorator;
919
+ /**
920
+ * Get the list of searchable property names for a given entity class.
921
+ * Returns an empty array if no properties are decorated with @ODataSearchable().
922
+ */
923
+ declare function getSearchableProperties(target: new (...args: unknown[]) => unknown): string[];
924
+ //#endregion
925
+ //#region src/decorators/edm-type.decorator.d.ts
926
+ /** Options for the @EdmType property decorator */
927
+ interface EdmTypeOptions {
928
+ readonly type: string;
929
+ readonly precision?: number;
930
+ readonly scale?: number;
931
+ readonly maxLength?: number;
932
+ }
933
+ /**
934
+ * @EdmType() — override the EDM primitive type for a property.
935
+ * Use when the TypeScript type cannot be automatically mapped, or when
936
+ * precision/scale/maxLength constraints are needed (e.g., Edm.Decimal).
937
+ *
938
+ * Zero imports from typeorm, @nestjs/common, or any ORM. Pure reflect-metadata.
939
+ */
940
+ declare function EdmType(options: EdmTypeOptions): PropertyDecorator;
941
+ /** Read all @EdmType overrides registered on a class */
942
+ declare function getEdmTypeOverrides(target: object): Record<string, EdmTypeOptions>;
943
+ //#endregion
944
+ //#region src/decorators/odata-exclude.decorator.d.ts
945
+ /**
946
+ * @ODataExclude() — marks a property to be excluded from the OData EDM.
947
+ * The property will not appear in $metadata and will be stripped from responses.
948
+ *
949
+ * Zero imports from typeorm, @nestjs/common, or any ORM. Pure reflect-metadata.
950
+ */
951
+ declare function ODataExclude(): PropertyDecorator;
952
+ /** Read all property names excluded via @ODataExclude() on a class */
953
+ declare function getExcludedProperties(target: object): Set<string>;
954
+ //#endregion
955
+ //#region src/decorators/odata-entity-set.decorator.d.ts
956
+ /**
957
+ * @ODataEntitySet(name) — override the entity set name for an entity class.
958
+ * Without this decorator, the adapter will auto-pluralize the class name.
959
+ *
960
+ * Zero imports from typeorm, @nestjs/common, or any ORM. Pure reflect-metadata.
961
+ */
962
+ declare function ODataEntitySet(name: string): ClassDecorator;
963
+ /** Read the entity set name set via @ODataEntitySet(), or undefined if not set */
964
+ declare function getEntitySetName(target: object): string | undefined;
965
+ //#endregion
966
+ //#region src/decorators/odata-key.decorator.d.ts
967
+ /**
968
+ * @ODataKey() — marks a property as part of the entity key.
969
+ * Can be applied to multiple properties for composite keys.
970
+ *
971
+ * Zero imports from typeorm, @nestjs/common, or any ORM. Pure reflect-metadata.
972
+ */
973
+ declare function ODataKey(): PropertyDecorator;
974
+ /** Read all key property names registered via @ODataKey() on a class */
975
+ declare function getKeyProperties(target: object): string[];
976
+ //#endregion
977
+ //#region src/decorators/odata-view.decorator.d.ts
978
+ /** Options for the @ODataView class decorator */
979
+ interface ODataViewOptions {
980
+ readonly sourceEntity: string;
981
+ readonly exposedProperties: string[];
982
+ readonly entitySetName?: string;
983
+ }
984
+ /**
985
+ * @ODataView() — marks a class as a virtual OData view.
986
+ * A virtual view projects a subset of an existing entity type's properties
987
+ * as its own EntitySet without duplicating entity registration. Per D-22, D-23.
988
+ *
989
+ * Zero imports from typeorm, @nestjs/common, or any ORM. Pure reflect-metadata.
990
+ */
991
+ declare function ODataView(options: ODataViewOptions): ClassDecorator;
992
+ /** Read the ODataView options from a class, or undefined if not decorated */
993
+ declare function getODataViewOptions(target: object): ODataViewOptions | undefined;
994
+ //#endregion
995
+ //#region src/decorators/odata-get.decorator.d.ts
996
+ /**
997
+ * Options for the @ODataGet() decorator.
998
+ */
999
+ interface ODataGetOptions {
1000
+ /** Custom route path. Defaults to '' (empty, NestJS will use the controller prefix). */
1001
+ path?: string;
1002
+ /**
1003
+ * When true, signals the auto-handler mechanism (wired in Plan 04 by the typeorm module)
1004
+ * to replace the decorated method body with a generated handler.
1005
+ * Default: false
1006
+ */
1007
+ autoHandler?: boolean;
1008
+ }
1009
+ /**
1010
+ * Composite method decorator for OData GET endpoints.
1011
+ *
1012
+ * Composes:
1013
+ * 1. Get(path) — NestJS route decorator
1014
+ * 2. SetMetadata(ODATA_ROUTE_KEY, { entitySetName }) — marks the route as OData
1015
+ * 3. UseInterceptors(ODataResponseInterceptor) — wraps result in OData JSON envelope
1016
+ * 4. UseFilters(ODataExceptionFilter) — formats errors as OData v4 error bodies
1017
+ *
1018
+ * @param entitySetName - The OData entity set name (e.g. 'Products'). Used to build
1019
+ * the @odata.context URL and for response formatting.
1020
+ * @param options - Optional configuration
1021
+ *
1022
+ * Per D-12, D-13, RESEARCH.md Pattern 5.
1023
+ * Zero TypeORM imports — per PKG-01 architecture constraint.
1024
+ */
1025
+ declare function ODataGet(entitySetName: string, options?: ODataGetOptions): MethodDecorator;
1026
+ //#endregion
1027
+ //#region src/decorators/odata-query.decorator.d.ts
1028
+ /**
1029
+ * Parameter decorator that extracts req.query and auto-applies ODataQueryPipe.
1030
+ *
1031
+ * Usage:
1032
+ * @Get()
1033
+ * async getProducts(@ODataQueryParam('Products') query: ODataQuery) { ... }
1034
+ *
1035
+ * The decorator passes the entitySetName as `data` so that the ODataQueryPipe
1036
+ * can inject it into the query object for context URL construction and field validation.
1037
+ *
1038
+ * Per D-04: auto-applies ODataQueryPipe — no @UsePipes(ODataQueryPipe) needed.
1039
+ * Per D-05: ODataQueryPipe remains exported for advanced use cases.
1040
+ * Per D-06: eliminates silent validation bypass when forgetting @UsePipes.
1041
+ *
1042
+ * NestJS resolves ODataQueryPipe from DI automatically when a class reference
1043
+ * is passed as the pipe argument to a createParamDecorator result.
1044
+ *
1045
+ * Zero TypeORM imports — per PKG-01 architecture constraint.
1046
+ */
1047
+ declare const ODataQueryParam: (entitySetName?: string) => ParameterDecorator;
1048
+ //#endregion
1049
+ //#region src/decorators/odata-post.decorator.d.ts
1050
+ interface ODataPostOptions {
1051
+ path?: string;
1052
+ }
1053
+ /**
1054
+ * Composite method decorator for OData POST (create) endpoints.
1055
+ *
1056
+ * Composes:
1057
+ * 1. Post(path) — NestJS route decorator
1058
+ * 2. HttpCode(201) — POST returns 201 Created per D-03
1059
+ * 3. SetMetadata(ODATA_ROUTE_KEY, { entitySetName, operation: 'create' })
1060
+ * 4. UseInterceptors(ODataResponseInterceptor) — wraps result in OData JSON envelope
1061
+ * 5. UseFilters(ODataExceptionFilter) — formats errors as OData v4 error bodies
1062
+ *
1063
+ * Per D-03: POST returns 201. Per D-12: explicit opt-in per operation.
1064
+ * Zero TypeORM imports — per PKG-01 architecture constraint.
1065
+ */
1066
+ declare function ODataPost(entitySetName: string, options?: ODataPostOptions): MethodDecorator;
1067
+ //#endregion
1068
+ //#region src/decorators/odata-patch.decorator.d.ts
1069
+ interface ODataPatchOptions {
1070
+ path?: string;
1071
+ }
1072
+ /**
1073
+ * Composite method decorator for OData PATCH (update) endpoints.
1074
+ *
1075
+ * Composes:
1076
+ * 1. Patch(path) — NestJS route decorator, defaults to ':key'
1077
+ * 2. SetMetadata(ODATA_ROUTE_KEY, { entitySetName, operation: 'update', isSingleEntity: true })
1078
+ * 3. UseInterceptors(ODataResponseInterceptor) — wraps result in single-entity OData envelope
1079
+ * 4. UseFilters(ODataExceptionFilter) — formats errors as OData v4 error bodies
1080
+ *
1081
+ * Per D-01: merge-patch semantics. Per D-02: parenthetical key via ':key' param.
1082
+ * Zero TypeORM imports — per PKG-01 architecture constraint.
1083
+ */
1084
+ declare function ODataPatch(entitySetName: string, options?: ODataPatchOptions): MethodDecorator;
1085
+ //#endregion
1086
+ //#region src/decorators/odata-put.decorator.d.ts
1087
+ interface ODataPutOptions {
1088
+ path?: string;
1089
+ }
1090
+ /**
1091
+ * Composite method decorator for OData PUT (full entity replacement) endpoints.
1092
+ *
1093
+ * Composes:
1094
+ * 1. Put(path) — NestJS route decorator, defaults to ':key'
1095
+ * 2. SetMetadata(ODATA_ROUTE_KEY, { entitySetName, operation: 'replace', isSingleEntity: true })
1096
+ * 3. UseInterceptors(ODataResponseInterceptor) — wraps result in single-entity OData envelope
1097
+ * 4. UseFilters(ODataExceptionFilter) — formats errors as OData v4 error bodies
1098
+ *
1099
+ * Per D-01: PUT semantics — full entity replacement. All unspecified fields reset to
1100
+ * column defaults (null for nullable, default value for columns with defaults).
1101
+ * Per D-02: parenthetical key via ':key' param.
1102
+ * Zero TypeORM imports — per PKG-01 architecture constraint.
1103
+ */
1104
+ declare function ODataPut(entitySetName: string, options?: ODataPutOptions): MethodDecorator;
1105
+ //#endregion
1106
+ //#region src/decorators/odata-delete.decorator.d.ts
1107
+ interface ODataDeleteOptions {
1108
+ path?: string;
1109
+ }
1110
+ /**
1111
+ * Composite method decorator for OData DELETE endpoints.
1112
+ *
1113
+ * Composes:
1114
+ * 1. Delete(path) — NestJS route decorator, defaults to ':key'
1115
+ * 2. HttpCode(204) — DELETE returns 204 No Content per D-04
1116
+ * 3. SetMetadata(ODATA_ROUTE_KEY, { entitySetName, operation: 'delete' })
1117
+ * 4. UseFilters(ODataExceptionFilter) — formats errors as OData v4 error bodies
1118
+ *
1119
+ * Per D-04: DELETE returns 204 with no body. No ODataResponseInterceptor — no body.
1120
+ * Zero TypeORM imports — per PKG-01 architecture constraint.
1121
+ */
1122
+ declare function ODataDelete(entitySetName: string, options?: ODataDeleteOptions): MethodDecorator;
1123
+ //#endregion
1124
+ //#region src/decorators/odata-get-by-key.decorator.d.ts
1125
+ interface ODataGetByKeyOptions {
1126
+ path?: string;
1127
+ }
1128
+ /**
1129
+ * Composite method decorator for OData GET-by-key (single entity) endpoints.
1130
+ *
1131
+ * Composes:
1132
+ * 1. Get(path) — NestJS route decorator, defaults to ':key'
1133
+ * 2. SetMetadata(ODATA_ROUTE_KEY, { entitySetName, operation: 'getByKey', isSingleEntity: true })
1134
+ * 3. UseInterceptors(ODataResponseInterceptor) — returns entity directly (not wrapped in value array)
1135
+ * 4. UseFilters(ODataExceptionFilter) — formats errors as OData v4 error bodies
1136
+ *
1137
+ * Per D-05: Returns single entity, not wrapped in value array.
1138
+ * Context URL uses /$entity suffix for single-entity responses.
1139
+ * Zero TypeORM imports — per PKG-01 architecture constraint.
1140
+ */
1141
+ declare function ODataGetByKey(entitySetName: string, options?: ODataGetByKeyOptions): MethodDecorator;
1142
+ //#endregion
1143
+ //#region src/decorators/odata-controller.decorator.d.ts
1144
+ interface ODataControllerOptions {
1145
+ /** Override the route path. Defaults to entitySetName. */
1146
+ path?: string;
1147
+ }
1148
+ /**
1149
+ * Class decorator for OData controllers.
1150
+ *
1151
+ * Composes:
1152
+ * 1. Controller(path) — NestJS route prefix, defaults to entitySetName
1153
+ * 2. SetMetadata(ODATA_CONTROLLER_KEY, entitySetName) — marks as OData controller
1154
+ * (used by ODataModule.forRoot() to patch PATH_METADATA with serviceRoot)
1155
+ *
1156
+ * Per D-11: Separate from @Controller() — sets entity context and route prefix.
1157
+ * Per D-17: The service root prefix is applied by ODataModule.forRoot({ controllers })
1158
+ * via Reflect.defineMetadata(PATH_METADATA) — the same pattern used by MetadataController.
1159
+ * The controller is initially registered with just the entitySetName as path;
1160
+ * the module prepends serviceRoot during forRoot().
1161
+ *
1162
+ * Note: ODataResponseInterceptor and ODataExceptionFilter are NOT applied at class level.
1163
+ * Each CRUD method decorator (@ODataGet, @ODataPost, @ODataPatch, @ODataDelete, @ODataGetByKey)
1164
+ * applies these per-method. This avoids double-wrapping when method and class decorators
1165
+ * are both used.
1166
+ *
1167
+ * @param entitySetName - The OData entity set name (e.g. 'Products')
1168
+ * @param options - Optional configuration
1169
+ *
1170
+ * Zero TypeORM imports — per PKG-01 architecture constraint.
1171
+ */
1172
+ declare function ODataController(entitySetName: string, options?: ODataControllerOptions): ClassDecorator;
1173
+ //#endregion
1174
+ //#region src/response/odata-context-url.builder.d.ts
1175
+ /**
1176
+ * Builds the OData v4 @odata.context URL per spec section 10.
1177
+ *
1178
+ * Format: {serviceRoot}/$metadata#{entitySetName}[(field1,field2,...)]
1179
+ * Single-entity format: {serviceRoot}/$metadata#{entitySetName}/$entity
1180
+ *
1181
+ * Per D-07: when $select has specific items (not all, not undefined),
1182
+ * a select projection suffix is appended.
1183
+ * Per D-05: when isSingleEntity is true, /$entity suffix is appended.
1184
+ *
1185
+ * @param serviceRoot - The OData service root path (e.g. '/odata')
1186
+ * @param entitySetName - The name of the entity set (e.g. 'Products')
1187
+ * @param select - Optional SelectNode from the parsed query
1188
+ * @param isSingleEntity - When true, appends /$entity suffix for single-entity responses
1189
+ */
1190
+ declare function buildContextUrl(serviceRoot: string, entitySetName: string, select?: SelectNode, isSingleEntity?: boolean): string;
1191
+ //#endregion
1192
+ //#region src/response/odata-annotation.builder.d.ts
1193
+ /**
1194
+ * Context needed to annotate an entity with OData metadata annotations.
1195
+ */
1196
+ interface AnnotationContext {
1197
+ /** The OData service root path (e.g. '/odata') */
1198
+ readonly serviceRoot: string;
1199
+ /** The OData entity set name (e.g. 'Products') */
1200
+ readonly entitySetName: string;
1201
+ /** The EDM entity type descriptor */
1202
+ readonly entityType: EdmEntityType;
1203
+ /** The EDM namespace (e.g. 'Default') */
1204
+ readonly namespace: string;
1205
+ }
1206
+ /**
1207
+ * Annotate a single entity object with OData v4 metadata annotations.
1208
+ *
1209
+ * Adds:
1210
+ * - @odata.id — canonical URL for the entity (RESP-04)
1211
+ * - @odata.type — qualified type name with namespace prefix (RESP-05)
1212
+ * - {navProp}@odata.navigationLink — for each navigation property (RESP-06)
1213
+ *
1214
+ * Returns the entity unchanged if it is null, undefined, or not an object.
1215
+ *
1216
+ * This function is pure — it never mutates the input entity.
1217
+ *
1218
+ * @param entity - The entity object to annotate
1219
+ * @param ctx - The annotation context (service root, entity set name, entity type, namespace)
1220
+ */
1221
+ declare function annotateEntity(entity: Record<string, unknown>, ctx: AnnotationContext): Record<string, unknown>;
1222
+ /**
1223
+ * Annotate an array of entity objects with OData v4 metadata annotations.
1224
+ *
1225
+ * Applies `annotateEntity` to every item in the array and returns a new array.
1226
+ * Does not mutate the input array or any items.
1227
+ *
1228
+ * @param items - The array of entity objects to annotate
1229
+ * @param ctx - The annotation context
1230
+ */
1231
+ declare function annotateEntities(items: Record<string, unknown>[], ctx: AnnotationContext): Record<string, unknown>[];
1232
+ //#endregion
1233
+ //#region src/response/odata-response.interceptor.d.ts
1234
+ /**
1235
+ * NestJS interceptor that wraps ODataQueryResult into an OData v4 JSON envelope.
1236
+ *
1237
+ * Per D-05: only activates on routes that have ODATA_ROUTE_KEY metadata (set by @ODataGet()).
1238
+ * Non-OData routes pass through completely unchanged (T-03-09).
1239
+ *
1240
+ * Response format:
1241
+ * {
1242
+ * '@odata.context': '/odata/$metadata#EntitySet',
1243
+ * '@odata.id': '/odata/EntitySet(key)', (per RESP-04)
1244
+ * '@odata.type': '#Namespace.EntityType', (per RESP-05)
1245
+ * '{nav}@odata.navigationLink': '...', (per RESP-06)
1246
+ * 'value': [...],
1247
+ * '@odata.count': N, (only when present)
1248
+ * '@odata.nextLink': '...' (only when present)
1249
+ * }
1250
+ *
1251
+ * Per D-08: undefined keys are omitted entirely — not set to null or undefined.
1252
+ *
1253
+ * Zero TypeORM imports — per PKG-01 architecture constraint.
1254
+ */
1255
+ declare class ODataResponseInterceptor implements NestInterceptor {
1256
+ private readonly reflector;
1257
+ private readonly options;
1258
+ private readonly edmRegistry;
1259
+ constructor(reflector: Reflector, options: ODataModuleResolvedOptions, edmRegistry: EdmRegistry);
1260
+ /**
1261
+ * Resolve the AnnotationContext for an entity set name.
1262
+ * Returns null when the entity set or its type is not registered (graceful degradation).
1263
+ */
1264
+ private resolveAnnotationContext;
1265
+ intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
1266
+ }
1267
+ //#endregion
1268
+ //#region src/response/odata-exception.filter.d.ts
1269
+ /**
1270
+ * NestJS exception filter that converts all caught exceptions into OData v4 error format.
1271
+ *
1272
+ * Per D-09: all errors are formatted as { error: { code, message, details } }.
1273
+ * Per D-11, T-03-08: never includes stack traces or internal server details.
1274
+ *
1275
+ * Handled exception types:
1276
+ * - ODataParseError -> HTTP 400, code 'BadRequest', user message
1277
+ * - ODataValidationError -> HTTP 400, code 'BadRequest', user message
1278
+ * - HttpException -> uses exception's HTTP status code, generic OData code
1279
+ * - All others -> HTTP 500, code 'InternalServerError', generic message (no details leaked)
1280
+ *
1281
+ * Zero TypeORM imports — per PKG-01 architecture constraint.
1282
+ */
1283
+ declare class ODataExceptionFilter implements ExceptionFilter {
1284
+ catch(exception: unknown, host: ArgumentsHost): void;
1285
+ }
1286
+ //#endregion
1287
+ //#region src/metadata/csdl-builder.d.ts
1288
+ /**
1289
+ * CsdlBuilder — builds OData v4 CSDL XML from the EdmRegistry.
1290
+ *
1291
+ * Per D-17: XML is built once at init time and cached.
1292
+ * Per D-08: Default namespace is 'Default'.
1293
+ * Per D-09: Container name is 'Container'.
1294
+ * Per T-02-08: Caching mitigates DoS risk from repeated CSDL requests.
1295
+ */
1296
+ declare class CsdlBuilder {
1297
+ private readonly edmRegistry;
1298
+ private readonly options;
1299
+ private cachedXml;
1300
+ constructor(edmRegistry: EdmRegistry, options: ODataModuleOptions);
1301
+ /**
1302
+ * Build and return the CSDL XML string.
1303
+ * Builds once on first call; returns cached string on subsequent calls.
1304
+ */
1305
+ buildCsdlXml(): string;
1306
+ private buildXml;
1307
+ private buildEntityTypeXml;
1308
+ private buildPropertyXml;
1309
+ private buildNavigationPropertyXml;
1310
+ private buildEntitySetXml;
1311
+ }
1312
+ //#endregion
1313
+ //#region src/metadata/service-document-builder.d.ts
1314
+ /** A single entity set entry in the OData service document */
1315
+ interface ServiceDocumentEntry {
1316
+ readonly name: string;
1317
+ readonly url: string;
1318
+ readonly kind: 'EntitySet';
1319
+ }
1320
+ /** OData service document response shape */
1321
+ interface ServiceDocument {
1322
+ readonly '@odata.context': string;
1323
+ readonly value: readonly ServiceDocumentEntry[];
1324
+ }
1325
+ /**
1326
+ * ServiceDocumentBuilder — builds the OData service document (root URL response).
1327
+ *
1328
+ * Per D-24: Service document lists all registered EntitySets with name, url, and kind.
1329
+ */
1330
+ declare class ServiceDocumentBuilder {
1331
+ private readonly edmRegistry;
1332
+ private readonly options;
1333
+ constructor(edmRegistry: EdmRegistry, options: ODataModuleOptions);
1334
+ /**
1335
+ * Build the OData service document JSON object.
1336
+ * Returns @odata.context pointing to $metadata and a value array with all EntitySets.
1337
+ */
1338
+ buildServiceDocument(): ServiceDocument;
1339
+ }
1340
+ //#endregion
1341
+ //#region src/metadata/metadata.controller.d.ts
1342
+ /**
1343
+ * MetadataController — serves the $metadata CSDL XML and OData service document.
1344
+ *
1345
+ * Routes (relative to serviceRoot, e.g. '/odata'):
1346
+ * GET /odata/$metadata → CSDL XML (Content-Type: application/xml)
1347
+ * GET /odata → Service document JSON (Content-Type: application/json)
1348
+ *
1349
+ * The controller uses @Controller() with no prefix because consumers mount it
1350
+ * via ODataModule which sets the serviceRoot path. The routes use the serviceRoot
1351
+ * path directly to ensure correct routing when integrated into a NestJS app.
1352
+ *
1353
+ * Implementation uses a fixed serviceRoot-relative routing strategy:
1354
+ * The @Controller() prefix is set to the serviceRoot and endpoints are
1355
+ * '/$metadata' and '/' respectively. This is the most NestJS-idiomatic approach.
1356
+ */
1357
+ declare class MetadataController {
1358
+ private readonly csdlBuilder;
1359
+ private readonly serviceDocumentBuilder;
1360
+ readonly options: ODataModuleOptions;
1361
+ constructor(csdlBuilder: CsdlBuilder, serviceDocumentBuilder: ServiceDocumentBuilder, options: ODataModuleOptions);
1362
+ /**
1363
+ * GET {serviceRoot}/$metadata
1364
+ * Returns OData v4 CSDL XML document.
1365
+ * Content-Type: application/xml per OData spec.
1366
+ */
1367
+ getMetadata(): string;
1368
+ /**
1369
+ * GET {serviceRoot}/ (service document)
1370
+ * Returns OData service document listing all available EntitySets.
1371
+ * Content-Type: application/json per OData spec.
1372
+ */
1373
+ getServiceDocument(): object;
1374
+ }
1375
+ //#endregion
1376
+ //#region src/utils/odata-key-parser.d.ts
1377
+ /**
1378
+ * Parse an OData parenthetical key string into a Record of key-value pairs.
1379
+ *
1380
+ * Formats per D-02:
1381
+ * Simple: '42' -> { Id: 42 }
1382
+ * String: "'hello'" -> { Name: 'hello' }
1383
+ * Composite: 'OrderId=1,ItemId=3' -> { OrderId: 1, ItemId: 3 }
1384
+ *
1385
+ * Per T-04-02: Key values are returned as typed JS values (number, string, boolean)
1386
+ * — never interpolated into SQL. Safe for parameterized ORM queries.
1387
+ */
1388
+ /**
1389
+ * Parse an OData parenthetical key string into a Record of key-value pairs.
1390
+ *
1391
+ * @param keyStr - The key string extracted from parentheses (e.g., '42', "'hello'", 'OrderId=1,ItemId=3')
1392
+ * @param keyProperties - Ordered list of key property names (used for simple keys)
1393
+ * @returns Record mapping property names to coerced values
1394
+ * @throws Error if keyStr is empty
1395
+ */
1396
+ declare function parseODataKey(keyStr: string, keyProperties: readonly string[]): Record<string, unknown>;
1397
+ //#endregion
1398
+ //#region src/index.d.ts
1399
+ declare const VERSION = "0.0.1";
1400
+ //#endregion
1401
+ export { AggregateExpression, type AnnotationContext, ApplyAggregateStep, ApplyFilterStep, ApplyGroupByStep, ApplyNode, ApplyStep, BatchChangesetPart, BatchPart, BatchRequestPart, BatchResponse, BatchResponsePart, BinaryExprNode, BinaryOperator, CsdlBuilder, EDM_DERIVER, EDM_ENTITY_CONFIGS, EDM_TYPE_KEY, ETAG_PROVIDER, EdmEntityConfig, EdmEntitySet, EdmEntityType, EdmNavigationProperty, EdmPrimitiveType, EdmProperty, EdmRegistry, EdmType, type EdmTypeOptions, EdmVirtualView, EntityClass, ExpandItem, ExpandNode, FilterNode, FilterVisitor, FunctionCallNode, IETagProvider, IEdmDeriver, IQueryTranslator, ISearchProvider, LambdaExprNode, LambdaOperator, LiteralKind, LiteralNode, MAX_BATCH_OPERATIONS, MetadataController, ODATA_CONTROLLER_KEY, ODATA_ENTITY_SET_KEY, ODATA_ETAG_KEY, ODATA_EXCLUDE_KEY, ODATA_KEY_KEY, ODATA_MODULE_OPTIONS, ODATA_ROUTE_KEY, ODATA_SEARCHABLE_KEY, ODATA_VIEW_KEY, ODataController, type ODataControllerOptions, ODataDelete, type ODataDeleteOptions, ODataETag, type ODataEntitySecurityOptions, ODataEntitySet, ODataExceptionFilter, ODataExclude, ODataGet, ODataGetByKey, type ODataGetByKeyOptions, type ODataGetOptions, ODataKey, ODataModule, ODataModuleOptions, ODataModuleResolvedOptions, ODataParseError, ODataPatch, type ODataPatchOptions, ODataPost, type ODataPostOptions, ODataPut, type ODataPutOptions, type ODataQuery, ODataQueryParam, ODataQueryPipe, type ODataQueryResult, ODataResponseInterceptor, ODataSearchable, ODataValidationError, ODataView, type ODataViewOptions, OrderByDirection, OrderByItem, OrderByNode, ParsedBatch, PropertyAccessNode, QUERY_TRANSLATOR, QueryOptions, SEARCH_PROVIDER, SearchBinaryNode, SearchNode, SearchTermNode, SelectItem, SelectNode, type ServiceDocument, ServiceDocumentBuilder, type ServiceDocumentEntry, Token, TokenKind, UnaryExprNode, UnaryOperator, UnmappedTypeStrategy, VERSION, acceptVisitor, annotateEntities, annotateEntity, buildContextUrl, extractBoundary, getETagProperty, getEdmTypeOverrides, getEntitySetName, getExcludedProperties, getKeyProperties, getODataViewOptions, getSearchableProperties, parseApply, parseBatchBody, parseFilter, parseODataKey, parseQuery, parseSearch, pluralizeEntityName, tokenize };
1402
+ //# sourceMappingURL=index.d.mts.map