@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/src/index.ts DELETED
@@ -1,51 +0,0 @@
1
- import {
2
- getAvailableSubjectIds,
3
- loadSubject,
4
- loadSubjects,
5
- loadAllSubjects,
6
- type SubjectInfo,
7
- type Subject,
8
- } from "./parser";
9
-
10
- /**
11
- * Get basic information about all available subjects
12
- * @returns Array of subject info (id and title)
13
- */
14
- export function getSubjects(): SubjectInfo[] {
15
- const ids = getAvailableSubjectIds();
16
- return ids.map((id) => {
17
- const subject = loadSubject(id);
18
- return {
19
- id: subject.id,
20
- title: subject.title,
21
- };
22
- });
23
- }
24
-
25
- /**
26
- * Get one or more subjects by their IDs
27
- * @param ids Subject IDs to load
28
- * @returns Array of complete subject data
29
- * @example
30
- * ```ts
31
- * // Get single subject
32
- * const [routes] = getSubject("defining-routes");
33
- *
34
- * // Get multiple subjects for combined context
35
- * const [adapters, kysely] = getSubject("database-adapters", "kysely-adapter");
36
- * ```
37
- */
38
- export function getSubject(...ids: string[]): Subject[] {
39
- return loadSubjects(ids);
40
- }
41
-
42
- /**
43
- * Get all available subjects
44
- * @returns Array of all subjects with complete data
45
- */
46
- export function getAllSubjects(): Subject[] {
47
- return loadAllSubjects();
48
- }
49
-
50
- // Re-export types
51
- export type { Subject, SubjectInfo, Example } from "./parser.js";
@@ -1,115 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { parseMarkdownFile, markdownToSubject } from "./parser.js";
3
-
4
- describe("parseMarkdownFile", () => {
5
- it("should extract title from markdown", () => {
6
- const content = `# Test Title
7
-
8
- Description here`;
9
-
10
- const result = parseMarkdownFile(content);
11
- expect(result.title).toBe("Test Title");
12
- });
13
-
14
- it("should extract description", () => {
15
- const content = `# Test Title
16
-
17
- This is a description.
18
-
19
- \`\`\`typescript @fragno-imports
20
- import { test } from "test";
21
- \`\`\``;
22
-
23
- const result = parseMarkdownFile(content);
24
- expect(result.description).toBe("This is a description.");
25
- });
26
-
27
- it("should extract imports block", () => {
28
- const content = `# Test
29
-
30
- \`\`\`typescript @fragno-imports
31
- import { defineRoute } from "@fragno-dev/core";
32
- import { z } from "zod";
33
- \`\`\``;
34
-
35
- const result = parseMarkdownFile(content);
36
- expect(result.imports).toBe(`import { defineRoute } from "@fragno-dev/core";
37
- import { z } from "zod";`);
38
- });
39
-
40
- it("should extract init block when present", () => {
41
- const content = `# Test
42
-
43
- \`\`\`typescript @fragno-imports
44
- import { x } from "y";
45
- \`\`\`
46
-
47
- \`\`\`typescript @fragno-init
48
- const config = { key: "value" };
49
- \`\`\``;
50
-
51
- const result = parseMarkdownFile(content);
52
- expect(result.init).toBe(`const config = { key: "value" };`);
53
- });
54
-
55
- it("should handle missing init block", () => {
56
- const content = `# Test
57
-
58
- \`\`\`typescript @fragno-imports
59
- import { x } from "y";
60
- \`\`\``;
61
-
62
- const result = parseMarkdownFile(content);
63
- expect(result.init).toBe("");
64
- });
65
-
66
- it("should extract multiple test blocks", () => {
67
- const content = `# Test
68
-
69
- \`\`\`typescript @fragno-imports
70
- import { x } from "y";
71
- \`\`\`
72
-
73
- \`\`\`typescript @fragno-test
74
- const test1 = "value1";
75
- \`\`\`
76
-
77
- This is explanation for test1.
78
-
79
- \`\`\`typescript @fragno-test
80
- const test2 = "value2";
81
- \`\`\`
82
-
83
- This is explanation for test2.`;
84
-
85
- const result = parseMarkdownFile(content);
86
- expect(result.testBlocks).toHaveLength(2);
87
- expect(result.testBlocks[0].code).toBe(`const test1 = "value1";`);
88
- expect(result.testBlocks[0].explanation).toContain("explanation for test1");
89
- expect(result.testBlocks[1].code).toBe(`const test2 = "value2";`);
90
- expect(result.testBlocks[1].explanation).toContain("explanation for test2");
91
- });
92
- });
93
-
94
- describe("markdownToSubject", () => {
95
- it("should convert parsed markdown to Subject", () => {
96
- const parsed = {
97
- title: "Test Subject",
98
- description: "Test description",
99
- imports: "import { x } from 'y';",
100
- init: "const config = {};",
101
- testBlocks: [{ code: "const test = 1;", explanation: "Test explanation" }],
102
- };
103
-
104
- const result = markdownToSubject("test-id", parsed);
105
-
106
- expect(result.id).toBe("test-id");
107
- expect(result.title).toBe("Test Subject");
108
- expect(result.description).toBe("Test description");
109
- expect(result.imports).toBe("import { x } from 'y';");
110
- expect(result.init).toBe("const config = {};");
111
- expect(result.examples).toHaveLength(1);
112
- expect(result.examples[0].code).toBe("const test = 1;");
113
- expect(result.examples[0].explanation).toBe("Test explanation");
114
- });
115
- });
package/src/parser.ts DELETED
@@ -1,182 +0,0 @@
1
- import { readFileSync, readdirSync } from "node:fs";
2
- import { join, basename, dirname } from "node:path";
3
- import { fileURLToPath } from "node:url";
4
-
5
- const __filename = fileURLToPath(import.meta.url);
6
- const __dirname = dirname(__filename);
7
-
8
- /**
9
- * Basic information about a subject
10
- */
11
- export interface SubjectInfo {
12
- id: string;
13
- title: string;
14
- }
15
-
16
- /**
17
- * A single example within a subject
18
- */
19
- export interface Example {
20
- code: string;
21
- explanation: string;
22
- testType?: "route" | "database" | "none";
23
- testName?: string;
24
- }
25
-
26
- /**
27
- * Complete subject with all examples and metadata
28
- */
29
- export interface Subject {
30
- id: string;
31
- title: string;
32
- description: string;
33
- imports: string;
34
- init: string;
35
- examples: Example[];
36
- }
37
-
38
- /**
39
- * Raw parsed data from markdown before processing
40
- */
41
- export interface ParsedMarkdown {
42
- title: string;
43
- description: string;
44
- imports: string;
45
- init: string;
46
- testBlocks: Array<{
47
- code: string;
48
- explanation: string;
49
- testType?: "route" | "database" | "none";
50
- testName?: string;
51
- }>;
52
- }
53
-
54
- // Look for subjects directory in source or relative to built dist
55
- const SUBJECTS_DIR = (() => {
56
- // Try dist/../src/subjects (when running from built code)
57
- const distRelative = join(__dirname, "..", "src", "subjects");
58
- try {
59
- readdirSync(distRelative);
60
- return distRelative;
61
- } catch {
62
- // Fall back to ./subjects (when running from source)
63
- return join(__dirname, "subjects");
64
- }
65
- })();
66
-
67
- /**
68
- * Parses a markdown file and extracts structured content
69
- */
70
- export function parseMarkdownFile(content: string): ParsedMarkdown {
71
- // Extract title (first # heading)
72
- const titleMatch = content.match(/^#\s+(.+)$/m);
73
- const title = titleMatch ? titleMatch[1].trim() : "Untitled";
74
-
75
- // Extract imports block
76
- const importsMatch = content.match(/```typescript @fragno-imports\n([\s\S]*?)```/);
77
- const imports = importsMatch ? importsMatch[1].trim() : "";
78
-
79
- // Extract init block (optional)
80
- const initMatch = content.match(/```typescript @fragno-init\n([\s\S]*?)```/);
81
- const init = initMatch ? initMatch[1].trim() : "";
82
-
83
- // Extract all test blocks with their explanations and test type
84
- const testBlockRegex =
85
- /```typescript @fragno-test(?::(\w+))?\n([\s\S]*?)```([\s\S]*?)(?=```typescript @fragno-test|$)/g;
86
- const testBlocks: Array<{
87
- code: string;
88
- explanation: string;
89
- testType?: "route" | "database" | "none";
90
- testName?: string;
91
- }> = [];
92
-
93
- let match;
94
- while ((match = testBlockRegex.exec(content)) !== null) {
95
- const testTypeRaw = match[1]; // route, database, or undefined
96
- const testType = testTypeRaw === "route" || testTypeRaw === "database" ? testTypeRaw : "none";
97
-
98
- const code = match[2].trim();
99
-
100
- // Extract test name from first line if it's a comment
101
- const lines = code.split("\n");
102
- let testName: string | undefined;
103
- if (lines[0]?.trim().startsWith("//")) {
104
- testName = lines[0].replace(/^\/\/\s*/, "").trim();
105
- }
106
-
107
- // Get explanation text after the code block until next code block or end
108
- const afterBlock = match[3];
109
- const explanation = afterBlock
110
- .split(/```/)[0] // Stop at next code block
111
- .trim();
112
-
113
- testBlocks.push({ code, explanation, testType, testName });
114
- }
115
-
116
- // Extract description (everything between title and first code block or ## heading)
117
- const afterTitle = content.substring(content.indexOf(title) + title.length);
118
- const descriptionMatch = afterTitle.match(/\n\n([\s\S]*?)(?=```|##|$)/);
119
- const description = descriptionMatch ? descriptionMatch[1].trim() : "";
120
-
121
- return {
122
- title,
123
- description,
124
- imports,
125
- init,
126
- testBlocks,
127
- };
128
- }
129
-
130
- /**
131
- * Converts parsed markdown to a Subject
132
- */
133
- export function markdownToSubject(id: string, parsed: ParsedMarkdown): Subject {
134
- const examples: Example[] = parsed.testBlocks.map((block) => ({
135
- code: block.code,
136
- explanation: block.explanation,
137
- testType: block.testType,
138
- testName: block.testName,
139
- }));
140
-
141
- return {
142
- id,
143
- title: parsed.title,
144
- description: parsed.description,
145
- imports: parsed.imports,
146
- init: parsed.init,
147
- examples,
148
- };
149
- }
150
-
151
- /**
152
- * Loads and parses a subject file by ID
153
- */
154
- export function loadSubject(id: string): Subject {
155
- const filePath = join(SUBJECTS_DIR, `${id}.md`);
156
- const content = readFileSync(filePath, "utf-8");
157
- const parsed = parseMarkdownFile(content);
158
- return markdownToSubject(id, parsed);
159
- }
160
-
161
- /**
162
- * Gets all available subject IDs from the subjects directory
163
- */
164
- export function getAvailableSubjectIds(): string[] {
165
- const files = readdirSync(SUBJECTS_DIR);
166
- return files.filter((file) => file.endsWith(".md")).map((file) => basename(file, ".md"));
167
- }
168
-
169
- /**
170
- * Loads multiple subjects by their IDs
171
- */
172
- export function loadSubjects(ids: string[]): Subject[] {
173
- return ids.map((id) => loadSubject(id));
174
- }
175
-
176
- /**
177
- * Loads all available subjects
178
- */
179
- export function loadAllSubjects(): Subject[] {
180
- const ids = getAvailableSubjectIds();
181
- return loadSubjects(ids);
182
- }
@@ -1,68 +0,0 @@
1
- # Database Adapters
2
-
3
- Database adapters connect Fragno's database API to your existing ORM (Kysely or Drizzle). They allow
4
- fragments to work with your application's database without dictating which ORM you use.
5
-
6
- ```typescript @fragno-imports
7
- import type { DatabaseAdapter } from "@fragno-dev/db";
8
- ```
9
-
10
- ## What is a Database Adapter?
11
-
12
- A database adapter is a bridge between Fragno's type-safe database API and your underlying ORM. It
13
- translates Fragno's query operations into ORM-specific syntax.
14
-
15
- ```typescript
16
- // Adapters implement the DatabaseAdapter interface
17
- declare const adapter: DatabaseAdapter;
18
-
19
- // Fragments receive an adapter through their configuration
20
- interface FragmentConfig {
21
- databaseAdapter: DatabaseAdapter;
22
- }
23
- ```
24
-
25
- When a fragment needs database access, users pass an adapter configured with their ORM instance.
26
-
27
- ## Supported Providers
28
-
29
- Both KyselyAdapter and DrizzleAdapter support three database providers:
30
-
31
- - `"postgresql"` - PostgreSQL databases
32
- - `"mysql"` - MySQL and MariaDB databases
33
- - `"sqlite"` - SQLite databases
34
-
35
- Choose the provider that matches your database type when creating an adapter.
36
-
37
- ## Shared Adapters
38
-
39
- Multiple fragments can share the same adapter, meaning they all use your application's single
40
- database connection.
41
-
42
- ```typescript
43
- declare const adapter: DatabaseAdapter;
44
-
45
- // All fragments use the same adapter
46
- export interface Fragment1Config {
47
- databaseAdapter: typeof adapter;
48
- }
49
-
50
- export interface Fragment2Config {
51
- databaseAdapter: typeof adapter;
52
- }
53
- ```
54
-
55
- This ensures fragments integrate seamlessly with your existing database infrastructure.
56
-
57
- ## Cleanup
58
-
59
- Adapters manage connection lifecycle automatically. Call `close()` when shutting down your
60
- application to properly release database connections.
61
-
62
- ```typescript
63
- declare const adapter: DatabaseAdapter;
64
-
65
- export async function cleanup() {
66
- await adapter.close();
67
- }
68
- ```
@@ -1,227 +0,0 @@
1
- # Database Querying
2
-
3
- Fragno provides a unified database query API that works across different ORMs. This guide covers
4
- CRUD operations and querying with conditions.
5
-
6
- ```typescript @fragno-imports
7
- import { defineFragnoDatabase, schema, idColumn, column } from "@fragno-dev/db";
8
- import type { AbstractQuery } from "@fragno-dev/db";
9
- ```
10
-
11
- ```typescript @fragno-init
12
- // Example schema for testing
13
- const userSchema = schema((s) => {
14
- return s
15
- .addTable("users", (t) => {
16
- return t
17
- .addColumn("id", idColumn())
18
- .addColumn("email", column("string"))
19
- .addColumn("name", column("string"))
20
- .addColumn("age", column("integer").nullable())
21
- .createIndex("idx_email", ["email"], { unique: true });
22
- })
23
- .addTable("posts", (t) => {
24
- return t
25
- .addColumn("id", idColumn())
26
- .addColumn("title", column("string"))
27
- .addColumn("content", column("string"))
28
- .addColumn("authorId", column("string"))
29
- .addColumn("publishedAt", column("timestamp").nullable())
30
- .createIndex("idx_author", ["authorId"]);
31
- });
32
- });
33
-
34
- type UserSchema = typeof userSchema;
35
- declare const orm: AbstractQuery<UserSchema>;
36
- ```
37
-
38
- ## Create
39
-
40
- Create a single record in the database.
41
-
42
- ```typescript
43
- export async function createUser() {
44
- const userId = await orm.create("users", {
45
- id: "user-123",
46
- email: "john@example.com",
47
- name: "John Doe",
48
- age: 30,
49
- });
50
-
51
- return userId; // Returns a FragnoId object
52
- }
53
- ```
54
-
55
- The `create` method returns a `FragnoId` object representing the created record's ID.
56
-
57
- ## Create Many
58
-
59
- Create multiple records at once.
60
-
61
- ```typescript
62
- export async function createMultipleUsers() {
63
- const userIds = await orm.createMany("users", [
64
- {
65
- id: "user-1",
66
- email: "user1@example.com",
67
- name: "User One",
68
- age: 25,
69
- },
70
- {
71
- id: "user-2",
72
- email: "user2@example.com",
73
- name: "User Two",
74
- age: 35,
75
- },
76
- ]);
77
-
78
- return userIds; // Returns an array of FragnoId objects
79
- }
80
- ```
81
-
82
- ## Find First
83
-
84
- Query for a single record using an index.
85
-
86
- ```typescript
87
- export async function findUserByEmail(email: string) {
88
- const user = await orm.findFirst("users", (b) =>
89
- b.whereIndex("idx_email", (eb) => eb("email", "=", email)),
90
- );
91
-
92
- return user; // Returns the user object or null if not found
93
- }
94
- ```
95
-
96
- Use `findFirst` when you expect a single result or want to get the first matching record.
97
-
98
- ## Find First with Select
99
-
100
- Query a single record and select specific columns.
101
-
102
- ```typescript
103
- export async function findUserEmailOnly(userId: string) {
104
- const user = await orm.findFirst("users", (b) =>
105
- b.whereIndex("primary", (eb) => eb("id", "=", userId)).select(["id", "email"]),
106
- );
107
-
108
- return user; // Returns only id and email fields
109
- }
110
- ```
111
-
112
- ## Find Many
113
-
114
- Query for multiple records matching conditions.
115
-
116
- ```typescript
117
- export async function findPostsByAuthor(authorId: string) {
118
- const posts = await orm.find("posts", (b) =>
119
- b.whereIndex("idx_author", (eb) => eb("authorId", "=", authorId)),
120
- );
121
-
122
- return posts; // Returns an array of posts
123
- }
124
- ```
125
-
126
- The `find` method returns all records matching the where clause.
127
-
128
- ## Find with Pagination
129
-
130
- Limit the number of results returned.
131
-
132
- ```typescript
133
- export async function findUsersPaginated(page: number, pageSize: number) {
134
- const users = await orm.find("users", (b) => b.whereIndex("primary").pageSize(pageSize));
135
-
136
- return users;
137
- }
138
- ```
139
-
140
- ## Find All
141
-
142
- Query all records from a table.
143
-
144
- ```typescript
145
- export async function findAllUsers() {
146
- const users = await orm.find("users", (b) => b.whereIndex("primary"));
147
-
148
- return users;
149
- }
150
- ```
151
-
152
- When using `whereIndex("primary")` without conditions, it returns all records.
153
-
154
- ## Update
155
-
156
- Update a single record by ID.
157
-
158
- ```typescript
159
- export async function updateUserEmail(userId: string, newEmail: string) {
160
- await orm.update("users", userId, (b) =>
161
- b.set({
162
- email: newEmail,
163
- }),
164
- );
165
- }
166
- ```
167
-
168
- The `update` method modifies a single record identified by its ID.
169
-
170
- ## Update Many
171
-
172
- Update multiple records matching a condition.
173
-
174
- ```typescript
175
- export async function updatePostsPublishedDate(authorId: string, publishedAt: Date) {
176
- await orm.updateMany("posts", (b) =>
177
- b.whereIndex("idx_author", (eb) => eb("authorId", "=", authorId)).set({ publishedAt }),
178
- );
179
- }
180
- ```
181
-
182
- `updateMany` finds all matching records and updates them.
183
-
184
- ## Delete
185
-
186
- Delete a single record by ID.
187
-
188
- ```typescript
189
- export async function deleteUser(userId: string) {
190
- await orm.delete("users", userId);
191
- }
192
- ```
193
-
194
- ## Delete Many
195
-
196
- Delete multiple records matching a condition.
197
-
198
- ```typescript
199
- export async function deletePostsByAuthor(authorId: string) {
200
- await orm.deleteMany("posts", (b) =>
201
- b.whereIndex("idx_author", (eb) => eb("authorId", "=", authorId)),
202
- );
203
- }
204
- ```
205
-
206
- `deleteMany` finds all matching records and deletes them.
207
-
208
- ## Querying with Conditions
209
-
210
- Use condition builders to create complex queries.
211
-
212
- ```typescript
213
- export async function findAdultUsers() {
214
- const adults = await orm.find("users", (b) =>
215
- b.whereIndex("primary").where((cb) => {
216
- // Note: Condition builders depend on the underlying ORM
217
- // This is a simplified example
218
- return cb;
219
- }),
220
- );
221
-
222
- return adults;
223
- }
224
- ```
225
-
226
- Condition builders allow you to add additional filtering beyond index lookups, though the exact API
227
- depends on your adapter (Kysely or Drizzle).