@proofkit/fmodata 0.1.0-alpha.15 → 0.1.0-alpha.17
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 +6 -167
- package/dist/esm/client/builders/expand-builder.d.ts +3 -1
- package/dist/esm/client/builders/expand-builder.js +3 -2
- package/dist/esm/client/builders/expand-builder.js.map +1 -1
- package/dist/esm/client/builders/query-string-builder.d.ts +2 -0
- package/dist/esm/client/builders/query-string-builder.js +1 -1
- package/dist/esm/client/builders/query-string-builder.js.map +1 -1
- package/dist/esm/client/builders/response-processor.d.ts +2 -0
- package/dist/esm/client/builders/response-processor.js +3 -2
- package/dist/esm/client/builders/response-processor.js.map +1 -1
- package/dist/esm/client/builders/select-mixin.d.ts +3 -2
- package/dist/esm/client/builders/select-mixin.js +2 -2
- package/dist/esm/client/builders/select-mixin.js.map +1 -1
- package/dist/esm/client/entity-set.d.ts +4 -13
- package/dist/esm/client/entity-set.js +5 -2
- package/dist/esm/client/entity-set.js.map +1 -1
- package/dist/esm/client/error-parser.js.map +1 -1
- package/dist/esm/client/filemaker-odata.d.ts +8 -0
- package/dist/esm/client/filemaker-odata.js +14 -0
- package/dist/esm/client/filemaker-odata.js.map +1 -1
- package/dist/esm/client/query/query-builder.d.ts +10 -16
- package/dist/esm/client/query/query-builder.js +27 -85
- package/dist/esm/client/query/query-builder.js.map +1 -1
- package/dist/esm/client/query/response-processor.d.ts +2 -0
- package/dist/esm/client/query/types.d.ts +5 -5
- package/dist/esm/client/record-builder.d.ts +11 -9
- package/dist/esm/client/record-builder.js +41 -10
- package/dist/esm/client/record-builder.js.map +1 -1
- package/dist/esm/index.d.ts +3 -2
- package/dist/esm/logger.d.ts +47 -0
- package/dist/esm/logger.js +72 -0
- package/dist/esm/logger.js.map +1 -0
- package/dist/esm/logger.test.d.ts +1 -0
- package/dist/esm/orm/column.d.ts +21 -4
- package/dist/esm/orm/column.js +5 -2
- package/dist/esm/orm/column.js.map +1 -1
- package/dist/esm/orm/field-builders.d.ts +1 -1
- package/dist/esm/orm/field-builders.js +1 -1
- package/dist/esm/orm/field-builders.js.map +1 -1
- package/dist/esm/orm/operators.d.ts +19 -19
- package/dist/esm/orm/operators.js +31 -12
- package/dist/esm/orm/operators.js.map +1 -1
- package/dist/esm/orm/table.d.ts +10 -9
- package/dist/esm/orm/table.js +5 -3
- package/dist/esm/orm/table.js.map +1 -1
- package/dist/esm/transform.js +1 -5
- package/dist/esm/transform.js.map +1 -1
- package/dist/esm/types.d.ts +11 -43
- package/dist/esm/types.js.map +1 -1
- package/package.json +1 -1
- package/src/client/builders/expand-builder.ts +6 -2
- package/src/client/builders/query-string-builder.ts +3 -1
- package/src/client/builders/response-processor.ts +4 -1
- package/src/client/builders/select-mixin.ts +5 -4
- package/src/client/entity-set.ts +30 -9
- package/src/client/error-parser.ts +3 -0
- package/src/client/filemaker-odata.ts +18 -0
- package/src/client/query/query-builder.ts +32 -128
- package/src/client/query/response-processor.ts +2 -0
- package/src/client/query/types.ts +6 -5
- package/src/client/record-builder.ts +77 -34
- package/src/index.ts +5 -15
- package/src/logger.test.ts +34 -0
- package/src/logger.ts +140 -0
- package/src/orm/column.ts +33 -5
- package/src/orm/field-builders.ts +1 -1
- package/src/orm/operators.ts +105 -51
- package/src/orm/table.ts +21 -13
- package/src/types.ts +12 -51
- package/dist/esm/filter-types.d.ts +0 -76
- package/src/filter-types.ts +0 -97
package/README.md
CHANGED
|
@@ -262,9 +262,9 @@ Get a single field value:
|
|
|
262
262
|
|
|
263
263
|
```typescript
|
|
264
264
|
const result = await db
|
|
265
|
-
.from(
|
|
265
|
+
.from(users)
|
|
266
266
|
.get("user-123")
|
|
267
|
-
.getSingleField(
|
|
267
|
+
.getSingleField(users.email)
|
|
268
268
|
.execute();
|
|
269
269
|
|
|
270
270
|
if (result.data) {
|
|
@@ -323,156 +323,6 @@ Available operators:
|
|
|
323
323
|
- **Null**: `isNull()`, `isNotNull()`
|
|
324
324
|
- **Logical**: `and()`, `or()`, `not()`
|
|
325
325
|
|
|
326
|
-
#### Legacy Filter API (DO NOT USE, will be removed shortly)
|
|
327
|
-
|
|
328
|
-
The filter system supports three syntaxes: shorthand, single operator objects, and arrays for multiple operators.
|
|
329
|
-
|
|
330
|
-
You can use the legacy `filter()` method in three ways:
|
|
331
|
-
|
|
332
|
-
**1. Shorthand (direct value):**
|
|
333
|
-
|
|
334
|
-
```typescript
|
|
335
|
-
.filter({ name: "John" })
|
|
336
|
-
// Equivalent to: { name: [{ eq: "John" }] }
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
**2. Single operator object:**
|
|
340
|
-
|
|
341
|
-
```typescript
|
|
342
|
-
.filter({ age: { gt: 18 } })
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
**3. Array of operators (for multiple operators on same field):**
|
|
346
|
-
|
|
347
|
-
```typescript
|
|
348
|
-
.filter({ age: [{ gt: 18 }, { lt: 65 }] })
|
|
349
|
-
// Result: age gt 18 and age lt 65
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
The array pattern prevents duplicate operators on the same field and allows multiple conditions with implicit AND.
|
|
353
|
-
|
|
354
|
-
#### Available Operators
|
|
355
|
-
|
|
356
|
-
**String fields:**
|
|
357
|
-
|
|
358
|
-
- `eq`, `ne` - equality/inequality
|
|
359
|
-
- `contains`, `startswith`, `endswith` - string functions
|
|
360
|
-
- `gt`, `ge`, `lt`, `le` - comparison
|
|
361
|
-
- `in` - match any value in array
|
|
362
|
-
|
|
363
|
-
**Number fields:**
|
|
364
|
-
|
|
365
|
-
- `eq`, `ne`, `gt`, `ge`, `lt`, `le` - comparisons
|
|
366
|
-
- `in` - match any value in array
|
|
367
|
-
|
|
368
|
-
**Boolean fields:**
|
|
369
|
-
|
|
370
|
-
- `eq`, `ne` - equality only
|
|
371
|
-
|
|
372
|
-
**Date fields:**
|
|
373
|
-
|
|
374
|
-
- `eq`, `ne`, `gt`, `ge`, `lt`, `le` - date comparisons
|
|
375
|
-
- `in` - match any date in array
|
|
376
|
-
|
|
377
|
-
#### Shorthand Syntax
|
|
378
|
-
|
|
379
|
-
For simple equality checks, use the shorthand:
|
|
380
|
-
|
|
381
|
-
```typescript
|
|
382
|
-
const result = await db.from("users").list().filter({ name: "John" }).execute();
|
|
383
|
-
// Equivalent to: { name: [{ eq: "John" }] }
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
#### Examples
|
|
387
|
-
|
|
388
|
-
```typescript
|
|
389
|
-
// Equality filter (single operator)
|
|
390
|
-
const activeUsers = await db
|
|
391
|
-
.from("users")
|
|
392
|
-
.list()
|
|
393
|
-
.filter({ active: { eq: true } })
|
|
394
|
-
.execute();
|
|
395
|
-
|
|
396
|
-
// Comparison operators (single operator)
|
|
397
|
-
const adultUsers = await db
|
|
398
|
-
.from("users")
|
|
399
|
-
.list()
|
|
400
|
-
.filter({ age: { gt: 18 } })
|
|
401
|
-
.execute();
|
|
402
|
-
|
|
403
|
-
// String operators (single operator)
|
|
404
|
-
const johns = await db
|
|
405
|
-
.from("users")
|
|
406
|
-
.list()
|
|
407
|
-
.filter({ name: { contains: "John" } })
|
|
408
|
-
.execute();
|
|
409
|
-
|
|
410
|
-
// Multiple operators on same field (array syntax, implicit AND)
|
|
411
|
-
const rangeQuery = await db
|
|
412
|
-
.from("users")
|
|
413
|
-
.list()
|
|
414
|
-
.filter({ age: [{ gt: 18 }, { lt: 65 }] })
|
|
415
|
-
.execute();
|
|
416
|
-
|
|
417
|
-
// Combine filters with AND
|
|
418
|
-
const result = await db
|
|
419
|
-
.from("users")
|
|
420
|
-
.list()
|
|
421
|
-
.filter({
|
|
422
|
-
and: [{ active: [{ eq: true }] }, { age: [{ gt: 18 }] }],
|
|
423
|
-
})
|
|
424
|
-
.execute();
|
|
425
|
-
|
|
426
|
-
// Combine filters with OR
|
|
427
|
-
const result = await db
|
|
428
|
-
.from("users")
|
|
429
|
-
.list()
|
|
430
|
-
.filter({
|
|
431
|
-
or: [{ name: [{ eq: "John" }] }, { name: [{ eq: "Jane" }] }],
|
|
432
|
-
})
|
|
433
|
-
.execute();
|
|
434
|
-
|
|
435
|
-
// IN operator
|
|
436
|
-
const result = await db
|
|
437
|
-
.from("users")
|
|
438
|
-
.list()
|
|
439
|
-
.filter({ age: [{ in: [18, 21, 25] }] })
|
|
440
|
-
.execute();
|
|
441
|
-
|
|
442
|
-
// Null checks
|
|
443
|
-
const result = await db
|
|
444
|
-
.from("users")
|
|
445
|
-
.list()
|
|
446
|
-
.filter({ deletedAt: [{ eq: null }] })
|
|
447
|
-
.execute();
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
#### Logical Operators
|
|
451
|
-
|
|
452
|
-
Combine multiple conditions with `and`, `or`, `not`:
|
|
453
|
-
|
|
454
|
-
```typescript
|
|
455
|
-
const result = await db
|
|
456
|
-
.from("users")
|
|
457
|
-
.list()
|
|
458
|
-
.filter({
|
|
459
|
-
and: [{ name: [{ contains: "John" }] }, { age: [{ gt: 18 }] }],
|
|
460
|
-
})
|
|
461
|
-
.execute();
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
#### Escape Hatch
|
|
465
|
-
|
|
466
|
-
For unsupported edge cases, pass a raw OData filter string:
|
|
467
|
-
|
|
468
|
-
```typescript
|
|
469
|
-
const result = await db
|
|
470
|
-
.from("users")
|
|
471
|
-
.list()
|
|
472
|
-
.filter("substringof('John', name)")
|
|
473
|
-
.execute();
|
|
474
|
-
```
|
|
475
|
-
|
|
476
326
|
### Sorting
|
|
477
327
|
|
|
478
328
|
Sort results using `orderBy()`. The method supports both column references (new ORM API) and string field names (legacy API).
|
|
@@ -565,7 +415,7 @@ Use `single()` to ensure exactly one record is returned (returns an error if zer
|
|
|
565
415
|
const result = await db
|
|
566
416
|
.from(users)
|
|
567
417
|
.list()
|
|
568
|
-
.
|
|
418
|
+
.where(eq(users.email, "user@example.com"))
|
|
569
419
|
.single()
|
|
570
420
|
.execute();
|
|
571
421
|
|
|
@@ -581,7 +431,7 @@ Use `maybeSingle()` when you want at most one record (returns `null` if no recor
|
|
|
581
431
|
const result = await db
|
|
582
432
|
.from(users)
|
|
583
433
|
.list()
|
|
584
|
-
.
|
|
434
|
+
.where(eq(users.email, "user@example.com"))
|
|
585
435
|
.maybeSingle()
|
|
586
436
|
.execute();
|
|
587
437
|
|
|
@@ -618,17 +468,6 @@ const result = await db
|
|
|
618
468
|
.top(10)
|
|
619
469
|
.skip(0)
|
|
620
470
|
.execute();
|
|
621
|
-
|
|
622
|
-
// Using legacy API
|
|
623
|
-
const result = await db
|
|
624
|
-
.from("users")
|
|
625
|
-
.list()
|
|
626
|
-
.select("username", "email", "age")
|
|
627
|
-
.filter({ age: { gt: 18 } })
|
|
628
|
-
.orderBy("username")
|
|
629
|
-
.top(10)
|
|
630
|
-
.skip(0)
|
|
631
|
-
.execute();
|
|
632
471
|
```
|
|
633
472
|
|
|
634
473
|
## CRUD Operations
|
|
@@ -712,7 +551,7 @@ const result = await db
|
|
|
712
551
|
const result = await db
|
|
713
552
|
.from("users")
|
|
714
553
|
.update({ active: false })
|
|
715
|
-
.where((q) => q.
|
|
554
|
+
.where((q) => q.where(eq(users.active, true)).top(10))
|
|
716
555
|
.execute();
|
|
717
556
|
```
|
|
718
557
|
|
|
@@ -1798,7 +1637,7 @@ const queryString = db
|
|
|
1798
1637
|
.from("users")
|
|
1799
1638
|
.list()
|
|
1800
1639
|
.select("username", "email")
|
|
1801
|
-
.
|
|
1640
|
+
.where(eq(users.active, true))
|
|
1802
1641
|
.orderBy("username")
|
|
1803
1642
|
.top(10)
|
|
1804
1643
|
.getQueryString();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FMTable } from '../../orm/table.js';
|
|
2
2
|
import { ExpandValidationConfig } from '../../validation.js';
|
|
3
3
|
import { ExpandConfig } from './shared-types.js';
|
|
4
|
+
import { InternalLogger } from '../../logger.js';
|
|
4
5
|
/**
|
|
5
6
|
* Builds OData expand query strings and validation configs.
|
|
6
7
|
* Handles nested expands recursively and transforms relation names to FMTIDs
|
|
@@ -8,7 +9,8 @@ import { ExpandConfig } from './shared-types.js';
|
|
|
8
9
|
*/
|
|
9
10
|
export declare class ExpandBuilder {
|
|
10
11
|
private useEntityIds;
|
|
11
|
-
|
|
12
|
+
private logger;
|
|
13
|
+
constructor(useEntityIds: boolean, logger: InternalLogger);
|
|
12
14
|
/**
|
|
13
15
|
* Builds OData $expand query string from expand configurations.
|
|
14
16
|
*/
|
|
@@ -3,8 +3,9 @@ import { getBaseTableConfig, getTableName, getNavigationPaths, FMTable } from ".
|
|
|
3
3
|
import { formatSelectFields } from "./select-utils.js";
|
|
4
4
|
import { getDefaultSelectFields } from "./default-select.js";
|
|
5
5
|
class ExpandBuilder {
|
|
6
|
-
constructor(useEntityIds) {
|
|
6
|
+
constructor(useEntityIds, logger) {
|
|
7
7
|
this.useEntityIds = useEntityIds;
|
|
8
|
+
this.logger = logger;
|
|
8
9
|
}
|
|
9
10
|
/**
|
|
10
11
|
* Builds OData $expand query string from expand configurations.
|
|
@@ -57,7 +58,7 @@ class ExpandBuilder {
|
|
|
57
58
|
if (sourceTable) {
|
|
58
59
|
const navigationPaths = getNavigationPaths(sourceTable);
|
|
59
60
|
if (navigationPaths && !navigationPaths.includes(relationName)) {
|
|
60
|
-
|
|
61
|
+
this.logger.warn(
|
|
61
62
|
`Cannot expand to "${relationName}". Valid navigation paths: ${navigationPaths.length > 0 ? navigationPaths.join(", ") : "none"}`
|
|
62
63
|
);
|
|
63
64
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expand-builder.js","sources":["../../../../src/client/builders/expand-builder.ts"],"sourcesContent":["import { QueryOptions } from \"odata-query\";\nimport buildQuery from \"odata-query\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { FMTable } from \"../../orm/table\";\nimport {\n getBaseTableConfig,\n getTableName,\n getNavigationPaths,\n} from \"../../orm/table\";\nimport type { ExpandValidationConfig } from \"../../validation\";\nimport type { ExpandConfig } from \"./shared-types\";\nimport { formatSelectFields } from \"./select-utils\";\nimport { getDefaultSelectFields } from \"./default-select\";\n\n/**\n * Builds OData expand query strings and validation configs.\n * Handles nested expands recursively and transforms relation names to FMTIDs\n * when using entity IDs.\n */\nexport class ExpandBuilder {\n constructor(private useEntityIds: boolean) {}\n\n /**\n * Builds OData $expand query string from expand configurations.\n */\n buildExpandString(configs: ExpandConfig[]): string {\n if (configs.length === 0) return \"\";\n\n return configs.map((config) => this.buildSingleExpand(config)).join(\",\");\n }\n\n /**\n * Builds validation configs for expanded navigation properties.\n */\n buildValidationConfigs(configs: ExpandConfig[]): ExpandValidationConfig[] {\n return configs.map((config) => {\n const targetTable = config.targetTable;\n\n let targetSchema: Record<string, StandardSchemaV1> | undefined;\n if (targetTable) {\n const baseTableConfig = getBaseTableConfig(targetTable);\n const containerFields = baseTableConfig.containerFields || [];\n\n // Filter out container fields from schema\n const schema = { ...baseTableConfig.schema };\n for (const containerField of containerFields) {\n delete schema[containerField as string];\n }\n\n targetSchema = schema;\n }\n\n const selectedFields = config.options?.select\n ? Array.isArray(config.options.select)\n ? config.options.select.map(String)\n : [String(config.options.select)]\n : undefined;\n\n return {\n relation: config.relation,\n targetSchema,\n targetTable,\n table: targetTable,\n selectedFields,\n nestedExpands: undefined,\n };\n });\n }\n\n /**\n * Process an expand() call and return the expand config.\n * Used by both QueryBuilder and RecordBuilder to eliminate duplication.\n *\n * @param targetTable - The target table to expand to\n * @param sourceTable - The source table (for validation)\n * @param callback - Optional callback to configure the expand query\n * @param builderFactory - Function that creates a QueryBuilder for the target table\n * @returns ExpandConfig to add to the builder's expandConfigs array\n */\n processExpand<TargetTable extends FMTable<any, any>, Builder = any>(\n targetTable: TargetTable,\n sourceTable: FMTable<any, any> | undefined,\n callback?: (builder: Builder) => Builder,\n builderFactory?: () => Builder,\n ): ExpandConfig {\n // Extract name and validate\n const relationName = getTableName(targetTable);\n\n // Runtime validation: Check if relation name is in navigationPaths\n if (sourceTable) {\n const navigationPaths = getNavigationPaths(sourceTable);\n if (navigationPaths && !navigationPaths.includes(relationName)) {\n console.warn(\n `Cannot expand to \"${relationName}\". Valid navigation paths: ${navigationPaths.length > 0 ? navigationPaths.join(\", \") : \"none\"}`,\n );\n }\n }\n\n if (callback && builderFactory) {\n // Create a new QueryBuilder for the target table\n const targetBuilder = builderFactory();\n\n // Pass to callback and get configured builder\n const configuredBuilder = callback(targetBuilder);\n\n // Extract the builder's query options\n const expandOptions: Partial<QueryOptions<any>> = {\n ...(configuredBuilder as any).queryOptions,\n };\n\n // If callback didn't provide select, apply defaultSelect from target table\n if (!expandOptions.select) {\n const defaultFields = getDefaultSelectFields(targetTable);\n if (defaultFields) {\n expandOptions.select = defaultFields;\n }\n }\n\n // If the configured builder has nested expands, we need to include them\n if ((configuredBuilder as any).expandConfigs?.length > 0) {\n // Build nested expand string from the configured builder's expand configs\n const nestedExpandString = this.buildExpandString(\n (configuredBuilder as any).expandConfigs,\n );\n if (nestedExpandString) {\n // Add nested expand to options\n expandOptions.expand = nestedExpandString as any;\n }\n }\n\n return {\n relation: relationName,\n options: expandOptions,\n targetTable,\n };\n } else {\n // Simple expand without callback - apply defaultSelect if available\n const defaultFields = getDefaultSelectFields(targetTable);\n if (defaultFields) {\n return {\n relation: relationName,\n options: { select: defaultFields },\n targetTable,\n };\n } else {\n return {\n relation: relationName,\n targetTable,\n };\n }\n }\n }\n\n /**\n * Builds a single expand string with its options.\n */\n private buildSingleExpand(config: ExpandConfig): string {\n const relationName = this.resolveRelationName(config);\n const parts = this.buildExpandParts(config);\n\n if (parts.length === 0) {\n return relationName;\n }\n\n return `${relationName}(${parts.join(\";\")})`;\n }\n\n /**\n * Resolves relation name, using FMTID if entity IDs are enabled.\n */\n private resolveRelationName(config: ExpandConfig): string {\n if (!this.useEntityIds) {\n return config.relation;\n }\n\n const targetTable = config.targetTable;\n if (targetTable && FMTable.Symbol.EntityId in targetTable) {\n const tableId = (targetTable as any)[FMTable.Symbol.EntityId] as\n | `FMTID:${string}`\n | undefined;\n if (tableId) {\n return tableId;\n }\n }\n\n return config.relation;\n }\n\n /**\n * Builds expand parts (select, filter, orderBy, etc.) for a single expand.\n */\n private buildExpandParts(config: ExpandConfig): string[] {\n if (!config.options || Object.keys(config.options).length === 0) {\n return [];\n }\n\n const parts: string[] = [];\n const opts = config.options;\n\n if (opts.select) {\n const selectArray = Array.isArray(opts.select)\n ? opts.select.map(String)\n : [String(opts.select)];\n const selectFields = formatSelectFields(\n selectArray,\n config.targetTable,\n this.useEntityIds,\n );\n parts.push(`$select=${selectFields}`);\n }\n\n if (opts.filter) {\n const filterQuery = buildQuery({ filter: opts.filter });\n const match = filterQuery.match(/\\$filter=([^&]+)/);\n if (match) parts.push(`$filter=${match[1]}`);\n }\n\n if (opts.orderBy) {\n const orderByValue = Array.isArray(opts.orderBy)\n ? opts.orderBy.join(\",\")\n : String(opts.orderBy);\n parts.push(`$orderby=${orderByValue}`);\n }\n\n if (opts.top !== undefined) parts.push(`$top=${opts.top}`);\n if (opts.skip !== undefined) parts.push(`$skip=${opts.skip}`);\n\n if (opts.expand) {\n if (typeof opts.expand === \"string\") {\n parts.push(`$expand=${opts.expand}`);\n }\n }\n\n return parts;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"expand-builder.js","sources":["../../../../src/client/builders/expand-builder.ts"],"sourcesContent":["import { QueryOptions } from \"odata-query\";\nimport buildQuery from \"odata-query\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { FMTable } from \"../../orm/table\";\nimport {\n getBaseTableConfig,\n getTableName,\n getNavigationPaths,\n} from \"../../orm/table\";\nimport type { ExpandValidationConfig } from \"../../validation\";\nimport type { ExpandConfig } from \"./shared-types\";\nimport { formatSelectFields } from \"./select-utils\";\nimport { getDefaultSelectFields } from \"./default-select\";\nimport { InternalLogger } from \"../../logger\";\n\n/**\n * Builds OData expand query strings and validation configs.\n * Handles nested expands recursively and transforms relation names to FMTIDs\n * when using entity IDs.\n */\nexport class ExpandBuilder {\n constructor(\n private useEntityIds: boolean,\n private logger: InternalLogger,\n ) {}\n\n /**\n * Builds OData $expand query string from expand configurations.\n */\n buildExpandString(configs: ExpandConfig[]): string {\n if (configs.length === 0) return \"\";\n\n return configs.map((config) => this.buildSingleExpand(config)).join(\",\");\n }\n\n /**\n * Builds validation configs for expanded navigation properties.\n */\n buildValidationConfigs(configs: ExpandConfig[]): ExpandValidationConfig[] {\n return configs.map((config) => {\n const targetTable = config.targetTable;\n\n let targetSchema: Record<string, StandardSchemaV1> | undefined;\n if (targetTable) {\n const baseTableConfig = getBaseTableConfig(targetTable);\n const containerFields = baseTableConfig.containerFields || [];\n\n // Filter out container fields from schema\n const schema = { ...baseTableConfig.schema };\n for (const containerField of containerFields) {\n delete schema[containerField as string];\n }\n\n targetSchema = schema;\n }\n\n const selectedFields = config.options?.select\n ? Array.isArray(config.options.select)\n ? config.options.select.map(String)\n : [String(config.options.select)]\n : undefined;\n\n return {\n relation: config.relation,\n targetSchema,\n targetTable,\n table: targetTable,\n selectedFields,\n nestedExpands: undefined,\n };\n });\n }\n\n /**\n * Process an expand() call and return the expand config.\n * Used by both QueryBuilder and RecordBuilder to eliminate duplication.\n *\n * @param targetTable - The target table to expand to\n * @param sourceTable - The source table (for validation)\n * @param callback - Optional callback to configure the expand query\n * @param builderFactory - Function that creates a QueryBuilder for the target table\n * @returns ExpandConfig to add to the builder's expandConfigs array\n */\n processExpand<TargetTable extends FMTable<any, any>, Builder = any>(\n targetTable: TargetTable,\n sourceTable: FMTable<any, any> | undefined,\n callback?: (builder: Builder) => Builder,\n builderFactory?: () => Builder,\n ): ExpandConfig {\n // Extract name and validate\n const relationName = getTableName(targetTable);\n\n // Runtime validation: Check if relation name is in navigationPaths\n if (sourceTable) {\n const navigationPaths = getNavigationPaths(sourceTable);\n if (navigationPaths && !navigationPaths.includes(relationName)) {\n this.logger.warn(\n `Cannot expand to \"${relationName}\". Valid navigation paths: ${navigationPaths.length > 0 ? navigationPaths.join(\", \") : \"none\"}`,\n );\n }\n }\n\n if (callback && builderFactory) {\n // Create a new QueryBuilder for the target table\n const targetBuilder = builderFactory();\n\n // Pass to callback and get configured builder\n const configuredBuilder = callback(targetBuilder);\n\n // Extract the builder's query options\n const expandOptions: Partial<QueryOptions<any>> = {\n ...(configuredBuilder as any).queryOptions,\n };\n\n // If callback didn't provide select, apply defaultSelect from target table\n if (!expandOptions.select) {\n const defaultFields = getDefaultSelectFields(targetTable);\n if (defaultFields) {\n expandOptions.select = defaultFields;\n }\n }\n\n // If the configured builder has nested expands, we need to include them\n if ((configuredBuilder as any).expandConfigs?.length > 0) {\n // Build nested expand string from the configured builder's expand configs\n const nestedExpandString = this.buildExpandString(\n (configuredBuilder as any).expandConfigs,\n );\n if (nestedExpandString) {\n // Add nested expand to options\n expandOptions.expand = nestedExpandString as any;\n }\n }\n\n return {\n relation: relationName,\n options: expandOptions,\n targetTable,\n };\n } else {\n // Simple expand without callback - apply defaultSelect if available\n const defaultFields = getDefaultSelectFields(targetTable);\n if (defaultFields) {\n return {\n relation: relationName,\n options: { select: defaultFields },\n targetTable,\n };\n } else {\n return {\n relation: relationName,\n targetTable,\n };\n }\n }\n }\n\n /**\n * Builds a single expand string with its options.\n */\n private buildSingleExpand(config: ExpandConfig): string {\n const relationName = this.resolveRelationName(config);\n const parts = this.buildExpandParts(config);\n\n if (parts.length === 0) {\n return relationName;\n }\n\n return `${relationName}(${parts.join(\";\")})`;\n }\n\n /**\n * Resolves relation name, using FMTID if entity IDs are enabled.\n */\n private resolveRelationName(config: ExpandConfig): string {\n if (!this.useEntityIds) {\n return config.relation;\n }\n\n const targetTable = config.targetTable;\n if (targetTable && FMTable.Symbol.EntityId in targetTable) {\n const tableId = (targetTable as any)[FMTable.Symbol.EntityId] as\n | `FMTID:${string}`\n | undefined;\n if (tableId) {\n return tableId;\n }\n }\n\n return config.relation;\n }\n\n /**\n * Builds expand parts (select, filter, orderBy, etc.) for a single expand.\n */\n private buildExpandParts(config: ExpandConfig): string[] {\n if (!config.options || Object.keys(config.options).length === 0) {\n return [];\n }\n\n const parts: string[] = [];\n const opts = config.options;\n\n if (opts.select) {\n const selectArray = Array.isArray(opts.select)\n ? opts.select.map(String)\n : [String(opts.select)];\n const selectFields = formatSelectFields(\n selectArray,\n config.targetTable,\n this.useEntityIds,\n );\n parts.push(`$select=${selectFields}`);\n }\n\n if (opts.filter) {\n const filterQuery = buildQuery({ filter: opts.filter });\n const match = filterQuery.match(/\\$filter=([^&]+)/);\n if (match) parts.push(`$filter=${match[1]}`);\n }\n\n if (opts.orderBy) {\n const orderByValue = Array.isArray(opts.orderBy)\n ? opts.orderBy.join(\",\")\n : String(opts.orderBy);\n parts.push(`$orderby=${orderByValue}`);\n }\n\n if (opts.top !== undefined) parts.push(`$top=${opts.top}`);\n if (opts.skip !== undefined) parts.push(`$skip=${opts.skip}`);\n\n if (opts.expand) {\n if (typeof opts.expand === \"string\") {\n parts.push(`$expand=${opts.expand}`);\n }\n }\n\n return parts;\n }\n}\n"],"names":[],"mappings":";;;;AAoBO,MAAM,cAAc;AAAA,EACzB,YACU,cACA,QACR;AAFQ,SAAA,eAAA;AACA,SAAA,SAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMV,kBAAkB,SAAiC;AAC7C,QAAA,QAAQ,WAAW,EAAU,QAAA;AAE1B,WAAA,QAAQ,IAAI,CAAC,WAAW,KAAK,kBAAkB,MAAM,CAAC,EAAE,KAAK,GAAG;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMzE,uBAAuB,SAAmD;AACjE,WAAA,QAAQ,IAAI,CAAC,WAAW;;AAC7B,YAAM,cAAc,OAAO;AAEvB,UAAA;AACJ,UAAI,aAAa;AACT,cAAA,kBAAkB,mBAAmB,WAAW;AAChD,cAAA,kBAAkB,gBAAgB,mBAAmB,CAAC;AAG5D,cAAM,SAAS,EAAE,GAAG,gBAAgB,OAAO;AAC3C,mBAAW,kBAAkB,iBAAiB;AAC5C,iBAAO,OAAO,cAAwB;AAAA,QAAA;AAGzB,uBAAA;AAAA,MAAA;AAGX,YAAA,mBAAiB,YAAO,YAAP,mBAAgB,UACnC,MAAM,QAAQ,OAAO,QAAQ,MAAM,IACjC,OAAO,QAAQ,OAAO,IAAI,MAAM,IAChC,CAAC,OAAO,OAAO,QAAQ,MAAM,CAAC,IAChC;AAEG,aAAA;AAAA,QACL,UAAU,OAAO;AAAA,QACjB;AAAA,QACA;AAAA,QACA,OAAO;AAAA,QACP;AAAA,QACA,eAAe;AAAA,MACjB;AAAA,IAAA,CACD;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaH,cACE,aACA,aACA,UACA,gBACc;;AAER,UAAA,eAAe,aAAa,WAAW;AAG7C,QAAI,aAAa;AACT,YAAA,kBAAkB,mBAAmB,WAAW;AACtD,UAAI,mBAAmB,CAAC,gBAAgB,SAAS,YAAY,GAAG;AAC9D,aAAK,OAAO;AAAA,UACV,qBAAqB,YAAY,8BAA8B,gBAAgB,SAAS,IAAI,gBAAgB,KAAK,IAAI,IAAI,MAAM;AAAA,QACjI;AAAA,MAAA;AAAA,IACF;AAGF,QAAI,YAAY,gBAAgB;AAE9B,YAAM,gBAAgB,eAAe;AAG/B,YAAA,oBAAoB,SAAS,aAAa;AAGhD,YAAM,gBAA4C;AAAA,QAChD,GAAI,kBAA0B;AAAA,MAChC;AAGI,UAAA,CAAC,cAAc,QAAQ;AACnB,cAAA,gBAAgB,uBAAuB,WAAW;AACxD,YAAI,eAAe;AACjB,wBAAc,SAAS;AAAA,QAAA;AAAA,MACzB;AAIG,YAAA,uBAA0B,kBAA1B,mBAAyC,UAAS,GAAG;AAExD,cAAM,qBAAqB,KAAK;AAAA,UAC7B,kBAA0B;AAAA,QAC7B;AACA,YAAI,oBAAoB;AAEtB,wBAAc,SAAS;AAAA,QAAA;AAAA,MACzB;AAGK,aAAA;AAAA,QACL,UAAU;AAAA,QACV,SAAS;AAAA,QACT;AAAA,MACF;AAAA,IAAA,OACK;AAEC,YAAA,gBAAgB,uBAAuB,WAAW;AACxD,UAAI,eAAe;AACV,eAAA;AAAA,UACL,UAAU;AAAA,UACV,SAAS,EAAE,QAAQ,cAAc;AAAA,UACjC;AAAA,QACF;AAAA,MAAA,OACK;AACE,eAAA;AAAA,UACL,UAAU;AAAA,UACV;AAAA,QACF;AAAA,MAAA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMM,kBAAkB,QAA8B;AAChD,UAAA,eAAe,KAAK,oBAAoB,MAAM;AAC9C,UAAA,QAAQ,KAAK,iBAAiB,MAAM;AAEtC,QAAA,MAAM,WAAW,GAAG;AACf,aAAA;AAAA,IAAA;AAGT,WAAO,GAAG,YAAY,IAAI,MAAM,KAAK,GAAG,CAAC;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMnC,oBAAoB,QAA8B;AACpD,QAAA,CAAC,KAAK,cAAc;AACtB,aAAO,OAAO;AAAA,IAAA;AAGhB,UAAM,cAAc,OAAO;AAC3B,QAAI,eAAe,QAAQ,OAAO,YAAY,aAAa;AACzD,YAAM,UAAW,YAAoB,QAAQ,OAAO,QAAQ;AAG5D,UAAI,SAAS;AACJ,eAAA;AAAA,MAAA;AAAA,IACT;AAGF,WAAO,OAAO;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMR,iBAAiB,QAAgC;AACnD,QAAA,CAAC,OAAO,WAAW,OAAO,KAAK,OAAO,OAAO,EAAE,WAAW,GAAG;AAC/D,aAAO,CAAC;AAAA,IAAA;AAGV,UAAM,QAAkB,CAAC;AACzB,UAAM,OAAO,OAAO;AAEpB,QAAI,KAAK,QAAQ;AACf,YAAM,cAAc,MAAM,QAAQ,KAAK,MAAM,IACzC,KAAK,OAAO,IAAI,MAAM,IACtB,CAAC,OAAO,KAAK,MAAM,CAAC;AACxB,YAAM,eAAe;AAAA,QACnB;AAAA,QACA,OAAO;AAAA,QACP,KAAK;AAAA,MACP;AACM,YAAA,KAAK,WAAW,YAAY,EAAE;AAAA,IAAA;AAGtC,QAAI,KAAK,QAAQ;AACf,YAAM,cAAc,WAAW,EAAE,QAAQ,KAAK,QAAQ;AAChD,YAAA,QAAQ,YAAY,MAAM,kBAAkB;AAClD,UAAI,MAAa,OAAA,KAAK,WAAW,MAAM,CAAC,CAAC,EAAE;AAAA,IAAA;AAG7C,QAAI,KAAK,SAAS;AAChB,YAAM,eAAe,MAAM,QAAQ,KAAK,OAAO,IAC3C,KAAK,QAAQ,KAAK,GAAG,IACrB,OAAO,KAAK,OAAO;AACjB,YAAA,KAAK,YAAY,YAAY,EAAE;AAAA,IAAA;AAGnC,QAAA,KAAK,QAAQ,OAAW,OAAM,KAAK,QAAQ,KAAK,GAAG,EAAE;AACrD,QAAA,KAAK,SAAS,OAAW,OAAM,KAAK,SAAS,KAAK,IAAI,EAAE;AAE5D,QAAI,KAAK,QAAQ;AACX,UAAA,OAAO,KAAK,WAAW,UAAU;AACnC,cAAM,KAAK,WAAW,KAAK,MAAM,EAAE;AAAA,MAAA;AAAA,IACrC;AAGK,WAAA;AAAA,EAAA;AAEX;"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FMTable } from '../../orm/table.js';
|
|
2
2
|
import { ExpandConfig } from './shared-types.js';
|
|
3
|
+
import { InternalLogger } from '../../logger.js';
|
|
3
4
|
/**
|
|
4
5
|
* Builds OData query string for $select and $expand parameters.
|
|
5
6
|
* Used by both QueryBuilder and RecordBuilder to eliminate duplication.
|
|
@@ -12,4 +13,5 @@ export declare function buildSelectExpandQueryString(config: {
|
|
|
12
13
|
expandConfigs: ExpandConfig[];
|
|
13
14
|
table?: FMTable<any, any>;
|
|
14
15
|
useEntityIds: boolean;
|
|
16
|
+
logger: InternalLogger;
|
|
15
17
|
}): string;
|
|
@@ -2,7 +2,7 @@ import { ExpandBuilder } from "./expand-builder.js";
|
|
|
2
2
|
import { formatSelectFields } from "./select-utils.js";
|
|
3
3
|
function buildSelectExpandQueryString(config) {
|
|
4
4
|
const parts = [];
|
|
5
|
-
const expandBuilder = new ExpandBuilder(config.useEntityIds);
|
|
5
|
+
const expandBuilder = new ExpandBuilder(config.useEntityIds, config.logger);
|
|
6
6
|
if (config.selectedFields && config.selectedFields.length > 0) {
|
|
7
7
|
const selectString = formatSelectFields(
|
|
8
8
|
config.selectedFields,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-string-builder.js","sources":["../../../../src/client/builders/query-string-builder.ts"],"sourcesContent":["import type { FMTable } from \"../../orm/table\";\nimport { ExpandBuilder } from \"./expand-builder\";\nimport type { ExpandConfig } from \"./shared-types\";\nimport { formatSelectFields } from \"./select-utils\";\n\n/**\n * Builds OData query string for $select and $expand parameters.\n * Used by both QueryBuilder and RecordBuilder to eliminate duplication.\n *\n * @param config - Configuration object\n * @returns Query string starting with ? or empty string if no parameters\n */\nexport function buildSelectExpandQueryString(config: {\n selectedFields?: string[];\n expandConfigs: ExpandConfig[];\n table?: FMTable<any, any>;\n useEntityIds: boolean;\n}): string {\n const parts: string[] = [];\n const expandBuilder = new ExpandBuilder(config.useEntityIds);\n\n // Build $select\n if (config.selectedFields && config.selectedFields.length > 0) {\n const selectString = formatSelectFields(\n config.selectedFields,\n config.table,\n config.useEntityIds,\n );\n if (selectString) {\n parts.push(`$select=${selectString}`);\n }\n }\n\n // Build $expand\n const expandString = expandBuilder.buildExpandString(config.expandConfigs);\n if (expandString) {\n parts.push(`$expand=${expandString}`);\n }\n\n return parts.length > 0 ? `?${parts.join(\"&\")}` : \"\";\n}\n"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"query-string-builder.js","sources":["../../../../src/client/builders/query-string-builder.ts"],"sourcesContent":["import type { FMTable } from \"../../orm/table\";\nimport { ExpandBuilder } from \"./expand-builder\";\nimport type { ExpandConfig } from \"./shared-types\";\nimport { formatSelectFields } from \"./select-utils\";\nimport { InternalLogger } from \"../../logger\";\n\n/**\n * Builds OData query string for $select and $expand parameters.\n * Used by both QueryBuilder and RecordBuilder to eliminate duplication.\n *\n * @param config - Configuration object\n * @returns Query string starting with ? or empty string if no parameters\n */\nexport function buildSelectExpandQueryString(config: {\n selectedFields?: string[];\n expandConfigs: ExpandConfig[];\n table?: FMTable<any, any>;\n useEntityIds: boolean;\n logger: InternalLogger;\n}): string {\n const parts: string[] = [];\n const expandBuilder = new ExpandBuilder(config.useEntityIds, config.logger);\n\n // Build $select\n if (config.selectedFields && config.selectedFields.length > 0) {\n const selectString = formatSelectFields(\n config.selectedFields,\n config.table,\n config.useEntityIds,\n );\n if (selectString) {\n parts.push(`$select=${selectString}`);\n }\n }\n\n // Build $expand\n const expandString = expandBuilder.buildExpandString(config.expandConfigs);\n if (expandString) {\n parts.push(`$expand=${expandString}`);\n }\n\n return parts.length > 0 ? `?${parts.join(\"&\")}` : \"\";\n}\n"],"names":[],"mappings":";;AAaO,SAAS,6BAA6B,QAMlC;AACT,QAAM,QAAkB,CAAC;AACzB,QAAM,gBAAgB,IAAI,cAAc,OAAO,cAAc,OAAO,MAAM;AAG1E,MAAI,OAAO,kBAAkB,OAAO,eAAe,SAAS,GAAG;AAC7D,UAAM,eAAe;AAAA,MACnB,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,IACT;AACA,QAAI,cAAc;AACV,YAAA,KAAK,WAAW,YAAY,EAAE;AAAA,IAAA;AAAA,EACtC;AAIF,QAAM,eAAe,cAAc,kBAAkB,OAAO,aAAa;AACzE,MAAI,cAAc;AACV,UAAA,KAAK,WAAW,YAAY,EAAE;AAAA,EAAA;AAG/B,SAAA,MAAM,SAAS,IAAI,IAAI,MAAM,KAAK,GAAG,CAAC,KAAK;AACpD;"}
|
|
@@ -2,6 +2,7 @@ import { FMTable } from '../../orm/table.js';
|
|
|
2
2
|
import { Result } from '../../types.js';
|
|
3
3
|
import { ExpandValidationConfig } from '../../validation.js';
|
|
4
4
|
import { ExpandConfig } from './shared-types.js';
|
|
5
|
+
import { InternalLogger } from '../../logger.js';
|
|
5
6
|
export interface ProcessResponseConfig {
|
|
6
7
|
table?: FMTable<any, any>;
|
|
7
8
|
schema?: Record<string, any>;
|
|
@@ -36,4 +37,5 @@ export declare function processQueryResponse<T>(response: any, config: {
|
|
|
36
37
|
skipValidation?: boolean;
|
|
37
38
|
useEntityIds?: boolean;
|
|
38
39
|
fieldMapping?: Record<string, string>;
|
|
40
|
+
logger: InternalLogger;
|
|
39
41
|
}): Promise<Result<any>>;
|
|
@@ -140,9 +140,10 @@ async function processQueryResponse(response, config) {
|
|
|
140
140
|
expandConfigs,
|
|
141
141
|
skipValidation,
|
|
142
142
|
useEntityIds,
|
|
143
|
-
fieldMapping
|
|
143
|
+
fieldMapping,
|
|
144
|
+
logger
|
|
144
145
|
} = config;
|
|
145
|
-
const expandBuilder = new ExpandBuilder(useEntityIds ?? false);
|
|
146
|
+
const expandBuilder = new ExpandBuilder(useEntityIds ?? false, logger);
|
|
146
147
|
const expandValidationConfigs = expandBuilder.buildValidationConfigs(expandConfigs);
|
|
147
148
|
const selectedFields = queryOptions.select ? Array.isArray(queryOptions.select) ? queryOptions.select.map(String) : [String(queryOptions.select)] : void 0;
|
|
148
149
|
let processedResponse = await processODataResponse(response, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response-processor.js","sources":["../../../../src/client/builders/response-processor.ts"],"sourcesContent":["import type { FMTable } from \"../../orm/table\";\nimport type { Result } from \"../../types\";\nimport type { ExpandValidationConfig } from \"../../validation\";\nimport { validateSingleResponse, validateListResponse } from \"../../validation\";\nimport { transformResponseFields } from \"../../transform\";\nimport { RecordCountMismatchError } from \"../../errors\";\nimport { getBaseTableConfig } from \"../../orm/table\";\nimport { ExpandBuilder } from \"./expand-builder\";\nimport type { ExpandConfig } from \"./shared-types\";\n\nexport interface ProcessResponseConfig {\n table?: FMTable<any, any>;\n schema?: Record<string, any>;\n singleMode: \"exact\" | \"maybe\" | false;\n selectedFields?: string[];\n expandValidationConfigs?: ExpandValidationConfig[];\n skipValidation?: boolean;\n useEntityIds?: boolean;\n // Mapping from field names to output keys (for renamed fields in select)\n fieldMapping?: Record<string, string>;\n}\n\n/**\n * Processes OData response with transformation and validation.\n * Shared by QueryBuilder and RecordBuilder.\n */\nexport async function processODataResponse<T>(\n rawResponse: any,\n config: ProcessResponseConfig,\n): Promise<Result<T>> {\n const {\n table,\n schema,\n singleMode,\n selectedFields,\n expandValidationConfigs,\n skipValidation,\n useEntityIds,\n fieldMapping,\n } = config;\n\n // Transform field IDs back to names if using entity IDs\n let response = rawResponse;\n if (table && useEntityIds) {\n response = transformResponseFields(\n response,\n table,\n expandValidationConfigs,\n );\n }\n\n // Fast path: skip validation\n if (skipValidation) {\n const result = extractRecords(response, singleMode);\n // Rename fields AFTER extraction (but before returning)\n if (result.data && fieldMapping && Object.keys(fieldMapping).length > 0) {\n if (result.error) {\n return { data: undefined, error: result.error } as Result<T>;\n }\n return {\n data: renameFieldsInResponse(result.data, fieldMapping) as T,\n error: undefined,\n };\n }\n return result as Result<T>;\n }\n\n // Validation path\n if (singleMode !== false) {\n const validation = await validateSingleResponse<any>(\n response,\n schema,\n selectedFields as any,\n expandValidationConfigs,\n singleMode,\n );\n\n if (!validation.valid) {\n return { data: undefined, error: validation.error };\n }\n\n // Rename fields AFTER validation completes\n if (fieldMapping && Object.keys(fieldMapping).length > 0) {\n return {\n data: renameFieldsInResponse(validation.data, fieldMapping) as T,\n error: undefined,\n };\n }\n\n return { data: validation.data as T, error: undefined };\n }\n\n const validation = await validateListResponse<any>(\n response,\n schema,\n selectedFields as any,\n expandValidationConfigs,\n );\n\n if (!validation.valid) {\n return { data: undefined, error: validation.error };\n }\n\n // Rename fields AFTER validation completes\n if (fieldMapping && Object.keys(fieldMapping).length > 0) {\n return {\n data: renameFieldsInResponse(validation.data, fieldMapping) as T,\n error: undefined,\n };\n }\n\n return { data: validation.data as T, error: undefined };\n}\n\n/**\n * Extracts records from response without validation.\n */\nfunction extractRecords<T>(\n response: any,\n singleMode: \"exact\" | \"maybe\" | false,\n): Result<T> {\n if (singleMode === false) {\n const records = response.value ?? [];\n return { data: records as T, error: undefined };\n }\n\n const records = response.value ?? [response];\n const count = Array.isArray(records) ? records.length : 1;\n\n if (count > 1) {\n return {\n data: undefined,\n error: new RecordCountMismatchError(\n singleMode === \"exact\" ? \"one\" : \"at-most-one\",\n count,\n ),\n };\n }\n\n if (count === 0) {\n if (singleMode === \"exact\") {\n return { data: undefined, error: new RecordCountMismatchError(\"one\", 0) };\n }\n return { data: null as T, error: undefined };\n }\n\n const record = Array.isArray(records) ? records[0] : records;\n return { data: record as T, error: undefined };\n}\n\n/**\n * Gets schema from a table occurrence, excluding container fields.\n * Container fields are never returned in regular responses (only via getSingleField).\n */\nexport function getSchemaFromTable(\n table: FMTable<any, any> | undefined,\n): Record<string, any> | undefined {\n if (!table) return undefined;\n const baseTableConfig = getBaseTableConfig(table);\n const containerFields = baseTableConfig.containerFields || [];\n\n // Filter out container fields from schema\n const schema = { ...baseTableConfig.schema };\n for (const containerField of containerFields) {\n delete schema[containerField as string];\n }\n\n return schema;\n}\n\n/**\n * Renames fields in response data according to the field mapping.\n * Used when select() is called with renamed fields (e.g., { userEmail: users.email }).\n */\nfunction renameFieldsInResponse(\n data: any,\n fieldMapping: Record<string, string>,\n): any {\n if (!data || typeof data !== \"object\") {\n return data;\n }\n\n // Handle array responses\n if (Array.isArray(data)) {\n return data.map((item) => renameFieldsInResponse(item, fieldMapping));\n }\n\n // Handle OData list response structure\n if (\"value\" in data && Array.isArray(data.value)) {\n return {\n ...data,\n value: data.value.map((item: any) =>\n renameFieldsInResponse(item, fieldMapping),\n ),\n };\n }\n\n // Handle single record\n const renamed: Record<string, any> = {};\n for (const [key, value] of Object.entries(data)) {\n // Check if this field should be renamed\n const outputKey = fieldMapping[key];\n if (outputKey) {\n renamed[outputKey] = value;\n } else {\n renamed[key] = value;\n }\n }\n return renamed;\n}\n\n/**\n * Processes query response with expand configs.\n * This is a convenience wrapper that builds validation configs from expand configs.\n */\nexport async function processQueryResponse<T>(\n response: any,\n config: {\n occurrence?: FMTable<any, any>;\n singleMode: \"exact\" | \"maybe\" | false;\n queryOptions: { select?: (keyof T)[] | string[] };\n expandConfigs: ExpandConfig[];\n skipValidation?: boolean;\n useEntityIds?: boolean;\n // Mapping from field names to output keys (for renamed fields in select)\n fieldMapping?: Record<string, string>;\n },\n): Promise<Result<any>> {\n const {\n occurrence,\n singleMode,\n queryOptions,\n expandConfigs,\n skipValidation,\n useEntityIds,\n fieldMapping,\n } = config;\n\n const expandBuilder = new ExpandBuilder(useEntityIds ?? false);\n const expandValidationConfigs =\n expandBuilder.buildValidationConfigs(expandConfigs);\n\n const selectedFields = queryOptions.select\n ? Array.isArray(queryOptions.select)\n ? queryOptions.select.map(String)\n : [String(queryOptions.select)]\n : undefined;\n\n // Process the response first\n let processedResponse = await processODataResponse(response, {\n table: occurrence,\n schema: getSchemaFromTable(occurrence),\n singleMode,\n selectedFields,\n expandValidationConfigs,\n skipValidation,\n useEntityIds,\n });\n\n // Rename fields if field mapping is provided (for renamed fields in select)\n if (\n processedResponse.data &&\n fieldMapping &&\n Object.keys(fieldMapping).length > 0\n ) {\n processedResponse = {\n ...processedResponse,\n data: renameFieldsInResponse(processedResponse.data, fieldMapping),\n };\n }\n\n return processedResponse;\n}\n"],"names":["validation","records"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"response-processor.js","sources":["../../../../src/client/builders/response-processor.ts"],"sourcesContent":["import type { FMTable } from \"../../orm/table\";\nimport type { Result } from \"../../types\";\nimport type { ExpandValidationConfig } from \"../../validation\";\nimport { validateSingleResponse, validateListResponse } from \"../../validation\";\nimport { transformResponseFields } from \"../../transform\";\nimport { RecordCountMismatchError } from \"../../errors\";\nimport { getBaseTableConfig } from \"../../orm/table\";\nimport { ExpandBuilder } from \"./expand-builder\";\nimport type { ExpandConfig } from \"./shared-types\";\nimport { InternalLogger } from \"../../logger\";\n\nexport interface ProcessResponseConfig {\n table?: FMTable<any, any>;\n schema?: Record<string, any>;\n singleMode: \"exact\" | \"maybe\" | false;\n selectedFields?: string[];\n expandValidationConfigs?: ExpandValidationConfig[];\n skipValidation?: boolean;\n useEntityIds?: boolean;\n // Mapping from field names to output keys (for renamed fields in select)\n fieldMapping?: Record<string, string>;\n}\n\n/**\n * Processes OData response with transformation and validation.\n * Shared by QueryBuilder and RecordBuilder.\n */\nexport async function processODataResponse<T>(\n rawResponse: any,\n config: ProcessResponseConfig,\n): Promise<Result<T>> {\n const {\n table,\n schema,\n singleMode,\n selectedFields,\n expandValidationConfigs,\n skipValidation,\n useEntityIds,\n fieldMapping,\n } = config;\n\n // Transform field IDs back to names if using entity IDs\n let response = rawResponse;\n if (table && useEntityIds) {\n response = transformResponseFields(\n response,\n table,\n expandValidationConfigs,\n );\n }\n\n // Fast path: skip validation\n if (skipValidation) {\n const result = extractRecords(response, singleMode);\n // Rename fields AFTER extraction (but before returning)\n if (result.data && fieldMapping && Object.keys(fieldMapping).length > 0) {\n if (result.error) {\n return { data: undefined, error: result.error } as Result<T>;\n }\n return {\n data: renameFieldsInResponse(result.data, fieldMapping) as T,\n error: undefined,\n };\n }\n return result as Result<T>;\n }\n\n // Validation path\n if (singleMode !== false) {\n const validation = await validateSingleResponse<any>(\n response,\n schema,\n selectedFields as any,\n expandValidationConfigs,\n singleMode,\n );\n\n if (!validation.valid) {\n return { data: undefined, error: validation.error };\n }\n\n // Rename fields AFTER validation completes\n if (fieldMapping && Object.keys(fieldMapping).length > 0) {\n return {\n data: renameFieldsInResponse(validation.data, fieldMapping) as T,\n error: undefined,\n };\n }\n\n return { data: validation.data as T, error: undefined };\n }\n\n const validation = await validateListResponse<any>(\n response,\n schema,\n selectedFields as any,\n expandValidationConfigs,\n );\n\n if (!validation.valid) {\n return { data: undefined, error: validation.error };\n }\n\n // Rename fields AFTER validation completes\n if (fieldMapping && Object.keys(fieldMapping).length > 0) {\n return {\n data: renameFieldsInResponse(validation.data, fieldMapping) as T,\n error: undefined,\n };\n }\n\n return { data: validation.data as T, error: undefined };\n}\n\n/**\n * Extracts records from response without validation.\n */\nfunction extractRecords<T>(\n response: any,\n singleMode: \"exact\" | \"maybe\" | false,\n): Result<T> {\n if (singleMode === false) {\n const records = response.value ?? [];\n return { data: records as T, error: undefined };\n }\n\n const records = response.value ?? [response];\n const count = Array.isArray(records) ? records.length : 1;\n\n if (count > 1) {\n return {\n data: undefined,\n error: new RecordCountMismatchError(\n singleMode === \"exact\" ? \"one\" : \"at-most-one\",\n count,\n ),\n };\n }\n\n if (count === 0) {\n if (singleMode === \"exact\") {\n return { data: undefined, error: new RecordCountMismatchError(\"one\", 0) };\n }\n return { data: null as T, error: undefined };\n }\n\n const record = Array.isArray(records) ? records[0] : records;\n return { data: record as T, error: undefined };\n}\n\n/**\n * Gets schema from a table occurrence, excluding container fields.\n * Container fields are never returned in regular responses (only via getSingleField).\n */\nexport function getSchemaFromTable(\n table: FMTable<any, any> | undefined,\n): Record<string, any> | undefined {\n if (!table) return undefined;\n const baseTableConfig = getBaseTableConfig(table);\n const containerFields = baseTableConfig.containerFields || [];\n\n // Filter out container fields from schema\n const schema = { ...baseTableConfig.schema };\n for (const containerField of containerFields) {\n delete schema[containerField as string];\n }\n\n return schema;\n}\n\n/**\n * Renames fields in response data according to the field mapping.\n * Used when select() is called with renamed fields (e.g., { userEmail: users.email }).\n */\nfunction renameFieldsInResponse(\n data: any,\n fieldMapping: Record<string, string>,\n): any {\n if (!data || typeof data !== \"object\") {\n return data;\n }\n\n // Handle array responses\n if (Array.isArray(data)) {\n return data.map((item) => renameFieldsInResponse(item, fieldMapping));\n }\n\n // Handle OData list response structure\n if (\"value\" in data && Array.isArray(data.value)) {\n return {\n ...data,\n value: data.value.map((item: any) =>\n renameFieldsInResponse(item, fieldMapping),\n ),\n };\n }\n\n // Handle single record\n const renamed: Record<string, any> = {};\n for (const [key, value] of Object.entries(data)) {\n // Check if this field should be renamed\n const outputKey = fieldMapping[key];\n if (outputKey) {\n renamed[outputKey] = value;\n } else {\n renamed[key] = value;\n }\n }\n return renamed;\n}\n\n/**\n * Processes query response with expand configs.\n * This is a convenience wrapper that builds validation configs from expand configs.\n */\nexport async function processQueryResponse<T>(\n response: any,\n config: {\n occurrence?: FMTable<any, any>;\n singleMode: \"exact\" | \"maybe\" | false;\n queryOptions: { select?: (keyof T)[] | string[] };\n expandConfigs: ExpandConfig[];\n skipValidation?: boolean;\n useEntityIds?: boolean;\n // Mapping from field names to output keys (for renamed fields in select)\n fieldMapping?: Record<string, string>;\n logger: InternalLogger;\n },\n): Promise<Result<any>> {\n const {\n occurrence,\n singleMode,\n queryOptions,\n expandConfigs,\n skipValidation,\n useEntityIds,\n fieldMapping,\n logger,\n } = config;\n\n const expandBuilder = new ExpandBuilder(useEntityIds ?? false, logger);\n const expandValidationConfigs =\n expandBuilder.buildValidationConfigs(expandConfigs);\n\n const selectedFields = queryOptions.select\n ? Array.isArray(queryOptions.select)\n ? queryOptions.select.map(String)\n : [String(queryOptions.select)]\n : undefined;\n\n // Process the response first\n let processedResponse = await processODataResponse(response, {\n table: occurrence,\n schema: getSchemaFromTable(occurrence),\n singleMode,\n selectedFields,\n expandValidationConfigs,\n skipValidation,\n useEntityIds,\n });\n\n // Rename fields if field mapping is provided (for renamed fields in select)\n if (\n processedResponse.data &&\n fieldMapping &&\n Object.keys(fieldMapping).length > 0\n ) {\n processedResponse = {\n ...processedResponse,\n data: renameFieldsInResponse(processedResponse.data, fieldMapping),\n };\n }\n\n return processedResponse;\n}\n"],"names":["validation","records"],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { InternalLogger } from '../../logger.js';
|
|
1
2
|
import { Column } from '../../orm/column.js';
|
|
2
3
|
/**
|
|
3
4
|
* Utility function for processing select() calls.
|
|
@@ -6,7 +7,7 @@ import { Column } from '../../orm/column.js';
|
|
|
6
7
|
* @param fields - Field names or Column references
|
|
7
8
|
* @returns Object with selectedFields array
|
|
8
9
|
*/
|
|
9
|
-
export declare function processSelectFields(...fields: (string | Column<any, string>)[]): {
|
|
10
|
+
export declare function processSelectFields(...fields: (string | Column<any, any, string>)[]): {
|
|
10
11
|
selectedFields: string[];
|
|
11
12
|
};
|
|
12
13
|
/**
|
|
@@ -18,7 +19,7 @@ export declare function processSelectFields(...fields: (string | Column<any, str
|
|
|
18
19
|
* @param tableName - Expected table name for validation
|
|
19
20
|
* @returns Object with selectedFields array and fieldMapping for renamed fields
|
|
20
21
|
*/
|
|
21
|
-
export declare function processSelectWithRenames<TTableName extends string>(fields: Record<string, Column<any, TTableName>>, tableName: string): {
|
|
22
|
+
export declare function processSelectWithRenames<TTableName extends string>(fields: Record<string, Column<any, any, TTableName>>, tableName: string, logger: InternalLogger): {
|
|
22
23
|
selectedFields: string[];
|
|
23
24
|
fieldMapping: Record<string, string>;
|
|
24
25
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isColumn } from "../../orm/column.js";
|
|
2
|
-
function processSelectWithRenames(fields, tableName) {
|
|
2
|
+
function processSelectWithRenames(fields, tableName, logger) {
|
|
3
3
|
const selectedFields = [];
|
|
4
4
|
const fieldMapping = {};
|
|
5
5
|
for (const [outputKey, column] of Object.entries(fields)) {
|
|
@@ -9,7 +9,7 @@ function processSelectWithRenames(fields, tableName) {
|
|
|
9
9
|
);
|
|
10
10
|
}
|
|
11
11
|
if (column.tableName !== tableName) {
|
|
12
|
-
|
|
12
|
+
logger.warn(
|
|
13
13
|
`Column ${column.toString()} is from table "${column.tableName}", but query is for table "${tableName}"`
|
|
14
14
|
);
|
|
15
15
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select-mixin.js","sources":["../../../../src/client/builders/select-mixin.ts"],"sourcesContent":["import { isColumn, type Column } from \"../../orm/column\";\n\n/**\n * Utility function for processing select() calls.\n * Used by both QueryBuilder and RecordBuilder to eliminate duplication.\n *\n * @param fields - Field names or Column references\n * @returns Object with selectedFields array\n */\nexport function processSelectFields(\n ...fields: (string | Column<any, string>)[]\n): { selectedFields: string[] } {\n const fieldNames = fields.map((field) => {\n if (isColumn(field)) {\n return field.fieldName as string;\n }\n return String(field);\n });\n return { selectedFields: [...new Set(fieldNames)] };\n}\n\n/**\n * Processes select() calls with field renaming support.\n * Validates columns belong to the correct table and builds field mapping for renamed fields.\n * Used by both QueryBuilder and RecordBuilder to eliminate duplication.\n *\n * @param fields - Object mapping output keys to column references\n * @param tableName - Expected table name for validation\n * @returns Object with selectedFields array and fieldMapping for renamed fields\n */\nexport function processSelectWithRenames<TTableName extends string>(\n fields: Record<string, Column<any, TTableName>>,\n tableName: string,\n): { selectedFields: string[]; fieldMapping: Record<string, string> } {\n const selectedFields: string[] = [];\n const fieldMapping: Record<string, string> = {};\n\n for (const [outputKey, column] of Object.entries(fields)) {\n if (!isColumn(column)) {\n throw new Error(\n `select() expects column references, but got: ${typeof column}`,\n );\n }\n\n // Warn (not throw) on table mismatch for consistency\n if (column.tableName !== tableName) {\n
|
|
1
|
+
{"version":3,"file":"select-mixin.js","sources":["../../../../src/client/builders/select-mixin.ts"],"sourcesContent":["import { InternalLogger } from \"../../logger\";\nimport { isColumn, type Column } from \"../../orm/column\";\n\n/**\n * Utility function for processing select() calls.\n * Used by both QueryBuilder and RecordBuilder to eliminate duplication.\n *\n * @param fields - Field names or Column references\n * @returns Object with selectedFields array\n */\nexport function processSelectFields(\n ...fields: (string | Column<any, any, string>)[]\n): { selectedFields: string[] } {\n const fieldNames = fields.map((field) => {\n if (isColumn(field)) {\n return field.fieldName as string;\n }\n return String(field);\n });\n return { selectedFields: [...new Set(fieldNames)] };\n}\n\n/**\n * Processes select() calls with field renaming support.\n * Validates columns belong to the correct table and builds field mapping for renamed fields.\n * Used by both QueryBuilder and RecordBuilder to eliminate duplication.\n *\n * @param fields - Object mapping output keys to column references\n * @param tableName - Expected table name for validation\n * @returns Object with selectedFields array and fieldMapping for renamed fields\n */\nexport function processSelectWithRenames<TTableName extends string>(\n fields: Record<string, Column<any, any, TTableName>>,\n tableName: string,\n logger: InternalLogger,\n): { selectedFields: string[]; fieldMapping: Record<string, string> } {\n const selectedFields: string[] = [];\n const fieldMapping: Record<string, string> = {};\n\n for (const [outputKey, column] of Object.entries(fields)) {\n if (!isColumn(column)) {\n throw new Error(\n `select() expects column references, but got: ${typeof column}`,\n );\n }\n\n // Warn (not throw) on table mismatch for consistency\n if (column.tableName !== tableName) {\n logger.warn(\n `Column ${column.toString()} is from table \"${column.tableName}\", but query is for table \"${tableName}\"`,\n );\n }\n\n const fieldName = column.fieldName;\n selectedFields.push(fieldName);\n\n // Build mapping from field name to output key (only if renamed)\n if (fieldName !== outputKey) {\n fieldMapping[fieldName] = outputKey;\n }\n }\n\n return {\n selectedFields,\n fieldMapping: Object.keys(fieldMapping).length > 0 ? fieldMapping : {},\n };\n}\n\n/**\n * Legacy class name for backward compatibility.\n * @deprecated Use processSelectFields function instead\n */\nexport class SelectMixin {\n static processSelect = processSelectFields;\n}\n"],"names":[],"mappings":";AA+BgB,SAAA,yBACd,QACA,WACA,QACoE;AACpE,QAAM,iBAA2B,CAAC;AAClC,QAAM,eAAuC,CAAC;AAE9C,aAAW,CAAC,WAAW,MAAM,KAAK,OAAO,QAAQ,MAAM,GAAG;AACpD,QAAA,CAAC,SAAS,MAAM,GAAG;AACrB,YAAM,IAAI;AAAA,QACR,gDAAgD,OAAO,MAAM;AAAA,MAC/D;AAAA,IAAA;AAIE,QAAA,OAAO,cAAc,WAAW;AAC3B,aAAA;AAAA,QACL,UAAU,OAAO,UAAU,mBAAmB,OAAO,SAAS,8BAA8B,SAAS;AAAA,MACvG;AAAA,IAAA;AAGF,UAAM,YAAY,OAAO;AACzB,mBAAe,KAAK,SAAS;AAG7B,QAAI,cAAc,WAAW;AAC3B,mBAAa,SAAS,IAAI;AAAA,IAAA;AAAA,EAC5B;AAGK,SAAA;AAAA,IACL;AAAA,IACA,cAAc,OAAO,KAAK,YAAY,EAAE,SAAS,IAAI,eAAe,CAAA;AAAA,EACtE;AACF;"}
|
|
@@ -5,16 +5,7 @@ import { InsertBuilder } from './insert-builder.js';
|
|
|
5
5
|
import { DeleteBuilder } from './delete-builder.js';
|
|
6
6
|
import { UpdateBuilder } from './update-builder.js';
|
|
7
7
|
import { Database } from './database.js';
|
|
8
|
-
import { FMTable, InferSchemaOutputFromFMTable, InsertDataFromFMTable, UpdateDataFromFMTable, ValidExpandTarget
|
|
9
|
-
import { Column } from '../orm/column.js';
|
|
10
|
-
import { FieldBuilder } from '../orm/field-builders.js';
|
|
11
|
-
/**
|
|
12
|
-
* Helper type to extract properly-typed columns from an FMTable.
|
|
13
|
-
* This preserves the specific column types instead of widening to `any`.
|
|
14
|
-
*/
|
|
15
|
-
type ExtractColumnsFromOcc<T> = T extends FMTable<infer TFields, infer TName, any> ? TFields extends Record<string, FieldBuilder<any, any, any, any>> ? {
|
|
16
|
-
[K in keyof TFields]: Column<InferFieldOutput<TFields[K]>, TName>;
|
|
17
|
-
} : never : never;
|
|
8
|
+
import { FMTable, InferSchemaOutputFromFMTable, InsertDataFromFMTable, UpdateDataFromFMTable, ValidExpandTarget } from '../orm/table.js';
|
|
18
9
|
export declare class EntitySet<Occ extends FMTable<any, any>> {
|
|
19
10
|
private occurrence;
|
|
20
11
|
private databaseName;
|
|
@@ -25,6 +16,7 @@ export declare class EntitySet<Occ extends FMTable<any, any>> {
|
|
|
25
16
|
private navigateSourceTableName?;
|
|
26
17
|
private navigateBasePath?;
|
|
27
18
|
private databaseUseEntityIds;
|
|
19
|
+
private logger;
|
|
28
20
|
constructor(config: {
|
|
29
21
|
occurrence: Occ;
|
|
30
22
|
databaseName: string;
|
|
@@ -37,8 +29,8 @@ export declare class EntitySet<Occ extends FMTable<any, any>> {
|
|
|
37
29
|
context: ExecutionContext;
|
|
38
30
|
database: Database;
|
|
39
31
|
}): EntitySet<Occ>;
|
|
40
|
-
list(): QueryBuilder<Occ, keyof InferSchemaOutputFromFMTable<Occ>, false, false, {}
|
|
41
|
-
get(id: string | number): RecordBuilder<Occ, false,
|
|
32
|
+
list(): QueryBuilder<Occ, keyof InferSchemaOutputFromFMTable<Occ>, false, false, {}>;
|
|
33
|
+
get(id: string | number): RecordBuilder<Occ, false, undefined, keyof InferSchemaOutputFromFMTable<Occ>, {}>;
|
|
42
34
|
insert(data: InsertDataFromFMTable<Occ>, options: {
|
|
43
35
|
returnFullRecord: false;
|
|
44
36
|
}): InsertBuilder<Occ, "minimal">;
|
|
@@ -54,4 +46,3 @@ export declare class EntitySet<Occ extends FMTable<any, any>> {
|
|
|
54
46
|
delete(): DeleteBuilder<Occ>;
|
|
55
47
|
navigate<TargetTable extends FMTable<any, any>>(targetTable: ValidExpandTarget<Occ, TargetTable>): EntitySet<TargetTable extends FMTable<any, any> ? TargetTable : never>;
|
|
56
48
|
}
|
|
57
|
-
export {};
|
|
@@ -7,6 +7,7 @@ import { InsertBuilder } from "./insert-builder.js";
|
|
|
7
7
|
import { DeleteBuilder } from "./delete-builder.js";
|
|
8
8
|
import { UpdateBuilder } from "./update-builder.js";
|
|
9
9
|
import { getDefaultSelect, FMTable, getTableColumns, getTableName } from "../orm/table.js";
|
|
10
|
+
import { createLogger } from "../logger.js";
|
|
10
11
|
class EntitySet {
|
|
11
12
|
constructor(config) {
|
|
12
13
|
__publicField(this, "occurrence");
|
|
@@ -20,12 +21,14 @@ class EntitySet {
|
|
|
20
21
|
__publicField(this, "navigateBasePath");
|
|
21
22
|
// Full base path for chained navigations
|
|
22
23
|
__publicField(this, "databaseUseEntityIds");
|
|
23
|
-
|
|
24
|
+
__publicField(this, "logger");
|
|
25
|
+
var _a, _b, _c;
|
|
24
26
|
this.occurrence = config.occurrence;
|
|
25
27
|
this.databaseName = config.databaseName;
|
|
26
28
|
this.context = config.context;
|
|
27
29
|
this.database = config.database;
|
|
28
30
|
this.databaseUseEntityIds = ((_a = config.database) == null ? void 0 : _a._useEntityIds) ?? false;
|
|
31
|
+
this.logger = ((_c = (_b = config.context) == null ? void 0 : _b._getLogger) == null ? void 0 : _c.call(_b)) ?? createLogger();
|
|
29
32
|
}
|
|
30
33
|
// Type-only method to help TypeScript infer the schema from table
|
|
31
34
|
static create(config) {
|
|
@@ -167,7 +170,7 @@ class EntitySet {
|
|
|
167
170
|
if (this.occurrence && FMTable.Symbol.NavigationPaths in this.occurrence) {
|
|
168
171
|
const navigationPaths = this.occurrence[FMTable.Symbol.NavigationPaths];
|
|
169
172
|
if (navigationPaths && !navigationPaths.includes(relationName)) {
|
|
170
|
-
|
|
173
|
+
this.logger.warn(
|
|
171
174
|
`Cannot navigate to "${relationName}". Valid navigation paths: ${navigationPaths.length > 0 ? navigationPaths.join(", ") : "none"}`
|
|
172
175
|
);
|
|
173
176
|
}
|