@ 经典/ repo- core
驱动-不可知寄存器原始. 钩子 过滤 IR、 操作注册、 页码、 URL 查询解析、 缓存合同—— 共享基础 @classytic/mongokit, (中文(简体) ), @classytic/sqlitekit,以及未来 @classytic/pgkit 编号 : @classytic/prismakit。 。 。 .
复古核心为 工具包作者的基础设施. 终端用户安装一个工具包(mongokit / sqlitekit),并从该命名空间导入完整的API. Repo-core是每个套件运行时间所基于的——你通常不会直接在应用代码中导入.
设计原则
- 仅限无害环境管理.
.mjs+ 键.d.mts输出。节点22+. - 无根桶. 每个公共地表都是它的子路径
exports。直接从符号所在位置导入;未使用的模块从不输入您解析图. - 零运行时间依赖关系. 没有驱动器导入到任何地方
src/。 。 。 . - 树能被建筑所震动.
unbundle: true在tsdown——1:1 src-dist,没有共享块. - 这里没有插件 . 每个套件都拥有自己的插件执行功能,这样它们就可以使用驱动本地特性(mongoose的内置时间戳, SQLite 触发器, Postgres)
now()普里斯玛@default) (中文(简体) ). Repo-core提供原始人包作曲:CacheAdapter, (中文(简体) ),stableStringify, (中文(简体) ),buildTenantScope, (中文(简体) ),HOOK_PRIORITY,钩状引擎,过滤器IR.
子路径
// Hook engine + priority constants + canonical event names. HOOK_EVENTS turns
// raw strings into compile-checked constants so typos fail at build time.
import { HOOK_EVENTS, HOOK_PRIORITY, HookEngine } from '@classytic/repo-core/hooks';
// Abstract repository base + MinimalRepo / StandardRepo contracts + plugin types.
import { RepositoryBase, type MinimalRepo, type Plugin } from '@classytic/repo-core/repository';
// Driver-agnostic filter AST — combinators (eq/and/or/in/like/between/...) + walk/match + scope helpers.
import { and, eq, gte, in_, like, buildTenantScope, matchFilter } from '@classytic/repo-core/filter';
// URL → ParsedQuery grammar. Backend frameworks (Express/Arc/Fastify) parse req.query here.
import { parseUrl } from '@classytic/repo-core/query-parser';
// Pagination primitives — cursor codec, keyset helpers, offset math, the canonical
// result types (`OffsetPaginationResult`, `KeysetPaginationResult`,
// `AggregatePaginationResult`, `PaginationResult`) and the wire helper
// `toCanonicalList()`. Single source of truth — primitives' duplicate dropped,
// mongokit/sqlitekit re-export from here.
import { encodeCursor, decodeCursor, validateKeysetSort, toCanonicalList } from '@classytic/repo-core/pagination';
import type { OffsetPaginationResult, KeysetPaginationResult, AggregatePaginationResult, PaginationResult } from '@classytic/repo-core/pagination';
// Tenant config — the canonical `TenantConfig`, `TenantStrategy`, `TenantFieldType`,
// `resolveTenantConfig`, `DEFAULT_TENANT_CONFIG`, `ResolvedTenantConfig`. Kits'
// `MultiTenantOptions extends Pick<TenantConfig, ...>`.
import { resolveTenantConfig, DEFAULT_TENANT_CONFIG } from '@classytic/repo-core/tenant';
import type { TenantConfig, ResolvedTenantConfig } from '@classytic/repo-core/tenant';
// Cache plumbing — the CacheAdapter interface every kit's cachePlugin writes against.
import { type CacheAdapter, stableStringify, createMemoryCacheAdapter } from '@classytic/repo-core/cache';
// Error contracts — `HttpError` throwable + `ErrorContract` wire shape +
// `ErrorDetail` + `ErrorCode` + `ERROR_CODES` + `toErrorContract()` +
// `statusToErrorCode()`. Single source of truth — primitives' errors module dropped,
// mongokit's local `HttpError` dropped, `ArcError implements HttpError`.
import { toErrorContract, statusToErrorCode, ERROR_CODES, createError, conservativeMongoIsDuplicateKey } from '@classytic/repo-core/errors';
import type { HttpError, ErrorContract, ErrorDetail, ErrorCode } from '@classytic/repo-core/errors';
// Error kinds: one coarse class per wire code (validation, unauthorized, forbidden,
// not_found, conflict, precondition_failed, rate_limited, unavailable, internal).
// `errorKind(code, status?)` reads the table first, then falls back to the status.
import { errorKind, ERROR_KIND_BY_CODE, type ErrorKind } from '@classytic/repo-core/errors';
// Change feed: the ChangeLogStore contract a sync engine or a replaying SSE stream
// reads, plus the in-memory reference store. See "Change feed for realtime replay".
import { MemoryChangeLogStore, CursorExpiredError, isCursorExpiredError } from '@classytic/repo-core/sync';
import type { ChangeLogStore, ChangeEntry, ChangesSinceOptions, ChangesPage } from '@classytic/repo-core/sync';
// Schema generator interface — kits ship `SchemaGenerator<TModel>` + the
// compile-time conformance assertion; arc adapters are typed against it.
import type { SchemaGenerator, SchemaGeneratorContext } from '@classytic/repo-core/schema';
import { isSchemaGenerator } from '@classytic/repo-core/schema';
// Operation registry (for arc-level policy dispatch + doc generation).
import { CORE_OP_REGISTRY, describe } from '@classytic/repo-core/operations';
// Repository hook context type (for plugin authors).
import type { RepositoryContext } from '@classytic/repo-core/context';
// Capabilities + resilience (0.6.0) — runtime feature detection, the unified
// retry/abort primitives, and the change-feed contract types.
import { withRetry, throwIfAborted } from '@classytic/repo-core/repository';
import type { RepoCapabilities, RetryPolicy, ChangeEvent, WatchOptions } from '@classytic/repo-core/repository';
// Domain events (0.6.0) — pass `events: { transport }` at construction and every
// mutating op publishes `<resource>.<verb>` through any arc-compatible transport.
import type { RepositoryEventPublisher, DomainEvent } from '@classytic/repo-core/events';
// Standard Schema validation (0.6.0) — `schema` / `updateSchema` construction
// options accept any Zod / Valibot / ArkType / Effect schema.
import { validateStandardSchema, type StandardSchemaV1 } from '@classytic/repo-core/schema';
没有 . / 根条目. 从确切的子路径导入——这就是保持树摇的诚实的合同.
示例:工具包的仓库扩展 RepositoryBase
import { RepositoryBase } from '@classytic/repo-core/repository';
import { compileFilter } from './my-kit-compiler.js';
export class MyKitRepository<T> extends RepositoryBase {
// Kit-specific CRUD methods. Each routes through `_buildContext` → plugins
// → native driver call → `_emitAfter` so every plugin (timestamp, cache,
// multi-tenant, audit) composes identically across kits.
async getAll(params: {...}) {
const context = await this._buildContext('getAll', params);
const cached = this._cachedValue(context);
if (cached) return cached;
const where = compileFilter(context.filters);
const result = await this.driver.query(where);
await this._emitAfter('getAll', context, result);
return result;
}
}
示例: URL – 解析查询 – 任何工具包
每个工具包的URL语法都是一样的. 前端释放出一个 URL; 交换后端 DB 不改变单个查询字符串.
import { parseUrl } from '@classytic/repo-core/query-parser';
app.get('/users', async (req, res) => {
const parsed = parseUrl(req.query, {
allowedFilterFields: ['email', 'role', 'active', 'createdAt'],
fieldTypes: { active: 'boolean', createdAt: 'date' },
maxLimit: 200,
});
// Works identically against any kit's repository:
// const page = await sqliteUserRepo.getAll(parsed);
// const page = await mongoUserRepo.getAll(parsed);
res.json(await userRepo.getAll(parsed));
});
URL 语法 :
| 網址 | 已生产过滤器IR |
|---|---|
?status=active | eq('status', 'active') |
?age[gte]=18&age[lt]=65 | and(gte('age', 18), lt('age', 65)) |
?role[in]=admin,editor | in_('role', ['admin', 'editor']) |
?name[contains]=john | like('name', '%john%') |
?price[between]=10,100 | and(gte(...), lte(...)) |
?deletedAt[exists]=false | isNull('deletedAt') |
?sort=-createdAt,name | { createdAt: -1, name: 1 } 联 合 国 .sort(中文(简体) ) |
?select=name,email,-password | { name: 1, email: 1, password: 0 } |
?page=2&limit=50 | 解析查询上的页形字段 |
?after=eyJ2Ij... | 不透明的密钥集光标 |
方便插件的事件名称
import { HOOK_EVENTS, HOOK_PRIORITY } from '@classytic/repo-core/hooks';
// Cross-kit plugin — works identically on mongokit, sqlitekit, pgkit, prismakit.
export function stampOrgId(orgId: string): Plugin {
return {
name: 'stamp-org-id',
apply(repo) {
repo.on(HOOK_EVENTS.BEFORE_CREATE, (ctx) => {
if (!ctx.data?.organizationId) ctx.data = { ...ctx.data, organizationId: orgId };
}, { priority: HOOK_PRIORITY.POLICY });
},
};
}
泰波斯喜欢 'before:craete' 成为编译错误。 订阅给定的套件不会放出的事件是无声的不放出(这是钩子引擎如何工作的),所以插件可以安全地为完整的标准集接线收听器.
能力——特征检测,而非运行时出乎意料
每个工具箱声明 readonly capabilities: RepoCapabilities (必须
StandardRepo 从0.6.0起。 手提式主机分支在启动时一次:
if (!repo.capabilities.arrayOperators) {
// SQL kit without JSON array rewrites — model tags as a join table
}
if (repo.capabilities.aggregateOps?.percentile) {
dashboard.enableLatencyPercentiles();
}
那套套间 ConformanceFeatures 是同一类别名——
一个工具箱在运行时宣布的 恰恰是交叉设备组的验证.
配置驱动验证+事件(0.6.0)
RepositoryBaseOptions 遵循媒体 kit 的配置驱动激活: 通过
插槽和特性点亮; 省去它和接线是惰性的.
import { z } from 'zod';
const repo = createRepository(UserModel, {
// Any Standard Schema validator — Zod, Valibot, ArkType, Effect.
schema: z.object({ name: z.string().min(1), email: z.string().email() }),
updateSchema: z.object({ name: z.string().min(1) }).partial(),
// Any arc / primitives-compatible EventTransport. Every mutating op then
// publishes `user.created` / `user.updated` / `user.deleted` / ...
events: { transport: redisTransport, source: 'commerce' },
});
验证运行于 HOOK_PRIORITY.VALIDATION (150) — 政策插件之后
(有租户印出字段),缓存前. 事件出版从未
操作失败; 向 error:events 钩.
警告 - 弧形主机 : 这些是相同的事件名称
@classytic/arc是因为eventStrategy: 'auto'发射 连接回波级桥和弧 用于一个资源双publishes的自动事件无声( arc 双publish) dev-warn 无法看到 repo 层). 每个资源选择一层.
更改实时重播的种子
@classytic/repo-core/sync 出口 ChangeLogStore 合同:仅是附件、承诺命令的文档更改种子。 同步引擎拉动它;服务器发送事件流可以重放它,所以重新连接的客户端 Last-Event-ID (条目: cursor)在光标后获得每个更改,因为每个文档的最新状态如果种子是压缩的. MemoryChangeLogStore 是参考商店 mongokit 的 createChangeLogStore 是一个持久的。 证明一个商店 runChangeLogStoreConformance 从 @classytic/repo-core/testing。 。 。 .
每家店都遵守四条规则 如果有五条规则
- 咒语很不透明,每个店都按顺序订购 绝不允许检查站经过后来实施的入境.
append(entry, { session })加入呼叫者的交易, 因此种子条目会以商务写入 .- 删除为墓碑(
op: 'delete',没有doc),永无间隙. since(cursor)不包含cursor(单位:千美元)''表示从开始)并按光标顺序返回条目; 页时hasMore,呼应返回cursor。 。 。 .- 执行可选的商店
prune(before)保存每个文档的最新状态并返回新视野;光标比它老CursorExpiredError。 。 。 .
import { isCursorExpiredError, type ChangeLogStore, type ChangesSinceOptions } from '@classytic/repo-core/sync';
// MemoryChangeLogStore and mongokit only: 16-digit cursors, or '' for "from the beginning".
const CURSOR = /^(\d{16})?$/;
type Read = Pick<ChangesSinceOptions, 'tenantId' | 'scopes' | 'sharedScopes'>;
// One pass: every entry after `from`, in cursor order. Call it on connect with Last-Event-ID,
// then on every wake-up (a timer, or a "feed changed" ping) with the cursor it returned.
// The feed is the only source of events, so the hand-off from replay to live has no gap.
async function pump(store: ChangeLogStore, from: string, read: Read, send: (e: unknown) => void) {
// A malformed cursor, or one ahead of the head (a restored DB, a restarted memory store, a
// forged id), resyncs: echoing it back would replay nothing until the sequence caught up.
if (!CURSOR.test(from) || from > (await store.latestCursor())) return { resync: true } as const;
let cursor = from;
try {
for (;;) {
const page = await store.since(cursor, { ...read, limit: 500 });
for (const entry of page.changes) send(entry); // SSE `id:` = entry.cursor
cursor = page.cursor;
if (!page.hasMore) return { cursor } as const;
}
} catch (err) {
if (isCursorExpiredError(err)) return { resync: true } as const;
throw err; // a failure, not a resync: see "Errors" below
}
}
- 谁能读出什么. 构建
read在服务器上,从不从请求。 租户读取tenantId从认证范围和 未关闭 当没有的时候:since与tenantId: undefined返回两个商店中每个租户的条目,所以一个没有被选中的组织的用户会流出每个人的数据.scopes财务报告和审定财务报表sharedScopes缩放sharedScopes增加其他租户在这些范围中的条目,所以它们来自服务器配置或服务器侧允许列表. 平台阅读器( 否)tenantId)是一个单独的,有明确入门的分支,如提升或平台范围. - Resync. (原始内容存档于2018-10-12). 带着头
latestCursor(read)在此之前 客户端重新装入其快照并将其作为新的id:; 继续从中可以重复快照已有的条目,这是安全的(下个点)。 抓取头部 在快照丢失的条目之间.CursorExpiredError(与isCursorExpiredError,该匹配name,因此它能幸存两个再录核拷贝在一个图中),而上面的检查失败的光标就是再录信号. - 至少从头开始. 通过再连接和再同步,客户端可以两次看到一个条目. 应用条目
(scope, docId, version)忽视谁version并不比持有的要新. - 现场直播. 如果中枢从feed以外的地方推动直播事件(arc事件,一个酒吧/子消息),它们不会携带feed光标和SSE
id:无法保持一致。 民意调查since作为活源(上),或订阅活的种子条目,先缓冲,再播放,再放下缓冲条目cursor实值<=最后一个重播. - 错误. 任何其他
since错误是失败: 在服务器上登录原因, 发送固定的{ code, message }(例如internal_error并关闭。 永远不要回声错误 :CursorExpiredError而mongokit的光标错误嵌入了客户端的生光标,驱动程序错误可以命名收藏,索引和查询形状. - 固定宽光标.
MemoryChangeLogStore和mongokit薄荷 16位零加序号,所以CURSOR而简单的字符串比较对他们是有用的。 其他商店可能不会:将光标视为不透明,除非您知道该商店. 比起未过滤器latestCursor(): 一个被过滤后的头可以坐在光标后面 asharedScopes读者合法持有. - 不要在记忆和梦幻中磨蹭.
MemoryChangeLogStore没有prune,所以它从来没有扔CursorExpiredError;用于测试和dev,而不是证明再同步路径. 它的序列随过程以零重新开始. Mongokit的店铺契约compactSuperseded(没有地平线)并且也没有抛出它,所以读者也应该封住它回放多远. - 最新状态,不是每个过渡. 之后
prune(或梦幻怪兽)compactSuperseded) 重放输出每个文档的最新条目。 需要每一个过渡的读者决不能缩小这一范围.
输入结果额外
import type { OffsetPaginationResult } from '@classytic/repo-core/pagination';
// Kit adds a typed surface-level extra without breaking cross-kit substitutability.
type MongokitPage<T> = OffsetPaginationResult<T, { warning?: string }>;
function render(page: MongokitPage<User>) {
if (page.warning) showBanner(page.warning);
return page.docs.map(userRow);
}
默认 TExtra 实值 Record<string, never> —— 说 OffsetPaginationResult<User> 前后行为相同; 通用可随意忽略.
状态
v. 6.0 — 标准化发布. 需求 RepoCapabilities 特性
检测( 与 ConformanceFeatures),标准Schema验证
插槽、域活性排放(/events, (中文(简体) ), watch() 换食合同,
统一 RetryPolicy + 键 signal 取消,和 recordToFilter
从每个孩子的拷贝中晋升.
由下列人士组成:
@classytic/mongokit3.16 —Repository extends RepositoryBase; 声明MONGOKIT_CAPABILITIES; 执行watch()通过更改流; 钩子引擎, 插件顺序验证器,HOOK_PRIORITY,页码+HttpError键入所有源代码。 蒙哥克特自己来的QueryParser保持独立.@classytic/sqlitekit≥0.6 -SqliteRepository extends RepositoryBase; 声明SQLITEKIT_CAPABILITIES; Filter IR 编译为 Drizzle / 原始 SQL 本土;recordToFilter从这里消耗.@classytic/arc2.12 - 适配器对准SchemaGenerator<TModel>· ;ArcError implements HttpError; 页形接线信封(method歧视者)toCanonicalList()与reply.sendList()。 。 。 .
见 INFRA.md (英语) 用于建筑原则、子路径图、建造/工具决定和pgkit/棱镜路线图.
见 文件/数据寿命周期.md 10亿册——保留/TTL, archiveByFilter 冷藏取出, cursor() 流线扫描、房客清洗、分配钥匙警卫和每后端的硬化/分块/后置游戏本(工具包拥有的与数据库拥有的).
许可证
麻省理工学院——见 自由主义。 。 。 .
商标
密码是麻省理工学院许可的. "classytic","arc",并标志为: 经典专卖局长 并且现在 没有 根据麻省理工学院颁发的许可证——见 贸易商。 。 。 . 叉子必须更名;许可证覆盖了代码,而不是品牌.