{"id":15579301,"url":"https://github.com/maicol07/spraypaintkt","last_synced_at":"2025-04-19T13:26:17.534Z","repository":{"id":225214019,"uuid":"765294358","full_name":"maicol07/spraypaintkt","owner":"maicol07","description":"Kotlin Multiplatform JSONAPI client inspired by Spraypaint.JS","archived":false,"fork":false,"pushed_at":"2025-01-02T22:08:40.000Z","size":915,"stargazers_count":8,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-29T08:12:24.018Z","etag":null,"topics":["json-api","jsonapi","koin","kotlin","kotlin-multiplatform","ktor","ktor-client"],"latest_commit_sha":null,"homepage":"https://maicol07.github.io/spraypaintkt/","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/maicol07.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-02-29T16:39:32.000Z","updated_at":"2025-02-09T18:44:23.000Z","dependencies_parsed_at":"2024-03-08T15:28:55.143Z","dependency_job_id":"e66f9a75-5948-46aa-b4a7-b3d03e1f0140","html_url":"https://github.com/maicol07/spraypaintkt","commit_stats":{"total_commits":127,"total_committers":1,"mean_commits":127.0,"dds":0.0,"last_synced_commit":"5bc1ff8e3fcaa512ceb110c0c18757a4e1a47203"},"previous_names":["maicol07/spraypaintkt"],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maicol07%2Fspraypaintkt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maicol07%2Fspraypaintkt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maicol07%2Fspraypaintkt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maicol07%2Fspraypaintkt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maicol07","download_url":"https://codeload.github.com/maicol07/spraypaintkt/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249702854,"owners_count":21312767,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["json-api","jsonapi","koin","kotlin","kotlin-multiplatform","ktor","ktor-client"],"created_at":"2024-10-02T19:14:31.309Z","updated_at":"2025-04-19T13:26:17.489Z","avatar_url":"https://github.com/maicol07.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Spraypaint.kt\n\n\u003e Inspired by [Spraypaint.JS](https://github.com/graphiti-api/spraypaint.js)\n\n\u003e [!NOTE]\n\u003e This README is meant for Spraypaint.kt 2.0.0 and above. For the previous version, see the [1.0.0 tag](https://github.com/maicol07/spraypaintkt/tree/1.0.0).\n\nA Kotlin library for interacting with JSONAPI-compliant APIs.\n\n# Installation\n\n## Android/Jvm only\nAdd the following to your `build.gradle.kts` file:\n```kotlin\nplugins {\n    id(\"com.google.devtools.ksp\") version \"2.0.0-1.0.22\"\n}\n\ndependencies {\n    implementation(\"it.maicol07.spraypaintkt:core:$latest_version\")\n    implementation(\"it.maicol07.spraypaintkt:annotation:$latest_version\")\n    ksp(\"it.maicol07.spraypaintkt:processor:$latest_version\")\n}\n```\n\n## Multiplatform\nAdd the following to your `build.gradle.kts` file:\n```kotlin\nplugins {\n    id(\"com.google.devtools.ksp\") version \"2.0.0-1.0.22\"\n}\n\nkotlin {\n    sourceSets {\n        commonMain.dependencies {\n            implementation(\"it.maicol07.spraypaintkt:core:$latest_version\")\n            implementation(\"it.maicol07.spraypaintkt:annotation:$latest_version\")\n        }\n    }\n}\n\ndependencies {\n    add(\"kspCommonMainMetadata\", \"it.maicol07.spraypaintkt:processor:$latest_version\")\n}\n\n// Workaround for KSP only in Common Main.\n// https://github.com/google/ksp/issues/567\ntasks.withType\u003corg.jetbrains.kotlin.gradle.dsl.KotlinCompile\u003c*\u003e\u003e().all {\n    if (name != \"kspCommonMainKotlinMetadata\") {\n        dependsOn(\"kspCommonMainKotlinMetadata\")\n    }\n}\n\nkotlin.sourceSets.commonMain {\n    kotlin.srcDir(\"build/generated/ksp/metadata/commonMain/kotlin\")\n}\n```\n\n## Snapshots\nYou can find snapshots on [Github Packages](https://github.com/maicol07?tab=packages\u0026repo_name=spraypaintkt).\nTo use them, you need to add the following to your `settings.gradle.kts` file:\n```kotlin\ndependencyResolutionManagement {\n    repositories {\n        maven {\n            url = uri(\"https://maven.pkg.github.com/maicol07/spraypaintkt\")\n            credentials {\n                username = project.findProperty(\"ghpr.user\") as String? ?: System.getenv(\"USERNAME\")\n                password = project.findProperty(\"ghpr.key\") as String? ?: System.getenv(\"TOKEN\")\n            }\n        }\n    }\n}\n```\nThen, you have to add your username and a personal access token to your `local.properties` file:\n```properties\nghpr.user=USERNAME\nghpr.key=TOKEN\n```\n\u003e [!NOTE]\n\u003e More info can be found [here](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-gradle-registry#using-a-published-package)\n\n# Usage\n## Configuration\nCreate a configuration object that implements the `JsonApiConfig` interface and mark it with the `@DefaultInstance` annotation\n(this way the default instance will be used for every resource when not specified).\nThis object will contain the base URL of the API, the pagination strategy, and the HTTP client to use.\nYou can use any HTTP client you want, as long as you wrap it in a `HttpClient` implementation.\nThe library provides a Ktor integration (see [Ktor Integration](#ktor))\n\nExample:\n```kotlin\n@DefaultInstance\ndata object AppJsonApiConfig: JsonApiConfig {\n    override val baseUrl: String = \"https://safrs.onrender.com/api\"\n    override val paginationStrategy: PaginationStrategy = PaginationStrategy.OFFSET_BASED\n    override val httpClient: HttpClient = KtorHttpClient()\n}\n```\n\n## Defining resources\nYou can define your resources by creating an interface or (abstract) class that ends with `Schema` and annotating it with the `@ResourceSchema` annotation.\nThe annotation requires the `resourceType` and `endpoint` properties to be set, but you can also set the configuration object to use (if not the default one).\n```kotlin\n@ResourceSchema(resourceType = \"Book\", endpoint = \"Books\")\ninterface BookSchema {}\n\n// Or\n@ResourceSchema(resourceType = \"Book\", endpoint = \"Books\")\nabstract class BookSchema {}\n```\n\n### Adding attributes\nYou can add attributes to your resource schema by prepending the `@Attr` annotation to an interface (or abstract class) property:\n```kotlin\n@ResourceSchema(resourceType = \"Book\", endpoint = \"Books\")\ninterface BookSchema {\n    @Attr val title: String\n}\n\n// Or\n\n@ResourceSchema(resourceType = \"Book\", endpoint = \"Books\")\nabstract class BookSchema {\n    @Attr abstract val title: String\n}\n```\n\nIf you wish to use a different property name than the one returned by the API, you can set the attribute name returned by the API in the `@Attr` annotation:\n```kotlin\n@ResourceSchema(resourceType = \"Book\", endpoint = \"Books\")\ninterface BookSchema {\n    @Attr(\"my_title\") val title: String\n}\n```\n\nBy default, the library will convert property names to snake_case when searching for the attribute in the JSONAPI response.\nYou can toggle this behavior by setting the `autoTransform` property in the `@ResourceSchema` annotation.\n```kotlin\n@ResourceSchema(resourceType = \"Book\", endpoint = \"Books\")\ninterface BookSchema {\n    @Attr(autoTransform = true) val title: String\n}\n```\n\nYou can set a default value for the attribute by setting a getter for the interface property or an initializer for the abstract class property:\n```kotlin\n@ResourceSchema(resourceType = \"Book\", endpoint = \"Books\")\ninterface BookSchema {\n    @Attr val title: String\n        get() = \"Default title\"\n}\n\n// Or\n\n@ResourceSchema(resourceType = \"Book\", endpoint = \"Books\")\nabstract class BookSchema {\n    @Attr val title: String = \"Default title\"\n}\n```\n\n\u003e [!IMPORTANT]\n\u003e An interface doesn't have a backing field for the property, so it won't be stored in the property but the getter will be called every time you access the property.\n\n### Adding relationships\nYou can add relationships to your model by prepending the `@Relation` annotation to an interface (or abstract class) property.\n```kotlin\n@ResourceSchema(resourceType = \"Book\", endpoint = \"Books\")\ninterface BookSchema {\n    @Attr val title: String\n    \n    @Relation val reviews: List\u003cReviewSchema\u003e\n    @Relation val publisher: PublisherSchema\n    @Relation val author: PersonSchema\n    @Relation val reader: PersonSchema\n}\n```\nTo-One relationships are automatically identified with the correct type, while To-Many relationships are identified when the type is a `List` or `MutableList` (with the model type in generics).\n\u003e [!IMPORTANT]\n\u003e It makes no difference if you use `List` or `MutableList`, the library will always return a `MutableList` if the annotation `mutable` property is set to `true` (default is `true`).\n\nIf you wish to use a different property name than the one returned by the API, you can set the relationship name returned by the API in the annotation:\n```kotlin\n@ResourceSchema(resourceType = \"Book\", endpoint = \"Books\")\n@ToManyRelationship(\"reviews\", ReviewSchema::class, propertyName = \"my_reviews\")\n@ToOneRelationship(\"publisher\", PublisherSchema::class)\n@ToOneRelationship(\"author\", PersonSchema::class)\n@ToOneRelationship(\"reader\", PersonSchema::class)\ninterface BookSchema {\n    @Attr val title: String\n    \n    @Relation(\"field_reviews\") val reviews: List\u003cReviewSchema\u003e\n    @Relation val publisher: PublisherSchema\n    @Relation val author: PersonSchema\n    @Relation val reader: PersonSchema\n}\n```\n\nYou can set a default value for the relationship by setting a getter for the interface property or an initializer for the abstract class property:\n```kotlin\n@ResourceSchema(resourceType = \"Book\", endpoint = \"Books\")\ninterface BookSchema {\n    @Attr val title: String\n    \n    @Relation val reviews: List\u003cReviewSchema\u003e\n    @Relation val publisher: PublisherSchema\n    @Relation val author: PersonSchema\n    @Relation val reader: PersonSchema?\n        get() = null\n}\n\n// Or\n\n@ResourceSchema(resourceType = \"Book\", endpoint = \"Books\")\nabstract class BookSchema {\n    @Attr abstract val title: String\n    \n    @Relation abstract val reviews: List\u003cReviewSchema\u003e\n    @Relation abstract val publisher: PublisherSchema\n    @Relation abstract val author: PersonSchema\n    @Relation open val reader: PersonSchema? = null\n}\n```\n\n\u003e [!WARNING]\n\u003e Always set the relation property with a default value to `open` if you're using an abstract class, otherwise a Kotlin inheritance error will be thrown.\n\n\u003e [!TIP]\n\u003e If you want a relationship to be nullable (it can assume the value `null`), you can set the type nullable by adding a `?` after the type.\n\n### Registering resources\nTo be able to resolve the relationships, you need to register the resources. When you use the generated resources class, these are automatically registered.\nBut when you aren't using them directly, such as when deserializing instances, you need to register them manually by calling the `registerResources` method on the `ResourcesRegistry` object.\n```kotlin\nResourcesRegistry.registerResources()\n```\n\n## Generating resources\nFrom now on, resources are automatically generated during the build process when schemas have been changed and you should use these classes to interact with the API.\nThese classes implement the resource schema interfaces you defined and they are named like your schema interface without the `Schema` suffix\nand they have the same package as the schema interface.\n\n## Querying\n```kotlin\n// Find the book with ID = 1\nval response = Book.find(1)\nval user = response.data\n\n// Fetch all the books\nval response = Book.all()\nval users = response.data\n\n// Fetch the first book in the response\nval response = Book.first()\nval user = response.data\n```\n\n### Filtering\nYou can filter the results using the `where` method:\n```kotlin\nval response = Book.where(\"title\", \"Journey to the Center of the Earth\").all()\nval user = response.data\n\n// You can also use the `where` method multiple times\nval response = Book.where(\"title\", \"Journey to the Center of the Earth\").where(\"email\", \"john@doe.com\").all()\nval user = response.data\n```\n\n### Sorting\nYou can sort the results using the `order` method:\n```kotlin\nval response = Book.order(\"title\", SortDirection.DESC).all()\nval user = response.data\n```\n\n### Pagination\nYou can paginate the results using the `per` and `page` methods:\n```kotlin\nval response = Book.per(10).page(2).all()\nval user = response.data\n```\n\nIf you're using offset-based pagination, you have to change the pagination strategy when creating the client and use the `offset` and `limit` methods:\n```kotlin\nval response = Book.offset(10).limit(50).all()\nval user = response.data\n```\n\n### Including relationships\nYou can include the relationships using the `includes` method:\n```kotlin\nval response = Book.includes(\"reviews\", \"author\").all()\nval user = response.data\n```\n\n\u003e [!CAUTION]\n\u003e Only if you include the relationships in the request, the library will be able to resolve them in your model.\n\n## Creating\nYou can create a new resource using its constructor, filling all the attributes and relationships and then calling the `save` method:\n\n\u003e [!TIP]\n\u003e Be sure to use the generated resource class, not the schema interface.\n\n```kotlin\nval book = Book()\nbook.title = \"Journey to the Center of the Earth\"\nval result = book.save()\nif (result) {\n    // The resource has been created\n}\n```\n\n## Updating\nYou can update a resource by modifying the attributes and relationships and then calling the `save` method:\n\n```kotlin\nval book = Book.find(1).data\nbook.title = \"Harry Potter and the Philosopher's Stone\"\nval result = client.save(user)\nif (result) {\n    // The resource has been updated\n}\n```\n\n## Deleting\nTo delete a resource, you can use the `destroy` method:\n\n```kotlin\nval result = book.destroy()\nif (result) {\n    // The resource has been deleted\n}\n```\n\n# Exceptions\nThe library provides a `JsonApiException` class, which is thrown when the JSONAPI server returns an error.\nYou can catch it and handle it as you want:\n\n```kotlin\ntry {\n    val book = Book.find(1)\n} catch (e: JsonApiException) {\n    // Handle the exception\n    println(e.errors)\n}\n```\n\n# Integrations\n## Ktor\nThe library provides a Ktor integration, which allows you to use the `HttpClient` implementation provided by Ktor.\n\nAdd the following to the dependencies (or commonDependencies) block of your `build.gradle.kts` file:\n```kotlin\nimplementation(\"it.maicol07.spraypaintkt:ktor-integration:$latest_version\")\n```\n\nYou can now create a `Client` instance using the `KtorHttpClient`:\n\n```kotlin\n@DefaultInstance\ndata object AppJsonApiConfig: JsonApiConfig {\n    override val baseUrl: String = \"https://api.example.com\"\n    override val paginationStrategy: PaginationStrategy = PaginationStrategy.OFFSET_BASED\n    override val httpClient: HttpClient = KtorHttpClient()\n}\n```\n\n### Ktor Client Configuration\nYou can configure the Ktor client by passing a `HttpClientConfig` instance to the `KtorHttpClient` constructor:\n\n```kotlin\n@DefaultInstance\ndata object AppJsonApiConfig: JsonApiConfig {\n    override val baseUrl: String = \"https://api.example.com\"\n    override val paginationStrategy: PaginationStrategy = PaginationStrategy.OFFSET_BASED\n    override val httpClient: HttpClient = KtorHttpClient {\n        install(JsonFeature) {\n            serializer = KotlinxSerializer()\n        }\n    }\n}\n```\n\n### Custom Ktor Client\nYou can use a custom full Ktor client by passing it to the `KtorHttpClient` constructor:\n\n```kotlin\n\n@DefaultInstance\ndata object AppJsonApiConfig: JsonApiConfig {\n    override val baseUrl: String = \"https://api.example.com\"\n    override val paginationStrategy: PaginationStrategy = PaginationStrategy.OFFSET_BASED\n    override val httpClient: HttpClient = KtorHttpClient(\n        client = HttpClient {\n            install(JsonFeature) {\n                serializer = KotlinxSerializer()\n            }\n        }\n    )\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaicol07%2Fspraypaintkt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaicol07%2Fspraypaintkt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaicol07%2Fspraypaintkt/lists"}