Loading...
Loading...
Compare original and translation side by side
@cipherstash/stack@cipherstash/stack@cipherstash/protect@cipherstash/stack@cipherstash/protect@cipherstash/stacknpm install @cipherstash/stack@cipherstash/protect-ffiserverExternalPackagesnpm install @cipherstash/stack@cipherstash/protect-ffiserverExternalPackages.envCS_WORKSPACE_CRN=crn:ap-southeast-2.aws:your-workspace-id
CS_CLIENT_ID=your-client-id
CS_CLIENT_KEY=your-client-key
CS_CLIENT_ACCESS_KEY=your-access-key.envCS_WORKSPACE_CRN=crn:ap-southeast-2.aws:your-workspace-id
CS_CLIENT_ID=your-client-id
CS_CLIENT_KEY=your-client-key
CS_CLIENT_ACCESS_KEY=your-access-keyconst client = await Encryption({
schemas: [users],
config: {
workspaceCrn: "crn:ap-southeast-2.aws:your-workspace-id",
clientId: "your-client-id",
clientKey: "your-client-key",
accessKey: "your-access-key",
keyset: { name: "my-keyset" }, // optional: multi-tenant isolation
},
})configCS_*const client = await Encryption({
schemas: [users],
config: {
workspaceCrn: "crn:ap-southeast-2.aws:your-workspace-id",
clientId: "your-client-id",
clientKey: "your-client-key",
accessKey: "your-access-key",
keyset: { name: "my-keyset" }, // 可选:多租户隔离
},
})configCS_*STASH_LOG_LEVEL=debug # debug | info | warn | error (enables logging automatically)STASH_LOG_LEVEL=debug # debug | info | warn | error(自动启用日志)const client = await Encryption({
schemas: [users],
logging: {
enabled: true, // toggle logging on/off (default: false, auto-enabled by STASH_LOG_LEVEL)
pretty: true, // pretty-print in development (default: auto-detected)
},
})const client = await Encryption({
schemas: [users],
logging: {
enabled: true, // 开启/关闭日志(默认:false,由STASH_LOG_LEVEL自动启用)
pretty: true, // 开发环境下格式化输出(默认:自动检测)
},
})const client = await Encryption({
schemas: [users],
logging: {
drain: (ctx) => {
// Forward to Axiom, Datadog, OTLP, etc.
fetch("https://your-service.com/logs", {
method: "POST",
body: JSON.stringify(ctx.event),
})
},
},
})const client = await Encryption({
schemas: [users],
logging: {
drain: (ctx) => {
// 转发到Axiom、Datadog、OTLP等平台
fetch("https://your-service.com/logs", {
method: "POST",
body: JSON.stringify(ctx.event),
})
},
},
})| Import Path | Provides |
|---|---|
| |
| |
| |
| |
| |
| |
| |
| Client-safe exports (schema builders + types only, no native FFI) |
| All TypeScript types |
| 导入路径 | 提供内容 |
|---|---|
| |
| |
| |
| |
| 用于Drizzle ORM的 |
| 用于Supabase的 |
| 用于DynamoDB的 |
| 客户端安全导出(仅模式构建器和类型,无原生FFI) |
| 所有TypeScript类型 |
encryptedTableencryptedColumnimport { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
const users = encryptedTable("users", {
email: encryptedColumn("email")
.equality() // exact-match queries
.freeTextSearch() // full-text / fuzzy search
.orderAndRange(), // sorting and range queries
age: encryptedColumn("age")
.dataType("number")
.equality()
.orderAndRange(),
address: encryptedColumn("address"), // encrypt-only, no search indexes
})
const documents = encryptedTable("documents", {
metadata: encryptedColumn("metadata")
.searchableJson(), // encrypted JSONB queries (JSONPath + containment)
})encryptedTableencryptedColumnimport { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
const users = encryptedTable("users", {
email: encryptedColumn("email")
.equality() // 精确匹配查询
.freeTextSearch() // 全文/模糊搜索
.orderAndRange(), // 排序和范围查询
age: encryptedColumn("age")
.dataType("number")
.equality()
.orderAndRange(),
address: encryptedColumn("address"), // 仅加密,无搜索索引
})
const documents = encryptedTable("documents", {
metadata: encryptedColumn("metadata")
.searchableJson(), // 加密JSONB查询(JSONPath + 包含查询)
})| Method | Purpose | Query Type |
|---|---|---|
| Exact match lookups | |
| Full-text / fuzzy search | |
| Sorting, comparison, range queries | |
| Encrypted JSONB path and containment queries (auto-sets | |
| Set plaintext data type | N/A |
'string''number''boolean''date''bigint''json'| 方法 | 用途 | 查询类型 |
|---|---|---|
| 精确匹配查找 | |
| 全文/模糊搜索 | |
| 排序、比较、范围查询 | |
| 加密JSONB路径和包含查询(自动将 | |
| 设置明文数据类型 | N/A |
'string''number''boolean''date''bigint''json'encryptedColumn("bio").freeTextSearch({
tokenizer: { kind: "ngram", token_length: 3 }, // or { kind: "standard" }
token_filters: [{ kind: "downcase" }],
k: 6,
m: 2048,
include_original: false,
})encryptedColumn("bio").freeTextSearch({
tokenizer: { kind: "ngram", token_length: 3 }, // 或 { kind: "standard" }
token_filters: [{ kind: "downcase" }],
k: 6,
m: 2048,
include_original: false,
})import type { InferPlaintext, InferEncrypted } from "@cipherstash/stack/schema"
type UserPlaintext = InferPlaintext<typeof users>
// { email: string; age: string; address: string }
type UserEncrypted = InferEncrypted<typeof users>
// { email: Encrypted; age: Encrypted; address: Encrypted }import type { InferPlaintext, InferEncrypted } from "@cipherstash/stack/schema"
type UserPlaintext = InferPlaintext<typeof users>
// { email: string; age: string; address: string }
type UserEncrypted = InferEncrypted<typeof users>
// { email: Encrypted; age: Encrypted; address: Encrypted }import { Encryption } from "@cipherstash/stack"
const client = await Encryption({ schemas: [users, documents] })Encryption()Promise<EncryptionClient>// Error handling
try {
const client = await Encryption({ schemas: [users] })
} catch (error) {
console.error("Init failed:", error.message)
}import { Encryption } from "@cipherstash/stack"
const client = await Encryption({ schemas: [users, documents] })Encryption()Promise<EncryptionClient>// 错误处理
try {
const client = await Encryption({ schemas: [users] })
} catch (error) {
console.error("初始化失败:", error.message)
}// Encrypt
const encrypted = await client.encrypt("hello@example.com", {
column: users.email,
table: users,
})
if (encrypted.failure) {
console.error(encrypted.failure.message)
} else {
console.log(encrypted.data) // Encrypted payload (opaque object)
}
// Decrypt
const decrypted = await client.decrypt(encrypted.data)
if (!decrypted.failure) {
console.log(decrypted.data) // "hello@example.com"
}nullnull// 加密
const encrypted = await client.encrypt("hello@example.com", {
column: users.email,
table: users,
})
if (encrypted.failure) {
console.error(encrypted.failure.message)
} else {
console.log(encrypted.data) // 加密载荷(不透明对象)
}
// 解密
const decrypted = await client.decrypt(encrypted.data)
if (!decrypted.failure) {
console.log(decrypted.data) // "hello@example.com"
}nullnullEncrypted<User>type User = { id: string; email: string; createdAt: Date }
const user = {
id: "user_123",
email: "alice@example.com", // defined in schema -> encrypted
createdAt: new Date(), // not in schema -> unchanged
}
// Encrypt model — let TypeScript infer the return type from the schema
const encResult = await client.encryptModel(user, users)
if (!encResult.failure) {
// encResult.data.email is typed as Encrypted
// encResult.data.id is typed as string
// encResult.data.createdAt is typed as Date
}
// Decrypt model
const decResult = await client.decryptModel(encResult.data)
if (!decResult.failure) {
console.log(decResult.data.email) // "alice@example.com"
}Decrypted<T>client.encryptModel<User>(...)UserEncrypted<User>type User = { id: string; email: string; createdAt: Date }
const user = {
id: "user_123",
email: "alice@example.com", // 已在模式中定义 -> 会被加密
createdAt: new Date(), // 未在模式中定义 -> 保持不变
}
// 加密模型 — 让TypeScript从模式中推断返回类型
const encResult = await client.encryptModel(user, users)
if (!encResult.failure) {
// encResult.data.email 类型为 Encrypted
// encResult.data.id 类型为 string
// encResult.data.createdAt 类型为 Date
}
// 解密模型
const decResult = await client.decryptModel(encResult.data)
if (!decResult.failure) {
console.log(decResult.data.email) // "alice@example.com"
}Decrypted<T>client.encryptModel<User>(...)Userconst plaintexts = [
{ id: "u1", plaintext: "alice@example.com" },
{ id: "u2", plaintext: "bob@example.com" },
{ id: "u3", plaintext: null }, // null values preserved
]
const encrypted = await client.bulkEncrypt(plaintexts, {
column: users.email,
table: users,
})
// encrypted.data = [{ id: "u1", data: EncryptedPayload }, ...]
const decrypted = await client.bulkDecrypt(encrypted.data)
// Per-item error handling:
for (const item of decrypted.data) {
if ("data" in item) {
console.log(`${item.id}: ${item.data}`)
} else {
console.error(`${item.id} failed: ${item.error}`)
}
}const plaintexts = [
{ id: "u1", plaintext: "alice@example.com" },
{ id: "u2", plaintext: "bob@example.com" },
{ id: "u3", plaintext: null }, // 空值会被保留
]
const encrypted = await client.bulkEncrypt(plaintexts, {
column: users.email,
table: users,
})
// encrypted.data = [{ id: "u1", data: EncryptedPayload }, ...]
const decrypted = await client.bulkDecrypt(encrypted.data)
// 逐项错误处理:
for (const item of decrypted.data) {
if ("data" in item) {
console.log(`${item.id}: ${item.data}`)
} else {
console.error(`${item.id} 失败: ${item.error}`)
}
}const userModels = [
{ id: "1", email: "alice@example.com" },
{ id: "2", email: "bob@example.com" },
]
const encrypted = await client.bulkEncryptModels(userModels, users)
const decrypted = await client.bulkDecryptModels(encrypted.data)const userModels = [
{ id: "1", email: "alice@example.com" },
{ id: "2", email: "bob@example.com" },
]
const encrypted = await client.bulkEncryptModels(userModels, users)
const decrypted = await client.bulkDecryptModels(encrypted.data)// Equality query
const eqQuery = await client.encryptQuery("alice@example.com", {
column: users.email,
table: users,
queryType: "equality",
})
// Free-text search
const matchQuery = await client.encryptQuery("alice", {
column: users.email,
table: users,
queryType: "freeTextSearch",
})
// Order and range
const rangeQuery = await client.encryptQuery(25, {
column: users.age,
table: users,
queryType: "orderAndRange",
})queryType// 等值查询
const eqQuery = await client.encryptQuery("alice@example.com", {
column: users.email,
table: users,
queryType: "equality",
})
// 全文搜索
const matchQuery = await client.encryptQuery("alice", {
column: users.email,
table: users,
queryType: "freeTextSearch",
})
// 范围查询
const rangeQuery = await client.encryptQuery(25, {
column: users.age,
table: users,
queryType: "orderAndRange",
})queryTypereturnTypereturnTypeencryptQueryEncryptedreturnType | Output | Use case |
|---|---|---|
| | Parameterized queries, ORMs accepting JSON |
| | Supabase |
| | Embedding inside another string or JSON value |
// Get a composite literal string for use with Supabase
const term = await client.encryptQuery("alice@example.com", {
column: users.email,
table: users,
queryType: "equality",
returnType: "composite-literal",
})
// term.data is a stringreturnTypeencryptQueryEncryptedreturnType | 输出 | 适用场景 |
|---|---|---|
| | 参数化查询、接受JSON的ORM |
| | Supabase |
| | 嵌入到另一个字符串或JSON值中 |
// 获取复合字面量字符串用于Supabase
const term = await client.encryptQuery("alice@example.com", {
column: users.email,
table: users,
queryType: "equality",
returnType: "composite-literal",
})
// term.data 是一个字符串returnType.searchableJson()// String -> JSONPath selector query
const pathQuery = await client.encryptQuery("$.user.email", {
column: documents.metadata,
table: documents,
})
// Object/Array -> containment query
const containsQuery = await client.encryptQuery({ role: "admin" }, {
column: documents.metadata,
table: documents,
}).searchableJson()// 字符串 -> JSONPath选择器查询
const pathQuery = await client.encryptQuery("$.user.email", {
column: documents.metadata,
table: documents,
})
// 对象/数组 -> 包含查询
const containsQuery = await client.encryptQuery({ role: "admin" }, {
column: documents.metadata,
table: documents,
})const terms = [
{ value: "alice@example.com", column: users.email, table: users, queryType: "equality" as const },
{ value: "bob", column: users.email, table: users, queryType: "freeTextSearch" as const },
]
const results = await client.encryptQuery(terms)
// results.data = [EncryptedPayload, EncryptedPayload]const terms = [
{ value: "alice@example.com", column: users.email, table: users, queryType: "equality" as const },
{ value: "bob", column: users.email, table: users, queryType: "freeTextSearch" as const },
]
const results = await client.encryptQuery(terms)
// results.data = [EncryptedPayload, EncryptedPayload]import { LockContext } from "@cipherstash/stack/identity"
// 1. Create a lock context (defaults to the "sub" claim)
const lc = new LockContext()
// Or with custom claims: new LockContext({ context: { identityClaim: ["sub", "org_id"] } })
// 2. Identify the user with their JWT
const identifyResult = await lc.identify(userJwt)
if (identifyResult.failure) {
throw new Error(identifyResult.failure.message)
}
const lockContext = identifyResult.data
// 3. Encrypt with lock context
const encrypted = await client
.encrypt("sensitive data", { column: users.email, table: users })
.withLockContext(lockContext)
// 4. Decrypt with the same lock context
const decrypted = await client
.decrypt(encrypted.data)
.withLockContext(lockContext)encryptdecryptencryptModeldecryptModelbulkEncryptbulkDecryptbulkEncryptModelsbulkDecryptModelsencryptQueryimport { LockContext } from "@cipherstash/stack/identity"
// 1. 创建锁上下文(默认使用"sub"声明)
const lc = new LockContext()
// 或使用自定义声明: new LockContext({ context: { identityClaim: ["sub", "org_id"] } })
// 2. 使用用户JWT识别用户
const identifyResult = await lc.identify(userJwt)
if (identifyResult.failure) {
throw new Error(identifyResult.failure.message)
}
const lockContext = identifyResult.data
// 3. 使用锁上下文加密
const encrypted = await client
.encrypt("敏感数据", { column: users.email, table: users })
.withLockContext(lockContext)
// 4. 使用相同的锁上下文解密
const decrypted = await client
.decrypt(encrypted.data)
.withLockContext(lockContext)encryptdecryptencryptModeldecryptModelbulkEncryptbulkDecryptbulkEncryptModelsbulkDecryptModelsencryptQueryCS_CTS_ENDPOINT=https://ap-southeast-2.aws.auth.viturhosted.netCS_CTS_ENDPOINT=https://ap-southeast-2.aws.auth.viturhosted.net// By name
const client = await Encryption({
schemas: [users],
config: { keyset: { name: "Company A" } },
})
// By UUID
const client = await Encryption({
schemas: [users],
config: { keyset: { id: "123e4567-e89b-12d3-a456-426614174000" } },
})// 按名称
const client = await Encryption({
schemas: [users],
config: { keyset: { name: "Company A" } },
})
// 按UUID
const client = await Encryption({
schemas: [users],
config: { keyset: { id: "123e4567-e89b-12d3-a456-426614174000" } },
})const result = await client
.encrypt(plaintext, { column: users.email, table: users })
.withLockContext(lockContext) // optional: identity-aware
.audit({ metadata: { action: "create" } }) // optional: audit trailconst result = await client
.encrypt(明文, { column: users.email, table: users })
.withLockContext(lockContext) // 可选:身份感知
.audit({ metadata: { action: "create" } }) // 可选:审计追踪Resultdatafailureconst result = await client.encrypt("hello", { column: users.email, table: users })
if (result.failure) {
console.error(result.failure.type, result.failure.message)
// type is one of: "ClientInitError" | "EncryptionError" | "DecryptionError"
// | "LockContextError" | "CtsTokenError"
} else {
console.log(result.data)
}Resultdatafailureconst result = await client.encrypt("hello", { column: users.email, table: users })
if (result.failure) {
console.error(result.failure.type, result.failure.message)
// type 可选值: "ClientInitError" | "EncryptionError" | "DecryptionError"
// | "LockContextError" | "CtsTokenError"
} else {
console.log(result.data)
}| Type | When |
|---|---|
| Client initialization fails (bad credentials, missing config) |
| An encrypt operation fails |
| A decrypt operation fails |
| Lock context creation or usage fails |
| Identity token exchange fails |
| 类型 | 触发场景 |
|---|---|
| 客户端初始化失败(无效凭证、缺失配置) |
| 加密操作失败 |
| 解密操作失败 |
| 锁上下文创建或使用失败 |
| 身份令牌交换失败 |
freeTextSearchencryptedTablefreeTextSearchencryptedTableCREATE EXTENSION IF NOT EXISTS eql_v2;
CREATE TABLE users (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email eql_v2_encrypted
);CREATE TABLE users (
id SERIAL PRIMARY KEY,
email jsonb NOT NULL
);CREATE EXTENSION IF NOT EXISTS eql_v2;
CREATE TABLE users (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email eql_v2_encrypted
);CREATE TABLE users (
id SERIAL PRIMARY KEY,
email jsonb NOT NULL
); | | Import Path |
|---|---|---|
| | |
| | |
| | |
| | |
Result | | 导入路径 |
|---|---|---|
| | |
| | |
| | |
| | |
Result| Method | Signature | Returns |
|---|---|---|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
.withLockContext().audit()| 方法 | 签名 | 返回值 |
|---|---|---|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
.withLockContext().audit()encryptedTable(tableName: string, columns: Record<string, EncryptedColumn | EncryptedField | nested>)
encryptedColumn(columnName: string) // chainable: .equality(), .freeTextSearch(), .orderAndRange(), .searchableJson(), .dataType()
encryptedField(valueName: string) // for nested encrypted fields (not searchable), chainable: .dataType()encryptedTable(tableName: string, columns: Record<string, EncryptedColumn | EncryptedField | nested>)
encryptedColumn(columnName: string) // 支持链式调用: .equality(), .freeTextSearch(), .orderAndRange(), .searchableJson(), .dataType()
encryptedField(valueName: string) // 用于嵌套加密字段(不可搜索),支持链式调用: .dataType()