{"version":3,"file":"index.cjs","names":[],"sources":["../../../src/adapters/memory/index.ts"],"sourcesContent":["import type { AccessControl, IamAdapter, IamPrimitives, IamRequest } from '../../core/types'\n\nexport namespace IamMemory {\n  /**\n   * Describes initial seed data for {@link IamMemoryAdapter}.\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   */\n  export interface IInit<\n    TAction extends string = string,\n    TResource extends string = string,\n    TRole extends string = string,\n    TScope extends string = string,\n  > {\n    /** Seeds the adapter with these policies on construction. */\n    policies?: AccessControl.IPolicy<TAction, TResource, TRole>[]\n    /** Seeds the adapter with these roles on construction. */\n    roles?: AccessControl.IRole<TAction, TResource, TRole, TScope>[]\n    /** Maps subject IDs to their initial unscoped roles. */\n    assignments?: Record<string, TRole[]>\n    /** Maps subject IDs to their initial attribute bag. */\n    attributes?: Record<string, IamPrimitives.Attributes>\n  }\n}\n\n/**\n * In-memory {@link IamAdapter.IAdapter} backed by `Map` storage; tests + prototypes only.\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 */\nexport class IamMemoryAdapter<\n  TAction extends string = string,\n  TResource extends string = string,\n  TRole extends string = string,\n  TScope extends string = string,\n> implements IamAdapter.IAdapter<TAction, TResource, TRole, TScope>\n{\n  private _policies = new Map<string, AccessControl.IPolicy<TAction, TResource, TRole>>()\n  private _roles = new Map<string, AccessControl.IRole<TAction, TResource, TRole, TScope>>()\n  private _assignments = new Map<string, Array<{ role: TRole; scope?: TScope }>>()\n  private _attributes = new Map<string, IamPrimitives.Attributes>()\n\n  /**\n   * Creates a new in-memory adapter, optionally seeded with initial data.\n   *\n   * @param init - Provides optional seed policies, roles, assignments, and attributes.\n   */\n  constructor(init?: IamMemory.IInit<TAction, TResource, TRole, TScope>) {\n    for (const p of init?.policies ?? []) this._policies.set(p.id, p)\n    for (const r of init?.roles ?? []) this._roles.set(r.id, r)\n    for (const [uid, roles] of Object.entries(init?.assignments ?? {})) {\n      this._assignments.set(\n        uid,\n        roles.map((r) => ({ role: r })),\n      )\n    }\n    for (const [uid, attrs] of Object.entries(init?.attributes ?? {})) {\n      this._attributes.set(uid, attrs)\n    }\n  }\n\n  /**\n   * Lists every stored policy.\n   *\n   * @param _opts - Ignored read options accepted for interface compatibility.\n   * @returns All policies currently held in memory.\n   */\n  async listPolicies(_opts?: IamAdapter.IReadOptions): Promise<AccessControl.IPolicy<TAction, TResource, TRole>[]> {\n    return [...this._policies.values()]\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    return this._policies.get(id) ?? null\n  }\n\n  /**\n   * Stores or overwrites a policy keyed by its ID.\n   *\n   * @param p - Provides the policy to persist.\n   * @returns Resolves once the write completes.\n   */\n  async savePolicy(p: AccessControl.IPolicy<TAction, TResource, TRole>): Promise<void> {\n    this._policies.set(p.id, p)\n  }\n\n  /**\n   * Removes a policy by ID.\n   *\n   * @param id - Identifies the policy to delete.\n   * @returns Resolves once the entry is removed (no-op when absent).\n   */\n  async deletePolicy(id: string): Promise<void> {\n    this._policies.delete(id)\n  }\n\n  /**\n   * Lists every stored role.\n   *\n   * @param _opts - Ignored read options accepted for interface compatibility.\n   * @returns All roles currently held in memory.\n   */\n  async listRoles(_opts?: IamAdapter.IReadOptions): Promise<AccessControl.IRole<TAction, TResource, TRole, TScope>[]> {\n    return [...this._roles.values()]\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    return this._roles.get(id) ?? null\n  }\n\n  /**\n   * Stores or overwrites a role keyed by its ID.\n   *\n   * @param r - Provides the role to persist.\n   * @returns Resolves once the write completes.\n   */\n  async saveRole(r: AccessControl.IRole<TAction, TResource, TRole, TScope>): Promise<void> {\n    this._roles.set(r.id, r)\n  }\n\n  /**\n   * Removes a role by ID.\n   *\n   * @param id - Identifies the role to delete.\n   * @returns Resolves once the entry is removed (no-op when absent).\n   */\n  async deleteRole(id: string): Promise<void> {\n    this._roles.delete(id)\n  }\n\n  /**\n   * Lists unscoped (global) roles assigned to a subject.\n   *\n   * @param id - Identifies the subject whose global roles are read.\n   * @param _opts - Ignored read options accepted for interface compatibility.\n   * @returns Deduplicated array of role IDs without any scope binding.\n   */\n  async getSubjectRoles(id: string, _opts?: IamAdapter.IReadOptions): Promise<TRole[]> {\n    const entries = this._assignments.get(id) ?? []\n    return [...new Set(entries.filter((e) => e.scope == null).map((e) => e.role))]\n  }\n\n  /**\n   * Lists the scoped role assignments for a subject.\n   *\n   * @param id - 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    id: string,\n    _opts?: IamAdapter.IReadOptions,\n  ): Promise<IamRequest.IScopedRole<TRole, TScope>[]> {\n    const hasScope = (e: { role: TRole; scope?: TScope }): e is { role: TRole; scope: TScope } => e.scope != null\n    return (this._assignments.get(id) ?? []).filter(hasScope).map((e) => ({ role: e.role, scope: e.scope }))\n  }\n\n  /**\n   * Grants a role to a subject, optionally restricted to a scope.\n   *\n   * Duplicate `(role, scope)` pairs are silently ignored.\n   *\n   * @param id - 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 assignment is recorded.\n   */\n  async assignRole(id: string, roleId: TRole, scope?: TScope): Promise<void> {\n    let entries = this._assignments.get(id)\n    if (!entries) {\n      entries = []\n      this._assignments.set(id, entries)\n    }\n    if (!entries.some((e) => e.role === roleId && e.scope === scope)) {\n      entries.push({ role: roleId, scope })\n    }\n  }\n\n  /**\n   * Removes a role assignment from a subject.\n   *\n   * @param id - Identifies the subject losing the role.\n   * @param roleId - Specifies the role being revoked.\n   * @param scope - Optional scope to match; omit to revoke unscoped only.\n   * @returns Resolves once the assignment is removed.\n   */\n  async revokeRole(id: string, roleId: TRole, scope?: TScope): Promise<void> {\n    const entries = this._assignments.get(id)\n    if (!entries) return\n    // Omitting `scope` removes EVERY assignment for the role across all\n    // scopes - matches the redis/drizzle/prisma contract.\n    const filtered =\n      scope === undefined\n        ? entries.filter((e) => e.role !== roleId)\n        : entries.filter((e) => !(e.role === roleId && e.scope === scope))\n    this._assignments.set(id, filtered)\n  }\n\n  /**\n   * Fetches the attribute bag stored for a subject.\n   *\n   * @param id - 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(id: string, _opts?: IamAdapter.IReadOptions): Promise<IamPrimitives.Attributes> {\n    return this._attributes.get(id) ?? {}\n  }\n\n  /**\n   * Shallow-merges new attributes into the subject's existing bag.\n   *\n   * @param id - Identifies the subject whose attributes are written.\n   * @param attrs - Provides the partial attribute patch to merge in.\n   * @returns Resolves once the merge completes.\n   */\n  async setSubjectAttributes(id: string, attrs: IamPrimitives.Attributes): Promise<void> {\n    if (typeof attrs !== 'object' || attrs === null || Array.isArray(attrs)) {\n      const got = attrs === null ? 'null' : Array.isArray(attrs) ? 'array' : typeof attrs\n      throw new Error(`[@gentleduck/iam:memory] attributes for \"${id}\" must be a plain object (got ${got})`)\n    }\n    this._attributes.set(id, { ...(this._attributes.get(id) ?? {}), ...attrs })\n  }\n}\n"],"mappings":"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"}