@headless-lms/adapter-db 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/dist/_virtual/_rolldown/runtime.js +13 -0
  3. package/dist/client.d.ts +17 -0
  4. package/dist/client.js +11 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/index.d.ts +22 -0
  7. package/dist/index.js +22 -0
  8. package/dist/migrate.d.ts +7 -0
  9. package/dist/migrate.js +27 -0
  10. package/dist/migrate.js.map +1 -0
  11. package/dist/repositories/assets.d.ts +17 -0
  12. package/dist/repositories/assets.js +90 -0
  13. package/dist/repositories/assets.js.map +1 -0
  14. package/dist/repositories/automations.d.ts +30 -0
  15. package/dist/repositories/automations.js +165 -0
  16. package/dist/repositories/automations.js.map +1 -0
  17. package/dist/repositories/content.d.ts +52 -0
  18. package/dist/repositories/content.js +350 -0
  19. package/dist/repositories/content.js.map +1 -0
  20. package/dist/repositories/credentials.d.ts +25 -0
  21. package/dist/repositories/credentials.js +86 -0
  22. package/dist/repositories/credentials.js.map +1 -0
  23. package/dist/repositories/dashboard.d.ts +14 -0
  24. package/dist/repositories/dashboard.js +55 -0
  25. package/dist/repositories/dashboard.js.map +1 -0
  26. package/dist/repositories/discussion.d.ts +31 -0
  27. package/dist/repositories/discussion.js +201 -0
  28. package/dist/repositories/discussion.js.map +1 -0
  29. package/dist/repositories/entitlements.d.ts +23 -0
  30. package/dist/repositories/entitlements.js +129 -0
  31. package/dist/repositories/entitlements.js.map +1 -0
  32. package/dist/repositories/identity.d.ts +17 -0
  33. package/dist/repositories/identity.js +44 -0
  34. package/dist/repositories/identity.js.map +1 -0
  35. package/dist/repositories/integrations.d.ts +18 -0
  36. package/dist/repositories/integrations.js +65 -0
  37. package/dist/repositories/integrations.js.map +1 -0
  38. package/dist/repositories/learn.d.ts +19 -0
  39. package/dist/repositories/learn.js +54 -0
  40. package/dist/repositories/learn.js.map +1 -0
  41. package/dist/repositories/members.d.ts +16 -0
  42. package/dist/repositories/members.js +116 -0
  43. package/dist/repositories/members.js.map +1 -0
  44. package/dist/repositories/org-user-profile.d.ts +92 -0
  45. package/dist/repositories/org-user-profile.js +16 -0
  46. package/dist/repositories/org-user-profile.js.map +1 -0
  47. package/dist/repositories/organizations.d.ts +33 -0
  48. package/dist/repositories/organizations.js +198 -0
  49. package/dist/repositories/organizations.js.map +1 -0
  50. package/dist/repositories/outbox.d.ts +24 -0
  51. package/dist/repositories/outbox.js +56 -0
  52. package/dist/repositories/outbox.js.map +1 -0
  53. package/dist/repositories/pg-errors.d.ts +11 -0
  54. package/dist/repositories/pg-errors.js +15 -0
  55. package/dist/repositories/pg-errors.js.map +1 -0
  56. package/dist/repositories/progress.d.ts +18 -0
  57. package/dist/repositories/progress.js +59 -0
  58. package/dist/repositories/progress.js.map +1 -0
  59. package/dist/repositories/settings.d.ts +22 -0
  60. package/dist/repositories/settings.js +73 -0
  61. package/dist/repositories/settings.js.map +1 -0
  62. package/dist/repositories/students.d.ts +15 -0
  63. package/dist/repositories/students.js +83 -0
  64. package/dist/repositories/students.js.map +1 -0
  65. package/dist/schema/assets.d.ts +181 -0
  66. package/dist/schema/assets.js +24 -0
  67. package/dist/schema/assets.js.map +1 -0
  68. package/dist/schema/automations.d.ts +364 -0
  69. package/dist/schema/automations.js +42 -0
  70. package/dist/schema/automations.js.map +1 -0
  71. package/dist/schema/better-auth.d.ts +2311 -0
  72. package/dist/schema/better-auth.js +156 -0
  73. package/dist/schema/better-auth.js.map +1 -0
  74. package/dist/schema/content.d.ts +966 -0
  75. package/dist/schema/content.js +163 -0
  76. package/dist/schema/content.js.map +1 -0
  77. package/dist/schema/credentials.d.ts +113 -0
  78. package/dist/schema/credentials.js +15 -0
  79. package/dist/schema/credentials.js.map +1 -0
  80. package/dist/schema/discussion.d.ts +399 -0
  81. package/dist/schema/discussion.js +89 -0
  82. package/dist/schema/discussion.js.map +1 -0
  83. package/dist/schema/drift.d.ts +9 -0
  84. package/dist/schema/drift.js +1 -0
  85. package/dist/schema/entitlements.d.ts +147 -0
  86. package/dist/schema/entitlements.js +30 -0
  87. package/dist/schema/entitlements.js.map +1 -0
  88. package/dist/schema/identity.d.ts +130 -0
  89. package/dist/schema/identity.js +16 -0
  90. package/dist/schema/identity.js.map +1 -0
  91. package/dist/schema/index.d.ts +18 -0
  92. package/dist/schema/index.js +43 -0
  93. package/dist/schema/index.js.map +1 -0
  94. package/dist/schema/integrations.d.ts +149 -0
  95. package/dist/schema/integrations.js +25 -0
  96. package/dist/schema/integrations.js.map +1 -0
  97. package/dist/schema/organizations.d.ts +416 -0
  98. package/dist/schema/organizations.js +58 -0
  99. package/dist/schema/organizations.js.map +1 -0
  100. package/dist/schema/outbox.d.ts +166 -0
  101. package/dist/schema/outbox.js +19 -0
  102. package/dist/schema/outbox.js.map +1 -0
  103. package/dist/schema/progress.d.ts +166 -0
  104. package/dist/schema/progress.js +30 -0
  105. package/dist/schema/progress.js.map +1 -0
  106. package/dist/schema/settings.d.ts +115 -0
  107. package/dist/schema/settings.js +19 -0
  108. package/dist/schema/settings.js.map +1 -0
  109. package/dist/unit-of-work.d.ts +13 -0
  110. package/dist/unit-of-work.js +16 -0
  111. package/dist/unit-of-work.js.map +1 -0
  112. package/drizzle/0000_baseline.sql +526 -0
  113. package/drizzle/meta/0000_snapshot.json +3925 -0
  114. package/drizzle/meta/_journal.json +13 -0
  115. package/package.json +56 -0
