GithubHelp home page GithubHelp logo

encoredev / encore Goto Github PK

View Code? Open in Web Editor NEW
4.5K 42.0 216.0 10.86 MB

Encore is the Backend Development Platform purpose-built to help you create event-driven and distributed systems.

Home Page: https://encore.dev

License: Mozilla Public License 2.0

Go 74.86% TypeScript 1.76% Shell 0.27% Batchfile 0.13% Dockerfile 0.01% Rust 22.90% Handlebars 0.08%
go api backend microservices encore cloud-native golang distributed-systems backend-api backend-framework

encore's Introduction

encore icon

Encore – Backend Development Platform

Encore provides a purpose-built workflow to help you create event-driven and distributed systems — from local development to your cloud on AWS & GCP.

It consists of a Microservice Framework & Infrastructure SDK, a Local Development Environment with tools like tracing, and a Cloud Platform for automating CI/CD and cloud infrastructure provisioning.

🏁 Try Encore: Quick Start Guide

💻 See example apps: Example Apps Repo

🚀 Discover products built with Encore: Showcase

⭐ Star this repository to help spread the word.

🍿 Intro video: Watch this video for an introduction to Encore concepts & features

👋 Have questions? Join the friendly developer community on Discord.

📞 See if Encore fits your project: Book a 1:1 demo.

Introduction to Encore

Cloud services enable us to build highly scalable applications, but often lead to a poor developer experience — forcing developers to manage significant complexity during development and do a lot of repetitive manual work.

Encore is purpose-built to solve this problem and provides a complete toolset for backend development — from local development and testing, to cloud infrastructure management and DevOps.

Encore Overview

How it works

Encore's Open Source declarative Infrastructure SDK, available for Go and TypeScript (Beta), lets you define resources like services, databases, cron jobs, and Pub/Sub, as type-safe objects in your application code.

With the SDK you only define infrastructure semanticsthe things that matter to your application's behavior — not configuration for specific cloud services. Encore parses your application and builds a graph of both its logical architecture and its infrastructure requirements, it then automatically generates boilerplate and orchestrates the relevant infrastructure for each environment. This means your application code can be used to run locally, test in preview environments, and provision and deploy to cloud environments on AWS and GCP.

This completely removes the need for separate infrastructure configuration like Terraform, increases standardization in both your codebase and infrastructure, and makes your application portable across cloud providers by default.

When your application is deployed to your cloud, there are no runtime dependencies on Encore and there is no proprietary code running in your cloud.

Example: Using Pub/Sub

If you want a Pub/Sub Topic, you declare it directly in your application code and Encore will automatically provision the infrastructure and generate the boilerplate code necessary for each environment:

  • NSQ for local development
  • GCP Pub/Sub for environments on GCP
  • SNS/SQS for environments on AWS

Using the Go SDK, it looks like so:

import "encore.dev/pubsub"
 
type User struct { /* fields... */ }
 
var Signup = pubsub.NewTopic[*User]("signup", pubsub.TopicConfig{
  DeliveryGuarantee: pubsub.AtLeastOnce,
})
 
// Publish messages by calling a method
Signup.Publish(ctx, &User{...})

Using the TypeScript SDK, it looks like so:

import { Topic } "encore.dev/pubsub"

export interface SignupEvent {
    userID: string;
}

export const signups = new Topic<SignupEvent>("signups", {
    deliveryGuarantee: "at-least-once",
});

Learn more in the docs

See how to use the Infrastructure SDK in the docs:

Using Encore: An end-to-end workflow from local to cloud

Encore provides purpose-built tooling for each step in the development process, from local development and testing, to cloud DevOps. Here we'll cover the key features for each part of the process.

Local Development

Local Development

When you run your app locally using the Encore CLI, Encore parses your code and automatically sets up the necessary local infrastructure on the fly. No more messing around with Docker Compose!

You also get built-in tools for an efficient workflow when creating distributed systems and event-driven applications:

  • Local environment matches cloud: Encore automatically handles the semantics of service communication and interfacing with different types of infrastructure services, so that the local environment is a 1:1 representation of your cloud environment.
  • Cross-service type-safety: When building microservices applications with Encore, you get type-safety and auto-complete in your IDE when making cross-service API calls.
  • Type-aware infrastructure: With Encore, infrastructure like Pub/Sub queues are type-aware objects in your program. This enables full end-to-end type-safety when building event-driven applications.
  • Secrets management: Built-in secrets management for all environments.
  • Tracing: The local development dashboard provides local tracing to help understand application behavior and find bugs.
  • Automatic API docs & clients: Encore generates API docs and API clients in Go, TypeScript, JavaScript, and OpenAPI specification.

Here's a video showing the local development dashboard:

localdashvideo.2.mp4

Testing

testing

Encore comes with several built-in tools to help with testing:

  • Built-in service/API mocking: Encore provides built-in support for mocking API calls, and interfaces for automatically generating mock objects for your services.
  • Local test infra: When running tests locally, Encore automatically provides dedicated test infrastructure to isolate individual tests.
  • Local test tracing: The local dev dashboard provides distributed tracing for tests, providing great visibility into what's happening and making it easier to understand why a test failed.
  • Preview Environments: Encore automatically provisions a Preview Environment for each Pull Request, an effective tool when doing end-to-end testing.

DevOps

DevOps

With Encore you can focus your engineering effort on your product and avoid investing time in building a developer platform.

A core feature Encore provides is automatic infrastructure provisioning in your cloud. Because your application code is the source of truth for the application's infrastructure requirements, instead of writing Terraform, YAML, or clicking in cloud consoles, you connect your cloud account and deploy. This approach also lets you swap out your infrastructure over time, without needing to make code changes or manually update infrastructure config files.

When you deploy, Encore automatically provisions infrastructure using battle-tested cloud services on AWS and GCP, such as:

  • Compute: GCP Cloud Run, AWS Fargate, Kubernetes (GKE and EKS)
  • Databases: GCP Cloud SQL, AWS RDS
  • Pub/Sub: GCP Pub/Sub, AWS SQS/SNS
  • Caches: GCP Memorystore, Amazon ElastiCache
  • Secrets: GCP Secret Manager, AWS Secrets Manager
  • Etc.

Encore also provides built-in DevOps tools to help automate >90% of the day-to-day DevOps work:

  • Automatic least-privilege IAM: Encore parses your application code and sets up least-privilege IAM to match the requirements of the application.
  • Infra tracking & approvals workflow: Encore keeps track of all the infrastructure it provisions and provides an approval workflow as part of the deployment process, so Admins can verify and approve all infra changes.
  • Cloud config 2-way sync: Encore provides a simple UI to make configuration changes, and also supports syncing changes you make in your cloud console in AWS/GCP.
  • Cost analytics: A simple overview to monitor costs for all infrastructure provisioned by Encore in your cloud.
  • Logging & Metrics: Encore automatically provides logging, metrics, and integrates with 3rd party tools like Datadog and Grafana.
  • Service Catalog: Encore automatically generates a service catalog with complete API documentation.
  • Architecture diagrams: To help with onboarding and collaboration, Encore generates architecture diagrams for your application, including infrastructure dependencies.
  • Extensible through Encore's Terraform Provider: Extend your system with any infrastructure services you need, integration is simple because all infrastructure is provisioned in your cloud. Encore also has a Terraform Provider to simplify this process.

Here's a video showing the Cloud Platform Dashboard:

envs.mp4

