api-design

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

API Design

API设计

Use this skill before implementation or when reviewing a public API surface. Target explicit types, stable semantics, and no irrelevant internals in the public contract.
If the library is a Nitro Module, use this skill for the public TypeScript and React API shape first, then use
build-nitro-modules
for Nitro-specific spec, native-state, and binding constraints. If the library is JS-only, React-only, or React Native JS-only, stay in this skill.
在实现前或审查公共API表面时使用此技能。目标是明确的类型、稳定的语义,且公共契约中不包含无关的内部实现。
如果库是Nitro Module,请先使用此技能定义公共TypeScript和React API结构,再使用
build-nitro-modules
处理Nitro特定的规范、原生状态和绑定约束。如果库是纯JavaScript、仅React或仅React Native JavaScript库,则仅使用此技能即可。

Workflow

工作流程

  1. Sketch the user-facing TypeScript API before implementing internals.
  2. Write 2-3 realistic call-site examples, including error and cleanup paths.
  3. Check the surface against the rules below.
  4. Verify the exported TypeScript with the repo's typecheck/lint/docs tooling before treating the API as done.
  5. Implement only after the public shape is coherent and verified.
  1. 在实现内部逻辑之前,草拟面向用户的TypeScript API。
  2. 编写2-3个贴合实际的调用示例,包括错误处理和清理流程。
  3. 根据以下规则检查API表面。
  4. 在将API视为完成之前,使用仓库的类型检查/代码检查/文档工具验证导出的TypeScript代码。
  5. 仅在公共结构连贯且通过验证后再进行实现。

API Freshness

API时效性

Before choosing public API shape, dependency APIs, platform capabilities, or implementation strategy, verify current official sources instead of relying on trained memory. Library, React, React Native, platform, and tooling APIs evolve quickly.
  • Prefer official docs, source repositories, release notes, changelogs, package READMEs, and current package metadata.
  • Look for
    llms.txt
    or
    llms-full.txt
    on official docs sites when available, and use those as compact current context.
  • Treat remembered API details as a starting hypothesis only. If current docs or source disagree, follow the current docs/source and mention the change when relevant.
  • Avoid designing against stale blog posts, old snippets, or outdated trained assumptions when an official current source is available.
在选择公共API结构、依赖API、平台能力或实现策略之前,请验证当前的官方来源,而非依赖已训练的记忆。库、React、React Native、平台和工具API的更新速度很快。
  • 优先参考官方文档、源代码仓库、发布说明、变更日志、包README和当前包元数据。
  • 如果官方文档站点提供
    llms.txt
    llms-full.txt
    ,请使用这些文件作为简洁的当前上下文。
  • 仅将记忆中的API细节作为初始假设。如果当前文档或源代码与之不符,请遵循当前文档/源代码,并在相关时提及变更。
  • 当有官方当前来源可用时,避免基于过时的博客文章、旧代码片段或已过时的训练假设进行设计。

API Shape Rules

