@murumets-ee/auth 0.1.5 → 0.3.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.
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.mts","names":[],"sources":["../src/client.ts"],"mappings":"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"}
1
+ {"version":3,"file":"client.d.mts","names":[],"sources":["../src/client.ts"],"mappings":";;;;UAWiB,iBAAA;EAgCD;EA9Bd,OAAA;;EAEA,aAAA;AAAA;;;;;;;;;;;;AAsCD;;;;;;;;;;;AAYkB;;;iBAtBH,YAAA,CACd,OAAA,GAAU,iBAAA,GACT,UAAA,QAAkB,gBAAA,IAAoB,mBAAA;;;;;UAkB/B,kBAAA;EACR,IAAA,GAAO,CAAA;EACP,KAAA;IAAU,OAAA;EAAA;AAAA;;;;;;;;;;UAYF,mBAAA;EACR,KAAA;IACE,SAAA,GAAY,IAAA;MACV,KAAA;QACE,KAAA;QACA,MAAA;QACA,MAAA;QACA,aAAA;QACA,WAAA;QACA,WAAA;QACA,cAAA;MAAA;IAAA,MAEE,OAAA,CAAQ,kBAAA;MAAqB,KAAA,EAAO,SAAA;MAAa,KAAA;IAAA;IACvD,UAAA,GAAa,IAAA;MACX,IAAA;MACA,KAAA;MACA,QAAA;MACA,IAAA;IAAA,MACI,OAAA,CAAQ,kBAAA;IACd,UAAA,GAAa,IAAA;MACX,MAAA;MACA,IAAA,EAAM,MAAA;IAAA,MACF,OAAA,CAAQ,kBAAA;IACd,UAAA,GAAa,IAAA;MAAQ,MAAA;IAAA,MAAqB,OAAA,CAAQ,kBAAA;IAClD,OAAA,GAAU,IAAA;MAAQ,MAAA;MAAgB,IAAA;IAAA,MAAmB,OAAA,CAAQ,kBAAA;IAC7D,OAAA,GAAU,IAAA;MACR,MAAA;MACA,SAAA;MACA,YAAA;IAAA,MACI,OAAA,CAAQ,kBAAA;IACd,SAAA,GAAY,IAAA;MAAQ,MAAA;IAAA,MAAqB,OAAA,CAAQ,kBAAA;IACjD,kBAAA,GAAqB,IAAA;MAAQ,MAAA;IAAA,MAAqB,OAAA,CAAQ,kBAAA;EAAA;AAAA;AAAA,UAIpD,cAAA;EACR,KAAA;EACA,MAAA;EACA,MAAA;EACA,aAAA;EACA,WAAA;EACA,WAAA;EACA,cAAA;AAAA;AAAA,UAGQ,cAAA;EACR,EAAA;EACA,IAAA;EACA,KAAA;EACA,aAAA;EACA,KAAA;EACA,SAAA;EACA,SAAA;EACA,IAAA;EACA,MAAA;EACA,SAAA;EACA,UAAA;AAAA;;;;;;;AAdc;;;;;;;;;;iBAiCA,cAAA,CAAe,MAAA,EAAQ,mBAAA;cAYjB,cAAA,GAAiB,OAAA;IAAU,KAAA,EAAO,cAAA;IAAkB,KAAA;EAAA;;IAsBjD,IAAA;IAAc,KAAA;IAAe,QAAA;IAAkB,IAAA;EAAA,IAAc,OAAA;yBAUvD,IAAA;IAAU,IAAA;EAAA,IAAe,OAAA;0BAKzB,OAAA;0BAKC,IAAA,WAAc,OAAA;sBAKlB,OAAA;IAAc,MAAA;IAAiB,SAAA;EAAA,IAAoB,OAAA;yBASjD,OAAA;kCAKS,OAAA;AAAA"}
package/dist/client.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{adminClient as e,organizationClient as t}from"better-auth/client/plugins";import{createAuthClient as n}from"better-auth/react";function r(r){return n({baseURL:r?.baseURL,plugins:[e(),...r?.organizations?[t()]:[]]})}function i(e){function t(e){if(e.error)throw Error(e.error.message??`Unknown error`);return e.data}return{async list(n){let r=t(await e.admin.listUsers({query:{limit:n.limit,offset:n.offset,...n.sortBy?{sortBy:n.sortBy,sortDirection:n.sortDirection}:{},...n.searchValue?{searchValue:n.searchValue,searchField:n.searchField??`email`,searchOperator:n.searchOperator??`contains`}:{}}}));return{users:(r.users??[]).map(a),total:r.total??0}},async create(n){t(await e.admin.createUser({name:n.name,email:n.email,password:n.password,role:n.role}))},async update(n,r){t(await e.admin.updateUser({userId:n,data:r}))},async remove(n){t(await e.admin.removeUser({userId:n}))},async setRole(n,r){t(await e.admin.setRole({userId:n,role:r}))},async ban(n,r){t(await e.admin.banUser({userId:n,...r?.reason?{banReason:r.reason}:{},...r?.expiresIn?{banExpiresIn:r.expiresIn}:{}}))},async unban(n){t(await e.admin.unbanUser({userId:n}))},async revokeSessions(n){t(await e.admin.revokeUserSessions({userId:n}))}}}function a(e){let t=e;return{id:t.id,name:t.name??``,email:t.email,emailVerified:t.emailVerified??!1,image:t.image??null,createdAt:String(t.createdAt??``),updatedAt:String(t.updatedAt??``),role:t.role??null,banned:t.banned??null,banReason:t.banReason??null,banExpires:t.banExpires?String(t.banExpires):null}}export{r as createClient,i as createUsersApi};
1
+ import{adminClient as e,organizationClient as t}from"better-auth/client/plugins";import{createAuthClient as n}from"better-auth/react";function r(r){return n({baseURL:r?.baseURL,plugins:[e(),...r?.organizations?[t()]:[]]})}function i(e){function t(e){if(e.error)throw Error(e.error.message??`Unknown error`);return e.data}return{async list(n){let r=t(await e.admin.listUsers({query:{limit:n.limit,offset:n.offset,...n.sortBy?{sortBy:n.sortBy,sortDirection:n.sortDirection}:{},...n.searchValue?{searchValue:n.searchValue,searchField:n.searchField??`email`,searchOperator:n.searchOperator??`contains`}:{}}}));return{users:(r.users??[]).map(a),total:r.total??0}},async create(n){t(await e.admin.createUser({name:n.name,email:n.email,password:n.password,role:n.role}))},async update(n,r){t(await e.admin.updateUser({userId:n,data:r}))},async remove(n){t(await e.admin.removeUser({userId:n}))},async setRole(n,r){t(await e.admin.setRole({userId:n,role:r}))},async ban(n,r){t(await e.admin.banUser({userId:n,...r?.reason?{banReason:r.reason}:{},...r?.expiresIn?{banExpiresIn:r.expiresIn}:{}}))},async unban(n){t(await e.admin.unbanUser({userId:n}))},async revokeSessions(n){t(await e.admin.revokeUserSessions({userId:n}))}}}function a(e){return{id:e.id,name:e.name??``,email:e.email,emailVerified:e.emailVerified??!1,image:e.image??null,createdAt:e.createdAt instanceof Date?e.createdAt.toISOString():String(e.createdAt??``),updatedAt:e.updatedAt instanceof Date?e.updatedAt.toISOString():String(e.updatedAt??``),role:e.role??null,banned:e.banned??null,banReason:e.banReason??null,banExpires:e.banExpires instanceof Date?e.banExpires.toISOString():e.banExpires?String(e.banExpires):null}}export{r as createClient,i as createUsersApi};
2
2
  //# sourceMappingURL=client.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["/**\n * Browser-safe auth client factory.\n *\n * This module has NO server-only imports and is safe to use in React components.\n * Import from '@murumets-ee/auth/client'.\n */\n\nimport { adminClient, organizationClient } from 'better-auth/client/plugins'\nimport { createAuthClient } from 'better-auth/react'\n\nexport interface AuthClientOptions {\n /** Base URL of the auth API. Defaults to window.location.origin. */\n baseURL?: string\n /** Enable organization client plugin (must match server config) */\n organizations?: boolean\n}\n\n/**\n * Create a typed auth client for use in React components.\n *\n * @example\n * ```typescript\n * // lib/auth-client.ts\n * import { createClient } from '@murumets-ee/auth/client'\n *\n * export const authClient = createClient()\n *\n * // In a component:\n * const { data: session } = authClient.useSession()\n * ```\n */\nexport function createClient(options?: AuthClientOptions) {\n return createAuthClient({\n baseURL: options?.baseURL,\n plugins: [adminClient(), ...(options?.organizations ? [organizationClient()] : [])],\n })\n}\n\n// ---------------------------------------------------------------------------\n// UsersApi adapter — bridges better-auth admin client → @murumets-ee/admin-ui/users\n// ---------------------------------------------------------------------------\n\ntype AuthClient = ReturnType<typeof createClient>\n\ninterface UsersListQuery {\n limit: number\n offset: number\n sortBy?: string\n sortDirection?: 'asc' | 'desc'\n searchValue?: string\n searchField?: 'email' | 'name'\n searchOperator?: 'contains' | 'starts_with' | 'ends_with'\n}\n\ninterface NormalizedUser {\n id: string\n name: string\n email: string\n emailVerified: boolean\n image: string | null\n createdAt: string\n updatedAt: string\n role: string | null\n banned: boolean | null\n banReason: string | null\n banExpires: string | null\n}\n\n/**\n * Create a users API adapter from a better-auth client that includes the\n * `adminClient()` plugin. The returned object is structurally compatible\n * with the `UsersApi` interface from `@murumets-ee/admin-ui/users`.\n *\n * @example\n * ```tsx\n * import { createClient, createUsersApi } from '@murumets-ee/auth/client'\n * import { UsersManagement } from '@murumets-ee/admin-ui/users'\n *\n * const authClient = createClient()\n * const usersApi = createUsersApi(authClient)\n *\n * <UsersManagement api={usersApi} currentUserId={session.user.id} />\n * ```\n */\nexport function createUsersApi(client: AuthClient) {\n /**\n * Unwrap better-auth's discriminated union response.\n * On error branch, `error` exists with optional `message`.\n * On success branch, `data` exists.\n */\n function unwrap<T>(res: { data?: T; error?: { message?: string } }): T {\n if (res.error) throw new Error(res.error.message ?? 'Unknown error')\n return res.data as T\n }\n\n return {\n async list(query: UsersListQuery): Promise<{ users: NormalizedUser[]; total: number }> {\n const res = await client.admin.listUsers({\n query: {\n limit: query.limit,\n offset: query.offset,\n ...(query.sortBy ? { sortBy: query.sortBy, sortDirection: query.sortDirection } : {}),\n ...(query.searchValue\n ? {\n searchValue: query.searchValue,\n searchField: query.searchField ?? 'email',\n searchOperator: query.searchOperator ?? 'contains',\n }\n : {}),\n },\n })\n // biome-ignore lint/suspicious/noExplicitAny: better-auth's union return type doesn't match a simple generic pattern\n const data = unwrap<{ users: unknown[]; total: number }>(res as any)\n return {\n users: (data.users ?? []).map(mapUser),\n total: data.total ?? 0,\n }\n },\n\n async create(data: { name: string; email: string; password: string; role: string }) {\n const res = await client.admin.createUser({\n name: data.name,\n email: data.email,\n password: data.password,\n // better-auth admin plugin types role as a narrow union; cast is safe\n // because server validates the role against configured access control\n role: data.role as 'admin',\n })\n // biome-ignore lint/suspicious/noExplicitAny: better-auth union return type\n unwrap(res as any)\n },\n\n async update(userId: string, data: { name?: string }) {\n const res = await client.admin.updateUser({ userId, data })\n // biome-ignore lint/suspicious/noExplicitAny: better-auth union return type\n unwrap(res as any)\n },\n\n async remove(userId: string) {\n const res = await client.admin.removeUser({ userId })\n // biome-ignore lint/suspicious/noExplicitAny: better-auth union return type\n unwrap(res as any)\n },\n\n async setRole(userId: string, role: string) {\n const res = await client.admin.setRole({\n userId,\n role: role as 'admin',\n })\n // biome-ignore lint/suspicious/noExplicitAny: better-auth union return type\n unwrap(res as any)\n },\n\n async ban(userId: string, options?: { reason?: string; expiresIn?: number }) {\n const res = await client.admin.banUser({\n userId,\n ...(options?.reason ? { banReason: options.reason } : {}),\n ...(options?.expiresIn ? { banExpiresIn: options.expiresIn } : {}),\n })\n // biome-ignore lint/suspicious/noExplicitAny: better-auth union return type\n unwrap(res as any)\n },\n\n async unban(userId: string) {\n const res = await client.admin.unbanUser({ userId })\n // biome-ignore lint/suspicious/noExplicitAny: better-auth union return type\n unwrap(res as any)\n },\n\n async revokeSessions(userId: string) {\n const res = await client.admin.revokeUserSessions({ userId })\n // biome-ignore lint/suspicious/noExplicitAny: better-auth union return type\n unwrap(res as any)\n },\n }\n}\n\n/** Normalize a better-auth user record to a consistent shape. */\nfunction mapUser(u: unknown): NormalizedUser {\n const r = u as Record<string, unknown>\n return {\n id: r.id as string,\n name: (r.name as string) ?? '',\n email: r.email as string,\n emailVerified: (r.emailVerified as boolean) ?? false,\n image: (r.image as string) ?? null,\n createdAt: String(r.createdAt ?? ''),\n updatedAt: String(r.updatedAt ?? ''),\n role: (r.role as string) ?? null,\n banned: (r.banned as boolean) ?? null,\n banReason: (r.banReason as string) ?? null,\n banExpires: r.banExpires ? String(r.banExpires) : null,\n }\n}\n"],"mappings":"sIA+BA,SAAgB,EAAa,EAA6B,CACxD,OAAO,EAAiB,CACtB,QAAS,GAAS,QAClB,QAAS,CAAC,GAAa,CAAE,GAAI,GAAS,cAAgB,CAAC,GAAoB,CAAC,CAAG,EAAE,CAAE,CACpF,CAAC,CAiDJ,SAAgB,EAAe,EAAoB,CAMjD,SAAS,EAAU,EAAoD,CACrE,GAAI,EAAI,MAAO,MAAU,MAAM,EAAI,MAAM,SAAW,gBAAgB,CACpE,OAAO,EAAI,KAGb,MAAO,CACL,MAAM,KAAK,EAA4E,CAgBrF,IAAM,EAAO,EAfD,MAAM,EAAO,MAAM,UAAU,CACvC,MAAO,CACL,MAAO,EAAM,MACb,OAAQ,EAAM,OACd,GAAI,EAAM,OAAS,CAAE,OAAQ,EAAM,OAAQ,cAAe,EAAM,cAAe,CAAG,EAAE,CACpF,GAAI,EAAM,YACN,CACE,YAAa,EAAM,YACnB,YAAa,EAAM,aAAe,QAClC,eAAgB,EAAM,gBAAkB,WACzC,CACD,EAAE,CACP,CACF,CAAC,CAEkE,CACpE,MAAO,CACL,OAAQ,EAAK,OAAS,EAAE,EAAE,IAAI,EAAQ,CACtC,MAAO,EAAK,OAAS,EACtB,EAGH,MAAM,OAAO,EAAuE,CAUlF,EATY,MAAM,EAAO,MAAM,WAAW,CACxC,KAAM,EAAK,KACX,MAAO,EAAK,MACZ,SAAU,EAAK,SAGf,KAAM,EAAK,KACZ,CAAC,CAEgB,EAGpB,MAAM,OAAO,EAAgB,EAAyB,CAGpD,EAFY,MAAM,EAAO,MAAM,WAAW,CAAE,SAAQ,OAAM,CAAC,CAEzC,EAGpB,MAAM,OAAO,EAAgB,CAG3B,EAFY,MAAM,EAAO,MAAM,WAAW,CAAE,SAAQ,CAAC,CAEnC,EAGpB,MAAM,QAAQ,EAAgB,EAAc,CAM1C,EALY,MAAM,EAAO,MAAM,QAAQ,CACrC,SACM,OACP,CAAC,CAEgB,EAGpB,MAAM,IAAI,EAAgB,EAAmD,CAO3E,EANY,MAAM,EAAO,MAAM,QAAQ,CACrC,SACA,GAAI,GAAS,OAAS,CAAE,UAAW,EAAQ,OAAQ,CAAG,EAAE,CACxD,GAAI,GAAS,UAAY,CAAE,aAAc,EAAQ,UAAW,CAAG,EAAE,CAClE,CAAC,CAEgB,EAGpB,MAAM,MAAM,EAAgB,CAG1B,EAFY,MAAM,EAAO,MAAM,UAAU,CAAE,SAAQ,CAAC,CAElC,EAGpB,MAAM,eAAe,EAAgB,CAGnC,EAFY,MAAM,EAAO,MAAM,mBAAmB,CAAE,SAAQ,CAAC,CAE3C,EAErB,CAIH,SAAS,EAAQ,EAA4B,CAC3C,IAAM,EAAI,EACV,MAAO,CACL,GAAI,EAAE,GACN,KAAO,EAAE,MAAmB,GAC5B,MAAO,EAAE,MACT,cAAgB,EAAE,eAA6B,GAC/C,MAAQ,EAAE,OAAoB,KAC9B,UAAW,OAAO,EAAE,WAAa,GAAG,CACpC,UAAW,OAAO,EAAE,WAAa,GAAG,CACpC,KAAO,EAAE,MAAmB,KAC5B,OAAS,EAAE,QAAsB,KACjC,UAAY,EAAE,WAAwB,KACtC,WAAY,EAAE,WAAa,OAAO,EAAE,WAAW,CAAG,KACnD"}
