@proofkit/fmodata 0.1.0-alpha.19 → 0.1.0-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +198 -0
- package/dist/esm/client/builders/default-select.d.ts +4 -1
- package/dist/esm/client/builders/default-select.js +3 -2
- package/dist/esm/client/builders/default-select.js.map +1 -1
- package/dist/esm/client/builders/expand-builder.js.map +1 -1
- package/dist/esm/client/builders/query-string-builder.d.ts +1 -0
- package/dist/esm/client/builders/query-string-builder.js.map +1 -1
- package/dist/esm/client/builders/response-processor.d.ts +2 -0
- package/dist/esm/client/builders/response-processor.js +8 -3
- package/dist/esm/client/builders/response-processor.js.map +1 -1
- package/dist/esm/client/database.d.ts +27 -4
- package/dist/esm/client/database.js +17 -4
- package/dist/esm/client/database.js.map +1 -1
- package/dist/esm/client/delete-builder.d.ts +2 -0
- package/dist/esm/client/delete-builder.js +2 -0
- package/dist/esm/client/delete-builder.js.map +1 -1
- package/dist/esm/client/entity-set.d.ts +8 -7
- package/dist/esm/client/entity-set.js +21 -26
- package/dist/esm/client/entity-set.js.map +1 -1
- package/dist/esm/client/error-parser.js.map +1 -1
- package/dist/esm/client/filemaker-odata.d.ts +15 -2
- package/dist/esm/client/filemaker-odata.js +25 -2
- package/dist/esm/client/filemaker-odata.js.map +1 -1
- package/dist/esm/client/insert-builder.d.ts +2 -0
- package/dist/esm/client/insert-builder.js +2 -0
- package/dist/esm/client/insert-builder.js.map +1 -1
- package/dist/esm/client/query/query-builder.d.ts +26 -15
- package/dist/esm/client/query/query-builder.js +43 -9
- package/dist/esm/client/query/query-builder.js.map +1 -1
- package/dist/esm/client/query/response-processor.d.ts +1 -0
- package/dist/esm/client/query/types.d.ts +24 -3
- package/dist/esm/client/record-builder.d.ts +24 -13
- package/dist/esm/client/record-builder.js +50 -17
- package/dist/esm/client/record-builder.js.map +1 -1
- package/dist/esm/client/update-builder.d.ts +2 -0
- package/dist/esm/client/update-builder.js +2 -0
- package/dist/esm/client/update-builder.js.map +1 -1
- package/dist/esm/client/webhook-builder.d.ts +126 -0
- package/dist/esm/client/webhook-builder.js +197 -0
- package/dist/esm/client/webhook-builder.js.map +1 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/orm/field-builders.d.ts +12 -2
- package/dist/esm/orm/field-builders.js +18 -2
- package/dist/esm/orm/field-builders.js.map +1 -1
- package/dist/esm/orm/table.d.ts +23 -10
- package/dist/esm/orm/table.js +17 -30
- package/dist/esm/orm/table.js.map +1 -1
- package/dist/esm/types.d.ts +32 -2
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/validation.d.ts +5 -5
- package/dist/esm/validation.js +44 -13
- package/dist/esm/validation.js.map +1 -1
- package/package.json +2 -2
- package/src/client/builders/default-select.ts +12 -1
- package/src/client/builders/expand-builder.ts +1 -1
- package/src/client/builders/query-string-builder.ts +6 -0
- package/src/client/builders/response-processor.ts +10 -0
- package/src/client/database.ts +54 -12
- package/src/client/delete-builder.ts +5 -1
- package/src/client/entity-set.ts +72 -44
- package/src/client/error-parser.ts +3 -0
- package/src/client/filemaker-odata.ts +39 -6
- package/src/client/insert-builder.ts +4 -0
- package/src/client/query/query-builder.ts +198 -35
- package/src/client/query/response-processor.ts +15 -25
- package/src/client/query/types.ts +35 -6
- package/src/client/record-builder.ts +159 -32
- package/src/client/update-builder.ts +4 -1
- package/src/client/webhook-builder.ts +285 -0
- package/src/index.ts +6 -0
- package/src/orm/field-builders.ts +24 -2
- package/src/orm/table.ts +40 -48
- package/src/types.ts +62 -6
- package/src/validation.ts +58 -13
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"insert-builder.js","sources":["../../../src/client/insert-builder.ts"],"sourcesContent":["import type {\n ExecutionContext,\n ExecutableBuilder,\n Result,\n ODataRecordMetadata,\n InferSchemaType,\n ExecuteOptions,\n ConditionallyWithODataAnnotations,\n ExecuteMethodOptions,\n} from \"../types\";\nimport { getAcceptHeader } from \"../types\";\nimport type { FMTable } from \"../orm/table\";\nimport {\n getBaseTableConfig,\n getTableName,\n getTableId as getTableIdHelper,\n isUsingEntityIds,\n} from \"../orm/table\";\nimport {\n validateSingleResponse,\n validateAndTransformInput,\n} from \"../validation\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport {\n transformFieldNamesToIds,\n transformResponseFields,\n} from \"../transform\";\nimport { InvalidLocationHeaderError } from \"../errors\";\nimport { safeJsonParse } from \"./sanitize-json\";\nimport { parseErrorResponse } from \"./error-parser\";\n\nexport type InsertOptions = {\n return?: \"minimal\" | \"representation\";\n};\n\nimport type { InferSchemaOutputFromFMTable } from \"../orm/table\";\n\nexport class InsertBuilder<\n Occ extends FMTable<any, any> | undefined = undefined,\n ReturnPreference extends \"minimal\" | \"representation\" = \"representation\",\n> implements\n ExecutableBuilder<\n ReturnPreference extends \"minimal\"\n ? { ROWID: number }\n : InferSchemaOutputFromFMTable<NonNullable<Occ>>\n >\n{\n private table?: Occ;\n private databaseName: string;\n private context: ExecutionContext;\n private data: Partial<InferSchemaOutputFromFMTable<NonNullable<Occ>>>;\n private returnPreference: ReturnPreference;\n\n private databaseUseEntityIds: boolean;\n\n constructor(config: {\n occurrence?: Occ;\n databaseName: string;\n context: ExecutionContext;\n data: Partial<InferSchemaOutputFromFMTable<NonNullable<Occ>>>;\n returnPreference?: ReturnPreference;\n databaseUseEntityIds?: boolean;\n }) {\n this.table = config.occurrence;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.data = config.data;\n this.returnPreference = (config.returnPreference ||\n \"representation\") as ReturnPreference;\n this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;\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 // If useEntityIds is not set in options, use the database-level setting\n return {\n ...options,\n useEntityIds: options?.useEntityIds ?? this.databaseUseEntityIds,\n };\n }\n\n /**\n * Parse ROWID from Location header\n * Expected formats:\n * - contacts(ROWID=4583)\n * - contacts('some-uuid')\n */\n private parseLocationHeader(locationHeader: string | undefined): number {\n if (!locationHeader) {\n throw new InvalidLocationHeaderError(\n \"Location header is required but was not provided\",\n );\n }\n\n // Try to match ROWID=number pattern\n const rowidMatch = locationHeader.match(/ROWID=(\\d+)/);\n if (rowidMatch && rowidMatch[1]) {\n return parseInt(rowidMatch[1], 10);\n }\n\n // Try to extract value from parentheses and parse as number\n const parenMatch = locationHeader.match(/\\(['\"]?([^'\"]+)['\"]?\\)/);\n if (parenMatch && parenMatch[1]) {\n const value = parenMatch[1];\n const numValue = parseInt(value, 10);\n if (!isNaN(numValue)) {\n return numValue;\n }\n }\n\n throw new InvalidLocationHeaderError(\n `Could not extract ROWID from Location header: ${locationHeader}`,\n locationHeader,\n );\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 getTableId(useEntityIds?: boolean): string {\n if (!this.table) {\n throw new Error(\"Table occurrence is required\");\n }\n\n const contextDefault = this.context._getUseEntityIds?.() ?? false;\n const shouldUseIds = useEntityIds ?? contextDefault;\n\n if (shouldUseIds) {\n if (!isUsingEntityIds(this.table)) {\n throw new Error(\n `useEntityIds is true but table \"${getTableName(this.table)}\" does not have entity IDs configured`,\n );\n }\n return getTableIdHelper(this.table);\n }\n\n return getTableName(this.table);\n }\n\n async execute<EO extends ExecuteOptions>(\n options?: ExecuteMethodOptions<EO>,\n ): Promise<\n Result<\n ReturnPreference extends \"minimal\"\n ? { ROWID: number }\n : ConditionallyWithODataAnnotations<\n InferSchemaOutputFromFMTable<NonNullable<Occ>>,\n EO[\"includeODataAnnotations\"] extends true ? true : false\n >\n >\n > {\n // Merge database-level useEntityIds with per-request options\n const mergedOptions = this.mergeExecuteOptions(options);\n\n // Get table identifier with override support\n const tableId = this.getTableId(mergedOptions.useEntityIds);\n const url = `/${this.databaseName}/${tableId}`;\n\n // Validate and transform input data using input validators (writeValidators)\n let validatedData = this.data;\n if (this.table) {\n const baseTableConfig = getBaseTableConfig(this.table);\n const inputSchema = baseTableConfig.inputSchema;\n\n try {\n validatedData = await validateAndTransformInput(this.data, inputSchema);\n } catch (error) {\n // If validation fails, return error immediately\n return {\n data: undefined,\n error: error instanceof Error ? error : new Error(String(error)),\n } as any;\n }\n }\n\n // Transform field names to FMFIDs if using entity IDs\n // Only transform if useEntityIds resolves to true (respects per-request override)\n const shouldUseIds = mergedOptions.useEntityIds ?? false;\n\n const transformedData =\n this.table && shouldUseIds\n ? transformFieldNamesToIds(validatedData, this.table)\n : validatedData;\n\n // Set Prefer header based on return preference\n const preferHeader =\n this.returnPreference === \"minimal\"\n ? \"return=minimal\"\n : \"return=representation\";\n\n // Make POST request with JSON body\n const result = await this.context._makeRequest<any>(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Prefer: preferHeader,\n ...((mergedOptions as any)?.headers || {}),\n },\n body: JSON.stringify(transformedData),\n ...mergedOptions,\n });\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n // Handle return=minimal case\n if (this.returnPreference === \"minimal\") {\n // The response should be empty (204 No Content)\n // _makeRequest will return { _location: string } when there's a Location header\n const responseData = result.data as any;\n\n if (!responseData || !responseData._location) {\n throw new InvalidLocationHeaderError(\n \"Location header is required when using return=minimal but was not found in response\",\n );\n }\n\n const rowid = this.parseLocationHeader(responseData._location);\n return { data: { ROWID: rowid } as any, error: undefined };\n }\n\n let response = result.data;\n\n // Transform response field IDs back to names if using entity IDs\n // Only transform if useEntityIds resolves to true (respects per-request override)\n if (this.table && shouldUseIds) {\n response = transformResponseFields(\n response,\n this.table,\n undefined, // No expand configs for insert\n );\n }\n\n // Get schema from table if available, excluding container fields\n let schema: Record<string, any> | undefined;\n if (this.table) {\n const baseTableConfig = getBaseTableConfig(this.table);\n const containerFields = baseTableConfig.containerFields || [];\n\n // Filter out container fields from schema\n schema = { ...baseTableConfig.schema };\n for (const containerField of containerFields) {\n delete schema[containerField as string];\n }\n }\n\n // Validate the response (FileMaker returns the created record)\n const validation = await validateSingleResponse<\n InferSchemaOutputFromFMTable<NonNullable<Occ>>\n >(\n response,\n schema,\n undefined, // No selected fields for insert\n undefined, // No expand configs\n \"exact\", // Expect exactly one record\n );\n\n if (!validation.valid) {\n return { data: undefined, error: validation.error };\n }\n\n // Handle null response (shouldn't happen for insert, but handle it)\n if (validation.data === null) {\n return {\n data: undefined,\n error: new Error(\"Insert operation returned null response\"),\n };\n }\n\n return { data: validation.data as any, error: undefined };\n }\n\n getRequestConfig(): { method: string; url: string; body?: any } {\n // For batch operations, use database-level setting (no per-request override available here)\n // Note: Input validation happens in execute() and processResponse() for batch operations\n const tableId = this.getTableId(this.databaseUseEntityIds);\n\n // Transform field names to FMFIDs if using entity IDs\n const transformedData =\n this.table && this.databaseUseEntityIds\n ? transformFieldNamesToIds(this.data, this.table)\n : this.data;\n\n return {\n method: \"POST\",\n url: `/${this.databaseName}/${tableId}`,\n body: JSON.stringify(transformedData),\n };\n }\n\n toRequest(baseUrl: string, options?: ExecuteOptions): Request {\n const config = this.getRequestConfig();\n const fullUrl = `${baseUrl}${config.url}`;\n\n // Set Prefer header based on return preference\n const preferHeader =\n this.returnPreference === \"minimal\"\n ? \"return=minimal\"\n : \"return=representation\";\n\n return new Request(fullUrl, {\n method: config.method,\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: getAcceptHeader(options?.includeODataAnnotations),\n Prefer: preferHeader,\n },\n body: config.body,\n });\n }\n\n async processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<\n Result<\n ReturnPreference extends \"minimal\"\n ? { ROWID: number }\n : InferSchemaOutputFromFMTable<NonNullable<Occ>>\n >\n > {\n // Check for error responses (important for batch operations)\n if (!response.ok) {\n const tableName = this.table ? getTableName(this.table) : \"unknown\";\n const error = await parseErrorResponse(\n response,\n response.url || `/${this.databaseName}/${tableName}`,\n );\n return { data: undefined, error };\n }\n\n // Handle 204 No Content (common in batch/changeset operations)\n // FileMaker uses return=minimal for changeset operations regardless of Prefer header\n if (response.status === 204) {\n // Check for Location header (for return=minimal)\n if (this.returnPreference === \"minimal\") {\n const locationHeader =\n response.headers.get(\"Location\") || response.headers.get(\"location\");\n if (locationHeader) {\n const rowid = this.parseLocationHeader(locationHeader);\n return { data: { ROWID: rowid } as any, error: undefined };\n }\n throw new InvalidLocationHeaderError(\n \"Location header is required when using return=minimal but was not found in response\",\n );\n }\n\n // For 204 responses without return=minimal, FileMaker doesn't return the created entity\n // This is valid OData behavior for changeset operations\n // We return a success indicator but no actual data\n return {\n data: {} as any,\n error: undefined,\n };\n }\n\n // If we expected return=minimal but got a body, that's unexpected\n if (this.returnPreference === \"minimal\") {\n throw new InvalidLocationHeaderError(\n \"Expected 204 No Content for return=minimal, but received response with body\",\n );\n }\n\n // Use safeJsonParse to handle FileMaker's invalid JSON with unquoted ? values\n let rawResponse;\n try {\n rawResponse = await safeJsonParse(response);\n } catch (err) {\n // If parsing fails with 204, handle it gracefully\n if (response.status === 204) {\n return {\n data: {} as any,\n error: undefined,\n };\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 // Validate and transform input data using input validators (writeValidators)\n // This is needed for processResponse because it's called from batch operations\n // where the data hasn't been validated yet\n let validatedData = this.data;\n if (this.table) {\n const baseTableConfig = getBaseTableConfig(this.table);\n const inputSchema = baseTableConfig.inputSchema;\n try {\n validatedData = await validateAndTransformInput(this.data, inputSchema);\n } catch (error) {\n return {\n data: undefined,\n error: error instanceof Error ? error : new Error(String(error)),\n } as any;\n }\n }\n\n // Transform response field IDs back to names if using entity IDs\n // Only transform if useEntityIds resolves to true (respects per-request override)\n const shouldUseIds = options?.useEntityIds ?? this.databaseUseEntityIds;\n\n let transformedResponse = rawResponse;\n if (this.table && shouldUseIds) {\n transformedResponse = transformResponseFields(\n rawResponse,\n this.table,\n undefined, // No expand configs for insert\n );\n }\n\n // Get schema from table if available, excluding container fields\n let schema: Record<string, any> | undefined;\n if (this.table) {\n const baseTableConfig = getBaseTableConfig(this.table);\n const containerFields = baseTableConfig.containerFields || [];\n\n // Filter out container fields from schema\n schema = { ...baseTableConfig.schema };\n for (const containerField of containerFields) {\n delete schema[containerField as string];\n }\n }\n\n // Validate the response (FileMaker returns the created record)\n const validation = await validateSingleResponse<\n InferSchemaOutputFromFMTable<NonNullable<Occ>>\n >(\n transformedResponse,\n schema,\n undefined, // No selected fields for insert\n undefined, // No expand configs\n \"exact\", // Expect exactly one record\n );\n\n if (!validation.valid) {\n return { data: undefined, error: validation.error };\n }\n\n // Handle null response (shouldn't happen for insert, but handle it)\n if (validation.data === null) {\n return {\n data: undefined,\n error: new Error(\"Insert operation returned null response\"),\n };\n }\n\n return { data: validation.data as any, error: undefined };\n }\n}\n"],"names":["getTableIdHelper"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"insert-builder.js","sources":["../../../src/client/insert-builder.ts"],"sourcesContent":["import type {\n ExecutionContext,\n ExecutableBuilder,\n Result,\n ODataRecordMetadata,\n InferSchemaType,\n ExecuteOptions,\n ConditionallyWithODataAnnotations,\n ExecuteMethodOptions,\n} from \"../types\";\nimport { getAcceptHeader } from \"../types\";\nimport type { FMTable } from \"../orm/table\";\nimport {\n getBaseTableConfig,\n getTableName,\n getTableId as getTableIdHelper,\n isUsingEntityIds,\n} from \"../orm/table\";\nimport {\n validateSingleResponse,\n validateAndTransformInput,\n} from \"../validation\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport {\n transformFieldNamesToIds,\n transformResponseFields,\n} from \"../transform\";\nimport { InvalidLocationHeaderError } from \"../errors\";\nimport { safeJsonParse } from \"./sanitize-json\";\nimport { parseErrorResponse } from \"./error-parser\";\n\nexport type InsertOptions = {\n return?: \"minimal\" | \"representation\";\n};\n\nimport type { InferSchemaOutputFromFMTable } from \"../orm/table\";\n\nexport class InsertBuilder<\n Occ extends FMTable<any, any> | undefined = undefined,\n ReturnPreference extends \"minimal\" | \"representation\" = \"representation\",\n> implements\n ExecutableBuilder<\n ReturnPreference extends \"minimal\"\n ? { ROWID: number }\n : InferSchemaOutputFromFMTable<NonNullable<Occ>>\n >\n{\n private table?: Occ;\n private databaseName: string;\n private context: ExecutionContext;\n private data: Partial<InferSchemaOutputFromFMTable<NonNullable<Occ>>>;\n private returnPreference: ReturnPreference;\n\n private databaseUseEntityIds: boolean;\n private databaseIncludeSpecialColumns: boolean;\n\n constructor(config: {\n occurrence?: Occ;\n databaseName: string;\n context: ExecutionContext;\n data: Partial<InferSchemaOutputFromFMTable<NonNullable<Occ>>>;\n returnPreference?: ReturnPreference;\n databaseUseEntityIds?: boolean;\n databaseIncludeSpecialColumns?: boolean;\n }) {\n this.table = config.occurrence;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.data = config.data;\n this.returnPreference = (config.returnPreference ||\n \"representation\") as ReturnPreference;\n this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;\n this.databaseIncludeSpecialColumns =\n config.databaseIncludeSpecialColumns ?? false;\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 // If useEntityIds is not set in options, use the database-level setting\n return {\n ...options,\n useEntityIds: options?.useEntityIds ?? this.databaseUseEntityIds,\n };\n }\n\n /**\n * Parse ROWID from Location header\n * Expected formats:\n * - contacts(ROWID=4583)\n * - contacts('some-uuid')\n */\n private parseLocationHeader(locationHeader: string | undefined): number {\n if (!locationHeader) {\n throw new InvalidLocationHeaderError(\n \"Location header is required but was not provided\",\n );\n }\n\n // Try to match ROWID=number pattern\n const rowidMatch = locationHeader.match(/ROWID=(\\d+)/);\n if (rowidMatch && rowidMatch[1]) {\n return parseInt(rowidMatch[1], 10);\n }\n\n // Try to extract value from parentheses and parse as number\n const parenMatch = locationHeader.match(/\\(['\"]?([^'\"]+)['\"]?\\)/);\n if (parenMatch && parenMatch[1]) {\n const value = parenMatch[1];\n const numValue = parseInt(value, 10);\n if (!isNaN(numValue)) {\n return numValue;\n }\n }\n\n throw new InvalidLocationHeaderError(\n `Could not extract ROWID from Location header: ${locationHeader}`,\n locationHeader,\n );\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 getTableId(useEntityIds?: boolean): string {\n if (!this.table) {\n throw new Error(\"Table occurrence is required\");\n }\n\n const contextDefault = this.context._getUseEntityIds?.() ?? false;\n const shouldUseIds = useEntityIds ?? contextDefault;\n\n if (shouldUseIds) {\n if (!isUsingEntityIds(this.table)) {\n throw new Error(\n `useEntityIds is true but table \"${getTableName(this.table)}\" does not have entity IDs configured`,\n );\n }\n return getTableIdHelper(this.table);\n }\n\n return getTableName(this.table);\n }\n\n async execute<EO extends ExecuteOptions>(\n options?: ExecuteMethodOptions<EO>,\n ): Promise<\n Result<\n ReturnPreference extends \"minimal\"\n ? { ROWID: number }\n : ConditionallyWithODataAnnotations<\n InferSchemaOutputFromFMTable<NonNullable<Occ>>,\n EO[\"includeODataAnnotations\"] extends true ? true : false\n >\n >\n > {\n // Merge database-level useEntityIds with per-request options\n const mergedOptions = this.mergeExecuteOptions(options);\n\n // Get table identifier with override support\n const tableId = this.getTableId(mergedOptions.useEntityIds);\n const url = `/${this.databaseName}/${tableId}`;\n\n // Validate and transform input data using input validators (writeValidators)\n let validatedData = this.data;\n if (this.table) {\n const baseTableConfig = getBaseTableConfig(this.table);\n const inputSchema = baseTableConfig.inputSchema;\n\n try {\n validatedData = await validateAndTransformInput(this.data, inputSchema);\n } catch (error) {\n // If validation fails, return error immediately\n return {\n data: undefined,\n error: error instanceof Error ? error : new Error(String(error)),\n } as any;\n }\n }\n\n // Transform field names to FMFIDs if using entity IDs\n // Only transform if useEntityIds resolves to true (respects per-request override)\n const shouldUseIds = mergedOptions.useEntityIds ?? false;\n\n const transformedData =\n this.table && shouldUseIds\n ? transformFieldNamesToIds(validatedData, this.table)\n : validatedData;\n\n // Set Prefer header based on return preference\n const preferHeader =\n this.returnPreference === \"minimal\"\n ? \"return=minimal\"\n : \"return=representation\";\n\n // Make POST request with JSON body\n const result = await this.context._makeRequest<any>(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Prefer: preferHeader,\n ...((mergedOptions as any)?.headers || {}),\n },\n body: JSON.stringify(transformedData),\n ...mergedOptions,\n });\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n // Handle return=minimal case\n if (this.returnPreference === \"minimal\") {\n // The response should be empty (204 No Content)\n // _makeRequest will return { _location: string } when there's a Location header\n const responseData = result.data as any;\n\n if (!responseData || !responseData._location) {\n throw new InvalidLocationHeaderError(\n \"Location header is required when using return=minimal but was not found in response\",\n );\n }\n\n const rowid = this.parseLocationHeader(responseData._location);\n return { data: { ROWID: rowid } as any, error: undefined };\n }\n\n let response = result.data;\n\n // Transform response field IDs back to names if using entity IDs\n // Only transform if useEntityIds resolves to true (respects per-request override)\n if (this.table && shouldUseIds) {\n response = transformResponseFields(\n response,\n this.table,\n undefined, // No expand configs for insert\n );\n }\n\n // Get schema from table if available, excluding container fields\n let schema: Record<string, any> | undefined;\n if (this.table) {\n const baseTableConfig = getBaseTableConfig(this.table);\n const containerFields = baseTableConfig.containerFields || [];\n\n // Filter out container fields from schema\n schema = { ...baseTableConfig.schema };\n for (const containerField of containerFields) {\n delete schema[containerField as string];\n }\n }\n\n // Validate the response (FileMaker returns the created record)\n const validation = await validateSingleResponse<\n InferSchemaOutputFromFMTable<NonNullable<Occ>>\n >(\n response,\n schema,\n undefined, // No selected fields for insert\n undefined, // No expand configs\n \"exact\", // Expect exactly one record\n );\n\n if (!validation.valid) {\n return { data: undefined, error: validation.error };\n }\n\n // Handle null response (shouldn't happen for insert, but handle it)\n if (validation.data === null) {\n return {\n data: undefined,\n error: new Error(\"Insert operation returned null response\"),\n };\n }\n\n return { data: validation.data as any, error: undefined };\n }\n\n getRequestConfig(): { method: string; url: string; body?: any } {\n // For batch operations, use database-level setting (no per-request override available here)\n // Note: Input validation happens in execute() and processResponse() for batch operations\n const tableId = this.getTableId(this.databaseUseEntityIds);\n\n // Transform field names to FMFIDs if using entity IDs\n const transformedData =\n this.table && this.databaseUseEntityIds\n ? transformFieldNamesToIds(this.data, this.table)\n : this.data;\n\n return {\n method: \"POST\",\n url: `/${this.databaseName}/${tableId}`,\n body: JSON.stringify(transformedData),\n };\n }\n\n toRequest(baseUrl: string, options?: ExecuteOptions): Request {\n const config = this.getRequestConfig();\n const fullUrl = `${baseUrl}${config.url}`;\n\n // Set Prefer header based on return preference\n const preferHeader =\n this.returnPreference === \"minimal\"\n ? \"return=minimal\"\n : \"return=representation\";\n\n return new Request(fullUrl, {\n method: config.method,\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: getAcceptHeader(options?.includeODataAnnotations),\n Prefer: preferHeader,\n },\n body: config.body,\n });\n }\n\n async processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<\n Result<\n ReturnPreference extends \"minimal\"\n ? { ROWID: number }\n : InferSchemaOutputFromFMTable<NonNullable<Occ>>\n >\n > {\n // Check for error responses (important for batch operations)\n if (!response.ok) {\n const tableName = this.table ? getTableName(this.table) : \"unknown\";\n const error = await parseErrorResponse(\n response,\n response.url || `/${this.databaseName}/${tableName}`,\n );\n return { data: undefined, error };\n }\n\n // Handle 204 No Content (common in batch/changeset operations)\n // FileMaker uses return=minimal for changeset operations regardless of Prefer header\n if (response.status === 204) {\n // Check for Location header (for return=minimal)\n if (this.returnPreference === \"minimal\") {\n const locationHeader =\n response.headers.get(\"Location\") || response.headers.get(\"location\");\n if (locationHeader) {\n const rowid = this.parseLocationHeader(locationHeader);\n return { data: { ROWID: rowid } as any, error: undefined };\n }\n throw new InvalidLocationHeaderError(\n \"Location header is required when using return=minimal but was not found in response\",\n );\n }\n\n // For 204 responses without return=minimal, FileMaker doesn't return the created entity\n // This is valid OData behavior for changeset operations\n // We return a success indicator but no actual data\n return {\n data: {} as any,\n error: undefined,\n };\n }\n\n // If we expected return=minimal but got a body, that's unexpected\n if (this.returnPreference === \"minimal\") {\n throw new InvalidLocationHeaderError(\n \"Expected 204 No Content for return=minimal, but received response with body\",\n );\n }\n\n // Use safeJsonParse to handle FileMaker's invalid JSON with unquoted ? values\n let rawResponse;\n try {\n rawResponse = await safeJsonParse(response);\n } catch (err) {\n // If parsing fails with 204, handle it gracefully\n if (response.status === 204) {\n return {\n data: {} as any,\n error: undefined,\n };\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 // Validate and transform input data using input validators (writeValidators)\n // This is needed for processResponse because it's called from batch operations\n // where the data hasn't been validated yet\n let validatedData = this.data;\n if (this.table) {\n const baseTableConfig = getBaseTableConfig(this.table);\n const inputSchema = baseTableConfig.inputSchema;\n try {\n validatedData = await validateAndTransformInput(this.data, inputSchema);\n } catch (error) {\n return {\n data: undefined,\n error: error instanceof Error ? error : new Error(String(error)),\n } as any;\n }\n }\n\n // Transform response field IDs back to names if using entity IDs\n // Only transform if useEntityIds resolves to true (respects per-request override)\n const shouldUseIds = options?.useEntityIds ?? this.databaseUseEntityIds;\n\n let transformedResponse = rawResponse;\n if (this.table && shouldUseIds) {\n transformedResponse = transformResponseFields(\n rawResponse,\n this.table,\n undefined, // No expand configs for insert\n );\n }\n\n // Get schema from table if available, excluding container fields\n let schema: Record<string, any> | undefined;\n if (this.table) {\n const baseTableConfig = getBaseTableConfig(this.table);\n const containerFields = baseTableConfig.containerFields || [];\n\n // Filter out container fields from schema\n schema = { ...baseTableConfig.schema };\n for (const containerField of containerFields) {\n delete schema[containerField as string];\n }\n }\n\n // Validate the response (FileMaker returns the created record)\n const validation = await validateSingleResponse<\n InferSchemaOutputFromFMTable<NonNullable<Occ>>\n >(\n transformedResponse,\n schema,\n undefined, // No selected fields for insert\n undefined, // No expand configs\n \"exact\", // Expect exactly one record\n );\n\n if (!validation.valid) {\n return { data: undefined, error: validation.error };\n }\n\n // Handle null response (shouldn't happen for insert, but handle it)\n if (validation.data === null) {\n return {\n data: undefined,\n error: new Error(\"Insert operation returned null response\"),\n };\n }\n\n return { data: validation.data as any, error: undefined };\n }\n}\n"],"names":["getTableIdHelper"],"mappings":";;;;;;;;;;AAqCO,MAAM,cASb;AAAA,EAUE,YAAY,QAQT;AAjBK;AACA;AACA;AACA;AACA;AAEA;AACA;AAWN,SAAK,QAAQ,OAAO;AACpB,SAAK,eAAe,OAAO;AAC3B,SAAK,UAAU,OAAO;AACtB,SAAK,OAAO,OAAO;AACd,SAAA,mBAAoB,OAAO,oBAC9B;AACG,SAAA,uBAAuB,OAAO,wBAAwB;AACtD,SAAA,gCACH,OAAO,iCAAiC;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMpC,oBACN,SAC0D;AAEnD,WAAA;AAAA,MACL,GAAG;AAAA,MACH,eAAc,mCAAS,iBAAgB,KAAK;AAAA,IAC9C;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASM,oBAAoB,gBAA4C;AACtE,QAAI,CAAC,gBAAgB;AACnB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IAAA;AAII,UAAA,aAAa,eAAe,MAAM,aAAa;AACjD,QAAA,cAAc,WAAW,CAAC,GAAG;AAC/B,aAAO,SAAS,WAAW,CAAC,GAAG,EAAE;AAAA,IAAA;AAI7B,UAAA,aAAa,eAAe,MAAM,wBAAwB;AAC5D,QAAA,cAAc,WAAW,CAAC,GAAG;AACzB,YAAA,QAAQ,WAAW,CAAC;AACpB,YAAA,WAAW,SAAS,OAAO,EAAE;AAC/B,UAAA,CAAC,MAAM,QAAQ,GAAG;AACb,eAAA;AAAA,MAAA;AAAA,IACT;AAGF,UAAM,IAAI;AAAA,MACR,iDAAiD,cAAc;AAAA,MAC/D;AAAA,IACF;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOM,WAAW,cAAgC;;AAC7C,QAAA,CAAC,KAAK,OAAO;AACT,YAAA,IAAI,MAAM,8BAA8B;AAAA,IAAA;AAGhD,UAAM,mBAAiB,gBAAK,SAAQ,qBAAb,gCAAqC;AAC5D,UAAM,eAAe,gBAAgB;AAErC,QAAI,cAAc;AAChB,UAAI,CAAC,iBAAiB,KAAK,KAAK,GAAG;AACjC,cAAM,IAAI;AAAA,UACR,mCAAmC,aAAa,KAAK,KAAK,CAAC;AAAA,QAC7D;AAAA,MAAA;AAEK,aAAAA,WAAiB,KAAK,KAAK;AAAA,IAAA;AAG7B,WAAA,aAAa,KAAK,KAAK;AAAA,EAAA;AAAA,EAGhC,MAAM,QACJ,SAUA;AAEM,UAAA,gBAAgB,KAAK,oBAAoB,OAAO;AAGtD,UAAM,UAAU,KAAK,WAAW,cAAc,YAAY;AAC1D,UAAM,MAAM,IAAI,KAAK,YAAY,IAAI,OAAO;AAG5C,QAAI,gBAAgB,KAAK;AACzB,QAAI,KAAK,OAAO;AACR,YAAA,kBAAkB,mBAAmB,KAAK,KAAK;AACrD,YAAM,cAAc,gBAAgB;AAEhC,UAAA;AACF,wBAAgB,MAAM,0BAA0B,KAAK,MAAM,WAAW;AAAA,eAC/D,OAAO;AAEP,eAAA;AAAA,UACL,MAAM;AAAA,UACN,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAAA,QACjE;AAAA,MAAA;AAAA,IACF;AAKI,UAAA,eAAe,cAAc,gBAAgB;AAE7C,UAAA,kBACJ,KAAK,SAAS,eACV,yBAAyB,eAAe,KAAK,KAAK,IAClD;AAGN,UAAM,eACJ,KAAK,qBAAqB,YACtB,mBACA;AAGN,UAAM,SAAS,MAAM,KAAK,QAAQ,aAAkB,KAAK;AAAA,MACvD,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,QAAQ;AAAA,QACR,IAAK,+CAAuB,YAAW,CAAA;AAAA,MACzC;AAAA,MACA,MAAM,KAAK,UAAU,eAAe;AAAA,MACpC,GAAG;AAAA,IAAA,CACJ;AAED,QAAI,OAAO,OAAO;AAChB,aAAO,EAAE,MAAM,QAAW,OAAO,OAAO,MAAM;AAAA,IAAA;AAI5C,QAAA,KAAK,qBAAqB,WAAW;AAGvC,YAAM,eAAe,OAAO;AAE5B,UAAI,CAAC,gBAAgB,CAAC,aAAa,WAAW;AAC5C,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MAAA;AAGF,YAAM,QAAQ,KAAK,oBAAoB,aAAa,SAAS;AAC7D,aAAO,EAAE,MAAM,EAAE,OAAO,MAAM,GAAU,OAAO,OAAU;AAAA,IAAA;AAG3D,QAAI,WAAW,OAAO;AAIlB,QAAA,KAAK,SAAS,cAAc;AACnB,iBAAA;AAAA,QACT;AAAA,QACA,KAAK;AAAA,QACL;AAAA;AAAA,MACF;AAAA,IAAA;AAIE,QAAA;AACJ,QAAI,KAAK,OAAO;AACR,YAAA,kBAAkB,mBAAmB,KAAK,KAAK;AAC/C,YAAA,kBAAkB,gBAAgB,mBAAmB,CAAC;AAGnD,eAAA,EAAE,GAAG,gBAAgB,OAAO;AACrC,iBAAW,kBAAkB,iBAAiB;AAC5C,eAAO,OAAO,cAAwB;AAAA,MAAA;AAAA,IACxC;AAIF,UAAM,aAAa,MAAM;AAAA,MAGvB;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MACA;AAAA;AAAA,MACA;AAAA;AAAA,IACF;AAEI,QAAA,CAAC,WAAW,OAAO;AACrB,aAAO,EAAE,MAAM,QAAW,OAAO,WAAW,MAAM;AAAA,IAAA;AAIhD,QAAA,WAAW,SAAS,MAAM;AACrB,aAAA;AAAA,QACL,MAAM;AAAA,QACN,OAAO,IAAI,MAAM,yCAAyC;AAAA,MAC5D;AAAA,IAAA;AAGF,WAAO,EAAE,MAAM,WAAW,MAAa,OAAO,OAAU;AAAA,EAAA;AAAA,EAG1D,mBAAgE;AAG9D,UAAM,UAAU,KAAK,WAAW,KAAK,oBAAoB;AAGnD,UAAA,kBACJ,KAAK,SAAS,KAAK,uBACf,yBAAyB,KAAK,MAAM,KAAK,KAAK,IAC9C,KAAK;AAEJ,WAAA;AAAA,MACL,QAAQ;AAAA,MACR,KAAK,IAAI,KAAK,YAAY,IAAI,OAAO;AAAA,MACrC,MAAM,KAAK,UAAU,eAAe;AAAA,IACtC;AAAA,EAAA;AAAA,EAGF,UAAU,SAAiB,SAAmC;AACtD,UAAA,SAAS,KAAK,iBAAiB;AACrC,UAAM,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG;AAGvC,UAAM,eACJ,KAAK,qBAAqB,YACtB,mBACA;AAEC,WAAA,IAAI,QAAQ,SAAS;AAAA,MAC1B,QAAQ,OAAO;AAAA,MACf,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,QAAQ,gBAAgB,mCAAS,uBAAuB;AAAA,QACxD,QAAQ;AAAA,MACV;AAAA,MACA,MAAM,OAAO;AAAA,IAAA,CACd;AAAA,EAAA;AAAA,EAGH,MAAM,gBACJ,UACA,SAOA;AAEI,QAAA,CAAC,SAAS,IAAI;AAChB,YAAM,YAAY,KAAK,QAAQ,aAAa,KAAK,KAAK,IAAI;AAC1D,YAAM,QAAQ,MAAM;AAAA,QAClB;AAAA,QACA,SAAS,OAAO,IAAI,KAAK,YAAY,IAAI,SAAS;AAAA,MACpD;AACO,aAAA,EAAE,MAAM,QAAW,MAAM;AAAA,IAAA;AAK9B,QAAA,SAAS,WAAW,KAAK;AAEvB,UAAA,KAAK,qBAAqB,WAAW;AACjC,cAAA,iBACJ,SAAS,QAAQ,IAAI,UAAU,KAAK,SAAS,QAAQ,IAAI,UAAU;AACrE,YAAI,gBAAgB;AACZ,gBAAA,QAAQ,KAAK,oBAAoB,cAAc;AACrD,iBAAO,EAAE,MAAM,EAAE,OAAO,MAAM,GAAU,OAAO,OAAU;AAAA,QAAA;AAE3D,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MAAA;AAMK,aAAA;AAAA,QACL,MAAM,CAAC;AAAA,QACP,OAAO;AAAA,MACT;AAAA,IAAA;AAIE,QAAA,KAAK,qBAAqB,WAAW;AACvC,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IAAA;AAIE,QAAA;AACA,QAAA;AACY,oBAAA,MAAM,cAAc,QAAQ;AAAA,aACnC,KAAK;AAER,UAAA,SAAS,WAAW,KAAK;AACpB,eAAA;AAAA,UACL,MAAM,CAAC;AAAA,UACP,OAAO;AAAA,QACT;AAAA,MAAA;AAEK,aAAA;AAAA,QACL,MAAM;AAAA,QACN,OAAO;AAAA,UACL,MAAM;AAAA,UACN,SAAS,kCAAkC,eAAe,QAAQ,IAAI,UAAU,eAAe;AAAA,UAC/F,+BAAe,KAAK;AAAA,QAAA;AAAA,MAExB;AAAA,IAAA;AAMF,QAAI,gBAAgB,KAAK;AACzB,QAAI,KAAK,OAAO;AACR,YAAA,kBAAkB,mBAAmB,KAAK,KAAK;AACrD,YAAM,cAAc,gBAAgB;AAChC,UAAA;AACF,wBAAgB,MAAM,0BAA0B,KAAK,MAAM,WAAW;AAAA,eAC/D,OAAO;AACP,eAAA;AAAA,UACL,MAAM;AAAA,UACN,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAAA,QACjE;AAAA,MAAA;AAAA,IACF;AAKI,UAAA,gBAAe,mCAAS,iBAAgB,KAAK;AAEnD,QAAI,sBAAsB;AACtB,QAAA,KAAK,SAAS,cAAc;AACR,4BAAA;AAAA,QACpB;AAAA,QACA,KAAK;AAAA,QACL;AAAA;AAAA,MACF;AAAA,IAAA;AAIE,QAAA;AACJ,QAAI,KAAK,OAAO;AACR,YAAA,kBAAkB,mBAAmB,KAAK,KAAK;AAC/C,YAAA,kBAAkB,gBAAgB,mBAAmB,CAAC;AAGnD,eAAA,EAAE,GAAG,gBAAgB,OAAO;AACrC,iBAAW,kBAAkB,iBAAiB;AAC5C,eAAO,OAAO,cAAwB;AAAA,MAAA;AAAA,IACxC;AAIF,UAAM,aAAa,MAAM;AAAA,MAGvB;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MACA;AAAA;AAAA,MACA;AAAA;AAAA,IACF;AAEI,QAAA,CAAC,WAAW,OAAO;AACrB,aAAO,EAAE,MAAM,QAAW,OAAO,WAAW,MAAM;AAAA,IAAA;AAIhD,QAAA,WAAW,SAAS,MAAM;AACrB,aAAA;AAAA,QACL,MAAM;AAAA,QACN,OAAO,IAAI,MAAM,yCAAyC;AAAA,MAC5D;AAAA,IAAA;AAGF,WAAO,EAAE,MAAM,WAAW,MAAa,OAAO,OAAU;AAAA,EAAA;AAE5D;"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { ExecutionContext, ExecutableBuilder, Result, ExecuteOptions, ConditionallyWithODataAnnotations, ExecuteMethodOptions } from '../../types.js';
|
|
1
|
+
import { ExecutionContext, ExecutableBuilder, Result, ExecuteOptions, ConditionallyWithODataAnnotations, ConditionallyWithSpecialColumns, NormalizeIncludeSpecialColumns, ExecuteMethodOptions } from '../../types.js';
|
|
2
2
|
import { Column } from '../../orm/column.js';
|
|
3
3
|
import { FilterExpression, OrderByExpression } from '../../orm/operators.js';
|
|
4
4
|
import { FMTable, InferSchemaOutputFromFMTable, ValidExpandTarget, ExtractTableName } from '../../orm/table.js';
|
|
5
5
|
import { ExpandedRelations } from '../builders/index.js';
|
|
6
|
-
import { TypeSafeOrderBy, QueryReturnType } from './types.js';
|
|
6
|
+
import { TypeSafeOrderBy, QueryReturnType, SystemColumnsOption } from './types.js';
|
|
7
7
|
export type { QueryReturnType };
|
|
8
8
|
export type { TypeSafeOrderBy, ExpandedRelations };
|
|
9
|
-
export declare class QueryBuilder<Occ extends FMTable<any, any>, Selected extends keyof InferSchemaOutputFromFMTable<Occ> | Record<string, Column<any, any, ExtractTableName<Occ>>> = keyof InferSchemaOutputFromFMTable<Occ>, SingleMode extends "exact" | "maybe" | false = false, IsCount extends boolean = false, Expands extends ExpandedRelations = {}> implements ExecutableBuilder<QueryReturnType<InferSchemaOutputFromFMTable<Occ>, Selected, SingleMode, IsCount, Expands>> {
|
|
9
|
+
export declare class QueryBuilder<Occ extends FMTable<any, any>, Selected extends keyof InferSchemaOutputFromFMTable<Occ> | Record<string, Column<any, any, ExtractTableName<Occ>>> = keyof InferSchemaOutputFromFMTable<Occ>, SingleMode extends "exact" | "maybe" | false = false, IsCount extends boolean = false, Expands extends ExpandedRelations = {}, DatabaseIncludeSpecialColumns extends boolean = false, SystemCols extends SystemColumnsOption | undefined = undefined> implements ExecutableBuilder<QueryReturnType<InferSchemaOutputFromFMTable<Occ>, Selected, SingleMode, IsCount, Expands, SystemCols>> {
|
|
10
10
|
private queryOptions;
|
|
11
11
|
private expandConfigs;
|
|
12
12
|
private singleMode;
|
|
@@ -16,18 +16,21 @@ export declare class QueryBuilder<Occ extends FMTable<any, any>, Selected extend
|
|
|
16
16
|
private context;
|
|
17
17
|
private navigation?;
|
|
18
18
|
private databaseUseEntityIds;
|
|
19
|
+
private databaseIncludeSpecialColumns;
|
|
19
20
|
private expandBuilder;
|
|
20
21
|
private urlBuilder;
|
|
21
22
|
private fieldMapping?;
|
|
23
|
+
private systemColumns?;
|
|
22
24
|
private logger;
|
|
23
25
|
constructor(config: {
|
|
24
26
|
occurrence: Occ;
|
|
25
27
|
databaseName: string;
|
|
26
28
|
context: ExecutionContext;
|
|
27
29
|
databaseUseEntityIds?: boolean;
|
|
30
|
+
databaseIncludeSpecialColumns?: boolean;
|
|
28
31
|
});
|
|
29
32
|
/**
|
|
30
|
-
* Helper to merge database-level useEntityIds with per-request options
|
|
33
|
+
* Helper to merge database-level useEntityIds and includeSpecialColumns with per-request options
|
|
31
34
|
*/
|
|
32
35
|
private mergeExecuteOptions;
|
|
33
36
|
/**
|
|
@@ -55,10 +58,18 @@ export declare class QueryBuilder<Occ extends FMTable<any, any>, Selected extend
|
|
|
55
58
|
* userEmail: users.email // renamed!
|
|
56
59
|
* })
|
|
57
60
|
*
|
|
61
|
+
* @example
|
|
62
|
+
* // Include system columns (ROWID, ROWMODID) when using select()
|
|
63
|
+
* db.from(users).list().select(
|
|
64
|
+
* { name: users.name },
|
|
65
|
+
* { ROWID: true, ROWMODID: true }
|
|
66
|
+
* )
|
|
67
|
+
*
|
|
58
68
|
* @param fields - Object mapping output keys to column references (container fields excluded)
|
|
69
|
+
* @param systemColumns - Optional object to request system columns (ROWID, ROWMODID)
|
|
59
70
|
* @returns QueryBuilder with updated selected fields
|
|
60
71
|
*/
|
|
61
|
-
select<TSelect extends Record<string, Column<any, any, ExtractTableName<Occ>, false
|
|
72
|
+
select<TSelect extends Record<string, Column<any, any, ExtractTableName<Occ>, false>>, TSystemCols extends SystemColumnsOption = {}>(fields: TSelect, systemColumns?: TSystemCols): QueryBuilder<Occ, TSelect, SingleMode, IsCount, Expands, DatabaseIncludeSpecialColumns, TSystemCols>;
|
|
62
73
|
/**
|
|
63
74
|
* Filter results using operator expressions (new ORM-style API).
|
|
64
75
|
* Supports eq, gt, lt, and, or, etc. operators with Column references.
|
|
@@ -69,7 +80,7 @@ export declare class QueryBuilder<Occ extends FMTable<any, any>, Selected extend
|
|
|
69
80
|
* .where(and(eq(users.active, true), gt(users.age, 18)))
|
|
70
81
|
* .where("status eq 'active'") // Raw OData string escape hatch
|
|
71
82
|
*/
|
|
72
|
-
where(expression: FilterExpression | string): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands>;
|
|
83
|
+
where(expression: FilterExpression | string): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands, DatabaseIncludeSpecialColumns, SystemCols>;
|
|
73
84
|
/**
|
|
74
85
|
* Specify the sort order for query results.
|
|
75
86
|
*
|
|
@@ -99,24 +110,24 @@ export declare class QueryBuilder<Occ extends FMTable<any, any>, Selected extend
|
|
|
99
110
|
] | [
|
|
100
111
|
Column<any, any, ExtractTableName<Occ>>,
|
|
101
112
|
...Array<Column<any, any, ExtractTableName<Occ>> | OrderByExpression<ExtractTableName<Occ>>>
|
|
102
|
-
]): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands>;
|
|
103
|
-
top(count: number): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands>;
|
|
104
|
-
skip(count: number): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands>;
|
|
113
|
+
]): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands, DatabaseIncludeSpecialColumns, SystemCols>;
|
|
114
|
+
top(count: number): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands, DatabaseIncludeSpecialColumns, SystemCols>;
|
|
115
|
+
skip(count: number): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands, DatabaseIncludeSpecialColumns, SystemCols>;
|
|
105
116
|
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
117
|
[K in ExtractTableName<TargetTable>]: {
|
|
107
118
|
schema: InferSchemaOutputFromFMTable<TargetTable>;
|
|
108
119
|
selected: TSelected;
|
|
109
120
|
nested: TNestedExpands;
|
|
110
121
|
};
|
|
111
|
-
}>;
|
|
112
|
-
single(): QueryBuilder<Occ, Selected, "exact", IsCount, Expands>;
|
|
113
|
-
maybeSingle(): QueryBuilder<Occ, Selected, "maybe", IsCount, Expands>;
|
|
114
|
-
count(): QueryBuilder<Occ, Selected, SingleMode, true, Expands>;
|
|
122
|
+
}, DatabaseIncludeSpecialColumns, SystemCols>;
|
|
123
|
+
single(): QueryBuilder<Occ, Selected, "exact", IsCount, Expands, DatabaseIncludeSpecialColumns, SystemCols>;
|
|
124
|
+
maybeSingle(): QueryBuilder<Occ, Selected, "maybe", IsCount, Expands, DatabaseIncludeSpecialColumns, SystemCols>;
|
|
125
|
+
count(): QueryBuilder<Occ, Selected, SingleMode, true, Expands, DatabaseIncludeSpecialColumns, SystemCols>;
|
|
115
126
|
/**
|
|
116
127
|
* Builds the OData query string from current query options and expand configs.
|
|
117
128
|
*/
|
|
118
129
|
private buildQueryString;
|
|
119
|
-
execute<EO extends ExecuteOptions>(options?: ExecuteMethodOptions<EO>): Promise<Result<ConditionallyWithODataAnnotations<QueryReturnType<InferSchemaOutputFromFMTable<Occ>, Selected, SingleMode, IsCount, Expands>, EO["includeODataAnnotations"] extends true ? true : false>>>;
|
|
130
|
+
execute<EO extends ExecuteOptions>(options?: ExecuteMethodOptions<EO>): Promise<Result<ConditionallyWithODataAnnotations<ConditionallyWithSpecialColumns<QueryReturnType<InferSchemaOutputFromFMTable<Occ>, Selected, SingleMode, IsCount, Expands, SystemCols>, NormalizeIncludeSpecialColumns<EO["includeSpecialColumns"], DatabaseIncludeSpecialColumns>, Selected extends Record<string, Column<any, any, any>> ? true : Selected extends keyof InferSchemaOutputFromFMTable<Occ> ? false : true>, EO["includeODataAnnotations"] extends true ? true : false>>>;
|
|
120
131
|
getQueryString(): string;
|
|
121
132
|
getRequestConfig(): {
|
|
122
133
|
method: string;
|
|
@@ -124,5 +135,5 @@ export declare class QueryBuilder<Occ extends FMTable<any, any>, Selected extend
|
|
|
124
135
|
body?: any;
|
|
125
136
|
};
|
|
126
137
|
toRequest(baseUrl: string, options?: ExecuteOptions): Request;
|
|
127
|
-
processResponse(response: Response, options?: ExecuteOptions): Promise<Result<QueryReturnType<InferSchemaOutputFromFMTable<Occ>, Selected, SingleMode, IsCount, Expands>>>;
|
|
138
|
+
processResponse(response: Response, options?: ExecuteOptions): Promise<Result<QueryReturnType<InferSchemaOutputFromFMTable<Occ>, Selected, SingleMode, IsCount, Expands, SystemCols>>>;
|
|
128
139
|
}
|
|
@@ -27,10 +27,13 @@ class QueryBuilder {
|
|
|
27
27
|
__publicField(this, "context");
|
|
28
28
|
__publicField(this, "navigation");
|
|
29
29
|
__publicField(this, "databaseUseEntityIds");
|
|
30
|
+
__publicField(this, "databaseIncludeSpecialColumns");
|
|
30
31
|
__publicField(this, "expandBuilder");
|
|
31
32
|
__publicField(this, "urlBuilder");
|
|
32
33
|
// Mapping from field names to output keys (for renamed fields in select)
|
|
33
34
|
__publicField(this, "fieldMapping");
|
|
35
|
+
// System columns requested via select() second argument
|
|
36
|
+
__publicField(this, "systemColumns");
|
|
34
37
|
__publicField(this, "logger");
|
|
35
38
|
var _a, _b;
|
|
36
39
|
this.occurrence = config.occurrence;
|
|
@@ -38,6 +41,7 @@ class QueryBuilder {
|
|
|
38
41
|
this.context = config.context;
|
|
39
42
|
this.logger = ((_b = (_a = config.context) == null ? void 0 : _a._getLogger) == null ? void 0 : _b.call(_a)) ?? createLogger();
|
|
40
43
|
this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;
|
|
44
|
+
this.databaseIncludeSpecialColumns = config.databaseIncludeSpecialColumns ?? false;
|
|
41
45
|
this.expandBuilder = new ExpandBuilder(
|
|
42
46
|
this.databaseUseEntityIds,
|
|
43
47
|
this.logger
|
|
@@ -49,10 +53,14 @@ class QueryBuilder {
|
|
|
49
53
|
);
|
|
50
54
|
}
|
|
51
55
|
/**
|
|
52
|
-
* Helper to merge database-level useEntityIds with per-request options
|
|
56
|
+
* Helper to merge database-level useEntityIds and includeSpecialColumns with per-request options
|
|
53
57
|
*/
|
|
54
58
|
mergeExecuteOptions(options) {
|
|
55
|
-
|
|
59
|
+
const merged = mergeExecuteOptions(options, this.databaseUseEntityIds);
|
|
60
|
+
return {
|
|
61
|
+
...merged,
|
|
62
|
+
includeSpecialColumns: (options == null ? void 0 : options.includeSpecialColumns) ?? this.databaseIncludeSpecialColumns
|
|
63
|
+
};
|
|
56
64
|
}
|
|
57
65
|
/**
|
|
58
66
|
* Gets the FMTable instance
|
|
@@ -81,7 +89,8 @@ class QueryBuilder {
|
|
|
81
89
|
occurrence: this.occurrence,
|
|
82
90
|
databaseName: this.databaseName,
|
|
83
91
|
context: this.context,
|
|
84
|
-
databaseUseEntityIds: this.databaseUseEntityIds
|
|
92
|
+
databaseUseEntityIds: this.databaseUseEntityIds,
|
|
93
|
+
databaseIncludeSpecialColumns: this.databaseIncludeSpecialColumns
|
|
85
94
|
});
|
|
86
95
|
newBuilder.queryOptions = {
|
|
87
96
|
...this.queryOptions,
|
|
@@ -91,6 +100,7 @@ class QueryBuilder {
|
|
|
91
100
|
newBuilder.singleMode = changes.singleMode ?? this.singleMode;
|
|
92
101
|
newBuilder.isCountMode = changes.isCountMode ?? this.isCountMode;
|
|
93
102
|
newBuilder.fieldMapping = changes.fieldMapping ?? this.fieldMapping;
|
|
103
|
+
newBuilder.systemColumns = changes.systemColumns !== void 0 ? changes.systemColumns : this.systemColumns;
|
|
94
104
|
newBuilder.navigation = this.navigation;
|
|
95
105
|
newBuilder.urlBuilder = new QueryUrlBuilder(
|
|
96
106
|
this.databaseName,
|
|
@@ -110,22 +120,38 @@ class QueryBuilder {
|
|
|
110
120
|
* userEmail: users.email // renamed!
|
|
111
121
|
* })
|
|
112
122
|
*
|
|
123
|
+
* @example
|
|
124
|
+
* // Include system columns (ROWID, ROWMODID) when using select()
|
|
125
|
+
* db.from(users).list().select(
|
|
126
|
+
* { name: users.name },
|
|
127
|
+
* { ROWID: true, ROWMODID: true }
|
|
128
|
+
* )
|
|
129
|
+
*
|
|
113
130
|
* @param fields - Object mapping output keys to column references (container fields excluded)
|
|
131
|
+
* @param systemColumns - Optional object to request system columns (ROWID, ROWMODID)
|
|
114
132
|
* @returns QueryBuilder with updated selected fields
|
|
115
133
|
*/
|
|
116
|
-
select(fields) {
|
|
134
|
+
select(fields, systemColumns) {
|
|
117
135
|
const tableName = getTableName(this.occurrence);
|
|
118
136
|
const { selectedFields, fieldMapping } = processSelectWithRenames(
|
|
119
137
|
fields,
|
|
120
138
|
tableName,
|
|
121
139
|
this.logger
|
|
122
140
|
);
|
|
141
|
+
const finalSelectedFields = [...selectedFields];
|
|
142
|
+
if (systemColumns == null ? void 0 : systemColumns.ROWID) {
|
|
143
|
+
finalSelectedFields.push("ROWID");
|
|
144
|
+
}
|
|
145
|
+
if (systemColumns == null ? void 0 : systemColumns.ROWMODID) {
|
|
146
|
+
finalSelectedFields.push("ROWMODID");
|
|
147
|
+
}
|
|
123
148
|
return this.cloneWithChanges({
|
|
124
149
|
selectedFields: fields,
|
|
125
150
|
queryOptions: {
|
|
126
|
-
select:
|
|
151
|
+
select: finalSelectedFields
|
|
127
152
|
},
|
|
128
|
-
fieldMapping: Object.keys(fieldMapping).length > 0 ? fieldMapping : void 0
|
|
153
|
+
fieldMapping: Object.keys(fieldMapping).length > 0 ? fieldMapping : void 0,
|
|
154
|
+
systemColumns
|
|
129
155
|
});
|
|
130
156
|
}
|
|
131
157
|
/**
|
|
@@ -281,7 +307,8 @@ class QueryBuilder {
|
|
|
281
307
|
occurrence: targetTable,
|
|
282
308
|
databaseName: this.databaseName,
|
|
283
309
|
context: this.context,
|
|
284
|
-
databaseUseEntityIds: this.databaseUseEntityIds
|
|
310
|
+
databaseUseEntityIds: this.databaseUseEntityIds,
|
|
311
|
+
databaseIncludeSpecialColumns: this.databaseIncludeSpecialColumns
|
|
285
312
|
})
|
|
286
313
|
);
|
|
287
314
|
this.expandConfigs.push(expandConfig);
|
|
@@ -302,13 +329,14 @@ class QueryBuilder {
|
|
|
302
329
|
/**
|
|
303
330
|
* Builds the OData query string from current query options and expand configs.
|
|
304
331
|
*/
|
|
305
|
-
buildQueryString() {
|
|
332
|
+
buildQueryString(includeSpecialColumns) {
|
|
306
333
|
const queryOptionsWithoutExpandAndSelect = { ...this.queryOptions };
|
|
307
334
|
const originalSelect = queryOptionsWithoutExpandAndSelect.select;
|
|
308
335
|
delete queryOptionsWithoutExpandAndSelect.expand;
|
|
309
336
|
delete queryOptionsWithoutExpandAndSelect.select;
|
|
310
337
|
let queryString = buildQuery(queryOptionsWithoutExpandAndSelect);
|
|
311
338
|
const selectArray = originalSelect ? Array.isArray(originalSelect) ? originalSelect.map(String) : [String(originalSelect)] : void 0;
|
|
339
|
+
includeSpecialColumns ?? this.databaseIncludeSpecialColumns;
|
|
312
340
|
const selectExpandString = buildSelectExpandQueryString({
|
|
313
341
|
selectedFields: selectArray,
|
|
314
342
|
expandConfigs: this.expandConfigs,
|
|
@@ -325,7 +353,9 @@ class QueryBuilder {
|
|
|
325
353
|
}
|
|
326
354
|
async execute(options) {
|
|
327
355
|
const mergedOptions = this.mergeExecuteOptions(options);
|
|
328
|
-
const queryString = this.buildQueryString(
|
|
356
|
+
const queryString = this.buildQueryString(
|
|
357
|
+
mergedOptions.includeSpecialColumns
|
|
358
|
+
);
|
|
329
359
|
if (this.isCountMode) {
|
|
330
360
|
const url2 = this.urlBuilder.build(queryString, {
|
|
331
361
|
isCount: true,
|
|
@@ -348,6 +378,7 @@ class QueryBuilder {
|
|
|
348
378
|
if (result.error) {
|
|
349
379
|
return { data: void 0, error: result.error };
|
|
350
380
|
}
|
|
381
|
+
this.queryOptions.select !== void 0;
|
|
351
382
|
return processQueryResponse(result.data, {
|
|
352
383
|
occurrence: this.occurrence,
|
|
353
384
|
singleMode: this.singleMode,
|
|
@@ -355,6 +386,7 @@ class QueryBuilder {
|
|
|
355
386
|
expandConfigs: this.expandConfigs,
|
|
356
387
|
skipValidation: options == null ? void 0 : options.skipValidation,
|
|
357
388
|
useEntityIds: mergedOptions.useEntityIds,
|
|
389
|
+
includeSpecialColumns: mergedOptions.includeSpecialColumns,
|
|
358
390
|
fieldMapping: this.fieldMapping,
|
|
359
391
|
logger: this.logger
|
|
360
392
|
});
|
|
@@ -429,6 +461,7 @@ class QueryBuilder {
|
|
|
429
461
|
};
|
|
430
462
|
}
|
|
431
463
|
const mergedOptions = this.mergeExecuteOptions(options);
|
|
464
|
+
this.queryOptions.select !== void 0;
|
|
432
465
|
return processQueryResponse(rawData, {
|
|
433
466
|
occurrence: this.occurrence,
|
|
434
467
|
singleMode: this.singleMode,
|
|
@@ -436,6 +469,7 @@ class QueryBuilder {
|
|
|
436
469
|
expandConfigs: this.expandConfigs,
|
|
437
470
|
skipValidation: options == null ? void 0 : options.skipValidation,
|
|
438
471
|
useEntityIds: mergedOptions.useEntityIds,
|
|
472
|
+
includeSpecialColumns: mergedOptions.includeSpecialColumns,
|
|
439
473
|
fieldMapping: this.fieldMapping,
|
|
440
474
|
logger: this.logger
|
|
441
475
|
});
|
|
@@ -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<\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":";;;;;;;;;;;;;;;;;;AA6DO,MAAM,aAqBb;AAAA,EAkBE,YAAY,QAKT;AAtBK,wCAEJ,CAAC;AACG,yCAAgC,CAAC;AACjC,sCAAyB;AACzB,uCAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;;AAQN,SAAK,aAAa,OAAO;AACzB,SAAK,eAAe,OAAO;AAC3B,SAAK,UAAU,OAAO;AACtB,SAAK,WAAS,kBAAO,YAAP,mBAAgB,eAAhB,gCAAkC,aAAa;AACxD,SAAA,uBAAuB,OAAO,wBAAwB;AAC3D,SAAK,gBAAgB,IAAI;AAAA,MACvB,KAAK;AAAA,MACL,KAAK;AAAA,IACP;AACA,SAAK,aAAa,IAAI;AAAA,MACpB,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,IACP;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMM,oBACN,SAC0D;AACnD,WAAA,oBAAoB,SAAS,KAAK,oBAAoB;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMvD,WAA0C;AAChD,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAON,iBAAiB,cAAgC;AAChD,WAAA;AAAA,MACL,KAAK;AAAA,MACL,aAAa,KAAK,UAAU;AAAA,MAC5B,KAAK;AAAA,MACL;AAAA,IACF;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOM,iBAMN,SAM+D;AACzD,UAAA,aAAa,IAAI,aAMrB;AAAA,MACA,YAAY,KAAK;AAAA,MACjB,cAAc,KAAK;AAAA,MACnB,SAAS,KAAK;AAAA,MACd,sBAAsB,KAAK;AAAA,IAAA,CAC5B;AACD,eAAW,eAAe;AAAA,MACxB,GAAG,KAAK;AAAA,MACR,GAAG,QAAQ;AAAA,IACb;AACA,eAAW,gBAAgB,CAAC,GAAG,KAAK,aAAa;AACtC,eAAA,aAAc,QAAQ,cAAc,KAAK;AACzC,eAAA,cAAe,QAAQ,eAAe,KAAK;AAC3C,eAAA,eAAe,QAAQ,gBAAgB,KAAK;AAEvD,eAAW,aAAa,KAAK;AAC7B,eAAW,aAAa,IAAI;AAAA,MAC1B,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,IACP;AACO,WAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBT,OAKE,QAA2E;AACrE,UAAA,YAAY,aAAa,KAAK,UAAU;AACxC,UAAA,EAAE,gBAAgB,aAAA,IAAiB;AAAA,MACvC;AAAA,MACA;AAAA,MACA,KAAK;AAAA,IACP;AAEA,WAAO,KAAK,iBAAiB;AAAA,MAC3B,gBAAgB;AAAA,MAChB,cAAc;AAAA,QACZ,QAAQ;AAAA,MACV;AAAA,MACA,cACE,OAAO,KAAK,YAAY,EAAE,SAAS,IAAI,eAAe;AAAA,IAAA,CACzD;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaH,MACE,YAC2D;AAEvD,QAAA,OAAO,eAAe,UAAU;AAClC,WAAK,aAAa,SAAS;AACpB,aAAA;AAAA,IAAA;AAGT,UAAM,eAAe,WAAW,cAAc,KAAK,oBAAoB;AACvE,SAAK,aAAa,SAAS;AACpB,WAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA2BT,WACK,aAawD;AACrD,UAAA,YAAY,aAAa,KAAK,UAAU;AAG1C,QAAA,YAAY,SAAS,GAAG;AAC1B,YAAM,eAAe,YAAY,IAAI,CAAC,QAAQ;AACxC,YAAA,oBAAoB,GAAG,GAAG;AAExB,cAAA,IAAI,OAAO,cAAc,WAAW;AACtC,iBAAK,OAAO;AAAA,cACV,UAAU,IAAI,OAAO,SAAS,CAAC,mBAAmB,IAAI,OAAO,SAAS,8BAA8B,SAAS;AAAA,YAC/G;AAAA,UAAA;AAEI,gBAAA,YAAY,IAAI,OAAO;AAC7B,gBAAM,mBAAmB,KAAK,aAC1B,sBAAsB,WAAW,KAAK,UAAU,IAChD;AACJ,iBAAO,GAAG,gBAAgB,IAAI,IAAI,SAAS;AAAA,QAAA,WAClC,SAAS,GAAG,GAAG;AAEpB,cAAA,IAAI,cAAc,WAAW;AAC/B,iBAAK,OAAO;AAAA,cACV,UAAU,IAAI,UAAU,mBAAmB,IAAI,SAAS,8BAA8B,SAAS;AAAA,YACjG;AAAA,UAAA;AAEF,gBAAM,YAAY,IAAI;AACtB,gBAAM,mBAAmB,KAAK,aAC1B,sBAAsB,WAAW,KAAK,UAAU,IAChD;AACG,iBAAA;AAAA,QAAA,OACF;AACL,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QAAA;AAAA,MACF,CACD;AACD,WAAK,aAAa,UAAU;AACrB,aAAA;AAAA,IAAA;AAIH,UAAA,UAAU,YAAY,CAAC;AAGzB,QAAA,oBAAoB,OAAO,GAAG;AAE5B,UAAA,QAAQ,OAAO,cAAc,WAAW;AAC1C,aAAK,OAAO;AAAA,UACV,UAAU,QAAQ,OAAO,SAAS,CAAC,mBAAmB,QAAQ,OAAO,SAAS,8BAA8B,SAAS;AAAA,QACvH;AAAA,MAAA;AAEI,YAAA,YAAY,QAAQ,OAAO;AACjC,YAAM,mBAAmB,KAAK,aAC1B,sBAAsB,WAAW,KAAK,UAAU,IAChD;AACJ,WAAK,aAAa,UAAU,GAAG,gBAAgB,IAAI,QAAQ,SAAS;AAC7D,aAAA;AAAA,IAAA;AAIL,QAAA,SAAS,OAAO,GAAG;AAEjB,UAAA,QAAQ,cAAc,WAAW;AACnC,aAAK,OAAO;AAAA,UACV,UAAU,QAAQ,UAAU,mBAAmB,QAAQ,SAAS,8BAA8B,SAAS;AAAA,QACzG;AAAA,MAAA;AAGF,YAAM,YAAY,QAAQ;AACrB,WAAA,aAAa,UAAU,KAAK,aAC7B,sBAAsB,WAAW,KAAK,UAAU,IAChD;AACG,aAAA;AAAA,IAAA;AAGL,QAAA,KAAK,cAAc,SAAS;AAC1B,UAAA,MAAM,QAAQ,OAAO,GAAG;AAGxB,YAAA,QAAQ,WAAW,MAClB,OAAO,QAAQ,CAAC,MAAM,YAAY,SAAS,QAAQ,CAAC,CAAC,OACrD,QAAQ,CAAC,MAAM,SAAS,QAAQ,CAAC,MAAM,SACxC;AAEM,gBAAA,QAAQ,SAAS,QAAQ,CAAC,CAAC,IAC7B,QAAQ,CAAC,EAAE,YACX,QAAQ,CAAC;AACP,gBAAA,YAAY,QAAQ,CAAC;AACtB,eAAA,aAAa,UAAU,GAAG,sBAAsB,OAAO,KAAK,UAAU,CAAC,IAAI,SAAS;AAAA,QAAA,OACpF;AAEA,eAAA,aAAa,UAChB,QACA,IAAI,CAAC,CAAC,YAAY,SAAS,MAAM;AACjC,kBAAM,QAAQ,SAAS,UAAU,IAC7B,WAAW,YACX,OAAO,UAAU;AACrB,kBAAM,mBAAmB;AAAA,cACvB;AAAA,cACA,KAAK;AAAA,YACP;AACO,mBAAA,GAAG,gBAAgB,IAAI,SAAS;AAAA,UAAA,CACxC;AAAA,QAAA;AAAA,MACH,OACK;AAEL,aAAK,aAAa,UAAU;AAAA,UAC1B,OAAO,OAAO;AAAA,UACd,KAAK;AAAA,QACP;AAAA,MAAA;AAAA,IACF,OACK;AAED,UAAA,MAAM,QAAQ,OAAO,GAAG;AAExB,YAAA,QAAQ,WAAW,MAClB,OAAO,QAAQ,CAAC,MAAM,YAAY,SAAS,QAAQ,CAAC,CAAC,OACrD,QAAQ,CAAC,MAAM,SAAS,QAAQ,CAAC,MAAM,SACxC;AAEM,gBAAA,QAAQ,SAAS,QAAQ,CAAC,CAAC,IAC7B,QAAQ,CAAC,EAAE,YACX,QAAQ,CAAC;AACP,gBAAA,YAAY,QAAQ,CAAC;AAC3B,eAAK,aAAa,UAAU,GAAG,KAAK,IAAI,SAAS;AAAA,QAAA,OAC5C;AAEA,eAAA,aAAa,UAChB,QACA,IAAI,CAAC,CAAC,YAAY,SAAS,MAAM;AACjC,kBAAM,QAAQ,SAAS,UAAU,IAC7B,WAAW,YACX,OAAO,UAAU;AACd,mBAAA,GAAG,KAAK,IAAI,SAAS;AAAA,UAAA,CAC7B;AAAA,QAAA;AAAA,MACH,OACK;AACL,aAAK,aAAa,UAAU;AAAA,MAAA;AAAA,IAC9B;AAEK,WAAA;AAAA,EAAA;AAAA,EAGT,IACE,OAC2D;AAC3D,SAAK,aAAa,MAAM;AACjB,WAAA;AAAA,EAAA;AAAA,EAGT,KACE,OAC2D;AAC3D,SAAK,aAAa,OAAO;AAClB,WAAA;AAAA,EAAA;AAAA,EAGT,OAUE,aACA,UAqBA;AASM,UAAA,eAAe,KAAK,cAAc;AAAA,MAItC;AAAA,MACA,KAAK;AAAA,MACL;AAAA,MACA,MACE,IAAI,aAA0B;AAAA,QAC5B,YAAY;AAAA,QACZ,cAAc,KAAK;AAAA,QACnB,SAAS,KAAK;AAAA,QACd,sBAAsB,KAAK;AAAA,MAC5B,CAAA;AAAA,IACL;AAEK,SAAA,cAAc,KAAK,YAAY;AAC7B,WAAA;AAAA,EAAA;AAAA,EAGT,SAAiE;AAC/D,WAAO,KAAK,iBAAiB,EAAE,YAAY,SAAkB;AAAA,EAAA;AAAA,EAG/D,cAAsE;AACpE,WAAO,KAAK,iBAAiB,EAAE,YAAY,SAAkB;AAAA,EAAA;AAAA,EAG/D,QAAgE;AAC9D,WAAO,KAAK,iBAAiB;AAAA,MAC3B,aAAa;AAAA,MACb,cAAc,EAAE,OAAO,KAAK;AAAA,IAAA,CAC7B;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMK,mBAA2B;AAEjC,UAAM,qCAAqC,EAAE,GAAG,KAAK,aAAa;AAClE,UAAM,iBAAiB,mCAAmC;AAC1D,WAAO,mCAAmC;AAC1C,WAAO,mCAAmC;AAEtC,QAAA,cAAc,WAAW,kCAAkC;AAG/D,UAAM,cAAc,iBAChB,MAAM,QAAQ,cAAc,IAC1B,eAAe,IAAI,MAAM,IACzB,CAAC,OAAO,cAAc,CAAC,IACzB;AAEJ,UAAM,qBAAqB,6BAA6B;AAAA,MACtD,gBAAgB;AAAA,MAChB,eAAe,KAAK;AAAA,MACpB,OAAO,KAAK;AAAA,MACZ,cAAc,KAAK;AAAA,MACnB,QAAQ,KAAK;AAAA,IAAA,CACd;AAGD,QAAI,oBAAoB;AAEhB,YAAA,SAAS,mBAAmB,WAAW,GAAG,IAC5C,mBAAmB,MAAM,CAAC,IAC1B;AACJ,YAAM,YAAY,YAAY,SAAS,GAAG,IAAI,MAAM;AACpD,oBAAc,GAAG,WAAW,GAAG,SAAS,GAAG,MAAM;AAAA,IAAA;AAG5C,WAAA;AAAA,EAAA;AAAA,EAGT,MAAM,QACJ,SAcA;AACM,UAAA,gBAAgB,KAAK,oBAAoB,OAAO;AAChD,UAAA,cAAc,KAAK,iBAAiB;AAG1C,QAAI,KAAK,aAAa;AACpB,YAAMA,OAAM,KAAK,WAAW,MAAM,aAAa;AAAA,QAC7C,SAAS;AAAA,QACT,cAAc,cAAc;AAAA,QAC5B,YAAY,KAAK;AAAA,MAAA,CAClB;AACD,YAAMC,UAAS,MAAM,KAAK,QAAQ,aAAaD,MAAK,aAAa;AAEjE,UAAIC,QAAO,OAAO;AAChB,eAAO,EAAE,MAAM,QAAW,OAAOA,QAAO,MAAM;AAAA,MAAA;AAI1C,YAAA,QACJ,OAAOA,QAAO,SAAS,WAAW,OAAOA,QAAO,IAAI,IAAIA,QAAO;AACjE,aAAO,EAAE,MAAM,OAAiB,OAAO,OAAU;AAAA,IAAA;AAGnD,UAAM,MAAM,KAAK,WAAW,MAAM,aAAa;AAAA,MAC7C,SAAS,KAAK;AAAA,MACd,cAAc,cAAc;AAAA,MAC5B,YAAY,KAAK;AAAA,IAAA,CAClB;AAED,UAAM,SAAS,MAAM,KAAK,QAAQ,aAAa,KAAK,aAAa;AAEjE,QAAI,OAAO,OAAO;AAChB,aAAO,EAAE,MAAM,QAAW,OAAO,OAAO,MAAM;AAAA,IAAA;AAGzC,WAAA,qBAAqB,OAAO,MAAM;AAAA,MACvC,YAAY,KAAK;AAAA,MACjB,YAAY,KAAK;AAAA,MACjB,cAAc,KAAK;AAAA,MACnB,eAAe,KAAK;AAAA,MACpB,gBAAgB,mCAAS;AAAA,MACzB,cAAc,cAAc;AAAA,MAC5B,cAAc,KAAK;AAAA,MACnB,QAAQ,KAAK;AAAA,IAAA,CACd;AAAA,EAAA;AAAA,EAGH,iBAAyB;AACjB,UAAA,cAAc,KAAK,iBAAiB;AACnC,WAAA,KAAK,WAAW,UAAU,aAAa;AAAA,MAC5C,cAAc,KAAK;AAAA,MACnB,YAAY,KAAK;AAAA,IAAA,CAClB;AAAA,EAAA;AAAA,EAGH,mBAAgE;AACxD,UAAA,cAAc,KAAK,iBAAiB;AAC1C,UAAM,MAAM,KAAK,WAAW,MAAM,aAAa;AAAA,MAC7C,SAAS,KAAK;AAAA,MACd,cAAc,KAAK;AAAA,MACnB,YAAY,KAAK;AAAA,IAAA,CAClB;AAEM,WAAA;AAAA,MACL,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EAAA;AAAA,EAGF,UAAU,SAAiB,SAAmC;AACtD,UAAA,SAAS,KAAK,iBAAiB;AAC9B,WAAA,mBAAmB,SAAS,QAAQ,OAAO;AAAA,EAAA;AAAA,EAGpD,MAAM,gBACJ,UACA,SAWA;AAEI,QAAA,CAAC,SAAS,IAAI;AAChB,YAAM,QAAQ,MAAM;AAAA,QAClB;AAAA,QACA,SAAS,OACP,IAAI,KAAK,YAAY,IAAI,aAAa,KAAK,UAAU,CAAC;AAAA,MAC1D;AACO,aAAA,EAAE,MAAM,QAAW,MAAM;AAAA,IAAA;AAI9B,QAAA,SAAS,WAAW,KAAK;AAEvB,UAAA,KAAK,eAAe,OAAO;AACzB,YAAA,KAAK,eAAe,SAAS;AAC/B,iBAAO,EAAE,MAAM,MAAa,OAAO,OAAU;AAAA,QAAA;AAExC,eAAA;AAAA,UACL,MAAM;AAAA,UACN,OAAO,IAAI,yBAAyB,OAAO,CAAC;AAAA,QAC9C;AAAA,MAAA;AAEF,aAAO,EAAE,MAAM,IAAW,OAAO,OAAU;AAAA,IAAA;AAIzC,QAAA;AACA,QAAA;AACQ,gBAAA,MAAM,cAAc,QAAQ;AAAA,aAC/B,KAAK;AAEZ,UAAI,eAAe,eAAe,SAAS,WAAW,KAAK;AAEzD,eAAO,EAAE,MAAM,IAAW,OAAO,OAAU;AAAA,MAAA;AAEtC,aAAA;AAAA,QACL,MAAM;AAAA,QACN,OAAO;AAAA,UACL,MAAM;AAAA,UACN,SAAS,kCAAkC,eAAe,QAAQ,IAAI,UAAU,eAAe;AAAA,UAC/F,+BAAe,KAAK;AAAA,QAAA;AAAA,MAExB;AAAA,IAAA;AAGF,QAAI,CAAC,SAAS;AACL,aAAA;AAAA,QACL,MAAM;AAAA,QACN,OAAO;AAAA,UACL,MAAM;AAAA,UACN,SAAS;AAAA,UACT,+BAAe,KAAK;AAAA,QAAA;AAAA,MAExB;AAAA,IAAA;AAGI,UAAA,gBAAgB,KAAK,oBAAoB,OAAO;AACtD,WAAO,qBAAqB,SAAS;AAAA,MACnC,YAAY,KAAK;AAAA,MACjB,YAAY,KAAK;AAAA,MACjB,cAAc,KAAK;AAAA,MACnB,eAAe,KAAK;AAAA,MACpB,gBAAgB,mCAAS;AAAA,MACzB,cAAc,cAAc;AAAA,MAC5B,cAAc,KAAK;AAAA,MACnB,QAAQ,KAAK;AAAA,IAAA,CACd;AAAA,EAAA;AAEL;"}
|
|
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 ConditionallyWithSpecialColumns,\n NormalizeIncludeSpecialColumns,\n ExecuteMethodOptions,\n} from \"../../types\";\nimport { RecordCountMismatchError } from \"../../errors\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport { transformOrderByField } 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 getTableName,\n} from \"../../orm/table\";\nimport {\n ExpandBuilder,\n type ExpandConfig,\n type ExpandedRelations,\n resolveTableId,\n mergeExecuteOptions,\n processQueryResponse,\n processSelectWithRenames,\n buildSelectExpandQueryString,\n createODataRequest,\n} from \"../builders/index\";\nimport { QueryUrlBuilder, type NavigationConfig } from \"./url-builder\";\nimport type {\n TypeSafeOrderBy,\n QueryReturnType,\n SystemColumnsOption,\n} 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 DatabaseIncludeSpecialColumns extends boolean = false,\n SystemCols extends SystemColumnsOption | undefined = undefined,\n> implements\n ExecutableBuilder<\n QueryReturnType<\n InferSchemaOutputFromFMTable<Occ>,\n Selected,\n SingleMode,\n IsCount,\n Expands,\n SystemCols\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 databaseIncludeSpecialColumns: 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 // System columns requested via select() second argument\n private systemColumns?: SystemColumnsOption;\n private logger: InternalLogger;\n\n constructor(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n databaseUseEntityIds?: boolean;\n databaseIncludeSpecialColumns?: 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.databaseIncludeSpecialColumns =\n config.databaseIncludeSpecialColumns ?? 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 and includeSpecialColumns with per-request options\n */\n private mergeExecuteOptions(\n options?: RequestInit & FFetchOptions & ExecuteOptions,\n ): RequestInit &\n FFetchOptions & {\n useEntityIds?: boolean;\n includeSpecialColumns?: boolean;\n } {\n const merged = mergeExecuteOptions(options, this.databaseUseEntityIds);\n return {\n ...merged,\n includeSpecialColumns:\n options?.includeSpecialColumns ?? this.databaseIncludeSpecialColumns,\n };\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 NewSystemCols extends SystemColumnsOption | undefined = SystemCols,\n >(changes: {\n selectedFields?: NewSelected;\n singleMode?: NewSingle;\n isCountMode?: NewCount;\n queryOptions?: Partial<QueryOptions<InferSchemaOutputFromFMTable<Occ>>>;\n fieldMapping?: Record<string, string>;\n systemColumns?: NewSystemCols;\n }): QueryBuilder<\n Occ,\n NewSelected,\n NewSingle,\n NewCount,\n Expands,\n DatabaseIncludeSpecialColumns,\n NewSystemCols\n > {\n const newBuilder = new QueryBuilder<\n Occ,\n NewSelected,\n NewSingle,\n NewCount,\n Expands,\n DatabaseIncludeSpecialColumns,\n NewSystemCols\n >({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n databaseIncludeSpecialColumns: this.databaseIncludeSpecialColumns,\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 newBuilder.systemColumns =\n changes.systemColumns !== undefined\n ? changes.systemColumns\n : this.systemColumns;\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 * @example\n * // Include system columns (ROWID, ROWMODID) when using select()\n * db.from(users).list().select(\n * { name: users.name },\n * { ROWID: true, ROWMODID: true }\n * )\n *\n * @param fields - Object mapping output keys to column references (container fields excluded)\n * @param systemColumns - Optional object to request system columns (ROWID, ROWMODID)\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 TSystemCols extends SystemColumnsOption = {},\n >(\n fields: TSelect,\n systemColumns?: TSystemCols,\n ): QueryBuilder<\n Occ,\n TSelect,\n SingleMode,\n IsCount,\n Expands,\n DatabaseIncludeSpecialColumns,\n TSystemCols\n > {\n const tableName = getTableName(this.occurrence);\n const { selectedFields, fieldMapping } = processSelectWithRenames(\n fields,\n tableName,\n this.logger,\n );\n\n // Add system columns to selectedFields if requested\n const finalSelectedFields = [...selectedFields];\n if (systemColumns?.ROWID) {\n finalSelectedFields.push(\"ROWID\");\n }\n if (systemColumns?.ROWMODID) {\n finalSelectedFields.push(\"ROWMODID\");\n }\n\n return this.cloneWithChanges({\n selectedFields: fields as any,\n queryOptions: {\n select: finalSelectedFields,\n },\n fieldMapping:\n Object.keys(fieldMapping).length > 0 ? fieldMapping : undefined,\n systemColumns: systemColumns as any,\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<\n Occ,\n Selected,\n SingleMode,\n IsCount,\n Expands,\n DatabaseIncludeSpecialColumns,\n SystemCols\n > {\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<\n Occ,\n Selected,\n SingleMode,\n IsCount,\n Expands,\n DatabaseIncludeSpecialColumns,\n SystemCols\n > {\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<\n Occ,\n Selected,\n SingleMode,\n IsCount,\n Expands,\n DatabaseIncludeSpecialColumns,\n SystemCols\n > {\n this.queryOptions.top = count;\n return this;\n }\n\n skip(\n count: number,\n ): QueryBuilder<\n Occ,\n Selected,\n SingleMode,\n IsCount,\n Expands,\n DatabaseIncludeSpecialColumns,\n SystemCols\n > {\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 DatabaseIncludeSpecialColumns,\n SystemCols\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 DatabaseIncludeSpecialColumns\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<\n TargetTable,\n any,\n any,\n any,\n any,\n DatabaseIncludeSpecialColumns,\n undefined\n >({\n occurrence: targetTable,\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n databaseIncludeSpecialColumns: this.databaseIncludeSpecialColumns,\n }),\n );\n\n this.expandConfigs.push(expandConfig);\n return this as any;\n }\n\n single(): QueryBuilder<\n Occ,\n Selected,\n \"exact\",\n IsCount,\n Expands,\n DatabaseIncludeSpecialColumns,\n SystemCols\n > {\n return this.cloneWithChanges({ singleMode: \"exact\" as const });\n }\n\n maybeSingle(): QueryBuilder<\n Occ,\n Selected,\n \"maybe\",\n IsCount,\n Expands,\n DatabaseIncludeSpecialColumns,\n SystemCols\n > {\n return this.cloneWithChanges({ singleMode: \"maybe\" as const });\n }\n\n count(): QueryBuilder<\n Occ,\n Selected,\n SingleMode,\n true,\n Expands,\n DatabaseIncludeSpecialColumns,\n SystemCols\n > {\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(includeSpecialColumns?: boolean): 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 // Use merged includeSpecialColumns if provided, otherwise use database-level default\n const finalIncludeSpecialColumns =\n includeSpecialColumns ?? this.databaseIncludeSpecialColumns;\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 includeSpecialColumns: finalIncludeSpecialColumns,\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 ConditionallyWithSpecialColumns<\n QueryReturnType<\n InferSchemaOutputFromFMTable<Occ>,\n Selected,\n SingleMode,\n IsCount,\n Expands,\n SystemCols\n >,\n // Use the merged value: if explicitly provided in options, use that; otherwise use database default\n NormalizeIncludeSpecialColumns<\n EO[\"includeSpecialColumns\"],\n DatabaseIncludeSpecialColumns\n >,\n // Check if select was applied: if Selected is Record (object select) or a subset of keys, select was applied\n Selected extends Record<string, Column<any, any, any>>\n ? true\n : Selected extends keyof InferSchemaOutputFromFMTable<Occ>\n ? false\n : true\n >,\n EO[\"includeODataAnnotations\"] extends true ? true : false\n >\n >\n > {\n const mergedOptions = this.mergeExecuteOptions(options);\n const queryString = this.buildQueryString(\n mergedOptions.includeSpecialColumns,\n );\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 // Check if select was applied (runtime check)\n const hasSelect = this.queryOptions.select !== undefined;\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 includeSpecialColumns: mergedOptions.includeSpecialColumns,\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 SystemCols\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 // Check if select was applied (runtime check)\n const hasSelect = this.queryOptions.select !== undefined;\n\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 includeSpecialColumns: mergedOptions.includeSpecialColumns,\n fieldMapping: this.fieldMapping,\n logger: this.logger,\n });\n }\n}\n"],"names":["url","result"],"mappings":"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"}
|
|
@@ -48,9 +48,30 @@ export type ResolveExpandType<Exp extends {
|
|
|
48
48
|
export type ResolveExpandedRelations<Exps extends ExpandedRelations> = {
|
|
49
49
|
[K in keyof Exps]: ResolveExpandType<Exps[K]>[];
|
|
50
50
|
};
|
|
51
|
-
|
|
51
|
+
/**
|
|
52
|
+
* System columns option for select() method.
|
|
53
|
+
* Allows explicitly requesting ROWID and/or ROWMODID when using select().
|
|
54
|
+
*/
|
|
55
|
+
export type SystemColumnsOption = {
|
|
56
|
+
ROWID?: boolean;
|
|
57
|
+
ROWMODID?: boolean;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Extract system columns type from SystemColumnsOption.
|
|
61
|
+
* Returns an object type with ROWID and/or ROWMODID properties when set to true.
|
|
62
|
+
*/
|
|
63
|
+
export type SystemColumnsFromOption<T extends SystemColumnsOption | undefined> = (T extends {
|
|
64
|
+
ROWID: true;
|
|
65
|
+
} ? {
|
|
66
|
+
ROWID: number;
|
|
67
|
+
} : {}) & (T extends {
|
|
68
|
+
ROWMODID: true;
|
|
69
|
+
} ? {
|
|
70
|
+
ROWMODID: number;
|
|
71
|
+
} : {});
|
|
72
|
+
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, SystemCols extends SystemColumnsOption | undefined = undefined> = IsCount extends true ? number : [
|
|
52
73
|
Selected
|
|
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>)[] : [
|
|
74
|
+
] extends [Record<string, Column<any, any, any, any>>] ? SingleMode extends "exact" ? MapSelectToReturnType<Selected, T> & ResolveExpandedRelations<Expands> & SystemColumnsFromOption<SystemCols> : SingleMode extends "maybe" ? (MapSelectToReturnType<Selected, T> & ResolveExpandedRelations<Expands> & SystemColumnsFromOption<SystemCols>) | null : (MapSelectToReturnType<Selected, T> & ResolveExpandedRelations<Expands> & SystemColumnsFromOption<SystemCols>)[] : [
|
|
54
75
|
Selected
|
|
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;
|
|
76
|
+
] extends [keyof T] ? SingleMode extends "exact" ? Pick<T, Selected> & ResolveExpandedRelations<Expands> & SystemColumnsFromOption<SystemCols> : SingleMode extends "maybe" ? (Pick<T, Selected> & ResolveExpandedRelations<Expands> & SystemColumnsFromOption<SystemCols>) | null : (Pick<T, Selected> & ResolveExpandedRelations<Expands> & SystemColumnsFromOption<SystemCols>)[] : never;
|
|
56
77
|
export {};
|