API结构规则

  • Prefer a single source of truth. Do not split related state across booleans and dependent values when one typed value can express the state. Prefer
    timeoutMs?: number
    over
    enableTimeout: boolean
    plus
    timeoutMs?: number
    .
  • Use option objects or named structs once a function has 3 or more parameters, parameters of the same primitive type, or values that are likely to grow.
  • Keep APIs specific instead of accepting every possible input shape. A millisecond timeout should be a
    number
    , not
    number | string | bigint | object | null
    .
  • Avoid giant "does everything" objects. Split by domain or lifecycle when responsibilities differ.
  • Before simplifying or redesigning an API, inventory the workflows the feature is supposed to support. Do not silently drop a workflow, such as a live session API, because a one-shot path is easier to implement. Split workflows into separate APIs when needed.
  • Prefer literal unions, discriminated unions, interfaces, and typed option groups when the valid states are known. In TypeScript libraries, prefer string literal unions over runtime
    enum
    s unless consumers need a runtime value.
  • Avoid untyped dictionaries, boolean clusters, stringly typed commands, and loosely shaped events when the valid states are known.
  • Do not model a binary option as an optional two-case string union such as
    'enabled' | 'disabled'
    . If omitted means "use the default" and provided means true/false, use an optional boolean and document the default.
  • Do not represent multiple object states as one interface full of optional fields. Use a discriminated union, inheritance, or separate variant interfaces so impossible field combinations are unrepresentable.
  • Keep related fields together on the variant where they are required. If
    barcode
    and
    barcodeType
    only make sense together, both should be nonoptional on
    ScannedBarcode
    , not optional on a generic
    ScannedData
    .
  • If you create variant interfaces, use them in the actual public type. Do not define
    RecognizedTextDataType
    and
    RecognizedBarcodeDataType
    but keep
    recognizedDataTypes: RecognizedDataType[]
    where
    RecognizedDataType
    still contains every variant field as optional.
  • Use
    undefined
    or optional fields for absence. Use
    null
    only when "explicit none" means something different from "not provided".
  • Prefer discriminated unions for state machines, loading states, and result variants.
  • Model user intent separately from resolved state when negotiation is involved. For example, an ordered array of constraint objects can express priorities, while a resolved config object reports what the platform actually selected.
  • For complex negotiation, prefer ranked constraints or preference objects over exposing a combinatorial support matrix. Let callers describe intent, resolve the closest working configuration internally, and expose the resolved configuration through a return value, callback, or explicit resolver method.
  • Expose common presets as
    as const satisfies Record<string, Type>
    objects. Keep the accepted type structural so users can provide their own values.
  • Do not expose ambient facts the caller already knows, such as a
    platform
    field that only repeats
    Platform.OS
    , unless the API can return data produced by a different platform than the current runtime.
  • Do not freeze today's platform support matrix into the type shape. Prefer runtime capability fields such as
    availableTextTypes: []
    or
    supportedFormats: []
    over separate platform-specific types or static exclusions. This lets newer native capabilities become available without redesigning the JS API.
  • Prefer one unified options object. Avoid
    ios
    /
    android
    option bags and platform-prefixed methods unless the concepts are genuinely platform-only and cannot be described as a cross-platform capability or no-op.
  • Classify configuration fields as either requirements or preferences. Throw when a requirement cannot be met. Treat preferences as best-effort when the feature can still perform its core job, such as quality, guidance UI, high-frame-rate tracking, auto-zoom, or a wider scan area. Document best-effort fields and expose capabilities or resolved configuration when callers need to know what was applied.
  • Split by stable semantic capability when capabilities have different options, results, or futures. For example, barcode scanning and text scanning may deserve separate
    BarcodeScanner
    and
    TextScanner
    APIs even when one native API happens to implement both today. A platform that lacks text scanning should fail
    createTextScanner()
    or report
    isTextScannerAvailable: false
    , not force text-specific fields to be nullable on a generic scanner used for barcode scanning.
  • Runtime availability should describe whether a stable capability exists today, not permanently restrict the API shape to the current platform matrix. If Android gains text scanning later, the existing
    TextScanner
    capability should become available without changing barcode APIs or broadening nullable result types.
  • Keep capability discovery separate from object contracts. Use capability fields to decide whether a workflow can be created or which optional preferences may apply. Once a factory returns a specific session/resource object, its baseline methods should be guaranteed by construction; otherwise return a narrower type or fail creation instead of making callers check
    can*
    before every normal method.
  • Splitting workflows should reduce runtime capability checks. Do not keep broad capability flags only to compensate for one oversized object. For example, a one-shot scanner and an app-owned live scanner can be separate APIs; if the live scanner implementation guarantees zoom, photo capture, or region control, those can be part of the live scanner contract instead of nullable properties plus
    can*
    checks inherited from a one-shot backend.
  • Encode lifecycle transitions in the API object graph. If commands are valid only after
    configure
    ,
    connect
    ,
    start
    , or another lifecycle transition, expose those commands on a handle returned by that transition, not on the parent object with "maybe active" checks. For example,
    session.configure(device, outputs)
    can return a
    Controller
    that owns
    setZoom(...)
    and
    focusTo(...)
    ; the
    Session
    owns graph configuration and start/stop.
  • Avoid stale-state APIs. If reconfiguration changes the native resource a command targets, return a new handle and invalidate or dispose the old one. Callers should not be able to accidentally call a command on a parent object that no longer knows which configured device/output it applies to.
  • Use callbacks or returned resolved objects for post-negotiation facts. If an output, controller, or session config becomes meaningful only after connection, provide
    onConfigured
    , a returned controller/config, or an explicit
    resolve...(...)
    method rather than forcing callers to poll nullable properties.
  • Avoid pushing defaults into implementation-facing option objects when a thin TypeScript facade can normalize them. Public wrappers can accept ergonomic optional options, but the core API should receive resolved values when that avoids native optional handling, repeated default branches, or extra bridging work.
  • When "all" is a meaningful requested value, model it explicitly instead of using
    undefined
    as a hidden command. For example, prefer
    targetFormats: 'all' | BarcodeFormat[]
    or
    TargetBarcodeFormat = BarcodeFormat | 'all'
    when the implementation benefits from a concrete value.
  • Do not return half-initialized objects that require a separate
    prepare()
    ,
    initialize()
    , or
    load()
    call before normal use. If setup is required, make the factory async and resolve with a ready object. Keep lifecycle methods for real repeatable transitions such as
    start()
    /
    stop()
    , not construction readiness.
  • For larger libraries, expose one small public root or factory that creates stateful domain objects. Keep object construction, async setup, I/O, and validation behind factory methods instead of forcing callers through static functions or half-ready instances.
  • Return
    undefined
    only for normal domain absence, and document exactly when it occurs. Do not use optional returns as an unstated error path; throw or reject when an operation fails.
  • Decide whether returned data is a plain value or a resource. Use plain structs/interfaces for small immutable data whose fields are cheap and semantically complete. Use classes/objects/resources when the value owns native state, needs lazy expensive access, can grow behavior, or should expose methods later.
  • Choose data representations by semantics first, then performance. Use
    string
    for decoded text payloads. Use
    ArrayBuffer
    or byte-oriented objects for raw binary, opaque bytes, media, or large data where zero-copy access is part of the contract.
  • 优先使用单一数据源。当一个类型化的值可以表达状态时,不要将相关状态拆分到布尔值和依赖值中。优先使用
    timeoutMs?: number
    而非
    enableTimeout: boolean
    搭配
    timeoutMs?: number
  • 当函数有3个或更多参数、相同原始类型的参数,或值可能会扩展时,使用选项对象或命名结构体。
  • 保持API的特定性,不要接受所有可能的输入形状。毫秒级超时应该是
    number
    类型,而非
    number | string | bigint | object | null
  • 避免庞大的“全能”对象。当职责不同时,按领域或生命周期拆分。
  • 在简化或重新设计API之前,先梳理该功能应支持的工作流程。不要因为一次性路径更容易实现就悄悄舍弃某个工作流程(例如实时会话API)。必要时将工作流程拆分为独立的API。
  • 当已知有效状态时,优先使用字面量联合、可辨识联合、接口和类型化选项组。在TypeScript库中,优先使用字符串字面量联合而非运行时
    enum
    ,除非消费者需要运行时值。
  • 当已知有效状态时,避免无类型字典、布尔集群、字符串化命令和松散形状的事件。
  • 不要将二元选项建模为可选的双案例字符串联合,如
    'enabled' | 'disabled'
    。如果省略表示“使用默认值”,提供值表示真/假,则使用可选布尔值并记录默认值。
  • 不要将多个对象状态表示为一个充满可选字段的接口。使用可辨识联合、继承或单独的变体接口,使不可能的字段组合无法被表示。
  • 将相关字段放在需要它们的变体中。如果
    barcode
    barcodeType
    只有在一起才有意义,那么两者都应该是
    ScannedBarcode
    中的必填字段,而非通用
    ScannedData
    中的可选字段。
  • 如果创建了变体接口,请在实际的公共类型中使用它们。不要定义
    RecognizedTextDataType
    RecognizedBarcodeDataType
    ,却保留
    recognizedDataTypes: RecognizedDataType[]
    ,其中
    RecognizedDataType
    仍然包含每个变体的所有可选字段。
  • 使用
    undefined
    或可选字段表示缺失。仅当“显式无”与“未提供”含义不同时才使用
    null
  • 对于状态机、加载状态和结果变体,优先使用可辨识联合。
  • 当涉及协商时,将用户意图与解析后的状态分开建模。例如,约束对象的有序数组可以表达优先级,而解析后的配置对象则报告平台实际选择的内容。
  • 对于复杂的协商,优先使用排序后的约束或偏好对象,而非暴露组合式支持矩阵。让调用者描述意图,在内部解析最接近的可行配置,并通过返回值、回调或显式解析器方法暴露解析后的配置。
  • 将常见预设暴露为
    as const satisfies Record<string, Type>
    对象。保持接受的类型为结构化类型,以便用户可以提供自己的值。
  • 不要暴露调用者已经知道的环境信息,例如仅重复
    Platform.OS
    platform
    字段,除非API可以返回当前运行时以外的其他平台生成的数据。
  • 不要将当前的平台支持矩阵固化到类型结构中。优先使用运行时能力字段,如
    availableTextTypes: []
    supportedFormats: []
    ,而非单独的平台特定类型或静态排除项。这样无需重新设计JS API,就能支持新的原生能力。
  • 优先使用统一的选项对象。避免
    ios
    /
    android
    选项包和平台前缀方法,除非这些概念确实是平台专属的,无法描述为跨平台能力或空操作。
  • 将配置字段分类为要求或偏好。当要求无法满足时抛出错误。当功能仍能完成其核心工作时,将偏好视为尽力而为的选项,例如质量、引导UI、高帧率跟踪、自动缩放或更宽的扫描区域。记录尽力而为的字段,并在调用者需要了解应用了哪些配置时暴露能力或解析后的配置。
  • 当能力具有不同的选项、结果或未来发展方向时,按稳定的语义能力拆分。例如,条形码扫描和文本扫描可能需要单独的
    BarcodeScanner
    TextScanner
    API,即使当前某个原生API同时实现了这两者。不支持文本扫描的平台应使
    createTextScanner()
    失败或报告
    isTextScannerAvailable: false
    ,而非强制文本特定字段在用于条形码扫描的通用扫描器上为可空类型。
  • 运行时可用性应描述当前是否存在稳定的能力,而非永久将API结构限制在当前平台矩阵中。如果Android后来支持文本扫描,现有的
    TextScanner
    能力应该变得可用,而无需更改条形码API或扩大可空结果类型。
  • 将能力发现与对象契约分开。使用能力字段来决定是否可以创建工作流程,或哪些可选偏好可能适用。一旦工厂返回特定的会话/资源对象,其基线方法应通过构造保证可用;否则返回更窄的类型或创建失败,而非让调用者在每次调用常规方法前检查
    can*
  • 拆分工作流程应减少运行时能力检查。不要仅为了弥补一个过大的对象而保留宽泛的能力标志。例如,一次性扫描器和应用拥有的实时扫描器可以是独立的API;如果实时扫描器的实现保证了缩放、照片捕获或区域控制,这些可以成为实时扫描器契约的一部分,而非继承自一次性后端的可空属性加
    can*
    检查。
  • 在API对象图中编码生命周期转换。如果命令仅在
    configure
    connect
    start
    或其他生命周期转换后才有效,则在该转换返回的句柄上暴露这些命令,而非在父对象上进行“可能处于活动状态”的检查。例如,
    session.configure(device, outputs)
    可以返回拥有
    setZoom(...)
    focusTo(...)
    Controller
    Session
    负责图配置和启动/停止。
  • 避免过时状态API。如果重新配置更改了命令指向的原生资源,请返回新的句柄并使旧句柄失效或销毁。调用者不应意外调用父对象上的命令,而该父对象已不再知道它适用于哪个已配置的设备/输出。
  • 使用回调或返回的解析对象获取协商后的事实。如果输出、控制器或会话配置仅在连接后才有意义,请提供
    onConfigured
    、返回的控制器/配置或显式的
    resolve...(...)
    方法,而非强制调用者轮询可空属性。
  • 当轻量级TypeScript门面可以规范化默认值时,不要将默认值推入面向实现的选项对象。公共包装器可以接受符合人体工程学的可选选项,但核心API应接收解析后的值,以避免原生可选处理、重复的默认分支或额外的桥接工作。
  • 当“全部”是有意义的请求值时,显式建模它,而非使用
    undefined
    作为隐藏命令。例如,当实现受益于具体值时,优先使用
    targetFormats: 'all' | BarcodeFormat[]
    TargetBarcodeFormat = BarcodeFormat | 'all'
  • 不要返回需要单独调用
    prepare()
    initialize()
    load()
    才能正常使用的半初始化对象。如果需要设置,请使工厂为异步函数,并返回已就绪的对象。将生命周期方法用于真正可重复的转换,如
    start()
    /
    stop()
    ,而非构造就绪状态。
  • 对于较大的库,暴露一个小型的公共根或工厂来创建有状态的领域对象。将对象构造、异步设置、I/O和验证放在工厂方法后面,而非强制调用者通过静态函数或半就绪实例进行操作。
  • 仅在正常领域缺失时返回
    undefined
    ,并准确记录其发生的场景。不要将可选返回作为未声明的错误路径;当操作失败时抛出错误或拒绝Promise。
  • 决定返回的数据是普通值还是资源。对于字段成本低且语义完整的小型不可变数据,使用普通结构体/接口。当值拥有原生状态、需要延迟的昂贵访问、可以扩展行为或以后应暴露方法时,使用类/对象/资源。
  • 优先根据语义选择数据表示,然后考虑性能。对解码后的文本负载使用
    string
    。对于原始二进制、不透明字节、媒体或需要零拷贝访问的大数据,使用
    ArrayBuffer
    或面向字节的对象。

