UNPKG

@proofkit/fmodata

Version:

FileMaker OData API client

270 lines (269 loc) 10.6 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 { 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