@alxia/ 租户
申请的租赁 @softistx/mongo数字 :
一份合同, TenantStore,以及背后的两个策略. perOrganization
维持一个核心数据库(账户、组织登记册、跨组织)
每个组织有一个数据库; single 保留一个数据库和一个
固定租户。 请求在标题中标明其组织及其范围
得到的是组织的数据库, 所以解析器从来没有建立数据库
名称和查询无法到达另一个组织的数据.
bun add @alxia/tenancy @alxia/modules @softistx/mongo mongodb
bun add -d typescript
@softistx/mongo 是私人的,在 GitHub 包上。 绘制范围
项目的 bunfig.toml 和导出 GITHUB_TOKEN,与 read:packages数字 :
[install.scopes]
"@softistx" = { url = "https://npm.pkg.github.com/", token = "$GITHUB_TOKEN" }
30秒内
import { connectMongo, defineMongo, openMongo } from '@softistx/mongo';
import { perOrganization } from '@alxia/tenancy';
const { client } = await connectMongo(process.env.MONGO_URI!);
// The host's core database: the registry of organisations, accounts...
const core = await openMongo(
defineMongo({ client, database: 'app_core', collections: coreCollections }),
);
export const tenancy = perOrganization({
core,
registry, // the port over your `organizations` collection, see the guide
database: 'app_org_', // databases are named `app_org_<id>`
configure: (database) =>
defineMongo({ client, database, collections: tenantCollections }),
membership: async (organizationId, accountId) =>
(await roleOf(organizationId, accountId)) ?? null, // null: not a member
});
已经有一个缓存从 createTenantDatabases (与工作和
测试)? 过 databases 改为 database 财务报告和审定财务报表 configure: 仓库
复用并永不闭塞,主机有.
import { createTenantDatabases, perOrganization } from '@alxia/tenancy';
const databases = createTenantDatabases({ database: 'app_org_', configure });
perOrganization({ core, registry, databases, membership });
请求的范围
const scope = await tenancy.scope(request, viewer); // viewer: { id } or null
scope.tenant; // the organisation record
scope.mongo; // ITS database, writes stamped with the viewer
scope.core; // the host's core database
scope.member; // what `membership` answered, a role for instance
它扔一个 TenancyError 带有 code页:1 status 财务报告和审定财务报表 params 映射到
错误: tenancy.errors.organization-required (400,标题缺失),
tenancy.errors.unknown-organization (404) 用于未知、不活动或
非会员组织, 无论是什么组织.
每个请求一次解决范围问题,只有在有东西问:
import { lazyScope } from '@alxia/tenancy';
const getScope = lazyScope(tenancy, request, viewer);
const { mongo } = await getScope(); // the same promise every time
一个模块的手柄
@alxia/modules 是需要的对等点。 这个 modules 选项是可选的 :
没有它, forModule 掷出 module "x" is not registered。 。 。 .
给存储您的注册和插件代码只触及其控件
模块,通过 Register; 它从不命名数据库 :
import { resolveModules } from '@alxia/modules';
const modules = resolveModules(registered);
const tenancy = perOrganization({ /* … */ modules });
const scope = await tenancy.scope(request, viewer);
const { mongo, core } = tenancy.forModule(scope, 'notes');
mongo.db.notes; // the module's tenant collections and its `dependsOn` ones
core.db.accounts; // its `coreCollections`, on the core database
collections 租户数据库中的土地, coreCollections 输入核心
一个; 在 single 存储两者都是相同的数据库. forModule 还要
{ mongo, core: tenancy.core } 租户 eachTenant 把手举起来 它会扔
(单位:千美元) ModuleWiringError 对于一个缺少的模块,或者您的收藏
defineMongo 接线不接 (用 collectionsOf(modules) 财务报告和审定财务报表
coreCollectionsOf(modules)规定, sync 财务报告和审定财务报表 migrate 拿着
从登记册移出migrationsOf, (中文(简体) ), coreMigrationsOf),在
联合国 migrations 财务报告和审定财务报表 coreMigrations。 。 。 .
标题, ID 或 sult
标题是 x-organization-id 以 id 命名组织
或者用弹子,无论如何。 换一个 header数字 :
perOrganization({ /* … */ header: 'x-tenant-id' });
默认导出为 DEFAULT_HEADER。只显示 active 组织决定。 除了请求之外,
tenancy.resolve(ref) 进行同样的检查.
每个租户,一个工作
await tenancy.eachTenant(async (tenant, mongo) => {
await mongo.db.outbox.deleteMany({ sentAt: { $lt: cutoff } });
}); // the active organisations, one after the other
await tenancy.eachTenant(visit, { withDeleted: true }); // every registered one
// keep going when a tenant fails (database unreachable, `fn` throws)
const results = await tenancy.eachTenant(visit, { onError: 'collect' });
const failed = results.filter((r) => !r.ok); // { tenant, ok: false, error }
// the others are { tenant, ok: true, value }
默认第一个失败者拒绝,而后来的租户则不被探视.
提供、同步、迁移
const tenant = await tenancy.provision({ slug: 'north', name: 'North' }, actorId);
// registered as `provisioning`, database synced and migrated, then `active`
// throws a TenancyError `tenancy.errors.slug-taken` (409) when the slug is taken
// a failed `provision` leaves the tenant `provisioning`: fix the cause, then
await tenancy.prepare(tenant.id);
await tenancy.activate(tenant.id, actorId); // `perOrganization`; active tenants are returned as is
await tenancy.sync(); // `db:sync`: the core, then every organisation
await tenancy.migrate({ kind: 'apply', dryRun: false }); // `db:migrate`
await tenancy.migrate({ kind: 'rollback', to: '2026-10-27-marker', dryRun: true });
一个数据库
对于服务于一个组织的东道方,同样的合同没有标题 不检查会员:
import { single } from '@alxia/tenancy';
export const tenancy = single({
mongo, // the one database
tenant: { id: 'main', slug: 'main', name: 'Main' },
migrations,
});
代码写入反对 TenantStore 两家店都一样.
测试数据库
slotNaming 名称数据库 <prefix>_slot1, (中文(简体) ), <prefix>_slot2. 所以一个套房
设立数量有限的组织,尽管它规定许多组织:
import { slotNaming } from '@alxia/tenancy';
const database = slotNaming('app_test', 4);
perOrganization({ /* … */ database });
database.reset(); // between tests
API 密码
| 导出 | 它会做什么 |
|---|---|
perOrganization(options), (中文(简体) ), PerOrganizationOptions, (中文(简体) ), PerOrganizationStore | 每个组织的核心数据库加一个数据库 |
single(options), (中文(简体) ), SingleOptions, (中文(简体) ), SingleStore | 一个数据库,一个固定租户 |
TenantStore | 合同双方都回答: resolve, (中文(简体) ), tenantOf, (中文(简体) ), databaseOf, (中文(简体) ), scope, (中文(简体) ), forModule, (中文(简体) ), eachTenant, (中文(简体) ), provision, (中文(简体) ), prepare, (中文(简体) ), sync, (中文(简体) ), migrate, (中文(简体) ), close |
lazyScope(store, request, viewer) | 请求的范围,在首次使用时解决一次 |
viewerMemberships(membership, viewer), (中文(简体) ), ViewerMemberships | 观众在任何租户中的成员资格,按要求读取一次 |
slotNaming(prefix, slots), (中文(简体) ), SlotNaming, (中文(简体) ), DatabaseNaming | <prefix>_slot<n> 数据库名称和类型 database 选项( 租户代号的前缀或函数) |
createTenantDatabases(options), (中文(简体) ), TenantDatabases, (中文(简体) ), TenantDatabasesOptions, (中文(简体) ), isTenantId, (中文(简体) ), dbOf | 限定的租户缓存 Mongos,对到达数据库名称的id的检查,驱动程序 Db a 项下 Mongo |
ModuleScope, (中文(简体) ), ModuleSource | 什麽 forModule 返回( r)mongo, (中文(简体) ), core并且具有:范围,或 { mongo, core: store.core } 租户 eachTenant 访问 |
TenancyError, (中文(简体) ), TenancyErrorCode, (中文(简体) ), UnknownMigrationError, (中文(简体) ), ModuleWiringError | 拒绝(tenancy.errors.*,见下文)和两个靴子出错,带有代码 tenancy.unknown-migration 财务报告和审定财务报表 tenancy.module-wiring |
organizationRequired(header), (中文(简体) ), unknownOrganization(), (中文(简体) ), slugTaken(slug) | 页:1 TenancyErrors 商店扔出,供主机自己的商店或spec扔出相同的商店 |
DEFAULT_HEADER | 'x-organization-id',在 header 未给出 |
membershipOf(source) | (单位:千美元) Membership 对于一个 spec, 从一个纯函数( 同步或 sync) 或 a { [tenantId]: answer } 地图; undefined (b) 使用一个功能来区分账户 |
noMembers | (单位:千美元) Membership 承认没有人, 对于一个spec 或锁定主机 |
Tenant, (中文(简体) ), TenantStatus, (中文(简体) ), TenantRegistry, (中文(简体) ), TenantResult, (中文(简体) ), EachTenant, (中文(简体) ), Membership, (中文(简体) ), TenantScope, (中文(简体) ), Viewer, (中文(简体) ), Resolution, (中文(简体) ), EachTenantOptions, (中文(简体) ), MigrationLists, (中文(简体) ), MigrateMode, (中文(简体) ), MigrationOutcome | 类型(Viewer 实值 { id }· ; @alxia/access 拥有相同的形状) |
拒绝代码
无法满足的请求 TenancyError 谁 code 实值
tenancy.errors.organization-required (400个), tenancy.errors.unknown-organization
(404)或(404) tenancy.errors.slug-taken (409) (英语). 这些代码保存着 tenancy.errors.
有意命名空间: 它们是已存在主机的 i18n 目录的密钥
翻译,它们符合 x-organization-id 页眉和 perOrganization
名称。 重新校正他们会无益地打破每一译本.
靴子出错( A)tenancy.module-wiring, (中文(简体) ), tenancy.unknown-migration是一个不同的
类型: 开发者, 从未向用户显示, 因此它们使用内核的后缀形状
(单位:千美元)tenancy.<reason> 和一条消息.
端点 : @alxia/modules, (中文(简体) ), @softistx/mongo, (中文(简体) ), mongodb, (中文(简体) ), typescript。 。 。 .
文档
许可证
麻省理工学院