Variant Example

变体示例

Avoid nullable clusters when an object can be in several distinct states:
typescript
interface ScannedData {
  position: Point
  text?: string
  barcode?: string
  barcodeType?: BarcodeType
  face?: Rect
}
Prefer a base type plus variants with nonoptional state-specific fields:
typescript
interface ScannedData {
  position: Point
}

interface ScannedText extends ScannedData {
  text: string
}

interface ScannedBarcode extends ScannedData {
  barcode: string
  barcodeType: BarcodeType
}

interface ScannedFace extends ScannedData {
  face: Rect
}

type ScannedResult = ScannedText | ScannedBarcode | ScannedFace
当对象可以处于几种不同状态时,避免使用可空集群:
typescript
interface ScannedData {
  position: Point
  text?: string
  barcode?: string
  barcodeType?: BarcodeType
  face?: Rect
}
优先使用基类型加带有必填状态特定字段的变体:
typescript
interface ScannedData {
  position: Point
}

interface ScannedText extends ScannedData {
  text: string
}

interface ScannedBarcode extends ScannedData {
  barcode: string
  barcodeType: BarcodeType
}

interface ScannedFace extends ScannedData {
  face: Rect
}

type ScannedResult = ScannedText | ScannedBarcode | ScannedFace

Public API Organization

