@proofkit/fmodata 0.1.0-alpha.14 → 0.1.0-alpha.15
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/dist/esm/client/batch-builder.d.ts +2 -3
- package/dist/esm/client/batch-builder.js.map +1 -1
- package/dist/esm/client/builders/expand-builder.d.ts +1 -1
- package/dist/esm/client/builders/expand-builder.js.map +1 -1
- package/dist/esm/client/delete-builder.d.ts +2 -5
- package/dist/esm/client/delete-builder.js.map +1 -1
- package/dist/esm/client/entity-set.d.ts +1 -1
- package/dist/esm/client/entity-set.js.map +1 -1
- package/dist/esm/client/insert-builder.d.ts +2 -3
- package/dist/esm/client/insert-builder.js.map +1 -1
- package/dist/esm/client/query/query-builder.d.ts +3 -4
- package/dist/esm/client/query/query-builder.js.map +1 -1
- package/dist/esm/client/query-builder.d.ts +1 -1
- package/dist/esm/client/record-builder.d.ts +3 -4
- package/dist/esm/client/record-builder.js.map +1 -1
- package/dist/esm/client/update-builder.d.ts +2 -5
- package/dist/esm/client/update-builder.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/types.d.ts +58 -0
- package/dist/esm/types.js.map +1 -1
- package/package.json +3 -1
- package/src/client/batch-builder.ts +2 -1
- package/src/client/builders/expand-builder.ts +3 -3
- package/src/client/delete-builder.ts +2 -1
- package/src/client/entity-set.ts +1 -1
- package/src/client/insert-builder.ts +2 -1
- package/src/client/query/query-builder.ts +13 -3
- package/src/client/query-builder.ts +1 -1
- package/src/client/record-builder.ts +13 -3
- package/src/client/update-builder.ts +17 -8
- package/src/index.ts +3 -0
- package/src/types.ts +68 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { ExecutableBuilder, ExecutionContext, Result, ExecuteOptions, BatchResult } from '../types.js';
|
|
2
|
-
import { FFetchOptions } from '@fetchkit/ffetch';
|
|
1
|
+
import { ExecutableBuilder, ExecutionContext, Result, ExecuteOptions, BatchResult, ExecuteMethodOptions } from '../types.js';
|
|
3
2
|
/**
|
|
4
3
|
* Helper type to extract result types from a tuple of ExecutableBuilders.
|
|
5
4
|
* Uses a mapped type which TypeScript 4.1+ can handle for tuples.
|
|
@@ -52,6 +51,6 @@ export declare class BatchBuilder<Builders extends readonly ExecutableBuilder<an
|
|
|
52
51
|
* @param options - Optional fetch options and batch-specific options (includes beforeRequest hook)
|
|
53
52
|
* @returns A BatchResult containing individual results for each operation
|
|
54
53
|
*/
|
|
55
|
-
execute<EO extends ExecuteOptions>(options?:
|
|
54
|
+
execute<EO extends ExecuteOptions>(options?: ExecuteMethodOptions<EO>): Promise<BatchResult<ExtractTupleTypes<Builders>>>;
|
|
56
55
|
}
|
|
57
56
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batch-builder.js","sources":["../../../src/client/batch-builder.ts"],"sourcesContent":["import type {\n ExecutableBuilder,\n ExecutionContext,\n Result,\n ExecuteOptions,\n BatchResult,\n BatchItemResult,\n} from \"../types\";\nimport { BatchTruncatedError } from \"../errors\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport {\n formatBatchRequestFromNative,\n parseBatchResponse,\n type ParsedBatchResponse,\n} from \"./batch-request\";\n\n/**\n * Helper type to extract result types from a tuple of ExecutableBuilders.\n * Uses a mapped type which TypeScript 4.1+ can handle for tuples.\n */\ntype ExtractTupleTypes<T extends readonly ExecutableBuilder<any>[]> = {\n [K in keyof T]: T[K] extends ExecutableBuilder<infer U> ? U : never;\n};\n\n/**\n * Converts a ParsedBatchResponse to a native Response object\n * @param parsed - The parsed batch response\n * @returns A native Response object\n */\nfunction parsedToResponse(parsed: ParsedBatchResponse): Response {\n const headers = new Headers(parsed.headers);\n\n // Handle null body\n if (parsed.body === null || parsed.body === undefined) {\n return new Response(null, {\n status: parsed.status,\n statusText: parsed.statusText,\n headers,\n });\n }\n\n // Convert body to string if it's not already\n const bodyString =\n typeof parsed.body === \"string\" ? parsed.body : JSON.stringify(parsed.body);\n\n // Handle 204 No Content status - it cannot have a body per HTTP spec\n // If FileMaker returns 204 with a body, treat it as 200\n let status = parsed.status;\n if (status === 204 && bodyString && bodyString.trim() !== \"\") {\n status = 200;\n }\n\n return new Response(status === 204 ? null : bodyString, {\n status: status,\n statusText: parsed.statusText,\n headers,\n });\n}\n\n/**\n * Builder for batch operations that allows multiple queries to be executed together\n * in a single transactional request.\n *\n * Note: BatchBuilder does not implement ExecutableBuilder because execute() returns\n * BatchResult instead of Result, which is a different return type structure.\n */\nexport class BatchBuilder<Builders extends readonly ExecutableBuilder<any>[]> {\n private builders: ExecutableBuilder<any>[];\n private readonly originalBuilders: Builders;\n\n constructor(\n builders: Builders,\n private readonly databaseName: string,\n private readonly context: ExecutionContext,\n ) {\n // Convert readonly tuple to mutable array for dynamic additions\n this.builders = [...builders];\n // Store original tuple for type preservation\n this.originalBuilders = builders;\n }\n\n /**\n * Add a request to the batch dynamically.\n * This allows building up batch operations programmatically.\n *\n * @param builder - An executable builder to add to the batch\n * @returns This BatchBuilder for method chaining\n * @example\n * ```ts\n * const batch = db.batch([]);\n * batch.addRequest(db.from('contacts').list());\n * batch.addRequest(db.from('users').list());\n * const result = await batch.execute();\n * ```\n */\n addRequest<T>(builder: ExecutableBuilder<T>): this {\n this.builders.push(builder);\n return this;\n }\n\n /**\n * Get the request configuration for this batch operation.\n * This is used internally by the execution system.\n */\n getRequestConfig(): { method: string; url: string; body?: any } {\n // Note: This method is kept for compatibility but batch operations\n // should use execute() directly which handles the full Request/Response flow\n return {\n method: \"POST\",\n url: `/${this.databaseName}/$batch`,\n body: undefined, // Body is constructed in execute()\n };\n }\n\n toRequest(baseUrl: string, options?: ExecuteOptions): Request {\n // Batch operations are not designed to be nested, but we provide\n // a basic implementation for interface compliance\n const fullUrl = `${baseUrl}/${this.databaseName}/$batch`;\n return new Request(fullUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"multipart/mixed\",\n \"OData-Version\": \"4.0\",\n },\n });\n }\n\n async processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<Result<any>> {\n // This should not typically be called for batch operations\n // as they handle their own response processing\n return {\n data: undefined,\n error: {\n name: \"NotImplementedError\",\n message: \"Batch operations handle response processing internally\",\n timestamp: new Date(),\n } as any,\n };\n }\n\n /**\n * Execute the batch operation.\n *\n * @param options - Optional fetch options and batch-specific options (includes beforeRequest hook)\n * @returns A BatchResult containing individual results for each operation\n */\n async execute<EO extends ExecuteOptions>(\n options?: RequestInit & FFetchOptions & EO,\n ): Promise<BatchResult<ExtractTupleTypes<Builders>>> {\n const baseUrl = this.context._getBaseUrl?.();\n if (!baseUrl) {\n // Return BatchResult with all operations marked as failed\n const errorCount = this.builders.length;\n const results: BatchItemResult<any>[] = this.builders.map((_, i) => ({\n data: undefined,\n error: {\n name: \"ConfigurationError\",\n message:\n \"Base URL not available - execution context must implement _getBaseUrl()\",\n timestamp: new Date(),\n } as any,\n status: 0,\n }));\n\n return {\n results: results as any,\n successCount: 0,\n errorCount,\n truncated: false,\n firstErrorIndex: 0,\n };\n }\n\n try {\n // Convert builders to native Request objects\n const requests: Request[] = this.builders.map((builder) =>\n builder.toRequest(baseUrl, options),\n );\n\n // Format batch request (automatically groups mutations into changesets)\n const { body, boundary } = await formatBatchRequestFromNative(\n requests,\n baseUrl,\n );\n\n // Execute the batch request\n const response = await this.context._makeRequest<string>(\n `/${this.databaseName}/$batch`,\n {\n ...options,\n method: \"POST\",\n headers: {\n ...options?.headers,\n \"Content-Type\": `multipart/mixed; boundary=${boundary}`,\n \"OData-Version\": \"4.0\",\n },\n body,\n },\n );\n\n if (response.error) {\n // Return BatchResult with all operations marked as failed\n const errorCount = this.builders.length;\n const results: BatchItemResult<any>[] = this.builders.map((_, i) => ({\n data: undefined,\n error: response.error,\n status: 0,\n }));\n\n return {\n results: results as any,\n successCount: 0,\n errorCount,\n truncated: false,\n firstErrorIndex: 0,\n };\n }\n\n // Extract the actual boundary from the response\n // FileMaker uses its own boundary, not the one we sent\n const firstLine =\n response.data.split(\"\\r\\n\")[0] || response.data.split(\"\\n\")[0] || \"\";\n const actualBoundary = firstLine.startsWith(\"--\")\n ? firstLine.substring(2)\n : boundary;\n\n // Parse the multipart response\n const contentTypeHeader = `multipart/mixed; boundary=${actualBoundary}`;\n const parsedResponses = parseBatchResponse(\n response.data,\n contentTypeHeader,\n );\n\n // Process each response using the corresponding builder\n // Build BatchResult with per-item results\n type ResultTuple = ExtractTupleTypes<Builders>;\n\n const results: BatchItemResult<any>[] = [];\n let successCount = 0;\n let errorCount = 0;\n let firstErrorIndex: number | null = null;\n const truncated = parsedResponses.length < this.builders.length;\n\n // Process builders sequentially to preserve tuple order\n for (let i = 0; i < this.builders.length; i++) {\n const builder = this.builders[i];\n const parsed = parsedResponses[i];\n\n if (!parsed) {\n // Truncated - operation never executed\n const failedAtIndex = firstErrorIndex ?? i;\n results.push({\n data: undefined,\n error: new BatchTruncatedError(i, failedAtIndex),\n status: 0,\n });\n errorCount++;\n continue;\n }\n\n if (!builder) {\n // Should not happen, but handle gracefully\n results.push({\n data: undefined,\n error: {\n name: \"BatchError\",\n message: `Builder at index ${i} is undefined`,\n timestamp: new Date(),\n } as any,\n status: parsed.status,\n });\n errorCount++;\n if (firstErrorIndex === null) firstErrorIndex = i;\n continue;\n }\n\n // Convert parsed response to native Response\n const nativeResponse = parsedToResponse(parsed);\n\n // Let the builder process its own response\n const result = await builder.processResponse(nativeResponse, options);\n\n if (result.error) {\n results.push({\n data: undefined,\n error: result.error,\n status: parsed.status,\n });\n errorCount++;\n if (firstErrorIndex === null) firstErrorIndex = i;\n } else {\n results.push({\n data: result.data,\n error: undefined,\n status: parsed.status,\n });\n successCount++;\n }\n }\n\n return {\n results: results as any,\n successCount,\n errorCount,\n truncated,\n firstErrorIndex,\n };\n } catch (err) {\n // On exception, return a BatchResult with all operations marked as failed\n const errorCount = this.builders.length;\n const results: BatchItemResult<any>[] = this.builders.map((_, i) => ({\n data: undefined,\n error: {\n name: \"BatchError\",\n message: err instanceof Error ? err.message : \"Unknown error\",\n timestamp: new Date(),\n } as any,\n status: 0,\n }));\n\n return {\n results: results as any,\n successCount: 0,\n errorCount,\n truncated: false,\n firstErrorIndex: 0,\n };\n }\n }\n}\n"],"names":["errorCount","results"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"batch-builder.js","sources":["../../../src/client/batch-builder.ts"],"sourcesContent":["import type {\n ExecutableBuilder,\n ExecutionContext,\n Result,\n ExecuteOptions,\n BatchResult,\n BatchItemResult,\n ExecuteMethodOptions,\n} from \"../types\";\nimport { BatchTruncatedError } from \"../errors\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport {\n formatBatchRequestFromNative,\n parseBatchResponse,\n type ParsedBatchResponse,\n} from \"./batch-request\";\n\n/**\n * Helper type to extract result types from a tuple of ExecutableBuilders.\n * Uses a mapped type which TypeScript 4.1+ can handle for tuples.\n */\ntype ExtractTupleTypes<T extends readonly ExecutableBuilder<any>[]> = {\n [K in keyof T]: T[K] extends ExecutableBuilder<infer U> ? U : never;\n};\n\n/**\n * Converts a ParsedBatchResponse to a native Response object\n * @param parsed - The parsed batch response\n * @returns A native Response object\n */\nfunction parsedToResponse(parsed: ParsedBatchResponse): Response {\n const headers = new Headers(parsed.headers);\n\n // Handle null body\n if (parsed.body === null || parsed.body === undefined) {\n return new Response(null, {\n status: parsed.status,\n statusText: parsed.statusText,\n headers,\n });\n }\n\n // Convert body to string if it's not already\n const bodyString =\n typeof parsed.body === \"string\" ? parsed.body : JSON.stringify(parsed.body);\n\n // Handle 204 No Content status - it cannot have a body per HTTP spec\n // If FileMaker returns 204 with a body, treat it as 200\n let status = parsed.status;\n if (status === 204 && bodyString && bodyString.trim() !== \"\") {\n status = 200;\n }\n\n return new Response(status === 204 ? null : bodyString, {\n status: status,\n statusText: parsed.statusText,\n headers,\n });\n}\n\n/**\n * Builder for batch operations that allows multiple queries to be executed together\n * in a single transactional request.\n *\n * Note: BatchBuilder does not implement ExecutableBuilder because execute() returns\n * BatchResult instead of Result, which is a different return type structure.\n */\nexport class BatchBuilder<Builders extends readonly ExecutableBuilder<any>[]> {\n private builders: ExecutableBuilder<any>[];\n private readonly originalBuilders: Builders;\n\n constructor(\n builders: Builders,\n private readonly databaseName: string,\n private readonly context: ExecutionContext,\n ) {\n // Convert readonly tuple to mutable array for dynamic additions\n this.builders = [...builders];\n // Store original tuple for type preservation\n this.originalBuilders = builders;\n }\n\n /**\n * Add a request to the batch dynamically.\n * This allows building up batch operations programmatically.\n *\n * @param builder - An executable builder to add to the batch\n * @returns This BatchBuilder for method chaining\n * @example\n * ```ts\n * const batch = db.batch([]);\n * batch.addRequest(db.from('contacts').list());\n * batch.addRequest(db.from('users').list());\n * const result = await batch.execute();\n * ```\n */\n addRequest<T>(builder: ExecutableBuilder<T>): this {\n this.builders.push(builder);\n return this;\n }\n\n /**\n * Get the request configuration for this batch operation.\n * This is used internally by the execution system.\n */\n getRequestConfig(): { method: string; url: string; body?: any } {\n // Note: This method is kept for compatibility but batch operations\n // should use execute() directly which handles the full Request/Response flow\n return {\n method: \"POST\",\n url: `/${this.databaseName}/$batch`,\n body: undefined, // Body is constructed in execute()\n };\n }\n\n toRequest(baseUrl: string, options?: ExecuteOptions): Request {\n // Batch operations are not designed to be nested, but we provide\n // a basic implementation for interface compliance\n const fullUrl = `${baseUrl}/${this.databaseName}/$batch`;\n return new Request(fullUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"multipart/mixed\",\n \"OData-Version\": \"4.0\",\n },\n });\n }\n\n async processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<Result<any>> {\n // This should not typically be called for batch operations\n // as they handle their own response processing\n return {\n data: undefined,\n error: {\n name: \"NotImplementedError\",\n message: \"Batch operations handle response processing internally\",\n timestamp: new Date(),\n } as any,\n };\n }\n\n /**\n * Execute the batch operation.\n *\n * @param options - Optional fetch options and batch-specific options (includes beforeRequest hook)\n * @returns A BatchResult containing individual results for each operation\n */\n async execute<EO extends ExecuteOptions>(\n options?: ExecuteMethodOptions<EO>,\n ): Promise<BatchResult<ExtractTupleTypes<Builders>>> {\n const baseUrl = this.context._getBaseUrl?.();\n if (!baseUrl) {\n // Return BatchResult with all operations marked as failed\n const errorCount = this.builders.length;\n const results: BatchItemResult<any>[] = this.builders.map((_, i) => ({\n data: undefined,\n error: {\n name: \"ConfigurationError\",\n message:\n \"Base URL not available - execution context must implement _getBaseUrl()\",\n timestamp: new Date(),\n } as any,\n status: 0,\n }));\n\n return {\n results: results as any,\n successCount: 0,\n errorCount,\n truncated: false,\n firstErrorIndex: 0,\n };\n }\n\n try {\n // Convert builders to native Request objects\n const requests: Request[] = this.builders.map((builder) =>\n builder.toRequest(baseUrl, options),\n );\n\n // Format batch request (automatically groups mutations into changesets)\n const { body, boundary } = await formatBatchRequestFromNative(\n requests,\n baseUrl,\n );\n\n // Execute the batch request\n const response = await this.context._makeRequest<string>(\n `/${this.databaseName}/$batch`,\n {\n ...options,\n method: \"POST\",\n headers: {\n ...options?.headers,\n \"Content-Type\": `multipart/mixed; boundary=${boundary}`,\n \"OData-Version\": \"4.0\",\n },\n body,\n },\n );\n\n if (response.error) {\n // Return BatchResult with all operations marked as failed\n const errorCount = this.builders.length;\n const results: BatchItemResult<any>[] = this.builders.map((_, i) => ({\n data: undefined,\n error: response.error,\n status: 0,\n }));\n\n return {\n results: results as any,\n successCount: 0,\n errorCount,\n truncated: false,\n firstErrorIndex: 0,\n };\n }\n\n // Extract the actual boundary from the response\n // FileMaker uses its own boundary, not the one we sent\n const firstLine =\n response.data.split(\"\\r\\n\")[0] || response.data.split(\"\\n\")[0] || \"\";\n const actualBoundary = firstLine.startsWith(\"--\")\n ? firstLine.substring(2)\n : boundary;\n\n // Parse the multipart response\n const contentTypeHeader = `multipart/mixed; boundary=${actualBoundary}`;\n const parsedResponses = parseBatchResponse(\n response.data,\n contentTypeHeader,\n );\n\n // Process each response using the corresponding builder\n // Build BatchResult with per-item results\n type ResultTuple = ExtractTupleTypes<Builders>;\n\n const results: BatchItemResult<any>[] = [];\n let successCount = 0;\n let errorCount = 0;\n let firstErrorIndex: number | null = null;\n const truncated = parsedResponses.length < this.builders.length;\n\n // Process builders sequentially to preserve tuple order\n for (let i = 0; i < this.builders.length; i++) {\n const builder = this.builders[i];\n const parsed = parsedResponses[i];\n\n if (!parsed) {\n // Truncated - operation never executed\n const failedAtIndex = firstErrorIndex ?? i;\n results.push({\n data: undefined,\n error: new BatchTruncatedError(i, failedAtIndex),\n status: 0,\n });\n errorCount++;\n continue;\n }\n\n if (!builder) {\n // Should not happen, but handle gracefully\n results.push({\n data: undefined,\n error: {\n name: \"BatchError\",\n message: `Builder at index ${i} is undefined`,\n timestamp: new Date(),\n } as any,\n status: parsed.status,\n });\n errorCount++;\n if (firstErrorIndex === null) firstErrorIndex = i;\n continue;\n }\n\n // Convert parsed response to native Response\n const nativeResponse = parsedToResponse(parsed);\n\n // Let the builder process its own response\n const result = await builder.processResponse(nativeResponse, options);\n\n if (result.error) {\n results.push({\n data: undefined,\n error: result.error,\n status: parsed.status,\n });\n errorCount++;\n if (firstErrorIndex === null) firstErrorIndex = i;\n } else {\n results.push({\n data: result.data,\n error: undefined,\n status: parsed.status,\n });\n successCount++;\n }\n }\n\n return {\n results: results as any,\n successCount,\n errorCount,\n truncated,\n firstErrorIndex,\n };\n } catch (err) {\n // On exception, return a BatchResult with all operations marked as failed\n const errorCount = this.builders.length;\n const results: BatchItemResult<any>[] = this.builders.map((_, i) => ({\n data: undefined,\n error: {\n name: \"BatchError\",\n message: err instanceof Error ? err.message : \"Unknown error\",\n timestamp: new Date(),\n } as any,\n status: 0,\n }));\n\n return {\n results: results as any,\n successCount: 0,\n errorCount,\n truncated: false,\n firstErrorIndex: 0,\n };\n }\n }\n}\n"],"names":["errorCount","results"],"mappings":"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"}
|
|
@@ -27,7 +27,7 @@ export declare class ExpandBuilder {
|
|
|
27
27
|
* @param builderFactory - Function that creates a QueryBuilder for the target table
|
|
28
28
|
* @returns ExpandConfig to add to the builder's expandConfigs array
|
|
29
29
|
*/
|
|
30
|
-
processExpand<TargetTable extends FMTable<any, any
|
|
30
|
+
processExpand<TargetTable extends FMTable<any, any>, Builder = any>(targetTable: TargetTable, sourceTable: FMTable<any, any> | undefined, callback?: (builder: Builder) => Builder, builderFactory?: () => Builder): ExpandConfig;
|
|
31
31
|
/**
|
|
32
32
|
* Builds a single expand string with its options.
|
|
33
33
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expand-builder.js","sources":["../../../../src/client/builders/expand-builder.ts"],"sourcesContent":["import { QueryOptions } from \"odata-query\";\nimport buildQuery from \"odata-query\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { FMTable } from \"../../orm/table\";\nimport {\n getBaseTableConfig,\n getTableName,\n getNavigationPaths,\n} from \"../../orm/table\";\nimport type { ExpandValidationConfig } from \"../../validation\";\nimport type { ExpandConfig } from \"./shared-types\";\nimport { formatSelectFields } from \"./select-utils\";\nimport { getDefaultSelectFields } from \"./default-select\";\n\n/**\n * Builds OData expand query strings and validation configs.\n * Handles nested expands recursively and transforms relation names to FMTIDs\n * when using entity IDs.\n */\nexport class ExpandBuilder {\n constructor(private useEntityIds: boolean) {}\n\n /**\n * Builds OData $expand query string from expand configurations.\n */\n buildExpandString(configs: ExpandConfig[]): string {\n if (configs.length === 0) return \"\";\n\n return configs.map((config) => this.buildSingleExpand(config)).join(\",\");\n }\n\n /**\n * Builds validation configs for expanded navigation properties.\n */\n buildValidationConfigs(configs: ExpandConfig[]): ExpandValidationConfig[] {\n return configs.map((config) => {\n const targetTable = config.targetTable;\n\n let targetSchema: Record<string, StandardSchemaV1> | undefined;\n if (targetTable) {\n const baseTableConfig = getBaseTableConfig(targetTable);\n const containerFields = baseTableConfig.containerFields || [];\n\n // Filter out container fields from schema\n const schema = { ...baseTableConfig.schema };\n for (const containerField of containerFields) {\n delete schema[containerField as string];\n }\n\n targetSchema = schema;\n }\n\n const selectedFields = config.options?.select\n ? Array.isArray(config.options.select)\n ? config.options.select.map(String)\n : [String(config.options.select)]\n : undefined;\n\n return {\n relation: config.relation,\n targetSchema,\n targetTable,\n table: targetTable,\n selectedFields,\n nestedExpands: undefined,\n };\n });\n }\n\n /**\n * Process an expand() call and return the expand config.\n * Used by both QueryBuilder and RecordBuilder to eliminate duplication.\n *\n * @param targetTable - The target table to expand to\n * @param sourceTable - The source table (for validation)\n * @param callback - Optional callback to configure the expand query\n * @param builderFactory - Function that creates a QueryBuilder for the target table\n * @returns ExpandConfig to add to the builder's expandConfigs array\n */\n processExpand<TargetTable extends FMTable<any, any>>(\n targetTable: TargetTable,\n sourceTable: FMTable<any, any> | undefined,\n callback?: (builder: any) => any,\n builderFactory?: () => any,\n ): ExpandConfig {\n // Extract name and validate\n const relationName = getTableName(targetTable);\n\n // Runtime validation: Check if relation name is in navigationPaths\n if (sourceTable) {\n const navigationPaths = getNavigationPaths(sourceTable);\n if (navigationPaths && !navigationPaths.includes(relationName)) {\n console.warn(\n `Cannot expand to \"${relationName}\". Valid navigation paths: ${navigationPaths.length > 0 ? navigationPaths.join(\", \") : \"none\"}`,\n );\n }\n }\n\n if (callback && builderFactory) {\n // Create a new QueryBuilder for the target table\n const targetBuilder = builderFactory();\n\n // Pass to callback and get configured builder\n const configuredBuilder = callback(targetBuilder);\n\n // Extract the builder's query options\n const expandOptions: Partial<QueryOptions<any>> = {\n ...(configuredBuilder as any).queryOptions,\n };\n\n // If callback didn't provide select, apply defaultSelect from target table\n if (!expandOptions.select) {\n const defaultFields = getDefaultSelectFields(targetTable);\n if (defaultFields) {\n expandOptions.select = defaultFields;\n }\n }\n\n // If the configured builder has nested expands, we need to include them\n if ((configuredBuilder as any).expandConfigs?.length > 0) {\n // Build nested expand string from the configured builder's expand configs\n const nestedExpandString = this.buildExpandString(\n (configuredBuilder as any).expandConfigs,\n );\n if (nestedExpandString) {\n // Add nested expand to options\n expandOptions.expand = nestedExpandString as any;\n }\n }\n\n return {\n relation: relationName,\n options: expandOptions,\n targetTable,\n };\n } else {\n // Simple expand without callback - apply defaultSelect if available\n const defaultFields = getDefaultSelectFields(targetTable);\n if (defaultFields) {\n return {\n relation: relationName,\n options: { select: defaultFields },\n targetTable,\n };\n } else {\n return {\n relation: relationName,\n targetTable,\n };\n }\n }\n }\n\n /**\n * Builds a single expand string with its options.\n */\n private buildSingleExpand(config: ExpandConfig): string {\n const relationName = this.resolveRelationName(config);\n const parts = this.buildExpandParts(config);\n\n if (parts.length === 0) {\n return relationName;\n }\n\n return `${relationName}(${parts.join(\";\")})`;\n }\n\n /**\n * Resolves relation name, using FMTID if entity IDs are enabled.\n */\n private resolveRelationName(config: ExpandConfig): string {\n if (!this.useEntityIds) {\n return config.relation;\n }\n\n const targetTable = config.targetTable;\n if (targetTable && FMTable.Symbol.EntityId in targetTable) {\n const tableId = (targetTable as any)[FMTable.Symbol.EntityId] as\n | `FMTID:${string}`\n | undefined;\n if (tableId) {\n return tableId;\n }\n }\n\n return config.relation;\n }\n\n /**\n * Builds expand parts (select, filter, orderBy, etc.) for a single expand.\n */\n private buildExpandParts(config: ExpandConfig): string[] {\n if (!config.options || Object.keys(config.options).length === 0) {\n return [];\n }\n\n const parts: string[] = [];\n const opts = config.options;\n\n if (opts.select) {\n const selectArray = Array.isArray(opts.select)\n ? opts.select.map(String)\n : [String(opts.select)];\n const selectFields = formatSelectFields(\n selectArray,\n config.targetTable,\n this.useEntityIds,\n );\n parts.push(`$select=${selectFields}`);\n }\n\n if (opts.filter) {\n const filterQuery = buildQuery({ filter: opts.filter });\n const match = filterQuery.match(/\\$filter=([^&]+)/);\n if (match) parts.push(`$filter=${match[1]}`);\n }\n\n if (opts.orderBy) {\n const orderByValue = Array.isArray(opts.orderBy)\n ? opts.orderBy.join(\",\")\n : String(opts.orderBy);\n parts.push(`$orderby=${orderByValue}`);\n }\n\n if (opts.top !== undefined) parts.push(`$top=${opts.top}`);\n if (opts.skip !== undefined) parts.push(`$skip=${opts.skip}`);\n\n if (opts.expand) {\n if (typeof opts.expand === \"string\") {\n parts.push(`$expand=${opts.expand}`);\n }\n }\n\n return parts;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"expand-builder.js","sources":["../../../../src/client/builders/expand-builder.ts"],"sourcesContent":["import { QueryOptions } from \"odata-query\";\nimport buildQuery from \"odata-query\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { FMTable } from \"../../orm/table\";\nimport {\n getBaseTableConfig,\n getTableName,\n getNavigationPaths,\n} from \"../../orm/table\";\nimport type { ExpandValidationConfig } from \"../../validation\";\nimport type { ExpandConfig } from \"./shared-types\";\nimport { formatSelectFields } from \"./select-utils\";\nimport { getDefaultSelectFields } from \"./default-select\";\n\n/**\n * Builds OData expand query strings and validation configs.\n * Handles nested expands recursively and transforms relation names to FMTIDs\n * when using entity IDs.\n */\nexport class ExpandBuilder {\n constructor(private useEntityIds: boolean) {}\n\n /**\n * Builds OData $expand query string from expand configurations.\n */\n buildExpandString(configs: ExpandConfig[]): string {\n if (configs.length === 0) return \"\";\n\n return configs.map((config) => this.buildSingleExpand(config)).join(\",\");\n }\n\n /**\n * Builds validation configs for expanded navigation properties.\n */\n buildValidationConfigs(configs: ExpandConfig[]): ExpandValidationConfig[] {\n return configs.map((config) => {\n const targetTable = config.targetTable;\n\n let targetSchema: Record<string, StandardSchemaV1> | undefined;\n if (targetTable) {\n const baseTableConfig = getBaseTableConfig(targetTable);\n const containerFields = baseTableConfig.containerFields || [];\n\n // Filter out container fields from schema\n const schema = { ...baseTableConfig.schema };\n for (const containerField of containerFields) {\n delete schema[containerField as string];\n }\n\n targetSchema = schema;\n }\n\n const selectedFields = config.options?.select\n ? Array.isArray(config.options.select)\n ? config.options.select.map(String)\n : [String(config.options.select)]\n : undefined;\n\n return {\n relation: config.relation,\n targetSchema,\n targetTable,\n table: targetTable,\n selectedFields,\n nestedExpands: undefined,\n };\n });\n }\n\n /**\n * Process an expand() call and return the expand config.\n * Used by both QueryBuilder and RecordBuilder to eliminate duplication.\n *\n * @param targetTable - The target table to expand to\n * @param sourceTable - The source table (for validation)\n * @param callback - Optional callback to configure the expand query\n * @param builderFactory - Function that creates a QueryBuilder for the target table\n * @returns ExpandConfig to add to the builder's expandConfigs array\n */\n processExpand<TargetTable extends FMTable<any, any>, Builder = any>(\n targetTable: TargetTable,\n sourceTable: FMTable<any, any> | undefined,\n callback?: (builder: Builder) => Builder,\n builderFactory?: () => Builder,\n ): ExpandConfig {\n // Extract name and validate\n const relationName = getTableName(targetTable);\n\n // Runtime validation: Check if relation name is in navigationPaths\n if (sourceTable) {\n const navigationPaths = getNavigationPaths(sourceTable);\n if (navigationPaths && !navigationPaths.includes(relationName)) {\n console.warn(\n `Cannot expand to \"${relationName}\". Valid navigation paths: ${navigationPaths.length > 0 ? navigationPaths.join(\", \") : \"none\"}`,\n );\n }\n }\n\n if (callback && builderFactory) {\n // Create a new QueryBuilder for the target table\n const targetBuilder = builderFactory();\n\n // Pass to callback and get configured builder\n const configuredBuilder = callback(targetBuilder);\n\n // Extract the builder's query options\n const expandOptions: Partial<QueryOptions<any>> = {\n ...(configuredBuilder as any).queryOptions,\n };\n\n // If callback didn't provide select, apply defaultSelect from target table\n if (!expandOptions.select) {\n const defaultFields = getDefaultSelectFields(targetTable);\n if (defaultFields) {\n expandOptions.select = defaultFields;\n }\n }\n\n // If the configured builder has nested expands, we need to include them\n if ((configuredBuilder as any).expandConfigs?.length > 0) {\n // Build nested expand string from the configured builder's expand configs\n const nestedExpandString = this.buildExpandString(\n (configuredBuilder as any).expandConfigs,\n );\n if (nestedExpandString) {\n // Add nested expand to options\n expandOptions.expand = nestedExpandString as any;\n }\n }\n\n return {\n relation: relationName,\n options: expandOptions,\n targetTable,\n };\n } else {\n // Simple expand without callback - apply defaultSelect if available\n const defaultFields = getDefaultSelectFields(targetTable);\n if (defaultFields) {\n return {\n relation: relationName,\n options: { select: defaultFields },\n targetTable,\n };\n } else {\n return {\n relation: relationName,\n targetTable,\n };\n }\n }\n }\n\n /**\n * Builds a single expand string with its options.\n */\n private buildSingleExpand(config: ExpandConfig): string {\n const relationName = this.resolveRelationName(config);\n const parts = this.buildExpandParts(config);\n\n if (parts.length === 0) {\n return relationName;\n }\n\n return `${relationName}(${parts.join(\";\")})`;\n }\n\n /**\n * Resolves relation name, using FMTID if entity IDs are enabled.\n */\n private resolveRelationName(config: ExpandConfig): string {\n if (!this.useEntityIds) {\n return config.relation;\n }\n\n const targetTable = config.targetTable;\n if (targetTable && FMTable.Symbol.EntityId in targetTable) {\n const tableId = (targetTable as any)[FMTable.Symbol.EntityId] as\n | `FMTID:${string}`\n | undefined;\n if (tableId) {\n return tableId;\n }\n }\n\n return config.relation;\n }\n\n /**\n * Builds expand parts (select, filter, orderBy, etc.) for a single expand.\n */\n private buildExpandParts(config: ExpandConfig): string[] {\n if (!config.options || Object.keys(config.options).length === 0) {\n return [];\n }\n\n const parts: string[] = [];\n const opts = config.options;\n\n if (opts.select) {\n const selectArray = Array.isArray(opts.select)\n ? opts.select.map(String)\n : [String(opts.select)];\n const selectFields = formatSelectFields(\n selectArray,\n config.targetTable,\n this.useEntityIds,\n );\n parts.push(`$select=${selectFields}`);\n }\n\n if (opts.filter) {\n const filterQuery = buildQuery({ filter: opts.filter });\n const match = filterQuery.match(/\\$filter=([^&]+)/);\n if (match) parts.push(`$filter=${match[1]}`);\n }\n\n if (opts.orderBy) {\n const orderByValue = Array.isArray(opts.orderBy)\n ? opts.orderBy.join(\",\")\n : String(opts.orderBy);\n parts.push(`$orderby=${orderByValue}`);\n }\n\n if (opts.top !== undefined) parts.push(`$top=${opts.top}`);\n if (opts.skip !== undefined) parts.push(`$skip=${opts.skip}`);\n\n if (opts.expand) {\n if (typeof opts.expand === \"string\") {\n parts.push(`$expand=${opts.expand}`);\n }\n }\n\n return parts;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { ExecutionContext, ExecutableBuilder, Result, ExecuteOptions } from '../types.js';
|
|
1
|
+
import { ExecutionContext, ExecutableBuilder, Result, ExecuteOptions, ExecuteMethodOptions } from '../types.js';
|
|
2
2
|
import { FMTable } from '../orm/table.js';
|
|
3
3
|
import { QueryBuilder } from './query-builder.js';
|
|
4
|
-
import { FFetchOptions } from '@fetchkit/ffetch';
|
|
5
4
|
/**
|
|
6
5
|
* Initial delete builder returned from EntitySet.delete()
|
|
7
6
|
* Requires calling .byId() or .where() before .execute() is available
|
|
@@ -59,9 +58,7 @@ export declare class ExecutableDeleteBuilder<Occ extends FMTable<any, any>> impl
|
|
|
59
58
|
* @param useEntityIds - Optional override for entity ID usage
|
|
60
59
|
*/
|
|
61
60
|
private getTableId;
|
|
62
|
-
execute(options?:
|
|
63
|
-
useEntityIds?: boolean;
|
|
64
|
-
}): Promise<Result<{
|
|
61
|
+
execute(options?: ExecuteMethodOptions<ExecuteOptions>): Promise<Result<{
|
|
65
62
|
deletedCount: number;
|
|
66
63
|
}>>;
|
|
67
64
|
getRequestConfig(): {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delete-builder.js","sources":["../../../src/client/delete-builder.ts"],"sourcesContent":["import type {\n ExecutionContext,\n ExecutableBuilder,\n Result,\n WithSystemFields,\n ExecuteOptions,\n} from \"../types\";\nimport { getAcceptHeader } from \"../types\";\nimport type { FMTable, InferSchemaOutputFromFMTable } from \"../orm/table\";\nimport {\n getTableName,\n getTableId as getTableIdHelper,\n isUsingEntityIds,\n} from \"../orm/table\";\nimport { QueryBuilder } from \"./query-builder\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport { parseErrorResponse } from \"./error-parser\";\n\n/**\n * Initial delete builder returned from EntitySet.delete()\n * Requires calling .byId() or .where() before .execute() is available\n */\nexport class DeleteBuilder<Occ extends FMTable<any, any>> {\n private databaseName: string;\n private context: ExecutionContext;\n private table: Occ;\n private databaseUseEntityIds: boolean;\n\n constructor(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n databaseUseEntityIds?: boolean;\n }) {\n this.table = config.occurrence;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;\n }\n\n /**\n * Delete a single record by ID\n */\n byId(id: string | number): ExecutableDeleteBuilder<Occ> {\n return new ExecutableDeleteBuilder<Occ>({\n occurrence: this.table,\n databaseName: this.databaseName,\n context: this.context,\n mode: \"byId\",\n recordId: id,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n\n /**\n * Delete records matching a filter query\n * @param fn Callback that receives a QueryBuilder for building the filter\n */\n where(\n fn: (q: QueryBuilder<Occ>) => QueryBuilder<Occ>,\n ): ExecutableDeleteBuilder<Occ> {\n // Create a QueryBuilder for the user to configure\n const queryBuilder = new QueryBuilder<Occ>({\n occurrence: this.table,\n databaseName: this.databaseName,\n context: this.context,\n });\n\n // Let the user configure it\n const configuredBuilder = fn(queryBuilder);\n\n return new ExecutableDeleteBuilder<Occ>({\n occurrence: this.table,\n databaseName: this.databaseName,\n context: this.context,\n mode: \"byFilter\",\n queryBuilder: configuredBuilder,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n}\n\n/**\n * Executable delete builder - has execute() method\n * Returned after calling .byId() or .where()\n */\nexport class ExecutableDeleteBuilder<Occ extends FMTable<any, any>>\n implements ExecutableBuilder<{ deletedCount: number }>\n{\n private databaseName: string;\n private context: ExecutionContext;\n private table: Occ;\n private mode: \"byId\" | \"byFilter\";\n private recordId?: string | number;\n private queryBuilder?: QueryBuilder<Occ>;\n private databaseUseEntityIds: boolean;\n\n constructor(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n mode: \"byId\" | \"byFilter\";\n recordId?: string | number;\n queryBuilder?: QueryBuilder<Occ>;\n databaseUseEntityIds?: boolean;\n }) {\n this.table = config.occurrence;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.mode = config.mode;\n this.recordId = config.recordId;\n this.queryBuilder = config.queryBuilder;\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 * 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 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(\n options?: RequestInit & FFetchOptions & { useEntityIds?: boolean },\n ): Promise<Result<{ deletedCount: number }>> {\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\n let url: string;\n\n if (this.mode === \"byId\") {\n // Delete single record by ID: DELETE /{database}/{table}('id')\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n } else {\n // Delete by filter: DELETE /{database}/{table}?$filter=...\n if (!this.queryBuilder) {\n throw new Error(\"Query builder is required for filter-based delete\");\n }\n\n // Get the query string from the configured QueryBuilder\n const queryString = this.queryBuilder.getQueryString();\n // Remove the leading \"/\" and table name from the query string as we'll build our own URL\n const tableName = getTableName(this.table);\n const queryParams = queryString.startsWith(`/${tableId}`)\n ? queryString.slice(`/${tableId}`.length)\n : queryString.startsWith(`/${tableName}`)\n ? queryString.slice(`/${tableName}`.length)\n : queryString;\n\n url = `/${this.databaseName}/${tableId}${queryParams}`;\n }\n\n // Make DELETE request\n const result = await this.context._makeRequest(url, {\n method: \"DELETE\",\n ...mergedOptions,\n });\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n const response = result.data;\n\n // OData returns 204 No Content with fmodata.affected_rows header\n // The _makeRequest should handle extracting the header value\n // For now, we'll check if response contains the count\n let deletedCount = 0;\n\n if (typeof response === \"number\") {\n deletedCount = response;\n } else if (response && typeof response === \"object\") {\n // Check if the response has a count property (fallback)\n deletedCount = (response as any).deletedCount || 0;\n }\n\n return { data: { deletedCount }, 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 const tableId = this.getTableId(this.databaseUseEntityIds);\n\n let url: string;\n\n if (this.mode === \"byId\") {\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n } else {\n if (!this.queryBuilder) {\n throw new Error(\"Query builder is required for filter-based delete\");\n }\n\n const queryString = this.queryBuilder.getQueryString();\n const tableName = getTableName(this.table);\n const queryParams = queryString.startsWith(`/${tableId}`)\n ? queryString.slice(`/${tableId}`.length)\n : queryString.startsWith(`/${tableName}`)\n ? queryString.slice(`/${tableName}`.length)\n : queryString;\n\n url = `/${this.databaseName}/${tableId}${queryParams}`;\n }\n\n return {\n method: \"DELETE\",\n url,\n };\n }\n\n toRequest(baseUrl: string, options?: ExecuteOptions): Request {\n const config = this.getRequestConfig();\n const fullUrl = `${baseUrl}${config.url}`;\n\n return new Request(fullUrl, {\n method: config.method,\n headers: {\n Accept: getAcceptHeader(options?.includeODataAnnotations),\n },\n });\n }\n\n async processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<Result<{ deletedCount: number }>> {\n // Check for error responses (important for batch operations)\n if (!response.ok) {\n const tableName = getTableName(this.table);\n const error = await parseErrorResponse(\n response,\n response.url || `/${this.databaseName}/${tableName}`,\n );\n return { data: undefined, error };\n }\n\n // Check for empty response (204 No Content)\n const text = await response.text();\n if (!text || text.trim() === \"\") {\n // For 204 No Content, check the fmodata.affected_rows header\n const affectedRows = response.headers.get(\"fmodata.affected_rows\");\n const deletedCount = affectedRows ? parseInt(affectedRows, 10) : 1;\n return { data: { deletedCount }, error: undefined };\n }\n\n const rawResponse = JSON.parse(text);\n\n // OData returns 204 No Content with fmodata.affected_rows header\n // The _makeRequest should handle extracting the header value\n // For now, we'll check if response contains the count\n let deletedCount = 0;\n\n if (typeof rawResponse === \"number\") {\n deletedCount = rawResponse;\n } else if (rawResponse && typeof rawResponse === \"object\") {\n // Check if the response has a count property (fallback)\n deletedCount = (rawResponse as any).deletedCount || 0;\n }\n\n return { data: { deletedCount }, error: undefined };\n }\n}\n"],"names":["getTableIdHelper","deletedCount"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"delete-builder.js","sources":["../../../src/client/delete-builder.ts"],"sourcesContent":["import type {\n ExecutionContext,\n ExecutableBuilder,\n Result,\n WithSystemFields,\n ExecuteOptions,\n ExecuteMethodOptions,\n} from \"../types\";\nimport { getAcceptHeader } from \"../types\";\nimport type { FMTable, InferSchemaOutputFromFMTable } from \"../orm/table\";\nimport {\n getTableName,\n getTableId as getTableIdHelper,\n isUsingEntityIds,\n} from \"../orm/table\";\nimport { QueryBuilder } from \"./query-builder\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport { parseErrorResponse } from \"./error-parser\";\n\n/**\n * Initial delete builder returned from EntitySet.delete()\n * Requires calling .byId() or .where() before .execute() is available\n */\nexport class DeleteBuilder<Occ extends FMTable<any, any>> {\n private databaseName: string;\n private context: ExecutionContext;\n private table: Occ;\n private databaseUseEntityIds: boolean;\n\n constructor(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n databaseUseEntityIds?: boolean;\n }) {\n this.table = config.occurrence;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;\n }\n\n /**\n * Delete a single record by ID\n */\n byId(id: string | number): ExecutableDeleteBuilder<Occ> {\n return new ExecutableDeleteBuilder<Occ>({\n occurrence: this.table,\n databaseName: this.databaseName,\n context: this.context,\n mode: \"byId\",\n recordId: id,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n\n /**\n * Delete records matching a filter query\n * @param fn Callback that receives a QueryBuilder for building the filter\n */\n where(\n fn: (q: QueryBuilder<Occ>) => QueryBuilder<Occ>,\n ): ExecutableDeleteBuilder<Occ> {\n // Create a QueryBuilder for the user to configure\n const queryBuilder = new QueryBuilder<Occ>({\n occurrence: this.table,\n databaseName: this.databaseName,\n context: this.context,\n });\n\n // Let the user configure it\n const configuredBuilder = fn(queryBuilder);\n\n return new ExecutableDeleteBuilder<Occ>({\n occurrence: this.table,\n databaseName: this.databaseName,\n context: this.context,\n mode: \"byFilter\",\n queryBuilder: configuredBuilder,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n}\n\n/**\n * Executable delete builder - has execute() method\n * Returned after calling .byId() or .where()\n */\nexport class ExecutableDeleteBuilder<Occ extends FMTable<any, any>>\n implements ExecutableBuilder<{ deletedCount: number }>\n{\n private databaseName: string;\n private context: ExecutionContext;\n private table: Occ;\n private mode: \"byId\" | \"byFilter\";\n private recordId?: string | number;\n private queryBuilder?: QueryBuilder<Occ>;\n private databaseUseEntityIds: boolean;\n\n constructor(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n mode: \"byId\" | \"byFilter\";\n recordId?: string | number;\n queryBuilder?: QueryBuilder<Occ>;\n databaseUseEntityIds?: boolean;\n }) {\n this.table = config.occurrence;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.mode = config.mode;\n this.recordId = config.recordId;\n this.queryBuilder = config.queryBuilder;\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 * 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 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(\n options?: ExecuteMethodOptions<ExecuteOptions>,\n ): Promise<Result<{ deletedCount: number }>> {\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\n let url: string;\n\n if (this.mode === \"byId\") {\n // Delete single record by ID: DELETE /{database}/{table}('id')\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n } else {\n // Delete by filter: DELETE /{database}/{table}?$filter=...\n if (!this.queryBuilder) {\n throw new Error(\"Query builder is required for filter-based delete\");\n }\n\n // Get the query string from the configured QueryBuilder\n const queryString = this.queryBuilder.getQueryString();\n // Remove the leading \"/\" and table name from the query string as we'll build our own URL\n const tableName = getTableName(this.table);\n const queryParams = queryString.startsWith(`/${tableId}`)\n ? queryString.slice(`/${tableId}`.length)\n : queryString.startsWith(`/${tableName}`)\n ? queryString.slice(`/${tableName}`.length)\n : queryString;\n\n url = `/${this.databaseName}/${tableId}${queryParams}`;\n }\n\n // Make DELETE request\n const result = await this.context._makeRequest(url, {\n method: \"DELETE\",\n ...mergedOptions,\n });\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n const response = result.data;\n\n // OData returns 204 No Content with fmodata.affected_rows header\n // The _makeRequest should handle extracting the header value\n // For now, we'll check if response contains the count\n let deletedCount = 0;\n\n if (typeof response === \"number\") {\n deletedCount = response;\n } else if (response && typeof response === \"object\") {\n // Check if the response has a count property (fallback)\n deletedCount = (response as any).deletedCount || 0;\n }\n\n return { data: { deletedCount }, 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 const tableId = this.getTableId(this.databaseUseEntityIds);\n\n let url: string;\n\n if (this.mode === \"byId\") {\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n } else {\n if (!this.queryBuilder) {\n throw new Error(\"Query builder is required for filter-based delete\");\n }\n\n const queryString = this.queryBuilder.getQueryString();\n const tableName = getTableName(this.table);\n const queryParams = queryString.startsWith(`/${tableId}`)\n ? queryString.slice(`/${tableId}`.length)\n : queryString.startsWith(`/${tableName}`)\n ? queryString.slice(`/${tableName}`.length)\n : queryString;\n\n url = `/${this.databaseName}/${tableId}${queryParams}`;\n }\n\n return {\n method: \"DELETE\",\n url,\n };\n }\n\n toRequest(baseUrl: string, options?: ExecuteOptions): Request {\n const config = this.getRequestConfig();\n const fullUrl = `${baseUrl}${config.url}`;\n\n return new Request(fullUrl, {\n method: config.method,\n headers: {\n Accept: getAcceptHeader(options?.includeODataAnnotations),\n },\n });\n }\n\n async processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<Result<{ deletedCount: number }>> {\n // Check for error responses (important for batch operations)\n if (!response.ok) {\n const tableName = getTableName(this.table);\n const error = await parseErrorResponse(\n response,\n response.url || `/${this.databaseName}/${tableName}`,\n );\n return { data: undefined, error };\n }\n\n // Check for empty response (204 No Content)\n const text = await response.text();\n if (!text || text.trim() === \"\") {\n // For 204 No Content, check the fmodata.affected_rows header\n const affectedRows = response.headers.get(\"fmodata.affected_rows\");\n const deletedCount = affectedRows ? parseInt(affectedRows, 10) : 1;\n return { data: { deletedCount }, error: undefined };\n }\n\n const rawResponse = JSON.parse(text);\n\n // OData returns 204 No Content with fmodata.affected_rows header\n // The _makeRequest should handle extracting the header value\n // For now, we'll check if response contains the count\n let deletedCount = 0;\n\n if (typeof rawResponse === \"number\") {\n deletedCount = rawResponse;\n } else if (rawResponse && typeof rawResponse === \"object\") {\n // Check if the response has a count property (fallback)\n deletedCount = (rawResponse as any).deletedCount || 0;\n }\n\n return { data: { deletedCount }, error: undefined };\n }\n}\n"],"names":["getTableIdHelper","deletedCount"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ExecutionContext } from '../types.js';
|
|
2
|
-
import { QueryBuilder } from './query.js';
|
|
2
|
+
import { QueryBuilder } from './query/index.js';
|
|
3
3
|
import { RecordBuilder } from './record-builder.js';
|
|
4
4
|
import { InsertBuilder } from './insert-builder.js';
|
|
5
5
|
import { DeleteBuilder } from './delete-builder.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity-set.js","sources":["../../../src/client/entity-set.ts"],"sourcesContent":["import type { ExecutionContext, InferSchemaType } from \"../types\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { QueryBuilder } from \"./query\";\nimport { RecordBuilder } from \"./record-builder\";\nimport { InsertBuilder } from \"./insert-builder\";\nimport { DeleteBuilder } from \"./delete-builder\";\nimport { UpdateBuilder } from \"./update-builder\";\nimport { Database } from \"./database\";\nimport type {\n FMTable,\n InferSchemaOutputFromFMTable,\n InferInputSchemaFromFMTable,\n InsertDataFromFMTable,\n UpdateDataFromFMTable,\n ValidExpandTarget,\n ExtractTableName,\n FMTableWithColumns,\n InferFieldOutput,\n} from \"../orm/table\";\nimport {\n FMTable as FMTableClass,\n getDefaultSelect,\n getNavigationPaths,\n getTableName,\n getTableColumns,\n getTableFields,\n} from \"../orm/table\";\nimport type { Column } from \"../orm/column\";\nimport type { FieldBuilder } from \"../orm/field-builders\";\n\n// Helper type to extract defaultSelect from an FMTable\n// Since TypeScript can't extract Symbol-indexed properties at the type level,\n// we simplify to return keyof InferSchemaFromFMTable<O> when O is an FMTable.\n// The actual defaultSelect logic is handled at runtime.\ntype ExtractDefaultSelect<O> =\n O extends FMTable<any, any> ? keyof InferSchemaOutputFromFMTable<O> : never;\n\n/**\n * Helper type to extract properly-typed columns from an FMTable.\n * This preserves the specific column types instead of widening to `any`.\n */\ntype ExtractColumnsFromOcc<T> =\n T extends FMTable<infer TFields, infer TName, any>\n ? TFields extends Record<string, FieldBuilder<any, any, any, any>>\n ? { [K in keyof TFields]: Column<InferFieldOutput<TFields[K]>, TName> }\n : never\n : never;\n\nexport class EntitySet<Occ extends FMTable<any, any>> {\n private occurrence: Occ;\n private databaseName: string;\n private context: ExecutionContext;\n private database: Database; // Database instance for accessing occurrences\n private isNavigateFromEntitySet?: boolean;\n private navigateRelation?: string;\n private navigateSourceTableName?: string;\n private navigateBasePath?: string; // Full base path for chained navigations\n private databaseUseEntityIds: boolean;\n\n constructor(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n database?: any;\n }) {\n this.occurrence = config.occurrence;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.database = config.database;\n // Get useEntityIds from database if available, otherwise default to false\n this.databaseUseEntityIds =\n (config.database as any)?._useEntityIds ?? false;\n }\n\n // Type-only method to help TypeScript infer the schema from table\n static create<Occ extends FMTable<any, any>>(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n database: Database;\n }): EntitySet<Occ> {\n return new EntitySet<Occ>({\n occurrence: config.occurrence,\n databaseName: config.databaseName,\n context: config.context,\n database: config.database,\n });\n }\n\n list() {\n const builder = new QueryBuilder<Occ>({\n occurrence: this.occurrence as Occ,\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n\n // Apply defaultSelect if occurrence exists and select hasn't been called\n if (this.occurrence) {\n // FMTable - access via helper functions\n const defaultSelectValue = getDefaultSelect(this.occurrence);\n const tableSchema = (this.occurrence as any)[FMTableClass.Symbol.Schema];\n let schema: Record<string, StandardSchemaV1> | undefined;\n\n if (tableSchema) {\n // Extract schema from StandardSchemaV1\n const zodSchema = tableSchema[\"~standard\"]?.schema;\n if (\n zodSchema &&\n typeof zodSchema === \"object\" &&\n \"shape\" in zodSchema\n ) {\n schema = zodSchema.shape as Record<string, StandardSchemaV1>;\n }\n }\n\n if (defaultSelectValue === \"schema\") {\n // Use getTableColumns to get all columns and select them\n // This is equivalent to select(getTableColumns(occurrence))\n // Use ExtractColumnsFromOcc to preserve the properly-typed column types\n const allColumns = getTableColumns(\n this.occurrence as any,\n ) as ExtractColumnsFromOcc<Occ>;\n return builder.select(allColumns).top(1000);\n } else if (typeof defaultSelectValue === \"object\") {\n // defaultSelectValue is a select object (Record<string, Column>)\n // Use it directly with select()\n // Use ExtractColumnsFromOcc to preserve the properly-typed column types\n return builder\n .select(defaultSelectValue as ExtractColumnsFromOcc<Occ>)\n .top(1000);\n }\n // If defaultSelect is \"all\", no changes needed (current behavior)\n }\n\n // Propagate navigation context if present\n if (\n this.isNavigateFromEntitySet &&\n this.navigateRelation &&\n this.navigateSourceTableName\n ) {\n (builder as any).navigation = {\n relation: this.navigateRelation,\n sourceTableName: this.navigateSourceTableName,\n basePath: this.navigateBasePath,\n // recordId is intentionally not set (undefined) to indicate navigation from EntitySet\n };\n }\n\n // Apply default pagination limit of 1000 records to prevent stack overflow\n // with large datasets. Users can override with .top() if needed.\n return builder.top(1000);\n }\n\n get(\n id: string | number,\n ): RecordBuilder<\n Occ,\n false,\n keyof InferSchemaOutputFromFMTable<Occ>,\n keyof InferSchemaOutputFromFMTable<Occ>,\n {}\n > {\n const builder = new RecordBuilder<Occ>({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n recordId: id,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n\n // Apply defaultSelect if occurrence exists\n if (this.occurrence) {\n // FMTable - access via helper functions\n const defaultSelectValue = getDefaultSelect(this.occurrence);\n const tableSchema = (this.occurrence as any)[FMTableClass.Symbol.Schema];\n let schema: Record<string, StandardSchemaV1> | undefined;\n\n if (tableSchema) {\n // Extract schema from StandardSchemaV1\n const zodSchema = tableSchema[\"~standard\"]?.schema;\n if (\n zodSchema &&\n typeof zodSchema === \"object\" &&\n \"shape\" in zodSchema\n ) {\n schema = zodSchema.shape as Record<string, StandardSchemaV1>;\n }\n }\n\n if (defaultSelectValue === \"schema\") {\n // Use getTableColumns to get all columns and select them\n // This is equivalent to select(getTableColumns(occurrence))\n // Use ExtractColumnsFromOcc to preserve the properly-typed column types\n const allColumns = getTableColumns(\n this.occurrence as any,\n ) as ExtractColumnsFromOcc<Occ>;\n const selectedBuilder = builder.select(allColumns);\n // Propagate navigation context if present\n if (\n this.isNavigateFromEntitySet &&\n this.navigateRelation &&\n this.navigateSourceTableName\n ) {\n (selectedBuilder as any).navigation = {\n relation: this.navigateRelation,\n sourceTableName: this.navigateSourceTableName,\n basePath: this.navigateBasePath,\n };\n }\n return selectedBuilder as any;\n } else if (\n typeof defaultSelectValue === \"object\" &&\n defaultSelectValue !== null &&\n !Array.isArray(defaultSelectValue)\n ) {\n // defaultSelectValue is a select object (Record<string, Column>)\n // Use it directly with select()\n // Use ExtractColumnsFromOcc to preserve the properly-typed column types\n const selectedBuilder = builder.select(\n defaultSelectValue as ExtractColumnsFromOcc<Occ>,\n );\n // Propagate navigation context if present\n if (\n this.isNavigateFromEntitySet &&\n this.navigateRelation &&\n this.navigateSourceTableName\n ) {\n (selectedBuilder as any).navigation = {\n relation: this.navigateRelation,\n sourceTableName: this.navigateSourceTableName,\n basePath: this.navigateBasePath,\n };\n }\n return selectedBuilder as any;\n }\n // If defaultSelect is \"all\", no changes needed (current behavior)\n }\n\n // Propagate navigation context if present\n if (\n this.isNavigateFromEntitySet &&\n this.navigateRelation &&\n this.navigateSourceTableName\n ) {\n (builder as any).navigation = {\n relation: this.navigateRelation,\n sourceTableName: this.navigateSourceTableName,\n basePath: this.navigateBasePath,\n };\n }\n return builder as any;\n }\n\n // Overload: when returnFullRecord is false\n insert(\n data: InsertDataFromFMTable<Occ>,\n options: { returnFullRecord: false },\n ): InsertBuilder<Occ, \"minimal\">;\n\n // Overload: when returnFullRecord is true or omitted (default)\n insert(\n data: InsertDataFromFMTable<Occ>,\n options?: { returnFullRecord?: true },\n ): InsertBuilder<Occ, \"representation\">;\n\n // Implementation\n insert(\n data: InsertDataFromFMTable<Occ>,\n options?: { returnFullRecord?: boolean },\n ): InsertBuilder<Occ, \"minimal\" | \"representation\"> {\n const returnPreference =\n options?.returnFullRecord === false ? \"minimal\" : \"representation\";\n\n return new InsertBuilder<Occ, typeof returnPreference>({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n data: data as any, // Input type is validated/transformed at runtime\n returnPreference: returnPreference as any,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n\n // Overload: when returnFullRecord is explicitly true\n update(\n data: UpdateDataFromFMTable<Occ>,\n options: { returnFullRecord: true },\n ): UpdateBuilder<Occ, \"representation\">;\n\n // Overload: when returnFullRecord is false or omitted (default)\n update(\n data: UpdateDataFromFMTable<Occ>,\n options?: { returnFullRecord?: false },\n ): UpdateBuilder<Occ, \"minimal\">;\n\n // Implementation\n update(\n data: UpdateDataFromFMTable<Occ>,\n options?: { returnFullRecord?: boolean },\n ): UpdateBuilder<Occ, \"minimal\" | \"representation\"> {\n const returnPreference =\n options?.returnFullRecord === true ? \"representation\" : \"minimal\";\n\n return new UpdateBuilder<Occ, typeof returnPreference>({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n data: data as any, // Input type is validated/transformed at runtime\n returnPreference: returnPreference as any,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n\n delete(): DeleteBuilder<Occ> {\n return new DeleteBuilder<Occ>({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n }) as any;\n }\n\n // Implementation\n navigate<TargetTable extends FMTable<any, any>>(\n targetTable: ValidExpandTarget<Occ, TargetTable>,\n ): EntitySet<TargetTable extends FMTable<any, any> ? TargetTable : never> {\n // Check if it's an FMTable object or a string\n let relationName: string;\n\n // FMTable object - extract name and validate\n relationName = getTableName(targetTable);\n\n // Runtime validation: Check if relation name is in navigationPaths\n if (\n this.occurrence &&\n FMTableClass.Symbol.NavigationPaths in this.occurrence\n ) {\n const navigationPaths = (this.occurrence as any)[\n FMTableClass.Symbol.NavigationPaths\n ] as readonly string[];\n if (navigationPaths && !navigationPaths.includes(relationName)) {\n console.warn(\n `Cannot navigate to \"${relationName}\". Valid navigation paths: ${navigationPaths.length > 0 ? navigationPaths.join(\", \") : \"none\"}`,\n );\n }\n }\n\n // Create EntitySet with target table\n const entitySet = new EntitySet<any>({\n occurrence: targetTable,\n databaseName: this.databaseName,\n context: this.context,\n database: this.database,\n });\n // Store the navigation info in the EntitySet\n (entitySet as any).isNavigateFromEntitySet = true;\n (entitySet as any).navigateRelation = relationName;\n\n // Build the full base path for chained navigations\n if (this.isNavigateFromEntitySet && this.navigateBasePath) {\n // Already have a base path from previous navigation - extend it with current relation\n (entitySet as any).navigateBasePath =\n `${this.navigateBasePath}/${this.navigateRelation}`;\n (entitySet as any).navigateSourceTableName = this.navigateSourceTableName;\n } else if (this.isNavigateFromEntitySet && this.navigateRelation) {\n // First chained navigation - create base path from source/relation\n (entitySet as any).navigateBasePath =\n `${this.navigateSourceTableName}/${this.navigateRelation}`;\n (entitySet as any).navigateSourceTableName = this.navigateSourceTableName;\n } else {\n // Initial navigation - source is just the table name\n (entitySet as any).navigateSourceTableName = getTableName(\n this.occurrence,\n );\n }\n return entitySet;\n }\n}\n"],"names":["FMTableClass"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"entity-set.js","sources":["../../../src/client/entity-set.ts"],"sourcesContent":["import type { ExecutionContext, InferSchemaType } from \"../types\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { QueryBuilder } from \"./query/index\";\nimport { RecordBuilder } from \"./record-builder\";\nimport { InsertBuilder } from \"./insert-builder\";\nimport { DeleteBuilder } from \"./delete-builder\";\nimport { UpdateBuilder } from \"./update-builder\";\nimport { Database } from \"./database\";\nimport type {\n FMTable,\n InferSchemaOutputFromFMTable,\n InferInputSchemaFromFMTable,\n InsertDataFromFMTable,\n UpdateDataFromFMTable,\n ValidExpandTarget,\n ExtractTableName,\n FMTableWithColumns,\n InferFieldOutput,\n} from \"../orm/table\";\nimport {\n FMTable as FMTableClass,\n getDefaultSelect,\n getNavigationPaths,\n getTableName,\n getTableColumns,\n getTableFields,\n} from \"../orm/table\";\nimport type { Column } from \"../orm/column\";\nimport type { FieldBuilder } from \"../orm/field-builders\";\n\n// Helper type to extract defaultSelect from an FMTable\n// Since TypeScript can't extract Symbol-indexed properties at the type level,\n// we simplify to return keyof InferSchemaFromFMTable<O> when O is an FMTable.\n// The actual defaultSelect logic is handled at runtime.\ntype ExtractDefaultSelect<O> =\n O extends FMTable<any, any> ? keyof InferSchemaOutputFromFMTable<O> : never;\n\n/**\n * Helper type to extract properly-typed columns from an FMTable.\n * This preserves the specific column types instead of widening to `any`.\n */\ntype ExtractColumnsFromOcc<T> =\n T extends FMTable<infer TFields, infer TName, any>\n ? TFields extends Record<string, FieldBuilder<any, any, any, any>>\n ? { [K in keyof TFields]: Column<InferFieldOutput<TFields[K]>, TName> }\n : never\n : never;\n\nexport class EntitySet<Occ extends FMTable<any, any>> {\n private occurrence: Occ;\n private databaseName: string;\n private context: ExecutionContext;\n private database: Database; // Database instance for accessing occurrences\n private isNavigateFromEntitySet?: boolean;\n private navigateRelation?: string;\n private navigateSourceTableName?: string;\n private navigateBasePath?: string; // Full base path for chained navigations\n private databaseUseEntityIds: boolean;\n\n constructor(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n database?: any;\n }) {\n this.occurrence = config.occurrence;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.database = config.database;\n // Get useEntityIds from database if available, otherwise default to false\n this.databaseUseEntityIds =\n (config.database as any)?._useEntityIds ?? false;\n }\n\n // Type-only method to help TypeScript infer the schema from table\n static create<Occ extends FMTable<any, any>>(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n database: Database;\n }): EntitySet<Occ> {\n return new EntitySet<Occ>({\n occurrence: config.occurrence,\n databaseName: config.databaseName,\n context: config.context,\n database: config.database,\n });\n }\n\n list() {\n const builder = new QueryBuilder<Occ>({\n occurrence: this.occurrence as Occ,\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n\n // Apply defaultSelect if occurrence exists and select hasn't been called\n if (this.occurrence) {\n // FMTable - access via helper functions\n const defaultSelectValue = getDefaultSelect(this.occurrence);\n const tableSchema = (this.occurrence as any)[FMTableClass.Symbol.Schema];\n let schema: Record<string, StandardSchemaV1> | undefined;\n\n if (tableSchema) {\n // Extract schema from StandardSchemaV1\n const zodSchema = tableSchema[\"~standard\"]?.schema;\n if (\n zodSchema &&\n typeof zodSchema === \"object\" &&\n \"shape\" in zodSchema\n ) {\n schema = zodSchema.shape as Record<string, StandardSchemaV1>;\n }\n }\n\n if (defaultSelectValue === \"schema\") {\n // Use getTableColumns to get all columns and select them\n // This is equivalent to select(getTableColumns(occurrence))\n // Use ExtractColumnsFromOcc to preserve the properly-typed column types\n const allColumns = getTableColumns(\n this.occurrence as any,\n ) as ExtractColumnsFromOcc<Occ>;\n return builder.select(allColumns).top(1000);\n } else if (typeof defaultSelectValue === \"object\") {\n // defaultSelectValue is a select object (Record<string, Column>)\n // Use it directly with select()\n // Use ExtractColumnsFromOcc to preserve the properly-typed column types\n return builder\n .select(defaultSelectValue as ExtractColumnsFromOcc<Occ>)\n .top(1000);\n }\n // If defaultSelect is \"all\", no changes needed (current behavior)\n }\n\n // Propagate navigation context if present\n if (\n this.isNavigateFromEntitySet &&\n this.navigateRelation &&\n this.navigateSourceTableName\n ) {\n (builder as any).navigation = {\n relation: this.navigateRelation,\n sourceTableName: this.navigateSourceTableName,\n basePath: this.navigateBasePath,\n // recordId is intentionally not set (undefined) to indicate navigation from EntitySet\n };\n }\n\n // Apply default pagination limit of 1000 records to prevent stack overflow\n // with large datasets. Users can override with .top() if needed.\n return builder.top(1000);\n }\n\n get(\n id: string | number,\n ): RecordBuilder<\n Occ,\n false,\n keyof InferSchemaOutputFromFMTable<Occ>,\n keyof InferSchemaOutputFromFMTable<Occ>,\n {}\n > {\n const builder = new RecordBuilder<Occ>({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n recordId: id,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n\n // Apply defaultSelect if occurrence exists\n if (this.occurrence) {\n // FMTable - access via helper functions\n const defaultSelectValue = getDefaultSelect(this.occurrence);\n const tableSchema = (this.occurrence as any)[FMTableClass.Symbol.Schema];\n let schema: Record<string, StandardSchemaV1> | undefined;\n\n if (tableSchema) {\n // Extract schema from StandardSchemaV1\n const zodSchema = tableSchema[\"~standard\"]?.schema;\n if (\n zodSchema &&\n typeof zodSchema === \"object\" &&\n \"shape\" in zodSchema\n ) {\n schema = zodSchema.shape as Record<string, StandardSchemaV1>;\n }\n }\n\n if (defaultSelectValue === \"schema\") {\n // Use getTableColumns to get all columns and select them\n // This is equivalent to select(getTableColumns(occurrence))\n // Use ExtractColumnsFromOcc to preserve the properly-typed column types\n const allColumns = getTableColumns(\n this.occurrence as any,\n ) as ExtractColumnsFromOcc<Occ>;\n const selectedBuilder = builder.select(allColumns);\n // Propagate navigation context if present\n if (\n this.isNavigateFromEntitySet &&\n this.navigateRelation &&\n this.navigateSourceTableName\n ) {\n (selectedBuilder as any).navigation = {\n relation: this.navigateRelation,\n sourceTableName: this.navigateSourceTableName,\n basePath: this.navigateBasePath,\n };\n }\n return selectedBuilder as any;\n } else if (\n typeof defaultSelectValue === \"object\" &&\n defaultSelectValue !== null &&\n !Array.isArray(defaultSelectValue)\n ) {\n // defaultSelectValue is a select object (Record<string, Column>)\n // Use it directly with select()\n // Use ExtractColumnsFromOcc to preserve the properly-typed column types\n const selectedBuilder = builder.select(\n defaultSelectValue as ExtractColumnsFromOcc<Occ>,\n );\n // Propagate navigation context if present\n if (\n this.isNavigateFromEntitySet &&\n this.navigateRelation &&\n this.navigateSourceTableName\n ) {\n (selectedBuilder as any).navigation = {\n relation: this.navigateRelation,\n sourceTableName: this.navigateSourceTableName,\n basePath: this.navigateBasePath,\n };\n }\n return selectedBuilder as any;\n }\n // If defaultSelect is \"all\", no changes needed (current behavior)\n }\n\n // Propagate navigation context if present\n if (\n this.isNavigateFromEntitySet &&\n this.navigateRelation &&\n this.navigateSourceTableName\n ) {\n (builder as any).navigation = {\n relation: this.navigateRelation,\n sourceTableName: this.navigateSourceTableName,\n basePath: this.navigateBasePath,\n };\n }\n return builder as any;\n }\n\n // Overload: when returnFullRecord is false\n insert(\n data: InsertDataFromFMTable<Occ>,\n options: { returnFullRecord: false },\n ): InsertBuilder<Occ, \"minimal\">;\n\n // Overload: when returnFullRecord is true or omitted (default)\n insert(\n data: InsertDataFromFMTable<Occ>,\n options?: { returnFullRecord?: true },\n ): InsertBuilder<Occ, \"representation\">;\n\n // Implementation\n insert(\n data: InsertDataFromFMTable<Occ>,\n options?: { returnFullRecord?: boolean },\n ): InsertBuilder<Occ, \"minimal\" | \"representation\"> {\n const returnPreference =\n options?.returnFullRecord === false ? \"minimal\" : \"representation\";\n\n return new InsertBuilder<Occ, typeof returnPreference>({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n data: data as any, // Input type is validated/transformed at runtime\n returnPreference: returnPreference as any,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n\n // Overload: when returnFullRecord is explicitly true\n update(\n data: UpdateDataFromFMTable<Occ>,\n options: { returnFullRecord: true },\n ): UpdateBuilder<Occ, \"representation\">;\n\n // Overload: when returnFullRecord is false or omitted (default)\n update(\n data: UpdateDataFromFMTable<Occ>,\n options?: { returnFullRecord?: false },\n ): UpdateBuilder<Occ, \"minimal\">;\n\n // Implementation\n update(\n data: UpdateDataFromFMTable<Occ>,\n options?: { returnFullRecord?: boolean },\n ): UpdateBuilder<Occ, \"minimal\" | \"representation\"> {\n const returnPreference =\n options?.returnFullRecord === true ? \"representation\" : \"minimal\";\n\n return new UpdateBuilder<Occ, typeof returnPreference>({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n data: data as any, // Input type is validated/transformed at runtime\n returnPreference: returnPreference as any,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n\n delete(): DeleteBuilder<Occ> {\n return new DeleteBuilder<Occ>({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n }) as any;\n }\n\n // Implementation\n navigate<TargetTable extends FMTable<any, any>>(\n targetTable: ValidExpandTarget<Occ, TargetTable>,\n ): EntitySet<TargetTable extends FMTable<any, any> ? TargetTable : never> {\n // Check if it's an FMTable object or a string\n let relationName: string;\n\n // FMTable object - extract name and validate\n relationName = getTableName(targetTable);\n\n // Runtime validation: Check if relation name is in navigationPaths\n if (\n this.occurrence &&\n FMTableClass.Symbol.NavigationPaths in this.occurrence\n ) {\n const navigationPaths = (this.occurrence as any)[\n FMTableClass.Symbol.NavigationPaths\n ] as readonly string[];\n if (navigationPaths && !navigationPaths.includes(relationName)) {\n console.warn(\n `Cannot navigate to \"${relationName}\". Valid navigation paths: ${navigationPaths.length > 0 ? navigationPaths.join(\", \") : \"none\"}`,\n );\n }\n }\n\n // Create EntitySet with target table\n const entitySet = new EntitySet<any>({\n occurrence: targetTable,\n databaseName: this.databaseName,\n context: this.context,\n database: this.database,\n });\n // Store the navigation info in the EntitySet\n (entitySet as any).isNavigateFromEntitySet = true;\n (entitySet as any).navigateRelation = relationName;\n\n // Build the full base path for chained navigations\n if (this.isNavigateFromEntitySet && this.navigateBasePath) {\n // Already have a base path from previous navigation - extend it with current relation\n (entitySet as any).navigateBasePath =\n `${this.navigateBasePath}/${this.navigateRelation}`;\n (entitySet as any).navigateSourceTableName = this.navigateSourceTableName;\n } else if (this.isNavigateFromEntitySet && this.navigateRelation) {\n // First chained navigation - create base path from source/relation\n (entitySet as any).navigateBasePath =\n `${this.navigateSourceTableName}/${this.navigateRelation}`;\n (entitySet as any).navigateSourceTableName = this.navigateSourceTableName;\n } else {\n // Initial navigation - source is just the table name\n (entitySet as any).navigateSourceTableName = getTableName(\n this.occurrence,\n );\n }\n return entitySet;\n }\n}\n"],"names":["FMTableClass"],"mappings":"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"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { ExecutionContext, ExecutableBuilder, Result, ExecuteOptions, ConditionallyWithODataAnnotations } from '../types.js';
|
|
1
|
+
import { ExecutionContext, ExecutableBuilder, Result, ExecuteOptions, ConditionallyWithODataAnnotations, ExecuteMethodOptions } from '../types.js';
|
|
2
2
|
import { FMTable, InferSchemaOutputFromFMTable } from '../orm/table.js';
|
|
3
|
-
import { FFetchOptions } from '@fetchkit/ffetch';
|
|
4
3
|
export type InsertOptions = {
|
|
5
4
|
return?: "minimal" | "representation";
|
|
6
5
|
};
|
|
@@ -37,7 +36,7 @@ export declare class InsertBuilder<Occ extends FMTable<any, any> | undefined = u
|
|
|
37
36
|
* @param useEntityIds - Optional override for entity ID usage
|
|
38
37
|
*/
|
|
39
38
|
private getTableId;
|
|
40
|
-
execute<EO extends ExecuteOptions>(options?:
|
|
39
|
+
execute<EO extends ExecuteOptions>(options?: ExecuteMethodOptions<EO>): Promise<Result<ReturnPreference extends "minimal" ? {
|
|
41
40
|
ROWID: number;
|
|
42
41
|
} : ConditionallyWithODataAnnotations<InferSchemaOutputFromFMTable<NonNullable<Occ>>, EO["includeODataAnnotations"] extends true ? true : false>>>;
|
|
43
42
|
getRequestConfig(): {
|
|
@@ -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} 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?: RequestInit & FFetchOptions & 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\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,10 +1,9 @@
|
|
|
1
|
-
import { ExecutionContext, ExecutableBuilder, Result, ExecuteOptions, ConditionallyWithODataAnnotations, ExtractSchemaFromOccurrence } from '../../types.js';
|
|
1
|
+
import { ExecutionContext, ExecutableBuilder, Result, ExecuteOptions, ConditionallyWithODataAnnotations, ExtractSchemaFromOccurrence, ExecuteMethodOptions } from '../../types.js';
|
|
2
2
|
import { Filter } from '../../filter-types.js';
|
|
3
|
-
import { FFetchOptions } from '@fetchkit/ffetch';
|
|
4
3
|
import { Column } from '../../orm/column.js';
|
|
5
4
|
import { FilterExpression, OrderByExpression } from '../../orm/operators.js';
|
|
6
5
|
import { FMTable, InferSchemaOutputFromFMTable, ValidExpandTarget, ExtractTableName } from '../../orm/table.js';
|
|
7
|
-
import { ExpandedRelations } from '../builders.js';
|
|
6
|
+
import { ExpandedRelations } from '../builders/index.js';
|
|
8
7
|
import { TypeSafeOrderBy, QueryReturnType } from './types.js';
|
|
9
8
|
export type { QueryReturnType };
|
|
10
9
|
export type { TypeSafeOrderBy, ExpandedRelations };
|
|
@@ -122,7 +121,7 @@ export declare class QueryBuilder<Occ extends FMTable<any, any>, Selected extend
|
|
|
122
121
|
* Builds the OData query string from current query options and expand configs.
|
|
123
122
|
*/
|
|
124
123
|
private buildQueryString;
|
|
125
|
-
execute<EO extends ExecuteOptions>(options?:
|
|
124
|
+
execute<EO extends ExecuteOptions>(options?: ExecuteMethodOptions<EO>): Promise<Result<ConditionallyWithODataAnnotations<QueryReturnType<InferSchemaOutputFromFMTable<Occ>, Selected, SingleMode, IsCount, Expands>, EO["includeODataAnnotations"] extends true ? true : false>>>;
|
|
126
125
|
getQueryString(): string;
|
|
127
126
|
getRequestConfig(): {
|
|
128
127
|
method: string;
|