@fragno-dev/corpus 0.0.2 → 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/README.md +57 -22
- package/dist/index.d.ts +98 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +247 -0
- package/dist/index.js.map +1 -0
- package/package.json +10 -5
- package/.turbo/turbo-build.log +0 -15
- package/CHANGELOG.md +0 -7
- package/src/index.test.ts +0 -107
- package/src/index.ts +0 -51
- package/src/parser.test.ts +0 -115
- package/src/parser.ts +0 -182
- package/src/subjects/database-adapters.md +0 -68
- package/src/subjects/database-querying.md +0 -227
- package/src/subjects/defining-routes.md +0 -272
- package/src/subjects/drizzle-adapter.md +0 -60
- package/src/subjects/kysely-adapter.md +0 -59
- package/src/test-setup.ts +0 -110
- package/tsconfig.json +0 -10
- package/tsdown.config.ts +0 -7
- package/vitest.config.ts +0 -13
package/README.md
CHANGED
|
@@ -21,9 +21,9 @@ import { getSubjects, getSubject, getAllSubjects } from "@fragno-dev/corpus";
|
|
|
21
21
|
const subjects = getSubjects();
|
|
22
22
|
// [{ id: "defining-routes", title: "Defining Routes" }, ...]
|
|
23
23
|
|
|
24
|
-
// Get one or more subjects
|
|
24
|
+
// Get one or more subjects (deterministically ordered by subject tree)
|
|
25
25
|
const [routes] = getSubject("defining-routes");
|
|
26
|
-
// { id, title, description, imports,
|
|
26
|
+
// { id, title, description, imports, prelude, testInit, examples, sections }
|
|
27
27
|
|
|
28
28
|
// Get multiple subjects for combined context
|
|
29
29
|
const [adapters, kysely] = getSubject("database-adapters", "kysely-adapter");
|
|
@@ -40,26 +40,34 @@ Required block at the top with all imports:
|
|
|
40
40
|
\`\`\`typescript @fragno-imports import { defineRoute } from "@fragno-dev/core"; import { z } from
|
|
41
41
|
"zod"; \`\`\`
|
|
42
42
|
|
|
43
|
-
### @fragno-
|
|
43
|
+
### @fragno-prelude (optional)
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
Setup code shown to users reading the corpus (e.g., schema definitions, configuration):
|
|
46
46
|
|
|
47
|
-
\`\`\`typescript @fragno-
|
|
47
|
+
\`\`\`typescript @fragno-prelude:schema const userSchema = schema((s) => { return
|
|
48
|
+
s.addTable("users", (t) => { return t.addColumn("id", idColumn()); }); }); \`\`\`
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
Optional ID syntax (`:schema`) helps identify code blocks for agent references.
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
### @fragno-test-init (optional)
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
method: "GET", path: "/hello", outputSchema: z.string(), handler: async (\_, { json }) =>
|
|
55
|
-
json("Hello"), });
|
|
54
|
+
Test-only initialization code (not shown to users, only used in generated tests):
|
|
56
55
|
|
|
57
|
-
|
|
56
|
+
\`\`\`typescript @fragno-test-init const { fragment } = await
|
|
57
|
+
createDatabaseFragmentForTest(testFragmentDef, []); const orm = fragment.services.orm; \`\`\`
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
### @fragno-test
|
|
60
|
+
|
|
61
|
+
Runnable test code with optional IDs:
|
|
62
|
+
|
|
63
|
+
\`\`\`typescript @fragno-test:create-user // should create a single user const userId = await
|
|
64
|
+
orm.create("users", { id: "user-123", email: "john@example.com", });
|
|
65
|
+
|
|
66
|
+
expect(userId).toBeDefined(); \`\`\`
|
|
67
|
+
|
|
68
|
+
- Optional ID syntax (`:create-user`) helps agents reference specific examples
|
|
62
69
|
- First comment line becomes the test name
|
|
70
|
+
- IDs are optional - tests generate properly without them
|
|
63
71
|
|
|
64
72
|
Between code blocks, add markdown explanations.
|
|
65
73
|
|
|
@@ -67,26 +75,53 @@ Between code blocks, add markdown explanations.
|
|
|
67
75
|
|
|
68
76
|
1. Create `src/subjects/your-subject.md`
|
|
69
77
|
2. Add `@fragno-imports` block at top
|
|
70
|
-
3. Add optional `@fragno-
|
|
71
|
-
4. Add
|
|
72
|
-
5.
|
|
73
|
-
6.
|
|
78
|
+
3. Add optional `@fragno-prelude` for user-visible setup code
|
|
79
|
+
4. Add optional `@fragno-test-init` for test-only initialization
|
|
80
|
+
5. Add multiple `@fragno-test` blocks with examples (optional IDs)
|
|
81
|
+
6. Write explanations between code blocks
|
|
82
|
+
7. Update `src/subject-tree.ts` to add subject to the tree structure
|
|
83
|
+
8. Run `pnpm test` to validate
|
|
74
84
|
|
|
75
85
|
## Testing
|
|
76
86
|
|
|
77
87
|
Tests use vitest `globalSetup` to:
|
|
78
88
|
|
|
79
89
|
1. Parse all markdown files
|
|
80
|
-
2. Extract `@fragno-
|
|
90
|
+
2. Extract `@fragno-prelude` (user-visible setup), `@fragno-test-init` (test-only), and
|
|
91
|
+
`@fragno-test` blocks
|
|
81
92
|
3. Generate temporary `.test.ts` files with actual vitest test cases
|
|
82
93
|
4. Type-check and execute them
|
|
83
94
|
|
|
84
|
-
Each
|
|
85
|
-
|
|
95
|
+
Each subject's test file includes:
|
|
96
|
+
|
|
97
|
+
- Imports from `@fragno-imports`
|
|
98
|
+
- Setup from `@fragno-prelude` (shown to users)
|
|
99
|
+
- Initialization from `@fragno-test-init` (test-only)
|
|
100
|
+
- Test cases from `@fragno-test` blocks
|
|
101
|
+
|
|
102
|
+
Test names come from the first comment line in each `@fragno-test` block.
|
|
86
103
|
|
|
87
104
|
Run tests: `pnpm test`
|
|
88
105
|
|
|
89
|
-
Generated tests are in
|
|
106
|
+
Generated tests are in `corpus-tests/` (temporary directory).
|
|
107
|
+
|
|
108
|
+
## CLI Integration
|
|
109
|
+
|
|
110
|
+
The corpus can be viewed via the Fragno CLI with various options:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# View a subject
|
|
114
|
+
fragno-cli corpus database-querying
|
|
115
|
+
|
|
116
|
+
# Show only headings and code block IDs with line numbers
|
|
117
|
+
fragno-cli corpus --headings database-querying
|
|
118
|
+
|
|
119
|
+
# Show specific line range with line numbers
|
|
120
|
+
fragno-cli corpus --line-numbers --start 1 --end 50 database-querying
|
|
121
|
+
|
|
122
|
+
# Multiple subjects (automatically ordered by subject tree)
|
|
123
|
+
fragno-cli corpus database-adapters kysely-adapter
|
|
124
|
+
```
|
|
90
125
|
|
|
91
126
|
## TODO: Future Subjects
|
|
92
127
|
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
//#region src/parser.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Basic information about a subject
|
|
4
|
+
*/
|
|
5
|
+
interface SubjectInfo {
|
|
6
|
+
id: string;
|
|
7
|
+
title: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A single example within a subject
|
|
11
|
+
*/
|
|
12
|
+
interface Example {
|
|
13
|
+
code: string;
|
|
14
|
+
explanation: string;
|
|
15
|
+
testName?: string;
|
|
16
|
+
id?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A code block with optional ID
|
|
20
|
+
*/
|
|
21
|
+
interface CodeBlock {
|
|
22
|
+
code: string;
|
|
23
|
+
id?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A markdown section with heading and content
|
|
27
|
+
*/
|
|
28
|
+
interface Section {
|
|
29
|
+
heading: string;
|
|
30
|
+
content: string;
|
|
31
|
+
lineNumber?: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Complete subject with all examples and metadata
|
|
35
|
+
*/
|
|
36
|
+
interface Subject {
|
|
37
|
+
id: string;
|
|
38
|
+
title: string;
|
|
39
|
+
description: string;
|
|
40
|
+
imports: string;
|
|
41
|
+
prelude: CodeBlock[];
|
|
42
|
+
testInit: CodeBlock[];
|
|
43
|
+
examples: Example[];
|
|
44
|
+
sections: Section[];
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/subject-tree.d.ts
|
|
48
|
+
/**
|
|
49
|
+
* Gets the parent of a subject, or null if it's a root subject
|
|
50
|
+
*/
|
|
51
|
+
declare function getSubjectParent(subjectId: string): string | null;
|
|
52
|
+
/**
|
|
53
|
+
* Gets the children of a subject
|
|
54
|
+
*/
|
|
55
|
+
declare function getSubjectChildren(subjectId: string): string[];
|
|
56
|
+
/**
|
|
57
|
+
* Orders an array of subject IDs according to the tree structure
|
|
58
|
+
* This ensures deterministic ordering regardless of input order
|
|
59
|
+
*/
|
|
60
|
+
declare function orderSubjects(subjectIds: string[]): string[];
|
|
61
|
+
/**
|
|
62
|
+
* Expands a subject ID to include its children if it has any
|
|
63
|
+
* Useful for when a user requests a parent topic and wants to see all related content
|
|
64
|
+
*/
|
|
65
|
+
declare function expandSubjectWithChildren(subjectId: string): string[];
|
|
66
|
+
/**
|
|
67
|
+
* Gets all subject IDs in tree order
|
|
68
|
+
*/
|
|
69
|
+
declare function getAllSubjectIdsInOrder(): string[];
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/index.d.ts
|
|
72
|
+
/**
|
|
73
|
+
* Get basic information about all available subjects
|
|
74
|
+
* @returns Array of subject info (id and title)
|
|
75
|
+
*/
|
|
76
|
+
declare function getSubjects(): SubjectInfo[];
|
|
77
|
+
/**
|
|
78
|
+
* Get one or more subjects by their IDs
|
|
79
|
+
* @param ids Subject IDs to load
|
|
80
|
+
* @returns Array of complete subject data ordered by the subject tree
|
|
81
|
+
* @example
|
|
82
|
+
* ```ts
|
|
83
|
+
* // Get single subject
|
|
84
|
+
* const [routes] = getSubject("defining-routes");
|
|
85
|
+
*
|
|
86
|
+
* // Get multiple subjects for combined context
|
|
87
|
+
* const [adapters, kysely] = getSubject("database-adapters", "kysely-adapter");
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
declare function getSubject(...ids: string[]): Subject[];
|
|
91
|
+
/**
|
|
92
|
+
* Get all available subjects
|
|
93
|
+
* @returns Array of all subjects with complete data
|
|
94
|
+
*/
|
|
95
|
+
declare function getAllSubjects(): Subject[];
|
|
96
|
+
//#endregion
|
|
97
|
+
export { type CodeBlock, type Example, type Section, type Subject, type SubjectInfo, expandSubjectWithChildren, getAllSubjectIdsInOrder, getAllSubjects, getSubject, getSubjectChildren, getSubjectParent, getSubjects, orderSubjects };
|
|
98
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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;;;;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
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
//#region src/parser.ts
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const SUBJECTS_DIR = (() => {
|
|
8
|
+
const distRelative = join(__dirname, "..", "src", "subjects");
|
|
9
|
+
try {
|
|
10
|
+
readdirSync(distRelative);
|
|
11
|
+
return distRelative;
|
|
12
|
+
} catch {
|
|
13
|
+
return join(__dirname, "subjects");
|
|
14
|
+
}
|
|
15
|
+
})();
|
|
16
|
+
/**
|
|
17
|
+
* Helper function to extract code blocks with optional IDs from a directive
|
|
18
|
+
*/
|
|
19
|
+
function extractCodeBlocks(content, directive) {
|
|
20
|
+
const regex = new RegExp(`\`\`\`typescript @fragno-${directive}(?::(\\w+(?:-\\w+)*))?\\n([\\s\\S]*?)\`\`\``, "g");
|
|
21
|
+
const blocks = [];
|
|
22
|
+
let match;
|
|
23
|
+
while ((match = regex.exec(content)) !== null) {
|
|
24
|
+
const id = match[1] || void 0;
|
|
25
|
+
const code = match[2].trim();
|
|
26
|
+
blocks.push({
|
|
27
|
+
code,
|
|
28
|
+
id
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return blocks;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Parses a markdown file and extracts structured content
|
|
35
|
+
*/
|
|
36
|
+
function parseMarkdownFile(content) {
|
|
37
|
+
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
38
|
+
const title = titleMatch ? titleMatch[1].trim() : "Untitled";
|
|
39
|
+
const importsMatch = content.match(/```typescript @fragno-imports\n([\s\S]*?)```/);
|
|
40
|
+
const imports = importsMatch ? importsMatch[1].trim() : "";
|
|
41
|
+
const prelude = extractCodeBlocks(content, "prelude");
|
|
42
|
+
const testInit = extractCodeBlocks(content, "test-init");
|
|
43
|
+
const testBlockRegex = /```typescript @fragno-test(?::(\w+(?:-\w+)*))?\n([\s\S]*?)```([\s\S]*?)(?=```typescript @fragno-test|$)/g;
|
|
44
|
+
const testBlocks = [];
|
|
45
|
+
let match;
|
|
46
|
+
while ((match = testBlockRegex.exec(content)) !== null) {
|
|
47
|
+
const id = match[1] || void 0;
|
|
48
|
+
const code = match[2].trim();
|
|
49
|
+
const lines = code.split("\n");
|
|
50
|
+
let testName;
|
|
51
|
+
if (lines[0]?.trim().startsWith("//")) testName = lines[0].replace(/^\/\/\s*/, "").trim();
|
|
52
|
+
const explanation = match[3].split(/```/)[0].trim();
|
|
53
|
+
testBlocks.push({
|
|
54
|
+
code,
|
|
55
|
+
explanation,
|
|
56
|
+
testName,
|
|
57
|
+
id
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const descriptionMatch = content.substring(content.indexOf(title) + title.length).match(/\n\n([\s\S]*?)(?=```|##|$)/);
|
|
61
|
+
const description = descriptionMatch ? descriptionMatch[1].trim() : "";
|
|
62
|
+
const sections = [];
|
|
63
|
+
const matches = [...content.matchAll(/^##\s+(.+)$/gm)];
|
|
64
|
+
for (let i = 0; i < matches.length; i++) {
|
|
65
|
+
const match$1 = matches[i];
|
|
66
|
+
const heading = match$1[1].trim();
|
|
67
|
+
const sectionStart = match$1.index + match$1[0].length;
|
|
68
|
+
const nextSectionStart = matches[i + 1]?.index ?? content.length;
|
|
69
|
+
let sectionContent = content.substring(sectionStart, nextSectionStart).trim();
|
|
70
|
+
sectionContent = sectionContent.replace(/```typescript @fragno-\w+(?::\w+(?:-\w+)*)?/g, "```typescript");
|
|
71
|
+
sectionContent = sectionContent.trim();
|
|
72
|
+
if (sectionContent) sections.push({
|
|
73
|
+
heading,
|
|
74
|
+
content: sectionContent
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
title,
|
|
79
|
+
description,
|
|
80
|
+
imports,
|
|
81
|
+
prelude,
|
|
82
|
+
testInit,
|
|
83
|
+
testBlocks,
|
|
84
|
+
sections
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Converts parsed markdown to a Subject
|
|
89
|
+
*/
|
|
90
|
+
function markdownToSubject(id, parsed) {
|
|
91
|
+
const examples = parsed.testBlocks.map((block) => ({
|
|
92
|
+
code: block.code,
|
|
93
|
+
explanation: block.explanation,
|
|
94
|
+
testName: block.testName,
|
|
95
|
+
id: block.id
|
|
96
|
+
}));
|
|
97
|
+
return {
|
|
98
|
+
id,
|
|
99
|
+
title: parsed.title,
|
|
100
|
+
description: parsed.description,
|
|
101
|
+
imports: parsed.imports,
|
|
102
|
+
prelude: parsed.prelude,
|
|
103
|
+
testInit: parsed.testInit,
|
|
104
|
+
examples,
|
|
105
|
+
sections: parsed.sections
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Loads and parses a subject file by ID
|
|
110
|
+
*/
|
|
111
|
+
function loadSubject(id) {
|
|
112
|
+
return markdownToSubject(id, parseMarkdownFile(readFileSync(join(SUBJECTS_DIR, `${id}.md`), "utf-8")));
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Gets all available subject IDs from the subjects directory
|
|
116
|
+
*/
|
|
117
|
+
function getAvailableSubjectIds() {
|
|
118
|
+
return readdirSync(SUBJECTS_DIR).filter((file) => file.endsWith(".md")).map((file) => basename(file, ".md"));
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Loads multiple subjects by their IDs
|
|
122
|
+
*/
|
|
123
|
+
function loadSubjects(ids) {
|
|
124
|
+
return ids.map((id) => loadSubject(id));
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Loads all available subjects
|
|
128
|
+
*/
|
|
129
|
+
function loadAllSubjects() {
|
|
130
|
+
return loadSubjects(getAvailableSubjectIds());
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/subject-tree.ts
|
|
135
|
+
/**
|
|
136
|
+
* Tree structure defining subject hierarchy and ordering
|
|
137
|
+
* - Root-level subjects are listed in order
|
|
138
|
+
* - Children are indented under their parents
|
|
139
|
+
*/
|
|
140
|
+
const SUBJECT_TREE = [
|
|
141
|
+
{ id: "defining-routes" },
|
|
142
|
+
{ id: "fragment-services" },
|
|
143
|
+
{ id: "fragment-instantiation" },
|
|
144
|
+
{ id: "database-querying" },
|
|
145
|
+
{
|
|
146
|
+
id: "database-adapters",
|
|
147
|
+
children: ["kysely-adapter", "drizzle-adapter"]
|
|
148
|
+
}
|
|
149
|
+
];
|
|
150
|
+
/**
|
|
151
|
+
* Flattened map of all subjects and their parent relationships
|
|
152
|
+
*/
|
|
153
|
+
const SUBJECT_PARENT_MAP = /* @__PURE__ */ new Map();
|
|
154
|
+
const SUBJECT_ORDER_MAP = /* @__PURE__ */ new Map();
|
|
155
|
+
let orderIndex = 0;
|
|
156
|
+
for (const node of SUBJECT_TREE) {
|
|
157
|
+
SUBJECT_PARENT_MAP.set(node.id, null);
|
|
158
|
+
SUBJECT_ORDER_MAP.set(node.id, orderIndex++);
|
|
159
|
+
if (node.children) for (const childId of node.children) {
|
|
160
|
+
SUBJECT_PARENT_MAP.set(childId, node.id);
|
|
161
|
+
SUBJECT_ORDER_MAP.set(childId, orderIndex++);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Gets the parent of a subject, or null if it's a root subject
|
|
166
|
+
*/
|
|
167
|
+
function getSubjectParent(subjectId) {
|
|
168
|
+
return SUBJECT_PARENT_MAP.get(subjectId) ?? null;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Gets the children of a subject
|
|
172
|
+
*/
|
|
173
|
+
function getSubjectChildren(subjectId) {
|
|
174
|
+
return SUBJECT_TREE.find((n) => n.id === subjectId)?.children ?? [];
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Orders an array of subject IDs according to the tree structure
|
|
178
|
+
* This ensures deterministic ordering regardless of input order
|
|
179
|
+
*/
|
|
180
|
+
function orderSubjects(subjectIds) {
|
|
181
|
+
return [...subjectIds].sort((a, b) => {
|
|
182
|
+
return (SUBJECT_ORDER_MAP.get(a) ?? Number.MAX_SAFE_INTEGER) - (SUBJECT_ORDER_MAP.get(b) ?? Number.MAX_SAFE_INTEGER);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Expands a subject ID to include its children if it has any
|
|
187
|
+
* Useful for when a user requests a parent topic and wants to see all related content
|
|
188
|
+
*/
|
|
189
|
+
function expandSubjectWithChildren(subjectId) {
|
|
190
|
+
const children = getSubjectChildren(subjectId);
|
|
191
|
+
if (children.length > 0) return [subjectId, ...children];
|
|
192
|
+
return [subjectId];
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Gets all subject IDs in tree order
|
|
196
|
+
*/
|
|
197
|
+
function getAllSubjectIdsInOrder() {
|
|
198
|
+
const ids = [];
|
|
199
|
+
for (const node of SUBJECT_TREE) {
|
|
200
|
+
ids.push(node.id);
|
|
201
|
+
if (node.children) ids.push(...node.children);
|
|
202
|
+
}
|
|
203
|
+
return ids;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
//#endregion
|
|
207
|
+
//#region src/index.ts
|
|
208
|
+
/**
|
|
209
|
+
* Get basic information about all available subjects
|
|
210
|
+
* @returns Array of subject info (id and title)
|
|
211
|
+
*/
|
|
212
|
+
function getSubjects() {
|
|
213
|
+
return getAvailableSubjectIds().map((id) => {
|
|
214
|
+
const subject = loadSubject(id);
|
|
215
|
+
return {
|
|
216
|
+
id: subject.id,
|
|
217
|
+
title: subject.title
|
|
218
|
+
};
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Get one or more subjects by their IDs
|
|
223
|
+
* @param ids Subject IDs to load
|
|
224
|
+
* @returns Array of complete subject data ordered by the subject tree
|
|
225
|
+
* @example
|
|
226
|
+
* ```ts
|
|
227
|
+
* // Get single subject
|
|
228
|
+
* const [routes] = getSubject("defining-routes");
|
|
229
|
+
*
|
|
230
|
+
* // Get multiple subjects for combined context
|
|
231
|
+
* const [adapters, kysely] = getSubject("database-adapters", "kysely-adapter");
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
function getSubject(...ids) {
|
|
235
|
+
return loadSubjects(orderSubjects(ids));
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Get all available subjects
|
|
239
|
+
* @returns Array of all subjects with complete data
|
|
240
|
+
*/
|
|
241
|
+
function getAllSubjects() {
|
|
242
|
+
return loadAllSubjects();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
//#endregion
|
|
246
|
+
export { expandSubjectWithChildren, getAllSubjectIdsInOrder, getAllSubjects, getSubject, getSubjectChildren, getSubjectParent, getSubjects, orderSubjects };
|
|
247
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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: \"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",
|
|
@@ -12,15 +12,20 @@
|
|
|
12
12
|
"default": "./dist/index.js"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
15
18
|
"devDependencies": {
|
|
16
|
-
"
|
|
19
|
+
"@types/marked-terminal": "^6.1.1",
|
|
17
20
|
"@types/node": "^22",
|
|
18
21
|
"drizzle-orm": "^0.44.7",
|
|
19
22
|
"kysely": "^0.28.0",
|
|
23
|
+
"zod": "^4.0.5",
|
|
24
|
+
"@fragno-dev/core": "0.1.8",
|
|
25
|
+
"@fragno-dev/db": "0.1.14",
|
|
26
|
+
"@fragno-dev/test": "0.1.12",
|
|
20
27
|
"@fragno-private/typescript-config": "0.0.1",
|
|
21
|
-
"@fragno-private/vitest-config": "0.0.0"
|
|
22
|
-
"@fragno-dev/core": "0.1.6",
|
|
23
|
-
"@fragno-dev/db": "0.1.12"
|
|
28
|
+
"@fragno-private/vitest-config": "0.0.0"
|
|
24
29
|
},
|
|
25
30
|
"scripts": {
|
|
26
31
|
"build": "tsdown",
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> @fragno-dev/corpus@0.0.2 build /home/runner/work/fragno/fragno/packages/corpus
|
|
3
|
-
> tsdown
|
|
4
|
-
|
|
5
|
-
[34mℹ[39m tsdown [2mv0.15.12[22m powered by rolldown [2mv1.0.0-beta.45[22m
|
|
6
|
-
[34mℹ[39m Using tsdown config: [4m/home/runner/work/fragno/fragno/packages/corpus/tsdown.config.ts[24m
|
|
7
|
-
[34mℹ[39m entry: [34msrc/index.ts[39m
|
|
8
|
-
[34mℹ[39m tsconfig: [34mtsconfig.json[39m
|
|
9
|
-
[34mℹ[39m Build start
|
|
10
|
-
[34mℹ[39m [2mdist/[22m[1mindex.js[22m [2m3.84 kB[22m [2m│ gzip: 1.42 kB[22m
|
|
11
|
-
[34mℹ[39m [2mdist/[22mindex.js.map [2m8.62 kB[22m [2m│ gzip: 2.81 kB[22m
|
|
12
|
-
[34mℹ[39m [2mdist/[22mindex.d.ts.map [2m0.34 kB[22m [2m│ gzip: 0.22 kB[22m
|
|
13
|
-
[34mℹ[39m [2mdist/[22m[32m[1mindex.d.ts[22m[39m [2m1.39 kB[22m [2m│ gzip: 0.57 kB[22m
|
|
14
|
-
[34mℹ[39m 4 files, total: 14.18 kB
|
|
15
|
-
[32m✔[39m Build complete in [32m7620ms[39m
|
package/CHANGELOG.md
DELETED
package/src/index.test.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { getSubjects, getSubject, getAllSubjects } from "./index.js";
|
|
3
|
-
|
|
4
|
-
describe("corpus API", () => {
|
|
5
|
-
describe("getSubjects", () => {
|
|
6
|
-
it("should return array of subject info", () => {
|
|
7
|
-
const subjects = getSubjects();
|
|
8
|
-
|
|
9
|
-
expect(subjects).toBeInstanceOf(Array);
|
|
10
|
-
expect(subjects.length).toBeGreaterThan(0);
|
|
11
|
-
|
|
12
|
-
// Check structure of first subject
|
|
13
|
-
const first = subjects[0];
|
|
14
|
-
expect(first).toHaveProperty("id");
|
|
15
|
-
expect(first).toHaveProperty("title");
|
|
16
|
-
expect(typeof first.id).toBe("string");
|
|
17
|
-
expect(typeof first.title).toBe("string");
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it("should include expected subjects", () => {
|
|
21
|
-
const subjects = getSubjects();
|
|
22
|
-
const ids = subjects.map((s) => s.id);
|
|
23
|
-
|
|
24
|
-
expect(ids).toContain("defining-routes");
|
|
25
|
-
expect(ids).toContain("database-querying");
|
|
26
|
-
expect(ids).toContain("database-adapters");
|
|
27
|
-
expect(ids).toContain("kysely-adapter");
|
|
28
|
-
expect(ids).toContain("drizzle-adapter");
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
describe("getSubject", () => {
|
|
33
|
-
it("should return single subject when given one id", () => {
|
|
34
|
-
const [subject] = getSubject("defining-routes");
|
|
35
|
-
|
|
36
|
-
expect(subject).toBeDefined();
|
|
37
|
-
expect(subject.id).toBe("defining-routes");
|
|
38
|
-
expect(subject.title).toBeTruthy();
|
|
39
|
-
expect(subject.description).toBeTruthy();
|
|
40
|
-
expect(subject.imports).toBeTruthy();
|
|
41
|
-
expect(subject.examples).toBeInstanceOf(Array);
|
|
42
|
-
expect(subject.examples.length).toBeGreaterThan(0);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("should return multiple subjects when given multiple ids", () => {
|
|
46
|
-
const subjects = getSubject("database-adapters", "kysely-adapter");
|
|
47
|
-
|
|
48
|
-
expect(subjects).toHaveLength(2);
|
|
49
|
-
expect(subjects[0].id).toBe("database-adapters");
|
|
50
|
-
expect(subjects[1].id).toBe("kysely-adapter");
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("should include examples with code and explanation", () => {
|
|
54
|
-
const [subject] = getSubject("defining-routes");
|
|
55
|
-
|
|
56
|
-
const example = subject.examples[0];
|
|
57
|
-
expect(example).toHaveProperty("code");
|
|
58
|
-
expect(example).toHaveProperty("explanation");
|
|
59
|
-
expect(typeof example.code).toBe("string");
|
|
60
|
-
expect(typeof example.explanation).toBe("string");
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe("getAllSubjects", () => {
|
|
65
|
-
it("should return all available subjects", () => {
|
|
66
|
-
const allSubjects = getAllSubjects();
|
|
67
|
-
const subjectsList = getSubjects();
|
|
68
|
-
|
|
69
|
-
expect(allSubjects).toHaveLength(subjectsList.length);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("should return complete subject data", () => {
|
|
73
|
-
const allSubjects = getAllSubjects();
|
|
74
|
-
|
|
75
|
-
for (const subject of allSubjects) {
|
|
76
|
-
expect(subject.id).toBeTruthy();
|
|
77
|
-
expect(subject.title).toBeTruthy();
|
|
78
|
-
expect(subject.imports).toBeDefined(); // Can be empty string
|
|
79
|
-
expect(subject.examples).toBeInstanceOf(Array);
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
describe("subject content validation", () => {
|
|
85
|
-
it("defining-routes should have multiple examples", () => {
|
|
86
|
-
const [subject] = getSubject("defining-routes");
|
|
87
|
-
|
|
88
|
-
expect(subject.examples.length).toBeGreaterThan(3);
|
|
89
|
-
expect(subject.imports).toContain("defineRoute");
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it("database-querying should have database content", () => {
|
|
93
|
-
const [subject] = getSubject("database-querying");
|
|
94
|
-
|
|
95
|
-
// Database querying examples are currently documentation-only
|
|
96
|
-
expect(subject.title).toBe("Database Querying");
|
|
97
|
-
expect(subject.imports).toContain("defineFragnoDatabase");
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it("database-adapters should have adapter overview", () => {
|
|
101
|
-
const [subject] = getSubject("database-adapters");
|
|
102
|
-
|
|
103
|
-
expect(subject.description).toContain("adapter");
|
|
104
|
-
expect(subject.imports).toContain("DatabaseAdapter");
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
});
|