@proofkit/fmodata
Version:
FileMaker OData API client
197 lines (196 loc) • 7.22 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
import buildQuery from "odata-query";
import { getBaseTableConfig, getTableName, getNavigationPaths, FMTable } from "../../orm/table.js";
import { getDefaultSelectFields } from "./default-select.js";
import { formatSelectFields } from "./select-utils.js";
const FILTER_QUERY_REGEX = /\$filter=([^&]+)/;
class ExpandBuilder {
constructor(useEntityIds, logger) {
__publicField(this, "useEntityIds");
__publicField(this, "logger");
this.useEntityIds = useEntityIds;
this.logger = logger;
}
/**
* Builds OData $expand query string from expand configurations.
*/
buildExpandString(configs) {
if (configs.length === 0) {
return "";
}
return configs.map((config) => this.buildSingleExpand(config)).join(",");
}
/**
* Builds validation configs for expanded navigation properties.
*/
buildValidationConfigs(configs) {
return configs.map((config) => {
var _a;
const targetTable = config.targetTable;
let targetSchema;
if (targetTable) {
const baseTableConfig = getBaseTableConfig(targetTable);
const containerFields = baseTableConfig.containerFields || [];
const schema = { ...baseTableConfig.schema };
for (const containerField of containerFields) {
delete schema[containerField];
}
targetSchema = schema;
}
let selectedFields;
if ((_a = config.options) == null ? void 0 : _a.select) {
selectedFields = Array.isArray(config.options.select) ? config.options.select.map(String) : [String(config.options.select)];
}
const nestedExpands = config.nestedExpandConfigs ? this.buildValidationConfigs(config.nestedExpandConfigs) : void 0;
return {
relation: config.relation,
targetSchema,
targetTable,
table: targetTable,
selectedFields,
nestedExpands
};
});
}
/**
* Process an expand() call and return the expand config.
* Used by both QueryBuilder and RecordBuilder to eliminate duplication.
*
* @param targetTable - The target table to expand to
* @param sourceTable - The source table (for validation)
* @param callback - Optional callback to configure the expand query
* @param builderFactory - Function that creates a QueryBuilder for the target table
* @returns ExpandConfig to add to the builder's expandConfigs array
*/
// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration, generic Builder type
processExpand(targetTable, sourceTable, callback, builderFactory) {
var _a;
const relationName = getTableName(targetTable);
if (sourceTable) {
const navigationPaths = getNavigationPaths(sourceTable);
if (navigationPaths && !navigationPaths.includes(relationName)) {
this.logger.warn(
`Cannot expand to "${relationName}". Valid navigation paths: ${navigationPaths.length > 0 ? navigationPaths.join(", ") : "none"}`
);
}
}
if (callback && builderFactory) {
const targetBuilder = builderFactory();
const configuredBuilder = callback(targetBuilder);
const expandOptions = {
// biome-ignore lint/suspicious/noExplicitAny: Type assertion for internal builder property access
...configuredBuilder.queryOptions
};
const filterExpression = (_a = configuredBuilder.readState) == null ? void 0 : _a.filterExpression;
if (filterExpression && !expandOptions.filter && typeof filterExpression.toODataFilter === "function") {
expandOptions.filter = filterExpression.toODataFilter(this.useEntityIds);
}
if (!expandOptions.select) {
const defaultFields2 = getDefaultSelectFields(targetTable);
if (defaultFields2) {
expandOptions.select = defaultFields2;
}
}
const nestedExpandConfigs = configuredBuilder.expandConfigs;
if ((nestedExpandConfigs == null ? void 0 : nestedExpandConfigs.length) > 0) {
const nestedExpandString = this.buildExpandString(nestedExpandConfigs);
if (nestedExpandString) {
expandOptions.expand = nestedExpandString;
}
}
return {
relation: relationName,
options: expandOptions,
targetTable,
nestedExpandConfigs: (nestedExpandConfigs == null ? void 0 : nestedExpandConfigs.length) > 0 ? nestedExpandConfigs : void 0
};
}
const defaultFields = getDefaultSelectFields(targetTable);
if (defaultFields) {
return {
relation: relationName,
options: { select: defaultFields },
targetTable
};
}
return {
relation: relationName,
targetTable
};
}
/**
* Builds a single expand string with its options.
*/
buildSingleExpand(config) {
const relationName = this.resolveRelationName(config);
const parts = this.buildExpandParts(config);
if (parts.length === 0) {
return relationName;
}
return `${relationName}(${parts.join(";")})`;
}
/**
* Resolves relation name, using FMTID if entity IDs are enabled.
*/
resolveRelationName(config) {
if (!this.useEntityIds) {
return config.relation;
}
const targetTable = config.targetTable;
if (targetTable && FMTable.Symbol.EntityId in targetTable) {
const tableId = targetTable[FMTable.Symbol.EntityId];
if (tableId) {
return tableId;
}
}
return config.relation;
}
/**
* Builds expand parts (select, filter, orderBy, etc.) for a single expand.
*/
buildExpandParts(config) {
if (!config.options || Object.keys(config.options).length === 0) {
return [];
}
const parts = [];
const opts = config.options;
if (opts.select) {
const selectArray = Array.isArray(opts.select) ? opts.select.map(String) : [String(opts.select)];
const selectFields = formatSelectFields(selectArray, config.targetTable, this.useEntityIds);
if (selectFields) {
parts.push(`$select=${selectFields}`);
}
}
if (opts.filter) {
if (typeof opts.filter === "string") {
parts.push(`$filter=${opts.filter}`);
} else {
const filterQuery = buildQuery({ filter: opts.filter });
const match = filterQuery.match(FILTER_QUERY_REGEX);
if (match) {
parts.push(`$filter=${match[1]}`);
}
}
}
if (opts.orderBy) {
const orderByValue = Array.isArray(opts.orderBy) ? opts.orderBy.join(",") : String(opts.orderBy);
parts.push(`$orderby=${orderByValue}`);
}
if (opts.top !== void 0) {
parts.push(`$top=${opts.top}`);
}
if (opts.skip !== void 0) {
parts.push(`$skip=${opts.skip}`);
}
if (opts.expand && typeof opts.expand === "string") {
parts.push(`$expand=${opts.expand}`);
}
return parts;
}
}
export {
ExpandBuilder
};
//# sourceMappingURL=expand-builder.js.map