@tekki-forge/runtime-api 0.1.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/README.md +5 -0
- package/dist/access.d.ts +15 -0
- package/dist/access.js +51 -0
- package/dist/access.js.map +1 -0
- package/dist/api-keys.d.ts +140 -0
- package/dist/api-keys.js +361 -0
- package/dist/api-keys.js.map +1 -0
- package/dist/backup.d.ts +142 -0
- package/dist/backup.js +280 -0
- package/dist/backup.js.map +1 -0
- package/dist/bootstrap.d.ts +57 -0
- package/dist/bootstrap.js +191 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/comments.d.ts +139 -0
- package/dist/comments.js +370 -0
- package/dist/comments.js.map +1 -0
- package/dist/computed.d.ts +27 -0
- package/dist/computed.js +174 -0
- package/dist/computed.js.map +1 -0
- package/dist/controllers.d.ts +309 -0
- package/dist/controllers.js +634 -0
- package/dist/controllers.js.map +1 -0
- package/dist/dashboard.d.ts +58 -0
- package/dist/dashboard.js +258 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +55 -0
- package/dist/errors.js.map +1 -0
- package/dist/events.d.ts +39 -0
- package/dist/events.js +36 -0
- package/dist/events.js.map +1 -0
- package/dist/extension.d.ts +103 -0
- package/dist/extension.js +8 -0
- package/dist/extension.js.map +1 -0
- package/dist/import.d.ts +69 -0
- package/dist/import.js +347 -0
- package/dist/import.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/jobs.d.ts +41 -0
- package/dist/jobs.js +10 -0
- package/dist/jobs.js.map +1 -0
- package/dist/notifications.d.ts +61 -0
- package/dist/notifications.js +195 -0
- package/dist/notifications.js.map +1 -0
- package/dist/plugin.d.ts +43 -0
- package/dist/plugin.js +4 -0
- package/dist/plugin.js.map +1 -0
- package/dist/query.d.ts +29 -0
- package/dist/query.js +192 -0
- package/dist/query.js.map +1 -0
- package/dist/saved-views.d.ts +42 -0
- package/dist/saved-views.js +130 -0
- package/dist/saved-views.js.map +1 -0
- package/dist/search.d.ts +17 -0
- package/dist/search.js +42 -0
- package/dist/search.js.map +1 -0
- package/dist/seed.d.ts +12 -0
- package/dist/seed.js +132 -0
- package/dist/seed.js.map +1 -0
- package/dist/serialize.d.ts +37 -0
- package/dist/serialize.js +165 -0
- package/dist/serialize.js.map +1 -0
- package/dist/service.d.ts +168 -0
- package/dist/service.js +757 -0
- package/dist/service.js.map +1 -0
- package/dist/teams.d.ts +63 -0
- package/dist/teams.js +131 -0
- package/dist/teams.js.map +1 -0
- package/dist/testing.d.ts +63 -0
- package/dist/testing.js +420 -0
- package/dist/testing.js.map +1 -0
- package/package.json +61 -0
package/dist/backup.d.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database backups: a compressed plain-SQL pg_dump plus a manifest (and the list of uploaded files),
|
|
3
|
+
* stored in a local folder or object storage. Used by `forge backup` and by the scheduled
|
|
4
|
+
* `forge.backup` job when the jobs plugin runs a worker. Node built-ins only.
|
|
5
|
+
*/
|
|
6
|
+
export interface PgConnection {
|
|
7
|
+
host: string;
|
|
8
|
+
port: number;
|
|
9
|
+
user: string;
|
|
10
|
+
password: string;
|
|
11
|
+
database: string;
|
|
12
|
+
sslmode?: string;
|
|
13
|
+
schema?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function parseDatabaseUrl(url: string): PgConnection;
|
|
16
|
+
/** Where pg_dump/psql run: on this machine, or inside a PostgreSQL container (docker exec / docker compose exec). */
|
|
17
|
+
export type PgExec = {
|
|
18
|
+
kind: "local";
|
|
19
|
+
} | {
|
|
20
|
+
kind: "docker";
|
|
21
|
+
container: string;
|
|
22
|
+
} | {
|
|
23
|
+
kind: "compose";
|
|
24
|
+
service: string;
|
|
25
|
+
cwd: string;
|
|
26
|
+
};
|
|
27
|
+
export interface PgCommand {
|
|
28
|
+
command: string;
|
|
29
|
+
args: string[];
|
|
30
|
+
/** Extra environment (the password never appears in arguments). */
|
|
31
|
+
env: Record<string, string>;
|
|
32
|
+
cwd?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Build the command line for a PostgreSQL client tool. Inside a container the tool connects over the
|
|
36
|
+
* local socket as the database user (trusted in the official image), so no password is passed at all.
|
|
37
|
+
*/
|
|
38
|
+
export declare function pgCommand(tool: "pg_dump" | "psql", exec: PgExec, conn: PgConnection, args: string[]): PgCommand;
|
|
39
|
+
/** FORGE_PG_CONTAINER=<name> runs the tools in that container (docker exec), otherwise on this machine. */
|
|
40
|
+
export declare function defaultPgExec(env?: NodeJS.ProcessEnv): PgExec;
|
|
41
|
+
/** pg_dump arguments: plain SQL that drops and recreates every object, without owners or grants (portable). */
|
|
42
|
+
export declare function pgDumpArgs(conn: PgConnection): string[];
|
|
43
|
+
/** psql arguments for a restore: stop at the first error, all or nothing. */
|
|
44
|
+
export declare function restoreArgs(): string[];
|
|
45
|
+
/** Run a query through psql and return its unaligned output. */
|
|
46
|
+
export declare function psqlQuery(exec: PgExec, conn: PgConnection, sql: string): Promise<string>;
|
|
47
|
+
/** Major version of a local client tool, or null when it is not installed. */
|
|
48
|
+
export declare function localToolMajor(tool: "pg_dump" | "psql"): Promise<number | null>;
|
|
49
|
+
export interface BackupStore {
|
|
50
|
+
/** Human-readable location, e.g. "/srv/backups" or "s3://bucket/backups/". */
|
|
51
|
+
readonly location: string;
|
|
52
|
+
put(name: string, file: string, source: string): Promise<void>;
|
|
53
|
+
/** Copy a stored file to a local path. */
|
|
54
|
+
fetch(name: string, file: string, target: string): Promise<void>;
|
|
55
|
+
/** Names of the stored backups (those with a manifest). */
|
|
56
|
+
list(): Promise<string[]>;
|
|
57
|
+
remove(name: string): Promise<void>;
|
|
58
|
+
}
|
|
59
|
+
export declare function localBackupStore(dir: string): BackupStore;
|
|
60
|
+
export interface FileEntry {
|
|
61
|
+
key: string;
|
|
62
|
+
name: string;
|
|
63
|
+
size: number;
|
|
64
|
+
mimeType: string;
|
|
65
|
+
entity: string;
|
|
66
|
+
field: string;
|
|
67
|
+
recordId: string | null;
|
|
68
|
+
status: string;
|
|
69
|
+
}
|
|
70
|
+
export interface BackupManifest {
|
|
71
|
+
format: 1;
|
|
72
|
+
app: string;
|
|
73
|
+
name: string;
|
|
74
|
+
createdAt: string;
|
|
75
|
+
database: {
|
|
76
|
+
host: string;
|
|
77
|
+
port: number;
|
|
78
|
+
database: string;
|
|
79
|
+
schema?: string;
|
|
80
|
+
};
|
|
81
|
+
dump: {
|
|
82
|
+
file: string;
|
|
83
|
+
bytes: number;
|
|
84
|
+
sha256: string;
|
|
85
|
+
tool: string;
|
|
86
|
+
};
|
|
87
|
+
/** Uploaded files referenced by the database (the objects themselves stay in their storage). */
|
|
88
|
+
files: {
|
|
89
|
+
count: number;
|
|
90
|
+
bytes: number;
|
|
91
|
+
storage: string | null;
|
|
92
|
+
items: FileEntry[];
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/** "<slug>-20261001T030000Z" — sortable, safe as a folder or object prefix. */
|
|
96
|
+
export declare function backupName(slug: string, at?: Date): string;
|
|
97
|
+
/** Backups to delete so that the `keep` most recent remain (only names Forge created are considered). */
|
|
98
|
+
export declare function backupsToPrune(names: string[], keep: number): string[];
|
|
99
|
+
export interface CreateBackupOptions {
|
|
100
|
+
databaseUrl: string;
|
|
101
|
+
store: BackupStore;
|
|
102
|
+
exec?: PgExec;
|
|
103
|
+
app: {
|
|
104
|
+
name: string;
|
|
105
|
+
slug: string;
|
|
106
|
+
};
|
|
107
|
+
/** Uploaded files to list in the manifest (storage plugin's File table). */
|
|
108
|
+
files?: () => Promise<FileEntry[]>;
|
|
109
|
+
/** Where uploaded objects live, for the manifest (e.g. "s3://bucket"). */
|
|
110
|
+
fileStorage?: string | null;
|
|
111
|
+
/** Delete older backups so this many remain. */
|
|
112
|
+
keep?: number;
|
|
113
|
+
name?: string;
|
|
114
|
+
log?: (line: string) => void;
|
|
115
|
+
}
|
|
116
|
+
export declare function createBackup(o: CreateBackupOptions): Promise<{
|
|
117
|
+
manifest: BackupManifest;
|
|
118
|
+
pruned: string[];
|
|
119
|
+
}>;
|
|
120
|
+
export declare function readManifest(store: BackupStore, name: string): Promise<BackupManifest>;
|
|
121
|
+
/** Manifests of every stored backup, newest first. */
|
|
122
|
+
export declare function listBackups(store: BackupStore): Promise<BackupManifest[]>;
|
|
123
|
+
/** Uploaded files listed in the storage plugin's `files` table (empty without the plugin), read through psql. */
|
|
124
|
+
export declare function filesFromDatabase(exec: PgExec, conn: PgConnection): Promise<FileEntry[]>;
|
|
125
|
+
/** The same list through Prisma (worker side). */
|
|
126
|
+
export declare function filesFromPrisma(prisma: any): Promise<FileEntry[]>;
|
|
127
|
+
/** Tables of the target database with their (estimated) row counts — what a restore overwrites. */
|
|
128
|
+
export declare function databaseTables(exec: PgExec, conn: PgConnection): Promise<{
|
|
129
|
+
table: string;
|
|
130
|
+
rows: number;
|
|
131
|
+
}[]>;
|
|
132
|
+
/** Replace the database's contents with a backup (one transaction; the dump drops and recreates its objects). */
|
|
133
|
+
export declare function restoreBackup(o: {
|
|
134
|
+
databaseUrl: string;
|
|
135
|
+
store: BackupStore;
|
|
136
|
+
name: string;
|
|
137
|
+
exec?: PgExec;
|
|
138
|
+
log?: (line: string) => void;
|
|
139
|
+
}): Promise<BackupManifest>;
|
|
140
|
+
export declare function formatBytes(n: number): string;
|
|
141
|
+
/** A database URL that points at a non-local host or a production environment. */
|
|
142
|
+
export declare function looksLikeProduction(conn: PgConnection, env?: NodeJS.ProcessEnv): boolean;
|
package/dist/backup.js
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { createReadStream, createWriteStream, existsSync } from "node:fs";
|
|
4
|
+
import { mkdir, mkdtemp, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join, resolve } from "node:path";
|
|
7
|
+
import { pipeline } from "node:stream/promises";
|
|
8
|
+
import { createGunzip, createGzip } from "node:zlib";
|
|
9
|
+
export function parseDatabaseUrl(url) {
|
|
10
|
+
let u;
|
|
11
|
+
try {
|
|
12
|
+
u = new URL(url);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
throw new Error("DATABASE_URL is not a valid postgresql:// URL");
|
|
16
|
+
}
|
|
17
|
+
if (u.protocol !== "postgresql:" && u.protocol !== "postgres:")
|
|
18
|
+
throw new Error("DATABASE_URL must be a postgresql:// URL");
|
|
19
|
+
const database = decodeURIComponent(u.pathname.replace(/^\//, ""));
|
|
20
|
+
if (!database)
|
|
21
|
+
throw new Error("DATABASE_URL has no database name");
|
|
22
|
+
return {
|
|
23
|
+
host: u.hostname || "localhost",
|
|
24
|
+
port: Number(u.port || 5432),
|
|
25
|
+
user: decodeURIComponent(u.username || "postgres"),
|
|
26
|
+
password: decodeURIComponent(u.password),
|
|
27
|
+
database,
|
|
28
|
+
...(u.searchParams.get("sslmode") ? { sslmode: u.searchParams.get("sslmode") } : {}),
|
|
29
|
+
...(u.searchParams.get("schema") ? { schema: u.searchParams.get("schema") } : {}),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Build the command line for a PostgreSQL client tool. Inside a container the tool connects over the
|
|
34
|
+
* local socket as the database user (trusted in the official image), so no password is passed at all.
|
|
35
|
+
*/
|
|
36
|
+
export function pgCommand(tool, exec, conn, args) {
|
|
37
|
+
if (exec.kind === "local") {
|
|
38
|
+
const env = {};
|
|
39
|
+
if (conn.password)
|
|
40
|
+
env.PGPASSWORD = conn.password;
|
|
41
|
+
if (conn.sslmode)
|
|
42
|
+
env.PGSSLMODE = conn.sslmode;
|
|
43
|
+
return { command: tool, args: ["-h", conn.host, "-p", String(conn.port), "-U", conn.user, "-d", conn.database, ...args], env };
|
|
44
|
+
}
|
|
45
|
+
const inner = [tool, "-U", conn.user, "-d", conn.database, ...args];
|
|
46
|
+
if (exec.kind === "docker")
|
|
47
|
+
return { command: "docker", args: ["exec", "-i", exec.container, ...inner], env: {} };
|
|
48
|
+
return { command: "docker", args: ["compose", "exec", "-T", exec.service, ...inner], env: {}, cwd: exec.cwd };
|
|
49
|
+
}
|
|
50
|
+
/** FORGE_PG_CONTAINER=<name> runs the tools in that container (docker exec), otherwise on this machine. */
|
|
51
|
+
export function defaultPgExec(env = process.env) {
|
|
52
|
+
return env.FORGE_PG_CONTAINER ? { kind: "docker", container: env.FORGE_PG_CONTAINER } : { kind: "local" };
|
|
53
|
+
}
|
|
54
|
+
/** pg_dump arguments: plain SQL that drops and recreates every object, without owners or grants (portable). */
|
|
55
|
+
export function pgDumpArgs(conn) {
|
|
56
|
+
return ["--format=plain", "--no-owner", "--no-privileges", "--clean", "--if-exists", ...(conn.schema ? [`--schema=${conn.schema}`] : [])];
|
|
57
|
+
}
|
|
58
|
+
/** psql arguments for a restore: stop at the first error, all or nothing. */
|
|
59
|
+
export function restoreArgs() {
|
|
60
|
+
return ["-v", "ON_ERROR_STOP=1", "--single-transaction", "--quiet", "-f", "-"];
|
|
61
|
+
}
|
|
62
|
+
function spawnTool(cmd, stdio) {
|
|
63
|
+
return new Promise((resolvePromise, reject) => {
|
|
64
|
+
const child = spawn(cmd.command, cmd.args, { cwd: cmd.cwd, env: { ...process.env, ...cmd.env }, stdio: ["pipe", stdio.stdout ? "pipe" : "pipe", "pipe"] });
|
|
65
|
+
let out = "";
|
|
66
|
+
let err = "";
|
|
67
|
+
child.stderr.on("data", (d) => (err += d));
|
|
68
|
+
if (!stdio.stdout)
|
|
69
|
+
child.stdout.on("data", (d) => (out += d));
|
|
70
|
+
const streams = [];
|
|
71
|
+
if (stdio.stdout)
|
|
72
|
+
streams.push(pipeline(child.stdout, stdio.stdout));
|
|
73
|
+
if (stdio.stdin)
|
|
74
|
+
streams.push(pipeline(stdio.stdin, child.stdin));
|
|
75
|
+
else
|
|
76
|
+
child.stdin.end();
|
|
77
|
+
child.on("error", (e) => reject(e.code === "ENOENT" ? new Error(`${cmd.command} not found — install the PostgreSQL client tools or use the database container`) : e));
|
|
78
|
+
child.on("close", (code) => {
|
|
79
|
+
Promise.all(streams).then(() => (code === 0 ? resolvePromise(out) : reject(new Error(`${cmd.command} ${cmd.args.slice(0, 4).join(" ")} … exited with code ${code}${err ? `: ${err.trim().split("\n").slice(-5).join("\n")}` : ""}`))), (e) => reject(code === 0 ? e : new Error(`${cmd.command} exited with code ${code}${err ? `: ${err.trim().split("\n").slice(-5).join("\n")}` : ""}`)));
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/** Run a query through psql and return its unaligned output. */
|
|
84
|
+
export function psqlQuery(exec, conn, sql) {
|
|
85
|
+
return spawnTool(pgCommand("psql", exec, conn, ["-X", "-A", "-t", "-v", "ON_ERROR_STOP=1", "-c", sql]), {});
|
|
86
|
+
}
|
|
87
|
+
/** Major version of a local client tool, or null when it is not installed. */
|
|
88
|
+
export async function localToolMajor(tool) {
|
|
89
|
+
try {
|
|
90
|
+
const out = await spawnTool({ command: tool, args: ["--version"], env: {} }, {});
|
|
91
|
+
const m = /(\d+)(?:\.\d+)?/.exec(out);
|
|
92
|
+
return m ? Number(m[1]) : null;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const SAFE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,120}$/;
|
|
99
|
+
function assertName(name) {
|
|
100
|
+
if (!SAFE_NAME.test(name))
|
|
101
|
+
throw new Error(`Invalid backup name "${name}"`);
|
|
102
|
+
}
|
|
103
|
+
export function localBackupStore(dir) {
|
|
104
|
+
const root = resolve(dir);
|
|
105
|
+
return {
|
|
106
|
+
location: root,
|
|
107
|
+
async put(name, file, source) {
|
|
108
|
+
assertName(name);
|
|
109
|
+
await mkdir(join(root, name), { recursive: true });
|
|
110
|
+
await pipeline(createReadStream(source), createWriteStream(join(root, name, file)));
|
|
111
|
+
},
|
|
112
|
+
async fetch(name, file, target) {
|
|
113
|
+
assertName(name);
|
|
114
|
+
const p = join(root, name, file);
|
|
115
|
+
if (!existsSync(p))
|
|
116
|
+
throw new Error(`Backup "${name}" has no ${file} in ${root}`);
|
|
117
|
+
await pipeline(createReadStream(p), createWriteStream(target));
|
|
118
|
+
},
|
|
119
|
+
async list() {
|
|
120
|
+
if (!existsSync(root))
|
|
121
|
+
return [];
|
|
122
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
123
|
+
return entries.filter((e) => e.isDirectory() && SAFE_NAME.test(e.name) && existsSync(join(root, e.name, "manifest.json"))).map((e) => e.name);
|
|
124
|
+
},
|
|
125
|
+
async remove(name) {
|
|
126
|
+
assertName(name);
|
|
127
|
+
await rm(join(root, name), { recursive: true, force: true });
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/** "<slug>-20261001T030000Z" — sortable, safe as a folder or object prefix. */
|
|
132
|
+
export function backupName(slug, at = new Date()) {
|
|
133
|
+
const ts = at.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
|
|
134
|
+
return `${slug}-${ts}`;
|
|
135
|
+
}
|
|
136
|
+
const STAMP = /-(\d{8}T\d{6}Z)$/;
|
|
137
|
+
/** Backups to delete so that the `keep` most recent remain (only names Forge created are considered). */
|
|
138
|
+
export function backupsToPrune(names, keep) {
|
|
139
|
+
if (!Number.isInteger(keep) || keep < 1)
|
|
140
|
+
throw new Error("keep must be a positive integer");
|
|
141
|
+
const ours = names.filter((n) => STAMP.test(n)).sort((a, b) => STAMP.exec(b)[1].localeCompare(STAMP.exec(a)[1]) || b.localeCompare(a));
|
|
142
|
+
return ours.slice(keep);
|
|
143
|
+
}
|
|
144
|
+
async function sha256File(path) {
|
|
145
|
+
const hash = createHash("sha256");
|
|
146
|
+
await pipeline(createReadStream(path), hash);
|
|
147
|
+
return hash.digest("hex");
|
|
148
|
+
}
|
|
149
|
+
export async function createBackup(o) {
|
|
150
|
+
const conn = parseDatabaseUrl(o.databaseUrl);
|
|
151
|
+
const exec = o.exec ?? defaultPgExec();
|
|
152
|
+
const name = o.name ?? backupName(o.app.slug);
|
|
153
|
+
assertName(name);
|
|
154
|
+
const dir = await mkdtemp(join(tmpdir(), "forge-backup-"));
|
|
155
|
+
try {
|
|
156
|
+
const dumpPath = join(dir, "database.sql.gz");
|
|
157
|
+
o.log?.(`Dumping ${conn.database} on ${conn.host}:${conn.port}…`);
|
|
158
|
+
const gzip = createGzip({ level: 6 });
|
|
159
|
+
const out = createWriteStream(dumpPath);
|
|
160
|
+
const piped = pipeline(gzip, out);
|
|
161
|
+
await spawnTool(pgCommand("pg_dump", exec, conn, pgDumpArgs(conn)), { stdout: gzip });
|
|
162
|
+
await piped;
|
|
163
|
+
const files = o.files ? await o.files() : [];
|
|
164
|
+
const manifest = {
|
|
165
|
+
format: 1,
|
|
166
|
+
app: o.app.name,
|
|
167
|
+
name,
|
|
168
|
+
createdAt: new Date().toISOString(),
|
|
169
|
+
database: { host: conn.host, port: conn.port, database: conn.database, ...(conn.schema ? { schema: conn.schema } : {}) },
|
|
170
|
+
dump: { file: "database.sql.gz", bytes: (await stat(dumpPath)).size, sha256: await sha256File(dumpPath), tool: exec.kind === "local" ? "pg_dump" : `pg_dump (${exec.kind === "docker" ? exec.container : exec.service} container)` },
|
|
171
|
+
files: { count: files.length, bytes: files.reduce((s, f) => s + (f.size ?? 0), 0), storage: o.fileStorage ?? null, items: files },
|
|
172
|
+
};
|
|
173
|
+
const manifestPath = join(dir, "manifest.json");
|
|
174
|
+
await writeFile(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
175
|
+
// The dump first, the manifest last: a backup without a manifest is incomplete and never listed.
|
|
176
|
+
await o.store.put(name, "database.sql.gz", dumpPath);
|
|
177
|
+
await o.store.put(name, "manifest.json", manifestPath);
|
|
178
|
+
o.log?.(`Saved ${name} (${formatBytes(manifest.dump.bytes)}) to ${o.store.location}`);
|
|
179
|
+
const pruned = o.keep ? backupsToPrune(await o.store.list(), o.keep) : [];
|
|
180
|
+
for (const old of pruned) {
|
|
181
|
+
await o.store.remove(old);
|
|
182
|
+
o.log?.(`Removed old backup ${old}`);
|
|
183
|
+
}
|
|
184
|
+
return { manifest, pruned };
|
|
185
|
+
}
|
|
186
|
+
finally {
|
|
187
|
+
await rm(dir, { recursive: true, force: true });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
export async function readManifest(store, name) {
|
|
191
|
+
assertName(name);
|
|
192
|
+
const dir = await mkdtemp(join(tmpdir(), "forge-backup-"));
|
|
193
|
+
try {
|
|
194
|
+
await store.fetch(name, "manifest.json", join(dir, "manifest.json"));
|
|
195
|
+
return JSON.parse(await readFile(join(dir, "manifest.json"), "utf8"));
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
await rm(dir, { recursive: true, force: true });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/** Manifests of every stored backup, newest first. */
|
|
202
|
+
export async function listBackups(store) {
|
|
203
|
+
const out = [];
|
|
204
|
+
for (const name of await store.list()) {
|
|
205
|
+
try {
|
|
206
|
+
out.push(await readManifest(store, name));
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
// unreadable manifest: skip
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return out.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
213
|
+
}
|
|
214
|
+
/** Uploaded files listed in the storage plugin's `files` table (empty without the plugin), read through psql. */
|
|
215
|
+
export async function filesFromDatabase(exec, conn) {
|
|
216
|
+
const exists = (await psqlQuery(exec, conn, "select to_regclass('files') is not null")).trim();
|
|
217
|
+
if (exists !== "t")
|
|
218
|
+
return [];
|
|
219
|
+
const out = await psqlQuery(exec, conn, "select coalesce(json_agg(json_build_object('key', key, 'name', name, 'size', size, 'mimeType', mime_type, 'entity', entity, 'field', field, 'recordId', record_id, 'status', status) order by created_at), '[]') from files where status <> 'deleted'");
|
|
220
|
+
return JSON.parse(out.trim() || "[]");
|
|
221
|
+
}
|
|
222
|
+
/** The same list through Prisma (worker side). */
|
|
223
|
+
export async function filesFromPrisma(prisma) {
|
|
224
|
+
if (!prisma.file)
|
|
225
|
+
return [];
|
|
226
|
+
const rows = await prisma.file.findMany({
|
|
227
|
+
where: { status: { not: "deleted" } },
|
|
228
|
+
orderBy: { createdAt: "asc" },
|
|
229
|
+
select: { key: true, name: true, size: true, mimeType: true, entity: true, field: true, recordId: true, status: true },
|
|
230
|
+
});
|
|
231
|
+
return rows;
|
|
232
|
+
}
|
|
233
|
+
/** Tables of the target database with their (estimated) row counts — what a restore overwrites. */
|
|
234
|
+
export async function databaseTables(exec, conn) {
|
|
235
|
+
const out = await psqlQuery(exec, conn, "select schemaname || '.' || relname, n_live_tup from pg_stat_user_tables order by 1");
|
|
236
|
+
return out
|
|
237
|
+
.split("\n")
|
|
238
|
+
.filter(Boolean)
|
|
239
|
+
.map((l) => {
|
|
240
|
+
const [table, rows] = l.split("|");
|
|
241
|
+
return { table: table, rows: Number(rows) };
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
/** Replace the database's contents with a backup (one transaction; the dump drops and recreates its objects). */
|
|
245
|
+
export async function restoreBackup(o) {
|
|
246
|
+
const conn = parseDatabaseUrl(o.databaseUrl);
|
|
247
|
+
const manifest = await readManifest(o.store, o.name);
|
|
248
|
+
const dir = await mkdtemp(join(tmpdir(), "forge-restore-"));
|
|
249
|
+
try {
|
|
250
|
+
const dumpPath = join(dir, manifest.dump.file);
|
|
251
|
+
await o.store.fetch(o.name, manifest.dump.file, dumpPath);
|
|
252
|
+
if ((await sha256File(dumpPath)) !== manifest.dump.sha256)
|
|
253
|
+
throw new Error(`Backup ${o.name} is corrupted (checksum mismatch) — nothing was changed`);
|
|
254
|
+
o.log?.(`Restoring ${o.name} into ${conn.database} on ${conn.host}:${conn.port}…`);
|
|
255
|
+
await spawnTool(pgCommand("psql", o.exec ?? defaultPgExec(), conn, restoreArgs()), { stdin: createReadStream(dumpPath).pipe(createGunzip()) });
|
|
256
|
+
return manifest;
|
|
257
|
+
}
|
|
258
|
+
finally {
|
|
259
|
+
await rm(dir, { recursive: true, force: true });
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
export function formatBytes(n) {
|
|
263
|
+
if (n < 1024)
|
|
264
|
+
return `${n} B`;
|
|
265
|
+
const units = ["KB", "MB", "GB", "TB"];
|
|
266
|
+
let v = n / 1024;
|
|
267
|
+
let i = 0;
|
|
268
|
+
while (v >= 1024 && i < units.length - 1) {
|
|
269
|
+
v /= 1024;
|
|
270
|
+
i++;
|
|
271
|
+
}
|
|
272
|
+
return `${v.toFixed(v < 10 ? 1 : 0)} ${units[i]}`;
|
|
273
|
+
}
|
|
274
|
+
/** A database URL that points at a non-local host or a production environment. */
|
|
275
|
+
export function looksLikeProduction(conn, env = process.env) {
|
|
276
|
+
if (env.NODE_ENV === "production" || env.FORGE_ENV === "production")
|
|
277
|
+
return true;
|
|
278
|
+
return !["localhost", "127.0.0.1", "::1", "[::1]", "postgres", "db", "host.docker.internal"].includes(conn.host);
|
|
279
|
+
}
|
|
280
|
+
//# sourceMappingURL=backup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backup.js","sourceRoot":"","sources":["../src/backup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
import { type DynamicModule, type INestApplication, type LogLevel, type Type } from "@nestjs/common";
|
|
3
|
+
import type { JobDefinition } from "./jobs.js";
|
|
4
|
+
import type { AppIR } from "@tekki-forge/compiler";
|
|
5
|
+
import type { ForgeApiPlugin, ForgeWorkerPlugin, PluginContext } from "./plugin.js";
|
|
6
|
+
import { ForgeService, type ForgeServiceOptions } from "./service.js";
|
|
7
|
+
export interface ForgeApiOptions extends ForgeServiceOptions {
|
|
8
|
+
/** Plugin runtimes, generated into apps/api/src/.generated/plugins.ts. */
|
|
9
|
+
plugins?: ForgeApiPlugin[];
|
|
10
|
+
/** Job definitions from apps/api/src/jobs (generated registry). */
|
|
11
|
+
jobs?: JobDefinition[];
|
|
12
|
+
/** Generated OpenAPI document served at `<prefix>/openapi.json` and `<prefix>/docs`. */
|
|
13
|
+
openapi?: Record<string, unknown>;
|
|
14
|
+
/** Allowed browser origins for direct (non-proxied) API calls. */
|
|
15
|
+
cors?: string[];
|
|
16
|
+
/** Extra Nest modules contributed by the application or plugins. */
|
|
17
|
+
imports?: DynamicModule["imports"];
|
|
18
|
+
logger?: LogLevel[] | false;
|
|
19
|
+
}
|
|
20
|
+
export declare function forgeModule(service: ForgeService, imports?: DynamicModule["imports"], pluginControllers?: Type[]): DynamicModule;
|
|
21
|
+
export interface ForgeApi {
|
|
22
|
+
app: INestApplication;
|
|
23
|
+
service: ForgeService;
|
|
24
|
+
plugins: {
|
|
25
|
+
runtime: ForgeApiPlugin;
|
|
26
|
+
ctx: PluginContext;
|
|
27
|
+
}[];
|
|
28
|
+
}
|
|
29
|
+
/** Instantiate the service and run every plugin's setup (shared by the API server and the worker). */
|
|
30
|
+
export declare function setupForge(options: ForgeApiOptions): Promise<{
|
|
31
|
+
service: ForgeService;
|
|
32
|
+
plugins: {
|
|
33
|
+
runtime: ForgeApiPlugin;
|
|
34
|
+
ctx: PluginContext;
|
|
35
|
+
}[];
|
|
36
|
+
}>;
|
|
37
|
+
/**
|
|
38
|
+
* `backups: { schedule }` in forge.config.ts: a cron job run by the jobs plugin's worker. It needs pg_dump
|
|
39
|
+
* (postgresql-client, installed in the generated API image) and stores to S3 when the storage plugin uses
|
|
40
|
+
* S3, else to FORGE_BACKUP_DIR (default ./backups — mount a volume there).
|
|
41
|
+
*/
|
|
42
|
+
export declare function backupJob(app: AppIR, service: ForgeService): JobDefinition;
|
|
43
|
+
export declare function createForgeApi(options: ForgeApiOptions): Promise<INestApplication>;
|
|
44
|
+
export declare function createForge(options: ForgeApiOptions): Promise<ForgeApi>;
|
|
45
|
+
export declare function startForgeApi(options: ForgeApiOptions & {
|
|
46
|
+
port?: number;
|
|
47
|
+
host?: string;
|
|
48
|
+
}): Promise<INestApplication>;
|
|
49
|
+
/**
|
|
50
|
+
* Background worker process: same setup as the API (plugins, file store, mail) but no HTTP server.
|
|
51
|
+
* Started by apps/api/src/worker.ts.
|
|
52
|
+
*/
|
|
53
|
+
export declare function startForgeWorker(options: ForgeApiOptions & {
|
|
54
|
+
workerPlugins: ForgeWorkerPlugin[];
|
|
55
|
+
}): Promise<{
|
|
56
|
+
stop(): Promise<void>;
|
|
57
|
+
}>;
|