@headless-lms/adapter-db 0.6.1 → 0.6.3

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.
@@ -15,7 +15,6 @@ import { and, asc, desc, eq, exists, ilike, inArray, or, sql } from "drizzle-orm
15
15
  import { union } from "drizzle-orm/pg-core";
16
16
  //#region src/repositories/admin.ts
17
17
  const entitlementCountExpr = sql`count(${entitlements.id})`;
18
- const avgProgressExpr = sql`0`;
19
18
  function hasNoOrgRole() {
20
19
  return sql`not exists (
21
20
  select 1 from ${roleAssignments} ra
@@ -32,6 +31,33 @@ function invitedAs(roleKey) {
32
31
  function isStudent(memberRoleKey) {
33
32
  return and(hasNoOrgRole(), or(eq(orgUsers.status, "active"), and(eq(orgUsers.status, "invited"), invitedAs(memberRoleKey))));
34
33
  }
34
+ const avgProgressExpr = sql`coalesce((
35
+ select round(avg(per_course.progress))
36
+ from (
37
+ select case
38
+ when count(a.id) = 0 then 0
39
+ else round(
40
+ least(count(pr.id) filter (where pr.completed_at is not null)::numeric / count(a.id), 1) * 100
41
+ )
42
+ end as progress
43
+ from (
44
+ select distinct ca.content_id
45
+ from content_access ca
46
+ join courses c on c.org_id = ca.org_id and c.id = ca.content_id
47
+ where ca.org_id = ${orgUsers.orgId} and ca.org_user_id = ${orgUsers.id}
48
+ ) held
49
+ left join activities a
50
+ on a.org_id = ${orgUsers.orgId}
51
+ and a.course_id = held.content_id
52
+ and coalesce((a.settings ->> 'published')::boolean, true)
53
+ left join progress_records pr
54
+ on pr.org_id = ${orgUsers.orgId}
55
+ and pr.org_user_id = ${orgUsers.id}
56
+ and pr.target_type = 'activity'
57
+ and pr.target_id = a.id
58
+ group by held.content_id
59
+ ) per_course
60
+ ), 0)::int`;
35
61
  const lastActiveExpr = sql`(
36
62
  select max(pr.updated_at) from progress_records pr
37
63
  where pr.org_id = ${orgUsers.orgId} and pr.org_user_id = ${orgUsers.id}
@@ -434,12 +460,13 @@ var DrizzleAdminRepository = class {
434
460
  const descending = sort?.startsWith("-") ?? false;
435
461
  const field = sort ? descending ? sort.slice(1) : sort : "firstName";
436
462
  const dir = descending ? desc : asc;
463
+ const nullsLast = (expr) => descending ? sql`${expr} desc nulls last` : sql`${expr} asc nulls last`;
437
464
  switch (field) {
438
465
  case "email": return [dir(users.email)];
439
466
  case "entitlementCount": return [dir(entitlementCountExpr)];
440
467
  case "avgProgress": return [dir(avgProgressExpr)];
441
468
  case "joinedAt": return [dir(orgUsers.createdAt)];
442
- case "lastActiveAt": return [dir(lastActiveExpr)];
469
+ case "lastActiveAt": return [nullsLast(lastActiveExpr)];
443
470
  case "lastName": return [dir(studentLastName), dir(studentFirstName)];
444
471
  default: return [dir(studentFirstName), dir(studentLastName)];
445
472
  }
@@ -1 +1 @@
1
- {"version":3,"file":"admin.js","names":[],"sources":["../../src/repositories/admin.ts"],"sourcesContent":["// reporting/admin — Drizzle repository.\nimport { and, asc, desc, eq, exists, ilike, inArray, or, sql, type SQL } from \"drizzle-orm\";\nimport { union } from \"drizzle-orm/pg-core\";\nimport type { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport type {\n AdminReportRepository,\n CommunityAnalytics,\n CommunityMembersQuery,\n CourseActivityEngagement,\n CourseAnalytics,\n CourseEnrollmentPoint,\n CourseLearner,\n CourseLearnersQuery,\n EnrollmentPoint,\n EventAnalytics,\n EventAttendeeRow,\n LearnerGrantStatus,\n OverviewStats,\n Page,\n StudentAnalytics,\n StudentCourseProgress,\n StudentItem,\n StudentRow as StudentRecord,\n StudentsQuery,\n} from \"@headless-lms/core/reporting/admin\";\nimport {\n commentFlags,\n comments,\n communities,\n contentAccess,\n courses,\n entitlements,\n invites,\n messageFlags,\n messages,\n orgUsers,\n roleAssignments,\n postFlags,\n posts,\n spaces,\n users,\n} from \"../schema/index.js\";\nimport { user } from \"../schema/better-auth.js\";\nimport type { OrgUserProfile, OrgUserStatus } from \"@headless-lms/core/types\";\nimport type { Logger } from \"@headless-lms/core/shared/ports\";\nimport { noopLogger } from \"@headless-lms/core/shared/logger\";\nimport {\n orgUserProfileColumns,\n studentFirstName,\n studentLastName,\n studentProfileColumns,\n} from \"./org-user-profile.js\";\nimport { translateDbErrors } from \"./pg-errors.js\";\nimport { grantExpired, grantOpens } from \"./grant-covers.js\";\nimport { eventAnalyticsFigures, eventAttendeePage, upcomingEventCount } from \"./event-reports.js\";\n\nconst entitlementCountExpr = sql<number>`count(${entitlements.id})`;\n// Completion now lives in the progress domain; the students report no longer\n// derives a percentage from entitlements. Placeholder until wired to progress.\nconst avgProgressExpr = sql<number>`0`;\n// Last learning activity: the newest touch on any of the student's progress\n// records. A scalar subquery so it neither fans out the entitlement count nor\n// widens the GROUP BY.\nfunction hasNoOrgRole(): SQL {\n return sql`not exists (\n select 1 from ${roleAssignments} ra\n where ra.org_id = ${orgUsers.orgId} and ra.org_user_id = ${orgUsers.id} and ra.content_id is null\n )`;\n}\n\nfunction invitedAs(roleKey: string): SQL {\n return sql`not exists (\n select 1 from ${invites} i\n where i.org_id = ${orgUsers.orgId} and lower(i.email) = lower(${users.email})\n and i.status = 'pending' and i.role <> ${roleKey}\n )`;\n}\n\nfunction isStudent(memberRoleKey: string): SQL {\n return and(\n hasNoOrgRole(),\n or(\n eq(orgUsers.status, \"active\"),\n and(eq(orgUsers.status, \"invited\"), invitedAs(memberRoleKey)),\n ),\n )!;\n}\n\nconst lastActiveExpr = sql<Date | null>`(\n select max(pr.updated_at) from progress_records pr\n where pr.org_id = ${orgUsers.orgId} and pr.org_user_id = ${orgUsers.id}\n)`;\n\ninterface StudentRow {\n id: string;\n email: string;\n image: string | null;\n firstName: string | null;\n lastName: string | null;\n status: OrgUserStatus;\n createdAt: Date;\n entitlementCount: number;\n avgProgress: number;\n lastActiveAt: Date | null;\n}\n\nfunction toStudent(row: StudentRow, items: StudentItem[]): StudentRecord {\n return {\n items,\n id: row.id,\n email: row.email,\n image: row.image ?? null,\n firstName: row.firstName,\n lastName: row.lastName,\n status: row.status,\n entitlementCount: Number(row.entitlementCount),\n avgProgress: Number(row.avgProgress),\n joinedAt: row.createdAt.toISOString(),\n lastActiveAt: row.lastActiveAt ? new Date(row.lastActiveAt).toISOString() : null,\n };\n}\n\nexport class DrizzleAdminRepository implements AdminReportRepository {\n constructor(\n private readonly db: NodePgDatabase,\n private readonly logger: Logger = noopLogger,\n ) {}\n\n async overview(orgId: string): Promise<OverviewStats> {\n const [courseCounts] = await this.db\n .select({\n published: sql<number>`count(*) filter (where ${courses.status} = 'published')`,\n draft: sql<number>`count(*) filter (where ${courses.status} = 'draft')`,\n })\n .from(courses)\n .where(eq(courses.orgId, orgId));\n\n const [entitlementCounts] = await this.db\n .select({\n activeStudents: sql<number>`count(distinct ${contentAccess.orgUserId})`,\n expiringSoon: sql<number>`count(distinct ${contentAccess.entitlementId}) filter (where ${grantExpired(contentAccess.expiresAt, sql`now() + interval '14 days'`)})`,\n })\n .from(contentAccess)\n .where(eq(contentAccess.orgId, orgId));\n\n return {\n publishedCourses: Number(courseCounts?.published ?? 0),\n draftCourses: Number(courseCounts?.draft ?? 0),\n activeStudents: Number(entitlementCounts?.activeStudents ?? 0),\n expiringSoon: Number(entitlementCounts?.expiringSoon ?? 0),\n upcomingEvents: await upcomingEventCount(this.db, orgId),\n };\n }\n\n // Grants per day over the trailing window, zero-filled so the series has one\n // point per calendar day (UTC) even when nothing was granted.\n async enrollments(orgId: string, days: number): Promise<EnrollmentPoint[]> {\n const result = await this.db.execute(sql`\n select\n to_char(d.day, 'YYYY-MM-DD') as date,\n count(${entitlements.id})::int as count\n from generate_series(\n (now() at time zone 'utc')::date - (${days} - 1),\n (now() at time zone 'utc')::date,\n interval '1 day'\n ) as d(day)\n left join ${entitlements}\n on ${entitlements.orgId} = ${orgId}\n and (${entitlements.grantedAt} at time zone 'utc')::date = d.day::date\n group by d.day\n order by d.day\n `);\n const rows = result.rows as { date: string; count: number }[];\n return rows.map((r) => ({ date: r.date, count: Number(r.count) }));\n }\n\n private async courseExists(orgId: string, courseId: string): Promise<boolean> {\n const [row] = await this.db\n .select({ id: courses.id })\n .from(courses)\n .where(and(eq(courses.orgId, orgId), eq(courses.id, courseId)));\n return row !== undefined;\n }\n\n async courseAnalytics(orgId: string, courseId: string): Promise<CourseAnalytics | null> {\n if (!(await this.courseExists(orgId, courseId))) {\n return null;\n }\n\n const cohort = sql`\n select distinct org_user_id from content_access\n where org_id = ${orgId}\n and content_id = ${courseId}\n `;\n const publishedActivities = sql`\n select a.id, a.settings, a.seq, a.module_id\n from activities a\n where a.org_id = ${orgId}\n and a.course_id = ${courseId}\n and coalesce((a.settings ->> 'published')::boolean, true)\n `;\n\n const summaryResult = await this.db.execute(sql`\n with cohort as (${cohort}),\n acts as (${publishedActivities}),\n per_learner as (\n select c.org_user_id,\n count(pr.id) filter (where pr.completed_at is not null) as done\n from cohort c\n left join progress_records pr\n on pr.org_id = ${orgId}\n and pr.org_user_id = c.org_user_id\n and pr.target_type = 'activity'\n and pr.target_id in (select id from acts)\n group by c.org_user_id\n )\n select\n (select count(*) from cohort)::int as enrolled,\n (\n select count(distinct pr.org_user_id) from progress_records pr\n where pr.org_id = ${orgId}\n and pr.org_user_id in (select org_user_id from cohort)\n and (\n (pr.target_type = 'activity' and pr.target_id in (select id from acts))\n or (pr.target_type = 'course' and pr.target_id = ${courseId})\n )\n )::int as started,\n (\n select count(*) from progress_records pr\n where pr.org_id = ${orgId}\n and pr.org_user_id in (select org_user_id from cohort)\n and pr.target_type = 'course'\n and pr.target_id = ${courseId}\n and pr.completed_at is not null\n )::int as completed,\n coalesce((\n select round(avg(least(done::numeric / nullif((select count(*) from acts), 0), 1) * 100))\n from per_learner\n ), 0)::int as avg_progress\n `);\n const summary = summaryResult.rows[0] as {\n enrolled: number;\n started: number;\n completed: number;\n avg_progress: number;\n };\n\n const funnelResult = await this.db.execute(sql`\n with cohort as (${cohort})\n select\n a.id,\n coalesce(a.settings ->> 'title', '') as title,\n m.id as module_id,\n m.title as module_title,\n count(pr.id)::int as started,\n count(pr.id) filter (where pr.completed_at is not null)::int as completed\n from activities a\n join modules m on m.org_id = a.org_id and m.id = a.module_id\n left join progress_records pr\n on pr.org_id = ${orgId}\n and pr.target_type = 'activity'\n and pr.target_id = a.id\n and pr.org_user_id in (select org_user_id from cohort)\n where a.org_id = ${orgId}\n and a.course_id = ${courseId}\n and coalesce((a.settings ->> 'published')::boolean, true)\n group by a.id, a.settings, m.id, m.title, m.seq, a.seq\n order by m.seq, a.seq\n `);\n const activities: CourseActivityEngagement[] = (\n funnelResult.rows as {\n id: string;\n title: string;\n module_id: string;\n module_title: string;\n started: number;\n completed: number;\n }[]\n ).map((r) => ({\n activityId: r.id,\n title: r.title,\n moduleId: r.module_id,\n moduleTitle: r.module_title,\n started: Number(r.started),\n completed: Number(r.completed),\n }));\n\n return {\n enrolled: Number(summary?.enrolled ?? 0),\n started: Number(summary?.started ?? 0),\n completed: Number(summary?.completed ?? 0),\n avgProgress: Number(summary?.avg_progress ?? 0),\n activities,\n };\n }\n\n // Grants per day for this course over the trailing window, zero-filled so the\n // series has one point per calendar day (UTC) even when nothing was granted.\n async courseEnrollments(\n orgId: string,\n courseId: string,\n days: number,\n ): Promise<CourseEnrollmentPoint[] | null> {\n if (!(await this.courseExists(orgId, courseId))) {\n return null;\n }\n const result = await this.db.execute(sql`\n select\n to_char(d.day, 'YYYY-MM-DD') as date,\n count(${entitlements.id})::int as count\n from generate_series(\n (now() at time zone 'utc')::date - (${days} - 1),\n (now() at time zone 'utc')::date,\n interval '1 day'\n ) as d(day)\n left join ${entitlements}\n on ${entitlements.orgId} = ${orgId}\n and ${entitlements.contentId} = ${courseId}\n and (${entitlements.grantedAt} at time zone 'utc')::date = d.day::date\n group by d.day\n order by d.day\n `);\n const rows = result.rows as { date: string; count: number }[];\n return rows.map((r) => ({ date: r.date, count: Number(r.count) }));\n }\n\n async courseLearners(\n orgId: string,\n courseId: string,\n query: CourseLearnersQuery,\n ): Promise<Page<CourseLearner> | null> {\n if (!(await this.courseExists(orgId, courseId))) {\n return null;\n }\n const expired = grantExpired(entitlements.expiresAt);\n const grantStatus = sql<LearnerGrantStatus>`case\n when ${entitlements.status} = 'revoked' then 'revoked'\n when ${expired} then 'expired'\n else 'active'\n end`;\n const grantRank = sql`case\n when ${entitlements.status} = 'revoked' then 2\n when ${expired} then 1\n else 0\n end`;\n const learnerGrant = this.db.$with(\"learner_grant\").as(\n this.db\n .selectDistinctOn([entitlements.orgUserId], {\n orgUserId: entitlements.orgUserId,\n status: grantStatus.as(\"grant_status\"),\n source: entitlements.source,\n grantedAt: entitlements.grantedAt,\n expiresAt: entitlements.expiresAt,\n })\n .from(entitlements)\n .where(\n and(eq(entitlements.orgId, orgId), grantOpens(entitlements, sql`${courseId}`, \"course\")),\n )\n .orderBy(\n entitlements.orgUserId,\n grantRank,\n sql`${entitlements.expiresAt} desc nulls first`,\n desc(entitlements.grantedAt),\n ),\n );\n const publishedActivities = sql`(\n select a.id from activities a\n where a.org_id = ${orgId}\n and a.course_id = ${courseId}\n and coalesce((a.settings ->> 'published')::boolean, true)\n )`;\n const completedActivities = sql<number>`(\n select count(*) from progress_records pr\n where pr.org_id = ${orgId}\n and pr.org_user_id = ${orgUsers.id}\n and pr.target_type = 'activity'\n and pr.completed_at is not null\n and pr.target_id in ${publishedActivities}\n )`;\n const lastActivityAt = sql<Date | string | null>`(\n select max(pr.updated_at) from progress_records pr\n where pr.org_id = ${orgId}\n and pr.org_user_id = ${orgUsers.id}\n and (\n (pr.target_type = 'activity' and pr.target_id in ${publishedActivities})\n or (pr.target_type = 'course' and pr.target_id = ${courseId})\n )\n )`;\n const completedAt = sql<Date | string | null>`(\n select pr.completed_at from progress_records pr\n where pr.org_id = ${orgId}\n and pr.org_user_id = ${orgUsers.id}\n and pr.target_type = 'course'\n and pr.target_id = ${courseId}\n )`;\n\n const filters: SQL[] = [eq(orgUsers.orgId, orgId)];\n const q = query.search?.trim();\n if (q) {\n const like = `%${q}%`;\n filters.push(\n or(ilike(studentFirstName, like), ilike(studentLastName, like), ilike(users.email, like))!,\n );\n }\n const where = and(...filters);\n const learner = and(eq(orgUsers.orgId, orgId), eq(orgUsers.id, learnerGrant.orgUserId));\n\n const [[totals], [activityCount], rows] = await Promise.all([\n this.db\n .with(learnerGrant)\n .select({ total: sql<number>`count(*)` })\n .from(learnerGrant)\n .innerJoin(orgUsers, learner)\n .innerJoin(users, eq(users.id, orgUsers.userId))\n .where(where),\n this.db\n .select({ total: sql<number>`count(*)` })\n .from(sql`${publishedActivities} as published_activities`),\n this.db\n .with(learnerGrant)\n .select({\n ...studentProfileColumns,\n status: learnerGrant.status,\n source: learnerGrant.source,\n grantedAt: learnerGrant.grantedAt,\n expiresAt: learnerGrant.expiresAt,\n completedActivities,\n lastActivityAt,\n completedAt,\n })\n .from(learnerGrant)\n .innerJoin(orgUsers, learner)\n .innerJoin(users, eq(users.id, orgUsers.userId))\n .leftJoin(user, eq(user.id, users.externalId))\n .where(where)\n .orderBy(asc(studentFirstName), asc(studentLastName), asc(orgUsers.id))\n .limit(query.pageSize)\n .offset((query.page - 1) * query.pageSize),\n ]);\n\n const totalActivities = Number(activityCount?.total ?? 0);\n return {\n rows: rows.map((row) => {\n const done = Number(row.completedActivities);\n return {\n id: row.id,\n firstName: row.firstName,\n lastName: row.lastName,\n email: row.email,\n image: row.image ?? null,\n status: row.status,\n source: row.source,\n grantedAt: new Date(row.grantedAt).toISOString(),\n expiresAt: row.expiresAt ? new Date(row.expiresAt).toISOString() : null,\n totalActivities,\n completedActivities: done,\n progress: totalActivities > 0 ? Math.round(Math.min(done / totalActivities, 1) * 100) : 0,\n lastActivityAt: row.lastActivityAt ? new Date(row.lastActivityAt).toISOString() : null,\n completedAt: row.completedAt ? new Date(row.completedAt).toISOString() : null,\n };\n }),\n total: Number(totals?.total ?? 0),\n page: query.page,\n pageSize: query.pageSize,\n };\n }\n\n async listStudents(\n orgId: string,\n query: StudentsQuery,\n memberRoleKey: string,\n ): Promise<Page<StudentRecord>> {\n const filters: SQL[] = [eq(orgUsers.orgId, orgId), isStudent(memberRoleKey)];\n const q = query.search?.trim();\n if (q) {\n const like = `%${q}%`;\n filters.push(\n or(ilike(studentFirstName, like), ilike(studentLastName, like), ilike(users.email, like))!,\n );\n }\n const where = and(...filters);\n\n const [totals] = await this.db\n .select({ total: sql<number>`count(*)` })\n .from(orgUsers)\n .innerJoin(users, eq(users.id, orgUsers.userId))\n .where(where);\n\n const rows = await this.db\n .select({\n ...studentProfileColumns,\n status: orgUsers.status,\n createdAt: orgUsers.createdAt,\n entitlementCount: entitlementCountExpr,\n avgProgress: avgProgressExpr,\n lastActiveAt: lastActiveExpr,\n })\n .from(orgUsers)\n .leftJoin(\n entitlements,\n and(eq(entitlements.orgId, orgUsers.orgId), eq(entitlements.orgUserId, orgUsers.id)),\n )\n .innerJoin(users, eq(users.id, orgUsers.userId))\n .leftJoin(user, eq(user.id, users.externalId))\n .where(where)\n // Group by the full composite PK (orgId, id): grouping by id alone gives\n // Postgres no functional dependency for the other students columns.\n .groupBy(\n orgUsers.orgId,\n orgUsers.id,\n orgUsers.status,\n users.id,\n users.externalId,\n users.email,\n users.firstName,\n users.lastName,\n user.image,\n )\n .orderBy(...this.resolveOrder(query.sort))\n .limit(query.pageSize)\n .offset((query.page - 1) * query.pageSize);\n\n const items = await this.itemsOf(\n orgId,\n rows.map((row) => row.id),\n );\n return {\n rows: rows.map((row) => toStudent(row, items.get(row.id) ?? [])),\n total: Number(totals?.total ?? 0),\n page: query.page,\n pageSize: query.pageSize,\n };\n }\n\n async getStudent(orgId: string, id: string): Promise<StudentRecord | null> {\n const [row] = await this.db\n .select({\n ...studentProfileColumns,\n status: orgUsers.status,\n createdAt: orgUsers.createdAt,\n entitlementCount: entitlementCountExpr,\n avgProgress: avgProgressExpr,\n lastActiveAt: lastActiveExpr,\n })\n .from(orgUsers)\n .leftJoin(\n entitlements,\n and(eq(entitlements.orgId, orgUsers.orgId), eq(entitlements.orgUserId, orgUsers.id)),\n )\n .innerJoin(users, eq(users.id, orgUsers.userId))\n .leftJoin(user, eq(user.id, users.externalId))\n .where(and(eq(orgUsers.orgId, orgId), eq(orgUsers.id, id)))\n .groupBy(\n orgUsers.orgId,\n orgUsers.id,\n orgUsers.status,\n users.id,\n users.externalId,\n users.email,\n users.firstName,\n users.lastName,\n user.image,\n )\n .limit(1);\n if (!row) {\n return null;\n }\n const items = await this.itemsOf(orgId, [row.id]);\n return toStudent(row, items.get(row.id) ?? []);\n }\n\n private async itemsOf(orgId: string, orgUserIds: string[]): Promise<Map<string, StudentItem[]>> {\n const byPerson = new Map<string, StudentItem[]>();\n if (orgUserIds.length === 0) {\n return byPerson;\n }\n const rows = await this.db\n .select({\n orgUserId: roleAssignments.orgUserId,\n contentId: roleAssignments.contentId,\n contentType: roleAssignments.contentType,\n })\n .from(roleAssignments)\n .where(\n and(\n eq(roleAssignments.orgId, orgId),\n inArray(roleAssignments.orgUserId, orgUserIds),\n sql`${roleAssignments.contentId} is not null`,\n ),\n )\n .orderBy(asc(roleAssignments.createdAt));\n for (const row of rows) {\n if (row.contentId === null || row.contentType === null) {\n continue;\n }\n const items = byPerson.get(row.orgUserId) ?? [];\n items.push({ contentId: row.contentId, contentType: row.contentType });\n byPerson.set(row.orgUserId, items);\n }\n return byPerson;\n }\n\n // Learner record: one row per course the student holds an effective-active\n // entitlement to (mirroring the course-analytics cohort rule), with progress\n // computed over published activities the same way the course analytics read does.\n async studentAnalytics(orgId: string, id: string): Promise<StudentAnalytics | null> {\n const [exists] = await this.db\n .select({ id: orgUsers.id })\n .from(orgUsers)\n .where(and(eq(orgUsers.orgId, orgId), eq(orgUsers.id, id)));\n if (!exists) {\n return null;\n }\n\n const result = await this.db.execute(sql`\n with cohort_courses as (\n select distinct c.id, c.title\n from content_access ca\n join courses c on c.org_id = ca.org_id and c.id = ca.content_id\n where ca.org_id = ${orgId}\n and ca.org_user_id = ${id}\n ),\n acts as (\n select a.course_id, a.id\n from activities a\n where a.org_id = ${orgId}\n and a.course_id in (select id from cohort_courses)\n and coalesce((a.settings ->> 'published')::boolean, true)\n )\n select\n cc.id as course_id,\n cc.title,\n count(a.id)::int as total_activities,\n count(pr.id) filter (where pr.completed_at is not null)::int as completed_activities,\n min(pr.started_at) as started_at,\n max(pr.updated_at) as last_activity_at,\n (\n select prc.completed_at from progress_records prc\n where prc.org_id = ${orgId}\n and prc.org_user_id = ${id}\n and prc.target_type = 'course'\n and prc.target_id = cc.id\n ) as completed_at\n from cohort_courses cc\n left join acts a on a.course_id = cc.id\n left join progress_records pr\n on pr.org_id = ${orgId}\n and pr.org_user_id = ${id}\n and pr.target_type = 'activity'\n and pr.target_id = a.id\n group by cc.id, cc.title\n order by cc.title\n `);\n\n const courses: StudentCourseProgress[] = (\n result.rows as {\n course_id: string;\n title: string;\n total_activities: number;\n completed_activities: number;\n started_at: Date | string | null;\n last_activity_at: Date | string | null;\n completed_at: Date | string | null;\n }[]\n ).map((r) => {\n const total = Number(r.total_activities);\n const done = Number(r.completed_activities);\n return {\n courseId: r.course_id,\n title: r.title,\n totalActivities: total,\n completedActivities: done,\n progress: total > 0 ? Math.round(Math.min(done / total, 1) * 100) : 0,\n startedAt: r.started_at ? new Date(r.started_at).toISOString() : null,\n lastActivityAt: r.last_activity_at ? new Date(r.last_activity_at).toISOString() : null,\n completedAt: r.completed_at ? new Date(r.completed_at).toISOString() : null,\n };\n });\n\n const started = courses.filter((c) => c.startedAt !== null).length;\n const completed = courses.filter((c) => c.completedAt !== null).length;\n const avgProgress =\n courses.length > 0\n ? Math.round(courses.reduce((sum, c) => sum + c.progress, 0) / courses.length)\n : 0;\n\n return { enrolled: courses.length, started, completed, avgProgress, courses };\n }\n\n private resolveOrder(sort?: string): SQL[] {\n const descending = sort?.startsWith(\"-\") ?? false;\n const field = sort ? (descending ? sort.slice(1) : sort) : \"firstName\";\n const dir = descending ? desc : asc;\n switch (field) {\n case \"email\":\n return [dir(users.email)];\n case \"entitlementCount\":\n return [dir(entitlementCountExpr)];\n case \"avgProgress\":\n return [dir(avgProgressExpr)];\n case \"joinedAt\":\n return [dir(orgUsers.createdAt)];\n case \"lastActiveAt\":\n return [dir(lastActiveExpr)];\n case \"lastName\":\n return [dir(studentLastName), dir(studentFirstName)];\n case \"firstName\":\n default:\n return [dir(studentFirstName), dir(studentLastName)];\n }\n }\n\n async communityAnalytics(orgId: string, communityId: string): Promise<CommunityAnalytics | null> {\n const [community] = await this.db\n .select({ id: communities.id })\n .from(communities)\n .where(and(eq(communities.orgId, orgId), eq(communities.id, communityId)))\n .limit(1);\n if (!community) {\n return null;\n }\n\n const opens = grantOpens(entitlements, sql`${communityId}`, \"community\");\n const lapsed = and(\n eq(entitlements.orgId, orgId),\n eq(entitlements.status, \"active\"),\n grantExpired(entitlements.expiresAt),\n opens,\n );\n const recent = sql`now() - interval '30 days'`;\n\n const [\n [members],\n [lapsedGrants],\n authors,\n postsPerSpace,\n commentsPerPost,\n [sent],\n [postFlagRow],\n [commentFlagRow],\n [messageFlagRow],\n lapsedHolders,\n ] = await Promise.all([\n this.db\n .select({ count: sql<number>`cast(count(distinct ${contentAccess.orgUserId}) as int)` })\n .from(contentAccess)\n .where(and(eq(contentAccess.orgId, orgId), eq(contentAccess.contentId, communityId))),\n this.db\n .select({ count: sql<number>`cast(count(*) as int)` })\n .from(entitlements)\n .where(lapsed),\n union(\n this.db\n .select({ id: posts.orgUserId })\n .from(posts)\n .where(\n and(\n eq(posts.orgId, orgId),\n eq(posts.communityId, communityId),\n sql`${posts.createdAt} >= ${recent}`,\n ),\n ),\n this.db\n .select({ id: comments.orgUserId })\n .from(comments)\n .where(\n and(\n eq(comments.orgId, orgId),\n eq(comments.communityId, communityId),\n sql`${comments.createdAt} >= ${recent}`,\n ),\n ),\n this.db\n .select({ id: messages.orgUserId })\n .from(messages)\n .where(\n and(\n eq(messages.orgId, orgId),\n eq(messages.communityId, communityId),\n sql`${messages.createdAt} >= ${recent}`,\n ),\n ),\n ),\n this.db\n .select({\n spaceId: spaces.id,\n count: sql<number>`cast(count(${posts.id}) as int)`,\n })\n .from(spaces)\n .leftJoin(\n posts,\n and(\n eq(posts.orgId, spaces.orgId),\n eq(posts.spaceId, spaces.id),\n eq(posts.status, \"published\"),\n ),\n )\n .where(and(eq(spaces.orgId, orgId), eq(spaces.communityId, communityId)))\n .groupBy(spaces.id),\n this.db\n .select({\n postId: posts.id,\n count: sql<number>`cast(count(${comments.id}) as int)`,\n })\n .from(posts)\n .leftJoin(\n comments,\n and(\n eq(comments.orgId, posts.orgId),\n eq(comments.postId, posts.id),\n eq(comments.status, \"published\"),\n ),\n )\n .where(\n and(\n eq(posts.orgId, orgId),\n eq(posts.communityId, communityId),\n eq(posts.status, \"published\"),\n ),\n )\n .groupBy(posts.id),\n this.db\n .select({ count: sql<number>`cast(count(*) as int)` })\n .from(messages)\n .where(and(eq(messages.orgId, orgId), eq(messages.communityId, communityId))),\n this.db\n .select({\n raised: sql<number>`cast(count(*) as int)`,\n resolved: sql<number>`cast(count(*) filter (where ${postFlags.resolvedAt} is not null) as int)`,\n })\n .from(postFlags)\n .innerJoin(posts, and(eq(posts.orgId, postFlags.orgId), eq(posts.id, postFlags.postId)))\n .where(and(eq(postFlags.orgId, orgId), eq(posts.communityId, communityId))),\n this.db\n .select({\n raised: sql<number>`cast(count(*) as int)`,\n resolved: sql<number>`cast(count(*) filter (where ${commentFlags.resolvedAt} is not null) as int)`,\n })\n .from(commentFlags)\n .innerJoin(\n comments,\n and(eq(comments.orgId, commentFlags.orgId), eq(comments.id, commentFlags.commentId)),\n )\n .where(and(eq(commentFlags.orgId, orgId), eq(comments.communityId, communityId))),\n this.db\n .select({\n raised: sql<number>`cast(count(*) as int)`,\n resolved: sql<number>`cast(count(*) filter (where ${messageFlags.resolvedAt} is not null) as int)`,\n })\n .from(messageFlags)\n .where(and(eq(messageFlags.orgId, orgId), eq(messageFlags.communityId, communityId))),\n this.db\n .select({ orgUserId: entitlements.orgUserId })\n .from(entitlements)\n .where(lapsed)\n .groupBy(entitlements.orgUserId),\n ]);\n\n const holderIds = lapsedHolders.map((row) => row.orgUserId);\n const [renewed] =\n holderIds.length === 0\n ? [{ count: 0 }]\n : await this.db\n .select({ count: sql<number>`cast(count(distinct ${contentAccess.orgUserId}) as int)` })\n .from(contentAccess)\n .where(\n and(\n eq(contentAccess.orgId, orgId),\n eq(contentAccess.contentId, communityId),\n inArray(contentAccess.orgUserId, holderIds),\n ),\n );\n\n const raised =\n Number(postFlagRow?.raised ?? 0) +\n Number(commentFlagRow?.raised ?? 0) +\n Number(messageFlagRow?.raised ?? 0);\n const resolved =\n Number(postFlagRow?.resolved ?? 0) +\n Number(commentFlagRow?.resolved ?? 0) +\n Number(messageFlagRow?.resolved ?? 0);\n\n return {\n memberCount: Number(members?.count ?? 0),\n activeMembers: authors.length,\n postsPerSpace: postsPerSpace.map((row) => ({\n spaceId: row.spaceId,\n count: Number(row.count),\n })),\n commentsPerPost: commentsPerPost.map((row) => ({\n postId: row.postId,\n count: Number(row.count),\n })),\n messagesSent: Number(sent?.count ?? 0),\n flagsRaised: raised,\n flagsResolved: resolved,\n grantsLapsed: Number(lapsedGrants?.count ?? 0),\n grantsRenewed: Number(renewed?.count ?? 0),\n };\n }\n\n async communityMembers(\n orgId: string,\n communityId: string,\n query: CommunityMembersQuery,\n ): Promise<Page<OrgUserProfile> | null> {\n const [community] = await this.db\n .select({ id: communities.id })\n .from(communities)\n .where(and(eq(communities.orgId, orgId), eq(communities.id, communityId)))\n .limit(1);\n if (!community) {\n return null;\n }\n const holds = exists(\n this.db\n .select({ id: contentAccess.entitlementId })\n .from(contentAccess)\n .where(\n and(\n eq(contentAccess.orgId, orgUsers.orgId),\n eq(contentAccess.orgUserId, orgUsers.id),\n eq(contentAccess.contentId, communityId),\n ),\n ),\n );\n const filters: SQL[] = [eq(orgUsers.orgId, orgId), holds];\n const q = query.search?.trim();\n if (q) {\n const like = `%${q}%`;\n filters.push(\n or(ilike(studentFirstName, like), ilike(studentLastName, like), ilike(users.email, like))!,\n );\n }\n const where = and(...filters);\n const [totals] = await this.db\n .select({ total: sql<number>`count(*)` })\n .from(orgUsers)\n .innerJoin(users, eq(users.id, orgUsers.userId))\n .where(where);\n const rows = await this.db\n .select(orgUserProfileColumns)\n .from(orgUsers)\n .innerJoin(users, eq(users.id, orgUsers.userId))\n .leftJoin(user, eq(user.id, users.externalId))\n .where(where)\n .orderBy(asc(users.firstName), asc(users.lastName), asc(orgUsers.id))\n .limit(query.pageSize)\n .offset((query.page - 1) * query.pageSize);\n return {\n rows: rows.map((row) => ({ ...row, image: row.image ?? null })),\n total: Number(totals?.total ?? 0),\n page: query.page,\n pageSize: query.pageSize,\n };\n }\n\n eventAttendees(\n orgId: string,\n eventId: string,\n page: { page: number; pageSize: number },\n ): Promise<Page<EventAttendeeRow>> {\n return eventAttendeePage(this.db, orgId, eventId, page);\n }\n\n eventAnalytics(\n orgId: string,\n eventId: string,\n startsAt: Date,\n ): Promise<Omit<EventAnalytics, \"capacity\">> {\n return eventAnalyticsFigures(this.db, orgId, eventId, startsAt);\n }\n}\ntranslateDbErrors(DrizzleAdminRepository);\n"],"mappings":"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"}
1
+ {"version":3,"file":"admin.js","names":[],"sources":["../../src/repositories/admin.ts"],"sourcesContent":["// reporting/admin — Drizzle repository.\nimport { and, asc, desc, eq, exists, ilike, inArray, or, sql, type SQL } from \"drizzle-orm\";\nimport { union } from \"drizzle-orm/pg-core\";\nimport type { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport type {\n AdminReportRepository,\n CommunityAnalytics,\n CommunityMembersQuery,\n CourseActivityEngagement,\n CourseAnalytics,\n CourseEnrollmentPoint,\n CourseLearner,\n CourseLearnersQuery,\n EnrollmentPoint,\n EventAnalytics,\n EventAttendeeRow,\n LearnerGrantStatus,\n OverviewStats,\n Page,\n StudentAnalytics,\n StudentCourseProgress,\n StudentItem,\n StudentRow as StudentRecord,\n StudentsQuery,\n} from \"@headless-lms/core/reporting/admin\";\nimport {\n commentFlags,\n comments,\n communities,\n contentAccess,\n courses,\n entitlements,\n invites,\n messageFlags,\n messages,\n orgUsers,\n roleAssignments,\n postFlags,\n posts,\n spaces,\n users,\n} from \"../schema/index.js\";\nimport { user } from \"../schema/better-auth.js\";\nimport type { OrgUserProfile, OrgUserStatus } from \"@headless-lms/core/types\";\nimport type { Logger } from \"@headless-lms/core/shared/ports\";\nimport { noopLogger } from \"@headless-lms/core/shared/logger\";\nimport {\n orgUserProfileColumns,\n studentFirstName,\n studentLastName,\n studentProfileColumns,\n} from \"./org-user-profile.js\";\nimport { translateDbErrors } from \"./pg-errors.js\";\nimport { grantExpired, grantOpens } from \"./grant-covers.js\";\nimport { eventAnalyticsFigures, eventAttendeePage, upcomingEventCount } from \"./event-reports.js\";\n\nconst entitlementCountExpr = sql<number>`count(${entitlements.id})`;\nfunction hasNoOrgRole(): SQL {\n return sql`not exists (\n select 1 from ${roleAssignments} ra\n where ra.org_id = ${orgUsers.orgId} and ra.org_user_id = ${orgUsers.id} and ra.content_id is null\n )`;\n}\n\nfunction invitedAs(roleKey: string): SQL {\n return sql`not exists (\n select 1 from ${invites} i\n where i.org_id = ${orgUsers.orgId} and lower(i.email) = lower(${users.email})\n and i.status = 'pending' and i.role <> ${roleKey}\n )`;\n}\n\nfunction isStudent(memberRoleKey: string): SQL {\n return and(\n hasNoOrgRole(),\n or(\n eq(orgUsers.status, \"active\"),\n and(eq(orgUsers.status, \"invited\"), invitedAs(memberRoleKey)),\n ),\n )!;\n}\n\n// The mean, over the courses the student can open, of the share of each\n// course's published activities they completed: the same figure as\n// studentAnalytics.avgProgress.\nconst avgProgressExpr = sql<number>`coalesce((\n select round(avg(per_course.progress))\n from (\n select case\n when count(a.id) = 0 then 0\n else round(\n least(count(pr.id) filter (where pr.completed_at is not null)::numeric / count(a.id), 1) * 100\n )\n end as progress\n from (\n select distinct ca.content_id\n from content_access ca\n join courses c on c.org_id = ca.org_id and c.id = ca.content_id\n where ca.org_id = ${orgUsers.orgId} and ca.org_user_id = ${orgUsers.id}\n ) held\n left join activities a\n on a.org_id = ${orgUsers.orgId}\n and a.course_id = held.content_id\n and coalesce((a.settings ->> 'published')::boolean, true)\n left join progress_records pr\n on pr.org_id = ${orgUsers.orgId}\n and pr.org_user_id = ${orgUsers.id}\n and pr.target_type = 'activity'\n and pr.target_id = a.id\n group by held.content_id\n ) per_course\n), 0)::int`;\n\n// Last learning activity: the newest touch on any of the student's progress\n// records. A scalar subquery so it neither fans out the entitlement count nor\n// widens the GROUP BY.\nconst lastActiveExpr = sql<Date | null>`(\n select max(pr.updated_at) from progress_records pr\n where pr.org_id = ${orgUsers.orgId} and pr.org_user_id = ${orgUsers.id}\n)`;\n\ninterface StudentRow {\n id: string;\n email: string;\n image: string | null;\n firstName: string | null;\n lastName: string | null;\n status: OrgUserStatus;\n createdAt: Date;\n entitlementCount: number;\n avgProgress: number;\n lastActiveAt: Date | null;\n}\n\nfunction toStudent(row: StudentRow, items: StudentItem[]): StudentRecord {\n return {\n items,\n id: row.id,\n email: row.email,\n image: row.image ?? null,\n firstName: row.firstName,\n lastName: row.lastName,\n status: row.status,\n entitlementCount: Number(row.entitlementCount),\n avgProgress: Number(row.avgProgress),\n joinedAt: row.createdAt.toISOString(),\n lastActiveAt: row.lastActiveAt ? new Date(row.lastActiveAt).toISOString() : null,\n };\n}\n\nexport class DrizzleAdminRepository implements AdminReportRepository {\n constructor(\n private readonly db: NodePgDatabase,\n private readonly logger: Logger = noopLogger,\n ) {}\n\n async overview(orgId: string): Promise<OverviewStats> {\n const [courseCounts] = await this.db\n .select({\n published: sql<number>`count(*) filter (where ${courses.status} = 'published')`,\n draft: sql<number>`count(*) filter (where ${courses.status} = 'draft')`,\n })\n .from(courses)\n .where(eq(courses.orgId, orgId));\n\n const [entitlementCounts] = await this.db\n .select({\n activeStudents: sql<number>`count(distinct ${contentAccess.orgUserId})`,\n expiringSoon: sql<number>`count(distinct ${contentAccess.entitlementId}) filter (where ${grantExpired(contentAccess.expiresAt, sql`now() + interval '14 days'`)})`,\n })\n .from(contentAccess)\n .where(eq(contentAccess.orgId, orgId));\n\n return {\n publishedCourses: Number(courseCounts?.published ?? 0),\n draftCourses: Number(courseCounts?.draft ?? 0),\n activeStudents: Number(entitlementCounts?.activeStudents ?? 0),\n expiringSoon: Number(entitlementCounts?.expiringSoon ?? 0),\n upcomingEvents: await upcomingEventCount(this.db, orgId),\n };\n }\n\n // Grants per day over the trailing window, zero-filled so the series has one\n // point per calendar day (UTC) even when nothing was granted.\n async enrollments(orgId: string, days: number): Promise<EnrollmentPoint[]> {\n const result = await this.db.execute(sql`\n select\n to_char(d.day, 'YYYY-MM-DD') as date,\n count(${entitlements.id})::int as count\n from generate_series(\n (now() at time zone 'utc')::date - (${days} - 1),\n (now() at time zone 'utc')::date,\n interval '1 day'\n ) as d(day)\n left join ${entitlements}\n on ${entitlements.orgId} = ${orgId}\n and (${entitlements.grantedAt} at time zone 'utc')::date = d.day::date\n group by d.day\n order by d.day\n `);\n const rows = result.rows as { date: string; count: number }[];\n return rows.map((r) => ({ date: r.date, count: Number(r.count) }));\n }\n\n private async courseExists(orgId: string, courseId: string): Promise<boolean> {\n const [row] = await this.db\n .select({ id: courses.id })\n .from(courses)\n .where(and(eq(courses.orgId, orgId), eq(courses.id, courseId)));\n return row !== undefined;\n }\n\n async courseAnalytics(orgId: string, courseId: string): Promise<CourseAnalytics | null> {\n if (!(await this.courseExists(orgId, courseId))) {\n return null;\n }\n\n const cohort = sql`\n select distinct org_user_id from content_access\n where org_id = ${orgId}\n and content_id = ${courseId}\n `;\n const publishedActivities = sql`\n select a.id, a.settings, a.seq, a.module_id\n from activities a\n where a.org_id = ${orgId}\n and a.course_id = ${courseId}\n and coalesce((a.settings ->> 'published')::boolean, true)\n `;\n\n const summaryResult = await this.db.execute(sql`\n with cohort as (${cohort}),\n acts as (${publishedActivities}),\n per_learner as (\n select c.org_user_id,\n count(pr.id) filter (where pr.completed_at is not null) as done\n from cohort c\n left join progress_records pr\n on pr.org_id = ${orgId}\n and pr.org_user_id = c.org_user_id\n and pr.target_type = 'activity'\n and pr.target_id in (select id from acts)\n group by c.org_user_id\n )\n select\n (select count(*) from cohort)::int as enrolled,\n (\n select count(distinct pr.org_user_id) from progress_records pr\n where pr.org_id = ${orgId}\n and pr.org_user_id in (select org_user_id from cohort)\n and (\n (pr.target_type = 'activity' and pr.target_id in (select id from acts))\n or (pr.target_type = 'course' and pr.target_id = ${courseId})\n )\n )::int as started,\n (\n select count(*) from progress_records pr\n where pr.org_id = ${orgId}\n and pr.org_user_id in (select org_user_id from cohort)\n and pr.target_type = 'course'\n and pr.target_id = ${courseId}\n and pr.completed_at is not null\n )::int as completed,\n coalesce((\n select round(avg(least(done::numeric / nullif((select count(*) from acts), 0), 1) * 100))\n from per_learner\n ), 0)::int as avg_progress\n `);\n const summary = summaryResult.rows[0] as {\n enrolled: number;\n started: number;\n completed: number;\n avg_progress: number;\n };\n\n const funnelResult = await this.db.execute(sql`\n with cohort as (${cohort})\n select\n a.id,\n coalesce(a.settings ->> 'title', '') as title,\n m.id as module_id,\n m.title as module_title,\n count(pr.id)::int as started,\n count(pr.id) filter (where pr.completed_at is not null)::int as completed\n from activities a\n join modules m on m.org_id = a.org_id and m.id = a.module_id\n left join progress_records pr\n on pr.org_id = ${orgId}\n and pr.target_type = 'activity'\n and pr.target_id = a.id\n and pr.org_user_id in (select org_user_id from cohort)\n where a.org_id = ${orgId}\n and a.course_id = ${courseId}\n and coalesce((a.settings ->> 'published')::boolean, true)\n group by a.id, a.settings, m.id, m.title, m.seq, a.seq\n order by m.seq, a.seq\n `);\n const activities: CourseActivityEngagement[] = (\n funnelResult.rows as {\n id: string;\n title: string;\n module_id: string;\n module_title: string;\n started: number;\n completed: number;\n }[]\n ).map((r) => ({\n activityId: r.id,\n title: r.title,\n moduleId: r.module_id,\n moduleTitle: r.module_title,\n started: Number(r.started),\n completed: Number(r.completed),\n }));\n\n return {\n enrolled: Number(summary?.enrolled ?? 0),\n started: Number(summary?.started ?? 0),\n completed: Number(summary?.completed ?? 0),\n avgProgress: Number(summary?.avg_progress ?? 0),\n activities,\n };\n }\n\n // Grants per day for this course over the trailing window, zero-filled so the\n // series has one point per calendar day (UTC) even when nothing was granted.\n async courseEnrollments(\n orgId: string,\n courseId: string,\n days: number,\n ): Promise<CourseEnrollmentPoint[] | null> {\n if (!(await this.courseExists(orgId, courseId))) {\n return null;\n }\n const result = await this.db.execute(sql`\n select\n to_char(d.day, 'YYYY-MM-DD') as date,\n count(${entitlements.id})::int as count\n from generate_series(\n (now() at time zone 'utc')::date - (${days} - 1),\n (now() at time zone 'utc')::date,\n interval '1 day'\n ) as d(day)\n left join ${entitlements}\n on ${entitlements.orgId} = ${orgId}\n and ${entitlements.contentId} = ${courseId}\n and (${entitlements.grantedAt} at time zone 'utc')::date = d.day::date\n group by d.day\n order by d.day\n `);\n const rows = result.rows as { date: string; count: number }[];\n return rows.map((r) => ({ date: r.date, count: Number(r.count) }));\n }\n\n async courseLearners(\n orgId: string,\n courseId: string,\n query: CourseLearnersQuery,\n ): Promise<Page<CourseLearner> | null> {\n if (!(await this.courseExists(orgId, courseId))) {\n return null;\n }\n const expired = grantExpired(entitlements.expiresAt);\n const grantStatus = sql<LearnerGrantStatus>`case\n when ${entitlements.status} = 'revoked' then 'revoked'\n when ${expired} then 'expired'\n else 'active'\n end`;\n const grantRank = sql`case\n when ${entitlements.status} = 'revoked' then 2\n when ${expired} then 1\n else 0\n end`;\n const learnerGrant = this.db.$with(\"learner_grant\").as(\n this.db\n .selectDistinctOn([entitlements.orgUserId], {\n orgUserId: entitlements.orgUserId,\n status: grantStatus.as(\"grant_status\"),\n source: entitlements.source,\n grantedAt: entitlements.grantedAt,\n expiresAt: entitlements.expiresAt,\n })\n .from(entitlements)\n .where(\n and(eq(entitlements.orgId, orgId), grantOpens(entitlements, sql`${courseId}`, \"course\")),\n )\n .orderBy(\n entitlements.orgUserId,\n grantRank,\n sql`${entitlements.expiresAt} desc nulls first`,\n desc(entitlements.grantedAt),\n ),\n );\n const publishedActivities = sql`(\n select a.id from activities a\n where a.org_id = ${orgId}\n and a.course_id = ${courseId}\n and coalesce((a.settings ->> 'published')::boolean, true)\n )`;\n const completedActivities = sql<number>`(\n select count(*) from progress_records pr\n where pr.org_id = ${orgId}\n and pr.org_user_id = ${orgUsers.id}\n and pr.target_type = 'activity'\n and pr.completed_at is not null\n and pr.target_id in ${publishedActivities}\n )`;\n const lastActivityAt = sql<Date | string | null>`(\n select max(pr.updated_at) from progress_records pr\n where pr.org_id = ${orgId}\n and pr.org_user_id = ${orgUsers.id}\n and (\n (pr.target_type = 'activity' and pr.target_id in ${publishedActivities})\n or (pr.target_type = 'course' and pr.target_id = ${courseId})\n )\n )`;\n const completedAt = sql<Date | string | null>`(\n select pr.completed_at from progress_records pr\n where pr.org_id = ${orgId}\n and pr.org_user_id = ${orgUsers.id}\n and pr.target_type = 'course'\n and pr.target_id = ${courseId}\n )`;\n\n const filters: SQL[] = [eq(orgUsers.orgId, orgId)];\n const q = query.search?.trim();\n if (q) {\n const like = `%${q}%`;\n filters.push(\n or(ilike(studentFirstName, like), ilike(studentLastName, like), ilike(users.email, like))!,\n );\n }\n const where = and(...filters);\n const learner = and(eq(orgUsers.orgId, orgId), eq(orgUsers.id, learnerGrant.orgUserId));\n\n const [[totals], [activityCount], rows] = await Promise.all([\n this.db\n .with(learnerGrant)\n .select({ total: sql<number>`count(*)` })\n .from(learnerGrant)\n .innerJoin(orgUsers, learner)\n .innerJoin(users, eq(users.id, orgUsers.userId))\n .where(where),\n this.db\n .select({ total: sql<number>`count(*)` })\n .from(sql`${publishedActivities} as published_activities`),\n this.db\n .with(learnerGrant)\n .select({\n ...studentProfileColumns,\n status: learnerGrant.status,\n source: learnerGrant.source,\n grantedAt: learnerGrant.grantedAt,\n expiresAt: learnerGrant.expiresAt,\n completedActivities,\n lastActivityAt,\n completedAt,\n })\n .from(learnerGrant)\n .innerJoin(orgUsers, learner)\n .innerJoin(users, eq(users.id, orgUsers.userId))\n .leftJoin(user, eq(user.id, users.externalId))\n .where(where)\n .orderBy(asc(studentFirstName), asc(studentLastName), asc(orgUsers.id))\n .limit(query.pageSize)\n .offset((query.page - 1) * query.pageSize),\n ]);\n\n const totalActivities = Number(activityCount?.total ?? 0);\n return {\n rows: rows.map((row) => {\n const done = Number(row.completedActivities);\n return {\n id: row.id,\n firstName: row.firstName,\n lastName: row.lastName,\n email: row.email,\n image: row.image ?? null,\n status: row.status,\n source: row.source,\n grantedAt: new Date(row.grantedAt).toISOString(),\n expiresAt: row.expiresAt ? new Date(row.expiresAt).toISOString() : null,\n totalActivities,\n completedActivities: done,\n progress: totalActivities > 0 ? Math.round(Math.min(done / totalActivities, 1) * 100) : 0,\n lastActivityAt: row.lastActivityAt ? new Date(row.lastActivityAt).toISOString() : null,\n completedAt: row.completedAt ? new Date(row.completedAt).toISOString() : null,\n };\n }),\n total: Number(totals?.total ?? 0),\n page: query.page,\n pageSize: query.pageSize,\n };\n }\n\n async listStudents(\n orgId: string,\n query: StudentsQuery,\n memberRoleKey: string,\n ): Promise<Page<StudentRecord>> {\n const filters: SQL[] = [eq(orgUsers.orgId, orgId), isStudent(memberRoleKey)];\n const q = query.search?.trim();\n if (q) {\n const like = `%${q}%`;\n filters.push(\n or(ilike(studentFirstName, like), ilike(studentLastName, like), ilike(users.email, like))!,\n );\n }\n const where = and(...filters);\n\n const [totals] = await this.db\n .select({ total: sql<number>`count(*)` })\n .from(orgUsers)\n .innerJoin(users, eq(users.id, orgUsers.userId))\n .where(where);\n\n const rows = await this.db\n .select({\n ...studentProfileColumns,\n status: orgUsers.status,\n createdAt: orgUsers.createdAt,\n entitlementCount: entitlementCountExpr,\n avgProgress: avgProgressExpr,\n lastActiveAt: lastActiveExpr,\n })\n .from(orgUsers)\n .leftJoin(\n entitlements,\n and(eq(entitlements.orgId, orgUsers.orgId), eq(entitlements.orgUserId, orgUsers.id)),\n )\n .innerJoin(users, eq(users.id, orgUsers.userId))\n .leftJoin(user, eq(user.id, users.externalId))\n .where(where)\n // Group by the full composite PK (orgId, id): grouping by id alone gives\n // Postgres no functional dependency for the other students columns.\n .groupBy(\n orgUsers.orgId,\n orgUsers.id,\n orgUsers.status,\n users.id,\n users.externalId,\n users.email,\n users.firstName,\n users.lastName,\n user.image,\n )\n .orderBy(...this.resolveOrder(query.sort))\n .limit(query.pageSize)\n .offset((query.page - 1) * query.pageSize);\n\n const items = await this.itemsOf(\n orgId,\n rows.map((row) => row.id),\n );\n return {\n rows: rows.map((row) => toStudent(row, items.get(row.id) ?? [])),\n total: Number(totals?.total ?? 0),\n page: query.page,\n pageSize: query.pageSize,\n };\n }\n\n async getStudent(orgId: string, id: string): Promise<StudentRecord | null> {\n const [row] = await this.db\n .select({\n ...studentProfileColumns,\n status: orgUsers.status,\n createdAt: orgUsers.createdAt,\n entitlementCount: entitlementCountExpr,\n avgProgress: avgProgressExpr,\n lastActiveAt: lastActiveExpr,\n })\n .from(orgUsers)\n .leftJoin(\n entitlements,\n and(eq(entitlements.orgId, orgUsers.orgId), eq(entitlements.orgUserId, orgUsers.id)),\n )\n .innerJoin(users, eq(users.id, orgUsers.userId))\n .leftJoin(user, eq(user.id, users.externalId))\n .where(and(eq(orgUsers.orgId, orgId), eq(orgUsers.id, id)))\n .groupBy(\n orgUsers.orgId,\n orgUsers.id,\n orgUsers.status,\n users.id,\n users.externalId,\n users.email,\n users.firstName,\n users.lastName,\n user.image,\n )\n .limit(1);\n if (!row) {\n return null;\n }\n const items = await this.itemsOf(orgId, [row.id]);\n return toStudent(row, items.get(row.id) ?? []);\n }\n\n private async itemsOf(orgId: string, orgUserIds: string[]): Promise<Map<string, StudentItem[]>> {\n const byPerson = new Map<string, StudentItem[]>();\n if (orgUserIds.length === 0) {\n return byPerson;\n }\n const rows = await this.db\n .select({\n orgUserId: roleAssignments.orgUserId,\n contentId: roleAssignments.contentId,\n contentType: roleAssignments.contentType,\n })\n .from(roleAssignments)\n .where(\n and(\n eq(roleAssignments.orgId, orgId),\n inArray(roleAssignments.orgUserId, orgUserIds),\n sql`${roleAssignments.contentId} is not null`,\n ),\n )\n .orderBy(asc(roleAssignments.createdAt));\n for (const row of rows) {\n if (row.contentId === null || row.contentType === null) {\n continue;\n }\n const items = byPerson.get(row.orgUserId) ?? [];\n items.push({ contentId: row.contentId, contentType: row.contentType });\n byPerson.set(row.orgUserId, items);\n }\n return byPerson;\n }\n\n // Learner record: one row per course the student holds an effective-active\n // entitlement to (mirroring the course-analytics cohort rule), with progress\n // computed over published activities the same way the course analytics read does.\n async studentAnalytics(orgId: string, id: string): Promise<StudentAnalytics | null> {\n const [exists] = await this.db\n .select({ id: orgUsers.id })\n .from(orgUsers)\n .where(and(eq(orgUsers.orgId, orgId), eq(orgUsers.id, id)));\n if (!exists) {\n return null;\n }\n\n const result = await this.db.execute(sql`\n with cohort_courses as (\n select distinct c.id, c.title\n from content_access ca\n join courses c on c.org_id = ca.org_id and c.id = ca.content_id\n where ca.org_id = ${orgId}\n and ca.org_user_id = ${id}\n ),\n acts as (\n select a.course_id, a.id\n from activities a\n where a.org_id = ${orgId}\n and a.course_id in (select id from cohort_courses)\n and coalesce((a.settings ->> 'published')::boolean, true)\n )\n select\n cc.id as course_id,\n cc.title,\n count(a.id)::int as total_activities,\n count(pr.id) filter (where pr.completed_at is not null)::int as completed_activities,\n min(pr.started_at) as started_at,\n max(pr.updated_at) as last_activity_at,\n (\n select prc.completed_at from progress_records prc\n where prc.org_id = ${orgId}\n and prc.org_user_id = ${id}\n and prc.target_type = 'course'\n and prc.target_id = cc.id\n ) as completed_at\n from cohort_courses cc\n left join acts a on a.course_id = cc.id\n left join progress_records pr\n on pr.org_id = ${orgId}\n and pr.org_user_id = ${id}\n and pr.target_type = 'activity'\n and pr.target_id = a.id\n group by cc.id, cc.title\n order by cc.title\n `);\n\n const courses: StudentCourseProgress[] = (\n result.rows as {\n course_id: string;\n title: string;\n total_activities: number;\n completed_activities: number;\n started_at: Date | string | null;\n last_activity_at: Date | string | null;\n completed_at: Date | string | null;\n }[]\n ).map((r) => {\n const total = Number(r.total_activities);\n const done = Number(r.completed_activities);\n return {\n courseId: r.course_id,\n title: r.title,\n totalActivities: total,\n completedActivities: done,\n progress: total > 0 ? Math.round(Math.min(done / total, 1) * 100) : 0,\n startedAt: r.started_at ? new Date(r.started_at).toISOString() : null,\n lastActivityAt: r.last_activity_at ? new Date(r.last_activity_at).toISOString() : null,\n completedAt: r.completed_at ? new Date(r.completed_at).toISOString() : null,\n };\n });\n\n const started = courses.filter((c) => c.startedAt !== null).length;\n const completed = courses.filter((c) => c.completedAt !== null).length;\n const avgProgress =\n courses.length > 0\n ? Math.round(courses.reduce((sum, c) => sum + c.progress, 0) / courses.length)\n : 0;\n\n return { enrolled: courses.length, started, completed, avgProgress, courses };\n }\n\n private resolveOrder(sort?: string): SQL[] {\n const descending = sort?.startsWith(\"-\") ?? false;\n const field = sort ? (descending ? sort.slice(1) : sort) : \"firstName\";\n const dir = descending ? desc : asc;\n const nullsLast = (expr: SQL): SQL =>\n descending ? sql`${expr} desc nulls last` : sql`${expr} asc nulls last`;\n switch (field) {\n case \"email\":\n return [dir(users.email)];\n case \"entitlementCount\":\n return [dir(entitlementCountExpr)];\n case \"avgProgress\":\n return [dir(avgProgressExpr)];\n case \"joinedAt\":\n return [dir(orgUsers.createdAt)];\n case \"lastActiveAt\":\n return [nullsLast(lastActiveExpr)];\n case \"lastName\":\n return [dir(studentLastName), dir(studentFirstName)];\n case \"firstName\":\n default:\n return [dir(studentFirstName), dir(studentLastName)];\n }\n }\n\n async communityAnalytics(orgId: string, communityId: string): Promise<CommunityAnalytics | null> {\n const [community] = await this.db\n .select({ id: communities.id })\n .from(communities)\n .where(and(eq(communities.orgId, orgId), eq(communities.id, communityId)))\n .limit(1);\n if (!community) {\n return null;\n }\n\n const opens = grantOpens(entitlements, sql`${communityId}`, \"community\");\n const lapsed = and(\n eq(entitlements.orgId, orgId),\n eq(entitlements.status, \"active\"),\n grantExpired(entitlements.expiresAt),\n opens,\n );\n const recent = sql`now() - interval '30 days'`;\n\n const [\n [members],\n [lapsedGrants],\n authors,\n postsPerSpace,\n commentsPerPost,\n [sent],\n [postFlagRow],\n [commentFlagRow],\n [messageFlagRow],\n lapsedHolders,\n ] = await Promise.all([\n this.db\n .select({ count: sql<number>`cast(count(distinct ${contentAccess.orgUserId}) as int)` })\n .from(contentAccess)\n .where(and(eq(contentAccess.orgId, orgId), eq(contentAccess.contentId, communityId))),\n this.db\n .select({ count: sql<number>`cast(count(*) as int)` })\n .from(entitlements)\n .where(lapsed),\n union(\n this.db\n .select({ id: posts.orgUserId })\n .from(posts)\n .where(\n and(\n eq(posts.orgId, orgId),\n eq(posts.communityId, communityId),\n sql`${posts.createdAt} >= ${recent}`,\n ),\n ),\n this.db\n .select({ id: comments.orgUserId })\n .from(comments)\n .where(\n and(\n eq(comments.orgId, orgId),\n eq(comments.communityId, communityId),\n sql`${comments.createdAt} >= ${recent}`,\n ),\n ),\n this.db\n .select({ id: messages.orgUserId })\n .from(messages)\n .where(\n and(\n eq(messages.orgId, orgId),\n eq(messages.communityId, communityId),\n sql`${messages.createdAt} >= ${recent}`,\n ),\n ),\n ),\n this.db\n .select({\n spaceId: spaces.id,\n count: sql<number>`cast(count(${posts.id}) as int)`,\n })\n .from(spaces)\n .leftJoin(\n posts,\n and(\n eq(posts.orgId, spaces.orgId),\n eq(posts.spaceId, spaces.id),\n eq(posts.status, \"published\"),\n ),\n )\n .where(and(eq(spaces.orgId, orgId), eq(spaces.communityId, communityId)))\n .groupBy(spaces.id),\n this.db\n .select({\n postId: posts.id,\n count: sql<number>`cast(count(${comments.id}) as int)`,\n })\n .from(posts)\n .leftJoin(\n comments,\n and(\n eq(comments.orgId, posts.orgId),\n eq(comments.postId, posts.id),\n eq(comments.status, \"published\"),\n ),\n )\n .where(\n and(\n eq(posts.orgId, orgId),\n eq(posts.communityId, communityId),\n eq(posts.status, \"published\"),\n ),\n )\n .groupBy(posts.id),\n this.db\n .select({ count: sql<number>`cast(count(*) as int)` })\n .from(messages)\n .where(and(eq(messages.orgId, orgId), eq(messages.communityId, communityId))),\n this.db\n .select({\n raised: sql<number>`cast(count(*) as int)`,\n resolved: sql<number>`cast(count(*) filter (where ${postFlags.resolvedAt} is not null) as int)`,\n })\n .from(postFlags)\n .innerJoin(posts, and(eq(posts.orgId, postFlags.orgId), eq(posts.id, postFlags.postId)))\n .where(and(eq(postFlags.orgId, orgId), eq(posts.communityId, communityId))),\n this.db\n .select({\n raised: sql<number>`cast(count(*) as int)`,\n resolved: sql<number>`cast(count(*) filter (where ${commentFlags.resolvedAt} is not null) as int)`,\n })\n .from(commentFlags)\n .innerJoin(\n comments,\n and(eq(comments.orgId, commentFlags.orgId), eq(comments.id, commentFlags.commentId)),\n )\n .where(and(eq(commentFlags.orgId, orgId), eq(comments.communityId, communityId))),\n this.db\n .select({\n raised: sql<number>`cast(count(*) as int)`,\n resolved: sql<number>`cast(count(*) filter (where ${messageFlags.resolvedAt} is not null) as int)`,\n })\n .from(messageFlags)\n .where(and(eq(messageFlags.orgId, orgId), eq(messageFlags.communityId, communityId))),\n this.db\n .select({ orgUserId: entitlements.orgUserId })\n .from(entitlements)\n .where(lapsed)\n .groupBy(entitlements.orgUserId),\n ]);\n\n const holderIds = lapsedHolders.map((row) => row.orgUserId);\n const [renewed] =\n holderIds.length === 0\n ? [{ count: 0 }]\n : await this.db\n .select({ count: sql<number>`cast(count(distinct ${contentAccess.orgUserId}) as int)` })\n .from(contentAccess)\n .where(\n and(\n eq(contentAccess.orgId, orgId),\n eq(contentAccess.contentId, communityId),\n inArray(contentAccess.orgUserId, holderIds),\n ),\n );\n\n const raised =\n Number(postFlagRow?.raised ?? 0) +\n Number(commentFlagRow?.raised ?? 0) +\n Number(messageFlagRow?.raised ?? 0);\n const resolved =\n Number(postFlagRow?.resolved ?? 0) +\n Number(commentFlagRow?.resolved ?? 0) +\n Number(messageFlagRow?.resolved ?? 0);\n\n return {\n memberCount: Number(members?.count ?? 0),\n activeMembers: authors.length,\n postsPerSpace: postsPerSpace.map((row) => ({\n spaceId: row.spaceId,\n count: Number(row.count),\n })),\n commentsPerPost: commentsPerPost.map((row) => ({\n postId: row.postId,\n count: Number(row.count),\n })),\n messagesSent: Number(sent?.count ?? 0),\n flagsRaised: raised,\n flagsResolved: resolved,\n grantsLapsed: Number(lapsedGrants?.count ?? 0),\n grantsRenewed: Number(renewed?.count ?? 0),\n };\n }\n\n async communityMembers(\n orgId: string,\n communityId: string,\n query: CommunityMembersQuery,\n ): Promise<Page<OrgUserProfile> | null> {\n const [community] = await this.db\n .select({ id: communities.id })\n .from(communities)\n .where(and(eq(communities.orgId, orgId), eq(communities.id, communityId)))\n .limit(1);\n if (!community) {\n return null;\n }\n const holds = exists(\n this.db\n .select({ id: contentAccess.entitlementId })\n .from(contentAccess)\n .where(\n and(\n eq(contentAccess.orgId, orgUsers.orgId),\n eq(contentAccess.orgUserId, orgUsers.id),\n eq(contentAccess.contentId, communityId),\n ),\n ),\n );\n const filters: SQL[] = [eq(orgUsers.orgId, orgId), holds];\n const q = query.search?.trim();\n if (q) {\n const like = `%${q}%`;\n filters.push(\n or(ilike(studentFirstName, like), ilike(studentLastName, like), ilike(users.email, like))!,\n );\n }\n const where = and(...filters);\n const [totals] = await this.db\n .select({ total: sql<number>`count(*)` })\n .from(orgUsers)\n .innerJoin(users, eq(users.id, orgUsers.userId))\n .where(where);\n const rows = await this.db\n .select(orgUserProfileColumns)\n .from(orgUsers)\n .innerJoin(users, eq(users.id, orgUsers.userId))\n .leftJoin(user, eq(user.id, users.externalId))\n .where(where)\n .orderBy(asc(users.firstName), asc(users.lastName), asc(orgUsers.id))\n .limit(query.pageSize)\n .offset((query.page - 1) * query.pageSize);\n return {\n rows: rows.map((row) => ({ ...row, image: row.image ?? null })),\n total: Number(totals?.total ?? 0),\n page: query.page,\n pageSize: query.pageSize,\n };\n }\n\n eventAttendees(\n orgId: string,\n eventId: string,\n page: { page: number; pageSize: number },\n ): Promise<Page<EventAttendeeRow>> {\n return eventAttendeePage(this.db, orgId, eventId, page);\n }\n\n eventAnalytics(\n orgId: string,\n eventId: string,\n startsAt: Date,\n ): Promise<Omit<EventAnalytics, \"capacity\">> {\n return eventAnalyticsFigures(this.db, orgId, eventId, startsAt);\n }\n}\ntranslateDbErrors(DrizzleAdminRepository);\n"],"mappings":";;;;;;;;;;;;;;;;AAwDA,MAAM,uBAAuB,GAAW,SAAS,aAAa,GAAG;AACjE,SAAS,eAAoB;CAC3B,OAAO,GAAG;oBACQ,gBAAgB;wBACZ,SAAS,MAAM,wBAAwB,SAAS,GAAG;;AAE3E;AAEA,SAAS,UAAU,SAAsB;CACvC,OAAO,GAAG;oBACQ,QAAQ;uBACL,SAAS,MAAM,8BAA8B,MAAM,MAAM;+CACjC,QAAQ;;AAEvD;AAEA,SAAS,UAAU,eAA4B;CAC7C,OAAO,IACL,aAAa,GACb,GACE,GAAG,SAAS,QAAQ,QAAQ,GAC5B,IAAI,GAAG,SAAS,QAAQ,SAAS,GAAG,UAAU,aAAa,CAAC,CAC9D,CACF;AACF;AAKA,MAAM,kBAAkB,GAAW;;;;;;;;;;;;;0BAaT,SAAS,MAAM,wBAAwB,SAAS,GAAG;;;sBAGvD,SAAS,MAAM;;;;uBAId,SAAS,MAAM;6BACT,SAAS,GAAG;;;;;;AAUzC,MAAM,iBAAiB,GAAgB;;sBAEjB,SAAS,MAAM,wBAAwB,SAAS,GAAG;;AAgBzE,SAAS,UAAU,KAAiB,OAAqC;CACvE,OAAO;EACL;EACA,IAAI,IAAI;EACR,OAAO,IAAI;EACX,OAAO,IAAI,SAAS;EACpB,WAAW,IAAI;EACf,UAAU,IAAI;EACd,QAAQ,IAAI;EACZ,kBAAkB,OAAO,IAAI,gBAAgB;EAC7C,aAAa,OAAO,IAAI,WAAW;EACnC,UAAU,IAAI,UAAU,YAAY;EACpC,cAAc,IAAI,eAAe,IAAI,KAAK,IAAI,YAAY,CAAC,CAAC,YAAY,IAAI;CAC9E;AACF;AAEA,IAAa,yBAAb,MAAqE;CAEhD;CACA;CAFnB,YACE,IACA,SAAkC,YAClC;EAFiB,KAAA,KAAA;EACA,KAAA,SAAA;CAChB;CAEH,MAAM,SAAS,OAAuC;EACpD,MAAM,CAAC,gBAAgB,MAAM,KAAK,GAC/B,OAAO;GACN,WAAW,GAAW,0BAA0B,QAAQ,OAAO;GAC/D,OAAO,GAAW,0BAA0B,QAAQ,OAAO;EAC7D,CAAC,CAAC,CACD,KAAK,OAAO,CAAC,CACb,MAAM,GAAG,QAAQ,OAAO,KAAK,CAAC;EAEjC,MAAM,CAAC,qBAAqB,MAAM,KAAK,GACpC,OAAO;GACN,gBAAgB,GAAW,kBAAkB,cAAc,UAAU;GACrE,cAAc,GAAW,kBAAkB,cAAc,cAAc,kBAAkB,aAAa,cAAc,WAAW,GAAG,4BAA4B,EAAE;EAClK,CAAC,CAAC,CACD,KAAK,aAAa,CAAC,CACnB,MAAM,GAAG,cAAc,OAAO,KAAK,CAAC;EAEvC,OAAO;GACL,kBAAkB,OAAO,cAAc,aAAa,CAAC;GACrD,cAAc,OAAO,cAAc,SAAS,CAAC;GAC7C,gBAAgB,OAAO,mBAAmB,kBAAkB,CAAC;GAC7D,cAAc,OAAO,mBAAmB,gBAAgB,CAAC;GACzD,gBAAgB,MAAM,mBAAmB,KAAK,IAAI,KAAK;EACzD;CACF;CAIA,MAAM,YAAY,OAAe,MAA0C;EAiBzE,QADa,MAfQ,KAAK,GAAG,QAAQ,GAAG;;;gBAG5B,aAAa,GAAG;;8CAEc,KAAK;;;;kBAIjC,aAAa;aAClB,aAAa,MAAM,KAAK,MAAM;eAC5B,aAAa,UAAU;;;KAGjC,EAAA,CACmB,KACR,KAAK,OAAO;GAAE,MAAM,EAAE;GAAM,OAAO,OAAO,EAAE,KAAK;EAAE,EAAE;CACnE;CAEA,MAAc,aAAa,OAAe,UAAoC;EAC5E,MAAM,CAAC,OAAO,MAAM,KAAK,GACtB,OAAO,EAAE,IAAI,QAAQ,GAAG,CAAC,CAAC,CAC1B,KAAK,OAAO,CAAC,CACb,MAAM,IAAI,GAAG,QAAQ,OAAO,KAAK,GAAG,GAAG,QAAQ,IAAI,QAAQ,CAAC,CAAC;EAChE,OAAO,QAAQ,KAAA;CACjB;CAEA,MAAM,gBAAgB,OAAe,UAAmD;EACtF,IAAI,CAAE,MAAM,KAAK,aAAa,OAAO,QAAQ,GAC3C,OAAO;EAGT,MAAM,SAAS,GAAG;;uBAEC,MAAM;2BACF,SAAS;;EAEhC,MAAM,sBAAsB,GAAG;;;yBAGV,MAAM;4BACH,SAAS;;;EA0CjC,MAAM,WAAU,MAtCY,KAAK,GAAG,QAAQ,GAAG;wBAC3B,OAAO;iBACd,oBAAoB;;;;;;2BAMV,MAAM;;;;;;;;;;8BAUH,MAAM;;;;iEAI6B,SAAS;;;;;8BAK5C,MAAM;;;iCAGH,SAAS;;;;;;;KAOrC,EAAA,CAC6B,KAAK;EA6BnC,MAAM,cACJ,MAvByB,KAAK,GAAG,QAAQ,GAAG;wBAC1B,OAAO;;;;;;;;;;;yBAWN,MAAM;;;;yBAIN,MAAM;4BACH,SAAS;;;;KAIhC,EAAA,CAEc,KAQb,KAAK,OAAO;GACZ,YAAY,EAAE;GACd,OAAO,EAAE;GACT,UAAU,EAAE;GACZ,aAAa,EAAE;GACf,SAAS,OAAO,EAAE,OAAO;GACzB,WAAW,OAAO,EAAE,SAAS;EAC/B,EAAE;EAEF,OAAO;GACL,UAAU,OAAO,SAAS,YAAY,CAAC;GACvC,SAAS,OAAO,SAAS,WAAW,CAAC;GACrC,WAAW,OAAO,SAAS,aAAa,CAAC;GACzC,aAAa,OAAO,SAAS,gBAAgB,CAAC;GAC9C;EACF;CACF;CAIA,MAAM,kBACJ,OACA,UACA,MACyC;EACzC,IAAI,CAAE,MAAM,KAAK,aAAa,OAAO,QAAQ,GAC3C,OAAO;EAmBT,QADa,MAhBQ,KAAK,GAAG,QAAQ,GAAG;;;gBAG5B,aAAa,GAAG;;8CAEc,KAAK;;;;kBAIjC,aAAa;aAClB,aAAa,MAAM,KAAK,MAAM;cAC7B,aAAa,UAAU,KAAK,SAAS;eACpC,aAAa,UAAU;;;KAGjC,EAAA,CACmB,KACR,KAAK,OAAO;GAAE,MAAM,EAAE;GAAM,OAAO,OAAO,EAAE,KAAK;EAAE,EAAE;CACnE;CAEA,MAAM,eACJ,OACA,UACA,OACqC;EACrC,IAAI,CAAE,MAAM,KAAK,aAAa,OAAO,QAAQ,GAC3C,OAAO;EAET,MAAM,UAAU,aAAa,aAAa,SAAS;EACnD,MAAM,cAAc,GAAuB;aAClC,aAAa,OAAO;aACpB,QAAQ;;;EAGjB,MAAM,YAAY,GAAG;aACZ,aAAa,OAAO;aACpB,QAAQ;;;EAGjB,MAAM,eAAe,KAAK,GAAG,MAAM,eAAe,CAAC,CAAC,GAClD,KAAK,GACF,iBAAiB,CAAC,aAAa,SAAS,GAAG;GAC1C,WAAW,aAAa;GACxB,QAAQ,YAAY,GAAG,cAAc;GACrC,QAAQ,aAAa;GACrB,WAAW,aAAa;GACxB,WAAW,aAAa;EAC1B,CAAC,CAAC,CACD,KAAK,YAAY,CAAC,CAClB,MACC,IAAI,GAAG,aAAa,OAAO,KAAK,GAAG,WAAW,cAAc,GAAG,GAAG,YAAY,QAAQ,CAAC,CACzF,CAAC,CACA,QACC,aAAa,WACb,WACA,GAAG,GAAG,aAAa,UAAU,oBAC7B,KAAK,aAAa,SAAS,CAC7B,CACJ;EACA,MAAM,sBAAsB,GAAG;;yBAEV,MAAM;4BACH,SAAS;;;EAGjC,MAAM,sBAAsB,GAAW;;0BAEjB,MAAM;+BACD,SAAS,GAAG;;;8BAGb,oBAAoB;;EAE9C,MAAM,iBAAiB,GAAyB;;0BAE1B,MAAM;+BACD,SAAS,GAAG;;6DAEkB,oBAAoB;6DACpB,SAAS;;;EAGlE,MAAM,cAAc,GAAyB;;0BAEvB,MAAM;+BACD,SAAS,GAAG;;6BAEd,SAAS;;EAGlC,MAAM,UAAiB,CAAC,GAAG,SAAS,OAAO,KAAK,CAAC;EACjD,MAAM,IAAI,MAAM,QAAQ,KAAK;EAC7B,IAAI,GAAG;GACL,MAAM,OAAO,IAAI,EAAE;GACnB,QAAQ,KACN,GAAG,MAAM,kBAAkB,IAAI,GAAG,MAAM,iBAAiB,IAAI,GAAG,MAAM,MAAM,OAAO,IAAI,CAAC,CAC1F;EACF;EACA,MAAM,QAAQ,IAAI,GAAG,OAAO;EAC5B,MAAM,UAAU,IAAI,GAAG,SAAS,OAAO,KAAK,GAAG,GAAG,SAAS,IAAI,aAAa,SAAS,CAAC;EAEtF,MAAM,CAAC,CAAC,SAAS,CAAC,gBAAgB,QAAQ,MAAM,QAAQ,IAAI;GAC1D,KAAK,GACF,KAAK,YAAY,CAAC,CAClB,OAAO,EAAE,OAAO,GAAW,WAAW,CAAC,CAAC,CACxC,KAAK,YAAY,CAAC,CAClB,UAAU,UAAU,OAAO,CAAC,CAC5B,UAAU,OAAO,GAAG,MAAM,IAAI,SAAS,MAAM,CAAC,CAAC,CAC/C,MAAM,KAAK;GACd,KAAK,GACF,OAAO,EAAE,OAAO,GAAW,WAAW,CAAC,CAAC,CACxC,KAAK,GAAG,GAAG,oBAAoB,yBAAyB;GAC3D,KAAK,GACF,KAAK,YAAY,CAAC,CAClB,OAAO;IACN,GAAG;IACH,QAAQ,aAAa;IACrB,QAAQ,aAAa;IACrB,WAAW,aAAa;IACxB,WAAW,aAAa;IACxB;IACA;IACA;GACF,CAAC,CAAC,CACD,KAAK,YAAY,CAAC,CAClB,UAAU,UAAU,OAAO,CAAC,CAC5B,UAAU,OAAO,GAAG,MAAM,IAAI,SAAS,MAAM,CAAC,CAAC,CAC/C,SAAS,MAAM,GAAG,KAAK,IAAI,MAAM,UAAU,CAAC,CAAC,CAC7C,MAAM,KAAK,CAAC,CACZ,QAAQ,IAAI,gBAAgB,GAAG,IAAI,eAAe,GAAG,IAAI,SAAS,EAAE,CAAC,CAAC,CACtE,MAAM,MAAM,QAAQ,CAAC,CACrB,QAAQ,MAAM,OAAO,KAAK,MAAM,QAAQ;EAC7C,CAAC;EAED,MAAM,kBAAkB,OAAO,eAAe,SAAS,CAAC;EACxD,OAAO;GACL,MAAM,KAAK,KAAK,QAAQ;IACtB,MAAM,OAAO,OAAO,IAAI,mBAAmB;IAC3C,OAAO;KACL,IAAI,IAAI;KACR,WAAW,IAAI;KACf,UAAU,IAAI;KACd,OAAO,IAAI;KACX,OAAO,IAAI,SAAS;KACpB,QAAQ,IAAI;KACZ,QAAQ,IAAI;KACZ,WAAW,IAAI,KAAK,IAAI,SAAS,CAAC,CAAC,YAAY;KAC/C,WAAW,IAAI,YAAY,IAAI,KAAK,IAAI,SAAS,CAAC,CAAC,YAAY,IAAI;KACnE;KACA,qBAAqB;KACrB,UAAU,kBAAkB,IAAI,KAAK,MAAM,KAAK,IAAI,OAAO,iBAAiB,CAAC,IAAI,GAAG,IAAI;KACxF,gBAAgB,IAAI,iBAAiB,IAAI,KAAK,IAAI,cAAc,CAAC,CAAC,YAAY,IAAI;KAClF,aAAa,IAAI,cAAc,IAAI,KAAK,IAAI,WAAW,CAAC,CAAC,YAAY,IAAI;IAC3E;GACF,CAAC;GACD,OAAO,OAAO,QAAQ,SAAS,CAAC;GAChC,MAAM,MAAM;GACZ,UAAU,MAAM;EAClB;CACF;CAEA,MAAM,aACJ,OACA,OACA,eAC8B;EAC9B,MAAM,UAAiB,CAAC,GAAG,SAAS,OAAO,KAAK,GAAG,UAAU,aAAa,CAAC;EAC3E,MAAM,IAAI,MAAM,QAAQ,KAAK;EAC7B,IAAI,GAAG;GACL,MAAM,OAAO,IAAI,EAAE;GACnB,QAAQ,KACN,GAAG,MAAM,kBAAkB,IAAI,GAAG,MAAM,iBAAiB,IAAI,GAAG,MAAM,MAAM,OAAO,IAAI,CAAC,CAC1F;EACF;EACA,MAAM,QAAQ,IAAI,GAAG,OAAO;EAE5B,MAAM,CAAC,UAAU,MAAM,KAAK,GACzB,OAAO,EAAE,OAAO,GAAW,WAAW,CAAC,CAAC,CACxC,KAAK,QAAQ,CAAC,CACd,UAAU,OAAO,GAAG,MAAM,IAAI,SAAS,MAAM,CAAC,CAAC,CAC/C,MAAM,KAAK;EAEd,MAAM,OAAO,MAAM,KAAK,GACrB,OAAO;GACN,GAAG;GACH,QAAQ,SAAS;GACjB,WAAW,SAAS;GACpB,kBAAkB;GAClB,aAAa;GACb,cAAc;EAChB,CAAC,CAAC,CACD,KAAK,QAAQ,CAAC,CACd,SACC,cACA,IAAI,GAAG,aAAa,OAAO,SAAS,KAAK,GAAG,GAAG,aAAa,WAAW,SAAS,EAAE,CAAC,CACrF,CAAC,CACA,UAAU,OAAO,GAAG,MAAM,IAAI,SAAS,MAAM,CAAC,CAAC,CAC/C,SAAS,MAAM,GAAG,KAAK,IAAI,MAAM,UAAU,CAAC,CAAC,CAC7C,MAAM,KAAK,CAAC,CAGZ,QACC,SAAS,OACT,SAAS,IACT,SAAS,QACT,MAAM,IACN,MAAM,YACN,MAAM,OACN,MAAM,WACN,MAAM,UACN,KAAK,KACP,CAAC,CACA,QAAQ,GAAG,KAAK,aAAa,MAAM,IAAI,CAAC,CAAC,CACzC,MAAM,MAAM,QAAQ,CAAC,CACrB,QAAQ,MAAM,OAAO,KAAK,MAAM,QAAQ;EAE3C,MAAM,QAAQ,MAAM,KAAK,QACvB,OACA,KAAK,KAAK,QAAQ,IAAI,EAAE,CAC1B;EACA,OAAO;GACL,MAAM,KAAK,KAAK,QAAQ,UAAU,KAAK,MAAM,IAAI,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;GAC/D,OAAO,OAAO,QAAQ,SAAS,CAAC;GAChC,MAAM,MAAM;GACZ,UAAU,MAAM;EAClB;CACF;CAEA,MAAM,WAAW,OAAe,IAA2C;EACzE,MAAM,CAAC,OAAO,MAAM,KAAK,GACtB,OAAO;GACN,GAAG;GACH,QAAQ,SAAS;GACjB,WAAW,SAAS;GACpB,kBAAkB;GAClB,aAAa;GACb,cAAc;EAChB,CAAC,CAAC,CACD,KAAK,QAAQ,CAAC,CACd,SACC,cACA,IAAI,GAAG,aAAa,OAAO,SAAS,KAAK,GAAG,GAAG,aAAa,WAAW,SAAS,EAAE,CAAC,CACrF,CAAC,CACA,UAAU,OAAO,GAAG,MAAM,IAAI,SAAS,MAAM,CAAC,CAAC,CAC/C,SAAS,MAAM,GAAG,KAAK,IAAI,MAAM,UAAU,CAAC,CAAC,CAC7C,MAAM,IAAI,GAAG,SAAS,OAAO,KAAK,GAAG,GAAG,SAAS,IAAI,EAAE,CAAC,CAAC,CAAC,CAC1D,QACC,SAAS,OACT,SAAS,IACT,SAAS,QACT,MAAM,IACN,MAAM,YACN,MAAM,OACN,MAAM,WACN,MAAM,UACN,KAAK,KACP,CAAC,CACA,MAAM,CAAC;EACV,IAAI,CAAC,KACH,OAAO;EAGT,OAAO,UAAU,MAAK,MADF,KAAK,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC,EAAA,CACpB,IAAI,IAAI,EAAE,KAAK,CAAC,CAAC;CAC/C;CAEA,MAAc,QAAQ,OAAe,YAA2D;EAC9F,MAAM,2BAAW,IAAI,IAA2B;EAChD,IAAI,WAAW,WAAW,GACxB,OAAO;EAET,MAAM,OAAO,MAAM,KAAK,GACrB,OAAO;GACN,WAAW,gBAAgB;GAC3B,WAAW,gBAAgB;GAC3B,aAAa,gBAAgB;EAC/B,CAAC,CAAC,CACD,KAAK,eAAe,CAAC,CACrB,MACC,IACE,GAAG,gBAAgB,OAAO,KAAK,GAC/B,QAAQ,gBAAgB,WAAW,UAAU,GAC7C,GAAG,GAAG,gBAAgB,UAAU,aAClC,CACF,CAAC,CACA,QAAQ,IAAI,gBAAgB,SAAS,CAAC;EACzC,KAAK,MAAM,OAAO,MAAM;GACtB,IAAI,IAAI,cAAc,QAAQ,IAAI,gBAAgB,MAChD;GAEF,MAAM,QAAQ,SAAS,IAAI,IAAI,SAAS,KAAK,CAAC;GAC9C,MAAM,KAAK;IAAE,WAAW,IAAI;IAAW,aAAa,IAAI;GAAY,CAAC;GACrE,SAAS,IAAI,IAAI,WAAW,KAAK;EACnC;EACA,OAAO;CACT;CAKA,MAAM,iBAAiB,OAAe,IAA8C;EAClF,MAAM,CAAC,UAAU,MAAM,KAAK,GACzB,OAAO,EAAE,IAAI,SAAS,GAAG,CAAC,CAAC,CAC3B,KAAK,QAAQ,CAAC,CACd,MAAM,IAAI,GAAG,SAAS,OAAO,KAAK,GAAG,GAAG,SAAS,IAAI,EAAE,CAAC,CAAC;EAC5D,IAAI,CAAC,QACH,OAAO;EA2CT,MAAM,WACJ,MAzCmB,KAAK,GAAG,QAAQ,GAAG;;;;;4BAKhB,MAAM;iCACD,GAAG;;;;;2BAKT,MAAM;;;;;;;;;;;;;+BAaF,MAAM;oCACD,GAAG;;;;;;;yBAOd,MAAM;+BACA,GAAG;;;;;KAK7B,EAAA,CAGQ,KASP,KAAK,MAAM;GACX,MAAM,QAAQ,OAAO,EAAE,gBAAgB;GACvC,MAAM,OAAO,OAAO,EAAE,oBAAoB;GAC1C,OAAO;IACL,UAAU,EAAE;IACZ,OAAO,EAAE;IACT,iBAAiB;IACjB,qBAAqB;IACrB,UAAU,QAAQ,IAAI,KAAK,MAAM,KAAK,IAAI,OAAO,OAAO,CAAC,IAAI,GAAG,IAAI;IACpE,WAAW,EAAE,aAAa,IAAI,KAAK,EAAE,UAAU,CAAC,CAAC,YAAY,IAAI;IACjE,gBAAgB,EAAE,mBAAmB,IAAI,KAAK,EAAE,gBAAgB,CAAC,CAAC,YAAY,IAAI;IAClF,aAAa,EAAE,eAAe,IAAI,KAAK,EAAE,YAAY,CAAC,CAAC,YAAY,IAAI;GACzE;EACF,CAAC;EAED,MAAM,UAAU,QAAQ,QAAQ,MAAM,EAAE,cAAc,IAAI,CAAC,CAAC;EAC5D,MAAM,YAAY,QAAQ,QAAQ,MAAM,EAAE,gBAAgB,IAAI,CAAC,CAAC;EAChE,MAAM,cACJ,QAAQ,SAAS,IACb,KAAK,MAAM,QAAQ,QAAQ,KAAK,MAAM,MAAM,EAAE,UAAU,CAAC,IAAI,QAAQ,MAAM,IAC3E;EAEN,OAAO;GAAE,UAAU,QAAQ;GAAQ;GAAS;GAAW;GAAa;EAAQ;CAC9E;CAEA,aAAqB,MAAsB;EACzC,MAAM,aAAa,MAAM,WAAW,GAAG,KAAK;EAC5C,MAAM,QAAQ,OAAQ,aAAa,KAAK,MAAM,CAAC,IAAI,OAAQ;EAC3D,MAAM,MAAM,aAAa,OAAO;EAChC,MAAM,aAAa,SACjB,aAAa,GAAG,GAAG,KAAK,oBAAoB,GAAG,GAAG,KAAK;EACzD,QAAQ,OAAR;GACE,KAAK,SACH,OAAO,CAAC,IAAI,MAAM,KAAK,CAAC;GAC1B,KAAK,oBACH,OAAO,CAAC,IAAI,oBAAoB,CAAC;GACnC,KAAK,eACH,OAAO,CAAC,IAAI,eAAe,CAAC;GAC9B,KAAK,YACH,OAAO,CAAC,IAAI,SAAS,SAAS,CAAC;GACjC,KAAK,gBACH,OAAO,CAAC,UAAU,cAAc,CAAC;GACnC,KAAK,YACH,OAAO,CAAC,IAAI,eAAe,GAAG,IAAI,gBAAgB,CAAC;GAErD,SACE,OAAO,CAAC,IAAI,gBAAgB,GAAG,IAAI,eAAe,CAAC;EACvD;CACF;CAEA,MAAM,mBAAmB,OAAe,aAAyD;EAC/F,MAAM,CAAC,aAAa,MAAM,KAAK,GAC5B,OAAO,EAAE,IAAI,YAAY,GAAG,CAAC,CAAC,CAC9B,KAAK,WAAW,CAAC,CACjB,MAAM,IAAI,GAAG,YAAY,OAAO,KAAK,GAAG,GAAG,YAAY,IAAI,WAAW,CAAC,CAAC,CAAC,CACzE,MAAM,CAAC;EACV,IAAI,CAAC,WACH,OAAO;EAGT,MAAM,QAAQ,WAAW,cAAc,GAAG,GAAG,eAAe,WAAW;EACvE,MAAM,SAAS,IACb,GAAG,aAAa,OAAO,KAAK,GAC5B,GAAG,aAAa,QAAQ,QAAQ,GAChC,aAAa,aAAa,SAAS,GACnC,KACF;EACA,MAAM,SAAS,GAAG;EAElB,MAAM,CACJ,CAAC,UACD,CAAC,eACD,SACA,eACA,iBACA,CAAC,OACD,CAAC,cACD,CAAC,iBACD,CAAC,iBACD,iBACE,MAAM,QAAQ,IAAI;GACpB,KAAK,GACF,OAAO,EAAE,OAAO,GAAW,uBAAuB,cAAc,UAAU,WAAW,CAAC,CAAC,CACvF,KAAK,aAAa,CAAC,CACnB,MAAM,IAAI,GAAG,cAAc,OAAO,KAAK,GAAG,GAAG,cAAc,WAAW,WAAW,CAAC,CAAC;GACtF,KAAK,GACF,OAAO,EAAE,OAAO,GAAW,wBAAwB,CAAC,CAAC,CACrD,KAAK,YAAY,CAAC,CAClB,MAAM,MAAM;GACf,MACE,KAAK,GACF,OAAO,EAAE,IAAI,MAAM,UAAU,CAAC,CAAC,CAC/B,KAAK,KAAK,CAAC,CACX,MACC,IACE,GAAG,MAAM,OAAO,KAAK,GACrB,GAAG,MAAM,aAAa,WAAW,GACjC,GAAG,GAAG,MAAM,UAAU,MAAM,QAC9B,CACF,GACF,KAAK,GACF,OAAO,EAAE,IAAI,SAAS,UAAU,CAAC,CAAC,CAClC,KAAK,QAAQ,CAAC,CACd,MACC,IACE,GAAG,SAAS,OAAO,KAAK,GACxB,GAAG,SAAS,aAAa,WAAW,GACpC,GAAG,GAAG,SAAS,UAAU,MAAM,QACjC,CACF,GACF,KAAK,GACF,OAAO,EAAE,IAAI,SAAS,UAAU,CAAC,CAAC,CAClC,KAAK,QAAQ,CAAC,CACd,MACC,IACE,GAAG,SAAS,OAAO,KAAK,GACxB,GAAG,SAAS,aAAa,WAAW,GACpC,GAAG,GAAG,SAAS,UAAU,MAAM,QACjC,CACF,CACJ;GACA,KAAK,GACF,OAAO;IACN,SAAS,OAAO;IAChB,OAAO,GAAW,cAAc,MAAM,GAAG;GAC3C,CAAC,CAAC,CACD,KAAK,MAAM,CAAC,CACZ,SACC,OACA,IACE,GAAG,MAAM,OAAO,OAAO,KAAK,GAC5B,GAAG,MAAM,SAAS,OAAO,EAAE,GAC3B,GAAG,MAAM,QAAQ,WAAW,CAC9B,CACF,CAAC,CACA,MAAM,IAAI,GAAG,OAAO,OAAO,KAAK,GAAG,GAAG,OAAO,aAAa,WAAW,CAAC,CAAC,CAAC,CACxE,QAAQ,OAAO,EAAE;GACpB,KAAK,GACF,OAAO;IACN,QAAQ,MAAM;IACd,OAAO,GAAW,cAAc,SAAS,GAAG;GAC9C,CAAC,CAAC,CACD,KAAK,KAAK,CAAC,CACX,SACC,UACA,IACE,GAAG,SAAS,OAAO,MAAM,KAAK,GAC9B,GAAG,SAAS,QAAQ,MAAM,EAAE,GAC5B,GAAG,SAAS,QAAQ,WAAW,CACjC,CACF,CAAC,CACA,MACC,IACE,GAAG,MAAM,OAAO,KAAK,GACrB,GAAG,MAAM,aAAa,WAAW,GACjC,GAAG,MAAM,QAAQ,WAAW,CAC9B,CACF,CAAC,CACA,QAAQ,MAAM,EAAE;GACnB,KAAK,GACF,OAAO,EAAE,OAAO,GAAW,wBAAwB,CAAC,CAAC,CACrD,KAAK,QAAQ,CAAC,CACd,MAAM,IAAI,GAAG,SAAS,OAAO,KAAK,GAAG,GAAG,SAAS,aAAa,WAAW,CAAC,CAAC;GAC9E,KAAK,GACF,OAAO;IACN,QAAQ,GAAW;IACnB,UAAU,GAAW,+BAA+B,UAAU,WAAW;GAC3E,CAAC,CAAC,CACD,KAAK,SAAS,CAAC,CACf,UAAU,OAAO,IAAI,GAAG,MAAM,OAAO,UAAU,KAAK,GAAG,GAAG,MAAM,IAAI,UAAU,MAAM,CAAC,CAAC,CAAC,CACvF,MAAM,IAAI,GAAG,UAAU,OAAO,KAAK,GAAG,GAAG,MAAM,aAAa,WAAW,CAAC,CAAC;GAC5E,KAAK,GACF,OAAO;IACN,QAAQ,GAAW;IACnB,UAAU,GAAW,+BAA+B,aAAa,WAAW;GAC9E,CAAC,CAAC,CACD,KAAK,YAAY,CAAC,CAClB,UACC,UACA,IAAI,GAAG,SAAS,OAAO,aAAa,KAAK,GAAG,GAAG,SAAS,IAAI,aAAa,SAAS,CAAC,CACrF,CAAC,CACA,MAAM,IAAI,GAAG,aAAa,OAAO,KAAK,GAAG,GAAG,SAAS,aAAa,WAAW,CAAC,CAAC;GAClF,KAAK,GACF,OAAO;IACN,QAAQ,GAAW;IACnB,UAAU,GAAW,+BAA+B,aAAa,WAAW;GAC9E,CAAC,CAAC,CACD,KAAK,YAAY,CAAC,CAClB,MAAM,IAAI,GAAG,aAAa,OAAO,KAAK,GAAG,GAAG,aAAa,aAAa,WAAW,CAAC,CAAC;GACtF,KAAK,GACF,OAAO,EAAE,WAAW,aAAa,UAAU,CAAC,CAAC,CAC7C,KAAK,YAAY,CAAC,CAClB,MAAM,MAAM,CAAC,CACb,QAAQ,aAAa,SAAS;EACnC,CAAC;EAED,MAAM,YAAY,cAAc,KAAK,QAAQ,IAAI,SAAS;EAC1D,MAAM,CAAC,WACL,UAAU,WAAW,IACjB,CAAC,EAAE,OAAO,EAAE,CAAC,IACb,MAAM,KAAK,GACR,OAAO,EAAE,OAAO,GAAW,uBAAuB,cAAc,UAAU,WAAW,CAAC,CAAC,CACvF,KAAK,aAAa,CAAC,CACnB,MACC,IACE,GAAG,cAAc,OAAO,KAAK,GAC7B,GAAG,cAAc,WAAW,WAAW,GACvC,QAAQ,cAAc,WAAW,SAAS,CAC5C,CACF;EAER,MAAM,SACJ,OAAO,aAAa,UAAU,CAAC,IAC/B,OAAO,gBAAgB,UAAU,CAAC,IAClC,OAAO,gBAAgB,UAAU,CAAC;EACpC,MAAM,WACJ,OAAO,aAAa,YAAY,CAAC,IACjC,OAAO,gBAAgB,YAAY,CAAC,IACpC,OAAO,gBAAgB,YAAY,CAAC;EAEtC,OAAO;GACL,aAAa,OAAO,SAAS,SAAS,CAAC;GACvC,eAAe,QAAQ;GACvB,eAAe,cAAc,KAAK,SAAS;IACzC,SAAS,IAAI;IACb,OAAO,OAAO,IAAI,KAAK;GACzB,EAAE;GACF,iBAAiB,gBAAgB,KAAK,SAAS;IAC7C,QAAQ,IAAI;IACZ,OAAO,OAAO,IAAI,KAAK;GACzB,EAAE;GACF,cAAc,OAAO,MAAM,SAAS,CAAC;GACrC,aAAa;GACb,eAAe;GACf,cAAc,OAAO,cAAc,SAAS,CAAC;GAC7C,eAAe,OAAO,SAAS,SAAS,CAAC;EAC3C;CACF;CAEA,MAAM,iBACJ,OACA,aACA,OACsC;EACtC,MAAM,CAAC,aAAa,MAAM,KAAK,GAC5B,OAAO,EAAE,IAAI,YAAY,GAAG,CAAC,CAAC,CAC9B,KAAK,WAAW,CAAC,CACjB,MAAM,IAAI,GAAG,YAAY,OAAO,KAAK,GAAG,GAAG,YAAY,IAAI,WAAW,CAAC,CAAC,CAAC,CACzE,MAAM,CAAC;EACV,IAAI,CAAC,WACH,OAAO;EAET,MAAM,QAAQ,OACZ,KAAK,GACF,OAAO,EAAE,IAAI,cAAc,cAAc,CAAC,CAAC,CAC3C,KAAK,aAAa,CAAC,CACnB,MACC,IACE,GAAG,cAAc,OAAO,SAAS,KAAK,GACtC,GAAG,cAAc,WAAW,SAAS,EAAE,GACvC,GAAG,cAAc,WAAW,WAAW,CACzC,CACF,CACJ;EACA,MAAM,UAAiB,CAAC,GAAG,SAAS,OAAO,KAAK,GAAG,KAAK;EACxD,MAAM,IAAI,MAAM,QAAQ,KAAK;EAC7B,IAAI,GAAG;GACL,MAAM,OAAO,IAAI,EAAE;GACnB,QAAQ,KACN,GAAG,MAAM,kBAAkB,IAAI,GAAG,MAAM,iBAAiB,IAAI,GAAG,MAAM,MAAM,OAAO,IAAI,CAAC,CAC1F;EACF;EACA,MAAM,QAAQ,IAAI,GAAG,OAAO;EAC5B,MAAM,CAAC,UAAU,MAAM,KAAK,GACzB,OAAO,EAAE,OAAO,GAAW,WAAW,CAAC,CAAC,CACxC,KAAK,QAAQ,CAAC,CACd,UAAU,OAAO,GAAG,MAAM,IAAI,SAAS,MAAM,CAAC,CAAC,CAC/C,MAAM,KAAK;EAUd,OAAO;GACL,OAAM,MAVW,KAAK,GACrB,OAAO,qBAAqB,CAAC,CAC7B,KAAK,QAAQ,CAAC,CACd,UAAU,OAAO,GAAG,MAAM,IAAI,SAAS,MAAM,CAAC,CAAC,CAC/C,SAAS,MAAM,GAAG,KAAK,IAAI,MAAM,UAAU,CAAC,CAAC,CAC7C,MAAM,KAAK,CAAC,CACZ,QAAQ,IAAI,MAAM,SAAS,GAAG,IAAI,MAAM,QAAQ,GAAG,IAAI,SAAS,EAAE,CAAC,CAAC,CACpE,MAAM,MAAM,QAAQ,CAAC,CACrB,QAAQ,MAAM,OAAO,KAAK,MAAM,QAAQ,EAAA,CAE9B,KAAK,SAAS;IAAE,GAAG;IAAK,OAAO,IAAI,SAAS;GAAK,EAAE;GAC9D,OAAO,OAAO,QAAQ,SAAS,CAAC;GAChC,MAAM,MAAM;GACZ,UAAU,MAAM;EAClB;CACF;CAEA,eACE,OACA,SACA,MACiC;EACjC,OAAO,kBAAkB,KAAK,IAAI,OAAO,SAAS,IAAI;CACxD;CAEA,eACE,OACA,SACA,UAC2C;EAC3C,OAAO,sBAAsB,KAAK,IAAI,OAAO,SAAS,QAAQ;CAChE;AACF;AACA,kBAAkB,sBAAsB"}
@@ -76,7 +76,7 @@ declare const assets: import("drizzle-orm/pg-core").PgTableWithColumns<{
76
76
  tableName: "assets";
77
77
  dataType: "string";
78
78
  columnType: "PgText";
79
- data: "member" | "library";
79
+ data: "library" | "member";
80
80
  driverParam: string;
81
81
  notNull: true;
82
82
  hasDefault: true;
@@ -123,6 +123,7 @@ declare const automations: import("drizzle-orm/pg-core").PgTableWithColumns<{
123
123
  identity: undefined;
124
124
  generated: undefined;
125
125
  }, {}, {
126
+ size: undefined;
126
127
  baseBuilder: import("drizzle-orm/pg-core").PgColumnBuilder<{
127
128
  name: "triggers";
128
129
  dataType: "string";
@@ -131,7 +132,6 @@ declare const automations: import("drizzle-orm/pg-core").PgTableWithColumns<{
131
132
  enumValues: [string, ...string[]];
132
133
  driverParam: string;
133
134
  }, {}, {}, import("drizzle-orm").ColumnBuilderExtraConfig>;
134
- size: undefined;
135
135
  }>;
136
136
  actions: import("drizzle-orm/pg-core").PgColumn<{
137
137
  name: "actions";
@@ -597,6 +597,8 @@ declare const notifications: import("drizzle-orm/pg-core").PgTableWithColumns<{
597
597
  identity: undefined;
598
598
  generated: undefined;
599
599
  }, {}, {
600
+ $type: Reason[];
601
+ size: undefined;
600
602
  baseBuilder: import("drizzle-orm/pg-core").PgColumnBuilder<{
601
603
  name: "reasons";
602
604
  dataType: "string";
@@ -605,8 +607,6 @@ declare const notifications: import("drizzle-orm/pg-core").PgTableWithColumns<{
605
607
  enumValues: [string, ...string[]];
606
608
  driverParam: string;
607
609
  }, {}, {}, import("drizzle-orm").ColumnBuilderExtraConfig>;
608
- size: undefined;
609
- $type: Reason[];
610
610
  }>;
611
611
  matchedReason: import("drizzle-orm/pg-core").PgColumn<{
612
612
  name: "matched_reason";
@@ -1156,6 +1156,7 @@ declare const oauthClient: import("drizzle-orm/pg-core").PgTableWithColumns<{
1156
1156
  identity: undefined;
1157
1157
  generated: undefined;
1158
1158
  }, {}, {
1159
+ size: undefined;
1159
1160
  baseBuilder: import("drizzle-orm/pg-core").PgColumnBuilder<{
1160
1161
  name: "scopes";
1161
1162
  dataType: "string";
@@ -1164,7 +1165,6 @@ declare const oauthClient: import("drizzle-orm/pg-core").PgTableWithColumns<{
1164
1165
  enumValues: [string, ...string[]];
1165
1166
  driverParam: string;
1166
1167
  }, {}, {}, import("drizzle-orm").ColumnBuilderExtraConfig>;
1167
- size: undefined;
1168
1168
  }>;
1169
1169
  clientCredentialsScopes: import("drizzle-orm/pg-core").PgColumn<{
1170
1170
  name: "client_credentials_scopes";
@@ -1199,6 +1199,7 @@ declare const oauthClient: import("drizzle-orm/pg-core").PgTableWithColumns<{
1199
1199
  identity: undefined;
1200
1200
  generated: undefined;
1201
1201
  }, {}, {
1202
+ size: undefined;
1202
1203
  baseBuilder: import("drizzle-orm/pg-core").PgColumnBuilder<{
1203
1204
  name: "client_credentials_scopes";
1204
1205
  dataType: "string";
@@ -1207,7 +1208,6 @@ declare const oauthClient: import("drizzle-orm/pg-core").PgTableWithColumns<{
1207
1208
  enumValues: [string, ...string[]];
1208
1209
  driverParam: string;
1209
1210
  }, {}, {}, import("drizzle-orm").ColumnBuilderExtraConfig>;
1210
- size: undefined;
1211
1211
  }>;
1212
1212
  userId: import("drizzle-orm/pg-core").PgColumn<{
1213
1213
  name: "user_id";
@@ -1344,6 +1344,7 @@ declare const oauthClient: import("drizzle-orm/pg-core").PgTableWithColumns<{
1344
1344
  identity: undefined;
1345
1345
  generated: undefined;
1346
1346
  }, {}, {
1347
+ size: undefined;
1347
1348
  baseBuilder: import("drizzle-orm/pg-core").PgColumnBuilder<{
1348
1349
  name: "contacts";
1349
1350
  dataType: "string";
@@ -1352,7 +1353,6 @@ declare const oauthClient: import("drizzle-orm/pg-core").PgTableWithColumns<{
1352
1353
  enumValues: [string, ...string[]];
1353
1354
  driverParam: string;
1354
1355
  }, {}, {}, import("drizzle-orm").ColumnBuilderExtraConfig>;
1355
- size: undefined;
1356
1356
  }>;
1357
1357
  tos: import("drizzle-orm/pg-core").PgColumn<{
1358
1358
  name: "tos";
@@ -1472,6 +1472,7 @@ declare const oauthClient: import("drizzle-orm/pg-core").PgTableWithColumns<{
1472
1472
  identity: undefined;
1473
1473
  generated: undefined;
1474
1474
  }, {}, {
1475
+ size: undefined;
1475
1476
  baseBuilder: import("drizzle-orm/pg-core").PgColumnBuilder<{
1476
1477
  name: "redirect_uris";
1477
1478
  dataType: "string";
@@ -1480,7 +1481,6 @@ declare const oauthClient: import("drizzle-orm/pg-core").PgTableWithColumns<{
1480
1481
  enumValues: [string, ...string[]];
1481
1482
  driverParam: string;
1482
1483
  }, {}, {}, import("drizzle-orm").ColumnBuilderExtraConfig>;
1483
- size: undefined;
1484
1484
  }>;
1485
1485
  postLogoutRedirectUris: import("drizzle-orm/pg-core").PgColumn<{
1486
1486
  name: "post_logout_redirect_uris";
@@ -1515,6 +1515,7 @@ declare const oauthClient: import("drizzle-orm/pg-core").PgTableWithColumns<{
1515
1515
  identity: undefined;
1516
1516
  generated: undefined;
1517
1517
  }, {}, {
1518
+ size: undefined;
1518
1519
  baseBuilder: import("drizzle-orm/pg-core").PgColumnBuilder<{
1519
1520
  name: "post_logout_redirect_uris";
1520
1521
  dataType: "string";
@@ -1523,7 +1524,6 @@ declare const oauthClient: import("drizzle-orm/pg-core").PgTableWithColumns<{
1523
1524
  enumValues: [string, ...string[]];
1524
1525
  driverParam: string;
1525
1526
  }, {}, {}, import("drizzle-orm").ColumnBuilderExtraConfig>;
1526
- size: undefined;
1527
1527
  }>;
1528
1528
  backchannelLogoutUri: import("drizzle-orm/pg-core").PgColumn<{
1529
1529
  name: "backchannel_logout_uri";
@@ -1660,6 +1660,7 @@ declare const oauthClient: import("drizzle-orm/pg-core").PgTableWithColumns<{
1660
1660
  identity: undefined;
1661
1661
  generated: undefined;
1662
1662
  }, {}, {
1663
+ size: undefined;
1663
1664
  baseBuilder: import("drizzle-orm/pg-core").PgColumnBuilder<{
1664
1665
  name: "grant_types";
1665
1666
  dataType: "string";
@@ -1668,7 +1669,6 @@ declare const oauthClient: import("drizzle-orm/pg-core").PgTableWithColumns<{
1668
1669
  enumValues: [string, ...string[]];
1669
1670
  driverParam: string;
1670
1671
  }, {}, {}, import("drizzle-orm").ColumnBuilderExtraConfig>;
1671
- size: undefined;
1672
1672
  }>;
1673
1673
  responseTypes: import("drizzle-orm/pg-core").PgColumn<{
1674
1674
  name: "response_types";
@@ -1703,6 +1703,7 @@ declare const oauthClient: import("drizzle-orm/pg-core").PgTableWithColumns<{
1703
1703
  identity: undefined;
1704
1704
  generated: undefined;
1705
1705
  }, {}, {
1706
+ size: undefined;
1706
1707
  baseBuilder: import("drizzle-orm/pg-core").PgColumnBuilder<{
1707
1708
  name: "response_types";
1708
1709
  dataType: "string";
@@ -1711,7 +1712,6 @@ declare const oauthClient: import("drizzle-orm/pg-core").PgTableWithColumns<{
1711
1712
  enumValues: [string, ...string[]];
1712
1713
  driverParam: string;
1713
1714
  }, {}, {}, import("drizzle-orm").ColumnBuilderExtraConfig>;
1714
- size: undefined;
1715
1715
  }>;
1716
1716
  requirePKCE: import("drizzle-orm/pg-core").PgColumn<{
1717
1717
  name: "require_pkce";
@@ -1940,6 +1940,7 @@ declare const oauthRefreshToken: import("drizzle-orm/pg-core").PgTableWithColumn
1940
1940
  identity: undefined;
1941
1941
  generated: undefined;
1942
1942
  }, {}, {
1943
+ size: undefined;
1943
1944
  baseBuilder: import("drizzle-orm/pg-core").PgColumnBuilder<{
1944
1945
  name: "resources";
1945
1946
  dataType: "string";
@@ -1948,7 +1949,6 @@ declare const oauthRefreshToken: import("drizzle-orm/pg-core").PgTableWithColumn
1948
1949
  enumValues: [string, ...string[]];
1949
1950
  driverParam: string;
1950
1951
  }, {}, {}, import("drizzle-orm").ColumnBuilderExtraConfig>;
1951
- size: undefined;
1952
1952
  }>;
1953
1953
  requestedUserInfoClaims: import("drizzle-orm/pg-core").PgColumn<{
1954
1954
  name: "requested_user_info_claims";
@@ -1983,6 +1983,7 @@ declare const oauthRefreshToken: import("drizzle-orm/pg-core").PgTableWithColumn
1983
1983
  identity: undefined;
1984
1984
  generated: undefined;
1985
1985
  }, {}, {
1986
+ size: undefined;
1986
1987
  baseBuilder: import("drizzle-orm/pg-core").PgColumnBuilder<{
1987
1988
  name: "requested_user_info_claims";
1988
1989
  dataType: "string";
@@ -1991,7 +1992,6 @@ declare const oauthRefreshToken: import("drizzle-orm/pg-core").PgTableWithColumn
1991
1992
  enumValues: [string, ...string[]];
1992
1993
  driverParam: string;
1993
1994
  }, {}, {}, import("drizzle-orm").ColumnBuilderExtraConfig>;
1994
- size: undefined;
1995
1995
  }>;
1996
1996
  expiresAt: import("drizzle-orm/pg-core").PgColumn<{
1997
1997
  name: "expires_at";
@@ -2162,6 +2162,7 @@ declare const oauthRefreshToken: import("drizzle-orm/pg-core").PgTableWithColumn
2162
2162
  identity: undefined;
2163
2163
  generated: undefined;
2164
2164
  }, {}, {
2165
+ size: undefined;
2165
2166
  baseBuilder: import("drizzle-orm/pg-core").PgColumnBuilder<{
2166
2167
  name: "scopes";
2167
2168
  dataType: "string";
@@ -2170,7 +2171,6 @@ declare const oauthRefreshToken: import("drizzle-orm/pg-core").PgTableWithColumn
2170
2171
  enumValues: [string, ...string[]];
2171
2172
  driverParam: string;
2172
2173
  }, {}, {}, import("drizzle-orm").ColumnBuilderExtraConfig>;
2173
- size: undefined;
2174
2174
  }>;
2175
2175
  };
2176
2176
  dialect: "pg";
@@ -2331,6 +2331,7 @@ declare const oauthAccessToken: import("drizzle-orm/pg-core").PgTableWithColumns
2331
2331
  identity: undefined;
2332
2332
  generated: undefined;
2333
2333
  }, {}, {
2334
+ size: undefined;
2334
2335
  baseBuilder: import("drizzle-orm/pg-core").PgColumnBuilder<{
2335
2336
  name: "resources";
2336
2337
  dataType: "string";
@@ -2339,7 +2340,6 @@ declare const oauthAccessToken: import("drizzle-orm/pg-core").PgTableWithColumns
2339
2340
  enumValues: [string, ...string[]];
2340
2341
  driverParam: string;
2341
2342
  }, {}, {}, import("drizzle-orm").ColumnBuilderExtraConfig>;
2342
- size: undefined;
2343
2343
  }>;
2344
2344
  requestedUserInfoClaims: import("drizzle-orm/pg-core").PgColumn<{
2345
2345
  name: "requested_user_info_claims";
@@ -2374,6 +2374,7 @@ declare const oauthAccessToken: import("drizzle-orm/pg-core").PgTableWithColumns
2374
2374
  identity: undefined;
2375
2375
  generated: undefined;
2376
2376
  }, {}, {
2377
+ size: undefined;
2377
2378
  baseBuilder: import("drizzle-orm/pg-core").PgColumnBuilder<{
2378
2379
  name: "requested_user_info_claims";
2379
2380
  dataType: "string";
@@ -2382,7 +2383,6 @@ declare const oauthAccessToken: import("drizzle-orm/pg-core").PgTableWithColumns
2382
2383
  enumValues: [string, ...string[]];
2383
2384
  driverParam: string;
2384
2385
  }, {}, {}, import("drizzle-orm").ColumnBuilderExtraConfig>;
2385
- size: undefined;
2386
2386
  }>;
2387
2387
  refreshId: import("drizzle-orm/pg-core").PgColumn<{
2388
2388
  name: "refresh_id";
@@ -2502,6 +2502,7 @@ declare const oauthAccessToken: import("drizzle-orm/pg-core").PgTableWithColumns
2502
2502
  identity: undefined;
2503
2503
  generated: undefined;
2504
2504
  }, {}, {
2505
+ size: undefined;
2505
2506
  baseBuilder: import("drizzle-orm/pg-core").PgColumnBuilder<{
2506
2507
  name: "scopes";
2507
2508
  dataType: "string";
@@ -2510,7 +2511,6 @@ declare const oauthAccessToken: import("drizzle-orm/pg-core").PgTableWithColumns
2510
2511
  enumValues: [string, ...string[]];
2511
2512
  driverParam: string;
2512
2513
  }, {}, {}, import("drizzle-orm").ColumnBuilderExtraConfig>;
2513
- size: undefined;
2514
2514
  }>;
2515
2515
  };
2516
2516
  dialect: "pg";
@@ -2620,6 +2620,7 @@ declare const oauthConsent: import("drizzle-orm/pg-core").PgTableWithColumns<{
2620
2620
  identity: undefined;
2621
2621
  generated: undefined;
2622
2622
  }, {}, {
2623
+ size: undefined;
2623
2624
  baseBuilder: import("drizzle-orm/pg-core").PgColumnBuilder<{
2624
2625
  name: "resources";
2625
2626
  dataType: "string";
@@ -2628,7 +2629,6 @@ declare const oauthConsent: import("drizzle-orm/pg-core").PgTableWithColumns<{
2628
2629
  enumValues: [string, ...string[]];
2629
2630
  driverParam: string;
2630
2631
  }, {}, {}, import("drizzle-orm").ColumnBuilderExtraConfig>;
2631
- size: undefined;
2632
2632
  }>;
2633
2633
  requestedUserInfoClaims: import("drizzle-orm/pg-core").PgColumn<{
2634
2634
  name: "requested_user_info_claims";
@@ -2663,6 +2663,7 @@ declare const oauthConsent: import("drizzle-orm/pg-core").PgTableWithColumns<{
2663
2663
  identity: undefined;
2664
2664
  generated: undefined;
2665
2665
  }, {}, {
2666
+ size: undefined;
2666
2667
  baseBuilder: import("drizzle-orm/pg-core").PgColumnBuilder<{
2667
2668
  name: "requested_user_info_claims";
2668
2669
  dataType: "string";
@@ -2671,7 +2672,6 @@ declare const oauthConsent: import("drizzle-orm/pg-core").PgTableWithColumns<{
2671
2672
  enumValues: [string, ...string[]];
2672
2673
  driverParam: string;
2673
2674
  }, {}, {}, import("drizzle-orm").ColumnBuilderExtraConfig>;
2674
- size: undefined;
2675
2675
  }>;
2676
2676
  scopes: import("drizzle-orm/pg-core").PgColumn<{
2677
2677
  name: "scopes";
@@ -2706,6 +2706,7 @@ declare const oauthConsent: import("drizzle-orm/pg-core").PgTableWithColumns<{
2706
2706
  identity: undefined;
2707
2707
  generated: undefined;
2708
2708
  }, {}, {
2709
+ size: undefined;
2709
2710
  baseBuilder: import("drizzle-orm/pg-core").PgColumnBuilder<{
2710
2711
  name: "scopes";
2711
2712
  dataType: "string";
@@ -2714,7 +2715,6 @@ declare const oauthConsent: import("drizzle-orm/pg-core").PgTableWithColumns<{
2714
2715
  enumValues: [string, ...string[]];
2715
2716
  driverParam: string;
2716
2717
  }, {}, {}, import("drizzle-orm").ColumnBuilderExtraConfig>;
2717
- size: undefined;
2718
2718
  }>;
2719
2719
  createdAt: import("drizzle-orm/pg-core").PgColumn<{
2720
2720
  name: "created_at";
@@ -2909,6 +2909,7 @@ declare const oauthResource: import("drizzle-orm/pg-core").PgTableWithColumns<{
2909
2909
  identity: undefined;
2910
2910
  generated: undefined;
2911
2911
  }, {}, {
2912
+ size: undefined;
2912
2913
  baseBuilder: import("drizzle-orm/pg-core").PgColumnBuilder<{
2913
2914
  name: "allowed_scopes";
2914
2915
  dataType: "string";
@@ -2917,7 +2918,6 @@ declare const oauthResource: import("drizzle-orm/pg-core").PgTableWithColumns<{
2917
2918
  enumValues: [string, ...string[]];
2918
2919
  driverParam: string;
2919
2920
  }, {}, {}, import("drizzle-orm").ColumnBuilderExtraConfig>;
2920
- size: undefined;
2921
2921
  }>;
2922
2922
  customClaims: import("drizzle-orm/pg-core").PgColumn<{
2923
2923
  name: "custom_claims";
@@ -93,7 +93,7 @@ declare const entitlements: import("drizzle-orm/pg-core").PgTableWithColumns<{
93
93
  tableName: "entitlements";
94
94
  dataType: "string";
95
95
  columnType: "PgText";
96
- data: "revoked" | "active";
96
+ data: "active" | "revoked";
97
97
  driverParam: string;
98
98
  notNull: true;
99
99
  hasDefault: true;
@@ -287,7 +287,7 @@ declare const spaceMembers: import("drizzle-orm/pg-core").PgTableWithColumns<{
287
287
  tableName: "space_members";
288
288
  dataType: "string";
289
289
  columnType: "PgText";
290
- data: "revoked" | "invited" | "active" | "requested";
290
+ data: "invited" | "active" | "revoked" | "requested";
291
291
  driverParam: string;
292
292
  notNull: true;
293
293
  hasDefault: false;
@@ -330,7 +330,7 @@ declare const invites: import("drizzle-orm/pg-core").PgTableWithColumns<{
330
330
  tableName: "invites";
331
331
  dataType: "string";
332
332
  columnType: "PgText";
333
- data: "pending" | "canceled" | "accepted" | "rejected";
333
+ data: "pending" | "accepted" | "rejected" | "canceled";
334
334
  driverParam: string;
335
335
  notNull: true;
336
336
  hasDefault: false;
@@ -138,6 +138,7 @@ declare const orgRoles: import("drizzle-orm/pg-core").PgTableWithColumns<{
138
138
  identity: undefined;
139
139
  generated: undefined;
140
140
  }, {}, {
141
+ size: undefined;
141
142
  baseBuilder: import("drizzle-orm/pg-core").PgColumnBuilder<{
142
143
  name: "levels";
143
144
  dataType: "string";
@@ -146,7 +147,6 @@ declare const orgRoles: import("drizzle-orm/pg-core").PgTableWithColumns<{
146
147
  enumValues: ["org", "course", "download", "community", "event"];
147
148
  driverParam: string;
148
149
  }, {}, {}, import("drizzle-orm").ColumnBuilderExtraConfig>;
149
- size: undefined;
150
150
  }>;
151
151
  permissions: import("drizzle-orm/pg-core").PgColumn<{
152
152
  name: "permissions";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@headless-lms/adapter-db",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -21,7 +21,7 @@
21
21
  "dependencies": {
22
22
  "drizzle-orm": "^0.45.2",
23
23
  "pg": "^8.13.1",
24
- "@headless-lms/core": "0.7.0"
24
+ "@headless-lms/core": "0.9.0"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/node": "^22.10.2",