Why use Encore?

  • Faster Development: Encore streamlines the development process with its infrastructure SDK, clear abstractions, and built-in development tools, enabling you to build and deploy applications more quickly.
  • Reduced Costs: Encore's automatic infrastructure management minimizes wasteful cloud expenses and reduces DevOps workload, allowing you to work more efficiently.
  • Scalability & Performance: Encore simplifies building large-scale microservices applications that can handle growing user bases and demands, without the normal boilerplate and complexity.
  • Control & Standardization: Built-in tools like automated architecture diagrams, infrastructure tracking and approval workflows, make it easy for teams and leaders to get an overview of the entire application.
  • Security & Compliance: Encore helps ensure your application is secure and compliant by enforcing security standards and provisioning infrastructure according to best practices for each cloud provider.

Common use cases

Encore is designed to give teams a productive and less complex experience when solving most backend use cases. Many teams use Encore to build things like:

  • High-performance B2B Platforms
  • Fintech & Consumer apps
  • Global E-commerce marketplaces
  • Microservices backends for SaaS applications and mobile apps
  • And much more...

Getting started

Open Source

Encore's Infrastructure SDK, parser, compiler, and CLI are all Open Source — this includes all code needed for local development and everything that runs in your cloud. A free Encore account is needed to use features like distributed tracing, secrets management, and deploying to cloud environments, as this functionality is orchestrated by Encore's Cloud Platform.

The Open Source CLI also provides a mechanism to generate a standalone Docker image for your application, so you can deploy it without using the Cloud Platform. Learn more in the docs.

Join the most pioneering developer community

Developers building with Encore are forward-thinkers who want to focus on creative programming and building great software to solve meaningful problems. It's a friendly place, great for exchanging ideas and learning new things! Join the conversation on Discord.

We rely on your contributions and feedback to improve Encore for everyone who is using it. Here's how you can contribute:

  • Star and watch this repository to help spread the word and stay up to date.
  • Meet fellow Encore developers and chat on Discord.
  • Follow Encore on Twitter.
  • Share feedback or ask questions via email.
  • Leave feedback on the Public Roadmap.
  • Send a pull request here on GitHub with your contribution.

Videos

Visuals

Code example (Go)

Encore.example.1.mp4

Local Development Dashboard

indexvideo_5.mp4

Generated Architecture Diagrams & Service Catalog

index_1.mp4

Auto-Provisioning Infrastructure & Multi-cloud Deployments

envs.mp4

Distributed Tracing & Metrics

indexxxxx.mp4

Frequently Asked Questions (FAQ)

Who's behind Encore?

Encore was founded by long-time backend engineers from Spotify, Google, and Monzo with over 50 years of collective experience. We’ve lived through the challenges of building complex distributed systems with thousands of services, and scaling to hundreds of millions of users.

Encore grew out of these experiences and is a solution to the frustrations that came with them: unnecessary crippling complexity and constant repetition of undifferentiated work that suffocates the developer’s creativity. With Encore, we want to set developers free to achieve their creative potential.

Who is Encore for?

For individual developers building for the cloud, Encore provides a radically improved experience. With Encore you’re able to stay in the flow state and experience the joy and creativity of building.

For startup teams who need to build a scalable backend to support the growth of their product, Encore lets them get up and running in the cloud within minutes. It lets them focus on solving the needs of their users, instead of spending most of their time re-solving the everyday challenges of building distributed systems in the cloud.

For individual teams in large organizations that want to focus on innovating and building new features, Encore lets them stop spending time on operations and onboarding new team members. Using Encore for new feature development is easy, just spin up a new backend service in a few minutes.

How is Encore different?

Encore is the only tool that understands what you’re building. Encore uses static analysis to deeply understand the application you’re building. This enables a unique developer experience that helps you stay in the flow as you’re building. For instance, you don't need to bother with configuring and managing infrastructure, setting up environments and keeping them in sync, or writing documentation and drafting architecture diagrams. Encore does all of this automatically out of the box.

Unlike many tools that aim to only make cloud deployment easier, Encore is not a cloud hosting provider. With Encore, you can use your cloud account with AWS and GCP. This means you’re in control of your data and can maintain your trust relationship with your cloud provider. You can also use Encore's development cloud for free, with pretty generous "fair use" limits.

Why is the Encore SDK integrated with a cloud platform?

We've found that to meaningfully improve the developer experience, you have to operate across the full stack. Unless you understand how an application is deployed, there are a large number of things in the development process that you can't simplify. That's why so many other developer tools have such a limited impact. With Encore's more integrated approach, we're able to unlock a radically better experience for developers.

What if I want to migrate away from Encore?

Encore has been designed to let you go outside of the SDK when you want to, and easily drop down in abstraction level when you need to, so you never need to run into any dead-ends.

Should you want to migrate away, it's straightforward and does not require a big rewrite. 95% of your code is regular Go or TypeScript and the code specific to Encore is limited to using the Infrastructure SDK.

Encore has built-in support for ejecting your application as a way of removing the connection to the Encore Platform. Ejecting your app produces a standalone Docker image that can be deployed anywhere you'd like, and can help facilitate the migration away according to the process above.

Migrating away is low risk since Encore deploys to your cloud account from the start, which means there's never any data to migrate.

Open Source also plays a role. Encore's code generation, compiler, and parser are all open source and can be used however you want. So if you run into something unforeseen down the line, you have free access to the tools you might need.

And since Encore is designed for building distributed systems, it's straightforward to use it in combination with other backends that aren't built with Encore. So if you come across a use case where Encore for some reason doesn't fit, you won't need to tear everything up and start from scratch. You can just build that specific part without Encore.

We believe that adopting Encore is a low-risk decision, given it needs no initial investment in foundational work. The ambition is to simply add a lot of value to your everyday development process, from day one.

Contributing to Encore and building from source

See CONTRIBUTING.md.

encore's People

Contributors

alexandear avatar anish749 avatar delta456 avatar dependabot[bot] avatar domblack avatar eandre avatar ekerfelt avatar flexicon avatar frzam avatar horiajurcut avatar klemmster avatar magnuswahlstrand avatar marcuskohlberg avatar matthewjamesboyle avatar maxdanielsson avatar melkstam avatar michizhou avatar miles170 avatar minivera avatar phakornkiong avatar rewop avatar sam4llis avatar sammous avatar sarojwasti avatar simon-johansson avatar steverusso avatar testwill avatar tomassar avatar valeriavg avatar willyham avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

encore's Issues

Encore failing to deploy infrastrustrure

Hello,

Issue: I've created a basic application and I'm running into the below error message when deploying to prod:

remote:  05:40 |  infra | Failed to configure infrastructure: cloud.Encore.Configure: failed to run op *k8s.ingressCreator: clusterroles.rbac.authorization.k8s.io "external-ingress" is forbidden: user "system:serviceaccount:default:encore-deploy" (groups=["system:serviceaccounts" "system:serviceaccounts:default" "system:authenticated"]) is attempting to grant RBAC permissions not currently held:
remote: {APIGroups:["extensions"], Resources:["ingressclasses"], Verbs:["get" "list" "watch"]}
remote: {APIGroups:["networking.k8s.io"], Resources:["ingressclasses"], Verbs:["get" "list" "watch"]}

Additionally I created a dev branch and attempted to deploy the same code as an attempt to replicate and ran into the same issue.

Full prod deploy log: https://gist.github.com/phippsy22/709da31bf3d53c54a4d229ed337ddade
Full dev deploy log: https://gist.github.com/phippsy22/ba3b8ffb696c69c454805667691e0a8f

Note

  • I am a noobie with encore so I am not sure if I have missed anything. However I was not able to find any information on defining infrastructure.

Version:

encore version v0.14.5

Deployment for the hello world project failing at the Build stage

When I try to deploy the template app, I get the error: remote: __encore_main/main.go:97:3: unknown field 'MsgHandlers' in struct literal of type config.Service

