@manablox/db 0.2.0 → 0.3.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.
Files changed (56) hide show
  1. package/dist/index-Cyf_N5K3.d.ts +658 -0
  2. package/dist/index-rZ24t-Ln.d.ts +4338 -0
  3. package/dist/index.d.ts +123 -0
  4. package/dist/index.js +60 -0
  5. package/dist/repositories-DYjzuuF6.js +1533 -0
  6. package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
  7. package/dist/schema-Bb4p16Yz.js +539 -0
  8. package/dist/schema.d.ts +2 -0
  9. package/dist/schema.js +2 -0
  10. package/dist/testing.d.ts +77 -0
  11. package/dist/testing.js +217 -0
  12. package/package.json +18 -10
  13. package/drizzle.config.ts +0 -11
  14. package/src/bootstrap.ts +0 -13
  15. package/src/cli/create-db.ts +0 -30
  16. package/src/cli/migrate.ts +0 -17
  17. package/src/client.ts +0 -44
  18. package/src/columns.ts +0 -39
  19. package/src/errors.ts +0 -50
  20. package/src/index.ts +0 -19
  21. package/src/migrate.ts +0 -21
  22. package/src/pagination.ts +0 -52
  23. package/src/query.ts +0 -213
  24. package/src/repositories/asset-usage.ts +0 -166
  25. package/src/repositories/asset.ts +0 -181
  26. package/src/repositories/content-type.ts +0 -116
  27. package/src/repositories/content.ts +0 -811
  28. package/src/repositories/index.ts +0 -40
  29. package/src/repositories/menu.ts +0 -235
  30. package/src/repositories/role.ts +0 -85
  31. package/src/repositories/space.ts +0 -83
  32. package/src/repositories/user.ts +0 -280
  33. package/src/repositories/webhook.ts +0 -46
  34. package/src/repositories/workflow.ts +0 -306
  35. package/src/schema/assets.ts +0 -108
  36. package/src/schema/auth.ts +0 -166
  37. package/src/schema/content-types.ts +0 -31
  38. package/src/schema/content.ts +0 -133
  39. package/src/schema/index.ts +0 -38
  40. package/src/schema/menus.ts +0 -61
  41. package/src/schema/relations.ts +0 -64
  42. package/src/schema/spaces.ts +0 -20
  43. package/src/schema/webhooks.ts +0 -46
  44. package/src/schema/workflows.ts +0 -92
  45. package/src/testing-fixtures.ts +0 -139
  46. package/src/testing.ts +0 -105
  47. package/test/asset-usage.test.ts +0 -101
  48. package/test/menu.test.ts +0 -126
  49. package/test/publish.test.ts +0 -130
  50. package/test/query.test.ts +0 -170
  51. package/test/role.test.ts +0 -81
  52. package/test/tree.test.ts +0 -188
  53. package/test/user.test.ts +0 -126
  54. package/test/webhook.test.ts +0 -48
  55. package/tsconfig.json +0 -4
  56. package/vitest.config.ts +0 -10
