Repository: fuelen/ecto_erd Branch: main Commit: da71179f04c3 Files: 63 Total size: 715.5 KB Directory structure: gitextract_ouem7f1z/ ├── .formatter.exs ├── .gitignore ├── LICENSE.txt ├── README.md ├── examples/ │ ├── dbml/ │ │ ├── changelog.com/ │ │ │ ├── Clusters.dbml │ │ │ └── Default.dbml │ │ ├── hexpm/ │ │ │ ├── Contexts-as-clusters.dbml │ │ │ ├── Default.dbml │ │ │ └── Only-selected-cluster-Accounts-context.dbml │ │ └── plausible-analytics/ │ │ ├── Contexts-as-clusters.dbml │ │ └── Default.dbml │ ├── dot/ │ │ ├── changelog.com/ │ │ │ ├── Clusters.dot │ │ │ ├── Default.dot │ │ │ └── No-fields.dot │ │ ├── hexpm/ │ │ │ ├── Contexts-as-clusters-no-fields.dot │ │ │ ├── Contexts-as-clusters.dot │ │ │ ├── Default.dot │ │ │ ├── No-fields.dot │ │ │ ├── Only-embedded-schemas.dot │ │ │ └── Only-selected-cluster-Accounts-context.dot │ │ └── plausible-analytics/ │ │ ├── Contexts-as-clusters-no-fields.dot │ │ ├── Contexts-as-clusters.dot │ │ ├── Default.dot │ │ └── No-fields.dot │ ├── mermaid/ │ │ ├── changelog.com/ │ │ │ ├── Default.mmd │ │ │ └── No-fields.mmd │ │ ├── hexpm/ │ │ │ ├── Default.mmd │ │ │ └── No-fields.mmd │ │ └── plausible-analytics/ │ │ ├── Default.mmd │ │ └── No-fields.mmd │ ├── plantuml/ │ │ ├── changelog.com/ │ │ │ ├── Clusters.puml │ │ │ └── Default.puml │ │ ├── hexpm/ │ │ │ ├── Contexts-as-clusters-no-fields.puml │ │ │ ├── Contexts-as-clusters.puml │ │ │ ├── Default.puml │ │ │ ├── Only-embedded-schemas.puml │ │ │ └── Only-selected-cluster-Accounts-context.puml │ │ └── plausible-analytics/ │ │ ├── Contexts-as-clusters-no-fields.puml │ │ ├── Contexts-as-clusters.puml │ │ └── Default.puml │ └── quick_dbd/ │ ├── changelog.com/ │ │ └── Default.qdbd │ ├── hexpm/ │ │ ├── Default.qdbd │ │ └── Only-selected-cluster-Accounts-context.qdbd │ └── plausible-analytics/ │ └── Default.qdbd ├── examples_generator.exs ├── lib/ │ ├── ecto/ │ │ └── erd/ │ │ ├── color.ex │ │ ├── document/ │ │ │ ├── dbml.ex │ │ │ ├── dot.ex │ │ │ ├── mermaid.ex │ │ │ ├── plantuml.ex │ │ │ └── quick_dbd.ex │ │ ├── document.ex │ │ ├── edge.ex │ │ ├── field.ex │ │ ├── graph.ex │ │ ├── html.ex │ │ ├── node.ex │ │ ├── render.ex │ │ └── schema_modules.ex │ └── mix/ │ └── tasks/ │ └── ecto.gen.erd.ex ├── mix.exs └── test/ ├── ecto/ │ └── erd_test.exs └── test_helper.exs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .formatter.exs ================================================ # Used by "mix format" [ inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] ] ================================================ FILE: .gitignore ================================================ # The directory Mix will write compiled artifacts to. /_build/ # If you run "mix test --cover", coverage assets end up here. /cover/ # The directory Mix downloads your dependencies sources to. /deps/ # Where third-party dependencies like ExDoc output generated docs. /doc/ # Ignore .fetch files in case you like to edit your project deps locally. /.fetch # If the VM crashes, it generates a dump, let's ignore it too. erl_crash.dump # Also ignore archive artifacts (built via "mix archive.build"). *.ez # Ignore package tarball (built via "mix hex.build"). ecto_erd-*.tar # Temporary files, for example, from tests. /tmp/ ================================================ FILE: LICENSE.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # Ecto.ERD [![Hex.pm](https://img.shields.io/hexpm/v/ecto_erd.svg)](https://hex.pm/packages/ecto_erd) A mix task for generating an ERD (Entity-Relationship Diagram) in various formats for all Ecto schemas in your project. Supported formats: * [DOT](https://en.wikipedia.org/wiki/DOT_(graph_description_language)) (default) * [PlantUML](https://plantuml.com) * [DBML](https://www.dbml.org/) * [QuickDBD](https://www.quickdatabasediagrams.com) * [Mermaid](https://mermaid-js.github.io/mermaid/#/entityRelationshipDiagram) ![Simple blog demo](assets/simple_blog_dot_demo.png)
Definition of schemas ```elixir defmodule Blog.Post do use Ecto.Schema schema "posts" do field(:title, :string) field(:text, :string) timestamps() belongs_to(:user, Blog.User) has_many(:comments, Blog.Comment) end end defmodule Blog.Comment do use Ecto.Schema schema "comments" do field(:text, :string) timestamps() belongs_to(:post, Blog.Post) belongs_to(:user, Blog.User) end end defmodule Blog.User do use Ecto.Schema schema "users" do field(:email, :string) has_many(:posts, Blog.Post) has_many(:comments, Blog.Comment) end end ```
## Installation The package can be installed by adding `ecto_erd` to your list of dependencies in `mix.exs`: ```elixir def deps do [ {:ecto_erd, "~> 0.6", only: :dev} ] end ``` ## Usage Just run: ```sh mix ecto.gen.erd ``` The command above produces a DOT file, which you can convert to an image using the Graphviz utility: ```sh dot -Tpng ecto_erd.dot -o erd.png ``` Configuration is possible via the `.ecto_erd.exs` file. The docs can be found at [https://hexdocs.pm/ecto_erd](https://hexdocs.pm/ecto_erd). Configuration examples and output for a few open-source projects can be found in the PAGES section under EXAMPLES. ## Troubleshooting Trying to run `ecto_erd` in an umbrella project? You might see this error: ``` $ mix ecto.gen.erd ** (RuntimeError) Unable to detect `:otp_app`, please specify it explicitly ``` The easiest solution is to run the command on one of the apps in the `apps/` directory. Another option is to create a configuration file and specify the `:otp_app`. See the [docs for details](https://hexdocs.pm/ecto_erd/Mix.Tasks.Ecto.Gen.Erd.html#module-configuration-file). ================================================ FILE: examples/dbml/changelog.com/Clusters.dbml ================================================ TableGroup EPISODE { episodes episode_guests episode_hosts episode_requests episode_sponsors episode_stats episode_topics } TableGroup NEWS { news_ads news_issues news_issue_ads news_issue_items news_items news_item_comments news_item_topics news_queue news_sources news_sponsorships } TableGroup PERSON { people } TableGroup PODCAST { podcasts podcast_hosts podcast_topics } TableGroup POST { posts post_topics } TableGroup SPONSOR { sponsors sponsor_reps } Table feeds { id integer [pk] name varchar slug varchar description varchar title_format varchar plusplus boolean autosub boolean starts_at timestamp cover varchar podcast_ids array person_ids array owner_id integer inserted_at timestamp updated_at timestamp } Table subscriptions { id integer [pk] unsubscribed_at timestamp context varchar episode_id integer item_id integer person_id integer podcast_id integer inserted_at timestamp updated_at timestamp } Table topics { id integer [pk] name varchar slug varchar description varchar website varchar twitter_handle varchar icon varchar inserted_at timestamp updated_at timestamp } Table schema_migrations { version integer [pk] inserted_at timestamp } Table oban_jobs { id integer [pk] state varchar queue varchar worker varchar args jsonb meta jsonb tags array errors array attempt integer attempted_by array max_attempts integer priority integer attempted_at timestamp cancelled_at timestamp completed_at timestamp discarded_at timestamp inserted_at timestamp scheduled_at timestamp } Table episodes { id integer [pk] slug varchar guid varchar title varchar subtitle varchar type integer featured boolean highlight varchar subhighlight varchar summary varchar notes varchar doc_url varchar socialize_url varchar published boolean published_at timestamp recorded_at timestamp recorded_live boolean youtube_id varchar cover varchar audio_file varchar audio_bytes integer audio_duration integer audio_chapters jsonb plusplus_file varchar plusplus_bytes integer plusplus_duration integer plusplus_chapters jsonb download_count float import_count float reach_count integer email_subject varchar email_teaser varchar email_content varchar email_sends integer email_opens integer transcript array podcast_id integer request_id integer inserted_at timestamp updated_at timestamp } Table episode_guests { id integer [pk] position integer thanks boolean discount_code varchar episode_id integer person_id integer inserted_at timestamp updated_at timestamp } Table episode_hosts { id integer [pk] position integer person_id integer episode_id integer inserted_at timestamp updated_at timestamp } Table episode_requests { id integer [pk] status integer hosts varchar guests varchar topics varchar pitch varchar pronunciation varchar message varchar podcast_id integer submitter_id integer inserted_at timestamp updated_at timestamp } Table episode_sponsors { id integer [pk] position integer title varchar link_url varchar description varchar starts_at float ends_at float episode_id integer sponsor_id integer inserted_at timestamp updated_at timestamp } Table episode_stats { id integer [pk] date date episode_bytes integer total_bytes integer downloads float uniques integer demographics jsonb episode_id integer podcast_id integer inserted_at timestamp updated_at timestamp } Table episode_topics { id integer [pk] position integer topic_id integer episode_id integer inserted_at timestamp updated_at timestamp } Table news_ads { id integer [pk] url varchar headline varchar story varchar image varchar active boolean newsletter boolean impression_count integer click_count integer sponsorship_id integer inserted_at timestamp updated_at timestamp } Table news_issues { id integer [pk] slug varchar note varchar teaser varchar published boolean published_at timestamp inserted_at timestamp updated_at timestamp } Table news_issue_ads { id integer [pk] position integer image boolean ad_id integer issue_id integer inserted_at timestamp updated_at timestamp } Table news_issue_items { id integer [pk] position integer image boolean issue_id integer item_id integer inserted_at timestamp updated_at timestamp } Table news_items { id integer [pk] status integer type integer url varchar headline varchar story varchar image varchar object_id varchar feed_only boolean pinned boolean published_at timestamp refreshed_at timestamp impression_count integer click_count integer message varchar author_id integer logger_id integer submitter_id integer source_id integer inserted_at timestamp updated_at timestamp } Table news_item_comments { id integer [pk] content varchar approved boolean edited_at timestamp deleted_at timestamp item_id integer author_id integer parent_id integer inserted_at timestamp updated_at timestamp } Table news_item_topics { id integer [pk] position integer item_id integer topic_id integer inserted_at timestamp updated_at timestamp } Table news_queue { id integer [pk] position float item_id integer } Table news_sources { id integer [pk] name varchar slug varchar website varchar twitter_handle varchar description varchar feed varchar regex varchar publication boolean icon varchar inserted_at timestamp updated_at timestamp } Table news_sponsorships { id integer [pk] name varchar weeks array impression_count integer click_count integer sponsor_id integer inserted_at timestamp updated_at timestamp } Table people { id integer [pk] name varchar email varchar handle varchar github_handle varchar linkedin_handle varchar mastodon_handle varchar twitter_handle varchar slack_id varchar website varchar bio varchar location varchar auth_token varchar auth_token_expires_at timestamp joined_at timestamp signed_in_at timestamp approved boolean avatar varchar admin boolean host boolean editor boolean public_profile boolean settings jsonb inserted_at timestamp updated_at timestamp } Table podcasts { id integer [pk] name varchar slug varchar status integer welcome varchar description varchar extended_description varchar vanity_domain varchar keywords varchar mastodon_handle varchar twitter_handle varchar apple_url varchar spotify_url varchar riverside_url varchar chartable_id varchar schedule_note varchar download_count float reach_count integer recorded_live boolean partner boolean position integer subscribers jsonb cover varchar inserted_at timestamp updated_at timestamp } Table podcast_hosts { id integer [pk] position integer retired boolean person_id integer podcast_id integer inserted_at timestamp updated_at timestamp } Table podcast_topics { id integer [pk] position integer podcast_id integer topic_id integer inserted_at timestamp updated_at timestamp } Table posts { id integer [pk] title varchar subtitle varchar slug varchar guid varchar canonical_url varchar image varchar tldr varchar body varchar published boolean published_at timestamp author_id integer editor_id integer inserted_at timestamp updated_at timestamp } Table post_topics { id integer [pk] position integer topic_id integer post_id integer inserted_at timestamp updated_at timestamp } Table sponsors { id integer [pk] name varchar description varchar website varchar github_handle varchar twitter_handle varchar avatar varchar color_logo varchar dark_logo varchar light_logo varchar inserted_at timestamp updated_at timestamp } Table sponsor_reps { id integer [pk] sponsor_id integer person_id integer inserted_at timestamp updated_at timestamp } Ref: episode_requests.id - episodes.request_id Ref: episodes.id < episode_guests.episode_id Ref: episodes.id < episode_hosts.episode_id Ref: episodes.id < episode_sponsors.episode_id Ref: episodes.id < episode_stats.episode_id Ref: episodes.id < episode_topics.episode_id Ref: episodes.id < subscriptions.episode_id Ref: news_ads.id < news_issue_ads.ad_id Ref: news_issues.id < news_issue_ads.issue_id Ref: news_issues.id < news_issue_items.issue_id Ref: news_item_comments.id < news_item_comments.parent_id Ref: news_items.id < news_issue_items.item_id Ref: news_items.id < news_item_comments.item_id Ref: news_items.id < news_item_topics.item_id Ref: news_items.id - news_queue.item_id Ref: news_items.id < subscriptions.item_id Ref: news_sources.id < news_items.source_id Ref: news_sponsorships.id < news_ads.sponsorship_id Ref: people.id < episode_guests.person_id Ref: people.id < episode_hosts.person_id Ref: people.id < episode_requests.submitter_id Ref: people.id < feeds.owner_id Ref: people.id < news_item_comments.author_id Ref: people.id < news_items.author_id Ref: people.id < news_items.logger_id Ref: people.id < news_items.submitter_id Ref: people.id < podcast_hosts.person_id Ref: people.id < posts.author_id Ref: people.id < posts.editor_id Ref: people.id < sponsor_reps.person_id Ref: people.id < subscriptions.person_id Ref: podcasts.id < episode_requests.podcast_id Ref: podcasts.id < episode_stats.podcast_id Ref: podcasts.id < episodes.podcast_id Ref: podcasts.id < podcast_hosts.podcast_id Ref: podcasts.id < podcast_topics.podcast_id Ref: podcasts.id < subscriptions.podcast_id Ref: posts.id < post_topics.post_id Ref: sponsors.id < episode_sponsors.sponsor_id Ref: sponsors.id < news_sponsorships.sponsor_id Ref: sponsors.id < sponsor_reps.sponsor_id Ref: topics.id < episode_topics.topic_id Ref: topics.id < news_item_topics.topic_id Ref: topics.id < podcast_topics.topic_id Ref: topics.id < post_topics.topic_id ================================================ FILE: examples/dbml/changelog.com/Default.dbml ================================================ Table episodes { id integer [pk] slug varchar guid varchar title varchar subtitle varchar type integer featured boolean highlight varchar subhighlight varchar summary varchar notes varchar doc_url varchar socialize_url varchar published boolean published_at timestamp recorded_at timestamp recorded_live boolean youtube_id varchar cover varchar audio_file varchar audio_bytes integer audio_duration integer audio_chapters jsonb plusplus_file varchar plusplus_bytes integer plusplus_duration integer plusplus_chapters jsonb download_count float import_count float reach_count integer email_subject varchar email_teaser varchar email_content varchar email_sends integer email_opens integer transcript array podcast_id integer request_id integer inserted_at timestamp updated_at timestamp } Table episode_guests { id integer [pk] position integer thanks boolean discount_code varchar episode_id integer person_id integer inserted_at timestamp updated_at timestamp } Table episode_hosts { id integer [pk] position integer person_id integer episode_id integer inserted_at timestamp updated_at timestamp } Table episode_requests { id integer [pk] status integer hosts varchar guests varchar topics varchar pitch varchar pronunciation varchar message varchar podcast_id integer submitter_id integer inserted_at timestamp updated_at timestamp } Table episode_sponsors { id integer [pk] position integer title varchar link_url varchar description varchar starts_at float ends_at float episode_id integer sponsor_id integer inserted_at timestamp updated_at timestamp } Table episode_stats { id integer [pk] date date episode_bytes integer total_bytes integer downloads float uniques integer demographics jsonb episode_id integer podcast_id integer inserted_at timestamp updated_at timestamp } Table episode_topics { id integer [pk] position integer topic_id integer episode_id integer inserted_at timestamp updated_at timestamp } Table feeds { id integer [pk] name varchar slug varchar description varchar title_format varchar plusplus boolean autosub boolean starts_at timestamp cover varchar podcast_ids array person_ids array owner_id integer inserted_at timestamp updated_at timestamp } Table news_ads { id integer [pk] url varchar headline varchar story varchar image varchar active boolean newsletter boolean impression_count integer click_count integer sponsorship_id integer inserted_at timestamp updated_at timestamp } Table news_issues { id integer [pk] slug varchar note varchar teaser varchar published boolean published_at timestamp inserted_at timestamp updated_at timestamp } Table news_issue_ads { id integer [pk] position integer image boolean ad_id integer issue_id integer inserted_at timestamp updated_at timestamp } Table news_issue_items { id integer [pk] position integer image boolean issue_id integer item_id integer inserted_at timestamp updated_at timestamp } Table news_items { id integer [pk] status integer type integer url varchar headline varchar story varchar image varchar object_id varchar feed_only boolean pinned boolean published_at timestamp refreshed_at timestamp impression_count integer click_count integer message varchar author_id integer logger_id integer submitter_id integer source_id integer inserted_at timestamp updated_at timestamp } Table news_item_comments { id integer [pk] content varchar approved boolean edited_at timestamp deleted_at timestamp item_id integer author_id integer parent_id integer inserted_at timestamp updated_at timestamp } Table news_item_topics { id integer [pk] position integer item_id integer topic_id integer inserted_at timestamp updated_at timestamp } Table news_queue { id integer [pk] position float item_id integer } Table news_sources { id integer [pk] name varchar slug varchar website varchar twitter_handle varchar description varchar feed varchar regex varchar publication boolean icon varchar inserted_at timestamp updated_at timestamp } Table news_sponsorships { id integer [pk] name varchar weeks array impression_count integer click_count integer sponsor_id integer inserted_at timestamp updated_at timestamp } Table people { id integer [pk] name varchar email varchar handle varchar github_handle varchar linkedin_handle varchar mastodon_handle varchar twitter_handle varchar slack_id varchar website varchar bio varchar location varchar auth_token varchar auth_token_expires_at timestamp joined_at timestamp signed_in_at timestamp approved boolean avatar varchar admin boolean host boolean editor boolean public_profile boolean settings jsonb inserted_at timestamp updated_at timestamp } Table podcasts { id integer [pk] name varchar slug varchar status integer welcome varchar description varchar extended_description varchar vanity_domain varchar keywords varchar mastodon_handle varchar twitter_handle varchar apple_url varchar spotify_url varchar riverside_url varchar chartable_id varchar schedule_note varchar download_count float reach_count integer recorded_live boolean partner boolean position integer subscribers jsonb cover varchar inserted_at timestamp updated_at timestamp } Table podcast_hosts { id integer [pk] position integer retired boolean person_id integer podcast_id integer inserted_at timestamp updated_at timestamp } Table podcast_topics { id integer [pk] position integer podcast_id integer topic_id integer inserted_at timestamp updated_at timestamp } Table posts { id integer [pk] title varchar subtitle varchar slug varchar guid varchar canonical_url varchar image varchar tldr varchar body varchar published boolean published_at timestamp author_id integer editor_id integer inserted_at timestamp updated_at timestamp } Table post_topics { id integer [pk] position integer topic_id integer post_id integer inserted_at timestamp updated_at timestamp } Table sponsors { id integer [pk] name varchar description varchar website varchar github_handle varchar twitter_handle varchar avatar varchar color_logo varchar dark_logo varchar light_logo varchar inserted_at timestamp updated_at timestamp } Table sponsor_reps { id integer [pk] sponsor_id integer person_id integer inserted_at timestamp updated_at timestamp } Table subscriptions { id integer [pk] unsubscribed_at timestamp context varchar episode_id integer item_id integer person_id integer podcast_id integer inserted_at timestamp updated_at timestamp } Table topics { id integer [pk] name varchar slug varchar description varchar website varchar twitter_handle varchar icon varchar inserted_at timestamp updated_at timestamp } Table schema_migrations { version integer [pk] inserted_at timestamp } Table oban_jobs { id integer [pk] state varchar queue varchar worker varchar args jsonb meta jsonb tags array errors array attempt integer attempted_by array max_attempts integer priority integer attempted_at timestamp cancelled_at timestamp completed_at timestamp discarded_at timestamp inserted_at timestamp scheduled_at timestamp } Ref: episode_requests.id - episodes.request_id Ref: episodes.id < episode_guests.episode_id Ref: episodes.id < episode_hosts.episode_id Ref: episodes.id < episode_sponsors.episode_id Ref: episodes.id < episode_stats.episode_id Ref: episodes.id < episode_topics.episode_id Ref: episodes.id < subscriptions.episode_id Ref: news_ads.id < news_issue_ads.ad_id Ref: news_issues.id < news_issue_ads.issue_id Ref: news_issues.id < news_issue_items.issue_id Ref: news_item_comments.id < news_item_comments.parent_id Ref: news_items.id < news_issue_items.item_id Ref: news_items.id < news_item_comments.item_id Ref: news_items.id < news_item_topics.item_id Ref: news_items.id - news_queue.item_id Ref: news_items.id < subscriptions.item_id Ref: news_sources.id < news_items.source_id Ref: news_sponsorships.id < news_ads.sponsorship_id Ref: people.id < episode_guests.person_id Ref: people.id < episode_hosts.person_id Ref: people.id < episode_requests.submitter_id Ref: people.id < feeds.owner_id Ref: people.id < news_item_comments.author_id Ref: people.id < news_items.author_id Ref: people.id < news_items.logger_id Ref: people.id < news_items.submitter_id Ref: people.id < podcast_hosts.person_id Ref: people.id < posts.author_id Ref: people.id < posts.editor_id Ref: people.id < sponsor_reps.person_id Ref: people.id < subscriptions.person_id Ref: podcasts.id < episode_requests.podcast_id Ref: podcasts.id < episode_stats.podcast_id Ref: podcasts.id < episodes.podcast_id Ref: podcasts.id < podcast_hosts.podcast_id Ref: podcasts.id < podcast_topics.podcast_id Ref: podcasts.id < subscriptions.podcast_id Ref: posts.id < post_topics.post_id Ref: sponsors.id < episode_sponsors.sponsor_id Ref: sponsors.id < news_sponsorships.sponsor_id Ref: sponsors.id < sponsor_reps.sponsor_id Ref: topics.id < episode_topics.topic_id Ref: topics.id < news_item_topics.topic_id Ref: topics.id < podcast_topics.topic_id Ref: topics.id < post_topics.topic_id ================================================ FILE: examples/dbml/hexpm/Contexts-as-clusters.dbml ================================================ TableGroup "Ecto.Migration" { schema_migrations } TableGroup "Hexpm.Accounts" { audit_logs emails keys organizations organization_users password_resets sessions users } TableGroup "Hexpm.BlockAddress" { blocked_addresses } TableGroup "Hexpm.Repository" { downloads installs packages package_dependants package_downloads package_owners package_reports package_report_comments package_report_releases releases release_downloads repositories requirements } TableGroup "Hexpm.ShortURLs" { short_urls } Table schema_migrations { version integer [pk] inserted_at timestamp } Table audit_logs { id integer [pk] user_agent varchar remote_ip varchar action varchar params jsonb user_id integer organization_id integer key_id integer inserted_at timestamp } Table emails { id integer [pk] email varchar verified boolean primary boolean public boolean gravatar boolean verification_key varchar verification_expiry timestamp user_id integer inserted_at timestamp updated_at timestamp } Table keys { id integer [pk] name varchar secret_first varchar secret_second varchar public boolean revoke_at timestamp inserted_at timestamp updated_at timestamp last_use jsonb user_id integer organization_id integer permissions jsonb } Table organizations { id integer [pk] name varchar billing_active boolean billing_override boolean trial_end timestamp inserted_at timestamp updated_at timestamp } Table organization_users { id integer [pk] role varchar organization_id integer user_id integer inserted_at timestamp updated_at timestamp } Table password_resets { id integer [pk] key varchar primary_email varchar user_id integer inserted_at timestamp } Table sessions { id integer [pk] token bytea data jsonb inserted_at timestamp updated_at timestamp } Table users { id integer [pk] username varchar full_name varchar password varchar service boolean deactivated_at timestamp role varchar inserted_at timestamp updated_at timestamp handles jsonb tfa jsonb organization_id integer } Table blocked_addresses { id integer [pk] ip varchar comment varchar } Table downloads { id integer [pk] package_id integer release_id integer downloads integer day date } Table installs { id integer [pk] hex varchar elixirs array } Table packages { id integer [pk] name varchar docs_updated_at timestamp inserted_at timestamp updated_at timestamp repository_id integer meta jsonb } Table package_dependants { id integer [pk] package_id integer name varchar repo varchar } Table package_downloads { package_id integer view varchar downloads integer } Table package_owners { id integer [pk] level varchar package_id integer user_id integer inserted_at timestamp updated_at timestamp } Table package_reports { id integer [pk] state varchar description varchar author_id integer package_id integer inserted_at timestamp updated_at timestamp } Table package_report_comments { id integer [pk] text varchar inserted_at timestamp updated_at timestamp package_report_id integer author_id integer } Table package_report_releases { id integer [pk] release_id integer package_report_id integer inserted_at timestamp updated_at timestamp } Table releases { id integer [pk] version varchar inner_checksum bytea outer_checksum bytea has_docs boolean inserted_at timestamp updated_at timestamp package_id integer publisher_id integer meta jsonb retirement jsonb } Table release_downloads { package_id integer release_id integer downloads integer } Table repositories { id integer [pk] name varchar inserted_at timestamp updated_at timestamp organization_id integer } Table requirements { id integer [pk] app varchar requirement varchar optional boolean release_id integer dependency_id integer } Table short_urls { id integer [pk] url varchar short_code varchar inserted_at timestamp } Ref: keys.id < audit_logs.key_id Ref: organizations.id < audit_logs.organization_id Ref: organizations.id < keys.organization_id Ref: organizations.id < organization_users.organization_id Ref: organizations.id - repositories.organization_id Ref: organizations.id - users.organization_id Ref: package_reports.id < package_report_comments.package_report_id Ref: package_reports.id < package_report_releases.package_report_id Ref: packages.id < downloads.package_id Ref: packages.id < package_dependants.package_id Ref: packages.id < package_downloads.package_id Ref: packages.id < package_owners.package_id Ref: packages.id < package_reports.package_id Ref: packages.id < release_downloads.package_id Ref: packages.id < releases.package_id Ref: packages.id < requirements.dependency_id Ref: releases.id < downloads.release_id Ref: releases.id < package_report_releases.release_id Ref: releases.id - release_downloads.release_id Ref: releases.id < requirements.release_id Ref: repositories.id < packages.repository_id Ref: users.id < audit_logs.user_id Ref: users.id < emails.user_id Ref: users.id < keys.user_id Ref: users.id < organization_users.user_id Ref: users.id < package_owners.user_id Ref: users.id < package_report_comments.author_id Ref: users.id < package_reports.author_id Ref: users.id < password_resets.user_id Ref: users.id < releases.publisher_id ================================================ FILE: examples/dbml/hexpm/Default.dbml ================================================ Table schema_migrations { version integer [pk] inserted_at timestamp } Table audit_logs { id integer [pk] user_agent varchar remote_ip varchar action varchar params jsonb user_id integer organization_id integer key_id integer inserted_at timestamp } Table emails { id integer [pk] email varchar verified boolean primary boolean public boolean gravatar boolean verification_key varchar verification_expiry timestamp user_id integer inserted_at timestamp updated_at timestamp } Table keys { id integer [pk] name varchar secret_first varchar secret_second varchar public boolean revoke_at timestamp inserted_at timestamp updated_at timestamp last_use jsonb user_id integer organization_id integer permissions jsonb } Table organizations { id integer [pk] name varchar billing_active boolean billing_override boolean trial_end timestamp inserted_at timestamp updated_at timestamp } Table organization_users { id integer [pk] role varchar organization_id integer user_id integer inserted_at timestamp updated_at timestamp } Table password_resets { id integer [pk] key varchar primary_email varchar user_id integer inserted_at timestamp } Table sessions { id integer [pk] token bytea data jsonb inserted_at timestamp updated_at timestamp } Table users { id integer [pk] username varchar full_name varchar password varchar service boolean deactivated_at timestamp role varchar inserted_at timestamp updated_at timestamp handles jsonb tfa jsonb organization_id integer } Table blocked_addresses { id integer [pk] ip varchar comment varchar } Table downloads { id integer [pk] package_id integer release_id integer downloads integer day date } Table installs { id integer [pk] hex varchar elixirs array } Table packages { id integer [pk] name varchar docs_updated_at timestamp inserted_at timestamp updated_at timestamp repository_id integer meta jsonb } Table package_dependants { id integer [pk] package_id integer name varchar repo varchar } Table package_downloads { package_id integer view varchar downloads integer } Table package_owners { id integer [pk] level varchar package_id integer user_id integer inserted_at timestamp updated_at timestamp } Table package_reports { id integer [pk] state varchar description varchar author_id integer package_id integer inserted_at timestamp updated_at timestamp } Table package_report_comments { id integer [pk] text varchar inserted_at timestamp updated_at timestamp package_report_id integer author_id integer } Table package_report_releases { id integer [pk] release_id integer package_report_id integer inserted_at timestamp updated_at timestamp } Table releases { id integer [pk] version varchar inner_checksum bytea outer_checksum bytea has_docs boolean inserted_at timestamp updated_at timestamp package_id integer publisher_id integer meta jsonb retirement jsonb } Table release_downloads { package_id integer release_id integer downloads integer } Table repositories { id integer [pk] name varchar inserted_at timestamp updated_at timestamp organization_id integer } Table requirements { id integer [pk] app varchar requirement varchar optional boolean release_id integer dependency_id integer } Table short_urls { id integer [pk] url varchar short_code varchar inserted_at timestamp } Ref: keys.id < audit_logs.key_id Ref: organizations.id < audit_logs.organization_id Ref: organizations.id < keys.organization_id Ref: organizations.id < organization_users.organization_id Ref: organizations.id - repositories.organization_id Ref: organizations.id - users.organization_id Ref: package_reports.id < package_report_comments.package_report_id Ref: package_reports.id < package_report_releases.package_report_id Ref: packages.id < downloads.package_id Ref: packages.id < package_dependants.package_id Ref: packages.id < package_downloads.package_id Ref: packages.id < package_owners.package_id Ref: packages.id < package_reports.package_id Ref: packages.id < release_downloads.package_id Ref: packages.id < releases.package_id Ref: packages.id < requirements.dependency_id Ref: releases.id < downloads.release_id Ref: releases.id < package_report_releases.release_id Ref: releases.id - release_downloads.release_id Ref: releases.id < requirements.release_id Ref: repositories.id < packages.repository_id Ref: users.id < audit_logs.user_id Ref: users.id < emails.user_id Ref: users.id < keys.user_id Ref: users.id < organization_users.user_id Ref: users.id < package_owners.user_id Ref: users.id < package_report_comments.author_id Ref: users.id < package_reports.author_id Ref: users.id < password_resets.user_id Ref: users.id < releases.publisher_id ================================================ FILE: examples/dbml/hexpm/Only-selected-cluster-Accounts-context.dbml ================================================ TableGroup "Hexpm.Accounts" { audit_logs emails keys organizations organization_users password_resets sessions users } Table audit_logs { id integer [pk] user_agent varchar remote_ip varchar action varchar params jsonb user_id integer organization_id integer key_id integer inserted_at timestamp } Table emails { id integer [pk] email varchar verified boolean primary boolean public boolean gravatar boolean verification_key varchar verification_expiry timestamp user_id integer inserted_at timestamp updated_at timestamp } Table keys { id integer [pk] name varchar secret_first varchar secret_second varchar public boolean revoke_at timestamp inserted_at timestamp updated_at timestamp last_use jsonb user_id integer organization_id integer permissions jsonb } Table organizations { id integer [pk] name varchar billing_active boolean billing_override boolean trial_end timestamp inserted_at timestamp updated_at timestamp } Table organization_users { id integer [pk] role varchar organization_id integer user_id integer inserted_at timestamp updated_at timestamp } Table password_resets { id integer [pk] key varchar primary_email varchar user_id integer inserted_at timestamp } Table sessions { id integer [pk] token bytea data jsonb inserted_at timestamp updated_at timestamp } Table users { id integer [pk] username varchar full_name varchar password varchar service boolean deactivated_at timestamp role varchar inserted_at timestamp updated_at timestamp handles jsonb tfa jsonb organization_id integer } Ref: keys.id < audit_logs.key_id Ref: organizations.id < audit_logs.organization_id Ref: organizations.id < keys.organization_id Ref: organizations.id < organization_users.organization_id Ref: organizations.id - users.organization_id Ref: users.id < audit_logs.user_id Ref: users.id < emails.user_id Ref: users.id < keys.user_id Ref: users.id < organization_users.user_id Ref: users.id < password_resets.user_id ================================================ FILE: examples/dbml/plausible-analytics/Contexts-as-clusters.dbml ================================================ TableGroup "Ecto.Migration" { schema_migrations } TableGroup "FunWithFlags.Store" { fun_with_flags_toggles } TableGroup Oban { oban_jobs } TableGroup Plausible { events_v2 sessions_v2 funnels goals sites } TableGroup "Plausible.Auth" { api_keys email_activation_codes invitations totp_recovery_codes users } TableGroup "Plausible.Billing" { enterprise_plans subscriptions } TableGroup "Plausible.DataMigration" { domains_lookup } TableGroup "Plausible.Funnel" { funnel_steps } TableGroup "Plausible.Imported" { imported_browsers imported_devices imported_entry_pages imported_exit_pages imported_locations imported_operating_systems imported_pages site_imports imported_sources imported_visitors } TableGroup "Plausible.Ingestion" { ingest_counters } TableGroup "Plausible.Plugins" { plugins_api_tokens } TableGroup "Plausible.Shield" { shield_rules_country shield_rules_hostname shield_rules_ip shield_rules_page } TableGroup "Plausible.Site" { google_auth site_memberships monthly_reports shared_links spike_notifications site_user_preferences weekly_reports } Enum billing_interval { yearly monthly } Enum currency { XBD BYN HKD XOF SOS ARS EGP XDR GMD MAD XAG XAU COU UYW LKR SAR BBD XCD ZMW ZWL CZK JPY SEK PLN KYD THB QAR SLL RON CUC MOP CHW KGS ALL CLP XXX IDR BZD PYG LAK OMR HRK CHF BTN MRU GEL BOV AFN RSD XTS UYU BHD HNL GBP WST COP MKD ZAR SYP SZL HTG SVC NPR MXV MMK PKR GTQ AWG SGD TWD AOA TOP XBB KRW XBA TRY XPT SBD MUR NIO CNY BWP NOK LSL IRR BOB BRL SDG BIF BDT UYI NGN LBP GYD RWF ILS PGK TTD SSP MWK ETB INR AUD XPD CVE TMT YER BAM XSU MVR SCR JOD CHE CUP PAB MGA VES JMD VUV MNT NZD XBC KES GNF XAF TZS BND MYR LRD KPW PHP IQD CRC UZS TND DJF DOP GIP XUA CLF MDL NAD PEN CDF USD BGN STN UAH EUR FKP GHS BSD CAD FJD SRD KHR MXN HUF UGX AED XPF DZD RUB KZT AZN KMF AMD VND USN BMD ANG KWD TJS LYD DKK ERN ISK MZN SHP } Enum role { owner admin viewer } Enum action { allow deny } Enum source { noop csv universal_analytics google_analytics_4 } Enum site_imports_status { pending failed completed importing } Enum subscriptions_status { active deleted past_due paused } Enum theme { system light dark } Table schema_migrations { version integer [pk] inserted_at timestamp } Table fun_with_flags_toggles { id integer [pk] flag_name varchar gate_type varchar target varchar enabled boolean } Table oban_jobs { id integer [pk] state varchar queue varchar worker varchar args jsonb meta jsonb tags array errors array attempt integer attempted_by array max_attempts integer priority integer attempted_at timestamp cancelled_at timestamp completed_at timestamp discarded_at timestamp inserted_at timestamp scheduled_at timestamp } Table events_v2 { name unknown site_id unknown hostname varchar pathname varchar user_id unknown session_id unknown timestamp timestamp "meta.key" array "meta.value" array revenue_source_amount unknown revenue_source_currency unknown revenue_reporting_amount unknown revenue_reporting_currency unknown referrer varchar referrer_source varchar utm_medium varchar utm_source varchar utm_campaign varchar utm_content varchar utm_term varchar country_code unknown subdivision1_code unknown subdivision2_code unknown city_geoname_id unknown screen_size unknown operating_system unknown operating_system_version unknown browser unknown browser_version unknown } Table sessions_v2 { hostname varchar site_id unknown user_id unknown session_id unknown start timestamp duration unknown is_bounce unknown entry_page varchar exit_page varchar exit_page_hostname varchar pageviews unknown events unknown sign unknown "entry_meta.key" array "entry_meta.value" array utm_medium varchar utm_source varchar utm_campaign varchar utm_content varchar utm_term varchar referrer varchar referrer_source varchar country_code unknown subdivision1_code unknown subdivision2_code unknown city_geoname_id unknown screen_size unknown operating_system unknown operating_system_version unknown browser unknown browser_version unknown timestamp timestamp transferred_from varchar } Table funnels { id integer [pk] name varchar site_id integer inserted_at timestamp updated_at timestamp } Table goals { id integer [pk] event_name varchar page_path varchar currency currency site_id integer inserted_at timestamp updated_at timestamp } Table sites { id integer [pk] domain varchar timezone varchar public boolean locked boolean stats_start_date date native_stats_start_at timestamp allowed_event_props array conversions_enabled boolean props_enabled boolean funnels_enabled boolean ingest_rate_limit_scale_seconds integer ingest_rate_limit_threshold integer domain_changed_from varchar domain_changed_at timestamp imported_data jsonb inserted_at timestamp updated_at timestamp } Table api_keys { id integer [pk] name varchar scopes array hourly_request_limit integer key_hash varchar key_prefix varchar user_id integer inserted_at timestamp updated_at timestamp } Table email_activation_codes { id integer [pk] code varchar issued_at timestamp user_id integer } Table invitations { id integer [pk] invitation_id varchar email varchar role role inviter_id integer site_id integer inserted_at timestamp updated_at timestamp } Table totp_recovery_codes { id integer [pk] code_digest varchar user_id integer inserted_at timestamp } Table users { id integer [pk] email varchar password_hash varchar name varchar last_seen timestamp trial_expiry_date date theme theme email_verified boolean previous_email varchar accept_traffic_until date allow_next_upgrade_override boolean totp_enabled boolean totp_secret bytea totp_token varchar totp_last_used_at timestamp grace_period jsonb inserted_at timestamp updated_at timestamp } Table enterprise_plans { id integer [pk] paddle_plan_id varchar billing_interval billing_interval monthly_pageview_limit integer site_limit integer team_member_limit integer features array hourly_api_request_limit integer user_id integer inserted_at timestamp updated_at timestamp } Table subscriptions { id integer [pk] paddle_subscription_id varchar paddle_plan_id varchar update_url varchar cancel_url varchar status subscriptions_status next_bill_amount varchar next_bill_date date last_bill_date date currency_code varchar user_id integer inserted_at timestamp updated_at timestamp } Table domains_lookup { site_id unknown domain varchar } Table funnel_steps { id integer [pk] step_order integer funnel_id integer goal_id integer inserted_at timestamp updated_at timestamp } Table imported_browsers { site_id unknown import_id unknown date date browser varchar browser_version varchar visitors unknown visits unknown visit_duration unknown pageviews unknown bounces unknown } Table imported_devices { site_id unknown import_id unknown date date device varchar visitors unknown visits unknown visit_duration unknown pageviews unknown bounces unknown } Table imported_entry_pages { site_id unknown import_id unknown date date entry_page varchar visitors unknown entrances unknown visit_duration unknown pageviews unknown bounces unknown } Table imported_exit_pages { site_id unknown import_id unknown date date exit_page varchar exits unknown visitors unknown visit_duration unknown pageviews unknown bounces unknown } Table imported_locations { site_id unknown import_id unknown date date country varchar region varchar city unknown visitors unknown visits unknown visit_duration unknown pageviews unknown bounces unknown } Table imported_operating_systems { site_id unknown import_id unknown date date operating_system varchar operating_system_version varchar visitors unknown visits unknown visit_duration unknown pageviews unknown bounces unknown } Table imported_pages { site_id unknown import_id unknown date date hostname varchar page varchar visits unknown visitors unknown active_visitors unknown pageviews unknown exits unknown time_on_page unknown } Table site_imports { id integer [pk] start_date date end_date date label varchar source source status site_imports_status legacy boolean site_id integer imported_by_id integer inserted_at timestamp updated_at timestamp } Table imported_sources { site_id unknown import_id unknown date date source varchar referrer varchar utm_source varchar utm_medium varchar utm_campaign varchar utm_content varchar utm_term varchar visitors unknown visits unknown visit_duration unknown pageviews unknown bounces unknown } Table imported_visitors { site_id unknown import_id unknown date date visitors unknown pageviews unknown bounces unknown visits unknown visit_duration unknown } Table ingest_counters { event_timebucket timestamp site_id unknown domain unknown metric unknown value unknown } Table plugins_api_tokens { id uuid [pk] inserted_at timestamp updated_at timestamp token_hash bytea description varchar hint varchar last_used_at timestamp site_id integer } Table shield_rules_country { id uuid [pk] site_id integer country_code varchar action action added_by varchar inserted_at timestamp updated_at timestamp } Table shield_rules_hostname { id uuid [pk] site_id integer hostname varchar hostname_pattern varchar action action added_by varchar inserted_at timestamp updated_at timestamp } Table shield_rules_ip { id uuid [pk] site_id integer inet inet action action description varchar added_by varchar inserted_at timestamp updated_at timestamp } Table shield_rules_page { id uuid [pk] site_id integer page_path varchar page_path_pattern varchar action action added_by varchar inserted_at timestamp updated_at timestamp } Table google_auth { id integer [pk] email varchar property varchar refresh_token varchar access_token varchar expires timestamp user_id integer site_id integer inserted_at timestamp updated_at timestamp } Table site_memberships { id integer [pk] role role site_id integer user_id integer inserted_at timestamp updated_at timestamp } Table monthly_reports { id integer [pk] recipients array site_id integer inserted_at timestamp updated_at timestamp } Table shared_links { id integer [pk] site_id integer name varchar slug varchar password_hash varchar inserted_at timestamp updated_at timestamp } Table spike_notifications { id integer [pk] recipients array threshold integer last_sent timestamp site_id integer inserted_at timestamp updated_at timestamp } Table site_user_preferences { id integer [pk] pinned_at timestamp user_id integer site_id integer inserted_at timestamp updated_at timestamp } Table weekly_reports { id integer [pk] recipients array site_id integer inserted_at timestamp updated_at timestamp } Ref: funnels.id < funnel_steps.funnel_id Ref: goals.id < funnel_steps.goal_id Ref: sites.id < funnels.site_id Ref: sites.id < goals.site_id Ref: sites.id - google_auth.site_id Ref: sites.id < invitations.site_id Ref: sites.id - monthly_reports.site_id Ref: sites.id < plugins_api_tokens.site_id Ref: sites.id < shared_links.site_id Ref: sites.id < shield_rules_country.site_id Ref: sites.id < shield_rules_hostname.site_id Ref: sites.id < shield_rules_ip.site_id Ref: sites.id < shield_rules_page.site_id Ref: sites.id < site_imports.site_id Ref: sites.id - site_memberships.site_id Ref: sites.id < site_user_preferences.site_id Ref: sites.id - spike_notifications.site_id Ref: sites.id - weekly_reports.site_id Ref: users.id < api_keys.user_id Ref: users.id < email_activation_codes.user_id Ref: users.id - enterprise_plans.user_id Ref: users.id - google_auth.user_id Ref: users.id < invitations.inviter_id Ref: users.id < site_imports.imported_by_id Ref: users.id < site_memberships.user_id Ref: users.id < site_user_preferences.user_id Ref: users.id - subscriptions.user_id Ref: users.id < totp_recovery_codes.user_id ================================================ FILE: examples/dbml/plausible-analytics/Default.dbml ================================================ Enum billing_interval { yearly monthly } Enum currency { KMF AUD SAR BWP BBD EGP YER CDF IQD MRU JOD XPT XBB NGN BDT CNY ANG GTQ HTG TWD OMR STN AOA MUR XCD TND THB KES GIP MZN ERN MAD FKP MVR BND KZT EUR SYP MYR RSD KRW COU GMD ILS BAM XAG AZN AFN AWG SOS PAB AED UYI BTN USN KPW IDR XPD MOP GEL MXV CHW XAF UGX DJF SGD PGK IRR VES PHP SSP BOB XDR JPY BHD UAH ZAR BSD TMT XOF XTS MNT XSU XPF TTD PLN AMD SBD LSL GBP DOP SEK MDL CUP CZK SZL COP XAU NOK CLF RWF NAD KHR TRY LAK SDG XXX PEN LBP BZD CLP KGS TZS GNF KWD NZD SVC LRD CHF PKR SHP XUA XBD LYD BIF JMD ALL BYN CUC UZS MKD ZWL RON NIO MMK SRD ETB ARS GHS XBA XBC UYW HKD ISK DZD MWK RUB SLL SCR CHE CVE VND ZMW HNL HUF INR DKK FJD HRK UYU PYG BMD KYD VUV BGN TOP MXN CAD MGA BOV BRL WST NPR CRC GYD TJS LKR QAR USD } Enum role { owner admin viewer } Enum action { allow deny } Enum source { noop csv universal_analytics google_analytics_4 } Enum site_imports_status { pending failed completed importing } Enum subscriptions_status { active deleted past_due paused } Enum theme { system light dark } Table schema_migrations { version integer [pk] inserted_at timestamp } Table fun_with_flags_toggles { id integer [pk] flag_name varchar gate_type varchar target varchar enabled boolean } Table oban_jobs { id integer [pk] state varchar queue varchar worker varchar args jsonb meta jsonb tags array errors array attempt integer attempted_by array max_attempts integer priority integer attempted_at timestamp cancelled_at timestamp completed_at timestamp discarded_at timestamp inserted_at timestamp scheduled_at timestamp } Table api_keys { id integer [pk] name varchar scopes array hourly_request_limit integer key_hash varchar key_prefix varchar user_id integer inserted_at timestamp updated_at timestamp } Table email_activation_codes { id integer [pk] code varchar issued_at timestamp user_id integer } Table invitations { id integer [pk] invitation_id varchar email varchar role role inviter_id integer site_id integer inserted_at timestamp updated_at timestamp } Table totp_recovery_codes { id integer [pk] code_digest varchar user_id integer inserted_at timestamp } Table users { id integer [pk] email varchar password_hash varchar name varchar last_seen timestamp trial_expiry_date date theme theme email_verified boolean previous_email varchar accept_traffic_until date allow_next_upgrade_override boolean totp_enabled boolean totp_secret bytea totp_token varchar totp_last_used_at timestamp grace_period jsonb inserted_at timestamp updated_at timestamp } Table enterprise_plans { id integer [pk] paddle_plan_id varchar billing_interval billing_interval monthly_pageview_limit integer site_limit integer team_member_limit integer features array hourly_api_request_limit integer user_id integer inserted_at timestamp updated_at timestamp } Table subscriptions { id integer [pk] paddle_subscription_id varchar paddle_plan_id varchar update_url varchar cancel_url varchar status subscriptions_status next_bill_amount varchar next_bill_date date last_bill_date date currency_code varchar user_id integer inserted_at timestamp updated_at timestamp } Table events_v2 { name unknown site_id unknown hostname varchar pathname varchar user_id unknown session_id unknown timestamp timestamp "meta.key" array "meta.value" array revenue_source_amount unknown revenue_source_currency unknown revenue_reporting_amount unknown revenue_reporting_currency unknown referrer varchar referrer_source varchar utm_medium varchar utm_source varchar utm_campaign varchar utm_content varchar utm_term varchar country_code unknown subdivision1_code unknown subdivision2_code unknown city_geoname_id unknown screen_size unknown operating_system unknown operating_system_version unknown browser unknown browser_version unknown } Table sessions_v2 { hostname varchar site_id unknown user_id unknown session_id unknown start timestamp duration unknown is_bounce unknown entry_page varchar exit_page varchar exit_page_hostname varchar pageviews unknown events unknown sign unknown "entry_meta.key" array "entry_meta.value" array utm_medium varchar utm_source varchar utm_campaign varchar utm_content varchar utm_term varchar referrer varchar referrer_source varchar country_code unknown subdivision1_code unknown subdivision2_code unknown city_geoname_id unknown screen_size unknown operating_system unknown operating_system_version unknown browser unknown browser_version unknown timestamp timestamp transferred_from varchar } Table domains_lookup { site_id unknown domain varchar } Table funnels { id integer [pk] name varchar site_id integer inserted_at timestamp updated_at timestamp } Table funnel_steps { id integer [pk] step_order integer funnel_id integer goal_id integer inserted_at timestamp updated_at timestamp } Table goals { id integer [pk] event_name varchar page_path varchar currency currency site_id integer inserted_at timestamp updated_at timestamp } Table imported_browsers { site_id unknown import_id unknown date date browser varchar browser_version varchar visitors unknown visits unknown visit_duration unknown pageviews unknown bounces unknown } Table imported_devices { site_id unknown import_id unknown date date device varchar visitors unknown visits unknown visit_duration unknown pageviews unknown bounces unknown } Table imported_entry_pages { site_id unknown import_id unknown date date entry_page varchar visitors unknown entrances unknown visit_duration unknown pageviews unknown bounces unknown } Table imported_exit_pages { site_id unknown import_id unknown date date exit_page varchar exits unknown visitors unknown visit_duration unknown pageviews unknown bounces unknown } Table imported_locations { site_id unknown import_id unknown date date country varchar region varchar city unknown visitors unknown visits unknown visit_duration unknown pageviews unknown bounces unknown } Table imported_operating_systems { site_id unknown import_id unknown date date operating_system varchar operating_system_version varchar visitors unknown visits unknown visit_duration unknown pageviews unknown bounces unknown } Table imported_pages { site_id unknown import_id unknown date date hostname varchar page varchar visits unknown visitors unknown active_visitors unknown pageviews unknown exits unknown time_on_page unknown } Table site_imports { id integer [pk] start_date date end_date date label varchar source source status site_imports_status legacy boolean site_id integer imported_by_id integer inserted_at timestamp updated_at timestamp } Table imported_sources { site_id unknown import_id unknown date date source varchar referrer varchar utm_source varchar utm_medium varchar utm_campaign varchar utm_content varchar utm_term varchar visitors unknown visits unknown visit_duration unknown pageviews unknown bounces unknown } Table imported_visitors { site_id unknown import_id unknown date date visitors unknown pageviews unknown bounces unknown visits unknown visit_duration unknown } Table ingest_counters { event_timebucket timestamp site_id unknown domain unknown metric unknown value unknown } Table plugins_api_tokens { id uuid [pk] inserted_at timestamp updated_at timestamp token_hash bytea description varchar hint varchar last_used_at timestamp site_id integer } Table shield_rules_country { id uuid [pk] site_id integer country_code varchar action action added_by varchar inserted_at timestamp updated_at timestamp } Table shield_rules_hostname { id uuid [pk] site_id integer hostname varchar hostname_pattern varchar action action added_by varchar inserted_at timestamp updated_at timestamp } Table shield_rules_ip { id uuid [pk] site_id integer inet inet action action description varchar added_by varchar inserted_at timestamp updated_at timestamp } Table shield_rules_page { id uuid [pk] site_id integer page_path varchar page_path_pattern varchar action action added_by varchar inserted_at timestamp updated_at timestamp } Table sites { id integer [pk] domain varchar timezone varchar public boolean locked boolean stats_start_date date native_stats_start_at timestamp allowed_event_props array conversions_enabled boolean props_enabled boolean funnels_enabled boolean ingest_rate_limit_scale_seconds integer ingest_rate_limit_threshold integer domain_changed_from varchar domain_changed_at timestamp imported_data jsonb inserted_at timestamp updated_at timestamp } Table google_auth { id integer [pk] email varchar property varchar refresh_token varchar access_token varchar expires timestamp user_id integer site_id integer inserted_at timestamp updated_at timestamp } Table site_memberships { id integer [pk] role role site_id integer user_id integer inserted_at timestamp updated_at timestamp } Table monthly_reports { id integer [pk] recipients array site_id integer inserted_at timestamp updated_at timestamp } Table shared_links { id integer [pk] site_id integer name varchar slug varchar password_hash varchar inserted_at timestamp updated_at timestamp } Table spike_notifications { id integer [pk] recipients array threshold integer last_sent timestamp site_id integer inserted_at timestamp updated_at timestamp } Table site_user_preferences { id integer [pk] pinned_at timestamp user_id integer site_id integer inserted_at timestamp updated_at timestamp } Table weekly_reports { id integer [pk] recipients array site_id integer inserted_at timestamp updated_at timestamp } Ref: funnels.id < funnel_steps.funnel_id Ref: goals.id < funnel_steps.goal_id Ref: sites.id < funnels.site_id Ref: sites.id < goals.site_id Ref: sites.id - google_auth.site_id Ref: sites.id < invitations.site_id Ref: sites.id - monthly_reports.site_id Ref: sites.id < plugins_api_tokens.site_id Ref: sites.id < shared_links.site_id Ref: sites.id < shield_rules_country.site_id Ref: sites.id < shield_rules_hostname.site_id Ref: sites.id < shield_rules_ip.site_id Ref: sites.id < shield_rules_page.site_id Ref: sites.id < site_imports.site_id Ref: sites.id - site_memberships.site_id Ref: sites.id < site_user_preferences.site_id Ref: sites.id - spike_notifications.site_id Ref: sites.id - weekly_reports.site_id Ref: users.id < api_keys.user_id Ref: users.id < email_activation_codes.user_id Ref: users.id - enterprise_plans.user_id Ref: users.id - google_auth.user_id Ref: users.id < invitations.inviter_id Ref: users.id < site_imports.imported_by_id Ref: users.id < site_memberships.user_id Ref: users.id < site_user_preferences.user_id Ref: users.id - subscriptions.user_id Ref: users.id < totp_recovery_codes.user_id ================================================ FILE: examples/dot/changelog.com/Clusters.dot ================================================ digraph { ranksep=1.0; rankdir=LR; node [shape = none, fontname="Roboto Mono"]; "Changelog.Feed" [label= <
Changelog.Feed
feeds
:id :id
:name :string
:slug :string
:description :string
:title_format :string
:plusplus :boolean
:autosub :boolean
:starts_at :utc_datetime
:cover Changelog.Files.Cover.Type
:podcast_ids {:array, :integer}
:person_ids {:array, :integer}
:owner_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.Subscription" [label= <
Changelog.Subscription
subscriptions
:id :id
:unsubscribed_at :utc_datetime
:context :string
:episode_id :id
:item_id :id
:person_id :id
:podcast_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.Topic" [label= <
Changelog.Topic
topics
:id :id
:name :string
:slug :string
:description :string
:website :string
:twitter_handle :string
:icon Changelog.Files.Icon.Type
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Ecto.Migration.SchemaMigration" [label= <
Ecto.Migration.SchemaMigration
schema_migrations
:version :integer
:inserted_at :naive_datetime
>] "Oban.Job" [label= <
Oban.Job
oban_jobs
:id :id
:state :string
:queue :string
:worker :string
:args :map
:meta :map
:tags {:array, :string}
:errors {:array, :map}
:attempt :integer
:attempted_by {:array, :string}
:max_attempts :integer
:priority :integer
:attempted_at :utc_datetime_usec
:cancelled_at :utc_datetime_usec
:completed_at :utc_datetime_usec
:discarded_at :utc_datetime_usec
:inserted_at :utc_datetime_usec
:scheduled_at :utc_datetime_usec
>] subgraph cluster_EPISODE { style=filled fontname="Roboto Mono" color = "#b4eeb4" label = <EPISODE> "Changelog.Episode" [label= <
Changelog.Episode
episodes
:id :id
:slug :string
:guid :string
:title :string
:subtitle :string
:type Changelog.Episode.Type
:featured :boolean
:highlight :string
:subhighlight :string
:summary :string
:notes :string
:doc_url :string
:socialize_url :string
:published :boolean
:published_at :utc_datetime
:recorded_at :utc_datetime
:recorded_live :boolean
:youtube_id :string
:cover Changelog.Files.Cover.Type
:audio_file Changelog.Files.Audio.Type
:audio_bytes :integer
:audio_duration :integer
:audio_chapters #Ecto.Embedded<[many: Changelog.EpisodeChapter]>
:plusplus_file Changelog.Files.PlusPlus.Type
:plusplus_bytes :integer
:plusplus_duration :integer
:plusplus_chapters #Ecto.Embedded<[many: Changelog.EpisodeChapter]>
:download_count :float
:import_count :float
:reach_count :integer
:email_subject :string
:email_teaser :string
:email_content :string
:email_sends :integer
:email_opens :integer
:transcript {:array, :map}
:podcast_id :id
:request_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.EpisodeChapter" [label= <
Changelog.EpisodeChapter
:id :binary_id
:title :string
:starts_at :float
:ends_at :float
:link_url :string
:image_url :string
>] "Changelog.EpisodeGuest" [label= <
Changelog.EpisodeGuest
episode_guests
:id :id
:position :integer
:thanks :boolean
:discount_code :string
:episode_id :id
:person_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.EpisodeHost" [label= <
Changelog.EpisodeHost
episode_hosts
:id :id
:position :integer
:person_id :id
:episode_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.EpisodeRequest" [label= <
Changelog.EpisodeRequest
episode_requests
:id :id
:status Changelog.EpisodeRequest.Status
:hosts :string
:guests :string
:topics :string
:pitch :string
:pronunciation :string
:message :string
:podcast_id :id
:submitter_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.EpisodeSponsor" [label= <
Changelog.EpisodeSponsor
episode_sponsors
:id :id
:position :integer
:title :string
:link_url :string
:description :string
:starts_at :float
:ends_at :float
:episode_id :id
:sponsor_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.EpisodeStat" [label= <
Changelog.EpisodeStat
episode_stats
:id :id
:date :date
:episode_bytes :integer
:total_bytes :integer
:downloads :float
:uniques :integer
:demographics :map
:episode_id :id
:podcast_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.EpisodeTopic" [label= <
Changelog.EpisodeTopic
episode_topics
:id :id
:position :integer
:topic_id :id
:episode_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] } subgraph cluster_NEWS { style=filled fontname="Roboto Mono" color = "#eee5de" label = <NEWS> "Changelog.NewsAd" [label= <
Changelog.NewsAd
news_ads
:id :id
:url :string
:headline :string
:story :string
:image Changelog.Files.Image.Type
:active :boolean
:newsletter :boolean
:impression_count :integer
:click_count :integer
:sponsorship_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.NewsIssue" [label= <
Changelog.NewsIssue
news_issues
:id :id
:slug :string
:note :string
:teaser :string
:published :boolean
:published_at :utc_datetime
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.NewsIssueAd" [label= <
Changelog.NewsIssueAd
news_issue_ads
:id :id
:position :integer
:image :boolean
:ad_id :id
:issue_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.NewsIssueItem" [label= <
Changelog.NewsIssueItem
news_issue_items
:id :id
:position :integer
:image :boolean
:issue_id :id
:item_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.NewsItem" [label= <
Changelog.NewsItem
news_items
:id :id
:status Changelog.NewsItem.Status
:type Changelog.NewsItem.Type
:url :string
:headline :string
:story :string
:image Changelog.Files.Image.Type
:object_id :string
:feed_only :boolean
:pinned :boolean
:published_at :utc_datetime
:refreshed_at :utc_datetime
:impression_count :integer
:click_count :integer
:message :string
:author_id :id
:logger_id :id
:submitter_id :id
:source_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.NewsItemComment" [label= <
Changelog.NewsItemComment
news_item_comments
:id :id
:content :string
:approved :boolean
:edited_at :utc_datetime
:deleted_at :utc_datetime
:item_id :id
:author_id :id
:parent_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.NewsItemTopic" [label= <
Changelog.NewsItemTopic
news_item_topics
:id :id
:position :integer
:item_id :id
:topic_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.NewsQueue" [label= <
Changelog.NewsQueue
news_queue
:id :id
:position :float
:item_id :id
>] "Changelog.NewsSource" [label= <
Changelog.NewsSource
news_sources
:id :id
:name :string
:slug :string
:website :string
:twitter_handle :string
:description :string
:feed :string
:regex :string
:publication :boolean
:icon Changelog.Files.Icon.Type
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.NewsSponsorship" [label= <
Changelog.NewsSponsorship
news_sponsorships
:id :id
:name :string
:weeks {:array, :date}
:impression_count :integer
:click_count :integer
:sponsor_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] } subgraph cluster_PERSON { style=filled fontname="Roboto Mono" color = "#f0ffff" label = <PERSON> "Changelog.Person" [label= <
Changelog.Person
people
:id :id
:name :string
:email :string
:handle :string
:github_handle :string
:linkedin_handle :string
:mastodon_handle :string
:twitter_handle :string
:slack_id :string
:website :string
:bio :string
:location :string
:auth_token :string
:auth_token_expires_at :utc_datetime
:joined_at :utc_datetime
:signed_in_at :utc_datetime
:approved :boolean
:avatar Changelog.Files.Avatar.Type
:admin :boolean
:host :boolean
:editor :boolean
:public_profile :boolean
:settings #Ecto.Embedded<[one: Changelog.Person.Settings]>
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.Person.Settings" [label= <
Changelog.Person.Settings
:subscribe_to_contributed_news :boolean
:subscribe_to_participated_episodes :boolean
:email_on_authored_news :boolean
:email_on_submitted_news :boolean
:email_on_comment_replies :boolean
:email_on_comment_mentions :boolean
>] } subgraph cluster_PODCAST { style=filled fontname="Roboto Mono" color = "#ffefd5" label = <PODCAST> "Changelog.Podcast" [label= <
Changelog.Podcast
podcasts
:id :id
:name :string
:slug :string
:status Changelog.Podcast.Status
:welcome :string
:description :string
:extended_description :string
:vanity_domain :string
:keywords :string
:mastodon_handle :string
:twitter_handle :string
:apple_url :string
:spotify_url :string
:riverside_url :string
:chartable_id :string
:schedule_note :string
:download_count :float
:reach_count :integer
:recorded_live :boolean
:partner :boolean
:position :integer
:subscribers :map
:cover Changelog.Files.Cover.Type
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.PodcastHost" [label= <
Changelog.PodcastHost
podcast_hosts
:id :id
:position :integer
:retired :boolean
:person_id :id
:podcast_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.PodcastTopic" [label= <
Changelog.PodcastTopic
podcast_topics
:id :id
:position :integer
:podcast_id :id
:topic_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] } subgraph cluster_POST { style=filled fontname="Roboto Mono" color = "#eee5de" label = <POST> "Changelog.Post" [label= <
Changelog.Post
posts
:id :id
:title :string
:subtitle :string
:slug :string
:guid :string
:canonical_url :string
:image Changelog.Files.Image.Type
:tldr :string
:body :string
:published :boolean
:published_at :utc_datetime
:author_id :id
:editor_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.PostTopic" [label= <
Changelog.PostTopic
post_topics
:id :id
:position :integer
:topic_id :id
:post_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] } subgraph cluster_SPONSOR { style=filled fontname="Roboto Mono" color = "#fffafa" label = <SPONSOR> "Changelog.Sponsor" [label= <
Changelog.Sponsor
sponsors
:id :id
:name :string
:description :string
:website :string
:github_handle :string
:twitter_handle :string
:avatar Changelog.Files.Avatar.Type
:color_logo Changelog.Files.ColorLogo.Type
:dark_logo Changelog.Files.DarkLogo.Type
:light_logo Changelog.Files.LightLogo.Type
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.SponsorRep" [label= <
Changelog.SponsorRep
sponsor_reps
:id :id
:sponsor_id :id
:person_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] } "Changelog.EpisodeRequest":"field@id":e -> "Changelog.Episode":"field@request_id":w [dir=none] "Changelog.Episode":"field@audio_chapters":e -> "Changelog.EpisodeChapter":"header@schema_module":w "Changelog.Episode":"field@id":e -> "Changelog.EpisodeGuest":"field@episode_id":w "Changelog.Episode":"field@id":e -> "Changelog.EpisodeHost":"field@episode_id":w "Changelog.Episode":"field@id":e -> "Changelog.EpisodeSponsor":"field@episode_id":w "Changelog.Episode":"field@id":e -> "Changelog.EpisodeStat":"field@episode_id":w "Changelog.Episode":"field@id":e -> "Changelog.EpisodeTopic":"field@episode_id":w "Changelog.Episode":"field@id":e -> "Changelog.Subscription":"field@episode_id":w "Changelog.Episode":"field@plusplus_chapters":e -> "Changelog.EpisodeChapter":"header@schema_module":w "Changelog.NewsAd":"field@id":e -> "Changelog.NewsIssueAd":"field@ad_id":w "Changelog.NewsIssue":"field@id":e -> "Changelog.NewsIssueAd":"field@issue_id":w "Changelog.NewsIssue":"field@id":e -> "Changelog.NewsIssueItem":"field@issue_id":w "Changelog.NewsItemComment":"field@id":e -> "Changelog.NewsItemComment":"field@parent_id":w "Changelog.NewsItem":"field@id":e -> "Changelog.NewsIssueItem":"field@item_id":w "Changelog.NewsItem":"field@id":e -> "Changelog.NewsItemComment":"field@item_id":w "Changelog.NewsItem":"field@id":e -> "Changelog.NewsItemTopic":"field@item_id":w "Changelog.NewsItem":"field@id":e -> "Changelog.NewsQueue":"field@item_id":w [dir=none] "Changelog.NewsItem":"field@id":e -> "Changelog.Subscription":"field@item_id":w "Changelog.NewsSource":"field@id":e -> "Changelog.NewsItem":"field@source_id":w "Changelog.NewsSponsorship":"field@id":e -> "Changelog.NewsAd":"field@sponsorship_id":w "Changelog.Person":"field@id":e -> "Changelog.EpisodeGuest":"field@person_id":w "Changelog.Person":"field@id":e -> "Changelog.EpisodeHost":"field@person_id":w "Changelog.Person":"field@id":e -> "Changelog.EpisodeRequest":"field@submitter_id":w "Changelog.Person":"field@id":e -> "Changelog.Feed":"field@owner_id":w "Changelog.Person":"field@id":e -> "Changelog.NewsItemComment":"field@author_id":w "Changelog.Person":"field@id":e -> "Changelog.NewsItem":"field@author_id":w "Changelog.Person":"field@id":e -> "Changelog.NewsItem":"field@logger_id":w "Changelog.Person":"field@id":e -> "Changelog.NewsItem":"field@submitter_id":w "Changelog.Person":"field@id":e -> "Changelog.PodcastHost":"field@person_id":w "Changelog.Person":"field@id":e -> "Changelog.Post":"field@author_id":w "Changelog.Person":"field@id":e -> "Changelog.Post":"field@editor_id":w "Changelog.Person":"field@id":e -> "Changelog.SponsorRep":"field@person_id":w "Changelog.Person":"field@id":e -> "Changelog.Subscription":"field@person_id":w "Changelog.Person":"field@settings":e -> "Changelog.Person.Settings":"header@schema_module":w [dir=none] "Changelog.Podcast":"field@id":e -> "Changelog.EpisodeRequest":"field@podcast_id":w "Changelog.Podcast":"field@id":e -> "Changelog.EpisodeStat":"field@podcast_id":w "Changelog.Podcast":"field@id":e -> "Changelog.Episode":"field@podcast_id":w "Changelog.Podcast":"field@id":e -> "Changelog.PodcastHost":"field@podcast_id":w "Changelog.Podcast":"field@id":e -> "Changelog.PodcastTopic":"field@podcast_id":w "Changelog.Podcast":"field@id":e -> "Changelog.Subscription":"field@podcast_id":w "Changelog.Post":"field@id":e -> "Changelog.PostTopic":"field@post_id":w "Changelog.Sponsor":"field@id":e -> "Changelog.EpisodeSponsor":"field@sponsor_id":w "Changelog.Sponsor":"field@id":e -> "Changelog.NewsSponsorship":"field@sponsor_id":w "Changelog.Sponsor":"field@id":e -> "Changelog.SponsorRep":"field@sponsor_id":w "Changelog.Topic":"field@id":e -> "Changelog.EpisodeTopic":"field@topic_id":w "Changelog.Topic":"field@id":e -> "Changelog.NewsItemTopic":"field@topic_id":w "Changelog.Topic":"field@id":e -> "Changelog.PodcastTopic":"field@topic_id":w "Changelog.Topic":"field@id":e -> "Changelog.PostTopic":"field@topic_id":w } ================================================ FILE: examples/dot/changelog.com/Default.dot ================================================ digraph { ranksep=1.0; rankdir=LR; node [shape = none, fontname="Roboto Mono"]; "Changelog.Episode" [label= <
Changelog.Episode
episodes
:id :id
:slug :string
:guid :string
:title :string
:subtitle :string
:type Changelog.Episode.Type
:featured :boolean
:highlight :string
:subhighlight :string
:summary :string
:notes :string
:doc_url :string
:socialize_url :string
:published :boolean
:published_at :utc_datetime
:recorded_at :utc_datetime
:recorded_live :boolean
:youtube_id :string
:cover Changelog.Files.Cover.Type
:audio_file Changelog.Files.Audio.Type
:audio_bytes :integer
:audio_duration :integer
:audio_chapters #Ecto.Embedded<[many: Changelog.EpisodeChapter]>
:plusplus_file Changelog.Files.PlusPlus.Type
:plusplus_bytes :integer
:plusplus_duration :integer
:plusplus_chapters #Ecto.Embedded<[many: Changelog.EpisodeChapter]>
:download_count :float
:import_count :float
:reach_count :integer
:email_subject :string
:email_teaser :string
:email_content :string
:email_sends :integer
:email_opens :integer
:transcript {:array, :map}
:podcast_id :id
:request_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.EpisodeChapter" [label= <
Changelog.EpisodeChapter
:id :binary_id
:title :string
:starts_at :float
:ends_at :float
:link_url :string
:image_url :string
>] "Changelog.EpisodeGuest" [label= <
Changelog.EpisodeGuest
episode_guests
:id :id
:position :integer
:thanks :boolean
:discount_code :string
:episode_id :id
:person_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.EpisodeHost" [label= <
Changelog.EpisodeHost
episode_hosts
:id :id
:position :integer
:person_id :id
:episode_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.EpisodeRequest" [label= <
Changelog.EpisodeRequest
episode_requests
:id :id
:status Changelog.EpisodeRequest.Status
:hosts :string
:guests :string
:topics :string
:pitch :string
:pronunciation :string
:message :string
:podcast_id :id
:submitter_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.EpisodeSponsor" [label= <
Changelog.EpisodeSponsor
episode_sponsors
:id :id
:position :integer
:title :string
:link_url :string
:description :string
:starts_at :float
:ends_at :float
:episode_id :id
:sponsor_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.EpisodeStat" [label= <
Changelog.EpisodeStat
episode_stats
:id :id
:date :date
:episode_bytes :integer
:total_bytes :integer
:downloads :float
:uniques :integer
:demographics :map
:episode_id :id
:podcast_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.EpisodeTopic" [label= <
Changelog.EpisodeTopic
episode_topics
:id :id
:position :integer
:topic_id :id
:episode_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.Feed" [label= <
Changelog.Feed
feeds
:id :id
:name :string
:slug :string
:description :string
:title_format :string
:plusplus :boolean
:autosub :boolean
:starts_at :utc_datetime
:cover Changelog.Files.Cover.Type
:podcast_ids {:array, :integer}
:person_ids {:array, :integer}
:owner_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.NewsAd" [label= <
Changelog.NewsAd
news_ads
:id :id
:url :string
:headline :string
:story :string
:image Changelog.Files.Image.Type
:active :boolean
:newsletter :boolean
:impression_count :integer
:click_count :integer
:sponsorship_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.NewsIssue" [label= <
Changelog.NewsIssue
news_issues
:id :id
:slug :string
:note :string
:teaser :string
:published :boolean
:published_at :utc_datetime
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.NewsIssueAd" [label= <
Changelog.NewsIssueAd
news_issue_ads
:id :id
:position :integer
:image :boolean
:ad_id :id
:issue_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.NewsIssueItem" [label= <
Changelog.NewsIssueItem
news_issue_items
:id :id
:position :integer
:image :boolean
:issue_id :id
:item_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.NewsItem" [label= <
Changelog.NewsItem
news_items
:id :id
:status Changelog.NewsItem.Status
:type Changelog.NewsItem.Type
:url :string
:headline :string
:story :string
:image Changelog.Files.Image.Type
:object_id :string
:feed_only :boolean
:pinned :boolean
:published_at :utc_datetime
:refreshed_at :utc_datetime
:impression_count :integer
:click_count :integer
:message :string
:author_id :id
:logger_id :id
:submitter_id :id
:source_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.NewsItemComment" [label= <
Changelog.NewsItemComment
news_item_comments
:id :id
:content :string
:approved :boolean
:edited_at :utc_datetime
:deleted_at :utc_datetime
:item_id :id
:author_id :id
:parent_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.NewsItemTopic" [label= <
Changelog.NewsItemTopic
news_item_topics
:id :id
:position :integer
:item_id :id
:topic_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.NewsQueue" [label= <
Changelog.NewsQueue
news_queue
:id :id
:position :float
:item_id :id
>] "Changelog.NewsSource" [label= <
Changelog.NewsSource
news_sources
:id :id
:name :string
:slug :string
:website :string
:twitter_handle :string
:description :string
:feed :string
:regex :string
:publication :boolean
:icon Changelog.Files.Icon.Type
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.NewsSponsorship" [label= <
Changelog.NewsSponsorship
news_sponsorships
:id :id
:name :string
:weeks {:array, :date}
:impression_count :integer
:click_count :integer
:sponsor_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.Person" [label= <
Changelog.Person
people
:id :id
:name :string
:email :string
:handle :string
:github_handle :string
:linkedin_handle :string
:mastodon_handle :string
:twitter_handle :string
:slack_id :string
:website :string
:bio :string
:location :string
:auth_token :string
:auth_token_expires_at :utc_datetime
:joined_at :utc_datetime
:signed_in_at :utc_datetime
:approved :boolean
:avatar Changelog.Files.Avatar.Type
:admin :boolean
:host :boolean
:editor :boolean
:public_profile :boolean
:settings #Ecto.Embedded<[one: Changelog.Person.Settings]>
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.Person.Settings" [label= <
Changelog.Person.Settings
:subscribe_to_contributed_news :boolean
:subscribe_to_participated_episodes :boolean
:email_on_authored_news :boolean
:email_on_submitted_news :boolean
:email_on_comment_replies :boolean
:email_on_comment_mentions :boolean
>] "Changelog.Podcast" [label= <
Changelog.Podcast
podcasts
:id :id
:name :string
:slug :string
:status Changelog.Podcast.Status
:welcome :string
:description :string
:extended_description :string
:vanity_domain :string
:keywords :string
:mastodon_handle :string
:twitter_handle :string
:apple_url :string
:spotify_url :string
:riverside_url :string
:chartable_id :string
:schedule_note :string
:download_count :float
:reach_count :integer
:recorded_live :boolean
:partner :boolean
:position :integer
:subscribers :map
:cover Changelog.Files.Cover.Type
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.PodcastHost" [label= <
Changelog.PodcastHost
podcast_hosts
:id :id
:position :integer
:retired :boolean
:person_id :id
:podcast_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.PodcastTopic" [label= <
Changelog.PodcastTopic
podcast_topics
:id :id
:position :integer
:podcast_id :id
:topic_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.Post" [label= <
Changelog.Post
posts
:id :id
:title :string
:subtitle :string
:slug :string
:guid :string
:canonical_url :string
:image Changelog.Files.Image.Type
:tldr :string
:body :string
:published :boolean
:published_at :utc_datetime
:author_id :id
:editor_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.PostTopic" [label= <
Changelog.PostTopic
post_topics
:id :id
:position :integer
:topic_id :id
:post_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.Sponsor" [label= <
Changelog.Sponsor
sponsors
:id :id
:name :string
:description :string
:website :string
:github_handle :string
:twitter_handle :string
:avatar Changelog.Files.Avatar.Type
:color_logo Changelog.Files.ColorLogo.Type
:dark_logo Changelog.Files.DarkLogo.Type
:light_logo Changelog.Files.LightLogo.Type
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.SponsorRep" [label= <
Changelog.SponsorRep
sponsor_reps
:id :id
:sponsor_id :id
:person_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.Subscription" [label= <
Changelog.Subscription
subscriptions
:id :id
:unsubscribed_at :utc_datetime
:context :string
:episode_id :id
:item_id :id
:person_id :id
:podcast_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Changelog.Topic" [label= <
Changelog.Topic
topics
:id :id
:name :string
:slug :string
:description :string
:website :string
:twitter_handle :string
:icon Changelog.Files.Icon.Type
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Ecto.Migration.SchemaMigration" [label= <
Ecto.Migration.SchemaMigration
schema_migrations
:version :integer
:inserted_at :naive_datetime
>] "Oban.Job" [label= <
Oban.Job
oban_jobs
:id :id
:state :string
:queue :string
:worker :string
:args :map
:meta :map
:tags {:array, :string}
:errors {:array, :map}
:attempt :integer
:attempted_by {:array, :string}
:max_attempts :integer
:priority :integer
:attempted_at :utc_datetime_usec
:cancelled_at :utc_datetime_usec
:completed_at :utc_datetime_usec
:discarded_at :utc_datetime_usec
:inserted_at :utc_datetime_usec
:scheduled_at :utc_datetime_usec
>] "Changelog.EpisodeRequest":"field@id":e -> "Changelog.Episode":"field@request_id":w [dir=none] "Changelog.Episode":"field@audio_chapters":e -> "Changelog.EpisodeChapter":"header@schema_module":w "Changelog.Episode":"field@id":e -> "Changelog.EpisodeGuest":"field@episode_id":w "Changelog.Episode":"field@id":e -> "Changelog.EpisodeHost":"field@episode_id":w "Changelog.Episode":"field@id":e -> "Changelog.EpisodeSponsor":"field@episode_id":w "Changelog.Episode":"field@id":e -> "Changelog.EpisodeStat":"field@episode_id":w "Changelog.Episode":"field@id":e -> "Changelog.EpisodeTopic":"field@episode_id":w "Changelog.Episode":"field@id":e -> "Changelog.Subscription":"field@episode_id":w "Changelog.Episode":"field@plusplus_chapters":e -> "Changelog.EpisodeChapter":"header@schema_module":w "Changelog.NewsAd":"field@id":e -> "Changelog.NewsIssueAd":"field@ad_id":w "Changelog.NewsIssue":"field@id":e -> "Changelog.NewsIssueAd":"field@issue_id":w "Changelog.NewsIssue":"field@id":e -> "Changelog.NewsIssueItem":"field@issue_id":w "Changelog.NewsItemComment":"field@id":e -> "Changelog.NewsItemComment":"field@parent_id":w "Changelog.NewsItem":"field@id":e -> "Changelog.NewsIssueItem":"field@item_id":w "Changelog.NewsItem":"field@id":e -> "Changelog.NewsItemComment":"field@item_id":w "Changelog.NewsItem":"field@id":e -> "Changelog.NewsItemTopic":"field@item_id":w "Changelog.NewsItem":"field@id":e -> "Changelog.NewsQueue":"field@item_id":w [dir=none] "Changelog.NewsItem":"field@id":e -> "Changelog.Subscription":"field@item_id":w "Changelog.NewsSource":"field@id":e -> "Changelog.NewsItem":"field@source_id":w "Changelog.NewsSponsorship":"field@id":e -> "Changelog.NewsAd":"field@sponsorship_id":w "Changelog.Person":"field@id":e -> "Changelog.EpisodeGuest":"field@person_id":w "Changelog.Person":"field@id":e -> "Changelog.EpisodeHost":"field@person_id":w "Changelog.Person":"field@id":e -> "Changelog.EpisodeRequest":"field@submitter_id":w "Changelog.Person":"field@id":e -> "Changelog.Feed":"field@owner_id":w "Changelog.Person":"field@id":e -> "Changelog.NewsItemComment":"field@author_id":w "Changelog.Person":"field@id":e -> "Changelog.NewsItem":"field@author_id":w "Changelog.Person":"field@id":e -> "Changelog.NewsItem":"field@logger_id":w "Changelog.Person":"field@id":e -> "Changelog.NewsItem":"field@submitter_id":w "Changelog.Person":"field@id":e -> "Changelog.PodcastHost":"field@person_id":w "Changelog.Person":"field@id":e -> "Changelog.Post":"field@author_id":w "Changelog.Person":"field@id":e -> "Changelog.Post":"field@editor_id":w "Changelog.Person":"field@id":e -> "Changelog.SponsorRep":"field@person_id":w "Changelog.Person":"field@id":e -> "Changelog.Subscription":"field@person_id":w "Changelog.Person":"field@settings":e -> "Changelog.Person.Settings":"header@schema_module":w [dir=none] "Changelog.Podcast":"field@id":e -> "Changelog.EpisodeRequest":"field@podcast_id":w "Changelog.Podcast":"field@id":e -> "Changelog.EpisodeStat":"field@podcast_id":w "Changelog.Podcast":"field@id":e -> "Changelog.Episode":"field@podcast_id":w "Changelog.Podcast":"field@id":e -> "Changelog.PodcastHost":"field@podcast_id":w "Changelog.Podcast":"field@id":e -> "Changelog.PodcastTopic":"field@podcast_id":w "Changelog.Podcast":"field@id":e -> "Changelog.Subscription":"field@podcast_id":w "Changelog.Post":"field@id":e -> "Changelog.PostTopic":"field@post_id":w "Changelog.Sponsor":"field@id":e -> "Changelog.EpisodeSponsor":"field@sponsor_id":w "Changelog.Sponsor":"field@id":e -> "Changelog.NewsSponsorship":"field@sponsor_id":w "Changelog.Sponsor":"field@id":e -> "Changelog.SponsorRep":"field@sponsor_id":w "Changelog.Topic":"field@id":e -> "Changelog.EpisodeTopic":"field@topic_id":w "Changelog.Topic":"field@id":e -> "Changelog.NewsItemTopic":"field@topic_id":w "Changelog.Topic":"field@id":e -> "Changelog.PodcastTopic":"field@topic_id":w "Changelog.Topic":"field@id":e -> "Changelog.PostTopic":"field@topic_id":w } ================================================ FILE: examples/dot/changelog.com/No-fields.dot ================================================ strict digraph { ranksep=1.0; rankdir=LR; node [shape = none, fontname="Roboto Mono"]; "Changelog.Episode" [label= <
Changelog.Episode
episodes
>] "Changelog.EpisodeChapter" [label= <
Changelog.EpisodeChapter
>] "Changelog.EpisodeGuest" [label= <
Changelog.EpisodeGuest
episode_guests
>] "Changelog.EpisodeHost" [label= <
Changelog.EpisodeHost
episode_hosts
>] "Changelog.EpisodeRequest" [label= <
Changelog.EpisodeRequest
episode_requests
>] "Changelog.EpisodeSponsor" [label= <
Changelog.EpisodeSponsor
episode_sponsors
>] "Changelog.EpisodeStat" [label= <
Changelog.EpisodeStat
episode_stats
>] "Changelog.EpisodeTopic" [label= <
Changelog.EpisodeTopic
episode_topics
>] "Changelog.Feed" [label= <
Changelog.Feed
feeds
>] "Changelog.NewsAd" [label= <
Changelog.NewsAd
news_ads
>] "Changelog.NewsIssue" [label= <
Changelog.NewsIssue
news_issues
>] "Changelog.NewsIssueAd" [label= <
Changelog.NewsIssueAd
news_issue_ads
>] "Changelog.NewsIssueItem" [label= <
Changelog.NewsIssueItem
news_issue_items
>] "Changelog.NewsItem" [label= <
Changelog.NewsItem
news_items
>] "Changelog.NewsItemComment" [label= <
Changelog.NewsItemComment
news_item_comments
>] "Changelog.NewsItemTopic" [label= <
Changelog.NewsItemTopic
news_item_topics
>] "Changelog.NewsQueue" [label= <
Changelog.NewsQueue
news_queue
>] "Changelog.NewsSource" [label= <
Changelog.NewsSource
news_sources
>] "Changelog.NewsSponsorship" [label= <
Changelog.NewsSponsorship
news_sponsorships
>] "Changelog.Person" [label= <
Changelog.Person
people
>] "Changelog.Person.Settings" [label= <
Changelog.Person.Settings
>] "Changelog.Podcast" [label= <
Changelog.Podcast
podcasts
>] "Changelog.PodcastHost" [label= <
Changelog.PodcastHost
podcast_hosts
>] "Changelog.PodcastTopic" [label= <
Changelog.PodcastTopic
podcast_topics
>] "Changelog.Post" [label= <
Changelog.Post
posts
>] "Changelog.PostTopic" [label= <
Changelog.PostTopic
post_topics
>] "Changelog.Sponsor" [label= <
Changelog.Sponsor
sponsors
>] "Changelog.SponsorRep" [label= <
Changelog.SponsorRep
sponsor_reps
>] "Changelog.Subscription" [label= <
Changelog.Subscription
subscriptions
>] "Changelog.Topic" [label= <
Changelog.Topic
topics
>] "Ecto.Migration.SchemaMigration" [label= <
Ecto.Migration.SchemaMigration
schema_migrations
>] "Oban.Job" [label= <
Oban.Job
oban_jobs
>] "Changelog.EpisodeRequest":e -> "Changelog.Episode":w [dir=none] "Changelog.Episode":e -> "Changelog.EpisodeChapter":w "Changelog.Episode":e -> "Changelog.EpisodeGuest":w "Changelog.Episode":e -> "Changelog.EpisodeHost":w "Changelog.Episode":e -> "Changelog.EpisodeSponsor":w "Changelog.Episode":e -> "Changelog.EpisodeStat":w "Changelog.Episode":e -> "Changelog.EpisodeTopic":w "Changelog.Episode":e -> "Changelog.Subscription":w "Changelog.Episode":e -> "Changelog.EpisodeChapter":w "Changelog.NewsAd":e -> "Changelog.NewsIssueAd":w "Changelog.NewsIssue":e -> "Changelog.NewsIssueAd":w "Changelog.NewsIssue":e -> "Changelog.NewsIssueItem":w "Changelog.NewsItemComment":e -> "Changelog.NewsItemComment":w "Changelog.NewsItem":e -> "Changelog.NewsIssueItem":w "Changelog.NewsItem":e -> "Changelog.NewsItemComment":w "Changelog.NewsItem":e -> "Changelog.NewsItemTopic":w "Changelog.NewsItem":e -> "Changelog.NewsQueue":w [dir=none] "Changelog.NewsItem":e -> "Changelog.Subscription":w "Changelog.NewsSource":e -> "Changelog.NewsItem":w "Changelog.NewsSponsorship":e -> "Changelog.NewsAd":w "Changelog.Person":e -> "Changelog.EpisodeGuest":w "Changelog.Person":e -> "Changelog.EpisodeHost":w "Changelog.Person":e -> "Changelog.EpisodeRequest":w "Changelog.Person":e -> "Changelog.Feed":w "Changelog.Person":e -> "Changelog.NewsItemComment":w "Changelog.Person":e -> "Changelog.NewsItem":w "Changelog.Person":e -> "Changelog.NewsItem":w "Changelog.Person":e -> "Changelog.NewsItem":w "Changelog.Person":e -> "Changelog.PodcastHost":w "Changelog.Person":e -> "Changelog.Post":w "Changelog.Person":e -> "Changelog.Post":w "Changelog.Person":e -> "Changelog.SponsorRep":w "Changelog.Person":e -> "Changelog.Subscription":w "Changelog.Person":e -> "Changelog.Person.Settings":w [dir=none] "Changelog.Podcast":e -> "Changelog.EpisodeRequest":w "Changelog.Podcast":e -> "Changelog.EpisodeStat":w "Changelog.Podcast":e -> "Changelog.Episode":w "Changelog.Podcast":e -> "Changelog.PodcastHost":w "Changelog.Podcast":e -> "Changelog.PodcastTopic":w "Changelog.Podcast":e -> "Changelog.Subscription":w "Changelog.Post":e -> "Changelog.PostTopic":w "Changelog.Sponsor":e -> "Changelog.EpisodeSponsor":w "Changelog.Sponsor":e -> "Changelog.NewsSponsorship":w "Changelog.Sponsor":e -> "Changelog.SponsorRep":w "Changelog.Topic":e -> "Changelog.EpisodeTopic":w "Changelog.Topic":e -> "Changelog.NewsItemTopic":w "Changelog.Topic":e -> "Changelog.PodcastTopic":w "Changelog.Topic":e -> "Changelog.PostTopic":w } ================================================ FILE: examples/dot/hexpm/Contexts-as-clusters-no-fields.dot ================================================ strict digraph { ranksep=1.0; rankdir=LR; node [shape = none, fontname="Roboto Mono"]; subgraph "cluster_Ecto.Migration" { style=filled fontname="Roboto Mono" color = "#f0f8ff" label = <Ecto.Migration> "Ecto.Migration.SchemaMigration" [label= <
Ecto.Migration.SchemaMigration
schema_migrations
>] } subgraph "cluster_Hexpm.Accounts" { style=filled fontname="Roboto Mono" color = "#8deeee" label = <Hexpm.Accounts> "Hexpm.Accounts.AuditLog" [label= <
Hexpm.Accounts.AuditLog
audit_logs
>] "Hexpm.Accounts.Email" [label= <
Hexpm.Accounts.Email
emails
>] "Hexpm.Accounts.Key" [label= <
Hexpm.Accounts.Key
keys
>] "Hexpm.Accounts.Key.Use" [label= <
Hexpm.Accounts.Key.Use
>] "Hexpm.Accounts.KeyPermission" [label= <
Hexpm.Accounts.KeyPermission
>] "Hexpm.Accounts.Organization" [label= <
Hexpm.Accounts.Organization
organizations
>] "Hexpm.Accounts.OrganizationUser" [label= <
Hexpm.Accounts.OrganizationUser
organization_users
>] "Hexpm.Accounts.PasswordReset" [label= <
Hexpm.Accounts.PasswordReset
password_resets
>] "Hexpm.Accounts.RecoveryCode" [label= <
Hexpm.Accounts.RecoveryCode
>] "Hexpm.Accounts.Session" [label= <
Hexpm.Accounts.Session
sessions
>] "Hexpm.Accounts.TFA" [label= <
Hexpm.Accounts.TFA
>] "Hexpm.Accounts.User" [label= <
Hexpm.Accounts.User
users
>] "Hexpm.Accounts.UserHandles" [label= <
Hexpm.Accounts.UserHandles
>] } subgraph "cluster_Hexpm.BlockAddress" { style=filled fontname="Roboto Mono" color = "#fffafa" label = <Hexpm.BlockAddress> "Hexpm.BlockAddress.Entry" [label= <
Hexpm.BlockAddress.Entry
blocked_addresses
>] } subgraph "cluster_Hexpm.Repository" { style=filled fontname="Roboto Mono" color = "#eedfcc" label = <Hexpm.Repository> "Hexpm.Repository.Download" [label= <
Hexpm.Repository.Download
downloads
>] "Hexpm.Repository.Install" [label= <
Hexpm.Repository.Install
installs
>] "Hexpm.Repository.Package" [label= <
Hexpm.Repository.Package
packages
>] "Hexpm.Repository.PackageDependant" [label= <
Hexpm.Repository.PackageDependant
package_dependants
>] "Hexpm.Repository.PackageDownload" [label= <
Hexpm.Repository.PackageDownload
package_downloads
>] "Hexpm.Repository.PackageMetadata" [label= <
Hexpm.Repository.PackageMetadata
>] "Hexpm.Repository.PackageOwner" [label= <
Hexpm.Repository.PackageOwner
package_owners
>] "Hexpm.Repository.PackageReport" [label= <
Hexpm.Repository.PackageReport
package_reports
>] "Hexpm.Repository.PackageReportComment" [label= <
Hexpm.Repository.PackageReportComment
package_report_comments
>] "Hexpm.Repository.PackageReportRelease" [label= <
Hexpm.Repository.PackageReportRelease
package_report_releases
>] "Hexpm.Repository.Release" [label= <
Hexpm.Repository.Release
releases
>] "Hexpm.Repository.ReleaseDownload" [label= <
Hexpm.Repository.ReleaseDownload
release_downloads
>] "Hexpm.Repository.ReleaseMetadata" [label= <
Hexpm.Repository.ReleaseMetadata
>] "Hexpm.Repository.ReleaseRetirement" [label= <
Hexpm.Repository.ReleaseRetirement
>] "Hexpm.Repository.Repository" [label= <
Hexpm.Repository.Repository
repositories
>] "Hexpm.Repository.Requirement" [label= <
Hexpm.Repository.Requirement
requirements
>] } subgraph "cluster_Hexpm.ShortURLs" { style=filled fontname="Roboto Mono" color = "#b4eeb4" label = <Hexpm.ShortURLs> "Hexpm.ShortURLs.ShortURL" [label= <
Hexpm.ShortURLs.ShortURL
short_urls
>] } "Hexpm.Accounts.TFA":e -> "Hexpm.Accounts.RecoveryCode":w "Hexpm.Accounts.Key":e -> "Hexpm.Accounts.AuditLog":w "Hexpm.Accounts.Key":e -> "Hexpm.Accounts.Key.Use":w [dir=none] "Hexpm.Accounts.Key":e -> "Hexpm.Accounts.KeyPermission":w "Hexpm.Accounts.Organization":e -> "Hexpm.Accounts.AuditLog":w "Hexpm.Accounts.Organization":e -> "Hexpm.Accounts.Key":w "Hexpm.Accounts.Organization":e -> "Hexpm.Accounts.OrganizationUser":w "Hexpm.Accounts.Organization":e -> "Hexpm.Repository.Repository":w [dir=none] "Hexpm.Accounts.Organization":e -> "Hexpm.Accounts.User":w [dir=none] "Hexpm.Repository.PackageReport":e -> "Hexpm.Repository.PackageReportComment":w "Hexpm.Repository.PackageReport":e -> "Hexpm.Repository.PackageReportRelease":w "Hexpm.Repository.Package":e -> "Hexpm.Repository.Download":w "Hexpm.Repository.Package":e -> "Hexpm.Repository.PackageDependant":w "Hexpm.Repository.Package":e -> "Hexpm.Repository.PackageDownload":w "Hexpm.Repository.Package":e -> "Hexpm.Repository.PackageOwner":w "Hexpm.Repository.Package":e -> "Hexpm.Repository.PackageReport":w "Hexpm.Repository.Package":e -> "Hexpm.Repository.ReleaseDownload":w "Hexpm.Repository.Package":e -> "Hexpm.Repository.Release":w "Hexpm.Repository.Package":e -> "Hexpm.Repository.Requirement":w "Hexpm.Repository.Package":e -> "Hexpm.Repository.PackageMetadata":w [dir=none] "Hexpm.Repository.Release":e -> "Hexpm.Repository.Download":w "Hexpm.Repository.Release":e -> "Hexpm.Repository.PackageReportRelease":w "Hexpm.Repository.Release":e -> "Hexpm.Repository.ReleaseDownload":w [dir=none] "Hexpm.Repository.Release":e -> "Hexpm.Repository.Requirement":w "Hexpm.Repository.Release":e -> "Hexpm.Repository.ReleaseMetadata":w [dir=none] "Hexpm.Repository.Release":e -> "Hexpm.Repository.ReleaseRetirement":w [dir=none] "Hexpm.Repository.Repository":e -> "Hexpm.Repository.Package":w "Hexpm.Accounts.User":e -> "Hexpm.Accounts.UserHandles":w [dir=none] "Hexpm.Accounts.User":e -> "Hexpm.Accounts.AuditLog":w "Hexpm.Accounts.User":e -> "Hexpm.Accounts.Email":w "Hexpm.Accounts.User":e -> "Hexpm.Accounts.Key":w "Hexpm.Accounts.User":e -> "Hexpm.Accounts.OrganizationUser":w "Hexpm.Accounts.User":e -> "Hexpm.Repository.PackageOwner":w "Hexpm.Accounts.User":e -> "Hexpm.Repository.PackageReportComment":w "Hexpm.Accounts.User":e -> "Hexpm.Repository.PackageReport":w "Hexpm.Accounts.User":e -> "Hexpm.Accounts.PasswordReset":w "Hexpm.Accounts.User":e -> "Hexpm.Repository.Release":w "Hexpm.Accounts.User":e -> "Hexpm.Accounts.TFA":w [dir=none] } ================================================ FILE: examples/dot/hexpm/Contexts-as-clusters.dot ================================================ digraph { ranksep=1.0; rankdir=LR; node [shape = none, fontname="Roboto Mono"]; subgraph "cluster_Ecto.Migration" { style=filled fontname="Roboto Mono" color = "#f0f8ff" label = <Ecto.Migration> "Ecto.Migration.SchemaMigration" [label= <
Ecto.Migration.SchemaMigration
schema_migrations
:version :integer
:inserted_at :naive_datetime
>] } subgraph "cluster_Hexpm.Accounts" { style=filled fontname="Roboto Mono" color = "#8deeee" label = <Hexpm.Accounts> "Hexpm.Accounts.AuditLog" [label= <
Hexpm.Accounts.AuditLog
audit_logs
:id :id
:user_agent :string
:remote_ip :string
:action :string
:params :map
:user_id :id
:organization_id :id
:key_id :id
:inserted_at :utc_datetime_usec
>] "Hexpm.Accounts.Email" [label= <
Hexpm.Accounts.Email
emails
:id :id
:email :string
:verified :boolean
:primary :boolean
:public :boolean
:gravatar :boolean
:verification_key :string
:verification_expiry :utc_datetime_usec
:user_id :id
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
>] "Hexpm.Accounts.Key" [label= <
Hexpm.Accounts.Key
keys
:id :id
:name :string
:secret_first :string
:secret_second :string
:public :boolean
:revoke_at :utc_datetime_usec
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
:last_use #Ecto.Embedded<[one: Hexpm.Accounts.Key.Use]>
:user_id :id
:organization_id :id
:permissions #Ecto.Embedded<[many: Hexpm.Accounts.KeyPermission]>
>] "Hexpm.Accounts.Key.Use" [label= <
Hexpm.Accounts.Key.Use
:id :binary_id
:used_at :utc_datetime_usec
:user_agent :string
:ip :string
>] "Hexpm.Accounts.KeyPermission" [label= <
Hexpm.Accounts.KeyPermission
:id :binary_id
:domain :string
:resource :string
>] "Hexpm.Accounts.Organization" [label= <
Hexpm.Accounts.Organization
organizations
:id :id
:name :string
:billing_active :boolean
:billing_override :boolean
:trial_end :utc_datetime_usec
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
>] "Hexpm.Accounts.OrganizationUser" [label= <
Hexpm.Accounts.OrganizationUser
organization_users
:id :id
:role :string
:organization_id :id
:user_id :id
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
>] "Hexpm.Accounts.PasswordReset" [label= <
Hexpm.Accounts.PasswordReset
password_resets
:id :id
:key :string
:primary_email :string
:user_id :id
:inserted_at :utc_datetime_usec
>] "Hexpm.Accounts.RecoveryCode" [label= <
Hexpm.Accounts.RecoveryCode
:id :binary_id
:code :string
:used_at :utc_datetime_usec
>] "Hexpm.Accounts.Session" [label= <
Hexpm.Accounts.Session
sessions
:id :id
:token :binary
:data :map
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
>] "Hexpm.Accounts.TFA" [label= <
Hexpm.Accounts.TFA
:secret :string
:tfa_enabled :boolean
:app_enabled :boolean
:recovery_codes #Ecto.Embedded<[many: Hexpm.Accounts.RecoveryCode]>
>] "Hexpm.Accounts.User" [label= <
Hexpm.Accounts.User
users
:id :id
:username :string
:full_name :string
:password :string
:service :boolean
:deactivated_at :utc_datetime_usec
:role :string
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
:handles #Ecto.Embedded<[one: Hexpm.Accounts.UserHandles]>
:tfa #Ecto.Embedded<[one: Hexpm.Accounts.TFA]>
:organization_id :id
>] "Hexpm.Accounts.UserHandles" [label= <
Hexpm.Accounts.UserHandles
:id :binary_id
:twitter :string
:github :string
:elixirforum :string
:freenode :string
:slack :string
>] } subgraph "cluster_Hexpm.BlockAddress" { style=filled fontname="Roboto Mono" color = "#fffafa" label = <Hexpm.BlockAddress> "Hexpm.BlockAddress.Entry" [label= <
Hexpm.BlockAddress.Entry
blocked_addresses
:id :id
:ip :string
:comment :string
>] } subgraph "cluster_Hexpm.Repository" { style=filled fontname="Roboto Mono" color = "#eedfcc" label = <Hexpm.Repository> "Hexpm.Repository.Download" [label= <
Hexpm.Repository.Download
downloads
:id :id
:package_id :id
:release_id :id
:downloads :integer
:day :date
>] "Hexpm.Repository.Install" [label= <
Hexpm.Repository.Install
installs
:id :id
:hex :string
:elixirs {:array, :string}
>] "Hexpm.Repository.Package" [label= <
Hexpm.Repository.Package
packages
:id :id
:name :string
:docs_updated_at :utc_datetime_usec
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
:repository_id :id
:meta #Ecto.Embedded<[one: Hexpm.Repository.PackageMetadata]>
>] "Hexpm.Repository.PackageDependant" [label= <
Hexpm.Repository.PackageDependant
package_dependants
:id :id
:package_id :id
:name :string
:repo :string
>] "Hexpm.Repository.PackageDownload" [label= <
Hexpm.Repository.PackageDownload
package_downloads
:package_id :id
:view :string
:downloads :integer
>] "Hexpm.Repository.PackageMetadata" [label= <
Hexpm.Repository.PackageMetadata
:id :binary_id
:description :string
:licenses {:array, :string}
:links {:map, :string}
:maintainers {:array, :string}
:extra :map
>] "Hexpm.Repository.PackageOwner" [label= <
Hexpm.Repository.PackageOwner
package_owners
:id :id
:level :string
:package_id :id
:user_id :id
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
>] "Hexpm.Repository.PackageReport" [label= <
Hexpm.Repository.PackageReport
package_reports
:id :id
:state :string
:description :string
:author_id :id
:package_id :id
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
>] "Hexpm.Repository.PackageReportComment" [label= <
Hexpm.Repository.PackageReportComment
package_report_comments
:id :id
:text :string
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
:package_report_id :id
:author_id :id
>] "Hexpm.Repository.PackageReportRelease" [label= <
Hexpm.Repository.PackageReportRelease
package_report_releases
:id :id
:release_id :id
:package_report_id :id
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
>] "Hexpm.Repository.Release" [label= <
Hexpm.Repository.Release
releases
:id :id
:version Hexpm.Version
:inner_checksum :binary
:outer_checksum :binary
:has_docs :boolean
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
:package_id :id
:publisher_id :id
:meta #Ecto.Embedded<[one: Hexpm.Repository.ReleaseMetadata]>
:retirement #Ecto.Embedded<[one: Hexpm.Repository.ReleaseRetirement]>
>] "Hexpm.Repository.ReleaseDownload" [label= <
Hexpm.Repository.ReleaseDownload
release_downloads
:package_id :id
:release_id :id
:downloads :integer
>] "Hexpm.Repository.ReleaseMetadata" [label= <
Hexpm.Repository.ReleaseMetadata
:id :binary_id
:app :string
:build_tools {:array, :string}
:elixir :string
>] "Hexpm.Repository.ReleaseRetirement" [label= <
Hexpm.Repository.ReleaseRetirement
:id :binary_id
:reason :string
:message :string
>] "Hexpm.Repository.Repository" [label= <
Hexpm.Repository.Repository
repositories
:id :id
:name :string
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
:organization_id :id
>] "Hexpm.Repository.Requirement" [label= <
Hexpm.Repository.Requirement
requirements
:id :id
:app :string
:requirement :string
:optional :boolean
:release_id :id
:dependency_id :id
>] } subgraph "cluster_Hexpm.ShortURLs" { style=filled fontname="Roboto Mono" color = "#b4eeb4" label = <Hexpm.ShortURLs> "Hexpm.ShortURLs.ShortURL" [label= <
Hexpm.ShortURLs.ShortURL
short_urls
:id :id
:url :string
:short_code :string
:inserted_at :utc_datetime_usec
>] } "Hexpm.Accounts.TFA":"field@recovery_codes":e -> "Hexpm.Accounts.RecoveryCode":"header@schema_module":w "Hexpm.Accounts.Key":"field@id":e -> "Hexpm.Accounts.AuditLog":"field@key_id":w "Hexpm.Accounts.Key":"field@last_use":e -> "Hexpm.Accounts.Key.Use":"header@schema_module":w [dir=none] "Hexpm.Accounts.Key":"field@permissions":e -> "Hexpm.Accounts.KeyPermission":"header@schema_module":w "Hexpm.Accounts.Organization":"field@id":e -> "Hexpm.Accounts.AuditLog":"field@organization_id":w "Hexpm.Accounts.Organization":"field@id":e -> "Hexpm.Accounts.Key":"field@organization_id":w "Hexpm.Accounts.Organization":"field@id":e -> "Hexpm.Accounts.OrganizationUser":"field@organization_id":w "Hexpm.Accounts.Organization":"field@id":e -> "Hexpm.Repository.Repository":"field@organization_id":w [dir=none] "Hexpm.Accounts.Organization":"field@id":e -> "Hexpm.Accounts.User":"field@organization_id":w [dir=none] "Hexpm.Repository.PackageReport":"field@id":e -> "Hexpm.Repository.PackageReportComment":"field@package_report_id":w "Hexpm.Repository.PackageReport":"field@id":e -> "Hexpm.Repository.PackageReportRelease":"field@package_report_id":w "Hexpm.Repository.Package":"field@id":e -> "Hexpm.Repository.Download":"field@package_id":w "Hexpm.Repository.Package":"field@id":e -> "Hexpm.Repository.PackageDependant":"field@package_id":w "Hexpm.Repository.Package":"field@id":e -> "Hexpm.Repository.PackageDownload":"field@package_id":w "Hexpm.Repository.Package":"field@id":e -> "Hexpm.Repository.PackageOwner":"field@package_id":w "Hexpm.Repository.Package":"field@id":e -> "Hexpm.Repository.PackageReport":"field@package_id":w "Hexpm.Repository.Package":"field@id":e -> "Hexpm.Repository.ReleaseDownload":"field@package_id":w "Hexpm.Repository.Package":"field@id":e -> "Hexpm.Repository.Release":"field@package_id":w "Hexpm.Repository.Package":"field@id":e -> "Hexpm.Repository.Requirement":"field@dependency_id":w "Hexpm.Repository.Package":"field@meta":e -> "Hexpm.Repository.PackageMetadata":"header@schema_module":w [dir=none] "Hexpm.Repository.Release":"field@id":e -> "Hexpm.Repository.Download":"field@release_id":w "Hexpm.Repository.Release":"field@id":e -> "Hexpm.Repository.PackageReportRelease":"field@release_id":w "Hexpm.Repository.Release":"field@id":e -> "Hexpm.Repository.ReleaseDownload":"field@release_id":w [dir=none] "Hexpm.Repository.Release":"field@id":e -> "Hexpm.Repository.Requirement":"field@release_id":w "Hexpm.Repository.Release":"field@meta":e -> "Hexpm.Repository.ReleaseMetadata":"header@schema_module":w [dir=none] "Hexpm.Repository.Release":"field@retirement":e -> "Hexpm.Repository.ReleaseRetirement":"header@schema_module":w [dir=none] "Hexpm.Repository.Repository":"field@id":e -> "Hexpm.Repository.Package":"field@repository_id":w "Hexpm.Accounts.User":"field@handles":e -> "Hexpm.Accounts.UserHandles":"header@schema_module":w [dir=none] "Hexpm.Accounts.User":"field@id":e -> "Hexpm.Accounts.AuditLog":"field@user_id":w "Hexpm.Accounts.User":"field@id":e -> "Hexpm.Accounts.Email":"field@user_id":w "Hexpm.Accounts.User":"field@id":e -> "Hexpm.Accounts.Key":"field@user_id":w "Hexpm.Accounts.User":"field@id":e -> "Hexpm.Accounts.OrganizationUser":"field@user_id":w "Hexpm.Accounts.User":"field@id":e -> "Hexpm.Repository.PackageOwner":"field@user_id":w "Hexpm.Accounts.User":"field@id":e -> "Hexpm.Repository.PackageReportComment":"field@author_id":w "Hexpm.Accounts.User":"field@id":e -> "Hexpm.Repository.PackageReport":"field@author_id":w "Hexpm.Accounts.User":"field@id":e -> "Hexpm.Accounts.PasswordReset":"field@user_id":w "Hexpm.Accounts.User":"field@id":e -> "Hexpm.Repository.Release":"field@publisher_id":w "Hexpm.Accounts.User":"field@tfa":e -> "Hexpm.Accounts.TFA":"header@schema_module":w [dir=none] } ================================================ FILE: examples/dot/hexpm/Default.dot ================================================ digraph { ranksep=1.0; rankdir=LR; node [shape = none, fontname="Roboto Mono"]; "Ecto.Migration.SchemaMigration" [label= <
Ecto.Migration.SchemaMigration
schema_migrations
:version :integer
:inserted_at :naive_datetime
>] "Hexpm.Accounts.AuditLog" [label= <
Hexpm.Accounts.AuditLog
audit_logs
:id :id
:user_agent :string
:remote_ip :string
:action :string
:params :map
:user_id :id
:organization_id :id
:key_id :id
:inserted_at :utc_datetime_usec
>] "Hexpm.Accounts.Email" [label= <
Hexpm.Accounts.Email
emails
:id :id
:email :string
:verified :boolean
:primary :boolean
:public :boolean
:gravatar :boolean
:verification_key :string
:verification_expiry :utc_datetime_usec
:user_id :id
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
>] "Hexpm.Accounts.Key" [label= <
Hexpm.Accounts.Key
keys
:id :id
:name :string
:secret_first :string
:secret_second :string
:public :boolean
:revoke_at :utc_datetime_usec
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
:last_use #Ecto.Embedded<[one: Hexpm.Accounts.Key.Use]>
:user_id :id
:organization_id :id
:permissions #Ecto.Embedded<[many: Hexpm.Accounts.KeyPermission]>
>] "Hexpm.Accounts.Key.Use" [label= <
Hexpm.Accounts.Key.Use
:id :binary_id
:used_at :utc_datetime_usec
:user_agent :string
:ip :string
>] "Hexpm.Accounts.KeyPermission" [label= <
Hexpm.Accounts.KeyPermission
:id :binary_id
:domain :string
:resource :string
>] "Hexpm.Accounts.Organization" [label= <
Hexpm.Accounts.Organization
organizations
:id :id
:name :string
:billing_active :boolean
:billing_override :boolean
:trial_end :utc_datetime_usec
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
>] "Hexpm.Accounts.OrganizationUser" [label= <
Hexpm.Accounts.OrganizationUser
organization_users
:id :id
:role :string
:organization_id :id
:user_id :id
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
>] "Hexpm.Accounts.PasswordReset" [label= <
Hexpm.Accounts.PasswordReset
password_resets
:id :id
:key :string
:primary_email :string
:user_id :id
:inserted_at :utc_datetime_usec
>] "Hexpm.Accounts.RecoveryCode" [label= <
Hexpm.Accounts.RecoveryCode
:id :binary_id
:code :string
:used_at :utc_datetime_usec
>] "Hexpm.Accounts.Session" [label= <
Hexpm.Accounts.Session
sessions
:id :id
:token :binary
:data :map
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
>] "Hexpm.Accounts.TFA" [label= <
Hexpm.Accounts.TFA
:secret :string
:tfa_enabled :boolean
:app_enabled :boolean
:recovery_codes #Ecto.Embedded<[many: Hexpm.Accounts.RecoveryCode]>
>] "Hexpm.Accounts.User" [label= <
Hexpm.Accounts.User
users
:id :id
:username :string
:full_name :string
:password :string
:service :boolean
:deactivated_at :utc_datetime_usec
:role :string
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
:handles #Ecto.Embedded<[one: Hexpm.Accounts.UserHandles]>
:tfa #Ecto.Embedded<[one: Hexpm.Accounts.TFA]>
:organization_id :id
>] "Hexpm.Accounts.UserHandles" [label= <
Hexpm.Accounts.UserHandles
:id :binary_id
:twitter :string
:github :string
:elixirforum :string
:freenode :string
:slack :string
>] "Hexpm.BlockAddress.Entry" [label= <
Hexpm.BlockAddress.Entry
blocked_addresses
:id :id
:ip :string
:comment :string
>] "Hexpm.Repository.Download" [label= <
Hexpm.Repository.Download
downloads
:id :id
:package_id :id
:release_id :id
:downloads :integer
:day :date
>] "Hexpm.Repository.Install" [label= <
Hexpm.Repository.Install
installs
:id :id
:hex :string
:elixirs {:array, :string}
>] "Hexpm.Repository.Package" [label= <
Hexpm.Repository.Package
packages
:id :id
:name :string
:docs_updated_at :utc_datetime_usec
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
:repository_id :id
:meta #Ecto.Embedded<[one: Hexpm.Repository.PackageMetadata]>
>] "Hexpm.Repository.PackageDependant" [label= <
Hexpm.Repository.PackageDependant
package_dependants
:id :id
:package_id :id
:name :string
:repo :string
>] "Hexpm.Repository.PackageDownload" [label= <
Hexpm.Repository.PackageDownload
package_downloads
:package_id :id
:view :string
:downloads :integer
>] "Hexpm.Repository.PackageMetadata" [label= <
Hexpm.Repository.PackageMetadata
:id :binary_id
:description :string
:licenses {:array, :string}
:links {:map, :string}
:maintainers {:array, :string}
:extra :map
>] "Hexpm.Repository.PackageOwner" [label= <
Hexpm.Repository.PackageOwner
package_owners
:id :id
:level :string
:package_id :id
:user_id :id
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
>] "Hexpm.Repository.PackageReport" [label= <
Hexpm.Repository.PackageReport
package_reports
:id :id
:state :string
:description :string
:author_id :id
:package_id :id
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
>] "Hexpm.Repository.PackageReportComment" [label= <
Hexpm.Repository.PackageReportComment
package_report_comments
:id :id
:text :string
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
:package_report_id :id
:author_id :id
>] "Hexpm.Repository.PackageReportRelease" [label= <
Hexpm.Repository.PackageReportRelease
package_report_releases
:id :id
:release_id :id
:package_report_id :id
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
>] "Hexpm.Repository.Release" [label= <
Hexpm.Repository.Release
releases
:id :id
:version Hexpm.Version
:inner_checksum :binary
:outer_checksum :binary
:has_docs :boolean
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
:package_id :id
:publisher_id :id
:meta #Ecto.Embedded<[one: Hexpm.Repository.ReleaseMetadata]>
:retirement #Ecto.Embedded<[one: Hexpm.Repository.ReleaseRetirement]>
>] "Hexpm.Repository.ReleaseDownload" [label= <
Hexpm.Repository.ReleaseDownload
release_downloads
:package_id :id
:release_id :id
:downloads :integer
>] "Hexpm.Repository.ReleaseMetadata" [label= <
Hexpm.Repository.ReleaseMetadata
:id :binary_id
:app :string
:build_tools {:array, :string}
:elixir :string
>] "Hexpm.Repository.ReleaseRetirement" [label= <
Hexpm.Repository.ReleaseRetirement
:id :binary_id
:reason :string
:message :string
>] "Hexpm.Repository.Repository" [label= <
Hexpm.Repository.Repository
repositories
:id :id
:name :string
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
:organization_id :id
>] "Hexpm.Repository.Requirement" [label= <
Hexpm.Repository.Requirement
requirements
:id :id
:app :string
:requirement :string
:optional :boolean
:release_id :id
:dependency_id :id
>] "Hexpm.ShortURLs.ShortURL" [label= <
Hexpm.ShortURLs.ShortURL
short_urls
:id :id
:url :string
:short_code :string
:inserted_at :utc_datetime_usec
>] "Hexpm.Accounts.TFA":"field@recovery_codes":e -> "Hexpm.Accounts.RecoveryCode":"header@schema_module":w "Hexpm.Accounts.Key":"field@id":e -> "Hexpm.Accounts.AuditLog":"field@key_id":w "Hexpm.Accounts.Key":"field@last_use":e -> "Hexpm.Accounts.Key.Use":"header@schema_module":w [dir=none] "Hexpm.Accounts.Key":"field@permissions":e -> "Hexpm.Accounts.KeyPermission":"header@schema_module":w "Hexpm.Accounts.Organization":"field@id":e -> "Hexpm.Accounts.AuditLog":"field@organization_id":w "Hexpm.Accounts.Organization":"field@id":e -> "Hexpm.Accounts.Key":"field@organization_id":w "Hexpm.Accounts.Organization":"field@id":e -> "Hexpm.Accounts.OrganizationUser":"field@organization_id":w "Hexpm.Accounts.Organization":"field@id":e -> "Hexpm.Repository.Repository":"field@organization_id":w [dir=none] "Hexpm.Accounts.Organization":"field@id":e -> "Hexpm.Accounts.User":"field@organization_id":w [dir=none] "Hexpm.Repository.PackageReport":"field@id":e -> "Hexpm.Repository.PackageReportComment":"field@package_report_id":w "Hexpm.Repository.PackageReport":"field@id":e -> "Hexpm.Repository.PackageReportRelease":"field@package_report_id":w "Hexpm.Repository.Package":"field@id":e -> "Hexpm.Repository.Download":"field@package_id":w "Hexpm.Repository.Package":"field@id":e -> "Hexpm.Repository.PackageDependant":"field@package_id":w "Hexpm.Repository.Package":"field@id":e -> "Hexpm.Repository.PackageDownload":"field@package_id":w "Hexpm.Repository.Package":"field@id":e -> "Hexpm.Repository.PackageOwner":"field@package_id":w "Hexpm.Repository.Package":"field@id":e -> "Hexpm.Repository.PackageReport":"field@package_id":w "Hexpm.Repository.Package":"field@id":e -> "Hexpm.Repository.ReleaseDownload":"field@package_id":w "Hexpm.Repository.Package":"field@id":e -> "Hexpm.Repository.Release":"field@package_id":w "Hexpm.Repository.Package":"field@id":e -> "Hexpm.Repository.Requirement":"field@dependency_id":w "Hexpm.Repository.Package":"field@meta":e -> "Hexpm.Repository.PackageMetadata":"header@schema_module":w [dir=none] "Hexpm.Repository.Release":"field@id":e -> "Hexpm.Repository.Download":"field@release_id":w "Hexpm.Repository.Release":"field@id":e -> "Hexpm.Repository.PackageReportRelease":"field@release_id":w "Hexpm.Repository.Release":"field@id":e -> "Hexpm.Repository.ReleaseDownload":"field@release_id":w [dir=none] "Hexpm.Repository.Release":"field@id":e -> "Hexpm.Repository.Requirement":"field@release_id":w "Hexpm.Repository.Release":"field@meta":e -> "Hexpm.Repository.ReleaseMetadata":"header@schema_module":w [dir=none] "Hexpm.Repository.Release":"field@retirement":e -> "Hexpm.Repository.ReleaseRetirement":"header@schema_module":w [dir=none] "Hexpm.Repository.Repository":"field@id":e -> "Hexpm.Repository.Package":"field@repository_id":w "Hexpm.Accounts.User":"field@handles":e -> "Hexpm.Accounts.UserHandles":"header@schema_module":w [dir=none] "Hexpm.Accounts.User":"field@id":e -> "Hexpm.Accounts.AuditLog":"field@user_id":w "Hexpm.Accounts.User":"field@id":e -> "Hexpm.Accounts.Email":"field@user_id":w "Hexpm.Accounts.User":"field@id":e -> "Hexpm.Accounts.Key":"field@user_id":w "Hexpm.Accounts.User":"field@id":e -> "Hexpm.Accounts.OrganizationUser":"field@user_id":w "Hexpm.Accounts.User":"field@id":e -> "Hexpm.Repository.PackageOwner":"field@user_id":w "Hexpm.Accounts.User":"field@id":e -> "Hexpm.Repository.PackageReportComment":"field@author_id":w "Hexpm.Accounts.User":"field@id":e -> "Hexpm.Repository.PackageReport":"field@author_id":w "Hexpm.Accounts.User":"field@id":e -> "Hexpm.Accounts.PasswordReset":"field@user_id":w "Hexpm.Accounts.User":"field@id":e -> "Hexpm.Repository.Release":"field@publisher_id":w "Hexpm.Accounts.User":"field@tfa":e -> "Hexpm.Accounts.TFA":"header@schema_module":w [dir=none] } ================================================ FILE: examples/dot/hexpm/No-fields.dot ================================================ strict digraph { ranksep=1.0; rankdir=LR; node [shape = none, fontname="Roboto Mono"]; "Ecto.Migration.SchemaMigration" [label= <
Ecto.Migration.SchemaMigration
schema_migrations
>] "Hexpm.Accounts.AuditLog" [label= <
Hexpm.Accounts.AuditLog
audit_logs
>] "Hexpm.Accounts.Email" [label= <
Hexpm.Accounts.Email
emails
>] "Hexpm.Accounts.Key" [label= <
Hexpm.Accounts.Key
keys
>] "Hexpm.Accounts.Key.Use" [label= <
Hexpm.Accounts.Key.Use
>] "Hexpm.Accounts.KeyPermission" [label= <
Hexpm.Accounts.KeyPermission
>] "Hexpm.Accounts.Organization" [label= <
Hexpm.Accounts.Organization
organizations
>] "Hexpm.Accounts.OrganizationUser" [label= <
Hexpm.Accounts.OrganizationUser
organization_users
>] "Hexpm.Accounts.PasswordReset" [label= <
Hexpm.Accounts.PasswordReset
password_resets
>] "Hexpm.Accounts.RecoveryCode" [label= <
Hexpm.Accounts.RecoveryCode
>] "Hexpm.Accounts.Session" [label= <
Hexpm.Accounts.Session
sessions
>] "Hexpm.Accounts.TFA" [label= <
Hexpm.Accounts.TFA
>] "Hexpm.Accounts.User" [label= <
Hexpm.Accounts.User
users
>] "Hexpm.Accounts.UserHandles" [label= <
Hexpm.Accounts.UserHandles
>] "Hexpm.BlockAddress.Entry" [label= <
Hexpm.BlockAddress.Entry
blocked_addresses
>] "Hexpm.Repository.Download" [label= <
Hexpm.Repository.Download
downloads
>] "Hexpm.Repository.Install" [label= <
Hexpm.Repository.Install
installs
>] "Hexpm.Repository.Package" [label= <
Hexpm.Repository.Package
packages
>] "Hexpm.Repository.PackageDependant" [label= <
Hexpm.Repository.PackageDependant
package_dependants
>] "Hexpm.Repository.PackageDownload" [label= <
Hexpm.Repository.PackageDownload
package_downloads
>] "Hexpm.Repository.PackageMetadata" [label= <
Hexpm.Repository.PackageMetadata
>] "Hexpm.Repository.PackageOwner" [label= <
Hexpm.Repository.PackageOwner
package_owners
>] "Hexpm.Repository.PackageReport" [label= <
Hexpm.Repository.PackageReport
package_reports
>] "Hexpm.Repository.PackageReportComment" [label= <
Hexpm.Repository.PackageReportComment
package_report_comments
>] "Hexpm.Repository.PackageReportRelease" [label= <
Hexpm.Repository.PackageReportRelease
package_report_releases
>] "Hexpm.Repository.Release" [label= <
Hexpm.Repository.Release
releases
>] "Hexpm.Repository.ReleaseDownload" [label= <
Hexpm.Repository.ReleaseDownload
release_downloads
>] "Hexpm.Repository.ReleaseMetadata" [label= <
Hexpm.Repository.ReleaseMetadata
>] "Hexpm.Repository.ReleaseRetirement" [label= <
Hexpm.Repository.ReleaseRetirement
>] "Hexpm.Repository.Repository" [label= <
Hexpm.Repository.Repository
repositories
>] "Hexpm.Repository.Requirement" [label= <
Hexpm.Repository.Requirement
requirements
>] "Hexpm.ShortURLs.ShortURL" [label= <
Hexpm.ShortURLs.ShortURL
short_urls
>] "Hexpm.Accounts.TFA":e -> "Hexpm.Accounts.RecoveryCode":w "Hexpm.Accounts.Key":e -> "Hexpm.Accounts.AuditLog":w "Hexpm.Accounts.Key":e -> "Hexpm.Accounts.Key.Use":w [dir=none] "Hexpm.Accounts.Key":e -> "Hexpm.Accounts.KeyPermission":w "Hexpm.Accounts.Organization":e -> "Hexpm.Accounts.AuditLog":w "Hexpm.Accounts.Organization":e -> "Hexpm.Accounts.Key":w "Hexpm.Accounts.Organization":e -> "Hexpm.Accounts.OrganizationUser":w "Hexpm.Accounts.Organization":e -> "Hexpm.Repository.Repository":w [dir=none] "Hexpm.Accounts.Organization":e -> "Hexpm.Accounts.User":w [dir=none] "Hexpm.Repository.PackageReport":e -> "Hexpm.Repository.PackageReportComment":w "Hexpm.Repository.PackageReport":e -> "Hexpm.Repository.PackageReportRelease":w "Hexpm.Repository.Package":e -> "Hexpm.Repository.Download":w "Hexpm.Repository.Package":e -> "Hexpm.Repository.PackageDependant":w "Hexpm.Repository.Package":e -> "Hexpm.Repository.PackageDownload":w "Hexpm.Repository.Package":e -> "Hexpm.Repository.PackageOwner":w "Hexpm.Repository.Package":e -> "Hexpm.Repository.PackageReport":w "Hexpm.Repository.Package":e -> "Hexpm.Repository.ReleaseDownload":w "Hexpm.Repository.Package":e -> "Hexpm.Repository.Release":w "Hexpm.Repository.Package":e -> "Hexpm.Repository.Requirement":w "Hexpm.Repository.Package":e -> "Hexpm.Repository.PackageMetadata":w [dir=none] "Hexpm.Repository.Release":e -> "Hexpm.Repository.Download":w "Hexpm.Repository.Release":e -> "Hexpm.Repository.PackageReportRelease":w "Hexpm.Repository.Release":e -> "Hexpm.Repository.ReleaseDownload":w [dir=none] "Hexpm.Repository.Release":e -> "Hexpm.Repository.Requirement":w "Hexpm.Repository.Release":e -> "Hexpm.Repository.ReleaseMetadata":w [dir=none] "Hexpm.Repository.Release":e -> "Hexpm.Repository.ReleaseRetirement":w [dir=none] "Hexpm.Repository.Repository":e -> "Hexpm.Repository.Package":w "Hexpm.Accounts.User":e -> "Hexpm.Accounts.UserHandles":w [dir=none] "Hexpm.Accounts.User":e -> "Hexpm.Accounts.AuditLog":w "Hexpm.Accounts.User":e -> "Hexpm.Accounts.Email":w "Hexpm.Accounts.User":e -> "Hexpm.Accounts.Key":w "Hexpm.Accounts.User":e -> "Hexpm.Accounts.OrganizationUser":w "Hexpm.Accounts.User":e -> "Hexpm.Repository.PackageOwner":w "Hexpm.Accounts.User":e -> "Hexpm.Repository.PackageReportComment":w "Hexpm.Accounts.User":e -> "Hexpm.Repository.PackageReport":w "Hexpm.Accounts.User":e -> "Hexpm.Accounts.PasswordReset":w "Hexpm.Accounts.User":e -> "Hexpm.Repository.Release":w "Hexpm.Accounts.User":e -> "Hexpm.Accounts.TFA":w [dir=none] } ================================================ FILE: examples/dot/hexpm/Only-embedded-schemas.dot ================================================ digraph { ranksep=1.0; rankdir=LR; node [shape = none, fontname="Roboto Mono"]; "Hexpm.Accounts.Key.Use" [label= <
Hexpm.Accounts.Key.Use
:id :binary_id
:used_at :utc_datetime_usec
:user_agent :string
:ip :string
>] "Hexpm.Accounts.KeyPermission" [label= <
Hexpm.Accounts.KeyPermission
:id :binary_id
:domain :string
:resource :string
>] "Hexpm.Accounts.RecoveryCode" [label= <
Hexpm.Accounts.RecoveryCode
:id :binary_id
:code :string
:used_at :utc_datetime_usec
>] "Hexpm.Accounts.TFA" [label= <
Hexpm.Accounts.TFA
:secret :string
:tfa_enabled :boolean
:app_enabled :boolean
:recovery_codes #Ecto.Embedded<[many: Hexpm.Accounts.RecoveryCode]>
>] "Hexpm.Accounts.UserHandles" [label= <
Hexpm.Accounts.UserHandles
:id :binary_id
:twitter :string
:github :string
:elixirforum :string
:freenode :string
:slack :string
>] "Hexpm.Repository.PackageMetadata" [label= <
Hexpm.Repository.PackageMetadata
:id :binary_id
:description :string
:licenses {:array, :string}
:links {:map, :string}
:maintainers {:array, :string}
:extra :map
>] "Hexpm.Repository.ReleaseMetadata" [label= <
Hexpm.Repository.ReleaseMetadata
:id :binary_id
:app :string
:build_tools {:array, :string}
:elixir :string
>] "Hexpm.Repository.ReleaseRetirement" [label= <
Hexpm.Repository.ReleaseRetirement
:id :binary_id
:reason :string
:message :string
>] "Hexpm.Accounts.TFA":"field@recovery_codes":e -> "Hexpm.Accounts.RecoveryCode":"header@schema_module":w } ================================================ FILE: examples/dot/hexpm/Only-selected-cluster-Accounts-context.dot ================================================ digraph { ranksep=1.0; rankdir=LR; node [shape = none, fontname="Roboto Mono"]; subgraph "cluster_Hexpm.Accounts" { style=filled fontname="Roboto Mono" color = "#8deeee" label = <Hexpm.Accounts> "Hexpm.Accounts.AuditLog" [label= <
Hexpm.Accounts.AuditLog
audit_logs
:id :id
:user_agent :string
:remote_ip :string
:action :string
:params :map
:user_id :id
:organization_id :id
:key_id :id
:inserted_at :utc_datetime_usec
>] "Hexpm.Accounts.Email" [label= <
Hexpm.Accounts.Email
emails
:id :id
:email :string
:verified :boolean
:primary :boolean
:public :boolean
:gravatar :boolean
:verification_key :string
:verification_expiry :utc_datetime_usec
:user_id :id
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
>] "Hexpm.Accounts.Key" [label= <
Hexpm.Accounts.Key
keys
:id :id
:name :string
:secret_first :string
:secret_second :string
:public :boolean
:revoke_at :utc_datetime_usec
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
:last_use #Ecto.Embedded<[one: Hexpm.Accounts.Key.Use]>
:user_id :id
:organization_id :id
:permissions #Ecto.Embedded<[many: Hexpm.Accounts.KeyPermission]>
>] "Hexpm.Accounts.Key.Use" [label= <
Hexpm.Accounts.Key.Use
:id :binary_id
:used_at :utc_datetime_usec
:user_agent :string
:ip :string
>] "Hexpm.Accounts.KeyPermission" [label= <
Hexpm.Accounts.KeyPermission
:id :binary_id
:domain :string
:resource :string
>] "Hexpm.Accounts.Organization" [label= <
Hexpm.Accounts.Organization
organizations
:id :id
:name :string
:billing_active :boolean
:billing_override :boolean
:trial_end :utc_datetime_usec
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
>] "Hexpm.Accounts.OrganizationUser" [label= <
Hexpm.Accounts.OrganizationUser
organization_users
:id :id
:role :string
:organization_id :id
:user_id :id
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
>] "Hexpm.Accounts.PasswordReset" [label= <
Hexpm.Accounts.PasswordReset
password_resets
:id :id
:key :string
:primary_email :string
:user_id :id
:inserted_at :utc_datetime_usec
>] "Hexpm.Accounts.RecoveryCode" [label= <
Hexpm.Accounts.RecoveryCode
:id :binary_id
:code :string
:used_at :utc_datetime_usec
>] "Hexpm.Accounts.Session" [label= <
Hexpm.Accounts.Session
sessions
:id :id
:token :binary
:data :map
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
>] "Hexpm.Accounts.TFA" [label= <
Hexpm.Accounts.TFA
:secret :string
:tfa_enabled :boolean
:app_enabled :boolean
:recovery_codes #Ecto.Embedded<[many: Hexpm.Accounts.RecoveryCode]>
>] "Hexpm.Accounts.User" [label= <
Hexpm.Accounts.User
users
:id :id
:username :string
:full_name :string
:password :string
:service :boolean
:deactivated_at :utc_datetime_usec
:role :string
:inserted_at :utc_datetime_usec
:updated_at :utc_datetime_usec
:handles #Ecto.Embedded<[one: Hexpm.Accounts.UserHandles]>
:tfa #Ecto.Embedded<[one: Hexpm.Accounts.TFA]>
:organization_id :id
>] "Hexpm.Accounts.UserHandles" [label= <
Hexpm.Accounts.UserHandles
:id :binary_id
:twitter :string
:github :string
:elixirforum :string
:freenode :string
:slack :string
>] } "Hexpm.Accounts.TFA":"field@recovery_codes":e -> "Hexpm.Accounts.RecoveryCode":"header@schema_module":w "Hexpm.Accounts.Key":"field@id":e -> "Hexpm.Accounts.AuditLog":"field@key_id":w "Hexpm.Accounts.Key":"field@last_use":e -> "Hexpm.Accounts.Key.Use":"header@schema_module":w [dir=none] "Hexpm.Accounts.Key":"field@permissions":e -> "Hexpm.Accounts.KeyPermission":"header@schema_module":w "Hexpm.Accounts.Organization":"field@id":e -> "Hexpm.Accounts.AuditLog":"field@organization_id":w "Hexpm.Accounts.Organization":"field@id":e -> "Hexpm.Accounts.Key":"field@organization_id":w "Hexpm.Accounts.Organization":"field@id":e -> "Hexpm.Accounts.OrganizationUser":"field@organization_id":w "Hexpm.Accounts.Organization":"field@id":e -> "Hexpm.Accounts.User":"field@organization_id":w [dir=none] "Hexpm.Accounts.User":"field@handles":e -> "Hexpm.Accounts.UserHandles":"header@schema_module":w [dir=none] "Hexpm.Accounts.User":"field@id":e -> "Hexpm.Accounts.AuditLog":"field@user_id":w "Hexpm.Accounts.User":"field@id":e -> "Hexpm.Accounts.Email":"field@user_id":w "Hexpm.Accounts.User":"field@id":e -> "Hexpm.Accounts.Key":"field@user_id":w "Hexpm.Accounts.User":"field@id":e -> "Hexpm.Accounts.OrganizationUser":"field@user_id":w "Hexpm.Accounts.User":"field@id":e -> "Hexpm.Accounts.PasswordReset":"field@user_id":w "Hexpm.Accounts.User":"field@tfa":e -> "Hexpm.Accounts.TFA":"header@schema_module":w [dir=none] } ================================================ FILE: examples/dot/plausible-analytics/Contexts-as-clusters-no-fields.dot ================================================ strict digraph { ranksep=1.0; rankdir=LR; node [shape = none, fontname="Roboto Mono"]; subgraph "cluster_Ecto.Migration" { style=filled fontname="Roboto Mono" color = "#f0f8ff" label = <Ecto.Migration> "Ecto.Migration.SchemaMigration" [label= <
Ecto.Migration.SchemaMigration
schema_migrations
>] } subgraph "cluster_FunWithFlags.Store" { style=filled fontname="Roboto Mono" color = "#f0ffff" label = <FunWithFlags.Store> "FunWithFlags.Store.Persistent.Ecto.Record" [label= <
FunWithFlags.Store.Persistent.Ecto.Record
fun_with_flags_toggles
>] } subgraph cluster_Oban { style=filled fontname="Roboto Mono" color = "#f0ffff" label = <Oban> "Oban.Job" [label= <
Oban.Job
oban_jobs
>] } subgraph cluster_Plausible { style=filled fontname="Roboto Mono" color = "#eee5de" label = <Plausible> "Plausible.ClickhouseEventV2" [label= <
Plausible.ClickhouseEventV2
events_v2
>] "Plausible.ClickhouseSessionV2" [label= <
Plausible.ClickhouseSessionV2
sessions_v2
>] "Plausible.Funnel" [label= <
Plausible.Funnel
funnels
>] "Plausible.Goal" [label= <
Plausible.Goal
goals
>] "Plausible.Site" [label= <
Plausible.Site
sites
>] } subgraph "cluster_Plausible.Auth" { style=filled fontname="Roboto Mono" color = "#ffefd5" label = <Plausible.Auth> "Plausible.Auth.ApiKey" [label= <
Plausible.Auth.ApiKey
api_keys
>] "Plausible.Auth.EmailActivationCode" [label= <
Plausible.Auth.EmailActivationCode
email_activation_codes
>] "Plausible.Auth.GracePeriod" [label= <
Plausible.Auth.GracePeriod
>] "Plausible.Auth.Invitation" [label= <
Plausible.Auth.Invitation
invitations
>] "Plausible.Auth.TOTP.RecoveryCode" [label= <
Plausible.Auth.TOTP.RecoveryCode
totp_recovery_codes
>] "Plausible.Auth.User" [label= <
Plausible.Auth.User
users
>] } subgraph "cluster_Plausible.Billing" { style=filled fontname="Roboto Mono" color = "#f0ffff" label = <Plausible.Billing> "Plausible.Billing.EnterprisePlan" [label= <
Plausible.Billing.EnterprisePlan
enterprise_plans
>] "Plausible.Billing.Plan" [label= <
Plausible.Billing.Plan
>] "Plausible.Billing.Subscription" [label= <
Plausible.Billing.Subscription
subscriptions
>] } subgraph "cluster_Plausible.DataMigration" { style=filled fontname="Roboto Mono" color = "#8deeee" label = <Plausible.DataMigration> "Plausible.DataMigration.NumericIDs.DomainsLookup" [label= <
Plausible.DataMigration.NumericIDs.DomainsLookup
domains_lookup
>] } subgraph "cluster_Plausible.Funnel" { style=filled fontname="Roboto Mono" color = "#fffafa" label = <Plausible.Funnel> "Plausible.Funnel.Step" [label= <
Plausible.Funnel.Step
funnel_steps
>] } subgraph "cluster_Plausible.Imported" { style=filled fontname="Roboto Mono" color = "#eedfcc" label = <Plausible.Imported> "Plausible.Imported.Browser" [label= <
Plausible.Imported.Browser
imported_browsers
>] "Plausible.Imported.Device" [label= <
Plausible.Imported.Device
imported_devices
>] "Plausible.Imported.EntryPage" [label= <
Plausible.Imported.EntryPage
imported_entry_pages
>] "Plausible.Imported.ExitPage" [label= <
Plausible.Imported.ExitPage
imported_exit_pages
>] "Plausible.Imported.Location" [label= <
Plausible.Imported.Location
imported_locations
>] "Plausible.Imported.OperatingSystem" [label= <
Plausible.Imported.OperatingSystem
imported_operating_systems
>] "Plausible.Imported.Page" [label= <
Plausible.Imported.Page
imported_pages
>] "Plausible.Imported.SiteImport" [label= <
Plausible.Imported.SiteImport
site_imports
>] "Plausible.Imported.Source" [label= <
Plausible.Imported.Source
imported_sources
>] "Plausible.Imported.Visitor" [label= <
Plausible.Imported.Visitor
imported_visitors
>] } subgraph "cluster_Plausible.Ingestion" { style=filled fontname="Roboto Mono" color = "#8deeee" label = <Plausible.Ingestion> "Plausible.Ingestion.Counters.Record" [label= <
Plausible.Ingestion.Counters.Record
ingest_counters
>] "Plausible.Ingestion.Request" [label= <
Plausible.Ingestion.Request
>] } subgraph "cluster_Plausible.Plugins" { style=filled fontname="Roboto Mono" color = "#eee5de" label = <Plausible.Plugins> "Plausible.Plugins.API.Token" [label= <
Plausible.Plugins.API.Token
plugins_api_tokens
>] } subgraph "cluster_Plausible.Shield" { style=filled fontname="Roboto Mono" color = "#8deeee" label = <Plausible.Shield> "Plausible.Shield.CountryRule" [label= <
Plausible.Shield.CountryRule
shield_rules_country
>] "Plausible.Shield.HostnameRule" [label= <
Plausible.Shield.HostnameRule
shield_rules_hostname
>] "Plausible.Shield.IPRule" [label= <
Plausible.Shield.IPRule
shield_rules_ip
>] "Plausible.Shield.PageRule" [label= <
Plausible.Shield.PageRule
shield_rules_page
>] } subgraph "cluster_Plausible.Site" { style=filled fontname="Roboto Mono" color = "#f0f8ff" label = <Plausible.Site> "Plausible.Site.GoogleAuth" [label= <
Plausible.Site.GoogleAuth
google_auth
>] "Plausible.Site.ImportedData" [label= <
Plausible.Site.ImportedData
>] "Plausible.Site.Membership" [label= <
Plausible.Site.Membership
site_memberships
>] "Plausible.Site.MonthlyReport" [label= <
Plausible.Site.MonthlyReport
monthly_reports
>] "Plausible.Site.SharedLink" [label= <
Plausible.Site.SharedLink
shared_links
>] "Plausible.Site.SpikeNotification" [label= <
Plausible.Site.SpikeNotification
spike_notifications
>] "Plausible.Site.UserPreference" [label= <
Plausible.Site.UserPreference
site_user_preferences
>] "Plausible.Site.WeeklyReport" [label= <
Plausible.Site.WeeklyReport
weekly_reports
>] } "Plausible.Funnel":e -> "Plausible.Funnel.Step":w "Plausible.Goal":e -> "Plausible.Funnel.Step":w "Plausible.Site":e -> "Plausible.Funnel":w "Plausible.Site":e -> "Plausible.Goal":w "Plausible.Site":e -> "Plausible.Site.GoogleAuth":w [dir=none] "Plausible.Site":e -> "Plausible.Auth.Invitation":w "Plausible.Site":e -> "Plausible.Site.MonthlyReport":w [dir=none] "Plausible.Site":e -> "Plausible.Plugins.API.Token":w "Plausible.Site":e -> "Plausible.Site.SharedLink":w "Plausible.Site":e -> "Plausible.Shield.CountryRule":w "Plausible.Site":e -> "Plausible.Shield.HostnameRule":w "Plausible.Site":e -> "Plausible.Shield.IPRule":w "Plausible.Site":e -> "Plausible.Shield.PageRule":w "Plausible.Site":e -> "Plausible.Imported.SiteImport":w "Plausible.Site":e -> "Plausible.Site.Membership":w [dir=none] "Plausible.Site":e -> "Plausible.Site.UserPreference":w "Plausible.Site":e -> "Plausible.Site.SpikeNotification":w [dir=none] "Plausible.Site":e -> "Plausible.Site.WeeklyReport":w [dir=none] "Plausible.Site":e -> "Plausible.Site.ImportedData":w [dir=none] "Plausible.Auth.User":e -> "Plausible.Auth.GracePeriod":w [dir=none] "Plausible.Auth.User":e -> "Plausible.Auth.ApiKey":w "Plausible.Auth.User":e -> "Plausible.Auth.EmailActivationCode":w "Plausible.Auth.User":e -> "Plausible.Billing.EnterprisePlan":w [dir=none] "Plausible.Auth.User":e -> "Plausible.Site.GoogleAuth":w [dir=none] "Plausible.Auth.User":e -> "Plausible.Auth.Invitation":w "Plausible.Auth.User":e -> "Plausible.Imported.SiteImport":w "Plausible.Auth.User":e -> "Plausible.Site.Membership":w "Plausible.Auth.User":e -> "Plausible.Site.UserPreference":w "Plausible.Auth.User":e -> "Plausible.Billing.Subscription":w [dir=none] "Plausible.Auth.User":e -> "Plausible.Auth.TOTP.RecoveryCode":w } ================================================ FILE: examples/dot/plausible-analytics/Contexts-as-clusters.dot ================================================ digraph { ranksep=1.0; rankdir=LR; node [shape = none, fontname="Roboto Mono"]; subgraph "cluster_Ecto.Migration" { style=filled fontname="Roboto Mono" color = "#f0f8ff" label = <Ecto.Migration> "Ecto.Migration.SchemaMigration" [label= <
Ecto.Migration.SchemaMigration
schema_migrations
:version :integer
:inserted_at :naive_datetime
>] } subgraph "cluster_FunWithFlags.Store" { style=filled fontname="Roboto Mono" color = "#f0ffff" label = <FunWithFlags.Store> "FunWithFlags.Store.Persistent.Ecto.Record" [label= <
FunWithFlags.Store.Persistent.Ecto.Record
fun_with_flags_toggles
:id :id
:flag_name :string
:gate_type :string
:target :string
:enabled :boolean
>] } subgraph cluster_Oban { style=filled fontname="Roboto Mono" color = "#f0ffff" label = <Oban> "Oban.Job" [label= <
Oban.Job
oban_jobs
:id :id
:state :string
:queue :string
:worker :string
:args :map
:meta :map
:tags {:array, :string}
:errors {:array, :map}
:attempt :integer
:attempted_by {:array, :string}
:max_attempts :integer
:priority :integer
:attempted_at :utc_datetime_usec
:cancelled_at :utc_datetime_usec
:completed_at :utc_datetime_usec
:discarded_at :utc_datetime_usec
:inserted_at :utc_datetime_usec
:scheduled_at :utc_datetime_usec
>] } subgraph cluster_Plausible { style=filled fontname="Roboto Mono" color = "#eee5de" label = <Plausible> "Plausible.ClickhouseEventV2" [label= <
Plausible.ClickhouseEventV2
events_v2
:name {:parameterized, Ch, {:low_cardinality, :string}}
:site_id {:parameterized, Ch, :u64}
:hostname :string
:pathname :string
:user_id {:parameterized, Ch, :u64}
:session_id {:parameterized, Ch, :u64}
:timestamp :naive_datetime
:"meta.key" {:array, :string}
:"meta.value" {:array, :string}
:revenue_source_amount {:parameterized, Ch, {:nullable, {:decimal64, 3}}}
:revenue_source_currency {:parameterized, Ch, {:fixed_string, 3}}
:revenue_reporting_amount {:parameterized, Ch, {:nullable, {:decimal64, 3}}}
:revenue_reporting_currency {:parameterized, Ch, {:fixed_string, 3}}
:referrer :string
:referrer_source :string
:utm_medium :string
:utm_source :string
:utm_campaign :string
:utm_content :string
:utm_term :string
:country_code {:parameterized, Ch, {:fixed_string, 2}}
:subdivision1_code {:parameterized, Ch, {:low_cardinality, :string}}
:subdivision2_code {:parameterized, Ch, {:low_cardinality, :string}}
:city_geoname_id {:parameterized, Ch, :u32}
:screen_size {:parameterized, Ch, {:low_cardinality, :string}}
:operating_system {:parameterized, Ch, {:low_cardinality, :string}}
:operating_system_version {:parameterized, Ch, {:low_cardinality, :string}}
:browser {:parameterized, Ch, {:low_cardinality, :string}}
:browser_version {:parameterized, Ch, {:low_cardinality, :string}}
>] "Plausible.ClickhouseSessionV2" [label= <
Plausible.ClickhouseSessionV2
sessions_v2
:hostname :string
:site_id {:parameterized, Ch, :u64}
:user_id {:parameterized, Ch, :u64}
:session_id {:parameterized, Ch, :u64}
:start :naive_datetime
:duration {:parameterized, Ch, :u32}
:is_bounce Plausible.ClickhouseSessionV2.BoolUInt8
:entry_page :string
:exit_page :string
:exit_page_hostname :string
:pageviews {:parameterized, Ch, :i32}
:events {:parameterized, Ch, :i32}
:sign {:parameterized, Ch, :i8}
:"entry_meta.key" {:array, :string}
:"entry_meta.value" {:array, :string}
:utm_medium :string
:utm_source :string
:utm_campaign :string
:utm_content :string
:utm_term :string
:referrer :string
:referrer_source :string
:country_code {:parameterized, Ch, {:low_cardinality, {:fixed_string, 2}}}
:subdivision1_code {:parameterized, Ch, {:low_cardinality, :string}}
:subdivision2_code {:parameterized, Ch, {:low_cardinality, :string}}
:city_geoname_id {:parameterized, Ch, :u32}
:screen_size {:parameterized, Ch, {:low_cardinality, :string}}
:operating_system {:parameterized, Ch, {:low_cardinality, :string}}
:operating_system_version {:parameterized, Ch, {:low_cardinality, :string}}
:browser {:parameterized, Ch, {:low_cardinality, :string}}
:browser_version {:parameterized, Ch, {:low_cardinality, :string}}
:timestamp :naive_datetime
:transferred_from :string
>] "Plausible.Funnel" [label= <
Plausible.Funnel
funnels
:id :id
:name :string
:site_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Goal" [label= <
Plausible.Goal
goals
:id :id
:event_name :string
:page_path :string
:currency #Enum<[:AED, :AFN, :ALL, :AMD, :ANG, :AOA, :ARS, :AUD, :AWG, :AZN, ...]>
:site_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Site" [label= <
Plausible.Site
sites
:id :id
:domain :string
:timezone :string
:public :boolean
:locked :boolean
:stats_start_date :date
:native_stats_start_at :naive_datetime
:allowed_event_props {:array, :string}
:conversions_enabled :boolean
:props_enabled :boolean
:funnels_enabled :boolean
:ingest_rate_limit_scale_seconds :integer
:ingest_rate_limit_threshold :integer
:domain_changed_from :string
:domain_changed_at :naive_datetime
:imported_data #Ecto.Embedded<[one: Plausible.Site.ImportedData]>
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] } subgraph "cluster_Plausible.Auth" { style=filled fontname="Roboto Mono" color = "#ffefd5" label = <Plausible.Auth> "Plausible.Auth.ApiKey" [label= <
Plausible.Auth.ApiKey
api_keys
:id :id
:name :string
:scopes {:array, :string}
:hourly_request_limit :integer
:key_hash :string
:key_prefix :string
:user_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Auth.EmailActivationCode" [label= <
Plausible.Auth.EmailActivationCode
email_activation_codes
:id :id
:code :string
:issued_at :naive_datetime
:user_id :id
>] "Plausible.Auth.GracePeriod" [label= <
Plausible.Auth.GracePeriod
:id :binary_id
:end_date :date
:is_over :boolean
:manual_lock :boolean
>] "Plausible.Auth.Invitation" [label= <
Plausible.Auth.Invitation
invitations
:id :id
:invitation_id :string
:email :string
:role #Enum<[:admin, :owner, :viewer]>
:inviter_id :id
:site_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Auth.TOTP.RecoveryCode" [label= <
Plausible.Auth.TOTP.RecoveryCode
totp_recovery_codes
:id :id
:code_digest :string
:user_id :id
:inserted_at :naive_datetime
>] "Plausible.Auth.User" [label= <
Plausible.Auth.User
users
:id :id
:email :string
:password_hash :string
:name :string
:last_seen :naive_datetime
:trial_expiry_date :date
:theme #Enum<[:dark, :light, :system]>
:email_verified :boolean
:previous_email :string
:accept_traffic_until :date
:allow_next_upgrade_override :boolean
:totp_enabled :boolean
:totp_secret Plausible.Auth.TOTP.EncryptedBinary
:totp_token :string
:totp_last_used_at :naive_datetime
:grace_period #Ecto.Embedded<[one: Plausible.Auth.GracePeriod]>
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] } subgraph "cluster_Plausible.Billing" { style=filled fontname="Roboto Mono" color = "#f0ffff" label = <Plausible.Billing> "Plausible.Billing.EnterprisePlan" [label= <
Plausible.Billing.EnterprisePlan
enterprise_plans
:id :id
:paddle_plan_id :string
:billing_interval #Enum<[:monthly, :yearly]>
:monthly_pageview_limit :integer
:site_limit :integer
:team_member_limit Plausible.Billing.Ecto.Limit
:features Plausible.Billing.Ecto.FeatureList
:hourly_api_request_limit :integer
:user_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Billing.Plan" [label= <
Plausible.Billing.Plan
:id :binary_id
:generation :integer
:kind #Enum<[:business, :growth]>
:features Plausible.Billing.Ecto.FeatureList
:monthly_pageview_limit :integer
:site_limit :integer
:team_member_limit Plausible.Billing.Ecto.Limit
:volume :string
:data_retention_in_years :integer
:monthly_cost :string
:monthly_product_id :string
:yearly_cost :string
:yearly_product_id :string
>] "Plausible.Billing.Subscription" [label= <
Plausible.Billing.Subscription
subscriptions
:id :id
:paddle_subscription_id :string
:paddle_plan_id :string
:update_url :string
:cancel_url :string
:status #Enum<[:active, :deleted, :past_due, :paused]>
:next_bill_amount :string
:next_bill_date :date
:last_bill_date :date
:currency_code :string
:user_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] } subgraph "cluster_Plausible.DataMigration" { style=filled fontname="Roboto Mono" color = "#8deeee" label = <Plausible.DataMigration> "Plausible.DataMigration.NumericIDs.DomainsLookup" [label= <
Plausible.DataMigration.NumericIDs.DomainsLookup
domains_lookup
:site_id {:parameterized, Ch, :u64}
:domain :string
>] } subgraph "cluster_Plausible.Funnel" { style=filled fontname="Roboto Mono" color = "#fffafa" label = <Plausible.Funnel> "Plausible.Funnel.Step" [label= <
Plausible.Funnel.Step
funnel_steps
:id :id
:step_order :integer
:funnel_id :id
:goal_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] } subgraph "cluster_Plausible.Imported" { style=filled fontname="Roboto Mono" color = "#eedfcc" label = <Plausible.Imported> "Plausible.Imported.Browser" [label= <
Plausible.Imported.Browser
imported_browsers
:site_id {:parameterized, Ch, :u64}
:import_id {:parameterized, Ch, :u64}
:date :date
:browser :string
:browser_version :string
:visitors {:parameterized, Ch, :u64}
:visits {:parameterized, Ch, :u64}
:visit_duration {:parameterized, Ch, :u64}
:pageviews {:parameterized, Ch, :u64}
:bounces {:parameterized, Ch, :u32}
>] "Plausible.Imported.Device" [label= <
Plausible.Imported.Device
imported_devices
:site_id {:parameterized, Ch, :u64}
:import_id {:parameterized, Ch, :u64}
:date :date
:device :string
:visitors {:parameterized, Ch, :u64}
:visits {:parameterized, Ch, :u64}
:visit_duration {:parameterized, Ch, :u64}
:pageviews {:parameterized, Ch, :u64}
:bounces {:parameterized, Ch, :u32}
>] "Plausible.Imported.EntryPage" [label= <
Plausible.Imported.EntryPage
imported_entry_pages
:site_id {:parameterized, Ch, :u64}
:import_id {:parameterized, Ch, :u64}
:date :date
:entry_page :string
:visitors {:parameterized, Ch, :u64}
:entrances {:parameterized, Ch, :u64}
:visit_duration {:parameterized, Ch, :u64}
:pageviews {:parameterized, Ch, :u64}
:bounces {:parameterized, Ch, :u32}
>] "Plausible.Imported.ExitPage" [label= <
Plausible.Imported.ExitPage
imported_exit_pages
:site_id {:parameterized, Ch, :u64}
:import_id {:parameterized, Ch, :u64}
:date :date
:exit_page :string
:exits {:parameterized, Ch, :u64}
:visitors {:parameterized, Ch, :u64}
:visit_duration {:parameterized, Ch, :u64}
:pageviews {:parameterized, Ch, :u64}
:bounces {:parameterized, Ch, :u32}
>] "Plausible.Imported.Location" [label= <
Plausible.Imported.Location
imported_locations
:site_id {:parameterized, Ch, :u64}
:import_id {:parameterized, Ch, :u64}
:date :date
:country :string
:region :string
:city {:parameterized, Ch, :u64}
:visitors {:parameterized, Ch, :u64}
:visits {:parameterized, Ch, :u64}
:visit_duration {:parameterized, Ch, :u64}
:pageviews {:parameterized, Ch, :u64}
:bounces {:parameterized, Ch, :u32}
>] "Plausible.Imported.OperatingSystem" [label= <
Plausible.Imported.OperatingSystem
imported_operating_systems
:site_id {:parameterized, Ch, :u64}
:import_id {:parameterized, Ch, :u64}
:date :date
:operating_system :string
:operating_system_version :string
:visitors {:parameterized, Ch, :u64}
:visits {:parameterized, Ch, :u64}
:visit_duration {:parameterized, Ch, :u64}
:pageviews {:parameterized, Ch, :u64}
:bounces {:parameterized, Ch, :u32}
>] "Plausible.Imported.Page" [label= <
Plausible.Imported.Page
imported_pages
:site_id {:parameterized, Ch, :u64}
:import_id {:parameterized, Ch, :u64}
:date :date
:hostname :string
:page :string
:visits {:parameterized, Ch, :u64}
:visitors {:parameterized, Ch, :u64}
:active_visitors {:parameterized, Ch, :u64}
:pageviews {:parameterized, Ch, :u64}
:exits {:parameterized, Ch, :u64}
:time_on_page {:parameterized, Ch, :u64}
>] "Plausible.Imported.SiteImport" [label= <
Plausible.Imported.SiteImport
site_imports
:id :id
:start_date :date
:end_date :date
:label :string
:source #Enum<[:csv, :google_analytics_4, :noop, :universal_analytics]>
:status #Enum<[:completed, :failed, :importing, :pending]>
:legacy :boolean
:site_id :id
:imported_by_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Imported.Source" [label= <
Plausible.Imported.Source
imported_sources
:site_id {:parameterized, Ch, :u64}
:import_id {:parameterized, Ch, :u64}
:date :date
:source :string
:referrer :string
:utm_source :string
:utm_medium :string
:utm_campaign :string
:utm_content :string
:utm_term :string
:visitors {:parameterized, Ch, :u64}
:visits {:parameterized, Ch, :u64}
:visit_duration {:parameterized, Ch, :u64}
:pageviews {:parameterized, Ch, :u64}
:bounces {:parameterized, Ch, :u32}
>] "Plausible.Imported.Visitor" [label= <
Plausible.Imported.Visitor
imported_visitors
:site_id {:parameterized, Ch, :u64}
:import_id {:parameterized, Ch, :u64}
:date :date
:visitors {:parameterized, Ch, :u64}
:pageviews {:parameterized, Ch, :u64}
:bounces {:parameterized, Ch, :u64}
:visits {:parameterized, Ch, :u64}
:visit_duration {:parameterized, Ch, :u64}
>] } subgraph "cluster_Plausible.Ingestion" { style=filled fontname="Roboto Mono" color = "#8deeee" label = <Plausible.Ingestion> "Plausible.Ingestion.Counters.Record" [label= <
Plausible.Ingestion.Counters.Record
ingest_counters
:event_timebucket :utc_datetime
:site_id {:parameterized, Ch, {:nullable, :u64}}
:domain {:parameterized, Ch, {:low_cardinality, :string}}
:metric {:parameterized, Ch, {:low_cardinality, :string}}
:value {:parameterized, Ch, :u64}
>] "Plausible.Ingestion.Request" [label= <
Plausible.Ingestion.Request
:remote_ip :string
:user_agent :string
:event_name Plausible.Ecto.EventName
:uri :map
:hostname :string
:referrer :string
:domains {:array, :string}
:ip_classification :string
:hash_mode :integer
:pathname :string
:props :map
:revenue_source :map
:query_params :map
:timestamp :naive_datetime
>] } subgraph "cluster_Plausible.Plugins" { style=filled fontname="Roboto Mono" color = "#eee5de" label = <Plausible.Plugins> "Plausible.Plugins.API.Token" [label= <
Plausible.Plugins.API.Token
plugins_api_tokens
:id :binary_id
:inserted_at :naive_datetime
:updated_at :naive_datetime
:token_hash :binary
:description :string
:hint :string
:last_used_at :naive_datetime
:site_id :id
>] } subgraph "cluster_Plausible.Shield" { style=filled fontname="Roboto Mono" color = "#8deeee" label = <Plausible.Shield> "Plausible.Shield.CountryRule" [label= <
Plausible.Shield.CountryRule
shield_rules_country
:id :binary_id
:site_id :id
:country_code :string
:action #Enum<[:allow, :deny]>
:added_by :string
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Shield.HostnameRule" [label= <
Plausible.Shield.HostnameRule
shield_rules_hostname
:id :binary_id
:site_id :id
:hostname :string
:hostname_pattern Plausible.Ecto.Types.CompiledRegex
:action #Enum<[:allow, :deny]>
:added_by :string
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Shield.IPRule" [label= <
Plausible.Shield.IPRule
shield_rules_ip
:id :binary_id
:site_id :id
:inet EctoNetwork.INET
:action #Enum<[:allow, :deny]>
:description :string
:added_by :string
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Shield.PageRule" [label= <
Plausible.Shield.PageRule
shield_rules_page
:id :binary_id
:site_id :id
:page_path :string
:page_path_pattern Plausible.Ecto.Types.CompiledRegex
:action #Enum<[:allow, :deny]>
:added_by :string
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] } subgraph "cluster_Plausible.Site" { style=filled fontname="Roboto Mono" color = "#f0f8ff" label = <Plausible.Site> "Plausible.Site.GoogleAuth" [label= <
Plausible.Site.GoogleAuth
google_auth
:id :id
:email :string
:property :string
:refresh_token :string
:access_token :string
:expires :naive_datetime
:user_id :id
:site_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Site.ImportedData" [label= <
Plausible.Site.ImportedData
:id :binary_id
:start_date :date
:end_date :date
:source :string
:status :string
>] "Plausible.Site.Membership" [label= <
Plausible.Site.Membership
site_memberships
:id :id
:role #Enum<[:admin, :owner, :viewer]>
:site_id :id
:user_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Site.MonthlyReport" [label= <
Plausible.Site.MonthlyReport
monthly_reports
:id :id
:recipients {:array, :string}
:site_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Site.SharedLink" [label= <
Plausible.Site.SharedLink
shared_links
:id :id
:site_id :id
:name :string
:slug :string
:password_hash :string
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Site.SpikeNotification" [label= <
Plausible.Site.SpikeNotification
spike_notifications
:id :id
:recipients {:array, :string}
:threshold :integer
:last_sent :naive_datetime
:site_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Site.UserPreference" [label= <
Plausible.Site.UserPreference
site_user_preferences
:id :id
:pinned_at :naive_datetime
:user_id :id
:site_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Site.WeeklyReport" [label= <
Plausible.Site.WeeklyReport
weekly_reports
:id :id
:recipients {:array, :string}
:site_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] } "Plausible.Funnel":"field@id":e -> "Plausible.Funnel.Step":"field@funnel_id":w "Plausible.Goal":"field@id":e -> "Plausible.Funnel.Step":"field@goal_id":w "Plausible.Site":"field@id":e -> "Plausible.Funnel":"field@site_id":w "Plausible.Site":"field@id":e -> "Plausible.Goal":"field@site_id":w "Plausible.Site":"field@id":e -> "Plausible.Site.GoogleAuth":"field@site_id":w [dir=none] "Plausible.Site":"field@id":e -> "Plausible.Auth.Invitation":"field@site_id":w "Plausible.Site":"field@id":e -> "Plausible.Site.MonthlyReport":"field@site_id":w [dir=none] "Plausible.Site":"field@id":e -> "Plausible.Plugins.API.Token":"field@site_id":w "Plausible.Site":"field@id":e -> "Plausible.Site.SharedLink":"field@site_id":w "Plausible.Site":"field@id":e -> "Plausible.Shield.CountryRule":"field@site_id":w "Plausible.Site":"field@id":e -> "Plausible.Shield.HostnameRule":"field@site_id":w "Plausible.Site":"field@id":e -> "Plausible.Shield.IPRule":"field@site_id":w "Plausible.Site":"field@id":e -> "Plausible.Shield.PageRule":"field@site_id":w "Plausible.Site":"field@id":e -> "Plausible.Imported.SiteImport":"field@site_id":w "Plausible.Site":"field@id":e -> "Plausible.Site.Membership":"field@site_id":w [dir=none] "Plausible.Site":"field@id":e -> "Plausible.Site.UserPreference":"field@site_id":w "Plausible.Site":"field@id":e -> "Plausible.Site.SpikeNotification":"field@site_id":w [dir=none] "Plausible.Site":"field@id":e -> "Plausible.Site.WeeklyReport":"field@site_id":w [dir=none] "Plausible.Site":"field@imported_data":e -> "Plausible.Site.ImportedData":"header@schema_module":w [dir=none] "Plausible.Auth.User":"field@grace_period":e -> "Plausible.Auth.GracePeriod":"header@schema_module":w [dir=none] "Plausible.Auth.User":"field@id":e -> "Plausible.Auth.ApiKey":"field@user_id":w "Plausible.Auth.User":"field@id":e -> "Plausible.Auth.EmailActivationCode":"field@user_id":w "Plausible.Auth.User":"field@id":e -> "Plausible.Billing.EnterprisePlan":"field@user_id":w [dir=none] "Plausible.Auth.User":"field@id":e -> "Plausible.Site.GoogleAuth":"field@user_id":w [dir=none] "Plausible.Auth.User":"field@id":e -> "Plausible.Auth.Invitation":"field@inviter_id":w "Plausible.Auth.User":"field@id":e -> "Plausible.Imported.SiteImport":"field@imported_by_id":w "Plausible.Auth.User":"field@id":e -> "Plausible.Site.Membership":"field@user_id":w "Plausible.Auth.User":"field@id":e -> "Plausible.Site.UserPreference":"field@user_id":w "Plausible.Auth.User":"field@id":e -> "Plausible.Billing.Subscription":"field@user_id":w [dir=none] "Plausible.Auth.User":"field@id":e -> "Plausible.Auth.TOTP.RecoveryCode":"field@user_id":w } ================================================ FILE: examples/dot/plausible-analytics/Default.dot ================================================ digraph { ranksep=1.0; rankdir=LR; node [shape = none, fontname="Roboto Mono"]; "Ecto.Migration.SchemaMigration" [label= <
Ecto.Migration.SchemaMigration
schema_migrations
:version :integer
:inserted_at :naive_datetime
>] "FunWithFlags.Store.Persistent.Ecto.Record" [label= <
FunWithFlags.Store.Persistent.Ecto.Record
fun_with_flags_toggles
:id :id
:flag_name :string
:gate_type :string
:target :string
:enabled :boolean
>] "Oban.Job" [label= <
Oban.Job
oban_jobs
:id :id
:state :string
:queue :string
:worker :string
:args :map
:meta :map
:tags {:array, :string}
:errors {:array, :map}
:attempt :integer
:attempted_by {:array, :string}
:max_attempts :integer
:priority :integer
:attempted_at :utc_datetime_usec
:cancelled_at :utc_datetime_usec
:completed_at :utc_datetime_usec
:discarded_at :utc_datetime_usec
:inserted_at :utc_datetime_usec
:scheduled_at :utc_datetime_usec
>] "Plausible.Auth.ApiKey" [label= <
Plausible.Auth.ApiKey
api_keys
:id :id
:name :string
:scopes {:array, :string}
:hourly_request_limit :integer
:key_hash :string
:key_prefix :string
:user_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Auth.EmailActivationCode" [label= <
Plausible.Auth.EmailActivationCode
email_activation_codes
:id :id
:code :string
:issued_at :naive_datetime
:user_id :id
>] "Plausible.Auth.GracePeriod" [label= <
Plausible.Auth.GracePeriod
:id :binary_id
:end_date :date
:is_over :boolean
:manual_lock :boolean
>] "Plausible.Auth.Invitation" [label= <
Plausible.Auth.Invitation
invitations
:id :id
:invitation_id :string
:email :string
:role #Enum<[:admin, :owner, :viewer]>
:inviter_id :id
:site_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Auth.TOTP.RecoveryCode" [label= <
Plausible.Auth.TOTP.RecoveryCode
totp_recovery_codes
:id :id
:code_digest :string
:user_id :id
:inserted_at :naive_datetime
>] "Plausible.Auth.User" [label= <
Plausible.Auth.User
users
:id :id
:email :string
:password_hash :string
:name :string
:last_seen :naive_datetime
:trial_expiry_date :date
:theme #Enum<[:dark, :light, :system]>
:email_verified :boolean
:previous_email :string
:accept_traffic_until :date
:allow_next_upgrade_override :boolean
:totp_enabled :boolean
:totp_secret Plausible.Auth.TOTP.EncryptedBinary
:totp_token :string
:totp_last_used_at :naive_datetime
:grace_period #Ecto.Embedded<[one: Plausible.Auth.GracePeriod]>
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Billing.EnterprisePlan" [label= <
Plausible.Billing.EnterprisePlan
enterprise_plans
:id :id
:paddle_plan_id :string
:billing_interval #Enum<[:monthly, :yearly]>
:monthly_pageview_limit :integer
:site_limit :integer
:team_member_limit Plausible.Billing.Ecto.Limit
:features Plausible.Billing.Ecto.FeatureList
:hourly_api_request_limit :integer
:user_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Billing.Plan" [label= <
Plausible.Billing.Plan
:id :binary_id
:generation :integer
:kind #Enum<[:business, :growth]>
:features Plausible.Billing.Ecto.FeatureList
:monthly_pageview_limit :integer
:site_limit :integer
:team_member_limit Plausible.Billing.Ecto.Limit
:volume :string
:data_retention_in_years :integer
:monthly_cost :string
:monthly_product_id :string
:yearly_cost :string
:yearly_product_id :string
>] "Plausible.Billing.Subscription" [label= <
Plausible.Billing.Subscription
subscriptions
:id :id
:paddle_subscription_id :string
:paddle_plan_id :string
:update_url :string
:cancel_url :string
:status #Enum<[:active, :deleted, :past_due, :paused]>
:next_bill_amount :string
:next_bill_date :date
:last_bill_date :date
:currency_code :string
:user_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.ClickhouseEventV2" [label= <
Plausible.ClickhouseEventV2
events_v2
:name {:parameterized, Ch, {:low_cardinality, :string}}
:site_id {:parameterized, Ch, :u64}
:hostname :string
:pathname :string
:user_id {:parameterized, Ch, :u64}
:session_id {:parameterized, Ch, :u64}
:timestamp :naive_datetime
:"meta.key" {:array, :string}
:"meta.value" {:array, :string}
:revenue_source_amount {:parameterized, Ch, {:nullable, {:decimal64, 3}}}
:revenue_source_currency {:parameterized, Ch, {:fixed_string, 3}}
:revenue_reporting_amount {:parameterized, Ch, {:nullable, {:decimal64, 3}}}
:revenue_reporting_currency {:parameterized, Ch, {:fixed_string, 3}}
:referrer :string
:referrer_source :string
:utm_medium :string
:utm_source :string
:utm_campaign :string
:utm_content :string
:utm_term :string
:country_code {:parameterized, Ch, {:fixed_string, 2}}
:subdivision1_code {:parameterized, Ch, {:low_cardinality, :string}}
:subdivision2_code {:parameterized, Ch, {:low_cardinality, :string}}
:city_geoname_id {:parameterized, Ch, :u32}
:screen_size {:parameterized, Ch, {:low_cardinality, :string}}
:operating_system {:parameterized, Ch, {:low_cardinality, :string}}
:operating_system_version {:parameterized, Ch, {:low_cardinality, :string}}
:browser {:parameterized, Ch, {:low_cardinality, :string}}
:browser_version {:parameterized, Ch, {:low_cardinality, :string}}
>] "Plausible.ClickhouseSessionV2" [label= <
Plausible.ClickhouseSessionV2
sessions_v2
:hostname :string
:site_id {:parameterized, Ch, :u64}
:user_id {:parameterized, Ch, :u64}
:session_id {:parameterized, Ch, :u64}
:start :naive_datetime
:duration {:parameterized, Ch, :u32}
:is_bounce Plausible.ClickhouseSessionV2.BoolUInt8
:entry_page :string
:exit_page :string
:exit_page_hostname :string
:pageviews {:parameterized, Ch, :i32}
:events {:parameterized, Ch, :i32}
:sign {:parameterized, Ch, :i8}
:"entry_meta.key" {:array, :string}
:"entry_meta.value" {:array, :string}
:utm_medium :string
:utm_source :string
:utm_campaign :string
:utm_content :string
:utm_term :string
:referrer :string
:referrer_source :string
:country_code {:parameterized, Ch, {:low_cardinality, {:fixed_string, 2}}}
:subdivision1_code {:parameterized, Ch, {:low_cardinality, :string}}
:subdivision2_code {:parameterized, Ch, {:low_cardinality, :string}}
:city_geoname_id {:parameterized, Ch, :u32}
:screen_size {:parameterized, Ch, {:low_cardinality, :string}}
:operating_system {:parameterized, Ch, {:low_cardinality, :string}}
:operating_system_version {:parameterized, Ch, {:low_cardinality, :string}}
:browser {:parameterized, Ch, {:low_cardinality, :string}}
:browser_version {:parameterized, Ch, {:low_cardinality, :string}}
:timestamp :naive_datetime
:transferred_from :string
>] "Plausible.DataMigration.NumericIDs.DomainsLookup" [label= <
Plausible.DataMigration.NumericIDs.DomainsLookup
domains_lookup
:site_id {:parameterized, Ch, :u64}
:domain :string
>] "Plausible.Funnel" [label= <
Plausible.Funnel
funnels
:id :id
:name :string
:site_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Funnel.Step" [label= <
Plausible.Funnel.Step
funnel_steps
:id :id
:step_order :integer
:funnel_id :id
:goal_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Goal" [label= <
Plausible.Goal
goals
:id :id
:event_name :string
:page_path :string
:currency #Enum<[:AED, :AFN, :ALL, :AMD, :ANG, :AOA, :ARS, :AUD, :AWG, :AZN, ...]>
:site_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Imported.Browser" [label= <
Plausible.Imported.Browser
imported_browsers
:site_id {:parameterized, Ch, :u64}
:import_id {:parameterized, Ch, :u64}
:date :date
:browser :string
:browser_version :string
:visitors {:parameterized, Ch, :u64}
:visits {:parameterized, Ch, :u64}
:visit_duration {:parameterized, Ch, :u64}
:pageviews {:parameterized, Ch, :u64}
:bounces {:parameterized, Ch, :u32}
>] "Plausible.Imported.Device" [label= <
Plausible.Imported.Device
imported_devices
:site_id {:parameterized, Ch, :u64}
:import_id {:parameterized, Ch, :u64}
:date :date
:device :string
:visitors {:parameterized, Ch, :u64}
:visits {:parameterized, Ch, :u64}
:visit_duration {:parameterized, Ch, :u64}
:pageviews {:parameterized, Ch, :u64}
:bounces {:parameterized, Ch, :u32}
>] "Plausible.Imported.EntryPage" [label= <
Plausible.Imported.EntryPage
imported_entry_pages
:site_id {:parameterized, Ch, :u64}
:import_id {:parameterized, Ch, :u64}
:date :date
:entry_page :string
:visitors {:parameterized, Ch, :u64}
:entrances {:parameterized, Ch, :u64}
:visit_duration {:parameterized, Ch, :u64}
:pageviews {:parameterized, Ch, :u64}
:bounces {:parameterized, Ch, :u32}
>] "Plausible.Imported.ExitPage" [label= <
Plausible.Imported.ExitPage
imported_exit_pages
:site_id {:parameterized, Ch, :u64}
:import_id {:parameterized, Ch, :u64}
:date :date
:exit_page :string
:exits {:parameterized, Ch, :u64}
:visitors {:parameterized, Ch, :u64}
:visit_duration {:parameterized, Ch, :u64}
:pageviews {:parameterized, Ch, :u64}
:bounces {:parameterized, Ch, :u32}
>] "Plausible.Imported.Location" [label= <
Plausible.Imported.Location
imported_locations
:site_id {:parameterized, Ch, :u64}
:import_id {:parameterized, Ch, :u64}
:date :date
:country :string
:region :string
:city {:parameterized, Ch, :u64}
:visitors {:parameterized, Ch, :u64}
:visits {:parameterized, Ch, :u64}
:visit_duration {:parameterized, Ch, :u64}
:pageviews {:parameterized, Ch, :u64}
:bounces {:parameterized, Ch, :u32}
>] "Plausible.Imported.OperatingSystem" [label= <
Plausible.Imported.OperatingSystem
imported_operating_systems
:site_id {:parameterized, Ch, :u64}
:import_id {:parameterized, Ch, :u64}
:date :date
:operating_system :string
:operating_system_version :string
:visitors {:parameterized, Ch, :u64}
:visits {:parameterized, Ch, :u64}
:visit_duration {:parameterized, Ch, :u64}
:pageviews {:parameterized, Ch, :u64}
:bounces {:parameterized, Ch, :u32}
>] "Plausible.Imported.Page" [label= <
Plausible.Imported.Page
imported_pages
:site_id {:parameterized, Ch, :u64}
:import_id {:parameterized, Ch, :u64}
:date :date
:hostname :string
:page :string
:visits {:parameterized, Ch, :u64}
:visitors {:parameterized, Ch, :u64}
:active_visitors {:parameterized, Ch, :u64}
:pageviews {:parameterized, Ch, :u64}
:exits {:parameterized, Ch, :u64}
:time_on_page {:parameterized, Ch, :u64}
>] "Plausible.Imported.SiteImport" [label= <
Plausible.Imported.SiteImport
site_imports
:id :id
:start_date :date
:end_date :date
:label :string
:source #Enum<[:csv, :google_analytics_4, :noop, :universal_analytics]>
:status #Enum<[:completed, :failed, :importing, :pending]>
:legacy :boolean
:site_id :id
:imported_by_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Imported.Source" [label= <
Plausible.Imported.Source
imported_sources
:site_id {:parameterized, Ch, :u64}
:import_id {:parameterized, Ch, :u64}
:date :date
:source :string
:referrer :string
:utm_source :string
:utm_medium :string
:utm_campaign :string
:utm_content :string
:utm_term :string
:visitors {:parameterized, Ch, :u64}
:visits {:parameterized, Ch, :u64}
:visit_duration {:parameterized, Ch, :u64}
:pageviews {:parameterized, Ch, :u64}
:bounces {:parameterized, Ch, :u32}
>] "Plausible.Imported.Visitor" [label= <
Plausible.Imported.Visitor
imported_visitors
:site_id {:parameterized, Ch, :u64}
:import_id {:parameterized, Ch, :u64}
:date :date
:visitors {:parameterized, Ch, :u64}
:pageviews {:parameterized, Ch, :u64}
:bounces {:parameterized, Ch, :u64}
:visits {:parameterized, Ch, :u64}
:visit_duration {:parameterized, Ch, :u64}
>] "Plausible.Ingestion.Counters.Record" [label= <
Plausible.Ingestion.Counters.Record
ingest_counters
:event_timebucket :utc_datetime
:site_id {:parameterized, Ch, {:nullable, :u64}}
:domain {:parameterized, Ch, {:low_cardinality, :string}}
:metric {:parameterized, Ch, {:low_cardinality, :string}}
:value {:parameterized, Ch, :u64}
>] "Plausible.Ingestion.Request" [label= <
Plausible.Ingestion.Request
:remote_ip :string
:user_agent :string
:event_name Plausible.Ecto.EventName
:uri :map
:hostname :string
:referrer :string
:domains {:array, :string}
:ip_classification :string
:hash_mode :integer
:pathname :string
:props :map
:revenue_source :map
:query_params :map
:timestamp :naive_datetime
>] "Plausible.Plugins.API.Token" [label= <
Plausible.Plugins.API.Token
plugins_api_tokens
:id :binary_id
:inserted_at :naive_datetime
:updated_at :naive_datetime
:token_hash :binary
:description :string
:hint :string
:last_used_at :naive_datetime
:site_id :id
>] "Plausible.Shield.CountryRule" [label= <
Plausible.Shield.CountryRule
shield_rules_country
:id :binary_id
:site_id :id
:country_code :string
:action #Enum<[:allow, :deny]>
:added_by :string
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Shield.HostnameRule" [label= <
Plausible.Shield.HostnameRule
shield_rules_hostname
:id :binary_id
:site_id :id
:hostname :string
:hostname_pattern Plausible.Ecto.Types.CompiledRegex
:action #Enum<[:allow, :deny]>
:added_by :string
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Shield.IPRule" [label= <
Plausible.Shield.IPRule
shield_rules_ip
:id :binary_id
:site_id :id
:inet EctoNetwork.INET
:action #Enum<[:allow, :deny]>
:description :string
:added_by :string
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Shield.PageRule" [label= <
Plausible.Shield.PageRule
shield_rules_page
:id :binary_id
:site_id :id
:page_path :string
:page_path_pattern Plausible.Ecto.Types.CompiledRegex
:action #Enum<[:allow, :deny]>
:added_by :string
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Site" [label= <
Plausible.Site
sites
:id :id
:domain :string
:timezone :string
:public :boolean
:locked :boolean
:stats_start_date :date
:native_stats_start_at :naive_datetime
:allowed_event_props {:array, :string}
:conversions_enabled :boolean
:props_enabled :boolean
:funnels_enabled :boolean
:ingest_rate_limit_scale_seconds :integer
:ingest_rate_limit_threshold :integer
:domain_changed_from :string
:domain_changed_at :naive_datetime
:imported_data #Ecto.Embedded<[one: Plausible.Site.ImportedData]>
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Site.GoogleAuth" [label= <
Plausible.Site.GoogleAuth
google_auth
:id :id
:email :string
:property :string
:refresh_token :string
:access_token :string
:expires :naive_datetime
:user_id :id
:site_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Site.ImportedData" [label= <
Plausible.Site.ImportedData
:id :binary_id
:start_date :date
:end_date :date
:source :string
:status :string
>] "Plausible.Site.Membership" [label= <
Plausible.Site.Membership
site_memberships
:id :id
:role #Enum<[:admin, :owner, :viewer]>
:site_id :id
:user_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Site.MonthlyReport" [label= <
Plausible.Site.MonthlyReport
monthly_reports
:id :id
:recipients {:array, :string}
:site_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Site.SharedLink" [label= <
Plausible.Site.SharedLink
shared_links
:id :id
:site_id :id
:name :string
:slug :string
:password_hash :string
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Site.SpikeNotification" [label= <
Plausible.Site.SpikeNotification
spike_notifications
:id :id
:recipients {:array, :string}
:threshold :integer
:last_sent :naive_datetime
:site_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Site.UserPreference" [label= <
Plausible.Site.UserPreference
site_user_preferences
:id :id
:pinned_at :naive_datetime
:user_id :id
:site_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Site.WeeklyReport" [label= <
Plausible.Site.WeeklyReport
weekly_reports
:id :id
:recipients {:array, :string}
:site_id :id
:inserted_at :naive_datetime
:updated_at :naive_datetime
>] "Plausible.Funnel":"field@id":e -> "Plausible.Funnel.Step":"field@funnel_id":w "Plausible.Goal":"field@id":e -> "Plausible.Funnel.Step":"field@goal_id":w "Plausible.Site":"field@id":e -> "Plausible.Funnel":"field@site_id":w "Plausible.Site":"field@id":e -> "Plausible.Goal":"field@site_id":w "Plausible.Site":"field@id":e -> "Plausible.Site.GoogleAuth":"field@site_id":w [dir=none] "Plausible.Site":"field@id":e -> "Plausible.Auth.Invitation":"field@site_id":w "Plausible.Site":"field@id":e -> "Plausible.Site.MonthlyReport":"field@site_id":w [dir=none] "Plausible.Site":"field@id":e -> "Plausible.Plugins.API.Token":"field@site_id":w "Plausible.Site":"field@id":e -> "Plausible.Site.SharedLink":"field@site_id":w "Plausible.Site":"field@id":e -> "Plausible.Shield.CountryRule":"field@site_id":w "Plausible.Site":"field@id":e -> "Plausible.Shield.HostnameRule":"field@site_id":w "Plausible.Site":"field@id":e -> "Plausible.Shield.IPRule":"field@site_id":w "Plausible.Site":"field@id":e -> "Plausible.Shield.PageRule":"field@site_id":w "Plausible.Site":"field@id":e -> "Plausible.Imported.SiteImport":"field@site_id":w "Plausible.Site":"field@id":e -> "Plausible.Site.Membership":"field@site_id":w [dir=none] "Plausible.Site":"field@id":e -> "Plausible.Site.UserPreference":"field@site_id":w "Plausible.Site":"field@id":e -> "Plausible.Site.SpikeNotification":"field@site_id":w [dir=none] "Plausible.Site":"field@id":e -> "Plausible.Site.WeeklyReport":"field@site_id":w [dir=none] "Plausible.Site":"field@imported_data":e -> "Plausible.Site.ImportedData":"header@schema_module":w [dir=none] "Plausible.Auth.User":"field@grace_period":e -> "Plausible.Auth.GracePeriod":"header@schema_module":w [dir=none] "Plausible.Auth.User":"field@id":e -> "Plausible.Auth.ApiKey":"field@user_id":w "Plausible.Auth.User":"field@id":e -> "Plausible.Auth.EmailActivationCode":"field@user_id":w "Plausible.Auth.User":"field@id":e -> "Plausible.Billing.EnterprisePlan":"field@user_id":w [dir=none] "Plausible.Auth.User":"field@id":e -> "Plausible.Site.GoogleAuth":"field@user_id":w [dir=none] "Plausible.Auth.User":"field@id":e -> "Plausible.Auth.Invitation":"field@inviter_id":w "Plausible.Auth.User":"field@id":e -> "Plausible.Imported.SiteImport":"field@imported_by_id":w "Plausible.Auth.User":"field@id":e -> "Plausible.Site.Membership":"field@user_id":w "Plausible.Auth.User":"field@id":e -> "Plausible.Site.UserPreference":"field@user_id":w "Plausible.Auth.User":"field@id":e -> "Plausible.Billing.Subscription":"field@user_id":w [dir=none] "Plausible.Auth.User":"field@id":e -> "Plausible.Auth.TOTP.RecoveryCode":"field@user_id":w } ================================================ FILE: examples/dot/plausible-analytics/No-fields.dot ================================================ strict digraph { ranksep=1.0; rankdir=LR; node [shape = none, fontname="Roboto Mono"]; "Ecto.Migration.SchemaMigration" [label= <
Ecto.Migration.SchemaMigration
schema_migrations
>] "FunWithFlags.Store.Persistent.Ecto.Record" [label= <
FunWithFlags.Store.Persistent.Ecto.Record
fun_with_flags_toggles
>] "Oban.Job" [label= <
Oban.Job
oban_jobs
>] "Plausible.Auth.ApiKey" [label= <
Plausible.Auth.ApiKey
api_keys
>] "Plausible.Auth.EmailActivationCode" [label= <
Plausible.Auth.EmailActivationCode
email_activation_codes
>] "Plausible.Auth.GracePeriod" [label= <
Plausible.Auth.GracePeriod
>] "Plausible.Auth.Invitation" [label= <
Plausible.Auth.Invitation
invitations
>] "Plausible.Auth.TOTP.RecoveryCode" [label= <
Plausible.Auth.TOTP.RecoveryCode
totp_recovery_codes
>] "Plausible.Auth.User" [label= <
Plausible.Auth.User
users
>] "Plausible.Billing.EnterprisePlan" [label= <
Plausible.Billing.EnterprisePlan
enterprise_plans
>] "Plausible.Billing.Plan" [label= <
Plausible.Billing.Plan
>] "Plausible.Billing.Subscription" [label= <
Plausible.Billing.Subscription
subscriptions
>] "Plausible.ClickhouseEventV2" [label= <
Plausible.ClickhouseEventV2
events_v2
>] "Plausible.ClickhouseSessionV2" [label= <
Plausible.ClickhouseSessionV2
sessions_v2
>] "Plausible.DataMigration.NumericIDs.DomainsLookup" [label= <
Plausible.DataMigration.NumericIDs.DomainsLookup
domains_lookup
>] "Plausible.Funnel" [label= <
Plausible.Funnel
funnels
>] "Plausible.Funnel.Step" [label= <
Plausible.Funnel.Step
funnel_steps
>] "Plausible.Goal" [label= <
Plausible.Goal
goals
>] "Plausible.Imported.Browser" [label= <
Plausible.Imported.Browser
imported_browsers
>] "Plausible.Imported.Device" [label= <
Plausible.Imported.Device
imported_devices
>] "Plausible.Imported.EntryPage" [label= <
Plausible.Imported.EntryPage
imported_entry_pages
>] "Plausible.Imported.ExitPage" [label= <
Plausible.Imported.ExitPage
imported_exit_pages
>] "Plausible.Imported.Location" [label= <
Plausible.Imported.Location
imported_locations
>] "Plausible.Imported.OperatingSystem" [label= <
Plausible.Imported.OperatingSystem
imported_operating_systems
>] "Plausible.Imported.Page" [label= <
Plausible.Imported.Page
imported_pages
>] "Plausible.Imported.SiteImport" [label= <
Plausible.Imported.SiteImport
site_imports
>] "Plausible.Imported.Source" [label= <
Plausible.Imported.Source
imported_sources
>] "Plausible.Imported.Visitor" [label= <
Plausible.Imported.Visitor
imported_visitors
>] "Plausible.Ingestion.Counters.Record" [label= <
Plausible.Ingestion.Counters.Record
ingest_counters
>] "Plausible.Ingestion.Request" [label= <
Plausible.Ingestion.Request
>] "Plausible.Plugins.API.Token" [label= <
Plausible.Plugins.API.Token
plugins_api_tokens
>] "Plausible.Shield.CountryRule" [label= <
Plausible.Shield.CountryRule
shield_rules_country
>] "Plausible.Shield.HostnameRule" [label= <
Plausible.Shield.HostnameRule
shield_rules_hostname
>] "Plausible.Shield.IPRule" [label= <
Plausible.Shield.IPRule
shield_rules_ip
>] "Plausible.Shield.PageRule" [label= <
Plausible.Shield.PageRule
shield_rules_page
>] "Plausible.Site" [label= <
Plausible.Site
sites
>] "Plausible.Site.GoogleAuth" [label= <
Plausible.Site.GoogleAuth
google_auth
>] "Plausible.Site.ImportedData" [label= <
Plausible.Site.ImportedData
>] "Plausible.Site.Membership" [label= <
Plausible.Site.Membership
site_memberships
>] "Plausible.Site.MonthlyReport" [label= <
Plausible.Site.MonthlyReport
monthly_reports
>] "Plausible.Site.SharedLink" [label= <
Plausible.Site.SharedLink
shared_links
>] "Plausible.Site.SpikeNotification" [label= <
Plausible.Site.SpikeNotification
spike_notifications
>] "Plausible.Site.UserPreference" [label= <
Plausible.Site.UserPreference
site_user_preferences
>] "Plausible.Site.WeeklyReport" [label= <
Plausible.Site.WeeklyReport
weekly_reports
>] "Plausible.Funnel":e -> "Plausible.Funnel.Step":w "Plausible.Goal":e -> "Plausible.Funnel.Step":w "Plausible.Site":e -> "Plausible.Funnel":w "Plausible.Site":e -> "Plausible.Goal":w "Plausible.Site":e -> "Plausible.Site.GoogleAuth":w [dir=none] "Plausible.Site":e -> "Plausible.Auth.Invitation":w "Plausible.Site":e -> "Plausible.Site.MonthlyReport":w [dir=none] "Plausible.Site":e -> "Plausible.Plugins.API.Token":w "Plausible.Site":e -> "Plausible.Site.SharedLink":w "Plausible.Site":e -> "Plausible.Shield.CountryRule":w "Plausible.Site":e -> "Plausible.Shield.HostnameRule":w "Plausible.Site":e -> "Plausible.Shield.IPRule":w "Plausible.Site":e -> "Plausible.Shield.PageRule":w "Plausible.Site":e -> "Plausible.Imported.SiteImport":w "Plausible.Site":e -> "Plausible.Site.Membership":w [dir=none] "Plausible.Site":e -> "Plausible.Site.UserPreference":w "Plausible.Site":e -> "Plausible.Site.SpikeNotification":w [dir=none] "Plausible.Site":e -> "Plausible.Site.WeeklyReport":w [dir=none] "Plausible.Site":e -> "Plausible.Site.ImportedData":w [dir=none] "Plausible.Auth.User":e -> "Plausible.Auth.GracePeriod":w [dir=none] "Plausible.Auth.User":e -> "Plausible.Auth.ApiKey":w "Plausible.Auth.User":e -> "Plausible.Auth.EmailActivationCode":w "Plausible.Auth.User":e -> "Plausible.Billing.EnterprisePlan":w [dir=none] "Plausible.Auth.User":e -> "Plausible.Site.GoogleAuth":w [dir=none] "Plausible.Auth.User":e -> "Plausible.Auth.Invitation":w "Plausible.Auth.User":e -> "Plausible.Imported.SiteImport":w "Plausible.Auth.User":e -> "Plausible.Site.Membership":w "Plausible.Auth.User":e -> "Plausible.Site.UserPreference":w "Plausible.Auth.User":e -> "Plausible.Billing.Subscription":w [dir=none] "Plausible.Auth.User":e -> "Plausible.Auth.TOTP.RecoveryCode":w } ================================================ FILE: examples/mermaid/changelog.com/Default.mmd ================================================ erDiagram episodes { integer id PK varchar slug varchar guid varchar title varchar subtitle integer type boolean featured varchar highlight varchar subhighlight varchar summary varchar notes varchar doc_url varchar socialize_url boolean published timestamp published_at timestamp recorded_at boolean recorded_live varchar youtube_id varchar cover varchar audio_file integer audio_bytes integer audio_duration jsonb audio_chapters varchar plusplus_file integer plusplus_bytes integer plusplus_duration jsonb plusplus_chapters float download_count float import_count integer reach_count varchar email_subject varchar email_teaser varchar email_content integer email_sends integer email_opens array transcript integer podcast_id integer request_id timestamp inserted_at timestamp updated_at } episode_guests { integer id PK integer position boolean thanks varchar discount_code integer episode_id integer person_id timestamp inserted_at timestamp updated_at } episode_hosts { integer id PK integer position integer person_id integer episode_id timestamp inserted_at timestamp updated_at } episode_requests { integer id PK integer status varchar hosts varchar guests varchar topics varchar pitch varchar pronunciation varchar message integer podcast_id integer submitter_id timestamp inserted_at timestamp updated_at } episode_sponsors { integer id PK integer position varchar title varchar link_url varchar description float starts_at float ends_at integer episode_id integer sponsor_id timestamp inserted_at timestamp updated_at } episode_stats { integer id PK date date integer episode_bytes integer total_bytes float downloads integer uniques jsonb demographics integer episode_id integer podcast_id timestamp inserted_at timestamp updated_at } episode_topics { integer id PK integer position integer topic_id integer episode_id timestamp inserted_at timestamp updated_at } feeds { integer id PK varchar name varchar slug varchar description varchar title_format boolean plusplus boolean autosub timestamp starts_at varchar cover array podcast_ids array person_ids integer owner_id timestamp inserted_at timestamp updated_at } news_ads { integer id PK varchar url varchar headline varchar story varchar image boolean active boolean newsletter integer impression_count integer click_count integer sponsorship_id timestamp inserted_at timestamp updated_at } news_issues { integer id PK varchar slug varchar note varchar teaser boolean published timestamp published_at timestamp inserted_at timestamp updated_at } news_issue_ads { integer id PK integer position boolean image integer ad_id integer issue_id timestamp inserted_at timestamp updated_at } news_issue_items { integer id PK integer position boolean image integer issue_id integer item_id timestamp inserted_at timestamp updated_at } news_items { integer id PK integer status integer type varchar url varchar headline varchar story varchar image varchar object_id boolean feed_only boolean pinned timestamp published_at timestamp refreshed_at integer impression_count integer click_count varchar message integer author_id integer logger_id integer submitter_id integer source_id timestamp inserted_at timestamp updated_at } news_item_comments { integer id PK varchar content boolean approved timestamp edited_at timestamp deleted_at integer item_id integer author_id integer parent_id timestamp inserted_at timestamp updated_at } news_item_topics { integer id PK integer position integer item_id integer topic_id timestamp inserted_at timestamp updated_at } news_queue { integer id PK float position integer item_id } news_sources { integer id PK varchar name varchar slug varchar website varchar twitter_handle varchar description varchar feed varchar regex boolean publication varchar icon timestamp inserted_at timestamp updated_at } news_sponsorships { integer id PK varchar name array weeks integer impression_count integer click_count integer sponsor_id timestamp inserted_at timestamp updated_at } people { integer id PK varchar name varchar email varchar handle varchar github_handle varchar linkedin_handle varchar mastodon_handle varchar twitter_handle varchar slack_id varchar website varchar bio varchar location varchar auth_token timestamp auth_token_expires_at timestamp joined_at timestamp signed_in_at boolean approved varchar avatar boolean admin boolean host boolean editor boolean public_profile jsonb settings timestamp inserted_at timestamp updated_at } podcasts { integer id PK varchar name varchar slug integer status varchar welcome varchar description varchar extended_description varchar vanity_domain varchar keywords varchar mastodon_handle varchar twitter_handle varchar apple_url varchar spotify_url varchar riverside_url varchar chartable_id varchar schedule_note float download_count integer reach_count boolean recorded_live boolean partner integer position jsonb subscribers varchar cover timestamp inserted_at timestamp updated_at } podcast_hosts { integer id PK integer position boolean retired integer person_id integer podcast_id timestamp inserted_at timestamp updated_at } podcast_topics { integer id PK integer position integer podcast_id integer topic_id timestamp inserted_at timestamp updated_at } posts { integer id PK varchar title varchar subtitle varchar slug varchar guid varchar canonical_url varchar image varchar tldr varchar body boolean published timestamp published_at integer author_id integer editor_id timestamp inserted_at timestamp updated_at } post_topics { integer id PK integer position integer topic_id integer post_id timestamp inserted_at timestamp updated_at } sponsors { integer id PK varchar name varchar description varchar website varchar github_handle varchar twitter_handle varchar avatar varchar color_logo varchar dark_logo varchar light_logo timestamp inserted_at timestamp updated_at } sponsor_reps { integer id PK integer sponsor_id integer person_id timestamp inserted_at timestamp updated_at } subscriptions { integer id PK timestamp unsubscribed_at varchar context integer episode_id integer item_id integer person_id integer podcast_id timestamp inserted_at timestamp updated_at } topics { integer id PK varchar name varchar slug varchar description varchar website varchar twitter_handle varchar icon timestamp inserted_at timestamp updated_at } schema_migrations { integer version PK timestamp inserted_at } oban_jobs { integer id PK varchar state varchar queue varchar worker jsonb args jsonb meta array tags array errors integer attempt array attempted_by integer max_attempts integer priority timestamp attempted_at timestamp cancelled_at timestamp completed_at timestamp discarded_at timestamp inserted_at timestamp scheduled_at } episode_requests ||--o| episodes : "" episodes ||--|{ episode_guests : "" episodes ||--|{ episode_hosts : "" episodes ||--|{ episode_sponsors : "" episodes ||--|{ episode_stats : "" episodes ||--|{ episode_topics : "" episodes ||--|{ subscriptions : "" news_ads ||--|{ news_issue_ads : "" news_issues ||--|{ news_issue_ads : "" news_issues ||--|{ news_issue_items : "" news_item_comments ||--|{ news_item_comments : "" news_items ||--|{ news_issue_items : "" news_items ||--|{ news_item_comments : "" news_items ||--|{ news_item_topics : "" news_items ||--o| news_queue : "" news_items ||--|{ subscriptions : "" news_sources ||--|{ news_items : "" news_sponsorships ||--|{ news_ads : "" people ||--|{ episode_guests : "" people ||--|{ episode_hosts : "" people ||--|{ episode_requests : "" people ||--|{ feeds : "" people ||--|{ news_item_comments : "" people ||--|{ news_items : "" people ||--|{ news_items : "" people ||--|{ news_items : "" people ||--|{ podcast_hosts : "" people ||--|{ posts : "" people ||--|{ posts : "" people ||--|{ sponsor_reps : "" people ||--|{ subscriptions : "" podcasts ||--|{ episode_requests : "" podcasts ||--|{ episode_stats : "" podcasts ||--|{ episodes : "" podcasts ||--|{ podcast_hosts : "" podcasts ||--|{ podcast_topics : "" podcasts ||--|{ subscriptions : "" posts ||--|{ post_topics : "" sponsors ||--|{ episode_sponsors : "" sponsors ||--|{ news_sponsorships : "" sponsors ||--|{ sponsor_reps : "" topics ||--|{ episode_topics : "" topics ||--|{ news_item_topics : "" topics ||--|{ podcast_topics : "" topics ||--|{ post_topics : "" ================================================ FILE: examples/mermaid/changelog.com/No-fields.mmd ================================================ erDiagram episodes episode_guests episode_hosts episode_requests episode_sponsors episode_stats episode_topics feeds news_ads news_issues news_issue_ads news_issue_items news_items news_item_comments news_item_topics news_queue news_sources news_sponsorships people podcasts podcast_hosts podcast_topics posts post_topics sponsors sponsor_reps subscriptions topics schema_migrations oban_jobs episode_requests ||--o| episodes : "" episodes ||--|{ episode_guests : "" episodes ||--|{ episode_hosts : "" episodes ||--|{ episode_sponsors : "" episodes ||--|{ episode_stats : "" episodes ||--|{ episode_topics : "" episodes ||--|{ subscriptions : "" news_ads ||--|{ news_issue_ads : "" news_issues ||--|{ news_issue_ads : "" news_issues ||--|{ news_issue_items : "" news_item_comments ||--|{ news_item_comments : "" news_items ||--|{ news_issue_items : "" news_items ||--|{ news_item_comments : "" news_items ||--|{ news_item_topics : "" news_items ||--o| news_queue : "" news_items ||--|{ subscriptions : "" news_sources ||--|{ news_items : "" news_sponsorships ||--|{ news_ads : "" people ||--|{ episode_guests : "" people ||--|{ episode_hosts : "" people ||--|{ episode_requests : "" people ||--|{ feeds : "" people ||--|{ news_item_comments : "" people ||--|{ news_items : "" people ||--|{ news_items : "" people ||--|{ news_items : "" people ||--|{ podcast_hosts : "" people ||--|{ posts : "" people ||--|{ posts : "" people ||--|{ sponsor_reps : "" people ||--|{ subscriptions : "" podcasts ||--|{ episode_requests : "" podcasts ||--|{ episode_stats : "" podcasts ||--|{ episodes : "" podcasts ||--|{ podcast_hosts : "" podcasts ||--|{ podcast_topics : "" podcasts ||--|{ subscriptions : "" posts ||--|{ post_topics : "" sponsors ||--|{ episode_sponsors : "" sponsors ||--|{ news_sponsorships : "" sponsors ||--|{ sponsor_reps : "" topics ||--|{ episode_topics : "" topics ||--|{ news_item_topics : "" topics ||--|{ podcast_topics : "" topics ||--|{ post_topics : "" ================================================ FILE: examples/mermaid/hexpm/Default.mmd ================================================ erDiagram schema_migrations { integer version PK timestamp inserted_at } audit_logs { integer id PK varchar user_agent varchar remote_ip varchar action jsonb params integer user_id integer organization_id integer key_id timestamp inserted_at } emails { integer id PK varchar email boolean verified boolean primary boolean public boolean gravatar varchar verification_key timestamp verification_expiry integer user_id timestamp inserted_at timestamp updated_at } keys { integer id PK varchar name varchar secret_first varchar secret_second boolean public timestamp revoke_at timestamp inserted_at timestamp updated_at jsonb last_use integer user_id integer organization_id jsonb permissions } organizations { integer id PK varchar name boolean billing_active boolean billing_override timestamp trial_end timestamp inserted_at timestamp updated_at } organization_users { integer id PK varchar role integer organization_id integer user_id timestamp inserted_at timestamp updated_at } password_resets { integer id PK varchar key varchar primary_email integer user_id timestamp inserted_at } sessions { integer id PK bytea token jsonb data timestamp inserted_at timestamp updated_at } users { integer id PK varchar username varchar full_name varchar password boolean service timestamp deactivated_at varchar role timestamp inserted_at timestamp updated_at jsonb handles jsonb tfa integer organization_id } blocked_addresses { integer id PK varchar ip varchar comment } downloads { integer id PK integer package_id integer release_id integer downloads date day } installs { integer id PK varchar hex array elixirs } packages { integer id PK varchar name timestamp docs_updated_at timestamp inserted_at timestamp updated_at integer repository_id jsonb meta } package_dependants { integer id PK integer package_id varchar name varchar repo } package_downloads { integer package_id varchar view integer downloads } package_owners { integer id PK varchar level integer package_id integer user_id timestamp inserted_at timestamp updated_at } package_reports { integer id PK varchar state varchar description integer author_id integer package_id timestamp inserted_at timestamp updated_at } package_report_comments { integer id PK varchar text timestamp inserted_at timestamp updated_at integer package_report_id integer author_id } package_report_releases { integer id PK integer release_id integer package_report_id timestamp inserted_at timestamp updated_at } releases { integer id PK varchar version bytea inner_checksum bytea outer_checksum boolean has_docs timestamp inserted_at timestamp updated_at integer package_id integer publisher_id jsonb meta jsonb retirement } release_downloads { integer package_id integer release_id integer downloads } repositories { integer id PK varchar name timestamp inserted_at timestamp updated_at integer organization_id } requirements { integer id PK varchar app varchar requirement boolean optional integer release_id integer dependency_id } short_urls { integer id PK varchar url varchar short_code timestamp inserted_at } keys ||--|{ audit_logs : "" organizations ||--|{ audit_logs : "" organizations ||--|{ keys : "" organizations ||--|{ organization_users : "" organizations ||--o| repositories : "" organizations ||--o| users : "" package_reports ||--|{ package_report_comments : "" package_reports ||--|{ package_report_releases : "" packages ||--|{ downloads : "" packages ||--|{ package_dependants : "" packages ||--|{ package_downloads : "" packages ||--|{ package_owners : "" packages ||--|{ package_reports : "" packages ||--|{ release_downloads : "" packages ||--|{ releases : "" packages ||--|{ requirements : "" releases ||--|{ downloads : "" releases ||--|{ package_report_releases : "" releases ||--o| release_downloads : "" releases ||--|{ requirements : "" repositories ||--|{ packages : "" users ||--|{ audit_logs : "" users ||--|{ emails : "" users ||--|{ keys : "" users ||--|{ organization_users : "" users ||--|{ package_owners : "" users ||--|{ package_report_comments : "" users ||--|{ package_reports : "" users ||--|{ password_resets : "" users ||--|{ releases : "" ================================================ FILE: examples/mermaid/hexpm/No-fields.mmd ================================================ erDiagram schema_migrations audit_logs emails keys organizations organization_users password_resets sessions users blocked_addresses downloads installs packages package_dependants package_downloads package_owners package_reports package_report_comments package_report_releases releases release_downloads repositories requirements short_urls keys ||--|{ audit_logs : "" organizations ||--|{ audit_logs : "" organizations ||--|{ keys : "" organizations ||--|{ organization_users : "" organizations ||--o| repositories : "" organizations ||--o| users : "" package_reports ||--|{ package_report_comments : "" package_reports ||--|{ package_report_releases : "" packages ||--|{ downloads : "" packages ||--|{ package_dependants : "" packages ||--|{ package_downloads : "" packages ||--|{ package_owners : "" packages ||--|{ package_reports : "" packages ||--|{ release_downloads : "" packages ||--|{ releases : "" packages ||--|{ requirements : "" releases ||--|{ downloads : "" releases ||--|{ package_report_releases : "" releases ||--o| release_downloads : "" releases ||--|{ requirements : "" repositories ||--|{ packages : "" users ||--|{ audit_logs : "" users ||--|{ emails : "" users ||--|{ keys : "" users ||--|{ organization_users : "" users ||--|{ package_owners : "" users ||--|{ package_report_comments : "" users ||--|{ package_reports : "" users ||--|{ password_resets : "" users ||--|{ releases : "" ================================================ FILE: examples/mermaid/plausible-analytics/Default.mmd ================================================ erDiagram schema_migrations { integer version PK timestamp inserted_at } fun_with_flags_toggles { integer id PK varchar flag_name varchar gate_type varchar target boolean enabled } oban_jobs { integer id PK varchar state varchar queue varchar worker jsonb args jsonb meta array tags array errors integer attempt array attempted_by integer max_attempts integer priority timestamp attempted_at timestamp cancelled_at timestamp completed_at timestamp discarded_at timestamp inserted_at timestamp scheduled_at } api_keys { integer id PK varchar name array scopes integer hourly_request_limit varchar key_hash varchar key_prefix integer user_id timestamp inserted_at timestamp updated_at } email_activation_codes { integer id PK varchar code timestamp issued_at integer user_id } invitations { integer id PK varchar invitation_id varchar email varchar role integer inviter_id integer site_id timestamp inserted_at timestamp updated_at } totp_recovery_codes { integer id PK varchar code_digest integer user_id timestamp inserted_at } users { integer id PK varchar email varchar password_hash varchar name timestamp last_seen date trial_expiry_date varchar theme boolean email_verified varchar previous_email date accept_traffic_until boolean allow_next_upgrade_override boolean totp_enabled bytea totp_secret varchar totp_token timestamp totp_last_used_at jsonb grace_period timestamp inserted_at timestamp updated_at } enterprise_plans { integer id PK varchar paddle_plan_id varchar billing_interval integer monthly_pageview_limit integer site_limit integer team_member_limit array features integer hourly_api_request_limit integer user_id timestamp inserted_at timestamp updated_at } subscriptions { integer id PK varchar paddle_subscription_id varchar paddle_plan_id varchar update_url varchar cancel_url varchar status varchar next_bill_amount date next_bill_date date last_bill_date varchar currency_code integer user_id timestamp inserted_at timestamp updated_at } events_v2 { unknown name unknown site_id varchar hostname varchar pathname unknown user_id unknown session_id timestamp timestamp unknown revenue_source_amount unknown revenue_source_currency unknown revenue_reporting_amount unknown revenue_reporting_currency varchar referrer varchar referrer_source varchar utm_medium varchar utm_source varchar utm_campaign varchar utm_content varchar utm_term unknown country_code unknown subdivision1_code unknown subdivision2_code unknown city_geoname_id unknown screen_size unknown operating_system unknown operating_system_version unknown browser unknown browser_version } sessions_v2 { varchar hostname unknown site_id unknown user_id unknown session_id timestamp start unknown duration unknown is_bounce varchar entry_page varchar exit_page varchar exit_page_hostname unknown pageviews unknown events unknown sign varchar utm_medium varchar utm_source varchar utm_campaign varchar utm_content varchar utm_term varchar referrer varchar referrer_source unknown country_code unknown subdivision1_code unknown subdivision2_code unknown city_geoname_id unknown screen_size unknown operating_system unknown operating_system_version unknown browser unknown browser_version timestamp timestamp varchar transferred_from } domains_lookup { unknown site_id varchar domain } funnels { integer id PK varchar name integer site_id timestamp inserted_at timestamp updated_at } funnel_steps { integer id PK integer step_order integer funnel_id integer goal_id timestamp inserted_at timestamp updated_at } goals { integer id PK varchar event_name varchar page_path varchar currency integer site_id timestamp inserted_at timestamp updated_at } imported_browsers { unknown site_id unknown import_id date date varchar browser varchar browser_version unknown visitors unknown visits unknown visit_duration unknown pageviews unknown bounces } imported_devices { unknown site_id unknown import_id date date varchar device unknown visitors unknown visits unknown visit_duration unknown pageviews unknown bounces } imported_entry_pages { unknown site_id unknown import_id date date varchar entry_page unknown visitors unknown entrances unknown visit_duration unknown pageviews unknown bounces } imported_exit_pages { unknown site_id unknown import_id date date varchar exit_page unknown exits unknown visitors unknown visit_duration unknown pageviews unknown bounces } imported_locations { unknown site_id unknown import_id date date varchar country varchar region unknown city unknown visitors unknown visits unknown visit_duration unknown pageviews unknown bounces } imported_operating_systems { unknown site_id unknown import_id date date varchar operating_system varchar operating_system_version unknown visitors unknown visits unknown visit_duration unknown pageviews unknown bounces } imported_pages { unknown site_id unknown import_id date date varchar hostname varchar page unknown visits unknown visitors unknown active_visitors unknown pageviews unknown exits unknown time_on_page } site_imports { integer id PK date start_date date end_date varchar label varchar source varchar status boolean legacy integer site_id integer imported_by_id timestamp inserted_at timestamp updated_at } imported_sources { unknown site_id unknown import_id date date varchar source varchar referrer varchar utm_source varchar utm_medium varchar utm_campaign varchar utm_content varchar utm_term unknown visitors unknown visits unknown visit_duration unknown pageviews unknown bounces } imported_visitors { unknown site_id unknown import_id date date unknown visitors unknown pageviews unknown bounces unknown visits unknown visit_duration } ingest_counters { timestamp event_timebucket unknown site_id unknown domain unknown metric unknown value } plugins_api_tokens { uuid id PK timestamp inserted_at timestamp updated_at bytea token_hash varchar description varchar hint timestamp last_used_at integer site_id } shield_rules_country { uuid id PK integer site_id varchar country_code varchar action varchar added_by timestamp inserted_at timestamp updated_at } shield_rules_hostname { uuid id PK integer site_id varchar hostname varchar hostname_pattern varchar action varchar added_by timestamp inserted_at timestamp updated_at } shield_rules_ip { uuid id PK integer site_id inet inet varchar action varchar description varchar added_by timestamp inserted_at timestamp updated_at } shield_rules_page { uuid id PK integer site_id varchar page_path varchar page_path_pattern varchar action varchar added_by timestamp inserted_at timestamp updated_at } sites { integer id PK varchar domain varchar timezone boolean public boolean locked date stats_start_date timestamp native_stats_start_at array allowed_event_props boolean conversions_enabled boolean props_enabled boolean funnels_enabled integer ingest_rate_limit_scale_seconds integer ingest_rate_limit_threshold varchar domain_changed_from timestamp domain_changed_at jsonb imported_data timestamp inserted_at timestamp updated_at } google_auth { integer id PK varchar email varchar property varchar refresh_token varchar access_token timestamp expires integer user_id integer site_id timestamp inserted_at timestamp updated_at } site_memberships { integer id PK varchar role integer site_id integer user_id timestamp inserted_at timestamp updated_at } monthly_reports { integer id PK array recipients integer site_id timestamp inserted_at timestamp updated_at } shared_links { integer id PK integer site_id varchar name varchar slug varchar password_hash timestamp inserted_at timestamp updated_at } spike_notifications { integer id PK array recipients integer threshold timestamp last_sent integer site_id timestamp inserted_at timestamp updated_at } site_user_preferences { integer id PK timestamp pinned_at integer user_id integer site_id timestamp inserted_at timestamp updated_at } weekly_reports { integer id PK array recipients integer site_id timestamp inserted_at timestamp updated_at } funnels ||--|{ funnel_steps : "" goals ||--|{ funnel_steps : "" sites ||--|{ funnels : "" sites ||--|{ goals : "" sites ||--o| google_auth : "" sites ||--|{ invitations : "" sites ||--o| monthly_reports : "" sites ||--|{ plugins_api_tokens : "" sites ||--|{ shared_links : "" sites ||--|{ shield_rules_country : "" sites ||--|{ shield_rules_hostname : "" sites ||--|{ shield_rules_ip : "" sites ||--|{ shield_rules_page : "" sites ||--|{ site_imports : "" sites ||--o| site_memberships : "" sites ||--|{ site_user_preferences : "" sites ||--o| spike_notifications : "" sites ||--o| weekly_reports : "" users ||--|{ api_keys : "" users ||--|{ email_activation_codes : "" users ||--o| enterprise_plans : "" users ||--o| google_auth : "" users ||--|{ invitations : "" users ||--|{ site_imports : "" users ||--|{ site_memberships : "" users ||--|{ site_user_preferences : "" users ||--o| subscriptions : "" users ||--|{ totp_recovery_codes : "" ================================================ FILE: examples/mermaid/plausible-analytics/No-fields.mmd ================================================ erDiagram schema_migrations fun_with_flags_toggles oban_jobs api_keys email_activation_codes invitations totp_recovery_codes users enterprise_plans subscriptions events_v2 sessions_v2 domains_lookup funnels funnel_steps goals imported_browsers imported_devices imported_entry_pages imported_exit_pages imported_locations imported_operating_systems imported_pages site_imports imported_sources imported_visitors ingest_counters plugins_api_tokens shield_rules_country shield_rules_hostname shield_rules_ip shield_rules_page sites google_auth site_memberships monthly_reports shared_links spike_notifications site_user_preferences weekly_reports funnels ||--|{ funnel_steps : "" goals ||--|{ funnel_steps : "" sites ||--|{ funnels : "" sites ||--|{ goals : "" sites ||--o| google_auth : "" sites ||--|{ invitations : "" sites ||--o| monthly_reports : "" sites ||--|{ plugins_api_tokens : "" sites ||--|{ shared_links : "" sites ||--|{ shield_rules_country : "" sites ||--|{ shield_rules_hostname : "" sites ||--|{ shield_rules_ip : "" sites ||--|{ shield_rules_page : "" sites ||--|{ site_imports : "" sites ||--o| site_memberships : "" sites ||--|{ site_user_preferences : "" sites ||--o| spike_notifications : "" sites ||--o| weekly_reports : "" users ||--|{ api_keys : "" users ||--|{ email_activation_codes : "" users ||--o| enterprise_plans : "" users ||--o| google_auth : "" users ||--|{ invitations : "" users ||--|{ site_imports : "" users ||--|{ site_memberships : "" users ||--|{ site_user_preferences : "" users ||--o| subscriptions : "" users ||--|{ totp_recovery_codes : "" ================================================ FILE: examples/plantuml/changelog.com/Clusters.puml ================================================ @startuml set namespaceSeparator none hide circle hide methods skinparam linetype ortho skinparam defaultFontName Roboto Mono skinparam shadowing false namespace EPISODE #b4eeb4 { entity Changelog.Episode { id : id -- slug : string guid : string title : string subtitle : string type : integer featured : boolean highlight : string subhighlight : string summary : string notes : string doc_url : string socialize_url : string published : boolean published_at : utc_datetime recorded_at : utc_datetime recorded_live : boolean youtube_id : string cover : string audio_file : string audio_bytes : integer audio_duration : integer audio_chapters : map plusplus_file : string plusplus_bytes : integer plusplus_duration : integer plusplus_chapters : map download_count : float import_count : float reach_count : integer email_subject : string email_teaser : string email_content : string email_sends : integer email_opens : integer transcript : array podcast_id : id request_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.EpisodeChapter { id : binary_id -- title : string starts_at : float ends_at : float link_url : string image_url : string } entity Changelog.EpisodeGuest { id : id -- position : integer thanks : boolean discount_code : string episode_id : id person_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.EpisodeHost { id : id -- position : integer person_id : id episode_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.EpisodeRequest { id : id -- status : integer hosts : string guests : string topics : string pitch : string pronunciation : string message : string podcast_id : id submitter_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.EpisodeSponsor { id : id -- position : integer title : string link_url : string description : string starts_at : float ends_at : float episode_id : id sponsor_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.EpisodeStat { id : id -- date : date episode_bytes : integer total_bytes : integer downloads : float uniques : integer demographics : map episode_id : id podcast_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.EpisodeTopic { id : id -- position : integer topic_id : id episode_id : id inserted_at : naive_datetime updated_at : naive_datetime } } namespace NEWS #eee5de { entity Changelog.NewsAd { id : id -- url : string headline : string story : string image : string active : boolean newsletter : boolean impression_count : integer click_count : integer sponsorship_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.NewsIssue { id : id -- slug : string note : string teaser : string published : boolean published_at : utc_datetime inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.NewsIssueAd { id : id -- position : integer image : boolean ad_id : id issue_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.NewsIssueItem { id : id -- position : integer image : boolean issue_id : id item_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.NewsItem { id : id -- status : integer type : integer url : string headline : string story : string image : string object_id : string feed_only : boolean pinned : boolean published_at : utc_datetime refreshed_at : utc_datetime impression_count : integer click_count : integer message : string author_id : id logger_id : id submitter_id : id source_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.NewsItemComment { id : id -- content : string approved : boolean edited_at : utc_datetime deleted_at : utc_datetime item_id : id author_id : id parent_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.NewsItemTopic { id : id -- position : integer item_id : id topic_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.NewsQueue { id : id -- position : float item_id : id } entity Changelog.NewsSource { id : id -- name : string slug : string website : string twitter_handle : string description : string feed : string regex : string publication : boolean icon : string inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.NewsSponsorship { id : id -- name : string weeks : array impression_count : integer click_count : integer sponsor_id : id inserted_at : naive_datetime updated_at : naive_datetime } } namespace PERSON #f0ffff { entity Changelog.Person { id : id -- name : string email : string handle : string github_handle : string linkedin_handle : string mastodon_handle : string twitter_handle : string slack_id : string website : string bio : string location : string auth_token : string auth_token_expires_at : utc_datetime joined_at : utc_datetime signed_in_at : utc_datetime approved : boolean avatar : string admin : boolean host : boolean editor : boolean public_profile : boolean settings : map inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.Person.Settings { subscribe_to_contributed_news : boolean subscribe_to_participated_episodes : boolean email_on_authored_news : boolean email_on_submitted_news : boolean email_on_comment_replies : boolean email_on_comment_mentions : boolean } } namespace PODCAST #ffefd5 { entity Changelog.Podcast { id : id -- name : string slug : string status : integer welcome : string description : string extended_description : string vanity_domain : string keywords : string mastodon_handle : string twitter_handle : string apple_url : string spotify_url : string riverside_url : string chartable_id : string schedule_note : string download_count : float reach_count : integer recorded_live : boolean partner : boolean position : integer subscribers : map cover : string inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.PodcastHost { id : id -- position : integer retired : boolean person_id : id podcast_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.PodcastTopic { id : id -- position : integer podcast_id : id topic_id : id inserted_at : naive_datetime updated_at : naive_datetime } } namespace POST #eee5de { entity Changelog.Post { id : id -- title : string subtitle : string slug : string guid : string canonical_url : string image : string tldr : string body : string published : boolean published_at : utc_datetime author_id : id editor_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.PostTopic { id : id -- position : integer topic_id : id post_id : id inserted_at : naive_datetime updated_at : naive_datetime } } namespace SPONSOR #fffafa { entity Changelog.Sponsor { id : id -- name : string description : string website : string github_handle : string twitter_handle : string avatar : string color_logo : string dark_logo : string light_logo : string inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.SponsorRep { id : id -- sponsor_id : id person_id : id inserted_at : naive_datetime updated_at : naive_datetime } } entity Changelog.Feed { id : id -- name : string slug : string description : string title_format : string plusplus : boolean autosub : boolean starts_at : utc_datetime cover : string podcast_ids : array person_ids : array owner_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.Subscription { id : id -- unsubscribed_at : utc_datetime context : string episode_id : id item_id : id person_id : id podcast_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.Topic { id : id -- name : string slug : string description : string website : string twitter_handle : string icon : string inserted_at : naive_datetime updated_at : naive_datetime } entity Ecto.Migration.SchemaMigration { version : integer -- inserted_at : naive_datetime } entity Oban.Job { id : id -- state : string queue : string worker : string args : map meta : map tags : array errors : array attempt : integer attempted_by : array max_attempts : integer priority : integer attempted_at : utc_datetime_usec cancelled_at : utc_datetime_usec completed_at : utc_datetime_usec discarded_at : utc_datetime_usec inserted_at : utc_datetime_usec scheduled_at : utc_datetime_usec } Changelog.EpisodeRequest ||--o| Changelog.Episode Changelog.Episode ||--|{ Changelog.EpisodeChapter Changelog.Episode ||--|{ Changelog.EpisodeGuest Changelog.Episode ||--|{ Changelog.EpisodeHost Changelog.Episode ||--|{ Changelog.EpisodeSponsor Changelog.Episode ||--|{ Changelog.EpisodeStat Changelog.Episode ||--|{ Changelog.EpisodeTopic Changelog.Episode ||--|{ Changelog.Subscription Changelog.NewsAd ||--|{ Changelog.NewsIssueAd Changelog.NewsIssue ||--|{ Changelog.NewsIssueAd Changelog.NewsIssue ||--|{ Changelog.NewsIssueItem Changelog.NewsItemComment ||--|{ Changelog.NewsItemComment Changelog.NewsItem ||--|{ Changelog.NewsIssueItem Changelog.NewsItem ||--|{ Changelog.NewsItemComment Changelog.NewsItem ||--|{ Changelog.NewsItemTopic Changelog.NewsItem ||--o| Changelog.NewsQueue Changelog.NewsItem ||--|{ Changelog.Subscription Changelog.NewsSource ||--|{ Changelog.NewsItem Changelog.NewsSponsorship ||--|{ Changelog.NewsAd Changelog.Person ||--|{ Changelog.EpisodeGuest Changelog.Person ||--|{ Changelog.EpisodeHost Changelog.Person ||--|{ Changelog.EpisodeRequest Changelog.Person ||--|{ Changelog.Feed Changelog.Person ||--|{ Changelog.NewsItemComment Changelog.Person ||--|{ Changelog.NewsItem Changelog.Person ||--|{ Changelog.PodcastHost Changelog.Person ||--|{ Changelog.Post Changelog.Person ||--|{ Changelog.SponsorRep Changelog.Person ||--|{ Changelog.Subscription Changelog.Person ||--o| Changelog.Person.Settings Changelog.Podcast ||--|{ Changelog.EpisodeRequest Changelog.Podcast ||--|{ Changelog.EpisodeStat Changelog.Podcast ||--|{ Changelog.Episode Changelog.Podcast ||--|{ Changelog.PodcastHost Changelog.Podcast ||--|{ Changelog.PodcastTopic Changelog.Podcast ||--|{ Changelog.Subscription Changelog.Post ||--|{ Changelog.PostTopic Changelog.Sponsor ||--|{ Changelog.EpisodeSponsor Changelog.Sponsor ||--|{ Changelog.NewsSponsorship Changelog.Sponsor ||--|{ Changelog.SponsorRep Changelog.Topic ||--|{ Changelog.EpisodeTopic Changelog.Topic ||--|{ Changelog.NewsItemTopic Changelog.Topic ||--|{ Changelog.PodcastTopic Changelog.Topic ||--|{ Changelog.PostTopic @enduml ================================================ FILE: examples/plantuml/changelog.com/Default.puml ================================================ @startuml set namespaceSeparator none hide circle hide methods skinparam linetype ortho skinparam defaultFontName Roboto Mono skinparam shadowing false entity Changelog.Episode { id : id -- slug : string guid : string title : string subtitle : string type : integer featured : boolean highlight : string subhighlight : string summary : string notes : string doc_url : string socialize_url : string published : boolean published_at : utc_datetime recorded_at : utc_datetime recorded_live : boolean youtube_id : string cover : string audio_file : string audio_bytes : integer audio_duration : integer audio_chapters : map plusplus_file : string plusplus_bytes : integer plusplus_duration : integer plusplus_chapters : map download_count : float import_count : float reach_count : integer email_subject : string email_teaser : string email_content : string email_sends : integer email_opens : integer transcript : array podcast_id : id request_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.EpisodeChapter { id : binary_id -- title : string starts_at : float ends_at : float link_url : string image_url : string } entity Changelog.EpisodeGuest { id : id -- position : integer thanks : boolean discount_code : string episode_id : id person_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.EpisodeHost { id : id -- position : integer person_id : id episode_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.EpisodeRequest { id : id -- status : integer hosts : string guests : string topics : string pitch : string pronunciation : string message : string podcast_id : id submitter_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.EpisodeSponsor { id : id -- position : integer title : string link_url : string description : string starts_at : float ends_at : float episode_id : id sponsor_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.EpisodeStat { id : id -- date : date episode_bytes : integer total_bytes : integer downloads : float uniques : integer demographics : map episode_id : id podcast_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.EpisodeTopic { id : id -- position : integer topic_id : id episode_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.Feed { id : id -- name : string slug : string description : string title_format : string plusplus : boolean autosub : boolean starts_at : utc_datetime cover : string podcast_ids : array person_ids : array owner_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.NewsAd { id : id -- url : string headline : string story : string image : string active : boolean newsletter : boolean impression_count : integer click_count : integer sponsorship_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.NewsIssue { id : id -- slug : string note : string teaser : string published : boolean published_at : utc_datetime inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.NewsIssueAd { id : id -- position : integer image : boolean ad_id : id issue_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.NewsIssueItem { id : id -- position : integer image : boolean issue_id : id item_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.NewsItem { id : id -- status : integer type : integer url : string headline : string story : string image : string object_id : string feed_only : boolean pinned : boolean published_at : utc_datetime refreshed_at : utc_datetime impression_count : integer click_count : integer message : string author_id : id logger_id : id submitter_id : id source_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.NewsItemComment { id : id -- content : string approved : boolean edited_at : utc_datetime deleted_at : utc_datetime item_id : id author_id : id parent_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.NewsItemTopic { id : id -- position : integer item_id : id topic_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.NewsQueue { id : id -- position : float item_id : id } entity Changelog.NewsSource { id : id -- name : string slug : string website : string twitter_handle : string description : string feed : string regex : string publication : boolean icon : string inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.NewsSponsorship { id : id -- name : string weeks : array impression_count : integer click_count : integer sponsor_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.Person { id : id -- name : string email : string handle : string github_handle : string linkedin_handle : string mastodon_handle : string twitter_handle : string slack_id : string website : string bio : string location : string auth_token : string auth_token_expires_at : utc_datetime joined_at : utc_datetime signed_in_at : utc_datetime approved : boolean avatar : string admin : boolean host : boolean editor : boolean public_profile : boolean settings : map inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.Person.Settings { subscribe_to_contributed_news : boolean subscribe_to_participated_episodes : boolean email_on_authored_news : boolean email_on_submitted_news : boolean email_on_comment_replies : boolean email_on_comment_mentions : boolean } entity Changelog.Podcast { id : id -- name : string slug : string status : integer welcome : string description : string extended_description : string vanity_domain : string keywords : string mastodon_handle : string twitter_handle : string apple_url : string spotify_url : string riverside_url : string chartable_id : string schedule_note : string download_count : float reach_count : integer recorded_live : boolean partner : boolean position : integer subscribers : map cover : string inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.PodcastHost { id : id -- position : integer retired : boolean person_id : id podcast_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.PodcastTopic { id : id -- position : integer podcast_id : id topic_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.Post { id : id -- title : string subtitle : string slug : string guid : string canonical_url : string image : string tldr : string body : string published : boolean published_at : utc_datetime author_id : id editor_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.PostTopic { id : id -- position : integer topic_id : id post_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.Sponsor { id : id -- name : string description : string website : string github_handle : string twitter_handle : string avatar : string color_logo : string dark_logo : string light_logo : string inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.SponsorRep { id : id -- sponsor_id : id person_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.Subscription { id : id -- unsubscribed_at : utc_datetime context : string episode_id : id item_id : id person_id : id podcast_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Changelog.Topic { id : id -- name : string slug : string description : string website : string twitter_handle : string icon : string inserted_at : naive_datetime updated_at : naive_datetime } entity Ecto.Migration.SchemaMigration { version : integer -- inserted_at : naive_datetime } entity Oban.Job { id : id -- state : string queue : string worker : string args : map meta : map tags : array errors : array attempt : integer attempted_by : array max_attempts : integer priority : integer attempted_at : utc_datetime_usec cancelled_at : utc_datetime_usec completed_at : utc_datetime_usec discarded_at : utc_datetime_usec inserted_at : utc_datetime_usec scheduled_at : utc_datetime_usec } Changelog.EpisodeRequest ||--o| Changelog.Episode Changelog.Episode ||--|{ Changelog.EpisodeChapter Changelog.Episode ||--|{ Changelog.EpisodeGuest Changelog.Episode ||--|{ Changelog.EpisodeHost Changelog.Episode ||--|{ Changelog.EpisodeSponsor Changelog.Episode ||--|{ Changelog.EpisodeStat Changelog.Episode ||--|{ Changelog.EpisodeTopic Changelog.Episode ||--|{ Changelog.Subscription Changelog.NewsAd ||--|{ Changelog.NewsIssueAd Changelog.NewsIssue ||--|{ Changelog.NewsIssueAd Changelog.NewsIssue ||--|{ Changelog.NewsIssueItem Changelog.NewsItemComment ||--|{ Changelog.NewsItemComment Changelog.NewsItem ||--|{ Changelog.NewsIssueItem Changelog.NewsItem ||--|{ Changelog.NewsItemComment Changelog.NewsItem ||--|{ Changelog.NewsItemTopic Changelog.NewsItem ||--o| Changelog.NewsQueue Changelog.NewsItem ||--|{ Changelog.Subscription Changelog.NewsSource ||--|{ Changelog.NewsItem Changelog.NewsSponsorship ||--|{ Changelog.NewsAd Changelog.Person ||--|{ Changelog.EpisodeGuest Changelog.Person ||--|{ Changelog.EpisodeHost Changelog.Person ||--|{ Changelog.EpisodeRequest Changelog.Person ||--|{ Changelog.Feed Changelog.Person ||--|{ Changelog.NewsItemComment Changelog.Person ||--|{ Changelog.NewsItem Changelog.Person ||--|{ Changelog.PodcastHost Changelog.Person ||--|{ Changelog.Post Changelog.Person ||--|{ Changelog.SponsorRep Changelog.Person ||--|{ Changelog.Subscription Changelog.Person ||--o| Changelog.Person.Settings Changelog.Podcast ||--|{ Changelog.EpisodeRequest Changelog.Podcast ||--|{ Changelog.EpisodeStat Changelog.Podcast ||--|{ Changelog.Episode Changelog.Podcast ||--|{ Changelog.PodcastHost Changelog.Podcast ||--|{ Changelog.PodcastTopic Changelog.Podcast ||--|{ Changelog.Subscription Changelog.Post ||--|{ Changelog.PostTopic Changelog.Sponsor ||--|{ Changelog.EpisodeSponsor Changelog.Sponsor ||--|{ Changelog.NewsSponsorship Changelog.Sponsor ||--|{ Changelog.SponsorRep Changelog.Topic ||--|{ Changelog.EpisodeTopic Changelog.Topic ||--|{ Changelog.NewsItemTopic Changelog.Topic ||--|{ Changelog.PodcastTopic Changelog.Topic ||--|{ Changelog.PostTopic @enduml ================================================ FILE: examples/plantuml/hexpm/Contexts-as-clusters-no-fields.puml ================================================ @startuml set namespaceSeparator none hide circle hide methods hide fields skinparam linetype ortho skinparam defaultFontName Roboto Mono skinparam shadowing false namespace Ecto.Migration #f0f8ff { entity Ecto.Migration.SchemaMigration } namespace Hexpm.Accounts #8deeee { entity Hexpm.Accounts.AuditLog entity Hexpm.Accounts.Email entity Hexpm.Accounts.Key entity Hexpm.Accounts.Key.Use entity Hexpm.Accounts.KeyPermission entity Hexpm.Accounts.Organization entity Hexpm.Accounts.OrganizationUser entity Hexpm.Accounts.PasswordReset entity Hexpm.Accounts.RecoveryCode entity Hexpm.Accounts.Session entity Hexpm.Accounts.TFA entity Hexpm.Accounts.User entity Hexpm.Accounts.UserHandles } namespace Hexpm.BlockAddress #fffafa { entity Hexpm.BlockAddress.Entry } namespace Hexpm.Repository #eedfcc { entity Hexpm.Repository.Download entity Hexpm.Repository.Install entity Hexpm.Repository.Package entity Hexpm.Repository.PackageDependant entity Hexpm.Repository.PackageDownload entity Hexpm.Repository.PackageMetadata entity Hexpm.Repository.PackageOwner entity Hexpm.Repository.PackageReport entity Hexpm.Repository.PackageReportComment entity Hexpm.Repository.PackageReportRelease entity Hexpm.Repository.Release entity Hexpm.Repository.ReleaseDownload entity Hexpm.Repository.ReleaseMetadata entity Hexpm.Repository.ReleaseRetirement entity Hexpm.Repository.Repository entity Hexpm.Repository.Requirement } namespace Hexpm.ShortURLs #b4eeb4 { entity Hexpm.ShortURLs.ShortURL } Hexpm.Accounts.TFA ||--|{ Hexpm.Accounts.RecoveryCode Hexpm.Accounts.Key ||--|{ Hexpm.Accounts.AuditLog Hexpm.Accounts.Key ||--o| Hexpm.Accounts.Key.Use Hexpm.Accounts.Key ||--|{ Hexpm.Accounts.KeyPermission Hexpm.Accounts.Organization ||--|{ Hexpm.Accounts.AuditLog Hexpm.Accounts.Organization ||--|{ Hexpm.Accounts.Key Hexpm.Accounts.Organization ||--|{ Hexpm.Accounts.OrganizationUser Hexpm.Accounts.Organization ||--o| Hexpm.Repository.Repository Hexpm.Accounts.Organization ||--o| Hexpm.Accounts.User Hexpm.Repository.PackageReport ||--|{ Hexpm.Repository.PackageReportComment Hexpm.Repository.PackageReport ||--|{ Hexpm.Repository.PackageReportRelease Hexpm.Repository.Package ||--|{ Hexpm.Repository.Download Hexpm.Repository.Package ||--|{ Hexpm.Repository.PackageDependant Hexpm.Repository.Package ||--|{ Hexpm.Repository.PackageDownload Hexpm.Repository.Package ||--|{ Hexpm.Repository.PackageOwner Hexpm.Repository.Package ||--|{ Hexpm.Repository.PackageReport Hexpm.Repository.Package ||--|{ Hexpm.Repository.ReleaseDownload Hexpm.Repository.Package ||--|{ Hexpm.Repository.Release Hexpm.Repository.Package ||--|{ Hexpm.Repository.Requirement Hexpm.Repository.Package ||--o| Hexpm.Repository.PackageMetadata Hexpm.Repository.Release ||--|{ Hexpm.Repository.Download Hexpm.Repository.Release ||--|{ Hexpm.Repository.PackageReportRelease Hexpm.Repository.Release ||--o| Hexpm.Repository.ReleaseDownload Hexpm.Repository.Release ||--|{ Hexpm.Repository.Requirement Hexpm.Repository.Release ||--o| Hexpm.Repository.ReleaseMetadata Hexpm.Repository.Release ||--o| Hexpm.Repository.ReleaseRetirement Hexpm.Repository.Repository ||--|{ Hexpm.Repository.Package Hexpm.Accounts.User ||--o| Hexpm.Accounts.UserHandles Hexpm.Accounts.User ||--|{ Hexpm.Accounts.AuditLog Hexpm.Accounts.User ||--|{ Hexpm.Accounts.Email Hexpm.Accounts.User ||--|{ Hexpm.Accounts.Key Hexpm.Accounts.User ||--|{ Hexpm.Accounts.OrganizationUser Hexpm.Accounts.User ||--|{ Hexpm.Repository.PackageOwner Hexpm.Accounts.User ||--|{ Hexpm.Repository.PackageReportComment Hexpm.Accounts.User ||--|{ Hexpm.Repository.PackageReport Hexpm.Accounts.User ||--|{ Hexpm.Accounts.PasswordReset Hexpm.Accounts.User ||--|{ Hexpm.Repository.Release Hexpm.Accounts.User ||--o| Hexpm.Accounts.TFA @enduml ================================================ FILE: examples/plantuml/hexpm/Contexts-as-clusters.puml ================================================ @startuml set namespaceSeparator none hide circle hide methods skinparam linetype ortho skinparam defaultFontName Roboto Mono skinparam shadowing false namespace Ecto.Migration #f0f8ff { entity Ecto.Migration.SchemaMigration { version : integer -- inserted_at : naive_datetime } } namespace Hexpm.Accounts #8deeee { entity Hexpm.Accounts.AuditLog { id : id -- user_agent : string remote_ip : string action : string params : map user_id : id organization_id : id key_id : id inserted_at : utc_datetime_usec } entity Hexpm.Accounts.Email { id : id -- email : string verified : boolean primary : boolean public : boolean gravatar : boolean verification_key : string verification_expiry : utc_datetime_usec user_id : id inserted_at : utc_datetime_usec updated_at : utc_datetime_usec } entity Hexpm.Accounts.Key { id : id -- name : string secret_first : string secret_second : string public : boolean revoke_at : utc_datetime_usec inserted_at : utc_datetime_usec updated_at : utc_datetime_usec last_use : map user_id : id organization_id : id permissions : map } entity Hexpm.Accounts.Key.Use { id : binary_id -- used_at : utc_datetime_usec user_agent : string ip : string } entity Hexpm.Accounts.KeyPermission { id : binary_id -- domain : string resource : string } entity Hexpm.Accounts.Organization { id : id -- name : string billing_active : boolean billing_override : boolean trial_end : utc_datetime_usec inserted_at : utc_datetime_usec updated_at : utc_datetime_usec } entity Hexpm.Accounts.OrganizationUser { id : id -- role : string organization_id : id user_id : id inserted_at : utc_datetime_usec updated_at : utc_datetime_usec } entity Hexpm.Accounts.PasswordReset { id : id -- key : string primary_email : string user_id : id inserted_at : utc_datetime_usec } entity Hexpm.Accounts.RecoveryCode { id : binary_id -- code : string used_at : utc_datetime_usec } entity Hexpm.Accounts.Session { id : id -- token : binary data : map inserted_at : utc_datetime_usec updated_at : utc_datetime_usec } entity Hexpm.Accounts.TFA { secret : string tfa_enabled : boolean app_enabled : boolean recovery_codes : map } entity Hexpm.Accounts.User { id : id -- username : string full_name : string password : string service : boolean deactivated_at : utc_datetime_usec role : string inserted_at : utc_datetime_usec updated_at : utc_datetime_usec handles : map tfa : map organization_id : id } entity Hexpm.Accounts.UserHandles { id : binary_id -- twitter : string github : string elixirforum : string freenode : string slack : string } } namespace Hexpm.BlockAddress #fffafa { entity Hexpm.BlockAddress.Entry { id : id -- ip : string comment : string } } namespace Hexpm.Repository #eedfcc { entity Hexpm.Repository.Download { id : id -- package_id : id release_id : id downloads : integer day : date } entity Hexpm.Repository.Install { id : id -- hex : string elixirs : array } entity Hexpm.Repository.Package { id : id -- name : string docs_updated_at : utc_datetime_usec inserted_at : utc_datetime_usec updated_at : utc_datetime_usec repository_id : id meta : map } entity Hexpm.Repository.PackageDependant { id : id -- package_id : id name : string repo : string } entity Hexpm.Repository.PackageDownload { package_id : id view : string downloads : integer } entity Hexpm.Repository.PackageMetadata { id : binary_id -- description : string licenses : array links : map maintainers : array extra : map } entity Hexpm.Repository.PackageOwner { id : id -- level : string package_id : id user_id : id inserted_at : utc_datetime_usec updated_at : utc_datetime_usec } entity Hexpm.Repository.PackageReport { id : id -- state : string description : string author_id : id package_id : id inserted_at : utc_datetime_usec updated_at : utc_datetime_usec } entity Hexpm.Repository.PackageReportComment { id : id -- text : string inserted_at : utc_datetime_usec updated_at : utc_datetime_usec package_report_id : id author_id : id } entity Hexpm.Repository.PackageReportRelease { id : id -- release_id : id package_report_id : id inserted_at : utc_datetime_usec updated_at : utc_datetime_usec } entity Hexpm.Repository.Release { id : id -- version : string inner_checksum : binary outer_checksum : binary has_docs : boolean inserted_at : utc_datetime_usec updated_at : utc_datetime_usec package_id : id publisher_id : id meta : map retirement : map } entity Hexpm.Repository.ReleaseDownload { package_id : id release_id : id downloads : integer } entity Hexpm.Repository.ReleaseMetadata { id : binary_id -- app : string build_tools : array elixir : string } entity Hexpm.Repository.ReleaseRetirement { id : binary_id -- reason : string message : string } entity Hexpm.Repository.Repository { id : id -- name : string inserted_at : utc_datetime_usec updated_at : utc_datetime_usec organization_id : id } entity Hexpm.Repository.Requirement { id : id -- app : string requirement : string optional : boolean release_id : id dependency_id : id } } namespace Hexpm.ShortURLs #b4eeb4 { entity Hexpm.ShortURLs.ShortURL { id : id -- url : string short_code : string inserted_at : utc_datetime_usec } } Hexpm.Accounts.TFA ||--|{ Hexpm.Accounts.RecoveryCode Hexpm.Accounts.Key ||--|{ Hexpm.Accounts.AuditLog Hexpm.Accounts.Key ||--o| Hexpm.Accounts.Key.Use Hexpm.Accounts.Key ||--|{ Hexpm.Accounts.KeyPermission Hexpm.Accounts.Organization ||--|{ Hexpm.Accounts.AuditLog Hexpm.Accounts.Organization ||--|{ Hexpm.Accounts.Key Hexpm.Accounts.Organization ||--|{ Hexpm.Accounts.OrganizationUser Hexpm.Accounts.Organization ||--o| Hexpm.Repository.Repository Hexpm.Accounts.Organization ||--o| Hexpm.Accounts.User Hexpm.Repository.PackageReport ||--|{ Hexpm.Repository.PackageReportComment Hexpm.Repository.PackageReport ||--|{ Hexpm.Repository.PackageReportRelease Hexpm.Repository.Package ||--|{ Hexpm.Repository.Download Hexpm.Repository.Package ||--|{ Hexpm.Repository.PackageDependant Hexpm.Repository.Package ||--|{ Hexpm.Repository.PackageDownload Hexpm.Repository.Package ||--|{ Hexpm.Repository.PackageOwner Hexpm.Repository.Package ||--|{ Hexpm.Repository.PackageReport Hexpm.Repository.Package ||--|{ Hexpm.Repository.ReleaseDownload Hexpm.Repository.Package ||--|{ Hexpm.Repository.Release Hexpm.Repository.Package ||--|{ Hexpm.Repository.Requirement Hexpm.Repository.Package ||--o| Hexpm.Repository.PackageMetadata Hexpm.Repository.Release ||--|{ Hexpm.Repository.Download Hexpm.Repository.Release ||--|{ Hexpm.Repository.PackageReportRelease Hexpm.Repository.Release ||--o| Hexpm.Repository.ReleaseDownload Hexpm.Repository.Release ||--|{ Hexpm.Repository.Requirement Hexpm.Repository.Release ||--o| Hexpm.Repository.ReleaseMetadata Hexpm.Repository.Release ||--o| Hexpm.Repository.ReleaseRetirement Hexpm.Repository.Repository ||--|{ Hexpm.Repository.Package Hexpm.Accounts.User ||--o| Hexpm.Accounts.UserHandles Hexpm.Accounts.User ||--|{ Hexpm.Accounts.AuditLog Hexpm.Accounts.User ||--|{ Hexpm.Accounts.Email Hexpm.Accounts.User ||--|{ Hexpm.Accounts.Key Hexpm.Accounts.User ||--|{ Hexpm.Accounts.OrganizationUser Hexpm.Accounts.User ||--|{ Hexpm.Repository.PackageOwner Hexpm.Accounts.User ||--|{ Hexpm.Repository.PackageReportComment Hexpm.Accounts.User ||--|{ Hexpm.Repository.PackageReport Hexpm.Accounts.User ||--|{ Hexpm.Accounts.PasswordReset Hexpm.Accounts.User ||--|{ Hexpm.Repository.Release Hexpm.Accounts.User ||--o| Hexpm.Accounts.TFA @enduml ================================================ FILE: examples/plantuml/hexpm/Default.puml ================================================ @startuml set namespaceSeparator none hide circle hide methods skinparam linetype ortho skinparam defaultFontName Roboto Mono skinparam shadowing false entity Ecto.Migration.SchemaMigration { version : integer -- inserted_at : naive_datetime } entity Hexpm.Accounts.AuditLog { id : id -- user_agent : string remote_ip : string action : string params : map user_id : id organization_id : id key_id : id inserted_at : utc_datetime_usec } entity Hexpm.Accounts.Email { id : id -- email : string verified : boolean primary : boolean public : boolean gravatar : boolean verification_key : string verification_expiry : utc_datetime_usec user_id : id inserted_at : utc_datetime_usec updated_at : utc_datetime_usec } entity Hexpm.Accounts.Key { id : id -- name : string secret_first : string secret_second : string public : boolean revoke_at : utc_datetime_usec inserted_at : utc_datetime_usec updated_at : utc_datetime_usec last_use : map user_id : id organization_id : id permissions : map } entity Hexpm.Accounts.Key.Use { id : binary_id -- used_at : utc_datetime_usec user_agent : string ip : string } entity Hexpm.Accounts.KeyPermission { id : binary_id -- domain : string resource : string } entity Hexpm.Accounts.Organization { id : id -- name : string billing_active : boolean billing_override : boolean trial_end : utc_datetime_usec inserted_at : utc_datetime_usec updated_at : utc_datetime_usec } entity Hexpm.Accounts.OrganizationUser { id : id -- role : string organization_id : id user_id : id inserted_at : utc_datetime_usec updated_at : utc_datetime_usec } entity Hexpm.Accounts.PasswordReset { id : id -- key : string primary_email : string user_id : id inserted_at : utc_datetime_usec } entity Hexpm.Accounts.RecoveryCode { id : binary_id -- code : string used_at : utc_datetime_usec } entity Hexpm.Accounts.Session { id : id -- token : binary data : map inserted_at : utc_datetime_usec updated_at : utc_datetime_usec } entity Hexpm.Accounts.TFA { secret : string tfa_enabled : boolean app_enabled : boolean recovery_codes : map } entity Hexpm.Accounts.User { id : id -- username : string full_name : string password : string service : boolean deactivated_at : utc_datetime_usec role : string inserted_at : utc_datetime_usec updated_at : utc_datetime_usec handles : map tfa : map organization_id : id } entity Hexpm.Accounts.UserHandles { id : binary_id -- twitter : string github : string elixirforum : string freenode : string slack : string } entity Hexpm.BlockAddress.Entry { id : id -- ip : string comment : string } entity Hexpm.Repository.Download { id : id -- package_id : id release_id : id downloads : integer day : date } entity Hexpm.Repository.Install { id : id -- hex : string elixirs : array } entity Hexpm.Repository.Package { id : id -- name : string docs_updated_at : utc_datetime_usec inserted_at : utc_datetime_usec updated_at : utc_datetime_usec repository_id : id meta : map } entity Hexpm.Repository.PackageDependant { id : id -- package_id : id name : string repo : string } entity Hexpm.Repository.PackageDownload { package_id : id view : string downloads : integer } entity Hexpm.Repository.PackageMetadata { id : binary_id -- description : string licenses : array links : map maintainers : array extra : map } entity Hexpm.Repository.PackageOwner { id : id -- level : string package_id : id user_id : id inserted_at : utc_datetime_usec updated_at : utc_datetime_usec } entity Hexpm.Repository.PackageReport { id : id -- state : string description : string author_id : id package_id : id inserted_at : utc_datetime_usec updated_at : utc_datetime_usec } entity Hexpm.Repository.PackageReportComment { id : id -- text : string inserted_at : utc_datetime_usec updated_at : utc_datetime_usec package_report_id : id author_id : id } entity Hexpm.Repository.PackageReportRelease { id : id -- release_id : id package_report_id : id inserted_at : utc_datetime_usec updated_at : utc_datetime_usec } entity Hexpm.Repository.Release { id : id -- version : string inner_checksum : binary outer_checksum : binary has_docs : boolean inserted_at : utc_datetime_usec updated_at : utc_datetime_usec package_id : id publisher_id : id meta : map retirement : map } entity Hexpm.Repository.ReleaseDownload { package_id : id release_id : id downloads : integer } entity Hexpm.Repository.ReleaseMetadata { id : binary_id -- app : string build_tools : array elixir : string } entity Hexpm.Repository.ReleaseRetirement { id : binary_id -- reason : string message : string } entity Hexpm.Repository.Repository { id : id -- name : string inserted_at : utc_datetime_usec updated_at : utc_datetime_usec organization_id : id } entity Hexpm.Repository.Requirement { id : id -- app : string requirement : string optional : boolean release_id : id dependency_id : id } entity Hexpm.ShortURLs.ShortURL { id : id -- url : string short_code : string inserted_at : utc_datetime_usec } Hexpm.Accounts.TFA ||--|{ Hexpm.Accounts.RecoveryCode Hexpm.Accounts.Key ||--|{ Hexpm.Accounts.AuditLog Hexpm.Accounts.Key ||--o| Hexpm.Accounts.Key.Use Hexpm.Accounts.Key ||--|{ Hexpm.Accounts.KeyPermission Hexpm.Accounts.Organization ||--|{ Hexpm.Accounts.AuditLog Hexpm.Accounts.Organization ||--|{ Hexpm.Accounts.Key Hexpm.Accounts.Organization ||--|{ Hexpm.Accounts.OrganizationUser Hexpm.Accounts.Organization ||--o| Hexpm.Repository.Repository Hexpm.Accounts.Organization ||--o| Hexpm.Accounts.User Hexpm.Repository.PackageReport ||--|{ Hexpm.Repository.PackageReportComment Hexpm.Repository.PackageReport ||--|{ Hexpm.Repository.PackageReportRelease Hexpm.Repository.Package ||--|{ Hexpm.Repository.Download Hexpm.Repository.Package ||--|{ Hexpm.Repository.PackageDependant Hexpm.Repository.Package ||--|{ Hexpm.Repository.PackageDownload Hexpm.Repository.Package ||--|{ Hexpm.Repository.PackageOwner Hexpm.Repository.Package ||--|{ Hexpm.Repository.PackageReport Hexpm.Repository.Package ||--|{ Hexpm.Repository.ReleaseDownload Hexpm.Repository.Package ||--|{ Hexpm.Repository.Release Hexpm.Repository.Package ||--|{ Hexpm.Repository.Requirement Hexpm.Repository.Package ||--o| Hexpm.Repository.PackageMetadata Hexpm.Repository.Release ||--|{ Hexpm.Repository.Download Hexpm.Repository.Release ||--|{ Hexpm.Repository.PackageReportRelease Hexpm.Repository.Release ||--o| Hexpm.Repository.ReleaseDownload Hexpm.Repository.Release ||--|{ Hexpm.Repository.Requirement Hexpm.Repository.Release ||--o| Hexpm.Repository.ReleaseMetadata Hexpm.Repository.Release ||--o| Hexpm.Repository.ReleaseRetirement Hexpm.Repository.Repository ||--|{ Hexpm.Repository.Package Hexpm.Accounts.User ||--o| Hexpm.Accounts.UserHandles Hexpm.Accounts.User ||--|{ Hexpm.Accounts.AuditLog Hexpm.Accounts.User ||--|{ Hexpm.Accounts.Email Hexpm.Accounts.User ||--|{ Hexpm.Accounts.Key Hexpm.Accounts.User ||--|{ Hexpm.Accounts.OrganizationUser Hexpm.Accounts.User ||--|{ Hexpm.Repository.PackageOwner Hexpm.Accounts.User ||--|{ Hexpm.Repository.PackageReportComment Hexpm.Accounts.User ||--|{ Hexpm.Repository.PackageReport Hexpm.Accounts.User ||--|{ Hexpm.Accounts.PasswordReset Hexpm.Accounts.User ||--|{ Hexpm.Repository.Release Hexpm.Accounts.User ||--o| Hexpm.Accounts.TFA @enduml ================================================ FILE: examples/plantuml/hexpm/Only-embedded-schemas.puml ================================================ @startuml set namespaceSeparator none hide circle hide methods skinparam linetype ortho skinparam defaultFontName Roboto Mono skinparam shadowing false entity Hexpm.Accounts.Key.Use { id : binary_id -- used_at : utc_datetime_usec user_agent : string ip : string } entity Hexpm.Accounts.KeyPermission { id : binary_id -- domain : string resource : string } entity Hexpm.Accounts.RecoveryCode { id : binary_id -- code : string used_at : utc_datetime_usec } entity Hexpm.Accounts.TFA { secret : string tfa_enabled : boolean app_enabled : boolean recovery_codes : map } entity Hexpm.Accounts.UserHandles { id : binary_id -- twitter : string github : string elixirforum : string freenode : string slack : string } entity Hexpm.Repository.PackageMetadata { id : binary_id -- description : string licenses : array links : map maintainers : array extra : map } entity Hexpm.Repository.ReleaseMetadata { id : binary_id -- app : string build_tools : array elixir : string } entity Hexpm.Repository.ReleaseRetirement { id : binary_id -- reason : string message : string } Hexpm.Accounts.TFA ||--|{ Hexpm.Accounts.RecoveryCode @enduml ================================================ FILE: examples/plantuml/hexpm/Only-selected-cluster-Accounts-context.puml ================================================ @startuml set namespaceSeparator none hide circle hide methods skinparam linetype ortho skinparam defaultFontName Roboto Mono skinparam shadowing false namespace Hexpm.Accounts #8deeee { entity Hexpm.Accounts.AuditLog { id : id -- user_agent : string remote_ip : string action : string params : map user_id : id organization_id : id key_id : id inserted_at : utc_datetime_usec } entity Hexpm.Accounts.Email { id : id -- email : string verified : boolean primary : boolean public : boolean gravatar : boolean verification_key : string verification_expiry : utc_datetime_usec user_id : id inserted_at : utc_datetime_usec updated_at : utc_datetime_usec } entity Hexpm.Accounts.Key { id : id -- name : string secret_first : string secret_second : string public : boolean revoke_at : utc_datetime_usec inserted_at : utc_datetime_usec updated_at : utc_datetime_usec last_use : map user_id : id organization_id : id permissions : map } entity Hexpm.Accounts.Key.Use { id : binary_id -- used_at : utc_datetime_usec user_agent : string ip : string } entity Hexpm.Accounts.KeyPermission { id : binary_id -- domain : string resource : string } entity Hexpm.Accounts.Organization { id : id -- name : string billing_active : boolean billing_override : boolean trial_end : utc_datetime_usec inserted_at : utc_datetime_usec updated_at : utc_datetime_usec } entity Hexpm.Accounts.OrganizationUser { id : id -- role : string organization_id : id user_id : id inserted_at : utc_datetime_usec updated_at : utc_datetime_usec } entity Hexpm.Accounts.PasswordReset { id : id -- key : string primary_email : string user_id : id inserted_at : utc_datetime_usec } entity Hexpm.Accounts.RecoveryCode { id : binary_id -- code : string used_at : utc_datetime_usec } entity Hexpm.Accounts.Session { id : id -- token : binary data : map inserted_at : utc_datetime_usec updated_at : utc_datetime_usec } entity Hexpm.Accounts.TFA { secret : string tfa_enabled : boolean app_enabled : boolean recovery_codes : map } entity Hexpm.Accounts.User { id : id -- username : string full_name : string password : string service : boolean deactivated_at : utc_datetime_usec role : string inserted_at : utc_datetime_usec updated_at : utc_datetime_usec handles : map tfa : map organization_id : id } entity Hexpm.Accounts.UserHandles { id : binary_id -- twitter : string github : string elixirforum : string freenode : string slack : string } } Hexpm.Accounts.TFA ||--|{ Hexpm.Accounts.RecoveryCode Hexpm.Accounts.Key ||--|{ Hexpm.Accounts.AuditLog Hexpm.Accounts.Key ||--o| Hexpm.Accounts.Key.Use Hexpm.Accounts.Key ||--|{ Hexpm.Accounts.KeyPermission Hexpm.Accounts.Organization ||--|{ Hexpm.Accounts.AuditLog Hexpm.Accounts.Organization ||--|{ Hexpm.Accounts.Key Hexpm.Accounts.Organization ||--|{ Hexpm.Accounts.OrganizationUser Hexpm.Accounts.Organization ||--o| Hexpm.Accounts.User Hexpm.Accounts.User ||--o| Hexpm.Accounts.UserHandles Hexpm.Accounts.User ||--|{ Hexpm.Accounts.AuditLog Hexpm.Accounts.User ||--|{ Hexpm.Accounts.Email Hexpm.Accounts.User ||--|{ Hexpm.Accounts.Key Hexpm.Accounts.User ||--|{ Hexpm.Accounts.OrganizationUser Hexpm.Accounts.User ||--|{ Hexpm.Accounts.PasswordReset Hexpm.Accounts.User ||--o| Hexpm.Accounts.TFA @enduml ================================================ FILE: examples/plantuml/plausible-analytics/Contexts-as-clusters-no-fields.puml ================================================ @startuml set namespaceSeparator none hide circle hide methods hide fields skinparam linetype ortho skinparam defaultFontName Roboto Mono skinparam shadowing false namespace Ecto.Migration #f0f8ff { entity Ecto.Migration.SchemaMigration } namespace FunWithFlags.Store #f0ffff { entity FunWithFlags.Store.Persistent.Ecto.Record } namespace Oban #f0ffff { entity Oban.Job } namespace Plausible #eee5de { entity Plausible.ClickhouseEventV2 entity Plausible.ClickhouseSessionV2 entity Plausible.Funnel entity Plausible.Goal entity Plausible.Site } namespace Plausible.Auth #ffefd5 { entity Plausible.Auth.ApiKey entity Plausible.Auth.EmailActivationCode entity Plausible.Auth.GracePeriod entity Plausible.Auth.Invitation entity Plausible.Auth.TOTP.RecoveryCode entity Plausible.Auth.User } namespace Plausible.Billing #f0ffff { entity Plausible.Billing.EnterprisePlan entity Plausible.Billing.Plan entity Plausible.Billing.Subscription } namespace Plausible.DataMigration #8deeee { entity Plausible.DataMigration.NumericIDs.DomainsLookup } namespace Plausible.Funnel #fffafa { entity Plausible.Funnel.Step } namespace Plausible.Imported #eedfcc { entity Plausible.Imported.Browser entity Plausible.Imported.Device entity Plausible.Imported.EntryPage entity Plausible.Imported.ExitPage entity Plausible.Imported.Location entity Plausible.Imported.OperatingSystem entity Plausible.Imported.Page entity Plausible.Imported.SiteImport entity Plausible.Imported.Source entity Plausible.Imported.Visitor } namespace Plausible.Ingestion #8deeee { entity Plausible.Ingestion.Counters.Record entity Plausible.Ingestion.Request } namespace Plausible.Plugins #eee5de { entity Plausible.Plugins.API.Token } namespace Plausible.Shield #8deeee { entity Plausible.Shield.CountryRule entity Plausible.Shield.HostnameRule entity Plausible.Shield.IPRule entity Plausible.Shield.PageRule } namespace Plausible.Site #f0f8ff { entity Plausible.Site.GoogleAuth entity Plausible.Site.ImportedData entity Plausible.Site.Membership entity Plausible.Site.MonthlyReport entity Plausible.Site.SharedLink entity Plausible.Site.SpikeNotification entity Plausible.Site.UserPreference entity Plausible.Site.WeeklyReport } Plausible.Funnel ||--|{ Plausible.Funnel.Step Plausible.Goal ||--|{ Plausible.Funnel.Step Plausible.Site ||--|{ Plausible.Funnel Plausible.Site ||--|{ Plausible.Goal Plausible.Site ||--o| Plausible.Site.GoogleAuth Plausible.Site ||--|{ Plausible.Auth.Invitation Plausible.Site ||--o| Plausible.Site.MonthlyReport Plausible.Site ||--|{ Plausible.Plugins.API.Token Plausible.Site ||--|{ Plausible.Site.SharedLink Plausible.Site ||--|{ Plausible.Shield.CountryRule Plausible.Site ||--|{ Plausible.Shield.HostnameRule Plausible.Site ||--|{ Plausible.Shield.IPRule Plausible.Site ||--|{ Plausible.Shield.PageRule Plausible.Site ||--|{ Plausible.Imported.SiteImport Plausible.Site ||--o| Plausible.Site.Membership Plausible.Site ||--|{ Plausible.Site.UserPreference Plausible.Site ||--o| Plausible.Site.SpikeNotification Plausible.Site ||--o| Plausible.Site.WeeklyReport Plausible.Site ||--o| Plausible.Site.ImportedData Plausible.Auth.User ||--o| Plausible.Auth.GracePeriod Plausible.Auth.User ||--|{ Plausible.Auth.ApiKey Plausible.Auth.User ||--|{ Plausible.Auth.EmailActivationCode Plausible.Auth.User ||--o| Plausible.Billing.EnterprisePlan Plausible.Auth.User ||--o| Plausible.Site.GoogleAuth Plausible.Auth.User ||--|{ Plausible.Auth.Invitation Plausible.Auth.User ||--|{ Plausible.Imported.SiteImport Plausible.Auth.User ||--|{ Plausible.Site.Membership Plausible.Auth.User ||--|{ Plausible.Site.UserPreference Plausible.Auth.User ||--o| Plausible.Billing.Subscription Plausible.Auth.User ||--|{ Plausible.Auth.TOTP.RecoveryCode @enduml ================================================ FILE: examples/plantuml/plausible-analytics/Contexts-as-clusters.puml ================================================ @startuml set namespaceSeparator none hide circle hide methods skinparam linetype ortho skinparam defaultFontName Roboto Mono skinparam shadowing false namespace Ecto.Migration #f0f8ff { entity Ecto.Migration.SchemaMigration { version : integer -- inserted_at : naive_datetime } } namespace FunWithFlags.Store #f0ffff { entity FunWithFlags.Store.Persistent.Ecto.Record { id : id -- flag_name : string gate_type : string target : string enabled : boolean } } namespace Oban #f0ffff { entity Oban.Job { id : id -- state : string queue : string worker : string args : map meta : map tags : array errors : array attempt : integer attempted_by : array max_attempts : integer priority : integer attempted_at : utc_datetime_usec cancelled_at : utc_datetime_usec completed_at : utc_datetime_usec discarded_at : utc_datetime_usec inserted_at : utc_datetime_usec scheduled_at : utc_datetime_usec } } namespace Plausible #eee5de { entity Plausible.ClickhouseEventV2 { name : unknown site_id : unknown hostname : string pathname : string user_id : unknown session_id : unknown timestamp : naive_datetime meta.key : array meta.value : array revenue_source_amount : unknown revenue_source_currency : unknown revenue_reporting_amount : unknown revenue_reporting_currency : unknown referrer : string referrer_source : string utm_medium : string utm_source : string utm_campaign : string utm_content : string utm_term : string country_code : unknown subdivision1_code : unknown subdivision2_code : unknown city_geoname_id : unknown screen_size : unknown operating_system : unknown operating_system_version : unknown browser : unknown browser_version : unknown } entity Plausible.ClickhouseSessionV2 { hostname : string site_id : unknown user_id : unknown session_id : unknown start : naive_datetime duration : unknown is_bounce : unknown entry_page : string exit_page : string exit_page_hostname : string pageviews : unknown events : unknown sign : unknown entry_meta.key : array entry_meta.value : array utm_medium : string utm_source : string utm_campaign : string utm_content : string utm_term : string referrer : string referrer_source : string country_code : unknown subdivision1_code : unknown subdivision2_code : unknown city_geoname_id : unknown screen_size : unknown operating_system : unknown operating_system_version : unknown browser : unknown browser_version : unknown timestamp : naive_datetime transferred_from : string } entity Plausible.Funnel { id : id -- name : string site_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Goal { id : id -- event_name : string page_path : string currency : enum(XBD,BYN,HKD,XOF,SOS,ARS,EGP,XDR,GMD,...) site_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Site { id : id -- domain : string timezone : string public : boolean locked : boolean stats_start_date : date native_stats_start_at : naive_datetime allowed_event_props : array conversions_enabled : boolean props_enabled : boolean funnels_enabled : boolean ingest_rate_limit_scale_seconds : integer ingest_rate_limit_threshold : integer domain_changed_from : string domain_changed_at : naive_datetime imported_data : map inserted_at : naive_datetime updated_at : naive_datetime } } namespace Plausible.Auth #ffefd5 { entity Plausible.Auth.ApiKey { id : id -- name : string scopes : array hourly_request_limit : integer key_hash : string key_prefix : string user_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Auth.EmailActivationCode { id : id -- code : string issued_at : naive_datetime user_id : id } entity Plausible.Auth.GracePeriod { id : binary_id -- end_date : date is_over : boolean manual_lock : boolean } entity Plausible.Auth.Invitation { id : id -- invitation_id : string email : string role : enum(owner,admin,viewer) inviter_id : id site_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Auth.TOTP.RecoveryCode { id : id -- code_digest : string user_id : id inserted_at : naive_datetime } entity Plausible.Auth.User { id : id -- email : string password_hash : string name : string last_seen : naive_datetime trial_expiry_date : date theme : enum(system,light,dark) email_verified : boolean previous_email : string accept_traffic_until : date allow_next_upgrade_override : boolean totp_enabled : boolean totp_secret : binary totp_token : string totp_last_used_at : naive_datetime grace_period : map inserted_at : naive_datetime updated_at : naive_datetime } } namespace Plausible.Billing #f0ffff { entity Plausible.Billing.EnterprisePlan { id : id -- paddle_plan_id : string billing_interval : enum(yearly,monthly) monthly_pageview_limit : integer site_limit : integer team_member_limit : integer features : array hourly_api_request_limit : integer user_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Billing.Plan { id : binary_id -- generation : integer kind : enum(growth,business) features : array monthly_pageview_limit : integer site_limit : integer team_member_limit : integer volume : string data_retention_in_years : integer monthly_cost : string monthly_product_id : string yearly_cost : string yearly_product_id : string } entity Plausible.Billing.Subscription { id : id -- paddle_subscription_id : string paddle_plan_id : string update_url : string cancel_url : string status : enum(active,deleted,past_due,paused) next_bill_amount : string next_bill_date : date last_bill_date : date currency_code : string user_id : id inserted_at : naive_datetime updated_at : naive_datetime } } namespace Plausible.DataMigration #8deeee { entity Plausible.DataMigration.NumericIDs.DomainsLookup { site_id : unknown domain : string } } namespace Plausible.Funnel #fffafa { entity Plausible.Funnel.Step { id : id -- step_order : integer funnel_id : id goal_id : id inserted_at : naive_datetime updated_at : naive_datetime } } namespace Plausible.Imported #eedfcc { entity Plausible.Imported.Browser { site_id : unknown import_id : unknown date : date browser : string browser_version : string visitors : unknown visits : unknown visit_duration : unknown pageviews : unknown bounces : unknown } entity Plausible.Imported.Device { site_id : unknown import_id : unknown date : date device : string visitors : unknown visits : unknown visit_duration : unknown pageviews : unknown bounces : unknown } entity Plausible.Imported.EntryPage { site_id : unknown import_id : unknown date : date entry_page : string visitors : unknown entrances : unknown visit_duration : unknown pageviews : unknown bounces : unknown } entity Plausible.Imported.ExitPage { site_id : unknown import_id : unknown date : date exit_page : string exits : unknown visitors : unknown visit_duration : unknown pageviews : unknown bounces : unknown } entity Plausible.Imported.Location { site_id : unknown import_id : unknown date : date country : string region : string city : unknown visitors : unknown visits : unknown visit_duration : unknown pageviews : unknown bounces : unknown } entity Plausible.Imported.OperatingSystem { site_id : unknown import_id : unknown date : date operating_system : string operating_system_version : string visitors : unknown visits : unknown visit_duration : unknown pageviews : unknown bounces : unknown } entity Plausible.Imported.Page { site_id : unknown import_id : unknown date : date hostname : string page : string visits : unknown visitors : unknown active_visitors : unknown pageviews : unknown exits : unknown time_on_page : unknown } entity Plausible.Imported.SiteImport { id : id -- start_date : date end_date : date label : string source : enum(noop,csv,universal_analytics,google_analytics_4) status : enum(pending,failed,completed,importing) legacy : boolean site_id : id imported_by_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Imported.Source { site_id : unknown import_id : unknown date : date source : string referrer : string utm_source : string utm_medium : string utm_campaign : string utm_content : string utm_term : string visitors : unknown visits : unknown visit_duration : unknown pageviews : unknown bounces : unknown } entity Plausible.Imported.Visitor { site_id : unknown import_id : unknown date : date visitors : unknown pageviews : unknown bounces : unknown visits : unknown visit_duration : unknown } } namespace Plausible.Ingestion #8deeee { entity Plausible.Ingestion.Counters.Record { event_timebucket : utc_datetime site_id : unknown domain : unknown metric : unknown value : unknown } entity Plausible.Ingestion.Request { remote_ip : string user_agent : string event_name : string uri : map hostname : string referrer : string domains : array ip_classification : string hash_mode : integer pathname : string props : map revenue_source : map query_params : map timestamp : naive_datetime } } namespace Plausible.Plugins #eee5de { entity Plausible.Plugins.API.Token { id : binary_id -- inserted_at : naive_datetime updated_at : naive_datetime token_hash : binary description : string hint : string last_used_at : naive_datetime site_id : id } } namespace Plausible.Shield #8deeee { entity Plausible.Shield.CountryRule { id : binary_id -- site_id : id country_code : string action : enum(allow,deny) added_by : string inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Shield.HostnameRule { id : binary_id -- site_id : id hostname : string hostname_pattern : string action : enum(allow,deny) added_by : string inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Shield.IPRule { id : binary_id -- site_id : id inet : inet action : enum(allow,deny) description : string added_by : string inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Shield.PageRule { id : binary_id -- site_id : id page_path : string page_path_pattern : string action : enum(allow,deny) added_by : string inserted_at : naive_datetime updated_at : naive_datetime } } namespace Plausible.Site #f0f8ff { entity Plausible.Site.GoogleAuth { id : id -- email : string property : string refresh_token : string access_token : string expires : naive_datetime user_id : id site_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Site.ImportedData { id : binary_id -- start_date : date end_date : date source : string status : string } entity Plausible.Site.Membership { id : id -- role : enum(owner,admin,viewer) site_id : id user_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Site.MonthlyReport { id : id -- recipients : array site_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Site.SharedLink { id : id -- site_id : id name : string slug : string password_hash : string inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Site.SpikeNotification { id : id -- recipients : array threshold : integer last_sent : naive_datetime site_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Site.UserPreference { id : id -- pinned_at : naive_datetime user_id : id site_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Site.WeeklyReport { id : id -- recipients : array site_id : id inserted_at : naive_datetime updated_at : naive_datetime } } Plausible.Funnel ||--|{ Plausible.Funnel.Step Plausible.Goal ||--|{ Plausible.Funnel.Step Plausible.Site ||--|{ Plausible.Funnel Plausible.Site ||--|{ Plausible.Goal Plausible.Site ||--o| Plausible.Site.GoogleAuth Plausible.Site ||--|{ Plausible.Auth.Invitation Plausible.Site ||--o| Plausible.Site.MonthlyReport Plausible.Site ||--|{ Plausible.Plugins.API.Token Plausible.Site ||--|{ Plausible.Site.SharedLink Plausible.Site ||--|{ Plausible.Shield.CountryRule Plausible.Site ||--|{ Plausible.Shield.HostnameRule Plausible.Site ||--|{ Plausible.Shield.IPRule Plausible.Site ||--|{ Plausible.Shield.PageRule Plausible.Site ||--|{ Plausible.Imported.SiteImport Plausible.Site ||--o| Plausible.Site.Membership Plausible.Site ||--|{ Plausible.Site.UserPreference Plausible.Site ||--o| Plausible.Site.SpikeNotification Plausible.Site ||--o| Plausible.Site.WeeklyReport Plausible.Site ||--o| Plausible.Site.ImportedData Plausible.Auth.User ||--o| Plausible.Auth.GracePeriod Plausible.Auth.User ||--|{ Plausible.Auth.ApiKey Plausible.Auth.User ||--|{ Plausible.Auth.EmailActivationCode Plausible.Auth.User ||--o| Plausible.Billing.EnterprisePlan Plausible.Auth.User ||--o| Plausible.Site.GoogleAuth Plausible.Auth.User ||--|{ Plausible.Auth.Invitation Plausible.Auth.User ||--|{ Plausible.Imported.SiteImport Plausible.Auth.User ||--|{ Plausible.Site.Membership Plausible.Auth.User ||--|{ Plausible.Site.UserPreference Plausible.Auth.User ||--o| Plausible.Billing.Subscription Plausible.Auth.User ||--|{ Plausible.Auth.TOTP.RecoveryCode @enduml ================================================ FILE: examples/plantuml/plausible-analytics/Default.puml ================================================ @startuml set namespaceSeparator none hide circle hide methods skinparam linetype ortho skinparam defaultFontName Roboto Mono skinparam shadowing false entity Ecto.Migration.SchemaMigration { version : integer -- inserted_at : naive_datetime } entity FunWithFlags.Store.Persistent.Ecto.Record { id : id -- flag_name : string gate_type : string target : string enabled : boolean } entity Oban.Job { id : id -- state : string queue : string worker : string args : map meta : map tags : array errors : array attempt : integer attempted_by : array max_attempts : integer priority : integer attempted_at : utc_datetime_usec cancelled_at : utc_datetime_usec completed_at : utc_datetime_usec discarded_at : utc_datetime_usec inserted_at : utc_datetime_usec scheduled_at : utc_datetime_usec } entity Plausible.Auth.ApiKey { id : id -- name : string scopes : array hourly_request_limit : integer key_hash : string key_prefix : string user_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Auth.EmailActivationCode { id : id -- code : string issued_at : naive_datetime user_id : id } entity Plausible.Auth.GracePeriod { id : binary_id -- end_date : date is_over : boolean manual_lock : boolean } entity Plausible.Auth.Invitation { id : id -- invitation_id : string email : string role : enum(owner,admin,viewer) inviter_id : id site_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Auth.TOTP.RecoveryCode { id : id -- code_digest : string user_id : id inserted_at : naive_datetime } entity Plausible.Auth.User { id : id -- email : string password_hash : string name : string last_seen : naive_datetime trial_expiry_date : date theme : enum(system,light,dark) email_verified : boolean previous_email : string accept_traffic_until : date allow_next_upgrade_override : boolean totp_enabled : boolean totp_secret : binary totp_token : string totp_last_used_at : naive_datetime grace_period : map inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Billing.EnterprisePlan { id : id -- paddle_plan_id : string billing_interval : enum(yearly,monthly) monthly_pageview_limit : integer site_limit : integer team_member_limit : integer features : array hourly_api_request_limit : integer user_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Billing.Plan { id : binary_id -- generation : integer kind : enum(growth,business) features : array monthly_pageview_limit : integer site_limit : integer team_member_limit : integer volume : string data_retention_in_years : integer monthly_cost : string monthly_product_id : string yearly_cost : string yearly_product_id : string } entity Plausible.Billing.Subscription { id : id -- paddle_subscription_id : string paddle_plan_id : string update_url : string cancel_url : string status : enum(active,deleted,past_due,paused) next_bill_amount : string next_bill_date : date last_bill_date : date currency_code : string user_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.ClickhouseEventV2 { name : unknown site_id : unknown hostname : string pathname : string user_id : unknown session_id : unknown timestamp : naive_datetime meta.key : array meta.value : array revenue_source_amount : unknown revenue_source_currency : unknown revenue_reporting_amount : unknown revenue_reporting_currency : unknown referrer : string referrer_source : string utm_medium : string utm_source : string utm_campaign : string utm_content : string utm_term : string country_code : unknown subdivision1_code : unknown subdivision2_code : unknown city_geoname_id : unknown screen_size : unknown operating_system : unknown operating_system_version : unknown browser : unknown browser_version : unknown } entity Plausible.ClickhouseSessionV2 { hostname : string site_id : unknown user_id : unknown session_id : unknown start : naive_datetime duration : unknown is_bounce : unknown entry_page : string exit_page : string exit_page_hostname : string pageviews : unknown events : unknown sign : unknown entry_meta.key : array entry_meta.value : array utm_medium : string utm_source : string utm_campaign : string utm_content : string utm_term : string referrer : string referrer_source : string country_code : unknown subdivision1_code : unknown subdivision2_code : unknown city_geoname_id : unknown screen_size : unknown operating_system : unknown operating_system_version : unknown browser : unknown browser_version : unknown timestamp : naive_datetime transferred_from : string } entity Plausible.DataMigration.NumericIDs.DomainsLookup { site_id : unknown domain : string } entity Plausible.Funnel { id : id -- name : string site_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Funnel.Step { id : id -- step_order : integer funnel_id : id goal_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Goal { id : id -- event_name : string page_path : string currency : enum(KMF,AUD,SAR,BWP,BBD,EGP,YER,CDF,IQD,...) site_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Imported.Browser { site_id : unknown import_id : unknown date : date browser : string browser_version : string visitors : unknown visits : unknown visit_duration : unknown pageviews : unknown bounces : unknown } entity Plausible.Imported.Device { site_id : unknown import_id : unknown date : date device : string visitors : unknown visits : unknown visit_duration : unknown pageviews : unknown bounces : unknown } entity Plausible.Imported.EntryPage { site_id : unknown import_id : unknown date : date entry_page : string visitors : unknown entrances : unknown visit_duration : unknown pageviews : unknown bounces : unknown } entity Plausible.Imported.ExitPage { site_id : unknown import_id : unknown date : date exit_page : string exits : unknown visitors : unknown visit_duration : unknown pageviews : unknown bounces : unknown } entity Plausible.Imported.Location { site_id : unknown import_id : unknown date : date country : string region : string city : unknown visitors : unknown visits : unknown visit_duration : unknown pageviews : unknown bounces : unknown } entity Plausible.Imported.OperatingSystem { site_id : unknown import_id : unknown date : date operating_system : string operating_system_version : string visitors : unknown visits : unknown visit_duration : unknown pageviews : unknown bounces : unknown } entity Plausible.Imported.Page { site_id : unknown import_id : unknown date : date hostname : string page : string visits : unknown visitors : unknown active_visitors : unknown pageviews : unknown exits : unknown time_on_page : unknown } entity Plausible.Imported.SiteImport { id : id -- start_date : date end_date : date label : string source : enum(noop,csv,universal_analytics,google_analytics_4) status : enum(pending,failed,completed,importing) legacy : boolean site_id : id imported_by_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Imported.Source { site_id : unknown import_id : unknown date : date source : string referrer : string utm_source : string utm_medium : string utm_campaign : string utm_content : string utm_term : string visitors : unknown visits : unknown visit_duration : unknown pageviews : unknown bounces : unknown } entity Plausible.Imported.Visitor { site_id : unknown import_id : unknown date : date visitors : unknown pageviews : unknown bounces : unknown visits : unknown visit_duration : unknown } entity Plausible.Ingestion.Counters.Record { event_timebucket : utc_datetime site_id : unknown domain : unknown metric : unknown value : unknown } entity Plausible.Ingestion.Request { remote_ip : string user_agent : string event_name : string uri : map hostname : string referrer : string domains : array ip_classification : string hash_mode : integer pathname : string props : map revenue_source : map query_params : map timestamp : naive_datetime } entity Plausible.Plugins.API.Token { id : binary_id -- inserted_at : naive_datetime updated_at : naive_datetime token_hash : binary description : string hint : string last_used_at : naive_datetime site_id : id } entity Plausible.Shield.CountryRule { id : binary_id -- site_id : id country_code : string action : enum(allow,deny) added_by : string inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Shield.HostnameRule { id : binary_id -- site_id : id hostname : string hostname_pattern : string action : enum(allow,deny) added_by : string inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Shield.IPRule { id : binary_id -- site_id : id inet : inet action : enum(allow,deny) description : string added_by : string inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Shield.PageRule { id : binary_id -- site_id : id page_path : string page_path_pattern : string action : enum(allow,deny) added_by : string inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Site { id : id -- domain : string timezone : string public : boolean locked : boolean stats_start_date : date native_stats_start_at : naive_datetime allowed_event_props : array conversions_enabled : boolean props_enabled : boolean funnels_enabled : boolean ingest_rate_limit_scale_seconds : integer ingest_rate_limit_threshold : integer domain_changed_from : string domain_changed_at : naive_datetime imported_data : map inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Site.GoogleAuth { id : id -- email : string property : string refresh_token : string access_token : string expires : naive_datetime user_id : id site_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Site.ImportedData { id : binary_id -- start_date : date end_date : date source : string status : string } entity Plausible.Site.Membership { id : id -- role : enum(owner,admin,viewer) site_id : id user_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Site.MonthlyReport { id : id -- recipients : array site_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Site.SharedLink { id : id -- site_id : id name : string slug : string password_hash : string inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Site.SpikeNotification { id : id -- recipients : array threshold : integer last_sent : naive_datetime site_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Site.UserPreference { id : id -- pinned_at : naive_datetime user_id : id site_id : id inserted_at : naive_datetime updated_at : naive_datetime } entity Plausible.Site.WeeklyReport { id : id -- recipients : array site_id : id inserted_at : naive_datetime updated_at : naive_datetime } Plausible.Funnel ||--|{ Plausible.Funnel.Step Plausible.Goal ||--|{ Plausible.Funnel.Step Plausible.Site ||--|{ Plausible.Funnel Plausible.Site ||--|{ Plausible.Goal Plausible.Site ||--o| Plausible.Site.GoogleAuth Plausible.Site ||--|{ Plausible.Auth.Invitation Plausible.Site ||--o| Plausible.Site.MonthlyReport Plausible.Site ||--|{ Plausible.Plugins.API.Token Plausible.Site ||--|{ Plausible.Site.SharedLink Plausible.Site ||--|{ Plausible.Shield.CountryRule Plausible.Site ||--|{ Plausible.Shield.HostnameRule Plausible.Site ||--|{ Plausible.Shield.IPRule Plausible.Site ||--|{ Plausible.Shield.PageRule Plausible.Site ||--|{ Plausible.Imported.SiteImport Plausible.Site ||--o| Plausible.Site.Membership Plausible.Site ||--|{ Plausible.Site.UserPreference Plausible.Site ||--o| Plausible.Site.SpikeNotification Plausible.Site ||--o| Plausible.Site.WeeklyReport Plausible.Site ||--o| Plausible.Site.ImportedData Plausible.Auth.User ||--o| Plausible.Auth.GracePeriod Plausible.Auth.User ||--|{ Plausible.Auth.ApiKey Plausible.Auth.User ||--|{ Plausible.Auth.EmailActivationCode Plausible.Auth.User ||--o| Plausible.Billing.EnterprisePlan Plausible.Auth.User ||--o| Plausible.Site.GoogleAuth Plausible.Auth.User ||--|{ Plausible.Auth.Invitation Plausible.Auth.User ||--|{ Plausible.Imported.SiteImport Plausible.Auth.User ||--|{ Plausible.Site.Membership Plausible.Auth.User ||--|{ Plausible.Site.UserPreference Plausible.Auth.User ||--o| Plausible.Billing.Subscription Plausible.Auth.User ||--|{ Plausible.Auth.TOTP.RecoveryCode @enduml ================================================ FILE: examples/quick_dbd/changelog.com/Default.qdbd ================================================ episodes --- id integer PK slug varchar guid varchar title varchar subtitle varchar type integer featured boolean highlight varchar subhighlight varchar summary varchar notes varchar doc_url varchar socialize_url varchar published boolean published_at timestamp recorded_at timestamp recorded_live boolean youtube_id varchar cover varchar audio_file varchar audio_bytes integer audio_duration integer audio_chapters jsonb plusplus_file varchar plusplus_bytes integer plusplus_duration integer plusplus_chapters jsonb download_count float import_count float reach_count integer email_subject varchar email_teaser varchar email_content varchar email_sends integer email_opens integer transcript array podcast_id integer FK >- podcasts.id request_id integer FK - episode_requests.id inserted_at timestamp updated_at timestamp episode_guests --- id integer PK position integer thanks boolean discount_code varchar episode_id integer FK >- episodes.id person_id integer FK >- people.id inserted_at timestamp updated_at timestamp episode_hosts --- id integer PK position integer person_id integer FK >- people.id episode_id integer FK >- episodes.id inserted_at timestamp updated_at timestamp episode_requests --- id integer PK status integer hosts varchar guests varchar topics varchar pitch varchar pronunciation varchar message varchar podcast_id integer FK >- podcasts.id submitter_id integer FK >- people.id inserted_at timestamp updated_at timestamp episode_sponsors --- id integer PK position integer title varchar link_url varchar description varchar starts_at float ends_at float episode_id integer FK >- episodes.id sponsor_id integer FK >- sponsors.id inserted_at timestamp updated_at timestamp episode_stats --- id integer PK date date episode_bytes integer total_bytes integer downloads float uniques integer demographics jsonb episode_id integer FK >- episodes.id podcast_id integer FK >- podcasts.id inserted_at timestamp updated_at timestamp episode_topics --- id integer PK position integer topic_id integer FK >- topics.id episode_id integer FK >- episodes.id inserted_at timestamp updated_at timestamp feeds --- id integer PK name varchar slug varchar description varchar title_format varchar plusplus boolean autosub boolean starts_at timestamp cover varchar podcast_ids array person_ids array owner_id integer FK >- people.id inserted_at timestamp updated_at timestamp news_ads --- id integer PK url varchar headline varchar story varchar image varchar active boolean newsletter boolean impression_count integer click_count integer sponsorship_id integer FK >- news_sponsorships.id inserted_at timestamp updated_at timestamp news_issues --- id integer PK slug varchar note varchar teaser varchar published boolean published_at timestamp inserted_at timestamp updated_at timestamp news_issue_ads --- id integer PK position integer image boolean ad_id integer FK >- news_ads.id issue_id integer FK >- news_issues.id inserted_at timestamp updated_at timestamp news_issue_items --- id integer PK position integer image boolean issue_id integer FK >- news_issues.id item_id integer FK >- news_items.id inserted_at timestamp updated_at timestamp news_items --- id integer PK status integer type integer url varchar headline varchar story varchar image varchar object_id varchar feed_only boolean pinned boolean published_at timestamp refreshed_at timestamp impression_count integer click_count integer message varchar author_id integer FK >- people.id logger_id integer FK >- people.id submitter_id integer FK >- people.id source_id integer FK >- news_sources.id inserted_at timestamp updated_at timestamp news_item_comments --- id integer PK content varchar approved boolean edited_at timestamp deleted_at timestamp item_id integer FK >- news_items.id author_id integer FK >- people.id parent_id integer FK >- news_item_comments.id inserted_at timestamp updated_at timestamp news_item_topics --- id integer PK position integer item_id integer FK >- news_items.id topic_id integer FK >- topics.id inserted_at timestamp updated_at timestamp news_queue --- id integer PK position float item_id integer FK - news_items.id news_sources --- id integer PK name varchar slug varchar website varchar twitter_handle varchar description varchar feed varchar regex varchar publication boolean icon varchar inserted_at timestamp updated_at timestamp news_sponsorships --- id integer PK name varchar weeks array impression_count integer click_count integer sponsor_id integer FK >- sponsors.id inserted_at timestamp updated_at timestamp people --- id integer PK name varchar email varchar handle varchar github_handle varchar linkedin_handle varchar mastodon_handle varchar twitter_handle varchar slack_id varchar website varchar bio varchar location varchar auth_token varchar auth_token_expires_at timestamp joined_at timestamp signed_in_at timestamp approved boolean avatar varchar admin boolean host boolean editor boolean public_profile boolean settings jsonb inserted_at timestamp updated_at timestamp podcasts --- id integer PK name varchar slug varchar status integer welcome varchar description varchar extended_description varchar vanity_domain varchar keywords varchar mastodon_handle varchar twitter_handle varchar apple_url varchar spotify_url varchar riverside_url varchar chartable_id varchar schedule_note varchar download_count float reach_count integer recorded_live boolean partner boolean position integer subscribers jsonb cover varchar inserted_at timestamp updated_at timestamp podcast_hosts --- id integer PK position integer retired boolean person_id integer FK >- people.id podcast_id integer FK >- podcasts.id inserted_at timestamp updated_at timestamp podcast_topics --- id integer PK position integer podcast_id integer FK >- podcasts.id topic_id integer FK >- topics.id inserted_at timestamp updated_at timestamp posts --- id integer PK title varchar subtitle varchar slug varchar guid varchar canonical_url varchar image varchar tldr varchar body varchar published boolean published_at timestamp author_id integer FK >- people.id editor_id integer FK >- people.id inserted_at timestamp updated_at timestamp post_topics --- id integer PK position integer topic_id integer FK >- topics.id post_id integer FK >- posts.id inserted_at timestamp updated_at timestamp sponsors --- id integer PK name varchar description varchar website varchar github_handle varchar twitter_handle varchar avatar varchar color_logo varchar dark_logo varchar light_logo varchar inserted_at timestamp updated_at timestamp sponsor_reps --- id integer PK sponsor_id integer FK >- sponsors.id person_id integer FK >- people.id inserted_at timestamp updated_at timestamp subscriptions --- id integer PK unsubscribed_at timestamp context varchar episode_id integer FK >- episodes.id item_id integer FK >- news_items.id person_id integer FK >- people.id podcast_id integer FK >- podcasts.id inserted_at timestamp updated_at timestamp topics --- id integer PK name varchar slug varchar description varchar website varchar twitter_handle varchar icon varchar inserted_at timestamp updated_at timestamp schema_migrations --- version integer PK inserted_at timestamp oban_jobs --- id integer PK state varchar queue varchar worker varchar args jsonb meta jsonb tags array errors array attempt integer attempted_by array max_attempts integer priority integer attempted_at timestamp cancelled_at timestamp completed_at timestamp discarded_at timestamp inserted_at timestamp scheduled_at timestamp ================================================ FILE: examples/quick_dbd/hexpm/Default.qdbd ================================================ schema_migrations --- version integer PK inserted_at timestamp audit_logs --- id integer PK user_agent varchar remote_ip varchar action varchar params jsonb user_id integer FK >- users.id organization_id integer FK >- organizations.id key_id integer FK >- keys.id inserted_at timestamp emails --- id integer PK email varchar verified boolean primary boolean public boolean gravatar boolean verification_key varchar verification_expiry timestamp user_id integer FK >- users.id inserted_at timestamp updated_at timestamp keys --- id integer PK name varchar secret_first varchar secret_second varchar public boolean revoke_at timestamp inserted_at timestamp updated_at timestamp last_use jsonb user_id integer FK >- users.id organization_id integer FK >- organizations.id permissions jsonb organizations --- id integer PK name varchar billing_active boolean billing_override boolean trial_end timestamp inserted_at timestamp updated_at timestamp organization_users --- id integer PK role varchar organization_id integer FK >- organizations.id user_id integer FK >- users.id inserted_at timestamp updated_at timestamp password_resets --- id integer PK key varchar primary_email varchar user_id integer FK >- users.id inserted_at timestamp sessions --- id integer PK token bytea data jsonb inserted_at timestamp updated_at timestamp users --- id integer PK username varchar full_name varchar password varchar service boolean deactivated_at timestamp role varchar inserted_at timestamp updated_at timestamp handles jsonb tfa jsonb organization_id integer FK - organizations.id blocked_addresses --- id integer PK ip varchar comment varchar downloads --- id integer PK package_id integer FK >- packages.id release_id integer FK >- releases.id downloads integer day date installs --- id integer PK hex varchar elixirs array packages --- id integer PK name varchar docs_updated_at timestamp inserted_at timestamp updated_at timestamp repository_id integer FK >- repositories.id meta jsonb package_dependants --- id integer PK package_id integer FK >- packages.id name varchar repo varchar package_downloads --- package_id integer FK >- packages.id view varchar downloads integer package_owners --- id integer PK level varchar package_id integer FK >- packages.id user_id integer FK >- users.id inserted_at timestamp updated_at timestamp package_reports --- id integer PK state varchar description varchar author_id integer FK >- users.id package_id integer FK >- packages.id inserted_at timestamp updated_at timestamp package_report_comments --- id integer PK text varchar inserted_at timestamp updated_at timestamp package_report_id integer FK >- package_reports.id author_id integer FK >- users.id package_report_releases --- id integer PK release_id integer FK >- releases.id package_report_id integer FK >- package_reports.id inserted_at timestamp updated_at timestamp releases --- id integer PK version varchar inner_checksum bytea outer_checksum bytea has_docs boolean inserted_at timestamp updated_at timestamp package_id integer FK >- packages.id publisher_id integer FK >- users.id meta jsonb retirement jsonb release_downloads --- package_id integer FK >- packages.id release_id integer FK - releases.id downloads integer repositories --- id integer PK name varchar inserted_at timestamp updated_at timestamp organization_id integer FK - organizations.id requirements --- id integer PK app varchar requirement varchar optional boolean release_id integer FK >- releases.id dependency_id integer FK >- packages.id short_urls --- id integer PK url varchar short_code varchar inserted_at timestamp ================================================ FILE: examples/quick_dbd/hexpm/Only-selected-cluster-Accounts-context.qdbd ================================================ audit_logs --- id integer PK user_agent varchar remote_ip varchar action varchar params jsonb user_id integer FK >- users.id organization_id integer FK >- organizations.id key_id integer FK >- keys.id inserted_at timestamp emails --- id integer PK email varchar verified boolean primary boolean public boolean gravatar boolean verification_key varchar verification_expiry timestamp user_id integer FK >- users.id inserted_at timestamp updated_at timestamp keys --- id integer PK name varchar secret_first varchar secret_second varchar public boolean revoke_at timestamp inserted_at timestamp updated_at timestamp last_use jsonb user_id integer FK >- users.id organization_id integer FK >- organizations.id permissions jsonb organizations --- id integer PK name varchar billing_active boolean billing_override boolean trial_end timestamp inserted_at timestamp updated_at timestamp organization_users --- id integer PK role varchar organization_id integer FK >- organizations.id user_id integer FK >- users.id inserted_at timestamp updated_at timestamp password_resets --- id integer PK key varchar primary_email varchar user_id integer FK >- users.id inserted_at timestamp sessions --- id integer PK token bytea data jsonb inserted_at timestamp updated_at timestamp users --- id integer PK username varchar full_name varchar password varchar service boolean deactivated_at timestamp role varchar inserted_at timestamp updated_at timestamp handles jsonb tfa jsonb organization_id integer FK - organizations.id ================================================ FILE: examples/quick_dbd/plausible-analytics/Default.qdbd ================================================ schema_migrations --- version integer PK inserted_at timestamp fun_with_flags_toggles --- id integer PK flag_name varchar gate_type varchar target varchar enabled boolean oban_jobs --- id integer PK state varchar queue varchar worker varchar args jsonb meta jsonb tags array errors array attempt integer attempted_by array max_attempts integer priority integer attempted_at timestamp cancelled_at timestamp completed_at timestamp discarded_at timestamp inserted_at timestamp scheduled_at timestamp api_keys --- id integer PK name varchar scopes array hourly_request_limit integer key_hash varchar key_prefix varchar user_id integer FK >- users.id inserted_at timestamp updated_at timestamp email_activation_codes --- id integer PK code varchar issued_at timestamp user_id integer FK >- users.id invitations --- id integer PK invitation_id varchar email varchar role "enum(owner,admin,viewer)" inviter_id integer FK >- users.id site_id integer FK >- sites.id inserted_at timestamp updated_at timestamp totp_recovery_codes --- id integer PK code_digest varchar user_id integer FK >- users.id inserted_at timestamp users --- id integer PK email varchar password_hash varchar name varchar last_seen timestamp trial_expiry_date date theme "enum(system,light,dark)" email_verified boolean previous_email varchar accept_traffic_until date allow_next_upgrade_override boolean totp_enabled boolean totp_secret bytea totp_token varchar totp_last_used_at timestamp grace_period jsonb inserted_at timestamp updated_at timestamp enterprise_plans --- id integer PK paddle_plan_id varchar billing_interval "enum(yearly,monthly)" monthly_pageview_limit integer site_limit integer team_member_limit integer features array hourly_api_request_limit integer user_id integer FK - users.id inserted_at timestamp updated_at timestamp subscriptions --- id integer PK paddle_subscription_id varchar paddle_plan_id varchar update_url varchar cancel_url varchar status "enum(active,deleted,past_due,paused)" next_bill_amount varchar next_bill_date date last_bill_date date currency_code varchar user_id integer FK - users.id inserted_at timestamp updated_at timestamp events_v2 --- name unknown site_id unknown hostname varchar pathname varchar user_id unknown session_id unknown timestamp timestamp "meta.key" array "meta.value" array revenue_source_amount unknown revenue_source_currency unknown revenue_reporting_amount unknown revenue_reporting_currency unknown referrer varchar referrer_source varchar utm_medium varchar utm_source varchar utm_campaign varchar utm_content varchar utm_term varchar country_code unknown subdivision1_code unknown subdivision2_code unknown city_geoname_id unknown screen_size unknown operating_system unknown operating_system_version unknown browser unknown browser_version unknown sessions_v2 --- hostname varchar site_id unknown user_id unknown session_id unknown start timestamp duration unknown is_bounce unknown entry_page varchar exit_page varchar exit_page_hostname varchar pageviews unknown events unknown sign unknown "entry_meta.key" array "entry_meta.value" array utm_medium varchar utm_source varchar utm_campaign varchar utm_content varchar utm_term varchar referrer varchar referrer_source varchar country_code unknown subdivision1_code unknown subdivision2_code unknown city_geoname_id unknown screen_size unknown operating_system unknown operating_system_version unknown browser unknown browser_version unknown timestamp timestamp transferred_from varchar domains_lookup --- site_id unknown domain varchar funnels --- id integer PK name varchar site_id integer FK >- sites.id inserted_at timestamp updated_at timestamp funnel_steps --- id integer PK step_order integer funnel_id integer FK >- funnels.id goal_id integer FK >- goals.id inserted_at timestamp updated_at timestamp goals --- id integer PK event_name varchar page_path varchar currency "enum(KMF,AUD,SAR,BWP,BBD,EGP,YER,CDF,IQD,MRU,JOD,XPT,XBB,NGN,BDT,CNY,ANG,GTQ,HTG,TWD,OMR,STN,AOA,MUR,XCD,TND,THB,KES,GIP,MZN,ERN,MAD,FKP,MVR,BND,KZT,EUR,SYP,MYR,RSD,KRW,COU,GMD,ILS,BAM,XAG,AZN,AFN,AWG,SOS,PAB,AED,UYI,BTN,USN,KPW,IDR,XPD,MOP,GEL,MXV,CHW,XAF,UGX,DJF,SGD,PGK,IRR,VES,PHP,SSP,BOB,XDR,JPY,BHD,UAH,ZAR,BSD,TMT,XOF,XTS,MNT,XSU,XPF,TTD,PLN,AMD,SBD,LSL,GBP,DOP,SEK,MDL,CUP,CZK,SZL,COP,XAU,NOK,CLF,RWF,NAD,KHR,TRY,LAK,SDG,XXX,PEN,LBP,BZD,CLP,KGS,TZS,GNF,KWD,NZD,SVC,LRD,CHF,PKR,SHP,XUA,XBD,LYD,BIF,JMD,ALL,BYN,CUC,UZS,MKD,ZWL,RON,NIO,MMK,SRD,ETB,ARS,GHS,XBA,XBC,UYW,HKD,ISK,DZD,MWK,RUB,SLL,SCR,CHE,CVE,VND,ZMW,HNL,HUF,INR,DKK,FJD,HRK,UYU,PYG,BMD,KYD,VUV,BGN,TOP,MXN,CAD,MGA,BOV,BRL,WST,NPR,CRC,GYD,TJS,LKR,QAR,USD)" site_id integer FK >- sites.id inserted_at timestamp updated_at timestamp imported_browsers --- site_id unknown import_id unknown date date browser varchar browser_version varchar visitors unknown visits unknown visit_duration unknown pageviews unknown bounces unknown imported_devices --- site_id unknown import_id unknown date date device varchar visitors unknown visits unknown visit_duration unknown pageviews unknown bounces unknown imported_entry_pages --- site_id unknown import_id unknown date date entry_page varchar visitors unknown entrances unknown visit_duration unknown pageviews unknown bounces unknown imported_exit_pages --- site_id unknown import_id unknown date date exit_page varchar exits unknown visitors unknown visit_duration unknown pageviews unknown bounces unknown imported_locations --- site_id unknown import_id unknown date date country varchar region varchar city unknown visitors unknown visits unknown visit_duration unknown pageviews unknown bounces unknown imported_operating_systems --- site_id unknown import_id unknown date date operating_system varchar operating_system_version varchar visitors unknown visits unknown visit_duration unknown pageviews unknown bounces unknown imported_pages --- site_id unknown import_id unknown date date hostname varchar page varchar visits unknown visitors unknown active_visitors unknown pageviews unknown exits unknown time_on_page unknown site_imports --- id integer PK start_date date end_date date label varchar source "enum(noop,csv,universal_analytics,google_analytics_4)" status "enum(pending,failed,completed,importing)" legacy boolean site_id integer FK >- sites.id imported_by_id integer FK >- users.id inserted_at timestamp updated_at timestamp imported_sources --- site_id unknown import_id unknown date date source varchar referrer varchar utm_source varchar utm_medium varchar utm_campaign varchar utm_content varchar utm_term varchar visitors unknown visits unknown visit_duration unknown pageviews unknown bounces unknown imported_visitors --- site_id unknown import_id unknown date date visitors unknown pageviews unknown bounces unknown visits unknown visit_duration unknown ingest_counters --- event_timebucket timestamp site_id unknown domain unknown metric unknown value unknown plugins_api_tokens --- id uuid PK inserted_at timestamp updated_at timestamp token_hash bytea description varchar hint varchar last_used_at timestamp site_id integer FK >- sites.id shield_rules_country --- id uuid PK site_id integer FK >- sites.id country_code varchar action "enum(allow,deny)" added_by varchar inserted_at timestamp updated_at timestamp shield_rules_hostname --- id uuid PK site_id integer FK >- sites.id hostname varchar hostname_pattern varchar action "enum(allow,deny)" added_by varchar inserted_at timestamp updated_at timestamp shield_rules_ip --- id uuid PK site_id integer FK >- sites.id inet inet action "enum(allow,deny)" description varchar added_by varchar inserted_at timestamp updated_at timestamp shield_rules_page --- id uuid PK site_id integer FK >- sites.id page_path varchar page_path_pattern varchar action "enum(allow,deny)" added_by varchar inserted_at timestamp updated_at timestamp sites --- id integer PK domain varchar timezone varchar public boolean locked boolean stats_start_date date native_stats_start_at timestamp allowed_event_props array conversions_enabled boolean props_enabled boolean funnels_enabled boolean ingest_rate_limit_scale_seconds integer ingest_rate_limit_threshold integer domain_changed_from varchar domain_changed_at timestamp imported_data jsonb inserted_at timestamp updated_at timestamp google_auth --- id integer PK email varchar property varchar refresh_token varchar access_token varchar expires timestamp user_id integer FK - users.id site_id integer FK - sites.id inserted_at timestamp updated_at timestamp site_memberships --- id integer PK role "enum(owner,admin,viewer)" site_id integer FK - sites.id user_id integer FK >- users.id inserted_at timestamp updated_at timestamp monthly_reports --- id integer PK recipients array site_id integer FK - sites.id inserted_at timestamp updated_at timestamp shared_links --- id integer PK site_id integer FK >- sites.id name varchar slug varchar password_hash varchar inserted_at timestamp updated_at timestamp spike_notifications --- id integer PK recipients array threshold integer last_sent timestamp site_id integer FK - sites.id inserted_at timestamp updated_at timestamp site_user_preferences --- id integer PK pinned_at timestamp user_id integer FK >- users.id site_id integer FK >- sites.id inserted_at timestamp updated_at timestamp weekly_reports --- id integer PK recipients array site_id integer FK - sites.id inserted_at timestamp updated_at timestamp ================================================ FILE: examples_generator.exs ================================================ defmodule Ecto.ERD.ExamplesGenerator do require Logger @shared_examples [ [name: "Default", formats: [:dbml, :dot, :qdbd, :puml, :mmd]], [ name: "No fields", formats: [:dot, :mmd], config: """ [ columns: [] ] """ ], [ name: "Contexts as clusters", formats: [:dbml, :dot, :puml], config: """ alias Ecto.ERD.Node [ map_node: fn %Node{schema_module: schema_module} = node -> case Module.split(schema_module) do [_] -> node [namespace, _] -> node |> Node.set_cluster(namespace) parts -> node |> Node.set_cluster(parts |> Enum.take(2) |> Enum.join(".")) end end ] """ ], [ name: "Contexts as clusters (no fields)", formats: [:dot, :puml], config: """ alias Ecto.ERD.Node [ columns: [], map_node: fn %Node{schema_module: schema_module} = node -> case Module.split(schema_module) do [_] -> node [namespace, _] -> node |> Node.set_cluster(namespace) parts -> node |> Node.set_cluster(parts |> Enum.take(2) |> Enum.join(".")) end end ] """ ] ] @data %{ "plausible-analytics" => %{ repo: "git@github.com:plausible/analytics.git", commit: "ca25b6c7649c6ab9c9268eb57c7931dca1393b94", examples: @shared_examples }, "changelog.com" => %{ repo: "git@github.com:thechangelog/changelog.com.git", commit: "840f6e4fa69e51b680d5462f99db9f5953bf0fdf", examples: Enum.filter(@shared_examples, fn example -> example[:name] in ["Default", "No fields"] end) ++ [ [ name: "Clusters", formats: [:dbml, :dot, :puml], config: """ alias Ecto.ERD.Node [ map_node: fn %Node{schema_module: schema_module} = node -> cluster_name = cond do schema_module in [ Changelog.Episode, Changelog.EpisodeChapter, Changelog.EpisodeGuest, Changelog.EpisodeHost, Changelog.EpisodeRequest, Changelog.EpisodeSponsor, Changelog.EpisodeStat, Changelog.EpisodeTopic ] -> "EPISODE" schema_module in [Changelog.Post, Changelog.PostTopic] -> "POST" schema_module in [Changelog.Podcast, Changelog.PodcastHost, Changelog.PodcastTopic] -> "PODCAST" schema_module in [Changelog.Sponsor, Changelog.SponsorRep] -> "SPONSOR" schema_module in [Changelog.Person, Changelog.Person.Settings] -> "PERSON" schema_module in [ Changelog.EpisodeNewsItem, Changelog.NewsAd, Changelog.NewsIssue, Changelog.NewsIssueAd, Changelog.NewsIssueItem, Changelog.NewsItem, Changelog.NewsItemComment, Changelog.NewsItemTopic, Changelog.NewsQueue, Changelog.NewsSource, Changelog.NewsSponsorship, Changelog.PostNewsItem ] -> "NEWS" true -> nil end Node.set_cluster(node, cluster_name) end ] """ ] ] }, "hexpm" => %{ repo: "git@github.com:hexpm/hexpm.git", commit: "1fb4816abaa8ef23f40795dfa93b9e6b7c569452", examples: @shared_examples ++ [ [ name: "Only selected cluster (Accounts context)", formats: [:dbml, :dot, :qdbd, :puml], config: """ alias Ecto.ERD.Node [ map_node: fn %Node{schema_module: schema_module} = node -> cluster_name = schema_module |> Module.split() |> Enum.take(2) |> Enum.join(".") case cluster_name do "Hexpm.Accounts" -> node |> Node.set_cluster(cluster_name) _ -> nil end end ] """ ], [ name: "Only embedded schemas", formats: [:dot, :puml], config: """ alias Ecto.ERD.Node [ map_node: fn %Node{source: nil} = node -> node _ -> nil end ] """ ] ] } } @formats %{ dot: %{examples_dir: "examples/dot", name: "DOT", image?: true}, dbml: %{examples_dir: "examples/dbml", name: "DBML", image?: false}, qdbd: %{examples_dir: "examples/quick_dbd", name: "QuickDBD", image?: false}, puml: %{examples_dir: "examples/plantuml", name: "PlantUML", image?: true}, mmd: %{examples_dir: "examples/mermaid", name: "Mermaid", image?: false} } def run(source_url_root) do File.mkdir_p("tmp/docs") Enum.each(@formats, fn {_, %{examples_dir: dir}} -> File.mkdir_p(dir) end) File.mkdir("tmp/repos") File.mkdir("tmp/config_files") Logger.debug("Init projects", ansi_color: :yellow) Enum.each(@data, fn {project_name, %{repo: repo, commit: commit}} -> Enum.each(@formats, fn {_, %{examples_dir: dir}} -> File.mkdir(Path.join(dir, project_name)) end) File.mkdir(Path.join("tmp/config_files", project_name)) init_project(project_name, repo, commit) end) Logger.debug("Generating examples", ansi_color: :yellow) @data |> Enum.flat_map(fn {project_name, %{examples: examples}} -> Enum.map(examples, fn example -> Task.async(fn -> generate_example(example, project_name) end) end) end) |> Task.yield_many(:infinity) Logger.debug("Generating markdown docs", ansi_color: :yellow) Enum.each(@data, &generate_doc(&1, source_url_root)) Logger.debug("Done", ansi_color: :green) end defp generate_doc({project_name, %{repo: repo, examples: examples}}, source_url_root) do examples_md = examples |> Enum.map_join("\n\n", fn example -> config_content = if example[:config] do """ #### Config file ```elixir # .ecto_erd.exs #{example[:config]} ``` """ end output = example[:formats] |> Enum.map(fn format -> document_url = Path.join([ source_url_root, @formats[format].examples_dir, project_name, slugify(example[:name]) <> ".#{format}" ]) image_url = if @formats[format].image? do Path.rootname(document_url) <> ".png" end [ "**#{@formats[format].name}**" | Enum.map([document_url, image_url], fn nil -> "—" url -> "[View](#{url})" end) ] end) output_content = """ #### Output #{as_table(output, ["Format", "Document", "Image"])} """ """ ## #{example[:name]} #{output_content} #{config_content} """ end) content = """ # #{project_name} Repo: `#{repo}` #{examples_md} """ File.write!(Path.join("tmp/docs", project_name <> ".md"), content) end defp generate_example(example, project_name) do Logger.debug("#{project_name}: generating #{example[:name]}") slug = slugify(example[:name]) example[:formats] |> Enum.map(fn format -> {format, Path.expand( Path.join([@formats[format].examples_dir, project_name, slug <> ".#{format}"]) )} end) |> Enum.each(fn {format, output_path} -> old_output_content = case File.read(output_path) do {:ok, content} -> content {:error, _} -> nil end if example[:config] do config_path = Path.expand(Path.join(["tmp/config_files", project_name, slug <> ".exs"])) File.write!(config_path, example[:config]) System.cmd( "mix", [ "ecto.gen.erd", "--output-path=#{output_path}", "--config-path=#{config_path}" ], cd: Path.join("tmp/repos", project_name) ) else System.cmd("mix", ["ecto.gen.erd", "--output-path=#{output_path}"], cd: Path.join("tmp/repos", project_name) ) end {:ok, new_output_content} = File.read(output_path) if @formats[format].image? and (old_output_content != new_output_content or not File.exists?(Path.rootname(output_path) <> ".png")) do Logger.debug("Generating image from #{output_path}") generate_image(format, output_path) end end) Logger.debug("#{project_name}: generated #{example[:name]}") end defp generate_image(:dot, file) do System.cmd("dot", ["-Tpng", file, "-o", Path.rootname(file) <> ".png"]) end defp generate_image(:puml, file) do System.cmd("plantuml", [file], env: %{"PLANTUML_LIMIT_SIZE" => "8192"}) end defp init_project(project_name, repo, commit) do Logger.debug("#{project_name}: clone repo") System.cmd("git", ["clone", repo, project_name], cd: "tmp/repos") System.cmd("git", ["fetch", "origin"], cd: Path.join("tmp/repos", project_name)) System.cmd("git", ["checkout", "--", "."], cd: Path.join("tmp/repos", project_name)) System.cmd("git", ["checkout", commit], cd: Path.join("tmp/repos", project_name)) add_ecto_erd_to_dependencies(Path.join(["tmp/repos", project_name, "mix.exs"])) Logger.debug("#{project_name}: get dependencies") System.cmd("mix", ["deps.get"], cd: Path.join("tmp/repos", project_name)) Logger.debug("#{project_name}: compile") {_, 0} = System.cmd("mix", ["compile"], cd: Path.join("tmp/repos", project_name)) end defp slugify(name) do name |> String.replace(~r/\s+/, "-") |> String.replace(~r/[^\w-]+/, "") end defp add_ecto_erd_to_dependencies(path) do dependency_line = inspect({:ecto_erd, path: "../../../"}) content = File.read!(path) if String.contains?(content, dependency_line) do Logger.debug("#{dependency_line} already present in #{path}") else new_content = content |> String.replace(~r|defp? deps(?:\(\))? do\n( +)\[|, ~s|\\0\n\\1 #{dependency_line},|) File.write!(path, new_content) Logger.debug("Added #{dependency_line} to #{path}") end end def projects, do: Map.keys(@data) defp as_table(rows, header) do columns_number = length(header) column_widths = 1..columns_number |> Map.new(fn column_number -> max_length = [header | rows] |> Enum.map(fn row -> row |> Enum.at(column_number - 1) |> to_string |> String.length() end) |> Enum.max() {column_number, max_length} end) header_delimiter = 1..columns_number |> Enum.map(fn column_number -> String.duplicate("-", column_widths[column_number]) end) ([header, header_delimiter] ++ rows) |> Enum.map(fn row -> body = row |> Enum.with_index(1) |> Enum.map_join(" | ", fn {cell, column_number} -> cell |> to_string() |> String.pad_trailing(column_widths[column_number]) end) "| " <> body <> " |" end) |> Enum.intersperse("\n") end end ================================================ FILE: lib/ecto/erd/color.ex ================================================ defmodule Ecto.ERD.Color do @moduledoc false @colors ~w( #eedfcc #f0ffff #eee5de #fffafa #f0f8ff #8deeee #b4eeb4 #eee685 #eee5de #ffefd5 ) def get(term) do Enum.at(@colors, :erlang.phash2(term, length(@colors))) end end ================================================ FILE: lib/ecto/erd/document/dbml.ex ================================================ defmodule Ecto.ERD.Document.DBML do @moduledoc false alias Ecto.ERD.{Node, Field, Edge, Graph, Render} @behaviour Ecto.ERD.Document @impl true def schemaless?, do: true @impl true def render(%Graph{nodes: nodes, edges: edges}, _options) do groups = nodes |> Enum.group_by(& &1.cluster, & &1.source) |> Map.delete(nil) |> Enum.map_join(fn {cluster_name, sources} -> """ TableGroup #{Render.in_quotes(cluster_name)} { #{Enum.map_join(sources, "\n ", &Render.in_quotes/1)} } """ end) enums_mapping = enums_mapping(nodes) enums = enums_mapping |> Map.values() |> Enum.uniq() |> Enum.map_join("\n", fn {name, values} -> """ Enum #{Render.in_quotes(name)} { #{values |> Enum.map_join("\n ", &Render.in_quotes/1)} } """ end) tables = Enum.map_join(nodes, "\n", fn %Node{source: source, fields: fields} -> enum_name_by_field_name = fn field_name -> {enum_name, _values} = enums_mapping[[source, field_name]] enum_name end """ Table #{Render.in_quotes(source)} { #{Enum.map_join(fields, "\n ", &render_field(&1, enum_name_by_field_name))} } """ end) refs = edges |> Enum.map(&render_edge/1) |> Enum.reject(&is_nil/1) |> Enum.join("\n") groups <> "\n" <> enums <> "\n" <> tables <> "\n" <> refs end defp render_edge(%Edge{to: {nil, _, _}}), do: nil defp render_edge(%Edge{ from: {from_source, _from_schema, {:field, from_field}}, to: {to_source, _to_schema, {:field, to_field}}, assoc_types: assoc_types }) do operator = if {:has, :one} in assoc_types do "-" else "<" end [ "Ref:", Render.in_quotes(from_source) <> "." <> Render.in_quotes(from_field), operator, Render.in_quotes(to_source) <> "." <> Render.in_quotes(to_field) ] |> Enum.join(" ") end # tries to cut name from #source_#field format to just #field @doc false def enums_mapping(nodes) do nodes |> Enum.flat_map(fn %Node{source: source, fields: fields} -> fields |> Enum.flat_map(fn %Field{name: name, type: {:parameterized, {Ecto.Enum, %{on_dump: on_dump}}}} -> values = on_dump |> Map.values() |> Enum.sort() [{source, name, values}] _ -> [] end) end) |> Enum.group_by(fn {_source, name, _values} -> name end, fn {source, _name, values} -> {source, values} end) |> Enum.flat_map(fn {name, items} -> values_to_sources = Enum.group_by(items, fn {_source, values} -> values end, fn {source, _values} -> source end) enum_name = if map_size(values_to_sources) == 1 do fn _ -> to_string(name) end else fn source -> "#{source}_#{name}" end end Enum.map(items, fn {source, values} -> {[source, name], {enum_name.(source), values}} end) end) |> Map.new() end defp render_field(%Field{name: name, type: type, primary?: primary?}, enum_name_by_field_name) do settings = if primary? do " [pk]" else "" end case type do {:parameterized, {Ecto.Enum, _}} -> "#{Render.in_quotes(name)} #{Render.in_quotes(enum_name_by_field_name.(name))}#{settings}" _ -> "#{Render.in_quotes(name)} #{format_type(type)}#{settings}" end end defp format_type(type) do case Ecto.Type.type(type) do {:array, _t} -> "array" :id -> "integer" :identity -> "bigint" :binary_id -> "uuid" :string -> "varchar" :binary -> "bytea" :map -> "jsonb" {:map, _} -> "jsonb" :time_usec -> "time" :utc_datetime -> "timestamp" :utc_datetime_usec -> "timestamp" :naive_datetime -> "timestamp" :naive_datetime_usec -> "timestamp" atom when is_atom(atom) -> Atom.to_string(atom) {:parameterized, _} -> "unknown" end end end ================================================ FILE: lib/ecto/erd/document/dot.ex ================================================ defmodule Ecto.ERD.Document.Dot do @moduledoc false alias Ecto.ERD.{HTML, Edge, Node, Field, Graph, Render} @behaviour Ecto.ERD.Document @impl true def schemaless?, do: false @impl true def render(%Graph{nodes: nodes, edges: edges}, opts) do fontname = opts[:fontname] || "Roboto Mono" columns = opts[:columns] || [:name, :type] clusters = Enum.group_by(nodes, & &1.cluster) {global_nodes, clusters} = Map.pop(clusters, nil) global_nodes = List.wrap(global_nodes) subgraphs = Enum.map(clusters, fn {cluster_name, nodes} -> """ subgraph #{Render.in_quotes("cluster_#{cluster_name}")} { style=filled fontname=#{Render.in_quotes(fontname)} color = #{Render.in_quotes(Ecto.ERD.Color.get(cluster_name))} label = <#{{:font, ["point-size": 24], {:b, [], cluster_name}} |> HTML.to_iodata()}> #{Enum.map_join(nodes, "\n ", &render_node(&1, columns))} } """ end) strict? = columns == [] """ #{if strict?, do: "strict "}digraph { ranksep=1.0; rankdir=LR; node [shape = none, fontname=#{Render.in_quotes(fontname)}]; #{Enum.map_join(global_nodes, "\n ", &render_node(&1, columns))} #{subgraphs} #{Enum.map_join(edges, "\n ", &render_edge(&1, columns == []))} } """ end defp render_edge( %Edge{ from: from, to: to, assoc_types: assoc_types }, skip_port? ) do result = "#{render_position(from, skip_port?)}:e -> #{render_position(to, skip_port?)}:w" # don't draw arrow if relation is 1 <-> 1 if {:has, :one} in assoc_types do result <> " [dir=none]" else result end end defp render_position({source, schema_module, port}, skip_port?) do string = Render.in_quotes(Node.id(source, schema_module)) if skip_port?, do: string, else: string <> ":" <> Render.in_quotes(Edge.port_name(port)) end defp render_node( %Node{ fields: fields, source: source, schema_module: schema_module }, columns ) do field_rows = if columns == [] or fields == [] do [] else column_width = Map.new( columns, fn column -> max_length = fields |> Enum.map(fn field -> field |> format_field(column) |> String.length() end) |> Enum.max() {column, max_length + 5} end ) Enum.map(fields, fn %Field{name: name} = field -> {:tr, [], {:td, [align: :left, port: Edge.port_name({:field, name})], Enum.map(columns, fn column -> text = String.pad_trailing(format_field(field, column), column_width[column]) case column do :type -> {:i, [], {:font, [color: :gray54], text}} :name -> if field.primary?, do: {:b, [], text}, else: text end end)}} end) end table = {:table, [align: :left, border: 1, style: :rounded, cellspacing: 0, cellpadding: 4, cellborder: 0], [ if(schema_module, do: {:tr, [], {:td, if(not is_nil(source) or Enum.empty?(field_rows), do: [], else: [border: 1, sides: :b, colspan: length(columns)] ) ++ [ port: Edge.port_name({:header, :schema_module}) ], {:font, ["point-size": 18], " " <> inspect(schema_module) <> " "}}} ), if(source, do: {:tr, [], {:td, if(Enum.empty?(field_rows), do: [], else: [border: 1, sides: :b, colspan: length(columns)] ), [ {:font, ["point-size": 14], {:i, [], source}} ]}} ), field_rows ]} |> HTML.to_iodata() Render.in_quotes(Node.id(source, schema_module)) <> " [label= <#{table}>]" end defp format_field(%Field{name: name}, :name), do: inspect(name) defp format_field(%Field{type: type}, :type), do: format_type(type) defp format_type({:parameterized, {Ecto.Enum, %{on_dump: on_dump}}}) do "#Enum<#{inspect(Enum.sort(Map.keys(on_dump)), limit: 10)}>" end defp format_type( {:parameterized, {Ecto.Embedded, %Ecto.Embedded{cardinality: cardinality, related: related}}} ) do "#Ecto.Embedded<#{inspect([{cardinality, related}])}>" end defp format_type({:array, type}) do "{:array, #{format_type(type)}}" end defp format_type(type), do: inspect(type) end ================================================ FILE: lib/ecto/erd/document/mermaid.ex ================================================ defmodule Ecto.ERD.Document.Mermaid do @moduledoc false alias Ecto.ERD.{Node, Field, Edge, Graph} @behaviour Ecto.ERD.Document require Logger @impl true def schemaless?, do: true @impl true def render(%Graph{nodes: nodes, edges: edges}, opts) do fields_config = case opts[:columns] || [:type, :name] do [] -> :hide_fields [:type, :name] -> :show_fields _ -> raise """ Mermaid doesn't support rich customization of columns. You should either set :columns to `[]` in order to hide fields or keep the default value. """ end """ erDiagram #{nodes |> Enum.map(&render_node(&1, fields_config)) |> Enum.reject(&is_nil/1) |> Enum.join("\n ")} #{edges |> Enum.map(&render_edge/1) |> Enum.reject(&is_nil/1) |> Enum.join("\n ")} """ end defp render_node(%Node{source: source, fields: fields}, fields_config) do if name_valid?(source) do case fields_config do :show_fields -> [ source, " {\n", fields |> Enum.map(&render_field/1) |> Enum.reject(&is_nil/1) |> Enum.map(&[" ", &1]) |> Enum.intersperse("\n"), "\n }" ] :hide_fields -> source end else Logger.warning("Source #{inspect(source)} contains invalid symbols and cannot be displayed") nil end end defp render_edge(%Edge{ from: {from_source, _from_schema, {:field, _from_field}}, to: {to_source, _to_schema, {:field, _to_field}}, assoc_types: assoc_types }) do if name_valid?(from_source) and name_valid?(to_source) do operator = if {:has, :one} in assoc_types do "||--o|" else "||--|{" end [ from_source, operator, to_source, ":", "\"\"" ] |> Enum.join(" ") end end defp render_field(%Field{} = field) do if name_valid?(to_string(field.name)) do format_type(field.type) <> " " <> to_string(field.name) <> if field.primary? do " PK" else "" end <> if field.comment do ~s( "#{field.comment}") else "" end else Logger.warning( "Field name #{inspect(field.name)} contains invalid symbols and cannot be displayed" ) nil end end defp format_type(type) do case Ecto.Type.type(type) do {:array, _t} -> "array" :id -> "integer" :identity -> "bigint" :binary_id -> "uuid" :string -> "varchar" :binary -> "bytea" :map -> "jsonb" {:map, _} -> "jsonb" :time_usec -> "time" :utc_datetime -> "timestamp" :utc_datetime_usec -> "timestamp" :naive_datetime -> "timestamp" :naive_datetime_usec -> "timestamp" atom when is_atom(atom) -> Atom.to_string(atom) {:parameterized, _} -> "unknown" end end defp name_valid?(source_or_field) do source_or_field =~ ~r/^[a-zA-z\-_\d]+$/ end end ================================================ FILE: lib/ecto/erd/document/plantuml.ex ================================================ defmodule Ecto.ERD.Document.PlantUML do @moduledoc false alias Ecto.ERD.{Node, Edge, Graph, Render, Color} @behaviour Ecto.ERD.Document @safe_name_pattern ~r/^[a-z\d_\.:\?]+$/i @impl true def schemaless?, do: false @impl true def render(%Graph{nodes: nodes, edges: edges}, opts) do fontname = opts[:fontname] || "Roboto Mono" columns = opts[:columns] || [:name, :type] clusters = Enum.group_by(nodes, & &1.cluster) {global_nodes, clusters} = Map.pop(clusters, nil) ensure_cluster_names_valid!(Map.keys(clusters)) global_nodes = List.wrap(global_nodes) namespaces = Enum.map(clusters, fn {cluster_name, nodes} -> """ namespace #{cluster_name} #{Color.get(cluster_name)} { #{Enum.map_join(nodes, "\n", &render_node(&1, columns, " "))} } """ end) entities = Enum.map_join(global_nodes, "\n", &render_node(&1, columns, "")) refs = edges |> Enum.uniq_by(fn %Edge{ from: {from_source, from_schema, _}, to: {to_source, to_schema, _} } -> {from_source, from_schema, to_source, to_schema} end) |> Enum.map_join("\n", &render_edge/1) """ @startuml set namespaceSeparator none hide circle hide methods #{case columns do [] -> "hide fields" _ -> "" end} skinparam linetype ortho skinparam defaultFontName #{fontname} skinparam shadowing false #{namespaces} #{entities} #{refs} @enduml """ end defp render_node( %Node{ source: source, fields: fields, schema_module: schema_module }, columns, padding ) do case columns do [] -> "#{padding}entity #{Render.in_quotes(Node.id(source, schema_module), @safe_name_pattern)}" columns -> items = case Enum.split_with(fields, & &1.primary?) do {[], fields} -> fields {pk, fields} -> pk ++ ["--"] ++ fields end content = Enum.map_join(items, "\n#{padding} ", fn "--" -> "--" field -> columns |> Enum.map_join( " : ", fn :name -> Render.in_quotes(field.name, @safe_name_pattern) :type -> format_type(field.type) end ) end) """ #{padding}entity #{Render.in_quotes(Node.id(source, schema_module), @safe_name_pattern)} { #{padding} #{content} #{padding}} """ end end defp render_edge(%Edge{ from: {from_source, from_schema, _}, to: {to_source, to_schema, _}, assoc_types: assoc_types }) do operator = if {:has, :one} in assoc_types do "||--o|" else "||--|{" end [ Render.in_quotes(Node.id(from_source, from_schema), @safe_name_pattern), operator, Render.in_quotes(Node.id(to_source, to_schema), @safe_name_pattern) ] |> Enum.join(" ") end defp format_type({:parameterized, {Ecto.Enum, %{on_dump: on_dump}}}) do elements_limit = 10 values = Map.values(on_dump) length = length(values) values = if length <= elements_limit do values else values |> Enum.slice(0, elements_limit) |> List.replace_at(-1, "...") end "enum(#{Enum.join(values, ",")})" end defp format_type(type) do case Ecto.Type.type(type) do {parent, _t} -> Atom.to_string(parent) atom when is_atom(atom) -> Atom.to_string(atom) {:parameterized, _} -> "unknown" end end # namespaces cannot be quoted, so this check is necessary defp ensure_cluster_names_valid!(names) do Enum.each(names, fn name -> unless name =~ @safe_name_pattern do raise "Cluster name #{inspect(name)} contains symbols which are unsupported by PlantUML." end end) end end ================================================ FILE: lib/ecto/erd/document/quick_dbd.ex ================================================ defmodule Ecto.ERD.Document.QuickDBD do @moduledoc false alias Ecto.ERD.{Node, Field, Edge, Graph, Render} @behaviour Ecto.ERD.Document @impl true def schemaless?, do: true @impl true def render(%Graph{nodes: nodes, edges: edges}, _opts) do foreign_keys_mapping = Map.new(edges, fn %Edge{to: {to_source, _to_schema, {:field, to_field}}} = edge -> {{to_source, to_field}, edge} end) Enum.map_join(nodes, "\n\n", &render_node(&1, foreign_keys_mapping)) end defp render_node(%Node{source: source, fields: fields}, foreign_keys_mapping) do get_fkey_edge = fn field_name -> foreign_keys_mapping[{source, field_name}] end [ source, "\n---\n", fields |> Enum.map(&render_field(&1, get_fkey_edge)) |> Enum.intersperse("\n") ] end defp render_field(%Field{name: name, type: type, primary?: primary?}, get_fkey_edge) do settings = if primary? do ["PK"] else case get_fkey_edge.(name) do %Edge{ from: {from_source, _from_schema, {:field, from_field}}, assoc_types: assoc_types } -> operator = if {:has, :one} in assoc_types do "-" else ">-" end [ "FK #{operator} #{Render.in_quotes(from_source)}.#{Render.in_quotes(from_field)}" ] _ -> [] end end Enum.intersperse( [Render.in_quotes(name), Render.in_quotes(format_type(type)) | settings], " " ) end defp format_type({:parameterized, {Ecto.Enum, %{on_dump: on_dump}}}) do "enum(#{Enum.join(Map.values(on_dump), ",")})" end defp format_type(type) do case Ecto.Type.type(type) do {:array, _t} -> "array" :id -> "integer" :identity -> "bigint" :binary_id -> "uuid" :string -> "varchar" :binary -> "bytea" :map -> "jsonb" {:map, _} -> "jsonb" :time_usec -> "time" :utc_datetime -> "timestamp" :utc_datetime_usec -> "timestamp" :naive_datetime -> "timestamp" :naive_datetime_usec -> "timestamp" atom when is_atom(atom) -> Atom.to_string(atom) {:parameterized, _} -> "unknown" end end end ================================================ FILE: lib/ecto/erd/document.ex ================================================ defmodule Ecto.ERD.Document do @moduledoc false @callback render(Ecto.ERD.Graph.t(), opts :: Keyword.t()) :: iodata() @callback schemaless?() :: boolean() def render(schema_modules, format, map_node_callback, render_opts) when is_function(map_node_callback, 1) do document_module = case format do ".dbml" -> Ecto.ERD.Document.DBML ".dot" -> Ecto.ERD.Document.Dot ".mmd" -> Ecto.ERD.Document.Mermaid ".puml" -> Ecto.ERD.Document.PlantUML ".qdbd" -> Ecto.ERD.Document.QuickDBD format -> raise "Unsupported format #{format}" end schema_modules |> Ecto.ERD.Graph.new( if document_module.schemaless?() do [:associations] else [:associations, :embeds] end ) |> Ecto.ERD.Graph.map_nodes(map_node_callback) |> then( if document_module.schemaless?() do &Ecto.ERD.Graph.make_schemaless/1 else &Function.identity/1 end ) |> Ecto.ERD.Graph.sort() |> document_module.render(render_opts) end end ================================================ FILE: lib/ecto/erd/edge.ex ================================================ defmodule Ecto.ERD.Edge do @moduledoc false defstruct [:from, :to, :assoc_types] def new(%{ from: {_source1, _module1, _port1} = from, to: {_source2, _module2, _port2} = to, assoc_types: assoc_types }) when is_list(assoc_types) do %__MODULE__{ from: from, to: to, assoc_types: assoc_types } end def connected_with_node?( %__MODULE__{ from: {source1, module1, _port1}, to: {source2, module2, _port2} }, node ) do (source1 == node.source and module1 == node.schema_module) or (source2 == node.source and module2 == node.schema_module) end def merge(%__MODULE__{} = edge1, %__MODULE__{} = edge2) do %__MODULE__{ edge1 | assoc_types: edge1.assoc_types ++ edge2.assoc_types } end def port_name({type, id}) when type in [:header, :field] and is_atom(id), do: "#{type}@#{id}" end ================================================ FILE: lib/ecto/erd/field.ex ================================================ defmodule Ecto.ERD.Field do @moduledoc """ Field struct. Represents the data for an individual field in `Ecto.ERD.Node`. You can update fields on this struct in `.ecto_erd.exs` if needed. """ defstruct [:name, :type, :primary?, :comment] @typedoc """ A field comment. Rendering comments is currently supported only by the Mermaid format. ## Example # File: .ecto_erd.exs [ map_node: fn %Ecto.ERD.Node{schema_module: schema_module} = node -> update_in(node, [Access.key(:fields), Access.all()], fn field -> # `DocumentationLib` is a fictional module that returns documentation about a field case DocumentationLib.doc({schema_module, field}) do nil -> field doc -> %{field | comment: doc} end end) end ] """ @type comment :: String.t() @type t :: %__MODULE__{ name: atom(), type: atom() | tuple(), primary?: boolean(), comment: comment() | nil } def new(%{name: name, type: type} = params) do %__MODULE__{ name: name, type: type, primary?: Map.get(params, :primary?, false) } end end ================================================ FILE: lib/ecto/erd/graph.ex ================================================ defmodule Ecto.ERD.Graph do @moduledoc false alias Ecto.ERD.{Edge, Node, Field} defstruct [:edges, :nodes] require Logger @type t :: %__MODULE__{} def new(modules, relation_types) do data = modules |> Enum.flat_map(fn module -> components(module, relation_types) end) |> Enum.group_by(fn %Edge{} -> :edges %Node{} -> :nodes end) %__MODULE__{ # multiple nodes could be generated by multiple schemas which use the same table in many to many relation nodes: Enum.uniq(Map.get(data, :nodes, [])), edges: merge_edges_with_same_direction(Map.get(data, :edges, [])) } end def sort(%__MODULE__{nodes: nodes, edges: edges}) do %__MODULE__{ nodes: Enum.sort_by(nodes, &{&1.cluster, &1.schema_module, &1.source}), edges: Enum.sort_by(edges, &{&1.from, &1.to}) } end def map_nodes(%__MODULE__{nodes: nodes, edges: edges}, map_node_callback) when is_function(map_node_callback, 1) do {nodes, removed_nodes} = Enum.flat_map_reduce(nodes, [], fn node, removed_nodes -> case map_node_callback.(node) do nil -> {[], [node | removed_nodes]} node -> {[node], removed_nodes} end end) edges = Enum.reject(edges, fn edge -> Enum.any?(removed_nodes, fn node -> Edge.connected_with_node?(edge, node) end) end) %__MODULE__{ nodes: List.wrap(nodes), edges: edges } end # it, actually, doesn't remove ALL information about schemas, but only in duplicated entities - # when nodes have the same source or when edges are equal by source and port. def make_schemaless(%__MODULE__{nodes: nodes, edges: edges}) do nodes = nodes |> Enum.group_by(fn %Node{source: source} -> source end) |> Map.delete(nil) |> Enum.map(fn {_source, nodes} -> Enum.reduce(nodes, &Node.merge_to_schemaless/2) end) edges = edges |> Enum.uniq_by(fn %Edge{from: {source1, _module1, port1}, to: {source2, _module2, port2}} -> {{source1, port1}, {source2, port2}} end) %__MODULE__{ nodes: nodes, edges: edges } end defp merge_edges_with_same_direction(edges) do edges |> Enum.group_by(fn %Edge{from: from, to: to} -> {from, to} end) |> Enum.map(fn {_direction, edges} -> Enum.reduce(edges, &Edge.merge/2) end) end defp components(schema_module, relation_types) do if ecto_schema?(schema_module) do relation_components = Enum.flat_map(relation_types, &components_from_relations(schema_module, &1)) primary_keys = schema_module.__schema__(:primary_key) node = Node.new( schema_module, schema_module.__schema__(:source), Enum.map( schema_module.__schema__(:fields), fn field -> Field.new(%{ name: field, type: schema_module.__schema__(:type, field), primary?: field in primary_keys }) end ) ) [node | relation_components] else Logger.warning("Skipping schema #{schema_module}: not found") [] end end defp components_from_relations(module, :associations) do :associations |> module.__schema__() |> Enum.flat_map(fn assoc_field -> from_relation_struct(module.__schema__(:association, assoc_field)) end) end defp components_from_relations(module, :embeds) do :embeds |> module.__schema__() |> Enum.flat_map(fn embed_field -> from_relation_struct(module.__schema__(:embed, embed_field)) end) end defp from_relation_struct(%Ecto.Embedded{ owner: owner, related: related, field: field, cardinality: cardinality }) do if ecto_schema?(related) do [ Edge.new(%{ from: {owner.__schema__(:source), owner, {:field, field}}, to: {related.__schema__(:source), related, {:header, :schema_module}}, assoc_types: [has: cardinality] }) ] else Logger.warning( "Skipping association `embeds_#{cardinality} #{inspect(field)}` in schema #{inspect(owner)}: #{inspect(related)} is not an Ecto schema" ) [] end end defp from_relation_struct(%Ecto.Association.BelongsTo{ owner: owner, owner_key: owner_key, related: related, related_key: related_key, field: field }) do if ecto_schema?(related) do related_source = related.__schema__(:source) owner_source = owner.__schema__(:source) [ Edge.new(%{ from: {related_source, related, {:field, related_key}}, to: {owner_source, owner, {:field, owner_key}}, assoc_types: [:belongs_to] }) ] else Logger.warning( "Skipping association `belongs_to #{inspect(field)}` in schema #{inspect(owner)}: #{inspect(related)} is not an Ecto schema" ) [] end end defp from_relation_struct(%Ecto.Association.Has{ owner: owner, owner_key: owner_key, related: related, related_key: related_key, cardinality: cardinality, field: field }) do if ecto_schema?(related) do related_source = related.__schema__(:source) owner_source = owner.__schema__(:source) [ Edge.new(%{ from: {owner_source, owner, {:field, owner_key}}, to: {related_source, related, {:field, related_key}}, assoc_types: [has: cardinality] }) ] else Logger.warning( "Skipping association `has_#{cardinality} #{inspect(field)}` in schema #{inspect(owner)}: #{inspect(related)} is not an Ecto schema" ) [] end end defp from_relation_struct(%Ecto.Association.ManyToMany{ join_through: join_through, owner: owner, field: field, related: related, join_keys: [{join_source_owner_fk, owner_pk}, {join_source_related_fk, related_pk}] }) do if ecto_schema?(related) do if is_atom(join_through) and not ecto_schema?(join_through) do Logger.warning( "Skipping association `many_to_many #{inspect(field)}` in schema #{inspect(owner)}: #{inspect(join_through)} is not an Ecto schema" ) [] else {join_module, join_source} = case join_through do value when is_atom(value) -> {value, value.__schema__(:source)} value when is_binary(value) -> {nil, value} end nodes = case join_module do nil -> fields = [ Field.new(%{ name: join_source_owner_fk, type: owner.__schema__(:type, owner_pk), primary?: false }), Field.new(%{ name: join_source_related_fk, type: related.__schema__(:type, related_pk), primary?: false }) ] [Node.new(join_source, Enum.sort(fields))] _join_module -> [] end nodes ++ [ Edge.new(%{ from: {owner.__schema__(:source), owner, {:field, owner_pk}}, to: {join_source, join_module, {:field, join_source_owner_fk}}, assoc_types: [has: :many] }), Edge.new(%{ from: {related.__schema__(:source), related, {:field, related_pk}}, to: {join_source, join_module, {:field, join_source_related_fk}}, assoc_types: [has: :many] }) ] end else Logger.warning( "Skipping association `many_to_many #{inspect(field)}` in schema #{inspect(owner)}: module #{inspect(related)} is not an Ecto schema" ) [] end end defp from_relation_struct(%Ecto.Association.HasThrough{}) do [] end defp ecto_schema?(module) do function_exported?(module, :__schema__, 1) end end ================================================ FILE: lib/ecto/erd/html.ex ================================================ defmodule Ecto.ERD.HTML do @moduledoc false def to_iodata(tuples) when is_list(tuples) do Enum.map(tuples, &to_iodata/1) end def to_iodata({tag_name, attrs, content}) do tag( to_string(tag_name), attrs, content |> List.wrap() |> to_iodata() ) end def to_iodata(term), do: HtmlEntities.encode(to_string(term)) defp tag(name, attrs, content) do ["<", name, attrs(attrs), ">", content, ""] end defp attrs([]), do: [] defp attrs(attrs) do [ " ", Enum.map_intersperse(attrs, " ", fn {key, value} -> [to_string(key), "='", to_string(value), "'"] end) ] end end ================================================ FILE: lib/ecto/erd/node.ex ================================================ defmodule Ecto.ERD.Node do @moduledoc """ Node struct. * If `source` is `nil`, then `schema_module` cannot be `nil` and node describes embedded schema. * If `source` is not `nil` and `schema_module` is not `nil`, then the node describes a regular schema. * If `schema_module` is `nil`, then `source` cannot be `nil`, and the node describes a source (table) that was automatically inferred from many-to-many relations. * If `cluster` is `nil`, then the node is rendered outside any cluster. """ defstruct [ :source, :schema_module, :fields, :cluster ] @type t() :: %__MODULE__{ source: nil | String.t(), schema_module: nil | module(), fields: [Ecto.ERD.Field.t()], cluster: nil | String.t() } @doc """ Set a `cluster` for the given `node`. A cluster is a group of nodes that are displayed together. """ @spec set_cluster(t(), nil | String.t()) :: t() def set_cluster(%__MODULE__{} = node, cluster) when is_nil(cluster) or is_binary(cluster) do %{node | cluster: cluster} end @doc false def id(source, schema_module) when (is_nil(source) or is_binary(source)) and is_atom(schema_module) do if schema_module do inspect(schema_module) else source end end @doc false def new(schema_module \\ nil, source, fields) do %__MODULE__{ schema_module: schema_module, source: source, fields: fields } end @doc false def merge_to_schemaless( %__MODULE__{source: source, fields: fields1, cluster: cluster1}, %__MODULE__{ source: source, fields: fields2, cluster: cluster2 } ) when not is_nil(source) do # If fields have different types with the same name, only one type will be chosen fields = Enum.uniq_by(fields1 ++ fields2, & &1.name) cluster = case {cluster1, cluster2} do {nil, cluster} -> cluster {cluster, nil} -> cluster {cluster, cluster} -> cluster {cluster1, cluster2} -> IO.warn( "Trying to merge two nodes with source #{inspect(source)} but with different clusters " <> "(#{inspect(cluster1)} and #{inspect(cluster2)}); removing the cluster in favor of the global space" ) nil end %__MODULE__{ source: source, fields: fields, cluster: cluster } end end ================================================ FILE: lib/ecto/erd/render.ex ================================================ defmodule Ecto.ERD.Render do @moduledoc false def in_quotes(value, pattern \\ ~r/^[a-z\d_]+$/i) do value = to_string(value) # avoid redundant quotes if possible if value =~ pattern do value else "\"" <> String.replace(value, "\"", "\\\"") <> "\"" end end end ================================================ FILE: lib/ecto/erd/schema_modules.ex ================================================ defmodule Ecto.ERD.SchemaModules do @moduledoc false def scan(otp_app) do {:ok, applications} = :application.get_key(otp_app, :applications) [otp_app | applications] |> Enum.flat_map(fn application -> case :application.get_key(application, :modules) do {:ok, modules} -> modules _error -> [] end end) |> Enum.filter(fn module -> Code.ensure_loaded!(module) function_exported?(module, :__schema__, 1) and module != Ecto.Schema end) end end ================================================ FILE: lib/mix/tasks/ecto.gen.erd.ex ================================================ defmodule Mix.Tasks.Ecto.Gen.Erd do @moduledoc """ A Mix task that generates an ERD (Entity‑Relationship Diagram) in various formats. Supported formats: * [DOT](#module-dot) (default) * [PlantUML](#module-plantuml) * [DBML](#module-dbml) * [QuickDBD](#module-quickdbd) * [Mermaid](#module-mermaid) Configuration examples and sample output for a few open-source projects can be found in the PAGES section under EXAMPLES. ## DOT The [DOT](https://en.wikipedia.org/wiki/DOT_(graph_description_language)) format can represent all available entity types: * schemas * embedded schemas * schemaless tables (automatically derived from many-to-many relations) Clusters are supported and can be set via the `:map_node` option using `Ecto.ERD.Node.set_cluster/2`. Install [Graphviz](https://graphviz.org/) to convert a `*.dot` file to an image. ``` $ mix ecto.gen.erd # generates ecto_erd.dot $ mix ecto.gen.erd --output-path=ecto_erd.dot $ mix ecto.gen.erd && dot -Tpng ecto_erd.dot -o erd.png && xdg-open erd.png ``` ## PlantUML [PlantUML](https://plantuml.com) supports the same features as DOT. Install [PlantUML](https://plantuml.com/download) to convert a `*.puml` file to an image. ``` $ mix ecto.gen.erd --output-path=erd.puml $ mix ecto.gen.erd --output-path=erd.puml && plantuml erd.puml && xdg-open erd.png ``` *Tip: If the output image is cropped, adjust the image size with the `PLANTUML_LIMIT_SIZE` environment variable.* ## DBML The [DBML](https://www.dbml.org/) format is more limited than DOT and PlantUML, as it focuses on tables only. Multiple schemas that use the same table are merged into one. Embedded schemas cannot be displayed. `TableGroup`s are supported and can be set via the `:map_node` option using `Ecto.ERD.Node.set_cluster/2`. This format is handy if you use [dbdiagram.io](https://dbdiagram.io) or [dbdocs.io](https://dbdocs.io). ``` $ mix ecto.gen.erd --output-path=ecto_erd.dbml ``` ## QuickDBD A format used by [QuickDBD](https://www.quickdatabasediagrams.com), a competitor of [dbdiagram.io](https://dbdiagram.io). Like DBML, it focuses on tables and cannot display embedded schemas. However, this format doesn't support clusters. It doesn't have a reserved file extension, but we use `*.qdbd`. ``` $ mix ecto.gen.erd --output-path=ecto_erd.qdbd ``` ## Mermaid [Mermaid](https://mermaid-js.github.io/mermaid/#/entityRelationshipDiagram) is also limited compared to [DOT](#module-dot) and [PlantUML](#module-plantuml) and focuses on tables only. Multiple schemas that use the same table are merged into one. Embedded schemas cannot be displayed. Clusters are not supported. The benefit of this format is that it is [supported by GitHub](https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/). ``` $ mix ecto.gen.erd --output-path=ecto_erd.mmd ``` If you have installed [mermaid-cli](https://github.com/mermaid-js/mermaid-cli) locally, you can convert the output `*.mmd` file to an image using the following command: ``` $ mmdc -i ecto_erd.mmd -o erd.png -w 6000 -H 6000 ``` The default `-w` and `-H` values are small (`800` and `600`, respectively), so it's better to provide larger values from the start. ## Command line options * `--output-path` - path to the output file. Defaults to `ecto_erd.dot`. Supported file extensions: `dot`, `puml`, `dbml`, `qdbd`. * `--config-path` - path to the config file. Defaults to `.ecto_erd.exs`. ## Configuration file When you run the `mix ecto.gen.erd` task, it tries to read configuration from the `.ecto_erd.exs` file in the current working directory. The configuration file must return a keyword list. ### Options * `:fontname` - font name. Defaults to `Roboto Mono`. Must be a monospaced font if the output format is `dot` and more than one column is displayed. Supported only for `dot` and `puml` files. * `:columns` - list of columns displayed for each node (schema/source). Set to `[]` to hide fields completely. Available columns: `:name`, `:type`. Supported for `dot`, `puml`, and `mmd` (`mmd` allows only `[]` or the default value). Default values: * `[:name, :type]` for `dot` and `puml` * `[:type, :name]` for `mmd` * `:map_node` - a function that removes a node from the diagram or assigns it to a cluster. Defaults to `Function.identity/1`, which means all nodes are displayed outside any cluster by default. Use `Ecto.ERD.Node.set_cluster/2` in this function to set a cluster. Supported only by [DOT](#module-dot), [PlantUML](#module-plantuml), and [DBML](#module-dbml). Return `nil` to remove a node from the diagram. * `:otp_app` - the application to scan (along with its dependencies) to collect Ecto schemas. Defaults to `Mix.Project.config()[:app]`. Configure this only when running the task from an umbrella root. A configuration file with default values for `dot` and `puml` can look like this: # .ecto_erd.exs [ fontname: "Roboto Mono", columns: [:name, :type], map_node: &Function.identity/1, otp_app: Mix.Project.config()[:app] ] """ @shortdoc "Generate an ERD" use Mix.Task @requirements ["app.config"] @impl true def run(args) do {cli_opts, _} = OptionParser.parse!(args, strict: [output_path: :string, config_path: :string]) config_path = Keyword.get(cli_opts, :config_path, ".ecto_erd.exs") file_opts = if File.exists?(config_path) do {file_opts, _} = Code.eval_file(config_path) file_opts else [] end otp_app = cond do Keyword.has_key?(file_opts, :otp_app) -> file_opts[:otp_app] not is_nil(Mix.Project.config()[:app]) -> Mix.Project.config()[:app] true -> raise "Unable to detect `:otp_app`, please specify it explicitly" end output_path = cli_opts[:output_path] || file_opts[:output_path] || "ecto_erd.dot" map_node_callback = file_opts[:map_node] || (&Function.identity/1) output = otp_app |> Ecto.ERD.SchemaModules.scan() |> Ecto.ERD.Document.render( Path.extname(output_path), map_node_callback, Keyword.take(file_opts, [:fontname, :columns]) ) File.write!(output_path, output) end end ================================================ FILE: mix.exs ================================================ # examples_generator.exs is needed only for docs and is not shipped in package if File.exists?("examples_generator.exs") do Code.compile_file("examples_generator.exs") else defmodule Ecto.ERD.ExamplesGenerator do def projects, do: [] def run(_), do: :noop end end defmodule Ecto.ERD.MixProject do use Mix.Project @source_url "https://github.com/fuelen/ecto_erd/" @version "0.6.6" def project do [ app: :ecto_erd, version: @version, elixir: "~> 1.12", start_permanent: Mix.env() == :prod, deps: deps(), package: package(), description: description(), name: "Ecto ERD", docs: docs(), aliases: [docs: [&generate_examples/1, "docs"]], dialyzer: [plt_add_apps: [:mix]] ] end defp description do "ERD generator for Ecto users" end defp docs do [ main: "Mix.Tasks.Ecto.Gen.Erd", extras: Enum.map(Ecto.ERD.ExamplesGenerator.projects(), fn project -> {:"tmp/docs/#{project}.md", [title: project]} end), source_url: @source_url, source_ref: "v#{@version}", groups_for_extras: [ Examples: ~r"tmp/docs/" ] ] end defp package do [ licenses: ["Apache-2.0"], links: %{ GitHub: @source_url } ] end def application do [ extra_applications: [:logger] ] end defp generate_examples(_) do run? = "Generate examples? y/n: " |> IO.gets() |> String.trim() |> String.downcase() == "y" if run? do Ecto.ERD.ExamplesGenerator.run(Path.join([@source_url, "blob", "v#{@version}"])) end end defp deps do [ {:ex_doc, "~> 0.24", only: :dev, runtime: false}, {:html_entities, "~> 0.5"}, {:ecto, "~> 3.12"}, {:dialyxir, "~> 1.0", only: [:dev], runtime: false} ] end end ================================================ FILE: test/ecto/erd_test.exs ================================================ defmodule Ecto.ERDTest do use ExUnit.Case alias Ecto.ERD.{Node, Field, Graph} alias Ecto.ERD.Document.{DBML, Dot} test inspect(&DBML.enums_mapping/1) do result = [ %Node{ source: "credentials", fields: [ Field.new(%{ name: :flow, type: {:parameterized, {Ecto.Enum, Ecto.Enum.init(values: [:simple, :complex])}} }) ] }, %Node{ source: "invitations", fields: [ Field.new(%{ name: :flow, type: {:parameterized, {Ecto.Enum, Ecto.Enum.init(values: [:simple, :complex])}} }) ] }, %Node{ source: "users", fields: [ Field.new(%{ name: :status, type: {:parameterized, {Ecto.Enum, Ecto.Enum.init(values: [:active, :suspended, :invited])}} }) ] }, %Node{ source: "admins", fields: [ Field.new(%{ name: :status, type: {:parameterized, {Ecto.Enum, Ecto.Enum.init(values: [:active, :suspended])}} }) ] }, %Node{ source: "projects", fields: [ Field.new(%{ name: :status, type: {:parameterized, {Ecto.Enum, Ecto.Enum.init(values: [:live, :closed])}} }) ] } ] |> DBML.enums_mapping() assert result == %{ ["admins", :status] => {"admins_status", ["active", "suspended"]}, ["credentials", :flow] => {"flow", ["complex", "simple"]}, ["invitations", :flow] => {"flow", ["complex", "simple"]}, ["projects", :status] => {"projects_status", ["closed", "live"]}, ["users", :status] => {"users_status", ["active", "invited", "suspended"]} } end test "DOT format renders primary key indicator" do graph = %Graph{ nodes: [ %Node{ source: "users", schema_module: MyApp.User, fields: [ Field.new(%{name: :id, type: :integer, primary?: true}), Field.new(%{name: :email, type: :string, primary?: false}), Field.new(%{name: :name, type: :string, primary?: false}) ] } ], edges: [] } result = Dot.render(graph, []) # Verify that the primary key field is bold assert result =~ ~r/:id\s*<\/b>/ # Verify that non-primary fields are not bold refute result =~ ~r/:email\s*<\/b>/ refute result =~ ~r/:name\s*<\/b>/ end end ================================================ FILE: test/test_helper.exs ================================================ ExUnit.start()