# `nupp.host` `nupp.host` asks whatever is running the program for something and waits for the answer: the page's JavaScript in a browser, or the C or Rust application that embeds the runtime natively. Reach for it when a library needs a facility the host has and Nupp does not, such as a canvas, a clipboard or an engine's asset loader, and should not care which host it is running under. A request names a *kind*, a dotted name the host answers, and carries a flat list of values. Declare the kind's signature once as a function type and bind it; callers then get named arguments and a checked call: ```nupp:fragment local host = nupp.host --- Returns the host's id for the decoded image, and its width and height. local type DecodeImage = function(url: string, format: string?): (integer, integer, integer) local type ReleaseImage = function(id: integer): nil local releaseImage = host.bindPost("tecs.image.release") local decodeImage = host.bind("tecs.image.decode", late = function(id: any, ...: any): nil releaseImage(id) end) local id, width, height = decodeImage(url = "sprites.png") ``` A call suspends through `nupp.suspension` until the host answers, so it composes with [task scopes](../../../learn/runtime/concurrency/task-scopes/index.html), deadlines and cancellation. A host failure is raised in the caller with the host's message. `bind` allocates its closure once; a call through it passes its arguments straight through rather than gathering them into a table. ## Values Arguments and results are each a count of flat values: `nil`, booleans, numbers, strings and byte spans. Every position is kept, so interior and trailing `nil`s arrive as written. A number must be finite and a string must be UTF-8 text; arbitrary bytes are a `nupp.mem.span` of bytes, never a string holding binary. Tables and functions are refused, naming the argument's position. Structured data is encoded into bytes by the library that owns it. A byte span is a borrow, so it crosses through a bound kind whose signature says so; `call` and `post` take their arguments as `any`, which a borrow cannot cross, and carry the other values only: ```nupp:fragment local host = nupp.host local span = nupp.mem.span local type Digest = function(borrows bytes: span.ByteSpan): (integer, span.ByteSpan) local digest = host.bind("app.digest") const text = "hello" local length, echoed = digest(bytes = span.fromString(text)) ``` A byte result arrives as a byte span the caller owns. A string or byte value has a size limit: a string at most 64 KiB, bytes sent to the host at most 64 MiB, and bytes answered at most 8 MiB. A host names its own resources with integers it assigns. ## Results nobody is waiting for A call cancelled after it reached the host may still be answered. When the results name a host resource, declare `late`: it receives those results, runs where no task is waiting, and must not suspend, so it releases through `post`, which queues a request without waiting for it. A kind without `late` drops a late answer. `post` and `bindPost` never suspend. The host runs the request like any call; its answer is discarded and its failure reported rather than raised. ## Streams A stream carries messages one way, without answers. `send` appends one to a kind's outbound stream, which ships with the frame it was sent in; `bindSend` binds a kind to a typed function as `bind` does, which is how bytes are sent. `route` delivers a kind's inbound messages as `nupp.events` on a bus the program owns, at an address it picks, with each message's values filling the event's fields in declaration order: ```nupp:fragment local host = nupp.host local events = nupp.events @derive(events.Event) local struct PointerMove x: number y: number end const WINDOW = 1 local input: events.MessageBus = events.newMessageBus() input:observe(WINDOW, PointerMove, |move| -> print(move.x, move.y), "cursor") local route = host.route("app.pointer.move", PointerMove, input, WINDOW, "dropOldest", 64) ``` Messages arrive at a turn boundary, never in the middle of the code that runs between them, and an event nothing observes is never built. A stream that fills applies its policy: `block` suspends the sender until the frame ships, `latest` keeps only the newest message, and `dropOldest` drops the oldest past the limit and counts it. An inbound message carries scalars and strings, which is what an event's fields hold; bytes travel on a call. Natively a send reaches the application's handler at once, so nothing waits to ship and a policy never applies. ## Kinds A kind is two or more dot-separated segments of letters, digits, `_` and `-`, such as `tecs.image.decode`. Names under `nupp.` are reserved for the runtime. ::: seealso - [Suspension](../../../learn/runtime/concurrency/suspension/index.html) for what a wait does under a handler - [Embedding Nupp](../../../learn/projects/embedding/index.html) for answering requests from C ::: ## Submodules | Module | Description | | --- | --- | | `nupp.host.pump` | nupp.host.pump runs one operation at a time for an application that embeds the runtime and owns its own loop. | | `nupp.host.spi` | The interface a nupp.host implementation provides. | ## Types ### `Late` _type_ ```nupp type Late = function(...: any): nil ``` Called with the results of a call whose caller stopped waiting. ### `Policy` _type_ ```nupp type Policy = "block" | "latest" | "dropOldest" ``` What a full stream does with one more message. ### `Route` _record_ ```nupp record host.Route ... ``` An inbound stream's delivery to a bus; close it to stop. #### Members | Name | Kind | Description | | --- | --- | --- | | [`stop`](#nupp.host.Route.stop) | method | | | [`droppedCount`](#nupp.host.Route.droppedCount) | method | | | [`close`](#nupp.host.Route.close) | method | Stops delivering; messages already on their way are discarded. | | [`dropped`](#nupp.host.Route.dropped) | method | How many inbound messages this route has dropped under its policy. | #### `stop` _method_ ```nupp stop: function(): nil ``` `@private` ##### Returns | Type | Description | | --- | --- | | `nil` | | #### `droppedCount` _method_ ```nupp droppedCount: function(): integer ``` `@private` ##### Returns | Type | Description | | --- | --- | | `integer` | | #### `close` _method_ ```nupp close: function close(self: host.Route): nil ``` Stops delivering; messages already on their way are discarded. ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `host.Route` | | ##### Returns | Type | Description | | --- | --- | | `nil` | | #### `dropped` _method_ ```nupp dropped: function dropped(self: host.Route): integer ``` How many inbound messages this route has dropped under its policy. ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `host.Route` | | ##### Returns | Type | Description | | --- | --- | | `integer` | | ## Functions ### `host.attached` _function_ ```nupp function host.attached(): boolean ``` Reports whether a host is attached to answer requests. Natively that means an embedding application; a program run on its own has no host. In a browser the page is always attached, though it may answer a kind it does not know with a failure. #### Returns | Type | Description | | --- | --- | | `boolean` | | ### `host.bind` _function_ ```nupp function host.bind(kind: string, late: contracts.Late?): F ``` Binds `kind` to a function of type `F` that calls it. `F` supplies the parameter names and result types callers see, so a bound kind takes named arguments and checks them. Nothing checks that the host's answer matches `F`'s results. #### Type parameters | Name | Description | | --- | --- | | `F` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `kind` | `string` | the dotted name the host answers | | `late` | `contracts.Late?` | receives the results of a call answered after its caller was cancelled; it must not suspend | #### Returns | Type | Description | | --- | --- | | `F` | a function calling `kind` with its arguments | ### `host.bindPost` _function_ ```nupp function host.bindPost(kind: string): F ``` Binds `kind` to a function of type `F` that posts it. #### Type parameters | Name | Description | | --- | --- | | `F` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `kind` | `string` | the dotted name the host answers | #### Returns | Type | Description | | --- | --- | | `F` | a function posting `kind` with its arguments | ### `host.bindSend` _function_ ```nupp function host.bindSend(kind: string, policy: contracts.Policy?, limit: integer?): F ``` Binds `kind`'s outbound stream to a function of type `F` that sends on it. #### Type parameters | Name | Description | | --- | --- | | `F` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `kind` | `string` | the dotted name the host answers | | `policy` | `contracts.Policy?` | what a full stream does with one more message, `block` unless named | | `limit` | `integer?` | how many unshipped messages make the stream full, 1024 unless named | #### Returns | Type | Description | | --- | --- | | `F` | a function sending on `kind` with its arguments | ### `host.call` _function_ ```nupp function host.call(kind: string, ...: any): ...any ``` Sends a request of `kind` and waits for the host to answer it. #### Arguments | Name | Type | Description | | --- | --- | --- | | `kind` | `string` | the dotted name the host answers | | `...` | `any` | | #### Returns | Type | Description | | --- | --- | | `...any` | the host's results, each position kept | #### Raises | Type | Condition | | --- | --- | | `string` | when a value cannot cross, no host answers the kind, or the host fails the request | ### `host.post` _function_ ```nupp function host.post(kind: string, ...: any): nil ``` Queues a request of `kind` and returns without waiting for its answer. #### Arguments | Name | Type | Description | | --- | --- | --- | | `kind` | `string` | the dotted name the host answers | | `...` | `any` | | #### Returns | Type | Description | | --- | --- | | `nil` | | #### Raises | Type | Condition | | --- | --- | | `string` | when a value cannot cross or too many posts are pending | ### `host.route` _function_ ```nupp function host.route(kind: string, event: Type, bus: events.MessageBus, address: integer, policy: contracts.Policy?, limit: integer?): host.Route ``` Delivers `kind`'s inbound messages to `bus` as `event`s at `address`. Addresses are integers, the ids a host gives its windows, pads and devices. #### Type parameters | Name | Description | | --- | --- | | `E` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `kind` | `string` | the dotted name the host pushes | | `event` | `Type\` | the event each message fills, its fields in declaration order | | `bus` | `events.MessageBus\` | | | `address` | `integer` | | | `policy` | `contracts.Policy?` | `latest` or `dropOldest`; `dropOldest` unless named | | `limit` | `integer?` | how many messages one turn delivers, 1024 unless named | #### Returns | Type | Description | | --- | --- | | `host.Route` | the route, which stops when closed | #### Raises | Type | Condition | | --- | --- | | `string` | when `policy` is `block`, which an inbound stream cannot do | ### `host.send` _function_ ```nupp function host.send(kind: string, ...: any): nil ``` Sends a message on `kind`'s outbound stream. #### Arguments | Name | Type | Description | | --- | --- | --- | | `kind` | `string` | the dotted name the host answers | | `...` | `any` | | #### Returns | Type | Description | | --- | --- | | `nil` | | #### Raises | Type | Condition | | --- | --- | | `string` | when a value cannot cross |