@@ -0,0 +1,658 @@
1
+ import { R as contents, S as webhooks, _ as index_d_exports, a as ContentRow, c as MembershipRow, d as PushSubscriptionRow, f as RoleRow, g as WorkflowRunRow, h as WorkflowRow, l as MenuItemRow, m as UserRow, p as SpaceRow, r as AssetVariantRow, t as AssetRow, u as MenuRow, x as webhookDeliveries, z as publishedContents } from "./index-rZ24t-Ln.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 { ContentStatus, ContentTypeDefinition, ContentTypeInput, ContentTypeRegistry, DatabaseConfig, FilterOperator, Loose, 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
+ variants(assetIds: string[]): Promise<AssetVariantRow[]>;
142
+ findVariant(assetId: string, preset: string, format: string): Promise<AssetVariantRow | null>;
143
+ /** Drops every variant row; the caller removes the files. */
144
+ deleteVariants(assetId: string): Promise<void>;
145
+ addVariant(data: {
146
+ assetId: string;
147
+ preset: string;
148
+ format: string;
149
+ key: string;
150
+ width?: number | null;
151
+ height?: number | null;
152
+ size: number;
153
+ }): Promise<AssetVariantRow>;
154
+ }
155
+ //#endregion
156
+ //#region src/repositories/asset-usage.d.ts
157
+ /**
158
+ * The asset → document reachability index.
159
+ *
160
+ * `published` tracks the *published projection*, not the draft: a draft that adds an
161
+ * image does not make that image public, and a draft that removes one does not make it
162
+ * private until the change is published. Every method below preserves that distinction,
163
+ * which is why the column exists rather than the table simply holding published rows.
164
+ */
165
+ declare class AssetUsageRepository {
166
+ private readonly db;
167
+ constructor(db: Database);
168
+ /** The subset of `assetIds` reachable from at least one published document. */
169
+ filterPublished(assetIds: string[]): Promise<Set<string>>;
170
+ forContent(contentId: string): Promise<Array<{
171
+ assetId: string;
172
+ published: boolean;
173
+ }>>;
174
+ /**
175
+ * Records what a *draft* references.
176
+ *
177
+ * Rows the draft dropped are removed only if they are not currently published —
178
+ * otherwise editing a draft would silently revoke access to an image the live page is
179
+ * still showing.
180
+ */
181
+ recordDraft(contentId: string, spaceId: string, assetIds: string[]): Promise<void>;
182
+ /**
183
+ * Records what the published projection references.
184
+ *
185
+ * Assets the new revision no longer uses lose their published flag but keep their row
186
+ * when the draft still references them, so the admin's "where is this used" view stays
187
+ * complete.
188
+ */
189
+ recordPublished(contentId: string, spaceId: string, assetIds: string[]): Promise<void>;
190
+ /** Unpublishing revokes every asset this document was keeping public. */
191
+ clearPublished(contentId: string): Promise<void>;
192
+ deleteForContent(contentId: string): Promise<void>;
193
+ count(): Promise<number>;
194
+ /**
195
+ * Every document with its draft and published field values, for the backfill.
196
+ *
197
+ * References are derived from field-type definitions rather than stored, so the
198
+ * backfill cannot be a SQL migration — it has to run inside the application.
199
+ */
200
+ backfillSource(): Promise<Array<{
201
+ id: string;
202
+ spaceId: string;
203
+ typeId: string;
204
+ draftFields: Record<string, unknown>;
205
+ publishedFields: Record<string, unknown> | null;
206
+ }>>;
207
+ }
208
+ //#endregion
209
+ //#region src/repositories/content.d.ts
210
+ interface ContentWriteData {
211
+ id?: string | undefined;
212
+ spaceId: string;
213
+ typeId: string;
214
+ locale: string;
215
+ localizationId?: string | undefined;
216
+ parentId?: string | null | undefined;
217
+ title: string;
218
+ slug: string;
219
+ fields: Record<string, unknown>;
220
+ searchText?: string | undefined;
221
+ position?: number | undefined;
222
+ status?: ContentStatus | undefined;
223
+ /** Whether the content type contributes its slug to descendants' permalinks. */
224
+ hasSlug: boolean;
225
+ actorId?: string | null | undefined;
226
+ /** Expected current version; a mismatch raises `content.version.conflict`. */
227
+ expectedVersion?: number | undefined;
228
+ }
229
+ interface TreeNode {
230
+ content: ContentRow;
231
+ depth: number;
232
+ children: TreeNode[];
233
+ }
234
+ declare class ContentRepository {
235
+ private readonly db;
236
+ private readonly registry;
237
+ constructor(db: Executor, registry: ContentTypeRegistry);
238
+ findById(id: string, published?: boolean): Promise<ContentRow | null>;
239
+ /**
240
+ * `spaceId` bounds a lookup by id to one tenant.
241
+ *
242
+ * The delivery API takes ids straight from the caller, so without it a public instance
243
+ * pinned to one space still answers for any other space's published documents.
244
+ */
245
+ findManyByIds(ids: string[], published?: boolean, spaceId?: string | null): Promise<ContentRow[]>;
246
+ /** Children of many parents in one query, for the tree loader. */
247
+ findChildrenOf(parentIds: string[], published?: boolean, spaceId?: string | null): Promise<ContentRow[]>;
248
+ findByPermalink(spaceId: string, locale: string, permalink: string, published?: boolean): Promise<ContentRow | null>;
249
+ /**
250
+ * The document a space nominates as its home, in one locale.
251
+ *
252
+ * `settings.homeContentId` names a single row, which belongs to one locale. Every
253
+ * translation of that document shares its `localizationId`, so the requested locale is
254
+ * resolved through that rather than by pinning one row per language.
255
+ */
256
+ findHome(spaceId: string, locale: string, published?: boolean): Promise<ContentRow | null>;
257
+ /**
258
+ * Every other row in a document's localization group — its translations.
259
+ */
260
+ /**
261
+ * One document per localization group, for checking that several groups exist in a
262
+ * space at once — a menu's entries, say. The locale returned is whichever sorts first;
263
+ * a caller that needs a particular one asks `localizationSiblings` for that group.
264
+ */
265
+ findByLocalizationIds(spaceId: string, localizationIds: string[]): Promise<ContentRow[]>;
266
+ localizationSiblings(spaceId: string, localizationId: string, excludeId?: string): Promise<ContentRow[]>;
267
+ /**
268
+ * Merges a few field values into rows without touching the rest of the document.
269
+ *
270
+ * Used to carry a non-localized field across a document's translations: a jsonb `||`
271
+ * so concurrent edits to *other* fields on those rows are not clobbered.
272
+ */
273
+ patchFields(ids: string[], patch: Record<string, unknown>): Promise<void>;
274
+ list(filter: ContentFilter, pagination: Pagination, sorts?: ContentSort[], published?: boolean): Promise<Paginated<ContentRow>>;
275
+ /**
276
+ * The whole tree below `rootId` in **one** query: a GiST-indexed `path <@ root`
277
+ * returns every descendant, and `nlevel()` gives the depth to rebuild the hierarchy.
278
+ */
279
+ tree(spaceId: string, locale: string, rootId?: string | null, maxDepth?: number, published?: boolean): Promise<TreeNode[]>;
280
+ /** Ancestors of a node, root first — read straight off the materialised path. */
281
+ ancestors(id: string, published?: boolean): Promise<ContentRow[]>;
282
+ create(data: ContentWriteData): Promise<ContentRow>;
283
+ update(id: string, data: ContentWriteData): Promise<ContentRow>;
284
+ /**
285
+ * Reparents a subtree with one statement. `subpath(path, nlevel(:oldPath))` is the part
286
+ * of each descendant's path *below* the moved node; prefixing it with the node's new
287
+ * path rebases the whole subtree.
288
+ */
289
+ /**
290
+ * Reparents and reorders a node in one transaction.
291
+ *
292
+ * Separate from `update` because a drag is a structural change, not an edit: it writes
293
+ * no field values, takes no version bump and records no snapshot, so an editor open on
294
+ * the document does not hit a version conflict because someone reordered the tree.
295
+ *
296
+ * `position` is the index among the destination's children, clamped to the ends.
297
+ * Siblings on both sides are renumbered densely afterwards, so positions never drift
298
+ * into ties that the tree's `position asc, title asc` ordering would resolve by name.
299
+ */
300
+ move(id: string, parentId: string | null, position: number): Promise<ContentRow>;
301
+ /**
302
+ * Writes `position = index` for a whole sibling list in one statement: the ids and
303
+ * their new positions travel as two arrays and are joined by `unnest`, so a drag in a
304
+ * forty-child section costs one round trip rather than forty. Rows already in place
305
+ * are left untouched, so their `updated_at` and version do not move either.
306
+ */
307
+ private renumber;
308
+ private moveSubtree;
309
+ /**
310
+ * Recomputes permalinks for a node and everything beneath it in one recursive CTE.
311
+ * Each level derives from *its own* parent's freshly computed value (`t.pl`), and
312
+ * `concat_ws` drops NULL segments so a type without a slug is transparent in the path.
313
+ */
314
+ private recomputePermalinks;
315
+ /** Deletes a node and its whole subtree, in both the draft and published tables. */
316
+ delete(id: string): Promise<number>;
317
+ /**
318
+ * Copies a draft into the delivery projection inside one transaction, so a reader
319
+ * never observes a partially published tree.
320
+ */
321
+ publish(id: string, actorId?: string | null): Promise<ContentRow>;
322
+ unpublish(id: string): Promise<void>;
323
+ versions(contentId: string, limit?: number): Promise<Array<{
324
+ version: number;
325
+ createdAt: Date;
326
+ createdBy: string | null;
327
+ label: string | null;
328
+ }>>;
329
+ /** The row exactly as it was at that version — `snapshot()` stores the whole row. */
330
+ versionSnapshot(contentId: string, version: number): Promise<ContentRow | null>;
331
+ private snapshot;
332
+ private findRow;
333
+ private lockRow;
334
+ private parentPath;
335
+ private parentPermalinkPath;
336
+ /** Guards against making a node its own ancestor, which would orphan the subtree. */
337
+ private assertNotOwnDescendant;
338
+ }
339
+ declare function buildTree(rows: Array<ContentRow & {
340
+ depth: number;
341
+ }>, rootId: string | null): TreeNode[];
342
+ //#endregion
343
+ //#region src/repositories/content-type.d.ts
344
+ /**
345
+ * Persistence for *runtime-defined* content types only. Code-defined types come from
346
+ * `manablox.config.ts` and are never written here — the registry merges both into one
347
+ * shape, and `source` tells the admin which are read-only.
348
+ */
349
+ declare class ContentTypeRepository {
350
+ private readonly db;
351
+ constructor(db: Database);
352
+ all(): Promise<ContentTypeDefinition[]>;
353
+ findById(id: string): Promise<ContentTypeDefinition | null>;
354
+ create(input: ContentTypeInput): Promise<ContentTypeDefinition>;
355
+ update(id: string, input: ContentTypeInput): Promise<ContentTypeDefinition>;
356
+ delete(id: string): Promise<void>;
357
+ }
358
+ //#endregion
359
+ //#region src/repositories/menu.d.ts
360
+ interface MenuWriteData {
361
+ id?: string | undefined;
362
+ spaceId: string;
363
+ name: string;
364
+ machineName: string;
365
+ description?: string | null | undefined;
366
+ }
367
+ /**
368
+ * One entry as the admin hands over the whole menu: a content entry names a
369
+ * `localizationId`, a link entry a `url`; either may carry a label. `id` is kept when
370
+ * given, so an unchanged entry keeps its identity across saves.
371
+ */
372
+ interface MenuItemInput {
373
+ id?: string | undefined;
374
+ localizationId?: string | null | undefined;
375
+ label?: string | null | undefined;
376
+ url?: string | null | undefined;
377
+ children?: MenuItemInput[] | undefined;
378
+ }
379
+ interface MenuItemNode {
380
+ item: MenuItemRow;
381
+ children: MenuItemNode[];
382
+ }
383
+ /** An entry with its document looked up for one locale; `content` is null for a link. */
384
+ interface ResolvedMenuItem {
385
+ id: string;
386
+ label: string | null;
387
+ url: string | null;
388
+ localizationId: string | null;
389
+ content: ContentRow | null;
390
+ children: ResolvedMenuItem[];
391
+ }
392
+ declare class MenuRepository {
393
+ private readonly db;
394
+ constructor(db: Database);
395
+ listBySpace(spaceId: string): Promise<MenuRow[]>;
396
+ findById(id: string): Promise<MenuRow | null>;
397
+ findByMachineName(spaceId: string, machineName: string): Promise<MenuRow | null>;
398
+ create(data: MenuWriteData): Promise<MenuRow>;
399
+ update(id: string, data: Loose<Omit<MenuWriteData, 'spaceId'>>): Promise<MenuRow>;
400
+ delete(id: string): Promise<void>;
401
+ /** Every entry of a menu, flat, in tree order within each level. */
402
+ items(menuId: string): Promise<MenuItemRow[]>;
403
+ tree(menuId: string): Promise<MenuItemNode[]>;
404
+ /**
405
+ * Replaces the whole entry tree in one transaction.
406
+ *
407
+ * A menu is edited as one document and saved as one, so this is simpler and safer than
408
+ * a per-entry API whose partial failures would leave a half-reordered menu behind.
409
+ */
410
+ setItems(menuId: string, tree: MenuItemInput[]): Promise<MenuItemNode[]>;
411
+ /** Menus that carry the document, for the editor's "used in" hint. */
412
+ menusReferencing(spaceId: string, localizationId: string): Promise<MenuRow[]>;
413
+ /** Drops every entry pointing at a document, in every menu; sub-entries cascade. */
414
+ removeContent(localizationId: string): Promise<number>;
415
+ /**
416
+ * The tree with each content entry's document for one locale. A content entry whose
417
+ * document has no row in that locale — or, on the published table, no published one —
418
+ * comes back with `content: null`; the caller decides whether to show or drop it.
419
+ */
420
+ resolve(menu: MenuRow, locale: string, published?: boolean): Promise<ResolvedMenuItem[]>;
421
+ }
422
+ //#endregion
423
+ //#region src/repositories/role.d.ts
424
+ interface RoleWriteData {
425
+ name: string;
426
+ machineName: string;
427
+ description?: string | null;
428
+ permissions: string[];
429
+ }
430
+ declare class RoleRepository {
431
+ private readonly db;
432
+ constructor(db: Database);
433
+ listBySpace(spaceId: string): Promise<RoleRow[]>;
434
+ findById(id: string): Promise<RoleRow | null>;
435
+ findByMachineName(spaceId: string, machineName: string): Promise<RoleRow | null>;
436
+ create(spaceId: string, data: RoleWriteData): Promise<RoleRow>;
437
+ update(id: string, data: Partial<RoleWriteData>): Promise<RoleRow>;
438
+ delete(id: string): Promise<void>;
439
+ /** How many members of the role's space hold it, by name. */
440
+ countMembers(spaceId: string, machineName: string): Promise<number>;
441
+ /**
442
+ * Drops every grant narrowed to a content type from every role, once the type is
443
+ * gone. Grants are a JSON array, so this is one statement across the roles that carry
444
+ * such a grant rather than a read-modify-write per role.
445
+ */
446
+ pruneContentType(typeId: string): Promise<void>;
447
+ }
448
+ //#endregion
449
+ //#region src/repositories/space.d.ts
450
+ interface SpaceWriteData {
451
+ id?: string | undefined;
452
+ name: string;
453
+ machineName: string;
454
+ description?: string | null | undefined;
455
+ url: string;
456
+ defaultLocale?: string | undefined;
457
+ locales?: string[] | undefined;
458
+ settings?: Record<string, unknown> | undefined;
459
+ }
460
+ declare class SpaceRepository {
461
+ private readonly db;
462
+ constructor(db: Database);
463
+ all(): Promise<SpaceRow[]>;
464
+ findManyByIds(ids: string[]): Promise<SpaceRow[]>;
465
+ findById(id: string): Promise<SpaceRow | null>;
466
+ findByMachineName(machineName: string): Promise<SpaceRow | null>;
467
+ create(data: SpaceWriteData): Promise<SpaceRow>;
468
+ update(id: string, data: Loose<SpaceWriteData>): Promise<SpaceRow>;
469
+ delete(id: string): Promise<void>;
470
+ }
471
+ //#endregion
472
+ //#region src/repositories/user.d.ts
473
+ /** The name of a role in a space: one of the built-in five, or a row in `roles`. */
474
+ type SpaceRole = string;
475
+ /** What it takes to create an account that can sign in with a password. */
476
+ interface UserCreateData {
477
+ name: string;
478
+ email: string;
479
+ role: string;
480
+ /** Already hashed; the repository never sees a plaintext password. */
481
+ passwordHash: string;
482
+ }
483
+ interface UserUpdateData {
484
+ name?: string;
485
+ email?: string;
486
+ }
487
+ declare class UserRepository {
488
+ private readonly db;
489
+ constructor(db: Database);
490
+ findById(id: string): Promise<UserRow | null>;
491
+ findManyByIds(ids: string[]): Promise<UserRow[]>;
492
+ findByEmail(email: string): Promise<UserRow | null>;
493
+ list(pagination: Pagination, search?: string): Promise<Paginated<UserRow>>;
494
+ /**
495
+ * Users who are not members of a space, matching a search, newest first: the
496
+ * add-member picker's candidates, decided in SQL rather than by loading a page of
497
+ * users and filtering it here.
498
+ */
499
+ candidates(spaceId: string, search: string | undefined, limit: number): Promise<UserRow[]>;
500
+ count(): Promise<number>;
501
+ /**
502
+ * Inserts the user and its password credential together, so a failure on the second
503
+ * row cannot leave an account nobody can sign in to. The account row is shaped the way
504
+ * better-auth writes it on sign-up, so a sign-in later finds it as its own.
505
+ */
506
+ create(data: UserCreateData): Promise<UserRow>;
507
+ update(id: string, data: UserUpdateData): Promise<UserRow>;
508
+ delete(id: string): Promise<void>;
509
+ setBanned(id: string, banned: boolean, reason: string | null): Promise<UserRow>;
510
+ /**
511
+ * Replaces the password credential, creating it for an account that only ever signed
512
+ * in through another provider.
513
+ */
514
+ setPasswordHash(userId: string, passwordHash: string): Promise<void>;
515
+ /** Signs the user out everywhere. */
516
+ revokeSessions(userId: string): Promise<void>;
517
+ countByRole(role: string): Promise<number>;
518
+ setRole(id: string, role: string): Promise<UserRow>;
519
+ /**
520
+ * Authoritative role plus space memberships in one query.
521
+ *
522
+ * Read on every authenticated request rather than trusting the role embedded in the
523
+ * session: better-auth caches the session payload (five minutes by default), so a
524
+ * promotion or demotion would otherwise not take effect until that cache expired.
525
+ *
526
+ * A membership naming a custom role joins that role's grants; one naming a built-in
527
+ * role has none here, and the auth package answers those from its own table.
528
+ */
529
+ principal(userId: string): Promise<{
530
+ role: string;
531
+ banned: boolean;
532
+ spaces: Record<string, SpaceRole>;
533
+ permissions: Record<string, string[]>;
534
+ } | null>;
535
+ memberships(userId: string): Promise<MembershipRow[]>;
536
+ /** The user's memberships with the space each one is in, for a per-user view. */
537
+ membershipsWithSpaces(userId: string): Promise<Array<MembershipRow & {
538
+ space: SpaceRow;
539
+ }>>;
540
+ membersOf(spaceId: string): Promise<Array<MembershipRow & {
541
+ user: UserRow;
542
+ }>>;
543
+ roleIn(userId: string, spaceId: string): Promise<SpaceRole | null>;
544
+ grant(userId: string, spaceId: string, role: SpaceRole): Promise<void>;
545
+ revoke(userId: string, spaceId: string): Promise<void>;
546
+ }
547
+ //#endregion
548
+ //#region src/repositories/webhook.d.ts
549
+ type WebhookRow = typeof webhooks.$inferSelect;
550
+ type WebhookDeliveryRow = typeof webhookDeliveries.$inferSelect;
551
+ interface WebhookDeliveryData {
552
+ webhookId: string;
553
+ event: string;
554
+ payload: Record<string, unknown>;
555
+ status: number | null;
556
+ error: string | null;
557
+ }
558
+ /** The webhooks of a space and the log of what was sent to them. */
559
+ declare class WebhookRepository {
560
+ private readonly db;
561
+ constructor(db: Database);
562
+ findById(id: string): Promise<WebhookRow | null>;
563
+ /** The switched-on webhooks of a space, for fanning an event out. */
564
+ findEnabled(spaceId: string): Promise<WebhookRow[]>;
565
+ recordDelivery(data: WebhookDeliveryData): Promise<WebhookDeliveryRow>;
566
+ deliveries(webhookId: string, limit?: number): Promise<WebhookDeliveryRow[]>;
567
+ }
568
+ //#endregion
569
+ //#region src/repositories/workflow.d.ts
570
+ interface WorkflowWriteData {
571
+ id?: string | undefined;
572
+ spaceId: string;
573
+ name: string;
574
+ description?: string | null | undefined;
575
+ enabled?: boolean | undefined;
576
+ trigger: WorkflowTrigger;
577
+ steps: WorkflowStep[];
578
+ }
579
+ interface WorkflowRunCreateData {
580
+ workflowId: string;
581
+ spaceId: string;
582
+ trigger: string;
583
+ context: WorkflowRunContext;
584
+ }
585
+ /** How many runs a workflow keeps; older ones are pruned as new ones are written. */
586
+ declare const RUNS_KEPT_PER_WORKFLOW = 200;
587
+ declare class WorkflowRepository {
588
+ private readonly db;
589
+ constructor(db: Database);
590
+ listBySpace(spaceId: string): Promise<WorkflowRow[]>;
591
+ /** Every enabled workflow of a space, for the dispatcher; every enabled one at all for the scheduler. */
592
+ listEnabled(spaceId?: string): Promise<WorkflowRow[]>;
593
+ findById(id: string): Promise<WorkflowRow | null>;
594
+ create(data: WorkflowWriteData): Promise<WorkflowRow>;
595
+ update(id: string, data: Loose<Omit<WorkflowWriteData, 'spaceId'>>): Promise<WorkflowRow>;
596
+ delete(id: string): Promise<void>;
597
+ /**
598
+ * Claims a scheduled workflow for one minute. Returns false when another process got
599
+ * there first — the update matches nothing once `lastScheduledAt` is already `minute`.
600
+ */
601
+ claimSchedule(id: string, minute: Date): Promise<boolean>;
602
+ touchRun(id: string, at: Date): Promise<void>;
603
+ createRun(data: WorkflowRunCreateData): Promise<WorkflowRunRow>;
604
+ findRun(id: string): Promise<WorkflowRunRow | null>;
605
+ listRuns(workflowId: string, limit?: number): Promise<WorkflowRunRow[]>;
606
+ /**
607
+ * Moves a run from `queued` or `waiting` to `running`, or reports that it is not
608
+ * there to be moved. The status check in the predicate is what keeps two workers off
609
+ * the same run.
610
+ */
611
+ claimRun(id: string): Promise<WorkflowRunRow | null>;
612
+ /** Runs paused by a delay step whose time has come. */
613
+ dueRuns(now: Date, limit?: number): Promise<WorkflowRunRow[]>;
614
+ saveRunProgress(id: string, data: {
615
+ status: WorkflowRunStatus;
616
+ cursor: WorkflowCursor;
617
+ log: WorkflowStepLog[];
618
+ error?: string | null | undefined;
619
+ resumeAt?: Date | null | undefined;
620
+ finished?: boolean | undefined;
621
+ }): Promise<void>;
622
+ private pruneRuns;
623
+ /** The documents a scheduled workflow's selection names, newest change first. */
624
+ selectDocuments(spaceId: string, selection: WorkflowSelection, limit?: number): Promise<ContentRow[]>;
625
+ subscriptionsFor(userIds: string[]): Promise<PushSubscriptionRow[]>;
626
+ subscriptionsOf(userId: string): Promise<PushSubscriptionRow[]>;
627
+ /** Upserts on the endpoint: a browser re-subscribing keeps one row, not two. */
628
+ subscribe(data: {
629
+ userId: string;
630
+ endpoint: string;
631
+ keys: {
632
+ p256dh: string;
633
+ auth: string;
634
+ };
635
+ userAgent: string | null;
636
+ }): Promise<PushSubscriptionRow>;
637
+ unsubscribe(userId: string, endpoint: string): Promise<void>;
638
+ /** A push service answered 404/410: the browser is gone, and so is the row. */
639
+ dropSubscription(id: string): Promise<void>;
640
+ markSubscriptionUsed(id: string): Promise<void>;
641
+ }
642
+ //#endregion
643
+ //#region src/repositories/index.d.ts
644
+ interface Repositories {
645
+ content: ContentRepository;
646
+ contentTypes: ContentTypeRepository;
647
+ spaces: SpaceRepository;
648
+ assets: AssetRepository;
649
+ assetUsages: AssetUsageRepository;
650
+ users: UserRepository;
651
+ menus: MenuRepository;
652
+ roles: RoleRepository;
653
+ workflows: WorkflowRepository;
654
+ webhooks: WebhookRepository;
655
+ }
656
+ declare function createRepositories(db: Database, registry: ContentTypeRegistry): Repositories;
657
+ //#endregion
658
+ export { AssetRepository as A, buildContentWhere as B, ContentRepository as C, buildTree as D, TreeNode as E, ContentSort as F, Executor as G, Database as H, ContentTable as I, createDatabase as J, Sql as K, FieldFilter as L, Paginated as M, paginate as N, AssetUsageRepository as O, ContentFilter as P, Pagination as R, ContentTypeRepository as S, SQL$1 as T, DatabaseHandle as U, buildOrderBy as V, DatabaseOptions as W, MenuItemInput as _, WorkflowRunCreateData as a, MenuWriteData as b, WebhookDeliveryRow as c, SpaceRole as d, UserCreateData as f, SpaceWriteData as g, SpaceRepository as h, WorkflowRepository as i, AssetWriteData as j, AssetFilter as k, WebhookRepository as l, UserUpdateData as m, createRepositories as n, WorkflowWriteData as o, UserRepository as p, Transaction as q, RUNS_KEPT_PER_WORKFLOW as r, WebhookDeliveryData as s, Repositories as t, WebhookRow as u, MenuItemNode as v, ContentWriteData as w, ResolvedMenuItem as x, MenuRepository as y, PgColumn$1 as z };