/*** IMPORT ------------------------------------------- ***/ import { dirname, join } from "@std/path"; /*** UTILITY ------------------------------------------ ***/ const { cwd, readFileSync } = Deno; const DYNAMIC_IMPORT_REGEX = /^#\s(DYNAMIC_IMPORTS)/gm; const ENCODING = "utf-8"; const FILE_REGEX = /\w*(.graphql)/g; const IMPORT_REGEX = /^#\s(import)\s.*(.graphql")/gm; /*** EXPORT ------------------------------------------- ***/ /** * Reads a `.graphql` file and resolves its imports into a single SDL string. * * Two mechanisms are supported, and both can appear in the same file: * * 1. **Explicit imports** — lines like `# import "./user.graphql"` are replaced * inline with the contents of the referenced file, resolved relative to the * entry file. * 2. **Dynamic imports** — if the file contains `# DYNAMIC_IMPORTS`, every * `.graphql` file found one level below `/schema/` is inlined at that * marker. Useful for feature-folder layouts. * * Errors are logged and an empty string is returned so boot doesn't crash. * * @param path - Path to the entry `.graphql` file, relative to `Deno.cwd()`. * @returns The fully-expanded schema string. */ export async function importQL(path: string): Promise { const SCHEMA_DIRECTORY = join(cwd(), "schema"); try { const decoder = new TextDecoder(ENCODING); const file = readFileSync(join(cwd(), String(path))); const imports = decoder.decode(file).match(IMPORT_REGEX) || []; const shouldTryDynamicallyImporting = decoder.decode(file).match(DYNAMIC_IMPORT_REGEX) ? true : false; let parsedFile = decoder.decode(file); /*** `import` statements in the supplied schema file are parsed to dynamically bring in linked files. ***/ imports.map((imp: string) => { const matchedFilename: null | Array = imp.match(FILE_REGEX); if (!matchedFilename || !matchedFilename.length || matchedFilename.length < 1) return; const filename = matchedFilename[0]; const importedFileDecoder = new TextDecoder(ENCODING); const importedFile = Deno.readFileSync(join(dirname(String(path)), filename)); const decodedFile = importedFileDecoder.decode(importedFile); parsedFile = parsedFile.replace(imp, decodedFile); }); /*** With dynamic importing, we just look inside the `program` directory to find `.graphql` files and automatically bring them in, if `# DYNAMIC_IMPORTS` exists in `schema.graphql`. ***/ if (shouldTryDynamicallyImporting) { const graphqlFiles = []; for await (const dirEntry of Deno.readDir(SCHEMA_DIRECTORY)) { const { isDirectory } = dirEntry; if (isDirectory) { const DIR = join(SCHEMA_DIRECTORY, dirEntry.name); for await (const dirEntry of Deno.readDir(DIR)) { const { isFile } = dirEntry; if (isFile && dirEntry.name.match(FILE_REGEX)) graphqlFiles.push(join(DIR, dirEntry.name)); } } } for (const file of graphqlFiles) { const importedFileDecoder = new TextDecoder(ENCODING); const importedFile = Deno.readFileSync(file); const decodedFile = importedFileDecoder.decode(importedFile); const insertPosition = parsedFile.indexOf("# DYNAMIC_IMPORTS"); if (insertPosition !== -1) { parsedFile = parsedFile.substring(0, insertPosition) + decodedFile + parsedFile.substring(insertPosition); } } } return parsedFile; } catch(parseError) { console.error(new Error(`error parsing file [${String(path)}]`), parseError); return ""; } }