Skip to content

Commit 80ea79a

Browse files
committed
good progress on exercise 1
1 parent 225813d commit 80ea79a

135 files changed

Lines changed: 7577 additions & 111 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Annotations

exercises/01.sampling/01.problem.simple/other/caveat-variable-font.ttf renamed to exercises/01.advanced-tools/01.problem.annotations/other/caveat-variable-font.ttf

File renamed without changes.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "exercises_01.advanced-tools_01.problem.annotations",
3+
"private": true,
4+
"type": "module",
5+
"scripts": {
6+
"dev": "mcp-dev",
7+
"dev:mcp": "tsx src/index.ts",
8+
"test": "vitest",
9+
"typecheck": "tsc",
10+
"inspect": "mcp-inspector"
11+
},
12+
"dependencies": {
13+
"@epic-web/invariant": "^1.0.0",
14+
"@modelcontextprotocol/sdk": "^1.15.0",
15+
"zod": "^3.25.67"
16+
},
17+
"devDependencies": {
18+
"@epic-web/config": "^1.21.0",
19+
"@epic-web/mcp-dev": "*",
20+
"@faker-js/faker": "^9.8.0",
21+
"@modelcontextprotocol/inspector": "^0.15.0",
22+
"@types/node": "^24.0.3",
23+
"tsx": "^4.20.3",
24+
"typescript": "^5.8.3",
25+
"vitest": "^3.2.4"
26+
},
27+
"license": "GPL-3.0-only"
28+
}

exercises/01.sampling/01.solution.simple/src/db/index.ts renamed to exercises/01.advanced-tools/01.problem.annotations/src/db/index.ts

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
type Tag,
88
type NewTag,
99
type EntryTag,
10+
type EntryWithTags,
1011
entrySchema,
1112
newEntrySchema,
1213
tagSchema,
@@ -19,6 +20,25 @@ export type { Entry, NewEntry, Tag, NewTag, EntryTag }
1920

2021
export class DB {
2122
#db: DatabaseSync
23+
#subscribers = new Set<
24+
(changes: { tags?: number[]; entries?: number[] }) => void
25+
>()
26+
27+
subscribe(
28+
subscriber: (changes: { tags?: number[]; entries?: number[] }) => void,
29+
) {
30+
this.#subscribers.add(subscriber)
31+
return () => {
32+
this.#subscribers.delete(subscriber)
33+
}
34+
}
35+
36+
#notifySubscribers(changes: { tags?: number[]; entries?: number[] }) {
37+
for (const subscriber of this.#subscribers) {
38+
subscriber(changes)
39+
}
40+
}
41+
2242
constructor(db: DatabaseSync) {
2343
this.#db = db
2444
}
@@ -60,6 +80,7 @@ export class DB {
6080
if (!createdEntry) {
6181
throw new Error('Failed to query created entry')
6282
}
83+
this.#notifySubscribers({ entries: [id] })
6384
return createdEntry
6485
}
6586

@@ -87,7 +108,7 @@ export class DB {
87108
const tags = z
88109
.array(z.object({ id: z.number(), name: z.string() }))
89110
.parse(tagsResult)
90-
return { ...entry, tags }
111+
return { ...entry, tags } as EntryWithTags
91112
}
92113

