OxL Reference

Interfaces: import proto, oxl gen, ORM relations

English translation of docs/interfaces.md (the Russian text is the original). A guide to К17 and К7 ([С §16.3, §5.9, §12.8]); the decisions taken are D-4740..D-4799 in decisions.md, the design is in the section "import proto and oxl gen" of ARCHITECTURE.md.

import proto#

oxl
import proto "proto/inventory.proto" { Inventory }     // the names in braces are optional: without them everything is imported
use std.os { args }

fn main() io, net {
  let inv = Inventory.connect(args()[1], tls: ClientTls{})
  match inv.Reserve(sku: "HAM-1", qty: 2) {            // the request fields are the rpc parameters
    ReserveReply r => print("{r.left}")
    NetError e => print("failed {e}")
  }
}
  • The path is looked up next to the source, in the package directory, in src/ and in proto/; import "x/y.proto" inside a .proto is looked up from the same root. OXL_DUMP_FFI=1 oxl build … prints the generated declarations.
  • message → data (@tag(N) = the field number, default values so that a message without zero fields is accepted), enum → @open enum, oneof → optional fields (and the form of oxl export for an enum with fields is again an enum), map → Map, repeated → List.
  • service → pub extern service with the path /package.Service/method. The rpc parameters are the fields of the request message; a response consisting of one field with number 1 is that field, any other response is the message type.
  • option idempotency_level = IDEMPOTENT | NO_SIDE_EFFECTS → @idempotent / @readonly. option (oxl.error) = true on a message → an @error type; option (oxl.raises) = "A, B" on an rpc → -> R | A | B. oxl export --proto writes these options itself (oxl/options.proto).
  • @serve before import proto turns the services into ordinary services with @public rpcs — they can be implemented through impl.
  • Not supported: proto2, edition, fixed*, sfixed*, Any, Struct; client/bidi streaming of a foreign .proto is skipped with the warning import-skip.

Round-trip test: oxl export --proto → import proto → a call of a live service gives the same bytes (ctest proto_import_roundtrip); a foreign server — ctest proto_import_interop.

oxl gen#

sh
oxl gen --py  [path] --out dir [--name module] [--all]
oxl gen --ts  [path] --out dir
oxl gen --go  [path] --out dir
oxl gen --kotlin [path] --out dir

Writes <package>_client.py|ts and oxl_runtime.py|ts (transport: grpcio / @grpc/grpc-js). Only pub(api) services (--all — all of them). --go (D-8230) writes a Go module (go.mod, <package>_client.go, oxlrt/oxlrt.go) with no dependencies but the standard library (Go 1.24, gRPC over net/http): methods take a context.Context, errors are error values (errors.As(err, &shop.NotFound{})), streams are iter.Seq / iter.Seq2. --kotlin (D-8231) writes <Package>Client.kt and OxlRuntime.kt (gRPC for the JVM: io.grpc:grpc-api, grpc-stub and a channel such as grpc-netty-shaded): data class, enum class, sealed class, errors are OxlExceptions. cpp is after 1.0. The Kotlin client is covered by a golden file and structural checks; the test compiles it when kotlinc and $OXL_KOTLIN_CP exist.

python
import shop_client as shop
with shop.CatalogClient("127.0.0.1:7000", token=jwt, timeout=2.0) as c:
    p = c.get("HAM-1")                         # Product (dataclass)
    try:
        c.reserve(shop.Line(sku="HAM-1", qty=100))
    except shop.OutOfStock as e:               # @error types are exceptions
        print(e.available)
    for p in c.list(shop.Category.Tools): ...  # server stream
    c.worth(iter(["HAM-1", "SAW-4"]))          # client stream; bidi — an iterator on input and on output
ts
import { CatalogClient, NotFound, result } from "./shop_client.ts";
const c = new CatalogClient("127.0.0.1:7000", { token: jwt, timeoutMs: 2000 });
const r = await result(c.get("NOPE-0"));       // { ok: false, error: NotFound }
for await (const p of c.list(Category.Tools)) console.log(p.name);
  • Refined types (type Sku = str where len(1..=16)) are checked on the client before sending and after receiving (RefinementError).
  • Timeout, Unavailable, Cancelled, AccessDenied, RpcError are used instead of NetError.
  • TypeScript uses erasable syntax only: it runs on node ≥ 22.18, Deno, Bun without compilation; 64-bit numbers are bigint.

Table relations and queries#

oxl
use std.orm { Db, ref, many, Ref, Many }

@table("customers")
data Customer { @pk @auto id: i64; name: str; orders: many<Order> = Many<Order>{} }

@table("orders")
data Order { @pk @auto id: i64; customer: ref<Customer>; total: i64 }

let big = from o in db.Order where o.customer.id == 7 && o.total > 100 order by o.id load(customer)
let withOrders = from c in db.Customer order by c.id load(orders)
let perRegion = from s in db.Sale group by s.region into g select (g.key, g.count())
  • ref<T> is a column holding the key (i64) of a row of T; the value is Ref<Customer>{ id: 7 }; after load(customer) the row is in o.customer.loaded[0].
  • many<T> has no column; T has exactly one ref field to this table; after load(orders) the rows are in c.orders.rows.
  • load runs one query per field (a semi-join on the same where/skip/take), identically on SQLite and PostgreSQL. builtin:// — not yet.
  • group by over a table: where/order by are in SQL, the groups are built over the fetched rows (D-4783).

Source: docs/interfaces.en.md

OxL 1.0.0-beta · Documentation version 1.0.0-beta© OxwaldLink · generated from the Standard, the std sources and the guides