公共API组织

  • Split public surfaces into focused files or modules and re-export them from a clear package entry point. Do not create catch-all files that contain a feature's main object plus every enum, option, result, event, and helper type.
  • Default to one exported public type per file. Group multiple exported types in one file only when they form one tightly coupled logical construct and are rarely imported independently, such as a
    DynamicRange
    type plus the exact literal unions that define it.
  • Re-export public package types only from package entry points such as
    src/index.ts
    . Do not make feature/spec/type files re-export unrelated types from the same folder.
  • Use direct re-export syntax at package entry points instead of importing just to export again. Use
    export type { Foo } from './Foo'
    for type-only symbols and
    export { Foo } from './Foo'
    for runtime values.
  • A 600-line type/spec file is not acceptable when the types can be split by concept, such as barcode formats, scanned values, configuration, capabilities, and subscriptions.
  • Put shared fields and methods on a base interface or class. Subtypes should add only the members that are specific to that subtype, not repeat fields such as
    format
    ,
    valueType
    ,
    id
    , or
    bounds
    across every concrete variant.
  • Put helpers and conversions on the smallest meaningful receiver: if a conversion only reads one element, put it on that element even when it returns zero, one, or many target values. Compose arrays with normal
    map
    ,
    flatMap
    ,
    reduce
    , or
    Set(...)
    at the call site.
  • Add collection-level helpers only when the collection itself has domain semantics, such as validation across elements, deduplication, ordering guarantees, caching, batching for performance, nonempty checks, or error aggregation. Do not add a collection helper merely to hide one standard collection operation.
  • Keep performance and implementation strategy out of the public shape unless it changes how the caller should use the API. For example, the API may expose an explicit conversion method, but names and docs should not advertise internal details like "lazy", "lightweight", or "normalized" unless that behavior is directly observable.
  • 将公共表面拆分为聚焦的文件或模块,并从清晰的包入口点重新导出。不要创建包含功能主对象以及所有枚举、选项、结果、事件和辅助类型的全能文件。
  • 默认每个文件导出一个公共类型。仅当多个导出类型形成一个紧密耦合的逻辑结构且很少被独立导入时,才将它们放在一个文件中,例如
    DynamicRange
    类型加上定义它的精确字面量联合。
  • 仅从包入口点(如
    src/index.ts
    )重新导出公共包类型。不要让功能/规范/类型文件重新导出同一文件夹中的无关类型。
  • 在包入口点使用直接重新导出语法,而非导入后再导出。对仅类型符号使用
    export type { Foo } from './Foo'
    ,对运行时值使用
    export { Foo } from './Foo'
  • 当类型可以按概念拆分时,600行的类型/规范文件是不可接受的,例如条形码格式、扫描值、配置、能力和订阅。
  • 将共享字段和方法放在基接口或类上。子类型应仅添加特定于该子类型的成员,而非在每个具体变体中重复
    format
    valueType
    id
    bounds
    等字段。
  • 将辅助函数和转换放在最小的有意义接收者上:如果转换仅读取一个元素,则将其放在该元素上,即使它返回零个、一个或多个目标值。在调用点使用普通的
    map
    flatMap
    reduce
    Set(...)
    组合数组。
  • 仅当集合本身具有领域语义时才添加集合级辅助函数,例如元素验证、去重、排序保证、缓存、性能批处理、非空检查或错误聚合。不要仅仅为了隐藏一个标准集合操作而添加集合辅助函数。
  • 除非性能和实现策略会改变调用者使用API的方式,否则不要将它们暴露在公共结构中。例如,API可以暴露显式的转换方法,但名称和文档不应宣传内部细节,如“lazy”、“lightweight”或“normalized”,除非该行为是可直接观察的。

Names and Members

