Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

165 lines
3.9 KiB
JavaScript

function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function getLatestRun({ db, workflowId, tenantId, startedAfter }) {
const rows = await db.query(
`
select
run_id,
workflow_id,
workflow_version,
tenant,
status,
event_type,
source_payload_schema_ref,
trigger_mapping_applied,
started_at,
completed_at,
updated_at,
error_json
from workflow_runs
where workflow_id = $1
and tenant = $2
and started_at >= $3
order by started_at desc
limit 1
`,
[workflowId, tenantId, startedAfter]
);
return rows[0] ?? null;
}
async function listRecentRuns({ db, workflowId, tenantId, limit = 5 }) {
return db.query(
`
select
run_id,
status,
event_type,
started_at,
completed_at
from workflow_runs
where workflow_id = $1
and tenant = $2
order by started_at desc
limit $3
`,
[workflowId, tenantId, limit]
);
}
async function getRunSteps({ db, runId }) {
if (!db) throw new Error('getRunSteps requires db');
if (!runId) throw new Error('getRunSteps requires runId');
return db.query(
`
select
step_id,
run_id,
step_path,
definition_step_id,
status,
attempt,
duration_ms,
error_json,
started_at,
completed_at
from workflow_run_steps
where run_id = $1
order by started_at asc, step_path asc
`,
[runId]
);
}
async function getRunLogs({ db, runId, limit = 200 }) {
if (!db) throw new Error('getRunLogs requires db');
if (!runId) throw new Error('getRunLogs requires runId');
return db.query(
`
select
log_id,
run_id,
step_id,
step_path,
level,
message,
context_json,
correlation_key,
event_name,
source,
created_at
from workflow_run_logs
where run_id = $1
order by created_at desc
limit $2
`,
[runId, limit]
);
}
function summarizeSteps(steps) {
const counts = {};
const failed = [];
for (const s of steps) {
const status = String(s.status || 'UNKNOWN');
counts[status] = (counts[status] || 0) + 1;
if (status === 'FAILED') {
failed.push({
stepPath: s.step_path,
definitionStepId: s.definition_step_id,
attempt: s.attempt,
error: s.error_json ?? null
});
}
}
return { counts, failed };
}
async function waitForRun({
db,
workflowId,
tenantId,
startedAfter,
timeoutMs,
pollMs = 500,
terminalOnly = true
}) {
if (!db) throw new Error('waitForRun requires db');
if (!workflowId) throw new Error('waitForRun requires workflowId');
if (!tenantId) throw new Error('waitForRun requires tenantId');
if (!startedAfter) throw new Error('waitForRun requires startedAfter (ISO)');
if (!timeoutMs || timeoutMs <= 0) throw new Error('waitForRun requires timeoutMs');
const deadline = Date.now() + timeoutMs;
let last = null;
while (Date.now() < deadline) {
// eslint-disable-next-line no-await-in-loop
last = await getLatestRun({ db, workflowId, tenantId, startedAfter });
if (last) {
const status = String(last.status || '');
const isTerminal = status === 'SUCCEEDED' || status === 'FAILED' || status === 'CANCELED';
if (!terminalOnly || isTerminal) return last;
}
// eslint-disable-next-line no-await-in-loop
await sleep(pollMs);
}
const recent = await listRecentRuns({ db, workflowId, tenantId, limit: 10 });
const err = new Error(
`Timed out waiting for workflow run (workflowId=${workflowId}, tenantId=${tenantId}, startedAfter=${startedAfter}).`
);
err.details = { lastSeen: last, recentRuns: recent };
throw err;
}
module.exports = {
waitForRun,
listRecentRuns,
getRunSteps,
getRunLogs,
summarizeSteps
};