@proofkit/fmodata
Version:
FileMaker OData API client
270 lines (269 loc) • 10.6 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 { Effect } from "effect";
import { tryEffect, requestFromService, runLayerResult } from "../effect.js";
import { BuilderInvariantError } from "../errors.js";
import { getTableName, getBaseTableConfig } from "../orm/table.js";
import { transformFieldNamesToIds, transformResponseFields } from "../transform.js";
import { getAcceptHeader } from "../types.js";
import { validateAndTransformInput, validateSingleResponse } from "../validation.js";
import { mergeMutationExecuteOptions, resolveMutationTableId, buildMutationUrl, mergePreferHeaderValues, extractAffectedRows } from "./builders/mutation-helpers.js";
import { normalizeDatabasePath } from "./database-name.js";
import { parseErrorResponse } from "./error-parser.js";
import { QueryBuilder } from "./query/query-builder.js";
import { createClientRuntime } from "./runtime.js";
class UpdateBuilder {
constructor(config) {
__publicField(this, "table");
__publicField(this, "data");
__publicField(this, "returnPreference");
__publicField(this, "layer");
__publicField(this, "config");
this.table = config.occurrence;
const runtime = createClientRuntime(config.layer);
this.layer = runtime.layer;
this.data = config.data;
this.returnPreference = config.returnPreference;
this.config = runtime.config;
}
/**
* Update a single record by ID
* Returns updated count by default, or full record if returnFullRecord was set to true
*/
byId(id) {
return new ExecutableUpdateBuilder({
occurrence: this.table,
layer: this.layer,
data: this.data,
mode: "byId",
recordLocator: id,
returnPreference: this.returnPreference
});
}
/**
* Update a single record by ROWID
*/
byRowId(rowId) {
return new ExecutableUpdateBuilder({
occurrence: this.table,
layer: this.layer,
data: this.data,
mode: "byId",
recordLocator: { ROWID: rowId },
returnPreference: this.returnPreference
});
}
/**
* Update records matching a filter query
* Returns updated count by default, or full record if returnFullRecord was set to true
* @param fn Callback that receives a QueryBuilder for building the filter
*/
where(fn) {
const queryBuilder = new QueryBuilder({
occurrence: this.table,
layer: this.layer
});
const configuredBuilder = fn(queryBuilder);
return new ExecutableUpdateBuilder({
occurrence: this.table,
layer: this.layer,
data: this.data,
mode: "byFilter",
queryBuilder: configuredBuilder,
returnPreference: this.returnPreference
});
}
}
class ExecutableUpdateBuilder {
constructor(config) {
__publicField(this, "table");
__publicField(this, "data");
__publicField(this, "mode");
__publicField(this, "recordLocator");
__publicField(this, "queryBuilder");
__publicField(this, "returnPreference");
__publicField(this, "layer");
__publicField(this, "config");
this.table = config.occurrence;
this.layer = config.layer;
this.data = config.data;
this.mode = config.mode;
this.recordLocator = config.recordLocator;
this.queryBuilder = config.queryBuilder;
this.returnPreference = config.returnPreference;
const runtime = createClientRuntime(this.layer);
this.config = runtime.config;
}
execute(options) {
const mergedOptions = mergeMutationExecuteOptions(
options,
this.config.useEntityIds,
this.config.includeSpecialColumns
);
const { method: _method, body: _body, headers: callerHeaders, ...requestOptions } = mergedOptions;
const shouldUseIds = mergedOptions.useEntityIds ?? this.config.useEntityIds;
const includeSpecialColumns = mergedOptions.includeSpecialColumns ?? this.config.includeSpecialColumns;
const tableId = resolveMutationTableId(this.table, shouldUseIds, "ExecutableUpdateBuilder");
const url = buildMutationUrl({
databaseName: this.config.databaseName,
tableId,
tableName: getTableName(this.table),
mode: this.mode,
recordLocator: this.recordLocator,
queryBuilder: this.queryBuilder,
useEntityIds: shouldUseIds,
builderName: "ExecutableUpdateBuilder"
});
const requestHeaders = new Headers(callerHeaders || {});
const preferHeader = mergePreferHeaderValues(
this.returnPreference === "representation" ? "return=representation" : void 0,
shouldUseIds ? "fmodata.entity-ids" : void 0,
includeSpecialColumns ? "fmodata.include-specialcolumns" : void 0,
requestHeaders.get("Prefer") ?? void 0
);
requestHeaders.set("Content-Type", "application/json");
if (preferHeader) {
requestHeaders.set("Prefer", preferHeader);
} else {
requestHeaders.delete("Prefer");
}
const pipeline = Effect.gen(this, function* () {
let validatedData = this.data;
if (this.table) {
const baseTableConfig = getBaseTableConfig(this.table);
validatedData = yield* tryEffect(
() => validateAndTransformInput(this.data, baseTableConfig.inputSchema),
(e) => e instanceof Error ? e : new BuilderInvariantError("ExecutableUpdateBuilder.execute", String(e))
);
}
const transformedData = this.table && shouldUseIds ? transformFieldNamesToIds(validatedData, this.table) : validatedData;
const response = yield* requestFromService(url, {
...requestOptions,
method: "PATCH",
headers: requestHeaders,
body: JSON.stringify(transformedData)
});
if (this.returnPreference === "representation") {
return response;
}
const updatedCount = extractAffectedRows(response, void 0, 0, "updatedCount");
return { updatedCount };
});
return runLayerResult(this.layer, pipeline, "fmodata.update", {
"fmodata.table": getTableName(this.table)
});
}
// biome-ignore lint/suspicious/noExplicitAny: Request body can be any JSON-serializable value
getRequestConfig() {
const tableId = resolveMutationTableId(this.table, this.config.useEntityIds, "ExecutableUpdateBuilder");
const transformedData = this.table && this.config.useEntityIds ? transformFieldNamesToIds(this.data, this.table) : this.data;
const url = buildMutationUrl({
databaseName: this.config.databaseName,
tableId,
tableName: getTableName(this.table),
mode: this.mode,
recordLocator: this.recordLocator,
queryBuilder: this.queryBuilder,
useEntityIds: this.config.useEntityIds,
builderName: "ExecutableUpdateBuilder"
});
return {
method: "PATCH",
url,
body: JSON.stringify(transformedData)
};
}
toRequest(baseUrl, options) {
const config = this.getRequestConfig();
const fullUrl = `${baseUrl}${normalizeDatabasePath(config.url, {
normalizeDatabaseName: (options == null ? void 0 : options.normalizeDatabaseName) ?? this.config.normalizeDatabaseName
})}`;
const preferHeader = mergePreferHeaderValues(
this.returnPreference === "representation" ? "return=representation" : void 0,
(options == null ? void 0 : options.useEntityIds) ?? this.config.useEntityIds ? "fmodata.entity-ids" : void 0,
(options == null ? void 0 : options.includeSpecialColumns) ?? this.config.includeSpecialColumns ? "fmodata.include-specialcolumns" : void 0
);
return new Request(fullUrl, {
method: config.method,
headers: {
"Content-Type": "application/json",
Accept: getAcceptHeader(options == null ? void 0 : options.includeODataAnnotations),
...preferHeader ? { Prefer: preferHeader } : {}
},
body: config.body
});
}
async processResponse(response, options) {
if (!response.ok) {
const tableName = getTableName(this.table);
const error = await parseErrorResponse(response, response.url || `/${this.config.databaseName}/${tableName}`);
return { data: void 0, error };
}
const text = await response.text();
if (!text || text.trim() === "") {
const updatedCount2 = extractAffectedRows(void 0, response.headers, 1, "updatedCount");
return {
data: { updatedCount: updatedCount2 },
error: void 0
};
}
const rawResponse = JSON.parse(text);
let _validatedData = this.data;
if (this.table) {
const baseTableConfig = getBaseTableConfig(this.table);
const inputSchema = baseTableConfig.inputSchema;
try {
_validatedData = await validateAndTransformInput(this.data, inputSchema);
} catch (error) {
return {
data: void 0,
error: error instanceof Error ? error : new BuilderInvariantError("ExecutableUpdateBuilder.processResponse", String(error))
// biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic return type
};
}
}
if (this.returnPreference === "representation") {
const shouldUseIds = (options == null ? void 0 : options.useEntityIds) ?? this.config.useEntityIds;
const includeSpecialColumns = (options == null ? void 0 : options.includeSpecialColumns) ?? this.config.includeSpecialColumns;
let transformedResponse = rawResponse;
if (this.table && shouldUseIds) {
transformedResponse = transformResponseFields(rawResponse, this.table, void 0);
}
const validation = await validateSingleResponse(
transformedResponse,
getBaseTableConfig(this.table).schema,
void 0,
void 0,
"exact",
includeSpecialColumns
);
if (!validation.valid) {
return { data: void 0, error: validation.error };
}
if (validation.data === null) {
return {
data: void 0,
error: new BuilderInvariantError(
"ExecutableUpdateBuilder.processResponse",
"update operation returned null response"
)
};
}
return {
data: validation.data,
error: void 0
};
}
const updatedCount = extractAffectedRows(rawResponse, response.headers, 0, "updatedCount");
return {
data: { updatedCount },
error: void 0
};
}
}
export {
ExecutableUpdateBuilder,
UpdateBuilder
};
//# sourceMappingURL=update-builder.js.map