1
+ {"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["/**\n * Browser-safe auth client factory.\n *\n * This module has NO server-only imports and is safe to use in React components.\n * Import from '@murumets-ee/auth/client'.\n */\n\nimport { adminClient, organizationClient } from 'better-auth/client/plugins'\nimport { createAuthClient } from 'better-auth/react'\nimport type { AdminUser } from './types.js'\n\nexport interface AuthClientOptions {\n /** Base URL of the auth API. Defaults to window.location.origin. */\n baseURL?: string\n /** Enable organization client plugin (must match server config) */\n organizations?: boolean\n}\n\n/**\n * Create a typed auth client for use in React components.\n *\n * The explicit `ReturnType<typeof createAuthClient>` annotation is required\n * because better-auth 1.6's client return type references internal modules\n * (`client/path-to-object`, `client/query`) and zod@4 types that tsdown's\n * dts generator cannot name portably across pnpm's symlink paths (TS2742).\n *\n * Widening to the base `createAuthClient` return type loses the plugin-\n * specific client fields (`client.admin.listUsers`, etc.) at type level.\n * Consumers who need typed access to admin endpoints should use\n * `createUsersApi(client)` which bridges to a stable `UsersApi` interface\n * the `@murumets-ee/admin-ui/users` package expects.\n *\n * @example\n * ```typescript\n * // lib/auth-client.ts\n * import { createClient } from '@murumets-ee/auth/client'\n *\n * export const authClient = createClient()\n *\n * // In a component:\n * const { data: session } = authClient.useSession()\n * ```\n */\nexport function createClient(\n options?: AuthClientOptions,\n): ReturnType<typeof createAuthClient> & AuthClientWithAdmin {\n // The intersection adds admin methods to the widened base type.\n // Safe: adminClient() plugin adds these methods at runtime — the\n // base annotation just can't declare them (TS2742 on internals).\n return createAuthClient({\n baseURL: options?.baseURL,\n plugins: [adminClient(), ...(options?.organizations ? [organizationClient()] : [])],\n }) as ReturnType<typeof createAuthClient> & AuthClientWithAdmin\n}\n\n// ---------------------------------------------------------------------------\n// UsersApi adapter — bridges better-auth admin client → @murumets-ee/admin-ui/users\n// ---------------------------------------------------------------------------\n\n/**\n * Better-auth's client response shape: discriminated union with data or error.\n * Defined here so we don't depend on better-auth's internal response types.\n */\ninterface BetterAuthResponse<T = unknown> {\n data?: T\n error?: { message?: string }\n}\n\n/**\n * Structural interface for the admin client methods we use.\n *\n * better-auth's `createAuthClient({ plugins: [adminClient()] })` adds an\n * `admin` namespace at runtime with these methods. The widened return type\n * annotation on `createClient()` loses them (TS2742), so we define just the\n * shape we need. TypeScript verifies at call sites that the passed client\n * structurally matches.\n */\ninterface AuthClientWithAdmin {\n admin: {\n listUsers: (opts: {\n query: {\n limit: number\n offset: number\n sortBy?: string\n sortDirection?: 'asc' | 'desc'\n searchValue?: string\n searchField?: string\n searchOperator?: string\n }\n }) => Promise<BetterAuthResponse<{ users: AdminUser[]; total: number }>>\n createUser: (opts: {\n name: string\n email: string\n password: string\n role: string\n }) => Promise<BetterAuthResponse>\n updateUser: (opts: {\n userId: string\n data: Record<string, unknown>\n }) => Promise<BetterAuthResponse>\n removeUser: (opts: { userId: string }) => Promise<BetterAuthResponse>\n setRole: (opts: { userId: string; role: string }) => Promise<BetterAuthResponse>\n banUser: (opts: {\n userId: string\n banReason?: string\n banExpiresIn?: number\n }) => Promise<BetterAuthResponse>\n unbanUser: (opts: { userId: string }) => Promise<BetterAuthResponse>\n revokeUserSessions: (opts: { userId: string }) => Promise<BetterAuthResponse>\n }\n}\n\ninterface UsersListQuery {\n limit: number\n offset: number\n sortBy?: string\n sortDirection?: 'asc' | 'desc'\n searchValue?: string\n searchField?: 'email' | 'name'\n searchOperator?: 'contains' | 'starts_with' | 'ends_with'\n}\n\ninterface NormalizedUser {\n id: string\n name: string\n email: string\n emailVerified: boolean\n image: string | null\n createdAt: string\n updatedAt: string\n role: string | null\n banned: boolean | null\n banReason: string | null\n banExpires: string | null\n}\n\n/**\n * Create a users API adapter from a better-auth client that includes the\n * `adminClient()` plugin. The returned object is structurally compatible\n * with the `UsersApi` interface from `@murumets-ee/admin-ui/users`.\n *\n * @example\n * ```tsx\n * import { createClient, createUsersApi } from '@murumets-ee/auth/client'\n * import { UsersManagement } from '@murumets-ee/admin-ui/users'\n *\n * const authClient = createClient()\n * const usersApi = createUsersApi(authClient)\n *\n * <UsersManagement api={usersApi} currentUserId={session.user.id} />\n * ```\n */\nexport function createUsersApi(client: AuthClientWithAdmin) {\n /**\n * Unwrap better-auth's discriminated union response.\n * On error branch, `error` exists with optional `message`.\n * On success branch, `data` exists.\n */\n function unwrap<T>(res: BetterAuthResponse<T>): T {\n if (res.error) throw new Error(res.error.message ?? 'Unknown error')\n return res.data as T\n }\n\n return {\n async list(query: UsersListQuery): Promise<{ users: NormalizedUser[]; total: number }> {\n const res = await client.admin.listUsers({\n query: {\n limit: query.limit,\n offset: query.offset,\n ...(query.sortBy ? { sortBy: query.sortBy, sortDirection: query.sortDirection } : {}),\n ...(query.searchValue\n ? {\n searchValue: query.searchValue,\n searchField: query.searchField ?? 'email',\n searchOperator: query.searchOperator ?? 'contains',\n }\n : {}),\n },\n })\n const data = unwrap(res)\n return {\n users: (data.users ?? []).map(mapUser),\n total: data.total ?? 0,\n }\n },\n\n async create(data: { name: string; email: string; password: string; role: string }) {\n const res = await client.admin.createUser({\n name: data.name,\n email: data.email,\n password: data.password,\n role: data.role,\n })\n unwrap(res)\n },\n\n async update(userId: string, data: { name?: string }) {\n const res = await client.admin.updateUser({ userId, data })\n unwrap(res)\n },\n\n async remove(userId: string) {\n const res = await client.admin.removeUser({ userId })\n unwrap(res)\n },\n\n async setRole(userId: string, role: string) {\n const res = await client.admin.setRole({ userId, role })\n unwrap(res)\n },\n\n async ban(userId: string, options?: { reason?: string; expiresIn?: number }) {\n const res = await client.admin.banUser({\n userId,\n ...(options?.reason ? { banReason: options.reason } : {}),\n ...(options?.expiresIn ? { banExpiresIn: options.expiresIn } : {}),\n })\n unwrap(res)\n },\n\n async unban(userId: string) {\n const res = await client.admin.unbanUser({ userId })\n unwrap(res)\n },\n\n async revokeSessions(userId: string) {\n const res = await client.admin.revokeUserSessions({ userId })\n unwrap(res)\n },\n }\n}\n\n/** Normalize a better-auth admin user to a serializable shape (Dates → strings). */\nfunction mapUser(u: AdminUser): NormalizedUser {\n return {\n id: u.id,\n name: u.name ?? '',\n email: u.email,\n emailVerified: u.emailVerified ?? false,\n image: u.image ?? null,\n createdAt: u.createdAt instanceof Date ? u.createdAt.toISOString() : String(u.createdAt ?? ''),\n updatedAt: u.updatedAt instanceof Date ? u.updatedAt.toISOString() : String(u.updatedAt ?? ''),\n role: u.role ?? null,\n banned: u.banned ?? null,\n banReason: u.banReason ?? null,\n banExpires: u.banExpires instanceof Date ? u.banExpires.toISOString() : u.banExpires ? String(u.banExpires) : null,\n }\n}\n"],"mappings":"sIA2CA,SAAgB,EACd,EAC2D,CAI3D,OAAO,EAAiB,CACtB,QAAS,GAAS,QAClB,QAAS,CAAC,GAAa,CAAE,GAAI,GAAS,cAAgB,CAAC,GAAoB,CAAC,CAAG,EAAE,CAAE,CACpF,CAAC,CAoGJ,SAAgB,EAAe,EAA6B,CAM1D,SAAS,EAAU,EAA+B,CAChD,GAAI,EAAI,MAAO,MAAU,MAAM,EAAI,MAAM,SAAW,gBAAgB,CACpE,OAAO,EAAI,KAGb,MAAO,CACL,MAAM,KAAK,EAA4E,CAerF,IAAM,EAAO,EAdD,MAAM,EAAO,MAAM,UAAU,CACvC,MAAO,CACL,MAAO,EAAM,MACb,OAAQ,EAAM,OACd,GAAI,EAAM,OAAS,CAAE,OAAQ,EAAM,OAAQ,cAAe,EAAM,cAAe,CAAG,EAAE,CACpF,GAAI,EAAM,YACN,CACE,YAAa,EAAM,YACnB,YAAa,EAAM,aAAe,QAClC,eAAgB,EAAM,gBAAkB,WACzC,CACD,EAAE,CACP,CACF,CAAC,CACsB,CACxB,MAAO,CACL,OAAQ,EAAK,OAAS,EAAE,EAAE,IAAI,EAAQ,CACtC,MAAO,EAAK,OAAS,EACtB,EAGH,MAAM,OAAO,EAAuE,CAOlF,EANY,MAAM,EAAO,MAAM,WAAW,CACxC,KAAM,EAAK,KACX,MAAO,EAAK,MACZ,SAAU,EAAK,SACf,KAAM,EAAK,KACZ,CAAC,CACS,EAGb,MAAM,OAAO,EAAgB,EAAyB,CAEpD,EADY,MAAM,EAAO,MAAM,WAAW,CAAE,SAAQ,OAAM,CAAC,CAChD,EAGb,MAAM,OAAO,EAAgB,CAE3B,EADY,MAAM,EAAO,MAAM,WAAW,CAAE,SAAQ,CAAC,CAC1C,EAGb,MAAM,QAAQ,EAAgB,EAAc,CAE1C,EADY,MAAM,EAAO,MAAM,QAAQ,CAAE,SAAQ,OAAM,CAAC,CAC7C,EAGb,MAAM,IAAI,EAAgB,EAAmD,CAM3E,EALY,MAAM,EAAO,MAAM,QAAQ,CACrC,SACA,GAAI,GAAS,OAAS,CAAE,UAAW,EAAQ,OAAQ,CAAG,EAAE,CACxD,GAAI,GAAS,UAAY,CAAE,aAAc,EAAQ,UAAW,CAAG,EAAE,CAClE,CAAC,CACS,EAGb,MAAM,MAAM,EAAgB,CAE1B,EADY,MAAM,EAAO,MAAM,UAAU,CAAE,SAAQ,CAAC,CACzC,EAGb,MAAM,eAAe,EAAgB,CAEnC,EADY,MAAM,EAAO,MAAM,mBAAmB,CAAE,SAAQ,CAAC,CAClD,EAEd,CAIH,SAAS,EAAQ,EAA8B,CAC7C,MAAO,CACL,GAAI,EAAE,GACN,KAAM,EAAE,MAAQ,GAChB,MAAO,EAAE,MACT,cAAe,EAAE,eAAiB,GAClC,MAAO,EAAE,OAAS,KAClB,UAAW,EAAE,qBAAqB,KAAO,EAAE,UAAU,aAAa,CAAG,OAAO,EAAE,WAAa,GAAG,CAC9F,UAAW,EAAE,qBAAqB,KAAO,EAAE,UAAU,aAAa,CAAG,OAAO,EAAE,WAAa,GAAG,CAC9F,KAAM,EAAE,MAAQ,KAChB,OAAQ,EAAE,QAAU,KACpB,UAAW,EAAE,WAAa,KAC1B,WAAY,EAAE,sBAAsB,KAAO,EAAE,WAAW,aAAa,CAAG,EAAE,WAAa,OAAO,EAAE,WAAW,CAAG,KAC/G"}
package/dist/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
- import { i as AuthConfig, n as getAuth, r as Auth, t as auth } from "./plugin-Dvyj8Crv.mjs";
1
+ import { n as AuthConfig, t as AdminUser } from "./types-Dxs8Jhxs.mjs";
2
+ import { i as AuthAdminApi, n as getAuth, r as Auth, t as auth } from "./plugin-DlfYYNXb.mjs";
2
3
  import { createAccessControl } from "better-auth/plugins/access";
3
4
  import * as _$better_auth_plugins0 from "better-auth/plugins";
4
5
  import { PermissionChecker, RequestContext } from "@murumets-ee/core";
@@ -100,5 +101,5 @@ declare function buildDefaultRoles(ac: ReturnType<typeof createAccessControl>, e
100
101
  };
101
102
  };
102
103
  //#endregion
103
- export { ACTIONS, ACTIONS_WITH_PUBLISH, type Auth, type AuthConfig, BUILT_IN_ROLES, METHOD_TO_ACTION, auth, buildDefaultRoles, buildInitialRoleDefinitions, buildPermissionChecker, buildResourceCatalog, buildStatements, createAccessControl, getAuth, resolveAuthContext };
104
+ export { ACTIONS, ACTIONS_WITH_PUBLISH, type AdminUser, type Auth, type AuthAdminApi, type AuthConfig, BUILT_IN_ROLES, METHOD_TO_ACTION, auth, buildDefaultRoles, buildInitialRoleDefinitions, buildPermissionChecker, buildResourceCatalog, buildStatements, createAccessControl, getAuth, resolveAuthContext };
104
105
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/context.ts","../src/permissions.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;iBA8BsB,kBAAA,CAAmB,IAAA,EAAM,IAAA,EAAM,OAAA,EAAS,OAAA,GAAU,OAAA,CAAQ,cAAA;;;cCf1E,OAAA;AAAA,cACA,oBAAA;;cASO,cAAA;;cAGA,gBAAA,EAAkB,MAAA;;;;;;;iBAiBf,2BAAA,CAAA,GAA+B,MAAA,SAAe,MAAA;;AAhCE;;;;;AAED;;iBA6C/C,sBAAA,CACd,eAAA,EAAiB,MAAA,SAAe,MAAA,sBAC/B,iBAAA;;;AArCH;;;;;AAGA;;;;iBAwEgB,oBAAA,CACd,QAAA;EAAY,IAAA;EAAc,SAAA;IAAc,IAAA;EAAA;AAAA,KACxC,MAAA;EAAW,QAAA;EAAmB,OAAA;AAAA,MAC7B,MAAA;;;;;;;;iBA+Ca,eAAA,CAAgB,QAAA,EAAU,MAAA,KAAQ,MAAA;;;;AAlDlD;;;iBAkEgB,iBAAA,CAAkB,EAAA,EAAI,UAAA,QAAkB,mBAAA,GAAsB,QAAA,EAAU,MAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/context.ts","../src/permissions.ts"],"mappings":";;;;;;;;AA8BA;;;;;;;;;;;;;;;;;;;AAAA,iBAAsB,kBAAA,CAAmB,IAAA,EAAM,IAAA,EAAM,OAAA,EAAS,OAAA,GAAU,OAAA,CAAQ,cAAA;;;cCf1E,OAAA;AAAA,cACA,oBAAA;;cASO,cAAA;;cAGA,gBAAA,EAAkB,MAAA;;;;;;;iBAiBf,2BAAA,CAAA,GAA+B,MAAA,SAAe,MAAA;;;AAhCE;;;;;AAED;iBA6C/C,sBAAA,CACd,eAAA,EAAiB,MAAA,SAAe,MAAA,sBAC/B,iBAAA;;;;AArCH;;;;;AAGA;;;iBAwEgB,oBAAA,CACd,QAAA;EAAY,IAAA;EAAc,SAAA;IAAc,IAAA;EAAA;AAAA,KACxC,MAAA;EAAW,QAAA;EAAmB,OAAA;AAAA,MAC7B,MAAA;;;;;;;;iBA+Ca,eAAA,CAAgB,QAAA,EAAU,MAAA,KAAQ,MAAA;;;;;AAlDlD;;iBAkEgB,iBAAA,CAAkB,EAAA,EAAI,UAAA,QAAkB,mBAAA,GAAsB,QAAA,EAAU,MAAA"}
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{a as e,c as t,i as n,l as r,n as i,o as a,r as o,s,t as c,u as l}from"./permissions-DH0BNEtU.mjs";import{auth as u,getAuth as d}from"./plugin.mjs";import"server-only";async function f(e,t){let n=await e.api.getSession({headers:t});return n?{user:{id:n.user.id,groups:[n.user.role??`viewer`],name:n.user.name??void 0,email:n.user.email??void 0},requestId:crypto.randomUUID()}:{requestId:crypto.randomUUID()}}export{c as ACTIONS,i as ACTIONS_WITH_PUBLISH,o as BUILT_IN_ROLES,n as METHOD_TO_ACTION,u as auth,e as buildDefaultRoles,a as buildInitialRoleDefinitions,s as buildPermissionChecker,t as buildResourceCatalog,r as buildStatements,l as createAccessControl,d as getAuth,f as resolveAuthContext};
1
+ import{a as e,c as t,i as n,l as r,n as i,o as a,r as o,s,t as c,u as l}from"./permissions-DREmJByu.mjs";import{auth as u,getAuth as d}from"./plugin.mjs";import"server-only";async function f(e,t){let n=await e.api.getSession({headers:t});return n?{user:{id:n.user.id,groups:[n.user.role??`viewer`],name:n.user.name??void 0,email:n.user.email??void 0},requestId:crypto.randomUUID()}:{requestId:crypto.randomUUID()}}export{c as ACTIONS,i as ACTIONS_WITH_PUBLISH,o as BUILT_IN_ROLES,n as METHOD_TO_ACTION,u as auth,e as buildDefaultRoles,a as buildInitialRoleDefinitions,s as buildPermissionChecker,t as buildResourceCatalog,r as buildStatements,l as createAccessControl,d as getAuth,f as resolveAuthContext};
2
2
  //# sourceMappingURL=index.mjs.map
@@ -1,2 +1,2 @@
1
1
  import{createAccessControl as e}from"better-auth/plugins/access";const t=[`view`,`create`,`update`,`delete`],n=[`view`,`create`,`update`,`delete`,`publish`],r=[`admin`,`public`,`authenticated`],i={GET:`view`,POST:`create`,PATCH:`update`,DELETE:`delete`};function a(){return{public:{},authenticated:{}}}function o(e){let t=new Map;for(let[n,r]of Object.entries(e)){let e=new Map;for(let[t,n]of Object.entries(r))e.set(t,new Set(n));t.set(n,e)}return(e,n,r)=>e===`admin`?!0:t.get(e)?.get(n)?.has(r)??!1}function s(e){return e.behaviors?.some(e=>e.name===`publishable`)??!1}function c(e,t){let n={};for(let t of e)n[t.name]=s(t)?[`view`,`create`,`update`,`delete`,`publish`]:[`view`,`create`,`update`,`delete`];if(t)for(let e of t)e.resource&&e.actions&&!(e.resource in n)&&(n[e.resource]=[...e.actions]);return n}const l={user:[`create`,`list`,`set-role`,`ban`,`impersonate`,`delete`,`set-password`,`get`,`update`],session:[`list`,`revoke`,`delete`]};function u(e){let r={...l};for(let i of e)r[i.name]=s(i)?n:t;return r}function d(e,t){let n={},r={};n.user=[...l.user],n.session=[...l.session],r.user=[],r.session=[];for(let e of t)n[e.name]=s(e)?[`view`,`create`,`update`,`delete`,`publish`]:[`view`,`create`,`update`,`delete`],r[e.name]=[`view`];return{admin:e.newRole(n),authenticated:e.newRole(r)}}export{d as a,c,i,u as l,n,a as o,r,o as s,t,e as u};
2
- //# sourceMappingURL=permissions-DH0BNEtU.mjs.map
2
+ //# sourceMappingURL=permissions-DREmJByu.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"permissions-DH0BNEtU.mjs","names":[],"sources":["../src/permissions.ts"],"sourcesContent":["/**\n * Permission system — bridges entity access definitions to better-auth's access control,\n * and provides the configurable permission checker for the admin API.\n *\n * Two layers:\n * 1. **better-auth integration**: `buildStatements` / `buildDefaultRoles` — feeds into\n * better-auth's `createAccessControl` for its internal permission system.\n * 2. **Admin API enforcement**: `buildPermissionChecker` / `buildInitialRoleDefinitions` —\n * the toolkit's own firewall-model permission system, persisted in settings.\n */\n\nimport type { PermissionChecker } from '@murumets-ee/core'\nimport type { Entity } from '@murumets-ee/entity'\nimport { createAccessControl } from 'better-auth/plugins/access'\n\nconst ACTIONS = ['view', 'create', 'update', 'delete'] as const\nconst ACTIONS_WITH_PUBLISH = ['view', 'create', 'update', 'delete', 'publish'] as const\n\nexport { ACTIONS, ACTIONS_WITH_PUBLISH }\n\n// ---------------------------------------------------------------------------\n// Built-in roles & constants\n// ---------------------------------------------------------------------------\n\n/** Built-in role names — cannot be deleted via the roles editor. */\nexport const BUILT_IN_ROLES = ['admin', 'public', 'authenticated'] as const\n\n/** Maps HTTP methods to permission action names. */\nexport const METHOD_TO_ACTION: Record<string, string> = {\n GET: 'view',\n POST: 'create',\n PATCH: 'update',\n DELETE: 'delete',\n}\n\n// ---------------------------------------------------------------------------\n// Admin API permission system (firewall model)\n// ---------------------------------------------------------------------------\n\n/**\n * Build initial role definitions for first run (no settings saved yet).\n *\n * All built-in non-admin roles start with ZERO permissions.\n * Admin is never stored — it's hardcoded in the checker.\n */\nexport function buildInitialRoleDefinitions(): Record<string, Record<string, string[]>> {\n return {\n public: {},\n authenticated: {},\n }\n}\n\n/**\n * Build a synchronous permission checker from role definitions.\n *\n * Rules:\n * - `admin` role: ALWAYS returns `true` (hardcoded safety net, ignores settings)\n * - All other roles: exact match from `roleDefinitions` (deny-by-default)\n * - Unknown role / unknown resource / unknown action → `false`\n */\nexport function buildPermissionChecker(\n roleDefinitions: Record<string, Record<string, string[]>>,\n): PermissionChecker {\n // Pre-build lookup maps for O(1) checks\n const perms = new Map<string, Map<string, Set<string>>>()\n\n for (const [role, resources] of Object.entries(roleDefinitions)) {\n const resourceMap = new Map<string, Set<string>>()\n for (const [resource, actions] of Object.entries(resources)) {\n resourceMap.set(resource, new Set(actions))\n }\n perms.set(role, resourceMap)\n }\n\n return (role: string, resource: string, action: string): boolean => {\n if (role === 'admin') return true // Safety net — admin always passes\n return perms.get(role)?.get(resource)?.has(action) ?? false\n }\n}\n\n// ---------------------------------------------------------------------------\n// Resource catalog builder\n// ---------------------------------------------------------------------------\n\n/** Check if an entity has the publishable behavior. */\nfunction isPublishable(entity: { behaviors?: { name: string }[] }): boolean {\n return entity.behaviors?.some((b) => b.name === 'publishable') ?? false\n}\n\n/**\n * Build a complete resource catalog from entities and admin routes.\n *\n * Used by:\n * - `permissionRoutes()` config (`getStatements` callback)\n * - Server-side permission page data loaders\n *\n * Entities automatically get CRUD actions. Publishable entities also get\n * the `publish` action, which gates who can set status to 'published'.\n * Routes with `resource` and `actions` are added if not already present.\n */\nexport function buildResourceCatalog(\n entities: { name: string; behaviors?: { name: string }[] }[],\n routes?: { resource?: string; actions?: readonly string[] }[],\n): Record<string, string[]> {\n const catalog: Record<string, string[]> = {}\n\n for (const entity of entities) {\n catalog[entity.name] = isPublishable(entity)\n ? ['view', 'create', 'update', 'delete', 'publish']\n : ['view', 'create', 'update', 'delete']\n }\n\n if (routes) {\n for (const route of routes) {\n if (route.resource && route.actions && !(route.resource in catalog)) {\n catalog[route.resource] = [...route.actions]\n }\n }\n }\n\n return catalog\n}\n\n// ---------------------------------------------------------------------------\n// better-auth integration (unchanged, used by createAuthServer)\n// ---------------------------------------------------------------------------\n\n/** Admin plugin's built-in resources — must be included for listUsers, ban, etc. */\nconst ADMIN_STATEMENTS = {\n user: [\n 'create',\n 'list',\n 'set-role',\n 'ban',\n 'impersonate',\n 'delete',\n 'set-password',\n 'get',\n 'update',\n ] as const,\n session: ['list', 'revoke', 'delete'] as const,\n}\n\n/**\n * Build a permission statement object from all registered entities,\n * plus the admin plugin's built-in user/session resources.\n *\n * Publishable entities get the additional `publish` action.\n * Result shape: `{ user: [...], session: [...], article: ['view', ...], category: [...] }`\n */\nexport function buildStatements(entities: Entity[]) {\n const statement: Record<string, readonly string[]> = {\n ...ADMIN_STATEMENTS,\n }\n for (const entity of entities) {\n statement[entity.name] = isPublishable(entity) ? ACTIONS_WITH_PUBLISH : ACTIONS\n }\n return statement\n}\n\n/**\n * Build the default toolkit roles for better-auth's access control.\n *\n * - **admin**: full CRUD on all entities + user/session management\n * - **authenticated**: view only (better-auth's `defaultRole`)\n */\nexport function buildDefaultRoles(ac: ReturnType<typeof createAccessControl>, entities: Entity[]) {\n const adminPerms: Record<string, string[]> = {}\n const authenticatedPerms: Record<string, string[]> = {}\n\n // Admin gets full control of user/session management\n adminPerms.user = [...ADMIN_STATEMENTS.user]\n adminPerms.session = [...ADMIN_STATEMENTS.session]\n // Authenticated gets no user/session management\n authenticatedPerms.user = []\n authenticatedPerms.session = []\n\n for (const entity of entities) {\n adminPerms[entity.name] = isPublishable(entity)\n ? ['view', 'create', 'update', 'delete', 'publish']\n : ['view', 'create', 'update', 'delete']\n authenticatedPerms[entity.name] = ['view']\n }\n\n return {\n admin: ac.newRole(adminPerms),\n authenticated: ac.newRole(authenticatedPerms),\n }\n}\n\nexport { createAccessControl }\n"],"mappings":"iEAeA,MAAM,EAAU,CAAC,OAAQ,SAAU,SAAU,SAAS,CAChD,EAAuB,CAAC,OAAQ,SAAU,SAAU,SAAU,UAAU,CASjE,EAAiB,CAAC,QAAS,SAAU,gBAAgB,CAGrD,EAA2C,CACtD,IAAK,OACL,KAAM,SACN,MAAO,SACP,OAAQ,SACT,CAYD,SAAgB,GAAwE,CACtF,MAAO,CACL,OAAQ,EAAE,CACV,cAAe,EAAE,CAClB,CAWH,SAAgB,EACd,EACmB,CAEnB,IAAM,EAAQ,IAAI,IAElB,IAAK,GAAM,CAAC,EAAM,KAAc,OAAO,QAAQ,EAAgB,CAAE,CAC/D,IAAM,EAAc,IAAI,IACxB,IAAK,GAAM,CAAC,EAAU,KAAY,OAAO,QAAQ,EAAU,CACzD,EAAY,IAAI,EAAU,IAAI,IAAI,EAAQ,CAAC,CAE7C,EAAM,IAAI,EAAM,EAAY,CAG9B,OAAQ,EAAc,EAAkB,IAClC,IAAS,QAAgB,GACtB,EAAM,IAAI,EAAK,EAAE,IAAI,EAAS,EAAE,IAAI,EAAO,EAAI,GAS1D,SAAS,EAAc,EAAqD,CAC1E,OAAO,EAAO,WAAW,KAAM,GAAM,EAAE,OAAS,cAAc,EAAI,GAcpE,SAAgB,EACd,EACA,EAC0B,CAC1B,IAAM,EAAoC,EAAE,CAE5C,IAAK,IAAM,KAAU,EACnB,EAAQ,EAAO,MAAQ,EAAc,EAAO,CACxC,CAAC,OAAQ,SAAU,SAAU,SAAU,UAAU,CACjD,CAAC,OAAQ,SAAU,SAAU,SAAS,CAG5C,GAAI,MACG,IAAM,KAAS,EACd,EAAM,UAAY,EAAM,SAAW,EAAE,EAAM,YAAY,KACzD,EAAQ,EAAM,UAAY,CAAC,GAAG,EAAM,QAAQ,EAKlD,OAAO,EAQT,MAAM,EAAmB,CACvB,KAAM,CACJ,SACA,OACA,WACA,MACA,cACA,SACA,eACA,MACA,SACD,CACD,QAAS,CAAC,OAAQ,SAAU,SAAS,CACtC,CASD,SAAgB,EAAgB,EAAoB,CAClD,IAAM,EAA+C,CACnD,GAAG,EACJ,CACD,IAAK,IAAM,KAAU,EACnB,EAAU,EAAO,MAAQ,EAAc,EAAO,CAAG,EAAuB,EAE1E,OAAO,EAST,SAAgB,EAAkB,EAA4C,EAAoB,CAChG,IAAM,EAAuC,EAAE,CACzC,EAA+C,EAAE,CAGvD,EAAW,KAAO,CAAC,GAAG,EAAiB,KAAK,CAC5C,EAAW,QAAU,CAAC,GAAG,EAAiB,QAAQ,CAElD,EAAmB,KAAO,EAAE,CAC5B,EAAmB,QAAU,EAAE,CAE/B,IAAK,IAAM,KAAU,EACnB,EAAW,EAAO,MAAQ,EAAc,EAAO,CAC3C,CAAC,OAAQ,SAAU,SAAU,SAAU,UAAU,CACjD,CAAC,OAAQ,SAAU,SAAU,SAAS,CAC1C,EAAmB,EAAO,MAAQ,CAAC,OAAO,CAG5C,MAAO,CACL,MAAO,EAAG,QAAQ,EAAW,CAC7B,cAAe,EAAG,QAAQ,EAAmB,CAC9C"}
1
+ {"version":3,"file":"permissions-DREmJByu.mjs","names":[],"sources":["../src/permissions.ts"],"sourcesContent":["/**\n * Permission system — bridges entity access definitions to better-auth's access control,\n * and provides the configurable permission checker for the admin API.\n *\n * Two layers:\n * 1. **better-auth integration**: `buildStatements` / `buildDefaultRoles` — feeds into\n * better-auth's `createAccessControl` for its internal permission system.\n * 2. **Admin API enforcement**: `buildPermissionChecker` / `buildInitialRoleDefinitions` —\n * the toolkit's own firewall-model permission system, persisted in settings.\n */\n\nimport type { PermissionChecker } from '@murumets-ee/core'\nimport type { Entity } from '@murumets-ee/entity'\nimport { createAccessControl } from 'better-auth/plugins/access'\n\nconst ACTIONS = ['view', 'create', 'update', 'delete'] as const\nconst ACTIONS_WITH_PUBLISH = ['view', 'create', 'update', 'delete', 'publish'] as const\n\nexport { ACTIONS, ACTIONS_WITH_PUBLISH }\n\n// ---------------------------------------------------------------------------\n// Built-in roles & constants\n// ---------------------------------------------------------------------------\n\n/** Built-in role names — cannot be deleted via the roles editor. */\nexport const BUILT_IN_ROLES = ['admin', 'public', 'authenticated'] as const\n\n/** Maps HTTP methods to permission action names. */\nexport const METHOD_TO_ACTION: Record<string, string> = {\n GET: 'view',\n POST: 'create',\n PATCH: 'update',\n DELETE: 'delete',\n}\n\n// ---------------------------------------------------------------------------\n// Admin API permission system (firewall model)\n// ---------------------------------------------------------------------------\n\n/**\n * Build initial role definitions for first run (no settings saved yet).\n *\n * All built-in non-admin roles start with ZERO permissions.\n * Admin is never stored — it's hardcoded in the checker.\n */\nexport function buildInitialRoleDefinitions(): Record<string, Record<string, string[]>> {\n return {\n public: {},\n authenticated: {},\n }\n}\n\n/**\n * Build a synchronous permission checker from role definitions.\n *\n * Rules:\n * - `admin` role: ALWAYS returns `true` (hardcoded safety net, ignores settings)\n * - All other roles: exact match from `roleDefinitions` (deny-by-default)\n * - Unknown role / unknown resource / unknown action → `false`\n */\nexport function buildPermissionChecker(\n roleDefinitions: Record<string, Record<string, string[]>>,\n): PermissionChecker {\n // Pre-build lookup maps for O(1) checks\n const perms = new Map<string, Map<string, Set<string>>>()\n\n for (const [role, resources] of Object.entries(roleDefinitions)) {\n const resourceMap = new Map<string, Set<string>>()\n for (const [resource, actions] of Object.entries(resources)) {\n resourceMap.set(resource, new Set(actions))\n }\n perms.set(role, resourceMap)\n }\n\n return (role: string, resource: string, action: string): boolean => {\n if (role === 'admin') return true // Safety net — admin always passes\n return perms.get(role)?.get(resource)?.has(action) ?? false\n }\n}\n\n// ---------------------------------------------------------------------------\n// Resource catalog builder\n// ---------------------------------------------------------------------------\n\n/** Check if an entity has the publishable behavior. */\nfunction isPublishable(entity: { behaviors?: { name: string }[] }): boolean {\n return entity.behaviors?.some((b) => b.name === 'publishable') ?? false\n}\n\n/**\n * Build a complete resource catalog from entities and admin routes.\n *\n * Used by:\n * - `permissionRoutes()` config (`getStatements` callback)\n * - Server-side permission page data loaders\n *\n * Entities automatically get CRUD actions. Publishable entities also get\n * the `publish` action, which gates who can set status to 'published'.\n * Routes with `resource` and `actions` are added if not already present.\n */\nexport function buildResourceCatalog(\n entities: { name: string; behaviors?: { name: string }[] }[],\n routes?: { resource?: string; actions?: readonly string[] }[],\n): Record<string, string[]> {\n const catalog: Record<string, string[]> = {}\n\n for (const entity of entities) {\n catalog[entity.name] = isPublishable(entity)\n ? ['view', 'create', 'update', 'delete', 'publish']\n : ['view', 'create', 'update', 'delete']\n }\n\n if (routes) {\n for (const route of routes) {\n if (route.resource && route.actions && !(route.resource in catalog)) {\n catalog[route.resource] = [...route.actions]\n }\n }\n }\n\n return catalog\n}\n\n// ---------------------------------------------------------------------------\n// better-auth integration (unchanged, used by createAuthServer)\n// ---------------------------------------------------------------------------\n\n/** Admin plugin's built-in resources — must be included for listUsers, ban, etc. */\nconst ADMIN_STATEMENTS = {\n user: [\n 'create',\n 'list',\n 'set-role',\n 'ban',\n 'impersonate',\n 'delete',\n 'set-password',\n 'get',\n 'update',\n ] as const,\n session: ['list', 'revoke', 'delete'] as const,\n}\n\n/**\n * Build a permission statement object from all registered entities,\n * plus the admin plugin's built-in user/session resources.\n *\n * Publishable entities get the additional `publish` action.\n * Result shape: `{ user: [...], session: [...], article: ['view', ...], category: [...] }`\n */\nexport function buildStatements(entities: Entity[]) {\n const statement: Record<string, readonly string[]> = {\n ...ADMIN_STATEMENTS,\n }\n for (const entity of entities) {\n statement[entity.name] = isPublishable(entity) ? ACTIONS_WITH_PUBLISH : ACTIONS\n }\n return statement\n}\n\n/**\n * Build the default toolkit roles for better-auth's access control.\n *\n * - **admin**: full CRUD on all entities + user/session management\n * - **authenticated**: view only (better-auth's `defaultRole`)\n */\nexport function buildDefaultRoles(ac: ReturnType<typeof createAccessControl>, entities: Entity[]) {\n const adminPerms: Record<string, string[]> = {}\n const authenticatedPerms: Record<string, string[]> = {}\n\n // Admin gets full control of user/session management\n adminPerms.user = [...ADMIN_STATEMENTS.user]\n adminPerms.session = [...ADMIN_STATEMENTS.session]\n // Authenticated gets no user/session management\n authenticatedPerms.user = []\n authenticatedPerms.session = []\n\n for (const entity of entities) {\n adminPerms[entity.name] = isPublishable(entity)\n ? ['view', 'create', 'update', 'delete', 'publish']\n : ['view', 'create', 'update', 'delete']\n authenticatedPerms[entity.name] = ['view']\n }\n\n return {\n admin: ac.newRole(adminPerms),\n authenticated: ac.newRole(authenticatedPerms),\n }\n}\n\nexport { createAccessControl }\n"],"mappings":"iEAeA,MAAM,EAAU,CAAC,OAAQ,SAAU,SAAU,SAAS,CAChD,EAAuB,CAAC,OAAQ,SAAU,SAAU,SAAU,UAAU,CASjE,EAAiB,CAAC,QAAS,SAAU,gBAAgB,CAGrD,EAA2C,CACtD,IAAK,OACL,KAAM,SACN,MAAO,SACP,OAAQ,SACT,CAYD,SAAgB,GAAwE,CACtF,MAAO,CACL,OAAQ,EAAE,CACV,cAAe,EAAE,CAClB,CAWH,SAAgB,EACd,EACmB,CAEnB,IAAM,EAAQ,IAAI,IAElB,IAAK,GAAM,CAAC,EAAM,KAAc,OAAO,QAAQ,EAAgB,CAAE,CAC/D,IAAM,EAAc,IAAI,IACxB,IAAK,GAAM,CAAC,EAAU,KAAY,OAAO,QAAQ,EAAU,CACzD,EAAY,IAAI,EAAU,IAAI,IAAI,EAAQ,CAAC,CAE7C,EAAM,IAAI,EAAM,EAAY,CAG9B,OAAQ,EAAc,EAAkB,IAClC,IAAS,QAAgB,GACtB,EAAM,IAAI,EAAK,EAAE,IAAI,EAAS,EAAE,IAAI,EAAO,EAAI,GAS1D,SAAS,EAAc,EAAqD,CAC1E,OAAO,EAAO,WAAW,KAAM,GAAM,EAAE,OAAS,cAAc,EAAI,GAcpE,SAAgB,EACd,EACA,EAC0B,CAC1B,IAAM,EAAoC,EAAE,CAE5C,IAAK,IAAM,KAAU,EACnB,EAAQ,EAAO,MAAQ,EAAc,EAAO,CACxC,CAAC,OAAQ,SAAU,SAAU,SAAU,UAAU,CACjD,CAAC,OAAQ,SAAU,SAAU,SAAS,CAG5C,GAAI,MACG,IAAM,KAAS,EACd,EAAM,UAAY,EAAM,SAAW,EAAE,EAAM,YAAY,KACzD,EAAQ,EAAM,UAAY,CAAC,GAAG,EAAM,QAAQ,EAKlD,OAAO,EAQT,MAAM,EAAmB,CACvB,KAAM,CACJ,SACA,OACA,WACA,MACA,cACA,SACA,eACA,MACA,SACD,CACD,QAAS,CAAC,OAAQ,SAAU,SAAS,CACtC,CASD,SAAgB,EAAgB,EAAoB,CAClD,IAAM,EAA+C,CACnD,GAAG,EACJ,CACD,IAAK,IAAM,KAAU,EACnB,EAAU,EAAO,MAAQ,EAAc,EAAO,CAAG,EAAuB,EAE1E,OAAO,EAST,SAAgB,EAAkB,EAA4C,EAAoB,CAChG,IAAM,EAAuC,EAAE,CACzC,EAA+C,EAAE,CAGvD,EAAW,KAAO,CAAC,GAAG,EAAiB,KAAK,CAC5C,EAAW,QAAU,CAAC,GAAG,EAAiB,QAAQ,CAElD,EAAmB,KAAO,EAAE,CAC5B,EAAmB,QAAU,EAAE,CAE/B,IAAK,IAAM,KAAU,EACnB,EAAW,EAAO,MAAQ,EAAc,EAAO,CAC3C,CAAC,OAAQ,SAAU,SAAU,SAAU,UAAU,CACjD,CAAC,OAAQ,SAAU,SAAU,SAAS,CAC1C,EAAmB,EAAO,MAAQ,CAAC,OAAO,CAG5C,MAAO,CACL,MAAO,EAAG,QAAQ,EAAW,CAC7B,cAAe,EAAG,QAAQ,EAAmB,CAC9C"}
@@ -0,0 +1,71 @@
1
+ import { n as AuthConfig, t as AdminUser } from "./types-Dxs8Jhxs.mjs";
2
+ import { Auth } from "better-auth";
3
+ import { Plugin } from "@murumets-ee/core";
4
+ //#region src/server.d.ts
5
+ /** Type of the auth server instance. Aliased from better-auth's generic
6
+ * `Auth` because the explicit annotation on `createAuthServer` means
7
+ * `ReturnType<typeof createAuthServer>` is already `BetterAuthBase`. */
8
+ type Auth$1 = Auth;
9
+ /**
10
+ * Structural interface for the server-side admin API methods.
11
+ *
12
+ * The widened `Auth` type (BetterAuthBase) doesn't expose admin plugin
13
+ * endpoints. This interface describes just the methods consumers need
14
+ * so they can access them without `as any`.
15
+ *
16
+ * Usage: `(auth.api as AuthAdminApi).listUsers(...)`
17
+ */
18
+ interface AuthAdminApi {
19
+ listUsers: (opts: {
20
+ headers: Headers;
21
+ query: {
22
+ limit: number;
23
+ sortBy: string;
24
+ sortDirection: 'asc' | 'desc';
25
+ };
26
+ }) => Promise<{
27
+ users: AdminUser[];
28
+ total: number;
29
+ }>;
30
+ }
31
+ //#endregion
32
+ //#region src/plugin.d.ts
33
+ /** Auth with admin API — admin plugin is always loaded in createAuthServer */
34
+ type AuthWithAdmin = Auth$1 & {
35
+ api: AuthAdminApi;
36
+ };
37
+ /**
38
+ * Get the auth server instance.
39
+ * Throws if the auth plugin hasn't been initialized yet.
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * // app/api/auth/[...all]/route.ts (user writes this)
44
+ * import { toNextJsHandler } from 'better-auth/next-js'
45
+ * import { getAuth } from '@murumets-ee/auth'
46
+ *
47
+ * export const { GET, POST } = toNextJsHandler(getAuth())
48
+ * ```
49
+ */
50
+ declare function getAuth(): AuthWithAdmin;
51
+ /**
52
+ * Create the auth toolkit plugin.
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * import { defineConfig } from '@murumets-ee/core'
57
+ * import { auth } from '@murumets-ee/auth'
58
+ *
59
+ * export default defineConfig({
60
+ * db: { url: process.env.DATABASE_URL! },
61
+ * entities: [Article, Category],
62
+ * plugins: [
63
+ * auth({ providers: ['email'] }),
64
+ * ],
65
+ * })
66
+ * ```
67
+ */
68
+ declare function auth(config?: AuthConfig): Plugin;
69
+ //#endregion
70
+ export { AuthAdminApi as i, getAuth as n, Auth$1 as r, auth as t };
71
+ //# sourceMappingURL=plugin-DlfYYNXb.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-DlfYYNXb.d.mts","names":[],"sources":["../src/server.ts","../src/plugin.ts"],"mappings":";;;;;;;KAsYY,MAAA,GAAO,IAAA;;ACnXyB;;;;;;;;UD8X3B,YAAA;EACf,SAAA,GAAY,IAAA;IACV,OAAA,EAAS,OAAA;IACT,KAAA;MACE,KAAA;MACA,MAAA;MACA,aAAA;IAAA;EAAA,MAEE,OAAA;IAAU,KAAA,EAAO,SAAA;IAAa,KAAA;EAAA;AAAA;;;;KCnYjC,aAAA,GAAgB,MAAA;EAAS,GAAA,EAAK,YAAA;AAAA;;;;;;;;;;;;;;iBAkBnB,OAAA,CAAA,GAAW,aAAA;;;;;;;;;;AArBiB;;;;;;;;iBA6C5B,IAAA,CAAK,MAAA,GAAQ,UAAA,GAAkB,MAAA"}
package/dist/plugin.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { n as getAuth, t as auth } from "./plugin-Dvyj8Crv.mjs";
1
+ import { n as getAuth, t as auth } from "./plugin-DlfYYNXb.mjs";
2
2
  export { auth, getAuth };
package/dist/plugin.mjs CHANGED
@@ -1,2 +1,2 @@
1
- let e=null;function t(){if(!e)throw Error(`@murumets-ee/auth not initialized. Add auth() to your toolkit config plugins.`);return e}function n(t={}){return{name:`@murumets-ee/auth`,init:async n=>{let{createAuthServer:r}=await import(`./server-DgG2m6uD.mjs`),i;if(t.audit!==!1){let{createAuditLogger:e,createAuditDbWriter:t,createLogger:r}=await import(`@murumets-ee/logging`);i=e({logger:r({name:`auth-audit`}),dbWriter:t(n.db.readWrite)})}e=r(t,n,i),n.logger.info(`Auth plugin initialized`)}}}export{n as auth,t as getAuth};
1
+ import{a as e,f as t,m as n,r,s as i,t as a,u as o}from"./schema-Je6e5yt2.mjs";let s=null;function c(){if(!s)throw Error(`@murumets-ee/auth not initialized. Add auth() to your toolkit config plugins.`);return s}function l(c={}){return{name:`@murumets-ee/auth`,tables:{user:t,session:o,account:a,verification:n,organization:i,member:e,invitation:r},init:async e=>{let{createAuthServer:t}=await import(`./server-B7Gdv2He.mjs`),n;if(c.audit!==!1){let{createAuditLogger:t,createAuditDbWriter:r,createLogger:i}=await import(`@murumets-ee/logging`);n=t({logger:i({name:`auth-audit`}),dbWriter:r(e.db.readWrite)})}s=t(c,e,n),e.logger.info(`Auth plugin initialized`)}}}export{l as auth,c as getAuth};
2
2
  //# sourceMappingURL=plugin.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.mjs","names":[],"sources":["../src/plugin.ts"],"sourcesContent":["/**\n * Toolkit plugin implementation for @murumets-ee/auth.\n *\n * Implements the Plugin interface from @murumets-ee/core.\n * Creates and stores the better-auth server instance during init.\n */\n\nimport type { Plugin } from '@murumets-ee/core'\nimport type { AuditLogger } from '@murumets-ee/logging'\nimport type { Auth } from './server.js'\nimport type { AuthConfig } from './types.js'\n\n/** The initialized auth server instance (set during plugin init) */\nlet _auth: Auth | null = null\n\n/**\n * Get the auth server instance.\n * Throws if the auth plugin hasn't been initialized yet.\n *\n * @example\n * ```typescript\n * // app/api/auth/[...all]/route.ts (user writes this)\n * import { toNextJsHandler } from 'better-auth/next-js'\n * import { getAuth } from '@murumets-ee/auth'\n *\n * export const { GET, POST } = toNextJsHandler(getAuth())\n * ```\n */\nexport function getAuth(): Auth {\n if (!_auth) {\n throw new Error('@murumets-ee/auth not initialized. Add auth() to your toolkit config plugins.')\n }\n return _auth\n}\n\n/**\n * Create the auth toolkit plugin.\n *\n * @example\n * ```typescript\n * import { defineConfig } from '@murumets-ee/core'\n * import { auth } from '@murumets-ee/auth'\n *\n * export default defineConfig({\n * db: { url: process.env.DATABASE_URL! },\n * entities: [Article, Category],\n * plugins: [\n * auth({ providers: ['email'] }),\n * ],\n * })\n * ```\n */\nexport function auth(config: AuthConfig = {}): Plugin {\n return {\n name: '@murumets-ee/auth',\n // better-auth manages its own tables no toolkit entities or migrations.\n // Use `npx @better-auth/cli generate` + `drizzle-kit` for schema.\n init: async (app) => {\n const { createAuthServer } = await import('./server.js')\n\n // Create audit logger for auth events (login, signup, ban, etc.)\n // unless explicitly disabled via `audit: false`\n let auditLogger: AuditLogger | undefined\n if (config.audit !== false) {\n const { createAuditLogger, createAuditDbWriter, createLogger } = await import(\n '@murumets-ee/logging'\n )\n auditLogger = createAuditLogger({\n logger: createLogger({ name: 'auth-audit' }),\n dbWriter: createAuditDbWriter(app.db.readWrite),\n })\n }\n\n _auth = createAuthServer(config, app, auditLogger)\n app.logger.info('Auth plugin initialized')\n },\n }\n}\n"],"mappings":"AAaA,IAAI,EAAqB,KAezB,SAAgB,GAAgB,CAC9B,GAAI,CAAC,EACH,MAAU,MAAM,gFAAgF,CAElG,OAAO,EAoBT,SAAgB,EAAK,EAAqB,EAAE,CAAU,CACpD,MAAO,CACL,KAAM,oBAGN,KAAM,KAAO,IAAQ,CACnB,GAAM,CAAE,oBAAqB,MAAM,OAAO,yBAItC,EACJ,GAAI,EAAO,QAAU,GAAO,CAC1B,GAAM,CAAE,oBAAmB,sBAAqB,gBAAiB,MAAM,OACrE,wBAEF,EAAc,EAAkB,CAC9B,OAAQ,EAAa,CAAE,KAAM,aAAc,CAAC,CAC5C,SAAU,EAAoB,EAAI,GAAG,UAAU,CAChD,CAAC,CAGJ,EAAQ,EAAiB,EAAQ,EAAK,EAAY,CAClD,EAAI,OAAO,KAAK,0BAA0B,EAE7C"}
1
+ {"version":3,"file":"plugin.mjs","names":[],"sources":["../src/plugin.ts"],"sourcesContent":["/**\n * Toolkit plugin implementation for @murumets-ee/auth.\n *\n * Implements the Plugin interface from @murumets-ee/core.\n * Creates and stores the better-auth server instance during init.\n */\n\nimport type { Plugin } from '@murumets-ee/core'\nimport type { AuditLogger } from '@murumets-ee/logging'\nimport {\n account,\n invitation,\n member,\n organization,\n session,\n user,\n verification,\n} from './schema.js'\nimport type { Auth, AuthAdminApi } from './server.js'\nimport type { AuthConfig } from './types.js'\n\n/** Auth with admin API — admin plugin is always loaded in createAuthServer */\ntype AuthWithAdmin = Auth & { api: AuthAdminApi }\n\n/** The initialized auth server instance (set during plugin init) */\nlet _auth: AuthWithAdmin | null = null\n\n/**\n * Get the auth server instance.\n * Throws if the auth plugin hasn't been initialized yet.\n *\n * @example\n * ```typescript\n * // app/api/auth/[...all]/route.ts (user writes this)\n * import { toNextJsHandler } from 'better-auth/next-js'\n * import { getAuth } from '@murumets-ee/auth'\n *\n * export const { GET, POST } = toNextJsHandler(getAuth())\n * ```\n */\nexport function getAuth(): AuthWithAdmin {\n if (!_auth) {\n throw new Error('@murumets-ee/auth not initialized. Add auth() to your toolkit config plugins.')\n }\n return _auth\n}\n\n/**\n * Create the auth toolkit plugin.\n *\n * @example\n * ```typescript\n * import { defineConfig } from '@murumets-ee/core'\n * import { auth } from '@murumets-ee/auth'\n *\n * export default defineConfig({\n * db: { url: process.env.DATABASE_URL! },\n * entities: [Article, Category],\n * plugins: [\n * auth({ providers: ['email'] }),\n * ],\n * })\n * ```\n */\nexport function auth(config: AuthConfig = {}): Plugin {\n return {\n name: '@murumets-ee/auth',\n // Expose better-auth's Drizzle tables so `lumi migrate` picks them up\n // automatically. The canonical schema lives in\n // `@murumets-ee/auth/schema` committed, not codegen-generated.\n tables: { user, session, account, verification, organization, member, invitation },\n init: async (app) => {\n const { createAuthServer } = await import('./server.js')\n\n // Create audit logger for auth events (login, signup, ban, etc.)\n // unless explicitly disabled via `audit: false`\n let auditLogger: AuditLogger | undefined\n if (config.audit !== false) {\n const { createAuditLogger, createAuditDbWriter, createLogger } = await import(\n '@murumets-ee/logging'\n )\n auditLogger = createAuditLogger({\n logger: createLogger({ name: 'auth-audit' }),\n dbWriter: createAuditDbWriter(app.db.readWrite),\n })\n }\n\n // createAuthServer always loads the admin plugin, so listUsers etc.\n // exist at runtime. The return type is widened to BetterAuthBase for\n // DTS portability (TS2742 — zod@4 internal paths). This single\n // assertion bridges the gap so getAuth() consumers get typed admin API.\n _auth = createAuthServer(config, app, auditLogger) as AuthWithAdmin\n app.logger.info('Auth plugin initialized')\n },\n }\n}\n"],"mappings":"+EAyBA,IAAI,EAA8B,KAelC,SAAgB,GAAyB,CACvC,GAAI,CAAC,EACH,MAAU,MAAM,gFAAgF,CAElG,OAAO,EAoBT,SAAgB,EAAK,EAAqB,EAAE,CAAU,CACpD,MAAO,CACL,KAAM,oBAIN,OAAQ,CAAE,OAAM,UAAS,UAAS,eAAc,eAAc,SAAQ,aAAY,CAClF,KAAM,KAAO,IAAQ,CACnB,GAAM,CAAE,oBAAqB,MAAM,OAAO,yBAItC,EACJ,GAAI,EAAO,QAAU,GAAO,CAC1B,GAAM,CAAE,oBAAmB,sBAAqB,gBAAiB,MAAM,OACrE,wBAEF,EAAc,EAAkB,CAC9B,OAAQ,EAAa,CAAE,KAAM,aAAc,CAAC,CAC5C,SAAU,EAAoB,EAAI,GAAG,UAAU,CAChD,CAAC,CAOJ,EAAQ,EAAiB,EAAQ,EAAK,EAAY,CAClD,EAAI,OAAO,KAAK,0BAA0B,EAE7C"}
@@ -0,0 +1,2 @@
1
+ import{relations as e}from"drizzle-orm";import{boolean as t,index as n,pgTable as r,text as i,timestamp as a,uniqueIndex as o}from"drizzle-orm/pg-core";var s=Object.defineProperty,c=((e,t)=>{let n={};for(var r in e)s(n,r,{get:e[r],enumerable:!0});return t||s(n,Symbol.toStringTag,{value:`Module`}),n})({account:()=>d,accountRelations:()=>v,invitation:()=>h,invitationRelations:()=>x,member:()=>m,memberRelations:()=>b,organization:()=>p,organizationRelations:()=>y,session:()=>u,sessionRelations:()=>_,user:()=>l,userRelations:()=>g,verification:()=>f});const l=r(`user`,{id:i(`id`).primaryKey(),name:i(`name`).notNull(),email:i(`email`).notNull().unique(),emailVerified:t(`email_verified`).default(!1).notNull(),image:i(`image`),createdAt:a(`created_at`).defaultNow().notNull(),updatedAt:a(`updated_at`).defaultNow().$onUpdate(()=>new Date).notNull(),role:i(`role`),banned:t(`banned`).default(!1),banReason:i(`ban_reason`),banExpires:a(`ban_expires`)}),u=r(`session`,{id:i(`id`).primaryKey(),expiresAt:a(`expires_at`).notNull(),token:i(`token`).notNull().unique(),createdAt:a(`created_at`).defaultNow().notNull(),updatedAt:a(`updated_at`).$onUpdate(()=>new Date).notNull(),ipAddress:i(`ip_address`),userAgent:i(`user_agent`),userId:i(`user_id`).notNull().references(()=>l.id,{onDelete:`cascade`}),impersonatedBy:i(`impersonated_by`),activeOrganizationId:i(`active_organization_id`)},e=>[n(`session_userId_idx`).on(e.userId)]),d=r(`account`,{id:i(`id`).primaryKey(),accountId:i(`account_id`).notNull(),providerId:i(`provider_id`).notNull(),userId:i(`user_id`).notNull().references(()=>l.id,{onDelete:`cascade`}),accessToken:i(`access_token`),refreshToken:i(`refresh_token`),idToken:i(`id_token`),accessTokenExpiresAt:a(`access_token_expires_at`),refreshTokenExpiresAt:a(`refresh_token_expires_at`),scope:i(`scope`),password:i(`password`),createdAt:a(`created_at`).defaultNow().notNull(),updatedAt:a(`updated_at`).$onUpdate(()=>new Date).notNull()},e=>[n(`account_userId_idx`).on(e.userId)]),f=r(`verification`,{id:i(`id`).primaryKey(),identifier:i(`identifier`).notNull(),value:i(`value`).notNull(),expiresAt:a(`expires_at`).notNull(),createdAt:a(`created_at`).defaultNow().notNull(),updatedAt:a(`updated_at`).defaultNow().$onUpdate(()=>new Date).notNull()},e=>[n(`verification_identifier_idx`).on(e.identifier)]),p=r(`organization`,{id:i(`id`).primaryKey(),name:i(`name`).notNull(),slug:i(`slug`).notNull().unique(),logo:i(`logo`),createdAt:a(`created_at`).notNull(),metadata:i(`metadata`)},e=>[o(`organization_slug_uidx`).on(e.slug)]),m=r(`member`,{id:i(`id`).primaryKey(),organizationId:i(`organization_id`).notNull().references(()=>p.id,{onDelete:`cascade`}),userId:i(`user_id`).notNull().references(()=>l.id,{onDelete:`cascade`}),role:i(`role`).default(`member`).notNull(),createdAt:a(`created_at`).notNull()},e=>[n(`member_organizationId_idx`).on(e.organizationId),n(`member_userId_idx`).on(e.userId)]),h=r(`invitation`,{id:i(`id`).primaryKey(),organizationId:i(`organization_id`).notNull().references(()=>p.id,{onDelete:`cascade`}),email:i(`email`).notNull(),role:i(`role`),status:i(`status`).default(`pending`).notNull(),expiresAt:a(`expires_at`).notNull(),createdAt:a(`created_at`).defaultNow().notNull(),inviterId:i(`inviter_id`).notNull().references(()=>l.id,{onDelete:`cascade`})},e=>[n(`invitation_organizationId_idx`).on(e.organizationId),n(`invitation_email_idx`).on(e.email)]),g=e(l,({many:e})=>({sessions:e(u),accounts:e(d),members:e(m),invitations:e(h)})),_=e(u,({one:e})=>({user:e(l,{fields:[u.userId],references:[l.id]})})),v=e(d,({one:e})=>({user:e(l,{fields:[d.userId],references:[l.id]})})),y=e(p,({many:e})=>({members:e(m),invitations:e(h)})),b=e(m,({one:e})=>({organization:e(p,{fields:[m.organizationId],references:[p.id]}),user:e(l,{fields:[m.userId],references:[l.id]})})),x=e(h,({one:e})=>({organization:e(p,{fields:[h.organizationId],references:[p.id]}),user:e(l,{fields:[h.inviterId],references:[l.id]})}));export{m as a,y as c,_ as d,l as f,x as i,c as l,f as m,v as n,b as o,g as p,h as r,p as s,d as t,u};
2
+ //# sourceMappingURL=schema-Je6e5yt2.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-Je6e5yt2.mjs","names":[],"sources":["../src/schema.ts"],"sourcesContent":["/**\n * Pre-generated Drizzle schema for better-auth tables.\n *\n * This file is the canonical snapshot of every table better-auth's\n * `admin` and `organization` plugins create when configured with the\n * toolkit's default server. It is committed source — NOT generated at\n * scaffold time — so scaffolded projects don't need to invoke\n * `@better-auth/cli generate`, don't need a separate `auth.config.ts`,\n * and don't carry a `generated/auth-schema.ts` file in their repo.\n *\n * Regenerating this file:\n *\n * 1. Only when bumping better-auth or changing the plugin list in\n * `packages/auth/src/server.ts`.\n * 2. Run `pnpm --filter @murumets-ee/auth gen:schema` (which runs\n * `@better-auth/cli generate` against `scripts/schema-source.ts`)\n * and review the diff.\n * 3. Commit.\n *\n * Consumers: `import * as authSchema from '@murumets-ee/auth/schema'`\n * and pass it directly to drizzle-kit/api's `generateDrizzleJson`.\n */\n\nimport { relations } from 'drizzle-orm'\nimport { boolean, index, pgTable, text, timestamp, uniqueIndex } from 'drizzle-orm/pg-core'\n\nexport const user = pgTable('user', {\n id: text('id').primaryKey(),\n name: text('name').notNull(),\n email: text('email').notNull().unique(),\n emailVerified: boolean('email_verified').default(false).notNull(),\n image: text('image'),\n createdAt: timestamp('created_at').defaultNow().notNull(),\n updatedAt: timestamp('updated_at')\n .defaultNow()\n .$onUpdate(() => /* @__PURE__ */ new Date())\n .notNull(),\n role: text('role'),\n banned: boolean('banned').default(false),\n banReason: text('ban_reason'),\n banExpires: timestamp('ban_expires'),\n})\n\nexport const session = pgTable(\n 'session',\n {\n id: text('id').primaryKey(),\n expiresAt: timestamp('expires_at').notNull(),\n token: text('token').notNull().unique(),\n createdAt: timestamp('created_at').defaultNow().notNull(),\n updatedAt: timestamp('updated_at')\n .$onUpdate(() => /* @__PURE__ */ new Date())\n .notNull(),\n ipAddress: text('ip_address'),\n userAgent: text('user_agent'),\n userId: text('user_id')\n .notNull()\n .references(() => user.id, { onDelete: 'cascade' }),\n impersonatedBy: text('impersonated_by'),\n activeOrganizationId: text('active_organization_id'),\n },\n (table) => [index('session_userId_idx').on(table.userId)],\n)\n\nexport const account = pgTable(\n 'account',\n {\n id: text('id').primaryKey(),\n accountId: text('account_id').notNull(),\n providerId: text('provider_id').notNull(),\n userId: text('user_id')\n .notNull()\n .references(() => user.id, { onDelete: 'cascade' }),\n accessToken: text('access_token'),\n refreshToken: text('refresh_token'),\n idToken: text('id_token'),\n accessTokenExpiresAt: timestamp('access_token_expires_at'),\n refreshTokenExpiresAt: timestamp('refresh_token_expires_at'),\n scope: text('scope'),\n password: text('password'),\n createdAt: timestamp('created_at').defaultNow().notNull(),\n updatedAt: timestamp('updated_at')\n .$onUpdate(() => /* @__PURE__ */ new Date())\n .notNull(),\n },\n (table) => [index('account_userId_idx').on(table.userId)],\n)\n\nexport const verification = pgTable(\n 'verification',\n {\n id: text('id').primaryKey(),\n identifier: text('identifier').notNull(),\n value: text('value').notNull(),\n expiresAt: timestamp('expires_at').notNull(),\n createdAt: timestamp('created_at').defaultNow().notNull(),\n updatedAt: timestamp('updated_at')\n .defaultNow()\n .$onUpdate(() => /* @__PURE__ */ new Date())\n .notNull(),\n },\n (table) => [index('verification_identifier_idx').on(table.identifier)],\n)\n\nexport const organization = pgTable(\n 'organization',\n {\n id: text('id').primaryKey(),\n name: text('name').notNull(),\n slug: text('slug').notNull().unique(),\n logo: text('logo'),\n createdAt: timestamp('created_at').notNull(),\n metadata: text('metadata'),\n },\n (table) => [uniqueIndex('organization_slug_uidx').on(table.slug)],\n)\n\nexport const member = pgTable(\n 'member',\n {\n id: text('id').primaryKey(),\n organizationId: text('organization_id')\n .notNull()\n .references(() => organization.id, { onDelete: 'cascade' }),\n userId: text('user_id')\n .notNull()\n .references(() => user.id, { onDelete: 'cascade' }),\n role: text('role').default('member').notNull(),\n createdAt: timestamp('created_at').notNull(),\n },\n (table) => [\n index('member_organizationId_idx').on(table.organizationId),\n index('member_userId_idx').on(table.userId),\n ],\n)\n\nexport const invitation = pgTable(\n 'invitation',\n {\n id: text('id').primaryKey(),\n organizationId: text('organization_id')\n .notNull()\n .references(() => organization.id, { onDelete: 'cascade' }),\n email: text('email').notNull(),\n role: text('role'),\n status: text('status').default('pending').notNull(),\n expiresAt: timestamp('expires_at').notNull(),\n createdAt: timestamp('created_at').defaultNow().notNull(),\n inviterId: text('inviter_id')\n .notNull()\n .references(() => user.id, { onDelete: 'cascade' }),\n },\n (table) => [\n index('invitation_organizationId_idx').on(table.organizationId),\n index('invitation_email_idx').on(table.email),\n ],\n)\n\nexport const userRelations = relations(user, ({ many }) => ({\n sessions: many(session),\n accounts: many(account),\n members: many(member),\n invitations: many(invitation),\n}))\n\nexport const sessionRelations = relations(session, ({ one }) => ({\n user: one(user, {\n fields: [session.userId],\n references: [user.id],\n }),\n}))\n\nexport const accountRelations = relations(account, ({ one }) => ({\n user: one(user, {\n fields: [account.userId],\n references: [user.id],\n }),\n}))\n\nexport const organizationRelations = relations(organization, ({ many }) => ({\n members: many(member),\n invitations: many(invitation),\n}))\n\nexport const memberRelations = relations(member, ({ one }) => ({\n organization: one(organization, {\n fields: [member.organizationId],\n references: [organization.id],\n }),\n user: one(user, {\n fields: [member.userId],\n references: [user.id],\n }),\n}))\n\nexport const invitationRelations = relations(invitation, ({ one }) => ({\n organization: one(organization, {\n fields: [invitation.organizationId],\n references: [organization.id],\n }),\n user: one(user, {\n fields: [invitation.inviterId],\n references: [user.id],\n }),\n}))\n"],"mappings":"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"}