@niroai/niro 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/README.md +123 -302
  3. package/bin/niro.js +12 -1
  4. package/docs/agent-antigravity.md +37 -0
  5. package/docs/agent-claude-code.md +48 -0
  6. package/docs/agent-claude-desktop.md +47 -0
  7. package/docs/agent-cline.md +42 -0
  8. package/docs/agent-codex.md +45 -0
  9. package/docs/agent-copilot-cli.md +42 -0
  10. package/docs/agent-cursor.md +44 -0
  11. package/docs/agent-factory.md +42 -0
  12. package/docs/agent-gemini-cli.md +42 -0
  13. package/docs/agent-goose.md +42 -0
  14. package/docs/agent-kiro.md +37 -0
  15. package/docs/agent-opencode.md +37 -0
  16. package/docs/agent-vscode.md +45 -0
  17. package/docs/agent-windsurf.md +38 -0
  18. package/docs/answer-shapes.md +69 -0
  19. package/docs/builds-and-updates.md +68 -0
  20. package/docs/cloud-README.md +372 -0
  21. package/docs/configuring-a-folder.md +51 -0
  22. package/docs/connect-ci.md +24 -0
  23. package/docs/connect-overview.md +20 -0
  24. package/docs/connect-review-bots.md +17 -0
  25. package/docs/connect-team-server.md +12 -0
  26. package/docs/connect-terminal.md +49 -0
  27. package/docs/connect-web-agents.md +9 -0
  28. package/docs/connect-your-ai-assistant.md +59 -175
  29. package/docs/coverage-audit-and-consent.md +51 -0
  30. package/docs/evaluate-what-niro-did.md +271 -0
  31. package/docs/faq-assistants-and-mcp.md +122 -0
  32. package/docs/faq-feedback-and-receipts.md +33 -0
  33. package/docs/faq-indexing.md +127 -0
  34. package/docs/faq-install-and-setup.md +103 -0
  35. package/docs/faq-licence-and-plans.md +86 -0
  36. package/docs/faq-login-and-account.md +69 -0
  37. package/docs/faq-offline.md +34 -0
  38. package/docs/faq-performance.md +33 -0
  39. package/docs/faq-privacy-and-security.md +113 -0
  40. package/docs/faq-uninstall.md +47 -0
  41. package/docs/faq-updates.md +59 -0
  42. package/docs/faq-worktrees-and-branches.md +59 -0
  43. package/docs/faq-wrong-or-missing-answers.md +275 -0
  44. package/docs/hosted-only.md +33 -0
  45. package/docs/how-the-graph-works.md +47 -0
  46. package/docs/index.md +157 -0
  47. package/docs/licence-and-plans.md +72 -0
  48. package/docs/lifecycle-debug.md +48 -0
  49. package/docs/lifecycle-onboarding.md +32 -0
  50. package/docs/lifecycle-overview.md +77 -0
  51. package/docs/lifecycle-plan-and-design.md +44 -0
  52. package/docs/lifecycle-review.md +30 -0
  53. package/docs/lifecycle-security-and-privacy.md +35 -0
  54. package/docs/lifecycle-test.md +28 -0
  55. package/docs/lifecycle-write-code.md +35 -0
  56. package/docs/llms-full.txt +8015 -0
  57. package/docs/llms.txt +126 -0
  58. package/docs/playbook-add-a-feature.md +108 -0
  59. package/docs/playbook-change-safely.md +93 -0
  60. package/docs/playbook-data-and-events.md +107 -0
  61. package/docs/playbook-debug-after-a-change.md +96 -0
  62. package/docs/playbook-debug-config.md +95 -0
  63. package/docs/playbook-debug-exception.md +124 -0
  64. package/docs/playbook-debug-failing-service-call.md +119 -0
  65. package/docs/playbook-debug-slow-path.md +80 -0
  66. package/docs/playbook-debug-wrong-value.md +137 -0
  67. package/docs/playbook-onboard.md +95 -0
  68. package/docs/playbook-review-a-change.md +72 -0
  69. package/docs/playbook-test-a-change.md +73 -0
  70. package/docs/reference-cli.md +113 -0
  71. package/docs/reference-config-files.md +42 -0
  72. package/docs/reference-engine-commands.md +56 -0
  73. package/docs/reference-environment-variables.md +89 -0
  74. package/docs/reference-exit-codes.md +67 -0
  75. package/docs/reference-limits.md +75 -0
  76. package/docs/reference-mcp-tools.md +326 -0
  77. package/docs/reference-messages.md +180 -0
  78. package/docs/secrets-and-masking.md +48 -0
  79. package/docs/supported-c.md +130 -0
  80. package/docs/supported-cpp.md +157 -0
  81. package/docs/supported-csharp.md +211 -0
  82. package/docs/supported-go.md +258 -0
  83. package/docs/supported-java.md +226 -0
  84. package/docs/supported-javascript.md +223 -0
  85. package/docs/supported-kotlin.md +124 -0
  86. package/docs/supported-overview.md +89 -0
  87. package/docs/supported-php.md +157 -0
  88. package/docs/supported-python.md +224 -0
  89. package/docs/supported-ruby.md +162 -0
  90. package/docs/supported-rust.md +150 -0
  91. package/docs/supported-swift.md +133 -0
  92. package/docs/supported-typescript.md +220 -0
  93. package/docs/tokens-speed-and-accuracy.md +99 -0
  94. package/docs/troubleshooting.md +42 -0
  95. package/docs/uninstall.md +74 -0
  96. package/docs/what-leaves-your-machine.md +71 -0
  97. package/docs/where-things-are-stored.md +59 -0
  98. package/docs/worktrees-and-branches.md +55 -0
  99. package/package.json +5 -3
  100. package/src/commands/add-repo.js +5 -0
  101. package/src/commands/auditSend.js +67 -0
  102. package/src/commands/build.js +6 -0
  103. package/src/commands/docsSend.js +73 -0
  104. package/src/commands/engine.js +120 -0
  105. package/src/commands/engineRoutes.js +105 -0
  106. package/src/commands/guide.js +254 -0
  107. package/src/commands/init.js +6 -2
  108. package/src/commands/initLocal.js +96 -0
  109. package/src/commands/journalSend.js +47 -0
  110. package/src/commands/login.js +89 -12
  111. package/src/commands/mcp.js +22 -6
  112. package/src/commands/plan.js +99 -0
  113. package/src/commands/repoPush.js +33 -0
  114. package/src/commands/requestIntegration.js +38 -0
  115. package/src/commands/share.js +56 -0
  116. package/src/commands/start.js +451 -371
  117. package/src/commands/status.js +57 -0
  118. package/src/commands/takeover.js +6 -0
  119. package/src/commands/uninstall.js +180 -0
  120. package/src/commands/upgrade.js +38 -8
  121. package/src/commands/watch.js +420 -6
  122. package/src/index.js +174 -3
  123. package/src/lib/agentApi.js +32 -1
  124. package/src/lib/api.js +20 -6
  125. package/src/lib/auditUpload.js +293 -0
  126. package/src/lib/branchDrift.js +149 -0
  127. package/src/lib/choose.js +116 -0
  128. package/src/lib/cloudGuard.js +74 -0
  129. package/src/lib/credentials.js +2 -2
  130. package/src/lib/docsMarked.js +55 -0
  131. package/src/lib/docsState.js +213 -0
  132. package/src/lib/docsUpload.js +481 -0
  133. package/src/lib/engine.js +619 -0
  134. package/src/lib/folderSync.js +2 -1
  135. package/src/lib/graphCheck.js +40 -0
  136. package/src/lib/guide.js +293 -0
  137. package/src/lib/guideSearch.js +252 -0
  138. package/src/lib/integrationRequest.js +89 -0
  139. package/src/lib/journalUpload.js +147 -0
  140. package/src/lib/languages.js +129 -0
  141. package/src/lib/lease.js +375 -0
  142. package/src/lib/localBuild.js +285 -0
  143. package/src/lib/localEngine.js +210 -0
  144. package/src/lib/localOnboard.js +124 -0
  145. package/src/lib/localWorktrees.js +243 -0
  146. package/src/lib/machineCheck.js +58 -0
  147. package/src/lib/mcpClients.js +10 -1
  148. package/src/lib/migrationNotice.js +118 -0
  149. package/src/lib/modelFetch.js +153 -0
  150. package/src/lib/nestedOutput.js +23 -9
  151. package/src/lib/onboardingLog.js +34 -0
  152. package/src/lib/planUsage.js +165 -0
  153. package/src/lib/progressBar.js +46 -0
  154. package/src/lib/prompt.js +1 -1
  155. package/src/lib/redact.js +155 -0
  156. package/src/lib/release.js +33 -0
  157. package/src/lib/repoPush.js +495 -0
  158. package/src/lib/secret.js +1 -1
  159. package/src/lib/service.js +372 -63
  160. package/src/lib/shareCode.js +367 -0
  161. package/src/lib/shareExclusions.js +114 -0
  162. package/src/lib/shareFiles.js +129 -0
  163. package/src/lib/spinner.js +9 -5
  164. package/src/lib/watchDaemon.js +33 -10
  165. package/src/lib/watchState.js +12 -1
  166. package/src/lib/windowsPaths.js +67 -0
  167. package/src/mcp/lineCheck.js +382 -0
  168. package/src/mcp/lineName.js +92 -0
  169. package/src/mcp/localBackend.js +751 -0
  170. package/src/mcp/localMode.js +383 -0
  171. package/src/mcp/localSkill.js +189 -0
  172. package/src/mcp/server.js +610 -79
  173. package/src/mcp/setup.js +519 -131
  174. package/src/mcp/shellSearchHook.js +509 -0
  175. package/src/mcp/teardown.js +173 -91
  176. package/src/mcp/usingNiro.js +39 -0
  177. package/docs/working-on-branches.md +0 -162
