{"id":40836294,"url":"https://github.com/mpecan/upsert","last_synced_at":"2026-01-21T22:38:24.390Z","repository":{"id":283740749,"uuid":"952678854","full_name":"mpecan/upsert","owner":"mpecan","description":"Spring Data Extension for Upsert Management","archived":false,"fork":false,"pushed_at":"2025-12-18T14:13:52.000Z","size":489,"stargazers_count":5,"open_issues_count":12,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-19T23:46:17.059Z","etag":null,"topics":["mysql","postgresql","spring-boot","spring-data-jpa","upsert"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mpecan.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-03-21T17:28:53.000Z","updated_at":"2025-08-01T06:55:13.000Z","dependencies_parsed_at":"2025-07-16T22:56:19.518Z","dependency_job_id":"c9de5b29-53b3-45ef-8039-52936a64c9cc","html_url":"https://github.com/mpecan/upsert","commit_stats":null,"previous_names":["mpecan/upsert"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/mpecan/upsert","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mpecan%2Fupsert","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mpecan%2Fupsert/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mpecan%2Fupsert/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mpecan%2Fupsert/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mpecan","download_url":"https://codeload.github.com/mpecan/upsert/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mpecan%2Fupsert/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28645551,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-21T21:29:11.980Z","status":"ssl_error","status_checked_at":"2026-01-21T21:24:31.872Z","response_time":86,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["mysql","postgresql","spring-boot","spring-data-jpa","upsert"],"created_at":"2026-01-21T22:38:23.625Z","updated_at":"2026-01-21T22:38:24.385Z","avatar_url":"https://github.com/mpecan.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Upsert Repository\n\n[![Maven Central Version](https://img.shields.io/maven-central/v/io.github.mpecan/upsert)](https://central.sonatype.com/artifact/io.github.mpecan/upsert)\n\nA Spring Data JPA extension that provides upsert capabilities for repositories. This library simplifies the process of inserting or updating records in a database using Spring Data JPA.\n\n## Full disclosure\n\nThis project was built with the help of the following AI tools:\n\n- [JetBrains Junie](https://www.jetbrains.com/junie/)\n- [GitHub Copilot](https://copilot.github.com/)\n- [Claude-Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview)\n\nThat said: all of the code has been reviewed and refinished by me, and I have made sure that it is\nall correct and functional.\n\n## Features\n\n- Upsert a single entity or a list of entities\n- Support for custom ON clauses and ignored fields\n- Conditional upserts with comparison operators (\u003e, \u003e=, \u003c, \u003c=)\n- Compatible with Spring Data JPA repositories\n- Database-specific optimizations for MySQL and PostgreSQL\n- Automatic handling of generated keys\n- Batch operation support for improved performance\n- Support for @MappedSuperclass inheritance\n\n## What is Upsert?\n\n\"Upsert\" is a combination of \"update\" and \"insert\" - it's an operation that will:\n- Insert a new record if it doesn't exist\n- Update an existing record if it does exist\n\nThis is particularly useful when you don't know whether a record exists and want to ensure it's created or updated in a single operation.\n\nIt also generally performs better than separate insert and update operations, especially when\ndealing with large datasets. For data based comparisons please see\nthe [Performance Testing](PERFORMANCE-TESTING.md) document and\nthe [Performance Report](PERFORMANCE-REPORT.md).\n\n## Using the Library\n\nThis library is available on Maven Central. You can add it to your project using:\n\n### Gradle (Kotlin DSL)\n\n```kotlin\ndependencies {\n    implementation(\"io.github.mpecan:upsert:1.5.1\")\n}\n```\n\n### Gradle (Groovy DSL)\n\n```groovy\ndependencies {\n    implementation 'io.github.mpecan:upsert:1.5.1'\n}\n```\n\n### Maven\n\n```xml\n\n\u003cdependency\u003e\n  \u003cgroupId\u003eio.github.mpecan\u003c/groupId\u003e\n  \u003cartifactId\u003eupsert\u003c/artifactId\u003e\n  \u003cversion\u003e1.5.1\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## Database Support\n\nThis library supports the following databases:\n\n- **PostgreSQL**: Uses the `INSERT ... ON CONFLICT ... DO UPDATE` syntax (requires PostgreSQL 9.5+)\n- **MySQL**: Uses the `INSERT ... ON DUPLICATE KEY UPDATE` syntax\n\nFor detailed information about each implementation, see:\n- [MySQL Implementation Details](docs/mysql.md)\n- [PostgreSQL Implementation Details](docs/postgresql.md)\n\n## Usage\n\n### Basic Usage\n\nTo use the upsert capabilities, your repository interface should extend `UpsertRepository`:\n\n```kotlin\ninterface UserRepository : UpsertRepository\u003cUser, Long\u003e {\n    // Standard Spring Data JPA methods\n    fun findByUsername(username: String): User?\n}\n```\n\nThen you can use the `upsert` and `upsertAll` methods:\n\n```kotlin\n// Upsert a single entity\nval user = User(username = \"john\", email = \"john@example.com\")\nuserRepository.upsert(user)\n\n// Upsert multiple entities\nval users = listOf(\n    User(username = \"john\", email = \"john@example.com\"),\n    User(username = \"jane\", email = \"jane@example.com\")\n)\nuserRepository.upsertAll(users)\n```\n\n### Custom ON Clauses and Ignored Fields\n\nYou can also use custom ON clauses and ignored fields by defining methods in your repository interface with specific naming patterns:\n\n```kotlin\ninterface UserRepository : UpsertRepository\u003cUser, Long\u003e {\n    // Upsert using username as the ON clause\n    fun upsertOnUsername(user: User): Int\n\n    // Upsert using username as the ON clause and ignoring updatedAt field\n    fun upsertOnUsernameIgnoringUpdatedAt(user: User): Int\n\n    // Upsert all using username as the ON clause\n    fun upsertAllOnUsername(users: List\u003cUser\u003e): Int\n\n    // Upsert all using username as the ON clause and ignoring updatedAt field\n    fun upsertAllOnUsernameIgnoringUpdatedAt(users: List\u003cUser\u003e): Int\n\n    // Upsert using username and email as the ON clause\n    fun upsertOnUsernameAndEmail(user: User): Int\n\n    // Upsert using username and email as the ON clause and ignoring all fields\n    // This will only insert new rows and not update existing ones\n    fun upsertOnUsernameAndEmailIgnoringAllFields(user: User): Int\n}\n```\n\nThe method name is parsed to extract the following information:\n- `upsert` or `upsertAll`: Whether to upsert a single entity or a list of entities\n- `On\u003cFieldName\u003e`: The field(s) to use for the ON clause (e.g., `OnUsername`, `OnUsernameAndEmail`)\n- `Ignoring\u003cFieldName\u003e`: The field(s) to ignore during updates (e.g., `IgnoringUpdatedAt`)\n- `IgnoringAllFields`: Whether to ignore all fields during updates (only insert new rows)\n\n### Conditional Upserts\n\nSince version 1.3.0, the library supports conditional upserts using the `When` clause in method names. This allows you to specify conditions under which the update should occur, preventing updates when certain conditions are not met.\n\nYou can use comparison operators to check field values:\n- `More` (\u003e): Update only when the new value is greater than the existing value\n- `MoreOrEqual` (\u003e=): Update only when the new value is greater than or equal to the existing value\n- `Less` (\u003c): Update only when the new value is less than the existing value\n- `LessOrEqual` (\u003c=): Update only when the new value is less than or equal to the existing value\n\n```kotlin\ninterface UserRepository : UpsertRepository\u003cUser, Long\u003e {\n    // Update only if the new updatedAt is more recent than the existing one\n    fun upsertOnIdWhenUpdatedAtMore(user: User): Int\n    \n    // Update only if the new version is greater than or equal to the existing one\n    fun upsertOnIdWhenVersionMoreOrEqual(user: User): Int\n    \n    // Update only if the new price is less than the existing one\n    fun upsertOnIdWhenPriceLess(user: User): Int\n    \n    // Combine conditional with ignored fields\n    fun upsertOnIdWhenVersionMoreIgnoringCreatedAt(user: User): Int\n    \n    // Batch operations with conditions\n    fun upsertAllOnIdWhenUpdatedAtMore(users: List\u003cUser\u003e): Int\n}\n```\n\nThis is particularly useful for:\n- **Optimistic locking**: Update only if the version number is higher\n- **Time-based updates**: Update only with more recent data\n- **Price protection**: Prevent accidental price increases\n- **Concurrent update protection**: Avoid overwriting newer data with older data\n\n### @MappedSuperclass Support\n\nSince version 1.5.0, the library fully supports entities that inherit fields from classes annotated with `@MappedSuperclass`. This allows you to define common fields in a base class and have them properly recognized during upsert operations.\n\n```kotlin\n@MappedSuperclass\nabstract class BaseEntity(\n    @Column(name = \"created_at\")\n    open val createdAt: LocalDateTime = LocalDateTime.now(),\n    \n    @Column(name = \"updated_at\")\n    open val updatedAt: LocalDateTime = LocalDateTime.now(),\n    \n    @Column(name = \"version\")\n    open val version: Int = 1\n)\n\n@Entity\n@Table(name = \"users\", uniqueConstraints = [UniqueConstraint(columnNames = [\"username\"])])\nclass User(\n    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)\n    val id: Long? = null,\n    \n    @Column(unique = true)\n    val username: String,\n    \n    val email: String,\n    \n    // Inherited fields from BaseEntity\n    createdAt: LocalDateTime = LocalDateTime.now(),\n    updatedAt: LocalDateTime = LocalDateTime.now(),\n    version: Int = 1\n) : BaseEntity(createdAt, updatedAt, version)\n```\n\nThe library automatically discovers fields from parent classes annotated with `@MappedSuperclass`, allowing you to:\n- Define common audit fields (createdAt, updatedAt) in a base class\n- Implement versioning for optimistic locking\n- Share common fields across multiple entities\n- Use all upsert features with inherited fields\n\n## Configuration\n\nThe library is automatically configured when you include it in your Spring Boot application. No\nadditional configuration is required.\n\nSimply add the dependency to your project and create repositories that extend `UpsertRepository`:\n\n```kotlin\n@SpringBootApplication\n@EnableJpaRepositories(\n    basePackages = [\"com.example.repositories\"]\n)\nclass Application {\n    // ...\n}\n```\n\n### Legacy Configuration (Pre-1.1.0)\n\nIn older versions, you needed to explicitly specify the `UpsertRepositoryFactoryBean`:\n\n```kotlin\n@Configuration\n@EnableJpaRepositories(\n    repositoryFactoryBeanClass = UpsertRepositoryFactoryBean::class\n)\nclass AppConfig {\n    // ...\n}\n```\n\nThis is no longer necessary as the library now uses Spring Boot's auto-configuration mechanism.\n\n# Type Mapping System\n\nThe type mapping system provides a centralized, extensible way to handle Java/Kotlin to SQL type\nconversions in the upsert library.\n\n## Overview\n\nThe system consists of:\n\n1. `TypeMapper` interface - Defines how Java/Kotlin types are mapped to SQL types\n2. `TypeMapperRegistry` - Central registry for type mappers\n3. `DefaultTypeMapper` - Handles common types\n\n## Usage\n\n### Default Behavior\n\nThe library comes with support for common Java/Kotlin types out of the box. You don't need to do\nanything to use these mappings.\n\n### Registering Custom Type Mappers\n\nTo add support for custom types, create a custom TypeMapper implementation and register it as a\nSpring bean:\n\n```kotlin\n@Component\nclass MyCustomTypeMapper : TypeMapper {\n    override fun canHandle(field: Field): Boolean {\n        return field.type == MyJsonType::class.java\n    }\n\n    override fun canHandleValue(value: Any?): Boolean {\n        return value is MyJsonType\n    }\n\n    override fun convertToJdbcValue(value: Any?): Any? {\n        if (value is MyJsonType) {\n            return objectMapper.writeValueAsString(value)\n        }\n        return value\n    }\n}\n```\n\n### Creating a Custom Type Mapper for a Library\n\nIf you're creating a library that extends the upsert library with additional type support:\n\n```kotlin\n// In your library's auto-configuration class\n@Configuration\nclass MyLibraryConfiguration {\n    @Bean\n    fun myCustomTypeMapper(): TypeMapper {\n        return object : TypeMapper {\n            override fun canHandle(field: Field): Boolean {\n                return field.type == MyCustomType::class.java\n            }\n\n            override fun canHandleValue(value: Any?): Boolean {\n                return value is MyCustomType\n            }\n\n            override fun convertToJdbcValue(value: Any?): Any? {\n                if (value is MyCustomType) {\n                    // Convert MyCustomType to a JDBC-compatible value\n                    return convertMyType(value)\n                }\n                return value\n            }\n        }\n    }\n}\n```\n\n# JSON Mapping in Upsert Library\n\nThis document explains how to use the JSON mapping capabilities in your application.\n\n## Including the Dependencies\n\nTo use the JSON mapping capabilities, you need to include at least one JSON library in your project.\n\n### Gradle\n\nAdd one of the following dependencies to your `build.gradle` or `build.gradle.kts`:\n\n```kotlin\n// Option 1: Jackson (preferred)\nimplementation(\"com.fasterxml.jackson.core:jackson-databind:2.15.2\")\nimplementation(\"com.fasterxml.jackson.module:jackson-module-kotlin:2.15.2\") // If using Kotlin\n\n// Option 2: Gson\nimplementation(\"com.google.code.gson:gson:2.10.1\")\n\n// Option 3: JSON-B\nimplementation(\"jakarta.json.bind:jakarta.json.bind-api:3.0.0\")\nimplementation(\"org.eclipse:yasson:3.0.3\") // JSON-B implementation\n```\n\n### Maven\n\nAdd one of the following dependencies to your `pom.xml`:\n\n```xml\n\u003c!-- Option 1: Jackson (preferred) --\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.fasterxml.jackson.core\u003c/groupId\u003e\n  \u003cartifactId\u003ejackson-databind\u003c/artifactId\u003e\n  \u003cversion\u003e1.5.1\u003c/version\u003e\n\u003c/dependency\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.fasterxml.jackson.module\u003c/groupId\u003e\n  \u003cartifactId\u003ejackson-module-kotlin\u003c/artifactId\u003e\n  \u003cversion\u003e1.5.1\u003c/version\u003e\n\u003c/dependency\u003e\n\n\u003c!-- Option 2: Gson --\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.google.code.gson\u003c/groupId\u003e\n  \u003cartifactId\u003egson\u003c/artifactId\u003e\n  \u003cversion\u003e1.5.1\u003c/version\u003e\n\u003c/dependency\u003e\n\n\u003c!-- Option 3: JSON-B --\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ejakarta.json.bind\u003c/groupId\u003e\n  \u003cartifactId\u003ejakarta.json.bind-api\u003c/artifactId\u003e\n  \u003cversion\u003e1.5.1\u003c/version\u003e\n\u003c/dependency\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003eorg.eclipse\u003c/groupId\u003e\n  \u003cartifactId\u003eyasson\u003c/artifactId\u003e\n  \u003cversion\u003e1.5.1\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## Using JSON Mapping\n\nOnce you've included a JSON library, the library will automatically configure the appropriate JSON\nmapper. You can then use JSON mapping in your entity classes:\n\n```kotlin\n@Entity\n@Table(name = \"product\")\ndata class Product(\n    @Id\n    val id: Long,\n\n    // Option 1: Explicit JSON column definition\n    @Column(columnDefinition = \"jsonb\") // or \"json\"\n    val attributes: Map\u003cString, String\u003e,\n\n    // Option 2: Automatic detection of common JSON types\n    val tags: List\u003cString\u003e,\n\n    // Option 3: Custom classes\n    val metadata: ProductMetadata\n)\n\ndata class ProductMetadata(\n    val manufacturer: String,\n    val countryOfOrigin: String\n)\n```\n\n## Testing\n\nFor testing, it's recommended to include Jackson in your test dependencies:\n\n```kotlin\n// Gradle\ntestImplementation(\"com.fasterxml.jackson.core:jackson-databind:2.15.2\")\ntestImplementation(\"com.fasterxml.jackson.module:jackson-module-kotlin:2.15.2\")\n```\n\n```xml\n\u003c!-- Maven --\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.fasterxml.jackson.core\u003c/groupId\u003e\n  \u003cartifactId\u003ejackson-databind\u003c/artifactId\u003e\n  \u003cversion\u003e1.5.1\u003c/version\u003e\n  \u003cscope\u003etest\u003c/scope\u003e\n\u003c/dependency\u003e\n\u003cdependency\u003e\n\u003cgroupId\u003ecom.fasterxml.jackson.module\u003c/groupId\u003e\n\u003cartifactId\u003ejackson-module-kotlin\u003c/artifactId\u003e\n\u003cversion\u003e1.5.1\u003c/version\u003e\n\u003cscope\u003etest\u003c/scope\u003e\n\u003c/dependency\u003e\n```\n\n## Library Priority\n\nThe library automatically selects a JSON mapper in the following order:\n\n1. Jackson\n2. Gson\n3. JSON-B\n\nIf multiple libraries are present, the highest priority one will be used.\n\n## Custom JSON Mappers\n\nIf you need custom JSON serialization, you can provide your own `JsonTypeMapper` implementation:\n\n```kotlin\n@Component\n@Primary // To override the default mapper\nclass MyCustomJsonTypeMapper : AbstractJsonTypeMapper() {\n    override fun toJson(value: Any): String {\n        // Your custom JSON serialization logic here\n        return \"...\"\n    }\n}\n```\n\n## How It Works\n\nWhen the library needs to determine a SQL type for a field or value:\n\n1. It asks the `TypeMapperRegistry` for the appropriate type mapper\n2. The registry checks all registered mappers to find one that can handle the type\n3. The mapper determines the SQL type and any necessary value conversion\n\nFor JPA-annotated fields with `@Convert` annotations, the system:\n\n1. Detects the converter class\n2. Determines the target SQL type based on the converter's output type\n3. Uses the converter to transform values when needed\n\n## Extension Points\n\nYou can extend the type mapping system in several ways:\n\n1. Create a custom `TypeMapper` implementation and register it as a Spring bean\n2. Override the default mapper by creating a bean with higher precedence\n3. Create a library with auto-configuration that provides additional type mappers\n\n## Best Practices\n\n1. Use Spring's dependency injection to register type mappers\n2. Implement the `TypeMapper` interface for your custom types\n3. Use the `@Order` annotation to control the precedence of your type mappers\n4. Test your type mappers with a variety of input values\n\n## Implementation Details\n\n### How It Works\n\n1. **Method Parsing**: When you call an upsert method, the library parses the method name to determine the operation type, ON clause fields, and ignored fields.\n2. **SQL Generation**: The library generates the appropriate SQL statement based on the database type and the parsed method information.\n3. **Execution**: The SQL statement is executed using Spring's `JdbcTemplate`.\n4. **Generated Keys**: Any generated keys (such as auto-increment IDs) are retrieved and set on the entity objects.\n\n### Database-Specific Implementations\n\n#### MySQL\n\nMySQL uses the `INSERT ... ON DUPLICATE KEY UPDATE` syntax for upsert operations. This relies on the presence of a unique or primary key constraint on the table.\n\nExample:\n```sql\nINSERT INTO users (id, username, email)\nVALUES (:id, :username, :email)\nON DUPLICATE KEY UPDATE\n    username = VALUES(username),\n    email = VALUES(email)\n```\n\nWith conditional updates (MySQL 8.0.19+):\n```sql\nINSERT INTO users (id, username, email, version)\nVALUES (:id, :username, :email, :version)\nON DUPLICATE KEY UPDATE\n    username = IF(VALUES(version) \u003e version, VALUES(username), username),\n    email = IF(VALUES(version) \u003e version, VALUES(email), email),\n    version = IF(VALUES(version) \u003e version, VALUES(version), version)\n```\n\n[Learn more about MySQL implementation](docs/mysql.md)\n\n#### PostgreSQL\n\nPostgreSQL uses the `INSERT ... ON CONFLICT ... DO UPDATE` syntax for upsert operations. This allows for more control over which columns are used for conflict detection.\n\nExample:\n```sql\nINSERT INTO users (id, username, email)\nVALUES (:id, :username, :email)\nON CONFLICT (id) DO UPDATE SET\n    username = EXCLUDED.username,\n    email = EXCLUDED.email\n```\n\nWith conditional updates:\n```sql\nINSERT INTO users (id, username, email, updated_at)\nVALUES (:id, :username, :email, :updated_at)\nON CONFLICT (id) DO UPDATE SET\n    username = EXCLUDED.username,\n    email = EXCLUDED.email,\n    updated_at = EXCLUDED.updated_at\nWHERE EXCLUDED.updated_at \u003e users.updated_at\n```\n\n[Learn more about PostgreSQL implementation](docs/postgresql.md)\n\n## Best Practices\n\n1. **Define Appropriate Constraints**: Ensure that your tables have appropriate unique or primary key constraints for the columns you want to use in the ON clause.\n2. **Use Batch Operations**: When upserting multiple entities, use the `upsertAll` method to take advantage of batch operation support.\n3. **Consider Performance**: For large datasets, consider using custom methods with specific ON clauses and ignored fields to optimize performance.\n\n### Contributing\n\nFor information on how to contribute to this project, including deploying to Maven Central and using\nGitHub Actions for deployment, please see the [CONTRIBUTING.md](CONTRIBUTING.md) file.\n\n## License\n\nThis project is licensed under the MIT License - see the LICENSE file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmpecan%2Fupsert","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmpecan%2Fupsert","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmpecan%2Fupsert/lists"}