357 lines · 11.9 KB · TypeScript
RawDownload
import { Effect, Option, Predicate, Schema } from "effect"; import {  type FumaTables,  type PluginStorageEntry,  type StorageDeps,  type StorageFailure,} from "@executor-js/sdk/core"; import {  ConfiguredHeaderBinding,  OAuth2SourceConfig,  OperationBinding,  type ConfiguredHeaderValue,} from "./types";export {  StoredSourceSchema,  type StoredSourceSchemaType,  headerBindingSlot,  oauth2ClientIdSlot,  oauth2ClientSecretSlot,  oauth2ConnectionSlot,  queryParamBindingSlot,} from "./source-contracts"; export const openapiSchema = {} satisfies FumaTables;export type OpenapiSchema = typeof openapiSchema; export interface SourceConfig {  readonly spec: string;  readonly sourceUrl?: string;  readonly baseUrl?: string;  readonly namespace?: string;  readonly headers?: Record<string, ConfiguredHeaderValue>;  readonly queryParams?: Record<string, ConfiguredHeaderValue>;  readonly specFetchCredentials?: OpenApiSpecFetchCredentials;  readonly oauth2?: OAuth2SourceConfig;} export interface OpenApiSpecFetchCredentials {  readonly headers?: Record<string, ConfiguredHeaderValue>;  readonly queryParams?: Record<string, ConfiguredHeaderValue>;} export interface StoredSource {  readonly namespace: string;  readonly scope: string;  readonly name: string;  readonly config: SourceConfig;} export interface StoredOperation {  readonly toolId: string;  readonly sourceId: string;  readonly binding: OperationBinding;} const SOURCE_COLLECTION = "source";const OPERATION_COLLECTION = "operation"; const encodeBinding = Schema.encodeSync(OperationBinding);const decodeBinding = Schema.decodeUnknownSync(OperationBinding);const decodeBindingJson = Schema.decodeUnknownSync(Schema.fromJsonString(OperationBinding)); const decodeOAuth2SourceConfigOption = Schema.decodeUnknownOption(OAuth2SourceConfig);const decodeOAuth2SourceConfigJsonOption = Schema.decodeUnknownOption(  Schema.fromJsonString(OAuth2SourceConfig),);const encodeOAuth2SourceConfig = Schema.encodeSync(OAuth2SourceConfig); const NullableString = Schema.NullOr(Schema.String);const OptionalNullableString = Schema.optional(NullableString);const ConfiguredHeaderBindingStorage = Schema.Struct({  kind: Schema.Literal("binding"),  slot: Schema.String,  prefix: OptionalNullableString,});const ConfiguredHeaderValueStorage = Schema.Union([Schema.String, ConfiguredHeaderBindingStorage]);const ConfiguredHeaderMapStorage = Schema.Record(Schema.String, ConfiguredHeaderValueStorage);const SpecFetchCredentialsStorage = Schema.Struct({  headers: Schema.optional(ConfiguredHeaderMapStorage),  queryParams: Schema.optional(ConfiguredHeaderMapStorage),});const SourceConfigStorage = Schema.Struct({  spec: Schema.String,  sourceUrl: Schema.optional(Schema.String),  baseUrl: Schema.optional(Schema.String),  namespace: Schema.optional(Schema.String),  headers: Schema.optional(ConfiguredHeaderMapStorage),  queryParams: Schema.optional(ConfiguredHeaderMapStorage),  specFetchCredentials: Schema.optional(SpecFetchCredentialsStorage),  oauth2: Schema.optional(Schema.Unknown),});const SourceStorage = Schema.Struct({  namespace: Schema.String,  scope: Schema.String,  name: Schema.String,  config: SourceConfigStorage,});const OperationStorage = Schema.Struct({  toolId: Schema.String,  sourceId: Schema.String,  binding: Schema.Unknown,});const decodeSourceStorage = Schema.decodeUnknownOption(SourceStorage);const decodeOperationStorage = Schema.decodeUnknownOption(OperationStorage); const toJsonRecord = (value: unknown): Record<string, unknown> => value as Record<string, unknown>; const normalizeStoredOAuth2 = (value: unknown): OAuth2SourceConfig | undefined => {  if (value == null) return undefined;  const sourceConfig =    typeof value === "string"      ? decodeOAuth2SourceConfigJsonOption(value)      : decodeOAuth2SourceConfigOption(value);  if (Option.isSome(sourceConfig)) return sourceConfig.value;  return undefined;}; const normalizeConfiguredMap = (  values: Readonly<Record<string, typeof ConfiguredHeaderValueStorage.Type>> | undefined,): Record<string, ConfiguredHeaderValue> | undefined => {  if (!values) return undefined;  const normalized: Record<string, ConfiguredHeaderValue> = {};  for (const [name, value] of Object.entries(values)) {    if (typeof value === "string") {      normalized[name] = value;    } else {      normalized[name] =        value.prefix != null          ? ConfiguredHeaderBinding.make({              kind: "binding",              slot: value.slot,              prefix: value.prefix,            })          : ConfiguredHeaderBinding.make({              kind: "binding",              slot: value.slot,            });    }  }  return normalized;}; const encodeSourceConfig = (config: SourceConfig): Record<string, unknown> => ({  spec: config.spec,  ...(config.sourceUrl ? { sourceUrl: config.sourceUrl } : {}),  ...(config.baseUrl ? { baseUrl: config.baseUrl } : {}),  ...(config.namespace ? { namespace: config.namespace } : {}),  ...(config.headers ? { headers: config.headers } : {}),  ...(config.queryParams ? { queryParams: config.queryParams } : {}),  ...(config.specFetchCredentials ? { specFetchCredentials: config.specFetchCredentials } : {}),  ...(config.oauth2 ? { oauth2: toJsonRecord(encodeOAuth2SourceConfig(config.oauth2)) } : {}),}); const rowToSource = (row: PluginStorageEntry): StoredSource | null => {  const decoded = decodeSourceStorage(row.data);  if (Option.isNone(decoded)) return null;  const stored = decoded.value;  const oauth2 = normalizeStoredOAuth2(stored.config.oauth2);  return {    namespace: stored.namespace,    scope: stored.scope,    name: stored.name,    config: {      spec: stored.config.spec,      sourceUrl: stored.config.sourceUrl,      baseUrl: stored.config.baseUrl,      namespace: stored.config.namespace,      headers: normalizeConfiguredMap(stored.config.headers),      queryParams: normalizeConfiguredMap(stored.config.queryParams),      specFetchCredentials: stored.config.specFetchCredentials        ? {            headers: normalizeConfiguredMap(stored.config.specFetchCredentials.headers),            queryParams: normalizeConfiguredMap(stored.config.specFetchCredentials.queryParams),          }        : undefined,      oauth2,    },  };}; const rowToOperation = (row: PluginStorageEntry): StoredOperation | null => {  const decoded = decodeOperationStorage(row.data);  if (Option.isNone(decoded)) return null;  const operation = decoded.value;  return {    toolId: operation.toolId,    sourceId: operation.sourceId,    binding: decodeBinding(      typeof operation.binding === "string"        ? decodeBindingJson(operation.binding)        : operation.binding,    ),  };}; export interface OpenapiStore {  readonly upsertSource: (    input: StoredSource,    operations: readonly StoredOperation[],  ) => Effect.Effect<void, StorageFailure>;  readonly updateSourceMeta: (    namespace: string,    scope: string,    patch: {      readonly name?: string;      readonly baseUrl?: string;      readonly headers?: Record<string, ConfiguredHeaderValue>;      readonly queryParams?: Record<string, ConfiguredHeaderValue>;      readonly specFetchCredentials?: OpenApiSpecFetchCredentials;      readonly oauth2?: OAuth2SourceConfig;    },  ) => Effect.Effect<void, StorageFailure>;  readonly getSource: (    namespace: string,    scope: string,  ) => Effect.Effect<StoredSource | null, StorageFailure>;  readonly listSources: () => Effect.Effect<readonly StoredSource[], StorageFailure>;  readonly getOperationByToolId: (    toolId: string,    scope: string,  ) => Effect.Effect<StoredOperation | null, StorageFailure>;  readonly listOperationsBySource: (    sourceId: string,    scope: string,  ) => Effect.Effect<readonly StoredOperation[], StorageFailure>;  readonly removeSource: (namespace: string, scope: string) => Effect.Effect<void, StorageFailure>;} export const makeDefaultOpenapiStore = ({  pluginStorage,}: StorageDeps<OpenapiSchema>): OpenapiStore => {  const sourceData = (source: StoredSource) => ({    namespace: source.namespace,    scope: source.scope,    name: source.name,    config: encodeSourceConfig(source.config),  });   const operationData = (operation: StoredOperation) => ({    toolId: operation.toolId,    sourceId: operation.sourceId,    binding: toJsonRecord(encodeBinding(operation.binding)),  });   const listOperationRowsForSourceScope = (sourceId: string, scope: string) =>    pluginStorage      .list({        collection: OPERATION_COLLECTION,        keyPrefix: `${sourceId}.`,      })      .pipe(        Effect.map((rows) =>          rows.filter(            (row) => String(row.scopeId) === scope && rowToOperation(row)?.sourceId === sourceId,          ),        ),      );   const removeOperationsForSourceScope = (sourceId: string, scope: string) =>    Effect.gen(function* () {      const rows = yield* listOperationRowsForSourceScope(sourceId, scope);      for (const row of rows) {        yield* pluginStorage.remove({          scope,          collection: OPERATION_COLLECTION,          key: row.key,        });      }    });   const deleteSource = (namespace: string, scope: string) =>    Effect.gen(function* () {      yield* removeOperationsForSourceScope(namespace, scope);      yield* pluginStorage.remove({        scope,        collection: SOURCE_COLLECTION,        key: namespace,      });    });   return {    upsertSource: (input, operations) =>      Effect.gen(function* () {        yield* deleteSource(input.namespace, input.scope);        yield* pluginStorage.put({          scope: input.scope,          collection: SOURCE_COLLECTION,          key: input.namespace,          data: sourceData(input),        });        for (const operation of operations) {          yield* pluginStorage.put({            scope: input.scope,            collection: OPERATION_COLLECTION,            key: operation.toolId,            data: operationData(operation),          });        }      }),     updateSourceMeta: (namespace, scope, patch) =>      Effect.gen(function* () {        const existing = yield* pluginStorage.getAtScope({          scope,          collection: SOURCE_COLLECTION,          key: namespace,        });        if (!existing) return;        const source = rowToSource(existing);        if (!source) return;        const next: StoredSource = {          ...source,          name: patch.name?.trim() || source.name,          config: {            ...source.config,            ...(patch.baseUrl !== undefined ? { baseUrl: patch.baseUrl } : {}),            ...(patch.headers !== undefined ? { headers: patch.headers } : {}),            ...(patch.queryParams !== undefined ? { queryParams: patch.queryParams } : {}),            ...(patch.specFetchCredentials !== undefined              ? { specFetchCredentials: patch.specFetchCredentials }              : {}),            ...(patch.oauth2 !== undefined ? { oauth2: patch.oauth2 } : {}),          },        };        yield* pluginStorage.put({          scope,          collection: SOURCE_COLLECTION,          key: namespace,          data: sourceData(next),        });      }),     getSource: (namespace, scope) =>      pluginStorage        .getAtScope({ scope, collection: SOURCE_COLLECTION, key: namespace })        .pipe(Effect.map((row) => (row ? rowToSource(row) : null))),     listSources: () =>      pluginStorage        .list({ collection: SOURCE_COLLECTION })        .pipe(Effect.map((rows) => rows.map(rowToSource).filter(Predicate.isNotNull))),     getOperationByToolId: (toolId, scope) =>      pluginStorage        .getAtScope({ scope, collection: OPERATION_COLLECTION, key: toolId })        .pipe(Effect.map((row) => (row ? rowToOperation(row) : null))),     listOperationsBySource: (sourceId, scope) =>      listOperationRowsForSourceScope(sourceId, scope).pipe(        Effect.map((rows) => rows.map(rowToOperation).filter(Predicate.isNotNull)),      ),     removeSource: (namespace, scope) => deleteSource(namespace, scope),  };};