fronds/blog/entities/Post.ts
1 import { entity, oneOf, primary, readOnly, text } from '@fougere/schema';
2
3 export default class Post extends entity({
4 id: primary(),
5 title: text({ min: 1, max: 160 }),
6 status: readOnly(oneOf('draft', 'published', { default : 'draft' })),
7 }) {}
fronds/blog/entities/PostPublished.ts
1 import Post from './Post.js';
2
3 export default class PostPublished extends Post.pick('id', 'title') {}c
fronds/blog/handlers/PostHandler.ts
+ import { ErrorCode, FougereError, Repository, type Emit } from '@fougere/core';
2 import Post from '../entities/Post.js';
+ import type PostPublished from '../entities/PostPublished.js';
4
5 export default class PostHandler extends Repository<Post> {
+ constructor (private published: Emit<PostPublished>) {
7 super();
8 }
9
10 async publish(id: Post['id']): Promise<Post> {
11 const post = await this.findById(id);
12 if (!post) throw new FougereError({ code: ErrorCode.NOT_FOUND, message: 'No such post' });
13 if (post.status === 'published') throw new FougereError({ code: ErrorCode.CONFLICT, message: 'Already published' });
14
+ await this.published({ id: post.id, title: post.title });
16
17 return this.update(id, { status: 'published' });
18 }
19 }
fronds/mail/handlers/MailHandler.ts
1 import type { Fact } from '@fougere/core';
2 import type PostPublished from '@fronds/blog/entities/PostPublished.js';
3 import Mailer from '../services/Mailer.js';
4
5 export default class MailHandler {
6 constructor (private mailer: Mailer) {}
7
8 async onPublished(post: Fact<PostPublished>): Promise<void> {a b
9 await this.mailer.send(`Published: ${post.title}`);
10 }
11 }
fronds/mail/services/AuditMailer.ts
1 import type { Logger } from '@fougere/core';
2 import type Mailer from './Mailer.js';
3
4 export default class AuditMailer implements Partial<Mailer> {
5 constructor (private inner: Mailer, private log: Logger) {}
6
7 async send(subject: string): Promise<void> {
8 this.log.info('sending', subject);
9
10 return this.inner.send(subject);
11 }
12 }
fronds/mail/services/ConsoleMailer.ts
1 import Mailer from './Mailer.js';
2
3 export default class ConsoleMailer extends Mailer {
4 async send(subject: string): Promise<void> {
5 console.log(`mail: ${subject}`);
6 }
7 }
fronds/mail/services/Mailer.ts
1 export default abstract class Mailer {
2 abstract send(subject: string): Promise<void>;
3 }
fougere.config.ts
1 import '@fougere/adapter-rest';
2 import '@fougere/adapter-sql';
3 import { defineFougere } from '@fougere/core';
4
5 export default defineFougere({
6 protocols: { rest: {} },
7 sources: { main: { type : 'sqlite', path: '.data/blog.db' } },
8 fronds: { '@fougere/log': {} },
9 });
package.json
1 {
2 "name": "my-blog",
3 "private ": true,
4 "type ": "module",
5 "scripts": {
6 "typecheck": "tsc",
7 "test": "vitest run --config tests/vitest.config.ts"
8 },
9 "dependencies": {
10 "@fougere/adapter-rest": "latest",
11 "@fougere/adapter-sql": "latest",
12 "@fougere/core": "latest",
13 "@fougere/log": "latest",
14 "@fougere/schema": "latest",
15 "better-sqlite3": "^13.0.3"
16 },
17 "devDependencies": {
18 "@fougere/cli": "latest",
19 "@fougere/testing": "latest",
20 "@types/node": "^22.19.15",
21 "typescript": "^7.0.2",
22 "vitest": "^4.1.0"
23 }
24 }