命名与成员

  • Name commands with a verb and subject. Add unit suffixes to numeric values. Prefer
    getCurrentSystemTimestampMs()
    over
    timestamp()
    .
  • Name booleans by their role, not just by their type.
  • Use predicate names such as
    is*
    ,
    has*
    ,
    can*
    , or
    supports*
    for booleans that reflect observed state, capability, or resolved configuration. Examples:
    isFlashEnabled
    ,
    isFlashAvailable
    ,
    hasFlash
    ,
    supportsFlash
    .
  • Use command or preference names such as
    enable*
    ,
    allow*
    , or domain-specific verbs for boolean options that control future behavior. Examples:
    enableFlash
    ,
    allowCellularAccess
    ,
    preferHighAccuracy
    .
  • In React and React Native public APIs, reserve
    use*
    names for hooks. Outside React contexts,
    use*
    can be acceptable only when it reads naturally in the local domain and cannot be confused with a hook.
  • Avoid
    is*Enabled
    for input preferences when the feature is not enabled yet.
    enableGuidance?: boolean
    reads as a requested setting;
    isGuidanceEnabled: boolean
    reads as resolved state.
  • Use properties for cheap observed state or capability. Prefer
    readonly isAccelerometerAvailable: boolean
    over
    accelerometerAvailable()
    .
  • Use methods for side effects, expensive work, allocation, mutation, async boundaries, or operations that can fail.
  • Make units explicit in names:
    timeoutMs
    ,
    byteSize
    ,
    maxRetries
    ,
    createdAt
    .
  • Avoid mechanically prefixing every exported type with the package, module, or feature name. Imports and package namespaces already provide context, and callers can alias names when needed. Use prefixes only when the unprefixed name is ambiguous at package-root import sites; prefer domain names like
    CameraPermissionStatus
    ,
    ScannedResultSource
    ,
    Point
    , or
    Rect
    over repeating
    DataScanner
    on every type.
  • Use
    readonly
    for public state the caller observes but does not set. Use mutable properties only when assigning the property is itself the intended command.
  • Writable properties must be cheap, synchronous, and unlikely to fail. If setting a value needs native negotiation, permissions, I/O, allocation, fallible validation, or thread/process hops, expose an explicit async method such as
    setZoomFactor(zoomFactor): Promise<void>
    .
  • For public string literal unions, prefer lowercase kebab-case values such as
    manual-input
    over camelCase values such as
    manualInput
    .
  • 使用动词加主语命名命令。为数值添加单位后缀。优先使用
    getCurrentSystemTimestampMs()
    而非
    timestamp()
  • 根据角色命名布尔值,而非仅根据类型。
  • 对于反映观察状态、能力或解析后配置的布尔值,使用谓词名称如
    is*
    has*
    can*
    supports*
    。示例:
    isFlashEnabled
    isFlashAvailable
    hasFlash
    supportsFlash
  • 对于控制未来行为的布尔选项,使用命令或偏好名称如
    enable*
    allow*
    或领域特定动词。示例:
    enableFlash
    allowCellularAccess
    preferHighAccuracy
  • 在React和React Native公共API中,将
    use*
    名称保留给hooks。在React上下文之外,仅当
    use*
    在本地领域中读起来自然且不会与hook混淆时才可以使用。
  • 当功能尚未启用时,避免对输入偏好使用
    is*Enabled
    enableGuidance?: boolean
    被解读为请求的设置;
    isGuidanceEnabled: boolean
    被解读为解析后的状态。
  • 对于成本低的观察状态或能力,使用属性。优先使用
    readonly isAccelerometerAvailable: boolean
    而非
    accelerometerAvailable()
  • 对于副作用、昂贵工作、分配、突变、异步边界或可能失败的操作,使用方法。
  • 在名称中明确单位:
    timeoutMs
    byteSize
    maxRetries
    createdAt
  • 不要机械地为每个导出类型添加包、模块或功能名称前缀。导入和包命名空间已经提供了上下文,调用者可以根据需要别名名称。仅当无前缀名称在包根导入站点存在歧义时才使用前缀;优先使用领域名称如
    CameraPermissionStatus
    ScannedResultSource
    Point
    Rect
    ,而非在每个类型上重复
    DataScanner
  • 对于调用者观察但不设置的公共状态,使用
    readonly
    。仅当分配属性本身是预期命令时才使用可变属性。
  • 可写属性必须是低成本、同步且不太可能失败的。如果设置值需要原生协商、权限、I/O、分配、可能失败的验证或线程/进程跳转,请暴露显式的异步方法,如
    setZoomFactor(zoomFactor): Promise<void>
  • 对于公共字符串字面量联合,优先使用小写短横线分隔的值,如
    manual-input
    而非驼峰式的
    manualInput

Async, Events, and React

