{"version":3,"file":"index.cjs","names":["parsePolicyRow","parseRoleRow"],"sources":["../../../src/adapters/prisma/index.ts"],"sourcesContent":["import type { AccessControl, IamAdapter, IamPrimitives, IamRequest } from '../../core/types'\nimport { parsePolicyRow, parseRoleRow } from '../../core/validate'\n\n/** IamPrisma adapter integration types. Type-only namespace - zero bundle cost. */\nexport namespace IamPrisma {\n  /** Row shape returned by `prisma.accessPolicy` queries. */\n  export interface IPolicyRow {\n    id: string\n    name: string\n    description: string | null\n    version: number\n    algorithm: string\n    rules: unknown\n    targets: unknown | null\n  }\n\n  /** Row shape returned by `prisma.accessRole` queries. */\n  export interface IRoleRow {\n    id: string\n    name: string\n    description: string | null\n    permissions: unknown\n    inherits: string[] | null\n    scope: string | null\n    metadata: unknown | null\n  }\n\n  /** Row shape returned by `prisma.accessAssignment` queries. */\n  export interface IAssignmentRow {\n    subjectId: string\n    roleId: string\n    scope: string | null\n  }\n\n  /** Row shape returned by `prisma.accessSubjectAttr` queries. */\n  export interface IAttrRow {\n    subjectId: string\n    data: unknown\n  }\n\n  /**\n   * Generic Prisma client type so we don't require @prisma/client as a hard dep.\n   * Your PrismaClient just needs these models.\n   */\n  export interface ILike {\n    accessPolicy: {\n      findMany: (args?: unknown) => Promise<IPolicyRow[]>\n      findUnique: (args: { where: { id: string } }) => Promise<IPolicyRow | null>\n      upsert: (args: {\n        where: { id: string }\n        create: Record<string, unknown>\n        update: Record<string, unknown>\n      }) => Promise<IPolicyRow>\n      delete: (args: { where: { id: string } }) => Promise<IPolicyRow>\n    }\n    accessRole: {\n      findMany: (args?: unknown) => Promise<IRoleRow[]>\n      findUnique: (args: { where: { id: string } }) => Promise<IRoleRow | null>\n      upsert: (args: {\n        where: { id: string }\n        create: Record<string, unknown>\n        update: Record<string, unknown>\n      }) => Promise<IRoleRow>\n      delete: (args: { where: { id: string } }) => Promise<IRoleRow>\n    }\n    accessAssignment: {\n      findMany: (args: { where: { subjectId: string; scope?: string | null } }) => Promise<IAssignmentRow[]>\n      create: (args: { data: Record<string, unknown> }) => Promise<IAssignmentRow>\n      deleteMany: (args: { where: Record<string, unknown> }) => Promise<{ count: number }>\n    }\n    accessSubjectAttr: {\n      findUnique: (args: { where: { subjectId: string } }) => Promise<IAttrRow | null>\n      upsert: (args: {\n        where: { subjectId: string }\n        create: Record<string, unknown>\n        update: Record<string, unknown>\n      }) => Promise<IAttrRow>\n    }\n  }\n}\n\n/**\n * Prisma-backed adapter; expects `accessPolicy`, `accessRole`, `accessAssignment`, `accessSubjectAttr` models.\n *\n * @template TAction - Constrains valid action strings.\n * @template TResource - Constrains valid resource strings.\n * @template TRole - Constrains valid role strings.\n * @template TScope - Constrains valid scope strings.\n * @template TPrisma - Constrains the Prisma client shape.\n */\nexport class IamPrismaAdapter<\n  TAction extends string = string,\n  TResource extends string = string,\n  TRole extends string = string,\n  TScope extends string = string,\n  TPrisma extends IamPrisma.ILike = IamPrisma.ILike,\n> implements IamAdapter.IAdapter<TAction, TResource, TRole, TScope>\n{\n  private _prisma: TPrisma\n\n  /**\n   * Creates a new Prisma adapter.\n   *\n   * @param prisma - Provides the Prisma client instance with required models.\n   */\n  constructor(prisma: TPrisma) {\n    this._prisma = prisma\n  }\n\n  /**\n   * Lists every policy in the database.\n   *\n   * @param _opts - Ignored read options accepted for interface compatibility.\n   * @returns All policies parsed from `accessPolicy` rows.\n   */\n  async listPolicies(_opts?: IamAdapter.IReadOptions): Promise<AccessControl.IPolicy<TAction, TResource, TRole>[]> {\n    const rows = await this._prisma.accessPolicy.findMany()\n    const out: AccessControl.IPolicy<TAction, TResource, TRole>[] = []\n    for (const row of rows) {\n      const policy = parsePolicyRow<TAction, TResource, TRole>(toPolicy(row))\n      if (policy !== null) out.push(policy)\n    }\n    return out\n  }\n\n  /**\n   * Fetches a single policy by ID.\n   *\n   * @param id - Identifies the policy to look up.\n   * @param _opts - Ignored read options accepted for interface compatibility.\n   * @returns The matching policy or `null` when absent.\n   */\n  async getPolicy(\n    id: string,\n    _opts?: IamAdapter.IReadOptions,\n  ): Promise<AccessControl.IPolicy<TAction, TResource, TRole> | null> {\n    const row = await this._prisma.accessPolicy.findUnique({ where: { id } })\n    return row ? parsePolicyRow<TAction, TResource, TRole>(toPolicy(row)) : null\n  }\n\n  /**\n   * Upserts a policy through Prisma.\n   *\n   * @param p - Provides the policy to persist.\n   * @returns Resolves once the upsert completes.\n   */\n  async savePolicy(p: AccessControl.IPolicy<TAction, TResource, TRole>): Promise<void> {\n    const data = fromPolicy(p)\n    await this._prisma.accessPolicy.upsert({\n      where: { id: p.id },\n      create: data,\n      update: data,\n    })\n  }\n\n  /**\n   * Removes a policy by ID.\n   *\n   * @param id - Identifies the policy to delete.\n   * @returns Resolves once the delete completes.\n   */\n  async deletePolicy(id: string): Promise<void> {\n    await this._prisma.accessPolicy.delete({ where: { id } })\n  }\n\n  /**\n   * Lists every role in the database.\n   *\n   * @param _opts - Ignored read options accepted for interface compatibility.\n   * @returns All roles parsed from `accessRole` rows.\n   */\n  async listRoles(_opts?: IamAdapter.IReadOptions): Promise<AccessControl.IRole<TAction, TResource, TRole, TScope>[]> {\n    const rows = await this._prisma.accessRole.findMany()\n    const out: AccessControl.IRole<TAction, TResource, TRole, TScope>[] = []\n    for (const row of rows) {\n      const role = parseRoleRow<TAction, TResource, TRole, TScope>(toRole(row))\n      if (role !== null) out.push(role)\n    }\n    return out\n  }\n\n  /**\n   * Fetches a single role by ID.\n   *\n   * @param id - Identifies the role to look up.\n   * @param _opts - Ignored read options accepted for interface compatibility.\n   * @returns The matching role or `null` when absent.\n   */\n  async getRole(\n    id: string,\n    _opts?: IamAdapter.IReadOptions,\n  ): Promise<AccessControl.IRole<TAction, TResource, TRole, TScope> | null> {\n    const row = await this._prisma.accessRole.findUnique({ where: { id } })\n    return row ? parseRoleRow<TAction, TResource, TRole, TScope>(toRole(row)) : null\n  }\n\n  /**\n   * Upserts a role through Prisma.\n   *\n   * @param r - Provides the role to persist.\n   * @returns Resolves once the upsert completes.\n   */\n  async saveRole(r: AccessControl.IRole<TAction, TResource, TRole, TScope>): Promise<void> {\n    const data = fromRole(r)\n    await this._prisma.accessRole.upsert({\n      where: { id: r.id },\n      create: data,\n      update: data,\n    })\n  }\n\n  /**\n   * Removes a role by ID.\n   *\n   * @param id - Identifies the role to delete.\n   * @returns Resolves once the delete completes.\n   */\n  async deleteRole(id: string): Promise<void> {\n    await this._prisma.accessRole.delete({ where: { id } })\n  }\n\n  /**\n   * Lists deduplicated role IDs assigned to a subject (scoped or unscoped).\n   *\n   * @param subjectId - Identifies the subject whose roles are read.\n   * @param _opts - Ignored read options accepted for interface compatibility.\n   * @returns Deduplicated array of role IDs.\n   */\n  async getSubjectRoles(subjectId: string, _opts?: IamAdapter.IReadOptions): Promise<TRole[]> {\n    // Unscoped (global) roles only. Scoped assignments are surfaced\n    // separately via getSubjectScopedRoles.\n    const rows = await this._prisma.accessAssignment.findMany({\n      where: { subjectId, scope: null },\n    })\n    return [...new Set(rows.map((r) => r.roleId as TRole))]\n  }\n\n  /**\n   * Lists scoped role assignments for a subject.\n   *\n   * @param subjectId - Identifies the subject whose scoped roles are read.\n   * @param _opts - Ignored read options accepted for interface compatibility.\n   * @returns Array of `(role, scope)` pairs for scoped assignments only.\n   */\n  async getSubjectScopedRoles(\n    subjectId: string,\n    _opts?: IamAdapter.IReadOptions,\n  ): Promise<IamRequest.IScopedRole<TRole, TScope>[]> {\n    const rows = await this._prisma.accessAssignment.findMany({\n      where: { subjectId },\n    })\n    return rows.filter((r) => r.scope != null).map((r) => ({ role: r.roleId as TRole, scope: r.scope as TScope }))\n  }\n\n  /**\n   * Grants a role to a subject, optionally restricted to a scope.\n   *\n   * @param subjectId - Identifies the subject receiving the role.\n   * @param roleId - Specifies the role being granted.\n   * @param scope - Optional scope binding the assignment.\n   * @returns Resolves once the row is inserted.\n   */\n  async assignRole(subjectId: string, roleId: TRole, scope?: TScope): Promise<void> {\n    await this._prisma.accessAssignment.create({\n      data: { subjectId, roleId, scope: scope ?? null },\n    })\n  }\n\n  /**\n   * Removes role assignments matching the given filters.\n   *\n   * @param subjectId - Identifies the subject losing the role.\n   * @param roleId - Specifies the role being revoked.\n   * @param scope - Optional scope filter to narrow the delete.\n   * @returns Resolves once the delete completes.\n   */\n  async revokeRole(subjectId: string, roleId: TRole, scope?: TScope): Promise<void> {\n    await this._prisma.accessAssignment.deleteMany({\n      where: { subjectId, roleId, ...(scope ? { scope } : {}) },\n    })\n  }\n\n  /**\n   * Fetches the attribute bag stored for a subject.\n   *\n   * @param subjectId - Identifies the subject whose attributes are read.\n   * @param _opts - Ignored read options accepted for interface compatibility.\n   * @returns The subject's attributes or `{}` when none are recorded.\n   */\n  async getSubjectAttributes(subjectId: string, _opts?: IamAdapter.IReadOptions): Promise<IamPrimitives.Attributes> {\n    const row = await this._prisma.accessSubjectAttr.findUnique({\n      where: { subjectId },\n    })\n    if (!row) return {}\n    const data = row.data\n    if (typeof data !== 'object' || data === null || Array.isArray(data)) {\n      throw new Error(\n        `[@gentleduck/iam:prisma] corrupted attributes for \"${subjectId}\" (expected JSON object, got ${\n          data === null ? 'null' : Array.isArray(data) ? 'array' : typeof data\n        })`,\n      )\n    }\n    // Reconstruct as a fresh Record to avoid sharing the Prisma-managed object.\n    const attrs: IamPrimitives.Attributes = {}\n    for (const [k, v] of Object.entries(data)) {\n      attrs[k] = v as IamPrimitives.AttributeValue\n    }\n    return attrs\n  }\n\n  /**\n   * Shallow-merges new attributes into the subject's existing bag via upsert.\n   *\n   * @param subjectId - Identifies the subject whose attributes are written.\n   * @param attrs - Provides the partial attribute patch to merge in.\n   * @returns Resolves once the upsert completes.\n   */\n  async setSubjectAttributes(subjectId: string, attrs: IamPrimitives.Attributes): Promise<void> {\n    // Recover from corrupt existing data instead of locking the operator out.\n    let existing: IamPrimitives.Attributes\n    try {\n      existing = await this.getSubjectAttributes(subjectId)\n    } catch {\n      existing = {}\n    }\n    const merged = { ...existing, ...attrs }\n    await this._prisma.accessSubjectAttr.upsert({\n      where: { subjectId },\n      create: { subjectId, data: merged },\n      update: { data: merged },\n    })\n  }\n}\n\n/** Converts a {@link IamPrisma.IPolicyRow} database row into a {@link AccessControl.IPolicy} domain object. */\nfunction toPolicy(row: IamPrisma.IPolicyRow): AccessControl.IPolicy {\n  return {\n    id: row.id,\n    name: row.name,\n    description: row.description ?? undefined,\n    version: row.version,\n    algorithm: row.algorithm as AccessControl.IPolicy['algorithm'],\n    rules: row.rules as AccessControl.IPolicy['rules'],\n    targets: (row.targets as AccessControl.IPolicy['targets']) ?? undefined,\n  }\n}\n\n/** Converts a {@link AccessControl.IPolicy} domain object into a flat record suitable for Prisma create/update. */\nfunction fromPolicy(p: AccessControl.IPolicy): Record<string, unknown> {\n  return {\n    id: p.id,\n    name: p.name,\n    description: p.description ?? null,\n    version: p.version ?? 1,\n    algorithm: p.algorithm,\n    rules: p.rules,\n    targets: p.targets ?? null,\n  }\n}\n\n/** Converts a {@link IamPrisma.IRoleRow} database row into a {@link AccessControl.IRole} domain object. */\nfunction toRole(row: IamPrisma.IRoleRow): AccessControl.IRole {\n  return {\n    id: row.id,\n    name: row.name,\n    description: row.description ?? undefined,\n    permissions: row.permissions as AccessControl.IRole['permissions'],\n    inherits: row.inherits ?? [],\n    scope: row.scope ?? undefined,\n    metadata: (row.metadata as AccessControl.IRole['metadata']) ?? undefined,\n  }\n}\n\n/** Converts a {@link AccessControl.IRole} domain object into a flat record suitable for Prisma create/update. */\nfunction fromRole(r: AccessControl.IRole): Record<string, unknown> {\n  return {\n    id: r.id,\n    name: r.name,\n    description: r.description ?? null,\n    permissions: r.permissions,\n    inherits: r.inherits ?? [],\n    scope: r.scope ?? null,\n    metadata: r.metadata ?? null,\n  }\n}\n"],"mappings":";;;;;;;;;;;;;AA0FA,IAAa,mBAAb,MAOA;CACE,AAAQ;;;;;;CAOR,YAAY,QAAiB;EAC3B,KAAK,UAAU;CACjB;;;;;;;CAQA,MAAM,aAAa,OAA8F;EAC/G,MAAM,OAAO,MAAM,KAAK,QAAQ,aAAa,SAAS;EACtD,MAAM,MAA0D,CAAC;EACjE,KAAK,MAAM,OAAO,MAAM;GACtB,MAAM,SAASA,gCAA0C,SAAS,GAAG,CAAC;GACtE,IAAI,WAAW,MAAM,IAAI,KAAK,MAAM;EACtC;EACA,OAAO;CACT;;;;;;;;CASA,MAAM,UACJ,IACA,OACkE;EAClE,MAAM,MAAM,MAAM,KAAK,QAAQ,aAAa,WAAW,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;EACxE,OAAO,MAAMA,gCAA0C,SAAS,GAAG,CAAC,IAAI;CAC1E;;;;;;;CAQA,MAAM,WAAW,GAAoE;EACnF,MAAM,OAAO,WAAW,CAAC;EACzB,MAAM,KAAK,QAAQ,aAAa,OAAO;GACrC,OAAO,EAAE,IAAI,EAAE,GAAG;GAClB,QAAQ;GACR,QAAQ;EACV,CAAC;CACH;;;;;;;CAQA,MAAM,aAAa,IAA2B;EAC5C,MAAM,KAAK,QAAQ,aAAa,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;CAC1D;;;;;;;CAQA,MAAM,UAAU,OAAoG;EAClH,MAAM,OAAO,MAAM,KAAK,QAAQ,WAAW,SAAS;EACpD,MAAM,MAAgE,CAAC;EACvE,KAAK,MAAM,OAAO,MAAM;GACtB,MAAM,OAAOC,8BAAgD,OAAO,GAAG,CAAC;GACxE,IAAI,SAAS,MAAM,IAAI,KAAK,IAAI;EAClC;EACA,OAAO;CACT;;;;;;;;CASA,MAAM,QACJ,IACA,OACwE;EACxE,MAAM,MAAM,MAAM,KAAK,QAAQ,WAAW,WAAW,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;EACtE,OAAO,MAAMA,8BAAgD,OAAO,GAAG,CAAC,IAAI;CAC9E;;;;;;;CAQA,MAAM,SAAS,GAA0E;EACvF,MAAM,OAAO,SAAS,CAAC;EACvB,MAAM,KAAK,QAAQ,WAAW,OAAO;GACnC,OAAO,EAAE,IAAI,EAAE,GAAG;GAClB,QAAQ;GACR,QAAQ;EACV,CAAC;CACH;;;;;;;CAQA,MAAM,WAAW,IAA2B;EAC1C,MAAM,KAAK,QAAQ,WAAW,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;CACxD;;;;;;;;CASA,MAAM,gBAAgB,WAAmB,OAAmD;EAG1F,MAAM,OAAO,MAAM,KAAK,QAAQ,iBAAiB,SAAS,EACxD,OAAO;GAAE;GAAW,OAAO;EAAK,EAClC,CAAC;EACD,OAAO,CAAC,GAAG,IAAI,IAAI,KAAK,KAAK,MAAM,EAAE,MAAe,CAAC,CAAC;CACxD;;;;;;;;CASA,MAAM,sBACJ,WACA,OACkD;EAIlD,QAAO,MAHY,KAAK,QAAQ,iBAAiB,SAAS,EACxD,OAAO,EAAE,UAAU,EACrB,CAAC,EACU,CAAC,QAAQ,MAAM,EAAE,SAAS,IAAI,CAAC,CAAC,KAAK,OAAO;GAAE,MAAM,EAAE;GAAiB,OAAO,EAAE;EAAgB,EAAE;CAC/G;;;;;;;;;CAUA,MAAM,WAAW,WAAmB,QAAe,OAA+B;EAChF,MAAM,KAAK,QAAQ,iBAAiB,OAAO,EACzC,MAAM;GAAE;GAAW;GAAQ,OAAO,SAAS;EAAK,EAClD,CAAC;CACH;;;;;;;;;CAUA,MAAM,WAAW,WAAmB,QAAe,OAA+B;EAChF,MAAM,KAAK,QAAQ,iBAAiB,WAAW,EAC7C,OAAO;GAAE;GAAW;GAAQ,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;EAAG,EAC1D,CAAC;CACH;;;;;;;;CASA,MAAM,qBAAqB,WAAmB,OAAoE;EAChH,MAAM,MAAM,MAAM,KAAK,QAAQ,kBAAkB,WAAW,EAC1D,OAAO,EAAE,UAAU,EACrB,CAAC;EACD,IAAI,CAAC,KAAK,OAAO,CAAC;EAClB,MAAM,OAAO,IAAI;EACjB,IAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,MAAM,QAAQ,IAAI,GACjE,MAAM,IAAI,MACR,sDAAsD,UAAU,+BAC9D,SAAS,OAAO,SAAS,MAAM,QAAQ,IAAI,IAAI,UAAU,OAAO,KACjE,EACH;EAGF,MAAM,QAAkC,CAAC;EACzC,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,IAAI,GACtC,MAAM,KAAK;EAEb,OAAO;CACT;;;;;;;;CASA,MAAM,qBAAqB,WAAmB,OAAgD;EAE5F,IAAI;EACJ,IAAI;GACF,WAAW,MAAM,KAAK,qBAAqB,SAAS;EACtD,QAAQ;GACN,WAAW,CAAC;EACd;EACA,MAAM,SAAS;GAAE,GAAG;GAAU,GAAG;EAAM;EACvC,MAAM,KAAK,QAAQ,kBAAkB,OAAO;GAC1C,OAAO,EAAE,UAAU;GACnB,QAAQ;IAAE;IAAW,MAAM;GAAO;GAClC,QAAQ,EAAE,MAAM,OAAO;EACzB,CAAC;CACH;AACF;;AAGA,SAAS,SAAS,KAAkD;CAClE,OAAO;EACL,IAAI,IAAI;EACR,MAAM,IAAI;EACV,aAAa,IAAI,eAAe;EAChC,SAAS,IAAI;EACb,WAAW,IAAI;EACf,OAAO,IAAI;EACX,SAAU,IAAI,WAAgD;CAChE;AACF;;AAGA,SAAS,WAAW,GAAmD;CACrE,OAAO;EACL,IAAI,EAAE;EACN,MAAM,EAAE;EACR,aAAa,EAAE,eAAe;EAC9B,SAAS,EAAE,WAAW;EACtB,WAAW,EAAE;EACb,OAAO,EAAE;EACT,SAAS,EAAE,WAAW;CACxB;AACF;;AAGA,SAAS,OAAO,KAA8C;CAC5D,OAAO;EACL,IAAI,IAAI;EACR,MAAM,IAAI;EACV,aAAa,IAAI,eAAe;EAChC,aAAa,IAAI;EACjB,UAAU,IAAI,YAAY,CAAC;EAC3B,OAAO,IAAI,SAAS;EACpB,UAAW,IAAI,YAAgD;CACjE;AACF;;AAGA,SAAS,SAAS,GAAiD;CACjE,OAAO;EACL,IAAI,EAAE;EACN,MAAM,EAAE;EACR,aAAa,EAAE,eAAe;EAC9B,aAAa,EAAE;EACf,UAAU,EAAE,YAAY,CAAC;EACzB,OAAO,EAAE,SAAS;EAClB,UAAU,EAAE,YAAY;CAC1B;AACF"}