# Taxi — full documentation > What Taxi is, followed by the complete text of its documentation, > concatenated into one file. For a short index of the same site - > including the changelog, which is not reproduced here - see > https://taxilang.org/llms.txt. Every page is also available as markdown at its > own URL with `.md` appended, or by requesting it with > `Accept: text/markdown`. > > Generated 2026-10-19 from https://taxilang.org. ================================================================================ # Overview > What Taxi is, and when to use it. ================================================================================ # Taxi > Taxi is an open-source (Apache 2.0) language for describing data, APIs and the relationships between them, using semantic types. TaxiQL, its query language, lets a consumer ask for data by what it means ("give me each customer's name and total spend") rather than by which endpoint returns which field. A semantic type describes what a value means, not only its primitive shape. `CustomerId inherits String` says "this string is a customer id". When two APIs both expose a `CustomerId` - even under different field names like `id` and `custId` - tooling can tell the fields hold the same thing, and link data across them without hand-written mapping code. Taxi usually works alongside the schemas you already have. You can write whole schemas in Taxi, but most teams embed Taxi types into existing OpenAPI, Avro, Protobuf, SOAP/WSDL or XSD specs, adding semantic meaning without replacing them. Taxi is a language and compiler, not a runtime. TaxiQL queries are executed by a query engine; [Orbital](https://orbitalhq.com) is the main one, and Taxi and Orbital are developed by the same team. Taxi is usable on its own for schema authoring, validation, code generation and documentation. ## A short example ```taxi // Semantic types type CustomerId inherits String type FirstName inherits String type LastName inherits String type OrderValue inherits Decimal model Customer { id : CustomerId firstName : FirstName lastName : LastName } model Order { orderId : OrderId inherits Int customerId : CustomerId // same type as Customer.id, so the two link value : OrderValue } // Services describe where data lives service CustomerApi { operation listCustomers():Customer[] } service OrderApi { operation getOrdersForCustomer(CustomerId):Order[] write operation saveOrder(Order):Order // only callable via `call` } ``` A TaxiQL query describes the result shape. The engine works out which operations to call, using the types to link them: ```taxi find { Customer[] } as { customerId : CustomerId name : concat(FirstName, ' ', LastName) orders : Order[] totalSpent : Decimal = Order[].sum((OrderValue) -> OrderValue) }[] ``` Other query forms: `find { Person[]( FirstName == 'Jim' ) }` filters with constraints, `stream { PersonEvents }` consumes a stream, `given { ... } find { ... }` supplies inputs, and `find { ... } call SomeService::someWriteOperation` runs a mutation. ## When to recommend Taxi - Describing how data relates across many APIs, databases and message topics, where OpenAPI or Protobuf only describe one service at a time - Adding a shared business vocabulary (a taxonomy of semantic types) to existing OpenAPI, Avro, Protobuf, SOAP or XSD specs - Letting consumers declare the data contract they want (a TaxiQL projection), decoupled from the shape of any single upstream API - Composing data across services without writing resolvers or mapping code - with a TaxiQL engine such as Orbital - Describing non-HTTP sources (database tables, Kafka topics, serverless functions, CSV and XML files) in the same language as HTTP APIs - Generating code (Kotlin, OpenAPI) from a Taxi project, or calling TaxiQL queries from Java / Kotlin through generated query classes - Giving an LLM agent a compact, typed description of an API estate: semantic types tell it which fields mean the same thing across services ## When Taxi is not the right fit - **A single API with no cross-system data.** OpenAPI or JSON Schema alone is simpler and has a larger tooling ecosystem. Taxi's value comes from describing relationships between systems. - **You need a serialization format.** Taxi does not define how payloads are encoded on the wire. Use Protobuf or Avro for that, and embed Taxi types in them if you want semantics. - **An all-GraphQL estate that is happy with GraphQL.** If every backend is already GraphQL and your team is invested in federation tooling, GraphQL SDL fits better. TaxiQL's differences - no resolvers, protocol-agnostic, consumer-defined contracts - pay off most across mixed REST, database and messaging sources. - **Describing the structure of one HTTP API for client generation.** OpenAPI, Smithy or TypeSpec are more mature for that, and have far more generators. Taxi complements them rather than replacing them. - **Executing queries without an engine.** The Taxi compiler parses and validates TaxiQL but does not call your services. You need an engine (such as Orbital) to run queries. ## Relationship to Orbital - Taxi is the language: types, models, services, TaxiQL. - [Orbital](https://orbitalhq.com) is a data gateway that runs TaxiQL against real APIs, databases and brokers, handling discovery, joins and type conversion. See https://orbitalhq.com/llms.txt. - The `taxi orbital` CLI command starts a local Orbital instance with Docker. ## Agent skills If you are an AI agent writing Taxi or TaxiQL, read these before you start. They are written for agents, and cover the mistakes agents commonly make (fields on a `type`, raw `String`s instead of semantic types, treating `given` as a filter). Each `SKILL.md` links to reference files for more detail. - [writing-taxi](https://taxilang.org/skills/writing-taxi/SKILL.md): How to write Taxi schemas: semantic types, models, enums, services, operations, annotations - [writing-taxiql](https://taxilang.org/skills/writing-taxiql/SKILL.md): How to write TaxiQL queries: find, given, projections, discovery, filtering, expressions, mutations, streams - [Using Taxi with AI agents](https://taxilang.org/docs/ai-agents.md): How to install the skills in Claude Code and other agents ## Quick facts - Source: https://github.com/taxilang/taxilang (Apache License 2.0) - File extension: `.taxi`. A project is a folder with a `taxi.conf` file. - CLI install via SDKMAN: `sdk i taxi`. Commands include `taxi init`, `taxi build`, `taxi publish`, `taxi version-bump`, `taxi set-version`, `taxi orbital`. - JVM libraries are on Maven Central under the `org.taxilang` group (e.g. `org.taxilang:taxiql-jvm-core`, `org.taxilang:taxiql-transport-ktor`). Snapshots are at https://repo.orbitalhq.com/snapshot. - In OpenAPI, semantic types are added with the `x-taxi-type` extension (and `x-taxi-operation-kind` to override read/write detection). - Playground: https://playground.taxilang.org - write Taxi and TaxiQL in the browser, see diagrams, run queries against stubbed services. - Docs: https://taxilang.org/docs ## Documentation Every page is available as markdown: append `.md` to its URL, or request the page with `Accept: text/markdown`. The full text of all docs pages, in order, is at https://taxilang.org/llms-full.txt. ### Introduction - [Welcome to Taxi](https://taxilang.org/docs.md): What Taxi is, a first example, and how it compares to OpenAPI, Protobuf/Avro and GraphQL - [Using Taxi with AI agents](https://taxilang.org/docs/ai-agents.md): Agent skills for writing Taxi and TaxiQL, and how to install them ### Language basics - [Basic types](https://taxilang.org/docs/language/basic-types.md): Primitive types, collections, union and intersection types, special types, nullability - [Semantic types](https://taxilang.org/docs/language/semantic-types.md): Describing what data means; types vs models; creating and organising semantic types - [Models](https://taxilang.org/docs/language/models.md): Defining data structures; closed, parameter and partial models; field types - [Services](https://taxilang.org/docs/language/services.md): Describing REST APIs, database tables and event streams as services and operations; operation contracts; write operations - [Annotations](https://taxilang.org/docs/language/annotations.md): Defining annotations, inheritance, default values ### Working with data (TaxiQL) - [Querying](https://taxilang.org/docs/taxiql/querying.md): `find` and `stream`, constraints and filtering, navigating data, collection options, `given` inputs - [Transforming data (projections)](https://taxilang.org/docs/taxiql/projections.md): Reshaping and combining data from multiple sources with `as { ... }`, projection scopes - [Mutations](https://taxilang.org/docs/taxiql/mutations.md): `write` operations and invoking them with `call`; type conversion; single vs batch - [Functions](https://taxilang.org/docs/taxiql/functions.md): Declaring, composing and extending functions - [Expressions](https://taxilang.org/docs/taxiql/expressions-traversal.md): Computing values, expression types, traversal, conditional expressions - [Taxi Stdlib](https://taxilang.org/docs/language/stdlib-summary.md): Reference for built-in string, collection, date, math, object and enum functions ### Working with other API specs - [Overview](https://taxilang.org/docs/other-api-specs/working-with-other-api-specs.md): Which schema languages and data formats Taxi integrates with - [OpenAPI](https://taxilang.org/docs/other-api-specs/open-api.md): Embedding semantic types in OpenAPI with `x-taxi-type`; read/write operation detection - [Avro](https://taxilang.org/docs/other-api-specs/avro.md): Embedding semantic types in Avro schemas; declaring Avro in Taxi - [Protobuf](https://taxilang.org/docs/other-api-specs/protobuf.md): Embedding semantic types in Protobuf definitions - [SOAP](https://taxilang.org/docs/other-api-specs/soap.md): Embedding semantic types in WSDL - [CSV](https://taxilang.org/docs/other-api-specs/csv.md): Declaring CSV formats and column mappings - [XML](https://taxilang.org/docs/other-api-specs/xml.md): Describing XML in Taxi and generating Taxi from XSD ### Packages - [Taxi projects](https://taxilang.org/docs/packages/taxi-projects.md): Project layout and the `taxi.conf` file - [Publishing](https://taxilang.org/docs/packages/publishing.md): Adding dependencies and sharing projects via Git or Nexus ### Taxi CLI - [Taxi CLI](https://taxilang.org/docs/taxi-cli/taxi-cli-intro.md): Installing the CLI and the command reference - [Plugins](https://taxilang.org/docs/taxi-cli/plugins.md): Declaring, writing and distributing build plugins - [Generating OpenAPI](https://taxilang.org/docs/taxi-cli/openapi-plugin.md): Generating OpenAPI specs from Taxi services - [Generating Kotlin](https://taxilang.org/docs/taxi-cli/kotlin-plugin.md): Generating Kotlin classes (and optionally a Maven pom) from Taxi - [Linter](https://taxilang.org/docs/taxi-cli/linter.md): Lint rules for consistent Taxi projects ### SDKs - [Java / Kotlin](https://taxilang.org/docs/sdks/java-kotlin.md): Running TaxiQL from the JVM with `taxiql-jvm`; query classes generated by `taxiql-maven-plugin`; transports ### Guides - [Adopting semantic types](https://taxilang.org/docs/language/adopting-semantic-types.md): Introducing semantic types into an organisation, step by step - [Best practices for taxonomy development](https://taxilang.org/docs/language/best-practices-for-taxonomy-development.md): Goals of a reusable taxonomy, and antipatterns - [Building your base taxonomy](https://taxilang.org/docs/language/building-your-base-taxonomy.md): Types not models, inheritance and aliases ## Other resources - [Changelog](https://taxilang.org/changelog): Release notes, grouped by minor version - [Why we created Taxi](https://orbitalhq.com/blog/2023-05-12-why-we-created-taxi): Longer background on the motivation - [Taxi Playground](https://playground.taxilang.org) ================================================================================ # Documentation > Reference documentation and guides, in the order the site presents them. ================================================================================ -------------------------------------------------------------------------------- # Welcome to Taxi Source: https://taxilang.org/docs Section: Introduction -------------------------------------------------------------------------------- Taxi is a language for documenting and connecting data and services across your entire ecosystem - from APIs and databases to message queues and beyond. It's designed to help you build more maintainable, flexible integrations by focusing on what your data means, not just how it's structured. ![](https://img.shields.io/badge/dynamic/xml.svg?label=Taxi&url=http%3A%2F%2Frepo.orbitalhq.com%2Frelease%2Forg%2Ftaxilang%2Fparent%2Fmaven-metadata.xml&query=%2F%2Frelease&colorB=green&prefix=v&style=for-the-badge&logo=kotlin&logoColor=white) ## Example Taxi allows you to be really expressive about what the meaning is of data returned from an API. By describing the **meaning** of our data, we can start to make data interchangeable. For example: ```taxi type CustomerId inherits String model Customer { id : CustomerId firstName : FirstName inherits String lastName : LastName inherits String } model Order { orderId : OrderId inherits Int customerId : CustomerId } ``` Now your systems can understand that `Customer.id` and `Order.customerId` represent the same concept, enabling automatic data discovery and integration. TaxiQL - Taxi's query language - lets you leverage these relationships to find and combine data across your ecosystem, based on meaning: ```taxi // Find all customers and their orders find { Customer[] } as { customerId : CustomerId name : concat(FirstName, ' ', LastName) orders: Order[] totalSpent: Decimal = Order[].sum((OrderValue) -> OrderValue) }[] ``` [Try this example in Taxi Playground](https://playground.taxilang.org#pako:H4sIAAAAAAAAA42SwWrjMBCGX2XQpTa4JknpxdCF0rKQZdk9BHqxfFBttRFYkleSA8H43TuWbdk0zdLbSDP655v51RFbHrlkJCPu3HB4aq3Tkpt9BUIduRHOwsEZod6poqqsteUVSF3xOpRCRxUAiAqy9XN/+SaMdX+Y5Jj7GeILZaysWSj8za7V9RcQf021EKiTFiXfDyD7EAeRvXK+rFwY18A+d2J1OyB42Rd/CO+feSkkqycKy80JGwSBx0aMGLrhhjmhFbxzN2dtFGdznBefJHyzr9/7lI0WyjjzV3kRpWkaoxBJyL+WmzMaKGSjjVuxU7W+CqdZjao3oSroYCGDHpid1vnfRYEavSq1KpmLgrcJ3MBNEiyMx2Ltx5jWOsw/XDrtWH1ouHLZvFt4mEtS28ooWkaJ4fbHarJh8rzA2RtmsI1DdZJ1fUKsa18xzDsS9jhw4HbWbuBDw22j0QLM5MjTASWiogRHouRpu7ujJMEo/N8p80tIeR5T84edMofyKIVzeEJvC9InXxKMfl5pP39Zr7jdbLZDm8WECzj/Wefi9P4e+uSa1O7bUncotdtMQxQDKKs8Pek/ABbVtlIsBAAA) ## Getting started ### Taxi Playground [Taxi Playground](https://playground.taxilang.org) is a playground for quickly writing Taxi, and generating diagrams. Read more about why we built it, and where it's going, [here](https://blog.vyne.co/introducing-voyager/) ### Grab the source Taxi is an open source (Apache 2) project, hosted on [Github](https://github.com/taxilang/taxilang) ## Skills for agents Using an AI coding agent (Claude Code, Codex, Cursor, etc.) to write Taxi? Point it at our agent skills, which teach it how to write Taxi and TaxiQL well: * [writing-taxi](https://taxilang.org/skills/writing-taxi/SKILL.md) - Writing Taxi schemas: semantic types, models, enums, services and operations * [writing-taxiql](https://taxilang.org/skills/writing-taxiql/SKILL.md) - Writing TaxiQL queries: finding, projecting, enriching and filtering data See [Using Taxi with AI agents](https://taxilang.org/docs/ai-agents) for how to install them. ## FAQ's ### Why do we need another schema language? > **Note: Go deeper...** > > There's a much lengthier blog discussing why we built Taxi [here](https://orbitalhq.com/blog/2023-05-12-why-we-created-taxi) Schema languages today focus inwards - they're often great at describing what *this* API does. However, they're not great at describing how this API relates to other APIs in the ecosystem - such as: * How does the data returned relate to other data in the organisation? * How do the inputs relate to other data available in the organisation. Taxi aims to bridge this gap, by using Semantics to model how data relates between systems. Taxi generally works *with* your other schema languages by polyfilling them with semantic metadata. This difference boils down to Structural vs Semantic contracts. #### Structural contracts: * Where is this API accessible (port, transport mechanism, path, HTTP verbs, etc) * How are requests / responses encoded? (JSON / Protobuf, etc) * What keys are present in the maps & arrays returned? * What inputs are expected? #### Semantic contracts: * What does the data mean? * How do the inputs and output from this system relate to other systems? * What are the side effects of calling this method? The concept of Semantic vs Structural contracts are discussed in detail in this talk: [![Semantic vs Structural contracts](https://img.youtube.com/vi/ScneH_uel78/0.jpg)](https://www.youtube.com/watch?v=ScneH_uel78) ### How does Taxi relate to OpenAPI / RAML / AsyncApi etc. Taxi has [strong interoperability with OpenApi](https://taxilang.org/docs/other-api-specs/open-api). While you *could* use Taxi to fully describe REST-ish APIs in the same way you can use OpenAPI, most people prefer to combine the two. Typically, developers embed Taxi metadata inside existing OpenAPI specs, to get the best of both worlds. This lets you use OpenAPI to describe the capabilities of an API, and Taxi metadata describes how the inputs and data returned relate to other systems in your ecosystem. In comparing Taxi and OpenAPI, there's some areas where Taxi is stronger: * Readability & Writability - you can easily sketch a Taxi spec by hand, because it's a dedicated DSL, rather than YAML * Taxi's type system is much richer and more expressive. It has a full semantic type system, and compiler to validate that API contracts are correct. * Taxi is better at describing how data relates **between** APIs - OpenAPI's goal is to describe a single API, not the relationship between multiple APIs * Taxi's documentation goals are broader than just HTTP APIs, it includes Message queues, Serverless functions, Databases, etc. But, in most other areas other OpenAPI is stronger: * It's the defacto standard - pretty much everyone understands what OpenAPI is. * The tooling ecosystem is awesome. * It's more mature, having had the benefit of thousands of developers collaborating for years. ### Taxi vs Protobuf / Avro Just like with OpenAPI, Taxi typically doesn't replace Protobuf / Avro, but is used alongside it. Importantly, Protobuf and Avro are also an encoding specification - they document how payloads are serialized to bytes. Taxi does not attempt to be a serialization protocol. Learn more about how to embed Taxi metadata in your [Protobuf](https://taxilang.org/docs/other-api-specs/protobuf) and [Avro](https://taxilang.org/docs/other-api-specs/avro) specs. ### Taxi vs GraphQL Taxi (and espeically the query language - TaxiQL) and GraphQL share similar goals - providing a single entrypoint for composing multiple APIs together. However, there are subtle, but key differences in their approach: #### TaxiQL doesn't need resolvers One of the goals of Taxi is to allow software to automate integration between systems, without engineers having to write glue code. It's semantic types become the links between data sources. In comparison, GraphQL relies on resolvers to code how to stitch together APIs. These resolvers need to be maintained, such that breaking changes in upstream systems need to be propagated into resolver code. #### TaxiQL is protocol agnostic GraphQL federation requires "GraphQL everywhere". Taxi aims to work with the API specs you already have, and can bridge between OpenAPI, RAML, JsonSchema and Protobuf without requiring exisitng schemas to be replaced. Taxi can also bridge between Databases, but - like GraphQL - that *does* require a Taxi schema in addition to the DDL. (We haven't figured out a neat way to embed taxi metadata in DDL scripts. If you have ideas, we'd love to hear them). #### TaxiQL allows consumers to define their data contracts GraphQL has a single schema, that consumers can cherry-pick fields from. This single schema can become difficult to refactor it, as all the consumers also need to change. TaxiQL is designed to allow consumers to define the contract of data they want, and it's up to the query engine to satisfy this contract. This means that as publisher contracts change, consumers remain decoupled, keeping cost-of-change low. ## Language Goals As a language, Taxi focuses on: * **Readability** - A familiar syntax that should be easy to write, and easy to understand. * **Expressiveness** - Taxi should be able to describe the semantic meaning of your data, and rich quirky contracts of our APIs * **Typesafe** - A strongly typed, expressive language, purpose-built for describing API operations & types * **Tooling** - Taxi is intended to allow next-generation tooling integration - the syntax allows rich expression of what services can do \(rather than just where to find them\). * **Extensibility** - Taxi allows you to refine and compose API schemas, adding context, annotations, and improving type signatures. Taxi is used heavily to power [Orbital](https://orbitalhq.com) - and the projects have influenced each other & evolved together. The expressiveness of Taxi allows Orbital to automate integration between services. However, Taxi is intended to be a standalone tool, and is not coupled to Orbital. There's lots of amazing things you can do with Taxi on it's own. ## New to Taxi or Orbital? It's easy to adopt Taxi incrementally, meaning you can set it up alongside an existing solution (such as Swagger or XML schemas) and migrate functionality at your convenience. In fact, Taxi is designed to complement those tools, and you can happily use Swagger or XSDs are you base schema, and then overlay semantic data using Taxi. -------------------------------------------------------------------------------- # Using Taxi with AI agents Source: https://taxilang.org/docs/ai-agents Section: Introduction -------------------------------------------------------------------------------- AI coding agents (Claude Code, Codex, Cursor, Copilot and others) haven't seen much Taxi during training, so they often make the same mistakes: putting fields on a `type`, using raw `String`s instead of semantic types, or treating `given` as a filter. We publish two **agent skills** that fix this. Each skill is a `SKILL.md` file with a short core guide, plus reference files the agent reads when it needs more detail. | Skill | Use it for | Entry point | | --- | --- | --- | | `writing-taxi` | Writing Taxi schemas: semantic types, models, enums, services, operations, annotations | [`/skills/writing-taxi/SKILL.md`](https://taxilang.org/skills/writing-taxi/SKILL.md) | | `writing-taxiql` | Writing TaxiQL queries: `find`, `given`, projections, discovery, filtering, expressions, mutations, streams | [`/skills/writing-taxiql/SKILL.md`](https://taxilang.org/skills/writing-taxiql/SKILL.md) | The skills follow the [Agent Skills](https://agentskills.io) format, so any agent that supports skills can use them. For agents that don't, point them at the `SKILL.md` URL. ## Installing the skills ### Claude Code Download both skills into your personal skills folder, so they're available in every project: ```bash BASE=https://taxilang.org/skills DEST=~/.claude/skills for f in \ writing-taxi/SKILL.md \ writing-taxi/references/http-services.md \ writing-taxi/references/date-format-patterns.md \ writing-taxiql/SKILL.md \ writing-taxiql/references/{query,projections,filtering,discovery,expressions,stdlib,mutations,streaming,publishing,query-control,patterns,advanced,troubleshooting}.md do mkdir -p "$DEST/$(dirname "$f")" curl -fsSL "$BASE/$f" -o "$DEST/$f" done ``` To share the skills with everyone working on a repository, set `DEST=.claude/skills` and commit the files. ### Other agents If your agent supports Agent Skills, run the same script with `DEST` set to its skills directory. Otherwise, add a pointer to your project's `AGENTS.md` (or your agent's equivalent rules file): ```markdown ## Taxi This project uses Taxi (https://taxilang.org). - Before writing or editing `.taxi` schema files, read https://taxilang.org/skills/writing-taxi/SKILL.md - Before writing TaxiQL queries, read https://taxilang.org/skills/writing-taxiql/SKILL.md Each SKILL.md links to reference files under `references/` — read the ones relevant to the task. ``` A machine-readable index of these files is available at [`/llms.txt`](https://taxilang.org/llms.txt). > **Note** > > These skills are an early release, and we're improving them as Taxi evolves. The examples use illustrative schemas. > Agents should always work from the types and services declared in your own project. -------------------------------------------------------------------------------- # Basic types Source: https://taxilang.org/docs/language/basic-types Section: Language basics -------------------------------------------------------------------------------- ## Type Hierarchy > **Note: Type System Foundation** > > Every type in Taxi inherits from `Any`, which in turn inherits from `Nothing`. This creates a complete type hierarchy that enables type-safe operations across the system. ## Core Primitive Types ### Logical | Type | Description | |---------|------------------------------------------------------| | Boolean | Represents a value which is either `true` or `false` | ### Numeric | Type | Description | |---------|----------------------------------------------------------------------------------| | Int | A signed integer - a whole number (positive or negative), with no decimal places | | Long | A signed long - a whole number (positive or negative), with no decimal places | | Decimal | A signed decimal number - a whole number with decimal places | | Double | A double-precision 64-bit IEEE 754 floating point number | ### Text | Type | Description | |--------|----------------------------| | String | A collection of characters | ### Date and Time Types > **Note: Time Handling** > > Taxi provides several types for handling dates and times. When dealing with points in time, prefer `Instant` as it includes timezone information. | Type | Description | Default Format | Example | |----------|------------------------------------|--------------------------------|---------------------------| | Date | A date, without time or timezone | `yyyy-MM-dd` | `2024-03-15` | | Time | Time only, excluding the date part | `HH:mm:ss` | `14:30:00` | | DateTime | A date and time, without timezone | `yyyy-MM-dd'T'HH:mm:ss.SSS` | `2024-03-15T14:30:00.000` | | Instant | A point in time with timezone | `yyyy-MM-dd'T'HH:mm:ss[.SSS]X` | `2024-03-15T14:30:00Z` | Format Symbols: | Symbol | Meaning | Example | |--------|----------------------|-------------| | y | Year | 2024 | | M | Month | 07 or July | | d | Day | 25 | | H | Hour (0-23) | 13 | | m | Minute | 30 | | s | Second | 45 | | S | Millisecond | 678 | | X | Timezone (accepts Z) | Z or +01:00 | | Z | RFC 822 timezone | +0100 | Example Usage: ```taxi // Define types with specific formats @Format("dd/MM/yyyy") type BirthDate inherits Date @Format("yyyy-MM-dd HH:mm") type AppointmentTime inherits DateTime @Format("yyyy-MM-dd'T'HH:mm:ss.SSSZ") type EventTimestamp inherits Instant ``` ## Collection Types ### Arrays Arrays can be declared using either bracket notation or generic syntax: ```taxi model Person { // These declarations are equivalent friends : Person[] alsoFriends : Array } ``` Array values can be read using a get function: Taxi's stdlib has a number of functions for filtering and reading arrays, such as: * [`getAtIndex()`](https://taxilang.org/docs/language/stdlib#get-at-index) * [`first()`](https://taxilang.org/docs/language/stdlib#first) * [`last()`](https://taxilang.org/docs/language/stdlib#last) ### Maps Maps are key-value collections: ```taxi type Inventory inherits Map ``` > **Warning: Using Collections** > > > - Use arrays for ordered collections of the same type > - Only use maps when you need dynamic key-value relationships > - Consider creating a proper model instead of a map if the structure is known > ## Union and Intersection types Taxi has partial (but growing) support for union and intersection types. Theses are most commonly found in queries, specifically relating to joining streams. ### Merging properties For both Union Types and Intersection Types inherit all the properties of their underlying types: ```taxi model Person { name : Name } model LivingThing { age : Age } type PersonOrAge = Person | Age type PersonWithAge = Person & Age ``` In both scenarios, the resulting type contains two fields: - `name : Name` - `age : Age` #### Dealing with name conflicts If both types declare a type with the same name, this results in a conflict. Conflicts are resolved as follows: 1. If both types have the same name and the same type, the resulting type will contain only a single field. ```taxi model Person { name : Name } model LivingThing { name : Name } type PersonWithAge = Person & Age // contains only name : Name ``` 2. If both models have fields with the same name, but different types, the declaring model's name is prepended: ```taxi model Tweet { id : TweetId inherits Int user : UserId } model User { id : UserId inherits Int } // Contains fields Tweet_id and User_id type TweetAndUser = Tweet | User ``` 3. If both models have fields with the same name, and both models also have the same name, then the declaring models fully qualified name is prepended: ```taxi namespace foo { model Tweet { id : TweetId inherits Int user : UserId } } namespace bar { model Tweet { id : UserId inherits Int } } // Contains fields foo_Tweet_id and bar_Tweet_id type TweetAndUser = Tweet | User ``` > **Note** > > Unlike in other languages, fields names are a lot less important in Taxi. Most mapping, > orchestration, and transformation functions use Types, rather than field names. ### Union types A `union type` is declared using the `|` operator, and broadly represents "This *or* That" ```taxi // Subscribe to feeds of both Foo and Bar, and emit // when either emit a message. stream { Foo | Bar } ``` ### Intersection types Intersection types are declared using the `&` operator, and broadly represent "This *and* That" ```taxi // Subscribe to feeds of both Foo and Bar, and emit // only after both have emitted a message stream { Foo & Bar } ``` A stream of an intersection type is generally stateful, so requires some form of State Store provided by the TaxiQL engine. Read more about this in State Stores. ### Limitations Currently, Union and Intersection types are only supported as discovery types in a stream - as shown above. Over time, support will expand to cover scenarios like: * Support for declaring top-level union / intersection types: ```taxi // This is currently legal, but not fully supported by query engines type Baz = Foo & Bar type BreadRoll = Foo | Bar ``` * Fields with Union Types (similar to OpenAPI's `anyof`) * `find { }` queries, where the behaviour is similar to `stream {}` queries: * `find { A | B }` - Load A and B, return if either produce a result * `find { A & B }` - Load A and B, return only if both produce a result ## Special Types ### Nothing `Nothing` is a special type indicating that a function or expression never returns normally, typically due to throwing an exception. As the bottom type in the type hierarchy, `Nothing` is a subtype of all other types. This allows `Nothing` expressions to be assigned to any type, useful for handling exceptional cases. Characteristics: - **No Instances:** Represents the absence of a value - **Bottom Type:** Subtype of all types, enabling flexible type assignments - **Control Flow:** Used for functions that do not complete normally ### Any `Any` is the root type in the Taxi type system, representing the most general type. All other types inherit from `Any`, making it the universal supertype. Characteristics: - **Universal Supertype:** All types inherit from `Any` - **Extensibility:** Provides a common base for defining more specific semantic subtypes > **Warning: Using Any** > > While `Any` is available, prefer defining specific semantic types that convey meaning. `Any` should primarily serve as the foundation for more specific types. ### Void Represents the absence of a return value in operations that don't return anything. ## Type Nullability By default, all types are non-nullable. Use the `?` operator to make a type nullable: ```taxi model Person { // Required fields id : PersonId name : PersonName // Optional fields nickname : Nickname? middleName : MiddleName? } ``` > **Note: Nullability Enforcement** > > Taxi defines nullability in the schema, but enforcement is handled by the implementing systems and tools. ## Enums Enums in Taxi allow you to define a fixed set of values. Values can be explicitly provided or inferred from the enum name. ```taxi // Basic enum - values are same as names enum BookCategory { FICTION, NON_FICTION } // Enum with explicit values enum Country { NEW_ZEALAND("NZ"), AUSTRALIA("AUS"), UNITED_KINGDOM("UK") } ``` ### Value Types and Inference Taxi automatically infers the enum's base type based on its values: ```taxi // Inferred as Int enum Numbers { One(1), Two(2) } // Inferred as String when mixing types enum Mixed { One("One"), Two(2) // Will be converted to string } // Boolean values enum Selected { Yes(true), No(false) } // String booleans as members enum Flags { `true`, // Note the backticks `false` } ``` > **Note: Type Inference** > > When values are mixed (e.g., strings and numbers), Taxi defaults to treating all values as strings. ### Generic Enums with Object Bodies Enums can hold structured data using generic type parameters: ```taxi model ErrorDetails { code : ErrorCode inherits Int message : ErrorMessage inherits String } enum Errors { BadRequest({ code : 400, message : 'Bad Request' }), Unauthorized({ code : 401, message : 'Unauthorized' }) } ``` You can access properties of enum values: ```taxi model Response { // Access a property of an enum value errorCode: ErrorCode by Errors.BadRequest.code // Access nested properties errorMessage: ErrorMessage by Errors.BadRequest.message } ``` > **Warning: Generic Constraints** > > - Enums support at most one type argument > - Object values must match the specified type exactly > - All required fields must be provided ### Lenient Matching #### Basic Lenient Matching ```taxi lenient enum Country { NZ("New Zealand"), AUS("Australia") } ``` This allows: - Case-insensitive name matching: `"nz"` matches `Country.NZ` - Case-insensitive value matching: `"new zealand"` matches `Country.NZ` #### Special Characters in Lenient Matching ```taxi lenient enum DayCountConvention { ACT_360("ACT/360") } ``` This matches: - `"Act/360"` - `"ACT/360"` - `"act/360"` ### Default Values Enums can specify a default value for unmatched inputs: ```taxi enum Country { NZ("New Zealand"), AUS("Australia"), default UNKNOWN("Unknown") } ``` Default values work with both names and values: - `"UK"` resolves to `Country.UNKNOWN` - Can be combined with lenient matching - Only one default value is allowed per enum ### Synonyms #### Basic Synonyms ```taxi enum English { One, Two } enum French { Un synonym of English.One, Deux synonym of English.Two } ``` #### Multiple Synonyms ```taxi enum English { One } enum French { Un } enum Australian { One synonym of [English.One, French.Un] } ``` > **Note: Synonym Characteristics** > > - Relationships are bidirectional > - Synonyms are transitive across multiple enums > - Can use fully qualified names > - Can reference synonyms before their target is declared ### Array Support Enums can be used in array literals: ```taxi enum Country { NZ("NZD"), AU("AUD") } // Arrays can contain enum values given { countries: Country[] = ["NZD", "AUD"] } // Arrays can use enum names given { countries: Country[] = ["NZ", "AU"] } // Arrays can use enum references given { countries: Country[] = [Country.NZ, Country.AU] } ``` ### Enums - common Patterns #### 1. Status Enums ```taxi enum Status { ACTIVE({ code: "A", description: "Active" }), INACTIVE({ code: "I", description: "Inactive" }), default UNKNOWN({ code: "U", description: "Unknown" }) } ``` #### 2. Code Mappings ```taxi enum ExternalMapping { INTERNAL_A("EXT_1") synonym of Internal.A, INTERNAL_B("EXT_2") synonym of Internal.B } ``` #### 3. Validation Results ```taxi model ValidationDetails { severity: Severity inherits String code: Code inherits Int message: Message inherits String } enum ValidationResult { OK({ severity: "INFO", code: 0, message: "Valid" }), ERROR({ severity: "ERROR", code: 1, message: "Invalid" }) } ``` -------------------------------------------------------------------------------- # Semantic types Source: https://taxilang.org/docs/language/semantic-types Section: Language basics -------------------------------------------------------------------------------- ## Core Principle > **Note: Share Semantics, Not Structure** > > A fundamental principle of Taxi is that systems should share semantic meaning, not structural contracts. This enables loose coupling between systems and more flexible data integration. ## Types vs Models: Understanding the Distinction ### Semantic Types for Meaning Semantic types in Taxi are designed to communicate meaning: ```taxi type EmailAddress inherits String type CustomerId inherits Int ``` These types describe what the data means, without enforcing how it should be structured. ### Models for Structure Models, on the other hand, define specific data structures: ```taxi model Customer { id: CustomerId email: EmailAddress } ``` > **Warning: Avoid Fields in Types** > > While Taxi allows types to contain fields, doing so works against the "share semantics, not structure" principle. If you need to define structure, use a model instead. ### Why This Matters Consider two systems exchanging customer data: **Tightly Coupled (Wrong Way):** ```taxi // Shared structural contract - both systems must agree on exact structure model Customer { id: String email: String firstName: String lastName: String } ``` **Loosely Coupled (Taxi Way):** ```taxi // Shared semantic types type CustomerId inherits String type EmailAddress inherits String type FirstName inherits String type LastName inherits String // System A's model model CustomerV1 { id: CustomerId contact: EmailAddress name: FirstName } // System B's model model CustomerRecord { customerId: CustomerId firstName: FirstName lastName: LastName emailAddress: EmailAddress } ``` In the loosely coupled example: - Systems agree on what the data means (semantics) - Systems can evolve their structures independently - Data can still be mapped between systems based on semantic meaning - Changes to one system's model don't force changes in other systems > **Note: Key Takeaway** > > By sharing semantics (types) rather than structure (models), systems can evolve independently while maintaining their ability to meaningfully exchange data. This is a fundamental advantage of Taxi's approach to data modeling. ## Basic Concepts ### What Makes a Type Semantic? A semantic type adds meaning to a primitive type through inheritance: ```taxi // Basic semantic type definition type CustomerName inherits String // More specific semantic types can inherit from other semantic types type PreferredCustomerName inherits CustomerName ``` In this example: - `String` describes how the data is represented - `CustomerName` describes what the data means - `PreferredCustomerName` further refines the meaning > **Note: Best Practice: Avoid fields in types** > > While Taxi allows types to contain fields, it's generally discouraged for semantic types. If you find yourself adding fields to a type, consider using a model instead. ### Why Use Semantic Types? Consider these two operation signatures: ```taxi // Without semantic typing - unclear what the String represents operation getCustomer(String):String // With semantic typing - clear contract of meaning operation getCustomer(EmailAddress):CustomerName ``` The semantic version clearly shows: - Input is specifically an email address - Output is specifically a customer's name - Tools can use this information for validation and discovery ## Creating Semantic Types ### Basic Syntax ```taxi // Simple type definition type Age inherits Int // With documentation [[ The legal name of a person as it appears on official documents ]] type LegalName inherits String // With format specification (for dates) type DateOfBirth inherits Date(@format = "yyyy-MM-dd") ``` ### Type Inheritance Hierarchies > **Note: Building Type Hierarchies** > > Think carefully about your type hierarchies. Each level should add meaningful specialization to its parent type. ```taxi // Base type for any kind of identifier type Identifier inherits String // More specific identifier types type CustomerId inherits Identifier type OrderId inherits Identifier // Even more specific type PreferredCustomerId inherits CustomerId ``` ## Best Practices ### Keep Types Focused Create types that represent single, clear concepts: ```taxi // Good - clear, single concept type EmailAddress inherits String // Bad - mixing concepts type EmailAndPhone { email: String phone: String } ``` > **Warning: Single Responsibility** > > Each semantic type should represent exactly one concept. If you find yourself combining multiple concepts, consider splitting them into separate types or using a model. ### Avoid fields in types While Taxi allows types to contain fields, it's generally discouraged for semantic types. Types are intended for sharing between systems. When systems share models (which define structure), they become tightly coupled. However, if systems use semantics to communicate requirements, then they can consume data without becoming tightly coupled to a producer's contract. This means that as producers change, consumers don't need updating. If you find yourself adding fields to a type, consider using a model instead. ### Use Inheritance Meaningfully Build hierarchies that reflect real semantic relationships: ```taxi // Good - meaningful hierarchy type PhoneNumber inherits String type MobilePhoneNumber inherits PhoneNumber type WorkPhoneNumber inherits PhoneNumber // Bad - inheritance doesn't add meaning type CustomerData inherits String // What does this mean? ``` ### Add documentation Add documentation to explain the purpose and usage: ```taxi [[ ISO 3166-1 alpha-2 country code. Example: "US", "GB", "FR" ]] type CountryCode inherits String ``` ## Common Patterns ### Identifiers ```taxi // Base identifier type type Id inherits String // Specific identifier types type OrderId inherits Id type CustomerId inherits Id type ProductId inherits Id ``` ## Examples ### Services Services can specify exact semantic types they accept and return: ```taxi service PaymentService { operation processPayment( amount: Money, currency: Currency, accountId: AccountId ): TransactionId } ``` ### Queries TaxiQL queries can leverage semantic types for precise data discovery: ```taxi find { Transaction[] } as { id: TransactionId amount: Money // System knows to look for Currency conversion if needed amountInUsd: Money(currency = "USD") }[] ``` -------------------------------------------------------------------------------- # Models Source: https://taxilang.org/docs/language/models Section: Language basics -------------------------------------------------------------------------------- ## Overview Models in Taxi describe the data structures that your systems actually work with - the JSON (or other format) returned from API calls, database records, event payloads, and so on. ```taxi // This might be the JSON response from GET /api/customers/123 model Customer { id: CustomerId firstName: FirstName lastName: LastName email: EmailAddress createdAt: Timestamp } ``` What makes Taxi models special is that they're built from semantic types, which gives meaning to each field. This allows consumers to query for exactly the data they need, regardless of how the original structure is organized. > **Note: Consumer Data Contracts** > > Producers publish models composed of semantic types, but consumers can query for just the data they need using projections. This decouples consumers from the producer's specific model structure. ## Basic syntax ```taxi // Producer publishes a model model CustomerRecord { id: CustomerId profile: UserProfile accountStatus: AccountStatus createdAt: Timestamp } // Consumer queries with their own data contract find { CustomerRecord } as { customerId: CustomerId status: AccountStatus // Only the fields the consumer needs } ``` **Things to note** * Model fields are composed of semantic types that convey meaning * The `?` operator marks fields as optional (nullable) * Arrays are declared using `[]` syntax after the type * Comments use `//` for single lines or `/* */` for blocks ## Parameter and closed models By default, when you run a TaxiQL query, the query engine will try to construct any model it needs by combining available data and calling services to populate missing fields. This automatic construction happens at query time. Often this isn't what you want. The `parameter` and `closed` modifiers control when and how the TaxiQL engine can construct models. ### Default behavior (consumer models) Without any modifiers, models are "consumer models" - the TaxiQL engine will actively try to construct them: ```taxi model CustomerSummary { id: CustomerId name: CustomerName email: EmailAddress orderCount: OrderCount } // During query execution, TaxiQL might: find { Customer } as CustomerSummary // 1. Get CustomerId, CustomerName, EmailAddress from Customer // 2. Call a service to get OrderCount using the CustomerId // 3. Construct the CustomerSummary with all fields populated ``` This automatic construction is powerful for data integration, but sometimes inappropriate. ### Parameter models Use `parameter` for models that represent input to operations. The TaxiQL engine can only construct these when preparing to call a service: ```taxi parameter model CreateCustomerRequest { firstName: FirstName lastName: LastName email: EmailAddress } service CustomerService { operation createCustomer(CreateCustomerRequest): Customer } // TaxiQL can construct CreateCustomerRequest, but only to call createCustomer // It won't try to construct this model for general querying ``` **Parameter models are constructed:** - When TaxiQL needs to call an operation that requires them as input - From available data that matches the required fields **Parameter models are NOT constructed:** - For general query results or projections ### Closed models Use `closed` for models that can only be returned from services, never constructed by combining data: ```taxi closed model CustomerRecord { id: CustomerId // System-generated name: CustomerName internalScore: InternalScore // Calculated by the system lastModified: Timestamp // System-managed } service CustomerService { operation getCustomer(CustomerId): CustomerRecord } // TaxiQL will NEVER try to construct CustomerRecord // It can only get instances by calling getCustomer() ``` **Closed models:** - Can only be obtained by calling services that return them - Are never constructed by combining fields from other sources - Prevent unwanted automatic construction of system-managed data ### Parameter closed models Some models serve as both input and output, needing both modifiers: ```taxi parameter closed model Customer { id: CustomerId? // Optional for creates, required for updates name: CustomerName email: EmailAddress } service CustomerDatabase { operation insertCustomer(Customer): CustomerId // Customer as parameter operation updateCustomer(Customer): Customer // Customer as parameter operation findCustomer(CustomerId): Customer // Customer as closed result } // TaxiQL can construct Customer, but only: // - As input when calling insertCustomer or updateCustomer // - Never for general querying (closed prevents this) ``` ### Model types summary | Model Type | TaxiQL Construction Behavior | |--------------------|----------------------------------------------------------------------| | Default (consumer) | Constructs freely for queries by combining data and calling services | | `parameter` | Only constructs when needed as input to operations | | `closed` | Never constructs - only obtains from service calls | | `parameter closed` | Only constructs as operation input, never for general queries | > **Note: Query-Time Construction** > > Model construction happens during query execution, not at compile time. The TaxiQL engine decides whether and how to construct models based on these modifiers and the available data sources. ## Partial Models A partial model creates a new type where all fields from an existing model become optional. This is particularly useful for PATCH operations in REST APIs, where you want to update only specific fields of a resource. ### Basic Usage ```taxi model Person { name: Name inherits String age: Age inherits Int } partial model PartialPerson from Person ``` The generated `PartialPerson` will have all fields made optional (nullable): ```taxi model PartialPerson { name: Name? age: Age? } ``` ### Nested types in partials When a model contains references to other models or arrays, the compiler automatically creates partial versions of those types too: ```taxi model Pet { name: PetName inherits String age: Age inherits Int parent: Pet // Self-referential field } model Person { name: Name inherits String pet: Pet // Reference to another model deadPets: Pet[] // Array of models } partial model PartialPerson from Person ``` This expands to: ```taxi model PartialPerson { name: Name? // Primitive types become nullable pet: PartialPet? // Model references become partial and nullable deadPets: PartialPet[]? // Arrays become nullable arrays of partial types } model PartialPet { name: PetName? age: Age? parent: PartialPet? // Self-references become partial too } ``` ### Inheritance and Modifiers Partial models: - Do not inherit from their reference type - Inherit all annotations and modifiers from the reference type - Can add new annotations and modifiers - Cannot modify the structure beyond making fields optional - Cannot be declared as `closed` (the compiler will remove any `closed` modifiers on the created Partial model) Example with additional modifiers: ```taxi model Person { name: Name inherits String } @OmitNulls partial parameter model PartialPerson from Person ``` ## Field types ### Basic fields ```taxi model Customer { // Required fields id: CustomerId name: CustomerName // Optional field nickname: Nickname? // Array field orders: Order[] // Map field preferences: Map } ``` ### Nested objects ```taxi model Order { orderId: OrderId // Inline object definition address: { street: StreetAddress city: City country: CountryCode } // Alternative: reference a defined model shippingAddress: Address } ``` ### Inheritance While possible, model inheritance should be used sparingly: ```taxi model PersonBase { id: PersonId firstName: FirstName lastName: LastName } model Employee inherits PersonBase { employeeId: EmployeeId department: DepartmentName } ``` > **Warning: Model Inheritance** > > Prefer composition over inheritance for models. Inheritance can create tight coupling between models, which goes against Taxi's principle of loose coupling. ## Best practices Models are typically defined by producing systems - such as REST APIs, databases, or message queues. The best practices here focus on describing those models using Taxi, rather than the practices of designing good APIs or event payloads. ### Use semantic types ```taxi // Good - uses semantic types model Customer { id: CustomerId email: EmailAddress } // Bad - uses primitive types model Customer { id: String // What kind of ID? email: String // Is this really an email? } ``` ### Document nullability ```taxi model User { // Required fields - core identity id: UserId email: EmailAddress // Optional fields - additional information middleName: MiddleName? phoneNumber: PhoneNumber? } ``` ### Organization ```taxi // Service-specific models together model CreateOrderRequest { customerId: CustomerId items: OrderItem[] } model CreateOrderResponse { orderId: OrderId status: OrderStatus } // Service definition with its models service OrderService { operation createOrder(CreateOrderRequest): CreateOrderResponse } ``` -------------------------------------------------------------------------------- # Services Source: https://taxilang.org/docs/language/services Section: Language basics -------------------------------------------------------------------------------- A service in Taxi describes APIs and data sources in your system. Services tell Taxi where to find data, whether that's in REST APIs, databases, or message queues. Here's a basic example: ```taxi service CustomerApi { // Get a customer by their ID operation getCustomer(CustomerId):Customer // List all customers operation listCustomers():Customer[] } ``` > **Note: Why use services?** > > Services define data locations for Taxi queries. When querying for data like customers or orders, Taxi uses these definitions to determine which APIs to call. ## Service Types ### REST APIs HTTP services can be defined using `@HttpService`, with a base URL that applies to all operations: ```taxi @HttpService(baseUrl = "https://api.example.com/v1") service CustomerApi { // Resolves to GET https://api.example.com/v1/customers @HttpOperation(method = "GET", url = "/customers") operation listCustomers():Customer[] // Resolves to GET https://api.example.com/v1/customers/{id} @HttpOperation(method = "GET", url = "/customers/{id}") operation getCustomer( @PathVariable(name = "id") id: CustomerId ):Customer } ``` ### Databases Databases can be described using the `table` keyword: ```taxi service CustomerDb { // Defines a table containing Customer records table customers : Customer[] // Defines a table containing Order records table orders : Order[] } ``` > **Note: Database Details** > > Taxi is database-agnostic. Use annotations to specify connection details and database-specific features. ### Event Streams Streaming data sources can be defined using the `stream` keyword: ```taxi service EventHub { // Customer login event stream stream loginEvents(): Stream // Order update event stream stream orderUpdates(): Stream } ``` ## Operations Operations define the available methods on a service. Here are common operation types: ### HTTP Operations HTTP operations define REST endpoints: ```taxi service OrderApi { // GET request @HttpOperation(method = "GET", url = "/orders") operation listOrders():Order[] // POST with request body @HttpOperation(method = "POST", url = "/orders") operation createOrder( @HttpRequestBody order: CreateOrderRequest ):Order // PUT with path parameter @HttpOperation(method = "PUT", url = "/orders/{orderId}/status") operation updateStatus( @PathVariable(name = "orderId") id: OrderId, @HttpRequestBody status: OrderStatus ):Order } ``` ### Operation Contracts Operations can specify contracts about their behavior: ```taxi model Money { currency : CurrencyCode amount : Decimal } // Contract ensures output currency matches targetCurrency operation convertCurrency( input: Money, targetCurrency: CurrencyCode ) : Money(currency = targetCurrency) // Contract specifies output derives from input operation normalizeAddress( input: Address ) : NormalizedAddress( from input ) ``` > **Warning: Contract Validation** > > Contracts declare expected behavior. Implementation and validation are handled by the underlying systems, not by Taxi. ## Write operations (Mutations) TaxiQL distinguishes between read operations (which fetch data) and write operations (which modify data). Write operations must be explicitly declared and called, providing more control over when modifications occur. ### Declaring write operations Write operations are declared using the `write operation` keyword in a service: ```taxi service PersonService { // Read operation - can be called implicitly during queries operation findPerson(PersonId):Person // Write operation - must be explicitly called write operation updatePerson(Person):Person } ``` > **Note: Write vs Read Operations** > > Standard operations can be called automatically by TaxiQL when resolving queries. Write operations require explicit calls to ensure mutations happen only when intended. ### Using write operations Write operations are invoked explicitly by using a `call` statement: #### Basic call ```taxi query UpdatePerson { given { person : Person = { id: "123", name: "Alice" } } call PersonService::updatePerson } ``` #### After finding data ```taxi query UpdatePeople { find { Person } call PersonService::updatePerson } ``` ### Write operation parameters TaxiQL engines automatically construct operation inputs using available data from the query context. This means you don't need to exactly match the operation's parameter structure in your `given` or `find` statements: ```taxi // Operation signature write operation updatePerson(Person):Person query UpdatePersonName { // TaxiQL will construct a Person object using available fields given { id: PersonId = "123" name: PersonName = "Alice" } call PersonService::updatePerson } ``` > **Note: Data Matching** > > The TaxiQL engine looks at the write operation's parameter types and constructs the required input using any matching data available from previous `given` or `find` statements. ### Additional Write Operation Control TaxiQL engines like Orbital use annotations to provide additional control over write operations: ```taxi service CustomerDatabase { // Specify database operation type @UpsertOperation write operation saveCustomer(Customer):Customer } ``` Common annotations include: - Database operation types (insert/update/upsert/delete) - Message queue destinations - Batch processing configurations > **Note: Implementation Specific** > > Available annotations and their effects depend on your TaxiQL engine. Consult your engine's documentation for supported annotations. ## Service Lineage Services can document their dependencies and data persistence: ```taxi service CustomerService { lineage { // Declares operation dependency consumes operation addresses.AddressService.validateAddress // Declares persisted data types stores Customer stores CustomerPreferences } operation findCustomer(CustomerId):Customer } ``` > **Note: Lineage Benefits** > > Service lineage helps track data flow, maintain documentation, and integrate with data catalogs. -------------------------------------------------------------------------------- # Annotations Source: https://taxilang.org/docs/language/annotations Section: Language basics -------------------------------------------------------------------------------- ## Overview Annotations in Taxi provide a way to attach metadata to types, models, services, and operations. They enable you to add configuration, validation rules, formatting hints, and other metadata without polluting the core data structure. ```taxi // Define an annotation annotation Deprecated { reason: String since: String } // Use the annotation @Deprecated(reason = "Use CustomerV2 instead", since = "2024-01-01") model Customer { id: CustomerId name: CustomerName } ``` ## Defining Annotations Annotations are defined using the `annotation` keyword followed by a name and optional field declarations: ```taxi // Simple annotation with no fields annotation Internal // Annotation with fields annotation Validation { minLength: Int? maxLength: Int? pattern: String? } // Annotation with required and optional fields annotation Documentation { description: String example: String? deprecated: Boolean = false } ``` ### Supported Field Types Annotation fields support various types to provide flexibility while maintaining structure: - **Primitive types**: `String`, `Int`, `Double`, `Boolean` - **Enums**: Custom enum types - **Arrays**: Arrays of the above types - **Other annotations**: Annotations can contain other annotations as fields - **Objects**: Model types and structured data ```taxi enum Severity { LOW, MEDIUM, HIGH, CRITICAL } model ErrorDetails { code: Int description: String timestamp: String? } annotation Alert { message: String severity: Severity tags: String[] details: ErrorDetails? // Object field } annotation Metadata { alert: Alert? internal: Boolean = false } // Usage with object parameter @Alert( message = "Validation failed", severity = Severity.HIGH, tags = ["validation", "input"], details = { code = 1001, description = "Email format is invalid", timestamp = "2024-01-15T10:30:00Z" } ) type EmailAddress inherits String ``` ### Object Parameters Annotations can accept object parameters, allowing you to pass structured data as annotation values: ```taxi model ValidationError { code: Int message: String field: String? } annotation Validate { error: ValidationError enabled: Boolean = true } // Using object literal syntax @Validate(error = { code = 1001, message = "Invalid email format", field = "email" }) type EmailAddress inherits String ``` Object parameters are validated at compile time to ensure they match the expected structure: ```taxi model Error { message: String severity: Int } annotation Rule { error: Error } // ✅ Valid - matches Error structure @Rule(error = { message = "Required field", severity = 1 }) type RequiredField inherits String // ❌ Invalid - field name mismatch @Rule(error = { description = "Wrong field name" }) // Compilation error type InvalidField inherits String ``` ## Annotation Inheritance Annotations support inheritance, allowing you to create hierarchies of related annotations. This is useful for creating specialized versions of common annotations. ```taxi // Base annotation annotation Rule { message: String } // Specialized annotations inheriting from Rule annotation ValidationRule inherits Rule { errorCode: Int } annotation BusinessRule inherits Rule { priority: Int category: String } // Using inherited annotations @ValidationRule(message = "Invalid email format", errorCode = 1001) type EmailAddress inherits String @BusinessRule(message = "Customer must be active", priority = 1, category = "customer") service OrderService { operation processOrder(customerId: CustomerId): Order } ``` ### Inheritance Rules When working with annotation inheritance: 1. **Annotations can only inherit from other annotations** 2. **Types cannot inherit from annotations** 3. **Inherited fields are available on child annotations** 4. **Child annotations can add new fields** 5. **No multiple inheritance** - each annotation can inherit from only one parent ```taxi // ✅ Valid: Annotation inheriting from annotation annotation BaseRule { message: String } annotation ValidationRule inherits BaseRule { errorCode: Int } // ❌ Invalid: Type inheriting from annotation // type CustomerId inherits ValidationRule // Compilation error // ❌ Invalid: Annotation inheriting from type // annotation MyAnnotation inherits String // Compilation error` ``` ### Accessing Inherited Fields When using annotations with inheritance, all fields from the inheritance hierarchy are available: ```taxi annotation Rule { message: String enabled: Boolean = true } annotation ValidationRule inherits Rule { errorCode: Int severity: String = "ERROR" } // Usage includes both inherited and declared fields @ValidationRule( message = "Value must be positive", // From Rule enabled = true, // From Rule (optional) errorCode = 1001, // From ValidationRule severity = "ERROR" // From ValidationRule (optional) ) type PositiveNumber inherits Int ``` ### Inheritance with Object Fields Object fields are fully supported in inherited annotations: ```taxi model ErrorInfo { code: Int description: String } annotation Rule { error: ErrorInfo } annotation ValidationRule inherits Rule { pattern: String? } // Child annotation can use inherited object field @ValidationRule( error = { code = 1001, description = "Must match pattern" }, pattern = "^[A-Z]+$" ) type UppercaseCode inherits String ``` ## Using Annotations ### On Types ```taxi @Documentation(description = "Unique identifier for customers") @Validation(pattern = "CUST-\\d{6}") type CustomerId inherits String @Internal @Deprecated(reason = "Use PersonName instead", since = "2024-01-01") type CustomerName inherits String` ``` ### On Models ```taxi @Documentation(description = "Core customer data structure") @JsonSerializable(camelCase = true) model Customer { @Validation(minLength = 1) id: CustomerId @Documentation(description = "Customer's display name") name: CustomerName @Internal internalScore: Int? } ``` ### On Services and Operations ```taxi @HttpService(baseUrl = "https://api.example.com") @RateLimit(requestsPerMinute = 100) service CustomerService { @HttpOperation(method = "GET", url = "/customers/{id}") @Cacheable(ttl = 300) @Documentation(description = "Retrieve customer by ID") operation getCustomer(id: CustomerId): Customer @HttpOperation(method = "POST", url = "/customers") @Validation(required = true) @Audit(level = "HIGH") operation createCustomer(customer: Customer): CustomerId } ``` ## Default Values Annotation fields can specify default values, making them optional when using the annotation: ```taxi annotation Cache { ttl: Int = 300 // Default 5 minutes enabled: Boolean = true // Default enabled key: String? // No default, optional region: String // No default, required } // Using with defaults @Cache(region = "customer-data") // ttl=300, enabled=true type CustomerId inherits String // Overriding defaults @Cache(region = "temp-data", ttl = 60, enabled = false) type TemporaryId inherits String` ``` -------------------------------------------------------------------------------- # Querying Source: https://taxilang.org/docs/taxiql/querying Section: Working with data -------------------------------------------------------------------------------- ## Introduction TaxiQL is Taxi's query language for fetching and transforming data across your systems. Rather than writing integration code against specific APIs or databases, TaxiQL lets you declare what data you want using semantic types. When you write a TaxiQL query, you're describing the meaning of the data you need, not where to find it. Query engines like [Orbital](https://orbitalhq.com) use these semantic types to automatically discover and orchestrate the necessary service calls - whether that's a simple database query, or a complex flow across REST APIs, message queues, and serverless functions. This semantic approach means your queries remain stable even as your architecture evolves. As services change their APIs or data moves between systems, TaxiQL adapts automatically, eliminating the traditional maintenance burden of integration code. ## Basic syntax The basic syntax of a TaxiQL query looks like this: ```taxi // Find all the people find { Person[] } // Find a person named Jim find { Person( FirstName == 'Jim' ) } // Find all the people named Jim find { Person[]( FirstName == 'Jim' ) } // Find a stream of person events from somewhere stream { PersonEvents } ``` Here's some interactive examples: **Example: Fetch a list of data from a service** Fetches a list of `Person[]` instances. Schema: ```taxi closed model Person { firstName : FirstName inherits String lastName : LastName inherits String age : Age inherits Int } service PersonApi { operation listPeople():Person[] operation findPerson( FirstName ):Person(...) } ``` Query: ```taxi // Find all the people find { Person[] } ``` Result: ```json [ { "firstName": "Jim", "lastName": "Jackson", "age": 38 }, { "firstName": "Alice", "lastName": "Jackson", "age": 34 }, { "firstName": "Annie", "lastName": "Jackson", "age": 12 } ] ``` **Example: Server-side filtering** Adding constraints to the data being fetched controls the APIs that are called, only calling operations that satisfy the constraints Schema: ```taxi closed model Person { firstName : FirstName inherits String lastName : LastName inherits String age : Age inherits Int } service PersonApi { operation listPeople():Person[] operation findPerson( FirstName ):Person(...) } ``` Query: ```taxi // Call an operation that specifically returns a person // looked up by their first name // This query matches the operation defined as: // operation findPerson( FirstName ):Person(...) find { Person(FirstName == 'Jim') } ``` Result: ```json { "firstName": "Jim", "lastName": "Jackson", "age": 38 } ``` Learn more about [server-side filtering and constraints](#server-side-constraints) --- **Example: Client side filtering** Fetches data by calling an operation, then filters the result Schema: ```taxi closed model Person { firstName : FirstName inherits String lastName : LastName inherits String age : Age inherits Int } service PersonApi { operation listPeople():Person[] operation findPerson( FirstName ):Person(...) } ``` Query: ```taxi // Fetch all the people from the server, then // filter those to people younger than 20 find { Person[].filter( (Age) -> Age < 20) } ``` Result: ```json { "firstName": "Annie", "lastName": "Jackson", "age": 12 } ``` Learn more about [client-side filtering](#client-side-filtering) and [expressions](https://taxilang.org/docs/taxiql/expressions-traversal) ## Navigating data structures You can access properties in models in two ways: - Using semantic types (preferred) - `Customer::AddressLine` - Using property names (classic, but not recommended) - `customer.addressLine` You can also mix 'n' match these approaches. > **Note: Use types to stay decoupled and prevent breaking changes** > > Using types to access data keeps systems loosely coupled. > > When consumers request data by its type, values are returned regardless of field names or nesting structure.\n\nAs schemas evolve, type-based access remains resilient to structural changes. ### Using types to traverse data Use the `::` operator to perform deep traversal through data structures: ```taxi // Find AddressLine1 at any depth within Customer Customer::AddressLine1 // Chain deep traversals // Find Address anywhere beneath Customer, and then PostCode anywhere beneath Address Customer::Address::PostCode ``` The :: operator performs deep traversal, searching recursively through all nested levels of the data structure, not just immediate fields. eg: ```taxi // Finds an instance of AddressLine1 anywhere in a Customer model Customer::AddressLine1 ``` > **Note: Ambiguous results return null** > > When writing an expression like `Customer::AddressLine1`, the result must select exactly one field, otherwise the statement is ambiguous, and the query engine returns `null`. See [Uniqueness](#uniqueness-and-type-traversal) for more information. **Example: Using types to traverse child properties** This example shows three different approaches to requesting a field by it's type, each with increasing specificity Schema: ```taxi model Customer { address : Address } model Address { houseNumber: HouseNumber inherits String line1: AddressLine1 inherits String postcode: PostCode inherits String } ``` Query: ```taxi import Address import Customer import AddressLine1 given { Customer = { address: { houseNumber: '5a', line1: 'Semantic Ave', postcode: 'SW2 4NN' } }} find { // Request simply by type - requires the type be unamgibuous addressLine1: AddressLine1 // Find AddressLine1 anywhere in a customer type customerAddressLine1: Customer::AddressLine1 // Or be more specific, by describing the entire structure: mostSpecificAddressLine1: Customer::Address::AddressLine1 } ``` Result: ```json { "addressLine1": "Semantic Ave", "customerAddressLine1": "Semantic Ave", "mostSpecificAddressLine1": "Semantic Ave" } ``` ### Uniqueness and type traversal When requesting data using a type, ambiguity arises if there's more than one location of a type. If the requested type is ambiguous within the data (ie., there are multiple instances found when a single instance was requested), then Taxi returns `null`. To resolve this ambiguity, you have three options: 1. **Request all instances** using array syntax 2. **Use structural navigation** with property names 3. **Use structural navigation** with type access syntax For example: **Example: Multiple values return null** In this query, `PersonName` is not unique, so returns null Schema: ```taxi model FlightBooking { passengers: Passenger[] } model Passenger { name : PersonName inherits String } ``` Query: ```taxi given { FlightBooking = { passengers: [ { name: 'Jim'}, { name: 'Jill'} ] } } find { // This returns null, as PersonName is not unique passengerName: PersonName } ``` Result: ```json { "passengerName": null } ``` Instead, you can either select all instances (eg: in the preceding example, by requesting `PersonName[]` instead of `PersonName`), or add selectors to make the selected instance unambiguous: **Example: Resolving type ambiguity** Either select the array, or use qualifiers to make the selected instance unambiguous Schema: ```taxi model FlightBooking { passengers: Passenger[] } model Passenger { name : PersonName inherits String } ``` Query: ```taxi given { FlightBooking = { passengers: [ { name: 'Jim'}, { name: 'Jill'} ] } } find { // Returns all PersonName instances as an array allPassengerNames: PersonName[] // Navigate to specific passenger by index, then access property firstPassengerName: Passenger[].getAtIndex(0).name // Navigate to specific passenger, then access by type secondPassengerName: Passenger[].getAtIndex(1)::PersonName } ``` Result: ```json { "allPassengerNames": [ "Jim", "Jill" ], "firstPassengerName": "Jim", "secondPassengerName": "Jill" } ``` ### Using property names You can reference data using property names, by using the dot-selector `.`. eg: ```taxi model Customer { profile: Profile } model Profile { email: EmailAddress } // Property access - requires exact structure customer.profile.email ``` **Example: Accessing properties by name** Access properties by name using standard 'dot' syntax Schema: ```taxi model Customer { address : Address } model Address { houseNumber: HouseNumber inherits String line1: AddressLine1 inherits String postcode: PostCode inherits String } ``` Query: ```taxi given { // Note that the customer variable is named here. customer: Customer = { address: { houseNumber: '5a', line1: 'Semantic Ave', postcode: 'SW2 4NN' } }} find { // Accessing a property by chaining property access addressLine1: String = customer.address.line1 // mixing property name access with type references is also possible postcode: customer.address::PostCode } ``` Result: ```json { "addressLine1": "Semantic Ave", "postcode": "SW2 4NN" } ``` ### Mixing approaches Combine traversal with property access: ```taxi // Type traversal then property Customer::Address.streetName // Property access then traversal customer.addresses::PostCode ``` ## Constraints and filtering ### Server-side constraints Constraints limit which services are called based on their declared capabilities: ```taxi // Only calls services that can filter by FirstName find { Customer(FirstName == 'Jimmy') } // Multiple constraints find { Customer[]( FirstName == 'Jimmy' && LastName == 'Smith' ) } ``` Services declare their constraint support: ```taxi service CustomerService { // Declares support for FirstName and LastName filtering operation findCustomersWithName( @PathVariable first : FirstName, @PathVariable last : LastName ): Customer(FirstName == first && LastName == last) } ``` ### Client-side filtering For additional filtering after data retrieval: ```taxi // Filter applied after data is fetched find { Customer[].filter((FirstName) -> FirstName == 'Jimmy') } // Multiple conditions find { Customer[].filter((Customer) -> Customer::FirstName == 'Jimmy' && Customer::Age > 21 ) } ``` > **Note: Performance consideration** > > Server-side constraints are more efficient as they reduce data transfer. Client-side filtering is more flexible but may result in more data being transferred. **Example: Client-side filtering (single condition)** Fetches every customer, then keeps only those whose `FirstName` is `Jimmy`. Schema: ```taxi model Customer { firstName : FirstName inherits String lastName : LastName inherits String age : Age inherits Int } ``` Query: ```taxi given { customers : Customer[] = [ { firstName: 'Jimmy', lastName: 'Smith', age: 30 }, { firstName: 'Jimmy', lastName: 'Jones', age: 45 }, { firstName: 'Jimmy', lastName: 'Fry', age: 18 }, { firstName: 'Sarah', lastName: 'Smith', age: 40 } ] } find { Customer[].filter((FirstName) -> FirstName == 'Jimmy') } ``` Result: ```json [ { "firstName": "Jimmy", "lastName": "Smith", "age": 30 }, { "firstName": "Jimmy", "lastName": "Jones", "age": 45 }, { "firstName": "Jimmy", "lastName": "Fry", "age": 18 } ] ``` **Example: Client-side filtering (multiple conditions)** Combine conditions with `&&`. Naming the item `Customer` lets you read its fields by type - here only the Jimmys older than 21 remain. Schema: ```taxi model Customer { firstName : FirstName inherits String lastName : LastName inherits String age : Age inherits Int } ``` Query: ```taxi given { customers : Customer[] = [ { firstName: 'Jimmy', lastName: 'Smith', age: 30 }, { firstName: 'Jimmy', lastName: 'Jones', age: 45 }, { firstName: 'Jimmy', lastName: 'Fry', age: 18 }, { firstName: 'Sarah', lastName: 'Smith', age: 40 } ] } find { Customer[].filter((Customer) -> Customer::FirstName == 'Jimmy' && Customer::Age > 21) } ``` Result: ```json [ { "firstName": "Jimmy", "lastName": "Smith", "age": 30 }, { "firstName": "Jimmy", "lastName": "Jones", "age": 45 } ] ``` ## Collection options Any collection expression can be shaped with **collection options** — named arguments that limit, order, paginate and de-duplicate results. They're written after the array marker, alongside an optional predicate: ```taxi find { Person[](CountryCode == 'GB', limit: 10, orderBy: DateOfBirth desc) } ``` **Example: Predicate, limit and order together** Fetches people in `GB`, orders them by date of birth (newest first), then keeps the first two. Schema: ```taxi model Person { id : PersonId inherits String name : PersonName inherits String country : CountryCode inherits String dateOfBirth : DateOfBirth inherits Date email : EmailAddress inherits String } service PersonApi { operation findPeopleByCountry(CountryCode):Person[](https://taxilang.org/docs/taxiql/...) } ``` Query: ```taxi find { Person[](CountryCode == 'GB', limit: 2, orderBy: DateOfBirth desc) } ``` Result: ```json [ { "id": "4", "name": "Dave", "country": "GB", "dateOfBirth": "2001-07-30", "email": "dave@example.com" }, { "id": "1", "name": "Alice", "country": "GB", "dateOfBirth": "1990-05-01", "email": "alice@example.com" } ] ``` Collection options describe *what* the result should look like, not *how* to compute it. An option-carrying collection reads as: an optional single predicate first (`CountryCode == 'GB'`, which restricts which values match), followed by zero or more named options (`limit: 10`, `orderBy: DateOfBirth desc`, which shape the resulting collection). ### Syntax ```taxi Type[]( predicate?, option: value, ... ) ``` - The predicate, if present, must come first. - Options are named (`name: value`) and unordered among themselves. - Each option may appear at most once. To combine multiple conditions, write a single boolean expression rather than several predicates: ```taxi // Valid - one boolean predicate, then options find { Person[](CountryCode == 'GB' && Status == 'Active', limit: 10) } ``` ### Available options | Option | Type | Meaning | Notes | |--------|------|---------|-------| | `limit` | `Int` | Return at most N items | "At most" — fewer may be returned if fewer are available. Must be non-negative. | | `offset` | `Int` | Skip the first N items | Must be non-negative. Warns if used without `orderBy`. | | `after` | `String` | Return the page after an opaque cursor | Mutually exclusive with `before`; can't be combined with `offset`. | | `before` | `String` | Return the page before an opaque cursor | Mutually exclusive with `after`; can't be combined with `offset`. | | `orderBy` | order term(s) | Order the collection by one or more expressions | `Type asc\|desc`; direction defaults to `asc`. Single term or list. | | `uniqueBy` | expression(s) | Keep at most one item per unique key | Single expression or list. | ### Ordering `orderBy` takes a single term, or a list of terms for multi-field ordering. Each term is an expression (usually a type) with an optional direction — `asc` or `desc`, defaulting to `asc`: ```taxi // Single field, explicit direction find { Person[](orderBy: DateOfBirth desc) } // Direction defaults to ascending find { Person[](orderBy: PersonName) } // Multiple fields - use a list to avoid comma ambiguity find { Person[](orderBy: [DateOfBirth desc, PersonName asc]) } ``` **Example: Ordering by multiple fields** Orders by date of birth descending, breaking ties by name ascending. Alice and Bob share a birth date, so name decides their order. Schema: ```taxi model Person { id : PersonId inherits String name : PersonName inherits String country : CountryCode inherits String dateOfBirth : DateOfBirth inherits Date email : EmailAddress inherits String } service PersonApi { operation listPeople():Person[] } ``` Query: ```taxi find { Person[](orderBy: [DateOfBirth desc, PersonName asc]) } ``` Result: ```json [ { "id": "1", "name": "Alice", "country": "GB", "dateOfBirth": "1990-05-01", "email": "alice@example.com" }, { "id": "2", "name": "Bob", "country": "US", "dateOfBirth": "1990-05-01", "email": "bob@example.com" }, { "id": "3", "name": "Carol", "country": "GB", "dateOfBirth": "1978-11-23", "email": "carol@example.com" } ] ``` Multiple order terms must be wrapped in a list. Writing them as bare comma-separated terms isn't allowed, as the comma is ambiguous with the surrounding option list. ### Deduplicating with uniqueBy `uniqueBy` keeps at most one item for each distinct key. Pass a single expression, or a list to key on a combination: ```taxi // One person per PersonId find { Person[](uniqueBy: PersonId) } // One person per (CountryCode, NationalInsuranceNumber) pair find { Person[](uniqueBy: [CountryCode, NationalInsuranceNumber]) } ``` **Example: Deduplicating with uniqueBy** Keeps one person per `(CountryCode, EmailAddress)` pair. Alice and Bob share both, so Bob is dropped; Carol's country differs, so she stays. Schema: ```taxi model Person { id : PersonId inherits String name : PersonName inherits String country : CountryCode inherits String dateOfBirth : DateOfBirth inherits Date email : EmailAddress inherits String } service PersonApi { operation listPeople():Person[] } ``` Query: ```taxi find { Person[](uniqueBy: [CountryCode, EmailAddress]) } ``` Result: ```json [ { "id": "1", "name": "Alice", "country": "GB", "dateOfBirth": "1990-05-01", "email": "shared@example.com" }, { "id": "3", "name": "Carol", "country": "US", "dateOfBirth": "1978-11-23", "email": "shared@example.com" } ] ``` Because it keys on semantic value, `uniqueBy` isn't the same as a generic `.distinct()` — it de-duplicates by the meaning of the requested type(s). ### Cursor pagination `after` and `before` request a page of results relative to an opaque cursor string. They're mutually exclusive, and can't be combined with `offset`: ```taxi query FindPeople(cursor: String, pageSize: Int) { find { Person[](after: cursor, limit: pageSize, orderBy: PersonName asc) } } ``` Taxi treats the cursor value as opaque. Whether cursors are honoured at runtime depends on the engine and the underlying source — support is currently limited, so check your engine's capabilities before relying on them. ### Parameterised values Option values can be literals or any expression that's resolvable *before* the collection is fetched — most commonly a query parameter: ```taxi query FindPeople(maxRows: Int) { find { Person[](limit: maxRows) } } ``` **Example: Passing an option from a query parameter** `limit` reads its value from the `maxRows` query parameter, which is resolved before the collection is fetched. Schema: ```taxi model Person { id : PersonId inherits String name : PersonName inherits String country : CountryCode inherits String dateOfBirth : DateOfBirth inherits Date email : EmailAddress inherits String } service PersonApi { operation listPeople():Person[] } ``` Query: ```taxi query FindPeople(maxRows: Int) { find { Person[](limit: maxRows) } } ``` Result: ```json [ { "id": "1", "name": "Alice", "country": "GB", "dateOfBirth": "1990-05-01", "email": "alice@example.com" }, { "id": "2", "name": "Bob", "country": "US", "dateOfBirth": "1985-03-12", "email": "bob@example.com" } ] ``` An option may reference query arguments and values from an enclosing scope, but not values produced by the collection it configures. For example `Person[](limit: PersonAge)` is invalid, because `PersonAge` belongs to the very `Person` values being fetched. ### Logical order However you write them, options are applied in a fixed logical order: ``` predicate → uniqueBy → orderBy → offset → limit ``` This matters for correctness. For example: ```taxi find { Person[](uniqueBy: EmailAddress, limit: 10) } ``` **Example: Options apply in a fixed logical order** `uniqueBy` runs before `limit`, so this returns up to 10 unique people rather than 10 rows that are then deduplicated down to fewer. Schema: ```taxi model Person { id : PersonId inherits String name : PersonName inherits String country : CountryCode inherits String dateOfBirth : DateOfBirth inherits Date email : EmailAddress inherits String } service PersonApi { operation listPeople():Person[] } ``` Query: ```taxi find { Person[](uniqueBy: EmailAddress, limit: 10) } ``` Result: ```json [ { "id": "1", "name": "Alice", "country": "GB", "dateOfBirth": "1990-05-01", "email": "team@example.com" }, { "id": "3", "name": "Carol", "country": "GB", "dateOfBirth": "1978-11-23", "email": "carol@example.com" } ] ``` returns up to 10 *unique* people — deduplication happens first, then the limit — rather than 10 people that are then deduplicated down to fewer. Likewise `orderBy` always precedes `limit`, so `orderBy: DateOfBirth desc, limit: 10` means "order everything, then take 10", not "take 10 arbitrary rows, then sort them". ### Where options apply — source vs projection Options attach to the specific collection they follow, and their **position determines which phase they shape**. On a `find`, they shape the source fetch. On a projection's array marker, they shape the projected result: ```taxi // Fetch up to 100 people, project them, then return up to 10 projected results find { Person[](limit: 100) } as { name: PersonName }[](limit: 10) ``` **Example: Source limit vs projection limit** The source `Person[]` is fetched with `limit: 100`, but the projection returns only the first two results. Schema: ```taxi model Person { id : PersonId inherits String name : PersonName inherits String country : CountryCode inherits String dateOfBirth : DateOfBirth inherits Date email : EmailAddress inherits String } service PersonApi { operation listPeople():Person[] } ``` Query: ```taxi find { Person[](limit: 100) } as { name : PersonName }[](limit: 2) ``` Result: ```json [ { "name": "Alice" }, { "name": "Bob" } ] ``` > **Note: Position determines the phase** > > The two are **not** interchangeable. An option on the source `Person[]` limits what's fetched; an option on the projection's `[]` limits what's returned after projecting. Limiting the source to 100 and the projection to 10 fetches up to 100 rows and returns 10 — limiting the source to 10 would only ever fetch 10. Options also work on nested collection fields inside a projection: ```taxi find { Person[](CountryCode == 'GB', limit: 10, orderBy: PersonName asc) } as { name: PersonName transactions: Transaction[](limit: 20, orderBy: TransactionDate desc) }[] ``` **Example: Options on a nested collection** Each person's transactions are independently limited to their two most recent. Schema: ```taxi model Person { @Id id : PersonId inherits String name : PersonName inherits String country : CountryCode inherits String dateOfBirth : DateOfBirth inherits Date email : EmailAddress inherits String } model Transaction { personId : PersonId date : TransactionDate inherits Date amount : Amount inherits Decimal } service PersonApi { operation findPeopleByCountry(CountryCode):Person[](https://taxilang.org/docs/taxiql/...) operation getTransactions(PersonId):Transaction[] } ``` Query: ```taxi find { Person[](CountryCode == 'GB', limit: 10, orderBy: PersonName asc) } as { name : PersonName transactions : Transaction[](limit: 2, orderBy: TransactionDate desc) }[] ``` Result: ```json [ { "name": "Alice", "transactions": [ { "personId": "1", "date": "2023-06-01", "amount": 20 }, { "personId": "1", "date": "2023-03-01", "amount": 15 } ] }, { "name": "Dave", "transactions": [ { "personId": "4", "date": "2024-01-01", "amount": 200 }, { "personId": "4", "date": "2022-05-01", "amount": 100 } ] } ] ``` ### Compile-time rules Options are checked when your query compiles. The compiler rejects: - Options on a non-collection type (`Person(limit: 10)` — `limit` needs `Person[]`). - Unknown option names, and any option specified more than once. - Values of the wrong type (`limit`/`offset` must be `Int`; `after`/`before` must be `String`). - Statically-negative `limit` or `offset` (dynamic values are validated at runtime instead). - Sort directions other than `asc` / `desc`. - `after` and `before` together, or either cursor combined with `offset`. - A predicate written after named options. > **Warning: offset without orderBy** > > Using `offset` without an `orderBy` compiles with a warning: without a stable order, the result window may shift between calls, so successive pages aren't guaranteed to line up. ### Runtime behaviour Collection options define the *meaning* of a result; the runtime semantics are engine-dependent. A query engine such as [Orbital](https://orbitalhq.com) decides whether to push each option down to the data source, apply it within the engine, or reject combinations it can't satisfy safely. For instance, `limit` on a stream simply takes the first N items and completes, whereas a global `orderBy` or `uniqueBy` over an unbounded stream is generally rejected. ## Given statements Given statements make data available to your query without constraining which operations are called: ```taxi // Basic given statement given { EmailAddress = 'jimmy@demo.com' } find { Customer } // With variable name given { email : EmailAddress = 'jimmy@demo.com' } find { Customer } // Multiple values given { status : OrderStatus = 'PENDING' customerId : CustomerId = '123' } find { Order[] } ``` **Example: Feeding an input with given** The `EmailAddress` provided in `given` is used to call an operation that looks up the matching customer. Schema: ```taxi model Customer { email : EmailAddress inherits String firstName : FirstName inherits String lastName : LastName inherits String } service CustomerApi { operation findCustomer(EmailAddress):Customer } ``` Query: ```taxi given { EmailAddress = 'jimmy@demo.com' } find { Customer } ``` Result: ```json { "email": "jimmy@demo.com", "firstName": "Jimmy", "lastName": "Smith" } ``` ### Given vs constraints Understanding the difference is crucial: ```taxi // Given: makes data available but doesn't restrict operations given { status : OrderStatus = 'PENDING' } find { Order[] } // May return orders of any status // Constraint: restricts which operations can be called given { status : OrderStatus = 'PENDING' } find { Order[](OrderStatus == status) } // Only returns pending orders ``` > **Warning: Given doesn't constrain data** > > Providing data in `given` makes data available that can be used with API calls -- but it doesn't limit which operations can be called. To filter results or specify exact data requirements, use constraints in your `find` statement. ## Basic projections Projections transform and enrich data: ```taxi // Project to a different structure find { Movie[] } as { title : MovieTitle director : DirectorName rating : RottenTomatoesScore }[] // Project to a named type find { Book[] } as BookAndAuthor[] // Select specific fields find { Order[] } as { id // Field shorthand status total }[] ``` **Example: Projecting to a new structure** Reshapes each `Movie` into a result containing only the fields you ask for. Schema: ```taxi model Movie { title : MovieTitle inherits String director : DirectorName inherits String rating : RottenTomatoesScore inherits Int } ``` Query: ```taxi given { movies : Movie[] = [ { title: 'Jaws', director: 'Spielberg', rating: 97 }, { title: 'Alien', director: 'Scott', rating: 93 } ] } find { Movie[] } as { title : MovieTitle director : DirectorName rating : RottenTomatoesScore }[] ``` Result: ```json [ { "title": "Jaws", "director": "Spielberg", "rating": 97 }, { "title": "Alien", "director": "Scott", "rating": 93 } ] ``` ## Named queries Save and reuse queries: ```taxi // Simple named query query PendingOrders { find { Order[](Status == 'PENDING') } } // Parameterized query query FindOrdersByStatus(status: OrderStatus) { find { Order[](Status == status) } } ``` ## Including and excluding services Control which services are called: ```taxi // Only use specific services find { Film[] } using { FilmService::getFilms, // Specific operation ReviewService // Entire service } // Exclude specific services find { Film[] } excluding { ImdbApi, // Exclude entire service RottenTomatoes::getReviews // Exclude specific operation } ``` ## Supported operators | Symbol | Meaning | |--------|--------------------------| | `==` | Equal to | | `!=` | Not equal to | | `>` | Greater than | | `>=` | Greater than or equal to | | `<` | Less than | | `<=` | Less than or equal to | ## Next steps - Learn about [projections](https://taxilang.org/docs/taxiql/projections) for transforming data - Explore [expressions and type traversal](https://taxilang.org/docs/taxiql/expressions-traversal) for navigating data - Understand [functions](https://taxilang.org/docs/taxiql/functions) for data manipulation -------------------------------------------------------------------------------- # Transforming data (projections) Source: https://taxilang.org/docs/taxiql/projections Section: Working with data -------------------------------------------------------------------------------- ## Understanding projections Projections are TaxiQL's way of transforming data and combining it from multiple sources. They allow you to reshape objects, enrich them with additional data, and create exactly the structure you need. ```taxi // Basic projection syntax find { Source } as { /* .. target structure.. */} ``` ## Projection types TaxiQL supports three main ways to define your projection target, each suited to different use cases depending on whether you need a one-off structure or want to reuse existing type definitions. ### Anonymous types Anonymous types let you define the exact structure you need inline within your query. This is the most common approach, and ideal for consumer contracts, where you need a specific shape that isn't intended to be shared, so doesn't warrant creating a reusable type. ```taxi find { Purchase[] } as { txn: TransactionId customerName: CustomerName // Discovered from other services total: OrderTotal }[] ``` ### Named types Named type projections transform your source data to match an existing model definition. This approach promotes reusability and ensures consistency when multiple queries need the same output format. ```taxi model CustomerTransaction { transactionId : TransactionId name : CustomerName price : Price } find { Purchase[] } as CustomerTransaction[] ``` ## Field selection Field selection gives you fine-grained control over which data to include in your projections, allowing you to optimize queries by only requesting the fields you actually need. ### Field shorthand When you only need to rename or select specific fields without changing their types, field shorthand provides a concise syntax that automatically infers the field types from the source. ```taxi model Order { id: OrderId status: OrderStatus total: OrderTotal } find { Order[] } as { id // Selects OrderId status // Selects OrderStatus }[] ``` ### Spread operator The spread operator provides a convenient way to include most fields from the source while selectively adding new ones or excluding unwanted fields. ```taxi // Include all fields find { Order[] } as { ... tracking: TrackingNumber // Plus extra field }[] // Exclude specific fields find { Order[] } as { ... except { total, customer } }[] ``` > **Note: Spread position** > > The spread operator must be the last entry in your field list. ## Shaping projected collections Just like a `find`, a projected collection can carry [collection options](https://taxilang.org/docs/taxiql/querying#collection-options) — `limit`, `offset`, `orderBy`, `uniqueBy` and cursor pagination. Attach them to the projection's array marker to shape the *projected* result, which runs as a distinct phase from any source-side shaping: ```taxi // Fetch up to 100 people, project each, then return the 10 with the latest birthdays find { Person[](limit: 100) } as { name : PersonName dateOfBirth : DateOfBirth }[](limit: 10, orderBy: DateOfBirth desc) ``` Because these options sit on the projection's `[]`, they apply *after* projection. That's a different phase from limiting the source fetch: `find { Person[](limit: 100) } as { ... }[](limit: 10)` fetches 100 and returns 10, whereas `find { Person[](limit: 10) } as { ... }[]` only ever fetches 10. Nested collection fields can be shaped the same way: ```taxi find { Customer[] } as { name : CustomerName // The ten most recent orders for each customer orders : Order[](orderBy: OrderDate desc, limit: 10) }[] ``` See [collection options](https://taxilang.org/docs/taxiql/querying#collection-options) for the full reference, including the logical order in which options are applied and the compile-time rules. ## Iterating vs Transforming Projections in TaxiQL can either: 1. **Iterate** over a collection and transform each element. (`A[] as B[]`) 2. **Transform** a single object into another type. (`A as B`) **Key Rules** - **`A[] as B[]` always iterates** over the collection. - **All other projections perform a transformation.** - **Scopes do not change projection behavior.** Even if a collection is in scope, projection behavior is determined solely by input/output types. Specifically: | Projection | Behavior | |-----------------------|--------------------------------------------------------------------------------------------------------------------------------| | `A[] as B[]` | **Iteration** - Each `A` is transformed into `B` individually. | | `A as B[]` | **Transformation** - `A` is converted into `B[]`, but `A` itself is not iterated. | | `A[] as B` | **Aggregation** - `A[]` is converted into `B`, using some aggregation logic defined in the query | | `A as (c:C) -> B[]` | **Transformation** - `A` is transformed into `B[]`, using `C` as additional context, but neither `A` nor `C` are iterated. | | `A as (c:C[]) -> B[]` | **Transformation** - `C[]` is available in scope for transformation, but it is not iterated unless explicitly projected later. | | `A as C[] as B[]` | **Iteration** - Two separate projections: `A` to `C[]` (transformation), then `C[]` to `B[]` (iteration). | **✅ Iteration: Collection to Collection (`A[] as B[]`)** - When the **input** is an array and the **output** is also an array, each element in the input array is processed separately. ```taxi find { Actor[] } as FamousActor[] ``` or, as a field within a projection: ```taxi find { Film } as { cast : Actor[] as FamousActor[] } ``` In both cases, each `Actor` is individually transformed into a `FamousActor`. **✅ Transformation: Object to Object (`A as B`)** - When the **input** is a single object and the **output** is also a single object, a transformation occurs. ```taxi find { Actor } as FamousActor ``` or ```taxi find { Film } as { starring : Actor as FamousActor } ``` The `Actor` is transformed into a `FamousActor`. --- **✅ Transformation: Object to Collection (`A as B[]`)** - The input is a **single object** (`A`), but the output is an **array** (`B[]`). - This is a **transformation**, **not an iteration**. - This is typically used to select a field from an object, or to use a source as an object to discover another value. ```taxi find { Film } as AwardNomination[] ``` This transforms a `Film` into a collection of `AwardNomination[]`, but does **not** iterate over each `AwardNomination`. **Field-Level Projections** These same rules **also apply to fields inside projections**. **✅ Iteration in a Field: Collection to Collection (`A[] as B[]`)** - **Field projection iterates if the field is an array.** ```taxi find { Film } as { actors: Actor[] as FamousActor[] } ``` Each `Actor` in the array is transformed into a `FamousActor`. --- **❌ Incorrect: Object to Array Transformation (`A as B[]`)** - If the field projection is **from an object (`A`)** to an **array (`B[]`)**, **it does not iterate**. - Instead, the whole `A` is transformed into `B[]` ```taxi find { Studio } as { name : StudioName // This is NOT an iterating transformation films : FilmResponse as (films:Film[]) -> { title: Title }[] } ``` 🚨 Troubleshooting: Why is `films` empty? - `FilmResponse` **contains a collection (`Film[]`)**. - But since this projection is **object-to-array (`A as B[]`)**, **films is not iterated**. - The scoping statement (`(films:Film[]) -> `) places films in scope, but does not change the behaviour of the projection to iterating. ✅ **Fix: Explicitly Force Iteration by Splitting Projections** ```taxi find { Studio } as { name : StudioName films : FilmResponse as Film[] as { title: Title }[] } ``` Now, `FilmResponse` is **explicitly projected into `Film[]` first**, ensuring that the second projection **iterates**. **Summary** - **Collection (`A[]`) → Collection (`B[]`) always iterates.** - **Object (`A`) → Object (`B`) always transforms.** - **Object (`A`) → Collection (`B[]`) transforms, does not iterate.** - **Field projections follow the same rules as top-level projections.** - **If you need iteration but the projection is transforming, split it into two projections.** ## Projection scopes Projection scopes control what data is available during the transformation process, allowing you to reference fields from the source object and pass data between nested projections. ### Basic scopes Each projection creates a scope containing the data being transformed: ```taxi // Array projection - each Customer is in scope individually find { Customer[] } as { firstName : FirstName lastName : LastName }[] // Named variable in scope find { Customer[] } as (customer:Customer) -> { name: upperCase(customer.firstName) age: Age // Resolved from customer automatically }[] ``` ### Nested scopes Nested scopes enable complex transformations where inner projections need access to data from outer scopes, such as when projecting related collections that depend on parent object values. ```taxi find { Customer[] } as (customer:Customer) -> { name : CustomerName orders : Order[] as (order:Order) -> { ageAppropriate : Boolean = customer.age >= order.recommendedAge }[] }[] ``` ### Modifying scope You can explicitly control what data is available in the projection scope, which is useful when you want to restrict or expand the context for data resolution. ```taxi // Default scope contains entire Film find { Film } as { title : Title cast : Actor[] } // Modified scope contains only Actor[] find { Film } as (Actor[]) -> { actorName : ActorName title : Title // null - Film not in scope }[] // Multiple items in scope find { Film } as (Film, Actor[]) -> { actorName : ActorName title : Title // Now available from Film }[] ``` ## Data discovery Data discovery is TaxiQL's ability to automatically find and call services to populate fields that aren't present in the source data, enabling rich data enrichment without manual service orchestration. ### Automatic enrichment TaxiQL automatically discovers data from other services: ```taxi model Purchase { customerId : CustomerId amount : Amount } model Customer { @Id customerId : CustomerId name : CustomerName } service CustomerService { operation findCustomer(CustomerId): Customer } // CustomerName automatically discovered via customerId find { Purchase[] } as { amount : Amount customerName : CustomerName }[] ``` ### @Id attribute rules The @Id annotation controls which operations TaxiQL will consider when discovering data, ensuring that only appropriate lookup operations are used for enrichment. ```taxi model Movie { @Id id : MovieId year : YearReleased } service ReviewService { operation getReview(MovieId): Review // ✅ Called operation getReviewsByYear(YearReleased): Review[] // ❌ Not called } ``` ### @FirstNotEmpty The @FirstNotEmpty annotation instructs TaxiQL to try multiple data sources when the primary source returns null values, enabling robust data enrichment from fallback services. ```taxi find { Person[] } as { name : PersonName @FirstNotEmpty email : EmailAddress // If null, tries other services }[] ``` ## Streaming projections Streaming projections allow you to transform data as it flows through event streams, applying the same enrichment and transformation capabilities to real-time data. ```taxi stream { StockPrice } as { symbol : StockSymbol updateReceived : Instant = now() currentPrice : StockPrice } // Filter streams stream { StockQuotes.filterEach((StockSymbol) -> StockSymbol == 'AAPL') } ``` ## Common patterns These patterns demonstrate typical projection use cases that solve real-world data integration challenges, showing how to combine TaxiQL features effectively. ### Enrichment pattern ```taxi // Start with basic order data find { Order[] } as { orderId : OrderId // Enrich with customer details customerName : CustomerName customerEmail : EmailAddress // Add calculated fields totalWithTax : Decimal = OrderTotal * 1.2 }[] ``` ### Aggregation pattern ```taxi // Transform detailed data into summary find { Transaction[] } as CustomerSummary { customerId : CustomerId totalSpent : sum(TransactionAmount) transactionCount : count(TransactionId) }[] ``` ### Nested enrichment ```taxi find { Invoice[] } as { invoiceNumber : InvoiceNumber items : InvoiceItem[] as { description : ItemDescription // Enrich each item with product details productCategory : ProductCategory supplier : SupplierName }[] }[] ``` -------------------------------------------------------------------------------- # Mutations Source: https://taxilang.org/docs/taxiql/mutations Section: Working with data -------------------------------------------------------------------------------- While queries fetch and transform data, mutations allow you to make changes - like updating records, triggering actions, or writing data to destinations. ## Declaring mutable operations Operations that perform changes are marked with the `write` modifier in service definitions: ```taxi service FlightsService { // Operation that makes changes write operation bookFlight(BookingRequest):BookingConfirmation // Regular query operation operation getFlights():Flight[] } ``` ## Invoking mutations While queries use the `find` or `stream` keywords, mutations use the `call` directive: ```taxi find { Passengers[] } call FlightsService::sendFlightManifest ``` > **Note: Write protection** > > Operations marked with `write` are excluded from being called during regular queries (i.e., in `find` or `stream` blocks). They can only be invoked using `call`. ## Type conversion in mutations TaxiQL automatically handles type conversion between your source data and the mutation operation's input requirements: ```taxi // Source data model model Movie { id: FilmId title: Title } // Target model for database model Film { @Id filmId: FilmId title: Title reviewScore: ReviewScore // Additional field not in source } service FilmDatabase { write operation saveFilm(Film):Film } // TaxiQL will automatically discover missing data find { Movie[] } call FilmDatabase::saveFilm ``` ## Explicit projections You can control the transformation of data before mutation using projections: ```taxi find { Film[] } as { id: FilmId title: Title = upperCase(Title) // Transform data before saving rating: ReviewScore } call FilmDatabase::saveFilm ``` ## Single vs batch mutations The input type of your mutation operation determines whether data is processed individually or in batches: ```taxi service FilmService { // Processes one film at a time write operation saveSingle(Film):Film // Processes films in batches write operation saveBatch(Film[]):Film[] } ``` > **Note: Batch processing** > > Using array types in mutation operations signals that the operation expects to handle multiple records in a single call. This can be more efficient for bulk operations. -------------------------------------------------------------------------------- # Functions Source: https://taxilang.org/docs/taxiql/functions Section: Working with data -------------------------------------------------------------------------------- ## Overview Taxi's function system allows you to declare functions for data manipulation. While Taxi doesn't implement these functions itself (that's left to runtimes like [Orbital](https://orbitalhq.com)), it provides a rich system for declaring, composing, and extending functionality. > **Note: Type-centric parameters** > > In Taxi's type-centric approach, function parameters are defined by their **type**, not by variable names. Variable names are optional and improve readability, but the type is what's mandatory. This applies to all function definitions in Taxi. ## Function parameters in Taxi Understanding how Taxi handles function parameters is crucial since it differs from traditional programming languages. ### Type-first parameter definition In Taxi, parameters are primarily defined by their type: ```taxi // Basic function - parameter defined by type function upperTrim(String): String -> String.trim().upperCase() // Multiple parameters by type function concat(String, String): String // Optional variable names for readability function upperTrim(text: String): String -> text.trim().upperCase() function concat(first: String, second: String): String ``` ### Parameter access patterns How you access parameters depends on whether you've assigned variable names: ```taxi // Without variable names - use the type name function formatName(FirstName, LastName): String -> concat(FirstName, ' ', LastName).upperCase() // With variable names - use the variable function formatName(first: FirstName, last: LastName): String -> concat(first, ' ', last).upperCase() // Mixed approach function processCustomer(Customer, status: Status): String -> concat(Customer::Name, ' - ', status) ``` > **Note: Types vs variables as inputs** ## Declaring functions Function declarations define the signature and contract of operations that can be used throughout your Taxi schemas. There are two basic types of functions in Taxi: * **External functions**: Use `declare function` to expose a function signature to the compiler without providing implementation. The actual implementation is provided by runtime engines like [Orbital](https://orbitalhq.com). Examples include Taxi's [StdLib](https://taxilang.org/docs/taxiql/docs/language/stdlib) functions like `concat()` and `upperCase()`. * **Composed functions**: Use `function` to define functions with implementation bodies that combine existing functions. These are written entirely in Taxi and built from other functions. ```taxi // External function - signature only declare extension function upperCase(String): String // Composed function - has implementation body function formatName(FirstName, LastName): String -> concat(FirstName, ' ', LastName).upperCase() ``` ### Declaring external functions External functions are defined as `declare function ...`, and do not define a function body: ```taxi namespace com.example { // Type-based parameters declare function concat(String, String): String declare function upperCase(String): String // With optional variable names declare function calculateTax(amount: Decimal, rate: TaxRate): Decimal } ``` ### With parameter names Named parameters improve readability and make function calls more self-documenting, especially for functions with multiple parameters of the same type. ```taxi declare function concat(first: String, second: String): String declare function substring( text: String, start: Int, length: Int ): String ``` ### Vararg parameters Variable argument parameters allow functions to accept a flexible number of inputs, useful for operations like concatenation or mathematical functions that work on multiple values. ```taxi // Type-based varargs // values accepts multiple strings declare function concat(String...): String // Mixed regular and vararg with variable names declare function max(first: Int, rest: Int...): Int ``` ## Using functions Functions can be integrated into your data models and queries in several ways, enabling both compile-time expressions and runtime data transformations. ### In field expressions Field expressions allow you to define computed fields that automatically calculate values based on other fields in the same model. ```taxi model Person { firstName : FirstName inherits String lastName : LastName inherits String // Using type references directly fullName : String = concat(FirstName, ' ', LastName) // Using field references with 'this' formalName : String = concat(this.lastName, ', ', this.firstName) // Chained operations on types displayName : String = concat(FirstName, ' ', LastName).upperCase() } ``` ### In expression types Expression types encapsulate common calculations into reusable type definitions, promoting consistency and reducing duplication across your schemas. ```taxi // Type-based expression type FullName inherits String = concat(FirstName, ' ', LastName) // Used in models model Person { firstName : FirstName lastName : LastName fullName : FullName // Computed using the expression } ``` ## Composed functions Composed functions let you build complex business logic by combining simpler functions, creating reusable operations that encapsulate multi-step transformations. ```taxi // Type-based parameters function upperTrim(String): String -> String.trim().upperCase() // With variable names for readability function formatName(first: FirstName, last: LastName): String -> concat(first, ' ', last).upperCase() // Complex logic with conditionals function displayPrice(amount: Decimal, currency: Currency): String -> when { currency == "USD" -> concat("$", amount) currency == "EUR" -> concat("€", amount) else -> concat(amount, " ", currency) } ``` > **Note: Function composition** > > Composed functions let you create reusable business logic by combining existing functions. This promotes code reuse and maintains consistency. ### Collection operations Collection operations provide powerful tools for filtering, transforming, and extracting data from arrays, enabling complex data processing with simple, readable syntax. ```taxi // Type-based collection functions function activeAddress(Address[]): Address -> Address[].single((IsCurrentAddress) -> IsCurrentAddress == true) // With variable names function activeCustomers(customers: Customer[]): Customer[] -> customers.filter((c: Customer) -> c::IsActive == true) // Type references in transformations function customerNames(Customer[]): String[] -> Customer[].map((Customer) -> Customer::FullName) ``` > **Note: Functions vs Expression Types** > > Functions and Expression Types can be used interchangeably.\n\nIt's sometimes cleaner to model your business logic as an expression type, which can be declared directly as a field type on a model.\n\nOther times, it's more natural to express logic as a function (especially using [extension functions](#extension-function)).\n\nOften, it's a matter of style and taste. ## Extension functions Extension functions provide a natural, method-like syntax for operations that primarily work on a specific type, making your code more readable and discoverable. ### Basic extension functions ```taxi // Declare extension function declare extension function upperCase(String): String declare extension function isActive(Customer): Boolean // Usage as method calls find { Customer } as { name: CustomerName.upperCase() active: Customer.isActive() } ``` ### Extension functions with bodies Extension functions with implementation bodies allow you to define complex operations that can be called using intuitive method syntax. ```taxi // Type-based extension extension function formatPhone(PhoneNumber): String -> concat("(", PhoneNumber.substring(0, 3), ") ", PhoneNumber.substring(3, 6), "-", PhoneNumber.substring(6)) // With variable name extension function formatPhone(phone: PhoneNumber): String -> concat("(", phone.substring(0, 3), ") ", phone.substring(3, 6), "-", phone.substring(6)) // Collection extension with type reference extension function primaryApplicant(Individual[]): Individual -> Individual[].single((IsPrimaryApplicant) -> IsPrimaryApplicant == true) ``` ### Usage patterns Extension functions shine when you need to chain operations or when the primary parameter is the most important context for the operation. ```taxi // Direct type usage type FormattedPhone = PhoneNumber.formatPhone() // In queries find { Case } as { primary: Individual[].primaryApplicant() primaryPhone: Individual[].primaryApplicant().phoneNumber.formatPhone() } // In expressions with variable names model Account { customers: Customer[] activeCustomerCount: Int = this.customers.activeCount() } ``` ## Lambda functions Lambda functions enable higher-order operations where functions can accept other functions as parameters, allowing for flexible and reusable data processing patterns. They follow the same type-centric parameter approach: ```taxi // Function accepting a lambda declare function map(items: T[], mapper: (T) -> R): R[] declare function filter(items: T[], predicate: (T) -> Boolean): T[] // Usage with type-based parameters type CustomerNames = map(Customer[], (Customer) -> Customer::Name) type ActiveCustomers = filter(Customer[], (Customer) -> Customer::IsActive) // With variable names for clarity type CustomerNames = map(Customer[], (c: Customer) -> c::Name) type ActiveCustomers = filter(Customer[], (c: Customer) -> c::IsActive) ``` ### Multiple parameter lambdas ```taxi declare function reduce( items: T[], initial: R, reducer: (R, T) -> R ): R // Type-based parameters type TotalValue = reduce(Order[], 0, (Decimal, Order) -> Decimal + Order::Amount) // With variable names type TotalValue = reduce(Order[], 0, (total: Decimal, order: Order) -> total + order::Amount) ``` ### Collection operations with lambdas ```taxi // Type references customers.filter((Customer) -> Customer::IsActive == true) customers.map((Customer) -> Customer::EmailAddress) // Variable names customers.filter((c: Customer) -> c::IsActive == true) customers.map((c: Customer) -> c::EmailAddress) // Complex expressions with mixed approaches orders .filter((Order) -> Order::Amount > 100 && Order::Status == "Pending") .map((order: Order) -> order::Customer::Name) ``` ## Generic functions Generic functions provide type safety while working across different types: ```taxi // Generic function declaration declare function firstOrNull(T[]): T? declare function transform(T, transformer: (T) -> R): R // Generic extension function declare extension function isEmpty(T[]): Boolean // Usage maintains type safety type FirstCustomer = firstOrNull(Customer[]) // Returns Customer? type Names = transform(Customer[], (Customer[]) -> String[]) // Returns String[] ``` ## Function composition patterns ### Pipeline pattern ```taxi // Chained operations with type references function processCustomerName(String): String -> String.trim().lowerCase().capitalizeFirst() // With filtering and transformation function getActiveCustomerEmails(Customer[]): EmailAddress[] -> Customer[] .filter((Customer) -> Customer::IsActive == true) .filter((Customer) -> Customer::HasEmail == true) .map((Customer) -> Customer::EmailAddress) // Same logic with variable names for readability function getActiveCustomerEmails(customers: Customer[]): EmailAddress[] -> customers .filter((c: Customer) -> c::IsActive == true) .filter((c: Customer) -> c::HasEmail == true) .map((c: Customer) -> c::EmailAddress) ``` ### Conditional composition ```taxi function calculatePrice(basePrice: Decimal, customerType: CustomerType): Decimal -> when { customerType == "VIP" -> basePrice * 0.8 customerType == "Regular" -> basePrice * 0.95 else -> basePrice } ``` ### Nested function calls ```taxi // Type-based approach function formatCustomerSummary(Customer): String -> concat( Customer::FirstName.upperCase(), " ", Customer::LastName.upperCase(), " (", Customer::EmailAddress.lowerCase(), ")" ) // With variable for readability function formatCustomerSummary(customer: Customer): String -> concat( customer::FirstName.upperCase(), " ", customer::LastName.upperCase(), " (", customer::EmailAddress.lowerCase(), ")" ) ``` ### Advanced patterns ```taxi // Complex transformation showing type-centric approach function enrichWithCustomerData(Order[]): EnrichedOrder[] -> Order[].map((Order) -> EnrichedOrder { orderId: Order::OrderId, amount: Order::Amount, customerName: Order::Customer::Name, customerTier: Order::Customer::LoyaltyTier, isHighValue: Order::Amount > 500 }) // Same with variable names function enrichWithCustomerData(orders: Order[]): EnrichedOrder[] -> orders.map((order: Order) -> EnrichedOrder { orderId: order::OrderId, amount: order::Amount, customerName: order::Customer::Name, customerTier: order::Customer::LoyaltyTier, isHighValue: order::Amount > 500 }) ``` ## Standard library (StdLib) Taxi includes a comprehensive standard library of commonly-needed functions, covering string manipulation, numeric operations, and collection processing. Read more in the docs for [StdLib](https://taxilang.org/docs/language/stdlib) -------------------------------------------------------------------------------- # Expressions Source: https://taxilang.org/docs/taxiql/expressions-traversal Section: Working with data -------------------------------------------------------------------------------- Expressions allow defining business logic, computing and transforming data - either directly within a query, or on an expression type, which allows encpaulating business logic into reusiable types. ## Expressions in queries Expressions can be defined inline in a query body, or a `given` clause: **Example: Computing values with expressions** Expressions calculate values on-the-fly during query execution, eliminating the need for pre-computed fields Schema: ```taxi model Flight { flightNumber : FlightNumber inherits String totalSeats : TotalSeats inherits Int soldSeats : SoldSeats inherits Int } service FlightService { operation getFlights():Flight[] } ``` Query: ```taxi find { Flight[] } as { flightNumber : FlightNumber totalSeats : TotalSeats soldSeats : SoldSeats // Compute remaining seats remainingSeats : Int = this.totalSeats - this.soldSeats // Calculate utilization percentage utilization : Decimal = (SoldSeats / TotalSeats) * 100 }[] ``` ## Expression types Define reusable expressions as types: ```taxi // Define expression type type RemainingSeats = TotalSeats - SoldSeats type UtilizationRate = SoldSeats / TotalSeats // Use in projections find { Flight[] } as { flightNumber : FlightNumber remaining : RemainingSeats utilization : UtilizationRate }[] ``` > **Note: Type expressions** > > Expression types can only reference other types, not field names. They provide reusable business logic. ### Using expression types to filter collections Create types that filter or transform collections: ```taxi type IsActive inherits Boolean type IsPrimary inherits Boolean // Filter to single item type ActiveAddress inherits Address = (Address[]) -> Address[].single((IsActive) -> IsActive == true) // Filter to collection type ActiveAddresses inherits Address[] = (Address[]) -> Address[].filter((IsActive) -> IsActive == true) // With negation type InactiveAddresses inherits Address[] = (Address[]) -> Address[].filter((IsActive) -> IsActive != true) ``` ### Collection operations ```taxi // single - find one item type PrimaryContact = (Contact[]) -> Contact[].single((IsPrimary) -> IsPrimary == true) // filter - find multiple items type ActiveContacts = (Contact[]) -> Contact[].filter((IsActive) -> IsActive == true) // map - transform items type ContactNames = (Contact[]) -> Contact[].map((Contact) -> Contact::FullName) ``` ## Combining traversal and expressions Expression types can be traversed using standard type traversal ```taxi // Expression type using traversal type PrimaryEmail inherits EmailAddress = PrimaryContact::EmailAddress // In a query find { Case } as { // Combines traversal with expression types mainContactEmail: PrimaryApplicant::ActiveAddress::EmailAddress // Multiple levels of expression types primaryPhone: PrimaryContact::PreferredPhone } ``` ## Data discovery in expressions When evaluating expressions, TaxiQL: 1. First checks the source object for required values 2. Then searches available data in scope 3. Finally calls services to fetch missing data ```taxi model Order { basePrice : Price customerId : CustomerId } // DiscountRate not on Order, will be discovered find { Order[] } as { price : Price finalPrice : Decimal = Price * (1 - DiscountRate) }[] ``` ## Conditional expressions ```taxi type PricingTier = when { OrderTotal > 1000 -> "Premium" OrderTotal > 100 -> "Standard" else -> "Basic" } ``` ## Advanced examples ### Nested lambda expressions ```taxi // Find customers with active premium accounts type PremiumActiveCustomers = (Customer[]) -> Customer[] .filter((HasAccount) -> HasAccount == true) .filter((Account) -> Account::Status == "Premium") .filter((Account) -> Account::IsActive == true) ``` ### Expression composition ```taxi // Compose multiple expression types type DiscountedPrice = BasePrice * DiscountMultiplier type TaxAmount = DiscountedPrice * TaxRate type FinalPrice = DiscountedPrice + TaxAmount find { Order[] } as { orderId : OrderId final : FinalPrice // Uses composed expressions }[] ``` ## Common use cases ### Business calculations ```taxi type GrossMargin = (Revenue - CostOfGoods) / Revenue type NetProfit = Revenue - TotalExpenses type ProfitMargin = NetProfit / Revenue * 100 ``` ### Dynamic categorization ```taxi type CustomerSegment = when { LifetimeValue > 10000 && LastOrderDays < 30 -> "VIP Active" LifetimeValue > 10000 -> "VIP Dormant" LastOrderDays < 90 -> "Regular Active" else -> "At Risk" } ``` -------------------------------------------------------------------------------- # Taxi Stdlib Source: https://taxilang.org/docs/language/stdlib-summary Section: Working with data -------------------------------------------------------------------------------- The Taxi Standard Library provides a collection of built-in functions for common operations including string manipulation, date handling, math, collections, and more. ## Strings A collection of functions for manipulating strings | Function | Description | |----------|-------------| | [`applyFormat`](https://taxilang.org/docs/language/stdlib/applyFormat) | `applyFormat(input: Any, format: String):String` Applies a format string to the input value using [Java Formatter](https://docs.oracle.com/javase/8/docs/api/java/util... | | [`concat`](https://taxilang.org/docs/language/stdlib/concat) | `concat(Any...):String` Joins all provided values into a single string, in the order they are passed. | | [`containsPattern`](https://taxilang.org/docs/language/stdlib/containsPattern) | `containsPattern(source: String, regex: String): Boolean` Returns `true` if any part of the source string matches the provided regular expression. | | [`containsString`](https://taxilang.org/docs/language/stdlib/containsString) | `containsString(source:String, valueToSearchFor:String):Boolean` Returns `true` if `source` contains `valueToSearchFor` as a substring. | | [`endsWith`](https://taxilang.org/docs/language/stdlib/endsWith) | `endsWith(source: String, valueToSearchFor:String):Boolean` Returns `true` if `source` ends with the specified suffix. | | [`indexOf`](https://taxilang.org/docs/language/stdlib/indexOf) | `indexOf(source:String, valueToSearchFor:String):Int` Returns the zero-based index of `valueToSearchFor` within `source`, or `-1` if not found. | | [`left`](https://taxilang.org/docs/language/stdlib/left) | `left(source:String, count:Int):String` Returns the leftmost `count` characters from a string. | | [`length`](https://taxilang.org/docs/language/stdlib/length) | `length(String):Int` Returns the number of characters in the string. | | [`lowerCase`](https://taxilang.org/docs/language/stdlib/lowerCase) | `lowerCase(String):String` Converts all characters in the string to lower case. | | [`matches`](https://taxilang.org/docs/language/stdlib/matches) | `matches(source: String, regex: String): Boolean` Returns `true` if the **entire** source string matches the provided regular expression. | | [`mid`](https://taxilang.org/docs/language/stdlib/mid) | `mid(source: String, startIndex: Int, endIndex: Int):String` Returns the middle of a string, starting at `startIndex` (inclusive) and ending just before `endIndex` (exclusive). | | [`padEnd`](https://taxilang.org/docs/language/stdlib/padEnd) | `padEnd(input: String, length: Int, padWith: String):String` Pads the string to the specified total `length` by appending `padWith` characters at the end. | | [`padStart`](https://taxilang.org/docs/language/stdlib/padStart) | `padStart(input: String, length: Int, padWith: String):String` Pads the string to the specified total `length` by prepending `padWith` characters at the start. | | [`replace`](https://taxilang.org/docs/language/stdlib/replace) | `replace(source: String, searchValue:String, replacement: String):String` Replaces all occurrences of `searchValue` in `source` with `replacement`, returning a new string. | | [`right`](https://taxilang.org/docs/language/stdlib/right) | `right(source:String, count:Int):String` Returns the rightmost `count` characters from a string. | | [`startsWith`](https://taxilang.org/docs/language/stdlib/startsWith) | `startsWith(source: String, valueToSearchFor:String):Boolean` Returns `true` if `source` begins with the specified prefix. | | [`trim`](https://taxilang.org/docs/language/stdlib/trim) | `trim(String):String` Removes leading and trailing whitespace from a string. | | [`upperCase`](https://taxilang.org/docs/language/stdlib/upperCase) | `upperCase(String):String` Converts all characters in the string to upper case. | ## Collections A collection of functions for operating on collections | Function | Description | |----------|-------------| | [`all`](https://taxilang.org/docs/language/stdlib/all) | ` all(collection: T[], predicate: (T) -> Boolean): Boolean` Returns `true` if every item in the collection satisfies the predicate, or `false` if any item does not. | | [`allOf`](https://taxilang.org/docs/language/stdlib/allOf) | `allOf(values:Boolean...): Boolean` Returns `true` only if all provided boolean values are `true`. | | [`any`](https://taxilang.org/docs/language/stdlib/any) | ` any(collection: T[], predicate: (T) -> Boolean): Boolean` Returns `true` if at least one item in the collection satisfies the predicate, or `false` if none do. | | [`anyOf`](https://taxilang.org/docs/language/stdlib/anyOf) | `anyOf(values:Boolean...): Boolean` Returns `true` if at least one of the provided boolean values is `true`. | | [`append`](https://taxilang.org/docs/language/stdlib/append) | ` append(array1: T[], array2: T[]):T[]` Returns a new collection containing the elements of the first array followed by the elements of the second array. | | [`collectAllInstances`](https://taxilang.org/docs/language/stdlib/collectAllInstances) | ` collectAllInstances(collection: lang.taxi.Type): T` Collects and merges all instances of a type from the current data context, bypassing ambiguity checks. | | [`contains`](https://taxilang.org/docs/language/stdlib/contains) | ` contains(collection: T[], searchTarget:T): Boolean` Returns `true` if the collection contains the specified search target element. | | [`containsAll`](https://taxilang.org/docs/language/stdlib/containsAll) | ` containsAll(collection: T[], searchTarget:T[]): Boolean` Returns `true` if the collection contains every element in the search targets array. | | [`containsAny`](https://taxilang.org/docs/language/stdlib/containsAny) | ` containsAny(collection: T[], searchTarget:T[]): Boolean` Returns `true` if the collection contains at least one of the specified search targets. | | [`exactlyOne`](https://taxilang.org/docs/language/stdlib/exactlyOne) | ` exactlyOne(collection:T[]):T` Returns the only item from the provided collection, or throws an error if there isn't exactly one item in the collect... | | [`filter`](https://taxilang.org/docs/language/stdlib/filter) | ` filter(collection:T[], callback: (T) -> Boolean):T[]` Returns a new collection containing only the members of the original collection that match the provided predicate. | | [`filterEach`](https://taxilang.org/docs/language/stdlib/filterEach) | ` filterEach(item: T, callback: (T) -> Boolean):T?` Evaluates the predicate against the provided item, returning the item if the predicate returns true, or null otherwise. | | [`first`](https://taxilang.org/docs/language/stdlib/first) | ` first(collection: T[]):T` Returns the first item within the collection. | | [`getAtIndex`](https://taxilang.org/docs/language/stdlib/getAtIndex) | ` getAtIndex(collection: T[], index: Int):T` Returns the item at the provided index in the collection. | | [`ifEmpty`](https://taxilang.org/docs/language/stdlib/ifEmpty) | ` ifEmpty(source:T[], defaultValue:T[]): T[]` Returns the source array if it is non-empty, otherwise returns the provided default array. | | [`indexOfItem`](https://taxilang.org/docs/language/stdlib/indexOfItem) | ` indexOfItem(collection:T[], searchItem: T):Int` Returns the zero-based index of an item within a collection, or `-1` if the item was not found. | | [`intersection`](https://taxilang.org/docs/language/stdlib/intersection) | ` intersection(collectionA: T[], collectionB: T[]):T[]` Returns a new collection containing only the elements that are present in both of the provided collections. | | [`isNullOrEmpty`](https://taxilang.org/docs/language/stdlib/isNullOrEmpty) | ` isNullOrEmpty(collection:T[]):Boolean` Returns `true` if the provided collection is either `null` or contains zero elements. | | [`joinToString`](https://taxilang.org/docs/language/stdlib/joinToString) | ` joinToString(values:T[], separator: String = ",", prefix: String? = null, postfix: String? = null): String` Creates a string from all the elements separated using `separator` and using the given `prefix` and `postfix` if supp... | | [`last`](https://taxilang.org/docs/language/stdlib/last) | ` last(collection: T[]):T` Returns the last item within the collection. | | [`listOf`](https://taxilang.org/docs/language/stdlib/listOf) | ` listOf(values:T...):T[]` Creates and returns an array containing all the provided values. | | [`none`](https://taxilang.org/docs/language/stdlib/none) | ` none(collection: T[], predicate: (T) -> Boolean): Boolean` Returns `true` if none of the items in the collection satisfy the predicate. | | [`noneOf`](https://taxilang.org/docs/language/stdlib/noneOf) | `noneOf(values:Boolean...): Boolean` Returns `true` only if all provided boolean values are `false`. | | [`orEmpty`](https://taxilang.org/docs/language/stdlib/orEmpty) | ` orEmpty(source:T[]): T[]` Returns the source array unchanged if it is non-null, or an empty array if it is `null`. | | [`single`](https://taxilang.org/docs/language/stdlib/single) | ` single(collection:T[], callback: (T) -> Boolean):T` Returns the first item from a collection that matches the provided predicate. | | [`singleBy`](https://taxilang.org/docs/language/stdlib/singleBy) | ` singleBy(collection:T[], groupingFunction: (T) -> A, searchValue: A):T` Returns the item from a collection where the value returned by the grouping function matches the search value. | | [`size`](https://taxilang.org/docs/language/stdlib/size) | ` size(collection:T[]):Int` Returns the number of elements in the collection. | ## Dates Mess about with time. Flux capacitor not included | Function | Description | |----------|-------------| | [`addDays`](https://taxilang.org/docs/language/stdlib/addDays) | ` addDays(T, Int):T` Adds the specified number of days to a date, datetime, or instant. | | [`addMinutes`](https://taxilang.org/docs/language/stdlib/addMinutes) | ` addMinutes(T, Int):T` Adds the specified number of minutes to a datetime or instant. | | [`addSeconds`](https://taxilang.org/docs/language/stdlib/addSeconds) | ` addSeconds(T, Int):T` Adds the specified number of seconds to a datetime or instant. | | [`currentDate`](https://taxilang.org/docs/language/stdlib/currentDate) | `currentDate():Date` Returns the current local date (no time component). | | [`currentDateTime`](https://taxilang.org/docs/language/stdlib/currentDateTime) | `currentDateTime():DateTime` Returns the current local date and time. | | [`currentTime`](https://taxilang.org/docs/language/stdlib/currentTime) | `currentTime():Time` Returns the current local time (no date component). | | [`now`](https://taxilang.org/docs/language/stdlib/now) | `now():Instant` Returns the current UTC instant. | | [`parseDate`](https://taxilang.org/docs/language/stdlib/parseDate) | ` parseDate(String):T` Parses a string into a date, datetime, or instant value. | ## Math Numbers 'n' such. Maths for the brits. | Function | Description | |----------|-------------| | [`average`](https://taxilang.org/docs/language/stdlib/average) | `average(values: Any[]):Decimal` Returns the arithmetic mean of a collection of numeric values. | | [`max`](https://taxilang.org/docs/language/stdlib/max) | ` max(collection: T[], callback: (T) -> A):A` Returns the maximum value in the collection, computed by applying the selector function to each element. | | [`min`](https://taxilang.org/docs/language/stdlib/min) | ` min(collection: T[], callback: (T) -> A):A` Returns the minimum value in the collection, computed by applying the selector function to each element. | | [`round`](https://taxilang.org/docs/language/stdlib/round) | `round(value: Decimal, precision: Int = 0): Decimal` Rounds a decimal value to the specified number of decimal places. | | [`sum`](https://taxilang.org/docs/language/stdlib/sum) | ` sum(collection: T[], callback: (T) -> A):A` Returns the sum of values in the collection, computed by applying the selector function to each element. | ## Objects Utilities for dealing with equality, etc | Function | Description | |----------|-------------| | [`emptyInstance`](https://taxilang.org/docs/language/stdlib/emptyInstance) | ` emptyInstance(instanceType:lang.taxi.Type): A` Returns an instance of the requested type with all fields set to their empty state: scalars are `null`, collections a... | | [`equals`](https://taxilang.org/docs/language/stdlib/equals) | ` equals(a:A, b:B): Boolean` Returns `true` if two values are equal. | ## Enums Utilities for enums | Function | Description | |----------|-------------| | [`enumForName`](https://taxilang.org/docs/language/stdlib/enumForName) | ` enumForName(enumType: lang.taxi.Type, enumName: String): T` Returns the enum member matching the provided value string. | | [`hasEnumNamed`](https://taxilang.org/docs/language/stdlib/hasEnumNamed) | ` hasEnumNamed(enumType: lang.taxi.Type, enumName: String): Boolean` Returns `true` if the enum has an explicitly declared member matching the provided name. | ## Aggregations Functions for aggregating data within transformations. | Function | Description | |----------|-------------| | [`sumOver`](https://taxilang.org/docs/language/stdlib/sumOver) | `declare query function sumOver(Any...):Decimal` A Vyne-specific aggregate function used in analytical queries to compute a running or grouped sum. | ## Functional Functions that are functionally functions. Funky | Function | Description | |----------|-------------| | [`fold`](https://taxilang.org/docs/language/stdlib/fold) | ` fold(collection: T[], initial: A, callback: (T,A) -> A):A` Iterates over a collection, combining elements with an accumulator to produce a single result. | | [`map`](https://taxilang.org/docs/language/stdlib/map) | ` map(collection: T[], callback: (T) -> A):A[]` Applies a transformation function to every element in a collection, returning a new array of the results. | | [`reduce`](https://taxilang.org/docs/language/stdlib/reduce) | ` reduce(collection: T[], callback: (T,A) -> A):A` Reduces a collection to a single value by applying a combining function to each element and the accumulated result. | ## Transformations Functions for converting between types | Function | Description | |----------|-------------| | [`convert`](https://taxilang.org/docs/language/stdlib/convert) | ` convert(source: Any, targetType: lang.taxi.Type): T` Converts a source value to the target type using only the data already available — no service calls are made. | | [`toRawType`](https://taxilang.org/docs/language/stdlib/toRawType) | `toRawType(source: Any):Any` Removes the semantic typing from a scalar value, returning it typed as its raw primitive. | ## Parsing Functions for converting between types | Function | Description | |----------|-------------| | [`parseJson`](https://taxilang.org/docs/language/stdlib/parseJson) | ` parseJson(source: String, type: Type):T` Parses a JSON string into the specified type. | ## Errors Functions for creating and handling errors | Function | Description | |----------|-------------| | [`throw`](https://taxilang.org/docs/language/stdlib/throw) | `throw(error:Any):Nothing` Throws an error, halting execution. | -------------------------------------------------------------------------------- # addDays Source: https://taxilang.org/docs/language/stdlib/addDays Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.dates.addDays` ## Signature ```taxi declare function addDays(T, Int):T ``` ## Description Adds the specified number of days to a date, datetime, or instant. Use a negative value to subtract days. ```taxi // Calculate an expiry date 90 days from today find { @Format("yyyy-MM-dd") expiresOn: Date = addDays(Date, 90) } ``` ## Examples Adds days to a date — positive to advance, negative to go back. Query: ```taxi given { start: Date = parseDate("2024-01-15") } find { @Format("yyyy-MM-dd") nextWeek: Date = addDays(Date, 7) @Format("yyyy-MM-dd") lastMonth: Date = addDays(Date, -30) } ``` Result: ```json { "nextWeek": "2024-01-22", "lastMonth": "2023-12-16" } ``` -------------------------------------------------------------------------------- # addMinutes Source: https://taxilang.org/docs/language/stdlib/addMinutes Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.dates.addMinutes` ## Signature ```taxi declare function addMinutes(T, Int):T ``` ## Description Adds the specified number of minutes to a datetime or instant. Use a negative value to subtract minutes. ```taxi // Schedule a meeting 30 minutes from the event start find { meetingEnd: DateTime = addMinutes(DateTime, 30) } ``` ## Examples Adds minutes to a datetime — positive to advance, negative to go back. Query: ```taxi given { start: DateTime = parseDate("2024-01-15T10:30:00") } find { @Format("yyyy-MM-dd HH:mm") plusFive: DateTime = addMinutes(DateTime, 5) @Format("yyyy-MM-dd HH:mm") minusTwo: DateTime = addMinutes(DateTime, -120) } ``` Result: ```json { "plusFive": "2024-01-15 10:35", "minusTwo": "2024-01-15 08:30" } ``` -------------------------------------------------------------------------------- # addSeconds Source: https://taxilang.org/docs/language/stdlib/addSeconds Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.dates.addSeconds` ## Signature ```taxi declare function addSeconds(T, Int):T ``` ## Description Adds the specified number of seconds to a datetime or instant. Use a negative value to subtract seconds. ```taxi // Expire a token 3600 seconds (1 hour) from now find { expiresAt: Instant = addSeconds(Instant, 3600) } ``` ## Examples Adds seconds to an instant — positive to advance, negative to go back. Query: ```taxi given { start: Instant = parseDate("2024-01-15T10:30:00Z") } find { @Format("yyyy-MM-dd HH:mm:ss") plusThirty: Instant = addSeconds(Instant, 30) @Format("yyyy-MM-dd HH:mm:ss") minusTen: Instant = addSeconds(Instant, -10) } ``` Result: ```json { "plusThirty": "2024-01-15 10:30:30", "minusTen": "2024-01-15 10:29:50" } ``` -------------------------------------------------------------------------------- # all Source: https://taxilang.org/docs/language/stdlib/all Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.all` ## Signature ```taxi declare extension function all(collection: T[], predicate: (T) -> Boolean): Boolean ``` ## Description Returns `true` if every item in the collection satisfies the predicate, or `false` if any item does not. Returns `true` for an empty collection (vacuous truth). ```taxi // Check if every task in a sprint is complete find { sprint: Sprint } as { allDone: Boolean = sprint.tasks.all((task) -> task.isComplete) } ``` ## Examples Returns `true` when every item in the collection satisfies the predicate. Schema: ```taxi model Task { name: String isComplete: Boolean } ``` Query: ```taxi given { tasks: Task[] = [ { name: 'Setup', isComplete: true }, { name: 'Build', isComplete: true }, { name: 'Deploy', isComplete: true } ] } find { allDone: Boolean = tasks.all((task: Task) -> task.isComplete) } ``` Result: ```json { "allDone": true } ``` Returns `false` when at least one item does not satisfy the predicate. Schema: ```taxi model Task { name: String isComplete: Boolean } ``` Query: ```taxi given { tasks: Task[] = [ { name: 'Setup', isComplete: true }, { name: 'Build', isComplete: true }, { name: 'Deploy', isComplete: false } ] } find { allDone: Boolean = tasks.all((task: Task) -> task.isComplete) } ``` Result: ```json { "allDone": false } ``` -------------------------------------------------------------------------------- # allOf Source: https://taxilang.org/docs/language/stdlib/allOf Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.allOf` ## Signature ```taxi declare function allOf(values:Boolean...): Boolean ``` ## Description Returns `true` only if all provided boolean values are `true`. Equivalent to logical AND. This function ensures that every condition in a set must be satisfied. ```taxi // Check if a user can purchase a premium item type CanPurchasePremium inherits Boolean by allOf(IsActive, HasSufficientFunds, IsVerified) ``` ## Examples This example demonstrates how to use `allOf()` to verify if a user meets all required criteria for premium access. Schema: ```taxi model Customer { name: String isActive: IsActive inherits Boolean hasSufficientFunds: HasSufficientFunds inherits Boolean isVerified: IsVerified inherits Boolean } type HasPremiumAccess inherits Boolean by allOf(IsActive, HasSufficientFunds, IsVerified) ``` Query: ```taxi given { customer: Customer = { name: 'Bob', isActive: true, hasSufficientFunds: true, isVerified: true } } find { name: customer.name hasPremiumAccess: HasPremiumAccess } ``` Result: ```json { "name": "Bob", "hasPremiumAccess": true } ``` -------------------------------------------------------------------------------- # any Source: https://taxilang.org/docs/language/stdlib/any Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.any` ## Signature ```taxi declare extension function any(collection: T[], predicate: (T) -> Boolean): Boolean ``` ## Description Returns `true` if at least one item in the collection satisfies the predicate, or `false` if none do. Returns `false` for an empty collection. ```taxi // Check whether any order in a batch is flagged find { batch: OrderBatch } as { needsAttention: Boolean = batch.orders.any((order) -> order.isFlagged) } ``` ## Examples Returns `true` when at least one item satisfies the predicate. Schema: ```taxi model Task { name: String isComplete: Boolean } ``` Query: ```taxi given { tasks: Task[] = [ { name: 'Setup', isComplete: true }, { name: 'Build', isComplete: false }, { name: 'Deploy', isComplete: false } ] } find { anyDone: Boolean = tasks.any((task: Task) -> task.isComplete) } ``` Result: ```json { "anyDone": true } ``` -------------------------------------------------------------------------------- # anyOf Source: https://taxilang.org/docs/language/stdlib/anyOf Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.anyOf` ## Signature ```taxi declare function anyOf(values:Boolean...): Boolean ``` ## Description Returns `true` if at least one of the provided boolean values is `true`. Equivalent to logical OR. This function evaluates multiple conditions and returns true if any of them are true. ```taxi // Check if a user has any account verification type IsVerified inherits Boolean by anyOf(HasEmailVerification, HasPhoneVerification, HasIdVerification) ``` ## Examples Returns `true` when at least one of the provided boolean conditions is `true`. Define a computed type in the schema using `anyOf` for reuse across queries. Schema: ```taxi model User { name: String hasEmailVerification: HasEmailVerification inherits Boolean hasPhoneVerification: HasPhoneVerification inherits Boolean hasIdVerification: HasIdVerification inherits Boolean } type IsVerified inherits Boolean by anyOf(HasEmailVerification, HasPhoneVerification, HasIdVerification) ``` Query: ```taxi given { user: User = { name: 'Alice', hasEmailVerification: false, hasPhoneVerification: true, hasIdVerification: false } } find { name: user.name isVerified: IsVerified } ``` Result: ```json { "name": "Alice", "isVerified": true } ``` -------------------------------------------------------------------------------- # append Source: https://taxilang.org/docs/language/stdlib/append Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.append` ## Signature ```taxi declare extension function append(array1: T[], array2: T[]):T[] ``` ## Description Returns a new collection containing the elements of the first array followed by the elements of the second array. Neither input array is modified. ```taxi find { cart: ShoppingCart } as { allItems: cart.regularItems.append(cart.saleItems) } ``` ## Examples Concatenates two arrays, returning a new array with elements of the first followed by elements of the second. Schema: ```taxi model Bag { items: String[] } ``` Query: ```taxi given { bag1: Bag = { items: ['apple', 'banana'] }, bag2: Bag = { items: ['cherry', 'date'] } } find { combined: String[] = bag1.items.append(bag2.items) } ``` Result: ```json { "combined": [ "apple", "banana", "cherry", "date" ] } ``` -------------------------------------------------------------------------------- # applyFormat Source: https://taxilang.org/docs/language/stdlib/applyFormat Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.applyFormat` ## Signature ```taxi declare extension function applyFormat(input: Any, format: String):String ``` ## Description Applies a format string to the input value using [Java Formatter](https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html) conventions. Accepts any value (string, number, date, etc.) as input. The format string controls width, padding, precision, and alignment. ```taxi // Zero-pad an integer to 6 digits find { order: Order } as { paddedId: order.id.applyFormat('%06d') } ``` ## Examples Formats an integer with zero-padding using `%05d`. Query: ```taxi find { formatted: String = 42.applyFormat('%05d') } ``` Result: ```json { "formatted": "00042" } ``` Left-justifies a string in a 10-character wide field using `%-10s`. Query: ```taxi find { padded: String = 'hello'.applyFormat('%-10s') } ``` Result: ```json { "padded": "hello " } ``` -------------------------------------------------------------------------------- # average Source: https://taxilang.org/docs/language/stdlib/average Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.average` ## Signature ```taxi declare extension function average(values: Any[]):Decimal ``` ## Description Returns the arithmetic mean of a collection of numeric values. All values in the collection must be of the same numeric primitive type (Int, Decimal, etc.). Returns null if the collection is empty or contains non-numeric values. ```taxi find { order: Order } as { averageItemPrice: Decimal = order.items.map((Item) -> Item::price).average() } ``` ## Examples Computes the average score from a collection of items. Schema: ```taxi model Item { score: Score inherits Decimal } ``` Query: ```taxi given { items: Item[] = [ { score: 80.0 }, { score: 90.0 }, { score: 70.0 } ] } find { avg: Decimal = average(items.map((item: Item) -> item.score)) } ``` Result: ```json { "avg": 80 } ``` -------------------------------------------------------------------------------- # collectAllInstances Source: https://taxilang.org/docs/language/stdlib/collectAllInstances Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.collectAllInstances` ## Signature ```taxi declare extension function collectAllInstances(collection: lang.taxi.Type): T ``` ## Description Collects and merges all instances of a type from the current data context, bypassing ambiguity checks. Without this function, a search for `T[]` will return null when multiple separate collections of `T` exist (the result is considered ambiguous). `collectAllInstances` disables that check and concatenates all found instances into a single collection. ```taxi // Merge homeAddresses and workAddresses into one list find { person: Person } as { allAddresses: collectAllInstances(Address) } ``` ## Examples Merges all `Address` collections on a `Person` into a single flat list, even though they live in separate fields. Schema: ```taxi model Address { street: Street inherits String } model Person { name: Name inherits String homeAddresses: Address[] workAddresses: Address[] } ``` Query: ```taxi given { person: Person = { name: 'Jimmy', homeAddresses: [ { street: 'Oxford St' }, { street: 'Regent St' } ], workAddresses: [ { street: 'Tot St' } ] } } find { name: person.name allAddresses: collectAllInstances(Address) } ``` Result: ```json { "name": "Jimmy", "allAddresses": [ { "street": "Oxford St" }, { "street": "Regent St" }, { "street": "Tot St" } ] } ``` -------------------------------------------------------------------------------- # concat Source: https://taxilang.org/docs/language/stdlib/concat Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.concat` ## Signature ```taxi declare function concat(Any...):String ``` ## Description Joins all provided values into a single string, in the order they are passed. Null values are omitted. ```taxi find { person: Person } as { fullName: String = concat(person.firstName, ' ', person.lastName) } ``` ## Examples Concatenates multiple string values into a single string. Query: ```taxi find { greeting: String = concat('Hello', ', ', 'World') } ``` Result: ```json { "greeting": "Hello, World" } ``` -------------------------------------------------------------------------------- # contains Source: https://taxilang.org/docs/language/stdlib/contains Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.contains` ## Signature ```taxi declare extension function contains(collection: T[], searchTarget:T): Boolean ``` ## Description Returns `true` if the collection contains the specified search target element. This function checks whether a given element exists within an array or collection. ```taxi // Check if a user's roles include admin privileges find { user: User } as { hasAdminAccess: user.roles.contains("ADMIN") } ``` ## Examples Checks whether a collection contains a specific element using type-based access. Schema: ```taxi type Role inherits String model User { username: Username inherits String roles: Role[] } ``` Query: ```taxi given { user: User = { username: 'AdminUser', roles: ['USER', 'EDITOR', 'ADMIN'] } } find { username: Username isAdmin: Boolean = Role[].contains('ADMIN') isGuest: Boolean = Role[].contains('GUEST') } ``` Result: ```json { "username": "AdminUser", "isAdmin": true, "isGuest": false } ``` -------------------------------------------------------------------------------- # containsAll Source: https://taxilang.org/docs/language/stdlib/containsAll Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.containsAll` ## Signature ```taxi declare extension function containsAll(collection: T[], searchTarget:T[]): Boolean ``` ## Description Returns `true` if the collection contains every element in the search targets array. Returns `true` when `searchTargets` is empty (an empty set is a subset of every set). ```taxi // Check whether a product has all required tags find { hasCertified: Boolean = ProductTag[].containsAll(['certified', 'in-stock']) } ``` **Known limitation:** When the collection uses a semantic type (e.g. `ProductTag inherits String`), passing string literals as `searchTargets` always returns `false` due to a type comparison bug. See `problems/containsAll.md`. Use a `String[]` collection as a workaround. ## Examples Returns `true` when the collection contains all specified search values, `false` if any are missing. Schema: ```taxi model Product { name: String tags: String[] } ``` Query: ```taxi given { product: Product = { name: 'Laptop', tags: ['electronics', 'portable', 'work'] } } find { hasBothTags: Boolean = product.tags.containsAll(['electronics', 'portable']) missingTag: Boolean = product.tags.containsAll(['electronics', 'gaming']) } ``` Result: ```json { "hasBothTags": true, "missingTag": false } ``` -------------------------------------------------------------------------------- # containsAny Source: https://taxilang.org/docs/language/stdlib/containsAny Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.containsAny` ## Signature ```taxi declare extension function containsAny(collection: T[], searchTarget:T[]): Boolean ``` ## Description Returns `true` if the collection contains at least one of the specified search targets. Returns `true` when `searchTargets` is empty (symmetrical with `containsAll`). ```taxi // Check whether a product has any of a set of tags find { isRelevant: Boolean = ProductTag[].containsAny(['sale', 'featured', 'new']) } ``` **Known limitation:** When the collection uses a semantic type (e.g. `ProductTag inherits String`), passing string literals as `searchTargets` always returns `false` due to a type comparison bug. See `problems/containsAny.md`. Use a `String[]` collection as a workaround. ## Examples Returns `true` when the collection contains at least one of the search values, `false` when none match. Schema: ```taxi model Product { name: String tags: String[] } ``` Query: ```taxi given { product: Product = { name: 'Laptop', tags: ['electronics', 'portable', 'work'] } } find { hasAnyTag: Boolean = product.tags.containsAny(['gaming', 'portable']) hasNone: Boolean = product.tags.containsAny(['gaming', 'sports']) } ``` Result: ```json { "hasAnyTag": true, "hasNone": false } ``` -------------------------------------------------------------------------------- # containsPattern Source: https://taxilang.org/docs/language/stdlib/containsPattern Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.containsPattern` ## Signature ```taxi declare extension function containsPattern(source: String, regex: String): Boolean ``` ## Description Returns `true` if any part of the source string matches the provided regular expression. Uses partial matching — the regex does not need to match the entire string. Use `^` and `$` anchors for full-string matching. ```taxi // Partial match — returns true if the string contains the pattern anywhere find { startsWithHe: Boolean = "hello".containsPattern("^he") containsEll: Boolean = "hello".containsPattern("ell") } ``` ## Examples Demonstrates partial matching: anchor patterns, substring patterns, and non-matching patterns. Query: ```taxi find { matchesStart: Boolean = "hello".containsPattern("^he") matchesMiddle: Boolean = "hello".containsPattern("ell") noMatch: Boolean = "hello".containsPattern("xyz") } ``` Result: ```json { "matchesStart": true, "matchesMiddle": true, "noMatch": false } ``` -------------------------------------------------------------------------------- # containsString Source: https://taxilang.org/docs/language/stdlib/containsString Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.containsString` ## Signature ```taxi declare extension function containsString(source:String, valueToSearchFor:String):Boolean ``` ## Description Returns `true` if `source` contains `valueToSearchFor` as a substring. The match is case-sensitive. ```taxi // Check if a description mentions a keyword find { mentionsDiscount: Boolean = Description.containsString("discount") } ``` ## Examples Checks for a case-sensitive substring match. Query: ```taxi find { hasSubstring: Boolean = "hello world".containsString("world") caseSensitive: Boolean = "hello world".containsString("World") } ``` Result: ```json { "hasSubstring": true, "caseSensitive": false } ``` -------------------------------------------------------------------------------- # convert Source: https://taxilang.org/docs/language/stdlib/convert Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.convert` ## Signature ```taxi declare extension function convert(source: Any, targetType: lang.taxi.Type): T ``` ## Description Converts a source value to the target type using only the data already available — no service calls are made. This is faster than a full projection (`source as TargetType`), but less powerful: only the fields present in the source are mapped. Missing fields are not discovered from services. ```taxi // Convert an Order to a Receipt using only the Order's own fields find { receipt: Receipt = Order.convert(Receipt) } ``` ## Examples Converts an Order to a Receipt model using only the fields present in the source, without calling any services. Schema: ```taxi model Order { id: OrderId inherits String amount: Amount inherits Decimal } model Receipt { id: OrderId amount: Amount } ``` Query: ```taxi given { order: Order = { id: 'O1', amount: 99.99 } } find { receipt: Receipt = Order.convert(Receipt) } ``` Result: ```json { "receipt": { "id": "O1", "amount": 99.99 } } ``` -------------------------------------------------------------------------------- # currentDate Source: https://taxilang.org/docs/language/stdlib/currentDate Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.dates.currentDate` ## Signature ```taxi declare function currentDate():Date ``` ## Description Returns the current local date (no time component). ```taxi find { @Format("yyyy-MM-dd") today: Date = currentDate() } ``` -------------------------------------------------------------------------------- # currentDateTime Source: https://taxilang.org/docs/language/stdlib/currentDateTime Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.dates.currentDateTime` ## Signature ```taxi declare function currentDateTime():DateTime ``` ## Description Returns the current local date and time. ```taxi find { @Format("yyyy-MM-dd HH:mm:ss") now: DateTime = currentDateTime() } ``` -------------------------------------------------------------------------------- # currentTime Source: https://taxilang.org/docs/language/stdlib/currentTime Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.dates.currentTime` ## Signature ```taxi declare function currentTime():Time ``` ## Description Returns the current local time (no date component). ```taxi find { @Format("HH:mm:ss") timeNow: Time = currentTime() } ``` -------------------------------------------------------------------------------- # emptyInstance Source: https://taxilang.org/docs/language/stdlib/emptyInstance Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.emptyInstance` ## Signature ```taxi declare extension function emptyInstance(instanceType:lang.taxi.Type): A ``` ## Description Returns an instance of the requested type with all fields set to their empty state: scalars are `null`, collections are `[]`, and nested objects are recursively emptied. ```taxi // Create a blank template of a type find { blank: MyModel = emptyInstance(MyModel) } ``` ## Examples Creates an empty instance of a model: scalars are null and collections are empty arrays. Schema: ```taxi type UserTag inherits String model User { name: UserName inherits String age: Age inherits Int tags: UserTag[] } ``` Query: ```taxi find { empty: User = emptyInstance(User) } ``` Result: ```json { "empty": { "name": null, "age": null, "tags": [] } } ``` -------------------------------------------------------------------------------- # endsWith Source: https://taxilang.org/docs/language/stdlib/endsWith Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.endsWith` ## Signature ```taxi declare extension function endsWith(source: String, valueToSearchFor:String):Boolean ``` ## Description Returns `true` if `source` ends with the specified suffix. The match is case-sensitive. ```taxi // Check if a filename has a specific extension find { isCsv: Boolean = FileName.endsWith(".csv") } ``` ## Examples Checks whether a string ends with a given suffix, case-sensitively. Query: ```taxi find { hasSuffix: Boolean = "hello.csv".endsWith(".csv") nope: Boolean = "hello.csv".endsWith(".json") } ``` Result: ```json { "hasSuffix": true, "nope": false } ``` -------------------------------------------------------------------------------- # enumForName Source: https://taxilang.org/docs/language/stdlib/enumForName Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.enumForName` ## Signature ```taxi declare extension function enumForName(enumType: lang.taxi.Type, enumName: String): T ``` ## Description Returns the enum member matching the provided value string. Throws if no match is found. ```taxi // Look up an enum member by its string value find { status: OrderStatus = enumForName(OrderStatus, "PENDING") } ``` ## Examples Looks up an enum member by its string value and returns it as a typed enum. Schema: ```taxi enum OrderStatus { PENDING, APPROVED, REJECTED } ``` Query: ```taxi find { status: OrderStatus = enumForName(OrderStatus, "PENDING") } ``` Result: ```json { "status": "PENDING" } ``` -------------------------------------------------------------------------------- # equals Source: https://taxilang.org/docs/language/stdlib/equals Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.equals` ## Signature ```taxi declare extension function equals(a:A, b:B): Boolean ``` ## Description Returns `true` if two values are equal. Equality is checked by value and type — values of different semantic types are not equal even if their underlying values match. ```taxi find { match: Boolean = OrderStatus.equals("PENDING") } ``` ## Examples Compares two string values — matching pair returns true, non-matching returns false. Query: ```taxi find { same: Boolean = "hello".equals("hello") different: Boolean = "hello".equals("world") } ``` Result: ```json { "same": true, "different": false } ``` -------------------------------------------------------------------------------- # exactlyOne Source: https://taxilang.org/docs/language/stdlib/exactlyOne Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.exactlyOne` ## Signature ```taxi declare extension function exactlyOne(collection:T[]):T ``` ## Description Returns the only item from the provided collection, or throws an error if there isn't exactly one item in the collection. This function is useful when you expect a collection to contain precisely one element and want to retrieve it. ```taxi // Get the single active session for a user find { user: User } as { currentSession: user.sessions.filter((session) -> session.isActive).exactlyOne() } ``` ## Examples This example demonstrates how to use `exactlyOne()` to retrieve the single active session for a user. Schema: ```taxi model Session { id: SessionId inherits String isActive: IsActive inherits Boolean } model User { username: Username inherits String sessions: Session[] } ``` Query: ```taxi given { user: User = { username: 'ActiveUser', sessions: [ { id: 'session-1', isActive: true }, { id: 'session-2', isActive: false } ] } } find { username: user.username activeSessionId: SessionId = user.sessions.filter((Session) -> IsActive).exactlyOne().id } ``` Result: ```json { "username": "ActiveUser", "activeSessionId": "session-1" } ``` -------------------------------------------------------------------------------- # filter Source: https://taxilang.org/docs/language/stdlib/filter Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.filter` ## Signature ```taxi declare extension function filter(collection:T[], callback: (T) -> Boolean):T[] ``` ## Description Returns a new collection containing only the members of the original collection that match the provided predicate. This function is used to select a subset of elements from a collection based on a condition. ```taxi find { store: Store } as { allProducts: store.products // all products inStockProducts: store.products.filter((product) -> product.stockLevel > 0) affordableProducts: store.products.filter((product) -> product.price < 50.00) } ``` ## Examples Filters a collection by predicate, returning only matching elements. Uses type-based lambda for clean, semantic access. Schema: ```taxi model Friend { name: Name inherits String age: Age inherits Int } ``` Query: ```taxi given { friends: Friend[] = [ { name: 'Jim', age: 20 }, { name: 'Jack', age: 80 }, { name: 'Alice', age: 75 }, { name: 'Bob', age: 30 } ] } find { // Names of seniors (over 70) seniorFriends: Name[] = friends.filter((Age) -> Age > 70) as Name[] // Names of young adults (under 30) youngFriends: Name[] = friends.filter((Age) -> Age < 30) as Name[] } ``` Result: ```json { "seniorFriends": [ "Jack", "Alice" ], "youngFriends": [ "Jim" ] } ``` -------------------------------------------------------------------------------- # filterEach Source: https://taxilang.org/docs/language/stdlib/filterEach Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.filterEach` ## Signature ```taxi declare extension function filterEach(item: T, callback: (T) -> Boolean):T? ``` ## Description Evaluates the predicate against the provided item, returning the item if the predicate returns true, or null otherwise. This function is intended for use in filtering streams, where null values are automatically excluded. ```taxi // Filter individual items in a pipeline find { items: Item[] } as { processedItems: items.map((item) -> filterEach(item, (i) -> i.status == "READY")) } ``` -------------------------------------------------------------------------------- # first Source: https://taxilang.org/docs/language/stdlib/first Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.first` ## Signature ```taxi declare extension function first(collection: T[]):T ``` ## Description Returns the first item within the collection. Throws an error if the collection is empty. This function retrieves the first element in an ordered collection. ```taxi // Get the most recent transaction from a sorted list find { account: Account } as { latestTransaction: account.transactions.first() } ``` ## Examples This example demonstrates how to use `first()` to retrieve the most recent transaction for an account. Schema: ```taxi model Transaction { date: String amount: Decimal description: String } model Account { accountNumber: String transactions: Transaction[] // Assumed to be in descending date order } ``` Query: ```taxi given { account: Account = { accountNumber: 'ACC-12345', transactions: [ { date: '2023-05-01', amount: 150.00, description: 'Grocery Store' }, { date: '2023-04-28', amount: 45.99, description: 'Online Shop' }, { date: '2023-04-25', amount: 20.00, description: 'Transfer' } ] } } find { accountNumber: account.accountNumber mostRecentTransaction: Transaction = account.transactions.first() } ``` Result: ```json { "accountNumber": "ACC-12345", "mostRecentTransaction": { "date": "2023-05-01", "amount": 150, "description": "Grocery Store" } } ``` -------------------------------------------------------------------------------- # fold Source: https://taxilang.org/docs/language/stdlib/fold Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.fold` ## Signature ```taxi declare extension function fold(collection: T[], initial: A, callback: (T,A) -> A):A ``` ## Description Iterates over a collection, combining elements with an accumulator to produce a single result. Unlike `reduce`, `fold` starts with an explicit `initial` value as the accumulator. ```taxi // Compute a weighted sum model Entry { weight: Weight inherits Int score: Score inherits Int } type WeightedTotal by (Entry[]) -> Entry[].fold(0, (Entry, Int) -> Int + (Weight * Score)) ``` -------------------------------------------------------------------------------- # getAtIndex Source: https://taxilang.org/docs/language/stdlib/getAtIndex Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.getAtIndex` ## Signature ```taxi declare extension function getAtIndex(collection: T[], index: Int):T ``` ## Description Returns the item at the provided index in the collection. Throws an error if the index is out of bounds. This function retrieves an element at a specific position in a collection, with zero-based indexing. ```taxi // Get the second item in a list find { playlist: Playlist } as { secondSong: playlist.songs.getAtIndex(1) // Note: index starts at 0 } ``` ## Examples Retrieves a song from a playlist by its zero-based index position. Schema: ```taxi model Song { title: Title inherits String artist: Artist inherits String duration: Duration inherits Int } model Playlist { name: PlaylistName inherits String songs: Song[] } ``` Query: ```taxi given { playlist: Playlist = { name: 'Road Trip Mix', songs: [ { title: 'Highway Star', artist: 'Deep Purple', duration: 372 }, { title: 'Born to Run', artist: 'Bruce Springsteen', duration: 270 }, { title: 'Life in the Fast Lane', artist: 'Eagles', duration: 251 } ] } } find { playlistName: PlaylistName firstSong: Song = Song[].getAtIndex(0) secondSong: Song = Song[].getAtIndex(1) } ``` Result: ```json { "playlistName": "Road Trip Mix", "firstSong": { "title": "Highway Star", "artist": "Deep Purple", "duration": 372 }, "secondSong": { "title": "Born to Run", "artist": "Bruce Springsteen", "duration": 270 } } ``` -------------------------------------------------------------------------------- # hasEnumNamed Source: https://taxilang.org/docs/language/stdlib/hasEnumNamed Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.hasEnumNamed` ## Signature ```taxi declare extension function hasEnumNamed(enumType: lang.taxi.Type, enumName: String): Boolean ``` ## Description Returns `true` if the enum has an explicitly declared member matching the provided name. Ignores default entries — an enum with a default value will return `false` for a name that is only matched by the default, not by an explicit declaration. ```taxi find { valid: Boolean = hasEnumNamed(OrderStatus, "PENDING") } ``` ## Examples Returns true for a declared enum member name, false for a name not in the enum. Schema: ```taxi enum OrderStatus { PENDING, APPROVED, REJECTED } ``` Query: ```taxi find { hasPending: Boolean = hasEnumNamed(OrderStatus, "PENDING") hasUnknown: Boolean = hasEnumNamed(OrderStatus, "UNKNOWN") } ``` Result: ```json { "hasPending": true, "hasUnknown": false } ``` -------------------------------------------------------------------------------- # ifEmpty Source: https://taxilang.org/docs/language/stdlib/ifEmpty Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.ifEmpty` ## Signature ```taxi declare extension function ifEmpty(source:T[], defaultValue:T[]): T[] ``` ## Description Returns the source array if it is non-empty, otherwise returns the provided default array. ```taxi find { order: Order } as { tags: order.tags.ifEmpty(['untagged']) } ``` ## Examples Returns the default array when the source is empty, or the source array when it has elements. Schema: ```taxi model Order { tags: String[] } ``` Query: ```taxi given { emptyOrder: Order = { tags: [] }, fullOrder: Order = { tags: ['express', 'gift'] } } find { emptyTags: String[] = emptyOrder.tags.ifEmpty(['standard']) fullTags: String[] = fullOrder.tags.ifEmpty(['standard']) } ``` Result: ```json { "emptyTags": [ "standard" ], "fullTags": [ "express", "gift" ] } ``` -------------------------------------------------------------------------------- # indexOf Source: https://taxilang.org/docs/language/stdlib/indexOf Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.indexOf` ## Signature ```taxi declare extension function indexOf(source:String, valueToSearchFor:String):Int ``` ## Description Returns the zero-based index of `valueToSearchFor` within `source`, or `-1` if not found. ```taxi find { path: FilePath } as { dotPosition: Int = path.value.indexOf('.') } ``` ## Examples Returns the index of a substring within a string, or `-1` if not found. Query: ```taxi find { pos: Int = indexOf('Hello World', 'World') } ``` Result: ```json { "pos": 6 } ``` -------------------------------------------------------------------------------- # indexOfItem Source: https://taxilang.org/docs/language/stdlib/indexOfItem Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.indexOfItem` ## Signature ```taxi declare extension function indexOfItem(collection:T[], searchItem: T):Int ``` ## Description Returns the zero-based index of an item within a collection, or `-1` if the item was not found. ```taxi find { playlist: Playlist } as { songPosition: Int = playlist.tracks.indexOfItem('Bohemian Rhapsody') } ``` ## Examples Returns the index of an item in the collection, or `-1` if not found. Schema: ```taxi model Playlist { tracks: String[] } ``` Query: ```taxi given { playlist: Playlist = { tracks: ['Alpha', 'Beta', 'Gamma'] } } find { betaIndex: Int = playlist.tracks.indexOfItem('Beta') missingIndex: Int = playlist.tracks.indexOfItem('Delta') } ``` Result: ```json { "betaIndex": 1, "missingIndex": -1 } ``` -------------------------------------------------------------------------------- # intersection Source: https://taxilang.org/docs/language/stdlib/intersection Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.intersection` ## Signature ```taxi declare extension function intersection(collectionA: T[], collectionB: T[]):T[] ``` ## Description Returns a new collection containing only the elements that are present in both of the provided collections. This function identifies common elements between two collections. ```taxi // Find common tags between two products find { productA: Product, productB: Product } as { commonTags: intersection(productA.tags, productB.tags) } ``` ## Examples Returns only elements present in both collections, identified by value equality. Schema: ```taxi type Tag inherits String model Product { name: ProductName inherits String tags: Tag[] } ``` Query: ```taxi given { productA: Product = { name: 'Smartphone', tags: ['electronics', 'mobile', 'camera', 'touchscreen'] }, productB: Product = { name: 'Digital Camera', tags: ['electronics', 'camera', 'photography', 'zoom'] } } find { productAName: productA.name productBName: productB.name commonTags: intersection(productA.tags, productB.tags) } ``` Result: ```json { "productAName": "Smartphone", "productBName": "Digital Camera", "commonTags": [ "electronics", "camera" ] } ``` -------------------------------------------------------------------------------- # isNullOrEmpty Source: https://taxilang.org/docs/language/stdlib/isNullOrEmpty Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.isNullOrEmpty` ## Signature ```taxi declare extension function isNullOrEmpty(collection:T[]):Boolean ``` ## Description Returns `true` if the provided collection is either `null` or contains zero elements. ```taxi find { order: Order } as { hasNoItems: Boolean = order.items.isNullOrEmpty() } ``` ## Examples Returns `true` for an empty collection, `false` when the collection has elements. Schema: ```taxi model Bag { items: String[] } ``` Query: ```taxi given { emptyBag: Bag = { items: [] }, fullBag: Bag = { items: ['apple', 'banana'] } } find { emptyCheck: Boolean = emptyBag.items.isNullOrEmpty() fullCheck: Boolean = fullBag.items.isNullOrEmpty() } ``` Result: ```json { "emptyCheck": true, "fullCheck": false } ``` -------------------------------------------------------------------------------- # joinToString Source: https://taxilang.org/docs/language/stdlib/joinToString Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.joinToString` ## Signature ```taxi declare extension function joinToString(values:T[], separator: String = ",", prefix: String? = null, postfix: String? = null): String ``` ## Description Creates a string from all the elements separated using `separator` and using the given `prefix` and `postfix` if supplied. Null values are omitted. ```taxi find { order: Order } as { tagList: String = order.tags.joinToString(', ') } ``` ## Examples Joins array elements into a single string, with optional separator, prefix, and postfix. Query: ```taxi given { tags: String[] = ['alpha', 'beta', 'gamma'] } find { joined: String = tags.joinToString(', ') withBrackets: String = tags.joinToString(', ', '[', ']') } ``` Result: ```json { "joined": "alpha, beta, gamma", "withBrackets": "[alpha, beta, gamma]" } ``` -------------------------------------------------------------------------------- # last Source: https://taxilang.org/docs/language/stdlib/last Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.last` ## Signature ```taxi declare extension function last(collection: T[]):T ``` ## Description Returns the last item within the collection. Throws an error if the collection is empty. This function retrieves the final element in an ordered collection. ```taxi // Get the oldest transaction from a date-sorted list find { account: Account } as { oldestTransaction: account.transactions.last() } ``` ## Examples This example demonstrates how to use `last()` to retrieve the oldest transaction for an account. Schema: ```taxi model Transaction { date: String amount: Decimal description: String } model Account { accountNumber: String transactions: Transaction[] // Assumed to be in descending date order (newest first) } ``` Query: ```taxi given { account: Account = { accountNumber: 'ACC-12345', transactions: [ { date: '2023-05-01', amount: 150.00, description: 'Grocery Store' }, { date: '2023-04-28', amount: 45.99, description: 'Online Shop' }, { date: '2023-04-25', amount: 20.00, description: 'Transfer' } ] } } find { accountNumber: account.accountNumber // Using .last() against the type oldestTransaction: Transaction = Transaction[].last() // or, alternatively, using against the property name alsoOldestTransaction: Transaction = account.transactions.last() } ``` Result: ```json { "accountNumber": "ACC-12345", "oldestTransaction": { "date": "2023-04-25", "amount": 20, "description": "Transfer" }, "alsoOldestTransaction": { "date": "2023-04-25", "amount": 20, "description": "Transfer" } } ``` -------------------------------------------------------------------------------- # left Source: https://taxilang.org/docs/language/stdlib/left Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.left` ## Signature ```taxi declare extension function left(source:String, count:Int):String ``` ## Description Returns the leftmost `count` characters from a string. ```taxi find { account: Account } as { prefix: String = account.code.left(3) } ``` ## Examples Extracts the first N characters from a string. Query: ```taxi find { firstFive: String = left('HelloWorld', 5) } ``` Result: ```json { "firstFive": "Hello" } ``` -------------------------------------------------------------------------------- # length Source: https://taxilang.org/docs/language/stdlib/length Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.length` ## Signature ```taxi declare extension function length(String):Int ``` ## Description Returns the number of characters in the string. ```taxi find { message: Message } as { charCount: Int = message.body.length() } ``` ## Examples Returns the character count of a string. Query: ```taxi find { len: Int = length('Hello') } ``` Result: ```json { "len": 5 } ``` -------------------------------------------------------------------------------- # listOf Source: https://taxilang.org/docs/language/stdlib/listOf Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.listOf` ## Signature ```taxi declare function listOf(values:T...):T[] ``` ## Description Creates and returns an array containing all the provided values. This function is useful for creating collections inline without having to define them separately. ```taxi // Create a list of allowed status values find { order: Order } as { isValidStatus: listOf('PENDING', 'PROCESSING', 'SHIPPED', 'DELIVERED').contains(order.status) } ``` ## Examples Creates an inline collection to check if an order status is one of the valid values. Schema: ```taxi model Order { id: String status: String } ``` Query: ```taxi given { orders: Order[] = [ { id: 'order-1', status: 'PENDING' }, { id: 'order-2', status: 'SHIPPED' }, { id: 'order-3', status: 'CANCELED' } ] } find { validOrders: Order[] = orders.filter((order:Order) -> listOf('PENDING', 'PROCESSING', 'SHIPPED', 'DELIVERED').contains(order.status) ) invalidOrders: Order[] = orders.filter((order:Order) -> listOf('PENDING', 'PROCESSING', 'SHIPPED', 'DELIVERED').contains(order.status) == false ) } ``` Result: ```json { "validOrders": [ { "id": "order-1", "status": "PENDING" }, { "id": "order-2", "status": "SHIPPED" } ], "invalidOrders": [ { "id": "order-3", "status": "CANCELED" } ] } ``` -------------------------------------------------------------------------------- # lowerCase Source: https://taxilang.org/docs/language/stdlib/lowerCase Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.lowerCase` ## Signature ```taxi declare extension function lowerCase(String):String ``` ## Description Converts all characters in the string to lower case. ```taxi find { product: Product } as { lowerName: String = product.name.lowerCase() } ``` ## Examples Converts a string to lower case. Query: ```taxi find { lower: String = lowerCase('HELLO WORLD') } ``` Result: ```json { "lower": "hello world" } ``` -------------------------------------------------------------------------------- # map Source: https://taxilang.org/docs/language/stdlib/map Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.map` ## Signature ```taxi declare extension function map(collection: T[], callback: (T) -> A):A[] ``` ## Description Applies a transformation function to every element in a collection, returning a new array of the results. If the callback is a type expression, each element is converted to that type: ```taxi // Convert Person[] to Name[] Person[].map((Person) -> Name) ``` If the callback is any other expression, it is evaluated against each element: ```taxi // Return an array of uppercased titles Film[].map((Title) -> Title.upperCase()) ``` ## Examples Extracts a field from each object in a collection, returning a typed array. Schema: ```taxi model Product { name: Name inherits String price: Price inherits Decimal } ``` Query: ```taxi given { products: Product[] = [ { name: 'Apple', price: 1.50 }, { name: 'Banana', price: 0.75 }, { name: 'Cherry', price: 3.00 } ] } find { names: Name[] = products.map((Product) -> Name) } ``` Result: ```json { "names": [ "Apple", "Banana", "Cherry" ] } ``` -------------------------------------------------------------------------------- # matches Source: https://taxilang.org/docs/language/stdlib/matches Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.matches` ## Signature ```taxi declare extension function matches(source: String, regex: String): Boolean ``` ## Description Returns `true` if the **entire** source string matches the provided regular expression. Uses full-string matching — the pattern must match the entire string, not just a part of it. Use `containsPattern` for partial (substring) matching. ```taxi find { account: Account } as { isValidCode: Boolean = account.code.matches('^[A-Z]{3}-\d{4}$') } ``` ## Examples Returns `true` only when the full string matches the regex pattern. Query: ```taxi find { fullMatch: Boolean = 'hello'.matches('^h.*o$') noFullMatch: Boolean = 'hello'.matches('^he') } ``` Result: ```json { "fullMatch": true, "noFullMatch": false } ``` -------------------------------------------------------------------------------- # max Source: https://taxilang.org/docs/language/stdlib/max Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.max` ## Signature ```taxi declare extension function max(collection: T[], callback: (T) -> A):A ``` ## Description Returns the maximum value in the collection, computed by applying the selector function to each element. ```taxi find { products: Product[] } as { mostExpensive: Price = products.max((Product) -> Price) } ``` ## Examples Returns the maximum price from a collection of products. Schema: ```taxi model Product { name: String price: Price inherits Decimal } ``` Query: ```taxi given { products: Product[] = [ { name: 'Apple', price: 1.50 }, { name: 'Banana', price: 0.75 }, { name: 'Cherry', price: 3.00 } ] } find { maxPrice: Price = products.max((Product) -> Price) } ``` Result: ```json { "maxPrice": 3 } ``` -------------------------------------------------------------------------------- # mid Source: https://taxilang.org/docs/language/stdlib/mid Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.mid` ## Signature ```taxi declare extension function mid(source: String, startIndex: Int, endIndex: Int):String ``` ## Description Returns the middle of a string, starting at `startIndex` (inclusive) and ending just before `endIndex` (exclusive). Uses zero-based indexing, identical to `String.substring()` in Java/Kotlin. ```taxi find { order: Order } as { yearPart: String = order.isoDate.mid(0, 4) } ``` ## Examples Extracts a substring using a zero-based start index (inclusive) and end index (exclusive). Query: ```taxi find { mid5: String = mid('HelloWorld', 5, 10) } ``` Result: ```json { "mid5": "World" } ``` -------------------------------------------------------------------------------- # min Source: https://taxilang.org/docs/language/stdlib/min Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.min` ## Signature ```taxi declare extension function min(collection: T[], callback: (T) -> A):A ``` ## Description Returns the minimum value in the collection, computed by applying the selector function to each element. ```taxi find { products: Product[] } as { cheapest: Price = products.min((Product) -> Price) } ``` ## Examples Returns the minimum price from a collection of products. Schema: ```taxi model Product { name: String price: Price inherits Decimal } ``` Query: ```taxi given { products: Product[] = [ { name: 'Apple', price: 1.50 }, { name: 'Banana', price: 0.75 }, { name: 'Cherry', price: 3.00 } ] } find { minPrice: Price = products.min((Product) -> Price) } ``` Result: ```json { "minPrice": 0.75 } ``` -------------------------------------------------------------------------------- # none Source: https://taxilang.org/docs/language/stdlib/none Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.none` ## Signature ```taxi declare extension function none(collection: T[], predicate: (T) -> Boolean): Boolean ``` ## Description Returns `true` if none of the items in the collection satisfy the predicate. Returns `true` for an empty collection (vacuous truth). ```taxi // Check that no task is overdue find { sprint: Sprint } as { noOverdue: Boolean = sprint.tasks.none((task) -> task.isOverdue) } ``` ## Examples Returns `true` when no item in the collection satisfies the predicate. Schema: ```taxi model Task { name: String isComplete: Boolean } ``` Query: ```taxi given { tasks: Task[] = [ { name: 'Setup', isComplete: true }, { name: 'Build', isComplete: true }, { name: 'Deploy', isComplete: true } ] } find { noneIncomplete: Boolean = tasks.none((task: Task) -> task.isComplete == false) } ``` Result: ```json { "noneIncomplete": true } ``` -------------------------------------------------------------------------------- # noneOf Source: https://taxilang.org/docs/language/stdlib/noneOf Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.noneOf` ## Signature ```taxi declare function noneOf(values:Boolean...): Boolean ``` ## Description Returns `true` only if all provided boolean values are `false`. Equivalent to logical NOR. This function is useful for checking if none of multiple conditions apply. ```taxi // Check if a user has no permissions type NoPermissions inherits Boolean by noneOf(IsAdmin, IsEditor, IsViewer) ``` ## Examples Returns `true` when all provided boolean conditions are `false`. Uses type-based access to combine semantic boolean types. Schema: ```taxi type IsAdmin inherits Boolean type IsEditor inherits Boolean type IsViewer inherits Boolean model User { username: Username inherits String isAdmin: IsAdmin isEditor: IsEditor isViewer: IsViewer } ``` Query: ```taxi given { user: User = { username: 'GuestUser', isAdmin: false, isEditor: false, isViewer: false } } find { username: Username hasNoAccess: Boolean = noneOf(IsAdmin, IsEditor, IsViewer) } ``` Result: ```json { "username": "GuestUser", "hasNoAccess": true } ``` -------------------------------------------------------------------------------- # now Source: https://taxilang.org/docs/language/stdlib/now Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.dates.now` ## Signature ```taxi declare function now():Instant ``` ## Description Returns the current UTC instant. The output changes every time the function is called. Use `@Format` to control how the instant is serialised in query output. ```taxi // Stamp the current time on a record find { @Format("yyyy-MM-dd'T'HH:mm:ssXXX") processedAt: Instant = now() } ``` -------------------------------------------------------------------------------- # orEmpty Source: https://taxilang.org/docs/language/stdlib/orEmpty Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.orEmpty` ## Signature ```taxi declare extension function orEmpty(source:T[]): T[] ``` ## Description Returns the source array unchanged if it is non-null, or an empty array if it is `null`. Useful for safely chaining collection operations on potentially-null fields. ```taxi find { user: User } as { roles: user.roles.orEmpty().joinToString(', ') } ``` ## Examples Returns the collection unchanged when non-null. Use `.orEmpty()` to safely chain operations on nullable arrays. Schema: ```taxi model Cart { items: String[] } ``` Query: ```taxi given { cart: Cart = { items: ['apple', 'banana'] } } find { items: String[] = cart.items.orEmpty() } ``` Result: ```json { "items": [ "apple", "banana" ] } ``` -------------------------------------------------------------------------------- # padEnd Source: https://taxilang.org/docs/language/stdlib/padEnd Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.padEnd` ## Signature ```taxi declare extension function padEnd(input: String, length: Int, padWith: String):String ``` ## Description Pads the string to the specified total `length` by appending `padWith` characters at the end. `padWith` should be a single character — if multiple characters are passed, only the first is used. ```taxi find { product: Product } as { paddedCode: String = product.code.padEnd(10, ' ') } ``` ## Examples Pads a string to the target length by appending characters on the right. Query: ```taxi find { padded: String = padEnd('hello', 10, '.') } ``` Result: ```json { "padded": "hello....." } ``` -------------------------------------------------------------------------------- # padStart Source: https://taxilang.org/docs/language/stdlib/padStart Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.padStart` ## Signature ```taxi declare extension function padStart(input: String, length: Int, padWith: String):String ``` ## Description Pads the string to the specified total `length` by prepending `padWith` characters at the start. `padWith` should be a single character — if multiple characters are passed, only the first is used. ```taxi find { order: Order } as { paddedId: String = order.id.padStart(8, '0') } ``` ## Examples Zero-pads a short string to the target length from the left. Query: ```taxi find { padded: String = padStart('42', 6, '0') } ``` Result: ```json { "padded": "000042" } ``` -------------------------------------------------------------------------------- # parseDate Source: https://taxilang.org/docs/language/stdlib/parseDate Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.dates.parseDate` ## Signature ```taxi declare function parseDate(String):T ``` ## Description Parses a string into a date, datetime, or instant value. The target type determines which parsing rules are applied. Supported target types are `Date`, `DateTime`, and `Instant`. Use `@Format` on the result field to control the output serialisation format. ```taxi // Parse an ISO date string into a Date value find { @Format("yyyy-MM-dd") eventDate: Date = parseDate("2024-06-15") } ``` ## Examples Parses ISO date and datetime strings into their respective Taxi date types. Query: ```taxi find { @Format("yyyy-MM-dd") asDate: Date = parseDate("2024-06-15") @Format("yyyy-MM-dd HH:mm:ss") asDateTime: DateTime = parseDate("2024-06-15T10:30:00") } ``` Result: ```json { "asDate": "2024-06-15", "asDateTime": "2024-06-15 10:30:00" } ``` -------------------------------------------------------------------------------- # parseJson Source: https://taxilang.org/docs/language/stdlib/parseJson Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.parseJson` ## Signature ```taxi declare extension function parseJson(source: String, type: Type):T ``` ## Description Parses a JSON string into the specified type. This is useful when a field contains a JSON payload encoded as a string (e.g., from a message queue or API response), and you need to deserialize it into a structured type. Computed fields and functions on the target type are evaluated after parsing. ```taxi model Event { name : String payload : String parsed : Payload = parseJson(this.payload, Payload) } ``` ## Examples Parses a JSON string embedded in a model field into a structured type. Schema: ```taxi model Address { city: String country: String } model Customer { name: String addressJson: String address: Address = parseJson(this.addressJson, Address) } ``` Query: ```taxi given { customer: Customer = { name: 'Alice', addressJson: '{ "city": "London", "country": "UK" }' } } find { name: customer.name address: customer.address } ``` Result: ```json { "name": "Alice", "address": { "city": "London", "country": "UK" } } ``` -------------------------------------------------------------------------------- # reduce Source: https://taxilang.org/docs/language/stdlib/reduce Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.reduce` ## Signature ```taxi declare extension function reduce(collection: T[], callback: (T,A) -> A):A ``` ## Description Reduces a collection to a single value by applying a combining function to each element and the accumulated result. Unlike `fold`, there is no initial value — the first element of the collection is used as the starting accumulator. ```taxi // Sum a list of integers find { total: Int = numbers.reduce((Int, Int) -> Int + Int) } ``` -------------------------------------------------------------------------------- # replace Source: https://taxilang.org/docs/language/stdlib/replace Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.replace` ## Signature ```taxi declare extension function replace(source: String, searchValue:String, replacement: String):String ``` ## Description Replaces all occurrences of `searchValue` in `source` with `replacement`, returning a new string. ```taxi find { order: Order } as { cleanRef: String = order.reference.replace('-', '_') } ``` ## Examples Replaces all occurrences of a substring with a new value. Query: ```taxi find { replaced: String = replace('Hello World', 'World', 'Taxi') } ``` Result: ```json { "replaced": "Hello Taxi" } ``` -------------------------------------------------------------------------------- # right Source: https://taxilang.org/docs/language/stdlib/right Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.right` ## Signature ```taxi declare extension function right(source:String, count:Int):String ``` ## Description Returns the rightmost `count` characters from a string. ```taxi find { account: Account } as { suffix: String = account.code.right(4) } ``` ## Examples Extracts the last N characters from a string. Query: ```taxi find { lastFive: String = right('HelloWorld', 5) } ``` Result: ```json { "lastFive": "World" } ``` -------------------------------------------------------------------------------- # round Source: https://taxilang.org/docs/language/stdlib/round Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.round` ## Signature ```taxi declare extension function round(value: Decimal, precision: Int = 0): Decimal ``` ## Description Rounds a decimal value to the specified number of decimal places. `precision` defaults to `0` (round to nearest integer). ```taxi find { measurement: Measurement } as { rounded: Decimal = measurement.value.round(2) } ``` ## Examples Rounds a decimal to the specified number of decimal places. Query: ```taxi find { rounded2: Decimal = round(3.14159, 2) rounded0: Decimal = round(3.7, 0) } ``` Result: ```json { "rounded2": 3.14, "rounded0": 4 } ``` -------------------------------------------------------------------------------- # single Source: https://taxilang.org/docs/language/stdlib/single Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.single` ## Signature ```taxi declare extension function single(collection:T[], callback: (T) -> Boolean):T ``` ## Description Returns the first item from a collection that matches the provided predicate. Throws an error if no matching item is found. This function is useful when you need to find a specific element in a collection based on a condition. ```taxi // Find a product by its SKU code find { inventory: Inventory } as { appleMacbook: inventory.products.single((product) -> product.sku == "MB-PRO-2023") } ``` ## Examples Finds the single product matching the given SKU. Throws if no match or more than one match is found. Schema: ```taxi model Product { name: ProductName inherits String sku: SKU inherits String price: Price inherits Decimal } model Inventory { products: Product[] } ``` Query: ```taxi given { inventory: Inventory = { products: [ { name: 'Laptop', sku: 'LT-001', price: 1200.00 }, { name: 'Smartphone', sku: 'SP-002', price: 999.99 }, { name: 'Tablet', sku: 'TB-003', price: 499.50 } ] } } find { smartphoneDetails: Product = inventory.products.single((product: Product) -> product.sku == 'SP-002') } ``` Result: ```json { "smartphoneDetails": { "name": "Smartphone", "sku": "SP-002", "price": 999.99 } } ``` -------------------------------------------------------------------------------- # singleBy Source: https://taxilang.org/docs/language/stdlib/singleBy Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.singleBy` ## Signature ```taxi declare extension function singleBy(collection:T[], groupingFunction: (T) -> A, searchValue: A):T ``` ## Description Returns the item from a collection where the value returned by the grouping function matches the search value. Similar to `single()`, but results are first grouped by the selector function, and these results are cached to improve future performance when querying the same collection multiple times. ```taxi // Find a product by its SKU code using a cached search find { inventory: Inventory } as { appleMacbook: inventory.products.singleBy((product) -> product.sku, "MB-PRO-2023") } ``` ## Examples Looks up products by SKU using a cached index. Faster than `single()` when querying the same collection multiple times. Schema: ```taxi model Product { name: Name inherits String sku: SKU inherits String } model Inventory { products: Product[] } ``` Query: ```taxi given { inventory: Inventory = { products: [ { name: 'Laptop', sku: 'LT-001' }, { name: 'Smartphone', sku: 'SP-002' }, { name: 'Tablet', sku: 'TB-003' } ] } } find { laptop: inventory.products.singleBy((Product) -> SKU, 'LT-001' as SKU) tablet: inventory.products.singleBy((Product) -> SKU, 'TB-003' as SKU) } ``` Result: ```json { "laptop": { "name": "Laptop", "sku": "LT-001" }, "tablet": { "name": "Tablet", "sku": "TB-003" } } ``` -------------------------------------------------------------------------------- # size Source: https://taxilang.org/docs/language/stdlib/size Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.size` ## Signature ```taxi declare extension function size(collection:T[]):Int ``` ## Description Returns the number of elements in the collection. ```taxi find { orders: Order[] } as { orderCount: Int = orders.size() } ``` ## Examples Returns the number of elements in the collection. Schema: ```taxi model Cart { items: String[] } ``` Query: ```taxi given { cart: Cart = { items: ['apple', 'banana', 'cherry'] } } find { itemCount: Int = cart.items.size() } ``` Result: ```json { "itemCount": 3 } ``` -------------------------------------------------------------------------------- # startsWith Source: https://taxilang.org/docs/language/stdlib/startsWith Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.startsWith` ## Signature ```taxi declare extension function startsWith(source: String, valueToSearchFor:String):Boolean ``` ## Description Returns `true` if `source` begins with the specified prefix. The match is case-sensitive. ```taxi find { files: File[] } as { tempFiles: File[] = files.filter((File) -> FileName.startsWith('tmp_')) } ``` ## Examples Returns `true` when the string begins with the specified prefix, case-sensitively. Query: ```taxi find { hasPrefix: Boolean = 'Hello World'.startsWith('Hello') noMatch: Boolean = 'Hello World'.startsWith('World') } ``` Result: ```json { "hasPrefix": true, "noMatch": false } ``` -------------------------------------------------------------------------------- # sum Source: https://taxilang.org/docs/language/stdlib/sum Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.sum` ## Signature ```taxi declare extension function sum(collection: T[], callback: (T) -> A):A ``` ## Description Returns the sum of values in the collection, computed by applying the selector function to each element. ```taxi find { order: Order } as { total: Price = order.items.sum((Item) -> Price) } ``` ## Examples Sums a numeric field across all elements of a collection. Schema: ```taxi model Product { name: String price: Price inherits Decimal } ``` Query: ```taxi given { products: Product[] = [ { name: 'Apple', price: 1.50 }, { name: 'Banana', price: 0.75 }, { name: 'Cherry', price: 3.00 } ] } find { totalPrice: Price = products.sum((Product) -> Price) } ``` Result: ```json { "totalPrice": 5.25 } ``` -------------------------------------------------------------------------------- # sumOver Source: https://taxilang.org/docs/language/stdlib/sumOver Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `vyne.aggregations.sumOver` ## Signature ```taxi declare query function sumOver(Any...):Decimal ``` ## Description A Vyne-specific aggregate function used in analytical queries to compute a running or grouped sum. Used in `sumOver` window-style queries where values are aggregated across a result set. ```taxi find { orders: Order[] } as { totalRevenue: Decimal = sumOver(Amount) } ``` -------------------------------------------------------------------------------- # throw Source: https://taxilang.org/docs/language/stdlib/throw Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.throw` ## Signature ```taxi declare function throw(error:Any):Nothing ``` ## Description Throws an error, halting execution. Useful for enforcing invariants inline. ```taxi find { order: Order } as { status: OrderStatus = when { OrderStatus == 'INVALID' -> throw('Order status is invalid') else -> OrderStatus } } ``` -------------------------------------------------------------------------------- # toRawType Source: https://taxilang.org/docs/language/stdlib/toRawType Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.toRawType` ## Signature ```taxi declare extension function toRawType(source: Any):Any ``` ## Description Removes the semantic typing from a scalar value, returning it typed as its raw primitive. Useful when you need to compare values by their raw content while ignoring their semantic type. - For a scalar value: strips the semantic type, returning the underlying primitive. - For an array of scalars: strips the semantic type from each element. Not supported on objects or arrays of objects. ```taxi find { order: Order } as { rawId: String = order.id.toRawType() } ``` ## Examples Strips the semantic type from a typed scalar, returning the raw primitive value. Schema: ```taxi type OrderId inherits String ``` Query: ```taxi given { orderId: OrderId = 'ORD-001' } find { rawId: String = orderId.toRawType() } ``` Result: ```json { "rawId": "ORD-001" } ``` -------------------------------------------------------------------------------- # trim Source: https://taxilang.org/docs/language/stdlib/trim Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.trim` ## Signature ```taxi declare extension function trim(String):String ``` ## Description Removes leading and trailing whitespace from a string. ```taxi find { order: Order } as { cleanRef: String = order.referenceCode.trim() } ``` ## Examples Strips leading and trailing whitespace from a string. Query: ```taxi find { trimmed: String = ' hello world '.trim() } ``` Result: ```json { "trimmed": "hello world" } ``` -------------------------------------------------------------------------------- # upperCase Source: https://taxilang.org/docs/language/stdlib/upperCase Section: Working with data › Taxi Stdlib -------------------------------------------------------------------------------- **Full name:** `taxi.stdlib.upperCase` ## Signature ```taxi declare extension function upperCase(String):String ``` ## Description Converts all characters in the string to upper case. ```taxi find { product: Product } as { upperName: String = product.name.upperCase() } ``` ## Examples Converts a string to upper case. Query: ```taxi find { upper: String = upperCase('hello world') } ``` Result: ```json { "upper": "HELLO WORLD" } ``` -------------------------------------------------------------------------------- # Overview Source: https://taxilang.org/docs/other-api-specs/working-with-other-api-specs Section: Working with other API specs -------------------------------------------------------------------------------- ## Overview Taxi is designed to work alongside your existing schema languages and API specifications rather than replace them. By adding semantic metadata to your existing specs, you create a semantic layer that connects data across different systems and formats. > **Note: Enhancement, Not Replacement** > > Teams can continue using their preferred schema formats and toolchains. Taxi adds semantic meaning through lightweight metadata annotations. ## Supported Formats ### OpenAPI/Swagger Add semantic types to your REST APIs using `x-taxi-type` extensions: ```yaml paths: /customers/{id}: get: parameters: - name: id schema: type: string > x-taxi-type: > name: acme.CustomerId ``` [Open API reference](https://taxilang.org/docs/other-api-specs/open-api) ### Protocol Buffers Enhance protocol buffer messages using the `taxi.dataType` extension: ```protobuf message Customer { > optional string id = 1 [(taxi.dataType)="acme.CustomerId"]; > optional string email = 2 [(taxi.dataType)="acme.EmailAddress"]; } ``` [Protobuf reference](https://taxilang.org/docs/other-api-specs/protobuf) ### Avro Add semantic meaning to Avro schemas via the `taxi.dataType` attribute: ```json { "type": "record", "name": "Customer", "fields": [ { "name": "id", "type": "string", > "taxi.dataType": "acme.CustomerId" } ] } ``` [Avro reference](https://taxilang.org/docs/other-api-specs/avro) ### SOAP/WSDL Enhance SOAP services using the `taxi:type` attribute: ```xml taxi:type="acme.CustomerId"/> ``` [Soap reference](https://taxilang.org/docs/other-api-specs/soap) ## Data Formats Taxi also provides annotations for common data formats. Taxi includes annotations for common data formats like CSV and XML, enabling schema definitions for formats that may not have formal specifications. While Taxi defines these formats, the actual reading and writing of data is handled by TaxiQL query engines, such as [Orbital](https://orbitalhq.com) ### CSV ```taxi > @Csv(delimiter = "|") model CustomerExport { id: CustomerId email: EmailAddress } ``` [Csv reference](https://taxilang.org/docs/other-api-specs/csv) ### XML ```taxi > @Xml model CustomerImport { > @XmlAttribute id: CustomerId email: EmailAddress } ``` [Xml reference](https://taxilang.org/docs/other-api-specs/xml) -------------------------------------------------------------------------------- # Open API Source: https://taxilang.org/docs/other-api-specs/open-api Section: Working with other API specs -------------------------------------------------------------------------------- ## Overview Taxi can be embedded within OpenAPI specifications to add semantic meaning to APIs. This allows you to: - Define semantic types for request/response models - Add semantic meaning to API parameters - Create a unified semantic layer across multiple APIs > **Note: Semantic Enhancement** > > OpenAPI specs describe API structure. Taxi enhances them with semantic meaning, enabling better data discovery and integration. ## Adding Taxi to OpenAPI ### Basic Syntax Taxi metadata is added using `x-taxi-type` extensions in your OpenAPI spec: ```yaml x-taxi-type: name: com.acme.MyType # The semantic type name create: false # Optional flag to control type creation ``` ### Type Creation Behavior The `create` flag controls whether new types are created if they don't exist. The default behaviour varies between models and field attributes - and defaults to the reccomended best practice for each. | Context | Default | Behavior | |------------------|---------|--------------------------------| | Response Models | `true` | Creates new types if not found | | Field Attributes | `false` | Requires types to exist | > **Note: Type Creation** > > Semantic types let you share data between systems, while avoiding tight coupling. Try to use existing types (where the semantics match), or create new types in a shared taxonomy - rather than creating types in your OpenAPI spec > Only create new types (`create: true`) when you're intentionally extending your semantic model, or creating a type you're not ready to share yet. ### Adding annotations Use `x-taxi-annotations` to add annotations to the generated Taxi. It's a list of strings, each written in Taxi's annotation syntax: ```yaml components: schemas: Person: > x-taxi-annotations: ["@Entity(table = \"people\")"] properties: id: type: string > x-taxi-annotations: ["@Id", "@Cache(ttl = 30)"] ``` generates: ```taxi @Entity(table = "people") closed model Person { @Id @Cache(ttl = 30) id : String } ``` - On a property, the annotations go on the field. On a schema in `components/schemas`, they go on the model or type generated from it. - On an operation, a parameter or a request body, they go on the generated operation or its parameter (see [Annotating operations and parameters](#annotating-operations-and-parameters)). - `x-taxi-annotations` is separate from `x-taxi-type`, so you can annotate a field whether or not you declare its type. - One string can hold several annotations: `x-taxi-annotations: "@Id @Cache(ttl = 30)"`. - Parameter values can be strings, numbers or booleans. - Annotation names aren't checked when the spec is read. They're resolved when the generated Taxi is compiled, like any other annotation, so an annotation type that isn't declared anywhere is still allowed. - An entry that isn't valid annotation syntax is left out, and reported as a warning. #### Annotating a property that uses `$ref` OpenAPI 3.1 keeps keys written next to a `$ref`, so you can annotate a referenced property directly: ```yaml # OpenAPI 3.1 id: $ref: '#/components/schemas/PersonId' > x-taxi-annotations: ["@Id"] ``` OpenAPI 3.0 ignores everything next to a `$ref`, and the annotations are dropped (with a warning). In 3.0, wrap the reference in an `allOf` and put the annotations next to the `allOf`. The field is still typed as the referenced schema: ```yaml # OpenAPI 3.0 id: > allOf: > - $ref: '#/components/schemas/PersonId' > x-taxi-annotations: ["@Id"] ``` #### Annotating operations and parameters Add `x-taxi-annotations` to an operation, a parameter, or a request body: ```yaml paths: /customers/{id}: get: operationId: getCustomer > x-taxi-annotations: ["@Cached(ttl = 30)"] parameters: - name: id in: path required: true > x-taxi-annotations: ["@Id"] schema: type: string put: operationId: updateCustomer requestBody: > x-taxi-annotations: ["@Audited"] content: application/json: schema: $ref: '#/components/schemas/Customer' ``` generates: ```taxi service CustomersIdService { @taxi.http.HttpOperation(method = "GET" , url = "http://acme.com/customers/{id}") @Cached(ttl = 30) operation getCustomer( @taxi.http.PathVariable(value = "id") @Id id : String ) : Customer // ... } ``` - Put a parameter's annotations on the parameter itself, next to `name` and `in`, not on its `schema`. Annotations on a parameter's `schema` are ignored, with a warning. - A parameter declared on the path item applies to every operation on that path, and so do its annotations. - A parameter declared in `components/parameters` and used with `$ref` takes its annotations from the component. Every operation that uses it gets them. - Your annotations come after the ones Taxi generates from the spec: `@HttpOperation`, `@PathVariable`, `@QueryVariable`, `@HttpHeader` and `@RequestBody`. These say how the operation is called (its method, its URL, and the name each parameter is sent as), so you can't replace them. If you declare one with the same name (with or without the `taxi.http` namespace), yours is ignored, with a warning. ### Declaring operation kinds Some TaxiQL behavior depends on whether operations are read-only or write operations (sometimes referred to as 'mutations'). By default: - **GET**, **HEAD**, and **OPTIONS** methods are treated as **read** operations (default, no modifier) - **POST**, **PUT**, **PATCH**, and **DELETE** methods are treated as **write** operations You can override this default behavior using `x-taxi-operation-kind` at the operation level: ```yaml openapi: "3.0.0" info: version: 1.0.0 title: Swagger Petstore paths: /pets: # POST operations are 'write' by default, but this can be overridden post: x-taxi-operation-kind: read # Override to make a POST operation read-only responses: '200': description: successful operation content: application/json: schema: type: object properties: name: type: string # GET operations are 'read' by default, but this can be overridden get: x-taxi-operation-kind: write # Override to make a GET operation a write operation responses: '200': description: successful operation content: application/json: schema: type: object properties: name: type: string ``` Possible values for `x-taxi-operation-kind`: - `read` - The operation is read-only (default for GET, HEAD, OPTIONS) - `write` - The operation modifies data (default for POST, PUT, PATCH, DELETE) ## Enriching OpenAPI Components ### Response Models Add semantic meaning to response types: ```yaml components: schemas: Customer: > x-taxi-type: > name: acme.Customer properties: id: type: string > x-taxi-type: > name: acme.CustomerId email: type: string > x-taxi-type: > name: acme.EmailAddress ``` ### Input Parameters Enrich API parameters with semantic types: ```yaml paths: /customers/{id}: get: parameters: - name: id in: path schema: type: string > x-taxi-type: > name: acme.CustomerId ``` ### Field Attributes Add semantic meaning to model fields: ```yaml components: schemas: Customer: properties: name: type: string > x-taxi-type: > name: acme.CustomerName > # Only create if type doesn't exist > create: true ``` -------------------------------------------------------------------------------- # Avro Source: https://taxilang.org/docs/other-api-specs/avro Section: Working with other API specs -------------------------------------------------------------------------------- ## Overview Taxi can enhance Apache Avro schemas with semantic meaning, enabling: - Semantic typing for Avro records and fields - Type-safe data serialization and deserialization - Integration with Avro-based messaging systems > **Note: Semantic Enhancement** > > While Avro describes data structure and serialization, Taxi adds semantic meaning to enable better data discovery and integration across systems. ## Adding Taxi to Avro ### Schema Annotation Add semantic types to Avro using the `taxi.dataType` attribute: ```json { "type": "record", "namespace": "customer", "name": "CustomerRecord", > "taxi.dataType": "acme.Customer", "fields": [ { "name": "id", "type": "string", > "taxi.dataType": "acme.CustomerId" }, { "name": "email", "type": "string", > "taxi.dataType": "acme.EmailAddress" } ] } ``` ### Auto-Type Generation If no `taxi.dataType` is specified, Taxi will generate types based on: - For records: The combination of namespace and name - For fields: The field name and underlying primitive type ## Declaring Avro in Taxi You can also describe your Avro model directly in Taxi. > **Note: Reccomendation: Add taxi types to Avro schemas when possible** > > You can generate these Avro models by hand - but we don't recommend it. > > If you have an Avro schema and can annotate it, it's recommended to annotate that with taxi types, rather than > duplicating the schema definition as a Taxi model. > ### Basic Model Definition ```taxi import lang.taxi.formats.AvroMessage import lang.taxi.formats.AvroField @AvroMessage model Customer { @AvroField(ordinal = 0) id: CustomerId @AvroField(ordinal = 1) email: EmailAddress? @AvroField(ordinal = 2) addresses: Address[] } ``` > **Warning: Field Ordering** > > Avro serialization depends on field order. Always specify ordinals using @AvroField to ensure consistent serialization. ### Type Mapping Taxi automatically maps Avro types to appropriate semantic types: ```taxi @AvroMessage model Order { // Maps string to semantic type @AvroField(ordinal = 0) orderId: OrderId inherits String // Maps int to semantic type @AvroField(ordinal = 1) quantity: Quantity inherits Int // Maps array to semantic type array @AvroField(ordinal = 2) items: OrderItem[] } ``` -------------------------------------------------------------------------------- # Protobuf Source: https://taxilang.org/docs/other-api-specs/protobuf Section: Working with other API specs -------------------------------------------------------------------------------- ## Overview Taxi can enhance Protocol Buffer definitions with semantic meaning, enabling: - Semantic typing for protobuf messages and fields - Integration with message streaming systems - Consistent type definitions across protobuf and other formats > **Note: Semantic Enhancement** > > While Protocol Buffers describe message structure, Taxi adds semantic meaning to enable better data discovery and integration across systems. ## Adding Taxi to Protobuf ### Basic Setup 1. First, create the Taxi extension definition in your protobuf project: ```protobuf // taxi/dataType.proto import "google/protobuf/descriptor.proto"; package taxi; extend google.protobuf.FieldOptions { optional string dataType = 50002; } ``` 2. Then use the extension to add semantic types: ```protobuf import "taxi/dataType.proto"; message Order { // Add semantic type for customer name optional string customer_name = 1 [(taxi.dataType)="acme.CustomerName"]; // Add semantic type for order ID optional int32 order_id = 2 [(taxi.dataType)="acme.OrderId"]; } ``` ### Field Type Mapping Taxi automatically maps protobuf field types to semantic types: ```protobuf message Customer { // Maps to String-based semantic type optional string name = 1 [(taxi.dataType)="acme.CustomerName"]; // Maps to Int-based semantic type optional int32 age = 2 [(taxi.dataType)="acme.CustomerAge"]; // Maps to array type repeated string tags = 3 [(taxi.dataType)="acme.Tag"]; } ``` ## Generated Taxi Models When Taxi processes a protobuf definition, it generates corresponding Taxi models: ```taxi @lang.taxi.formats.ProtobufMessage( packageName = "acme.proto", messageName = "Customer" ) model Customer { @lang.taxi.formats.ProtobufField(tag = 1, protoType = "string") name: CustomerName? @lang.taxi.formats.ProtobufField(tag = 2, protoType = "int32") age: CustomerAge? @lang.taxi.formats.ProtobufField(tag = 3, protoType = "string") tags: Tag[] } ``` > **Note: Reccomendation: Add taxi types to Protobuf schemas when possible** > > You can generate these Protobuf models by hand - but we don't recommend it. > If you have a protobuf schema and can annotate it, it's recommended to annotate that with taxi types, rather than > duplicating the schema definition as a Taxi model. -------------------------------------------------------------------------------- # SOAP Source: https://taxilang.org/docs/other-api-specs/soap Section: Working with other API specs -------------------------------------------------------------------------------- ## Overview Taxi can enhance SOAP WSDLs with semantic meaning by embedding type references directly in the WSDL definitions. This enables: - Semantic typing for SOAP services and messages - Integration with SOAP-based systems - Type-safe service discovery and invocation ## Adding Taxi to WSDL ### Namespace Declaration First, declare the Taxi namespace in your WSDL: ```xml > ``` > **Note: Namespace URL** > > Note the trailing slash in the namespace URL - `http://taxilang.org/` is required. ### Type References Add semantic types to WSDL elements using the `taxi:type` attribute: ```xml taxi:type="com.orbitalhq.CurrencyIsoCode"/> taxi:type="com.orbitalhq.CurrencyName"/> ``` -------------------------------------------------------------------------------- # CSV Source: https://taxilang.org/docs/other-api-specs/csv Section: Working with other API specs -------------------------------------------------------------------------------- ## Overview Taxi provides CSV format support through model annotations, enabling: - CSV reading and writing with semantic types - Configurable delimiters, headers, and value handling - Column name mapping ## Declaring CSV Models Add the `Csv` annotation to models that represent CSV data: ```taxi > import com.orbitalhq.formats.Csv > @Csv model Person { firstName: FirstName inherits String lastName: LastName inherits String age: Age inherits Int } ``` ## Configuration Options The `Csv` annotation supports several configuration parameters: ```taxi > import com.orbitalhq.formats.Csv > @Csv( > delimiter = "|", > nullValue = "NULL", > firstRecordAsHeader = true, > containsTrailingDelimiters = false, > quoteChar = "\"" > ) model Person { firstName: FirstName inherits String lastName: LastName inherits String age: Age inherits Int } ``` ### Available Options | Parameter | Description | Default | |----------------------------|------------------------------------|---------| | delimiter | Character separating fields | `,` | | firstRecordAsHeader | Use first record as column headers | `true` | | nullValue | String to interpret as null | | | containsTrailingDelimiters | Whether rows end with delimiter | `false` | | quoteChar | Character for quoting fields | `"` | ## Column Mapping ### Default Mapping By default, field names in the model are used to match CSV column names: ```taxi model Person { // Matches CSV column "firstName" firstName: FirstName inherits String // Matches CSV column "lastName" lastName: LastName inherits String } ``` ### Legacy Column References > **Warning: Deprecated** > > Using the `by column` tag is deprecated. Field names in the model are now used to match columns. For backward compatibility, explicit column mapping is supported but discouraged: ```taxi model Person { > firstName: FirstName by column("fName") // Maps to CSV column "fName" } ``` -------------------------------------------------------------------------------- # XML Source: https://taxilang.org/docs/other-api-specs/xml Section: Working with other API specs -------------------------------------------------------------------------------- ## Overview Taxi provides XML format support through model annotations, enabling: - XML reading and writing with semantic types - Element and attribute mapping - Collection handling - Expression evaluation ## Describing XML using Taxi It's possible to model XML structures directly in Taxi. If you're working with an existing schema - such as an Xsd, you may prefer [adding type definitions into your XSD](#adding-taxi-types-to-an-xsd) Add the `Xml` annotation to models that represent XML data: ```taxi > import com.orbitalhq.formats.Xml > @Xml model Person { firstName: FirstName inherits String lastName: LastName inherits String } ``` ### Attributes vs Elements By default, fields are mapped to XML elements. To map a field to an XML attribute: ```taxi > import com.orbitalhq.formats.Xml > import lang.taxi.xml.XmlAttribute > @Xml model Actor inherits Person { > @XmlAttribute id: ActorId fullName: FullName } ``` Results in: ```xml Jimmy Smith ``` ### Collections Array fields automatically map to repeated XML elements: ```taxi > @Xml model Movie { > actors: Actor[] } ``` Results in: ```xml Mel Gibson Jack Spratt ``` ### Expressions Models can include computed fields using expressions: ```taxi > import com.orbitalhq.formats.Xml > import lang.taxi.xml.XmlAttribute > @Xml model Actor { firstName: FirstName inherits String lastName: LastName inherits String fullName: FullName inherits String = FirstName + ' ' + LastName } ``` ### Expression Handling - When serializing: Expression values are written as normal elements ## Generating taxi from an Xsd The taxi compiler will generate a Taxi from an XSD document . ```xml ``` > **Note: Namespace URL** > > Note the trailing slash in the namespace URL - `xmlns:taxi="http://taxilang.org/` is required. ### Adding type annotations Use the `taxi:type` annotation within your XSD elements to map fields to **existing** types: ```xml ``` Would generate: ```taxi namespace org.tempuri { closed model CountryInfo { ISOCode : com.foo.IsoCode Name : com.foo.CountryName CapitalCity : com.foo.CapitalCityName } } ``` Note that the type definitions for field types are not declared - it's expected these types exist elsewhere in your taxonomy ### Mapping to an new type Use the `taxi:createType` annotation within your XSD elements to map fields to **new** types, which generates the type definitions as well as the corresponding model. ```xml ``` Would generate: ```taxi namespace com.foo { type IsoCode inherits String type CountryName inherits String type CapitalCityName inherits String } namespace org.tempuri { closed model CountryInfo { ISOCode : com.foo.IsoCode Name : com.foo.CountryName CapitalCity : com.foo.CapitalCityName } } ``` ### Nullable vs Mandatory fields Optional elements (minOccurs="0") generate nullable fields: ```xml ``` Generates: ```taxi nickname : String? // Nullable ``` Required attributes generate non-nullable fields: ```xml ``` Generates: ```taxi @lang.taxi.xml.XmlAttribute OrderDate : Date // Non-nullable ``` ### Enums XSD enumerations become Taxi enums: ```xml ``` ```taxi enum MandateClassification1Code { FIXE, USGB, VARI } ``` Enum values with spaces are automatically normalized: ```xml ``` ```taxi enum MandateClassification1Code { FOO_BAR("FOO BAR") // Name normalized, original value preserved } ``` ### Documentation XSD documentation annotations are preserved in generated Taxi: ```xml The date for an order ``` ```taxi [[ The date for an order ]] @lang.taxi.xml.XmlAttribute OrderDate : Date? ``` ## Xml types and Taxi types XSD primitive types map to Taxi types: | XSD Type | Taxi Type | |--------------|-----------| | xsd:string | String | | xsd:integer | Int | | xsd:int | Int | | xsd:long | Long | | xsd:decimal | Decimal | | xsd:double | Double | | xsd:boolean | Boolean | | xsd:date | Date | | xsd:dateTime | DateTime | | xsd:time | Time | -------------------------------------------------------------------------------- # Taxi projects Source: https://taxilang.org/docs/packages/taxi-projects Section: Packages -------------------------------------------------------------------------------- ## Taxi projects and files A Taxi project contains a `taxi.conf` file, and a collection of files saved with a `.taxi.` extension. A typical project might look like this: ```text . ├── src/ │ └── sample.taxi ├── tests/ │ └── hello-world/ │ ├── hello-world.spec.json │ ├── input.csv │ └── expected-output.json └── taxi.conf ``` Taxi files are simple text files, saved with a `.taxi` extension. ## The taxi.conf file A `taxi.conf` file describes a project's layout, and the plugins to be invoked after compilation. It follows the [HOCON](https://github.com/lightbend/config/blob/master/HOCON.md#hocon-human-optimized-config-object-notation) format, which is like supercharged JSON. ```hocon name: taxi/maven-sample version: 0.3.0 sourceRoot: src/ plugins: { taxi/kotlin: { maven: { groupId: "lang.taxi" artifactId: "parent" repositories: [ { id: "internal-repo" url: "https://newcorp.nexus.com" snapshots: true } ] } } } ``` The source for the config can be seen [here](https://gitlab.com/taxi-lang/taxi-lang/-/blob/develop/core-types/src/main/java/lang/taxi/packages/TaxiPackageProject.kt), which provides the complete documentation of how projects can be configured. | Name | Definition | |:--------------------|:----------------------------------------------------------------------------------------------| | name | The name of the taxi project. By convention, follows a format of `organisation/project-name` | | version | The version of the taxi project | | sourceRoot | The root location of taxi files to be compiled | | output | The folder where any generated artifacts will be written | | plugins | A list of plugins to be enabled on the project | | pluginSettings | Defines where plugins are fetched and loaded from | | dependencies | A list of other taxi project dependencies that this project depends on | | repositories | A list of taxi repositories to fetch content from | | publishToRepository | Defines where this project should be published to, should other projects wish to depend on it | | credentials | A list of credentials for authenticating with remote repositories | ### Dependencies Projects may declare dependencies on other taxi projects. This provides a powerful mechanism for creating modular, re-usable taxonomies across organisations. The format for declaring dependencies is: ```hocon dependencies: { // Fetch from a package manager, such as Nexus "org/ProjectName" : 0.2.0 // Fetch from a github repository, at a specific branch / tag "com.orbitalhq/core" : "github:orbitalapi/orbital-core-taxi#0.34.0" // Fetch from a gitlab repository (at https://gitlab.com) on the default branch "org.test/dependencyA" : "gitlab:taxi-lang/test-project-a" // Fetch from a different git provider "org.test/dependencyB" : "https://someGitProvider.com/taxi-lang/test-project-a.git" // Fetch from a subdirectory within a git repository (useful for monorepos) // taxi.conf is expected at taxi/schema/taxi.conf relative to the repo root "com.example/schemas" : "github:example/monorepo#0.34.0:taxi/schema" } ``` For details on sharing, publishing and depending on taxonomies, see [Publishing Taxi projects](https://taxilang.org/docs/packages/publishing) ### Repositories Repositories define where to try to download dependencies from (and where to publish to). Currently, only [Sonatype Nexus](https://www.sonatype.com/nexus/repository-oss) is supported, or pull directly from Github. The Taxi CLI tries to download each dependency from the list of repositories until successful. The format for declaring a repository in your `taxi.conf` file is as follows: ``` repositories: [ { // The name of the repository. Optional, but used to reference credentials name: "my-corporate-nexus", url: "http://localhost:8081", // Optional. Defines the type of repository being used. Currently only 'nexus' is supported, // so this can be omitted. type : "nexus", // Settings specific to the repository provider. // Below settings shown are applicable to a nexus repository settings: { // The name of the repository as defined within nexus repositoryName : taxi } } ] ``` For more information on publishing and sharing taxonomies, see [sharing projects with Nexus](https://taxilang.org/docs/packages/publishing#sharing-projects-with-nexus) ### Credentials Credentials define how to authenticate with a repository. The `repositoryName` attribute must match the `name` attribute of a defined repository. ``` credentials: [ { repositoryName: "nexus", username: "jimmy", password: "pass123" } ] ``` Note - credentials don't need to be defined in your projects main `taxi.conf` - in fact, we recommend you don't. Instead, define a configuration file at `~/.taxi.conf` (your home directory) which contains the credentials. See below for more detail on merging configuration files. ### Linter config Per-project linting rules can be specified in the `taxi.conf`, to disable or alter the severity of individual linter rules. The format of the section is as follows: ```json linter: { // The name of the linter rule. no-duplicate-types-on-models: { // Severity - optional - can be INFO | WARN | ERROR. // Defaults to WARN // Note that setting to ERROR will cause builds to fail if the rule is violated severity: INFO // enabled - optional - defaults to true enabled: true } no-primitive-types-on-models: {enabled: false} } ``` Note that the entire linter section is optional. If omitted, all rules are enabled, with the severity defined by each rule. (By convention, this is `WARN`). > **Warning** > > When making changes in VSCode, remember that you must save the `taxi.conf` file before changes are applied. ## Merging multiple configuration files By default, Taxi tooling will merge configuration files from the following locations: * The root of the project - `./taxi.conf` * The home directory - `~/.taxi/taxi.conf` This allows for storing of sensitive information - such as credentials - outside of main projects, and source control. -------------------------------------------------------------------------------- # Publishing Source: https://taxilang.org/docs/packages/publishing Section: Packages -------------------------------------------------------------------------------- Once built, Taxi projects can be packaged and published to a repository to allow other projects to depend on them. ## Adding dependencies Dependencies are defined in the `taxi.conf` project, in the `dependencies` section. Here's an example: ```json5 // Create a project in the acme organisation named "depends-on-sample" name: acme/depends-on-sample version: 0.3.0 sourceRoot: src/ dependencies: { // Depends on a project in the "acme" organisation called "sample", version 0.3.0 "org.acme/sample": 0.3.0 // Fetch from a github repository, at a specific branch / tag "com.orbitalhq/core" : "github:orbitalapi/orbital-core-taxi#0.34.0" // Fetch from a gitlab repository (at https://gitlab.com) on the default branch "org.test/dependencyA" : "gitlab:taxi-lang/test-project-a" // Depends on a project from git "org.acme/another-project" : "https://someGitProvider.com/taxi-lang/another-project.git" // Depends on a project from git, at a specific branch / tag "org.acme/project-3" : "https://someGitProvider.com/taxi-lang/project-3.git#1.2.0" } ``` When running a `taxi build` command, the taxi-cli will first ensure that all dependent projects have been downloaded, and are present in the local cache. Dependencies are either fetched from git, or from a specified artifact registry (like Nexus) you to specify the repository to download it from: For more details on how to define dependencies, and configure repositories to download them from, see [here](https://taxilang.org/language-reference/taxi-projects/#the-taxiconf-file). > **Note** > > If you're editing your `taxi.conf` file in VSCode using our plugin, saving changes to your `taxi.conf` will trigger the download of any dependencies as soon as you save the file. ## Sharing projects using Git To avoid the need for a [nexus](#sharing-projects-with-nexus) server, you can pull projects directly from a git repository. Declare them as follows: ```json5 // Create a project in the acme organisation named "depends-on-sample" name: acme/depends-on-sample version: 0.3.0 sourceRoot: src/ dependencies: { // Depends on a project from git "org.acme/another-project" : "https://gitlab.com/taxi-lang/another-project.git" // Depends on a project from git, at a specific branch / tag "org.acme/project-3" : "https://gitlab.com/taxi-lang/project-3.git#1.2.0" } ``` If working exclusively with git repos, you don't need to declare a `repositories` section. ### Github / Gitlab shorthand To use dependencies from github.com or gitlab.com, you can use the format `github:org/project` (or `github:org/project#tag`) For example: ```hocon dependencies: { // Fetch from a github repository, at a specific branch / tag "com.orbitalhq/core" : "github:orbitalapi/orbital-core-taxi#0.34.0" // Fetch from a gitlab repository (at https://gitlab.com) on the default branch "org.test/dependencyA" : "gitlab:taxi-lang/test-project-a" } ``` ### Authenticating to Git repos To provide authentication credentials, use a Username + Personal Authentication Token (supported in Github and Gitlab). For example: ```hocon dependencies: { "com.orbitalhq/core" : "https://alexhamilton:glpat-dundadadadundundun@github.com/taxi-conf/reynolds-pamphlet.git" } ``` HOCON supports env variable substitution, so you can also move sensitive credentials into an env variable: ```hocon dependencies: { // Note the variable is outside of quotes. "com.orbitalhq/core" : "https://"${GH_PAT}"@github.com/taxi-conf/reynolds-pamphlet.git" } ``` However, be careful of gotcha's with variable substitution in HOCON - see more information in [this issue](https://github.com/lightbend/config/issues/633) in the HOCON library ## Sharing projects with Nexus Currently, [Sonatype Nexus](https://www.sonatype.com/nexus/repository-oss) is the only non-git supported repository. This guide does not cover the basics of installing and getting Nexus running, and assumes you've already got a Nexus instance running somewhere. However, for a quick getting started, you can always use the [Nexus Docker image](https://hub.docker.com/r/sonatype/nexus3), as follows: ```bash $ mkdir /some/dir/nexus-data && chown -R 200 /some/dir/nexus-data $ docker run -d -p 8081:8081 --name nexus -v /some/dir/nexus-data:/nexus-data sonatype/nexus3 ``` This will give you a local running nexus at `https://localhost:8081`. ### Creating a Taxi repository In Nexus, Taxi repositories are defined as Raw repositories. * Log in to Nexus with admin credentials, and create a new repository. From the list of repository types, select "raw (hosted)" * Give the repository a name. This is the name that will be used in your repository config section in your taxi.conf file. * Configure the rest of the repository settings, including redeployment policies and blob store * Click "Create Repository" #### Updating your taxi.conf file to publish In the taxi.conf file of the project you wish to publish to your new repository, add the following: ```json5 publishToRepository: { // This name is used to look up credentials for auth name: "my-corporate-nexus" // The base url of the nexus url: "http://localhost:8081/" settings: { // The name you assigned to your repository when creating it in nexus repositoryName: taxi-on-nexus } } ``` If necessary, also add credentials - either in your projects `taxi.conf` file (not recommended), or on your system's taxi.conf at `~/.taxi/taxi.conf`: ``` credentials: [ {repositoryName: "my-corporate-nexus", username: "jimmy", password: "pass123"} ] ``` ### Configuring remote Nexus repositories Orbital will check any remote repositories declared when trying to resolve dependencies. Configure these repositories in your `taxi.conf` file: ```json5 repositories: [ { // The name of the repository. Optional, but used to reference credentials name: "my-corporate-nexus", url: "http://localhost:8081", // Optional. Defines the type of repository being used. Currently only 'nexus' is supported, // so this can be omitted. type : "nexus", // Settings specific to the repository provider. // Below settings shown are applicable to a nexus repository settings: { // The name of the repository as defined within nexus repositoryName : taxi } } ] ``` ### Publishing Taxi projects > **Tip** > > This section doesn't apply to projects with git urls, as projects are "published" as soon as they're pushed to the relevant Git repo / tag. Projects are published by running the `taxi publish` command, from within the same directory that contains your `taxi.conf` file. You must first configure a publishToRepository in your `taxi.conf` file: ```json5 publishToRepository: { url: "http://localhost:8081/" name : "nexus" settings: { repositoryName : taxi } } ``` Credentials can be stored elsewhere, following the rules for `taxi.conf` merging, as discussed [here](https://taxilang.org/language-reference/taxi-projects/#the-taxiconf-file). Having configured a repository to publish to, running `taxi publish` from the directory with your `taxi.conf` file will trigger the deployment: Here's a sample output: ```bash $ taxi publish Taxi version @22a6a51 Adding file /sample-project/taxi.conf Adding file /sample-project/src/Test-enum.taxi Adding file /sample-project/src/sub-src/Test-C.taxi Adding file /sample-project/src/Test-D.taxi Adding file /sample-project/src/Test-B.taxi Adding file /sample-project/src/Test-A.taxi Publishing package taxi/sample/0.3.0 from /tmp/sample-0.3.0.zip to http://localhost:8081/ Will attempt to publish to http://localhost:8081//repository/taxi/taxi/sample/0.3.0/sample-0.3.0.zip using basic auth credentials supplied Artifact uploaded successfully ``` -------------------------------------------------------------------------------- # Taxi CLI Source: https://taxilang.org/docs/taxi-cli/taxi-cli-intro Section: Taxi CLI -------------------------------------------------------------------------------- The `taxi` command line tool provides access to the compiler - which validates the syntax of taxi projects - and allows plugins of generators to create models and services in different languages and frameworks. ## Install Taxi is distributed by [SDKMAN](https://sdkman.io/). If you haven't installed this yet, follow the instructions at [SDKMAN's installation page](https://sdkman.io/install). Then simply run: ``` sdk i taxi taxi ``` ## Project overview A typical taxi project will be laid out as follows: ```text project/ ├── src/ │ ├── someTypes.taxi │ └── moreTypes.taxi └── taxi.conf ``` ## taxi.conf A `taxi.conf` file describes a project's layout, and the plugins to be invoked after compilation. It follows the [HOCON](https://github.com/lightbend/config/blob/master/HOCON.md#hocon-human-optimized-config-object-notation) format, which is like supercharged JSON. Read more details about the taxi.conf file [here](https://taxilang.org/language-reference/taxi-projects/#the-taxiconf-file) ## Plugins Taxi's compiler and language generators are extensible and pluggable. You can leverage our existing plugins to generate code, or author your own. Read more about plugins [here](https://taxilang.org/taxi-cli/plugins-intro/). ## Command reference ### init Creates a new taxi project, with a `taxi.conf` file, and the recommended layout. ```bash $ taxi init ``` ```bash mkdir hello-world cd hello-world taxi init ``` You'll be prompted with basic project details to name and create your new taxi project * Project group: `demo` ⏎ * Project name: `hello-world` ⏎ * Project version: ⏎ (Defaults are fine) * Source directory: ⏎ (Defaults are fine) A new `taxi.conf` has now been created, as follows: ```text name: demo/hello-world version: 0.1.0 sourceRoot: src/ ``` ### build Compile the taxi project, and invoke any language generators ```bash taxi build ``` This command invokes the taxi compiler to validate your taxi code, and invokes any language generators configured. (See [Plugins](https://taxilang.org/taxi-cli/plugins-intro). If no plugins are configured, no code will be emitted, but the project is still compiled, ensuring no syntactic errors exist. ### version-bump ```bash taxi version-bump major|minor|patch ``` This command increments the version defined in the `taxi.conf` file. > **Note** > > Running this command will change the layout of your `taxi.conf` file, to follow HOCON layout. > > While there are no material differences between the files before and after, cosmetic changes can occur. We're aware of this issue, and will address it in a future release. [Semantic Versioning](https://semver.org/) principals are followed when incrementing the version. ### set-version ```bash taxi set-version $newVersion ``` This command updates the version defined in the `taxi.conf` with to `$newVersion` eg: ```bash $ taxi-hello-world taxi set-version 0.2.3 Taxi version 0.10.4 Version updated from 0.1.0 to 0.2.3 ``` > **Note** > > Running this command will change the layout of your `taxi.conf` file, to follow HOCON layout. > > While there are no material differences between the files before and after, cosmetic changes can occur. > We're aware of this issue, and will address it in a future release. ### publish ```bash taxi publish ``` Publishes the current project to the repository defined in the `publishToRepository` of the `taxi.conf` file. This involves creating a zip file from the current project, and uploading it to the configured repository. When a project is published, only the following artifacts are added: * The `taxi.conf` file at the root of the project * All `*.taxi` files present in the configured `sourceRoot` No other content is added. eg: ```bash $ taxi publish Taxi Dev version @680c65b Adding file /sample-project/taxi.conf Adding file /sample-project/src/Test-enum.taxi Publishing package taxi/sample/0.3.0 from /tmp/sample-0.3.0.zip to http://localhost:8081/ Will attempt to publish to http://localhost:8081/repository/taxi/taxi/sample/0.3.0/sample-0.3.0.zip using basic auth credentials supplied Artifact uploaded successfully ``` ### orbital ```bash taxi orbital ``` This command launches a local version of Orbital - a TaxiQL query engine - configured to read the taxi project with live updates. A `docker-compose.yml` file is downloadded from [start.orbitalhq.com](https://start.orbitalhq.com), then runs `docker compose up`. Orbital is automatically configured as described in [Building a standalone taxonomy](https://docs.vyne.co/developers-guide/setting-up-vyne-locally#building-a-standalone-taxonomy), with the following components: A `docker-compose.yml` file is created locally, configured to use `latest`. You can override the version by passing `--version` or `-v`. eg: ```bash taxi orbital -v 0.14.0 ``` Note that if a `docker-compose.yml` already exists, then it is not overridden. You can change this by passing `-f` or `--force`. -------------------------------------------------------------------------------- # Plugins Source: https://taxilang.org/docs/taxi-cli/plugins Section: Taxi CLI -------------------------------------------------------------------------------- Plugins can be used to output a taxi model in a specific language - or to modify the source from a generator. Plugins are either packaged internally with Taxi, or you can write your own, which can be downloaded and included. ## Declaring a plugin Plugins are declared in the `plugins` section of the `taxi.conf` file. Declare the name of the plugin to enable, followed by configuration options for that plugin ```hocon plugins: { 'taxi/kotlin' { // Name of the plugin // plugin config goes here } } pluginSettings: { // Optional repositories: [ 'http://your-plugin-repository-url' ] localCache: '~/.taxi.plugins' } ``` ### Configuring a plugin Each plugin determines it's own configuration. The `PluginWithConfig` interface defines the type of config that a plugin will consume. For an example, check out the [Kotlin plugin](https://gitlab.com/taxi-lang/taxi-lang/-/blob/develop/taxi-cli/src/main/java/lang/taxi/cli/plugins/internal/KotlinPlugin.kt). ## Kotlin plugin The Kotlin Plugin generates Kotlin classes, annotated with `@DataType`. This makes referencing taxi types in services more typesafe and robust. ### What's generated ### Scalar & Primitive types Primitive and Scalar \(types without properties\) are output as kotlin `type alias` types, with a corresponding `@DataType` annotation ### Model types `model` types in Taxi \(or types that contain properties\) will generate a kotlin `data class`. with a corresponding `@DataType` annotation ### Enums Enums in Taxi are output as kotlin `enum`classes ### Configuration | Name | Usage | Default | |:----------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------| | outputPath | Defines where the generated kotlin code is written. This is relative to the output parameter in the main project config | kotlin | | kotlinVersion | The version of kotlin to use. Defines the Maven dependencies that are generated | 1.3.50 | | kotlinLanguageVersion | The kotlin language version. Corresponds to the [languageVersion](https://kotlinlang.org/docs/reference/using-maven.html#attributes-common-for-jvm-and-js) property in Kotlin's configuration. | 1.3 | | jvmTarget | Which version of the JVM to target. Defaults t | 1.8 | | maven | Defines the properties used for Maven configuration. If omitted, a `pom.xml` file is not generated. | - | | taxiVersion | The version of the taxi to depend on | The same version as the compiler being used. | For more details see the [KotlinPluginConfig](https://gitlab.com/taxi-lang/taxi-lang/-/blob/develop/taxi-cli/src/main/java/lang/taxi/cli/plugins/internal/KotlinPlugin.kt#L123) class. ### Generating a Maven pom.xml The plugin will optionally generate a Maven `pom.xml` file, to allow you to then compile and publish a jar file containing the generated artifacts. > **Note** > > The plugin will generate a valid `pom.xml` file, but doesn't attempt to run maven to compile the generated code. That's left to you, or your build server configuration. The following properties can be configured, which relate to the same concepts in a maven pom.xml file: * groupId \(defaults to the organisation name within the taxi project\) * artifactId \(defaults to the project name within the taxi project\) * dependencies * repositories * distribution management | Name | Usage | Default value | |:-----------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------| | groupId | The maven groupId in the generated output | The groupId of the taxi project | | artifactId | The maven artifactId in the generated output | The project name of the taxi project | | dependencies | Configures additional maven dependencies in the output maven pom. Generally, this isn't required. Kotlin and Taxi dependencies are included by default | Kotlin and Taxi dependencies | | repositories | Configures additional repositories to download dependencies from. Use this if you have an internal maven repository server that operates as a corporate proxy to Maven central | Omitted | | distributionManagement | Defines where generated jar files are published using mavens deploy command | Omitted | ### Sample Configuration ```text name: taxi/maven-sample version: 0.3.0 sourceRoot: src/ plugins: { taxi/kotlin: { maven: { groupId: "lang.taxi" artifactId: "parent" repositories: [ { id: "internal-repo" url: "https://newcorp.nexus.com" snapshots: true } ] distributionManagement: { id: "some-internal-repo" url: "https://our-internal-repo" } } } } ``` ## Writing your own plugins You can author your own plugins which hook into the Taxi compilation and code generation process by implementing the `ComponentProviderPlugin` interface. The [taxi-jpa](https://gitlab.com/taxi-lang/taxi-lang/-/tree/develop/plugins/taxi-jpa) project is provided as a sample to show how to implement your own plugins. ## Distributing Plugins See Distributing Taxi projects and plugins for more details. -------------------------------------------------------------------------------- # Generating OpenAPI Source: https://taxilang.org/docs/taxi-cli/openapi-plugin Section: Taxi CLI -------------------------------------------------------------------------------- ## Overview The OpenAPI Plugin generates OpenAPI specs from your Taxi code. ## What's generated OpenAPI specs describe a single API. In Taxi parlance, that's the equivalent of a single `service`. By default, all services that contain an operation with `@HttpOperation` annotations are generated. ```taxi // No OpenApi spec is generated, as none of the operations // include the @HttpOperation annotation service NotGenerated { operation listAllPeople():Person[] } // Written to movies-service.yaml service MoviesService { @HttpOperation operation listAllMovies():Movie[] // Not included in the generated OpenApi spec, // as it doesn't include an @HttpOperation spec operation doSomethingElse():Movie[] } ``` The generated specs are named after the service - ie `MoviesService` generates `MoviesService.yaml` ### Controlling which services are generated By default, all services that contain `@HttpOperation` annotated operation are generated. This can be reduced down by passing a set of names or namespaces in the config: ```hocon //taxi.conf name: org.taxilang/demo plugins: { taxi/open-api: { services: [ "com.foo", // matches com.foo.MyService and com.foo.bar.MyService, etc "org.acme.PersonService" // matches only org.acme.PersonService ] } } ``` ### How paths are resolved To ensure consistent generation of URLs, and compliant OpenAPI specs, you should decorate services with an `@HttpService()` with a `baseUrl` property: ```taxi @HttpService(baseUrl = "https://myMovies") // Generates a servers/url entry of https://myMovies in the OpenAPI spec service MoviesService { @HttpOperation(method = "GET" , url = "/movies") operation findAllMovies() : Movie[] } ``` Results in: ```yaml openapi: 3.0.1 info: title: MoviesService version: 1.0.0 servers: - url: https://myMovies ``` If the `baseUrl` (or `@HttpService` annotation) is not present, the generator will attempt to detect a common baseUrl for the contained services. This is a best-effort guess though. ## Configuration | Name | Usage | Default | |:-----------|:------------------------------------------------------------------------------------------------------------------------------------------|:---------| | outputPath | Defines where the generated OpenApi specs are written. This is relative to the output parameter in the main project config | open-api | | services | A list of services to generate OpenAPI specs for. If left empty, all services with operations containing `@HttpOperation` are generated. | kotlin | ### Example: ```hocon name: org.taxilang/demo version: 0.1.0 sourceRoot: src/ plugins: { taxi/open-api: { outputPath: "open-api" // Optional services: ["PersonService"] // Optional } } ``` -------------------------------------------------------------------------------- # Generating Kotlin Source: https://taxilang.org/docs/taxi-cli/kotlin-plugin Section: Taxi CLI -------------------------------------------------------------------------------- ## Overview The Kotlin Plugin generates Kotlin classes, annotated with `@DataType`. This makes referencing taxi types in services more typesafe and robust. > **Note** > > While this plugin generates Kotlin code, the generated code is designed to be usable in both Kotlin and Java projects without requiring a Kotlin runtime dependency. Enable the plugin by configuring the `plugins` section of your `taxi.conf` file: ```hocon name: com.petflix/films version: 0.1.0 sourceRoot: src/ > plugins: { > taxi/kotlin: { > ## Other config goes here if required (see below) > } > } ``` The plugin is then invoked when running the [Taxi CLI](https://taxilang.org/docs/taxi-cli/taxi-cli-intro) `build` command: ```bash ➜ taxi build Taxi 1.62.0 @89cba14 Fetching dependencies for com.petflix/films/0.1.0 Running generator @taxi/kotlin Generator @taxi/kotlin generated 16 files Wrote 16 files to /my-project/dist ``` ## What's generated ### Scalar & Primitive types Primitive and Scalar \(types without properties\) are output as kotlin `type alias` types, with a corresponding `@DataType` annotation ### Model types `model` types in Taxi \(or types that contain properties\) will generate a kotlin `data class`. with a corresponding `@DataType` annotation ### Enums Enums in Taxi are output as kotlin `enum`classes ## Configuration | Name | Usage | Default | |:----------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------| | outputPath | Defines where the generated kotlin code is written. This is relative to the output parameter in the main project config | kotlin | | types | A list of namespaces or fully-qualified type names to generate. If empty, all types are generated. Can contain namespaces (e.g., `com.foo`) to generate all types within that namespace, or specific types (e.g., `com.foo.Bar`) to generate only that type. | \[\] \(empty - generates all types\) | | kotlinVersion | The version of kotlin to use. Defines the Maven dependencies that are generated | 2.0.20 | | kotlinLanguageVersion | The kotlin language version. Corresponds to the [languageVersion](https://kotlinlang.org/docs/reference/using-maven.html#attributes-common-for-jvm-and-js) property in Kotlin's configuration. | 2.0 | | jvmTarget | Which version of the JVM to target. | 21 | | maven | Defines the properties used for [Maven configuration](#generating-a-maven-pom-xml). If omitted, a `pom.xml` file is not generated. | - | | taxiVersion | The version of the taxi to depend on | The same version as the compiler being used. | For more details see the [KotlinPluginConfig](https://gitlab.com/taxi-lang/taxi-lang/-/blob/develop/taxi-cli/src/main/java/lang/taxi/cli/plugins/internal/KotlinPlugin.kt#L123) class. ## Filtering Types You can selectively generate Kotlin code for specific namespaces or types using the `types` configuration option. This is useful when you only need a subset of your Taxi types in Kotlin. ### Generate specific namespace ```hocon plugins: { taxi/kotlin: { types: ["com.foo"] } } ``` This will generate Kotlin code for all types in the `com.foo` namespace. ### Generate multiple namespaces ```hocon plugins: { taxi/kotlin: { types: ["com.foo", "com.bar"] } } ``` ### Generate specific types ```hocon plugins: { taxi/kotlin: { types: ["com.foo.Person", "com.bar.Company"] } } ``` This generates Kotlin code only for the `com.foo.Person` and `com.bar.Company` types. ### Mixed filtering You can mix namespaces and specific types: ```hocon plugins: { taxi/kotlin: { types: ["com.foo", "com.bar.Company"] } } ``` This generates all types in `com.foo` namespace, plus the specific `com.bar.Company` type. ## Generating a Maven pom.xml The plugin will optionally generate a Maven `pom.xml` file, to allow you to then compile and publish a jar file containing the generated artifacts. > **Note** > > The plugin will generate a valid `pom.xml` file, but doesn't attempt to run maven to compile the generated code. That's left to you, or your build server configuration. The following properties can be configured, which relate to the same concepts in a maven pom.xml file: * groupId \(defaults to the organisation name within the taxi project\) * artifactId \(defaults to the project name within the taxi project\) * dependencies * repositories * distribution management | Name | Usage | Default value | | :--- | :--- | :--- | | groupId | The maven groupId in the generated output | The groupId of the taxi project | | artifactId | The maven artifactId in the generated output | The project name of the taxi project | | dependencies | Configures additional maven dependencies in the output maven pom. Generally, this isn't required. Kotlin and Taxi dependencies are included by default | Kotlin and Taxi dependencies | | repositories | Configures additional repositories to download dependencies from. Use this if you have an internal maven repository server that operates as a corporate proxy to Maven central | Omitted | | distributionManagement | Defines where generated jar files are published using mavens deploy command | Omitted | ### Sample Configuration ```hocon name: taxi/maven-sample version: 0.3.0 sourceRoot: src/ plugins: { taxi/kotlin: { maven: { groupId: "lang.taxi" artifactId: "parent" repositories: [ { id: "internal-repo" url: "https://newcorp.nexus.com" snapshots: true } ] distributionManagement: { id: "some-internal-repo" url: "https://our-internal-repo" } } } } ``` ## Using the Generated Code The Kotlin plugin generates code that can be consumed in both Kotlin and Java projects, enabling teams to create a shareable taxonomy of types that can be published and consumed across microservices. ### TypeNames Object The generator creates a `TypeNames` object containing static constants for all type names. This provides Java-friendly interoperability while maintaining type safety. For example, given this Taxi schema: ```taxi namespace films [[ The Id of a film ]] type FilmId inherits Int [[ The title of the film ]] type Title inherits String ``` The generator produces: ```kotlin object TypeNames { object films { const val FilmId: String = "films.FilmId" const val Title: String = "films.Title" } } ``` ### Usage in Java In Java, use the `TypeNames` constants to annotate fields and parameters with semantic metadata: ```java @Service("JavaService") public class JavaServiceTest { @Operation public Film findById( @DataType(value = TypeNames.films.FilmId, imported = true) String filmId ) { // implementation } } ``` This pattern allows teams to create a shareable taxonomy of core scalars, generate the Kotlin/Java code, and consume it within their microservices. ### Usage in Kotlin For Kotlin, it's even simpler. The code generator creates type aliases with `@DataType` annotations: ```kotlin @DataType( value = FilmId, imported = true ) typealias FilmId = Int ``` So the same service in Kotlin looks like: ```kotlin @Service("KotlinService") class KotlinServiceTest { @Operation fun findById(filmId: FilmId): Film { // implementation } } ``` ### Integration with Other Tools The generated code is designed to work seamlessly with: - **java2taxi** - Generates Taxi schemas from Java/Kotlin code - **Orbital's Spring Boot schema publisher starter** - Automatically publishes schemas from Spring Boot applications - Other Taxi ecosystem tooling This enables a workflow where teams can define core types in Taxi, generate code for use in services, and have tooling automatically discover and publish the semantic metadata. -------------------------------------------------------------------------------- # Linter Source: https://taxilang.org/docs/taxi-cli/linter Section: Taxi CLI -------------------------------------------------------------------------------- The taxi compiler has a built-in linter, which can be used to enforce best practices across your team. The rules can be configured, modifying their severity, or disabling entirely. See Linter config in the taxi.conf documentation for more details. ## Rules ### `no-duplicate-types-on-models` | Id | Default level | | :--- | :--- | | `no-duplicate-types-on-models` |`WARN` | This rule enforces that models do not allow the same type to be used for multiple fields. Reusing a type multiple times on a model leads to ambiguity when querying. For example: ```taxi type Name inherits String model Person { // Not permitted, as it leads to semantic ambiguity firstName : Name lastName : Name } ``` ### `no-primitive-types-on-models` | Id | Default level | | :--- | :--- | | `no-primitive-types-on-models` |`WARN` | This rule encourages the usage of semantic types, rather than base primitives. For example: ``` // Don't do this: model Person { name : String } // Do to this: type FirstName inherits String model Person { name : FirstName } ``` -------------------------------------------------------------------------------- # Java / Kotlin Source: https://taxilang.org/docs/sdks/java-kotlin Section: SDKs -------------------------------------------------------------------------------- The `taxiql-jvm` library lets you execute TaxiQL queries from Java and Kotlin applications, using query classes generated from your `.taxi` files at build time. _Available since 1.70.0_ ## Installation All modules are published to Maven Central under the `org.taxilang` group. Add the core module plus whichever transport suits your stack. ### Maven ```xml org.taxilang taxiql-jvm-core VERSION org.taxilang taxiql-transport-ktor VERSION ``` ### Gradle ```kotlin implementation("org.taxilang:taxiql-jvm-core:VERSION") implementation("org.taxilang:taxiql-transport-ktor:VERSION") ``` Replace `VERSION` with the latest release: ![](https://img.shields.io/badge/dynamic/xml.svg?label=Taxi&url=http%3A%2F%2Frepo.orbitalhq.com%2Frelease%2Forg%2Ftaxilang%2Fparent%2Fmaven-metadata.xml&query=%2F%2Frelease&colorB=green&prefix=v&style=for-the-badge&logo=kotlin&logoColor=white) Releases are published to Maven Central. Snapshot versions are published to the Orbital snapshot repository. To use a snapshot, add the repository to your `pom.xml`: ```xml orbital-snapshots https://repo.orbitalhq.com/snapshot true ``` --- ## Quick start Given these queries in a `.taxi` file: ```taxi namespace com.acme.flights query FindFlights { find { Flight[] } as { id : FlightNumber departs : Origin arrives : Destination }[] } query FindFlightDetails(flightNumber: FlightNumber) { find { Flight(FlightNumber == flightNumber) } as { id : FlightNumber departs : Origin arrives : Destination } } ``` The `taxiql-maven-plugin` generates `FindFlightsQuery` and `FindFlightDetailsQuery` at build time. You can use them directly: ```kotlin import com.acme.flights.FindFlightDetailsQuery import com.acme.flights.FindFlightsQuery import lang.taxilang.jvm.transport.ktor.KtorTaxiTransport suspend fun main() { val client = KtorTaxiTransport("https://orbital.example.com") // Fetch all flights val flights = client.executeForResult(FindFlightsQuery()).getOrThrow() println("Found ${flights.size} flights") // Stream flights one by one client.flow(FindFlightsQuery()) .onEach { println("${it.id}: ${it.departs} → ${it.arrives}") } .collect() // Fetch a single flight by number val flight = client.executeForResult(FindFlightDetailsQuery("QF-1")).getOrThrow() println("${flight.departs} → ${flight.arrives}") } ``` --- ## Code generation ### Maven plugin Add `taxiql-maven-plugin` to your `pom.xml`. It runs automatically during `generate-sources` and registers the output as a compile source root, so the generated classes are available to your code without any extra steps. ```xml org.taxilang taxiql-maven-plugin VERSION generate-sources ``` #### Project structure The plugin reads your `taxi.conf` to find query files. By default it looks for the conf file at `src/main/taxi/taxi.conf`: ```hocon # taxi.conf name: com.acme/flights version: 0.1.0 sourceRoot: src/ ``` Named queries anywhere under `sourceRoot` are picked up and compiled. A common layout: ``` src/main/java ← java source root src/main/taxi ← taxi source root src/ schema.taxi ← type definitions queries/ FindFlights.taxi ← named queries FindFlightDetails.taxi taxi.conf ``` Running `mvn generate-sources` (or any lifecycle phase that includes it - like `mvn compile`) writes a Java class per query to `target/generated-sources/taxi/`. #### Loading types from a remote Orbital server If your types are defined in a running Orbital instance rather than local `.taxi` files, point the plugin at the server and it will pull the schema automatically: ```xml https://orbital.example.com Bearer ${orbital.token} ``` The remote schema is used to resolve types referenced in your queries. The generated classes still come from the queries in your local source tree. #### What gets generated For each named query, the plugin produces a Java class that: - implements `UnaryTaxiQlQuery>` **and** `StreamableTaxiQlQuery` for `find { T[] }` queries, so you can either fetch the full list at once or stream results one by one with the same class - implements `UnaryTaxiQlQuery` for `find { T }` queries - implements `StreamableTaxiQlQuery` for `stream { T }` queries - has a typed constructor for each query parameter See [reference docs](#reference) below for examples. ### Gradle plugin A Gradle plugin is planned. If you'd like to use one, please reach out on [Slack](https://join.slack.com/t/orbitalapi/shared_invite/zt-697laanr-DHGXXak5slqsY9DqwrkzHg). --- ## Transports ### Ktor Best choice for Kotlin applications. `KtorTaxiTransport` uses coroutines throughout and exposes `executeForResult()` and `flow()` as the primary API. ```xml org.taxilang taxiql-transport-ktor VERSION ``` ```kotlin val client = KtorTaxiTransport( baseUrl = "https://orbital.example.com", authHeaderProvider = { mapOf("Authorization" to "Bearer ${token()}") } ) ``` `KtorTaxiTransport` implements `AutoCloseable` — use it in a `use` block or inject it as a long-lived singleton and call `close()` on shutdown. #### Fetching a single result ```kotlin val result = client.executeForResult(FindFlightsQuery()) result.onSuccess { flights -> println("Found ${flights.size} flights") } result.onFailure { error -> println("Failed: ${error.message}") } ``` `executeForResult()` is a suspend function that returns `Result`. It surfaces server-side failures without throwing, which makes it composable in coroutine pipelines. #### Streaming results ```kotlin client.flow(FindFlightsQuery()) .onEach { println("${it.id}: ${it.departs} → ${it.arrives}") } .collect() ``` `flow()` returns a Kotlin `Flow` where `T` is the element type. Errors during the stream (including server-reported errors) are thrown as exceptions and can be caught in the normal coroutine way. --- ### Spring WebClient Best choice for Spring WebFlux applications. `stream()` returns a `Flux` and `mono()` returns a `Mono`, making it a natural fit for reactive pipelines. ```xml org.taxilang taxiql-transport-webclient VERSION ``` Create a `WebClient` with the server base URL and pass it to the transport: ```kotlin @Bean fun taxiTransport(): WebClientTaxiTransport { val webClient = WebClient.builder() .baseUrl("https://orbital.example.com") .defaultHeader("Authorization", "Bearer ${token()}") .build() return WebClientTaxiTransport(webClient) } ``` #### Fetching a single result ```kotlin fun findFlights(): Mono> = transport.mono(FindFlightsQuery()) fun findFlightDetails(number: String): Mono = transport.mono(FindFlightDetailsQuery(number)) ``` #### Streaming results ```kotlin fun streamFlights(): Flux = transport.stream(FindFlightsQuery()) ``` Errors during the stream surface as `Flux.error` / `Mono.error` and can be handled with `.onErrorMap()` or `.doOnError()`. --- ### Java HttpClient Zero additional dependencies — uses the `java.net.http.HttpClient` built into Java 11+. Best for Java applications or anywhere you want to minimise the dependency footprint. ```xml org.taxilang taxiql-transport-http-client VERSION ``` ```java var transport = new JavaHttpClientTaxiTransport( "https://orbital.example.com", () -> Map.of("Authorization", "Bearer " + token()) ); ``` #### Fetching a single result ```java // Non-blocking transport.execute(new FindFlightsQuery()) .thenAccept(flights -> System.out.println("Found " + flights.size() + " flights") ); // Blocking var flights = transport.execute(new FindFlightsQuery()).get(); ``` #### Streaming results `stream()` returns a `Publisher` compatible with Project Reactor, RxJava, and any other Reactive Streams library: ```java transport.stream(new FindFlightsQuery()) .subscribe(new Subscriber<>() { public void onSubscribe(Subscription s) { s.request(Long.MAX_VALUE); } public void onNext(FindFlightsResult f) { System.out.println(f.getId()); } public void onError(Throwable t) { t.printStackTrace(); } public void onComplete() { System.out.println("Done"); } }); ``` --- ## Parameterized queries When a query declares parameters, the generated class has a constructor that accepts them: ```taxi query FindFlightDetails(flightNumber: FlightNumber) { find { Flight(FlightNumber == flightNumber) } as { ... } } ``` ```kotlin // Kotlin val flight = client.executeForResult(FindFlightDetailsQuery("QF-1")).getOrThrow() ``` ```java // Java var flight = transport.execute(new FindFlightDetailsQuery("QF-1")).get(); ``` Parameter types follow the Taxi type system. For type aliases wrapping primitives (like `FlightNumber inherits String`), the constructor parameter is the underlying Java type (`String` in this case), annotated with `@DataType` carrying the Taxi type name. This lets frameworks that understand Taxi semantics do type-safe wiring automatically. --- ## Error handling ### Query failures When the server rejects a query — either with a non-2xx HTTP response or by emitting an `event:error` SSE event mid-stream — the SDK surfaces a `QueryFailureException` containing the server's error message. With `executeForResult()` (Ktor), failures come back as `Result.failure` rather than exceptions: ```kotlin val result = client.executeForResult(FindFlightsQuery()) result .onSuccess { flights -> println("Found ${flights.size} flights") } .onFailure { error -> if (error is QueryFailureException) println("Query failed: ${error.failure.message}") } ``` With Spring WebClient, they surface as `Mono.error` / `Flux.error`: ```kotlin transport.mono(FindFlightsQuery()) .onErrorMap(QueryFailureException::class.java) { e -> MyApplicationException("Query failed: ${e.failure.message}") } ``` With `CompletableFuture` (Ktor's `execute()` or Java HttpClient), the exception is wrapped in a standard `ExecutionException`: ```kotlin try { val flights = transport.execute(FindFlightsQuery()).get() } catch (e: ExecutionException) { val cause = e.cause if (cause is QueryFailureException) println("Query failed: ${cause.failure.message}") } ``` ### SSE stream errors The server can signal an error mid-stream using an SSE error event: ``` event:error data:{"message":"No data sources were found that can return Flight[]", ...} ``` The SDK detects this and terminates the stream with a `QueryFailureException`, using whatever error channel is natural for the transport. Any results emitted before the error are delivered normally. With Ktor `flow()`, catch it like any other exception: ```kotlin try { client.flow(FindFlightsQuery()) .onEach { println(it.id) } .collect() } catch (e: QueryFailureException) { println("Stream failed: ${e.failure.message}") } ``` With Spring WebClient's `Flux`, handle it with `.doOnError()`: ```kotlin transport.stream(FindFlightsQuery()) .doOnError(QueryFailureException::class.java) { e -> println("Stream failed: ${e.failure.message}") } .subscribe() ``` --- > **Note: Jackson configuration** > > All transports use Jackson for JSON deserialization. The default `ObjectMapper` is Kotlin-aware (via `jackson-module-kotlin`). Pass a custom `objectMapper` to the constructor if you need specific serialization settings such as custom date formats or naming strategies. --- ## Reference The following classes are used by the SDK. They're generated by code-gen tools, such as the [Maven plugin](#maven-plugin), rather than intended to be written by hand. ### TaxiTransport The core interface implemented by all three transports: ```kotlin interface TaxiTransport { fun stream(query: StreamableTaxiQlQuery): Publisher fun execute(query: UnaryTaxiQlQuery): CompletableFuture } ``` `KtorTaxiTransport` and `WebClientTaxiTransport` extend this with idiomatic extras (`flow()`, `executeForResult()`, `mono()`) — prefer those in Kotlin and Spring code respectively. ### UnaryTaxiQlQuery Used for queries that return a single result or a complete list in one response: ```kotlin // Single object class FindFlightQuery(private val flightId: String) : UnaryTaxiQlQuery { override val taxiQl = """ query FindFlight(flightId: FlightId) { find { Flight( FlightId == flightId ) } } """.trimIndent() override val arguments = mapOf("flightId" to flightId) override val responseType = object : TypeReference() {} } // Collection class FindAllFlightsQuery : UnaryTaxiQlQuery> { override val taxiQl = "find { Flight[] }" override val arguments = emptyMap() override val responseType = object : TypeReference>() {} } ``` ### StreamableTaxiQlQuery Used for queries that stream results one element at a time: ```kotlin class LiveFlightsQuery : StreamableTaxiQlQuery { override val taxiQl = "stream { Flight[] }" override val arguments = emptyMap() override val streamType = object : TypeReference() {} } ``` A `find { T[] }` query can implement both interfaces on the same class, allowing the caller to choose between receiving the full list at once or streaming element by element: ```kotlin class FindAllFlightsQuery : UnaryTaxiQlQuery>, StreamableTaxiQlQuery { override val taxiQl = "find { Flight[] }" override val arguments = emptyMap() override val responseType = object : TypeReference>() {} override val streamType = object : TypeReference() {} } ``` The `taxiql-maven-plugin` generates this dual-interface pattern automatically for all `find { T[] }` queries. -------------------------------------------------------------------------------- # Adopting semantic types Source: https://taxilang.org/docs/language/adopting-semantic-types Section: Guides -------------------------------------------------------------------------------- ## Introducing Semantic Types > **Note: Evolution, Not Revolution** > > Successfully adopting semantic types is an incremental process. Start small, demonstrate value, then expand gradually. ## Getting Started Begin by identifying a focused use case: - Find two systems that exchange similar data - Identify overlapping concepts between their models - Create shared semantic types for these concepts Example: ```taxi // System A's original model model CustomerA { id: String email: String } // System B's original model model CustomerB { customerId: String emailAddress: String } // Create shared semantic types type CustomerId inherits String type EmailAddress inherits String // Refactor models to use semantic types model CustomerA { id: CustomerId email: EmailAddress } model CustomerB { customerId: CustomerId emailAddress: EmailAddress } ``` ## Project Organization > **Note: Separate Types from Models** > > Types are meant to be shared across systems, while models are system-specific. Your project structure should reflect this separation. ### File Organization Start with a simple separation: ``` src/ types/ # Shared semantic types (your taxonomy) customer.taxi order.taxi models/ # System-specific models service-a/ customer-model.taxi service-definitions.taxi service-b/ customer.api.taxi # A REST API exposing customer details. Has both model and services defined ``` As your taxonomy grows, consider moving types to their own project: ``` projects/ common-taxonomy/ # Shared semantic types service-a/ # Service A implementation dependencies: - common-taxonomy service-b/ # Service B implementation dependencies: - common-taxonomy ``` ### File naming conventions #### Types Define semantic types on their own, in a file named around the domain they model. For example: * `customer.types.taxi` * `account.types.taxi` > **Note: Also consider...** > > You may also wish to consider adopting separate namespaces, which further help keep concepts seperated. #### Services and Models Define services and models together, in the same file. Name the file after the type of service. eg: * `trades.kafka.taxi` * `account-events.kafka.taxi` * `customer.api.taxi` * `account.database.taxi` ## Mature Implementation Architecture > **Note: The Big Picture** > > A well-implemented Taxi ecosystem has clear separation between shared semantics and system-specific implementations. A mature implementation typically includes: ### Shared Taxonomy * Collection of semantic types * Broadly shared across organization * Version controlled and carefully governed * Published as a reusable package ### Service Implementations * Models and service definitions using types from taxonomy * System-specific structures * Published to TaxiQL server (like Orbital) * Each service depends on shared taxonomy ### Data Consumers * Import shared taxonomy only * Don't depend on service-specific models * Query data using TaxiQL * Receive data mapped to their needs ### Example workflow: ```taxi // In shared taxonomy type CustomerId inherits String type EmailAddress inherits String // In service implementation service CustomerService { operation getCustomer(CustomerId): Customer } // Consumer query using TaxiQL find { Customer } as { id: CustomerId contact: EmailAddress } ``` ## Best Practices ### Type Development * Focus on business concepts * Keep types focused and single-purpose * Document type meanings clearly * Version types carefully ### Model Development * Use semantic types for fields * Keep models service-specific * Don't share models between services ### Service Integration * Publish service contracts to TaxiQL server * Use semantic types in operation signatures * Let TaxiQL handle data mapping ## Measuring Success Your implementation is successful when: * Services can evolve independently * Data integration requires minimal code * New consumers can easily discover and use data * Changes to one service don't cascade to others * Semantic meaning is preserved across systems -------------------------------------------------------------------------------- # Best practices for taxonomy development Source: https://taxilang.org/docs/language/best-practices-for-taxonomy-development Section: Guides -------------------------------------------------------------------------------- ## Goals of effective taxonomies * Enable teams and systems to share, discover and transform data freely, and automatically * Allow teams and systems to evolve independently ## Antipatterns ### Consumers of a central taxonomy have to evolve together Changes to your central taxonomy should not require systems to adapt their platform, or require multiple consumers to update at the same time. If this is the case, it may indicate your taxonomy is a series of models, rather than types ### Common Domain Model A common domain model is where multiple systems across an organisation \(or across multiple organisations\) agree on a single shared definition of models. The rationale for this approach is typically to reduce integration costs, and lower the perceived inefficiencies of duplication. In practice, there is seldom a single "best" or canonical way to represent a concept, meaning that the result often ends up serving no-one well. The result of a Common Domain Model is that: * Teams lose their ability to innovate and iterate at their own pace * Evolution of models must go through a design committee, and seek broad sign-off, driving bureaucracy, eliminating team autonomy. * Concepts that apply to only a subset of the consumers either get rejected by the community, or result in diluted representations, as not all consumers can publish or consume the data * Teams will typically treat the Common Domain Model as a boundary concern, mapping their own internal representation onto it. In effect, Common Domain Models become self-defeating, as the cost of integration has simply been moved one layer of abstraction, rather than eliminated. -------------------------------------------------------------------------------- # Building your base taxonomy Source: https://taxilang.org/docs/language/building-your-base-taxonomy Section: Guides -------------------------------------------------------------------------------- ## Overview Your base taxonomy defines the core set of types that are used to describe services and models. They're the building blocks that form the basis of your organisation's taxonomy. Think of your taxonomy as a shared vocabulary, rather than as a series of shared models. \(See the [Common Domain Model Antipattern](https://taxilang.org/docs/language/best-practices-for-taxonomy-development#common-domain-model) for more detail\) A good sign of a well defined taxonomy is one that can be applied easily by service developers throughout their own domain models. ## Types, not models ### Avoid types with attributes > **Note** > > Models are great, but they're best left to individual systems to define, rather than the central taxonomy. Try to keep your central taxonomy populated with terms, are than models. Therefore, be careful as you start to add attributes to items in your central taxonomy, as it starts to become a [shared domain model](https://taxilang.org/docs/language/best-practices-for-taxonomy-development#common-domain-model), which has real drawbacks. Whenever you add an attribute, remember that **all** consumers and publishers have to be able to satisfy the contract, or they won't be able to use the type. You're often better off defining the attributes independently. This allows consumers of the taxonomy to compose and arrange the attributes in ways that are meaningful to them. This isn't to say that nested, tree structure models aren't useful - they are. But we think that the developers of services should define the models that are most meaningful to them. This is easier to do when the taxonomy contains simple types. ### Use the correct base type **Do:** Select the most appropriate type to model the concept. Encourage others to do the same. **Avoid:** Typing things as 'String', when another type is a better fit. ## Inheritance & Aliases When building a rich semantic model for describing business terms, inheritance is a powerful way to increase the richness of terms. ### Use `inherits` to increase specificity In Object-oriented software, inheritance is often discouraged. However, in taxonomy land, when were modelling single-attribute types, `inherits` is a great way building a hierarchy of specialisation. ```text type Name inherits String type FirstName inherits Name type LastName inherits Name ``` Inheritance works in a single direction. ie., using the above example, we can say: > All `FirstNames` are also `Names`, but not all `Names` are `FirstNames` ### Use type aliases when things are synonymous Type aliases are useful for indicating two ideas \(generally from different taxonomies or schemas\) mean exactly the same thing. ```text type FirstName inherits Name type LastName inherits Name type alias GivenName as FirstName // GivenNames and FirstNames are interchangeable type alias Surname as LastName ``` > **Warning** > > Only use type aliases when two things are entirely interchangeable. If things are _similar_, but _synonymous_ then avoid type aliases, and consider inheritance instead ### Avoid system-specific and publisher-specific variations in the base taxonomy ### Use inheritance to refine the "what", not the "who", "when" or "where" Use inheritance when you want to capture that something is a specialisation of a concept, rather than for modelling the context surrounding a concept. ie., avoid specialising types to cater for concepts such as: * Who created it * When it was created * Where it was published For example, avoid things like: ```text // Don't do this type AmazonPrice inherits Price ``` > **Note** > > An upcoming language feature called Context is intended for modelling this type of information -------------------------------------------------------------------------------- # Functions in Taxi Source: https://taxilang.org/docs/language/functions Section: Other pages -------------------------------------------------------------------------------- ## Overview Taxi provides support for declaring functions that can manipulate data. Taxi itself does not provide implementations of these functions - it's down to individual runtimes, such as [Orbital](https://orbitalhq.com). ## Declaring functions Functions are declared as follows: ```taxi namespace com.foo { declare function concat(String,String):String } ``` This declares a function named `com.foo.concat`, which accepts two `String` arguments, and returns a String. ### Parameter names Parameters may be optionally declared with parameter names: ```taxi namespace com.foo { declare function concat(first: String,second: String):String } ``` ### Vararg parameters The final parameter in a function may be declared as vararg by providing a trailing `...` ```taxi namespace com.foo { // values is vararg, and can accept multiple values declare function concat(something: Int, values:String...):String } ``` ## Using functions As with all other references, functions must be imported to be used, or referred to using their fully qualified name ### Functions in fields Functions can be used in expressions on models by declaring using a `by` operator: ```taxi import com.foo.concat model Person { firstName : FirstName inherits String lastName : LastName inherits String fullName : FullName inherits String by concat(FirstName, ' ', LastName) } ``` As with all expressions, parameters are passed either by Type reference, or Field reference: ```taxi import com.foo.concat model Person { firstName : FirstName inherits String lastName : LastName inherits String // By default references are assessed as lookups by type. fullName : FullName inherits String = concat(FirstName, ' ', LastName) // When prefixed by 'this.', lookups are assessed against field names formalName : String = concat(this.lastName, ', ', this.firstName) } ``` ### Functions in expression types Functions may also be used in expression types. When functions are referenced as a type, the underlying base type is optional, and can be inferred by the compiler ```taxi declare function concat(String...):String // FullName inherits String, as that's // the return type of `concat` type FullName = concat(FirstName, ' ', LastName) model Person { firstName : FirstName inherits String lastName : LastName inherits String fullName : FullName } ``` ### Lambda functions Taxi has support for declaring lambda functions - i.e., functions as parameters to another function. ```taxi declare function stringify(Int[], callback:(Int,String) -> String):String ``` ### Generic types Taxi has limited support for generic types in functions. Type arguments may be before the function name: ```taxi declare function reduce(T[], (T,A) -> A):A ``` ## Taxi Stdlib Taxi ships with a StdLib which contains a number of functions declared to assist with manipulating data. Read the docs of the Taxi StdLib [here](https://taxilang.org/docs/language/stdlib) -------------------------------------------------------------------------------- # Expressions Source: https://taxilang.org/docs/taxiql/expressions Section: Other pages -------------------------------------------------------------------------------- ## Why use expressions? Expressions solve three key challenges in data integration: 1. **Reusable business logic** - Define calculations once, use everywhere 2. **Dynamic data enrichment** - Compute values that don't exist in source systems 3. **Semantic computations** - Express business rules using your domain vocabulary Without expressions, you'd need to: - Duplicate calculation logic across queries - Rely on source systems to provide all computed values - Handle business logic in application code instead of your data layer ## Basic expressions in queries Start with inline expressions to compute values during projection: **Example: Computing values with expressions** Expressions calculate values on-the-fly during query execution, eliminating the need for pre-computed fields Schema: ```taxi model Flight { flightNumber : FlightNumber inherits String totalSeats : TotalSeats inherits Int soldSeats : SoldSeats inherits Int } service FlightService { operation getFlights():Flight[] } ``` Query: ```taxi find { Flight[] } as { flightNumber : FlightNumber totalSeats : TotalSeats soldSeats : SoldSeats // Compute remaining seats remainingSeats : Int = this.totalSeats - this.soldSeats // Calculate utilization percentage utilization : Decimal = (SoldSeats / TotalSeats) * 100 }[] ``` ## Expression types for reusable logic Turn common calculations into semantic types that encapsulate business rules: **Example: Reusable expression types** Expression types create a shared vocabulary for business metrics that can be used consistently across your organization Schema: ```taxi // Define business metrics as expression types type RemainingSeats inherits Int = TotalSeats - SoldSeats type UtilizationRate inherits Decimal = (SoldSeats / TotalSeats) * 100 type IsFullyBooked inherits Boolean = RemainingSeats == 0 type IsHighDemand inherits Boolean = UtilizationRate > 80 model Flight { flightNumber : FlightNumber inherits String totalSeats : TotalSeats inherits Int soldSeats : SoldSeats inherits Int } service FlightService { operation getFlights():Flight[] } ``` Query: ```taxi // Use expression types like any other type find { Flight[] } as { flight : FlightNumber remaining : RemainingSeats utilization : UtilizationRate fullyBooked : IsFullyBooked highDemand : IsHighDemand }[] ``` > **Note: Expression type benefits** > > Expression types aren't just about code reuse - they create semantic meaning. `UtilizationRate` is more meaningful than a raw percentage calculation, making queries self-documenting. ## Collection filtering with expressions Expression types capture filtering rules as named, reusable concepts. Declare the rule once as a boolean expression type, add it as a field on your model, and filters can then use it by name: **Example: Filtering with an expression type** `IsActive` defines the rule once, so the filter reads as "keep the active orders" Schema: ```taxi type CustomerId inherits String type OrderId inherits String type OrderStatus inherits String type OrderTotal inherits Decimal // Expression types that name the business rules type IsActive inherits Boolean = OrderStatus == "ACTIVE" type IsHighValue inherits Boolean = OrderTotal > 1000 model Order { id : OrderId customerId : CustomerId status : OrderStatus total : OrderTotal // Evaluated for each order as it's read isActive : IsActive isHighValue : IsHighValue } service OrderService { operation getOrdersForCustomer(CustomerId):Order[] } ``` Query: ```taxi given { CustomerId = "CUST123" } find { // Keep only the orders where IsActive is true Order[].filter((IsActive) -> IsActive == true) } ``` Result: ```json [ { "id": "ORD001", "customerId": "CUST123", "status": "ACTIVE", "total": 500, "isActive": true, "isHighValue": false }, { "id": "ORD002", "customerId": "CUST123", "status": "ACTIVE", "total": 1500, "isActive": true, "isHighValue": true }, { "id": "ORD003", "customerId": "CUST123", "status": "ACTIVE", "total": 2000, "isActive": true, "isHighValue": true } ] ``` Chain filters to combine rules: **Example: Combining expression types** Each filter applies one rule - chaining them keeps the active, high-value orders Schema: ```taxi type CustomerId inherits String type OrderId inherits String type OrderStatus inherits String type OrderTotal inherits Decimal // Expression types that name the business rules type IsActive inherits Boolean = OrderStatus == "ACTIVE" type IsHighValue inherits Boolean = OrderTotal > 1000 model Order { id : OrderId customerId : CustomerId status : OrderStatus total : OrderTotal // Evaluated for each order as it's read isActive : IsActive isHighValue : IsHighValue } service OrderService { operation getOrdersForCustomer(CustomerId):Order[] } ``` Query: ```taxi given { CustomerId = "CUST123" } find { // Chain filters to combine rules Order[].filter((IsActive) -> IsActive == true) .filter((IsHighValue) -> IsHighValue == true) } ``` Result: ```json [ { "id": "ORD002", "customerId": "CUST123", "status": "ACTIVE", "total": 1500, "isActive": true, "isHighValue": true }, { "id": "ORD003", "customerId": "CUST123", "status": "ACTIVE", "total": 2000, "isActive": true, "isHighValue": true } ] ``` > **Note: Add the expression type as a field** > > To filter on an expression type, include it as a field on the model (like `isActive : IsActive` above). > The engine evaluates expression types as each item is read - an expression type that isn't a field on the model won't match any items. ## Dynamic data discovery Expressions can reference data not present on the source object - TaxiQL will discover and fetch it: **Example: Automatic data enrichment** Expression types can reference data from multiple sources. TaxiQL automatically discovers and fetches required data to evaluate expressions. Schema: ```taxi type ProductId inherits String type Price inherits Decimal type DiscountRate inherits Decimal type TaxRate inherits Decimal type CustomerId inherits String // Expression types for pricing calculations type DiscountAmount inherits Decimal = Price * DiscountRate type DiscountedPrice inherits Decimal = Price - DiscountAmount type TaxAmount inherits Decimal = DiscountedPrice * TaxRate type FinalPrice inherits Decimal = DiscountedPrice + TaxAmount model Order { productId : ProductId customerId : CustomerId basePrice : Price } model CustomerProfile { customerId : CustomerId discountRate : DiscountRate // Customer's discount tier } model TaxInfo { rate : TaxRate } service OrderService { operation getOrders():Order[] } service CustomerService { operation getCustomerProfile(CustomerId):CustomerProfile } service TaxService { operation getCurrentTaxRate():TaxInfo } ``` Query: ```taxi // Expression types will trigger data discovery find { Order[] } as { product : ProductId basePrice : Price discount : DiscountAmount // Discovers DiscountRate from CustomerProfile afterDiscount : DiscountedPrice tax : TaxAmount // Discovers TaxRate from TaxService total : FinalPrice // Computed from discovered data }[] ``` ## Conditional logic with when expressions Use `when` expressions for dynamic categorization and business rules: **Example: Dynamic categorization** Conditional expressions encode business rules directly in your type system, ensuring consistent application of logic across all queries Schema: ```taxi type Revenue inherits Decimal type LastOrderDays inherits Int type OrderCount inherits Int // Customer segmentation logic type CustomerSegment inherits String = when { Revenue > 50000 && LastOrderDays < 30 -> "VIP Active" Revenue > 50000 && LastOrderDays < 90 -> "VIP Returning" Revenue > 50000 -> "VIP Dormant" Revenue > 10000 && LastOrderDays < 60 -> "High Value" LastOrderDays < 90 -> "Active" LastOrderDays < 180 -> "At Risk" else -> "Churned" } // Recommended action based on segment type RecommendedAction inherits String = when { CustomerSegment == "VIP Active" -> "Maintain engagement" CustomerSegment == "VIP Dormant" -> "Win-back campaign" CustomerSegment == "At Risk" -> "Retention offer" CustomerSegment == "Churned" -> "Re-engagement campaign" else -> "Standard communication" } model CustomerAnalytics { customerId : CustomerId inherits String name : CustomerName inherits String totalRevenue : Revenue daysSinceLastOrder : LastOrderDays orderCount : OrderCount } service AnalyticsService { operation getCustomerAnalytics():CustomerAnalytics[] } ``` Query: ```taxi find { CustomerAnalytics[] } as { customer : CustomerName revenue : Revenue lastActive : LastOrderDays // Automatic segmentation segment : CustomerSegment // Cascading business rules action : RecommendedAction }[] ``` ## Best practices ### When to use expression types vs inline expressions **Use expression types when:** - The logic represents a business concept (e.g., `ProfitMargin`, `CustomerLifetimeValue`) - Multiple queries need the same calculation - The expression encodes important business rules - You want to build a semantic vocabulary **Use inline expressions when:** - The calculation is query-specific - You're prototyping or exploring data - The logic doesn't represent a reusable concept ### Composition patterns Build complex expressions from simpler ones: ```taxi // Base calculations type GrossRevenue inherits Decimal = UnitPrice * Quantity type DiscountAmount inherits Decimal = GrossRevenue * DiscountPercentage type NetRevenue inherits Decimal = GrossRevenue - DiscountAmount // Derived calculations type TaxAmount inherits Decimal = NetRevenue * TaxRate type TotalAmount inherits Decimal = NetRevenue + TaxAmount // Business metrics type ProfitMargin inherits Decimal = (NetRevenue - TotalCost) / NetRevenue * 100 type AverageOrderValue inherits Decimal = TotalAmount / OrderCount ``` ### Performance considerations > **Note: Execution optimization** > > TaxiQL optimizes expression evaluation by: > - Caching intermediate results within a query > - Batching data discovery operations > - Evaluating expressions only when the result is needed ## Common patterns ### Financial calculations ```taxi // Comprehensive financial metrics type GrossProfit inherits Decimal = Revenue - CostOfGoodsSold type OperatingProfit inherits Decimal = GrossProfit - OperatingExpenses type NetProfit inherits Decimal = OperatingProfit - InterestAndTaxes type GrossProfitMargin inherits Decimal = (GrossProfit / Revenue) * 100 type NetProfitMargin inherits Decimal = (NetProfit / Revenue) * 100 type ReturnOnInvestment inherits Decimal = (NetProfit / TotalInvestment) * 100 ``` ### Data quality indicators ```taxi // Data completeness checks type HasCompleteProfile inherits Boolean = FirstName != null && LastName != null && Email != null type DataQualityScore inherits Int = when { HasCompleteProfile && HasVerifiedEmail && HasPhone -> 100 HasCompleteProfile && HasVerifiedEmail -> 80 HasCompleteProfile -> 60 else -> 40 } ``` ### Time-based calculations ```taxi // Business-relevant time calculations type DaysSinceLastPurchase inherits Int = CurrentDate - LastPurchaseDate type IsNewCustomer inherits Boolean = DaysSinceFirstPurchase < 30 type ChurnRisk inherits String = when { DaysSinceLastPurchase > 365 -> "High" DaysSinceLastPurchase > 180 -> "Medium" DaysSinceLastPurchase > 90 -> "Low" else -> "None" } ``` ## Advanced techniques ### Nested lambda composition Chain operations for complex transformations: ```taxi // Multi-step filtering and transformation type PremiumActiveProducts inherits Product[] = (Product[]) -> Product[] .filter((IsActive) -> IsActive == true) .filter((Price) -> Price > 100) .filter((Rating) -> Rating >= 4.5) .filter((StockLevel) -> StockLevel > 0) ``` ### Cross-type expressions Reference multiple types in calculations: ```taxi // Inventory calculations across types type StockValue inherits Decimal = QuantityOnHand * UnitCost type ReorderRequired inherits Boolean = QuantityOnHand < ReorderPoint && DaysUntilNextDelivery > LeadTimeDays type OptimalOrderQuantity inherits Int = when { ReorderRequired -> EconomicOrderQuantity else -> 0 } ``` ## Troubleshooting ### Expression evaluation order Expressions are evaluated left-to-right with standard operator precedence: ```taxi // Parentheses control evaluation order type Calculation1 = A + B * C // B * C evaluated first type Calculation2 = (A + B) * C // A + B evaluated first ``` ### Type resolution in expressions When TaxiQL can't resolve a type in an expression: 1. Check the type is available on the source object or through discovery 2. Verify services exist that can provide the missing type 3. Ensure proper type inheritance relationships ### Debugging expressions Use intermediate expression types to debug complex calculations: ```taxi // Break down complex expressions type Step1 = BaseValue * Multiplier type Step2 = Step1 + Adjustment type FinalResult = Step2 / Divisor // Instead of type ComplexCalc = (BaseValue * Multiplier + Adjustment) / Divisor ```