豫言 / 豫言操作系统持久频道 @ 0.1.0

同一持久对象内跨事件的具名文字频道:先订阅后回放、发布保序、等待可限时,状态随对象实例存续。

源码浏览已生成;不能解析的文件以纯文本展示,不表示通过类型检查。

规范.汉语.md

# 持久频道接口规范 0.1.0

本包提供**同一个持久对象实例内、跨事件**的具名文字频道:一个事件订阅并阻塞等待,另一个事件发布,订阅事件被唤醒后转发。云工里每个 fetch、alarm 事件都新建一个豫言 Wasm 实例,事件之间没有内存共享,所以“事件 A 等、事件 B 叫醒 A”这类需求(事件流的实时推送、向长连接下发任务、把回传的分片转给正在读取的调用方)只能借宿主实例内的共享状态完成。本包在这份共享状态上给出保序、可限时等待、可关闭、订阅数可查的最小接口。接口包只依赖标准库。

**频道状态属于宿主实例,随持久对象被驱逐而丢失**:它不是持久存储,不跨实例、不跨重启。需要持久的事件序列(用于回放)用持久事务或关系数据库保存,频道只负责“已发生事件的即时通知”。本包只能在持久对象事件里使用,其他事件(普通 `fetch` 等)调用抛豫言异常。

## 频道与订阅

频道由名字标识:1 至 64 字节,字符限 `A-Z a-z 0-9 . _ : -`,区分大小写,不合规抛豫言异常。频道在第一个订阅出现时创建,最后一个订阅消失(取消、关闭、被断开)时消失。同一个实例内**至多 64 个频道**,每个频道**至多 256 个订阅**:订阅数超限时 `订阅持久频道` 抛可捕获的豫言异常(多个事件恰好同时越界时,后到者可能改由宿主拒绝);频道数超限由宿主拒绝,云工当前使宿主报错并中止本次运行,不一定能被「尝试运行」捕获。订阅是创建它的事件所拥有的资源,订阅柄是不透明字符串,**只在创建它的事件内有效**:另一个事件必须自己订阅,拿别处的柄调用抛豫言异常。**订阅之后**发布的消息一定送达该订阅,**订阅之前**已发布的消息不补发。每个订阅内消息保序:同一个发布者先发的先到,多个发布者交错时取决于各自发布的先后。

## 函数

`订阅持久频道` 订阅一个频道,返回订阅柄。订阅一经返回,之后到来的消息都会为它排队。

`发布持久频道文字` 把文字发布给频道当前的全部订阅,按订阅先后依次送达,返回送达的订阅数;频道不存在时返回 0。文字 0 至 65536 字节,空文字合法;文字不得以字节 0xFF 起首(合法 UTF-8 从不以它起首),违者抛异常;超过 65536 字节抛异常。已因积压被断开的订阅不计入送达数。

`等待持久频道消息` 接受订阅柄和超时毫秒(0 至 3600000,越界抛异常),阻塞豫言直到有消息、超时或订阅终结,返回(状态,文字):`0` 收到消息,文字是消息;`1` 超时,文字为空;`2` 频道已被关闭、订阅因积压超过 1 MiB 被断开或订阅已被取消,文字是原因。超时 0 表示立即轮询。状态 2 之后再等仍返回状态 2 与同一原因。频道关闭时,关闭前已排队的消息先交付,之后才得状态 2;订阅因积压被断开时,已排队而未读取的消息**全部丢弃**。阻塞期间不阻塞其他事件。

`取消订阅持久频道` 取消订阅并释放资源,幂等;取消之后再等待返回(2,`订阅已取消`)。**应用必须为每个订阅配一次取消,包括异常路径。**

`关闭持久频道` 关闭一个频道:向全部订阅先交付排队的消息,随后各订阅得状态 2 与原因(至多 1024 字节,超限抛异常),返回被关闭的订阅数,频道不存在时为 0。关闭之后同名频道可重新订阅(新一代,与旧订阅无关)。

`读取持久频道订阅数` 返回频道当前的订阅数,不存在时为 0;它不影响订阅端的等待时长,也不产生订阅端可见的消息。

失败分类:频道名、大小、超时范围、订阅柄、事件种类、订阅数上限等**违规**抛豫言异常;超时、关闭、被断开、已取消属**可恢复的业务情形**,用等待的返回状态表达。

## 先订阅、再回放、再合并实时(正确用法)

事件流服务要“连上后先补历史、再接实时”,且不能丢也不能重。正确的步骤是:

1. 发布者约定**先把事件持久化(得到单调递增的序号),再发布**,消息里带序号;
2. 订阅者**先订阅**频道,得到订阅柄——此后发布的消息一定不会丢;
3. **再读**持久存储:最大序号(称“头”)与 `after` 之后至头的历史,回放给客户端;
4. 之后循环等待订阅:消息序号 ≤ 头者已经在历史里回放过,**跳过**;序号 > 头者转发;
5. 结束前取消订阅。

