@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
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import "reflect-metadata";
|
|
8
|
+
import { Module } from "@nestjs/common";
|
|
9
|
+
import { NestFactory } from "@nestjs/core";
|
|
10
|
+
import { SwaggerModule } from "@nestjs/swagger";
|
|
11
|
+
import { toNodeHandler } from "better-auth/node";
|
|
12
|
+
import express from "express";
|
|
13
|
+
import { FORGE_SERVICE, ResourceController, SystemController } from "./controllers.js";
|
|
14
|
+
import { ForgeExceptionFilter } from "./errors.js";
|
|
15
|
+
import { createBackup, filesFromPrisma, localBackupStore } from "./backup.js";
|
|
16
|
+
import { ForgeService } from "./service.js";
|
|
17
|
+
let ForgeRootModule = class ForgeRootModule {
|
|
18
|
+
};
|
|
19
|
+
ForgeRootModule = __decorate([
|
|
20
|
+
Module({})
|
|
21
|
+
], ForgeRootModule);
|
|
22
|
+
export function forgeModule(service, imports = [], pluginControllers = []) {
|
|
23
|
+
return {
|
|
24
|
+
module: ForgeRootModule,
|
|
25
|
+
imports,
|
|
26
|
+
// Order matters: fixed system and plugin routes must win over the generic `:resource` routes.
|
|
27
|
+
controllers: [SystemController, ...pluginControllers, ResourceController],
|
|
28
|
+
providers: [{ provide: FORGE_SERVICE, useValue: service }],
|
|
29
|
+
exports: [FORGE_SERVICE],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** Instantiate the service and run every plugin's setup (shared by the API server and the worker). */
|
|
33
|
+
export async function setupForge(options) {
|
|
34
|
+
const service = new ForgeService(options);
|
|
35
|
+
const shared = service.shared;
|
|
36
|
+
shared.set("jobDefinitions", [...(options.jobs ?? []), ...(options.app.backups?.schedule ? [backupJob(options.app, service)] : [])]);
|
|
37
|
+
const plugins = [];
|
|
38
|
+
for (const runtime of options.plugins ?? []) {
|
|
39
|
+
const plugin = options.app.plugins.find((p) => p.name === runtime.name);
|
|
40
|
+
if (!plugin)
|
|
41
|
+
throw new Error(`[forge] plugin runtime "${runtime.name}" is installed but not listed in forge.config.ts plugins — run forge generate`);
|
|
42
|
+
const ctx = {
|
|
43
|
+
app: options.app,
|
|
44
|
+
plugin,
|
|
45
|
+
prisma: options.prisma,
|
|
46
|
+
service,
|
|
47
|
+
events: service.events,
|
|
48
|
+
env: process.env,
|
|
49
|
+
provideFileStore: (store) => service.useFileStore(store),
|
|
50
|
+
provide: (name, value) => void shared.set(name, value),
|
|
51
|
+
use: (name) => shared.get(name),
|
|
52
|
+
};
|
|
53
|
+
await runtime.setup?.(ctx);
|
|
54
|
+
plugins.push({ runtime, ctx });
|
|
55
|
+
}
|
|
56
|
+
for (const p of options.app.plugins) {
|
|
57
|
+
if (p.runtime.api && !plugins.some((x) => x.runtime.name === p.name)) {
|
|
58
|
+
throw new Error(`[forge] forge.config.ts lists the "${p.name}" plugin but its runtime is not loaded — run forge generate`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
service.assertReady();
|
|
62
|
+
return { service, plugins };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* `backups: { schedule }` in forge.config.ts: a cron job run by the jobs plugin's worker. It needs pg_dump
|
|
66
|
+
* (postgresql-client, installed in the generated API image) and stores to S3 when the storage plugin uses
|
|
67
|
+
* S3, else to FORGE_BACKUP_DIR (default ./backups — mount a volume there).
|
|
68
|
+
*/
|
|
69
|
+
export function backupJob(app, service) {
|
|
70
|
+
return {
|
|
71
|
+
name: "forge.backup",
|
|
72
|
+
schedule: app.backups.schedule,
|
|
73
|
+
attempts: 2,
|
|
74
|
+
backoff: 5 * 60_000,
|
|
75
|
+
timeout: 2 * 60 * 60_000,
|
|
76
|
+
async handler({ log }) {
|
|
77
|
+
const databaseUrl = process.env.DATABASE_URL;
|
|
78
|
+
if (!databaseUrl)
|
|
79
|
+
throw new Error("[backup] DATABASE_URL is not set");
|
|
80
|
+
const store = service.shared.get("backupStore");
|
|
81
|
+
const { manifest, pruned } = await createBackup({
|
|
82
|
+
databaseUrl,
|
|
83
|
+
store: store ?? localBackupStore(process.env.FORGE_BACKUP_DIR || "backups"),
|
|
84
|
+
app: { name: app.name, slug: app.slug },
|
|
85
|
+
files: () => filesFromPrisma(service.prisma),
|
|
86
|
+
fileStorage: service.shared.get("backupFileStorage") ?? null,
|
|
87
|
+
keep: app.backups.keep,
|
|
88
|
+
log,
|
|
89
|
+
});
|
|
90
|
+
return { name: manifest.name, bytes: manifest.dump.bytes, files: manifest.files.count, pruned };
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export async function createForgeApi(options) {
|
|
95
|
+
return (await createForge(options)).app;
|
|
96
|
+
}
|
|
97
|
+
export async function createForge(options) {
|
|
98
|
+
const { service, plugins } = await setupForge(options);
|
|
99
|
+
const prefix = options.app.api.prefix;
|
|
100
|
+
const pluginImports = plugins.flatMap((p) => p.runtime.imports ?? []);
|
|
101
|
+
const pluginControllers = plugins.flatMap((p) => p.runtime.controllers ?? []);
|
|
102
|
+
const app = await NestFactory.create(forgeModule(service, [...(options.imports ?? []), ...pluginImports], pluginControllers), {
|
|
103
|
+
bodyParser: false,
|
|
104
|
+
logger: options.logger ?? ["log", "warn", "error"],
|
|
105
|
+
});
|
|
106
|
+
const server = app.getHttpAdapter().getInstance();
|
|
107
|
+
server.set("trust proxy", true);
|
|
108
|
+
server.disable("x-powered-by");
|
|
109
|
+
// Better Auth reads the raw body, so it is mounted before the JSON parser.
|
|
110
|
+
server.all(`${prefix}/auth/*splat`, toNodeHandler(options.auth));
|
|
111
|
+
// Signed webhooks (e.g. Stripe) must be verified against the exact bytes received.
|
|
112
|
+
for (const path of plugins.flatMap((p) => p.runtime.rawBodyPaths ?? []))
|
|
113
|
+
server.use(`${prefix}${path}`, express.raw({ type: "*/*", limit: "1mb" }));
|
|
114
|
+
server.use(express.json({ limit: "1mb" }));
|
|
115
|
+
if (options.cors?.length)
|
|
116
|
+
app.enableCors({ origin: options.cors, credentials: true });
|
|
117
|
+
app.setGlobalPrefix(prefix.replace(/^\//, ""));
|
|
118
|
+
app.useGlobalFilters(new ForgeExceptionFilter());
|
|
119
|
+
if (options.openapi && options.app.api.docs) {
|
|
120
|
+
const doc = options.openapi;
|
|
121
|
+
server.get(`${prefix}/openapi.json`, (_req, res) => {
|
|
122
|
+
res.json(doc);
|
|
123
|
+
});
|
|
124
|
+
SwaggerModule.setup(`${prefix.replace(/^\//, "")}/docs`, app, doc, {
|
|
125
|
+
useGlobalPrefix: false,
|
|
126
|
+
customSiteTitle: `${options.app.name} API`,
|
|
127
|
+
swaggerOptions: { withCredentials: true, persistAuthorization: true },
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return { app, service, plugins };
|
|
131
|
+
}
|
|
132
|
+
export async function startForgeApi(options) {
|
|
133
|
+
const { app, plugins } = await createForge(options);
|
|
134
|
+
app.enableShutdownHooks();
|
|
135
|
+
const stop = async () => {
|
|
136
|
+
for (const p of plugins)
|
|
137
|
+
await p.runtime.onStop?.(p.ctx);
|
|
138
|
+
};
|
|
139
|
+
process.once("SIGTERM", () => void stop());
|
|
140
|
+
process.once("SIGINT", () => void stop());
|
|
141
|
+
const port = options.port ?? Number(process.env.API_PORT ?? process.env.PORT ?? 4000);
|
|
142
|
+
await app.listen(port, options.host ?? "0.0.0.0");
|
|
143
|
+
console.log(`[forge] ${options.app.name} API listening on http://localhost:${port}${options.app.api.prefix}`);
|
|
144
|
+
if (options.openapi && options.app.api.docs)
|
|
145
|
+
console.log(`[forge] API docs: http://localhost:${port}${options.app.api.prefix}/docs`);
|
|
146
|
+
for (const p of plugins)
|
|
147
|
+
await p.runtime.onStart?.(p.ctx);
|
|
148
|
+
return app;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Background worker process: same setup as the API (plugins, file store, mail) but no HTTP server.
|
|
152
|
+
* Started by apps/api/src/worker.ts.
|
|
153
|
+
*/
|
|
154
|
+
export async function startForgeWorker(options) {
|
|
155
|
+
const { service, plugins } = await setupForge(options);
|
|
156
|
+
const contexts = [];
|
|
157
|
+
for (const runtime of options.workerPlugins) {
|
|
158
|
+
const api = plugins.find((p) => p.runtime.name === runtime.name);
|
|
159
|
+
const plugin = options.app.plugins.find((p) => p.name === runtime.name);
|
|
160
|
+
if (!plugin)
|
|
161
|
+
throw new Error(`[forge] worker plugin "${runtime.name}" is not listed in forge.config.ts`);
|
|
162
|
+
const ctx = api?.ctx ?? {
|
|
163
|
+
app: options.app,
|
|
164
|
+
plugin,
|
|
165
|
+
prisma: options.prisma,
|
|
166
|
+
service,
|
|
167
|
+
events: service.events,
|
|
168
|
+
env: process.env,
|
|
169
|
+
provideFileStore: (store) => service.useFileStore(store),
|
|
170
|
+
provide: (name, value) => void service.shared.set(name, value),
|
|
171
|
+
use: (name) => service.shared.get(name),
|
|
172
|
+
};
|
|
173
|
+
await runtime.setup?.(ctx);
|
|
174
|
+
contexts.push({ runtime, ctx });
|
|
175
|
+
}
|
|
176
|
+
if (!options.workerPlugins.some((w) => w.name === "jobs"))
|
|
177
|
+
console.warn("[forge] the worker has nothing to run — add the jobs plugin (forge add jobs)");
|
|
178
|
+
for (const c of contexts)
|
|
179
|
+
await c.runtime.onStart?.(c.ctx);
|
|
180
|
+
console.log(`[forge] ${options.app.name} worker started`);
|
|
181
|
+
const stop = async () => {
|
|
182
|
+
for (const c of contexts)
|
|
183
|
+
await c.runtime.onStop?.(c.ctx);
|
|
184
|
+
for (const p of plugins)
|
|
185
|
+
await p.runtime.onStop?.(p.ctx);
|
|
186
|
+
};
|
|
187
|
+
process.once("SIGTERM", () => void stop().then(() => process.exit(0)));
|
|
188
|
+
process.once("SIGINT", () => void stop().then(() => process.exit(0)));
|
|
189
|
+
return { stop };
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=bootstrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { EntityIR } from "@tekki-forge/compiler";
|
|
2
|
+
import type { ForgeRequestContext } from "./extension.js";
|
|
3
|
+
import type { ForgeService } from "./service.js";
|
|
4
|
+
type Row = Record<string, any>;
|
|
5
|
+
export declare const COMMENT_MAX_LENGTH = 5000;
|
|
6
|
+
/** User ids mentioned in a comment body, in order of appearance, without duplicates. */
|
|
7
|
+
export declare function parseMentions(body: string): string[];
|
|
8
|
+
/** The body as plain text: mention tokens become `@Name`. */
|
|
9
|
+
export declare function commentText(body: string): string;
|
|
10
|
+
export interface TimelineChange {
|
|
11
|
+
field: string;
|
|
12
|
+
label: string;
|
|
13
|
+
/** false for values that are not shown (files). */
|
|
14
|
+
values: boolean;
|
|
15
|
+
from?: unknown;
|
|
16
|
+
to?: unknown;
|
|
17
|
+
}
|
|
18
|
+
export interface TimelineEvent {
|
|
19
|
+
id: string;
|
|
20
|
+
kind: "created" | "updated" | "deleted" | "restored" | "transition" | "action" | "comment";
|
|
21
|
+
at: string;
|
|
22
|
+
actor: {
|
|
23
|
+
id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
image: string | null;
|
|
26
|
+
} | null;
|
|
27
|
+
changes?: TimelineChange[];
|
|
28
|
+
transition?: {
|
|
29
|
+
name: string;
|
|
30
|
+
label: string;
|
|
31
|
+
from: string | null;
|
|
32
|
+
to: string | null;
|
|
33
|
+
};
|
|
34
|
+
action?: {
|
|
35
|
+
name: string;
|
|
36
|
+
label: string;
|
|
37
|
+
};
|
|
38
|
+
comment?: Row;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Comments on records (entities with `features: { comments: true }`), @mentions and the record timeline
|
|
42
|
+
* (audit history + comments). Everything is scoped to the records the caller can read, within their organization.
|
|
43
|
+
*/
|
|
44
|
+
export declare class CommentService {
|
|
45
|
+
private readonly service;
|
|
46
|
+
private readonly logger;
|
|
47
|
+
constructor(service: ForgeService);
|
|
48
|
+
private get prisma();
|
|
49
|
+
private principal;
|
|
50
|
+
/** Writing comments and mentioning people needs a person: API keys may only read comments and the timeline. */
|
|
51
|
+
private person;
|
|
52
|
+
private orgWhere;
|
|
53
|
+
private requireComments;
|
|
54
|
+
/** The record, when the caller can read it (404 otherwise). */
|
|
55
|
+
private record;
|
|
56
|
+
private canRead;
|
|
57
|
+
/**
|
|
58
|
+
* Users who may be mentioned: members of the caller's organization (all users without multi-tenancy), not banned,
|
|
59
|
+
* who can read the record. `ids` narrows to those users; `search` matches name or email.
|
|
60
|
+
*/
|
|
61
|
+
private readers;
|
|
62
|
+
/** Mentioned ids that are valid: same organization, can read the record. Others are dropped silently. */
|
|
63
|
+
private validMentions;
|
|
64
|
+
private body;
|
|
65
|
+
out(row: Row, ctx: ForgeRequestContext): {
|
|
66
|
+
id: any;
|
|
67
|
+
body: any;
|
|
68
|
+
mentions: any;
|
|
69
|
+
author: {
|
|
70
|
+
id: any;
|
|
71
|
+
name: any;
|
|
72
|
+
image: any;
|
|
73
|
+
} | null;
|
|
74
|
+
createdAt: any;
|
|
75
|
+
editedAt: any;
|
|
76
|
+
canEdit: boolean;
|
|
77
|
+
canDelete: boolean;
|
|
78
|
+
};
|
|
79
|
+
private find;
|
|
80
|
+
private notifyMentions;
|
|
81
|
+
list(entity: EntityIR, id: string, query: Row, ctx: ForgeRequestContext): Promise<{
|
|
82
|
+
data: any;
|
|
83
|
+
meta: {
|
|
84
|
+
page: number;
|
|
85
|
+
pageSize: number;
|
|
86
|
+
total: any;
|
|
87
|
+
pages: number;
|
|
88
|
+
};
|
|
89
|
+
}>;
|
|
90
|
+
create(entity: EntityIR, id: string, input: unknown, ctx: ForgeRequestContext): Promise<{
|
|
91
|
+
id: any;
|
|
92
|
+
body: any;
|
|
93
|
+
mentions: any;
|
|
94
|
+
author: {
|
|
95
|
+
id: any;
|
|
96
|
+
name: any;
|
|
97
|
+
image: any;
|
|
98
|
+
} | null;
|
|
99
|
+
createdAt: any;
|
|
100
|
+
editedAt: any;
|
|
101
|
+
canEdit: boolean;
|
|
102
|
+
canDelete: boolean;
|
|
103
|
+
}>;
|
|
104
|
+
update(entity: EntityIR, id: string, commentId: string, input: unknown, ctx: ForgeRequestContext): Promise<{
|
|
105
|
+
id: any;
|
|
106
|
+
body: any;
|
|
107
|
+
mentions: any;
|
|
108
|
+
author: {
|
|
109
|
+
id: any;
|
|
110
|
+
name: any;
|
|
111
|
+
image: any;
|
|
112
|
+
} | null;
|
|
113
|
+
createdAt: any;
|
|
114
|
+
editedAt: any;
|
|
115
|
+
canEdit: boolean;
|
|
116
|
+
canDelete: boolean;
|
|
117
|
+
}>;
|
|
118
|
+
remove(entity: EntityIR, id: string, commentId: string, ctx: ForgeRequestContext): Promise<void>;
|
|
119
|
+
/** People the caller can @mention on this record. */
|
|
120
|
+
mentionable(entity: EntityIR, id: string, query: Row, ctx: ForgeRequestContext): Promise<{
|
|
121
|
+
data: {
|
|
122
|
+
id: any;
|
|
123
|
+
name: any;
|
|
124
|
+
image: any;
|
|
125
|
+
}[];
|
|
126
|
+
}>;
|
|
127
|
+
/**
|
|
128
|
+
* Audit history and comments of one record, newest first, `limit` per page; pass `before` (the previous
|
|
129
|
+
* page's `nextCursor`) for older events. Changes to fields the caller cannot read are left out.
|
|
130
|
+
*/
|
|
131
|
+
timeline(entity: EntityIR, id: string, query: Row, ctx: ForgeRequestContext): Promise<{
|
|
132
|
+
data: TimelineEvent[];
|
|
133
|
+
nextCursor: string | null;
|
|
134
|
+
}>;
|
|
135
|
+
private commentEvent;
|
|
136
|
+
private auditEvent;
|
|
137
|
+
private changes;
|
|
138
|
+
}
|
|
139
|
+
export {};
|