@ cosmoner/ sdk (英语)
官方节点.js SDK 宇宙纳, 云台( 云台) 用于应用程序、数据库、存储、域和电子邮件。 完整文档于 https://cosmoner.com/docs/sdk 页面存档备份,存于互联网档案馆。 。 。 .
安装
npm install @cosmoner/sdk
使用量
import { Cosmoner } from "@cosmoner/sdk";
const client = new Cosmoner({
apiKey: "your-api-key",
projectId: "your-project-id",
});
// Send to a single recipient
const result = await client.email.send({
credentialId: "your-credential-id",
to: "[email protected]",
subject: "Hello from Cosmoner",
html: "<h1>Hello!</h1><p>This is a test email.</p>",
});
console.log(result.data.messageId);
// Send to multiple recipients (up to 50)
await client.email.send({
credentialId: "your-credential-id",
to: ["[email protected]", "[email protected]"],
subject: "Team Update",
text: "Plain text email body",
replyTo: "[email protected]",
});
配置
| 选项 | 需求 | 默认 | 说明 |
|---|---|---|---|
apiKey | 对 | —— 说 | 您的宇宙纳 API 密钥 |
projectId | 没有 | —— 说 | 默认工程; 也可以通过每个调用 |
baseUrl | 没有 | https://api.cosmoner.com | API 基础 URL 覆盖 |
timeout | 没有 | 30000 | 以毫秒计的请求超时 |
maxRetries | 没有 | 2 | 暂时性故障的检索(见 检索(中文(简体) ) |
projectId 是可选的,所以一个客户端可以跨越项目。 每个电话传到
覆盖默认 :
const client = new Cosmoner({ apiKey: "your-api-key" });
await client.email.send({ projectId: "proj-2", credentialId: "...", to: "...", subject: "...", text: "..." });
检索
瞬间故障会自动重试, 并有指数回放和满载
抖动, 荣誉 Retry-After 当API发送它.
- 429个 总是重审——请求在受理之前被驳回, 所以重放它不能重复副作用.
- 暂停和5xx 重审时只考虑独能方法。 例如:
email.send实值 没有 重放, 因为 API 可能已完成发送 在无法回答之前.
设定 maxRetries: 0 以完全禁用重试.
电子邮件
client.email.send(params)
使用原始内容(a) subject 与 html, (中文(简体) ), text 或两者兼有——或
存储 template提供所有三个。 两种形式是工会类型,所以
TypeScript 拒绝一个混合它们的呼叫,客户端拒绝一个
发送任何消息之前的运行时间 .
| 参数 | 类型 | 需求 | 说明 |
|---|---|---|---|
credentialId | string | 对 | SMTP 证书标识 |
to | string | string[] | 对 | 接收者,最多50人 |
subject | string | 对* | 电子邮件主题行 |
html | string | 无* | HTML 正文 |
text | string | 无* | 纯文本正文 |
template | EmailTemplateRef | 无* | 要发送的已存储模板 |
replyTo | string | string[] | 没有 | 对地址的答复,最多5次 |
projectId | string | 没有 | 覆盖客户端级项目 |
* 通过 subject 至少有一个 html 或者说 text,或通过 template
和没有三个.
template 实值 { id, version?, variables? }数字 : id 是模板的 id 或它的
别名, version 正整数( 已公布的版本被忽略时),以及
variables 模板宣布的变量值。 回应
名称 data.template (单位:千美元)id, (中文(简体) ), alias, (中文(简体) ), version) (中文(简体) ).
const { data } = await client.email.send({
credentialId: "your-credential-id",
to: "[email protected]",
template: { id: "welcome", variables: { name: "Ada" } },
});
console.log(data.messageId, data.template?.version);
发送模板需要使用 API 密钥 email-templates:read 以及
email:write。API对未知模板或版本的404进行回答,409
(单位:千美元)EMAIL_TEMPLATE_NOT_PUBLISHED当没有出版版本时,422
变量不匹配模板声明或渲染点击的
限制,当模板短暂无法使用时为503.
应用程序
const { data: apps } = await client.apps.list();
const web = apps.find((app) => app.name === "web")!;
const { data: started } = await client.apps.deploy(web.id, { tag: "v2" });
const finished = await client.apps.waitForDeployment(web.id, started.id);
if (finished.phase !== "ACTIVE") throw new Error(finished.error ?? finished.phase);
client.apps.deploy(appId, params?)
开始部署图像应用程序——从宇宙器注册处运行图像的应用程序
——不等待就返回部署. 从寄存器中构建的应用程序是
拒绝。 需要一个 API 密钥, 使用 apps:write。 。 。 .
| 参数 | 类型 | 需求 | 说明 |
|---|---|---|---|
tag | string | 没有 | 要从应用程序仓库部署的标记 |
digest | string | 没有 | 准确的图像, sha256:<64 hex characters> |
projectId | string | 没有 | 覆盖客户端级项目 |
过 tag 或者说 digest而不是两个。 没有, 应用程序的图像已经命名
被再次拉动.
client.apps.waitForDeployment(appId, deploymentId, params?)
投票直到部署结束,并在最后阶段解决——
ACTIVE, (中文(简体) ), ERROR, (中文(简体) ), CANCELED 或者说 SUPERSEDED。它只拒绝请求
失败或 timeout (默认10分)通过;部署继续进行
就是这样. interval 默认为3秒,以及 onPoll 每一个结果都要求.
需求 apps:read。 。 。 .
client.apps.list() 财务报告和审定财务报表 client.apps.getDeployment(appId, deploymentId) 为:
里面的单个电话.
client.apps.listDeployments(appId, params?) 财务报告和审定财务报表 client.apps.rollback(appId, deploymentId, params?)
listDeployments 返回应用程序的最新二十个部署,最新
先说 每个都带着 current,无论它的图像是应用程序运行的
现在,和 rollbackable,是否应用程序可以回到它。 需求 apps:read。 。 。 .
rollback 把应用程序放回图像上 一个较早的部署运行,没有
,然后不等待地返回新的部署。 对应用程序有用
由寄存器和图像应用程序所建。 仅图像返回: app
保留当前环境变量和设置。 需求 apps:write。 。 。 .
const { data: history } = await client.apps.listDeployments(appId);
const previous = history.find((d) => d.rollbackable);
if (previous) {
const { data: rollback } = await client.apps.rollback(appId, previous.id);
await client.apps.waitForDeployment(appId, rollback.id);
}
托管
读取一个项目的共享托管站点以及在哪里可以到达其文档. 需要一个
API 密钥为 hosting:read。 。 。 .
const { data: sites } = await client.hosting.list();
const { data: access } = await client.hosting.access(sites[0].id);
// access.username, access.host, access.sftp.port, access.ssh.{port,enabled}
client.hosting.get(siteId, { credentials: true }) 并返回网站的
sftpPassword它不需要超出 hosting:read,所以注意钥匙
相应.
站点图像、 PHP 版本和电子邮件
一个站点运行一个精确的 站点图像的构建 并且永远不会改变
除了对关键或已知被开发出的脆弱性的补救之外
后夜申请 forcedUpdate.deadline。 。 。 .
const { data: image } = await client.hosting.image(siteId);
// image.current.tag, image.update (null when up to date), image.forcedUpdate,
// image.rollback (with `until`), image.php.{version, securitySupportEnds, offered}
if (image.update) await client.hosting.updateImage(siteId);
await client.hosting.rollbackImage(siteId); // within 7 days of the last change
await client.hosting.setPhpVersion(siteId, { phpVersion: image.php.offered[0] });
updateImage, (中文(简体) ), rollbackImage 财务报告和审定财务报表 setPhpVersion 在网站开始前重新启动
他们回答,然后每个人都返回图像状态之后。 只有版本在
php.offered 被接受; API 拥有该列表, 因此 SDK 不检查
这个 他们需要 hosting:write。 。 。 .
const { data: mail } = await client.hosting.mail(siteId);
// mail.enabled, mail.fromAddress, mail.domains — the verified email domains it can send from
await client.hosting.enableMail(siteId, { emailDomainId: mail.domains[0].id, localPart: "wordpress" });
await client.hosting.disableMail(siteId);
enableMail 让网站的 PHP mail() 发送为 <localPart>@<domain>
通过新的 SMTP 证书, 并且它发送的每封邮件都会在
项目的电子邮件计划. enableMail 财务报告和审定财务报表 disableMail 需求 email:write 还有
作为 hosting:write。 。 。 .
const { data: domains } = await client.hosting.domains(siteId);
const { data: attached } = await client.hosting.attachDomain(siteId, { hostname: "shop.example.com" });
// attached.dnsRecordsCreated — true when Cosmoner runs the domain's DNS and added attached.instructions
await client.hosting.updateDomain(siteId, attached.domainId, { kind: "REDIRECT", redirectTarget: "https://example.com" });
await client.hosting.detachDomain(siteId, attached.domainId);
attachDomain 取一个工程的已验证域,或一个子域
1;其他任何用422来拒绝. 一个网站从
<hostname>/public_html 除非 path 命名另一个文件夹. updateDomain
替换整个行为,所以 { kind: "SITE" } 一个人把它移回它的
默认文件夹 。 这些需要 hosting:write· ; domains 需求 hosting:read。 。 。 .
资源命名空间
每一个都使用一种资源,并返回API信封.
每一个方法 { projectId } 以覆盖客户端默认值,但
projects,它读取整个账户,从未使用默认项目.
一种在发送任何信息之前, 使用 ID 的方法拒绝空的 .
| 命名空间 | 读取 | 变动 |
|---|---|---|
projects | list(), (中文(简体) ), get(project) ——由id或shult制作 | update(project, { name }), (中文(简体) ), delete(project) |
apps | get(appId), (中文(简体) ), logs(appId, { type: "BUILD" | "RUN" }),同时 list() 和部署方法 | update(appId, changes), (中文(简体) ), delete(appId) |
servers | list(), (中文(简体) ), get(serverId) - 添加安装的 sshKeys | update(serverId, { name }), (中文(简体) ), powerOn(serverId), (中文(简体) ), powerOff(serverId), (中文(简体) ), reboot(serverId), (中文(简体) ), delete(serverId) |
sshKeys | list() | create({ name, publicKey }), (中文(简体) ), delete(sshKeyId) |
databases | list() (每一种), listDedicated(), (中文(简体) ), getDedicated(databaseId), (中文(简体) ), listShared(), (中文(简体) ), getShared(tenantId) | rotateSharedPassword(tenantId), (中文(简体) ), deleteDedicated(databaseId), (中文(简体) ), deleteShared(tenantId) |
redis | list(), (中文(简体) ), get(redisId) | delete(redisId) |
domains | list(), (中文(简体) ), get(domain) - 通过身份或名称,例如: example.com | create(name), (中文(简体) ), verify(domain), (中文(简体) ), delete(domain) |
buckets | list() - 没有读到单纸盒 | delete(bucketId) |
registries | list(), (中文(简体) ), get(registryId) | delete(registryId) |
iam | list(), (中文(简体) ), get(iamUserName) - 名单保持 credentials + 部分失败 errors | delete(iamUserName) |
members | list() - 成员和待发邀请 | |
email | listDomains(), (中文(简体) ), getDomain(emailDomainId), (中文(简体) ), limits(),同时 send() | createDomain({ domainId }), (中文(简体) ), createExternalDomain({ domainName }), (中文(简体) ), verifyDomain(emailDomainId), (中文(简体) ), deleteDomain(emailDomainId) |
其中两个返回一个证书, 每个只需要名称空间读取
范围,所以要守护带它的密钥: databases.getDedicated 总是包括
connectionUri,与密码完全连接 URI,以及 redis.get
总是包括正文 password。 。 。 .
订购已付资源
servers.create, (中文(简体) ), redis.create, (中文(简体) ), databases.createDedicated, (中文(简体) ),
buckets.create, (中文(简体) ), registries.create, (中文(简体) ), hosting.create, (中文(简体) ), apps.create 财务报告和审定财务报表
apps.resize 立即向保存的卡片收费,但被拒绝
402 在项目无法计费时,任何事物都会被创建. 每个都有
preview (单位:千美元)previewDedicated, (中文(简体) ), resizePreview)首先返回价格:
monthly 准确的说, dueToday 项目一旦有估计数
订阅. client.catalog 列出要排序的大小、计划和区域
和,而且不需要项目。 一个应用程序在两个电话中订购: createDraft
免费保存它应该是什么 create 把草案变成账单
应用.
删除为永久的,删除已计费资源停止计费.
apps.update 仅免费更改设置;它留下环境变量
到部署文件. domains.create 添加您已经拥有的域: 发布
返回时 verificationRecord,然后调用 verify删除 SSH 密钥
放在服务器上,它已经安装在.. stillAuthorisedOn 计数
他们.
电源动作是同步的: 服务器读取 PROVISIONING 直至
动作就解决了 所以再读一遍看看结果. powerOff 切断电源
完全,一个停止的服务器仍然收费. projects.delete 被拒绝
与409, 而项目仍然持有资源. email.createDomain 设置
已经包含在项目中的域, createExternalDomain dNS 主机主机
其它位置; 发布返回 dnsRecords,然后调用 verifyDomain,其
第一次成功将项目的电子邮件计划放在订阅上.
databases.rotateSharedPassword 返回此新密码一次,然后
老家伙马上停止工作.
秘诀
存储部署文件提到的值 from_secret。需要一个 API
键为 secrets:read,以及 secrets:write 改变一切.
const { data: secret } = await client.secrets.create({
name: "DB_PASSWORD",
value: "hunter2",
environment: "production",
});
secret.value; // "hunter2" — returned here and nowhere else
secret.maskedValue; // "hu••••r2", safe to display
const { data: secrets } = await client.secrets.list({ environment: "production" });
// name, environment, version, who changed it and when — never the value
一个秘密的值在休息时被加密 并准确返回一次,通过呼叫
这下好了. list() 财务报告和审定财务报表 get() 描述一个没有价值的秘密,没有
路由解密 1,所以一个丢失的值被替换而不是被回收.
update(id, { value }) 替换一个值并凸起 version· ; delete(id)
删除; usage() 报告该项目拥有和可能拥有多少秘密;
audit(id) 读到谁改变了它和何时.
两种API行为在调试前值得知道:
- 写作需要拥有或管理角色. API检查关键行为的作用
项目的关键行动与服务
账户的角色,一个个人访问符 与它的持有者。 一个键上
developer服务账户被拒绝,即使它携带403个secrets:write。 。 。 . - 创建是限速的 每个项目每10分钟100个,由 所有在项目上工作的钥匙和机器 以及一个秘密的项目 限制答案 402.
变量
简洁的密文兄弟, 对于不敏感的配置一个部署
参考文件为 from_variable需求 variables:read,以及
variables:write 改变一切.
await client.variables.create({ name: "LOG_LEVEL", value: "debug" });
const { data: variables } = await client.variables.list();
variables[0].value; // "debug" — returned in full on every read
这是两种资源之间的区别,而不是一种疏忽:任何东西
值得隐藏属于 client.secrets。 。 。 . update() 这里需要一个值, a
说明,或两者兼有.
部署文件
验证 a .cosmoner/deployment.yaml - 你承诺要描述的档案
仓库部署——没有API密钥或网络调用.
import { validateDeployment } from "@cosmoner/sdk";
import { readFileSync } from "node:fs";
const { valid, issues, template } = validateDeployment(
readFileSync(".cosmoner/deployment.yaml", "utf8")
);
for (const issue of issues) {
console.log(`${issue.severity} ${issue.path}: ${issue.message}`);
}
error services.0.run_command: Static sites cannot define a run_command
warning services.0.prot: Unknown field "prot" — it will be ignored
警告是平台所容忍的 而你可能不是故意的 一个
未知键是主键 : 平台放弃它而不是拒绝
文件,所以为新字段编写的模板仍然应用其已知设置
对抗旧部署 将这里视为致命 将拒绝文件
平台接受,因此被报告为警告,说明后果.
过 strict 当你不愿让一个打字—— 它改变了
判决不是结果.
template 是平台读取的文件: 默认应用, 未知密钥
掉下来了 将它与你写的东西比起来就是重点——这是设置
这会真正到达.
在命令行上也有同样的检查, 对于CI或一个承诺前钩, 不安装任何东西:
npx @cosmoner/cli validate --strict
validateDeployment(source, options?)
| 参数 | 类型 | |
|---|---|---|
source | string | 文件是写成的——原始文本,而不是解析对象. |
options.strict | boolean | 将警告视为错误 。 默认为 false。 。 。 . |
回返 { valid, issues, template }。 。 。 . validateDeploymentDocument(document, options?) 是对一个已解析对象的同一检查, 对于一个文件
生成而不是从磁盘读取.
DEPLOYMENT_FILE_PATHS 列出平台检查的地点,按顺序排列
检查他们,和 APP_SCHEMA_URL 是出版的 JSON Schema 编辑器可以是
指点点.
处理错误
每一次失败都会投出一个子类 CosmonerError,这样你就可以广泛捕捉或
狭义:
import { Cosmoner, CosmonerError, RateLimitError } from "@cosmoner/sdk";
try {
await client.email.send({ ... });
} catch (err) {
if (err instanceof RateLimitError) {
console.error(err.retryAfter); // seconds, when the API supplies it
} else if (err instanceof CosmonerError) {
console.error(err.code); // e.g. "INSUFFICIENT_SCOPE"
console.error(err.status); // e.g. 403
console.error(err.details); // field-level validation errors, when present
console.error(err.docsUrl); // page explaining the fix, when the API links one
console.error(err.message); // Human-readable message
}
}
| 错误 | 抛出 |
|---|---|
ValidationError | 400,422 (中文(简体) ) |
AuthenticationError | 401 (中文(简体) ) |
InsufficientScopeError | 403 — API 密钥缺少 a resource:action |
NotFoundError | 404 (韩语) |
ConflictError | 409个 |
RateLimitError | 429个 |
ServerError | 5 (简体中文) |
CosmonerTimeoutError | 超过请求 timeout |
CosmonerConnectionError | DNS, TCP, TLS 或套接字失败 |