├── .changeset ├── README.md └── config.json ├── .dockerignore ├── .env.example ├── .env.infisical ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── logs.yml │ ├── proxy.yml │ └── webapp.yml ├── .gitignore ├── .idea ├── .gitignore ├── inspectionProfiles │ └── Project_Default.xml ├── modules.xml ├── turborepo-shuken.iml └── vcs.xml ├── .vscode └── launch.json ├── CHANGESETS.md ├── LICENSE ├── README.md ├── apps ├── logs │ ├── .env.infisical │ ├── .gitignore │ ├── .taprc │ ├── Dockerfile │ ├── README.md │ ├── database │ │ ├── migrations │ │ │ ├── 20221103115115_init │ │ │ │ └── migration.sql │ │ │ ├── 20221108125652_add_request_id_to_logs │ │ │ │ └── migration.sql │ │ │ ├── 20221109220606_add_environment_to_logs │ │ │ │ └── migration.sql │ │ │ └── migration_lock.toml │ │ └── schema.prisma │ ├── fly.toml │ ├── package.json │ ├── src │ │ ├── app.ts │ │ ├── plugins │ │ │ ├── README.md │ │ │ └── sensible.ts │ │ ├── routes │ │ │ ├── README.md │ │ │ ├── caching │ │ │ │ └── get.ts │ │ │ ├── healthcheck │ │ │ │ └── healthcheck.ts │ │ │ ├── logs │ │ │ │ ├── create.ts │ │ │ │ └── get.ts │ │ │ └── stats │ │ │ │ └── projects │ │ │ │ └── projects.ts │ │ ├── types.ts │ │ └── utilities │ │ │ ├── log-conversion.ts │ │ │ ├── named-sql.ts │ │ │ └── test-utilities.ts │ ├── test │ │ ├── helper.ts │ │ ├── routes │ │ │ ├── cache.get.test.ts │ │ │ ├── log.create.test.ts │ │ │ ├── log.get.test.ts │ │ │ └── root.test.ts │ │ └── tsconfig.json │ └── tsconfig.json ├── proxy │ ├── .env.example │ ├── .env.infisical │ ├── README.md │ ├── bindings.d.ts │ ├── build.js │ ├── package.json │ ├── src │ │ ├── constants.ts │ │ ├── index.ts │ │ ├── logger.ts │ │ ├── proxy.ts │ │ └── response.ts │ ├── test │ │ ├── globals.d.ts │ │ ├── logs.otherMimeTypes.test.ts │ │ ├── logs.test.ts │ │ ├── proxy.test.ts │ │ ├── response.test.ts │ │ └── tsconfig.json │ ├── tsconfig.json │ ├── vitest.config.ts │ └── wrangler.toml └── webapp │ ├── .env │ ├── .env.infisical │ ├── .eslintrc │ ├── .gitignore │ ├── .prettierignore │ ├── Dockerfile │ ├── README.md │ ├── app │ ├── assets │ │ └── images │ │ │ ├── api-logos │ │ │ ├── logo-github.png │ │ │ ├── logo-google.png │ │ │ ├── logo-hubspot.png │ │ │ ├── logo-salesforce.png │ │ │ ├── logo-sendgrid.png │ │ │ ├── logo-shopify.png │ │ │ ├── logo-skyscanner.png │ │ │ ├── logo-slack.png │ │ │ ├── logo-stripe.png │ │ │ ├── logo-twilio.png │ │ │ ├── logo-twitter.png │ │ │ ├── logo-xero.png │ │ │ └── logo-zendesk.png │ │ │ ├── apihero-small.png │ │ │ └── founders │ │ │ ├── dan.jpg │ │ │ ├── eric.jpg │ │ │ ├── james.jpg │ │ │ └── matt.jpg │ ├── components │ │ ├── EmptyState.tsx │ │ ├── ErrorDisplay.tsx │ │ ├── filters │ │ │ ├── DateRangeSelector.tsx │ │ │ └── FilterMenu.tsx │ │ └── tags.ts │ ├── db.server.ts │ ├── entry.client.tsx │ ├── entry.server.tsx │ ├── env.server.ts │ ├── lib.es5.d.ts │ ├── libraries │ │ ├── client │ │ │ ├── example │ │ │ │ └── example1.ts │ │ │ ├── index.ts │ │ │ └── src │ │ │ │ ├── client.ts │ │ │ │ ├── endpoint.ts │ │ │ │ ├── fetch.ts │ │ │ │ ├── gateway.ts │ │ │ │ ├── react.tsx │ │ │ │ └── types.ts │ │ ├── common │ │ │ ├── index.ts │ │ │ └── src │ │ │ │ ├── components │ │ │ │ ├── Buttons.tsx │ │ │ │ ├── DragBar.tsx │ │ │ │ ├── HTTPMethod.tsx │ │ │ │ ├── JSONHeroButton.tsx │ │ │ │ ├── KeyValueList.tsx │ │ │ │ ├── Pagination.tsx │ │ │ │ ├── Select.tsx │ │ │ │ ├── Spinner.tsx │ │ │ │ ├── StyledTabs.tsx │ │ │ │ ├── SwitchPanelPositionButton.tsx │ │ │ │ └── editor │ │ │ │ │ ├── JSONEditor.tsx │ │ │ │ │ ├── JavascriptEditor.tsx │ │ │ │ │ ├── codeMirrorSetup.ts │ │ │ │ │ └── codeMirrorTheme.ts │ │ │ │ ├── hooks │ │ │ │ ├── useCurrentProject.ts │ │ │ │ ├── useCurrentWorkspace.ts │ │ │ │ ├── useLogs.ts │ │ │ │ ├── usePanelPosition.ts │ │ │ │ └── useUser.ts │ │ │ │ ├── multi-sort.ts │ │ │ │ └── utilities │ │ │ │ ├── convert.ts │ │ │ │ ├── dates.ts │ │ │ │ ├── formData.ts │ │ │ │ └── prisma-utilities.ts │ │ ├── images │ │ │ └── ui │ │ │ │ ├── dashboard-disabled.png │ │ │ │ ├── switch-panel-icon-bottom.svg │ │ │ │ ├── switch-panel-icon-left.svg │ │ │ │ ├── switch-panel-icon-right.svg │ │ │ │ ├── switch-panel-icon-top.svg │ │ │ │ └── switch-panel-icon.svg │ │ ├── logging │ │ │ └── LogsTable.tsx │ │ ├── request-response-viewer │ │ │ ├── index.ts │ │ │ └── src │ │ │ │ ├── InputParameters.tsx │ │ │ │ ├── LogViewer.tsx │ │ │ │ ├── RequestResponseViewer.tsx │ │ │ │ └── RequestViewer.tsx │ │ ├── response │ │ │ ├── index.ts │ │ │ └── src │ │ │ │ └── types.ts │ │ └── ui │ │ │ ├── index.ts │ │ │ └── src │ │ │ └── components │ │ │ ├── ApiLogoContainer.tsx │ │ │ ├── Breadcrumb.tsx │ │ │ ├── Buttons │ │ │ ├── Buttons.tsx │ │ │ └── CopyTextButton.tsx │ │ │ ├── ComboBox.tsx │ │ │ ├── CopyText.tsx │ │ │ ├── CopyTextButton.tsx │ │ │ ├── DataAvgDuration.tsx │ │ │ ├── DataErrorCount.tsx │ │ │ ├── DataUptime.tsx │ │ │ ├── DocumentationIcon.tsx │ │ │ ├── FeedbackMenu.tsx │ │ │ ├── Footer.tsx │ │ │ ├── Forms.tsx │ │ │ ├── Header.tsx │ │ │ ├── Icons │ │ │ ├── DiscordIcon.tsx │ │ │ ├── SwitchPanelIcon.ts │ │ │ ├── SwitchPanelIconBottom.tsx │ │ │ ├── SwitchPanelIconLeft.tsx │ │ │ ├── SwitchPanelIconRight.tsx │ │ │ ├── SwitchPanelIconTop.tsx │ │ │ └── SwitchPanelMenuIcon.tsx │ │ │ ├── InfoPanel.tsx │ │ │ ├── LoginPromoPanel.tsx │ │ │ ├── Logo.tsx │ │ │ ├── LogsFilters.tsx │ │ │ ├── LogsOnboarding.tsx │ │ │ ├── LogsTabs.tsx │ │ │ ├── NavBar.tsx │ │ │ ├── Panel │ │ │ ├── Panel.tsx │ │ │ ├── PanelBody.tsx │ │ │ └── PanelHeader.tsx │ │ │ ├── Primitives │ │ │ ├── Body.tsx │ │ │ ├── BodyBold.tsx │ │ │ ├── ExtraLargeTitle.tsx │ │ │ ├── ExtraSmallBody.tsx │ │ │ ├── FormField.tsx │ │ │ ├── Input.tsx │ │ │ ├── Label.tsx │ │ │ ├── LargeMono.tsx │ │ │ ├── LargeTitle.tsx │ │ │ ├── MediumBody.tsx │ │ │ ├── Mono.tsx │ │ │ ├── SmallBody.tsx │ │ │ ├── SmallMono.tsx │ │ │ ├── SmallSubtitle.tsx │ │ │ ├── SmallTitle.tsx │ │ │ └── Title.tsx │ │ │ ├── ProjectKey.tsx │ │ │ ├── ProjectSettingsMenu.tsx │ │ │ ├── Resizable.tsx │ │ │ ├── ResponseInfo.tsx │ │ │ ├── SchemaEditor.tsx │ │ │ ├── Select.tsx │ │ │ ├── StatusCode.tsx │ │ │ ├── TwoColumnContainer.tsx │ │ │ ├── UserProfileMenu.tsx │ │ │ ├── UserProfilePhoto.tsx │ │ │ ├── WorkspaceMenu.tsx │ │ │ └── login │ │ │ └── GitHubLoginButton.tsx │ ├── mergent.server.ts │ ├── models │ │ ├── admin.server.ts │ │ ├── apiSchema.server.ts │ │ ├── authToken.server.ts │ │ ├── gateway.server.ts │ │ ├── generator.server.ts │ │ ├── httpClient.server.ts │ │ ├── httpClientAuthentication.server.ts │ │ ├── httpEndpoint.server.ts │ │ ├── integration.server.ts │ │ ├── message.server.ts │ │ ├── project.server.ts │ │ ├── release.server.ts │ │ ├── requestLog.server.ts │ │ ├── requestToken.server.ts │ │ ├── security.server.ts │ │ ├── user.server.ts │ │ └── workspace.server.ts │ ├── root.tsx │ ├── routes │ │ ├── __app.tsx │ │ ├── __app │ │ │ ├── index.tsx │ │ │ └── workspaces │ │ │ │ ├── $workspaceSlug │ │ │ │ └── projects │ │ │ │ │ ├── $projectSlug.tsx │ │ │ │ │ ├── $projectSlug │ │ │ │ │ ├── alerts │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── caching │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── home.tsx │ │ │ │ │ ├── home │ │ │ │ │ │ ├── dashboard.tsx │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ └── logs.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── settings │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── new.tsx │ │ │ │ └── new.tsx │ │ ├── admin.tsx │ │ ├── admin │ │ │ ├── index.tsx │ │ │ ├── integrations.tsx │ │ │ ├── integrations │ │ │ │ ├── $id.tsx │ │ │ │ ├── $id │ │ │ │ │ ├── edit.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── new.tsx │ │ │ ├── operations │ │ │ │ ├── $operationId.tsx │ │ │ │ └── $operationId │ │ │ │ │ ├── client.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── security.tsx │ │ │ ├── schemas.tsx │ │ │ └── schemas │ │ │ │ ├── $id.tsx │ │ │ │ ├── $id │ │ │ │ ├── client.tsx │ │ │ │ ├── document.tsx │ │ │ │ ├── download.tsx │ │ │ │ ├── downloadOriginal.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── models.tsx │ │ │ │ ├── models │ │ │ │ │ ├── $modelId.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── operations.tsx │ │ │ │ ├── releases.tsx │ │ │ │ ├── releases │ │ │ │ │ ├── $releaseId.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── new.tsx │ │ │ │ ├── securitySchemes.tsx │ │ │ │ ├── securitySchemes │ │ │ │ │ ├── $securitySchemeId.tsx │ │ │ │ │ ├── $securitySchemeId │ │ │ │ │ │ ├── edit.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── tags.tsx │ │ │ │ ├── $id[.json].tsx │ │ │ │ └── index.tsx │ │ ├── api │ │ │ ├── auth │ │ │ │ ├── requestTokens.$requestToken.ts │ │ │ │ └── requestTokens.ts │ │ │ ├── integrations.ts │ │ │ ├── logs.ts │ │ │ ├── projects │ │ │ │ └── $projectId.check-logs-onboarding.ts │ │ │ ├── workspaces.ts │ │ │ └── workspaces │ │ │ │ └── $workspaceId │ │ │ │ ├── projects.ts │ │ │ │ └── projects │ │ │ │ └── $projectId │ │ │ │ └── clients.ts │ │ ├── auth │ │ │ ├── cli │ │ │ │ └── $requestToken.tsx │ │ │ ├── github.ts │ │ │ └── github │ │ │ │ └── callback.tsx │ │ ├── healthcheck.tsx │ │ ├── legal.tsx │ │ ├── legal │ │ │ ├── abuse.mdx │ │ │ ├── privacy.mdx │ │ │ └── terms.mdx │ │ ├── login.magic.tsx │ │ ├── login.tsx │ │ ├── logos │ │ │ └── $integrationId.tsx │ │ ├── logout.tsx │ │ ├── logs │ │ │ └── $requestId.ts │ │ ├── magic.tsx │ │ ├── projects.$projectId.ts │ │ ├── requestLog │ │ │ └── $logId.$type.ts │ │ ├── resources │ │ │ └── workspaces │ │ │ │ └── $workspaceSlug │ │ │ │ └── projects │ │ │ │ └── $projectSlug │ │ │ │ └── completed-logs-onboarding.ts │ │ ├── sentry │ │ │ ├── frontendError.tsx │ │ │ └── loaderError.ts │ │ └── webhooks │ │ │ └── mailgun.ts │ ├── services │ │ ├── apihero.server.ts │ │ ├── auth.server.ts │ │ ├── authUser.ts │ │ ├── email.server.ts │ │ ├── emailAuth.server.tsx │ │ ├── gitHubAuth.server.ts │ │ ├── integrations │ │ │ └── update.server.ts │ │ ├── logs.server.ts │ │ ├── r2.server.ts │ │ ├── redirectTo.server.ts │ │ ├── session.server.ts │ │ ├── sessionStorage.server.ts │ │ └── upload.server.ts │ ├── utils.test.ts │ ├── utils.ts │ └── utils │ │ └── parseMappingsValue.ts │ ├── cypress.config.ts │ ├── cypress │ ├── .eslintrc.js │ ├── e2e │ │ └── smoke.cy.ts │ ├── fixtures │ │ └── test.json │ ├── support │ │ ├── commands.ts │ │ └── e2e.ts │ └── tsconfig.json │ ├── fly.toml │ ├── mocks │ ├── README.md │ └── index.js │ ├── package.json │ ├── postcss.config.js │ ├── prisma │ ├── migrations │ │ ├── 20220224172159_init │ │ │ └── migration.sql │ │ ├── 20220603162229_added_organisations_workspaces │ │ │ └── migration.sql │ │ ├── 20220603181405_added_workspace_timestamps │ │ │ └── migration.sql │ │ ├── 20220604113326_added_endpoints │ │ │ └── migration.sql │ │ ├── 20220605192230_added_endpoint_variables │ │ │ └── migration.sql │ │ ├── 20220607090933_renamed_workspace_to_project │ │ │ └── migration.sql │ │ ├── 20220607091451_workspace_to_project │ │ │ └── migration.sql │ │ ├── 20220607145803_rename_organization_to_workspace │ │ │ └── migration.sql │ │ ├── 20220608095640_added_endpoint_executions │ │ │ └── migration.sql │ │ ├── 20220610174026_endpoint_variables_default_required_example │ │ │ └── migration.sql │ │ ├── 20220617140257_gateway_client_endpoint │ │ │ └── migration.sql │ │ ├── 20220617141107_client_name_unique │ │ │ └── migration.sql │ │ ├── 20220617143531_endpoint_added_name │ │ │ └── migration.sql │ │ ├── 20220617151029_added_endpoint_method │ │ │ └── migration.sql │ │ ├── 20220617160851_added_endpoint_execution │ │ │ └── migration.sql │ │ ├── 20220617161617_added_endpoint_execution_method │ │ │ └── migration.sql │ │ ├── 20220617162655_added_endpoint_execution_optional_properties │ │ │ └── migration.sql │ │ ├── 20220701120756_execution_id_autoincrement │ │ │ └── migration.sql │ │ ├── 20220701145459_endpoint_add_props │ │ │ └── migration.sql │ │ ├── 20220701174835_endpoint_rename_variables │ │ │ └── migration.sql │ │ ├── 20220701175311_endpoint_variables_json │ │ │ └── migration.sql │ │ ├── 20220704132248_created_project_constants │ │ │ └── migration.sql │ │ ├── 20220704161008_project_constant_value_optional │ │ │ └── migration.sql │ │ ├── 20220705101023_project_constant_multiple_endpoints │ │ │ └── migration.sql │ │ ├── 20220705135154_project_constant_just_project_association │ │ │ └── migration.sql │ │ ├── 20220707094637_add_cache_ttl_to_client │ │ │ └── migration.sql │ │ ├── 20220707094940_add_is_cache_hit_to_execution │ │ │ └── migration.sql │ │ ├── 20220708113318_add_duration_and_response_size_to_executions │ │ │ └── migration.sql │ │ ├── 20220722093243_removed_password_added_github │ │ │ └── migration.sql │ │ ├── 20220722134023_user_name_avatar_url │ │ │ └── migration.sql │ │ ├── 20220805132405_initial_schema_migration │ │ │ └── migration.sql │ │ ├── 20220805152117_add_author_notes_to_schema │ │ │ └── migration.sql │ │ ├── 20220805153153_add_integration_table │ │ │ └── migration.sql │ │ ├── 20220805160507_improved_api_schema_casing │ │ │ └── migration.sql │ │ ├── 20220805161130_add_more_cascades │ │ │ └── migration.sql │ │ ├── 20220805163909_add_identifier_to_security_schemes │ │ │ └── migration.sql │ │ ├── 20220805165511_oath_flows_security_scheme_not_optional │ │ │ └── migration.sql │ │ ├── 20220805170849_not_sure_what_has_changed │ │ │ └── migration.sql │ │ ├── 20220805175835_adding_additional_style_values │ │ │ └── migration.sql │ │ ├── 20220805181221_adding_api_schema_example_table │ │ │ └── migration.sql │ │ ├── 20220806193209_adding_schema_models │ │ │ └── migration.sql │ │ ├── 20220806195302_rename_ref_to_name │ │ │ └── migration.sql │ │ ├── 20220806201919_tags_have_many_operations │ │ │ └── migration.sql │ │ ├── 20220806205627_tag_names_are_unique_per_schema │ │ │ └── migration.sql │ │ ├── 20220808082224_changes_to_schema_responses │ │ │ └── migration.sql │ │ ├── 20220808093750_changes_to_response_body_content │ │ │ └── migration.sql │ │ ├── 20220808095153_make_response_body_like_params │ │ │ └── migration.sql │ │ ├── 20220808095716_fixed_example_relation │ │ │ └── migration.sql │ │ ├── 20220808100838_add_is_array_to_response_and_request │ │ │ └── migration.sql │ │ ├── 20220808101243_example_value_can_be_null │ │ │ └── migration.sql │ │ ├── 20220808205754_add_examples_model_to_request_body_content │ │ │ └── migration.sql │ │ ├── 20220808210555_remove_model_from_request_body_content │ │ │ └── migration.sql │ │ ├── 20220808210722_make_validation_schema_optional │ │ │ └── migration.sql │ │ ├── 20220808213546_link_security_scopes_to_requirements │ │ │ └── migration.sql │ │ ├── 20220809133547_add_sort_index_to_paths_and_operations │ │ │ └── migration.sql │ │ ├── 20220809144832_move_data_to_schema │ │ │ └── migration.sql │ │ ├── 20220810090738_make_schema_optional_on_servers │ │ │ └── migration.sql │ │ ├── 20220810154341_add_summary_to_more_models │ │ │ └── migration.sql │ │ ├── 20220810164155_add_schema_to_response_headers │ │ │ └── migration.sql │ │ ├── 20220810164326_add_unique_index_to_headers │ │ │ └── migration.sql │ │ ├── 20220810164529_headers_can_belong_to_multiple_response_bodies │ │ │ └── migration.sql │ │ ├── 20220811081441_add_ref_to_examples │ │ │ └── migration.sql │ │ ├── 20220811081927_examples_can_belong_to_many │ │ │ └── migration.sql │ │ ├── 20220811084302_add_external_value_to_example │ │ │ └── migration.sql │ │ ├── 20220811084812_cascade_delete_servers │ │ │ └── migration.sql │ │ ├── 20220811141510_add_extensions_to_operations │ │ │ └── migration.sql │ │ ├── 20220812100148_add_schema_change │ │ │ └── migration.sql │ │ ├── 20220812153224_add_cascade_delete_to_changes │ │ │ └── migration.sql │ │ ├── 20220826134629_request_auth_tokens │ │ │ └── migration.sql │ │ ├── 20220826141139_request_auth_token_relation │ │ │ └── migration.sql │ │ ├── 20220826142551_add_releases │ │ │ └── migration.sql │ │ ├── 20220826150731_add_release_created_at │ │ │ └── migration.sql │ │ ├── 20220826152934_add_release_data │ │ │ └── migration.sql │ │ ├── 20220826181236_workspace_project_slug │ │ │ └── migration.sql │ │ ├── 20220826183200_slug_url_friendly │ │ │ └── migration.sql │ │ ├── 20220827175218_add_mappings_to_operation │ │ │ └── migration.sql │ │ ├── 20220830084444_user_admin │ │ │ └── migration.sql │ │ ├── 20220830110246_add_request_log │ │ │ └── migration.sql │ │ ├── 20220830123111_add_request_log_search │ │ │ └── migration.sql │ │ ├── 20220830124818_remove_request_log_id_default │ │ │ └── migration.sql │ │ ├── 20220830125055_request_log_id_string │ │ │ └── migration.sql │ │ ├── 20220830135902_add_http_client_and_endpoint │ │ │ └── migration.sql │ │ ├── 20220830222227_integration_name_keywords_description_documentation │ │ │ └── migration.sql │ │ ├── 20220831154451_add_http_client_authentication │ │ │ └── migration.sql │ │ ├── 20220902102139_add_enabled_to_security_schemes │ │ │ └── migration.sql │ │ ├── 20220902111919_add_caching_options_to_clients_and_endpoints │ │ │ └── migration.sql │ │ ├── 20220906204940_remove_legacy_models │ │ │ └── migration.sql │ │ ├── 20220906212525_add_current_schema │ │ │ └── migration.sql │ │ ├── 20220922102916_use_encrypted_fields_for_auth │ │ │ └── migration.sql │ │ ├── 20221007161505_add_magic_link_auth_method │ │ │ └── migration.sql │ │ ├── 20221013115803_add_logo_image_to_integration │ │ │ └── migration.sql │ │ ├── 20221110191519_project_add_haslogs_onboardingcomplete │ │ │ └── migration.sql │ │ └── migration_lock.toml │ ├── schema.prisma │ └── seed.ts │ ├── public │ ├── favicon.ico │ ├── fonts │ │ └── MonoLisa │ │ │ ├── woff │ │ │ ├── MonoLisa-Regular.woff │ │ │ └── MonoLisa-RegularItalic.woff │ │ │ └── woff2 │ │ │ ├── MonoLisa-Regular.woff2 │ │ │ └── MonoLisa-RegularItalic.woff2 │ └── react-date-range │ │ ├── styles.css │ │ └── theme │ │ └── default.css │ ├── remix.config.js │ ├── remix.env.d.ts │ ├── server.ts │ ├── start.sh │ ├── styles │ └── tailwind-include.css │ ├── tailwind.config.js │ ├── test │ └── setup-test-env.ts │ ├── tsconfig.json │ └── vitest.config.ts ├── config-packages ├── eslint-config-custom-next │ ├── index.js │ └── package.json ├── eslint-config-custom │ ├── index.js │ └── package.json ├── eslint-config-vite │ ├── eslint-preset.js │ └── package.json ├── tailwind-config │ ├── package.json │ ├── postcss.config.js │ └── tailwind.config.js └── tsconfig │ ├── base.json │ ├── nextjs.json │ ├── node18.json │ ├── package.json │ └── react-library.json ├── docker-compose-ci.yml ├── docker-compose.yml ├── examples ├── README.md ├── fetch-load-test │ ├── package.json │ ├── scripts │ │ └── run.sh │ ├── src │ │ └── index.ts │ └── tsconfig.json └── next-github-stars │ ├── .eslintrc.json │ ├── .gitignore │ ├── README.md │ ├── components │ ├── Main.tsx │ └── StarCount.tsx │ ├── next.config.js │ ├── package.json │ ├── pages │ ├── _app.tsx │ ├── api │ │ └── hello.ts │ └── index.tsx │ ├── public │ ├── favicon.ico │ ├── proxyServiceWorker.js │ └── vercel.svg │ ├── src │ └── apihero.ts │ ├── styles │ ├── Home.module.css │ └── globals.css │ └── tsconfig.json ├── package.json ├── packages ├── apihero-browser │ ├── .eslintrc.js │ ├── .gitignore │ ├── CHANGELOG.md │ ├── LICENSE.md │ ├── README.md │ ├── cli │ │ ├── index.js │ │ ├── init.js │ │ └── invariant.js │ ├── config │ │ ├── constants.js │ │ ├── copyServiceWorker.ts │ │ └── plugins │ │ │ └── esbuild │ │ │ └── workerScriptPlugin.ts │ ├── global.d.ts │ ├── package.json │ ├── src │ │ ├── babel-minify.d.ts │ │ ├── browser │ │ │ ├── setupWorker.ts │ │ │ ├── utils │ │ │ │ ├── enableProxying.ts │ │ │ │ ├── formatting.ts │ │ │ │ ├── integrityCheck.ts │ │ │ │ ├── internal.ts │ │ │ │ ├── messageChannel.ts │ │ │ │ └── workerInstance.ts │ │ │ └── worker │ │ │ │ ├── start.ts │ │ │ │ └── stop.ts │ │ ├── constants.ts │ │ ├── index.ts │ │ ├── matcher.ts │ │ ├── proxyServiceWorker.js │ │ └── types.ts │ ├── test │ │ ├── browser │ │ │ ├── setupWorker.example.ts │ │ │ ├── setupWorker.noAllow.example.ts │ │ │ ├── setupWorker.noAllow.test.ts │ │ │ └── setupWorker.test.ts │ │ ├── support │ │ │ ├── certs │ │ │ │ ├── cert.pem │ │ │ │ └── key.pem │ │ │ ├── httpServer.ts │ │ │ └── workerConsole.ts │ │ └── vitest.setup.ts │ ├── tsconfig.json │ ├── tsup.config.ts │ └── vitest.config.ts ├── apihero-fetch │ ├── CHANGELOG.md │ ├── lib │ │ ├── esm │ │ │ ├── index.js │ │ │ └── index.js.map │ │ ├── index.d.ts │ │ ├── index.js │ │ └── index.js.map │ ├── package.json │ ├── src │ │ └── index.ts │ ├── test │ │ ├── createFetchProxy.test.ts │ │ └── support │ │ │ └── httpServer.ts │ ├── tsconfig.json │ ├── tsup.config.ts │ └── vitest.config.ts ├── apihero-node │ ├── .eslintrc.js │ ├── .gitignore │ ├── CHANGELOG.md │ ├── LICENSE.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── matcher.ts │ │ ├── node │ │ │ └── index.ts │ │ └── types.ts │ ├── test │ │ ├── node │ │ │ ├── fetch.allow.test.ts │ │ │ ├── fetch.deny.test.ts │ │ │ ├── fetch.native.test.ts │ │ │ └── fetch.test.ts │ │ └── support │ │ │ ├── certs │ │ │ ├── cert.pem │ │ │ └── key.pem │ │ │ ├── httpServer.ts │ │ │ └── workerConsole.ts │ ├── tsconfig.json │ ├── tsup.config.ts │ └── vitest.config.ts ├── interceptors-js │ ├── .eslintrc.js │ ├── .gitignore │ ├── CHANGELOG.md │ ├── LICENSE.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── InteractiveIsomorphicRequest.ts │ │ ├── index.ts │ │ ├── interceptors │ │ │ ├── ClientRequest │ │ │ │ ├── NodeClientRequest.ts │ │ │ │ ├── http.get.ts │ │ │ │ ├── http.request.ts │ │ │ │ └── index.ts │ │ │ └── fetch │ │ │ │ └── index.ts │ │ ├── types.ts │ │ └── utils │ │ │ └── createImmediateCallback.ts │ ├── test │ │ ├── fetch │ │ │ ├── fetch.intercept.browser.runtime.js │ │ │ ├── fetch.intercept.browser.test.ts │ │ │ ├── fetch.intercept.node.test.ts │ │ │ ├── fetch.intercept.request.browser.test.ts │ │ │ ├── fetch.response.browser.runtime.js │ │ │ ├── fetch.response.browser.test.ts │ │ │ └── fetch.response.node.test.ts │ │ ├── helpers.ts │ │ ├── http │ │ │ ├── http.compliance.constructor.node.test.ts │ │ │ ├── http.intercept.node.test.ts │ │ │ ├── http.response.agents.node.test.ts │ │ │ └── http.response.node.test.ts │ │ ├── vitest.browser.setup.ts │ │ ├── vitest.expect.ts │ │ └── webpack.config.ts │ ├── tsconfig.json │ ├── vitest.browser.config.ts │ └── vitest.node.config.ts ├── internal-constants │ ├── package.json │ └── src │ │ └── index.ts └── internal-logs │ ├── package.json │ ├── src │ └── index.ts │ ├── tsconfig.json │ └── tsup.config.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── turbo.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.2.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "dev", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [ 11 | "proxy", 12 | "webapp", 13 | "logs" 14 | ] 15 | } -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .git 3 | .github 4 | # editor 5 | .idea 6 | # dependencies 7 | node_modules 8 | .pnp 9 | .pnp.js 10 | 11 | # testing 12 | coverage 13 | 14 | # next.js 15 | .next/ 16 | build 17 | 18 | # packages 19 | build 20 | dist 21 | packages/**/dist 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | .turbo 28 | .vercel 29 | .cache 30 | .output 31 | apps/**/public/build 32 | 33 | cypress/screenshots 34 | cypress/videos 35 | 36 | apps/**/styles/tailwind.css 37 | packages/**/styles/tailwind.css -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Environment variables declared in this file are automatically made available to Prisma. 2 | # See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema 3 | 4 | # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB (Preview) and CockroachDB (Preview). 5 | # See the documentation for all the connection string options: https://pris.ly/d/connection-strings 6 | 7 | # Defaults to the local database deployed from the `docker-compose.yml` stack 8 | 9 | DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" 10 | -------------------------------------------------------------------------------- /.env.infisical: -------------------------------------------------------------------------------- 1 | 634968076fd2e26f28e77c66 -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | */**.js 2 | */**.d.ts 3 | packages/*/dist 4 | packages/*/lib -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | // This tells ESLint to load the config from the package `eslint-config-custom` 4 | extends: ["custom"], 5 | settings: { 6 | next: { 7 | rootDir: ["apps/*/"], 8 | }, 9 | }, 10 | parserOptions: { 11 | sourceType: "module", 12 | ecmaVersion: 2020, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | postgres-data 4 | # dependencies 5 | node_modules 6 | .pnp 7 | .pnp.js 8 | 9 | # testing 10 | coverage 11 | 12 | # next.js 13 | .next/ 14 | out/ 15 | build 16 | dist 17 | packages/**/dist 18 | 19 | # Tailwind 20 | apps/**/styles/tailwind.css 21 | packages/**/styles/tailwind.css 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | !packages/apihero-node/test/support/certs/*.pem 27 | !packages/apihero-browser/test/support/certs/*.pem 28 | !packages/apihero-fetch/test/support/certs/*.pem 29 | 30 | # debug 31 | npm-debug.log* 32 | yarn-debug.log* 33 | yarn-error.log* 34 | 35 | # local env files 36 | .env.docker 37 | .env.local 38 | .env.development.local 39 | .env.test.local 40 | .env.production.local 41 | 42 | # turbo 43 | .turbo 44 | .vercel 45 | .cache 46 | .env 47 | .output 48 | apps/**/public/build -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/turborepo-shuken.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CHANGESETS.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | API Hero uses [changesets](https://github.com/changesets/changesets) to manage updated our packages and releasing them to npm. 4 | 5 | ## Release instructions 6 | 7 | Based on the instructions [here](https://github.com/changesets/changesets/blob/main/docs/intro-to-using-changesets.md) 8 | 9 | 1. Run `pnpm run changeset:version` 10 | 2. Run `pnpm run changeset:release` 11 | 12 | ## Adding a changeset 13 | 14 | To add a changeset, use `pnpm run changeset:add` and follow the instructions [here](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md). Please only ever select one of our public packages when adding a changeset, which currently are: 15 | 16 | - `apihero-js` 17 | - `@apihero/interceptors-js` 18 | -------------------------------------------------------------------------------- /apps/logs/.env.infisical: -------------------------------------------------------------------------------- 1 | 63693766172a4d3a78983d49 -------------------------------------------------------------------------------- /apps/logs/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | /logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # 0x 40 | profile-* 41 | 42 | # mac files 43 | .DS_Store 44 | 45 | # vim swap files 46 | *.swp 47 | 48 | # webstorm 49 | .idea 50 | 51 | # vscode 52 | .vscode 53 | *code-workspace 54 | 55 | # clinic 56 | profile* 57 | *clinic* 58 | *flamegraph* 59 | 60 | # generated code 61 | examples/typescript-server.js 62 | test/types/index.js 63 | 64 | # compiled app 65 | dist 66 | -------------------------------------------------------------------------------- /apps/logs/.taprc: -------------------------------------------------------------------------------- 1 | { 2 | "no-check-coverage": true 3 | } 4 | -------------------------------------------------------------------------------- /apps/logs/database/migrations/20221103115115_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- Install TimescaleDB extension 2 | CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE; 3 | 4 | -- CreateEnum 5 | CREATE TYPE "HTTPMethod" AS ENUM ('GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE', 'TRACE'); 6 | 7 | -- CreateTable 8 | CREATE TABLE "Log" ( 9 | "id" TEXT NOT NULL, 10 | "project_id" TEXT NOT NULL, 11 | "method" "HTTPMethod" NOT NULL DEFAULT 'GET', 12 | "status_code" INTEGER NOT NULL, 13 | "base_url" TEXT NOT NULL, 14 | "path" TEXT NOT NULL, 15 | "search" TEXT NOT NULL DEFAULT '', 16 | "request_body" JSONB, 17 | "request_headers" JSONB, 18 | "response_body" JSONB, 19 | "response_headers" JSONB, 20 | "is_cache_hit" BOOLEAN NOT NULL, 21 | "response_size" INTEGER NOT NULL DEFAULT 0, 22 | "request_duration" DOUBLE PRECISION NOT NULL DEFAULT 0, 23 | "gateway_duration" DOUBLE PRECISION NOT NULL DEFAULT 0, 24 | "time" TIMESTAMP(3) NOT NULL 25 | ); 26 | 27 | -- CreateIndex 28 | CREATE UNIQUE INDEX "Log_id_time_key" ON "Log"("id", "time"); 29 | 30 | -- CreateHyperTable 31 | SELECT create_hypertable('"Log"', 'time'); -------------------------------------------------------------------------------- /apps/logs/database/migrations/20221108125652_add_request_id_to_logs/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Log" ADD COLUMN "request_id" TEXT NOT NULL; 3 | 4 | -- CreateIndex 5 | CREATE UNIQUE INDEX "Log_request_id_time_key" ON "Log"("request_id", "time"); 6 | -------------------------------------------------------------------------------- /apps/logs/database/migrations/20221109220606_add_environment_to_logs/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Log" ADD COLUMN "environment" TEXT NOT NULL DEFAULT 'development'; 3 | -------------------------------------------------------------------------------- /apps/logs/database/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /apps/logs/database/schema.prisma: -------------------------------------------------------------------------------- 1 | datasource db { 2 | provider = "postgresql" 3 | url = env("DATABASE_URL") 4 | } 5 | 6 | model Log { 7 | id String @id 8 | 9 | project_id String 10 | request_id String 11 | 12 | method HTTPMethod @default(GET) 13 | status_code Int 14 | base_url String 15 | path String 16 | search String @default("") 17 | 18 | request_body Json? 19 | request_headers Json? 20 | response_body Json? 21 | response_headers Json? 22 | 23 | is_cache_hit Boolean 24 | response_size Int @default(0) 25 | 26 | request_duration Float @default(0) 27 | gateway_duration Float @default(0) 28 | 29 | environment String @default("development") 30 | 31 | time DateTime 32 | 33 | @@unique([id, time]) 34 | @@unique([request_id, time]) 35 | } 36 | 37 | enum HTTPMethod { 38 | GET 39 | HEAD 40 | POST 41 | PUT 42 | PATCH 43 | OPTIONS 44 | DELETE 45 | TRACE 46 | } 47 | -------------------------------------------------------------------------------- /apps/logs/fly.toml: -------------------------------------------------------------------------------- 1 | app = "ah-logs" 2 | kill_signal = "SIGINT" 3 | kill_timeout = 5 4 | processes = [ ] 5 | 6 | [env] 7 | NODE_ENV = "production" 8 | PORT = 8080 9 | SERVER_HOSTNAME = "0.0.0.0" 10 | PRIMARY_REGION = "mia" 11 | 12 | [deploy] 13 | release_command = "pnpx prisma migrate deploy --schema apps/logs/dist/schema.prisma" 14 | 15 | [experimental] 16 | allowed_public_ports = ["8080"] 17 | auto_rollback = true 18 | 19 | [[services]] 20 | internal_port = 8080 21 | processes = [ "app" ] 22 | protocol = "tcp" 23 | script_checks = [ ] 24 | 25 | [services.concurrency] 26 | hard_limit = 25 27 | soft_limit = 20 28 | type = "connections" 29 | 30 | [[services.ports]] 31 | handlers = [ "http" ] 32 | port = 80 33 | force_https = true 34 | 35 | [[services.ports]] 36 | handlers = [ "tls", "http" ] 37 | port = 443 38 | 39 | [[services.http_checks]] 40 | interval = "10s" 41 | grace_period = "5s" 42 | method = "get" 43 | path = "/healthcheck" 44 | protocol = "http" 45 | timeout = "2s" 46 | tls_skip_verify = true 47 | headers = { } 48 | -------------------------------------------------------------------------------- /apps/logs/src/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Plugins Folder 2 | 3 | Plugins define behavior that is common to all the routes in your 4 | application. Authentication, caching, templates, and all the other cross 5 | cutting concerns should be handled by plugins placed in this folder. 6 | 7 | Files in this folder are typically defined through the 8 | [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module, 9 | making them non-encapsulated. They can define decorators and set hooks 10 | that will then be used in the rest of your application. 11 | 12 | Check out: 13 | 14 | * [The hitchhiker's guide to plugins](https://www.fastify.io/docs/latest/Guides/Plugins-Guide/) 15 | * [Fastify decorators](https://www.fastify.io/docs/latest/Reference/Decorators/). 16 | * [Fastify lifecycle](https://www.fastify.io/docs/latest/Reference/Lifecycle/). 17 | -------------------------------------------------------------------------------- /apps/logs/src/plugins/sensible.ts: -------------------------------------------------------------------------------- 1 | import fp from "fastify-plugin"; 2 | import sensible, { SensibleOptions } from "@fastify/sensible"; 3 | 4 | /** 5 | * This plugins adds some utilities to handle http errors 6 | * 7 | * @see https://github.com/fastify/fastify-sensible 8 | */ 9 | export default fp(async (fastify) => { 10 | fastify.register(sensible); 11 | }); 12 | -------------------------------------------------------------------------------- /apps/logs/src/routes/healthcheck/healthcheck.ts: -------------------------------------------------------------------------------- 1 | import { FastifyPluginAsync } from "fastify"; 2 | import { ZodTypeProvider } from "fastify-type-provider-zod"; 3 | import { z } from "zod"; 4 | 5 | const healthcheck: FastifyPluginAsync = async (app, opts): Promise => { 6 | app.withTypeProvider().route({ 7 | method: "GET", 8 | url: "/", 9 | schema: { 10 | headers: z.object({ 11 | host: z.string().optional(), 12 | }), 13 | }, 14 | handler: async (request, reply) => { 15 | const host = request.headers["X-Forwarded-Host"] ?? request.headers.host; 16 | 17 | try { 18 | await app.pg.pool.query("SELECT 1"); 19 | reply.status(200).send("OK"); 20 | } catch (error: unknown) { 21 | console.log("healthcheck ❌", { error }); 22 | reply.status(500).send("ERROR"); 23 | } 24 | }, 25 | }); 26 | }; 27 | 28 | export default healthcheck; 29 | -------------------------------------------------------------------------------- /apps/logs/src/utilities/named-sql.ts: -------------------------------------------------------------------------------- 1 | type QueryReducerArray = [string, any[], number]; 2 | 3 | export function namedParameters( 4 | parameterizedSql: string, 5 | params: Record 6 | ) { 7 | const [text, values] = Object.entries(params).reduce( 8 | ([sql, array, index], [key, value]) => 9 | [ 10 | sql.replace(`:${key}`, `$${index}`), 11 | [...array, value], 12 | index + 1, 13 | ] as QueryReducerArray, 14 | [parameterizedSql, [], 1] as QueryReducerArray 15 | ); 16 | return { text, values }; 17 | } 18 | -------------------------------------------------------------------------------- /apps/logs/test/helper.ts: -------------------------------------------------------------------------------- 1 | // This file contains code that we reuse between our tests. 2 | const helper = require("fastify-cli/helper.js"); 3 | import * as path from "path"; 4 | import * as tap from "tap"; 5 | import Fastify from "fastify"; 6 | 7 | export type Test = typeof tap["Test"]["prototype"]; 8 | 9 | const AppPath = path.join(__dirname, "..", "src", "app.ts"); 10 | 11 | // Fill in this config with all the configurations 12 | // needed for testing the application 13 | async function config() { 14 | return {}; 15 | } 16 | 17 | // Automatically build and tear down our instance 18 | async function build(t: Test) { 19 | // you can set all the options supported by the fastify CLI command 20 | const argv = [AppPath]; 21 | 22 | // fastify-plugin ensures that all decorators 23 | // are exposed for testing purposes, this is 24 | // different from the production setup 25 | const app = (await helper.build(argv, await config())) as ReturnType< 26 | typeof Fastify 27 | >; 28 | 29 | await app.ready(); 30 | 31 | // Tear down our app after we are done 32 | t.teardown(async () => { 33 | await app.close(); 34 | }); 35 | 36 | return app; 37 | } 38 | 39 | export { config, build }; 40 | -------------------------------------------------------------------------------- /apps/logs/test/routes/root.test.ts: -------------------------------------------------------------------------------- 1 | import { test } from "tap"; 2 | import { build } from "../helper"; 3 | 4 | test("default root route", async (t) => { 5 | const app = await build(t); 6 | 7 | const res = await app.inject({ 8 | url: "/", 9 | }); 10 | 11 | t.same(res.statusCode, 404); 12 | }); 13 | -------------------------------------------------------------------------------- /apps/logs/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "noEmit": true 6 | }, 7 | "include": ["../src/**/*.ts", "**/*.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /apps/logs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "declaration": true, 7 | "pretty": true, 8 | "noEmitOnError": true, 9 | "strict": true, 10 | "resolveJsonModule": true, 11 | "removeComments": true, 12 | "esModuleInterop": true, 13 | "newLine": "lf", 14 | "noUnusedLocals": false, 15 | "noFallthroughCasesInSwitch": true, 16 | "useDefineForClassFields": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "skipLibCheck": true, 19 | "lib": ["esnext"], 20 | "outDir": "dist", 21 | "sourceMap": true 22 | }, 23 | "include": ["src/**/*.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /apps/proxy/.env.example: -------------------------------------------------------------------------------- 1 | LOGS_AUTHENTICATION_TOKEN="cla85axw20000pxdlgysabb0f" -------------------------------------------------------------------------------- /apps/proxy/.env.infisical: -------------------------------------------------------------------------------- 1 | 636a415816ff51145835e61d -------------------------------------------------------------------------------- /apps/proxy/bindings.d.ts: -------------------------------------------------------------------------------- 1 | interface Bindings { 2 | LOGS_AUTHENTICATION_TOKEN: string; 3 | LOGS_URL: string; 4 | LOGS_DEBUG: boolean; 5 | } 6 | -------------------------------------------------------------------------------- /apps/proxy/build.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { build } from "esbuild"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = path.dirname(__filename); 7 | 8 | build({ 9 | bundle: true, 10 | sourcemap: true, 11 | format: "esm", 12 | target: "esnext", 13 | external: ["__STATIC_CONTENT_MANIFEST"], 14 | conditions: ["worker", "browser"], 15 | entryPoints: [path.join(__dirname, "src", "index.ts")], 16 | outdir: path.join(__dirname, "dist"), 17 | outExtension: { ".js": ".mjs" }, 18 | }).catch(() => { 19 | process.exit(1); 20 | }); 21 | -------------------------------------------------------------------------------- /apps/proxy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "proxy", 4 | "version": "0.0.0", 5 | "type": "module", 6 | "module": "./dist/index.mjs", 7 | "author": "Matt Aitken", 8 | "license": "MIT", 9 | "scripts": { 10 | "build": "node build.js", 11 | "dev": "miniflare --live-reload --debug --modules dist/index.mjs --wrangler-env local", 12 | "dev:remote": "wrangler dev --env local", 13 | "test": "pnpm run build && NODE_OPTIONS=--experimental-vm-modules npx vitest run", 14 | "typecheck": "tsc && tsc -p tsconfig.json", 15 | "env:pull": "pnpm dlx infisical pull dev" 16 | }, 17 | "dependencies": { 18 | "cuid": "^2.1.8", 19 | "internal-logs": "*", 20 | "itty-router": "^2.6.5", 21 | "itty-router-extras": "^0.4.2", 22 | "zod": "^3.19.1" 23 | }, 24 | "devDependencies": { 25 | "@cloudflare/workers-types": "^3.18.0", 26 | "@types/itty-router-extras": "^0.4.0", 27 | "esbuild": "^0.14.41", 28 | "miniflare": "^2.11.0", 29 | "openapi-types": "^12.0.2", 30 | "prettier": "^2.7.1", 31 | "typescript": "^4.8.4", 32 | "vite": "^3.2.3", 33 | "vitest": "^0.24.1", 34 | "vitest-environment-miniflare": "^2.10.0", 35 | "wrangler": "^2.2.1" 36 | } 37 | } -------------------------------------------------------------------------------- /apps/proxy/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const DESTINATION_HEADER_NAME = "x-ah-origin"; 2 | export const PROJECT_KEY_HEADER_NAME = "x-ah-pk"; 3 | export const REQUEST_ID_HEADER_NAME = "x-ah-request-id"; 4 | export const PROTOCOL_HEADER_NAME = "x-ah-proto"; 5 | export const PAYLOAD_HEADER_NAME = "x-ah-payload"; 6 | -------------------------------------------------------------------------------- /apps/proxy/test/globals.d.ts: -------------------------------------------------------------------------------- 1 | import { describe } from "vitest"; 2 | 3 | declare global { 4 | function setupMiniflareIsolatedStorage(): typeof describe; 5 | } 6 | 7 | export {}; 8 | -------------------------------------------------------------------------------- /apps/proxy/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["@cloudflare/workers-types"] 5 | }, 6 | "include": ["../src/**/*", "**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /apps/proxy/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "lib": ["esnext"], 6 | "types": ["@cloudflare/workers-types"], 7 | "moduleResolution": "node", 8 | "strict": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "baseUrl": "./", 12 | "paths": { 13 | "@/*": ["src/*"], 14 | "internal-logs/*": ["../../packages/internal-logs/src/*"], 15 | "internal-logs": ["../../packages/internal-logs/src/index"] 16 | } 17 | }, 18 | "include": ["src/**/*", "bindings.d.ts"] 19 | } 20 | -------------------------------------------------------------------------------- /apps/proxy/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | 3 | export default defineConfig({ 4 | test: { 5 | environment: "miniflare", 6 | // Configuration is automatically loaded from `.env`, `package.json` and 7 | // `wrangler.toml` files by default, but you can pass any additional Miniflare 8 | // API options here: 9 | // environmentOptions: { 10 | // bindings: { KEY: "value" }, 11 | // kvNamespaces: ["TEST_NAMESPACE"], 12 | // }, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /apps/proxy/wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "apihero-proxy-dev" 2 | main = "dist/index.mjs" 3 | compatibility_date = "2022-09-30" 4 | 5 | [vars] 6 | LOGS_URL = "http://localhost:3001" 7 | LOGS_DEBUG = true 8 | 9 | [env.staging] 10 | name = "apihero-proxy-staging" 11 | routes = [ 12 | { pattern = "proxy-staging.apihero.run", custom_domain = true, zone_name = "apihero.run" } 13 | ] 14 | 15 | [env.staging.vars] 16 | LOGS_URL = "https://ah-logs-staging.fly.dev" 17 | LOGS_DEBUG = true 18 | 19 | [env.production] 20 | name = "apihero-proxy" 21 | routes = [ 22 | { pattern = "proxy.apihero.run", custom_domain = true, zone_name = "apihero.run" } 23 | ] 24 | 25 | [env.production.vars] 26 | LOGS_URL = "https://logs.apihero.run" 27 | LOGS_DEBUG = false 28 | 29 | [build] 30 | command = "node build.js" 31 | 32 | [miniflare] 33 | kv_persist = true 34 | cache_persist = true 35 | 36 | # Required secrets to be set: 37 | # - LOGS_AUTHENTICATION_TOKEN -------------------------------------------------------------------------------- /apps/webapp/.env: -------------------------------------------------------------------------------- 1 | ../../.env -------------------------------------------------------------------------------- /apps/webapp/.env.infisical: -------------------------------------------------------------------------------- 1 | 634997956fd2e26f28e7869f -------------------------------------------------------------------------------- /apps/webapp/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@remix-run/eslint-config", 4 | "@remix-run/eslint-config/node", 5 | "prettier" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /apps/webapp/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | /.cache 4 | /build 5 | /public/build 6 | 7 | /cypress/screenshots 8 | /cypress/videos 9 | 10 | /app/styles/tailwind.css -------------------------------------------------------------------------------- /apps/webapp/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | /build 4 | /public/build 5 | .env 6 | 7 | /cypress/screenshots 8 | /cypress/videos 9 | /postgres-data 10 | 11 | /app/styles/tailwind.css -------------------------------------------------------------------------------- /apps/webapp/README.md: -------------------------------------------------------------------------------- 1 | ## API Hero webapp - powered by Remix 2 | 3 | To start, run with `pnpm run dev --filter webapp` 4 | 5 | ### Build the docker image locally: 6 | 7 | ```sh 8 | pnpm run docker:build:webapp 9 | docker run -it apihero-webapp sh 10 | ``` 11 | -------------------------------------------------------------------------------- /apps/webapp/app/assets/images/api-logos/logo-github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggerdotdev/apihero/2f204e8fc57343a584d8b244d7ad492b72ed1242/apps/webapp/app/assets/images/api-logos/logo-github.png -------------------------------------------------------------------------------- /apps/webapp/app/assets/images/api-logos/logo-google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggerdotdev/apihero/2f204e8fc57343a584d8b244d7ad492b72ed1242/apps/webapp/app/assets/images/api-logos/logo-google.png -------------------------------------------------------------------------------- /apps/webapp/app/assets/images/api-logos/logo-hubspot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggerdotdev/apihero/2f204e8fc57343a584d8b244d7ad492b72ed1242/apps/webapp/app/assets/images/api-logos/logo-hubspot.png -------------------------------------------------------------------------------- /apps/webapp/app/assets/images/api-logos/logo-salesforce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggerdotdev/apihero/2f204e8fc57343a584d8b244d7ad492b72ed1242/apps/webapp/app/assets/images/api-logos/logo-salesforce.png -------------------------------------------------------------------------------- /apps/webapp/app/assets/images/api-logos/logo-sendgrid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggerdotdev/apihero/2f204e8fc57343a584d8b244d7ad492b72ed1242/apps/webapp/app/assets/images/api-logos/logo-sendgrid.png -------------------------------------------------------------------------------- /apps/webapp/app/assets/images/api-logos/logo-shopify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggerdotdev/apihero/2f204e8fc57343a584d8b244d7ad492b72ed1242/apps/webapp/app/assets/images/api-logos/logo-shopify.png -------------------------------------------------------------------------------- /apps/webapp/app/assets/images/api-logos/logo-skyscanner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggerdotdev/apihero/2f204e8fc57343a584d8b244d7ad492b72ed1242/apps/webapp/app/assets/images/api-logos/logo-skyscanner.png -------------------------------------------------------------------------------- /apps/webapp/app/assets/images/api-logos/logo-slack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggerdotdev/apihero/2f204e8fc57343a584d8b244d7ad492b72ed1242/apps/webapp/app/assets/images/api-logos/logo-slack.png -------------------------------------------------------------------------------- /apps/webapp/app/assets/images/api-logos/logo-stripe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggerdotdev/apihero/2f204e8fc57343a584d8b244d7ad492b72ed1242/apps/webapp/app/assets/images/api-logos/logo-stripe.png -------------------------------------------------------------------------------- /apps/webapp/app/assets/images/api-logos/logo-twilio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggerdotdev/apihero/2f204e8fc57343a584d8b244d7ad492b72ed1242/apps/webapp/app/assets/images/api-logos/logo-twilio.png -------------------------------------------------------------------------------- /apps/webapp/app/assets/images/api-logos/logo-twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggerdotdev/apihero/2f204e8fc57343a584d8b244d7ad492b72ed1242/apps/webapp/app/assets/images/api-logos/logo-twitter.png -------------------------------------------------------------------------------- /apps/webapp/app/assets/images/api-logos/logo-xero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggerdotdev/apihero/2f204e8fc57343a584d8b244d7ad492b72ed1242/apps/webapp/app/assets/images/api-logos/logo-xero.png -------------------------------------------------------------------------------- /apps/webapp/app/assets/images/api-logos/logo-zendesk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggerdotdev/apihero/2f204e8fc57343a584d8b244d7ad492b72ed1242/apps/webapp/app/assets/images/api-logos/logo-zendesk.png -------------------------------------------------------------------------------- /apps/webapp/app/assets/images/apihero-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggerdotdev/apihero/2f204e8fc57343a584d8b244d7ad492b72ed1242/apps/webapp/app/assets/images/apihero-small.png -------------------------------------------------------------------------------- /apps/webapp/app/assets/images/founders/dan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggerdotdev/apihero/2f204e8fc57343a584d8b244d7ad492b72ed1242/apps/webapp/app/assets/images/founders/dan.jpg -------------------------------------------------------------------------------- /apps/webapp/app/assets/images/founders/eric.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggerdotdev/apihero/2f204e8fc57343a584d8b244d7ad492b72ed1242/apps/webapp/app/assets/images/founders/eric.jpg -------------------------------------------------------------------------------- /apps/webapp/app/assets/images/founders/james.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggerdotdev/apihero/2f204e8fc57343a584d8b244d7ad492b72ed1242/apps/webapp/app/assets/images/founders/james.jpg -------------------------------------------------------------------------------- /apps/webapp/app/assets/images/founders/matt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggerdotdev/apihero/2f204e8fc57343a584d8b244d7ad492b72ed1242/apps/webapp/app/assets/images/founders/matt.jpg -------------------------------------------------------------------------------- /apps/webapp/app/components/ErrorDisplay.tsx: -------------------------------------------------------------------------------- 1 | export function ErrorDisplay({ errors }: { errors?: string[] }) { 2 | if (!errors) { 3 | return null; 4 | } 5 | 6 | return ( 7 |
8 | {errors?.map((error, index) => ( 9 |

{error}

10 | ))} 11 |
12 | ); 13 | } -------------------------------------------------------------------------------- /apps/webapp/app/components/tags.ts: -------------------------------------------------------------------------------- 1 | const validBackgroundColors = [ 2 | "bg-green-100", 3 | "bg-lime-100", 4 | "bg-yellow-100", 5 | "bg-red-100", 6 | "bg-blue-100", 7 | "bg-indigo-100", 8 | "bg-orange-100", 9 | "bg-pink-100", 10 | "bg-gray-100", 11 | "bg-purple-100", 12 | "bg-teal-100", 13 | ]; 14 | 15 | const validTextColors = [ 16 | "text-green-900", 17 | "text-lime-900", 18 | "text-yellow-900", 19 | "text-red-900", 20 | "text-blue-900", 21 | "text-indigo-900", 22 | "text-orange-900", 23 | "text-pink-900", 24 | "text-gray-900", 25 | "text-purple-900", 26 | "text-teal-900", 27 | ]; 28 | 29 | export function classNamesForTagName(name: string) { 30 | const backgroundColor = 31 | validBackgroundColors[ 32 | Math.abs(hashCode(name)) % validBackgroundColors.length 33 | ]; 34 | const textColor = 35 | validTextColors[Math.abs(hashCode(name)) % validTextColors.length]; 36 | 37 | return `${backgroundColor} ${textColor}`; 38 | } 39 | 40 | function hashCode(str: string) { 41 | let hash = 0; 42 | for (let i = 0; i < str.length; i++) { 43 | hash = str.charCodeAt(i) + ((hash << 5) - hash); 44 | } 45 | return hash; 46 | } 47 | -------------------------------------------------------------------------------- /apps/webapp/app/entry.client.tsx: -------------------------------------------------------------------------------- 1 | import { RemixBrowser, useLocation, useMatches } from "@remix-run/react"; 2 | import { hydrate } from "react-dom"; 3 | import * as Sentry from "@sentry/remix"; 4 | import { useEffect } from "react"; 5 | 6 | hydrate(, document); 7 | 8 | if (process.env.NODE_ENV === "production") { 9 | Sentry.init({ 10 | dsn: "https://a014169306c748b1adf61875c64b90de:a7fa7bfcc28d43e1bd293e121c677e4a@o4504169280569344.ingest.sentry.io/4504169281880064", 11 | tracesSampleRate: 1, 12 | integrations: [ 13 | new Sentry.BrowserTracing({ 14 | routingInstrumentation: Sentry.remixRouterInstrumentation( 15 | useEffect, 16 | useLocation, 17 | useMatches 18 | ), 19 | }), 20 | ], 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /apps/webapp/app/env.server.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | const EnvironmentSchema = z.object({ 4 | MAILGUN_KEY: z.string(), 5 | SENDGRID_FROM_EMAIL: z.string(), 6 | MERGENT_KEY: z.string(), 7 | APP_ORIGIN: z.string().default("https://app.apihero.run"), 8 | LOGS_ORIGIN: z.string().default("https://logs.apihero.run"), 9 | LOGS_API_AUTHENTICATION_TOKEN: z.string(), 10 | APIHERO_PROJECT_KEY: z.string(), 11 | SENTRY_DSN: z 12 | .string() 13 | .default( 14 | "https://a014169306c748b1adf61875c64b90de:a7fa7bfcc28d43e1bd293e121c677e4a@o4504169280569344.ingest.sentry.io/4504169281880064" 15 | ), 16 | }); 17 | 18 | export type Environment = z.infer; 19 | 20 | export const env = EnvironmentSchema.parse(process.env); 21 | -------------------------------------------------------------------------------- /apps/webapp/app/lib.es5.d.ts: -------------------------------------------------------------------------------- 1 | type Falsy = false | 0 | "" | null | undefined; 2 | 3 | interface Array { 4 | /** 5 | * Returns the elements of an array that meet the condition specified in a callback function. 6 | * @param predicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each element in the array. 7 | * @param thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value. 8 | */ 9 | filter( 10 | predicate: BooleanConstructor, 11 | thisArg?: any 12 | ): Exclude[]; 13 | } 14 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/client/example/example1.ts: -------------------------------------------------------------------------------- 1 | import { apiHero } from "../index"; 2 | import { z } from "zod"; 3 | import { createClient } from "../src/client"; 4 | import { createEndpoint } from "../src/react"; 5 | 6 | const gateway = apiHero.gateway({ 7 | gatewayUrl: "http://localhost:3000", 8 | projectId: "cl6ev8x4n0047jrrz3qslq7md", 9 | }); 10 | 11 | const gitHubClient = createClient(gateway, "github", { 12 | baseUrl: "https://api.github.com", 13 | authorization: { 14 | type: "basic", 15 | usernameKey: "GITHUB_USERNAME", 16 | passwordKey: "GITHUB_PASSWORD", 17 | }, 18 | cacheTtl: 10, 19 | }); 20 | 21 | type StarsEndpointProps = { 22 | org: string; 23 | repo: string; 24 | }; 25 | 26 | export const useGetStars = createEndpoint( 27 | gitHubClient, 28 | "get-stars", 29 | "repos/{org}/{repo}", 30 | { 31 | timeout: 1000, 32 | } 33 | ).get( 34 | z 35 | .object({ 36 | id: z.number(), 37 | full_name: z.string(), 38 | stargazers_count: z.number(), 39 | }) 40 | .passthrough() 41 | ); 42 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/client/index.ts: -------------------------------------------------------------------------------- 1 | import { gateway } from "./src/gateway"; 2 | import {} from "./src/client"; 3 | 4 | export * from "./src/types"; 5 | export const apiHero = { gateway }; 6 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/client/src/client.ts: -------------------------------------------------------------------------------- 1 | import type { Client, ClientConfig, Gateway } from "./types"; 2 | 3 | export function createClient( 4 | gateway: Gateway, 5 | name: string, 6 | clientConfig: ClientConfig 7 | ): Client { 8 | const client = { 9 | name: name, 10 | config: clientConfig, 11 | gateway: gateway, 12 | }; 13 | 14 | return client; 15 | } 16 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/client/src/endpoint.ts: -------------------------------------------------------------------------------- 1 | import { fetchRequest } from "./fetch"; 2 | import type { 3 | Client, 4 | EndpointConfig, 5 | HTTPResponse, 6 | ProcedureParserZodEsque, 7 | RequestRecords, 8 | } from "./types"; 9 | 10 | export function createEndpoint( 11 | client: Client, 12 | id: string, 13 | path: string, 14 | config?: EndpointConfig 15 | ): { 16 | get: ( 17 | schema?: ProcedureParserZodEsque | undefined 18 | ) => (props: EndpointProps) => Promise>; 19 | } { 20 | return { 21 | get: ( 22 | schema?: ProcedureParserZodEsque 23 | ) => { 24 | const getFunction = ( 25 | props: EndpointProps 26 | ): Promise> => { 27 | return fetchRequest({ 28 | id, 29 | client, 30 | requestProps: props, 31 | }); 32 | }; 33 | 34 | getFunction.__apiHero = { 35 | id, 36 | method: "GET", 37 | client, 38 | path, 39 | config, 40 | schema, 41 | }; 42 | 43 | return getFunction; 44 | }, 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/client/src/gateway.ts: -------------------------------------------------------------------------------- 1 | import type { Gateway } from "./types"; 2 | 3 | type GatewayConfig = { 4 | gatewayUrl?: string; 5 | projectId: string; 6 | }; 7 | 8 | export function gateway({ 9 | gatewayUrl = "https://apihero.run", 10 | projectId, 11 | }: GatewayConfig): Gateway { 12 | const gateway: Gateway = { 13 | gatewayUrl, 14 | projectId, 15 | }; 16 | return gateway; 17 | } 18 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./src/components/Select"; 2 | export * from "./src/components/DragBar"; 3 | export * from "./src/components/Buttons"; 4 | export * from "./src/components/editor/JSONEditor"; 5 | export * from "./src/components/KeyValueList"; 6 | export * as StyledTabs from "./src/components/StyledTabs"; 7 | export * from "./src/utilities/convert"; 8 | export * from "./src/utilities/dates"; 9 | export * from "./src/hooks/useUser"; 10 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/common/src/components/DragBar.tsx: -------------------------------------------------------------------------------- 1 | import classnames from "classnames"; 2 | 3 | type DragBarProps = { 4 | id?: string; 5 | dir?: string; 6 | isDragging: boolean; 7 | [key: string]: any; 8 | }; 9 | 10 | export function DragBar({ 11 | id = "drag-bar", 12 | dir, 13 | isDragging, 14 | ...props 15 | }: DragBarProps) { 16 | return ( 17 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/common/src/components/HTTPMethod.tsx: -------------------------------------------------------------------------------- 1 | import type { HTTPMethod } from ".prisma/client"; 2 | import classNames from "classnames"; 3 | 4 | type HTTPMethodProps = { 5 | method: HTTPMethod; 6 | className?: string; 7 | }; 8 | 9 | export function HTTPMethodLabel({ method, className }: HTTPMethodProps) { 10 | return ( 11 | 12 | {method} 13 | 14 | ); 15 | } 16 | 17 | export function colorClassNameForMethod(method: HTTPMethod): string { 18 | switch (method) { 19 | case "GET": 20 | return "text-green-500"; 21 | case "HEAD": 22 | return "text-green-500"; 23 | case "POST": 24 | return "text-blue-500"; 25 | case "PUT": 26 | return "text-orange-500"; 27 | case "PATCH": 28 | return "text-yellow-500"; 29 | case "OPTIONS": 30 | return "text-gray-500"; 31 | case "DELETE": 32 | return "text-red-500"; 33 | default: 34 | return "text-gray-500"; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/common/src/components/JSONHeroButton.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "@remix-run/react"; 2 | import Icon from "../../../../assets/images/apihero-small.png"; 3 | 4 | export function JSONHeroButton({ 5 | to, 6 | text = "Open in JSON Hero", 7 | }: { 8 | to: string; 9 | text?: string; 10 | }) { 11 | return ( 12 | 17 | JSON Hero icon 18 | {text} 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/common/src/components/Spinner.tsx: -------------------------------------------------------------------------------- 1 | export function Spinner() { 2 | return ( 3 | 11 | 20 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/common/src/hooks/useCurrentProject.ts: -------------------------------------------------------------------------------- 1 | import type { Project } from ".prisma/client"; 2 | import { useMatches } from "@remix-run/react"; 3 | 4 | export function useCurrentProjectSlug(): string | undefined { 5 | const matches = useMatches(); 6 | const appRoute = matches.find((match) => match.id.endsWith("/$projectSlug")); 7 | return appRoute?.params?.projectSlug; 8 | } 9 | 10 | export function useCurrentProject(): Project | undefined { 11 | const matches = useMatches(); 12 | const appRoute = matches.find((match) => 13 | match.id.endsWith("/$projectSlug/home") 14 | ); 15 | return appRoute?.data?.project; 16 | } 17 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/common/src/hooks/useCurrentWorkspace.ts: -------------------------------------------------------------------------------- 1 | import { useMatches } from "@remix-run/react"; 2 | 3 | export function useCurrentWorkspaceSlug(): string | undefined { 4 | const matches = useMatches(); 5 | const appRoute = matches.find((match) => 6 | match.id.includes("/$workspaceSlug") 7 | ); 8 | return appRoute?.params?.workspaceSlug; 9 | } 10 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/common/src/hooks/useLogs.ts: -------------------------------------------------------------------------------- 1 | import { useMatches } from "@remix-run/react"; 2 | import type { GetLogsSuccessResponse } from "internal-logs"; 3 | 4 | export function useLogs(): GetLogsSuccessResponse | undefined { 5 | const matches = useMatches(); 6 | const appRoute = matches.find((match) => match.id.endsWith("/home")); 7 | 8 | const appRouteData = appRoute?.data; 9 | 10 | if (!appRouteData) { 11 | throw new Error("Could not find app route data"); 12 | } 13 | 14 | if (appRouteData.logs === undefined) { 15 | return undefined; 16 | } 17 | 18 | return appRouteData.logs as GetLogsSuccessResponse; 19 | } 20 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/common/src/hooks/usePanelPosition.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from "react"; 2 | 3 | export type PanelPosition = "top" | "bottom" | "left" | "right"; 4 | 5 | type PanelPositionProps = { 6 | defaultPosition: PanelPosition; 7 | key: string; 8 | }; 9 | 10 | type PanelPositionReturn = [PanelPosition, (position: PanelPosition) => void]; 11 | 12 | export function usePanelPosition({ 13 | defaultPosition, 14 | key, 15 | }: PanelPositionProps): PanelPositionReturn { 16 | const [position, setPosition] = useState(defaultPosition); 17 | const hasLoaded = useRef(false); 18 | 19 | useEffect(() => { 20 | if (!hasLoaded.current) { 21 | return; 22 | } 23 | 24 | localStorage.setItem(key, position); 25 | }, [key, position]); 26 | 27 | useEffect(() => { 28 | hasLoaded.current = true; 29 | const storedPosition = localStorage.getItem(key); 30 | if (storedPosition) { 31 | setPosition(storedPosition as PanelPosition); 32 | } 33 | }, [key]); 34 | 35 | return [position, setPosition]; 36 | } 37 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/common/src/hooks/useUser.ts: -------------------------------------------------------------------------------- 1 | import { useMatches } from "@remix-run/react"; 2 | import type { User } from "~/models/user.server"; 3 | 4 | export function useUser(): User | undefined { 5 | const matches = useMatches(); 6 | const user = matches.find((m) => m.id === "root")?.data.user; 7 | return user; 8 | } 9 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/common/src/multi-sort.ts: -------------------------------------------------------------------------------- 1 | type Sortings = { 2 | key: K; 3 | direction: "asc" | "desc"; 4 | }; 5 | 6 | export function sorted( 7 | array: T[], 8 | sorting: Sortings[] 9 | ): T[] { 10 | const copy = [...array]; 11 | copy.sort((a, b) => { 12 | for (const { key, direction } of sorting) { 13 | if (a[key] < b[key]) { 14 | return direction === "asc" ? -1 : 1; 15 | } 16 | if (a[key] > b[key]) { 17 | return direction === "asc" ? 1 : -1; 18 | } 19 | } 20 | return 0; 21 | }); 22 | 23 | return copy; 24 | } 25 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/common/src/utilities/convert.ts: -------------------------------------------------------------------------------- 1 | export function toObject( 2 | parameters: any[], 3 | keyName: string = "key", 4 | valueName: string = "value" 5 | ): { 6 | [key: string]: string; 7 | } { 8 | return Object.assign( 9 | {}, 10 | ...parameters.map((h) => { 11 | const key = h[keyName]; 12 | const value = h[valueName]; 13 | return { [key]: value }; 14 | }) 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/common/src/utilities/formData.ts: -------------------------------------------------------------------------------- 1 | export function objectToFormData( 2 | object: Record 3 | ): FormData { 4 | const formData = new FormData(); 5 | 6 | for (const key of Object.keys(object)) { 7 | const value = object[key]; 8 | formData.append(key, `${value}`); 9 | } 10 | 11 | return formData; 12 | } 13 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/common/src/utilities/prisma-utilities.ts: -------------------------------------------------------------------------------- 1 | import type { Prisma } from ".prisma/client"; 2 | import { z } from "zod"; 3 | 4 | const StringRecordSchema = z.record(z.string()); 5 | type StringRecord = z.infer; 6 | 7 | export function isStringRecord(value: Prisma.JsonValue): value is StringRecord { 8 | return StringRecordSchema.safeParse(value).success; 9 | } 10 | 11 | const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]); 12 | type Literal = z.infer; 13 | 14 | export type Json = Literal | { [key: string]: Json } | Json[]; 15 | const jsonSchema: z.ZodType = z.lazy(() => 16 | z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)]) 17 | ); 18 | 19 | export function isJson(value: Prisma.JsonValue): value is Json { 20 | return jsonSchema.safeParse(value).success; 21 | } 22 | 23 | export type JsonObject = { [key: string]: Json }; 24 | const jsonObjectSchema = z.record(jsonSchema); 25 | export function isJsonObject(value: Prisma.JsonValue): value is JsonObject { 26 | return jsonObjectSchema.safeParse(value).success; 27 | } 28 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/images/ui/dashboard-disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggerdotdev/apihero/2f204e8fc57343a584d8b244d7ad492b72ed1242/apps/webapp/app/libraries/images/ui/dashboard-disabled.png -------------------------------------------------------------------------------- /apps/webapp/app/libraries/images/ui/switch-panel-icon-bottom.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/images/ui/switch-panel-icon-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/images/ui/switch-panel-icon-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/images/ui/switch-panel-icon-top.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/images/ui/switch-panel-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/request-response-viewer/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./src/RequestResponseViewer"; 2 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/response/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./src/types"; 2 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/response/src/types.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const HTTPResponseStatusSchema = z.object({ 4 | status: z.number(), 5 | duration: z.number().optional(), 6 | size: z.number().optional(), 7 | }); 8 | export type HTTPResponseStatus = z.infer; 9 | 10 | export const HTTPHeadersSchema = z.record(z.string(), z.string()); 11 | export type HTTPHeaders = z.infer; 12 | 13 | export const HTTPResponseSchema = z.object({ 14 | status: z.number(), 15 | duration: z.number().optional(), 16 | size: z.number().optional(), 17 | headers: HTTPHeadersSchema.optional(), 18 | body: z.any().optional(), 19 | }); 20 | 21 | export type HTTPResponse = z.infer; 22 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./src/components/NavBar"; 2 | export * from "./src/components/Header"; 3 | export * from "./src/components/Footer"; 4 | export * from "./src/components/Breadcrumb"; 5 | export * from "./src/components/ResponseInfo"; 6 | export * from "./src/components/SchemaEditor"; 7 | export * as TwoColumn from "./src/components/TwoColumnContainer"; 8 | export * from "./src/components/login/GitHubLoginButton"; 9 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/ApiLogoContainer.tsx: -------------------------------------------------------------------------------- 1 | import { CheckCircleIcon } from "@heroicons/react/24/solid"; 2 | 3 | export type ApiLogoProps = { 4 | ApiLogo: string; 5 | isSupported?: boolean; 6 | }; 7 | 8 | export function ApiLogoContainer({ ApiLogo, isSupported }: ApiLogoProps) { 9 | return ( 10 |
11 | {isSupported === true ? ( 12 | 13 | ) : ( 14 | <> 15 | )} 16 | API logo 17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/CopyText.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from "react"; 2 | 3 | export type CopyTextProps = { 4 | children?: React.ReactNode; 5 | value: string; 6 | className?: string; 7 | onCopied?: () => void; 8 | }; 9 | 10 | export function CopyText({ 11 | children, 12 | value, 13 | className, 14 | onCopied, 15 | }: CopyTextProps) { 16 | const onClick = useCallback(() => { 17 | navigator.clipboard.writeText(value); 18 | if (onCopied) { 19 | onCopied(); 20 | } 21 | }, [value, onCopied]); 22 | 23 | return ( 24 |
25 | {children} 26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/DataErrorCount.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | CheckCircleIcon, 3 | ExclamationCircleIcon, 4 | } from "@heroicons/react/24/solid"; 5 | 6 | export type DataErrorCountProps = { 7 | errorCount: number; 8 | }; 9 | 10 | export function DataErrorCount({ errorCount }: DataErrorCountProps) { 11 | return ( 12 |
13 |

14 | Errors: 15 |

16 | {errorCount === 0 ? ( 17 |
18 | 19 |

{errorCount}

20 |
21 | ) : ( 22 |
23 | 24 |

{errorCount}

25 |
26 | )} 27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/DocumentationIcon.tsx: -------------------------------------------------------------------------------- 1 | import DocumentTextIcon from "@heroicons/react/24/solid/DocumentTextIcon"; 2 | 3 | type DocumentationIconProps = { 4 | onClick?: () => void; 5 | className?: string; 6 | }; 7 | 8 | export function DocumentationIcon({ 9 | onClick, 10 | className, 11 | }: DocumentationIconProps) { 12 | return ( 13 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Icons/SwitchPanelIcon.ts: -------------------------------------------------------------------------------- 1 | import type { ReactSVGElement } from "react"; 2 | 3 | export type SwitchPanelIcon = ReactSVGElement; 4 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Icons/SwitchPanelIconBottom.tsx: -------------------------------------------------------------------------------- 1 | export type SwitchPanelIconBottomProps = { 2 | className?: string; 3 | }; 4 | 5 | export function SwitchPanelIconBottom({ 6 | className, 7 | }: SwitchPanelIconBottomProps) { 8 | return ( 9 | 17 | 22 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Icons/SwitchPanelIconLeft.tsx: -------------------------------------------------------------------------------- 1 | export type SwitchPanelIconLeftProps = { 2 | className?: string; 3 | }; 4 | 5 | export function SwitchPanelIconLeft({ className }: SwitchPanelIconLeftProps) { 6 | return ( 7 | 15 | 20 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Icons/SwitchPanelIconRight.tsx: -------------------------------------------------------------------------------- 1 | export type SwitchPanelIconRightProps = { 2 | className?: string; 3 | }; 4 | 5 | export function SwitchPanelIconRight({ className }: SwitchPanelIconRightProps) { 6 | return ( 7 | 15 | 20 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Icons/SwitchPanelIconTop.tsx: -------------------------------------------------------------------------------- 1 | export type SwitchPanelIconTopProps = { 2 | className?: string; 3 | }; 4 | 5 | export function SwitchPanelIconTop({ className }: SwitchPanelIconTopProps) { 6 | return ( 7 | 15 | 20 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Icons/SwitchPanelMenuIcon.tsx: -------------------------------------------------------------------------------- 1 | export type SwitchPanelIconProps = { 2 | className?: string; 3 | }; 4 | 5 | export function SwitchPanelMenuIcon({ className }: SwitchPanelIconProps) { 6 | return ( 7 | 15 | 20 | 28 | 36 | 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/NavBar.tsx: -------------------------------------------------------------------------------- 1 | import { Breadcrumb } from "./Breadcrumb"; 2 | 3 | export function NavBar() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Panel/Panel.tsx: -------------------------------------------------------------------------------- 1 | export type PanelProps = { 2 | children: React.ReactNode; 3 | className?: string; 4 | }; 5 | 6 | export function Panel({ children, className }: PanelProps) { 7 | return ( 8 |
9 | {children} 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Panel/PanelBody.tsx: -------------------------------------------------------------------------------- 1 | export type PanelBodyProps = { 2 | children: React.ReactNode; 3 | className?: string; 4 | }; 5 | 6 | export function PanelBody({ children, className }: PanelBodyProps) { 7 | return
{children}
; 8 | } 9 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Panel/PanelHeader.tsx: -------------------------------------------------------------------------------- 1 | import classNames from "classnames"; 2 | import { SmallTitle } from "../Primitives/SmallTitle"; 3 | 4 | export type PanelHeaderProps = { 5 | children: React.ReactNode; 6 | className?: string; 7 | }; 8 | 9 | export function PanelHeader({ children, className }: PanelHeaderProps) { 10 | return ( 11 | {children} 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Primitives/Body.tsx: -------------------------------------------------------------------------------- 1 | export type BodyProps = { 2 | children: React.ReactNode; 3 | className?: string; 4 | }; 5 | 6 | export function Body({ children, className }: BodyProps) { 7 | return

{children}

; 8 | } 9 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Primitives/BodyBold.tsx: -------------------------------------------------------------------------------- 1 | export type BodyBoldProps = { 2 | children: React.ReactNode; 3 | className: string; 4 | }; 5 | 6 | export function BodyBold({ children, className }: BodyBoldProps) { 7 | return ( 8 |

9 | {children} 10 |

11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Primitives/ExtraLargeTitle.tsx: -------------------------------------------------------------------------------- 1 | export type ExtraLargeTitleProps = { 2 | children: React.ReactNode; 3 | className: string; 4 | }; 5 | 6 | export function ExtraLargeTitle({ children, className }: ExtraLargeTitleProps) { 7 | return

{children}

; 8 | } 9 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Primitives/ExtraSmallBody.tsx: -------------------------------------------------------------------------------- 1 | export type ExtraSmallBodyProps = { 2 | children: React.ReactNode; 3 | className?: string; 4 | }; 5 | 6 | export function ExtraSmallBody({ children, className }: ExtraSmallBodyProps) { 7 | return

{children}

; 8 | } 9 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Primitives/FormField.tsx: -------------------------------------------------------------------------------- 1 | import { Label } from "./Label"; 2 | 3 | export function FormField({ 4 | label, 5 | name, 6 | children, 7 | }: { 8 | label: string; 9 | name: string; 10 | children: React.ReactNode; 11 | }) { 12 | return ( 13 |
14 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Primitives/Input.tsx: -------------------------------------------------------------------------------- 1 | import clsx from "clsx"; 2 | 3 | const roundedStyles = { 4 | roundedLeft: 5 | "rounded-l focus:outline-offset-[0px] focus:outline-blue-500 -mr-1", 6 | roundedRight: 7 | "rounded-r focus:outline-offset-[0px] focus:outline-blue-500 -ml-1", 8 | roundedFull: "rounded focus:outline-offset-[0px] focus:outline-blue-500", 9 | }; 10 | 11 | type InputProps = React.DetailedHTMLProps< 12 | React.InputHTMLAttributes, 13 | HTMLInputElement 14 | > & { 15 | roundedEdges?: "roundedLeft" | "roundedRight" | "roundedFull"; 16 | }; 17 | 18 | export function Input({ 19 | children, 20 | className, 21 | roundedEdges = "roundedFull", 22 | ...props 23 | }: InputProps) { 24 | const classes = clsx(roundedStyles[roundedEdges], className); 25 | 26 | return ( 27 | 31 | {children} 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Primitives/Label.tsx: -------------------------------------------------------------------------------- 1 | export function Label({ label, htmlFor }: { label: string; htmlFor?: string }) { 2 | return ( 3 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Primitives/LargeMono.tsx: -------------------------------------------------------------------------------- 1 | export type LargeMonoProps = { 2 | children: React.ReactNode; 3 | className: string; 4 | }; 5 | 6 | export function LargeMono({ children, className }: LargeMonoProps) { 7 | return

{children}

; 8 | } 9 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Primitives/LargeTitle.tsx: -------------------------------------------------------------------------------- 1 | export type LargeTitleProps = { 2 | children: React.ReactNode; 3 | className?: string; 4 | }; 5 | 6 | export function LargeTitle({ children, className }: LargeTitleProps) { 7 | return

{children}

; 8 | } 9 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Primitives/MediumBody.tsx: -------------------------------------------------------------------------------- 1 | export type MediumBodyProps = { 2 | children: React.ReactNode; 3 | className: string; 4 | }; 5 | 6 | export function MediumBody({ children, className }: MediumBodyProps) { 7 | return ( 8 |

{children}

9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Primitives/Mono.tsx: -------------------------------------------------------------------------------- 1 | export type MonoProps = { 2 | children: React.ReactNode; 3 | className: string; 4 | }; 5 | 6 | export function Mono({ children, className }: MonoProps) { 7 | return

{children}

; 8 | } 9 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Primitives/SmallBody.tsx: -------------------------------------------------------------------------------- 1 | export type SmallBodyProps = { 2 | children: React.ReactNode; 3 | className: string; 4 | }; 5 | 6 | export function SmallBody({ children, className }: SmallBodyProps) { 7 | return

{children}

; 8 | } 9 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Primitives/SmallMono.tsx: -------------------------------------------------------------------------------- 1 | export type SmallMonoProps = { 2 | children: React.ReactNode; 3 | className: string; 4 | }; 5 | 6 | export function SmallMono({ children, className }: SmallMonoProps) { 7 | return

{children}

; 8 | } 9 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Primitives/SmallSubtitle.tsx: -------------------------------------------------------------------------------- 1 | export type SmallSubtitleProps = { 2 | children: React.ReactNode; 3 | className: string; 4 | }; 5 | 6 | export function SmallSubtitle({ children, className }: SmallSubtitleProps) { 7 | return

{children}

; 8 | } 9 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Primitives/SmallTitle.tsx: -------------------------------------------------------------------------------- 1 | export type SmallTitleProps = { 2 | children: React.ReactNode; 3 | className?: string; 4 | }; 5 | 6 | export function SmallTitle({ children, className }: SmallTitleProps) { 7 | return

{children}

; 8 | } 9 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Primitives/Title.tsx: -------------------------------------------------------------------------------- 1 | export type TitleProps = { 2 | children: React.ReactNode; 3 | className?: string; 4 | }; 5 | 6 | export function Title({ children, className }: TitleProps) { 7 | return

{children}

; 8 | } 9 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/ProjectKey.tsx: -------------------------------------------------------------------------------- 1 | import { CopyTextButton } from "./Buttons/CopyTextButton"; 2 | import { Label } from "./Primitives/Label"; 3 | 4 | export function ProjectKey({ projectId }: { projectId: string }) { 5 | return ( 6 |
7 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/SchemaEditor.tsx: -------------------------------------------------------------------------------- 1 | export function SchemaEditor() { 2 | return ( 3 |
4 |

Schema Editor

5 |
6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/Select.tsx: -------------------------------------------------------------------------------- 1 | import classNames from "classnames"; 2 | 3 | type SelectProps = React.DetailedHTMLProps< 4 | React.SelectHTMLAttributes, 5 | HTMLSelectElement 6 | >; 7 | 8 | const defaultClasses = 9 | "mt-1 block w-full rounded-md border-gray-300 py-1 pl-2 pr-10 text-base focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm"; 10 | 11 | export function Select({ children, className, ...props }: SelectProps) { 12 | return ( 13 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /apps/webapp/app/libraries/ui/src/components/UserProfilePhoto.tsx: -------------------------------------------------------------------------------- 1 | import { UserCircleIcon } from "@heroicons/react/24/solid"; 2 | import classNames from "classnames"; 3 | import type { User } from "~/models/user.server"; 4 | 5 | export function UserProfilePhoto({ 6 | user, 7 | className, 8 | }: { 9 | user: User; 10 | className?: string; 11 | }) { 12 | return user.avatarUrl ? ( 13 | {user.name 18 | ) : ( 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /apps/webapp/app/mergent.server.ts: -------------------------------------------------------------------------------- 1 | import Mergent from "mergent"; 2 | import { env } from "./env.server"; 3 | 4 | export const mergent = new Mergent(env.MERGENT_KEY); 5 | -------------------------------------------------------------------------------- /apps/webapp/app/models/admin.server.ts: -------------------------------------------------------------------------------- 1 | import { prisma } from "~/db.server"; 2 | 3 | export async function adminGetUsers() { 4 | return await prisma.user.findMany({ 5 | select: { 6 | id: true, 7 | email: true, 8 | createdAt: true, 9 | displayName: true, 10 | admin: true, 11 | workspaces: { 12 | select: { 13 | title: true, 14 | projects: { 15 | select: { 16 | title: true, 17 | id: true, 18 | }, 19 | }, 20 | }, 21 | }, 22 | }, 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /apps/webapp/app/models/authToken.server.ts: -------------------------------------------------------------------------------- 1 | import { nanoid } from "nanoid"; 2 | import { prisma } from "~/db.server"; 3 | 4 | export function getAuthToken({ requestToken }: { requestToken: string }) { 5 | return prisma.authToken.findFirst({ 6 | where: { 7 | requestToken: { 8 | token: requestToken, 9 | }, 10 | }, 11 | select: { 12 | token: true, 13 | userId: true, 14 | }, 15 | }); 16 | } 17 | 18 | export function createAuthToken({ 19 | requestTokenId, 20 | userId, 21 | }: { 22 | requestTokenId: string; 23 | userId: string; 24 | }) { 25 | return prisma.authToken.create({ 26 | data: { 27 | requestTokenId, 28 | userId, 29 | token: nanoid(64), 30 | }, 31 | }); 32 | } 33 | 34 | export function getAuthTokenAndUser({ authToken }: { authToken: string }) { 35 | return prisma.authToken.findFirst({ 36 | where: { 37 | token: authToken, 38 | }, 39 | include: { 40 | User: true, 41 | }, 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /apps/webapp/app/models/requestToken.server.ts: -------------------------------------------------------------------------------- 1 | import { prisma } from "~/db.server"; 2 | import { nanoid } from "nanoid"; 3 | 4 | export function createRequestToken() { 5 | return prisma.requestToken.create({ 6 | data: { 7 | token: nanoid(32), 8 | //expires after one day 9 | expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), 10 | }, 11 | }); 12 | } 13 | 14 | export function getRequestToken({ token }: { token: string }) { 15 | return prisma.requestToken.findFirst({ 16 | where: { token, expiresAt: { gt: new Date() } }, 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /apps/webapp/app/models/security.server.ts: -------------------------------------------------------------------------------- 1 | import { prisma } from "~/db.server"; 2 | 3 | export async function findSecurityRequirementsByOperationId( 4 | operationId: string 5 | ) { 6 | return prisma.apiSchemaSecurityRequirement.findMany({ 7 | where: { 8 | operationId, 9 | }, 10 | include: { 11 | scopes: true, 12 | securityScheme: true, 13 | }, 14 | }); 15 | } 16 | 17 | export async function getSecurityRequirementsForIntegration( 18 | integrationId: string 19 | ) { 20 | const latestSchema = await prisma.apiSchema.findFirst({ 21 | where: { integrationId }, 22 | orderBy: { version: "desc" }, 23 | }); 24 | 25 | if (!latestSchema) { 26 | return []; 27 | } 28 | 29 | return prisma.apiSchemaSecurityScheme.findMany({ 30 | where: { 31 | schemaId: latestSchema.id, 32 | }, 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /apps/webapp/app/routes/__app/index.tsx: -------------------------------------------------------------------------------- 1 | import type { LoaderArgs } from "@remix-run/server-runtime"; 2 | import { redirect } from "remix-typedjson"; 3 | import { requireUserId } from "~/services/session.server"; 4 | import { getWorkspaces } from "~/models/workspace.server"; 5 | 6 | export const loader = async ({ request }: LoaderArgs) => { 7 | const userId = await requireUserId(request); 8 | const workspaces = await getWorkspaces({ userId }); 9 | const workspaceWithProject = workspaces.find( 10 | (workspace) => workspace.projects.length 11 | ); 12 | 13 | if (workspaceWithProject) { 14 | return redirect( 15 | `/workspaces/${workspaceWithProject.slug}/projects/${workspaceWithProject.projects[0].slug}/home` 16 | ); 17 | } 18 | return redirect(`/workspaces/new`); 19 | }; 20 | -------------------------------------------------------------------------------- /apps/webapp/app/routes/__app/workspaces/$workspaceSlug/projects/$projectSlug/index.tsx: -------------------------------------------------------------------------------- 1 | import type { LoaderArgs } from "@remix-run/server-runtime"; 2 | import { redirect } from "remix-typedjson"; 3 | import invariant from "tiny-invariant"; 4 | import { requireUserId } from "~/services/session.server"; 5 | 6 | export const loader = async ({ request, params }: LoaderArgs) => { 7 | await requireUserId(request); 8 | const { projectSlug, workspaceSlug } = params; 9 | invariant(workspaceSlug, "workspaceSlug not found"); 10 | invariant(projectSlug, "projectSlug not found"); 11 | 12 | return redirect(`/workspaces/${workspaceSlug}/projects/${projectSlug}/home`); 13 | }; 14 | -------------------------------------------------------------------------------- /apps/webapp/app/routes/admin/integrations/index.tsx: -------------------------------------------------------------------------------- 1 | import { EmptyState } from "~/components/EmptyState"; 2 | 3 | export default function IntegrationsIndexRoute() { 4 | return ( 5 |
6 | 12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /apps/webapp/app/routes/admin/schemas/$id/download.tsx: -------------------------------------------------------------------------------- 1 | import type { LoaderFunction } from "@remix-run/server-runtime"; 2 | import invariant from "tiny-invariant"; 3 | import { generateSpecFromSchema } from "~/models/apiSchema.server"; 4 | 5 | export const loader: LoaderFunction = async ({ params }) => { 6 | const { id } = params; 7 | 8 | invariant(id, "id is required"); 9 | 10 | const openApiSchema = await generateSpecFromSchema(id); 11 | 12 | return new Response(JSON.stringify(openApiSchema), { 13 | headers: { 14 | "content-type": "application/json", 15 | "content-disposition": `attachment; filename=${id}.json`, 16 | }, 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /apps/webapp/app/routes/admin/schemas/$id/downloadOriginal.tsx: -------------------------------------------------------------------------------- 1 | import type { LoaderFunction } from "@remix-run/server-runtime"; 2 | import invariant from "tiny-invariant"; 3 | import { findSchemaById } from "~/models/apiSchema.server"; 4 | 5 | export const loader: LoaderFunction = async ({ params }) => { 6 | const { id } = params; 7 | 8 | invariant(id, "id is required"); 9 | 10 | const schema = await findSchemaById(id); 11 | 12 | return new Response(JSON.stringify(schema?.rawData), { 13 | headers: { 14 | "content-type": "application/json", 15 | "content-disposition": `attachment; filename=${id}-original.json`, 16 | }, 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /apps/webapp/app/routes/admin/schemas/$id/models.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from "@remix-run/react"; 2 | 3 | export default function ModelsLayoutRoute() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /apps/webapp/app/routes/admin/schemas/$id/releases.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from "@remix-run/react"; 2 | 3 | export default function ReleasesLayoutRoute() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /apps/webapp/app/routes/admin/schemas/$id/securitySchemes.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from "@remix-run/react"; 2 | 3 | export default function SecuritySchemesLayoutRoute() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /apps/webapp/app/routes/admin/schemas/$id/securitySchemes/$securitySchemeId.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from "@remix-run/react"; 2 | 3 | export default function SecuritySchemeLayoutRoute() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /apps/webapp/app/routes/admin/schemas/$id[.json].tsx: -------------------------------------------------------------------------------- 1 | import type { LoaderFunction } from "@remix-run/server-runtime"; 2 | import { json } from "@remix-run/server-runtime"; 3 | import invariant from "tiny-invariant"; 4 | import { findSchemaById } from "~/models/apiSchema.server"; 5 | 6 | export const loader: LoaderFunction = async ({ params }) => { 7 | const { id } = params; 8 | 9 | invariant(id, "id is required"); 10 | 11 | const schema = await findSchemaById(id); 12 | 13 | return json(schema?.rawData); 14 | }; 15 | -------------------------------------------------------------------------------- /apps/webapp/app/routes/admin/schemas/index.tsx: -------------------------------------------------------------------------------- 1 | import { EmptyState } from "~/components/EmptyState"; 2 | 3 | export default function SchemasIndexRoute() { 4 | return ( 5 |
6 | 12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /apps/webapp/app/routes/api/auth/requestTokens.$requestToken.ts: -------------------------------------------------------------------------------- 1 | import type { LoaderFunction } from "@remix-run/server-runtime"; 2 | import { json } from "@remix-run/server-runtime"; 3 | import { getAuthToken } from "~/models/authToken.server"; 4 | 5 | export const loader: LoaderFunction = async ({ request, params }) => { 6 | if (request.method !== "GET") { 7 | return { 8 | status: 405, 9 | }; 10 | } 11 | 12 | const requestToken = params.requestToken; 13 | 14 | if (requestToken === undefined) { 15 | return { 16 | status: 400, 17 | }; 18 | } 19 | 20 | try { 21 | const authToken = await getAuthToken({ requestToken: requestToken }); 22 | 23 | if (authToken === null) { 24 | return { 25 | status: 404, 26 | }; 27 | } 28 | 29 | return json( 30 | { 31 | authToken: authToken.token, 32 | userId: authToken.userId, 33 | }, 34 | { status: 200 } 35 | ); 36 | } catch (error) { 37 | return json( 38 | { 39 | error: (error as Error).message, 40 | }, 41 | { status: 400 } 42 | ); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /apps/webapp/app/routes/api/auth/requestTokens.ts: -------------------------------------------------------------------------------- 1 | import type { ActionFunction } from "@remix-run/server-runtime"; 2 | import { json } from "@remix-run/server-runtime"; 3 | import { createRequestToken } from "~/models/requestToken.server"; 4 | 5 | export const action: ActionFunction = async ({ request }) => { 6 | if (request.method !== "POST") { 7 | return { 8 | status: 405, 9 | }; 10 | } 11 | 12 | try { 13 | const token = await createRequestToken(); 14 | return json( 15 | { 16 | token: token.token, 17 | }, 18 | { status: 200 } 19 | ); 20 | } catch (error) { 21 | return json( 22 | { 23 | error: (error as Error).message, 24 | }, 25 | { status: 400 } 26 | ); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /apps/webapp/app/routes/api/projects/$projectId.check-logs-onboarding.ts: -------------------------------------------------------------------------------- 1 | import type { LoaderArgs } from "@remix-run/server-runtime"; 2 | import { z } from "zod"; 3 | import { searchLogsInProject } from "~/services/logs.server"; 4 | import { requireUserId } from "~/services/session.server"; 5 | 6 | export async function loader({ params, request }: LoaderArgs) { 7 | await requireUserId(request); 8 | 9 | const { projectId } = z.object({ projectId: z.string() }).parse(params); 10 | 11 | const logs = await searchLogsInProject(new URLSearchParams(), projectId); 12 | 13 | const hasLogs = typeof logs === "undefined" ? false : logs.logs.length > 0; 14 | 15 | return { hasLogs }; 16 | } 17 | -------------------------------------------------------------------------------- /apps/webapp/app/routes/auth/github/callback.tsx: -------------------------------------------------------------------------------- 1 | import type { LoaderFunction } from "@remix-run/node"; 2 | import { authenticator } from "~/services/auth.server"; 3 | import { redirectCookie } from "../github"; 4 | 5 | export let loader: LoaderFunction = async ({ request }) => { 6 | const cookie = request.headers.get("Cookie"); 7 | const redirectValue = await redirectCookie.parse(cookie); 8 | const redirectTo = redirectValue ?? "/"; 9 | 10 | const authuser = await authenticator.authenticate("github", request, { 11 | successRedirect: redirectTo, 12 | failureRedirect: "/login", 13 | }); 14 | 15 | return authuser; 16 | }; 17 | -------------------------------------------------------------------------------- /apps/webapp/app/routes/healthcheck.tsx: -------------------------------------------------------------------------------- 1 | // learn more: https://fly.io/docs/reference/configuration/#services-http_checks 2 | import { prisma } from "~/db.server"; 3 | import type { LoaderFunction } from "@remix-run/node"; 4 | 5 | export const loader: LoaderFunction = async ({ request }) => { 6 | const host = 7 | request.headers.get("X-Forwarded-Host") ?? request.headers.get("host"); 8 | 9 | try { 10 | const url = new URL("/", `http://${host}`); 11 | // if we can connect to the database and make a simple query 12 | // and make a HEAD request to ourselves, then we're good. 13 | await Promise.all([ 14 | prisma.user.count(), 15 | fetch(url.toString(), { method: "HEAD" }).then((r) => { 16 | if (!r.ok) return Promise.reject(r); 17 | }), 18 | ]); 19 | return new Response("OK"); 20 | } catch (error: unknown) { 21 | console.log("healthcheck ❌", { error }); 22 | return new Response("ERROR", { status: 500 }); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /apps/webapp/app/routes/logos/$integrationId.tsx: -------------------------------------------------------------------------------- 1 | import type { LoaderArgs } from "@remix-run/server-runtime"; 2 | import { redirect } from "@remix-run/server-runtime"; 3 | import { z } from "zod"; 4 | import { findIntegrationById } from "~/models/integration.server"; 5 | import { getSignedGetUrl } from "~/services/upload.server"; 6 | 7 | export async function loader({ params }: LoaderArgs) { 8 | const { integrationId } = z 9 | .object({ integrationId: z.string() }) 10 | .parse(params); 11 | 12 | const integration = await findIntegrationById(integrationId); 13 | 14 | if (!integration || !integration.logoImage) { 15 | return new Response("Not found", { status: 404 }); 16 | } 17 | 18 | const signedUrl = await getSignedGetUrl(integration.logoImage); 19 | 20 | return redirect(signedUrl); 21 | } 22 | -------------------------------------------------------------------------------- /apps/webapp/app/routes/logout.tsx: -------------------------------------------------------------------------------- 1 | import type { ActionFunction, LoaderFunction } from "@remix-run/node"; 2 | import { authenticator } from "~/services/auth.server"; 3 | 4 | export const action: ActionFunction = async ({ request }) => { 5 | return await authenticator.logout(request, { redirectTo: "/" }); 6 | }; 7 | 8 | export const loader: LoaderFunction = async ({ request }) => { 9 | return await authenticator.logout(request, { redirectTo: "/" }); 10 | }; 11 | -------------------------------------------------------------------------------- /apps/webapp/app/routes/logs/$requestId.ts: -------------------------------------------------------------------------------- 1 | import type { LoaderArgs } from "@remix-run/server-runtime"; 2 | import { redirect } from "@remix-run/server-runtime"; 3 | import { z } from "zod"; 4 | import { getRequestLogbyId } from "~/models/requestLog.server"; 5 | import { requireUserId } from "~/services/session.server"; 6 | 7 | export async function loader({ params, request }: LoaderArgs) { 8 | await requireUserId(request); 9 | 10 | const { requestId } = z.object({ requestId: z.string() }).parse(params); 11 | 12 | const requestLog = await getRequestLogbyId(requestId); 13 | 14 | if (!requestLog) { 15 | return redirect("/"); 16 | } 17 | 18 | const path = `/workspaces/${requestLog.endpoint.client.project.workspace.slug}/projects/${requestLog.endpoint.client.project.slug}/${requestLog.endpoint.client.integration.slug}/${requestLog.endpoint.operation.method}/${requestLog.endpoint.operation.operationId}`; 19 | 20 | return redirect(path); 21 | } 22 | -------------------------------------------------------------------------------- /apps/webapp/app/routes/magic.tsx: -------------------------------------------------------------------------------- 1 | import type { LoaderArgs } from "@remix-run/server-runtime"; 2 | import { authenticator } from "~/services/auth.server"; 3 | import { getRedirectTo } from "~/services/redirectTo.server"; 4 | 5 | export async function loader({ request }: LoaderArgs) { 6 | const redirectTo = await getRedirectTo(request); 7 | 8 | await authenticator.authenticate("email-link", request, { 9 | successRedirect: redirectTo ?? "/", 10 | failureRedirect: "/login", 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /apps/webapp/app/routes/projects.$projectId.ts: -------------------------------------------------------------------------------- 1 | import type { LoaderFunction } from "@remix-run/server-runtime"; 2 | import { redirect } from "@remix-run/server-runtime"; 3 | import invariant from "tiny-invariant"; 4 | import { getWorkspaceForProjectId } from "~/models/workspace.server"; 5 | import { requireUserId } from "~/services/session.server"; 6 | 7 | export const loader: LoaderFunction = async ({ request, params }) => { 8 | const projectId = params.projectId; 9 | invariant(projectId, "projectId not found"); 10 | const userId = await requireUserId(request); 11 | 12 | const workspace = await getWorkspaceForProjectId({ projectId, userId }); 13 | invariant(workspace, "workspace not found"); 14 | 15 | return redirect(`/workspaces/${workspace.id}/projects/${projectId}`); 16 | }; 17 | -------------------------------------------------------------------------------- /apps/webapp/app/routes/resources/workspaces/$workspaceSlug/projects/$projectSlug/completed-logs-onboarding.ts: -------------------------------------------------------------------------------- 1 | import type { ActionFunction } from "@remix-run/server-runtime"; 2 | import { redirect } from "remix-typedjson"; 3 | import invariant from "tiny-invariant"; 4 | import { setHasCompletedLogsOnboarding } from "~/models/project.server"; 5 | 6 | export const action: ActionFunction = async ({ params }) => { 7 | const { projectSlug, workspaceSlug } = params; 8 | invariant(projectSlug, "projectId is required"); 9 | invariant(workspaceSlug, "workspaceSlug is required"); 10 | 11 | try { 12 | await setHasCompletedLogsOnboarding(projectSlug); 13 | return redirect(`/workspaces/${workspaceSlug}/projects/${projectSlug}`); 14 | } catch (error: any) { 15 | throw new Response(error.message, { status: 500 }); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /apps/webapp/app/routes/sentry/frontendError.tsx: -------------------------------------------------------------------------------- 1 | export default function ManualSentryError() { 2 | return ( 3 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /apps/webapp/app/routes/sentry/loaderError.ts: -------------------------------------------------------------------------------- 1 | import type { LoaderArgs } from "@remix-run/server-runtime"; 2 | 3 | export async function loader({ params }: LoaderArgs) { 4 | throw new Error("Sentry Error"); 5 | } 6 | -------------------------------------------------------------------------------- /apps/webapp/app/routes/webhooks/mailgun.ts: -------------------------------------------------------------------------------- 1 | import type { ActionArgs } from "@remix-run/server-runtime"; 2 | import { mailgunClient } from "~/services/email.server"; 3 | 4 | export async function action({ request }: ActionArgs) { 5 | const data = await request.json(); 6 | 7 | await mailgunClient.messages().send(data); 8 | 9 | return new Response(null, { status: 200 }); 10 | } 11 | -------------------------------------------------------------------------------- /apps/webapp/app/services/apihero.server.ts: -------------------------------------------------------------------------------- 1 | import type { SetupProxyInstance } from "@apihero/node"; 2 | import { setupProxy } from "@apihero/node"; 3 | import { env } from "~/env.server"; 4 | 5 | let proxy: SetupProxyInstance; 6 | 7 | declare global { 8 | var __ah_client__: SetupProxyInstance; 9 | } 10 | 11 | if (process.env.NODE_ENV === "production") { 12 | proxy = getProxy(); 13 | } else { 14 | if (!global.__ah_client__) { 15 | global.__ah_client__ = getProxy(); 16 | } 17 | proxy = global.__ah_client__; 18 | } 19 | 20 | function getProxy() { 21 | const result = setupProxy({ 22 | projectKey: env.APIHERO_PROJECT_KEY, 23 | env: process.env.NODE_ENV, 24 | allow: ["*api.mergent.co/*", "*api.mailgun.net/*"], 25 | }); 26 | 27 | process.once("SIGINT", () => result.stop()); 28 | process.once("SIGTERM", () => result.stop()); 29 | 30 | return result; 31 | } 32 | 33 | export { proxy }; 34 | -------------------------------------------------------------------------------- /apps/webapp/app/services/auth.server.ts: -------------------------------------------------------------------------------- 1 | import { Authenticator } from "remix-auth"; 2 | import type { AuthUser } from "./authUser"; 3 | import { addEmailLinkStrategy } from "./emailAuth.server"; 4 | import { addGitHubStrategy } from "./gitHubAuth.server"; 5 | import { sessionStorage } from "./sessionStorage.server"; 6 | 7 | // Create an instance of the authenticator, pass a generic with what 8 | // strategies will return and will store in the session 9 | const authenticator = new Authenticator(sessionStorage); 10 | 11 | addGitHubStrategy(authenticator); 12 | addEmailLinkStrategy(authenticator); 13 | 14 | export { authenticator }; 15 | -------------------------------------------------------------------------------- /apps/webapp/app/services/authUser.ts: -------------------------------------------------------------------------------- 1 | export type AuthUser = { 2 | userId: string; 3 | }; 4 | -------------------------------------------------------------------------------- /apps/webapp/app/services/integrations/update.server.ts: -------------------------------------------------------------------------------- 1 | import { prisma } from "~/db.server"; 2 | import { z } from "zod"; 3 | 4 | export class UpdateIntegration { 5 | FormSchema = z.object({ 6 | name: z.string().min(3).max(255), 7 | authorNotes: z.string(), 8 | description: z.string(), 9 | officialDocumentation: z.string().optional(), 10 | keywords: z.preprocess( 11 | (p) => (p as string).split(","), 12 | z.array(z.string().trim()) 13 | ), 14 | logoImage: z.string().optional(), 15 | }); 16 | 17 | async call(id: string, payload: Record) { 18 | const validation = this.FormSchema.safeParse(payload); 19 | 20 | if (!validation.success) { 21 | return { 22 | status: "validationError" as const, 23 | data: validation.error.format(), 24 | }; 25 | } 26 | 27 | const integration = await prisma.integration.update({ 28 | where: { id }, 29 | data: validation.data, 30 | }); 31 | 32 | return { 33 | status: "success" as const, 34 | data: integration, 35 | }; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /apps/webapp/app/services/sessionStorage.server.ts: -------------------------------------------------------------------------------- 1 | import { createCookieSessionStorage } from "@remix-run/node"; 2 | import invariant from "tiny-invariant"; 3 | 4 | invariant(process.env.SESSION_SECRET, "SESSION_SECRET must be set"); 5 | 6 | export const sessionStorage = createCookieSessionStorage({ 7 | cookie: { 8 | name: "__session", // use any name you want here 9 | sameSite: "lax", // this helps with CSRF 10 | path: "/", // remember to add this so the cookie will work in all routes 11 | httpOnly: true, // for security reasons, make this cookie http only 12 | secrets: [process.env.SESSION_SECRET], 13 | secure: process.env.NODE_ENV === "production", // enable this in prod only 14 | maxAge: 60 * 60 * 24 * 365, // 7 days 15 | }, 16 | }); 17 | 18 | export function getUserSession(request: Request) { 19 | return sessionStorage.getSession(request.headers.get("Cookie")); 20 | } 21 | 22 | export const { getSession, commitSession, destroySession } = sessionStorage; 23 | -------------------------------------------------------------------------------- /apps/webapp/app/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { validateEmail } from "./utils"; 2 | 3 | test("validateEmail returns false for non-emails", () => { 4 | expect(validateEmail(undefined)).toBe(false); 5 | expect(validateEmail(null)).toBe(false); 6 | expect(validateEmail("")).toBe(false); 7 | expect(validateEmail("not-an-email")).toBe(false); 8 | expect(validateEmail("n@")).toBe(false); 9 | }); 10 | 11 | test("validateEmail returns true for emails", () => { 12 | expect(validateEmail("kody@example.com")).toBe(true); 13 | }); 14 | -------------------------------------------------------------------------------- /apps/webapp/app/utils/parseMappingsValue.ts: -------------------------------------------------------------------------------- 1 | import type { Mapping } from "@apihero/openapi-spec-generator/lib/generate"; 2 | import type { Prisma } from ".prisma/client"; 3 | 4 | export function parseMappingsValue(value: Prisma.JsonValue): Mapping[] { 5 | if (typeof value === "string") { 6 | return JSON.parse(value); 7 | } 8 | 9 | if (!value) { 10 | return []; 11 | } 12 | 13 | return value as Mapping[]; 14 | } 15 | -------------------------------------------------------------------------------- /apps/webapp/cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "cypress"; 2 | 3 | export default defineConfig({ 4 | e2e: { 5 | setupNodeEvents: (on, config) => { 6 | const isDev = config.watchForFileChanges; 7 | const port = process.env.REMIX_APP_PORT ?? (isDev ? "3000" : "8811"); 8 | const configOverrides: Partial = { 9 | baseUrl: `http://localhost:${port}`, 10 | video: !process.env.CI, 11 | screenshotOnRunFailure: !process.env.CI, 12 | }; 13 | 14 | // To use this: 15 | // cy.task('log', whateverYouWantInTheTerminal) 16 | on("task", { 17 | log: (message) => { 18 | console.log(message); 19 | 20 | return null; 21 | }, 22 | }); 23 | 24 | return { ...config, ...configOverrides }; 25 | }, 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /apps/webapp/cypress/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | tsconfigRootDir: __dirname, 4 | project: "./tsconfig.json", 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /apps/webapp/cypress/e2e/smoke.cy.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | describe("smoke tests", () => { 4 | it("should allow you to register and login", () => { 5 | const loginForm = { 6 | email: `${faker.internet.userName()}@example.com`, 7 | password: faker.internet.password(), 8 | username: faker.internet.userName("Jeanne", "Doe"), 9 | }; 10 | cy.then(() => ({ email: loginForm.email })).as("user"); 11 | 12 | cy.visitAndCheck("/"); 13 | cy.findByText("Gospel Stack"); 14 | cy.findByRole("button"); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /apps/webapp/cypress/fixtures/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "foo": "bar" 3 | } 4 | -------------------------------------------------------------------------------- /apps/webapp/cypress/support/commands.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | 3 | declare global { 4 | namespace Cypress { 5 | interface Chainable { 6 | /** 7 | * Extends the standard visit command to wait for the page to load 8 | * 9 | * @returns {typeof visitAndCheck} 10 | * @memberof Chainable 11 | * @example 12 | * cy.visitAndCheck('/') 13 | * @example 14 | * cy.visitAndCheck('/', 500) 15 | */ 16 | visitAndCheck: typeof visitAndCheck; 17 | } 18 | } 19 | } 20 | 21 | // We're waiting a second because of this issue happen randomly 22 | // https://github.com/cypress-io/cypress/issues/7306 23 | // Also added custom types to avoid getting detached 24 | // https://github.com/cypress-io/cypress/issues/7306#issuecomment-1152752612 25 | // =========================================================== 26 | function visitAndCheck(url: string, waitTime: number = 1000) { 27 | cy.visit(url); 28 | cy.location("pathname").should("contain", url).wait(waitTime); 29 | } 30 | 31 | Cypress.Commands.add("visitAndCheck", visitAndCheck); 32 | /* 33 | eslint 34 | @typescript-eslint/no-namespace: "off", 35 | */ 36 | -------------------------------------------------------------------------------- /apps/webapp/cypress/support/e2e.ts: -------------------------------------------------------------------------------- 1 | import "@testing-library/cypress/add-commands"; 2 | import "./commands"; 3 | 4 | Cypress.on("uncaught:exception", (err) => { 5 | // Cypress and React Hydrating the document don't get along 6 | // for some unknown reason. Hopefully we figure out why eventually 7 | // so we can remove this. 8 | if ( 9 | /hydrat/i.test(err.message) || 10 | /Minified React error #418/.test(err.message) || 11 | /Minified React error #423/.test(err.message) 12 | ) { 13 | return false; 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /apps/webapp/fly.toml: -------------------------------------------------------------------------------- 1 | app = "apihero-webapp" 2 | kill_signal = "SIGINT" 3 | kill_timeout = 5 4 | processes = [ ] 5 | 6 | [env] 7 | REMIX_APP_PORT = "8080" 8 | PRIMARY_REGION = "mia" 9 | 10 | [deploy] 11 | release_command = "pnpx prisma migrate deploy --schema apps/webapp/build/schema.prisma" 12 | 13 | [experimental] 14 | allowed_public_ports = [ ] 15 | auto_rollback = true 16 | 17 | [[services]] 18 | internal_port = 8080 19 | processes = [ "app" ] 20 | protocol = "tcp" 21 | script_checks = [ ] 22 | 23 | [services.concurrency] 24 | hard_limit = 25 25 | soft_limit = 20 26 | type = "connections" 27 | 28 | [[services.ports]] 29 | handlers = [ "http" ] 30 | port = 80 31 | force_https = true 32 | 33 | [[services.ports]] 34 | handlers = [ "tls", "http" ] 35 | port = 443 36 | 37 | [[services.tcp_checks]] 38 | grace_period = "1s" 39 | interval = "15s" 40 | restart_limit = 0 41 | timeout = "2s" 42 | 43 | [[services.http_checks]] 44 | interval = "10s" 45 | grace_period = "5s" 46 | method = "get" 47 | path = "/healthcheck" 48 | protocol = "http" 49 | timeout = "2s" 50 | tls_skip_verify = false 51 | headers = { } 52 | -------------------------------------------------------------------------------- /apps/webapp/mocks/README.md: -------------------------------------------------------------------------------- 1 | # Mocks 2 | 3 | Use this to mock any third party HTTP resources that you don't have running locally and want to have mocked for local development as well as tests. 4 | 5 | Learn more about how to use this at [mswjs.io](https://mswjs.io/) 6 | 7 | For an extensive example, see the [source code for kentcdodds.com](https://github.com/kentcdodds/kentcdodds.com/blob/main/mocks/start.ts) 8 | -------------------------------------------------------------------------------- /apps/webapp/mocks/index.js: -------------------------------------------------------------------------------- 1 | const { setupServer } = require("msw/node"); 2 | 3 | const server = setupServer(); 4 | 5 | server.listen({ onUnhandledRequest: "bypass" }); 6 | console.info("🔶 Mock server running"); 7 | 8 | process.once("SIGINT", () => server.close()); 9 | process.once("SIGTERM", () => server.close()); 10 | -------------------------------------------------------------------------------- /apps/webapp/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require("postcss-import"), 4 | require("tailwindcss"), 5 | require("autoprefixer"), 6 | ], 7 | }; 8 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220603181405_added_workspace_timestamps/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `updatedAt` to the `Workspace` table without a default value. This is not possible if the table is not empty. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "Workspace" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 9 | ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL; 10 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220604113326_added_endpoints/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Endpoint" ( 3 | "id" TEXT NOT NULL, 4 | "request" JSONB, 5 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 6 | "updatedAt" TIMESTAMP(3) NOT NULL, 7 | "workspaceId" TEXT NOT NULL, 8 | 9 | CONSTRAINT "Endpoint_pkey" PRIMARY KEY ("id") 10 | ); 11 | 12 | -- AddForeignKey 13 | ALTER TABLE "Endpoint" ADD CONSTRAINT "Endpoint_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE CASCADE ON UPDATE CASCADE; 14 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220605192230_added_endpoint_variables/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "EndpointVariable" ( 3 | "id" TEXT NOT NULL, 4 | "name" TEXT NOT NULL, 5 | "value" TEXT NOT NULL, 6 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 7 | "updatedAt" TIMESTAMP(3) NOT NULL, 8 | "endpointId" TEXT NOT NULL, 9 | 10 | CONSTRAINT "EndpointVariable_pkey" PRIMARY KEY ("id") 11 | ); 12 | 13 | -- AddForeignKey 14 | ALTER TABLE "EndpointVariable" ADD CONSTRAINT "EndpointVariable_endpointId_fkey" FOREIGN KEY ("endpointId") REFERENCES "Endpoint"("id") ON DELETE CASCADE ON UPDATE CASCADE; 15 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220607090933_renamed_workspace_to_project/migration.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE IF EXISTS "Workspace" RENAME TO "Project"; 2 | ALTER TABLE IF EXISTS "Endpoint" RENAME COLUMN "workspaceId" TO "projectId"; -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220607091451_workspace_to_project/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Project" RENAME CONSTRAINT "Workspace_pkey" TO "Project_pkey"; 3 | 4 | -- RenameForeignKey 5 | ALTER TABLE "Endpoint" RENAME CONSTRAINT "Endpoint_workspaceId_fkey" TO "Endpoint_projectId_fkey"; 6 | 7 | -- RenameForeignKey 8 | ALTER TABLE "Project" RENAME CONSTRAINT "Workspace_organizationId_fkey" TO "Project_organizationId_fkey"; 9 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220608095640_added_endpoint_executions/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "EndpointExecution" ( 3 | "id" TEXT NOT NULL, 4 | "request" JSONB NOT NULL, 5 | "variables" JSONB NOT NULL, 6 | "response" JSONB NOT NULL, 7 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 8 | "endpointId" TEXT NOT NULL, 9 | 10 | CONSTRAINT "EndpointExecution_pkey" PRIMARY KEY ("id") 11 | ); 12 | 13 | -- AddForeignKey 14 | ALTER TABLE "EndpointExecution" ADD CONSTRAINT "EndpointExecution_endpointId_fkey" FOREIGN KEY ("endpointId") REFERENCES "Endpoint"("id") ON DELETE CASCADE ON UPDATE CASCADE; 15 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220610174026_endpoint_variables_default_required_example/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `value` on the `EndpointVariable` table. All the data in the column will be lost. 5 | - Added the required column `required` to the `EndpointVariable` table without a default value. This is not possible if the table is not empty. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "EndpointVariable" DROP COLUMN "value", 10 | ADD COLUMN "defaultValue" TEXT, 11 | ADD COLUMN "exampleValue" TEXT, 12 | ADD COLUMN "required" BOOLEAN NOT NULL; 13 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220617141107_client_name_unique/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[projectId,name]` on the table `Client` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- CreateIndex 8 | CREATE UNIQUE INDEX "Client_projectId_name_key" ON "Client"("projectId", "name"); 9 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220617143531_endpoint_added_name/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[clientId,name]` on the table `Endpoint` will be added. If there are existing duplicate values, this will fail. 5 | - Added the required column `name` to the `Endpoint` table without a default value. This is not possible if the table is not empty. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "Endpoint" ADD COLUMN "name" TEXT NOT NULL; 10 | 11 | -- CreateIndex 12 | CREATE UNIQUE INDEX "Endpoint_clientId_name_key" ON "Endpoint"("clientId", "name"); 13 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220617151029_added_endpoint_method/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "HTTPMethod" AS ENUM ('GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE'); 3 | 4 | -- AlterTable 5 | ALTER TABLE "Endpoint" ADD COLUMN "method" "HTTPMethod" NOT NULL DEFAULT E'GET'; 6 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220617160851_added_endpoint_execution/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "EndpointExecution" ( 3 | "id" TEXT NOT NULL, 4 | "url" TEXT NOT NULL, 5 | "statusCode" INTEGER NOT NULL, 6 | "developerError" JSONB, 7 | "props" JSONB, 8 | "request" JSONB, 9 | "response" JSONB, 10 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 11 | "endpointId" TEXT, 12 | "clientId" TEXT, 13 | "projectId" TEXT NOT NULL, 14 | 15 | CONSTRAINT "EndpointExecution_pkey" PRIMARY KEY ("id") 16 | ); 17 | 18 | -- AddForeignKey 19 | ALTER TABLE "EndpointExecution" ADD CONSTRAINT "EndpointExecution_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE; 20 | 21 | -- AddForeignKey 22 | ALTER TABLE "EndpointExecution" ADD CONSTRAINT "EndpointExecution_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "Client"("id") ON DELETE CASCADE ON UPDATE CASCADE; 23 | 24 | -- AddForeignKey 25 | ALTER TABLE "EndpointExecution" ADD CONSTRAINT "EndpointExecution_endpointId_fkey" FOREIGN KEY ("endpointId") REFERENCES "Endpoint"("id") ON DELETE CASCADE ON UPDATE CASCADE; 26 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220617161617_added_endpoint_execution_method/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "EndpointExecution" ADD COLUMN "method" "HTTPMethod" NOT NULL DEFAULT E'GET'; 3 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220617162655_added_endpoint_execution_optional_properties/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "EndpointExecution" ALTER COLUMN "url" DROP NOT NULL, 3 | ALTER COLUMN "statusCode" DROP NOT NULL; 4 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220701120756_execution_id_autoincrement/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - The primary key for the `EndpointExecution` table will be changed. If it partially fails, the table could be left without primary key constraint. 5 | - The `id` column on the `EndpointExecution` table would be dropped and recreated. This will lead to data loss if there is data in the column. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "EndpointExecution" DROP CONSTRAINT "EndpointExecution_pkey", 10 | DROP COLUMN "id", 11 | ADD COLUMN "id" SERIAL NOT NULL, 12 | ADD CONSTRAINT "EndpointExecution_pkey" PRIMARY KEY ("id"); 13 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220701145459_endpoint_add_props/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Endpoint" ADD COLUMN "props" TEXT[]; 3 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220701174835_endpoint_rename_variables/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `props` on the `Endpoint` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "Endpoint" DROP COLUMN "props", 9 | ADD COLUMN "variables" TEXT[]; 10 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220701175311_endpoint_variables_json/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - The `variables` column on the `Endpoint` table would be dropped and recreated. This will lead to data loss if there is data in the column. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "Endpoint" DROP COLUMN "variables", 9 | ADD COLUMN "variables" JSONB; 10 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220704132248_created_project_constants/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "ProjectConstant" ( 3 | "id" TEXT NOT NULL, 4 | "name" TEXT NOT NULL, 5 | "value" TEXT NOT NULL, 6 | "isSecret" BOOLEAN NOT NULL DEFAULT false, 7 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 8 | "updatedAt" TIMESTAMP(3) NOT NULL, 9 | "projectId" TEXT NOT NULL, 10 | "clientId" TEXT, 11 | "endpointId" TEXT, 12 | 13 | CONSTRAINT "ProjectConstant_pkey" PRIMARY KEY ("id") 14 | ); 15 | 16 | -- AddForeignKey 17 | ALTER TABLE "ProjectConstant" ADD CONSTRAINT "ProjectConstant_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE; 18 | 19 | -- AddForeignKey 20 | ALTER TABLE "ProjectConstant" ADD CONSTRAINT "ProjectConstant_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "Client"("id") ON DELETE CASCADE ON UPDATE CASCADE; 21 | 22 | -- AddForeignKey 23 | ALTER TABLE "ProjectConstant" ADD CONSTRAINT "ProjectConstant_endpointId_fkey" FOREIGN KEY ("endpointId") REFERENCES "Endpoint"("id") ON DELETE CASCADE ON UPDATE CASCADE; 24 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220704161008_project_constant_value_optional/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "ProjectConstant" ALTER COLUMN "value" DROP NOT NULL; 3 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220705135154_project_constant_just_project_association/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `clientId` on the `ProjectConstant` table. All the data in the column will be lost. 5 | - You are about to drop the `_EndpointToProjectConstant` table. If the table is not empty, all the data it contains will be lost. 6 | - A unique constraint covering the columns `[name,projectId]` on the table `ProjectConstant` will be added. If there are existing duplicate values, this will fail. 7 | 8 | */ 9 | -- DropForeignKey 10 | ALTER TABLE "ProjectConstant" DROP CONSTRAINT "ProjectConstant_clientId_fkey"; 11 | 12 | -- DropForeignKey 13 | ALTER TABLE "_EndpointToProjectConstant" DROP CONSTRAINT "_EndpointToProjectConstant_A_fkey"; 14 | 15 | -- DropForeignKey 16 | ALTER TABLE "_EndpointToProjectConstant" DROP CONSTRAINT "_EndpointToProjectConstant_B_fkey"; 17 | 18 | -- AlterTable 19 | ALTER TABLE "ProjectConstant" DROP COLUMN "clientId"; 20 | 21 | -- DropTable 22 | DROP TABLE "_EndpointToProjectConstant"; 23 | 24 | -- CreateIndex 25 | CREATE UNIQUE INDEX "ProjectConstant_name_projectId_key" ON "ProjectConstant"("name", "projectId"); 26 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220707094637_add_cache_ttl_to_client/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Client" ADD COLUMN "cacheTtl" INTEGER; 3 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220707094940_add_is_cache_hit_to_execution/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "EndpointExecution" ADD COLUMN "isCacheHit" BOOLEAN; 3 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220708113318_add_duration_and_response_size_to_executions/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "EndpointExecution" ADD COLUMN "duration" DOUBLE PRECISION NOT NULL DEFAULT 0, 3 | ADD COLUMN "responseSize" INTEGER NOT NULL DEFAULT 0; 4 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220722093243_removed_password_added_github/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "AuthenticationMethod" AS ENUM ('GITHUB'); 3 | 4 | -- DropForeignKey 5 | ALTER TABLE "Password" DROP CONSTRAINT "Password_userId_fkey"; 6 | 7 | TRUNCATE TABLE "User" CASCADE; 8 | 9 | -- AlterTable 10 | ALTER TABLE "User" ADD COLUMN "accessToken" TEXT NOT NULL, 11 | ADD COLUMN "authenticationExtraParams" JSONB, 12 | ADD COLUMN "authenticationMethod" "AuthenticationMethod" NOT NULL, 13 | ADD COLUMN "authenticationProfile" JSONB, 14 | ADD COLUMN "displayName" TEXT, 15 | ADD COLUMN "familyName" TEXT, 16 | ADD COLUMN "givenName" TEXT; 17 | 18 | -- DropTable 19 | DROP TABLE "Password"; 20 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220722134023_user_name_avatar_url/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `familyName` on the `User` table. All the data in the column will be lost. 5 | - You are about to drop the column `givenName` on the `User` table. All the data in the column will be lost. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "User" DROP COLUMN "familyName", 10 | DROP COLUMN "givenName", 11 | ADD COLUMN "avatarUrl" TEXT, 12 | ADD COLUMN "name" TEXT; 13 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220805152117_add_author_notes_to_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "APISchema" ADD COLUMN "authorNotes" TEXT NOT NULL DEFAULT E''; 3 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220805153153_add_integration_table/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `authorNotes` on the `APISchema` table. All the data in the column will be lost. 5 | - Added the required column `integrationId` to the `APISchema` table without a default value. This is not possible if the table is not empty. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "APISchema" DROP COLUMN "authorNotes", 10 | ADD COLUMN "integrationId" TEXT NOT NULL; 11 | 12 | -- CreateTable 13 | CREATE TABLE "Integration" ( 14 | "id" TEXT NOT NULL, 15 | "slug" TEXT NOT NULL, 16 | "authorNotes" TEXT NOT NULL DEFAULT E'', 17 | "data" JSONB, 18 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 19 | "updatedAt" TIMESTAMP(3) NOT NULL, 20 | 21 | CONSTRAINT "Integration_pkey" PRIMARY KEY ("id") 22 | ); 23 | 24 | -- CreateIndex 25 | CREATE UNIQUE INDEX "Integration_slug_key" ON "Integration"("slug"); 26 | 27 | -- AddForeignKey 28 | ALTER TABLE "APISchema" ADD CONSTRAINT "APISchema_integrationId_fkey" FOREIGN KEY ("integrationId") REFERENCES "Integration"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 29 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220805163909_add_identifier_to_security_schemes/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[identifier,schemaId]` on the table `ApiSchemaSecurityScheme` will be added. If there are existing duplicate values, this will fail. 5 | - Added the required column `identifier` to the `ApiSchemaSecurityScheme` table without a default value. This is not possible if the table is not empty. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "ApiSchemaSecurityScheme" ADD COLUMN "identifier" TEXT NOT NULL; 10 | 11 | -- CreateIndex 12 | CREATE UNIQUE INDEX "ApiSchemaSecurityScheme_identifier_schemaId_key" ON "ApiSchemaSecurityScheme"("identifier", "schemaId"); 13 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220805165511_oath_flows_security_scheme_not_optional/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[securitySchemeId,type]` on the table `ApiSchemaSecurityOAuthFlow` will be added. If there are existing duplicate values, this will fail. 5 | - Made the column `securitySchemeId` on table `ApiSchemaSecurityOAuthFlow` required. This step will fail if there are existing NULL values in that column. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "ApiSchemaSecurityOAuthFlow" ALTER COLUMN "securitySchemeId" SET NOT NULL; 10 | 11 | -- CreateIndex 12 | CREATE UNIQUE INDEX "ApiSchemaSecurityOAuthFlow_securitySchemeId_type_key" ON "ApiSchemaSecurityOAuthFlow"("securitySchemeId", "type"); 13 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220805170849_not_sure_what_has_changed/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[name,securitySchemeId]` on the table `ApiSchemaSecurityScope` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- CreateIndex 8 | CREATE UNIQUE INDEX "ApiSchemaSecurityScope_name_securitySchemeId_key" ON "ApiSchemaSecurityScope"("name", "securitySchemeId"); 9 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220805175835_adding_additional_style_values/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | -- This migration adds more than one value to an enum. 3 | -- With PostgreSQL versions 11 and earlier, this is not possible 4 | -- in a single migration. This can be worked around by creating 5 | -- multiple migrations, each migration adding only one value to 6 | -- the enum. 7 | 8 | 9 | ALTER TYPE "ApiSchemaParameterStyle" ADD VALUE 'LABEL'; 10 | ALTER TYPE "ApiSchemaParameterStyle" ADD VALUE 'SPACE_DELIMITED'; 11 | ALTER TYPE "ApiSchemaParameterStyle" ADD VALUE 'PIPE_DELIMITED'; 12 | ALTER TYPE "ApiSchemaParameterStyle" ADD VALUE 'DEEP_OBJECT'; 13 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220806195302_rename_ref_to_name/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `ref` on the `ApiSchemaModel` table. All the data in the column will be lost. 5 | - A unique constraint covering the columns `[name,schemaId]` on the table `ApiSchemaModel` will be added. If there are existing duplicate values, this will fail. 6 | - Added the required column `name` to the `ApiSchemaModel` table without a default value. This is not possible if the table is not empty. 7 | 8 | */ 9 | -- DropIndex 10 | DROP INDEX "ApiSchemaModel_ref_schemaId_key"; 11 | 12 | -- AlterTable 13 | ALTER TABLE "ApiSchemaModel" DROP COLUMN "ref", 14 | ADD COLUMN "name" TEXT NOT NULL; 15 | 16 | -- CreateIndex 17 | CREATE UNIQUE INDEX "ApiSchemaModel_name_schemaId_key" ON "ApiSchemaModel"("name", "schemaId"); 18 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220806205627_tag_names_are_unique_per_schema/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[name,schemaId]` on the table `ApiSchemaTag` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- CreateIndex 8 | CREATE UNIQUE INDEX "ApiSchemaTag_name_schemaId_key" ON "ApiSchemaTag"("name", "schemaId"); 9 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220808093750_changes_to_response_body_content/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `apiSchemaModelId` on the `ApiSchemaResponseBodyContent` table. All the data in the column will be lost. 5 | - You are about to drop the column `examples` on the `ApiSchemaResponseBodyContent` table. All the data in the column will be lost. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "ApiSchemaExample" ADD COLUMN "responseBodyId" TEXT; 10 | 11 | -- AlterTable 12 | ALTER TABLE "ApiSchemaResponseBodyContent" DROP COLUMN "apiSchemaModelId", 13 | DROP COLUMN "examples"; 14 | 15 | -- AddForeignKey 16 | ALTER TABLE "ApiSchemaExample" ADD CONSTRAINT "ApiSchemaExample_responseBodyId_fkey" FOREIGN KEY ("responseBodyId") REFERENCES "ApiSchemaResponseBodyContent"("id") ON DELETE SET NULL ON UPDATE CASCADE; 17 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220808095716_fixed_example_relation/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `responseBodyId` on the `ApiSchemaExample` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- DropForeignKey 8 | ALTER TABLE "ApiSchemaExample" DROP CONSTRAINT "ApiSchemaExample_responseBodyId_fkey"; 9 | 10 | -- AlterTable 11 | ALTER TABLE "ApiSchemaExample" DROP COLUMN "responseBodyId", 12 | ADD COLUMN "responseBodyContentId" TEXT; 13 | 14 | -- AddForeignKey 15 | ALTER TABLE "ApiSchemaExample" ADD CONSTRAINT "ApiSchemaExample_responseBodyContentId_fkey" FOREIGN KEY ("responseBodyContentId") REFERENCES "ApiSchemaResponseBodyContent"("id") ON DELETE SET NULL ON UPDATE CASCADE; 16 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220808100838_add_is_array_to_response_and_request/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "ApiSchemaRequestBodyContent" ADD COLUMN "isArray" BOOLEAN NOT NULL DEFAULT false; 3 | 4 | -- AlterTable 5 | ALTER TABLE "ApiSchemaResponseBodyContent" ADD COLUMN "isArray" BOOLEAN NOT NULL DEFAULT false; 6 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220808101243_example_value_can_be_null/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "ApiSchemaExample" ALTER COLUMN "value" DROP NOT NULL; 3 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220808205754_add_examples_model_to_request_body_content/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `examples` on the `ApiSchemaRequestBodyContent` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "ApiSchemaExample" ADD COLUMN "requestBodyContentId" TEXT; 9 | 10 | -- AlterTable 11 | ALTER TABLE "ApiSchemaRequestBodyContent" DROP COLUMN "examples"; 12 | 13 | -- AddForeignKey 14 | ALTER TABLE "ApiSchemaExample" ADD CONSTRAINT "ApiSchemaExample_requestBodyContentId_fkey" FOREIGN KEY ("requestBodyContentId") REFERENCES "ApiSchemaRequestBodyContent"("id") ON DELETE SET NULL ON UPDATE CASCADE; 15 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220808210722_make_validation_schema_optional/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "ApiSchemaRequestBodyContent" ALTER COLUMN "validationSchema" DROP NOT NULL; 3 | 4 | -- AlterTable 5 | ALTER TABLE "ApiSchemaResponseBodyContent" ALTER COLUMN "validationSchema" DROP NOT NULL; 6 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220809133547_add_sort_index_to_paths_and_operations/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "ApiSchemaOperation" ADD COLUMN "sortIndex" INTEGER NOT NULL DEFAULT 0; 3 | 4 | -- AlterTable 5 | ALTER TABLE "ApiSchemaPath" ADD COLUMN "sortIndex" INTEGER NOT NULL DEFAULT 0; 6 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220809144832_move_data_to_schema/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `data` on the `Integration` table. All the data in the column will be lost. 5 | - Added the required column `rawData` to the `ApiSchema` table without a default value. This is not possible if the table is not empty. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "ApiSchema" ADD COLUMN "rawData" JSONB NOT NULL; 10 | 11 | -- AlterTable 12 | ALTER TABLE "Integration" DROP COLUMN "data"; 13 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220810090738_make_schema_optional_on_servers/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "ApiSchemaServer" ALTER COLUMN "schemaId" DROP NOT NULL; 3 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220810154341_add_summary_to_more_models/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "ApiSchemaParameter" ADD COLUMN "summary" TEXT; 3 | 4 | -- AlterTable 5 | ALTER TABLE "ApiSchemaRequestBody" ADD COLUMN "summary" TEXT; 6 | 7 | -- AlterTable 8 | ALTER TABLE "ApiSchemaResponseBody" ADD COLUMN "summary" TEXT; 9 | 10 | -- AlterTable 11 | ALTER TABLE "ApiSchemaResponseHeader" ADD COLUMN "summary" TEXT; 12 | 13 | -- AlterTable 14 | ALTER TABLE "ApiSchemaSecurityScheme" ADD COLUMN "summary" TEXT; 15 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220810164155_add_schema_to_response_headers/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `schemaId` to the `ApiSchemaResponseHeader` table without a default value. This is not possible if the table is not empty. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "ApiSchemaResponseHeader" ADD COLUMN "schemaId" TEXT NOT NULL; 9 | 10 | -- AddForeignKey 11 | ALTER TABLE "ApiSchemaResponseHeader" ADD CONSTRAINT "ApiSchemaResponseHeader_schemaId_fkey" FOREIGN KEY ("schemaId") REFERENCES "ApiSchema"("id") ON DELETE CASCADE ON UPDATE CASCADE; 12 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220810164326_add_unique_index_to_headers/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[ref,schemaId]` on the table `ApiSchemaResponseHeader` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- CreateIndex 8 | CREATE UNIQUE INDEX "ApiSchemaResponseHeader_ref_schemaId_key" ON "ApiSchemaResponseHeader"("ref", "schemaId"); 9 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220811081441_add_ref_to_examples/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[ref,schemaId]` on the table `ApiSchemaExample` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "ApiSchemaExample" ADD COLUMN "ref" TEXT; 9 | 10 | -- CreateIndex 11 | CREATE UNIQUE INDEX "ApiSchemaExample_ref_schemaId_key" ON "ApiSchemaExample"("ref", "schemaId"); 12 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220811084302_add_external_value_to_example/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "ApiSchemaExample" ADD COLUMN "externalValue" TEXT; 3 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220811084812_cascade_delete_servers/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropForeignKey 2 | ALTER TABLE "ApiSchemaServer" DROP CONSTRAINT "ApiSchemaServer_operationId_fkey"; 3 | 4 | -- DropForeignKey 5 | ALTER TABLE "ApiSchemaServer" DROP CONSTRAINT "ApiSchemaServer_pathId_fkey"; 6 | 7 | -- AddForeignKey 8 | ALTER TABLE "ApiSchemaServer" ADD CONSTRAINT "ApiSchemaServer_pathId_fkey" FOREIGN KEY ("pathId") REFERENCES "ApiSchemaPath"("id") ON DELETE CASCADE ON UPDATE CASCADE; 9 | 10 | -- AddForeignKey 11 | ALTER TABLE "ApiSchemaServer" ADD CONSTRAINT "ApiSchemaServer_operationId_fkey" FOREIGN KEY ("operationId") REFERENCES "ApiSchemaOperation"("id") ON DELETE CASCADE ON UPDATE CASCADE; 12 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220811141510_add_extensions_to_operations/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "ApiSchemaOperation" ADD COLUMN "extensions" JSONB; 3 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220812100148_add_schema_change/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "ApiSchemaChange" ( 3 | "id" TEXT NOT NULL, 4 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 5 | "updatedAt" TIMESTAMP(3) NOT NULL, 6 | "rawData" JSONB NOT NULL, 7 | "schemaId" TEXT NOT NULL, 8 | 9 | CONSTRAINT "ApiSchemaChange_pkey" PRIMARY KEY ("id") 10 | ); 11 | 12 | -- AddForeignKey 13 | ALTER TABLE "ApiSchemaChange" ADD CONSTRAINT "ApiSchemaChange_schemaId_fkey" FOREIGN KEY ("schemaId") REFERENCES "ApiSchema"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 14 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220812153224_add_cascade_delete_to_changes/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropForeignKey 2 | ALTER TABLE "ApiSchemaChange" DROP CONSTRAINT "ApiSchemaChange_schemaId_fkey"; 3 | 4 | -- AddForeignKey 5 | ALTER TABLE "ApiSchemaChange" ADD CONSTRAINT "ApiSchemaChange_schemaId_fkey" FOREIGN KEY ("schemaId") REFERENCES "ApiSchema"("id") ON DELETE CASCADE ON UPDATE CASCADE; 6 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220826134629_request_auth_tokens/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "RequestTokenStatus" AS ENUM ('PENDING', 'CLAIMED'); 3 | 4 | -- CreateTable 5 | CREATE TABLE "RequestToken" ( 6 | "id" TEXT NOT NULL, 7 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 8 | "expiresAt" TIMESTAMP(3) NOT NULL, 9 | "token" TEXT NOT NULL, 10 | "status" "RequestTokenStatus" NOT NULL, 11 | 12 | CONSTRAINT "RequestToken_pkey" PRIMARY KEY ("id") 13 | ); 14 | 15 | -- CreateTable 16 | CREATE TABLE "AuthToken" ( 17 | "id" TEXT NOT NULL, 18 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 19 | "token" TEXT NOT NULL, 20 | "userId" TEXT, 21 | 22 | CONSTRAINT "AuthToken_pkey" PRIMARY KEY ("id") 23 | ); 24 | 25 | -- CreateIndex 26 | CREATE UNIQUE INDEX "RequestToken_token_key" ON "RequestToken"("token"); 27 | 28 | -- CreateIndex 29 | CREATE UNIQUE INDEX "AuthToken_token_key" ON "AuthToken"("token"); 30 | 31 | -- AddForeignKey 32 | ALTER TABLE "AuthToken" ADD CONSTRAINT "AuthToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; 33 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220826141139_request_auth_token_relation/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `status` on the `RequestToken` table. All the data in the column will be lost. 5 | - A unique constraint covering the columns `[requestTokenId]` on the table `AuthToken` will be added. If there are existing duplicate values, this will fail. 6 | - Added the required column `requestTokenId` to the `AuthToken` table without a default value. This is not possible if the table is not empty. 7 | 8 | */ 9 | -- AlterTable 10 | ALTER TABLE "AuthToken" ADD COLUMN "requestTokenId" TEXT NOT NULL; 11 | 12 | -- AlterTable 13 | ALTER TABLE "RequestToken" DROP COLUMN "status"; 14 | 15 | -- DropEnum 16 | DROP TYPE "RequestTokenStatus"; 17 | 18 | -- CreateIndex 19 | CREATE UNIQUE INDEX "AuthToken_requestTokenId_key" ON "AuthToken"("requestTokenId"); 20 | 21 | -- AddForeignKey 22 | ALTER TABLE "AuthToken" ADD CONSTRAINT "AuthToken_requestTokenId_fkey" FOREIGN KEY ("requestTokenId") REFERENCES "RequestToken"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 23 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220826142551_add_releases/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Release" ( 3 | "id" TEXT NOT NULL, 4 | "version" TEXT NOT NULL, 5 | "message" TEXT NOT NULL, 6 | "isPrerelease" BOOLEAN NOT NULL DEFAULT false, 7 | "integrationId" TEXT NOT NULL, 8 | "schemaId" TEXT NOT NULL, 9 | "commit" JSONB, 10 | "tagRef" JSONB, 11 | "gitRef" JSONB, 12 | 13 | CONSTRAINT "Release_pkey" PRIMARY KEY ("id") 14 | ); 15 | 16 | -- AddForeignKey 17 | ALTER TABLE "Release" ADD CONSTRAINT "Release_integrationId_fkey" FOREIGN KEY ("integrationId") REFERENCES "Integration"("id") ON DELETE CASCADE ON UPDATE CASCADE; 18 | 19 | -- AddForeignKey 20 | ALTER TABLE "Release" ADD CONSTRAINT "Release_schemaId_fkey" FOREIGN KEY ("schemaId") REFERENCES "ApiSchema"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 21 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220826150731_add_release_created_at/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Release" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; 3 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220826152934_add_release_data/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Release" ADD COLUMN "releaseData" JSONB; 3 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220826181236_workspace_project_slug/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Project" ADD COLUMN "slug" TEXT; 3 | UPDATE "Project" SET slug = title; 4 | ALTER TABLE "Project" ALTER COLUMN "slug" SET NOT NULL; 5 | 6 | -- AlterTable 7 | ALTER TABLE "Workspace" ADD COLUMN "slug" TEXT; 8 | UPDATE "Workspace" SET slug = title; 9 | ALTER TABLE "Workspace" ALTER COLUMN "slug" SET NOT NULL; 10 | 11 | -- CreateIndex 12 | CREATE UNIQUE INDEX "Project_workspaceId_slug_key" ON "Project"("workspaceId", "slug"); 13 | 14 | -- CreateIndex 15 | CREATE UNIQUE INDEX "Workspace_slug_key" ON "Workspace"("slug"); 16 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220826183200_slug_url_friendly/migration.sql: -------------------------------------------------------------------------------- 1 | UPDATE "Workspace" SET slug = lower(slug); 2 | UPDATE "Workspace" SET slug = regexp_replace(slug, '[^[:alpha:]]', '', 'g'); 3 | 4 | UPDATE "Project" SET slug = lower(slug); 5 | UPDATE "Project" SET slug = regexp_replace(slug, '[^[:alpha:]]', '', 'g'); -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220827175218_add_mappings_to_operation/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "ApiSchemaOperation" ADD COLUMN "mappings" JSONB; 3 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220830084444_user_admin/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "User" ADD COLUMN "admin" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220830123111_add_request_log_search/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "HttpRequestLog" ADD COLUMN "search" TEXT NOT NULL DEFAULT ''; 3 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220830124818_remove_request_log_id_default/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "HttpRequestLog" ALTER COLUMN "id" DROP DEFAULT; 3 | DROP SEQUENCE "HttpRequestLog_id_seq"; 4 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220830125055_request_log_id_string/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - The primary key for the `HttpRequestLog` table will be changed. If it partially fails, the table could be left without primary key constraint. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "HttpRequestLog" DROP CONSTRAINT "HttpRequestLog_pkey", 9 | ALTER COLUMN "id" SET DATA TYPE TEXT, 10 | ADD CONSTRAINT "HttpRequestLog_pkey" PRIMARY KEY ("id"); 11 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220830222227_integration_name_keywords_description_documentation/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Integration" ADD COLUMN "description" TEXT NOT NULL DEFAULT '', 3 | ADD COLUMN "keywords" TEXT[] DEFAULT ARRAY[]::TEXT[], 4 | ADD COLUMN "name" TEXT, 5 | ADD COLUMN "officialDocumentation" TEXT; 6 | 7 | UPDATE "Integration" SET "name" = slug; 8 | ALTER TABLE "Integration" ALTER COLUMN "name" SET NOT NULL; -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220831154451_add_http_client_authentication/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "HttpClientAuthentication" ( 3 | "id" TEXT NOT NULL, 4 | "httpClientId" TEXT NOT NULL, 5 | "securitySchemeId" TEXT NOT NULL, 6 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 7 | "updatedAt" TIMESTAMP(3) NOT NULL, 8 | "authenticationData" JSONB, 9 | 10 | CONSTRAINT "HttpClientAuthentication_pkey" PRIMARY KEY ("id") 11 | ); 12 | 13 | -- CreateIndex 14 | CREATE UNIQUE INDEX "HttpClientAuthentication_httpClientId_securitySchemeId_key" ON "HttpClientAuthentication"("httpClientId", "securitySchemeId"); 15 | 16 | -- AddForeignKey 17 | ALTER TABLE "HttpClientAuthentication" ADD CONSTRAINT "HttpClientAuthentication_httpClientId_fkey" FOREIGN KEY ("httpClientId") REFERENCES "HttpClient"("id") ON DELETE CASCADE ON UPDATE CASCADE; 18 | 19 | -- AddForeignKey 20 | ALTER TABLE "HttpClientAuthentication" ADD CONSTRAINT "HttpClientAuthentication_securitySchemeId_fkey" FOREIGN KEY ("securitySchemeId") REFERENCES "ApiSchemaSecurityScheme"("id") ON DELETE CASCADE ON UPDATE CASCADE; 21 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220902102139_add_enabled_to_security_schemes/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "ApiSchemaSecurityScheme" ADD COLUMN "isEnabled" BOOLEAN NOT NULL DEFAULT true, 3 | ADD COLUMN "title" TEXT; 4 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220902111919_add_caching_options_to_clients_and_endpoints/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "HttpClient" ADD COLUMN "cacheEnabled" BOOLEAN NOT NULL DEFAULT false, 3 | ADD COLUMN "cacheTtl" INTEGER NOT NULL DEFAULT 0; 4 | 5 | -- AlterTable 6 | ALTER TABLE "HttpEndpoint" ADD COLUMN "cacheEnabled" BOOLEAN NOT NULL DEFAULT false, 7 | ADD COLUMN "cacheTtl" INTEGER NOT NULL DEFAULT 0; 8 | 9 | -- AlterTable 10 | ALTER TABLE "HttpRequestLog" ADD COLUMN "clientAuthenticationId" TEXT; 11 | 12 | -- AddForeignKey 13 | ALTER TABLE "HttpRequestLog" ADD CONSTRAINT "HttpRequestLog_clientAuthenticationId_fkey" FOREIGN KEY ("clientAuthenticationId") REFERENCES "HttpClientAuthentication"("id") ON DELETE SET NULL ON UPDATE CASCADE; 14 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220906212525_add_current_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Integration" ADD COLUMN "currentSchemaId" TEXT; 3 | 4 | -- AddForeignKey 5 | ALTER TABLE "Integration" ADD CONSTRAINT "Integration_currentSchemaId_fkey" FOREIGN KEY ("currentSchemaId") REFERENCES "ApiSchema"("id") ON DELETE SET NULL ON UPDATE CASCADE; 6 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20220922102916_use_encrypted_fields_for_auth/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "HttpClientAuthentication" ADD COLUMN "password" TEXT, 3 | ADD COLUMN "username" TEXT; 4 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20221007161505_add_magic_link_auth_method/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "AuthenticationMethod" ADD VALUE 'MAGIC_LINK'; 3 | 4 | -- AlterTable 5 | ALTER TABLE "User" ALTER COLUMN "accessToken" DROP NOT NULL; 6 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20221013115803_add_logo_image_to_integration/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Integration" ADD COLUMN "logoImage" TEXT; 3 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/20221110191519_project_add_haslogs_onboardingcomplete/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Project" ADD COLUMN "hasCompletedOnboarding" BOOLEAN NOT NULL DEFAULT false, 3 | ADD COLUMN "hasLogs" BOOLEAN NOT NULL DEFAULT false; 4 | -------------------------------------------------------------------------------- /apps/webapp/prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /apps/webapp/prisma/seed.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from ".prisma/client"; 2 | 3 | const prisma = new PrismaClient(); 4 | 5 | async function seed() { 6 | console.log(`Database has been seeded. 🌱`); 7 | } 8 | 9 | seed() 10 | .catch((e) => { 11 | console.error(e); 12 | process.exit(1); 13 | }) 14 | .finally(async () => { 15 | await prisma.$disconnect(); 16 | }); 17 | -------------------------------------------------------------------------------- /apps/webapp/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggerdotdev/apihero/2f204e8fc57343a584d8b244d7ad492b72ed1242/apps/webapp/public/favicon.ico -------------------------------------------------------------------------------- /apps/webapp/public/fonts/MonoLisa/woff/MonoLisa-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggerdotdev/apihero/2f204e8fc57343a584d8b244d7ad492b72ed1242/apps/webapp/public/fonts/MonoLisa/woff/MonoLisa-Regular.woff -------------------------------------------------------------------------------- /apps/webapp/public/fonts/MonoLisa/woff/MonoLisa-RegularItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggerdotdev/apihero/2f204e8fc57343a584d8b244d7ad492b72ed1242/apps/webapp/public/fonts/MonoLisa/woff/MonoLisa-RegularItalic.woff -------------------------------------------------------------------------------- /apps/webapp/public/fonts/MonoLisa/woff2/MonoLisa-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggerdotdev/apihero/2f204e8fc57343a584d8b244d7ad492b72ed1242/apps/webapp/public/fonts/MonoLisa/woff2/MonoLisa-Regular.woff2 -------------------------------------------------------------------------------- /apps/webapp/public/fonts/MonoLisa/woff2/MonoLisa-RegularItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggerdotdev/apihero/2f204e8fc57343a584d8b244d7ad492b72ed1242/apps/webapp/public/fonts/MonoLisa/woff2/MonoLisa-RegularItalic.woff2 -------------------------------------------------------------------------------- /apps/webapp/remix.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@remix-run/dev').AppConfig} */ 2 | module.exports = { 3 | cacheDirectory: "./node_modules/.cache/remix", 4 | ignoredRouteFiles: ["**/.*"], 5 | devServerPort: 8002, 6 | serverDependenciesToBundle: [ 7 | "@apihero/internal-nobuild", 8 | "pretty-bytes", 9 | "marked", 10 | "@cfworker/json-schema", 11 | "@apihero/node", 12 | ], 13 | watchPaths: async () => { 14 | return ["../../packages/internal-nobuild/src/**/*"]; 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /apps/webapp/remix.env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /apps/webapp/start.sh: -------------------------------------------------------------------------------- 1 | set -ex 2 | npx prisma migrate deploy --schema packages/database/prisma/schema.prisma 3 | node ./server.js -------------------------------------------------------------------------------- /apps/webapp/styles/tailwind-include.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&family=Roboto+Mono:wght@300;400;500;600;700&display=swap"); 2 | 3 | @import "tailwindcss/base"; 4 | @import "tailwindcss/components"; 5 | @import "tailwindcss/utilities"; 6 | 7 | @import "../node_modules/react-date-range/dist/styles.css"; 8 | @import "../node_modules/react-date-range/dist/theme/default.css"; 9 | @import '@tremor/react/dist/esm/tremor.css'; -------------------------------------------------------------------------------- /apps/webapp/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | const parentConfig = require("@apihero/tailwind-config/tailwind.config"); 3 | const colors = require('tailwindcss/colors') 4 | const midnightColors = { 5 | 1000: "#030713", 6 | }; 7 | const toxicColors = { 8 | 500: "#41FF54" 9 | }; 10 | module.exports = { 11 | ...parentConfig, 12 | theme: { 13 | ...parentConfig.theme, 14 | extend: { 15 | ...parentConfig.theme.extend, 16 | height: { 17 | mainMobileContainerHeight: "calc(100vh - 145px)", 18 | mainDesktopContainerHeight: "calc(100vh - 65px)", 19 | editEndpointContainerHeight: "calc(100vh - 112px)", 20 | }, 21 | colors: { 22 | midnight: midnightColors[1000], 23 | toxic: toxicColors[500], 24 | }, 25 | backgroundImage: { 26 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", 27 | "gradient-background": `radial-gradient(${colors.slate[800]} 0%,${midnightColors[1000]} 50%, ${midnightColors[1000]} 100%)`, 28 | "gradient-secondary": `linear-gradient(90deg, ${colors.blue[600]} 0%, ${colors.purple[500]} 100%)`, 29 | }, 30 | }, 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /apps/webapp/test/setup-test-env.ts: -------------------------------------------------------------------------------- 1 | import { installGlobals } from "@remix-run/node"; 2 | import "@testing-library/jest-dom/extend-expect"; 3 | 4 | installGlobals(); 5 | -------------------------------------------------------------------------------- /apps/webapp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": ["./cypress", "./cypress.config.ts"], 3 | "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"], 4 | "compilerOptions": { 5 | "lib": ["DOM", "DOM.Iterable", "ES2019"], 6 | "isolatedModules": true, 7 | "esModuleInterop": true, 8 | "jsx": "react-jsx", 9 | "moduleResolution": "node", 10 | "resolveJsonModule": true, 11 | "target": "ES2019", 12 | "strict": true, 13 | "allowJs": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "skipLibCheck": true, 16 | "experimentalDecorators": true, 17 | "emitDecoratorMetadata": true, 18 | "baseUrl": ".", 19 | "paths": { 20 | "~/*": ["./app/*"], 21 | "@apihero/internal-nobuild": [ 22 | "../../packages/internal-nobuild/src/index" 23 | ], 24 | "@apihero/internal-nobuild/*": ["../../packages/internal-nobuild/src/*"] 25 | }, 26 | 27 | // Remix takes care of building everything in `remix build`. 28 | "noEmit": true 29 | } 30 | // "references": [{ "path": "../../packages/ui/tsconfig.json" }], 31 | } 32 | -------------------------------------------------------------------------------- /apps/webapp/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import { defineConfig } from "vite"; 5 | import react from "@vitejs/plugin-react"; 6 | import tsconfigPaths from "vite-tsconfig-paths"; 7 | 8 | export default defineConfig({ 9 | plugins: [react(), tsconfigPaths()], 10 | test: { 11 | globals: true, 12 | environment: "happy-dom", 13 | setupFiles: ["./test/setup-test-env.ts"], 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /config-packages/eslint-config-custom-next/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["next", "turbo", "prettier"], 3 | settings: { 4 | next: { 5 | rootDir: ["apps/*/", "packages/*/"], 6 | }, 7 | }, 8 | rules: { 9 | "@next/next/no-html-link-for-pages": "off", 10 | "react/jsx-key": "off", 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /config-packages/eslint-config-custom-next/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-config-custom-next", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "devDependencies": { 7 | "eslint": "^8.24.0", 8 | "eslint-config-next": "^12.3.1", 9 | "eslint-config-prettier": "^8.3.0", 10 | "eslint-config-turbo": "latest", 11 | "eslint-plugin-react": "7.31.8", 12 | "typescript": "^4.8.4" 13 | }, 14 | "publishConfig": { 15 | "access": "public" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /config-packages/eslint-config-custom/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["turbo", "prettier"], 3 | settings: { 4 | react: { 5 | version: "detect", 6 | }, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /config-packages/eslint-config-custom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-config-custom", 3 | "version": "0.0.0", 4 | "private": true, 5 | "license": "MIT", 6 | "main": "index.js", 7 | "devDependencies": { 8 | "eslint": "^8.24.0", 9 | "eslint-config-prettier": "^8.3.0", 10 | "eslint-config-turbo": "latest", 11 | "eslint-plugin-react": "7.31.8", 12 | "typescript": "^4.8.4" 13 | }, 14 | "publishConfig": { 15 | "access": "public" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /config-packages/eslint-config-vite/eslint-preset.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | node: true, 4 | }, 5 | parser: "@typescript-eslint/parser", 6 | extends: [ 7 | "turbo", 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "prettier", 11 | ], 12 | plugins: ["@typescript-eslint"], 13 | parserOptions: { 14 | sourceType: "module", 15 | ecmaVersion: 2020, 16 | }, 17 | rules: { 18 | "@typescript-eslint/no-non-null-assertion": "off", 19 | "no-var": "off", 20 | "@typescript-eslint/explicit-function-return-type": "off", 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /config-packages/eslint-config-vite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@apihero/eslint-config-vite", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "files": [ 7 | "eslint-preset.js" 8 | ], 9 | "devDependencies": { 10 | "@typescript-eslint/eslint-plugin": "^5.38.1", 11 | "@typescript-eslint/parser": "^5.38.1", 12 | "eslint": "^8.24.0", 13 | "eslint-config-prettier": "^8.5.0", 14 | "eslint-config-turbo": "latest", 15 | "typescript": "^4.8.4" 16 | }, 17 | "publishConfig": { 18 | "access": "public" 19 | } 20 | } -------------------------------------------------------------------------------- /config-packages/tailwind-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@apihero/tailwind-config", 3 | "version": "0.0.0", 4 | "private": true, 5 | "devDependencies": {}, 6 | "publishConfig": { 7 | "access": "public" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /config-packages/tailwind-config/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /config-packages/tailwind-config/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const colors = require("tailwindcss/colors"); 2 | 3 | module.exports = { 4 | content: [ 5 | // app content 6 | // "./src/**/*.{ts,jsx,tsx}", 7 | "./app/**/*.{ts,jsx,tsx}", 8 | // include packages if not transpiling 9 | "../../packages/**/*.{ts,tsx}", 10 | ], 11 | theme: { 12 | extend: { 13 | fontFamily: { 14 | sans: ["Inter", "sans-serif"], 15 | mono: ["Roboto Mono", "monospace"], 16 | }, 17 | colors: { 18 | brandblue: colors.blue[500], 19 | brandred: colors.red[500], 20 | }, 21 | }, 22 | }, 23 | plugins: [require("@tailwindcss/forms"), require("@tailwindcss/typography")], 24 | }; 25 | -------------------------------------------------------------------------------- /config-packages/tsconfig/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Default", 4 | "compilerOptions": { 5 | "composite": false, 6 | "declaration": true, 7 | "declarationMap": true, 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "inlineSources": false, 11 | "isolatedModules": true, 12 | "moduleResolution": "node", 13 | "noUnusedLocals": false, 14 | "noUnusedParameters": false, 15 | "preserveWatchOutput": true, 16 | "skipLibCheck": true, 17 | "strict": true, 18 | "experimentalDecorators": true, 19 | "emitDecoratorMetadata": true, 20 | "sourceMap": true, 21 | "resolveJsonModule": true 22 | }, 23 | "exclude": ["node_modules", "**/*/lib", "**/*/dist"], 24 | "references": [{ "path": "../utils/" }] 25 | } 26 | -------------------------------------------------------------------------------- /config-packages/tsconfig/nextjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Next.js", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "target": "es5", 7 | "lib": ["dom", "dom.iterable", "esnext"], 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "strict": false, 11 | "forceConsistentCasingInFileNames": true, 12 | "noEmit": true, 13 | "incremental": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "jsx": "preserve" 19 | }, 20 | "include": ["src", "next-env.d.ts"], 21 | "exclude": ["node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /config-packages/tsconfig/node18.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Node 18", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "lib": [ 7 | "ES2021" 8 | ], 9 | "module": "commonjs", 10 | "target": "ES2021", 11 | "strict": true, 12 | "esModuleInterop": true, 13 | "skipLibCheck": true, 14 | "forceConsistentCasingInFileNames": true 15 | } 16 | } -------------------------------------------------------------------------------- /config-packages/tsconfig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@apihero/tsconfig", 3 | "version": "0.0.0", 4 | "private": true, 5 | "license": "MIT", 6 | "publishConfig": { 7 | "access": "public" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /config-packages/tsconfig/react-library.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "React Library", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "lib": ["ES2015"], 7 | "module": "ESNext", 8 | "target": "ES6", 9 | "jsx": "react-jsx" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /docker-compose-ci.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | postgres: 4 | image: postgres:latest 5 | restart: always 6 | environment: 7 | - POSTGRES_USER=postgres 8 | - POSTGRES_PASSWORD=postgres 9 | - POSTGRES_DB=postgres 10 | ports: 11 | - "5432:5432" 12 | volumes: 13 | - ./postgres-data:/var/lib/postgresql/data 14 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | volumes: 4 | database: 5 | driver: local 6 | 7 | services: 8 | redis: 9 | image: redis:7.0.0-alpine 10 | command: redis-server 11 | restart: always 12 | ports: 13 | - 6379:6379 14 | db: 15 | image: postgres:latest 16 | restart: always 17 | environment: 18 | - POSTGRES_USER=postgres 19 | - POSTGRES_PASSWORD=postgres 20 | - POSTGRES_DB=postgres 21 | ports: 22 | - "5432:5432" 23 | volumes: 24 | - database:/var/lib/postgresql-docker/data 25 | networks: 26 | - app_network 27 | logs: 28 | image: timescale/timescaledb:latest-pg14 29 | restart: always 30 | environment: 31 | - POSTGRES_USER=postgres 32 | - POSTGRES_PASSWORD=postgres 33 | - POSTGRES_DB=postgres 34 | ports: 35 | - "5433:5432" 36 | volumes: 37 | - database:/var/lib/postgresql-docker/logs 38 | networks: 39 | - app_network 40 | 41 | networks: 42 | app_network: 43 | external: true 44 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | ## API Hero examples 2 | 3 | Each directory is it's own example, and should be run according to the instructions in the README in that specific example. 4 | -------------------------------------------------------------------------------- /examples/fetch-load-test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@examples/fetch-load-test", 4 | "version": "0.0.1", 5 | "description": "Fetch load test", 6 | "dependencies": { 7 | "@apihero/node": "workspace:*", 8 | "node-fetch": "2.6.x" 9 | }, 10 | "devDependencies": { 11 | "@apihero/tsconfig": "*", 12 | "@types/node": "^18.11.9", 13 | "@types/node-fetch": "2.6.x", 14 | "tsx": "^3.12.0" 15 | }, 16 | "scripts": { 17 | "start": "tsx src/index.ts", 18 | "start:bin": "docker run -p 8080:80 kennethreitz/httpbin" 19 | } 20 | } -------------------------------------------------------------------------------- /examples/fetch-load-test/scripts/run.sh: -------------------------------------------------------------------------------- 1 | # Don't run this script directly, but use it as a reference 2 | 3 | # In a separate tab, in /examples/fetch-load-test, run the following command to start the httpbin server: 4 | pnpm run start:bin 5 | 6 | # In the root of the repo, run this command to start the proxy server: 7 | pnpm run dev --filter proxy... 8 | 9 | # in the root of the repo, run this command to star the logs server: 10 | pnpm run dev --filter ./apps/logs 11 | 12 | # Now in /examples/fetch-load-test, you can run the following command to create a load test: 13 | pnpm run start -n 1 -p hero_123abc --env development --url "http://localhost:8787" -v --bin-url "http://localhost:8080" 14 | 15 | # If you want to run more iterations, you can run the following command: 16 | pnpm run start -n 100 -p hero_123abc --env development --url "http://localhost:8787" -v --bin-url "http://localhost:8080" -------------------------------------------------------------------------------- /examples/fetch-load-test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@apihero/tsconfig/node18.json", 3 | "include": ["src/**/*.ts"], 4 | "compilerOptions": { 5 | "experimentalDecorators": true, 6 | "emitDecoratorMetadata": true, 7 | "lib": ["esnext", "dom"], 8 | "outDir": "lib", 9 | "moduleResolution": "node" 10 | }, 11 | "exclude": ["node_modules", "**/*.test.*"] 12 | } 13 | -------------------------------------------------------------------------------- /examples/next-github-stars/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /examples/next-github-stars/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /examples/next-github-stars/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | swcMinify: true, 5 | } 6 | 7 | module.exports = nextConfig 8 | -------------------------------------------------------------------------------- /examples/next-github-stars/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@examples/next-github-stars", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev -p 8880", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "install:worker": "apihero-cli init public/" 11 | }, 12 | "dependencies": { 13 | "@apihero/browser": "workspace:*", 14 | "@apihero/node": "workspace:*", 15 | "@tanstack/react-query": "^4.16.1", 16 | "@types/node": "18.11.9", 17 | "@types/react": "^18.0.21", 18 | "@types/react-dom": "^18.0.6", 19 | "next": "13.0.3", 20 | "react": "^18.2.0", 21 | "react-dom": "^18.2.0", 22 | "typescript": "4.8.4" 23 | }, 24 | "apihero": { 25 | "workerDirectory": "public" 26 | }, 27 | "devDependencies": { 28 | "eslint": "^8.24.0", 29 | "eslint-config-next": "^13.0.3" 30 | } 31 | } -------------------------------------------------------------------------------- /examples/next-github-stars/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import "../styles/globals.css"; 2 | import type { AppProps } from "next/app"; 3 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 4 | 5 | require("../src/apihero"); 6 | 7 | const queryClient = new QueryClient(); 8 | 9 | export default function App({ Component, pageProps }: AppProps) { 10 | return ( 11 | 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /examples/next-github-stars/pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from 'next' 3 | 4 | type Data = { 5 | name: string 6 | } 7 | 8 | export default function handler( 9 | req: NextApiRequest, 10 | res: NextApiResponse 11 | ) { 12 | res.status(200).json({ name: 'John Doe' }) 13 | } 14 | -------------------------------------------------------------------------------- /examples/next-github-stars/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import Image from "next/image"; 3 | import { Main } from "../components/Main"; 4 | import styles from "../styles/Home.module.css"; 5 | 6 | export default function Home() { 7 | return ( 8 |
9 | 10 | Create Next App 11 | 12 | 13 | 14 | 15 |
16 | 17 | 29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /examples/next-github-stars/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggerdotdev/apihero/2f204e8fc57343a584d8b244d7ad492b72ed1242/examples/next-github-stars/public/favicon.ico -------------------------------------------------------------------------------- /examples/next-github-stars/public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /examples/next-github-stars/src/apihero.ts: -------------------------------------------------------------------------------- 1 | async function initProxy() { 2 | if (typeof window !== "undefined") { 3 | const { setupWorker } = await import("@apihero/browser"); 4 | const worker = setupWorker({ 5 | // You MUST supply the allow option for setupWorker, to ensure too many requests are not sent to the API Hero proxy 6 | allow: ["https://api.github.com/*"], 7 | projectKey: "hero_123abc", 8 | url: "http://localhost:8787", 9 | env: process.env.NODE_ENV, 10 | }); 11 | await worker.start(); 12 | } else { 13 | const { setupProxy } = await import("@apihero/node"); 14 | const proxy = setupProxy({ 15 | projectKey: "hero_123abc", 16 | url: "http://localhost:8787", 17 | env: process.env.NODE_ENV, 18 | }); 19 | 20 | await proxy.start(); 21 | } 22 | } 23 | 24 | initProxy(); 25 | 26 | export {}; 27 | -------------------------------------------------------------------------------- /examples/next-github-stars/styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | 18 | @media (prefers-color-scheme: dark) { 19 | html { 20 | color-scheme: dark; 21 | } 22 | body { 23 | color: white; 24 | background: black; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/next-github-stars/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true 17 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/apihero-browser/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require("@apihero/eslint-config-vite/eslint-preset"); 2 | -------------------------------------------------------------------------------- /packages/apihero-browser/.gitignore: -------------------------------------------------------------------------------- 1 | lib -------------------------------------------------------------------------------- /packages/apihero-browser/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2022] [API Hero, Inc.] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/apihero-browser/cli/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* eslint-disable @typescript-eslint/no-var-requires */ 3 | const yargs = require("yargs"); 4 | 5 | yargs 6 | .usage("$0 [args]") 7 | .command( 8 | "init ", 9 | "Initializes API Hero Service Worker at the specified directory", 10 | (yargs) => { 11 | yargs 12 | .positional("publicDir", { 13 | type: "string", 14 | description: "Relative path to the public directory", 15 | required: true, 16 | normalize: true, 17 | }) 18 | .example("init", "apihero-js init public") 19 | .option("save", { 20 | type: "boolean", 21 | description: "Save the worker directory in your package.json", 22 | }); 23 | }, 24 | require("./init") 25 | ) 26 | .demandCommand() 27 | .help().argv; 28 | -------------------------------------------------------------------------------- /packages/apihero-browser/cli/invariant.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const chalk = require("chalk"); 3 | 4 | module.exports = function (predicate, message, ...args) { 5 | if (!predicate) { 6 | console.error(chalk.red(message), ...args); 7 | process.exit(1); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /packages/apihero-browser/config/constants.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const path = require("path"); 3 | 4 | const SERVICE_WORKER_SOURCE_PATH = path.resolve( 5 | __dirname, 6 | "../", 7 | "src/proxyServiceWorker.js" 8 | ); 9 | 10 | const SERVICE_WORKER_BUILD_PATH = path.resolve( 11 | __dirname, 12 | "../lib", 13 | path.basename(SERVICE_WORKER_SOURCE_PATH) 14 | ); 15 | 16 | module.exports = { 17 | SERVICE_WORKER_SOURCE_PATH, 18 | SERVICE_WORKER_BUILD_PATH, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/apihero-browser/global.d.ts: -------------------------------------------------------------------------------- 1 | declare const SERVICE_WORKER_CHECKSUM: string; 2 | -------------------------------------------------------------------------------- /packages/apihero-browser/src/babel-minify.d.ts: -------------------------------------------------------------------------------- 1 | declare module "babel-minify"; 2 | -------------------------------------------------------------------------------- /packages/apihero-browser/src/browser/utils/formatting.ts: -------------------------------------------------------------------------------- 1 | const LIBRARY_PREFIX = "[APIHERO]"; 2 | 3 | export function formatMessage(message: string): string { 4 | return `${LIBRARY_PREFIX} ${message}`; 5 | } 6 | -------------------------------------------------------------------------------- /packages/apihero-browser/src/browser/utils/integrityCheck.ts: -------------------------------------------------------------------------------- 1 | import { SetupWorkerInternalContext } from "../../types"; 2 | 3 | export async function requestIntegrityCheck( 4 | context: SetupWorkerInternalContext, 5 | serviceWorker: ServiceWorker 6 | ): Promise { 7 | // Signal Service Worker to report back its integrity 8 | context.workerChannel.send("INTEGRITY_CHECK_REQUEST"); 9 | 10 | const { payload: actualChecksum } = await context.events.once( 11 | "INTEGRITY_CHECK_RESPONSE" 12 | ); 13 | 14 | // Compare the response from the Service Worker and the 15 | // global variable set during the build. 16 | if (actualChecksum !== SERVICE_WORKER_CHECKSUM) { 17 | throw new Error( 18 | `Currently active Service Worker (${actualChecksum}) is behind the latest published one (${SERVICE_WORKER_CHECKSUM}).` 19 | ); 20 | } 21 | 22 | return serviceWorker; 23 | } 24 | -------------------------------------------------------------------------------- /packages/apihero-browser/src/browser/utils/internal.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Deeply merges two given objects with the right one 3 | * having a priority during property assignment. 4 | */ 5 | export function mergeRight( 6 | left: Record, 7 | right: Record 8 | ) { 9 | return Object.entries(right).reduce((result, [key, rightValue]) => { 10 | const leftValue = result[key]; 11 | 12 | if (Array.isArray(leftValue) && Array.isArray(rightValue)) { 13 | result[key] = leftValue.concat(rightValue); 14 | return result; 15 | } 16 | 17 | if (isObject(leftValue) && isObject(rightValue)) { 18 | result[key] = mergeRight(leftValue, rightValue); 19 | return result; 20 | } 21 | 22 | result[key] = rightValue; 23 | return result; 24 | }, Object.assign({}, left)); 25 | } 26 | 27 | /** 28 | * Determines if the given value is an object. 29 | */ 30 | function isObject(value: any): boolean { 31 | return value != null && typeof value === "object" && !Array.isArray(value); 32 | } 33 | -------------------------------------------------------------------------------- /packages/apihero-browser/src/browser/utils/messageChannel.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ModifiedFetchRequest, 3 | ServiceWorkerIncomingEventsMap, 4 | } from "../../types"; 5 | 6 | export interface ServiceWorkerMessage< 7 | EventType extends keyof ServiceWorkerIncomingEventsMap, 8 | EventPayload 9 | > { 10 | type: EventType; 11 | payload: EventPayload; 12 | } 13 | 14 | interface WorkerChannelEventsMap { 15 | PROXY_REQUEST: [data: ModifiedFetchRequest]; 16 | PROXY_BYPASS: []; 17 | ERROR: [error: { name: string; message: string }]; 18 | } 19 | 20 | export class WorkerChannel { 21 | constructor(private readonly port: MessagePort) {} 22 | 23 | public postMessage( 24 | event: Event, 25 | ...rest: WorkerChannelEventsMap[Event] 26 | ): void { 27 | const [data] = rest; 28 | this.port.postMessage({ type: event, data }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/apihero-browser/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const DESTINATION_HEADER_NAME = "x-ah-origin"; 2 | export const PROJECT_KEY_HEADER_NAME = "x-ah-pk"; 3 | export const REQUEST_ID_HEADER_NAME = "x-ah-request-id"; 4 | export const PROTOCOL_HEADER_NAME = "x-ah-proto"; 5 | export const PAYLOAD_HEADER_NAME = "x-ah-payload"; 6 | -------------------------------------------------------------------------------- /packages/apihero-browser/src/index.ts: -------------------------------------------------------------------------------- 1 | export { setupWorker } from "./browser/setupWorker"; 2 | export * from "./types"; 3 | -------------------------------------------------------------------------------- /packages/apihero-browser/test/browser/setupWorker.example.ts: -------------------------------------------------------------------------------- 1 | import { setupWorker } from "apihero-js"; 2 | 3 | const worker = setupWorker({ 4 | projectKey: "hero_abc123", 5 | url: document.title, 6 | allow: ["http://httpbin.org/*"], 7 | }); 8 | 9 | worker.start(); 10 | -------------------------------------------------------------------------------- /packages/apihero-browser/test/browser/setupWorker.noAllow.example.ts: -------------------------------------------------------------------------------- 1 | import { setupWorker } from "apihero-js"; 2 | 3 | const worker = setupWorker({ 4 | projectKey: "hero_abc123", 5 | url: document.title, 6 | }); 7 | 8 | worker.start(); 9 | -------------------------------------------------------------------------------- /packages/apihero-browser/test/support/certs/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC7DCCAdQCCQCK5n/hQJeeYjANBgkqhkiG9w0BAQUFADA3MRQwEgYDVQQDDAth 3 | cGloZXJvLnJ1bjEfMB0GCSqGSIb3DQEJARYQZXJpY0BhcGloZXJvLnJ1bjAgFw0y 4 | MjExMDkxNTIxNTlaGA8yMDUwMDMyNjE1MjE1OVowNzEUMBIGA1UEAwwLYXBpaGVy 5 | by5ydW4xHzAdBgkqhkiG9w0BCQEWEGVyaWNAYXBpaGVyby5ydW4wggEiMA0GCSqG 6 | SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCekNz2Kze2JvFpLxnMPCRt+jMmkMej1qfo 7 | 8mvN+lBx6PPf94aDpvk73jiMmJYshEPW/V8G8HYliTrfqslTEOsftGWy+1KCeUGI 8 | nPYb+02SZvZj05KTz2bQ5bQWwPiuKuClZWpmUxA3ejwdxm3/Hz6Z/NrwLqxhFdfM 9 | 50QyqtUEhYtlA7wI6PAg1RjI50lG9mzp0wf3uslC2ZudiO3mPMgPrK5T/dODrQkg 10 | E3IT+xJBT9/ej/rQ26Zw/7ZH7HORPRqulaiW6uhqP81LOJmvKZ1qPNrv+wB2dZT5 11 | Yo0Iz2sPa9IprALi5MVjXPi9sglR4wzoR5PC7RjAGPx8qSY3ZB0BAgMBAAEwDQYJ 12 | KoZIhvcNAQEFBQADggEBAJGitJ8UJl+hUOvPojqVEKvgwN595xEu82k4pMmnSMsM 13 | kCQtVNXrglPo4NgZSGgTnscYjTAeD5kGtI3zDnMKaYppfX7Sjb/Fp4Tlm12tkMZw 14 | 1sjVcq1Ibvk7Tlt/l4hmIUJDLmqSqHFmwpVYLWnqksSMRGDrs51pvSUNCb5xwLco 15 | Uoiq0lHfkPqm2EhUPLwwzlQVuYKBKHcB3x7yb+wv5k9hnG5t/Q3dzZcYlc7B36An 16 | vdkpLofYWwVOqCJAfsnEdeaMPCRoKLO+BhvfFC7gKF6Ojv6+j8AEKt1d5qvmeV+e 17 | vhQD4HhABWE2ddaLnP1cGmt7tqwAxkqkZxYBv1ohyHA= 18 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /packages/apihero-browser/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "global.d.ts", 4 | "src/**/*.ts", 5 | "tsup.config.ts", 6 | "../interceptors-js/src/types.ts" 7 | ], 8 | "compilerOptions": { 9 | "strict": true, 10 | "target": "es6", 11 | "module": "ESNext", 12 | "moduleResolution": "node", 13 | "allowSyntheticDefaultImports": true, 14 | "esModuleInterop": true, 15 | "resolveJsonModule": true, 16 | "declaration": true, 17 | "declarationDir": "lib/types", 18 | "noEmit": true, 19 | "lib": ["es2017", "ESNext.AsyncIterable", "dom", "webworker"] 20 | }, 21 | "exclude": ["node_modules", "**/*.test.*"] 22 | } 23 | -------------------------------------------------------------------------------- /packages/apihero-browser/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | import { workerScriptPlugin } from "./config/plugins/esbuild/workerScriptPlugin"; 3 | 4 | export default defineConfig([ 5 | { 6 | name: "main", 7 | entry: ["./src/index.ts"], 8 | outDir: "./lib", 9 | format: ["esm", "cjs"], 10 | legacyOutput: true, 11 | sourcemap: true, 12 | clean: true, 13 | bundle: true, 14 | splitting: false, 15 | dts: true, 16 | esbuildPlugins: [workerScriptPlugin()], 17 | }, 18 | { 19 | name: "iife", 20 | entry: ["./src/index.ts"], 21 | outDir: "./lib", 22 | legacyOutput: true, 23 | format: ["iife"], 24 | platform: "browser", 25 | globalName: "ApiHero", 26 | bundle: true, 27 | sourcemap: true, 28 | splitting: false, 29 | dts: true, 30 | esbuildPlugins: [workerScriptPlugin()], 31 | }, 32 | ]); 33 | -------------------------------------------------------------------------------- /packages/apihero-browser/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import { defineConfig } from "vite"; 5 | import tsconfigPaths from "vite-tsconfig-paths"; 6 | 7 | export default defineConfig({ 8 | plugins: [tsconfigPaths()], 9 | test: { 10 | globals: true, 11 | environment: "happy-dom", 12 | setupFiles: ["./test/vitest.setup.ts"], 13 | // setupFiles: ["./test/setup-test-env.ts"], 14 | include: ["./test/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], 15 | watchExclude: [".*\\/node_modules\\/.*", ".*\\/build\\/.*"], 16 | exclude: [ 17 | "node_modules", 18 | "dist", 19 | ".idea", 20 | ".git", 21 | ".cache", 22 | "**/*integration.test.ts", 23 | ], 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /packages/apihero-fetch/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @apihero/fetch 2 | 3 | ## 0.1.0 4 | 5 | ### Minor Changes 6 | 7 | - 689c55d: Created new @apihero/fetch package, and fixed missing searchParams in others 8 | -------------------------------------------------------------------------------- /packages/apihero-fetch/lib/index.d.ts: -------------------------------------------------------------------------------- 1 | type FetchFunction = typeof fetch; 2 | type FetchProxyOptions = { 3 | projectKey: string; 4 | url?: string; 5 | env?: string; 6 | }; 7 | declare function createFetchProxy(options: FetchProxyOptions): FetchFunction; 8 | 9 | export { FetchFunction, FetchProxyOptions, createFetchProxy }; 10 | -------------------------------------------------------------------------------- /packages/apihero-fetch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@apihero/fetch", 3 | "version": "0.1.0", 4 | "publishConfig": { 5 | "access": "public" 6 | }, 7 | "license": "MIT", 8 | "main": "./lib/index.js", 9 | "types": "./lib/index.d.ts", 10 | "module": "./lib/esm/index.js", 11 | "files": [ 12 | "lib" 13 | ], 14 | "sideEffects": false, 15 | "scripts": { 16 | "build": "npm run clean && npm run build:tsup", 17 | "build:tsup": "tsup", 18 | "clean": "rimraf lib", 19 | "dev": "tsup --watch", 20 | "lint": "eslint ./src --fix", 21 | "test": "vitest -c vitest.config.ts --run" 22 | }, 23 | "dependencies": { 24 | "debug": "^4.3.4" 25 | }, 26 | "devDependencies": { 27 | "@apihero/eslint-config-vite": "workspace:*", 28 | "@apihero/tsconfig": "workspace:*", 29 | "internal-constants": "workspace:*", 30 | "@types/debug": "^4.1.7", 31 | "@types/expect": "^24.3.0", 32 | "cors": "^2.8.5", 33 | "express": "^4.18.2", 34 | "rimraf": "^3.0.2", 35 | "tsup": "^6.2.3", 36 | "typescript": "^4.9.0", 37 | "vite": "^3.1.4", 38 | "vite-tsconfig-paths": "^3.5.1", 39 | "vitest": "^0.24.4" 40 | } 41 | } -------------------------------------------------------------------------------- /packages/apihero-fetch/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*.ts", "tsup.config.ts"], 3 | "compilerOptions": { 4 | "strict": true, 5 | "target": "es6", 6 | "module": "ESNext", 7 | "moduleResolution": "node", 8 | "allowSyntheticDefaultImports": true, 9 | "esModuleInterop": true, 10 | "resolveJsonModule": true, 11 | "declaration": true, 12 | "declarationDir": "lib/types", 13 | "noEmit": true, 14 | "lib": ["es2017", "webworker"] 15 | }, 16 | "exclude": ["node_modules", "**/*.test.*"] 17 | } 18 | -------------------------------------------------------------------------------- /packages/apihero-fetch/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | 3 | // Prevent from bundling the "@apihero/*" packages 4 | // so that the users get the latest versions without 5 | // having to bump them in "apihero-js'." 6 | // const ecosystemDependencies = /^@apihero\/(.+)$/; 7 | 8 | export default defineConfig([ 9 | { 10 | name: "main", 11 | entry: ["./src/index.ts"], 12 | outDir: "./lib", 13 | platform: "neutral", 14 | format: ["cjs", "esm"], 15 | legacyOutput: true, 16 | sourcemap: true, 17 | clean: true, 18 | bundle: true, 19 | splitting: false, 20 | dts: true, 21 | }, 22 | ]); 23 | -------------------------------------------------------------------------------- /packages/apihero-fetch/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import { defineConfig } from "vite"; 5 | import tsconfigPaths from "vite-tsconfig-paths"; 6 | 7 | export default defineConfig({ 8 | plugins: [tsconfigPaths()], 9 | test: { 10 | globals: true, 11 | environment: "happy-dom", 12 | // setupFiles: ["./test/setup-test-env.ts"], 13 | include: ["./test/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], 14 | watchExclude: [".*\\/node_modules\\/.*", ".*\\/build\\/.*"], 15 | exclude: [ 16 | "node_modules", 17 | "dist", 18 | ".idea", 19 | ".git", 20 | ".cache", 21 | "**/*integration.test.ts", 22 | ], 23 | }, 24 | }); 25 | -------------------------------------------------------------------------------- /packages/apihero-node/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require("@apihero/eslint-config-vite/eslint-preset"); 2 | -------------------------------------------------------------------------------- /packages/apihero-node/.gitignore: -------------------------------------------------------------------------------- 1 | lib -------------------------------------------------------------------------------- /packages/apihero-node/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2022] [API Hero, Inc.] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/apihero-node/src/index.ts: -------------------------------------------------------------------------------- 1 | export { setupProxy } from "./node"; 2 | export * from "./types"; 3 | -------------------------------------------------------------------------------- /packages/apihero-node/src/types.ts: -------------------------------------------------------------------------------- 1 | export type PolicyMatcher = { 2 | method: string; 3 | url: string; 4 | }; 5 | 6 | export type PolicyRule = string | PolicyMatcher; 7 | 8 | export type SetupProxyOptions = { 9 | projectKey: string; 10 | url?: string; 11 | allow?: Array; 12 | deny?: Array; 13 | env?: string; 14 | }; 15 | 16 | export type SetupProxyInstance = { 17 | start(callback?: () => void): void; 18 | stop(): void; 19 | }; 20 | 21 | export type ModifiedFetchRequest = { 22 | url?: string; 23 | method?: string; 24 | headers?: Record; 25 | }; 26 | 27 | type Fn = (...arg: unknown[]) => unknown; 28 | export type RequiredDeep< 29 | Type, 30 | U extends Record | Fn | undefined = undefined 31 | > = Type extends Fn 32 | ? Type 33 | : /** 34 | * @note The "Fn" type satisfies the predicate below. 35 | * It must always come first, before the Record check. 36 | */ 37 | Type extends Record 38 | ? { 39 | [Key in keyof Type]-?: NonNullable extends NonNullable 40 | ? NonNullable 41 | : RequiredDeep, U>; 42 | } 43 | : Type; 44 | -------------------------------------------------------------------------------- /packages/apihero-node/test/support/certs/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC7DCCAdQCCQCK5n/hQJeeYjANBgkqhkiG9w0BAQUFADA3MRQwEgYDVQQDDAth 3 | cGloZXJvLnJ1bjEfMB0GCSqGSIb3DQEJARYQZXJpY0BhcGloZXJvLnJ1bjAgFw0y 4 | MjExMDkxNTIxNTlaGA8yMDUwMDMyNjE1MjE1OVowNzEUMBIGA1UEAwwLYXBpaGVy 5 | by5ydW4xHzAdBgkqhkiG9w0BCQEWEGVyaWNAYXBpaGVyby5ydW4wggEiMA0GCSqG 6 | SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCekNz2Kze2JvFpLxnMPCRt+jMmkMej1qfo 7 | 8mvN+lBx6PPf94aDpvk73jiMmJYshEPW/V8G8HYliTrfqslTEOsftGWy+1KCeUGI 8 | nPYb+02SZvZj05KTz2bQ5bQWwPiuKuClZWpmUxA3ejwdxm3/Hz6Z/NrwLqxhFdfM 9 | 50QyqtUEhYtlA7wI6PAg1RjI50lG9mzp0wf3uslC2ZudiO3mPMgPrK5T/dODrQkg 10 | E3IT+xJBT9/ej/rQ26Zw/7ZH7HORPRqulaiW6uhqP81LOJmvKZ1qPNrv+wB2dZT5 11 | Yo0Iz2sPa9IprALi5MVjXPi9sglR4wzoR5PC7RjAGPx8qSY3ZB0BAgMBAAEwDQYJ 12 | KoZIhvcNAQEFBQADggEBAJGitJ8UJl+hUOvPojqVEKvgwN595xEu82k4pMmnSMsM 13 | kCQtVNXrglPo4NgZSGgTnscYjTAeD5kGtI3zDnMKaYppfX7Sjb/Fp4Tlm12tkMZw 14 | 1sjVcq1Ibvk7Tlt/l4hmIUJDLmqSqHFmwpVYLWnqksSMRGDrs51pvSUNCb5xwLco 15 | Uoiq0lHfkPqm2EhUPLwwzlQVuYKBKHcB3x7yb+wv5k9hnG5t/Q3dzZcYlc7B36An 16 | vdkpLofYWwVOqCJAfsnEdeaMPCRoKLO+BhvfFC7gKF6Ojv6+j8AEKt1d5qvmeV+e 17 | vhQD4HhABWE2ddaLnP1cGmt7tqwAxkqkZxYBv1ohyHA= 18 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /packages/apihero-node/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*.ts", "tsup.config.ts"], 3 | "compilerOptions": { 4 | "strict": true, 5 | "target": "es6", 6 | "module": "ESNext", 7 | "moduleResolution": "node", 8 | "allowSyntheticDefaultImports": true, 9 | "esModuleInterop": true, 10 | "resolveJsonModule": true, 11 | "declaration": true, 12 | "declarationDir": "lib/types", 13 | "noEmit": true, 14 | "lib": ["es2017", "ESNext.AsyncIterable"] 15 | }, 16 | "exclude": ["node_modules", "**/*.test.*"] 17 | } 18 | -------------------------------------------------------------------------------- /packages/apihero-node/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | 3 | // Prevent from bundling the "@apihero/*" packages 4 | // so that the users get the latest versions without 5 | // having to bump them in "apihero-js'." 6 | // const ecosystemDependencies = /^@apihero\/(.+)$/; 7 | 8 | export default defineConfig([ 9 | { 10 | name: "main", 11 | entry: ["./src/index.ts"], 12 | outDir: "./lib", 13 | platform: "node", 14 | format: ["cjs"], 15 | legacyOutput: true, 16 | sourcemap: true, 17 | clean: true, 18 | bundle: true, 19 | splitting: false, 20 | dts: true, 21 | external: ["http", "https", "util", "events", "tty", "os", "timers"], 22 | esbuildPlugins: [], 23 | }, 24 | ]); 25 | -------------------------------------------------------------------------------- /packages/apihero-node/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import { defineConfig } from "vite"; 5 | import tsconfigPaths from "vite-tsconfig-paths"; 6 | 7 | export default defineConfig({ 8 | plugins: [tsconfigPaths()], 9 | test: { 10 | globals: true, 11 | environment: "node", 12 | // setupFiles: ["./test/setup-test-env.ts"], 13 | include: ["./test/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], 14 | watchExclude: [".*\\/node_modules\\/.*", ".*\\/build\\/.*"], 15 | exclude: [ 16 | "node_modules", 17 | "dist", 18 | ".idea", 19 | ".git", 20 | ".cache", 21 | "**/*integration.test.ts", 22 | ], 23 | }, 24 | }); 25 | -------------------------------------------------------------------------------- /packages/interceptors-js/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require("@apihero/eslint-config-vite/eslint-preset"); 2 | -------------------------------------------------------------------------------- /packages/interceptors-js/.gitignore: -------------------------------------------------------------------------------- 1 | lib -------------------------------------------------------------------------------- /packages/interceptors-js/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @apihero/interceptors-js 2 | 3 | ## 1.0.2 4 | 5 | ### Patch Changes 6 | 7 | - 8f3dd26: Add support for API Hero in Cloudflare Workers (or any other web-fetch environment) 8 | 9 | ## 1.0.1 10 | 11 | ### Patch Changes 12 | 13 | - 234f2e4: Now able to proxy requests through local proxy to an https endpoint 14 | - 949545c: Initial beta release 15 | -------------------------------------------------------------------------------- /packages/interceptors-js/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2022] [API Hero, Inc.] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/interceptors-js/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./InteractiveIsomorphicRequest"; 2 | export * from "./types"; 3 | -------------------------------------------------------------------------------- /packages/interceptors-js/src/interceptors/ClientRequest/http.get.ts: -------------------------------------------------------------------------------- 1 | import { ClientRequest } from "node:http"; 2 | import { 3 | NodeClientOptions, 4 | NodeClientRequest, 5 | Protocol, 6 | } from "./NodeClientRequest"; 7 | import { 8 | ClientRequestArgs, 9 | normalizeClientRequestArgs, 10 | } from "@mswjs/interceptors/lib/interceptors/ClientRequest/utils/normalizeClientRequestArgs"; 11 | 12 | export function get(protocol: Protocol, options: NodeClientOptions) { 13 | return (...args: ClientRequestArgs): ClientRequest => { 14 | const clientRequestArgs = normalizeClientRequestArgs( 15 | `${protocol}:`, 16 | ...args 17 | ); 18 | const request = new NodeClientRequest(clientRequestArgs, options); 19 | 20 | /** 21 | * @note https://nodejs.org/api/http.html#httpgetoptions-callback 22 | * "http.get" sets the method to "GET" and calls "req.end()" automatically. 23 | */ 24 | request.end(); 25 | 26 | return request; 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /packages/interceptors-js/src/interceptors/ClientRequest/http.request.ts: -------------------------------------------------------------------------------- 1 | import { debug } from "debug"; 2 | import { ClientRequest } from "http"; 3 | import { 4 | NodeClientOptions, 5 | NodeClientRequest, 6 | Protocol, 7 | } from "./NodeClientRequest"; 8 | import { 9 | ClientRequestArgs, 10 | normalizeClientRequestArgs, 11 | } from "@mswjs/interceptors/lib/interceptors/ClientRequest/utils/normalizeClientRequestArgs"; 12 | 13 | const log = debug("http request"); 14 | 15 | export function request(protocol: Protocol, options: NodeClientOptions) { 16 | return (...args: ClientRequestArgs): ClientRequest => { 17 | log('request call (protocol "%s"):', protocol, args); 18 | 19 | const clientRequestArgs = normalizeClientRequestArgs( 20 | `${protocol}:`, 21 | ...args 22 | ); 23 | return new NodeClientRequest(clientRequestArgs, options); 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /packages/interceptors-js/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { HeadersObject } from "headers-polyfill"; 2 | import type { InteractiveIsomorphicRequest } from "./InteractiveIsomorphicRequest"; 3 | import { IsomorphicRequest, IsomorphicResponse } from "@mswjs/interceptors"; 4 | 5 | export type { IsomorphicResponse }; 6 | export { IsomorphicRequest }; 7 | 8 | export const IS_PATCHED_MODULE: unique symbol = Symbol("isPatchedModule"); 9 | 10 | export type RequestCredentials = "omit" | "include" | "same-origin"; 11 | 12 | export interface ModifiedRequest 13 | extends Omit, "headers"> { 14 | headers?: HeadersObject; 15 | body?: string; 16 | } 17 | 18 | export type HttpRequestEventMap = { 19 | connect(request: InteractiveIsomorphicRequest): Promise | void; 20 | request(request: InteractiveIsomorphicRequest): Promise | void; 21 | response( 22 | request: IsomorphicRequest, 23 | response: IsomorphicResponse 24 | ): Promise | void; 25 | }; 26 | -------------------------------------------------------------------------------- /packages/interceptors-js/src/utils/createImmediateCallback.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AnyFunction, 3 | LazyCallbackOptions, 4 | } from "@mswjs/interceptors/lib/utils/createLazyCallback"; 5 | 6 | export type ImmediateCallbackReturnType = 7 | | Parameters 8 | | []; 9 | 10 | export interface ImmediateCallback { 11 | (...args: Parameters): void; 12 | invoked(): ImmediateCallbackReturnType; 13 | } 14 | 15 | export function createImmediateCallback( 16 | options: LazyCallbackOptions = {} 17 | ): ImmediateCallback { 18 | let calledTimes = 0; 19 | let resolve: ImmediateCallbackReturnType; 20 | 21 | const fn: ImmediateCallback = function (...args) { 22 | if (options.maxCalls && calledTimes >= options.maxCalls) { 23 | options.maxCallsCallback?.(); 24 | } 25 | 26 | resolve = args; 27 | calledTimes++; 28 | }; 29 | 30 | fn.invoked = () => { 31 | return resolve; 32 | }; 33 | 34 | return fn; 35 | } 36 | -------------------------------------------------------------------------------- /packages/interceptors-js/test/fetch/fetch.intercept.browser.runtime.js: -------------------------------------------------------------------------------- 1 | import { FetchInterceptor } from "@apihero/interceptors-js/lib/interceptors/fetch"; 2 | 3 | const interceptor = new FetchInterceptor(); 4 | interceptor.on("request", async (request) => { 5 | window.dispatchEvent( 6 | new CustomEvent("resolver", { 7 | detail: { 8 | id: request.id, 9 | method: request.method, 10 | url: request.url.href, 11 | headers: request.headers.all(), 12 | credentials: request.credentials, 13 | body: await request.text(), 14 | }, 15 | }) 16 | ); 17 | }); 18 | 19 | interceptor.apply(); 20 | -------------------------------------------------------------------------------- /packages/interceptors-js/test/fetch/fetch.response.browser.runtime.js: -------------------------------------------------------------------------------- 1 | import { FetchInterceptor } from "@apihero/interceptors-js/lib/interceptors/fetch"; 2 | 3 | const interceptor = new FetchInterceptor(); 4 | 5 | interceptor.on("request", (request) => { 6 | const { serverHttpUrl, serverHttpsUrl } = window; 7 | 8 | if ( 9 | [serverHttpUrl, serverHttpsUrl].includes(request.url.href) && 10 | request.url.pathname === "/" 11 | ) { 12 | const newUrl = new URL(request.url.href); 13 | newUrl.pathname = "/get"; 14 | 15 | request.requestWith({ 16 | url: newUrl, 17 | }); 18 | } 19 | }); 20 | 21 | interceptor.apply(); 22 | 23 | window.interceptor = interceptor; 24 | -------------------------------------------------------------------------------- /packages/interceptors-js/test/vitest.browser.setup.ts: -------------------------------------------------------------------------------- 1 | import { createBrowser, CreateBrowserApi } from "page-with"; 2 | import { afterAll, beforeAll } from "vitest"; 3 | import webpackConfig from "./webpack.config"; 4 | 5 | let browser: CreateBrowserApi; 6 | 7 | beforeAll(async () => { 8 | browser = await createBrowser({ 9 | launchOptions: { 10 | args: ["--allow-insecure-localhost"], 11 | }, 12 | serverOptions: { 13 | webpackConfig, 14 | }, 15 | }); 16 | }); 17 | 18 | afterAll(async () => { 19 | await browser.cleanup(); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/interceptors-js/test/webpack.config.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import * as webpack from "webpack"; 3 | 4 | const webpackConfig: webpack.Configuration = { 5 | target: "web", 6 | plugins: [ 7 | new webpack.EnvironmentPlugin({ 8 | NODE_DEBUG: JSON.stringify(true), 9 | }), 10 | ], 11 | resolve: { 12 | alias: { 13 | "@apihero/interceptors-js": path.resolve(__dirname, ".."), 14 | }, 15 | }, 16 | }; 17 | 18 | export default webpackConfig; 19 | -------------------------------------------------------------------------------- /packages/interceptors-js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@apihero/tsconfig/node18.json", 3 | "include": ["next-env.d.ts", "src/**/*.ts"], 4 | "compilerOptions": { 5 | "experimentalDecorators": true, 6 | "emitDecoratorMetadata": true, 7 | "lib": ["esnext", "dom"], 8 | "outDir": "lib" 9 | }, 10 | "exclude": ["node_modules", "**/*.test.*"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/interceptors-js/vitest.browser.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import { defineConfig } from "vite"; 5 | import tsconfigPaths from "vite-tsconfig-paths"; 6 | 7 | export default defineConfig({ 8 | plugins: [tsconfigPaths()], 9 | test: { 10 | globals: true, 11 | testTimeout: 1000000, 12 | environment: "happy-dom", 13 | setupFiles: ["./test/vitest.expect.ts", "./test/vitest.browser.setup.ts"], 14 | // setupFiles: ["./test/setup-test-env.ts"], 15 | include: [ 16 | "./test/**/*.browser.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}", 17 | ], 18 | watchExclude: [".*\\/node_modules\\/.*", ".*\\/build\\/.*"], 19 | exclude: [ 20 | "node_modules", 21 | "dist", 22 | ".idea", 23 | ".git", 24 | ".cache", 25 | "**/*integration.test.ts", 26 | ], 27 | }, 28 | }); 29 | -------------------------------------------------------------------------------- /packages/interceptors-js/vitest.node.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import { defineConfig } from "vite"; 5 | import tsconfigPaths from "vite-tsconfig-paths"; 6 | 7 | export default defineConfig({ 8 | plugins: [tsconfigPaths()], 9 | test: { 10 | globals: true, 11 | environment: "happy-dom", 12 | setupFiles: ["./test/vitest.expect.ts"], 13 | // setupFiles: ["./test/setup-test-env.ts"], 14 | include: ["./test/**/*.node.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], 15 | watchExclude: [".*\\/node_modules\\/.*", ".*\\/build\\/.*"], 16 | exclude: [ 17 | "node_modules", 18 | "dist", 19 | ".idea", 20 | ".git", 21 | ".cache", 22 | "**/*integration.test.ts", 23 | ], 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /packages/internal-constants/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "internal-constants", 3 | "version": "1.0.0", 4 | "private": true, 5 | "main": "./src/index.ts", 6 | "types": "./src/index.ts", 7 | "devDependencies": { 8 | "typescript": "^4.9.0" 9 | } 10 | } -------------------------------------------------------------------------------- /packages/internal-constants/src/index.ts: -------------------------------------------------------------------------------- 1 | export const DESTINATION_HEADER_NAME = "x-ah-origin"; 2 | export const PROJECT_KEY_HEADER_NAME = "x-ah-pk"; 3 | export const REQUEST_ID_HEADER_NAME = "x-ah-request-id"; 4 | export const PROTOCOL_HEADER_NAME = "x-ah-proto"; 5 | export const PAYLOAD_HEADER_NAME = "x-ah-payload"; 6 | -------------------------------------------------------------------------------- /packages/internal-logs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "internal-logs", 3 | "version": "1.0.0", 4 | "private": true, 5 | "main": "./dist/index.js", 6 | "module": "./dist/index.mjs", 7 | "types": "./dist/index.d.ts", 8 | "files": [ 9 | "dist/**" 10 | ], 11 | "scripts": { 12 | "clean": "rimraf dist", 13 | "test": "vitest run", 14 | "test:dev": "vitest", 15 | "dev": "tsup --watch", 16 | "build": "tsup", 17 | "typecheck": "tsc --project ./tsconfig.json --noEmit" 18 | }, 19 | "devDependencies": { 20 | "@apihero/tsconfig": "*", 21 | "@types/node": "^18.11.9", 22 | "@apihero/eslint-config-vite": "*", 23 | "rimraf": "^3.0.2", 24 | "tsup": "^6.2.3", 25 | "typescript": "^4.8.4", 26 | "vite": "^3.2.2", 27 | "vite-tsconfig-paths": "^3.5.2", 28 | "vitest": "^0.24.5" 29 | }, 30 | "dependencies": { 31 | "zod": "^3.19.1" 32 | } 33 | } -------------------------------------------------------------------------------- /packages/internal-logs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@apihero/tsconfig/node18.json", 3 | "include": ["./src/**/*.ts", "tsup.config.ts"], 4 | "compilerOptions": { 5 | "experimentalDecorators": true, 6 | "emitDecoratorMetadata": true, 7 | "paths": {} 8 | }, 9 | "exclude": ["node_modules"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/internal-logs/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | 3 | const isProduction = process.env.NODE_ENV === "production"; 4 | 5 | export default defineConfig({ 6 | clean: true, 7 | dts: true, 8 | entry: ["src/index.ts"], 9 | format: ["cjs", "esm"], 10 | minify: isProduction, 11 | sourcemap: true, 12 | }); 13 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "config-packages/*" 3 | - "packages/*" 4 | - "apps/**" 5 | - "examples/*" 6 | --------------------------------------------------------------------------------