├── .dockerignore ├── .gitattributes ├── .github ├── actions │ └── docker-build-push │ │ └── action.yml └── workflows │ ├── deno.yml │ ├── docker.yml │ ├── opengb.yml │ └── release-please.yml ├── .gitignore ├── .release-please-manifest.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── artifacts ├── dynamic_archive.json ├── module_schema.json ├── prisma_archive.json ├── project_schema.json └── runtime_archive.json ├── cli.Dockerfile ├── deno.jsonc ├── deno.lock ├── docs-old ├── CONVENTIONS.md ├── ERROR_HANDLING.md ├── FAQ.md ├── KNOWN_ISSUES.md ├── MODULE_ISOLATION.md ├── TIPS.md ├── WHY_PORT.md ├── evaluations │ ├── CACHING.md │ ├── DB.md │ ├── DRIZZLE.md │ └── MIDDLEWARE_BRAINSTORM.md └── getting-started │ ├── FOR_GODOT.md │ └── FOR_UNITY.md ├── docs ├── .gitignore ├── .vscode │ └── settings.json ├── README.md ├── _internal │ ├── scripts │ │ └── generate.ts │ └── templates │ │ ├── example_private.mdx │ │ ├── example_public.mdx │ │ ├── module_overview.mdx │ │ ├── modules_overview.mdx │ │ └── script.mdx ├── build │ ├── config.ts │ ├── conventions.mdx │ ├── crash-course.mdx │ ├── database.mdx │ ├── errors.mdx │ ├── ide.mdx │ ├── module-config.mdx │ ├── overview.mdx │ ├── public.mdx │ ├── raw-sql.mdx │ ├── registry.mdx │ └── scripts.mdx ├── comparison │ ├── nakama.mdx │ ├── playfab.mdx │ ├── pragma.mdx │ └── supabase.mdx ├── concepts │ ├── cli.mdx │ ├── enterprise-support.mdx │ ├── modules.mdx │ ├── multiple-games.mdx │ ├── project-config.mdx │ ├── quickstart.mdx │ ├── registries.mdx │ ├── roadmap.mdx │ ├── sdk.mdx │ ├── self-hosting.mdx │ ├── structure.mdx │ └── visual-sql-client.mdx ├── engine │ ├── design │ │ ├── deno.mdx │ │ ├── postgres.mdx │ │ ├── prisma.mdx │ │ ├── registries-dependencies.mdx │ │ └── typescript.mdx │ └── introduction.mdx ├── favicon.svg ├── images │ ├── hero-dark.png │ ├── hero-light.png │ ├── overview.d2 │ ├── overview.svg │ ├── structure.d2 │ └── structure.svg ├── integrations │ ├── infisical.mdx │ ├── overview.mdx │ └── sendgrid.mdx ├── introduction.mdx ├── logo │ ├── dark.svg │ └── light.svg ├── mint.json ├── mint.template.json ├── modules │ ├── auth │ │ ├── overview.mdx │ │ └── scripts │ │ │ ├── complete_email_verification.mdx │ │ │ └── send_email_verification.mdx │ ├── currency │ │ ├── overview.mdx │ │ └── scripts │ │ │ ├── deposit.mdx │ │ │ ├── get_balance.mdx │ │ │ ├── get_balance_by_token.mdx │ │ │ ├── set_balance.mdx │ │ │ └── withdraw.mdx │ ├── email │ │ ├── overview.mdx │ │ └── scripts │ │ │ └── send_email.mdx │ ├── friends │ │ ├── overview.mdx │ │ └── scripts │ │ │ ├── accept_request.mdx │ │ │ ├── decline_request.mdx │ │ │ ├── list_friends.mdx │ │ │ ├── list_incoming_friend_requests.mdx │ │ │ ├── list_outgoing_friend_requests.mdx │ │ │ ├── remove_friend.mdx │ │ │ └── send_request.mdx │ ├── overview.mdx │ ├── rate_limit │ │ ├── overview.mdx │ │ └── scripts │ │ │ ├── throttle.mdx │ │ │ └── throttle_public.mdx │ ├── tokens │ │ ├── overview.mdx │ │ └── scripts │ │ │ ├── create.mdx │ │ │ ├── extend.mdx │ │ │ ├── fetch.mdx │ │ │ ├── fetch_by_token.mdx │ │ │ ├── revoke.mdx │ │ │ └── validate.mdx │ ├── uploads │ │ ├── overview.mdx │ │ └── scripts │ │ │ ├── complete.mdx │ │ │ ├── delete.mdx │ │ │ ├── get.mdx │ │ │ ├── get_public_file_urls.mdx │ │ │ └── prepare.mdx │ └── users │ │ ├── overview.mdx │ │ └── scripts │ │ ├── authenticate_token.mdx │ │ ├── create.mdx │ │ ├── create_token.mdx │ │ └── fetch.mdx └── snippets │ ├── module-cards.mdx │ └── tags.mdx ├── media └── hero.png ├── release-please-config.json ├── src ├── artifacts │ ├── build_dynamic_archive.ts │ ├── build_prisma_archive.ts │ ├── build_runtime_archive.ts │ ├── build_schema.ts │ ├── deps.ts │ └── util.ts ├── build │ ├── deno_config.ts │ ├── deps.ts │ ├── entrypoint.ts │ ├── gen.ts │ ├── gen │ │ ├── code_builder.ts │ │ ├── mod.ts │ │ ├── module.ts │ │ ├── public.ts │ │ └── type.ts │ ├── inflate_runtime_archive.ts │ ├── meta.ts │ ├── misc.ts │ ├── mod.ts │ ├── module_config_schema.ts │ ├── module_config_schema.worker.ts │ ├── openapi.ts │ ├── plan │ │ ├── module.ts │ │ ├── project.ts │ │ └── script.ts │ ├── script_schema.ts │ ├── script_schema.worker.ts │ └── util.ts ├── build_state │ ├── cache.ts │ ├── deps.ts │ └── mod.ts ├── cli │ ├── commands │ │ ├── build.ts │ │ ├── clean.ts │ │ ├── create.ts │ │ ├── db.ts │ │ ├── dev.ts │ │ ├── format.ts │ │ ├── init.ts │ │ ├── internal.ts │ │ ├── lint.ts │ │ ├── module.ts │ │ ├── sdk.ts │ │ └── test.ts │ ├── common.ts │ ├── deps.ts │ └── main.ts ├── config │ ├── deps.ts │ ├── module.ts │ └── project.ts ├── deps.ts ├── dynamic │ ├── actor_cf.ts │ └── actor_deno.ts ├── error │ └── mod.ts ├── migrate │ ├── build_prisma_esm.ts │ ├── deploy.ts │ ├── deps.ts │ ├── dev.ts │ ├── generate.ts │ ├── mod.ts │ ├── prisma.ts │ ├── reset.ts │ ├── status.ts │ └── validate.ts ├── project │ ├── actor.ts │ ├── deps.ts │ ├── mod.ts │ ├── module.ts │ ├── project.ts │ ├── registry.ts │ ├── registry_default_rev.json │ └── script.ts ├── runtime │ ├── actor.ts │ ├── context.ts │ ├── deps.ts │ ├── error.ts │ ├── error_test.ts │ ├── mod.ts │ ├── postgres.ts │ ├── proxy.ts │ ├── runtime.ts │ ├── server.ts │ └── trace.ts ├── sdk │ └── generate.ts ├── template │ ├── deps.ts │ ├── module.ts │ ├── project.ts │ ├── script.ts │ ├── template_test.ts │ └── test.ts ├── term │ ├── deps.ts │ └── status.ts ├── types │ ├── case_conversions.ts │ ├── expandable.ts │ ├── identifiers │ │ ├── defs.ts │ │ ├── deps.ts │ │ ├── errors.ts │ │ └── mod.ts │ ├── json.ts │ └── registry.ts ├── utils │ ├── db.ts │ ├── once.ts │ ├── postgres_daemon.ts │ ├── shutdown_handler.ts │ └── worker_pool.ts └── watch │ ├── deps.ts │ └── mod.ts ├── tests └── basic │ ├── .gitignore │ ├── backend.json │ ├── clean_run.sh │ ├── deno.json │ ├── deno.lock │ └── modules │ ├── config_test │ ├── config.ts │ ├── module.json │ ├── scripts │ │ └── read_config.ts │ └── tests │ │ └── read_config.ts │ └── foo │ ├── actors │ └── ponger.ts │ ├── db │ ├── migrations │ │ ├── 20240307013756_init │ │ │ └── migration.sql │ │ ├── 20240312203025_init │ │ │ └── migration.sql │ │ └── migration_lock.toml │ └── schema.prisma │ ├── module.json │ ├── scripts │ ├── actor.ts │ ├── call_self.ts │ ├── create_entry.ts │ └── ping.ts │ ├── tests │ └── e2e.ts │ └── types │ └── common.ts └── vendor └── prisma ├── .dockerignore ├── .gitignore ├── package-lock.json └── package.json /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !deno.jsonc 3 | !deno.lock 4 | !src/ 5 | !vendor/prisma 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # These files are generated, so don't bother with conflicts. 2 | /artifacts/ merge=ours 3 | 4 | *.png binary 5 | -------------------------------------------------------------------------------- /.github/actions/docker-build-push/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Docker Build Push' 2 | description: 'Builds and pushes a Docker image to GitHub Container Registry' 3 | inputs: 4 | github_token: 5 | description: 'GitHub token for authentication' 6 | required: true 7 | context: 8 | description: 'The build context' 9 | required: true 10 | file: 11 | description: 'The Dockerfile to use' 12 | required: true 13 | platforms: 14 | description: 'The target platforms for the build' 15 | required: true 16 | sha_tag: 17 | description: 'The SHA tag for the image' 18 | required: true 19 | build_tags: 20 | description: 'The reference tag for the image' 21 | required: true 22 | runs: 23 | using: 'composite' 24 | steps: 25 | - name: Set up QEMU 26 | uses: docker/setup-qemu-action@v2 27 | with: 28 | platforms: arm64 29 | 30 | - name: Set up Docker Buildx 31 | uses: docker/setup-buildx-action@v2 32 | 33 | - name: Log in to GitHub Container Registry 34 | uses: docker/login-action@v2 35 | with: 36 | registry: ghcr.io 37 | username: ${{ github.repository_owner }} 38 | password: ${{ inputs.github_token }} 39 | 40 | - name: Build And Push 41 | uses: docker/build-push-action@v4 42 | with: 43 | context: ${{ inputs.context }} 44 | file: ${{ inputs.file }} 45 | platforms: ${{ inputs.platforms }} 46 | push: true 47 | tags: ${{ inputs.build_tags }} 48 | 49 | - name: Image Digest 50 | shell: bash 51 | run: | 52 | echo "Image digest (amd64): ${{ steps.build-and-push.outputs.digest-amd64 }}" 53 | echo "Image digest (arm64): ${{ steps.build-and-push.outputs.digest-arm64 }}" 54 | 55 | -------------------------------------------------------------------------------- /.github/workflows/deno.yml: -------------------------------------------------------------------------------- 1 | name: Deno 2 | 3 | on: 4 | - push 5 | 6 | jobs: 7 | format: 8 | runs-on: ubuntu-20.04 9 | timeout-minutes: 2 10 | steps: 11 | - uses: actions/checkout@v2 12 | 13 | - name: Install Deno 14 | uses: denoland/setup-deno@v1 15 | with: 16 | deno-version: "1.41.1" 17 | 18 | - name: Format 19 | run: deno task format:check 20 | 21 | lint: 22 | runs-on: ubuntu-20.04 23 | timeout-minutes: 2 24 | steps: 25 | - uses: actions/checkout@v2 26 | 27 | - name: Install Deno 28 | uses: denoland/setup-deno@v1 29 | with: 30 | deno-version: "1.41.1" 31 | 32 | - name: Lint 33 | run: deno task lint 34 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Build and Push Docker Image 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | 8 | jobs: 9 | build-and-push: 10 | runs-on: ubuntu-20.04 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v2 14 | 15 | - name: Docker Build Push 16 | uses: ./.github/actions/docker-build-push 17 | with: 18 | github_token: ${{ secrets.GITHUB_TOKEN }} 19 | context: . 20 | file: cli.Dockerfile 21 | platforms: linux/amd64,linux/arm64 22 | build_tags: | 23 | ghcr.io/rivet-gg/opengb:${{ github.sha }} 24 | ghcr.io/rivet-gg/opengb:${{ github.ref_name || '' }} 25 | 26 | -------------------------------------------------------------------------------- /.github/workflows/opengb.yml: -------------------------------------------------------------------------------- 1 | name: OpenGB 2 | 3 | on: 4 | - push 5 | 6 | jobs: 7 | test-core: 8 | runs-on: ubuntu-20.04 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v2 12 | 13 | - name: Install Deno 14 | uses: denoland/setup-deno@v1 15 | with: 16 | deno-version: "1.44.1" 17 | 18 | - name: Test Core 19 | env: 20 | VERBOSE: true 21 | run: deno task test:core 22 | 23 | test-project: 24 | runs-on: ubuntu-20.04 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v2 28 | 29 | - name: Install Deno 30 | uses: denoland/setup-deno@v1 31 | with: 32 | deno-version: "1.44.1" 33 | 34 | - name: Test Project 35 | env: 36 | VERBOSE: true 37 | run: deno task test:project 38 | 39 | test-modules: 40 | runs-on: ubuntu-20.04 41 | steps: 42 | - name: Checkout rivet-gg/opengb 43 | uses: actions/checkout@v2 44 | with: 45 | path: opengb 46 | 47 | # Ensure we check out the same modules that we use it he default registry 48 | - name: Read registry ref from JSON 49 | id: read-ref 50 | run: echo "OPENGB_MODULES_REF=$(jq -r '.' opengb/src/project/registry_default_rev.json)" >> $GITHUB_ENV 51 | 52 | - name: Checkout rivet-gg/opengb-modules 53 | uses: actions/checkout@v2 54 | with: 55 | ref: ${{ env.OPENGB_MODULES_REF }} 56 | repository: rivet-gg/opengb-modules 57 | path: opengb-modules 58 | token: ${{ secrets.GITHUB_TOKEN }} 59 | 60 | - name: Install Deno 61 | uses: denoland/setup-deno@v1 62 | with: 63 | deno-version: "1.44.1" 64 | 65 | - name: Test Modules 66 | env: 67 | VERBOSE: true 68 | run: cd opengb && deno task test:registry 69 | 70 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | name: release-please 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | permissions: 9 | contents: write 10 | pull-requests: write 11 | packages: write 12 | 13 | jobs: 14 | release-please: 15 | runs-on: ubuntu-latest 16 | outputs: 17 | release_created: ${{ steps.release.outputs.release_created }} 18 | tag_name: ${{ steps.release.outputs.tag_name }} 19 | release_sha: ${{ steps.release.outputs.sha }} 20 | steps: 21 | - id: release 22 | uses: google-github-actions/release-please-action@v4 23 | with: 24 | token: ${{ secrets.GITHUB_TOKEN }} 25 | config-file: release-please-config.json 26 | 27 | # We need to include this action here instead of in another workflow since 28 | # workflows aren't triggered by other workflows. 29 | publish: 30 | needs: release-please 31 | if: needs.release-please.outputs.release_created == 'true' 32 | runs-on: ubuntu-latest 33 | name: Build and Push Docker Image 34 | 35 | steps: 36 | - name: Checkout 37 | uses: actions/checkout@v2 38 | 39 | - name: Docker Build Push 40 | uses: ./.github/actions/docker-build-push 41 | with: 42 | github_token: ${{ secrets.GITHUB_TOKEN }} 43 | context: . 44 | file: cli.Dockerfile 45 | platforms: linux/amd64,linux/arm64 46 | build_tags: | 47 | ghcr.io/rivet-gg/opengb:${{ needs.release-please.outputs.release_sha }} 48 | ghcr.io/rivet-gg/opengb:${{ needs.release-please.outputs.tag_name }} 49 | 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OpenGB 2 | .opengb 3 | dist/ 4 | _gen/ 5 | *.gen.ts 6 | 7 | # Created by https://www.toptal.com/developers/gitignore/api/deno,macos 8 | # Edit at https://www.toptal.com/developers/gitignore?templates=deno,macos 9 | 10 | ### Deno ### 11 | /.idea/ 12 | /.vscode/ 13 | 14 | /node_modules 15 | 16 | .env 17 | *.orig 18 | *.pyc 19 | *.swp 20 | 21 | ### macOS ### 22 | # General 23 | .DS_Store 24 | .AppleDouble 25 | .LSOverride 26 | 27 | # Icon must end with two \r 28 | Icon 29 | 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | .com.apple.timemachine.donotpresent 42 | 43 | # Directories potentially created on remote AFP share 44 | .AppleDB 45 | .AppleDesktop 46 | Network Trash Folder 47 | Temporary Items 48 | .apdisk 49 | 50 | ### macOS Patch ### 51 | # iCloud generated files 52 | *.icloud 53 | 54 | # End of https://www.toptal.com/developers/gitignore/api/deno,macos 55 | 56 | 57 | -------------------------------------------------------------------------------- /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | ".": "0.1.5" 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Open Game Backend 2 | 3 | ![Backend Made Simple](./media/hero.png) 4 | 5 | - **Modular**: Mix, match, & modify modules as needed to fit your game's unique requirements. 6 | - **Script like a game engine**: Easily extend & adapt on top of the [OpenGB Engine](/engine/introduction) using 7 | TypeScript. Designed to be scripted by game developers. 8 | - **Batteries included**: Provides thoroughly reviewed, tested, and documented modules to get you started quickly & 9 | allow you to customize to fit your needs. 10 | - **Secure, load-tested, & resilient**: Built to withstand the chaos that games need to grow & stay online. Load 11 | testing, rate limits, captchas, strict schemas, and more are all enforced by default. 12 | 13 | ## Prerequisites 14 | 15 | - [Deno](https://docs.deno.com/runtime/manual/getting_started/installation) 16 | - [Docker](https://docs.docker.com/get-docker/) ([#125](https://github.com/rivet-gg/opengb/issues/125)) 17 | - Git 18 | 19 | ## Install 20 | 21 | **From GitHub (recommended)** 22 | 23 | ```sh 24 | deno install -n opengb -fgA https://raw.githubusercontent.com/rivet-gg/opengb/v0.1.1/src/cli/main.ts 25 | ``` 26 | 27 | **From source** 28 | 29 | After cloning the repo, run: 30 | 31 | ``` 32 | git clone https://github.com/rivet-gg/opengb.git 33 | cd opengb 34 | deno task cli:install 35 | ``` 36 | 37 | ## Technologies Used 38 | 39 | - **Language** TypeScript 40 | - **Runtime** Deno 41 | - **Database** Postgres 42 | - **ORM** Prisma 43 | 44 | ## Documentation 45 | 46 | - [Quickstart](http://opengb.dev/concepts/quickstart) 47 | - [All available modules](http://opengb.dev/modules) 48 | - [Building modules](https://opengb.dev/build/crash-course) 49 | - [OpenGB Engine internals](http://opengb.dev/engine/introduction) 50 | - Visit [opengb.dev](http://opengb.dev/introduction) for more 51 | 52 | ## Looking for the module registry? 53 | 54 | See [rivet-gg/opengb-modules](https://github.com/rivet-gg/opengb-modules.git). 55 | 56 | -------------------------------------------------------------------------------- /artifacts/module_schema.json: -------------------------------------------------------------------------------- 1 | {"type":"object","properties":{"status":{"enum":["beta","end_of_life","maintenance","preview","stable"],"type":"string"},"name":{"description":"The human readable name of the module.","type":"string"},"description":{"description":"A short description of the module.","type":"string"},"icon":{"description":"The [Font Awesome](https://fontawesome.com/icons) icon name of the module.","type":"string"},"tags":{"description":"The tags associated with this module.","type":"array","items":{"type":"string"}},"authors":{"description":"The GitHub handle of the authors of the module.","type":"array","items":{"type":"string"}},"scripts":{"type":"object","additionalProperties":{"$ref":"#/definitions/ScriptConfig"}},"actors":{"type":"object","additionalProperties":{"$ref":"#/definitions/ActorConfig"}},"errors":{"type":"object","additionalProperties":{"$ref":"#/definitions/ErrorConfig"}},"dependencies":{"type":"object","additionalProperties":{"$ref":"#/definitions/DependencyConfig"}},"defaultConfig":{"description":"Default user config.\n\nThe user-provided config will be deep merged with this config."}},"additionalProperties":false,"required":["errors","scripts"],"definitions":{"ScriptConfig":{"type":"object","properties":{"name":{"description":"The human readable name of the script.","type":"string"},"description":{"description":"A short description of the script.","type":"string"},"public":{"description":"If the script can be called from the public HTTP interface.\n\nIf enabled, ensure that authentication & rate limits are configured for\nthis endpoints. See the `user` and `rate_limit` modules.","default":false,"type":"boolean"}},"additionalProperties":false},"ActorConfig":{"type":"object","properties":{"storage_id":{"description":"A globally unique string for storing data for this actor.\n\n**IMPORTANT** Changing this will effectively unlink all data stored in this actor. Changing it back to\nthe old value will restore the data.","type":"string"}},"additionalProperties":false,"required":["storage_id"]},"ErrorConfig":{"type":"object","properties":{"name":{"description":"The human readable name of the error.","type":"string"},"description":{"description":"A short description of the error.","type":"string"}},"additionalProperties":false},"DependencyConfig":{"type":"object","additionalProperties":false}},"$schema":"http://json-schema.org/draft-07/schema#"} -------------------------------------------------------------------------------- /artifacts/project_schema.json: -------------------------------------------------------------------------------- 1 | {"type":"object","properties":{"registries":{"type":"object","additionalProperties":{"$ref":"#/definitions/RegistryConfig"}},"modules":{"type":"object","additionalProperties":{"$ref":"#/definitions/ProjectModuleConfig"}},"runtime":{"$ref":"#/definitions/RuntimeConfig"}},"additionalProperties":false,"required":["modules","registries"],"definitions":{"RegistryConfig":{"anyOf":[{"type":"object","properties":{"local":{"$ref":"#/definitions/RegistryConfigLocal"}},"additionalProperties":false,"required":["local"]},{"type":"object","properties":{"git":{"$ref":"#/definitions/RegistryConfigGit"}},"additionalProperties":false,"required":["git"]}]},"RegistryConfigLocal":{"type":"object","properties":{"directory":{"type":"string"},"isExternal":{"description":"If true, this will be treated like an external registry. This is\nimportant if multiple projects are using the same registry locally.\n\n Modules from this directory will not be tested, formatted, linted, and\n generate Prisma migrations.","type":"boolean"}},"additionalProperties":false,"required":["directory"]},"RegistryConfigGit":{"anyOf":[{"additionalProperties":false,"type":"object","properties":{"url":{"$ref":"#/definitions/RegistryConfigGitUrl"},"directory":{"type":"string"},"branch":{"type":"string"}},"required":["branch","url"]},{"additionalProperties":false,"type":"object","properties":{"url":{"$ref":"#/definitions/RegistryConfigGitUrl"},"directory":{"type":"string"},"tag":{"type":"string"}},"required":["tag","url"]},{"additionalProperties":false,"type":"object","properties":{"url":{"$ref":"#/definitions/RegistryConfigGitUrl"},"directory":{"type":"string"},"rev":{"type":"string"}},"required":["rev","url"]}]},"RegistryConfigGitUrl":{"description":"The URL to the git repository.\n\nIf both HTTPS and SSH URL are provided, they will both be tried and use the\none that works.","anyOf":[{"type":"object","properties":{"https":{"type":"string"},"ssh":{"type":"string"}},"additionalProperties":false},{"type":"string"}]},"ProjectModuleConfig":{"type":"object","properties":{"registry":{"description":"The name of the registry to fetch the module from.","type":"string"},"module":{"description":"Overrides the name of the module to fetch inside the registry.","type":"string"},"config":{"description":"The config that configures how this module is ran at runtime."}},"additionalProperties":false},"RuntimeConfig":{"type":"object","properties":{"cors":{"description":"If this is null, only requests made from the same origin will be accepted","$ref":"#/definitions/CorsConfig"}},"additionalProperties":false},"CorsConfig":{"type":"object","properties":{"origins":{"description":"The origins that are allowed to make requests to the server.","type":"array","items":{"type":"string"}}},"additionalProperties":false,"required":["origins"]}},"$schema":"http://json-schema.org/draft-07/schema#"} -------------------------------------------------------------------------------- /cli.Dockerfile: -------------------------------------------------------------------------------- 1 | # Use Debian instead of Alpine because of issues with Prisma & gcc 2 | 3 | FROM denoland/deno:debian-1.44.1 AS build 4 | # Required for installing Prisma dependencies 5 | RUN apt-get update \ 6 | && apt-get install -y nodejs npm unzip 7 | WORKDIR /app 8 | COPY . . 9 | RUN deno task cli:compile 10 | 11 | FROM denoland/deno:debian-1.44.1 12 | # Required for Git dependencies 13 | RUN apt-get update \ 14 | && apt-get install -y git \ 15 | && rm -rf /var/lib/apt/lists/* 16 | COPY --from=build /app/dist/cli /usr/bin/opengb 17 | ENV RUNNING_IN_DOCKER=1 18 | RUN VERBOSE=1 opengb _internal prewarm-prisma 19 | ENTRYPOINT ["/usr/bin/opengb"] 20 | 21 | -------------------------------------------------------------------------------- /deno.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | // Runs the CLI 4 | "cli:run": "deno task artifacts:build:all && deno task cli:run:dirty", 5 | "cli:run:dirty": "deno run -A --check src/cli/main.ts --path tests/basic", 6 | 7 | // Specific CLI tasks 8 | "cli:run:build": "deno task cli:run build", 9 | "cli:run:clean_build": "deno task artifacts:build:all && deno task cli:run:dirty clean && deno task cli:run build", 10 | 11 | // Compiles the CLI to a binary 12 | "cli:compile": "deno task artifacts:build:all && deno compile --check --allow-net --allow-read --allow-env --allow-run --allow-write --allow-sys --output dist/cli src/cli/main.ts", 13 | 14 | // Installs the CLI on the local machine 15 | "cli:install": "deno task artifacts:build:all && deno install --check --allow-net --allow-read --allow-env --allow-run --allow-write --allow-sys --name opengb --force src/cli/main.ts", 16 | 17 | // Build Dockerfile 18 | "docker:build": "docker build -t opengb -f cli.Dockerfile .", 19 | 20 | // Generates schema 21 | "artifacts:build:all": "deno task format && deno task artifacts:build:schema && deno task artifacts:build:runtime_archive && deno task artifacts:build:prisma_archive && deno task artifacts:build:dynamic_archive", 22 | "artifacts:build:schema": "deno run --allow-env --allow-read --allow-write src/artifacts/build_schema.ts", 23 | "artifacts:build:runtime_archive": "deno run --allow-env --allow-read --allow-write src/artifacts/build_runtime_archive.ts", 24 | "artifacts:build:prisma_archive": "deno run --allow-env --allow-read --allow-write --allow-run src/artifacts/build_prisma_archive.ts", 25 | "artifacts:build:dynamic_archive": "deno run --allow-env --allow-read --allow-write --allow-run src/artifacts/build_dynamic_archive.ts", 26 | 27 | // Format 28 | "format": "deno fmt src/", 29 | "format:check": "deno fmt --check src/", 30 | 31 | // Check 32 | "check": "deno task artifacts:build:all && deno check src/**/*.ts", 33 | 34 | // Lint 35 | "lint": "deno task artifacts:build:all && deno lint src/", 36 | "lint:fix": "deno task artifacts:build:all && deno lint --fix src/", 37 | 38 | // Runs tests 39 | "test:core": "deno task artifacts:build:all && deno test -A src/", 40 | "test:project": "deno task cli:run test --strict-schemas --force-deploy-migrations", 41 | "test:registry": "deno task artifacts:build:all && deno run -A --check src/cli/main.ts --path ../opengb-modules/tests/basic test --strict-schemas --force-deploy-migrations" 42 | }, 43 | "lint": { 44 | "include": ["src/"], 45 | "exclude": ["tests/"], 46 | "rules": { 47 | "exclude": ["no-empty-interface", "no-explicit-any", "require-await"] 48 | } 49 | }, 50 | "fmt": { 51 | "lineWidth": 120, 52 | "useTabs": true 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /docs-old/CONVENTIONS.md: -------------------------------------------------------------------------------- 1 | # TypeScript Conventions 2 | 3 | ## Use interfaces & functions, not classes 4 | 5 | Use exclusively interfaces & standalone functions internally. This keeps code 6 | clean, legible, and easy to refactor at the cost of slightly more verbosity. 7 | 8 | Examples: `Project`, `Registry`, `Module`, `Script` 9 | 10 | ## Only use classes for developer-facing APIs 11 | 12 | We don't expect the user to use pure interfaces & functions; consuming well 13 | designed OOP interfaces can be cleaner. 14 | 15 | Examples of using classes: `Context` and `Runtime` are classes designed to have 16 | a clean interface for the user. 17 | 18 | Examples of using interfaces: `Trace` is a simple data container that can be 19 | easily serialized & deserialized. There are standalone functions that can 20 | operate on a trace. 21 | 22 | ## Camel case + acryonyms 23 | 24 | Follow camel case strictly & treat acronyms as single words. 25 | 26 | Examples: 27 | 28 | - Prefer `OpenApi` insetad of `OpenAPI` 29 | - Prefer `Uuid` instead of `UUID` 30 | 31 | ## Uses of `id` included with type 32 | 33 | When referring to the ID of the current type, use `id`. When referring to a 34 | foreign type, use `{type name}Id`. 35 | 36 | Example: 37 | 38 | ```prisma 39 | model User { 40 | id String @id @default(uuid()) @db.Uuid 41 | posts Post[] 42 | } 43 | 44 | model Post { 45 | id String @id @default(uuid()) @db.Uuid 46 | userId String @db.Uuid 47 | user User @relation(fields: [userId], references: [id]) 48 | } 49 | ``` 50 | 51 | ## Externally tagged enums 52 | 53 | When representing an enum with associated data (often called "sum types" which are a kind of algebreic data type, ADT), represent using a nested object (often called "externally tagged enums"). 54 | 55 | This comes at the expense of not having exhaustive switch statements. 56 | 57 | Externally tagged enums are easy to represent in languages that don't support advanced type constraints, such as C# and most OpenAPI SDK generators (i.e. don't support `oneOf`). 58 | 59 | This example: 60 | 61 | ```typescript 62 | type MyEnum = { foo: MyEnumFoo } | { bar: MyEnumBar } | { baz: MyEnumBaz }; 63 | 64 | interface MyEnumFoo { 65 | 66 | } 67 | 68 | interface MyEnumBar { 69 | 70 | } 71 | 72 | interface MyEnumBaz { 73 | 74 | } 75 | ``` 76 | 77 | Can be represented in C# like this: 78 | 79 | ```cs 80 | class MyEnum { 81 | MyEnumFoo? Foo; 82 | MyEnumBar? Bar; 83 | MyEnumBaz? Baz; 84 | } 85 | 86 | class MyEnumFoo { 87 | } 88 | 89 | class MyEnumBar { 90 | } 91 | 92 | class MyEnumBaz { 93 | } 94 | ``` 95 | -------------------------------------------------------------------------------- /docs-old/ERROR_HANDLING.md: -------------------------------------------------------------------------------- 1 | # Error Handling 2 | 3 | ## Usage 4 | 5 | All errors that are thrown from modules should be defined in the `module.json`, 6 | like this: 7 | 8 | ```json 9 | { 10 | "errors": { 11 | "FOO": {}, 12 | "BAR": {} 13 | } 14 | } 15 | ``` 16 | 17 | The errors can then be thrown from within scripts like: 18 | 19 | ```typescript 20 | throw new RuntimeError("FOO"); 21 | ``` 22 | 23 | ## Motivation 24 | 25 | **HAndling errors** 26 | 27 | When calling external APIs, it's difficult to handle errors correctly. Some 28 | errors are meant to be recoverable (i.e. username not unique), others indicate 29 | an invalid state (i.e. not found), while others are unrecoverable (i.e. database 30 | errors). 31 | 32 | Enforcing each module to have concrete error tyeps makes it easier to explicitly 33 | handle different types of errors. 34 | 35 | **Documentation** 36 | 37 | One goal of Open Game Backend is to have high qulaity documentation, and that includes all 38 | errors. 39 | 40 | This error handling system is inspired by Stripe's well documented error types 41 | [here](https://stripe.com/docs/error-codes). 42 | -------------------------------------------------------------------------------- /docs-old/KNOWN_ISSUES.md: -------------------------------------------------------------------------------- 1 | # Known Issues 2 | 3 | ## `Record` produces JSON schema 4 | 5 | [typescript-json-schema#547](https://github.com/YousefED/typescript-json-schema/issues/547) 6 | 7 | Will be fixed with [#15](https://github.com/rivet-gg/opengb-engine/issues/15). 8 | 9 | ## Moving modules folder breaks imports 10 | 11 | ``` 12 | error: Module not found "file:///path/to/src/runtime/mod.ts". 13 | at file:///path/to/tests/basic/.opengb/entrypoint.ts:3:25 14 | ``` -------------------------------------------------------------------------------- /docs-old/MODULE_ISOLATION.md: -------------------------------------------------------------------------------- 1 | # Module Isolation 2 | 3 | ## Never touch another module's database 4 | -------------------------------------------------------------------------------- /docs-old/TIPS.md: -------------------------------------------------------------------------------- 1 | # Tips 2 | 3 | ## Validation annotations 4 | 5 | https://github.com/YousefED/typescript-json-schema?tab=readme-ov-file#annotations 6 | -------------------------------------------------------------------------------- /docs-old/WHY_PORT.md: -------------------------------------------------------------------------------- 1 | # Why port 6420? 2 | 3 | ## Unique 4 | 5 | We can't use port 8080 since it's commonly used by another program, like the game server that's being used alongside OpenGB. 6 | 7 | ## Numpad 8 | 9 | 642 = OGB (Open Game Backend) 10 | 11 | ## Easy to remember pattern 12 | 13 | ```rust 14 | (0..=6).rev().step_by(2) 15 | ``` 16 | 17 | -------------------------------------------------------------------------------- /docs-old/evaluations/CACHING.md: -------------------------------------------------------------------------------- 1 | # Caching 2 | 3 | **Goals** 4 | 5 | - 6 | 7 | **API surafce area** 8 | 9 | - Store value in cache 10 | - TTL 11 | - Purge global cache 12 | 13 | **Use cases** 14 | 15 | - Rate limiting 16 | - Caching 17 | 18 | **Other considerations** 19 | 20 | - Actors 21 | 22 | ## Cloudflare KV 23 | 24 | ## Cloudflare Cache API 25 | 26 | ## Upstash 27 | 28 | ## Redis 29 | -------------------------------------------------------------------------------- /docs-old/evaluations/DB.md: -------------------------------------------------------------------------------- 1 | # Database 2 | 3 | ## Requirements 4 | 5 | - Transactions 6 | - Handle high amount of writes 7 | - Indexes 8 | 9 | ## Postgres 10 | 11 | The future is Postgres. Everyone is building for Postgres. Always bet on 12 | Postgres. Don't reinvent the wheel, as tempting as it is. Don't mess with poorly 13 | licensed or closed source databases. Postgres provides the tooling you need. 14 | Postgres data will always be correct, handling corruption in NoSQL is not great. 15 | Neon has proven to scale. Postgres is great for prototyping and scaling. If 16 | you're at scale, there are many commercial services that can help you scale in 17 | in different methods (Citus, CockroachDB, etc). Prisma makes it easy to switch 18 | databases, so you can always switch to MySQL + PlanetScale or MongoDB for data 19 | that requires those use cases. Extensions make "can I do X" almost always a 20 | "yes." Postgres is more correct that MySQL. 21 | 22 | https://akorotkov.github.io/blog/2016/05/09/scalability-towards-millions-tps/ 23 | 24 | ## If scalability was a concern 25 | 26 | ### MongoDB 27 | 28 | ### DynamoDB & Cassandra 29 | 30 | ### PlanetScale/Vitess 31 | 32 | ## SQL vs KV 33 | 34 | ## SQL vs NoSQL (Cassandra/DynamoDB) 35 | 36 | ## SQL vs MongoDB 37 | 38 | ## New players 39 | 40 | ### SurrealDB 41 | 42 | ### FaunaDB 43 | 44 | ## More 45 | 46 | https://leerob.io/blog/backend 47 | -------------------------------------------------------------------------------- /docs-old/evaluations/DRIZZLE.md: -------------------------------------------------------------------------------- 1 | # Drizzle 2 | 3 | **License** 4 | 5 | Drizzle Kit is responsible for generating schemas for Drizzle, which is a core part of its functionality. 6 | 7 | Drizzle Kit is not open source, which prohibits OpenGB from using Drizzle entirely. 8 | 9 | **Querying** 10 | https://github.com/rivet-gg/opengb-engine/blob/823becd385ee3a1c6bd05172577a19441716eaa4/modules/users/scripts/get.ts#L18 11 | 12 | The `inArray` function either needs to be imported or used in this weird 13 | function interface. Intellisense isn't helpful here, you have to read the docs 14 | to understand this. 15 | 16 | Ideally this looks like sort of like: 17 | 18 | ``` 19 | await ctx.db.users.select().where({ id: { inArray: xxx } }) 20 | ``` 21 | 22 | **Inserting** 23 | https://github.com/rivet-gg/opengb-engine/blob/823becd385ee3a1c6bd05172577a19441716eaa4/modules/users/scripts/register.ts#L27 24 | 25 | You have to pass the schema in to the `.insert(` command, which is confusing 26 | compared to the `db.query`. I would not be able to figure this out without an 27 | example. 28 | 29 | Ideally this looks sort of like: 30 | 31 | ``` 32 | await ctx.db.users.insert({ username: "abc123" }) 33 | ``` 34 | 35 | **Migrations** 36 | 37 | The Drizzle way of auto-generating migrations incrementally is pretty slick, 38 | I'll give them that. 39 | 40 | But writing the migrations is nails on a chalkboard: 41 | https://github.com/rivet-gg/opengb-engine/blob/823becd385ee3a1c6bd05172577a19441716eaa4/modules/users/db/schema.ts#L4 42 | 43 | Need to read docs to know what to insert, default is not immediately obvious, 44 | references functions are wonky. To be clear, all of this is clean if you're a 45 | TypeScript native, but not if you're not well versed with backends. 46 | 47 | --- 48 | 49 | If I were writing something in TypeScript that didn't need external 50 | contributors, I'd probably go with Drizzle. It's really neat. But I don't think 51 | it's right for this project. 52 | 53 | -------------------------------------------------------------------------------- /docs-old/evaluations/MIDDLEWARE_BRAINSTORM.md: -------------------------------------------------------------------------------- 1 | # Middleware brainstorm 2 | 3 | ## Goals 4 | 5 | - type safe middlewares 6 | - not import module directly 7 | - future proofing 8 | - integrate in module.json 9 | - allow running middleware on remote functions without them knowing? 10 | 11 | ## Option A: integrate with request response 12 | 13 | pros: 14 | 15 | - simple, works with existing type systems 16 | 17 | cons: 18 | 19 | - error prone for user spoofing 20 | 21 | ## Option B: middleware context as any 22 | 23 | ## Option C: call module directly 24 | 25 | pros: 26 | 27 | - simple 28 | 29 | cons: 30 | 31 | - does not allow for verifying a rate limit is enabled on everything 32 | -------------------------------------------------------------------------------- /docs-old/getting-started/FOR_GODOT.md: -------------------------------------------------------------------------------- 1 | TODO 2 | -------------------------------------------------------------------------------- /docs-old/getting-started/FOR_UNITY.md: -------------------------------------------------------------------------------- 1 | TODO 2 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/macos 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=macos 3 | 4 | ### macOS ### 5 | # General 6 | .DS_Store 7 | .AppleDouble 8 | .LSOverride 9 | 10 | # Icon must end with two \r 11 | Icon 12 | 13 | 14 | # Thumbnails 15 | ._* 16 | 17 | # Files that might appear in the root of a volume 18 | .DocumentRevisions-V100 19 | .fseventsd 20 | .Spotlight-V100 21 | .TemporaryItems 22 | .Trashes 23 | .VolumeIcon.icns 24 | .com.apple.timemachine.donotpresent 25 | 26 | # Directories potentially created on remote AFP share 27 | .AppleDB 28 | .AppleDesktop 29 | Network Trash Folder 30 | Temporary Items 31 | .apdisk 32 | 33 | ### macOS Patch ### 34 | # iCloud generated files 35 | *.icloud 36 | 37 | # End of https://www.toptal.com/developers/gitignore/api/macos -------------------------------------------------------------------------------- /docs/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.enableFiletypes": [ 3 | "mdx" 4 | ] 5 | } -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Open Game Backend Documentation 2 | 3 | ### Development 4 | 5 | Install the [Mintlify CLI](https://www.npmjs.com/package/mintlify) to preview the documentation changes locally. To install, use the following command 6 | 7 | ``` 8 | npm i -g mintlify 9 | ``` 10 | 11 | Run the following command at the root of your documentation (where mint.json is) 12 | 13 | ``` 14 | mintlify dev 15 | ``` 16 | 17 | ### Publishing Changes 18 | 19 | Changes will be deployed to production automatically after pushing to the default branch. 20 | 21 | #### Troubleshooting 22 | 23 | - Mintlify dev isn't running - Run `mintlify install` it'll re-install dependencies. 24 | - Page loads as a 404 - Make sure you are running in a folder with `mint.json` 25 | -------------------------------------------------------------------------------- /docs/_internal/templates/example_private.mdx: -------------------------------------------------------------------------------- 1 | ```typescript OpenGB Script 2 | const data = await ctx.modules.%%MODULE_NAME_CAMEL%%.%%NAME_CAMEL%%({ 3 | // Request body 4 | }); 5 | ``` 6 | -------------------------------------------------------------------------------- /docs/_internal/templates/module_overview.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "%%DISPLAY_NAME%%" 3 | description: "%%DESCRIPTION%%" 4 | sidebarTitle: Overview 5 | --- 6 | 7 | | Source | Name | Status | Database | 8 | | -------------------------------------------------------------------------------- | ---------- | ---------- | ------------ | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/%%NAME%%) | `%%NAME%%` | %%STATUS%% | %%DATABASE%% | 10 | 11 | **Authors** 12 | 13 | %%AUTHORS%% 14 | 15 | **Dependencies** 16 | 17 | %%DEPENDENCIES%% 18 | 19 | ## Installation 20 | 21 | 22 | 23 | 24 | 25 | ```sh CLI 26 | opengb module add %%NAME%% 27 | ``` 28 | 29 | 30 | 31 | 32 | 33 | Add the following to your `backend.json`: 34 | 35 | ```json backend.json 36 | %%INSTALL_CONFIG%% 37 | ``` 38 | 39 | 40 | 41 | 42 | %%CONFIG_SCHEMA%% 43 | 44 | ## Scripts 45 | 46 | ### Public 47 | 48 | %%SCRIPTS_PUBLIC%% 49 | 50 | ### Internal 51 | 52 | %%SCRIPTS_INTERNAL%% 53 | 54 | ## Errors 55 | 56 | %%ERRORS%% 57 | -------------------------------------------------------------------------------- /docs/_internal/templates/modules_overview.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: All Modules 3 | --- 4 | 5 | import { ModuleCards } from "/snippets/module-cards.mdx"; 6 | import { Tags } from "/snippets/tags.mdx"; 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/_internal/templates/script.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "%%DISPLAY_NAME%%" 3 | description: "%%DESCRIPTION%%" 4 | sidebarTitle: "%%DISPLAY_NAME%%" 5 | --- 6 | 7 | | Source | Name | Public | 8 | | ----------------------------------------------------------------------------------------------------------- | ---------- | ---------- | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/%%MODULE_NAME%%/scripts/%%NAME%%.ts) | `%%NAME%%` | %%PUBLIC%% | 10 | 11 | %%REQUEST_EXAMPLES%% 12 | 13 | ## Request 14 | 15 | ```typescript 16 | %%REQUEST_SCHEMA%% 17 | ``` 18 | 19 | ## Response 20 | 21 | ```typescript 22 | %%RESPONSE_SCHEMA%% 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/build/config.ts: -------------------------------------------------------------------------------- 1 | --- 2 | title: User-Provided Config (config.ts) 3 | --- 4 | 5 | 6 | Documentation coming very soon! 7 | 8 | -------------------------------------------------------------------------------- /docs/build/conventions.mdx: -------------------------------------------------------------------------------- 1 | 2 | Documentation coming very soon! 3 | -------------------------------------------------------------------------------- /docs/build/database.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Database 3 | --- 4 | 5 | 6 | Documentation coming very soon! 7 | 8 | 9 | {/* TODO: Advanced migration management */} 10 | -------------------------------------------------------------------------------- /docs/build/errors.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Errors 3 | --- 4 | 5 | 6 | Documentation coming very soon! 7 | 8 | -------------------------------------------------------------------------------- /docs/build/ide.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: IDE Support 3 | --- 4 | 5 | ## Overview 6 | 7 | Open Game Backend requires your IDE to support TypeScript & Deno. All major IDEs support this. 8 | 9 | ## Visual Studio Code 10 | 11 | [Install Deno Plugin](https://marketplace.visualstudio.com/items?itemName=denoland.vscode-deno) 12 | 13 | [More info](https://docs.deno.com/runtime/manual/getting_started/setup_your_environment#visual-studio-code) 14 | 15 | ## JetBrains 16 | 17 | [Install Deno Plugin](https://plugins.jetbrains.com/plugin/14382-deno) 18 | 19 | [More info](https://docs.deno.com/runtime/manual/getting_started/setup_your_environment#jetbrains-ides) 20 | 21 | ## Neovim (`nvim-lsp-config`) 22 | 23 | [More info](https://docs.deno.com/runtime/manual/getting_started/setup_your_environment#neovim-06-using-the-built-in-language-server) 24 | 25 | ## Sublime Text 26 | 27 | [LSP plugin](https://lsp.sublimetext.io/language_servers/#deno) 28 | 29 | [TypeScript plugin](https://packagecontrol.io/packages/TypeScript) 30 | 31 | [More info](https://docs.deno.com/runtime/manual/getting_started/setup_your_environment#sublime-text) 32 | 33 | ## More options 34 | 35 | See the full list of IDEs that support Deno [here](https://docs.deno.com/runtime/manual/getting_started/setup_your_environment). 36 | -------------------------------------------------------------------------------- /docs/build/module-config.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Config (module.json)" 3 | --- 4 | 5 | This documentation page is a work in progress. 6 | 7 | ```typescript 8 | export interface Config { 9 | status?: "beta" | "end_of_life" | "maintenance" | "preview" | "stable"; 10 | /** 11 | * The human readable name of the module. 12 | */ 13 | name?: string; 14 | /** 15 | * A short description of the module. 16 | */ 17 | description?: string; 18 | /** 19 | * The [Font Awesome](https://fontawesome.com/icons) icon name of the module. 20 | */ 21 | icon?: string; 22 | /** 23 | * The tags associated with this module. 24 | */ 25 | tags?: string[]; 26 | /** 27 | * The GitHub handle of the authors of the module. 28 | */ 29 | authors?: string[]; 30 | scripts: { 31 | [k: string]: ScriptConfig; 32 | }; 33 | actors?: { 34 | [k: string]: ActorConfig; 35 | }; 36 | errors: { 37 | [k: string]: ErrorConfig; 38 | }; 39 | dependencies?: { 40 | [k: string]: DependencyConfig; 41 | }; 42 | /** 43 | * Default user config. 44 | * 45 | * The user-provided config will be deep merged with this config. 46 | */ 47 | defaultConfig?: { 48 | [k: string]: any; 49 | }; 50 | } 51 | export interface ScriptConfig { 52 | /** 53 | * The human readable name of the script. 54 | */ 55 | name?: string; 56 | /** 57 | * A short description of the script. 58 | */ 59 | description?: string; 60 | /** 61 | * If the script can be called from the public HTTP interface. 62 | * 63 | * If enabled, ensure that authentication & rate limits are configured for 64 | * this endpoints. See the `user` and `rate_limit` modules. 65 | */ 66 | public?: boolean; 67 | } 68 | export interface ActorConfig { 69 | /** 70 | * A globally unique string for storing data for this actor. 71 | * 72 | * **IMPORTANT** Changing this will effectively unlink all data stored in this actor. Changing it back to 73 | * the old value will restore the data. 74 | */ 75 | storage_id: string; 76 | } 77 | export interface ErrorConfig { 78 | /** 79 | * The human readable name of the error. 80 | */ 81 | name?: string; 82 | /** 83 | * A short description of the error. 84 | */ 85 | description?: string; 86 | } 87 | export interface DependencyConfig {} 88 | ``` 89 | -------------------------------------------------------------------------------- /docs/build/overview.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Overview 3 | --- 4 | 5 | Open Game Backend is built from the ground up to enable as many game developers as possible to write backend code & modify existing modules. 6 | 7 | ![Structure](/images/structure.svg) 8 | 9 | ## Components 10 | 11 | Modules have access to the following core components: 12 | 13 | - [**Scripts**](/build/scripts) Scripts are used to write TypeScript & [Deno](https://deno.com/) code that runs on the backend. Request & response types are automatically validated. 14 | - [**Database**](/build/database) Modules can write database schemas, query data from scripts with type safety, and automatically create + migrate databases. 15 | - [**User-Provided Config**](/build/config) Modules can allow projects to pass configuration options to the module. 16 | - [**Errors**](/build/errors) Modules specify the types of errors that can occur in their scripts. 17 | 18 | ## File structure 19 | 20 | **Required** 21 | 22 | - `module.json` Module configuration file. [Documentation](/build/module-config) 23 | - `scripts/` 24 | - `*.ts` Script to run on the backend. [Documentation](/build/scripts) 25 | - `db/` _(optional)_ Database-related files. [Documentation](/build/database) 26 | - `schema.prisma` Database schema. 27 | - `migrations/` Database migrations auto-generated from `schema.prisma`. This directory should not be manually modified unless you know what you are doing. 28 | - `config.ts` _(optional)_ Defines user-provided config schema. [Documentation](/build/config) 29 | - `public.ts` _(optional)_ Exports shared functions & types across modules. [Documentation](/build/public) 30 | -------------------------------------------------------------------------------- /docs/build/public.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Public (public.ts) 3 | --- 4 | 5 | 6 | Documentation coming very soon! 7 | 8 | -------------------------------------------------------------------------------- /docs/build/registry.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Publish to Registry 3 | --- 4 | 5 | 6 | Documentation coming very soon! 7 | 8 | -------------------------------------------------------------------------------- /docs/build/scripts.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Script 3 | --- 4 | 5 | 6 | Documentation coming very soon! 7 | 8 | -------------------------------------------------------------------------------- /docs/comparison/pragma.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: AccelByte 3 | --- 4 | 5 | 6 | Please read Open Game Backend's [roadmap](/concepts/roadmap) for known limitations. 7 | 8 | 9 | [Pragma](https://pragma.gg/) was founded in 202. Both platforms have similar limitations, so we'll compare them in the same document. 10 | 11 | ## What Pragma does well 12 | 13 | - Source-disclosed engine 14 | 15 | ## How Pragma and Open Game Backend compare 16 | 17 | ### Source-discord vs open-source 18 | 19 | ### Prefab backend vs modular backend 20 | 21 | Pragma provides a predefined set of features out of the box. While you can modify Pragma to extend it with your own features, you must have a license to the engine 22 | 23 | Nakama encourages developers to [use scripting](https://heroiclabs.com/docs/nakama/server-framework/introduction/index.html) to extend the server. While the scripting is extensive, it's not intended for all use cases. 24 | 25 | This means Nakama has first-class services written in Go and asks you to write second-class code written in a scripting language without access to a mature database. 26 | 27 | Open Game Backend is designed from the ground up to be modular at its core. It provides a set of official modules to get you started quickly, but the primary use case is to extend the server with your own modules. All modules are first-class citizens have access to a mature database. 28 | -------------------------------------------------------------------------------- /docs/concepts/cli.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CLI 3 | --- 4 | 5 | ## Start development server 6 | 7 | ``` 8 | opengb dev 9 | ``` 10 | 11 | This will automatically restart when files change. 12 | 13 | 14 | You can configure the database url using the `DATABASE_URL` env var. This will 15 | default to `postgres://postgres:password@localhost:5432/postgres` 16 | 17 | 18 | ## Run tests 19 | 20 | ``` 21 | opengb test 22 | ``` 23 | 24 | To automatically rerun tests when files change, run: 25 | 26 | ``` 27 | opengb test --watch 28 | ``` 29 | 30 | To test a specific file: 31 | 32 | ``` 33 | opengb test foo 34 | ``` 35 | 36 | ## Creating modules & scripts 37 | 38 | Create module: 39 | 40 | ``` 41 | opengb create module foo 42 | ``` 43 | 44 | Create script: 45 | 46 | ``` 47 | opengb create script foo bar 48 | ``` 49 | 50 | ## Generate SDKs 51 | 52 | ``` 53 | opengb sdk generate --output 54 | ``` 55 | 56 | ## OpenAPI & Postman/Insomnia/Paw 57 | 58 | Explore the APIs by opening `.opengb/openapi.json` in Postman/Insomnia/Paw. 59 | 60 | ## Help 61 | 62 | All commands are documented in the CLI: 63 | 64 | ``` 65 | opengb --help 66 | opengb create --help 67 | ``` -------------------------------------------------------------------------------- /docs/concepts/enterprise-support.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Enterprise Support 3 | --- 4 | 5 | The following companies provide enterprise support & custom modules for Open Game Backend: 6 | 7 | - [Rivet](https://rivet.gg/support) 8 | - _Create a PR to list your services for Open Game Backend_ 9 | -------------------------------------------------------------------------------- /docs/concepts/modules.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Modules 3 | --- 4 | 5 | ## What are modules? 6 | 7 | Modules are pieces of backend functionality that can be combined together to build & customize your game's backend. 8 | 9 | Modules include: 10 | 11 | - **Scripts** that implement the logic. 12 | - **Database** schemas to store persistent data. 13 | - **User configuration** to let you configure settings on the module. 14 | - **Explicit errors** to make it easy to understand & recover from errors returned from the module. 15 | 16 | You can think of modules like an NPM package/Unity package/Unreal plugin/Maven artifact/Rust crate but with a persistence layer and a standard + well-documented way of using it. 17 | 18 | ## How do I install modules? 19 | 20 | To install a module from the default registry, you have two options. 21 | 22 | ### CLI 23 | 24 | You can install the module from the CLI by running the following command: 25 | 26 | ```sh 27 | opengb module add my_module 28 | ``` 29 | 30 | This will update your `backend.json` file accordingly. 31 | 32 | ### `backend.json` 33 | 34 | You can also manually add the module to your `backend.json` file manually, like this: 35 | 36 | ```json 37 | { 38 | "modules": { 39 | "my_module": { 40 | "config": { 41 | // Your config here 42 | } 43 | } 44 | } 45 | } 46 | ``` 47 | 48 | ## Registries 49 | 50 | Modules come from registries. OpenGB provides a default registry with all of the modules available on this website. You can specify or host your own registries easily. Read more [here](/concepts/registries). -------------------------------------------------------------------------------- /docs/concepts/multiple-games.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Sharing a backend across multiple games" 3 | --- 4 | 5 | ## Motivation 6 | 7 | It's common for studios to have multiple games and want to share backend data across them. For example, a studio might want to share user accounts & friends across games, but keep game-specific data like leaderboards & achievements separate. 8 | 9 | ## Multiple module instances 10 | 11 | If we have a `leaderboard` module and we want to run multiple instances of it for the games `foo` and `bar`, we can use module aliases to create multiple instances of the module. 12 | 13 | ```json 14 | { 15 | "modules": { 16 | // Users module is shared across all games 17 | "users": {}, 18 | // Leaderboard for game `food` 19 | "foo_leaderboard": { 20 | "module": "leaderboard" 21 | }, 22 | // Leaderboard for game `bar` 23 | "bar_leaderboard": { 24 | "module": "leaderboard" 25 | } 26 | } 27 | } 28 | ``` 29 | 30 | This will create an isolated database for each game's leaderboard. 31 | 32 | To get the scores from the `foo` leaderboard, we can use the `foo_leaderboard` module: 33 | 34 | ```js 35 | const fooScores = await ctx.modules.fooLeaderboard.getScores(); 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/concepts/project-config.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Config (backend.json)" 3 | --- 4 | 5 | This documentation page is a work in progress. 6 | 7 | ```typescript 8 | export type RegistryConfig = 9 | | { 10 | local: RegistryConfigLocal; 11 | } 12 | | { 13 | git: RegistryConfigGit; 14 | }; 15 | export type RegistryConfigGit = 16 | | { 17 | url: RegistryConfigGitUrl; 18 | directory?: string; 19 | branch: string; 20 | } 21 | | { 22 | url: RegistryConfigGitUrl; 23 | directory?: string; 24 | tag: string; 25 | } 26 | | { 27 | url: RegistryConfigGitUrl; 28 | directory?: string; 29 | rev: string; 30 | }; 31 | /** 32 | * The URL to the git repository. 33 | * 34 | * If both HTTPS and SSH URL are provided, they will both be tried and use the 35 | * one that works. 36 | */ 37 | export type RegistryConfigGitUrl = 38 | | { 39 | https?: string; 40 | ssh?: string; 41 | } 42 | | string; 43 | 44 | export interface Config { 45 | registries: { 46 | [k: string]: RegistryConfig; 47 | }; 48 | modules: { 49 | [k: string]: ProjectModuleConfig; 50 | }; 51 | runtime?: RuntimeConfig; 52 | } 53 | export interface RegistryConfigLocal { 54 | directory: string; 55 | /** 56 | * If true, this will be treated like an external registry. This is 57 | * important if multiple projects are using the same registry locally. 58 | * 59 | * Modules from this directory will not be tested, formatted, linted, and 60 | * generate Prisma migrations. 61 | */ 62 | isExternal?: boolean; 63 | } 64 | export interface ProjectModuleConfig { 65 | /** 66 | * The name of the registry to fetch the module from. 67 | */ 68 | registry?: string; 69 | /** 70 | * Overrides the name of the module to fetch inside the registry. 71 | */ 72 | module?: string; 73 | /** 74 | * The config that configures how this module is ran at runtime. 75 | */ 76 | config?: { 77 | [k: string]: any; 78 | }; 79 | } 80 | export interface RuntimeConfig { 81 | cors?: CorsConfig; 82 | } 83 | /** 84 | * If this is null, only requests made from the same origin will be accepted 85 | */ 86 | export interface CorsConfig { 87 | /** 88 | * The origins that are allowed to make requests to the server. 89 | */ 90 | origins: string[]; 91 | } 92 | ``` 93 | -------------------------------------------------------------------------------- /docs/concepts/roadmap.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Roadmap" 3 | --- 4 | 5 | 6 | This document is intended as a rough sketch of what to expect from Open Game Backend in the near future. 7 | 8 | We ship fast and we ship often. Stay up to date in our [Discord](https://rivet.gg/discord) and subscribe to relevant [GitHub Issues](https://github.com/rivet-gg/opengb-modules/issues) to get notified. 9 | 10 | 11 | ## Engine 12 | 13 | - Realtime events 14 | - Stateful actors 15 | - SDKs 16 | - Godot 17 | - Unreal 18 | - Unity 19 | - Rust 20 | - WASM 21 | 22 | ## Modules 23 | 24 | - Achievements ([get notified](https://github.com/rivet-gg/opengb-modules/issues/4)) 25 | - Admin portal 26 | - Analytics ([get notified](https://github.com/rivet-gg/opengb-modules/issues/16)) 27 | - Chat ([get notified](https://github.com/rivet-gg/opengb-modules/issues/23)) 28 | - Cloud saves 29 | - Groups/guilds/clans ([get notified](https://github.com/rivet-gg/opengb-modules/issues/19)) 30 | - IAP 31 | - Inventory 32 | - Leaderboards ([get notified](https://github.com/rivet-gg/opengb-modules/issues/3)) 33 | - Notifications 34 | - Party ([get notified](https://github.com/rivet-gg/opengb-modules/issues/5)) 35 | - Player portal 36 | - Tournament 37 | 38 | ## Misc 39 | 40 | - GUI editor 41 | - Auto-generated docs for custom registries ([see here](https://github.com/rivet-gg/opengb-docs/blob/main/_internal/scripts/generate.ts)) 42 | -------------------------------------------------------------------------------- /docs/concepts/sdk.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: SDK Generation 3 | --- 4 | 5 | Open Game Backend generates a unique SDK for your backend with type-safe methods to call your scripts. 6 | 7 | ## Setup 8 | 9 | Godot, Unity, Unreal, and Rust SDKs coming soon. 10 | 11 | ### JavaScript & TypeScript 12 | 13 | 14 | 15 | 16 | Replace `` with the path where you want to generate the SDK. 17 | 18 | ```sh 19 | opengb sdk generate --output typescript 20 | ``` 21 | 22 | 23 | 24 | 25 | Replace `./path/to/sdk` with the path to the generated SDK. 26 | 27 | 28 | 29 | ```sh npm 30 | npm install ./path/to/sdk 31 | ``` 32 | 33 | ```sh yarn 34 | yarn add ./path/to/sdk 35 | ``` 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | ```typescript 44 | import { BackendApi } from "opengb-sdk"; 45 | const backend = new BackendApi({}); 46 | ``` 47 | 48 | 49 | The default SDK connects to `http://localhost:8080` by default (where `opengb dev` runs). See [#255](https://github.com/rivet-gg/opengb/issues/255). 50 | 51 | 52 | 53 | 54 | 55 | 56 | ```typescript 57 | const data = await backend.callUsersGetUser({ userIds }); 58 | console.log("Users", data.users); 59 | ``` 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /docs/concepts/self-hosting.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Self-Hosting 3 | --- 4 | 5 | # Self-Hosting 6 | 7 | This documentation page is a work in progress. 8 | 9 | ## Overview 10 | 11 | This guide will walk you through self-hosting Open Game Backend on a virtual machine. 12 | 13 | TODO: explain this is going to run everything on a single vm, suitable for hobby deployments 14 | 15 | ## Prerequisites 16 | 17 | - Linux VM with Debian 11 18 | - You can rent a VM from Linode, Digital Ocean, or AWS 19 | - [Deno](https://docs.deno.com/runtime/manual/getting_started/installation) 20 | - [Docker](https://docs.docker.com/get-docker/) 21 | - Git 22 | 23 | This guide works with most Linux distributions, but is not tested. Reach out on [Discord](https://rivet.gg/discord) if you have questions. 24 | 25 | ## Upload To Server 26 | 27 | TODO: demonstrate using git clone of their own repo 28 | 29 | ## Configure firewall 30 | 31 | Open port 80 32 | 33 | ## Write Dockerfile 34 | 35 | TODO: not using opengb dev for prod 36 | 37 | ```dockerfile 38 | # TODO: Need dockerfile for opengb 39 | - from debian 40 | - install opengb (pin version) 41 | - opengb build 42 | - set cmd to `deno run .opengb/entrypoint.ts` 43 | ``` 44 | 45 | ## Write Docker Compose 46 | 47 | TODO: quickly explain how this works 48 | 49 | ```yaml docker-compose.yaml 50 | # TODO: Not tested 51 | services: 52 | opengb: 53 | build: . 54 | restart: always 55 | ports: 56 | - "80:8000" 57 | environment: 58 | - DATABASE_URL=postgres://postgres:password@db:5432/ 59 | depends_on: 60 | db: 61 | condition: service_healthy 62 | 63 | db-migrate: 64 | build: . 65 | entrypoint: ["opengb", "db", "deploy"] 66 | environment: 67 | - DATABASE_URL=postgres://postgres:password@db:5432/ 68 | depends_on: 69 | db: 70 | condition: service_healthy 71 | 72 | db: 73 | image: postgres 74 | restart: always 75 | environment: 76 | POSTGRES_PASSWORD: password 77 | healthcheck: 78 | test: ["CMD-SHELL", "pg_isready -U postgres"] 79 | interval: 1s 80 | timeout: 5s 81 | retries: 10 82 | ``` 83 | 84 | ## Up 85 | 86 | ``` 87 | docker-compose up -d 88 | ``` 89 | 90 | ``` 91 | docker-compose logs 92 | ``` 93 | 94 | ## Next Steps 95 | 96 | - Set up SSL 97 | - Secure Postgres database credentials 98 | - Monitor with docker-compose logs 99 | -------------------------------------------------------------------------------- /docs/concepts/structure.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Structure 3 | --- 4 | -------------------------------------------------------------------------------- /docs/concepts/visual-sql-client.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Visual SQL Client 3 | --- 4 | 5 | ## Visual SQL Client 6 | 7 | 8 | Coming soon 9 | 10 | -------------------------------------------------------------------------------- /docs/engine/design/deno.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Why Deno? 3 | --- 4 | 5 | For simplicity, we'll assume that we're comparing Deno to Node.js. To read more on why Open Game Backend chose TypeScript as a language, see [here](/engine/design/typescript). 6 | 7 | ## Portability 8 | 9 | Unlike Node.js which uses CommonJS modules with a heavy standard library, Deno provides an absolutely minimal standard library and relies instead on ECMAScript modules which are very portable. ECMAScript modules are the standard way of managing JavaScript modules across multiple JS runtimes & browsers. 10 | 11 | This means that code written for Deno is usually very portable. Node modules can still be used easily via something like [ESM](https://esm.sh/), but they way they're used is more portable. 12 | 13 | ### Portable code 14 | 15 | This is important because it means that code written for Open Game Backend modules are very portable, instead of being built around a specific runtime (such as Node.js). 16 | 17 | Because of this, adding support for a runtime like [Bun](https://bun.sh/) would be very easy. 18 | 19 | ### Serverless Runtimes 20 | 21 | Open Game Backend aims to be as portable as possible, and this means that it should be able to run on any serverless runtime, such as Deno Deploy, Cloudflare Workers, Vercel, and Netlify. 22 | 23 | ## Tooling 24 | 25 | Open Game Backend was initiated by a bunch of Rust nerds who value the quality tooling (e.g. Cargo, Crates, Clippy, Rustfmt, RA, etc.) provided around the language. 26 | 27 | Deno aims to provide the same quality of tooling (tests, documentation, formatting, linting, package managers, etc) but for TypeScript, which provides faster iteration speeds and is accessible to a wider audience than Rust. If you've used Rust before, you'll notice a lot of similarities to the Rust ecosystem in the tooling provided by Deno. 28 | 29 | ## Easy installer 30 | 31 | Deno makes it very easy to install the Open Game Backend using the `deno install` command. 32 | -------------------------------------------------------------------------------- /docs/engine/design/prisma.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Why Prisma? 3 | --- 4 | 5 | ## Prisma vs vanilla SQL 6 | 7 | Open Game Backend is designed to use an ORM to be more friendly for game developers & provide type safety out to the database of the box. 8 | 9 | Vanilla SQL requires a lot of repetition between redefining the model in TypeScript & selecting columns to query. It's very easy to write hard to find bugs using vanilla SQL because of this repetition. 10 | 11 | ## Prisma vs Drizzle 12 | 13 | The original proof of concept of Open Game Backend was built using Drizzle. 14 | 15 | However, one core goal of Open Game Backend is to make using SQL databases easy for game devs who have no experience with SQL. Drizzle is explicitly designed for developers who know SQL well, which makes it difficult to pick up. 16 | 17 | Prisma is built primarily to make using SQL databases easy for developers who don't know SQL, while still providing a powerful API for developers who do know SQL. The API is easy to understand, schemas are easy to write, migrations are buit to be fool-proof, and the entire API is intellisense friendly so you rarely have to read the docs. 18 | 19 | While Drizzle does have a [Queries API](https://orm.drizzle.team/docs/rqb) intended to work similar to Prisma, designing schemas is still done in an SQL-like manner. 20 | 21 | ## Prisma myths 22 | 23 | ### Interactive transactions 24 | 25 | Prisma was late to support interactive database transactions, but they do exist now. See [here](https://www.prisma.io/docs/orm/prisma-client/queries/transactions#interactive-transactions-1). 26 | 27 | ## Prisma gripes 28 | 29 | Prisma is not perfect, so Open Game Backend needs to do some work to make Prisma play nice with the rest of the stack. 30 | 31 | ### Unnecessary `SELECT` statement after `create` 32 | 33 | See [this issue](https://github.com/prisma/prisma/issues/4246). 34 | 35 | This has a pretty nasty performance impact when inserting rows frequently. Most use cases won't notice the impact, but it's important to be aware of for performance-sensitive modules. 36 | 37 | ### Heavy WASM dependency 38 | 39 | Prisma depends ona WASM module in order to serialize & deserialize database queries. This is intended to make the client faster, but it makes Prisma (a) harder to run in serverless with larger bundles and (b) requires finnessing to reuse the same WASM engine across multiple generated libraries. 40 | 41 | ### No headless migrations CLI 42 | 43 | Prisma requires a TTY to run development migrations. It took a lot of work to get the Prisma CLI to play nice with the Open Game Backend `dev` command. It would be nice to have a raw API we can call to run migrations within our own code. 44 | 45 | ### Slow builds 46 | 47 | Prisma takes a long time to build new clients. Open Game Backend caches heavily and parallelizes builds to make this less of an issue, but it's still a pain point. 48 | -------------------------------------------------------------------------------- /docs/engine/design/registries-dependencies.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Registries & Dependencies 3 | --- 4 | 5 | ## Why do we pull source code instead of bundling modules? 6 | 7 | The OpenGB dev experience encourages developers to view & fork the source code of other modules. By downloading the original source files, developers can jump to the source code of their dependencies from their IDE. 8 | 9 | Downloading a bundle would require developers to manually find the repository & ref of their dependencies on GitHub. 10 | 11 | -------------------------------------------------------------------------------- /docs/engine/introduction.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | --- 4 | 5 | 6 | Documentation coming very soon! 7 | 8 | -------------------------------------------------------------------------------- /docs/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/images/hero-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinevue/game-backend/eeda24d105336cb4d64ab515ee2064f6cf714994/docs/images/hero-dark.png -------------------------------------------------------------------------------- /docs/images/hero-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinevue/game-backend/eeda24d105336cb4d64ab515ee2064f6cf714994/docs/images/hero-light.png -------------------------------------------------------------------------------- /docs/images/overview.d2: -------------------------------------------------------------------------------- 1 | direction: down 2 | 3 | grid-columns: 2 4 | grid-gap: 300 5 | 6 | local: { 7 | label: Running on your local computer 8 | grid-columns: 1 9 | style: { 10 | stroke-dash: 3 11 | stroke: gray 12 | fill: transparent 13 | font-size: 16 14 | italic: true 15 | } 16 | 17 | client: { 18 | label: Your Game 19 | 20 | grid-columns: 2 21 | grid-gap: 15 22 | 23 | sdk: {label: OpenGB SDK} 24 | } 25 | } 26 | 27 | cloud: { 28 | label: Running in Rivet Cloud or self-hosted 29 | grid-columns: 1 30 | style: { 31 | stroke-dash: 3 32 | stroke: gray 33 | fill: transparent 34 | font-size: 16 35 | italic: true 36 | } 37 | 38 | backend: { 39 | label: Open Game Backend 40 | grid-columns: 3 41 | grid-gap: 20 42 | 43 | a: { 44 | label: Module 45 | } 46 | 47 | b: { 48 | label: Module 49 | } 50 | 51 | c: { 52 | label: Module 53 | } 54 | } 55 | } 56 | 57 | local.client.sdk -> cloud.backend: backend.callMyScript() { 58 | style.font: mono 59 | } 60 | -------------------------------------------------------------------------------- /docs/images/structure.d2: -------------------------------------------------------------------------------- 1 | direction: right 2 | 3 | client: { 4 | label: Game 5 | 6 | grid-columns: 2 7 | grid-gap: 15 8 | 9 | sdk: {label: OpenGB SDK} 10 | } 11 | 12 | backend: { 13 | label: Open Game Backend 14 | grid-columns: 3 15 | grid-gap: 20 16 | 17 | a: { 18 | label: Module 19 | grid-rows: 2 20 | grid-gap: 15 21 | 22 | a: {label: "Script"} 23 | b: {label: "Script"} 24 | c: {label: "Script"} 25 | 26 | "Database".width: 200 27 | } 28 | b: { 29 | label: Module 30 | grid-rows: 2 31 | grid-gap: 15 32 | 33 | a: {label: "Script"} 34 | b: {label: "Script"} 35 | c: {label: "Script"} 36 | 37 | "Database".width: 200 38 | } 39 | 40 | c: { 41 | label: Module 42 | grid-rows: 2 43 | grid-gap: 15 44 | 45 | a: {label: "Script"} 46 | b: {label: "Script"} 47 | c: {label: "Script"} 48 | 49 | "Database".width: 200 50 | } 51 | } 52 | 53 | client.sdk -> backend -------------------------------------------------------------------------------- /docs/integrations/infisical.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Infisical 3 | description: Store secrets using Infisical 4 | --- 5 | 6 | Infisical is an easy to use tool for managing secrets. This is helpful for when dealing with API keys in development. 7 | 8 | 9 | 10 | [Documentation](https://infisical.com/docs/cli/overview) 11 | 12 | 13 | ```sh 14 | infisical login 15 | ``` 16 | 17 | 18 | [Documentation](https://infisical.com/docs/documentation/platform/project#secrets) 19 | 20 | 21 | ```sh 22 | infisical run -- opengb dev 23 | ``` 24 | 25 | -------------------------------------------------------------------------------- /docs/integrations/overview.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: All Integrations 3 | --- 4 | 5 | export const iconClasses = "w-7 h-7"; 6 | 7 | export const sendgrid = ( 8 | 9 | 10 | 11 | ); 12 | 13 | export const infisical = ( 14 | 15 | 16 | 17 | ); 18 | 19 | 20 | Send emails using SendGrid 21 | Store secrets in Infisical 22 | 23 | -------------------------------------------------------------------------------- /docs/integrations/sendgrid.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: SendGrid 3 | description: Send emails using SendGrid. 4 | --- 5 | 6 | 7 | 8 | 9 | ```sh 10 | opengb module add email 11 | ``` 12 | 13 | 14 | 15 | 16 | ```json backend.json 17 | { 18 | "modules": { 19 | "email": { 20 | "config": { 21 | "provider": { 22 | "sendgrid": { 23 | "apiKeyVariable": "SENDGRID_API_KEY" 24 | } 25 | } 26 | } 27 | } 28 | // ... 29 | } 30 | // ... 31 | } 32 | ``` 33 | 34 | 35 | 36 | ```sh 37 | export SENDGRID_API_KEY=your-api-key 38 | opengb dev 39 | ``` 40 | 41 | 42 | We recommend using Infisical to manage your API key. See [here](/integrations/infisical) for more information. 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /docs/modules/auth/overview.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Authentication" 3 | description: "Authenticate users with multiple authentication methods." 4 | sidebarTitle: Overview 5 | --- 6 | 7 | | Source | Name | Status | Database | 8 | | -------------------------------------------------------------------------------- | ---------- | ---------- | ------------ | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/auth) | `auth` | stable | Yes | 10 | 11 | **Authors** 12 | 13 | - [rivet-gg](https://github.com/rivet-gg) 14 | - [NathanFlurry](https://github.com/NathanFlurry) 15 | 16 | **Dependencies** 17 | 18 | - [Email](/modules/email/overview) 19 | - [Rate Limit](/modules/rate_limit/overview) 20 | - [Users](/modules/users/overview) 21 | 22 | ## Installation 23 | 24 | 25 | 26 | 27 | 28 | ```sh CLI 29 | opengb module add auth 30 | ``` 31 | 32 | 33 | 34 | 35 | 36 | Add the following to your `backend.json`: 37 | 38 | ```json backend.json 39 | "modules": { 40 | "auth": { 41 | "config": { 42 | // Your config here. See below for more details. 43 | } 44 | } 45 | } 46 | 47 | ``` 48 | 49 | 50 | 51 | 52 | ## Config 53 | 54 | ### Schema 55 | 56 | ```typescript 57 | export interface Config { 58 | email?: EmailConfig; 59 | } 60 | export interface EmailConfig { 61 | fromEmail: string; 62 | fromName?: string; 63 | } 64 | ``` 65 | 66 | ### Default 67 | 68 | ```json 69 | {} 70 | ``` 71 | 72 | 73 | 74 | ## Scripts 75 | 76 | ### Public 77 | 78 | Verify a user's email address with a one-time verification code.Send a one-time verification code to a user's email address to authenticate them. 79 | 80 | ### Internal 81 | 82 | _No internal scripts._ 83 | 84 | ## Errors 85 | 86 | - **Email Already Used** (`email_already_used`) 87 | - **Provider Disabled** (`provider_disabled`) 88 | - **Verification Code Already Used** (`verification_code_already_used`) 89 | - **Verification Code Attempt Limit** (`verification_code_attempt_limit`) 90 | - **Verification Code Expired** (`verification_code_expired`) 91 | - **Verification Code Invalid** (`verification_code_invalid`) 92 | -------------------------------------------------------------------------------- /docs/modules/currency/overview.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Currency" 3 | description: "Track user balances and allow them to deposit and withdraw." 4 | sidebarTitle: Overview 5 | --- 6 | 7 | | Source | Name | Status | Database | 8 | | -------------------------------------------------------------------------------- | ---------- | ---------- | ------------ | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/currency) | `currency` | preview | Yes | 10 | 11 | **Authors** 12 | 13 | - [ABCxFF](https://github.com/ABCxFF) 14 | 15 | **Dependencies** 16 | 17 | - [Rate Limit](/modules/rate_limit/overview) 18 | - [Users](/modules/users/overview) 19 | 20 | ## Installation 21 | 22 | 23 | 24 | 25 | 26 | ```sh CLI 27 | opengb module add currency 28 | ``` 29 | 30 | 31 | 32 | 33 | 34 | Add the following to your `backend.json`: 35 | 36 | ```json backend.json 37 | "modules": { 38 | "currency": {} 39 | } 40 | 41 | ``` 42 | 43 | 44 | 45 | 46 | 47 | 48 | ## Scripts 49 | 50 | ### Public 51 | 52 | 53 | 54 | ### Internal 55 | 56 | 57 | 58 | ## Errors 59 | 60 | - **Invalid Amount** (`invalid_amount`) 61 | - **Invalid User** (`invalid_user`) 62 | - **Not Enough Funds** (`not_enough_funds`) 63 | -------------------------------------------------------------------------------- /docs/modules/currency/scripts/deposit.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Deposit" 3 | description: "" 4 | sidebarTitle: "Deposit" 5 | --- 6 | 7 | | Source | Name | Public | 8 | | ----------------------------------------------------------------------------------------------------------- | ---------- | ---------- | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/currency/scripts/deposit.ts) | `deposit` | No | 10 | 11 | 12 | ```typescript OpenGB Script 13 | const data = await ctx.modules.currency.deposit({ 14 | // Request body 15 | }); 16 | ``` 17 | 18 | 19 | 20 | ## Request 21 | 22 | ```typescript 23 | export interface Request { 24 | userId: string; 25 | amount: number; 26 | } 27 | ``` 28 | 29 | ## Response 30 | 31 | ```typescript 32 | export interface Response { 33 | updatedBalance: number; 34 | } 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/modules/currency/scripts/get_balance.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Get Balance" 3 | description: "" 4 | sidebarTitle: "Get Balance" 5 | --- 6 | 7 | | Source | Name | Public | 8 | | ----------------------------------------------------------------------------------------------------------- | ---------- | ---------- | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/currency/scripts/get_balance.ts) | `get_balance` | No | 10 | 11 | 12 | ```typescript OpenGB Script 13 | const data = await ctx.modules.currency.getBalance({ 14 | // Request body 15 | }); 16 | ``` 17 | 18 | 19 | 20 | ## Request 21 | 22 | ```typescript 23 | export interface Request { 24 | userId: string; 25 | } 26 | ``` 27 | 28 | ## Response 29 | 30 | ```typescript 31 | export interface Response { 32 | balance: number; 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /docs/modules/currency/scripts/set_balance.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Set Balance" 3 | description: "" 4 | sidebarTitle: "Set Balance" 5 | --- 6 | 7 | | Source | Name | Public | 8 | | ----------------------------------------------------------------------------------------------------------- | ---------- | ---------- | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/currency/scripts/set_balance.ts) | `set_balance` | No | 10 | 11 | 12 | ```typescript OpenGB Script 13 | const data = await ctx.modules.currency.setBalance({ 14 | // Request body 15 | }); 16 | ``` 17 | 18 | 19 | 20 | ## Request 21 | 22 | ```typescript 23 | export interface Request { 24 | userId: string; 25 | balance: number; 26 | } 27 | ``` 28 | 29 | ## Response 30 | 31 | ```typescript 32 | export interface Response {} 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/modules/currency/scripts/withdraw.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Withdraw" 3 | description: "" 4 | sidebarTitle: "Withdraw" 5 | --- 6 | 7 | | Source | Name | Public | 8 | | ----------------------------------------------------------------------------------------------------------- | ---------- | ---------- | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/currency/scripts/withdraw.ts) | `withdraw` | No | 10 | 11 | 12 | ```typescript OpenGB Script 13 | const data = await ctx.modules.currency.withdraw({ 14 | // Request body 15 | }); 16 | ``` 17 | 18 | 19 | 20 | ## Request 21 | 22 | ```typescript 23 | export interface Request { 24 | userId: string; 25 | amount: number; 26 | } 27 | ``` 28 | 29 | ## Response 30 | 31 | ```typescript 32 | export interface Response { 33 | updatedBalance: number; 34 | } 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/modules/email/overview.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Email" 3 | description: "Send emails using multiple providers." 4 | sidebarTitle: Overview 5 | --- 6 | 7 | | Source | Name | Status | Database | 8 | | -------------------------------------------------------------------------------- | ---------- | ---------- | ------------ | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/email) | `email` | stable | No | 10 | 11 | **Authors** 12 | 13 | - [rivet-gg](https://github.com/rivet-gg) 14 | - [NathanFlurry](https://github.com/NathanFlurry) 15 | 16 | **Dependencies** 17 | 18 | _No dependencies_ 19 | 20 | ## Installation 21 | 22 | 23 | 24 | 25 | 26 | ```sh CLI 27 | opengb module add email 28 | ``` 29 | 30 | 31 | 32 | 33 | 34 | Add the following to your `backend.json`: 35 | 36 | ```json backend.json 37 | "modules": { 38 | "email": { 39 | "config": { 40 | // Your config here. See below for more details. 41 | } 42 | } 43 | } 44 | 45 | ``` 46 | 47 | 48 | 49 | 50 | ## Config 51 | 52 | ### Schema 53 | 54 | ```typescript 55 | export type Provider = 56 | | { 57 | test: ProviderTest; 58 | } 59 | | { 60 | sendGrid: ProviderSendGrid; 61 | }; 62 | 63 | export interface Config { 64 | provider: Provider; 65 | } 66 | export interface ProviderTest {} 67 | export interface ProviderSendGrid { 68 | apiKeyVariable?: string; 69 | } 70 | ``` 71 | 72 | ### Default 73 | 74 | ```json 75 | {} 76 | ``` 77 | 78 | 79 | 80 | ## Scripts 81 | 82 | ### Public 83 | 84 | _No public scripts._ 85 | 86 | ### Internal 87 | 88 | 89 | 90 | ## Errors 91 | 92 | - **Email Missing Content** (`email_missing_content`) Email must have `html` and/or `text` 93 | - **SendGrid Error** (`sendgrid_error`) 94 | -------------------------------------------------------------------------------- /docs/modules/email/scripts/send_email.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Send Email" 3 | description: "" 4 | sidebarTitle: "Send Email" 5 | --- 6 | 7 | | Source | Name | Public | 8 | | ----------------------------------------------------------------------------------------------------------- | ---------- | ---------- | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/email/scripts/send_email.ts) | `send_email` | No | 10 | 11 | 12 | ```typescript OpenGB Script 13 | const data = await ctx.modules.email.sendEmail({ 14 | // Request body 15 | }); 16 | ``` 17 | 18 | 19 | 20 | ## Request 21 | 22 | ```typescript 23 | export interface Request { 24 | from: Email; 25 | to: Email[]; 26 | cc?: Email[]; 27 | bcc?: Email[]; 28 | replyTo?: Email[]; 29 | subject: string; 30 | html?: string; 31 | text?: string; 32 | } 33 | /** 34 | * Identifies an email address and optionally a name. 35 | */ 36 | export interface Email { 37 | /** 38 | * Target email. 39 | */ 40 | email: string; 41 | /** 42 | * Optional display name for the email. 43 | */ 44 | name?: string; 45 | } 46 | ``` 47 | 48 | ## Response 49 | 50 | ```typescript 51 | export interface Response {} 52 | ``` 53 | -------------------------------------------------------------------------------- /docs/modules/friends/overview.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Friends" 3 | description: "Allow users to send and accept friend requests." 4 | sidebarTitle: Overview 5 | --- 6 | 7 | | Source | Name | Status | Database | 8 | | -------------------------------------------------------------------------------- | ---------- | ---------- | ------------ | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/friends) | `friends` | beta | Yes | 10 | 11 | **Authors** 12 | 13 | - [rivet-gg](https://github.com/rivet-gg) 14 | - [NathanFlurry](https://github.com/NathanFlurry) 15 | 16 | **Dependencies** 17 | 18 | - [Rate Limit](/modules/rate_limit/overview) 19 | - [Users](/modules/users/overview) 20 | 21 | ## Installation 22 | 23 | 24 | 25 | 26 | 27 | ```sh CLI 28 | opengb module add friends 29 | ``` 30 | 31 | 32 | 33 | 34 | 35 | Add the following to your `backend.json`: 36 | 37 | ```json backend.json 38 | "modules": { 39 | "friends": {} 40 | } 41 | 42 | ``` 43 | 44 | 45 | 46 | 47 | 48 | 49 | ## Scripts 50 | 51 | ### Public 52 | 53 | Accept a friend request from another user.Decline a friend request from another user.List all friends of a user.List all friend requests received by a user.List all friend requests sent by a user.Remove a friend from your friends list.Send a friend request to another user. 54 | 55 | ### Internal 56 | 57 | _No internal scripts._ 58 | 59 | ## Errors 60 | 61 | - **Already Friends** (`already_friends`) 62 | - **Cannot Send to Self** (`cannot_send_to_self`) 63 | - **Friend Request Already Accepted** (`friend_request_already_accepted`) 64 | - **Friend Request Already Declined** (`friend_request_already_declined`) 65 | - **Friend Request Already Exists** (`friend_request_already_exists`) 66 | - **Friend Request Not Found** (`friend_request_not_found`) 67 | - **Not Friend Request Recipient** (`not_friend_request_recipient`) 68 | -------------------------------------------------------------------------------- /docs/modules/overview.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: All Modules 3 | --- 4 | 5 | import { ModuleCards } from "/snippets/module-cards.mdx"; 6 | import { Tags } from "/snippets/tags.mdx"; 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/modules/rate_limit/overview.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Rate Limit" 3 | description: "Prevent abuse by limiting request rate." 4 | sidebarTitle: Overview 5 | --- 6 | 7 | | Source | Name | Status | Database | 8 | | -------------------------------------------------------------------------------- | ---------- | ---------- | ------------ | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/rate_limit) | `rate_limit` | stable | Yes | 10 | 11 | **Authors** 12 | 13 | - [rivet-gg](https://github.com/rivet-gg) 14 | - [NathanFlurry](https://github.com/NathanFlurry) 15 | 16 | **Dependencies** 17 | 18 | _No dependencies_ 19 | 20 | ## Installation 21 | 22 | 23 | 24 | 25 | 26 | ```sh CLI 27 | opengb module add rate_limit 28 | ``` 29 | 30 | 31 | 32 | 33 | 34 | Add the following to your `backend.json`: 35 | 36 | ```json backend.json 37 | "modules": { 38 | "rate_limit": {} 39 | } 40 | 41 | ``` 42 | 43 | 44 | 45 | 46 | 47 | 48 | ## Scripts 49 | 50 | ### Public 51 | 52 | _No public scripts._ 53 | 54 | ### Internal 55 | 56 | Limit the amount of times an request can be made by a given key.Limit the amount of times a public request can be made by a given key. This will rate limit based off the user's IP address. 57 | 58 | ## Errors 59 | 60 | - **Rate Limit Exceeded** (`rate_limit_exceeded`) 61 | -------------------------------------------------------------------------------- /docs/modules/rate_limit/scripts/throttle.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Throttle" 3 | description: "Limit the amount of times an request can be made by a given key." 4 | sidebarTitle: "Throttle" 5 | --- 6 | 7 | | Source | Name | Public | 8 | | ----------------------------------------------------------------------------------------------------------- | ---------- | ---------- | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/rate_limit/scripts/throttle.ts) | `throttle` | No | 10 | 11 | 12 | ```typescript OpenGB Script 13 | const data = await ctx.modules.rateLimit.throttle({ 14 | // Request body 15 | }); 16 | ``` 17 | 18 | 19 | 20 | ## Request 21 | 22 | ```typescript 23 | export interface Request { 24 | /** 25 | * The type of entity to rate limit by. For example, "ip" or "user". 26 | */ 27 | type: string; 28 | /** 29 | * The key to rate limit by. For example, the IP address or user ID. 30 | */ 31 | key: string; 32 | /** 33 | * Number of requests in `period` before rate limiting. 34 | */ 35 | requests: number; 36 | /** 37 | * How frequently to reset the request counter, in seconds. 38 | */ 39 | period: number; 40 | } 41 | ``` 42 | 43 | ## Response 44 | 45 | ```typescript 46 | export interface Response {} 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/modules/rate_limit/scripts/throttle_public.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Throttle Public" 3 | description: "Limit the amount of times a public request can be made by a given key. This will rate limit based off the user's IP address." 4 | sidebarTitle: "Throttle Public" 5 | --- 6 | 7 | | Source | Name | Public | 8 | | ----------------------------------------------------------------------------------------------------------- | ---------- | ---------- | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/rate_limit/scripts/throttle_public.ts) | `throttle_public` | No | 10 | 11 | 12 | ```typescript OpenGB Script 13 | const data = await ctx.modules.rateLimit.throttlePublic({ 14 | // Request body 15 | }); 16 | ``` 17 | 18 | 19 | 20 | ## Request 21 | 22 | ```typescript 23 | export interface Request { 24 | /** 25 | * Number of requests in `period` before rate limiting. 26 | */ 27 | requests?: number; 28 | /** 29 | * How frequently to reset the request counter. 30 | */ 31 | period?: number; 32 | } 33 | ``` 34 | 35 | ## Response 36 | 37 | ```typescript 38 | export interface Response {} 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/modules/tokens/overview.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Tokens" 3 | description: "Create & verify tokens for authorization purposes." 4 | sidebarTitle: Overview 5 | --- 6 | 7 | | Source | Name | Status | Database | 8 | | -------------------------------------------------------------------------------- | ---------- | ---------- | ------------ | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/tokens) | `tokens` | stable | Yes | 10 | 11 | **Authors** 12 | 13 | - [rivet-gg](https://github.com/rivet-gg) 14 | - [NathanFlurry](https://github.com/NathanFlurry) 15 | 16 | **Dependencies** 17 | 18 | _No dependencies_ 19 | 20 | ## Installation 21 | 22 | 23 | 24 | 25 | 26 | ```sh CLI 27 | opengb module add tokens 28 | ``` 29 | 30 | 31 | 32 | 33 | 34 | Add the following to your `backend.json`: 35 | 36 | ```json backend.json 37 | "modules": { 38 | "tokens": {} 39 | } 40 | 41 | ``` 42 | 43 | 44 | 45 | 46 | 47 | 48 | ## Scripts 49 | 50 | ### Public 51 | 52 | _No public scripts._ 53 | 54 | ### Internal 55 | 56 | Extend or remove the expiration date of a token. (Only works on valid tokens.)Get a token by its secret token.Get a token by its ID.Revoke a token, preventing it from being used again.Validate a token. Throws an error if the token is invalid. 57 | 58 | ## Errors 59 | 60 | - **Token Expired** (`token_expired`) 61 | - **Token Not Found** (`token_not_found`) 62 | - **Token Revoked** (`token_revoked`) 63 | -------------------------------------------------------------------------------- /docs/modules/tokens/scripts/create.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Create Token" 3 | description: "" 4 | sidebarTitle: "Create Token" 5 | --- 6 | 7 | | Source | Name | Public | 8 | | ----------------------------------------------------------------------------------------------------------- | ---------- | ---------- | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/tokens/scripts/create.ts) | `create` | No | 10 | 11 | 12 | ```typescript OpenGB Script 13 | const data = await ctx.modules.tokens.create({ 14 | // Request body 15 | }); 16 | ``` 17 | 18 | 19 | 20 | ## Request 21 | 22 | ```typescript 23 | export interface Request { 24 | type: string; 25 | meta: { 26 | [k: string]: any; 27 | }; 28 | expireAt?: string; 29 | } 30 | ``` 31 | 32 | ## Response 33 | 34 | ```typescript 35 | export interface Response { 36 | token: TokenWithSecret; 37 | } 38 | export interface TokenWithSecret { 39 | token: string; 40 | id: string; 41 | type: string; 42 | meta: any; 43 | createdAt: string; 44 | expireAt: null | string; 45 | revokedAt: null | string; 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/modules/tokens/scripts/extend.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Extend Token" 3 | description: "Extend or remove the expiration date of a token. (Only works on valid tokens.)" 4 | sidebarTitle: "Extend Token" 5 | --- 6 | 7 | | Source | Name | Public | 8 | | ----------------------------------------------------------------------------------------------------------- | ---------- | ---------- | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/tokens/scripts/extend.ts) | `extend` | No | 10 | 11 | 12 | ```typescript OpenGB Script 13 | const data = await ctx.modules.tokens.extend({ 14 | // Request body 15 | }); 16 | ``` 17 | 18 | 19 | 20 | ## Request 21 | 22 | ```typescript 23 | export interface Request { 24 | token: string; 25 | newExpiration: null | string; 26 | } 27 | ``` 28 | 29 | ## Response 30 | 31 | ```typescript 32 | export interface Response { 33 | token: TokenWithSecret; 34 | } 35 | export interface TokenWithSecret { 36 | token: string; 37 | id: string; 38 | type: string; 39 | meta: any; 40 | createdAt: string; 41 | expireAt: null | string; 42 | revokedAt: null | string; 43 | } 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/modules/tokens/scripts/fetch.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Fetch Token" 3 | description: "Get a token by its ID." 4 | sidebarTitle: "Fetch Token" 5 | --- 6 | 7 | | Source | Name | Public | 8 | | ----------------------------------------------------------------------------------------------------------- | ---------- | ---------- | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/tokens/scripts/fetch.ts) | `fetch` | No | 10 | 11 | 12 | ```typescript OpenGB Script 13 | const data = await ctx.modules.tokens.fetch({ 14 | // Request body 15 | }); 16 | ``` 17 | 18 | 19 | 20 | ## Request 21 | 22 | ```typescript 23 | export interface Request { 24 | tokenIds: string[]; 25 | } 26 | ``` 27 | 28 | ## Response 29 | 30 | ```typescript 31 | export interface Response { 32 | tokens: Token[]; 33 | } 34 | export interface Token { 35 | id: string; 36 | type: string; 37 | meta: any; 38 | createdAt: string; 39 | expireAt: null | string; 40 | revokedAt: null | string; 41 | } 42 | ``` 43 | -------------------------------------------------------------------------------- /docs/modules/tokens/scripts/fetch_by_token.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Fetch by Token" 3 | description: "Get a token by its secret token." 4 | sidebarTitle: "Fetch by Token" 5 | --- 6 | 7 | | Source | Name | Public | 8 | | ----------------------------------------------------------------------------------------------------------- | ---------- | ---------- | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/tokens/scripts/fetch_by_token.ts) | `fetch_by_token` | No | 10 | 11 | 12 | ```typescript OpenGB Script 13 | const data = await ctx.modules.tokens.fetchByToken({ 14 | // Request body 15 | }); 16 | ``` 17 | 18 | 19 | 20 | ## Request 21 | 22 | ```typescript 23 | export interface Request { 24 | tokens: string[]; 25 | } 26 | ``` 27 | 28 | ## Response 29 | 30 | ```typescript 31 | export interface Response { 32 | tokens: Token[]; 33 | } 34 | export interface Token { 35 | id: string; 36 | type: string; 37 | meta: any; 38 | createdAt: string; 39 | expireAt: null | string; 40 | revokedAt: null | string; 41 | } 42 | ``` 43 | -------------------------------------------------------------------------------- /docs/modules/tokens/scripts/revoke.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Revoke Token" 3 | description: "Revoke a token, preventing it from being used again." 4 | sidebarTitle: "Revoke Token" 5 | --- 6 | 7 | | Source | Name | Public | 8 | | ----------------------------------------------------------------------------------------------------------- | ---------- | ---------- | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/tokens/scripts/revoke.ts) | `revoke` | No | 10 | 11 | 12 | ```typescript OpenGB Script 13 | const data = await ctx.modules.tokens.revoke({ 14 | // Request body 15 | }); 16 | ``` 17 | 18 | 19 | 20 | ## Request 21 | 22 | ```typescript 23 | export interface Request { 24 | tokenIds: string[]; 25 | } 26 | ``` 27 | 28 | ## Response 29 | 30 | ```typescript 31 | export type TokenUpdate = "REVOKED" | "ALREADY_REVOKED" | "NOT_FOUND"; 32 | 33 | export interface Response { 34 | updates: { 35 | [k: string]: TokenUpdate; 36 | }; 37 | } 38 | ``` 39 | -------------------------------------------------------------------------------- /docs/modules/tokens/scripts/validate.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Validate Token" 3 | description: "Validate a token. Throws an error if the token is invalid." 4 | sidebarTitle: "Validate Token" 5 | --- 6 | 7 | | Source | Name | Public | 8 | | ----------------------------------------------------------------------------------------------------------- | ---------- | ---------- | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/tokens/scripts/validate.ts) | `validate` | No | 10 | 11 | 12 | ```typescript OpenGB Script 13 | const data = await ctx.modules.tokens.validate({ 14 | // Request body 15 | }); 16 | ``` 17 | 18 | 19 | 20 | ## Request 21 | 22 | ```typescript 23 | export interface Request { 24 | token: string; 25 | } 26 | ``` 27 | 28 | ## Response 29 | 30 | ```typescript 31 | export interface Response { 32 | token: Token; 33 | } 34 | export interface Token { 35 | id: string; 36 | type: string; 37 | meta: any; 38 | createdAt: string; 39 | expireAt: null | string; 40 | revokedAt: null | string; 41 | } 42 | ``` 43 | -------------------------------------------------------------------------------- /docs/modules/uploads/scripts/complete.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Complete Upload" 3 | description: "Alert the module that the upload has been completed" 4 | sidebarTitle: "Complete Upload" 5 | --- 6 | 7 | | Source | Name | Public | 8 | | ----------------------------------------------------------------------------------------------------------- | ---------- | ---------- | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/uploads/scripts/complete.ts) | `complete` | No | 10 | 11 | 12 | ```typescript OpenGB Script 13 | const data = await ctx.modules.uploads.complete({ 14 | // Request body 15 | }); 16 | ``` 17 | 18 | 19 | 20 | ## Request 21 | 22 | ```typescript 23 | export interface Request { 24 | uploadId: string; 25 | } 26 | ``` 27 | 28 | ## Response 29 | 30 | ```typescript 31 | export interface Response { 32 | upload: Upload; 33 | } 34 | export interface Upload { 35 | id: string; 36 | metadata?: any; 37 | bucket: string; 38 | /** 39 | * The total size of all files in the upload in bytes. 40 | * 41 | * *(This is a string instead of a bigint because JSON doesn't support 42 | * serializing/deserializing bigints, and we want to be able to represent 43 | * very large file sizes.)* 44 | */ 45 | contentLength: string; 46 | files: UploadFile[]; 47 | createdAt: string; 48 | updatedAt: string; 49 | completedAt: null | string; 50 | } 51 | export interface UploadFile { 52 | path: string; 53 | mime: null | string; 54 | /** 55 | * The size of the file in bytes. 56 | * 57 | * *(This is a string instead of a bigint because JSON doesn't support 58 | * serializing/deserializing bigints, and we want to be able to represent 59 | * very large file sizes.)* 60 | */ 61 | contentLength: string; 62 | } 63 | ``` 64 | -------------------------------------------------------------------------------- /docs/modules/uploads/scripts/delete.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Delete Upload" 3 | description: "Removes the upload and deletes the files from the bucket" 4 | sidebarTitle: "Delete Upload" 5 | --- 6 | 7 | | Source | Name | Public | 8 | | ----------------------------------------------------------------------------------------------------------- | ---------- | ---------- | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/uploads/scripts/delete.ts) | `delete` | No | 10 | 11 | 12 | ```typescript OpenGB Script 13 | const data = await ctx.modules.uploads.delete({ 14 | // Request body 15 | }); 16 | ``` 17 | 18 | 19 | 20 | ## Request 21 | 22 | ```typescript 23 | export interface Request { 24 | uploadId: string; 25 | } 26 | ``` 27 | 28 | ## Response 29 | 30 | ```typescript 31 | export interface Response { 32 | bytesDeleted: string; 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /docs/modules/uploads/scripts/get.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Get Upload Metadata" 3 | description: "Get the metadata (including contained files) for specified upload IDs" 4 | sidebarTitle: "Get Upload Metadata" 5 | --- 6 | 7 | | Source | Name | Public | 8 | | ----------------------------------------------------------------------------------------------------------- | ---------- | ---------- | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/uploads/scripts/get.ts) | `get` | No | 10 | 11 | 12 | ```typescript OpenGB Script 13 | const data = await ctx.modules.uploads.get({ 14 | // Request body 15 | }); 16 | ``` 17 | 18 | 19 | 20 | ## Request 21 | 22 | ```typescript 23 | export interface Request { 24 | uploadIds: string[]; 25 | includeFiles?: boolean; 26 | } 27 | ``` 28 | 29 | ## Response 30 | 31 | ```typescript 32 | export interface Response { 33 | uploads: UploadWithOptionalFiles[]; 34 | } 35 | export interface UploadWithOptionalFiles { 36 | metadata?: any; 37 | id: string; 38 | bucket: string; 39 | /** 40 | * The total size of all files in the upload in bytes. 41 | * 42 | * *(This is a string instead of a bigint because JSON doesn't support 43 | * serializing/deserializing bigints, and we want to be able to represent 44 | * very large file sizes.)* 45 | */ 46 | contentLength: string; 47 | createdAt: string; 48 | updatedAt: string; 49 | completedAt: null | string; 50 | files?: UploadFile[]; 51 | } 52 | export interface UploadFile { 53 | path: string; 54 | mime: null | string; 55 | /** 56 | * The size of the file in bytes. 57 | * 58 | * *(This is a string instead of a bigint because JSON doesn't support 59 | * serializing/deserializing bigints, and we want to be able to represent 60 | * very large file sizes.)* 61 | */ 62 | contentLength: string; 63 | } 64 | ``` 65 | -------------------------------------------------------------------------------- /docs/modules/uploads/scripts/get_public_file_urls.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Get File Link" 3 | description: "Get presigned download links for each of the specified files" 4 | sidebarTitle: "Get File Link" 5 | --- 6 | 7 | | Source | Name | Public | 8 | | ----------------------------------------------------------------------------------------------------------- | ---------- | ---------- | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/uploads/scripts/get_public_file_urls.ts) | `get_public_file_urls` | No | 10 | 11 | 12 | ```typescript OpenGB Script 13 | const data = await ctx.modules.uploads.getPublicFileUrls({ 14 | // Request body 15 | }); 16 | ``` 17 | 18 | 19 | 20 | ## Request 21 | 22 | ```typescript 23 | export interface Request { 24 | files: FileIdentifier[]; 25 | expirySeconds?: number; 26 | } 27 | export interface FileIdentifier { 28 | uploadId: string; 29 | path: string; 30 | } 31 | ``` 32 | 33 | ## Response 34 | 35 | ```typescript 36 | export interface Response { 37 | files: DownloadableFile[]; 38 | } 39 | export interface DownloadableFile { 40 | uploadId: string; 41 | url: string; 42 | path: string; 43 | mime: null | string; 44 | /** 45 | * The size of the file in bytes. 46 | * 47 | * *(This is a string instead of a bigint because JSON doesn't support 48 | * serializing/deserializing bigints, and we want to be able to represent 49 | * very large file sizes.)* 50 | */ 51 | contentLength: string; 52 | } 53 | ``` 54 | -------------------------------------------------------------------------------- /docs/modules/users/overview.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Users" 3 | description: "Identify and manage users." 4 | sidebarTitle: Overview 5 | --- 6 | 7 | | Source | Name | Status | Database | 8 | | -------------------------------------------------------------------------------- | ---------- | ---------- | ------------ | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/users) | `users` | stable | Yes | 10 | 11 | **Authors** 12 | 13 | - [rivet-gg](https://github.com/rivet-gg) 14 | - [NathanFlurry](https://github.com/NathanFlurry) 15 | 16 | **Dependencies** 17 | 18 | - [Rate Limit](/modules/rate_limit/overview) 19 | - [Tokens](/modules/tokens/overview) 20 | 21 | ## Installation 22 | 23 | 24 | 25 | 26 | 27 | ```sh CLI 28 | opengb module add users 29 | ``` 30 | 31 | 32 | 33 | 34 | 35 | Add the following to your `backend.json`: 36 | 37 | ```json backend.json 38 | "modules": { 39 | "users": {} 40 | } 41 | 42 | ``` 43 | 44 | 45 | 46 | 47 | 48 | 49 | ## Scripts 50 | 51 | ### Public 52 | 53 | Validate a user token. Throws an error if the token is invalid. 54 | 55 | ### Internal 56 | 57 | Create a token for a user to authenticate future requests. 58 | 59 | ## Errors 60 | 61 | - **Token Not User Token** (`token_not_user_token`) 62 | - **Unknown Identity Type** (`unknown_identity_type`) 63 | -------------------------------------------------------------------------------- /docs/modules/users/scripts/create.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Create User" 3 | description: "" 4 | sidebarTitle: "Create User" 5 | --- 6 | 7 | | Source | Name | Public | 8 | | ----------------------------------------------------------------------------------------------------------- | ---------- | ---------- | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/users/scripts/create.ts) | `create` | No | 10 | 11 | 12 | ```typescript OpenGB Script 13 | const data = await ctx.modules.users.create({ 14 | // Request body 15 | }); 16 | ``` 17 | 18 | 19 | 20 | ## Request 21 | 22 | ```typescript 23 | export interface Request { 24 | username?: string; 25 | } 26 | ``` 27 | 28 | ## Response 29 | 30 | ```typescript 31 | export interface Response { 32 | user: User; 33 | } 34 | export interface User { 35 | id: string; 36 | username: string; 37 | createdAt: string; 38 | updatedAt: string; 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/modules/users/scripts/create_token.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Create User Token" 3 | description: "Create a token for a user to authenticate future requests." 4 | sidebarTitle: "Create User Token" 5 | --- 6 | 7 | | Source | Name | Public | 8 | | ----------------------------------------------------------------------------------------------------------- | ---------- | ---------- | 9 | | [Source](https://github.com/rivet-gg/opengb-modules/tree/main/modules/users/scripts/create_token.ts) | `create_token` | No | 10 | 11 | 12 | ```typescript OpenGB Script 13 | const data = await ctx.modules.users.createToken({ 14 | // Request body 15 | }); 16 | ``` 17 | 18 | 19 | 20 | ## Request 21 | 22 | ```typescript 23 | export interface Request { 24 | userId: string; 25 | } 26 | ``` 27 | 28 | ## Response 29 | 30 | ```typescript 31 | export interface Response { 32 | token: TokenWithSecret; 33 | } 34 | export interface TokenWithSecret { 35 | token: string; 36 | id: string; 37 | type: string; 38 | meta: any; 39 | createdAt: string; 40 | expireAt: null | string; 41 | revokedAt: null | string; 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/snippets/module-cards.mdx: -------------------------------------------------------------------------------- 1 | export const ModuleCards = () => ( 2 | <> 3 |

