@proofkit/fmodata 0.1.0-alpha.17 → 0.1.0-alpha.19

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.
@@ -32,13 +32,14 @@ class ExpandBuilder {
32
32
  targetSchema = schema;
33
33
  }
34
34
  const selectedFields = ((_a = config.options) == null ? void 0 : _a.select) ? Array.isArray(config.options.select) ? config.options.select.map(String) : [String(config.options.select)] : void 0;
35
+ const nestedExpands = config.nestedExpandConfigs ? this.buildValidationConfigs(config.nestedExpandConfigs) : void 0;
35
36
  return {
36
37
  relation: config.relation,
37
38
  targetSchema,
38
39
  targetTable,
39
40
  table: targetTable,
40
41
  selectedFields,
41
- nestedExpands: void 0
42
+ nestedExpands
42
43
  };
43
44
  });
44
45
  }
@@ -53,7 +54,6 @@ class ExpandBuilder {
53
54
  * @returns ExpandConfig to add to the builder's expandConfigs array
54
55
  */
55
56
  processExpand(targetTable, sourceTable, callback, builderFactory) {
56
- var _a;
57
57
  const relationName = getTableName(targetTable);
58
58
  if (sourceTable) {
59
59
  const navigationPaths = getNavigationPaths(sourceTable);
@@ -75,10 +75,9 @@ class ExpandBuilder {
75
75
  expandOptions.select = defaultFields;
76
76
  }
77
77
  }
78
- if (((_a = configuredBuilder.expandConfigs) == null ? void 0 : _a.length) > 0) {
79
- const nestedExpandString = this.buildExpandString(
80
- configuredBuilder.expandConfigs
81
- );
78
+ const nestedExpandConfigs = configuredBuilder.expandConfigs;
79
+ if ((nestedExpandConfigs == null ? void 0 : nestedExpandConfigs.length) > 0) {
80
+ const nestedExpandString = this.buildExpandString(nestedExpandConfigs);
82
81
  if (nestedExpandString) {
83
82
  expandOptions.expand = nestedExpandString;
84
83
  }
@@ -86,7 +85,8 @@ class ExpandBuilder {
86
85
  return {
87
86
  relation: relationName,
88
87
  options: expandOptions,
89
- targetTable
88
+ targetTable,
89
+ nestedExpandConfigs: (nestedExpandConfigs == null ? void 0 : nestedExpandConfigs.length) > 0 ? nestedExpandConfigs : void 0
90
90
  };
91
91
  } else {
92
92
  const defaultFields = getDefaultSelectFields(targetTable);
@@ -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\";\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":"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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 // 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 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 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 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 } 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,4 +1,14 @@
1
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;
2
12
  /**
3
13
  * Formats select fields for use in OData query strings.
4
14
  * - Transforms field names to FMFIDs if using entity IDs
@@ -1,15 +1,23 @@
1
1
  import { transformFieldNamesArray } from "../../transform.js";
2
+ function needsFieldQuoting(fieldName) {
3
+ if (fieldName.startsWith("FMFID:") || fieldName.startsWith("FMTID:")) {
4
+ return false;
5
+ }
6
+ if (fieldName === "id") return true;
7
+ return fieldName.includes(" ") || fieldName.includes("_") || !/^[a-zA-Z][a-zA-Z0-9]*$/.test(fieldName);
8
+ }
2
9
  function formatSelectFields(select, table, useEntityIds) {
3
10
  if (!select || select.length === 0) return "";
4
11
  const selectArray = Array.isArray(select) ? select : [select];
5
12
  const transformedFields = table && useEntityIds ? transformFieldNamesArray(selectArray.map(String), table) : selectArray.map(String);
6
13
  return transformedFields.map((field) => {
7
- if (field === "id") return `"id"`;
14
+ if (needsFieldQuoting(field)) return `"${field}"`;
8
15
  const encoded = encodeURIComponent(field);
9
16
  return encoded.replace(/%20/g, " ");
10
17
  }).join(",");
11
18
  }
12
19
  export {
13
- formatSelectFields
20
+ formatSelectFields,
21
+ needsFieldQuoting
14
22
  };
15
23
  //# sourceMappingURL=select-utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"select-utils.js","sources":["../../../../src/client/builders/select-utils.ts"],"sourcesContent":["import type { FMTable } from \"../../orm/table\";\nimport { transformFieldNamesArray } from \"../../transform\";\n\n/**\n * Formats select fields for use in OData query strings.\n * - Transforms field names to FMFIDs if using entity IDs\n * - Wraps \"id\" fields in double quotes (OData reserved)\n * - URL-encodes special characters but preserves spaces\n */\nexport function formatSelectFields(\n select: string[] | readonly string[] | undefined,\n table?: FMTable<any, any>,\n useEntityIds?: boolean,\n): string {\n if (!select || select.length === 0) return \"\";\n\n const selectArray = Array.isArray(select) ? select : [select];\n\n // Transform to field IDs if using entity IDs\n const transformedFields =\n table && useEntityIds\n ? transformFieldNamesArray(selectArray.map(String), table)\n : selectArray.map(String);\n\n return transformedFields\n .map((field) => {\n if (field === \"id\") return `\"id\"`;\n const encoded = encodeURIComponent(field);\n return encoded.replace(/%20/g, \" \");\n })\n .join(\",\");\n}\n\n\n"],"names":[],"mappings":";AASgB,SAAA,mBACd,QACA,OACA,cACQ;AACR,MAAI,CAAC,UAAU,OAAO,WAAW,EAAU,QAAA;AAE3C,QAAM,cAAc,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAC,MAAM;AAG5D,QAAM,oBACJ,SAAS,eACL,yBAAyB,YAAY,IAAI,MAAM,GAAG,KAAK,IACvD,YAAY,IAAI,MAAM;AAErB,SAAA,kBACJ,IAAI,CAAC,UAAU;AACV,QAAA,UAAU,KAAa,QAAA;AACrB,UAAA,UAAU,mBAAmB,KAAK;AACjC,WAAA,QAAQ,QAAQ,QAAQ,GAAG;AAAA,EAAA,CACnC,EACA,KAAK,GAAG;AACb;"}
1
+ {"version":3,"file":"select-utils.js","sources":["../../../../src/client/builders/select-utils.ts"],"sourcesContent":["import type { FMTable } from \"../../orm/table\";\nimport { transformFieldNamesArray } from \"../../transform\";\n\n/**\n * Determines if a field name needs to be quoted in OData queries.\n * Per FileMaker docs: field names with special characters (spaces, underscores, etc.) must be quoted.\n * Also quotes \"id\" as it's an OData reserved word.\n * Entity IDs (FMFID:*, FMTID:*) are not quoted as they're identifiers, not field names.\n *\n * @param fieldName - The field name or identifier to check\n * @returns true if the field name should be quoted in OData queries\n */\nexport function needsFieldQuoting(fieldName: string): boolean {\n // Entity IDs are identifiers and don't need quoting\n if (fieldName.startsWith(\"FMFID:\") || fieldName.startsWith(\"FMTID:\")) {\n return false;\n }\n // Always quote \"id\" as it's an OData reserved word\n if (fieldName === \"id\") return true;\n // Quote if field name contains spaces, underscores, or other special characters\n return (\n fieldName.includes(\" \") ||\n fieldName.includes(\"_\") ||\n !/^[a-zA-Z][a-zA-Z0-9]*$/.test(fieldName)\n );\n}\n\n/**\n * Formats select fields for use in OData query strings.\n * - Transforms field names to FMFIDs if using entity IDs\n * - Wraps \"id\" fields in double quotes (OData reserved)\n * - URL-encodes special characters but preserves spaces\n */\nexport function formatSelectFields(\n select: string[] | readonly string[] | undefined,\n table?: FMTable<any, any>,\n useEntityIds?: boolean,\n): string {\n if (!select || select.length === 0) return \"\";\n\n const selectArray = Array.isArray(select) ? select : [select];\n\n // Transform to field IDs if using entity IDs\n const transformedFields =\n table && useEntityIds\n ? transformFieldNamesArray(selectArray.map(String), table)\n : selectArray.map(String);\n\n return transformedFields\n .map((field) => {\n if (needsFieldQuoting(field)) return `\"${field}\"`;\n const encoded = encodeURIComponent(field);\n return encoded.replace(/%20/g, \" \");\n })\n .join(\",\");\n}\n"],"names":[],"mappings":";AAYO,SAAS,kBAAkB,WAA4B;AAE5D,MAAI,UAAU,WAAW,QAAQ,KAAK,UAAU,WAAW,QAAQ,GAAG;AAC7D,WAAA;AAAA,EAAA;AAGL,MAAA,cAAc,KAAa,QAAA;AAG7B,SAAA,UAAU,SAAS,GAAG,KACtB,UAAU,SAAS,GAAG,KACtB,CAAC,yBAAyB,KAAK,SAAS;AAE5C;AAQgB,SAAA,mBACd,QACA,OACA,cACQ;AACR,MAAI,CAAC,UAAU,OAAO,WAAW,EAAU,QAAA;AAE3C,QAAM,cAAc,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAC,MAAM;AAG5D,QAAM,oBACJ,SAAS,eACL,yBAAyB,YAAY,IAAI,MAAM,GAAG,KAAK,IACvD,YAAY,IAAI,MAAM;AAErB,SAAA,kBACJ,IAAI,CAAC,UAAU;AACd,QAAI,kBAAkB,KAAK,EAAG,QAAO,IAAI,KAAK;AACxC,UAAA,UAAU,mBAAmB,KAAK;AACjC,WAAA,QAAQ,QAAQ,QAAQ,GAAG;AAAA,EAAA,CACnC,EACA,KAAK,GAAG;AACb;"}
@@ -8,6 +8,7 @@ export type ExpandConfig = {
8
8
  relation: string;
9
9
  options?: Partial<QueryOptions<any>>;
10
10
  targetTable?: FMTable<any, any>;
11
+ nestedExpandConfigs?: ExpandConfig[];
11
12
  };
12
13
  /**
13
14
  * Type to represent expanded relations in return types
@@ -1 +1 @@
1
- {"version":3,"file":"entity-set.js","sources":["../../../src/client/entity-set.ts"],"sourcesContent":["import type { ExecutionContext, InferSchemaType } from \"../types\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { QueryBuilder } from \"./query/index\";\nimport { RecordBuilder } from \"./record-builder\";\nimport { InsertBuilder } from \"./insert-builder\";\nimport { DeleteBuilder } from \"./delete-builder\";\nimport { UpdateBuilder } from \"./update-builder\";\nimport { Database } from \"./database\";\nimport type {\n FMTable,\n InferSchemaOutputFromFMTable,\n InferInputSchemaFromFMTable,\n InsertDataFromFMTable,\n UpdateDataFromFMTable,\n ValidExpandTarget,\n ExtractTableName,\n FMTableWithColumns,\n InferFieldOutput,\n ColumnMap,\n} from \"../orm/table\";\nimport {\n FMTable as FMTableClass,\n getDefaultSelect,\n getNavigationPaths,\n getTableName,\n getTableColumns,\n getTableFields,\n} from \"../orm/table\";\nimport type { Column } from \"../orm/column\";\nimport type { FieldBuilder } from \"../orm/field-builders\";\nimport { createLogger, InternalLogger } from \"../logger\";\n\n// Helper type to extract defaultSelect from an FMTable\n// Since TypeScript can't extract Symbol-indexed properties at the type level,\n// we simplify to return keyof InferSchemaFromFMTable<O> when O is an FMTable.\n// The actual defaultSelect logic is handled at runtime.\ntype ExtractDefaultSelect<O> =\n O extends FMTable<any, any> ? keyof InferSchemaOutputFromFMTable<O> : never;\n\n/**\n * Helper type to extract properly-typed columns from an FMTable.\n * This preserves the specific column types instead of widening to `any`.\n */\ntype ExtractColumnsFromOcc<T> =\n T extends FMTable<infer TFields, infer TName, any>\n ? TFields extends Record<string, FieldBuilder<any, any, any, any>>\n ? ColumnMap<TFields, TName>\n : never\n : never;\n\nexport class EntitySet<Occ extends FMTable<any, any>> {\n private occurrence: Occ;\n private databaseName: string;\n private context: ExecutionContext;\n private database: Database; // Database instance for accessing occurrences\n private isNavigateFromEntitySet?: boolean;\n private navigateRelation?: string;\n private navigateSourceTableName?: string;\n private navigateBasePath?: string; // Full base path for chained navigations\n private databaseUseEntityIds: boolean;\n private logger: InternalLogger;\n\n constructor(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n database?: any;\n }) {\n this.occurrence = config.occurrence;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.database = config.database;\n // Get useEntityIds from database if available, otherwise default to false\n this.databaseUseEntityIds =\n (config.database as any)?._useEntityIds ?? false;\n this.logger = config.context?._getLogger?.() ?? createLogger();\n }\n\n // Type-only method to help TypeScript infer the schema from table\n static create<Occ extends FMTable<any, any>>(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n database: Database;\n }): EntitySet<Occ> {\n return new EntitySet<Occ>({\n occurrence: config.occurrence,\n databaseName: config.databaseName,\n context: config.context,\n database: config.database,\n });\n }\n\n list(): QueryBuilder<\n Occ,\n keyof InferSchemaOutputFromFMTable<Occ>,\n false,\n false,\n {}\n > {\n const builder = new QueryBuilder<Occ>({\n occurrence: this.occurrence as Occ,\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n\n // Apply defaultSelect if occurrence exists and select hasn't been called\n if (this.occurrence) {\n // FMTable - access via helper functions\n const defaultSelectValue = getDefaultSelect(this.occurrence);\n const tableSchema = (this.occurrence as any)[FMTableClass.Symbol.Schema];\n let schema: Record<string, StandardSchemaV1> | undefined;\n\n if (tableSchema) {\n // Extract schema from StandardSchemaV1\n const zodSchema = tableSchema[\"~standard\"]?.schema;\n if (\n zodSchema &&\n typeof zodSchema === \"object\" &&\n \"shape\" in zodSchema\n ) {\n schema = zodSchema.shape as Record<string, StandardSchemaV1>;\n }\n }\n\n if (defaultSelectValue === \"schema\") {\n // Use getTableColumns to get all columns and select them\n // This is equivalent to select(getTableColumns(occurrence))\n // Cast to the declared return type - runtime behavior handles the actual selection\n const allColumns = getTableColumns(\n this.occurrence as any,\n ) as ExtractColumnsFromOcc<Occ>;\n return builder.select(allColumns).top(1000) as QueryBuilder<\n Occ,\n keyof InferSchemaOutputFromFMTable<Occ>,\n false,\n false,\n {}\n >;\n } else if (typeof defaultSelectValue === \"object\") {\n // defaultSelectValue is a select object (Record<string, Column>)\n // Cast to the declared return type - runtime behavior handles the actual selection\n return builder\n .select(defaultSelectValue as ExtractColumnsFromOcc<Occ>)\n .top(1000) as QueryBuilder<\n Occ,\n keyof InferSchemaOutputFromFMTable<Occ>,\n false,\n false,\n {}\n >;\n }\n // If defaultSelect is \"all\", no changes needed (current behavior)\n }\n\n // Propagate navigation context if present\n if (\n this.isNavigateFromEntitySet &&\n this.navigateRelation &&\n this.navigateSourceTableName\n ) {\n (builder as any).navigation = {\n relation: this.navigateRelation,\n sourceTableName: this.navigateSourceTableName,\n basePath: this.navigateBasePath,\n // recordId is intentionally not set (undefined) to indicate navigation from EntitySet\n };\n }\n\n // Apply default pagination limit of 1000 records to prevent stack overflow\n // with large datasets. Users can override with .top() if needed.\n return builder.top(1000);\n }\n\n get(\n id: string | number,\n ): RecordBuilder<\n Occ,\n false,\n undefined,\n keyof InferSchemaOutputFromFMTable<Occ>,\n {}\n > {\n const builder = new RecordBuilder<Occ>({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n recordId: id,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n\n // Apply defaultSelect if occurrence exists\n if (this.occurrence) {\n // FMTable - access via helper functions\n const defaultSelectValue = getDefaultSelect(this.occurrence);\n const tableSchema = (this.occurrence as any)[FMTableClass.Symbol.Schema];\n let schema: Record<string, StandardSchemaV1> | undefined;\n\n if (tableSchema) {\n // Extract schema from StandardSchemaV1\n const zodSchema = tableSchema[\"~standard\"]?.schema;\n if (\n zodSchema &&\n typeof zodSchema === \"object\" &&\n \"shape\" in zodSchema\n ) {\n schema = zodSchema.shape as Record<string, StandardSchemaV1>;\n }\n }\n\n if (defaultSelectValue === \"schema\") {\n // Use getTableColumns to get all columns and select them\n // This is equivalent to select(getTableColumns(occurrence))\n // Use ExtractColumnsFromOcc to preserve the properly-typed column types\n const allColumns = getTableColumns(\n this.occurrence as any,\n ) as ExtractColumnsFromOcc<Occ>;\n const selectedBuilder = builder.select(allColumns);\n // Propagate navigation context if present\n if (\n this.isNavigateFromEntitySet &&\n this.navigateRelation &&\n this.navigateSourceTableName\n ) {\n (selectedBuilder as any).navigation = {\n relation: this.navigateRelation,\n sourceTableName: this.navigateSourceTableName,\n basePath: this.navigateBasePath,\n };\n }\n return selectedBuilder as any;\n } else if (\n typeof defaultSelectValue === \"object\" &&\n defaultSelectValue !== null &&\n !Array.isArray(defaultSelectValue)\n ) {\n // defaultSelectValue is a select object (Record<string, Column>)\n // Use it directly with select()\n // Use ExtractColumnsFromOcc to preserve the properly-typed column types\n const selectedBuilder = builder.select(\n defaultSelectValue as ExtractColumnsFromOcc<Occ>,\n );\n // Propagate navigation context if present\n if (\n this.isNavigateFromEntitySet &&\n this.navigateRelation &&\n this.navigateSourceTableName\n ) {\n (selectedBuilder as any).navigation = {\n relation: this.navigateRelation,\n sourceTableName: this.navigateSourceTableName,\n basePath: this.navigateBasePath,\n };\n }\n return selectedBuilder as any;\n }\n // If defaultSelect is \"all\", no changes needed (current behavior)\n }\n\n // Propagate navigation context if present\n if (\n this.isNavigateFromEntitySet &&\n this.navigateRelation &&\n this.navigateSourceTableName\n ) {\n (builder as any).navigation = {\n relation: this.navigateRelation,\n sourceTableName: this.navigateSourceTableName,\n basePath: this.navigateBasePath,\n };\n }\n return builder as any;\n }\n\n // Overload: when returnFullRecord is false\n insert(\n data: InsertDataFromFMTable<Occ>,\n options: { returnFullRecord: false },\n ): InsertBuilder<Occ, \"minimal\">;\n\n // Overload: when returnFullRecord is true or omitted (default)\n insert(\n data: InsertDataFromFMTable<Occ>,\n options?: { returnFullRecord?: true },\n ): InsertBuilder<Occ, \"representation\">;\n\n // Implementation\n insert(\n data: InsertDataFromFMTable<Occ>,\n options?: { returnFullRecord?: boolean },\n ): InsertBuilder<Occ, \"minimal\" | \"representation\"> {\n const returnPreference =\n options?.returnFullRecord === false ? \"minimal\" : \"representation\";\n\n return new InsertBuilder<Occ, typeof returnPreference>({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n data: data as any, // Input type is validated/transformed at runtime\n returnPreference: returnPreference as any,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n\n // Overload: when returnFullRecord is explicitly true\n update(\n data: UpdateDataFromFMTable<Occ>,\n options: { returnFullRecord: true },\n ): UpdateBuilder<Occ, \"representation\">;\n\n // Overload: when returnFullRecord is false or omitted (default)\n update(\n data: UpdateDataFromFMTable<Occ>,\n options?: { returnFullRecord?: false },\n ): UpdateBuilder<Occ, \"minimal\">;\n\n // Implementation\n update(\n data: UpdateDataFromFMTable<Occ>,\n options?: { returnFullRecord?: boolean },\n ): UpdateBuilder<Occ, \"minimal\" | \"representation\"> {\n const returnPreference =\n options?.returnFullRecord === true ? \"representation\" : \"minimal\";\n\n return new UpdateBuilder<Occ, typeof returnPreference>({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n data: data as any, // Input type is validated/transformed at runtime\n returnPreference: returnPreference as any,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n\n delete(): DeleteBuilder<Occ> {\n return new DeleteBuilder<Occ>({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n }) as any;\n }\n\n // Implementation\n navigate<TargetTable extends FMTable<any, any>>(\n targetTable: ValidExpandTarget<Occ, TargetTable>,\n ): EntitySet<TargetTable extends FMTable<any, any> ? TargetTable : never> {\n // Check if it's an FMTable object or a string\n let relationName: string;\n\n // FMTable object - extract name and validate\n relationName = getTableName(targetTable);\n\n // Runtime validation: Check if relation name is in navigationPaths\n if (\n this.occurrence &&\n FMTableClass.Symbol.NavigationPaths in this.occurrence\n ) {\n const navigationPaths = (this.occurrence as any)[\n FMTableClass.Symbol.NavigationPaths\n ] as readonly string[];\n if (navigationPaths && !navigationPaths.includes(relationName)) {\n this.logger.warn(\n `Cannot navigate to \"${relationName}\". Valid navigation paths: ${navigationPaths.length > 0 ? navigationPaths.join(\", \") : \"none\"}`,\n );\n }\n }\n\n // Create EntitySet with target table\n const entitySet = new EntitySet<any>({\n occurrence: targetTable,\n databaseName: this.databaseName,\n context: this.context,\n database: this.database,\n });\n // Store the navigation info in the EntitySet\n (entitySet as any).isNavigateFromEntitySet = true;\n (entitySet as any).navigateRelation = relationName;\n\n // Build the full base path for chained navigations\n if (this.isNavigateFromEntitySet && this.navigateBasePath) {\n // Already have a base path from previous navigation - extend it with current relation\n (entitySet as any).navigateBasePath =\n `${this.navigateBasePath}/${this.navigateRelation}`;\n (entitySet as any).navigateSourceTableName = this.navigateSourceTableName;\n } else if (this.isNavigateFromEntitySet && this.navigateRelation) {\n // First chained navigation - create base path from source/relation\n (entitySet as any).navigateBasePath =\n `${this.navigateSourceTableName}/${this.navigateRelation}`;\n (entitySet as any).navigateSourceTableName = this.navigateSourceTableName;\n } else {\n // Initial navigation - source is just the table name\n (entitySet as any).navigateSourceTableName = getTableName(\n this.occurrence,\n );\n }\n return entitySet;\n }\n}\n"],"names":["FMTableClass"],"mappings":"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"}
1
+ {"version":3,"file":"entity-set.js","sources":["../../../src/client/entity-set.ts"],"sourcesContent":["import type { ExecutionContext } from \"../types\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { QueryBuilder } from \"./query/index\";\nimport { RecordBuilder } from \"./record-builder\";\nimport { InsertBuilder } from \"./insert-builder\";\nimport { DeleteBuilder } from \"./delete-builder\";\nimport { UpdateBuilder } from \"./update-builder\";\nimport { Database } from \"./database\";\nimport type {\n FMTable,\n InferSchemaOutputFromFMTable,\n InsertDataFromFMTable,\n UpdateDataFromFMTable,\n ValidExpandTarget,\n ColumnMap,\n} from \"../orm/table\";\nimport {\n FMTable as FMTableClass,\n getDefaultSelect,\n getTableName,\n getTableColumns,\n} from \"../orm/table\";\nimport type { FieldBuilder } from \"../orm/field-builders\";\nimport { createLogger, InternalLogger } from \"../logger\";\n\n// Helper type to extract defaultSelect from an FMTable\n// Since TypeScript can't extract Symbol-indexed properties at the type level,\n// we simplify to return keyof InferSchemaFromFMTable<O> when O is an FMTable.\n// The actual defaultSelect logic is handled at runtime.\ntype ExtractDefaultSelect<O> =\n O extends FMTable<any, any> ? keyof InferSchemaOutputFromFMTable<O> : never;\n\n/**\n * Helper type to extract properly-typed columns from an FMTable.\n * This preserves the specific column types instead of widening to `any`.\n */\ntype ExtractColumnsFromOcc<T> =\n T extends FMTable<infer TFields, infer TName, any>\n ? TFields extends Record<string, FieldBuilder<any, any, any, any>>\n ? ColumnMap<TFields, TName>\n : never\n : never;\n\nexport class EntitySet<Occ extends FMTable<any, any>> {\n private occurrence: Occ;\n private databaseName: string;\n private context: ExecutionContext;\n private database: Database; // Database instance for accessing occurrences\n private isNavigateFromEntitySet?: boolean;\n private navigateRelation?: string;\n private navigateSourceTableName?: string;\n private navigateBasePath?: string; // Full base path for chained navigations\n private databaseUseEntityIds: boolean;\n private logger: InternalLogger;\n\n constructor(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n database?: any;\n }) {\n this.occurrence = config.occurrence;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.database = config.database;\n // Get useEntityIds from database if available, otherwise default to false\n this.databaseUseEntityIds =\n (config.database as any)?._useEntityIds ?? false;\n this.logger = config.context?._getLogger?.() ?? createLogger();\n }\n\n // Type-only method to help TypeScript infer the schema from table\n static create<Occ extends FMTable<any, any>>(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n database: Database;\n }): EntitySet<Occ> {\n return new EntitySet<Occ>({\n occurrence: config.occurrence,\n databaseName: config.databaseName,\n context: config.context,\n database: config.database,\n });\n }\n\n list(): QueryBuilder<\n Occ,\n keyof InferSchemaOutputFromFMTable<Occ>,\n false,\n false,\n {}\n > {\n const builder = new QueryBuilder<Occ>({\n occurrence: this.occurrence as Occ,\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n\n // Apply defaultSelect if occurrence exists and select hasn't been called\n if (this.occurrence) {\n // FMTable - access via helper functions\n const defaultSelectValue = getDefaultSelect(this.occurrence);\n const tableSchema = (this.occurrence as any)[FMTableClass.Symbol.Schema];\n let schema: Record<string, StandardSchemaV1> | undefined;\n\n if (tableSchema) {\n // Extract schema from StandardSchemaV1\n const zodSchema = tableSchema[\"~standard\"]?.schema;\n if (\n zodSchema &&\n typeof zodSchema === \"object\" &&\n \"shape\" in zodSchema\n ) {\n schema = zodSchema.shape as Record<string, StandardSchemaV1>;\n }\n }\n\n if (defaultSelectValue === \"schema\") {\n // Use getTableColumns to get all columns and select them\n // This is equivalent to select(getTableColumns(occurrence))\n // Cast to the declared return type - runtime behavior handles the actual selection\n const allColumns = getTableColumns(\n this.occurrence,\n ) as ExtractColumnsFromOcc<Occ>;\n return builder.select(allColumns).top(1000) as QueryBuilder<\n Occ,\n keyof InferSchemaOutputFromFMTable<Occ>,\n false,\n false,\n {}\n >;\n } else if (typeof defaultSelectValue === \"object\") {\n // defaultSelectValue is a select object (Record<string, Column>)\n // Cast to the declared return type - runtime behavior handles the actual selection\n return builder\n .select(defaultSelectValue as ExtractColumnsFromOcc<Occ>)\n .top(1000) as QueryBuilder<\n Occ,\n keyof InferSchemaOutputFromFMTable<Occ>,\n false,\n false,\n {}\n >;\n }\n // If defaultSelect is \"all\", no changes needed (current behavior)\n }\n\n // Propagate navigation context if present\n if (\n this.isNavigateFromEntitySet &&\n this.navigateRelation &&\n this.navigateSourceTableName\n ) {\n (builder as any).navigation = {\n relation: this.navigateRelation,\n sourceTableName: this.navigateSourceTableName,\n basePath: this.navigateBasePath,\n // recordId is intentionally not set (undefined) to indicate navigation from EntitySet\n };\n }\n\n // Apply default pagination limit of 1000 records to prevent stack overflow\n // with large datasets. Users can override with .top() if needed.\n return builder.top(1000);\n }\n\n get(\n id: string | number,\n ): RecordBuilder<\n Occ,\n false,\n undefined,\n keyof InferSchemaOutputFromFMTable<Occ>,\n {}\n > {\n const builder = new RecordBuilder<Occ>({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n recordId: id,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n\n // Apply defaultSelect if occurrence exists\n if (this.occurrence) {\n // FMTable - access via helper functions\n const defaultSelectValue = getDefaultSelect(this.occurrence);\n const tableSchema = (this.occurrence as any)[FMTableClass.Symbol.Schema];\n let schema: Record<string, StandardSchemaV1> | undefined;\n\n if (tableSchema) {\n // Extract schema from StandardSchemaV1\n const zodSchema = tableSchema[\"~standard\"]?.schema;\n if (\n zodSchema &&\n typeof zodSchema === \"object\" &&\n \"shape\" in zodSchema\n ) {\n schema = zodSchema.shape as Record<string, StandardSchemaV1>;\n }\n }\n\n if (defaultSelectValue === \"schema\") {\n // Use getTableColumns to get all columns and select them\n // This is equivalent to select(getTableColumns(occurrence))\n // Use ExtractColumnsFromOcc to preserve the properly-typed column types\n const allColumns = getTableColumns(\n this.occurrence as any,\n ) as ExtractColumnsFromOcc<Occ>;\n const selectedBuilder = builder.select(allColumns);\n // Propagate navigation context if present\n if (\n this.isNavigateFromEntitySet &&\n this.navigateRelation &&\n this.navigateSourceTableName\n ) {\n (selectedBuilder as any).navigation = {\n relation: this.navigateRelation,\n sourceTableName: this.navigateSourceTableName,\n basePath: this.navigateBasePath,\n };\n }\n return selectedBuilder as any;\n } else if (\n typeof defaultSelectValue === \"object\" &&\n defaultSelectValue !== null &&\n !Array.isArray(defaultSelectValue)\n ) {\n // defaultSelectValue is a select object (Record<string, Column>)\n // Use it directly with select()\n // Use ExtractColumnsFromOcc to preserve the properly-typed column types\n const selectedBuilder = builder.select(\n defaultSelectValue as ExtractColumnsFromOcc<Occ>,\n );\n // Propagate navigation context if present\n if (\n this.isNavigateFromEntitySet &&\n this.navigateRelation &&\n this.navigateSourceTableName\n ) {\n (selectedBuilder as any).navigation = {\n relation: this.navigateRelation,\n sourceTableName: this.navigateSourceTableName,\n basePath: this.navigateBasePath,\n };\n }\n return selectedBuilder as any;\n }\n // If defaultSelect is \"all\", no changes needed (current behavior)\n }\n\n // Propagate navigation context if present\n if (\n this.isNavigateFromEntitySet &&\n this.navigateRelation &&\n this.navigateSourceTableName\n ) {\n (builder as any).navigation = {\n relation: this.navigateRelation,\n sourceTableName: this.navigateSourceTableName,\n basePath: this.navigateBasePath,\n };\n }\n return builder as any;\n }\n\n // Overload: when returnFullRecord is false\n insert(\n data: InsertDataFromFMTable<Occ>,\n options: { returnFullRecord: false },\n ): InsertBuilder<Occ, \"minimal\">;\n\n // Overload: when returnFullRecord is true or omitted (default)\n insert(\n data: InsertDataFromFMTable<Occ>,\n options?: { returnFullRecord?: true },\n ): InsertBuilder<Occ, \"representation\">;\n\n // Implementation\n insert(\n data: InsertDataFromFMTable<Occ>,\n options?: { returnFullRecord?: boolean },\n ): InsertBuilder<Occ, \"minimal\" | \"representation\"> {\n const returnPreference =\n options?.returnFullRecord === false ? \"minimal\" : \"representation\";\n\n return new InsertBuilder<Occ, typeof returnPreference>({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n data: data as any, // Input type is validated/transformed at runtime\n returnPreference: returnPreference as any,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n\n // Overload: when returnFullRecord is explicitly true\n update(\n data: UpdateDataFromFMTable<Occ>,\n options: { returnFullRecord: true },\n ): UpdateBuilder<Occ, \"representation\">;\n\n // Overload: when returnFullRecord is false or omitted (default)\n update(\n data: UpdateDataFromFMTable<Occ>,\n options?: { returnFullRecord?: false },\n ): UpdateBuilder<Occ, \"minimal\">;\n\n // Implementation\n update(\n data: UpdateDataFromFMTable<Occ>,\n options?: { returnFullRecord?: boolean },\n ): UpdateBuilder<Occ, \"minimal\" | \"representation\"> {\n const returnPreference =\n options?.returnFullRecord === true ? \"representation\" : \"minimal\";\n\n return new UpdateBuilder<Occ, typeof returnPreference>({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n data: data as any, // Input type is validated/transformed at runtime\n returnPreference: returnPreference as any,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n\n delete(): DeleteBuilder<Occ> {\n return new DeleteBuilder<Occ>({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n }) as any;\n }\n\n // Implementation\n navigate<TargetTable extends FMTable<any, any>>(\n targetTable: ValidExpandTarget<Occ, TargetTable>,\n ): EntitySet<TargetTable extends FMTable<any, any> ? TargetTable : never> {\n // Check if it's an FMTable object or a string\n let relationName: string;\n\n // FMTable object - extract name and validate\n relationName = getTableName(targetTable);\n\n // Runtime validation: Check if relation name is in navigationPaths\n if (\n this.occurrence &&\n FMTableClass.Symbol.NavigationPaths in this.occurrence\n ) {\n const navigationPaths = (this.occurrence as any)[\n FMTableClass.Symbol.NavigationPaths\n ] as readonly string[];\n if (navigationPaths && !navigationPaths.includes(relationName)) {\n this.logger.warn(\n `Cannot navigate to \"${relationName}\". Valid navigation paths: ${navigationPaths.length > 0 ? navigationPaths.join(\", \") : \"none\"}`,\n );\n }\n }\n\n // Create EntitySet with target table\n const entitySet = new EntitySet<any>({\n occurrence: targetTable,\n databaseName: this.databaseName,\n context: this.context,\n database: this.database,\n });\n // Store the navigation info in the EntitySet\n (entitySet as any).isNavigateFromEntitySet = true;\n (entitySet as any).navigateRelation = relationName;\n\n // Build the full base path for chained navigations\n if (this.isNavigateFromEntitySet && this.navigateBasePath) {\n // Already have a base path from previous navigation - extend it with current relation\n (entitySet as any).navigateBasePath =\n `${this.navigateBasePath}/${this.navigateRelation}`;\n (entitySet as any).navigateSourceTableName = this.navigateSourceTableName;\n } else if (this.isNavigateFromEntitySet && this.navigateRelation) {\n // First chained navigation - create base path from source/relation\n (entitySet as any).navigateBasePath =\n `${this.navigateSourceTableName}/${this.navigateRelation}`;\n (entitySet as any).navigateSourceTableName = this.navigateSourceTableName;\n } else {\n // Initial navigation - source is just the table name\n (entitySet as any).navigateSourceTableName = getTableName(\n this.occurrence,\n );\n }\n return entitySet;\n }\n}\n"],"names":["FMTableClass"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"error-parser.js","sources":["../../../src/client/error-parser.ts"],"sourcesContent":["import {\n HTTPError,\n ODataError,\n SchemaLockedError,\n FMODataErrorType,\n} from \"../errors\";\nimport { safeJsonParse } from \"./sanitize-json\";\n\n/**\n * Parses an error response and returns an appropriate error object.\n * This helper is used by builder processResponse methods to handle error responses\n * consistently, particularly important for batch operations where errors need to be\n * properly parsed from the response body.\n *\n * @param response - The Response object (may be from batch or direct request)\n * @param url - The URL that was requested (for error context)\n * @returns An appropriate error object (ODataError, SchemaLockedError, or HTTPError)\n */\nexport async function parseErrorResponse(\n response: Response,\n url: string,\n): Promise<FMODataErrorType> {\n // Try to parse error body if it's JSON\n let errorBody: { error?: { code?: string | number; message?: string } } | undefined;\n \n try {\n if (response.headers.get(\"content-type\")?.includes(\"application/json\")) {\n errorBody = await safeJsonParse<typeof errorBody>(response);\n }\n } catch {\n // Ignore JSON parse errors - we'll fall back to HTTPError\n }\n\n // Check if it's an OData error response\n if (errorBody?.error) {\n const errorCode = errorBody.error.code;\n const errorMessage = errorBody.error.message || response.statusText;\n\n // Check for schema locked error (code 303)\n if (errorCode === \"303\" || errorCode === 303) {\n return new SchemaLockedError(url, errorMessage, errorBody.error);\n }\n\n return new ODataError(\n url,\n errorMessage,\n String(errorCode),\n errorBody.error,\n );\n }\n\n // Fall back to generic HTTPError\n return new HTTPError(url, response.status, response.statusText, errorBody);\n}\n\n\n\n\n\n\n\n\n"],"names":[],"mappings":";;AAkBsB,eAAA,mBACpB,UACA,KAC2B;;AAEvB,MAAA;AAEA,MAAA;AACF,SAAI,cAAS,QAAQ,IAAI,cAAc,MAAnC,mBAAsC,SAAS,qBAAqB;AAC1D,kBAAA,MAAM,cAAgC,QAAQ;AAAA,IAAA;AAAA,EAC5D,QACM;AAAA,EAAA;AAKR,MAAI,uCAAW,OAAO;AACd,UAAA,YAAY,UAAU,MAAM;AAClC,UAAM,eAAe,UAAU,MAAM,WAAW,SAAS;AAGrD,QAAA,cAAc,SAAS,cAAc,KAAK;AAC5C,aAAO,IAAI,kBAAkB,KAAK,cAAc,UAAU,KAAK;AAAA,IAAA;AAGjE,WAAO,IAAI;AAAA,MACT;AAAA,MACA;AAAA,MACA,OAAO,SAAS;AAAA,MAChB,UAAU;AAAA,IACZ;AAAA,EAAA;AAIF,SAAO,IAAI,UAAU,KAAK,SAAS,QAAQ,SAAS,YAAY,SAAS;AAC3E;"}
1
+ {"version":3,"file":"error-parser.js","sources":["../../../src/client/error-parser.ts"],"sourcesContent":["import {\n HTTPError,\n ODataError,\n SchemaLockedError,\n FMODataErrorType,\n} from \"../errors\";\nimport { safeJsonParse } from \"./sanitize-json\";\n\n/**\n * Parses an error response and returns an appropriate error object.\n * This helper is used by builder processResponse methods to handle error responses\n * consistently, particularly important for batch operations where errors need to be\n * properly parsed from the response body.\n *\n * @param response - The Response object (may be from batch or direct request)\n * @param url - The URL that was requested (for error context)\n * @returns An appropriate error object (ODataError, SchemaLockedError, or HTTPError)\n */\nexport async function parseErrorResponse(\n response: Response,\n url: string,\n): Promise<FMODataErrorType> {\n // Try to parse error body if it's JSON\n let errorBody:\n | { error?: { code?: string | number; message?: string } }\n | undefined;\n\n try {\n if (response.headers.get(\"content-type\")?.includes(\"application/json\")) {\n errorBody = await safeJsonParse<typeof errorBody>(response);\n }\n } catch {\n // Ignore JSON parse errors - we'll fall back to HTTPError\n }\n\n // Check if it's an OData error response\n if (errorBody?.error) {\n const errorCode = errorBody.error.code;\n const errorMessage = errorBody.error.message || response.statusText;\n\n // Check for schema locked error (code 303)\n if (errorCode === \"303\" || errorCode === 303) {\n return new SchemaLockedError(url, errorMessage, errorBody.error);\n }\n\n return new ODataError(\n url,\n errorMessage,\n String(errorCode),\n errorBody.error,\n );\n }\n\n // Fall back to generic HTTPError\n return new HTTPError(url, response.status, response.statusText, errorBody);\n}\n"],"names":[],"mappings":";;AAkBsB,eAAA,mBACpB,UACA,KAC2B;;AAEvB,MAAA;AAIA,MAAA;AACF,SAAI,cAAS,QAAQ,IAAI,cAAc,MAAnC,mBAAsC,SAAS,qBAAqB;AAC1D,kBAAA,MAAM,cAAgC,QAAQ;AAAA,IAAA;AAAA,EAC5D,QACM;AAAA,EAAA;AAKR,MAAI,uCAAW,OAAO;AACd,UAAA,YAAY,UAAU,MAAM;AAClC,UAAM,eAAe,UAAU,MAAM,WAAW,SAAS;AAGrD,QAAA,cAAc,SAAS,cAAc,KAAK;AAC5C,aAAO,IAAI,kBAAkB,KAAK,cAAc,UAAU,KAAK;AAAA,IAAA;AAGjE,WAAO,IAAI;AAAA,MACT;AAAA,MACA;AAAA,MACA,OAAO,SAAS;AAAA,MAChB,UAAU;AAAA,IACZ;AAAA,EAAA;AAIF,SAAO,IAAI,UAAU,KAAK,SAAS,QAAQ,SAAS,YAAY,SAAS;AAC3E;"}
@@ -102,10 +102,11 @@ export declare class QueryBuilder<Occ extends FMTable<any, any>, Selected extend
102
102
  ]): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands>;
103
103
  top(count: number): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands>;
104
104
  skip(count: number): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands>;
105
- expand<TargetTable extends FMTable<any, any>>(targetTable: ValidExpandTarget<Occ, TargetTable>, callback?: (builder: QueryBuilder<TargetTable, keyof InferSchemaOutputFromFMTable<TargetTable>, false, false>) => QueryBuilder<TargetTable, any, any, any, any>): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands & {
105
+ expand<TargetTable extends FMTable<any, any>, TSelected extends keyof InferSchemaOutputFromFMTable<TargetTable> | Record<string, Column<any, any, ExtractTableName<TargetTable>>> = keyof InferSchemaOutputFromFMTable<TargetTable>, TNestedExpands extends ExpandedRelations = {}>(targetTable: ValidExpandTarget<Occ, TargetTable>, callback?: (builder: QueryBuilder<TargetTable, keyof InferSchemaOutputFromFMTable<TargetTable>, false, false, {}>) => QueryBuilder<TargetTable, TSelected, any, any, TNestedExpands>): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands & {
106
106
  [K in ExtractTableName<TargetTable>]: {
107
107
  schema: InferSchemaOutputFromFMTable<TargetTable>;
108
- selected: keyof InferSchemaOutputFromFMTable<TargetTable>;
108
+ selected: TSelected;
109
+ nested: TNestedExpands;
109
110
  };
110
111
  }>;
111
112
  single(): QueryBuilder<Occ, Selected, "exact", IsCount, Expands>;
@@ -1 +1 @@
1
- {"version":3,"file":"query-builder.js","sources":["../../../../src/client/query/query-builder.ts"],"sourcesContent":["import { QueryOptions } from \"odata-query\";\nimport buildQuery from \"odata-query\";\nimport type {\n ExecutionContext,\n ExecutableBuilder,\n Result,\n ExecuteOptions,\n ConditionallyWithODataAnnotations,\n ExtractSchemaFromOccurrence,\n ExecuteMethodOptions,\n} from \"../../types\";\nimport { RecordCountMismatchError } from \"../../errors\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport {\n transformFieldNamesArray,\n transformOrderByField,\n} from \"../../transform\";\nimport { safeJsonParse } from \"../sanitize-json\";\nimport { parseErrorResponse } from \"../error-parser\";\nimport { isColumn, type Column } from \"../../orm/column\";\nimport {\n FilterExpression,\n OrderByExpression,\n isOrderByExpression,\n} from \"../../orm/operators\";\nimport {\n FMTable,\n type InferSchemaOutputFromFMTable,\n type ValidExpandTarget,\n type ExtractTableName,\n type ValidateNoContainerFields,\n getTableName,\n} from \"../../orm/table\";\nimport {\n ExpandBuilder,\n type ExpandConfig,\n type ExpandedRelations,\n resolveTableId,\n mergeExecuteOptions,\n formatSelectFields,\n processQueryResponse,\n processSelectWithRenames,\n buildSelectExpandQueryString,\n createODataRequest,\n} from \"../builders/index\";\nimport { QueryUrlBuilder, type NavigationConfig } from \"./url-builder\";\nimport type { TypeSafeOrderBy, QueryReturnType } from \"./types\";\nimport { createLogger, InternalLogger } from \"../../logger\";\n\n// Re-export QueryReturnType for backward compatibility\nexport type { QueryReturnType };\n\n/**\n * Default maximum number of records to return in a list query.\n * This prevents stack overflow issues with large datasets while still\n * allowing substantial data retrieval. Users can override with .top().\n */\nconst DEFAULT_TOP = 1000;\n\nexport type { TypeSafeOrderBy, ExpandedRelations };\n\nexport class QueryBuilder<\n Occ extends FMTable<any, any>,\n Selected extends\n | keyof InferSchemaOutputFromFMTable<Occ>\n | Record<\n string,\n Column<any, any, ExtractTableName<Occ>>\n > = keyof InferSchemaOutputFromFMTable<Occ>,\n SingleMode extends \"exact\" | \"maybe\" | false = false,\n IsCount extends boolean = false,\n Expands extends ExpandedRelations = {},\n> implements\n ExecutableBuilder<\n QueryReturnType<\n InferSchemaOutputFromFMTable<Occ>,\n Selected,\n SingleMode,\n IsCount,\n Expands\n >\n >\n{\n private queryOptions: Partial<\n QueryOptions<InferSchemaOutputFromFMTable<Occ>>\n > = {};\n private expandConfigs: ExpandConfig[] = [];\n private singleMode: SingleMode = false as SingleMode;\n private isCountMode = false as IsCount;\n private occurrence: Occ;\n private databaseName: string;\n private context: ExecutionContext;\n private navigation?: NavigationConfig;\n private databaseUseEntityIds: boolean;\n private expandBuilder: ExpandBuilder;\n private urlBuilder: QueryUrlBuilder;\n // Mapping from field names to output keys (for renamed fields in select)\n private fieldMapping?: Record<string, string>;\n private logger: InternalLogger;\n\n constructor(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n databaseUseEntityIds?: boolean;\n }) {\n this.occurrence = config.occurrence;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.logger = config.context?._getLogger?.() ?? createLogger();\n this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;\n this.expandBuilder = new ExpandBuilder(\n this.databaseUseEntityIds,\n this.logger,\n );\n this.urlBuilder = new QueryUrlBuilder(\n this.databaseName,\n this.occurrence,\n this.context,\n );\n }\n\n /**\n * Helper to merge database-level useEntityIds with per-request options\n */\n private mergeExecuteOptions(\n options?: RequestInit & FFetchOptions & ExecuteOptions,\n ): RequestInit & FFetchOptions & { useEntityIds?: boolean } {\n return mergeExecuteOptions(options, this.databaseUseEntityIds);\n }\n\n /**\n * Gets the FMTable instance\n */\n private getTable(): FMTable<any, any> | undefined {\n return this.occurrence;\n }\n\n /**\n * Gets the table ID (FMTID) if using entity IDs, otherwise returns the table name\n * @param useEntityIds - Optional override for entity ID usage\n */\n private getTableIdOrName(useEntityIds?: boolean): string {\n return resolveTableId(\n this.occurrence,\n getTableName(this.occurrence),\n this.context,\n useEntityIds,\n );\n }\n\n /**\n * Creates a new QueryBuilder with modified configuration.\n * Used by single(), maybeSingle(), count(), and select() to create new instances.\n */\n private cloneWithChanges<\n NewSelected extends\n | keyof InferSchemaOutputFromFMTable<Occ>\n | Record<string, Column<any, any, ExtractTableName<Occ>>> = Selected,\n NewSingle extends \"exact\" | \"maybe\" | false = SingleMode,\n NewCount extends boolean = IsCount,\n >(changes: {\n selectedFields?: NewSelected;\n singleMode?: NewSingle;\n isCountMode?: NewCount;\n queryOptions?: Partial<QueryOptions<InferSchemaOutputFromFMTable<Occ>>>;\n fieldMapping?: Record<string, string>;\n }): QueryBuilder<Occ, NewSelected, NewSingle, NewCount, Expands> {\n const newBuilder = new QueryBuilder<\n Occ,\n NewSelected,\n NewSingle,\n NewCount,\n Expands\n >({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n newBuilder.queryOptions = {\n ...this.queryOptions,\n ...changes.queryOptions,\n };\n newBuilder.expandConfigs = [...this.expandConfigs];\n newBuilder.singleMode = (changes.singleMode ?? this.singleMode) as any;\n newBuilder.isCountMode = (changes.isCountMode ?? this.isCountMode) as any;\n newBuilder.fieldMapping = changes.fieldMapping ?? this.fieldMapping;\n // Copy navigation metadata\n newBuilder.navigation = this.navigation;\n newBuilder.urlBuilder = new QueryUrlBuilder(\n this.databaseName,\n this.occurrence,\n this.context,\n );\n return newBuilder;\n }\n\n /**\n * Select fields using column references.\n * Allows renaming fields by using different keys in the object.\n * Container fields cannot be selected and will cause a type error.\n *\n * @example\n * db.from(users).list().select({\n * name: users.name,\n * userEmail: users.email // renamed!\n * })\n *\n * @param fields - Object mapping output keys to column references (container fields excluded)\n * @returns QueryBuilder with updated selected fields\n */\n select<\n TSelect extends Record<\n string,\n Column<any, any, ExtractTableName<Occ>, false>\n >,\n >(fields: TSelect): QueryBuilder<Occ, TSelect, SingleMode, IsCount, Expands> {\n const tableName = getTableName(this.occurrence);\n const { selectedFields, fieldMapping } = processSelectWithRenames(\n fields,\n tableName,\n this.logger,\n );\n\n return this.cloneWithChanges({\n selectedFields: fields as any,\n queryOptions: {\n select: selectedFields,\n },\n fieldMapping:\n Object.keys(fieldMapping).length > 0 ? fieldMapping : undefined,\n });\n }\n\n /**\n * Filter results using operator expressions (new ORM-style API).\n * Supports eq, gt, lt, and, or, etc. operators with Column references.\n * Also supports raw OData filter strings as an escape hatch.\n *\n * @example\n * .where(eq(users.hobby, \"reading\"))\n * .where(and(eq(users.active, true), gt(users.age, 18)))\n * .where(\"status eq 'active'\") // Raw OData string escape hatch\n */\n where(\n expression: FilterExpression | string,\n ): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands> {\n // Handle raw string filters (escape hatch)\n if (typeof expression === \"string\") {\n this.queryOptions.filter = expression;\n return this;\n }\n // Convert FilterExpression to OData filter string\n const filterString = expression.toODataFilter(this.databaseUseEntityIds);\n this.queryOptions.filter = filterString;\n return this;\n }\n\n /**\n * Specify the sort order for query results.\n *\n * @example Single field (ascending by default)\n * ```ts\n * .orderBy(\"name\")\n * .orderBy(users.name) // Column reference\n * .orderBy(asc(users.name)) // Explicit ascending\n * ```\n *\n * @example Single field with explicit direction\n * ```ts\n * .orderBy([\"name\", \"desc\"])\n * .orderBy([users.name, \"desc\"]) // Column reference\n * .orderBy(desc(users.name)) // Explicit descending\n * ```\n *\n * @example Multiple fields with directions\n * ```ts\n * .orderBy([[\"name\", \"asc\"], [\"createdAt\", \"desc\"]])\n * .orderBy([[users.name, \"asc\"], [users.createdAt, \"desc\"]]) // Column references\n * .orderBy(users.name, desc(users.age)) // Variadic with helpers\n * ```\n */\n orderBy(\n ...orderByArgs:\n | [\n | TypeSafeOrderBy<InferSchemaOutputFromFMTable<Occ>>\n | Column<any, any, ExtractTableName<Occ>>\n | OrderByExpression<ExtractTableName<Occ>>,\n ]\n | [\n Column<any, any, ExtractTableName<Occ>>,\n ...Array<\n | Column<any, any, ExtractTableName<Occ>>\n | OrderByExpression<ExtractTableName<Occ>>\n >,\n ]\n ): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands> {\n const tableName = getTableName(this.occurrence);\n\n // Handle variadic arguments (multiple fields)\n if (orderByArgs.length > 1) {\n const orderByParts = orderByArgs.map((arg) => {\n if (isOrderByExpression(arg)) {\n // Validate table match\n if (arg.column.tableName !== tableName) {\n this.logger.warn(\n `Column ${arg.column.toString()} is from table \"${arg.column.tableName}\", but query is for table \"${tableName}\"`,\n );\n }\n const fieldName = arg.column.fieldName;\n const transformedField = this.occurrence\n ? transformOrderByField(fieldName, this.occurrence)\n : fieldName;\n return `${transformedField} ${arg.direction}`;\n } else if (isColumn(arg)) {\n // Validate table match\n if (arg.tableName !== tableName) {\n this.logger.warn(\n `Column ${arg.toString()} is from table \"${arg.tableName}\", but query is for table \"${tableName}\"`,\n );\n }\n const fieldName = arg.fieldName;\n const transformedField = this.occurrence\n ? transformOrderByField(fieldName, this.occurrence)\n : fieldName;\n return transformedField; // Default to ascending\n } else {\n throw new Error(\n \"Variadic orderBy() only accepts Column or OrderByExpression arguments\",\n );\n }\n });\n this.queryOptions.orderBy = orderByParts;\n return this;\n }\n\n // Handle single argument\n const orderBy = orderByArgs[0];\n\n // Handle OrderByExpression\n if (isOrderByExpression(orderBy)) {\n // Validate table match\n if (orderBy.column.tableName !== tableName) {\n this.logger.warn(\n `Column ${orderBy.column.toString()} is from table \"${orderBy.column.tableName}\", but query is for table \"${tableName}\"`,\n );\n }\n const fieldName = orderBy.column.fieldName;\n const transformedField = this.occurrence\n ? transformOrderByField(fieldName, this.occurrence)\n : fieldName;\n this.queryOptions.orderBy = `${transformedField} ${orderBy.direction}`;\n return this;\n }\n\n // Handle Column references\n if (isColumn(orderBy)) {\n // Validate table match\n if (orderBy.tableName !== tableName) {\n this.logger.warn(\n `Column ${orderBy.toString()} is from table \"${orderBy.tableName}\", but query is for table \"${tableName}\"`,\n );\n }\n // Single Column reference without direction (defaults to ascending)\n const fieldName = orderBy.fieldName;\n this.queryOptions.orderBy = this.occurrence\n ? transformOrderByField(fieldName, this.occurrence)\n : fieldName;\n return this;\n }\n // Transform field names to FMFIDs if using entity IDs\n if (this.occurrence && orderBy) {\n if (Array.isArray(orderBy)) {\n // Check if it's a single tuple [field, direction] or array of tuples\n if (\n orderBy.length === 2 &&\n (typeof orderBy[0] === \"string\" || isColumn(orderBy[0])) &&\n (orderBy[1] === \"asc\" || orderBy[1] === \"desc\")\n ) {\n // Single tuple: [field, direction] or [column, direction]\n const field = isColumn(orderBy[0])\n ? orderBy[0].fieldName\n : orderBy[0];\n const direction = orderBy[1] as \"asc\" | \"desc\";\n this.queryOptions.orderBy = `${transformOrderByField(field, this.occurrence)} ${direction}`;\n } else {\n // Array of tuples: [[field, dir], [field, dir], ...]\n this.queryOptions.orderBy = (\n orderBy as Array<[any, \"asc\" | \"desc\"]>\n ).map(([fieldOrCol, direction]) => {\n const field = isColumn(fieldOrCol)\n ? fieldOrCol.fieldName\n : String(fieldOrCol);\n const transformedField = transformOrderByField(\n field,\n this.occurrence!,\n );\n return `${transformedField} ${direction}`;\n });\n }\n } else {\n // Single field name (string)\n this.queryOptions.orderBy = transformOrderByField(\n String(orderBy),\n this.occurrence,\n );\n }\n } else {\n // No occurrence/baseTable - pass through as-is\n if (Array.isArray(orderBy)) {\n if (\n orderBy.length === 2 &&\n (typeof orderBy[0] === \"string\" || isColumn(orderBy[0])) &&\n (orderBy[1] === \"asc\" || orderBy[1] === \"desc\")\n ) {\n // Single tuple: [field, direction] or [column, direction]\n const field = isColumn(orderBy[0])\n ? orderBy[0].fieldName\n : orderBy[0];\n const direction = orderBy[1] as \"asc\" | \"desc\";\n this.queryOptions.orderBy = `${field} ${direction}`;\n } else {\n // Array of tuples\n this.queryOptions.orderBy = (\n orderBy as Array<[any, \"asc\" | \"desc\"]>\n ).map(([fieldOrCol, direction]) => {\n const field = isColumn(fieldOrCol)\n ? fieldOrCol.fieldName\n : String(fieldOrCol);\n return `${field} ${direction}`;\n });\n }\n } else {\n this.queryOptions.orderBy = orderBy;\n }\n }\n return this;\n }\n\n top(\n count: number,\n ): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands> {\n this.queryOptions.top = count;\n return this;\n }\n\n skip(\n count: number,\n ): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands> {\n this.queryOptions.skip = count;\n return this;\n }\n\n expand<TargetTable extends FMTable<any, any>>(\n targetTable: ValidExpandTarget<Occ, TargetTable>,\n callback?: (\n builder: QueryBuilder<\n TargetTable,\n keyof InferSchemaOutputFromFMTable<TargetTable>,\n false,\n false\n >,\n ) => QueryBuilder<TargetTable, any, any, any, any>,\n ): QueryBuilder<\n Occ,\n Selected,\n SingleMode,\n IsCount,\n Expands & {\n [K in ExtractTableName<TargetTable>]: {\n schema: InferSchemaOutputFromFMTable<TargetTable>;\n selected: keyof InferSchemaOutputFromFMTable<TargetTable>;\n };\n }\n > {\n // Use ExpandBuilder.processExpand to handle the expand logic\n type TargetBuilder = QueryBuilder<\n TargetTable,\n keyof InferSchemaOutputFromFMTable<TargetTable>,\n false,\n false\n >;\n const expandConfig = this.expandBuilder.processExpand<\n TargetTable,\n TargetBuilder\n >(\n targetTable,\n this.occurrence,\n callback,\n () =>\n new QueryBuilder<TargetTable>({\n occurrence: targetTable,\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n }),\n );\n\n this.expandConfigs.push(expandConfig);\n return this as any;\n }\n\n single(): QueryBuilder<Occ, Selected, \"exact\", IsCount, Expands> {\n return this.cloneWithChanges({ singleMode: \"exact\" as const });\n }\n\n maybeSingle(): QueryBuilder<Occ, Selected, \"maybe\", IsCount, Expands> {\n return this.cloneWithChanges({ singleMode: \"maybe\" as const });\n }\n\n count(): QueryBuilder<Occ, Selected, SingleMode, true, Expands> {\n return this.cloneWithChanges({\n isCountMode: true as const,\n queryOptions: { count: true },\n });\n }\n\n /**\n * Builds the OData query string from current query options and expand configs.\n */\n private buildQueryString(): string {\n // Build query without expand and select (we'll add them manually if using entity IDs)\n const queryOptionsWithoutExpandAndSelect = { ...this.queryOptions };\n const originalSelect = queryOptionsWithoutExpandAndSelect.select;\n delete queryOptionsWithoutExpandAndSelect.expand;\n delete queryOptionsWithoutExpandAndSelect.select;\n\n let queryString = buildQuery(queryOptionsWithoutExpandAndSelect);\n\n // Use shared helper for select/expand portion\n const selectArray = originalSelect\n ? Array.isArray(originalSelect)\n ? originalSelect.map(String)\n : [String(originalSelect)]\n : undefined;\n\n const selectExpandString = buildSelectExpandQueryString({\n selectedFields: selectArray,\n expandConfigs: this.expandConfigs,\n table: this.occurrence,\n useEntityIds: this.databaseUseEntityIds,\n logger: this.logger,\n });\n\n // Append select/expand to existing query string\n if (selectExpandString) {\n // Strip leading ? from helper result and append with appropriate separator\n const params = selectExpandString.startsWith(\"?\")\n ? selectExpandString.slice(1)\n : selectExpandString;\n const separator = queryString.includes(\"?\") ? \"&\" : \"?\";\n queryString = `${queryString}${separator}${params}`;\n }\n\n return queryString;\n }\n\n async execute<EO extends ExecuteOptions>(\n options?: ExecuteMethodOptions<EO>,\n ): Promise<\n Result<\n ConditionallyWithODataAnnotations<\n QueryReturnType<\n InferSchemaOutputFromFMTable<Occ>,\n Selected,\n SingleMode,\n IsCount,\n Expands\n >,\n EO[\"includeODataAnnotations\"] extends true ? true : false\n >\n >\n > {\n const mergedOptions = this.mergeExecuteOptions(options);\n const queryString = this.buildQueryString();\n\n // Handle $count endpoint\n if (this.isCountMode) {\n const url = this.urlBuilder.build(queryString, {\n isCount: true,\n useEntityIds: mergedOptions.useEntityIds,\n navigation: this.navigation,\n });\n const result = await this.context._makeRequest(url, mergedOptions);\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n // OData returns count as a string, convert to number\n const count =\n typeof result.data === \"string\" ? Number(result.data) : result.data;\n return { data: count as number, error: undefined } as any;\n }\n\n const url = this.urlBuilder.build(queryString, {\n isCount: this.isCountMode,\n useEntityIds: mergedOptions.useEntityIds,\n navigation: this.navigation,\n });\n\n const result = await this.context._makeRequest(url, mergedOptions);\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n return processQueryResponse(result.data, {\n occurrence: this.occurrence,\n singleMode: this.singleMode,\n queryOptions: this.queryOptions as any,\n expandConfigs: this.expandConfigs,\n skipValidation: options?.skipValidation,\n useEntityIds: mergedOptions.useEntityIds,\n fieldMapping: this.fieldMapping,\n logger: this.logger,\n });\n }\n\n getQueryString(): string {\n const queryString = this.buildQueryString();\n return this.urlBuilder.buildPath(queryString, {\n useEntityIds: this.databaseUseEntityIds,\n navigation: this.navigation,\n });\n }\n\n getRequestConfig(): { method: string; url: string; body?: any } {\n const queryString = this.buildQueryString();\n const url = this.urlBuilder.build(queryString, {\n isCount: this.isCountMode,\n useEntityIds: this.databaseUseEntityIds,\n navigation: this.navigation,\n });\n\n return {\n method: \"GET\",\n url,\n };\n }\n\n toRequest(baseUrl: string, options?: ExecuteOptions): Request {\n const config = this.getRequestConfig();\n return createODataRequest(baseUrl, config, options);\n }\n\n async processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<\n Result<\n QueryReturnType<\n InferSchemaOutputFromFMTable<Occ>,\n Selected,\n SingleMode,\n IsCount,\n Expands\n >\n >\n > {\n // Check for error responses (important for batch operations)\n if (!response.ok) {\n const error = await parseErrorResponse(\n response,\n response.url ||\n `/${this.databaseName}/${getTableName(this.occurrence)}`,\n );\n return { data: undefined, error };\n }\n\n // Handle 204 No Content (shouldn't happen for queries, but handle it gracefully)\n if (response.status === 204) {\n // Return empty list for list queries, null for single queries\n if (this.singleMode !== false) {\n if (this.singleMode === \"maybe\") {\n return { data: null as any, error: undefined };\n }\n return {\n data: undefined,\n error: new RecordCountMismatchError(\"one\", 0),\n };\n }\n return { data: [] as any, error: undefined };\n }\n\n // Parse the response body (using safeJsonParse to handle FileMaker's invalid JSON with unquoted ? values)\n let rawData;\n try {\n rawData = await safeJsonParse(response);\n } catch (err) {\n // Check if it's an empty body error (common with 204 responses)\n if (err instanceof SyntaxError && response.status === 204) {\n // Handled above, but just in case\n return { data: [] as any, error: undefined };\n }\n return {\n data: undefined,\n error: {\n name: \"ResponseParseError\",\n message: `Failed to parse response JSON: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n timestamp: new Date(),\n } as any,\n };\n }\n\n if (!rawData) {\n return {\n data: undefined,\n error: {\n name: \"ResponseError\",\n message: \"Response body was empty or null\",\n timestamp: new Date(),\n } as any,\n };\n }\n\n const mergedOptions = this.mergeExecuteOptions(options);\n return processQueryResponse(rawData, {\n occurrence: this.occurrence,\n singleMode: this.singleMode,\n queryOptions: this.queryOptions as any,\n expandConfigs: this.expandConfigs,\n skipValidation: options?.skipValidation,\n useEntityIds: mergedOptions.useEntityIds,\n fieldMapping: this.fieldMapping,\n logger: this.logger,\n });\n }\n}\n"],"names":["url","result"],"mappings":"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"}
1
+ {"version":3,"file":"query-builder.js","sources":["../../../../src/client/query/query-builder.ts"],"sourcesContent":["import { QueryOptions } from \"odata-query\";\nimport buildQuery from \"odata-query\";\nimport type {\n ExecutionContext,\n ExecutableBuilder,\n Result,\n ExecuteOptions,\n ConditionallyWithODataAnnotations,\n ExtractSchemaFromOccurrence,\n ExecuteMethodOptions,\n} from \"../../types\";\nimport { RecordCountMismatchError } from \"../../errors\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport {\n transformFieldNamesArray,\n transformOrderByField,\n} from \"../../transform\";\nimport { safeJsonParse } from \"../sanitize-json\";\nimport { parseErrorResponse } from \"../error-parser\";\nimport { isColumn, type Column } from \"../../orm/column\";\nimport {\n FilterExpression,\n OrderByExpression,\n isOrderByExpression,\n} from \"../../orm/operators\";\nimport {\n FMTable,\n type InferSchemaOutputFromFMTable,\n type ValidExpandTarget,\n type ExtractTableName,\n type ValidateNoContainerFields,\n getTableName,\n} from \"../../orm/table\";\nimport {\n ExpandBuilder,\n type ExpandConfig,\n type ExpandedRelations,\n resolveTableId,\n mergeExecuteOptions,\n formatSelectFields,\n processQueryResponse,\n processSelectWithRenames,\n buildSelectExpandQueryString,\n createODataRequest,\n} from \"../builders/index\";\nimport { QueryUrlBuilder, type NavigationConfig } from \"./url-builder\";\nimport type { TypeSafeOrderBy, QueryReturnType } from \"./types\";\nimport { createLogger, InternalLogger } from \"../../logger\";\n\n// Re-export QueryReturnType for backward compatibility\nexport type { QueryReturnType };\n\n/**\n * Default maximum number of records to return in a list query.\n * This prevents stack overflow issues with large datasets while still\n * allowing substantial data retrieval. Users can override with .top().\n */\nconst DEFAULT_TOP = 1000;\n\nexport type { TypeSafeOrderBy, ExpandedRelations };\n\nexport class QueryBuilder<\n Occ extends FMTable<any, any>,\n Selected extends\n | keyof InferSchemaOutputFromFMTable<Occ>\n | Record<\n string,\n Column<any, any, ExtractTableName<Occ>>\n > = keyof InferSchemaOutputFromFMTable<Occ>,\n SingleMode extends \"exact\" | \"maybe\" | false = false,\n IsCount extends boolean = false,\n Expands extends ExpandedRelations = {},\n> implements\n ExecutableBuilder<\n QueryReturnType<\n InferSchemaOutputFromFMTable<Occ>,\n Selected,\n SingleMode,\n IsCount,\n Expands\n >\n >\n{\n private queryOptions: Partial<\n QueryOptions<InferSchemaOutputFromFMTable<Occ>>\n > = {};\n private expandConfigs: ExpandConfig[] = [];\n private singleMode: SingleMode = false as SingleMode;\n private isCountMode = false as IsCount;\n private occurrence: Occ;\n private databaseName: string;\n private context: ExecutionContext;\n private navigation?: NavigationConfig;\n private databaseUseEntityIds: boolean;\n private expandBuilder: ExpandBuilder;\n private urlBuilder: QueryUrlBuilder;\n // Mapping from field names to output keys (for renamed fields in select)\n private fieldMapping?: Record<string, string>;\n private logger: InternalLogger;\n\n constructor(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n databaseUseEntityIds?: boolean;\n }) {\n this.occurrence = config.occurrence;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.logger = config.context?._getLogger?.() ?? createLogger();\n this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;\n this.expandBuilder = new ExpandBuilder(\n this.databaseUseEntityIds,\n this.logger,\n );\n this.urlBuilder = new QueryUrlBuilder(\n this.databaseName,\n this.occurrence,\n this.context,\n );\n }\n\n /**\n * Helper to merge database-level useEntityIds with per-request options\n */\n private mergeExecuteOptions(\n options?: RequestInit & FFetchOptions & ExecuteOptions,\n ): RequestInit & FFetchOptions & { useEntityIds?: boolean } {\n return mergeExecuteOptions(options, this.databaseUseEntityIds);\n }\n\n /**\n * Gets the FMTable instance\n */\n private getTable(): FMTable<any, any> | undefined {\n return this.occurrence;\n }\n\n /**\n * Gets the table ID (FMTID) if using entity IDs, otherwise returns the table name\n * @param useEntityIds - Optional override for entity ID usage\n */\n private getTableIdOrName(useEntityIds?: boolean): string {\n return resolveTableId(\n this.occurrence,\n getTableName(this.occurrence),\n this.context,\n useEntityIds,\n );\n }\n\n /**\n * Creates a new QueryBuilder with modified configuration.\n * Used by single(), maybeSingle(), count(), and select() to create new instances.\n */\n private cloneWithChanges<\n NewSelected extends\n | keyof InferSchemaOutputFromFMTable<Occ>\n | Record<string, Column<any, any, ExtractTableName<Occ>>> = Selected,\n NewSingle extends \"exact\" | \"maybe\" | false = SingleMode,\n NewCount extends boolean = IsCount,\n >(changes: {\n selectedFields?: NewSelected;\n singleMode?: NewSingle;\n isCountMode?: NewCount;\n queryOptions?: Partial<QueryOptions<InferSchemaOutputFromFMTable<Occ>>>;\n fieldMapping?: Record<string, string>;\n }): QueryBuilder<Occ, NewSelected, NewSingle, NewCount, Expands> {\n const newBuilder = new QueryBuilder<\n Occ,\n NewSelected,\n NewSingle,\n NewCount,\n Expands\n >({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n newBuilder.queryOptions = {\n ...this.queryOptions,\n ...changes.queryOptions,\n };\n newBuilder.expandConfigs = [...this.expandConfigs];\n newBuilder.singleMode = (changes.singleMode ?? this.singleMode) as any;\n newBuilder.isCountMode = (changes.isCountMode ?? this.isCountMode) as any;\n newBuilder.fieldMapping = changes.fieldMapping ?? this.fieldMapping;\n // Copy navigation metadata\n newBuilder.navigation = this.navigation;\n newBuilder.urlBuilder = new QueryUrlBuilder(\n this.databaseName,\n this.occurrence,\n this.context,\n );\n return newBuilder;\n }\n\n /**\n * Select fields using column references.\n * Allows renaming fields by using different keys in the object.\n * Container fields cannot be selected and will cause a type error.\n *\n * @example\n * db.from(users).list().select({\n * name: users.name,\n * userEmail: users.email // renamed!\n * })\n *\n * @param fields - Object mapping output keys to column references (container fields excluded)\n * @returns QueryBuilder with updated selected fields\n */\n select<\n TSelect extends Record<\n string,\n Column<any, any, ExtractTableName<Occ>, false>\n >,\n >(fields: TSelect): QueryBuilder<Occ, TSelect, SingleMode, IsCount, Expands> {\n const tableName = getTableName(this.occurrence);\n const { selectedFields, fieldMapping } = processSelectWithRenames(\n fields,\n tableName,\n this.logger,\n );\n\n return this.cloneWithChanges({\n selectedFields: fields as any,\n queryOptions: {\n select: selectedFields,\n },\n fieldMapping:\n Object.keys(fieldMapping).length > 0 ? fieldMapping : undefined,\n });\n }\n\n /**\n * Filter results using operator expressions (new ORM-style API).\n * Supports eq, gt, lt, and, or, etc. operators with Column references.\n * Also supports raw OData filter strings as an escape hatch.\n *\n * @example\n * .where(eq(users.hobby, \"reading\"))\n * .where(and(eq(users.active, true), gt(users.age, 18)))\n * .where(\"status eq 'active'\") // Raw OData string escape hatch\n */\n where(\n expression: FilterExpression | string,\n ): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands> {\n // Handle raw string filters (escape hatch)\n if (typeof expression === \"string\") {\n this.queryOptions.filter = expression;\n return this;\n }\n // Convert FilterExpression to OData filter string\n const filterString = expression.toODataFilter(this.databaseUseEntityIds);\n this.queryOptions.filter = filterString;\n return this;\n }\n\n /**\n * Specify the sort order for query results.\n *\n * @example Single field (ascending by default)\n * ```ts\n * .orderBy(\"name\")\n * .orderBy(users.name) // Column reference\n * .orderBy(asc(users.name)) // Explicit ascending\n * ```\n *\n * @example Single field with explicit direction\n * ```ts\n * .orderBy([\"name\", \"desc\"])\n * .orderBy([users.name, \"desc\"]) // Column reference\n * .orderBy(desc(users.name)) // Explicit descending\n * ```\n *\n * @example Multiple fields with directions\n * ```ts\n * .orderBy([[\"name\", \"asc\"], [\"createdAt\", \"desc\"]])\n * .orderBy([[users.name, \"asc\"], [users.createdAt, \"desc\"]]) // Column references\n * .orderBy(users.name, desc(users.age)) // Variadic with helpers\n * ```\n */\n orderBy(\n ...orderByArgs:\n | [\n | TypeSafeOrderBy<InferSchemaOutputFromFMTable<Occ>>\n | Column<any, any, ExtractTableName<Occ>>\n | OrderByExpression<ExtractTableName<Occ>>,\n ]\n | [\n Column<any, any, ExtractTableName<Occ>>,\n ...Array<\n | Column<any, any, ExtractTableName<Occ>>\n | OrderByExpression<ExtractTableName<Occ>>\n >,\n ]\n ): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands> {\n const tableName = getTableName(this.occurrence);\n\n // Handle variadic arguments (multiple fields)\n if (orderByArgs.length > 1) {\n const orderByParts = orderByArgs.map((arg) => {\n if (isOrderByExpression(arg)) {\n // Validate table match\n if (arg.column.tableName !== tableName) {\n this.logger.warn(\n `Column ${arg.column.toString()} is from table \"${arg.column.tableName}\", but query is for table \"${tableName}\"`,\n );\n }\n const fieldName = arg.column.fieldName;\n const transformedField = this.occurrence\n ? transformOrderByField(fieldName, this.occurrence)\n : fieldName;\n return `${transformedField} ${arg.direction}`;\n } else if (isColumn(arg)) {\n // Validate table match\n if (arg.tableName !== tableName) {\n this.logger.warn(\n `Column ${arg.toString()} is from table \"${arg.tableName}\", but query is for table \"${tableName}\"`,\n );\n }\n const fieldName = arg.fieldName;\n const transformedField = this.occurrence\n ? transformOrderByField(fieldName, this.occurrence)\n : fieldName;\n return transformedField; // Default to ascending\n } else {\n throw new Error(\n \"Variadic orderBy() only accepts Column or OrderByExpression arguments\",\n );\n }\n });\n this.queryOptions.orderBy = orderByParts;\n return this;\n }\n\n // Handle single argument\n const orderBy = orderByArgs[0];\n\n // Handle OrderByExpression\n if (isOrderByExpression(orderBy)) {\n // Validate table match\n if (orderBy.column.tableName !== tableName) {\n this.logger.warn(\n `Column ${orderBy.column.toString()} is from table \"${orderBy.column.tableName}\", but query is for table \"${tableName}\"`,\n );\n }\n const fieldName = orderBy.column.fieldName;\n const transformedField = this.occurrence\n ? transformOrderByField(fieldName, this.occurrence)\n : fieldName;\n this.queryOptions.orderBy = `${transformedField} ${orderBy.direction}`;\n return this;\n }\n\n // Handle Column references\n if (isColumn(orderBy)) {\n // Validate table match\n if (orderBy.tableName !== tableName) {\n this.logger.warn(\n `Column ${orderBy.toString()} is from table \"${orderBy.tableName}\", but query is for table \"${tableName}\"`,\n );\n }\n // Single Column reference without direction (defaults to ascending)\n const fieldName = orderBy.fieldName;\n this.queryOptions.orderBy = this.occurrence\n ? transformOrderByField(fieldName, this.occurrence)\n : fieldName;\n return this;\n }\n // Transform field names to FMFIDs if using entity IDs\n if (this.occurrence && orderBy) {\n if (Array.isArray(orderBy)) {\n // Check if it's a single tuple [field, direction] or array of tuples\n if (\n orderBy.length === 2 &&\n (typeof orderBy[0] === \"string\" || isColumn(orderBy[0])) &&\n (orderBy[1] === \"asc\" || orderBy[1] === \"desc\")\n ) {\n // Single tuple: [field, direction] or [column, direction]\n const field = isColumn(orderBy[0])\n ? orderBy[0].fieldName\n : orderBy[0];\n const direction = orderBy[1] as \"asc\" | \"desc\";\n this.queryOptions.orderBy = `${transformOrderByField(field, this.occurrence)} ${direction}`;\n } else {\n // Array of tuples: [[field, dir], [field, dir], ...]\n this.queryOptions.orderBy = (\n orderBy as Array<[any, \"asc\" | \"desc\"]>\n ).map(([fieldOrCol, direction]) => {\n const field = isColumn(fieldOrCol)\n ? fieldOrCol.fieldName\n : String(fieldOrCol);\n const transformedField = transformOrderByField(\n field,\n this.occurrence!,\n );\n return `${transformedField} ${direction}`;\n });\n }\n } else {\n // Single field name (string)\n this.queryOptions.orderBy = transformOrderByField(\n String(orderBy),\n this.occurrence,\n );\n }\n } else {\n // No occurrence/baseTable - pass through as-is\n if (Array.isArray(orderBy)) {\n if (\n orderBy.length === 2 &&\n (typeof orderBy[0] === \"string\" || isColumn(orderBy[0])) &&\n (orderBy[1] === \"asc\" || orderBy[1] === \"desc\")\n ) {\n // Single tuple: [field, direction] or [column, direction]\n const field = isColumn(orderBy[0])\n ? orderBy[0].fieldName\n : orderBy[0];\n const direction = orderBy[1] as \"asc\" | \"desc\";\n this.queryOptions.orderBy = `${field} ${direction}`;\n } else {\n // Array of tuples\n this.queryOptions.orderBy = (\n orderBy as Array<[any, \"asc\" | \"desc\"]>\n ).map(([fieldOrCol, direction]) => {\n const field = isColumn(fieldOrCol)\n ? fieldOrCol.fieldName\n : String(fieldOrCol);\n return `${field} ${direction}`;\n });\n }\n } else {\n this.queryOptions.orderBy = orderBy;\n }\n }\n return this;\n }\n\n top(\n count: number,\n ): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands> {\n this.queryOptions.top = count;\n return this;\n }\n\n skip(\n count: number,\n ): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands> {\n this.queryOptions.skip = count;\n return this;\n }\n\n expand<\n TargetTable extends FMTable<any, any>,\n TSelected extends\n | keyof InferSchemaOutputFromFMTable<TargetTable>\n | Record<\n string,\n Column<any, any, ExtractTableName<TargetTable>>\n > = keyof InferSchemaOutputFromFMTable<TargetTable>,\n TNestedExpands extends ExpandedRelations = {},\n >(\n targetTable: ValidExpandTarget<Occ, TargetTable>,\n callback?: (\n builder: QueryBuilder<\n TargetTable,\n keyof InferSchemaOutputFromFMTable<TargetTable>,\n false,\n false,\n {}\n >,\n ) => QueryBuilder<TargetTable, TSelected, any, any, TNestedExpands>,\n ): QueryBuilder<\n Occ,\n Selected,\n SingleMode,\n IsCount,\n Expands & {\n [K in ExtractTableName<TargetTable>]: {\n schema: InferSchemaOutputFromFMTable<TargetTable>;\n selected: TSelected;\n nested: TNestedExpands;\n };\n }\n > {\n // Use ExpandBuilder.processExpand to handle the expand logic\n type TargetBuilder = QueryBuilder<\n TargetTable,\n keyof InferSchemaOutputFromFMTable<TargetTable>,\n false,\n false,\n {}\n >;\n const expandConfig = this.expandBuilder.processExpand<\n TargetTable,\n TargetBuilder\n >(\n targetTable,\n this.occurrence,\n callback as ((builder: TargetBuilder) => TargetBuilder) | undefined,\n () =>\n new QueryBuilder<TargetTable>({\n occurrence: targetTable,\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n }),\n );\n\n this.expandConfigs.push(expandConfig);\n return this as any;\n }\n\n single(): QueryBuilder<Occ, Selected, \"exact\", IsCount, Expands> {\n return this.cloneWithChanges({ singleMode: \"exact\" as const });\n }\n\n maybeSingle(): QueryBuilder<Occ, Selected, \"maybe\", IsCount, Expands> {\n return this.cloneWithChanges({ singleMode: \"maybe\" as const });\n }\n\n count(): QueryBuilder<Occ, Selected, SingleMode, true, Expands> {\n return this.cloneWithChanges({\n isCountMode: true as const,\n queryOptions: { count: true },\n });\n }\n\n /**\n * Builds the OData query string from current query options and expand configs.\n */\n private buildQueryString(): string {\n // Build query without expand and select (we'll add them manually if using entity IDs)\n const queryOptionsWithoutExpandAndSelect = { ...this.queryOptions };\n const originalSelect = queryOptionsWithoutExpandAndSelect.select;\n delete queryOptionsWithoutExpandAndSelect.expand;\n delete queryOptionsWithoutExpandAndSelect.select;\n\n let queryString = buildQuery(queryOptionsWithoutExpandAndSelect);\n\n // Use shared helper for select/expand portion\n const selectArray = originalSelect\n ? Array.isArray(originalSelect)\n ? originalSelect.map(String)\n : [String(originalSelect)]\n : undefined;\n\n const selectExpandString = buildSelectExpandQueryString({\n selectedFields: selectArray,\n expandConfigs: this.expandConfigs,\n table: this.occurrence,\n useEntityIds: this.databaseUseEntityIds,\n logger: this.logger,\n });\n\n // Append select/expand to existing query string\n if (selectExpandString) {\n // Strip leading ? from helper result and append with appropriate separator\n const params = selectExpandString.startsWith(\"?\")\n ? selectExpandString.slice(1)\n : selectExpandString;\n const separator = queryString.includes(\"?\") ? \"&\" : \"?\";\n queryString = `${queryString}${separator}${params}`;\n }\n\n return queryString;\n }\n\n async execute<EO extends ExecuteOptions>(\n options?: ExecuteMethodOptions<EO>,\n ): Promise<\n Result<\n ConditionallyWithODataAnnotations<\n QueryReturnType<\n InferSchemaOutputFromFMTable<Occ>,\n Selected,\n SingleMode,\n IsCount,\n Expands\n >,\n EO[\"includeODataAnnotations\"] extends true ? true : false\n >\n >\n > {\n const mergedOptions = this.mergeExecuteOptions(options);\n const queryString = this.buildQueryString();\n\n // Handle $count endpoint\n if (this.isCountMode) {\n const url = this.urlBuilder.build(queryString, {\n isCount: true,\n useEntityIds: mergedOptions.useEntityIds,\n navigation: this.navigation,\n });\n const result = await this.context._makeRequest(url, mergedOptions);\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n // OData returns count as a string, convert to number\n const count =\n typeof result.data === \"string\" ? Number(result.data) : result.data;\n return { data: count as number, error: undefined } as any;\n }\n\n const url = this.urlBuilder.build(queryString, {\n isCount: this.isCountMode,\n useEntityIds: mergedOptions.useEntityIds,\n navigation: this.navigation,\n });\n\n const result = await this.context._makeRequest(url, mergedOptions);\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n return processQueryResponse(result.data, {\n occurrence: this.occurrence,\n singleMode: this.singleMode,\n queryOptions: this.queryOptions as any,\n expandConfigs: this.expandConfigs,\n skipValidation: options?.skipValidation,\n useEntityIds: mergedOptions.useEntityIds,\n fieldMapping: this.fieldMapping,\n logger: this.logger,\n });\n }\n\n getQueryString(): string {\n const queryString = this.buildQueryString();\n return this.urlBuilder.buildPath(queryString, {\n useEntityIds: this.databaseUseEntityIds,\n navigation: this.navigation,\n });\n }\n\n getRequestConfig(): { method: string; url: string; body?: any } {\n const queryString = this.buildQueryString();\n const url = this.urlBuilder.build(queryString, {\n isCount: this.isCountMode,\n useEntityIds: this.databaseUseEntityIds,\n navigation: this.navigation,\n });\n\n return {\n method: \"GET\",\n url,\n };\n }\n\n toRequest(baseUrl: string, options?: ExecuteOptions): Request {\n const config = this.getRequestConfig();\n return createODataRequest(baseUrl, config, options);\n }\n\n async processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<\n Result<\n QueryReturnType<\n InferSchemaOutputFromFMTable<Occ>,\n Selected,\n SingleMode,\n IsCount,\n Expands\n >\n >\n > {\n // Check for error responses (important for batch operations)\n if (!response.ok) {\n const error = await parseErrorResponse(\n response,\n response.url ||\n `/${this.databaseName}/${getTableName(this.occurrence)}`,\n );\n return { data: undefined, error };\n }\n\n // Handle 204 No Content (shouldn't happen for queries, but handle it gracefully)\n if (response.status === 204) {\n // Return empty list for list queries, null for single queries\n if (this.singleMode !== false) {\n if (this.singleMode === \"maybe\") {\n return { data: null as any, error: undefined };\n }\n return {\n data: undefined,\n error: new RecordCountMismatchError(\"one\", 0),\n };\n }\n return { data: [] as any, error: undefined };\n }\n\n // Parse the response body (using safeJsonParse to handle FileMaker's invalid JSON with unquoted ? values)\n let rawData;\n try {\n rawData = await safeJsonParse(response);\n } catch (err) {\n // Check if it's an empty body error (common with 204 responses)\n if (err instanceof SyntaxError && response.status === 204) {\n // Handled above, but just in case\n return { data: [] as any, error: undefined };\n }\n return {\n data: undefined,\n error: {\n name: \"ResponseParseError\",\n message: `Failed to parse response JSON: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n timestamp: new Date(),\n } as any,\n };\n }\n\n if (!rawData) {\n return {\n data: undefined,\n error: {\n name: \"ResponseError\",\n message: \"Response body was empty or null\",\n timestamp: new Date(),\n } as any,\n };\n }\n\n const mergedOptions = this.mergeExecuteOptions(options);\n return processQueryResponse(rawData, {\n occurrence: this.occurrence,\n singleMode: this.singleMode,\n queryOptions: this.queryOptions as any,\n expandConfigs: this.expandConfigs,\n skipValidation: options?.skipValidation,\n useEntityIds: mergedOptions.useEntityIds,\n fieldMapping: this.fieldMapping,\n logger: this.logger,\n });\n }\n}\n"],"names":["url","result"],"mappings":"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"}
@@ -19,6 +19,7 @@ export type ExpandConfig = {
19
19
  export type ExpandedRelations = Record<string, {
20
20
  schema: any;
21
21
  selected: any;
22
+ nested?: ExpandedRelations;
22
23
  }>;
23
24
  /**
24
25
  * Extract the value type from a Column.
@@ -32,21 +33,24 @@ type ExtractColumnType<C> = C extends Column<infer T, any, any, any> ? T : never
32
33
  type MapSelectToReturnType<TSelect extends Record<string, Column<any, any, any, any>>, TSchema extends Record<string, any>> = {
33
34
  [K in keyof TSelect]: ExtractColumnType<TSelect[K]>;
34
35
  };
36
+ /**
37
+ * Helper: Resolve a single expand's return type, including nested expands
38
+ */
39
+ export type ResolveExpandType<Exp extends {
40
+ schema: any;
41
+ selected: any;
42
+ nested?: ExpandedRelations;
43
+ }> = // Handle the selected fields
44
+ (Exp["selected"] extends Record<string, Column<any, any, any, any>> ? MapSelectToReturnType<Exp["selected"], Exp["schema"]> : Exp["selected"] extends keyof Exp["schema"] ? Pick<Exp["schema"], Exp["selected"]> : Exp["schema"]) & (Exp["nested"] extends ExpandedRelations ? ResolveExpandedRelations<Exp["nested"]> : {});
45
+ /**
46
+ * Helper: Resolve all expanded relations recursively
47
+ */
48
+ export type ResolveExpandedRelations<Exps extends ExpandedRelations> = {
49
+ [K in keyof Exps]: ResolveExpandType<Exps[K]>[];
50
+ };
35
51
  export type QueryReturnType<T extends Record<string, any>, Selected extends keyof T | Record<string, Column<any, any, any, any>>, SingleMode extends "exact" | "maybe" | false, IsCount extends boolean, Expands extends ExpandedRelations> = IsCount extends true ? number : [
36
52
  Selected
37
- ] extends [Record<string, Column<any, any, any, any>>] ? SingleMode extends "exact" ? MapSelectToReturnType<Selected, T> & {
38
- [K in keyof Expands]: Pick<Expands[K]["schema"], Expands[K]["selected"]>[];
39
- } : SingleMode extends "maybe" ? (MapSelectToReturnType<Selected, T> & {
40
- [K in keyof Expands]: Pick<Expands[K]["schema"], Expands[K]["selected"]>[];
41
- }) | null : (MapSelectToReturnType<Selected, T> & {
42
- [K in keyof Expands]: Pick<Expands[K]["schema"], Expands[K]["selected"]>[];
43
- })[] : [
53
+ ] extends [Record<string, Column<any, any, any, any>>] ? SingleMode extends "exact" ? MapSelectToReturnType<Selected, T> & ResolveExpandedRelations<Expands> : SingleMode extends "maybe" ? (MapSelectToReturnType<Selected, T> & ResolveExpandedRelations<Expands>) | null : (MapSelectToReturnType<Selected, T> & ResolveExpandedRelations<Expands>)[] : [
44
54
  Selected
45
- ] extends [keyof T] ? SingleMode extends "exact" ? Pick<T, Selected> & {
46
- [K in keyof Expands]: Pick<Expands[K]["schema"], Expands[K]["selected"]>[];
47
- } : SingleMode extends "maybe" ? (Pick<T, Selected> & {
48
- [K in keyof Expands]: Pick<Expands[K]["schema"], Expands[K]["selected"]>[];
49
- }) | null : (Pick<T, Selected> & {
50
- [K in keyof Expands]: Pick<Expands[K]["schema"], Expands[K]["selected"]>[];
51
- })[] : never;
55
+ ] extends [keyof T] ? SingleMode extends "exact" ? Pick<T, Selected> & ResolveExpandedRelations<Expands> : SingleMode extends "maybe" ? (Pick<T, Selected> & ResolveExpandedRelations<Expands>) | null : (Pick<T, Selected> & ResolveExpandedRelations<Expands>)[] : never;
52
56
  export {};
@@ -3,6 +3,7 @@ import { FMTable, InferSchemaOutputFromFMTable, ValidExpandTarget, ExtractTableN
3
3
  import { QueryBuilder } from './query-builder.js';
4
4
  import { Column } from '../orm/column.js';
5
5
  import { ExpandedRelations } from './builders/index.js';
6
+ import { ResolveExpandedRelations } from './query/types.js';
6
7
  /**
7
8
  * Extract the value type from a Column.
8
9
  * This uses the phantom type stored in Column to get the actual value type.
@@ -17,13 +18,9 @@ type MapSelectToReturnType<TSelect extends Record<string, Column<any, any, any,
17
18
  };
18
19
  export type RecordReturnType<Schema extends Record<string, any>, IsSingleField extends boolean, FieldColumn extends Column<any, any, any, any> | undefined, Selected extends keyof Schema | Record<string, Column<any, any, ExtractTableName<FMTable<any, any>>>>, Expands extends ExpandedRelations> = IsSingleField extends true ? FieldColumn extends Column<infer TOutput, any, any, any> ? TOutput : never : [
19
20
  Selected
20
- ] extends [Record<string, Column<any, any, any, any>>] ? MapSelectToReturnType<Selected, Schema> & {
21
- [K in keyof Expands]: Pick<Expands[K]["schema"], Expands[K]["selected"]>[];
22
- } : [
21
+ ] extends [Record<string, Column<any, any, any, any>>] ? MapSelectToReturnType<Selected, Schema> & ResolveExpandedRelations<Expands> : [
23
22
  Selected
24
- ] extends [keyof Schema] ? Pick<Schema, Selected> & {
25
- [K in keyof Expands]: Pick<Expands[K]["schema"], Expands[K]["selected"]>[];
26
- } : never;
23
+ ] extends [keyof Schema] ? Pick<Schema, Selected> & ResolveExpandedRelations<Expands> : never;
27
24
  export declare class RecordBuilder<Occ extends FMTable<any, any> = FMTable<any, any>, IsSingleField extends boolean = false, FieldColumn extends Column<any, any, any, any> | undefined = undefined, Selected extends keyof InferSchemaOutputFromFMTable<NonNullable<Occ>> | Record<string, Column<any, any, ExtractTableName<NonNullable<Occ>>>> = keyof InferSchemaOutputFromFMTable<NonNullable<Occ>>, Expands extends ExpandedRelations = {}> implements ExecutableBuilder<RecordReturnType<InferSchemaOutputFromFMTable<NonNullable<Occ>>, IsSingleField, FieldColumn, Selected, Expands>> {
28
25
  private table;
29
26
  private databaseName;
@@ -92,10 +89,11 @@ export declare class RecordBuilder<Occ extends FMTable<any, any> = FMTable<any,
92
89
  * .execute();
93
90
  * ```
94
91
  */
95
- expand<TargetTable extends FMTable<any, any>>(targetTable: ValidExpandTarget<Occ, TargetTable>, callback?: (builder: QueryBuilder<TargetTable, keyof InferSchemaOutputFromFMTable<TargetTable>, false, false>) => QueryBuilder<TargetTable, any, any, any, any>): RecordBuilder<Occ, false, FieldColumn, Selected, Expands & {
92
+ expand<TargetTable extends FMTable<any, any>, TSelected extends keyof InferSchemaOutputFromFMTable<TargetTable> | Record<string, Column<any, any, ExtractTableName<TargetTable>>> = keyof InferSchemaOutputFromFMTable<TargetTable>, TNestedExpands extends ExpandedRelations = {}>(targetTable: ValidExpandTarget<Occ, TargetTable>, callback?: (builder: QueryBuilder<TargetTable, keyof InferSchemaOutputFromFMTable<TargetTable>, false, false, {}>) => QueryBuilder<TargetTable, TSelected, any, any, TNestedExpands>): RecordBuilder<Occ, false, FieldColumn, Selected, Expands & {
96
93
  [K in ExtractTableName<TargetTable>]: {
97
94
  schema: InferSchemaOutputFromFMTable<TargetTable>;
98
- selected: keyof InferSchemaOutputFromFMTable<TargetTable>;
95
+ selected: TSelected;
96
+ nested: TNestedExpands;
99
97
  };
100
98
  }>;
101
99
  navigate<TargetTable extends FMTable<any, any>>(targetTable: ValidExpandTarget<Occ, TargetTable>): QueryBuilder<TargetTable, keyof InferSchemaOutputFromFMTable<TargetTable>, false, false>;