├── .changeset ├── README.md ├── config.json ├── good-squids-argue.md ├── weak-bikes-behave.md └── wicked-bats-marry.md ├── .editorconfig ├── .eslintrc.json ├── .github ├── COMMIT_CONVENTION.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── release.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .ncurc.json ├── .npmrc ├── .nvmrc ├── .prettierrc ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── package.json ├── packages ├── radonis-build │ ├── .eslintignore │ ├── .eslintrc.json │ ├── .npmignore │ ├── .prettierignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── bin │ │ ├── japaTypes.ts │ │ └── test.ts │ ├── index.ts │ ├── lint-staged.config.js │ ├── package.json │ ├── src │ │ ├── assets_manifest_builder.ts │ │ ├── client_builder.ts │ │ ├── constants.ts │ │ ├── exceptions.ts │ │ ├── loaders.ts │ │ ├── plugin.ts │ │ ├── types │ │ │ └── main.ts │ │ └── utils.ts │ ├── tsconfig.json │ └── utils.ts ├── radonis-form │ ├── .eslintignore │ ├── .eslintrc.json │ ├── .npmignore │ ├── .prettierignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── bin │ │ ├── japaTypes.ts │ │ └── test.ts │ ├── index.ts │ ├── lint-staged.config.js │ ├── package.json │ ├── src │ │ ├── components │ │ │ └── form.ts │ │ ├── exceptions.ts │ │ ├── hooks │ │ │ └── use_form.ts │ │ ├── singletons.ts │ │ └── types │ │ │ └── main.ts │ └── tsconfig.json ├── radonis-hooks │ ├── .eslintignore │ ├── .eslintrc.json │ ├── .npmignore │ ├── .prettierignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── bin │ │ ├── japaTypes.ts │ │ └── test.ts │ ├── index.ts │ ├── lint-staged.config.js │ ├── package.json │ ├── src │ │ ├── exceptions.ts │ │ ├── hooks │ │ │ ├── client │ │ │ │ ├── internal │ │ │ │ │ ├── use_get_latest.ts │ │ │ │ │ ├── use_is_mounted.ts │ │ │ │ │ ├── use_isomorphic_layout_effect.ts │ │ │ │ │ └── use_safe_callback.ts │ │ │ │ └── use_mutation.ts │ │ │ ├── isomorphic │ │ │ │ ├── use_flash_messages.ts │ │ │ │ ├── use_flush_effect.ts │ │ │ │ ├── use_globals.ts │ │ │ │ ├── use_i18n.ts │ │ │ │ ├── use_manifest.ts │ │ │ │ ├── use_params.ts │ │ │ │ ├── use_route.ts │ │ │ │ ├── use_routes.ts │ │ │ │ ├── use_search_params.ts │ │ │ │ └── use_url_builder.ts │ │ │ └── server │ │ │ │ ├── use_application.ts │ │ │ │ ├── use_assets_manager.ts │ │ │ │ ├── use_http_context.ts │ │ │ │ ├── use_manifest_manager.ts │ │ │ │ ├── use_renderer.ts │ │ │ │ ├── use_request.ts │ │ │ │ ├── use_router.ts │ │ │ │ ├── use_server.ts │ │ │ │ └── use_session.ts │ │ ├── singletons.ts │ │ └── types │ │ │ └── main.ts │ └── tsconfig.json ├── radonis-hydrate │ ├── .eslintignore │ ├── .eslintrc.json │ ├── .npmignore │ ├── .prettierignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── bin │ │ ├── japaTypes.ts │ │ └── test.ts │ ├── index.ts │ ├── lint-staged.config.js │ ├── package.json │ ├── src │ │ ├── components │ │ │ └── hydration_root.ts │ │ ├── contexts │ │ │ └── hydration_context.ts │ │ ├── exceptions.ts │ │ ├── hooks │ │ │ ├── use_hydrated.ts │ │ │ └── use_hydration.ts │ │ ├── hydrate_island.ts │ │ ├── hydrator │ │ │ ├── constants.ts │ │ │ └── main.ts │ │ ├── island.ts │ │ ├── singletons.ts │ │ ├── symbols.ts │ │ └── utils │ │ │ └── get_manifest_or_fail.ts │ └── tsconfig.json ├── radonis-query │ ├── .eslintignore │ ├── .eslintrc.json │ ├── .npmignore │ ├── .prettierignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── bin │ │ ├── japaTypes.ts │ │ └── test.ts │ ├── index.ts │ ├── lint-staged.config.js │ ├── package.json │ ├── src │ │ ├── components │ │ │ ├── query_dehydrator.ts │ │ │ └── query_hydrator.ts │ │ ├── contexts │ │ │ └── base_url_context.ts │ │ ├── exceptions.ts │ │ ├── hooks │ │ │ ├── use_query_base_url.ts │ │ │ ├── use_server_mutation.ts │ │ │ └── use_server_query.ts │ │ ├── plugin.ts │ │ ├── queryClient.ts │ │ ├── types │ │ │ └── main.ts │ │ └── utils │ │ │ ├── get_query_key_for_url.ts │ │ │ └── get_route_identifier.ts │ └── tsconfig.json ├── radonis-server │ ├── .eslintignore │ ├── .eslintrc.json │ ├── .npmignore │ ├── .prettierignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── adonis-typings │ │ ├── container.ts │ │ ├── http-context.ts │ │ ├── index.ts │ │ └── radonis.ts │ ├── bin │ │ ├── japaTypes.ts │ │ └── test.ts │ ├── commands │ │ ├── BuildClient.ts │ │ └── index.ts │ ├── instructions.md │ ├── instructions.ts │ ├── lint-staged.config.js │ ├── package.json │ ├── providers │ │ └── RadonisProvider.ts │ ├── src │ │ ├── assets_manager │ │ │ └── main.ts │ │ ├── components │ │ │ └── default_error_page.ts │ │ ├── contexts │ │ │ ├── assets_manager_context.ts │ │ │ ├── manifest_manager_context.ts │ │ │ ├── renderer_context.ts │ │ │ └── server_context.ts │ │ ├── exceptions.ts │ │ ├── head_manager │ │ │ └── main.ts │ │ ├── hydration_manager │ │ │ └── main.ts │ │ ├── manifest_manager │ │ │ ├── constants.ts │ │ │ └── main.ts │ │ ├── plugins_manager │ │ │ └── main.ts │ │ ├── renderer │ │ │ ├── constants.ts │ │ │ └── main.ts │ │ └── utils │ │ │ ├── build_title.ts │ │ │ ├── extract_root_routes.ts │ │ │ ├── generate_html_stream.ts │ │ │ ├── get_route_identifier.ts │ │ │ ├── transform_route_node.ts │ │ │ └── with_context_providers.ts │ ├── standalone.ts │ ├── templates │ │ ├── config.txt │ │ ├── contract.txt │ │ └── entry.client.txt │ ├── test │ │ └── plugins_manager.spec.ts │ └── tsconfig.json ├── radonis-shared │ ├── .eslintignore │ ├── .eslintrc.json │ ├── .npmignore │ ├── .prettierignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── bin │ │ ├── japaTypes.ts │ │ └── test.ts │ ├── index.ts │ ├── lint-staged.config.js │ ├── node.ts │ ├── package.json │ ├── src │ │ ├── exception │ │ │ └── main.ts │ │ ├── exceptions.ts │ │ ├── types │ │ │ └── main.ts │ │ ├── url_builder │ │ │ └── main.ts │ │ └── utils │ │ │ ├── create_internal_url.ts │ │ │ ├── environment.ts │ │ │ ├── get_fetch_or_fail.ts │ │ │ ├── node │ │ │ ├── ensure_dir_exists.ts │ │ │ └── fs_read_all.ts │ │ │ ├── non_null.ts │ │ │ ├── normalize_path.ts │ │ │ ├── radonis_fetch.ts │ │ │ ├── separate_array.ts │ │ │ ├── stringify_attributes.ts │ │ │ ├── strip_leading_slash.ts │ │ │ └── strip_origin_from_url.ts │ └── tsconfig.json ├── radonis-types │ ├── .eslintignore │ ├── .eslintrc.json │ ├── .npmignore │ ├── .prettierignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── bin │ │ ├── japaTypes.ts │ │ └── test.ts │ ├── index.ts │ ├── lint-staged.config.js │ ├── package.json │ ├── src │ │ └── contracts.ts │ └── tsconfig.json ├── radonis-unocss │ ├── .eslintignore │ ├── .eslintrc.json │ ├── .npmignore │ ├── .prettierignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── bin │ │ ├── japaTypes.ts │ │ └── test.ts │ ├── index.ts │ ├── lint-staged.config.js │ ├── package.json │ ├── src │ │ ├── config.ts │ │ ├── plugin.ts │ │ └── preflight.ts │ └── tsconfig.json └── radonis │ ├── .eslintignore │ ├── .eslintrc.json │ ├── .npmignore │ ├── .prettierignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── bin │ ├── japaTypes.ts │ └── test.ts │ ├── index.ts │ ├── lint-staged.config.js │ ├── package.json │ ├── src │ ├── define_plugin.ts │ ├── exceptions.ts │ ├── init_client.ts │ ├── singletons.ts │ ├── token.ts │ └── types │ │ └── main.ts │ └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── tsconfig.json └── turbo.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.2.0/schema.json", 3 | "changelog": ["@changesets/changelog-github", { "repo": "microeinhundert/radonis" }], 4 | "commit": true, 5 | "fixed": [], 6 | "linked": [ 7 | [ 8 | "@microeinhundert/radonis", 9 | "@microeinhundert/radonis-build", 10 | "@microeinhundert/radonis-form", 11 | "@microeinhundert/radonis-hooks", 12 | "@microeinhundert/radonis-hydrate", 13 | "@microeinhundert/radonis-query", 14 | "@microeinhundert/radonis-server", 15 | "@microeinhundert/radonis-shared", 16 | "@microeinhundert/radonis-types", 17 | "@microeinhundert/radonis-unocss" 18 | ] 19 | ], 20 | "access": "restricted", 21 | "baseBranch": "main", 22 | "updateInternalDependencies": "patch", 23 | "ignore": [] 24 | } 25 | -------------------------------------------------------------------------------- /.changeset/good-squids-argue.md: -------------------------------------------------------------------------------- 1 | --- 2 | '@microeinhundert/radonis-server': patch 3 | '@microeinhundert/radonis-build': patch 4 | --- 5 | 6 | Use Nodejs EventEmitter internally 7 | -------------------------------------------------------------------------------- /.changeset/weak-bikes-behave.md: -------------------------------------------------------------------------------- 1 | --- 2 | '@microeinhundert/radonis-shared': minor 3 | --- 4 | 5 | Renamed `make` to `make$` on UrlBuilder 6 | -------------------------------------------------------------------------------- /.changeset/wicked-bats-marry.md: -------------------------------------------------------------------------------- 1 | --- 2 | '@microeinhundert/radonis-hydrate': patch 3 | '@microeinhundert/radonis-server': patch 4 | '@microeinhundert/radonis-shared': patch 5 | '@microeinhundert/radonis-unocss': patch 6 | '@microeinhundert/radonis-build': patch 7 | '@microeinhundert/radonis-hooks': patch 8 | '@microeinhundert/radonis-query': patch 9 | '@microeinhundert/radonis-types': patch 10 | '@microeinhundert/radonis-form': patch 11 | '@microeinhundert/radonis': patch 12 | --- 13 | 14 | Updated dependencies 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.json] 12 | insert_final_newline = ignore 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:adonis/typescriptPackage", 4 | "prettier", 5 | "plugin:react/recommended", 6 | "plugin:react-hooks/recommended" 7 | ], 8 | "plugins": ["prettier", "simple-import-sort"], 9 | "settings": { 10 | "react": { 11 | "version": "18" 12 | } 13 | }, 14 | "rules": { 15 | "react/prop-types": "off", 16 | "react/display-name": "off", 17 | "prettier/prettier": ["error"], 18 | "simple-import-sort/imports": "error", 19 | "simple-import-sort/exports": "error", 20 | "@typescript-eslint/explicit-member-accessibility": "off", 21 | "@typescript-eslint/consistent-type-imports": ["error", { 22 | "prefer": "type-imports" 23 | }] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/COMMIT_CONVENTION.md: -------------------------------------------------------------------------------- 1 | ## Git Commit Message Convention 2 | 3 | > This is adapted from [Angular's commit convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular). 4 | 5 | Using conventional commit messages, we can automate the process of generating the CHANGELOG file. All commits messages will automatically be validated against the following regex. 6 | 7 | ``` js 8 | /^(revert: )?(feat|fix|docs|style|refactor|perf|test|workflow|ci|chore|types|build|improvement)((.+))?: .{1,50}/ 9 | ``` 10 | 11 | ## Commit Message Format 12 | 13 | A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**: 14 | 15 | > The **scope** is optional 16 | 17 | ``` 18 | feat(router): add support for prefix 19 | 20 | Prefix makes it easier to append a path to a group of routes 21 | ``` 22 | 23 | 1. `feat` is type. 24 | 2. `router` is scope and is optional. 25 | 3. `add support for prefix` is the subject. 26 | 4. The **body** is followed by a blank line. 27 | 5. The optional **footer** can be added after the body, followed by a blank line. 28 | 29 | ## Types 30 | 31 | Only one type can be used at a time and only following types are allowed. 32 | 33 | - feat 34 | - fix 35 | - docs 36 | - style 37 | - refactor 38 | - perf 39 | - test 40 | - workflow 41 | - ci 42 | - chore 43 | - types 44 | - build 45 | 46 | If a type is `feat`, `fix` or `perf`, then the commit will appear in the CHANGELOG.md file. However if there is any BREAKING CHANGE, the commit will always appear in the changelog. 47 | 48 | ### Revert 49 | 50 | If the commit reverts a previous commit, it should begin with `revert:`, followed by the header of the reverted commit. In the body it should say: `This reverts commit `, where the hash is the SHA of the commit being reverted. 51 | 52 | ## Scope 53 | 54 | The scope could be anything specifying place of the commit change. For example: `router`, `view`, `querybuilder`, `database`, `model` and so on. 55 | 56 | ## Subject 57 | 58 | The subject contains succinct description of the change: 59 | 60 | - Use the imperative, present tense: "change" not "changed" nor "changes". 61 | - Don't capitalize first letter. 62 | - No dot (.) at the end. 63 | 64 | ## Body 65 | 66 | Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes". 67 | The body should include the motivation for the change and contrast this with previous behavior. 68 | 69 | ## Footer 70 | 71 | The footer should contain any information about **Breaking Changes** and is also the place to 72 | reference GitHub issues that this commit **Closes**. 73 | 74 | **Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this. 75 | 76 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Following these guidelines will make your pull request easier to merge. 4 | 5 | ## Prerequisites 6 | 7 | - Install [EditorConfig](http://editorconfig.org/) plugin for your code editor to make sure it uses correct settings. 8 | - Fork the repository and clone your fork. 9 | - Install dependencies: `pnpm install`. 10 | 11 | ## Development workflow 12 | 13 | First, create a changeset for your changes: 14 | 15 | ```bash 16 | pnpm run changeset 17 | ``` 18 | 19 | Always make sure to lint and test your code before pushing it to GitHub: 20 | 21 | ```bash 22 | pnpm run test && pnpm run lint 23 | ``` 24 | 25 | ## Other notes 26 | 27 | - Do not change the version number inside the `package.json` file. 28 | - Do not update the `CHANGELOG.md` file. 29 | 30 | ## Need help? 31 | 32 | Feel free to ask. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Prerequisites 4 | 5 | We do our best to reply to all the issues on time. If you will follow the given guidelines, the turn around time will be faster. 6 | 7 | - Ensure the issue isn't already reported. 8 | 9 | *Delete the above section and the instructions in the sections below before submitting* 10 | 11 | ## Description 12 | 13 | If this is a feature request, explain why it should be added. Specific use-cases are best. 14 | 15 | For bug reports, please provide as much *relevant* info as possible. 16 | 17 | ## Package Version 18 | 19 | 20 | ## Error Message & Stack Trace 21 | 22 | ## Relevant Information 23 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Proposed changes 4 | 5 | Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request. If it fixes a bug or resolves a feature request, be sure to link to that issue. 6 | 7 | ## Types of changes 8 | 9 | What types of changes does your code introduce? 10 | 11 | _Put an `x` in the boxes that apply_ 12 | 13 | - [ ] Bugfix (non-breaking change which fixes an issue) 14 | - [ ] New feature (non-breaking change which adds functionality) 15 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 16 | 17 | ## Checklist 18 | 19 | _Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code._ 20 | 21 | - [ ] I have read the [CONTRIBUTING](https://github.com/microeinhundert/radonis/blob/master/.github/CONTRIBUTING.md) doc. 22 | - [ ] Lint and unit tests pass locally with my changes. 23 | - [ ] I have added tests that prove my fix is effective or that my feature works. 24 | - [ ] I have added necessary documentation (if appropriate). 25 | 26 | ## Further comments 27 | 28 | If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc... 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | concurrency: ${{ github.workflow }}-${{ github.ref }} 10 | 11 | jobs: 12 | release: 13 | name: Release 14 | timeout-minutes: 15 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Check out code 19 | uses: actions/checkout@v3 20 | with: 21 | fetch-depth: 0 22 | 23 | - name: Setup node 24 | uses: actions/setup-node@v3 25 | with: 26 | node-version-file: '.nvmrc' 27 | registry-url: 'https://registry.npmjs.org/' 28 | scope: '@microeinhundert' 29 | 30 | - name: Setup npmrc 31 | run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> .npmrc 32 | 33 | - name: Setup pnpm 34 | uses: pnpm/action-setup@v2.2.4 35 | with: 36 | version: 7.26.1 37 | run_install: false 38 | 39 | - name: Get pnpm store directory 40 | id: pnpm-cache 41 | shell: bash 42 | run: | 43 | echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT 44 | 45 | - uses: actions/cache@v3 46 | name: Setup pnpm cache 47 | with: 48 | path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} 49 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 50 | restore-keys: | 51 | ${{ runner.os }}-pnpm-store- 52 | 53 | - name: Install dependencies 54 | run: pnpm recursive install --ignore-scripts 55 | 56 | - name: Lint packages 57 | run: pnpm run lint 58 | 59 | - name: Build packages 60 | run: pnpm run build 61 | 62 | - name: Release packages 63 | uses: changesets/action@v1 64 | with: 65 | commit: 'chore: bump package versions' 66 | title: 'chore: bump package versions' 67 | publish: pnpm run ci:publish 68 | env: 69 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 70 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | coverage 4 | .DS_Store 5 | .env 6 | .turbo 7 | tmp 8 | *.tsbuildinfo 9 | .idea 10 | package-lock.json 11 | .pnpm* 12 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | HUSKY_GIT_PARAMS=$1 node ./node_modules/@adonisjs/mrm-preset/validate-commit/conventional/validate.js 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run pre-commit 5 | -------------------------------------------------------------------------------- /.ncurc.json: -------------------------------------------------------------------------------- 1 | { 2 | "upgrade": true, 3 | "deep": true, 4 | "reject": ["intl-messageformat", "@microeinhundert/*"] 5 | } 6 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | loglevel=error 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 18 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "semi": false, 4 | "singleQuote": true, 5 | "useTabs": false, 6 | "quoteProps": "consistent", 7 | "bracketSpacing": true, 8 | "arrowParens": "always", 9 | "printWidth": 120 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll.eslint": true 4 | }, 5 | "eslint.workingDirectories": [ 6 | { 7 | "directory": "packages/radonis", 8 | "changeProcessCWD": true 9 | }, 10 | { 11 | "directory": "packages/radonis-build", 12 | "changeProcessCWD": true 13 | }, 14 | { 15 | "directory": "packages/radonis-form", 16 | "changeProcessCWD": true 17 | }, 18 | { 19 | "directory": "packages/radonis-hooks", 20 | "changeProcessCWD": true 21 | }, 22 | { 23 | "directory": "packages/radonis-hydrate", 24 | "changeProcessCWD": true 25 | }, 26 | { 27 | "directory": "packages/radonis-query", 28 | "changeProcessCWD": true 29 | }, 30 | { 31 | "directory": "packages/radonis-server", 32 | "changeProcessCWD": true 33 | }, 34 | { 35 | "directory": "packages/radonis-shared", 36 | "changeProcessCWD": true 37 | }, 38 | { 39 | "directory": "packages/radonis-types", 40 | "changeProcessCWD": true 41 | }, 42 | { 43 | "directory": "packages/radonis-unocss", 44 | "changeProcessCWD": true 45 | } 46 | ], 47 | "typescript.tsdk": "node_modules/typescript/lib" 48 | } 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Leon Seipp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Radonis 2 | 3 | ![npm (scoped)](https://img.shields.io/npm/v/@microeinhundert/radonis) 4 | 5 | Radonis is a full-stack application framework for building monolithic applications with a modern, React-based frontend stack. Radonis is built on top of the Node.js MVC framework [AdonisJS](https://adonisjs.com/) and extends it with features for server-side rendering and client-side hydration. 6 | 7 | ## Documentation 8 | 9 | Documentation is available on [radonis.vercel.app](https://radonis.vercel.app/). 10 | 11 | ## Example application 12 | 13 | An example application can be found in the [radonis-example-application](https://github.com/microeinhundert/radonis-example-application) repository. This application demonstrates how Radonis works and is a great starting point for new projects. 14 | 15 | ## License 16 | 17 | [MIT](LICENSE) 18 | -------------------------------------------------------------------------------- /packages/radonis-build/.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /packages/radonis-build/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "root": true 4 | } 5 | -------------------------------------------------------------------------------- /packages/radonis-build/.npmignore: -------------------------------------------------------------------------------- 1 | **/tsconfig.tsbuildinfo 2 | **/test 3 | **/bin -------------------------------------------------------------------------------- /packages/radonis-build/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /packages/radonis-build/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Leon Seipp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/radonis-build/README.md: -------------------------------------------------------------------------------- 1 | # Radonis Build 2 | 3 | ![npm (scoped)](https://img.shields.io/npm/v/@microeinhundert/radonis-build) 4 | 5 | > **Note**: This is a package used by Radonis internally and should not be installed directly. 6 | 7 | ## About 8 | 9 | Radonis is a full-stack application framework for building monolithic applications with a modern, React-based frontend stack. Radonis is built on top of the Node.js MVC framework [AdonisJS](https://adonisjs.com/) and extends it with features for server-side rendering and client-side hydration. 10 | 11 | ## Documentation 12 | 13 | Documentation is available on [radonis.vercel.app](https://radonis.vercel.app/). 14 | 15 | ## License 16 | 17 | [MIT](LICENSE) 18 | -------------------------------------------------------------------------------- /packages/radonis-build/bin/japaTypes.ts: -------------------------------------------------------------------------------- 1 | import type { Assert } from '@japa/assert' 2 | 3 | declare module '@japa/runner' { 4 | interface TestContext { 5 | assert: Assert 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/radonis-build/bin/test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from '@japa/assert' 2 | import { runFailedTests } from '@japa/run-failed-tests' 3 | import { configure, processCliArgs, run } from '@japa/runner' 4 | import { specReporter } from '@japa/spec-reporter' 5 | 6 | process.env.NODE_ENV = 'test' 7 | 8 | /* 9 | |-------------------------------------------------------------------------- 10 | | Configure tests 11 | |-------------------------------------------------------------------------- 12 | | 13 | | The configure method accepts the configuration to configure the Japa 14 | | tests runner. 15 | | 16 | | The first method call "processCliArgs" process the command line arguments 17 | | and turns them into a config object. Using this method is not mandatory. 18 | | 19 | | Please consult japa.dev/runner-config for the config docs. 20 | */ 21 | configure({ 22 | ...processCliArgs(process.argv.slice(2)), 23 | ...{ 24 | files: ['test/**/*.spec.ts'], 25 | plugins: [assert(), runFailedTests()], 26 | reporters: [specReporter()], 27 | importer: (filePath: string) => import(filePath), 28 | }, 29 | }) 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Run tests 34 | |-------------------------------------------------------------------------- 35 | | 36 | | The following "run" method is required to execute all the tests. 37 | | 38 | */ 39 | run() 40 | -------------------------------------------------------------------------------- /packages/radonis-build/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-build 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | export { AssetsManifestBuilder } from './src/assets_manifest_builder' 11 | export { ClientBuilder } from './src/client_builder' 12 | export type { BuiltAssets } from './src/types/main' 13 | -------------------------------------------------------------------------------- /packages/radonis-build/lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.ts': ['npm run lint -- --fix'], 3 | '*.{json,md}': ['npm run format'], 4 | } 5 | -------------------------------------------------------------------------------- /packages/radonis-build/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@microeinhundert/radonis-build", 3 | "version": "5.0.4", 4 | "description": "Build package for Radonis", 5 | "type": "commonjs", 6 | "main": "./build/cjs/index.js", 7 | "types": "./build/cjs/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": { 11 | "types": "./build/esm/index.d.ts", 12 | "default": "./build/esm/index.js" 13 | }, 14 | "require": { 15 | "types": "./build/cjs/index.d.ts", 16 | "default": "./build/cjs/index.js" 17 | } 18 | }, 19 | "./utils": { 20 | "import": { 21 | "types": "./build/esm/utils.d.ts", 22 | "default": "./build/esm/utils.js" 23 | }, 24 | "require": { 25 | "types": "./build/cjs/utils.d.ts", 26 | "default": "./build/cjs/utils.js" 27 | } 28 | } 29 | }, 30 | "sideEffects": false, 31 | "files": [ 32 | "build/cjs", 33 | "build/esm" 34 | ], 35 | "scripts": { 36 | "clean": "del-cli build", 37 | "build:cjs": "tsc --outDir ./build/cjs --module commonjs", 38 | "build:esm": "tsc --outDir ./build/esm --module esnext", 39 | "build": "\"$npm_execpath\" run build:cjs && \"$npm_execpath\" run build:esm", 40 | "lint": "TIMING=1 eslint . --ext=.ts", 41 | "format": "prettier --write .", 42 | "test": "node -r @adonisjs/require-ts/build/register bin/test.ts" 43 | }, 44 | "repository": { 45 | "type": "git", 46 | "url": "git+ssh://git@github.com/microeinhundert/radonis.git", 47 | "directory": "packages/radonis-build" 48 | }, 49 | "bugs": { 50 | "url": "https://github.com/microeinhundert/radonis/issues" 51 | }, 52 | "publishConfig": { 53 | "access": "public" 54 | }, 55 | "engines": { 56 | "node": ">= 16.0.0", 57 | "npm": ">= 8.0.0" 58 | }, 59 | "keywords": [ 60 | "adonisjs", 61 | "adonis", 62 | "radonis", 63 | "build" 64 | ], 65 | "author": "Leon Seipp ", 66 | "license": "MIT", 67 | "devDependencies": { 68 | "@types/node": "^18.15.11" 69 | }, 70 | "dependencies": { 71 | "@microeinhundert/radonis-shared": "workspace:5.0.4", 72 | "@microeinhundert/radonis-types": "workspace:5.0.4", 73 | "esbuild": "^0.17.14", 74 | "tslib": "^2.5.0" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /packages/radonis-build/src/assets_manifest_builder.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-build 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { nonNull } from '@microeinhundert/radonis-shared' 11 | import type { Asset, AssetsManifest } from '@microeinhundert/radonis-types' 12 | 13 | import type { BuiltAsset, BuiltAssets } from './types/main' 14 | 15 | export class AssetsManifestBuilder { 16 | /** 17 | * The built assets 18 | */ 19 | #builtAssets: BuiltAssets 20 | 21 | constructor(builtAssets: BuiltAssets) { 22 | this.#builtAssets = builtAssets 23 | } 24 | 25 | /** 26 | * Build the assets manifest 27 | */ 28 | build(): AssetsManifest { 29 | return nonNull(Array.from(this.#builtAssets).flatMap(([_, asset]) => this.#createEntry(asset))) 30 | } 31 | 32 | /** 33 | * Reduce the tokens of multiple assets down to a single asset 34 | */ 35 | #reduceTokens(assets: Asset[]): string[] { 36 | return Array.from( 37 | assets.reduce>((tokens, asset) => { 38 | asset.tokens.forEach((token) => tokens.add(token)) 39 | return tokens 40 | }, new Set()) 41 | ) 42 | } 43 | 44 | /** 45 | * Create the entry for an asset 46 | */ 47 | #createEntry({ imports, ...asset }: BuiltAsset): Asset { 48 | const chunks = nonNull( 49 | imports.map(({ path: importPath, external }) => { 50 | if (external) { 51 | return null 52 | } 53 | const chunkAsset = this.#builtAssets.get(importPath) 54 | return chunkAsset ? this.#createEntry(chunkAsset) : null 55 | }) 56 | ) 57 | 58 | if (chunks.length) { 59 | return { 60 | ...asset, 61 | tokens: this.#reduceTokens([asset, ...chunks]), 62 | } 63 | } 64 | 65 | return asset 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/radonis-build/src/constants.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-build 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | export const TOKEN_REGEX = new RegExp(/\S\$(\(|=|:)(\W+)?["'](?\S+)["']/g) 11 | export const ISLAND_REGEX = new RegExp(/\bisland\((\s+)?["'](?\S+)["'],(\s+)?(?\S+)(\s+)?\)/g) 12 | export const ASSETS_MANIFEST_FILE_NAME = 'assets-manifest.json' 13 | -------------------------------------------------------------------------------- /packages/radonis-build/src/exceptions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-build 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { createError } from '@microeinhundert/radonis-shared' 11 | 12 | export const E_CANNOT_BUILD_CLIENT = createError<[message: string]>( 13 | 'Cannot build the Radonis client bundle: %s', 14 | 'E_CANNOT_BUILD_CLIENT', 15 | 500 16 | ) 17 | 18 | export const E_CANNOT_GET_FILE_LOADER = createError<[path: string]>( 19 | 'Cannot get a loader for the file at "%s". This most likely means that the file type you are importing is not currently supported by Radonis. You can add support for this file type yourself by overriding the esbuild configuration in the Radonis config file', 20 | 'E_CANNOT_GET_FILE_LOADER', 21 | 500 22 | ) 23 | 24 | export const E_CANNOT_FIND_OUTPUT = createError<[key: string]>( 25 | 'Cannot find the output for asset "%s". This is most likely a bug of Radonis', 26 | 'E_CANNOT_FIND_OUTPUT', 27 | 500 28 | ) 29 | -------------------------------------------------------------------------------- /packages/radonis-build/src/loaders.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-build 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { extname } from 'node:path' 11 | 12 | import type { Loader } from 'esbuild' 13 | 14 | import { E_CANNOT_GET_FILE_LOADER } from './exceptions' 15 | 16 | export const loaders: Record = { 17 | '.js': 'js', 18 | '.jsx': 'jsx', 19 | '.ts': 'ts', 20 | '.tsx': 'tsx', 21 | } 22 | 23 | export function getLoaderForFile(fileNameOrPath: string) { 24 | const ext = extname(fileNameOrPath) 25 | 26 | if (!(ext in loaders)) { 27 | throw new E_CANNOT_GET_FILE_LOADER([fileNameOrPath]) 28 | } 29 | 30 | return loaders[ext] 31 | } 32 | -------------------------------------------------------------------------------- /packages/radonis-build/src/types/main.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-build 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import type { AssetType, MaybePromise } from '@microeinhundert/radonis-types' 11 | import type { BuildOptions as EsbuildOptions, Metafile } from 'esbuild' 12 | 13 | /** 14 | * Built asset 15 | */ 16 | export interface BuiltAsset { 17 | type: AssetType 18 | name: string 19 | path: string 20 | islands: Islands 21 | tokens: string[] 22 | imports: Metafile['outputs'][0]['imports'] 23 | } 24 | 25 | /** 26 | * Built assets 27 | */ 28 | export type BuiltAssets = Map 29 | 30 | /** 31 | * Islands 32 | */ 33 | export type Islands = string[] 34 | 35 | /** 36 | * Islands by file 37 | */ 38 | export type IslandsByFile = Map 39 | 40 | /** 41 | * Build options 42 | */ 43 | export interface BuildOptions { 44 | entryPoints: string[] 45 | appRootPath: string 46 | publicPath: string 47 | outputPath: string 48 | outputForProduction?: boolean 49 | rebuildOnFileChanges?: boolean 50 | esbuildOptions?: EsbuildOptions 51 | } 52 | 53 | /** 54 | * Radonis plugin options 55 | */ 56 | export type RadonisPluginOptions = { 57 | publicPath: string 58 | minify?: boolean 59 | onEnd?: (builtAssets: BuiltAssets) => MaybePromise 60 | } 61 | -------------------------------------------------------------------------------- /packages/radonis-build/src/utils.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-build 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { readFile, writeFile } from 'node:fs/promises' 11 | import { join } from 'node:path' 12 | 13 | import { ensureDirExists, fsReadAll } from '@microeinhundert/radonis-shared/node' 14 | import type { AssetsManifest } from '@microeinhundert/radonis-types' 15 | import { AssetType } from '@microeinhundert/radonis-types' 16 | 17 | import { ASSETS_MANIFEST_FILE_NAME, TOKEN_REGEX } from './constants' 18 | 19 | /** 20 | * Get the entry points for the given location 21 | */ 22 | export async function getEntryPoints(location: string) { 23 | return fsReadAll(location, { 24 | filter: (filePath) => { 25 | return /\.(client|island)\.(ts(x)?|js(x)?)$/.test(filePath) 26 | }, 27 | }) 28 | } 29 | 30 | /** 31 | * Read the assets manifest from disk 32 | */ 33 | export async function readAssetsManifestFromDisk(location: string) { 34 | try { 35 | const fileContents = await readFile(join(location, ASSETS_MANIFEST_FILE_NAME), 'utf-8') 36 | 37 | return JSON.parse(fileContents) as AssetsManifest 38 | } catch { 39 | return null 40 | } 41 | } 42 | 43 | /** 44 | * Write the assets manifest to disk 45 | */ 46 | export async function writeAssetsManifestToDisk(assetsManifest: AssetsManifest, location: string) { 47 | await ensureDirExists(location) 48 | await writeFile(join(location, ASSETS_MANIFEST_FILE_NAME), JSON.stringify(assetsManifest, null, 2)) 49 | } 50 | 51 | /** 52 | * Extract tokens from a haystack 53 | */ 54 | export function extractTokens(haystack: string) { 55 | const matches = haystack.matchAll(TOKEN_REGEX) 56 | const tokens = new Set() 57 | 58 | for (const match of matches) { 59 | if (match?.groups?.identifier) { 60 | tokens.add(match.groups.identifier) 61 | } 62 | } 63 | 64 | return Array.from(tokens) 65 | } 66 | 67 | /** 68 | * Check if a string is a valid asset type 69 | */ 70 | export function isAssetType(value: string): value is AssetType { 71 | return Object.values(AssetType).includes(value) 72 | } 73 | 74 | /** 75 | * Get the meta information for a given output 76 | */ 77 | export function getOutputMeta(output: { entryPoint?: string }) { 78 | let [type, originalPath] = output.entryPoint?.split(/:(.*)/s) ?? [] 79 | 80 | type ||= AssetType.ChunkScript 81 | 82 | if (!isAssetType(type)) { 83 | throw new Error(`Invalid asset type "${type}" for entry "${output.entryPoint}"`) 84 | } 85 | 86 | return { 87 | type, 88 | originalPath, 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /packages/radonis-build/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "rootDir": "./", 6 | "composite": true, 7 | "lib": ["esnext"], 8 | "module": "commonjs", 9 | "outDir": "./build" 10 | }, 11 | "include": ["./**/*"], 12 | "exclude": ["./node_modules", "./build"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/radonis-build/utils.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-build 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | export { getEntryPoints, readAssetsManifestFromDisk, writeAssetsManifestToDisk } from './src/utils' 11 | -------------------------------------------------------------------------------- /packages/radonis-form/.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /packages/radonis-form/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "root": true 4 | } 5 | -------------------------------------------------------------------------------- /packages/radonis-form/.npmignore: -------------------------------------------------------------------------------- 1 | **/tsconfig.tsbuildinfo 2 | **/test 3 | **/bin -------------------------------------------------------------------------------- /packages/radonis-form/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /packages/radonis-form/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Leon Seipp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/radonis-form/README.md: -------------------------------------------------------------------------------- 1 | # Radonis Form 2 | 3 | ![npm (scoped)](https://img.shields.io/npm/v/@microeinhundert/radonis-form) 4 | 5 | > **Note**: This is a package used by Radonis internally and should not be installed directly. 6 | 7 | ## About 8 | 9 | Radonis is a full-stack application framework for building monolithic applications with a modern, React-based frontend stack. Radonis is built on top of the Node.js MVC framework [AdonisJS](https://adonisjs.com/) and extends it with features for server-side rendering and client-side hydration. 10 | 11 | ## Documentation 12 | 13 | Documentation is available on [radonis.vercel.app](https://radonis.vercel.app/). 14 | 15 | ## License 16 | 17 | [MIT](LICENSE) 18 | -------------------------------------------------------------------------------- /packages/radonis-form/bin/japaTypes.ts: -------------------------------------------------------------------------------- 1 | import type { Assert } from '@japa/assert' 2 | 3 | declare module '@japa/runner' { 4 | interface TestContext { 5 | assert: Assert 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/radonis-form/bin/test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from '@japa/assert' 2 | import { runFailedTests } from '@japa/run-failed-tests' 3 | import { configure, processCliArgs, run } from '@japa/runner' 4 | import { specReporter } from '@japa/spec-reporter' 5 | 6 | process.env.NODE_ENV = 'test' 7 | 8 | /* 9 | |-------------------------------------------------------------------------- 10 | | Configure tests 11 | |-------------------------------------------------------------------------- 12 | | 13 | | The configure method accepts the configuration to configure the Japa 14 | | tests runner. 15 | | 16 | | The first method call "processCliArgs" process the command line arguments 17 | | and turns them into a config object. Using this method is not mandatory. 18 | | 19 | | Please consult japa.dev/runner-config for the config docs. 20 | */ 21 | configure({ 22 | ...processCliArgs(process.argv.slice(2)), 23 | ...{ 24 | files: ['test/**/*.spec.ts'], 25 | plugins: [assert(), runFailedTests()], 26 | reporters: [specReporter()], 27 | importer: (filePath: string) => import(filePath), 28 | }, 29 | }) 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Run tests 34 | |-------------------------------------------------------------------------- 35 | | 36 | | The following "run" method is required to execute all the tests. 37 | | 38 | */ 39 | run() 40 | -------------------------------------------------------------------------------- /packages/radonis-form/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-form 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | export { Form } from './src/components/form' 11 | export { useForm } from './src/hooks/use_form' 12 | export type { FormChildrenProps, FormOptions, FormProps } from './src/types/main' 13 | -------------------------------------------------------------------------------- /packages/radonis-form/lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.ts': ['npm run lint -- --fix'], 3 | '*.{json,md}': ['npm run format'], 4 | } 5 | -------------------------------------------------------------------------------- /packages/radonis-form/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@microeinhundert/radonis-form", 3 | "version": "5.0.4", 4 | "description": "Form package for Radonis", 5 | "type": "commonjs", 6 | "main": "./build/cjs/index.js", 7 | "types": "./build/cjs/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": { 11 | "types": "./build/esm/index.d.ts", 12 | "default": "./build/esm/index.js" 13 | }, 14 | "require": { 15 | "types": "./build/cjs/index.d.ts", 16 | "default": "./build/cjs/index.js" 17 | } 18 | } 19 | }, 20 | "sideEffects": false, 21 | "files": [ 22 | "build/cjs", 23 | "build/esm" 24 | ], 25 | "scripts": { 26 | "clean": "del-cli build", 27 | "build:cjs": "tsc --outDir ./build/cjs --module commonjs", 28 | "build:esm": "tsc --outDir ./build/esm --module esnext", 29 | "build": "\"$npm_execpath\" run build:cjs && \"$npm_execpath\" run build:esm", 30 | "lint": "TIMING=1 eslint . --ext=.ts", 31 | "format": "prettier --write .", 32 | "test": "node -r @adonisjs/require-ts/build/register bin/test.ts" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "git+ssh://git@github.com/microeinhundert/radonis.git", 37 | "directory": "packages/radonis-form" 38 | }, 39 | "bugs": { 40 | "url": "https://github.com/microeinhundert/radonis/issues" 41 | }, 42 | "publishConfig": { 43 | "access": "public" 44 | }, 45 | "engines": { 46 | "node": ">= 16.0.0", 47 | "npm": ">= 8.0.0" 48 | }, 49 | "keywords": [ 50 | "adonisjs", 51 | "adonis", 52 | "radonis", 53 | "form" 54 | ], 55 | "author": "Leon Seipp ", 56 | "license": "MIT", 57 | "peerDependencies": { 58 | "react": "^18.2.0", 59 | "react-dom": "^18.2.0" 60 | }, 61 | "devDependencies": { 62 | "@types/node": "^18.15.11", 63 | "@types/react": "^18.0.31", 64 | "@types/react-dom": "^18.0.11" 65 | }, 66 | "dependencies": { 67 | "@microeinhundert/radonis-types": "workspace:5.0.4", 68 | "@microeinhundert/radonis-shared": "workspace:5.0.4", 69 | "@microeinhundert/radonis-server": "workspace:5.0.4", 70 | "@microeinhundert/radonis-hydrate": "workspace:5.0.4", 71 | "@microeinhundert/radonis-hooks": "workspace:5.0.4", 72 | "tslib": "^2.5.0" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/radonis-form/src/components/form.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-form 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { createElement as h } from 'react' 11 | 12 | import { useForm } from '../hooks/use_form' 13 | import type { FormProps } from '../types/main' 14 | 15 | /** 16 | * Component extending the default form behavior 17 | * @see https://radonis.vercel.app/docs/forms 18 | */ 19 | export function Form({ children, ...props }: FormProps) { 20 | const form = useForm(props) 21 | 22 | return h( 23 | 'form', 24 | form.getFormProps(), 25 | typeof children === 'function' 26 | ? children({ 27 | data: form.data ?? null, 28 | error: form.error ?? null, 29 | status: form.status, 30 | }) 31 | : children 32 | ) 33 | } 34 | 35 | Form.displayName = 'RadonisForm' 36 | -------------------------------------------------------------------------------- /packages/radonis-form/src/exceptions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-form 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { createError } from '@microeinhundert/radonis-shared' 11 | 12 | export const E_CANNOT_FETCH_WITHOUT_HYDRATION = createError<[action: string]>( 13 | 'The form with the action "%s" and the "noReload" prop set can\'t work without being hydrated client-side. Convert the component containing this form to an island and wrap it with an HydrationRoot', 14 | 'E_CANNOT_FETCH_WITHOUT_HYDRATION', 15 | 500 16 | ) 17 | 18 | export const E_CANNOT_USE_HOOKS_WHEN_RELOADING = createError<[action: string]>( 19 | 'The form with action "%s" cannot use the "hooks" prop without also setting the "noReload" prop', 20 | 'E_CANNOT_USE_HOOKS_WHEN_RELOADING', 21 | 500 22 | ) 23 | -------------------------------------------------------------------------------- /packages/radonis-form/src/singletons.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-form 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { HydrationManager } from '@microeinhundert/radonis-server/standalone' 11 | 12 | export const hydrationManager = HydrationManager.getSingletonInstance() 13 | -------------------------------------------------------------------------------- /packages/radonis-form/src/types/main.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-form 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import type { MutationHooks, MutationStatus } from '@microeinhundert/radonis-hooks' 11 | import type { RouteParams, RouteQueryParams } from '@microeinhundert/radonis-types' 12 | import type { FormHTMLAttributes, ReactNode } from 'react' 13 | 14 | /** 15 | * Form method 16 | */ 17 | export type FormMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' 18 | 19 | /** 20 | * Form hooks 21 | */ 22 | export type FormHooks = MutationHooks 23 | 24 | /** 25 | * Form options 26 | */ 27 | export interface FormOptions { 28 | action$: string 29 | params?: RouteParams 30 | queryParams?: RouteQueryParams 31 | method: FormMethod 32 | hooks?: FormHooks 33 | noReload?: boolean 34 | throwOnFailure?: boolean 35 | useErrorBoundary?: boolean 36 | [key: string]: unknown 37 | } 38 | 39 | /** 40 | * Form props 41 | */ 42 | export type FormProps = Omit, 'children' | 'action' | 'method'> & 43 | FormOptions & { 44 | children?: ((props: FormChildrenProps) => ReactNode) | ReactNode 45 | } 46 | 47 | /** 48 | * Form children props 49 | */ 50 | export type FormChildrenProps = { 51 | data: TData | null 52 | error: TError | null 53 | status: MutationStatus 54 | } 55 | -------------------------------------------------------------------------------- /packages/radonis-form/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "rootDir": "./", 6 | "composite": true, 7 | "lib": ["dom", "dom.iterable", "esnext"], 8 | "module": "commonjs", 9 | "outDir": "./build" 10 | }, 11 | "include": ["./**/*"], 12 | "exclude": ["./node_modules", "./build"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/radonis-hooks/.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /packages/radonis-hooks/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "root": true 4 | } 5 | -------------------------------------------------------------------------------- /packages/radonis-hooks/.npmignore: -------------------------------------------------------------------------------- 1 | **/tsconfig.tsbuildinfo 2 | **/test 3 | **/bin -------------------------------------------------------------------------------- /packages/radonis-hooks/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /packages/radonis-hooks/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Leon Seipp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/radonis-hooks/README.md: -------------------------------------------------------------------------------- 1 | # Radonis Hooks 2 | 3 | ![npm (scoped)](https://img.shields.io/npm/v/@microeinhundert/radonis-hooks) 4 | 5 | > **Note**: This is a package used by Radonis internally and should not be installed directly. 6 | 7 | ## About 8 | 9 | Radonis is a full-stack application framework for building monolithic applications with a modern, React-based frontend stack. Radonis is built on top of the Node.js MVC framework [AdonisJS](https://adonisjs.com/) and extends it with features for server-side rendering and client-side hydration. 10 | 11 | ## Documentation 12 | 13 | Documentation is available on [radonis.vercel.app](https://radonis.vercel.app/). 14 | 15 | ## License 16 | 17 | [MIT](LICENSE) 18 | -------------------------------------------------------------------------------- /packages/radonis-hooks/bin/japaTypes.ts: -------------------------------------------------------------------------------- 1 | import type { Assert } from '@japa/assert' 2 | 3 | declare module '@japa/runner' { 4 | interface TestContext { 5 | assert: Assert 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/radonis-hooks/bin/test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from '@japa/assert' 2 | import { runFailedTests } from '@japa/run-failed-tests' 3 | import { configure, processCliArgs, run } from '@japa/runner' 4 | import { specReporter } from '@japa/spec-reporter' 5 | 6 | process.env.NODE_ENV = 'test' 7 | 8 | /* 9 | |-------------------------------------------------------------------------- 10 | | Configure tests 11 | |-------------------------------------------------------------------------- 12 | | 13 | | The configure method accepts the configuration to configure the Japa 14 | | tests runner. 15 | | 16 | | The first method call "processCliArgs" process the command line arguments 17 | | and turns them into a config object. Using this method is not mandatory. 18 | | 19 | | Please consult japa.dev/runner-config for the config docs. 20 | */ 21 | configure({ 22 | ...processCliArgs(process.argv.slice(2)), 23 | ...{ 24 | files: ['test/**/*.spec.ts'], 25 | plugins: [assert(), runFailedTests()], 26 | reporters: [specReporter()], 27 | importer: (filePath: string) => import(filePath), 28 | }, 29 | }) 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Run tests 34 | |-------------------------------------------------------------------------- 35 | | 36 | | The following "run" method is required to execute all the tests. 37 | | 38 | */ 39 | run() 40 | -------------------------------------------------------------------------------- /packages/radonis-hooks/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | /* 11 | * Client-side hooks 12 | */ 13 | export { useMutation } from './src/hooks/client/use_mutation' 14 | 15 | /* 16 | * Isomorphic hooks 17 | */ 18 | export { useFlashMessages } from './src/hooks/isomorphic/use_flash_messages' 19 | export { useFlushEffect } from './src/hooks/isomorphic/use_flush_effect' 20 | export { useGlobals } from './src/hooks/isomorphic/use_globals' 21 | export { useI18n } from './src/hooks/isomorphic/use_i18n' 22 | export { useManifest } from './src/hooks/isomorphic/use_manifest' 23 | export { useParams } from './src/hooks/isomorphic/use_params' 24 | export { useRoute } from './src/hooks/isomorphic/use_route' 25 | export { useRoutes } from './src/hooks/isomorphic/use_routes' 26 | export { useSearchParams } from './src/hooks/isomorphic/use_search_params' 27 | export { useUrlBuilder } from './src/hooks/isomorphic/use_url_builder' 28 | 29 | /* 30 | * Server-side hooks 31 | */ 32 | export { useApplication } from './src/hooks/server/use_application' 33 | export { useAssetsManager } from './src/hooks/server/use_assets_manager' 34 | export { useHttpContext } from './src/hooks/server/use_http_context' 35 | export { useManifestManager } from './src/hooks/server/use_manifest_manager' 36 | export { useRenderer } from './src/hooks/server/use_renderer' 37 | export { useRequest } from './src/hooks/server/use_request' 38 | export { useRouter } from './src/hooks/server/use_router' 39 | export { useServer } from './src/hooks/server/use_server' 40 | export { useSession } from './src/hooks/server/use_session' 41 | 42 | /* 43 | * Types 44 | */ 45 | export type { MutationHooks, MutationOptions, MutationStatus } from './src/types/main' 46 | -------------------------------------------------------------------------------- /packages/radonis-hooks/lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.ts': ['npm run lint -- --fix'], 3 | '*.{json,md}': ['npm run format'], 4 | } 5 | -------------------------------------------------------------------------------- /packages/radonis-hooks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@microeinhundert/radonis-hooks", 3 | "version": "5.0.4", 4 | "description": "Hooks package for Radonis", 5 | "type": "commonjs", 6 | "main": "./build/cjs/index.js", 7 | "types": "./build/cjs/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": { 11 | "types": "./build/esm/index.d.ts", 12 | "default": "./build/esm/index.js" 13 | }, 14 | "require": { 15 | "types": "./build/cjs/index.d.ts", 16 | "default": "./build/cjs/index.js" 17 | } 18 | } 19 | }, 20 | "sideEffects": false, 21 | "files": [ 22 | "build/cjs", 23 | "build/esm" 24 | ], 25 | "scripts": { 26 | "clean": "del-cli build", 27 | "build:cjs": "tsc --outDir ./build/cjs --module commonjs", 28 | "build:esm": "tsc --outDir ./build/esm --module esnext", 29 | "build": "\"$npm_execpath\" run build:cjs && \"$npm_execpath\" run build:esm", 30 | "lint": "TIMING=1 eslint . --ext=.ts", 31 | "format": "prettier --write .", 32 | "test": "node -r @adonisjs/require-ts/build/register bin/test.ts" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "git+ssh://git@github.com/microeinhundert/radonis.git", 37 | "directory": "packages/radonis-hooks" 38 | }, 39 | "bugs": { 40 | "url": "https://github.com/microeinhundert/radonis/issues" 41 | }, 42 | "publishConfig": { 43 | "access": "public" 44 | }, 45 | "engines": { 46 | "node": ">= 16.0.0", 47 | "npm": ">= 8.0.0" 48 | }, 49 | "keywords": [ 50 | "adonisjs", 51 | "adonis", 52 | "radonis", 53 | "hooks" 54 | ], 55 | "author": "Leon Seipp ", 56 | "license": "MIT", 57 | "peerDependencies": { 58 | "react": "^18.2.0", 59 | "react-dom": "^18.2.0" 60 | }, 61 | "devDependencies": { 62 | "@types/node": "^18.15.11", 63 | "@types/react": "^18.0.31", 64 | "@types/react-dom": "^18.0.11" 65 | }, 66 | "dependencies": { 67 | "@microeinhundert/radonis-types": "workspace:5.0.4", 68 | "@microeinhundert/radonis-shared": "workspace:5.0.4", 69 | "@microeinhundert/radonis-server": "workspace:5.0.4", 70 | "@microeinhundert/radonis-hydrate": "workspace:5.0.4", 71 | "intl-messageformat": "10.1.5", 72 | "superjson": "^1.12.2", 73 | "tslib": "^2.5.0" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/exceptions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { createError } from '@microeinhundert/radonis-shared' 11 | 12 | export const E_CANNOT_FIND_MESSAGE = createError<[identifier: string]>( 13 | 'Cannot find a message named "%s". Make sure that the message exists and that it can be detected by static analysis, see https://radonis.vercel.app/docs/compiler#static-analysis for more information', 14 | 'E_CANNOT_FIND_MESSAGE', 15 | 404 16 | ) 17 | 18 | export const E_CANNOT_FIND_ROUTE = createError<[identifier: string]>( 19 | 'Cannot find a route named "%s". Make sure that the route exists and that it can be detected by static analysis, see https://radonis.vercel.app/docs/compiler#static-analysis for more information', 20 | 'E_CANNOT_FIND_ROUTE', 21 | 404 22 | ) 23 | 24 | export const E_CANNOT_RETRIEVE_MANIFEST = createError( 25 | 'Cannot retrieve the Radonis manifest. Make sure that the Radonis server provider is configured correctly', 26 | 'E_CANNOT_RETRIEVE_MANIFEST', 27 | 404 28 | ) 29 | 30 | export const E_CANNOT_USE_ON_CLIENT = createError<[name: string]>( 31 | 'The hook "%s()" cannot be used client-side', 32 | 'E_CANNOT_USE_ON_CLIENT', 33 | 500 34 | ) 35 | 36 | export const E_INVALID_MUTATION_ACTION = createError<[action: string]>( 37 | 'Invalid mutation action "%s"', 38 | 'E_INVALID_MUTATION_ACTION', 39 | 500 40 | ) 41 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/hooks/client/internal/use_get_latest.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { useCallback, useRef } from 'react' 11 | 12 | export function useGetLatest(value: TValue): () => TValue { 13 | const ref = useRef(value) 14 | ref.current = value 15 | 16 | return useCallback(() => ref.current, []) 17 | } 18 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/hooks/client/internal/use_is_mounted.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { useRef } from 'react' 11 | 12 | import { useIsomorphicLayoutEffect } from './use_isomorphic_layout_effect' 13 | 14 | export function useIsMounted() { 15 | const isMounted = useRef(false) 16 | 17 | useIsomorphicLayoutEffect(() => { 18 | isMounted.current = true 19 | 20 | return () => { 21 | isMounted.current = false 22 | } 23 | }, []) 24 | 25 | return isMounted 26 | } 27 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/hooks/client/internal/use_isomorphic_layout_effect.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { useEffect, useLayoutEffect } from 'react' 11 | 12 | export const useIsomorphicLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect 13 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/hooks/client/internal/use_safe_callback.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { useCallback } from 'react' 11 | 12 | import { useIsMounted } from './use_is_mounted' 13 | 14 | export function useSafeCallback( 15 | callback: (...args: TArguments) => TReturnValue 16 | ) { 17 | const isMounted = useIsMounted() 18 | 19 | return useCallback((...args: TArguments) => (isMounted.current ? callback(...args) : void 0), [callback, isMounted]) 20 | } 21 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/hooks/isomorphic/use_flash_messages.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { useHydration } from '@microeinhundert/radonis-hydrate' 11 | 12 | import { hydrationManager } from '../../singletons' 13 | import { useManifest } from './use_manifest' 14 | 15 | /** 16 | * Hook for retrieving session flash messages 17 | * @see https://radonis.vercel.app/docs/hooks/use-flash-messages 18 | */ 19 | export function useFlashMessages() { 20 | const { flashMessages } = useManifest() 21 | const hydration = useHydration() 22 | 23 | /** 24 | * Find the flash message inside the registered flash messages 25 | */ 26 | function findFlashMessage(identifier: string) { 27 | const flashMessage = flashMessages[identifier] 28 | 29 | /** 30 | * If the flash message does not exist, 31 | * check if it exists on the zero index 32 | */ 33 | if (typeof flashMessage !== 'string' && identifier && !identifier.match(/\.(\d*)$/i)) { 34 | return findFlashMessage(`${identifier}.0`) 35 | } 36 | 37 | if (hydration.id && flashMessage) { 38 | hydrationManager.requireFlashMessage(identifier) 39 | } 40 | 41 | return flashMessage 42 | } 43 | 44 | /** 45 | * Get all flash messages 46 | */ 47 | function all() { 48 | if (hydration.id) { 49 | hydrationManager.requireAllFlashMessages() 50 | } 51 | 52 | return flashMessages 53 | } 54 | 55 | /** 56 | * Check if a specific flash message exists 57 | */ 58 | function has(identifier: string) { 59 | return !!findFlashMessage(identifier) 60 | } 61 | 62 | /** 63 | * Check if any flash messages exist 64 | */ 65 | function hasAny() { 66 | return !!Object.keys(all()).length 67 | } 68 | 69 | /** 70 | * Get a specific flash message 71 | */ 72 | function get(identifier: string) { 73 | return findFlashMessage(identifier) 74 | } 75 | 76 | return { 77 | all, 78 | has$: has, 79 | hasAny, 80 | get$: get, 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/hooks/isomorphic/use_flush_effect.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import type { FlushCallback } from '@microeinhundert/radonis-types' 11 | 12 | import { useRenderer } from '../server/use_renderer' 13 | 14 | /** 15 | * Hook for injecting effects to be executed after rendering 16 | * @see https://radonis.vercel.app/docs/hooks/use-flush-effects 17 | */ 18 | export function useFlushEffect(callback: FlushCallback) { 19 | try { 20 | const renderer = useRenderer() 21 | 22 | renderer.withFlushCallbacks([callback]) 23 | } catch { 24 | /** 25 | * Don't throw when used on the client 26 | */ 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/hooks/isomorphic/use_globals.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { useManifest } from './use_manifest' 11 | 12 | /** 13 | * Hook for retrieving globals from the manifest 14 | * @see https://radonis.vercel.app/docs/hooks/use-globals 15 | */ 16 | export function useGlobals() { 17 | const { globals } = useManifest() 18 | 19 | return globals 20 | } 21 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/hooks/isomorphic/use_i18n.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { useHydration } from '@microeinhundert/radonis-hydrate' 11 | import type { MessageData } from '@microeinhundert/radonis-types' 12 | import IntlMessageFormat from 'intl-messageformat' 13 | 14 | import { E_CANNOT_FIND_MESSAGE } from '../../exceptions' 15 | import { hydrationManager } from '../../singletons' 16 | import { useManifest } from './use_manifest' 17 | 18 | /** 19 | * Hook for retrieving and formatting translation messages 20 | * @see https://radonis.vercel.app/docs/hooks/use-i18n 21 | */ 22 | export function useI18n() { 23 | const { locale, messages } = useManifest() 24 | const hydration = useHydration() 25 | 26 | /** 27 | * Find the message inside the registered messages and 28 | * raise exception when unable to 29 | */ 30 | function findMessageOrFail(identifier: string) { 31 | const message = messages[identifier] 32 | 33 | if (typeof message !== 'string') { 34 | throw new E_CANNOT_FIND_MESSAGE([identifier]) 35 | } 36 | 37 | if (hydration.id) { 38 | hydrationManager.requireMessage(identifier) 39 | } 40 | 41 | return message 42 | } 43 | 44 | /** 45 | * Format a message 46 | */ 47 | function formatMessage(identifier: string, data?: MessageData) { 48 | const message = findMessageOrFail(identifier) 49 | 50 | return new IntlMessageFormat( 51 | message, 52 | locale, 53 | {}, 54 | { 55 | formatters: { 56 | getNumberFormat: (...args) => new Intl.NumberFormat(...args), 57 | getDateTimeFormat: (...args) => new Intl.DateTimeFormat(...args), 58 | getPluralRules: (...args) => new Intl.PluralRules(...args), 59 | }, 60 | ignoreTag: true, 61 | } 62 | ).format(data || {}) as string 63 | } 64 | 65 | return { 66 | formatMessage$: formatMessage, 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/hooks/isomorphic/use_manifest.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { isClient } from '@microeinhundert/radonis-shared' 11 | import type { ManifestContract } from '@microeinhundert/radonis-types' 12 | import superjson from 'superjson' 13 | import type { SuperJSONResult } from 'superjson/dist/types' 14 | 15 | import { E_CANNOT_RETRIEVE_MANIFEST } from '../../exceptions' 16 | 17 | declare global { 18 | var radonisManifest: ManifestContract | undefined 19 | } 20 | 21 | let cachedManifest: Readonly | undefined 22 | 23 | /** 24 | * Hook for retrieving the Radonis manifest 25 | * @see https://radonis.vercel.app/docs/hooks/use-manifest 26 | */ 27 | export function useManifest() { 28 | if (cachedManifest && isClient) { 29 | return cachedManifest 30 | } 31 | 32 | const manifest = globalThis.radonisManifest 33 | 34 | if (!manifest) { 35 | throw new E_CANNOT_RETRIEVE_MANIFEST() 36 | } 37 | 38 | return (cachedManifest = isClient 39 | ? superjson.deserialize(manifest as unknown as SuperJSONResult) 40 | : manifest) as Readonly 41 | } 42 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/hooks/isomorphic/use_params.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { useRoute } from './use_route' 11 | 12 | /** 13 | * Hook for retrieving the params of the current route 14 | * @see https://radonis.vercel.app/docs/hooks/use-params 15 | */ 16 | export function useParams() { 17 | const { current } = useRoute() 18 | 19 | return current.params 20 | } 21 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/hooks/isomorphic/use_route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { useHydration } from '@microeinhundert/radonis-hydrate' 11 | 12 | import { hydrationManager } from '../../singletons' 13 | import { useManifest } from './use_manifest' 14 | 15 | /** 16 | * Hook for retrieving info about the current route 17 | * @see https://radonis.vercel.app/docs/hooks/use-route 18 | */ 19 | export function useRoute() { 20 | const { route, routes } = useManifest() 21 | const hydration = useHydration() 22 | 23 | function isCurrent(identifier: string, options?: { exact?: boolean }) { 24 | if (options?.exact) { 25 | return route?.identifier === identifier 26 | } 27 | 28 | if (typeof routes[identifier] === 'string') { 29 | if (hydration.id) { 30 | hydrationManager.requireRoute(identifier) 31 | } 32 | 33 | return !!route?.pattern?.startsWith(routes[identifier]) 34 | } 35 | 36 | return false 37 | } 38 | 39 | return { 40 | current: route!, 41 | isCurrent$: isCurrent, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/hooks/isomorphic/use_routes.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { useHydration } from '@microeinhundert/radonis-hydrate' 11 | 12 | import { hydrationManager } from '../../singletons' 13 | import { useManifest } from './use_manifest' 14 | 15 | /** 16 | * Hook for retrieving all routes available in the application 17 | * @see https://radonis.vercel.app/docs/hooks/use-routes 18 | */ 19 | export function useRoutes() { 20 | const { routes } = useManifest() 21 | const hydration = useHydration() 22 | 23 | if (hydration.id) { 24 | hydrationManager.requireAllRoutes() 25 | } 26 | 27 | return routes 28 | } 29 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/hooks/isomorphic/use_search_params.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { useRoute } from './use_route' 11 | 12 | /** 13 | * Hook for retrieving the search params of the current route 14 | * @see https://radonis.vercel.app/docs/hooks/use-search-params 15 | */ 16 | export function useSearchParams() { 17 | const { current } = useRoute() 18 | 19 | return current.searchParams 20 | } 21 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/hooks/isomorphic/use_url_builder.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { useHydration } from '@microeinhundert/radonis-hydrate' 11 | import { RadonisException } from '@microeinhundert/radonis-shared' 12 | import { UrlBuilder } from '@microeinhundert/radonis-shared' 13 | import { useMemo } from 'react' 14 | 15 | import { E_CANNOT_FIND_ROUTE } from '../../exceptions' 16 | import { hydrationManager } from '../../singletons' 17 | import { useManifest } from './use_manifest' 18 | 19 | /** 20 | * Hook for building URLs to routes 21 | * @see https://radonis.vercel.app/docs/hooks/use-url-builder 22 | */ 23 | export function useUrlBuilder() { 24 | const { routes } = useManifest() 25 | const hydration = useHydration() 26 | 27 | const urlBuilder = useMemo( 28 | () => 29 | new UrlBuilder(routes, { 30 | onFoundRoute: (identifier) => { 31 | if (hydration.id) { 32 | hydrationManager.requireRoute(identifier) 33 | } 34 | }, 35 | }), 36 | [routes, hydration] 37 | ) 38 | 39 | return { 40 | make$: (...args: Parameters) => { 41 | try { 42 | const url = urlBuilder.make$(...args) 43 | return url 44 | } catch (error) { 45 | if (error instanceof RadonisException && error.code === 'E_CANNOT_FIND_ROUTE') { 46 | throw new E_CANNOT_FIND_ROUTE([args[0]]) 47 | } 48 | throw error 49 | } 50 | }, 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/hooks/server/use_application.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { E_CANNOT_USE_ON_CLIENT } from '../../exceptions' 11 | import { useServer } from './use_server' 12 | 13 | /** 14 | * Hook for retrieving the AdonisJS `ApplicationContract` 15 | * @see https://radonis.vercel.app/docs/hooks/use-application 16 | */ 17 | export function useApplication() { 18 | try { 19 | const { application } = useServer() 20 | 21 | return application 22 | } catch { 23 | throw new E_CANNOT_USE_ON_CLIENT(['useApplication']) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/hooks/server/use_assets_manager.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { assetsManagerContext } from '@microeinhundert/radonis-server/standalone' 11 | import { useContext } from 'react' 12 | 13 | import { E_CANNOT_USE_ON_CLIENT } from '../../exceptions' 14 | 15 | /** 16 | * Hook for retrieving the Radonis `AssetsManagerContract` 17 | * @see https://radonis.vercel.app/docs/hooks/use-assets-manager 18 | */ 19 | export function useAssetsManager() { 20 | const context = useContext(assetsManagerContext) 21 | 22 | if (!context) { 23 | throw new E_CANNOT_USE_ON_CLIENT(['useAssetsManager']) 24 | } 25 | 26 | return context 27 | } 28 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/hooks/server/use_http_context.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { E_CANNOT_USE_ON_CLIENT } from '../../exceptions' 11 | import { useServer } from './use_server' 12 | 13 | /** 14 | * Hook for retrieving the AdonisJS `HttpContextContract` 15 | * @see https://radonis.vercel.app/docs/hooks/use-http-context 16 | */ 17 | export function useHttpContext() { 18 | try { 19 | const { httpContext } = useServer() 20 | 21 | return httpContext 22 | } catch { 23 | throw new E_CANNOT_USE_ON_CLIENT(['useHttpContext']) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/hooks/server/use_manifest_manager.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { manifestManagerContext } from '@microeinhundert/radonis-server/standalone' 11 | import { useContext } from 'react' 12 | 13 | import { E_CANNOT_USE_ON_CLIENT } from '../../exceptions' 14 | 15 | /** 16 | * Hook for retrieving the Radonis `ManifestManagerContract` 17 | * @see https://radonis.vercel.app/docs/hooks/use-manifest-manager 18 | */ 19 | export function useManifestManager() { 20 | const context = useContext(manifestManagerContext) 21 | 22 | if (!context) { 23 | throw new E_CANNOT_USE_ON_CLIENT(['useManifestManager']) 24 | } 25 | 26 | return context 27 | } 28 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/hooks/server/use_renderer.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { rendererContext } from '@microeinhundert/radonis-server/standalone' 11 | import { useContext } from 'react' 12 | 13 | import { E_CANNOT_USE_ON_CLIENT } from '../../exceptions' 14 | 15 | /** 16 | * Hook for retrieving the Radonis `RendererContract` 17 | * @see https://radonis.vercel.app/docs/hooks/use-renderer 18 | */ 19 | export function useRenderer() { 20 | const context = useContext(rendererContext) 21 | 22 | if (!context) { 23 | throw new E_CANNOT_USE_ON_CLIENT(['useRenderer']) 24 | } 25 | 26 | return context 27 | } 28 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/hooks/server/use_request.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { E_CANNOT_USE_ON_CLIENT } from '../../exceptions' 11 | import { useHttpContext } from './use_http_context' 12 | 13 | /** 14 | * Hook for retrieving the AdonisJS `RequestContract` 15 | * @see https://radonis.vercel.app/docs/hooks/use-request 16 | */ 17 | export function useRequest() { 18 | try { 19 | const { request } = useHttpContext() 20 | 21 | return request 22 | } catch { 23 | throw new E_CANNOT_USE_ON_CLIENT(['useRequest']) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/hooks/server/use_router.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { E_CANNOT_USE_ON_CLIENT } from '../../exceptions' 11 | import { useServer } from './use_server' 12 | 13 | /** 14 | * Hook for retrieving the AdonisJS `RouterContract` 15 | * @see https://radonis.vercel.app/docs/hooks/use-router 16 | */ 17 | export function useRouter() { 18 | try { 19 | const { router } = useServer() 20 | 21 | return router 22 | } catch { 23 | throw new E_CANNOT_USE_ON_CLIENT(['useRouter']) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/hooks/server/use_server.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { serverContext } from '@microeinhundert/radonis-server/standalone' 11 | import { useContext } from 'react' 12 | 13 | import { E_CANNOT_USE_ON_CLIENT } from '../../exceptions' 14 | 15 | /** 16 | * Hook for retrieving the Radonis `ServerContract` 17 | * @see https://radonis.vercel.app/docs/hooks/use-server 18 | */ 19 | export function useServer() { 20 | const context = useContext(serverContext) 21 | 22 | if (!context) { 23 | throw new E_CANNOT_USE_ON_CLIENT(['useServer']) 24 | } 25 | 26 | return context 27 | } 28 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/hooks/server/use_session.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { E_CANNOT_USE_ON_CLIENT } from '../../exceptions' 11 | import { useHttpContext } from './use_http_context' 12 | 13 | /** 14 | * Hook for retrieving the AdonisJS `SessionContract` 15 | * @see https://radonis.vercel.app/docs/hooks/use-session 16 | */ 17 | export function useSession() { 18 | try { 19 | const { session } = useHttpContext() 20 | 21 | return session 22 | } catch { 23 | throw new E_CANNOT_USE_ON_CLIENT(['useSession']) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/singletons.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { HydrationManager } from '@microeinhundert/radonis-server/standalone' 11 | 12 | export const hydrationManager = HydrationManager.getSingletonInstance() 13 | -------------------------------------------------------------------------------- /packages/radonis-hooks/src/types/main.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hooks 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import type { MaybePromise } from '@microeinhundert/radonis-types' 11 | 12 | export type MutationResetFunction = () => void 13 | 14 | export type MutationRollbackFunction = () => void 15 | 16 | export type MutationStatus = 'idle' | 'loading' | 'success' | 'failure' 17 | 18 | export type MutationResult = [ 19 | (input: TInput) => Promise, 20 | { status: MutationStatus; data?: TData; error?: TError; reset: MutationResetFunction } 21 | ] 22 | 23 | export interface MutationHooks { 24 | /** 25 | * A function to be executed before the mutation runs. 26 | * 27 | * It receives the same input as the mutate function. 28 | * 29 | * It can be an async or sync function, in both cases if it returns a function, 30 | * it will be kept as a way to rollback the changes applied inside onMutate. 31 | */ 32 | onMutate?(params: { input: TInput }): MaybePromise 33 | 34 | /** 35 | * A function to be executed after the mutation resolves successfully. 36 | * 37 | * It receives the result of the mutation. 38 | * 39 | * If a Promise is returned, it will be awaited before proceeding. 40 | */ 41 | onSuccess?(params: { data: TData; input: TInput }): MaybePromise 42 | 43 | /** 44 | * A function to be executed after the mutation failed to execute. 45 | * 46 | * If a Promise is returned, it will be awaited before proceeding. 47 | */ 48 | onFailure?(params: { error: TError; rollback: MutationRollbackFunction | void; input: TInput }): MaybePromise 49 | 50 | /** 51 | * A function to be executed after the mutation has resolved, either 52 | * successfully or as failure. 53 | * 54 | * This function receives the error or the result of the mutation. 55 | * 56 | * If a Promise is returned, it will be awaited before proceeding. 57 | */ 58 | onSettled?( 59 | params: 60 | | { status: 'success'; data: TData; input: TInput } 61 | | { 62 | status: 'failure' 63 | error: TError 64 | rollback: MutationRollbackFunction | void 65 | input: TInput 66 | } 67 | ): MaybePromise 68 | } 69 | 70 | export interface MutationOptions extends MutationHooks { 71 | /** 72 | * If `true`, a failure in the mutation will cause the `mutate` 73 | * function to throw. 74 | */ 75 | throwOnFailure?: boolean 76 | 77 | /** 78 | * If `true`, a failure in the mutation will cause the hook to 79 | * throw at render time, making error boundaries catch the error. 80 | */ 81 | useErrorBoundary?: boolean 82 | } 83 | -------------------------------------------------------------------------------- /packages/radonis-hooks/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "rootDir": "./", 6 | "composite": true, 7 | "lib": ["dom", "dom.iterable", "esnext"], 8 | "module": "commonjs", 9 | "outDir": "./build", 10 | "types": ["@adonisjs/session"] 11 | }, 12 | "include": ["./**/*"], 13 | "exclude": ["./node_modules", "./build"] 14 | } 15 | -------------------------------------------------------------------------------- /packages/radonis-hydrate/.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /packages/radonis-hydrate/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "root": true 4 | } 5 | -------------------------------------------------------------------------------- /packages/radonis-hydrate/.npmignore: -------------------------------------------------------------------------------- 1 | **/tsconfig.tsbuildinfo 2 | **/test 3 | **/bin -------------------------------------------------------------------------------- /packages/radonis-hydrate/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /packages/radonis-hydrate/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Leon Seipp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/radonis-hydrate/README.md: -------------------------------------------------------------------------------- 1 | # Radonis Hydrate 2 | 3 | ![npm (scoped)](https://img.shields.io/npm/v/@microeinhundert/radonis-hydrate) 4 | 5 | > **Note**: This is a package used by Radonis internally and should not be installed directly. 6 | 7 | ## About 8 | 9 | Radonis is a full-stack application framework for building monolithic applications with a modern, React-based frontend stack. Radonis is built on top of the Node.js MVC framework [AdonisJS](https://adonisjs.com/) and extends it with features for server-side rendering and client-side hydration. 10 | 11 | ## Documentation 12 | 13 | Documentation is available on [radonis.vercel.app](https://radonis.vercel.app/). 14 | 15 | ## License 16 | 17 | [MIT](LICENSE) 18 | -------------------------------------------------------------------------------- /packages/radonis-hydrate/bin/japaTypes.ts: -------------------------------------------------------------------------------- 1 | import type { Assert } from '@japa/assert' 2 | 3 | declare module '@japa/runner' { 4 | interface TestContext { 5 | assert: Assert 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/radonis-hydrate/bin/test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from '@japa/assert' 2 | import { runFailedTests } from '@japa/run-failed-tests' 3 | import { configure, processCliArgs, run } from '@japa/runner' 4 | import { specReporter } from '@japa/spec-reporter' 5 | 6 | process.env.NODE_ENV = 'test' 7 | 8 | /* 9 | |-------------------------------------------------------------------------- 10 | | Configure tests 11 | |-------------------------------------------------------------------------- 12 | | 13 | | The configure method accepts the configuration to configure the Japa 14 | | tests runner. 15 | | 16 | | The first method call "processCliArgs" process the command line arguments 17 | | and turns them into a config object. Using this method is not mandatory. 18 | | 19 | | Please consult japa.dev/runner-config for the config docs. 20 | */ 21 | configure({ 22 | ...processCliArgs(process.argv.slice(2)), 23 | ...{ 24 | files: ['test/**/*.spec.ts'], 25 | plugins: [assert(), runFailedTests()], 26 | reporters: [specReporter()], 27 | importer: (filePath: string) => import(filePath), 28 | }, 29 | }) 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Run tests 34 | |-------------------------------------------------------------------------- 35 | | 36 | | The following "run" method is required to execute all the tests. 37 | | 38 | */ 39 | run() 40 | -------------------------------------------------------------------------------- /packages/radonis-hydrate/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hydrate 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | export { HydrationRoot } from './src/components/hydration_root' 11 | export { hydrationContext, HydrationContextProvider } from './src/contexts/hydration_context' 12 | export { useHydrated } from './src/hooks/use_hydrated' 13 | export { useHydration } from './src/hooks/use_hydration' 14 | export { hydrateIsland } from './src/hydrate_island' 15 | export { Hydrator } from './src/hydrator/main' 16 | export { island } from './src/island' 17 | -------------------------------------------------------------------------------- /packages/radonis-hydrate/lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.ts': ['npm run lint -- --fix'], 3 | '*.{json,md}': ['npm run format'], 4 | } 5 | -------------------------------------------------------------------------------- /packages/radonis-hydrate/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@microeinhundert/radonis-hydrate", 3 | "version": "5.0.4", 4 | "description": "Hydration package for Radonis", 5 | "type": "commonjs", 6 | "main": "./build/cjs/index.js", 7 | "types": "./build/cjs/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": { 11 | "types": "./build/esm/index.d.ts", 12 | "default": "./build/esm/index.js" 13 | }, 14 | "require": { 15 | "types": "./build/cjs/index.d.ts", 16 | "default": "./build/cjs/index.js" 17 | } 18 | } 19 | }, 20 | "sideEffects": false, 21 | "files": [ 22 | "build/cjs", 23 | "build/esm" 24 | ], 25 | "scripts": { 26 | "clean": "del-cli build", 27 | "build:cjs": "tsc --outDir ./build/cjs --module commonjs", 28 | "build:esm": "tsc --outDir ./build/esm --module esnext", 29 | "build": "\"$npm_execpath\" run build:cjs && \"$npm_execpath\" run build:esm", 30 | "lint": "TIMING=1 eslint . --ext=.ts", 31 | "format": "prettier --write .", 32 | "test": "node -r @adonisjs/require-ts/build/register bin/test.ts" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "git+ssh://git@github.com/microeinhundert/radonis.git", 37 | "directory": "packages/radonis-hydrate" 38 | }, 39 | "bugs": { 40 | "url": "https://github.com/microeinhundert/radonis/issues" 41 | }, 42 | "publishConfig": { 43 | "access": "public" 44 | }, 45 | "engines": { 46 | "node": ">= 16.0.0", 47 | "npm": ">= 8.0.0" 48 | }, 49 | "keywords": [ 50 | "adonisjs", 51 | "adonis", 52 | "radonis", 53 | "hydrate" 54 | ], 55 | "author": "Leon Seipp ", 56 | "license": "MIT", 57 | "peerDependencies": { 58 | "react": "^18.2.0", 59 | "react-dom": "^18.2.0" 60 | }, 61 | "devDependencies": { 62 | "@types/node": "^18.15.11", 63 | "@types/react": "^18.0.31", 64 | "@types/react-dom": "^18.0.11" 65 | }, 66 | "dependencies": { 67 | "@microeinhundert/radonis-types": "workspace:5.0.4", 68 | "@microeinhundert/radonis-shared": "workspace:5.0.4", 69 | "@microeinhundert/radonis-server": "workspace:5.0.4", 70 | "@microeinhundert/radonis-build": "workspace:5.0.4", 71 | "superjson": "^1.12.2", 72 | "tslib": "^2.5.0" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/radonis-hydrate/src/components/hydration_root.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hydrate 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { assetsManagerContext, manifestManagerContext } from '@microeinhundert/radonis-server/standalone' 11 | import type { ReactElement } from 'react' 12 | import { Children, createElement as h, isValidElement, useContext, useId } from 'react' 13 | 14 | import { HydrationContextProvider } from '../contexts/hydration_context' 15 | import { E_CANNOT_HYDRATE_WITH_CHILDREN, E_NOT_AN_ISLAND } from '../exceptions' 16 | import { useHydration } from '../hooks/use_hydration' 17 | import { islandIdentifierSymbol } from '../symbols' 18 | 19 | /** 20 | * Component for hydrating islands 21 | * @see https://radonis.vercel.app/docs/components#hydrating-components 22 | */ 23 | export function HydrationRoot({ 24 | children, 25 | className, 26 | disabled, 27 | }: { 28 | children: ReactElement> 29 | className?: string 30 | disabled?: boolean 31 | }) { 32 | const manifestManager = useContext(manifestManagerContext) 33 | const assetsManager = useContext(assetsManagerContext) 34 | const { id: parentHydrationRootId } = useHydration() 35 | const hydrationRootId = useId() 36 | 37 | const island = Children.only(children) 38 | const islandIdentifier = island?.type?.[islandIdentifierSymbol] 39 | 40 | if (typeof islandIdentifier !== 'string' || !isValidElement(island)) { 41 | throw new E_NOT_AN_ISLAND([hydrationRootId]) 42 | } 43 | 44 | if (island.props.children) { 45 | throw new E_CANNOT_HYDRATE_WITH_CHILDREN([islandIdentifier, hydrationRootId]) 46 | } 47 | 48 | /* 49 | * Ignore if unsupported environment, disabled or child of another HydrationRoot 50 | */ 51 | if (!manifestManager || !assetsManager || disabled || parentHydrationRootId) { 52 | return h( 53 | 'div', 54 | { 55 | className, 56 | }, 57 | island 58 | ) 59 | } 60 | 61 | /* 62 | * Register the hydration on the ManifestManager 63 | */ 64 | manifestManager.registerHydration(hydrationRootId, islandIdentifier, island.props) 65 | 66 | /* 67 | * Require the island on the AssetsManager 68 | */ 69 | assetsManager.requireIsland(islandIdentifier) 70 | 71 | return h( 72 | HydrationContextProvider, 73 | { 74 | value: { 75 | hydrated: false, 76 | id: hydrationRootId, 77 | }, 78 | }, 79 | h( 80 | 'div', 81 | { 82 | className, 83 | 'data-hydration-root': hydrationRootId, 84 | }, 85 | island 86 | ) 87 | ) 88 | } 89 | 90 | HydrationRoot.displayName = 'RadonisHydrationRoot' 91 | -------------------------------------------------------------------------------- /packages/radonis-hydrate/src/contexts/hydration_context.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hydrate 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { createContext } from 'react' 11 | 12 | export const hydrationContext = createContext<{ 13 | hydrated: boolean 14 | id: string | null 15 | }>({ 16 | hydrated: false, 17 | id: null, 18 | }) 19 | 20 | export const HydrationContextProvider = hydrationContext.Provider 21 | -------------------------------------------------------------------------------- /packages/radonis-hydrate/src/exceptions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hydrate 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { createError } from '@microeinhundert/radonis-shared' 11 | 12 | export const E_CANNOT_HYDRATE_WITH_CHILDREN = createError<[islandIdentifier: string, hydrationRootId: string]>( 13 | 'The island "%s" within HydrationRoot "%s" has children', 14 | 'E_CANNOT_HYDRATE_WITH_CHILDREN', 15 | 500 16 | ) 17 | 18 | export const E_CANNOT_HYDRATE = createError<[islandIdentifier: string, hydrationRootId: string]>( 19 | 'The server-rendered island "%s" within HydrationRoot "%s" could not be hydrated. Restart the server and try again', 20 | 'E_CANNOT_HYDRATE', 21 | 500 22 | ) 23 | 24 | export const E_CANNOT_RETRIEVE_MANIFEST = createError( 25 | 'Cannot retrieve the Radonis manifest. Make sure that the Radonis server provider is configured correctly', 26 | 'E_CANNOT_RETRIEVE_MANIFEST', 27 | 404 28 | ) 29 | 30 | export const E_ISLAND_ALREADY_REGISTERED = createError<[islandIdentifier: string]>( 31 | 'The island "%s" is already registered for hydration. Make sure you do not use the same name for multiple islands', 32 | 'E_ISLAND_ALREADY_REGISTERED', 33 | 500 34 | ) 35 | 36 | export const E_NOT_AN_ISLAND = createError<[hydrationRootId: string]>( 37 | 'The component within HydrationRoot "%s" is not an island. Make sure the component is wrapped with the "island" function', 38 | 'E_NOT_AN_ISLAND', 39 | 500 40 | ) 41 | -------------------------------------------------------------------------------- /packages/radonis-hydrate/src/hooks/use_hydrated.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hydrate 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { useEffect, useState } from 'react' 11 | 12 | import { useHydration } from './use_hydration' 13 | 14 | /** 15 | * Hook for checking if a component was hydrated client-side 16 | * @see https://radonis.vercel.app/docs/hooks/use-hydrated 17 | */ 18 | export function useHydrated() { 19 | const hydration = useHydration() 20 | const [hydrated, setHydrated] = useState(false) 21 | 22 | useEffect(() => { 23 | setHydrated(hydration.hydrated) 24 | // eslint-disable-next-line react-hooks/exhaustive-deps 25 | }, []) 26 | 27 | return hydrated 28 | } 29 | -------------------------------------------------------------------------------- /packages/radonis-hydrate/src/hooks/use_hydration.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hydrate 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { useContext } from 'react' 11 | 12 | import { hydrationContext } from '../contexts/hydration_context' 13 | 14 | /** 15 | * Hook for retrieving info about the closest {@link https://radonis.vercel.app/docs/components#hydrating-components HydrationRoot} 16 | * @see https://radonis.vercel.app/docs/hooks/use-hydration 17 | */ 18 | export function useHydration() { 19 | const context = useContext(hydrationContext) 20 | 21 | return context 22 | } 23 | -------------------------------------------------------------------------------- /packages/radonis-hydrate/src/hydrate_island.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hydrate 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import type { ComponentType } from 'react' 11 | 12 | import { hydrator } from './singletons' 13 | 14 | /** 15 | * Hydrate an island (injected by the compiler) 16 | */ 17 | export function hydrateIsland>(islandIdentifier: string, Component: T): T { 18 | hydrator.registerIsland(islandIdentifier, Component) 19 | 20 | return Component 21 | } 22 | -------------------------------------------------------------------------------- /packages/radonis-hydrate/src/hydrator/constants.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hydrate 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | export const HYDRATION_ROOT_SELECTOR = '[data-hydration-root]' 11 | -------------------------------------------------------------------------------- /packages/radonis-hydrate/src/island.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hydrate 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import type { ComponentType } from 'react' 11 | 12 | import { islandIdentifierSymbol } from './symbols' 13 | 14 | /** 15 | * Make a component an island 16 | * @see https://radonis.vercel.app/docs/components#hydrating-components 17 | */ 18 | export function island>(islandIdentifier: string, Component: T): T { 19 | Component[islandIdentifierSymbol] = islandIdentifier 20 | 21 | return Component 22 | } 23 | -------------------------------------------------------------------------------- /packages/radonis-hydrate/src/singletons.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hydrate 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { PluginsManager } from '@microeinhundert/radonis-server/standalone' 11 | 12 | import { Hydrator } from './hydrator/main' 13 | 14 | export const pluginsManager = PluginsManager.getSingletonInstance() 15 | export const hydrator = Hydrator.getSingletonInstance(pluginsManager) 16 | -------------------------------------------------------------------------------- /packages/radonis-hydrate/src/symbols.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hydrate 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | export const islandIdentifierSymbol = Symbol('radonisIslandIdentifier') 11 | -------------------------------------------------------------------------------- /packages/radonis-hydrate/src/utils/get_manifest_or_fail.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-hydrate 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { isClient } from '@microeinhundert/radonis-shared' 11 | import type { ManifestContract } from '@microeinhundert/radonis-types' 12 | import superjson from 'superjson' 13 | import type { SuperJSONResult } from 'superjson/dist/types' 14 | 15 | import { E_CANNOT_RETRIEVE_MANIFEST } from '../exceptions' 16 | 17 | declare global { 18 | var radonisManifest: ManifestContract | undefined 19 | } 20 | 21 | let cachedManifest: Readonly | undefined 22 | 23 | /** 24 | * Get the manifest, fail if it does not exist on the global scope 25 | */ 26 | export function getManifestOrFail(): Readonly { 27 | if (cachedManifest && isClient) { 28 | return cachedManifest 29 | } 30 | 31 | const manifest = globalThis.radonisManifest 32 | 33 | if (!manifest) { 34 | throw new E_CANNOT_RETRIEVE_MANIFEST() 35 | } 36 | 37 | return (cachedManifest = isClient 38 | ? superjson.deserialize(manifest as unknown as SuperJSONResult) 39 | : manifest) as Readonly 40 | } 41 | -------------------------------------------------------------------------------- /packages/radonis-hydrate/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "rootDir": "./", 6 | "composite": true, 7 | "lib": ["dom", "dom.iterable", "esnext"], 8 | "module": "commonjs", 9 | "outDir": "./build" 10 | }, 11 | "include": ["./**/*"], 12 | "exclude": ["./node_modules", "./build"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/radonis-query/.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /packages/radonis-query/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "root": true 4 | } 5 | -------------------------------------------------------------------------------- /packages/radonis-query/.npmignore: -------------------------------------------------------------------------------- 1 | **/tsconfig.tsbuildinfo 2 | **/test 3 | **/bin -------------------------------------------------------------------------------- /packages/radonis-query/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /packages/radonis-query/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Leon Seipp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/radonis-query/README.md: -------------------------------------------------------------------------------- 1 | # Query Plugin for Radonis 2 | 3 | ![npm (scoped)](https://img.shields.io/npm/v/@microeinhundert/radonis-query) 4 | 5 | Plugin that adds asynchronous query utilities powered by [TanStack Query](https://tanstack.com/query/v4) to your Radonis application. 6 | 7 | > This plugin is experimental and subject to changes. 8 | 9 | ## About 10 | 11 | Radonis is a full-stack application framework for building monolithic applications with a modern, React-based frontend stack. Radonis is built on top of the Node.js MVC framework [AdonisJS](https://adonisjs.com/) and extends it with features for server-side rendering and client-side hydration. 12 | 13 | ## Documentation 14 | 15 | Documentation is coming soon. 16 | 17 | ## License 18 | 19 | [MIT](LICENSE) 20 | -------------------------------------------------------------------------------- /packages/radonis-query/bin/japaTypes.ts: -------------------------------------------------------------------------------- 1 | import type { Assert } from '@japa/assert' 2 | 3 | declare module '@japa/runner' { 4 | interface TestContext { 5 | assert: Assert 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/radonis-query/bin/test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from '@japa/assert' 2 | import { runFailedTests } from '@japa/run-failed-tests' 3 | import { configure, processCliArgs, run } from '@japa/runner' 4 | import { specReporter } from '@japa/spec-reporter' 5 | 6 | process.env.NODE_ENV = 'test' 7 | 8 | /* 9 | |-------------------------------------------------------------------------- 10 | | Configure tests 11 | |-------------------------------------------------------------------------- 12 | | 13 | | The configure method accepts the configuration to configure the Japa 14 | | tests runner. 15 | | 16 | | The first method call "processCliArgs" process the command line arguments 17 | | and turns them into a config object. Using this method is not mandatory. 18 | | 19 | | Please consult japa.dev/runner-config for the config docs. 20 | */ 21 | configure({ 22 | ...processCliArgs(process.argv.slice(2)), 23 | ...{ 24 | files: ['test/**/*.spec.ts'], 25 | plugins: [assert(), runFailedTests()], 26 | reporters: [specReporter()], 27 | importer: (filePath: string) => import(filePath), 28 | }, 29 | }) 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Run tests 34 | |-------------------------------------------------------------------------- 35 | | 36 | | The following "run" method is required to execute all the tests. 37 | | 38 | */ 39 | run() 40 | -------------------------------------------------------------------------------- /packages/radonis-query/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-query 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | export { useQueryBaseUrl } from './src/hooks/use_query_base_url' 11 | export { useServerMutation } from './src/hooks/use_server_mutation' 12 | export { useServerQuery } from './src/hooks/use_server_query' 13 | export { queryPlugin } from './src/plugin' 14 | export { getQueryKeyForURL } from './src/utils/get_query_key_for_url' 15 | -------------------------------------------------------------------------------- /packages/radonis-query/lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.ts': ['npm run lint -- --fix'], 3 | '*.{json,md}': ['npm run format'], 4 | } 5 | -------------------------------------------------------------------------------- /packages/radonis-query/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@microeinhundert/radonis-query", 3 | "version": "5.0.4", 4 | "description": "Query plugin for Radonis", 5 | "type": "commonjs", 6 | "main": "./build/cjs/index.js", 7 | "types": "./build/cjs/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": { 11 | "types": "./build/esm/index.d.ts", 12 | "default": "./build/esm/index.js" 13 | }, 14 | "require": { 15 | "types": "./build/cjs/index.d.ts", 16 | "default": "./build/cjs/index.js" 17 | } 18 | } 19 | }, 20 | "sideEffects": false, 21 | "files": [ 22 | "build/cjs", 23 | "build/esm" 24 | ], 25 | "scripts": { 26 | "clean": "del-cli build", 27 | "build:cjs": "tsc --outDir ./build/cjs --module commonjs", 28 | "build:esm": "tsc --outDir ./build/esm --module esnext", 29 | "build": "\"$npm_execpath\" run build:cjs && \"$npm_execpath\" run build:esm", 30 | "lint": "TIMING=1 eslint . --ext=.ts", 31 | "format": "prettier --write .", 32 | "test": "node -r @adonisjs/require-ts/build/register bin/test.ts" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "git+ssh://git@github.com/microeinhundert/radonis.git", 37 | "directory": "packages/radonis-query" 38 | }, 39 | "bugs": { 40 | "url": "https://github.com/microeinhundert/radonis/issues" 41 | }, 42 | "publishConfig": { 43 | "access": "public" 44 | }, 45 | "engines": { 46 | "node": ">= 16.0.0", 47 | "npm": ">= 8.0.0" 48 | }, 49 | "keywords": [ 50 | "adonisjs", 51 | "adonis", 52 | "radonis", 53 | "plugin", 54 | "query" 55 | ], 56 | "author": "Leon Seipp ", 57 | "license": "MIT", 58 | "peerDependencies": { 59 | "@microeinhundert/radonis": "workspace:5.0.4", 60 | "@adonisjs/core": "^5.8.0", 61 | "@tanstack/react-query": "^4.26.1" 62 | }, 63 | "devDependencies": { 64 | "@microeinhundert/radonis": "workspace:5.0.4", 65 | "@tanstack/react-query": "^4.28.0", 66 | "@types/node": "^18.15.11", 67 | "@types/react": "^18.0.31", 68 | "@types/react-dom": "^18.0.11" 69 | }, 70 | "dependencies": { 71 | "@microeinhundert/radonis-shared": "workspace:5.0.4", 72 | "tslib": "^2.5.0" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/radonis-query/src/components/query_dehydrator.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-query 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { useFlushEffect, useRenderer } from '@microeinhundert/radonis' 11 | import { dehydrate, useQueryClient } from '@tanstack/react-query' 12 | 13 | import type { QueryDehydratorProps } from '../types/main' 14 | 15 | /** 16 | * Component for dehydrating queries 17 | */ 18 | export function QueryDehydrator({ children }: QueryDehydratorProps) { 19 | const renderer = useRenderer() 20 | const queryClient = useQueryClient() 21 | 22 | useFlushEffect(() => { 23 | renderer.withGlobals({ dehydratedQueryState: dehydrate(queryClient) }) 24 | }) 25 | 26 | return children 27 | } 28 | 29 | QueryDehydrator.displayName = 'RadonisQueryDehydrator' 30 | -------------------------------------------------------------------------------- /packages/radonis-query/src/components/query_hydrator.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-query 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { useGlobals } from '@microeinhundert/radonis' 11 | import { Hydrate } from '@tanstack/react-query' 12 | import { createElement as h } from 'react' 13 | 14 | import type { QueryHydratorProps } from '../types/main' 15 | 16 | /** 17 | * Component for hydrating queries 18 | */ 19 | export function QueryHydrator({ children }: QueryHydratorProps) { 20 | const globals = useGlobals() as any 21 | 22 | return h(Hydrate, { state: globals.dehydratedQueryState }, children) 23 | } 24 | 25 | QueryHydrator.displayName = 'RadonisQueryHydrator' 26 | -------------------------------------------------------------------------------- /packages/radonis-query/src/contexts/base_url_context.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-query 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { createContext } from 'react' 11 | 12 | export const baseUrlContext = createContext(undefined) 13 | 14 | export const BaseUrlContextProvider = baseUrlContext.Provider 15 | -------------------------------------------------------------------------------- /packages/radonis-query/src/exceptions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-query 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { createError } from '@microeinhundert/radonis-shared' 11 | 12 | export const E_UNKNOWN_BASE_URL = createError( 13 | 'Cannot retrieve request data server-side because of an unknown base URL. Make sure that the "host" header is present on the request. Alternatively, you can set the base URL manually by passing it to the query plugin', 14 | 'E_UNKNOWN_BASE_URL', 15 | 500 16 | ) 17 | -------------------------------------------------------------------------------- /packages/radonis-query/src/hooks/use_query_base_url.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-query 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { useContext } from 'react' 11 | 12 | import { baseUrlContext } from '../contexts/base_url_context' 13 | import { E_UNKNOWN_BASE_URL } from '../exceptions' 14 | 15 | /** 16 | * Hook for retrieving the base URL set for queries 17 | * @see https://radonis.vercel.app/docs/plugins/query#querying-data 18 | */ 19 | export function useQueryBaseUrl() { 20 | const baseUrl = useContext(baseUrlContext) 21 | 22 | if (!baseUrl) { 23 | throw new E_UNKNOWN_BASE_URL() 24 | } 25 | 26 | return baseUrl 27 | } 28 | -------------------------------------------------------------------------------- /packages/radonis-query/src/hooks/use_server_mutation.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-query 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' 11 | import { useUrlBuilder } from '@microeinhundert/radonis' 12 | import { radonisFetch } from '@microeinhundert/radonis-shared' 13 | import { useMutation } from '@tanstack/react-query' 14 | import { useMemo } from 'react' 15 | 16 | import type { ServerMutationOptions } from '../types/main' 17 | import { useQueryBaseUrl } from './use_query_base_url' 18 | 19 | /** 20 | * Hook for mutating server data 21 | * @see https://radonis.vercel.app/docs/plugins/query#mutating-data 22 | */ 23 | export function useServerMutation< 24 | TControllerAction extends (ctx: HttpContextContract) => any, 25 | TError = unknown, 26 | TData = Awaited> 27 | >(routeIdentifier: string, options?: ServerMutationOptions) { 28 | const urlBuilder = useUrlBuilder() 29 | const baseUrl = useQueryBaseUrl() 30 | 31 | const url = useMemo( 32 | () => 33 | urlBuilder.make$(routeIdentifier, { 34 | baseUrl, 35 | params: options?.params, 36 | queryParams: options?.queryParams, 37 | }), 38 | [urlBuilder, routeIdentifier, baseUrl, options] 39 | ) 40 | 41 | const mutationFn = async (data) => { 42 | const response = await radonisFetch(url, { 43 | method: 'post', 44 | body: data, 45 | headers: options?.headers, 46 | }) 47 | 48 | return response.json() 49 | } 50 | 51 | return useMutation({ 52 | mutationFn, 53 | ...options?.mutation, 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /packages/radonis-query/src/hooks/use_server_query.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-query 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' 11 | import { useUrlBuilder } from '@microeinhundert/radonis' 12 | import { radonisFetch } from '@microeinhundert/radonis-shared' 13 | import { useQuery } from '@tanstack/react-query' 14 | import { useMemo } from 'react' 15 | 16 | import type { ServerQueryOptions } from '../types/main' 17 | import { getQueryKeyForURL } from '../utils/get_query_key_for_url' 18 | import { useQueryBaseUrl } from './use_query_base_url' 19 | 20 | /** 21 | * Hook for querying server data 22 | * @see https://radonis.vercel.app/docs/plugins/query#querying-data 23 | */ 24 | export function useServerQuery< 25 | TControllerAction extends (ctx: HttpContextContract) => any, 26 | TError = unknown, 27 | TData = Awaited> 28 | >(routeIdentifier: string, options?: ServerQueryOptions) { 29 | const urlBuilder = useUrlBuilder() 30 | const baseUrl = useQueryBaseUrl() 31 | 32 | const url = useMemo( 33 | () => 34 | urlBuilder.make$(routeIdentifier, { 35 | baseUrl, 36 | params: options?.params, 37 | queryParams: options?.queryParams, 38 | }), 39 | [urlBuilder, routeIdentifier, baseUrl, options] 40 | ) 41 | 42 | const queryFn = async () => { 43 | const response = await radonisFetch(url, { 44 | headers: options?.headers, 45 | }) 46 | 47 | return response.json() 48 | } 49 | 50 | return useQuery({ 51 | queryFn, 52 | ...options?.query, 53 | queryKey: getQueryKeyForURL(url, [routeIdentifier, options?.query?.queryKey]), 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /packages/radonis-query/src/plugin.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-query 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { definePlugin, UrlBuilder } from '@microeinhundert/radonis' 11 | import type { QueryClientConfig } from '@tanstack/react-query' 12 | import { QueryClientProvider } from '@tanstack/react-query' 13 | import { createElement as h } from 'react' 14 | 15 | import { QueryDehydrator } from './components/query_dehydrator' 16 | import { QueryHydrator } from './components/query_hydrator' 17 | import { BaseUrlContextProvider } from './contexts/base_url_context' 18 | import { getQueryClient } from './queryClient' 19 | import { getQueryKeyForURL } from './utils/get_query_key_for_url' 20 | import { getRouteIdentifier } from './utils/get_route_identifier' 21 | 22 | /** 23 | * Plugin for integrating {@link https://tanstack.com/query/v4 TanStack Query} with Radonis 24 | * @see https://radonis.vercel.app/docs/plugins/query 25 | */ 26 | export function queryPlugin(config?: QueryClientConfig & { baseUrl?: URL | string }) { 27 | const { baseUrl, ...queryClientConfig } = config ?? {} 28 | const queryClient = getQueryClient(queryClientConfig) 29 | 30 | return definePlugin({ 31 | name: 'query', 32 | environments: ['client', 'server'], 33 | beforeHydrate() { 34 | return (tree) => 35 | h( 36 | BaseUrlContextProvider, 37 | { value: baseUrl?.toString() || window.location.origin }, 38 | h(QueryClientProvider, { client: queryClient }, h(QueryHydrator, { children: tree })) 39 | ) 40 | }, 41 | beforeRequest() { 42 | queryClient.clear() 43 | }, 44 | beforeRender({ ctx, manifest, props }) { 45 | const host = ctx.request.header('host') 46 | const origin = host ? `https://${host}` : undefined 47 | const routeIdentifier = getRouteIdentifier(ctx.route) 48 | 49 | if (routeIdentifier && props) { 50 | const urlBuilder = new UrlBuilder(manifest.routes) 51 | const url = urlBuilder.make$(routeIdentifier, { params: ctx.request.params(), queryParams: ctx.request.qs() }) 52 | const queryKey = getQueryKeyForURL(url, [routeIdentifier]) 53 | 54 | queryClient.setQueryData(queryKey, props) 55 | } 56 | 57 | return (tree) => 58 | h( 59 | BaseUrlContextProvider, 60 | { value: baseUrl?.toString() || origin }, 61 | h(QueryClientProvider, { client: queryClient }, h(QueryDehydrator, { children: tree })) 62 | ) 63 | }, 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /packages/radonis-query/src/queryClient.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-query 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import type { QueryClientConfig } from '@tanstack/react-query' 11 | import { QueryClient } from '@tanstack/react-query' 12 | 13 | /** 14 | * The current QueryClient 15 | */ 16 | let queryClient: QueryClient 17 | 18 | /** 19 | * Get the QueryClient 20 | */ 21 | export function getQueryClient(config?: QueryClientConfig) { 22 | if (config) { 23 | queryClient = new QueryClient(config) 24 | } 25 | 26 | return (queryClient = queryClient ?? new QueryClient()) 27 | } 28 | -------------------------------------------------------------------------------- /packages/radonis-query/src/types/main.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-query 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import type { RouteParams, RouteQueryParams } from '@microeinhundert/radonis' 11 | import type { UseMutationOptions, UseQueryOptions } from '@tanstack/react-query' 12 | import type { ReactElement } from 'react' 13 | 14 | /** 15 | * Server query options 16 | */ 17 | export interface ServerQueryOptions { 18 | params?: RouteParams 19 | queryParams?: RouteQueryParams 20 | headers?: HeadersInit 21 | query?: UseQueryOptions 22 | } 23 | 24 | /** 25 | * Server mutation options 26 | */ 27 | export interface ServerMutationOptions { 28 | params?: RouteParams 29 | queryParams?: RouteQueryParams 30 | headers?: HeadersInit 31 | mutation?: UseMutationOptions 32 | } 33 | 34 | /** 35 | * Query dehydrator props 36 | */ 37 | export interface QueryDehydratorProps { 38 | children: ReactElement 39 | } 40 | 41 | /** 42 | * Query hydrator props 43 | */ 44 | export interface QueryHydratorProps { 45 | children: ReactElement 46 | } 47 | -------------------------------------------------------------------------------- /packages/radonis-query/src/utils/get_query_key_for_url.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-query 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { createInternalURL, stripOriginFromURL } from '@microeinhundert/radonis-shared' 11 | import type { QueryKey } from '@tanstack/react-query' 12 | 13 | /** 14 | * Get the query key for an URL 15 | */ 16 | export function getQueryKeyForURL(url: URL | string, prepend?: unknown[]): QueryKey { 17 | const urlQueryKey = stripOriginFromURL(createInternalURL(url)).split('/') 18 | const queryKey = [prepend, urlQueryKey].flat() 19 | 20 | return queryKey 21 | } 22 | -------------------------------------------------------------------------------- /packages/radonis-query/src/utils/get_route_identifier.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-query 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import type { RouteNode } from '@ioc:Adonis/Core/Route' 11 | 12 | /** 13 | * Get the route identifier 14 | */ 15 | export function getRouteIdentifier(routeNode: RouteNode | undefined): string | undefined { 16 | return routeNode?.name ?? (typeof routeNode?.handler === 'string' ? routeNode.handler : undefined) 17 | } 18 | -------------------------------------------------------------------------------- /packages/radonis-query/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "rootDir": "./", 6 | "composite": true, 7 | "lib": ["dom", "dom.iterable", "esnext"], 8 | "module": "commonjs", 9 | "outDir": "./build", 10 | "types": ["@adonisjs/core"] 11 | }, 12 | "include": ["./**/*"], 13 | "exclude": ["./node_modules", "./build"] 14 | } 15 | -------------------------------------------------------------------------------- /packages/radonis-server/.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /packages/radonis-server/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "root": true 4 | } 5 | -------------------------------------------------------------------------------- /packages/radonis-server/.npmignore: -------------------------------------------------------------------------------- 1 | **/tsconfig.tsbuildinfo 2 | **/test 3 | **/bin -------------------------------------------------------------------------------- /packages/radonis-server/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /packages/radonis-server/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Leon Seipp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/radonis-server/README.md: -------------------------------------------------------------------------------- 1 | # Radonis Server 2 | 3 | ![npm (scoped)](https://img.shields.io/npm/v/@microeinhundert/radonis-server) 4 | 5 | ## About 6 | 7 | Radonis is a full-stack application framework for building monolithic applications with a modern, React-based frontend stack. Radonis is built on top of the Node.js MVC framework [AdonisJS](https://adonisjs.com/) and extends it with features for server-side rendering and client-side hydration. 8 | 9 | ## Documentation 10 | 11 | Documentation is available on [radonis.vercel.app](https://radonis.vercel.app/). 12 | 13 | ## License 14 | 15 | [MIT](LICENSE) 16 | -------------------------------------------------------------------------------- /packages/radonis-server/adonis-typings/container.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-server 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | declare module '@ioc:Adonis/Core/Application' { 11 | import type { 12 | AssetsManagerContract, 13 | HeadManagerContract, 14 | HydrationManagerContract, 15 | ManifestManagerContract, 16 | PluginsManagerContract, 17 | RendererContract, 18 | } from '@microeinhundert/radonis-types' 19 | 20 | interface ContainerBindings { 21 | 'Microeinhundert/Radonis/AssetsManager': AssetsManagerContract 22 | 'Microeinhundert/Radonis/HeadManager': HeadManagerContract 23 | 'Microeinhundert/Radonis/HydrationManager': HydrationManagerContract 24 | 'Microeinhundert/Radonis/ManifestManager': ManifestManagerContract 25 | 'Microeinhundert/Radonis/PluginsManager': PluginsManagerContract 26 | 'Microeinhundert/Radonis/Renderer': RendererContract 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/radonis-server/adonis-typings/http-context.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-server 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | declare module '@ioc:Adonis/Core/HttpContext' { 11 | import type { RendererContract } from '@microeinhundert/radonis-types' 12 | 13 | interface HttpContextContract { 14 | radonis: RendererContract 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/radonis-server/adonis-typings/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-server 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | /// 11 | /// 12 | /// 13 | -------------------------------------------------------------------------------- /packages/radonis-server/adonis-typings/radonis.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-server 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | declare module '@ioc:Microeinhundert/Radonis' { 11 | import type { HeadMeta, Plugin } from '@microeinhundert/radonis-types' 12 | import type { BuildOptions } from 'esbuild' 13 | 14 | interface RadonisConfig { 15 | plugins: Plugin[] 16 | head: { 17 | title: { 18 | default: string 19 | prefix: string 20 | suffix: string 21 | separator: string 22 | } 23 | defaultMeta: HeadMeta 24 | } 25 | server: { 26 | streaming: boolean 27 | } 28 | client: { 29 | limitManifest: boolean 30 | buildOptions: Pick 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/radonis-server/bin/japaTypes.ts: -------------------------------------------------------------------------------- 1 | import type { Assert } from '@japa/assert' 2 | 3 | declare module '@japa/runner' { 4 | interface TestContext { 5 | assert: Assert 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/radonis-server/bin/test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from '@japa/assert' 2 | import { runFailedTests } from '@japa/run-failed-tests' 3 | import { configure, processCliArgs, run } from '@japa/runner' 4 | import { specReporter } from '@japa/spec-reporter' 5 | 6 | process.env.NODE_ENV = 'test' 7 | 8 | /* 9 | |-------------------------------------------------------------------------- 10 | | Configure tests 11 | |-------------------------------------------------------------------------- 12 | | 13 | | The configure method accepts the configuration to configure the Japa 14 | | tests runner. 15 | | 16 | | The first method call "processCliArgs" process the command line arguments 17 | | and turns them into a config object. Using this method is not mandatory. 18 | | 19 | | Please consult japa.dev/runner-config for the config docs. 20 | */ 21 | configure({ 22 | ...processCliArgs(process.argv.slice(2)), 23 | ...{ 24 | files: ['test/**/*.spec.ts'], 25 | plugins: [assert(), runFailedTests()], 26 | reporters: [specReporter()], 27 | importer: (filePath: string) => import(filePath), 28 | }, 29 | }) 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Run tests 34 | |-------------------------------------------------------------------------- 35 | | 36 | | The following "run" method is required to execute all the tests. 37 | | 38 | */ 39 | run() 40 | -------------------------------------------------------------------------------- /packages/radonis-server/commands/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-server 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | export default ['@microeinhundert/radonis-server/build/commands/BuildClient'] 11 | -------------------------------------------------------------------------------- /packages/radonis-server/instructions.md: -------------------------------------------------------------------------------- 1 | Radonis requires the official AdonisJS addons `@adonisjs/i18n` and `@adonisjs/session` to be installed and configured. Execute the following two commands and follow the instructions output to the terminal. 2 | 3 | ```shell 4 | node ace configure @adonisjs/i18n 5 | node ace configure @adonisjs/session 6 | ``` 7 | 8 | Documentation for Radonis is available on [radonis.vercel.app](https://radonis.vercel.app/). 9 | -------------------------------------------------------------------------------- /packages/radonis-server/lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.ts': ['npm run lint -- --fix'], 3 | '*.{json,md}': ['npm run format'], 4 | } 5 | -------------------------------------------------------------------------------- /packages/radonis-server/src/assets_manager/main.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-server 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import type { ApplicationContract } from '@ioc:Adonis/Core/Application' 11 | import { readAssetsManifestFromDisk } from '@microeinhundert/radonis-build/utils' 12 | import type { AssetsManagerContract, AssetsManifest, Resettable } from '@microeinhundert/radonis-types' 13 | import { AssetType } from '@microeinhundert/radonis-types' 14 | 15 | /** 16 | * Service for managing assets 17 | */ 18 | export class AssetsManager implements AssetsManagerContract, Resettable { 19 | /** 20 | * The singleton instance 21 | */ 22 | static instance?: AssetsManager 23 | 24 | /** 25 | * Get the singleton instance 26 | */ 27 | static getSingletonInstance(...args: ConstructorParameters): AssetsManager { 28 | return (AssetsManager.instance = AssetsManager.instance ?? new AssetsManager(...args)) 29 | } 30 | 31 | /** 32 | * The public path 33 | */ 34 | #publicPath: string 35 | 36 | /** 37 | * The assets manifest 38 | */ 39 | #assetsManifest: AssetsManifest 40 | 41 | /** 42 | * The required islands 43 | */ 44 | #requiredIslands: Set 45 | 46 | constructor(application: ApplicationContract) { 47 | this.#publicPath = application.publicPath('radonis') 48 | 49 | this.#assetsManifest = [] 50 | 51 | this.#setDefaults() 52 | } 53 | 54 | /** 55 | * The required assets 56 | */ 57 | get requiredAssets(): AssetsManifest { 58 | return this.#assetsManifest.reduce((assets, asset) => { 59 | if (asset.type === AssetType.ClientScript) { 60 | return [...assets, asset] 61 | } 62 | 63 | if (asset.islands.some((identifier) => this.#requiredIslands.has(identifier))) { 64 | return [asset, ...assets] 65 | } 66 | 67 | return assets 68 | }, []) 69 | } 70 | 71 | /** 72 | * Require an island 73 | */ 74 | requireIsland(identifier: string): void { 75 | this.#requiredIslands.add(identifier) 76 | } 77 | 78 | /** 79 | * Read the assets manifest 80 | */ 81 | async readAssetsManifest(): Promise { 82 | try { 83 | const assetsManifest = await readAssetsManifestFromDisk(this.#publicPath) 84 | if (assetsManifest) this.#assetsManifest = assetsManifest 85 | } catch { 86 | this.#assetsManifest = [] 87 | } 88 | } 89 | 90 | /** 91 | * Reset for a new request 92 | */ 93 | reset(): void { 94 | this.#setDefaults() 95 | } 96 | 97 | /** 98 | * Set the defaults 99 | */ 100 | #setDefaults(): void { 101 | this.#requiredIslands = new Set() 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /packages/radonis-server/src/components/default_error_page.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-server 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * Adapted from https://github.com/denoland/fresh 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | import { isProduction } from '@microeinhundert/radonis-shared' 13 | import { createElement as h } from 'react' 14 | 15 | /** 16 | * The default error page 17 | */ 18 | export function DefaultErrorPage({ error }: { error: unknown }) { 19 | let message 20 | 21 | if (!isProduction) { 22 | if (error instanceof Error) { 23 | message = error.stack 24 | } else { 25 | message = String(error) 26 | } 27 | } 28 | 29 | return h( 30 | 'div', 31 | { 32 | style: { 33 | display: 'flex', 34 | justifyContent: 'center', 35 | alignItems: 'center', 36 | }, 37 | }, 38 | h( 39 | 'div', 40 | { 41 | style: { 42 | border: '#f3f4f6 2px solid', 43 | borderTop: 'red 4px solid', 44 | background: '#f9fafb', 45 | margin: 16, 46 | minWidth: '300px', 47 | width: '50%', 48 | }, 49 | }, 50 | h( 51 | 'p', 52 | { 53 | style: { 54 | margin: 0, 55 | fontSize: '12pt', 56 | padding: 16, 57 | fontFamily: 'sans-serif', 58 | }, 59 | }, 60 | 'An error occurred during rendering.' 61 | ), 62 | message && 63 | h( 64 | 'pre', 65 | { 66 | style: { 67 | margin: 0, 68 | fontSize: '12pt', 69 | overflowY: 'auto', 70 | padding: 16, 71 | paddingTop: 0, 72 | fontFamily: 'monospace', 73 | }, 74 | }, 75 | message 76 | ) 77 | ) 78 | ) 79 | } 80 | 81 | DefaultErrorPage.displayName = 'RadonisDefaultErrorPage' 82 | -------------------------------------------------------------------------------- /packages/radonis-server/src/contexts/assets_manager_context.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-server 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import type { AssetsManagerContract } from '@microeinhundert/radonis-types' 11 | import { createContext } from 'react' 12 | 13 | export const assetsManagerContext = createContext(null as any) 14 | 15 | export const AssetsManagerContextProvider = assetsManagerContext.Provider 16 | -------------------------------------------------------------------------------- /packages/radonis-server/src/contexts/manifest_manager_context.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-server 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import type { ManifestManagerContract } from '@microeinhundert/radonis-types' 11 | import { createContext } from 'react' 12 | 13 | export const manifestManagerContext = createContext(null as any) 14 | 15 | export const ManifestManagerContextProvider = manifestManagerContext.Provider 16 | -------------------------------------------------------------------------------- /packages/radonis-server/src/contexts/renderer_context.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-server 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import type { RendererContract } from '@microeinhundert/radonis-types' 11 | import { createContext } from 'react' 12 | 13 | export const rendererContext = createContext(null as any) 14 | 15 | export const RendererContextProvider = rendererContext.Provider 16 | -------------------------------------------------------------------------------- /packages/radonis-server/src/contexts/server_context.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-server 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import type { ServerContract } from '@microeinhundert/radonis-types' 11 | import { createContext } from 'react' 12 | 13 | export const serverContext = createContext(null as any) 14 | 15 | export const ServerContextProvider = serverContext.Provider 16 | -------------------------------------------------------------------------------- /packages/radonis-server/src/exceptions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-server 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { createError } from '@microeinhundert/radonis-shared' 11 | 12 | export const E_CANNOT_SERIALIZE_MANIFEST = createError( 13 | 'The Radonis manifest cannot be serialized. Make sure you only pass data to islands that can be serialized by superjson', 14 | 'E_CANNOT_SERIALIZE_MANIFEST', 15 | 500 16 | ) 17 | 18 | export const E_CONFLICTING_PLUGINS = createError<[plugin: string, conflictingPlugins: string]>( 19 | 'The plugin "%s" conflicts with the following installed plugins: %s', 20 | 'E_CONFLICTING_PLUGINS', 21 | 500 22 | ) 23 | 24 | export const E_PLUGIN_ALREADY_INSTALLED = createError<[plugin: string]>( 25 | 'The plugin "%s" is already installed', 26 | 'E_PLUGIN_ALREADY_INSTALLED', 27 | 500 28 | ) 29 | 30 | export const E_PLUGIN_NOT_INSTALLABLE = createError<[plugin: string, environment: string]>( 31 | 'The plugin "%s" is not installable in the "%s" environment', 32 | 'E_PLUGIN_NOT_INSTALLABLE', 33 | 500 34 | ) 35 | -------------------------------------------------------------------------------- /packages/radonis-server/src/manifest_manager/constants.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-server 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | export const DEFAULT_LOCALE = 'en' 11 | -------------------------------------------------------------------------------- /packages/radonis-server/src/renderer/constants.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-server 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | export const ABORT_DELAY = 5000 11 | -------------------------------------------------------------------------------- /packages/radonis-server/src/utils/build_title.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-server 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { separateArray } from '@microeinhundert/radonis-shared' 11 | 12 | /** 13 | * Build the value for the head title tag 14 | */ 15 | export function buildTitle(title: string, prefix: string, suffix: string, separator: string): string { 16 | return separateArray( 17 | [prefix, title, suffix].filter(Boolean).map((part) => part.trim()), 18 | separator 19 | ).join(' ') 20 | } 21 | -------------------------------------------------------------------------------- /packages/radonis-server/src/utils/extract_root_routes.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-server 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import type { RouterContract } from '@ioc:Adonis/Core/Route' 11 | 12 | import { getRouteIdentifier } from './get_route_identifier' 13 | 14 | /** 15 | * Extract the root routes 16 | */ 17 | export function extractRootRoutes(router: RouterContract): Record { 18 | const rootRoutes = router.toJSON()?.['root'] ?? [] 19 | 20 | return rootRoutes.reduce>((routes, route) => { 21 | const routeIdentifier = getRouteIdentifier(route) 22 | 23 | if (routeIdentifier) { 24 | routes[routeIdentifier] = route.pattern 25 | } 26 | 27 | return routes 28 | }, {}) 29 | } 30 | -------------------------------------------------------------------------------- /packages/radonis-server/src/utils/generate_html_stream.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-server 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * Adapted from https://github.com/fastify/fastify-dx 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | import type { ContiguousData } from 'minipass' 13 | import type Minipass from 'minipass' 14 | 15 | export async function* generateHtmlStream({ 16 | head, 17 | body, 18 | footer, 19 | }: { 20 | head: () => string 21 | body: () => Minipass 22 | footer: () => Promise 23 | }) { 24 | yield head() 25 | 26 | if (body) { 27 | for await (const chunk of await body()) { 28 | yield chunk 29 | } 30 | } 31 | 32 | yield await footer() 33 | } 34 | -------------------------------------------------------------------------------- /packages/radonis-server/src/utils/get_route_identifier.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-server 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import type { RouteNode } from '@ioc:Adonis/Core/Route' 11 | 12 | /** 13 | * Get the route identifier 14 | */ 15 | export function getRouteIdentifier(routeNode: RouteNode | undefined): string | undefined { 16 | return routeNode?.name ?? (typeof routeNode?.handler === 'string' ? routeNode.handler : undefined) 17 | } 18 | -------------------------------------------------------------------------------- /packages/radonis-server/src/utils/transform_route_node.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-server 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import type { RouteNode } from '@ioc:Adonis/Core/Route' 11 | import type { Route } from '@microeinhundert/radonis-types' 12 | 13 | import { getRouteIdentifier } from './get_route_identifier' 14 | 15 | /** 16 | * Transform a RouteNode to match the shape expected by the manifest 17 | */ 18 | export function transformRouteNode(routeNode: RouteNode): Route { 19 | return { 20 | identifier: getRouteIdentifier(routeNode), 21 | pattern: routeNode.pattern, 22 | params: {}, 23 | searchParams: {}, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/radonis-server/src/utils/with_context_providers.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-server 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import type { 11 | AssetsManagerContract, 12 | ManifestManagerContract, 13 | RendererContract, 14 | ServerContract, 15 | } from '@microeinhundert/radonis-types' 16 | import type { ReactElement } from 'react' 17 | import { createElement as h } from 'react' 18 | 19 | import { AssetsManagerContextProvider } from '../contexts/assets_manager_context' 20 | import { ManifestManagerContextProvider } from '../contexts/manifest_manager_context' 21 | import { RendererContextProvider } from '../contexts/renderer_context' 22 | import { ServerContextProvider } from '../contexts/server_context' 23 | 24 | /** 25 | * Wrap a ReactElement with the required context providers 26 | */ 27 | export function withContextProviders( 28 | renderer: RendererContract, 29 | assetsManager: AssetsManagerContract, 30 | manifestManager: ManifestManagerContract, 31 | server: ServerContract, 32 | children: ReactElement 33 | ): ReactElement { 34 | return h( 35 | AssetsManagerContextProvider, 36 | { value: assetsManager }, 37 | h( 38 | ManifestManagerContextProvider, 39 | { value: manifestManager }, 40 | h(RendererContextProvider, { value: renderer }, h(ServerContextProvider, { value: server }, children)) 41 | ) 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /packages/radonis-server/standalone.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-server 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | /* 11 | * Contexts 12 | */ 13 | export { assetsManagerContext } from './src/contexts/assets_manager_context' 14 | export { manifestManagerContext } from './src/contexts/manifest_manager_context' 15 | export { rendererContext } from './src/contexts/renderer_context' 16 | export { serverContext } from './src/contexts/server_context' 17 | 18 | /* 19 | * Services 20 | */ 21 | export { HydrationManager } from './src/hydration_manager/main' 22 | export { PluginsManager } from './src/plugins_manager/main' 23 | -------------------------------------------------------------------------------- /packages/radonis-server/templates/contract.txt: -------------------------------------------------------------------------------- 1 | declare module '@microeinhundert/radonis-types' { 2 | /* 3 | |-------------------------------------------------------------------------- 4 | | Define typed globals 5 | |-------------------------------------------------------------------------- 6 | | 7 | | You can define types for globals inside the following interface and 8 | | Radonis will make sure that all usages of globals adhere 9 | | to the defined types. 10 | | 11 | | For example: 12 | | 13 | | interface Globals { 14 | | hello: string 15 | | } 16 | | 17 | | Now calling `radonis.withGlobals({ hello: 'Hello world!' })` will statically ensure correct types. 18 | | 19 | */ 20 | interface Globals {} 21 | } 22 | -------------------------------------------------------------------------------- /packages/radonis-server/templates/entry.client.txt: -------------------------------------------------------------------------------- 1 | import { initClient } from '@microeinhundert/radonis' 2 | 3 | initClient({ plugins: [] }) 4 | -------------------------------------------------------------------------------- /packages/radonis-server/test/plugins_manager.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-server 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { test } from '@japa/runner' 11 | import type { Plugin } from '@microeinhundert/radonis-types' 12 | 13 | import { PluginsManager } from '../src/plugins_manager/main' 14 | 15 | /** 16 | * Plugins Manager 17 | */ 18 | test.group('Plugins Manager', (group) => { 19 | let pluginsManager: PluginsManager 20 | 21 | group.each.setup(() => { 22 | pluginsManager = new PluginsManager() 23 | }) 24 | 25 | test('installs plugin', ({ assert }) => { 26 | const testPlugin: Plugin = { 27 | name: 'test-plugin', 28 | environments: ['client'], 29 | } 30 | 31 | assert.doesNotThrows(() => pluginsManager.install('client', testPlugin)) 32 | }) 33 | 34 | test('throws if plugin is not supported in current environment', ({ assert }) => { 35 | const testPlugin: Plugin = { 36 | name: 'test-plugin', 37 | environments: ['client'], 38 | } 39 | 40 | assert.throws( 41 | () => pluginsManager.install('server', testPlugin), 42 | 'The plugin "test-plugin" is not installable in the "server" environment' 43 | ) 44 | }) 45 | 46 | test('throws if two plugins conflict with each other', ({ assert }) => { 47 | const testPluginOne: Plugin = { 48 | name: 'test-plugin-1', 49 | conflictsWith: ['test-plugin-2'], 50 | environments: ['client'], 51 | } 52 | 53 | const testPluginTwo: Plugin = { 54 | name: 'test-plugin-2', 55 | conflictsWith: ['test-plugin-1'], 56 | environments: ['client'], 57 | } 58 | 59 | assert.throws( 60 | () => pluginsManager.install('client', testPluginOne, testPluginTwo), 61 | 'The plugin "test-plugin-1" conflicts with the following installed plugins: test-plugin-2' 62 | ) 63 | }) 64 | 65 | test('throws if plugin is already installed', ({ assert }) => { 66 | const testPlugin: Plugin = { 67 | name: 'test-plugin', 68 | environments: ['server'], 69 | } 70 | 71 | assert.throws( 72 | () => pluginsManager.install('server', testPlugin, testPlugin), 73 | 'The plugin "test-plugin" is already installed' 74 | ) 75 | }) 76 | }) 77 | -------------------------------------------------------------------------------- /packages/radonis-server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "rootDir": "./", 6 | "composite": true, 7 | "lib": ["esnext"], 8 | "module": "commonjs", 9 | "outDir": "./build", 10 | "types": ["@adonisjs/core", "@adonisjs/session", "@adonisjs/i18n"] 11 | }, 12 | "include": ["./**/*"], 13 | "exclude": ["./node_modules", "./build"] 14 | } 15 | -------------------------------------------------------------------------------- /packages/radonis-shared/.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /packages/radonis-shared/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "root": true 4 | } 5 | -------------------------------------------------------------------------------- /packages/radonis-shared/.npmignore: -------------------------------------------------------------------------------- 1 | **/tsconfig.tsbuildinfo 2 | **/test 3 | **/bin -------------------------------------------------------------------------------- /packages/radonis-shared/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /packages/radonis-shared/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Leon Seipp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/radonis-shared/README.md: -------------------------------------------------------------------------------- 1 | # Radonis Shared 2 | 3 | ![npm (scoped)](https://img.shields.io/npm/v/@microeinhundert/radonis-shared) 4 | 5 | > **Note**: This is a package used by Radonis internally and should not be installed directly. 6 | 7 | ## About 8 | 9 | Radonis is a full-stack application framework for building monolithic applications with a modern, React-based frontend stack. Radonis is built on top of the Node.js MVC framework [AdonisJS](https://adonisjs.com/) and extends it with features for server-side rendering and client-side hydration. 10 | 11 | ## Documentation 12 | 13 | Documentation is available on [radonis.vercel.app](https://radonis.vercel.app/). 14 | 15 | ## License 16 | 17 | [MIT](LICENSE) 18 | -------------------------------------------------------------------------------- /packages/radonis-shared/bin/japaTypes.ts: -------------------------------------------------------------------------------- 1 | import type { Assert } from '@japa/assert' 2 | 3 | declare module '@japa/runner' { 4 | interface TestContext { 5 | assert: Assert 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/radonis-shared/bin/test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from '@japa/assert' 2 | import { runFailedTests } from '@japa/run-failed-tests' 3 | import { configure, processCliArgs, run } from '@japa/runner' 4 | import { specReporter } from '@japa/spec-reporter' 5 | 6 | process.env.NODE_ENV = 'test' 7 | 8 | /* 9 | |-------------------------------------------------------------------------- 10 | | Configure tests 11 | |-------------------------------------------------------------------------- 12 | | 13 | | The configure method accepts the configuration to configure the Japa 14 | | tests runner. 15 | | 16 | | The first method call "processCliArgs" process the command line arguments 17 | | and turns them into a config object. Using this method is not mandatory. 18 | | 19 | | Please consult japa.dev/runner-config for the config docs. 20 | */ 21 | configure({ 22 | ...processCliArgs(process.argv.slice(2)), 23 | ...{ 24 | files: ['test/**/*.spec.ts'], 25 | plugins: [assert(), runFailedTests()], 26 | reporters: [specReporter()], 27 | importer: (filePath: string) => import(filePath), 28 | }, 29 | }) 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Run tests 34 | |-------------------------------------------------------------------------- 35 | | 36 | | The following "run" method is required to execute all the tests. 37 | | 38 | */ 39 | run() 40 | -------------------------------------------------------------------------------- /packages/radonis-shared/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-shared 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | export { createError, RadonisException } from './src/exception/main' 11 | export { UrlBuilder } from './src/url_builder/main' 12 | export { createInternalURL } from './src/utils/create_internal_url' 13 | export { isClient, isProduction, isServer } from './src/utils/environment' 14 | export { nonNull } from './src/utils/non_null' 15 | export { normalizePath } from './src/utils/normalize_path' 16 | export { radonisFetch } from './src/utils/radonis_fetch' 17 | export { separateArray } from './src/utils/separate_array' 18 | export { stringifyAttributes } from './src/utils/stringify_attributes' 19 | export { stripLeadingSlash } from './src/utils/strip_leading_slash' 20 | export { stripOriginFromURL } from './src/utils/strip_origin_from_url' 21 | -------------------------------------------------------------------------------- /packages/radonis-shared/lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.ts': ['npm run lint -- --fix'], 3 | '*.{json,md}': ['npm run format'], 4 | } 5 | -------------------------------------------------------------------------------- /packages/radonis-shared/node.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-shared 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | export { ensureDirExists } from './src/utils/node/ensure_dir_exists' 11 | export { fsReadAll } from './src/utils/node/fs_read_all' 12 | -------------------------------------------------------------------------------- /packages/radonis-shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@microeinhundert/radonis-shared", 3 | "version": "5.0.4", 4 | "description": "Shared package for Radonis", 5 | "type": "commonjs", 6 | "main": "./build/cjs/index.js", 7 | "types": "./build/cjs/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": { 11 | "types": "./build/esm/index.d.ts", 12 | "default": "./build/esm/index.js" 13 | }, 14 | "require": { 15 | "types": "./build/cjs/index.d.ts", 16 | "default": "./build/cjs/index.js" 17 | } 18 | }, 19 | "./node": { 20 | "import": { 21 | "types": "./build/esm/node.d.ts", 22 | "default": "./build/esm/node.js" 23 | }, 24 | "require": { 25 | "types": "./build/cjs/node.d.ts", 26 | "default": "./build/cjs/node.js" 27 | } 28 | } 29 | }, 30 | "sideEffects": false, 31 | "files": [ 32 | "build/cjs", 33 | "build/esm" 34 | ], 35 | "scripts": { 36 | "clean": "del-cli build", 37 | "build:cjs": "tsc --outDir ./build/cjs --module commonjs", 38 | "build:esm": "tsc --outDir ./build/esm --module esnext", 39 | "build": "\"$npm_execpath\" run build:cjs && \"$npm_execpath\" run build:esm", 40 | "lint": "TIMING=1 eslint . --ext=.ts", 41 | "format": "prettier --write .", 42 | "test": "node -r @adonisjs/require-ts/build/register bin/test.ts" 43 | }, 44 | "repository": { 45 | "type": "git", 46 | "url": "git+ssh://git@github.com/microeinhundert/radonis.git", 47 | "directory": "packages/radonis-shared" 48 | }, 49 | "bugs": { 50 | "url": "https://github.com/microeinhundert/radonis/issues" 51 | }, 52 | "publishConfig": { 53 | "access": "public" 54 | }, 55 | "engines": { 56 | "node": ">= 16.0.0", 57 | "npm": ">= 8.0.0" 58 | }, 59 | "keywords": [ 60 | "adonisjs", 61 | "adonis", 62 | "radonis", 63 | "shared" 64 | ], 65 | "author": "Leon Seipp ", 66 | "license": "MIT", 67 | "devDependencies": { 68 | "@microeinhundert/radonis-types": "workspace:5.0.4", 69 | "@types/node": "^18.15.11", 70 | "@types/react": "^18.0.31", 71 | "@types/react-dom": "^18.0.11" 72 | }, 73 | "dependencies": { 74 | "superjson": "^1.12.2", 75 | "tslib": "^2.5.0" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /packages/radonis-shared/src/exception/main.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-shared 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * Adapted from https://github.com/poppinss/utils 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | /** 13 | * Extended Error object with the option to set error `status` and `code` 14 | */ 15 | export class RadonisException extends Error { 16 | declare static help?: string 17 | declare static code?: string 18 | declare static status?: number 19 | declare static message?: string 20 | 21 | /** 22 | * Name of the class that raised the exception 23 | */ 24 | name: string 25 | 26 | /** 27 | * Optional help description for the error 28 | */ 29 | declare help?: string 30 | 31 | /** 32 | * A machine readable error code 33 | */ 34 | declare code?: string 35 | 36 | /** 37 | * A status code for the error 38 | */ 39 | status: number 40 | 41 | constructor(message?: string, options?: ErrorOptions & { code?: string; status?: number }) { 42 | super(message, options) 43 | 44 | const ErrorConstructor = this.constructor as typeof RadonisException 45 | 46 | this.name = ErrorConstructor.name 47 | this.message = message || ErrorConstructor.message || '' 48 | this.status = options?.status || ErrorConstructor.status || 500 49 | 50 | const code = options?.code || ErrorConstructor.code 51 | if (code !== undefined) { 52 | this.code = code 53 | } 54 | 55 | const help = ErrorConstructor.help 56 | if (help !== undefined) { 57 | this.help = help 58 | } 59 | 60 | Error.captureStackTrace(this, ErrorConstructor) 61 | } 62 | 63 | get [Symbol.toStringTag]() { 64 | return this.constructor.name 65 | } 66 | 67 | toString() { 68 | if (this.code) { 69 | return `${this.name} [${this.code}]: ${this.message}` 70 | } 71 | return `${this.name}: ${this.message}` 72 | } 73 | } 74 | 75 | /** 76 | * Helper to format strings 77 | */ 78 | function format(string: string, ...args: T[]) { 79 | return args.reduce((p, c) => p.replace(/%s/, c), string) 80 | } 81 | 82 | /** 83 | * Helper to create anonymous error classes 84 | */ 85 | export function createError( 86 | message: string, 87 | code: string, 88 | status?: number 89 | ): typeof RadonisException & T extends never 90 | ? { new (args?: any, options?: ErrorOptions): RadonisException } 91 | : { new (args: T, options?: ErrorOptions): RadonisException } { 92 | return class extends RadonisException { 93 | static message = message 94 | static code = code 95 | static status = status 96 | 97 | constructor(args: T, options?: ErrorOptions) { 98 | super(format(message, ...(args || [])), options) 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /packages/radonis-shared/src/exceptions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-shared 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { createError } from './exception/main' 11 | 12 | export const E_CANNOT_FIND_ROUTE = createError<[identifier: string]>( 13 | 'Cannot find a route named "%s"', 14 | 'E_CANNOT_FIND_ROUTE', 15 | 404 16 | ) 17 | 18 | export const E_MISSING_FETCH = createError( 19 | 'There is no server-side implementation of the "fetch" API available. Please include a polyfill', 20 | 'E_MISSING_FETCH', 21 | 500 22 | ) 23 | 24 | export const E_MISSING_ROUTE_PARAM = createError<[param: string, pattern: string]>( 25 | 'The param "%s" is required for building the URL to the route "%s"', 26 | 'E_MISSING_ROUTE_PARAM', 27 | 500 28 | ) 29 | 30 | export const E_WILDCARD_ROUTES_NOT_SUPPORTED = createError( 31 | 'Wildcard routes are not currently supported by the URL builder', 32 | 'E_WILDCARD_ROUTES_NOT_SUPPORTED', 33 | 500 34 | ) 35 | -------------------------------------------------------------------------------- /packages/radonis-shared/src/types/main.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-shared 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import type { RouteParams, RouteQueryParams } from '@microeinhundert/radonis-types' 11 | 12 | /** 13 | * URL builder options 14 | */ 15 | export interface UrlBuilderOptions { 16 | onFoundRoute: (routeIdentifier: string) => void 17 | } 18 | 19 | /** 20 | * URL builder make options 21 | */ 22 | export interface UrlBuilderMakeOptions { 23 | baseUrl?: string 24 | params?: RouteParams 25 | queryParams?: RouteQueryParams 26 | } 27 | -------------------------------------------------------------------------------- /packages/radonis-shared/src/utils/create_internal_url.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-shared 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | /** 11 | * Create an internal URL 12 | */ 13 | export function createInternalURL(location: URL | string) { 14 | return new URL(location, 'http://radonis') 15 | } 16 | -------------------------------------------------------------------------------- /packages/radonis-shared/src/utils/environment.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-shared 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | /** 11 | * Whether the current environment is a server or not 12 | */ 13 | export const isServer = typeof window === 'undefined' 14 | 15 | /** 16 | * Whether the current environment is a client or not 17 | */ 18 | export const isClient = !isServer 19 | 20 | /** 21 | * Whether the current environment is production or not 22 | */ 23 | export const isProduction = process.env.NODE_ENV === 'production' 24 | -------------------------------------------------------------------------------- /packages/radonis-shared/src/utils/get_fetch_or_fail.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-shared 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { E_MISSING_FETCH } from '../exceptions' 11 | 12 | /** 13 | * Get the available `fetch` implementation or fail 14 | */ 15 | export function getFetchOrFail() { 16 | if (!globalThis.fetch) { 17 | throw new E_MISSING_FETCH() 18 | } 19 | 20 | return globalThis.fetch 21 | } 22 | -------------------------------------------------------------------------------- /packages/radonis-shared/src/utils/node/ensure_dir_exists.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-shared 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { existsSync } from 'node:fs' 11 | import { mkdir } from 'node:fs/promises' 12 | import { dirname } from 'node:path' 13 | 14 | /** 15 | * Ensure a directory exists 16 | */ 17 | export async function ensureDirExists(path: string) { 18 | const dir = dirname(path) 19 | 20 | if (!existsSync(dir)) { 21 | await mkdir(dir, { recursive: true }) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/radonis-shared/src/utils/node/fs_read_all.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-shared 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * Adapted from https://github.com/poppinss/utils 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | import { readdir, stat } from 'node:fs/promises' 13 | import { join } from 'node:path' 14 | import { fileURLToPath } from 'node:url' 15 | 16 | /** 17 | * Filter to remove dot files 18 | */ 19 | function filterDotFiles(fileName: string) { 20 | return fileName[0] !== '.' 21 | } 22 | 23 | /** 24 | * Read all files from the directory recursively 25 | */ 26 | async function readFiles(root: string, files: string[], relativePath: string) { 27 | const location = join(root, relativePath) 28 | const stats = await stat(location) 29 | 30 | if (stats.isDirectory()) { 31 | let locationFiles = await readdir(location) 32 | 33 | await Promise.all( 34 | locationFiles.filter(filterDotFiles).map((file) => { 35 | return readFiles(root, files, join(relativePath, file)) 36 | }) 37 | ) 38 | 39 | return 40 | } 41 | 42 | files.push(location) 43 | } 44 | 45 | /** 46 | * Get an array of file paths from the given location 47 | */ 48 | export async function fsReadAll( 49 | location: URL | string, 50 | options?: { filter?: (filePath: string, index: number) => boolean } 51 | ) { 52 | const normalizedLocation = typeof location === 'string' ? location : fileURLToPath(location) 53 | 54 | const files: string[] = [] 55 | 56 | await stat(normalizedLocation) 57 | await readFiles(normalizedLocation, files, '') 58 | 59 | if (options?.filter) { 60 | return files.filter(options.filter) 61 | } 62 | 63 | return files 64 | } 65 | -------------------------------------------------------------------------------- /packages/radonis-shared/src/utils/non_null.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-shared 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | /** 11 | * Remove nullish entries from an array 12 | */ 13 | export function nonNull(array: (T | null)[]): T[] { 14 | return array.filter((entry): entry is T => entry !== null) 15 | } 16 | -------------------------------------------------------------------------------- /packages/radonis-shared/src/utils/normalize_path.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-shared 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { createInternalURL } from './create_internal_url' 11 | 12 | /** 13 | * Normalize a path 14 | */ 15 | export function normalizePath(path: string) { 16 | return createInternalURL(path).pathname 17 | } 18 | -------------------------------------------------------------------------------- /packages/radonis-shared/src/utils/radonis_fetch.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-shared 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * Adapted from https://github.com/unjs/ofetch/tree/main 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | import superjson from 'superjson' 13 | 14 | import { getFetchOrFail } from './get_fetch_or_fail' 15 | 16 | /** 17 | * Helper to fetch from the Radonis server 18 | */ 19 | export async function radonisFetch(input: RequestInfo | URL, init?: RequestInit) { 20 | const requestInit: RequestInit = { 21 | ...init, 22 | headers: { 23 | 'Accept': 'application/json', 24 | ...init?.headers, 25 | 'x-radonis-request': 'true', 26 | }, 27 | body: init?.body?.constructor === Object ? superjson.stringify(init.body) : init?.body, 28 | } 29 | 30 | const fetchImpl = getFetchOrFail() 31 | const response = await fetchImpl(input, requestInit) 32 | 33 | if (!response.ok) { 34 | throw new Error(response.statusText) 35 | } 36 | 37 | return { 38 | ...response, 39 | async json() { 40 | const json = await response.json() 41 | 42 | /** 43 | * If the response is sent by Radonis, 44 | * deserialize it with superjson 45 | */ 46 | if (response.headers.get('x-radonis-response') === 'true') { 47 | return superjson.deserialize(json) 48 | } 49 | 50 | return json as T 51 | }, 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/radonis-shared/src/utils/separate_array.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-shared 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | /** 11 | * Separate items of an array with a specific value 12 | */ 13 | export function separateArray(array: unknown[], separator: unknown) { 14 | return array.flatMap((item) => [item, separator]).slice(0, -1) 15 | } 16 | -------------------------------------------------------------------------------- /packages/radonis-shared/src/utils/stringify_attributes.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-shared 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import type { AttributeValue } from '@microeinhundert/radonis-types' 11 | 12 | import { nonNull } from './non_null' 13 | 14 | /** 15 | * Stringify attributes 16 | */ 17 | export function stringifyAttributes(attributes: Record) { 18 | return nonNull( 19 | Object.entries(attributes).map(([name, value]) => { 20 | if (typeof value === 'boolean') { 21 | return value ? name.trim() : null 22 | } 23 | 24 | if (value === null || value === undefined) { 25 | return null 26 | } 27 | 28 | return `${name.trim()}="${value}"` 29 | }) 30 | ).join(' ') 31 | } 32 | -------------------------------------------------------------------------------- /packages/radonis-shared/src/utils/strip_leading_slash.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-shared 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | /** 11 | * Strip the leading slash if it exists 12 | */ 13 | export function stripLeadingSlash(path: string) { 14 | return path.startsWith('/') ? path.slice(1) : path 15 | } 16 | -------------------------------------------------------------------------------- /packages/radonis-shared/src/utils/strip_origin_from_url.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-shared 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | /** 11 | * Strip the origin from an URL 12 | */ 13 | export function stripOriginFromURL(url: URL) { 14 | return url.toString().replace(url.origin, '') 15 | } 16 | -------------------------------------------------------------------------------- /packages/radonis-shared/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "rootDir": "./", 6 | "composite": true, 7 | "lib": ["dom", "dom.iterable", "esnext"], 8 | "module": "commonjs", 9 | "outDir": "./build" 10 | }, 11 | "include": ["./**/*"], 12 | "exclude": ["./node_modules", "./build"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/radonis-types/.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /packages/radonis-types/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "root": true 4 | } 5 | -------------------------------------------------------------------------------- /packages/radonis-types/.npmignore: -------------------------------------------------------------------------------- 1 | **/tsconfig.tsbuildinfo 2 | **/test 3 | **/bin -------------------------------------------------------------------------------- /packages/radonis-types/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /packages/radonis-types/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Leon Seipp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/radonis-types/README.md: -------------------------------------------------------------------------------- 1 | # Radonis Types 2 | 3 | ![npm (scoped)](https://img.shields.io/npm/v/@microeinhundert/radonis-types) 4 | 5 | > **Note**: This is a package used by Radonis internally and should not be installed directly. 6 | 7 | ## About 8 | 9 | Radonis is a full-stack application framework for building monolithic applications with a modern, React-based frontend stack. Radonis is built on top of the Node.js MVC framework [AdonisJS](https://adonisjs.com/) and extends it with features for server-side rendering and client-side hydration. 10 | 11 | ## Documentation 12 | 13 | Documentation is available on [radonis.vercel.app](https://radonis.vercel.app/). 14 | 15 | ## License 16 | 17 | [MIT](LICENSE) 18 | -------------------------------------------------------------------------------- /packages/radonis-types/bin/japaTypes.ts: -------------------------------------------------------------------------------- 1 | import type { Assert } from '@japa/assert' 2 | 3 | declare module '@japa/runner' { 4 | interface TestContext { 5 | assert: Assert 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/radonis-types/bin/test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from '@japa/assert' 2 | import { runFailedTests } from '@japa/run-failed-tests' 3 | import { configure, processCliArgs, run } from '@japa/runner' 4 | import { specReporter } from '@japa/spec-reporter' 5 | 6 | process.env.NODE_ENV = 'test' 7 | 8 | /* 9 | |-------------------------------------------------------------------------- 10 | | Configure tests 11 | |-------------------------------------------------------------------------- 12 | | 13 | | The configure method accepts the configuration to configure the Japa 14 | | tests runner. 15 | | 16 | | The first method call "processCliArgs" process the command line arguments 17 | | and turns them into a config object. Using this method is not mandatory. 18 | | 19 | | Please consult japa.dev/runner-config for the config docs. 20 | */ 21 | configure({ 22 | ...processCliArgs(process.argv.slice(2)), 23 | ...{ 24 | files: ['test/**/*.spec.ts'], 25 | plugins: [assert(), runFailedTests()], 26 | reporters: [specReporter()], 27 | importer: (filePath: string) => import(filePath), 28 | }, 29 | }) 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Run tests 34 | |-------------------------------------------------------------------------- 35 | | 36 | | The following "run" method is required to execute all the tests. 37 | | 38 | */ 39 | run() 40 | -------------------------------------------------------------------------------- /packages/radonis-types/lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.ts': ['npm run lint -- --fix'], 3 | '*.{json,md}': ['npm run format'], 4 | } 5 | -------------------------------------------------------------------------------- /packages/radonis-types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@microeinhundert/radonis-types", 3 | "version": "5.0.4", 4 | "description": "Types package for Radonis", 5 | "type": "commonjs", 6 | "main": "./build/cjs/index.js", 7 | "types": "./build/cjs/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": { 11 | "types": "./build/esm/index.d.ts", 12 | "default": "./build/esm/index.js" 13 | }, 14 | "require": { 15 | "types": "./build/cjs/index.d.ts", 16 | "default": "./build/cjs/index.js" 17 | } 18 | } 19 | }, 20 | "sideEffects": false, 21 | "files": [ 22 | "build/cjs", 23 | "build/esm" 24 | ], 25 | "scripts": { 26 | "clean": "del-cli build", 27 | "build:cjs": "tsc --outDir ./build/cjs --module commonjs", 28 | "build:esm": "tsc --outDir ./build/esm --module esnext", 29 | "build": "\"$npm_execpath\" run build:cjs && \"$npm_execpath\" run build:esm", 30 | "lint": "TIMING=1 eslint . --ext=.ts", 31 | "format": "prettier --write .", 32 | "test": "node -r @adonisjs/require-ts/build/register bin/test.ts" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "git+ssh://git@github.com/microeinhundert/radonis.git", 37 | "directory": "packages/radonis-types" 38 | }, 39 | "bugs": { 40 | "url": "https://github.com/microeinhundert/radonis/issues" 41 | }, 42 | "publishConfig": { 43 | "access": "public" 44 | }, 45 | "engines": { 46 | "node": ">= 16.0.0", 47 | "npm": ">= 8.0.0" 48 | }, 49 | "keywords": [ 50 | "adonisjs", 51 | "adonis", 52 | "radonis", 53 | "types" 54 | ], 55 | "author": "Leon Seipp ", 56 | "license": "MIT", 57 | "peerDependencies": { 58 | "@adonisjs/core": "^5.8.0" 59 | }, 60 | "devDependencies": { 61 | "@types/node": "^18.15.11", 62 | "@types/react": "^18.0.31", 63 | "@types/react-dom": "^18.0.11" 64 | }, 65 | "dependencies": { 66 | "tslib": "^2.5.0" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/radonis-types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "rootDir": "./", 6 | "composite": true, 7 | "lib": ["esnext"], 8 | "module": "commonjs", 9 | "outDir": "./build", 10 | "types": ["@adonisjs/core"] 11 | }, 12 | "include": ["./**/*"], 13 | "exclude": ["./node_modules", "./build"] 14 | } 15 | -------------------------------------------------------------------------------- /packages/radonis-unocss/.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /packages/radonis-unocss/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "root": true 4 | } 5 | -------------------------------------------------------------------------------- /packages/radonis-unocss/.npmignore: -------------------------------------------------------------------------------- 1 | **/tsconfig.tsbuildinfo 2 | **/test 3 | **/bin -------------------------------------------------------------------------------- /packages/radonis-unocss/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /packages/radonis-unocss/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Leon Seipp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/radonis-unocss/README.md: -------------------------------------------------------------------------------- 1 | # UnoCSS Plugin for Radonis 2 | 3 | ![npm (scoped)](https://img.shields.io/npm/v/@microeinhundert/radonis-unocss) 4 | 5 | Plugin that adds styling powered by [UnoCSS](https://github.com/unocss/unocss) to your Radonis application. 6 | 7 | ## About 8 | 9 | Radonis is a full-stack application framework for building monolithic applications with a modern, React-based frontend stack. Radonis is built on top of the Node.js MVC framework [AdonisJS](https://adonisjs.com/) and extends it with features for server-side rendering and client-side hydration. 10 | 11 | ## Documentation 12 | 13 | Documentation is available on [radonis.vercel.app](https://radonis.vercel.app/docs/plugins/unocss). 14 | 15 | ## License 16 | 17 | [MIT](LICENSE) 18 | -------------------------------------------------------------------------------- /packages/radonis-unocss/bin/japaTypes.ts: -------------------------------------------------------------------------------- 1 | import type { Assert } from '@japa/assert' 2 | 3 | declare module '@japa/runner' { 4 | interface TestContext { 5 | assert: Assert 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/radonis-unocss/bin/test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from '@japa/assert' 2 | import { runFailedTests } from '@japa/run-failed-tests' 3 | import { configure, processCliArgs, run } from '@japa/runner' 4 | import { specReporter } from '@japa/spec-reporter' 5 | 6 | process.env.NODE_ENV = 'test' 7 | 8 | /* 9 | |-------------------------------------------------------------------------- 10 | | Configure tests 11 | |-------------------------------------------------------------------------- 12 | | 13 | | The configure method accepts the configuration to configure the Japa 14 | | tests runner. 15 | | 16 | | The first method call "processCliArgs" process the command line arguments 17 | | and turns them into a config object. Using this method is not mandatory. 18 | | 19 | | Please consult japa.dev/runner-config for the config docs. 20 | */ 21 | configure({ 22 | ...processCliArgs(process.argv.slice(2)), 23 | ...{ 24 | files: ['test/**/*.spec.ts'], 25 | plugins: [assert(), runFailedTests()], 26 | reporters: [specReporter()], 27 | importer: (filePath: string) => import(filePath), 28 | }, 29 | }) 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Run tests 34 | |-------------------------------------------------------------------------- 35 | | 36 | | The following "run" method is required to execute all the tests. 37 | | 38 | */ 39 | run() 40 | -------------------------------------------------------------------------------- /packages/radonis-unocss/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-unocss 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | export { unocssPlugin } from './src/plugin' 11 | export { basePreflight, formsPreflight } from './src/preflight' 12 | -------------------------------------------------------------------------------- /packages/radonis-unocss/lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.ts': ['npm run lint -- --fix'], 3 | '*.{json,md}': ['npm run format'], 4 | } 5 | -------------------------------------------------------------------------------- /packages/radonis-unocss/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@microeinhundert/radonis-unocss", 3 | "version": "5.0.4", 4 | "description": "UnoCSS plugin for Radonis", 5 | "type": "commonjs", 6 | "main": "./build/cjs/index.js", 7 | "types": "./build/cjs/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": { 11 | "types": "./build/esm/index.d.ts", 12 | "default": "./build/esm/index.js" 13 | }, 14 | "require": { 15 | "types": "./build/cjs/index.d.ts", 16 | "default": "./build/cjs/index.js" 17 | } 18 | } 19 | }, 20 | "sideEffects": false, 21 | "files": [ 22 | "build/cjs", 23 | "build/esm" 24 | ], 25 | "scripts": { 26 | "clean": "del-cli build", 27 | "build:cjs": "tsc --outDir ./build/cjs --module commonjs", 28 | "build:esm": "tsc --outDir ./build/esm --module esnext", 29 | "build": "\"$npm_execpath\" run build:cjs && \"$npm_execpath\" run build:esm", 30 | "lint": "TIMING=1 eslint . --ext=.ts", 31 | "format": "prettier --write .", 32 | "test": "node -r @adonisjs/require-ts/build/register bin/test.ts" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "git+ssh://git@github.com/microeinhundert/radonis.git", 37 | "directory": "packages/radonis-unocss" 38 | }, 39 | "bugs": { 40 | "url": "https://github.com/microeinhundert/radonis/issues" 41 | }, 42 | "publishConfig": { 43 | "access": "public" 44 | }, 45 | "engines": { 46 | "node": ">= 16.0.0", 47 | "npm": ">= 8.0.0" 48 | }, 49 | "keywords": [ 50 | "adonisjs", 51 | "adonis", 52 | "radonis", 53 | "plugin", 54 | "unocss" 55 | ], 56 | "author": "Leon Seipp ", 57 | "license": "MIT", 58 | "peerDependencies": { 59 | "@microeinhundert/radonis": "workspace:5.0.4" 60 | }, 61 | "devDependencies": { 62 | "@microeinhundert/radonis": "workspace:5.0.4", 63 | "@types/node": "^18.15.11" 64 | }, 65 | "dependencies": { 66 | "@microeinhundert/radonis-shared": "workspace:5.0.4", 67 | "@unocss/core": "0.50.6", 68 | "@unocss/preset-wind": "0.50.6", 69 | "mini-svg-data-uri": "^1.4.4", 70 | "tslib": "^2.5.0" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/radonis-unocss/src/config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-unocss 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import type { UserConfig } from '@unocss/core' 11 | import presetWind from '@unocss/preset-wind' 12 | 13 | import { basePreflight, formsPreflight } from './preflight' 14 | 15 | export const config: UserConfig = { 16 | presets: [presetWind()], 17 | preflights: [basePreflight, formsPreflight], 18 | } 19 | -------------------------------------------------------------------------------- /packages/radonis-unocss/src/plugin.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis-unocss 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { readFile } from 'node:fs/promises' 11 | 12 | import { definePlugin } from '@microeinhundert/radonis' 13 | import { isProduction } from '@microeinhundert/radonis-shared' 14 | import { fsReadAll } from '@microeinhundert/radonis-shared/node' 15 | import type { UserConfig } from '@unocss/core' 16 | import { createGenerator } from '@unocss/core' 17 | 18 | import { config as defaultConfig } from './config' 19 | 20 | /** 21 | * Plugin for integrating {@link https://github.com/unocss/unocss UnoCSS} with Radonis 22 | * @see https://radonis.vercel.app/docs/plugins/unocss 23 | */ 24 | export function unocssPlugin(config?: UserConfig) { 25 | let cssInjected = false 26 | let css: string 27 | 28 | const tokens = new Set() 29 | 30 | return definePlugin({ 31 | name: 'unocss', 32 | environments: ['server'], 33 | async onBootServer({ resourcesPath }) { 34 | const extensions = ['.ts', '.tsx', '.js', '.jsx', '.json', '.md', '.mdx'] 35 | const generator = createGenerator(config ?? defaultConfig) 36 | const paths = await fsReadAll(resourcesPath, { 37 | filter: (path) => extensions.some((extension) => path.endsWith(extension)), 38 | }) 39 | 40 | /** 41 | * Search for classes in files 42 | */ 43 | for (const path of paths) { 44 | const contents = await readFile(path, { encoding: 'utf-8' }) 45 | await generator.applyExtractors(contents, path, tokens) 46 | } 47 | 48 | const generatorResult = await generator.generate(tokens, { 49 | minify: isProduction, 50 | }) 51 | 52 | css = generatorResult.css 53 | }, 54 | beforeRequest() { 55 | cssInjected = false 56 | }, 57 | afterRender() { 58 | return (html: string) => { 59 | const hasHead = html.includes('') 60 | 61 | if (!cssInjected && css && hasHead) { 62 | cssInjected = true 63 | 64 | return html.replace('', `\n\n`) 65 | } 66 | 67 | return html 68 | } 69 | }, 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /packages/radonis-unocss/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "rootDir": "./", 6 | "composite": true, 7 | "lib": ["esnext"], 8 | "module": "commonjs", 9 | "outDir": "./build" 10 | }, 11 | "include": ["./**/*"], 12 | "exclude": ["./node_modules", "./build"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/radonis/.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /packages/radonis/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "root": true 4 | } 5 | -------------------------------------------------------------------------------- /packages/radonis/.npmignore: -------------------------------------------------------------------------------- 1 | **/tsconfig.tsbuildinfo 2 | **/test 3 | **/bin -------------------------------------------------------------------------------- /packages/radonis/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /packages/radonis/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Leon Seipp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/radonis/README.md: -------------------------------------------------------------------------------- 1 | # Radonis 2 | 3 | ![npm (scoped)](https://img.shields.io/npm/v/@microeinhundert/radonis) 4 | 5 | ## About 6 | 7 | Radonis is a full-stack application framework for building monolithic applications with a modern, React-based frontend stack. Radonis is built on top of the Node.js MVC framework [AdonisJS](https://adonisjs.com/) and extends it with features for server-side rendering and client-side hydration. 8 | 9 | ## Documentation 10 | 11 | Documentation is available on [radonis.vercel.app](https://radonis.vercel.app/). 12 | 13 | ## Official Plugins 14 | 15 | - [UnoCSS](https://radonis.vercel.app/docs/plugins/unocss) 16 | 17 | ## License 18 | 19 | [MIT](LICENSE) 20 | -------------------------------------------------------------------------------- /packages/radonis/bin/japaTypes.ts: -------------------------------------------------------------------------------- 1 | import type { Assert } from '@japa/assert' 2 | 3 | declare module '@japa/runner' { 4 | interface TestContext { 5 | assert: Assert 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/radonis/bin/test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from '@japa/assert' 2 | import { runFailedTests } from '@japa/run-failed-tests' 3 | import { configure, processCliArgs, run } from '@japa/runner' 4 | import { specReporter } from '@japa/spec-reporter' 5 | 6 | process.env.NODE_ENV = 'test' 7 | 8 | /* 9 | |-------------------------------------------------------------------------- 10 | | Configure tests 11 | |-------------------------------------------------------------------------- 12 | | 13 | | The configure method accepts the configuration to configure the Japa 14 | | tests runner. 15 | | 16 | | The first method call "processCliArgs" process the command line arguments 17 | | and turns them into a config object. Using this method is not mandatory. 18 | | 19 | | Please consult japa.dev/runner-config for the config docs. 20 | */ 21 | configure({ 22 | ...processCliArgs(process.argv.slice(2)), 23 | ...{ 24 | files: ['test/**/*.spec.ts'], 25 | plugins: [assert(), runFailedTests()], 26 | reporters: [specReporter()], 27 | importer: (filePath: string) => import(filePath), 28 | }, 29 | }) 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Run tests 34 | |-------------------------------------------------------------------------- 35 | | 36 | | The following "run" method is required to execute all the tests. 37 | | 38 | */ 39 | run() 40 | -------------------------------------------------------------------------------- /packages/radonis/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | export { definePlugin } from './src/define_plugin' 11 | export { initClient } from './src/init_client' 12 | export { token$ } from './src/token' 13 | export type { FormChildrenProps, FormOptions, FormProps } from '@microeinhundert/radonis-form' 14 | export { Form, useForm } from '@microeinhundert/radonis-form' 15 | export { 16 | useApplication, 17 | useAssetsManager, 18 | useFlashMessages, 19 | useFlushEffect, 20 | useGlobals, 21 | useHttpContext, 22 | useI18n, 23 | useManifest, 24 | useManifestManager, 25 | useMutation, 26 | useParams, 27 | useRenderer, 28 | useRequest, 29 | useRoute, 30 | useRouter, 31 | useRoutes, 32 | useSearchParams, 33 | useServer, 34 | useSession, 35 | useUrlBuilder, 36 | } from '@microeinhundert/radonis-hooks' 37 | export { HydrationRoot, useHydrated, useHydration } from '@microeinhundert/radonis-hydrate' 38 | export { hydrateIsland as __internal__hydrateIsland, island } from '@microeinhundert/radonis-hydrate' 39 | export { createError, RadonisException, radonisFetch, UrlBuilder } from '@microeinhundert/radonis-shared' 40 | export type { HeadMeta, HeadTag, Plugin, RouteParams, RouteQueryParams } from '@microeinhundert/radonis-types' 41 | -------------------------------------------------------------------------------- /packages/radonis/lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.ts': ['npm run lint -- --fix'], 3 | '*.{json,md}': ['npm run format'], 4 | } 5 | -------------------------------------------------------------------------------- /packages/radonis/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@microeinhundert/radonis", 3 | "version": "5.0.4", 4 | "description": "Bridge the gap between React and AdonisJS", 5 | "type": "commonjs", 6 | "main": "./build/cjs/index.js", 7 | "types": "./build/cjs/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": { 11 | "types": "./build/esm/index.d.ts", 12 | "default": "./build/esm/index.js" 13 | }, 14 | "require": { 15 | "types": "./build/cjs/index.d.ts", 16 | "default": "./build/cjs/index.js" 17 | } 18 | } 19 | }, 20 | "sideEffects": false, 21 | "files": [ 22 | "build/cjs", 23 | "build/esm" 24 | ], 25 | "scripts": { 26 | "clean": "del-cli build", 27 | "build:cjs": "tsc --outDir ./build/cjs --module commonjs", 28 | "build:esm": "tsc --outDir ./build/esm --module esnext", 29 | "build": "\"$npm_execpath\" run build:cjs && \"$npm_execpath\" run build:esm", 30 | "lint": "TIMING=1 eslint . --ext=.ts", 31 | "format": "prettier --write .", 32 | "test": "node -r @adonisjs/require-ts/build/register bin/test.ts" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "git+ssh://git@github.com/microeinhundert/radonis.git", 37 | "directory": "packages/radonis" 38 | }, 39 | "bugs": { 40 | "url": "https://github.com/microeinhundert/radonis/issues" 41 | }, 42 | "publishConfig": { 43 | "access": "public" 44 | }, 45 | "engines": { 46 | "node": ">= 16.0.0", 47 | "npm": ">= 8.0.0" 48 | }, 49 | "keywords": [ 50 | "adonisjs", 51 | "adonis", 52 | "radonis" 53 | ], 54 | "author": "Leon Seipp ", 55 | "license": "MIT", 56 | "peerDependencies": { 57 | "react": "^18.2.0", 58 | "react-dom": "^18.2.0" 59 | }, 60 | "devDependencies": { 61 | "@types/node": "^18.15.11" 62 | }, 63 | "dependencies": { 64 | "@microeinhundert/radonis-types": "workspace:5.0.4", 65 | "@microeinhundert/radonis-shared": "workspace:5.0.4", 66 | "@microeinhundert/radonis-server": "workspace:5.0.4", 67 | "@microeinhundert/radonis-hydrate": "workspace:5.0.4", 68 | "@microeinhundert/radonis-hooks": "workspace:5.0.4", 69 | "@microeinhundert/radonis-form": "workspace:5.0.4", 70 | "tslib": "^2.5.0" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/radonis/src/define_plugin.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import type { Plugin } from '@microeinhundert/radonis-types' 11 | 12 | /** 13 | * Helper to define plugins in a type-safe manner 14 | */ 15 | export function definePlugin(plugin: Plugin): Plugin { 16 | return plugin 17 | } 18 | -------------------------------------------------------------------------------- /packages/radonis/src/exceptions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { createError } from '@microeinhundert/radonis-shared' 11 | 12 | export const E_CANNOT_INIT_CLIENT_MORE_THAN_ONCE = createError( 13 | 'The Radonis client cannot be initialized more than once. Make sure you initialize the client only once in your application, typically in your entry.client.ts file', 14 | 'E_CANNOT_INIT_CLIENT_MORE_THAN_ONCE', 15 | 500 16 | ) 17 | 18 | export const E_CANNOT_INIT_CLIENT_ON_SERVER = createError( 19 | 'The Radonis client cannot be initialized server-side. Make sure to only call "initClient" in the client bundle, typically in your "entry.client.ts" file', 20 | 'E_CANNOT_INIT_CLIENT_ON_SERVER', 21 | 500 22 | ) 23 | -------------------------------------------------------------------------------- /packages/radonis/src/init_client.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { isServer } from '@microeinhundert/radonis-shared' 11 | import { startTransition } from 'react' 12 | 13 | import { E_CANNOT_INIT_CLIENT_MORE_THAN_ONCE, E_CANNOT_INIT_CLIENT_ON_SERVER } from './exceptions' 14 | import { hydrator, pluginsManager } from './singletons' 15 | import type { ClientOptions } from './types/main' 16 | 17 | let isClientInitialized = false 18 | 19 | /** 20 | * Hydrate the page 21 | */ 22 | function hydrate() { 23 | startTransition(() => { 24 | hydrator.hydrateHydrationRoots() 25 | }) 26 | } 27 | 28 | /** 29 | * Initialize the client 30 | */ 31 | export async function initClient(options?: ClientOptions): Promise { 32 | if (isServer) { 33 | throw new E_CANNOT_INIT_CLIENT_ON_SERVER() 34 | } 35 | if (isClientInitialized) { 36 | throw new E_CANNOT_INIT_CLIENT_MORE_THAN_ONCE() 37 | } 38 | 39 | isClientInitialized = true 40 | 41 | if (options?.plugins?.length) { 42 | pluginsManager.install('client', ...options.plugins) 43 | await pluginsManager.execute('onInitClient', null, null) 44 | } 45 | 46 | if (window.requestIdleCallback) { 47 | window.requestIdleCallback(hydrate) 48 | } else { 49 | window.setTimeout(hydrate, 1) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/radonis/src/singletons.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { Hydrator } from '@microeinhundert/radonis-hydrate' 11 | import { PluginsManager } from '@microeinhundert/radonis-server/standalone' 12 | 13 | export const pluginsManager = PluginsManager.getSingletonInstance() 14 | export const hydrator = Hydrator.getSingletonInstance(pluginsManager) 15 | -------------------------------------------------------------------------------- /packages/radonis/src/token.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | /** 11 | * Helper to manually mark a string as 12 | * a token to be parsed by the Radonis compiler 13 | */ 14 | export function token$(token: T): T { 15 | return token 16 | } 17 | -------------------------------------------------------------------------------- /packages/radonis/src/types/main.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @microeinhundert/radonis 3 | * 4 | * (c) Leon Seipp 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import type { Plugin } from '@microeinhundert/radonis-types' 11 | 12 | /** 13 | * Client options 14 | */ 15 | export type ClientOptions = { 16 | plugins?: Plugin[] 17 | } 18 | -------------------------------------------------------------------------------- /packages/radonis/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "rootDir": "./", 6 | "composite": true, 7 | "lib": ["dom", "dom.iterable", "esnext"], 8 | "module": "commonjs", 9 | "outDir": "./build" 10 | }, 11 | "include": ["./**/*"], 12 | "exclude": ["./node_modules", "./build"] 13 | } 14 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/adonis-preset-ts/tsconfig", 3 | "compilerOptions": { 4 | "rootDir": "./", 5 | "target": "es2022", 6 | "module": "commonjs", 7 | "incremental": true, 8 | "removeComments": false, 9 | "moduleResolution": "node", 10 | "allowSyntheticDefaultImports": true, 11 | "declaration": true, 12 | "declarationMap": true, 13 | "esModuleInterop": true, 14 | "importHelpers": true, 15 | "skipLibCheck": true, 16 | "sourceMap": true, 17 | "resolveJsonModule": true 18 | }, 19 | "include": [], 20 | "references": [ 21 | { 22 | "path": "./packages/radonis" 23 | }, 24 | { 25 | "path": "./packages/radonis-build" 26 | }, 27 | { 28 | "path": "./packages/radonis-form" 29 | }, 30 | { 31 | "path": "./packages/radonis-hooks" 32 | }, 33 | { 34 | "path": "./packages/radonis-hydrate" 35 | }, 36 | { 37 | "path": "./packages/radonis-server" 38 | }, 39 | { 40 | "path": "./packages/radonis-shared" 41 | }, 42 | { 43 | "path": "./packages/radonis-unocss" 44 | }, 45 | { 46 | "path": "./packages/radonis-query" 47 | }, 48 | { 49 | "path": "./packages/radonis-types" 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turborepo.org/schema.json", 3 | "pipeline": { 4 | "build": { 5 | "outputs": ["build"], 6 | "dependsOn": ["clean", "^build"] 7 | }, 8 | "lint": { 9 | "outputs": [], 10 | "dependsOn": ["^lint"] 11 | }, 12 | "clean": { 13 | "outputs": [], 14 | "dependsOn": ["^clean"] 15 | }, 16 | "format": { 17 | "outputs": [] 18 | }, 19 | "test": { 20 | "outputs": [] 21 | }, 22 | "dev": { 23 | "cache": false 24 | } 25 | } 26 | } 27 | --------------------------------------------------------------------------------