异步、事件与React

  • Use sync APIs when results are immediate, deterministic, cheap, and local. Do not async-wrap simple value construction, cached metadata, or pure transforms just because the implementation is native.
  • Use
    Promise
    for one-shot async work. Use listener APIs, streams, or observable stores for repeated events.
  • Use
    Promise
    for permissions, hardware/session setup, I/O, capture/recording, platform async APIs, blocking work, native APIs that are async already, or work that may cross a thread or process boundary. Heavy or complex work should run on an owned worker/queue/dispatcher/runtime rather than blocking the caller.
  • Avoid the main/UI thread unless the underlying platform API requires it. Main-thread APIs should do only the required UI work there and move heavy setup, conversion, parsing, I/O, and computation to an owned background context.
  • Do not pass Promise resolver state through arbitrary layers. Prefer returning a Promise from the async boundary and using
    async
    /
    await
    , native Promise helpers, or a small completion callback at the exact bridge point. Dangling promises and double resolution are lifecycle bugs.
  • Benchmark ambiguous APIs. Make the method async when normal use can block visible UI work, waits on another thread/process, or has unpredictable runtime.
  • If a transform has both cheap and potentially heavy paths, expose explicit sync and async methods such as
    convertX()
    and
    convertXAsync()
    instead of hiding blocking work behind one ambiguous method.
  • Treat repeated internal thread/queue/runtime hops as an API design smell. Prefer an object that owns its execution context, or an explicit async lifecycle method that crosses into that context once. If callers need a result from another thread, expose a
    Promise
    , listener, stream, or returned stateful handle instead of hiding hop chains behind sync-looking methods or properties.
  • Do not fix race conditions, readiness, or ordering bugs with
    setTimeout
    , sleeps, artificial delays, extra thread hops, or calling the same method twice. Those hide a broken lifecycle contract. Fix the state machine, expose an explicit readiness/completion event, return a configured handle, or make the operation properly async. Retries are appropriate only for external nondeterminism such as network, remote services, OS services, or hardware, and they must be bounded, cancellable, and safe to repeat.
  • Use explicit listener methods with cleanup instead of writable callback properties:
    addOnValueChangedListener(listener): ListenerSubscription
    , not
    onValueChanged?: (...) => void
    .
  • Inline simple listener callback parameters in method signatures. Export a named callback type only when users import it directly, the same function contract is reused across multiple APIs, or the callback has a real domain meaning beyond being a listener function.
  • Listener registration must return the cleanup handle. Avoid
    addListener(...): number
    plus
    removeListener(listenerId: number)
    because caller-managed IDs are easy to leak, hard to type safely, and force hidden global/static listener tables. Prefer a flat subscription object with an idempotent
    remove
    function that owns the native or internal cleanup.
  • For repeated observations or events, default to
    addOn...Listener(callback): ListenerSubscription
    . This supports multiple independent consumers and gives each caller explicit ownership of cleanup. Do not use a single
    setOn...
    callback slot when one caller can accidentally replace another caller's listener.
  • Listener cleanup should prevent future emissions, not promise to cancel an event already being delivered from a snapshot. Do not overcomplicate the API or implementation to guarantee that a callback removed during dispatch cannot receive the current event.
  • Use a
    setOn...(callback | undefined)
    -style API only when replacement is the intended semantic: the object has exactly one callback slot, the callback is more like configuration than event subscription, or a hot-path native pipeline cannot reasonably multiplex listeners. Document that it replaces the previous callback and that
    undefined
    removes it.
  • Do not rely on the
    Callback
    or
    Listener
    suffix alone to communicate ownership. The
    add...
    vs
    set...
    verb and the return type should make it clear whether the callback is additive and caller-owned or a single replaceable slot.
  • Use callback option objects for one-shot operation progress when callbacks belong to one method call, such as capture or upload progress callbacks.
  • Do not expose native-side bookkeeping as the event contract unless it is the feature. Prefer simple observations and let JS derive app-specific diffs or state when that is cheap and clearer.
  • In React or React Native libraries, provide hooks on top of a stable imperative API when the value should drive UI. Use the appropriate React primitive, such as
    useEffect
    for subscriptions or
    useSyncExternalStore
    for external state.
  • Keep hooks as adapters over the imperative core, not the only way to use the library.
  • For React components, wrap the imperative core instead of creating a parallel API. A component can own setup, refs, gestures, and lifecycle, while hooks expose the same lower-level objects for advanced use.
  • Use stable callbacks and deterministic cleanup for subscriptions. Subscribe before lifecycle effects that can emit events when missing an event would be observable.
  • 当结果是即时、确定、低成本且本地的时,使用同步API。不要仅仅因为实现是原生的就对简单的值构造、缓存元数据或纯转换进行异步包装。
  • 对于一次性异步工作使用
    Promise
    。对于重复事件使用监听器API、流或可观察存储。
  • 对于权限、硬件/会话设置、I/O、捕获/录制、平台异步API、阻塞工作、本身就是异步的原生API,或可能跨线程/进程边界的工作,使用
    Promise
    。繁重或复杂的工作应在自有工作线程/队列/调度器/运行时上运行,而非阻塞调用者。
  • 除非底层平台API要求,否则避免使用主线程/UI线程。主线程API应仅在那里执行必要的UI工作,并将繁重的设置、转换、解析、I/O和计算移至自有后台上下文。
  • 不要通过任意层传递Promise解析器状态。优先从异步边界返回Promise,并在确切的桥接点使用
    async
    /
    await
    、原生Promise辅助函数或小型完成回调。悬空Promise和双重解析是生命周期错误。
  • 对模糊的API进行基准测试。当正常使用可能阻塞可见的UI工作、等待另一个线程/进程,或运行时不可预测时,将方法设为异步。
  • 如果转换同时存在低成本和潜在繁重的路径,请暴露显式的同步和异步方法,如
    convertX()
    convertXAsync()
    ,而非将阻塞工作隐藏在一个模糊的方法后面。
  • 将重复的内部线程/队列/运行时跳转视为API设计缺陷。优先使用拥有自己执行上下文的对象,或显式的异步生命周期方法,一次性进入该上下文。如果调用者需要来自另一个线程的结果,请暴露
    Promise
    、监听器、流或返回的有状态句柄,而非将跳转链隐藏在看似同步的方法或属性后面。
  • 不要使用
    setTimeout
    、睡眠、人为延迟、额外线程跳转或重复调用同一方法来修复竞争条件、就绪性或排序错误。这些方法掩盖了损坏的生命周期契约。修复状态机、暴露显式的就绪/完成事件、返回已配置的句柄,或使操作正确异步。仅当外部不确定性(如网络、远程服务、OS服务或硬件)时才适合重试,且重试必须是有界的、可取消的、重复安全的。
  • 使用带有清理功能的显式监听器方法,而非可写回调属性:
    addOnValueChangedListener(listener): ListenerSubscription
    ,而非
    onValueChanged?: (...) => void
  • 在方法签名中内联简单的监听器回调参数。仅当用户直接导入回调类型、同一函数契约在多个API中重用,或回调具有超出监听器函数的实际领域意义时,才导出命名回调类型。
  • 监听器注册必须返回清理句柄。避免
    addListener(...): number
    搭配
    removeListener(listenerId: number)
    ,因为调用者管理的ID容易泄漏、难以安全类型化,且需要隐藏的全局/静态监听器表。优先使用带有幂等
    remove
    函数的扁平订阅对象,该函数负责原生或内部清理。
  • 对于重复观察或事件,默认使用
    addOn...Listener(callback): ListenerSubscription
    。这支持多个独立消费者,并让每个调用者明确拥有清理权。当一个调用者可能意外替换另一个调用者的监听器时,不要使用单一的
    setOn...
    回调槽。
  • 监听器清理应防止未来的事件发射,而非承诺取消已从快照交付的事件。不要为了保证在调度期间移除的回调不会接收当前事件而过度复杂化API或实现。
  • 仅当替换是预期语义时才使用
    setOn...(callback | undefined)
    风格的API:对象恰好有一个回调槽、回调更像配置而非事件订阅,或热路径原生管道无法合理复用监听器。记录它会替换之前的回调,且
    undefined
    会移除它。
  • 不要仅依赖
    Callback
    Listener
    后缀来传达所有权。
    add...
    set...
    动词以及返回类型应明确表示回调是累加且调用者所有,还是单一可替换槽。
  • 当回调属于单个方法调用时,使用回调选项对象处理一次性操作进度,如捕获或上传进度回调。
  • 除非是功能本身,否则不要将原生端的簿记作为事件契约。优先使用简单的观察,并让JS推导应用特定的差异或状态,当这种方式成本低且更清晰时。
  • 在React或React Native库中,当值应驱动UI时,在稳定的命令式API之上提供hooks。使用适当的React原语,如
    useEffect
    用于订阅,或
    useSyncExternalStore
    用于外部状态。
  • 将hooks作为命令式核心的适配器,而非使用库的唯一方式。
  • 对于React组件,包装命令式核心而非创建并行API。组件可以负责设置、refs、手势和生命周期,而hooks为高级用法暴露相同的底层对象。
  • 为订阅使用稳定的回调和确定性清理。在可能发射事件的生命周期效应之前订阅,否则可能会错过可观察的事件。