Here's what I did:

  1. encore app create
  2. Choose "Hello world (Encore Introduction)"
  3. cd build-fail-repro && encore run
  4. encore test ./...
  5. git push encore
  6. It pushes, but nothing happens. I notice it had defaulted to the master branch
  7. Rename my branch and push with git branch -m main && git push -u encore main
  8. Still not deploying
  9. git push encore again for sanity, but everything is already up to date
  10. From the UI, add a production environment called prod to trigger deployments to encore when changes are pushed to main
  11. Try to deploy manually by clicking the button in the UI. When I try to deploy main or refs/heads/main it tells me the ref doesn't exist
  12. Change the code + tests to say Goodbye instead of Hello (just so I have something to commit)
  13. Commit and git push to main. That triggered the deployment in my terminal. Here's the stack trace:
➜  build-fail-repro git:(main) git push
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 12 threads
Compressing objects:  20% (1/5Compressing objects:  40% (2/5Compressing objects:  60% (3/5Compressing objects:  80% (4/5Compressing objects: 100% (5/5Compressing objects: 100% (5/5), done.
Writing objects: 100% (5/5), 430 bytes | 430.00 KiB/s, done.
Total 5 (delta 3), reused 0 (delta 0), pack-reused 0
remote: main: triggered deploy https://app.encore.dev/build-fail-repro-you2/deploys/ktqv5shgai
remote: streaming logs for deploy ktqv5shgai... press ctrl-c to close (will not stop deploy).
remote:  00:00 |   base | Checking out commit a4a6fc25a9d7a36c5c90545f71f54cbc02895c75
remote:  00:00 |  parse | Parsing metadata
remote:  00:00 |  parse | Successfully parsed Encore metadata
remote:  00:00 |  build | Compiling binary...
remote:  00:00 |   test | Running tests...
remote:  00:00 |  build | Compilation failed: # encore.app/__encore_main
remote: __encore_main/main.go:97:3: unknown field 'MsgHandlers' in struct literal of type config.Service
remote:  00:05 |   test | go: downloading github.com/json-iterator/go v1.1.10
remote:  00:05 |   test | go: downloading github.com/felixge/httpsnoop v1.0.1
remote:  00:05 |   test | go: downloading github.com/hashicorp/yamux v0.0.0-20200609203250-aecfd211c9ce
remote:  00:05 |   test | go: downloading github.com/julienschmidt/httprouter v1.3.0
remote:  00:05 |   test | go: downloading github.com/rs/zerolog v1.20.0
remote:  00:05 |   test | go: downloading github.com/jackc/pgx/v4 v4.10.1
remote:  00:05 |   test | go: downloading github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421
remote:  00:05 |   test | go: downloading github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742
remote:  00:05 |   test | go: downloading github.com/jackc/pgconn v1.8.0
remote:  00:05 |   test | go: downloading github.com/jackc/pgio v1.0.0
remote:  00:05 |   test | go: downloading github.com/jackc/pgproto3/v2 v2.0.6
remote:  00:05 |   test | go: downloading github.com/jackc/pgproto3 v1.1.0
remote:  00:05 |   test | go: downloading github.com/jackc/pgtype v1.6.2
remote:  00:05 |   test | go: downloading golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
remote:  00:05 |   test | go: downloading github.com/jackc/puddle v1.1.3
remote:  00:05 |   test | go: downloading github.com/jackc/chunkreader/v2 v2.0.1
remote:  00:05 |   test | go: downloading github.com/jackc/chunkreader v1.0.0
remote:  00:05 |   test | go: downloading github.com/jackc/pgpassfile v1.0.0
remote:  00:05 |   test | go: downloading github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b
remote:  00:05 |   test | go: downloading golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
remote:  00:05 |   test | go: downloading golang.org/x/text v0.3.3
remote:  01:07 |   test | === RUN   TestWorld
remote:  01:07 |   test | 1:23PM INF starting request endpoint=World service=hello
remote:  01:07 |   test | 1:23PM INF request completed code=ok duration=0.143968 endpoint=World service=hello
remote:  01:07 |   test | --- PASS: TestWorld (0.00s)
remote:  01:07 |   test | PASS
remote:  01:07 |   test | ok    encore.app/hello        0.005s
remote:  01:07 |   test | Tests completed: 1 pass, 0 fail
remote:  01:07 | build failed, aborting deploy.
To encore://build-fail-repro-you2
   c09daac..a4a6fc2  main -> main

Support for gRPC APIs

Hello there, awesome framework ! I had an idea to do something like this in the back of my head for moments but never actually committed to doing it.

Have you ever thought of bringing gRPC generation for user apis too ? The code generation would work similarly to what the JSON thingy is going. But then it'd generate a .proto file from go + some strapping code generation around that and bang !

(Sorry if you have that already, but I couldn't find it in docs/code).
Cheers !

Instructions for deploying to AWS are not clear on required permissions

After connecting the AWS account through the docs, and verifying, Encore is having issues deploying a new setup for me.

00:00:03 Empty or non-existent state file.
00:00:03
00:00:03 Refresh will do nothing. Refresh does not error or return an erroneous
00:00:03 exit status because many automation scripts use refresh, plan, then apply
00:00:03 and may not have a state file yet for the first run.
00:00:03
00:00:04 data.aws_iam_policy_document.encore-k8s-elb-policy: Refreshing state...
00:00:04 data.aws_iam_policy_document.encore-k8s-ebs-policy: Refreshing state...
00:00:04 data.aws_availability_zones.available: Refreshing state...
00:00:04 data.aws_iam_policy_document.encore-k8s-autoscale-policy: Refreshing state...
00:00:04
00:00:04 Error: Error fetching Availability Zones: UnauthorizedOperation: You are not authorized to perform this operation.
00:00:04 status code: 403, request id: XXXX
00:00:04
00:00:04
00:00:04
00:00:04 Warning: Empty or non-existent state
00:00:04
00:00:04 There are currently no resources tracked in the state, so there is nothing to
00:00:04 refresh.
00:00:04
00:00:04 Failed to refresh infrastructure changes.

Add possibility add identity as parameter

Hi,
I'm getting this error that is caused that I use useconfigonly = true settings in git. Can you create app without create repo or have option to pass user credentials as parameters?

$ encore app create my-app
? Select app template: Hello World

Downloaded template hello-world.
error: create initial commit repository: Author identity unknown

*** Please tell me who you are.

Run

  git config --global user.email "[email protected]"
  git config --global user.name "Your Name"

to set your account's default identity.
Omit --global to set the identity only in this repository.

fatal: no email was given and auto-detection is disabled
 (exit status 128)

Improved feedback for db migrations.

When you run encore run if you have new db migrations, the daemon sort of hangs for a couple of seconds (whilst the migrations run) but you get no feedback of this. I think log lines such as
running migration 3_adding_new_thing.up.sql would go a long way.

Secondly, if your migration does fail you get surfaced the error directly from go-migrate. I think adding a trouble shooting doc to the site of how to get out of this dirty state would be a welcome addition.

If you think the above issues are good ideas, I'm happy to work on them.

proposal: structured error responses

As discussed in #17 and elsewhere, Encore needs to provide the ability to control the error codes reported by the API as well as provide more structured data to callers.

In summary, Encore would respond to external API call errors with an error in the form:

{
   "code": "not_found",
   "message": "article not found",
   "details": {
        "article_id": 5
   }
}

In terms of API I propose the following design:

package errs // import "encore.dev/beta/errs"

// An Error is an error that provides structured information
// about the error. It includes an error code, a message, and
// optionally additional structured details about the error.
//
// Internally it captures an underlying error for printing
// and for use with errors.Is/As and call stack information.
//
// To provide accurate stack information, users are expected
// to convert non-Error errors into *Error as close to the
// root cause as possible. This is made simple with Wrap.
type Error struct {
	Code    ErrCode    `json:"code"`
	Message string     `json:"message"`
	Details ErrDetails `json:"details"`
	Meta    Metadata   `json:"-"` // not exposed to external clients
}

// Metadata represents structured key-value pairs, for attaching arbitrary
// metadata to errors. The metadata is propagated between internal services
// but is not exposed to external clients.
type Metadata map[string]interface{}

// New creates a new Error without wrapping another underlying error.
// If code == OK it returns nil.
func New(code ErrCode, msg string, metaPairs ...interface{}) error { /* ... */ }

// NewWithDetails is like New but also sets Details.
func NewWithDetails(code ErrCode, det ErrDetails, msg string, metaPairs ...interface{}) error { /* ... */ }

// Wrap wraps the err, adding additional error information.
// If err is nil it returns nil.
//
// If err is already an *Error its code, message, and details
// are copied over to the new error.
func Wrap(err error, msg string, metaPairs ...interface{}) error { /* ... */ }

// WrapCode is like Wrap but also sets the error code.
// If code is OK it reports nil.
func WrapCode(err error, code ErrCode, msg string, metaPairs ...interface{}) error { /* ... */ }

// Convert converts an error to an *Error.
// If the error is already an *Error it returns it unmodified.
// If err is nil it returns nil.
func Convert(err error) error { /* ... */ }

// Code reports the error code from an error.
// If err is nil it reports OK.
// Otherwise if err is not an *Error it reports Unknown.
func Code(err error) ErrCode { /* ... */ }

// Meta reports the metadata included in the error.
// If err is nil or the error lacks metadata it reports nil.
func Meta(err error) Metadata { /* ... */ }

// Details reports the error details included in the error.
// If err is nil or the error lacks details it reports nil.
func Details(err error) ErrDetails { /* ... */ }

// HTTPError writes structured error information to w using JSON encoding.
// The status code is computed with HTTPStatus.
//
// If err is nil it writes:
// 		{"code": "ok", "message": "", "details": null}
func HTTPError(w http.ResponseWriter, err error) { /* ... */ }

// HTTPStatus reports a suitable HTTP status code for an error, based on its code.
// If err is nil it reports 200. If it's not an *Error it reports 500.
func HTTPStatus(err error) int { /* ... */ }

// ErrDetails is a marker interface for telling Encore
// the type is used for reporting error details.
//
// We require a marker method (as opposed to using interface{})
// to facilitate static analysis and to ensure the type
// can be properly serialized across the network.
type ErrDetails interface {
	ErrDetails() // marker method; it need not do anything
}

// ErrCode is an RPC error code.
type ErrCode int

// These error codes are the same as the ones provided by gRPC for compatibility.
const (
	// OK indicates the operation was successful.
	OK ErrCode = 0

	// Canceled indicates the operation was canceled (typically by the caller).
	//
	// Encore will generate this error code when cancellation is requested.
	Canceled ErrCode = 1

	// Unknown error. An example of where this error may be returned is
	// if a Status value received from another address space belongs to
	// an error-space that is not known in this address space. Also
	// errors raised by APIs that do not return enough error information
	// may be converted to this error.
	//
	// Encore will generate this error code in the above two mentioned cases.
	Unknown ErrCode = 2

	// InvalidArgument indicates client specified an invalid argument.
	// Note that this differs from FailedPrecondition. It indicates arguments
	// that are problematic regardless of the state of the system
	// (e.g., a malformed file name).
	//
	// Encore will generate this error code if the request data cannot be parsed.
	InvalidArgument ErrCode = 3

	// DeadlineExceeded means operation expired before completion.
	// For operations that change the state of the system, this error may be
	// returned even if the operation has completed successfully. For
	// example, a successful response from a server could have been delayed
	// long enough for the deadline to expire.
	//
	// Encore will generate this error code when the deadline is exceeded.
	DeadlineExceeded ErrCode = 4

	// NotFound means some requested entity (e.g., file or directory) was
	// not found.
	//
	// Encore will not generate this error code.
	NotFound ErrCode = 5

	// AlreadyExists means an attempt to create an entity failed because one
	// already exists.
	//
	// Encore will not generate this error code.
	AlreadyExists ErrCode = 6

	// PermissionDenied indicates the caller does not have permission to
	// execute the specified operation. It must not be used for rejections
	// caused by exhausting some resource (use ResourceExhausted
	// instead for those errors). It must not be
	// used if the caller cannot be identified (use Unauthenticated
	// instead for those errors).
	//
	// Encore will not generate this error code.
	PermissionDenied ErrCode = 7

	// ResourceExhausted indicates some resource has been exhausted, perhaps
	// a per-user quota, or perhaps the entire file system is out of space.
	//
	// Encore will generate this error code in out-of-memory and server overload
	// situations, or when a message is larger than the configured maximum size.
	ResourceExhausted ErrCode = 8

	// FailedPrecondition indicates operation was rejected because the
	// system is not in a state required for the operation's execution.
	// For example, directory to be deleted may be non-empty, an rmdir
	// operation is applied to a non-directory, etc.
	//
	// A litmus test that may help a service implementor in deciding
	// between FailedPrecondition, Aborted, and Unavailable:
	//  (a) Use Unavailable if the client can retry just the failing call.
	//  (b) Use Aborted if the client should retry at a higher-level
	//      (e.g., restarting a read-modify-write sequence).
	//  (c) Use FailedPrecondition if the client should not retry until
	//      the system state has been explicitly fixed. E.g., if an "rmdir"
	//      fails because the directory is non-empty, FailedPrecondition
	//      should be returned since the client should not retry unless
	//      they have first fixed up the directory by deleting files from it.
	//  (d) Use FailedPrecondition if the client performs conditional
	//      Get/Update/Delete on a resource and the resource on the
	//      server does not match the condition. E.g., conflicting
	//      read-modify-write on the same resource.
	//
	// Encore will not generate this error code.
	FailedPrecondition ErrCode = 9

	// Aborted indicates the operation was aborted, typically due to a
	// concurrency issue like sequencer check failures, transaction aborts,
	// etc.
	//
	// See litmus test above for deciding between FailedPrecondition,
	// Aborted, and Unavailable.
	Aborted ErrCode = 10

	// OutOfRange means operation was attempted past the valid range.
	// E.g., seeking or reading past end of file.
	//
	// Unlike InvalidArgument, this error indicates a problem that may
	// be fixed if the system state changes. For example, a 32-bit file
	// system will generate InvalidArgument if asked to read at an
	// offset that is not in the range [0,2^32-1], but it will generate
	// OutOfRange if asked to read from an offset past the current
	// file size.
	//
	// There is a fair bit of overlap between FailedPrecondition and
	// OutOfRange. We recommend using OutOfRange (the more specific
	// error) when it applies so that callers who are iterating through
	// a space can easily look for an OutOfRange error to detect when
	// they are done.
	//
	// Encore will not generate this error code.
	OutOfRange ErrCode = 11

	// Unimplemented indicates operation is not implemented or not
	// supported/enabled in this service.
	//
	// Encore will generate this error code when an endpoint does not exist.
	Unimplemented ErrCode = 12

	// Internal errors. Means some invariants expected by underlying
	// system has been broken. If you see one of these errors,
	// something is very broken.
	//
	// Encore will generate this error code in several internal error conditions.
	Internal ErrCode = 13

	// Unavailable indicates the service is currently unavailable.
	// This is a most likely a transient condition and may be corrected
	// by retrying with a backoff. Note that it is not always safe to retry
	// non-idempotent operations.
	//
	// See litmus test above for deciding between FailedPrecondition,
	// Aborted, and Unavailable.
	//
	// Encore will generate this error code in aubrupt shutdown of a server process
	// or network connection.
	Unavailable ErrCode = 14

	// DataLoss indicates unrecoverable data loss or corruption.
	//
	// Encore will not generate this error code.
	DataLoss ErrCode = 15

	// Unauthenticated indicates the request does not have valid
	// authentication credentials for the operation.
	//
	// Encore will generate this error code when the authentication metadata
	// is invalid or missing, and expects auth handlers to return errors with
	// this code when the auth token is not valid.
	Unauthenticated ErrCode = 16
)

cli/daemon/dash/dashapp: API Explorer sometimes infers the wrong HTTP method

The API Explorer doesn't correctly update which HTTP method to use when changing RPC in the dropdown; it sticks with the previous endpoint's HTTP method. This causes the API Explorer to make a request to the wrong method (usually to an endpoint that doesn't exist). This also affects the "copy as curl" functionality for the same reason.

Handle autocomplete "/" character when connecting to db

Very minor annoyance; when using autocomplete to connect to a database (i.e. >>encore db shell service), a bash terminal will autocomplete to the directory ('service/'). This yields "database not found" which is confusing. Perhaps this is intended behavior, but it would be infinitely more convenient should the 'db shell' command just handle and ignore the extra slash character.

Error response

Hello,

thanks for this project, it looks very promising. Will be there a possibility to return an error as a JSON? I've tried to write a simple API, but in the case of error, only a text message "Internal server error" is returned. I'd like to specify a structure that holds more information.

Proposal: Support setting auth information

Summary

We propose a simple method for manually providing auth information to API calls made within Encore. This is first and foremost to facilitate better testing.

Motivation

Encore automatically propagates request and authentication information between requests. However, there is no built-in way of specifying the auth data by hand when making an API call. This can cause problems when writing tests where you want to test certain auth inputs.

For simple use cases it's easy to work around by separating the logic out into a separate function which takes the auth data as input, but for more complex tests that involve multiple API calls this doesn't work because the downstream API calls will not receive the auth data. We need a way to propagate the auth data for such use cases.

Design

We propose to extend the encore.dev/beta/auth package to provide a mechanism for overriding the auth information using the ctx argument to the API call.

package auth

// WithContext returns a new context that sets the auth information for outgoing API calls.
// It does not affect the auth information for the current request.
//
// Passing in an empty string as the uid results in unsetting the auth information,
// causing future API calls to behave as if there was no authenticated user.
//
// If the application's auth handler returns custom auth data, two additional
// requirements exist. First, the data parameter passed to WithContext must be of
// the same type as the auth handler returns. Second, if the uid argument is not
// the empty string then data may not be nil. If these requirements are not met,
// API calls made with these options will not be made and will immediately return
// a client-side error.
func WithContext(ctx context.Context, uid UID, data interface{}) context.Context { /* ... */ }

With proposed usage:

import "encore.dev/beta/auth"

type MyAuthData struct { /* ... */ }

//encore:authhandler
func MyAuthHandler(ctx context.Context, token string) (auth.UID, *MyAuthData, error) {
	return "some-user-id", &MyAuthData{}, nil
}

//encore:api public
func MyEndpoint(ctx context.Context) error {
	ctx = auth.WithContext(ctx, "my-user-id", &MyAuthData{})
	err := SomeOtherEndpoint(ctx)
	return err
}

//encore:api auth
func SomeOtherEndpoint(ctx context.Context) error {
   	uid, _ := auth.UserID()
	println(string(uid)) // prints "my-user-id"
	return nil
}

`encore run` output control codes shown in Windows

After I start encore run, the formatting control codes are shown in Windows:

←[90m8:35PM←[0m ←[32mINF←[0m registered endpoint ←[36mendpoint=←[0mWorld ←[36mpath=←[0m/hello.World ←[36mservice=←[0mhello

TypeScript Client improvements

Client generation functionality is awesome, but I think there are some things that would make it better:

  1. Add the ability to change token without re-creating a client instance. A simple solution would be to expose setter/getter for the said token instead of providing token as a constructor argument
  2. Check that token is truthy before adding auth header. I've accidentally set it to null and requests to not-auth paths started to fail with 401
  3. Return error response as parsed json instead of a stringified error. One solution could be to override the fetch implementation altogether, e.g. by providing it as one of the constructor options

Let me know what makes sense and I can implement it.

Dashboard API Docs sidebar not scrolling as expected

The sidebar for the API Docs in the dashapp has position: sticky. This works well for when the sidebar content fits onto the screen, but when it doesn't it doesn't scroll independently from the main content (like it does for the Encore docs or the API Docs on app.encore.dev). So for when the main content is large (> 50000 px for us at the moment), you have to scroll all the way down to be able to see what was out of the screen initally on the sidebar.

Minor issues with Hello World tutorial example

When I did git push encore on the hello world example I got the output remote:

Env Dashboard: https://app.encore.dev/test2-ave2/env/prod

but that link gives a “500 Internal Server Error response”. The code is pushed and works though.

A minute thing is that the tutorial documentation says that the development server is starting on port 4000, but it is 4060 (or 4061 if 4060 is occupied).

Api Client: Golang omit-always tags are included in the API Client

Say I have a struct in encore that is used in some endpoint and looks like

type Retailer struct {
	Id          uuid.UUID `json:"id"`
	Name        string    `json:"name"`
	Token       uuid.UUID `json:"-"`
}

This will in the generated TypeScript client be generated to the following invalid TypeScript code

export interface Retailer {
    id: string
    name: string
    -: string
}

My expectation is that the Token field should be omitted in the generated code.

Foreign keys between services is not possible

I have two services, one called identity where I store my user information, and one called content where I store user generated content. In both services, I have a few migrations that create the tables and indexes in my database, for example:

In the identity service:

CREATE TABLE "users" (
   id BIGSERIAL PRIMARY KEY,
   ...
);

And then in the content service:

CREATE TABLE "documents" (
    id BIGSERIAL PRIMARY KEY,
    user_id BIGSERIAL NOT NULL,
    ...
);

In the documents table, I'd want to add a constraint to set my foreign key do to things like cascade deletes, something like: CONSTRAINT fk_user FOREIGN KEY(user_id) REFERENCES "identity"."users"(id) ON DELETE CASCADE.

That would be doable even with micro-services by having all services use the same DB, but isolate the data in different schemas. With encore however, it seems the services instead use different databases on the same server, which makes these kind of indexes or constraints impossible. Looking at the code, we can see the deamon setting this up as different database systems.

I found it a bit confusing when trying to connect to my databases with a database client or my IDE (Since I needed to create one connection per service), but it's not a deal breaker. Not being able to create foreign keys is a bit more annoying though. This limitation also means that joins are not possible either.

Is there any reason why this decision was made? (Not asking as a trick question, I am genuinely curious) Would having each service run on isolated schemas on the same database rather than different schemas be something you could envision for encore? I'm happy to make a PR if you feel this could be changed.

[DOCS/CLI] Generating client fails due to wrong information

Tried to generate a client based off your Trello Clone example (unedited, kept as is) and it kept erroring out. I read the docs and even the cli help command. Docs and CLI say to use "ts" but when actually looking at the source code, "typescript" is the correct value instead of "ts"

Encore daemon doesn't detect restarted database container

The Encore daemon caches information about the running database container. This works fine most of the time but causes problems when the Docker container (or Docker itself) is restarted outside of Encore's control, leading to Encore trying to connect to the wrong (ephemeral) container port.

It leads to errors like:

error: could not connect to the database for service foo/: create db mapping: failed to connect to admin database: failed to connect to `host=0.0.0.0 user=encore database=postgres`: dial error (dial tcp 0.0.0.0:49153: connect: connection refused)

A workaround is to restart the encore daemon (by running encore daemon) to clear the cache, but the daemon should detect this by itself instead.

Windows button does not work on encore.dev

image
I am not sure if this is the right place to report a bug on the encore.dev website. There is a problem with the windows installation button. It works only if we click on the Linux button first. Tested with the latest chrome and firefox browsers.

`ExecTx` and `Exec` return type mismatch

The following two functions gives no linting errors, but it cannot compile using encore.

func DummyDbQuery1(ctx context.Context) (int64, error) {
	res, err := sqldb.Exec(ctx, `INSERT INTO foo (bar) VALUES (1)`)
	if err != nil {
		return 0, err
	}

	rowsAffected, err := res.RowsAffected()
	return rowsAffected, err
}

func DummyDbQuery2(ctx context.Context) (int64, error) {
	res, err := sqldb.Exec(ctx, `INSERT INTO foo (bar) VALUES (1)`)
	if err != nil {
		return 0, err
	}

	lastInsertedId, err := res.LastInsertId()
	return lastInsertedId, err
}

The following errors are instead returned when I try to run or check the program:

customization/repository/customization.go:66:20: assignment mismatch: 2 variables but res.RowsAffected returns 1 values
customization/repository/customization.go:76:28: res.LastInsertId undefined (type sqldb.ExecResult has no field or method LastInsertId)

The error seems to be the difference between

func ExecTx(svc string, tx *Tx, ctx context.Context, query string, args ...interface{}) (ExecResult, error) {
and https://github.com/encoredev/encore.dev/blob/0069c0de93b0c50cc8b9c33ceb61e08000f07f54/storage/sqldb/sqldb.go#L65. (ExecResult vs sql.Result)

Environment

$ go version
go version go1.16.6 darwin/amd64
$ encore version
encore version v0.16.2

Handling bodies for raw endpoints in the dashboard and TypeScript client

This issue covers the response and request bodies of raw endpoint in the dashboard and in the TypeScript client.

Dashboard

Currently, the even though Raw Endpoints may have request and response bodies, there are no way of editing the request body or viewing the response body. I understand that encore can't know if there will be a body and the format of it, but we should at least be able to edit it as if it were text. The same goes for viewing the response, all non-raw endpoints show their response, and the raw endpoints should probably show their reponse as well, probably just in plain-text.

TypeScript Client

A raw endpoint is currently generated into a function that looks like the following.

public World(): Promise<void> {
   return this.baseClient.doVoid("POST", `/hello.World`)
}

However, this does not allow for neither a response nor request body. Therefore, I think it would be nice if the client would in some way handle bodies. Example solutions are:

  1. Make the function take in a ReadableStream (The type of the fetch request body) and return the Request object.
  2. Make the function take a ReadableStream and return it as a string. If a user wants it in JSON, they can parse it.
  3. Assume the request and response body to still be JSON and use generics to specify the request and response schema. This would still allow for strings to be sent as they are JSON-strinfigy-able.

Error building cli/encore from source in windows 10.

  1. When I am building encore cli in windows, getting compilation error ..\..\daemon\dash\server.go:25:12: pattern dashapp/dist/*: no matching files found

encore-error

So to compile this, I have removed this line

//go:embed dashapp/dist/*

  1. After it compiles, then when I run ./encore.exe -h it panics.

encore-panic

This seems issue in wireguard/windows dependency. So after I update wireguard from v0.3.1 to v0.4, it works. But I feel like this can have impact somewhere else as well.
encore-mod

Please let me know what will be the permanent solution for both the issues, and if upgrading wireguard is only solution then I will pull request for it.

Thanks.

Document how to handle dirty database migrations

When a database migration doesn't apply cleanly, golang-migrate (the library we use under the hood to manage database migrations) marks the migration state as "dirty" and refuses to continue. This can be fixed by editing the database table (fixing the schema problem and then running UPDATE schema_migrations SET dirty = false), but that's not documented anywhere. We should add this to SQL Database documentation.

cli: json tags are added to generated documentation

When a request/response struct has a json tag like json:"foo,omitempty", the omitempty part is added to the rendered schema in the API documentation. See attached screenshot.

screenshot_29

Thanks Joe Ward for reporting!

Cannot have paths /user/me and /user/:id/posts at the same time

Currently, it's not possible to create the following endpoints at the same time:

method=GET path=/user/:id
method=GET path=/user/:id/posts

Paths with parameters as the first part are also conflicting with any other non-parametrical paths:

method=GET path=/users
method=GET path=/:username/posts

It'd be very nice to support these kinds of paths without conflicts, would it be possible?

Running tests shouldn't require Docker with no database

I newly installed encore and created a new default hello world application.
I went to run the tests via encore test ./... and the output says pull docker image: exit status 1.

I'm on MacOS running Go 1.15.6.

Any reasons why I'm getting this vs a pass or fail message?

cli/daemon/dash: bad content type detection of javascript files

We've seen a case where Go's standard library gets the content type detection of the bundled javascript files wrong.

The end result is that the browser refuses to load the files as javascript:

image

We can work around this by manually setting the content type for .js files.

Returning custom error messages on auth handlers

Hi,

I have a auth handler that looks something like this:

//encore:authhandler
func AuthHandler(ctx context.Context, token string) (auth.UID, *UserData, error) {
	response, err := GetUserInternal(ctx, &GetUserInternalParams{
		token: token,
	})
	if err != nil || response == nil {
		log.WithError(err).Warning("Failed to authenticate user")
		return "", nil, nil
	}

	if response.User.Status == model.UserStatus_Pending {
		log.Warning("Authentication failed, user is still pending")
		return "", nil, nil
	}
	if response.User.Status == model.UserStatus_Denied {
		log.Warning("Authentication failed, user is denied")
		return "", nil, nil
	}

	return auth.UID(strconv.FormatInt(response.User.ID, 10)), &UserData{
		ID:       response.User.ID,
	}, nil
}

I would like to be able to return custom unauthenticated messages for each of the three returns I have so a user knows why their auth failed. If I omit the error and return an empty string, the error message is something like "auth token missing", which is not very actionable for my users. If I do return the error, the 401 error turns into a 500 error with my message shown.

I could mode this code to my endpoints instead, but it's a lot of duplicated code. I would like to be able to do something like returning a &errs.Error to customize the return code from an auth handler error.

Proposal: REST API query parameter support

Summary

We propose to add support in Encore APIs for parsing parameters passed in query strings (for example GET /order?limit=10). This would happen automatically for APIs with parameters for HTTP methods that don’t accept request bodies, such as GET, HEAD, and DELETE.

For example, this would be callable as `GET /order?limit=10`:
type ListParams struct {
	Limit int
}

//encore:api public method=GET path=/order
func ListOrders(ctx context.Context, p *ListParams) (*Response, error) {
	/* ... */
}

Motivation

While the initial support for defining custom paths and methods made Encore much better for building REST APIs, a big limitation remains: receiving structured data in APIs that don’t accept a HTTP request body (specifically GET, HEAD, and DELETE). The only real workaround is to define the API as a raw endpoint. This works, but it reduces Encore’s ability to understand your system which makes several other aspects worse (API documentation, generated clients, and so on).

We need an Encore-native solution to defining such APIs, that works with the standard way of receiving request data for such HTTP methods: query parameters (such as ?limit=10&order=desc). This would make Encore dramatically better for defining REST APIs.

Design

When an Encore API takes structured data in the form of a named struct type (as in the example above), and the HTTP method is one that doesn’t accept an incoming request body, Encore will parse the request’s query string to fill the struct.

By default, the expected key in the query string will be the field name converted into snake_case. This can be overridden by specifying the struct tag qs:"name".

Unlike other HTTP methods, query strings cannot support nested or arbitrary data structures. To use query strings, the request parameter struct must not reference other types, and the fields must all have types that are "basic types" (strings, floats, UUIDs, time.Time, etc) or a slice of basic types. If you don't follow these constraints Encore will emit a compilation error.

Encore will automatically parse this data from the query string and populate the appropriate fields.

Quiting db proxy don't close assigned port

When opening a proxy to the DB using encore db proxy, the encore daemon opens a port. However, when closing this using ctrl-c, the port don't close. The only way I've found for closing the port is to restart the daemon using encore daemon. This is true for both when specifying a port using the -p-flag and when encore assigns a random port for me.

This is on macOS 11.6, I have not tested this on any other OS.

➜ encore db proxy        
dbproxy: listening for TCP connections on localhost:57849
^C%      
                                                                                                                                                                                                                               
➜ lsof -i:57849
COMMAND   PID     USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
encore  86031 --------   20u  IPv4     device_id_here      0t0  TCP localhost:57849 (LISTEN)

Wrapping the context too many times causes panics

Consider the following code:

package hello

import (
	"context"
	"golang.org/x/sync/errgroup"
	"net/http"
)

type Response struct {
	Message string
}

//encore:api public
func World(ctx context.Context) (*Response, error) {

	eg, ctx := errgroup.WithContext(ctx)

	for i := 0; i < 5; i++ {
		eg.Go(func() (err error) {
			return somefuncWithCtx(ctx)
		})
	}
	err := eg.Wait()
	if err != nil {
		return  nil, err
	}

	eg1, ctx := errgroup.WithContext(ctx)
	for i := 0; i < 5; i++ {
		eg.Go(func() (err error) {
			return somefuncWithCtx(ctx)
		})
	}

	err = eg1.Wait()
	if err != nil {
		return  nil, err
	}

	return &Response{Message: "Hello, world!"}, nil
}

func somefuncWithCtx(ctx context.Context) error {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://google.com", nil)
	if err != nil {
		return err
	}
	_, err = http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	return nil
}

If you hit this endpoint, encore will crash:

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x40 pc=0x13125a3]

goroutine 162 [running]:
encore.dev/runtime.httpCompleteRoundTrip(0xc0001f2100, 0x0, 0x15d6ca0, 0xc0000200d0)
	/usr/local/Cellar/encore/0.14.1/libexec/runtime/runtime/http.go:177 +0x4c3
net/http.encoreFinishRoundTrip(0xc0001f2100, 0x0, 0x15d6ca0, 0xc0000200d0)
	/usr/local/Cellar/encore/0.14.1/libexec/runtime/runtime/runtime.go:105 +0x49
net/http.(*Transport).roundTrip.func1(0xc000443a88, 0xc000443a90, 0xc000443a98)
	/usr/local/Cellar/encore/0.14.1/libexec/encore-go/src/net/http/transport.go:511 +0x51
net/http.(*Transport).roundTrip(0x1878760, 0xc0001f2100, 0x0, 0x15d6ca0, 0xc0000200d0)
	/usr/local/Cellar/encore/0.14.1/libexec/encore-go/src/net/http/transport.go:547 +0xf62
net/http.(*Transport).RoundTrip(0x1878760, 0xc0001f2100, 0x1878760, 0x0, 0x0)
	/usr/local/Cellar/encore/0.14.1/libexec/encore-go/src/net/http/roundtrip.go:17 +0x35
net/http.send(0xc0001f2100, 0x15d7780, 0x1878760, 0x0, 0x0, 0x0, 0xc000296020, 0x101a677, 0x1, 0x0)
	/usr/local/Cellar/encore/0.14.1/libexec/encore-go/src/net/http/client.go:251 +0x454
net/http.(*Client).send(0x187f860, 0xc0001f2100, 0x0, 0x0, 0x0, 0xc000296020, 0x0, 0x1, 0xc0001f2100)
	/usr/local/Cellar/encore/0.14.1/libexec/encore-go/src/net/http/client.go:175 +0xff
net/http.(*Client).do(0x187f860, 0xc0001f2100, 0x0, 0x0, 0x0)
	/usr/local/Cellar/encore/0.14.1/libexec/encore-go/src/net/http/client.go:717 +0x45f
net/http.(*Client).Do(...)
	/usr/local/Cellar/encore/0.14.1/libexec/encore-go/src/net/http/client.go:585
encore.app/hello.somefuncWithCtx(0x15e1918, 0xc00041e3c0, 0x0, 0x0)
	/Users/matthewboyle/Dev/budgsey-api/hello/hello.go:49 +0x97
encore.app/hello.World.func2(0x0, 0x0)
	/Users/matthewboyle/Dev/budgsey-api/hello/hello.go:31 +0x36
golang.org/x/sync/errgroup.(*Group).Go.func1(0xc00029a240, 0xc0006288e0)
	/Users/matthewboyle/go/pkg/mod/golang.org/x/[email protected]/errgroup/errgroup.go:57 +0x59
created by golang.org/x/sync/errgroup.(*Group).Go
	/Users/matthewboyle/go/pkg/mod/golang.org/x/[email protected]/errgroup/errgroup.go:54 +0x66

Here is the same code "working" in a standard go context: https://play.golang.org/p/n0izzHq-Igt

I fixed this for now in my code by using the regular error group without context (context.Background() worked too) but of course I lose the ability to cancel and tracing then.

Proposal: improved database support

Proposal: Improved database support

Summary

We propose several improvements to Encore’s SQL Database functionality, in order to improve flexibility by (1) supporting the use of ORMs and other libraries that rely on wrapping a *database/sql.DB, and (2) enabling multiple services to refer to the same database.

Motivation

Encore improves on several aspects relating to using SQL databases, such as provisioning and configuration, schema migrations, and easily connecting to and querying databases with minimal boilerplate necessary.

Encore currently provides package-global functions (such as encore.dev/storage/sqldb.Query) that leverage static analysis to automatically infer which service is calling them (and therefore which database to connect to, as we currently map databases one-to-one to services). This design has unfortunately proven to be too limiting.

The main limitation is that Encore requires all calls to sqldb functions to be made within a service, so that Encore can map them at compile-time to the relevant database. This prevents the use of reusable utility libraries and ORMs. It also makes it impossible to support multiple services referencing the same database or connecting to an external database. By lifting this limitation we’ll add a great deal of additional flexibility in how people can use Encore’s database support.

Detailed Design

There are two main challenges to consider:

  1. Mapping queries to the appropriate database
  2. Tracing database queries

For the first part, we propose to add the concept of Named Databases. This means that instead of mapping implicitly to “the current service’s database”, Encore will provide (in the encore.dev/storage/sqldb package) a function that provides a database object given a name:

// Named returns a database object for the db with the given name.
// The name must be provided as a constant string in order to facilitate
// compile-time checking and static analysis.
func Named(dbName string) *sqldb.Database { /* ... */ } 

// Stdlib returns a *sql.DB object that is connected to the same db,
// for use with libraries that expect a *sql.DB.
func (*sqldb.Database) Stdlib() *sql.DB

As noted in the documentation comment, the *sqldb.Database object returned can only be assigned to an unexported variable

Each Encore-provisioned database will have a name equal to the service it is created within. If other services (or packages outside the service that “owns” the database) want to access the same database, they can all refer to it by the service name:

package dbhelpers

var db = sqldb.Named("someservice")

func ListFoos(ctx context.Context) ([]*Foo, error) {
	rows, err := db.Query(ctx, "...")
	// ...
}

For backwards compatibility Encore will of course continue to provide the existing package-level sqldb.Query (etc.) functions that map to the current service’s database.

Tracing database queries

In order to continue automatically trace database queries the database object returned by sqldb.Named will use a custom driver that wraps the underlying database driver (in Encore’s case github.com/jackc/pgx), based on the approach taken by github.com/ngrok/sqlmw.

Downsides

The main downside to this approach is that when a developer writes a query statement (db.Query(ctx, "foo")) we can't in the general case know which database is being queried. This limits our ability to improve the user experience through code intelligence, query auto-completion, static analysis, and so on.

The best way to combat these limitations is through being clear about best practices:

  • Avoid passing around *sqldb.Database objects between functions; use a package-level variable instead.
  • Use a *sqldb.Database when possible over the standard library version

Future work

This proposal is designed with the intent of supporting connecting to external databases. The idea is that an external database will be given a name, just like an Encore-provisioned database, that can then be connected to with sqldb.Named("name"). That is not in scope for this proposal but is a planned extension of this work.

Proposal: Cron support

Summary

We propose a simple mechanism for scheduling recurring tasks ("cron jobs").

Motivation

Encore today provides great support for building APIs that execute some business logic in response to some external trigger (a user taking an action in a frontend, a webhook being called, and so on). But there's no built-in way to internally trigger something to happen. While there are many such use cases that we want to support natively, the simplest and most flexible is support for cron jobs.

Design

Cron's original design is incredibly simple: specify when (or how frequently) a command should be executed. But when translated to building reliable systems, it turns out there are many nuances:

  • If execution fails, what failure mode is appropriate? Running the cron job more than once, or possibly not running it at all? (see The Two Generals' Problem)
  • What to do about partial failures? What if a cron job performs two tasks, and one succeeds but the other fails?
  • If a cron job is supposed to be launched every minute, and it takes more than a minute to complete, what should happen? Should two instances run concurrently? Should the second launch be skipped? Should the delay be measured from the stop time of the previous job, or from the start of it?

And so on. What seemed simple at the surface turned out to be quite complex. At the same time we'd like to get a solution out sooner rather than later, so it's important that the design choices are (1) reasonable defaults, and (2) forward-compatible to support different trade-offs above, over time.

To provide a portable implementation, we propose to start with a design that's based on the Kubernetes CronJob design. Specifically to make the following assumptions:

  • Jobs are expected to be idempotent, meaning it's possible the same job is run more than once
  • If a job is still running by the time the next job is supposed to start, another instance is started and both jobs run concurrently

Configuration

In order to define cron jobs, we propose to extend the encore.app configuration file (which is in JSON format) with a new cron key:

"cron": [
	{
		// Required keys:
		"endpoint": "service.Endpoint",
		"schedule": "*/1 * * * *", // or "@hourly" etc

		// Optional keys (with defaults):
		"timezone": "UTC", // time zone the schedule is based on
		"prod": true,      // whether to run in prod environments
		"dev": true,       // whether to run in dev environments
		"local": true,     // whether to run for local development
	},
	...
]

Encore will validates all of these at compile-time. The endpoint must be a public or private endpoint which takes no parameters; that is, the signature is func Endpoint(context.Context) error or func Endpoint(context.Context) (*Response, error).

Cron configuration will be updated at deployment time. For local development with encore run changes will be reflected immediately (with live reload). For Kubernetes deployments the cron job will be configured as a Kubernetes CronJob object.

We believe this design strikes a good balance between supporting the most common use cases while being simple and portable. Adding support for additional use cases with different tradeoffs should be quite straightforward and backwards compatible by extending the configuration schema above.

API Explorer does not support unicode in payload

Encore's API Explorer encodes API payloads into base64 to serialize arbitrary bytes into JSON (and be compatible with Go's handling of []byte in JSON). Unfortunately this causes a problem due to how JavaScript's atob/btoa functions work with unicode (see The Unicode Problem on MDN), and results in an exception:

Uncaught (in promise) DOMException: Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.

`//encore:api auth` and `AuthHandler()` when you have multiple AuthN types/systems

Heya, saw your announcement on HN and the out-of-the-box functionality in encore looks great!

Reading through the docs, one question I had was how to address a scenario like this:

  • You run a business which has multiple AuthN systems. (Let's pretend we own a chain of fitness centers or sell software for them)
  • Fitness center employees login via a typical email/password authentication system
  • Fitness center members use a phone app and are auth'ed via SMS OTP system
  • Developers or sysadmins have yet another portal which is separate from either of these

In an implementation for something like this, I'd have 3 separate auth modules/endpoints.

Is there any non-hacky way to do this (IE, not by sending X-Authentication-Type header or something on the request context, and having a single endpoint that has a polymorphic request + response payload dependent on header value)?

These comment-directive things seem useful but I wonder if maybe there's not some way to allow folks to define their own/new ones?

Similar to Custom Decorators in Nest.js (which is a Spring clone):
https://docs.nestjs.com/custom-decorators

// Create @User decorator
export const User = createParamDecorator(
  (data: string, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest()
    const user = request.user

    return data ? user?.[data] : user
  },
);

// Create @Auth decorator
export function Auth(...roles: Role[]) {
  return applyDecorators(
    SetMetadata('roles', roles),
    UseGuards(AuthGuard, RolesGuard),
    ApiBearerAuth(),
    ApiUnauthorizedResponse({ description: 'Unauthorized' }),
  )
}
@Get()
@Auth('user')
async findOne(@User('firstName') firstName: string) {
  console.log(`Hello ${firstName}`)
}

Thanks =D
Gavin

Better database migration functionality

I understand that the topic of databases had to be heavily discussed to implement the current version of it. Anyway, I'd like to open a discussion (ask questions):

  1. It seems there is no way to revert a SQL migration right now. Only the .up.sql is present.

  2. For a while, I've been trying different platforms for fetching data like Hasura, Prisma, or entgo. There are different approaches to solve migrations, but from my experience, I can say that writing pure SQL migrations can be really painful. Especially, if you want to handle rollback or incremental changes. I thought it's not a big deal and I always created a scheme in an editor and then export it to a migration file. After a while, I found out it's a really cumbersome approach. For example, frameworks like Ruby on Rails, Laravel (PHP), or Buffalo has a really good approach - not using any SQL dialect, but rather use database-agnostic query builder (API).

  3. In addition to the second point, I really wish to have more control over my database workflow. Right now, it's a little bit of magic. Also, to have more freedom about choosing my database client (entgo, gorm, prisma, etc.) I know there must be some instrumentation to be able to send metric (that could be problematic), but once again, using a pure SQL in a code it's just a hell (sure it can be solved by many different way, use more SQL views, materialized views, use denormalized data from ELK, Redis, etc). I know it's a topic where people get sweaty :D .. it's just ... I don't have really good experience in using pure SQL queries.

I hope it makes sense what I've written. So, what do you think about that? :)

XSS in cli/daemon

pid := req.Header.Get("X-Encore-Proc-ID")
if pid == "" {
http.Error(w, "missing X-Encore-Proc-ID header", http.StatusBadRequest)
return
}
traceID, err := parseTraceID(req.Header.Get("X-Encore-Trace-ID"))
if err != nil {
http.Error(w, "invalid X-Encore-Trace-ID header: "+err.Error(), http.StatusBadRequest)
return
}
proc := s.runMgr.FindProc(pid)
if proc == nil {
http.Error(w, "process "+pid+" not running", http.StatusBadRequest)

Cross-site scripting vulnerability due to unsanitized pid header. Reported by Github CodeQL. See Security -> Code Scanning Alerts

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.