@vue-skuilder/mcp 0.1.8-3
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/CLAUDE.md +113 -0
- package/dist/examples/local-dev.d.cts +1 -0
- package/dist/examples/local-dev.d.ts +1 -0
- package/dist/examples/local-dev.js +1555 -0
- package/dist/examples/local-dev.js.map +1 -0
- package/dist/examples/local-dev.mjs +1554 -0
- package/dist/examples/local-dev.mjs.map +1 -0
- package/dist/index.d.cts +334 -0
- package/dist/index.d.ts +334 -0
- package/dist/index.js +1630 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1578 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +33 -0
- package/src/examples/index.ts +2 -0
- package/src/examples/local-dev.ts +49 -0
- package/src/index.ts +14 -0
- package/src/prompts/elo-scoring-guidance.ts +116 -0
- package/src/prompts/fill-in-card-authoring.ts +99 -0
- package/src/prompts/index.ts +9 -0
- package/src/resources/cards.ts +313 -0
- package/src/resources/course.ts +32 -0
- package/src/resources/index.ts +23 -0
- package/src/resources/shapes.ts +121 -0
- package/src/resources/tags.ts +277 -0
- package/src/server.ts +595 -0
- package/src/tools/create-card.ts +58 -0
- package/src/tools/delete-card.ts +85 -0
- package/src/tools/index.ts +13 -0
- package/src/tools/tag-card.ts +83 -0
- package/src/tools/update-card.ts +115 -0
- package/src/types/index.ts +33 -0
- package/src/types/resources.ts +20 -0
- package/src/types/tools.ts +74 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/pouchdb.ts +41 -0
- package/src/utils/tools.ts +32 -0
- package/tsconfig.json +16 -0
- package/tsup.config.ts +17 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import type { CourseDBInterface } from '@vue-skuilder/db';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { isSuccessRow } from '../utils/index.js';
|
|
4
|
+
|
|
5
|
+
// Types for card resources
|
|
6
|
+
export interface CardResourceData {
|
|
7
|
+
cardId: string;
|
|
8
|
+
datashape: string;
|
|
9
|
+
data: any;
|
|
10
|
+
tags: string[];
|
|
11
|
+
elo?: number;
|
|
12
|
+
created?: string;
|
|
13
|
+
modified?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface CardsCollection {
|
|
17
|
+
cards: CardResourceData[];
|
|
18
|
+
total: number;
|
|
19
|
+
page?: number;
|
|
20
|
+
limit?: number;
|
|
21
|
+
filter?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Schema for ELO range parsing
|
|
25
|
+
const EloRangeSchema = z.object({
|
|
26
|
+
min: z.number().min(0).max(5000),
|
|
27
|
+
max: z.number().min(0).max(5000)
|
|
28
|
+
}).refine(data => data.min <= data.max, {
|
|
29
|
+
message: "Min ELO must be less than or equal to max ELO"
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Handle cards://all resource - List all cards in the course
|
|
34
|
+
*/
|
|
35
|
+
export async function handleCardsAllResource(
|
|
36
|
+
courseDB: CourseDBInterface,
|
|
37
|
+
limit: number = 50,
|
|
38
|
+
offset: number = 0
|
|
39
|
+
): Promise<CardsCollection> {
|
|
40
|
+
try {
|
|
41
|
+
// Get course info for total count
|
|
42
|
+
const courseInfo = await courseDB.getCourseInfo();
|
|
43
|
+
|
|
44
|
+
// Get cards using ELO-based query (this gives us all cards sorted by ELO)
|
|
45
|
+
const cardIds = await courseDB.getCardsByELO(1500, limit + offset);
|
|
46
|
+
|
|
47
|
+
// Skip offset cards and take limit
|
|
48
|
+
const targetCardIds = cardIds.slice(offset, offset + limit);
|
|
49
|
+
|
|
50
|
+
if (targetCardIds.length === 0) {
|
|
51
|
+
return {
|
|
52
|
+
cards: [],
|
|
53
|
+
total: courseInfo.cardCount,
|
|
54
|
+
page: Math.floor(offset / limit) + 1,
|
|
55
|
+
limit,
|
|
56
|
+
filter: 'all'
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Get card documents
|
|
61
|
+
const cardDocs = await courseDB.getCourseDocs(targetCardIds);
|
|
62
|
+
|
|
63
|
+
// Get ELO data for these cards
|
|
64
|
+
const eloData = await courseDB.getCardEloData(targetCardIds);
|
|
65
|
+
const eloMap = new Map(eloData.map((elo, index) => [targetCardIds[index], elo.global?.score || 1500]));
|
|
66
|
+
|
|
67
|
+
// Transform to CardResourceData format
|
|
68
|
+
const cards: CardResourceData[] = [];
|
|
69
|
+
for (const row of cardDocs.rows) {
|
|
70
|
+
if (isSuccessRow(row)) {
|
|
71
|
+
const doc = row.doc;
|
|
72
|
+
cards.push({
|
|
73
|
+
cardId: doc._id,
|
|
74
|
+
datashape: (doc as any).shape?.name || 'unknown',
|
|
75
|
+
data: (doc as any).data || {},
|
|
76
|
+
tags: [], // Will be populated separately if needed
|
|
77
|
+
elo: eloMap.get(doc._id),
|
|
78
|
+
created: (doc as any).created,
|
|
79
|
+
modified: (doc as any).modified
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
cards,
|
|
86
|
+
total: courseInfo.cardCount,
|
|
87
|
+
page: Math.floor(offset / limit) + 1,
|
|
88
|
+
limit,
|
|
89
|
+
filter: 'all'
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error('Error fetching all cards:', error);
|
|
94
|
+
throw new Error(`Failed to fetch cards: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Handle cards://tag/[tagName] resource - Filter cards by tag
|
|
100
|
+
*/
|
|
101
|
+
export async function handleCardsTagResource(
|
|
102
|
+
courseDB: CourseDBInterface,
|
|
103
|
+
tagName: string,
|
|
104
|
+
limit: number = 50,
|
|
105
|
+
offset: number = 0
|
|
106
|
+
): Promise<CardsCollection> {
|
|
107
|
+
try {
|
|
108
|
+
// Get the tag to validate it exists
|
|
109
|
+
const tag = await courseDB.getTag(tagName);
|
|
110
|
+
if (!tag) {
|
|
111
|
+
throw new Error(`Tag not found: ${tagName}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Note: The current CourseDBInterface doesn't have a direct method to get cards by tag
|
|
115
|
+
// We would need to implement this by querying the tag associations
|
|
116
|
+
// For now, we'll return a placeholder that explains this limitation
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
cards: [],
|
|
120
|
+
total: 0,
|
|
121
|
+
page: Math.floor(offset / limit) + 1,
|
|
122
|
+
limit,
|
|
123
|
+
filter: `tag:${tagName}`,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error(`Error fetching cards for tag ${tagName}:`, error);
|
|
128
|
+
throw new Error(`Failed to fetch cards for tag ${tagName}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Handle cards://shape/[shapeName] resource - Filter cards by DataShape
|
|
134
|
+
*/
|
|
135
|
+
export async function handleCardsShapeResource(
|
|
136
|
+
courseDB: CourseDBInterface,
|
|
137
|
+
shapeName: string,
|
|
138
|
+
limit: number = 50,
|
|
139
|
+
offset: number = 0
|
|
140
|
+
): Promise<CardsCollection> {
|
|
141
|
+
try {
|
|
142
|
+
// Validate shape exists in course config
|
|
143
|
+
const courseConfig = await courseDB.getCourseConfig();
|
|
144
|
+
const validShapes = courseConfig.dataShapes.map(ds => ds.name);
|
|
145
|
+
|
|
146
|
+
if (!validShapes.includes(shapeName)) {
|
|
147
|
+
throw new Error(`DataShape not found: ${shapeName}. Available: ${validShapes.join(', ')}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Note: Direct filtering by DataShape would require a more sophisticated query
|
|
151
|
+
// For now, we'll get all cards and filter in memory (not optimal for large datasets)
|
|
152
|
+
const allCardIds = await courseDB.getCardsByELO(1500, 1000); // Get more cards to filter from
|
|
153
|
+
|
|
154
|
+
if (allCardIds.length === 0) {
|
|
155
|
+
return {
|
|
156
|
+
cards: [],
|
|
157
|
+
total: 0,
|
|
158
|
+
page: Math.floor(offset / limit) + 1,
|
|
159
|
+
limit,
|
|
160
|
+
filter: `shape:${shapeName}`
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Get card documents to check their shapes
|
|
165
|
+
const cardDocs = await courseDB.getCourseDocs(allCardIds);
|
|
166
|
+
|
|
167
|
+
// Filter by shape and collect card IDs
|
|
168
|
+
const filteredCardIds: string[] = [];
|
|
169
|
+
const allFilteredRows: any[] = [];
|
|
170
|
+
|
|
171
|
+
for (const row of cardDocs.rows) {
|
|
172
|
+
if (isSuccessRow(row) && (row.doc as any).shape?.name === shapeName) {
|
|
173
|
+
allFilteredRows.push(row);
|
|
174
|
+
filteredCardIds.push(row.doc._id);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Apply pagination to filtered results
|
|
179
|
+
const paginatedRows = allFilteredRows.slice(offset, offset + limit);
|
|
180
|
+
const paginatedCardIds = paginatedRows.map(row => row.doc._id);
|
|
181
|
+
|
|
182
|
+
// Get ELO data for paginated cards
|
|
183
|
+
const eloData = await courseDB.getCardEloData(paginatedCardIds);
|
|
184
|
+
const eloMap = new Map(eloData.map((elo, index) => [paginatedCardIds[index], elo.global?.score || 1500]));
|
|
185
|
+
|
|
186
|
+
// Transform to CardResourceData format
|
|
187
|
+
const cards: CardResourceData[] = [];
|
|
188
|
+
for (const row of paginatedRows) {
|
|
189
|
+
if (isSuccessRow(row)) {
|
|
190
|
+
const doc = row.doc;
|
|
191
|
+
cards.push({
|
|
192
|
+
cardId: doc._id,
|
|
193
|
+
datashape: (doc as any).shape?.name || 'unknown',
|
|
194
|
+
data: (doc as any).data || {},
|
|
195
|
+
tags: [],
|
|
196
|
+
elo: eloMap.get(doc._id),
|
|
197
|
+
created: (doc as any).created,
|
|
198
|
+
modified: (doc as any).modified
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Count total filtered cards
|
|
204
|
+
const totalFiltered = filteredCardIds.length;
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
cards,
|
|
208
|
+
total: totalFiltered,
|
|
209
|
+
page: Math.floor(offset / limit) + 1,
|
|
210
|
+
limit,
|
|
211
|
+
filter: `shape:${shapeName}`
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
} catch (error) {
|
|
215
|
+
console.error(`Error fetching cards for shape ${shapeName}:`, error);
|
|
216
|
+
throw new Error(`Failed to fetch cards for shape ${shapeName}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Handle cards://elo/[min]-[max] resource - Filter cards by ELO range
|
|
222
|
+
*/
|
|
223
|
+
export async function handleCardsEloResource(
|
|
224
|
+
courseDB: CourseDBInterface,
|
|
225
|
+
eloRange: string,
|
|
226
|
+
limit: number = 50,
|
|
227
|
+
offset: number = 0
|
|
228
|
+
): Promise<CardsCollection> {
|
|
229
|
+
try {
|
|
230
|
+
// Parse ELO range (format: "1200-1800")
|
|
231
|
+
const [minStr, maxStr] = eloRange.split('-');
|
|
232
|
+
if (!minStr || !maxStr) {
|
|
233
|
+
throw new Error(`Invalid ELO range format: ${eloRange}. Expected format: min-max (e.g., 1200-1800)`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const parsedRange = EloRangeSchema.parse({
|
|
237
|
+
min: parseInt(minStr, 10),
|
|
238
|
+
max: parseInt(maxStr, 10)
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Get cards around the middle of the ELO range
|
|
242
|
+
const targetElo = Math.floor((parsedRange.min + parsedRange.max) / 2);
|
|
243
|
+
const cardIds = await courseDB.getCardsByELO(targetElo, 1000); // Get more to filter from
|
|
244
|
+
|
|
245
|
+
if (cardIds.length === 0) {
|
|
246
|
+
return {
|
|
247
|
+
cards: [],
|
|
248
|
+
total: 0,
|
|
249
|
+
page: Math.floor(offset / limit) + 1,
|
|
250
|
+
limit,
|
|
251
|
+
filter: `elo:${eloRange}`
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Get ELO data for all cards
|
|
256
|
+
const eloData = await courseDB.getCardEloData(cardIds);
|
|
257
|
+
|
|
258
|
+
// Filter by ELO range
|
|
259
|
+
const filteredEloData = eloData
|
|
260
|
+
.map((elo, index) => ({ elo, cardId: cardIds[index] }))
|
|
261
|
+
.filter(({ elo }) => {
|
|
262
|
+
const score = elo.global?.score || 1500;
|
|
263
|
+
return score >= parsedRange.min && score <= parsedRange.max;
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Apply pagination
|
|
267
|
+
const paginatedEloData = filteredEloData.slice(offset, offset + limit);
|
|
268
|
+
const paginatedCardIds = paginatedEloData.map(({ cardId }) => cardId);
|
|
269
|
+
|
|
270
|
+
if (paginatedCardIds.length === 0) {
|
|
271
|
+
return {
|
|
272
|
+
cards: [],
|
|
273
|
+
total: filteredEloData.length,
|
|
274
|
+
page: Math.floor(offset / limit) + 1,
|
|
275
|
+
limit,
|
|
276
|
+
filter: `elo:${eloRange}`
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Get card documents
|
|
281
|
+
const cardDocs = await courseDB.getCourseDocs(paginatedCardIds);
|
|
282
|
+
const eloMap = new Map(paginatedEloData.map(({ elo, cardId }) => [cardId, elo.global?.score || 1500]));
|
|
283
|
+
|
|
284
|
+
// Transform to CardResourceData format
|
|
285
|
+
const cards: CardResourceData[] = [];
|
|
286
|
+
for (const row of cardDocs.rows) {
|
|
287
|
+
if (isSuccessRow(row)) {
|
|
288
|
+
const doc = row.doc;
|
|
289
|
+
cards.push({
|
|
290
|
+
cardId: doc._id,
|
|
291
|
+
datashape: (doc as any).shape?.name || 'unknown',
|
|
292
|
+
data: (doc as any).data || {},
|
|
293
|
+
tags: [],
|
|
294
|
+
elo: eloMap.get(doc._id),
|
|
295
|
+
created: (doc as any).created,
|
|
296
|
+
modified: (doc as any).modified
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
cards,
|
|
303
|
+
total: filteredEloData.length,
|
|
304
|
+
page: Math.floor(offset / limit) + 1,
|
|
305
|
+
limit,
|
|
306
|
+
filter: `elo:${eloRange}`
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
} catch (error) {
|
|
310
|
+
console.error(`Error fetching cards for ELO range ${eloRange}:`, error);
|
|
311
|
+
throw new Error(`Failed to fetch cards for ELO range ${eloRange}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { CourseDBInterface } from '@vue-skuilder/db';
|
|
3
|
+
import type { CourseResource } from '../types/resources.js';
|
|
4
|
+
|
|
5
|
+
export async function handleCourseConfigResource(
|
|
6
|
+
courseDB: CourseDBInterface
|
|
7
|
+
): Promise<CourseResource> {
|
|
8
|
+
try {
|
|
9
|
+
// Get course configuration
|
|
10
|
+
const config = await courseDB.getCourseConfig();
|
|
11
|
+
|
|
12
|
+
// TODO: Implement proper ELO distribution calculation
|
|
13
|
+
// For now, provide basic structure
|
|
14
|
+
const eloStats = {
|
|
15
|
+
min: 1000,
|
|
16
|
+
max: 2000,
|
|
17
|
+
mean: 1500,
|
|
18
|
+
distribution: [100, 200, 300, 250, 150] // Placeholder histogram
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
config,
|
|
23
|
+
eloStats
|
|
24
|
+
};
|
|
25
|
+
} catch (error) {
|
|
26
|
+
throw new Error(`Failed to load course config: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// URI pattern validation
|
|
31
|
+
export const CourseConfigUriSchema = z.string().regex(/^course:\/\/config$/);
|
|
32
|
+
export type CourseConfigUri = z.infer<typeof CourseConfigUriSchema>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Resource registry and exports
|
|
2
|
+
export * from './course.js';
|
|
3
|
+
export * from './cards.js';
|
|
4
|
+
export * from './shapes.js';
|
|
5
|
+
export * from './tags.js';
|
|
6
|
+
|
|
7
|
+
// Resource URI patterns
|
|
8
|
+
export const RESOURCE_PATTERNS = {
|
|
9
|
+
COURSE_CONFIG: 'course://config',
|
|
10
|
+
CARDS_ALL: 'cards://all',
|
|
11
|
+
CARDS_TAG: 'cards://tag/{tagName}',
|
|
12
|
+
CARDS_SHAPE: 'cards://shape/{shapeName}',
|
|
13
|
+
CARDS_ELO: 'cards://elo/{eloRange}',
|
|
14
|
+
SHAPES_ALL: 'shapes://all',
|
|
15
|
+
SHAPES_SPECIFIC: 'shapes://{shapeName}',
|
|
16
|
+
TAGS_ALL: 'tags://all',
|
|
17
|
+
TAGS_STATS: 'tags://stats',
|
|
18
|
+
TAGS_SPECIFIC: 'tags://{tagName}',
|
|
19
|
+
TAGS_UNION: 'tags://union/{tags}',
|
|
20
|
+
TAGS_INTERSECT: 'tags://intersect/{tags}',
|
|
21
|
+
TAGS_EXCLUSIVE: 'tags://exclusive/{tags}',
|
|
22
|
+
TAGS_DISTRIBUTION: 'tags://distribution',
|
|
23
|
+
} as const;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { CourseDBInterface } from '@vue-skuilder/db';
|
|
2
|
+
import { isSuccessRow } from '../utils/index.js';
|
|
3
|
+
|
|
4
|
+
export interface ShapeResource {
|
|
5
|
+
name: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
fields: Array<{
|
|
8
|
+
name: string;
|
|
9
|
+
type: string;
|
|
10
|
+
required?: boolean;
|
|
11
|
+
description?: string;
|
|
12
|
+
}>;
|
|
13
|
+
category?: string;
|
|
14
|
+
examples?: any[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ShapesCollection {
|
|
18
|
+
shapes: ShapeResource[];
|
|
19
|
+
total: number;
|
|
20
|
+
availableShapes: string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Handle shapes://all resource - List all available DataShapes
|
|
25
|
+
*/
|
|
26
|
+
export async function handleShapesAllResource(
|
|
27
|
+
courseDB: CourseDBInterface
|
|
28
|
+
): Promise<ShapesCollection> {
|
|
29
|
+
try {
|
|
30
|
+
// Get course config to access DataShapes
|
|
31
|
+
const courseConfig = await courseDB.getCourseConfig();
|
|
32
|
+
const dataShapes = courseConfig.dataShapes || [];
|
|
33
|
+
|
|
34
|
+
// Transform DataShapes to ShapeResource format
|
|
35
|
+
const shapes: ShapeResource[] = dataShapes.map(shape => ({
|
|
36
|
+
name: shape.name,
|
|
37
|
+
description: `DataShape for ${shape.name} content type`,
|
|
38
|
+
fields: (shape as any).fields?.map((field: any) => ({
|
|
39
|
+
name: field.name,
|
|
40
|
+
type: field.type || 'string',
|
|
41
|
+
required: field.required || false,
|
|
42
|
+
description: field.description || `Field for ${field.name}`
|
|
43
|
+
})) || [],
|
|
44
|
+
category: 'course-content',
|
|
45
|
+
examples: [] // Could be populated with example cards
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
const availableShapes = dataShapes.map(shape => shape.name);
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
shapes,
|
|
52
|
+
total: shapes.length,
|
|
53
|
+
availableShapes
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error('Error fetching all shapes:', error);
|
|
58
|
+
throw new Error(`Failed to fetch shapes: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Handle shapes://[shapeName] resource - Get specific DataShape definition
|
|
64
|
+
*/
|
|
65
|
+
export async function handleShapeSpecificResource(
|
|
66
|
+
courseDB: CourseDBInterface,
|
|
67
|
+
shapeName: string
|
|
68
|
+
): Promise<ShapeResource> {
|
|
69
|
+
try {
|
|
70
|
+
// Get course config to access DataShapes
|
|
71
|
+
const courseConfig = await courseDB.getCourseConfig();
|
|
72
|
+
const dataShapes = courseConfig.dataShapes || [];
|
|
73
|
+
|
|
74
|
+
// Find the specific shape
|
|
75
|
+
const targetShape = dataShapes.find(shape => shape.name === shapeName);
|
|
76
|
+
if (!targetShape) {
|
|
77
|
+
const availableShapes = dataShapes.map(s => s.name);
|
|
78
|
+
throw new Error(`DataShape not found: ${shapeName}. Available shapes: ${availableShapes.join(', ')}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Get examples by finding cards that use this shape
|
|
82
|
+
let examples: any[] = [];
|
|
83
|
+
try {
|
|
84
|
+
// Get a few cards that use this shape to provide examples
|
|
85
|
+
const cardIds = await courseDB.getCardsByELO(1500, 10); // Get some sample cards
|
|
86
|
+
if (cardIds.length > 0) {
|
|
87
|
+
const cardDocs = await courseDB.getCourseDocs(cardIds.slice(0, 5)); // Limit to 5 examples
|
|
88
|
+
examples = [];
|
|
89
|
+
for (const row of cardDocs.rows) {
|
|
90
|
+
if (isSuccessRow(row) && (row.doc as any).shape?.name === shapeName) {
|
|
91
|
+
const data = (row.doc as any)?.data;
|
|
92
|
+
if (data) {
|
|
93
|
+
examples.push(data);
|
|
94
|
+
if (examples.length >= 3) break; // Max 3 examples
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.warn('Could not fetch examples for shape:', error);
|
|
101
|
+
// Continue without examples
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
name: targetShape.name,
|
|
106
|
+
description: `DataShape definition for ${targetShape.name} content type`,
|
|
107
|
+
fields: (targetShape as any).fields?.map((field: any) => ({
|
|
108
|
+
name: field.name,
|
|
109
|
+
type: field.type || 'string',
|
|
110
|
+
required: field.required || false,
|
|
111
|
+
description: field.description || `Field for ${field.name}`
|
|
112
|
+
})) || [],
|
|
113
|
+
category: 'course-content',
|
|
114
|
+
examples
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error(`Error fetching shape ${shapeName}:`, error);
|
|
119
|
+
throw new Error(`Failed to fetch shape ${shapeName}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
120
|
+
}
|
|
121
|
+
}
|