import type { TCorePropositionalExpression, TCorePropositionalVariable, TCorePremise } from "../schemata/propositional.js"; import type { TCoreArgument, TCoreArgumentRoleState } from "../schemata/argument.js"; import type { TCoreChangeset } from "../types/mutation.js"; import { POSITION_INITIAL } from "./position.js"; /** * A single persistence operation extracted from a changeset, tagged with * its operation type (`insert`, `update`, or `delete`) and entity kind. * * Used as the element type for the ordered operation list returned by * {@link orderChangeset}. */ export type TOrderedOperation = { type: "delete"; entity: "expression"; data: TExpr; } | { type: "delete"; entity: "variable"; data: TVar; } | { type: "delete"; entity: "premise"; data: TPremise; } | { type: "insert"; entity: "premise"; data: TPremise; } | { type: "insert"; entity: "variable"; data: TVar; } | { type: "insert"; entity: "expression"; data: TExpr; } | { type: "update"; entity: "expression"; data: TExpr; } | { type: "update"; entity: "expression"; /** * A detach: only these fields change, and every other stored * field keeps its value. Write only the fields given. */ data: Pick & { parentId: null; position: typeof POSITION_INITIAL; }; } | { type: "update"; entity: "variable"; data: TVar; } | { type: "update"; entity: "premise"; data: TPremise; } | { type: "update"; entity: "argument"; data: TArg; } | { type: "update"; entity: "roles"; data: TCoreArgumentRoleState; }; /** * Converts a changeset into a flat, ordered array of persistence operations * that is safe to execute sequentially against a relational store with * foreign-key constraints. * * The FK dependency chain is: * - `expression.premiseId` → `premise.id` * - `expression.variableId` → `variable.id` (for variable-type expressions) * - `expression.parentId` → `expression.id` (self-FK for tree structure) * - `variable.boundPremiseId` → `premise.id` (for premise-bound variables) * - `variable.argumentId` → `argument.id` * - `premise.argumentId` → `argument.id` * * The resulting order guarantees that every referenced row exists before any * row that depends on it is inserted, and that every dependent row is removed * before the row it references is deleted. * * It does so without knowing what the store holds. A removed expression's * entry carries its parent at removal time, and a change in the same * changeset may have moved it first, so the stored row can still point at a * different parent. Every removed expression is therefore detached before * any expression is deleted, by an update that carries only its `id`, * `parentId: null` and `position` {@link POSITION_INITIAL}. Two conditions * follow for the store: * - Write only the fields an update carries. A detach is not a whole row; * the rest of the removed entry may be stale, and can name a variable this * changeset has not inserted yet. * - Until the deletes run, a premise can briefly have more than one root, so * a rule allowing one root per premise must be checked at the end of the * transaction, not per statement. * * An expression updated to point at a parent, a variable or a premise that * the same changeset inserts is moved to the root in phase 2 and updated in * full after the inserts. In phase 2, one late only for its new parent is * written whole with the parent cleared, since its variable and premise are * not new ones, so it leaves its old variable before that can be deleted. * One that names a new variable or premise can only be detached (`id`, * `parentId`, `position`), so the variable and premise deletes (phases 4 * and 5) wait until after its full update: the stored row still names its * old variable and premise, which may be among them, and a store whose keys * cascade would otherwise delete the row. * * Variable updates run between the variable and premise deletes, so a * rebound variable leaves its old premise before that premise is deleted, * and a rename can take a symbol a removed variable frees. One bound to a * premise the same changeset inserts runs after that insert instead, and * the premise deletes wait for it. A changeset with no variable update and * no expression update that needs an insert first keeps its previous order * exactly: the phase list below with every step for those two left out. * * Known exceptions, only when deletes wait, for a store that checks the rule * per statement. The inserts then run while the removed rows still exist, so: * - a variable inserted with the symbol of one being removed breaks a rule * that symbols are unique per argument, and so does one inserted with the * symbol a variable is being renamed off (both only when the variable * deletes wait, for an expression update: the renames then run after the * inserts); * - a premise inserted as the conclusion while the removed conclusion * premise still exists breaks a rule of one conclusion per argument. * * Ordering phases: * 1. Update premises — ensure premise rows have correct metadata before * dependent deletes run. * 2. Reparent expressions — update expressions whose IDs are NOT in the * removed set. This detaches reparented children from doomed parents * before ON DELETE CASCADE runs. Expressions that appear in both * modified and removed are skipped (the row is about to be deleted). * Then detach every removed expression (see above), so no stored row * points at an expression about to be deleted. A detach carries only * `id`, `parentId` and `position`. An update that needs an insert first * is written early here too (see above). * 3. Delete expressions — expression rows hold FKs to variables and premises, * so they must be removed first. Every one is detached by now, so their * order does not matter; children still come before parents. * 4. Delete variables — safe after expression deletes (no remaining FK * references from expressions). Then update variables, except one bound * to a new premise; one that is also removed is skipped, as an * expression is in phase 2. Held until after phase 8 when an expression update * names a new variable or premise (see above). * 5. Delete premises — safe after all child rows are removed. Held with * phase 4, or until after phase 6 for a variable bound to a new premise. * 6. Insert premises — new premises must exist before their expressions and * variables can be inserted. Then update the variables bound to them, * and delete premises if phase 5 waited for those. * 7. Insert variables — new variables must exist before variable-type * expressions can reference them. * 8. Insert expressions — topologically sorted so parent expressions are * inserted before their children (satisfies the parentId self-FK). Then * the updates held back in phase 2, then phases 4 and 5 if held. * 9. (No-op — variable updates are emitted with phase 4 or 6, or after * phase 8 when the deletes are held.) * 10. (No-op — expression updates are emitted in phase 2 or after phase 8.) * 11. Update argument metadata — if present. * 12. Update role state — if present. * * @param changeset - The changeset to convert into ordered operations. * @returns A flat array of {@link TOrderedOperation} entries in FK-safe * execution order. Returns an empty array if the changeset is empty. */ export declare function orderChangeset(changeset: TCoreChangeset): TOrderedOperation[]; //# sourceMappingURL=changeset-order.d.ts.map