{"id":13872115,"url":"https://github.com/GraphQLSwift/DataLoader","last_synced_at":"2025-07-16T01:33:01.939Z","repository":{"id":37025437,"uuid":"135921762","full_name":"GraphQLSwift/DataLoader","owner":"GraphQLSwift","description":"DataLoader is a generic utility to be used as part of your Swift application's data fetching layer to provide a simplified and consistent API over various remote data sources such as databases or web services via batching and caching.","archived":false,"fork":false,"pushed_at":"2024-10-15T15:33:58.000Z","size":59,"stargazers_count":34,"open_issues_count":1,"forks_count":10,"subscribers_count":7,"default_branch":"main","last_synced_at":"2024-10-16T19:14:46.894Z","etag":null,"topics":["dataloader","graphiti","graphql","swift","swift-linux","swift-nio","swiftpm","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/GraphQLSwift.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":"2018-06-03T16:36:34.000Z","updated_at":"2024-10-15T15:34:03.000Z","dependencies_parsed_at":"2024-10-16T18:14:12.705Z","dependency_job_id":null,"html_url":"https://github.com/GraphQLSwift/DataLoader","commit_stats":{"total_commits":47,"total_committers":5,"mean_commits":9.4,"dds":0.6170212765957447,"last_synced_commit":"3a6748ed940b61aa37967cf7b84b55bf0f9cca29"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GraphQLSwift%2FDataLoader","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GraphQLSwift%2FDataLoader/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GraphQLSwift%2FDataLoader/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GraphQLSwift%2FDataLoader/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GraphQLSwift","download_url":"https://codeload.github.com/GraphQLSwift/DataLoader/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226090030,"owners_count":17572114,"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":["dataloader","graphiti","graphql","swift","swift-linux","swift-nio","swiftpm","vapor"],"created_at":"2024-08-05T23:00:34.399Z","updated_at":"2025-07-16T01:33:01.908Z","avatar_url":"https://github.com/GraphQLSwift.png","language":"Swift","funding_links":[],"categories":["Swift"],"sub_categories":[],"readme":"# DataLoader\n\n[![](https://img.shields.io/badge/License-MIT-blue.svg?style=flat)](https://tldrlegal.com/license/mit-license)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FGraphQLSwift%2FDataLoader%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/GraphQLSwift/DataLoader)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FGraphQLSwift%2FDataLoader%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/GraphQLSwift/DataLoader)\n\nDataLoader is a generic utility to be used as part of your application's data fetching layer to provide a simplified and consistent API over various remote data sources such as databases or web services via batching and caching.\n\nThis is a Swift version of the Facebook [DataLoader](https://github.com/facebook/dataloader).\n\n## Gettings started 🚀\n\nInclude this repo in your `Package.swift` file.\n\n```swift\n.Package(url: \"https://github.com/GraphQLSwift/DataLoader.git\", .upToNextMajor(from: \"1.1.0\"))\n```\n\nTo get started, create a DataLoader. Each DataLoader instance represents a unique cache. Typically instances are created per request when used\nwithin a web-server if different users can see different things.\n\n## Batching 🍪\nBatching is not an advanced feature, it's DataLoader's primary feature.\n\nCreate a DataLoader by providing a batch loading function:\n```swift\nlet userLoader = Dataloader\u003cInt, User\u003e(batchLoadFunction: { keys in\n  try User.query(on: req).filter(\\User.id ~~ keys).all().map { users in\n    keys.map { key in\n      DataLoaderFutureValue.success(users.filter{ $0.id == key })\n    }\n  }\n})\n```\n\nThe order of the returned DataLoaderFutureValues must match the order of the keys.\n\n### Load individual keys\n```swift\nlet future1 = try userLoader.load(key: 1, on: eventLoopGroup)\nlet future2 = try userLoader.load(key: 2, on: eventLoopGroup)\nlet future3 = try userLoader.load(key: 1, on: eventLoopGroup)\n```\n\nThe example above will only fetch two users, because the user with key `1` is present twice in the list.\n\n### Load multiple keys\nThere is also a method to load multiple keys at once\n```swift\ntry userLoader.loadMany(keys: [1, 2, 3], on: eventLoopGroup)\n```\n\n### Execution\nBy default, a DataLoader will wait for a short time from the moment `load` is called to collect keys prior\nto running the `batchLoadFunction` and completing the `load` futures. This is to let keys accumulate and\nbatch into a smaller number of total requests. This amount of time is configurable using the `executionPeriod`\noption:\n\n```swift\nlet myLoader =  DataLoader\u003cString, String\u003e(\n    options: DataLoaderOptions(executionPeriod: .milliseconds(50)),\n    batchLoadFunction: { keys in\n        self.someBatchLoader(keys: keys).map { DataLoaderFutureValue.success($0) }\n    }\n)\n```\n\nLonger execution periods reduce the number of total data requests, but also reduce the responsiveness of the\n`load` futures.\n\nIf desired, you can manually execute the `batchLoadFunction` and complete the futures at any time, using the\n`.execute()` method.\n\nScheduled execution can be disabled by setting `executionPeriod` to `nil`, but be careful - you *must* call `.execute()`\nmanually in this case. Otherwise, the futures will never complete!\n\n### Disable batching\nIt is possible to disable batching by setting the   `batchingEnabled` option to `false`.\nIn this case, the `batchLoadFunction` will be invoked immediately when a key is loaded.\n\n\n## Caching 💰\nDataLoader provides a memoization cache. After `.load()` is called with a key, the resulting value is cached\nfor the lifetime of the DataLoader object. This eliminates redundant loads.\n\nIn addition to relieving pressure on your data storage, caching results also creates fewer objects which may\nrelieve memory pressure on your application:\n\n```swift\nlet userLoader = DataLoader\u003cInt, Int\u003e(...)\nlet future1 = userLoader.load(key: 1, on: eventLoopGroup)\nlet future2 = userLoader.load(key: 1, on: eventLoopGroup)\nprint(future1 == future2) // true\n```\n\n### Caching per-Request\n\nDataLoader caching *does not* replace Redis, Memcache, or any other shared\napplication-level cache. DataLoader is first and foremost a data loading mechanism,\nand its cache only serves the purpose of not repeatedly loading the same data in\nthe context of a single request to your Application. To do this, it maintains a\nsimple in-memory memoization cache (more accurately: `.load()` is a memoized function).\n\nAvoid multiple requests from different users using the DataLoader instance, which\ncould result in cached data incorrectly appearing in each request. Typically,\nDataLoader instances are created when a Request begins, and are not used once the\nRequest ends.\n\n### Clearing Cache\n\nIn certain uncommon cases, clearing the request cache may be necessary.\n\nThe most common example when clearing the loader's cache is necessary is after\na mutation or update within the same request, when a cached value could be out of\ndate and future loads should not use any possibly cached value.\n\nHere's a simple example using SQL UPDATE to illustrate.\n\n```swift\n// Request begins...\nlet userLoader = DataLoader\u003cInt, Int\u003e(...)\n\n// And a value happens to be loaded (and cached).\nuserLoader.load(key: 4, on: eventLoopGroup)\n\n// A mutation occurs, invalidating what might be in cache.\nsqlRun('UPDATE users WHERE id=4 SET username=\"zuck\"').whenComplete { userLoader.clear(key: 4) }\n\n// Later the value load is loaded again so the mutated data appears.\nuserLoader.load(key: 4, on: eventLoopGroup)\n\n// Request completes.\n```\n\n### Caching Errors\n\nIf a batch load fails (that is, a batch function throws or returns a DataLoaderFutureValue.failure(Error)),\nthen the requested values will not be cached. However if a batch\nfunction returns an `Error` instance for an individual value, that `Error` will\nbe cached to avoid frequently loading the same `Error`.\n\nIn some circumstances you may wish to clear the cache for these individual Errors:\n\n```swift\nuserLoader.load(key: 1, on: eventLoopGroup).whenFailure { error in\n    if (/* determine if should clear error */) {\n        userLoader.clear(key: 1);\n    }\n    throw error\n}\n```\n\n### Disabling Cache\n\nIn certain uncommon cases, a DataLoader which *does not* cache may be desirable.\nCalling `DataLoader(options: DataLoaderOptions(cachingEnabled: false), batchLoadFunction: batchLoadFunction)` will ensure that every\ncall to `.load()` will produce a *new* Future, and previously requested keys will not be\nsaved in memory.\n\nHowever, when the memoization cache is disabled, your batch function will\nreceive an array of keys which may contain duplicates! Each key will be\nassociated with each call to `.load()`. Your batch loader should provide a value\nfor each instance of the requested key.\n\nFor example:\n\n```swift\nlet myLoader =  DataLoader\u003cString, String\u003e(\n    options: DataLoaderOptions(cachingEnabled: false),\n    batchLoadFunction: { keys in\n        self.someBatchLoader(keys: keys).map { DataLoaderFutureValue.success($0) }\n    }\n)\n\nmyLoader.load(key: \"A\", on: eventLoopGroup)\nmyLoader.load(key: \"B\", on: eventLoopGroup)\nmyLoader.load(key: \"A\", on: eventLoopGroup)\n\n// \u003e [ \"A\", \"B\", \"A\" ]\n```\n\nMore complex cache behavior can be achieved by calling `.clear()` or `.clearAll()`\nrather than disabling the cache completely. For example, this DataLoader will\nprovide unique keys to a batch function due to the memoization cache being\nenabled, but will immediately clear its cache when the batch function is called\nso later requests will load new values.\n\n```swift\nlet myLoader = DataLoader\u003cString, String\u003e(batchLoadFunction: { keys in\n    identityLoader.clearAll()\n    return someBatchLoad(keys: keys)\n})\n```\n\n## Using with GraphQL 🎀\n\nDataLoader pairs nicely well with [GraphQL](https://github.com/GraphQLSwift/GraphQL) and\n[Graphiti](https://github.com/GraphQLSwift/Graphiti). GraphQL fields are designed to be\nstand-alone functions. Without a caching or batching mechanism,\nit's easy for a naive GraphQL server to issue new database requests each time a\nfield is resolved.\n\nConsider the following GraphQL request:\n\n```\n{\n  me {\n    name\n    bestFriend {\n      name\n    }\n    friends(first: 5) {\n      name\n      bestFriend {\n        name\n      }\n    }\n  }\n}\n```\n\nNaively, if `me`, `bestFriend` and `friends` each need to request the backend,\nthere could be at most 12 database requests!\n\nBy using DataLoader, we could batch our requests to a `User` type, and\nonly require at most 4 database requests, and possibly fewer if there are cache hits.\nHere's a full example using Graphiti:\n\n```swift\nstruct User : Codable {\n    let id: Int\n    let name: String\n    let bestFriendID: Int\n    let friendIDs: [Int]\n\n    func getBestFriend(context: UserContext, arguments: NoArguments, group: EventLoopGroup) throws -\u003e EventLoopFuture\u003cUser\u003e {\n        return try context.userLoader.load(key: user.bestFriendID, on: group)\n    }\n\n    struct FriendArguments {\n        first: Int\n    }\n    func getFriends(context: UserContext, arguments: FriendArguments, group: EventLoopGroup) throws -\u003e EventLoopFuture\u003c[User]\u003e {\n        return try context.userLoader.loadMany(keys: user.friendIDs[0..\u003carguments.first], on: group)\n    }\n}\n\nstruct UserResolver {\n    public func me(context: UserContext, arguments: NoArguments) -\u003e User {\n        ...\n    }\n}\n\nclass UserContext {\n    let database = ...\n    let userLoader = DataLoader\u003cInt, User\u003e() { [weak self] keys in\n        guard let self = self else { throw ContextError }\n        return User.query(on: self.database).filter(\\.$id ~~ keys).all().map { users in\n            keys.map { key in\n                users.first { $0.id == key }!\n            }\n        }\n    }\n}\n\nstruct UserAPI : API {\n    let resolver = UserResolver()\n    let schema = Schema\u003cUserResolver, UserContext\u003e {\n        Type(User.self) {\n            Field(\"name\", at: \\.content)\n            Field(\"bestFriend\", at: \\.getBestFriend, as: TypeReference\u003cUser\u003e.self)\n            Field(\"friends\", at: \\.getFriends, as: [TypeReference\u003cUser\u003e]?.self) {\n                Argument(\"first\", at: .\\first)\n            }\n        }\n\n        Query {\n            Field(\"me\", at: UserResolver.hero, as: User.self)\n        }\n    }\n}\n```\n\n## Contributing 🤘\n\nAll your feedback and help to improve this project is very welcome. Please create issues for your bugs, ideas and\nenhancement requests, or better yet, contribute directly by creating a PR. 😎\n\nWhen reporting an issue, please add a detailed example, and if possible a code snippet or test\nto reproduce your problem. 💥\n\nWhen creating a pull request, please adhere to the current coding style where possible, and create tests with your\ncode so it keeps providing an awesome test coverage level 💪\n\nThis repo uses [SwiftFormat](https://github.com/nicklockwood/SwiftFormat), and includes lint checks to enforce these formatting standards.\nTo format your code, install `swiftformat` and run:\n\n```bash\nswiftformat .\n```\n\n## Acknowledgements 👏\n\nThis library is entirely a Swift version of Facebooks [DataLoader](https://github.com/facebook/dataloader).\nDeveloped by  [Lee Byron](https://github.com/leebyron) and [Nicholas Schrock](https://github.com/schrockn)\nfrom [Facebook](https://www.facebook.com/).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FGraphQLSwift%2FDataLoader","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FGraphQLSwift%2FDataLoader","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FGraphQLSwift%2FDataLoader/lists"}