{"id":17272319,"url":"https://github.com/vmanot/corepersistence","last_synced_at":"2025-07-28T12:07:39.794Z","repository":{"id":122933266,"uuid":"542064662","full_name":"vmanot/CorePersistence","owner":"vmanot","description":"A protocol-oriented foundation for your app's persistence layer.","archived":false,"fork":false,"pushed_at":"2025-06-19T12:06:46.000Z","size":614,"stargazers_count":6,"open_issues_count":0,"forks_count":4,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-07-02T06:55:08.072Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vmanot.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","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}},"created_at":"2022-09-27T12:08:00.000Z","updated_at":"2025-06-19T12:06:50.000Z","dependencies_parsed_at":null,"dependency_job_id":"76eace46-c6e6-40d9-9c45-0ac221baae51","html_url":"https://github.com/vmanot/CorePersistence","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/vmanot/CorePersistence","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vmanot%2FCorePersistence","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vmanot%2FCorePersistence/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vmanot%2FCorePersistence/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vmanot%2FCorePersistence/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vmanot","download_url":"https://codeload.github.com/vmanot/CorePersistence/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vmanot%2FCorePersistence/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263828720,"owners_count":23516789,"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":[],"created_at":"2024-10-15T08:48:21.678Z","updated_at":"2025-07-06T00:06:46.207Z","avatar_url":"https://github.com/vmanot.png","language":"Swift","readme":"# CorePersistence\n\nA protocol-oriented, batteries-included foundation for persistence in Swift. \n\n# Goals\nThis library has ambitious goals:\n- Provide a protocol-oriented foundation for all the critical aspects of a typical, modern Swift application's persistence layer.\n- Provide standard, high performance primitives for the most common data formats (`JSON`, `CSV`, `XML` etc.).\n- Unf*** `Codable`.\n\n# Features\n- An opinionated, protocol-oriented encapsulated of persistent identifiers (both type identifiers and instance identifiers).\n- A modular plugin system for `Codable` (achieved by custom encoders \u0026 decoders that can wrap existing ones, macros, and a suite of protocols).\n- Better diagnostics for `Codable` errors (`EncodingError` and `DecodingError` are subpar).\n- Essential data storage primitives (see `@FileStorage` and `@FolderStorage` – similar to SwiftUI's `@AppStorage` but for the application's persistence layer.)\n- A high performance `JSON` primitive.\n- A high performance `CSV` primitive.\n- A high performance `XML` primitive (backed by the excellent `XMLCoder` library for now).\n\nExamples:\n- [JSONSchema](#jsonschema)\n- [@FileStorage](#filestorage)\n- [@HadeanIdentifier](#hadeanidentifier)\n\n## JSONSchema\nBroad description of the JSON schema. It is agnostic and independent of any programming language.\nBased on: [https://json-schema.org/draft/2019-09/json-schema-core.html](https://json-schema.org/draft/2019-09/json-schema-core) it implements only concepts used in the `rum-events-format` schemas.\n\n```swift\nlet restaurantBookingSchema = JSONSchema(\n    type: .object,\n    description: \"Information required to make a restaurant booking\",\n    properties: [\n        \"name\": JSONSchema(\n            type: .string,\n            description: \"The name of the restaurant\"\n        ),\n        \"date\" : JSONSchema(\n            type: .string,\n            description: \"The date of the restaurant booking in yyyy-MM-dd format. Should be a date with a year, month, day.\"\n        ),\n        \"time\" : JSONSchema(\n            type: .string,\n            description: \"The time of the reservation in HH:mm format. Should include hours and minutes.\"\n        ),\n        \"number_of_people\" : JSONSchema(\n            type: .integer,\n            description: \"The total number of people the reservation is for\"\n        )\n    ],\n    // the required parameter specifies whether all properties listed are required or not\n    // note that you can also pass in an array of strings specifying the properties that are required as follows:\n    // required: [\"name\", \"date\", \"time\"] - \"number_of_people\" not required\n    required: false \n)\n```\n\nYou can also create a `JSONSchema` based on your object as follows:\n\n```swift\nstruct RestaurantBooking: Codable, Hashable, Sendable {\n    let name: String?\n    let date: String?\n    let time: String?\n    let numberOfPeople: Int?\n}\n\nfunc createRestaurantBookingSchema() -\u003e JSONSchema? {\n    do {\n        let restaurantBookingSchema: JSONSchema = try JSONSchema(\n            type: RestaurantBooking.self,\n            description: \"Information required to make a restaurant booking\",\n            propertyDescriptions: [\n                \"name\": \"The name of the restaurant\",\n                \"date\": \"The date of the restaurant booking in yyyy-MM-dd format. Should be a date with a year, month, day.\",\n                \"time\": \"The time of the reservation in HH:mm format. Should include hours and minutes.\",\n                \"number_of_people\": \"The total number of people the reservation is for\"\n            ],\n            required: false\n        )\n        return restaurantBookingSchema\n    } catch {\n        print(error)\n        return nil\n    }\n}\n```\n\n## @FileStorage\nThe `@FileStorage` property wrapper is a tool designed to simplify data persistence by automatically handling the reading and writing of data to files. Features include:\n- **Automatic Data Handling**: `@FileStorage` automates the process of storing and retrieving data. Data is automatically written to a file whenever it changes, and read from the file when needed.\n- **Configurable Storage Location**: You can specify where the data should be stored, such as in the application's documents directory (`.appDocuments`) or other specific locations (e.g.  `.desktop`, `.downloads`, `.musicDirectory`) that can be configured based on permissions and user settings. This flexibility ensures that data storage can be adapted to different application needs and environments.\n- **Customizable Serialization**: It supports different data coders like JSON or property list, allowing for easy serialization and deserialization of complex data types. This is useful for storing custom objects, as long as they conform to the `Codable` protocol.\n- **Error Handling Strategies**: `@FileStorage` offers customizable error handling strategies, such as discarding corrupted data and resetting to default values, or even halting the application on errors. This is critical for maintaining data integrity and application stability.\n\nUsing `@FileStorage` can be used as an alternative to `SwiftData` for simpler, smaller-scale applications because it offers a more straightforward and lightweight approach to data persistence. It seamlessly integrates with SwiftUI, providing an easy-to-use, declarative syntax that minimizes boilerplate code and automatically handles serialization of Codable objects. This eliminates the need for complex database setup, schema management, and migrations, making it ideal for applications that don't require the advanced features and overhead of a full-fledged database system. Additionally, `@FileStorage` allows for customizable error handling strategies, ensuring data integrity without the complexity of managing a relational database.\n\n```swift\n// Making DataStore an ObservableObject allows to receive notifications when values change\n// In View: @StateObject var dataStore: DataStore = .shared\npublic final class DataStore: ObservableObject {\n    \n    @MainActor\n    // the DataStore should be a singleton\n    public static let shared = DataStore()\n    \n    @FileStorage(\n        // directory of the app docuemnts set to .appDocuments means that the file will be stored in the app sandbox. User permissions dialog will not show up\n        // if you use other options (e.g. .documents, .desktop, .downloads, .musicDirectory, etc), make sure to enable app permissions to access those folders. The user will have to grant permissions.\n        .appDocuments,\n        path: \"path.json\",\n        coder: .json, // .propertyList is also supported\n        // In case of read error, discard existing data (if any) and reset with the initial value.\n        options: .init(readErrorRecoveryStrategy: .discardAndReset)\n    )\n    \n    // the file will be auto-updated as objects are changed\n    var objects: IdentifierIndexingArrayOf\u003cMyIdentifiableObject\u003e = []\n\n    @MainActor\n    private init() {\n        if objects.isEmpty {\n            objects = [.init(someText: \"Hello World\")]\n        }\n    }\n}\n\n// must conform to Identifiable, Hashable, and Codable\npublic struct MyIdentifiableObject: Identifiable, Hashable, Codable {\n    public var id = UUID()\n    public var someText: String?\n    \n    init(someText: String?) {\n        self.someText = someText\n    }\n}\n```\n\nOther custom options for initializing @FileStorage:\n```swift\n// For Application Groups\n@FileStorage(\n    location: {\n        return try! URL(\n            directory: .securityApplicationGroup(\"group.com.yourgroupname.Shared\")\n        )\n        .appending(path: \"DirectoryPath\", directoryHint: .isDirectory)\n        .appending(path: \"data.json\")\n    },\n    coder: JSONCoder(), // TOMLCoder() also supported\n    options: .init(readErrorRecoveryStrategy: .fatalError)\n)\n\n// Storing in Home Directory using URL\n// This will require to updated settings to allow Read/Write access to the directory\n@FileStorage(\n    url: URL.homeDirectory.appending(path: \"data.json\"),\n    coder: JSONCoder()\n)\n\n// Specifying Path \u0026 Filename\n@FileStorage(\n    directory: .appDocuments,\n    path: \"ProjectName\",\n    filename: UUID.self,\n    coder: HadeanTopLevelCoder(coder: JSONCoder()),\n    options: .init(readErrorRecoveryStrategy: .discardAndReset)\n)\n```\n\n## @HadeanIdentifier\n`@HadeanIdentifier` is a general purpose persistent identifier to identify distinct objects. \nUsage:\n```swift\n@HadeanIdentifier(\"guvol-haboz-motiz-povag\")\nstruct MyObject {\n// your object code here\n}\n```\nIt is recommended to use a [Proquint](https://github.com/dsw/proquint) - Identifiers that are not real words but are as Readable, Spellable, and Pronounceable as words. \n\nPython scrip to generate a proquint: \n```python\nfrom proquint import uint2quint\nimport random\n\n# Generate a unique string of 4 proquint words\nunique_string = '-'.join(uint2quint(random.getrandbits(32)) for _ in range(4))\nprint(unique_string)\n```\n\n## TopLevelDecoder._modular()\n\nSample JSON Data: \n\n```swift\nlet jsonData = \"\"\"\n{\n    \"id\": 1,\n    \"name\": \"John Doe\",\n    \"email\": \"john@example.com\"\n}\n\"\"\".data(using: .utf8)!\n```\n\nUsing regular `JSONDecoder()`: \n```swift\ndo {\n    // using regular JSONDecoder()\n    let decoder = JSONDecoder()\n    let user = try decoder.decode(User.self, from: jsonData)\n} catch {\n    print(error)\n}\n```\n\nPrinted Error when using `JSONDecoder()`: \n\n```\nkeyNotFound(CodingKeys(stringValue: \"wrongKey\", intValue: nil),\nSwift.DecodingError.Context(codingPath: [],\ndebugDescription: \"No value associated with key CodingKeys(stringValue: \\\"wrongKey\\\", intValue: nil) (\\\"wrongKey\\\").\",\nunderlyingError: nil))\n```\n\nUsing `JSONDecoder()._modular()`:\n```swift\ndo {\n    // using regular JSONDecoder()._modular()\n    let decoder = JSONDecoder()._modular()\n    let user = try decoder.decode(User.self, from: jsonData)\n} catch {\n    print(error)\n}\n```\n\nPrinted Error when using `JSONDecoder()._modular()` (actual JSON Data printed out):\n\n```\nkeyNotFound(\"wrongKey\",\ncontext for User: (coding path: []),\nOptional([\"id\": 1.0, \"email\": \"john@example.com\", \"name\": \"John Doe\"]))\n```\n\n# License\n\nCorePersistence is licensed under the [MIT License](https://vmanot.mit-license.org).\n\n# Acknowledgments\n\n\u003cdetails\u003e\n\u003csummary\u003eXMLCoder\u003c/summary\u003e\n\n- **Link**: https://github.com/CoreOffice/XMLCoder\n- **License**: [MIT License](https://github.com/CoreOffice/XMLCoder/blob/main/LICENSE)\n- **Authors**: Shawn Moore and XMLCoder contributors\n\n\u003c/details\u003e\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvmanot%2Fcorepersistence","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvmanot%2Fcorepersistence","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvmanot%2Fcorepersistence/lists"}