订阅之前提交并发布的事件在历史里;订阅之后提交的事件从订阅收到;提交在读头之前、发布在订阅之后的事件两边都有,以序号去重。多个发布者并发时,实时消息的到达次序可能与序号略有出入,客户端应以序号为准。订阅积压超过 1 MiB 会被断开:回放很慢或事件很密时,应准备好在得到状态 2 后重新订阅并从最后转发的序号继续。

## 限制与生命周期

**订阅不随事件结束自动回收**(当前云工宿主):未取消的订阅继续占用名额并接收消息,直到积压被断开或实例被驱逐;事件因宿主时限或平台取消而提前终止时应用也来不及取消。这是宿主待改进项(建议宿主在事件结束时取消该事件遗留的订阅)。因此**频道名应取有限个固定名字,不要把请求号、用户号等无界量编进频道名**(消息里带号,订阅端自行过滤)。云工每个事件的豫言实例有墙钟时限(默认 30 秒,应用目录的 `执行配置.json` 可改到至多 15 分钟);阻塞等待可以超过时限,但醒来后下一次调用宿主即失败,所以等待与转发的循环应在时限之内结束。以上云端行为除注明本地 workerd 已验者,均“待云端验证”。

## 跨宿主一致性要求

各宿主须给出相同的返回值语义与异常类别:订阅后到来的消息必达、订阅内保序、送达数的含义、状态 0/1/2 的含义与优先次序、关闭时先交付后终止、积压断开丢弃排队消息、幂等的取消。宿主可以不同的只有上限数值的具体实现方式(须不低于上述数值或明确报告)、内部缓冲和调度。

## 一致性样例

1. 事件甲订阅 `c`;事件乙先后发布 `a`、`b`,各返回 1;甲两次等待依次得(0,`a`)、(0,`b`)。
2. 无订阅时发布返回 0;订阅之前发布的消息不补发。
3. 甲等待 200 毫秒而无消息,得(1,空),用时约 200 毫秒;期间读取订阅数不改变这一长短。
4. 乙以原因 `收摊` 关闭 `c`:甲先收到已排队的消息,再得(2,`收摊`);再等仍是(2,`收摊`);同名可重新订阅。
5. 甲订阅后不读取,乙连续发布超过 1 MiB:之后甲的等待得(2,含“积压”的原因),乙的发布不再送达甲。
6. 同一频道第 257 个订阅抛可捕获的豫言异常;第 65 个频道由宿主拒绝。
7. 事件乙用事件甲的订阅柄调用等待或取消,抛豫言异常。
8. 发布 0 与 65536 字节成功;65537 字节、字节 0xFF 起首的文字失败;频道名不合规失败。
9. 回放范例:事件丙先订阅,再读头与历史;事件丁在此窗口内追加并发布若干事件,丙的输出里每个序号恰好出现一次。

## 边界

不做持久存储、跨实例广播、消息确认或重传、序号分配、过期、订阅过滤与通配、二进制内容、限速;不解析消息;不保证事件结束时自动取消订阅(当前);不授予任何网络或存储能力。

规范.文言.md

# 持久频道公约 0.1.0

此包供**同一持久对象实例之内、跨事件**之具名文字频道:一事订而阻候,他事布而唤之,订者既醒,乃转发之。云工每 fetch、alarm 之事,皆新造豫言 Wasm 之实例,事与事之间无内存相通,故“甲事候、乙事唤甲”之需(事件流之即时推送、向长联下任务、以回传之片转与正读之调用方),惟借宿主实例内之共享状态而成。此包即于此共享状态之上,予保序、可限时而候、可闭、订数可查之至简接口。接口包惟依标准库。

**频道之状,属宿主实例,随持久对象被逐而失**:非持久之储,不跨实例,不跨重启。需持久之事列(以供回放)者,用持久事务或关系数据库存之;频道惟司“已发之事之即时告”。此包惟可用于持久对象之事,他事(如常 `fetch`)调之则抛豫言之异。

## 频道与订阅

频道以名识之:一至六十四字节,字限 `A-Z a-z 0-9 . _ : -`,分大小写,不合则抛豫言之异。频道始于首订之现,终于末订之失(取消、关闭、被断)。同一实例之内**至多六十四频道**,每频道**至多二百五十六订**:订数逾限,则 `订阅持久频道` 抛可捕之豫言异(诸事恰同时越限者,后至者或改由宿主拒之);频道数逾限,则宿主拒之,云工今使宿主报误而止本次运行,未必可以「尝试运行」捕之。订乃创之之事所有之资,订阅柄为不透明之串,**惟创之之事可用**:他事须自订,持他处之柄以调则抛豫言之异。**订之后**所发者,必达其订;**订之前**已发者,不补。每订之内,讯保其序:同一发者先发者先至,诸发者交错则视其发之先后。

## 术

