loqkit

中文文档
0.8.4发布于 2026/11/04MITTypeScriptESM + CJS构建来源已验证

JavaScript运行时的类型化记忆和分层内存、工作线程和文件锁。

中文使用文档
文档目录4 节
  1. 锁定
  2. 记忆有关
  3. 锁是如何工作的
  4. 释放

loqkit

JavaScript运行时的类型记忆和分层锁定。

Loqkit发布匹配的ESM和CommonJS入口点。

锁定

import { withLock } from "loqkit";

const result = await withLock(
  { resource: "cache" },
  async ({ resource, lock }) => {
    console.log(`locked ${resource} with ${lock.backends.length} layers`);
  },
  {
    key: "cache-refresh",
    predicate: async ({ resource, state }) => {
      return state === "predicate-after"
        ? await stillNeedsRefresh(resource)
        : true;
    },
    onCheckFailed: async ({ resource, state }) => {
      console.log(`${resource} skipped in ${state}`);
    },
  },
);

console.log(result.value, result.context.state, result.context.lock.waitedMs);

默认情况下, withLock 需要一个内存锁,使用工作线程和本机 flock 在可用时锁定,并在跳过本机群集时使用可移植轮询。动作后的参数可以是谓词函数或options对象,其中包含 predicate. 谓词在等待之前运行一次,在处理每个启用的层之后再运行一次。只有当两个检查都通过时,操作才会运行。

每个回调都会收到 ctx.state: "predicate-before" 对于第一张支票, "predicate-after" 用于收购后的支票,以及 "executed" 为了行动。当其中一个检查失败时, onCheckFailed(ctx) 在所有获得的层被释放后运行。它可以检查 ctx.state、记录、重读数据或启动其他锁工作而不持有此锁。

withLock 返回 { value, context }。 value 是行动的结果,还是 undefined 操作未运行时。返回的上下文具有与回调上下文相同的字段和状态值 "timeout" 添加,因为非抛出超时只能在结果中观察到。锁元数据(包括等待时间)在 result.context.lock。

省略 timeout 无限期地等待。一个数值超时抛出 LockTimeoutError 当它到期时。使用对象形式返回超时结果。零超时只进行一次非阻塞的获取尝试,并且不会休眠或轮询第二次:

const attempt = await withLock(context, action, {
  timeout: { ms: 0, error: false },
});

if (attempt.context.state === "timeout") {
  console.log("Lock is busy");
}

scope 是部分配置映射。省略映射或属性使用其默认值; false 禁用该层。工人和羊群另外接受 "auto",它在可用时使用该层,否则将其记录为跳过:

await withLock(context, action, {
  scope: { worker: false, flock: false },
});

await withLock(context, action, {
  scope: { memory: false, worker: true, flock: false, poll: false },
});
范围值默认的行为
memorytrue, falsetrue当前JavaScript隔离中的锁。
workertrue, false, "auto""auto"协调工作线程。 true 抛出 LockUnavailableError 当运行时API不可用时。
flocktrue, false, "auto""auto"使用本机咨询文件锁定。 true 抛出 LockUnavailableError 本机支持不可用或失败时。
polltrue, false, "auto""auto"使用可移植轮询文件锁。 "auto" 当获得本地羊群或其他需要时跳过它。

启用的层总是运行 memory, worker, flock, poll 顺序和释放顺序相反。将所有四个设置为 false 允许的,并且运行谓词和操作时没有独占性边界。

上的回调和结果公开已解析的结果 ctx.lock.scope. 它包含一个布尔值for memory, true, false,或 "skipped" 为 worker, flock, poll; "auto" 从来没有出现过。自动层可以从 true 在第一个谓词上下文中to "skipped" 收购后确定它不受支持或冗余。 ctx.lock.backends 保留低级实现和回退细节。

记忆有关

memoize 将零参数工厂转换为计算一次并重用其成功结果的getter。同步工厂保持同步。异步或可启用的工厂返回原生承诺,并发调用者共享相同的动态工作。

import { memoize } from "loqkit";

const getConfig = memoize(() => ({ region: "us-east-1" }));
const config = getConfig();

const getCatalog = memoize(async () => {
  const response = await fetch("https://example.com/catalog.json");
  return response.json();
});

const catalog = await getCatalog();

成功的结果将在记忆getter的生命周期内保持缓存。抛出的错误和拒绝的承诺不会被缓存,因此下一个调用将重试。没有TTL或过期行为。

flowchart TD
    call["Call memoized getter"] --> cached{"Successful result cached?"}
    cached -- "Yes" --> reuse["Return cached value or promise"]
    cached -- "No" --> factory["Run factory"]
    factory --> kind{"Thenable result?"}
    kind -- "No" --> storeValue["Cache and return value"]
    kind -- "Yes" --> storePromise["Cache and return in-flight promise"]
    storePromise --> outcome{"Promise outcome"}
    outcome -- "Resolved" --> keep["Keep successful promise"]
    outcome -- "Rejected" --> evict["Evict so the next call retries"]

