@wopr-network/platform-core 1.10.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,11 +7,79 @@ export interface IDeletionExecutorRepository {
7
7
  markCompleted(id: string, deletionSummary: string): Promise<boolean>;
8
8
  /** Find the active (pending) deletion request for a tenant, if any. */
9
9
  findPendingByTenant(tenantId: string): Promise<DeletionRequestRow | null>;
10
+ deleteBotInstances(tenantId: string): Promise<number>;
11
+ deleteCreditTransactions(tenantId: string): Promise<number>;
12
+ deleteCreditBalances(tenantId: string): Promise<number>;
13
+ deleteCreditAdjustments(tenantId: string): Promise<number | null>;
14
+ deleteMeterEvents(tenantId: string): Promise<number>;
15
+ deleteUsageSummaries(tenantId: string): Promise<number>;
16
+ deleteBillingPeriodSummaries(tenantId: string): Promise<number>;
17
+ deleteStripeUsageReports(tenantId: string): Promise<number>;
18
+ deleteNotificationQueue(tenantId: string): Promise<number>;
19
+ deleteNotificationPreferences(tenantId: string): Promise<number>;
20
+ deleteEmailNotifications(tenantId: string): Promise<number>;
21
+ deleteAuditLog(tenantId: string): Promise<number>;
22
+ anonymizeAuditLog(tenantId: string): Promise<number>;
23
+ deleteAdminNotes(tenantId: string): Promise<number>;
24
+ listSnapshotS3Keys(tenantId: string): Promise<{
25
+ id: string;
26
+ s3Key: string | null;
27
+ }[]>;
28
+ deleteSnapshots(tenantId: string): Promise<number>;
29
+ deleteBackupStatus(tenantId: string): Promise<number>;
30
+ deletePayramCharges(tenantId: string): Promise<number>;
31
+ deleteTenantStatus(tenantId: string): Promise<number>;
32
+ deleteUserRolesByUser(tenantId: string): Promise<number>;
33
+ deleteUserRolesByTenant(tenantId: string): Promise<number>;
34
+ deleteTenantCustomers(tenantId: string): Promise<number>;
35
+ deleteAuthUser(tenantId: string): Promise<{
36
+ sessionChanges: number;
37
+ accountChanges: number;
38
+ verificationChanges: number;
39
+ userChanges: number;
40
+ }>;
10
41
  }
11
42
  export declare class DrizzleDeletionExecutorRepository implements IDeletionExecutorRepository {
12
43
  private readonly db;
13
- constructor(db: PlatformDb);
44
+ private readonly authDb?;
45
+ constructor(db: PlatformDb, authDb?: {
46
+ query: (sql: string, params?: unknown[]) => Promise<{
47
+ affectedRows?: number;
48
+ rowCount?: number;
49
+ }>;
50
+ } | undefined);
14
51
  findRipe(now: string): Promise<DeletionRequestRow[]>;
15
52
  markCompleted(id: string, deletionSummary: string): Promise<boolean>;
16
53
  findPendingByTenant(tenantId: string): Promise<DeletionRequestRow | null>;
54
+ deleteBotInstances(tenantId: string): Promise<number>;
55
+ deleteCreditTransactions(tenantId: string): Promise<number>;
56
+ deleteCreditBalances(tenantId: string): Promise<number>;
57
+ deleteCreditAdjustments(tenantId: string): Promise<number | null>;
58
+ deleteMeterEvents(tenantId: string): Promise<number>;
59
+ deleteUsageSummaries(tenantId: string): Promise<number>;
60
+ deleteBillingPeriodSummaries(tenantId: string): Promise<number>;
61
+ deleteStripeUsageReports(tenantId: string): Promise<number>;
62
+ deleteNotificationQueue(tenantId: string): Promise<number>;
63
+ deleteNotificationPreferences(tenantId: string): Promise<number>;
64
+ deleteEmailNotifications(tenantId: string): Promise<number>;
65
+ deleteAuditLog(tenantId: string): Promise<number>;
66
+ anonymizeAuditLog(tenantId: string): Promise<number>;
67
+ deleteAdminNotes(tenantId: string): Promise<number>;
68
+ listSnapshotS3Keys(tenantId: string): Promise<{
69
+ id: string;
70
+ s3Key: string | null;
71
+ }[]>;
72
+ deleteSnapshots(tenantId: string): Promise<number>;
73
+ deleteBackupStatus(tenantId: string): Promise<number>;
74
+ deletePayramCharges(tenantId: string): Promise<number>;
75
+ deleteTenantStatus(tenantId: string): Promise<number>;
76
+ deleteUserRolesByUser(tenantId: string): Promise<number>;
77
+ deleteUserRolesByTenant(tenantId: string): Promise<number>;
78
+ deleteTenantCustomers(tenantId: string): Promise<number>;
79
+ deleteAuthUser(tenantId: string): Promise<{
80
+ sessionChanges: number;
81
+ accountChanges: number;
82
+ verificationChanges: number;
83
+ userChanges: number;
84
+ }>;
17
85
  }
