UNPKG

@proofkit/fmodata

Version:

FileMaker OData API client

197 lines (196 loc) 7.22 kB
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