// Per-async-flow context for the mutation wraps. // // AsyncLocalStorage gives every wrap a stack of in-flight op_ids so that ops // triggered while another op is running (e.g. cascading field deletes inside // a table delete) can attach themselves to the outer op as parent_op_id, and // share its correlation_id. const { AsyncLocalStorage } = require("async_hooks"); const { randomUuid } = require("./ids"); const storage = new AsyncLocalStorage(); const enterOp = async (opId, fn) => { const current = storage.getStore(); const correlationId = current ? current.correlationId : randomUuid(); const parentStack = current ? current.stack : []; const newStore = { stack: [...parentStack, opId], correlationId: correlationId }; return await storage.run(newStore, fn); }; const currentParentOpId = () => { const store = storage.getStore(); if (!store || store.stack.length < 2) { return null; } return store.stack[store.stack.length - 2]; }; const currentCorrelationId = () => { const store = storage.getStore(); return store ? store.correlationId : null; }; // Suppression mode: when applying ingested ops we run the underlying Saltcorn // model methods but want the CRUD wraps to pass through silently — no journal // writes, no entity_id assignment. The apply handler manages those side effects // itself with the source-env's UUID. const runSuppressed = async (fn) => { const current = storage.getStore() || { stack: [], correlationId: null }; return await storage.run({ ...current, suppressed: true }, fn); }; const isSuppressed = () => { const s = storage.getStore(); return !!(s && s.suppressed); }; module.exports = { enterOp, currentParentOpId, currentCorrelationId, runSuppressed, isSuppressed };