@proofkit/fmodata 0.1.0-alpha.10 → 0.1.0-alpha.12

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.
Files changed (65) hide show
  1. package/README.md +32 -22
  2. package/dist/esm/client/base-table.d.ts +8 -5
  3. package/dist/esm/client/base-table.js.map +1 -1
  4. package/dist/esm/client/batch-builder.d.ts +1 -1
  5. package/dist/esm/client/batch-builder.js +2 -2
  6. package/dist/esm/client/batch-builder.js.map +1 -1
  7. package/dist/esm/client/build-occurrences.d.ts +74 -0
  8. package/dist/esm/client/build-occurrences.js +31 -0
  9. package/dist/esm/client/build-occurrences.js.map +1 -0
  10. package/dist/esm/client/delete-builder.d.ts +1 -1
  11. package/dist/esm/client/delete-builder.js +3 -2
  12. package/dist/esm/client/delete-builder.js.map +1 -1
  13. package/dist/esm/client/entity-set.d.ts +3 -3
  14. package/dist/esm/client/entity-set.js +38 -1
  15. package/dist/esm/client/entity-set.js.map +1 -1
  16. package/dist/esm/client/filemaker-odata.js +25 -9
  17. package/dist/esm/client/filemaker-odata.js.map +1 -1
  18. package/dist/esm/client/insert-builder.d.ts +1 -5
  19. package/dist/esm/client/insert-builder.js +7 -23
  20. package/dist/esm/client/insert-builder.js.map +1 -1
  21. package/dist/esm/client/query-builder.d.ts +3 -7
  22. package/dist/esm/client/query-builder.js +69 -68
  23. package/dist/esm/client/query-builder.js.map +1 -1
  24. package/dist/esm/client/record-builder.d.ts +2 -7
  25. package/dist/esm/client/record-builder.js +38 -25
  26. package/dist/esm/client/record-builder.js.map +1 -1
  27. package/dist/esm/client/response-processor.d.ts +0 -5
  28. package/dist/esm/client/sanitize-json.d.ts +35 -0
  29. package/dist/esm/client/sanitize-json.js +27 -0
  30. package/dist/esm/client/sanitize-json.js.map +1 -0
  31. package/dist/esm/client/table-occurrence.d.ts +26 -12
  32. package/dist/esm/client/table-occurrence.js +14 -30
  33. package/dist/esm/client/table-occurrence.js.map +1 -1
  34. package/dist/esm/client/update-builder.d.ts +1 -1
  35. package/dist/esm/client/update-builder.js +3 -2
  36. package/dist/esm/client/update-builder.js.map +1 -1
  37. package/dist/esm/errors.d.ts +11 -1
  38. package/dist/esm/errors.js +15 -0
  39. package/dist/esm/errors.js.map +1 -1
  40. package/dist/esm/index.d.ts +6 -3
  41. package/dist/esm/index.js +7 -6
  42. package/dist/esm/index.js.map +1 -1
  43. package/dist/esm/types.d.ts +8 -1
  44. package/dist/esm/types.js +7 -0
  45. package/dist/esm/types.js.map +1 -0
  46. package/dist/esm/validation.js +3 -4
  47. package/dist/esm/validation.js.map +1 -1
  48. package/package.json +3 -2
  49. package/src/client/base-table.ts +8 -5
  50. package/src/client/batch-builder.ts +2 -2
  51. package/src/client/build-occurrences.ts +155 -0
  52. package/src/client/delete-builder.ts +3 -2
  53. package/src/client/entity-set.ts +60 -7
  54. package/src/client/filemaker-odata.ts +39 -10
  55. package/src/client/insert-builder.ts +8 -33
  56. package/src/client/query-builder.ts +82 -90
  57. package/src/client/record-builder.ts +51 -42
  58. package/src/client/response-processor.ts +0 -13
  59. package/src/client/sanitize-json.ts +66 -0
  60. package/src/client/table-occurrence.ts +50 -69
  61. package/src/client/update-builder.ts +3 -2
  62. package/src/errors.ts +24 -1
  63. package/src/index.ts +10 -8
  64. package/src/types.ts +13 -1
  65. package/src/validation.ts +4 -5
