import { Grafaid } from '#lib/grafaid/index' import { GraphqlChange } from '#lib/graphql-change/index' import type { GraphqlChangeset } from '#lib/graphql-changeset/index' import { debugPolen } from '#singletons/debug' import { Arr, Path } from '@wollybeard/kit' import { glob } from 'tinyglobby' import type { Schema } from '../../schema.js' import { FileNameExpression } from './file-name-expression/index.js' // const debug = debugPolen.sub([`schema`, `data-source-schema-directory`]) const debug = debugPolen.sub(`schema:data-source-schema-directory`) const defaultPaths = { schemaDirectory: `./schema`, } /** * Configuration for loading multiple schema versions from a directory. * * Enables the schema changelog feature by loading SDL files with date prefixes. */ export interface ConfigInput { /** * Path to the directory containing dated SDL files. * * Files should be named with ISO date prefixes: `YYYY-MM-DD.graphql` * * @default './schema' * * @example * ```ts * // Default location * path: './schema' * * // Custom location * path: './graphql/versions' * ``` * * Directory structure example: * ``` * schema/ * 2024-01-15.graphql * 2024-03-20.graphql * 2024-06-10.graphql * ``` */ path?: string projectRoot?: string } export interface Config { path: string } export const normalizeConfig = (configInput: ConfigInput): Config => { const ensureAbsolute = Path.ensureAbsoluteWith(configInput.projectRoot) const config: Config = { path: ensureAbsolute(configInput.path ?? defaultPaths.schemaDirectory), } return config } export const readOrThrow = async (configInput: ConfigInput): Promise => { const config = normalizeConfig(configInput) debug(`will search`, config) const filePaths = await glob({ cwd: config.path, absolute: true, onlyFiles: true, patterns: [`*.graphql`], }) debug(`did find`, filePaths) if (!Arr.isntEmpty(filePaths)) { return null } const fileNameExpressions = Arr.map(filePaths, FileNameExpression.parseOrThrow) debug(`parsed file names`, fileNameExpressions) const versions = await Promise.all(Arr.map(fileNameExpressions, async fileNameExpression => { const schemaFile = await Grafaid.Schema.read(fileNameExpression.filePath) // Should never happen since these paths come from the glob. if (!schemaFile) throw new Error(`Failed to read schema file: ${fileNameExpression.filePath}`) return { ...fileNameExpression, schema: schemaFile.content, } })) debug(`read schemas`) versions.sort((a, b) => a.date.getTime() - b.date.getTime()) const changesets = await Promise.all( Arr.map(versions, async (version, index): Promise => { const current = version const previous = versions[index - 1] const before = previous?.schema ?? Grafaid.Schema.empty const after = current.schema const changes = await GraphqlChange.calcChangeset({ before, after, }) return { date: current.date, before, after, changes, } }), ) changesets.reverse() const schema: Schema = { versions: changesets, } debug(`computed schema`) return schema }