@@ -0,0 +1,163 @@
1
+ import { organizations } from "./organizations.js";
2
+ import { assets } from "./assets.js";
3
+ import { sql } from "drizzle-orm";
4
+ import { check, foreignKey, integer, jsonb, pgTable, primaryKey, text, timestamp, unique } from "drizzle-orm/pg-core";
5
+ import { genId } from "@headless-lms/core/shared/id";
6
+ //#region src/schema/content.ts
7
+ const contentItems = pgTable("content_items", {
8
+ orgId: text("org_id").notNull().references(() => organizations.id),
9
+ id: text("id").notNull(),
10
+ type: text("type", { enum: ["course", "download"] }).notNull(),
11
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
12
+ }, (t) => ({
13
+ pk: primaryKey({ columns: [t.orgId, t.id] }),
14
+ typeUq: unique().on(t.orgId, t.id, t.type),
15
+ typeCk: check("content_items_type_check", sql`${t.type} in ('course', 'download')`)
16
+ }));
17
+ const courses = pgTable("courses", {
18
+ orgId: text("org_id").notNull().references(() => organizations.id),
19
+ id: text("id").notNull().$defaultFn(() => genId("course")),
20
+ type: text("type").$type().notNull().generatedAlwaysAs(sql`'course'`),
21
+ title: text("title").notNull(),
22
+ slug: text("slug").notNull(),
23
+ description: text("description").notNull().default(""),
24
+ status: text("status", { enum: ["draft", "published"] }).notNull().default("draft"),
25
+ category: text("category").notNull().default(""),
26
+ thumbnailAssetId: text("thumbnail_asset_id"),
27
+ settings: jsonb("settings").$type().notNull().default({}),
28
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
29
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date())
30
+ }, (t) => ({
31
+ pk: primaryKey({ columns: [t.orgId, t.id] }),
32
+ slugUq: unique().on(t.orgId, t.slug),
33
+ contentItemFk: foreignKey({
34
+ columns: [
35
+ t.orgId,
36
+ t.id,
37
+ t.type
38
+ ],
39
+ foreignColumns: [
40
+ contentItems.orgId,
41
+ contentItems.id,
42
+ contentItems.type
43
+ ]
44
+ }).onDelete("cascade"),
45
+ thumbnailAssetFk: foreignKey({
46
+ columns: [t.orgId, t.thumbnailAssetId],
47
+ foreignColumns: [assets.orgId, assets.id]
48
+ })
49
+ }));
50
+ const modules = pgTable("modules", {
51
+ orgId: text("org_id").notNull().references(() => organizations.id),
52
+ id: text("id").notNull().$defaultFn(() => genId("module")),
53
+ courseId: text("course_id").notNull(),
54
+ title: text("title").notNull(),
55
+ seq: integer("seq").notNull(),
56
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
57
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date())
58
+ }, (t) => ({
59
+ pk: primaryKey({ columns: [t.orgId, t.id] }),
60
+ courseFk: foreignKey({
61
+ columns: [t.orgId, t.courseId],
62
+ foreignColumns: [courses.orgId, courses.id]
63
+ }).onDelete("cascade"),
64
+ courseUq: unique().on(t.orgId, t.id, t.courseId)
65
+ }));
66
+ const activities = pgTable("activities", {
67
+ orgId: text("org_id").notNull().references(() => organizations.id),
68
+ id: text("id").notNull().$defaultFn(() => genId("activity")),
69
+ moduleId: text("module_id").notNull(),
70
+ courseId: text("course_id").notNull(),
71
+ seq: integer("seq").notNull(),
72
+ settings: jsonb("settings").$type(),
73
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
74
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date())
75
+ }, (t) => ({
76
+ pk: primaryKey({ columns: [t.orgId, t.id] }),
77
+ moduleFk: foreignKey({
78
+ columns: [
79
+ t.orgId,
80
+ t.moduleId,
81
+ t.courseId
82
+ ],
83
+ foreignColumns: [
84
+ modules.orgId,
85
+ modules.id,
86
+ modules.courseId
87
+ ]
88
+ }).onDelete("cascade"),
89
+ seqUq: unique().on(t.orgId, t.moduleId, t.seq)
90
+ }));
91
+ const activityAssets = pgTable("activity_assets", {
92
+ orgId: text("org_id").notNull().references(() => organizations.id),
93
+ id: text("id").notNull().$defaultFn(() => genId("activityAsset")),
94
+ activityId: text("activity_id").notNull(),
95
+ assetId: text("asset_id").notNull(),
96
+ seq: integer("seq").notNull().default(0)
97
+ }, (t) => ({
98
+ pk: primaryKey({ columns: [t.orgId, t.id] }),
99
+ activityFk: foreignKey({
100
+ columns: [t.orgId, t.activityId],
101
+ foreignColumns: [activities.orgId, activities.id]
102
+ }).onDelete("cascade"),
103
+ assetFk: foreignKey({
104
+ columns: [t.orgId, t.assetId],
105
+ foreignColumns: [assets.orgId, assets.id]
106
+ }),
107
+ activityAssetUq: unique().on(t.orgId, t.activityId, t.assetId)
108
+ }));
109
+ const downloads = pgTable("downloads", {
110
+ orgId: text("org_id").notNull().references(() => organizations.id),
111
+ id: text("id").notNull().$defaultFn(() => genId("download")),
112
+ type: text("type").$type().notNull().generatedAlwaysAs(sql`'download'`),
113
+ title: text("title").notNull(),
114
+ slug: text("slug").notNull(),
115
+ description: text("description").notNull().default(""),
116
+ status: text("status", { enum: ["draft", "published"] }).notNull().default("draft"),
117
+ category: text("category").notNull().default(""),
118
+ thumbnailAssetId: text("thumbnail_asset_id"),
119
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
120
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date())
121
+ }, (t) => ({
122
+ pk: primaryKey({ columns: [t.orgId, t.id] }),
123
+ slugUq: unique().on(t.orgId, t.slug),
124
+ contentItemFk: foreignKey({
125
+ columns: [
126
+ t.orgId,
127
+ t.id,
128
+ t.type
129
+ ],
130
+ foreignColumns: [
131
+ contentItems.orgId,
132
+ contentItems.id,
133
+ contentItems.type
134
+ ]
135
+ }).onDelete("cascade"),
136
+ thumbnailFk: foreignKey({
137
+ columns: [t.orgId, t.thumbnailAssetId],
138
+ foreignColumns: [assets.orgId, assets.id]
139
+ })
140
+ }));
141
+ const downloadAssets = pgTable("download_assets", {
142
+ orgId: text("org_id").notNull().references(() => organizations.id),
143
+ id: text("id").notNull().$defaultFn(() => genId("downloadAsset")),
144
+ downloadId: text("download_id").notNull(),
145
+ assetId: text("asset_id").notNull(),
146
+ seq: integer("seq").notNull().default(0),
147
+ displayName: text("display_name")
148
+ }, (t) => ({
149
+ pk: primaryKey({ columns: [t.orgId, t.id] }),
150
+ downloadFk: foreignKey({
151
+ columns: [t.orgId, t.downloadId],
152
+ foreignColumns: [downloads.orgId, downloads.id]
153
+ }).onDelete("cascade"),
154
+ assetFk: foreignKey({
155
+ columns: [t.orgId, t.assetId],
156
+ foreignColumns: [assets.orgId, assets.id]
157
+ }),
158
+ downloadAssetUq: unique().on(t.orgId, t.downloadId, t.assetId)
159
+ }));
160
+ //#endregion
161
+ export { activities, activityAssets, contentItems, courses, downloadAssets, downloads, modules };
162
+
163
+ //# sourceMappingURL=content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.js","names":[],"sources":["../../src/schema/content.ts"],"sourcesContent":["// content tables — the content domain (owns all content types; today: course).\n// Course type: Course → Module → Activity, where an Activity is the leaf sitting\n// directly in a module. An Activity is UNIFORM content — the domain does not\n// categorise it: a `seq`, and an opaque `settings` jsonb blob holding whatever\n// that content needs (title, type, body, completion rule, …). Assets are the one\n// thing kept OUT of the blob (owned by the assets domain) and linked via the\n// many-to-many `activity_assets`. Org-scoped: composite (org_id, id) keys.\nimport {\n pgTable,\n text,\n integer,\n jsonb,\n timestamp,\n primaryKey,\n foreignKey,\n unique,\n check,\n} from 'drizzle-orm/pg-core';\nimport { sql } from 'drizzle-orm';\nimport type {\n Activity,\n ActivityAsset,\n ContentItem,\n Course,\n Download,\n DownloadAsset,\n Module,\n} from '@headless-lms/core/schemas';\nimport { genId } from '@headless-lms/core/shared/id';\nimport { organizations } from './organizations.js';\nimport { assets } from './assets.js';\nimport type { Expect, NoDrift } from './drift.js';\n\n// The content registry (supertype table): one row per piece of content, any\n// type. A concrete content table shares its PK with a registry row (same id)\n// and references it via a type-pinned composite FK; the generic entitlements\n// table FKs here, so it never changes when a content type is added. Deletes go\n// through this table (cascade to the concrete row and the grants).\nexport const contentItems = pgTable(\n 'content_items',\n {\n orgId: text('org_id')\n .notNull()\n .references(() => organizations.id),\n id: text('id').notNull(),\n type: text('type', { enum: ['course', 'download'] }).notNull(),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n },\n (t) => ({\n pk: primaryKey({ columns: [t.orgId, t.id] }),\n // FK target for type-pinned references from concrete content tables.\n typeUq: unique().on(t.orgId, t.id, t.type),\n // Widened per new content type.\n typeCk: check('content_items_type_check', sql`${t.type} in ('course', 'download')`),\n }),\n);\n\nexport const courses = pgTable(\n 'courses',\n {\n orgId: text('org_id')\n .notNull()\n .references(() => organizations.id),\n id: text('id')\n .notNull()\n .$defaultFn(() => genId('course')),\n // Pinned to 'course' so the composite FK below cannot attach this row to a\n // registry row of another content type.\n type: text('type')\n .$type<Course['type']>()\n .notNull()\n .generatedAlwaysAs(sql`'course'`),\n title: text('title').notNull(),\n slug: text('slug').notNull(),\n description: text('description').notNull().default(''),\n status: text('status', { enum: ['draft', 'published'] })\n .notNull()\n .default('draft'),\n category: text('category').notNull().default(''),\n // Media-library asset rendered as the course's cover. Null = no cover.\n thumbnailAssetId: text('thumbnail_asset_id'),\n // Course-wide delivery toggles (CourseSettings). Modelled, unlike the\n // per-activity blob: a fixed surface the API validates.\n settings: jsonb('settings').$type<Course['settings']>().notNull().default({}),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true })\n .notNull()\n .defaultNow()\n .$onUpdate(() => new Date()),\n },\n (t) => ({\n pk: primaryKey({ columns: [t.orgId, t.id] }),\n slugUq: unique().on(t.orgId, t.slug),\n contentItemFk: foreignKey({\n columns: [t.orgId, t.id, t.type],\n foreignColumns: [contentItems.orgId, contentItems.id, contentItems.type],\n }).onDelete('cascade'),\n // Restrictive, like `activity_assets.assetFk` — the asset is owned by the\n // assets domain and must be unlinked before it can be deleted. MATCH SIMPLE\n // means the constraint is skipped entirely while the thumbnail is null.\n thumbnailAssetFk: foreignKey({\n columns: [t.orgId, t.thumbnailAssetId],\n foreignColumns: [assets.orgId, assets.id],\n }),\n }),\n);\n\nexport const modules = pgTable(\n 'modules',\n {\n orgId: text('org_id')\n .notNull()\n .references(() => organizations.id),\n id: text('id')\n .notNull()\n .$defaultFn(() => genId('module')),\n courseId: text('course_id').notNull(),\n title: text('title').notNull(),\n seq: integer('seq').notNull(),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true })\n .notNull()\n .defaultNow()\n .$onUpdate(() => new Date()),\n },\n (t) => ({\n pk: primaryKey({ columns: [t.orgId, t.id] }),\n // Modules are part of the course aggregate: deleting the course deletes them.\n courseFk: foreignKey({\n columns: [t.orgId, t.courseId],\n foreignColumns: [courses.orgId, courses.id],\n }).onDelete('cascade'),\n // FK target for the course-pinned reference from activities.\n courseUq: unique().on(t.orgId, t.id, t.courseId),\n }),\n);\n\n// The leaf, directly in a module. Uniform content: seq + opaque settings blob.\nexport const activities = pgTable(\n 'activities',\n {\n orgId: text('org_id')\n .notNull()\n .references(() => organizations.id),\n id: text('id')\n .notNull()\n .$defaultFn(() => genId('activity')),\n moduleId: text('module_id').notNull(),\n // Denormalised from the module: the course an activity belongs to is asked\n // for far more often than its module (progress, discussion, entitlements),\n // and the FK below pins the pair, so it cannot disagree with the module.\n courseId: text('course_id').notNull(),\n seq: integer('seq').notNull(),\n // Opaque per-activity blob: title, type, body, completion rule — whatever the\n // content needs. Assets are the one thing kept out of the blob.\n settings: jsonb('settings').$type<Activity['settings']>(),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true })\n .notNull()\n .defaultNow()\n .$onUpdate(() => new Date()),\n },\n (t) => ({\n pk: primaryKey({ columns: [t.orgId, t.id] }),\n // Course-pinned, like `courses.contentItemFk`: the module reference carries\n // `course_id`, so an activity whose stored course disagrees with its\n // module's is rejected by the database rather than silently rotting.\n moduleFk: foreignKey({\n columns: [t.orgId, t.moduleId, t.courseId],\n foreignColumns: [modules.orgId, modules.id, modules.courseId],\n }).onDelete('cascade'),\n seqUq: unique().on(t.orgId, t.moduleId, t.seq),\n }),\n);\n\n// activity ↔ asset: many-to-many (an activity uses many assets; an asset can be\n// reused by many activities). Assets owned by the assets domain, tracked here.\nexport const activityAssets = pgTable(\n 'activity_assets',\n {\n orgId: text('org_id')\n .notNull()\n .references(() => organizations.id),\n id: text('id')\n .notNull()\n .$defaultFn(() => genId('activityAsset')),\n activityId: text('activity_id').notNull(),\n assetId: text('asset_id').notNull(),\n seq: integer('seq').notNull().default(0),\n },\n (t) => ({\n pk: primaryKey({ columns: [t.orgId, t.id] }),\n // The link cascades with its activity; the asset itself is owned by the\n // assets domain and survives (assetFk stays restrictive).\n activityFk: foreignKey({\n columns: [t.orgId, t.activityId],\n foreignColumns: [activities.orgId, activities.id],\n }).onDelete('cascade'),\n assetFk: foreignKey({\n columns: [t.orgId, t.assetId],\n foreignColumns: [assets.orgId, assets.id],\n }),\n activityAssetUq: unique().on(t.orgId, t.activityId, t.assetId),\n }),\n);\n\n// A download: an ordered set of media-library assets. Shares its PK with a\n// registry row (same id) via the type-pinned composite FK, exactly like courses.\nexport const downloads = pgTable(\n 'downloads',\n {\n orgId: text('org_id')\n .notNull()\n .references(() => organizations.id),\n id: text('id')\n .notNull()\n .$defaultFn(() => genId('download')),\n // Pinned to 'download' so the composite FK below cannot attach this row to\n // a registry row of another content type.\n type: text('type')\n .$type<Download['type']>()\n .notNull()\n .generatedAlwaysAs(sql`'download'`),\n title: text('title').notNull(),\n slug: text('slug').notNull(),\n description: text('description').notNull().default(''),\n status: text('status', { enum: ['draft', 'published'] })\n .notNull()\n .default('draft'),\n category: text('category').notNull().default(''),\n thumbnailAssetId: text('thumbnail_asset_id'),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true })\n .notNull()\n .defaultNow()\n .$onUpdate(() => new Date()),\n },\n (t) => ({\n pk: primaryKey({ columns: [t.orgId, t.id] }),\n slugUq: unique().on(t.orgId, t.slug),\n contentItemFk: foreignKey({\n columns: [t.orgId, t.id, t.type],\n foreignColumns: [contentItems.orgId, contentItems.id, contentItems.type],\n }).onDelete('cascade'),\n // Restrictive: a thumbnail in use blocks deleting the asset.\n thumbnailFk: foreignKey({\n columns: [t.orgId, t.thumbnailAssetId],\n foreignColumns: [assets.orgId, assets.id],\n }),\n }),\n);\n\n// download ↔ asset: the ordered set. Mirrors activity_assets one level\n// shallower — a download has no intermediate structure.\nexport const downloadAssets = pgTable(\n 'download_assets',\n {\n orgId: text('org_id')\n .notNull()\n .references(() => organizations.id),\n id: text('id')\n .notNull()\n .$defaultFn(() => genId('downloadAsset')),\n downloadId: text('download_id').notNull(),\n assetId: text('asset_id').notNull(),\n seq: integer('seq').notNull().default(0),\n // Author's label; null falls back to the asset's filename.\n displayName: text('display_name'),\n },\n (t) => ({\n pk: primaryKey({ columns: [t.orgId, t.id] }),\n // The link cascades with its download; the asset is owned by the assets\n // domain and survives (assetFk stays restrictive).\n downloadFk: foreignKey({\n columns: [t.orgId, t.downloadId],\n foreignColumns: [downloads.orgId, downloads.id],\n }).onDelete('cascade'),\n assetFk: foreignKey({\n columns: [t.orgId, t.assetId],\n foreignColumns: [assets.orgId, assets.id],\n }),\n // Lets delivery key on asset_id rather than the link row id.\n downloadAssetUq: unique().on(t.orgId, t.downloadId, t.assetId),\n }),\n);\n\ntype _ContentItemsDrift = Expect<NoDrift<typeof contentItems.$inferSelect, ContentItem>>;\ntype _CoursesDrift = Expect<NoDrift<typeof courses.$inferSelect, Course>>;\ntype _ModulesDrift = Expect<NoDrift<typeof modules.$inferSelect, Module>>;\ntype _ActivitiesDrift = Expect<NoDrift<typeof activities.$inferSelect, Activity>>;\ntype _ActivityAssetsDrift = Expect<NoDrift<typeof activityAssets.$inferSelect, ActivityAsset>>;\ntype _DownloadsDrift = Expect<NoDrift<typeof downloads.$inferSelect, Download>>;\ntype _DownloadAssetsDrift = Expect<NoDrift<typeof downloadAssets.$inferSelect, DownloadAsset>>;\n"],"mappings":"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"}
@@ -0,0 +1,113 @@
1
+ //#region src/schema/credentials.d.ts
2
+ declare const credentials: import("drizzle-orm/pg-core").PgTableWithColumns<{
3
+ name: "credentials";
4
+ schema: undefined;
5
+ columns: {
6
+ orgId: import("drizzle-orm/pg-core").PgColumn<{
7
+ name: "org_id";
8
+ tableName: "credentials";
9
+ dataType: "string";
10
+ columnType: "PgText";
11
+ data: string;
12
+ driverParam: string;
13
+ notNull: true;
14
+ hasDefault: false;
15
+ isPrimaryKey: false;
16
+ isAutoincrement: false;
17
+ hasRuntimeDefault: false;
18
+ enumValues: [string, ...string[]];
19
+ baseColumn: never;
20
+ identity: undefined;
21
+ generated: undefined;
22
+ }, {}, {}>;
23
+ id: import("drizzle-orm/pg-core").PgColumn<{
24
+ name: "id";
25
+ tableName: "credentials";
26
+ dataType: "string";
27
+ columnType: "PgText";
28
+ data: string;
29
+ driverParam: string;
30
+ notNull: true;
31
+ hasDefault: false;
32
+ isPrimaryKey: false;
33
+ isAutoincrement: false;
34
+ hasRuntimeDefault: false;
35
+ enumValues: [string, ...string[]];
36
+ baseColumn: never;
37
+ identity: undefined;
38
+ generated: undefined;
39
+ }, {}, {}>;
40
+ ciphertext: import("drizzle-orm/pg-core").PgColumn<{
41
+ name: "ciphertext";
42
+ tableName: "credentials";
43
+ dataType: "string";
44
+ columnType: "PgText";
45
+ data: string;
46
+ driverParam: string;
47
+ notNull: true;
48
+ hasDefault: false;
49
+ isPrimaryKey: false;
50
+ isAutoincrement: false;
51
+ hasRuntimeDefault: false;
52
+ enumValues: [string, ...string[]];
53
+ baseColumn: never;
54
+ identity: undefined;
55
+ generated: undefined;
56
+ }, {}, {}>;
57
+ keyVersion: import("drizzle-orm/pg-core").PgColumn<{
58
+ name: "key_version";
59
+ tableName: "credentials";
60
+ dataType: "number";
61
+ columnType: "PgInteger";
62
+ data: number;
63
+ driverParam: string | number;
64
+ notNull: true;
65
+ hasDefault: true;
66
+ isPrimaryKey: false;
67
+ isAutoincrement: false;
68
+ hasRuntimeDefault: false;
69
+ enumValues: undefined;
70
+ baseColumn: never;
71
+ identity: undefined;
72
+ generated: undefined;
73
+ }, {}, {}>;
74
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
75
+ name: "created_at";
76
+ tableName: "credentials";
77
+ dataType: "date";
78
+ columnType: "PgTimestamp";
79
+ data: Date;
80
+ driverParam: string;
81
+ notNull: true;
82
+ hasDefault: true;
83
+ isPrimaryKey: false;
84
+ isAutoincrement: false;
85
+ hasRuntimeDefault: false;
86
+ enumValues: undefined;
87
+ baseColumn: never;
88
+ identity: undefined;
89
+ generated: undefined;
90
+ }, {}, {}>;
91
+ updatedAt: import("drizzle-orm/pg-core").PgColumn<{
92
+ name: "updated_at";
93
+ tableName: "credentials";
94
+ dataType: "date";
95
+ columnType: "PgTimestamp";
96
+ data: Date;
97
+ driverParam: string;
98
+ notNull: true;
99
+ hasDefault: true;
100
+ isPrimaryKey: false;
101
+ isAutoincrement: false;
102
+ hasRuntimeDefault: false;
103
+ enumValues: undefined;
104
+ baseColumn: never;
105
+ identity: undefined;
106
+ generated: undefined;
107
+ }, {}, {}>;
108
+ };
109
+ dialect: "pg";
110
+ }>;
111
+ //#endregion
112
+ export { credentials };
113
+ //# sourceMappingURL=credentials.d.ts.map
@@ -0,0 +1,15 @@
1
+ import { organizations } from "./organizations.js";
2
+ import { integer, pgTable, primaryKey, text, timestamp } from "drizzle-orm/pg-core";
3
+ //#region src/schema/credentials.ts
4
+ const credentials = pgTable("credentials", {
5
+ orgId: text("org_id").notNull().references(() => organizations.id),
6
+ id: text("id").notNull(),
7
+ ciphertext: text("ciphertext").notNull(),
8
+ keyVersion: integer("key_version").notNull().default(1),
9
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
10
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
11
+ }, (t) => ({ pk: primaryKey({ columns: [t.orgId, t.id] }) }));
12
+ //#endregion
13
+ export { credentials };
14
+
15
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.js","names":[],"sources":["../../src/schema/credentials.ts"],"sourcesContent":["// credentials — the shared secure credential store. One row per secret,\n// org-scoped with the standard composite (org_id, id) key. `ciphertext` is\n// base64(iv ‖ auth tag ‖ data), AES-256-GCM with AAD = `${orgId}:${id}`, so a\n// row's payload cannot be decrypted if copied to another row or org.\n// `key_version` names the encryption key used, enabling rotation later.\nimport { pgTable, text, integer, timestamp, primaryKey } from 'drizzle-orm/pg-core';\nimport type { Credential } from '@headless-lms/core/schemas';\nimport { organizations } from './organizations.js';\nimport type { Expect, NoDrift } from './drift.js';\n\nexport const credentials = pgTable(\n 'credentials',\n {\n orgId: text('org_id')\n .notNull()\n .references(() => organizations.id),\n id: text('id').notNull(),\n ciphertext: text('ciphertext').notNull(),\n keyVersion: integer('key_version').notNull().default(1),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n },\n (t) => ({ pk: primaryKey({ columns: [t.orgId, t.id] }) }),\n);\n\ntype _CredentialsDrift = Expect<NoDrift<typeof credentials.$inferSelect, Credential>>;\n"],"mappings":";;;AAUA,MAAa,cAAc,QACzB,eACA;CACE,OAAO,KAAK,QAAQ,CAAC,CAClB,QAAQ,CAAC,CACT,iBAAiB,cAAc,EAAE;CACpC,IAAI,KAAK,IAAI,CAAC,CAAC,QAAQ;CACvB,YAAY,KAAK,YAAY,CAAC,CAAC,QAAQ;CACvC,YAAY,QAAQ,aAAa,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC;CACtD,WAAW,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,WAAW;CAChF,WAAW,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,WAAW;AAClF,IACC,OAAO,EAAE,IAAI,WAAW,EAAE,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,EAAE,EACzD"}