官方插件

出站 HTTP(Wretch)

从不可变的 Wretch 基础实例派生业务客户端,并统一应用宿主的出站策略。

@pluxel/plugin-wretch 面向插件作者的公开入口是 . 与 ./workbench。可安装版本以 npm registry 为准;仓库内边界见 Package 矩阵。

@pluxel/plugin-wretch 提供一个原生、不可变的 Wretch 基础实例,并在请求真正发出前应用宿主级出站策略。URL 构造、addon、middleware、catcher 和响应链仍使用原生 Wretch API。

以下命令在快速开始生成的工作区根目录执行;按 添加插件 选择直接使用依赖的包,再运行 vp install。

vp exec pluxel pncat add @pluxel/plugin-wretch

普通 HTTP client 与宿主出站策略可在 services: [] 的 Host 中运行。只有显式启用受管出站设置时才需安装 Persistence 服务;未安装时,受管设置读写明确失败,不使用临时内存存储代替。

第一个 HTTP consumer

import { , type  } from '@pluxel/plugin-wretch'
import { ,  } from '@pluxel/core'
import { ,  } from '@pluxel/core/better-result'
import * as  from 'valibot'

const  = .({ : .(), : .() })
type  = .<typeof >

export class  extends ('CustomerNotFound')<{
	: string
	: string
}> {}

const  = .({
	: .(.(), .()),
})

@({ : 'Customer' })
export class  extends  {
	private readonly  = this..()
	private !: 

	constructor(private readonly : ) {
		super()
	}

	protected override (): void {
		this. = this...(this.., true)
	}

	(: string): <<, >> {
		return this.
			.(`/customers/${()}`)
			.(() =>
				.(new ({ , : `Customer ${} was not found` })),
			)
			.(() => .(.(, )))
	}
}

host 安装 provider:

以下 host 是 测试宿主,用于验证装配。应用入口按 添加插件 配置清单、配置记录和自动启动。

await host.start(WretchPlugin, {
	catalog: [CustomerPlugin],
	initialConfig: {
		timeoutMs: 30_000,
		maxConcurrentRequests: 64,
		maxQueuedRequests: 256,
		allowedOrigins: ['https://catalog.example'],
	},
})
await host.start(CustomerPlugin, {
	initialConfig: { baseUrl: 'https://catalog.example' },
})

client 本身就是 Wretch。Wretch 的 immutable 语义保证不同 consumer 通过 .url()、.options()、.headers()、.auth()、.addon() 或 .middlewares() 派生 client 时不会互相污染。

将可预期失败返回给调用方

上面的 CustomerPlugin.find() 是本地插件能力:上游返回 404 时,Wretch 的 .notFound() catcher 把它变成 CustomerNotFound,调用方用同一个 @pluxel/core/better-result 入口检查结果。成功响应先用 schema 验证; 其他 HTTP 状态、网络错误、超时和 lifecycle 撤回继续抛出,不能误报为“客户不存在”。 完整 Result 用法见 better-result 共享入口。

假设上面的 CustomerPlugin 从 @acme/customer 根入口导出,另一个插件可以直接依赖它并发布 HTTP route:

import { BasePlugin, Plugin } from '@pluxel/core'
import { Result } from '@pluxel/core/better-result'
import { ElysiaApp } from '@pluxel/services/elysia'
import { CustomerPlugin } from '@acme/customer'

@Plugin()
export class CustomerHttpPlugin extends BasePlugin {
	constructor(private readonly customers: CustomerPlugin) {
		super()
	}

	protected override init(): void {
		this.ctx.require(ElysiaApp).get('/customers/:id', async ({ params }) => {
			const result = await this.customers.find(params.id)
			if (Result.isError(result)) {
				return Response.json(
					{ code: 'customer_not_found', message: result.error.message },
					{ status: 404 },
				)
			}
			return Response.json(result.value)
		})
	}
}

把 CustomerHttpPlugin 加入宿主清单并启动;宿主还需安装 ElysiaApp 服务。 跨插件本地调用保留 Result 实例和 CustomerNotFound 类型;HTTP/Workbench/Worker 边界只传普通数据, 按传输协议选择状态码或 DTO。不要直接序列化 Result 或错误实例。发布 CustomerPlugin 时, 按插件包指南设置包含 /better-result 子入口的 Core peer 下限。

主入口导出 WretchPlugin、WretchConfig 及相关类型,不重新导出裸 wretch() factory 或 addons。需要 query-string addon、retry middleware 等上游扩展时,由 consumer 直接安装 wretch:

vp exec pluxel pncat add wretch

宿主 outbound policy

WretchConfig 是 configs.use() 使用的同一 schema。部署时可直接绑定,无需启用 Workbench:

import { envBinding } from '@pluxel/host'
import { WretchPlugin, WretchConfig } from '@pluxel/plugin-wretch'

// 放入应用的 envBindings 数组;HTTP_ORIGINS 使用 JSON 数组。
envBinding(WretchPlugin, {
	config: {
		schema: WretchConfig,
		mapping: {
			timeoutMs: 'HTTP_TIMEOUT_MS',
			allowedOrigins: 'HTTP_ORIGINS',
		},
	},
})

配置来源和只读规则遵循 Host 配置优先级。provider 的 origin、容量与 timeout 始终由这份配置控制;consumer 保存值不能放宽这些限制。

WretchPlugin 只有四个 Plugin config 字段:

字段默认值语义
timeoutMs30000每次底层 fetch attempt 的超时;0 关闭 host timeout
maxConcurrentRequests64所有 consumer 共享的进行中 fetch attempt 上限
maxQueuedRequests256并发满时最多等待多少 attempt;0 禁止排队
allowedOrigins[]空数组不限制;非空时只允许列出的精确 HTTP(S) origin

