@shortlink-org/portolan 0.1.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 (826) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +387 -0
  3. package/catalog/enum_test.go +46 -0
  4. package/catalog/model.go +932 -0
  5. package/catalog/roundtrip_test.go +185 -0
  6. package/catalog/via_test.go +38 -0
  7. package/cli/portolan.mjs +368 -0
  8. package/go.mod +14 -0
  9. package/go.sum +14 -0
  10. package/index.html +13 -0
  11. package/internal/commands/cargo.go +154 -0
  12. package/internal/commands/commands.go +143 -0
  13. package/internal/commands/commands_test.go +323 -0
  14. package/internal/commands/gradle.go +77 -0
  15. package/internal/commands/justfile.go +82 -0
  16. package/internal/commands/makefile.go +106 -0
  17. package/internal/commands/maven.go +93 -0
  18. package/internal/commands/packagejson.go +159 -0
  19. package/internal/commands/pyproject.go +169 -0
  20. package/internal/commands/taskfile.go +101 -0
  21. package/internal/commands/testdata/estate/.cargo/config.toml +7 -0
  22. package/internal/commands/testdata/estate/Makefile +33 -0
  23. package/internal/commands/testdata/estate/Taskfile.yml +21 -0
  24. package/internal/commands/testdata/estate/build.gradle.kts +21 -0
  25. package/internal/commands/testdata/estate/justfile +20 -0
  26. package/internal/commands/testdata/estate/package.json +13 -0
  27. package/internal/commands/testdata/estate/pom.xml +29 -0
  28. package/internal/commands/testdata/estate/pyproject.toml +27 -0
  29. package/internal/commands/testdata/estate/xtask/src/main.rs +27 -0
  30. package/internal/commands/testdata/golden/commands.json +305 -0
  31. package/internal/gohttp/analyze.go +2500 -0
  32. package/internal/gohttp/endpoints.go +1067 -0
  33. package/internal/gohttp/roots.go +320 -0
  34. package/internal/gohttp/typed.go +143 -0
  35. package/internal/goscan/constants.go +85 -0
  36. package/internal/goscan/goscan_test.go +227 -0
  37. package/internal/goscan/names.go +52 -0
  38. package/internal/goscan/parse_test.go +11 -0
  39. package/internal/goscan/source.go +36 -0
  40. package/internal/goscan/tree.go +155 -0
  41. package/internal/goscan/types.go +99 -0
  42. package/internal/wsdl/ids.go +127 -0
  43. package/internal/wsdl/ids_test.go +21 -0
  44. package/internal/wsdl/model.go +62 -0
  45. package/internal/wsdl/parse.go +920 -0
  46. package/internal/wsdl/parse_test.go +133 -0
  47. package/package.json +117 -0
  48. package/plugin/describe.go +107 -0
  49. package/plugin/describe_test.go +114 -0
  50. package/plugin/protocol.go +117 -0
  51. package/plugin/schematest/schematest.go +126 -0
  52. package/plugins/README.md +1160 -0
  53. package/plugins/extract-adr/describe.go +19 -0
  54. package/plugins/extract-adr/describe_test.go +11 -0
  55. package/plugins/extract-adr/extract.go +153 -0
  56. package/plugins/extract-adr/extract_test.go +350 -0
  57. package/plugins/extract-adr/history.go +99 -0
  58. package/plugins/extract-adr/main.go +65 -0
  59. package/plugins/extract-adr/options.schema.json +26 -0
  60. package/plugins/extract-adr/parse.go +642 -0
  61. package/plugins/extract-adr/parse_test.go +405 -0
  62. package/plugins/extract-asyncapi/describe.go +19 -0
  63. package/plugins/extract-asyncapi/describe_test.go +11 -0
  64. package/plugins/extract-asyncapi/extract.go +315 -0
  65. package/plugins/extract-asyncapi/extract_test.go +197 -0
  66. package/plugins/extract-asyncapi/main.go +48 -0
  67. package/plugins/extract-asyncapi/options.schema.json +26 -0
  68. package/plugins/extract-asyncapi/spec.go +151 -0
  69. package/plugins/extract-celery/README.md +169 -0
  70. package/plugins/extract-celery/extract.py +346 -0
  71. package/plugins/extract-celery/extract_test.py +192 -0
  72. package/plugins/extract-celery/main.py +66 -0
  73. package/plugins/extract-celery/options.py +31 -0
  74. package/plugins/extract-celery/options.schema.json +31 -0
  75. package/plugins/extract-celery/producers.py +156 -0
  76. package/plugins/extract-celery/routes_test.py +106 -0
  77. package/plugins/extract-commands/describe.go +19 -0
  78. package/plugins/extract-commands/describe_test.go +11 -0
  79. package/plugins/extract-commands/extract.go +79 -0
  80. package/plugins/extract-commands/extract_test.go +92 -0
  81. package/plugins/extract-commands/main.go +55 -0
  82. package/plugins/extract-commands/options.schema.json +20 -0
  83. package/plugins/extract-csr/avro.go +258 -0
  84. package/plugins/extract-csr/describe.go +19 -0
  85. package/plugins/extract-csr/describe_test.go +11 -0
  86. package/plugins/extract-csr/extract.go +338 -0
  87. package/plugins/extract-csr/extract_test.go +374 -0
  88. package/plugins/extract-csr/jsonschema.go +343 -0
  89. package/plugins/extract-csr/lock.go +25 -0
  90. package/plugins/extract-csr/main.go +84 -0
  91. package/plugins/extract-csr/options.schema.json +43 -0
  92. package/plugins/extract-csr/subject.go +102 -0
  93. package/plugins/extract-django/README.md +386 -0
  94. package/plugins/extract-django/apps.py +100 -0
  95. package/plugins/extract-django/auth.py +302 -0
  96. package/plugins/extract-django/auth_test.py +188 -0
  97. package/plugins/extract-django/clients.py +121 -0
  98. package/plugins/extract-django/contracts.py +458 -0
  99. package/plugins/extract-django/database.py +119 -0
  100. package/plugins/extract-django/domain.py +318 -0
  101. package/plugins/extract-django/events.py +86 -0
  102. package/plugins/extract-django/extract.py +601 -0
  103. package/plugins/extract-django/extract_test.py +397 -0
  104. package/plugins/extract-django/flows.py +1278 -0
  105. package/plugins/extract-django/flows_test.py +338 -0
  106. package/plugins/extract-django/ids.py +42 -0
  107. package/plugins/extract-django/ids_test.py +30 -0
  108. package/plugins/extract-django/lifecycle.py +222 -0
  109. package/plugins/extract-django/lifecycle_test.py +76 -0
  110. package/plugins/extract-django/main.py +66 -0
  111. package/plugins/extract-django/openapi.py +229 -0
  112. package/plugins/extract-django/openapi_test.py +60 -0
  113. package/plugins/extract-django/operations.py +96 -0
  114. package/plugins/extract-django/options.py +61 -0
  115. package/plugins/extract-django/options.schema.json +126 -0
  116. package/plugins/extract-django/routing.py +214 -0
  117. package/plugins/extract-django/routing_test.py +198 -0
  118. package/plugins/extract-django/serializers.py +558 -0
  119. package/plugins/extract-django/store.py +434 -0
  120. package/plugins/extract-django/transport.py +242 -0
  121. package/plugins/extract-flows/describe.go +19 -0
  122. package/plugins/extract-flows/describe_test.go +11 -0
  123. package/plugins/extract-flows/extract.go +91 -0
  124. package/plugins/extract-flows/main.go +45 -0
  125. package/plugins/extract-flows/options.schema.json +16 -0
  126. package/plugins/extract-flows/parse.go +593 -0
  127. package/plugins/extract-flows/parse_test.go +204 -0
  128. package/plugins/extract-glossary/describe.go +19 -0
  129. package/plugins/extract-glossary/describe_test.go +11 -0
  130. package/plugins/extract-glossary/extract.go +115 -0
  131. package/plugins/extract-glossary/extract_test.go +220 -0
  132. package/plugins/extract-glossary/main.go +59 -0
  133. package/plugins/extract-glossary/options.schema.json +21 -0
  134. package/plugins/extract-glossary/parse.go +214 -0
  135. package/plugins/extract-glossary/parse_test.go +203 -0
  136. package/plugins/extract-go/aggregate.go +214 -0
  137. package/plugins/extract-go/client.go +409 -0
  138. package/plugins/extract-go/client_test.go +266 -0
  139. package/plugins/extract-go/describe.go +19 -0
  140. package/plugins/extract-go/describe_test.go +11 -0
  141. package/plugins/extract-go/enum.go +195 -0
  142. package/plugins/extract-go/enum_test.go +82 -0
  143. package/plugins/extract-go/event.go +99 -0
  144. package/plugins/extract-go/extract.go +191 -0
  145. package/plugins/extract-go/extract_test.go +261 -0
  146. package/plugins/extract-go/flow.go +1441 -0
  147. package/plugins/extract-go/flow_test.go +609 -0
  148. package/plugins/extract-go/httpclient.go +174 -0
  149. package/plugins/extract-go/httpclient_test.go +296 -0
  150. package/plugins/extract-go/ids.go +92 -0
  151. package/plugins/extract-go/layout.go +225 -0
  152. package/plugins/extract-go/layout_test.go +125 -0
  153. package/plugins/extract-go/lifecycle.go +324 -0
  154. package/plugins/extract-go/lifecycle_test.go +108 -0
  155. package/plugins/extract-go/main.go +79 -0
  156. package/plugins/extract-go/operation.go +157 -0
  157. package/plugins/extract-go/options.schema.json +66 -0
  158. package/plugins/extract-go/source.go +316 -0
  159. package/plugins/extract-go/transport.go +321 -0
  160. package/plugins/extract-go/transport_test.go +145 -0
  161. package/plugins/extract-go/wiring.go +434 -0
  162. package/plugins/extract-go-nats/describe.go +19 -0
  163. package/plugins/extract-go-nats/describe_test.go +11 -0
  164. package/plugins/extract-go-nats/extract.go +177 -0
  165. package/plugins/extract-go-nats/extract_test.go +389 -0
  166. package/plugins/extract-go-nats/index.go +379 -0
  167. package/plugins/extract-go-nats/main.go +42 -0
  168. package/plugins/extract-go-nats/options.schema.json +21 -0
  169. package/plugins/extract-go-nats/resolve.go +161 -0
  170. package/plugins/extract-go-nats/sites.go +224 -0
  171. package/plugins/extract-graphql/describe.go +19 -0
  172. package/plugins/extract-graphql/describe_test.go +11 -0
  173. package/plugins/extract-graphql/extract.go +433 -0
  174. package/plugins/extract-graphql/extract_test.go +256 -0
  175. package/plugins/extract-graphql/ids.go +49 -0
  176. package/plugins/extract-graphql/lex.go +237 -0
  177. package/plugins/extract-graphql/main.go +51 -0
  178. package/plugins/extract-graphql/options.schema.json +31 -0
  179. package/plugins/extract-graphql/parse.go +621 -0
  180. package/plugins/extract-graphql/parse_test.go +122 -0
  181. package/plugins/extract-http-clients/describe.go +19 -0
  182. package/plugins/extract-http-clients/describe_test.go +11 -0
  183. package/plugins/extract-http-clients/extract.go +705 -0
  184. package/plugins/extract-http-clients/extract_test.go +1263 -0
  185. package/plugins/extract-http-clients/main.go +42 -0
  186. package/plugins/extract-http-clients/options.schema.json +33 -0
  187. package/plugins/extract-java/README.md +190 -0
  188. package/plugins/extract-java/build/org/portolan/extract/Catalog.class +0 -0
  189. package/plugins/extract-java/build/org/portolan/extract/Clients$1.class +0 -0
  190. package/plugins/extract-java/build/org/portolan/extract/Clients$2.class +0 -0
  191. package/plugins/extract-java/build/org/portolan/extract/Clients$Call.class +0 -0
  192. package/plugins/extract-java/build/org/portolan/extract/Clients$Client.class +0 -0
  193. package/plugins/extract-java/build/org/portolan/extract/Clients$Route.class +0 -0
  194. package/plugins/extract-java/build/org/portolan/extract/Clients.class +0 -0
  195. package/plugins/extract-java/build/org/portolan/extract/Domain$Aggregate.class +0 -0
  196. package/plugins/extract-java/build/org/portolan/extract/Domain.class +0 -0
  197. package/plugins/extract-java/build/org/portolan/extract/Events$Found.class +0 -0
  198. package/plugins/extract-java/build/org/portolan/extract/Events.class +0 -0
  199. package/plugins/extract-java/build/org/portolan/extract/Extract.class +0 -0
  200. package/plugins/extract-java/build/org/portolan/extract/Flows$1.class +0 -0
  201. package/plugins/extract-java/build/org/portolan/extract/Flows$Binding.class +0 -0
  202. package/plugins/extract-java/build/org/portolan/extract/Flows$Draft.class +0 -0
  203. package/plugins/extract-java/build/org/portolan/extract/Flows$Frame.class +0 -0
  204. package/plugins/extract-java/build/org/portolan/extract/Flows$Options.class +0 -0
  205. package/plugins/extract-java/build/org/portolan/extract/Flows.class +0 -0
  206. package/plugins/extract-java/build/org/portolan/extract/Ids.class +0 -0
  207. package/plugins/extract-java/build/org/portolan/extract/Json$Reader.class +0 -0
  208. package/plugins/extract-java/build/org/portolan/extract/Json.class +0 -0
  209. package/plugins/extract-java/build/org/portolan/extract/Lifecycle$1.class +0 -0
  210. package/plugins/extract-java/build/org/portolan/extract/Lifecycle$Move.class +0 -0
  211. package/plugins/extract-java/build/org/portolan/extract/Lifecycle.class +0 -0
  212. package/plugins/extract-java/build/org/portolan/extract/Main.class +0 -0
  213. package/plugins/extract-java/build/org/portolan/extract/OpenApi$Operation.class +0 -0
  214. package/plugins/extract-java/build/org/portolan/extract/OpenApi$Spec.class +0 -0
  215. package/plugins/extract-java/build/org/portolan/extract/OpenApi.class +0 -0
  216. package/plugins/extract-java/build/org/portolan/extract/Operations$1.class +0 -0
  217. package/plugins/extract-java/build/org/portolan/extract/Operations$UseCase.class +0 -0
  218. package/plugins/extract-java/build/org/portolan/extract/Operations.class +0 -0
  219. package/plugins/extract-java/build/org/portolan/extract/Proto$Rpc.class +0 -0
  220. package/plugins/extract-java/build/org/portolan/extract/Proto$Service.class +0 -0
  221. package/plugins/extract-java/build/org/portolan/extract/Proto.class +0 -0
  222. package/plugins/extract-java/build/org/portolan/extract/Protocol$Builder.class +0 -0
  223. package/plugins/extract-java/build/org/portolan/extract/Protocol$Input.class +0 -0
  224. package/plugins/extract-java/build/org/portolan/extract/Protocol$Options.class +0 -0
  225. package/plugins/extract-java/build/org/portolan/extract/Protocol.class +0 -0
  226. package/plugins/extract-java/build/org/portolan/extract/Source$Project.class +0 -0
  227. package/plugins/extract-java/build/org/portolan/extract/Source$Unit.class +0 -0
  228. package/plugins/extract-java/build/org/portolan/extract/Source.class +0 -0
  229. package/plugins/extract-java/build/org/portolan/extract/Transport$Endpoint.class +0 -0
  230. package/plugins/extract-java/build/org/portolan/extract/Transport.class +0 -0
  231. package/plugins/extract-java/build/org/portolan/extract/Yaml$Line.class +0 -0
  232. package/plugins/extract-java/build/org/portolan/extract/Yaml$Reader.class +0 -0
  233. package/plugins/extract-java/build/org/portolan/extract/Yaml.class +0 -0
  234. package/plugins/extract-java/options.schema.json +70 -0
  235. package/plugins/extract-openapi/describe.go +19 -0
  236. package/plugins/extract-openapi/describe_test.go +11 -0
  237. package/plugins/extract-openapi/discover.go +243 -0
  238. package/plugins/extract-openapi/extract.go +526 -0
  239. package/plugins/extract-openapi/extract_test.go +545 -0
  240. package/plugins/extract-openapi/main.go +75 -0
  241. package/plugins/extract-openapi/options.schema.json +62 -0
  242. package/plugins/extract-openapi/spec.go +350 -0
  243. package/plugins/extract-project/README.md +22 -0
  244. package/plugins/extract-project/describe.go +19 -0
  245. package/plugins/extract-project/describe_test.go +11 -0
  246. package/plugins/extract-project/extract.go +221 -0
  247. package/plugins/extract-project/extract_test.go +109 -0
  248. package/plugins/extract-project/main.go +41 -0
  249. package/plugins/extract-project/options.schema.json +48 -0
  250. package/plugins/extract-proto/ast.go +125 -0
  251. package/plugins/extract-proto/consumes.go +77 -0
  252. package/plugins/extract-proto/describe.go +19 -0
  253. package/plugins/extract-proto/extract.go +293 -0
  254. package/plugins/extract-proto/extract_test.go +459 -0
  255. package/plugins/extract-proto/ids.go +89 -0
  256. package/plugins/extract-proto/ids_test.go +57 -0
  257. package/plugins/extract-proto/lex.go +285 -0
  258. package/plugins/extract-proto/main.go +100 -0
  259. package/plugins/extract-proto/module.go +120 -0
  260. package/plugins/extract-proto/options.schema.json +51 -0
  261. package/plugins/extract-proto/parse.go +720 -0
  262. package/plugins/extract-proto/parse_test.go +307 -0
  263. package/plugins/extract-proto/provides.go +236 -0
  264. package/plugins/extract-proto/resolve.go +222 -0
  265. package/plugins/extract-proto/resolve_test.go +119 -0
  266. package/plugins/extract-python-kafka/README.md +36 -0
  267. package/plugins/extract-python-kafka/extract.py +211 -0
  268. package/plugins/extract-python-kafka/extract_test.py +68 -0
  269. package/plugins/extract-python-kafka/main.py +59 -0
  270. package/plugins/extract-python-kafka/options.py +29 -0
  271. package/plugins/extract-python-kafka/options.schema.json +31 -0
  272. package/plugins/extract-redis/README.md +11 -0
  273. package/plugins/extract-redis/describe.go +19 -0
  274. package/plugins/extract-redis/describe_test.go +11 -0
  275. package/plugins/extract-redis/extract.go +183 -0
  276. package/plugins/extract-redis/extract_test.go +168 -0
  277. package/plugins/extract-redis/keyspaces.go +469 -0
  278. package/plugins/extract-redis/main.go +44 -0
  279. package/plugins/extract-redis/options.schema.json +30 -0
  280. package/plugins/extract-river/describe.go +19 -0
  281. package/plugins/extract-river/describe_test.go +11 -0
  282. package/plugins/extract-river/extract.go +507 -0
  283. package/plugins/extract-river/extract_test.go +132 -0
  284. package/plugins/extract-river/main.go +42 -0
  285. package/plugins/extract-river/options.schema.json +21 -0
  286. package/plugins/extract-rust/Cargo.lock +133 -0
  287. package/plugins/extract-rust/Cargo.toml +23 -0
  288. package/plugins/extract-rust/README.md +204 -0
  289. package/plugins/extract-rust/options.schema.json +65 -0
  290. package/plugins/extract-rust/rustfmt.toml +1 -0
  291. package/plugins/extract-sql/ddl.go +893 -0
  292. package/plugins/extract-sql/ddl_test.go +401 -0
  293. package/plugins/extract-sql/describe.go +19 -0
  294. package/plugins/extract-sql/describe_test.go +11 -0
  295. package/plugins/extract-sql/layout.go +221 -0
  296. package/plugins/extract-sql/layout_test.go +55 -0
  297. package/plugins/extract-sql/lineage.go +107 -0
  298. package/plugins/extract-sql/main.go +141 -0
  299. package/plugins/extract-sql/maps.go +564 -0
  300. package/plugins/extract-sql/maps_java.go +117 -0
  301. package/plugins/extract-sql/maps_rust.go +333 -0
  302. package/plugins/extract-sql/maps_rust_test.go +70 -0
  303. package/plugins/extract-sql/maps_test.go +204 -0
  304. package/plugins/extract-sql/maps_ts.go +398 -0
  305. package/plugins/extract-sql/maps_ts_test.go +136 -0
  306. package/plugins/extract-sql/options.schema.json +53 -0
  307. package/plugins/extract-sql/projection.go +70 -0
  308. package/plugins/extract-sql/projection_test.go +50 -0
  309. package/plugins/extract-sql/store.go +420 -0
  310. package/plugins/extract-sql/store_test.go +233 -0
  311. package/plugins/extract-sql/view.go +295 -0
  312. package/plugins/extract-ts/README.md +306 -0
  313. package/plugins/extract-ts/ast.test.ts +178 -0
  314. package/plugins/extract-ts/ast.ts +648 -0
  315. package/plugins/extract-ts/clients.ts +185 -0
  316. package/plugins/extract-ts/domain.ts +164 -0
  317. package/plugins/extract-ts/extract.test.ts +208 -0
  318. package/plugins/extract-ts/extract.ts +200 -0
  319. package/plugins/extract-ts/flows.ts +783 -0
  320. package/plugins/extract-ts/graphql.test.ts +73 -0
  321. package/plugins/extract-ts/graphql.ts +181 -0
  322. package/plugins/extract-ts/ids.test.ts +21 -0
  323. package/plugins/extract-ts/ids.ts +52 -0
  324. package/plugins/extract-ts/lifecycle.ts +138 -0
  325. package/plugins/extract-ts/main.ts +48 -0
  326. package/plugins/extract-ts/openapi.test.ts +64 -0
  327. package/plugins/extract-ts/openapi.ts +99 -0
  328. package/plugins/extract-ts/operations.ts +93 -0
  329. package/plugins/extract-ts/options.schema.json +96 -0
  330. package/plugins/extract-ts/source.test.ts +174 -0
  331. package/plugins/extract-ts/source.ts +465 -0
  332. package/plugins/extract-ts/transport.ts +147 -0
  333. package/plugins/extract-ts/tsconfig.json +13 -0
  334. package/plugins/extract-ts/wiring.ts +190 -0
  335. package/plugins/extract-watermill/describe.go +19 -0
  336. package/plugins/extract-watermill/describe_test.go +11 -0
  337. package/plugins/extract-watermill/extract.go +1228 -0
  338. package/plugins/extract-watermill/extract_test.go +234 -0
  339. package/plugins/extract-watermill/main.go +41 -0
  340. package/plugins/extract-watermill/options.schema.json +21 -0
  341. package/plugins/extract-wsdl/describe.go +19 -0
  342. package/plugins/extract-wsdl/describe_test.go +11 -0
  343. package/plugins/extract-wsdl/extract.go +148 -0
  344. package/plugins/extract-wsdl/extract_test.go +45 -0
  345. package/plugins/extract-wsdl/main.go +53 -0
  346. package/plugins/extract-wsdl/options.schema.json +59 -0
  347. package/plugins/fetch-bsr/auth.go +114 -0
  348. package/plugins/fetch-bsr/bsr.go +240 -0
  349. package/plugins/fetch-bsr/cache.go +66 -0
  350. package/plugins/fetch-bsr/describe.go +19 -0
  351. package/plugins/fetch-bsr/fetch.go +192 -0
  352. package/plugins/fetch-bsr/fetch_test.go +465 -0
  353. package/plugins/fetch-bsr/lock.go +71 -0
  354. package/plugins/fetch-bsr/main.go +117 -0
  355. package/plugins/fetch-bsr/options.schema.json +42 -0
  356. package/plugins/fetch-csr/auth.go +95 -0
  357. package/plugins/fetch-csr/cache.go +69 -0
  358. package/plugins/fetch-csr/describe.go +19 -0
  359. package/plugins/fetch-csr/describe_test.go +11 -0
  360. package/plugins/fetch-csr/fetch.go +254 -0
  361. package/plugins/fetch-csr/fetch_test.go +494 -0
  362. package/plugins/fetch-csr/lock.go +95 -0
  363. package/plugins/fetch-csr/main.go +122 -0
  364. package/plugins/fetch-csr/options.schema.json +42 -0
  365. package/plugins/fetch-csr/registry.go +209 -0
  366. package/plugins/fetch-git/cache.go +65 -0
  367. package/plugins/fetch-git/describe.go +19 -0
  368. package/plugins/fetch-git/describe_test.go +11 -0
  369. package/plugins/fetch-git/fetch.go +181 -0
  370. package/plugins/fetch-git/fetch_test.go +332 -0
  371. package/plugins/fetch-git/git.go +169 -0
  372. package/plugins/fetch-git/lock.go +72 -0
  373. package/plugins/fetch-git/main.go +127 -0
  374. package/plugins/fetch-git/offline.go +39 -0
  375. package/plugins/fetch-git/options.schema.json +42 -0
  376. package/plugins/fetch-git/pin.go +90 -0
  377. package/plugins/fetch-git/pin_test.go +74 -0
  378. package/plugins/gen-backstage/describe.go +17 -0
  379. package/plugins/gen-backstage/main.go +22 -0
  380. package/plugins/gen-backstage/options.schema.json +30 -0
  381. package/plugins/gen-backstage/plugin.go +473 -0
  382. package/plugins/gen-backstage/plugin_test.go +145 -0
  383. package/plugins/gen-backstage.wasm +0 -0
  384. package/plugins/gen-markdown/adr.go +162 -0
  385. package/plugins/gen-markdown/aggregate.go +362 -0
  386. package/plugins/gen-markdown/canonical.go +93 -0
  387. package/plugins/gen-markdown/context.go +90 -0
  388. package/plugins/gen-markdown/coverage_test.go +89 -0
  389. package/plugins/gen-markdown/describe.go +19 -0
  390. package/plugins/gen-markdown/describe_test.go +11 -0
  391. package/plugins/gen-markdown/external.go +71 -0
  392. package/plugins/gen-markdown/flow.go +277 -0
  393. package/plugins/gen-markdown/glossary.go +76 -0
  394. package/plugins/gen-markdown/glossary_test.go +148 -0
  395. package/plugins/gen-markdown/llms.go +302 -0
  396. package/plugins/gen-markdown/main.go +22 -0
  397. package/plugins/gen-markdown/markdown.go +254 -0
  398. package/plugins/gen-markdown/markdown_test.go +100 -0
  399. package/plugins/gen-markdown/module.go +107 -0
  400. package/plugins/gen-markdown/options.schema.json +16 -0
  401. package/plugins/gen-markdown/plugin.go +39 -0
  402. package/plugins/gen-markdown/quality_test.go +176 -0
  403. package/plugins/gen-markdown/redis_test.go +38 -0
  404. package/plugins/gen-markdown/render.go +344 -0
  405. package/plugins/gen-markdown/render_test.go +186 -0
  406. package/plugins/gen-markdown/service.go +456 -0
  407. package/plugins/gen-markdown/source.go +177 -0
  408. package/plugins/gen-markdown/store.go +201 -0
  409. package/plugins/gen-markdown.wasm +0 -0
  410. package/plugins/gen-mermaid/describe.go +17 -0
  411. package/plugins/gen-mermaid/main.go +22 -0
  412. package/plugins/gen-mermaid/options.schema.json +10 -0
  413. package/plugins/gen-mermaid/plugin.go +104 -0
  414. package/plugins/gen-mermaid/plugin_test.go +33 -0
  415. package/plugins/gen-mermaid.wasm +0 -0
  416. package/plugins/openapi/ids.go +261 -0
  417. package/plugins/openapi/ids_test.go +98 -0
  418. package/plugins/pyplugin/catalog.py +209 -0
  419. package/plugins/pyplugin/celery_conf.py +436 -0
  420. package/plugins/pyplugin/celery_tasks.py +76 -0
  421. package/plugins/pyplugin/kafka.py +570 -0
  422. package/plugins/pyplugin/names.py +38 -0
  423. package/plugins/pyplugin/protocol.py +79 -0
  424. package/plugins/pyplugin/source.py +285 -0
  425. package/plugins/verify-codeowners/describe.go +19 -0
  426. package/plugins/verify-codeowners/describe_test.go +11 -0
  427. package/plugins/verify-codeowners/main.go +75 -0
  428. package/plugins/verify-codeowners/match.go +85 -0
  429. package/plugins/verify-codeowners/match_test.go +47 -0
  430. package/plugins/verify-codeowners/options.schema.json +15 -0
  431. package/plugins/verify-codeowners/owners.go +164 -0
  432. package/plugins/verify-codeowners/owners_test.go +225 -0
  433. package/plugins/verify-codeowners/parse.go +90 -0
  434. package/plugins/verify-codeowners/parse_test.go +62 -0
  435. package/plugins/verify-otel/describe.go +19 -0
  436. package/plugins/verify-otel/describe_test.go +11 -0
  437. package/plugins/verify-otel/main.go +53 -0
  438. package/plugins/verify-otel/match.go +336 -0
  439. package/plugins/verify-otel/options.schema.json +30 -0
  440. package/plugins/verify-otel/otlp.go +200 -0
  441. package/plugins/verify-otel/verify.go +734 -0
  442. package/plugins/verify-otel/verify_test.go +460 -0
  443. package/portolan.json +1073 -0
  444. package/public/favicon.svg +12 -0
  445. package/schema/portolan.schema.json +2558 -0
  446. package/scripts/README.md +53 -0
  447. package/scripts/build-report.mjs +60 -0
  448. package/scripts/build-report.test.mjs +44 -0
  449. package/scripts/builtin-plugins.mjs +49 -0
  450. package/scripts/catalog-sources.mjs +67 -0
  451. package/scripts/diff.mjs +315 -0
  452. package/scripts/diff.test.mjs +151 -0
  453. package/scripts/forge-comment.mjs +144 -0
  454. package/scripts/forge-comment.test.mjs +151 -0
  455. package/scripts/forge-release.mjs +138 -0
  456. package/scripts/forge-release.test.mjs +106 -0
  457. package/scripts/forge.mjs +61 -0
  458. package/scripts/gen-likec4.mjs +854 -0
  459. package/scripts/gen-likec4.test.mjs +258 -0
  460. package/scripts/gen.mjs +603 -0
  461. package/scripts/generated-docs.test.mjs +66 -0
  462. package/scripts/local-api.mjs +1192 -0
  463. package/scripts/local-api.test.mjs +387 -0
  464. package/scripts/manifest.mjs +214 -0
  465. package/scripts/manifest.test.mjs +148 -0
  466. package/scripts/output-path.mjs +79 -0
  467. package/scripts/output-path.test.mjs +45 -0
  468. package/scripts/package-smoke.mjs +37 -0
  469. package/scripts/plugin-host.mjs +419 -0
  470. package/scripts/plugin-host.test.mjs +109 -0
  471. package/scripts/plugin-wasm-worker.mjs +34 -0
  472. package/scripts/run-builtin.mjs +81 -0
  473. package/scripts/schema.mjs +357 -0
  474. package/scripts/site-docs.mjs +295 -0
  475. package/scripts/site-docs.test.mjs +181 -0
  476. package/scripts/vendor-lock.mjs +58 -0
  477. package/scripts/vendor-lock.test.mjs +69 -0
  478. package/src/README.md +38 -0
  479. package/src/app/App.tsx +448 -0
  480. package/src/app/BranchPicker.test.ts +46 -0
  481. package/src/app/BranchPicker.tsx +153 -0
  482. package/src/app/Breadcrumbs.test.ts +38 -0
  483. package/src/app/Breadcrumbs.tsx +223 -0
  484. package/src/app/BuildStamp.tsx +146 -0
  485. package/src/app/CommandPalette.tsx +292 -0
  486. package/src/app/HashScroll.tsx +45 -0
  487. package/src/app/Sidebar.tsx +1742 -0
  488. package/src/app/TopBar.tsx +393 -0
  489. package/src/app/density.tsx +64 -0
  490. package/src/app/forge-access.test.ts +27 -0
  491. package/src/app/forge-access.tsx +80 -0
  492. package/src/app/panels.tsx +141 -0
  493. package/src/app/pins.tsx +242 -0
  494. package/src/app/query-client.ts +24 -0
  495. package/src/app/responsive.ts +55 -0
  496. package/src/app/shortcuts.test.ts +39 -0
  497. package/src/app/shortcuts.tsx +282 -0
  498. package/src/app/theme.tsx +51 -0
  499. package/src/app/title.test.ts +14 -0
  500. package/src/app/title.ts +30 -0
  501. package/src/app/toast.tsx +59 -0
  502. package/src/app/tree-keys.test.ts +80 -0
  503. package/src/app/tree-keys.ts +87 -0
  504. package/src/app/ui-store.ts +52 -0
  505. package/src/catalog-error.test.ts +105 -0
  506. package/src/catalog-profile.test.ts +59 -0
  507. package/src/catalog-profile.ts +101 -0
  508. package/src/catalog-stores.test.ts +431 -0
  509. package/src/catalog.test.ts +1215 -0
  510. package/src/catalog.ts +3125 -0
  511. package/src/chat/Cards.tsx +408 -0
  512. package/src/chat/ChatPanel.tsx +79 -0
  513. package/src/chat/ChatSettings.tsx +219 -0
  514. package/src/chat/Composer.tsx +99 -0
  515. package/src/chat/Conversation.tsx +281 -0
  516. package/src/chat/Header.tsx +46 -0
  517. package/src/chat/Message.tsx +49 -0
  518. package/src/chat/Notice.tsx +195 -0
  519. package/src/chat/Starter.tsx +67 -0
  520. package/src/chat/Steps.tsx +45 -0
  521. package/src/chat/Waiting.tsx +87 -0
  522. package/src/chat/flags.test.ts +59 -0
  523. package/src/chat/flags.ts +155 -0
  524. package/src/chat/linkify.test.ts +30 -0
  525. package/src/chat/linkify.ts +45 -0
  526. package/src/chat/prefs.ts +56 -0
  527. package/src/chat/prompt.test.ts +43 -0
  528. package/src/chat/prompt.ts +157 -0
  529. package/src/chat/store.ts +35 -0
  530. package/src/chat/tools.ts +58 -0
  531. package/src/chat/transport.ts +70 -0
  532. package/src/components/AdrRow.tsx +29 -0
  533. package/src/components/AnchorLink.tsx +67 -0
  534. package/src/components/ApiReference.tsx +134 -0
  535. package/src/components/AsyncApiReference.tsx +120 -0
  536. package/src/components/CatalogStamp.tsx +155 -0
  537. package/src/components/ChannelRows.tsx +132 -0
  538. package/src/components/CommandRows.tsx +139 -0
  539. package/src/components/DiagramSkeleton.tsx +71 -0
  540. package/src/components/EditorLink.tsx +85 -0
  541. package/src/components/FieldTree.tsx +305 -0
  542. package/src/components/FlowTrigger.tsx +34 -0
  543. package/src/components/Ident.tsx +89 -0
  544. package/src/components/Integrations.tsx +243 -0
  545. package/src/components/LifecycleDiagram.tsx +110 -0
  546. package/src/components/MachineDocs.tsx +42 -0
  547. package/src/components/Markdown.tsx +173 -0
  548. package/src/components/MarkdownTable.tsx +206 -0
  549. package/src/components/Mermaid.tsx +211 -0
  550. package/src/components/MethodRows.tsx +309 -0
  551. package/src/components/Overlay.tsx +125 -0
  552. package/src/components/PageHeader.tsx +189 -0
  553. package/src/components/RowActions.tsx +97 -0
  554. package/src/components/SchemaDocument.tsx +89 -0
  555. package/src/components/Select.tsx +126 -0
  556. package/src/components/ShapeRows.tsx +118 -0
  557. package/src/components/SourceDoc.tsx +82 -0
  558. package/src/components/SourcePreview.tsx +441 -0
  559. package/src/components/TabRow.tsx +182 -0
  560. package/src/components/TechIcon.tsx +34 -0
  561. package/src/components/Toc.tsx +132 -0
  562. package/src/components/WhatLinksHere.tsx +273 -0
  563. package/src/components/WsdlReference.tsx +84 -0
  564. package/src/components/ddd-icons.tsx +107 -0
  565. package/src/components/kind.tsx +157 -0
  566. package/src/components/logo.tsx +61 -0
  567. package/src/components/primitives.tsx +264 -0
  568. package/src/data.test.ts +18 -0
  569. package/src/data.ts +202 -0
  570. package/src/enrich.test.ts +1150 -0
  571. package/src/enrich.ts +672 -0
  572. package/src/er/ErCanvas.tsx +533 -0
  573. package/src/er/RedisSchema.tsx +74 -0
  574. package/src/er/RelationCard.tsx +267 -0
  575. package/src/er/StoreHeader.tsx +155 -0
  576. package/src/er/TableNode.tsx +84 -0
  577. package/src/er/ViewNode.tsx +83 -0
  578. package/src/er/layout.test.ts +115 -0
  579. package/src/er/layout.ts +77 -0
  580. package/src/er/lineage.test.ts +140 -0
  581. package/src/er/lineage.ts +107 -0
  582. package/src/er/markers.tsx +79 -0
  583. package/src/er/spec.test.ts +344 -0
  584. package/src/er/spec.ts +426 -0
  585. package/src/flow/ChainList.tsx +162 -0
  586. package/src/flow/FlowTable.tsx +123 -0
  587. package/src/flow/FlowToolbar.tsx +372 -0
  588. package/src/flow/StepDetail.tsx +380 -0
  589. package/src/flow/StepRail.tsx +428 -0
  590. package/src/flow/answers.test.ts +50 -0
  591. package/src/flow/answers.ts +58 -0
  592. package/src/flow/chain.test.ts +383 -0
  593. package/src/flow/chain.ts +353 -0
  594. package/src/flow/chapters.test.ts +185 -0
  595. package/src/flow/chapters.ts +225 -0
  596. package/src/flow/continues.test.ts +102 -0
  597. package/src/flow/continues.ts +65 -0
  598. package/src/flow/cross-context.test.ts +97 -0
  599. package/src/flow/cross-context.ts +35 -0
  600. package/src/flow/mermaid.test.ts +22 -0
  601. package/src/flow/mermaid.ts +141 -0
  602. package/src/flow/outline.test.ts +212 -0
  603. package/src/flow/outline.ts +170 -0
  604. package/src/flow/paths.test.ts +129 -0
  605. package/src/flow/paths.ts +147 -0
  606. package/src/flow/prefs.test.ts +80 -0
  607. package/src/flow/prefs.ts +104 -0
  608. package/src/graph/DependencyGraph.tsx +283 -0
  609. package/src/graph/DependencyNodes.tsx +162 -0
  610. package/src/graph/EdgeLabel.tsx +61 -0
  611. package/src/graph/FocusedEventGraph.tsx +211 -0
  612. package/src/graph/GraphToolbar.tsx +187 -0
  613. package/src/graph/RoutedEdge.tsx +80 -0
  614. package/src/graph/dependency-layout.test.ts +360 -0
  615. package/src/graph/dependency-layout.ts +444 -0
  616. package/src/graph/elk.test.ts +70 -0
  617. package/src/graph/elk.ts +287 -0
  618. package/src/graph/nodes.tsx +97 -0
  619. package/src/graph/theme.ts +81 -0
  620. package/src/graph/useElkFlow.ts +125 -0
  621. package/src/index.css +1547 -0
  622. package/src/language/InLanguage.tsx +40 -0
  623. package/src/language/TermCard.tsx +132 -0
  624. package/src/language/cards.test.ts +114 -0
  625. package/src/language/cards.ts +111 -0
  626. package/src/lib/admonition.test.ts +49 -0
  627. package/src/lib/admonition.ts +48 -0
  628. package/src/lib/adr.test.ts +131 -0
  629. package/src/lib/adr.ts +68 -0
  630. package/src/lib/api.test.ts +122 -0
  631. package/src/lib/api.ts +135 -0
  632. package/src/lib/backlinks.test.ts +330 -0
  633. package/src/lib/backlinks.ts +567 -0
  634. package/src/lib/branch-compare.ts +16 -0
  635. package/src/lib/build-info.test.ts +76 -0
  636. package/src/lib/build-info.ts +80 -0
  637. package/src/lib/catalog-diff.test.ts +275 -0
  638. package/src/lib/catalog-diff.ts +655 -0
  639. package/src/lib/clipboard.ts +43 -0
  640. package/src/lib/comparison-memory.test.ts +40 -0
  641. package/src/lib/comparison-memory.ts +39 -0
  642. package/src/lib/context-color.ts +27 -0
  643. package/src/lib/context-map.test.ts +148 -0
  644. package/src/lib/context-map.ts +399 -0
  645. package/src/lib/data-model.test.ts +245 -0
  646. package/src/lib/data-model.ts +399 -0
  647. package/src/lib/data-problems.test.ts +178 -0
  648. package/src/lib/data-problems.ts +314 -0
  649. package/src/lib/derive.test.ts +251 -0
  650. package/src/lib/derive.ts +515 -0
  651. package/src/lib/editor-link.test.ts +125 -0
  652. package/src/lib/editor-link.ts +96 -0
  653. package/src/lib/editor-prefs.ts +27 -0
  654. package/src/lib/event-graph.test.ts +237 -0
  655. package/src/lib/event-graph.ts +327 -0
  656. package/src/lib/export-canvas.ts +37 -0
  657. package/src/lib/export-file.test.ts +30 -0
  658. package/src/lib/export-file.ts +20 -0
  659. package/src/lib/flow-tree.test.ts +177 -0
  660. package/src/lib/flow-tree.ts +160 -0
  661. package/src/lib/forge-refs.test.ts +50 -0
  662. package/src/lib/forge-refs.ts +60 -0
  663. package/src/lib/format.test.ts +35 -0
  664. package/src/lib/format.ts +64 -0
  665. package/src/lib/github-catalog.test.ts +301 -0
  666. package/src/lib/github-catalog.ts +610 -0
  667. package/src/lib/integrations.test.ts +119 -0
  668. package/src/lib/integrations.ts +133 -0
  669. package/src/lib/kinds.test.ts +91 -0
  670. package/src/lib/kinds.ts +204 -0
  671. package/src/lib/lifecycle.test.ts +93 -0
  672. package/src/lib/lifecycle.ts +165 -0
  673. package/src/lib/local-api.ts +194 -0
  674. package/src/lib/motion-features.ts +11 -0
  675. package/src/lib/motion.test.ts +120 -0
  676. package/src/lib/motion.tsx +269 -0
  677. package/src/lib/palette-recent.test.ts +80 -0
  678. package/src/lib/palette-recent.ts +90 -0
  679. package/src/lib/palette.test.ts +365 -0
  680. package/src/lib/palette.ts +593 -0
  681. package/src/lib/pins.test.ts +139 -0
  682. package/src/lib/pins.ts +130 -0
  683. package/src/lib/problems.test.ts +112 -0
  684. package/src/lib/project-preview.ts +5 -0
  685. package/src/lib/proto-problems.test.ts +121 -0
  686. package/src/lib/proto-problems.ts +65 -0
  687. package/src/lib/queries.test.ts +113 -0
  688. package/src/lib/queries.ts +131 -0
  689. package/src/lib/registry.test.ts +219 -0
  690. package/src/lib/registry.ts +208 -0
  691. package/src/lib/scenarios.ts +547 -0
  692. package/src/lib/setup-info.test.ts +199 -0
  693. package/src/lib/setup-info.ts +415 -0
  694. package/src/lib/shape.test.ts +289 -0
  695. package/src/lib/shape.ts +454 -0
  696. package/src/lib/sidebar-prefs.test.ts +32 -0
  697. package/src/lib/sidebar-prefs.ts +75 -0
  698. package/src/lib/source-code.test.ts +159 -0
  699. package/src/lib/source-code.ts +286 -0
  700. package/src/lib/source-doc.test.ts +131 -0
  701. package/src/lib/source-doc.ts +103 -0
  702. package/src/lib/source-highlight.test.ts +29 -0
  703. package/src/lib/source-highlight.ts +24 -0
  704. package/src/lib/source-link.test.ts +153 -0
  705. package/src/lib/source-link.ts +201 -0
  706. package/src/lib/spec-files.test.ts +24 -0
  707. package/src/lib/spec-files.ts +23 -0
  708. package/src/lib/table-directive.test.ts +139 -0
  709. package/src/lib/table-directive.ts +106 -0
  710. package/src/lib/tech.test.ts +120 -0
  711. package/src/lib/tech.ts +421 -0
  712. package/src/lib/terms.test.ts +67 -0
  713. package/src/lib/terms.ts +179 -0
  714. package/src/lib/wire-problems.test.ts +263 -0
  715. package/src/lib/wire-problems.ts +281 -0
  716. package/src/likec4/C4View.tsx +55 -0
  717. package/src/likec4/CanvasBridge.tsx +187 -0
  718. package/src/likec4/FlowView.tsx +235 -0
  719. package/src/likec4/InteractiveView.tsx +144 -0
  720. package/src/likec4/canvas-viewport.test.ts +149 -0
  721. package/src/likec4/canvas-viewport.ts +135 -0
  722. package/src/likec4/flow-edges.test.ts +51 -0
  723. package/src/likec4/flow-edges.ts +89 -0
  724. package/src/likec4/frame-theme.test.ts +76 -0
  725. package/src/likec4/frame-theme.ts +101 -0
  726. package/src/likec4/highlight-css.test.ts +49 -0
  727. package/src/likec4/highlight-css.ts +52 -0
  728. package/src/likec4/ids.test.ts +151 -0
  729. package/src/likec4/ids.ts +115 -0
  730. package/src/likec4/levels.tsx +70 -0
  731. package/src/likec4/mapping.test.ts +66 -0
  732. package/src/likec4/mapping.ts +54 -0
  733. package/src/likec4/reserved.json +141 -0
  734. package/src/likec4/view-index.ts +78 -0
  735. package/src/main.tsx +13 -0
  736. package/src/map/ContextMapGraph.tsx +274 -0
  737. package/src/map/ContextNode.tsx +70 -0
  738. package/src/map/FloatingEdge.tsx +86 -0
  739. package/src/merge.test.ts +1038 -0
  740. package/src/merge.ts +1004 -0
  741. package/src/pages/AdrDetail.tsx +242 -0
  742. package/src/pages/AdrIndex.tsx +114 -0
  743. package/src/pages/AggregatePage.tsx +727 -0
  744. package/src/pages/BlockPage.tsx +478 -0
  745. package/src/pages/CatalogFailure.tsx +111 -0
  746. package/src/pages/Changes.tsx +313 -0
  747. package/src/pages/ContextMap.tsx +534 -0
  748. package/src/pages/ContextPage.tsx +346 -0
  749. package/src/pages/EnumPage.tsx +282 -0
  750. package/src/pages/EventPage.tsx +686 -0
  751. package/src/pages/ExternalPage.tsx +204 -0
  752. package/src/pages/FlowDetail.tsx +663 -0
  753. package/src/pages/FlowIndex.tsx +303 -0
  754. package/src/pages/GraphPage.tsx +217 -0
  755. package/src/pages/Language.tsx +257 -0
  756. package/src/pages/ModulePage.tsx +366 -0
  757. package/src/pages/NotFound.tsx +54 -0
  758. package/src/pages/Overview.tsx +344 -0
  759. package/src/pages/Problems.tsx +360 -0
  760. package/src/pages/RegistryIndex.tsx +118 -0
  761. package/src/pages/ServicePage.tsx +746 -0
  762. package/src/pages/Settings.tsx +975 -0
  763. package/src/pages/StorePage.tsx +114 -0
  764. package/src/peek/PeekCard.test.ts +35 -0
  765. package/src/peek/PeekCard.tsx +118 -0
  766. package/src/peek/PeekLayer.tsx +147 -0
  767. package/src/peek/model.test.ts +156 -0
  768. package/src/peek/model.ts +253 -0
  769. package/src/routes.test.ts +222 -0
  770. package/src/routes.ts +449 -0
  771. package/src/selection/DetailPanel.tsx +1407 -0
  772. package/src/selection/hash.test.ts +73 -0
  773. package/src/selection/hash.ts +58 -0
  774. package/src/selection/model.test.ts +199 -0
  775. package/src/selection/model.ts +473 -0
  776. package/src/selection/pages.test.ts +115 -0
  777. package/src/selection/pages.ts +268 -0
  778. package/src/selection/store.test.ts +62 -0
  779. package/src/selection/store.ts +46 -0
  780. package/src/selection/sync.tsx +99 -0
  781. package/src/table/ColumnsMenu.tsx +72 -0
  782. package/src/table/DataTable.tsx +780 -0
  783. package/src/table/Facets.tsx +54 -0
  784. package/src/table/GroupMenu.tsx +69 -0
  785. package/src/table/Toolbar.tsx +190 -0
  786. package/src/table/cells.tsx +117 -0
  787. package/src/table/compare.test.ts +117 -0
  788. package/src/table/compare.ts +196 -0
  789. package/src/table/export.test.ts +88 -0
  790. package/src/table/export.ts +66 -0
  791. package/src/table/facet-groups.test.ts +74 -0
  792. package/src/table/facet-groups.ts +56 -0
  793. package/src/table/grouping.test.ts +84 -0
  794. package/src/table/grouping.ts +82 -0
  795. package/src/table/infer.test.ts +57 -0
  796. package/src/table/infer.ts +50 -0
  797. package/src/table/persist.test.ts +76 -0
  798. package/src/table/persist.ts +112 -0
  799. package/src/table/sort-url.test.ts +75 -0
  800. package/src/table/sort-url.ts +52 -0
  801. package/src/table/types.ts +80 -0
  802. package/src/table/useTableState.ts +220 -0
  803. package/src/test-catalog.ts +7 -0
  804. package/src/testing/estate/data__catalog.json +2847 -0
  805. package/src/testing/estate/data__flows.json +1724 -0
  806. package/src/testing/estate/examples__auth__portolan__api.json +212 -0
  807. package/src/testing/estate/examples__auth__portolan__domain.json +1204 -0
  808. package/src/testing/estate/examples__auth__portolan__glossary.json +226 -0
  809. package/src/testing/estate/examples__auth__portolan__observed.json +757 -0
  810. package/src/testing/estate/examples__auth__portolan__stores.json +195 -0
  811. package/src/testing/estate/examples__shop__cart__portolan__api.json +234 -0
  812. package/src/testing/estate/examples__shop__cart__portolan__commands.json +86 -0
  813. package/src/testing/estate/examples__shop__cart__portolan__domain.json +966 -0
  814. package/src/testing/estate/examples__shop__cart__portolan__observed.json +498 -0
  815. package/src/testing/estate/examples__shop__cart__portolan__stores.json +208 -0
  816. package/src/testing/estate/examples__shop__oms__portolan__glossary.json +98 -0
  817. package/src/testing/estate/examples__shop__oms__portolan__proto.json +186 -0
  818. package/src/testing/estate.ts +49 -0
  819. package/src/trail/Trail.tsx +81 -0
  820. package/src/trail/model.test.ts +157 -0
  821. package/src/trail/model.ts +189 -0
  822. package/src/trail/record.tsx +32 -0
  823. package/src/trail/store.test.ts +66 -0
  824. package/src/trail/store.ts +102 -0
  825. package/tsconfig.json +35 -0
  826. package/vite.config.ts +171 -0
