UNPKG

@proofkit/fmodata

Version:

FileMaker OData API client

222 lines (221 loc) 7.92 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 { requestFromService, runLayerResult } from "../effect.js"; import { BatchTruncatedError } from "../errors.js"; import { formatBatchRequestFromNative, parseBatchResponse } from "./batch-request.js"; import { normalizeDatabasePath } from "./database-name.js"; import { createClientRuntime } from "./runtime.js"; function parsedToResponse(parsed) { const headers = new Headers(parsed.headers); if (parsed.body === null || parsed.body === void 0) { return new Response(null, { status: parsed.status, statusText: parsed.statusText, headers }); } const bodyString = typeof parsed.body === "string" ? parsed.body : JSON.stringify(parsed.body); let status = parsed.status; if (status === 204 && bodyString && bodyString.trim() !== "") { status = 200; } return new Response(status === 204 ? null : bodyString, { status, statusText: parsed.statusText, headers }); } class BatchBuilder { constructor(builders, layer) { // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any ExecutableBuilder result type __publicField(this, "builders"); __publicField(this, "layer"); __publicField(this, "config"); this.builders = [...builders]; const runtime = createClientRuntime(layer); this.layer = runtime.layer; this.config = runtime.config; } /** * Add a request to the batch dynamically. * This allows building up batch operations programmatically. * * @param builder - An executable builder to add to the batch * @returns A BatchBuilder typed with the appended request result * @example * ```ts * const batch = db.batch([]); * batch.addRequest(db.from('contacts').list()); * batch.addRequest(db.from('users').list()); * const result = await batch.execute(); * ``` */ addRequest(builder) { this.builders.push(builder); return this; } /** * Get the request configuration for this batch operation. * This is used internally by the execution system. */ // biome-ignore lint/suspicious/noExplicitAny: Request body can be any JSON-serializable value getRequestConfig() { return { method: "POST", url: `/${this.config.databaseName}/$batch`, body: void 0 // Body is constructed in execute() }; } toRequest(baseUrl, _options) { const fullUrl = `${baseUrl}${normalizeDatabasePath(`/${this.config.databaseName}/$batch`, { normalizeDatabaseName: (_options == null ? void 0 : _options.normalizeDatabaseName) ?? this.config.normalizeDatabaseName })}`; return new Request(fullUrl, { method: "POST", headers: { "Content-Type": "multipart/mixed", "OData-Version": "4.0" } }); } // biome-ignore lint/suspicious/noExplicitAny: Generic return type for interface compliance processResponse(_response, _options) { return Promise.resolve({ data: void 0, error: { name: "NotImplementedError", message: "Batch operations handle response processing internally", timestamp: /* @__PURE__ */ new Date() // biome-ignore lint/suspicious/noExplicitAny: Type assertion for error object } }); } /** * Creates a failed BatchResult where all operations are marked as failed with the given error. */ // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any result type failAllResults(error) { const errorCount = this.builders.length; const results = this.builders.map(() => ({ data: void 0, error, status: 0 })); return { // biome-ignore lint/suspicious/noExplicitAny: Type assertion for complex generic return type results, successCount: 0, errorCount, truncated: false, firstErrorIndex: errorCount > 0 ? 0 : null }; } /** * Execute the batch operation. * * @param options - Optional fetch options and batch-specific options (includes beforeRequest hook) * @returns A BatchResult containing individual results for each operation */ async execute(options) { const baseUrl = this.config.baseUrl; if (!baseUrl) { return this.failAllResults({ name: "ConfigurationError", message: "Base URL not available in ODataConfig", timestamp: /* @__PURE__ */ new Date() }); } const pipeline = Effect.gen(this, function* () { const requests = this.builders.map((builder) => builder.toRequest(baseUrl, options)); const { body, boundary } = yield* Effect.tryPromise({ try: () => formatBatchRequestFromNative(requests, baseUrl), catch: (e) => e }); const responseData = yield* requestFromService(`/${this.config.databaseName}/$batch`, { ...options, method: "POST", headers: { ...options == null ? void 0 : options.headers, "Content-Type": `multipart/mixed; boundary=${boundary}`, "OData-Version": "4.0" }, body }); const firstLine = responseData.split("\r\n")[0] || responseData.split("\n")[0] || ""; const actualBoundary = firstLine.startsWith("--") ? firstLine.substring(2) : boundary; const contentTypeHeader = `multipart/mixed; boundary=${actualBoundary}`; const parsedResponses = parseBatchResponse(responseData, contentTypeHeader); const results = []; let successCount = 0; let errorCount = 0; let firstErrorIndex = null; const truncated = parsedResponses.length < this.builders.length; for (let i = 0; i < this.builders.length; i++) { const builder = this.builders[i]; const parsed = parsedResponses[i]; if (!parsed) { const failedAtIndex = firstErrorIndex ?? i; results.push({ data: void 0, error: new BatchTruncatedError(i, failedAtIndex), status: 0 }); errorCount++; continue; } if (!builder) { results.push({ data: void 0, error: { name: "BatchError", message: `Builder at index ${i} is undefined`, timestamp: /* @__PURE__ */ new Date() // biome-ignore lint/suspicious/noExplicitAny: Type assertion for error object }, status: parsed.status }); errorCount++; if (firstErrorIndex === null) { firstErrorIndex = i; } continue; } const nativeResponse = parsedToResponse(parsed); const result2 = yield* Effect.tryPromise({ try: () => builder.processResponse(nativeResponse, options), catch: (e) => e }); if (result2.error) { results.push({ data: void 0, error: result2.error, status: parsed.status }); errorCount++; if (firstErrorIndex === null) { firstErrorIndex = i; } } else { results.push({ data: result2.data, error: void 0, status: parsed.status }); successCount++; } } return { // biome-ignore lint/suspicious/noExplicitAny: Type assertion for complex generic return type results, successCount, errorCount, truncated, firstErrorIndex }; }); const result = await runLayerResult(this.layer, pipeline, "fmodata.batch"); if (result.error) { return this.failAllResults(result.error); } return result.data; } } export { BatchBuilder }; //# sourceMappingURL=batch-builder.js.map