allowedOrigins 中每项必须是没有 path、query、hash 或 credential 的 HTTP(S) origin。实际 request URL 也必须是 absolute HTTP(S) URL;不支持相对 URL 或其他协议。

policy 通过 Wretch defer() 在请求发送前安装。consumer 的 retry middleware 位于外层,所以每次 retry attempt 都会单独经过:

  1. absolute URL 与 origin 检查;
  2. 全局并发/队列 admission;
  3. timeout 与 lifecycle AbortSignal;
  4. 实际 fetch;
  5. 释放并发槽。

队列满时 reject Error('Outbound HTTP request queue is full');origin 不允许时 reject 包含 origin 的 Error;timeout 使用名为 TimeoutError 的 DOMException。这些目前不是带稳定 code 的 Pluxel error hierarchy,transport 不应依赖完整 message 做协议映射。

retry、dedupe、缓存、认证刷新和业务错误解析不属于 host policy。它们应由 consumer 用 Wretch middleware/addon 明确组合;需要缓存时由业务显式组合缓存实现;仓库内部可以使用 Cache 预览,它目前不是公开安装包。

生命周期与 cancellation

取得的 client 与 consumer owner 和 provider generation 绑定:

  • consumer stop/replacement 后,旧 client 的新请求会失败;
  • provider stop/replacement 后,所有旧 client 都会失败;
  • 正在等待并发槽的请求会从队列移除并 reject;
  • 已进入 fetch 的请求会收到 lifecycle abort signal;
  • consumer 自己传入的 options.signal 会和 lifecycle/timeout signal 合并。

自定义 .fetchPolyfill() 必须遵守标准 AbortSignal,才能在 teardown 或 timeout 时及时结束。不要清空受管 client 的 deferred callbacks,否则会移除 provider policy。

可选的 managed settings

默认情况下,consumer 只受 host policy 管理。若希望操作者在 Workbench 为某个 consumer 配置普通 headers、HTTP(S) proxy 和更短 timeout,consumer 必须显式调用:

protected override async init(): Promise<void> {
	await this.http.enableManagedSettings()
	this.api = this.http.client.url(this.config.baseUrl, true)
}

managed settings 按 caller 的完整 Plugin node address 隔离并持久化。client 每次发送请求前读取最新状态,所以设置保存后,已缓存的 immutable client 也会自动生效。

无管理界面时,consumer 在启用后通过同一依赖读取、保存或重置;失败继续抛出,调用方决定是否重试:

await this.http.enableManagedSettings()
const saved = await this.http.updateManagedSettings({
	headers: { 'X-Region': 'hk' },
	timeoutMs: 5_000,
})
const current = this.http.managedSettings
await this.http.resetManagedSettings()

保存是显式操作,不在每次启动时自动覆盖旧值。reset 删除 caller 保存值,恢复原生 client 的 headers/proxy 和 provider timeout。应用 endpoint、鉴权与重试仍属于 consumer 自身配置或 Vault;此文件不是第二份 provider 部署配置。后续启动若降低 provider timeout,已有 caller timeout 会按新上限截断。

WretchManagedSettings 的真实形状是:

type  = <{
	: <<string, string>>
	?: string
	?: number
}>
  • managed header 会覆盖请求中同名普通 header;最多 32 项,单值最长 4096 字符;
  • authorization、cookie、proxy-authorization、set-cookie、x-api-key 等 secret-bearing header 会被拒绝;
  • transport-owned header(如 host、content-length、connection)也会被拒绝;
  • proxy 必须是无 credential、path、query 或 hash 的 HTTP(S) origin;authenticated proxy 不受支持;
  • consumer timeoutMs 必须是正整数,并且只能收紧 host timeout;host timeout 为 0 时,它成为有效 timeout。

managed settings 使用 runtime persistence,而不是 secret store。不要把 access token、API key 或 cookie 放入该 contract。Workbench disabled 时不会创建 UI backend,但已显式启用的 persisted settings 仍会应用于核心 HTTP client。

放置 Workbench Attachment

从 @pluxel/plugin-wretch/workbench 导入 provider-owned Attachment,并在 consumer 的 Workbench 定义中选择 placement:

import { workbench } from '@pluxel/workbench'
import { WretchWorkbench } from '@pluxel/plugin-wretch/workbench'

export const CustomerWorkbench = workbench.define({
	http: WretchWorkbench.settings.place(
		workbench.tab({ label: 'HTTP', icon: workbench.icons.Settings }),
	),
})

consumer 启动时启用设置,并直接绑定 constructor-injected required dependency:

protected override async init(): Promise<void> {
	await this.http.enableManagedSettings()
	this.api = this.http.client.url(this.config.baseUrl, true)

	this.ctx.workbench?.publish(CustomerWorkbench, {
		http: { provider: this.http },
	})
}

View 打开时,WretchPlugin 根据 Workbench 提供的 exact consumer.node 找到已经初始化的 managed state, 并创建 fresh WretchSettingsApi capability。若没有先完成 enableManagedSettings(),打开 View 会失败。 provider-owned zero-props renderer 通过 descriptor-bound scope 的 query/mutation resources 取得 provider stub; 它提供 snapshotDto()、updateDto(settings) 与 resetDto(),Framework 自动 detach 返回 DTO 并在 mutation settle 后刷新 snapshot。 snapshot 同时包含 hostTimeoutMs 和最终 effectiveTimeoutMs。

同一 caller 并发执行 enableManagedSettings() 会共享一次初始化。View 关闭、session 结束或 caller/provider 停止后,旧 capability 被撤销;provider cleanup 也会关闭 managed ProxyAgent。UI 不是核心 HTTP 请求能够运行的前提。

最后更新于

本页目录