build and deploy Formulation application
Consolidate the application, culinary data model, read-only runtime, and registry-backed deployment.
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
|
||||
export function applyMigrations(database: DatabaseSync, options?: { migrationsDirectory?: string }): string[];
|
||||
@@ -1,24 +0,0 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
export function applyMigrations(database, options = {}) {
|
||||
const migrationsDirectory = options.migrationsDirectory ?? path.resolve(process.cwd(), "migrations");
|
||||
const names = fs.readdirSync(migrationsDirectory).filter((name) => /^\d+.*\.sql$/.test(name)).sort((left, right) => Number(left.match(/^\d+/)[0]) - Number(right.match(/^\d+/)[0]));
|
||||
database.exec("CREATE TABLE IF NOT EXISTS schema_migrations (version INTEGER PRIMARY KEY, name TEXT NOT NULL, applied_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP)");
|
||||
const applied = [];
|
||||
for (const name of names) {
|
||||
const version = Number(name.match(/^\d+/)[0]);
|
||||
if (database.prepare("SELECT 1 FROM schema_migrations WHERE version = ?").get(version)) continue;
|
||||
database.exec("BEGIN IMMEDIATE");
|
||||
try {
|
||||
database.exec(fs.readFileSync(path.join(migrationsDirectory, name), "utf8"));
|
||||
database.prepare("INSERT INTO schema_migrations(version, name) VALUES (?, ?)").run(version, name);
|
||||
database.exec("COMMIT");
|
||||
applied.push(name);
|
||||
} catch (error) {
|
||||
database.exec("ROLLBACK");
|
||||
throw new Error(`Failed database migration ${name}`, { cause: error });
|
||||
}
|
||||
}
|
||||
return applied;
|
||||
}
|
||||
@@ -34,12 +34,12 @@ export function createSiteProjection(database) {
|
||||
const recipeConversionQuery=database.prepare("SELECT * FROM recipe_measure_conversions WHERE recipe_id=? ORDER BY id");
|
||||
const recipes = database.prepare("SELECT * FROM recipes ORDER BY id").all().map((row)=>{
|
||||
const source=json(row.source_json,{}), oldComponents=new Map((source.components??[]).map((value)=>[value.id,value])), oldSteps=new Map((source.steps??[]).map((value)=>[value.id,value]));
|
||||
const components=componentQuery.all(row.id).map((component)=>{const old=oldComponents.get(component.id)??{};return {...old,id:component.id,name:component.name,notes:json(component.notes_json,[]),items:itemQuery.all(row.id,component.id).map((item)=>({id:item.id,reference:item.ingredient_id?{ingredient_id:item.ingredient_id}:{recipe_id:item.subrecipe_id,...(item.subrecipe_revision!=null?{revision:item.subrecipe_revision}:{})},amount:{quantity:item.quantity,unit_id:item.unit_id},...(item.percentage!=null?{percentage:item.percentage}:{}),...(item.basis_member?{basis_member:true}:{}),...(item.optional?{optional:true}:{}),...(item.notes?{notes:item.notes}:{}),...(item.nutrition_retention_factor!==1?{nutrition_retention_factor:item.nutrition_retention_factor}:{}),...(itemPrepQuery.all(row.id,item.id).length?{prep:itemPrepQuery.all(row.id,item.id).map((prep)=>({action_id:prep.action_id,...(prep.yield_factor!=null?{yield_factor:prep.yield_factor}:{}),...(prep.notes?{notes:prep.notes}:{})}))}:{})}))};});
|
||||
const components=componentQuery.all(row.id).map((component)=>{const old=oldComponents.get(component.id)??{};return {...old,id:component.id,name:component.name,notes:json(component.notes_json,[]),items:itemQuery.all(row.id,component.id).map((item)=>({id:item.id,reference:item.ingredient_id?{ingredient_id:item.ingredient_id}:{recipe_id:item.subrecipe_id},amount:{quantity:item.quantity,unit_id:item.unit_id},...(item.percentage!=null?{percentage:item.percentage}:{}),...(item.basis_member?{basis_member:true}:{}),...(item.optional?{optional:true}:{}),...(item.notes?{notes:item.notes}:{}),...(item.nutrition_retention_factor!==1?{nutrition_retention_factor:item.nutrition_retention_factor}:{}),...(itemPrepQuery.all(row.id,item.id).length?{prep:itemPrepQuery.all(row.id,item.id).map((prep)=>({action_id:prep.action_id,...(prep.yield_factor!=null?{yield_factor:prep.yield_factor}:{}),...(prep.notes?{notes:prep.notes}:{})}))}:{})}))};});
|
||||
const steps=stepQuery.all(row.id).map((step,index)=>{const old=oldSteps.get(step.id)??{},equipment_ids=stepEquipmentQuery.all(row.id,step.id).map((value)=>value.equipment_id);return {...old,id:step.id,order:index+1,instruction:step.instruction,...(step.critical_control_point?{critical_control_point:true}:{}),...(equipment_ids.length?{equipment_ids}:{})};});
|
||||
const equipment_ids=recipeEquipmentQuery.all(row.id).map((value)=>value.equipment_id);
|
||||
source.measure_conversions=recipeConversionQuery.all(row.id).map((value)=>({id:value.id,from:{quantity:value.from_quantity,unit_id:value.from_unit_id},to:{quantity:value.to_quantity,unit_id:value.to_unit_id},...(value.notes?{notes:value.notes}:{}),source:json(value.source_json,{})}));
|
||||
const scaling=row.scaling_mode?{mode:row.scaling_mode,...(row.scaling_basis_id?{basis_id:row.scaling_basis_id}:{}),...(row.scaling_basis_quantity!=null&&row.scaling_basis_unit_id?{basis_amount:{quantity:row.scaling_basis_quantity,unit_id:row.scaling_basis_unit_id}}:{})}:undefined;
|
||||
return {...source,schema_version:row.schema_version,id:row.id,revision:row.revision,status:row.status,title:row.title,...(row.summary?{summary:row.summary}:{}),categories:json(row.categories_json,[]),tags:json(row.tags_json,[]),...(row.station?{station:row.station}:{}),...(row.auto_yield?{auto_yield:true}:{}),yield:{...(source.yield??{}),amount:{quantity:row.yield_quantity,unit_id:row.yield_unit_id},...(row.yield_servings!=null?{servings:row.yield_servings}:{}),...(row.yield_basis?{basis:row.yield_basis}:{})},...(scaling?{scaling}:{}),components,steps,notes:json(row.notes_json,[]),...(equipment_ids.length?{equipment_ids}:{})};
|
||||
return {...source,schema_version:row.schema_version,id:row.id,title:row.title,...(row.summary?{summary:row.summary}:{}),categories:json(row.categories_json,[]),tags:json(row.tags_json,[]),...(row.station?{station:row.station}:{}),...(row.auto_yield?{auto_yield:true}:{}),yield:{...(source.yield??{}),amount:{quantity:row.yield_quantity,unit_id:row.yield_unit_id},...(row.yield_servings!=null?{servings:row.yield_servings}:{}),...(row.yield_basis?{basis:row.yield_basis}:{})},...(scaling?{scaling}:{}),components,steps,notes:json(row.notes_json,[]),...(equipment_ids.length?{equipment_ids}:{})};
|
||||
});
|
||||
return { generated_at:new Date().toISOString(),units,ingredients,equipment,prepActions,purchaseItems,sourceMappings,recipes };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user