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), };};
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),
};
};