diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f83e158075..b3b8f75cb7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -139,6 +139,15 @@ jobs: run: just init-db - name: Rebuild All run: just build-all + docs: + name: Build Docs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - uses: cashapp/activate-hermit@v1 + - run: cd docs && zola build integration-shard: name: Shard Integration Tests runs-on: ubuntu-latest diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000000..504eea4c67 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,22 @@ +on: + push: + branches: + - main +name: Publish Docs +jobs: + build: + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + steps: + - name: checkout + uses: actions/checkout@v4 + with: + submodules: true + - name: build_and_deploy + uses: shalzz/zola-deploy-action@v0.18.0 + env: + # Target branch + PAGES_BRANCH: gh-pages + # Or if publishing to the same repo, use the automatic token + TOKEN: ${{ secrets.GITHUB_TOKEN }} + BUILD_DIR: docs diff --git a/.gitignore b/.gitignore index de97cdd3d0..3339970ff0 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ go.work* junit*.xml /readme-tests **/_ftl +/docs/public diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..fcae792967 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "docs/themes/adidoks"] + path = docs/themes/adidoks + url = https://github.com/aaranxu/adidoks.git diff --git a/Justfile b/Justfile index 84b228815a..45c68f3a41 100644 --- a/Justfile +++ b/Justfile @@ -135,3 +135,8 @@ lint-frontend: build-frontend # Lint the backend lint-backend: @golangci-lint run ./... + +# Run live docs server +docs: + git submodule update --init --recursive + cd docs && zola serve \ No newline at end of file diff --git a/bin/.zola-0.18.0.pkg b/bin/.zola-0.18.0.pkg new file mode 120000 index 0000000000..383f4511d4 --- /dev/null +++ b/bin/.zola-0.18.0.pkg @@ -0,0 +1 @@ +hermit \ No newline at end of file diff --git a/bin/zola b/bin/zola new file mode 120000 index 0000000000..facf316c5b --- /dev/null +++ b/bin/zola @@ -0,0 +1 @@ +.zola-0.18.0.pkg \ No newline at end of file diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 47b521c121..0000000000 --- a/docs/README.md +++ /dev/null @@ -1,324 +0,0 @@ -# FTL Reference - -## Import the runtime - -Some aspects of FTL rely on a runtime which must be imported with: - -```go -import "github.com/TBD54566975/ftl/go-runtime/ftl" -``` - -## Verbs - -### Defining Verbs - -To declare a Verb, write a normal Go function with the following signature,annotated with the Go [comment directive](https://tip.golang.org/doc/comment#syntax) `//ftl:verb`: - -```go -//ftl:verb -func F(context.Context, In) (Out, error) { } -``` - -eg. - -```go -type EchoRequest struct {} - -type EchoResponse struct {} - -//ftl:verb -func Echo(ctx context.Context, in EchoRequest) (EchoResponse, error) { - // ... -} -``` - -By default verbs are only [visible](#Visibility) to other verbs in the same module. - -### Calling Verbs - -To call a verb use `ftl.Call()`. eg. - -```go -out, err := ftl.Call(ctx, echo.Echo, echo.EchoRequest{}) -``` - -## Types - -FTL supports the following types: `Int` (64-bit), `Float` (64-bit), `String`, `Bytes` (a byte array), `Bool`, `Time`, `Any` (a dynamic type), `Unit` (similar to "void"), arrays, maps, data structures, and constant enumerations. Each FTL type is mapped to a corresponding language-specific type. For example in Go `Float` is represented as `float64`, `Time` is represented by `time.Time`, and so on. [^2] - -Any Go type supported by FTL and referenced by an FTL declaration will be automatically exposed to an FTL type. - -For example, the following verb declaration will result in `Request` and `Response` being automatically translated to FTL types. - -```go -type Request struct {} -type Response struct {} - -//ftl:verb -func Hello(ctx context.Context, in Request) (Response, error) { - // ... -} -``` - -### Type enums (sum types) - -[Sum types](https://en.wikipedia.org/wiki/Tagged_union) are supported by FTL's type system, but aren't directly supported by Go. However they can be approximated with the use of [sealed interfaces](https://blog.chewxy.com/2018/03/18/golang-interfaces/). To declare a sum type in FTL use the comment directive `//ftl:enum`: - -```go -//ftl:enum -type Animal interface { animal() } - -type Cat struct {} -func (Cat) animal() {} - -type Dog struct {} -func (Dog) animal() {} -``` - -### Value enums - -A value enum is an enumerated set of string or integer values. - -```go -//ftl:enum -type Colour string - -const ( - Red Colour = "red" - Green Colour = "green" - Blue Colour = "blue" -) -``` - -### Type aliases - -A type alias is an alternate name for an existing type. It can be declared like so: - -```go -//ftl:typealias -type Alias Target -``` - -eg. - -```go -//ftl:typealias -type UserID string -``` - -## Visibility - -By default all declarations in FTL are visible only to the module they're declared in. The implicit visibility of types is that of the first verb or other declaration that references it. - -### Exporting declarations - -Exporting a declaration makes it accessible to other modules. Some declarations that are entirely local to a module, such as secrets/config, cannot be exported. - -Types that are transitively referenced by an exported declaration will be automatically exported unless they were already defined but unexported. In this case, an error will be raised and the type must be explicitly exported. - -The following table describes the directives used to export the corresponding declaration: - -| Symbol | Export syntax | -| ------------- | ------------------------ | -| Verb | `//ftl:verb export` | -| Data | `//ftl:data export` | -| Enum/Sum type | `//ftl:enum export` | -| Typealias | `//ftl:typealias export` | -| Topic | `//ftl:export` [^1] | - -eg. - -```go -//ftl:verb export -func Verb(ctx context.Context, in In) (Out, error) - -//ftl:typealias export -type UserID string -``` - -## HTTP ingress - -Verbs annotated with `ftl:ingress` will be exposed via HTTP (`http` is the default ingress type). These endpoints will then be available on one of our default `ingress` ports (local development defaults to `http://localhost:8891`). - -The following will be available at `http://localhost:8891/http/users/123/posts?postId=456`. - -```go -type GetRequest struct { - UserID string `json:"userId"` - PostID string `json:"postId"` -} - -type GetResponse struct { - Message string `json:"msg"` -} - -//ftl:ingress GET /http/users/{userId}/posts -func Get(ctx context.Context, req builtin.HttpRequest[GetRequest]) (builtin.HttpResponse[GetResponse, ErrorResponse], error) { - // ... -} -``` - -> [!NOTE] -> The `req` and `resp` types of HTTP `ingress` [verbs](#Verb) must be `builtin.HttpRequest` and `builtin.HttpResponse` respectively. These types provide the necessary fields for HTTP `ingress` (`headers`, `statusCode`, etc.) - -Key points to note - -- `path`, `query`, and `body` parameters are automatically mapped to the `req` and `resp` structures. In the example above, `{userId}` is extracted from the path parameter and `postId` is extracted from the query parameter. -- `ingress` verbs will be automatically exported by default. - -## Cron jobs - -A cron job is an Empty verb that will be called on a schedule. The syntax is described [here](https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/utilities/crontab.html). - -eg. The following function will be called hourly: - -```go -//ftl:cron 0 * * * * -func Hourly(ctx context.Context) error { - // ... -} -``` - -## Secrets/configuration - -### Configuration - -Configuration values are named, typed values. They are managed by the `ftl config` command-line. - -To declare a configuration value use the following syntax: - -```go -var defaultUser = ftl.Config[string]("default") -``` - -Then to retrieve a configuration value: - -```go -username = defaultUser.Get(ctx) -``` - -### Secrets - -Secrets are encrypted, named, typed values. They are managed by the `ftl secret` command-line. - -Declare a secret with the following: - -```go -var apiKey = ftl.Secret[string]("apiKey") -``` - -Then to retrieve a secret value: - -```go -username = defaultUser.Get(ctx) -``` - -### Transforming secrets/configuration - -Often, raw secret/configuration values aren't directly useful. For example, raw credentials might be used to create an API client. For those situations `ftl.Map()` can be used to transform a configuration or secret value into another type: - -```go -var client = ftl.Map(ftl.Secret[Credentials]("credentials"), - func(ctx context.Context, creds Credentials) (*api.Client, error) { - return api.NewClient(creds) -}) -``` - -## PubSub - -PubSub is a first-class concept in FTL, modelled on the concepts of topics (where events are sent), subscriptions (a cursor over the topic), and subscribers (where events are delivered to). Susbcribers are, as you would expect, Sinks. Each subscription is a cursor over the topic it is associated with. Each topic may have multiple subscriptions. Each subscription may have multiple subscribers, in which case events will be distributed among them. - -First, declare a new topic: - -```go -var invoicesTopic = ftl.Topic[Invoice]("invoices") -``` - -Then declare each subscription on the topic: - -```go -var _ = ftl.Subscription(invoicesTopic, "emailInvoices") -``` - -And finally define a Sink to consume from the subscription: - -```go -//ftl:subscribe emailInvoices -func SendInvoiceEmail(ctx context.Context, in Invoice) error { - // ... -} -``` - -> [!NOTE] -> PubSub topics cannot be published to from outside the module that declared them, they can only be subscribed to. That is, if a topic is declared in module `A`, module `B` cannot publish to it. - -## FSM - -FTL has first-class support for distributed [finite-state machines](https://en.wikipedia.org/wiki/Finite-state_machine). Each state in the state machine is a Sink, with events being values of the type of each sinks input. The FSM is declared once, with each executing instance of the FSM identified by a unique key when sending an event to it. - -Here's an example of an FSM that models a simple payment flow: - -```go -var payment = ftl.FSM( - "payment", - ftl.Start(Invoiced), - ftl.Start(Paid), - ftl.Transition(Invoiced, Paid), - ftl.Transition(Invoiced, Defaulted), -) - -//ftl:verb -func SendDefaulted(ctx context.Context, in DefaultedInvoice) error { - return payment.Send(ctx, in.InvoiceID, in.Timeout) -} - -//ftl:verb -func Invoiced(ctx context.Context, in Invoice) error { - if timedOut { - return ftl.CallAsync(ctx, SendDefaulted, Timeout{...}) - } -} - -//ftl:verb -func Paid(ctx context.Context, in Receipt) error { /* ... */ } - -//ftl:verb -func Defaulted(ctx context.Context, in Timeout) error { /* ... */ } -``` - -Then to send events to the FSM: - -```go -err := payment.Send(ctx, invoiceID, Invoice{Amount: 110}) -``` - -Sending an event to an FSM is asynchronous. From the time an event is sent until the state function completes execution, the FSM is transitioning. It is invalid to send an event to an FSM that is transitioning. - -## Retries - -Any verb called asynchronously (specifically, PubSub subscribers and FSM states), may optionally specify a basic exponential backoff retry policy via a Go comment directive. The directive has the following syntax: - -```go -//ftl:retry [] [] -``` - -`attempts` and `max-backoff` default to unlimited if not specified. - -For example, the following function will retry up to 10 times, with a delay of 5s, 10s, 20s, 40s, 60s, 60s, etc. - -```go -//ftl:retry 10 5s 1m -func Invoiced(ctx context.Context, in Invoice) error { - // ... -} -``` - -_[Verb]: func(context.Context, In) (Out, error) -_[Verbs]: func(context.Context, In) (Out, error) -_[Sink]: func(context.Context, In) error -_[Sinks]: func(context.Context, In) error -_[Source]: func(context.Context) (Out, error) -_[Sources]: func(context.Context) (Out, error) \*[Empty]: func(context.Context) error - -[^1]: Annotation of topics is usually unnecessary. -[^2]: Note that until [type widening](https://github.com/TBD54566975/ftl/issues/1296) is implemented, external types are not supported. diff --git a/docs/config.toml b/docs/config.toml new file mode 100644 index 0000000000..bf4eb8756b --- /dev/null +++ b/docs/config.toml @@ -0,0 +1,104 @@ +# The URL the site will be built for +base_url = "https://tbd54566975.github.io/ftl/" +title = "FTL" +description = "FTL - Towards a 𝝺-calculus for large-scale systems" + +# The site theme to use. +theme = "adidoks" + +# The default language; used in feeds and search index +# Note: the search index doesn't support Chinese/Japanese/Korean Languages +default_language = "en" + +# Whether to automatically compile all Sass files in the sass directory +compile_sass = true + +# Whether to generate a feed file for the site +generate_feed = true + +# When set to "true", the generated HTML files are minified. +minify_html = false + +# The taxonomies to be rendered for the site and their configuration. +taxonomies = [ + { name = "authors" }, # Basic definition: no feed or pagination +] + +# Whether to build a search index to be used later on by a JavaScript library +# When set to "true", a search index is built from the pages and section +# content for `default_language`. +build_search_index = true + +[search] +# Whether to include the title of the page/section in the index +include_title = true +# Whether to include the description of the page/section in the index +include_description = true +# Whether to include the rendered content of the page/section in the index +include_content = true + +[markdown] +# Whether to do syntax highlighting. +# Theme can be customised by setting the `highlight_theme` +# variable to a theme supported by Zola +highlight_code = true + +[extra] +# Put all your custom variables here +author = "The FTL Team" +github = "https://github.com/TBD54566975/ftl" +twitter = "https://twitter.com/TBD54566975" +email = "aat@block.xyz" + +# If running on netlify.app site, set to true +is_netlify = false + +# Set HTML file language +language_code = "en-US" + +# Set theme-color meta tag for Chrome browser +theme_color = "#fff" + +# More about site's title +title_separator = "|" # set as |, -, _, etc +title_addition = "Towards a 𝝺-calculus for large-scale systems" + + +# Set date format in blog publish metadata +timeformat = "%B %e, %Y" # e.g. June 14, 2021 +timezone = "Australia/NSW" + +# Edit page on reposity or not +edit_page = false +docs_repo = "https://github.com/TBD54566975/ftl" +repo_branch = "main" + +## Math settings +# options: true, false. Enable math support globally, +# default: false. You can always enable math on a per page. +math = false +library = "katex" # options: "katex", "mathjax". default is "katex". + +# Menu items +[[extra.menu.main]] +name = "Docs" +section = "docs" +url = "/docs/getting-started/introduction/" +weight = 10 + +[[extra.menu.social]] +name = "Twitter" +pre = '' +url = "https://twitter.com/TBD54566975" +weight = 10 + +[[extra.menu.social]] +name = "GitHub" +pre = '' +url = "https://github.com/TBD54566975/ftl" +post = "v0.1.0" +weight = 20 + +# Footer contents +[extra.footer] +info = 'Powered by Zola, and AdiDoks' diff --git a/docs/content/_index.md b/docs/content/_index.md new file mode 100644 index 0000000000..51600567c3 --- /dev/null +++ b/docs/content/_index.md @@ -0,0 +1,51 @@ ++++ +title = "FTL" + + +# The homepage contents +[extra] +lead = 'Towards a 𝝺-calculus for large-scale systems' +url = "/docs/getting-started/introduction/" +url_button = "Get started" +repo_version = "0.248.0" +repo_license = "Open-source Apache License." +repo_url = "https://github.com/TBD54566975/ftl" + +# Menu items +[[extra.menu.main]] +name = "Docs" +section = "docs" +url = "/docs/getting-started/introduction/" +weight = 10 + +# [[extra.menu.main]] +# name = "Blog" +# section = "blog" +# url = "/blog/" +# weight = 20 + +[[extra.list]] +title = "Infrastructure as code" +content = "Not YAML. Declare your infrastructure in the same language you're writing in as type-safe values, rather than in separate configuration files disassociated from their point of use." + +[[extra.list]] +title = "Language agnostic" +content = 'FTL makes it possible to write backend code in your language of choice. You write normal code, and FTL extracts a service interface from your code directly, making your functions and types automatically available to all supported languages.' + +[[extra.list]] +title = "Fearless development against production" +content = "There is no substitute for production data. FTL plans to support forking of production infrastructure _and_ code during development." + +[[extra.list]] +title = "Fearlessly modify types" +content = 'Multiple versions of a single verb with different signatures can be live concurrently. See Unison for inspiration. We can statically detect changes that would violate runtime and persistent data constraints.' + +[[extra.list]] +title = "AI from the ground up" +content = "We plan to integrate AI sensibly and deeply into the FTL platform. Automated AI-driven tuning suggestions, automated third-party API integration, and so on." + +# [[extra.list]] +# title = "Dark mode" +# content = "Switch to a low-light UI with the click of a button. Change colors with variables to match your branding." + ++++ diff --git a/docs/content/docs/_index.md b/docs/content/docs/_index.md new file mode 100644 index 0000000000..8d023418ff --- /dev/null +++ b/docs/content/docs/_index.md @@ -0,0 +1,9 @@ ++++ +title = "Docs" +description = "The documents of FTL." +date = 2025-05-01T08:00:00+00:00 +updated = 2021-05-01T08:00:00+00:00 +sort_by = "weight" +weight = 1 +template = "docs/section.html" ++++ diff --git a/docs/content/docs/getting-started/_index.md b/docs/content/docs/getting-started/_index.md new file mode 100644 index 0000000000..61d2ae186d --- /dev/null +++ b/docs/content/docs/getting-started/_index.md @@ -0,0 +1,10 @@ ++++ +title = "Getting Started" +description = "Quick start and guides for installing the AdiDoks theme on your preferred operating system." +date = 2025-05-01T08:00:00+00:00 +updated = 2021-05-01T08:00:00+00:00 +template = "docs/section.html" +sort_by = "weight" +weight = 10 +draft = false ++++ diff --git a/docs/content/docs/getting-started/introduction.md b/docs/content/docs/getting-started/introduction.md new file mode 100644 index 0000000000..302ecf57bf --- /dev/null +++ b/docs/content/docs/getting-started/introduction.md @@ -0,0 +1,16 @@ ++++ +title = "Introduction" +description = "An introduction to FTL" +date = 2021-05-01T08:00:00+00:00 +updated = 2021-05-01T08:00:00+00:00 +draft = false +weight = 10 +sort_by = "weight" +template = "docs/page.html" + +[extra] +toc = true +top = false ++++ + +FTL is tooling, runtimes, and frameworks for simplifying the creation of distributed systems. diff --git a/docs/content/docs/getting-started/quick-start.md b/docs/content/docs/getting-started/quick-start.md new file mode 100644 index 0000000000..592d3a0fba --- /dev/null +++ b/docs/content/docs/getting-started/quick-start.md @@ -0,0 +1,124 @@ ++++ +title = "Quick Start" +description = "One page summary of how to start a new FTL project." +date = 2021-05-01T08:20:00+00:00 +updated = 2021-05-01T08:20:00+00:00 +draft = false +weight = 20 +sort_by = "weight" +template = "docs/page.html" + +[extra] +lead = "One page summary of how to start a new FTL project." +toc = true +top = false ++++ + +## Requirements + +### Install the FTL CLI + +Install the FTL CLI via [Hermit](https://cashapp.github.io/hermit), [Homebrew](https://brew.sh/), or manually. + +#### Hermit (Mac or Linux) + +FTL can be installed from the main Hermit package repository by simply: + +``` +hermit install ftl +``` + +Alternatively you can add [hermit-ftl](https://github.com/TBD54566975/hermit-ftl) to your sources by adding the following to your Hermit environment's `bin/hermit.hcl` file: + +```hcl +sources = ["https://github.com/TBD54566975/hermit-ftl.git", "https://github.com/cashapp/hermit-packages.git"] +``` + +#### Homebrew (Mac or Linux) + +``` +brew tap TBD54566975/ftl && brew install ftl +``` + +#### Manually (Mac or Linux) + +Download binaries from the [latest release page](https://github.com/TBD54566975/ftl/releases/latest) and place them in your `$PATH`. + +### Install the VSCode extension + +The [FTL VSCode extension](https://marketplace.visualstudio.com/items?itemName=FTL.ftl) will run FTL within VSCode, and provide LSP support for FTL, displaying errors within the editor. + +## Development + +### Create a new module + +Once FTL is installed, create a new module: + +``` +mkdir myproject +cd myproject +ftl init go . alice +``` + +This will place the code for the new module `alice` in `myproject/alice/alice.go`: + +```go +package alice + +import ( + "context" + "fmt" + + "github.com/TBD54566975/ftl/go-runtime/ftl" // Import the FTL SDK. +) + +type EchoRequest struct { + Name ftl.Option[string] `json:"name"` +} + +type EchoResponse struct { + Message string `json:"message"` +} + +//ftl:verb +func Echo(ctx context.Context, req EchoRequest) (EchoResponse, error) { + return EchoResponse{Message: fmt.Sprintf("Hello, %s!", req.Name.Default("anonymous"))}, nil +} +``` + +Each module is its own Go module. + +Any number of modules can be added to your project, adjacent to each other. + +### Start the FTL cluster + +If using VSCode, opening the directory will prompt you to start FTL. + +Alternatively start the local FTL development cluster from the command-line: + +``` +ftl dev +``` + +This will build and deploy all local modules. Modifying the code will cause `ftl +dev` to rebuild and redeploy the module. + +### Open the console + +FTL has a console that allows navigation of the cluster topology, logs, traces, +and more. Open a browser window at [https://localhost:8892](https://localhost:8892) to view it. + +### Create another module + +Create another module and call `alice.echo` from it with: + +```go +//ftl:verb +import "ftl/alice" + +out, err := ftl.Call(ctx, alice.Echo, alice.EchoRequest{}) +``` + +### What next? + +Explore the [reference documentation](../../reference/start/). diff --git a/docs/content/docs/help/_index.md b/docs/content/docs/help/_index.md new file mode 100644 index 0000000000..a5189ba36e --- /dev/null +++ b/docs/content/docs/help/_index.md @@ -0,0 +1,10 @@ ++++ +title = "Help" +description = "Get help on FTL." +date = 2025-05-01T19:00:00+00:00 +updated = 2021-05-01T19:00:00+00:00 +template = "docs/section.html" +sort_by = "weight" +weight = 30 +draft = false ++++ diff --git a/docs/content/docs/help/faq.md b/docs/content/docs/help/faq.md new file mode 100644 index 0000000000..abd9f3b84c --- /dev/null +++ b/docs/content/docs/help/faq.md @@ -0,0 +1,76 @@ ++++ +title = "FAQ" +description = "Answers to frequently asked questions." +date = 2021-05-01T19:30:00+00:00 +updated = 2021-05-01T19:30:00+00:00 +draft = false +weight = 30 +sort_by = "weight" +template = "docs/page.html" + +[extra] +lead = "Answers to frequently asked questions." +toc = true +top = false ++++ + +## Why does FTL not allow external types? + +Because of the nature of writing FTL verbs and data types, it's easy to think of it as just writing standard native code. Through that lens it is then somewhat surprising when FTL disallows the use of arbitrary external data types. + +However, FTL types are not _just_ native types. FTL types are a more convenient method of writing an IDL such as [Protobufs](https://protobuf.dev/), [OpenAPI](https://www.openapis.org/) or [Thrift](https://thrift.apache.org/). With this in mind the constraint makes more sense. An IDL by its very nature must support a multitude of languages, so including an arbitrary type from a third party native library in one language may not be translatable to another language. + +There are also secondary reasons, such as: + +- Unclear ownership - in FTL a type must be owned by a single module. When importing a common third party type from multiple modules, which module owns that type? +- An external type must be representable in the FTL schema. The schema is then used to generate types for other modules, including those in other languages. For an external type, FTL could track the external library it belongs to to generate the "correct" code, but this would only be representable in a single language. +- External types often perform custom marshalling to/from JSON. This is not representable cross-language. +- Cleaner separation of abstraction layers - the ability to mix in abitrary external types is convenient, but can easily lead to mixing of concerns between internal and external data representations. + +So what to do? While there are good reasons to disallow external types, it's also very irritating to have to manually transcribe types, or translate between JSON "blobs" in FTL and strong internal types. We're not sure how, but we definitely want to improve this experience. There is a draft [design document](/S8iS08PFT4SdnXzs4BIt8A) enumerating some options, please add your thoughts. + +## What is a "module"? + +In its least abstract form, a module is a collection of verbs, and the resources (databases, queues, cron jobs, secrets, config, etc.) that those verbs rely on to operate. All resources are private to their owning module. + +More abstractly, the separation of concerns between modules is largely subjective. You _can_ think of each module as largely analogous to a traditional service, so when asking where the division between modules is that could inform your decision. That said, the ease of deploying modules in FTL is designed to give you more flexibility in how you structure your code. + +## How do I represent optional/nullable values? + +FTL's type system includes support for optionals. In Go this is represented as `ftl.Option[T]`, in languages with first-class support for optionals such as Kotlin, FTL will leverage the native type system. + +When FTL is mapping to JSON, optional values will be represented as `null`. + +In Go specifically, pointers to values are not supported because pointers are semantically ambiguous and error prone. They can mean, variously: "this value may or may not be present", or "this value just happens to be a pointer", or "this value is a pointer because it's mutable" + +Additionally pointers to builtin types are painful to use in Go because you can't obtain a reference to a literal. + +## Why must requests/responses be data structures, can't they be arrays, etc.? + +This is currently due to FTL relying on traditional [schema evolution](https://softwaremill.com/schema-evolution-protobuf-scalapb-fs2grpc/) for forwards/backwards compatibility - eg. changing a slice to a struct in a backward compatible way is not possible, as an existing deployed peer consuming the slice will fail if it suddenly changes to a data structure. + +Eventually FTL will allow multiple versions of a verb to be simultaneously deployed, such that a version returning a slice can coexist temporarily with a version returning a struct. Once all peers have been updated to support the new type signature, the old version will be dropped. + +## I can't export a Verb from a nested package inside a subdirectory of the module root. What do I do? + +Verbs and types can only be exported from the top level of each module. You are welcome to put any helper code you'd like in a nested package/directory. + +## What types are supported by FTL? + +FTL supports the following types: `Int` (64-bit), `Float` (64-bit), `String`, `Bytes` (a byte array), `Bool`, `Time`, `Any` (a dynamic type), `Unit` (similar to "void"), arrays, maps, data structures, and constant enumerations. Each FTL type is mapped to a corresponding language-specific type. For example in Go `Float` is represented as `float64`, `Time` is represented by `time.Time`, and so on. + +Note that currently (until [type widening](https://github.com/TBD54566975/ftl/issues/1296) is implemented), external types are not supported. + +## SQL errors on startup? + +For example: + +```bash +# ftl dev ~/src/ftl +info: Starting FTL with 1 controller(s) +ftl: error: ERROR: relation "fsm_executions" does not exist (SQLSTATE 42P01) +``` + +Run again with `ftl dev --recreate`. This usually indicates that your DB has an old schema. + +This can occur when FTL has been upgraded with schema changes, making the database out of date. While in alpha we do not use schema migrations, so this won't occur once we hit a stable release. diff --git a/docs/content/docs/help/glossary.md b/docs/content/docs/help/glossary.md new file mode 100644 index 0000000000..67e14971cb --- /dev/null +++ b/docs/content/docs/help/glossary.md @@ -0,0 +1,47 @@ ++++ +title = "Glossary" +description = "Glossary of terms and definitions in FTL" +date = 2021-05-01T19:30:00+00:00 +updated = 2021-05-01T19:30:00+00:00 +draft = false +weight = 40 +sort_by = "weight" +template = "docs/page.html" + +[extra] +lead = "Glossary of terms and definitions in FTL." +toc = true +top = false ++++ + +##### Verb + +A Verb is a remotely callable function that takes an input and returns an output. + +```go +func(context.Context, In) (Out, error) +``` + +##### Sink + +A Sink is a function that takes an input and returns nothing. + +```go +func(context.Context, In) error +``` + +##### Source + +A Source is a function that takes no input and returns an output. + +```go +func(context.Context) (Out, error) +``` + +##### Empty + +An Empty function is one that takes neither input or output. + +```go +func(context.Context) error +``` diff --git a/docs/content/docs/reference/_index.md b/docs/content/docs/reference/_index.md new file mode 100644 index 0000000000..418534c727 --- /dev/null +++ b/docs/content/docs/reference/_index.md @@ -0,0 +1,10 @@ ++++ +title = "Reference" +description = "Reference documentation for FTL" +date = 2025-05-01T18:00:00+00:00 +updated = 2021-05-01T18:00:00+00:00 +template = "docs/section.html" +sort_by = "weight" +weight = 20 +draft = false ++++ diff --git a/docs/content/docs/reference/cron.md b/docs/content/docs/reference/cron.md new file mode 100644 index 0000000000..79e4d46534 --- /dev/null +++ b/docs/content/docs/reference/cron.md @@ -0,0 +1,25 @@ ++++ +title = "Cron" +description = "Cron Jobs" +date = 2021-05-01T08:20:00+00:00 +updated = 2021-05-01T08:20:00+00:00 +draft = false +weight = 60 +sort_by = "weight" +template = "docs/page.html" + +[extra] +toc = true +top = false ++++ + +A cron job is an Empty verb that will be called on a schedule. The syntax is described [here](https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/utilities/crontab.html). + +eg. The following function will be called hourly: + +```go +//ftl:cron 0 * * * * +func Hourly(ctx context.Context) error { + // ... +} +``` diff --git a/docs/content/docs/reference/fsm.md b/docs/content/docs/reference/fsm.md new file mode 100644 index 0000000000..f3337f85f2 --- /dev/null +++ b/docs/content/docs/reference/fsm.md @@ -0,0 +1,54 @@ ++++ +title = "FSM" +description = "Distributed Finite-State Machines" +date = 2021-05-01T08:20:00+00:00 +updated = 2021-05-01T08:20:00+00:00 +draft = false +weight = 90 +sort_by = "weight" +template = "docs/page.html" + +[extra] +toc = true +top = false ++++ + +FTL has first-class support for distributed [finite-state machines](https://en.wikipedia.org/wiki/Finite-state_machine). Each state in the state machine is a Sink, with events being values of the type of each sinks input. The FSM is declared once, with each executing instance of the FSM identified by a unique key when sending an event to it. + +Here's an example of an FSM that models a simple payment flow: + +```go +var payment = ftl.FSM( + "payment", + ftl.Start(Invoiced), + ftl.Start(Paid), + ftl.Transition(Invoiced, Paid), + ftl.Transition(Invoiced, Defaulted), +) + +//ftl:verb +func SendDefaulted(ctx context.Context, in DefaultedInvoice) error { + return payment.Send(ctx, in.InvoiceID, in.Timeout) +} + +//ftl:verb +func Invoiced(ctx context.Context, in Invoice) error { + if timedOut { + return ftl.CallAsync(ctx, SendDefaulted, Timeout{...}) + } +} + +//ftl:verb +func Paid(ctx context.Context, in Receipt) error { /* ... */ } + +//ftl:verb +func Defaulted(ctx context.Context, in Timeout) error { /* ... */ } +``` + +Then to send events to the FSM: + +```go +err := payment.Send(ctx, invoiceID, Invoice{Amount: 110}) +``` + +Sending an event to an FSM is asynchronous. From the time an event is sent until the state function completes execution, the FSM is transitioning. It is invalid to send an event to an FSM that is transitioning. diff --git a/docs/content/docs/reference/ingress.md b/docs/content/docs/reference/ingress.md new file mode 100644 index 0000000000..7777aaff22 --- /dev/null +++ b/docs/content/docs/reference/ingress.md @@ -0,0 +1,42 @@ ++++ +title = "HTTP Ingress" +description = "Handling incoming HTTP requests" +date = 2021-05-01T08:20:00+00:00 +updated = 2021-05-01T08:20:00+00:00 +draft = false +weight = 50 +sort_by = "weight" +template = "docs/page.html" + +[extra] +toc = true +top = false ++++ + +Verbs annotated with `ftl:ingress` will be exposed via HTTP (`http` is the default ingress type). These endpoints will then be available on one of our default `ingress` ports (local development defaults to `http://localhost:8891`). + +The following will be available at `http://localhost:8891/http/users/123/posts?postId=456`. + +```go +type GetRequest struct { + UserID string `json:"userId"` + PostID string `json:"postId"` +} + +type GetResponse struct { + Message string `json:"msg"` +} + +//ftl:ingress GET /http/users/{userId}/posts +func Get(ctx context.Context, req builtin.HttpRequest[GetRequest]) (builtin.HttpResponse[GetResponse, ErrorResponse], error) { + // ... +} +``` + +> **NOTE!** +> The `req` and `resp` types of HTTP `ingress` [verbs](../verbs) must be `builtin.HttpRequest` and `builtin.HttpResponse` respectively. These types provide the necessary fields for HTTP `ingress` (`headers`, `statusCode`, etc.) + +Key points to note + +- `path`, `query`, and `body` parameters are automatically mapped to the `req` and `resp` structures. In the example above, `{userId}` is extracted from the path parameter and `postId` is extracted from the query parameter. +- `ingress` verbs will be automatically exported by default. diff --git a/docs/content/docs/reference/pubsub.md b/docs/content/docs/reference/pubsub.md new file mode 100644 index 0000000000..da5ac89e00 --- /dev/null +++ b/docs/content/docs/reference/pubsub.md @@ -0,0 +1,40 @@ ++++ +title = "PubSub" +description = "Asynchronous publishing of events to topics" +date = 2021-05-01T08:20:00+00:00 +updated = 2021-05-01T08:20:00+00:00 +draft = false +weight = 80 +sort_by = "weight" +template = "docs/page.html" + +[extra] +toc = true +top = false ++++ + +FTL has first-class support for PubSub, modelled on the concepts of topics (where events are sent), subscriptions (a cursor over the topic), and subscribers (functions events are delivered to). Susbcribers are, as you would expect, sinks. Each subscription is a cursor over the topic it is associated with. Each topic may have multiple subscriptions. Each subscription may have multiple subscribers, in which case events will be distributed among them. + +First, declare a new topic: + +```go +var invoicesTopic = ftl.Topic[Invoice]("invoices") +``` + +Then declare each subscription on the topic: + +```go +var _ = ftl.Subscription(invoicesTopic, "emailInvoices") +``` + +And finally define a Sink to consume from the subscription: + +```go +//ftl:subscribe emailInvoices +func SendInvoiceEmail(ctx context.Context, in Invoice) error { + // ... +} +``` + +> **NOTE!** +> PubSub topics cannot be published to from outside the module that declared them, they can only be subscribed to. That is, if a topic is declared in module `A`, module `B` cannot publish to it. diff --git a/docs/content/docs/reference/retries.md b/docs/content/docs/reference/retries.md new file mode 100644 index 0000000000..4aa4bd766c --- /dev/null +++ b/docs/content/docs/reference/retries.md @@ -0,0 +1,31 @@ ++++ +title = "Retries" +description = "Retrying asynchronous verbs" +date = 2021-05-01T08:20:00+00:00 +updated = 2021-05-01T08:20:00+00:00 +draft = false +weight = 100 +sort_by = "weight" +template = "docs/page.html" + +[extra] +toc = true +top = false ++++ + +Any verb called asynchronously (specifically, PubSub subscribers and FSM states), may optionally specify a basic exponential backoff retry policy via a Go comment directive. The directive has the following syntax: + +```go +//ftl:retry [] [] +``` + +`attempts` and `max-backoff` default to unlimited if not specified. + +For example, the following function will retry up to 10 times, with a delay of 5s, 10s, 20s, 40s, 60s, 60s, etc. + +```go +//ftl:retry 10 5s 1m +func Invoiced(ctx context.Context, in Invoice) error { + // ... +} +``` diff --git a/docs/content/docs/reference/secretsconfig.md b/docs/content/docs/reference/secretsconfig.md new file mode 100644 index 0000000000..4856b5d444 --- /dev/null +++ b/docs/content/docs/reference/secretsconfig.md @@ -0,0 +1,57 @@ ++++ +title = "Secrets/Config" +description = "Secrets and Configuration values" +date = 2021-05-01T08:20:00+00:00 +updated = 2021-05-01T08:20:00+00:00 +draft = false +weight = 70 +sort_by = "weight" +template = "docs/page.html" + +[extra] +toc = true +top = false ++++ + +### Configuration + +Configuration values are named, typed values. They are managed by the `ftl config` command-line. + +To declare a configuration value use the following syntax: + +```go +var defaultUser = ftl.Config[string]("default") +``` + +Then to retrieve a configuration value: + +```go +username = defaultUser.Get(ctx) +``` + +### Secrets + +Secrets are encrypted, named, typed values. They are managed by the `ftl secret` command-line. + +Declare a secret with the following: + +```go +var apiKey = ftl.Secret[string]("apiKey") +``` + +Then to retrieve a secret value: + +```go +username = defaultUser.Get(ctx) +``` + +### Transforming secrets/configuration + +Often, raw secret/configuration values aren't directly useful. For example, raw credentials might be used to create an API client. For those situations `ftl.Map()` can be used to transform a configuration or secret value into another type: + +```go +var client = ftl.Map(ftl.Secret[Credentials]("credentials"), + func(ctx context.Context, creds Credentials) (*api.Client, error) { + return api.NewClient(creds) +}) +``` diff --git a/docs/content/docs/reference/start.md b/docs/content/docs/reference/start.md new file mode 100644 index 0000000000..f3859a183e --- /dev/null +++ b/docs/content/docs/reference/start.md @@ -0,0 +1,22 @@ ++++ +title = "Start" +description = "Preparing to use FTL." +date = 2021-05-01T08:20:00+00:00 +updated = 2021-05-01T08:20:00+00:00 +draft = false +weight = 10 +sort_by = "weight" +template = "docs/page.html" + +[extra] +toc = true +top = false ++++ + +## Import the runtime + +Some aspects of FTL rely on a runtime which must be imported with: + +```go +import "github.com/TBD54566975/ftl/go-runtime/ftl" +``` diff --git a/docs/content/docs/reference/types.md b/docs/content/docs/reference/types.md new file mode 100644 index 0000000000..7a811b1bdf --- /dev/null +++ b/docs/content/docs/reference/types.md @@ -0,0 +1,78 @@ ++++ +title = "Types" +description = "Declaring and using Types" +date = 2021-05-01T08:20:00+00:00 +updated = 2021-05-01T08:20:00+00:00 +draft = false +weight = 30 +sort_by = "weight" +template = "docs/page.html" + +[extra] +toc = true +top = false ++++ + +FTL supports the following types: `Int` (64-bit), `Float` (64-bit), `String`, `Bytes` (a byte array), `Bool`, `Time`, `Any` (a dynamic type), `Unit` (similar to "void"), arrays, maps, data structures, and constant enumerations. Each FTL type is mapped to a corresponding language-specific type. For example in Go `Float` is represented as `float64`, `Time` is represented by `time.Time`, and so on. [^1] + +Any Go type supported by FTL and referenced by an FTL declaration will be automatically exposed to an FTL type. + +For example, the following verb declaration will result in `Request` and `Response` being automatically translated to FTL types. + +```go +type Request struct {} +type Response struct {} + +//ftl:verb +func Hello(ctx context.Context, in Request) (Response, error) { + // ... +} +``` + +## Type enums (sum types) + +[Sum types](https://en.wikipedia.org/wiki/Tagged_union) are supported by FTL's type system, but aren't directly supported by Go. However they can be approximated with the use of [sealed interfaces](https://blog.chewxy.com/2018/03/18/golang-interfaces/). To declare a sum type in FTL use the comment directive `//ftl:enum`: + +```go +//ftl:enum +type Animal interface { animal() } + +type Cat struct {} +func (Cat) animal() {} + +type Dog struct {} +func (Dog) animal() {} +``` + +## Value enums + +A value enum is an enumerated set of string or integer values. + +```go +//ftl:enum +type Colour string + +const ( + Red Colour = "red" + Green Colour = "green" + Blue Colour = "blue" +) +``` + +## Type aliases + +A type alias is an alternate name for an existing type. It can be declared like so: + +```go +//ftl:typealias +type Alias Target +``` + +eg. + +```go +//ftl:typealias +type UserID string +``` + +[^1]: Note that until [type widening](https://github.com/TBD54566975/ftl/issues/1296) is implemented, external types are not supported. diff --git a/docs/content/docs/reference/verbs.md b/docs/content/docs/reference/verbs.md new file mode 100644 index 0000000000..93c8f37fa5 --- /dev/null +++ b/docs/content/docs/reference/verbs.md @@ -0,0 +1,46 @@ ++++ +title = "Verbs" +description = "Declaring and calling Verbs" +date = 2021-05-01T08:20:00+00:00 +updated = 2021-05-01T08:20:00+00:00 +draft = false +weight = 20 +sort_by = "weight" +template = "docs/page.html" + +[extra] +toc = true +top = false ++++ + +## Defining Verbs + +To declare a Verb, write a normal Go function with the following signature,annotated with the Go [comment directive](https://tip.golang.org/doc/comment#syntax) `//ftl:verb`: + +```go +//ftl:verb +func F(context.Context, In) (Out, error) { } +``` + +eg. + +```go +type EchoRequest struct {} + +type EchoResponse struct {} + +//ftl:verb +func Echo(ctx context.Context, in EchoRequest) (EchoResponse, error) { + // ... +} +``` + +By default verbs are only [visible](../visibility) to other verbs in the same module. + +## Calling Verbs + +To call a verb use `ftl.Call()`. eg. + +```go +out, err := ftl.Call(ctx, echo.Echo, echo.EchoRequest{}) +``` diff --git a/docs/content/docs/reference/visibility.md b/docs/content/docs/reference/visibility.md new file mode 100644 index 0000000000..ddb0a05cdb --- /dev/null +++ b/docs/content/docs/reference/visibility.md @@ -0,0 +1,44 @@ ++++ +title = "Visibility" +description = "Managing visibility of FTL declarations." +date = 2021-05-01T08:20:00+00:00 +updated = 2021-05-01T08:20:00+00:00 +draft = false +weight = 40 +sort_by = "weight" +template = "docs/page.html" + +[extra] +toc = true +top = false ++++ + +By default all declarations in FTL are visible only to the module they're declared in. The implicit visibility of types is that of the first verb or other declaration that references it. + +## Exporting declarations + +Exporting a declaration makes it accessible to other modules. Some declarations that are entirely local to a module, such as secrets/config, cannot be exported. + +Types that are transitively referenced by an exported declaration will be automatically exported unless they were already defined but unexported. In this case, an error will be raised and the type must be explicitly exported. + +The following table describes the directives used to export the corresponding declaration: + +| Symbol | Export syntax | +| ------------- | ------------------------ | +| Verb | `//ftl:verb export` | +| Data | `//ftl:data export` | +| Enum/Sum type | `//ftl:enum export` | +| Typealias | `//ftl:typealias export` | +| Topic | `//ftl:export` [^1] | + +eg. + +```go +//ftl:verb export +func Verb(ctx context.Context, in In) (Out, error) + +//ftl:typealias export +type UserID string +``` + +[^1]: By default, topics do not require any annotations as the declaration itself is sufficient. diff --git a/docs/sass/.keepme b/docs/sass/.keepme new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/static/.keepme b/docs/static/.keepme new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/templates/.keepme b/docs/templates/.keepme new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/themes/adidoks b/docs/themes/adidoks new file mode 160000 index 0000000000..5c698271c4 --- /dev/null +++ b/docs/themes/adidoks @@ -0,0 +1 @@ +Subproject commit 5c698271c460046034605b743a15196b12e32887