Errors and Platform Differences

错误与平台差异

  • Never silently swallow user-reachable errors. Do not just return, log, or no-op. Throw, reject, or emit through a dedicated error listener.
  • Use real JavaScript
    Error
    objects for failures and error events, not custom
    { code, message }
    structs, unless the API is deliberately serializing errors across a boundary that cannot preserve prototypes.
  • Unsupported required capabilities or invalid inputs should throw specific errors. Name the missing capability, invalid value, valid range, or platform restriction.
  • Do not throw only because one platform ignores an optional preference that another platform supports. If ignoring it does not break the user's requested outcome, degrade gracefully and let capability/resolved-state APIs show whether it took effect.
  • Throw when ignoring an option would violate the user's intent, safety, privacy, or visible correctness. For example, a required flash mode should fail on hardware without flash; a best-effort quality hint can fall back.
  • Mention an alternative in the error message when a real alternative exists.
  • Static assertions or fatal errors are only for impossible internal states, not for states reachable from JS user code.
  • For React Native, design cross-platform concepts instead of mirroring iOS and Android APIs 1:1. Avoid leaking native class names or framework details unless that low-level access is the feature.
  • Keep low-level concepts explicit for GPU, zero-copy, native-resource, and hardware APIs. Do not force them into generic web-shaped names if that hides ownership, copying, sync/async behavior, thread affinity, or disposal.
  • 永远不要悄悄吞掉用户可触及的错误。不要只返回、记录或做空操作。抛出错误、拒绝Promise或通过专用错误监听器发射错误。
  • 对于失败和错误事件,使用真实的JavaScript
    Error
    对象,而非自定义的
    { code, message }
    结构体,除非API故意跨无法保留原型的边界序列化错误。
  • 不支持的必需能力或无效输入应抛出特定错误。命名缺失的能力、无效值、有效范围或平台限制。
  • 不要仅因为一个平台忽略了另一个平台支持的可选偏好就抛出错误。如果忽略它不会破坏用户请求的结果,则优雅降级,并让能力/解析状态API显示它是否生效。
  • 当忽略选项会违反用户意图、安全性、隐私或可见正确性时抛出错误。例如,必需的闪光灯模式应在无闪光灯硬件上失败;尽力而为的质量提示可以回退。
  • 当存在真正的替代方案时,在错误消息中提及它。
  • 静态断言或致命错误仅用于不可能的内部状态,而非JS用户代码可触及的状态。
  • 对于React Native,设计跨平台概念,而非1:1镜像iOS和Android API。避免泄露原生类名或框架细节,除非这种低级访问是功能本身。
  • 对于GPU、零拷贝、原生资源和硬件API,保持低级概念明确。不要将它们强制转换为通用的Web形状名称,如果这样会隐藏所有权、复制、同步/异步行为、线程亲和性或销毁逻辑。

Avoid Workaround APIs

避免变通方案API

  • Do not encode temporary patches, platform bugs, dependency quirks, or stale implementation details into the public API shape unless callers genuinely need to reason about them.
  • Prefer fixing the root cause, using official extension points, or hiding compatibility code behind a stable domain API.
  • If a workaround is unavoidable, keep it narrow and internal where possible. If it must be public, document why it exists, what issue it tracks, and when it can be removed.
  • 不要将临时补丁、平台错误、依赖项怪癖或过时的实现细节编码到公共API结构中,除非调用者确实需要对它们进行推理。
  • 优先修复根本原因、使用官方扩展点,或在稳定的领域API后面隐藏兼容性代码。
  • 如果变通方案不可避免,请尽可能将其限制在内部。如果必须公开,请记录其存在的原因、跟踪的问题以及可以移除的时间。

TypeScript Facades

TypeScript门面

  • Put defaults, convenience overloads, and discriminated unions in TypeScript only when they reduce implementation branches without changing behavior.
  • Keep TypeScript adapters thin. If the facade changes failure behavior, defaults, lifecycle, or ownership semantics, move the rule into the core API instead.
  • Prefer examples that show realistic setup, cleanup, unavailable-feature behavior, and invalid inputs.
  • 仅当TypeScript中的默认值、便利重载和可辨识联合可以减少实现分支而不改变行为时才使用它们。
  • 保持TypeScript适配器轻量化。如果门面改变了失败行为、默认值、生命周期或所有权语义,请将规则移至核心API中。
  • 优先展示贴合实际的设置、清理、不可用功能行为和无效输入的示例。