@@ -0,0 +1,224 @@
1
+ # Supported in Python
2
+
3
+ <!-- GENERATED by scripts/gen-supported-docs.js. Do not edit by hand: run the script again. -->
4
+ <!-- source: catalog/python.json; catalog/python.structure.json -->
5
+ <!-- measured: coverage corpus 1bb4ebb, niro-local 2b30d4fd -->
6
+
7
+ This page lists the Python libraries, frameworks and build tools Niro was tested against, and whether it recognised each one.
8
+
9
+ **What the marks mean.**
10
+
11
+ - **Supported:** Niro recognised every way of using it that the test code tries.
12
+ - **Partly:** Niro recognised some of those ways, or recognised one only in part.
13
+ - **Not yet:** Niro did not recognise it in the test code.
14
+
15
+ The test code is made up for the purpose: one small service per library, with the names invented. So "supported" means Niro recognised it there. It is good evidence, but it is not a promise about every codebase. If Niro misses something in your code, the coverage audit reports the gap on its own (see [coverage-audit-and-consent.md](coverage-audit-and-consent.md)).
16
+ <!-- source: niro-coverage-corpus README.md ("Synthetic test code ... All names are made up"); catalog status merged pairs one to one with measured found -->
17
+
18
+ ## Web frameworks and HTTP servers (the routes your code serves)
19
+
20
+ 22 supported, 4 partly, 6 not yet.
21
+
22
+ | Name | Status | Ways of using it that were tested |
23
+ |---|---|---|
24
+ | aiohttp | supported | yes: class X(web.View) with async post(), web.view(path, X)<br>yes: app.router.add_post(path, handler)<br>yes: routes = web.RouteTableDef(); @routes.get(path)<br>yes: app.add_routes([web.get(path, handler)]) |
25
+ | apiflask | not yet | no: APIFlask app with @app.get(path) (Flask subclass) |
26
+ | AWS SAM template.yaml | supported | yes: AWS::Serverless::Function with Events Type: Api (REST API) Properties Path and Method; Handler module.function under CodeUri names the handler<br>yes: AWS::Serverless::Function with Events Type: HttpApi Properties Path and Method (ANY, greedy {proxy+}) |
27
+ | Azure Functions (Python v1 programming model) | supported | yes: <FunctionName>/function.json with a httpTrigger binding (route, methods) beside __init__.py; no route means the folder name, no methods means every verb; host.json routePrefix in front |
28
+ | Azure Functions (Python v2 programming model) | supported | yes: @app.route(route="x/{id}", methods=[...]) on a func.FunctionApp in function_app.py, served under the host.json routePrefix (default api); no methods means every verb<br>yes: bp = func.Blueprint(); @bp.route(route=...) in another file, app.register_functions(bp)<br>yes: @app.route() with no route: the route is the @app.function_name name, else the Python function's name |
29
+ | blacksheep | supported | yes: from blacksheep import get; @get(path)<br>yes: @app.router.post(path) |
30
+ | bottle | supported | yes: app = Bottle(); @app.get(path)<br>yes: from bottle import route; @route(path) on the default app<br>yes: app.mount(prefix, sub_app) with routes on the sub-app |
31
+ | chalice | supported | yes: @app.route(path, methods=[...]) on a Chalice app |
32
+ | cherrypy | partly | no: @cherrypy.expose methods, path from cherrypy.tree.mount(obj, prefix)<br>yes: MethodDispatcher with GET/POST methods on an exposed class |
33
+ | connexion | not yet | no: routes in openapi.yaml, operationId names the Python function, app.add_api(spec, base_path) |
34
+ | django | supported | yes: path(route, View.as_view()) with get/post methods<br>yes: path("prefix/", include("app.urls"))<br>yes: urlpatterns = [path("route/<int:pk>/", view)]<br>yes: re_path(r"^regex$", view) |
35
+ | django-ninja | partly | partly: api = NinjaAPI(); @api.get(path), mounted with path("prefix/", api.urls)<br>no: Router() with @router.post, api.add_router(prefix, router) |
36
+ | djangorestframework | supported | yes: @action(detail=True, methods=["post"]) on a ViewSet<br>yes: @api_view(["GET"]) function view<br>yes: class X(APIView) with post(), path(route, X.as_view())<br>yes: DefaultRouter().register(prefix, ViewSet) + include(router.urls) |
37
+ | emmett | not yet | no: @app.route(path) on Emmett |
38
+ | falcon | supported | yes: resource class with on_get/on_post, app.add_route(path, Resource()) |
39
+ | fastapi | partly | yes: app.add_api_route(path, fn, methods=[...])<br>no: @app.api_route(path, methods=["GET", "PUT"])<br>yes: @app.get(path) on FastAPI()<br>partly: APIRouter() routes, app.include_router(router, prefix=...)<br>yes: APIRouter(prefix="/x") carrying its own prefix<br>no: @app.websocket(path) |
40
+ | flask | supported | yes: app.add_url_rule(path, view_func=fn)<br>yes: Blueprint routes, app.register_blueprint(bp, url_prefix=...)<br>yes: MethodView subclass, add_url_rule(path, view_func=X.as_view(name))<br>yes: @app.route(path, methods=["GET", "POST"])<br>yes: @app.get / @app.delete with a <int:id> converter |
41
+ | flask-restful | supported | yes: Api(app, prefix).add_resource(Resource, url) with get/delete methods |
42
+ | flask-restx | supported | yes: @ns.route(path) on a Resource class, api.add_namespace(ns, path=...) |
43
+ | flask-smorest | supported | yes: flask_smorest Blueprint(url_prefix) @blp.route on a MethodView |
44
+ | hug | not yet | no: @hug.get(path) |
45
+ | litestar | partly | yes: class X(Controller) with path and @get/@post methods<br>yes: standalone @get(path) handler in route_handlers<br>partly: Router(path=..., route_handlers=[...]) |
46
+ | pyramid | not yet | no: config.add_route(name, pattern) + config.add_view(fn, route_name=...)<br>no: config.add_route(name, pattern) + @view_config(route_name=...) |
47
+ | quart | supported | yes: Quart Blueprint(url_prefix=...) with @bp.post<br>yes: @app.route(path, methods=[...]) on Quart |
48
+ | responder | not yet | no: @api.route(path) on responder.API |
49
+ | robyn | supported | yes: @app.get(path) on Robyn<br>yes: SubRouter(__file__, prefix=...) + app.include_router |
50
+ | sanic | supported | yes: @app.get(path) on Sanic<br>yes: Blueprint(name, url_prefix=...) @bp.route, app.blueprint(bp)<br>yes: HTTPMethodView subclass, app.add_route(View.as_view(), path) |
51
+ | Serverless Framework serverless.yml | supported | yes: functions.<name>.handler file.function with events httpApi ('GET /path' or method+path) or http (REST, path without a leading slash) |
52
+ | starlette | supported | yes: Mount(prefix, routes=[Route("/", HTTPEndpoint subclass)])<br>yes: routes = [Route(path, fn, methods=[...])]; Starlette(routes=routes) |
53
+ | strawberry-graphql | supported | yes: app.include_router(GraphQLRouter(schema), prefix="/graphql") |
54
+ | tornado | supported | yes: tornado.web.Application([(r"/x/([0-9]+)", Handler)]) with get/post methods<br>yes: tornado.web.url(pattern, Handler, name=...) in the Application list |
55
+ | Vercel Functions (Python runtime, /api directory) | supported | yes: api/<name>.py defining class handler(BaseHTTPRequestHandler); path from the file, verbs from its do_GET/do_POST methods<br>yes: api/orders/[order_id].py: a bracketed file name is a dynamic path segment<br>yes: api/index.py exporting a Flask or FastAPI `app`, with a vercel.json rewrite sending every path to it; the app's own routes are the endpoints |
56
+
57
+ ## The language's own HTTP server
58
+
59
+ 3 supported, 0 partly, 2 not yet.
60
+
61
+ | Name | Status | Ways of using it that were tested |
62
+ |---|---|---|
63
+ | asyncio streams (stdlib) | not yet | no: asyncio.start_server(handler, host, port) raw TCP listener |
64
+ | http.server (stdlib) | supported | yes: BaseHTTPRequestHandler subclass dispatching through a dict of paths and getattr<br>yes: do_GET comparing self.path == "/x"<br>yes: do_DELETE testing self.path in ("/x", "/x/")<br>yes: do_GET with urlparse(self.path).path.startswith("/x/")<br>yes: SimpleHTTPRequestHandler subclass adding one API path<br>yes: do_POST reading urlparse(self.path).path into a local, then == |
65
+ | socketserver (stdlib) | not yet | no: socketserver.TCPServer + StreamRequestHandler line protocol |
66
+ | wsgiref (stdlib) | supported | yes: WSGI callable dispatching on environ["PATH_INFO"], served by wsgiref.make_server |
67
+ | xmlrpc.server / xmlrpc.client (stdlib) | supported | yes: SimpleXMLRPCServer.register_function(fn, "dotted.name")<br>yes: SimpleXMLRPCServer.register_instance(obj) |
68
+
69
+ ## HTTP client libraries (the calls your code makes to other services)
70
+
71
+ 10 supported, 3 partly, 2 not yet.
72
+
73
+ | Name | Status | Ways of using it that were tested |
74
+ |---|---|---|
75
+ | aiohttp | supported | yes: aiohttp.ClientSession(base_url=...) + relative path<br>yes: async with ClientSession() as s: async with s.get(url) as resp<br>yes: session = ClientSession(); await session.post(url) |
76
+ | gql | supported | yes: gql Client(transport=RequestsHTTPTransport(url=...)).execute(query) |
77
+ | grequests | supported | yes: grequests.get(url) mapped with grequests.map |
78
+ | httpcore | partly | yes: httpcore.request("GET", url)<br>partly: with httpcore.ConnectionPool() as pool: pool.request(verb, url) |
79
+ | httplib2 | supported | yes: httplib2.Http().request(url, "GET") |
80
+ | httpx | partly | yes: async with httpx.AsyncClient() as c: await c.patch(url)<br>partly: httpx.AsyncClient(base_url=SETTING) + relative path<br>partly: with httpx.Client() as c: c.post(url)<br>yes: httpx.Client(base_url="http://svc:port").get("/relative")<br>yes: httpx.get(f"http://{HOST}:{PORT}/path") from settings constants<br>yes: httpx.get(url) module function<br>yes: httpx.request("POST", url) |
81
+ | niquests | supported | yes: niquests.get(url)<br>yes: with niquests.Session() as s: s.post(url) |
82
+ | pycurl | supported | yes: pycurl.Curl(); c.setopt(c.URL, url); c.perform() |
83
+ | requests | supported | yes: requests.get(BASE + "/path/" + str(id))<br>yes: base URL from os.environ.get("X_URL", default) then requests.get(f"{BASE}/path")<br>yes: base URL from os.getenv("X_URL", default)<br>yes: requests.post(f"{CONST_BASE}/path")<br>yes: requests.get("http://svc:port/path")<br>yes: requests.request("DELETE", url)<br>yes: s = requests.Session(); s.put(url)<br>yes: with requests.Session() as s: s.get(url) |
84
+ | requests-futures | supported | yes: FuturesSession().get(url).result() |
85
+ | tornado | partly | yes: await AsyncHTTPClient().fetch(url)<br>yes: AsyncHTTPClient().fetch(url, method="POST", body=...)<br>yes: HTTPClient().fetch(url)<br>partly: fetch(HTTPRequest(url, method="GET")) |
86
+ | treq | supported | yes: treq.get(url) / treq.post(url) (Twisted) |
87
+ | uplink | not yet | no: uplink Consumer subclass with @get("/path") methods, base_url at construction |
88
+ | urllib3 | supported | yes: urllib3.PoolManager().request("GET", url)<br>yes: urllib3.request("POST", url) (urllib3 2.x helper) |
89
+ | zeep | not yet | no: zeep.Client(wsdl_url).service.Operation(...) |
90
+
91
+ ## The language's own HTTP client
92
+
93
+ 3 supported, 0 partly, 2 not yet.
94
+
95
+ | Name | Status | Ways of using it that were tested |
96
+ |---|---|---|
97
+ | asyncio streams (stdlib) | not yet | no: asyncio.open_connection(host, port) raw TCP client |
98
+ | http.client (stdlib) | supported | yes: http.client.HTTPConnection(host, port).request(verb, path)<br>yes: HTTPConnection("host:port") then request(verb, path)<br>yes: http.client.HTTPSConnection(host, port).request(verb, path) |
99
+ | socket (stdlib) | not yet | no: socket.create_connection((host, port)) raw TCP |
100
+ | urllib.request (stdlib) | supported | yes: urllib.request.Request(url, method="DELETE") passed to urlopen<br>yes: Request(url, data=..., method="POST") passed to urlopen<br>yes: urlopen(f"{BASE}/path") imported by name<br>yes: urllib.request.urlopen("http://host/path") |
101
+ | xmlrpc.client (stdlib) | supported | yes: xmlrpc.client.ServerProxy(url).dotted.method(...)<br>yes: with ServerProxy(url) as proxy: proxy.method(...) |
102
+
103
+ ## gRPC
104
+
105
+ 0 supported, 2 partly, 0 not yet.
106
+
107
+ | Name | Status | Ways of using it that were tested |
108
+ |---|---|---|
109
+ | betterproto + grpclib | partly | partly: grpclib Channel + betterproto InventoryStub(channel).lookup(req) awaited<br>yes: class X(<S>Base) from a betterproto module, snake_case async methods, grpclib Server([X()]) |
110
+ | grpcio | partly | yes: grpc.aio.server() + async servicer methods, add_<S>Servicer_to_server<br>yes: async with grpc.aio.insecure_channel(t) as ch: await <S>Stub(ch).M(req)<br>yes: stub held in a typed attribute (stub: <S>Stub), called later (declared_type)<br>yes: channel.unary_unary("/pkg.S/M") called directly (literal_path)<br>yes: class X(<mod>_pb2_grpc.<S>Servicer) + add_<S>Servicer_to_server (grpcio-servicer)<br>yes: server-streaming RPC method yielding replies on the servicer<br>yes: stub.M.future(req) on a target from the environment<br>yes: for update in stub.M(req) (server streaming)<br>yes: <mod>_pb2_grpc.<S>Stub(grpc.insecure_channel(t)).M(req) (factory/constructor)<br>no: stub.M.with_call(req) over grpc.secure_channel<br>no: server.add_generic_rpc_handlers((grpc.method_handlers_generic_handler("pkg.S", {...}),)) written by hand<br>no: grpc.intercept_channel(channel, interceptor) then stub calls<br>no: Connect protocol client (connect-python, early adoption) |
111
+
112
+ ## Databases and data stores
113
+
114
+ 21 supported, 2 partly, 11 not yet.
115
+
116
+ | Name | Status | Ways of using it that were tested |
117
+ |---|---|---|
118
+ | aiosqlite | supported | yes: async with aiosqlite.connect(...) as db: db.execute(sql) |
119
+ | asyncpg | supported | yes: await asyncpg.connect(dsn) then conn.fetch(sql)<br>yes: create_pool + pool.acquire() + conn.execute(sql) |
120
+ | beanie | supported | yes: Document subclass with Settings.name, await Model.find_one(...) |
121
+ | boto3 (DynamoDB) | supported | yes: boto3.client("dynamodb").query(TableName=...)<br>yes: boto3.resource("dynamodb").Table("x").get_item/put_item |
122
+ | boto3 (S3) | supported | yes: s3.put_object(Bucket=..., Key=...)<br>yes: s3.upload_file(path, bucket, key) |
123
+ | cassandra-driver | supported | yes: Cluster().connect(ks).execute(cql), prepare + execute |
124
+ | clickhouse-connect | supported | yes: clickhouse_connect client.query(sql) / client.insert(table, rows) |
125
+ | django (ORM) | supported | yes: Model.objects.create(...) (table from the model name)<br>yes: Model.objects.filter(...) with Meta.db_table<br>yes: Model.objects.raw(sql)<br>yes: django.db.connection.cursor().execute(sql) |
126
+ | duckdb | supported | yes: duckdb.connect(path).execute(sql) |
127
+ | elasticsearch | supported | yes: Elasticsearch(url).index(index=...) / search(index=...) |
128
+ | google-cloud-firestore | supported | yes: firestore.Client().collection("x").document(id).get/set |
129
+ | influxdb-client | not yet | no: write_api.write(bucket=...), query_api.query(flux) |
130
+ | mongoengine | not yet | no: Document with meta collection, Model.objects(...) |
131
+ | motor | not yet | no: AsyncIOMotorClient db.coll.find_one awaited |
132
+ | mysqlclient (MySQLdb) | supported | yes: MySQLdb.connect(...).cursor().execute(sql) |
133
+ | neo4j | supported | yes: driver.session().run(cypher) / driver.execute_query(cypher) |
134
+ | opensearch-py | not yet | no: OpenSearch(hosts).index/search(index=...) |
135
+ | oracledb | not yet | no: oracledb.connect(...).cursor().execute(sql) |
136
+ | peewee | supported | yes: Model.create(...) / Model.update(...).execute()<br>yes: Model.select().where(...) with Meta.table_name |
137
+ | pony | not yet | no: class X(db.Entity) with select(x for x in X) |
138
+ | psycopg (3) | supported | yes: psycopg.AsyncConnection + cursor.execute(sql)<br>yes: psycopg.connect(...).execute(sql) shortcut |
139
+ | psycopg2 | supported | yes: psycopg2.connect(dsn).cursor().execute("SELECT ... FROM t")<br>yes: with conn.cursor() as cur: cur.executemany(sql, rows)<br>yes: cur.execute(SQL_CONSTANT, params) with the SQL in a module constant |
140
+ | pymemcache | supported | yes: pymemcache Client.get/set(key) |
141
+ | pymongo | not yet | no: MongoClient()[db].coll.find_one(...) (attribute access)<br>no: db.get_collection("x").insert_one(...)<br>no: db["x"].update_one(...) (subscript access) |
142
+ | pymysql | supported | yes: pymysql.connect(...); with conn.cursor() as c: c.execute(sql) |
143
+ | pynamodb | not yet | no: pynamodb Model with Meta.table_name, Model.get(key) / save() |
144
+ | pyodbc | supported | yes: pyodbc.connect(conn_str).cursor().execute(sql) |
145
+ | redis (redis-py) | partly | yes: redis.asyncio.Redis().get(key) awaited<br>yes: r.hset(...), client.lpush(...)<br>yes: pipe = r.pipeline(); pipe.set(...); pipe.execute()<br>partly: redis.Redis(host).set/get(key) |
146
+ | snowflake-connector-python | not yet | no: snowflake.connector.connect(...).cursor().execute(sql) |
147
+ | sqlalchemy | partly | no: AsyncSession.execute(select(Model)) awaited<br>no: Core Table("t", metadata) + conn.execute(insert(table))<br>no: session.execute(select(Model).where(...))<br>yes: session.add(Model(...)) with __tablename__<br>no: session.query(Model).filter(...).first() (legacy)<br>yes: conn.execute(text("SELECT ... FROM t")) |
148
+ | sqlite3 (stdlib) | supported | yes: sqlite3.connect(path).execute(sql)<br>yes: conn.cursor().execute(sql) / executescript<br>yes: with sqlite3.connect(path) as conn: conn.executemany(sql, rows) |
149
+ | sqlmodel | not yet | no: SQLModel Session.add(Model(...))<br>no: SQLModel Session.exec(select(Model)) |
150
+ | supabase | not yet | no: supabase.table("x").select("*").execute() |
151
+ | tortoise-orm | supported | yes: await Model.create(...) with Meta.table<br>yes: await Model.filter(...).all() |
152
+
153
+ ## Messaging and events (queues, topics, streams)
154
+
155
+ 11 supported, 17 partly, 3 not yet.
156
+
157
+ | Name | Status | Ways of using it that were tested |
158
+ |---|---|---|
159
+ | aio-pika | partly | no: channel.default_exchange.publish(Message, routing_key="q")<br>partly: queue = await channel.declare_queue("q"); await queue.consume(cb)<br>partly: async with queue.iterator() as it: async for message in it |
160
+ | aiokafka | supported | yes: AIOKafkaConsumer("topic", ...); async for msg in consumer<br>yes: AIOKafkaProducer.send_and_wait("topic", value) |
161
+ | arq | partly | partly: await redis.enqueue_job("function_name", ...)<br>no: class WorkerSettings: functions = [fn] |
162
+ | azure-servicebus | partly | yes: client.get_queue_receiver(queue_name=...).receive_messages()<br>no: client.get_queue_sender(queue_name=...).send_messages(...) |
163
+ | boto3 (EventBridge) | partly | partly: boto3.client("events").put_events(Entries=[{... EventBusName}]) |
164
+ | boto3 (Kinesis) | supported | yes: boto3.client("kinesis").put_record(StreamName=...) |
165
+ | boto3 (SNS) | supported | yes: boto3.client("sns").publish(TopicArn=...) |
166
+ | boto3 (SQS) | supported | yes: sqs.receive_message(QueueUrl=...) polling loop<br>yes: sqs.send_message(QueueUrl=CONST, MessageBody=...) |
167
+ | celery | partly | yes: @app.task(name="x") on a Celery app<br>no: app.signature("task").apply_async(...)<br>no: app.signature("task").delay(...)<br>yes: app.send_task("task.name", args=[...])<br>yes: @shared_task |
168
+ | confluent-kafka | supported | yes: Consumer(conf).subscribe([TOPIC_CONST]) then poll<br>yes: Producer(conf).produce("topic", value=...) |
169
+ | django (signals) | supported | yes: @receiver(post_save, sender=Model) (in-process signal) |
170
+ | dramatiq | partly | yes: @dramatiq.actor(queue_name=...)<br>no: broker.enqueue(dramatiq.Message(actor_name=..., queue_name=...)) |
171
+ | faststream | partly | yes: async with KafkaBroker(...) as broker: await broker.publish(msg, topic=...)<br>partly: @broker.publisher("topic") on a subscriber<br>yes: @broker.subscriber("topic") |
172
+ | faust-streaming | not yet | no: @app.agent(app.topic("x")) |
173
+ | google-cloud-pubsub | partly | partly: PublisherClient().publish(topic_path(project, topic), data)<br>no: SubscriberClient().subscribe(subscription_path(project, sub), callback) |
174
+ | huey | supported | yes: @huey.task() / @huey.periodic_task(crontab(...)) |
175
+ | kafka-python | partly | yes: KafkaConsumer("topic", bootstrap_servers=...)<br>yes: KafkaConsumer(...).subscribe(["topic"])<br>yes: KafkaProducer.send(TOPIC_CONSTANT, ...)<br>partly: KafkaProducer(...).send("topic", value) |
176
+ | kombu | partly | no: ConsumerMixin.get_consumers returning Consumer(queues=[Queue("q")])<br>partly: kombu Producer(channel, exchange).publish(body, routing_key="q") |
177
+ | multiprocessing (stdlib) | not yet | no: multiprocessing.Queue between processes of one program (not cross-service)<br>no: BaseManager(address=(host, port)) serving a queue; client manager.connect() + get_queue().put |
178
+ | nameko | partly | no: nameko standalone event_dispatcher(config)(service, event, payload)<br>partly: @event_handler("service", "event")<br>partly: @rpc method on a nameko service class<br>no: ClusterRpcProxy(config) as rpc: rpc.service.method(...) |
179
+ | nats-py | supported | yes: js = nc.jetstream(); await js.publish(subject, data)<br>yes: await js.subscribe(subject, durable=...)<br>yes: nc = await nats.connect(url); await nc.publish(subject, data)<br>yes: await nc.subscribe(subject, cb=handler) |
180
+ | paho-mqtt | partly | no: paho.mqtt.publish.single(topic, payload, hostname=...)<br>yes: client.subscribe("topic") with on_message callback |
181
+ | pika | supported | yes: channel.basic_consume(queue="q", on_message_callback=cb)<br>yes: channel.basic_publish(exchange="", routing_key="q", body=...) |
182
+ | pulsar-client | supported | yes: client.create_producer(topic).send(data)<br>yes: client.subscribe(topic, subscription_name=...) |
183
+ | python-socketio | partly | partly: socketio.AsyncClient().emit("event", data)<br>partly: await sio.emit("event", data) on the server<br>yes: @sio.on("event") handler |
184
+ | pyzmq | not yet | no: ctx.socket(zmq.PULL).bind("tcp://*:port")<br>no: ctx.socket(zmq.PUSH).connect("tcp://host:port").send_json(...) |
185
+ | redis (pub/sub, streams) | partly | yes: r.publish("channel", message)<br>yes: pubsub = r.pubsub(); pubsub.subscribe("channel")<br>partly: r.xadd("stream", fields)<br>no: r.xgroup_create + r.xreadgroup(group, consumer, {"stream": ">"}) |
186
+ | rq | partly | partly: Queue("q", connection=Redis()).enqueue("module.fn", ...)<br>no: Worker([Queue("q")]).work() |
187
+ | stomp.py | supported | yes: stomp.Connection(...).send(destination="/queue/x", body=...)<br>yes: conn.subscribe(destination="/queue/x", id=...) with a ConnectionListener |
188
+ | taskiq | partly | partly: AsyncKicker(task_name="x", broker=b, labels={}).kiq(...)<br>no: @broker.task(task_name="x") |
189
+ | websockets | partly | no: async with websockets.connect("ws://svc:port/") as ws: await ws.send(...)<br>partly: websockets.serve(handler, host, port) |
190
+
191
+ ## Build tools and package managers
192
+
193
+ 14 supported, 0 partly, 0 not yet.
194
+
195
+ | Name | Status | Ways of using it that were tested |
196
+ |---|---|---|
197
+ | Bazel (rules_python) | supported | yes: MODULE.bazel, py_binary and py_library targets, deps by label //libs/shop_common |
198
+ | conda (environment.yml) | supported | yes: environment.yml per module, no pyproject, setup.py or requirements.txt |
199
+ | Flit (flit_core) | supported | yes: flit_core backend, module named after the project, flat layout (package folder at the top, no src/) |
200
+ | Hatch (hatchling) | supported | yes: hatchling backend, wheel packages list, a service run with python -m <package> (src/<pkg>/__main__.py) and a hatch env script |
201
+ | maturin (PyO3) | supported | yes: pyproject build-backend maturin with python-source = "python", Cargo.toml in the same folder, Rust src/lib.rs |
202
+ | Pants | supported | yes: pants.toml source root /src/python, BUILD files with python_sources() and pex_binary targets, 3rdparty requirements |
203
+ | PDM | supported | yes: pdm-backend, file:// path dependency with ${PROJECT_ROOT}, [tool.pdm.scripts], main.py service |
204
+ | pip (requirements.txt only) | supported | yes: requirements.txt with an -e ../shop-common line, scripts importing sibling files by bare module name (from handlers import ...) |
205
+ | Pipenv | supported | yes: Pipfile with an editable path dependency; one service started by app.py, one by worker.py |
206
+ | Poetry | supported | yes: [tool.poetry] with packages from src, path dependency on ../shop-common (develop = true), [tool.poetry.scripts]; second service started by app.py |
207
+ | setuptools (pyproject build-system + setup.cfg) | supported | yes: pyproject.toml holds only [build-system]; name, packages and console scripts live in setup.cfg |
208
+ | setuptools (setup.cfg) | supported | yes: metadata and [options.entry_points] console_scripts in setup.cfg, setup.py is a two-line shim |
209
+ | setuptools (setup.py) | supported | yes: setup.py with find_packages and console_scripts entry_points; library without entry_points<br>yes: a source module named setup.py inside a package (def setup(registry)), not a packaging script |
210
+ | uv (workspace) | supported | yes: root pyproject with [tool.uv.workspace] members globs and [tool.uv.sources] workspace = true; root holds no code |
211
+
212
+ ## Project and folder layouts
213
+
214
+ 7 supported, 0 partly, 0 not yet.
215
+
216
+ | Name | Status | Ways of using it that were tested |
217
+ |---|---|---|
218
+ | Django | supported | yes: Django project (manage.py, settings package, apps orders and billing), a reusable Django app as a library, a second service |
219
+ | Jupyter | supported | yes: an analysis/ folder of .ipynb notebooks (with its own requirements.txt) next to the packages |
220
+ | none (loose scripts) | supported | yes: a scripts/ folder of executable .py files with no build file, beside packaged services |
221
+ | pytest / unittest | supported | yes: tests/ with conftest.py, tests inside the package, *_test.py beside code, and a top-level integration-tests project with its own pyproject |
222
+ | setuptools (PEP 420 namespace) | supported | yes: three distributions share the namespace acme with no acme/__init__.py |
223
+ | setuptools (PEP 621 pyproject) | supported | yes: [project] + [project.scripts] + [tool.setuptools.packages.find] where=["src"], two packages in one distribution |
224
+ | setuptools (PEP 621) | supported | yes: a source package literally named build/ (not ignored) in one module, and a gitignored build/ output in another<br>yes: services run with python -m: one __main__.py at the folder top, one inside the package (invoice_worker/__main__.py)<br>yes: build/lib copy, *.egg-info, dist/, .tox, venv/ with pyvenv.cfg, .venv, vendor/, third_party/ copy, protoc output under generated/<br>yes: services/*, libs/* and tools/* each with its own pyproject, no workspace file, no compose<br>yes: a developer tool with its own pyproject and console script nested inside a service folder<br>yes: service code in a package folder: Flask app at shop/app.py, FastAPI app at app/main.py with app/routers/ (the python-mono shape), no console script<br>yes: two libraries side by side: shop-common in src layout, pricing-rules in flat layout |
@@ -0,0 +1,162 @@
1
+ # Supported in Ruby
2
+
3
+ <!-- GENERATED by scripts/gen-supported-docs.js. Do not edit by hand: run the script again. -->
4
+ <!-- source: catalog/ruby.json; catalog/ruby.structure.json -->
5
+ <!-- measured: coverage corpus 1bb4ebb, niro-local 2b30d4fd -->
6
+
7
+ This page lists the Ruby libraries, frameworks and build tools Niro was tested against, and whether it recognised each one.
8
+
9
+ **What the marks mean.**
10
+
11
+ - **Supported:** Niro recognised every way of using it that the test code tries.
12
+ - **Partly:** Niro recognised some of those ways, or recognised one only in part.
13
+ - **Not yet:** Niro did not recognise it in the test code.
14
+
15
+ The test code is made up for the purpose: one small service per library, with the names invented. So "supported" means Niro recognised it there. It is good evidence, but it is not a promise about every codebase. If Niro misses something in your code, the coverage audit reports the gap on its own (see [coverage-audit-and-consent.md](coverage-audit-and-consent.md)).
16
+ <!-- source: niro-coverage-corpus README.md ("Synthetic test code ... All names are made up"); catalog status merged pairs one to one with measured found -->
17
+
18
+ ## Web frameworks and HTTP servers (the routes your code serves)
19
+
20
+ 5 supported, 3 partly, 3 not yet.
21
+
22
+ | Name | Status | Ways of using it that were tested |
23
+ |---|---|---|
24
+ | AWS SAM template.yaml | supported | yes: AWS::Serverless::Function (Runtime ruby3.3, Handler file.method under CodeUri) with Events Type HttpApi or Api Path and Method |
25
+ | cuba | not yet | no: Cuba.define do on get, "vendors" do ... end end |
26
+ | grape | partly | partly: resource :shipments with version "v1", using: :path and prefix :api |
27
+ | hanami | supported | yes: class Routes < Hanami::Routes with get "/stores", to: "stores.index" and scope |
28
+ | padrino | not yet | no: App.controllers :reviews do get :index ... post :create, map: "/reviews" end |
29
+ | rails | partly | yes: get "/orders", to: "orders#index" in config/routes.rb<br>yes: get "orders/:id" written without a leading slash<br>yes: get "/legacy/orders" => "orders#legacy"<br>no: resources :carts, only: [...] expands to the REST routes<br>yes: namespace :admin do get "reports" ... end prefixes /admin<br>yes: scope path: "/v1" do ... end<br>yes: match "/checkout", to: ..., via: [:get, :post]<br>yes: root "home#index" |
30
+ | roda | not yet | no: route do \|r\| r.on "wishlists" do r.get true ... end; r.is "health" ... end |
31
+ | Serverless Framework serverless.yml | supported | yes: functions.<name>.handler file.method with events httpApi or http |
32
+ | sinatra | supported | yes: top-level get "/tickets" do ... end in a classic app<br>yes: class X < Sinatra::Base with get/delete blocks |
33
+ | sinatra-contrib | partly | partly: namespace "/api" do get "/status" end (Sinatra::Namespace) |
34
+ | Vercel Functions (Ruby runtime, /api directory) | supported | yes: api/<name>.rb defining Handler = Proc.new do \|request, response\|: the file path is the route, every verb<br>yes: api/<name>.rb defining class Handler < WEBrick::HTTPServlet::AbstractServlet with do_GET/do_POST: verbs from the do_ methods<br>yes: api/wishlists/[id].rb: a bracketed file name is a dynamic path segment |
35
+
36
+ ## The language's own HTTP server
37
+
38
+ 0 supported, 1 partly, 2 not yet.
39
+
40
+ | Name | Status | Ways of using it that were tested |
41
+ |---|---|---|
42
+ | drb | not yet | no: DRb.start_service("druby://host:port", front_object) |
43
+ | rack | not yet | no: def call(env) branching on env["PATH_INFO"] and env["REQUEST_METHOD"]<br>no: Rack::Request#get? and #path_info == "/gift-cards"<br>no: map "/admin/ping" do run lambda end in config.ru or Rack::Builder |
44
+ | webrick | partly | no: server.mount_proc "/receipts" do \|req, res\| ... end<br>yes: server.mount "/refunds", RefundServlet where the servlet defines do_GET / do_POST |
45
+
46
+ ## HTTP client libraries (the calls your code makes to other services)
47
+
48
+ 2 supported, 7 partly, 1 not yet.
49
+
50
+ | Name | Status | Ways of using it that were tested |
51
+ |---|---|---|
52
+ | activeresource | not yet | no: class X < ActiveResource::Base; self.site = base; X.find(:all) is GET /products.json |
53
+ | excon | partly | yes: Excon.get(absolute url)<br>partly: Excon.new(base).get(path: "/receipts")<br>no: conn.request(method: :post, path: ...) |
54
+ | faraday | partly | no: Faraday.new(url: base) then conn.get(relative path)<br>partly: conn.post(path) do \|req\| req.body = ... end<br>yes: Faraday.get(absolute url)<br>partly: Faraday.new(ENV.fetch("RODA_URL")) with the value in docker-compose<br>no: conn.run_request(:delete, path, body, headers) |
55
+ | http (http.rb) | partly | yes: HTTP.get(url)<br>yes: HTTP.auth(token).post(url, json: ...)<br>no: HTTP.persistent(base) do \|http\| http.get(path) end |
56
+ | httparty | partly | yes: HTTParty.get(absolute url)<br>no: include HTTParty; base_uri "..."; self.class.get(path) |
57
+ | httpclient | supported | yes: HTTPClient.new.get(url) and .post(url, body) |
58
+ | httpx | supported | yes: HTTPX.get(url) and HTTPX.post(url, json:) |
59
+ | rest-client | partly | yes: RestClient.get(url) and RestClient.post(url, payload, headers)<br>yes: RestClient::Resource.new(base)["vendors"].get<br>no: RestClient::Request.execute(method: :delete, url: ...) |
60
+ | several (main project) | partly | partly: main calls a server in each framework through a different client form |
61
+ | typhoeus | partly | yes: Typhoeus.get(url)<br>no: Typhoeus::Request.new(url, method: :post).run<br>no: hydra.queue(Typhoeus::Request.new(url)); hydra.run |
62
+
63
+ ## The language's own HTTP client
64
+
65
+ 0 supported, 2 partly, 1 not yet.
66
+
67
+ | Name | Status | Ways of using it that were tested |
68
+ |---|---|---|
69
+ | drb | not yet | no: DRbObject.new_with_uri("druby://host:port").method(args) |
70
+ | net/http | partly | yes: Net::HTTP.get(URI(url))<br>yes: Net::HTTP.get_response(URI.parse(url))<br>yes: Net::HTTP.post(URI(url), body, headers) and post_form<br>no: Net::HTTP.start(host, port) do \|http\| http.get(path) end<br>no: http.request(Net::HTTP::Delete.new(path)) and Net::HTTP::Post.new(uri) |
71
+ | open-uri | partly | yes: URI.open(url).read<br>partly: URI(url).open { } and URI.parse(url).read |
72
+
73
+ ## gRPC
74
+
75
+ 0 supported, 2 partly, 0 not yet.
76
+
77
+ | Name | Status | Ways of using it that were tested |
78
+ |---|---|---|
79
+ | grpc | partly | yes: class X < Pkg::S::Service with snake_case methods, GRPC::RpcServer#handle(X)<br>no: Pkg::S::Stub.new(addr, creds) then stub.place_order(req)<br>yes: stub.list_orders(req) returns an enumerator<br>no: stub.place_order(req, return_op: true).execute<br>no: GRPC::ClientStub#request_response("/pkg.S/M", req, marshal, unmarshal) |
80
+ | gruf | partly | yes: class X < Gruf::Controllers::Base; bind Pkg::S::Service; def place_order<br>no: Gruf::Client.new(service: Pkg::S, options: { hostname: }) then client.call(:PlaceOrder, ...) |
81
+
82
+ ## Databases and data stores
83
+
84
+ 8 supported, 2 partly, 8 not yet.
85
+
86
+ | Name | Status | Ways of using it that were tested |
87
+ |---|---|---|
88
+ | activerecord | partly | yes: Order.where(...) on a model with self.table_name<br>yes: Order.create!(attrs) and order.update!(...)<br>yes: model without table_name: Coupon reads table coupons<br>no: ActiveRecord::Base.connection.execute(raw SQL)<br>yes: Order.find_by_sql("SELECT ...") |
89
+ | aws-sdk-dynamodb | not yet | no: Aws::DynamoDB::Client#put_item(table_name:) and query |
90
+ | aws-sdk-s3 | not yet | no: Aws::S3::Client#put_object(bucket:) and get_object<br>no: Aws::S3::Resource.new.bucket(name).object(key).put(body:) |
91
+ | dalli | supported | yes: Dalli::Client.new(servers).set(key, value, ttl) |
92
+ | dynamoid | supported | yes: include Dynamoid::Document; table name: :wishes; Wish.where(...).all |
93
+ | elasticsearch (elasticsearch-ruby) | not yet | no: Elasticsearch::Client#index(index:) and search(index:) |
94
+ | File, FileUtils (core) | supported | yes: File.write(path, data), File.read(path), FileUtils.mkdir_p |
95
+ | mongo | not yet | no: client[:ratings].insert_one / find |
96
+ | mongoid | supported | yes: include Mongoid::Document; store_in collection: ...; Review.where / create! |
97
+ | mysql2 | supported | yes: Mysql2::Client.new(...).query(sql) and prepare(sql).execute |
98
+ | pg | supported | yes: PG.connect(...).exec(sql) and exec_params(sql, params) |
99
+ | rails (ActiveSupport cache) | not yet | no: Rails.cache.fetch(key, expires_in:) { } and Rails.cache.delete(key) |
100
+ | redis (redis-rb) | supported | yes: Redis.new(url:); redis.set / get / lpush |
101
+ | redis-client | not yet | no: RedisClient.config(...).new_client.call("SET", key, value) |
102
+ | searchkick | not yet | no: class Product < ApplicationRecord; searchkick; end; Product.search(text) |
103
+ | sequel | partly | partly: DB[:invoices].where(...).all and DB[:invoices].insert(...)<br>yes: class Invoice < Sequel::Model(:invoices); Invoice.where(...)<br>no: DB.run(raw SQL) |
104
+ | several (main project) | not yet | no: main writes tables, queues and topics that services read |
105
+ | sqlite3 | supported | yes: SQLite3::Database.new(path).execute(sql) |
106
+
107
+ ## Messaging and events (queues, topics, streams)
108
+
109
+ 5 supported, 7 partly, 9 not yet.
110
+
111
+ | Name | Status | Ways of using it that were tested |
112
+ |---|---|---|
113
+ | actioncable | not yet | no: ActionCable.server.broadcast("orders_channel", data) and stream_from "orders_channel" |
114
+ | activejob | not yet | no: Job.perform_later(args) and Job.set(wait:).perform_later<br>no: class X < ApplicationJob; queue_as :shipping; def perform |
115
+ | activesupport (Notifications) | not yet | no: ActiveSupport::Notifications.instrument("order.placed") and subscribe("order.placed") |
116
+ | aws-sdk-sns | supported | yes: Aws::SNS::Client#publish(topic_arn:, message:) |
117
+ | aws-sdk-sqs | partly | partly: Aws::SQS::Client#send_message(queue_url:) and receive_message |
118
+ | bunny | partly | no: channel.topic("orders").publish(body, routing_key:)<br>yes: channel.default_exchange.publish(body, routing_key: queue)<br>partly: channel.queue("invoice-requests").subscribe { \|d, p, body\| } |
119
+ | delayed_job | not yet | no: Delayed::Job.enqueue(job, queue:) and Object.delay.method(args) |
120
+ | google-cloud-pubsub | partly | partly: pubsub.topic(name).publish(data) and subscription(name).listen { } |
121
+ | karafka | partly | partly: routes.draw { topic :loyalty_events { consumer X } } with X#consume<br>yes: Karafka.producer.produce_async(topic:, payload:) |
122
+ | nats-pure | not yet | no: NATS.connect(url); nats.publish(subject, data); nats.subscribe(subject) { } |
123
+ | rdkafka | supported | yes: producer.produce(topic: "payments", payload:) and consumer.subscribe("payments-settled") |
124
+ | redis (redis-rb) | supported | yes: redis.publish(channel, msg) and redis.subscribe(channel) { \|on\| on.message { } } |
125
+ | resque | not yet | no: Resque.enqueue(JobClass, args) with @queue and self.perform |
126
+ | ruby-kafka | supported | yes: kafka.deliver_message(value, topic:) and consumer.subscribe + each_message |
127
+ | rufus-scheduler | supported | yes: scheduler.every "10m" do ... end and scheduler.cron "0 3 * * *" |
128
+ | shoryuken | partly | partly: include Shoryuken::Worker; shoryuken_options queue: ...; Worker.perform_async(body) |
129
+ | sidekiq | partly | partly: include Sidekiq::Job (the name since 6.3) with perform<br>partly: include Sidekiq::Worker with perform<br>no: Job.perform_async(args) and perform_in(seconds, args)<br>no: Job.set(queue: "critical").perform_async(args)<br>no: Sidekiq::Client.push("class" => "Job", "queue" => ...) |
130
+ | sneakers | partly | partly: include Sneakers::Worker; from_queue "q"; def work(msg) |
131
+ | sucker_punch | not yet | no: include SuckerPunch::Job; Job.perform_async(args) |
132
+ | waterdrop | not yet | no: WaterDrop::Producer.new { }.produce_sync(topic:, payload:) |
133
+ | wisper | not yet | no: include Wisper::Publisher; broadcast(:order_placed, ...); flow.subscribe(listener) / on(:event) |
134
+
135
+ ## Build tools and package managers
136
+
137
+ 6 supported, 1 partly, 0 not yet.
138
+
139
+ | Name | Status | Ways of using it that were tested |
140
+ |---|---|---|
141
+ | bundler | supported | yes: Gemfile plus config.ru Sinatra app, lib/, spec/, gems installed into vendor/bundle |
142
+ | bundler (appraisal) | supported | yes: gemfiles/*.gemfile variants generated by Appraisal beside the real Gemfile |
143
+ | bundler (path gems) | supported | yes: gems/* libraries and apps/* that load them through `gem "x", path: "../../gems/x"` |
144
+ | none (plain Ruby) | supported | yes: plain scripts: bin/*.rb load lib/*.rb with require_relative; no Gemfile |
145
+ | rake | supported | yes: a task project: Rakefile and lib/tasks/*.rake, no Gemfile |
146
+ | rubygems (gemspec executables) | partly | partly: a command-line gem: exe/shopctl named in spec.executables, Thor CLI in lib/ |
147
+ | rubygems (gemspec) + bundler | supported | yes: a library gem: .gemspec, Gemfile with `gemspec`, lib/<name>/, spec/, sig/ RBS, pkg/ and doc/ output |
148
+
149
+ ## Project and folder layouts
150
+
151
+ 8 supported, 0 partly, 0 not yet.
152
+
153
+ | Name | Status | Ways of using it that were tested |
154
+ |---|---|---|
155
+ | bundler (several apps) | supported | yes: services/orders (Rack), services/billing (Rails) and libs/shop_events (gem), each with its own Gemfile |
156
+ | hanami 2 | supported | yes: a Hanami 2 app with slices/admin and slices/main beside app/ |
157
+ | packwerk / packs-rails | supported | yes: one Rails app split into packs/<name>/ (package.yml, app/), all deployed together |
158
+ | rails | supported | yes: a Rails app: bin/rails, app/, lib/, test/ (Minitest), db/schema.rb (generated), public/assets (compiled), tmp/, log/ |
159
+ | rails engines | supported | yes: a host Rails app with engines/billing (an isolated engine gem with its own test/dummy app) |
160
+ | RSpec, Minitest, Cucumber | supported | yes: a library with spec/ (RSpec), test/ (Minitest) and features/ (Cucumber step definitions and support) |
161
+ | sorbet + tapioca | supported | yes: typed code with generated sorbet/rbi/gems and sorbet/rbi/dsl, and RBS signatures in sig/ |
162
+ | zeitwerk (no Rails) | supported | yes: a Rack app whose lib/ is autoloaded by Zeitwerk: constants are used with no require |
@@ -0,0 +1,150 @@
1
+ # Supported in Rust
2
+
3
+ <!-- GENERATED by scripts/gen-supported-docs.js. Do not edit by hand: run the script again. -->
4
+ <!-- source: catalog/rust.json; catalog/rust.structure.json -->
5
+ <!-- measured: coverage corpus 1bb4ebb, niro-local 2b30d4fd -->
6
+
7
+ This page lists the Rust libraries, frameworks and build tools Niro was tested against, and whether it recognised each one.
8
+
9
+ **What the marks mean.**
10
+
11
+ - **Supported:** Niro recognised every way of using it that the test code tries.
12
+ - **Partly:** Niro recognised some of those ways, or recognised one only in part.
13
+ - **Not yet:** Niro did not recognise it in the test code.
14
+
15
+ The test code is made up for the purpose: one small service per library, with the names invented. So "supported" means Niro recognised it there. It is good evidence, but it is not a promise about every codebase. If Niro misses something in your code, the coverage audit reports the gap on its own (see [coverage-audit-and-consent.md](coverage-audit-and-consent.md)).
16
+ <!-- source: niro-coverage-corpus README.md ("Synthetic test code ... All names are made up"); catalog status merged pairs one to one with measured found -->
17
+
18
+ The test catalog also holds 4 look-alike cases for Rust: code that Niro must NOT take for a route, a call or a store. They test that Niro ignores the look-alike, so they are left out of this page and its counts.
19
+ <!-- source: catalog/rust.json and catalog/rust.structure.json, rows marked as traps (scripts/gen-supported-docs.js isTrap) -->
20
+
21
+ ## Web frameworks and HTTP servers (the routes your code serves)
22
+
23
+ 10 supported, 0 partly, 4 not yet.
24
+
25
+ | Name | Status | Ways of using it that were tested |
26
+ |---|---|---|
27
+ | actix-web | supported | yes: #[get("/path")] attribute on an async fn, registered with App::service<br>yes: #[route(path, method = "PUT", method = "PATCH")]<br>yes: web::scope(prefix).route(path, web::get().to(handler))<br>yes: web::resource(path).route(web::post().to(handler))<br>yes: App::new().route(path, web::get().to(handler)) |
28
+ | axum | supported | yes: Router::new().route(path, get(h).post(h2))<br>yes: second verb chained on the method router: get(h).post(h2)<br>yes: Router::nest(prefix, sub_router()) from another module<br>yes: Router::merge(other_router()) built by a helper function |
29
+ | gotham | not yet | no: build_simple_router(\|route\| route.get(path).to(h)) |
30
+ | hyper | not yet | no: service_fn(route) with match (req.method(), req.uri().path()) arms |
31
+ | iron | supported | yes: router.get(path, handler, route_id) |
32
+ | ntex | supported | yes: #[web::get(path)] attribute<br>yes: App::new().route(path, web::delete().to(h)) |
33
+ | poem | supported | yes: Route::new().at(path, get(h).post(h2)) with #[handler] fns |
34
+ | poem-openapi | not yet | no: #[oai(path, method)] inside #[OpenApi] impl, nested under a prefix |
35
+ | rocket | supported | yes: #[get("/x/<id>")] attribute, mounted with routes![] under a prefix<br>yes: #[route(PUT, uri = "...")]<br>yes: #[post(path, data = "<body>")] |
36
+ | salvo | supported | yes: Router::with_path(p).get(h).post(h2).push(Router::with_path("<id>").get(h3)) |
37
+ | tide | supported | yes: app.at(path).get(h).post(h2) |
38
+ | tiny_http | not yet | no: for request in server.incoming_requests() with match on (method, url) |
39
+ | utoipa | supported | yes: #[utoipa::path(get, path = ...)] on an axum handler |
40
+ | warp | supported | yes: warp::path!("x" / u32).and(warp::get()).and_then(h)<br>yes: warp::path("x").and(warp::post()).and(body).and_then(h) |
41
+
42
+ ## The language's own HTTP server
43
+
44
+ 0 supported, 0 partly, 1 not yet.
45
+
46
+ | Name | Status | Ways of using it that were tested |
47
+ |---|---|---|
48
+ | std::net::TcpListener | not yet | no: TcpListener::bind + match on the request line string "GET /path HTTP/1.1" |
49
+
50
+ ## HTTP client libraries (the calls your code makes to other services)
51
+
52
+ 5 supported, 2 partly, 0 not yet.
53
+
54
+ | Name | Status | Ways of using it that were tested |
55
+ |---|---|---|
56
+ | attohttpc | supported | yes: attohttpc::get(url).send() |
57
+ | awc | supported | yes: awc::Client::default().get(url).send() |
58
+ | hyper | partly | no: hyper_util legacy Client; Request::builder().method(..).uri(url); client.request(req)<br>yes: client.get(Uri::from_static(url)) |
59
+ | isahc | supported | yes: isahc::get(url)<br>yes: HttpClient::new()?.post_async(url, body) |
60
+ | reqwest | partly | yes: let client = Client::new(); client.get(literal_url).send().await<br>yes: reqwest::Client::new().post(url).json(&body).send()<br>yes: reqwest::get(url) module function<br>no: client.request(Method::DELETE, format!("{}/x/{}", CONST_BASE, id)) on a ClientBuilder-built client<br>no: base URL from std::env::var with a literal fallback, joined with format!<br>yes: reqwest::blocking::Client::new().get(url).send()<br>no: client held in a struct field, base URL passed to new(), call in a method |
61
+ | surf | supported | yes: surf::get(url).recv_string()<br>yes: surf::Client::new().get(url) |
62
+ | ureq | supported | yes: ureq::get(url).call()<br>yes: ureq::post(url).send_json(..)<br>yes: let agent = AgentBuilder::new().build(); agent.get(url).call() |
63
+
64
+ ## The language's own HTTP client
65
+
66
+ 0 supported, 0 partly, 1 not yet.
67
+
68
+ | Name | Status | Ways of using it that were tested |
69
+ |---|---|---|
70
+ | std::net::TcpStream | not yet | no: TcpStream::connect(host) then write_all(b"GET /path HTTP/1.1...") |
71
+
72
+ ## gRPC
73
+
74
+ 1 supported, 1 partly, 0 not yet.
75
+
76
+ | Name | Status | Ways of using it that were tested |
77
+ |---|---|---|
78
+ | grpcio | supported | yes: server, form implements: impl <S> for T with RpcContext and UnarySink, create_<s>(impl) registration |
79
+ | tonic | partly | yes: server, form implements: #[tonic::async_trait] impl <S> for T, trait imported from pb::<s>_server<br>yes: tonic::include_proto!("pkg") names the proto package used in the wire path<br>yes: registration: Server::builder().add_service(<S>Server::new(impl))<br>no: client, form factory: <S>Client::connect(literal_url) then client.<rpc>(Request::new(..))<br>no: client, form constructor: Channel::from_static(url).connect(); <S>Client::new(channel)<br>yes: client, form factory with an address from std::env::var |
80
+
81
+ ## Databases and data stores
82
+
83
+ 8 supported, 4 partly, 8 not yet.
84
+
85
+ | Name | Status | Ways of using it that were tested |
86
+ |---|---|---|
87
+ | aws-sdk-dynamodb | supported | yes: client.put_item().table_name(t).item(..).send() |
88
+ | aws-sdk-s3 | supported | yes: client.put_object().bucket(b).key(k).send() |
89
+ | deadpool-postgres | supported | yes: let client = pool.get().await?; client.query(sql, &[]) |
90
+ | diesel | partly | no: table DSL: invoices::table.filter(col.eq(x)).load::<T>(conn)<br>no: diesel::insert_into(table).values(&x).execute(conn)<br>yes: raw SQL: diesel::sql_query(sql).load(conn) |
91
+ | elasticsearch | not yet | no: client.index(IndexParts::IndexId(index, id)) / search(SearchParts::Index(&[index])) |
92
+ | memcache | not yet | no: memcache::connect(url); client.set(key, v, ttl) |
93
+ | mongodb | not yet | no: client.database(db).collection(name) then insert_one / find / delete_many |
94
+ | mysql_async | supported | yes: conn.query_drop(sql) and conn.exec_first(sql, params) |
95
+ | postgres | supported | yes: postgres::Client::connect(..) then client.query(sql, &[]) |
96
+ | qdrant-client | not yet | no: client.upsert_points(UpsertPointsBuilder::new(collection, points)) |
97
+ | redis | not yet | no: con.set(key, v) / con.get(key) through redis::AsyncCommands<br>no: redis::cmd("HSET").arg(key)...query_async(&mut con) |
98
+ | rocksdb | not yet | no: DB::open_default(path); db.put(k, v); db.get(k) |
99
+ | rusqlite | supported | yes: conn.execute(sql, params![..]) |
100
+ | scylla | supported | yes: session.query_unpaged(cql, values) and session.prepare(cql) |
101
+ | sea-orm | partly | no: Entity::find().all(db) with #[sea_orm(table_name = ...)] on the model<br>no: ActiveModel { .. }.insert(db)<br>yes: db.query_all(Statement::from_string(backend, sql)) |
102
+ | sled | not yet | no: sled::open(path) then db.open_tree(name).insert(k, v) |
103
+ | sqlx | partly | yes: sqlx::query_as::<_, T>(sql).bind(x).fetch_one(pool)<br>partly: sqlx::query!(sql, args).execute(pool)<br>yes: sqlx::query_scalar(sql).fetch_one(pool)<br>yes: query(sql).execute(&mut *tx) inside pool.begin()<br>yes: shared table: main writes orders that services/sqlx reads |
104
+ | surrealdb | not yet | no: db.select(table) and db.query(surql) |
105
+ | tiberius | supported | yes: client.query(sql, &[&p]) on SQL Server |
106
+ | tokio-postgres | partly | yes: client.query(sql, &[]) on a connected Client<br>no: let stmt = client.prepare(sql); client.execute(&stmt, ..) |
107
+
108
+ ## Messaging and events (queues, topics, streams)
109
+
110
+ 6 supported, 4 partly, 5 not yet.
111
+
112
+ | Name | Status | Ways of using it that were tested |
113
+ |---|---|---|
114
+ | apalis | not yet | no: storage.push(Job); WorkerBuilder::new(name).backend(storage).build_fn(handler) |
115
+ | async-nats | partly | no: client.subscribe(subject) then .next()<br>no: client.publish(subject, payload)<br>no: client.request(subject, payload)<br>yes: async_nats::jetstream::new(client).publish(subject, payload)<br>partly: shared subject: main publishes shipments.created |
116
+ | aws-sdk-sns | supported | yes: client.publish().topic_arn(arn).message(m).send() |
117
+ | aws-sdk-sqs | partly | yes: client.send_message().queue_url(url).message_body(b).send()<br>no: client.receive_message().queue_url(url) in a polling loop<br>partly: shared SQS queue URL: main sends to refunds |
118
+ | crossbeam-channel | supported | yes: in-process: crossbeam_channel::unbounded(); s.send(x); r.recv() |
119
+ | google-cloud-pubsub | not yet | no: client.topic(name).new_publisher(None).publish(msg) |
120
+ | lapin | partly | no: channel.basic_publish(exchange, routing_key, ..)<br>no: channel.basic_consume(queue, consumer_tag, ..) then stream<br>partly: shared queue: main publishes to the default exchange with routing key invoice-emails |
121
+ | pulsar | partly | partly: pulsar.producer().with_topic(t).build(); producer.send(..)<br>yes: pulsar.consumer().with_topic(t).with_subscription(s).build() |
122
+ | rdkafka | supported | yes: FutureProducer.send(FutureRecord::to(topic).payload(..), timeout)<br>yes: BaseProducer.send(BaseRecord::to(topic))<br>yes: StreamConsumer.subscribe(&[topic]); consumer.recv()<br>yes: shared topic: main produces orders.placed that services/rdkafka consumes |
123
+ | redis | not yet | no: con.publish(channel, msg)<br>no: client.get_async_pubsub(); pubsub.subscribe(channel); on_message() |
124
+ | rumqttc | supported | yes: AsyncClient.subscribe(topic, qos) / publish(topic, qos, retain, payload)<br>yes: client.subscribe(topic, qos)<br>yes: shared MQTT topic: main publishes warehouse/temperature |
125
+ | std::sync::mpsc | supported | yes: built-in in-process: std::sync::mpsc::channel(); tx.send(x); for x in rx |
126
+ | tokio-cron-scheduler | not yet | no: JobScheduler::new(); sched.add(Job::new(cron, closure)) |
127
+ | tokio::sync | supported | yes: in-process: tokio::sync::mpsc::channel(); tx.send(x).await; rx.recv().await<br>yes: in-process: tokio::sync::broadcast::channel(); tx.send(x) |
128
+ | zmq | not yet | no: ctx.socket(zmq::PUB).bind(..).send(msg); SUB set_subscribe(prefix) |
129
+
130
+ ## Build tools and package managers
131
+
132
+ 3 supported, 0 partly, 0 not yet.
133
+
134
+ | Name | Status | Ways of using it that were tested |
135
+ |---|---|---|
136
+ | Bazel rules_rust | supported | yes: MODULE.bazel + BUILD.bazel with rust_binary and rust_library, no Cargo.toml |
137
+ | Cargo | supported | yes: one package with src/main.rs, src/<mod>.rs, tests/ and inline #[cfg(test)] tests |
138
+ | Cargo workspace | supported | yes: virtual manifest with members listed by path; two binaries depend on a library by path<br>yes: members by glob (crates/*), exclude, default-members<br>yes: [workspace.package] and [workspace.dependencies] with a path dependency inherited by `x.workspace = true`<br>yes: a proc-macro crate ([lib] proc-macro = true) used by a service through a derive |
139
+
140
+ ## Project and folder layouts
141
+
142
+ 5 supported, 0 partly, 0 not yet.
143
+
144
+ | Name | Status | Ways of using it that were tested |
145
+ |---|---|---|
146
+ | Cargo | supported | yes: one package with src/lib.rs, src/main.rs, src/bin/*.rs, examples/ and benches/<br>yes: build output target/ beside Cargo.toml with no .gitignore<br>yes: sibling crates with no workspace, path dependencies, one renamed with package = |
147
+ | Cargo + cc | supported | yes: build.rs compiles native/*.c with the cc crate; a CMakeLists.txt sits beside Cargo.toml |
148
+ | Cargo build script | supported | yes: build.rs writes code to OUT_DIR, included with include!(concat!(env!("OUT_DIR"), ..)); target/ listed in .gitignore |
149
+ | cargo vendor | supported | yes: vendor/ holding third-party crates, wired by .cargo/config.toml [source.vendored-sources] |
150
+ | Tauri | supported | yes: a web front end (package.json, src/) with the Rust backend in src-tauri/ (Cargo.toml, tauri.conf.json) |