/*** IMPORT ------------------------------------------- ***/ import { dirname, resolve } from "@std/path"; /*** UTILITY ------------------------------------------ ***/ const { readFileSync } = Deno; const DYNAMIC_IMPORT_MARKER = "# DYNAMIC_IMPORTS"; const DYNAMIC_IMPORT_REGEX = /^#\s(DYNAMIC_IMPORTS)/gm; const ENCODING = "utf-8"; const IMPORT_PATH_REGEX = /"([^"]+\.graphql)"/; 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 the entry file’s directory is * inlined at that marker. Useful for feature-folder layouts. * * All paths are resolved relative to the entry file itself, so consumers are * free to place their schema wherever they like. * * Errors are logged and an empty string is returned so boot doesn’t crash. * * @param path - Path to the entry `.graphql` file. Absolute paths are used * as-is; relative paths are resolved against `Deno.cwd()`. * @returns The fully-expanded schema string. */ export async function importQL(path: string): Promise { try { const decoder = new TextDecoder(ENCODING); const entryPath = resolve(String(path)); const entryDir = dirname(entryPath); const mainContent = decoder.decode(readFileSync(entryPath)); const imports = mainContent.match(IMPORT_REGEX) || []; const shouldTryDynamicallyImporting = DYNAMIC_IMPORT_REGEX.test(mainContent); let parsedFile = mainContent; /*** `import` statements in the supplied schema file are parsed to dynamically bring in linked files. ***/ for (const imp of imports) { const matched = imp.match(IMPORT_PATH_REGEX); if (!matched || !matched[1]) continue; const importedFile = readFileSync(resolve(entryDir, matched[1])); const decodedFile = decoder.decode(importedFile); parsedFile = parsedFile.replace(imp, decodedFile); } /*** With dynamic importing, we look one level below the entry file’s directory to find `.graphql` files and automatically bring them in, if `# DYNAMIC_IMPORTS` exists in the entry. ***/ if (shouldTryDynamicallyImporting) { const graphqlFiles: string[] = []; for await (const dirEntry of Deno.readDir(entryDir)) { if (!dirEntry.isDirectory) continue; const subDir = resolve(entryDir, dirEntry.name); for await (const sub of Deno.readDir(subDir)) { if (sub.isFile && sub.name.endsWith(".graphql")) graphqlFiles.push(resolve(subDir, sub.name)); } } for (const file of graphqlFiles) { const decodedFile = decoder.decode(readFileSync(file)); const insertPosition = parsedFile.indexOf(DYNAMIC_IMPORT_MARKER); 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 ""; } }