The TypeScript track
The same mirror of Part I, in Deno. Every listing is in examples/deno/ and was run
against a served ikigai-cli 0.1.18 as it was written. ikigai-deno is not on JSR yet, so
the listings import it by path — a sibling checkout at ../../../ikigai-deno; edit the
import if yours is elsewhere.
ikigai serve /tmp/ikbook-kernel.sock &
deno run -A examples/deno/client.ts /tmp/ikbook-kernel.sock
Resolution
// A name, resolved. `connect` speaks the wire protocol over the kernel's socket; the
// answer is a representation — bytes with a type — exactly what a Rust caller gets.
const k = await connect(path);
const rep = await k.source("urn:iki:fn:toUpper", { in: "resource oriented computing" });
console.log(rep.text); // RESOURCE ORIENTED COMPUTING
console.log(rep.mediaType); // text/plain;charset=utf-8
console.log(CacheStatus[rep.cacheStatus]); // how the kernel's cache answered: Miss, Hit, Uncacheable
connect returns a client whose methods are the five verbs; source is a Promise of
a representation — text, media type, cache verdict. The cache verdict is worth a look on
your first run: if another client already resolved that request against the same kernel,
it says Hit before you have done anything. The cache belongs to the kernel, not to the
connection.
Hello, resource
// The description is explicit here — `args:` is the ArgSpec list, stated the way the
// Rust chapter stated it — and the handler receives the named arguments. (ikigai-deno's
// `./zod` entry point derives the same list from a schema; this file stays
// zero-dependency on purpose.) `cacheable: true` is the same claim `.cacheable()` makes
// in Rust, and the mounting kernel is what honors it.
export const hello = endpoint("urn:ts:hello", {
summary: "Greet someone",
args: [{
name: "who",
required: true,
summary: "the name to greet",
class: XSD_STRING,
}],
cacheable: true,
}, ({ who }) => `Hello, ${who}!`);
export const shout = endpoint("urn:ts:shout", {
summary: "Uppercase a string, loudly",
args: [{
name: "text",
required: true,
summary: "the text to shout",
class: XSD_STRING,
}],
cacheable: true,
}, ({ text }) => `${String(text).toUpperCase()}!`);
if (import.meta.main) {
const path = Deno.args[0] ?? "/tmp/ts-hello.sock";
console.error(`serving urn:ts:hello and urn:ts:shout on ${path}`);
const server = new Server([hello, shout], path);
Deno.addSignalListener("SIGINT", () => server.shutdown());
await server.serve(); // blocks; speaks the wire protocol
}
Explicit args: here — the ArgSpec list stated the way the Rust chapter stated it, and
zero-dependency. ikigai-deno’s ./zod entry point derives the same list from a schema
instead (z.object({ who: z.string().describe(…) })), and that is the closer analog to
“the signature is the contract”; the sibling repository’s examples/endpoints.ts shows
it. Either way, a Rust host sees one description.
Why an endpoint describes itself
// Self-description, as data: the JSON face of Meta, parsed — the ArgSpecs an endpoint
// declared, which is what an agent's tool definition is made of.
const card = await k.describe("urn:iki:fn:toUpper");
console.log(card?.id, (card?.inputs as { name: string }[]).map((a) => a.name)); // toUpper [ "in" ]
// ...and the same card as a graph.
const turtle = await k.meta("urn:iki:fn:toUpper", "text/turtle");
console.log(turtle.text.split("\n")[0]); // @prefix ik: <https://ikigai-rs.dev/ns#> .
describe is the JSON face of Meta, parsed; meta(iri, "text/turtle") is the graph.
The same two faces the Rust chapters used, reached from a runtime that has never parsed
Turtle.
Binding
deno run -A examples/deno/hello.ts /tmp/ts-hello.sock &
ikigai --plain --mount urn:ts:=/tmp/ts-hello.sock -c 'source urn:ts:hello who=Ada'
ikigai --plain --mount urn:ts:=/tmp/ts-hello.sock -c 'source urn:ts:shout text="resource oriented computing" | urn:iki:fn:reverseList'
Hello, Ada!
[computed]
RESOURCE ORIENTED COMPUTING!
[2 computed]
The second line is the part to notice: a pipe from a TypeScript endpoint into a Rust one, in the host’s grammar, with neither knowing the other’s language. The host owns the topology; the peers own their answers.
What resolution buys you
// Cached once: the probe says not yet; a resolution; the probe says served now.
console.log(await k.isCached("urn:iki:fn:toUpper", { in: "a b" })); // false
await k.source("urn:iki:fn:toUpper", { in: "a b" });
console.log(await k.isCached("urn:iki:fn:toUpper", { in: "a b" })); // true
// Traced: the kernel records its own events and ships them back over the wire.
const [, events] = await k.sourceTraced("urn:iki:fn:toUpper", { in: "a b" });
for (const event of events) {
console.log(event.target, event.cacheHit ? "cache_hit" : "computed");
}
Capabilities
// Capabilities: connect under a narrowed authority and the kernel enforces it — a write
// to the file workspace under an inspect-only scope is refused, and the refusal arrives
// typed, as `DeniedError`, not as a string to parse.
const narrow = await connect(path, {
capability: Capability.scoped(["urn:cap:kernel:inspect"]),
});
try {
await narrow.sink("urn:file:notes.txt", "nope");
} catch (err) {
if (err instanceof DeniedError) console.log("denied:", err.message);
else throw err;
}
narrow.close();
instanceof DeniedError — the taxonomy crossed the wire, which is wire protocol v7’s
whole point and what examples/http_status.ts in the sibling repository turns into a
403.
What an L0 peer cannot do
The same limit as the Python track, and it is a property of the wire, not of
the language: a served peer has no back-channel into the kernel invoking it, so a
TypeScript endpoint cannot source another resource mid-invocation and cannot inherit a
golden thread. It is a leaf. Composition — pipes, maps, compose — happens in the host
above it, as the pipe above showed.