@@ -1 +1 @@
1
- {"version":3,"file":"record-builder.js","sources":["../../../src/client/record-builder.ts"],"sourcesContent":["import type {\n ExecutionContext,\n ExecutableBuilder,\n Result,\n ODataRecordMetadata,\n ODataFieldResponse,\n InferSchemaType,\n ExecuteOptions,\n WithSystemFields,\n ConditionallyWithODataAnnotations,\n} from \"../types\";\nimport type { TableOccurrence } from \"./table-occurrence\";\nimport type { BaseTable } from \"./base-table\";\nimport {\n transformTableName,\n transformResponseFields,\n getTableIdentifiers,\n transformFieldNamesArray,\n} from \"../transform\";\nimport { QueryBuilder } from \"./query-builder\";\nimport { validateSingleResponse, type ExpandValidationConfig } from \"../validation\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { QueryOptions } from \"odata-query\";\nimport buildQuery from \"odata-query\";\n\n// Helper type to extract schema from a TableOccurrence\ntype ExtractSchemaFromOccurrence<O> =\n O extends TableOccurrence<infer BT, any, any, any>\n ? BT extends BaseTable<infer S, any, any, any>\n ? S\n : never\n : never;\n\n// Helper type to extract navigation relation names from an occurrence\ntype ExtractNavigationNames<\n O extends TableOccurrence<any, any, any, any> | undefined,\n> =\n O extends TableOccurrence<any, any, infer Nav, any>\n ? Nav extends Record<string, any>\n ? keyof Nav & string\n : never\n : never;\n\n// Helper type to resolve a navigation item (handles both direct and lazy-loaded)\ntype ResolveNavigationItem<T> = T extends () => infer R ? R : T;\n\n// Helper type to find target occurrence by relation name\ntype FindNavigationTarget<\n O extends TableOccurrence<any, any, any, any> | undefined,\n Name extends string,\n> =\n O extends TableOccurrence<any, any, infer Nav, any>\n ? Nav extends Record<string, any>\n ? Name extends keyof Nav\n ? ResolveNavigationItem<Nav[Name]>\n : TableOccurrence<\n BaseTable<Record<string, StandardSchemaV1>, any, any, any>,\n any,\n any,\n any\n >\n : TableOccurrence<\n BaseTable<Record<string, StandardSchemaV1>, any, any, any>,\n any,\n any,\n any\n >\n : TableOccurrence<\n BaseTable<Record<string, StandardSchemaV1>, any, any, any>,\n any,\n any,\n any\n >;\n\n// Helper type to get the inferred schema type from a target occurrence\ntype GetTargetSchemaType<\n O extends TableOccurrence<any, any, any, any> | undefined,\n Rel extends string,\n> = [FindNavigationTarget<O, Rel>] extends [\n TableOccurrence<infer BT, any, any, any>,\n]\n ? [BT] extends [BaseTable<infer S, any, any, any>]\n ? [S] extends [Record<string, StandardSchemaV1>]\n ? InferSchemaType<S>\n : Record<string, any>\n : Record<string, any>\n : Record<string, any>;\n\n// Internal type for expand configuration\ntype ExpandConfig = {\n relation: string;\n options?: Partial<QueryOptions<any>>;\n};\n\n// Type to represent expanded relations\nexport type ExpandedRelations = Record<string, { schema: any; selected: any }>;\n\n// Return type for RecordBuilder execute\nexport type RecordReturnType<\n T extends Record<string, any>,\n IsSingleField extends boolean,\n FieldKey extends keyof T,\n Selected extends keyof T,\n Expands extends ExpandedRelations,\n> = IsSingleField extends true\n ? T[FieldKey]\n : Pick<T, Selected> & {\n [K in keyof Expands]: Pick<\n Expands[K][\"schema\"],\n Expands[K][\"selected\"]\n >[];\n };\n\nexport class RecordBuilder<\n T extends Record<string, any>,\n IsSingleField extends boolean = false,\n FieldKey extends keyof T = keyof T,\n Occ extends TableOccurrence<any, any, any, any> | undefined =\n | TableOccurrence<any, any, any, any>\n | undefined,\n Selected extends keyof T = keyof T,\n Expands extends ExpandedRelations = {},\n> implements\n ExecutableBuilder<\n RecordReturnType<T, IsSingleField, FieldKey, Selected, Expands>\n >\n{\n private occurrence?: Occ;\n private tableName: string;\n private databaseName: string;\n private context: ExecutionContext;\n private recordId: string | number;\n private operation?: \"getSingleField\" | \"navigate\";\n private operationParam?: string;\n private isNavigateFromEntitySet?: boolean;\n private navigateRelation?: string;\n private navigateSourceTableName?: string;\n\n private databaseUseEntityIds: boolean;\n\n // New properties for select/expand support\n private selectedFields?: string[];\n private expandConfigs: ExpandConfig[] = [];\n\n constructor(config: {\n occurrence?: Occ;\n tableName: string;\n databaseName: string;\n context: ExecutionContext;\n recordId: string | number;\n databaseUseEntityIds?: boolean;\n }) {\n this.occurrence = config.occurrence;\n this.tableName = config.tableName;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.recordId = config.recordId;\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 if (!this.occurrence) {\n return this.tableName;\n }\n\n const contextDefault = this.context._getUseEntityIds?.() ?? false;\n const shouldUseIds = useEntityIds ?? contextDefault;\n\n if (shouldUseIds) {\n const identifiers = getTableIdentifiers(this.occurrence);\n if (!identifiers.id) {\n throw new Error(\n `useEntityIds is true but TableOccurrence \"${identifiers.name}\" does not have an fmtId defined`\n );\n }\n return identifiers.id;\n }\n\n return this.occurrence.getTableName();\n }\n\n getSingleField<K extends keyof T>(\n field: K,\n ): RecordBuilder<T, true, K, Occ, keyof T, {}> {\n const newBuilder = new RecordBuilder<T, true, K, Occ, keyof T, {}>({\n occurrence: this.occurrence,\n tableName: this.tableName,\n databaseName: this.databaseName,\n context: this.context,\n recordId: this.recordId,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n newBuilder.operation = \"getSingleField\";\n newBuilder.operationParam = field.toString();\n // Preserve navigation context\n newBuilder.isNavigateFromEntitySet = this.isNavigateFromEntitySet;\n newBuilder.navigateRelation = this.navigateRelation;\n newBuilder.navigateSourceTableName = this.navigateSourceTableName;\n return newBuilder;\n }\n\n /**\n * Select specific fields to retrieve from the record.\n * Only the selected fields will be returned in the response.\n *\n * @example\n * ```typescript\n * const contact = await db.from(\"contacts\").get(\"uuid\").select(\"name\", \"email\").execute();\n * // contact.data has type { name: string; email: string }\n * ```\n */\n select<K extends keyof T>(\n ...fields: K[]\n ): RecordBuilder<T, false, FieldKey, Occ, K, Expands> {\n const uniqueFields = [...new Set(fields)];\n const newBuilder = new RecordBuilder<T, false, FieldKey, Occ, K, Expands>({\n occurrence: this.occurrence,\n tableName: this.tableName,\n databaseName: this.databaseName,\n context: this.context,\n recordId: this.recordId,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n newBuilder.selectedFields = uniqueFields.map((f) => String(f));\n newBuilder.expandConfigs = [...this.expandConfigs];\n // Preserve navigation context\n newBuilder.isNavigateFromEntitySet = this.isNavigateFromEntitySet;\n newBuilder.navigateRelation = this.navigateRelation;\n newBuilder.navigateSourceTableName = this.navigateSourceTableName;\n return newBuilder;\n }\n\n /**\n * Expand a navigation property to include related records.\n * Supports nested select, filter, orderBy, and expand operations.\n *\n * @example\n * ```typescript\n * // Simple expand\n * const contact = await db.from(\"contacts\").get(\"uuid\").expand(\"users\").execute();\n *\n * // Expand with select\n * const contact = await db.from(\"contacts\").get(\"uuid\")\n * .expand(\"users\", b => b.select(\"username\", \"email\"))\n * .execute();\n * ```\n */\n expand<\n Rel extends ExtractNavigationNames<Occ> | (string & {}),\n TargetOcc extends FindNavigationTarget<Occ, Rel> = FindNavigationTarget<\n Occ,\n Rel\n >,\n TargetSchema extends GetTargetSchemaType<Occ, Rel> = GetTargetSchemaType<\n Occ,\n Rel\n >,\n TargetSelected extends keyof TargetSchema = keyof TargetSchema,\n >(\n relation: Rel,\n callback?: (\n builder: QueryBuilder<\n TargetSchema,\n keyof TargetSchema,\n false,\n false,\n TargetOcc extends TableOccurrence<any, any, any, any>\n ? TargetOcc\n : undefined\n >,\n ) => QueryBuilder<\n WithSystemFields<TargetSchema>,\n TargetSelected,\n any,\n any,\n any\n >,\n ): RecordBuilder<\n T,\n false,\n FieldKey,\n Occ,\n Selected,\n Expands & {\n [K in Rel]: { schema: TargetSchema; selected: TargetSelected };\n }\n > {\n // Look up target occurrence from navigation\n const targetOccurrence = this.occurrence?.navigation[relation as string];\n\n // Create new builder with updated types\n const newBuilder = new RecordBuilder<\n T,\n false,\n FieldKey,\n Occ,\n Selected,\n Expands & {\n [K in Rel]: { schema: TargetSchema; selected: TargetSelected };\n }\n >({\n occurrence: this.occurrence,\n tableName: this.tableName,\n databaseName: this.databaseName,\n context: this.context,\n recordId: this.recordId,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n\n // Copy existing state\n newBuilder.selectedFields = this.selectedFields;\n newBuilder.expandConfigs = [...this.expandConfigs];\n newBuilder.isNavigateFromEntitySet = this.isNavigateFromEntitySet;\n newBuilder.navigateRelation = this.navigateRelation;\n newBuilder.navigateSourceTableName = this.navigateSourceTableName;\n\n if (callback) {\n // Create a new QueryBuilder for the target occurrence\n const targetBuilder = new QueryBuilder<any>({\n occurrence: targetOccurrence,\n tableName: targetOccurrence?.name ?? (relation as string),\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n\n // Cast to the expected type for the callback\n // At runtime, the builder is untyped (any), but at compile-time we enforce proper types\n const typedBuilder = targetBuilder as QueryBuilder<\n TargetSchema,\n keyof TargetSchema,\n false,\n false,\n TargetOcc extends TableOccurrence<any, any, any, any>\n ? TargetOcc\n : undefined\n >;\n\n // Pass to callback and get configured builder\n const configuredBuilder = callback(typedBuilder);\n\n // Extract the builder's query options\n const expandOptions: Partial<QueryOptions<any>> = {\n ...(configuredBuilder as any).queryOptions,\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 const expandConfig: ExpandConfig = {\n relation: relation as string,\n options: expandOptions,\n };\n\n newBuilder.expandConfigs.push(expandConfig);\n } else {\n // Simple expand without callback\n newBuilder.expandConfigs.push({ relation: relation as string });\n }\n\n return newBuilder;\n }\n\n // Overload for valid relation names - returns typed QueryBuilder\n navigate<RelationName extends ExtractNavigationNames<Occ>>(\n relationName: RelationName,\n ): QueryBuilder<\n ExtractSchemaFromOccurrence<\n FindNavigationTarget<Occ, RelationName>\n > extends Record<string, StandardSchemaV1>\n ? InferSchemaType<\n ExtractSchemaFromOccurrence<FindNavigationTarget<Occ, RelationName>>\n >\n : Record<string, any>\n >;\n // Overload for arbitrary strings - returns generic QueryBuilder with system fields\n navigate(\n relationName: string,\n ): QueryBuilder<{ ROWID: number; ROWMODID: number; [key: string]: any }>;\n // Implementation\n navigate(relationName: string): QueryBuilder<any> {\n // Use the target occurrence if available, otherwise allow untyped navigation\n // (useful when types might be incomplete)\n const targetOccurrence = this.occurrence?.navigation[relationName];\n const builder = new QueryBuilder<any>({\n occurrence: targetOccurrence,\n tableName: targetOccurrence?.name ?? relationName,\n databaseName: this.databaseName,\n context: this.context,\n });\n // Store the navigation info - we'll use it in execute\n // Transform relation name to FMTID if using entity IDs\n const relationId = targetOccurrence\n ? transformTableName(targetOccurrence)\n : relationName;\n\n (builder as any).isNavigate = true;\n (builder as any).navigateRecordId = this.recordId;\n (builder as any).navigateRelation = relationId;\n\n // If this RecordBuilder came from a navigated EntitySet, we need to preserve that base path\n if (\n this.isNavigateFromEntitySet &&\n this.navigateSourceTableName &&\n this.navigateRelation\n ) {\n // Build the base path: /sourceTable/relation('recordId')/newRelation\n (builder as any).navigateSourceTableName = this.navigateSourceTableName;\n (builder as any).navigateBaseRelation = this.navigateRelation;\n } else {\n // Normal record navigation: /tableName('recordId')/relation\n // Transform source table name to FMTID if using entity IDs\n const sourceTableId = this.occurrence\n ? transformTableName(this.occurrence)\n : this.tableName;\n (builder as any).navigateSourceTableName = sourceTableId;\n }\n\n return builder;\n }\n\n /**\n * Formats select fields for use in query strings.\n * - Transforms field names to FMFIDs if using entity IDs\n * - Wraps \"id\" fields in double quotes\n * - URL-encodes special characters but preserves spaces\n */\n private formatSelectFields(\n select: string[] | undefined,\n baseTable?: BaseTable<any, any, any, any>,\n ): string {\n if (!select || select.length === 0) return \"\";\n\n // Transform to field IDs if using entity IDs\n const transformedFields = baseTable\n ? transformFieldNamesArray(select, baseTable)\n : select;\n\n return transformedFields\n .map((field) => {\n if (field === \"id\") return `\"id\"`;\n const encodedField = encodeURIComponent(String(field));\n return encodedField.replace(/%20/g, \" \");\n })\n .join(\",\");\n }\n\n /**\n * Builds expand validation configs from internal expand configurations.\n * These are used to validate expanded navigation properties.\n */\n private buildExpandValidationConfigs(\n configs: ExpandConfig[],\n ): ExpandValidationConfig[] {\n return configs.map((config) => {\n // Look up target occurrence from navigation\n const targetOccurrence = this.occurrence?.navigation[config.relation];\n const targetSchema = targetOccurrence?.baseTable?.schema;\n\n // Extract selected fields from options\n const selectedFields = config.options?.select\n ? Array.isArray(config.options.select)\n ? config.options.select.map((f) => String(f))\n : [String(config.options.select)]\n : undefined;\n\n return {\n relation: config.relation,\n targetSchema: targetSchema,\n targetOccurrence: targetOccurrence,\n targetBaseTable: targetOccurrence?.baseTable,\n occurrence: targetOccurrence, // For transformation\n selectedFields: selectedFields,\n nestedExpands: undefined, // TODO: Handle nested expands if needed\n };\n });\n }\n\n /**\n * Builds OData expand query string from expand configurations.\n * Handles nested expands recursively.\n * Transforms relation names to FMTIDs if using entity IDs.\n */\n private buildExpandString(configs: ExpandConfig[]): string {\n if (configs.length === 0) {\n return \"\";\n }\n\n return configs\n .map((config) => {\n // Get target occurrence for this relation\n const targetOccurrence = this.occurrence?.navigation[config.relation];\n\n // When using entity IDs, use the target table's FMTID in the expand parameter\n // FileMaker expects FMTID in $expand when Prefer header is set\n const relationName =\n targetOccurrence && targetOccurrence.isUsingTableId?.()\n ? targetOccurrence.getTableId()\n : config.relation;\n\n if (!config.options || Object.keys(config.options).length === 0) {\n // Simple expand without options\n return relationName;\n }\n\n // Build query options for this expand\n const parts: string[] = [];\n\n if (config.options.select) {\n // Pass target base table for field transformation\n const selectFields = this.formatSelectFields(\n Array.isArray(config.options.select)\n ? config.options.select.map((f) => String(f))\n : [String(config.options.select)],\n targetOccurrence?.baseTable,\n );\n parts.push(`$select=${selectFields}`);\n }\n\n if (config.options.filter) {\n // Filter should already be transformed by the nested builder\n // Use odata-query to build filter string\n const filterQuery = buildQuery({ filter: config.options.filter });\n const filterMatch = filterQuery.match(/\\$filter=([^&]+)/);\n if (filterMatch) {\n parts.push(`$filter=${filterMatch[1]}`);\n }\n }\n\n if (config.options.orderBy) {\n const orderByQuery = buildQuery({ orderBy: config.options.orderBy });\n const orderByMatch = orderByQuery.match(/\\$orderby=([^&]+)/);\n if (orderByMatch) {\n parts.push(`$orderby=${orderByMatch[1]}`);\n }\n }\n\n if (config.options.top !== undefined) {\n parts.push(`$top=${config.options.top}`);\n }\n\n if (config.options.skip !== undefined) {\n parts.push(`$skip=${config.options.skip}`);\n }\n\n // Handle nested expand\n if (config.options.expand) {\n // Nested expand is already a string from buildExpandString\n parts.push(`$expand=${String(config.options.expand)}`);\n }\n\n if (parts.length === 0) {\n return relationName;\n }\n\n return `${relationName}(${parts.join(\";\")})`;\n })\n .join(\",\");\n }\n\n /**\n * Builds the complete query string including $select and $expand parameters.\n */\n private buildQueryString(): string {\n const parts: string[] = [];\n\n // Build $select\n if (this.selectedFields && this.selectedFields.length > 0) {\n const selectString = this.formatSelectFields(\n this.selectedFields,\n this.occurrence?.baseTable,\n );\n if (selectString) {\n parts.push(`$select=${selectString}`);\n }\n }\n\n // Build $expand\n const expandString = this.buildExpandString(this.expandConfigs);\n if (expandString) {\n parts.push(`$expand=${expandString}`);\n }\n\n if (parts.length === 0) {\n return \"\";\n }\n\n return `?${parts.join(\"&\")}`;\n }\n\n /**\n * Helper to conditionally strip OData annotations based on options\n */\n private stripODataAnnotationsIfNeeded<R extends Record<string, any>>(\n data: R,\n options?: ExecuteOptions,\n ): R {\n // Only include annotations if explicitly requested\n if (options?.includeODataAnnotations === true) {\n return data;\n }\n\n // Strip OData annotations\n const { \"@id\": _id, \"@editLink\": _editLink, ...rest } = data;\n return rest as R;\n }\n\n async execute<EO extends ExecuteOptions>(\n options?: RequestInit & FFetchOptions & EO,\n ): Promise<\n Result<\n ConditionallyWithODataAnnotations<\n RecordReturnType<T, IsSingleField, FieldKey, Selected, Expands>,\n EO[\"includeODataAnnotations\"] extends true ? true : false\n >\n >\n > {\n let url: string;\n\n // Build the base URL depending on whether this came from a navigated EntitySet\n if (\n this.isNavigateFromEntitySet &&\n this.navigateSourceTableName &&\n this.navigateRelation\n ) {\n // From navigated EntitySet: /sourceTable/relation('recordId')\n url = `/${this.databaseName}/${this.navigateSourceTableName}/${this.navigateRelation}('${this.recordId}')`;\n } else {\n // Normal record: /tableName('recordId') - use FMTID if configured\n const tableId = this.getTableId(\n options?.useEntityIds ?? this.databaseUseEntityIds,\n );\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n }\n\n if (this.operation === \"getSingleField\" && this.operationParam) {\n url += `/${this.operationParam}`;\n } else {\n // Add query string for select/expand (only when not getting a single field)\n const queryString = this.buildQueryString();\n url += queryString;\n }\n\n const mergedOptions = this.mergeExecuteOptions(options);\n const result = await this.context._makeRequest(url, mergedOptions);\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n let response = result.data;\n\n // Handle single field operation\n if (this.operation === \"getSingleField\") {\n // Single field returns a JSON object with @context and value\n const fieldResponse = response as ODataFieldResponse<T>;\n return { data: fieldResponse.value as any, error: undefined };\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 = mergedOptions.useEntityIds ?? false;\n\n // Build expand validation configs for transformation and validation\n const expandValidationConfigs =\n this.expandConfigs.length > 0\n ? this.buildExpandValidationConfigs(this.expandConfigs)\n : undefined;\n\n if (this.occurrence?.baseTable && shouldUseIds) {\n response = transformResponseFields(\n response,\n this.occurrence.baseTable,\n expandValidationConfigs,\n );\n }\n\n // Get schema from occurrence if available\n const schema = this.occurrence?.baseTable?.schema;\n\n // Validate the single record response\n const validation = await validateSingleResponse<any>(\n response,\n schema,\n this.selectedFields as (keyof T)[] | undefined,\n expandValidationConfigs,\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\n if (validation.data === null) {\n return { data: null as any, error: undefined };\n }\n\n // Strip OData annotations if not requested\n const stripped = this.stripODataAnnotationsIfNeeded(\n validation.data,\n options,\n );\n\n return { data: stripped as any, error: undefined };\n }\n\n getRequestConfig(): { method: string; url: string; body?: any } {\n let url: string;\n\n // Build the base URL depending on whether this came from a navigated EntitySet\n if (\n this.isNavigateFromEntitySet &&\n this.navigateSourceTableName &&\n this.navigateRelation\n ) {\n // From navigated EntitySet: /sourceTable/relation('recordId')\n url = `/${this.databaseName}/${this.navigateSourceTableName}/${this.navigateRelation}('${this.recordId}')`;\n } else {\n // For batch operations, use database-level setting (no per-request override available here)\n const tableId = this.getTableId(this.databaseUseEntityIds);\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n }\n\n if (this.operation === \"getSingleField\" && this.operationParam) {\n url += `/${this.operationParam}`;\n } else {\n // Add query string for select/expand (only when not getting a single field)\n const queryString = this.buildQueryString();\n url += queryString;\n }\n\n return {\n method: \"GET\",\n url,\n };\n }\n\n /**\n * Returns the query string for this record builder (for testing purposes).\n */\n getQueryString(): string {\n let path: string;\n\n // Build the path depending on navigation context\n if (\n this.isNavigateFromEntitySet &&\n this.navigateSourceTableName &&\n this.navigateRelation\n ) {\n path = `/${this.navigateSourceTableName}/${this.navigateRelation}('${this.recordId}')`;\n } else {\n path = `/${this.tableName}('${this.recordId}')`;\n }\n\n if (this.operation === \"getSingleField\" && this.operationParam) {\n return `${path}/${this.operationParam}`;\n }\n\n const queryString = this.buildQueryString();\n return `${path}${queryString}`;\n }\n\n toRequest(baseUrl: string): 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 \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n });\n }\n\n async processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<\n Result<RecordReturnType<T, IsSingleField, FieldKey, Selected, Expands>>\n > {\n const rawResponse = await response.json();\n\n // Handle single field operation\n if (this.operation === \"getSingleField\") {\n // Single field returns a JSON object with @context and value\n const fieldResponse = rawResponse as ODataFieldResponse<T>;\n return { data: fieldResponse.value as any, error: undefined };\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 // Build expand validation configs for transformation and validation\n const expandValidationConfigs =\n this.expandConfigs.length > 0\n ? this.buildExpandValidationConfigs(this.expandConfigs)\n : undefined;\n\n let transformedResponse = rawResponse;\n if (this.occurrence?.baseTable && shouldUseIds) {\n transformedResponse = transformResponseFields(\n rawResponse,\n this.occurrence.baseTable,\n expandValidationConfigs,\n );\n }\n\n // Get schema from occurrence if available\n const schema = this.occurrence?.baseTable?.schema;\n\n // Validate the single record response\n const validation = await validateSingleResponse<any>(\n transformedResponse,\n schema,\n this.selectedFields as (keyof T)[] | undefined,\n expandValidationConfigs,\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\n if (validation.data === null) {\n return { data: null as any, error: undefined };\n }\n\n // Strip OData annotations if not requested\n const stripped = this.stripODataAnnotationsIfNeeded(\n validation.data,\n options,\n );\n\n return { data: stripped as any, error: undefined };\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"record-builder.js","sources":["../../../src/client/record-builder.ts"],"sourcesContent":["import type {\n ExecutionContext,\n ExecutableBuilder,\n Result,\n ODataRecordMetadata,\n ODataFieldResponse,\n InferSchemaType,\n ExecuteOptions,\n WithSystemFields,\n ConditionallyWithODataAnnotations,\n} from \"../types\";\nimport { getAcceptHeader } from \"../types\";\nimport type { TableOccurrence } from \"./table-occurrence\";\nimport type { BaseTable } from \"./base-table\";\nimport {\n transformTableName,\n transformResponseFields,\n getTableIdentifiers,\n transformFieldNamesArray,\n} from \"../transform\";\nimport { safeJsonParse } from \"./sanitize-json\";\nimport { QueryBuilder } from \"./query-builder\";\nimport {\n validateSingleResponse,\n type ExpandValidationConfig,\n} from \"../validation\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { QueryOptions } from \"odata-query\";\nimport buildQuery from \"odata-query\";\n\n// Helper type to extract schema from a TableOccurrence\ntype ExtractSchemaFromOccurrence<O> =\n O extends TableOccurrence<infer BT, any, any, any>\n ? BT extends BaseTable<infer S, any, any, any>\n ? S\n : never\n : never;\n\n// Helper type to extract navigation relation names from an occurrence\ntype ExtractNavigationNames<\n O extends TableOccurrence<any, any, any, any> | undefined,\n> =\n O extends TableOccurrence<any, any, infer Nav, any>\n ? Nav extends Record<string, any>\n ? keyof Nav & string\n : never\n : never;\n\n// Helper type to find target occurrence by relation name\ntype FindNavigationTarget<\n O extends TableOccurrence<any, any, any, any> | undefined,\n Name extends string,\n> =\n O extends TableOccurrence<any, any, infer Nav, any>\n ? Nav extends Record<string, any>\n ? Name extends keyof Nav\n ? Nav[Name]\n : TableOccurrence<\n BaseTable<Record<string, StandardSchemaV1>, any, any, any>,\n any,\n any,\n any\n >\n : TableOccurrence<\n BaseTable<Record<string, StandardSchemaV1>, any, any, any>,\n any,\n any,\n any\n >\n : TableOccurrence<\n BaseTable<Record<string, StandardSchemaV1>, any, any, any>,\n any,\n any,\n any\n >;\n\n// Helper type to get the inferred schema type from a target occurrence\ntype GetTargetSchemaType<\n O extends TableOccurrence<any, any, any, any> | undefined,\n Rel extends string,\n> = [FindNavigationTarget<O, Rel>] extends [\n TableOccurrence<infer BT, any, any, any>,\n]\n ? [BT] extends [BaseTable<infer S, any, any, any>]\n ? [S] extends [Record<string, StandardSchemaV1>]\n ? InferSchemaType<S>\n : Record<string, any>\n : Record<string, any>\n : Record<string, any>;\n\n// Internal type for expand configuration\ntype ExpandConfig = {\n relation: string;\n options?: Partial<QueryOptions<any>>;\n};\n\n// Type to represent expanded relations\nexport type ExpandedRelations = Record<string, { schema: any; selected: any }>;\n\n// Return type for RecordBuilder execute\nexport type RecordReturnType<\n T extends Record<string, any>,\n IsSingleField extends boolean,\n FieldKey extends keyof T,\n Selected extends keyof T,\n Expands extends ExpandedRelations,\n> = IsSingleField extends true\n ? T[FieldKey]\n : Pick<T, Selected> & {\n [K in keyof Expands]: Pick<\n Expands[K][\"schema\"],\n Expands[K][\"selected\"]\n >[];\n };\n\nexport class RecordBuilder<\n T extends Record<string, any>,\n IsSingleField extends boolean = false,\n FieldKey extends keyof T = keyof T,\n Occ extends TableOccurrence<any, any, any, any> | undefined =\n | TableOccurrence<any, any, any, any>\n | undefined,\n Selected extends keyof T = keyof T,\n Expands extends ExpandedRelations = {},\n> implements\n ExecutableBuilder<\n RecordReturnType<T, IsSingleField, FieldKey, Selected, Expands>\n >\n{\n private occurrence?: Occ;\n private tableName: string;\n private databaseName: string;\n private context: ExecutionContext;\n private recordId: string | number;\n private operation?: \"getSingleField\" | \"navigate\";\n private operationParam?: string;\n private isNavigateFromEntitySet?: boolean;\n private navigateRelation?: string;\n private navigateSourceTableName?: string;\n\n private databaseUseEntityIds: boolean;\n\n // New properties for select/expand support\n private selectedFields?: string[];\n private expandConfigs: ExpandConfig[] = [];\n\n constructor(config: {\n occurrence?: Occ;\n tableName: string;\n databaseName: string;\n context: ExecutionContext;\n recordId: string | number;\n databaseUseEntityIds?: boolean;\n }) {\n this.occurrence = config.occurrence;\n this.tableName = config.tableName;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.recordId = config.recordId;\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 if (!this.occurrence) {\n return this.tableName;\n }\n\n const contextDefault = this.context._getUseEntityIds?.() ?? false;\n const shouldUseIds = useEntityIds ?? contextDefault;\n\n if (shouldUseIds) {\n const identifiers = getTableIdentifiers(this.occurrence);\n if (!identifiers.id) {\n throw new Error(\n `useEntityIds is true but TableOccurrence \"${identifiers.name}\" does not have an fmtId defined`,\n );\n }\n return identifiers.id;\n }\n\n return this.occurrence.getTableName();\n }\n\n getSingleField<K extends keyof T>(\n field: K,\n ): RecordBuilder<T, true, K, Occ, keyof T, {}> {\n const newBuilder = new RecordBuilder<T, true, K, Occ, keyof T, {}>({\n occurrence: this.occurrence,\n tableName: this.tableName,\n databaseName: this.databaseName,\n context: this.context,\n recordId: this.recordId,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n newBuilder.operation = \"getSingleField\";\n newBuilder.operationParam = field.toString();\n // Preserve navigation context\n newBuilder.isNavigateFromEntitySet = this.isNavigateFromEntitySet;\n newBuilder.navigateRelation = this.navigateRelation;\n newBuilder.navigateSourceTableName = this.navigateSourceTableName;\n return newBuilder;\n }\n\n /**\n * Select specific fields to retrieve from the record.\n * Only the selected fields will be returned in the response.\n *\n * @example\n * ```typescript\n * const contact = await db.from(\"contacts\").get(\"uuid\").select(\"name\", \"email\").execute();\n * // contact.data has type { name: string; email: string }\n * ```\n */\n select<K extends keyof T>(\n ...fields: K[]\n ): RecordBuilder<T, false, FieldKey, Occ, K, Expands> {\n const uniqueFields = [...new Set(fields)];\n const newBuilder = new RecordBuilder<T, false, FieldKey, Occ, K, Expands>({\n occurrence: this.occurrence,\n tableName: this.tableName,\n databaseName: this.databaseName,\n context: this.context,\n recordId: this.recordId,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n newBuilder.selectedFields = uniqueFields.map((f) => String(f));\n newBuilder.expandConfigs = [...this.expandConfigs];\n // Preserve navigation context\n newBuilder.isNavigateFromEntitySet = this.isNavigateFromEntitySet;\n newBuilder.navigateRelation = this.navigateRelation;\n newBuilder.navigateSourceTableName = this.navigateSourceTableName;\n return newBuilder;\n }\n\n /**\n * Expand a navigation property to include related records.\n * Supports nested select, filter, orderBy, and expand operations.\n *\n * @example\n * ```typescript\n * // Simple expand\n * const contact = await db.from(\"contacts\").get(\"uuid\").expand(\"users\").execute();\n *\n * // Expand with select\n * const contact = await db.from(\"contacts\").get(\"uuid\")\n * .expand(\"users\", b => b.select(\"username\", \"email\"))\n * .execute();\n * ```\n */\n expand<\n Rel extends ExtractNavigationNames<Occ> | (string & {}),\n TargetOcc extends FindNavigationTarget<Occ, Rel> = FindNavigationTarget<\n Occ,\n Rel\n >,\n TargetSchema extends GetTargetSchemaType<Occ, Rel> = GetTargetSchemaType<\n Occ,\n Rel\n >,\n TargetSelected extends keyof TargetSchema = keyof TargetSchema,\n >(\n relation: Rel,\n callback?: (\n builder: QueryBuilder<\n TargetSchema,\n keyof TargetSchema,\n false,\n false,\n TargetOcc extends TableOccurrence<any, any, any, any>\n ? TargetOcc\n : undefined\n >,\n ) => QueryBuilder<\n WithSystemFields<TargetSchema>,\n TargetSelected,\n any,\n any,\n any\n >,\n ): RecordBuilder<\n T,\n false,\n FieldKey,\n Occ,\n Selected,\n Expands & {\n [K in Rel]: { schema: TargetSchema; selected: TargetSelected };\n }\n > {\n // Look up target occurrence from navigation\n const targetOccurrence = this.occurrence?.navigation[relation as string];\n\n // Helper function to get defaultSelect fields from target occurrence\n const getDefaultSelectFields = (): string[] | undefined => {\n if (!targetOccurrence) return undefined;\n const defaultSelect = targetOccurrence.defaultSelect;\n if (defaultSelect === \"schema\") {\n const schema = targetOccurrence.baseTable?.schema;\n if (schema) {\n return [...new Set(Object.keys(schema))];\n }\n } else if (Array.isArray(defaultSelect)) {\n return [...new Set(defaultSelect)];\n }\n // If \"all\", return undefined (no select restriction)\n return undefined;\n };\n\n // Create new builder with updated types\n const newBuilder = new RecordBuilder<\n T,\n false,\n FieldKey,\n Occ,\n Selected,\n Expands & {\n [K in Rel]: { schema: TargetSchema; selected: TargetSelected };\n }\n >({\n occurrence: this.occurrence,\n tableName: this.tableName,\n databaseName: this.databaseName,\n context: this.context,\n recordId: this.recordId,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n\n // Copy existing state\n newBuilder.selectedFields = this.selectedFields;\n newBuilder.expandConfigs = [...this.expandConfigs];\n newBuilder.isNavigateFromEntitySet = this.isNavigateFromEntitySet;\n newBuilder.navigateRelation = this.navigateRelation;\n newBuilder.navigateSourceTableName = this.navigateSourceTableName;\n\n if (callback) {\n // Create a new QueryBuilder for the target occurrence\n const targetBuilder = new QueryBuilder<any>({\n occurrence: targetOccurrence,\n tableName: targetOccurrence?.name ?? (relation as string),\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n\n // Cast to the expected type for the callback\n // At runtime, the builder is untyped (any), but at compile-time we enforce proper types\n const typedBuilder = targetBuilder as QueryBuilder<\n TargetSchema,\n keyof TargetSchema,\n false,\n false,\n TargetOcc extends TableOccurrence<any, any, any, any>\n ? TargetOcc\n : undefined\n >;\n\n // Pass to callback and get configured builder\n const configuredBuilder = callback(typedBuilder);\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 occurrence\n if (!expandOptions.select) {\n const defaultFields = getDefaultSelectFields();\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 const expandConfig: ExpandConfig = {\n relation: relation as string,\n options: expandOptions,\n };\n\n newBuilder.expandConfigs.push(expandConfig);\n } else {\n // Simple expand without callback - apply defaultSelect if available\n const defaultFields = getDefaultSelectFields();\n if (defaultFields) {\n newBuilder.expandConfigs.push({\n relation: relation as string,\n options: { select: defaultFields },\n });\n } else {\n newBuilder.expandConfigs.push({ relation: relation as string });\n }\n }\n\n return newBuilder;\n }\n\n // Overload for valid relation names - returns typed QueryBuilder\n navigate<RelationName extends ExtractNavigationNames<Occ>>(\n relationName: RelationName,\n ): QueryBuilder<\n ExtractSchemaFromOccurrence<\n FindNavigationTarget<Occ, RelationName>\n > extends Record<string, StandardSchemaV1>\n ? InferSchemaType<\n ExtractSchemaFromOccurrence<FindNavigationTarget<Occ, RelationName>>\n >\n : Record<string, any>\n >;\n // Overload for arbitrary strings - returns generic QueryBuilder with system fields\n navigate(\n relationName: string,\n ): QueryBuilder<{ ROWID: number; ROWMODID: number; [key: string]: any }>;\n // Implementation\n navigate(relationName: string): QueryBuilder<any> {\n // Use the target occurrence if available, otherwise allow untyped navigation\n // (useful when types might be incomplete)\n const targetOccurrence = this.occurrence?.navigation[relationName];\n const builder = new QueryBuilder<any>({\n occurrence: targetOccurrence,\n tableName: targetOccurrence?.name ?? relationName,\n databaseName: this.databaseName,\n context: this.context,\n });\n // Store the navigation info - we'll use it in execute\n // Transform relation name to FMTID if using entity IDs\n const relationId = targetOccurrence\n ? transformTableName(targetOccurrence)\n : relationName;\n\n (builder as any).isNavigate = true;\n (builder as any).navigateRecordId = this.recordId;\n (builder as any).navigateRelation = relationId;\n\n // If this RecordBuilder came from a navigated EntitySet, we need to preserve that base path\n if (\n this.isNavigateFromEntitySet &&\n this.navigateSourceTableName &&\n this.navigateRelation\n ) {\n // Build the base path: /sourceTable/relation('recordId')/newRelation\n (builder as any).navigateSourceTableName = this.navigateSourceTableName;\n (builder as any).navigateBaseRelation = this.navigateRelation;\n } else {\n // Normal record navigation: /tableName('recordId')/relation\n // Transform source table name to FMTID if using entity IDs\n const sourceTableId = this.occurrence\n ? transformTableName(this.occurrence)\n : this.tableName;\n (builder as any).navigateSourceTableName = sourceTableId;\n }\n\n return builder;\n }\n\n /**\n * Formats select fields for use in query strings.\n * - Transforms field names to FMFIDs if using entity IDs\n * - Wraps \"id\" fields in double quotes\n * - URL-encodes special characters but preserves spaces\n */\n private formatSelectFields(\n select: string[] | undefined,\n baseTable?: BaseTable<any, any, any, any>,\n ): string {\n if (!select || select.length === 0) return \"\";\n\n // Transform to field IDs if using entity IDs\n const transformedFields = baseTable\n ? transformFieldNamesArray(select, baseTable)\n : select;\n\n return transformedFields\n .map((field) => {\n if (field === \"id\") return `\"id\"`;\n const encodedField = encodeURIComponent(String(field));\n return encodedField.replace(/%20/g, \" \");\n })\n .join(\",\");\n }\n\n /**\n * Builds expand validation configs from internal expand configurations.\n * These are used to validate expanded navigation properties.\n */\n private buildExpandValidationConfigs(\n configs: ExpandConfig[],\n ): ExpandValidationConfig[] {\n return configs.map((config) => {\n // Look up target occurrence from navigation\n const targetOccurrence = this.occurrence?.navigation[config.relation];\n const targetSchema = targetOccurrence?.baseTable?.schema;\n\n // Extract selected fields from options\n const selectedFields = config.options?.select\n ? Array.isArray(config.options.select)\n ? config.options.select.map((f) => String(f))\n : [String(config.options.select)]\n : undefined;\n\n return {\n relation: config.relation,\n targetSchema: targetSchema,\n targetOccurrence: targetOccurrence,\n targetBaseTable: targetOccurrence?.baseTable,\n occurrence: targetOccurrence, // For transformation\n selectedFields: selectedFields,\n nestedExpands: undefined, // TODO: Handle nested expands if needed\n };\n });\n }\n\n /**\n * Builds OData expand query string from expand configurations.\n * Handles nested expands recursively.\n * Transforms relation names to FMTIDs if using entity IDs.\n */\n private buildExpandString(configs: ExpandConfig[]): string {\n if (configs.length === 0) {\n return \"\";\n }\n\n return configs\n .map((config) => {\n // Get target occurrence for this relation\n const targetOccurrence = this.occurrence?.navigation[config.relation];\n\n // When using entity IDs, use the target table's FMTID in the expand parameter\n // FileMaker expects FMTID in $expand when Prefer header is set\n const relationName =\n targetOccurrence && targetOccurrence.isUsingTableId?.()\n ? targetOccurrence.getTableId()\n : config.relation;\n\n if (!config.options || Object.keys(config.options).length === 0) {\n // Simple expand without options\n return relationName;\n }\n\n // Build query options for this expand\n const parts: string[] = [];\n\n if (config.options.select) {\n // Pass target base table for field transformation\n const selectFields = this.formatSelectFields(\n Array.isArray(config.options.select)\n ? config.options.select.map((f) => String(f))\n : [String(config.options.select)],\n targetOccurrence?.baseTable,\n );\n parts.push(`$select=${selectFields}`);\n }\n\n if (config.options.filter) {\n // Filter should already be transformed by the nested builder\n // Use odata-query to build filter string\n const filterQuery = buildQuery({ filter: config.options.filter });\n const filterMatch = filterQuery.match(/\\$filter=([^&]+)/);\n if (filterMatch) {\n parts.push(`$filter=${filterMatch[1]}`);\n }\n }\n\n if (config.options.orderBy) {\n const orderByQuery = buildQuery({ orderBy: config.options.orderBy });\n const orderByMatch = orderByQuery.match(/\\$orderby=([^&]+)/);\n if (orderByMatch) {\n parts.push(`$orderby=${orderByMatch[1]}`);\n }\n }\n\n if (config.options.top !== undefined) {\n parts.push(`$top=${config.options.top}`);\n }\n\n if (config.options.skip !== undefined) {\n parts.push(`$skip=${config.options.skip}`);\n }\n\n // Handle nested expand\n if (config.options.expand) {\n // Nested expand is already a string from buildExpandString\n parts.push(`$expand=${String(config.options.expand)}`);\n }\n\n if (parts.length === 0) {\n return relationName;\n }\n\n return `${relationName}(${parts.join(\";\")})`;\n })\n .join(\",\");\n }\n\n /**\n * Builds the complete query string including $select and $expand parameters.\n */\n private buildQueryString(): string {\n const parts: string[] = [];\n\n // Build $select\n if (this.selectedFields && this.selectedFields.length > 0) {\n const selectString = this.formatSelectFields(\n this.selectedFields,\n this.occurrence?.baseTable,\n );\n if (selectString) {\n parts.push(`$select=${selectString}`);\n }\n }\n\n // Build $expand\n const expandString = this.buildExpandString(this.expandConfigs);\n if (expandString) {\n parts.push(`$expand=${expandString}`);\n }\n\n if (parts.length === 0) {\n return \"\";\n }\n\n return `?${parts.join(\"&\")}`;\n }\n\n\n async execute<EO extends ExecuteOptions>(\n options?: RequestInit & FFetchOptions & EO,\n ): Promise<\n Result<\n ConditionallyWithODataAnnotations<\n RecordReturnType<T, IsSingleField, FieldKey, Selected, Expands>,\n EO[\"includeODataAnnotations\"] extends true ? true : false\n >\n >\n > {\n let url: string;\n\n // Build the base URL depending on whether this came from a navigated EntitySet\n if (\n this.isNavigateFromEntitySet &&\n this.navigateSourceTableName &&\n this.navigateRelation\n ) {\n // From navigated EntitySet: /sourceTable/relation('recordId')\n url = `/${this.databaseName}/${this.navigateSourceTableName}/${this.navigateRelation}('${this.recordId}')`;\n } else {\n // Normal record: /tableName('recordId') - use FMTID if configured\n const tableId = this.getTableId(\n options?.useEntityIds ?? this.databaseUseEntityIds,\n );\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n }\n\n if (this.operation === \"getSingleField\" && this.operationParam) {\n url += `/${this.operationParam}`;\n } else {\n // Add query string for select/expand (only when not getting a single field)\n const queryString = this.buildQueryString();\n url += queryString;\n }\n\n const mergedOptions = this.mergeExecuteOptions(options);\n const result = await this.context._makeRequest(url, mergedOptions);\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n let response = result.data;\n\n // Handle single field operation\n if (this.operation === \"getSingleField\") {\n // Single field returns a JSON object with @context and value\n const fieldResponse = response as ODataFieldResponse<T>;\n return { data: fieldResponse.value as any, error: undefined };\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 = mergedOptions.useEntityIds ?? false;\n\n // Build expand validation configs for transformation and validation\n const expandValidationConfigs =\n this.expandConfigs.length > 0\n ? this.buildExpandValidationConfigs(this.expandConfigs)\n : undefined;\n\n if (this.occurrence?.baseTable && shouldUseIds) {\n response = transformResponseFields(\n response,\n this.occurrence.baseTable,\n expandValidationConfigs,\n );\n }\n\n // Get schema from occurrence if available\n const schema = this.occurrence?.baseTable?.schema;\n\n // Validate the single record response\n const validation = await validateSingleResponse<any>(\n response,\n schema,\n this.selectedFields as (keyof T)[] | undefined,\n expandValidationConfigs,\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\n if (validation.data === null) {\n return { data: null as any, error: undefined };\n }\n\n return { data: validation.data as any, error: undefined };\n }\n\n getRequestConfig(): { method: string; url: string; body?: any } {\n let url: string;\n\n // Build the base URL depending on whether this came from a navigated EntitySet\n if (\n this.isNavigateFromEntitySet &&\n this.navigateSourceTableName &&\n this.navigateRelation\n ) {\n // From navigated EntitySet: /sourceTable/relation('recordId')\n url = `/${this.databaseName}/${this.navigateSourceTableName}/${this.navigateRelation}('${this.recordId}')`;\n } else {\n // For batch operations, use database-level setting (no per-request override available here)\n const tableId = this.getTableId(this.databaseUseEntityIds);\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n }\n\n if (this.operation === \"getSingleField\" && this.operationParam) {\n url += `/${this.operationParam}`;\n } else {\n // Add query string for select/expand (only when not getting a single field)\n const queryString = this.buildQueryString();\n url += queryString;\n }\n\n return {\n method: \"GET\",\n url,\n };\n }\n\n /**\n * Returns the query string for this record builder (for testing purposes).\n */\n getQueryString(): string {\n let path: string;\n\n // Build the path depending on navigation context\n if (\n this.isNavigateFromEntitySet &&\n this.navigateSourceTableName &&\n this.navigateRelation\n ) {\n path = `/${this.navigateSourceTableName}/${this.navigateRelation}('${this.recordId}')`;\n } else {\n // Use getTableId to respect entity ID settings (same as getRequestConfig)\n const tableId = this.getTableId(this.databaseUseEntityIds);\n path = `/${tableId}('${this.recordId}')`;\n }\n\n if (this.operation === \"getSingleField\" && this.operationParam) {\n return `${path}/${this.operationParam}`;\n }\n\n const queryString = this.buildQueryString();\n return `${path}${queryString}`;\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 \"Content-Type\": \"application/json\",\n Accept: getAcceptHeader(options?.includeODataAnnotations),\n },\n });\n }\n\n async processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<\n Result<RecordReturnType<T, IsSingleField, FieldKey, Selected, Expands>>\n > {\n // Use safeJsonParse to handle FileMaker's invalid JSON with unquoted ? values\n const rawResponse = await safeJsonParse(response);\n\n // Handle single field operation\n if (this.operation === \"getSingleField\") {\n // Single field returns a JSON object with @context and value\n const fieldResponse = rawResponse as ODataFieldResponse<T>;\n return { data: fieldResponse.value as any, error: undefined };\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 // Build expand validation configs for transformation and validation\n const expandValidationConfigs =\n this.expandConfigs.length > 0\n ? this.buildExpandValidationConfigs(this.expandConfigs)\n : undefined;\n\n let transformedResponse = rawResponse;\n if (this.occurrence?.baseTable && shouldUseIds) {\n transformedResponse = transformResponseFields(\n rawResponse,\n this.occurrence.baseTable,\n expandValidationConfigs,\n );\n }\n\n // Get schema from occurrence if available\n const schema = this.occurrence?.baseTable?.schema;\n\n // Validate the single record response\n const validation = await validateSingleResponse<any>(\n transformedResponse,\n schema,\n this.selectedFields as (keyof T)[] | undefined,\n expandValidationConfigs,\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\n if (validation.data === null) {\n return { data: null as any, error: undefined };\n }\n\n return { data: validation.data as any, error: undefined };\n }\n}\n"],"names":["_a"],"mappings":"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"}
@@ -1,6 +1,5 @@
1
1
  import { StandardSchemaV1 } from '@standard-schema/spec';