These modules are thoroughly reviewed, tested, and documented to help you get your game backend up and running as quickly as possible. Learn more about creating your own module and using your own registries.

4 | 5 | 6 | 7 | 8 | 9 |
Authenticate users with multiple authentication methods.
10 | 11 |
12 | 13 | 14 |
Track user balances and allow them to deposit and withdraw.
15 | 16 |
17 | 18 | 19 |
Send emails using multiple providers.
20 | 21 |
22 | 23 | 24 |
Allow users to send and accept friend requests.
25 | 26 |
27 | 28 | 29 |
Prevent abuse by limiting request rate.
30 | 31 |
32 | 33 | 34 |
Create & verify tokens for authorization purposes.
35 | 36 |
37 | 38 | 39 |
Upload & store blobs of data.
40 | 41 |
42 | 43 | 44 |
Identify and manage users.
45 | 46 |
47 | 48 |
49 | 50 | ); 51 | -------------------------------------------------------------------------------- /docs/snippets/tags.mdx: -------------------------------------------------------------------------------- 1 | export const Tags = ({ tags }) => ( 2 |
3 | {tags.map(tag => ( 4 |
{tag}
5 | ))} 6 |
7 | ); 8 | -------------------------------------------------------------------------------- /media/hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinevue/game-backend/eeda24d105336cb4d64ab515ee2064f6cf714994/media/hero.png -------------------------------------------------------------------------------- /release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": { 3 | ".": { 4 | "release-type": "simple" 5 | } 6 | }, 7 | "changelog-sections": [ 8 | { 9 | "type": "feat", 10 | "hidden": false, 11 | "section": "Features" 12 | }, 13 | { 14 | "type": "fix", 15 | "hidden": false, 16 | "section": "Bug Fixes" 17 | }, 18 | { 19 | "type": "perf", 20 | "hidden": false, 21 | "section": "Performance Improvements" 22 | }, 23 | { 24 | "type": "revert", 25 | "hidden": false, 26 | "section": "Reverts" 27 | }, 28 | { 29 | "type": "docs", 30 | "hidden": false, 31 | "section": "Documentation" 32 | }, 33 | { 34 | "type": "style", 35 | "hidden": false, 36 | "section": "Styles" 37 | }, 38 | { 39 | "type": "refactor", 40 | "hidden": false, 41 | "section": "Code Refactoring" 42 | }, 43 | { 44 | "type": "test", 45 | "hidden": false, 46 | "section": "Tests" 47 | }, 48 | { 49 | "type": "build", 50 | "hidden": false, 51 | "section": "Build System" 52 | }, 53 | { 54 | "type": "ci", 55 | "hidden": false, 56 | "section": "Continuous Integration" 57 | }, 58 | { 59 | "type": "chore", 60 | "hidden": false, 61 | "section": "Chores" 62 | }, 63 | { 64 | "type": "other", 65 | "hidden": false, 66 | "section": "Others" 67 | } 68 | ] 69 | } -------------------------------------------------------------------------------- /src/artifacts/build_dynamic_archive.ts: -------------------------------------------------------------------------------- 1 | // deno task artifacts:build:runtime_archive 2 | // 3 | // Generates a JSON file with all of the files that need to be accessed dynamically. 4 | 5 | import { resolve } from "../deps.ts"; 6 | import { buildArtifacts, rootSrcPath } from "./util.ts"; 7 | 8 | await buildArtifacts({ 9 | rootPath: resolve(rootSrcPath(), "src", "dynamic"), 10 | patterns: ["*.ts"], 11 | outputPath: resolve(rootSrcPath(), "artifacts", "dynamic_archive.json"), 12 | encode: "string", 13 | }); 14 | -------------------------------------------------------------------------------- /src/artifacts/build_prisma_archive.ts: -------------------------------------------------------------------------------- 1 | // deno task artifacts:build:prisma_archive 2 | // 3 | // Generates a JSON file with the node_modules folder used for Prisma so we don't have to depend on Node/NPM to install Prisma. 4 | 5 | import { resolve } from "../deps.ts"; 6 | import { buildArtifacts, rootSrcPath } from "./util.ts"; 7 | 8 | // Install Prisma modules 9 | const npmInstallOutput = await new Deno.Command("npm", { 10 | args: ["ci"], 11 | stdout: "inherit", 12 | stderr: "inherit", 13 | cwd: resolve(rootSrcPath(), "vendor", "prisma"), 14 | }).output(); 15 | if (!npmInstallOutput.success) throw "Failed to install Prisma dependencies"; 16 | 17 | // Archive Prisma modules 18 | await buildArtifacts({ 19 | rootPath: resolve(rootSrcPath(), "vendor", "prisma", "node_modules"), 20 | patterns: [ 21 | // Source files 22 | "**/*.{js,json,d.ts}", 23 | // Only include WASM files we depend on 24 | "prisma/build/prisma_schema_build_bg.wasm", 25 | "@prisma/client/runtime/query_engine_bg.postgresql.wasm", 26 | ], 27 | outputPath: resolve(rootSrcPath(), "artifacts", "prisma_archive.json"), 28 | globOpts: { 29 | // Exclude large files we don't use 30 | ignore: ["prisma/build/index.js"], 31 | }, 32 | encode: "base64", 33 | }); 34 | -------------------------------------------------------------------------------- /src/artifacts/build_runtime_archive.ts: -------------------------------------------------------------------------------- 1 | // deno task artifacts:build:runtime_archive 2 | // 3 | // Generates a JSON file with all of the runtime's source that can be used to re-populate the source 4 | 5 | import { resolve } from "../deps.ts"; 6 | import { buildArtifacts, rootSrcPath } from "./util.ts"; 7 | 8 | await buildArtifacts({ 9 | rootPath: rootSrcPath(), 10 | patterns: [ 11 | "src/{runtime,types}/*.ts", 12 | "src/deps.ts", 13 | "src/utils/db.ts", 14 | ], 15 | outputPath: resolve(rootSrcPath(), "artifacts", "runtime_archive.json"), 16 | encode: "string", 17 | }); 18 | -------------------------------------------------------------------------------- /src/artifacts/build_schema.ts: -------------------------------------------------------------------------------- 1 | // deno task artifacts:build:schema 2 | // 3 | // Generates schema JSON from the module & project config 4 | 5 | import { tjs } from "./deps.ts"; 6 | import { dirname as parent, resolve } from "../deps.ts"; 7 | 8 | const dirname = import.meta.dirname; 9 | if (!dirname) throw new Error("Missing dirname"); 10 | 11 | await Deno.mkdir(resolve(dirname, "..", "..", "artifacts")).catch((e) => { 12 | if (!(e instanceof Deno.errors.AlreadyExists)) throw e; 13 | }); 14 | 15 | const CONFIGS = [ 16 | { 17 | name: "module", 18 | type: "ModuleConfig", 19 | }, 20 | { 21 | name: "project", 22 | type: "ProjectConfig", 23 | }, 24 | ]; 25 | 26 | for (const { name, type } of CONFIGS) { 27 | const srcFileName = `${name}.ts`; 28 | const schemaFileName = `${name}_schema.json`; 29 | 30 | const srcPath = resolve(dirname, "..", "config", srcFileName); 31 | const schemaPath = resolve(dirname, "..", "..", "artifacts", schemaFileName); 32 | 33 | // https://docs.deno.com/runtime/manual/advanced/typescript/configuration#what-an-implied-tsconfigjson-looks-like 34 | const DEFAULT_COMPILER_OPTIONS = { 35 | "allowJs": true, 36 | "esModuleInterop": true, 37 | "experimentalDecorators": false, 38 | "inlineSourceMap": true, 39 | "isolatedModules": true, 40 | "jsx": "react", 41 | "module": "esnext", 42 | "moduleDetection": "force", 43 | "strict": true, 44 | "target": "esnext", 45 | "useDefineForClassFields": true, 46 | 47 | "lib": ["esnext", "dom", "dom.iterable"], 48 | "allowImportingTsExtensions": true, 49 | }; 50 | 51 | const schemaFiles = [srcPath]; 52 | 53 | const program = tjs.getProgramFromFiles( 54 | schemaFiles, 55 | DEFAULT_COMPILER_OPTIONS, 56 | ); 57 | 58 | const schema = tjs.generateSchema(program, type, { 59 | esModuleInterop: true, 60 | noExtraProps: true, 61 | required: true, 62 | strictNullChecks: true, 63 | 64 | // TODO: Is this needed? 65 | include: schemaFiles, 66 | 67 | // TODO: Figure out how to work without this? Maybe we manually validate the request type exists? 68 | ignoreErrors: true, 69 | }); 70 | if (schema == null) throw new Error("Failed to generate schema"); 71 | 72 | // Create artifacts folder if it doesn't already exist 73 | await Deno.mkdir(parent(schemaPath), { recursive: true }); 74 | 75 | // Write schema to file 76 | await Deno.writeTextFile(schemaPath, JSON.stringify(schema)); 77 | } 78 | -------------------------------------------------------------------------------- /src/artifacts/deps.ts: -------------------------------------------------------------------------------- 1 | export * as glob from "https://esm.sh/glob@^10.3.10"; 2 | export * as tjs from "npm:typescript-json-schema@0.62.0"; 3 | 4 | // TODO: These load but cannot parse the file correctly 5 | // TODO: If getting Symbol error, it's because you're trying to import ts-node from typescript-json-schema 6 | // export * as tjs from "https://esm.sh/typescript-json-schema@0.62.0?bundle-deps&keep-names"; 7 | // export * as tjs from "https://esm.sh/@rivet-gg/typescript-json-schema@v0.64.0-rc.1?bundle-deps&target=deno"; 8 | -------------------------------------------------------------------------------- /src/artifacts/util.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "../deps.ts"; 2 | import { UnreachableError } from "../error/mod.ts"; 3 | import { glob } from "./deps.ts"; 4 | 5 | /** 6 | * Path to the root of the repo. Used for reading & writing files to the 7 | * project. 8 | */ 9 | export function rootSrcPath() { 10 | const dirname = import.meta.dirname; 11 | if (!dirname) throw new Error("Missing dirname"); 12 | 13 | return resolve(dirname, "..", ".."); 14 | } 15 | 16 | /** 17 | * @param files Files to include in the archive. Usually generated using glob. 18 | * @param outputPath JSON file to write the archive to. 19 | */ 20 | export async function buildArtifacts( 21 | { rootPath, patterns, outputPath, globOpts, encode }: { 22 | rootPath: string; 23 | patterns: string[]; 24 | outputPath: string; 25 | globOpts?: any; 26 | encode?: "base64" | "string"; 27 | }, 28 | ) { 29 | encode = encode ?? "base64"; 30 | 31 | // Glob files 32 | const files = await glob.glob(patterns, { 33 | cwd: rootPath, 34 | nodir: true, 35 | ...globOpts, 36 | }); 37 | 38 | // Build object 39 | const archiveFiles: Record = {}; 40 | for (const file of files) { 41 | if (encode == "base64") { 42 | const data = await Deno.readFile(resolve(rootPath, file)); 43 | const base64String = btoa(new Uint8Array(data).reduce((acc, byte) => acc + String.fromCharCode(byte), "")); 44 | archiveFiles[file] = base64String; 45 | } else if (encode == "string") { 46 | archiveFiles[file] = await Deno.readTextFile(resolve(rootPath, file)); 47 | } else { 48 | throw new UnreachableError(encode); 49 | } 50 | } 51 | 52 | // Write schema to file 53 | await Deno.writeTextFile( 54 | outputPath, 55 | JSON.stringify(archiveFiles), 56 | ); 57 | 58 | // Print largest files. File sizes are in encoded format. 59 | console.log("Largest files:"); 60 | Object.keys(archiveFiles) 61 | .sort((a, b) => archiveFiles[b].length - archiveFiles[a].length).slice(0, 10) 62 | .forEach((key) => console.log(key, Math.ceil(archiveFiles[key].length / 1000) + "KB")); 63 | } 64 | -------------------------------------------------------------------------------- /src/build/deno_config.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "../deps.ts"; 2 | import { Project } from "../project/mod.ts"; 3 | 4 | export async function generateDenoConfig(project: Project) { 5 | // Build config 6 | const config = { 7 | "lint": { 8 | "include": ["src/"], 9 | "exclude": ["tests/"], 10 | "rules": { 11 | "exclude": ["no-empty-interface", "no-explicit-any", "require-await"], 12 | }, 13 | }, 14 | "fmt": { 15 | "useTabs": true, 16 | }, 17 | }; 18 | 19 | // Write config 20 | const configPath = resolve(project.path, "deno.json"); 21 | await Deno.writeTextFile(configPath, JSON.stringify(config, null, 4)); 22 | } 23 | -------------------------------------------------------------------------------- /src/build/deps.ts: -------------------------------------------------------------------------------- 1 | export * as tjs from "npm:typescript-json-schema@0.62.0"; 2 | 3 | import Ajv from "https://esm.sh/ajv@^8.12.0"; 4 | export { Ajv }; 5 | 6 | import dedent from "https://esm.sh/dedent@^1.5.1"; 7 | export { dedent }; 8 | 9 | export * as colors from "https://deno.land/std@0.208.0/fmt/colors.ts"; 10 | -------------------------------------------------------------------------------- /src/build/gen.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinevue/game-backend/eeda24d105336cb4d64ab515ee2064f6cf714994/src/build/gen.ts -------------------------------------------------------------------------------- /src/build/gen/mod.ts: -------------------------------------------------------------------------------- 1 | export { GeneratedCodeBuilder } from "./code_builder.ts"; 2 | 3 | export { compileModuleHelper } from "./module.ts"; 4 | 5 | export { compilePublic } from "./public.ts"; 6 | export { compileActorTypeHelpers, compileTypeHelpers } from "./type.ts"; 7 | -------------------------------------------------------------------------------- /src/build/gen/public.ts: -------------------------------------------------------------------------------- 1 | import { genModulePublicInternal, Project, publicPath } from "../../project/mod.ts"; 2 | import { GeneratedCodeBuilder } from "./mod.ts"; 3 | import { exists } from "../../deps.ts"; 4 | import { genModulePublicExternal } from "../../project/project.ts"; 5 | import { camelify } from "../../types/case_conversions.ts"; 6 | 7 | export async function compilePublic(project: Project) { 8 | // Export public data in internal module 9 | // 10 | // These will be re-exported in external modules 11 | for (const module of project.modules.values()) { 12 | const reexporter = new GeneratedCodeBuilder(genModulePublicInternal(project, module), 2); 13 | 14 | // If it exists, reexport the `public.ts` file for this module 15 | if (await exists(publicPath(module), { isFile: true })) { 16 | reexporter.append` 17 | export * from ${JSON.stringify(reexporter.relative(publicPath(module)))}; 18 | `; 19 | } 20 | 21 | // Export the module's name as a constant 22 | reexporter.append`export const __CANONICAL_MODULE_NAME = "${module.name}";`; 23 | 24 | await reexporter.write(); 25 | } 26 | 27 | // Re-export public modules for module.gen.ts. This gets imported as 28 | // `Module` in the module.gen.ts. 29 | // 30 | // This exports the dependencies (the `genModulePublicInternal` files) with 31 | // their given module names. 32 | for (const module of project.modules.values()) { 33 | const reexporter = new GeneratedCodeBuilder(genModulePublicExternal(project, module), 2); 34 | 35 | for (const depName of Object.keys(module.config.dependencies || {})) { 36 | const dependency = project.modules.get(depName)!; 37 | 38 | const internalPath = reexporter.relative(genModulePublicInternal(project, dependency)); 39 | 40 | reexporter.append` 41 | // Reexports public utils for ${depName} 42 | export * as ${camelify(depName)} from ${JSON.stringify(internalPath)}; 43 | `; 44 | } 45 | 46 | await reexporter.write(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/build/inflate_runtime_archive.ts: -------------------------------------------------------------------------------- 1 | import runtimeArchive from "../../artifacts/runtime_archive.json" with { type: "json" }; 2 | import { dirname, emptyDir, resolve } from "../deps.ts"; 3 | import { Project } from "../project/mod.ts"; 4 | import { genPath, RUNTIME_PATH } from "../project/project.ts"; 5 | 6 | /** 7 | * Writes a copy of the OpenGB runtime bundled with the CLI to the project. 8 | */ 9 | export async function inflateRuntimeArchive(project: Project, signal?: AbortSignal) { 10 | signal?.throwIfAborted(); 11 | 12 | const inflateRuntimePath = genPath(project, RUNTIME_PATH); 13 | 14 | await emptyDir(inflateRuntimePath); 15 | 16 | for (const [file, value] of Object.entries(runtimeArchive)) { 17 | signal?.throwIfAborted(); 18 | 19 | const absPath = resolve(inflateRuntimePath, file); 20 | await Deno.mkdir(dirname(absPath), { recursive: true }); 21 | await Deno.writeTextFile(absPath, value); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/build/meta.ts: -------------------------------------------------------------------------------- 1 | import { ModuleConfig, ScriptConfig } from "../config/module.ts"; 2 | import { ProjectConfig } from "../config/project.ts"; 3 | import { RegistryConfig } from "../config/project.ts"; 4 | import { tjs } from "../deps.ts"; 5 | import { hasUserConfigSchema, Project } from "../project/mod.ts"; 6 | import { genPath, META_PATH } from "../project/project.ts"; 7 | import { camelify, pascalify } from "../types/case_conversions.ts"; 8 | 9 | export interface ProjectMeta { 10 | config: ProjectConfig; 11 | registries: Record; 12 | modules: Record; 13 | } 14 | 15 | export interface RegistryMeta { 16 | path: string; 17 | name: string; 18 | config: RegistryConfig; 19 | isExternal: boolean; 20 | } 21 | 22 | export interface ModuleMeta { 23 | path: string; 24 | name: string; 25 | nameCamel: string; 26 | namePascal: string; 27 | config: ModuleConfig; 28 | registryName: string; 29 | userConfig: unknown; 30 | userConfigSchema?: tjs.Definition; 31 | scripts: Record; 32 | db?: ModuleDatabaseMeta; 33 | hasUserConfigSchema: boolean; 34 | } 35 | 36 | export interface ModuleDatabaseMeta { 37 | schema: string; 38 | } 39 | 40 | export interface ScriptMeta { 41 | path: string; 42 | name: string; 43 | nameCamel: string; 44 | namePascal: string; 45 | config: ScriptConfig; 46 | requestSchema: tjs.Definition; 47 | responseSchema: tjs.Definition; 48 | } 49 | 50 | /** 51 | * Generates meta file that can be consumed externally to get information about 52 | * this project. For example, this is used to auto-generate docs from external 53 | * tools. 54 | */ 55 | export async function generateMeta(project: Project) { 56 | const registries: Record = Object.fromEntries( 57 | Array.from(project.registries.entries()).map(([name, registry]) => [name, { 58 | path: registry.path, 59 | name: name, 60 | config: registry.config, 61 | isExternal: registry.isExternal, 62 | }]), 63 | ); 64 | 65 | const modules: Record = {}; 66 | for (const module of project.modules.values()) { 67 | modules[module.name] = { 68 | path: module.path, 69 | name: module.name, 70 | nameCamel: camelify(module.name), 71 | namePascal: pascalify(module.name), 72 | config: module.config, 73 | registryName: module.registry.name, 74 | userConfig: module.userConfig, 75 | userConfigSchema: module.userConfigSchema, 76 | scripts: Object.fromEntries( 77 | Array.from(module.scripts.entries()).map(([name, script]) => [name, { 78 | path: script.path, 79 | name: name, 80 | nameCamel: camelify(name), 81 | namePascal: pascalify(name), 82 | config: script.config, 83 | requestSchema: script.requestSchema!, 84 | responseSchema: script.responseSchema!, 85 | }]), 86 | ), 87 | db: module.db, 88 | hasUserConfigSchema: await hasUserConfigSchema(module), 89 | }; 90 | } 91 | 92 | const meta: ProjectMeta = { 93 | config: project.config, 94 | registries, 95 | modules, 96 | }; 97 | 98 | await Deno.writeTextFile( 99 | genPath(project, META_PATH), 100 | JSON.stringify(meta, null, 4), 101 | ); 102 | } 103 | -------------------------------------------------------------------------------- /src/build/misc.ts: -------------------------------------------------------------------------------- 1 | import { dedent } from "./deps.ts"; 2 | 3 | export function autoGenHeader() { 4 | return dedent` 5 | // This file is auto-generated by the Open Game Backend build system. 6 | // Do not edit this file directly. 7 | // 8 | // Generated at ${new Date().toISOString()} 9 | 10 | `; 11 | } 12 | -------------------------------------------------------------------------------- /src/build/mod.ts: -------------------------------------------------------------------------------- 1 | import { Project } from "../project/project.ts"; 2 | import { ensurePostgresRunning } from "../utils/postgres_daemon.ts"; 3 | import { createBuildState, waitForBuildPromises } from "../build_state/mod.ts"; 4 | import { success } from "../term/status.ts"; 5 | import { planProjectBuild } from "./plan/project.ts"; 6 | import { UnreachableError } from "../error/mod.ts"; 7 | 8 | /** 9 | * Which format to use for building. 10 | */ 11 | export enum Format { 12 | Native, 13 | Bundled, 14 | } 15 | 16 | /** 17 | * Which runtime to target when building. 18 | */ 19 | export enum Runtime { 20 | Deno, 21 | CloudflareWorkersPlatforms, 22 | } 23 | 24 | /** 25 | * Which DB driver to use for the runtime. 26 | */ 27 | export enum DbDriver { 28 | NodePostgres, 29 | NeonServerless, 30 | CloudflareHyperdrive, 31 | } 32 | 33 | /** 34 | * Stores options used in the build command. 35 | */ 36 | export interface BuildOpts { 37 | format: Format; 38 | runtime: Runtime; 39 | dbDriver: DbDriver; 40 | /** If true, parse TypeScript to generate JSON schemas to be validated at runtime. */ 41 | strictSchemas: boolean; 42 | /** If true, don't run `deno check` on the generated code. */ 43 | skipDenoCheck: boolean; 44 | /** If exists, run database migrations. */ 45 | migrate?: { 46 | /** If true, run migrations automatically without dev mode. */ 47 | forceDeploy: boolean; 48 | }; 49 | signal?: AbortSignal; 50 | } 51 | 52 | export async function build(project: Project, opts: BuildOpts) { 53 | opts.signal?.throwIfAborted(); 54 | 55 | // Required for `migrateDev` and `migrateDeploy` 56 | await ensurePostgresRunning(project); 57 | 58 | const buildState = await createBuildState(project, opts.signal); 59 | 60 | await planProjectBuild(buildState, project, opts); 61 | 62 | // Wait for any remaining build steps 63 | await waitForBuildPromises(buildState); 64 | 65 | success("Success"); 66 | } 67 | 68 | export function runtimeToString(runtime: Runtime) { 69 | if (runtime == Runtime.Deno) return "Deno"; 70 | if (runtime == Runtime.CloudflareWorkersPlatforms) return "Cloudflare"; 71 | 72 | throw new UnreachableError(runtime); 73 | } 74 | -------------------------------------------------------------------------------- /src/build/module_config_schema.ts: -------------------------------------------------------------------------------- 1 | import { Ajv } from "./deps.ts"; 2 | import { Module, Project } from "../project/mod.ts"; 3 | import { runJob } from "../utils/worker_pool.ts"; 4 | import { WorkerRequest, WorkerResponse } from "./module_config_schema.worker.ts"; 5 | import { createWorkerPool } from "../utils/worker_pool.ts"; 6 | import { hasUserConfigSchema } from "../project/module.ts"; 7 | import { InternalError, UserError } from "../error/mod.ts"; 8 | import { resolve } from "../deps.ts"; 9 | 10 | const WORKER_POOL = createWorkerPool({ 11 | source: import.meta.resolve("./module_config_schema.worker.ts"), 12 | }); 13 | 14 | export interface CompileModuleConfigSchemaOpts { 15 | strictSchemas: boolean; 16 | onStart: () => void; 17 | } 18 | 19 | export async function compileModuleConfigSchema( 20 | _project: Project, 21 | module: Module, 22 | opts: CompileModuleConfigSchemaOpts, 23 | ): Promise { 24 | // Read schema 25 | if (await hasUserConfigSchema(module)) { 26 | if (opts.strictSchemas) { 27 | // Parse schema 28 | const res = await runJob({ pool: WORKER_POOL, request: { module }, onStart: opts.onStart }); 29 | module.userConfigSchema = res.moduleConfigSchema; 30 | } else { 31 | opts.onStart(); 32 | 33 | // No schema 34 | module.userConfigSchema = { 35 | "$schema": "http://json-schema.org/draft-07/schema#", 36 | "type": "object", 37 | "additionalProperties": true, 38 | }; 39 | } 40 | } else { 41 | opts.onStart(); 42 | 43 | // No config 44 | module.userConfigSchema = { 45 | "$schema": "http://json-schema.org/draft-07/schema#", 46 | "type": "object", 47 | "additionalProperties": false, 48 | }; 49 | } 50 | 51 | // Validate config 52 | const moduleConfigAjv = new Ajv({ 53 | schemas: [module.userConfigSchema], 54 | }); 55 | 56 | const moduleConfigSchema = moduleConfigAjv.getSchema("#"); 57 | if (!moduleConfigSchema) { 58 | throw new InternalError("Failed to get root type from user config schema.", { 59 | path: resolve(module.path, "config.ts"), 60 | }); 61 | } 62 | 63 | if (!moduleConfigSchema(module.userConfig)) { 64 | throw new UserError( 65 | `Invalid module config for ${module.name}.`, 66 | { 67 | details: `Config:\n\n${JSON.stringify(module.userConfig, null, 2)}\n\nErrors:\n\n${ 68 | JSON.stringify(moduleConfigSchema.errors, null, 2) 69 | }`, 70 | suggest: `Compare your config with the following schema:\n\n${ 71 | JSON.stringify(module.userConfigSchema, null, 2) 72 | }`, 73 | }, 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/build/module_config_schema.worker.ts: -------------------------------------------------------------------------------- 1 | // Runs synchronise TypeScript code to derive the schema from a script in a 2 | // background worker. 3 | 4 | /// 5 | /// 6 | 7 | import { tjs } from "./deps.ts"; 8 | import { configPath, Module } from "../project/module.ts"; 9 | import { InternalError } from "../error/mod.ts"; 10 | 11 | export interface WorkerRequest { 12 | module: Module; 13 | } 14 | 15 | export interface WorkerResponse { 16 | moduleConfigSchema: tjs.Definition; 17 | } 18 | 19 | self.onmessage = async (ev) => { 20 | const { module } = ev.data as WorkerRequest; 21 | const moduleConfigPath = configPath(module); 22 | 23 | // TODO: Dupe of project.ts 24 | // https://docs.deno.com/runtime/manual/advanced/typescript/configuration#what-an-implied-tsconfigjson-looks-like 25 | const DEFAULT_COMPILER_OPTIONS = { 26 | "allowJs": true, 27 | "esModuleInterop": true, 28 | "experimentalDecorators": false, 29 | "inlineSourceMap": true, 30 | "isolatedModules": true, 31 | "jsx": "react", 32 | "module": "esnext", 33 | "moduleDetection": "force", 34 | "strict": true, 35 | "target": "esnext", 36 | "useDefineForClassFields": true, 37 | 38 | "lib": ["esnext", "dom", "dom.iterable"], 39 | "allowImportingTsExtensions": true, 40 | }; 41 | 42 | const validateConfig: tjs.PartialArgs = { 43 | esModuleInterop: true, 44 | noExtraProps: true, 45 | required: true, 46 | strictNullChecks: true, 47 | 48 | // TODO: Is this needed? 49 | include: [moduleConfigPath], 50 | 51 | // TODO: Figure out how to work without this? Maybe we manually validate the request type exists? 52 | ignoreErrors: true, 53 | }; 54 | 55 | const program = tjs.getProgramFromFiles( 56 | [moduleConfigPath], 57 | DEFAULT_COMPILER_OPTIONS, 58 | ); 59 | 60 | const moduleConfigSchema = tjs.generateSchema( 61 | program, 62 | "Config", 63 | validateConfig, 64 | [moduleConfigPath], 65 | ); 66 | if (moduleConfigSchema === null) { 67 | throw new InternalError("Failed to generate config schema.", { path: moduleConfigPath }); 68 | } 69 | 70 | self.postMessage({ 71 | moduleConfigSchema, 72 | } as WorkerResponse); 73 | }; 74 | -------------------------------------------------------------------------------- /src/build/plan/module.ts: -------------------------------------------------------------------------------- 1 | import { BuildState, buildStep } from "../../build_state/mod.ts"; 2 | import { assertExists, resolve } from "../../deps.ts"; 3 | import { configPath, Module, Project } from "../../project/mod.ts"; 4 | import { compileModuleHelper } from "../gen/mod.ts"; 5 | import { compileModuleConfigSchema } from "../module_config_schema.ts"; 6 | import { planScriptBuild } from "./script.ts"; 7 | import { BuildOpts } from "../mod.ts"; 8 | import { publicPath } from "../../project/module.ts"; 9 | 10 | export async function planModuleBuild( 11 | buildState: BuildState, 12 | project: Project, 13 | module: Module, 14 | opts: BuildOpts, 15 | ) { 16 | buildStep(buildState, { 17 | id: `module.${module.name}.parse`, 18 | name: "Parse", 19 | description: `config.ts`, 20 | module, 21 | condition: { 22 | // TODO: use tjs.getProgramFiles() to get the dependent files? 23 | files: opts.strictSchemas ? [configPath(module)] : [], 24 | expressions: { 25 | strictSchemas: opts.strictSchemas, 26 | }, 27 | }, 28 | delayedStart: true, 29 | async build({ onStart }) { 30 | // Compile schema 31 | // 32 | // This mutates `module` 33 | await compileModuleConfigSchema(project, module, { strictSchemas: opts.strictSchemas, onStart }); 34 | }, 35 | async alreadyCached() { 36 | // Read schema from cache 37 | const schema = buildState.cache.persist.moduleConfigSchemas[module.name]; 38 | assertExists(schema); 39 | module.userConfigSchema = schema; 40 | }, 41 | async finally() { 42 | assertExists(module.userConfigSchema); 43 | 44 | // Populate cache with response 45 | buildState.cache.persist.moduleConfigSchemas[module.name] = module.userConfigSchema; 46 | }, 47 | }); 48 | 49 | buildStep(buildState, { 50 | id: `module.${module.name}.generate`, 51 | name: "Generate", 52 | description: `module.gen.ts`, 53 | module, 54 | condition: { 55 | files: [resolve(module.path, "module.json"), configPath(module), publicPath(module)], 56 | expressions: { 57 | db: !!module.db, 58 | }, 59 | }, 60 | async build() { 61 | await compileModuleHelper(project, module); 62 | }, 63 | }); 64 | 65 | for (const script of module.scripts.values()) { 66 | await planScriptBuild(buildState, project, module, script, opts); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/build/plan/script.ts: -------------------------------------------------------------------------------- 1 | import { BuildState, buildStep } from "../../build_state/mod.ts"; 2 | import { assertExists } from "../../deps.ts"; 3 | import { Module, Project, Script } from "../../project/mod.ts"; 4 | import { compileScriptSchema } from "../script_schema.ts"; 5 | import { BuildOpts } from "../mod.ts"; 6 | 7 | export async function planScriptBuild( 8 | buildState: BuildState, 9 | project: Project, 10 | module: Module, 11 | script: Script, 12 | opts: BuildOpts, 13 | ) { 14 | buildStep(buildState, { 15 | id: `module.${module.name}.script.${script.name}.parse`, 16 | name: "Parse", 17 | description: `${script.name}.ts`, 18 | module, 19 | script, 20 | condition: { 21 | // TODO: This module and all of its dependent scripts. Use tjs.getProgramFiles() to get the dependent files? 22 | files: [ 23 | // If the script is modified 24 | script.path, 25 | ], 26 | expressions: { 27 | strictSchemas: opts.strictSchemas, 28 | }, 29 | }, 30 | delayedStart: true, 31 | async build({ onStart }) { 32 | // Compile schema 33 | // 34 | // This mutates `script` 35 | await compileScriptSchema(project, module, script, { strictSchemas: opts.strictSchemas, onStart }); 36 | }, 37 | async alreadyCached() { 38 | // Read schemas from cache 39 | const schemas = buildState.cache.persist.scriptSchemas[module.name][script.name]; 40 | assertExists(schemas); 41 | script.requestSchema = schemas.request; 42 | script.responseSchema = schemas.response; 43 | }, 44 | async finally() { 45 | assertExists(script.requestSchema); 46 | assertExists(script.responseSchema); 47 | 48 | // Populate cache with response 49 | if (!buildState.cache.persist.scriptSchemas[module.name]) { 50 | buildState.cache.persist.scriptSchemas[module.name] = {}; 51 | } 52 | buildState.cache.persist.scriptSchemas[module.name][script.name] = { 53 | request: script.requestSchema, 54 | response: script.responseSchema, 55 | }; 56 | }, 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /src/build/script_schema.ts: -------------------------------------------------------------------------------- 1 | import { Module, Project, Script } from "../project/mod.ts"; 2 | import { runJob } from "../utils/worker_pool.ts"; 3 | import { WorkerRequest, WorkerResponse } from "./script_schema.worker.ts"; 4 | import { createWorkerPool } from "../utils/worker_pool.ts"; 5 | 6 | const WORKER_POOL = createWorkerPool({ 7 | source: import.meta.resolve("./script_schema.worker.ts"), 8 | }); 9 | 10 | export interface CompileScriptSchemaOpts { 11 | strictSchemas: boolean; 12 | onStart: () => void; 13 | } 14 | 15 | export async function compileScriptSchema( 16 | _project: Project, 17 | _module: Module, 18 | script: Script, 19 | opts: CompileScriptSchemaOpts, 20 | ): Promise { 21 | if (opts.strictSchemas) { 22 | // Parse schema 23 | const res = await runJob({ pool: WORKER_POOL, request: { script }, onStart: opts.onStart }); 24 | script.requestSchema = res.requestSchema; 25 | script.responseSchema = res.responseSchema; 26 | } else { 27 | opts.onStart(); 28 | 29 | // No schema 30 | script.requestSchema = { 31 | "$schema": "http://json-schema.org/draft-07/schema#", 32 | "type": "object", 33 | "additionalProperties": true, 34 | }; 35 | script.responseSchema = { 36 | "$schema": "http://json-schema.org/draft-07/schema#", 37 | "type": "object", 38 | "additionalProperties": true, 39 | }; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/build/script_schema.worker.ts: -------------------------------------------------------------------------------- 1 | // Runs synchronise TypeScript code to derive the schema from a script in a 2 | // background worker. 3 | 4 | /// 5 | /// 6 | 7 | import { tjs } from "./deps.ts"; 8 | import { Script } from "../project/mod.ts"; 9 | import { InternalError } from "../error/mod.ts"; 10 | 11 | export interface WorkerRequest { 12 | script: Script; 13 | } 14 | 15 | export interface WorkerResponse { 16 | requestSchema: tjs.Definition; 17 | responseSchema: tjs.Definition; 18 | } 19 | 20 | self.onmessage = async (ev) => { 21 | const { script } = ev.data as WorkerRequest; 22 | 23 | // TODO: Dupe of project.ts 24 | // https://docs.deno.com/runtime/manual/advanced/typescript/configuration#what-an-implied-tsconfigjson-looks-like 25 | const DEFAULT_COMPILER_OPTIONS = { 26 | "allowJs": true, 27 | "esModuleInterop": true, 28 | "experimentalDecorators": false, 29 | "inlineSourceMap": true, 30 | "isolatedModules": true, 31 | "jsx": "react", 32 | "module": "esnext", 33 | "moduleDetection": "force", 34 | "strict": true, 35 | "target": "esnext", 36 | "useDefineForClassFields": true, 37 | 38 | "lib": ["esnext", "dom", "dom.iterable"], 39 | "allowImportingTsExtensions": true, 40 | }; 41 | 42 | const validateConfig: tjs.PartialArgs = { 43 | esModuleInterop: true, 44 | noExtraProps: true, 45 | required: true, 46 | strictNullChecks: true, 47 | 48 | // TODO: Is this needed? 49 | include: [script.path], 50 | 51 | // TODO: Figure out how to work without this? Maybe we manually validate the request type exists? 52 | ignoreErrors: true, 53 | }; 54 | 55 | const program = tjs.getProgramFromFiles( 56 | [script.path], 57 | DEFAULT_COMPILER_OPTIONS, 58 | ); 59 | 60 | const requestSchema = tjs.generateSchema( 61 | program, 62 | "Request", 63 | validateConfig, 64 | [script.path], 65 | ); 66 | if (requestSchema === null) { 67 | throw new InternalError("Failed to generate request schema.", { path: script.path }); 68 | } 69 | 70 | const responseSchema = tjs.generateSchema( 71 | program, 72 | "Response", 73 | validateConfig, 74 | [script.path], 75 | ); 76 | if (responseSchema === null) { 77 | throw new InternalError( 78 | "Failed to generate response schema.", 79 | { path: script.path }, 80 | ); 81 | } 82 | 83 | self.postMessage({ 84 | requestSchema, 85 | responseSchema, 86 | } as WorkerResponse); 87 | }; 88 | -------------------------------------------------------------------------------- /src/build/util.ts: -------------------------------------------------------------------------------- 1 | import { dirname, emptyDir, resolve } from "../deps.ts"; 2 | import { UnreachableError } from "../error/mod.ts"; 3 | 4 | /** 5 | * Extract a JSON archive built by the src/artifacts/* scripts. 6 | */ 7 | export async function inflateArchive( 8 | archive: Record, 9 | outputPath: string, 10 | encode: "base64" | "string", 11 | signal?: AbortSignal, 12 | ) { 13 | signal?.throwIfAborted(); 14 | 15 | await emptyDir(outputPath); 16 | 17 | for (const [file, value] of Object.entries(archive)) { 18 | signal?.throwIfAborted(); 19 | 20 | const absPath = resolve(outputPath, file); 21 | await Deno.mkdir(dirname(absPath), { recursive: true }); 22 | 23 | if (encode == "base64") { 24 | const decodedData = atob(value); 25 | const uint8Array = new Uint8Array(decodedData.length).map((_, i) => decodedData.charCodeAt(i)); 26 | await Deno.writeFile(absPath, uint8Array); 27 | } else if (encode == "string") { 28 | await Deno.writeTextFile(absPath, value); 29 | } else { 30 | throw new UnreachableError(encode); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/build_state/deps.ts: -------------------------------------------------------------------------------- 1 | export { encodeHex } from "https://deno.land/std@0.208.0/encoding/hex.ts"; 2 | export { crypto } from "https://deno.land/std@0.208.0/crypto/mod.ts"; 3 | -------------------------------------------------------------------------------- /src/cli/commands/clean.ts: -------------------------------------------------------------------------------- 1 | import { Command } from "../deps.ts"; 2 | import { GlobalOpts, initProject } from "../common.ts"; 3 | import { cleanProject } from "../../project/project.ts"; 4 | 5 | export const cleanCommand = new Command() 6 | .description("Removes all build artifacts") 7 | .action( 8 | async (opts) => { 9 | const project = await initProject(opts); 10 | await cleanProject(project); 11 | }, 12 | ); 13 | -------------------------------------------------------------------------------- /src/cli/commands/create.ts: -------------------------------------------------------------------------------- 1 | import { Command } from "../deps.ts"; 2 | import { GlobalOpts, initProject } from "../common.ts"; 3 | import { templateScript } from "../../template/script.ts"; 4 | import { templateModule } from "../../template/module.ts"; 5 | import { validateIdentifier } from "../../types/identifiers/mod.ts"; 6 | import { Casing } from "../../types/identifiers/defs.ts"; 7 | import { templateTest } from "../../template/test.ts"; 8 | 9 | export const createCommand = new Command() 10 | .description("Create a new module or script"); 11 | 12 | createCommand.action(() => createCommand.showHelp()); 13 | 14 | createCommand.command("module").arguments("").action( 15 | async (opts, module) => { 16 | validateIdentifier( 17 | module, 18 | Casing.Snake, 19 | ); 20 | 21 | await templateModule(await initProject(opts), module); 22 | }, 23 | ); 24 | 25 | createCommand.command("script").arguments("