@fragno-dev/corpus 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/parser.ts","../src/subject-tree.ts","../src/index.ts"],"sourcesContent":[],"mappings":";;AAUA;AAQA;AAUiB,UAlBA,WAAA,CAkBS;EAQT,EAAA,EAAA,MAAO;EASP,KAAA,EAAA,MAAO;;;;;AAQL,UAnCF,OAAA,CAmCE;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/parser.ts","../src/subject-tree.ts","../src/index.ts"],"sourcesContent":[],"mappings":";;AAUA;AAQA;AAUiB,UAlBA,WAAA,CAkBS;EAQT,EAAA,EAAA,MAAO;EASP,KAAA,EAAA,MAAO;;;;;AAQL,UAnCF,OAAA,CAmCE;;;;ECLH,EAAA,CAAA,EAAA,MAAA;AAOhB;AASA;AAYA;AAWA;UD3DiB,SAAA;;;AEdjB;AAwBA;AAUA;;UFZiB,OAAA;;;;;;;;UASA,OAAA;;;;;WAKN;YACC;YACA;YACA;;;;;;;AAAO,iBCLH,gBAAA,CDKG,SAAA,EAAA,MAAA,CAAA,EAAA,MAAA,GAAA,IAAA;;;;ACLH,iBAOA,kBAAA,CAPgB,SAAA,EAAA,MAAA,CAAA,EAAA,MAAA,EAAA;AAOhC;AASA;AAYA;AAWA;iBAvBgB,aAAA;;;AClDhB;AAwBA;AAUgB,iBD4BA,yBAAA,CC5ByB,SAAA,EAAA,MAAA,CAAA,EAAA,MAAA,EAAA;;;;iBDuCzB,uBAAA,CAAA;;;AD7EhB;AAQA;AAUA;AAQA;AASiB,iBE/BD,WAAA,CAAA,CF+BQ,EE/BO,WF+BP,EAAA;;;;;;;;;ACGxB;AAOA;AASA;AAYA;AAWA;iBCjDgB,UAAA,oBAA8B;;;AAxB9C;AAwBA;AAUgB,iBAAA,cAAA,CAAA,CAAyB,EAAP,OAAO,EAAA"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["blocks: CodeBlock[]","testBlocks: Array<{\n code: string;\n explanation: string;\n testName?: string;\n id?: string;\n }>","testName: string | undefined","sections: Section[]","match","examples: Example[]","SUBJECT_TREE: SubjectNode[]","ids: string[]"],"sources":["../src/parser.ts","../src/subject-tree.ts","../src/index.ts"],"sourcesContent":["import { readFileSync, readdirSync } from \"node:fs\";\nimport { join, basename, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n/**\n * Basic information about a subject\n */\nexport interface SubjectInfo {\n id: string;\n title: string;\n}\n\n/**\n * A single example within a subject\n */\nexport interface Example {\n code: string;\n explanation: string;\n testName?: string;\n id?: string;\n}\n\n/**\n * A code block with optional ID\n */\nexport interface CodeBlock {\n code: string;\n id?: string;\n}\n\n/**\n * A markdown section with heading and content\n */\nexport interface Section {\n heading: string;\n content: string;\n lineNumber?: number;\n}\n\n/**\n * Complete subject with all examples and metadata\n */\nexport interface Subject {\n id: string;\n title: string;\n description: string;\n imports: string;\n prelude: CodeBlock[];\n testInit: CodeBlock[];\n examples: Example[];\n sections: Section[];\n}\n\n/**\n * Raw parsed data from markdown before processing\n */\nexport interface ParsedMarkdown {\n title: string;\n description: string;\n imports: string;\n prelude: CodeBlock[];\n testInit: CodeBlock[];\n testBlocks: Array<{\n code: string;\n explanation: string;\n testName?: string;\n id?: string;\n }>;\n sections: Section[];\n}\n\n// Look for subjects directory in source or relative to built dist\nconst SUBJECTS_DIR = (() => {\n // Try dist/../src/subjects (when running from built code)\n const distRelative = join(__dirname, \"..\", \"src\", \"subjects\");\n try {\n readdirSync(distRelative);\n return distRelative;\n } catch {\n // Fall back to ./subjects (when running from source)\n return join(__dirname, \"subjects\");\n }\n})();\n\n/**\n * Helper function to extract code blocks with optional IDs from a directive\n */\nfunction extractCodeBlocks(content: string, directive: string): CodeBlock[] {\n const regex = new RegExp(\n `\\`\\`\\`typescript @fragno-${directive}(?::(\\\\w+(?:-\\\\w+)*))?\\\\n([\\\\s\\\\S]*?)\\`\\`\\``,\n \"g\",\n );\n const blocks: CodeBlock[] = [];\n\n let match;\n while ((match = regex.exec(content)) !== null) {\n const id = match[1] || undefined;\n const code = match[2].trim();\n blocks.push({ code, id });\n }\n\n return blocks;\n}\n\n/**\n * Parses a markdown file and extracts structured content\n */\nexport function parseMarkdownFile(content: string): ParsedMarkdown {\n // Extract title (first # heading)\n const titleMatch = content.match(/^#\\s+(.+)$/m);\n const title = titleMatch ? titleMatch[1].trim() : \"Untitled\";\n\n // Extract imports block\n const importsMatch = content.match(/```typescript @fragno-imports\\n([\\s\\S]*?)```/);\n const imports = importsMatch ? importsMatch[1].trim() : \"\";\n\n // Extract prelude blocks\n const prelude = extractCodeBlocks(content, \"prelude\");\n\n // Extract test-init blocks\n const testInit = extractCodeBlocks(content, \"test-init\");\n\n // Extract all test blocks with their explanations and optional IDs\n const testBlockRegex =\n /```typescript @fragno-test(?::(\\w+(?:-\\w+)*))?\\n([\\s\\S]*?)```([\\s\\S]*?)(?=```typescript @fragno-test|$)/g;\n const testBlocks: Array<{\n code: string;\n explanation: string;\n testName?: string;\n id?: string;\n }> = [];\n\n let match;\n while ((match = testBlockRegex.exec(content)) !== null) {\n const id = match[1] || undefined;\n const code = match[2].trim();\n\n // Extract test name from first line if it's a comment\n const lines = code.split(\"\\n\");\n let testName: string | undefined;\n if (lines[0]?.trim().startsWith(\"//\")) {\n testName = lines[0].replace(/^\\/\\/\\s*/, \"\").trim();\n }\n\n // Get explanation text after the code block until next code block or end\n const afterBlock = match[3];\n const explanation = afterBlock\n .split(/```/)[0] // Stop at next code block\n .trim();\n\n testBlocks.push({ code, explanation, testName, id });\n }\n\n // Extract description (everything between title and first code block or ## heading)\n const afterTitle = content.substring(content.indexOf(title) + title.length);\n const descriptionMatch = afterTitle.match(/\\n\\n([\\s\\S]*?)(?=```|##|$)/);\n const description = descriptionMatch ? descriptionMatch[1].trim() : \"\";\n\n // Extract all sections (## headings and their content)\n const sections: Section[] = [];\n const sectionRegex = /^##\\s+(.+)$/gm;\n const matches = [...content.matchAll(sectionRegex)];\n\n for (let i = 0; i < matches.length; i++) {\n const match = matches[i];\n const heading = match[1].trim();\n const sectionStart = match.index! + match[0].length;\n const nextSectionStart = matches[i + 1]?.index ?? content.length;\n let sectionContent = content.substring(sectionStart, nextSectionStart).trim();\n\n // Convert @fragno directive code blocks to regular typescript blocks for display\n sectionContent = sectionContent.replace(\n /```typescript @fragno-\\w+(?::\\w+(?:-\\w+)*)?/g,\n \"```typescript\",\n );\n sectionContent = sectionContent.trim();\n\n if (sectionContent) {\n sections.push({ heading, content: sectionContent });\n }\n }\n\n return {\n title,\n description,\n imports,\n prelude,\n testInit,\n testBlocks,\n sections,\n };\n}\n\n/**\n * Converts parsed markdown to a Subject\n */\nexport function markdownToSubject(id: string, parsed: ParsedMarkdown): Subject {\n const examples: Example[] = parsed.testBlocks.map((block) => ({\n code: block.code,\n explanation: block.explanation,\n testName: block.testName,\n id: block.id,\n }));\n\n return {\n id,\n title: parsed.title,\n description: parsed.description,\n imports: parsed.imports,\n prelude: parsed.prelude,\n testInit: parsed.testInit,\n examples,\n sections: parsed.sections,\n };\n}\n\n/**\n * Loads and parses a subject file by ID\n */\nexport function loadSubject(id: string): Subject {\n const filePath = join(SUBJECTS_DIR, `${id}.md`);\n const content = readFileSync(filePath, \"utf-8\");\n const parsed = parseMarkdownFile(content);\n return markdownToSubject(id, parsed);\n}\n\n/**\n * Gets all available subject IDs from the subjects directory\n */\nexport function getAvailableSubjectIds(): string[] {\n const files = readdirSync(SUBJECTS_DIR);\n return files.filter((file) => file.endsWith(\".md\")).map((file) => basename(file, \".md\"));\n}\n\n/**\n * Loads multiple subjects by their IDs\n */\nexport function loadSubjects(ids: string[]): Subject[] {\n return ids.map((id) => loadSubject(id));\n}\n\n/**\n * Loads all available subjects\n */\nexport function loadAllSubjects(): Subject[] {\n const ids = getAvailableSubjectIds();\n return loadSubjects(ids);\n}\n","/**\n * Subject tree structure defining relationships and ordering\n */\n\nexport interface SubjectNode {\n id: string;\n children?: string[];\n}\n\n/**\n * Tree structure defining subject hierarchy and ordering\n * - Root-level subjects are listed in order\n * - Children are indented under their parents\n */\nconst SUBJECT_TREE: SubjectNode[] = [\n { id: \"defining-routes\" },\n { id: \"database-querying\" },\n {\n id: \"database-adapters\",\n children: [\"kysely-adapter\", \"drizzle-adapter\"],\n },\n];\n\n/**\n * Flattened map of all subjects and their parent relationships\n */\nconst SUBJECT_PARENT_MAP = new Map<string, string | null>();\nconst SUBJECT_ORDER_MAP = new Map<string, number>();\n\n// Build the parent and order maps\nlet orderIndex = 0;\nfor (const node of SUBJECT_TREE) {\n SUBJECT_PARENT_MAP.set(node.id, null);\n SUBJECT_ORDER_MAP.set(node.id, orderIndex++);\n\n if (node.children) {\n for (const childId of node.children) {\n SUBJECT_PARENT_MAP.set(childId, node.id);\n SUBJECT_ORDER_MAP.set(childId, orderIndex++);\n }\n }\n}\n\n/**\n * Gets the parent of a subject, or null if it's a root subject\n */\nexport function getSubjectParent(subjectId: string): string | null {\n return SUBJECT_PARENT_MAP.get(subjectId) ?? null;\n}\n\n/**\n * Gets the children of a subject\n */\nexport function getSubjectChildren(subjectId: string): string[] {\n const node = SUBJECT_TREE.find((n) => n.id === subjectId);\n return node?.children ?? [];\n}\n\n/**\n * Orders an array of subject IDs according to the tree structure\n * This ensures deterministic ordering regardless of input order\n */\nexport function orderSubjects(subjectIds: string[]): string[] {\n return [...subjectIds].sort((a, b) => {\n const orderA = SUBJECT_ORDER_MAP.get(a) ?? Number.MAX_SAFE_INTEGER;\n const orderB = SUBJECT_ORDER_MAP.get(b) ?? Number.MAX_SAFE_INTEGER;\n return orderA - orderB;\n });\n}\n\n/**\n * Expands a subject ID to include its children if it has any\n * Useful for when a user requests a parent topic and wants to see all related content\n */\nexport function expandSubjectWithChildren(subjectId: string): string[] {\n const children = getSubjectChildren(subjectId);\n if (children.length > 0) {\n return [subjectId, ...children];\n }\n return [subjectId];\n}\n\n/**\n * Gets all subject IDs in tree order\n */\nexport function getAllSubjectIdsInOrder(): string[] {\n const ids: string[] = [];\n for (const node of SUBJECT_TREE) {\n ids.push(node.id);\n if (node.children) {\n ids.push(...node.children);\n }\n }\n return ids;\n}\n","import {\n getAvailableSubjectIds,\n loadSubject,\n loadSubjects,\n loadAllSubjects,\n type SubjectInfo,\n type Subject,\n} from \"./parser\";\nimport { orderSubjects } from \"./subject-tree\";\n\n/**\n * Get basic information about all available subjects\n * @returns Array of subject info (id and title)\n */\nexport function getSubjects(): SubjectInfo[] {\n const ids = getAvailableSubjectIds();\n return ids.map((id) => {\n const subject = loadSubject(id);\n return {\n id: subject.id,\n title: subject.title,\n };\n });\n}\n\n/**\n * Get one or more subjects by their IDs\n * @param ids Subject IDs to load\n * @returns Array of complete subject data ordered by the subject tree\n * @example\n * ```ts\n * // Get single subject\n * const [routes] = getSubject(\"defining-routes\");\n *\n * // Get multiple subjects for combined context\n * const [adapters, kysely] = getSubject(\"database-adapters\", \"kysely-adapter\");\n * ```\n */\nexport function getSubject(...ids: string[]): Subject[] {\n // Order subjects deterministically according to the tree structure\n const orderedIds = orderSubjects(ids);\n return loadSubjects(orderedIds);\n}\n\n/**\n * Get all available subjects\n * @returns Array of all subjects with complete data\n */\nexport function getAllSubjects(): Subject[] {\n return loadAllSubjects();\n}\n\n// Re-export types\nexport type { Subject, SubjectInfo, Example, Section, CodeBlock } from \"./parser.js\";\n\n// Re-export subject tree utilities\nexport {\n orderSubjects,\n getSubjectParent,\n getSubjectChildren,\n expandSubjectWithChildren,\n getAllSubjectIdsInOrder,\n} from \"./subject-tree.js\";\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["blocks: CodeBlock[]","testBlocks: Array<{\n code: string;\n explanation: string;\n testName?: string;\n id?: string;\n }>","testName: string | undefined","sections: Section[]","match","examples: Example[]","SUBJECT_TREE: SubjectNode[]","ids: string[]"],"sources":["../src/parser.ts","../src/subject-tree.ts","../src/index.ts"],"sourcesContent":["import { readFileSync, readdirSync } from \"node:fs\";\nimport { join, basename, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n/**\n * Basic information about a subject\n */\nexport interface SubjectInfo {\n id: string;\n title: string;\n}\n\n/**\n * A single example within a subject\n */\nexport interface Example {\n code: string;\n explanation: string;\n testName?: string;\n id?: string;\n}\n\n/**\n * A code block with optional ID\n */\nexport interface CodeBlock {\n code: string;\n id?: string;\n}\n\n/**\n * A markdown section with heading and content\n */\nexport interface Section {\n heading: string;\n content: string;\n lineNumber?: number;\n}\n\n/**\n * Complete subject with all examples and metadata\n */\nexport interface Subject {\n id: string;\n title: string;\n description: string;\n imports: string;\n prelude: CodeBlock[];\n testInit: CodeBlock[];\n examples: Example[];\n sections: Section[];\n}\n\n/**\n * Raw parsed data from markdown before processing\n */\nexport interface ParsedMarkdown {\n title: string;\n description: string;\n imports: string;\n prelude: CodeBlock[];\n testInit: CodeBlock[];\n testBlocks: Array<{\n code: string;\n explanation: string;\n testName?: string;\n id?: string;\n }>;\n sections: Section[];\n}\n\n// Look for subjects directory in source or relative to built dist\nconst SUBJECTS_DIR = (() => {\n // Try dist/../src/subjects (when running from built code)\n const distRelative = join(__dirname, \"..\", \"src\", \"subjects\");\n try {\n readdirSync(distRelative);\n return distRelative;\n } catch {\n // Fall back to ./subjects (when running from source)\n return join(__dirname, \"subjects\");\n }\n})();\n\n/**\n * Helper function to extract code blocks with optional IDs from a directive\n */\nfunction extractCodeBlocks(content: string, directive: string): CodeBlock[] {\n const regex = new RegExp(\n `\\`\\`\\`typescript @fragno-${directive}(?::(\\\\w+(?:-\\\\w+)*))?\\\\n([\\\\s\\\\S]*?)\\`\\`\\``,\n \"g\",\n );\n const blocks: CodeBlock[] = [];\n\n let match;\n while ((match = regex.exec(content)) !== null) {\n const id = match[1] || undefined;\n const code = match[2].trim();\n blocks.push({ code, id });\n }\n\n return blocks;\n}\n\n/**\n * Parses a markdown file and extracts structured content\n */\nexport function parseMarkdownFile(content: string): ParsedMarkdown {\n // Extract title (first # heading)\n const titleMatch = content.match(/^#\\s+(.+)$/m);\n const title = titleMatch ? titleMatch[1].trim() : \"Untitled\";\n\n // Extract imports block\n const importsMatch = content.match(/```typescript @fragno-imports\\n([\\s\\S]*?)```/);\n const imports = importsMatch ? importsMatch[1].trim() : \"\";\n\n // Extract prelude blocks\n const prelude = extractCodeBlocks(content, \"prelude\");\n\n // Extract test-init blocks\n const testInit = extractCodeBlocks(content, \"test-init\");\n\n // Extract all test blocks with their explanations and optional IDs\n const testBlockRegex =\n /```typescript @fragno-test(?::(\\w+(?:-\\w+)*))?\\n([\\s\\S]*?)```([\\s\\S]*?)(?=```typescript @fragno-test|$)/g;\n const testBlocks: Array<{\n code: string;\n explanation: string;\n testName?: string;\n id?: string;\n }> = [];\n\n let match;\n while ((match = testBlockRegex.exec(content)) !== null) {\n const id = match[1] || undefined;\n const code = match[2].trim();\n\n // Extract test name from first line if it's a comment\n const lines = code.split(\"\\n\");\n let testName: string | undefined;\n if (lines[0]?.trim().startsWith(\"//\")) {\n testName = lines[0].replace(/^\\/\\/\\s*/, \"\").trim();\n }\n\n // Get explanation text after the code block until next code block or end\n const afterBlock = match[3];\n const explanation = afterBlock\n .split(/```/)[0] // Stop at next code block\n .trim();\n\n testBlocks.push({ code, explanation, testName, id });\n }\n\n // Extract description (everything between title and first code block or ## heading)\n const afterTitle = content.substring(content.indexOf(title) + title.length);\n const descriptionMatch = afterTitle.match(/\\n\\n([\\s\\S]*?)(?=```|##|$)/);\n const description = descriptionMatch ? descriptionMatch[1].trim() : \"\";\n\n // Extract all sections (## headings and their content)\n const sections: Section[] = [];\n const sectionRegex = /^##\\s+(.+)$/gm;\n const matches = [...content.matchAll(sectionRegex)];\n\n for (let i = 0; i < matches.length; i++) {\n const match = matches[i];\n const heading = match[1].trim();\n const sectionStart = match.index! + match[0].length;\n const nextSectionStart = matches[i + 1]?.index ?? content.length;\n let sectionContent = content.substring(sectionStart, nextSectionStart).trim();\n\n // Convert @fragno directive code blocks to regular typescript blocks for display\n sectionContent = sectionContent.replace(\n /```typescript @fragno-\\w+(?::\\w+(?:-\\w+)*)?/g,\n \"```typescript\",\n );\n sectionContent = sectionContent.trim();\n\n if (sectionContent) {\n sections.push({ heading, content: sectionContent });\n }\n }\n\n return {\n title,\n description,\n imports,\n prelude,\n testInit,\n testBlocks,\n sections,\n };\n}\n\n/**\n * Converts parsed markdown to a Subject\n */\nexport function markdownToSubject(id: string, parsed: ParsedMarkdown): Subject {\n const examples: Example[] = parsed.testBlocks.map((block) => ({\n code: block.code,\n explanation: block.explanation,\n testName: block.testName,\n id: block.id,\n }));\n\n return {\n id,\n title: parsed.title,\n description: parsed.description,\n imports: parsed.imports,\n prelude: parsed.prelude,\n testInit: parsed.testInit,\n examples,\n sections: parsed.sections,\n };\n}\n\n/**\n * Loads and parses a subject file by ID\n */\nexport function loadSubject(id: string): Subject {\n const filePath = join(SUBJECTS_DIR, `${id}.md`);\n const content = readFileSync(filePath, \"utf-8\");\n const parsed = parseMarkdownFile(content);\n return markdownToSubject(id, parsed);\n}\n\n/**\n * Gets all available subject IDs from the subjects directory\n */\nexport function getAvailableSubjectIds(): string[] {\n const files = readdirSync(SUBJECTS_DIR);\n return files.filter((file) => file.endsWith(\".md\")).map((file) => basename(file, \".md\"));\n}\n\n/**\n * Loads multiple subjects by their IDs\n */\nexport function loadSubjects(ids: string[]): Subject[] {\n return ids.map((id) => loadSubject(id));\n}\n\n/**\n * Loads all available subjects\n */\nexport function loadAllSubjects(): Subject[] {\n const ids = getAvailableSubjectIds();\n return loadSubjects(ids);\n}\n","/**\n * Subject tree structure defining relationships and ordering\n */\n\nexport interface SubjectNode {\n id: string;\n children?: string[];\n}\n\n/**\n * Tree structure defining subject hierarchy and ordering\n * - Root-level subjects are listed in order\n * - Children are indented under their parents\n */\nconst SUBJECT_TREE: SubjectNode[] = [\n { id: \"defining-routes\" },\n { id: \"fragment-services\" },\n { id: \"fragment-instantiation\" },\n { id: \"database-querying\" },\n {\n id: \"database-adapters\",\n children: [\"kysely-adapter\", \"drizzle-adapter\"],\n },\n];\n\n/**\n * Flattened map of all subjects and their parent relationships\n */\nconst SUBJECT_PARENT_MAP = new Map<string, string | null>();\nconst SUBJECT_ORDER_MAP = new Map<string, number>();\n\n// Build the parent and order maps\nlet orderIndex = 0;\nfor (const node of SUBJECT_TREE) {\n SUBJECT_PARENT_MAP.set(node.id, null);\n SUBJECT_ORDER_MAP.set(node.id, orderIndex++);\n\n if (node.children) {\n for (const childId of node.children) {\n SUBJECT_PARENT_MAP.set(childId, node.id);\n SUBJECT_ORDER_MAP.set(childId, orderIndex++);\n }\n }\n}\n\n/**\n * Gets the parent of a subject, or null if it's a root subject\n */\nexport function getSubjectParent(subjectId: string): string | null {\n return SUBJECT_PARENT_MAP.get(subjectId) ?? null;\n}\n\n/**\n * Gets the children of a subject\n */\nexport function getSubjectChildren(subjectId: string): string[] {\n const node = SUBJECT_TREE.find((n) => n.id === subjectId);\n return node?.children ?? [];\n}\n\n/**\n * Orders an array of subject IDs according to the tree structure\n * This ensures deterministic ordering regardless of input order\n */\nexport function orderSubjects(subjectIds: string[]): string[] {\n return [...subjectIds].sort((a, b) => {\n const orderA = SUBJECT_ORDER_MAP.get(a) ?? Number.MAX_SAFE_INTEGER;\n const orderB = SUBJECT_ORDER_MAP.get(b) ?? Number.MAX_SAFE_INTEGER;\n return orderA - orderB;\n });\n}\n\n/**\n * Expands a subject ID to include its children if it has any\n * Useful for when a user requests a parent topic and wants to see all related content\n */\nexport function expandSubjectWithChildren(subjectId: string): string[] {\n const children = getSubjectChildren(subjectId);\n if (children.length > 0) {\n return [subjectId, ...children];\n }\n return [subjectId];\n}\n\n/**\n * Gets all subject IDs in tree order\n */\nexport function getAllSubjectIdsInOrder(): string[] {\n const ids: string[] = [];\n for (const node of SUBJECT_TREE) {\n ids.push(node.id);\n if (node.children) {\n ids.push(...node.children);\n }\n }\n return ids;\n}\n","import {\n getAvailableSubjectIds,\n loadSubject,\n loadSubjects,\n loadAllSubjects,\n type SubjectInfo,\n type Subject,\n} from \"./parser\";\nimport { orderSubjects } from \"./subject-tree\";\n\n/**\n * Get basic information about all available subjects\n * @returns Array of subject info (id and title)\n */\nexport function getSubjects(): SubjectInfo[] {\n const ids = getAvailableSubjectIds();\n return ids.map((id) => {\n const subject = loadSubject(id);\n return {\n id: subject.id,\n title: subject.title,\n };\n });\n}\n\n/**\n * Get one or more subjects by their IDs\n * @param ids Subject IDs to load\n * @returns Array of complete subject data ordered by the subject tree\n * @example\n * ```ts\n * // Get single subject\n * const [routes] = getSubject(\"defining-routes\");\n *\n * // Get multiple subjects for combined context\n * const [adapters, kysely] = getSubject(\"database-adapters\", \"kysely-adapter\");\n * ```\n */\nexport function getSubject(...ids: string[]): Subject[] {\n // Order subjects deterministically according to the tree structure\n const orderedIds = orderSubjects(ids);\n return loadSubjects(orderedIds);\n}\n\n/**\n * Get all available subjects\n * @returns Array of all subjects with complete data\n */\nexport function getAllSubjects(): Subject[] {\n return loadAllSubjects();\n}\n\n// Re-export types\nexport type { Subject, SubjectInfo, Example, Section, CodeBlock } from \"./parser.js\";\n\n// Re-export subject tree utilities\nexport {\n orderSubjects,\n getSubjectParent,\n getSubjectChildren,\n expandSubjectWithChildren,\n getAllSubjectIdsInOrder,\n} from \"./subject-tree.js\";\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fragno-dev/corpus",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
"drizzle-orm": "^0.44.7",
|
|
22
22
|
"kysely": "^0.28.0",
|
|
23
23
|
"zod": "^4.0.5",
|
|
24
|
-
"@fragno-dev/core": "0.1.
|
|
25
|
-
"@fragno-dev/db": "0.1.
|
|
26
|
-
"@fragno-dev/test": "0.1.
|
|
24
|
+
"@fragno-dev/core": "0.1.8",
|
|
25
|
+
"@fragno-dev/db": "0.1.14",
|
|
26
|
+
"@fragno-dev/test": "0.1.12",
|
|
27
27
|
"@fragno-private/typescript-config": "0.0.1",
|
|
28
28
|
"@fragno-private/vitest-config": "0.0.0"
|
|
29
29
|
},
|