Documentation Contracts

文档契约

  • Treat exported TypeScript as product documentation. Add JSDoc to every exported declaration users import, including interfaces, type aliases, string-literal unions, runtime enums, classes, HybridObjects, option objects, event objects, hooks, components, and public constants.
  • Assume these comments will become API documentation through TypeDoc or a similar generator. Write them as navigable reference docs, not inline-only code notes.
  • Every JSDoc
    {@linkcode ...}
    or
    @see
    target must resolve in the file where it is written. Import type-only symbols that are referenced only by docs when necessary, or link to a local symbol that is already in scope. Do not leave editor or doc-tooling squiggles just because
    tsc
    happens to pass.
  • Do not create exported callback aliases only to attach JSDoc. Inline simple callback parameters and document the method or parameter that accepts them. Export and document a callback type only when it is intentionally part of the public API.
  • Use JSDoc for semantics, lifecycle, platform behavior, defaults, and performance costs that types cannot express.
  • Keep JSDoc user-facing. Do not mention native class names, framework implementation details, or current platform limitations unless the caller must know them to use the API correctly.
  • Write JSDoc that explains the domain meaning, not the fact that the API is JavaScript. Avoid empty comments such as "normalized for JavaScript"; prefer concrete semantics such as "Represents the format of a barcode" or "Represents the content type of a scanned text value."
  • Document every public property, including properties on options, capabilities, results, events, and HybridObjects. Short comments are fine for obvious fields, but defaults, units, availability, failure behavior, and interaction with related options belong on the property that exposes them.
  • Every type-level JSDoc should connect the type to a real related API with
    {@linkcode ...}
    or
    @see
    . Use the property, method, factory, or result that exposes the type, for example
    Represents the format of a {@linkcode Barcode}.
    plus
    @see {@linkcode Barcode.format}
    . Do not invent link targets or add filler links only to satisfy this rule.
  • For base interfaces or abstract concepts, describe how callers encounter the value and link concrete variants exported by the package, for example
    Represents a value scanned by {@linkcode DataScanner}. Concrete values include {@linkcode ScannedTextValue} and {@linkcode ScannedBarcodeValue}.
  • Use
    {@linkcode ...}
    or
    @see
    to point to related methods, configuration objects, and capability checks instead of writing generic warnings about not assuming the current OS or platform.
  • Link docs densely enough that users can click around the generated API reference: factories link to returned objects, options link to methods that consume them, result objects link to methods that produce them, events link to listener registration methods, and cleanup/disposal docs link to the API that creates the resource.
  • Use
    @default
    ,
    @throws
    ,
    @platform
    ,
    @example
    ,
    @see
    , and
    @discussion
    where they clarify behavior. Link related APIs with
    {@linkcode ...}
    .
  • Document resource ownership and cleanup explicitly. If a returned object must be disposed, say when and why.
  • 将导出的TypeScript视为产品文档。为用户导入的每个导出声明添加JSDoc,包括接口、类型别名、字符串字面量联合、运行时枚举、类、HybridObjects、选项对象、事件对象、hooks、组件和公共常量。
  • 假设这些注释将通过TypeDoc或类似生成器成为API文档。将它们编写为可导航的参考文档,而非仅内联的代码注释。
  • 每个JSDoc
    {@linkcode ...}
    @see
    目标必须在编写它的文件中解析。必要时仅导入文档引用的仅类型符号,或链接到已在作用域中的本地符号。不要仅仅因为
    tsc
    通过就留下编辑器或文档工具的波浪线错误。
  • 不要仅为了附加JSDoc而导出回调别名。内联简单的回调参数并记录接受它们的方法或参数。仅当回调类型是公共API的有意部分时才导出并记录它。
  • 使用JSDoc记录类型无法表达的语义、生命周期、平台行为、默认值和性能成本。
  • 保持JSDoc面向用户。除非调用者必须了解它们才能正确使用API,否则不要提及原生类名、框架实现细节或当前平台限制。
  • 编写解释领域含义的JSDoc,而非仅仅说明API是JavaScript。避免空注释如“normalized for JavaScript”;优先使用具体语义如“表示条形码的格式”或“表示扫描文本值的内容类型”。
  • 记录每个公共属性,包括选项、能力、结果、事件和HybridObjects上的属性。对于明显的字段,简短的注释即可,但默认值、单位、可用性、失败行为以及与相关选项的交互应记录在暴露它们的属性上。
  • 每个类型级别的JSDoc应使用
    {@linkcode ...}
    @see
    将类型与真实的相关API连接起来。使用暴露该类型的属性、方法、工厂或结果,例如
    表示{@linkcode Barcode}的格式。
    加上
    @see {@linkcode Barcode.format}
    。不要发明链接目标或仅为满足此规则添加填充链接。
  • 对于基接口或抽象概念,描述调用者如何遇到该值,并链接包导出的具体变体,例如
    表示{@linkcode DataScanner}扫描的值。具体值包括{@linkcode ScannedTextValue}和{@linkcode ScannedBarcodeValue}。
  • 使用
    {@linkcode ...}
    @see
    指向相关方法、配置对象和能力检查,而非编写关于不要假设当前OS或平台的通用警告。
  • 足够密集地链接文档,以便用户可以在生成的API参考中点击导航:工厂链接到返回的对象,选项链接到使用它们的方法,结果对象链接到生成它们的方法,事件链接到监听器注册方法,清理/销毁文档链接到创建资源的API。
  • 在需要澄清行为的地方使用
    @default
    @throws
    @platform
    @example
    @see
    @discussion
    。使用
    {@linkcode ...}
    链接相关API。
  • 明确记录资源所有权和清理。如果返回的对象必须被销毁,请说明何时以及为什么。