@proofkit/fmodata
Version:
FileMaker OData API client
267 lines (266 loc) • 9.33 kB
JavaScript
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