├── .eslintignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── config.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── build.yml ├── .gitignore ├── .husky ├── .gitignore ├── commit-msg └── pre-commit ├── .npmrc ├── .rspec ├── .rubocop.yml ├── .standard.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── lint ├── rake ├── rspec ├── rubocop └── standardrb ├── gemfiles ├── Gemfile-rails-edge ├── Gemfile-rails.7.1.x └── Gemfile-rails.7.2.x ├── netlify.toml ├── package.json ├── playground └── vanilla │ ├── .browserslistrc │ ├── .gitignore │ ├── .npmrc │ ├── .pnpm-debug.log │ ├── .rspec │ ├── Gemfile │ ├── Gemfile.lock │ ├── Procfile.dev │ ├── Rakefile │ ├── app │ ├── controllers │ │ ├── application_controller.rb │ │ ├── composers_controller.rb │ │ ├── songs_controller.rb │ │ └── videos_controller.rb │ ├── frontend │ │ ├── api │ │ │ ├── ComposersApi.ts │ │ │ ├── SongsApi.ts │ │ │ ├── VideosApi.ts │ │ │ ├── all.ts │ │ │ └── index.ts │ │ ├── app.ts │ │ ├── components.d.ts │ │ ├── components │ │ │ ├── InertiaLink.vue │ │ │ ├── Link.vue │ │ │ ├── PageList.vue │ │ │ ├── PageSubtitle.vue │ │ │ ├── PageTitle.vue │ │ │ ├── TheNavBar.vue │ │ │ └── YouTubePlayer.vue │ │ ├── composables │ │ │ └── page.ts │ │ ├── entrypoints │ │ │ └── application.ts │ │ ├── helpers │ │ │ └── object.ts │ │ ├── index.d.ts │ │ ├── inertia.ts │ │ ├── layouts │ │ │ └── DefaultLayout.vue │ │ ├── pages.ts │ │ ├── pages │ │ │ ├── composers │ │ │ │ ├── index.vue │ │ │ │ └── show.vue │ │ │ ├── songs │ │ │ │ ├── index.vue │ │ │ │ └── show.vue │ │ │ └── videos │ │ │ │ └── index.vue │ │ ├── services │ │ │ └── axios.ts │ │ ├── tsconfig.json │ │ ├── types │ │ │ ├── AnyModel.ts │ │ │ └── serializers │ │ │ │ ├── Composer.ts │ │ │ │ ├── ComposerWithSongs.ts │ │ │ │ ├── ComposerWithSongs │ │ │ │ └── Song.ts │ │ │ │ ├── Model.ts │ │ │ │ ├── Nested │ │ │ │ └── Album.ts │ │ │ │ ├── SnakeComposer.ts │ │ │ │ ├── Song.ts │ │ │ │ ├── SongWithVideos.ts │ │ │ │ ├── Video.ts │ │ │ │ ├── VideoWithSong.ts │ │ │ │ └── index.ts │ │ └── windi.config.ts │ ├── models │ │ ├── application_record.rb │ │ ├── composer.rb │ │ ├── song.rb │ │ └── video_clip.rb │ ├── serializers │ │ ├── base_serializer.rb │ │ ├── composer_serializer.rb │ │ ├── composer_with_songs_serializer.rb │ │ ├── model_serializer.rb │ │ ├── nested │ │ │ └── album_serializer.rb │ │ ├── snake_composer_serializer.rb │ │ ├── song_serializer.rb │ │ ├── song_with_videos_serializer.rb │ │ ├── video_serializer.rb │ │ └── video_with_song_serializer.rb │ └── views │ │ └── layouts │ │ └── application.html.erb │ ├── bin │ ├── bundle │ ├── rails │ ├── rake │ ├── setup │ └── vite │ ├── config.ru │ ├── config │ ├── application.rb │ ├── boot.rb │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ └── test.rb │ ├── initializers │ │ ├── application_controller_renderer.rb │ │ ├── backtrace_silencers.rb │ │ ├── content_security_policy.rb │ │ ├── cookies_serializer.rb │ │ ├── filter_parameter_logging.rb │ │ ├── inflections.rb │ │ ├── js_from_routes.rb │ │ ├── mime_types.rb │ │ ├── types_from_serializers.rb │ │ └── wrap_parameters.rb │ ├── locales │ │ └── en.yml │ ├── puma.rb │ ├── routes.rb │ └── vite.json │ ├── db │ ├── migrate │ │ ├── 20220709144820_create_composers.rb │ │ ├── 20220709151241_create_video_clips.rb │ │ ├── 20220709151259_create_songs.rb │ │ └── 20240227112250_add_enums_to_songs.rb │ ├── schema.rb │ └── seeds.rb │ ├── package.json │ ├── pnpm-lock.yaml │ ├── public │ ├── 404.html │ ├── 422.html │ ├── 500.html │ ├── apple-touch-icon-precomposed.png │ ├── apple-touch-icon.png │ ├── favicon.ico │ └── robots.txt │ └── vite.config.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── scripts ├── changelog.js ├── release.js └── verifyCommit.js ├── spec ├── spec_helper.rb └── types_from_serializers │ ├── __snapshots__ │ ├── interfaces_ComposerSerializer.snap │ ├── interfaces_ComposerWithSongsSerializer.snap │ ├── interfaces_ComposerWithSongsSerializer__SongSerializer.snap │ ├── interfaces_ModelSerializer.snap │ ├── interfaces_Nested__AlbumSerializer.snap │ ├── interfaces_SnakeComposerSerializer.snap │ ├── interfaces_SongSerializer.snap │ ├── interfaces_SongWithVideosSerializer.snap │ ├── interfaces_VideoSerializer.snap │ ├── interfaces_VideoWithSongSerializer.snap │ ├── interfaces_index.snap │ ├── namespace_interfaces_ComposerSerializer.snap │ ├── namespace_interfaces_ComposerWithSongsSerializer.snap │ ├── namespace_interfaces_ComposerWithSongsSerializer__SongSerializer.snap │ ├── namespace_interfaces_ModelSerializer.snap │ ├── namespace_interfaces_Nested__AlbumSerializer.snap │ ├── namespace_interfaces_SnakeComposerSerializer.snap │ ├── namespace_interfaces_SongSerializer.snap │ ├── namespace_interfaces_SongWithVideosSerializer.snap │ ├── namespace_interfaces_VideoSerializer.snap │ └── namespace_interfaces_VideoWithSongSerializer.snap │ └── generator_spec.rb ├── tsconfig.json └── types_from_serializers ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin └── release ├── lib ├── types_from_serializers.rb └── types_from_serializers │ ├── dsl.rb │ ├── generator.rb │ ├── railtie.rb │ └── version.rb └── types_from_serializers.gemspec /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | coverage/ 3 | build/ 4 | examples/ 5 | public/ 6 | node_modules/ 7 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | }, 5 | extends: ['@antfu/eslint-config'], 6 | rules: { 7 | '@typescript-eslint/space-before-function-paren': ['warn', 'always'], 8 | '@typescript-eslint/quotes': 'off', 9 | 'vue/attribute-hyphenation': ['warn', 'never'], 10 | 'vue/html-closing-bracket-spacing': ['warn', { 11 | startTag: 'never', 12 | endTag: 'never', 13 | selfClosingTag: 'never', 14 | }], 15 | }, 16 | globals: { 17 | defineProps: 'readonly', 18 | defineEmits: 'readonly', 19 | defineExpose: 'readonly', 20 | withDefaults: 'readonly', 21 | $: 'readonly', 22 | $$: 'readonly', 23 | $ref: 'readonly', 24 | $computed: 'readonly', 25 | }, 26 | } 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'bug: pending triage' 6 | assignees: '' 7 | --- 8 | 9 | [troubleshooting section]: https://types-from-serializers.netlify.app/faqs/ 10 | 11 | - [ ] I have tried upgrading by running `bundle update types_from_serializers`. 12 | - [ ] I have read the __[troubleshooting section]__ before opening an issue. 13 | 14 | ### Description 📖 15 | 16 | _Provide a clear and concise description of what the bug is._ 17 | 18 | ### Reproduction/Logs 🐞📜 19 | 20 | _Please provide a link to a repo that can reproduce the problem you ran into, or logs about the problem._ 21 | 22 | ### Screenshots 📷 23 | 24 | _Provide console or browser screenshots of the problem_. 25 | 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Troubleshooting & FAQs 4 | url: https://types-from-serializers.netlify.app/faqs/ 5 | about: 'Please check the most common problems before opening an issue' 6 | - name: Questions & Discussions 7 | url: https://github.com/ElMassimo/types_from_serializers/discussions 8 | about: Use GitHub discussions for message-board style questions and discussions. 9 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Description 📖 2 | 3 | This pull request 4 | 5 | ### Background 📜 6 | 7 | This was happening because 8 | 9 | ### The Fix 🔨 10 | 11 | By changing 12 | 13 | ### Screenshots 📷 14 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - '**' 10 | 11 | jobs: 12 | lint: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - uses: pnpm/action-setup@v4 19 | with: 20 | version: 9.8.0 21 | 22 | - uses: actions/setup-node@v4 23 | with: 24 | cache: 'pnpm' 25 | node-version: 22 26 | 27 | - run: pnpm install --frozen-lockfile 28 | 29 | - uses: ruby/setup-ruby@v1 30 | with: 31 | ruby-version: "3.0" 32 | bundler-cache: true 33 | 34 | - name: Rubocop 35 | run: bin/rubocop 36 | 37 | - name: Clean Types 38 | run: pnpm clean 39 | 40 | - name: Run Migrations 41 | run: pnpm migrate 42 | 43 | - name: Generate Types 44 | run: pnpm gen 45 | 46 | - name: Type Check 47 | run: pnpm tsc 48 | 49 | - name: ESLint 50 | run: pnpm lint 51 | 52 | test: 53 | name: test 54 | runs-on: ${{ matrix.os }} 55 | continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'debug' || matrix.experimental }} 56 | strategy: 57 | fail-fast: false 58 | matrix: 59 | os: [ubuntu-latest] 60 | ruby: [ 61 | "3.2", 62 | "3.3", 63 | ] 64 | gemfile: [ 65 | "Gemfile-rails.7.1.x", 66 | "Gemfile-rails.7.2.x", 67 | ] 68 | experimental: [false] 69 | include: 70 | - ruby: "3.3" 71 | os: ubuntu-latest 72 | gemfile: Gemfile-rails-edge 73 | experimental: true 74 | env: 75 | BUNDLE_JOBS: 4 76 | BUNDLE_RETRY: 3 77 | BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }} 78 | 79 | steps: 80 | - uses: actions/checkout@v4 81 | 82 | - uses: ruby/setup-ruby@v1 83 | with: 84 | ruby-version: ${{ matrix.ruby }} 85 | bundler-cache: true 86 | 87 | - name: Setup Code Climate test-reporter 88 | run: | 89 | curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter 90 | chmod +x ./cc-test-reporter 91 | ./cc-test-reporter before-build 92 | 93 | - name: Run Migrations 94 | run: cd playground/vanilla && bin/rails db:prepare 95 | 96 | - name: Ruby Specs 97 | run: bin/rspec 98 | 99 | - name: Upload code coverage to Code Climate 100 | if: ${{ contains(github.ref, 'main') }} 101 | run: | 102 | export GIT_BRANCH="${GITHUB_REF/refs\/heads\//}" 103 | ./cc-test-reporter after-build -r ${{secrets.CC_TEST_REPORTER_ID}} 104 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | spec/support/generated 2 | spec/support/sample_app/bin 3 | 4 | /.bundle 5 | pkg 6 | log 7 | tmp 8 | node_modules 9 | .byebug_history 10 | yarn-debug.log* 11 | yarn-error.log* 12 | .yarn-integrity 13 | gemfiles/*.lock 14 | .DS_Store 15 | 16 | # Vite on Rails 17 | /public/vite 18 | /public/vite-dev 19 | /public/vite-test 20 | test/test_app/public/vite-production 21 | node_modules 22 | *.local 23 | .DS_Store 24 | 25 | # Vitepress 26 | dist 27 | examples_dist 28 | node_modules 29 | coverage 30 | .nyc_output 31 | .rpt2_cache 32 | .env 33 | local.log 34 | .DS_Store 35 | e2e/reports 36 | e2e/screenshots 37 | __build__ 38 | playground_dist 39 | yarn-error.log 40 | temp 41 | markdown 42 | explorations 43 | selenium-server.log 44 | 45 | # Algolia 46 | .algolia.env 47 | 48 | # Hanami 49 | .env.local 50 | .env.*.local 51 | 52 | docs/pnpm-lock.yaml 53 | docs 54 | 55 | # Ruby version 56 | .ruby-version 57 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | node scripts/verifyCommit.js "$1" 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged || echo "⚠️ WARNING: This commit failed some of the linter checks. ⚠️" 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | ignore-workspace-root-check=true 2 | shamefully-hoist=true 3 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format=progress 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: 2 | - standard 3 | - rubocop-rails 4 | - rubocop-rspec 5 | - rubocop-performance 6 | 7 | inherit_mode: 8 | merge: 9 | - Exclude 10 | 11 | inherit_gem: 12 | standard: config/base.yml 13 | 14 | AllCops: 15 | SuggestExtensions: false 16 | NewCops: enable 17 | 18 | Exclude: 19 | - bin/**/* 20 | - node_modules/**/* 21 | - public/**/* 22 | - bin/bundle 23 | - playground/vanilla/bin/bundle 24 | 25 | Rails: 26 | Enabled: true # enable rubocop-rails cops 27 | Rails/Delegate: 28 | Enabled: false 29 | RSpec: 30 | Enabled: true # enable rubocop-rspec cops 31 | RSpec/DescribeClass: 32 | Enabled: false # ignore missing comments on classes 33 | RSpec/MultipleExpectations: 34 | Enabled: false 35 | RSpec/ExampleLength: 36 | Enabled: false 37 | RSpec/DescribedClass: 38 | Enabled: false 39 | RSpec/Capybara/FeatureMethods: 40 | Enabled: false 41 | 42 | # Trailing Commas 43 | Style/TrailingCommaInArrayLiteral: 44 | EnforcedStyleForMultiline: comma 45 | Style/TrailingCommaInHashLiteral: 46 | EnforcedStyleForMultiline: comma 47 | Style/TrailingCommaInArguments: 48 | EnforcedStyleForMultiline: comma 49 | 50 | # Outdent Access Modifier 51 | Layout/AccessModifierIndentation: 52 | EnforcedStyle: outdent 53 | -------------------------------------------------------------------------------- /.standard.yml: -------------------------------------------------------------------------------- 1 | fix: true 2 | ignore: 3 | - 'packages/**/*' 4 | - 'scripts/**/*' 5 | - 'playground/**/*' 6 | - '**/*': 7 | - Style/TrailingCommaInArrayLiteral 8 | - Style/TrailingCommaInHashLiteral 9 | - Style/TrailingCommaInArguments 10 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at maximomussini@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Setting Up a Development Environment 2 | 3 | 1. Install [pnpm](https://pnpm.js.org/) 4 | 5 | 2. Run the following commands to set up the development environment. 6 | 7 | ``` 8 | bundle install 9 | ``` 10 | 11 | ``` 12 | pnpm install 13 | ``` 14 | 15 | ``` 16 | pnpm migrate 17 | ``` 18 | 19 | ## Making sure your changes pass all tests 20 | 21 | There are a number of automated checks which run on GitHub Actions when a pull request is created. 22 | You can run those checks on your own locally to make sure that your changes would not break the CI build. 23 | 24 | ### 1. Check the code for JavaScript style violations 25 | 26 | ``` 27 | pnpm lint 28 | ``` 29 | 30 | ### 2. Check the code for Ruby style violations 31 | 32 | ``` 33 | bin/rubocop 34 | ``` 35 | 36 | ### 3. Run the test suite 37 | 38 | ``` 39 | bin/rspec 40 | ``` 41 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec path: "./types_from_serializers" 4 | 5 | gem "pry-byebug", require: false 6 | gem "debug" 7 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: types_from_serializers 3 | specs: 4 | types_from_serializers (2.4.0) 5 | listen (~> 3.2) 6 | oj_serializers (~> 2.0, >= 2.0.2) 7 | railties (>= 5.1) 8 | 9 | GEM 10 | remote: https://rubygems.org/ 11 | specs: 12 | actionpack (7.0.4.3) 13 | actionview (= 7.0.4.3) 14 | activesupport (= 7.0.4.3) 15 | rack (~> 2.0, >= 2.2.0) 16 | rack-test (>= 0.6.3) 17 | rails-dom-testing (~> 2.0) 18 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 19 | actionview (7.0.4.3) 20 | activesupport (= 7.0.4.3) 21 | builder (~> 3.1) 22 | erubi (~> 1.4) 23 | rails-dom-testing (~> 2.0) 24 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 25 | activemodel (7.0.4.3) 26 | activesupport (= 7.0.4.3) 27 | activerecord (7.0.4.3) 28 | activemodel (= 7.0.4.3) 29 | activesupport (= 7.0.4.3) 30 | activesupport (7.0.4.3) 31 | concurrent-ruby (~> 1.0, >= 1.0.2) 32 | i18n (>= 1.6, < 2) 33 | minitest (>= 5.1) 34 | tzinfo (~> 2.0) 35 | ast (2.4.2) 36 | awesome_print (1.9.2) 37 | builder (3.2.4) 38 | byebug (11.1.3) 39 | coderay (1.1.3) 40 | concurrent-ruby (1.2.2) 41 | crass (1.0.6) 42 | debug (1.7.2) 43 | irb (>= 1.5.0) 44 | reline (>= 0.3.1) 45 | diff-lcs (1.5.0) 46 | docile (1.4.0) 47 | erubi (1.12.0) 48 | ffi (1.15.5) 49 | given_core (3.8.2) 50 | sorcerer (>= 0.3.7) 51 | i18n (1.12.0) 52 | concurrent-ruby (~> 1.0) 53 | io-console (0.6.0) 54 | irb (1.6.3) 55 | reline (>= 0.3.0) 56 | js_from_routes (2.1.0) 57 | railties (>= 5.1, < 8) 58 | json (2.6.3) 59 | language_server-protocol (3.17.0.3) 60 | listen (3.8.0) 61 | rb-fsevent (~> 0.10, >= 0.10.3) 62 | rb-inotify (~> 0.9, >= 0.9.10) 63 | loofah (2.20.0) 64 | crass (~> 1.0.2) 65 | nokogiri (>= 1.5.9) 66 | method_source (1.0.0) 67 | mini_portile2 (2.8.1) 68 | minitest (5.18.0) 69 | nokogiri (1.14.2) 70 | mini_portile2 (~> 2.8.0) 71 | racc (~> 1.4) 72 | oj (3.14.2) 73 | oj_serializers (2.0.2) 74 | oj (>= 3.14.0) 75 | parallel (1.22.1) 76 | parser (3.2.2.0) 77 | ast (~> 2.4.1) 78 | pry (0.14.2) 79 | coderay (~> 1.1) 80 | method_source (~> 1.0) 81 | pry-byebug (3.10.1) 82 | byebug (~> 11.0) 83 | pry (>= 0.13, < 0.15) 84 | racc (1.6.2) 85 | rack (2.2.6.4) 86 | rack-test (2.1.0) 87 | rack (>= 1.3) 88 | rails-dom-testing (2.0.3) 89 | activesupport (>= 4.2.0) 90 | nokogiri (>= 1.6) 91 | rails-html-sanitizer (1.5.0) 92 | loofah (~> 2.19, >= 2.19.1) 93 | railties (7.0.4.3) 94 | actionpack (= 7.0.4.3) 95 | activesupport (= 7.0.4.3) 96 | method_source 97 | rake (>= 12.2) 98 | thor (~> 1.0) 99 | zeitwerk (~> 2.5) 100 | rainbow (3.1.1) 101 | rake (13.0.6) 102 | rb-fsevent (0.11.2) 103 | rb-inotify (0.10.1) 104 | ffi (~> 1.0) 105 | regexp_parser (2.7.0) 106 | reline (0.3.3) 107 | io-console (~> 0.5) 108 | rexml (3.2.5) 109 | rspec (3.12.0) 110 | rspec-core (~> 3.12.0) 111 | rspec-expectations (~> 3.12.0) 112 | rspec-mocks (~> 3.12.0) 113 | rspec-core (3.12.1) 114 | rspec-support (~> 3.12.0) 115 | rspec-expectations (3.12.2) 116 | diff-lcs (>= 1.2.0, < 2.0) 117 | rspec-support (~> 3.12.0) 118 | rspec-given (3.8.2) 119 | given_core (= 3.8.2) 120 | rspec (>= 2.14.0) 121 | rspec-mocks (3.12.5) 122 | diff-lcs (>= 1.2.0, < 2.0) 123 | rspec-support (~> 3.12.0) 124 | rspec-snapshot (2.0.1) 125 | awesome_print (> 1.0.0) 126 | rspec (> 3.0.0) 127 | rspec-support (3.12.0) 128 | rubocop (1.48.1) 129 | json (~> 2.3) 130 | parallel (~> 1.10) 131 | parser (>= 3.2.0.0) 132 | rainbow (>= 2.2.2, < 4.0) 133 | regexp_parser (>= 1.8, < 3.0) 134 | rexml (>= 3.2.5, < 4.0) 135 | rubocop-ast (>= 1.26.0, < 2.0) 136 | ruby-progressbar (~> 1.7) 137 | unicode-display_width (>= 2.4.0, < 3.0) 138 | rubocop-ast (1.28.0) 139 | parser (>= 3.2.1.0) 140 | rubocop-capybara (2.17.1) 141 | rubocop (~> 1.41) 142 | rubocop-performance (1.16.0) 143 | rubocop (>= 1.7.0, < 2.0) 144 | rubocop-ast (>= 0.4.0) 145 | rubocop-rails (2.18.0) 146 | activesupport (>= 4.2.0) 147 | rack (>= 1.1) 148 | rubocop (>= 1.33.0, < 2.0) 149 | rubocop-rspec (2.19.0) 150 | rubocop (~> 1.33) 151 | rubocop-capybara (~> 2.17) 152 | ruby-progressbar (1.13.0) 153 | simplecov (0.17.1) 154 | docile (~> 1.1) 155 | json (>= 1.8, < 3) 156 | simplecov-html (~> 0.10.0) 157 | simplecov-html (0.10.2) 158 | sorcerer (2.0.1) 159 | sqlite3 (1.6.2) 160 | mini_portile2 (~> 2.8.0) 161 | standard (1.26.0) 162 | language_server-protocol (~> 3.17.0.2) 163 | rubocop (~> 1.48.1) 164 | rubocop-performance (~> 1.16.0) 165 | thor (1.2.1) 166 | tzinfo (2.0.6) 167 | concurrent-ruby (~> 1.0) 168 | unicode-display_width (2.4.2) 169 | zeitwerk (2.6.7) 170 | 171 | PLATFORMS 172 | ruby 173 | 174 | DEPENDENCIES 175 | activerecord 176 | bundler (~> 2) 177 | debug 178 | js_from_routes 179 | pry-byebug 180 | rake (~> 13) 181 | rspec-given (~> 3.8) 182 | rspec-snapshot 183 | rubocop 184 | rubocop-performance 185 | rubocop-rails 186 | rubocop-rspec 187 | simplecov (< 0.18) 188 | sqlite3 189 | standard (~> 1.0) 190 | types_from_serializers! 191 | 192 | BUNDLED WITH 193 | 2.4.10 194 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Máximo Mussini 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Types From Serializers 3 |

