@proofkit/fmodata
Version:
FileMaker OData API client
222 lines (221 loc) • 7.92 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 { 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