/**
* @fileoverview Effect-based Gremlin service for graph database operations.
*
* Provides a modular, composable service layer for Gremlin graph databases using
* Effect-ts patterns. Handles connection management, schema caching, query execution,
* and error handling.
*/
import { Effect, Context, Layer, pipe } from 'effect';
import { type GremlinQueryResult, GremlinQueryResultSchema } from './models/index.js';
import { parseGremlinResultsWithMetadata } from '../utils/result-parser.js';
import { isGremlinResult } from '../utils/type-guards.js';
import { GremlinConnectionError, GremlinQueryError, Errors, ParseError } from '../errors.js';
import { GremlinClient } from './client.js';
import { SchemaService } from './schema.js';
import type { GremlinClientType, GremlinResultSet } from './types.js';
import type { GraphSchema, ServiceStatus } from './types.js';
/**
* Gremlin service using Effect Context.Tag pattern.
*
* Provides graph database operations including:
* - Connection status monitoring
* - Schema introspection and caching
* - Query execution with result parsing
* - Health check capabilities
*
* All operations return Effect types for composable error handling.
*/
export class GremlinService extends Context.Tag('GremlinService')<
GremlinService,
{
readonly getStatus: Effect.Effect<ServiceStatus, never>;
readonly getSchema: Effect.Effect<GraphSchema, GremlinConnectionError | GremlinQueryError>;
readonly getCachedSchema: Effect.Effect<GraphSchema | null, never>;
readonly refreshSchemaCache: Effect.Effect<void, GremlinConnectionError | GremlinQueryError>;
readonly executeQuery: (
query: string
) => Effect.Effect<GremlinQueryResult, GremlinQueryError | GremlinConnectionError | ParseError>;
readonly healthCheck: Effect.Effect<{ healthy: boolean; details: string }, never>;
}
>() {}
/**
* Creates the Gremlin service implementation with dependency injection.
*
* @returns Effect providing the complete service implementation
*
* The service implementation manages:
* - Connection state through the GremlinClient service
* - Schema cache with automatic generation and refresh capabilities
* - Query execution with result transformation and validation
* - Health monitoring and status reporting
*/
const makeGremlinService = Effect.gen(function* () {
const gremlinClient = yield* GremlinClient;
const schemaService = yield* SchemaService;
/**
* Executes a raw Gremlin query against the client.
*
* @param query - Gremlin traversal query string
* @param client - Gremlin client instance
* @returns Effect with query results or execution error
*/
const executeRawQuery = (
query: string,
client: GremlinClientType
): Effect.Effect<unknown, GremlinQueryError> =>
pipe(
Effect.tryPromise(() => client.submit(query)),
Effect.mapError((error: unknown) => Errors.query('Query execution failed', query, error))
);
/**
* Processes Gremlin ResultSet into standard array format.
*
* @param resultSet - Raw result from Gremlin client
* @returns Array of result items
*/
const processResultSet = (resultSet: unknown): unknown[] => {
if (resultSet && typeof resultSet === 'object' && '_items' in resultSet) {
return (resultSet as unknown as GremlinResultSet).toArray();
}
if (Array.isArray(resultSet)) {
return resultSet;
}
return resultSet !== undefined ? [resultSet] : [];
};
/**
* Transforms raw result set into parsed GremlinQueryResult format.
*
* @param query - Original query string for error context
* @param resultSet - Raw result set from Gremlin
* @returns Effect with parsed results and metadata
*/
const transformGremlinResult = (
query: string,
resultSet: unknown
): Effect.Effect<{ results: unknown[]; message: string }, ParseError> =>
pipe(
Effect.sync(() => processResultSet(resultSet)),
Effect.flatMap(dataArray => parseGremlinResultsWithMetadata(dataArray)),
Effect.map(parsed => ({
results: parsed.results,
message: 'Query executed successfully',
})),
Effect.mapError(error => Errors.parse('Result parsing failed', query, error))
);
/**
* Validates query result against the GremlinQueryResult schema.
*
* @param query - Original query string for error context
* @param result - Parsed result object
* @returns Effect with validated GremlinQueryResult
*/
const validateQueryResult = (
query: string,
result: unknown
): Effect.Effect<GremlinQueryResult, ParseError> =>
pipe(
Effect.try(() => GremlinQueryResultSchema.parse(result)),
Effect.mapError((error: unknown) => Errors.parse('Result validation failed', query, error))
);
/**
* Executes a Gremlin query with comprehensive error handling.
*
* @param query - Gremlin traversal query string
* @returns Effect with parsed and validated query results
*/
const executeQuery = (
query: string
): Effect.Effect<GremlinQueryResult, GremlinQueryError | GremlinConnectionError | ParseError> =>
pipe(
Effect.logDebug(`Executing Gremlin query: ${query}`),
Effect.andThen(() => executeRawQuery(query, gremlinClient.client)),
Effect.filterOrFail(isGremlinResult, resultSet =>
Errors.query('Invalid result format received', query, resultSet)
),
Effect.andThen(resultSet => transformGremlinResult(query, resultSet)),
Effect.andThen(parsedResults => validateQueryResult(query, parsedResults))
);
const getStatus = Effect.succeed({ status: 'connected' as const });
const healthCheck = Effect.succeed({
healthy: true,
details: 'Connected',
});
return {
getStatus,
getSchema: schemaService.getSchema,
getCachedSchema: schemaService.peekSchema,
refreshSchemaCache: schemaService.refreshSchema,
executeQuery,
healthCheck,
} as const;
});
/**
* Creates a layer providing the Gremlin service implementation.
*
* This layer depends on the `GremlinClient` service, which is expected
* to be provided elsewhere in the application's layer composition.
*/
export const GremlinServiceLive = Layer.effect(GremlinService, makeGremlinService);