4 | Build Status 5 | Maintainability 6 | 7 | Gem Version 8 | License 9 |

10 |

11 | 12 | [oj]: https://github.com/ohler55/oj 13 | [oj_serializers]: https://github.com/ElMassimo/oj_serializers 14 | [ams]: https://github.com/rails-api/active_model_serializers 15 | [Rails]: https://github.com/rails/rails 16 | [Issues]: https://github.com/ElMassimo/types_from_serializers/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc 17 | [Discussions]: https://github.com/ElMassimo/types_from_serializers/discussions 18 | [TypeScript]: https://www.typescriptlang.org/ 19 | [Vite Ruby]: https://github.com/ElMassimo/vite_ruby 20 | [vite-plugin-full-reload]: https://github.com/ElMassimo/vite-plugin-full-reload 21 | [base_serializers]: https://github.com/ElMassimo/types_from_serializers#base_serializers 22 | [config]: https://github.com/ElMassimo/types_from_serializers#configuration-%EF%B8%8F 23 | 24 | Automatically generate TypeScript interfaces from your [JSON serializers][oj_serializers]. 25 | 26 | _Currently, this library targets [`oj_serializers`][oj_serializers] and `ActiveRecord` in [Rails] applications_. 27 | 28 | ## Demo 🎬 29 | 30 | For a schema such as [this one](https://github.com/ElMassimo/types_from_serializers/blob/main/playground/vanilla/db/schema.rb): 31 | 32 |
33 | DB Schema 34 | 35 | ```ruby 36 | create_table "composers", force: :cascade do |t| 37 | t.text "first_name" 38 | t.text "last_name" 39 | t.datetime "created_at", precision: 6, null: false 40 | t.datetime "updated_at", precision: 6, null: false 41 | end 42 | 43 | create_table "songs", force: :cascade do |t| 44 | t.text "title" 45 | t.integer "composer_id" 46 | t.datetime "created_at", precision: 6, null: false 47 | t.datetime "updated_at", precision: 6, null: false 48 | end 49 | 50 | create_table "video_clips", force: :cascade do |t| 51 | t.text "title" 52 | t.text "youtube_id" 53 | t.integer "song_id" 54 | t.integer "composer_id" 55 | t.datetime "created_at", precision: 6, null: false 56 | t.datetime "updated_at", precision: 6, null: false 57 | end 58 | ``` 59 |
60 | 61 | and a serializer like the following: 62 | 63 | ```ruby 64 | class VideoSerializer < BaseSerializer 65 | object_as :video, model: :VideoClip 66 | 67 | attributes :id, :created_at, :title, :youtube_id 68 | 69 | type :string, optional: true 70 | def youtube_url 71 | "https://www.youtube.com/watch?v=#{video.youtube_id}" if video.youtube_id 72 | end 73 | 74 | has_one :song, serializer: SongSerializer 75 | end 76 | ``` 77 | 78 | it would generate a TypeScript interface like: 79 | 80 | ```ts 81 | import type Song from './Song' 82 | 83 | export default interface Video { 84 | id: number 85 | createdAt: string | Date 86 | title?: string 87 | youtubeId?: string 88 | youtubeUrl?: string 89 | song: Song 90 | } 91 | ``` 92 | 93 | > **Note** 94 | > 95 | > This is the default configuration, but you have [full control][config] over generation. 96 | 97 | 98 | ## Why? 🤔 99 | 100 | It's easy for the backend and the frontend to become out of sync. 101 | Traditionally, preventing bugs requires writing extensive integration tests. 102 | 103 | [TypeScript] is a great tool to catch this kind of bugs and mistakes, as it can 104 | detect incorrect usages and missing fields, but writing types manually is 105 | cumbersome, and they can become stale over time, giving a false sense of confidence. 106 | 107 | This library takes advantage of the declarative nature of serializer libraries 108 | such as [`active_model_serializers`][ams] and [`oj_serializers`][oj_serializers], 109 | extending them to allow embedding type information, as well as inferring types 110 | from the SQL schema when available. 111 | 112 | As a result, it's posible to easily detect mismatches between the backend and 113 | the frontend, as well as make the fields more discoverable and provide great 114 | autocompletion in the frontend, without having to manually write the types. 115 | 116 | ## Features ⚡️ 117 | 118 | - Start simple, no additional syntax required 119 | - Infers types from a related `ActiveRecord` model, using the SQL schema 120 | - Understands JS native types and how to map SQL columns: `string`, `boolean`, etc 121 | - Automatically types [associations](https://github.com/ElMassimo/oj_serializers#associations-dsl-), importing the generated types for the referenced serializers 122 | - Detects [conditional attributes](https://github.com/ElMassimo/oj_serializers#rendering-an-attribute-conditionally) and marks them as optional: `name?: string` 123 | - Fallback to a custom interface using `type_from` 124 | - Supports custom types and automatically adds the necessary imports 125 | 126 | 127 | ## Installation 💿 128 | 129 | Add this line to your application's Gemfile: 130 | 131 | ```ruby 132 | gem 'types_from_serializers' 133 | ``` 134 | 135 | And then run: 136 | 137 | $ bundle install 138 | 139 | ## Usage 🚀 140 | 141 | To get started, [create a `BaseSerializer`](https://github.com/ElMassimo/types_from_serializers/blob/main/playground/vanilla/app/serializers/base_serializer.rb) that extends [`Oj::Serializer`][oj_serializers], and include the `TypesFromSerializers::DSL` module. 142 | 143 | ```ruby 144 | # app/serializers/base_serializer.rb 145 | 146 | class BaseSerializer < Oj::Serializer 147 | include TypesFromSerializers::DSL 148 | end 149 | ``` 150 | 151 | > **Note** 152 | > 153 | > You can customize this behavior using [`base_serializers`][base_serializers]. 154 | 155 | > **Warning** 156 | > 157 | > All serializers should extend one of the [`base_serializers`][base_serializers], or they won't be 158 | detected. 159 | 160 | 161 | ### SQL Attributes 162 | 163 | In most cases, you'll want to let `TypesFromSerializers` infer the types from the [SQL schema](https://github.com/ElMassimo/types_from_serializers/blob/main/playground/vanilla/db/schema.rb). 164 | 165 | If you are using `ActiveRecord`, the model related to the serializer will be inferred can be inferred from the serializer name: 166 | 167 | ```ruby 168 | UserSerializer => User 169 | ``` 170 | 171 | It can also be inferred from an [object alias](https://github.com/ElMassimo/oj_serializers#using-a-different-alias-for-the-internal-object) if provided: 172 | 173 | ```ruby 174 | class PersonSerializer < BaseSerializer 175 | object_as :user 176 | ``` 177 | 178 | In cases where we want to use a different alias, you can provide the model name explicitly: 179 | 180 | ```ruby 181 | class PersonSerializer < BaseSerializer 182 | object_as :person, model: :User 183 | ``` 184 | 185 | ### Model Attributes 186 | 187 | When you want to be more strict than the SQL schema, or for attributes that are methods in the model, you can use: 188 | 189 | ```ruby 190 | attributes( 191 | name: {type: :string}, 192 | status: {type: :Status}, # a custom type in ~/types/Status.ts 193 | ) 194 | ``` 195 | 196 | ### Serializer Attributes 197 | 198 | For attributes defined in the serializer, use the `type` helper: 199 | 200 | ```ruby 201 | type :boolean 202 | def suspended 203 | user.status.suspended? 204 | end 205 | ``` 206 | 207 | > **Note** 208 | > 209 | > When specifying a type, [`attribute`](https://github.com/ElMassimo/oj_serializers#serializer_attributes) will be called automatically. 210 | 211 | ### Fallback Attributes 212 | 213 | You can also specify `types_from` to provide a TypeScript interface that should 214 | be used to obtain the field types: 215 | 216 | ```ruby 217 | class LocationSerializer < BaseSerializer 218 | object_as :location, types_from: :GoogleMapsLocation 219 | 220 | attributes( 221 | :lat, 222 | :lng, 223 | ) 224 | end 225 | ``` 226 | 227 | ```ts 228 | import GoogleMapsLocation from '~/types/GoogleMapsLocation' 229 | 230 | export default interface Location { 231 | lat: GoogleMapsLocation['lat'] 232 | lng: GoogleMapsLocation['lng'] 233 | } 234 | ``` 235 | 236 | ## Generation 📜 237 | 238 | To get started, run `bin/rails s` to start the `Rails` development server. 239 | 240 | `TypesFromSerializers` will automatically register a `Rails` reloader, which 241 | detects changes to serializer files, and will generate code on-demand only for 242 | the modified files. 243 | 244 | It can also detect when new serializer files are added, or removed, and update 245 | the generated code accordingly. 246 | 247 | ### Manually 248 | 249 | To generate types manually, use the rake task: 250 | 251 | ``` 252 | bundle exec rake types_from_serializers:generate 253 | ``` 254 | 255 | or if you prefer to do it manually from the console: 256 | 257 | ```ruby 258 | require "types_from_serializers/generator" 259 | 260 | TypesFromSerializers.generate(force: true) 261 | ``` 262 | 263 | ### With [`vite-plugin-full-reload`][vite-plugin-full-reload] ⚡️ 264 | 265 | When using _[Vite Ruby]_, you can add [`vite-plugin-full-reload`][vite-plugin-full-reload] 266 | to automatically reload the page when modifying serializers, causing the Rails 267 | reload process to be triggered, which is when generation occurs. 268 | 269 | ```ts 270 | // vite.config.ts 271 | import { defineConfig } from 'vite' 272 | import ruby from 'vite-plugin-ruby' 273 | import reloadOnChange from 'vite-plugin-full-reload' 274 | 275 | defineConfig({ 276 | plugins: [ 277 | ruby(), 278 | reloadOnChange(['app/serializers/**/*.rb'], { delay: 200 }), 279 | ], 280 | }) 281 | ``` 282 | 283 | As a result, when modifying a serializer and hitting save, the type for that 284 | serializer will be updated instantly! 285 | 286 | ## Configuration ⚙️ 287 | 288 | You can configure generation in a Rails initializer: 289 | 290 | ```ruby 291 | # config/initializers/types_from_serializers.rb 292 | 293 | if Rails.env.development? 294 | TypesFromSerializers.config do |config| 295 | config.name_from_serializer = ->(name) { name } 296 | end 297 | end 298 | ``` 299 | 300 | ### `namespace` 301 | 302 | _Default:_ `nil` 303 | 304 | Allows to specify a TypeScript namespace and generate `.d.ts` to make types 305 | available globally, avoiding the need to import types explicitly. 306 | 307 | ### `base_serializers` 308 | 309 | _Default:_ `["BaseSerializer"]` 310 | 311 | Allows you to specify the base serializers, that are used to detect other 312 | serializers in the app that you would like to generate interfaces for. 313 | 314 | ### `serializers_dirs` 315 | 316 | _Default:_ `["app/serializers"]` 317 | 318 | The dirs where the serializer files are located. 319 | 320 | ### `output_dir` 321 | 322 | _Default:_ `"app/frontend/types/serializers"` 323 | 324 | The dir where the generated TypeScript interface files are placed. 325 | 326 | ### `custom_types_dir` 327 | 328 | _Default:_ `"app/frontend/types"` 329 | 330 | The dir where the custom types are placed. 331 | 332 | ### `name_from_serializer` 333 | 334 | _Default:_ `->(name) { name.delete_suffix("Serializer") }` 335 | 336 | A `Proc` that specifies how to convert the name of the serializer into the name 337 | of the generated TypeScript interface. 338 | 339 | ### `global_types` 340 | 341 | _Default:_ `["Array", "Record", "Date"]` 342 | 343 | Types that don't need to be imported in TypeScript. 344 | 345 | You can extend this list as needed if you are using global definitions. 346 | 347 | ### `skip_serializer_if` 348 | 349 | _Default:_ `->(serializer) { false }` 350 | 351 | You can provide a proc to avoid generating serializers. 352 | 353 | Along with `base_serializers`, this provides more fine-grained control in cases 354 | where a single backend supports several frontends, allowing to generate types 355 | separately. 356 | 357 | ### `sql_to_typescript_type_mapping` 358 | 359 | Specifies [how to map](https://github.com/ElMassimo/types_from_serializers/blob/main/types_from_serializers/lib/types_from_serializers/generator.rb#L297-L308) SQL column types to TypeScript native and custom types. 360 | 361 | ```ruby 362 | # Example: You have response middleware that automatically converts date strings 363 | # into Date objects, and you want TypeScript to treat those fields as `Date`. 364 | config.sql_to_typescript_type_mapping.update( 365 | date: :Date, 366 | datetime: :Date, 367 | ) 368 | 369 | # Example: You won't transform fields when receiving data in the frontend 370 | # (date fields are serialized to JSON as strings). 371 | config.sql_to_typescript_type_mapping.update( 372 | date: :string, 373 | datetime: :string, 374 | ) 375 | 376 | # Example: You plan to introduce types slowly, and don't want to be strict with 377 | # untyped fields, so treat them as `any` instead of `unknown`. 378 | config.sql_to_typescript_type_mapping.default = :any 379 | ``` 380 | 381 | ### `transform_keys` 382 | 383 | _Default:_ `->(key) { key.camelize(:lower).chomp("?") }` 384 | 385 | You can provide a proc to transform property names. 386 | 387 | This library assumes that you will transform the casing client-side, but you can 388 | generate types preserving case by using `config.transform_keys = ->(key) { key }`. 389 | 390 | ## Contact ✉️ 391 | 392 | Please use [Issues] to report bugs you find, and [Discussions] to make feature requests or get help. 393 | 394 | Don't hesitate to _⭐️ star the project_ if you find it useful! 395 | 396 | Using it in production? Always love to hear about it! 😃 397 | 398 | ## License 399 | 400 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 401 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "rspec/core/rake_task" 2 | 3 | RSpec::Core::RakeTask.new 4 | 5 | task default: :spec 6 | -------------------------------------------------------------------------------- /bin/lint: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | bin/rubocop -A "$@" 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rake' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if /This file was generated by Bundler/.match?(File.read(bundle_binstub, 300)) 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("rake", "rake") 30 | -------------------------------------------------------------------------------- /bin/rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rspec' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if /This file was generated by Bundler/.match?(File.read(bundle_binstub, 300)) 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | ENV["UPDATE_SNAPSHOTS"] = "true" if ARGV.delete('--update') || ARGV.delete('--update-snapshots') 30 | 31 | load Gem.bin_path("rspec-core", "rspec") 32 | -------------------------------------------------------------------------------- /bin/rubocop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rubocop' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if /This file was generated by Bundler/.match?(File.read(bundle_binstub, 300)) 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("rubocop", "rubocop") 30 | -------------------------------------------------------------------------------- /bin/standardrb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'standardrb' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if /This file was generated by Bundler/.match?(File.read(bundle_binstub, 300)) 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("standard", "standardrb") 30 | -------------------------------------------------------------------------------- /gemfiles/Gemfile-rails-edge: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 4 | 5 | gem "rails", github: "rails/rails", branch: "main" 6 | 7 | gemspec path: "../types_from_serializers" 8 | -------------------------------------------------------------------------------- /gemfiles/Gemfile-rails.7.1.x: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "rails", "~> 7.1.0" 4 | 5 | gemspec path: "../types_from_serializers" 6 | -------------------------------------------------------------------------------- /gemfiles/Gemfile-rails.7.2.x: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "rails", "~> 7.2.0" 4 | 5 | gemspec path: "../types_from_serializers" 6 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | base = "docs/" 3 | ignore = "git diff --quiet 'HEAD^' HEAD ." 4 | publish = ".vitepress/dist" 5 | command = "yarn run build" 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "types_from_serializers_monorepo", 3 | "version": "1.0.0", 4 | "private": true, 5 | "license": "MIT", 6 | "scripts": { 7 | "clean": "rm -rf playground/vanilla/app/frontend/types/serializers", 8 | "migrate": "cd playground/vanilla && bundle install && bin/rails db:prepare", 9 | "gen": "cd playground/vanilla && bundle exec rake types_from_serializers:generate", 10 | "tsc": "npm -C playground/vanilla run tsc", 11 | "release": "node scripts/release", 12 | "lint": "eslint playground/vanilla/app/frontend scripts --ext .ts,.js,.vue", 13 | "postinstall": "husky install", 14 | "changelog": "node scripts/changelog", 15 | "test": "vitest" 16 | }, 17 | "devDependencies": { 18 | "@antfu/eslint-config": "^0.25.2", 19 | "@types/node": "^14.18.21", 20 | "@vue/tsconfig": "^0.1.3", 21 | "chalk": "^4.1.2", 22 | "conventional-changelog-cli": "^2.2.2", 23 | "enquirer": "^2.3.6", 24 | "eslint": "^8", 25 | "execa": "^5.1.1", 26 | "happy-dom": "^2.55.0", 27 | "husky": "^5.2.0", 28 | "lint-staged": "^10.5.4", 29 | "minimist": "^1.2.6", 30 | "semver": "^7.3.7", 31 | "typescript": "^4.7.4", 32 | "vitest": "^0.18.0" 33 | }, 34 | "lint-staged": { 35 | "*.{js,ts,tsx,jsx,vue}": [ 36 | "eslint --fix" 37 | ], 38 | "*.rb": [ 39 | "bin/rubocop -A" 40 | ] 41 | }, 42 | "repository": { 43 | "type": "git", 44 | "url": "https://github.com/ElMassimo/types_from_serializers" 45 | }, 46 | "homepage": "https://github.com/ElMassimo/types_from_serializers", 47 | "pnpm": { 48 | "overrides": { 49 | "eslint-plugin-vue": "9.2.0" 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /playground/vanilla/.browserslistrc: -------------------------------------------------------------------------------- 1 | defaults 2 | -------------------------------------------------------------------------------- /playground/vanilla/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-journal 13 | /db/*.sqlite3-* 14 | 15 | # Ignore all logfiles and tempfiles. 16 | /log/* 17 | /tmp/* 18 | !/log/.keep 19 | !/tmp/.keep 20 | 21 | # Ignore pidfiles, but keep the directory. 22 | /tmp/pids/* 23 | !/tmp/pids/ 24 | !/tmp/pids/.keep 25 | 26 | # Ignore uploaded files in development. 27 | /storage/* 28 | !/storage/.keep 29 | 30 | /public/assets 31 | .byebug_history 32 | 33 | # Ignore master key for decrypting credentials and more. 34 | /config/master.key 35 | 36 | /public/packs 37 | /public/packs-test 38 | /node_modules 39 | /yarn-error.log 40 | yarn-debug.log* 41 | .yarn-integrity 42 | 43 | # Vite Ruby 44 | /public/vite 45 | /public/vite-dev 46 | /public/vite-test 47 | node_modules 48 | *.local 49 | .DS_Store 50 | 51 | -------------------------------------------------------------------------------- /playground/vanilla/.npmrc: -------------------------------------------------------------------------------- 1 | ignore-workspace-root-check=true 2 | shamefully-hoist=true 3 | -------------------------------------------------------------------------------- /playground/vanilla/.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | -------------------------------------------------------------------------------- /playground/vanilla/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | gem "rails", "~> 6.0.3", ">= 6.0.3.2" 5 | 6 | # Use Puma as the app server 7 | gem "puma", "~> 4.1" 8 | 9 | # Use Vite.js as the frontend tool. Read more: https://github.com/ElMassimo/vite_ruby 10 | gem "vite_rails" 11 | 12 | # Use JS From Routes to generate Api helpers. 13 | gem "js_from_routes", "~> 2.0.6" 14 | 15 | # A more efficient version of ActiveModelSerializers (https://github.com/ElMassimo/oj_serializers) 16 | gem "oj_serializers" 17 | 18 | # Generate TypeScript interfaces from Ruby serializers. 19 | gem "types_from_serializers", path: "../.." 20 | 21 | # Use SQL Lite as the database. 22 | gem "sqlite3" 23 | 24 | # Use Inertia.js as the nexus between Rails and Vue (https://inertiajs.com/) 25 | gem "inertia_rails", ">= 1.2.2" 26 | 27 | group :development, :test do 28 | gem "listen", "~> 3.7" 29 | gem "debug" 30 | gem "rspec-rails" 31 | gem "rails-controller-testing" 32 | end 33 | -------------------------------------------------------------------------------- /playground/vanilla/Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: ../.. 3 | specs: 4 | types_from_serializers (2.3.0) 5 | listen (~> 3.2) 6 | oj_serializers (~> 2.0, >= 2.0.2) 7 | railties (>= 5.1) 8 | 9 | GEM 10 | remote: https://rubygems.org/ 11 | specs: 12 | actioncable (6.0.6.1) 13 | actionpack (= 6.0.6.1) 14 | nio4r (~> 2.0) 15 | websocket-driver (>= 0.6.1) 16 | actionmailbox (6.0.6.1) 17 | actionpack (= 6.0.6.1) 18 | activejob (= 6.0.6.1) 19 | activerecord (= 6.0.6.1) 20 | activestorage (= 6.0.6.1) 21 | activesupport (= 6.0.6.1) 22 | mail (>= 2.7.1) 23 | actionmailer (6.0.6.1) 24 | actionpack (= 6.0.6.1) 25 | actionview (= 6.0.6.1) 26 | activejob (= 6.0.6.1) 27 | mail (~> 2.5, >= 2.5.4) 28 | rails-dom-testing (~> 2.0) 29 | actionpack (6.0.6.1) 30 | actionview (= 6.0.6.1) 31 | activesupport (= 6.0.6.1) 32 | rack (~> 2.0, >= 2.0.8) 33 | rack-test (>= 0.6.3) 34 | rails-dom-testing (~> 2.0) 35 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 36 | actiontext (6.0.6.1) 37 | actionpack (= 6.0.6.1) 38 | activerecord (= 6.0.6.1) 39 | activestorage (= 6.0.6.1) 40 | activesupport (= 6.0.6.1) 41 | nokogiri (>= 1.8.5) 42 | actionview (6.0.6.1) 43 | activesupport (= 6.0.6.1) 44 | builder (~> 3.1) 45 | erubi (~> 1.4) 46 | rails-dom-testing (~> 2.0) 47 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 48 | activejob (6.0.6.1) 49 | activesupport (= 6.0.6.1) 50 | globalid (>= 0.3.6) 51 | activemodel (6.0.6.1) 52 | activesupport (= 6.0.6.1) 53 | activerecord (6.0.6.1) 54 | activemodel (= 6.0.6.1) 55 | activesupport (= 6.0.6.1) 56 | activestorage (6.0.6.1) 57 | actionpack (= 6.0.6.1) 58 | activejob (= 6.0.6.1) 59 | activerecord (= 6.0.6.1) 60 | marcel (~> 1.0) 61 | activesupport (6.0.6.1) 62 | concurrent-ruby (~> 1.0, >= 1.0.2) 63 | i18n (>= 0.7, < 2) 64 | minitest (~> 5.1) 65 | tzinfo (~> 1.1) 66 | zeitwerk (~> 2.2, >= 2.2.2) 67 | builder (3.2.4) 68 | concurrent-ruby (1.2.2) 69 | crass (1.0.6) 70 | date (3.3.3) 71 | debug (1.7.2) 72 | irb (>= 1.5.0) 73 | reline (>= 0.3.1) 74 | diff-lcs (1.5.0) 75 | dry-cli (1.0.0) 76 | erubi (1.12.0) 77 | ffi (1.15.5) 78 | globalid (1.1.0) 79 | activesupport (>= 5.0) 80 | i18n (1.12.0) 81 | concurrent-ruby (~> 1.0) 82 | inertia_rails (3.0.0) 83 | rails (>= 5) 84 | io-console (0.6.0) 85 | irb (1.6.3) 86 | reline (>= 0.3.0) 87 | js_from_routes (2.0.6) 88 | railties (>= 5.1, < 8) 89 | listen (3.8.0) 90 | rb-fsevent (~> 0.10, >= 0.10.3) 91 | rb-inotify (~> 0.9, >= 0.9.10) 92 | loofah (2.20.0) 93 | crass (~> 1.0.2) 94 | nokogiri (>= 1.5.9) 95 | mail (2.8.1) 96 | mini_mime (>= 0.1.1) 97 | net-imap 98 | net-pop 99 | net-smtp 100 | marcel (1.0.2) 101 | method_source (1.0.0) 102 | mini_mime (1.1.2) 103 | mini_portile2 (2.8.1) 104 | minitest (5.18.0) 105 | net-imap (0.3.4) 106 | date 107 | net-protocol 108 | net-pop (0.1.2) 109 | net-protocol 110 | net-protocol (0.2.1) 111 | timeout 112 | net-smtp (0.3.3) 113 | net-protocol 114 | nio4r (2.5.9) 115 | nokogiri (1.14.2) 116 | mini_portile2 (~> 2.8.0) 117 | racc (~> 1.4) 118 | oj (3.14.2) 119 | oj_serializers (2.0.2) 120 | oj (>= 3.14.0) 121 | puma (4.3.12) 122 | nio4r (~> 2.0) 123 | racc (1.6.2) 124 | rack (2.2.6.4) 125 | rack-proxy (0.7.6) 126 | rack 127 | rack-test (2.1.0) 128 | rack (>= 1.3) 129 | rails (6.0.6.1) 130 | actioncable (= 6.0.6.1) 131 | actionmailbox (= 6.0.6.1) 132 | actionmailer (= 6.0.6.1) 133 | actionpack (= 6.0.6.1) 134 | actiontext (= 6.0.6.1) 135 | actionview (= 6.0.6.1) 136 | activejob (= 6.0.6.1) 137 | activemodel (= 6.0.6.1) 138 | activerecord (= 6.0.6.1) 139 | activestorage (= 6.0.6.1) 140 | activesupport (= 6.0.6.1) 141 | bundler (>= 1.3.0) 142 | railties (= 6.0.6.1) 143 | sprockets-rails (>= 2.0.0) 144 | rails-controller-testing (1.0.5) 145 | actionpack (>= 5.0.1.rc1) 146 | actionview (>= 5.0.1.rc1) 147 | activesupport (>= 5.0.1.rc1) 148 | rails-dom-testing (2.0.3) 149 | activesupport (>= 4.2.0) 150 | nokogiri (>= 1.6) 151 | rails-html-sanitizer (1.5.0) 152 | loofah (~> 2.19, >= 2.19.1) 153 | railties (6.0.6.1) 154 | actionpack (= 6.0.6.1) 155 | activesupport (= 6.0.6.1) 156 | method_source 157 | rake (>= 0.8.7) 158 | thor (>= 0.20.3, < 2.0) 159 | rake (13.0.6) 160 | rb-fsevent (0.11.2) 161 | rb-inotify (0.10.1) 162 | ffi (~> 1.0) 163 | reline (0.3.3) 164 | io-console (~> 0.5) 165 | rspec-core (3.12.1) 166 | rspec-support (~> 3.12.0) 167 | rspec-expectations (3.12.2) 168 | diff-lcs (>= 1.2.0, < 2.0) 169 | rspec-support (~> 3.12.0) 170 | rspec-mocks (3.12.5) 171 | diff-lcs (>= 1.2.0, < 2.0) 172 | rspec-support (~> 3.12.0) 173 | rspec-rails (5.1.2) 174 | actionpack (>= 5.2) 175 | activesupport (>= 5.2) 176 | railties (>= 5.2) 177 | rspec-core (~> 3.10) 178 | rspec-expectations (~> 3.10) 179 | rspec-mocks (~> 3.10) 180 | rspec-support (~> 3.10) 181 | rspec-support (3.12.0) 182 | sprockets (4.2.0) 183 | concurrent-ruby (~> 1.0) 184 | rack (>= 2.2.4, < 4) 185 | sprockets-rails (3.4.2) 186 | actionpack (>= 5.2) 187 | activesupport (>= 5.2) 188 | sprockets (>= 3.0.0) 189 | sqlite3 (1.6.2) 190 | mini_portile2 (~> 2.8.0) 191 | thor (1.2.1) 192 | thread_safe (0.3.6) 193 | timeout (0.3.2) 194 | tzinfo (1.2.11) 195 | thread_safe (~> 0.1) 196 | vite_rails (3.0.14) 197 | railties (>= 5.1, < 8) 198 | vite_ruby (~> 3.0, >= 3.2.2) 199 | vite_ruby (3.3.0) 200 | dry-cli (>= 0.7, < 2) 201 | rack-proxy (~> 0.6, >= 0.6.1) 202 | zeitwerk (~> 2.2) 203 | websocket-driver (0.7.5) 204 | websocket-extensions (>= 0.1.0) 205 | websocket-extensions (0.1.5) 206 | zeitwerk (2.6.7) 207 | 208 | PLATFORMS 209 | ruby 210 | 211 | DEPENDENCIES 212 | debug 213 | inertia_rails (>= 1.2.2) 214 | js_from_routes (~> 2.0.6) 215 | listen (~> 3.7) 216 | oj_serializers 217 | puma (~> 4.1) 218 | rails (~> 6.0.3, >= 6.0.3.2) 219 | rails-controller-testing 220 | rspec-rails 221 | sqlite3 222 | types_from_serializers! 223 | vite_rails 224 | 225 | BUNDLED WITH 226 | 2.3.22 227 | -------------------------------------------------------------------------------- /playground/vanilla/Procfile.dev: -------------------------------------------------------------------------------- 1 | web: bin/rails s --port 3000 2 | vite: bin/vite dev 3 | -------------------------------------------------------------------------------- /playground/vanilla/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative "config/application" 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /playground/vanilla/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationController < ActionController::Base 4 | # Internal: Render an Inertia page that matches the current controller and action. 5 | def render_page(**props) 6 | render inertia: "#{controller_name}/#{action_name}", props: props 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /playground/vanilla/app/controllers/composers_controller.rb: -------------------------------------------------------------------------------- 1 | class ComposersController < ApplicationController 2 | def index 3 | render_page composers: ComposerSerializer.many(Composer.all) 4 | end 5 | 6 | def show 7 | render_page composer: ComposerWithSongsSerializer.one(Composer.find(params[:id])) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /playground/vanilla/app/controllers/songs_controller.rb: -------------------------------------------------------------------------------- 1 | class SongsController < ApplicationController 2 | def index 3 | render_page songs: SongSerializer.many(Song.all.order(:title)) 4 | end 5 | 6 | def show 7 | render_page song: SongWithVideosSerializer.one(Song.find(params[:id])) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /playground/vanilla/app/controllers/videos_controller.rb: -------------------------------------------------------------------------------- 1 | class VideosController < ApplicationController 2 | def index 3 | render_page videos: VideoWithSongSerializer.many(VideoClip.all.order(:created_at)) 4 | end 5 | 6 | def show 7 | render_page video: VideoWithSongSerializer.one(VideoClip.find(params[:id])) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /playground/vanilla/app/frontend/api/ComposersApi.ts: -------------------------------------------------------------------------------- 1 | // JsFromRoutes CacheKey 657f5e8dc19e6058650cf9bba2b0ef3d 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by JsFromRoutes. 4 | import { definePathHelper } from '@js-from-routes/inertia' 5 | 6 | export default { 7 | index: definePathHelper('get', '/composers'), 8 | show: definePathHelper('get', '/composers/:id'), 9 | } 10 | -------------------------------------------------------------------------------- /playground/vanilla/app/frontend/api/SongsApi.ts: -------------------------------------------------------------------------------- 1 | // JsFromRoutes CacheKey 3c504c24ef7fee1ff1bf10bc1f5939b4 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by JsFromRoutes. 4 | import { definePathHelper } from '@js-from-routes/inertia' 5 | 6 | export default { 7 | index: definePathHelper('get', '/songs'), 8 | show: definePathHelper('get', '/songs/:id'), 9 | } 10 | -------------------------------------------------------------------------------- /playground/vanilla/app/frontend/api/VideosApi.ts: -------------------------------------------------------------------------------- 1 | // JsFromRoutes CacheKey 460b3f8cfdc3c7b11c64fb451b5063a8 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by JsFromRoutes. 4 | import { definePathHelper } from '@js-from-routes/inertia' 5 | 6 | export default { 7 | index: definePathHelper('get', '/videos'), 8 | show: definePathHelper('get', '/videos/:id'), 9 | } 10 | -------------------------------------------------------------------------------- /playground/vanilla/app/frontend/api/all.ts: -------------------------------------------------------------------------------- 1 | // JsFromRoutes CacheKey 5d0e017a4589ba07dd01a67b2f32ddaa 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by JsFromRoutes. 4 | export { default as composers } from '~/api/ComposersApi' 5 | export { default as songs } from '~/api/SongsApi' 6 | export { default as videos } from '~/api/VideosApi' 7 | -------------------------------------------------------------------------------- /playground/vanilla/app/frontend/api/index.ts: -------------------------------------------------------------------------------- 1 | // JsFromRoutes CacheKey a376287889ecd8b72c981c0366ab5059 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by JsFromRoutes. 4 | import * as helpers from '~/api/all' 5 | export * from '~/api/all' 6 | export default helpers 7 | -------------------------------------------------------------------------------- /playground/vanilla/app/frontend/app.ts: -------------------------------------------------------------------------------- 1 | import { createApp as createVueApp, h } from 'vue' 2 | import { type CreateInertiaAppProps, createInertiaApp } from '~/inertia' 3 | import { resolvePage } from '~/pages' 4 | import '~/services/axios' 5 | 6 | type InertiaOptions = Omit 7 | 8 | export function createApp (options?: InertiaOptions) { 9 | return createInertiaApp({ 10 | resolve: resolvePage, 11 | setup ({ plugin, app, props, el }) { 12 | const vueApp = createVueApp({ render: () => h(app, props) }) 13 | .use(plugin) 14 | 15 | if (el) 16 | vueApp.mount(el) 17 | 18 | return vueApp 19 | }, 20 | ...options, 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /playground/vanilla/app/frontend/components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // Generated by unplugin-vue-components 5 | // Read more: https://github.com/vuejs/core/pull/3399 6 | import '@vue/runtime-core' 7 | 8 | export {} 9 | 10 | declare module '@vue/runtime-core' { 11 | export interface GlobalComponents { 12 | InertiaLink: typeof import('./components/InertiaLink.vue')['default'] 13 | Link: typeof import('./components/Link.vue')['default'] 14 | PageList: typeof import('./components/PageList.vue')['default'] 15 | PageSubtitle: typeof import('./components/PageSubtitle.vue')['default'] 16 | PageTitle: typeof import('./components/PageTitle.vue')['default'] 17 | TheNavBar: typeof import('./components/TheNavBar.vue')['default'] 18 | YouTubePlayer: typeof import('./components/YouTubePlayer.vue')['default'] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /playground/vanilla/app/frontend/components/InertiaLink.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /playground/vanilla/app/frontend/components/Link.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | 20 | 25 | -------------------------------------------------------------------------------- /playground/vanilla/app/frontend/components/PageList.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /playground/vanilla/app/frontend/components/PageSubtitle.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 23 | -------------------------------------------------------------------------------- /playground/vanilla/app/frontend/components/PageTitle.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /playground/vanilla/app/frontend/components/TheNavBar.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 25 | -------------------------------------------------------------------------------- /playground/vanilla/app/frontend/components/YouTubePlayer.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 |