@manablox/db 0.2.0 → 0.4.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/index-BLAkQMJT.d.ts +877 -0
- package/dist/index-DrNMGM9N.d.ts +5193 -0
- package/dist/index.d.ts +123 -0
- package/dist/index.js +60 -0
- package/dist/repositories-pz4NeWaF.js +1928 -0
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/schema-Dm3RcBst.js +648 -0
- package/dist/schema.d.ts +2 -0
- package/dist/schema.js +2 -0
- package/dist/testing.d.ts +77 -0
- package/dist/testing.js +217 -0
- package/migrations/0009_audit-log.sql +40 -0
- package/migrations/0010_notifications-approvals.sql +45 -0
- package/migrations/meta/0009_snapshot.json +3192 -0
- package/migrations/meta/0010_snapshot.json +3574 -0
- package/migrations/meta/_journal.json +14 -0
- package/package.json +18 -10
- package/drizzle.config.ts +0 -11
- package/src/bootstrap.ts +0 -13
- package/src/cli/create-db.ts +0 -30
- package/src/cli/migrate.ts +0 -17
- package/src/client.ts +0 -44
- package/src/columns.ts +0 -39
- package/src/errors.ts +0 -50
- package/src/index.ts +0 -19
- package/src/migrate.ts +0 -21
- package/src/pagination.ts +0 -52
- package/src/query.ts +0 -213
- package/src/repositories/asset-usage.ts +0 -166
- package/src/repositories/asset.ts +0 -181
- package/src/repositories/content-type.ts +0 -116
- package/src/repositories/content.ts +0 -811
- package/src/repositories/index.ts +0 -40
- package/src/repositories/menu.ts +0 -235
- package/src/repositories/role.ts +0 -85
- package/src/repositories/space.ts +0 -83
- package/src/repositories/user.ts +0 -280
- package/src/repositories/webhook.ts +0 -46
- package/src/repositories/workflow.ts +0 -306
- package/src/schema/assets.ts +0 -108
- package/src/schema/auth.ts +0 -166
- package/src/schema/content-types.ts +0 -31
- package/src/schema/content.ts +0 -133
- package/src/schema/index.ts +0 -38
- package/src/schema/menus.ts +0 -61
- package/src/schema/relations.ts +0 -64
- package/src/schema/spaces.ts +0 -20
- package/src/schema/webhooks.ts +0 -46
- package/src/schema/workflows.ts +0 -92
- package/src/testing-fixtures.ts +0 -139
- package/src/testing.ts +0 -105
- package/test/asset-usage.test.ts +0 -101
- package/test/menu.test.ts +0 -126
- package/test/publish.test.ts +0 -130
- package/test/query.test.ts +0 -170
- package/test/role.test.ts +0 -81
- package/test/tree.test.ts +0 -188
- package/test/user.test.ts +0 -126
- package/test/webhook.test.ts +0 -48
- package/tsconfig.json +0 -4
- package/vitest.config.ts +0 -10
|
@@ -0,0 +1,877 @@
|
|
|
1
|
+
import { T as webhooks, U as contents, W as publishedContents, _ as UserRow, a as ContentApprovalRow, b as index_d_exports, d as MenuItemRow, f as MenuRow, g as SpaceRow, h as RoleRow, i as AuditEntryRow, l as ContentVersionRow, m as PushSubscriptionRow, p as NotificationRow, r as AssetVariantRow, s as ContentRow, t as AssetRow, u as MembershipRow, v as WorkflowRow, w as webhookDeliveries, y as WorkflowRunRow } from "./index-DrNMGM9N.js";
|
|
2
|
+
import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
3
|
+
import postgres from "postgres";
|
|
4
|
+
import { SQL, SQL as SQL$1 } from "drizzle-orm";
|
|
5
|
+
import { PgColumn, PgColumn as PgColumn$1, PgTable } from "drizzle-orm/pg-core";
|
|
6
|
+
import { ApprovalStatus, AuditAction, AuditActor, AuditActorKind, AuditChange, AuditSortField, AuditTargetKind, ContentStatus, ContentTypeDefinition, ContentTypeInput, ContentTypeRegistry, DatabaseConfig, FilterOperator, Loose, NotificationKind, NotificationPreferences, WorkflowCursor, WorkflowRunContext, WorkflowRunStatus, WorkflowSelection, WorkflowStep, WorkflowStepLog, WorkflowTrigger } from "@manablox/core";
|
|
7
|
+
//#region src/client.d.ts
|
|
8
|
+
type Database = PostgresJsDatabase<typeof index_d_exports>;
|
|
9
|
+
/** What `db.transaction((tx) => …)` hands its callback. */
|
|
10
|
+
type Transaction = Parameters<Parameters<Database['transaction']>[0]>[0];
|
|
11
|
+
/**
|
|
12
|
+
* Anything a query can run on. Repository internals take this so the same helper serves
|
|
13
|
+
* a call inside a transaction and one outside it, without casting `tx` to `Database`.
|
|
14
|
+
*/
|
|
15
|
+
type Executor = Database | Transaction;
|
|
16
|
+
type Sql = ReturnType<typeof postgres>;
|
|
17
|
+
interface DatabaseHandle {
|
|
18
|
+
db: Database;
|
|
19
|
+
sql: Sql;
|
|
20
|
+
close: () => Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
interface DatabaseOptions {
|
|
23
|
+
/** Called once per statement sent to Postgres. Used by tests to assert query counts. */
|
|
24
|
+
onQuery?: (query: string) => void;
|
|
25
|
+
}
|
|
26
|
+
declare function createDatabase(config: DatabaseConfig, options?: DatabaseOptions): DatabaseHandle;
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/query.d.ts
|
|
29
|
+
/**
|
|
30
|
+
* Both content tables share a column set, so one filter builder serves the draft and
|
|
31
|
+
* the published projection. Typing the parameter as the union (rather than `PgTable`)
|
|
32
|
+
* keeps real column references, which is what lets Drizzle bind arrays and UUIDs
|
|
33
|
+
* correctly instead of stringifying them into the SQL text.
|
|
34
|
+
*/
|
|
35
|
+
type ContentTable = typeof contents | typeof publishedContents;
|
|
36
|
+
/**
|
|
37
|
+
* Note on `?: T | undefined` throughout the input types in this file: the workspace runs
|
|
38
|
+
* with `exactOptionalPropertyTypes`, under which a bare `?:` accepts an absent property
|
|
39
|
+
* but rejects an explicit `undefined`. Validators (Zod, and any Standard Schema) produce
|
|
40
|
+
* exactly that explicit `undefined` for optional fields, so input DTOs widen while the
|
|
41
|
+
* domain types they feed stay strict.
|
|
42
|
+
*/
|
|
43
|
+
interface FieldFilter {
|
|
44
|
+
/** Field machine name on the content type. */
|
|
45
|
+
name: string;
|
|
46
|
+
op: FilterOperator;
|
|
47
|
+
value?: unknown | undefined;
|
|
48
|
+
}
|
|
49
|
+
interface ContentFilter {
|
|
50
|
+
spaceId?: string | undefined;
|
|
51
|
+
/** Content type ids. */
|
|
52
|
+
typeIds?: string[] | undefined;
|
|
53
|
+
locale?: string | undefined;
|
|
54
|
+
status?: 'draft' | 'published' | 'archived' | undefined;
|
|
55
|
+
ids?: string[] | undefined;
|
|
56
|
+
parentId?: string | null | undefined;
|
|
57
|
+
/** Restrict to the subtree below this content id (inclusive of its descendants). */
|
|
58
|
+
under?: string | undefined;
|
|
59
|
+
slug?: string | undefined;
|
|
60
|
+
permalink?: string | undefined;
|
|
61
|
+
localizationId?: string | undefined;
|
|
62
|
+
/** Full-text query against `title` + field contributions. */
|
|
63
|
+
search?: string | undefined;
|
|
64
|
+
fields?: FieldFilter[] | undefined;
|
|
65
|
+
}
|
|
66
|
+
interface ContentSort {
|
|
67
|
+
by: 'position' | 'title' | 'createdAt' | 'updatedAt' | 'publishedAt' | 'slug';
|
|
68
|
+
direction: 'asc' | 'desc';
|
|
69
|
+
}
|
|
70
|
+
interface Pagination {
|
|
71
|
+
limit: number;
|
|
72
|
+
offset: number;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Translates a filter into SQL. Every field predicate is checked against the field
|
|
76
|
+
* type's declared `filters` list first: an unsupported operator is a 400, and every
|
|
77
|
+
* supported one has an index behind it.
|
|
78
|
+
*/
|
|
79
|
+
declare function buildContentWhere(table: ContentTable, filter: ContentFilter, registry: ContentTypeRegistry): SQL | undefined;
|
|
80
|
+
declare function buildOrderBy(sorts: ContentSort[]): SQL;
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/pagination.d.ts
|
|
83
|
+
interface Paginated<T> {
|
|
84
|
+
items: T[];
|
|
85
|
+
total: number;
|
|
86
|
+
limit: number;
|
|
87
|
+
offset: number;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* One page of a table plus the total, in one round trip: a window `count(*) over ()`
|
|
91
|
+
* rides along with the rows. A page past the end comes back empty and so carries no
|
|
92
|
+
* count; only then is the total asked for separately, so a caller paging by `total`
|
|
93
|
+
* still learns the true size.
|
|
94
|
+
*/
|
|
95
|
+
declare function paginate<TTable extends PgTable>(db: Executor, table: TTable, options: {
|
|
96
|
+
where?: SQL | undefined;
|
|
97
|
+
orderBy: SQL | PgColumn | (SQL | PgColumn)[];
|
|
98
|
+
pagination: Pagination;
|
|
99
|
+
}): Promise<Paginated<TTable['$inferSelect']>>;
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/repositories/asset.d.ts
|
|
102
|
+
interface AssetWriteData {
|
|
103
|
+
id?: string | undefined;
|
|
104
|
+
spaceId: string;
|
|
105
|
+
driver: string;
|
|
106
|
+
key: string;
|
|
107
|
+
filename: string;
|
|
108
|
+
name: string;
|
|
109
|
+
mimeType: string;
|
|
110
|
+
size: number;
|
|
111
|
+
width?: number | null | undefined;
|
|
112
|
+
height?: number | null | undefined;
|
|
113
|
+
duration?: number | null | undefined;
|
|
114
|
+
checksum?: string | null | undefined;
|
|
115
|
+
alt?: string | null | undefined;
|
|
116
|
+
title?: string | null | undefined;
|
|
117
|
+
meta?: Record<string, unknown> | undefined;
|
|
118
|
+
actorId?: string | null | undefined;
|
|
119
|
+
}
|
|
120
|
+
interface AssetFilter {
|
|
121
|
+
spaceId: string;
|
|
122
|
+
/** Prefix match on the mime type, e.g. `image/`. */
|
|
123
|
+
mimeType?: string | undefined;
|
|
124
|
+
search?: string | undefined;
|
|
125
|
+
}
|
|
126
|
+
declare class AssetRepository {
|
|
127
|
+
private readonly db;
|
|
128
|
+
constructor(db: Database);
|
|
129
|
+
findById(id: string): Promise<AssetRow | null>;
|
|
130
|
+
/**
|
|
131
|
+
* `spaceId` is not an optimisation. On the public instance an asset id is the only
|
|
132
|
+
* thing a caller supplies, and without this predicate any id resolves — including one
|
|
133
|
+
* belonging to another tenant sharing the process.
|
|
134
|
+
*/
|
|
135
|
+
findManyByIds(ids: string[], spaceId?: string | null): Promise<AssetRow[]>;
|
|
136
|
+
findByChecksum(spaceId: string, checksum: string): Promise<AssetRow | null>;
|
|
137
|
+
list(filter: AssetFilter, pagination: Pagination): Promise<Paginated<AssetRow>>;
|
|
138
|
+
create(data: AssetWriteData): Promise<AssetRow>;
|
|
139
|
+
update(id: string, data: Loose<Pick<AssetWriteData, 'name' | 'alt' | 'title' | 'meta'>>): Promise<AssetRow>;
|
|
140
|
+
delete(id: string): Promise<AssetRow | null>;
|
|
141
|
+
/** Timestamps an import carries over; `create()` can only stamp the moment of import. */
|
|
142
|
+
restoreTimestamps(id: string, createdAt: Date, updatedAt: Date): Promise<void>;
|
|
143
|
+
variants(assetIds: string[]): Promise<AssetVariantRow[]>;
|
|
144
|
+
findVariant(assetId: string, preset: string, format: string): Promise<AssetVariantRow | null>;
|
|
145
|
+
/** Drops every variant row; the caller removes the files. */
|
|
146
|
+
deleteVariants(assetId: string): Promise<void>;
|
|
147
|
+
addVariant(data: {
|
|
148
|
+
assetId: string;
|
|
149
|
+
preset: string;
|
|
150
|
+
format: string;
|
|
151
|
+
key: string;
|
|
152
|
+
width?: number | null;
|
|
153
|
+
height?: number | null;
|
|
154
|
+
size: number;
|
|
155
|
+
}): Promise<AssetVariantRow>;
|
|
156
|
+
}
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region src/repositories/asset-usage.d.ts
|
|
159
|
+
/**
|
|
160
|
+
* The asset → document reachability index.
|
|
161
|
+
*
|
|
162
|
+
* `published` tracks the *published projection*, not the draft: a draft that adds an
|
|
163
|
+
* image does not make that image public, and a draft that removes one does not make it
|
|
164
|
+
* private until the change is published. Every method below preserves that distinction,
|
|
165
|
+
* which is why the column exists rather than the table simply holding published rows.
|
|
166
|
+
*/
|
|
167
|
+
declare class AssetUsageRepository {
|
|
168
|
+
private readonly db;
|
|
169
|
+
constructor(db: Database);
|
|
170
|
+
/** The subset of `assetIds` reachable from at least one published document. */
|
|
171
|
+
filterPublished(assetIds: string[]): Promise<Set<string>>;
|
|
172
|
+
forContent(contentId: string): Promise<Array<{
|
|
173
|
+
assetId: string;
|
|
174
|
+
published: boolean;
|
|
175
|
+
}>>;
|
|
176
|
+
/**
|
|
177
|
+
* Records what a *draft* references.
|
|
178
|
+
*
|
|
179
|
+
* Rows the draft dropped are removed only if they are not currently published —
|
|
180
|
+
* otherwise editing a draft would silently revoke access to an image the live page is
|
|
181
|
+
* still showing.
|
|
182
|
+
*/
|
|
183
|
+
recordDraft(contentId: string, spaceId: string, assetIds: string[]): Promise<void>;
|
|
184
|
+
/**
|
|
185
|
+
* Records what the published projection references.
|
|
186
|
+
*
|
|
187
|
+
* Assets the new revision no longer uses lose their published flag but keep their row
|
|
188
|
+
* when the draft still references them, so the admin's "where is this used" view stays
|
|
189
|
+
* complete.
|
|
190
|
+
*/
|
|
191
|
+
recordPublished(contentId: string, spaceId: string, assetIds: string[]): Promise<void>;
|
|
192
|
+
/** Unpublishing revokes every asset this document was keeping public. */
|
|
193
|
+
clearPublished(contentId: string): Promise<void>;
|
|
194
|
+
deleteForContent(contentId: string): Promise<void>;
|
|
195
|
+
count(): Promise<number>;
|
|
196
|
+
/**
|
|
197
|
+
* Every document with its draft and published field values, for the backfill.
|
|
198
|
+
*
|
|
199
|
+
* References are derived from field-type definitions rather than stored, so the
|
|
200
|
+
* backfill cannot be a SQL migration — it has to run inside the application.
|
|
201
|
+
*/
|
|
202
|
+
backfillSource(): Promise<Array<{
|
|
203
|
+
id: string;
|
|
204
|
+
spaceId: string;
|
|
205
|
+
typeId: string;
|
|
206
|
+
draftFields: Record<string, unknown>;
|
|
207
|
+
publishedFields: Record<string, unknown> | null;
|
|
208
|
+
}>>;
|
|
209
|
+
}
|
|
210
|
+
//#endregion
|
|
211
|
+
//#region src/repositories/audit.d.ts
|
|
212
|
+
interface AuditAppendInput {
|
|
213
|
+
/** The space the action concerns; omit for an instance-wide action. */
|
|
214
|
+
spaceId?: string | null | undefined;
|
|
215
|
+
/** Who did it; the current actor (see `runAsActor`) when omitted. */
|
|
216
|
+
actor?: AuditActor | undefined;
|
|
217
|
+
action: AuditAction;
|
|
218
|
+
targetKind: AuditTargetKind;
|
|
219
|
+
targetId?: string | null | undefined;
|
|
220
|
+
targetLabel?: string | null | undefined;
|
|
221
|
+
changes?: AuditChange[] | undefined;
|
|
222
|
+
meta?: Record<string, unknown> | null | undefined;
|
|
223
|
+
at?: Date | undefined;
|
|
224
|
+
}
|
|
225
|
+
interface AuditFilter {
|
|
226
|
+
/** A space's entries; `null` for instance-wide entries only; absent for every entry. */
|
|
227
|
+
spaceId?: string | null | undefined;
|
|
228
|
+
actorKind?: AuditActorKind | undefined;
|
|
229
|
+
actorId?: string | undefined;
|
|
230
|
+
actions?: AuditAction[] | undefined;
|
|
231
|
+
targetKind?: AuditTargetKind | undefined;
|
|
232
|
+
targetId?: string | undefined;
|
|
233
|
+
from?: Date | undefined;
|
|
234
|
+
to?: Date | undefined;
|
|
235
|
+
/** Matched against the actor's label, the target's label and the action. */
|
|
236
|
+
search?: string | undefined;
|
|
237
|
+
}
|
|
238
|
+
interface AuditSort {
|
|
239
|
+
by: AuditSortField;
|
|
240
|
+
direction: 'asc' | 'desc';
|
|
241
|
+
}
|
|
242
|
+
/** The outcome of walking the chain: where it broke, if it did. */
|
|
243
|
+
interface AuditVerification {
|
|
244
|
+
ok: boolean;
|
|
245
|
+
checked: number;
|
|
246
|
+
/** The first entry whose hash does not match, or whose `prevHash` is not its predecessor's. */
|
|
247
|
+
brokenAt: {
|
|
248
|
+
seq: number;
|
|
249
|
+
id: string;
|
|
250
|
+
reason: 'hash' | 'link';
|
|
251
|
+
} | null;
|
|
252
|
+
}
|
|
253
|
+
interface AuditRepositoryOptions {
|
|
254
|
+
/** Told when `record` could not write; the action itself is not undone. */
|
|
255
|
+
onError?: ((error: unknown, input: AuditAppendInput) => void) | undefined;
|
|
256
|
+
/**
|
|
257
|
+
* Told after every entry has been committed: the realtime feed listens here, because
|
|
258
|
+
* an audit entry is written for every change and only after it has happened. A
|
|
259
|
+
* listener that throws is reported to `onError`; the entry stays written.
|
|
260
|
+
*/
|
|
261
|
+
onAppended?: ((row: AuditEntryRow) => void) | undefined;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Appends to and reads the audit log. There is no update and no delete: the table
|
|
265
|
+
* refuses both, so this class offers neither.
|
|
266
|
+
*/
|
|
267
|
+
declare class AuditRepository {
|
|
268
|
+
private readonly db;
|
|
269
|
+
private readonly options;
|
|
270
|
+
constructor(db: Database, options?: AuditRepositoryOptions);
|
|
271
|
+
/**
|
|
272
|
+
* Writes one entry, chained to the last. Writers are serialised on a transaction-level
|
|
273
|
+
* advisory lock, so two entries can never both claim the same predecessor; the lock
|
|
274
|
+
* is held for one read and one insert.
|
|
275
|
+
*/
|
|
276
|
+
append(input: AuditAppendInput): Promise<AuditEntryRow>;
|
|
277
|
+
private write;
|
|
278
|
+
/**
|
|
279
|
+
* `append` that never throws. The write it describes has already happened; failing
|
|
280
|
+
* the request now would report an error for something that succeeded, so the failure
|
|
281
|
+
* is reported to `onError` (the host logs it) and the caller carries on.
|
|
282
|
+
*/
|
|
283
|
+
record(input: AuditAppendInput): Promise<AuditEntryRow | null>;
|
|
284
|
+
findById(id: string): Promise<AuditEntryRow | null>;
|
|
285
|
+
list(filter: AuditFilter, sort?: AuditSort, pagination?: Pagination): Promise<Paginated<AuditEntryRow>>;
|
|
286
|
+
/** The entries about one thing, oldest first: a document's history. */
|
|
287
|
+
forTarget(targetKind: AuditTargetKind, targetId: string, limit?: number): Promise<AuditEntryRow[]>;
|
|
288
|
+
/**
|
|
289
|
+
* Recomputes every hash from the first entry on and checks each link to its
|
|
290
|
+
* predecessor. The chain is one across the instance, so this walks all of it, in
|
|
291
|
+
* batches; on a large log it takes a while and that is the point.
|
|
292
|
+
*/
|
|
293
|
+
verify(batchSize?: number): Promise<AuditVerification>;
|
|
294
|
+
count(filter?: AuditFilter): Promise<number>;
|
|
295
|
+
}
|
|
296
|
+
//#endregion
|
|
297
|
+
//#region src/repositories/content.d.ts
|
|
298
|
+
interface ContentWriteData {
|
|
299
|
+
id?: string | undefined;
|
|
300
|
+
spaceId: string;
|
|
301
|
+
typeId: string;
|
|
302
|
+
locale: string;
|
|
303
|
+
localizationId?: string | undefined;
|
|
304
|
+
parentId?: string | null | undefined;
|
|
305
|
+
title: string;
|
|
306
|
+
slug: string;
|
|
307
|
+
fields: Record<string, unknown>;
|
|
308
|
+
searchText?: string | undefined;
|
|
309
|
+
position?: number | undefined;
|
|
310
|
+
status?: ContentStatus | undefined;
|
|
311
|
+
/** Whether the content type contributes its slug to descendants' permalinks. */
|
|
312
|
+
hasSlug: boolean;
|
|
313
|
+
actorId?: string | null | undefined;
|
|
314
|
+
/** Expected current version; a mismatch raises `content.version.conflict`. */
|
|
315
|
+
expectedVersion?: number | undefined;
|
|
316
|
+
}
|
|
317
|
+
interface TreeNode {
|
|
318
|
+
content: ContentRow;
|
|
319
|
+
depth: number;
|
|
320
|
+
children: TreeNode[];
|
|
321
|
+
}
|
|
322
|
+
declare class ContentRepository {
|
|
323
|
+
private readonly db;
|
|
324
|
+
private readonly registry;
|
|
325
|
+
constructor(db: Executor, registry: ContentTypeRegistry);
|
|
326
|
+
findById(id: string, published?: boolean): Promise<ContentRow | null>;
|
|
327
|
+
/**
|
|
328
|
+
* `spaceId` bounds a lookup by id to one tenant.
|
|
329
|
+
*
|
|
330
|
+
* The delivery API takes ids straight from the caller, so without it a public instance
|
|
331
|
+
* pinned to one space still answers for any other space's published documents.
|
|
332
|
+
*/
|
|
333
|
+
findManyByIds(ids: string[], published?: boolean, spaceId?: string | null): Promise<ContentRow[]>;
|
|
334
|
+
/** Children of many parents in one query, for the tree loader. */
|
|
335
|
+
findChildrenOf(parentIds: string[], published?: boolean, spaceId?: string | null): Promise<ContentRow[]>;
|
|
336
|
+
findByPermalink(spaceId: string, locale: string, permalink: string, published?: boolean): Promise<ContentRow | null>;
|
|
337
|
+
/**
|
|
338
|
+
* The document a space nominates as its home, in one locale.
|
|
339
|
+
*
|
|
340
|
+
* `settings.homeContentId` names a single row, which belongs to one locale. Every
|
|
341
|
+
* translation of that document shares its `localizationId`, so the requested locale is
|
|
342
|
+
* resolved through that rather than by pinning one row per language.
|
|
343
|
+
*/
|
|
344
|
+
findHome(spaceId: string, locale: string, published?: boolean): Promise<ContentRow | null>;
|
|
345
|
+
/**
|
|
346
|
+
* Every other row in a document's localization group — its translations.
|
|
347
|
+
*/
|
|
348
|
+
/**
|
|
349
|
+
* One document per localization group, for checking that several groups exist in a
|
|
350
|
+
* space at once — a menu's entries, say. The locale returned is whichever sorts first;
|
|
351
|
+
* a caller that needs a particular one asks `localizationSiblings` for that group.
|
|
352
|
+
*/
|
|
353
|
+
findByLocalizationIds(spaceId: string, localizationIds: string[]): Promise<ContentRow[]>;
|
|
354
|
+
localizationSiblings(spaceId: string, localizationId: string, excludeId?: string): Promise<ContentRow[]>;
|
|
355
|
+
/**
|
|
356
|
+
* Merges a few field values into rows without touching the rest of the document.
|
|
357
|
+
*
|
|
358
|
+
* Used to carry a non-localized field across a document's translations: a jsonb `||`
|
|
359
|
+
* so concurrent edits to *other* fields on those rows are not clobbered.
|
|
360
|
+
*/
|
|
361
|
+
patchFields(ids: string[], patch: Record<string, unknown>): Promise<void>;
|
|
362
|
+
list(filter: ContentFilter, pagination: Pagination, sorts?: ContentSort[], published?: boolean): Promise<Paginated<ContentRow>>;
|
|
363
|
+
/**
|
|
364
|
+
* The whole tree below `rootId` in **one** query: a GiST-indexed `path <@ root`
|
|
365
|
+
* returns every descendant, and `nlevel()` gives the depth to rebuild the hierarchy.
|
|
366
|
+
*/
|
|
367
|
+
tree(spaceId: string, locale: string, rootId?: string | null, maxDepth?: number, published?: boolean): Promise<TreeNode[]>;
|
|
368
|
+
/** Ancestors of a node, root first — read straight off the materialised path. */
|
|
369
|
+
ancestors(id: string, published?: boolean): Promise<ContentRow[]>;
|
|
370
|
+
create(data: ContentWriteData): Promise<ContentRow>;
|
|
371
|
+
update(id: string, data: ContentWriteData): Promise<ContentRow>;
|
|
372
|
+
/**
|
|
373
|
+
* Reparents a subtree with one statement. `subpath(path, nlevel(:oldPath))` is the part
|
|
374
|
+
* of each descendant's path *below* the moved node; prefixing it with the node's new
|
|
375
|
+
* path rebases the whole subtree.
|
|
376
|
+
*/
|
|
377
|
+
/**
|
|
378
|
+
* Reparents and reorders a node in one transaction.
|
|
379
|
+
*
|
|
380
|
+
* Separate from `update` because a drag is a structural change, not an edit: it writes
|
|
381
|
+
* no field values, takes no version bump and records no snapshot, so an editor open on
|
|
382
|
+
* the document does not hit a version conflict because someone reordered the tree.
|
|
383
|
+
*
|
|
384
|
+
* `position` is the index among the destination's children, clamped to the ends.
|
|
385
|
+
* Siblings on both sides are renumbered densely afterwards, so positions never drift
|
|
386
|
+
* into ties that the tree's `position asc, title asc` ordering would resolve by name.
|
|
387
|
+
*/
|
|
388
|
+
move(id: string, parentId: string | null, position: number): Promise<ContentRow>;
|
|
389
|
+
/**
|
|
390
|
+
* Writes `position = index` for a whole sibling list in one statement: the ids and
|
|
391
|
+
* their new positions travel as two arrays and are joined by `unnest`, so a drag in a
|
|
392
|
+
* forty-child section costs one round trip rather than forty. Rows already in place
|
|
393
|
+
* are left untouched, so their `updated_at` and version do not move either.
|
|
394
|
+
*/
|
|
395
|
+
private renumber;
|
|
396
|
+
private moveSubtree;
|
|
397
|
+
/**
|
|
398
|
+
* Recomputes permalinks for a node and everything beneath it in one recursive CTE.
|
|
399
|
+
* Each level derives from *its own* parent's freshly computed value (`t.pl`), and
|
|
400
|
+
* `concat_ws` drops NULL segments so a type without a slug is transparent in the path.
|
|
401
|
+
*/
|
|
402
|
+
private recomputePermalinks;
|
|
403
|
+
/** Deletes a node and its whole subtree, in both the draft and published tables. */
|
|
404
|
+
delete(id: string): Promise<number>;
|
|
405
|
+
/**
|
|
406
|
+
* Copies a draft into the delivery projection inside one transaction, so a reader
|
|
407
|
+
* never observes a partially published tree.
|
|
408
|
+
*/
|
|
409
|
+
publish(id: string, actorId?: string | null): Promise<ContentRow>;
|
|
410
|
+
unpublish(id: string): Promise<void>;
|
|
411
|
+
versions(contentId: string, limit?: number): Promise<Array<{
|
|
412
|
+
version: number;
|
|
413
|
+
createdAt: Date;
|
|
414
|
+
createdBy: string | null;
|
|
415
|
+
label: string | null;
|
|
416
|
+
}>>;
|
|
417
|
+
/** The row exactly as it was at that version — `snapshot()` stores the whole row. */
|
|
418
|
+
versionSnapshot(contentId: string, version: number): Promise<ContentRow | null>;
|
|
419
|
+
/** Every stored version of these documents, oldest first, for a space export. */
|
|
420
|
+
history(contentIds: string[]): Promise<ContentVersionRow[]>;
|
|
421
|
+
/**
|
|
422
|
+
* Puts an imported document back the way its source had it: the version counter and
|
|
423
|
+
* timestamps `create()` had to invent, and the version history in place of the single
|
|
424
|
+
* snapshot `create()` wrote. The row's own columns are left alone otherwise.
|
|
425
|
+
*/
|
|
426
|
+
restoreHistory(id: string, data: {
|
|
427
|
+
version: number;
|
|
428
|
+
createdAt: Date;
|
|
429
|
+
updatedAt: Date;
|
|
430
|
+
versions: Array<{
|
|
431
|
+
version: number;
|
|
432
|
+
label: string | null;
|
|
433
|
+
snapshot: Record<string, unknown>;
|
|
434
|
+
createdAt: Date;
|
|
435
|
+
createdBy: string | null;
|
|
436
|
+
}>;
|
|
437
|
+
}): Promise<void>;
|
|
438
|
+
private snapshot;
|
|
439
|
+
private findRow;
|
|
440
|
+
private lockRow;
|
|
441
|
+
private parentPath;
|
|
442
|
+
private parentPermalinkPath;
|
|
443
|
+
/** Guards against making a node its own ancestor, which would orphan the subtree. */
|
|
444
|
+
private assertNotOwnDescendant;
|
|
445
|
+
}
|
|
446
|
+
declare function buildTree(rows: Array<ContentRow & {
|
|
447
|
+
depth: number;
|
|
448
|
+
}>, rootId: string | null): TreeNode[];
|
|
449
|
+
//#endregion
|
|
450
|
+
//#region src/repositories/content-approval.d.ts
|
|
451
|
+
interface ApprovalRequestData {
|
|
452
|
+
spaceId: string;
|
|
453
|
+
contentId: string;
|
|
454
|
+
typeId: string;
|
|
455
|
+
requestedBy: string | null;
|
|
456
|
+
requestedByLabel: string;
|
|
457
|
+
requestNote?: string | null | undefined;
|
|
458
|
+
contentVersion?: number | null | undefined;
|
|
459
|
+
}
|
|
460
|
+
interface ApprovalDecisionData {
|
|
461
|
+
status: Exclude<ApprovalStatus, 'pending'>;
|
|
462
|
+
decidedBy: string | null;
|
|
463
|
+
decidedByLabel: string | null;
|
|
464
|
+
decisionNote?: string | null | undefined;
|
|
465
|
+
}
|
|
466
|
+
/** A pending request with the document it concerns, for the reviewers' queue. */
|
|
467
|
+
interface PendingApproval extends ContentApprovalRow {
|
|
468
|
+
content: {
|
|
469
|
+
id: string;
|
|
470
|
+
title: string;
|
|
471
|
+
locale: string;
|
|
472
|
+
typeId: string;
|
|
473
|
+
updatedAt: Date;
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* The approval history of documents. Rows are appended when someone asks and closed
|
|
478
|
+
* when someone decides; the "one pending per document" rule is the service's, this
|
|
479
|
+
* only offers the reads it needs to keep it.
|
|
480
|
+
*/
|
|
481
|
+
declare class ContentApprovalRepository {
|
|
482
|
+
private readonly db;
|
|
483
|
+
constructor(db: Database);
|
|
484
|
+
findById(id: string): Promise<ContentApprovalRow | null>;
|
|
485
|
+
/** The open request on a document, if there is one. */
|
|
486
|
+
pendingFor(contentId: string): Promise<ContentApprovalRow | null>;
|
|
487
|
+
/** The most recent request on a document, whatever became of it. */
|
|
488
|
+
latestFor(contentId: string): Promise<ContentApprovalRow | null>;
|
|
489
|
+
historyFor(contentId: string, limit?: number): Promise<ContentApprovalRow[]>;
|
|
490
|
+
/**
|
|
491
|
+
* Every open request in a space, oldest first, with the document beside it. Narrowed
|
|
492
|
+
* to `typeIds` when the reader may only publish some types; `null` means every type.
|
|
493
|
+
*/
|
|
494
|
+
pendingInSpace(spaceId: string, typeIds?: string[] | null): Promise<PendingApproval[]>;
|
|
495
|
+
request(data: ApprovalRequestData): Promise<ContentApprovalRow>;
|
|
496
|
+
decide(id: string, data: ApprovalDecisionData): Promise<ContentApprovalRow>;
|
|
497
|
+
}
|
|
498
|
+
//#endregion
|
|
499
|
+
//#region src/repositories/content-type.d.ts
|
|
500
|
+
/**
|
|
501
|
+
* Persistence for *runtime-defined* content types only. Code-defined types come from
|
|
502
|
+
* `manablox.config.ts` and are never written here — the registry merges both into one
|
|
503
|
+
* shape, and `source` tells the admin which are read-only.
|
|
504
|
+
*/
|
|
505
|
+
declare class ContentTypeRepository {
|
|
506
|
+
private readonly db;
|
|
507
|
+
constructor(db: Database);
|
|
508
|
+
all(): Promise<ContentTypeDefinition[]>;
|
|
509
|
+
findById(id: string): Promise<ContentTypeDefinition | null>;
|
|
510
|
+
create(input: ContentTypeInput): Promise<ContentTypeDefinition>;
|
|
511
|
+
update(id: string, input: ContentTypeInput): Promise<ContentTypeDefinition>;
|
|
512
|
+
delete(id: string): Promise<void>;
|
|
513
|
+
}
|
|
514
|
+
//#endregion
|
|
515
|
+
//#region src/repositories/menu.d.ts
|
|
516
|
+
interface MenuWriteData {
|
|
517
|
+
id?: string | undefined;
|
|
518
|
+
spaceId: string;
|
|
519
|
+
name: string;
|
|
520
|
+
machineName: string;
|
|
521
|
+
description?: string | null | undefined;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* One entry as the admin hands over the whole menu: a content entry names a
|
|
525
|
+
* `localizationId`, a link entry a `url`; either may carry a label. `id` is kept when
|
|
526
|
+
* given, so an unchanged entry keeps its identity across saves.
|
|
527
|
+
*/
|
|
528
|
+
interface MenuItemInput {
|
|
529
|
+
id?: string | undefined;
|
|
530
|
+
localizationId?: string | null | undefined;
|
|
531
|
+
label?: string | null | undefined;
|
|
532
|
+
url?: string | null | undefined;
|
|
533
|
+
children?: MenuItemInput[] | undefined;
|
|
534
|
+
}
|
|
535
|
+
interface MenuItemNode {
|
|
536
|
+
item: MenuItemRow;
|
|
537
|
+
children: MenuItemNode[];
|
|
538
|
+
}
|
|
539
|
+
/** An entry with its document looked up for one locale; `content` is null for a link. */
|
|
540
|
+
interface ResolvedMenuItem {
|
|
541
|
+
id: string;
|
|
542
|
+
label: string | null;
|
|
543
|
+
url: string | null;
|
|
544
|
+
localizationId: string | null;
|
|
545
|
+
content: ContentRow | null;
|
|
546
|
+
children: ResolvedMenuItem[];
|
|
547
|
+
}
|
|
548
|
+
declare class MenuRepository {
|
|
549
|
+
private readonly db;
|
|
550
|
+
constructor(db: Database);
|
|
551
|
+
listBySpace(spaceId: string): Promise<MenuRow[]>;
|
|
552
|
+
findById(id: string): Promise<MenuRow | null>;
|
|
553
|
+
findByMachineName(spaceId: string, machineName: string): Promise<MenuRow | null>;
|
|
554
|
+
create(data: MenuWriteData): Promise<MenuRow>;
|
|
555
|
+
update(id: string, data: Loose<Omit<MenuWriteData, 'spaceId'>>): Promise<MenuRow>;
|
|
556
|
+
delete(id: string): Promise<void>;
|
|
557
|
+
/** Every entry of a menu, flat, in tree order within each level. */
|
|
558
|
+
items(menuId: string): Promise<MenuItemRow[]>;
|
|
559
|
+
tree(menuId: string): Promise<MenuItemNode[]>;
|
|
560
|
+
/**
|
|
561
|
+
* Replaces the whole entry tree in one transaction.
|
|
562
|
+
*
|
|
563
|
+
* A menu is edited as one document and saved as one, so this is simpler and safer than
|
|
564
|
+
* a per-entry API whose partial failures would leave a half-reordered menu behind.
|
|
565
|
+
*/
|
|
566
|
+
setItems(menuId: string, tree: MenuItemInput[]): Promise<MenuItemNode[]>;
|
|
567
|
+
/** Menus that carry the document, for the editor's "used in" hint. */
|
|
568
|
+
menusReferencing(spaceId: string, localizationId: string): Promise<MenuRow[]>;
|
|
569
|
+
/** Drops every entry pointing at a document, in every menu; sub-entries cascade. */
|
|
570
|
+
removeContent(localizationId: string): Promise<number>;
|
|
571
|
+
/**
|
|
572
|
+
* The tree with each content entry's document for one locale. A content entry whose
|
|
573
|
+
* document has no row in that locale — or, on the published table, no published one —
|
|
574
|
+
* comes back with `content: null`; the caller decides whether to show or drop it.
|
|
575
|
+
*/
|
|
576
|
+
resolve(menu: MenuRow, locale: string, published?: boolean): Promise<ResolvedMenuItem[]>;
|
|
577
|
+
}
|
|
578
|
+
//#endregion
|
|
579
|
+
//#region src/repositories/notification.d.ts
|
|
580
|
+
interface NotificationInsert {
|
|
581
|
+
userId: string;
|
|
582
|
+
spaceId?: string | null | undefined;
|
|
583
|
+
kind: NotificationKind;
|
|
584
|
+
title: string;
|
|
585
|
+
body?: string | undefined;
|
|
586
|
+
url?: string | null | undefined;
|
|
587
|
+
targetKind?: string | null | undefined;
|
|
588
|
+
targetId?: string | null | undefined;
|
|
589
|
+
actorId?: string | null | undefined;
|
|
590
|
+
actorLabel?: string | null | undefined;
|
|
591
|
+
meta?: Record<string, unknown> | null | undefined;
|
|
592
|
+
}
|
|
593
|
+
interface NotificationFilter {
|
|
594
|
+
unreadOnly?: boolean | undefined;
|
|
595
|
+
kinds?: NotificationKind[] | undefined;
|
|
596
|
+
spaceId?: string | null | undefined;
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* One person's inbox. Every method is scoped to a `userId`, so a row id from another
|
|
600
|
+
* account is inert: there is no way to read or change what is not yours.
|
|
601
|
+
*/
|
|
602
|
+
declare class NotificationRepository {
|
|
603
|
+
private readonly db;
|
|
604
|
+
constructor(db: Database);
|
|
605
|
+
/** Inserts one row per recipient in one statement; an empty list writes nothing. */
|
|
606
|
+
insertMany(rows: NotificationInsert[]): Promise<NotificationRow[]>;
|
|
607
|
+
list(userId: string, filter?: NotificationFilter, pagination?: Pagination): Promise<Paginated<NotificationRow>>;
|
|
608
|
+
findById(userId: string, id: string): Promise<NotificationRow | null>;
|
|
609
|
+
countUnread(userId: string): Promise<number>;
|
|
610
|
+
/** Marks the named rows read; rows already read keep their original time. Returns how many changed. */
|
|
611
|
+
markRead(userId: string, ids: string[]): Promise<number>;
|
|
612
|
+
markUnread(userId: string, ids: string[]): Promise<number>;
|
|
613
|
+
markAllRead(userId: string): Promise<number>;
|
|
614
|
+
delete(userId: string, ids: string[]): Promise<number>;
|
|
615
|
+
deleteRead(userId: string): Promise<number>;
|
|
616
|
+
}
|
|
617
|
+
//#endregion
|
|
618
|
+
//#region src/repositories/role.d.ts
|
|
619
|
+
interface RoleWriteData {
|
|
620
|
+
/** Kept when given, so an import can restore a role under the id its memberships name. */
|
|
621
|
+
id?: string | undefined;
|
|
622
|
+
name: string;
|
|
623
|
+
machineName: string;
|
|
624
|
+
description?: string | null;
|
|
625
|
+
permissions: string[];
|
|
626
|
+
}
|
|
627
|
+
declare class RoleRepository {
|
|
628
|
+
private readonly db;
|
|
629
|
+
constructor(db: Database);
|
|
630
|
+
listBySpace(spaceId: string): Promise<RoleRow[]>;
|
|
631
|
+
findById(id: string): Promise<RoleRow | null>;
|
|
632
|
+
findByMachineName(spaceId: string, machineName: string): Promise<RoleRow | null>;
|
|
633
|
+
create(spaceId: string, data: RoleWriteData): Promise<RoleRow>;
|
|
634
|
+
update(id: string, data: Partial<RoleWriteData>): Promise<RoleRow>;
|
|
635
|
+
delete(id: string): Promise<void>;
|
|
636
|
+
/** How many members of the role's space hold it, by name. */
|
|
637
|
+
countMembers(spaceId: string, machineName: string): Promise<number>;
|
|
638
|
+
/**
|
|
639
|
+
* Drops every grant narrowed to a content type from every role, once the type is
|
|
640
|
+
* gone. Grants are a JSON array, so this is one statement across the roles that carry
|
|
641
|
+
* such a grant rather than a read-modify-write per role.
|
|
642
|
+
*/
|
|
643
|
+
pruneContentType(typeId: string): Promise<void>;
|
|
644
|
+
}
|
|
645
|
+
//#endregion
|
|
646
|
+
//#region src/repositories/space.d.ts
|
|
647
|
+
interface SpaceWriteData {
|
|
648
|
+
id?: string | undefined;
|
|
649
|
+
name: string;
|
|
650
|
+
machineName: string;
|
|
651
|
+
description?: string | null | undefined;
|
|
652
|
+
url: string;
|
|
653
|
+
defaultLocale?: string | undefined;
|
|
654
|
+
locales?: string[] | undefined;
|
|
655
|
+
settings?: Record<string, unknown> | undefined;
|
|
656
|
+
}
|
|
657
|
+
declare class SpaceRepository {
|
|
658
|
+
private readonly db;
|
|
659
|
+
constructor(db: Database);
|
|
660
|
+
all(): Promise<SpaceRow[]>;
|
|
661
|
+
findManyByIds(ids: string[]): Promise<SpaceRow[]>;
|
|
662
|
+
findById(id: string): Promise<SpaceRow | null>;
|
|
663
|
+
findByMachineName(machineName: string): Promise<SpaceRow | null>;
|
|
664
|
+
create(data: SpaceWriteData): Promise<SpaceRow>;
|
|
665
|
+
update(id: string, data: Loose<SpaceWriteData>): Promise<SpaceRow>;
|
|
666
|
+
delete(id: string): Promise<void>;
|
|
667
|
+
}
|
|
668
|
+
//#endregion
|
|
669
|
+
//#region src/repositories/user.d.ts
|
|
670
|
+
/** The name of a role in a space: one of the built-in five, or a row in `roles`. */
|
|
671
|
+
type SpaceRole = string;
|
|
672
|
+
/** What it takes to create an account that can sign in with a password. */
|
|
673
|
+
interface UserCreateData {
|
|
674
|
+
name: string;
|
|
675
|
+
email: string;
|
|
676
|
+
role: string;
|
|
677
|
+
/** Already hashed; the repository never sees a plaintext password. */
|
|
678
|
+
passwordHash: string;
|
|
679
|
+
}
|
|
680
|
+
interface UserUpdateData {
|
|
681
|
+
name?: string;
|
|
682
|
+
email?: string;
|
|
683
|
+
}
|
|
684
|
+
declare class UserRepository {
|
|
685
|
+
private readonly db;
|
|
686
|
+
constructor(db: Database);
|
|
687
|
+
findById(id: string): Promise<UserRow | null>;
|
|
688
|
+
findManyByIds(ids: string[]): Promise<UserRow[]>;
|
|
689
|
+
/** Every account holding an instance role, for a fan-out to the superadmins. */
|
|
690
|
+
findByRole(role: string): Promise<UserRow[]>;
|
|
691
|
+
findByEmail(email: string): Promise<UserRow | null>;
|
|
692
|
+
list(pagination: Pagination, search?: string): Promise<Paginated<UserRow>>;
|
|
693
|
+
/**
|
|
694
|
+
* Users who are not members of a space, matching a search, newest first: the
|
|
695
|
+
* add-member picker's candidates, decided in SQL rather than by loading a page of
|
|
696
|
+
* users and filtering it here.
|
|
697
|
+
*/
|
|
698
|
+
candidates(spaceId: string, search: string | undefined, limit: number): Promise<UserRow[]>;
|
|
699
|
+
count(): Promise<number>;
|
|
700
|
+
/**
|
|
701
|
+
* Inserts the user and its password credential together, so a failure on the second
|
|
702
|
+
* row cannot leave an account nobody can sign in to. The account row is shaped the way
|
|
703
|
+
* better-auth writes it on sign-up, so a sign-in later finds it as its own.
|
|
704
|
+
*/
|
|
705
|
+
create(data: UserCreateData): Promise<UserRow>;
|
|
706
|
+
update(id: string, data: UserUpdateData): Promise<UserRow>;
|
|
707
|
+
delete(id: string): Promise<void>;
|
|
708
|
+
setBanned(id: string, banned: boolean, reason: string | null): Promise<UserRow>;
|
|
709
|
+
/**
|
|
710
|
+
* Replaces the password credential, creating it for an account that only ever signed
|
|
711
|
+
* in through another provider.
|
|
712
|
+
*/
|
|
713
|
+
setPasswordHash(userId: string, passwordHash: string): Promise<void>;
|
|
714
|
+
/** Signs the user out everywhere. */
|
|
715
|
+
revokeSessions(userId: string): Promise<void>;
|
|
716
|
+
countByRole(role: string): Promise<number>;
|
|
717
|
+
setNotificationPreferences(id: string, preferences: NotificationPreferences): Promise<UserRow>;
|
|
718
|
+
/** The stored hash of the password credential, or `null` for an account without one. */
|
|
719
|
+
passwordHash(userId: string): Promise<string | null>;
|
|
720
|
+
setRole(id: string, role: string): Promise<UserRow>;
|
|
721
|
+
/**
|
|
722
|
+
* Authoritative role plus space memberships in one query.
|
|
723
|
+
*
|
|
724
|
+
* Read on every authenticated request rather than trusting the role embedded in the
|
|
725
|
+
* session: better-auth caches the session payload (five minutes by default), so a
|
|
726
|
+
* promotion or demotion would otherwise not take effect until that cache expired.
|
|
727
|
+
*
|
|
728
|
+
* A membership naming a custom role joins that role's grants; one naming a built-in
|
|
729
|
+
* role has none here, and the auth package answers those from its own table.
|
|
730
|
+
*/
|
|
731
|
+
principal(userId: string): Promise<{
|
|
732
|
+
role: string;
|
|
733
|
+
banned: boolean;
|
|
734
|
+
spaces: Record<string, SpaceRole>;
|
|
735
|
+
permissions: Record<string, string[]>;
|
|
736
|
+
} | null>;
|
|
737
|
+
memberships(userId: string): Promise<MembershipRow[]>;
|
|
738
|
+
/** The user's memberships with the space each one is in, for a per-user view. */
|
|
739
|
+
membershipsWithSpaces(userId: string): Promise<Array<MembershipRow & {
|
|
740
|
+
space: SpaceRow;
|
|
741
|
+
}>>;
|
|
742
|
+
membersOf(spaceId: string): Promise<Array<MembershipRow & {
|
|
743
|
+
user: UserRow;
|
|
744
|
+
}>>;
|
|
745
|
+
roleIn(userId: string, spaceId: string): Promise<SpaceRole | null>;
|
|
746
|
+
grant(userId: string, spaceId: string, role: SpaceRole): Promise<void>;
|
|
747
|
+
revoke(userId: string, spaceId: string): Promise<void>;
|
|
748
|
+
}
|
|
749
|
+
//#endregion
|
|
750
|
+
//#region src/repositories/webhook.d.ts
|
|
751
|
+
type WebhookRow = typeof webhooks.$inferSelect;
|
|
752
|
+
type WebhookDeliveryRow = typeof webhookDeliveries.$inferSelect;
|
|
753
|
+
interface WebhookWriteData {
|
|
754
|
+
id?: string | undefined;
|
|
755
|
+
spaceId: string;
|
|
756
|
+
name: string;
|
|
757
|
+
url: string;
|
|
758
|
+
secret?: string | null | undefined;
|
|
759
|
+
events: string[];
|
|
760
|
+
enabled?: boolean | undefined;
|
|
761
|
+
}
|
|
762
|
+
interface WebhookDeliveryData {
|
|
763
|
+
webhookId: string;
|
|
764
|
+
event: string;
|
|
765
|
+
payload: Record<string, unknown>;
|
|
766
|
+
status: number | null;
|
|
767
|
+
error: string | null;
|
|
768
|
+
}
|
|
769
|
+
/** The webhooks of a space and the log of what was sent to them. */
|
|
770
|
+
declare class WebhookRepository {
|
|
771
|
+
private readonly db;
|
|
772
|
+
constructor(db: Database);
|
|
773
|
+
findById(id: string): Promise<WebhookRow | null>;
|
|
774
|
+
listBySpace(spaceId: string): Promise<WebhookRow[]>;
|
|
775
|
+
create(data: WebhookWriteData): Promise<WebhookRow>;
|
|
776
|
+
/** The switched-on webhooks of a space, for fanning an event out. */
|
|
777
|
+
findEnabled(spaceId: string): Promise<WebhookRow[]>;
|
|
778
|
+
recordDelivery(data: WebhookDeliveryData): Promise<WebhookDeliveryRow>;
|
|
779
|
+
deliveries(webhookId: string, limit?: number): Promise<WebhookDeliveryRow[]>;
|
|
780
|
+
}
|
|
781
|
+
//#endregion
|
|
782
|
+
//#region src/repositories/workflow.d.ts
|
|
783
|
+
interface WorkflowWriteData {
|
|
784
|
+
id?: string | undefined;
|
|
785
|
+
spaceId: string;
|
|
786
|
+
name: string;
|
|
787
|
+
description?: string | null | undefined;
|
|
788
|
+
enabled?: boolean | undefined;
|
|
789
|
+
trigger: WorkflowTrigger;
|
|
790
|
+
steps: WorkflowStep[];
|
|
791
|
+
}
|
|
792
|
+
interface WorkflowRunCreateData {
|
|
793
|
+
workflowId: string;
|
|
794
|
+
spaceId: string;
|
|
795
|
+
trigger: string;
|
|
796
|
+
context: WorkflowRunContext;
|
|
797
|
+
}
|
|
798
|
+
/** How many runs a workflow keeps; older ones are pruned as new ones are written. */
|
|
799
|
+
declare const RUNS_KEPT_PER_WORKFLOW = 200;
|
|
800
|
+
declare class WorkflowRepository {
|
|
801
|
+
private readonly db;
|
|
802
|
+
constructor(db: Database);
|
|
803
|
+
listBySpace(spaceId: string): Promise<WorkflowRow[]>;
|
|
804
|
+
/** Every enabled workflow of a space, for the dispatcher; every enabled one at all for the scheduler. */
|
|
805
|
+
listEnabled(spaceId?: string): Promise<WorkflowRow[]>;
|
|
806
|
+
findById(id: string): Promise<WorkflowRow | null>;
|
|
807
|
+
create(data: WorkflowWriteData): Promise<WorkflowRow>;
|
|
808
|
+
update(id: string, data: Loose<Omit<WorkflowWriteData, 'spaceId'>>): Promise<WorkflowRow>;
|
|
809
|
+
delete(id: string): Promise<void>;
|
|
810
|
+
/**
|
|
811
|
+
* Claims a scheduled workflow for one minute. Returns false when another process got
|
|
812
|
+
* there first — the update matches nothing once `lastScheduledAt` is already `minute`.
|
|
813
|
+
*/
|
|
814
|
+
claimSchedule(id: string, minute: Date): Promise<boolean>;
|
|
815
|
+
touchRun(id: string, at: Date): Promise<void>;
|
|
816
|
+
createRun(data: WorkflowRunCreateData): Promise<WorkflowRunRow>;
|
|
817
|
+
findRun(id: string): Promise<WorkflowRunRow | null>;
|
|
818
|
+
listRuns(workflowId: string, limit?: number): Promise<WorkflowRunRow[]>;
|
|
819
|
+
/**
|
|
820
|
+
* Moves a run from `queued` or `waiting` to `running`, or reports that it is not
|
|
821
|
+
* there to be moved. The status check in the predicate is what keeps two workers off
|
|
822
|
+
* the same run.
|
|
823
|
+
*/
|
|
824
|
+
claimRun(id: string): Promise<WorkflowRunRow | null>;
|
|
825
|
+
/** Runs paused by a delay step whose time has come. */
|
|
826
|
+
dueRuns(now: Date, limit?: number): Promise<WorkflowRunRow[]>;
|
|
827
|
+
saveRunProgress(id: string, data: {
|
|
828
|
+
status: WorkflowRunStatus;
|
|
829
|
+
cursor: WorkflowCursor;
|
|
830
|
+
log: WorkflowStepLog[];
|
|
831
|
+
error?: string | null | undefined;
|
|
832
|
+
resumeAt?: Date | null | undefined;
|
|
833
|
+
finished?: boolean | undefined;
|
|
834
|
+
}): Promise<void>;
|
|
835
|
+
private pruneRuns;
|
|
836
|
+
/** The documents a scheduled workflow's selection names, newest change first. */
|
|
837
|
+
selectDocuments(spaceId: string, selection: WorkflowSelection, limit?: number): Promise<ContentRow[]>;
|
|
838
|
+
subscriptionsFor(userIds: string[]): Promise<PushSubscriptionRow[]>;
|
|
839
|
+
subscriptionsOf(userId: string): Promise<PushSubscriptionRow[]>;
|
|
840
|
+
/** Upserts on the endpoint: a browser re-subscribing keeps one row, not two. */
|
|
841
|
+
subscribe(data: {
|
|
842
|
+
userId: string;
|
|
843
|
+
endpoint: string;
|
|
844
|
+
keys: {
|
|
845
|
+
p256dh: string;
|
|
846
|
+
auth: string;
|
|
847
|
+
};
|
|
848
|
+
userAgent: string | null;
|
|
849
|
+
}): Promise<PushSubscriptionRow>;
|
|
850
|
+
unsubscribe(userId: string, endpoint: string): Promise<void>;
|
|
851
|
+
/** A push service answered 404/410: the browser is gone, and so is the row. */
|
|
852
|
+
dropSubscription(id: string): Promise<void>;
|
|
853
|
+
markSubscriptionUsed(id: string): Promise<void>;
|
|
854
|
+
}
|
|
855
|
+
//#endregion
|
|
856
|
+
//#region src/repositories/index.d.ts
|
|
857
|
+
interface Repositories {
|
|
858
|
+
content: ContentRepository;
|
|
859
|
+
contentTypes: ContentTypeRepository;
|
|
860
|
+
spaces: SpaceRepository;
|
|
861
|
+
assets: AssetRepository;
|
|
862
|
+
assetUsages: AssetUsageRepository;
|
|
863
|
+
users: UserRepository;
|
|
864
|
+
menus: MenuRepository;
|
|
865
|
+
roles: RoleRepository;
|
|
866
|
+
workflows: WorkflowRepository;
|
|
867
|
+
webhooks: WebhookRepository;
|
|
868
|
+
audit: AuditRepository;
|
|
869
|
+
notifications: NotificationRepository;
|
|
870
|
+
approvals: ContentApprovalRepository;
|
|
871
|
+
}
|
|
872
|
+
interface RepositoriesOptions {
|
|
873
|
+
audit?: AuditRepositoryOptions | undefined;
|
|
874
|
+
}
|
|
875
|
+
declare function createRepositories(db: Database, registry: ContentTypeRegistry, options?: RepositoriesOptions): Repositories;
|
|
876
|
+
//#endregion
|
|
877
|
+
export { Pagination as $, ContentApprovalRepository as A, AuditRepositoryOptions as B, MenuItemNode as C, ContentTypeRepository as D, ResolvedMenuItem as E, TreeNode as F, AssetRepository as G, AuditVerification as H, buildTree as I, paginate as J, AssetWriteData as K, AuditAppendInput as L, ContentRepository as M, ContentWriteData as N, ApprovalDecisionData as O, SQL$1 as P, FieldFilter as Q, AuditFilter as R, MenuItemInput as S, MenuWriteData as T, AssetUsageRepository as U, AuditSort as V, AssetFilter as W, ContentSort as X, ContentFilter as Y, ContentTable as Z, SpaceRepository as _, WorkflowRepository as a, DatabaseOptions as at, NotificationInsert as b, WebhookDeliveryData as c, Transaction as ct, WebhookRow as d, PgColumn$1 as et, WebhookWriteData as f, UserUpdateData as g, UserRepository as h, RUNS_KEPT_PER_WORKFLOW as i, DatabaseHandle as it, PendingApproval as j, ApprovalRequestData as k, WebhookDeliveryRow as l, createDatabase as lt, UserCreateData as m, RepositoriesOptions as n, buildOrderBy as nt, WorkflowRunCreateData as o, Executor as ot, SpaceRole as p, Paginated as q, createRepositories as r, Database as rt, WorkflowWriteData as s, Sql as st, Repositories as t, buildContentWhere as tt, WebhookRepository as u, SpaceWriteData as v, MenuRepository as w, NotificationRepository as x, NotificationFilter as y, AuditRepository as z };
|