@proofkit/fmodata
Version:
FileMaker OData API client
110 lines (92 loc) • 3.76 kB
text/typescript
/**
* Effect Service definitions for fmodata.
*
* These services replace the monolithic ExecutionContext interface with
* composable, mockable Effect services. Each service has a single responsibility:
* - HttpClient: HTTP request execution
* - ODataConfig: Connection and database configuration
* - ODataLogger: Logging
*
* Services are combined into Layers provided by FMServerConnection (production)
* or MockFMServerConnection (testing).
*/
import type { FFetchOptions } from "@fetchkit/ffetch";
import { Context, Effect, Layer } from "effect";
import type { DatabaseNameNormalizationMode } from "./client/database-name";
import type { FMODataErrorType } from "./errors";
import { MissingLayerServiceError } from "./errors";
import type { InternalLogger } from "./logger";
// --- HttpClient Service ---
export interface HttpClient {
readonly request: <T>(
url: string,
options?: RequestInit &
FFetchOptions & {
normalizeDatabaseName?: boolean;
databaseNameNormalizationMode?: DatabaseNameNormalizationMode;
useEntityIds?: boolean;
includeSpecialColumns?: boolean;
includeODataAnnotations?: boolean;
retryPolicy?: import("./types").RetryPolicy;
},
) => Effect.Effect<T, FMODataErrorType>;
}
export const HttpClient = Context.GenericTag<HttpClient>("@proofkit/fmodata/HttpClient");
// --- ODataConfig Service ---
export interface ODataConfig {
readonly baseUrl: string;
readonly databaseName: string;
readonly normalizeDatabaseName: boolean;
readonly useEntityIds: boolean;
readonly includeSpecialColumns: boolean;
}
export const ODataConfig = Context.GenericTag<ODataConfig>("@proofkit/fmodata/ODataConfig");
// --- ODataLogger Service ---
export interface ODataLogger {
readonly logger: InternalLogger;
}
export const ODataLogger = Context.GenericTag<ODataLogger>("@proofkit/fmodata/ODataLogger");
// --- Combined layer type ---
export type FMODataLayer = Layer.Layer<HttpClient | ODataConfig | ODataLogger>;
// --- Layer utilities ---
/**
* Extracts ODataConfig and ODataLogger values from a Layer synchronously.
* Used by builders to access config in non-Effect methods (getRequestConfig, toRequest, etc.)
*/
export function extractConfigFromLayer(layer: FMODataLayer): { config: ODataConfig; logger: InternalLogger } {
const effect = Effect.gen(function* () {
const config = yield* ODataConfig.pipe(
Effect.mapError((error) => new MissingLayerServiceError("ODataConfig", { cause: error as Error })),
);
const { logger } = yield* ODataLogger.pipe(
Effect.mapError((error) => new MissingLayerServiceError("ODataLogger", { cause: error as Error })),
);
return { config, logger };
});
return Effect.runSync(Effect.provide(effect, layer));
}
/**
* Creates a database-scoped Layer by overriding ODataConfig with database-specific values.
* The HttpClient and ODataLogger services are preserved from the base layer.
*/
export function createDatabaseLayer(
baseLayer: FMODataLayer,
overrides: {
databaseName: string;
normalizeDatabaseName: boolean;
useEntityIds: boolean;
includeSpecialColumns: boolean;
},
): FMODataLayer {
// Extract base config to get baseUrl
const { config: baseConfig } = extractConfigFromLayer(baseLayer);
const dbConfigLayer = Layer.succeed(ODataConfig, {
baseUrl: baseConfig.baseUrl,
databaseName: overrides.databaseName,
normalizeDatabaseName: overrides.normalizeDatabaseName,
useEntityIds: overrides.useEntityIds,
includeSpecialColumns: overrides.includeSpecialColumns,
});
// Merge: dbConfigLayer overrides ODataConfig, but HttpClient and ODataLogger come from baseLayer
return Layer.merge(baseLayer, dbConfigLayer);
}