`订阅持久频道` 订一频道,返订阅柄。订既返,其后所来之讯皆为之排队。

`发布持久频道文字` 以文发于频道当前诸订,依订之先后而达,返所达之订数;频道不存则返零。文零至六万五千五百三十六字节,空文亦合法;文不得以字节 0xFF 起首(合法 UTF-8 绝不以之起首),违则抛异;逾六万五千五百三十六字节则抛异。已因积压被断之订,不计入所达。

`等待持久频道消息` 受订阅柄与超时毫秒(零至三百六十万,越界则抛异),阻豫言直至有讯、逾时或订终,返(状态,文):`0` 得讯,文为讯;`1` 逾时,文空;`2` 频道已闭、订因积压逾一 MiB 而被断,或订已被取消,文为其因。超时为零者,立即轮询。状态二之后再候,仍返状态二与同一因。频道闭时,闭前已排队之讯先交,而后得状态二;订因积压被断时,已排队而未读之讯**尽弃**。阻候之时,不阻他事。

`取消订阅持久频道` 撤订而释其资,幂等;撤后再候,返(二,`订阅已取消`)。**客当每订配一撤,异常之路亦然。**

`关闭持久频道` 闭一频道:先向诸订交所排之讯,其后各订得状态二与因(至多一千零二十四字节,逾则抛异),返所闭之订数,频道不存则零。闭后同名之频道可复订(新代,与旧订无涉)。

`读取持久频道订阅数` 返频道今有之订数,不存则零;不改订者候之久暂,亦不生订者可见之讯。

误之类:频道名、大小、时限之域、订阅柄、事类、订数之限等**违规**者抛豫言之异;逾时、闭、被断、已撤乃**可复之业务情形**,以候之返状态表之。

## 先订、后放、再并即时(正用)

事件流之务,欲“联上则先补旧史,再接即时”,而不遗不复。其正法:

一、发者约:**先使事持久(得单调递增之序号),后乃发布**,讯中带序号;
二、订者**先订**频道,得订阅柄——自此之后所发者必不失;
三、**后读**持久之储:最大序号(曰“首”)与 `after` 之后至首之旧史,放回于客;
四、其后循环候订:讯之序号不逾首者,已在旧史中放过,**略之**;逾首者转发;
五、将终,撤订。

订前已提而发者,在旧史;订后所提者,自订而收;提于读首之前、发于订之后者,两处皆有,以序号去其重。诸发者并发,则即时之讯所至之次或与序号微异,客当以序号为准。订积压逾一 MiB 则被断:放旧史甚缓或事甚密者,当备得状态二后复订,而自末所转发之序号续之。

## 限与生命周期

**订不随事毕自收**(今云工宿主):未撤之订续占其额而受讯,直至积压被断或实例被逐;事因宿主之限或平台之取消而早终者,客亦不及撤。此宿主待改之项(宜于事毕时撤该事所遗之订)。故**频道名当取有限之定名,勿以请求号、用户号等无界之量编入名**(讯中带号,订者自滤之)。云工每事之豫言实例有墙钟之限(默认三十秒,应用目录之 `执行配置.json` 可改至多十五分钟);阻候可逾其限,然醒后下一调宿主即败,故候与转之循环当于限内毕。此诸云端之行,除注明本地 workerd 已验者,皆“待云端验证”。

## 诸宿主一致之要

诸宿主须予同一返值之义与异之类:订后所来者必达、订内保序、所达数之义、状态零、一、二之义与先后、闭时先交而后终、积压断而弃排队之讯、撤之幂等。宿主可异者,惟上限数值之实现之法(不得低于上列之数,或明报之)、内部缓冲与调度。

## 一致性样例

一、甲事订 `c`;乙事先后发 `a`、`b`,各返一;甲二次候,依次得(零,`a`)、(零,`b`)。
二、无订而发,返零;订前所发者不补。
三、甲候二百毫秒而无讯,得(一,空),用时约二百毫秒;其间读订数,不改其久暂。
四、乙以因 `收摊` 闭 `c`:甲先收所排之讯,而后得(二,`收摊`);再候仍(二,`收摊`);同名可复订。
五、甲订而不读,乙连发逾一 MiB:其后甲之候得(二,含“积压”之因),乙之发不复达甲。
六、同一频道之第二百五十七订,抛可捕之豫言异;第六十五频道,宿主拒之。
七、乙事以甲事之订阅柄调候或撤,抛豫言之异。
八、发零与六万五千五百三十六字节者成;六万五千五百三十七字节者、字节 0xFF 起首者败;频道名不合者败。
九、回放之范:丙事先订,后读首与旧史;丁事于此窗内追加而发若干事;丙之所出,每序号恰现一次。

## 界

不作持久之储、跨实例之广播、讯之确认或重传、序号之分配、过期、订之过滤与通配、二进制之容、限速;不析其讯;不保事毕自撤订(今);不授网络或存储之能。