@noodleseed/one 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/alerts-ops.d.ts +2 -0
- package/dist/commands/alerts-ops.d.ts.map +1 -0
- package/dist/commands/alerts-ops.js +249 -0
- package/dist/commands/alerts-ops.js.map +1 -0
- package/dist/commands/analytics-ops.d.ts +13 -0
- package/dist/commands/analytics-ops.d.ts.map +1 -1
- package/dist/commands/analytics-ops.js +5 -2
- package/dist/commands/analytics-ops.js.map +1 -1
- package/dist/commands/catalog-data-resources.d.ts.map +1 -1
- package/dist/commands/catalog-data-resources.js +45 -0
- package/dist/commands/catalog-data-resources.js.map +1 -1
- package/dist/commands/github-ops.d.ts.map +1 -1
- package/dist/commands/github-ops.js +24 -4
- package/dist/commands/github-ops.js.map +1 -1
- package/dist/commands/github-runs-ops.d.ts +32 -0
- package/dist/commands/github-runs-ops.d.ts.map +1 -0
- package/dist/commands/github-runs-ops.js +132 -0
- package/dist/commands/github-runs-ops.js.map +1 -0
- package/node_modules/@noodle-borg/agent-kit/dist/curated/command-groups.d.ts.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/curated/command-groups.js +5 -0
- package/node_modules/@noodle-borg/agent-kit/dist/curated/command-groups.js.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.d.ts.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js +1 -0
- package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
- package/node_modules/@noodle-borg/authoring/dist/recording.js +18 -23
- package/node_modules/@noodle-borg/authoring/dist/recording.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/alert-evaluator.d.ts +57 -0
- package/node_modules/@noodle-borg/service/dist/alert-evaluator.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/alert-evaluator.js +158 -0
- package/node_modules/@noodle-borg/service/dist/alert-evaluator.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/alert-webhook.d.ts +72 -0
- package/node_modules/@noodle-borg/service/dist/alert-webhook.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/alert-webhook.js +138 -0
- package/node_modules/@noodle-borg/service/dist/alert-webhook.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/github/pending-installations.d.ts +15 -8
- package/node_modules/@noodle-borg/service/dist/github/pending-installations.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/github/pending-installations.js +16 -9
- package/node_modules/@noodle-borg/service/dist/github/pending-installations.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/index.d.ts +10 -2
- package/node_modules/@noodle-borg/service/dist/index.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/index.js +9 -1
- package/node_modules/@noodle-borg/service/dist/index.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/options.d.ts +20 -1
- package/node_modules/@noodle-borg/service/dist/options.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/alerts-dispatch.d.ts +28 -0
- package/node_modules/@noodle-borg/service/dist/routes/alerts-dispatch.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/alerts-dispatch.js +50 -0
- package/node_modules/@noodle-borg/service/dist/routes/alerts-dispatch.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/alerts.d.ts +39 -0
- package/node_modules/@noodle-borg/service/dist/routes/alerts.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/alerts.js +204 -0
- package/node_modules/@noodle-borg/service/dist/routes/alerts.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/contracts.d.ts +149 -0
- package/node_modules/@noodle-borg/service/dist/routes/contracts.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/contracts.js +81 -0
- package/node_modules/@noodle-borg/service/dist/routes/contracts.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/github-connection.js +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/github-connection.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/github-dispatch.d.ts +2 -1
- package/node_modules/@noodle-borg/service/dist/routes/github-dispatch.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/github-dispatch.js +37 -2
- package/node_modules/@noodle-borg/service/dist/routes/github-dispatch.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/github-runs.d.ts +8 -0
- package/node_modules/@noodle-borg/service/dist/routes/github-runs.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/github-runs.js +33 -0
- package/node_modules/@noodle-borg/service/dist/routes/github-runs.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/github-setup.d.ts +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/github-setup.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/github-setup.js +3 -3
- package/node_modules/@noodle-borg/service/dist/routes/github-setup.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/github-webhook.d.ts +29 -0
- package/node_modules/@noodle-borg/service/dist/routes/github-webhook.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/github-webhook.js +355 -0
- package/node_modules/@noodle-borg/service/dist/routes/github-webhook.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/org-admin-dispatch.d.ts +27 -0
- package/node_modules/@noodle-borg/service/dist/routes/org-admin-dispatch.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/org-admin-dispatch.js +61 -0
- package/node_modules/@noodle-borg/service/dist/routes/org-admin-dispatch.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/paths.d.ts +20 -1
- package/node_modules/@noodle-borg/service/dist/routes/paths.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/paths.js +54 -0
- package/node_modules/@noodle-borg/service/dist/routes/paths.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/serve.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/serve.js +42 -1
- package/node_modules/@noodle-borg/service/dist/serve.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/service.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/service.js +37 -53
- package/node_modules/@noodle-borg/service/dist/service.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store/alert-rules-json-file.d.ts +21 -0
- package/node_modules/@noodle-borg/service/dist/store/alert-rules-json-file.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/alert-rules-json-file.js +115 -0
- package/node_modules/@noodle-borg/service/dist/store/alert-rules-json-file.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/alert-rules.d.ts +108 -0
- package/node_modules/@noodle-borg/service/dist/store/alert-rules.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/alert-rules.js +181 -0
- package/node_modules/@noodle-borg/service/dist/store/alert-rules.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/github-connections-json-file.d.ts +1 -0
- package/node_modules/@noodle-borg/service/dist/store/github-connections-json-file.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store/github-connections-json-file.js +4 -0
- package/node_modules/@noodle-borg/service/dist/store/github-connections-json-file.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store/github-connections.d.ts +1 -0
- package/node_modules/@noodle-borg/service/dist/store/github-connections.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store/github-connections.js +4 -0
- package/node_modules/@noodle-borg/service/dist/store/github-connections.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store/github-runs-json-file.d.ts +22 -0
- package/node_modules/@noodle-borg/service/dist/store/github-runs-json-file.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/github-runs-json-file.js +116 -0
- package/node_modules/@noodle-borg/service/dist/store/github-runs-json-file.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/github-runs.d.ts +51 -0
- package/node_modules/@noodle-borg/service/dist/store/github-runs.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/github-runs.js +158 -0
- package/node_modules/@noodle-borg/service/dist/store/github-runs.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/pending-installations-postgres.d.ts +19 -0
- package/node_modules/@noodle-borg/service/dist/store/pending-installations-postgres.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/pending-installations-postgres.js +46 -0
- package/node_modules/@noodle-borg/service/dist/store/pending-installations-postgres.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/postgres-alerts.d.ts +26 -0
- package/node_modules/@noodle-borg/service/dist/store/postgres-alerts.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/postgres-alerts.js +142 -0
- package/node_modules/@noodle-borg/service/dist/store/postgres-alerts.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/postgres-github-runs.d.ts +9 -0
- package/node_modules/@noodle-borg/service/dist/store/postgres-github-runs.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/postgres-github-runs.js +113 -0
- package/node_modules/@noodle-borg/service/dist/store/postgres-github-runs.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/postgres-github.d.ts +1 -0
- package/node_modules/@noodle-borg/service/dist/store/postgres-github.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store/postgres-github.js +7 -2
- package/node_modules/@noodle-borg/service/dist/store/postgres-github.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store/postgres-schema.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store/postgres-schema.js +49 -0
- package/node_modules/@noodle-borg/service/dist/store/postgres-schema.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store/postgres.d.ts +9 -2
- package/node_modules/@noodle-borg/service/dist/store/postgres.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store/postgres.js +17 -1
- package/node_modules/@noodle-borg/service/dist/store/postgres.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store.d.ts +104 -0
- package/node_modules/@noodle-borg/service/dist/store.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store.js +2 -0
- package/node_modules/@noodle-borg/service/dist/store.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, readdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { DEFAULT_DEPLOY_RUN_LIST_LIMIT, findCreateDuplicate, matchesCancelFilter, shapeDeployRunList, supersedesQueued, validateCancelFilter, validateCreateDeployRunInput, } from './github-runs.js';
|
|
5
|
+
import { validateSlug } from './validate.js';
|
|
6
|
+
/**
|
|
7
|
+
* JSON-file {@link GithubDeployRunStore}: one file per run at
|
|
8
|
+
* `<dataDir>/github-runs/<runId>.json`, written atomically (temp file + `rename`, matching
|
|
9
|
+
* `JsonFileGithubConnectionStore`). `runId` is service-minted (`run-` + hex), so it is always a safe
|
|
10
|
+
* filename component. Every read-check-write mutation runs through the same mutation queue the
|
|
11
|
+
* connections store uses — the json-file analogue of the Postgres transaction, so concurrent webhook
|
|
12
|
+
* deliveries cannot both pass the idempotency checks.
|
|
13
|
+
*/
|
|
14
|
+
export class JsonFileGithubDeployRunStore {
|
|
15
|
+
#dir;
|
|
16
|
+
#now;
|
|
17
|
+
#mutationQueue = Promise.resolve();
|
|
18
|
+
constructor(dataDir, options = {}) {
|
|
19
|
+
this.#dir = join(dataDir, 'github-runs');
|
|
20
|
+
this.#now = options.now ?? (() => new Date());
|
|
21
|
+
}
|
|
22
|
+
#withMutation(operation) {
|
|
23
|
+
const run = this.#mutationQueue.then(operation, operation);
|
|
24
|
+
this.#mutationQueue = run.then(() => undefined, () => undefined);
|
|
25
|
+
return run;
|
|
26
|
+
}
|
|
27
|
+
createDeployRun(input) {
|
|
28
|
+
return this.#withMutation(async () => {
|
|
29
|
+
const safe = validateCreateDeployRunInput(input);
|
|
30
|
+
const all = await this.#loadAll();
|
|
31
|
+
const duplicate = findCreateDuplicate(all, safe);
|
|
32
|
+
if (duplicate !== undefined)
|
|
33
|
+
return { ok: false, ...duplicate };
|
|
34
|
+
const nowIso = this.#now().toISOString();
|
|
35
|
+
const superseded = [];
|
|
36
|
+
for (const record of all) {
|
|
37
|
+
if (!supersedesQueued(record, safe))
|
|
38
|
+
continue;
|
|
39
|
+
await this.#writeRecord({ ...record, status: 'superseded', finishedAt: nowIso });
|
|
40
|
+
superseded.push(record.runId);
|
|
41
|
+
}
|
|
42
|
+
const record = { ...safe, status: 'queued', createdAt: nowIso };
|
|
43
|
+
await this.#writeRecord(record);
|
|
44
|
+
return { ok: true, record, superseded };
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async getDeployRun(org, runId) {
|
|
48
|
+
const safeOrg = validateSlug('org', org);
|
|
49
|
+
const record = await this.#readRecord(runId);
|
|
50
|
+
return record?.orgSlug === safeOrg ? record : undefined;
|
|
51
|
+
}
|
|
52
|
+
async listDeployRuns(org, app, opts = {}) {
|
|
53
|
+
const safeOrg = validateSlug('org', org);
|
|
54
|
+
const safeApp = validateSlug('app', app);
|
|
55
|
+
const matching = (await this.#loadAll()).filter((record) => record.orgSlug === safeOrg && record.appSlug === safeApp);
|
|
56
|
+
return shapeDeployRunList(matching, opts.limit ?? DEFAULT_DEPLOY_RUN_LIST_LIMIT);
|
|
57
|
+
}
|
|
58
|
+
cancelQueuedDeployRuns(filter) {
|
|
59
|
+
return this.#withMutation(async () => {
|
|
60
|
+
const safe = validateCancelFilter(filter);
|
|
61
|
+
const nowIso = this.#now().toISOString();
|
|
62
|
+
const canceled = [];
|
|
63
|
+
for (const record of await this.#loadAll()) {
|
|
64
|
+
if (!matchesCancelFilter(record, safe))
|
|
65
|
+
continue;
|
|
66
|
+
await this.#writeRecord({ ...record, status: 'canceled', finishedAt: nowIso });
|
|
67
|
+
canceled.push(record.runId);
|
|
68
|
+
}
|
|
69
|
+
return canceled;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async #readRecord(runId) {
|
|
73
|
+
// runId is service-minted, but never trust it as a path segment anyway.
|
|
74
|
+
if (!/^[A-Za-z0-9_-]+$/.test(runId))
|
|
75
|
+
return undefined;
|
|
76
|
+
try {
|
|
77
|
+
const text = await readFile(join(this.#dir, `${runId}.json`), 'utf8');
|
|
78
|
+
return JSON.parse(text);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async #loadAll() {
|
|
85
|
+
let names;
|
|
86
|
+
try {
|
|
87
|
+
names = await readdir(this.#dir);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (error.code === 'ENOENT')
|
|
91
|
+
return [];
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
const records = [];
|
|
95
|
+
for (const name of names) {
|
|
96
|
+
if (!name.endsWith('.json'))
|
|
97
|
+
continue; // skip in-flight *.tmp files
|
|
98
|
+
try {
|
|
99
|
+
const text = await readFile(join(this.#dir, name), 'utf8');
|
|
100
|
+
records.push(JSON.parse(text));
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Fail-soft: a corrupt/unreadable record must not crash the caller — skip it.
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return records;
|
|
107
|
+
}
|
|
108
|
+
async #writeRecord(record) {
|
|
109
|
+
await mkdir(this.#dir, { recursive: true });
|
|
110
|
+
const target = join(this.#dir, `${record.runId}.json`);
|
|
111
|
+
const tmp = `${target}.${randomUUID()}.tmp`;
|
|
112
|
+
await writeFile(tmp, `${JSON.stringify(record, null, 2)}\n`, 'utf8');
|
|
113
|
+
await rename(tmp, target);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=github-runs-json-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-runs-json-file.js","sourceRoot":"","sources":["../../src/store/github-runs-json-file.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { CancelQueuedDeployRunsFilter, CreateDeployRunDuplicateReason, CreateDeployRunInput, CreateDeployRunResult, DeployRunRecord, DeployRunStatus, GithubDeployRunStore, ListDeployRunsResult } from '../store.js';
|
|
2
|
+
/**
|
|
3
|
+
* In-memory {@link GithubDeployRunStore} plus the shared pure helpers every backend reuses
|
|
4
|
+
* (validation, duplicate detection, the pinned state machine, list shaping). Mirrors
|
|
5
|
+
* `github-connections.ts`: the Map backend is the non-persistent default and the test double; the
|
|
6
|
+
* exported helpers keep the json-file backend byte-equivalent in semantics.
|
|
7
|
+
*/
|
|
8
|
+
/** Newest-first list cap when the caller doesn't pass one — mirrors the runs-feed page size. */
|
|
9
|
+
export declare const DEFAULT_DEPLOY_RUN_LIST_LIMIT = 50;
|
|
10
|
+
/**
|
|
11
|
+
* The pinned state machine as data (see `DeployRunStatus` in `../store.ts`). The
|
|
12
|
+
* `failed|canceled|superseded → queued` retry edge is deliberately ABSENT — it is reserved for
|
|
13
|
+
* `runs retry` (GHD-4) and must not be walkable until that slice adds it.
|
|
14
|
+
*/
|
|
15
|
+
export declare const DEPLOY_RUN_TRANSITIONS: Readonly<Record<DeployRunStatus, readonly DeployRunStatus[]>>;
|
|
16
|
+
export declare function canTransitionDeployRun(from: DeployRunStatus, to: DeployRunStatus): boolean;
|
|
17
|
+
/** Shared input validation, reused by the json-file backend. */
|
|
18
|
+
export declare function validateCreateDeployRunInput(input: CreateDeployRunInput): CreateDeployRunInput;
|
|
19
|
+
/**
|
|
20
|
+
* Shared duplicate detection over an already-loaded record set (json-file + in-memory; Postgres does
|
|
21
|
+
* the same checks inside its transaction). The idempotency contract lives on the interface doc:
|
|
22
|
+
* delivery ids are globally unique, and only a CANCELED run frees its `{repo, sha, env}` slot.
|
|
23
|
+
*/
|
|
24
|
+
export declare function findCreateDuplicate(records: readonly DeployRunRecord[], input: CreateDeployRunInput): {
|
|
25
|
+
reason: CreateDeployRunDuplicateReason;
|
|
26
|
+
existing: DeployRunRecord;
|
|
27
|
+
} | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Every backend refuses an unfiltered cancel: no webhook outcome ever means "cancel everything
|
|
30
|
+
* queued for the app" — a missing filter is a caller bug, not a mass cancelation.
|
|
31
|
+
*/
|
|
32
|
+
export declare function validateCancelFilter(filter: CancelQueuedDeployRunsFilter): CancelQueuedDeployRunsFilter;
|
|
33
|
+
/** A queued run for the same `(orgSlug, appSlug, envName)` that a new creation supersedes. */
|
|
34
|
+
export declare function supersedesQueued(record: DeployRunRecord, input: CreateDeployRunInput): boolean;
|
|
35
|
+
/** A queued run the cancel filter targets — org+app always, env and/or ref narrow it further. */
|
|
36
|
+
export declare function matchesCancelFilter(record: DeployRunRecord, filter: CancelQueuedDeployRunsFilter): boolean;
|
|
37
|
+
/** Newest-first (createdAt desc, runId desc tiebreak for same-millisecond rows), cap + flag. */
|
|
38
|
+
export declare function shapeDeployRunList(records: readonly DeployRunRecord[], limit: number): ListDeployRunsResult;
|
|
39
|
+
export declare class InMemoryGithubDeployRunStore implements GithubDeployRunStore {
|
|
40
|
+
#private;
|
|
41
|
+
constructor(options?: {
|
|
42
|
+
now?: () => Date;
|
|
43
|
+
});
|
|
44
|
+
createDeployRun(input: CreateDeployRunInput): Promise<CreateDeployRunResult>;
|
|
45
|
+
getDeployRun(org: string, runId: string): Promise<DeployRunRecord | undefined>;
|
|
46
|
+
listDeployRuns(org: string, app: string, opts?: {
|
|
47
|
+
readonly limit?: number;
|
|
48
|
+
}): Promise<ListDeployRunsResult>;
|
|
49
|
+
cancelQueuedDeployRuns(filter: CancelQueuedDeployRunsFilter): Promise<readonly string[]>;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=github-runs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-runs.d.ts","sourceRoot":"","sources":["../../src/store/github-runs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,4BAA4B,EAC5B,8BAA8B,EAC9B,oBAAoB,EACpB,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,oBAAoB,EACrB,MAAM,aAAa,CAAC;AAGrB;;;;;GAKG;AAEH,gGAAgG;AAChG,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAEhD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAAC,MAAM,CAAC,eAAe,EAAE,SAAS,eAAe,EAAE,CAAC,CAS9F,CAAC;AAEJ,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,eAAe,EAAE,EAAE,EAAE,eAAe,GAAG,OAAO,CAE1F;AAED,gEAAgE;AAChE,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,oBAAoB,GAAG,oBAAoB,CAoB9F;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,SAAS,eAAe,EAAE,EACnC,KAAK,EAAE,oBAAoB,GAC1B;IAAE,MAAM,EAAE,8BAA8B,CAAC;IAAC,QAAQ,EAAE,eAAe,CAAA;CAAE,GAAG,SAAS,CAYnF;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,4BAA4B,GACnC,4BAA4B,CAU9B;AAED,8FAA8F;AAC9F,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAO9F;AAED,iGAAiG;AACjG,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,eAAe,EACvB,MAAM,EAAE,4BAA4B,GACnC,OAAO,CAMT;AAED,gGAAgG;AAChG,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,SAAS,eAAe,EAAE,EACnC,KAAK,EAAE,MAAM,GACZ,oBAAoB,CAKtB;AAED,qBAAa,4BAA6B,YAAW,oBAAoB;;gBAI3D,OAAO,GAAE;QAAE,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;KAAO;IAMxC,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAiB5E,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;IAM9E,cAAc,CAClB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,IAAI,GAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GACrC,OAAO,CAAC,oBAAoB,CAAC;IAS1B,sBAAsB,CAAC,MAAM,EAAE,4BAA4B,GAAG,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC;CAW/F"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { validatePositiveInteger, validateSlug } from './validate.js';
|
|
2
|
+
/**
|
|
3
|
+
* In-memory {@link GithubDeployRunStore} plus the shared pure helpers every backend reuses
|
|
4
|
+
* (validation, duplicate detection, the pinned state machine, list shaping). Mirrors
|
|
5
|
+
* `github-connections.ts`: the Map backend is the non-persistent default and the test double; the
|
|
6
|
+
* exported helpers keep the json-file backend byte-equivalent in semantics.
|
|
7
|
+
*/
|
|
8
|
+
/** Newest-first list cap when the caller doesn't pass one — mirrors the runs-feed page size. */
|
|
9
|
+
export const DEFAULT_DEPLOY_RUN_LIST_LIMIT = 50;
|
|
10
|
+
/**
|
|
11
|
+
* The pinned state machine as data (see `DeployRunStatus` in `../store.ts`). The
|
|
12
|
+
* `failed|canceled|superseded → queued` retry edge is deliberately ABSENT — it is reserved for
|
|
13
|
+
* `runs retry` (GHD-4) and must not be walkable until that slice adds it.
|
|
14
|
+
*/
|
|
15
|
+
export const DEPLOY_RUN_TRANSITIONS = {
|
|
16
|
+
queued: ['building', 'canceled', 'superseded'],
|
|
17
|
+
building: ['deploying', 'failed', 'canceled'],
|
|
18
|
+
deploying: ['deployed', 'failed'],
|
|
19
|
+
deployed: [],
|
|
20
|
+
failed: [],
|
|
21
|
+
canceled: [],
|
|
22
|
+
superseded: [],
|
|
23
|
+
};
|
|
24
|
+
export function canTransitionDeployRun(from, to) {
|
|
25
|
+
return DEPLOY_RUN_TRANSITIONS[from].includes(to);
|
|
26
|
+
}
|
|
27
|
+
/** Shared input validation, reused by the json-file backend. */
|
|
28
|
+
export function validateCreateDeployRunInput(input) {
|
|
29
|
+
if (input.runId.length === 0)
|
|
30
|
+
throw new Error('runId must not be empty');
|
|
31
|
+
if (input.deliveryId.length === 0)
|
|
32
|
+
throw new Error('deliveryId must not be empty');
|
|
33
|
+
if (input.commitSha.length === 0)
|
|
34
|
+
throw new Error('commitSha must not be empty');
|
|
35
|
+
return {
|
|
36
|
+
runId: input.runId,
|
|
37
|
+
orgSlug: validateSlug('org', input.orgSlug),
|
|
38
|
+
appSlug: validateSlug('app', input.appSlug),
|
|
39
|
+
envName: input.envName,
|
|
40
|
+
sourceEvent: input.sourceEvent,
|
|
41
|
+
deliveryId: input.deliveryId,
|
|
42
|
+
githubRepositoryId: validatePositiveInteger('githubRepositoryId', input.githubRepositoryId),
|
|
43
|
+
commitSha: input.commitSha,
|
|
44
|
+
ref: input.ref,
|
|
45
|
+
...(input.prNumber !== undefined
|
|
46
|
+
? { prNumber: validatePositiveInteger('prNumber', input.prNumber) }
|
|
47
|
+
: {}),
|
|
48
|
+
actorLogin: input.actorLogin,
|
|
49
|
+
...(input.blockedReason !== undefined ? { blockedReason: input.blockedReason } : {}),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Shared duplicate detection over an already-loaded record set (json-file + in-memory; Postgres does
|
|
54
|
+
* the same checks inside its transaction). The idempotency contract lives on the interface doc:
|
|
55
|
+
* delivery ids are globally unique, and only a CANCELED run frees its `{repo, sha, env}` slot.
|
|
56
|
+
*/
|
|
57
|
+
export function findCreateDuplicate(records, input) {
|
|
58
|
+
const sameDelivery = records.find((record) => record.deliveryId === input.deliveryId);
|
|
59
|
+
if (sameDelivery !== undefined)
|
|
60
|
+
return { reason: 'duplicate_delivery', existing: sameDelivery };
|
|
61
|
+
const sameTarget = records.find((record) => record.status !== 'canceled' &&
|
|
62
|
+
record.githubRepositoryId === input.githubRepositoryId &&
|
|
63
|
+
record.commitSha === input.commitSha &&
|
|
64
|
+
record.envName === input.envName);
|
|
65
|
+
if (sameTarget !== undefined)
|
|
66
|
+
return { reason: 'duplicate_target', existing: sameTarget };
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Every backend refuses an unfiltered cancel: no webhook outcome ever means "cancel everything
|
|
71
|
+
* queued for the app" — a missing filter is a caller bug, not a mass cancelation.
|
|
72
|
+
*/
|
|
73
|
+
export function validateCancelFilter(filter) {
|
|
74
|
+
if (filter.envName === undefined && filter.ref === undefined) {
|
|
75
|
+
throw new Error('cancelQueuedDeployRuns requires envName or ref');
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
orgSlug: validateSlug('org', filter.orgSlug),
|
|
79
|
+
appSlug: validateSlug('app', filter.appSlug),
|
|
80
|
+
...(filter.envName !== undefined ? { envName: filter.envName } : {}),
|
|
81
|
+
...(filter.ref !== undefined ? { ref: filter.ref } : {}),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/** A queued run for the same `(orgSlug, appSlug, envName)` that a new creation supersedes. */
|
|
85
|
+
export function supersedesQueued(record, input) {
|
|
86
|
+
return (record.status === 'queued' &&
|
|
87
|
+
record.orgSlug === input.orgSlug &&
|
|
88
|
+
record.appSlug === input.appSlug &&
|
|
89
|
+
record.envName === input.envName);
|
|
90
|
+
}
|
|
91
|
+
/** A queued run the cancel filter targets — org+app always, env and/or ref narrow it further. */
|
|
92
|
+
export function matchesCancelFilter(record, filter) {
|
|
93
|
+
if (record.status !== 'queued')
|
|
94
|
+
return false;
|
|
95
|
+
if (record.orgSlug !== filter.orgSlug || record.appSlug !== filter.appSlug)
|
|
96
|
+
return false;
|
|
97
|
+
if (filter.envName !== undefined && record.envName !== filter.envName)
|
|
98
|
+
return false;
|
|
99
|
+
if (filter.ref !== undefined && record.ref !== filter.ref)
|
|
100
|
+
return false;
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
/** Newest-first (createdAt desc, runId desc tiebreak for same-millisecond rows), cap + flag. */
|
|
104
|
+
export function shapeDeployRunList(records, limit) {
|
|
105
|
+
const sorted = [...records].sort((a, b) => b.createdAt.localeCompare(a.createdAt) || b.runId.localeCompare(a.runId));
|
|
106
|
+
return { runs: sorted.slice(0, limit), truncated: sorted.length > limit };
|
|
107
|
+
}
|
|
108
|
+
export class InMemoryGithubDeployRunStore {
|
|
109
|
+
#records = new Map();
|
|
110
|
+
#now;
|
|
111
|
+
constructor(options = {}) {
|
|
112
|
+
this.#now = options.now ?? (() => new Date());
|
|
113
|
+
}
|
|
114
|
+
// All four methods are `async` so validation failures reject (matching the other backends)
|
|
115
|
+
// instead of throwing synchronously out of a Promise-returning signature.
|
|
116
|
+
async createDeployRun(input) {
|
|
117
|
+
const safe = validateCreateDeployRunInput(input);
|
|
118
|
+
const all = [...this.#records.values()];
|
|
119
|
+
const duplicate = findCreateDuplicate(all, safe);
|
|
120
|
+
if (duplicate !== undefined)
|
|
121
|
+
return { ok: false, ...duplicate };
|
|
122
|
+
const nowIso = this.#now().toISOString();
|
|
123
|
+
const superseded = [];
|
|
124
|
+
for (const record of all) {
|
|
125
|
+
if (!supersedesQueued(record, safe))
|
|
126
|
+
continue;
|
|
127
|
+
this.#records.set(record.runId, { ...record, status: 'superseded', finishedAt: nowIso });
|
|
128
|
+
superseded.push(record.runId);
|
|
129
|
+
}
|
|
130
|
+
const record = { ...safe, status: 'queued', createdAt: nowIso };
|
|
131
|
+
this.#records.set(record.runId, record);
|
|
132
|
+
return { ok: true, record, superseded };
|
|
133
|
+
}
|
|
134
|
+
async getDeployRun(org, runId) {
|
|
135
|
+
const safeOrg = validateSlug('org', org);
|
|
136
|
+
const record = this.#records.get(runId);
|
|
137
|
+
return record?.orgSlug === safeOrg ? record : undefined;
|
|
138
|
+
}
|
|
139
|
+
async listDeployRuns(org, app, opts = {}) {
|
|
140
|
+
const safeOrg = validateSlug('org', org);
|
|
141
|
+
const safeApp = validateSlug('app', app);
|
|
142
|
+
const matching = [...this.#records.values()].filter((record) => record.orgSlug === safeOrg && record.appSlug === safeApp);
|
|
143
|
+
return shapeDeployRunList(matching, opts.limit ?? DEFAULT_DEPLOY_RUN_LIST_LIMIT);
|
|
144
|
+
}
|
|
145
|
+
async cancelQueuedDeployRuns(filter) {
|
|
146
|
+
const safe = validateCancelFilter(filter);
|
|
147
|
+
const nowIso = this.#now().toISOString();
|
|
148
|
+
const canceled = [];
|
|
149
|
+
for (const record of this.#records.values()) {
|
|
150
|
+
if (!matchesCancelFilter(record, safe))
|
|
151
|
+
continue;
|
|
152
|
+
this.#records.set(record.runId, { ...record, status: 'canceled', finishedAt: nowIso });
|
|
153
|
+
canceled.push(record.runId);
|
|
154
|
+
}
|
|
155
|
+
return canceled;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=github-runs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-runs.js","sourceRoot":"","sources":["../../src/store/github-runs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Pool } from 'pg';
|
|
2
|
+
import { type PendingGithubInstallationStore } from '../github/pending-installations.js';
|
|
3
|
+
/**
|
|
4
|
+
* Postgres {@link PendingGithubInstallationStore} (GHD-2): the multi-instance backing for the GitHub
|
|
5
|
+
* install hand-off — the setup redirect and the CLI poll can land on different instances, so the
|
|
6
|
+
* correlation must live in the shared store (closes the gap documented in GHD-1 / ADR 0111). A tiny,
|
|
7
|
+
* self-pruning table: `ON CONFLICT DO NOTHING` IS the first-write-wins rule, expired rows are dropped
|
|
8
|
+
* opportunistically on every write and treated as absent on read. Mirrors `PostgresStateHandleStore`'s
|
|
9
|
+
* shape: pool injected, schema ensured by `ensureArtifactSchema`.
|
|
10
|
+
*/
|
|
11
|
+
export declare class PostgresPendingGithubInstallationStore implements PendingGithubInstallationStore {
|
|
12
|
+
#private;
|
|
13
|
+
constructor(pool: Pool, options?: {
|
|
14
|
+
now?: () => Date;
|
|
15
|
+
});
|
|
16
|
+
put(state: string, installationId: number): Promise<void>;
|
|
17
|
+
get(state: string): Promise<number | undefined>;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=pending-installations-postgres.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-installations-postgres.d.ts","sourceRoot":"","sources":["../../src/store/pending-installations-postgres.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAC/B,OAAO,EAEL,KAAK,8BAA8B,EACpC,MAAM,oCAAoC,CAAC;AAE5C;;;;;;;GAOG;AACH,qBAAa,sCAAuC,YAAW,8BAA8B;;gBAI/E,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE;QAAE,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;KAAO;IASpD,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAazD,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;CAqBtD"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { PENDING_GITHUB_INSTALLATION_TTL_MS, } from '../github/pending-installations.js';
|
|
2
|
+
/**
|
|
3
|
+
* Postgres {@link PendingGithubInstallationStore} (GHD-2): the multi-instance backing for the GitHub
|
|
4
|
+
* install hand-off — the setup redirect and the CLI poll can land on different instances, so the
|
|
5
|
+
* correlation must live in the shared store (closes the gap documented in GHD-1 / ADR 0111). A tiny,
|
|
6
|
+
* self-pruning table: `ON CONFLICT DO NOTHING` IS the first-write-wins rule, expired rows are dropped
|
|
7
|
+
* opportunistically on every write and treated as absent on read. Mirrors `PostgresStateHandleStore`'s
|
|
8
|
+
* shape: pool injected, schema ensured by `ensureArtifactSchema`.
|
|
9
|
+
*/
|
|
10
|
+
export class PostgresPendingGithubInstallationStore {
|
|
11
|
+
#pool;
|
|
12
|
+
#now;
|
|
13
|
+
constructor(pool, options = {}) {
|
|
14
|
+
this.#pool = pool;
|
|
15
|
+
this.#now = options.now ?? (() => new Date());
|
|
16
|
+
}
|
|
17
|
+
#expiryCutoff() {
|
|
18
|
+
return new Date(this.#now().getTime() - PENDING_GITHUB_INSTALLATION_TTL_MS);
|
|
19
|
+
}
|
|
20
|
+
async put(state, installationId) {
|
|
21
|
+
// Opportunistic hygiene first: never let an expired row satisfy the conflict check below.
|
|
22
|
+
await this.#pool.query('DELETE FROM pending_github_installations WHERE created_at < $1', [
|
|
23
|
+
this.#expiryCutoff(),
|
|
24
|
+
]);
|
|
25
|
+
await this.#pool.query(`INSERT INTO pending_github_installations (state, installation_id, created_at)
|
|
26
|
+
VALUES ($1, $2, $3)
|
|
27
|
+
ON CONFLICT (state) DO NOTHING`, [state, installationId, this.#now()]);
|
|
28
|
+
}
|
|
29
|
+
async get(state) {
|
|
30
|
+
const { rows } = await this.#pool.query('SELECT installation_id, created_at FROM pending_github_installations WHERE state = $1', [
|
|
31
|
+
state,
|
|
32
|
+
]);
|
|
33
|
+
const row = rows[0];
|
|
34
|
+
if (row === undefined)
|
|
35
|
+
return undefined;
|
|
36
|
+
const cutoff = this.#expiryCutoff();
|
|
37
|
+
if (new Date(row.created_at).getTime() < cutoff.getTime()) {
|
|
38
|
+
// Scope the cleanup to the expired row: another instance may have pruned it and landed a
|
|
39
|
+
// FRESH row for the same state between our SELECT and this DELETE.
|
|
40
|
+
await this.#pool.query('DELETE FROM pending_github_installations WHERE state = $1 AND created_at < $2', [state, cutoff]);
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
return Number(row.installation_id);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=pending-installations-postgres.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-installations-postgres.js","sourceRoot":"","sources":["../../src/store/pending-installations-postgres.ts"],"names":[],"mappings":"AACA,OAAO,EACL,kCAAkC,GAEnC,MAAM,oCAAoC,CAAC;AAE5C;;;;;;;GAOG;AACH,MAAM,OAAO,sCAAsC;IACxC,KAAK,CAAO;IACZ,IAAI,CAAa;IAE1B,YAAY,IAAU,EAAE,UAAgC,EAAE;QACxD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,aAAa;QACX,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,GAAG,kCAAkC,CAAC,CAAC;IAC9E,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,KAAa,EAAE,cAAsB;QAC7C,0FAA0F;QAC1F,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,gEAAgE,EAAE;YACvF,IAAI,CAAC,aAAa,EAAE;SACrB,CAAC,CAAC;QACH,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CACpB;;sCAEgC,EAChC,CAAC,KAAK,EAAE,cAAc,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CACrC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,KAAa;QACrB,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAGpC,uFAAuF,EAAE;YAC1F,KAAK;SACN,CAAC,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QACxC,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACpC,IAAI,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;YAC1D,yFAAyF;YACzF,mEAAmE;YACnE,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CACpB,+EAA+E,EAC/E,CAAC,KAAK,EAAE,MAAM,CAAC,CAChB,CAAC;YACF,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IACrC,CAAC;CACF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Pool } from 'pg';
|
|
2
|
+
import type { TenantRef } from '../store.js';
|
|
3
|
+
import { type AlertFiringState, type AlertRuleRecord, type AlertRuleStore, type CreateAlertRuleInput } from './alert-rules.js';
|
|
4
|
+
/**
|
|
5
|
+
* Relational {@link AlertRuleStore} backend (analytics alerting E2), a standalone pool-injected
|
|
6
|
+
* class mirroring `PostgresRequestEventStore` (the analytics-family precedent) rather than another
|
|
7
|
+
* facet of `PostgresArtifactStore` — alert rules belong to the analytics surface and this keeps
|
|
8
|
+
* `postgres.ts` under the size gate. Pure SQL; unit-testable against any local Postgres
|
|
9
|
+
* (`DATABASE_URL_TEST`).
|
|
10
|
+
*/
|
|
11
|
+
/** Create the `alert_rules` table if absent (idempotent; run once at startup). */
|
|
12
|
+
export declare function ensureAlertRuleSchema(pool: Pool): Promise<void>;
|
|
13
|
+
export declare class PostgresAlertRuleStore implements AlertRuleStore {
|
|
14
|
+
#private;
|
|
15
|
+
constructor(pool: Pool, options?: {
|
|
16
|
+
now?: () => Date;
|
|
17
|
+
});
|
|
18
|
+
ensureSchema(): Promise<void>;
|
|
19
|
+
createAlertRule(input: CreateAlertRuleInput): Promise<AlertRuleRecord>;
|
|
20
|
+
listAlertRules(ref: TenantRef): Promise<readonly AlertRuleRecord[]>;
|
|
21
|
+
getAlertRule(ref: TenantRef, id: string): Promise<AlertRuleRecord | undefined>;
|
|
22
|
+
deleteAlertRule(ref: TenantRef, id: string): Promise<boolean>;
|
|
23
|
+
listEnabledAlertRules(): Promise<readonly AlertRuleRecord[]>;
|
|
24
|
+
updateAlertFiringState(id: string, state: AlertFiringState): Promise<AlertRuleRecord | undefined>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=postgres-alerts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres-alerts.d.ts","sourceRoot":"","sources":["../../src/store/postgres-alerts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAC/B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EACL,KAAK,gBAAgB,EAErB,KAAK,eAAe,EACpB,KAAK,cAAc,EAEnB,KAAK,oBAAoB,EAG1B,MAAM,kBAAkB,CAAC;AAG1B;;;;;;GAMG;AAEH,kFAAkF;AAClF,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAgCrE;AAgDD,qBAAa,sBAAuB,YAAW,cAAc;;gBAI/C,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE;QAAE,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;KAAO;IAKpD,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7B,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,eAAe,CAAC;IA4BtE,cAAc,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC;IAUnE,YAAY,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;IAc9E,eAAe,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAc7D,qBAAqB,IAAI,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC;IAO5D,sBAAsB,CAC1B,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,gBAAgB,GACtB,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;CAYxC"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { newAlertRuleRecord, validateAlertRuleId, } from './alert-rules.js';
|
|
2
|
+
import { validateSlug } from './validate.js';
|
|
3
|
+
/**
|
|
4
|
+
* Relational {@link AlertRuleStore} backend (analytics alerting E2), a standalone pool-injected
|
|
5
|
+
* class mirroring `PostgresRequestEventStore` (the analytics-family precedent) rather than another
|
|
6
|
+
* facet of `PostgresArtifactStore` — alert rules belong to the analytics surface and this keeps
|
|
7
|
+
* `postgres.ts` under the size gate. Pure SQL; unit-testable against any local Postgres
|
|
8
|
+
* (`DATABASE_URL_TEST`).
|
|
9
|
+
*/
|
|
10
|
+
/** Create the `alert_rules` table if absent (idempotent; run once at startup). */
|
|
11
|
+
export async function ensureAlertRuleSchema(pool) {
|
|
12
|
+
await pool.query(`
|
|
13
|
+
CREATE TABLE IF NOT EXISTS alert_rules (
|
|
14
|
+
id uuid PRIMARY KEY,
|
|
15
|
+
org_slug text NOT NULL,
|
|
16
|
+
app_slug text NOT NULL,
|
|
17
|
+
environment text NOT NULL,
|
|
18
|
+
name text,
|
|
19
|
+
metric text NOT NULL,
|
|
20
|
+
threshold double precision NOT NULL,
|
|
21
|
+
window_minutes int NOT NULL,
|
|
22
|
+
comparison text NOT NULL DEFAULT '>=',
|
|
23
|
+
webhook_url text NOT NULL,
|
|
24
|
+
enabled boolean NOT NULL DEFAULT true,
|
|
25
|
+
cooldown_minutes int NOT NULL,
|
|
26
|
+
breaching boolean NOT NULL DEFAULT false,
|
|
27
|
+
last_observed double precision,
|
|
28
|
+
last_fired_at timestamptz,
|
|
29
|
+
created_by_subject text,
|
|
30
|
+
created_at timestamptz NOT NULL,
|
|
31
|
+
updated_at timestamptz NOT NULL
|
|
32
|
+
)
|
|
33
|
+
`);
|
|
34
|
+
await pool.query(`
|
|
35
|
+
CREATE INDEX IF NOT EXISTS alert_rules_tenant_idx
|
|
36
|
+
ON alert_rules (org_slug, app_slug, environment)
|
|
37
|
+
`);
|
|
38
|
+
await pool.query(`
|
|
39
|
+
CREATE INDEX IF NOT EXISTS alert_rules_enabled_idx
|
|
40
|
+
ON alert_rules (enabled)
|
|
41
|
+
WHERE enabled
|
|
42
|
+
`);
|
|
43
|
+
}
|
|
44
|
+
function rowToRecord(row) {
|
|
45
|
+
return {
|
|
46
|
+
id: row.id,
|
|
47
|
+
orgSlug: row.org_slug,
|
|
48
|
+
appSlug: row.app_slug,
|
|
49
|
+
environment: row.environment,
|
|
50
|
+
...(row.name !== null ? { name: row.name } : {}),
|
|
51
|
+
metric: row.metric,
|
|
52
|
+
threshold: Number(row.threshold),
|
|
53
|
+
windowMinutes: Number(row.window_minutes),
|
|
54
|
+
comparison: '>=',
|
|
55
|
+
webhookUrl: row.webhook_url,
|
|
56
|
+
enabled: row.enabled,
|
|
57
|
+
cooldownMinutes: Number(row.cooldown_minutes),
|
|
58
|
+
breaching: row.breaching,
|
|
59
|
+
...(row.last_observed !== null ? { lastObserved: Number(row.last_observed) } : {}),
|
|
60
|
+
...(row.last_fired_at !== null
|
|
61
|
+
? { lastFiredAt: new Date(row.last_fired_at).toISOString() }
|
|
62
|
+
: {}),
|
|
63
|
+
...(row.created_by_subject !== null ? { createdBySubject: row.created_by_subject } : {}),
|
|
64
|
+
createdAt: new Date(row.created_at).toISOString(),
|
|
65
|
+
updatedAt: new Date(row.updated_at).toISOString(),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export class PostgresAlertRuleStore {
|
|
69
|
+
#pool;
|
|
70
|
+
#now;
|
|
71
|
+
constructor(pool, options = {}) {
|
|
72
|
+
this.#pool = pool;
|
|
73
|
+
this.#now = options.now ?? (() => new Date());
|
|
74
|
+
}
|
|
75
|
+
async ensureSchema() {
|
|
76
|
+
await ensureAlertRuleSchema(this.#pool);
|
|
77
|
+
}
|
|
78
|
+
async createAlertRule(input) {
|
|
79
|
+
const record = newAlertRuleRecord(input, this.#now);
|
|
80
|
+
await this.#pool.query(`INSERT INTO alert_rules
|
|
81
|
+
(id, org_slug, app_slug, environment, name, metric, threshold, window_minutes,
|
|
82
|
+
comparison, webhook_url, enabled, cooldown_minutes, breaching,
|
|
83
|
+
created_by_subject, created_at, updated_at)
|
|
84
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, false, $13, $14, $14)`, [
|
|
85
|
+
record.id,
|
|
86
|
+
record.orgSlug,
|
|
87
|
+
record.appSlug,
|
|
88
|
+
record.environment,
|
|
89
|
+
record.name ?? null,
|
|
90
|
+
record.metric,
|
|
91
|
+
record.threshold,
|
|
92
|
+
record.windowMinutes,
|
|
93
|
+
record.comparison,
|
|
94
|
+
record.webhookUrl,
|
|
95
|
+
record.enabled,
|
|
96
|
+
record.cooldownMinutes,
|
|
97
|
+
record.createdBySubject ?? null,
|
|
98
|
+
record.createdAt,
|
|
99
|
+
]);
|
|
100
|
+
return record;
|
|
101
|
+
}
|
|
102
|
+
async listAlertRules(ref) {
|
|
103
|
+
const { rows } = await this.#pool.query(`SELECT * FROM alert_rules
|
|
104
|
+
WHERE org_slug = $1 AND app_slug = $2 AND environment = $3
|
|
105
|
+
ORDER BY created_at ASC, id ASC`, [validateSlug('org', ref.org), validateSlug('app', ref.app), validateSlug('env', ref.env)]);
|
|
106
|
+
return rows.map(rowToRecord);
|
|
107
|
+
}
|
|
108
|
+
async getAlertRule(ref, id) {
|
|
109
|
+
const { rows } = await this.#pool.query(`SELECT * FROM alert_rules
|
|
110
|
+
WHERE id = $1 AND org_slug = $2 AND app_slug = $3 AND environment = $4`, [
|
|
111
|
+
validateAlertRuleId(id),
|
|
112
|
+
validateSlug('org', ref.org),
|
|
113
|
+
validateSlug('app', ref.app),
|
|
114
|
+
validateSlug('env', ref.env),
|
|
115
|
+
]);
|
|
116
|
+
return rows[0] ? rowToRecord(rows[0]) : undefined;
|
|
117
|
+
}
|
|
118
|
+
async deleteAlertRule(ref, id) {
|
|
119
|
+
const { rowCount } = await this.#pool.query(`DELETE FROM alert_rules
|
|
120
|
+
WHERE id = $1 AND org_slug = $2 AND app_slug = $3 AND environment = $4`, [
|
|
121
|
+
validateAlertRuleId(id),
|
|
122
|
+
validateSlug('org', ref.org),
|
|
123
|
+
validateSlug('app', ref.app),
|
|
124
|
+
validateSlug('env', ref.env),
|
|
125
|
+
]);
|
|
126
|
+
return (rowCount ?? 0) > 0;
|
|
127
|
+
}
|
|
128
|
+
async listEnabledAlertRules() {
|
|
129
|
+
const { rows } = await this.#pool.query('SELECT * FROM alert_rules WHERE enabled ORDER BY created_at ASC, id ASC');
|
|
130
|
+
return rows.map(rowToRecord);
|
|
131
|
+
}
|
|
132
|
+
async updateAlertFiringState(id, state) {
|
|
133
|
+
const { rows } = await this.#pool.query(`UPDATE alert_rules
|
|
134
|
+
SET breaching = $2,
|
|
135
|
+
last_observed = $3,
|
|
136
|
+
last_fired_at = COALESCE($4, last_fired_at)
|
|
137
|
+
WHERE id = $1
|
|
138
|
+
RETURNING *`, [validateAlertRuleId(id), state.breaching, state.lastObserved, state.lastFiredAt ?? null]);
|
|
139
|
+
return rows[0] ? rowToRecord(rows[0]) : undefined;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=postgres-alerts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres-alerts.js","sourceRoot":"","sources":["../../src/store/postgres-alerts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Pool } from 'pg';
|
|
2
|
+
import type { CancelQueuedDeployRunsFilter, CreateDeployRunInput, CreateDeployRunResult, DeployRunRecord, ListDeployRunsResult } from '../store.js';
|
|
3
|
+
export declare function createDeployRunRow(pool: Pool, now: () => Date, input: CreateDeployRunInput): Promise<CreateDeployRunResult>;
|
|
4
|
+
export declare function getDeployRunRow(pool: Pool, org: string, runId: string): Promise<DeployRunRecord | undefined>;
|
|
5
|
+
export declare function listDeployRunRows(pool: Pool, org: string, app: string, opts?: {
|
|
6
|
+
readonly limit?: number;
|
|
7
|
+
}): Promise<ListDeployRunsResult>;
|
|
8
|
+
export declare function cancelQueuedDeployRunRows(pool: Pool, now: () => Date, filter: CancelQueuedDeployRunsFilter): Promise<readonly string[]>;
|
|
9
|
+
//# sourceMappingURL=postgres-github-runs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres-github-runs.d.ts","sourceRoot":"","sources":["../../src/store/postgres-github-runs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAc,MAAM,IAAI,CAAC;AAC3C,OAAO,KAAK,EACV,4BAA4B,EAC5B,oBAAoB,EACpB,qBAAqB,EACrB,eAAe,EACf,oBAAoB,EACrB,MAAM,aAAa,CAAC;AA6DrB,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,IAAI,EACV,GAAG,EAAE,MAAM,IAAI,EACf,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,qBAAqB,CAAC,CAkEhC;AAED,wBAAsB,eAAe,CACnC,IAAI,EAAE,IAAI,EACV,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAMtC;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,IAAI,EACV,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,IAAI,GAAE;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GACrC,OAAO,CAAC,oBAAoB,CAAC,CAW/B;AAED,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,IAAI,EACV,GAAG,EAAE,MAAM,IAAI,EACf,MAAM,EAAE,4BAA4B,GACnC,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAoB5B"}
|