{"id":32441154,"url":"https://github.com/eric-musliner/redis-om-swift","last_synced_at":"2025-10-26T01:55:07.093Z","repository":{"id":320526467,"uuid":"1019219492","full_name":"eric-musliner/redis-om-swift","owner":"eric-musliner","description":"Object mapping, and more, for Redis and Swift ","archived":false,"fork":false,"pushed_at":"2025-10-23T12:22:53.000Z","size":169,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-24T09:43:49.836Z","etag":null,"topics":["redis","redis-om","rejson","swift","vapor"],"latest_commit_sha":null,"homepage":"","language":"Swift","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/eric-musliner.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","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":"NOTICE","maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-07-14T01:49:46.000Z","updated_at":"2025-10-23T14:46:43.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/eric-musliner/redis-om-swift","commit_stats":null,"previous_names":["eric-musliner/redis-om-swift"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/eric-musliner/redis-om-swift","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eric-musliner%2Fredis-om-swift","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eric-musliner%2Fredis-om-swift/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eric-musliner%2Fredis-om-swift/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eric-musliner%2Fredis-om-swift/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eric-musliner","download_url":"https://codeload.github.com/eric-musliner/redis-om-swift/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eric-musliner%2Fredis-om-swift/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":281047757,"owners_count":26435124,"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","status":"online","status_checked_at":"2025-10-25T02:00:06.499Z","response_time":81,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["redis","redis-om","rejson","swift","vapor"],"created_at":"2025-10-26T01:53:35.532Z","updated_at":"2025-10-26T01:55:07.087Z","avatar_url":"https://github.com/eric-musliner.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cbr/\u003e\n  \u003cbr/\u003e\n  \u003cimg width=\"360\" src=\"docs/images/logo.svg\" alt=\"RedisOM\" /\u003e\n  \u003cbr/\u003e\n  \u003cbr/\u003e\n\u003c/div\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003cp align=\"center\"\u003e\n        Object mapping, and more, for Redis and Swift\n    \u003c/p\u003e\n\u003c/p\u003e\n\n---\n\n![Swift Version][ver-svg]\n[![License][license-image]][license-url]\n[![Build Status][ci-svg]][ci-url]\n\n## Overview\n`RedisOM Swift` is a high-level Redis client and object mapper for Swift inspired by `redis-om-python` and the other official RedisOM libraries published under the Redis org. It provides a typed, declarative way to model, persist, and query JSON documents in Redis using Swift's key paths and macros.\n\n`RedisOM Swift` combines RedisJSON, RediSearch, and connection pooling into a unified API that feels native to Swift. It integrates with both Vapor's app lifecycle and the Swift Service Lifecycle framwork, making it ideal for server applications, background workers, or distributed systems.\n\n**Key Features**\n* Declarative Models - Define models using the @Model macro and store them as queryable RedisJSON documnts.\n* Full-text and numeric search - Query your data using a fluent, type-safe biulder powered by RediSearch.\n* Lifecycle Integration - Works out of the box with Vapor and Service Lifecycle.\n* Automatic Index Migration - `RedisOM Swift` automatically creates and updates RediSearch indexes\n\n## Installation\nYou can add `RedisOM Swift` to your project using Swift Package Manager.\n\n### Swift Package Manager\n\nAdd the following to your `Package.swift` file:\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/eric-musliner/redis-om-swift.git\", from: \"0.1.0\")\n]\n...\n\ntargets : [\n    .product(name:\"RedisOM\", package: \"redis-om-swift\")\n]\n```\n\nThis ensures that the Redis connections and index migrations are automatically started and stopped as part of your service lifecycle\n\n## Modeling Your Data\nDefine your data models by implementing the `JsonModel` protocol\n\nUse the the `@Id` Property wrapper to define the id field and auto assign an UUID on save to the model records.\n\n\n```swift\nstruct User: JsonModel {\n    @Id var id: String?\n    var name: String\n    var email: String\n    var aliases: [String]?\n    var age: Int?\n    var createdAt: Date?\n\n    static let keyPrefix: String = \"user\"\n}\n```\n\nYou can then persist, retrieve, update, or delete models like:\n\n```swift\nvar user = User(name: \"Alice\", email: \"alice@example.com\")\ntry await user.save()\n\nvar user2 = User(name: \"Alice\", email: \"alice@example.com\", age: 45)\ntry await user2.save()\n\n// Retrieve model from Redis by id\nlet user = try await User.get(id: user.id!)\ntry await user.delete()\n\n// Make updates to model\nuser.name = \"Alicia\"\nuser.save()\n\n// Delete by id\ntry await User.delete(id: user2.id!)\n\n// Get all keys in Redis for a given model\ntry await User.allKeys()\n```\n\n## Indexing and Migration\nModel fields are automatically \"Indexed\" using the `@Index` Property wrapper on your model fields. `@Index` supports passing a type parameter to specify what type the index is in Redis for your field: eg. (`.text`, `.tag`, `.numeric`). The default type is `.tag`\n\n```swift\n@Index \nvar address: [Address]?\n\n@Index(type: .text)\nvar description: String?\n\n@Index(type: .numeric)\nvar weight: Int\n```\nWhen a model is registered, `RedisOM Swift` automatically builds and synchronizes its schema with Redis.\n\n```swift\nredis.register(User.self)\n```\n\nIf you change your model fields or index configuration, the migrator updates the RediSearch index definitions automatically on next startup.\n\nYou can also manually run migrations:\n\n```swift\nlet migrator = try Migrator(client: redis.poolService)\ntry await migrator.migrate(models: [User.self])\n```\n\n## Rich Queries and Embedded Models\nRedisOM supports rich, type-safe queries via RediSearch, allowing you to filter, sort, and combine complex predicates with a Fluent-style API.\n\nTo make your models searchable, simply annotate them with the `@Model` macro.\nThis macro generates the necessary schema metadata for RediSearch, enabling `RedisOM Swift` to:\n\n* Register the model automatically with the `RedisOM Swift` instance.\n* Create and migrate RediSearch indexes.\n* Support type-safe and chainable query builders.\n\n### Querying Flat Models\n\nHere's a basic example of a searchable model:\n```swift\n@Model\nstruct User: JsonModel {\n    @Id var id: String?\n    @Index(type: .text) var name: String\n    @Index var email: String\n    @Index var aliases: [String]?\n    @Index(type: .numeric) var age: Int?\n\n    static let keyPrefix: String = \"user\"\n}\n```\n\nPerform queries using a fluent API:\n\n```swift\nlet users: [User] = try await User.find().where(\\.$name == \"Alice\").all()\n```\n\nChain multiple predicates together:\n\n```swift\nlet users: [User] = try await User\n    .find()\n    .where(\\.$name == \"Alice\")\n    .or(\\.$name == \"Sandra\")\n    .and(\\.$age == 33)\n    .all()\n```\n\nYou can also use range operators, in, and between\n\n```swift\n@Model\nstruct Item: JsonModel {\n    @Id var id: String?\n    @Index(type: .numeric) var price: Double\n    @Index var name: String\n\n    static let keyPrefix: String = \"item\"\n}\n\nlet items: [Item] = try await Item\n    .find()\n    .where(\\.$price \u003c= 65.99)\n    .and(\\.$price \u003e 10)\n    .all()\n\n// Between\nlet users: [User] = try await User\n    .find()\n    .where(\\.$age...(34, 60))\n    .and(\\.$name == \"Bill\")\n    .all()\n\n// In\nlet items: [Item] = try await Item.find()\n    .where(\\.$price ~= [24.99, 50.99])\n    .all()\n\n```\n\n### Negation\nYou can invert any query predicate using the .not() modifier at the end of a query chain.\nThis tells `RedisOM Swift` to negate the preceding condition or group of conditions.\n\nFor example:\n\n```swift\nlet users: [User] = try await User\n    .find()\n    .where(\\.$name == \"Alice\")\n    .not()\n    .all()\n```\n\nThis generates a RediSearch query equivalent to:\n```\n-(@name:Alice)\n```\n\nYou can also chain .not() with other predicates to express complex filters:\n```swift\nlet users: [User] = try await User\n    .find()\n    .where(\\.$age \u003e= 18)\n    .and(\\.$email == \"alice@example.com\")\n    .not()\n    .all()\n```\nThis translates to\n```\n-((@age:[18 inf] @email:alice@example.com))\n```\n\n### Embedded and Nested Models\n`RedisOM Swift` allows you to embed nested models within your root model, while still making their fields searchable using the same key-path syntax\n\nFor example\n\n```swift\n@Model\nstruct Address: JsonModel {\n    @Id var id: String?\n    @Index var city: String\n    @Index var state: String\n    @Index var zip: String\n}\n\n@Model\nstruct Person: JsonModel {\n    @Id var id: String?\n    @Index var name: String\n    @Index var address: Address\n}\n```\nUnder the hood, `RedisOM Swift` automatically flattens the nested schema so that RediSearch can index it with fully qualified field names (e.g. address__city, address__state).\n\nYou can then query nested fields directly using key paths:\n\n```swift\nlet people = try await Person\n    .find()\n    .where(\\.$address.city == \"Boston\")\n    .and(\\.$address.state == \"MA\")\n    .all()\n```\n\nEven nested collections are supported:\n\n```swift\n@Model\nstruct Bike: JsonModel {\n    @Id var id: String?\n    @Index var model: String\n    @Index var brand: String\n    @Index(type: .numeric) var price: Int\n    @Index var type: String\n    @Index var specs: [Spec]\n    @Index(type: .text) var description: String?\n    var addons: [String]?\n    @Index var helmetIncluded: Bool\n    var createdAt: Date?\n\n    static let keyPrefix: String = \"bike\"\n}\n\n@Model\nstruct Spec: JsonModel {\n    var id: String?\n    @Index var manufacturer: String\n    @Index var material: String\n    @Index(type: .numeric) var weight: Int\n\n    static let keyPrefix: String = \"spec\"\n}\n```\n\nYou can query deeply into arrays of embedded models:\n\n```swift\nlet bikes = try await Bike\n    .find()\n    .where(\\.$specs[\\.$manufacturer] == \"Giant\")\n    .all()\n\nlet bikes = try await Bike\n    .find()\n    .where(\\.$specs[\\.$weight]...(40, 60))\n    .and(\\.$specs[\\.$material] == \"carbon fiber\")\n    .all()\n```\n\n### Combining Nested Predicates\nYou can freely mix predicates across nested and root fields:\n\n```swift\nlet results = try await Person\n    .find()\n    .where(\\.$name == \"Alice\")\n    .and(\\.$address.city == \"Cambridge\")\n    .or(\\.$address.state == \"MA\")\n    .all()\n```\nInternally, `RedisOM Swift` automatically generates a valid RediSearch query such as:\n\n```\n(@name:Alice @address__city:Cambridge) | (@address__state:MA)\n```\n\n### Limit, First, \u0026 Exist\n\nYou can also control the number of results, get only the first, or simply check if a record exists\n\n```swift\nlet items = try await Item\n    .find()\n    .where(\\.$price...(24.00, 70.0))\n    .limit(0..\u003c2)\n    .all()\n\nlet result: Item? = try await Item\n    .find()\n    .where(\\.$price...(24.00, 70.0))\n    .first()\n\nlet exists = try await Item.find()\n    .where(\\.$price \u003c= 65.99)\n    .exists()\n```\n\n## Why It Matters\n\nTraditional Redis clients treat Redis as a key-value store.\n`RedisOM Swift` turns it into a typed, searchable document store, allowing you to:\n\n * Model data like you would in an ORM.\n * Query using Swift key paths instead of strings.\n * Combine nested model fields in a type-safe way.\n * Let Redis handle full-text search and indexing behind the scenes.\n\n## Configuration\nBy default `RedisOM Swift` will connect to a Redis instance using the environment variable `REDIS_URL`.\n\n```\nexport REDIS_URL=redis://localhost:6379\n```\n\nYou can also pass the URL directly:\n\n```swift\nlet redis = try RedisOM(url: \"redis://localhost:6379\")\n```\n\nor for secure connections:\n```swift\nlet redis = try RedisOM(\n    url: \"rediss://:mySecretPassword@redis.example.com:6379\"\n)\n```\n\nYou can also customize the logger and retry policy:\n```swift\nlet redis = try RedisOM(\n    url: \"redis://localhost:6379\",\n    retryPolicy: .limited(3),\n    logger: Logger(label: \"redis.om\")\n)\n```\n\n### Advanced Configuration\n\nFor more granular control — such as setting custom authentication, selecting a database, or enabling TLS manually — you can construct a full RedisConfiguration object and pass it directly.\n\nPragmatic Configuration:\n\n```swift\nvar config = try RedisConfiguration(hostname: \"redis.prod.internal\", port: 6380)\nconfig.password = \"prodSecret\"\nconfig.tlsConfiguration = .forClient()\n\nlet logger = Logger(label: \"redis.om.prod\")\n\nlet redis = try RedisOM(\n    config: config,\n    logger: logger,\n    retryPolicy: .infinite\n)\n```\n\nThis is ideal when your app runs in environments that require explicit control over:\n\n* Authentication credentials\n* Database selection\n* TLS certificates / client auth\n* Socket options or timeouts\n\n## Usage with Vapor App Lifecycle\n\nWhen used in a Vapor app, `RedisOM Swift` can participate in the lifecycle and automatically migrate your search indexes during startup\n\n```swift\npublic func configure(_ app: Application) throws {\n    let redis = try RedisOM(url: \"redis://localhost:6379\")\n\n    # Register models for automatic indexing\n    redis.register(User.self)\n\n    app.lifecycle.use(redis)\n}\n```\nWhen the application boots, `RedisOM Swift` will automatically\n1. Establish a connection pool to Redis\n2. Create or re-create RediSearch indexes for all registered models\n3. Cleanly shut down connections on app termination\n\n## Usage with Swift Service Lifecycle\n\nIf you're building a service using Swift Service Lifecycle, `RedisOM Swift` can run as a managed service\n\n```swift\nimport RedisOM\nimport ServiceLifecycle\n\n@main\nstruct App {\n    static func main() async throws {\n        let redis = try RedisOM()\n        redis.register(User.self)\n\n        // Run as part of the Swift Service Lifecycle\n        let group = ServiceGroup(services: [redis])\n        try await group.run()\n    }\n}\n```\nThis ensures that the Redis connections and index migrations are automatically started and stopped as part of your service lifecycle\n\n## Logging\n\n`RedisOM Swift` integrates with Swift's `swift-log` system. You can inject a custom Logger to control verbosity:\n\n```swift\nvar logger = Logger(label: \"redis.om.debug\")\nlogger.logLevel = .debug\n\nlet redis = try RedisOM(logger: logger)\n```\n\n## License\n\nredis-om-swift is available under the MIT License. See LICENSE for details.\n\n\u003c!-- Badges --\u003e\n[license-image]: https://img.shields.io/badge/license-mit-green.svg?style=flat-square\n[license-url]: LICENSE\n[ci-svg]: https://github.com/eric-musliner/redis-om-swift/actions/workflows/ci.yml/badge.svg\n[ci-url]: https://github.com/eric-musliner/redis-om-swift/actions/workflows/ci.yml\n[ver-svg]: https://img.shields.io/badge/swift-6.2%20%2F%206.1-brightgreen.svg\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feric-musliner%2Fredis-om-swift","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feric-musliner%2Fredis-om-swift","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feric-musliner%2Fredis-om-swift/lists"}