@@ -1,13 +1,15 @@
1
- import { and, eq, lte, sql } from "drizzle-orm";
2
- import { accountDeletionRequests } from "../db/schema/index.js";
1
+ import { and, eq, like, lte, or, sql } from "drizzle-orm";
2
+ import { accountDeletionRequests, adminAuditLog, adminNotes, auditLog, backupStatus, billingPeriodSummaries, botInstances, creditBalances, creditTransactions, emailNotifications, meterEvents, notificationPreferences, notificationQueue, payramCharges, snapshots, stripeUsageReports, tenantCustomers, tenantStatus, usageSummaries, userRoles, } from "../db/schema/index.js";
3
3
  import { toRow } from "./deletion-repository.js";
4
4
  // ---------------------------------------------------------------------------
5
5
  // Implementation
6
6
  // ---------------------------------------------------------------------------
7
7
  export class DrizzleDeletionExecutorRepository {
8
8
  db;
9
- constructor(db) {
9
+ authDb;
10
+ constructor(db, authDb) {
10
11
  this.db = db;
12
+ this.authDb = authDb;
11
13
  }
12
14
  async findRipe(now) {
13
15
  const rows = await this.db
@@ -38,4 +40,194 @@ export class DrizzleDeletionExecutorRepository {
38
40
  const row = rows[0];
39
41
  return row ? toRow(row) : null;
40
42
  }
43
+ // --- Data deletion methods ---
44
+ async deleteBotInstances(tenantId) {
45
+ const result = await this.db
46
+ .delete(botInstances)
47
+ .where(eq(botInstances.tenantId, tenantId))
48
+ .returning({ id: botInstances.id });
49
+ return result.length;
50
+ }
51
+ async deleteCreditTransactions(tenantId) {
52
+ const result = await this.db
53
+ .delete(creditTransactions)
54
+ .where(eq(creditTransactions.tenantId, tenantId))
55
+ .returning({ id: creditTransactions.id });
56
+ return result.length;
57
+ }
58
+ async deleteCreditBalances(tenantId) {
59
+ const result = await this.db
60
+ .delete(creditBalances)
61
+ .where(eq(creditBalances.tenantId, tenantId))
62
+ .returning({ tenantId: creditBalances.tenantId });
63
+ return result.length;
64
+ }
65
+ async deleteCreditAdjustments(tenantId) {
66
+ // raw SQL: credit_adjustments is not in the Drizzle schema (optional table)
67
+ try {
68
+ const result = await this.db.execute(sql `DELETE FROM credit_adjustments WHERE tenant_id = ${tenantId}`);
69
+ return result.rowCount ?? 0;
70
+ }
71
+ catch (err) {
72
+ const pgCode = err.code;
73
+ if (pgCode === "42P01")
74
+ return null; // table does not exist
75
+ throw err;
76
+ }
77
+ }
78
+ async deleteMeterEvents(tenantId) {
79
+ const result = await this.db
80
+ .delete(meterEvents)
81
+ .where(eq(meterEvents.tenant, tenantId))
82
+ .returning({ id: meterEvents.id });
83
+ return result.length;
84
+ }
85
+ async deleteUsageSummaries(tenantId) {
86
+ const result = await this.db
87
+ .delete(usageSummaries)
88
+ .where(eq(usageSummaries.tenant, tenantId))
89
+ .returning({ id: usageSummaries.id });
90
+ return result.length;
91
+ }
92
+ async deleteBillingPeriodSummaries(tenantId) {
93
+ const result = await this.db
94
+ .delete(billingPeriodSummaries)
95
+ .where(eq(billingPeriodSummaries.tenant, tenantId))
96
+ .returning({ id: billingPeriodSummaries.id });
97
+ return result.length;
98
+ }
99
+ async deleteStripeUsageReports(tenantId) {
100
+ const result = await this.db
101
+ .delete(stripeUsageReports)
102
+ .where(eq(stripeUsageReports.tenant, tenantId))
103
+ .returning({ id: stripeUsageReports.id });
104
+ return result.length;
105
+ }
106
+ async deleteNotificationQueue(tenantId) {
107
+ const result = await this.db
108
+ .delete(notificationQueue)
109
+ .where(eq(notificationQueue.tenantId, tenantId))
110
+ .returning({ id: notificationQueue.id });
111
+ return result.length;
112
+ }
113
+ async deleteNotificationPreferences(tenantId) {
114
+ const result = await this.db
115
+ .delete(notificationPreferences)
116
+ .where(eq(notificationPreferences.tenantId, tenantId))
117
+ .returning({ tenantId: notificationPreferences.tenantId });
118
+ return result.length;
119
+ }
120
+ async deleteEmailNotifications(tenantId) {
121
+ const result = await this.db
122
+ .delete(emailNotifications)
123
+ .where(eq(emailNotifications.tenantId, tenantId))
124
+ .returning({ id: emailNotifications.id });
125
+ return result.length;
126
+ }
127
+ async deleteAuditLog(tenantId) {
128
+ const result = await this.db.delete(auditLog).where(eq(auditLog.userId, tenantId)).returning({ id: auditLog.id });
129
+ return result.length;
130
+ }
131
+ async anonymizeAuditLog(tenantId) {
132
+ const result = await this.db
133
+ .update(adminAuditLog)
134
+ .set({ targetTenant: "[deleted]", targetUser: "[deleted]" })
135
+ .where(or(eq(adminAuditLog.targetTenant, tenantId), eq(adminAuditLog.targetUser, tenantId)))
136
+ .returning({ id: adminAuditLog.id });
137
+ return result.length;
138
+ }
139
+ async deleteAdminNotes(tenantId) {
140
+ const result = await this.db
141
+ .delete(adminNotes)
142
+ .where(eq(adminNotes.tenantId, tenantId))
143
+ .returning({ id: adminNotes.id });
144
+ return result.length;
145
+ }
146
+ async listSnapshotS3Keys(tenantId) {
147
+ const rows = await this.db
148
+ .select({ id: snapshots.id, s3Key: snapshots.s3Key })
149
+ .from(snapshots)
150
+ .where(eq(snapshots.tenant, tenantId));
151
+ return rows;
152
+ }
153
+ async deleteSnapshots(tenantId) {
154
+ const result = await this.db
155
+ .delete(snapshots)
156
+ .where(eq(snapshots.tenant, tenantId))
157
+ .returning({ id: snapshots.id });
158
+ return result.length;
159
+ }
160
+ async deleteBackupStatus(tenantId) {
161
+ // Escape LIKE wildcards in tenantId to prevent injection
162
+ const safeTenantId = tenantId.replace(/%/g, "\\%").replace(/_/g, "\\_");
163
+ // backup_status uses containerId with pattern "tenant_{id}_..."
164
+ const result = await this.db
165
+ .delete(backupStatus)
166
+ .where(like(backupStatus.containerId, `tenant_${safeTenantId}%`))
167
+ .returning({ containerId: backupStatus.containerId });
168
+ return result.length;
169
+ }
170
+ async deletePayramCharges(tenantId) {
171
+ const result = await this.db
172
+ .delete(payramCharges)
173
+ .where(eq(payramCharges.tenantId, tenantId))
174
+ .returning({ referenceId: payramCharges.referenceId });
175
+ return result.length;
176
+ }
177
+ async deleteTenantStatus(tenantId) {
178
+ const result = await this.db
179
+ .delete(tenantStatus)
180
+ .where(eq(tenantStatus.tenantId, tenantId))
181
+ .returning({ tenantId: tenantStatus.tenantId });
182
+ return result.length;
183
+ }
184
+ async deleteUserRolesByUser(tenantId) {
185
+ const result = await this.db
186
+ .delete(userRoles)
187
+ .where(eq(userRoles.userId, tenantId))
188
+ .returning({ userId: userRoles.userId });
189
+ return result.length;
190
+ }
191
+ async deleteUserRolesByTenant(tenantId) {
192
+ const result = await this.db
193
+ .delete(userRoles)
194
+ .where(eq(userRoles.tenantId, tenantId))
195
+ .returning({ userId: userRoles.userId });
196
+ return result.length;
197
+ }
198
+ async deleteTenantCustomers(tenantId) {
199
+ const result = await this.db
200
+ .delete(tenantCustomers)
201
+ .where(eq(tenantCustomers.tenant, tenantId))
202
+ .returning({ tenant: tenantCustomers.tenant });
203
+ return result.length;
204
+ }
205
+ async deleteAuthUser(tenantId) {
206
+ if (!this.authDb) {
207
+ return { sessionChanges: 0, accountChanges: 0, verificationChanges: 0, userChanges: 0 };
208
+ }
209
+ const getCount = (result) => result.affectedRows ?? result.rowCount ?? 0;
210
+ // raw SQL: better-auth tables are not in the Drizzle schema
211
+ // Wrapped in a transaction — all four DELETEs must succeed or none.
212
+ await this.authDb.query("BEGIN", []);
213
+ try {
214
+ const sessionResult = await this.authDb.query(`DELETE FROM session WHERE user_id = $1`, [tenantId]);
215
+ const accountResult = await this.authDb.query(`DELETE FROM account WHERE user_id = $1`, [tenantId]);
216
+ const verificationResult = await this.authDb.query(`DELETE FROM email_verification_tokens WHERE user_id = $1`, [
217
+ tenantId,
218
+ ]);
219
+ const userResult = await this.authDb.query(`DELETE FROM "user" WHERE id = $1`, [tenantId]);
220
+ await this.authDb.query("COMMIT", []);
221
+ return {
222
+ sessionChanges: getCount(sessionResult),
223
+ accountChanges: getCount(accountResult),
224
+ verificationChanges: getCount(verificationResult),
225
+ userChanges: getCount(userResult),
226
+ };
227
+ }
228
+ catch (err) {
229
+ await this.authDb.query("ROLLBACK", []);
230
+ throw err;
231
+ }
232
+ }
41
233
  }
@@ -1,12 +1,23 @@
1
1
  import type { PlatformDb } from "../db/index.js";
2
2
  import { accountDeletionRequests } from "../db/schema/index.js";
3
- import type { DeletionRequestRow, InsertDeletionRequest } from "./repository-types.js";
3
+ import type { DeletionRequestRow, DeletionStatus, InsertDeletionRequest } from "./repository-types.js";
4
4
  export type { DeletionRequestRow, InsertDeletionRequest };
5
5
  export interface IDeletionRepository {
6
6
  insert(data: InsertDeletionRequest): Promise<void>;
7
7
  getById(id: string): Promise<DeletionRequestRow | null>;
8
8
  listByTenant(tenantId: string): Promise<DeletionRequestRow[]>;
9
+ getPendingForTenant(tenantId: string): Promise<DeletionRequestRow | null>;
9
10
  cancel(id: string, cancelReason: string): Promise<boolean>;
11
+ markCompleted(id: string, summary: string): Promise<void>;
12
+ findExpired(): Promise<DeletionRequestRow[]>;
13
+ list(opts: {
14
+ status?: DeletionStatus;
15
+ limit: number;
16
+ offset: number;
17
+ }): Promise<{
18
+ requests: DeletionRequestRow[];
19
+ total: number;
20
+ }>;
10
21
  }
11
22
  export declare class DrizzleDeletionRepository implements IDeletionRepository {
12
23
  private readonly db;
@@ -14,6 +25,17 @@ export declare class DrizzleDeletionRepository implements IDeletionRepository {
14
25
  insert(data: InsertDeletionRequest): Promise<void>;
15
26
  getById(id: string): Promise<DeletionRequestRow | null>;
16
27
  listByTenant(tenantId: string): Promise<DeletionRequestRow[]>;
28
+ getPendingForTenant(tenantId: string): Promise<DeletionRequestRow | null>;
17
29
  cancel(id: string, cancelReason: string): Promise<boolean>;
30
+ markCompleted(id: string, summary: string): Promise<void>;
31
+ findExpired(): Promise<DeletionRequestRow[]>;
32
+ list(opts: {
33
+ status?: DeletionStatus;
34
+ limit: number;
35
+ offset: number;
36
+ }): Promise<{
37
+ requests: DeletionRequestRow[];
38
+ total: number;
39
+ }>;
18
40
  }
19
41
  export declare function toRow(row: typeof accountDeletionRequests.$inferSelect): DeletionRequestRow;
@@ -1,4 +1,4 @@
1
- import { and, eq, sql } from "drizzle-orm";
1
+ import { and, count, desc, eq, lte, sql } from "drizzle-orm";
2
2
  import { accountDeletionRequests } from "../db/schema/index.js";
3
3
  // ---------------------------------------------------------------------------
4
4
  // Implementation
@@ -9,11 +9,12 @@ export class DrizzleDeletionRepository {
9
9
  this.db = db;
10
10
  }
11
11
  async insert(data) {
12
+ const deleteAfter = data.deleteAfter ?? new Date(Date.now() + (data.graceDays ?? 30) * 24 * 60 * 60 * 1000).toISOString();
12
13
  await this.db.insert(accountDeletionRequests).values({
13
14
  id: data.id,
14
15
  tenantId: data.tenantId,
15
16
  requestedBy: data.requestedBy,
16
- deleteAfter: data.deleteAfter,
17
+ deleteAfter,
17
18
  reason: data.reason ?? null,
18
19
  });
19
20
  }
@@ -29,6 +30,15 @@ export class DrizzleDeletionRepository {
29
30
  .where(eq(accountDeletionRequests.tenantId, tenantId));
30
31
  return rows.map(toRow);
31
32
  }
33
+ async getPendingForTenant(tenantId) {
34
+ const rows = await this.db
35
+ .select()
36
+ .from(accountDeletionRequests)
37
+ .where(and(eq(accountDeletionRequests.tenantId, tenantId), eq(accountDeletionRequests.status, "pending")))
38
+ .limit(1);
39
+ const row = rows[0];
40
+ return row ? toRow(row) : null;
41
+ }
32
42
  async cancel(id, cancelReason) {
33
43
  const result = await this.db
34
44
  .update(accountDeletionRequests)
@@ -41,6 +51,41 @@ export class DrizzleDeletionRepository {
41
51
  .returning({ id: accountDeletionRequests.id });
42
52
  return result.length > 0;
43
53
  }
54
+ async markCompleted(id, summary) {
55
+ await this.db
56
+ .update(accountDeletionRequests)
57
+ .set({
58
+ status: "completed",
59
+ completedAt: sql `now()`,
60
+ deletionSummary: summary,
61
+ updatedAt: sql `now()`,
62
+ })
63
+ .where(and(eq(accountDeletionRequests.id, id), eq(accountDeletionRequests.status, "pending")));
64
+ }
65
+ async findExpired() {
66
+ const rows = await this.db
67
+ .select()
68
+ .from(accountDeletionRequests)
69
+ .where(and(eq(accountDeletionRequests.status, "pending"), lte(accountDeletionRequests.deleteAfter, sql `now()`)));
70
+ return rows.map(toRow);
71
+ }
72
+ async list(opts) {
73
+ const conditions = opts.status ? eq(accountDeletionRequests.status, opts.status) : undefined;
74
+ const [rows, totalResult] = await Promise.all([
75
+ this.db
76
+ .select()
77
+ .from(accountDeletionRequests)
78
+ .where(conditions)
79
+ .orderBy(desc(accountDeletionRequests.createdAt))
80
+ .limit(opts.limit)
81
+ .offset(opts.offset),
82
+ this.db.select({ count: count() }).from(accountDeletionRequests).where(conditions),
83
+ ]);
84
+ return {
85
+ requests: rows.map(toRow),
86
+ total: totalResult[0]?.count ?? 0,
87
+ };
88
+ }
44
89
  }
45
90
  // ---------------------------------------------------------------------------
46
91
  // Row mapper
@@ -1,5 +1,5 @@
1
1
  import type { PlatformDb } from "../db/index.js";
2
- import type { ExportRequestRow, InsertExportRequest } from "./repository-types.js";
2
+ import type { ExportRequestRow, ExportStatus, InsertExportRequest } from "./repository-types.js";
3
3
  export type { ExportRequestRow, InsertExportRequest };
4
4
  export interface IExportRepository {
5
5
  insert(data: InsertExportRequest): Promise<void>;
@@ -8,6 +8,15 @@ export interface IExportRepository {
8
8
  markProcessing(id: string): Promise<boolean>;
9
9
  markCompleted(id: string, downloadUrl: string): Promise<boolean>;
10
10
  markFailed(id: string, errorMessage?: string): Promise<boolean>;
11
+ list(filters: {
12
+ status?: ExportStatus;
13
+ limit: number;
14
+ offset: number;
15
+ }): Promise<{
16
+ rows: ExportRequestRow[];
17
+ total: number;
18
+ }>;
19
+ updateStatus(id: string, status: ExportStatus, downloadUrl?: string): Promise<void>;
11
20
  }
12
21
  export declare class DrizzleExportRepository implements IExportRepository {
13
22
  private readonly db;
@@ -18,4 +27,13 @@ export declare class DrizzleExportRepository implements IExportRepository {
18
27
  markProcessing(id: string): Promise<boolean>;
19
28
  markCompleted(id: string, downloadUrl: string): Promise<boolean>;
20
29
  markFailed(id: string, _errorMessage?: string): Promise<boolean>;
30
+ list(filters: {
31
+ status?: ExportStatus;
32
+ limit: number;
33
+ offset: number;
34
+ }): Promise<{
35
+ rows: ExportRequestRow[];
36
+ total: number;
37
+ }>;
38
+ updateStatus(id: string, status: ExportStatus, downloadUrl?: string): Promise<void>;
21
39
  }
@@ -1,4 +1,4 @@
1
- import { and, eq, sql } from "drizzle-orm";
1
+ import { and, count, desc, eq, sql } from "drizzle-orm";
2
2
  import { accountExportRequests } from "../db/schema/index.js";
3
3
  // ---------------------------------------------------------------------------
4
4
  // Implementation
@@ -59,6 +59,34 @@ export class DrizzleExportRepository {
59
59
  .returning({ id: accountExportRequests.id });
60
60
  return result.length > 0;
61
61
  }
62
+ async list(filters) {
63
+ const conditions = filters.status ? eq(accountExportRequests.status, filters.status) : undefined;
64
+ const [rows, totalResult] = await Promise.all([
65
+ this.db
66
+ .select()
67
+ .from(accountExportRequests)
68
+ .where(conditions)
69
+ .orderBy(desc(accountExportRequests.createdAt))
70
+ .limit(filters.limit)
71
+ .offset(filters.offset),
72
+ this.db.select({ count: count() }).from(accountExportRequests).where(conditions),
73
+ ]);
74
+ return {
75
+ rows: rows.map(toRow),
76
+ total: totalResult[0]?.count ?? 0,
77
+ };
78
+ }
79
+ async updateStatus(id, status, downloadUrl) {
80
+ await this.db
81
+ .update(accountExportRequests)
82
+ .set({
83
+ status,
84
+ // Clear stale downloadUrl when transitioning away from completed
85
+ downloadUrl: downloadUrl ?? (status === "completed" ? undefined : null),
86
+ updatedAt: sql `now()`,
87
+ })
88
+ .where(eq(accountExportRequests.id, id));
89
+ }
62
90
  }
63
91
  // ---------------------------------------------------------------------------
64
92
  // Row mapper
@@ -17,8 +17,11 @@ export interface InsertDeletionRequest {
17
17
  id: string;
18
18
  tenantId: string;
19
19
  requestedBy: string;
20
- deleteAfter: string;
21
- reason?: string;
20
+ /** Explicit ISO timestamp for when deletion should execute. */
21
+ deleteAfter?: string;
22
+ /** Number of grace days from now. Used if deleteAfter is not provided. */
23
+ graceDays?: number;
24
+ reason?: string | null;
22
25
  }
23
26
  export interface ExportRequestRow {
24
27
  id: string;
@@ -15,6 +15,8 @@ import type { Context, Next } from "hono";
15
15
  import type { IApiKeyRepository } from "./api-key-repository.js";
16
16
  import type { Auth } from "./better-auth.js";
17
17
  import type { AuthUser } from "./index.js";
18
+ /** Minimum length for API key tokens (rejects obviously short tokens). */
19
+ export declare const MIN_API_KEY_LENGTH = 22;
18
20
  export interface SessionAuthEnv {
19
21
  Variables: {
20
22
  user: AuthUser;
@@ -12,6 +12,8 @@
12
12
  * If neither is present, the request is rejected with 401.
13
13
  */
14
14
  import { createHash } from "node:crypto";
15
+ /** Minimum length for API key tokens (rejects obviously short tokens). */
16
+ export const MIN_API_KEY_LENGTH = 22;
15
17
  /**
16
18
  * Create middleware that authenticates requests via better-auth session cookies.
17
19
  *
@@ -15,6 +15,9 @@ export type PlatformDb = DrizzleDb;
15
15
  /** Create a Drizzle database instance wrapping the given pg.Pool. */
16
16
  export declare function createDb(pool: Pool): PlatformDb;
17
17
  export { schema };
18
+ export type { SQL } from "drizzle-orm";
19
+ export { and, asc, count, desc, eq, gt, gte, ilike, inArray, isNull, like, lt, lte, ne, or, sql } from "drizzle-orm";
20
+ export { pgTable, text } from "drizzle-orm/pg-core";
18
21
  export type { AuthUser, IAuthUserRepository } from "./auth-user-repository.js";
19
22
  export { BetterAuthUserRepository } from "./auth-user-repository.js";
20
23
  export { creditColumn } from "./credit-column.js";
package/dist/db/index.js CHANGED
@@ -5,5 +5,10 @@ export function createDb(pool) {
5
5
  return drizzle(pool, { schema });
6
6
  }
7
7
  export { schema };
8
+ // Re-export commonly used drizzle-orm operators so consumers using pnpm link
9
+ // resolve them from the same drizzle-orm instance as the schema tables.
10
+ export { and, asc, count, desc, eq, gt, gte, ilike, inArray, isNull, like, lt, lte, ne, or, sql } from "drizzle-orm";
11
+ // Re-export pg-core table builders for consumers that define local tables.
12
+ export { pgTable, text } from "drizzle-orm/pg-core";
8
13
  export { BetterAuthUserRepository } from "./auth-user-repository.js";
9
14
  export { creditColumn } from "./credit-column.js";
@@ -12,11 +12,10 @@
12
12
  * - tts (Chatterbox GPU, ElevenLabs)
13
13
  * - transcription (Deepgram)
14
14
  * - embeddings (OpenRouter)
15
- *
16
- * Image-generation (Nano Banana, Replicate) will be wired once the
17
- * image-gen-factory PR merges.
15
+ * - image-generation (Replicate, Nano Banana)
18
16
  */
19
17
  import { type EmbeddingsFactoryConfig } from "./embeddings-factory.js";
18
+ import { type ImageGenFactoryConfig } from "./image-gen-factory.js";
20
19
  import { type TextGenFactoryConfig } from "./text-gen-factory.js";
21
20
  import { type TranscriptionFactoryConfig } from "./transcription-factory.js";
22
21
  import { type TTSFactoryConfig } from "./tts-factory.js";
@@ -31,6 +30,8 @@ export interface BootstrapConfig {
31
30
  transcription?: TranscriptionFactoryConfig;
32
31
  /** Embeddings adapter config */
33
32
  embeddings?: EmbeddingsFactoryConfig;
33
+ /** Image generation adapter config */
34
+ imageGen?: ImageGenFactoryConfig;
34
35
  }
35
36
  /** Result of bootstrapping all adapters. */
36
37
  export interface BootstrapResult {
@@ -42,6 +43,12 @@ export interface BootstrapResult {
42
43
  * capabilities (e.g. OpenRouter for both text-gen and embeddings). Each
43
44
  * instance is independently configured. Use the per-capability factory
44
45
  * results if you need a name→adapter map within a single capability.
46
+ *
47
+ * NOTE: Some adapters advertise more capabilities than the factory that
48
+ * created them (e.g. Replicate advertises text-generation and transcription
49
+ * in addition to image-generation). The ArbitrageRouter should use the
50
+ * factory-assigned capability (reflected in byCapability), not the adapter's
51
+ * own capabilities array, for routing decisions.
45
52
  */
46
53
  adapters: ProviderAdapter[];
47
54
  /** Names of providers that were skipped (missing config), grouped by capability. */
@@ -68,6 +75,7 @@ export declare function bootstrapAdapters(config: BootstrapConfig): BootstrapRes
68
75
  * - CHATTERBOX_BASE_URL, ELEVENLABS_API_KEY (TTS)
69
76
  * - DEEPGRAM_API_KEY (transcription)
70
77
  * - OPENROUTER_API_KEY (embeddings)
78
+ * - REPLICATE_API_TOKEN, NANO_BANANA_API_KEY (image-gen)
71
79
  *
72
80
  * Accepts optional per-capability config overrides.
73
81
  */
@@ -12,11 +12,10 @@
12
12
  * - tts (Chatterbox GPU, ElevenLabs)
13
13
  * - transcription (Deepgram)
14
14
  * - embeddings (OpenRouter)
15
- *
16
- * Image-generation (Nano Banana, Replicate) will be wired once the
17
- * image-gen-factory PR merges.
15
+ * - image-generation (Replicate, Nano Banana)
18
16
  */
19
17
  import { createEmbeddingsAdapters } from "./embeddings-factory.js";
18
+ import { createImageGenAdapters } from "./image-gen-factory.js";
20
19
  import { createTextGenAdapters } from "./text-gen-factory.js";
21
20
  import { createTranscriptionAdapters } from "./transcription-factory.js";
22
21
  import { createTTSAdapters } from "./tts-factory.js";
@@ -31,11 +30,13 @@ export function bootstrapAdapters(config) {
31
30
  const tts = createTTSAdapters(config.tts ?? {});
32
31
  const transcription = createTranscriptionAdapters(config.transcription ?? {});
33
32
  const embeddings = createEmbeddingsAdapters(config.embeddings ?? {});
33
+ const imageGen = createImageGenAdapters(config.imageGen ?? {});
34
34
  const adapters = [
35
35
  ...textGen.adapters,
36
36
  ...tts.adapters,
37
37
  ...transcription.adapters,
38
38
  ...embeddings.adapters,
39
+ ...imageGen.adapters,
39
40
  ];
40
41
  const skipped = {};
41
42
  if (textGen.skipped.length > 0)
@@ -46,6 +47,8 @@ export function bootstrapAdapters(config) {
46
47
  skipped.transcription = transcription.skipped;
47
48
  if (embeddings.skipped.length > 0)
48
49
  skipped.embeddings = embeddings.skipped;
50
+ if (imageGen.skipped.length > 0)
51
+ skipped["image-generation"] = imageGen.skipped;
49
52
  let totalSkipped = 0;
50
53
  for (const list of Object.values(skipped)) {
51
54
  totalSkipped += list.length;
@@ -61,6 +64,7 @@ export function bootstrapAdapters(config) {
61
64
  tts: tts.adapters.length,
62
65
  transcription: transcription.adapters.length,
63
66
  embeddings: embeddings.adapters.length,
67
+ "image-generation": imageGen.adapters.length,
64
68
  },
65
69
  },
66
70
  };
@@ -73,6 +77,7 @@ export function bootstrapAdapters(config) {
73
77
  * - CHATTERBOX_BASE_URL, ELEVENLABS_API_KEY (TTS)
74
78
  * - DEEPGRAM_API_KEY (transcription)
75
79
  * - OPENROUTER_API_KEY (embeddings)
80
+ * - REPLICATE_API_TOKEN, NANO_BANANA_API_KEY (image-gen)
76
81
  *
77
82
  * Accepts optional per-capability config overrides.
78
83
  */
@@ -99,5 +104,12 @@ export function bootstrapAdaptersFromEnv(overrides) {
99
104
  openrouterApiKey: process.env.OPENROUTER_API_KEY,
100
105
  ...overrides?.embeddings,
101
106
  },
107
+ imageGen: {
108
+ replicateApiToken: process.env.REPLICATE_API_TOKEN,
109
+ // Separate env var from GEMINI_API_KEY (used for text-gen) to avoid
110
+ // silently enabling image-gen when only text-gen is intended.
111
+ geminiApiKey: process.env.NANO_BANANA_API_KEY,
112
+ ...overrides?.imageGen,
113
+ },
102
114
  });
103
115
  }