@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.
Files changed (61) hide show
  1. package/dist/index-BLAkQMJT.d.ts +877 -0
  2. package/dist/index-DrNMGM9N.d.ts +5193 -0
  3. package/dist/index.d.ts +123 -0
  4. package/dist/index.js +60 -0
  5. package/dist/repositories-pz4NeWaF.js +1928 -0
  6. package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
  7. package/dist/schema-Dm3RcBst.js +648 -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/migrations/0009_audit-log.sql +40 -0
  13. package/migrations/0010_notifications-approvals.sql +45 -0
  14. package/migrations/meta/0009_snapshot.json +3192 -0
  15. package/migrations/meta/0010_snapshot.json +3574 -0
  16. package/migrations/meta/_journal.json +14 -0
  17. package/package.json +18 -10
  18. package/drizzle.config.ts +0 -11
  19. package/src/bootstrap.ts +0 -13
  20. package/src/cli/create-db.ts +0 -30
  21. package/src/cli/migrate.ts +0 -17
  22. package/src/client.ts +0 -44
  23. package/src/columns.ts +0 -39
  24. package/src/errors.ts +0 -50
  25. package/src/index.ts +0 -19
  26. package/src/migrate.ts +0 -21
  27. package/src/pagination.ts +0 -52
  28. package/src/query.ts +0 -213
  29. package/src/repositories/asset-usage.ts +0 -166
  30. package/src/repositories/asset.ts +0 -181
  31. package/src/repositories/content-type.ts +0 -116
  32. package/src/repositories/content.ts +0 -811
  33. package/src/repositories/index.ts +0 -40
  34. package/src/repositories/menu.ts +0 -235
  35. package/src/repositories/role.ts +0 -85
  36. package/src/repositories/space.ts +0 -83
  37. package/src/repositories/user.ts +0 -280
  38. package/src/repositories/webhook.ts +0 -46
  39. package/src/repositories/workflow.ts +0 -306
  40. package/src/schema/assets.ts +0 -108
  41. package/src/schema/auth.ts +0 -166
  42. package/src/schema/content-types.ts +0 -31
  43. package/src/schema/content.ts +0 -133
  44. package/src/schema/index.ts +0 -38
  45. package/src/schema/menus.ts +0 -61
  46. package/src/schema/relations.ts +0 -64
  47. package/src/schema/spaces.ts +0 -20
  48. package/src/schema/webhooks.ts +0 -46
  49. package/src/schema/workflows.ts +0 -92
  50. package/src/testing-fixtures.ts +0 -139
  51. package/src/testing.ts +0 -105
  52. package/test/asset-usage.test.ts +0 -101
  53. package/test/menu.test.ts +0 -126
  54. package/test/publish.test.ts +0 -130
  55. package/test/query.test.ts +0 -170
  56. package/test/role.test.ts +0 -81
  57. package/test/tree.test.ts +0 -188
  58. package/test/user.test.ts +0 -126
  59. package/test/webhook.test.ts +0 -48
  60. package/tsconfig.json +0 -4
  61. package/vitest.config.ts +0 -10
