方块
一个用于 Arkiv 块链交互的 TypeScript 客户端库. Arkiv SDK大力发展 维埃姆 library - 它扩展了Viem的客户端,带有Arkiv的链式特有功能.
viem 是一个 同伴依赖安装在SDK旁边.
安装
npm install @arkiv-network/sdk viem
# or
pnpm install @arkiv-network/sdk viem
# or
bun add @arkiv-network/sdk viem
# or
yarn add @arkiv-network/sdk viem
使用量
下面是帮助您创建使用 Arkiv 查询和写入数据的简单脚本的教程.
先决条件
- 节点js 22.1.0或更新 (见 节点js.org),或
bun,一个JavaScript/TypeScript运行时间和包管理器,在本地支持TypeScript而无需转接. - Arkiv 访问密钥 . Arkiv的公开RPC在防止虐待方面受到很大限制,因此我们建议在 网络/接入密钥。 。 。 .
- 一些测试网资金. 写一个实体是一种交易,其气体以 tGLM 测试网支付. 从水龙头取来一些你的账户地址 网络/浮标。 。 。 . 项目设置 显示在没有账户的情况下如何创建账户.
项目设置
创建新目录并浏览其中 :
mkdir arkiv-sample
cd arkiv-sample
创建空 example.ts 文件 :
touch example.ts
初始化一个新的 JavaScript/ TypeScript 项目 :
npm init
您可以按下所有默认选项 Enter 在每一个提示.
这一步之后 package.json 将创建类似内容的文件 :
{
"name": "arkiv-sample",
"version": "1.0.0",
"description": "",
"license": "ISC",
"author": "",
"type": "commonjs",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
}
}
修改 "main" 条目为 "example.ts" 设置 "type" 改为 "module" 这么说你 package.json 看起来像这样:
{
"name": "arkiv-sample",
"version": "1.0.0",
"description": "",
"license": "ISC",
"author": "",
"type": "module",
"main": "example.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
}
}
安装 @arkiv-network/sdk 联合国 viem 使用您首选的软件包管理器对等依赖 :
npm install @arkiv-network/sdk viem
此命令将更新您的 package.json 其中有一节如下:
"dependencies": {
"@arkiv-network/sdk": "^0.9.0",
"viem": "^2.38.2"
}
它还将创建一个 node_modules 安装了所有依赖关系的目录.
如果你还没有测试网的账户, 创建一个Viem。 此打印出一个新的私人密钥及其地址 :
node --input-type=module -e "import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; const key = generatePrivateKey(); console.log('Private key:', key); console.log('Address:', privateKeyToAccount(key).address)"
资助地址 水龙头。 。 。 .
最后,创建一个 .env 带有访问密钥和私人密钥的文件。 例子从那里读出来,所以你代码里都没有:
ARKIV_ACCESS_KEY=your-access-key
PRIVATE_KEY=0x-your-private-key
定义外观
Arkiv存储实体,而一个实体是一组被命名,被打入的属性. 你曾经描述过他们 schema,多用 SQL 来描述表格的列,并按列选择节点 索引它。 只有索引列才能被过滤;其余的可以存储和选择,不能 支付指数.
以下是博客文章使用这一计划的例子。 每个例子都表扬在自己的顶端
文件,以便它作为Node.js、Bun或 tsc 类似:
import { addr, bytes, dec, defineSchema, str } from "@arkiv-network/sdk/schema"
// Called with no arguments, a type constructor describes a column.
const Posts = defineSchema("posts", {
author: addr().indexed(),
category: str().indexed(),
score: dec().indexed(),
title: str(), // stored, not indexed
body: bytes(), // a blob; bytes can never be indexed
thumbnail: bytes().optional(), // any number of bytes columns; this one may be absent
mimeType: str().default("text/markdown"), // written on create when left out
})
类型构造器是 bool, (中文(简体) ), i32, (中文(简体) ), u64, (中文(简体) ), u256, (中文(简体) ), dec, (中文(简体) ), str, (中文(简体) ), addr, (中文(简体) ), key 财务报告和审定财务报表 bytes。 。 。 .
每栏可采用下列方法加以改进: .indexed() (每类但 bytes, (中文(简体) ), .optional() 财务报告和审定财务报表
.default(...)。 。 。 .
相同的构造器,使用一个值,生成一个打入的值: dec("4.1") 是一个小数, SQL 方式
已经 DECIMAL 作为类型和 DECIMAL '4.1' 当作一字.
Schema是 SDK 边会:节点只看到属性,每个属性都有类型和索引 旗帜。 计划是您写作和阅读的键盘, 并在运行时检查.
这个名字, "posts",是可选但建议。 一个命名的图案标记它创建的每个实体
已索引 arkiv.schema 属性持有其名称,通过它查询时只匹配实体
用那个标记——一种收藏将一个模型的文档与另一个模型的文档分开的方式. 为了这个
原因 arkiv.schema 无法用作列名.
创建实体
粘贴如下 example.ts。它设置了一个公共客户端,该客户端读取,以及一个钱包客户端,该客户端在交易中签名并发送交易,然后通过计划创建一个帖子:
import { createPublicClient, createWalletClient, ExpirationTime } from "@arkiv-network/sdk"
import { rabdi } from "@arkiv-network/sdk/chains"
import { and, eq, gt } from "@arkiv-network/sdk/query"
import { addr, bytes, dec, defineSchema, str } from "@arkiv-network/sdk/schema"
import { hexToString, http, isHex, stringToBytes } from "viem"
import { privateKeyToAccount } from "viem/accounts"
// The schema from "Define a Schema" above
const Posts = defineSchema("posts", {
author: addr().indexed(),
category: str().indexed(),
score: dec().indexed(),
title: str(),
body: bytes(),
thumbnail: bytes().optional(),
mimeType: str().default("text/markdown"),
})
// Rabdi is the Arkiv testnet
const chain = rabdi({ accessKey: process.env.ARKIV_ACCESS_KEY ?? "" })
const privateKey = process.env.PRIVATE_KEY
if (!privateKey || !isHex(privateKey)) {
throw new Error("Set PRIVATE_KEY in .env to the private key of a funded testnet account")
}
const account = privateKeyToAccount(privateKey)
const publicClient = createPublicClient({ chain, transport: http() })
const walletClient = createWalletClient({ chain, transport: http(), account })
// Create an entity. Every required column must be given; a defaulted one (mimeType) left out is
// written with its default. Values carry their type: use the constructors from
// "@arkiv-network/sdk/schema", or a bare string, boolean, number, bigint or Uint8Array where it is
// unambiguous (string -> str, Uint8Array -> bytes, ...). createEntity resolves once the
// transaction is in a block.
const { entityKey, txHash } = await walletClient.createEntity(
Posts,
{
author: addr(account.address),
category: "docs",
score: dec("4.5"),
title: "Hello from Arkiv",
body: stringToBytes("# Hello\n\nArkiv wishes you all the best!"),
},
{ expiration: ExpirationTime.fromDays(1) }, // the entity expires in a day
)
console.log("Created entity:", entityKey)
console.log("Transaction hash:", txHash)
// Change one column, leaving the rest alone
await walletClient.patchEntity(Posts, entityKey, { set: { score: dec("4.7") } })
查询它
现在读回帖子。 在结尾添加以下内容: example.ts数字 :
// Get the entity by key, read through the schema
const post = await publicClient.getEntity(entityKey, Posts)
console.log("Title:", post.title.value, "score:", post.score.value)
// Or find it with a query: select what to return, name the schema, then filter
const result = await publicClient
.select({ $key: true, title: true, score: true, body: true })
.from(Posts)
.where(and(eq("category", "docs"), gt("score", dec("4.0"))))
.ownedBy(account.address)
.limit(10)
.fetch()
for (const row of result.entities) {
console.log(row.$key, row.title.value, row.score.value, hexToString(row.body.value))
}
选择字段 select()
传递无( 或) "*"——通过计划、每个实体领域和每个
栏中——或通过一个对象只获取特定字段。 选择名称实体字段和
属性并列,每个结果都是平行。 实体字段从 $就像他们一样
where数字 : $key, (中文(简体) ), $owner, (中文(简体) ), $creator, (中文(简体) ), $createdAt, (中文(简体) ), $updatedAt, (中文(简体) ), $expiresAt 财务报告和审定财务报表
$creationFlags。任何其他名称都是属性。 属性名称不能从 $,所以请
二从不相撞——一个叫做 owner 刚刚 owner数字 :
// Every entity field and every column
await publicClient.select().from(Posts).where(eq("category", "docs")).fetch();
// Only the fields you need
await publicClient.select({ $key: true, title: true }).from(Posts).where(eq("category", "docs")).fetch();
仅选择所需. 每个选定的字段都会从网络中获取, 所以请求 您不会使用的数据会让查询更慢。 缩小选择范围使响应量小而快.
从您的选择和计划中推断出结果类型: 每个列作为计划键入 声明它,读取您没有选择的字段是一个编译错误 .
const row = (
await publicClient.select({ $owner: true, score: true }).from(Posts).where(eq("category", "docs")).fetch()
).entities[0]!;
row.$owner; // ✅ Hex
row.score; // ✅ DecValue — row.score.value is "4.1"
row.title; // ❌ compile error — not selected
where 将对照计划检查。 它需要Schema的索引列和系统
属性 $key, (中文(简体) ), $owner, (中文(简体) ), $creator 财务报告和审定财务报表 $expiresAt。在列上过滤时,计划不会
索引,或将一列与另一类型的数值相比较,是一个编译错误:
.where(eq("title", "x")) // ❌ title is not indexed
.where(eq("score", 5)) // ❌ score is a dec, 5 is an i32 — write dec(5)
.where(between("score", dec(1), 5)) // ❌ both bounds need one type
排序为 orderBy,通过一个键:a asc(...) 或一个 desc(...)调用 orderBy 再来一次
替换命令。 节点将属性排序为一种类型,所以密钥有一种类型: 通过一个计划它是
列,和系统属性 $key, (中文(简体) ), $owner, (中文(简体) ), $creator 财务报告和审定财务报表 $expiresAt 已经修正
这些。 没有计划,写吧。 打电话 orderBy 之后 from(...),因此编译器可以检查
密钥对照方案; 仅在发送查询时检查前给定的密钥 :
import { asc, desc } from "@arkiv-network/sdk/query"
.from(Posts).orderBy(desc("score")) // score:dec DESC
.from(Posts).orderBy(asc("title")) // title:str ASC, indexed or not
.from(Posts).orderBy(asc("$expiresAt")) // $expiresAt:u64 ASC
.where(eq("category", "docs")).orderBy(desc("score", "dec")) // without a schema
.from(Posts).orderBy(asc("body")) // ❌ bytes cannot be sorted on
.from(Posts).orderBy(desc("$createdAt")) // ❌ $createdAt cannot be sorted on
.from(Posts).orderBy(desc("score"), asc("$key")) // ❌ one key only
任何一栏,但a bytes 一个可以排序,索引或不。 属性未设置或设置为
其它类型,没有值可按每个值排序,并在每个值下排序: asc最后一个
desc。命令贯穿所有匹配,而不是每个页面.
页为 limit 财务报告和审定财务报表 offset。页面最多可容纳100行 offset 跳过之前的匹配
这个 按一个不变的密钥排序, 如 $key,所以页面排行:
const page3 = await publicClient
.select({ $key: true, title: true })
.from(Posts)
.orderBy(asc("$key"))
.limit(20)
.offset(40)
.fetch()
在两个请求之间创建或删除的实体在它之后会转移偏移,所以一行可以重复
或被错过。 要在其他人写的时候准确读取每个匹配, 页面按键: 保留
asc("$key") 排序和过滤器 gt("$key", key(lastKey))时 lastKey 是最后一个 $key 联 合 国
上页。 通过一个计划,通过 { onMismatch: "throw" }: 跳过的实体的一页
没有行,所以没有键可以继续.
一个命名的策略不需要 where 完全没有.. select().from(Posts) 匹配每个文章。 未命名的策略类型
过滤它,所以 where 仅决定哪个实体匹配.
只检查您选择的内容 。 匹配但不符合方案的实体——选定
框,但缺少或持有其他类型,或,对于一个命名的图案,另一个图案
标记 – 放在页面外。 任何人都可以给一个实体写一个查询匹配, 所以一个不
从不辜负别人的阅读 页面可以比其限制少一行,并报告
有多少没有作为 skipped。通过 { onMismatch: "throw" } 无法使用
SchemaMismatchError 相反:
const page = await publicClient.select().from(Posts).where(eq("category", "docs")).fetch();
page.skipped; // matching entities on this page that did not fit Posts
一个查询读取相同,没有计划——比如,探索一个节点持有什么. 别管了
.from(...) 选择意味着它做了什么 "*",下面),各行保留其
形状。 只有检查不见了,还有一个命名的 schema 标记过滤器: 每个属性都会回来
实体拥有的任何价值,或 undefined,以及 where 取出任何属性 :
const row = (
await publicClient.select({ $key: true, title: true }).where(eq("category", "docs")).fetch()
).entities[0]!;
row.title; // ArkivValue | undefined — e.g. { type: "str", value: "Hello" }
在不命名的情况下读取每个属性, 请选择 $attributes一张他们所有的地图. "*" (或
无一无一地)在没有计划时与实体字段并取,因为没有栏
以列表形式列出.
$attributeSchema 用于找出实体持有哪些东西而不获取其价值:
属性的名称、类型以及是否索引。 价值可以很大——a bytes 属性
超过128KiB, RPC的端点可能会限制它发出的响应,所以100的一页
具有每个属性的实体可能比它返回时还要多。 首先获取计划, 然后选择
名称所需的属性。 它从来没有参与 "*"; 按名称选择它 :
const page = await publicClient.select({ $key: true, $attributeSchema: true }).where(eq("category", "docs")).fetch();
page.entities[0]?.$attributeSchema; // { title: { type: "str", indexed: false }, body: { type: "bytes", indexed: false } }
两者都通过一个计划.
脚枪 : 通过选择的内线。 存储在变量中的选择会拓宽其
true数值 改为boolean,因此只有运行时才知道哪些字段返回,结果的每个字段都是 选项。 如果你需要重用,请注明as const数字 :const fields = { $owner: true, title: true } as const; await publicClient.select(fields).from(Posts).where(eq("category", "docs")).fetch();
运行示例
您有多个选项可以运行您的 TypeScript 样本 。 每个装入你的 .env 文件 :
-
带有Node.js(使用实验类型Script支持):
node --env-file=.env --experimental-strip-types example.ts -
带有 Bun( 本地类型脚本支持, 负载)
.env单枪匹马:bun example.ts -
经典节点.js(使用转录到JavaScript):
- 如果您还没有安装类型脚本和节点.js类型定义 :
npm install typescript @types/node - 以默认设置初始化类型脚本配置 :
这将创建一个npx tsc --inittsconfig.json文档。 设定"types": ["node"]在其中,这样process.env类型检查. - 翻译你的
.ts文件输入.js数字 :
这创造了一个npx tsc --outDir distdist包含example.js(被转接的代码),连同相应的类型声明和源地图文件. - 运行转写脚本 :
node --env-file=.env dist/example.js
- 如果您还没有安装类型脚本和节点.js类型定义 :
样本代码也可以在 sample 此仓库的目录 .
软件包分发
此软件包支持多个模块格式实现最大兼容性 :
- ES 模块 (单位:千美元)
dist/*.js- 对于现代import声明 - 常见JS (单位:千美元)
dist/*.cjs- 为节点.jsrequire() - 类型声明 (单位:千美元)
dist/*.d.ts财务报告和审定财务报表dist/*.d.cts) - 完整类型脚本支持
建筑用途 s 生成具有适当类型声明的无害环境管理格式和共同JS格式.
运行时间支持
节点.js(ESM):
import { createPublicClient } from '@arkiv-network/sdk'; // Uses compiled ESM
节点.js(CommonJS):
const { createPublicClient } = require('@arkiv-network/sdk'); // Uses compiled CJS
Bun( TypeScript 本地语) :
import { createPublicClient } from '@arkiv-network/sdk'; // Uses *.ts directly
所有格式在使用TypeScript时都提供全类型安全性和IntelliSense支持.
发展
要安装依赖性 :
bun install
构建所有产出(ESM、CommonJS和类型声明):
bun run build
有关构建SDK的更多信息,请参见: 贝鲁特,md
要运行类型检查 :
bun run type-check
给 :
bun run lint
欲了解更多信息,请查阅: 捐款额
微博日志
设定 DEBUG 环境变量(或在浏览器中 DEBUG 键入 localStorage查看动词日志:
DEBUG=arkiv:* bun run your-script
调整命名空间(例如, arkiv:query:* 或者说 arkiv:actions:wallet:*以特定日志源为目标,前缀1 - 将它排除在外。 未设置 DEBUG 关闭调试输出.