@@ -0,0 +1,558 @@
1
+ """DRF serializers as conservative OpenAPI schemas.
2
+
3
+ Nothing is imported. Serializer declarations, their Meta model/fields and a
4
+ view's serializer_class are enough to recover the common DRF contract while
5
+ leaving custom runtime behaviour unknown.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import ast
11
+ from collections import Counter
12
+ from dataclasses import dataclass, field as dc_field
13
+ from typing import Any, Dict, List, Optional, Set, Tuple
14
+
15
+ import domain
16
+ from apps import App
17
+ from ids import pascal
18
+ from source import Module, Project, assigned, bases, const_str, dotted, inner_class, keyword, keyword_bool, keyword_int, methods
19
+
20
+
21
+ @dataclass
22
+ class SerializerDef:
23
+ name: str
24
+ node: ast.ClassDef
25
+ module: Module
26
+ app: App
27
+ component: str = ""
28
+
29
+
30
+ class Registry:
31
+ def __init__(self, project: Project, apps: List[App], serializers: List[SerializerDef], models: List[domain.ModelDef]):
32
+ self.project = project
33
+ self.apps = apps
34
+ self.serializers = serializers
35
+ self.models = models
36
+ counts = Counter(item.name for item in serializers)
37
+ for item in serializers:
38
+ item.component = item.name if counts[item.name] == 1 else pascal(item.app.label) + item.name
39
+ self.by_symbol = {(item.module.dotted, item.name): item for item in serializers}
40
+ self.by_name: Dict[str, List[SerializerDef]] = {}
41
+ for item in serializers:
42
+ self.by_name.setdefault(item.name, []).append(item)
43
+ self._schemas: Dict[str, Dict[str, Any]] = {}
44
+
45
+ def resolve(self, module: Module, expression: ast.AST) -> Optional[SerializerDef]:
46
+ name = dotted(expression)
47
+ if not name:
48
+ return None
49
+ target = resolve_symbol(self.project, module, name)
50
+ if target in self.by_symbol:
51
+ return self.by_symbol[target]
52
+ candidates = self.by_name.get(name.split(".")[-1], [])
53
+ if len(candidates) == 1:
54
+ return candidates[0]
55
+ same_app = [item for item in candidates if module.dotted == item.app.dotted or module.dotted.startswith(item.app.dotted + ".")]
56
+ return same_app[0] if len(same_app) == 1 else None
57
+
58
+ def components(self) -> Dict[str, Dict[str, Any]]:
59
+ for serializer in self.serializers:
60
+ self.schema(serializer, set())
61
+ return {name: self._schemas[name] for name in sorted(self._schemas)}
62
+
63
+ def schema(self, serializer: SerializerDef, stack: Set[Tuple[str, str]]) -> Dict[str, Any]:
64
+ if serializer.component in self._schemas:
65
+ return self._schemas[serializer.component]
66
+ key = (serializer.module.dotted, serializer.name)
67
+ if key in stack:
68
+ return {"type": "object"}
69
+ fields = self.fields(serializer, stack | {key})
70
+ schema: Dict[str, Any] = {"type": "object", "properties": {name: value[0] for name, value in fields.items()}}
71
+ required = [name for name, (_, is_required) in fields.items() if is_required]
72
+ if required:
73
+ schema["required"] = required
74
+ description = ast.get_docstring(serializer.node, clean=True) or ""
75
+ if description:
76
+ schema["description"] = description.strip()
77
+ schema["x-portolan-inferred"] = True
78
+ schema["x-portolan-source"] = serializer.module.where(serializer.node)
79
+ self._schemas[serializer.component] = schema
80
+ return schema
81
+
82
+ def fields(self, serializer: SerializerDef, stack: Set[Tuple[str, str]]) -> Dict[str, Tuple[Dict[str, Any], bool]]:
83
+ out: Dict[str, Tuple[Dict[str, Any], bool]] = {}
84
+ for base in serializer.node.bases:
85
+ parent = self.resolve(serializer.module, base)
86
+ parent_key = (parent.module.dotted, parent.name) if parent is not None else None
87
+ if parent is not None and parent_key not in stack:
88
+ out.update(self.fields(parent, stack | {parent_key}))
89
+
90
+ meta = inner_class(serializer.node, "Meta")
91
+ meta_fields: Optional[List[str]] = None
92
+ read_only: Set[str] = set()
93
+ model = None
94
+ if meta is not None:
95
+ values = {name: value for name, value, _ in assigned(meta)}
96
+ if "fields" in values:
97
+ meta_fields = string_list(values["fields"])
98
+ if const_str(values["fields"]) == "__all__":
99
+ meta_fields = []
100
+ read_only = set(string_list(values.get("read_only_fields")))
101
+ if "model" in values:
102
+ model = self.model_for(serializer, values["model"])
103
+
104
+ if model is not None:
105
+ model_fields = self.model_fields(model, set())
106
+ selected = list(model_fields) if meta_fields == [] else (meta_fields or [])
107
+ for name in selected:
108
+ model_field = model_fields.get(name)
109
+ if model_field is None:
110
+ schema, required = fallback_model_field(name)
111
+ else:
112
+ schema, required = model_schema(model_field)
113
+ if name in read_only:
114
+ schema["readOnly"] = True
115
+ required = False
116
+ out[name] = (schema, required)
117
+
118
+ for name, value, _ in assigned(serializer.node):
119
+ if not isinstance(value, ast.Call):
120
+ continue
121
+ nested = self.resolve(serializer.module, value.func)
122
+ if nested is not None:
123
+ schema: Dict[str, Any] = {"$ref": "#/components/schemas/%s" % nested.component}
124
+ self.schema(nested, stack)
125
+ if keyword_bool(value, "many"):
126
+ schema = {"type": "array", "items": schema}
127
+ else:
128
+ schema = serializer_field_schema(value, self, serializer.module, stack)
129
+ required = serializer_field_required(value)
130
+ if keyword_bool(value, "read_only"):
131
+ schema["readOnly"] = True
132
+ required = False
133
+ if keyword_bool(value, "write_only"):
134
+ schema["writeOnly"] = True
135
+ out[name] = (schema, required)
136
+
137
+ if meta_fields is not None:
138
+ out = {name: out[name] if name in out else fallback_model_field(name) for name in meta_fields if name in out or model is not None}
139
+ return out
140
+
141
+ def model_for(self, serializer: SerializerDef, expression: ast.AST) -> Optional[domain.ModelDef]:
142
+ return self.resolve_model(serializer.module, expression, serializer.app)
143
+
144
+ def resolve_model(self, module: Module, expression: ast.AST, preferred_app: Optional[App] = None) -> Optional[domain.ModelDef]:
145
+ """Resolve a model expression without importing the Django project."""
146
+ name = dotted(expression)
147
+ if not name:
148
+ return None
149
+ short = name.split(".")[-1]
150
+ candidates = [model for model in self.models if model.name == short]
151
+ target = resolve_symbol(self.project, module, name)
152
+ exact = [model for model in candidates if (model.module.dotted, model.name) == target]
153
+ if len(exact) == 1:
154
+ return exact[0]
155
+ same_app = [model for model in candidates if preferred_app is not None and model.app.dotted == preferred_app.dotted]
156
+ if len(same_app) == 1:
157
+ return same_app[0]
158
+ return candidates[0] if len(candidates) == 1 else None
159
+
160
+ def serializer_model(self, serializer: Optional[SerializerDef]) -> Optional[domain.ModelDef]:
161
+ if serializer is None:
162
+ return None
163
+ meta = inner_class(serializer.node, "Meta")
164
+ if meta is None:
165
+ return None
166
+ values = {name: value for name, value, _ in assigned(meta)}
167
+ return self.model_for(serializer, values["model"]) if "model" in values else None
168
+
169
+ def model_for_endpoint(self, endpoint) -> Optional[domain.ModelDef]:
170
+ """The row an inherited DRF action operates on, when syntax proves it.
171
+
172
+ ``queryset`` and ``get_queryset`` are the strongest evidence. The
173
+ selected serializer's ``Meta.model`` is next; finally, a single model
174
+ named by any serializer declaration on the view is an unambiguous
175
+ fallback for action-specific plain serializers.
176
+ """
177
+ view_name = endpoint.view.split(".", 1)[0]
178
+ view = next((node for node in endpoint.module.classes() if node.name == view_name), None)
179
+ if view is None:
180
+ return None
181
+
182
+ values = {name: value for name, value, _ in assigned(view)}
183
+ queryset = values.get("queryset")
184
+ if queryset is not None:
185
+ found = self.model_in_queryset(endpoint.module, queryset)
186
+ if found is not None:
187
+ return found
188
+ selector = next((node for node in methods(view) if node.name == "get_queryset"), None)
189
+ if selector is not None:
190
+ found = self.model_in_queryset(endpoint.module, selector)
191
+ if found is not None:
192
+ return found
193
+
194
+ selected = self.for_endpoint(endpoint)
195
+ found = self.serializer_model(selected)
196
+ if found is not None:
197
+ return found
198
+
199
+ candidates: List[domain.ModelDef] = []
200
+ expressions: List[ast.AST] = []
201
+ if "serializer_class" in values:
202
+ expressions.append(values["serializer_class"])
203
+ serializer_selector = next((node for node in methods(view) if node.name == "get_serializer_class"), None)
204
+ if serializer_selector is not None:
205
+ expressions += [node.value for node in ast.walk(serializer_selector) if isinstance(node, ast.Return) and node.value is not None]
206
+ for expression in expressions:
207
+ model = self.serializer_model(self.resolve(endpoint.module, expression))
208
+ if model is not None and model not in candidates:
209
+ candidates.append(model)
210
+ return candidates[0] if len(candidates) == 1 else None
211
+
212
+ def model_in_queryset(self, module: Module, node: ast.AST) -> Optional[domain.ModelDef]:
213
+ for child in ast.walk(node):
214
+ name = dotted(child.func) if isinstance(child, ast.Call) else dotted(child)
215
+ parts = name.split(".")
216
+ if "objects" not in parts:
217
+ continue
218
+ index = parts.index("objects")
219
+ if index == 0:
220
+ continue
221
+ expression = ast.parse(".".join(parts[:index]), mode="eval").body
222
+ model = self.resolve_model(module, expression)
223
+ if model is not None:
224
+ return model
225
+ return None
226
+
227
+ def model_fields(self, model: domain.ModelDef, stack: Set[str]) -> Dict[str, domain.FieldDef]:
228
+ if model.name in stack:
229
+ return {}
230
+ out: Dict[str, domain.FieldDef] = {}
231
+ for base in bases(model.node):
232
+ candidates = [item for item in self.models if item.app is model.app and item.name == base.split(".")[-1]]
233
+ if len(candidates) == 1:
234
+ out.update(self.model_fields(candidates[0], stack | {model.name}))
235
+ out.update({item.name: item for item in model.fields})
236
+ return out
237
+
238
+ def for_endpoint(self, endpoint) -> Optional[SerializerDef]:
239
+ view_name = endpoint.view.split(".", 1)[0]
240
+ view = next((node for node in endpoint.module.classes() if node.name == view_name), None)
241
+ if view is None:
242
+ return None
243
+ handler = next((node for node in view.body if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == endpoint.action), None)
244
+ if handler is not None:
245
+ for decorator in handler.decorator_list:
246
+ if not isinstance(decorator, ast.Call):
247
+ continue
248
+ value = keyword(decorator, "serializer_class")
249
+ if value is not None:
250
+ found = self.resolve(endpoint.module, value)
251
+ if found is not None:
252
+ return found
253
+ selector = next((node for node in view.body if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == "get_serializer_class"), None)
254
+ if selector is not None:
255
+ selected = selected_return(selector.body, endpoint.action)
256
+ if selected is not None:
257
+ found = self.resolve(endpoint.module, selected)
258
+ if found is not None:
259
+ return found
260
+ choices = []
261
+ for node in ast.walk(selector):
262
+ if isinstance(node, ast.Return) and node.value is not None:
263
+ found = self.resolve(endpoint.module, node.value)
264
+ if found is not None and found not in choices:
265
+ choices.append(found)
266
+ if len(choices) == 1:
267
+ return choices[0]
268
+ values = {name: value for name, value, _ in assigned(view)}
269
+ if "serializer_class" in values:
270
+ found = self.resolve(endpoint.module, values["serializer_class"])
271
+ if found is not None:
272
+ return found
273
+ return None
274
+
275
+ def is_list(self, endpoint) -> bool:
276
+ return endpoint.action == "list"
277
+
278
+ def describes_contract(self, endpoint) -> bool:
279
+ # A class node means DRF supplied the inherited CRUD implementation,
280
+ # whose serializer semantics are fixed by the framework. A custom
281
+ # handler can wrap or ignore serializer_class, so only its own action
282
+ # decorator is strong enough evidence until its expressions are read.
283
+ if isinstance(endpoint.node, ast.ClassDef):
284
+ return True
285
+ for decorator in getattr(endpoint.node, "decorator_list", []):
286
+ if isinstance(decorator, ast.Call) and keyword(decorator, "serializer_class") is not None:
287
+ return True
288
+ return False
289
+
290
+
291
+ def read(project: Project, apps: List[App]) -> Registry:
292
+ models = []
293
+ for app in apps:
294
+ app_models = domain.read_models(app)
295
+ model_modules = {module.dotted: module for module in app.models}
296
+ known_models = {(model.module.dotted, model.name) for model in app_models}
297
+ # A project-specific model base is still a model when the class body
298
+ # declares Django fields. Domain boundary discovery stays stricter;
299
+ # serializers only need the shape explicitly named by Meta.model.
300
+ for module in app.models:
301
+ for node in module.classes():
302
+ key = (module.dotted, node.name)
303
+ fields = domain.read_fields(node, module, model_modules)
304
+ if key not in known_models and fields:
305
+ app_models.append(domain.ModelDef(node.name, node, module, app, fields=fields))
306
+ known_models.add(key)
307
+ models += app_models
308
+ found: List[SerializerDef] = []
309
+ known: Set[Tuple[str, str]] = set()
310
+ changed = True
311
+ while changed:
312
+ changed = False
313
+ for app in apps:
314
+ for module in app.package("serializers"):
315
+ for node in module.classes():
316
+ key = (module.dotted, node.name)
317
+ if key in known:
318
+ continue
319
+ parents = [base.split(".")[-1] for base in bases(node)]
320
+ if not any(parent in ("Serializer", "ModelSerializer") or any(item.name == parent for item in found) for parent in parents):
321
+ continue
322
+ found.append(SerializerDef(node.name, node, module, app))
323
+ known.add(key)
324
+ changed = True
325
+ return Registry(project, apps, found, models)
326
+
327
+
328
+ def resolve_symbol(project: Project, module: Module, name: str) -> Tuple[str, str]:
329
+ parts = name.split(".")
330
+ imported = module.imports.get(parts[0])
331
+ if imported is None:
332
+ return module.dotted, name
333
+ combined = imported.module.split(".")
334
+ if imported.name != "*":
335
+ combined.append(imported.name)
336
+ combined += parts[1:]
337
+ for end in range(len(combined), 0, -1):
338
+ candidate = ".".join(combined[:end])
339
+ if project.module(candidate) is not None:
340
+ return candidate, ".".join(combined[end:])
341
+ return "", combined[-1] if combined else ""
342
+
343
+
344
+ def selected_return(statements: List[ast.stmt], action: str) -> Optional[ast.AST]:
345
+ """The serializer returned for a statically decidable self.action branch."""
346
+ for statement in statements:
347
+ if isinstance(statement, ast.Return):
348
+ return statement.value
349
+ if not isinstance(statement, ast.If):
350
+ continue
351
+ matches = action_condition(statement.test, action)
352
+ if matches is True:
353
+ return selected_return(statement.body, action)
354
+ if matches is False and statement.orelse:
355
+ chosen = selected_return(statement.orelse, action)
356
+ if chosen is not None:
357
+ return chosen
358
+ if matches is None:
359
+ return None
360
+ return None
361
+
362
+
363
+ def action_condition(node: ast.AST, action: str) -> Optional[bool]:
364
+ if isinstance(node, ast.BoolOp):
365
+ values = [action_condition(value, action) for value in node.values]
366
+ if any(value is None for value in values):
367
+ return None
368
+ return all(values) if isinstance(node.op, ast.And) else any(values)
369
+ if not isinstance(node, ast.Compare) or len(node.ops) != 1 or len(node.comparators) != 1:
370
+ return None
371
+ left, right = node.left, node.comparators[0]
372
+ if dotted(left) == "self.action":
373
+ expected = right
374
+ elif dotted(right) == "self.action":
375
+ expected = left
376
+ else:
377
+ return None
378
+ op = node.ops[0]
379
+ if isinstance(op, (ast.Eq, ast.NotEq)):
380
+ value = const_str(expected)
381
+ if not value:
382
+ return None
383
+ equal = action == value
384
+ return not equal if isinstance(op, ast.NotEq) else equal
385
+ if isinstance(op, (ast.In, ast.NotIn)):
386
+ values = string_list(expected)
387
+ if not values:
388
+ return None
389
+ included = action in values
390
+ return not included if isinstance(op, ast.NotIn) else included
391
+ return None
392
+
393
+
394
+ def string_list(node: Optional[ast.AST]) -> List[str]:
395
+ if isinstance(node, (ast.List, ast.Tuple, ast.Set)):
396
+ return [value for value in (const_str(item) for item in node.elts) if value]
397
+ return []
398
+
399
+
400
+ def constant(node: Optional[ast.AST]) -> Any:
401
+ if isinstance(node, ast.Constant) and isinstance(node.value, (str, int, float, bool)):
402
+ return node.value
403
+ return None
404
+
405
+
406
+ def serializer_field_required(call: ast.Call) -> bool:
407
+ required = keyword(call, "required")
408
+ if isinstance(required, ast.Constant) and isinstance(required.value, bool):
409
+ return required.value
410
+ return not keyword_bool(call, "read_only") and keyword(call, "default") is None
411
+
412
+
413
+ def nullable(schema: Dict[str, Any], call: ast.Call) -> Dict[str, Any]:
414
+ if not keyword_bool(call, "allow_null"):
415
+ return schema
416
+ if isinstance(schema.get("type"), str):
417
+ schema["type"] = [schema["type"], "null"]
418
+ else:
419
+ schema = {"anyOf": [schema, {"type": "null"}]}
420
+ return schema
421
+
422
+
423
+ def serializer_field_schema(call: ast.Call, registry: Registry, module: Module, stack: Set[Tuple[str, str]]) -> Dict[str, Any]:
424
+ kind = dotted(call.func).split(".")[-1]
425
+ if kind in ("IntegerField",):
426
+ schema: Dict[str, Any] = {"type": "integer"}
427
+ elif kind in ("FloatField",):
428
+ schema = {"type": "number", "format": "float"}
429
+ elif kind in ("DecimalField",):
430
+ schema = {"type": "number"}
431
+ elif kind in ("BooleanField", "NullBooleanField"):
432
+ schema = {"type": "boolean"}
433
+ elif kind == "DateTimeField":
434
+ schema = {"type": "string", "format": "date-time"}
435
+ elif kind == "DateField":
436
+ schema = {"type": "string", "format": "date"}
437
+ elif kind == "TimeField":
438
+ schema = {"type": "string", "format": "time"}
439
+ elif kind == "UUIDField":
440
+ schema = {"type": "string", "format": "uuid"}
441
+ elif kind == "EmailField":
442
+ schema = {"type": "string", "format": "email"}
443
+ elif kind == "URLField":
444
+ schema = {"type": "string", "format": "uri"}
445
+ elif kind == "SlugField":
446
+ schema = {"type": "string", "pattern": "^[-a-zA-Z0-9_]+$"}
447
+ elif kind in ("JSONField", "DictField"):
448
+ schema = {"type": "object"}
449
+ elif kind in ("ListField", "ListSerializer", "ManyRelatedField"):
450
+ child = keyword(call, "child") or (call.args[0] if call.args else None)
451
+ item_schema: Dict[str, Any] = {}
452
+ if isinstance(child, ast.Call):
453
+ nested = registry.resolve(module, child.func)
454
+ item_schema = {"$ref": "#/components/schemas/%s" % nested.component} if nested else serializer_field_schema(child, registry, module, stack)
455
+ schema = {"type": "array", "items": item_schema}
456
+ elif kind in ("PrimaryKeyRelatedField", "HyperlinkedRelatedField"):
457
+ schema = {"type": "integer"}
458
+ if keyword_bool(call, "many"):
459
+ schema = {"type": "array", "items": schema}
460
+ elif kind == "ChoiceField":
461
+ schema = {"type": "string"}
462
+ choices = keyword(call, "choices") or (call.args[0] if call.args else None)
463
+ values = literal_choices(choices)
464
+ if values:
465
+ schema["enum"] = values
466
+ elif kind in ("CharField", "RegexField", "FilePathField", "IPField"):
467
+ schema = {"type": "string"}
468
+ else:
469
+ schema = {}
470
+ maximum = keyword_int(call, "max_length")
471
+ minimum = keyword_int(call, "min_length")
472
+ if maximum is not None and schema.get("type") == "string":
473
+ schema["maxLength"] = maximum
474
+ if minimum is not None and schema.get("type") == "string":
475
+ schema["minLength"] = minimum
476
+ help_text = const_str(keyword(call, "help_text"))
477
+ if help_text:
478
+ schema["description"] = help_text
479
+ default = constant(keyword(call, "default"))
480
+ if default is not None:
481
+ schema["default"] = default
482
+ return nullable(schema, call)
483
+
484
+
485
+ def literal_choices(node: Optional[ast.AST]) -> List[Any]:
486
+ if not isinstance(node, (ast.List, ast.Tuple, ast.Set)):
487
+ return []
488
+ out = []
489
+ for item in node.elts:
490
+ if isinstance(item, (ast.List, ast.Tuple)) and item.elts:
491
+ item = item.elts[0]
492
+ value = constant(item)
493
+ if value is not None:
494
+ out.append(value)
495
+ return out
496
+
497
+
498
+ def model_schema(field: domain.FieldDef) -> Tuple[Dict[str, Any], bool]:
499
+ kind = field.kind
500
+ if kind in ("AutoField", "BigAutoField", "IntegerField", "SmallIntegerField", "PositiveIntegerField", "PositiveSmallIntegerField"):
501
+ schema: Dict[str, Any] = {"type": "integer"}
502
+ if kind == "BigAutoField":
503
+ schema["format"] = "int64"
504
+ elif kind == "BigIntegerField":
505
+ schema = {"type": "integer", "format": "int64"}
506
+ elif kind in ("FloatField",):
507
+ schema = {"type": "number", "format": "float"}
508
+ elif kind in ("DecimalField",):
509
+ schema = {"type": "number"}
510
+ elif kind in ("BooleanField", "NullBooleanField"):
511
+ schema = {"type": "boolean"}
512
+ elif kind == "DateTimeField":
513
+ schema = {"type": "string", "format": "date-time"}
514
+ elif kind == "DateField":
515
+ schema = {"type": "string", "format": "date"}
516
+ elif kind == "TimeField":
517
+ schema = {"type": "string", "format": "time"}
518
+ elif kind == "UUIDField":
519
+ schema = {"type": "string", "format": "uuid"}
520
+ elif kind == "EmailField":
521
+ schema = {"type": "string", "format": "email"}
522
+ elif kind == "URLField":
523
+ schema = {"type": "string", "format": "uri"}
524
+ elif kind == "SlugField":
525
+ schema = {"type": "string", "pattern": "^[-a-zA-Z0-9_]+$"}
526
+ elif kind in ("JSONField",):
527
+ schema = {"type": "object"}
528
+ elif kind == "ManyToManyField":
529
+ schema = {"type": "array", "items": {"type": "integer"}}
530
+ elif kind in ("ForeignKey", "OneToOneField"):
531
+ schema = {"type": "integer"}
532
+ else:
533
+ schema = {"type": "string"}
534
+ maximum = keyword_int(field.call, "max_length")
535
+ if maximum is not None and schema.get("type") == "string":
536
+ schema["maxLength"] = maximum
537
+ if field.help():
538
+ schema["description"] = field.help()
539
+ choices = literal_choices(keyword(field.call, "choices"))
540
+ if choices:
541
+ schema["enum"] = choices
542
+ null = keyword_bool(field.call, "null")
543
+ if null:
544
+ if isinstance(schema.get("type"), str):
545
+ schema["type"] = [schema["type"], "null"]
546
+ else:
547
+ schema = {"anyOf": [schema, {"type": "null"}]}
548
+ auto = kind in ("AutoField", "BigAutoField") or keyword_bool(field.call, "primary_key") and keyword(field.call, "default") is not None
549
+ if auto:
550
+ schema["readOnly"] = True
551
+ required = not null and not keyword_bool(field.call, "blank") and keyword(field.call, "default") is None and not auto
552
+ return schema, required
553
+
554
+
555
+ def fallback_model_field(name: str) -> Tuple[Dict[str, Any], bool]:
556
+ if name == "id":
557
+ return {"type": "integer", "readOnly": True}, False
558
+ return {}, False