@@ -0,0 +1,13 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __defProp = Object.defineProperty;
3
+ var __exportAll = (all, no_symbols) => {
4
+ let target = {};
5
+ for (var name in all) __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true
8
+ });
9
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
10
+ return target;
11
+ };
12
+ //#endregion
13
+ export { __exportAll as t };
@@ -0,0 +1,648 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
+ import { relations, sql } from "drizzle-orm";
3
+ import { bigserial, boolean, customType, index, integer, jsonb, pgTable, primaryKey, text, timestamp, unique, uniqueIndex, uuid } from "drizzle-orm/pg-core";
4
+ //#region src/columns.ts
5
+ /**
6
+ * `ltree` — the materialised ancestor path of a content node. A subtree move is one
7
+ * `UPDATE ... SET path = :newParent || subpath(path, nlevel(:oldParent))`.
8
+ */
9
+ const ltree = customType({ dataType: () => "ltree" });
10
+ /** `tsvector` — generated from `title` + `search_text`, never written directly. */
11
+ const tsvector = customType({ dataType: () => "tsvector" });
12
+ /**
13
+ * ltree labels accept only `[A-Za-z0-9_]`, so UUID hyphens are swapped for underscores.
14
+ * The transform is total and reversible.
15
+ */
16
+ const idToLabel = (id) => id.replaceAll("-", "_");
17
+ const labelToId = (label) => {
18
+ const hex = label.replaceAll("_", "");
19
+ return [
20
+ hex.slice(0, 8),
21
+ hex.slice(8, 12),
22
+ hex.slice(12, 16),
23
+ hex.slice(16, 20),
24
+ hex.slice(20, 32)
25
+ ].join("-");
26
+ };
27
+ /** Builds the path of a node from its ancestors' ids (root first) plus its own. */
28
+ const buildPath = (ancestorIds, selfId) => [...ancestorIds, selfId].map(idToLabel).join(".");
29
+ /** Splits a stored path back into ids, root first, self last. */
30
+ const parsePath = (path) => path.split(".").filter(Boolean).map(labelToId);
31
+ const pathDepth = (path) => path ? path.split(".").length : 0;
32
+ //#endregion
33
+ //#region src/schema/spaces.ts
34
+ const spaces = pgTable("spaces", {
35
+ id: uuid().primaryKey().defaultRandom(),
36
+ name: text().notNull(),
37
+ machineName: text().notNull(),
38
+ description: text(),
39
+ /** Public origin of the space's frontend — used by the visual editor iframe. */
40
+ url: text().notNull(),
41
+ defaultLocale: text().notNull().default("en"),
42
+ locales: jsonb().$type().notNull().default(sql`'["en"]'::jsonb`),
43
+ settings: jsonb().$type().notNull().default(sql`'{}'::jsonb`),
44
+ createdAt: timestamp({ withTimezone: true }).notNull().defaultNow(),
45
+ updatedAt: timestamp({ withTimezone: true }).notNull().defaultNow()
46
+ }, (table) => [uniqueIndex("spaces_machine_name_key").on(table.machineName)]);
47
+ //#endregion
48
+ //#region src/schema/content.ts
49
+ /** Shared column set for `contents` and its published projection. */
50
+ const contentColumns = {
51
+ id: uuid().primaryKey().defaultRandom(),
52
+ spaceId: uuid().notNull().references(() => spaces.id, { onDelete: "cascade" }),
53
+ typeId: uuid().notNull(),
54
+ locale: text().notNull(),
55
+ /** Shared by all translations of one logical document. */
56
+ localizationId: uuid().notNull(),
57
+ parentId: uuid(),
58
+ title: text().notNull(),
59
+ slug: text().notNull(),
60
+ /** Materialised ancestor path, root first, ending in this node's own id. */
61
+ path: ltree().notNull(),
62
+ /** Routable URL path. NULL when the content type has no slug — such a node is a
63
+ * structural container, not a page, so it must not occupy a permalink of its own. */
64
+ permalink: text(),
65
+ /**
66
+ * Accumulated permalink prefix: every ancestor segment plus this node's own, with
67
+ * slug-less levels skipped. Descendants derive from this rather than from `permalink`,
68
+ * which is NULL for a slug-less node and would otherwise truncate the chain.
69
+ */
70
+ permalinkPath: text().notNull().default(""),
71
+ /**
72
+ * This node's own contribution to its descendants' permalinks: the slug when the
73
+ * content type has one, NULL when it does not.
74
+ *
75
+ * Denormalised deliberately, so permalink recomputation is a single recursive CTE that
76
+ * needs no knowledge of the type registry.
77
+ */
78
+ permalinkSegment: text(),
79
+ status: text().$type().notNull().default("draft"),
80
+ position: integer().notNull().default(0),
81
+ fields: jsonb().$type().notNull().default(sql`'{}'::jsonb`),
82
+ /** Concatenated contributions from each field type's `search()`. */
83
+ searchText: text().notNull().default(""),
84
+ /** Optimistic-lock counter; a write with a stale version is rejected. */
85
+ version: integer().notNull().default(1),
86
+ createdAt: timestamp({ withTimezone: true }).notNull().defaultNow(),
87
+ updatedAt: timestamp({ withTimezone: true }).notNull().defaultNow(),
88
+ createdBy: uuid(),
89
+ updatedBy: uuid(),
90
+ publishedAt: timestamp({ withTimezone: true })
91
+ };
92
+ const contents = pgTable("contents", {
93
+ ...contentColumns,
94
+ search: tsvector().generatedAlwaysAs(sql`to_tsvector('simple', coalesce(title, '') || ' ' || coalesce(search_text, ''))`)
95
+ }, (table) => [
96
+ index("contents_path_gist_idx").using("gist", table.path),
97
+ index("contents_parent_idx").on(table.parentId),
98
+ index("contents_space_type_idx").on(table.spaceId, table.typeId),
99
+ index("contents_localization_idx").on(table.localizationId),
100
+ index("contents_status_idx").on(table.spaceId, table.status),
101
+ index("contents_fields_gin_idx").using("gin", sql`${table.fields} jsonb_path_ops`),
102
+ index("contents_search_gin_idx").using("gin", table.search),
103
+ unique("contents_sibling_slug_key").on(table.spaceId, table.parentId, table.locale, table.slug).nullsNotDistinct(),
104
+ uniqueIndex("contents_permalink_key").on(table.spaceId, table.locale, table.permalink).where(sql`permalink is not null`)
105
+ ]);
106
+ /**
107
+ * The delivery projection. Written transactionally on publish, read by the public
108
+ * GraphQL API. Mirrors `contents` so a row can be copied column-for-column.
109
+ */
110
+ const publishedContents = pgTable("published_contents", {
111
+ ...contentColumns,
112
+ /** Version of `contents` this projection was made from. */
113
+ sourceVersion: integer().notNull().default(1),
114
+ search: tsvector().generatedAlwaysAs(sql`to_tsvector('simple', coalesce(title, '') || ' ' || coalesce(search_text, ''))`)
115
+ }, (table) => [
116
+ index("published_contents_path_gist_idx").using("gist", table.path),
117
+ index("published_contents_parent_idx").on(table.parentId),
118
+ index("published_contents_space_type_idx").on(table.spaceId, table.typeId),
119
+ index("published_contents_fields_gin_idx").using("gin", sql`${table.fields} jsonb_path_ops`),
120
+ index("published_contents_search_gin_idx").using("gin", table.search),
121
+ uniqueIndex("published_contents_permalink_key").on(table.spaceId, table.locale, table.permalink).where(sql`permalink is not null`)
122
+ ]);
123
+ /** Full snapshot per save, so any edit can be rolled back. */
124
+ const contentVersions = pgTable("content_versions", {
125
+ id: uuid().primaryKey().defaultRandom(),
126
+ contentId: uuid().notNull(),
127
+ version: integer().notNull(),
128
+ label: text(),
129
+ snapshot: jsonb().$type().notNull(),
130
+ createdAt: timestamp({ withTimezone: true }).notNull().defaultNow(),
131
+ createdBy: uuid()
132
+ }, (table) => [uniqueIndex("content_versions_content_version_key").on(table.contentId, table.version), index("content_versions_content_idx").on(table.contentId, table.createdAt)]);
133
+ //#endregion
134
+ //#region src/schema/assets.ts
135
+ const assets = pgTable("assets", {
136
+ id: uuid().primaryKey().defaultRandom(),
137
+ spaceId: uuid().notNull().references(() => spaces.id, { onDelete: "cascade" }),
138
+ driver: text().notNull().default("local"),
139
+ /** Storage-relative key, e.g. `<space>/2026/09/photo.jpg`. */
140
+ key: text().notNull(),
141
+ filename: text().notNull(),
142
+ name: text().notNull(),
143
+ mimeType: text().notNull(),
144
+ size: integer().notNull(),
145
+ width: integer(),
146
+ height: integer(),
147
+ /** Seconds, for audio/video. */
148
+ duration: integer(),
149
+ /** SHA-256 of the bytes, for dedupe. */
150
+ checksum: text(),
151
+ alt: text(),
152
+ title: text(),
153
+ meta: jsonb().$type().notNull().default(sql`'{}'::jsonb`),
154
+ createdAt: timestamp({ withTimezone: true }).notNull().defaultNow(),
155
+ updatedAt: timestamp({ withTimezone: true }).notNull().defaultNow(),
156
+ createdBy: uuid()
157
+ }, (table) => [
158
+ uniqueIndex("assets_driver_key_key").on(table.driver, table.key),
159
+ index("assets_space_idx").on(table.spaceId, table.createdAt),
160
+ index("assets_checksum_idx").on(table.spaceId, table.checksum),
161
+ index("assets_mime_idx").on(table.spaceId, table.mimeType)
162
+ ]);
163
+ const assetVariants = pgTable("asset_variants", {
164
+ id: uuid().primaryKey().defaultRandom(),
165
+ assetId: uuid().notNull().references(() => assets.id, { onDelete: "cascade" }),
166
+ preset: text().notNull(),
167
+ format: text().notNull(),
168
+ key: text().notNull(),
169
+ width: integer(),
170
+ height: integer(),
171
+ size: integer().notNull(),
172
+ createdAt: timestamp({ withTimezone: true }).notNull().defaultNow()
173
+ }, (table) => [uniqueIndex("asset_variants_asset_preset_format_key").on(table.assetId, table.preset, table.format)]);
174
+ /**
175
+ * Which documents reference which assets, and whether any of them is published.
176
+ *
177
+ * Assets have no draft/published distinction of their own, so before this table an
178
+ * asset id resolved on the public API whether or not anything published pointed at it —
179
+ * including a file uploaded for a draft that never shipped. Maintained from the same
180
+ * publish/unpublish/delete hooks that purge the cache, out of the references each field
181
+ * type already declares.
182
+ */
183
+ const assetUsages = pgTable("asset_usages", {
184
+ assetId: uuid().notNull().references(() => assets.id, { onDelete: "cascade" }),
185
+ contentId: uuid().notNull().references(() => contents.id, { onDelete: "cascade" }),
186
+ spaceId: uuid().notNull().references(() => spaces.id, { onDelete: "cascade" }),
187
+ /** True when the *published* projection of `contentId` references the asset. */
188
+ published: boolean().notNull().default(false),
189
+ updatedAt: timestamp({ withTimezone: true }).notNull().defaultNow()
190
+ }, (table) => [
191
+ primaryKey({ columns: [table.assetId, table.contentId] }),
192
+ index("asset_usages_published_idx").on(table.assetId, table.published),
193
+ index("asset_usages_content_idx").on(table.contentId)
194
+ ]);
195
+ //#endregion
196
+ //#region src/schema/audit.ts
197
+ /**
198
+ * The audit log: one row per action, appended and never changed.
199
+ *
200
+ * Nothing here references another table. A space, a user or a document may be deleted
201
+ * long after the row was written, and the row must still say who did what: the actor
202
+ * and the target are recorded by id *and* by label, as they were at the time. A trigger
203
+ * (see the migration) refuses every `update` and `delete`, and each row carries the
204
+ * SHA-256 of its content chained to the previous row's, so a gap or an edit shows up
205
+ * when the chain is verified.
206
+ */
207
+ const auditEntries = pgTable("audit_entries", {
208
+ id: uuid().primaryKey().defaultRandom(),
209
+ /** The order of the chain; assigned by the database, gapless within a transaction. */
210
+ seq: bigserial({ mode: "number" }).notNull(),
211
+ at: timestamp({ withTimezone: true }).notNull().defaultNow(),
212
+ /** Null for an instance-wide action: an account, a key, creating a space. */
213
+ spaceId: uuid(),
214
+ actorKind: text().$type().notNull(),
215
+ actorId: text(),
216
+ actorLabel: text().notNull(),
217
+ actorDetail: jsonb().$type(),
218
+ action: text().$type().notNull(),
219
+ targetKind: text().$type().notNull(),
220
+ targetId: text(),
221
+ targetLabel: text(),
222
+ changes: jsonb().$type().notNull().default(sql`'[]'::jsonb`),
223
+ meta: jsonb().$type(),
224
+ prevHash: text(),
225
+ hash: text().notNull()
226
+ }, (table) => [
227
+ index("audit_entries_seq_idx").on(table.seq),
228
+ index("audit_entries_space_at_idx").on(table.spaceId, table.at),
229
+ index("audit_entries_target_idx").on(table.targetKind, table.targetId),
230
+ index("audit_entries_actor_idx").on(table.actorKind, table.actorId),
231
+ index("audit_entries_action_idx").on(table.action)
232
+ ]);
233
+ //#endregion
234
+ //#region src/schema/auth.ts
235
+ const users = pgTable("users", {
236
+ id: uuid().primaryKey().defaultRandom(),
237
+ name: text().notNull().default(""),
238
+ email: text().notNull(),
239
+ emailVerified: boolean().notNull().default(false),
240
+ image: text(),
241
+ /** Instance-wide role. Space-scoped roles live on `memberships`. */
242
+ role: text().notNull().default("editor"),
243
+ banned: boolean().notNull().default(false),
244
+ banReason: text(),
245
+ /**
246
+ * Per notification kind, the channels this person switched on or off. Only what
247
+ * differs from the catalogue's defaults is stored; see `resolveNotificationPreferences`.
248
+ */
249
+ notificationPreferences: jsonb().$type().notNull().default(sql`'{}'::jsonb`),
250
+ createdAt: timestamp({ withTimezone: true }).notNull().defaultNow(),
251
+ updatedAt: timestamp({ withTimezone: true }).notNull().defaultNow()
252
+ }, (table) => [uniqueIndex("users_email_key").on(table.email)]);
253
+ /** One row per active session, so multiple devices can be signed in at once. */
254
+ const sessions = pgTable("sessions", {
255
+ id: uuid().primaryKey().defaultRandom(),
256
+ userId: uuid().notNull().references(() => users.id, { onDelete: "cascade" }),
257
+ token: text().notNull(),
258
+ expiresAt: timestamp({ withTimezone: true }).notNull(),
259
+ ipAddress: text(),
260
+ userAgent: text(),
261
+ createdAt: timestamp({ withTimezone: true }).notNull().defaultNow(),
262
+ updatedAt: timestamp({ withTimezone: true }).notNull().defaultNow()
263
+ }, (table) => [
264
+ uniqueIndex("sessions_token_key").on(table.token),
265
+ index("sessions_user_idx").on(table.userId),
266
+ index("sessions_expires_idx").on(table.expiresAt)
267
+ ]);
268
+ const accounts = pgTable("accounts", {
269
+ id: uuid().primaryKey().defaultRandom(),
270
+ userId: uuid().notNull().references(() => users.id, { onDelete: "cascade" }),
271
+ accountId: text().notNull(),
272
+ providerId: text().notNull(),
273
+ /** Required by better-auth 1.7 for OIDC issuer disambiguation. */
274
+ issuer: text().notNull().default(""),
275
+ accessToken: text(),
276
+ refreshToken: text(),
277
+ accessTokenExpiresAt: timestamp({ withTimezone: true }),
278
+ refreshTokenExpiresAt: timestamp({ withTimezone: true }),
279
+ scope: text(),
280
+ idToken: text(),
281
+ password: text(),
282
+ createdAt: timestamp({ withTimezone: true }).notNull().defaultNow(),
283
+ updatedAt: timestamp({ withTimezone: true }).notNull().defaultNow()
284
+ }, (table) => [uniqueIndex("accounts_provider_account_key").on(table.providerId, table.accountId)]);
285
+ const verifications = pgTable("verifications", {
286
+ id: uuid().primaryKey().defaultRandom(),
287
+ identifier: text().notNull(),
288
+ value: text().notNull(),
289
+ expiresAt: timestamp({ withTimezone: true }).notNull(),
290
+ createdAt: timestamp({ withTimezone: true }).notNull().defaultNow(),
291
+ updatedAt: timestamp({ withTimezone: true }).notNull().defaultNow()
292
+ }, (table) => [index("verifications_identifier_idx").on(table.identifier)]);
293
+ const apikeys = pgTable("apikeys", {
294
+ id: uuid().primaryKey().defaultRandom(),
295
+ name: text(),
296
+ start: text(),
297
+ prefix: text(),
298
+ key: text().notNull(),
299
+ userId: uuid().notNull().references(() => users.id, { onDelete: "cascade" }),
300
+ enabled: boolean().notNull().default(true),
301
+ expiresAt: timestamp({ withTimezone: true }),
302
+ lastRequest: timestamp({ withTimezone: true }),
303
+ /**
304
+ * Grants this key is confined to, in the roles' vocabulary; `null` means whatever
305
+ * the owner's role allows. Like `spaceIds`, a restriction only ever narrows.
306
+ */
307
+ permissions: jsonb().$type(),
308
+ /**
309
+ * Space ids this key may act in; `null` means every space the owner belongs to.
310
+ * A restriction only ever narrows the owner's own access, never widens it.
311
+ */
312
+ spaceIds: jsonb().$type(),
313
+ metadata: text(),
314
+ createdAt: timestamp({ withTimezone: true }).notNull().defaultNow(),
315
+ updatedAt: timestamp({ withTimezone: true }).notNull().defaultNow()
316
+ }, (table) => [index("apikeys_user_idx").on(table.userId)]);
317
+ /** Space-scoped role assignment. */
318
+ const memberships = pgTable("memberships", {
319
+ userId: uuid().notNull().references(() => users.id, { onDelete: "cascade" }),
320
+ spaceId: uuid().notNull().references(() => spaces.id, { onDelete: "cascade" }),
321
+ /** The machine name of a built-in role or of a row in `roles` for the same space. */
322
+ role: text().notNull().default("editor"),
323
+ createdAt: timestamp({ withTimezone: true }).notNull().defaultNow()
324
+ }, (table) => [primaryKey({ columns: [table.userId, table.spaceId] }), index("memberships_space_idx").on(table.spaceId)]);
325
+ /**
326
+ * A role someone created for a space, beside the built-in five. `permissions` holds the
327
+ * grants: `space:write`, `content:read` for every type, or `content:read:<typeId>` for
328
+ * one. Memberships name it by `machineName`, so a custom role is one row and one name.
329
+ */
330
+ const roles = pgTable("roles", {
331
+ id: uuid().primaryKey().defaultRandom(),
332
+ spaceId: uuid().notNull().references(() => spaces.id, { onDelete: "cascade" }),
333
+ name: text().notNull(),
334
+ machineName: text().notNull(),
335
+ description: text(),
336
+ permissions: jsonb().$type().notNull().default(sql`'[]'::jsonb`),
337
+ createdAt: timestamp({ withTimezone: true }).notNull().defaultNow(),
338
+ updatedAt: timestamp({ withTimezone: true }).notNull().defaultNow()
339
+ }, (table) => [uniqueIndex("roles_space_machine_name_key").on(table.spaceId, table.machineName)]);
340
+ //#endregion
341
+ //#region src/schema/content-types.ts
342
+ /** Runtime-defined content types only; code-defined ones live in the registry. */
343
+ const contentTypes = pgTable("content_types", {
344
+ id: uuid().primaryKey().defaultRandom(),
345
+ spaceId: uuid().references(() => spaces.id, { onDelete: "cascade" }),
346
+ name: text().notNull(),
347
+ label: text().notNull(),
348
+ description: text(),
349
+ icon: text(),
350
+ kind: text().$type().notNull().default("content"),
351
+ hasSlug: boolean().notNull().default(true),
352
+ isPublishable: boolean().notNull().default(true),
353
+ isVisibleInTree: boolean().notNull().default(true),
354
+ canBeVisibleInMenu: boolean().notNull().default(true),
355
+ requiresApproval: boolean().notNull().default(false),
356
+ fields: jsonb().$type().notNull().default(sql`'[]'::jsonb`),
357
+ createdAt: timestamp({ withTimezone: true }).notNull().defaultNow(),
358
+ updatedAt: timestamp({ withTimezone: true }).notNull().defaultNow()
359
+ }, (table) => [unique("content_types_space_name_key").on(table.spaceId, table.name).nullsNotDistinct(), index("content_types_space_idx").on(table.spaceId)]);
360
+ //#endregion
361
+ //#region src/schema/menus.ts
362
+ /**
363
+ * A named navigation a site renders: "main", "footer", "legal". A document may sit in
364
+ * any number of them, which is why membership is a row here and not a flag on the
365
+ * document.
366
+ */
367
+ const menus = pgTable("menus", {
368
+ id: uuid().primaryKey().defaultRandom(),
369
+ spaceId: uuid().notNull().references(() => spaces.id, { onDelete: "cascade" }),
370
+ name: text().notNull(),
371
+ /** What the delivery API is asked for: `menu(name: "main")`. */
372
+ machineName: text().notNull(),
373
+ description: text(),
374
+ createdAt: timestamp({ withTimezone: true }).notNull().defaultNow(),
375
+ updatedAt: timestamp({ withTimezone: true }).notNull().defaultNow()
376
+ }, (table) => [unique("menus_space_machine_name_key").on(table.spaceId, table.machineName)]);
377
+ /**
378
+ * One entry of a menu, nested to any depth.
379
+ *
380
+ * A content entry points at the document's `localizationId` rather than at one row, so
381
+ * the same menu serves every locale: delivery resolves the translation for the locale
382
+ * it is asked for. A link entry has a `url` instead. Either may carry a `label`; for a
383
+ * content entry it overrides the document's title.
384
+ */
385
+ const menuItems = pgTable("menu_items", {
386
+ id: uuid().primaryKey().defaultRandom(),
387
+ menuId: uuid().notNull().references(() => menus.id, { onDelete: "cascade" }),
388
+ /** Cascades, so removing an entry takes its sub-entries with it. */
389
+ parentId: uuid().references(() => menuItems.id, { onDelete: "cascade" }),
390
+ position: integer().notNull().default(0),
391
+ localizationId: uuid(),
392
+ label: text(),
393
+ url: text()
394
+ }, (table) => [index("menu_items_menu_idx").on(table.menuId, table.parentId, table.position), index("menu_items_localization_idx").on(table.localizationId)]);
395
+ //#endregion
396
+ //#region src/schema/notifications.ts
397
+ /**
398
+ * One notification, for one person. A single event that concerns five people is five
399
+ * rows, so each can be read, kept or dropped on its own; the fan-out is the service's.
400
+ *
401
+ * The row is the in-app copy. Email and push are sent at the same time and leave no
402
+ * row of their own: a notification the person turned off in the admin but kept by
403
+ * email is a mail, not a row here.
404
+ */
405
+ const notifications = pgTable("notifications", {
406
+ id: uuid().primaryKey().defaultRandom(),
407
+ userId: uuid().notNull().references(() => users.id, { onDelete: "cascade" }),
408
+ /** Null for something that concerns the instance rather than a space. */
409
+ spaceId: uuid().references(() => spaces.id, { onDelete: "cascade" }),
410
+ kind: text().$type().notNull(),
411
+ title: text().notNull(),
412
+ body: text().notNull().default(""),
413
+ /** Where to go in the admin, as a path: `/content/<id>`. */
414
+ url: text(),
415
+ /** What it is about, so the inbox can group and a target's later fate can be shown. */
416
+ targetKind: text(),
417
+ targetId: text(),
418
+ /**
419
+ * Who caused it; `null` for the system. Text rather than a uuid, like the audit
420
+ * log's actor: it is a snapshot of whoever acted, not a reference.
421
+ */
422
+ actorId: text(),
423
+ actorLabel: text(),
424
+ meta: jsonb().$type(),
425
+ readAt: timestamp({ withTimezone: true }),
426
+ createdAt: timestamp({ withTimezone: true }).notNull().defaultNow()
427
+ }, (table) => [
428
+ index("notifications_user_created_idx").on(table.userId, table.createdAt),
429
+ index("notifications_user_unread_idx").on(table.userId, table.readAt),
430
+ index("notifications_target_idx").on(table.targetKind, table.targetId)
431
+ ]);
432
+ /**
433
+ * A request to publish a document on the author's behalf, and what became of it. One
434
+ * document may have several over its life (sent back, resubmitted), but at most one
435
+ * pending at a time; the service keeps that rule, the table keeps the history.
436
+ *
437
+ * Cascades with the document: a deleted draft has nothing left to approve.
438
+ */
439
+ const contentApprovals = pgTable("content_approvals", {
440
+ id: uuid().primaryKey().defaultRandom(),
441
+ spaceId: uuid().notNull().references(() => spaces.id, { onDelete: "cascade" }),
442
+ contentId: uuid().notNull().references(() => contents.id, { onDelete: "cascade" }),
443
+ typeId: uuid().notNull(),
444
+ status: text().$type().notNull().default("pending"),
445
+ /** Who asked; kept even once the account is gone, so the history stays readable. */
446
+ requestedBy: uuid(),
447
+ requestedByLabel: text().notNull().default(""),
448
+ requestNote: text(),
449
+ /** The document's version when it was submitted, so a reviewer sees what changed since. */
450
+ contentVersion: integer(),
451
+ requestedAt: timestamp({ withTimezone: true }).notNull().defaultNow(),
452
+ decidedBy: uuid(),
453
+ decidedByLabel: text(),
454
+ decisionNote: text(),
455
+ decidedAt: timestamp({ withTimezone: true })
456
+ }, (table) => [index("content_approvals_content_idx").on(table.contentId, table.requestedAt), index("content_approvals_space_status_idx").on(table.spaceId, table.status, table.requestedAt)]);
457
+ //#endregion
458
+ //#region src/schema/relations.ts
459
+ const spacesRelations = relations(spaces, ({ many }) => ({
460
+ contents: many(contents),
461
+ contentTypes: many(contentTypes),
462
+ assets: many(assets),
463
+ memberships: many(memberships),
464
+ menus: many(menus),
465
+ roles: many(roles)
466
+ }));
467
+ const contentsRelations = relations(contents, ({ one, many }) => ({
468
+ space: one(spaces, {
469
+ fields: [contents.spaceId],
470
+ references: [spaces.id]
471
+ }),
472
+ parent: one(contents, {
473
+ fields: [contents.parentId],
474
+ references: [contents.id],
475
+ relationName: "tree"
476
+ }),
477
+ children: many(contents, { relationName: "tree" }),
478
+ versions: many(contentVersions)
479
+ }));
480
+ const assetsRelations = relations(assets, ({ one, many }) => ({
481
+ space: one(spaces, {
482
+ fields: [assets.spaceId],
483
+ references: [spaces.id]
484
+ }),
485
+ variants: many(assetVariants)
486
+ }));
487
+ const assetVariantsRelations = relations(assetVariants, ({ one }) => ({ asset: one(assets, {
488
+ fields: [assetVariants.assetId],
489
+ references: [assets.id]
490
+ }) }));
491
+ const assetUsagesRelations = relations(assetUsages, ({ one }) => ({
492
+ asset: one(assets, {
493
+ fields: [assetUsages.assetId],
494
+ references: [assets.id]
495
+ }),
496
+ content: one(contents, {
497
+ fields: [assetUsages.contentId],
498
+ references: [contents.id]
499
+ })
500
+ }));
501
+ const usersRelations = relations(users, ({ many }) => ({
502
+ sessions: many(sessions),
503
+ memberships: many(memberships)
504
+ }));
505
+ const membershipsRelations = relations(memberships, ({ one }) => ({
506
+ user: one(users, {
507
+ fields: [memberships.userId],
508
+ references: [users.id]
509
+ }),
510
+ space: one(spaces, {
511
+ fields: [memberships.spaceId],
512
+ references: [spaces.id]
513
+ })
514
+ }));
515
+ const menusRelations = relations(menus, ({ one, many }) => ({
516
+ space: one(spaces, {
517
+ fields: [menus.spaceId],
518
+ references: [spaces.id]
519
+ }),
520
+ items: many(menuItems)
521
+ }));
522
+ const menuItemsRelations = relations(menuItems, ({ one }) => ({ menu: one(menus, {
523
+ fields: [menuItems.menuId],
524
+ references: [menus.id]
525
+ }) }));
526
+ const rolesRelations = relations(roles, ({ one }) => ({ space: one(spaces, {
527
+ fields: [roles.spaceId],
528
+ references: [spaces.id]
529
+ }) }));
530
+ //#endregion
531
+ //#region src/schema/webhooks.ts
532
+ const webhooks = pgTable("webhooks", {
533
+ id: uuid().primaryKey().defaultRandom(),
534
+ spaceId: uuid().notNull().references(() => spaces.id, { onDelete: "cascade" }),
535
+ name: text().notNull(),
536
+ url: text().notNull(),
537
+ secret: text(),
538
+ events: jsonb().$type().notNull().default(sql`'[]'::jsonb`),
539
+ enabled: boolean().notNull().default(true),
540
+ createdAt: timestamp({ withTimezone: true }).notNull().defaultNow()
541
+ }, (table) => [index("webhooks_space_idx").on(table.spaceId)]);
542
+ const webhookDeliveries = pgTable("webhook_deliveries", {
543
+ id: uuid().primaryKey().defaultRandom(),
544
+ webhookId: uuid().notNull().references(() => webhooks.id, { onDelete: "cascade" }),
545
+ event: text().notNull(),
546
+ payload: jsonb().$type().notNull(),
547
+ status: integer(),
548
+ error: text(),
549
+ attempt: integer().notNull().default(1),
550
+ createdAt: timestamp({ withTimezone: true }).notNull().defaultNow()
551
+ }, (table) => [index("webhook_deliveries_webhook_idx").on(table.webhookId, table.createdAt)]);
552
+ //#endregion
553
+ //#region src/schema/workflows.ts
554
+ /**
555
+ * A workflow: something that happens when content changes, or on a schedule. The
556
+ * trigger and the steps are documents rather than rows — a workflow is edited and saved
557
+ * as one thing, and a step type added later needs no migration.
558
+ */
559
+ const workflows = pgTable("workflows", {
560
+ id: uuid().primaryKey().defaultRandom(),
561
+ spaceId: uuid().notNull().references(() => spaces.id, { onDelete: "cascade" }),
562
+ name: text().notNull(),
563
+ description: text(),
564
+ enabled: boolean().notNull().default(false),
565
+ trigger: jsonb().$type().notNull(),
566
+ steps: jsonb().$type().notNull().default(sql`'[]'::jsonb`),
567
+ /**
568
+ * The minute a scheduled workflow was last claimed for, so two processes ticking at
569
+ * once start one run between them rather than one each.
570
+ */
571
+ lastScheduledAt: timestamp({ withTimezone: true }),
572
+ lastRunAt: timestamp({ withTimezone: true }),
573
+ createdAt: timestamp({ withTimezone: true }).notNull().defaultNow(),
574
+ updatedAt: timestamp({ withTimezone: true }).notNull().defaultNow()
575
+ }, (table) => [index("workflows_space_idx").on(table.spaceId, table.enabled)]);
576
+ /**
577
+ * One execution of a workflow, with everything needed to continue it after a pause: the
578
+ * context the templates read, the index of the next step and the log so far.
579
+ */
580
+ const workflowRuns = pgTable("workflow_runs", {
581
+ id: uuid().primaryKey().defaultRandom(),
582
+ workflowId: uuid().notNull().references(() => workflows.id, { onDelete: "cascade" }),
583
+ spaceId: uuid().notNull().references(() => spaces.id, { onDelete: "cascade" }),
584
+ /** The event name, or `schedule` / `manual`. */
585
+ trigger: text().notNull(),
586
+ status: text().$type().notNull().default("queued"),
587
+ context: jsonb().$type().notNull(),
588
+ /** Where to continue; a paused run resumes here. See `WorkflowCursor`. */
589
+ cursor: jsonb().$type().notNull().default(sql`'[]'::jsonb`),
590
+ log: jsonb().$type().notNull().default(sql`'[]'::jsonb`),
591
+ error: text(),
592
+ /** Set while `waiting`: when the scheduler should pick the run up again. */
593
+ resumeAt: timestamp({ withTimezone: true }),
594
+ startedAt: timestamp({ withTimezone: true }),
595
+ finishedAt: timestamp({ withTimezone: true }),
596
+ createdAt: timestamp({ withTimezone: true }).notNull().defaultNow()
597
+ }, (table) => [index("workflow_runs_workflow_idx").on(table.workflowId, table.createdAt), index("workflow_runs_resume_idx").on(table.status, table.resumeAt)]);
598
+ /** A browser a user allowed push notifications in; one row per device. */
599
+ const pushSubscriptions = pgTable("push_subscriptions", {
600
+ id: uuid().primaryKey().defaultRandom(),
601
+ userId: uuid().notNull().references(() => users.id, { onDelete: "cascade" }),
602
+ endpoint: text().notNull().unique("push_subscriptions_endpoint_key"),
603
+ keys: jsonb().$type().notNull(),
604
+ userAgent: text(),
605
+ createdAt: timestamp({ withTimezone: true }).notNull().defaultNow(),
606
+ lastUsedAt: timestamp({ withTimezone: true })
607
+ }, (table) => [index("push_subscriptions_user_idx").on(table.userId)]);
608
+ //#endregion
609
+ //#region src/schema/index.ts
610
+ var schema_exports = /* @__PURE__ */ __exportAll({
611
+ accounts: () => accounts,
612
+ apikeys: () => apikeys,
613
+ assetUsages: () => assetUsages,
614
+ assetUsagesRelations: () => assetUsagesRelations,
615
+ assetVariants: () => assetVariants,
616
+ assetVariantsRelations: () => assetVariantsRelations,
617
+ assets: () => assets,
618
+ assetsRelations: () => assetsRelations,
619
+ auditEntries: () => auditEntries,
620
+ contentApprovals: () => contentApprovals,
621
+ contentTypes: () => contentTypes,
622
+ contentVersions: () => contentVersions,
623
+ contents: () => contents,
624
+ contentsRelations: () => contentsRelations,
625
+ memberships: () => memberships,
626
+ membershipsRelations: () => membershipsRelations,
627
+ menuItems: () => menuItems,
628
+ menuItemsRelations: () => menuItemsRelations,
629
+ menus: () => menus,
630
+ menusRelations: () => menusRelations,
631
+ notifications: () => notifications,
632
+ publishedContents: () => publishedContents,
633
+ pushSubscriptions: () => pushSubscriptions,
634
+ roles: () => roles,
635
+ rolesRelations: () => rolesRelations,
636
+ sessions: () => sessions,
637
+ spaces: () => spaces,
638
+ spacesRelations: () => spacesRelations,
639
+ users: () => users,
640
+ usersRelations: () => usersRelations,
641
+ verifications: () => verifications,
642
+ webhookDeliveries: () => webhookDeliveries,
643
+ webhooks: () => webhooks,
644
+ workflowRuns: () => workflowRuns,
645
+ workflows: () => workflows
646
+ });
647
+ //#endregion
648
+ export { assetUsages as A, ltree as B, apikeys as C, users as D, sessions as E, publishedContents as F, pathDepth as H, spaces as I, buildPath as L, assets as M, contentVersions as N, verifications as O, contents as P, idToLabel as R, accounts as S, roles as T, tsvector as U, parsePath as V, contentApprovals as _, webhookDeliveries as a, menus as b, assetVariantsRelations as c, membershipsRelations as d, menuItemsRelations as f, usersRelations as g, spacesRelations as h, workflows as i, assetVariants as j, auditEntries as k, assetsRelations as l, rolesRelations as m, pushSubscriptions as n, webhooks as o, menusRelations as p, workflowRuns as r, assetUsagesRelations as s, schema_exports as t, contentsRelations as u, notifications as v, memberships as w, contentTypes as x, menuItems as y, labelToId as z };
@@ -0,0 +1,2 @@
1
+ import { $ as assetUsages, A as contentsRelations, B as menus, C as workflows, D as assetUsagesRelations, E as spaces, F as spacesRelations, G as accounts, H as contentVersions, I as usersRelations, J as roles, K as apikeys, L as contentApprovals, M as menuItemsRelations, N as menusRelations, O as assetVariantsRelations, P as rolesRelations, Q as auditEntries, R as notifications, S as workflowRuns, T as webhooks, U as contents, V as contentTypes, W as publishedContents, X as users, Y as sessions, Z as verifications, _ as UserRow, a as ContentApprovalRow, c as ContentTypeRow, d as MenuItemRow, et as assetVariants, f as MenuRow, g as SpaceRow, h as RoleRow, i as AuditEntryRow, j as membershipsRelations, k as assetsRelations, l as ContentVersionRow, m as PushSubscriptionRow, n as AssetUsageRow, o as ContentInsert, p as NotificationRow, q as memberships, r as AssetVariantRow, s as ContentRow, t as AssetRow, tt as assets, u as MembershipRow, v as WorkflowRow, w as webhookDeliveries, x as pushSubscriptions, y as WorkflowRunRow, z as menuItems } from "./index-DrNMGM9N.js";
2
+ export { AssetRow, AssetUsageRow, AssetVariantRow, AuditEntryRow, ContentApprovalRow, ContentInsert, ContentRow, ContentTypeRow, ContentVersionRow, MembershipRow, MenuItemRow, MenuRow, NotificationRow, PushSubscriptionRow, RoleRow, SpaceRow, UserRow, WorkflowRow, WorkflowRunRow, accounts, apikeys, assetUsages, assetUsagesRelations, assetVariants, assetVariantsRelations, assets, assetsRelations, auditEntries, contentApprovals, contentTypes, contentVersions, contents, contentsRelations, memberships, membershipsRelations, menuItems, menuItemsRelations, menus, menusRelations, notifications, publishedContents, pushSubscriptions, roles, rolesRelations, sessions, spaces, spacesRelations, users, usersRelations, verifications, webhookDeliveries, webhooks, workflowRuns, workflows };
package/dist/schema.js ADDED
@@ -0,0 +1,2 @@
1
+ import { A as assetUsages, C as apikeys, D as users, E as sessions, F as publishedContents, I as spaces, M as assets, N as contentVersions, O as verifications, P as contents, S as accounts, T as roles, _ as contentApprovals, a as webhookDeliveries, b as menus, c as assetVariantsRelations, d as membershipsRelations, f as menuItemsRelations, g as usersRelations, h as spacesRelations, i as workflows, j as assetVariants, k as auditEntries, l as assetsRelations, m as rolesRelations, n as pushSubscriptions, o as webhooks, p as menusRelations, r as workflowRuns, s as assetUsagesRelations, u as contentsRelations, v as notifications, w as memberships, x as contentTypes, y as menuItems } from "./schema-Dm3RcBst.js";
2
+ export { accounts, apikeys, assetUsages, assetUsagesRelations, assetVariants, assetVariantsRelations, assets, assetsRelations, auditEntries, contentApprovals, contentTypes, contentVersions, contents, contentsRelations, memberships, membershipsRelations, menuItems, menuItemsRelations, menus, menusRelations, notifications, publishedContents, pushSubscriptions, roles, rolesRelations, sessions, spaces, spacesRelations, users, usersRelations, verifications, webhookDeliveries, webhooks, workflowRuns, workflows };