2
2
  import { BaseTable } from './base-table.js';
3
- import { ExecuteOptions } from '../types.js';
4
3
  import { ExpandValidationConfig } from '../validation.js';
5
4
  import { ValidationError, ResponseStructureError } from '../errors.js';
6
5
  export type ODataResponse<T = unknown> = T & {
@@ -14,10 +13,6 @@ export type ODataRecordResponse<T = unknown> = ODataResponse<T & {
14
13
  "@id"?: string;
15
14
  "@editLink"?: string;
16
15
  }>;
17
- /**
18
- * Strip OData annotations from a single record
19
- */
20
- export declare function stripODataAnnotations<T extends Record<string, unknown>>(record: ODataRecordResponse<T>, options?: ExecuteOptions): T;
21
16
  /**
22
17
  * Transform field IDs back to names using the base table configuration
23
18
  */
@@ -0,0 +1,35 @@
1
+ /**
2
+ * FileMaker OData API sometimes returns invalid JSON containing unquoted `?`
3
+ * characters as field values (e.g., `"fieldName": ?`), which causes JSON.parse()
4
+ * to fail. This module provides utilities to sanitize such responses before parsing.
5
+ */
6
+ /**
7
+ * Sanitizes FileMaker OData JSON responses by replacing unquoted `?` values with `null`.
8
+ *
9
+ * FileMaker uses `?` to represent undefined/null values in its OData responses,
10
+ * but this is not valid JSON. This function converts those to proper `null` values.
11
+ *
12
+ * The regex uses two patterns:
13
+ * 1. `/:\s*\?(?=\s*[,}\]])/g` - for values in objects (after `:`)
14
+ * 2. `/(?<=[\[,])\s*\?(?=\s*[,\]])/g` - for values in arrays (after `[` or `,`)
15
+ *
16
+ * @param text - The raw response text from FileMaker OData API
17
+ * @returns Sanitized JSON string with `?` values replaced by `null`
18
+ *
19
+ * @example
20
+ * sanitizeFileMakerJson('{"field1": "valid", "field2": ?, "field3": null}')
21
+ * // Returns: '{"field1": "valid", "field2": null, "field3": null}'
22
+ */
23
+ export declare function sanitizeFileMakerJson(text: string): string;
24
+ /**
25
+ * Safely parses a Response body as JSON, handling FileMaker's invalid JSON responses.
26
+ *
27
+ * This function reads the response as text first, sanitizes any invalid `?` values,
28
+ * and then parses the sanitized JSON. This approach handles the case where FileMaker
29
+ * returns a Content-Type of application/json but the body contains invalid JSON.
30
+ *
31
+ * @param response - The fetch Response object
32
+ * @returns Parsed JSON data
33
+ * @throws ResponseParseError if the JSON is still invalid after sanitization (includes sanitized text for debugging)
34
+ */
35
+ export declare function safeJsonParse<T = unknown>(response: Response): Promise<T>;
@@ -0,0 +1,27 @@
1
+ import { ResponseParseError } from "../errors.js";
2
+ function sanitizeFileMakerJson(text) {
3
+ let result = text.replace(/:\s*\?(?=\s*[,}\]])/g, ": null");
4
+ result = result.replace(new RegExp("(?<=[\\[,])\\s*\\?(?=\\s*[,\\]])", "g"), " null");
5
+ return result;
6
+ }
7
+ async function safeJsonParse(response) {
8
+ const text = await response.text();
9
+ const sanitized = sanitizeFileMakerJson(text);
10
+ try {
11
+ return JSON.parse(sanitized);
12
+ } catch (err) {
13
+ throw new ResponseParseError(
14
+ response.url,
15
+ `Failed to parse response as JSON: ${err instanceof Error ? err.message : "Unknown error"}`,
16
+ {
17
+ rawText: sanitized,
18
+ cause: err instanceof Error ? err : void 0
19
+ }
20
+ );
21
+ }
22
+ }
23
+ export {
24
+ safeJsonParse,
25
+ sanitizeFileMakerJson
26
+ };
27
+ //# sourceMappingURL=sanitize-json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize-json.js","sources":["../../../src/client/sanitize-json.ts"],"sourcesContent":["/**\n * FileMaker OData API sometimes returns invalid JSON containing unquoted `?`\n * characters as field values (e.g., `\"fieldName\": ?`), which causes JSON.parse()\n * to fail. This module provides utilities to sanitize such responses before parsing.\n */\n\nimport { ResponseParseError } from \"../errors\";\n\n/**\n * Sanitizes FileMaker OData JSON responses by replacing unquoted `?` values with `null`.\n *\n * FileMaker uses `?` to represent undefined/null values in its OData responses,\n * but this is not valid JSON. This function converts those to proper `null` values.\n *\n * The regex uses two patterns:\n * 1. `/:\\s*\\?(?=\\s*[,}\\]])/g` - for values in objects (after `:`)\n * 2. `/(?<=[\\[,])\\s*\\?(?=\\s*[,\\]])/g` - for values in arrays (after `[` or `,`)\n *\n * @param text - The raw response text from FileMaker OData API\n * @returns Sanitized JSON string with `?` values replaced by `null`\n *\n * @example\n * sanitizeFileMakerJson('{\"field1\": \"valid\", \"field2\": ?, \"field3\": null}')\n * // Returns: '{\"field1\": \"valid\", \"field2\": null, \"field3\": null}'\n */\nexport function sanitizeFileMakerJson(text: string): string {\n // Replace unquoted ? values in objects (after colon)\n // Also handles arrays when the array is a value in an object\n let result = text.replace(/:\\s*\\?(?=\\s*[,}\\]])/g, \": null\");\n\n // Replace unquoted ? values directly in arrays (not after colon)\n // e.g., [1, ?, 3] -> [1, null, 3]\n result = result.replace(/(?<=[\\[,])\\s*\\?(?=\\s*[,\\]])/g, \" null\");\n\n return result;\n}\n\n/**\n * Safely parses a Response body as JSON, handling FileMaker's invalid JSON responses.\n *\n * This function reads the response as text first, sanitizes any invalid `?` values,\n * and then parses the sanitized JSON. This approach handles the case where FileMaker\n * returns a Content-Type of application/json but the body contains invalid JSON.\n *\n * @param response - The fetch Response object\n * @returns Parsed JSON data\n * @throws ResponseParseError if the JSON is still invalid after sanitization (includes sanitized text for debugging)\n */\nexport async function safeJsonParse<T = unknown>(\n response: Response,\n): Promise<T> {\n const text = await response.text();\n const sanitized = sanitizeFileMakerJson(text);\n try {\n return JSON.parse(sanitized) as T;\n } catch (err) {\n throw new ResponseParseError(\n response.url,\n `Failed to parse response as JSON: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n {\n rawText: sanitized,\n cause: err instanceof Error ? err : undefined,\n },\n );\n }\n}\n"],"names":[],"mappings":";AAyBO,SAAS,sBAAsB,MAAsB;AAG1D,MAAI,SAAS,KAAK,QAAQ,wBAAwB,QAAQ;AAIjD,WAAA,OAAO,QAAQ,WAAA,oCAAA,GAAA,GAAgC,OAAO;AAExD,SAAA;AACT;AAaA,eAAsB,cACpB,UACY;AACN,QAAA,OAAO,MAAM,SAAS,KAAK;AAC3B,QAAA,YAAY,sBAAsB,IAAI;AACxC,MAAA;AACK,WAAA,KAAK,MAAM,SAAS;AAAA,WACpB,KAAK;AACZ,UAAM,IAAI;AAAA,MACR,SAAS;AAAA,MACT,qCAAqC,eAAe,QAAQ,IAAI,UAAU,eAAe;AAAA,MACzF;AAAA,QACE,SAAS;AAAA,QACT,OAAO,eAAe,QAAQ,MAAM;AAAA,MAAA;AAAA,IAExC;AAAA,EAAA;AAEJ;"}
@@ -1,23 +1,20 @@
1
1
  import { BaseTable } from './base-table.js';
