{"_id":"@agilehead/permiso-client","name":"@agilehead/permiso-client","dist-tags":{"latest":"0.0.6"},"versions":{"0.0.6":{"name":"@agilehead/permiso-client","version":"0.0.6","description":"Client library for Permiso RBAC API","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","sideEffects":false,"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"build":"tsc","test":"mocha --exit","test:build":"tsc -p tsconfig.test.json --noEmit","test:integration":"mocha --exit --spec src/tests/index.test.ts","test:grep":"mocha --exit --grep","lint":"eslint src","lint:fix":"eslint src --fix"},"dependencies":{},"devDependencies":{"@codespin/permiso-test-utils":"file:../permiso-test-utils","@types/chai":"^5.2.2","@types/mocha":"^10.0.10","@types/node":"^22.10.2","@types/pg":"^8.11.10","chai":"^5.2.1","mocha":"^11.7.1","pg":"^8.16.3","tsx":"^4.19.4","typescript":"^5.7.3"},"keywords":["rbac","permissions","access-control","client","api"],"author":{"name":"Agilehead"},"license":"MIT","_id":"@agilehead/permiso-client@0.0.6","gitHead":"9cb0bbabcabff2adb839752a51f98d10fc58d644","_nodeVersion":"24.9.0","_npmVersion":"11.6.0","dist":{"integrity":"sha512-ESXBkvMMAw7bqU+mKGQN+yVz3qUytgaknWMoILuHT3+2uPmNrcmtg7sPS1g3MTrST2amTK5tAYt0zVUWZ+TvjA==","shasum":"1c275d4945a14e3eba5c62905833275c45705c04","tarball":"https://registry.npmjs.org/@agilehead/permiso-client/-/permiso-client-0.0.6.tgz","fileCount":46,"unpackedSize":172810,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIF9jRwTNjz+d8fyUl0CZjKzf7nFfDDCnRZ2w6LEolh2aAiEAnIvDiTuVDvY3mXHaBiHizydU/WZm8KcAiMPkFO4WYqo="}]},"_npmUser":{"name":"jeswin","email":"jeswinpk@agilehead.com"},"directories":{},"maintainers":[{"name":"jeswin","email":"jeswinpk@agilehead.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/permiso-client_0.0.6_1770811127838_0.7184302127107047"},"_hasShrinkwrap":false}},"time":{"created":"2026-02-11T11:58:47.720Z","0.0.6":"2026-02-11T11:58:47.992Z","modified":"2026-02-11T11:58:48.199Z"},"maintainers":[{"name":"jeswin","email":"jeswinpk@agilehead.com"}],"description":"Client library for Permiso RBAC API","keywords":["rbac","permissions","access-control","client","api"],"author":{"name":"Agilehead"},"license":"MIT","readme":"# @codespin/permiso-client\n\nA TypeScript client library for the Permiso RBAC (Role-Based Access Control) API. This package provides a simple, type-safe way to interact with Permiso without needing to write GraphQL queries.\n\n## Features\n\n- 🔒 **Type-safe** - Full TypeScript support with comprehensive type definitions\n- 🚀 **Zero GraphQL knowledge required** - Simple function calls instead of query strings\n- ⚡ **Lightweight** - Minimal dependencies, tree-shakeable\n- 🛡️ **Result types** - Explicit error handling with discriminated unions\n- 🔄 **Consistent API** - Uniform patterns across all operations\n- 📦 **Pagination support** - Built-in pagination for list operations\n- 🔍 **Property filtering** - Filter entities by custom properties\n- 🎯 **IDE friendly** - Full auto-completion and inline documentation\n\n## Installation\n\n```bash\nnpm install @codespin/permiso-client\n```\n\n## Quick Start\n\n```typescript\nimport {\n  createTenant,\n  createUser,\n  assignUserRole,\n  hasPermission,\n  PermisoConfig,\n} from \"@codespin/permiso-client\";\n\n// Configure the client\nconst config: PermisoConfig = {\n  endpoint: \"http://localhost:5001\",\n  apiKey: \"your-bearer-token\", // optional - will be sent as Bearer token\n  timeout: 30000, // optional, in milliseconds\n};\n\n// Create a tenant\nconst tenantResult = await createTenant(config, {\n  id: \"acme-corp\",\n  name: \"ACME Corporation\",\n  description: \"A sample tenant\",\n});\n\nif (tenantResult.success) {\n  console.log(\"Created tenant:\", tenantResult.data);\n}\n\n// Check if a user has permission\nconst hasPermResult = await hasPermission(config, {\n  tenantId: \"acme-corp\",\n  userId: \"john-doe\",\n  resourceId: \"/api/users/*\",\n  action: \"read\",\n});\n\nif (hasPermResult.success) {\n  console.log(\"Has permission:\", hasPermResult.data);\n}\n```\n\n## Configuration\n\n### Basic Configuration\n\n```typescript\nimport { PermisoConfig } from \"@codespin/permiso-client\";\n\nconst config: PermisoConfig = {\n  endpoint: \"http://localhost:5001\", // GraphQL endpoint URL\n  apiKey: \"your-bearer-token\", // Optional: Bearer token for authentication\n  timeout: 30000, // Optional: Request timeout in ms (default: 30000)\n  headers: {\n    // Optional: Additional headers\n    \"X-Custom-Header\": \"value\",\n  },\n};\n```\n\n### Environment-based Configuration\n\n```typescript\nconst config: PermisoConfig = {\n  endpoint: process.env.PERMISO_ENDPOINT || \"http://localhost:5001\",\n  apiKey: process.env.PERMISO_API_KEY,\n  timeout: parseInt(process.env.PERMISO_TIMEOUT || \"30000\"),\n};\n```\n\n## Error Handling\n\nAll functions return a `Result` type that explicitly handles success and failure cases:\n\n```typescript\nconst result = await createUser(config, {\n  id: \"john-doe\",\n  tenantId: \"acme-corp\",\n  identityProvider: \"google\",\n  identityProviderUserId: \"john@example.com\",\n});\n\nif (result.success) {\n  // Type-safe access to data\n  console.log(\"User created:\", result.data.id);\n} else {\n  // Type-safe access to error\n  console.error(\"Failed to create user:\", result.error.message);\n}\n```\n\n## API Reference\n\n### Tenants\n\n- `getTenant(config, id)` - Get a tenant by ID\n- `listTenants(config, options?)` - List tenants with optional filtering and pagination\n- `getTenantsByIds(config, ids)` - Get multiple tenants by IDs\n- `createTenant(config, input)` - Create a new tenant\n- `updateTenant(config, id, input)` - Update a tenant\n- `deleteTenant(config, id, safetyKey?)` - Delete a tenant\n- `getTenantProperty(config, tenantId, propertyName)` - Get a specific property\n- `setTenantProperty(config, tenantId, name, value, hidden?)` - Set a property\n- `deleteTenantProperty(config, tenantId, name)` - Delete a property\n\n### Users\n\n- `getUser(config, tenantId, userId)` - Get a user\n- `listUsers(config, tenantId, options?)` - List users with optional filtering and pagination\n- `getUsersByIds(config, tenantId, ids)` - Get multiple users by IDs\n- `getUsersByIdentity(config, identityProvider, identityProviderUserId)` - Find users by identity\n- `createUser(config, input)` - Create a new user\n- `updateUser(config, tenantId, userId, input)` - Update a user\n- `deleteUser(config, tenantId, userId)` - Delete a user\n- `getUserProperty(config, tenantId, userId, propertyName)` - Get a user property\n- `setUserProperty(config, tenantId, userId, name, value, hidden?)` - Set a user property\n- `deleteUserProperty(config, tenantId, userId, name)` - Delete a user property\n- `assignUserRole(config, tenantId, userId, roleId)` - Assign a role to a user\n- `unassignUserRole(config, tenantId, userId, roleId)` - Remove a role from a user\n\n### Roles\n\n- `getRole(config, tenantId, roleId)` - Get a role\n- `listRoles(config, tenantId, options?)` - List roles with optional filtering and pagination\n- `getRolesByIds(config, tenantId, ids)` - Get multiple roles by IDs\n- `createRole(config, input)` - Create a new role\n- `updateRole(config, tenantId, roleId, input)` - Update a role\n- `deleteRole(config, tenantId, roleId)` - Delete a role\n- `getRoleProperty(config, tenantId, roleId, propertyName)` - Get a role property\n- `setRoleProperty(config, tenantId, roleId, name, value, hidden?)` - Set a role property\n- `deleteRoleProperty(config, tenantId, roleId, name)` - Delete a role property\n\n### Resources\n\n- `getResource(config, tenantId, resourceId)` - Get a resource\n- `listResources(config, tenantId, options?)` - List resources with optional filtering and pagination\n- `getResourcesByIdPrefix(config, tenantId, idPrefix)` - Get resources by ID prefix\n- `createResource(config, input)` - Create a new resource\n- `updateResource(config, tenantId, resourceId, input)` - Update a resource\n- `deleteResource(config, tenantId, resourceId)` - Delete a resource\n\n### Permissions\n\n- `hasPermission(config, params)` - Check if a user has permission\n- `getUserPermissions(config, params)` - Get user permissions\n- `getRolePermissions(config, params)` - Get role permissions\n- `getEffectivePermissions(config, params)` - Get effective permissions for a user\n- `getEffectivePermissionsByPrefix(config, params)` - Get effective permissions by resource prefix\n- `grantUserPermission(config, input)` - Grant permission to a user\n- `revokeUserPermission(config, params)` - Revoke permission from a user\n- `grantRolePermission(config, input)` - Grant permission to a role\n- `revokeRolePermission(config, params)` - Revoke permission from a role\n\n## Pagination\n\nList operations support pagination through the `PaginationInput` type:\n\n```typescript\nconst result = await listUsers(config, \"acme-corp\", {\n  pagination: {\n    limit: 10,\n    offset: 20,\n    sortDirection: \"DESC\", // \"ASC\" or \"DESC\", defaults to \"ASC\"\n  },\n});\n\nif (result.success) {\n  console.log(\"Users:\", result.data.nodes);\n  console.log(\"Total count:\", result.data.totalCount);\n  console.log(\"Has next page:\", result.data.pageInfo.hasNextPage);\n}\n```\n\n## Filtering\n\nList operations support filtering by properties:\n\n```typescript\nconst result = await listUsers(config, \"acme-corp\", {\n  filter: {\n    properties: [\n      { name: \"department\", value: \"engineering\" },\n      { name: \"active\", value: true },\n    ],\n  },\n});\n```\n\n```typescript\n// Set a hidden property (e.g., for sensitive data)\nawait setUserProperty(\n  config,\n  \"acme-corp\",\n  \"john-doe\",\n  \"apiToken\",\n  \"secret-token-123\",\n  true, // hidden = true\n);\n```\n\n### Property Filtering\n\nFilter entities by their properties:\n\n```typescript\n// Set a property\nconst setPropResult = await setUserProperty(\n  config,\n  \"acme-corp\",\n  \"john-doe\",\n  \"preferences\",\n  { theme: \"dark\", language: \"en\" },\n  false, // not hidden\n);\n\n// Get a property\nconst getPropResult = await getUserProperty(\n  config,\n  \"acme-corp\",\n  \"john-doe\",\n  \"preferences\",\n);\n\nif (getPropResult.success && getPropResult.data) {\n  console.log(\"User preferences:\", getPropResult.data.value);\n}\n```\n\n## Advanced Usage\n\n### Batch Operations\n\nFor better performance when creating multiple entities:\n\n```typescript\n// Create multiple users efficiently\nconst users = [\n  {\n    id: \"user-1\",\n    tenantId: \"tenant-1\",\n    identityProvider: \"auth0\",\n    identityProviderUserId: \"auth0|123\",\n  },\n  {\n    id: \"user-2\",\n    tenantId: \"tenant-1\",\n    identityProvider: \"auth0\",\n    identityProviderUserId: \"auth0|456\",\n  },\n  {\n    id: \"user-3\",\n    tenantId: \"tenant-1\",\n    identityProvider: \"auth0\",\n    identityProviderUserId: \"auth0|789\",\n  },\n];\n\nconst results = await Promise.all(\n  users.map((user) => createUser(config, user)),\n);\n\nconst failed = results.filter((r) => !r.success);\nif (failed.length > 0) {\n  console.error(\"Some users failed to create:\", failed);\n}\n```\n\n### Permission Checking Patterns\n\n```typescript\n// Check single permission\nconst canRead = await hasPermission(config, {\n  tenantId: \"acme-corp\",\n  userId: \"john-doe\",\n  resourceId: \"/api/users/*\",\n  action: \"read\",\n});\n\n// Check multiple permissions\nconst permissions = await Promise.all([\n  hasPermission(config, {\n    tenantId,\n    userId,\n    resourceId: \"/api/users/*\",\n    action: \"read\",\n  }),\n  hasPermission(config, {\n    tenantId,\n    userId,\n    resourceId: \"/api/users/*\",\n    action: \"write\",\n  }),\n  hasPermission(config, {\n    tenantId,\n    userId,\n    resourceId: \"/api/billing/*\",\n    action: \"read\",\n  }),\n]);\n\nconst [canReadUsers, canWriteUsers, canReadBilling] = permissions.map(\n  (r) => r.success && r.data,\n);\n\n// Get all effective permissions for a user\nconst effectivePerms = await getEffectivePermissions(config, {\n  tenantId: \"acme-corp\",\n  userId: \"john-doe\",\n});\n\nif (effectivePerms.success) {\n  const groupedByResource = effectivePerms.data.reduce(\n    (acc, perm) => {\n      if (!acc[perm.resourceId]) acc[perm.resourceId] = [];\n      acc[perm.resourceId].push(perm.action);\n      return acc;\n    },\n    {} as Record<string, string[]>,\n  );\n\n  console.log(\"Permissions by resource:\", groupedByResource);\n}\n```\n\n### Resource Path Patterns\n\nPermiso uses Unix-like path patterns for resources:\n\n```typescript\n// Exact match\nconst resource1 = await createResource(config, {\n  id: \"/api/users\",\n  tenantId: \"acme-corp\",\n  description: \"User management API\",\n});\n\n// Wildcard match (matches any sub-path)\nconst resource2 = await createResource(config, {\n  id: \"/api/users/*\",\n  tenantId: \"acme-corp\",\n  description: \"All user endpoints\",\n});\n\n// Specific endpoint\nconst resource3 = await createResource(config, {\n  id: \"/api/users/profile\",\n  tenantId: \"acme-corp\",\n  description: \"User profile endpoint\",\n});\n\n// Hierarchical resources\nconst resource4 = await createResource(config, {\n  id: \"/api/billing/invoices/*\",\n  tenantId: \"acme-corp\",\n  description: \"Invoice management\",\n});\n```\n\n### Handling Pagination\n\n```typescript\n// Fetch all users page by page\nasync function getAllUsers(config: PermisoConfig, tenantId: string) {\n  const allUsers = [];\n  let offset = 0;\n  const limit = 50;\n\n  while (true) {\n    const result = await listUsers(config, tenantId, {\n      pagination: { limit, offset },\n    });\n\n    if (!result.success) {\n      throw new Error(`Failed to fetch users: ${result.error.message}`);\n    }\n\n    allUsers.push(...result.data.nodes);\n\n    if (!result.data.pageInfo.hasNextPage) {\n      break;\n    }\n\n    offset += limit;\n  }\n\n  return allUsers;\n}\n```\n\n## Types\n\nThe client exports all TypeScript types from the Permiso API:\n\n```typescript\nimport type {\n  // Core entities\n  Tenant,\n  User,\n  Role,\n  Resource,\n  Permission,\n  Property,\n\n  // Input types\n  CreateTenantInput,\n  CreateUserInput,\n  CreateRoleInput,\n  CreateResourceInput,\n  UpdateTenantInput,\n  UpdateUserInput,\n  UpdateRoleInput,\n\n  // Permission types\n  UserPermission,\n  RolePermission,\n  EffectivePermission,\n\n  // Utility types\n  PaginationInput,\n  Connection,\n  PageInfo,\n  Result,\n\n  // Configuration\n  PermisoConfig,\n} from \"@codespin/permiso-client\";\n```\n\n## Best Practices\n\n### 1. Configuration Management\n\nStore configuration in a central location:\n\n```typescript\n// config/permiso.ts\nexport const permisoConfig: PermisoConfig = {\n  endpoint: process.env.PERMISO_ENDPOINT!,\n  apiKey: process.env.PERMISO_API_KEY,\n};\n\n// Usage in other files\nimport { permisoConfig } from \"./config/permiso\";\nimport { createUser } from \"@codespin/permiso-client\";\n\nconst result = await createUser(permisoConfig, userData);\n```\n\n### 2. Error Handling Wrapper\n\nCreate a wrapper for consistent error handling:\n\n```typescript\nasync function executePermiso<T>(\n  operation: Promise<Result<T>>,\n  errorMessage: string,\n): Promise<T> {\n  const result = await operation;\n\n  if (!result.success) {\n    console.error(`${errorMessage}:`, result.error);\n    throw new Error(`${errorMessage}: ${result.error.message}`);\n  }\n\n  return result.data;\n}\n\n// Usage\nconst user = await executePermiso(\n  createUser(config, userData),\n  \"Failed to create user\",\n);\n```\n\n### 3. Type Guards\n\nUse type guards for property values:\n\n```typescript\ninterface UserPreferences {\n  theme: \"light\" | \"dark\";\n  language: string;\n  notifications: boolean;\n}\n\nfunction isUserPreferences(value: unknown): value is UserPreferences {\n  return (\n    typeof value === \"object\" &&\n    value !== null &&\n    \"theme\" in value &&\n    \"language\" in value &&\n    \"notifications\" in value\n  );\n}\n\n// Usage\nconst prefResult = await getUserProperty(config, tenantId, userId, \"preferences\");\nif (prefResult.success && prefResult.data) {\n  const value = prefResult.data.value;\n  if (isUserPreferences(value)) {\n    console.log(\"User theme:\", value.theme);\n  }\n}\n```\n\n### 4. Resource Naming Conventions\n\nFollow consistent patterns for resource IDs:\n\n```typescript\n// API endpoints\n\"/api/users\";\n\"/api/users/*\";\n\"/api/users/{id}\";\n\"/api/users/{id}/profile\";\n\n// Feature-based\n\"/features/billing\";\n\"/features/billing/*\";\n\"/features/reporting\";\n\n// Service-based\n\"/services/auth\";\n\"/services/notifications\";\n\"/services/analytics/*\";\n```\n\n## Troubleshooting\n\n### Connection Issues\n\n```typescript\n// Add retry logic for transient failures\nasync function withRetry<T>(\n  operation: () => Promise<Result<T>>,\n  maxRetries = 3,\n): Promise<Result<T>> {\n  for (let i = 0; i < maxRetries; i++) {\n    const result = await operation();\n\n    if (result.success || i === maxRetries - 1) {\n      return result;\n    }\n\n    // Check if error is retryable\n    if (\n      result.error.message.includes(\"ECONNREFUSED\") ||\n      result.error.message.includes(\"ETIMEDOUT\")\n    ) {\n      await new Promise((resolve) => setTimeout(resolve, 1000 * (i + 1)));\n      continue;\n    }\n\n    return result;\n  }\n\n  return { success: false, error: new Error(\"Max retries exceeded\") };\n}\n```\n\n### Debugging\n\nEnable debug logging:\n\n```typescript\n// Create a debugging wrapper\nfunction createDebugClient(config: PermisoConfig) {\n  return new Proxy(\n    {},\n    {\n      get(target, prop) {\n        const original = (await import(\"@codespin/permiso-client\"))[prop];\n        if (typeof original === \"function\") {\n          return async (...args) => {\n            console.log(`Calling ${String(prop)} with:`, args);\n            const result = await original(...args);\n            console.log(`Result:`, result);\n            return result;\n          };\n        }\n        return original;\n      },\n    },\n  );\n}\n```\n\n## Migration Guide\n\n### From Direct GraphQL\n\nIf migrating from direct GraphQL queries:\n\n```typescript\n// Before (GraphQL)\nconst query = `\n  mutation CreateUser($input: CreateUserInput!) {\n    createUser(input: $input) {\n      id\n      tenantId\n    }\n  }\n`;\nconst result = await graphqlClient.request(query, { input: userData });\n\n// After (Client)\nconst result = await createUser(config, userData);\nif (result.success) {\n  console.log(result.data.id, result.data.tenantId);\n}\n```\n\n## Testing\n\nThe client package includes comprehensive integration tests that verify all API operations against a real Permiso server.\n\n### Running Tests\n\n```bash\n# From the project root\nnpm run test:client\n\n# Run specific test suite\nnpm run test:client:grep -- \"Tenants\"\n```\n\n### Test Coverage\n\nThe test suite covers all API operations:\n\n- **Tenants** (12 tests): CRUD, properties, pagination\n- **Users** (11 tests): CRUD, role assignment, properties, search\n- **Roles** (13 tests): CRUD, hidden properties, filtering\n- **Resources** (13 tests): CRUD, wildcards, hierarchical paths\n- **Permissions** (15 tests): Grants, effective permissions, inheritance\n\n### Test Infrastructure\n\n- Uses separate test database (`permiso_client_test`)\n- Runs on port 5003 (isolated from main server)\n- Database cleaned between tests\n- Migrations run automatically\n\n## API Stability\n\nThis client follows semantic versioning. The API is stable and breaking changes will only be introduced in major versions.\n\n## Contributing\n\nContributions are welcome! Please see the main [Permiso repository](https://github.com/codespin-ai/permiso) for contribution guidelines.\n\n## License\n\nMIT © Codespin\n","readmeFilename":"README.md","_rev":"1-07fda1436730a1cf7c6d145f88037dbe"}