@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/comments.js
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import { allowsRecord, authorize, effectiveRoles } from "@tekki-forge/runtime-permissions";
|
|
2
|
+
import { BadRequestException, ForbiddenException, Logger, NotFoundException, UnauthorizedException } from "@nestjs/common";
|
|
3
|
+
import { unreadableFields } from "./access.js";
|
|
4
|
+
import { label } from "./serialize.js";
|
|
5
|
+
import { TeamService } from "./teams.js";
|
|
6
|
+
export const COMMENT_MAX_LENGTH = 5000;
|
|
7
|
+
const MAX_MENTIONS = 20;
|
|
8
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
9
|
+
/** A mention inside a comment body: `@[Ada Lovelace](<user id>)`. */
|
|
10
|
+
const MENTION = /@\[([^\]\n]{1,100})\]\(([A-Za-z0-9_-]{1,64})\)/g;
|
|
11
|
+
const AUTHOR = { select: { id: true, name: true, image: true } };
|
|
12
|
+
/** User ids mentioned in a comment body, in order of appearance, without duplicates. */
|
|
13
|
+
export function parseMentions(body) {
|
|
14
|
+
return [...new Set([...body.matchAll(MENTION)].map((m) => m[2]))];
|
|
15
|
+
}
|
|
16
|
+
/** The body as plain text: mention tokens become `@Name`. */
|
|
17
|
+
export function commentText(body) {
|
|
18
|
+
return body.replace(MENTION, (_m, name) => `@${name}`);
|
|
19
|
+
}
|
|
20
|
+
const excerpt = (text, max = 280) => (text.length > max ? `${text.slice(0, max - 1).trimEnd()}…` : text);
|
|
21
|
+
const invalid = (issues) => new BadRequestException({ statusCode: 400, error: "Bad Request", message: "Validation failed", issues });
|
|
22
|
+
/**
|
|
23
|
+
* Comments on records (entities with `features: { comments: true }`), @mentions and the record timeline
|
|
24
|
+
* (audit history + comments). Everything is scoped to the records the caller can read, within their organization.
|
|
25
|
+
*/
|
|
26
|
+
export class CommentService {
|
|
27
|
+
service;
|
|
28
|
+
logger = new Logger("ForgeComments");
|
|
29
|
+
constructor(service) {
|
|
30
|
+
this.service = service;
|
|
31
|
+
}
|
|
32
|
+
get prisma() {
|
|
33
|
+
return this.service.prisma;
|
|
34
|
+
}
|
|
35
|
+
principal(ctx) {
|
|
36
|
+
if (!ctx.principal)
|
|
37
|
+
throw new UnauthorizedException("Sign in to continue");
|
|
38
|
+
return ctx.principal;
|
|
39
|
+
}
|
|
40
|
+
/** Writing comments and mentioning people needs a person: API keys may only read comments and the timeline. */
|
|
41
|
+
person(ctx) {
|
|
42
|
+
const p = this.principal(ctx);
|
|
43
|
+
if (ctx.apiKey)
|
|
44
|
+
throw new ForbiddenException("API keys can read comments but not write them");
|
|
45
|
+
return p;
|
|
46
|
+
}
|
|
47
|
+
orgWhere(ctx) {
|
|
48
|
+
return this.service.app.tenancy.enabled ? { organizationId: ctx.principal?.organizationId ?? "__none__" } : {};
|
|
49
|
+
}
|
|
50
|
+
requireComments(entity) {
|
|
51
|
+
if (!entity.features.comments)
|
|
52
|
+
throw new NotFoundException(`Comments are not enabled for ${entity.pluralLabel.toLowerCase()}`);
|
|
53
|
+
}
|
|
54
|
+
/** The record, when the caller can read it (404 otherwise). */
|
|
55
|
+
record(entity, id, ctx, trashed = "without") {
|
|
56
|
+
this.principal(ctx);
|
|
57
|
+
if (!UUID.test(id))
|
|
58
|
+
throw new NotFoundException(`${entity.label} not found`);
|
|
59
|
+
return this.service.loadFor(entity, id, "read", ctx, { trashed: entity.features.softDelete ? trashed : "without" });
|
|
60
|
+
}
|
|
61
|
+
async canRead(entity, record, principal) {
|
|
62
|
+
const check = this.service.policyCheck(entity, { principal, user: null, session: null });
|
|
63
|
+
return allowsRecord(entity, authorize(entity, "read", principal), principal, record, check);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Users who may be mentioned: members of the caller's organization (all users without multi-tenancy), not banned,
|
|
67
|
+
* who can read the record. `ids` narrows to those users; `search` matches name or email.
|
|
68
|
+
*/
|
|
69
|
+
async readers(entity, record, ctx, opts) {
|
|
70
|
+
const p = this.principal(ctx);
|
|
71
|
+
const tenancy = this.service.app.tenancy.enabled;
|
|
72
|
+
const userWhere = [{ OR: [{ banned: null }, { banned: false }] }];
|
|
73
|
+
if (opts.ids)
|
|
74
|
+
userWhere.push({ id: { in: opts.ids } });
|
|
75
|
+
if (opts.search)
|
|
76
|
+
userWhere.push({ OR: [{ name: { contains: opts.search, mode: "insensitive" } }, { email: { contains: opts.search, mode: "insensitive" } }] });
|
|
77
|
+
const select = { id: true, name: true, image: true, role: true };
|
|
78
|
+
const candidates = tenancy
|
|
79
|
+
? (await this.prisma.member.findMany({
|
|
80
|
+
where: { organizationId: p.organizationId ?? "__none__", user: { AND: userWhere } },
|
|
81
|
+
include: { user: { select } },
|
|
82
|
+
orderBy: { user: { name: "asc" } },
|
|
83
|
+
take: opts.ids ? opts.ids.length : 100,
|
|
84
|
+
})).map((m) => ({ user: m.user, memberRole: m.role }))
|
|
85
|
+
: (await this.prisma.user.findMany({ where: { AND: userWhere }, select, orderBy: { name: "asc" }, take: opts.ids ? opts.ids.length : 100 })).map((u) => ({ user: u, memberRole: null }));
|
|
86
|
+
// "team" rules: each candidate's teams in this organization.
|
|
87
|
+
const teams = new Map();
|
|
88
|
+
if (this.service.app.teams?.enabled && tenancy && candidates.length) {
|
|
89
|
+
const rows = await this.prisma.teamMember.findMany({ where: { userId: { in: candidates.map((c) => c.user.id) }, team: { organizationId: p.organizationId ?? "__none__" } }, select: { userId: true, teamId: true } });
|
|
90
|
+
for (const r of rows)
|
|
91
|
+
teams.set(r.userId, [...(teams.get(r.userId) ?? []), r.teamId]);
|
|
92
|
+
}
|
|
93
|
+
const out = [];
|
|
94
|
+
for (const { user, memberRole } of candidates) {
|
|
95
|
+
const principal = { id: user.id, roles: effectiveRoles(user.role, memberRole, tenancy), organizationId: tenancy ? (p.organizationId ?? null) : null, teamIds: teams.get(user.id) ?? [] };
|
|
96
|
+
if (await this.canRead(entity, record, principal))
|
|
97
|
+
out.push(user);
|
|
98
|
+
if (out.length >= opts.limit)
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
/** Mentioned ids that are valid: same organization, can read the record. Others are dropped silently. */
|
|
104
|
+
async validMentions(entity, record, ids, ctx) {
|
|
105
|
+
if (!ids.length)
|
|
106
|
+
return [];
|
|
107
|
+
const ok = new Set((await this.readers(entity, record, ctx, { ids, limit: ids.length })).map((u) => u.id));
|
|
108
|
+
return ids.filter((id) => ok.has(id));
|
|
109
|
+
}
|
|
110
|
+
body(input) {
|
|
111
|
+
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
112
|
+
throw invalid([{ path: "body", message: "Required" }]);
|
|
113
|
+
const issues = [];
|
|
114
|
+
for (const key of Object.keys(input))
|
|
115
|
+
if (key !== "body")
|
|
116
|
+
issues.push({ path: key, message: `Unrecognized key "${key}"` });
|
|
117
|
+
const raw = input.body;
|
|
118
|
+
const body = typeof raw === "string" ? raw.replace(/\r\n/g, "\n").trim() : "";
|
|
119
|
+
if (raw !== undefined && raw !== null && typeof raw !== "string")
|
|
120
|
+
issues.push({ path: "body", message: "Expected text" });
|
|
121
|
+
else if (!body)
|
|
122
|
+
issues.push({ path: "body", message: "Required" });
|
|
123
|
+
else if (body.length > COMMENT_MAX_LENGTH)
|
|
124
|
+
issues.push({ path: "body", message: `Must be at most ${COMMENT_MAX_LENGTH} characters` });
|
|
125
|
+
else if (parseMentions(body).length > MAX_MENTIONS)
|
|
126
|
+
issues.push({ path: "body", message: `Mention at most ${MAX_MENTIONS} people` });
|
|
127
|
+
if (issues.length)
|
|
128
|
+
throw invalid(issues);
|
|
129
|
+
return body;
|
|
130
|
+
}
|
|
131
|
+
out(row, ctx) {
|
|
132
|
+
const mine = !!row.authorId && row.authorId === ctx.principal?.id;
|
|
133
|
+
return {
|
|
134
|
+
id: row.id,
|
|
135
|
+
body: row.body,
|
|
136
|
+
mentions: row.mentions ?? [],
|
|
137
|
+
author: row.author ? { id: row.author.id, name: row.author.name, image: row.author.image ?? null } : null,
|
|
138
|
+
createdAt: row.createdAt.toISOString(),
|
|
139
|
+
editedAt: row.editedAt ? row.editedAt.toISOString() : null,
|
|
140
|
+
canEdit: mine,
|
|
141
|
+
canDelete: mine || !!ctx.principal?.roles.includes("admin"),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
async find(entity, recordId, commentId, ctx) {
|
|
145
|
+
const row = UUID.test(commentId)
|
|
146
|
+
? await this.prisma.recordComment.findFirst({ where: { id: commentId, entity: entity.storageName, recordId, deletedAt: null, ...this.orgWhere(ctx) } })
|
|
147
|
+
: null;
|
|
148
|
+
if (!row)
|
|
149
|
+
throw new NotFoundException("Comment not found");
|
|
150
|
+
return row;
|
|
151
|
+
}
|
|
152
|
+
async notifyMentions(entity, record, comment, userIds, ctx) {
|
|
153
|
+
const ids = userIds.filter((id) => id !== ctx.principal?.id);
|
|
154
|
+
if (!ids.length)
|
|
155
|
+
return;
|
|
156
|
+
const actor = ctx.user?.name ?? "Someone";
|
|
157
|
+
const recordLabel = label(entity, record);
|
|
158
|
+
const text = excerpt(commentText(comment.body));
|
|
159
|
+
const link = `/${entity.path}/${record.id}#comment-${comment.id}`;
|
|
160
|
+
await this.service.notifications.notify(ids, {
|
|
161
|
+
title: `${actor} mentioned you on ${recordLabel}`,
|
|
162
|
+
body: text,
|
|
163
|
+
link,
|
|
164
|
+
organizationId: ctx.principal?.organizationId ?? null,
|
|
165
|
+
entity: entity.storageName,
|
|
166
|
+
recordId: record.id,
|
|
167
|
+
event: `${entity.name}.mentioned`,
|
|
168
|
+
});
|
|
169
|
+
const mail = this.service.mail;
|
|
170
|
+
if (!mail)
|
|
171
|
+
return;
|
|
172
|
+
const users = await this.prisma.user.findMany({ where: { id: { in: ids } }, select: { email: true } });
|
|
173
|
+
for (const u of users) {
|
|
174
|
+
await mail
|
|
175
|
+
.send({ to: u.email, template: "mention", props: { actor, record: recordLabel, entity: entity.label, excerpt: text, link } })
|
|
176
|
+
.catch((e) => this.logger.warn(`Mention email to ${u.email} failed: ${e.message}`));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// ─── Comments API ───────────────────────────────────────────────────────────
|
|
180
|
+
async list(entity, id, query, ctx) {
|
|
181
|
+
this.requireComments(entity);
|
|
182
|
+
await this.record(entity, id, ctx, "with");
|
|
183
|
+
const page = Math.max(1, Number(query.page) || 1);
|
|
184
|
+
const pageSize = Math.min(100, Math.max(1, Number(query.pageSize) || 50));
|
|
185
|
+
const where = { entity: entity.storageName, recordId: id, deletedAt: null, ...this.orgWhere(ctx) };
|
|
186
|
+
const [total, rows] = await this.prisma.$transaction([
|
|
187
|
+
this.prisma.recordComment.count({ where }),
|
|
188
|
+
this.prisma.recordComment.findMany({ where, orderBy: [{ createdAt: "asc" }, { id: "asc" }], skip: (page - 1) * pageSize, take: pageSize, include: { author: AUTHOR } }),
|
|
189
|
+
]);
|
|
190
|
+
return { data: rows.map((r) => this.out(r, ctx)), meta: { page, pageSize, total, pages: Math.max(1, Math.ceil(total / pageSize)) } };
|
|
191
|
+
}
|
|
192
|
+
async create(entity, id, input, ctx) {
|
|
193
|
+
this.requireComments(entity);
|
|
194
|
+
const p = this.person(ctx);
|
|
195
|
+
const record = await this.record(entity, id, ctx);
|
|
196
|
+
const body = this.body(input);
|
|
197
|
+
const mentions = await this.validMentions(entity, record, parseMentions(body), ctx);
|
|
198
|
+
const row = await this.prisma.recordComment.create({
|
|
199
|
+
data: { entity: entity.storageName, recordId: id, authorId: p.id, body, mentions, ...(this.service.app.tenancy.enabled ? { organizationId: p.organizationId ?? null } : {}) },
|
|
200
|
+
include: { author: AUTHOR },
|
|
201
|
+
});
|
|
202
|
+
await this.notifyMentions(entity, record, row, mentions, ctx);
|
|
203
|
+
return this.out(row, ctx);
|
|
204
|
+
}
|
|
205
|
+
async update(entity, id, commentId, input, ctx) {
|
|
206
|
+
this.requireComments(entity);
|
|
207
|
+
const p = this.person(ctx);
|
|
208
|
+
const record = await this.record(entity, id, ctx);
|
|
209
|
+
const existing = await this.find(entity, id, commentId, ctx);
|
|
210
|
+
if (existing.authorId !== p.id)
|
|
211
|
+
throw new ForbiddenException("Only the author can edit this comment");
|
|
212
|
+
const body = this.body(input);
|
|
213
|
+
const mentions = await this.validMentions(entity, record, parseMentions(body), ctx);
|
|
214
|
+
const row = await this.prisma.recordComment.update({ where: { id: commentId }, data: { body, mentions, editedAt: new Date() }, include: { author: AUTHOR } });
|
|
215
|
+
const before = new Set(existing.mentions ?? []);
|
|
216
|
+
await this.notifyMentions(entity, record, row, mentions.filter((m) => !before.has(m)), ctx);
|
|
217
|
+
return this.out(row, ctx);
|
|
218
|
+
}
|
|
219
|
+
async remove(entity, id, commentId, ctx) {
|
|
220
|
+
this.requireComments(entity);
|
|
221
|
+
const p = this.person(ctx);
|
|
222
|
+
await this.record(entity, id, ctx, "with");
|
|
223
|
+
const existing = await this.find(entity, id, commentId, ctx);
|
|
224
|
+
if (existing.authorId !== p.id && !p.roles.includes("admin"))
|
|
225
|
+
throw new ForbiddenException("Only the author or an admin can delete this comment");
|
|
226
|
+
await this.prisma.recordComment.update({ where: { id: commentId }, data: { deletedAt: new Date() } });
|
|
227
|
+
}
|
|
228
|
+
/** People the caller can @mention on this record. */
|
|
229
|
+
async mentionable(entity, id, query, ctx) {
|
|
230
|
+
this.requireComments(entity);
|
|
231
|
+
const p = this.person(ctx);
|
|
232
|
+
const record = await this.record(entity, id, ctx, "with");
|
|
233
|
+
const search = typeof query.search === "string" ? query.search.trim().slice(0, 100) : "";
|
|
234
|
+
const limit = Math.min(20, Math.max(1, Number(query.limit) || 8));
|
|
235
|
+
const users = await this.readers(entity, record, ctx, { ...(search ? { search } : {}), limit: limit + 1 });
|
|
236
|
+
return { data: users.filter((u) => u.id !== p.id).slice(0, limit).map((u) => ({ id: u.id, name: u.name, image: u.image ?? null })) };
|
|
237
|
+
}
|
|
238
|
+
// ─── Timeline ───────────────────────────────────────────────────────────────
|
|
239
|
+
/**
|
|
240
|
+
* Audit history and comments of one record, newest first, `limit` per page; pass `before` (the previous
|
|
241
|
+
* page's `nextCursor`) for older events. Changes to fields the caller cannot read are left out.
|
|
242
|
+
*/
|
|
243
|
+
async timeline(entity, id, query, ctx) {
|
|
244
|
+
if (!entity.features.audit && !entity.features.comments)
|
|
245
|
+
throw new NotFoundException(`Activity is not recorded for ${entity.pluralLabel.toLowerCase()}`);
|
|
246
|
+
const record = await this.record(entity, id, ctx, "with");
|
|
247
|
+
const limit = Math.min(100, Math.max(1, Number(query.limit) || 20));
|
|
248
|
+
const cursor = parseCursor(query.before);
|
|
249
|
+
const before = cursor ? [{ OR: [{ createdAt: { lt: cursor.at } }, { createdAt: cursor.at, id: { lt: cursor.id } }] }] : [];
|
|
250
|
+
const order = [{ createdAt: "desc" }, { id: "desc" }];
|
|
251
|
+
const [audits, comments] = await Promise.all([
|
|
252
|
+
entity.features.audit
|
|
253
|
+
? this.prisma.auditLog.findMany({ where: { AND: [{ entity: entity.storageName, recordId: id, ...this.orgWhere(ctx) }, ...before] }, orderBy: order, take: limit + 1, include: { user: AUTHOR, apiKey: { select: { id: true, name: true } } } })
|
|
254
|
+
: [],
|
|
255
|
+
entity.features.comments
|
|
256
|
+
? this.prisma.recordComment.findMany({ where: { AND: [{ entity: entity.storageName, recordId: id, deletedAt: null, ...this.orgWhere(ctx) }, ...before] }, orderBy: order, take: limit + 1, include: { author: AUTHOR } })
|
|
257
|
+
: [],
|
|
258
|
+
]);
|
|
259
|
+
const merged = [...audits.map((row) => ({ row, comment: false })), ...comments.map((row) => ({ row, comment: true }))].sort((a, b) => b.row.createdAt.getTime() - a.row.createdAt.getTime() || (a.row.id < b.row.id ? 1 : a.row.id > b.row.id ? -1 : 0));
|
|
260
|
+
const page = merged.slice(0, limit);
|
|
261
|
+
const last = page[page.length - 1];
|
|
262
|
+
const hidden = await unreadableFields(entity, ctx.principal, record, this.service.policyCheck(entity, ctx));
|
|
263
|
+
const labels = new LabelCache(this.service, ctx);
|
|
264
|
+
const data = [];
|
|
265
|
+
for (const { row, comment } of page) {
|
|
266
|
+
const event = comment ? this.commentEvent(row, ctx) : await this.auditEvent(entity, row, hidden, labels);
|
|
267
|
+
if (event)
|
|
268
|
+
data.push(event);
|
|
269
|
+
}
|
|
270
|
+
return { data, nextCursor: merged.length > limit && last ? `${last.row.createdAt.toISOString()}_${last.row.id}` : null };
|
|
271
|
+
}
|
|
272
|
+
commentEvent(row, ctx) {
|
|
273
|
+
const comment = this.out(row, ctx);
|
|
274
|
+
return { id: row.id, kind: "comment", at: comment.createdAt, actor: comment.author, comment };
|
|
275
|
+
}
|
|
276
|
+
async auditEvent(entity, row, hidden, labels) {
|
|
277
|
+
const base = { id: row.id, at: row.createdAt.toISOString(), actor: row.user ? { id: row.user.id, name: row.user.name, image: row.user.image ?? null } : row.apiKey ? { id: `apikey:${row.apiKey.id}`, name: `API key "${row.apiKey.name}"`, image: null } : null };
|
|
278
|
+
const action = String(row.action);
|
|
279
|
+
if (action === "create" || action === "import")
|
|
280
|
+
return { ...base, kind: "created" };
|
|
281
|
+
if (action === "delete" || action === "soft-delete")
|
|
282
|
+
return { ...base, kind: "deleted" };
|
|
283
|
+
if (action === "restore")
|
|
284
|
+
return { ...base, kind: "restored" };
|
|
285
|
+
if (action === "update") {
|
|
286
|
+
const changes = await this.changes(entity, row.changes ?? {}, hidden, labels);
|
|
287
|
+
// An update that only touched fields this viewer can't read is not shown at all.
|
|
288
|
+
return changes.length ? { ...base, kind: "updated", changes } : null;
|
|
289
|
+
}
|
|
290
|
+
if (action.startsWith("transition:")) {
|
|
291
|
+
const name = action.slice("transition:".length);
|
|
292
|
+
const wf = entity.workflow;
|
|
293
|
+
const t = wf?.transitions.find((x) => x.name === name);
|
|
294
|
+
const change = wf ? row.changes?.[wf.field] : undefined;
|
|
295
|
+
const visible = !!wf && !hidden.has(wf.field);
|
|
296
|
+
return { ...base, kind: "transition", transition: { name, label: t?.label ?? name, from: visible ? (change?.from ?? null) : null, to: visible ? (change?.to ?? null) : null } };
|
|
297
|
+
}
|
|
298
|
+
const name = action.startsWith("action:") ? action.slice("action:".length) : action;
|
|
299
|
+
return { ...base, kind: "action", action: { name, label: entity.actions.find((a) => a.name === name)?.label ?? name } };
|
|
300
|
+
}
|
|
301
|
+
async changes(entity, changes, hidden, labels) {
|
|
302
|
+
const out = [];
|
|
303
|
+
for (const [key, change] of Object.entries(changes)) {
|
|
304
|
+
if (key.startsWith("_") || !change || typeof change !== "object")
|
|
305
|
+
continue;
|
|
306
|
+
const f = entity.fields.find((x) => x.name === key || x.apiName === key || x.relation?.foreignKey === key);
|
|
307
|
+
if (!f || f.hidden || hidden.has(f.name))
|
|
308
|
+
continue;
|
|
309
|
+
const { from, to } = change;
|
|
310
|
+
if (f.file)
|
|
311
|
+
out.push({ field: f.name, label: f.label, values: false });
|
|
312
|
+
else if (f.relation)
|
|
313
|
+
out.push({ field: f.name, label: f.label, values: true, from: await labels.resolve(f, from), to: await labels.resolve(f, to) });
|
|
314
|
+
else
|
|
315
|
+
out.push({ field: f.name, label: f.label, values: true, from: from ?? null, to: to ?? null });
|
|
316
|
+
}
|
|
317
|
+
return out;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function parseCursor(v) {
|
|
321
|
+
if (v === undefined || v === null || v === "")
|
|
322
|
+
return null;
|
|
323
|
+
const m = typeof v === "string" ? /^(.+)_([0-9a-f-]{36})$/i.exec(v) : null;
|
|
324
|
+
const at = m ? new Date(m[1]) : null;
|
|
325
|
+
if (!m || !at || Number.isNaN(at.getTime()))
|
|
326
|
+
throw new BadRequestException({ statusCode: 400, error: "Bad Request", message: "Invalid cursor", issues: [{ path: "before", message: "Invalid cursor" }] });
|
|
327
|
+
return { at, id: m[2] };
|
|
328
|
+
}
|
|
329
|
+
/** Labels of related records in change history, resolved within the viewer's permissions (unknown ones stay null). */
|
|
330
|
+
class LabelCache {
|
|
331
|
+
service;
|
|
332
|
+
ctx;
|
|
333
|
+
cache = new Map();
|
|
334
|
+
constructor(service, ctx) {
|
|
335
|
+
this.service = service;
|
|
336
|
+
this.ctx = ctx;
|
|
337
|
+
}
|
|
338
|
+
async resolve(f, value) {
|
|
339
|
+
if (value === null || value === undefined)
|
|
340
|
+
return null;
|
|
341
|
+
const ids = (Array.isArray(value) ? value : [value]).filter((x) => typeof x === "string");
|
|
342
|
+
const target = f.relation.entity;
|
|
343
|
+
const known = this.cache.get(target) ?? new Map();
|
|
344
|
+
this.cache.set(target, known);
|
|
345
|
+
const missing = ids.filter((id) => !known.has(id));
|
|
346
|
+
if (missing.length) {
|
|
347
|
+
let found = [];
|
|
348
|
+
try {
|
|
349
|
+
if (target === "User")
|
|
350
|
+
found = (await this.service.lookupUsers({ ids: missing.join(",") }, this.ctx)).data;
|
|
351
|
+
// Team names are visible to every member of the organization (the team picker shows them too).
|
|
352
|
+
else if (target === "Team")
|
|
353
|
+
found = (await new TeamService(this.service).lookup({ ids: missing.join(",") }, this.ctx)).data;
|
|
354
|
+
else if (target !== "Organization") {
|
|
355
|
+
const entity = this.service.entityByName(target);
|
|
356
|
+
if (entity && authorize(entity, "read", this.ctx.principal).allowed)
|
|
357
|
+
found = (await this.service.lookup(entity, { ids: missing.join(",") }, this.ctx)).data;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
found = [];
|
|
362
|
+
}
|
|
363
|
+
for (const id of missing)
|
|
364
|
+
known.set(id, found.find((x) => x.id === id)?.label ?? null);
|
|
365
|
+
}
|
|
366
|
+
const refs = ids.map((id) => ({ id, label: known.get(id) ?? null }));
|
|
367
|
+
return Array.isArray(value) ? refs : (refs[0] ?? null);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
//# sourceMappingURL=comments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comments.js","sourceRoot":"","sources":["../src/comments.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { AppIR, EntityIR, FieldIR } from "@tekki-forge/compiler";
|
|
2
|
+
type Row = Record<string, any>;
|
|
3
|
+
type Db = any;
|
|
4
|
+
/** Convert an expression result into the value stored for a field of `f.kind`. */
|
|
5
|
+
export declare function toStorage(f: FieldIR, value: unknown): unknown;
|
|
6
|
+
export declare class ComputedEngine {
|
|
7
|
+
private readonly byName;
|
|
8
|
+
constructor(app: AppIR);
|
|
9
|
+
hasComputed(entity: EntityIR): boolean;
|
|
10
|
+
/** Run every aggregate the entity's computed fields need, for one parent record. */
|
|
11
|
+
private loadAggregates;
|
|
12
|
+
/** Computed values for `record` (a full Prisma row). */
|
|
13
|
+
compute(db: Db, entity: EntityIR, record: Row, now?: Date): Promise<Row>;
|
|
14
|
+
/**
|
|
15
|
+
* Recompute the computed fields of one record and persist changes.
|
|
16
|
+
* Returns the updated row (or the input when nothing changed).
|
|
17
|
+
*/
|
|
18
|
+
refresh(db: Db, entity: EntityIR, record: Row, include?: Row): Promise<Row>;
|
|
19
|
+
/**
|
|
20
|
+
* After rows of `child` changed, recompute the parents whose aggregates cover them.
|
|
21
|
+
* `before`/`after` are the child rows (either may be null for create/delete).
|
|
22
|
+
*/
|
|
23
|
+
refreshParents(db: Db, child: EntityIR, before: Row | null, after: Row | null, depth?: number): Promise<void>;
|
|
24
|
+
/** Recompute every row of an entity (after adding or changing a computed field). */
|
|
25
|
+
recomputeAll(db: Db, entity: EntityIR, onProgress?: (done: number) => void): Promise<number>;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
package/dist/computed.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { evaluate } from "@tekki-forge/expression";
|
|
2
|
+
import DecimalDefault from "decimal.js";
|
|
3
|
+
// decimal.js typings under NodeNext expose the default export as a namespace; unwrap it.
|
|
4
|
+
const Decimal = (DecimalDefault.default ?? DecimalDefault);
|
|
5
|
+
const aggregateKey = (fn, relation, field) => `${fn}:${relation}:${field ?? ""}`;
|
|
6
|
+
/** Convert an expression result into the value stored for a field of `f.kind`. */
|
|
7
|
+
export function toStorage(f, value) {
|
|
8
|
+
if (value === null || value === undefined)
|
|
9
|
+
return null;
|
|
10
|
+
switch (f.kind) {
|
|
11
|
+
case "integer": {
|
|
12
|
+
const n = Number(typeof value === "object" ? String(value) : value);
|
|
13
|
+
return Number.isFinite(n) ? Math.round(n) : null;
|
|
14
|
+
}
|
|
15
|
+
case "percentage": {
|
|
16
|
+
const n = Number(typeof value === "object" ? String(value) : value);
|
|
17
|
+
return Number.isFinite(n) ? n : null;
|
|
18
|
+
}
|
|
19
|
+
case "decimal":
|
|
20
|
+
case "money":
|
|
21
|
+
try {
|
|
22
|
+
return new Decimal(String(value)).toDecimalPlaces(f.scale ?? 6, Decimal.ROUND_HALF_UP).toFixed();
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
case "boolean":
|
|
28
|
+
return Boolean(value);
|
|
29
|
+
case "date": {
|
|
30
|
+
const d = value instanceof Date ? value : new Date(String(value));
|
|
31
|
+
return Number.isNaN(d.getTime()) ? null : new Date(`${d.toISOString().slice(0, 10)}T00:00:00.000Z`);
|
|
32
|
+
}
|
|
33
|
+
case "datetime": {
|
|
34
|
+
const d = value instanceof Date ? value : new Date(String(value));
|
|
35
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
36
|
+
}
|
|
37
|
+
default:
|
|
38
|
+
return String(value);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Compare stored and freshly computed values (Decimal objects, Dates and numbers normalized). */
|
|
42
|
+
function same(a, b) {
|
|
43
|
+
const norm = (v) => {
|
|
44
|
+
if (v === null || v === undefined)
|
|
45
|
+
return null;
|
|
46
|
+
if (v instanceof Date)
|
|
47
|
+
return v.getTime();
|
|
48
|
+
if (typeof v === "object") {
|
|
49
|
+
try {
|
|
50
|
+
return new Decimal(String(v)).toFixed();
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return JSON.stringify(v);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (typeof v === "string" && /^-?\d+(\.\d+)?$/.test(v))
|
|
57
|
+
return new Decimal(v).toFixed();
|
|
58
|
+
return v;
|
|
59
|
+
};
|
|
60
|
+
return norm(a) === norm(b);
|
|
61
|
+
}
|
|
62
|
+
export class ComputedEngine {
|
|
63
|
+
byName;
|
|
64
|
+
constructor(app) {
|
|
65
|
+
this.byName = new Map(app.entities.map((e) => [e.name, e]));
|
|
66
|
+
}
|
|
67
|
+
hasComputed(entity) {
|
|
68
|
+
return entity.computedOrder.length > 0;
|
|
69
|
+
}
|
|
70
|
+
/** Run every aggregate the entity's computed fields need, for one parent record. */
|
|
71
|
+
async loadAggregates(db, entity, recordId) {
|
|
72
|
+
const out = new Map();
|
|
73
|
+
for (const name of entity.computedOrder) {
|
|
74
|
+
const f = entity.fields.find((x) => x.name === name);
|
|
75
|
+
for (const a of f.computed.aggregates) {
|
|
76
|
+
const key = aggregateKey(a.fn, a.relation, a.field);
|
|
77
|
+
if (out.has(key))
|
|
78
|
+
continue;
|
|
79
|
+
const inv = entity.inverseRelations.find((r) => r.name === a.relation && r.type === "one-to-many");
|
|
80
|
+
const child = inv && this.byName.get(inv.entity);
|
|
81
|
+
if (!inv || !child)
|
|
82
|
+
continue;
|
|
83
|
+
const where = { [inv.foreignKey]: recordId, ...(child.features.softDelete ? { deletedAt: null } : {}) };
|
|
84
|
+
const delegate = db[child.delegate];
|
|
85
|
+
if (a.fn === "count")
|
|
86
|
+
out.set(key, await delegate.count({ where }));
|
|
87
|
+
else {
|
|
88
|
+
const op = `_${a.fn}`;
|
|
89
|
+
const res = await delegate.aggregate({ where, [op]: { [a.field]: true } });
|
|
90
|
+
const v = res[op]?.[a.field];
|
|
91
|
+
// An empty relation sums to 0; averages/min/max of nothing stay empty.
|
|
92
|
+
out.set(key, v === null || v === undefined ? (a.fn === "sum" ? 0 : null) : v);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
/** Computed values for `record` (a full Prisma row). */
|
|
99
|
+
async compute(db, entity, record, now = new Date()) {
|
|
100
|
+
const aggregates = entity.computedOrder.some((n) => entity.fields.find((f) => f.name === n).computed.aggregates.length)
|
|
101
|
+
? await this.loadAggregates(db, entity, record.id)
|
|
102
|
+
: new Map();
|
|
103
|
+
const scope = { ...record };
|
|
104
|
+
const out = {};
|
|
105
|
+
for (const name of entity.computedOrder) {
|
|
106
|
+
const f = entity.fields.find((x) => x.name === name);
|
|
107
|
+
const value = evaluate(f.computed.ast, {
|
|
108
|
+
fields: scope,
|
|
109
|
+
aggregate: (fn, relation, field) => aggregates.get(aggregateKey(fn, relation, field)) ?? null,
|
|
110
|
+
now,
|
|
111
|
+
});
|
|
112
|
+
const stored = toStorage(f, value);
|
|
113
|
+
out[name] = stored;
|
|
114
|
+
scope[name] = stored;
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Recompute the computed fields of one record and persist changes.
|
|
120
|
+
* Returns the updated row (or the input when nothing changed).
|
|
121
|
+
*/
|
|
122
|
+
async refresh(db, entity, record, include) {
|
|
123
|
+
if (!this.hasComputed(entity))
|
|
124
|
+
return record;
|
|
125
|
+
const values = await this.compute(db, entity, record);
|
|
126
|
+
const changed = Object.fromEntries(Object.entries(values).filter(([k, v]) => !same(record[k], v)));
|
|
127
|
+
if (Object.keys(changed).length === 0)
|
|
128
|
+
return record;
|
|
129
|
+
return db[entity.delegate].update({ where: { id: record.id }, data: changed, ...(include ? { include } : {}) });
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* After rows of `child` changed, recompute the parents whose aggregates cover them.
|
|
133
|
+
* `before`/`after` are the child rows (either may be null for create/delete).
|
|
134
|
+
*/
|
|
135
|
+
async refreshParents(db, child, before, after, depth = 0) {
|
|
136
|
+
if (depth > 8)
|
|
137
|
+
return; // defensive: aggregate chains deeper than this are a schema smell
|
|
138
|
+
for (const p of child.aggregateParents) {
|
|
139
|
+
const parent = this.byName.get(p.entity);
|
|
140
|
+
if (!parent)
|
|
141
|
+
continue;
|
|
142
|
+
const ids = new Set([before?.[p.foreignKey], after?.[p.foreignKey]].filter((v) => typeof v === "string"));
|
|
143
|
+
for (const id of ids) {
|
|
144
|
+
const row = await db[parent.delegate].findUnique({ where: { id } });
|
|
145
|
+
if (!row)
|
|
146
|
+
continue;
|
|
147
|
+
const updated = await this.refresh(db, parent, row);
|
|
148
|
+
if (updated !== row)
|
|
149
|
+
await this.refreshParents(db, parent, row, updated, depth + 1);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/** Recompute every row of an entity (after adding or changing a computed field). */
|
|
154
|
+
async recomputeAll(db, entity, onProgress) {
|
|
155
|
+
if (!this.hasComputed(entity))
|
|
156
|
+
return 0;
|
|
157
|
+
let cursor;
|
|
158
|
+
let done = 0;
|
|
159
|
+
for (;;) {
|
|
160
|
+
const rows = await db[entity.delegate].findMany({ take: 200, orderBy: { id: "asc" }, ...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}) });
|
|
161
|
+
if (rows.length === 0)
|
|
162
|
+
return done;
|
|
163
|
+
for (const row of rows) {
|
|
164
|
+
const updated = await this.refresh(db, entity, row);
|
|
165
|
+
if (updated !== row)
|
|
166
|
+
await this.refreshParents(db, entity, row, updated);
|
|
167
|
+
done++;
|
|
168
|
+
}
|
|
169
|
+
onProgress?.(done);
|
|
170
|
+
cursor = rows[rows.length - 1].id;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=computed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"computed.js","sourceRoot":"","sources":["../src/computed.ts"],"names":[],"mappings":"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"}
|