@labacacia/nps-sdk 1.0.0-alpha.1 → 1.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.npmrc.publish +1 -0
- package/CHANGELOG.cn.md +39 -0
- package/CHANGELOG.md +39 -0
- package/CONTRIBUTING.cn.md +35 -0
- package/CONTRIBUTING.md +2 -0
- package/README.cn.md +155 -0
- package/README.md +5 -3
- package/dist/core/frames.d.ts +1 -0
- package/dist/core/frames.d.ts.map +1 -1
- package/dist/core/frames.js +1 -0
- package/dist/core/frames.js.map +1 -1
- package/dist/core/index.d.ts +6 -4
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +17 -5
- package/dist/core/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/ncp/frames.d.ts +18 -0
- package/dist/ncp/frames.d.ts.map +1 -1
- package/dist/ncp/frames.js +45 -0
- package/dist/ncp/frames.js.map +1 -1
- package/dist/ncp/registry.d.ts.map +1 -1
- package/dist/ncp/registry.js +2 -1
- package/dist/ncp/registry.js.map +1 -1
- package/doc/nps-sdk.core.cn.md +321 -0
- package/doc/nps-sdk.core.md +326 -0
- package/doc/nps-sdk.ncp.cn.md +270 -0
- package/doc/nps-sdk.ncp.md +276 -0
- package/doc/nps-sdk.ndp.cn.md +267 -0
- package/doc/nps-sdk.ndp.md +273 -0
- package/doc/nps-sdk.nip.cn.md +235 -0
- package/doc/nps-sdk.nip.md +242 -0
- package/doc/nps-sdk.nop.cn.md +329 -0
- package/doc/nps-sdk.nop.md +332 -0
- package/doc/nps-sdk.nwp.cn.md +217 -0
- package/doc/nps-sdk.nwp.md +224 -0
- package/doc/overview.cn.md +149 -0
- package/doc/overview.md +153 -0
- package/package.json +21 -4
- package/src/core/frames.ts +1 -0
- package/src/core/index.ts +37 -5
- package/src/index.ts +1 -1
- package/src/ncp/frames.ts +52 -0
- package/src/ncp/registry.ts +2 -1
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
[English Version](./nps-sdk.core.md) | 中文版
|
|
2
|
+
|
|
3
|
+
# `@labacacia/nps-sdk/core` — 类与方法参考
|
|
4
|
+
|
|
5
|
+
> 规范:[NPS-1 NCP v0.4](https://github.com/labacacia/NPS-Release/blob/main/spec/NPS-1-NCP.md)
|
|
6
|
+
|
|
7
|
+
线路层原语:帧头解析、编解码器对(Tier-1 JSON / Tier-2 MsgPack)、
|
|
8
|
+
锚点缓存、错误类型,以及 NIP 签名使用的规范化 JSON 辅助函数。
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 目录
|
|
13
|
+
|
|
14
|
+
- [帧类型与常量](#帧类型与常量)
|
|
15
|
+
- [`FrameHeader`](#frameheader)
|
|
16
|
+
- [`NpsFrameCodec`](#npsframecodec)
|
|
17
|
+
- [函数式编解码 API](#函数式编解码-api)
|
|
18
|
+
- [`FrameRegistry`](#frameregistry)
|
|
19
|
+
- [`AnchorCache`](#anchorcache)
|
|
20
|
+
- [规范化 JSON](#规范化-json)
|
|
21
|
+
- [异常](#异常)
|
|
22
|
+
- [状态码](#状态码)
|
|
23
|
+
- [`CryptoProvider`](#cryptoprovider)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 帧类型与常量
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
export enum FrameType {
|
|
31
|
+
// NCP
|
|
32
|
+
Anchor = 0x01, Diff = 0x02, Stream = 0x03,
|
|
33
|
+
Caps = 0x04, Align = 0x05, Hello = 0x06,
|
|
34
|
+
// NWP
|
|
35
|
+
Query = 0x10, Action = 0x11, Subscribe = 0x12,
|
|
36
|
+
// NIP
|
|
37
|
+
Ident = 0x20, Trust = 0x21, Revoke = 0x22,
|
|
38
|
+
// NDP
|
|
39
|
+
Announce = 0x30, Resolve = 0x31, Graph = 0x32,
|
|
40
|
+
// NOP
|
|
41
|
+
Task = 0x40, Delegate = 0x41, Sync = 0x42,
|
|
42
|
+
AlignStream = 0x43,
|
|
43
|
+
// System
|
|
44
|
+
Error = 0xFE,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export enum EncodingTier {
|
|
48
|
+
JSON = 0x00,
|
|
49
|
+
MSGPACK = 0x01,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const FrameFlags = {
|
|
53
|
+
NONE: 0x00,
|
|
54
|
+
TIER1_JSON: 0x00,
|
|
55
|
+
TIER2_MSGPACK: 0x01,
|
|
56
|
+
FINAL: 0x04,
|
|
57
|
+
ENCRYPTED: 0x08,
|
|
58
|
+
EXT: 0x80,
|
|
59
|
+
} as const;
|
|
60
|
+
|
|
61
|
+
export const DEFAULT_HEADER_SIZE = 4; // 字节
|
|
62
|
+
export const EXTENDED_HEADER_SIZE = 8;
|
|
63
|
+
export const DEFAULT_MAX_PAYLOAD = 0xFFFF; // 64 KiB − 1
|
|
64
|
+
export const EXTENDED_MAX_PAYLOAD = 0xFFFF_FFFF; // 4 GiB − 1
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`Align (0x05)` 已弃用 —— 请改用 NOP 的 `AlignStream (0x43)`。
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## `FrameHeader`
|
|
72
|
+
|
|
73
|
+
可解析 + 可序列化的线路帧头(NPS-1 §3.1)。
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
class FrameHeader {
|
|
77
|
+
constructor(
|
|
78
|
+
public readonly frameType: FrameType,
|
|
79
|
+
public readonly flags: number,
|
|
80
|
+
public readonly payloadLength: number,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
readonly isExtended: boolean; // EXT 位
|
|
84
|
+
readonly headerSize: number; // 4 或 8
|
|
85
|
+
readonly encodingTier: EncodingTier; // 低 2 位
|
|
86
|
+
readonly isFinal: boolean; // 第 2 位
|
|
87
|
+
readonly isEncrypted: boolean; // 第 3 位
|
|
88
|
+
|
|
89
|
+
static parse(buf: Uint8Array): FrameHeader;
|
|
90
|
+
toBytes(): Uint8Array;
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
默认帧头:`[type][flags][len_be_u16]`(4 字节)。
|
|
95
|
+
扩展帧头(`EXT=1`):`[type][flags][0 0][len_be_u32]`(8 字节)。
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## `NpsFrameCodec`
|
|
100
|
+
|
|
101
|
+
顶层编解码器,根据 flag 字节在 Tier-1 JSON 和 Tier-2 MsgPack 之间分派。
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
interface NpsFrame {
|
|
105
|
+
readonly frameType: FrameType;
|
|
106
|
+
readonly preferredTier: EncodingTier;
|
|
107
|
+
toDict(): Record<string, unknown>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
class NpsFrameCodec {
|
|
111
|
+
constructor(
|
|
112
|
+
registry: FrameRegistry,
|
|
113
|
+
options?: { maxPayload?: number },
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
encode(frame: NpsFrame, options?: { overrideTier?: EncodingTier }): Uint8Array;
|
|
117
|
+
decode(wire: Uint8Array): NpsFrame;
|
|
118
|
+
|
|
119
|
+
static peekHeader(wire: Uint8Array): FrameHeader;
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### `encode(frame, opts?)`
|
|
124
|
+
|
|
125
|
+
通过所选 tier 序列化帧的 `toDict()` 并前置帧头。当 payload
|
|
126
|
+
超过 `DEFAULT_MAX_PAYLOAD` 时自动设置 `EXT=1`。对 `StreamFrame`,
|
|
127
|
+
当 `isLast === true` 时设置 `FINAL` flag;其他每个帧都始终设置。
|
|
128
|
+
|
|
129
|
+
以下情况抛 `NpsCodecError`:
|
|
130
|
+
- 编码失败;
|
|
131
|
+
- 编码后 payload 超过 `maxPayload`(默认 65 535)。
|
|
132
|
+
|
|
133
|
+
### `decode(wire)`
|
|
134
|
+
|
|
135
|
+
解析帧头、切出 payload、从注册表解析帧类、调用 `fromDict(data)`。
|
|
136
|
+
|
|
137
|
+
### `peekHeader(wire)`(静态)
|
|
138
|
+
|
|
139
|
+
返回解析后的帧头而不解码 payload —— 对路由、计长或转储很有用。
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 函数式编解码 API
|
|
144
|
+
|
|
145
|
+
从 `@labacacia/nps-sdk/core` 重新导出。轻量、少分配的函数对,
|
|
146
|
+
被测试和不希望持有类实例的工具使用。
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
// Tier 级辅助
|
|
150
|
+
function encodeJson(payload: unknown): Uint8Array;
|
|
151
|
+
function decodeJson(bytes: Uint8Array): unknown;
|
|
152
|
+
|
|
153
|
+
function encodeMsgPack(payload: unknown): Uint8Array;
|
|
154
|
+
function decodeMsgPack(bytes: Uint8Array): unknown;
|
|
155
|
+
|
|
156
|
+
// 完整帧辅助
|
|
157
|
+
function encodeFrame(
|
|
158
|
+
payload: unknown,
|
|
159
|
+
options: {
|
|
160
|
+
frameType: number;
|
|
161
|
+
tier?: EncodingTier;
|
|
162
|
+
final?: boolean;
|
|
163
|
+
encrypted?: boolean;
|
|
164
|
+
extended?: boolean;
|
|
165
|
+
},
|
|
166
|
+
): Uint8Array;
|
|
167
|
+
|
|
168
|
+
function decodeFrame(
|
|
169
|
+
buffer: Uint8Array,
|
|
170
|
+
options?: { maxFramePayload?: number },
|
|
171
|
+
): {
|
|
172
|
+
header: FrameHeader; // 来自 frame-header.ts 的接口形态
|
|
173
|
+
payload: unknown;
|
|
174
|
+
bytesConsumed: number;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// 低层帧头 I/O
|
|
178
|
+
function parseFrameHeader(buffer: Uint8Array, opts?: { max_frame_payload?: number }): FrameHeaderInterface;
|
|
179
|
+
function writeFrameHeader(header: FrameHeaderInterface, buffer: Uint8Array): number;
|
|
180
|
+
function buildFlags(options: {
|
|
181
|
+
tier?: EncodingTier; final?: boolean; encrypted?: boolean; extended?: boolean;
|
|
182
|
+
}): number;
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
错误以 `NcpError` 抛出,并带协议错误码(如
|
|
186
|
+
`NCP-FRAME-FLAGS-INVALID`、`NCP-FRAME-PAYLOAD-TOO-LARGE`、
|
|
187
|
+
`NCP-FRAME-PARSE-ERROR`)。
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## `FrameRegistry`
|
|
192
|
+
|
|
193
|
+
将 `FrameType` 字节映射到实现 `FrameClass.fromDict` 的帧类。
|
|
194
|
+
`NpsFrameCodec.decode` 用它来实例化有类型的实例。
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
class FrameRegistry {
|
|
198
|
+
register(frameType: FrameType, cls: FrameClass): void;
|
|
199
|
+
resolve(frameType: FrameType): FrameClass; // 未知类型抛 NpsFrameError
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
interface FrameClass {
|
|
203
|
+
fromDict(data: Record<string, unknown>): NpsFrame;
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
根包导出两个工厂:
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
import { createDefaultRegistry, createFullRegistry } from "@labacacia/nps-sdk";
|
|
211
|
+
|
|
212
|
+
createDefaultRegistry(); // 仅 NCP —— ANCHOR + DIFF + STREAM + CAPS + ERROR
|
|
213
|
+
createFullRegistry(); // NCP + NWP + NIP + NDP + NOP
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
当需要编解码器解码任意帧时使用 `createFullRegistry()`;客户端
|
|
217
|
+
内部会构造合适的注册表。
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## `AnchorCache`
|
|
222
|
+
|
|
223
|
+
有界、TTL 感知的 schema 缓存(NPS-1 §5.3、§7.2、§9)。
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
class AnchorCache {
|
|
227
|
+
constructor(options?: { maxSize?: number; getNow?: () => number });
|
|
228
|
+
|
|
229
|
+
set(frame: AnchorFrame): void;
|
|
230
|
+
get(anchorId: string): AnchorFrame | null;
|
|
231
|
+
getRequired(anchorId: string): AnchorFrame; // 抛 NcpError NCP-ANCHOR-NOT-FOUND
|
|
232
|
+
readonly size: number;
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### 行为
|
|
237
|
+
|
|
238
|
+
- `ttl === 0` → 帧**不**被缓存(规范 §4.1,"仅本 session")。
|
|
239
|
+
- 用**不同** schema 重新 set 同一锚点会抛
|
|
240
|
+
`NcpError("NCP-ANCHOR-ID-MISMATCH")` —— 投毒检测(§7.2)。
|
|
241
|
+
- 缓存满时,最近访问时间最早的条目被清除。
|
|
242
|
+
- 过期在每次 `get()` 时评估;没有后台定时器。
|
|
243
|
+
- 为可重现测试覆写 `getNow`。
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## 规范化 JSON
|
|
248
|
+
|
|
249
|
+
SDK 提供两种不同的 JSON 规范化方案:
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
function jcsStringify(obj: unknown): string; // RFC 8785 (JCS)
|
|
253
|
+
function sortKeysStringify(obj: unknown): string; // 按键排序、紧凑分隔符
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
- `jcsStringify` 是 `AnchorFrame.anchor_id` 哈希所用的规范形式
|
|
257
|
+
(对 JCS 字节做 SHA-256)。
|
|
258
|
+
- `sortKeysStringify` 镜像 Python 的
|
|
259
|
+
`json.dumps(sort_keys=True, separators=(",", ":"))`,NIP 签名
|
|
260
|
+
使用它以实现跨语言一致。
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## 异常
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
class NpsError extends Error {}
|
|
268
|
+
class NpsFrameError extends NpsError {}
|
|
269
|
+
class NpsCodecError extends NpsError {}
|
|
270
|
+
class NpsAnchorNotFoundError extends NpsError { readonly anchorId: string; }
|
|
271
|
+
class NpsAnchorPoisonError extends NpsError { readonly anchorId: string; }
|
|
272
|
+
class NpsStreamError extends NpsError {}
|
|
273
|
+
|
|
274
|
+
class NcpError extends Error { readonly code: string; }
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
`NcpError` 携带规范错误码(如 `NCP-STREAM-SEQ-GAP`)。函数式
|
|
278
|
+
编解码器、stream manager 和校验器抛它;基于类的编解码器和
|
|
279
|
+
缓存抛 `NpsError` 子类。
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## 状态码
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
import { NpsStatusCodes } from "@labacacia/nps-sdk/core";
|
|
287
|
+
|
|
288
|
+
NpsStatusCodes.NPS_OK; // "NPS-OK"
|
|
289
|
+
NpsStatusCodes.NPS_CLIENT_NOT_FOUND; // "NPS-CLIENT-NOT-FOUND"
|
|
290
|
+
NpsStatusCodes.NPS_STREAM_SEQ_GAP; // "NPS-STREAM-SEQ-GAP"
|
|
291
|
+
// …
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
与 `spec/status-codes.md` 对应的常量包。发出 `ErrorFrame` 或
|
|
295
|
+
与 `status` 字段比较时使用。
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## `CryptoProvider`
|
|
300
|
+
|
|
301
|
+
可插拔异步加密的结构性脚手架(Node `node:crypto` 对比浏览器
|
|
302
|
+
`SubtleCrypto`)。今日公共 API 不会实例化它;NIP 目前直接使用
|
|
303
|
+
`@noble/ed25519`。导出供下游实现者使用。
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
interface CryptoProvider {
|
|
307
|
+
randomBytes(n: number): Uint8Array;
|
|
308
|
+
|
|
309
|
+
ed25519GenerateKeyPair(): Promise<{ publicKey: Uint8Array; privateKey: Uint8Array }>;
|
|
310
|
+
ed25519Sign(privateKey: Uint8Array, data: Uint8Array): Promise<Uint8Array>;
|
|
311
|
+
ed25519Verify(publicKey: Uint8Array, data: Uint8Array, sig: Uint8Array): Promise<boolean>;
|
|
312
|
+
|
|
313
|
+
aesGcmEncrypt(key: Uint8Array, iv: Uint8Array, plaintext: Uint8Array): Promise<Uint8Array>;
|
|
314
|
+
aesGcmDecrypt(key: Uint8Array, iv: Uint8Array, ciphertextAndTag: Uint8Array): Promise<Uint8Array>;
|
|
315
|
+
|
|
316
|
+
pbkdf2Sha256(
|
|
317
|
+
passphrase: Uint8Array, salt: Uint8Array,
|
|
318
|
+
iterations: number, keyLen: number,
|
|
319
|
+
): Promise<Uint8Array>;
|
|
320
|
+
}
|
|
321
|
+
```
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
English | [中文版](./nps-sdk.core.cn.md)
|
|
2
|
+
|
|
3
|
+
# `@labacacia/nps-sdk/core` — Class and Method Reference
|
|
4
|
+
|
|
5
|
+
> Spec: [NPS-1 NCP v0.4](https://github.com/labacacia/NPS-Release/blob/main/spec/NPS-1-NCP.md)
|
|
6
|
+
|
|
7
|
+
Wire-level primitives: frame header parsing, the codec pair
|
|
8
|
+
(Tier-1 JSON / Tier-2 MsgPack), the anchor cache, error types, and
|
|
9
|
+
canonical-JSON helpers used by NIP signing.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Table of contents
|
|
14
|
+
|
|
15
|
+
- [Frame types & constants](#frame-types--constants)
|
|
16
|
+
- [`FrameHeader`](#frameheader)
|
|
17
|
+
- [`NpsFrameCodec`](#npsframecodec)
|
|
18
|
+
- [Functional codec API](#functional-codec-api)
|
|
19
|
+
- [`FrameRegistry`](#frameregistry)
|
|
20
|
+
- [`AnchorCache`](#anchorcache)
|
|
21
|
+
- [Canonical JSON](#canonical-json)
|
|
22
|
+
- [Exceptions](#exceptions)
|
|
23
|
+
- [Status codes](#status-codes)
|
|
24
|
+
- [`CryptoProvider`](#cryptoprovider)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Frame types & constants
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
export enum FrameType {
|
|
32
|
+
// NCP
|
|
33
|
+
Anchor = 0x01, Diff = 0x02, Stream = 0x03,
|
|
34
|
+
Caps = 0x04, Align = 0x05, Hello = 0x06,
|
|
35
|
+
// NWP
|
|
36
|
+
Query = 0x10, Action = 0x11, Subscribe = 0x12,
|
|
37
|
+
// NIP
|
|
38
|
+
Ident = 0x20, Trust = 0x21, Revoke = 0x22,
|
|
39
|
+
// NDP
|
|
40
|
+
Announce = 0x30, Resolve = 0x31, Graph = 0x32,
|
|
41
|
+
// NOP
|
|
42
|
+
Task = 0x40, Delegate = 0x41, Sync = 0x42,
|
|
43
|
+
AlignStream = 0x43,
|
|
44
|
+
// System
|
|
45
|
+
Error = 0xFE,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export enum EncodingTier {
|
|
49
|
+
JSON = 0x00,
|
|
50
|
+
MSGPACK = 0x01,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const FrameFlags = {
|
|
54
|
+
NONE: 0x00,
|
|
55
|
+
TIER1_JSON: 0x00,
|
|
56
|
+
TIER2_MSGPACK: 0x01,
|
|
57
|
+
FINAL: 0x04,
|
|
58
|
+
ENCRYPTED: 0x08,
|
|
59
|
+
EXT: 0x80,
|
|
60
|
+
} as const;
|
|
61
|
+
|
|
62
|
+
export const DEFAULT_HEADER_SIZE = 4; // bytes
|
|
63
|
+
export const EXTENDED_HEADER_SIZE = 8;
|
|
64
|
+
export const DEFAULT_MAX_PAYLOAD = 0xFFFF; // 64 KiB − 1
|
|
65
|
+
export const EXTENDED_MAX_PAYLOAD = 0xFFFF_FFFF; // 4 GiB − 1
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`Align (0x05)` is deprecated — use `AlignStream (0x43)` from NOP instead.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## `FrameHeader`
|
|
73
|
+
|
|
74
|
+
Parsed + serialisable wire header (NPS-1 §3.1).
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
class FrameHeader {
|
|
78
|
+
constructor(
|
|
79
|
+
public readonly frameType: FrameType,
|
|
80
|
+
public readonly flags: number,
|
|
81
|
+
public readonly payloadLength: number,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
readonly isExtended: boolean; // EXT bit
|
|
85
|
+
readonly headerSize: number; // 4 or 8
|
|
86
|
+
readonly encodingTier: EncodingTier; // lower 2 bits
|
|
87
|
+
readonly isFinal: boolean; // bit 2
|
|
88
|
+
readonly isEncrypted: boolean; // bit 3
|
|
89
|
+
|
|
90
|
+
static parse(buf: Uint8Array): FrameHeader;
|
|
91
|
+
toBytes(): Uint8Array;
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Default header: `[type][flags][len_be_u16]` (4 bytes).
|
|
96
|
+
Extended header (`EXT=1`): `[type][flags][0 0][len_be_u32]` (8 bytes).
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## `NpsFrameCodec`
|
|
101
|
+
|
|
102
|
+
Top-level codec dispatching between Tier-1 JSON and Tier-2 MsgPack based on
|
|
103
|
+
the flags byte.
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
interface NpsFrame {
|
|
107
|
+
readonly frameType: FrameType;
|
|
108
|
+
readonly preferredTier: EncodingTier;
|
|
109
|
+
toDict(): Record<string, unknown>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
class NpsFrameCodec {
|
|
113
|
+
constructor(
|
|
114
|
+
registry: FrameRegistry,
|
|
115
|
+
options?: { maxPayload?: number },
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
encode(frame: NpsFrame, options?: { overrideTier?: EncodingTier }): Uint8Array;
|
|
119
|
+
decode(wire: Uint8Array): NpsFrame;
|
|
120
|
+
|
|
121
|
+
static peekHeader(wire: Uint8Array): FrameHeader;
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `encode(frame, opts?)`
|
|
126
|
+
|
|
127
|
+
Serialises the frame's `toDict()` via the chosen tier and prepends the
|
|
128
|
+
header. Automatically sets `EXT=1` if the payload exceeds
|
|
129
|
+
`DEFAULT_MAX_PAYLOAD`. For `StreamFrame`, `FINAL` flag is set when
|
|
130
|
+
`isLast === true`; for every other frame it is always set.
|
|
131
|
+
|
|
132
|
+
Raises `NpsCodecError` when:
|
|
133
|
+
- encoding fails,
|
|
134
|
+
- the encoded payload exceeds `maxPayload` (default 65 535).
|
|
135
|
+
|
|
136
|
+
### `decode(wire)`
|
|
137
|
+
|
|
138
|
+
Parses the header, slices the payload, resolves the frame class from the
|
|
139
|
+
registry, and calls `fromDict(data)`.
|
|
140
|
+
|
|
141
|
+
### `peekHeader(wire)` *(static)*
|
|
142
|
+
|
|
143
|
+
Returns the parsed header without decoding the payload — useful for
|
|
144
|
+
routing, sizing, or dumping.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Functional codec API
|
|
149
|
+
|
|
150
|
+
Re-exported from `@labacacia/nps-sdk/core`. Thin, allocation-light pair
|
|
151
|
+
used by tests and tools that don't want a class instance.
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
// Tier-level helpers
|
|
155
|
+
function encodeJson(payload: unknown): Uint8Array;
|
|
156
|
+
function decodeJson(bytes: Uint8Array): unknown;
|
|
157
|
+
|
|
158
|
+
function encodeMsgPack(payload: unknown): Uint8Array;
|
|
159
|
+
function decodeMsgPack(bytes: Uint8Array): unknown;
|
|
160
|
+
|
|
161
|
+
// Full frame helpers
|
|
162
|
+
function encodeFrame(
|
|
163
|
+
payload: unknown,
|
|
164
|
+
options: {
|
|
165
|
+
frameType: number;
|
|
166
|
+
tier?: EncodingTier;
|
|
167
|
+
final?: boolean;
|
|
168
|
+
encrypted?: boolean;
|
|
169
|
+
extended?: boolean;
|
|
170
|
+
},
|
|
171
|
+
): Uint8Array;
|
|
172
|
+
|
|
173
|
+
function decodeFrame(
|
|
174
|
+
buffer: Uint8Array,
|
|
175
|
+
options?: { maxFramePayload?: number },
|
|
176
|
+
): {
|
|
177
|
+
header: FrameHeader; // the interface shape from frame-header.ts
|
|
178
|
+
payload: unknown;
|
|
179
|
+
bytesConsumed: number;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// Low-level header I/O
|
|
183
|
+
function parseFrameHeader(buffer: Uint8Array, opts?: { max_frame_payload?: number }): FrameHeaderInterface;
|
|
184
|
+
function writeFrameHeader(header: FrameHeaderInterface, buffer: Uint8Array): number;
|
|
185
|
+
function buildFlags(options: {
|
|
186
|
+
tier?: EncodingTier; final?: boolean; encrypted?: boolean; extended?: boolean;
|
|
187
|
+
}): number;
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Errors are raised as `NcpError` with a protocol code (e.g.
|
|
191
|
+
`NCP-FRAME-FLAGS-INVALID`, `NCP-FRAME-PAYLOAD-TOO-LARGE`,
|
|
192
|
+
`NCP-FRAME-PARSE-ERROR`).
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## `FrameRegistry`
|
|
197
|
+
|
|
198
|
+
Maps `FrameType` bytes to frame classes implementing `FrameClass.fromDict`.
|
|
199
|
+
Used by `NpsFrameCodec.decode` to materialise typed instances.
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
class FrameRegistry {
|
|
203
|
+
register(frameType: FrameType, cls: FrameClass): void;
|
|
204
|
+
resolve(frameType: FrameType): FrameClass; // throws NpsFrameError if unknown
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
interface FrameClass {
|
|
208
|
+
fromDict(data: Record<string, unknown>): NpsFrame;
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The root package exports two factories:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
import { createDefaultRegistry, createFullRegistry } from "@labacacia/nps-sdk";
|
|
216
|
+
|
|
217
|
+
createDefaultRegistry(); // NCP only — ANCHOR + DIFF + STREAM + CAPS + ERROR
|
|
218
|
+
createFullRegistry(); // NCP + NWP + NIP + NDP + NOP
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Use `createFullRegistry()` when you need the codec to decode arbitrary
|
|
222
|
+
frames; the clients construct a suitable registry internally.
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## `AnchorCache`
|
|
227
|
+
|
|
228
|
+
Bounded, TTL-aware schema cache (NPS-1 §5.3, §7.2, §9).
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
class AnchorCache {
|
|
232
|
+
constructor(options?: { maxSize?: number; getNow?: () => number });
|
|
233
|
+
|
|
234
|
+
set(frame: AnchorFrame): void;
|
|
235
|
+
get(anchorId: string): AnchorFrame | null;
|
|
236
|
+
getRequired(anchorId: string): AnchorFrame; // throws NcpError NCP-ANCHOR-NOT-FOUND
|
|
237
|
+
readonly size: number;
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Behaviour
|
|
242
|
+
|
|
243
|
+
- `ttl === 0` → frame is NOT cached (spec §4.1, "session-only").
|
|
244
|
+
- Re-setting an anchor with a **different** schema throws
|
|
245
|
+
`NcpError("NCP-ANCHOR-ID-MISMATCH")` — poisoning detection (§7.2).
|
|
246
|
+
- When the cache is full, the least-recently-accessed entry is evicted.
|
|
247
|
+
- Expiry is evaluated on every `get()`; no background timer.
|
|
248
|
+
- Override `getNow` for deterministic tests.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Canonical JSON
|
|
253
|
+
|
|
254
|
+
Two distinct JSON normalisations ship with the SDK:
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
function jcsStringify(obj: unknown): string; // RFC 8785 (JCS)
|
|
258
|
+
function sortKeysStringify(obj: unknown): string; // sort keys, compact separators
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
- `jcsStringify` is the canonical form used for `AnchorFrame.anchor_id`
|
|
262
|
+
hashing (SHA-256 over the JCS bytes).
|
|
263
|
+
- `sortKeysStringify` mirrors Python's `json.dumps(sort_keys=True, separators=(",", ":"))`
|
|
264
|
+
and is used by NIP signing for cross-language parity.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Exceptions
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
class NpsError extends Error {}
|
|
272
|
+
class NpsFrameError extends NpsError {}
|
|
273
|
+
class NpsCodecError extends NpsError {}
|
|
274
|
+
class NpsAnchorNotFoundError extends NpsError { readonly anchorId: string; }
|
|
275
|
+
class NpsAnchorPoisonError extends NpsError { readonly anchorId: string; }
|
|
276
|
+
class NpsStreamError extends NpsError {}
|
|
277
|
+
|
|
278
|
+
class NcpError extends Error { readonly code: string; }
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
`NcpError` carries a spec error code (e.g. `NCP-STREAM-SEQ-GAP`). It is
|
|
282
|
+
thrown by the functional codec, the stream manager, and the validators;
|
|
283
|
+
`NpsError` subclasses are thrown by the class-based codec and cache.
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Status codes
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
import { NpsStatusCodes } from "@labacacia/nps-sdk/core";
|
|
291
|
+
|
|
292
|
+
NpsStatusCodes.NPS_OK; // "NPS-OK"
|
|
293
|
+
NpsStatusCodes.NPS_CLIENT_NOT_FOUND; // "NPS-CLIENT-NOT-FOUND"
|
|
294
|
+
NpsStatusCodes.NPS_STREAM_SEQ_GAP; // "NPS-STREAM-SEQ-GAP"
|
|
295
|
+
// …
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Constant bundle matching `spec/status-codes.md`. Use these whenever you
|
|
299
|
+
need to emit an `ErrorFrame` or compare against the `status` field.
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## `CryptoProvider`
|
|
304
|
+
|
|
305
|
+
Structural scaffold for pluggable async crypto (Node `node:crypto` vs
|
|
306
|
+
browser `SubtleCrypto`). Not instantiated by the public API today; NIP
|
|
307
|
+
currently uses `@noble/ed25519` directly. Exported for downstream
|
|
308
|
+
implementers.
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
interface CryptoProvider {
|
|
312
|
+
randomBytes(n: number): Uint8Array;
|
|
313
|
+
|
|
314
|
+
ed25519GenerateKeyPair(): Promise<{ publicKey: Uint8Array; privateKey: Uint8Array }>;
|
|
315
|
+
ed25519Sign(privateKey: Uint8Array, data: Uint8Array): Promise<Uint8Array>;
|
|
316
|
+
ed25519Verify(publicKey: Uint8Array, data: Uint8Array, sig: Uint8Array): Promise<boolean>;
|
|
317
|
+
|
|
318
|
+
aesGcmEncrypt(key: Uint8Array, iv: Uint8Array, plaintext: Uint8Array): Promise<Uint8Array>;
|
|
319
|
+
aesGcmDecrypt(key: Uint8Array, iv: Uint8Array, ciphertextAndTag: Uint8Array): Promise<Uint8Array>;
|
|
320
|
+
|
|
321
|
+
pbkdf2Sha256(
|
|
322
|
+
passphrase: Uint8Array, salt: Uint8Array,
|
|
323
|
+
iterations: number, keyLen: number,
|
|
324
|
+
): Promise<Uint8Array>;
|
|
325
|
+
}
|
|
326
|
+
```
|