@ ema- 无限制/ frontier- apps- sdk
SDK for Frontier Apps ——通过该程序提供数据库,存储和Ema API访问
frontier-apps-gateway。每种能力都是一种单独的子路径导入,所以应用程序
只能拉他们用的东西.
状态 : v.x- DB 路线 (
/v1/db/*(已部署) 储存路线 (单位:千美元)/v1/storage/*添加到 0.2.0 (中文(简体) ) (目前为rc频道; 见rc(原始内容存档于2019-09-31). Dutt-tag. Ema API线路计划未来发布.
为什么
每个部署的边境App 之前直接与蒙戈DB交谈。 单人 运行后查询可以饱和每个应用程序的复制集. 这个SDK部队 所有通过安全屏蔽列表的网关的流量,每个应用程序连接 游泳池,审计线索现场直播.
App代码只更改其导入行——调用表面( Mongoose) Schema, (中文(简体) ),
model(),查询链)被保留.
安装
pnpm add @ema-unlimited/frontier-apps-sdk mongoose
mongoose 是 DB 子路径所需的对等依赖。 SDK再出口
mongoose.Schema 财务报告和审定财务报表 InferSchemaType 因此,从一个直接的迁移
Mongoose连接是一个单行导入互换. SDK从不打开
MongoDB连接本身——Schema建筑纯粹是JS道路建设.
该软件包发布在 npm 公共注册处——不需要认证:
npm install @ema-unlimited/frontier-apps-sdk
子路径
| 导入 | 状态 | 它会做什么 |
|---|---|---|
@ema-unlimited/frontier-apps-sdk | 可用 | 网关输入( E)initGatewayClient+ 基准错误类 |
@ema-unlimited/frontier-apps-sdk/mongodb | 可用 | MongoDB 式 DB 访问( 完整的 Mongoose 表面) |
@ema-unlimited/frontier-apps-sdk/storage | 可用 | 通过 V4 签名的 URL 文件存储 (put 编号 : get 编号 : list / .) (中文(简体) ) |
@ema-unlimited/frontier-apps-sdk/ema (Ema API 代理)已经计划,但尚未添加.
根 (E)@ema-unlimited/frontier-apps-sdk(中文(简体) )
根出口只是网关基础设施,而不是一种能力.
import { initGatewayClient, FrontierError } from '@ema-unlimited/frontier-apps-sdk'
import type { GatewayClientConfig } from '@ema-unlimited/frontier-apps-sdk'
一次性客户端输入
initGatewayClient({
gatewayUrl: process.env.FRONTIER_GATEWAY_URL!, // injected by publisher
appId: process.env.APP_ID!, // injected by publisher
})
APP_ID 是出版商提供的值:用于出版范围的完整UUID,
preview-{shortId} 用于预览范围。 一次在应用靴子上叫这个——它有线
所有子路径使用的共享运输.
蒙戈DB (英语:@ema-unlimited/frontier-apps-sdk/mongodb(中文(简体) )
定义一个方案( Mongoose 的机械交换)
// Before
import mongoose, { Schema, type InferSchemaType } from 'mongoose'
// After — only the import line changes
import { Schema, model, type InferSchemaType } from '@ema-unlimited/frontier-apps-sdk/mongodb'
const noteSchema = new Schema(
{
userId: { type: String, required: true, index: true },
title: { type: String, required: true },
content: { type: String, default: '' },
},
{ timestamps: true },
)
export type NoteDocument = InferSchemaType<typeof noteSchema>
export const Note = model<NoteDocument>('Note', noteSchema)
查询
const notes = await Note.find({ userId }).sort({ createdAt: -1 }).limit(20)
const note = await Note.findOneAndUpdate(
{ _id: id, userId },
{ $set: { title: 'New' } },
{ new: true },
)
await Note.deleteOne({ _id: id, userId })
支助行动
| 方法 | 回返 |
|---|---|
find(filter?, options?) | QueryBuilder<T[]> (可兑换) |
findOne(filter) | QueryBuilder<T | null> |
findById(id) | QueryBuilder<T | null> |
findOneAndUpdate(filter, update, ?) | QueryBuilder<T | null> |
findByIdAndUpdate(id, update, ?) | QueryBuilder<T | null> |
findOneAndDelete(filter) | QueryBuilder<T | null> |
findByIdAndDelete(id) | QueryBuilder<T | null> |
create(doc | docs[]) | Promise<T | T[]> |
insertOne(doc) | Promise<T> |
insertMany(docs[]) | Promise<T[]> |
updateOne(filter, update) | Promise<UpdateResult> |
updateMany(filter, update) | Promise<UpdateResult> |
deleteOne(filter) | Promise<DeleteResult> |
deleteMany(filter) | Promise<DeleteResult> |
countDocuments(filter?) | Promise<number> |
distinct(field, filter?) | Promise<unknown[]> |
aggregate(pipeline[]) | Promise<unknown[]> |
bulkWrite(ops[], options?) | Promise<BulkWriteResult> |
createIndex(keys, options?) | Promise<string> (索引名称) |
listIndexes() | Promise<IndexInfo[]> |
查询链很懒—— .sort(), (中文(简体) ), .limit(), (中文(简体) ), .skip(), (中文(简体) ), .lean() 返回时
同样的 QueryBuilder 而 HTTP 仅在 await编.
.lean() 是一个无操作( 网关总是返回普通 JSON ).
批量写入
bulkWrite 使用相同的操作阵列 mongoose.Model.bulkWrite 选择:
await LineItem.bulkWrite([
{ updateOne: { filter: { _id: id }, update: { $set: { verdict: 'pass' } } } },
{ insertOne: { document: { sku: 'new' } } },
{ deleteOne: { filter: { sku: 'old' } } },
])
序与孟oose相异. 此默认为 ordered: false 地点
默认值为 true,所以失败的行动不会丢弃剩下的
批号。 过 { ordered: true } 用于止于一时的语义.
一个批量的上限为1000 行动, 整个请求机构在10 MiB——块 以上为两个。 即使一些操作落地, 批次也会丢出失败; 错误 信件带有应用过的计数器.
索引
// Idempotent: safe to call at every boot.
await Note.createIndex({ userId: 1, createdAt: -1 })
await Person.createIndex({ email: 1 }, { unique: true })
const live = await Note.listIndexes()
密钥顺序对复合指数很重要 并且保存在电线上,
因此,将密钥谱构建为对象字元。 支持的选项 : name, (中文(简体) ),
unique, (中文(简体) ), sparse, (中文(简体) ), hidden, (中文(简体) ), expireAfterSeconds, (中文(简体) ), partialFilterExpression。 。 。 .
任何其他选项是: 被拒绝而不是丢弃,所以一个应用永远不会
认为它有限制 数据库没有强制.
重新创建一个具有相同光谱的索引是没有的。 一样 名称 与
不同的规格是 IndexOptionsConflict 从服务器。 一个收藏是
上限为64个指数,MongoDB自己的极限.
createIndex 使用5分钟而非通常的30秒: a
索引构建运行到命令返回前完成 .
屏蔽在外层
这些在任何网络呼叫前同步抛出 :
.populate()• 使用aggregate与$lookup.drop()编号 :dropCollection()- 计划管理没有暴露dropIndex()• 使用createIndex财务报告和审定财务报表listIndexes
额外屏蔽的网关 $where, (中文(简体) ), $function, (中文(简体) ), $accumulator, (中文(简体) ), $out, (中文(简体) ),
mapReduce,且无约束 find({}) 打电话 这些返回 HTTP 400,即:
SDK 表面为 FrontierDbError。 。 。 .
处理错误
import { FrontierError } from '@ema-unlimited/frontier-apps-sdk'
import { FrontierDbError } from '@ema-unlimited/frontier-apps-sdk/mongodb'
try {
await Note.find({})
} catch (err) {
if (err instanceof FrontierDbError) {
console.error(err.status, err.code, err.message)
}
// Or catch any SDK error across subpaths:
if (err instanceof FrontierError) { ... }
}
储存@ema-unlimited/frontier-apps-sdk/storage(中文(简体) )
每个应用程序文件存储由GCS支持. SDK从不触及GCS证书——
网关薄荷短命 V4 签名的 URL (5-min PUT, 15-min Get) (中文(简体) )
浏览器/节点客户端直接向
storage.googleapis.com控制平面(自动、内容类型验证,
配额,审计)运行于网关;数据平面跳过.
存储是一个纯字节存储器——它从不分析您的文件——所以它接受 任意
结构良好的内容类型。没有上传允许列表可以生长。 安全问题
对活性内容执行 服务 时间: 网关服务
text/html, (中文(简体) ), image/svg+xml,以及 */xml 对象为 Content-Disposition: attachment 因此,他们下载,而不是在共享的GCS上执行内置
源代码。 (这不影响 <img> 编号 : <video> 编号 : <audio> 嵌入- 浏览器
忽略 Content-Disposition 用于子资源负载。 )
密钥布局
SDK发送的每个密钥都经过了消毒和前缀的服务器侧:
tenants/<tenantId>/frontier-apps/<appId>/uploads/<your-key>
租户+应用由网关解决(每个应用K8s秘密)——SDK
不能影响他们。 用户提供的密钥必须匹配 [A-Za-z0-9._/-] 财务报告和审定财务报表
无法从 / 或包含 .. / NUL (英语).
高级上传( 为您签名)
import { put } from '@ema-unlimited/frontier-apps-sdk/storage'
const blob = new Blob([fileBytes], { type: 'image/png' })
const meta = await put(`users/${userId}/avatar.png`, blob, {
contentType: 'image/png',
})
// → { key, size, contentType }
在车盖下 put() 签名 + PUT(直接对GCS) + 确认 .
如果PUT失败, confirm-put 实值 没有 没有孤儿记录.
浏览器直接上传( 大文件, 节点代理 hop)
当上传来自浏览器时, 将 URL 服务器端薄荷并让
浏览器执行 PUT. 一样 Content-Type + 大小被钉入
签名,所以调用者无法在
网关授权.
import { signPut, confirmPut } from '@ema-unlimited/frontier-apps-sdk/storage'
// 1. Server side — mint the signed URL.
const signed = await signPut(`users/${userId}/big.zip`, {
contentType: 'application/zip',
size: file.size,
})
// 2. Browser side — PUT bytes direct to GCS.
await fetch(signed.url, {
method: 'PUT',
headers: signed.requiredHeaders, // includes Content-Type + X-Goog-Content-Length-Range
body: file,
})
// 3. Server side — record the upload.
const meta = await confirmPut(`users/${userId}/big.zip`)
回读 / 列表 / 删除
import { get, getMeta, list, del } from '@ema-unlimited/frontier-apps-sdk/storage'
// Signed GET URL — usable directly in <img src>, <video src>, <a download>.
const { url, expiresAt, size, contentType } = await get(`users/${userId}/avatar.png`)
// Metadata only (no signing, no transfer).
const meta = await getMeta(`users/${userId}/avatar.png`)
// Paginated list scoped to the app's prefix.
const { items, nextCursor } = await list(`users/${userId}/`, { limit: 50 })
// Idempotent delete.
await del(`users/${userId}/avatar.png`)
制约因素(服务器强制)
| 限制 | 默认 |
|---|---|
| 每个对象大小 | 1个GIB |
| 已签名 PUT TTL | 5分钟 |
| 签名获取 TTL | 15分钟 |
| 内容类型 | 任何身材不错的 type/subtype 已接受。 活动类型( A)text/html, (中文(简体) ), image/svg+xml, (中文(简体) ), */xml)作为下载服务. |
| 密钥字符集 | [A-Za-z0-9._/-] 没有 ..无线索 /,无 NUL |
处理错误
import { FrontierStorageError } from '@ema-unlimited/frontier-apps-sdk/storage'
try {
await put('uploads/x.pdf', blob, { contentType: 'application/pdf' })
} catch (err) {
if (err instanceof FrontierStorageError) {
console.error(err.status, err.code, err.message)
// err.status: HTTP status (e.g. 422 for a denied/invalid content-type or oversize)
// err.code: the gateway error code (e.g. "VALIDATION_ERROR") or a client
// classifier ("network_error" | "gcs_put_failed" | "timeout" | "aborted")
}
}
哪里的东西降落在GCS
gs://<project-id>-frontier-apps-uploads/
tenants/
<tenantId>/
frontier-apps/
<appId>/
uploads/
users/<userId>/avatar.png ← written by the storage SDK
users/<userId>/reports/2026-Q1.pdf
该桶是专门用于每个应用程序用户上传的,与
共享 <env>-emu-blobs 那边的桶 frontier-apps-builder 保留它
即兴表演 租户下船是单人
gsutil -m rm -r gs://<bucket>/tenants/<tenantId>/ 对抗这个桶.
发展
pnpm install
pnpm typecheck
pnpm test
pnpm build