Files
famapp/src/modules/_core/registry.ts
T
ginnoir 7f2c5a44dd feat: garden care reminder bodies, task sync, upload cleanup
- care reminders now fire with plant-specific body ("Time to water Pothos")
  via title/body columns on the reminders table (migration 0016)
- bi-directional task sync: checking off a garden-linked task list item
  creates a care log; logging care from garden marks the linked task done
  (list_items.metadata stores gardenPlantId + gardenCareType linkage)
- upload error response no longer leaks debug detail; error message
  corrected from "5 MB" to "100 MB"
2026-06-01 23:58:00 -05:00

108 lines
2.9 KiB
TypeScript

import type {
ModuleManifest,
EntityTypeRegistration,
DashboardWidget,
QuickAddAction,
} from "./module";
const modules = new Map<string, ModuleManifest>();
const entityTypes = new Map<string, EntityTypeRegistration>();
const widgets = new Map<string, DashboardWidget>();
export function registerModule(manifest: ModuleManifest): void {
modules.set(manifest.id, manifest);
for (const entity of manifest.entities) {
entityTypes.set(entity.type, entity);
}
for (const widget of manifest.dashboardWidgets ?? []) {
widgets.set(widget.id, widget);
}
}
export function getRegistry() {
return Object.freeze({
modules: Object.freeze([...modules.values()]),
entityTypes: Object.freeze([...entityTypes.values()]),
widgets: Object.freeze([...widgets.values()]),
});
}
export function getEntityType(type: string): EntityTypeRegistration | undefined {
return entityTypes.get(type);
}
export function getWidget(id: string): DashboardWidget | undefined {
return widgets.get(id);
}
export type QuickAddItem = QuickAddAction & { moduleId: string; moduleName: string };
/** Serializable subset safe to pass from server components to client components. */
export type SerializedQuickAddItem = {
id: string;
label: string;
icon?: string;
url: string;
moduleId: string;
moduleName: string;
};
export type SerializedWidgetMeta = {
id: string;
title: string;
description: string;
category?: string;
defaultSize: { w: number; h: number };
minSize?: { w: number; h: number };
maxSize?: { w: number; h: number };
defaultConfig: unknown;
};
export function getWidgetMetas(): SerializedWidgetMeta[] {
return [...widgets.values()].map(
({ id, title, description, category, defaultSize, minSize, maxSize, defaultConfig }) => ({
id,
title,
description,
category,
defaultSize,
minSize,
maxSize,
defaultConfig,
}),
);
}
// ─── Item toggle hooks ────────────────────────────────────────────────────────
type ItemTogglePayload = {
itemId: string;
metadata: Record<string, unknown>;
done: boolean;
userId: string;
};
type ItemToggleHook = (payload: ItemTogglePayload) => Promise<void>;
const itemToggleHooks = new Map<string, ItemToggleHook>();
export function registerItemToggleHook(id: string, hook: ItemToggleHook): void {
itemToggleHooks.set(id, hook);
}
export async function fireItemToggleHooks(payload: ItemTogglePayload): Promise<void> {
await Promise.allSettled([...itemToggleHooks.values()].map((h) => h(payload)));
}
export function getQuickAdds(): SerializedQuickAddItem[] {
return [...modules.values()].flatMap((manifest) =>
(manifest.quickAdds ?? []).map(({ id, label, icon, url }) => ({
id,
label,
icon,
url,
moduleId: manifest.id,
moduleName: manifest.name,
})),
);
}