Skip to main content

SHELL

Constant SHELL 

Source
pub const SHELL: &str = "// The trait-independent half of a web-rpc Javascript endpoint. This file is embedded verbatim\n// at the top of every module rendered by `web_rpc::js::endpoint!`, ahead of the generated\n// schemas, method tables and class. It has no imports and no top-level side effects.\n//\n// The generated part is data: every type reachable from the traits becomes a schema value\n// (see `Codec`), and every method becomes an entry in a method table. This half interprets\n// them.\n\nconst MASK_64 = 0xffffffffffffffffn;\nconst MASK_128 = 0xffffffffffffffffffffffffffffffffn;\n\n// `MessageHeader`, by declaration order.\nconst REQUEST = 0;\nconst ABORT = 1;\nconst RESPONSE = 2;\nconst STREAM_ITEM = 3;\nconst STREAM_END = 4;\n\n// `WireArg`, by declaration order.\nconst WIRE_JS = 0;\nconst WIRE_BYTES = 1;\nconst WIRE_NONE = 2;\nconst WIRE_SOME = 3;\nconst WIRE_OK = 4;\nconst WIRE_ERR = 5;\n\nconst textEncoder = new TextEncoder();\nconst textDecoder = new TextDecoder(\"utf-8\", { fatal: true });\n\n/** A growable postcard writer. */\nexport class Writer {\n  constructor() {\n    this.buffer = new Uint8Array(64);\n    this.length = 0;\n    this.view = new DataView(this.buffer.buffer);\n  }\n\n  reserve(extra) {\n    if (this.length + extra <= this.buffer.length) return;\n    let capacity = this.buffer.length * 2;\n    while (capacity < this.length + extra) capacity *= 2;\n    const grown = new Uint8Array(capacity);\n    grown.set(this.buffer.subarray(0, this.length));\n    this.buffer = grown;\n    this.view = new DataView(this.buffer.buffer);\n  }\n\n  /** The bytes written so far, in a buffer of exactly that length. */\n  take() {\n    return this.buffer.slice(0, this.length);\n  }\n\n  u8(value) {\n    this.reserve(1);\n    this.buffer[this.length++] = value & 0xff;\n  }\n\n  i8(value) {\n    this.u8(value < 0 ? value + 256 : value);\n  }\n\n  bool(value) {\n    this.u8(value ? 1 : 0);\n  }\n\n  varint32(value) {\n    let rest = value >>> 0;\n    while (rest >= 0x80) {\n      this.u8((rest & 0x7f) | 0x80);\n      rest = rest >>> 7;\n    }\n    this.u8(rest);\n  }\n\n  zigzag32(value) {\n    this.varint32(((value << 1) ^ (value >> 31)) >>> 0);\n  }\n\n  varintBig(value, mask) {\n    let rest = BigInt(value) & mask;\n    while (rest >= 0x80n) {\n      this.u8(Number(rest & 0x7fn) | 0x80);\n      rest >>= 7n;\n    }\n    this.u8(Number(rest));\n  }\n\n  varint64(value) {\n    this.varintBig(value, MASK_64);\n  }\n\n  zigzag64(value) {\n    const wide = BigInt(value);\n    this.varintBig((wide << 1n) ^ (wide >> 63n), MASK_64);\n  }\n\n  varint128(value) {\n    this.varintBig(value, MASK_128);\n  }\n\n  zigzag128(value) {\n    const wide = BigInt(value);\n    this.varintBig((wide << 1n) ^ (wide >> 127n), MASK_128);\n  }\n\n  f32(value) {\n    this.reserve(4);\n    this.view.setFloat32(this.length, value, true);\n    this.length += 4;\n  }\n\n  f64(value) {\n    this.reserve(8);\n    this.view.setFloat64(this.length, value, true);\n    this.length += 8;\n  }\n\n  /** A length-prefixed run of bytes, from a `Uint8Array`, any other view, or an `ArrayBuffer`. */\n  bytes(value) {\n    const view = ArrayBuffer.isView(value)\n      ? new Uint8Array(value.buffer, value.byteOffset, value.byteLength)\n      : new Uint8Array(value);\n    this.varint32(view.length);\n    this.reserve(view.length);\n    this.buffer.set(view, this.length);\n    this.length += view.length;\n  }\n\n  string(value) {\n    this.bytes(textEncoder.encode(value));\n  }\n}\n\n/** A postcard reader over a byte view. */\nexport class Reader {\n  constructor(view) {\n    this.buffer = view;\n    this.position = 0;\n    this.view = new DataView(view.buffer, view.byteOffset, view.byteLength);\n  }\n\n  u8() {\n    if (this.position >= this.buffer.length) {\n      throw new RangeError(\"web-rpc: message ended mid-value\");\n    }\n    return this.buffer[this.position++];\n  }\n\n  i8() {\n    const byte = this.u8();\n    return byte > 127 ? byte - 256 : byte;\n  }\n\n  bool() {\n    return this.u8() !== 0;\n  }\n\n  varint32() {\n    let value = 0;\n    let shift = 0;\n    for (;;) {\n      const byte = this.u8();\n      value += (byte & 0x7f) * 2 ** shift;\n      if ((byte & 0x80) === 0) break;\n      shift += 7;\n    }\n    return value >>> 0;\n  }\n\n  zigzag32() {\n    const value = this.varint32();\n    return (value >>> 1) ^ -(value & 1);\n  }\n\n  varintBig() {\n    let value = 0n;\n    let shift = 0n;\n    for (;;) {\n      const byte = this.u8();\n      value |= BigInt(byte & 0x7f) << shift;\n      if ((byte & 0x80) === 0) break;\n      shift += 7n;\n    }\n    return value;\n  }\n\n  varint64() {\n    return this.varintBig() & MASK_64;\n  }\n\n  zigzag64() {\n    const value = this.varintBig() & MASK_64;\n    return BigInt.asIntN(64, (value >> 1n) ^ -(value & 1n));\n  }\n\n  varint128() {\n    return this.varintBig() & MASK_128;\n  }\n\n  zigzag128() {\n    const value = this.varintBig() & MASK_128;\n    return BigInt.asIntN(128, (value >> 1n) ^ -(value & 1n));\n  }\n\n  f32() {\n    const value = this.view.getFloat32(this.position, true);\n    this.position += 4;\n    return value;\n  }\n\n  f64() {\n    const value = this.view.getFloat64(this.position, true);\n    this.position += 8;\n    return value;\n  }\n\n  bytes() {\n    const length = this.varint32();\n    if (this.position + length > this.buffer.length) {\n      throw new RangeError(\"web-rpc: message ended mid-value\");\n    }\n    const slice = this.buffer.subarray(this.position, this.position + length);\n    this.position += length;\n    return slice;\n  }\n\n  string() {\n    return textDecoder.decode(this.bytes());\n  }\n\n  /** A reader over the next length-prefixed run of bytes. */\n  sub() {\n    return new Reader(this.bytes());\n  }\n}\n\n/** The `Writer` and `Reader` method that carries each primitive schema. */\nconst PRIMITIVES = {\n  bool: \"bool\",\n  u8: \"u8\",\n  i8: \"i8\",\n  u16: \"varint32\",\n  u32: \"varint32\",\n  i16: \"zigzag32\",\n  i32: \"zigzag32\",\n  u64: \"varint64\",\n  i64: \"zigzag64\",\n  u128: \"varint128\",\n  i128: \"zigzag128\",\n  f32: \"f32\",\n  f64: \"f64\",\n  char: \"string\",\n  string: \"string\",\n  bytes: \"bytes\",\n};\n\nfunction isUnitOnly(variants) {\n  return variants.every(([, variant]) => variant === \"unit\");\n}\n\nfunction expectTag(actual, expected, what) {\n  if (actual !== expected) {\n    throw new TypeError(`web-rpc: expected ${what}, found wire tag ${actual}`);\n  }\n}\n\n/**\n * Postcard and `WireArg` codecs driven by schema values.\n *\n * A schema is either a string naming a primitive, or an object with a `kind`:\n *\n *   \"bool\" \"u8\" \"i8\" \"u16\" \"i16\" \"u32\" \"i32\" \"u64\" \"i64\" \"u128\" \"i128\"\n *   \"f32\" \"f64\" \"char\" \"string\" \"bytes\" \"unit\"\n *   { kind: \"option\", inner }      `undefined` or `null` is `None`\n *   { kind: \"seq\", inner }         an array, or a `Uint8Array` when `inner` is \"u8\"\n *   { kind: \"tuple\", items }       an array of fixed length\n *   { kind: \"map\", key, value }    a `Map`\n *   { kind: \"struct\", fields }     `fields` is `[[name, schema], ...]`\n *   { kind: \"enum\", variants }     `variants` is `[[name, variant], ...]`\n *   { kind: \"ref\", name }          a schema declared under `name` in the table given to the\n *                                  constructor\n *\n * A variant is \"unit\", `{ kind: \"newtype\", inner }`, or a tuple or struct schema. An enum\n * whose variants are all unit is a string; any other enum is `{ tag: name, value }`, or\n * `{ tag: name, ...fields }` for a struct variant.\n *\n * A wire description says how one argument or return value crosses the channel:\n *\n *   { kind: \"js\", transfer }       a Javascript value in the message, transferred if asked\n *   { kind: \"postcard\", schema }   postcard bytes behind a tag and a length\n *   { kind: \"inline\", schema }     postcard bytes with no tag and no length\n *   { kind: \"option\", inner }\n *   { kind: \"result\", ok, err }    `{ tag: \"Ok\", value }` or `{ tag: \"Err\", value }`\n */\nexport class Codec {\n  constructor(schemas) {\n    this.schemas = schemas;\n  }\n\n  encode(schema, writer, value) {\n    if (typeof schema === \"string\") {\n      if (schema === \"unit\") return;\n      const method = PRIMITIVES[schema];\n      if (!method) throw new TypeError(`web-rpc: unknown schema ${schema}`);\n      writer[method](value);\n      return;\n    }\n    switch (schema.kind) {\n      case \"ref\":\n        this.encode(this.schemas[schema.name], writer, value);\n        return;\n      case \"option\":\n        if (value === undefined || value === null) {\n          writer.u8(0);\n        } else {\n          writer.u8(1);\n          this.encode(schema.inner, writer, value);\n        }\n        return;\n      case \"seq\":\n        if (schema.inner === \"u8\") {\n          writer.bytes(value);\n          return;\n        }\n        writer.varint32(value.length);\n        for (const item of value) this.encode(schema.inner, writer, item);\n        return;\n      case \"tuple\":\n        schema.items.forEach((item, index) => this.encode(item, writer, value[index]));\n        return;\n      case \"map\":\n        writer.varint32(value.size);\n        for (const [key, item] of value) {\n          this.encode(schema.key, writer, key);\n          this.encode(schema.value, writer, item);\n        }\n        return;\n      case \"struct\":\n        for (const [name, field] of schema.fields) this.encode(field, writer, value[name]);\n        return;\n      case \"enum\":\n        this.encodeEnum(schema, writer, value);\n        return;\n      default:\n        throw new TypeError(`web-rpc: unknown schema kind ${schema.kind}`);\n    }\n  }\n\n  encodeEnum(schema, writer, value) {\n    const tag = isUnitOnly(schema.variants) ? value : value.tag;\n    const index = schema.variants.findIndex(([name]) => name === tag);\n    if (index < 0) throw new TypeError(`web-rpc: unknown variant ${tag}`);\n    writer.varint32(index);\n    const variant = schema.variants[index][1];\n    if (variant === \"unit\") return;\n    switch (variant.kind) {\n      case \"newtype\":\n        this.encode(variant.inner, writer, value.value);\n        return;\n      case \"tuple\":\n        this.encode(variant, writer, value.value);\n        return;\n      case \"struct\":\n        this.encode(variant, writer, value);\n        return;\n      default:\n        throw new TypeError(`web-rpc: unknown variant kind ${variant.kind}`);\n    }\n  }\n\n  decode(schema, reader) {\n    if (typeof schema === \"string\") {\n      if (schema === \"unit\") return undefined;\n      const method = PRIMITIVES[schema];\n      if (!method) throw new TypeError(`web-rpc: unknown schema ${schema}`);\n      return reader[method]();\n    }\n    switch (schema.kind) {\n      case \"ref\":\n        return this.decode(this.schemas[schema.name], reader);\n      case \"option\":\n        return reader.u8() === 0 ? undefined : this.decode(schema.inner, reader);\n      case \"seq\": {\n        if (schema.inner === \"u8\") return reader.bytes();\n        const length = reader.varint32();\n        const items = [];\n        for (let index = 0; index < length; index += 1) {\n          items.push(this.decode(schema.inner, reader));\n        }\n        return items;\n      }\n      case \"tuple\":\n        return schema.items.map((item) => this.decode(item, reader));\n      case \"map\": {\n        const length = reader.varint32();\n        const map = new Map();\n        for (let index = 0; index < length; index += 1) {\n          const key = this.decode(schema.key, reader);\n          map.set(key, this.decode(schema.value, reader));\n        }\n        return map;\n      }\n      case \"struct\": {\n        const object = {};\n        for (const [name, field] of schema.fields) object[name] = this.decode(field, reader);\n        return object;\n      }\n      case \"enum\":\n        return this.decodeEnum(schema, reader);\n      default:\n        throw new TypeError(`web-rpc: unknown schema kind ${schema.kind}`);\n    }\n  }\n\n  decodeEnum(schema, reader) {\n    const index = reader.varint32();\n    const entry = schema.variants[index];\n    if (!entry) throw new RangeError(`web-rpc: unknown variant index ${index}`);\n    const [tag, variant] = entry;\n    if (variant === \"unit\") return isUnitOnly(schema.variants) ? tag : { tag };\n    switch (variant.kind) {\n      case \"newtype\":\n        return { tag, value: this.decode(variant.inner, reader) };\n      case \"tuple\":\n        return { tag, value: this.decode(variant, reader) };\n      case \"struct\":\n        return { tag, ...this.decode(variant, reader) };\n      default:\n        throw new TypeError(`web-rpc: unknown variant kind ${variant.kind}`);\n    }\n  }\n\n  encodeWire(description, writer, value, jsValues, transferList) {\n    switch (description.kind) {\n      case \"js\":\n        writer.varint32(WIRE_JS);\n        jsValues.push(value);\n        if (description.transfer) transferList.push(value);\n        return;\n      case \"postcard\": {\n        writer.varint32(WIRE_BYTES);\n        const inner = new Writer();\n        this.encode(description.schema, inner, value);\n        writer.bytes(inner.take());\n        return;\n      }\n      case \"inline\":\n        this.encode(description.schema, writer, value);\n        return;\n      case \"option\":\n        if (value === undefined || value === null) {\n          writer.varint32(WIRE_NONE);\n        } else {\n          writer.varint32(WIRE_SOME);\n          this.encodeWire(description.inner, writer, value, jsValues, transferList);\n        }\n        return;\n      case \"result\":\n        if (value && value.tag === \"Ok\") {\n          writer.varint32(WIRE_OK);\n          this.encodeWire(description.ok, writer, value.value, jsValues, transferList);\n        } else if (value && value.tag === \"Err\") {\n          writer.varint32(WIRE_ERR);\n          this.encodeWire(description.err, writer, value.value, jsValues, transferList);\n        } else {\n          throw new TypeError(\'web-rpc: expected { tag: \"Ok\" } or { tag: \"Err\" }\');\n        }\n        return;\n      default:\n        throw new TypeError(`web-rpc: unknown wire kind ${description.kind}`);\n    }\n  }\n\n  decodeWire(description, reader, jsValues) {\n    if (description.kind === \"inline\") return this.decode(description.schema, reader);\n    const tag = reader.varint32();\n    switch (description.kind) {\n      case \"js\":\n        expectTag(tag, WIRE_JS, \"a Javascript value\");\n        return jsValues.shift();\n      case \"postcard\":\n        expectTag(tag, WIRE_BYTES, \"postcard bytes\");\n        return this.decode(description.schema, reader.sub());\n      case \"option\":\n        if (tag === WIRE_NONE) return undefined;\n        expectTag(tag, WIRE_SOME, \"an option\");\n        return this.decodeWire(description.inner, reader, jsValues);\n      case \"result\":\n        if (tag === WIRE_OK) {\n          return { tag: \"Ok\", value: this.decodeWire(description.ok, reader, jsValues) };\n        }\n        expectTag(tag, WIRE_ERR, \"a result\");\n        return { tag: \"Err\", value: this.decodeWire(description.err, reader, jsValues) };\n      default:\n        throw new TypeError(`web-rpc: unknown wire kind ${description.kind}`);\n    }\n  }\n}\n\n/** True when a method\'s promise resolves the `Ok` payload and rejects with the `Err`. */\nfunction isFallible(method) {\n  return method.kind === \"value\" && method.returns.kind === \"result\";\n}\n\n/**\n * A `Worker`, `MessagePort` or `DedicatedWorkerGlobalScope`, wrapped so that the endpoint has\n * one way to post and one way to listen.\n *\n * The transport is used, never owned: nothing here creates it, terminates it, or calls\n * `start()` on a `MessagePort`. An unstarted port delivers nothing to these listeners.\n */\nclass Transport {\n  constructor(target) {\n    if (\n      !target ||\n      typeof target.postMessage !== \"function\" ||\n      typeof target.addEventListener !== \"function\"\n    ) {\n      throw new TypeError(\"web-rpc: endpoint must be a Worker, MessagePort or worker scope\");\n    }\n    this.target = target;\n    this.listeners = [];\n  }\n\n  post(message, transferList) {\n    this.target.postMessage(message, transferList);\n  }\n\n  on(type, handler) {\n    this.target.addEventListener(type, handler);\n    this.listeners.push([type, handler]);\n  }\n\n  detach() {\n    for (const [type, handler] of this.listeners) {\n      this.target.removeEventListener(type, handler);\n    }\n    this.listeners = [];\n  }\n}\n\nfunction abortError(message) {\n  const error = new Error(message);\n  error.name = \"AbortError\";\n  return error;\n}\n\n/** An `Error` becomes its message; anything else is encoded as thrown. */\nfunction reduceThrown(thrown) {\n  return thrown instanceof Error ? thrown.message : thrown;\n}\n\n/**\n * The base class of every generated endpoint.\n *\n * `methods` describes the trait this endpoint calls and `handlers` the trait it implements.\n * Each entry is `{ name, kind, args, returns }`, where `kind` is \"value\", \"notify\" or\n * \"stream\", `args` is a list of wire descriptions, and `returns` is the wire description of\n * the response or stream item, or null for a notification. An entry\'s position is the\n * method\'s index on the wire.\n */\nclass Endpoint {\n  constructor(options, className, codec, methods, handlers) {\n    const { endpoint, handlers: implementation } = options ?? {};\n    if (!endpoint) {\n      throw new TypeError(`${className}: options.endpoint is required`);\n    }\n    if (handlers.length > 0) {\n      if (!implementation || typeof implementation !== \"object\") {\n        throw new TypeError(`${className}: options.handlers is required`);\n      }\n      const missing = handlers\n        .filter((handler) => typeof implementation[handler.name] !== \"function\")\n        .map((handler) => handler.name);\n      if (missing.length > 0) {\n        throw new TypeError(`${className}: missing handlers: ${missing.join(\", \")}`);\n      }\n    }\n\n    this._className = className;\n    this._codec = codec;\n    this._methods = methods;\n    this._handlers = handlers;\n    this._implementation = implementation;\n    this._transport = new Transport(endpoint);\n    this._sequence = 0;\n    this._pendingRequests = new Map();\n    this._openStreams = new Map();\n    this._runningIterators = new Map();\n    this._inflightRequests = new Set();\n    this._ready = false;\n    this._failure = null;\n    this._closed = false;\n    this._queuedSends = [];\n    this._inbox = [];\n\n    this._transport.on(\"message\", (event) => this._onMessage(event.data));\n    this._transport.on(\"messageerror\", () =>\n      this._fail(new Error(`${className}: a message could not be deserialized`)),\n    );\n    this._transport.on(\"error\", (event) =>\n      this._fail(\n        new Error(`${className}: the transport failed to start: ${event.message ?? event.type}`),\n      ),\n    );\n\n    this._poll = setInterval(() => {\n      if (!this._ready && !this._closed && !this._failure) this._transport.post(null, []);\n    }, 10);\n    this._transport.post(null, []);\n  }\n\n  // -- handshake and transport -------------------------------------------------\n\n  _onMessage(data) {\n    if (this._closed) return;\n    if (!Array.isArray(data)) {\n      // Handshake: the peer is listening. Answer once so it sees us too.\n      if (!this._ready) {\n        this._ready = true;\n        clearInterval(this._poll);\n        this._transport.post(null, []);\n        const queued = this._queuedSends;\n        this._queuedSends = [];\n        for (const send of queued) send();\n        const inbox = this._inbox;\n        this._inbox = [];\n        for (const message of inbox) this._dispatch(message);\n      }\n      return;\n    }\n    if (!this._ready) {\n      this._inbox.push(data);\n      return;\n    }\n    this._dispatch(data);\n  }\n\n  _fail(error) {\n    if (this._failure || this._closed) return;\n    this._failure = error;\n    clearInterval(this._poll);\n    for (const pending of this._pendingRequests.values()) pending.reject(error);\n    this._pendingRequests.clear();\n    for (const stream of this._openStreams.values()) stream.rejectDone(error);\n    this._openStreams.clear();\n    this._queuedSends = [];\n  }\n\n  /** Post `[header, payload?, ...jsValues]`, or queue it until the handshake completes. */\n  _send(kind, sequence, payload, jsValues, transferList) {\n    if (this._closed) return;\n    const send = () => {\n      const header = new Writer();\n      header.varint32(kind);\n      header.varint32(sequence);\n      const headerBytes = header.take();\n      const message = [headerBytes.buffer];\n      const transfer = [headerBytes.buffer];\n      if (payload) {\n        const payloadBytes = payload.take();\n        message.push(payloadBytes.buffer);\n        transfer.push(payloadBytes.buffer);\n      }\n      this._transport.post(message.concat(jsValues), transfer.concat(transferList));\n    };\n    if (this._ready) send();\n    else this._queuedSends.push(send);\n  }\n\n  _encodeCall(index, args) {\n    const method = this._methods[index];\n    const payload = new Writer();\n    const jsValues = [];\n    const transferList = [];\n    payload.varint32(index);\n    method.args.forEach((description, position) => {\n      this._codec.encodeWire(description, payload, args[position], jsValues, transferList);\n    });\n    return { payload, jsValues, transferList };\n  }\n\n  _decodeReturn(index, method, payload, jsValues) {\n    const actual = payload.varint32();\n    if (actual !== index) {\n      throw new TypeError(`${this._className}: response is for another method`);\n    }\n    return this._codec.decodeWire(method.returns, payload, jsValues);\n  }\n\n  // -- calling the other side --------------------------------------------------\n\n  _request(index, args) {\n    const method = this._methods[index];\n    const sequence = this._sequence++;\n    let settle;\n    const promise = new Promise((resolve, reject) => {\n      settle = { resolve, reject };\n    });\n    if (this._closed) {\n      settle.reject(abortError(`${this._className}: endpoint closed`));\n    } else if (this._failure) {\n      settle.reject(this._failure);\n    } else {\n      const { payload, jsValues, transferList } = this._encodeCall(index, args);\n      this._pendingRequests.set(sequence, { ...settle, index, method });\n      this._send(REQUEST, sequence, payload, jsValues, transferList);\n    }\n    promise.abort = () => {\n      if (!this._pendingRequests.has(sequence)) return;\n      this._pendingRequests.delete(sequence);\n      this._send(ABORT, sequence, null, [], []);\n      settle.reject(abortError(`${this._className}: request aborted`));\n    };\n    return promise;\n  }\n\n  _notify(index, args) {\n    if (this._closed || this._failure) return;\n    const { payload, jsValues, transferList } = this._encodeCall(index, args);\n    this._send(REQUEST, this._sequence++, payload, jsValues, transferList);\n  }\n\n  _stream(index, args, callback) {\n    if (typeof callback !== \"function\") {\n      throw new TypeError(`${this._className}: a streaming method needs a callback`);\n    }\n    const method = this._methods[index];\n    const sequence = this._sequence++;\n    let resolveDone;\n    let rejectDone;\n    const done = new Promise((resolve, reject) => {\n      resolveDone = resolve;\n      rejectDone = reject;\n    });\n    if (this._closed || this._failure) {\n      rejectDone(this._failure ?? abortError(`${this._className}: endpoint closed`));\n      return { close() {}, done };\n    }\n    const state = {\n      index,\n      method,\n      callback,\n      queue: Promise.resolve(),\n      resolveDone,\n      rejectDone,\n      closed: false,\n    };\n    this._openStreams.set(sequence, state);\n    const { payload, jsValues, transferList } = this._encodeCall(index, args);\n    this._send(REQUEST, sequence, payload, jsValues, transferList);\n    return {\n      close: () => {\n        if (state.closed) return;\n        state.closed = true;\n        this._send(ABORT, sequence, null, [], []);\n      },\n      done,\n    };\n  }\n\n  /** Detach listeners, reject pending requests and close open streams. */\n  close() {\n    if (this._closed) return;\n    this._closed = true;\n    clearInterval(this._poll);\n    this._transport.detach();\n    const error = abortError(`${this._className}: endpoint closed`);\n    for (const pending of this._pendingRequests.values()) pending.reject(error);\n    this._pendingRequests.clear();\n    for (const stream of this._openStreams.values()) {\n      stream.queue.then(stream.resolveDone, stream.resolveDone);\n    }\n    this._openStreams.clear();\n    for (const iterator of this._runningIterators.values()) {\n      if (typeof iterator.return === \"function\") iterator.return();\n    }\n    this._runningIterators.clear();\n    this._inflightRequests.clear();\n    this._queuedSends = [];\n  }\n\n  // -- inbound -----------------------------------------------------------------\n\n  _dispatch(message) {\n    const slots = message.slice();\n    const header = new Reader(new Uint8Array(slots.shift()));\n    const kind = header.varint32();\n    const sequence = header.varint32();\n    switch (kind) {\n      case RESPONSE: {\n        const payload = new Reader(new Uint8Array(slots.shift()));\n        const pending = this._pendingRequests.get(sequence);\n        if (!pending) return;\n        this._pendingRequests.delete(sequence);\n        try {\n          const value = this._decodeReturn(pending.index, pending.method, payload, slots);\n          if (!isFallible(pending.method)) pending.resolve(value);\n          else if (value.tag === \"Ok\") pending.resolve(value.value);\n          else pending.reject(value.value);\n        } catch (error) {\n          pending.reject(error);\n        }\n        return;\n      }\n      case STREAM_ITEM: {\n        const payload = new Reader(new Uint8Array(slots.shift()));\n        const state = this._openStreams.get(sequence);\n        if (!state) return;\n        let item;\n        try {\n          item = this._decodeReturn(state.index, state.method, payload, slots);\n        } catch (error) {\n          this._openStreams.delete(sequence);\n          state.rejectDone(error);\n          return;\n        }\n        state.queue = state.queue.then(() => state.callback(item));\n        return;\n      }\n      case STREAM_END: {\n        const state = this._openStreams.get(sequence);\n        if (!state) return;\n        this._openStreams.delete(sequence);\n        state.queue.then(state.resolveDone, state.rejectDone);\n        return;\n      }\n      case REQUEST: {\n        const payload = new Reader(new Uint8Array(slots.shift()));\n        this._onRequest(sequence, payload, slots);\n        return;\n      }\n      case ABORT: {\n        this._inflightRequests.delete(sequence);\n        const iterator = this._runningIterators.get(sequence);\n        if (iterator) {\n          this._runningIterators.delete(sequence);\n          if (typeof iterator.return === \"function\") iterator.return();\n        }\n        return;\n      }\n      default:\n        console.error(`${this._className}: unknown message kind ${kind}`);\n    }\n  }\n\n  _onRequest(sequence, payload, slots) {\n    let index;\n    let handler;\n    let args;\n    try {\n      index = payload.varint32();\n      handler = this._handlers[index];\n      if (!handler) throw new RangeError(`unknown method index ${index}`);\n      args = handler.args.map((description) =>\n        this._codec.decodeWire(description, payload, slots),\n      );\n    } catch (error) {\n      console.error(`${this._className}: could not decode an inbound request:`, error);\n      return;\n    }\n    const implementation = this._implementation[handler.name];\n    if (handler.kind === \"notify\") {\n      try {\n        implementation.apply(this._implementation, args);\n      } catch (error) {\n        console.error(`${this._className}: ${handler.name} threw:`, error);\n      }\n      return;\n    }\n    if (handler.kind === \"stream\") {\n      this._runStream(sequence, index, handler, implementation, args);\n      return;\n    }\n    this._inflightRequests.add(sequence);\n    Promise.resolve()\n      .then(() => implementation.apply(this._implementation, args))\n      .then(\n        (value) => {\n          if (!this._inflightRequests.delete(sequence)) return;\n          // A handler returns the `Ok` payload.\n          const returned = isFallible(handler) ? { tag: \"Ok\", value } : value;\n          this._respond(sequence, index, handler, returned);\n        },\n        (error) => {\n          if (!this._inflightRequests.delete(sequence)) return;\n          if (isFallible(handler)) {\n            this._respond(sequence, index, handler, { tag: \"Err\", value: reduceThrown(error) });\n          } else {\n            console.error(`${this._className}: ${handler.name} threw:`, error);\n          }\n        },\n      );\n  }\n\n  _encodeReturn(index, handler, value) {\n    const payload = new Writer();\n    const jsValues = [];\n    const transferList = [];\n    payload.varint32(index);\n    this._codec.encodeWire(handler.returns, payload, value, jsValues, transferList);\n    return { payload, jsValues, transferList };\n  }\n\n  _respond(sequence, index, handler, value) {\n    let encoded;\n    try {\n      encoded = this._encodeReturn(index, handler, value);\n    } catch (error) {\n      console.error(`${this._className}: could not encode the result of ${handler.name}:`, error);\n      return;\n    }\n    this._send(RESPONSE, sequence, encoded.payload, encoded.jsValues, encoded.transferList);\n  }\n\n  /**\n   * Drive one inbound streaming call.\n   *\n   * An `Abort` arrives as a message event, so it is only observed between event loop turns: a\n   * producer that never awaits a macrotask runs to completion whatever the caller does.\n   */\n  _runStream(sequence, index, handler, implementation, args) {\n    let iterator;\n    try {\n      const iterable = implementation.apply(this._implementation, args);\n      iterator =\n        typeof iterable[Symbol.asyncIterator] === \"function\"\n          ? iterable[Symbol.asyncIterator]()\n          : iterable[Symbol.iterator]();\n    } catch (error) {\n      console.error(`${this._className}: ${handler.name} threw:`, error);\n      this._send(STREAM_END, sequence, null, [], []);\n      return;\n    }\n    this._runningIterators.set(sequence, iterator);\n    (async () => {\n      try {\n        for (;;) {\n          const step = await iterator.next();\n          if (step.done) break;\n          if (!this._runningIterators.has(sequence)) break;\n          const encoded = this._encodeReturn(index, handler, step.value);\n          this._send(STREAM_ITEM, sequence, encoded.payload, encoded.jsValues, encoded.transferList);\n        }\n      } catch (error) {\n        console.error(`${this._className}: ${handler.name} threw:`, error);\n      } finally {\n        this._runningIterators.delete(sequence);\n        this._send(STREAM_END, sequence, null, [], []);\n      }\n    })();\n  }\n}\n";
Expand description

The trait-independent part of every generated endpoint, emitted ahead of the rendered schemas, method tables and class.