2
2
  type ExtractSchema<BT> = BT extends BaseTable<infer S, any, any, any> ? S : never;
3
- type ResolveNavigation<T> = {
4
- [K in keyof T]: T[K] extends () => infer R ? R : T[K];
5
- };
6
- export declare class TableOccurrence<BT extends BaseTable<any, any, any, any> = any, Name extends string = string, Nav extends Record<string, TableOccurrence<any, any, any, any> | (() => TableOccurrence<any, any, any, any>)> = {}, DefSelect extends "all" | "schema" | readonly (keyof ExtractSchema<BT>)[] = "schema"> {
3
+ declare const INTERNAL_NAV: unique symbol;
4
+ export declare class TableOccurrence<BT extends BaseTable<any, any, any, any> = any, Name extends string = string, Nav extends Record<string, TableOccurrence<any, any, any, any>> = {}, DefSelect extends "all" | "schema" | readonly (keyof ExtractSchema<BT>)[] = "schema"> {
7
5
  readonly name: Name;
8
6
  readonly baseTable: BT;
9
- protected _navigationConfig: Nav;
10
- readonly navigation: ResolveNavigation<Nav>;
7
+ readonly navigation: Nav;
11
8
  readonly defaultSelect: DefSelect;
12
9
  readonly fmtId?: `FMTID:${string}`;
13
10
  constructor(config: {
14
11
  readonly name: Name;
15
12
  readonly baseTable: BT;
16
- readonly navigation?: Nav;
17
13
  readonly defaultSelect?: DefSelect;
18
14
  readonly fmtId?: `FMTID:${string}`;
15
+ /** @internal Used by buildOccurrences - do not use directly */
16
+ readonly [INTERNAL_NAV]?: Nav;
19
17
  });
20
- addNavigation<NewNav extends Record<string, TableOccurrence<any, any, any, any> | (() => TableOccurrence<any, any, any, any>)>>(nav: NewNav): TableOccurrence<BT, Name, Nav & NewNav, DefSelect>;
21
18
  /**
22
19
  * Returns the FileMaker table occurrence ID (FMTID) if available, or the table name.
23
20
  * @returns The FMTID string or the table name
@@ -32,6 +29,10 @@ export declare class TableOccurrence<BT extends BaseTable<any, any, any, any> =
32
29
  * Returns true if this TableOccurrence is using FileMaker table occurrence IDs.
33
30
  */
34
31
  isUsingTableId(): boolean;
32
+ /**
33
+ * @internal Creates a new TableOccurrence with navigation - used by buildOccurrences
34
+ */
35
+ static _withNavigation<BT extends BaseTable<any, any, any, any>, Name extends string, Nav extends Record<string, TableOccurrence<any, any, any, any>>, DefSelect extends "all" | "schema" | readonly (keyof ExtractSchema<BT>)[] = "schema">(base: TableOccurrence<BT, Name, any, DefSelect>, navigation: Nav): TableOccurrence<BT, Name, Nav, DefSelect>;
35
36
  }
36
37
  export declare function createTableOccurrence<const Name extends string, BT extends BaseTable<any, any, any, any>, DefSelect extends "all" | "schema" | readonly (keyof ExtractSchema<BT>)[] = "schema">(config: {
37
38
  name: Name;
@@ -42,10 +43,10 @@ export declare function createTableOccurrence<const Name extends string, BT exte
42
43
  /**
43
44
  * Creates a TableOccurrence with proper TypeScript type inference.
44
45
  *
45
- * Use this function instead of `new TableOccurrence()` to ensure
46
- * field names are properly typed.
46
+ * Use this function to create TableOccurrence instances with full type safety.
47
+ * For navigation between tables, use `buildOccurrences()` after defining your TOs.
47
48
  *
48
- * @example Without entity IDs
49
+ * @example
49
50
  * ```ts
50
51
  * const users = defineTableOccurrence({
51
52
  * name: "users",
@@ -61,12 +62,25 @@ export declare function createTableOccurrence<const Name extends string, BT exte
61
62
  * fmtId: "FMTID:12345",
62
63
  * });
63
64
  * ```
65
+ *
66
+ * @example With navigation (use buildOccurrences)
67
+ * ```ts
68
+ * const _users = defineTableOccurrence({ name: "users", baseTable: usersBase });
69
+ * const _contacts = defineTableOccurrence({ name: "contacts", baseTable: contactsBase });
70
+ *
71
+ * const [users, contacts] = buildOccurrences({
72
+ * occurrences: [_users, _contacts],
73
+ * navigation: {
74
+ * users: ["contacts"],
75
+ * contacts: ["users"],
76
+ * },
77
+ * });
78
+ * ```
64
79
  */
65
80
  export declare function defineTableOccurrence<const Name extends string, BT extends BaseTable<any, any, any, any>, const DefSelect extends "all" | "schema" | readonly (keyof ExtractSchema<BT>)[] = "schema">(config: {
66
81
  readonly name: Name;
67
82
  readonly baseTable: BT;
68
83
  readonly fmtId?: `FMTID:${string}`;
69
84
  readonly defaultSelect?: DefSelect;
70
- readonly navigation?: Record<string, TableOccurrence<any, any, any, any> | (() => TableOccurrence<any, any, any, any>)>;
71
85
  }): TableOccurrence<BT, Name, {}, DefSelect>;
72
86
  export {};
@@ -1,43 +1,19 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- function createNavigationGetters(navConfig) {
5
- const result = {};
6
- for (const key in navConfig) {
7
- Object.defineProperty(result, key, {
8
- get() {
9
- const navItem = navConfig[key];
10
- return typeof navItem === "function" ? navItem() : navItem;
11
- },
12
- enumerable: true,
13
- configurable: true
14
- });
15
- }
16
- return result;
17
- }
4
+ const INTERNAL_NAV = Symbol("internal-navigation");
18
5
  class TableOccurrence {
19
6
  constructor(config) {
20
7
  __publicField(this, "name");
21
8
  __publicField(this, "baseTable");
22
- __publicField(this, "_navigationConfig");
23
9
  __publicField(this, "navigation");
24
10
  __publicField(this, "defaultSelect");
25
11
  __publicField(this, "fmtId");
26
12
  this.name = config.name;
27
13
  this.baseTable = config.baseTable;
28
- this._navigationConfig = config.navigation ?? {};
14
+ this.navigation = config[INTERNAL_NAV] ?? {};
29
15
  this.defaultSelect = config.defaultSelect ?? "schema";
30
16
  this.fmtId = config.fmtId;
31
- this.navigation = createNavigationGetters(this._navigationConfig);
32
- }
33
- addNavigation(nav) {
34
- return new TableOccurrence({
35
- name: this.name,
36
- baseTable: this.baseTable,
37
- navigation: { ...this._navigationConfig, ...nav },
38
- defaultSelect: this.defaultSelect,
39
- fmtId: this.fmtId
40
- });
41
17
  }
42
18
  /**
43
19
  * Returns the FileMaker table occurrence ID (FMTID) if available, or the table name.
@@ -59,16 +35,24 @@ class TableOccurrence {
59
35
  isUsingTableId() {
60
36
  return this.fmtId !== void 0;
61
37
  }
62
- }
63
- function createTableOccurrence(config) {
64
- return new TableOccurrence(config);
38
+ /**
39
+ * @internal Creates a new TableOccurrence with navigation - used by buildOccurrences
40
+ */
41
+ static _withNavigation(base, navigation) {
42
+ return new TableOccurrence({
43
+ name: base.name,
44
+ baseTable: base.baseTable,
45
+ defaultSelect: base.defaultSelect,
46
+ fmtId: base.fmtId,
47
+ [INTERNAL_NAV]: navigation
48
+ });
49
+ }
65
50
  }
66
51
  function defineTableOccurrence(config) {
67
52
  return new TableOccurrence(config);
68
53
  }
69
54
  export {
70
55
  TableOccurrence,
71
- createTableOccurrence,
72
56
  defineTableOccurrence
73
57
  };
74
58
  //# sourceMappingURL=table-occurrence.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"table-occurrence.js","sources":["../../../src/client/table-occurrence.ts"],"sourcesContent":["import { BaseTable } from \"./base-table\";\n\n// Helper type to extract schema from BaseTable\ntype ExtractSchema<BT> =\n BT extends BaseTable<infer S, any, any, any> ? S : never;\n\n// Helper type to resolve navigation functions to their return types\ntype ResolveNavigation<T> = {\n [K in keyof T]: T[K] extends () => infer R ? R : T[K];\n};\n\n// Helper to create a getter-based navigation object\nfunction createNavigationGetters<\n Nav extends Record<\n string,\n | TableOccurrence<any, any, any, any>\n | (() => TableOccurrence<any, any, any, any>)\n >,\n>(navConfig: Nav): ResolveNavigation<Nav> {\n const result: any = {};\n\n for (const key in navConfig) {\n Object.defineProperty(result, key, {\n get() {\n const navItem = navConfig[key];\n return typeof navItem === \"function\" ? navItem() : navItem;\n },\n enumerable: true,\n configurable: true,\n });\n }\n\n return result as ResolveNavigation<Nav>;\n}\n\nexport class TableOccurrence<\n BT extends BaseTable<any, any, any, any> = any,\n Name extends string = string,\n Nav extends Record<\n string,\n | TableOccurrence<any, any, any, any>\n | (() => TableOccurrence<any, any, any, any>)\n > = {},\n DefSelect extends\n | \"all\"\n | \"schema\"\n | readonly (keyof ExtractSchema<BT>)[] = \"schema\",\n> {\n public readonly name: Name;\n public readonly baseTable: BT;\n protected _navigationConfig: Nav;\n public readonly navigation: ResolveNavigation<Nav>;\n public readonly defaultSelect: DefSelect;\n public readonly fmtId?: `FMTID:${string}`;\n constructor(config: {\n readonly name: Name;\n readonly baseTable: BT;\n readonly navigation?: Nav;\n readonly defaultSelect?: DefSelect;\n readonly fmtId?: `FMTID:${string}`;\n }) {\n this.name = config.name;\n this.baseTable = config.baseTable;\n this._navigationConfig = (config.navigation ?? {}) as Nav;\n this.defaultSelect = (config.defaultSelect ?? \"schema\") as DefSelect;\n this.fmtId = config.fmtId;\n // Create navigation getters that lazily resolve functions\n this.navigation = createNavigationGetters(this._navigationConfig);\n }\n\n addNavigation<\n NewNav extends Record<\n string,\n | TableOccurrence<any, any, any, any>\n | (() => TableOccurrence<any, any, any, any>)\n >,\n >(nav: NewNav): TableOccurrence<BT, Name, Nav & NewNav, DefSelect> {\n return new TableOccurrence({\n name: this.name,\n baseTable: this.baseTable,\n navigation: { ...this._navigationConfig, ...nav } as Nav & NewNav,\n defaultSelect: this.defaultSelect,\n fmtId: this.fmtId,\n });\n }\n\n /**\n * Returns the FileMaker table occurrence ID (FMTID) if available, or the table name.\n * @returns The FMTID string or the table name\n */\n getTableId(): string {\n return this.fmtId ?? this.name;\n }\n\n /**\n * Returns the table occurrence name.\n * @returns The table name\n */\n getTableName(): string {\n return this.name;\n }\n\n /**\n * Returns true if this TableOccurrence is using FileMaker table occurrence IDs.\n */\n isUsingTableId(): boolean {\n return this.fmtId !== undefined;\n }\n}\n\n// Helper function to create TableOccurrence with proper type inference\nexport function createTableOccurrence<\n const Name extends string,\n BT extends BaseTable<any, any, any, any>,\n DefSelect extends\n | \"all\"\n | \"schema\"\n | readonly (keyof ExtractSchema<BT>)[] = \"schema\",\n>(config: {\n name: Name;\n baseTable: BT;\n defaultSelect?: DefSelect;\n fmtId?: `FMTID:${string}`;\n}): TableOccurrence<BT, Name, {}, DefSelect> {\n return new TableOccurrence(config);\n}\n\n/**\n * Creates a TableOccurrence with proper TypeScript type inference.\n *\n * Use this function instead of `new TableOccurrence()` to ensure\n * field names are properly typed.\n *\n * @example Without entity IDs\n * ```ts\n * const users = defineTableOccurrence({\n * name: \"users\",\n * baseTable: usersBase,\n * });\n * ```\n *\n * @example With entity IDs\n * ```ts\n * const products = defineTableOccurrence({\n * name: \"products\",\n * baseTable: productsBase,\n * fmtId: \"FMTID:12345\",\n * });\n * ```\n */\nexport function defineTableOccurrence<\n const Name extends string,\n BT extends BaseTable<any, any, any, any>,\n const DefSelect extends\n | \"all\"\n | \"schema\"\n | readonly (keyof ExtractSchema<BT>)[] = \"schema\",\n>(config: {\n readonly name: Name;\n readonly baseTable: BT;\n readonly fmtId?: `FMTID:${string}`;\n readonly defaultSelect?: DefSelect;\n readonly navigation?: Record<\n string,\n | TableOccurrence<any, any, any, any>\n | (() => TableOccurrence<any, any, any, any>)\n >;\n}): TableOccurrence<BT, Name, {}, DefSelect> {\n return new TableOccurrence(config) as TableOccurrence<\n BT,\n Name,\n {},\n DefSelect\n >;\n}\n"],"names":[],"mappings":";;;AAYA,SAAS,wBAMP,WAAwC;AACxC,QAAM,SAAc,CAAC;AAErB,aAAW,OAAO,WAAW;AACpB,WAAA,eAAe,QAAQ,KAAK;AAAA,MACjC,MAAM;AACE,cAAA,UAAU,UAAU,GAAG;AAC7B,eAAO,OAAO,YAAY,aAAa,QAAY,IAAA;AAAA,MACrD;AAAA,MACA,YAAY;AAAA,MACZ,cAAc;AAAA,IAAA,CACf;AAAA,EAAA;AAGI,SAAA;AACT;AAEO,MAAM,gBAYX;AAAA,EAOA,YAAY,QAMT;AAZa;AACA;AACN;AACM;AACA;AACA;AAQd,SAAK,OAAO,OAAO;AACnB,SAAK,YAAY,OAAO;AACnB,SAAA,oBAAqB,OAAO,cAAc,CAAC;AAC3C,SAAA,gBAAiB,OAAO,iBAAiB;AAC9C,SAAK,QAAQ,OAAO;AAEf,SAAA,aAAa,wBAAwB,KAAK,iBAAiB;AAAA,EAAA;AAAA,EAGlE,cAME,KAAiE;AACjE,WAAO,IAAI,gBAAgB;AAAA,MACzB,MAAM,KAAK;AAAA,MACX,WAAW,KAAK;AAAA,MAChB,YAAY,EAAE,GAAG,KAAK,mBAAmB,GAAG,IAAI;AAAA,MAChD,eAAe,KAAK;AAAA,MACpB,OAAO,KAAK;AAAA,IAAA,CACb;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOH,aAAqB;AACZ,WAAA,KAAK,SAAS,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO5B,eAAuB;AACrB,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMd,iBAA0B;AACxB,WAAO,KAAK,UAAU;AAAA,EAAA;AAE1B;AAGO,SAAS,sBAOd,QAK2C;AACpC,SAAA,IAAI,gBAAgB,MAAM;AACnC;AAyBO,SAAS,sBAOd,QAU2C;AACpC,SAAA,IAAI,gBAAgB,MAAM;AAMnC;"}
1
+ {"version":3,"file":"table-occurrence.js","sources":["../../../src/client/table-occurrence.ts"],"sourcesContent":["import { BaseTable } from \"./base-table\";\n\n// Helper type to extract schema from BaseTable\ntype ExtractSchema<BT> =\n BT extends BaseTable<infer S, any, any, any> ? S : never;\n\n// Symbol for internal navigation setting (used by buildOccurrences)\nconst INTERNAL_NAV = Symbol(\"internal-navigation\");\n\nexport class TableOccurrence<\n BT extends BaseTable<any, any, any, any> = any,\n Name extends string = string,\n Nav extends Record<string, TableOccurrence<any, any, any, any>> = {},\n DefSelect extends\n | \"all\"\n | \"schema\"\n | readonly (keyof ExtractSchema<BT>)[] = \"schema\",\n> {\n public readonly name: Name;\n public readonly baseTable: BT;\n public readonly navigation: Nav;\n public readonly defaultSelect: DefSelect;\n public readonly fmtId?: `FMTID:${string}`;\n\n constructor(config: {\n readonly name: Name;\n readonly baseTable: BT;\n readonly defaultSelect?: DefSelect;\n readonly fmtId?: `FMTID:${string}`;\n /** @internal Used by buildOccurrences - do not use directly */\n readonly [INTERNAL_NAV]?: Nav;\n }) {\n this.name = config.name;\n this.baseTable = config.baseTable;\n this.navigation = (config[INTERNAL_NAV] ?? {}) as Nav;\n this.defaultSelect = (config.defaultSelect ?? \"schema\") as DefSelect;\n this.fmtId = config.fmtId;\n }\n\n /**\n * Returns the FileMaker table occurrence ID (FMTID) if available, or the table name.\n * @returns The FMTID string or the table name\n */\n getTableId(): string {\n return this.fmtId ?? this.name;\n }\n\n /**\n * Returns the table occurrence name.\n * @returns The table name\n */\n getTableName(): string {\n return this.name;\n }\n\n /**\n * Returns true if this TableOccurrence is using FileMaker table occurrence IDs.\n */\n isUsingTableId(): boolean {\n return this.fmtId !== undefined;\n }\n\n /**\n * @internal Creates a new TableOccurrence with navigation - used by buildOccurrences\n */\n static _withNavigation<\n BT extends BaseTable<any, any, any, any>,\n Name extends string,\n Nav extends Record<string, TableOccurrence<any, any, any, any>>,\n DefSelect extends\n | \"all\"\n | \"schema\"\n | readonly (keyof ExtractSchema<BT>)[] = \"schema\",\n >(\n base: TableOccurrence<BT, Name, any, DefSelect>,\n navigation: Nav,\n ): TableOccurrence<BT, Name, Nav, DefSelect> {\n return new TableOccurrence({\n name: base.name,\n baseTable: base.baseTable,\n defaultSelect: base.defaultSelect,\n fmtId: base.fmtId,\n [INTERNAL_NAV]: navigation,\n }) as TableOccurrence<BT, Name, Nav, DefSelect>;\n }\n}\n\n// Helper function to create TableOccurrence with proper type inference\nexport function createTableOccurrence<\n const Name extends string,\n BT extends BaseTable<any, any, any, any>,\n DefSelect extends\n | \"all\"\n | \"schema\"\n | readonly (keyof ExtractSchema<BT>)[] = \"schema\",\n>(config: {\n name: Name;\n baseTable: BT;\n defaultSelect?: DefSelect;\n fmtId?: `FMTID:${string}`;\n}): TableOccurrence<BT, Name, {}, DefSelect> {\n return new TableOccurrence(config);\n}\n\n/**\n * Creates a TableOccurrence with proper TypeScript type inference.\n *\n * Use this function to create TableOccurrence instances with full type safety.\n * For navigation between tables, use `buildOccurrences()` after defining your TOs.\n *\n * @example\n * ```ts\n * const users = defineTableOccurrence({\n * name: \"users\",\n * baseTable: usersBase,\n * });\n * ```\n *\n * @example With entity IDs\n * ```ts\n * const products = defineTableOccurrence({\n * name: \"products\",\n * baseTable: productsBase,\n * fmtId: \"FMTID:12345\",\n * });\n * ```\n *\n * @example With navigation (use buildOccurrences)\n * ```ts\n * const _users = defineTableOccurrence({ name: \"users\", baseTable: usersBase });\n * const _contacts = defineTableOccurrence({ name: \"contacts\", baseTable: contactsBase });\n *\n * const [users, contacts] = buildOccurrences({\n * occurrences: [_users, _contacts],\n * navigation: {\n * users: [\"contacts\"],\n * contacts: [\"users\"],\n * },\n * });\n * ```\n */\nexport function defineTableOccurrence<\n const Name extends string,\n BT extends BaseTable<any, any, any, any>,\n const DefSelect extends\n | \"all\"\n | \"schema\"\n | readonly (keyof ExtractSchema<BT>)[] = \"schema\",\n>(config: {\n readonly name: Name;\n readonly baseTable: BT;\n readonly fmtId?: `FMTID:${string}`;\n readonly defaultSelect?: DefSelect;\n}): TableOccurrence<BT, Name, {}, DefSelect> {\n return new TableOccurrence(config);\n}\n"],"names":[],"mappings":";;;AAOA,MAAM,eAAe,OAAO,qBAAqB;AAE1C,MAAM,gBAQX;AAAA,EAOA,YAAY,QAOT;AAba;AACA;AACA;AACA;AACA;AAUd,SAAK,OAAO,OAAO;AACnB,SAAK,YAAY,OAAO;AACxB,SAAK,aAAc,OAAO,YAAY,KAAK,CAAC;AACvC,SAAA,gBAAiB,OAAO,iBAAiB;AAC9C,SAAK,QAAQ,OAAO;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOtB,aAAqB;AACZ,WAAA,KAAK,SAAS,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO5B,eAAuB;AACrB,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMd,iBAA0B;AACxB,WAAO,KAAK,UAAU;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMxB,OAAO,gBASL,MACA,YAC2C;AAC3C,WAAO,IAAI,gBAAgB;AAAA,MACzB,MAAM,KAAK;AAAA,MACX,WAAW,KAAK;AAAA,MAChB,eAAe,KAAK;AAAA,MACpB,OAAO,KAAK;AAAA,MACZ,CAAC,YAAY,GAAG;AAAA,IAAA,CACjB;AAAA,EAAA;AAEL;AAwDO,SAAS,sBAOd,QAK2C;AACpC,SAAA,IAAI,gBAAgB,MAAM;AACnC;"}
@@ -85,7 +85,7 @@ export declare class ExecutableUpdateBuilder<T extends Record<string, any>, IsBy
85
85
  url: string;
86
86
  body?: any;
87
87
  };
88
- toRequest(baseUrl: string): Request;
88
+ toRequest(baseUrl: string, options?: ExecuteOptions): Request;
89
89
  processResponse(response: Response, options?: ExecuteOptions): Promise<Result<ReturnPreference extends "minimal" ? {
90
90
  updatedCount: number;
91
91
  } : T>>;
@@ -1,6 +1,7 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ import { getAcceptHeader } from "../types.js";
4
5
  import { QueryBuilder } from "./query-builder.js";
5
6
  import { getTableIdentifiers, transformFieldNamesToIds } from "../transform.js";
6
7
  class UpdateBuilder {
@@ -189,14 +190,14 @@ class ExecutableUpdateBuilder {
189
190
  body: JSON.stringify(transformedData)
190
191
  };
191
192
  }
192
- toRequest(baseUrl) {
193
+ toRequest(baseUrl, options) {
193
194
  const config = this.getRequestConfig();
194
195
  const fullUrl = `${baseUrl}${config.url}`;
195
196
  return new Request(fullUrl, {
196
197
  method: config.method,
197
198
  headers: {
198
199
  "Content-Type": "application/json",
199
- Accept: "application/json"
200
+ Accept: getAcceptHeader(options == null ? void 0 : options.includeODataAnnotations)
200
201
  },
201
202
  body: config.body
202
203
  });
@@ -1 +1 @@
1
- {"version":3,"file":"update-builder.js","sources":["../../../src/client/update-builder.ts"],"sourcesContent":["import type {\n ExecutionContext,\n ExecutableBuilder,\n Result,\n WithSystemFields,\n ExecuteOptions,\n} from \"../types\";\nimport type { TableOccurrence } from \"./table-occurrence\";\nimport type { BaseTable } from \"./base-table\";\nimport { QueryBuilder } from \"./query-builder\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport {\n transformFieldNamesToIds,\n transformTableName,\n getTableIdentifiers,\n} from \"../transform\";\n\n/**\n * Initial update builder returned from EntitySet.update(data)\n * Requires calling .byId() or .where() before .execute() is available\n */\nexport class UpdateBuilder<\n T extends Record<string, any>,\n BT extends BaseTable<any, any, any, any>,\n ReturnPreference extends \"minimal\" | \"representation\" = \"minimal\",\n> {\n private tableName: string;\n private databaseName: string;\n private context: ExecutionContext;\n private occurrence?: TableOccurrence<any, any, any, any>;\n private data: Partial<T>;\n private returnPreference: ReturnPreference;\n\n private databaseUseEntityIds: boolean;\n\n constructor(config: {\n occurrence?: TableOccurrence<any, any, any, any>;\n tableName: string;\n databaseName: string;\n context: ExecutionContext;\n data: Partial<T>;\n returnPreference: ReturnPreference;\n databaseUseEntityIds?: boolean;\n }) {\n this.occurrence = config.occurrence;\n this.tableName = config.tableName;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.data = config.data;\n this.returnPreference = config.returnPreference;\n this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;\n }\n\n /**\n * Update a single record by ID\n * Returns updated count by default, or full record if returnFullRecord was set to true\n */\n byId(\n id: string | number,\n ): ExecutableUpdateBuilder<T, true, ReturnPreference> {\n return new ExecutableUpdateBuilder<T, true, ReturnPreference>({\n occurrence: this.occurrence,\n tableName: this.tableName,\n databaseName: this.databaseName,\n context: this.context,\n data: this.data,\n mode: \"byId\",\n recordId: id,\n returnPreference: this.returnPreference,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n\n /**\n * Update records matching a filter query\n * Returns updated count by default, or full record if returnFullRecord was set to true\n * @param fn Callback that receives a QueryBuilder for building the filter\n */\n where(\n fn: (\n q: QueryBuilder<WithSystemFields<T>>,\n ) => QueryBuilder<WithSystemFields<T>>,\n ): ExecutableUpdateBuilder<T, true, ReturnPreference> {\n // Create a QueryBuilder for the user to configure\n const queryBuilder = new QueryBuilder<\n WithSystemFields<T>,\n keyof WithSystemFields<T>,\n false,\n false,\n undefined\n >({\n occurrence: undefined,\n tableName: this.tableName,\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 ExecutableUpdateBuilder<T, true, ReturnPreference>({\n occurrence: this.occurrence,\n tableName: this.tableName,\n databaseName: this.databaseName,\n context: this.context,\n data: this.data,\n mode: \"byFilter\",\n queryBuilder: configuredBuilder,\n returnPreference: this.returnPreference,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n}\n\n/**\n * Executable update builder - has execute() method\n * Returned after calling .byId() or .where()\n * Can return either updated count or full record based on returnFullRecord option\n */\nexport class ExecutableUpdateBuilder<\n T extends Record<string, any>,\n IsByFilter extends boolean,\n ReturnPreference extends \"minimal\" | \"representation\" = \"minimal\",\n> implements\n ExecutableBuilder<\n ReturnPreference extends \"minimal\" ? { updatedCount: number } : T\n >\n{\n private tableName: string;\n private databaseName: string;\n private context: ExecutionContext;\n private occurrence?: TableOccurrence<any, any, any, any>;\n private data: Partial<T>;\n private mode: \"byId\" | \"byFilter\";\n private recordId?: string | number;\n private queryBuilder?: QueryBuilder<any>;\n private returnPreference: ReturnPreference;\n private databaseUseEntityIds: boolean;\n\n constructor(config: {\n occurrence?: TableOccurrence<any, any, any, any>;\n tableName: string;\n databaseName: string;\n context: ExecutionContext;\n data: Partial<T>;\n mode: \"byId\" | \"byFilter\";\n recordId?: string | number;\n queryBuilder?: QueryBuilder<any>;\n returnPreference: ReturnPreference;\n databaseUseEntityIds?: boolean;\n }) {\n this.occurrence = config.occurrence;\n this.tableName = config.tableName;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.data = config.data;\n this.mode = config.mode;\n this.recordId = config.recordId;\n this.queryBuilder = config.queryBuilder;\n this.returnPreference = config.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 * 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.occurrence) {\n return this.tableName;\n }\n\n const contextDefault = this.context._getUseEntityIds?.() ?? false;\n const shouldUseIds = useEntityIds ?? contextDefault;\n\n if (shouldUseIds) {\n const identifiers = getTableIdentifiers(this.occurrence);\n if (!identifiers.id) {\n throw new Error(\n `useEntityIds is true but TableOccurrence \"${identifiers.name}\" does not have an fmtId defined`,\n );\n }\n return identifiers.id;\n }\n\n return this.occurrence.getTableName();\n }\n\n async execute(\n options?: RequestInit & FFetchOptions & { useEntityIds?: boolean },\n ): Promise<\n Result<ReturnPreference extends \"minimal\" ? { updatedCount: number } : T>\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\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.occurrence?.baseTable && shouldUseIds\n ? transformFieldNamesToIds(this.data, this.occurrence.baseTable)\n : this.data;\n\n let url: string;\n\n if (this.mode === \"byId\") {\n // Update single record by ID: PATCH /{database}/{table}('id')\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n } else {\n // Update by filter: PATCH /{database}/{table}?$filter=...\n if (!this.queryBuilder) {\n throw new Error(\"Query builder is required for filter-based update\");\n }\n\n // Get the query string from the configured QueryBuilder\n const queryString = this.queryBuilder.getQueryString();\n // The query string will have the tableId already transformed by QueryBuilder\n // Remove the leading \"/\" and table name from the query string as we'll build our own URL\n const queryParams = queryString.startsWith(`/${tableId}`)\n ? queryString.slice(`/${tableId}`.length)\n : queryString.startsWith(`/${this.tableName}`)\n ? queryString.slice(`/${this.tableName}`.length)\n : queryString;\n\n url = `/${this.databaseName}/${tableId}${queryParams}`;\n }\n\n // Set Prefer header based on returnPreference\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n\n if (this.returnPreference === \"representation\") {\n headers[\"Prefer\"] = \"return=representation\";\n }\n\n // Make PATCH request with JSON body\n const result = await this.context._makeRequest(url, {\n method: \"PATCH\",\n headers,\n body: JSON.stringify(transformedData),\n ...mergedOptions,\n });\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n const response = result.data;\n\n // Handle based on return preference\n if (this.returnPreference === \"representation\") {\n // Return the full updated record\n return {\n data: response as ReturnPreference extends \"minimal\"\n ? { updatedCount: number }\n : T,\n error: undefined,\n };\n } else {\n // Return updated count (minimal)\n let updatedCount = 0;\n\n if (typeof response === \"number\") {\n updatedCount = response;\n } else if (response && typeof response === \"object\") {\n // Check if the response has a count property (fallback)\n updatedCount = (response as any).updatedCount || 0;\n }\n\n return {\n data: { updatedCount } as ReturnPreference extends \"minimal\"\n ? { updatedCount: number }\n : T,\n error: undefined,\n };\n }\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 // Transform field names to FMFIDs if using entity IDs\n const transformedData =\n this.occurrence?.baseTable && this.databaseUseEntityIds\n ? transformFieldNamesToIds(this.data, this.occurrence.baseTable)\n : this.data;\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 update\");\n }\n\n const queryString = this.queryBuilder.getQueryString();\n const queryParams = queryString.startsWith(`/${tableId}`)\n ? queryString.slice(`/${tableId}`.length)\n : queryString.startsWith(`/${this.tableName}`)\n ? queryString.slice(`/${this.tableName}`.length)\n : queryString;\n\n url = `/${this.databaseName}/${tableId}${queryParams}`;\n }\n\n return {\n method: \"PATCH\",\n url,\n body: JSON.stringify(transformedData),\n };\n }\n\n toRequest(baseUrl: string): 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 \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: config.body,\n });\n }\n\n async processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<\n Result<ReturnPreference extends \"minimal\" ? { updatedCount: number } : T>\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 updatedCount = affectedRows ? parseInt(affectedRows, 10) : 1;\n return {\n data: { updatedCount } as ReturnPreference extends \"minimal\"\n ? { updatedCount: number }\n : T,\n error: undefined,\n };\n }\n\n const rawResponse = JSON.parse(text);\n\n // Handle based on return preference\n if (this.returnPreference === \"representation\") {\n // Return the full updated record\n return {\n data: rawResponse as ReturnPreference extends \"minimal\"\n ? { updatedCount: number }\n : T,\n error: undefined,\n };\n } else {\n // Return updated count (minimal)\n let updatedCount = 0;\n\n if (typeof rawResponse === \"number\") {\n updatedCount = rawResponse;\n } else if (rawResponse && typeof rawResponse === \"object\") {\n // Check if the response has a count property (fallback)\n updatedCount = (rawResponse as any).updatedCount || 0;\n }\n\n return {\n data: { updatedCount } as ReturnPreference extends \"minimal\"\n ? { updatedCount: number }\n : T,\n error: undefined,\n };\n }\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"update-builder.js","sources":["../../../src/client/update-builder.ts"],"sourcesContent":["import type {\n ExecutionContext,\n ExecutableBuilder,\n Result,\n WithSystemFields,\n ExecuteOptions,\n} from \"../types\";\nimport { getAcceptHeader } from \"../types\";\nimport type { TableOccurrence } from \"./table-occurrence\";\nimport type { BaseTable } from \"./base-table\";\nimport { QueryBuilder } from \"./query-builder\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport {\n transformFieldNamesToIds,\n transformTableName,\n getTableIdentifiers,\n} from \"../transform\";\n\n/**\n * Initial update builder returned from EntitySet.update(data)\n * Requires calling .byId() or .where() before .execute() is available\n */\nexport class UpdateBuilder<\n T extends Record<string, any>,\n BT extends BaseTable<any, any, any, any>,\n ReturnPreference extends \"minimal\" | \"representation\" = \"minimal\",\n> {\n private tableName: string;\n private databaseName: string;\n private context: ExecutionContext;\n private occurrence?: TableOccurrence<any, any, any, any>;\n private data: Partial<T>;\n private returnPreference: ReturnPreference;\n\n private databaseUseEntityIds: boolean;\n\n constructor(config: {\n occurrence?: TableOccurrence<any, any, any, any>;\n tableName: string;\n databaseName: string;\n context: ExecutionContext;\n data: Partial<T>;\n returnPreference: ReturnPreference;\n databaseUseEntityIds?: boolean;\n }) {\n this.occurrence = config.occurrence;\n this.tableName = config.tableName;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.data = config.data;\n this.returnPreference = config.returnPreference;\n this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;\n }\n\n /**\n * Update a single record by ID\n * Returns updated count by default, or full record if returnFullRecord was set to true\n */\n byId(\n id: string | number,\n ): ExecutableUpdateBuilder<T, true, ReturnPreference> {\n return new ExecutableUpdateBuilder<T, true, ReturnPreference>({\n occurrence: this.occurrence,\n tableName: this.tableName,\n databaseName: this.databaseName,\n context: this.context,\n data: this.data,\n mode: \"byId\",\n recordId: id,\n returnPreference: this.returnPreference,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n\n /**\n * Update records matching a filter query\n * Returns updated count by default, or full record if returnFullRecord was set to true\n * @param fn Callback that receives a QueryBuilder for building the filter\n */\n where(\n fn: (\n q: QueryBuilder<WithSystemFields<T>>,\n ) => QueryBuilder<WithSystemFields<T>>,\n ): ExecutableUpdateBuilder<T, true, ReturnPreference> {\n // Create a QueryBuilder for the user to configure\n const queryBuilder = new QueryBuilder<\n WithSystemFields<T>,\n keyof WithSystemFields<T>,\n false,\n false,\n undefined\n >({\n occurrence: undefined,\n tableName: this.tableName,\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 ExecutableUpdateBuilder<T, true, ReturnPreference>({\n occurrence: this.occurrence,\n tableName: this.tableName,\n databaseName: this.databaseName,\n context: this.context,\n data: this.data,\n mode: \"byFilter\",\n queryBuilder: configuredBuilder,\n returnPreference: this.returnPreference,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n}\n\n/**\n * Executable update builder - has execute() method\n * Returned after calling .byId() or .where()\n * Can return either updated count or full record based on returnFullRecord option\n */\nexport class ExecutableUpdateBuilder<\n T extends Record<string, any>,\n IsByFilter extends boolean,\n ReturnPreference extends \"minimal\" | \"representation\" = \"minimal\",\n> implements\n ExecutableBuilder<\n ReturnPreference extends \"minimal\" ? { updatedCount: number } : T\n >\n{\n private tableName: string;\n private databaseName: string;\n private context: ExecutionContext;\n private occurrence?: TableOccurrence<any, any, any, any>;\n private data: Partial<T>;\n private mode: \"byId\" | \"byFilter\";\n private recordId?: string | number;\n private queryBuilder?: QueryBuilder<any>;\n private returnPreference: ReturnPreference;\n private databaseUseEntityIds: boolean;\n\n constructor(config: {\n occurrence?: TableOccurrence<any, any, any, any>;\n tableName: string;\n databaseName: string;\n context: ExecutionContext;\n data: Partial<T>;\n mode: \"byId\" | \"byFilter\";\n recordId?: string | number;\n queryBuilder?: QueryBuilder<any>;\n returnPreference: ReturnPreference;\n databaseUseEntityIds?: boolean;\n }) {\n this.occurrence = config.occurrence;\n this.tableName = config.tableName;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.data = config.data;\n this.mode = config.mode;\n this.recordId = config.recordId;\n this.queryBuilder = config.queryBuilder;\n this.returnPreference = config.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 * 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.occurrence) {\n return this.tableName;\n }\n\n const contextDefault = this.context._getUseEntityIds?.() ?? false;\n const shouldUseIds = useEntityIds ?? contextDefault;\n\n if (shouldUseIds) {\n const identifiers = getTableIdentifiers(this.occurrence);\n if (!identifiers.id) {\n throw new Error(\n `useEntityIds is true but TableOccurrence \"${identifiers.name}\" does not have an fmtId defined`,\n );\n }\n return identifiers.id;\n }\n\n return this.occurrence.getTableName();\n }\n\n async execute(\n options?: RequestInit & FFetchOptions & { useEntityIds?: boolean },\n ): Promise<\n Result<ReturnPreference extends \"minimal\" ? { updatedCount: number } : T>\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\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.occurrence?.baseTable && shouldUseIds\n ? transformFieldNamesToIds(this.data, this.occurrence.baseTable)\n : this.data;\n\n let url: string;\n\n if (this.mode === \"byId\") {\n // Update single record by ID: PATCH /{database}/{table}('id')\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n } else {\n // Update by filter: PATCH /{database}/{table}?$filter=...\n if (!this.queryBuilder) {\n throw new Error(\"Query builder is required for filter-based update\");\n }\n\n // Get the query string from the configured QueryBuilder\n const queryString = this.queryBuilder.getQueryString();\n // The query string will have the tableId already transformed by QueryBuilder\n // Remove the leading \"/\" and table name from the query string as we'll build our own URL\n const queryParams = queryString.startsWith(`/${tableId}`)\n ? queryString.slice(`/${tableId}`.length)\n : queryString.startsWith(`/${this.tableName}`)\n ? queryString.slice(`/${this.tableName}`.length)\n : queryString;\n\n url = `/${this.databaseName}/${tableId}${queryParams}`;\n }\n\n // Set Prefer header based on returnPreference\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n\n if (this.returnPreference === \"representation\") {\n headers[\"Prefer\"] = \"return=representation\";\n }\n\n // Make PATCH request with JSON body\n const result = await this.context._makeRequest(url, {\n method: \"PATCH\",\n headers,\n body: JSON.stringify(transformedData),\n ...mergedOptions,\n });\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n const response = result.data;\n\n // Handle based on return preference\n if (this.returnPreference === \"representation\") {\n // Return the full updated record\n return {\n data: response as ReturnPreference extends \"minimal\"\n ? { updatedCount: number }\n : T,\n error: undefined,\n };\n } else {\n // Return updated count (minimal)\n let updatedCount = 0;\n\n if (typeof response === \"number\") {\n updatedCount = response;\n } else if (response && typeof response === \"object\") {\n // Check if the response has a count property (fallback)\n updatedCount = (response as any).updatedCount || 0;\n }\n\n return {\n data: { updatedCount } as ReturnPreference extends \"minimal\"\n ? { updatedCount: number }\n : T,\n error: undefined,\n };\n }\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 // Transform field names to FMFIDs if using entity IDs\n const transformedData =\n this.occurrence?.baseTable && this.databaseUseEntityIds\n ? transformFieldNamesToIds(this.data, this.occurrence.baseTable)\n : this.data;\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 update\");\n }\n\n const queryString = this.queryBuilder.getQueryString();\n const queryParams = queryString.startsWith(`/${tableId}`)\n ? queryString.slice(`/${tableId}`.length)\n : queryString.startsWith(`/${this.tableName}`)\n ? queryString.slice(`/${this.tableName}`.length)\n : queryString;\n\n url = `/${this.databaseName}/${tableId}${queryParams}`;\n }\n\n return {\n method: \"PATCH\",\n url,\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 return new Request(fullUrl, {\n method: config.method,\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: getAcceptHeader(options?.includeODataAnnotations),\n },\n body: config.body,\n });\n }\n\n async processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<\n Result<ReturnPreference extends \"minimal\" ? { updatedCount: number } : T>\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 updatedCount = affectedRows ? parseInt(affectedRows, 10) : 1;\n return {\n data: { updatedCount } as ReturnPreference extends \"minimal\"\n ? { updatedCount: number }\n : T,\n error: undefined,\n };\n }\n\n const rawResponse = JSON.parse(text);\n\n // Handle based on return preference\n if (this.returnPreference === \"representation\") {\n // Return the full updated record\n return {\n data: rawResponse as ReturnPreference extends \"minimal\"\n ? { updatedCount: number }\n : T,\n error: undefined,\n };\n } else {\n // Return updated count (minimal)\n let updatedCount = 0;\n\n if (typeof rawResponse === \"number\") {\n updatedCount = rawResponse;\n } else if (rawResponse && typeof rawResponse === \"object\") {\n // Check if the response has a count property (fallback)\n updatedCount = (rawResponse as any).updatedCount || 0;\n }\n\n return {\n data: { updatedCount } as ReturnPreference extends \"minimal\"\n ? { updatedCount: number }\n : T,\n error: undefined,\n };\n }\n }\n}\n"],"names":[],"mappings":"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"}