feat(domain): add seed dataset and domain barrel

Add SCHEMA_VERSION and createSeedDataset to initialize first-run state
with three budget envelopes (Video Games, Food, Events and Social),
newId() for generating unique identifiers, and complete the domain
export barrel with all pure domain modules.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Lucas Winther
2026-09-01 19:51:57 +02:00
co-authored by Claude Opus 5
parent 564a5f4079
commit 438d53d016
3 changed files with 96 additions and 0 deletions
+10
View File
@@ -1 +1,11 @@
export const DOMAIN_READY = true;
export * from "./types.ts";
export * from "./money.ts";
export * from "./months.ts";
export * from "./fx.ts";
export * from "./allocation.ts";
export * from "./charges.ts";
export * from "./fold.ts";
export * from "./views.ts";
export * from "./seed.ts";
+48
View File
@@ -0,0 +1,48 @@
import { test, expect } from "bun:test";
import { createSeedDataset, SCHEMA_VERSION, newId } from "./seed.ts";
import { monthView } from "./views.ts";
test("seeds exactly the three named posts, in order", () => {
const data = createSeedDataset("2026-09");
expect(data.posts.map((p) => p.name)).toEqual([
"Video Games",
"Food",
"Events and Social",
]);
expect(data.posts.map((p) => p.order)).toEqual([0, 1, 2]);
});
test("seeded posts are active with a zero fixed standing rule", () => {
const data = createSeedDataset("2026-09");
for (const post of data.posts) {
expect(post.archived).toBe(false);
expect(post.standingRule).toEqual({
kind: "fixed",
amount: { amount: 0, currency: "DKK" },
});
}
});
test("the fold starts at the given month and DKK is the base currency", () => {
const data = createSeedDataset("2026-09");
expect(data.settings.foldStartMonth).toBe("2026-09");
expect(data.settings.baseCurrency).toBe("DKK");
expect(data.settings.schemaVersion).toBe(SCHEMA_VERSION);
});
test("seeds a month record with zero income and no purchases", () => {
const data = createSeedDataset("2026-09");
expect(data.months.map((m) => m.id)).toEqual(["2026-09"]);
expect(data.purchases).toEqual([]);
});
test("the seed dataset renders a valid month view", () => {
const view = monthView(createSeedDataset("2026-09"), "2026-09");
expect(view.rows).toHaveLength(3);
expect(view.income).toBe(0);
expect(view.unallocated).toBe(0);
});
test("newId returns distinct ids", () => {
expect(newId()).not.toBe(newId());
});
+38
View File
@@ -0,0 +1,38 @@
import type { Dataset, MonthId, Post } from "./types.ts";
export const SCHEMA_VERSION = 1;
const SEED_POST_NAMES = ["Video Games", "Food", "Events and Social"] as const;
export function newId(): string {
return crypto.randomUUID();
}
/**
* The first-run dataset. Takes the start month as an argument because the
* domain layer must not read the ambient clock.
*/
export function createSeedDataset(startMonth: MonthId): Dataset {
const posts: Post[] = SEED_POST_NAMES.map((name, order) => ({
id: newId(),
name,
order,
archived: false,
currency: "DKK",
standingRule: { kind: "fixed", amount: { amount: 0, currency: "DKK" } },
}));
return {
settings: {
baseCurrency: "DKK",
foldStartMonth: startMonth,
schemaVersion: SCHEMA_VERSION,
},
fxRates: [],
posts,
months: [
{ id: startMonth, income: { amount: 0, currency: "DKK" }, ruleOverrides: {} },
],
purchases: [],
};
}