锁是如何工作的

withLock 是一个键作用域锁。具有相同名称空间和键的调用一次只运行一个;具有不同键的调用可以并发运行。如果没有提供键,loqkit将上下文散列到一个稳定的键中。

当 scope 省略时,loqkit按顺序处理这些层,并反向释放获得的层:

  1. 一个内存中的 async-mutex lock序列化当前JavaScript隔离中的调用者。
  2. Node的工作锁管理器在运行时提供时协调工作线程。默认的 "auto" mode在API不可用时将该层记录为跳过。
  3. 在macOS和Linux上,可选的 fs-ext 图层使用native flock. 默认的 "auto" 当本机锁定不可用或获取失败(包括拒绝的文件系统)时,Mode将该层记录为跳过 flock。
  4. 一个 proper-lockfile 轮询锁协调共享配置的锁目录的进程。它的默认 "auto" Mode在flock被跳过或禁用时获取它,在flock成功后跳过它。

只有在不可用或非争用获取失败时才绕过自动群层。普通的争用继续等待羊群,这样排他性得以保留。集 scope.poll 来 true 需要两种机制;由于flock已经序列化了loqkit调用者,因此在该状态下的轮询争用立即失败 LockUnavailableError 而不是永远等待。集 scope.flock 来 false 对于已知具有不可靠群语义的网络挂载。 lockDirectory 或 LOQKIT_LOCK_DIR 两个基于文件的层存储其锁文件的控件。

flowchart TD
    call["Call withLock"] --> options["Validate options and choose a key"]
    options --> checking["Create predicate-before context"]
    checking --> before{"Predicate passes before waiting?"}
    before -- "No" --> failedBefore["Run onCheckFailed without a lock"]
    failedBefore --> skippedBefore["Return predicate-before context"]
    before -- "Yes or absent" --> memory["Acquire memory lock"]
    memory --> worker["Acquire worker-thread lock"]
    worker --> flock["Acquire or bypass native flock"]
    flock --> poll["Acquire or bypass portable polling lock"]
    memory -. "Timeout" .-> timeout{"timeout.error?"}
    worker -. "Timeout" .-> timeout
    flock -. "Timeout" .-> timeout
    poll -. "Timeout" .-> timeout
    timeout -- "Yes" --> timeoutError["Throw LockTimeoutError"]
    timeout -- "No" --> timeoutResult["Return timeout context"]
    poll --> acquired["Create predicate-after context with enabled layers handled"]
    acquired --> after{"Predicate still passes?"}
    after -- "No" --> releaseFailed["Release all layers"]
    releaseFailed --> failedAfter["Run onCheckFailed without a lock"]
    failedAfter --> skippedAfter["Return predicate-after context"]
    after -- "Yes or absent" --> action["Run action with executed context"]
    action --> release["Release poll, flock, worker, then memory"]
    release --> result["Return value and executed context"]

当另一个调用者在此调用者等待时更改底层状态时,第二个谓词检查很重要:

sequenceDiagram
    participant A as Caller A
    participant B as Caller B
    participant L as Layered lock for one key

    A->>L: Acquire enabled memory, worker, flock, poll layers
    L-->>A: Granted
    B->>B: Check predicate
    B->>L: Acquire same layers
    Note over B,L: Caller B waits
    A->>A: Run action
    A-->>L: Release all layers
    L-->>B: Granted
    B->>B: Check predicate again
    B->>B: Run action if still needed
    B-->>L: Release all layers

默认的文件目录在主机本地。容器只有在共享配置的锁目录时才会协调,而单独的主机仍然需要共享锁服务或数据库支持的锁。

释放

运行 bun run release 从干净,同步 main 分支。Patch是默认增量; minor, major, --dry-run 也支持。现有的未标记的npm 0.0.1 需要一次性引导表单 bun run release -- --initial。

本地命令验证Git标识,从远程GitHub标记派生下一个版本,运行完整的套件,并在自动推送发布提交和标记之前打包包。 标签工作流 重复验证,因为发布标签不受保护,然后通过npm Trusted Publishing发布生成的存档,并注明出处。它不使用npm令牌。

版本与发布记录

loqkit 共发布 13 个版本,首次发布于 2026/11/01;过去 12 个月发布了 13 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.8.4latest2026/11/04今天
0.8.32026/11/041 天前
0.8.22026/11/041 天前
0.8.12026/11/041 天前
0.8.02026/11/041 天前
0.0.72026/11/031 天前

关于 loqkit 的常见问题

loqkit 是什么?

JavaScript运行时的类型化记忆和分层内存、工作线程和文件锁。

如何安装 loqkit?

在项目目录运行 npm install loqkit,或使用 pnpm add / yarn add / bun add。

loqkit 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

loqkit 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

loqkit 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

loqkit 可以商用吗?

loqkit 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

loqkit 还在维护吗?

最新版本 0.8.4 发布于 2026/11/04(今天);至今共发布 13 个版本,其中过去 12 个月发布了 13 个;首次发布于 2026/11/01。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。