@proofkit/fmodata 0.1.0-alpha.9 → 0.1.0-beta.24
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/LICENSE.md +21 -0
- package/README.md +655 -453
- package/dist/esm/client/batch-builder.d.ts +10 -9
- package/dist/esm/client/batch-builder.js +119 -56
- package/dist/esm/client/batch-builder.js.map +1 -1
- package/dist/esm/client/batch-request.js +16 -21
- package/dist/esm/client/batch-request.js.map +1 -1
- package/dist/esm/client/builders/default-select.d.ts +10 -0
- package/dist/esm/client/builders/default-select.js +41 -0
- package/dist/esm/client/builders/default-select.js.map +1 -0
- package/dist/esm/client/builders/expand-builder.d.ts +45 -0
- package/dist/esm/client/builders/expand-builder.js +185 -0
- package/dist/esm/client/builders/expand-builder.js.map +1 -0
- package/dist/esm/client/builders/index.d.ts +9 -0
- package/dist/esm/client/builders/query-string-builder.d.ts +18 -0
- package/dist/esm/client/builders/query-string-builder.js +21 -0
- package/dist/esm/client/builders/query-string-builder.js.map +1 -0
- package/dist/esm/client/builders/response-processor.d.ts +43 -0
- package/dist/esm/client/builders/response-processor.js +175 -0
- package/dist/esm/client/builders/response-processor.js.map +1 -0
- package/dist/esm/client/builders/select-mixin.d.ts +25 -0
- package/dist/esm/client/builders/select-mixin.js +28 -0
- package/dist/esm/client/builders/select-mixin.js.map +1 -0
- package/dist/esm/client/builders/select-utils.d.ts +18 -0
- package/dist/esm/client/builders/select-utils.js +30 -0
- package/dist/esm/client/builders/select-utils.js.map +1 -0
- package/dist/esm/client/builders/shared-types.d.ts +40 -0
- package/dist/esm/client/builders/table-utils.d.ts +35 -0
- package/dist/esm/client/builders/table-utils.js +44 -0
- package/dist/esm/client/builders/table-utils.js.map +1 -0
- package/dist/esm/client/database.d.ts +34 -22
- package/dist/esm/client/database.js +48 -84
- package/dist/esm/client/database.js.map +1 -1
- package/dist/esm/client/delete-builder.d.ts +25 -30
- package/dist/esm/client/delete-builder.js +45 -30
- package/dist/esm/client/delete-builder.js.map +1 -1
- package/dist/esm/client/entity-set.d.ts +35 -43
- package/dist/esm/client/entity-set.js +126 -52
- package/dist/esm/client/entity-set.js.map +1 -1
- package/dist/esm/client/error-parser.d.ts +12 -0
- package/dist/esm/client/error-parser.js +25 -0
- package/dist/esm/client/error-parser.js.map +1 -0
- package/dist/esm/client/filemaker-odata.d.ts +26 -7
- package/dist/esm/client/filemaker-odata.js +65 -42
- package/dist/esm/client/filemaker-odata.js.map +1 -1
- package/dist/esm/client/insert-builder.d.ts +19 -24
- package/dist/esm/client/insert-builder.js +94 -58
- package/dist/esm/client/insert-builder.js.map +1 -1
- package/dist/esm/client/query/expand-builder.d.ts +35 -0
- package/dist/esm/client/query/index.d.ts +4 -0
- package/dist/esm/client/query/query-builder.d.ts +132 -0
- package/dist/esm/client/query/query-builder.js +456 -0
- package/dist/esm/client/query/query-builder.js.map +1 -0
- package/dist/esm/client/query/response-processor.d.ts +25 -0
- package/dist/esm/client/query/types.d.ts +77 -0
- package/dist/esm/client/query/url-builder.d.ts +71 -0
- package/dist/esm/client/query/url-builder.js +100 -0
- package/dist/esm/client/query/url-builder.js.map +1 -0
- package/dist/esm/client/query-builder.d.ts +2 -115
- package/dist/esm/client/record-builder.d.ts +108 -36
- package/dist/esm/client/record-builder.js +284 -119
- package/dist/esm/client/record-builder.js.map +1 -1
- package/dist/esm/client/response-processor.d.ts +4 -9
- package/dist/esm/client/sanitize-json.d.ts +35 -0
- package/dist/esm/client/sanitize-json.js +27 -0
- package/dist/esm/client/sanitize-json.js.map +1 -0
- package/dist/esm/client/schema-manager.d.ts +5 -5
- package/dist/esm/client/schema-manager.js +45 -31
- package/dist/esm/client/schema-manager.js.map +1 -1
- package/dist/esm/client/update-builder.d.ts +34 -40
- package/dist/esm/client/update-builder.js +99 -58
- package/dist/esm/client/update-builder.js.map +1 -1
- package/dist/esm/client/webhook-builder.d.ts +126 -0
- package/dist/esm/client/webhook-builder.js +189 -0
- package/dist/esm/client/webhook-builder.js.map +1 -0
- package/dist/esm/errors.d.ts +19 -2
- package/dist/esm/errors.js +39 -4
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +10 -8
- package/dist/esm/index.js +40 -10
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/logger.d.ts +47 -0
- package/dist/esm/logger.js +69 -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 +62 -0
- package/dist/esm/orm/column.js +63 -0
- package/dist/esm/orm/column.js.map +1 -0
- package/dist/esm/orm/field-builders.d.ts +164 -0
- package/dist/esm/orm/field-builders.js +158 -0
- package/dist/esm/orm/field-builders.js.map +1 -0
- package/dist/esm/orm/index.d.ts +5 -0
- package/dist/esm/orm/operators.d.ts +173 -0
- package/dist/esm/orm/operators.js +260 -0
- package/dist/esm/orm/operators.js.map +1 -0
- package/dist/esm/orm/table.d.ts +355 -0
- package/dist/esm/orm/table.js +202 -0
- package/dist/esm/orm/table.js.map +1 -0
- package/dist/esm/transform.d.ts +20 -21
- package/dist/esm/transform.js +44 -45
- package/dist/esm/transform.js.map +1 -1
- package/dist/esm/types.d.ts +96 -30
- package/dist/esm/types.js +7 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/validation.d.ts +22 -12
- package/dist/esm/validation.js +132 -85
- package/dist/esm/validation.js.map +1 -1
- package/package.json +34 -29
- package/src/client/batch-builder.ts +153 -89
- package/src/client/batch-request.ts +25 -41
- package/src/client/builders/default-select.ts +75 -0
- package/src/client/builders/expand-builder.ts +246 -0
- package/src/client/builders/index.ts +11 -0
- package/src/client/builders/query-string-builder.ts +46 -0
- package/src/client/builders/response-processor.ts +279 -0
- package/src/client/builders/select-mixin.ts +65 -0
- package/src/client/builders/select-utils.ts +59 -0
- package/src/client/builders/shared-types.ts +45 -0
- package/src/client/builders/table-utils.ts +83 -0
- package/src/client/database.ts +89 -183
- package/src/client/delete-builder.ts +74 -84
- package/src/client/entity-set.ts +286 -293
- package/src/client/error-parser.ts +41 -0
- package/src/client/filemaker-odata.ts +98 -66
- package/src/client/insert-builder.ts +157 -118
- package/src/client/query/expand-builder.ts +160 -0
- package/src/client/query/index.ts +14 -0
- package/src/client/query/query-builder.ts +729 -0
- package/src/client/query/response-processor.ts +226 -0
- package/src/client/query/types.ts +126 -0
- package/src/client/query/url-builder.ts +151 -0
- package/src/client/query-builder.ts +10 -1455
- package/src/client/record-builder.ts +575 -240
- package/src/client/response-processor.ts +15 -42
- package/src/client/sanitize-json.ts +64 -0
- package/src/client/schema-manager.ts +61 -76
- package/src/client/update-builder.ts +161 -143
- package/src/client/webhook-builder.ts +265 -0
- package/src/errors.ts +49 -16
- package/src/index.ts +99 -54
- package/src/logger.test.ts +34 -0
- package/src/logger.ts +116 -0
- package/src/orm/column.ts +106 -0
- package/src/orm/field-builders.ts +250 -0
- package/src/orm/index.ts +61 -0
- package/src/orm/operators.ts +473 -0
- package/src/orm/table.ts +741 -0
- package/src/transform.ts +90 -70
- package/src/types.ts +154 -113
- package/src/validation.ts +200 -115
- package/dist/esm/client/base-table.d.ts +0 -125
- package/dist/esm/client/base-table.js +0 -57
- package/dist/esm/client/base-table.js.map +0 -1
- package/dist/esm/client/query-builder.js +0 -896
- package/dist/esm/client/query-builder.js.map +0 -1
- package/dist/esm/client/table-occurrence.d.ts +0 -72
- package/dist/esm/client/table-occurrence.js +0 -74
- package/dist/esm/client/table-occurrence.js.map +0 -1
- package/dist/esm/filter-types.d.ts +0 -76
- package/src/client/base-table.ts +0 -175
- package/src/client/query-builder.ts.bak +0 -1457
- package/src/client/table-occurrence.ts +0 -175
- package/src/filter-types.ts +0 -97
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { InternalLogger } from '../../logger.js';
|
|
2
|
+
import { FMTable } from '../../orm/table.js';
|
|
3
|
+
import { ExpandValidationConfig } from '../../validation.js';
|
|
4
|
+
import { ExpandConfig } from './shared-types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Builds OData expand query strings and validation configs.
|
|
7
|
+
* Handles nested expands recursively and transforms relation names to FMTIDs
|
|
8
|
+
* when using entity IDs.
|
|
9
|
+
*/
|
|
10
|
+
export declare class ExpandBuilder {
|
|
11
|
+
private readonly useEntityIds;
|
|
12
|
+
private readonly logger;
|
|
13
|
+
constructor(useEntityIds: boolean, logger: InternalLogger);
|
|
14
|
+
/**
|
|
15
|
+
* Builds OData $expand query string from expand configurations.
|
|
16
|
+
*/
|
|
17
|
+
buildExpandString(configs: ExpandConfig[]): string;
|
|
18
|
+
/**
|
|
19
|
+
* Builds validation configs for expanded navigation properties.
|
|
20
|
+
*/
|
|
21
|
+
buildValidationConfigs(configs: ExpandConfig[]): ExpandValidationConfig[];
|
|
22
|
+
/**
|
|
23
|
+
* Process an expand() call and return the expand config.
|
|
24
|
+
* Used by both QueryBuilder and RecordBuilder to eliminate duplication.
|
|
25
|
+
*
|
|
26
|
+
* @param targetTable - The target table to expand to
|
|
27
|
+
* @param sourceTable - The source table (for validation)
|
|
28
|
+
* @param callback - Optional callback to configure the expand query
|
|
29
|
+
* @param builderFactory - Function that creates a QueryBuilder for the target table
|
|
30
|
+
* @returns ExpandConfig to add to the builder's expandConfigs array
|
|
31
|
+
*/
|
|
32
|
+
processExpand<TargetTable extends FMTable<any, any>, Builder = any>(targetTable: TargetTable, sourceTable: FMTable<any, any> | undefined, callback?: (builder: Builder) => Builder, builderFactory?: () => Builder): ExpandConfig;
|
|
33
|
+
/**
|
|
34
|
+
* Builds a single expand string with its options.
|
|
35
|
+
*/
|
|
36
|
+
private buildSingleExpand;
|
|
37
|
+
/**
|
|
38
|
+
* Resolves relation name, using FMTID if entity IDs are enabled.
|
|
39
|
+
*/
|
|
40
|
+
private resolveRelationName;
|
|
41
|
+
/**
|
|
42
|
+
* Builds expand parts (select, filter, orderBy, etc.) for a single expand.
|
|
43
|
+
*/
|
|
44
|
+
private buildExpandParts;
|
|
45
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
import buildQuery from "odata-query";
|
|
5
|
+
import { getBaseTableConfig, getTableName, getNavigationPaths, FMTable } from "../../orm/table.js";
|
|
6
|
+
import { getDefaultSelectFields } from "./default-select.js";
|
|
7
|
+
import { formatSelectFields } from "./select-utils.js";
|
|
8
|
+
const FILTER_QUERY_REGEX = /\$filter=([^&]+)/;
|
|
9
|
+
class ExpandBuilder {
|
|
10
|
+
constructor(useEntityIds, logger) {
|
|
11
|
+
__publicField(this, "useEntityIds");
|
|
12
|
+
__publicField(this, "logger");
|
|
13
|
+
this.useEntityIds = useEntityIds;
|
|
14
|
+
this.logger = logger;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Builds OData $expand query string from expand configurations.
|
|
18
|
+
*/
|
|
19
|
+
buildExpandString(configs) {
|
|
20
|
+
if (configs.length === 0) {
|
|
21
|
+
return "";
|
|
22
|
+
}
|
|
23
|
+
return configs.map((config) => this.buildSingleExpand(config)).join(",");
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Builds validation configs for expanded navigation properties.
|
|
27
|
+
*/
|
|
28
|
+
buildValidationConfigs(configs) {
|
|
29
|
+
return configs.map((config) => {
|
|
30
|
+
var _a;
|
|
31
|
+
const targetTable = config.targetTable;
|
|
32
|
+
let targetSchema;
|
|
33
|
+
if (targetTable) {
|
|
34
|
+
const baseTableConfig = getBaseTableConfig(targetTable);
|
|
35
|
+
const containerFields = baseTableConfig.containerFields || [];
|
|
36
|
+
const schema = { ...baseTableConfig.schema };
|
|
37
|
+
for (const containerField of containerFields) {
|
|
38
|
+
delete schema[containerField];
|
|
39
|
+
}
|
|
40
|
+
targetSchema = schema;
|
|
41
|
+
}
|
|
42
|
+
let selectedFields;
|
|
43
|
+
if ((_a = config.options) == null ? void 0 : _a.select) {
|
|
44
|
+
selectedFields = Array.isArray(config.options.select) ? config.options.select.map(String) : [String(config.options.select)];
|
|
45
|
+
}
|
|
46
|
+
const nestedExpands = config.nestedExpandConfigs ? this.buildValidationConfigs(config.nestedExpandConfigs) : void 0;
|
|
47
|
+
return {
|
|
48
|
+
relation: config.relation,
|
|
49
|
+
targetSchema,
|
|
50
|
+
targetTable,
|
|
51
|
+
table: targetTable,
|
|
52
|
+
selectedFields,
|
|
53
|
+
nestedExpands
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Process an expand() call and return the expand config.
|
|
59
|
+
* Used by both QueryBuilder and RecordBuilder to eliminate duplication.
|
|
60
|
+
*
|
|
61
|
+
* @param targetTable - The target table to expand to
|
|
62
|
+
* @param sourceTable - The source table (for validation)
|
|
63
|
+
* @param callback - Optional callback to configure the expand query
|
|
64
|
+
* @param builderFactory - Function that creates a QueryBuilder for the target table
|
|
65
|
+
* @returns ExpandConfig to add to the builder's expandConfigs array
|
|
66
|
+
*/
|
|
67
|
+
// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration, generic Builder type
|
|
68
|
+
processExpand(targetTable, sourceTable, callback, builderFactory) {
|
|
69
|
+
const relationName = getTableName(targetTable);
|
|
70
|
+
if (sourceTable) {
|
|
71
|
+
const navigationPaths = getNavigationPaths(sourceTable);
|
|
72
|
+
if (navigationPaths && !navigationPaths.includes(relationName)) {
|
|
73
|
+
this.logger.warn(
|
|
74
|
+
`Cannot expand to "${relationName}". Valid navigation paths: ${navigationPaths.length > 0 ? navigationPaths.join(", ") : "none"}`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (callback && builderFactory) {
|
|
79
|
+
const targetBuilder = builderFactory();
|
|
80
|
+
const configuredBuilder = callback(targetBuilder);
|
|
81
|
+
const expandOptions = {
|
|
82
|
+
// biome-ignore lint/suspicious/noExplicitAny: Type assertion for internal builder property access
|
|
83
|
+
...configuredBuilder.queryOptions
|
|
84
|
+
};
|
|
85
|
+
if (!expandOptions.select) {
|
|
86
|
+
const defaultFields2 = getDefaultSelectFields(targetTable);
|
|
87
|
+
if (defaultFields2) {
|
|
88
|
+
expandOptions.select = defaultFields2;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const nestedExpandConfigs = configuredBuilder.expandConfigs;
|
|
92
|
+
if ((nestedExpandConfigs == null ? void 0 : nestedExpandConfigs.length) > 0) {
|
|
93
|
+
const nestedExpandString = this.buildExpandString(nestedExpandConfigs);
|
|
94
|
+
if (nestedExpandString) {
|
|
95
|
+
expandOptions.expand = nestedExpandString;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
relation: relationName,
|
|
100
|
+
options: expandOptions,
|
|
101
|
+
targetTable,
|
|
102
|
+
nestedExpandConfigs: (nestedExpandConfigs == null ? void 0 : nestedExpandConfigs.length) > 0 ? nestedExpandConfigs : void 0
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const defaultFields = getDefaultSelectFields(targetTable);
|
|
106
|
+
if (defaultFields) {
|
|
107
|
+
return {
|
|
108
|
+
relation: relationName,
|
|
109
|
+
options: { select: defaultFields },
|
|
110
|
+
targetTable
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
relation: relationName,
|
|
115
|
+
targetTable
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Builds a single expand string with its options.
|
|
120
|
+
*/
|
|
121
|
+
buildSingleExpand(config) {
|
|
122
|
+
const relationName = this.resolveRelationName(config);
|
|
123
|
+
const parts = this.buildExpandParts(config);
|
|
124
|
+
if (parts.length === 0) {
|
|
125
|
+
return relationName;
|
|
126
|
+
}
|
|
127
|
+
return `${relationName}(${parts.join(";")})`;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Resolves relation name, using FMTID if entity IDs are enabled.
|
|
131
|
+
*/
|
|
132
|
+
resolveRelationName(config) {
|
|
133
|
+
if (!this.useEntityIds) {
|
|
134
|
+
return config.relation;
|
|
135
|
+
}
|
|
136
|
+
const targetTable = config.targetTable;
|
|
137
|
+
if (targetTable && FMTable.Symbol.EntityId in targetTable) {
|
|
138
|
+
const tableId = targetTable[FMTable.Symbol.EntityId];
|
|
139
|
+
if (tableId) {
|
|
140
|
+
return tableId;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return config.relation;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Builds expand parts (select, filter, orderBy, etc.) for a single expand.
|
|
147
|
+
*/
|
|
148
|
+
buildExpandParts(config) {
|
|
149
|
+
if (!config.options || Object.keys(config.options).length === 0) {
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
const parts = [];
|
|
153
|
+
const opts = config.options;
|
|
154
|
+
if (opts.select) {
|
|
155
|
+
const selectArray = Array.isArray(opts.select) ? opts.select.map(String) : [String(opts.select)];
|
|
156
|
+
const selectFields = formatSelectFields(selectArray, config.targetTable, this.useEntityIds);
|
|
157
|
+
parts.push(`$select=${selectFields}`);
|
|
158
|
+
}
|
|
159
|
+
if (opts.filter) {
|
|
160
|
+
const filterQuery = buildQuery({ filter: opts.filter });
|
|
161
|
+
const match = filterQuery.match(FILTER_QUERY_REGEX);
|
|
162
|
+
if (match) {
|
|
163
|
+
parts.push(`$filter=${match[1]}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (opts.orderBy) {
|
|
167
|
+
const orderByValue = Array.isArray(opts.orderBy) ? opts.orderBy.join(",") : String(opts.orderBy);
|
|
168
|
+
parts.push(`$orderby=${orderByValue}`);
|
|
169
|
+
}
|
|
170
|
+
if (opts.top !== void 0) {
|
|
171
|
+
parts.push(`$top=${opts.top}`);
|
|
172
|
+
}
|
|
173
|
+
if (opts.skip !== void 0) {
|
|
174
|
+
parts.push(`$skip=${opts.skip}`);
|
|
175
|
+
}
|
|
176
|
+
if (opts.expand && typeof opts.expand === "string") {
|
|
177
|
+
parts.push(`$expand=${opts.expand}`);
|
|
178
|
+
}
|
|
179
|
+
return parts;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
export {
|
|
183
|
+
ExpandBuilder
|
|
184
|
+
};
|
|
185
|
+
//# sourceMappingURL=expand-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expand-builder.js","sources":["../../../../src/client/builders/expand-builder.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport buildQuery, { type QueryOptions } from \"odata-query\";\nimport type { InternalLogger } from \"../../logger\";\nimport { FMTable, getBaseTableConfig, getNavigationPaths, getTableName } from \"../../orm/table\";\nimport type { ExpandValidationConfig } from \"../../validation\";\nimport { getDefaultSelectFields } from \"./default-select\";\nimport { formatSelectFields } from \"./select-utils\";\nimport type { ExpandConfig } from \"./shared-types\";\n\nconst FILTER_QUERY_REGEX = /\\$filter=([^&]+)/;\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 private readonly useEntityIds: boolean;\n private readonly logger: InternalLogger;\n\n constructor(useEntityIds: boolean, logger: InternalLogger) {\n this.useEntityIds = useEntityIds;\n this.logger = logger;\n }\n\n /**\n * Builds OData $expand query string from expand configurations.\n */\n buildExpandString(configs: ExpandConfig[]): string {\n if (configs.length === 0) {\n return \"\";\n }\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: Partial<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 let selectedFields: string[] | undefined;\n if (config.options?.select) {\n selectedFields = Array.isArray(config.options.select)\n ? config.options.select.map(String)\n : [String(config.options.select)];\n }\n\n // Recursively build validation configs for nested expands\n const nestedExpands = config.nestedExpandConfigs\n ? this.buildValidationConfigs(config.nestedExpandConfigs)\n : undefined;\n\n return {\n relation: config.relation,\n targetSchema,\n targetTable,\n table: targetTable,\n selectedFields,\n nestedExpands,\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 // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration, generic Builder type\n processExpand<TargetTable extends FMTable<any, any>, Builder = any>(\n targetTable: TargetTable,\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\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 // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any QueryOptions configuration\n const expandOptions: Partial<QueryOptions<any>> = {\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for internal builder property access\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 // biome-ignore lint/suspicious/noExplicitAny: Type assertion for internal builder property access\n const nestedExpandConfigs = (configuredBuilder as any).expandConfigs;\n if (nestedExpandConfigs?.length > 0) {\n // Build nested expand string from the configured builder's expand configs\n const nestedExpandString = this.buildExpandString(nestedExpandConfigs);\n if (nestedExpandString) {\n // Add nested expand to options\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for expand string\n expandOptions.expand = nestedExpandString as any;\n }\n }\n\n return {\n relation: relationName,\n options: expandOptions,\n targetTable,\n nestedExpandConfigs: nestedExpandConfigs?.length > 0 ? nestedExpandConfigs : undefined,\n };\n }\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 }\n return {\n relation: relationName,\n targetTable,\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 // biome-ignore lint/suspicious/noExplicitAny: Type assertion for Symbol property access\n const tableId = (targetTable as any)[FMTable.Symbol.EntityId] as `FMTID:${string}` | 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) ? opts.select.map(String) : [String(opts.select)];\n const selectFields = formatSelectFields(selectArray, config.targetTable, this.useEntityIds);\n parts.push(`$select=${selectFields}`);\n }\n\n if (opts.filter) {\n const filterQuery = buildQuery({ filter: opts.filter });\n const match = filterQuery.match(FILTER_QUERY_REGEX);\n if (match) {\n parts.push(`$filter=${match[1]}`);\n }\n }\n\n if (opts.orderBy) {\n const orderByValue = Array.isArray(opts.orderBy) ? opts.orderBy.join(\",\") : String(opts.orderBy);\n parts.push(`$orderby=${orderByValue}`);\n }\n\n if (opts.top !== undefined) {\n parts.push(`$top=${opts.top}`);\n }\n if (opts.skip !== undefined) {\n parts.push(`$skip=${opts.skip}`);\n }\n\n if (opts.expand && typeof opts.expand === \"string\") {\n parts.push(`$expand=${opts.expand}`);\n }\n\n return parts;\n }\n}\n"],"names":["defaultFields"],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** biome-ignore-all lint/performance/noBarrelFile: Re-exporting all builder utilities */
|
|
2
|
+
export * from './default-select.js';
|
|
3
|
+
export * from './expand-builder.js';
|
|
4
|
+
export * from './query-string-builder.js';
|
|
5
|
+
export * from './response-processor.js';
|
|
6
|
+
export * from './select-mixin.js';
|
|
7
|
+
export * from './select-utils.js';
|
|
8
|
+
export * from './shared-types.js';
|
|
9
|
+
export * from './table-utils.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { InternalLogger } from '../../logger.js';
|
|
2
|
+
import { FMTable } from '../../orm/table.js';
|
|
3
|
+
import { ExpandConfig } from './shared-types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Builds OData query string for $select and $expand parameters.
|
|
6
|
+
* Used by both QueryBuilder and RecordBuilder to eliminate duplication.
|
|
7
|
+
*
|
|
8
|
+
* @param config - Configuration object
|
|
9
|
+
* @returns Query string starting with ? or empty string if no parameters
|
|
10
|
+
*/
|
|
11
|
+
export declare function buildSelectExpandQueryString(config: {
|
|
12
|
+
selectedFields?: string[];
|
|
13
|
+
expandConfigs: ExpandConfig[];
|
|
14
|
+
table?: FMTable<any, any>;
|
|
15
|
+
useEntityIds: boolean;
|
|
16
|
+
logger: InternalLogger;
|
|
17
|
+
includeSpecialColumns?: boolean;
|
|
18
|
+
}): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ExpandBuilder } from "./expand-builder.js";
|
|
2
|
+
import { formatSelectFields } from "./select-utils.js";
|
|
3
|
+
function buildSelectExpandQueryString(config) {
|
|
4
|
+
const parts = [];
|
|
5
|
+
const expandBuilder = new ExpandBuilder(config.useEntityIds, config.logger);
|
|
6
|
+
if (config.selectedFields && config.selectedFields.length > 0) {
|
|
7
|
+
const selectString = formatSelectFields(config.selectedFields, config.table, config.useEntityIds);
|
|
8
|
+
if (selectString) {
|
|
9
|
+
parts.push(`$select=${selectString}`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const expandString = expandBuilder.buildExpandString(config.expandConfigs);
|
|
13
|
+
if (expandString) {
|
|
14
|
+
parts.push(`$expand=${expandString}`);
|
|
15
|
+
}
|
|
16
|
+
return parts.length > 0 ? `?${parts.join("&")}` : "";
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
buildSelectExpandQueryString
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=query-string-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-string-builder.js","sources":["../../../../src/client/builders/query-string-builder.ts"],"sourcesContent":["import type { InternalLogger } from \"../../logger\";\nimport type { FMTable } from \"../../orm/table\";\nimport { ExpandBuilder } from \"./expand-builder\";\nimport { formatSelectFields } from \"./select-utils\";\nimport type { ExpandConfig } from \"./shared-types\";\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 // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n table?: FMTable<any, any>;\n useEntityIds: boolean;\n logger: InternalLogger;\n includeSpecialColumns?: boolean;\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 // Important: do NOT implicitly add system columns (ROWID/ROWMODID) here.\n // - `includeSpecialColumns` controls the Prefer header + response parsing, but should not\n // mutate/expand an explicit `$select` (e.g. when the user calls `.select({ ... })`).\n // - If system columns are desired with `.select()`, they must be explicitly included via\n // the `systemColumns` argument, which will already have added them to `selectedFields`.\n const selectString = formatSelectFields(config.selectedFields, config.table, config.useEntityIds);\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,QAQlC;AACT,QAAM,QAAkB,CAAA;AACxB,QAAM,gBAAgB,IAAI,cAAc,OAAO,cAAc,OAAO,MAAM;AAG1E,MAAI,OAAO,kBAAkB,OAAO,eAAe,SAAS,GAAG;AAM7D,UAAM,eAAe,mBAAmB,OAAO,gBAAgB,OAAO,OAAO,OAAO,YAAY;AAChG,QAAI,cAAc;AAChB,YAAM,KAAK,WAAW,YAAY,EAAE;AAAA,IACtC;AAAA,EACF;AAGA,QAAM,eAAe,cAAc,kBAAkB,OAAO,aAAa;AACzE,MAAI,cAAc;AAChB,UAAM,KAAK,WAAW,YAAY,EAAE;AAAA,EACtC;AAEA,SAAO,MAAM,SAAS,IAAI,IAAI,MAAM,KAAK,GAAG,CAAC,KAAK;AACpD;"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { InternalLogger } from '../../logger.js';
|
|
2
|
+
import { FMTable } from '../../orm/table.js';
|
|
3
|
+
import { Result } from '../../types.js';
|
|
4
|
+
import { ExpandValidationConfig } from '../../validation.js';
|
|
5
|
+
import { ExpandConfig } from './shared-types.js';
|
|
6
|
+
export interface ProcessResponseConfig {
|
|
7
|
+
table?: FMTable<any, any>;
|
|
8
|
+
schema?: Record<string, any>;
|
|
9
|
+
singleMode: "exact" | "maybe" | false;
|
|
10
|
+
selectedFields?: string[];
|
|
11
|
+
expandValidationConfigs?: ExpandValidationConfig[];
|
|
12
|
+
skipValidation?: boolean;
|
|
13
|
+
useEntityIds?: boolean;
|
|
14
|
+
includeSpecialColumns?: boolean;
|
|
15
|
+
fieldMapping?: Record<string, string>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Processes OData response with transformation and validation.
|
|
19
|
+
* Shared by QueryBuilder and RecordBuilder.
|
|
20
|
+
*/
|
|
21
|
+
export declare function processODataResponse<T>(rawResponse: any, config: ProcessResponseConfig): Promise<Result<T>>;
|
|
22
|
+
/**
|
|
23
|
+
* Gets schema from a table occurrence, excluding container fields.
|
|
24
|
+
* Container fields are never returned in regular responses (only via getSingleField).
|
|
25
|
+
*/
|
|
26
|
+
export declare function getSchemaFromTable(table: FMTable<any, any> | undefined): Record<string, any> | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Processes query response with expand configs.
|
|
29
|
+
* This is a convenience wrapper that builds validation configs from expand configs.
|
|
30
|
+
*/
|
|
31
|
+
export declare function processQueryResponse<T>(response: any, config: {
|
|
32
|
+
occurrence?: FMTable<any, any>;
|
|
33
|
+
singleMode: "exact" | "maybe" | false;
|
|
34
|
+
queryOptions: {
|
|
35
|
+
select?: (keyof T)[] | string[];
|
|
36
|
+
};
|
|
37
|
+
expandConfigs: ExpandConfig[];
|
|
38
|
+
skipValidation?: boolean;
|
|
39
|
+
useEntityIds?: boolean;
|
|
40
|
+
includeSpecialColumns?: boolean;
|
|
41
|
+
fieldMapping?: Record<string, string>;
|
|
42
|
+
logger: InternalLogger;
|
|
43
|
+
}): Promise<Result<any>>;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { RecordCountMismatchError } from "../../errors.js";
|
|
2
|
+
import { getBaseTableConfig } from "../../orm/table.js";
|
|
3
|
+
import { transformResponseFields } from "../../transform.js";
|
|
4
|
+
import { validateSingleResponse, validateListResponse } from "../../validation.js";
|
|
5
|
+
import { ExpandBuilder } from "./expand-builder.js";
|
|
6
|
+
async function processODataResponse(rawResponse, config) {
|
|
7
|
+
const {
|
|
8
|
+
table,
|
|
9
|
+
schema,
|
|
10
|
+
singleMode,
|
|
11
|
+
selectedFields,
|
|
12
|
+
expandValidationConfigs,
|
|
13
|
+
skipValidation,
|
|
14
|
+
useEntityIds,
|
|
15
|
+
includeSpecialColumns,
|
|
16
|
+
fieldMapping
|
|
17
|
+
} = config;
|
|
18
|
+
let response = rawResponse;
|
|
19
|
+
if (table && useEntityIds) {
|
|
20
|
+
response = transformResponseFields(response, table, expandValidationConfigs);
|
|
21
|
+
}
|
|
22
|
+
if (skipValidation) {
|
|
23
|
+
const result = extractRecords(response, singleMode);
|
|
24
|
+
if (result.data && fieldMapping && Object.keys(fieldMapping).length > 0) {
|
|
25
|
+
if (result.error) {
|
|
26
|
+
return { data: void 0, error: result.error };
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
data: renameFieldsInResponse(result.data, fieldMapping),
|
|
30
|
+
error: void 0
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
if (singleMode !== false) {
|
|
36
|
+
const validation2 = await validateSingleResponse(
|
|
37
|
+
response,
|
|
38
|
+
schema,
|
|
39
|
+
// biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic type parameter
|
|
40
|
+
selectedFields,
|
|
41
|
+
expandValidationConfigs,
|
|
42
|
+
singleMode,
|
|
43
|
+
includeSpecialColumns
|
|
44
|
+
);
|
|
45
|
+
if (!validation2.valid) {
|
|
46
|
+
return { data: void 0, error: validation2.error };
|
|
47
|
+
}
|
|
48
|
+
if (fieldMapping && Object.keys(fieldMapping).length > 0) {
|
|
49
|
+
return {
|
|
50
|
+
data: renameFieldsInResponse(validation2.data, fieldMapping),
|
|
51
|
+
error: void 0
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return { data: validation2.data, error: void 0 };
|
|
55
|
+
}
|
|
56
|
+
const validation = await validateListResponse(
|
|
57
|
+
response,
|
|
58
|
+
schema,
|
|
59
|
+
// biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic type parameter
|
|
60
|
+
selectedFields,
|
|
61
|
+
expandValidationConfigs,
|
|
62
|
+
includeSpecialColumns
|
|
63
|
+
);
|
|
64
|
+
if (!validation.valid) {
|
|
65
|
+
return { data: void 0, error: validation.error };
|
|
66
|
+
}
|
|
67
|
+
if (fieldMapping && Object.keys(fieldMapping).length > 0) {
|
|
68
|
+
return {
|
|
69
|
+
data: renameFieldsInResponse(validation.data, fieldMapping),
|
|
70
|
+
error: void 0
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return { data: validation.data, error: void 0 };
|
|
74
|
+
}
|
|
75
|
+
function extractRecords(response, singleMode) {
|
|
76
|
+
if (singleMode === false) {
|
|
77
|
+
const records2 = response.value ?? [];
|
|
78
|
+
return { data: records2, error: void 0 };
|
|
79
|
+
}
|
|
80
|
+
const records = response.value ?? [response];
|
|
81
|
+
const count = Array.isArray(records) ? records.length : 1;
|
|
82
|
+
if (count > 1) {
|
|
83
|
+
return {
|
|
84
|
+
data: void 0,
|
|
85
|
+
error: new RecordCountMismatchError(singleMode === "exact" ? "one" : "at-most-one", count)
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (count === 0) {
|
|
89
|
+
if (singleMode === "exact") {
|
|
90
|
+
return { data: void 0, error: new RecordCountMismatchError("one", 0) };
|
|
91
|
+
}
|
|
92
|
+
return { data: null, error: void 0 };
|
|
93
|
+
}
|
|
94
|
+
const record = Array.isArray(records) ? records[0] : records;
|
|
95
|
+
return { data: record, error: void 0 };
|
|
96
|
+
}
|
|
97
|
+
function getSchemaFromTable(table) {
|
|
98
|
+
if (!table) {
|
|
99
|
+
return void 0;
|
|
100
|
+
}
|
|
101
|
+
const baseTableConfig = getBaseTableConfig(table);
|
|
102
|
+
const containerFields = baseTableConfig.containerFields || [];
|
|
103
|
+
const schema = { ...baseTableConfig.schema };
|
|
104
|
+
for (const containerField of containerFields) {
|
|
105
|
+
delete schema[containerField];
|
|
106
|
+
}
|
|
107
|
+
return schema;
|
|
108
|
+
}
|
|
109
|
+
function renameFieldsInResponse(data, fieldMapping) {
|
|
110
|
+
if (!data || typeof data !== "object") {
|
|
111
|
+
return data;
|
|
112
|
+
}
|
|
113
|
+
if (Array.isArray(data)) {
|
|
114
|
+
return data.map((item) => renameFieldsInResponse(item, fieldMapping));
|
|
115
|
+
}
|
|
116
|
+
if ("value" in data && Array.isArray(data.value)) {
|
|
117
|
+
return {
|
|
118
|
+
...data,
|
|
119
|
+
// biome-ignore lint/suspicious/noExplicitAny: Dynamic record transformation
|
|
120
|
+
value: data.value.map((item) => renameFieldsInResponse(item, fieldMapping))
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const renamed = {};
|
|
124
|
+
for (const [key, value] of Object.entries(data)) {
|
|
125
|
+
const outputKey = fieldMapping[key];
|
|
126
|
+
if (outputKey) {
|
|
127
|
+
renamed[outputKey] = value;
|
|
128
|
+
} else {
|
|
129
|
+
renamed[key] = value;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return renamed;
|
|
133
|
+
}
|
|
134
|
+
async function processQueryResponse(response, config) {
|
|
135
|
+
const {
|
|
136
|
+
occurrence,
|
|
137
|
+
singleMode,
|
|
138
|
+
queryOptions,
|
|
139
|
+
expandConfigs,
|
|
140
|
+
skipValidation,
|
|
141
|
+
useEntityIds,
|
|
142
|
+
includeSpecialColumns,
|
|
143
|
+
fieldMapping,
|
|
144
|
+
logger
|
|
145
|
+
} = config;
|
|
146
|
+
const expandBuilder = new ExpandBuilder(useEntityIds ?? false, logger);
|
|
147
|
+
const expandValidationConfigs = expandBuilder.buildValidationConfigs(expandConfigs);
|
|
148
|
+
let selectedFields;
|
|
149
|
+
if (queryOptions.select) {
|
|
150
|
+
selectedFields = Array.isArray(queryOptions.select) ? queryOptions.select.map(String) : [String(queryOptions.select)];
|
|
151
|
+
}
|
|
152
|
+
let processedResponse = await processODataResponse(response, {
|
|
153
|
+
table: occurrence,
|
|
154
|
+
schema: getSchemaFromTable(occurrence),
|
|
155
|
+
singleMode,
|
|
156
|
+
selectedFields,
|
|
157
|
+
expandValidationConfigs,
|
|
158
|
+
skipValidation,
|
|
159
|
+
useEntityIds,
|
|
160
|
+
includeSpecialColumns
|
|
161
|
+
});
|
|
162
|
+
if (processedResponse.data && fieldMapping && Object.keys(fieldMapping).length > 0) {
|
|
163
|
+
processedResponse = {
|
|
164
|
+
...processedResponse,
|
|
165
|
+
data: renameFieldsInResponse(processedResponse.data, fieldMapping)
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
return processedResponse;
|
|
169
|
+
}
|
|
170
|
+
export {
|
|
171
|
+
getSchemaFromTable,
|
|
172
|
+
processODataResponse,
|
|
173
|
+
processQueryResponse
|
|
174
|
+
};
|
|
175
|
+
//# sourceMappingURL=response-processor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response-processor.js","sources":["../../../../src/client/builders/response-processor.ts"],"sourcesContent":["import { RecordCountMismatchError } from \"../../errors\";\nimport type { InternalLogger } from \"../../logger\";\nimport type { FMTable } from \"../../orm/table\";\nimport { getBaseTableConfig } from \"../../orm/table\";\nimport { transformResponseFields } from \"../../transform\";\nimport type { Result } from \"../../types\";\nimport type { ExpandValidationConfig } from \"../../validation\";\nimport { validateListResponse, validateSingleResponse } from \"../../validation\";\nimport { ExpandBuilder } from \"./expand-builder\";\nimport type { ExpandConfig } from \"./shared-types\";\n\nexport interface ProcessResponseConfig {\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n table?: FMTable<any, any>;\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic schema shape from table configuration\n schema?: Record<string, any>;\n singleMode: \"exact\" | \"maybe\" | false;\n selectedFields?: string[];\n expandValidationConfigs?: ExpandValidationConfig[];\n skipValidation?: boolean;\n useEntityIds?: boolean;\n includeSpecialColumns?: 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 */\n// biome-ignore lint/suspicious/noExplicitAny: Dynamic response type from OData API\nexport async function processODataResponse<T>(rawResponse: any, config: ProcessResponseConfig): Promise<Result<T>> {\n const {\n table,\n schema,\n singleMode,\n selectedFields,\n expandValidationConfigs,\n skipValidation,\n useEntityIds,\n includeSpecialColumns,\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(response, table, expandValidationConfigs);\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 // Note: Special columns are excluded when using QueryBuilder.single() method,\n // but included for RecordBuilder.get() method (both use singleMode: \"exact\")\n // The exclusion is handled in QueryBuilder's processQueryResponse, not here\n if (singleMode !== false) {\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape\n const validation = await validateSingleResponse<any>(\n response,\n schema,\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic type parameter\n selectedFields as any,\n expandValidationConfigs,\n singleMode,\n includeSpecialColumns,\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 // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape\n const validation = await validateListResponse<any>(\n response,\n schema,\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic type parameter\n selectedFields as any,\n expandValidationConfigs,\n includeSpecialColumns,\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 */\n// biome-ignore lint/suspicious/noExplicitAny: Dynamic response type from OData API\nfunction extractRecords<T>(response: any, singleMode: \"exact\" | \"maybe\" | false): 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(singleMode === \"exact\" ? \"one\" : \"at-most-one\", count),\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 */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration, dynamic schema shape\nexport function getSchemaFromTable(table: FMTable<any, any> | undefined): Record<string, any> | undefined {\n if (!table) {\n return undefined;\n }\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 */\n// biome-ignore lint/suspicious/noExplicitAny: Dynamic response data transformation\nfunction renameFieldsInResponse(data: any, fieldMapping: Record<string, string>): 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 // biome-ignore lint/suspicious/noExplicitAny: Dynamic record transformation\n value: data.value.map((item: any) => renameFieldsInResponse(item, fieldMapping)),\n };\n }\n\n // Handle single record\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic field transformation\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 // biome-ignore lint/suspicious/noExplicitAny: Dynamic response type from OData API\n response: any,\n config: {\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\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 includeSpecialColumns?: boolean;\n // Mapping from field names to output keys (for renamed fields in select)\n fieldMapping?: Record<string, string>;\n logger: InternalLogger;\n },\n // biome-ignore lint/suspicious/noExplicitAny: Generic return type for interface compliance\n): Promise<Result<any>> {\n const {\n occurrence,\n singleMode,\n queryOptions,\n expandConfigs,\n skipValidation,\n useEntityIds,\n includeSpecialColumns,\n fieldMapping,\n logger,\n } = config;\n\n const expandBuilder = new ExpandBuilder(useEntityIds ?? false, logger);\n const expandValidationConfigs = expandBuilder.buildValidationConfigs(expandConfigs);\n\n let selectedFields: string[] | undefined;\n if (queryOptions.select) {\n selectedFields = Array.isArray(queryOptions.select)\n ? queryOptions.select.map(String)\n : [String(queryOptions.select)];\n }\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 includeSpecialColumns,\n });\n\n // Rename fields if field mapping is provided (for renamed fields in select)\n if (processedResponse.data && fieldMapping && Object.keys(fieldMapping).length > 0) {\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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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { InternalLogger } from '../../logger.js';
|
|
2
|
+
import { Column } from '../../orm/column.js';
|
|
3
|
+
/**
|
|
4
|
+
* Utility function for processing select() calls.
|
|
5
|
+
* Used by both QueryBuilder and RecordBuilder to eliminate duplication.
|
|
6
|
+
*
|
|
7
|
+
* @param fields - Field names or Column references
|
|
8
|
+
* @returns Object with selectedFields array
|
|
9
|
+
*/
|
|
10
|
+
export declare function processSelectFields(...fields: (string | Column<any, any, string>)[]): {
|
|
11
|
+
selectedFields: string[];
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Processes select() calls with field renaming support.
|
|
15
|
+
* Validates columns belong to the correct table and builds field mapping for renamed fields.
|
|
16
|
+
* Used by both QueryBuilder and RecordBuilder to eliminate duplication.
|
|
17
|
+
*
|
|
18
|
+
* @param fields - Object mapping output keys to column references
|
|
19
|
+
* @param tableName - Expected table name for validation
|
|
20
|
+
* @returns Object with selectedFields array and fieldMapping for renamed fields
|
|
21
|
+
*/
|
|
22
|
+
export declare function processSelectWithRenames<TTableName extends string>(fields: Record<string, Column<any, any, TTableName>>, tableName: string, logger: InternalLogger): {
|
|
23
|
+
selectedFields: string[];
|
|
24
|
+
fieldMapping: Record<string, string>;
|
|
25
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { isColumn } from "../../orm/column.js";
|
|
2
|
+
function processSelectWithRenames(fields, tableName, logger) {
|
|
3
|
+
const selectedFields = [];
|
|
4
|
+
const fieldMapping = {};
|
|
5
|
+
for (const [outputKey, column] of Object.entries(fields)) {
|
|
6
|
+
if (!isColumn(column)) {
|
|
7
|
+
throw new Error(`select() expects column references, but got: ${typeof column}`);
|
|
8
|
+
}
|
|
9
|
+
if (column.tableName !== tableName) {
|
|
10
|
+
logger.warn(
|
|
11
|
+
`Column ${column.toString()} is from table "${column.tableName}", but query is for table "${tableName}"`
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
const fieldName = column.fieldName;
|
|
15
|
+
selectedFields.push(fieldName);
|
|
16
|
+
if (fieldName !== outputKey) {
|
|
17
|
+
fieldMapping[fieldName] = outputKey;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
selectedFields,
|
|
22
|
+
fieldMapping: Object.keys(fieldMapping).length > 0 ? fieldMapping : {}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
processSelectWithRenames
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=select-mixin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select-mixin.js","sources":["../../../../src/client/builders/select-mixin.ts"],"sourcesContent":["import type { InternalLogger } from \"../../logger\";\nimport { type Column, isColumn } 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 */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\nexport function processSelectFields(...fields: (string | Column<any, any, string>)[]): { 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 // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\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(`select() expects column references, but got: ${typeof column}`);\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"],"names":[],"mappings":";AA8BO,SAAS,yBAEd,QACA,WACA,QACoE;AACpE,QAAM,iBAA2B,CAAA;AACjC,QAAM,eAAuC,CAAA;AAE7C,aAAW,CAAC,WAAW,MAAM,KAAK,OAAO,QAAQ,MAAM,GAAG;AACxD,QAAI,CAAC,SAAS,MAAM,GAAG;AACrB,YAAM,IAAI,MAAM,gDAAgD,OAAO,MAAM,EAAE;AAAA,IACjF;AAGA,QAAI,OAAO,cAAc,WAAW;AAClC,aAAO;AAAA,QACL,UAAU,OAAO,UAAU,mBAAmB,OAAO,SAAS,8BAA8B,SAAS;AAAA,MAAA;AAAA,IAEzG;AAEA,UAAM,YAAY,OAAO;AACzB,mBAAe,KAAK,SAAS;AAG7B,QAAI,cAAc,WAAW;AAC3B,mBAAa,SAAS,IAAI;AAAA,IAC5B;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,cAAc,OAAO,KAAK,YAAY,EAAE,SAAS,IAAI,eAAe,CAAA;AAAA,EAAC;AAEzE;"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { FMTable } from '../../orm/table.js';
|
|
2
|
+
/**
|
|
3
|
+
* Determines if a field name needs to be quoted in OData queries.
|
|
4
|
+
* Per FileMaker docs: field names with special characters (spaces, underscores, etc.) must be quoted.
|
|
5
|
+
* Also quotes "id" as it's an OData reserved word.
|
|
6
|
+
* Entity IDs (FMFID:*, FMTID:*) are not quoted as they're identifiers, not field names.
|
|
7
|
+
*
|
|
8
|
+
* @param fieldName - The field name or identifier to check
|
|
9
|
+
* @returns true if the field name should be quoted in OData queries
|
|
10
|
+
*/
|
|
11
|
+
export declare function needsFieldQuoting(fieldName: string): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Formats select fields for use in OData query strings.
|
|
14
|
+
* - Transforms field names to FMFIDs if using entity IDs
|
|
15
|
+
* - Wraps "id" fields in double quotes (OData reserved)
|
|
16
|
+
* - URL-encodes special characters but preserves spaces
|
|
17
|
+
*/
|
|
18
|
+
export declare function formatSelectFields(select: string[] | readonly string[] | undefined, table?: FMTable<any, any>, useEntityIds?: boolean): string;
|