@sema-agent/server 1.315.0 → 1.316.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/http/idempotency.d.ts +1 -0
- package/dist/http/idempotency.js +3 -0
- package/dist/http/principal-gate.d.ts +12 -0
- package/dist/http/principal-gate.js +52 -0
- package/dist/http/route-ctx.d.ts +67 -0
- package/dist/http/route-ctx.js +2 -0
- package/dist/http/routes/attachments.d.ts +4 -0
- package/dist/http/routes/attachments.js +106 -0
- package/dist/http/routes/capabilities.d.ts +4 -0
- package/dist/http/routes/capabilities.js +111 -0
- package/dist/http/routes/images.d.ts +20 -0
- package/dist/http/routes/images.js +572 -0
- package/dist/http/routes/memory-policy.d.ts +4 -0
- package/dist/http/routes/memory-policy.js +105 -0
- package/dist/http/routes/observability.d.ts +4 -0
- package/dist/http/routes/observability.js +63 -0
- package/dist/http/routes/session-sync.d.ts +36 -0
- package/dist/http/routes/session-sync.js +513 -0
- package/dist/http/routes/sessions-list.d.ts +4 -0
- package/dist/http/routes/sessions-list.js +117 -0
- package/dist/http/routes/sessions.d.ts +17 -0
- package/dist/http/routes/sessions.js +746 -0
- package/dist/http/send.d.ts +6 -0
- package/dist/http/send.js +19 -0
- package/dist/http/server.d.ts +6 -7
- package/dist/http/server.js +125 -2413
- package/dist/http/sse-log.d.ts +25 -0
- package/dist/http/sse-log.js +59 -0
- package/dist/http/workspace-content.d.ts +3 -0
- package/dist/http/workspace-content.js +20 -0
- package/dist/plugins/approval-store-sql.d.ts +52 -0
- package/dist/plugins/approval-store-sql.js +119 -0
- package/dist/plugins/breaker-state-sql.d.ts +29 -0
- package/dist/plugins/breaker-state-sql.js +104 -0
- package/dist/plugins/file-snapshot-store-sql.d.ts +38 -0
- package/dist/plugins/file-snapshot-store-sql.js +210 -0
- package/dist/plugins/image-index-sql.d.ts +33 -0
- package/dist/plugins/image-index-sql.js +328 -0
- package/dist/plugins/outcome-ledger-sql.d.ts +70 -0
- package/dist/plugins/outcome-ledger-sql.js +158 -0
- package/dist/plugins/pg-approval-store.d.ts +1 -27
- package/dist/plugins/pg-approval-store.js +1 -83
- package/dist/plugins/pg-breaker-state.d.ts +1 -19
- package/dist/plugins/pg-breaker-state.js +1 -88
- package/dist/plugins/pg-file-snapshot-store.d.ts +1 -27
- package/dist/plugins/pg-file-snapshot-store.js +1 -190
- package/dist/plugins/pg-image-index.d.ts +1 -21
- package/dist/plugins/pg-image-index.js +1 -217
- package/dist/plugins/pg-outcome-ledger.d.ts +2 -22
- package/dist/plugins/pg-outcome-ledger.js +1 -104
- package/dist/plugins/pg-resume-anchor-store.d.ts +1 -13
- package/dist/plugins/pg-resume-anchor-store.js +1 -22
- package/dist/plugins/pg-run-store.d.ts +1 -75
- package/dist/plugins/pg-run-store.js +1 -287
- package/dist/plugins/pg-session-policy-store.d.ts +1 -11
- package/dist/plugins/pg-session-policy-store.js +1 -90
- package/dist/plugins/pg-tool-result-store.d.ts +1 -15
- package/dist/plugins/pg-tool-result-store.js +1 -52
- package/dist/plugins/pg-workflow-journal-store.d.ts +1 -20
- package/dist/plugins/pg-workflow-journal-store.js +1 -34
- package/dist/plugins/pg-workflow-run-store.d.ts +1 -55
- package/dist/plugins/pg-workflow-run-store.js +1 -220
- package/dist/plugins/resume-anchor-store-sql.d.ts +23 -0
- package/dist/plugins/resume-anchor-store-sql.js +38 -0
- package/dist/plugins/run-store-sql.d.ts +89 -0
- package/dist/plugins/run-store-sql.js +397 -0
- package/dist/plugins/session-policy-store-sql.d.ts +22 -0
- package/dist/plugins/session-policy-store-sql.js +109 -0
- package/dist/plugins/tidb-approval-store.d.ts +1 -39
- package/dist/plugins/tidb-approval-store.js +1 -71
- package/dist/plugins/tidb-breaker-state.d.ts +1 -17
- package/dist/plugins/tidb-breaker-state.js +1 -74
- package/dist/plugins/tidb-file-snapshot-store.d.ts +1 -27
- package/dist/plugins/tidb-file-snapshot-store.js +1 -192
- package/dist/plugins/tidb-image-index.d.ts +1 -19
- package/dist/plugins/tidb-image-index.js +1 -149
- package/dist/plugins/tidb-outcome-ledger.d.ts +1 -57
- package/dist/plugins/tidb-outcome-ledger.js +1 -97
- package/dist/plugins/tidb-resume-anchor-store.d.ts +1 -13
- package/dist/plugins/tidb-resume-anchor-store.js +1 -22
- package/dist/plugins/tidb-run-store.d.ts +1 -75
- package/dist/plugins/tidb-run-store.js +1 -306
- package/dist/plugins/tidb-session-policy-store.d.ts +1 -11
- package/dist/plugins/tidb-session-policy-store.js +1 -86
- package/dist/plugins/tidb-tool-result-store.d.ts +1 -13
- package/dist/plugins/tidb-tool-result-store.js +1 -38
- package/dist/plugins/tidb-workflow-journal-store.d.ts +1 -20
- package/dist/plugins/tidb-workflow-journal-store.js +1 -34
- package/dist/plugins/tidb-workflow-run-store.d.ts +1 -61
- package/dist/plugins/tidb-workflow-run-store.js +1 -231
- package/dist/plugins/tool-result-store-sql.d.ts +25 -0
- package/dist/plugins/tool-result-store-sql.js +69 -0
- package/dist/plugins/workflow-journal-store-sql.d.ts +30 -0
- package/dist/plugins/workflow-journal-store-sql.js +53 -0
- package/dist/plugins/workflow-run-store-sql.d.ts +85 -0
- package/dist/plugins/workflow-run-store-sql.js +299 -0
- package/package.json +1 -1
|
@@ -1,84 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
import { pgProtocolJsonStringify } from "./pg-safe-json.js";
|
|
3
|
-
export const PG_APPROVAL_SCHEMA = [
|
|
4
|
-
`CREATE TABLE IF NOT EXISTS approval (
|
|
5
|
-
id VARCHAR(64) NOT NULL,
|
|
6
|
-
task_id VARCHAR(64),
|
|
7
|
-
session_id VARCHAR(64),
|
|
8
|
-
owner VARCHAR(190),
|
|
9
|
-
-- Single-DB fleet scope guard (tenant isolation). NULLABLE on purpose: a NULL-scope row is an untenanted
|
|
10
|
-
-- run, and every read/decide path matches it with \`scope IS NOT DISTINCT FROM $n\` (the PG twin of TiDB
|
|
11
|
-
-- \`<=>\`) so NULL matches only NULL and one tenant's scope NEVER matches another's.
|
|
12
|
-
scope VARCHAR(190),
|
|
13
|
-
tool_name VARCHAR(190) NOT NULL,
|
|
14
|
-
args JSONB,
|
|
15
|
-
status VARCHAR(16) NOT NULL,
|
|
16
|
-
reason TEXT,
|
|
17
|
-
decided_by VARCHAR(190),
|
|
18
|
-
created_at TIMESTAMPTZ(3) NOT NULL,
|
|
19
|
-
decided_at TIMESTAMPTZ(3),
|
|
20
|
-
PRIMARY KEY (id)
|
|
21
|
-
)`,
|
|
22
|
-
`CREATE INDEX IF NOT EXISTS idx_approval_owner_status ON approval (owner, status)`,
|
|
23
|
-
`CREATE INDEX IF NOT EXISTS idx_approval_status ON approval (status)`,
|
|
24
|
-
`CREATE INDEX IF NOT EXISTS idx_approval_scope_status ON approval (scope, status)`,
|
|
25
|
-
];
|
|
26
|
-
export async function ensureSchema(pool) {
|
|
27
|
-
for (const stmt of PG_APPROVAL_SCHEMA)
|
|
28
|
-
await pool.query(stmt);
|
|
29
|
-
}
|
|
30
|
-
export class PgApprovalStore {
|
|
31
|
-
pool;
|
|
32
|
-
constructor(pool) {
|
|
33
|
-
this.pool = pool;
|
|
34
|
-
}
|
|
35
|
-
async createPending(id, scope, ctx) {
|
|
36
|
-
await this.pool.query("INSERT INTO approval (id, task_id, session_id, owner, scope, tool_name, args, status, created_at) " +
|
|
37
|
-
"VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb,'pending',$8)", [
|
|
38
|
-
id,
|
|
39
|
-
ctx.taskId ?? null,
|
|
40
|
-
ctx.sessionId ?? null,
|
|
41
|
-
ctx.owner ?? null,
|
|
42
|
-
scope,
|
|
43
|
-
ctx.toolName,
|
|
44
|
-
ctx.args == null ? null : pgProtocolJsonStringify(ctx.args, "approval args"),
|
|
45
|
-
new Date(),
|
|
46
|
-
]);
|
|
47
|
-
}
|
|
48
|
-
async getStatus(id, scope) {
|
|
49
|
-
const res = await this.pool.query("SELECT status, reason, decided_by FROM approval WHERE id = $1 AND scope IS NOT DISTINCT FROM $2", [id, scope]);
|
|
50
|
-
const r = res.rows[0];
|
|
51
|
-
if (!r)
|
|
52
|
-
return undefined;
|
|
53
|
-
return { status: r.status, reason: r.reason ?? null, decidedBy: r.decided_by ?? null };
|
|
54
|
-
}
|
|
55
|
-
async decide(id, scope, decision, reason, decidedBy) {
|
|
56
|
-
const res = await this.pool.query("UPDATE approval SET status = $1, reason = $2, decided_by = $3, decided_at = $4 WHERE id = $5 AND scope IS NOT DISTINCT FROM $6 AND status = 'pending'", [decision, reason, decidedBy, new Date(), id, scope]);
|
|
57
|
-
return (res.rowCount ?? 0) === 1;
|
|
58
|
-
}
|
|
59
|
-
async listPending(scope, owner) {
|
|
60
|
-
const res = owner
|
|
61
|
-
? await this.pool.query("SELECT * FROM approval WHERE status = 'pending' AND scope IS NOT DISTINCT FROM $1 AND owner = $2 ORDER BY created_at ASC", [scope, owner])
|
|
62
|
-
: await this.pool.query("SELECT * FROM approval WHERE status = 'pending' AND scope IS NOT DISTINCT FROM $1 ORDER BY created_at ASC", [scope]);
|
|
63
|
-
return res.rows.map(mapRow);
|
|
64
|
-
}
|
|
65
|
-
async get(id, scope) {
|
|
66
|
-
const res = await this.pool.query("SELECT * FROM approval WHERE id = $1 AND scope IS NOT DISTINCT FROM $2", [id, scope]);
|
|
67
|
-
return res.rows[0] ? mapRow(res.rows[0]) : undefined;
|
|
68
|
-
}
|
|
69
|
-
async getById(id) {
|
|
70
|
-
const res = await this.pool.query("SELECT * FROM approval WHERE id = $1", [id]);
|
|
71
|
-
return res.rows[0] ? mapRow(res.rows[0]) : undefined;
|
|
72
|
-
}
|
|
73
|
-
async listPendingAll() {
|
|
74
|
-
const res = await this.pool.query("SELECT * FROM approval WHERE status = 'pending' ORDER BY created_at ASC");
|
|
75
|
-
return res.rows.map(mapRow);
|
|
76
|
-
}
|
|
77
|
-
async expireStale(olderThanMs) {
|
|
78
|
-
const cutoff = new Date(Date.now() - olderThanMs);
|
|
79
|
-
const res = await this.pool.query("UPDATE approval SET status = 'expired', reason = 'timed out before decision', decided_at = $1 " +
|
|
80
|
-
"WHERE status = 'pending' AND created_at < $2", [new Date(), cutoff]);
|
|
81
|
-
return res.rowCount ?? 0;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
1
|
+
export { PgApprovalStore, PG_APPROVAL_SCHEMA, ensureSchema, } from "./approval-store-sql.js";
|
|
84
2
|
//# sourceMappingURL=pg-approval-store.js.map
|
|
@@ -1,20 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
import type { BreakerSnapshot, BreakerState } from "@sema-agent/core";
|
|
3
|
-
export declare const PG_BREAKER_STATE_SCHEMA: string[];
|
|
4
|
-
export declare function ensureSchema(pool: Pool | PoolClient): Promise<void>;
|
|
5
|
-
export declare class PgBreakerState implements BreakerState {
|
|
6
|
-
private readonly pool;
|
|
7
|
-
private readonly onWriteFail?;
|
|
8
|
-
private cache;
|
|
9
|
-
private timer?;
|
|
10
|
-
private readonly pending;
|
|
11
|
-
constructor(pool: Pool, onWriteFail?: ((streak: number) => void) | undefined);
|
|
12
|
-
private writeFailStreak;
|
|
13
|
-
get(key: string): BreakerSnapshot | undefined;
|
|
14
|
-
set(key: string, snap: BreakerSnapshot): void;
|
|
15
|
-
private writeThrough;
|
|
16
|
-
refresh(): Promise<void>;
|
|
17
|
-
startRefresh(intervalMs?: number): this;
|
|
18
|
-
stop(): void;
|
|
19
|
-
}
|
|
1
|
+
export { PgBreakerState, PG_BREAKER_STATE_SCHEMA, ensureSchema } from "./breaker-state-sql.js";
|
|
20
2
|
//# sourceMappingURL=pg-breaker-state.d.ts.map
|
|
@@ -1,89 +1,2 @@
|
|
|
1
|
-
export
|
|
2
|
-
`CREATE TABLE IF NOT EXISTS circuit_breaker (
|
|
3
|
-
breaker_key VARCHAR(190) NOT NULL,
|
|
4
|
-
phase VARCHAR(16) NOT NULL,
|
|
5
|
-
failures INTEGER NOT NULL DEFAULT 0,
|
|
6
|
-
opened_at TIMESTAMPTZ(3),
|
|
7
|
-
updated_at TIMESTAMPTZ(3) NOT NULL,
|
|
8
|
-
PRIMARY KEY (breaker_key)
|
|
9
|
-
)`,
|
|
10
|
-
];
|
|
11
|
-
export async function ensureSchema(pool) {
|
|
12
|
-
for (const stmt of PG_BREAKER_STATE_SCHEMA)
|
|
13
|
-
await pool.query(stmt);
|
|
14
|
-
}
|
|
15
|
-
export class PgBreakerState {
|
|
16
|
-
pool;
|
|
17
|
-
onWriteFail;
|
|
18
|
-
cache = new Map();
|
|
19
|
-
timer;
|
|
20
|
-
pending = new Set();
|
|
21
|
-
constructor(pool, onWriteFail) {
|
|
22
|
-
this.pool = pool;
|
|
23
|
-
this.onWriteFail = onWriteFail;
|
|
24
|
-
}
|
|
25
|
-
writeFailStreak = 0;
|
|
26
|
-
get(key) {
|
|
27
|
-
return this.cache.get(key);
|
|
28
|
-
}
|
|
29
|
-
set(key, snap) {
|
|
30
|
-
this.cache.set(key, snap);
|
|
31
|
-
this.pending.add(key);
|
|
32
|
-
void this.writeThrough(key, snap);
|
|
33
|
-
}
|
|
34
|
-
async writeThrough(key, snap) {
|
|
35
|
-
try {
|
|
36
|
-
await this.pool.query("INSERT INTO circuit_breaker (breaker_key, phase, failures, opened_at, updated_at) VALUES ($1,$2,$3,$4,$5) " +
|
|
37
|
-
"ON CONFLICT (breaker_key) DO UPDATE SET phase=EXCLUDED.phase, failures=EXCLUDED.failures, opened_at=EXCLUDED.opened_at, updated_at=EXCLUDED.updated_at", [key, snap.phase, snap.failures, snap.openedAt != null ? new Date(snap.openedAt) : null, new Date()]);
|
|
38
|
-
this.pending.delete(key);
|
|
39
|
-
if (this.writeFailStreak > 0) {
|
|
40
|
-
this.writeFailStreak = 0;
|
|
41
|
-
try {
|
|
42
|
-
this.onWriteFail?.(0);
|
|
43
|
-
}
|
|
44
|
-
catch { }
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
try {
|
|
49
|
-
this.onWriteFail?.(++this.writeFailStreak);
|
|
50
|
-
}
|
|
51
|
-
catch { }
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
async refresh() {
|
|
55
|
-
try {
|
|
56
|
-
const pendingAtStart = new Set(this.pending);
|
|
57
|
-
const guarded = (key) => this.pending.has(key) || pendingAtStart.has(key);
|
|
58
|
-
const res = await this.pool.query("SELECT breaker_key, phase, failures, opened_at FROM circuit_breaker");
|
|
59
|
-
const seen = new Set();
|
|
60
|
-
for (const r of res.rows) {
|
|
61
|
-
const key = String(r.breaker_key);
|
|
62
|
-
seen.add(key);
|
|
63
|
-
if (guarded(key))
|
|
64
|
-
continue;
|
|
65
|
-
this.cache.set(key, {
|
|
66
|
-
phase: r.phase,
|
|
67
|
-
failures: Number(r.failures),
|
|
68
|
-
openedAt: r.opened_at != null ? new Date(r.opened_at).getTime() : undefined,
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
for (const key of this.cache.keys())
|
|
72
|
-
if (!seen.has(key) && !guarded(key))
|
|
73
|
-
this.cache.delete(key);
|
|
74
|
-
}
|
|
75
|
-
catch {
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
startRefresh(intervalMs = 5_000) {
|
|
79
|
-
this.timer ??= setInterval(() => void this.refresh(), intervalMs);
|
|
80
|
-
this.timer.unref?.();
|
|
81
|
-
return this;
|
|
82
|
-
}
|
|
83
|
-
stop() {
|
|
84
|
-
if (this.timer)
|
|
85
|
-
clearInterval(this.timer);
|
|
86
|
-
this.timer = undefined;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
1
|
+
export { PgBreakerState, PG_BREAKER_STATE_SCHEMA, ensureSchema } from "./breaker-state-sql.js";
|
|
89
2
|
//# sourceMappingURL=pg-breaker-state.js.map
|
|
@@ -1,28 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
import type { Pool } from "pg";
|
|
3
|
-
import { type BlobBackend } from "./blob-backend.js";
|
|
4
|
-
export declare class PgFileSnapshotStore implements FileSnapshotStore {
|
|
5
|
-
private readonly pool;
|
|
6
|
-
private readonly bounds;
|
|
7
|
-
private readonly inFlight;
|
|
8
|
-
private readonly blobs;
|
|
9
|
-
onSkippedVanished: ((count: number) => void | Promise<void>) | undefined;
|
|
10
|
-
constructor(pool: Pool, blobBackend?: BlobBackend, bounds?: FileSnapshotBounds);
|
|
11
|
-
private putBlobRow;
|
|
12
|
-
putBlob(hash: string, bytes: Uint8Array): Promise<FileSnapshotResult>;
|
|
13
|
-
snapshot(scope: string, key: string, env: ExecutionEnv, root: string, signal?: AbortSignal): Promise<FileSnapshotResult>;
|
|
14
|
-
importManifest(scope: string, key: string, manifest: Map<string, string>, srcGetBlob: (hash: string) => Promise<Uint8Array | undefined>): Promise<FileSnapshotResult>;
|
|
15
|
-
restore(scope: string, key: string, env: ExecutionEnv, root: string, signal?: AbortSignal): Promise<FileSnapshotResult>;
|
|
16
|
-
has(scope: string, key: string): Promise<boolean>;
|
|
17
|
-
listKeys(scope: string): Promise<string[]>;
|
|
18
|
-
exportManifest(scope: string, key: string): Promise<Map<string, string> | null>;
|
|
19
|
-
getBlob(hash: string): Promise<Uint8Array | undefined>;
|
|
20
|
-
hasBlobs(hashes: string[]): Promise<Set<string>>;
|
|
21
|
-
blobSizes(hashes: string[]): Promise<Map<string, number>>;
|
|
22
|
-
reap(scope: string, keepKeys: string[]): Promise<number>;
|
|
23
|
-
deleteBySession(scope: string): Promise<number>;
|
|
24
|
-
private gcOrphanBlobs;
|
|
25
|
-
sweepOrphanBlobs(): Promise<number>;
|
|
26
|
-
private tx;
|
|
27
|
-
}
|
|
1
|
+
export { PgFileSnapshotStore } from "./file-snapshot-store-sql.js";
|
|
28
2
|
//# sourceMappingURL=pg-file-snapshot-store.d.ts.map
|
|
@@ -1,191 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
3
|
-
import { SqlBlobBackend, sqlBlobSizes } from "./blob-backend.js";
|
|
4
|
-
export class PgFileSnapshotStore {
|
|
5
|
-
pool;
|
|
6
|
-
bounds;
|
|
7
|
-
inFlight = new Set();
|
|
8
|
-
blobs;
|
|
9
|
-
onSkippedVanished = undefined;
|
|
10
|
-
constructor(pool, blobBackend, bounds = DEFAULT_SNAPSHOT_BOUNDS) {
|
|
11
|
-
this.pool = pool;
|
|
12
|
-
this.bounds = bounds;
|
|
13
|
-
this.blobs = blobBackend ?? new SqlBlobBackend("pg", pool);
|
|
14
|
-
}
|
|
15
|
-
async putBlobRow(hash, bytes) {
|
|
16
|
-
await this.blobs.putBlob(hash, bytes);
|
|
17
|
-
}
|
|
18
|
-
async putBlob(hash, bytes) {
|
|
19
|
-
if (createHash("sha256").update(bytes).digest("hex") !== hash) {
|
|
20
|
-
return { ok: false, error: { code: "read_failed", message: `putBlob ${hash} fails content-address integrity — nothing stored` } };
|
|
21
|
-
}
|
|
22
|
-
try {
|
|
23
|
-
await this.putBlobRow(hash, bytes);
|
|
24
|
-
return { ok: true };
|
|
25
|
-
}
|
|
26
|
-
catch (err) {
|
|
27
|
-
return { ok: false, error: { code: "restore_failed", message: `putBlob ${hash} write/upload failed: ${err.message}` } };
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
async snapshot(scope, key, env, root, signal) {
|
|
31
|
-
const flightKey = `${scope} ${key}`;
|
|
32
|
-
if (this.inFlight.has(flightKey))
|
|
33
|
-
return { ok: true };
|
|
34
|
-
this.inFlight.add(flightKey);
|
|
35
|
-
try {
|
|
36
|
-
if (await this.has(scope, key))
|
|
37
|
-
return { ok: true };
|
|
38
|
-
let cap;
|
|
39
|
-
try {
|
|
40
|
-
cap = await captureManifest(env, root, this.bounds, signal, async (hash, bytes) => {
|
|
41
|
-
await this.putBlobRow(hash, bytes);
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
catch (err) {
|
|
45
|
-
return { ok: false, error: { code: "restore_failed", message: `snapshot blob write failed: ${err.message}` } };
|
|
46
|
-
}
|
|
47
|
-
if (!cap.ok)
|
|
48
|
-
return { ok: false, error: cap.error };
|
|
49
|
-
if (cap.skippedVanished > 0 && this.onSkippedVanished) {
|
|
50
|
-
try {
|
|
51
|
-
void Promise.resolve(this.onSkippedVanished(cap.skippedVanished)).catch(() => { });
|
|
52
|
-
}
|
|
53
|
-
catch { }
|
|
54
|
-
}
|
|
55
|
-
await this.tx(async (c) => {
|
|
56
|
-
const exists = await c.query("SELECT 1 FROM snapshot_manifest WHERE scope = $1 AND snap_key = $2 LIMIT 1", [scope, key]);
|
|
57
|
-
if (exists.rows[0])
|
|
58
|
-
return;
|
|
59
|
-
for (const [relPath, hash] of cap.value) {
|
|
60
|
-
await c.query("INSERT INTO snapshot_manifest (scope, snap_key, rel_path, blob_hash) VALUES ($1,$2,$3,$4)", [scope, key, relPath, hash]);
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
return { ok: true };
|
|
64
|
-
}
|
|
65
|
-
catch (err) {
|
|
66
|
-
return { ok: false, error: { code: "restore_failed", message: `snapshot persist failed: ${err.message}` } };
|
|
67
|
-
}
|
|
68
|
-
finally {
|
|
69
|
-
this.inFlight.delete(flightKey);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
async importManifest(scope, key, manifest, srcGetBlob) {
|
|
73
|
-
const flightKey = `${scope} ${key}`;
|
|
74
|
-
if (this.inFlight.has(flightKey))
|
|
75
|
-
return { ok: true };
|
|
76
|
-
this.inFlight.add(flightKey);
|
|
77
|
-
try {
|
|
78
|
-
if (await this.has(scope, key))
|
|
79
|
-
return { ok: true };
|
|
80
|
-
for (const hash of new Set(manifest.values())) {
|
|
81
|
-
let bytes;
|
|
82
|
-
try {
|
|
83
|
-
bytes = await srcGetBlob(hash);
|
|
84
|
-
}
|
|
85
|
-
catch (err) {
|
|
86
|
-
return { ok: false, error: { code: "read_failed", message: `source blob ${hash} fetch failed: ${err.message}` } };
|
|
87
|
-
}
|
|
88
|
-
if (!bytes)
|
|
89
|
-
return { ok: false, error: { code: "read_failed", message: `missing source blob ${hash}` } };
|
|
90
|
-
if (createHash("sha256").update(bytes).digest("hex") !== hash) {
|
|
91
|
-
return { ok: false, error: { code: "read_failed", message: `source blob ${hash} fails content-address integrity` } };
|
|
92
|
-
}
|
|
93
|
-
await this.putBlobRow(hash, bytes);
|
|
94
|
-
}
|
|
95
|
-
await this.tx(async (c) => {
|
|
96
|
-
const exists = await c.query("SELECT 1 FROM snapshot_manifest WHERE scope = $1 AND snap_key = $2 LIMIT 1", [scope, key]);
|
|
97
|
-
if (exists.rows[0])
|
|
98
|
-
return;
|
|
99
|
-
for (const [relPath, hash] of manifest) {
|
|
100
|
-
await c.query("INSERT INTO snapshot_manifest (scope, snap_key, rel_path, blob_hash) VALUES ($1,$2,$3,$4)", [scope, key, relPath, hash]);
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
return { ok: true };
|
|
104
|
-
}
|
|
105
|
-
catch (err) {
|
|
106
|
-
return { ok: false, error: { code: "restore_failed", message: `import persist failed: ${err.message}` } };
|
|
107
|
-
}
|
|
108
|
-
finally {
|
|
109
|
-
this.inFlight.delete(flightKey);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
async restore(scope, key, env, root, signal) {
|
|
113
|
-
try {
|
|
114
|
-
const r = await this.pool.query("SELECT rel_path, blob_hash FROM snapshot_manifest WHERE scope = $1 AND snap_key = $2", [scope, key]);
|
|
115
|
-
if (r.rows.length === 0)
|
|
116
|
-
return { ok: false, error: { code: "not_found", message: `no file snapshot for ${scope}/${key}` } };
|
|
117
|
-
const manifest = new Map(r.rows.map((row) => [row.rel_path, row.blob_hash]));
|
|
118
|
-
return await applyManifest(env, root, this.bounds, signal, manifest, (hash) => this.blobs.getBlob(hash));
|
|
119
|
-
}
|
|
120
|
-
catch (err) {
|
|
121
|
-
return { ok: false, error: { code: "restore_failed", message: `restore blob read failed: ${err.message}` } };
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
async has(scope, key) {
|
|
125
|
-
const r = await this.pool.query("SELECT 1 FROM snapshot_manifest WHERE scope = $1 AND snap_key = $2 LIMIT 1", [scope, key]);
|
|
126
|
-
return Boolean(r.rows[0]);
|
|
127
|
-
}
|
|
128
|
-
async listKeys(scope) {
|
|
129
|
-
const r = await this.pool.query("SELECT DISTINCT snap_key FROM snapshot_manifest WHERE scope = $1", [scope]);
|
|
130
|
-
return r.rows.map((row) => row.snap_key);
|
|
131
|
-
}
|
|
132
|
-
async exportManifest(scope, key) {
|
|
133
|
-
const r = await this.pool.query("SELECT rel_path, blob_hash FROM snapshot_manifest WHERE scope = $1 AND snap_key = $2", [scope, key]);
|
|
134
|
-
if (r.rows.length === 0)
|
|
135
|
-
return null;
|
|
136
|
-
return new Map(r.rows.map((row) => [row.rel_path, row.blob_hash]));
|
|
137
|
-
}
|
|
138
|
-
async getBlob(hash) {
|
|
139
|
-
return this.blobs.getBlob(hash);
|
|
140
|
-
}
|
|
141
|
-
async hasBlobs(hashes) {
|
|
142
|
-
return this.blobs.hasBlobs(hashes);
|
|
143
|
-
}
|
|
144
|
-
async blobSizes(hashes) {
|
|
145
|
-
return sqlBlobSizes("pg", this.pool, hashes);
|
|
146
|
-
}
|
|
147
|
-
async reap(scope, keepKeys) {
|
|
148
|
-
return this.tx(async (c) => {
|
|
149
|
-
const keys = await c.query("SELECT DISTINCT snap_key FROM snapshot_manifest WHERE scope = $1", [scope]);
|
|
150
|
-
const drop = keys.rows.map((k) => k.snap_key).filter((k) => !keepKeys.includes(k));
|
|
151
|
-
for (const k of drop)
|
|
152
|
-
await c.query("DELETE FROM snapshot_manifest WHERE scope = $1 AND snap_key = $2", [scope, k]);
|
|
153
|
-
return drop.length;
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
async deleteBySession(scope) {
|
|
157
|
-
const r = await this.pool.query("DELETE FROM snapshot_manifest WHERE scope = $1", [scope]);
|
|
158
|
-
return r.rowCount ?? 0;
|
|
159
|
-
}
|
|
160
|
-
async gcOrphanBlobs() {
|
|
161
|
-
const r = await this.pool.query("SELECT blob_hash FROM snapshot_blob WHERE blob_hash NOT IN (SELECT blob_hash FROM snapshot_manifest)").catch(() => undefined);
|
|
162
|
-
const orphans = (r?.rows ?? []).map((row) => row.blob_hash);
|
|
163
|
-
if (orphans.length === 0)
|
|
164
|
-
return 0;
|
|
165
|
-
return this.blobs.deleteBlobs(orphans);
|
|
166
|
-
}
|
|
167
|
-
async sweepOrphanBlobs() {
|
|
168
|
-
return this.gcOrphanBlobs();
|
|
169
|
-
}
|
|
170
|
-
async tx(fn) {
|
|
171
|
-
const c = await this.pool.connect();
|
|
172
|
-
try {
|
|
173
|
-
await c.query("BEGIN");
|
|
174
|
-
const out = await fn(c);
|
|
175
|
-
await c.query("COMMIT");
|
|
176
|
-
return out;
|
|
177
|
-
}
|
|
178
|
-
catch (err) {
|
|
179
|
-
try {
|
|
180
|
-
await c.query("ROLLBACK");
|
|
181
|
-
}
|
|
182
|
-
catch {
|
|
183
|
-
}
|
|
184
|
-
throw err;
|
|
185
|
-
}
|
|
186
|
-
finally {
|
|
187
|
-
c.release();
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
1
|
+
export { PgFileSnapshotStore } from "./file-snapshot-store-sql.js";
|
|
191
2
|
//# sourceMappingURL=pg-file-snapshot-store.js.map
|
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
import { type ImageStatus, type ImageIndexEntry, type ImageIndexUpsert, type ImageViewer, type ImageListFilter } from "./store-contracts.js";
|
|
1
|
+
export { PgImageIndex, PG_IMAGE_INDEX_SCHEMA, ensureSchema } from "./image-index-sql.js";
|
|
3
2
|
export type { ImageNestedBuildMode, ImageCapabilities, ImagePodContract, ImageStatus, ImageVisibility, ImageIndexEntry, ImageIndexUpsert, ImageViewer, ImageListFilter, } from "./store-contracts.js";
|
|
4
|
-
export declare const PG_IMAGE_INDEX_SCHEMA: string[];
|
|
5
|
-
export declare function ensureSchema(pool: Pool | PoolClient): Promise<void>;
|
|
6
|
-
export declare class PgImageIndex {
|
|
7
|
-
private readonly pool;
|
|
8
|
-
constructor(pool: Pool);
|
|
9
|
-
static idFor(repo: string, digest: string): string;
|
|
10
|
-
upsert(e: ImageIndexUpsert): Promise<string>;
|
|
11
|
-
registerBuilding(e: ImageIndexUpsert): Promise<string>;
|
|
12
|
-
getById(id: string): Promise<ImageIndexEntry | null>;
|
|
13
|
-
getByDigest(digest: string): Promise<ImageIndexEntry | null>;
|
|
14
|
-
latestPublished(profile: string, viewer: ImageViewer): Promise<ImageIndexEntry | null>;
|
|
15
|
-
list(filter: ImageListFilter): Promise<{
|
|
16
|
-
entries: ImageIndexEntry[];
|
|
17
|
-
nextCursor: string | null;
|
|
18
|
-
}>;
|
|
19
|
-
setStatus(id: string, status: ImageStatus, opts?: {
|
|
20
|
-
supersedes?: string | null;
|
|
21
|
-
}): Promise<void>;
|
|
22
|
-
}
|
|
23
3
|
//# sourceMappingURL=pg-image-index.d.ts.map
|
|
@@ -1,218 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
import { pgSafeJsonStringify, pgSanitizeText, pgHasUnstorable, PgUnstorableError } from "./pg-safe-json.js";
|
|
3
|
-
import { mapImageRow as mapRow, } from "./store-contracts.js";
|
|
4
|
-
export const PG_IMAGE_INDEX_SCHEMA = [
|
|
5
|
-
`CREATE TABLE IF NOT EXISTS sandbox_image_index (
|
|
6
|
-
id VARCHAR(64) NOT NULL,
|
|
7
|
-
profile VARCHAR(64) NOT NULL,
|
|
8
|
-
bands JSONB,
|
|
9
|
-
repo VARCHAR(255) NOT NULL,
|
|
10
|
-
tag VARCHAR(128) NOT NULL,
|
|
11
|
-
digest VARCHAR(80) NOT NULL,
|
|
12
|
-
toolchain_versions JSONB,
|
|
13
|
-
capabilities JSONB,
|
|
14
|
-
pod_contract JSONB,
|
|
15
|
-
size_bytes BIGINT,
|
|
16
|
-
status VARCHAR(16) NOT NULL DEFAULT 'building',
|
|
17
|
-
visibility VARCHAR(16) NOT NULL DEFAULT 'public',
|
|
18
|
-
tenant_id VARCHAR(64),
|
|
19
|
-
manifest_sha VARCHAR(64),
|
|
20
|
-
recipe_git_sha VARCHAR(64),
|
|
21
|
-
generator_version VARCHAR(32),
|
|
22
|
-
build_date TIMESTAMPTZ(3),
|
|
23
|
-
supersedes VARCHAR(64),
|
|
24
|
-
signed SMALLINT NOT NULL DEFAULT 0,
|
|
25
|
-
created_at TIMESTAMPTZ(3) NOT NULL,
|
|
26
|
-
updated_at TIMESTAMPTZ(3) NOT NULL,
|
|
27
|
-
PRIMARY KEY (id)
|
|
28
|
-
)`,
|
|
29
|
-
`CREATE UNIQUE INDEX IF NOT EXISTS uq_repo_digest ON sandbox_image_index (repo, digest)`,
|
|
30
|
-
`CREATE INDEX IF NOT EXISTS idx_profile_status ON sandbox_image_index (profile, status, visibility)`,
|
|
31
|
-
`CREATE INDEX IF NOT EXISTS idx_digest ON sandbox_image_index (digest)`,
|
|
32
|
-
`CREATE INDEX IF NOT EXISTS idx_tenant ON sandbox_image_index (tenant_id, status)`,
|
|
33
|
-
];
|
|
34
|
-
export async function ensureSchema(pool) {
|
|
35
|
-
for (const stmt of PG_IMAGE_INDEX_SCHEMA)
|
|
36
|
-
await pool.query(stmt);
|
|
37
|
-
}
|
|
38
|
-
const MAX_LIMIT = 200;
|
|
39
|
-
const DEFAULT_LIMIT = 50;
|
|
40
|
-
function visibilityClause(viewer, startIdx) {
|
|
41
|
-
if (viewer.operator)
|
|
42
|
-
return { sql: "1=1", params: [], nextIdx: startIdx };
|
|
43
|
-
if (viewer.tenantId)
|
|
44
|
-
return {
|
|
45
|
-
sql: `(visibility = 'public' OR tenant_id = $${startIdx})`,
|
|
46
|
-
params: [viewer.tenantId],
|
|
47
|
-
nextIdx: startIdx + 1,
|
|
48
|
-
};
|
|
49
|
-
return { sql: "visibility = 'public'", params: [], nextIdx: startIdx };
|
|
50
|
-
}
|
|
51
|
-
function encodeCursor(e) {
|
|
52
|
-
return Buffer.from(`${e.createdAt}|${e.id}`, "utf8").toString("base64url");
|
|
53
|
-
}
|
|
54
|
-
function decodeCursor(c) {
|
|
55
|
-
try {
|
|
56
|
-
const [createdAt, id] = Buffer.from(c, "base64url").toString("utf8").split("|");
|
|
57
|
-
if (!createdAt || !id)
|
|
58
|
-
return null;
|
|
59
|
-
return { createdAt, id };
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
const UPSERT_COLS = "(id, profile, bands, repo, tag, digest, toolchain_versions, capabilities, pod_contract, size_bytes, " +
|
|
66
|
-
"status, visibility, tenant_id, manifest_sha, recipe_git_sha, generator_version, build_date, supersedes, " +
|
|
67
|
-
"signed, created_at, updated_at)";
|
|
68
|
-
const UPSERT_VALUES = "($1,$2,$3::jsonb,$4,$5,$6,$7::jsonb,$8::jsonb,$9::jsonb,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21)";
|
|
69
|
-
function upsertParams(e, id, now) {
|
|
70
|
-
return [
|
|
71
|
-
id,
|
|
72
|
-
pgSanitizeText(e.profile),
|
|
73
|
-
pgSafeJsonStringify(e.bands ?? []),
|
|
74
|
-
e.repo,
|
|
75
|
-
pgSanitizeText(e.tag),
|
|
76
|
-
e.digest,
|
|
77
|
-
pgSafeJsonStringify(e.toolchainVersions ?? {}),
|
|
78
|
-
pgSafeJsonStringify(e.capabilities ?? {}),
|
|
79
|
-
pgSafeJsonStringify(e.podContract ?? {}),
|
|
80
|
-
e.sizeBytes,
|
|
81
|
-
e.status,
|
|
82
|
-
e.visibility,
|
|
83
|
-
e.tenantId ?? null,
|
|
84
|
-
e.manifestSha ?? null,
|
|
85
|
-
e.recipeGitSha == null ? null : pgSanitizeText(e.recipeGitSha),
|
|
86
|
-
e.generatorVersion == null ? null : pgSanitizeText(e.generatorVersion),
|
|
87
|
-
e.buildDate ? new Date(e.buildDate) : null,
|
|
88
|
-
e.supersedes == null ? null : pgSanitizeText(e.supersedes),
|
|
89
|
-
e.signed ? 1 : 0,
|
|
90
|
-
now,
|
|
91
|
-
now,
|
|
92
|
-
];
|
|
93
|
-
}
|
|
94
|
-
export class PgImageIndex {
|
|
95
|
-
pool;
|
|
96
|
-
constructor(pool) {
|
|
97
|
-
this.pool = pool;
|
|
98
|
-
}
|
|
99
|
-
static idFor(repo, digest) {
|
|
100
|
-
return createHash("sha256").update(`${repo}@${digest}`).digest("hex").slice(0, 64);
|
|
101
|
-
}
|
|
102
|
-
async upsert(e) {
|
|
103
|
-
if (pgHasUnstorable(e.repo))
|
|
104
|
-
throw new PgUnstorableError("image repo (identity)");
|
|
105
|
-
if (e.tenantId != null && pgHasUnstorable(e.tenantId))
|
|
106
|
-
throw new PgUnstorableError("image tenantId (identity)");
|
|
107
|
-
const id = PgImageIndex.idFor(e.repo, e.digest);
|
|
108
|
-
const now = new Date();
|
|
109
|
-
await this.pool.query(`INSERT INTO sandbox_image_index ${UPSERT_COLS}
|
|
110
|
-
VALUES ${UPSERT_VALUES}
|
|
111
|
-
ON CONFLICT (id) DO UPDATE SET
|
|
112
|
-
profile = EXCLUDED.profile, bands = EXCLUDED.bands, tag = EXCLUDED.tag,
|
|
113
|
-
toolchain_versions = EXCLUDED.toolchain_versions, capabilities = EXCLUDED.capabilities,
|
|
114
|
-
pod_contract = EXCLUDED.pod_contract, size_bytes = EXCLUDED.size_bytes,
|
|
115
|
-
status = EXCLUDED.status, visibility = EXCLUDED.visibility, tenant_id = EXCLUDED.tenant_id,
|
|
116
|
-
manifest_sha = EXCLUDED.manifest_sha, recipe_git_sha = EXCLUDED.recipe_git_sha,
|
|
117
|
-
generator_version = EXCLUDED.generator_version, build_date = EXCLUDED.build_date,
|
|
118
|
-
supersedes = EXCLUDED.supersedes, signed = EXCLUDED.signed, updated_at = EXCLUDED.updated_at`, upsertParams(e, id, now));
|
|
119
|
-
return id;
|
|
120
|
-
}
|
|
121
|
-
async registerBuilding(e) {
|
|
122
|
-
if (pgHasUnstorable(e.repo))
|
|
123
|
-
throw new PgUnstorableError("image repo (identity)");
|
|
124
|
-
if (e.tenantId != null && pgHasUnstorable(e.tenantId))
|
|
125
|
-
throw new PgUnstorableError("image tenantId (identity)");
|
|
126
|
-
const id = PgImageIndex.idFor(e.repo, e.digest);
|
|
127
|
-
const now = new Date();
|
|
128
|
-
await this.pool.query(`INSERT INTO sandbox_image_index ${UPSERT_COLS}
|
|
129
|
-
VALUES ${UPSERT_VALUES}
|
|
130
|
-
ON CONFLICT (id) DO UPDATE SET
|
|
131
|
-
profile = EXCLUDED.profile, bands = EXCLUDED.bands, tag = EXCLUDED.tag,
|
|
132
|
-
toolchain_versions = EXCLUDED.toolchain_versions, capabilities = EXCLUDED.capabilities,
|
|
133
|
-
pod_contract = EXCLUDED.pod_contract, size_bytes = EXCLUDED.size_bytes,
|
|
134
|
-
status = CASE WHEN sandbox_image_index.status = 'published'
|
|
135
|
-
THEN sandbox_image_index.status ELSE EXCLUDED.status END,
|
|
136
|
-
visibility = CASE WHEN sandbox_image_index.status = 'published'
|
|
137
|
-
THEN sandbox_image_index.visibility ELSE EXCLUDED.visibility END,
|
|
138
|
-
tenant_id = CASE WHEN sandbox_image_index.status = 'published'
|
|
139
|
-
THEN sandbox_image_index.tenant_id ELSE EXCLUDED.tenant_id END,
|
|
140
|
-
manifest_sha = EXCLUDED.manifest_sha, recipe_git_sha = EXCLUDED.recipe_git_sha,
|
|
141
|
-
generator_version = EXCLUDED.generator_version, build_date = EXCLUDED.build_date,
|
|
142
|
-
supersedes = EXCLUDED.supersedes,
|
|
143
|
-
signed = CASE WHEN sandbox_image_index.status = 'published'
|
|
144
|
-
THEN sandbox_image_index.signed ELSE EXCLUDED.signed END,
|
|
145
|
-
updated_at = EXCLUDED.updated_at`, upsertParams(e, id, now));
|
|
146
|
-
return id;
|
|
147
|
-
}
|
|
148
|
-
async getById(id) {
|
|
149
|
-
const res = await this.pool.query("SELECT * FROM sandbox_image_index WHERE id = $1 LIMIT 1", [id]);
|
|
150
|
-
return res.rows[0] ? mapRow(res.rows[0]) : null;
|
|
151
|
-
}
|
|
152
|
-
async getByDigest(digest) {
|
|
153
|
-
const res = await this.pool.query("SELECT * FROM sandbox_image_index WHERE digest = $1 ORDER BY updated_at DESC LIMIT 1", [digest]);
|
|
154
|
-
return res.rows[0] ? mapRow(res.rows[0]) : null;
|
|
155
|
-
}
|
|
156
|
-
async latestPublished(profile, viewer) {
|
|
157
|
-
const vis = visibilityClause(viewer, 2);
|
|
158
|
-
const res = await this.pool.query(`SELECT * FROM sandbox_image_index
|
|
159
|
-
WHERE profile = $1 AND status = 'published' AND ${vis.sql}
|
|
160
|
-
ORDER BY build_date DESC NULLS LAST, created_at DESC LIMIT 1`, [profile, ...vis.params]);
|
|
161
|
-
return res.rows[0] ? mapRow(res.rows[0]) : null;
|
|
162
|
-
}
|
|
163
|
-
async list(filter) {
|
|
164
|
-
const limit = Math.min(Math.max(1, filter.limit ?? DEFAULT_LIMIT), MAX_LIMIT);
|
|
165
|
-
const vis = visibilityClause(filter, 1);
|
|
166
|
-
const where = [vis.sql];
|
|
167
|
-
const params = [...vis.params];
|
|
168
|
-
let idx = vis.nextIdx;
|
|
169
|
-
if (filter.profile) {
|
|
170
|
-
where.push(`profile = $${idx++}`);
|
|
171
|
-
params.push(filter.profile);
|
|
172
|
-
}
|
|
173
|
-
if (filter.status) {
|
|
174
|
-
where.push(`status = $${idx++}`);
|
|
175
|
-
params.push(filter.status);
|
|
176
|
-
}
|
|
177
|
-
if (filter.capability) {
|
|
178
|
-
where.push(`capabilities -> $${idx++} = 'true'::jsonb`);
|
|
179
|
-
params.push(filter.capability);
|
|
180
|
-
}
|
|
181
|
-
if (filter.cursor) {
|
|
182
|
-
const c = decodeCursor(filter.cursor);
|
|
183
|
-
if (c) {
|
|
184
|
-
where.push(`(created_at < $${idx} OR (created_at = $${idx + 1} AND id < $${idx + 2}))`);
|
|
185
|
-
params.push(new Date(c.createdAt), new Date(c.createdAt), c.id);
|
|
186
|
-
idx += 3;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
const sql = `SELECT * FROM sandbox_image_index WHERE ${where.join(" AND ")} ` +
|
|
190
|
-
`ORDER BY created_at DESC, id DESC LIMIT $${idx}`;
|
|
191
|
-
params.push(limit + 1);
|
|
192
|
-
const res = await this.pool.query(sql, params);
|
|
193
|
-
let entries = res.rows.map(mapRow);
|
|
194
|
-
let nextCursor = null;
|
|
195
|
-
if (entries.length > limit) {
|
|
196
|
-
entries = entries.slice(0, limit);
|
|
197
|
-
const last = entries[entries.length - 1];
|
|
198
|
-
nextCursor = encodeCursor({ createdAt: last.createdAt, id: last.id });
|
|
199
|
-
}
|
|
200
|
-
if (filter.latestOnly) {
|
|
201
|
-
const seen = new Set();
|
|
202
|
-
entries = entries.filter((e) => (seen.has(e.profile) ? false : (seen.add(e.profile), true)));
|
|
203
|
-
}
|
|
204
|
-
return { entries, nextCursor };
|
|
205
|
-
}
|
|
206
|
-
async setStatus(id, status, opts) {
|
|
207
|
-
const sets = ["status = $1", "updated_at = $2"];
|
|
208
|
-
const params = [status, new Date()];
|
|
209
|
-
let idx = 3;
|
|
210
|
-
if (opts && "supersedes" in opts) {
|
|
211
|
-
sets.push(`supersedes = $${idx++}`);
|
|
212
|
-
params.push(opts.supersedes ?? null);
|
|
213
|
-
}
|
|
214
|
-
params.push(id);
|
|
215
|
-
await this.pool.query(`UPDATE sandbox_image_index SET ${sets.join(", ")} WHERE id = $${idx}`, params);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
1
|
+
export { PgImageIndex, PG_IMAGE_INDEX_SCHEMA, ensureSchema } from "./image-index-sql.js";
|
|
218
2
|
//# sourceMappingURL=pg-image-index.js.map
|