@kanonak-protocol/codec

中文文档
3.0.1发布于 2026/11/01Apache-2.0TypeScriptES Module构建来源已验证

The generic Kanonak codec runtime: builds the canonical input model from typed nodes + an embedded schema, content-addresses it via @kanonak-protocol/canonical, and serializes/deserializes the normalized-JSON wire form. Referenced by generated typed SDKs;

原始使用文档
文档目录5 节
  1. What it does
  2. Self-contained by design
  3. Shape
  4. Embedded values (0.2.0)
  5. Reading earlier compatible versions (0.6.1)

@kanonak-protocol/codec

The generic, ontology-independent codec runtime referenced by Kanonak's generated typed SDKs. It turns typed objects into Kanonak content addresses and the normalized-JSON wire form, given the per-package CodecSchema that a generated SDK embeds.

It is the TypeScript port of the codec runtime. The C#/Rust/Go/Java/Python ports reference their kanonak-canonical library for the same canonical-hash entry; TypeScript references @kanonak-protocol/canonical — its only dependency.

What it does

  • packageContentHash(nodes, schema, pkg) → sha256:… — the permanent content address of the package those nodes form. Builds the language-neutral canonical input model (kanonak.org/canonical-form) and hashes it via @kanonak-protocol/canonical. Byte-identical to kanonak hash of the equivalent authored .kan.yml, including the synthesized rdf:type triples and the package-wrapper subject.
  • packageCanonicalForm(nodes, schema, pkg) — the canonical form itself (the {subjects:[…]} JSON), for inspection/debugging.
  • serialize(node, schema) / deserialize(json, schema) — the normalized-JSON wire form. Open-world assertions outside the type-model round-trip losslessly through $extra (top-level wire fields, collected under $extra on the typed node — [JsonExtensionData] semantics). From 3.0.0, each integer and decimal value travels in its JSON form (kanonak-protocol 4.11.0's numbers-in-json, runtime#54), through lists and embedded values. deserialize gives a wide integer (integer, its four sign-restricted forms, long, unsignedLong) as a bigint, a decimal as a string of its canonical digits, and int and narrower as a number; serialize writes a wide integer and a decimal as a string of their canonical digits, and a narrow integer as a number. An embedded value whose class the schema doesn't hold is carried as given. deserialize judges the values it's given: JSON.parse has already read a number such as 1.0000000000000000001 as 1, so a caller that must refuse it judges the number's source, as a reviver's context.source gives it (Node 22). A JSON number past ±(2^53 − 1) for a wide integer, a number for a decimal, a string for a narrow integer, or a value outside its datatype's range is refused, ending [json-number-form], as codec-vectors-json-numbers.json pins in every port.

Hashing also refuses a statement the schema doesn't declare, an $extra entry or a key the class doesn't model, with [hash-undeclared-statement]: a codec holds one package's schema and can't type another package's predicate, so such a node is hashed with the SDK, over the graph. Serializing and deserializing still carry it (codec 2.0.0, runtime#37).

Self-contained by design

Carriers come from the schema's datatype URIs, and the resolved foundation URIs (type/label predicates, the Package class) are embedded by the generator — so hashing needs no runtime ontology resolution and no registry access. A generated SDK plus this library (and its one @kanonak-protocol/canonical dependency) is enough to content-address typed objects in a serverless/edge runtime.

Shape

import { packageContentHash, serialize, deserialize } from '@kanonak-protocol/codec';
import type { CodecSchema, CodecNode, PackageContext } from '@kanonak-protocol/codec';

// `Schema` is the constant a generated SDK embeds for its own classes.
const hash = packageContentHash(nodes, Schema, {
  publisher: 'example.com', packageName: 'my-data', version: '1.0.0',
});

canonicalFormVersion: "1" — the content-address rules are frozen and evolve only by minting a new version (via the underlying canonical-form spec).

Embedded values (0.2.0)

An object property's value is a reference ({ $ref: uri }) or an embedded node: a map with no $id, an optional $name (the authored dict-key — hash-relevant), an optional $type, and schema-mapped fields. An explicit $type emits a type statement inside the embedded (hash-relevant even when it equals the range-derived type); without one, fields are mapped via the containing property's range and no type statement is emitted. Embedded collections are ordered lists — list order is semantic and hashed. An empty list contributes no statement (absent and empty are identical at the canonical layer). Gated by vectors/codec-vectors-embedded.json.

A reference value addresses a named resource: publisher/package[@version]/name. A $ref containing a URI fragment (#…) is rejected by canonicalization, serialize and deserialize alike, at any depth, with an error ending [fragment-reference]. Fragments address embedded resources for navigation; they are not part of the reference graph. Gated by vectors/codec-vectors-references.json.

Reading earlier compatible versions (0.6.1)

A node is typed with the version of the class its producer's import resolved to. A codec generated from a later compatible version of the same package still reads it: deserialize takes the exact versioned class first, then the same class at the highest schema version that isReadableBy (from @kanonak-protocol/canonical) says can read the written one. The node keeps the $type it was written with.

  • typeMatches(node, classUri, schema) — what a generated type guard asks. Each of the node's types ($types, else $type) goes through the same compatible lookup and matches when the class is classUri or has it among its ancestors, the optional transitive superclass list a CodecClass may carry. Classes compare by versionless identity.
  • enumMember(schema, ref) — the enumeration member a {"$ref"} names, exact first, then the same member at a compatible later version.
  • Hashing stays exact-version. A content hash covers the producer's predicate and type URIs, versions included, so a schema at a later version cannot reproduce it. Hashing a node whose exact class is missing fails with [hash-needs-exact-version] when only a compatible class exists.

Errors end in a bracketed kind: [unknown-type], [newer-version] (the data may use terms this schema lacks), [other-major], [other-minor-line] (below 1.0.0 the minor is the incompatible line) and [hash-needs-exact-version]. Gated by vectors/codec-vectors-compat.json.

Source & issues: https://github.com/kanonak-protocol/runtime

版本与发布记录

@kanonak-protocol/codec 共发布 15 个版本,首次发布于 2026/06/08;过去 12 个月发布了 15 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
3.0.1latest2026/11/012 天前
3.0.02026/10/295 天前
2.1.02026/10/277 天前
2.0.02026/10/259 天前
1.0.12026/10/2410 天前
1.0.02026/10/0529 天前

关于 @kanonak-protocol/codec 的常见问题

@kanonak-protocol/codec 是什么?

通用Kanonak编解码器运行时:从类型化节点+嵌入式模式构建规范化输入模型,通过@kanonak-protocol/canonical对其进行内容寻址,并序列化/反序列化规范化json连接表单。由生成的类型化sdk引用;

如何安装 @kanonak-protocol/codec?

在项目目录运行 npm install @kanonak-protocol/codec,或使用 pnpm add / yarn add / bun add。

@kanonak-protocol/codec 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@kanonak-protocol/codec 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@kanonak-protocol/codec 可以商用吗?

@kanonak-protocol/codec 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@kanonak-protocol/codec 还在维护吗?

最新版本 3.0.1 发布于 2026/11/01(2 天前);至今共发布 15 个版本,其中过去 12 个月发布了 15 个;首次发布于 2026/06/08。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。