UNPKG

@proofkit/fmodata

Version:
267 lines (266 loc) 9.33 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); import { validateSingleResponse } from "../validation.js"; import { getTableIdentifiers, transformFieldNamesToIds, transformResponseFields } from "../transform.js"; import { InvalidLocationHeaderError } from "../errors.js"; class InsertBuilder { constructor(config) { __publicField(this, "occurrence"); __publicField(this, "tableName"); __publicField(this, "databaseName"); __publicField(this, "context"); __publicField(this, "data"); __publicField(this, "returnPreference"); __publicField(this, "databaseUseEntityIds"); this.occurrence = config.occurrence; this.tableName = config.tableName; this.databaseName = config.databaseName; this.context = config.context; this.data = config.data; this.returnPreference = config.returnPreference || "representation"; this.databaseUseEntityIds = config.databaseUseEntityIds ?? false; } /** * Helper to merge database-level useEntityIds with per-request options */ mergeExecuteOptions(options) { return { ...options, useEntityIds: (options == null ? void 0 : options.useEntityIds) ?? this.databaseUseEntityIds }; } /** * Helper to conditionally strip OData annotations based on options */ stripODataAnnotationsIfNeeded(data, options) { if ((options == null ? void 0 : options.includeODataAnnotations) === true) { return data; } const { "@id": _id, "@editLink": _editLink, ...rest } = data; return rest; } /** * Parse ROWID from Location header * Expected formats: * - contacts(ROWID=4583) * - contacts('some-uuid') */ parseLocationHeader(locationHeader) { if (!locationHeader) { throw new InvalidLocationHeaderError( "Location header is required but was not provided" ); } const rowidMatch = locationHeader.match(/ROWID=(\d+)/); if (rowidMatch && rowidMatch[1]) { return parseInt(rowidMatch[1], 10); } const parenMatch = locationHeader.match(/\(['"]?([^'"]+)['"]?\)/); if (parenMatch && parenMatch[1]) { const value = parenMatch[1]; const numValue = parseInt(value, 10); if (!isNaN(numValue)) { return numValue; } } throw new InvalidLocationHeaderError( `Could not extract ROWID from Location header: ${locationHeader}`, locationHeader ); } /** * Gets the table ID (FMTID) if using entity IDs, otherwise returns the table name * @param useEntityIds - Optional override for entity ID usage */ getTableId(useEntityIds) { var _a, _b; if (!this.occurrence) { return this.tableName; } const contextDefault = ((_b = (_a = this.context)._getUseEntityIds) == null ? void 0 : _b.call(_a)) ?? false; const shouldUseIds = useEntityIds ?? contextDefault; if (shouldUseIds) { const identifiers = getTableIdentifiers(this.occurrence); if (!identifiers.id) { throw new Error( `useEntityIds is true but TableOccurrence "${identifiers.name}" does not have an fmtId defined` ); } return identifiers.id; } return this.occurrence.getTableName(); } async execute(options) { var _a, _b, _c, _d; const mergedOptions = this.mergeExecuteOptions(options); const tableId = this.getTableId(mergedOptions.useEntityIds); const url = `/${this.databaseName}/${tableId}`; const shouldUseIds = mergedOptions.useEntityIds ?? false; const transformedData = ((_a = this.occurrence) == null ? void 0 : _a.baseTable) && shouldUseIds ? transformFieldNamesToIds(this.data, this.occurrence.baseTable) : this.data; const preferHeader = this.returnPreference === "minimal" ? "return=minimal" : "return=representation"; const result = await this.context._makeRequest(url, { method: "POST", headers: { "Content-Type": "application/json", Prefer: preferHeader, ...(mergedOptions == null ? void 0 : mergedOptions.headers) || {} }, body: JSON.stringify(transformedData), ...mergedOptions }); if (result.error) { return { data: void 0, error: result.error }; } if (this.returnPreference === "minimal") { const responseData = result.data; if (!responseData || !responseData._location) { throw new InvalidLocationHeaderError( "Location header is required when using return=minimal but was not found in response" ); } const rowid = this.parseLocationHeader(responseData._location); return { data: { ROWID: rowid }, error: void 0 }; } let response = result.data; if (((_b = this.occurrence) == null ? void 0 : _b.baseTable) && shouldUseIds) { response = transformResponseFields( response, this.occurrence.baseTable, void 0 // No expand configs for insert ); } const schema = (_d = (_c = this.occurrence) == null ? void 0 : _c.baseTable) == null ? void 0 : _d.schema; const validation = await validateSingleResponse( response, schema, void 0, // No selected fields for insert void 0, // No expand configs "exact" // Expect exactly one record ); if (!validation.valid) { return { data: void 0, error: validation.error }; } if (validation.data === null) { return { data: void 0, error: new Error("Insert operation returned null response") }; } const finalData = this.stripODataAnnotationsIfNeeded( validation.data, options ); return { data: finalData, error: void 0 }; } getRequestConfig() { var _a; const tableId = this.getTableId(this.databaseUseEntityIds); const transformedData = ((_a = this.occurrence) == null ? void 0 : _a.baseTable) && this.databaseUseEntityIds ? transformFieldNamesToIds(this.data, this.occurrence.baseTable) : this.data; return { method: "POST", url: `/${this.databaseName}/${tableId}`, body: JSON.stringify(transformedData) }; } toRequest(baseUrl) { const config = this.getRequestConfig(); const fullUrl = `${baseUrl}${config.url}`; const preferHeader = this.returnPreference === "minimal" ? "return=minimal" : "return=representation"; return new Request(fullUrl, { method: config.method, headers: { "Content-Type": "application/json", Accept: "application/json", Prefer: preferHeader }, body: config.body }); } async processResponse(response, options) { var _a, _b, _c; if (response.status === 204) { if (this.returnPreference === "minimal") { const locationHeader = response.headers.get("Location") || response.headers.get("location"); if (locationHeader) { const rowid = this.parseLocationHeader(locationHeader); return { data: { ROWID: rowid }, error: void 0 }; } throw new InvalidLocationHeaderError( "Location header is required when using return=minimal but was not found in response" ); } return { data: {}, error: void 0 }; } if (this.returnPreference === "minimal") { throw new InvalidLocationHeaderError( "Expected 204 No Content for return=minimal, but received response with body" ); } let rawResponse; try { rawResponse = await response.json(); } catch (err) { if (response.status === 204) { return { data: {}, error: void 0 }; } return { data: void 0, error: { name: "ResponseParseError", message: `Failed to parse response JSON: ${err instanceof Error ? err.message : "Unknown error"}`, timestamp: /* @__PURE__ */ new Date() } }; } const shouldUseIds = (options == null ? void 0 : options.useEntityIds) ?? this.databaseUseEntityIds; let transformedResponse = rawResponse; if (((_a = this.occurrence) == null ? void 0 : _a.baseTable) && shouldUseIds) { transformedResponse = transformResponseFields( rawResponse, this.occurrence.baseTable, void 0 // No expand configs for insert ); } const schema = (_c = (_b = this.occurrence) == null ? void 0 : _b.baseTable) == null ? void 0 : _c.schema; const validation = await validateSingleResponse( transformedResponse, schema, void 0, // No selected fields for insert void 0, // No expand configs "exact" // Expect exactly one record ); if (!validation.valid) { return { data: void 0, error: validation.error }; } if (validation.data === null) { return { data: void 0, error: new Error("Insert operation returned null response") }; } const finalData = this.stripODataAnnotationsIfNeeded( validation.data, options ); return { data: finalData, error: void 0 }; } } export { InsertBuilder }; //# sourceMappingURL=insert-builder.js.map