@mesob/ai-hono 0.5.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.
package/dist/index.js ADDED
@@ -0,0 +1,834 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
7
+ // src/db/index.ts
8
+ import { drizzle } from "drizzle-orm/node-postgres";
9
+ import { Pool } from "pg";
10
+
11
+ // src/db/schema.ts
12
+ var schema_exports = {};
13
+ __export(schema_exports, {
14
+ accountChangesInIam: () => accountChangesInIam,
15
+ accountsInIam: () => accountsInIam,
16
+ ai: () => ai,
17
+ cvInAi: () => cvInAi,
18
+ domainsInIam: () => domainsInIam,
19
+ iam: () => iam,
20
+ permissionsInIam: () => permissionsInIam,
21
+ promptInAi: () => promptInAi,
22
+ promptSystemInAi: () => promptSystemInAi,
23
+ rolePermissionsInIam: () => rolePermissionsInIam,
24
+ rolesInIam: () => rolesInIam,
25
+ sessionsInIam: () => sessionsInIam,
26
+ tenantsInIam: () => tenantsInIam,
27
+ userRolesInIam: () => userRolesInIam,
28
+ usersInIam: () => usersInIam,
29
+ verificationsInIam: () => verificationsInIam
30
+ });
31
+ import { pgSchema, index, foreignKey, pgPolicy, check, uuid, varchar, timestamp, text, smallint, unique, inet, jsonb, boolean, uniqueIndex, vector } from "drizzle-orm/pg-core";
32
+ import { sql } from "drizzle-orm";
33
+ var iam = pgSchema("iam");
34
+ var ai = pgSchema("ai");
35
+ var verificationsInIam = iam.table("verifications", {
36
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
37
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
38
+ createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
39
+ updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
40
+ userId: uuid("user_id").notNull(),
41
+ code: text().notNull(),
42
+ expiresAt: timestamp("expires_at", { withTimezone: true, mode: "string" }).notNull(),
43
+ type: text(),
44
+ attempt: smallint().default(0),
45
+ to: text()
46
+ }, (table) => [
47
+ index("verifications_expires_at_idx").using("btree", table.expiresAt.asc().nullsLast().op("timestamptz_ops")),
48
+ index("verifications_lookup_idx").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.userId.asc().nullsLast().op("text_ops"), table.type.asc().nullsLast().op("uuid_ops"), table.to.asc().nullsLast().op("text_ops"), table.code.asc().nullsLast().op("text_ops")),
49
+ foreignKey({
50
+ columns: [table.tenantId],
51
+ foreignColumns: [tenantsInIam.id],
52
+ name: "verifications_tenant_id_fkey"
53
+ }).onUpdate("cascade").onDelete("cascade"),
54
+ foreignKey({
55
+ columns: [table.userId],
56
+ foreignColumns: [usersInIam.id],
57
+ name: "verifications_user_id_fkey"
58
+ }).onUpdate("cascade").onDelete("cascade"),
59
+ pgPolicy("tenant_isolation", { as: "permissive", for: "all", to: ["public"], using: sql`((tenant_id)::text = (iam.current_tenant_id())::text)`, withCheck: sql`((tenant_id)::text = (iam.current_tenant_id())::text)` }),
60
+ check("verifications_attempt_nonnegative_check", sql`attempt >= 0`),
61
+ check("verifications_expires_after_created_check", sql`expires_at > created_at`)
62
+ ]);
63
+ var sessionsInIam = iam.table("sessions", {
64
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
65
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
66
+ createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
67
+ updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
68
+ userId: uuid("user_id").notNull(),
69
+ expiresAt: timestamp("expires_at", { withTimezone: true, mode: "string" }).notNull(),
70
+ userAgent: text("user_agent"),
71
+ ip: inet(),
72
+ meta: jsonb(),
73
+ token: text().notNull(),
74
+ rotatedAt: timestamp("rotated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`)
75
+ }, (table) => [
76
+ index("sessions_expires_at_idx").using("btree", table.expiresAt.asc().nullsLast().op("timestamptz_ops")),
77
+ index("sessions_tenant_user_idx").using("btree", table.tenantId.asc().nullsLast().op("uuid_ops"), table.userId.asc().nullsLast().op("text_ops")),
78
+ foreignKey({
79
+ columns: [table.tenantId],
80
+ foreignColumns: [tenantsInIam.id],
81
+ name: "sessions_tenant_id_fkey"
82
+ }).onUpdate("cascade").onDelete("cascade"),
83
+ foreignKey({
84
+ columns: [table.userId],
85
+ foreignColumns: [usersInIam.id],
86
+ name: "sessions_user_id_fkey"
87
+ }).onUpdate("cascade").onDelete("cascade"),
88
+ unique("sessions_token_key").on(table.token),
89
+ pgPolicy("tenant_isolation", { as: "permissive", for: "all", to: ["public"], using: sql`((tenant_id)::text = (iam.current_tenant_id())::text)`, withCheck: sql`((tenant_id)::text = (iam.current_tenant_id())::text)` }),
90
+ check("sessions_expires_after_created_check", sql`expires_at > created_at`)
91
+ ]);
92
+ var accountChangesInIam = iam.table("account_changes", {
93
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
94
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
95
+ userId: uuid("user_id").notNull(),
96
+ createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
97
+ updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
98
+ changeType: text("change_type").notNull(),
99
+ oldEmail: varchar("old_email"),
100
+ newEmail: varchar("new_email"),
101
+ oldPhone: text("old_phone"),
102
+ newPhone: text("new_phone"),
103
+ status: varchar().notNull(),
104
+ expiresAt: timestamp("expires_at", { withTimezone: true, mode: "string" }).notNull(),
105
+ confirmedAt: timestamp("confirmed_at", { withTimezone: true, mode: "string" }),
106
+ cancelledAt: timestamp("cancelled_at", { withTimezone: true, mode: "string" }),
107
+ reason: text()
108
+ }, (table) => [
109
+ index("account_changes_expires_at_idx").using("btree", table.expiresAt.asc().nullsLast().op("timestamptz_ops")),
110
+ index("account_changes_tenant_user_status_idx").using("btree", table.tenantId.asc().nullsLast().op("uuid_ops"), table.userId.asc().nullsLast().op("text_ops"), table.status.asc().nullsLast().op("uuid_ops")),
111
+ index("idx_account_changes_expired").using("btree", table.expiresAt.asc().nullsLast().op("text_ops"), table.status.asc().nullsLast().op("text_ops")).where(sql`((status)::text = 'pending'::text)`),
112
+ foreignKey({
113
+ columns: [table.tenantId],
114
+ foreignColumns: [tenantsInIam.id],
115
+ name: "account_changes_tenant_id_fkey"
116
+ }).onUpdate("cascade").onDelete("cascade"),
117
+ foreignKey({
118
+ columns: [table.userId],
119
+ foreignColumns: [usersInIam.id],
120
+ name: "account_changes_user_id_fkey"
121
+ }).onUpdate("cascade").onDelete("cascade"),
122
+ pgPolicy("tenant_isolation", { as: "permissive", for: "all", to: ["public"], using: sql`((tenant_id)::text = (iam.current_tenant_id())::text)`, withCheck: sql`((tenant_id)::text = (iam.current_tenant_id())::text)` }),
123
+ check("account_changes_expires_after_created_check", sql`expires_at > created_at`),
124
+ check("account_changes_change_type_check", sql`((change_type = 'EMAIL'::text) AND (old_email IS NOT NULL) AND (new_email IS NOT NULL) AND (old_phone IS NULL) AND (new_phone IS NULL)) OR ((change_type = 'PHONE'::text) AND (old_phone IS NOT NULL) AND (new_phone IS NOT NULL) AND (old_email IS NULL) AND (new_email IS NULL))`),
125
+ check("account_changes_status_check", sql`(status)::text = ANY (ARRAY[('PENDING'::character varying)::text, ('APPLIED'::character varying)::text, ('CANCELLED'::character varying)::text, ('EXPIRED'::character varying)::text])`)
126
+ ]);
127
+ var tenantsInIam = iam.table("tenants", {
128
+ id: varchar({ length: 30 }).primaryKey().notNull(),
129
+ createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
130
+ updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
131
+ name: jsonb().notNull(),
132
+ description: jsonb(),
133
+ theme: jsonb(),
134
+ supportedLanguages: jsonb("supported_languages"),
135
+ defaultLanguage: text("default_language"),
136
+ supportedCurrency: jsonb("supported_currency"),
137
+ defaultCurrency: text("default_currency"),
138
+ timezone: text(),
139
+ isActive: boolean("is_active").default(true).notNull(),
140
+ locale: jsonb(),
141
+ settings: jsonb(),
142
+ seo: jsonb()
143
+ }, (table) => [
144
+ index("tenants_is_active_idx").using("btree", table.isActive.asc().nullsLast().op("bool_ops"))
145
+ ]);
146
+ var rolePermissionsInIam = iam.table("role_permissions", {
147
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
148
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
149
+ permissionId: text("permission_id").notNull(),
150
+ roleId: uuid("role_id").notNull()
151
+ }, (table) => [
152
+ index("idx_role_permissions_permission_id").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.permissionId.asc().nullsLast().op("text_ops")),
153
+ foreignKey({
154
+ columns: [table.tenantId],
155
+ foreignColumns: [tenantsInIam.id],
156
+ name: "role_permissions_tenant_id_fkey"
157
+ }).onUpdate("cascade").onDelete("cascade"),
158
+ foreignKey({
159
+ columns: [table.permissionId],
160
+ foreignColumns: [permissionsInIam.id],
161
+ name: "role_permissions_permission_id_fkey"
162
+ }).onUpdate("cascade").onDelete("cascade"),
163
+ foreignKey({
164
+ columns: [table.tenantId, table.roleId],
165
+ foreignColumns: [rolesInIam.tenantId, rolesInIam.id],
166
+ name: "role_permissions_tenant_role_fkey"
167
+ }).onDelete("cascade"),
168
+ unique("role_permissions_tenant_role_permission_unique").on(table.tenantId, table.permissionId, table.roleId),
169
+ pgPolicy("tenant_isolation", { as: "permissive", for: "all", to: ["public"], using: sql`((tenant_id)::text = (iam.current_tenant_id())::text)`, withCheck: sql`((tenant_id)::text = (iam.current_tenant_id())::text)` })
170
+ ]);
171
+ var permissionsInIam = iam.table("permissions", {
172
+ id: text().primaryKey().notNull(),
173
+ description: jsonb().notNull(),
174
+ activity: text().notNull(),
175
+ application: text().notNull(),
176
+ feature: text().notNull()
177
+ }, (table) => [
178
+ unique("permissions_activity_application_feature_key").on(table.activity, table.application, table.feature)
179
+ ]);
180
+ var accountsInIam = iam.table("accounts", {
181
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
182
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
183
+ userId: uuid("user_id").notNull(),
184
+ provider: text().notNull(),
185
+ providerAccountId: text("provider_account_id").notNull(),
186
+ password: text(),
187
+ passwordLastChangedAt: timestamp("password_last_changed_at", { withTimezone: true, mode: "string" }),
188
+ idToken: text("id_token"),
189
+ accessToken: text("access_token"),
190
+ accessTokenExpiresAt: timestamp("access_token_expires_at", { withTimezone: true, mode: "string" }),
191
+ refreshToken: text("refresh_token"),
192
+ refreshTokenExpiresAt: timestamp("refresh_token_expires_at", { withTimezone: true, mode: "string" }),
193
+ scope: text(),
194
+ expiresAt: timestamp("expires_at", { withTimezone: true, mode: "string" }),
195
+ meta: jsonb()
196
+ }, (table) => [
197
+ index("idx_accounts_provider_lookup").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.provider.asc().nullsLast().op("text_ops"), table.providerAccountId.asc().nullsLast().op("text_ops")),
198
+ index("idx_accounts_user_id").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.userId.asc().nullsLast().op("text_ops")),
199
+ foreignKey({
200
+ columns: [table.tenantId],
201
+ foreignColumns: [tenantsInIam.id],
202
+ name: "accounts_tenant_id_fkey"
203
+ }).onUpdate("cascade").onDelete("cascade"),
204
+ foreignKey({
205
+ columns: [table.userId],
206
+ foreignColumns: [usersInIam.id],
207
+ name: "accounts_user_id_fkey"
208
+ }).onUpdate("cascade").onDelete("cascade"),
209
+ unique("accounts_tenant_provider_account_unique").on(table.tenantId, table.provider, table.providerAccountId),
210
+ pgPolicy("tenant_isolation", { as: "permissive", for: "all", to: ["public"], using: sql`((tenant_id)::text = (iam.current_tenant_id())::text)`, withCheck: sql`((tenant_id)::text = (iam.current_tenant_id())::text)` })
211
+ ]);
212
+ var usersInIam = iam.table("users", {
213
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
214
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
215
+ createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
216
+ updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
217
+ fullName: text("full_name").notNull(),
218
+ image: text(),
219
+ phone: text(),
220
+ email: text(),
221
+ handle: text().notNull(),
222
+ emailVerified: boolean("email_verified").default(false).notNull(),
223
+ phoneVerified: boolean("phone_verified").default(false).notNull(),
224
+ bannedUntil: timestamp("banned_until", { withTimezone: true, mode: "string" }),
225
+ lastSignInAt: timestamp("last_sign_in_at", { withTimezone: true, mode: "string" }),
226
+ loginAttempt: smallint("login_attempt").default(0).notNull(),
227
+ userType: text("user_type").array().default(["RAY"]).notNull()
228
+ }, (table) => [
229
+ index("idx_users_auth_lookup").using("btree", table.tenantId.asc().nullsLast().op("bool_ops"), table.email.asc().nullsLast().op("bool_ops"), table.id.asc().nullsLast().op("timestamptz_ops"), table.emailVerified.asc().nullsLast().op("timestamptz_ops"), table.bannedUntil.asc().nullsLast().op("uuid_ops")).where(sql`(email IS NOT NULL)`),
230
+ index("idx_users_email_lookup").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.email.asc().nullsLast().op("text_ops")).where(sql`(email IS NOT NULL)`),
231
+ index("idx_users_handle_lookup").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.handle.asc().nullsLast().op("text_ops")),
232
+ index("idx_users_phone_lookup").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.phone.asc().nullsLast().op("text_ops")).where(sql`(phone IS NOT NULL)`),
233
+ index("idx_users_tenant_email_unique").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.email.asc().nullsLast().op("text_ops")).where(sql`(email IS NOT NULL)`),
234
+ index("idx_users_tenant_is_admin").using("btree", table.tenantId.asc().nullsLast().op("text_ops")).where(sql`(user_type @> ARRAY['admin'::text])`),
235
+ index("idx_users_tenant_is_candidate").using("btree", table.tenantId.asc().nullsLast().op("text_ops")).where(sql`(user_type @> ARRAY['candidate'::text])`),
236
+ index("idx_users_tenant_is_employee").using("btree", table.tenantId.asc().nullsLast().op("text_ops")).where(sql`(user_type @> ARRAY['employee'::text])`),
237
+ index("idx_users_user_types_gin").using("gin", table.userType.asc().nullsLast().op("array_ops")),
238
+ uniqueIndex("users_tenant_lower_email_idx").using("btree", sql`tenant_id`, sql`lower(email)`),
239
+ uniqueIndex("users_tenant_lower_handle_idx").using("btree", sql`tenant_id`, sql`lower(handle)`),
240
+ foreignKey({
241
+ columns: [table.tenantId],
242
+ foreignColumns: [tenantsInIam.id],
243
+ name: "users_tenant_id_fkey"
244
+ }).onUpdate("cascade").onDelete("cascade"),
245
+ unique("users_tenant_phone_key").on(table.tenantId, table.phone),
246
+ pgPolicy("tenant_isolation", { as: "permissive", for: "all", to: ["public"], using: sql`((tenant_id)::text = (iam.current_tenant_id())::text)`, withCheck: sql`((tenant_id)::text = (iam.current_tenant_id())::text)` }),
247
+ check("users_login_attempt_nonnegative_check", sql`login_attempt >= 0`),
248
+ check("users_contact_required_check", sql`(email IS NOT NULL) OR (phone IS NOT NULL)`),
249
+ check("users_user_type_check", sql`user_type <@ ARRAY['candidate'::text, 'employee'::text, 'admin'::text]`)
250
+ ]);
251
+ var rolesInIam = iam.table("roles", {
252
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
253
+ createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
254
+ updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
255
+ name: jsonb().notNull(),
256
+ description: jsonb().notNull(),
257
+ code: text().notNull(),
258
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
259
+ isSystem: boolean("is_system").default(false).notNull(),
260
+ isEditable: boolean("is_editable").default(true).notNull(),
261
+ isDeletable: boolean("is_deletable").default(true).notNull()
262
+ }, (table) => [
263
+ foreignKey({
264
+ columns: [table.tenantId],
265
+ foreignColumns: [tenantsInIam.id],
266
+ name: "roles_tenant_id_fkey"
267
+ }).onUpdate("cascade").onDelete("cascade"),
268
+ unique("roles_tenant_code_unique").on(table.tenantId, table.code),
269
+ unique("roles_tenant_id_unique").on(table.tenantId, table.id),
270
+ pgPolicy("tenant_isolation", { as: "permissive", for: "all", to: ["public"], using: sql`((tenant_id)::text = (iam.current_tenant_id())::text)`, withCheck: sql`((tenant_id)::text = (iam.current_tenant_id())::text)` })
271
+ ]);
272
+ var promptInAi = ai.table("prompt", {
273
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
274
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
275
+ createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
276
+ updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
277
+ meta: jsonb(),
278
+ code: text().notNull(),
279
+ name: jsonb().notNull(),
280
+ instruction: text()
281
+ }, (table) => [
282
+ index("ix_ai_prompt_tenant_created").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.createdAt.desc().nullsFirst().op("timestamptz_ops")),
283
+ index("ix_ai_prompt_tenant_id").using("btree", table.tenantId.asc().nullsLast().op("text_ops")),
284
+ foreignKey({
285
+ columns: [table.tenantId],
286
+ foreignColumns: [tenantsInIam.id],
287
+ name: "fk_ai_prompt_tenant"
288
+ }).onUpdate("cascade").onDelete("cascade"),
289
+ unique("uq_ai_prompt_tenant_code").on(table.tenantId, table.code),
290
+ pgPolicy("tenant_isolation", { as: "permissive", for: "all", to: ["public"], using: sql`((tenant_id)::text = (iam.current_tenant_id())::text)`, withCheck: sql`((tenant_id)::text = (iam.current_tenant_id())::text)` })
291
+ ]);
292
+ var promptSystemInAi = ai.table("prompt_system", {
293
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
294
+ createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
295
+ updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
296
+ meta: jsonb(),
297
+ code: text().notNull(),
298
+ name: jsonb().notNull(),
299
+ instruction: text()
300
+ }, (table) => [
301
+ index("ix_ai_prompt_system_code").using("btree", table.code.asc().nullsLast().op("text_ops")),
302
+ unique("uq_ai_prompt_system_code").on(table.code)
303
+ ]);
304
+ var cvInAi = ai.table("cv", {
305
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
306
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
307
+ createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
308
+ updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
309
+ meta: jsonb(),
310
+ userId: uuid("user_id").notNull(),
311
+ model: text(),
312
+ content: text(),
313
+ embedding: vector({ dimensions: 1536 }),
314
+ metadata: jsonb()
315
+ }, (table) => [
316
+ index("ix_ai_cv_embedding_cosine").using("hnsw", table.embedding.asc().nullsLast().op("vector_cosine_ops")).with({ m: "16", ef_construction: "64" }),
317
+ index("ix_ai_cv_tenant_created").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.createdAt.desc().nullsFirst().op("timestamptz_ops")),
318
+ index("ix_ai_cv_tenant_id").using("btree", table.tenantId.asc().nullsLast().op("text_ops")),
319
+ index("ix_ai_cv_tenant_user").using("btree", table.tenantId.asc().nullsLast().op("uuid_ops"), table.userId.asc().nullsLast().op("uuid_ops")),
320
+ foreignKey({
321
+ columns: [table.tenantId],
322
+ foreignColumns: [tenantsInIam.id],
323
+ name: "fk_ai_cv_tenant"
324
+ }).onUpdate("cascade").onDelete("cascade"),
325
+ foreignKey({
326
+ columns: [table.userId],
327
+ foreignColumns: [usersInIam.id],
328
+ name: "fk_ai_cv_user"
329
+ }).onUpdate("cascade").onDelete("cascade"),
330
+ pgPolicy("tenant_isolation", { as: "permissive", for: "all", to: ["public"], using: sql`((tenant_id)::text = (iam.current_tenant_id())::text)`, withCheck: sql`((tenant_id)::text = (iam.current_tenant_id())::text)` })
331
+ ]);
332
+ var userRolesInIam = iam.table("user_roles", {
333
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
334
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
335
+ userId: uuid("user_id").notNull(),
336
+ roleId: uuid("role_id").notNull()
337
+ }, (table) => [
338
+ index("idx_user_roles_tenant_user").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.userId.asc().nullsLast().op("uuid_ops")),
339
+ foreignKey({
340
+ columns: [table.tenantId],
341
+ foreignColumns: [tenantsInIam.id],
342
+ name: "user_roles_tenant_id_fkey"
343
+ }).onUpdate("cascade").onDelete("cascade"),
344
+ foreignKey({
345
+ columns: [table.userId],
346
+ foreignColumns: [usersInIam.id],
347
+ name: "user_roles_user_id_fkey"
348
+ }).onUpdate("cascade").onDelete("cascade"),
349
+ foreignKey({
350
+ columns: [table.tenantId, table.roleId],
351
+ foreignColumns: [rolesInIam.tenantId, rolesInIam.id],
352
+ name: "user_roles_tenant_role_fkey"
353
+ }).onDelete("cascade"),
354
+ unique("user_roles_tenant_user_role_unique").on(table.tenantId, table.userId, table.roleId),
355
+ pgPolicy("tenant_isolation", { as: "permissive", for: "all", to: ["public"], using: sql`((tenant_id)::text = (iam.current_tenant_id())::text)`, withCheck: sql`((tenant_id)::text = (iam.current_tenant_id())::text)` })
356
+ ]);
357
+ var domainsInIam = iam.table("domains", {
358
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
359
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
360
+ domain: text().notNull(),
361
+ status: text().default("pending").notNull(),
362
+ meta: jsonb(),
363
+ isPrimary: boolean("is_primary").default(false).notNull(),
364
+ createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
365
+ updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
366
+ app: text()
367
+ }, (table) => [
368
+ uniqueIndex("domains_domain_unique_idx").using("btree", sql`lower(domain)`),
369
+ uniqueIndex("domains_primary_per_tenant_idx").using("btree", table.tenantId.asc().nullsLast().op("text_ops")).where(sql`(is_primary = true)`),
370
+ index("domains_tenant_status_idx").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.status.asc().nullsLast().op("text_ops")),
371
+ index("idx_domains_tenant_domain_status").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.domain.asc().nullsLast().op("text_ops"), table.status.asc().nullsLast().op("text_ops")),
372
+ foreignKey({
373
+ columns: [table.tenantId],
374
+ foreignColumns: [tenantsInIam.id],
375
+ name: "domains_tenant_id_fkey"
376
+ }).onUpdate("cascade").onDelete("cascade"),
377
+ pgPolicy("tenant_isolation", { as: "permissive", for: "all", to: ["public"], using: sql`((tenant_id)::text = (iam.current_tenant_id())::text)`, withCheck: sql`((tenant_id)::text = (iam.current_tenant_id())::text)` }),
378
+ check("domains_domain_format_check", sql`domain ~ '^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)+$'::text`),
379
+ check("domains_status_check", sql`status = ANY (ARRAY['PENDING'::text, 'ACTIVE'::text, 'DISABLED'::text, 'DELETED'::text])`)
380
+ ]);
381
+
382
+ // src/db/index.ts
383
+ var schemaConfig = { schema: { ...schema_exports } };
384
+ var createDatabase = (connectionString) => {
385
+ const pool = new Pool({ connectionString });
386
+ return drizzle({ client: pool, ...schemaConfig });
387
+ };
388
+
389
+ // src/handler.ts
390
+ import { OpenAPIHono as OpenAPIHono3 } from "@hono/zod-openapi";
391
+
392
+ // src/routes/index.ts
393
+ import { OpenAPIHono as OpenAPIHono2 } from "@hono/zod-openapi";
394
+
395
+ // src/routes/ai-form/ai-form.route.ts
396
+ import { createRoute, OpenAPIHono } from "@hono/zod-openapi";
397
+
398
+ // src/routes/ai.schema.ts
399
+ import { z } from "zod";
400
+ var messageSchema = z.object({
401
+ message: z.string()
402
+ });
403
+ var aiErrorResponseSchema = z.object({
404
+ ok: z.literal(false),
405
+ error: z.object({
406
+ code: z.enum([
407
+ "INVALID_JSON",
408
+ "INVALID_REQUEST",
409
+ "INVALID_SCHEMA",
410
+ "INSUFFICIENT_INPUT",
411
+ "MODEL_NOT_CONFIGURED",
412
+ "GENERATION_FAILED"
413
+ ]),
414
+ message: z.string()
415
+ })
416
+ });
417
+ var jsonSchemaObjectSchema = z.object({
418
+ type: z.literal("object")
419
+ }).passthrough();
420
+ var aiFormRequestSchema = z.object({
421
+ prompt: z.string().min(1),
422
+ code: z.string().min(1),
423
+ /** App-supplied facts (user, locale, prior state); optional. */
424
+ context: z.string().max(1e5).optional(),
425
+ count: z.number().int().min(1).max(100).optional(),
426
+ schema: jsonSchemaObjectSchema
427
+ });
428
+ var aiFormRequestOpenApiSchema = z.object({
429
+ prompt: z.string().min(1),
430
+ code: z.string().min(1),
431
+ context: z.string().max(1e5).optional(),
432
+ count: z.number().int().min(1).max(100).optional(),
433
+ schema: z.object({
434
+ type: z.literal("object"),
435
+ description: z.string().optional(),
436
+ additionalProperties: z.boolean().optional()
437
+ }).passthrough()
438
+ });
439
+ var aiFormSuccessSchema = z.object({
440
+ ok: z.literal(true),
441
+ data: z.unknown()
442
+ });
443
+ var aiFormResponseSchema = z.union([
444
+ aiFormSuccessSchema,
445
+ aiErrorResponseSchema
446
+ ]);
447
+
448
+ // src/routes/ai-form/handler/ai-form.ts
449
+ import { generateText, jsonSchema, Output } from "ai";
450
+
451
+ // src/utils/detect-refusal-in-data.ts
452
+ var REFUSAL_PHRASES = [
453
+ "does not provide enough information",
454
+ "does not contain enough",
455
+ "insufficient data",
456
+ "insufficient information",
457
+ "nonsensical",
458
+ "not enough information",
459
+ "placeholder",
460
+ "unable to extract",
461
+ "no meaningful",
462
+ "no profile information"
463
+ ];
464
+ var PLACEHOLDER_EXACT = /* @__PURE__ */ new Set([
465
+ "unknown",
466
+ "n/a",
467
+ "na",
468
+ "tbd",
469
+ "none",
470
+ "null",
471
+ "undefined",
472
+ "not applicable",
473
+ "not available",
474
+ "unspecified"
475
+ ]);
476
+ function stringLooksLikeRefusalOrPlaceholder(s) {
477
+ const low = s.toLowerCase();
478
+ if (REFUSAL_PHRASES.some((p) => low.includes(p))) {
479
+ return true;
480
+ }
481
+ const t = s.trim().toLowerCase();
482
+ return PLACEHOLDER_EXACT.has(t);
483
+ }
484
+ function refusalPhrasesInValues(data) {
485
+ const walk = (v) => {
486
+ if (typeof v === "string") {
487
+ return stringLooksLikeRefusalOrPlaceholder(v);
488
+ }
489
+ if (Array.isArray(v)) {
490
+ return v.some(walk);
491
+ }
492
+ if (v && typeof v === "object") {
493
+ return Object.values(v).some(walk);
494
+ }
495
+ return false;
496
+ };
497
+ return walk(data);
498
+ }
499
+
500
+ // src/utils/openai-model.ts
501
+ import { createOpenAI } from "@ai-sdk/openai";
502
+ var DEFAULT_OPENAI_MODEL = "gpt-5.4-mini";
503
+ function resolveOpenAiModel(args) {
504
+ const apiKey = args.apiKey?.trim();
505
+ if (!apiKey) {
506
+ return {
507
+ success: false,
508
+ message: "OpenAI API key is not configured."
509
+ };
510
+ }
511
+ const modelId = args.model?.trim() || DEFAULT_OPENAI_MODEL;
512
+ const openai = createOpenAI({ apiKey });
513
+ return {
514
+ success: true,
515
+ model: openai(modelId)
516
+ };
517
+ }
518
+
519
+ // src/utils/status-code.ts
520
+ var HTTP_STATUS_CODE = {
521
+ OK: 200,
522
+ BAD_REQUEST: 400,
523
+ UNPROCESSABLE_ENTITY: 422,
524
+ INTERNAL_SERVER_ERROR: 500
525
+ };
526
+
527
+ // src/utils/wrap-ai-form-output-schema.ts
528
+ function wrapAiFormOutputSchema(userSchema, count) {
529
+ const { $schema: _schema, $id: _id, ...itemSchema } = userSchema;
530
+ const dataSchema = typeof count === "number" ? {
531
+ type: "array",
532
+ minItems: count,
533
+ maxItems: count,
534
+ items: itemSchema
535
+ } : itemSchema;
536
+ return {
537
+ type: "object",
538
+ additionalProperties: false,
539
+ properties: {
540
+ ok: { type: "boolean" },
541
+ data: {
542
+ anyOf: [dataSchema, { type: "null" }]
543
+ },
544
+ reason: { type: "string" }
545
+ },
546
+ required: ["ok", "data", "reason"]
547
+ };
548
+ }
549
+
550
+ // src/db/orm/prompt.ts
551
+ import { eq } from "drizzle-orm";
552
+ var fetchSystemPromptByCode = async ({
553
+ database,
554
+ code
555
+ }) => {
556
+ const [prompt] = await database.select({
557
+ id: promptSystemInAi.id,
558
+ code: promptSystemInAi.code,
559
+ instruction: promptSystemInAi.instruction
560
+ }).from(promptSystemInAi).where(eq(promptSystemInAi.code, code)).limit(1);
561
+ return prompt || null;
562
+ };
563
+
564
+ // src/routes/ai-form/handler/load-system-prompt.ts
565
+ async function loadSystemPrompt(args) {
566
+ if (!args.database) {
567
+ return {
568
+ systemPrompt: ""
569
+ };
570
+ }
571
+ try {
572
+ const prompt = await fetchSystemPromptByCode({
573
+ database: args.database,
574
+ code: args.code
575
+ });
576
+ return {
577
+ systemPrompt: prompt?.instruction?.trim() || ""
578
+ };
579
+ } catch (_error) {
580
+ return {
581
+ systemPrompt: ""
582
+ };
583
+ }
584
+ }
585
+
586
+ // src/routes/ai-form/handler/ai-form.ts
587
+ function isWrappedFailure(out) {
588
+ return typeof out === "object" && out !== null && out.ok === false && typeof out.reason === "string";
589
+ }
590
+ function isWrappedSuccess(out) {
591
+ return typeof out === "object" && out !== null && out.ok === true && "data" in out;
592
+ }
593
+ var aiFormHandler = async (c) => {
594
+ let requestBody;
595
+ try {
596
+ requestBody = await c.req.json();
597
+ } catch {
598
+ return c.json(
599
+ {
600
+ ok: false,
601
+ error: {
602
+ code: "INVALID_JSON",
603
+ message: "Invalid JSON body."
604
+ }
605
+ },
606
+ HTTP_STATUS_CODE.BAD_REQUEST
607
+ );
608
+ }
609
+ const parsedBody = aiFormRequestSchema.safeParse(requestBody);
610
+ if (!parsedBody.success) {
611
+ return c.json(
612
+ {
613
+ ok: false,
614
+ error: {
615
+ code: "INVALID_REQUEST",
616
+ message: "prompt, code, and schema are required."
617
+ }
618
+ },
619
+ HTTP_STATUS_CODE.BAD_REQUEST
620
+ );
621
+ }
622
+ const body = parsedBody.data;
623
+ const config = c.get("config");
624
+ const database = c.get("database");
625
+ const modelResult = resolveOpenAiModel({
626
+ apiKey: config.apiKey,
627
+ model: config.model ?? DEFAULT_OPENAI_MODEL
628
+ });
629
+ if (!modelResult.success) {
630
+ return c.json(
631
+ {
632
+ ok: false,
633
+ error: {
634
+ code: "MODEL_NOT_CONFIGURED",
635
+ message: modelResult.message
636
+ }
637
+ },
638
+ HTTP_STATUS_CODE.INTERNAL_SERVER_ERROR
639
+ );
640
+ }
641
+ const outputSchema = jsonSchema(
642
+ wrapAiFormOutputSchema(body.schema, body.count)
643
+ );
644
+ const { systemPrompt } = await loadSystemPrompt({
645
+ database,
646
+ code: body.code
647
+ });
648
+ const ctx = body.context?.trim();
649
+ const prompt = [
650
+ systemPrompt ? `<SystemPrompt>
651
+ ${systemPrompt}
652
+ </SystemPrompt>` : void 0,
653
+ ctx ? `<Context>
654
+ ${ctx}
655
+ </Context>` : void 0,
656
+ "User request:<UserRequest>",
657
+ body.prompt.trim(),
658
+ "</UserRequest>",
659
+ [
660
+ "Respond with a JSON object matching the wrapper schema.",
661
+ "Always include ok, data, and reason keys.",
662
+ "If ok is true, set reason to empty string and set data to the generated value.",
663
+ typeof body.count === "number" ? `Generate exactly ${body.count} items in data array.` : void 0,
664
+ "If the user or context or system prompt text does not contain enough concrete facts to fill required fields with real values taken from their words, set ok to false, explain in reason, and set data to null.",
665
+ "Never use Unknown, N/A, placeholder strings, or fabricated values to satisfy the inner data schema."
666
+ ].join(" ")
667
+ ].filter(Boolean).join("\n\n");
668
+ try {
669
+ const result = await generateText({
670
+ model: modelResult.model,
671
+ prompt,
672
+ output: Output.object({
673
+ schema: outputSchema
674
+ })
675
+ });
676
+ const out = result.output;
677
+ if (isWrappedFailure(out)) {
678
+ return c.json(
679
+ {
680
+ ok: false,
681
+ error: {
682
+ code: "INSUFFICIENT_INPUT",
683
+ message: out.reason
684
+ }
685
+ },
686
+ HTTP_STATUS_CODE.UNPROCESSABLE_ENTITY
687
+ );
688
+ }
689
+ if (!isWrappedSuccess(out)) {
690
+ return c.json(
691
+ {
692
+ ok: false,
693
+ error: {
694
+ code: "GENERATION_FAILED",
695
+ message: "Unexpected model output shape."
696
+ }
697
+ },
698
+ HTTP_STATUS_CODE.INTERNAL_SERVER_ERROR
699
+ );
700
+ }
701
+ if (refusalPhrasesInValues(out.data)) {
702
+ return c.json(
703
+ {
704
+ ok: false,
705
+ error: {
706
+ code: "INSUFFICIENT_INPUT",
707
+ message: "Model output describes missing or nonsensical input instead of extracted data."
708
+ }
709
+ },
710
+ HTTP_STATUS_CODE.UNPROCESSABLE_ENTITY
711
+ );
712
+ }
713
+ return c.json(
714
+ {
715
+ ok: true,
716
+ data: out.data
717
+ },
718
+ HTTP_STATUS_CODE.OK
719
+ );
720
+ } catch (error) {
721
+ return c.json(
722
+ {
723
+ ok: false,
724
+ error: {
725
+ code: "GENERATION_FAILED",
726
+ message: error instanceof Error ? error.message : "Object generation failed."
727
+ }
728
+ },
729
+ HTTP_STATUS_CODE.INTERNAL_SERVER_ERROR
730
+ );
731
+ }
732
+ };
733
+
734
+ // src/routes/ai-form/ai-form.route.ts
735
+ var aiFormRoute = createRoute({
736
+ method: "post",
737
+ path: "/ai-form",
738
+ tags: ["AI"],
739
+ summary: "Generate structured object from prompt and JSON schema",
740
+ request: {
741
+ body: {
742
+ content: {
743
+ "application/json": {
744
+ schema: aiFormRequestOpenApiSchema
745
+ }
746
+ }
747
+ }
748
+ },
749
+ responses: {
750
+ 200: {
751
+ description: "Generated object",
752
+ content: {
753
+ "application/json": {
754
+ schema: aiFormResponseSchema
755
+ }
756
+ }
757
+ },
758
+ 400: {
759
+ description: "Invalid request",
760
+ content: {
761
+ "application/json": {
762
+ schema: aiErrorResponseSchema
763
+ }
764
+ }
765
+ },
766
+ 422: {
767
+ description: "Prompt does not yield extractable structured data",
768
+ content: {
769
+ "application/json": {
770
+ schema: aiErrorResponseSchema
771
+ }
772
+ }
773
+ },
774
+ 500: {
775
+ description: "Generation failed",
776
+ content: {
777
+ "application/json": {
778
+ schema: aiErrorResponseSchema
779
+ }
780
+ }
781
+ }
782
+ }
783
+ });
784
+ var aiFormRoutes = new OpenAPIHono().openapi(
785
+ aiFormRoute,
786
+ aiFormHandler
787
+ );
788
+ var ai_form_route_default = aiFormRoutes;
789
+
790
+ // src/routes/index.ts
791
+ var routes = new OpenAPIHono2().route("/", ai_form_route_default);
792
+ var routes_default = routes;
793
+
794
+ // src/handler.ts
795
+ var createAiMiddleware = ({ config, database }) => {
796
+ return async (c, next) => {
797
+ c.set("config", config);
798
+ c.set("database", database);
799
+ await next();
800
+ };
801
+ };
802
+ var createAiRoutes = (opts) => {
803
+ const app = new OpenAPIHono3();
804
+ app.use("*", createAiMiddleware(opts));
805
+ app.route("/", routes_default);
806
+ app.doc("/openapi.json", {
807
+ openapi: "3.0.0",
808
+ info: {
809
+ title: "Mesob AI API",
810
+ version: "0.4.7"
811
+ }
812
+ });
813
+ return app;
814
+ };
815
+
816
+ // src/index.ts
817
+ var defaultConfig = {
818
+ model: "gpt-5.4-mini",
819
+ basePath: "/api/ai"
820
+ };
821
+ var createMesobAi = (config = {}) => {
822
+ const merged = { ...defaultConfig, ...config };
823
+ const database = merged.connectionString ? createDatabase(merged.connectionString) : void 0;
824
+ const routes2 = createAiRoutes({
825
+ config: merged,
826
+ database
827
+ });
828
+ return { routes: routes2, database };
829
+ };
830
+ export {
831
+ createDatabase,
832
+ createMesobAi
833
+ };
834
+ //# sourceMappingURL=index.js.map