This repo is following Readme-Driven Development with LLM Codegen. Until you see tagged releases, what is below is aspirational. See the article at My LLM Code Generation Workflow (for now) that explains this readme in git is for aider
to understand what we are building.
This monorepo implements a decoupled microfrontend architecture called Omega-µfe.
we use using Svelte, TypeScript, and GraphQL to demonstrate how to use modern browser APIs to avoid heavy frameworks. Key goals include:
userStore
, dataStore
)graphql-codegen
Generate TypeScript types from GraphQL schema
npx graphql-codegen --config codegen.yml
text
- **Example `codegen.yml`**:
schema: "http://localhost:4000/graphql"
documents: "./src/**/*.graphql"
generates:
./src/generated/types.ts:
plugins:
- "typescript"
- "typescript-operations"
/ Example: Immutable store using Immer
import { writable } from 'svelte/store';
import { produce } from 'immer';
// Create base writable store
const createImmerStore = <T>(initialState: T) => {
const { subscribe, set, update } = writable<T>(initialState);
return {
subscribe,
set,
// Use Immer's produce for immutable updates
update: (fn: (state: T) => void) => update(state => produce(state, fn))
};
};
// Example store
export const userStore = createImmerStore({ name: '', id: '', role: '' });
// Usage in component
userStore.update(state => {
state.name = "New Name"; // Looks mutable but creates immutable update
});
// Dispatch domain event from a microfrontend
// Using BroadcastChannel API (implementation detail)
const channel = new BroadcastChannel("domain_events");
channel.postMessage({
type: "USER_SELECTED",
payload: { userId: "123" }
});
// Data Access Layer listens for domain events
channel.onmessage = (event) => {
const { type, payload } = event.data;
if (type === "USER_SELECTED") {
// Translate domain event to data operation
dataAccessLayer.fetchUser(payload.userId);
}
};
type Product {
oldField: String @deprecated(reason: "Use newField instead")
newField: String!
}
Use a Backend-For-Frontend layer to aggregate microservices using Schema Stitching (never invasive Federation)
We're creating a system to handle GraphQL data in Svelte that: Keeps your data immutable (prevents accidental mutations) Makes updating nested data easy (without the usual headache) Works well with TypeScript
Object.freeze() in Development
if (import.meta.env.DEV) {
Object.freeze(initialState)
}
In simple terms: This is just a safety check during development. It "locks" your data so if you accidentally try to modify it directly (instead of using the proper methods), JavaScript will throw an error. It's like putting training wheels on your code while developing. In production: This check is skipped for better performance.
Here's the practical, stripped-down approach:
Create your store with Immer:
// stores/user-store.ts
import { writable } from 'svelte/store'
import { produce } from 'immer'
import type { UserData } from '../types'
// Create a store with initial data
const createUserStore = () => {
const { subscribe, update } = writable<UserData>({ user: null })
return {
subscribe,
// This is the key method you'll use to update data
update: (fn: (draft: UserData) => void) => {
update(state => produce(state, fn))
}
}
}
export const userStore = createUserStore()
Use it in your Svelt components:
// In a Svelte component
import { userStore } from '../stores/user-store'
// When you get data from GraphQL
onMount(async () => {
const result = await graphqlClient.query({
query: USER_QUERY,
variables: { id: "123" }
})
// Update the store with the result
userStore.update(draft => {
draft.user = result.data.user
})
})
// Later, when you need to update something deeply nested
function updateCommentText(postId, commentId, newText) {
userStore.update(draft => {
// Without Immer, this would be a nightmare
const post = draft.user.posts.find(p => p.id === postId)
const comment = post?.comments.find(c => c.id === commentId)
if (comment) {
comment.text = newText // This looks like direct mutation but Immer makes it safe
}
})
}
That's it! The beauty is in the simplicity - you write code that looks like you're directly changing objects, but behind the scenes Immer creates proper immutable updates.
.
├── packages/
│ ├── shell/ # Container app
│ ├── mfe-home/ # Svelte microfrontend
│ ├── mfe-dashboard/ # Another Svelte MF
│ ├── shared-utils/ # Pure TS utilities
│ ├── model/ # GraphQL schema + codegen
│ └── dal/ # Apollo Client instance
├── package.json
└── tsconfig.base.json # Base TypeScript config
# Root package.json
{
"name": "monorepo",
"workspaces": [
"packages/*"
],
"scripts": {
"build": "npm run build --workspaces",
"codegen": "npm run codegen --workspace=model"
}
}
cd packages/model
npm init -y
npm install @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations
# codegen.yml
schema: "./schema.graphql"
generates:
./src/generated-types.ts:
plugins:
- "typescript"
- "typescript-operations"
config:
immutableTypes: true
enumsAsTypes: true
cd packages/dal
npm init -y
npm install @apollo/client graphql immer
// packages/dal/src/client.ts
import { ApolloClient, InMemoryCache } from '@apollo/client';
import { GeneratedTypes } from 'model'; // Import from your model package
export const client = new ApolloClient({
uri: 'YOUR_GRAPHQL_ENDPOINT',
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
// Use generated types here
yourField: {
merge(existing: GeneratedTypes.YourType, incoming: GeneratedTypes.YourType) {
return { ...existing, ...incoming };
}
}
}
}
}
})
});
cd packages/mfe-home
npm init -y
npm install svelte @sveltejs/vite-plugin-svelte
// svelte.config.js
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
export default {
preprocess: vitePreprocess(),
compilerOptions: {
customElement: true
}
};
Shared TypeScript Config:
// tsconfig.base.json
{
"compilerOptions": {
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
VS Code Workspace Settings (.vscode/settings.json):
{
"eslint.validate": [
"svelte",
"typescript"
],
"prettier.enable": true,
"typescript.tsdk": "node_modules/typescript/lib",
"svelte.enable-ts-plugin": true
}
Build Pipeline:
# In model/package.json
"scripts": {
"codegen": "graphql-codegen --config codegen.yml"
}
# In each Svelte MF package.json
"scripts": {
"build": "vite build"
}
# In shared-utils package.json
"scripts": {
"build": "tsc"
}
Start codegen watcher:
npm run codegen -- --watch
Use npm link for local package dependencies:
cd packages/model
npm link
cd ../dal
npm link model
Debugging Setup (.vscode/launch.json):
{
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome",
"url": "http://localhost:5500/packages/shell",
"webRoot": "${workspaceFolder}"
}
]
}