{"id":16681972,"url":"https://github.com/stevebuik/ns-clone","last_synced_at":"2026-05-16T23:40:50.789Z","repository":{"id":83074254,"uuid":"128293798","full_name":"stevebuik/ns-clone","owner":"stevebuik","description":"Clone any Clojure namespace, restrict available functions and add middleware","archived":false,"fork":false,"pushed_at":"2018-05-05T00:54:42.000Z","size":37,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-20T07:12:47.845Z","etag":null,"topics":["clojure","datomic","middleware"],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stevebuik.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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-04-06T03:00:20.000Z","updated_at":"2018-06-17T01:51:58.000Z","dependencies_parsed_at":"2023-04-06T22:04:38.265Z","dependency_job_id":null,"html_url":"https://github.com/stevebuik/ns-clone","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stevebuik%2Fns-clone","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stevebuik%2Fns-clone/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stevebuik%2Fns-clone/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stevebuik%2Fns-clone/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stevebuik","download_url":"https://codeload.github.com/stevebuik/ns-clone/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243392298,"owners_count":20283560,"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":["clojure","datomic","middleware"],"created_at":"2024-10-12T14:05:49.060Z","updated_at":"2025-12-29T23:08:50.835Z","avatar_url":"https://github.com/stevebuik.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Namespace Clone\n\nTools to clone (some or all) functions and arguments in a namespace and invoke them using Pedestal Interceptor chains.\n\nThe **clone** namespace can then be used instead of the **cloned** namespace in the *require* section of other namespaces that use the **cloned** namespace.\n\nThis provides two benefits:\n\n1. the clone namespace can support a subset of fns available in the cloned namespace i.e. reduce the api size to fns that you want exposed.\n2. the interceptor chain allows custom cross-cutting/middleware to be added to all function calls.\nThis opens up many useful behaviours at the namespace/API layer e.g.\n    * API call logging/timing\n    * Translating an api function to some other function\n    * Error handling / translation e.g. using [Phrase](https://github.com/alexanderkiel/phrase) to convert some spec errors to user facing error messages\n    * Application context specific concerns e.g. access controls\n    * Metrics / sampling\n    * Canary releases driven by the app context data\n    * Versioning\n    * Experiments : transparently running a new version of a fn async next to the original and reporting when results differ\n    * What else?\n\nThe clone api uses multi-methods to allow open extension of any application context shape and interceptor chains.\n\n## Original use-case\n\nA wrapper for the Datomic API functions available in all libs i.e. the lowest common denominator set of functions from the Client AND Peer libraries.\n\nUseful when you want local dev/test against a local \"mem\" database but want to run your code in production against the Cloud/Client API.\n\nThis use-case is used as the sample and test source in this project.\nIt also provides a Datomic clone namespace for any project needing consistent peer vs client code.\n\nThe [datomic-peer-clone](https://github.com/stevebuik/ns-clone/tree/master/datomic-peer-clone) sub-project has an implementation of the delegate interceptors for use with the Peer API.\n\nTODO **datomic-client-clone** sub-project\n\n**On Datomic API open-ness**\n\nThe Datomic peer api is less open (i.e. functions only) but the client api is a protocol so much better.\nThis lib can provide a consistent api for both.\n\n## Migration use-case\n\nA cloned api can also be used to migrate away from using a namespace without changing consumers of that namespace.\nSince your application code chooses how to delegate the clone functions, you can change the underlying implementation whenever you want.\ne.g. d/entity calls can be transparently translated to d/pull calls.\n\nThe **datomic-clone.api** in this project does not include the *d/entity* function.\nThis forces any peer based project to remove *d/entity* calls before using the clone api.\nThis is because there is no reliable way to clone the *d/entity* function in the client api.\n*d/pull* is the closest but it's behaviour is just different enough that, even with a clone function, the delegate would be difficult to write.\nThe easiest solution seems to be to avoid *d/entity*. If you need *d/entity* or any other missing function,\nyou can create your own clone namespace instead of using **datomic-clone.api** and then you can clone exactly what you need.\n\n*d/invoke* is also absent in **datomic-clone.api** because there is currently no support for it in Cloud/Client.\n\n## Usage\n\nLook at the Datomic example for how to clone a namespace. Then:\n\n1. If cloning a non-Datomic namespace, create a clone namespace following the pattern in the datomic-clone.api namespace\n2. Implement the *defmethods* for your application context and interceptor chains. Follow the pattern in [datomic-peer-clone](https://github.com/stevebuik/ns-clone/tree/master/datomic-peer-clone) or the tests in this project.\n3. Optionally implement a spec for your app context data\n4. Replace the `(:require [datomic.api :as d])` with your clone namespace anywhere that it is required\n5. Try using the logger interceptor, the feedback is interesting, especially for functions that make network calls\n\nThe initial wrapped argument can be produced any way you want e.g. your own function.\nLook at the datomic-clone-test to see that the initial *conn* value being created.\n\nThe datomic-clone-test namespace provides a simple mocked version of this.\n\nThen look at the [datomic-peer-clone](https://github.com/stevebuik/ns-clone/tree/master/datomic-peer-clone) sub-project to see how to implement a clone with a real delegate interceptor instead of the mock delegates in the tests.\n\nThe [datomic-peer-clone](https://github.com/stevebuik/ns-clone/tree/master/datomic-peer-clone) sub-project is intended for use as a dependency in Datomic projects which need to run using both the local (peer) or Cloud (client) APIs.\nIt includes the ns-clone project as its only dependency.\n\nDespite the Datomic slant, the **ns-clone** project has no Datomic dependencies and can be used to clone any namespace, without Datomic.\nThat is why [datomic-peer-clone](https://github.com/stevebuik/ns-clone/tree/master/datomic-peer-clone) is a sub-project, to avoid unwanted dependencies when used elsewhere.\n\n## Installation\n\nTo run tests:\n\n1. `lein test`\n2. `lein sub test`\n\nTo install from source:\n\n1. `lein install` installs the ns-clone lib\n2. `lein sub install` installs the datomic-peer-clone lib\n\n## Assumptions\n\nThis lib works by passing around a wrapper (map) in place of one of the args to all cloned functions.\nThe wrapped value (the original arg) is passed in the :UNSAFE! map entry. This makes it available for the *delegate* interceptor to use it to call the underlying function.\nOf course any function can access the :UNSAFE! value, but that will appear in source and discourage any developer from going around the interceptor chain.\n\nIf the wrapped arg is translated into another value, for use in another cloned function, then it should transfer all the map data and assoc the new value in the :UNSAFE! key.\n\nIt is possible to not wrap any arguments. In **datomic-clone.api** this is done for d/tempid and d/squuid.\nThis excludes certain types of middleware but still provides many benefits.\n\n## Status\n\nThis lib is being used in a Datomic webapp, which is not yet running in Production.\n\nThere are likely missing features around database values in the [datomic-peer-clone](https://github.com/stevebuik/ns-clone/tree/master/datomic-peer-clone).\nThe initial version has just enough for my use-case and I'm sure that's not enough for everyone.\nHence the need for feedback from people willing to try it.\n\nNot yet deployed to Clojars, will do so after initial feedback.\n\n## Disadvantages\n\n* Stacktraces : will be bigger because, the interceptor chain handling calls are included in the stack.\n* Exceptions : will be more complex because the Pedestal interceptor execution will wrap them in an ExceptionInfo with additional execution data.\nThis is pretty easy to spot so not too bad.\n* Error messages : arity errors in api calls are reported by the clone namespace. This is tolerable since the same error would occur using the cloned namespace.\nCurrently they are reported by the delegate. This could be changed to be reported by the clone fn instead. What do you think?\n* Documentation : when you use your IDE to show doc for a clone fn, you will see the clone function doc and not the underlying (cloned) functions doc.\n\n## Ideas / Future\n\n1. A Datomic Client api sub-project. Requires mapping the **datomic-clone.api** functions to client api calls using delegates.\nThe transact-async delegate will need to translate from a core.async channel to a future to keep the clone api consistent.\n2. Enhance the logger interceptor to record the invoking function i.e. one above in the call stack.\nThis will be useful when wanting to know where in the source each cloned function was called.\n3. Add a macro to:\n    1. generate the clone fns and queue factory fns i.e. less boilerplate to use this lib\n    2. copy over the doc from the cloned functions\n    3. compare each fn with the underlying cloned fn to ensure the APIs don't diverge\n\n## Acknowledgements\n\nCloning the Datomic API was inspired by Rob Stuttafords [Bridge](https://github.com/robert-stuttaford/bridge) project.\n\nGot some help on futures for [datomic-peer-clone](https://github.com/stevebuik/ns-clone/tree/master/datomic-peer-clone) from [Leo Borges](https://twitter.com/leonardo_borges)\n\n## License\n\nCopyright © 2018 Steve Buikhuizen\n\nEPL, same as Clojure.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstevebuik%2Fns-clone","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstevebuik%2Fns-clone","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstevebuik%2Fns-clone/lists"}