93114
// TODO: listEntries to actually filter by tagIds
@@ -109,25 +130,25 @@ export class DB {
109130
}
110131
const updates = Object.entries(entry)
111132
.filter(([key, value]) => value !== undefined)
112-
.map(([key], index) => `${key} = ?${index + 2}`)
133+
.map(([key]) => `${key} = ?`)
113134
.join(', ')
114135
if (!updates) {
115136
return existingEntry
116137
}
117-
const stmt = this.#db.prepare(sql`
118-
UPDATE entries
119-
SET ${updates}, updated_at = CURRENT_TIMESTAMP
120-
WHERE id = ?1
121-
`)
122138
const updateValues = [
123-
id,
124139
...Object.entries(entry)
125140
.filter(([, value]) => value !== undefined)
126141
.map(([, value]) => value),
142+
id,
127143
]
128144
if (updateValues.some((v) => v === undefined)) {
129145
throw new Error('Undefined value in updateEntry parameters')
130146
}
147+
const stmt = this.#db.prepare(sql`
148+
UPDATE entries
149+
SET ${updates}, updated_at = CURRENT_TIMESTAMP
150+
WHERE id = ?
151+
`)
131152
const result = stmt.run(...updateValues)
132153
if (!result.changes) {
133154
throw new Error('Failed to update entry')
@@ -136,6 +157,7 @@ export class DB {
136157
if (!updatedEntry) {
137158
throw new Error('Failed to query updated entry')
138159
}
160+
this.#notifySubscribers({ entries: [id] })
139161
return updatedEntry
140162
}
141163

@@ -149,6 +171,7 @@ export class DB {
149171
if (!result.changes) {
150172
throw new Error('Failed to delete entry')
151173
}
174+
this.#notifySubscribers({ entries: [id] })
152175
return true
153176
}
154177

@@ -171,6 +194,7 @@ export class DB {
171194
if (!createdTag) {
172195
throw new Error('Failed to query created tag')
173196
}
197+
this.#notifySubscribers({ tags: [id] })
174198
return createdTag
175199
}
176200

@@ -202,25 +226,25 @@ export class DB {
202226
}
203227
const updates = Object.entries(tag)
204228
.filter(([, value]) => value !== undefined)
205-
.map(([key], index) => `${key} = ?${index + 2}`)
229+
.map(([key]) => `${key} = ?`)
206230
.join(', ')
207231
if (!updates) {
208232
return existingTag
209233
}
210-
const stmt = this.#db.prepare(sql`
211-
UPDATE tags
212-
SET ${updates}, updated_at = CURRENT_TIMESTAMP
213-
WHERE id = ?1
214-
`)
215234
const updateValues = [
216-
id,
217235
...Object.entries(tag)
218236
.filter(([, value]) => value !== undefined)
219237
.map(([, value]) => value),
238+
id,
220239
]
221240
if (updateValues.some((v) => v === undefined)) {
222241
throw new Error('Undefined value in updateTag parameters')
223242
}
243+
const stmt = this.#db.prepare(sql`
244+
UPDATE tags
245+
SET ${updates}, updated_at = CURRENT_TIMESTAMP
246+
WHERE id = ?
247+
`)
224248
const result = stmt.run(...updateValues)
225249
if (!result.changes) {
226250
throw new Error('Failed to update tag')
@@ -229,6 +253,7 @@ export class DB {
229253
if (!updatedTag) {
230254
throw new Error('Failed to query updated tag')
231255
}
256+
this.#notifySubscribers({ tags: [id] })
232257
return updatedTag
233258
}
234259

@@ -242,6 +267,7 @@ export class DB {
242267
if (!result.changes) {
243268
throw new Error('Failed to delete tag')
244269
}
270+
this.#notifySubscribers({ tags: [id] })
245271
return true
246272
}
247273

@@ -271,6 +297,7 @@ export class DB {
271297
if (!created) {
272298
throw new Error('Failed to query created entry tag')
273299
}
300+
this.#notifySubscribers({ entries: [entryId], tags: [tagId] })
274301
return created
275302
}
276303

exercises/01.sampling/01.problem.simple/src/db/migrations.ts renamed to exercises/01.advanced-tools/01.problem.annotations/src/db/migrations.ts

File renamed without changes.

exercises/01.sampling/01.solution.simple/src/db/schema.ts renamed to exercises/01.advanced-tools/01.problem.annotations/src/db/schema.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ export const entrySchema = z.object({
2525
updatedAt: timestampSchema,
2626
})
2727

28+
export const entryWithTagsSchema = entrySchema.extend({
29+
tags: z.array(z.object({ id: z.number(), name: z.string() })),
30+
})
31+
2832
export const newEntrySchema = z.object({
2933
title: z.string(),
3034
content: z.string(),
@@ -56,6 +60,13 @@ export const entryTagSchema = z.object({
5660
updatedAt: timestampSchema,
5761
})
5862

63+
export const entryIdSchema = { id: z.number().describe('The ID of the entry') }
64+
export const tagIdSchema = { id: z.number().describe('The ID of the tag') }
65+
export const entryTagIdSchema = {
66+
entryId: z.number().describe('The ID of the entry'),
67+
tagId: z.number().describe('The ID of the tag'),
68+
}
69+
5970
export const createEntryInputSchema = {
6071
title: z.string().describe('The title of the entry'),
6172
content: z.string().describe('The content of the entry'),
@@ -95,13 +106,61 @@ export const createEntryInputSchema = {
95106
.describe('The IDs of the tags to add to the entry'),
96107
}
97108

109+
export const updateEntryInputSchema = {
110+
id: z.number(),
111+
title: z.string().optional().describe('The title of the entry'),
112+
content: z.string().optional().describe('The content of the entry'),
113+
mood: z
114+
.string()
115+
.nullable()
116+
.optional()
117+
.describe(
118+
'The mood of the entry (for example: "happy", "sad", "anxious", "excited")',
119+
),
120+
location: z
121+
.string()
122+
.nullable()
123+
.optional()
124+
.describe(
125+
'The location of the entry (for example: "home", "work", "school", "park")',
126+
),
127+
weather: z
128+
.string()
129+
.nullable()
130+
.optional()
131+
.describe(
132+
'The weather of the entry (for example: "sunny", "cloudy", "rainy", "snowy")',
133+
),
134+
isPrivate: z
135+
.number()
136+
.optional()
137+
.describe('Whether the entry is private (1 for private, 0 for public)'),
138+
isFavorite: z
139+
.number()
140+
.optional()
141+
.describe(
142+
'Whether the entry is a favorite (1 for favorite, 0 for not favorite)',
143+
),
144+
}
145+
98146
export const createTagInputSchema = {
99147
name: z.string().describe('The name of the tag'),
100148
description: z.string().optional().describe('The description of the tag'),
101149
}
102150

151+
export const updateTagInputSchema = {
152+
id: z.number(),
153+
...Object.fromEntries(
154+
Object.entries(createTagInputSchema).map(([key, value]) => [
155+
key,
156+
value.nullable().optional(),
157+
]),
158+
),
159+
}
160+
103161
export type Entry = z.infer<typeof entrySchema>
104162
export type NewEntry = z.infer<typeof newEntrySchema>
105163
export type Tag = z.infer<typeof tagSchema>
106164
export type NewTag = z.infer<typeof newTagSchema>
107165
export type EntryTag = z.infer<typeof entryTagSchema>
166+
export type EntryWithTags = z.infer<typeof entryWithTagsSchema>

exercises/01.sampling/01.problem.simple/src/db/seed.ts renamed to exercises/01.advanced-tools/01.problem.annotations/src/db/seed.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,8 @@ import { DB } from './index.js'
44

55
async function seed() {
66
const dbPath = path.join(process.cwd(), 'db.sqlite')
7-
const dbExists = await fs.stat(dbPath).then(
8-
() => true,
9-
() => false,
10-
)
117
// delete the db file if it exists
12-
if (dbExists) await fs.unlink(dbPath)
8+
await fs.unlink(dbPath).catch(() => {})
139

1410
const db = DB.getInstance(dbPath)
1511

exercises/01.sampling/01.problem.simple/src/db/utils.ts renamed to exercises/01.advanced-tools/01.problem.annotations/src/db/utils.ts

File renamed without changes.

exercises/01.sampling/01.problem.simple/src/index.test.ts renamed to exercises/01.advanced-tools/01.problem.annotations/src/index.test.ts

File renamed without changes.

exercises/01.sampling/01.problem.simple/src/index.ts renamed to exercises/01.advanced-tools/01.problem.annotations/src/index.ts

File renamed without changes.

0 commit comments

Comments
 (0)