Initial commit: Monokai Syntax Obsidian theme

This commit is contained in:
Darkings 2026-05-08 08:57:12 +08:00
commit 3cc59263c7
2869 changed files with 586144 additions and 0 deletions

6
.claude/settings.json Normal file
View file

@ -0,0 +1,6 @@
{
"statusLine": {
"type": "command",
"command": "bash /d/Projects/Monokai\\ Syntax/.claude/statusline-command.sh"
}
}

View file

@ -0,0 +1,9 @@
{
"permissions": {
"allow": [
"Bash(git init *)",
"Bash(git add *)",
"Bash(git commit -m ' *)"
]
}
}

View file

@ -0,0 +1,52 @@
#!/bin/bash
# Claude Code Status Line — dot-style progress bars with green→yellow→red gradient
input=$(cat)
model=$(echo "$input" | jq -r '.model.display_name')
dir=$(echo "$input" | jq -r '.workspace.current_dir')
five=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
week=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
# dot progress bar: 10 dots, color transitions green→yellow→red
# usage: dots_bar <percentage (0-100)>
dots_bar() {
local pct=${1%.*}
local total=10
local filled=$(( pct * total / 100 ))
[ $filled -gt $total ] && filled=$total
[ $filled -lt 0 ] && filled=0
local color
if [ $pct -le 30 ]; then
color="32" # green
elif [ $pct -le 70 ]; then
color="33" # yellow
else
color="31" # red
fi
local i result=""
for ((i=0; i<total; i++)); do
if [ $i -lt $filled ]; then
result="${result}$(printf '\033[%sm●\033[0m' "$color")"
else
result="${result}$(printf '\033[90m●\033[0m')"
fi
done
printf '%s' "$result"
}
# build status line
printf '\033[1m%s\033[0m \033[36m%s\033[0m' "$model" "$dir"
if [ -n "$five" ]; then
printf ' 5h:'
dots_bar "$five"
fi
if [ -n "$week" ]; then
printf ' 7d:'
dots_bar "$week"
fi
printf '\n'

1
.github/.gitkeep vendored Normal file
View file

@ -0,0 +1 @@

33
AGENTS.md Normal file
View file

@ -0,0 +1,33 @@
# 项目协作规范
## 文档语言
- 本项目所有文档必须使用中文书写。
## 项目文档位置
- Windows 系统下本项目的长期项目文档、研究文档、路线图、QA 文档与问题汇总优先维护在:
```text
C:\Users\Jie\iCloudDrive\iCloud~md~obsidian\SecondBrain\project\Monokai Syntax
```
- 当前仓库仅保留开发、构建、协作、发布与源码旁注所必需的文档。
- 将仓库文档迁移到上述 Obsidian 项目目录前,需要先确认该文档不会被构建脚本、发布流程、协作规则或源码注释直接依赖。
- 已迁移到 Obsidian 项目目录的文档,不应在仓库中继续维护重复副本;如仓库流程需要引用,应在仓库文档中写明 Obsidian 目录中的目标位置。
- `docs/` 目录仅作为临时整理区使用。稳定的说明文档、QA 清单、路线图和研究报告应迁移到 Obsidian 项目目录。
# Codex任务执行引导
1. 当接收到指令“continue to next task”时首先读取当前目录下的TODO.md文件
2. 检查TODO List中未标记“已完成”的第一个任务执行该任务
3. 任务执行完成后在TODO.md中标记该任务为“已完成”
4. 若执行过程中遇到错误如文件不存在、代码报错输出问题详细信息到ERRORS.md 文件,并尝试解决该问题,如解决不了。将问题输出详细错误信息后停止,等待进一步指令。
5. 当接收到指令格式为“active 文档名”时,在以下目录查找同名 Markdown 文档:
```text
C:\Users\Jie\iCloudDrive\iCloud~md~obsidian\SecondBrain\project\Monokai Syntax\Active
```
6. 若找到同名文档,将该文档内容读取、整理并整合到当前仓库的 `TODO.md` 中;整合时保留现有 TODO 内容,不得删除未完成任务。
7. 若未找到同名文档,或出现多个可能匹配项,应将详细问题写入 `ERRORS.md`,并停止等待进一步指令。

66
CLAUDE.md Normal file
View file

@ -0,0 +1,66 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Build & Commands
```
npm run dev # Watch mode (rebuilds theme.css on SCSS changes)
npm run build # Production build → dist/theme.css
npm run lint:css # Stylelint on src/scss/**/*.scss
npm run check:contrast # WCAG contrast ratio audit
npm run audit:css # CSS quality audit
npm run verify:icons # Validate icon font coverage
npm run verify:graph # Validate graph view CSS variables
npm run generate:icons # Regenerate file-icons.generated.scss from icon font
npm run release:pack # Full pre-release check (verify + lint + build + audit)
npm run build:vault # Build and sync to Obsidian vault
```
The `generate:icons` script runs automatically before `build` and `lint:css` via `prebuild`/`prelint:css` hooks.
## Architecture
This is an **Obsidian theme** that maps the VS Code Monokai Pro color palette onto Obsidian's ~400 CSS custom properties. The build toolchain is **Vite** with a custom `obsidianThemeBundle` plugin that concatenates `license.css` + compiled SCSS + Style Settings YAML configs into a single `dist/theme.css`.
### SCSS module hierarchy (all under `src/scss/`)
| File | Role |
|---|---|
| `_variables.scss` | Design tokens: 40+ color primitives (dark + light), font stacks, spacing, radii |
| `_mixins.scss` | `focus-ring`, `compact-density`, `reduced-motion` mixins |
| `_base.scss` | Maps SCSS variables to Obsidian CSS custom properties on `:root`, `.theme-dark`, `.theme-light`. Also contains global UI rules (nav, metadata, search, tags) |
| `components/_editor.scss` | Markdown rendering and CodeMirror 6 syntax highlighting (`.cm-s-obsidian` token colors) |
| `components/_ribbon.scss` | Left sidebar ribbon (Obsidian's Activity Bar analog) |
| `components/_tabs.scss` | Workspace tab headers with active-state accent underline |
| `components/_modals.scss` | Command palette, prompts, suggestion popups |
| `components/_file-icons.scss` | Custom icon font (`monokai-pro-icons`) for file tree, plus monochrome toggle |
| `plugins/_graph.scss` | Graph view: node/line colors, panel controls, focus ring animation |
| `plugins/_canvas.scss` | Canvas card colors mapped to the 6-color Monokai spectrum |
| `plugins/_style-settings.scss` | Dynamic toggles: 5 filter themes (Machine/Octagon/Ristretto/Spectrum), compact mode, monochrome icons, user-overridable accent/link/code colors via `body` classes |
The entry point is `src/main.js`, which only imports `src/scss/index.scss`. That file forwards `variables → mixins → base → components → plugins`.
### Style Settings config (`src/css/style-settings/`)
These `*.css.md` files contain YAML metadata blocks parsed by the `obsidian-style-settings` community plugin. They are sorted alphabetically and concatenated at the end of `theme.css` by the Vite plugin. Files: `00-overview`, `10-palette`, `20-density`, `30-icons`, `40-typography`, `50-accents`.
### Key Obsidian CSS variables
The theme works by setting Obsidian's documented CSS custom properties. The most important groups:
- `--background-primary/secondary` — main editor vs sidebar backgrounds
- `--text-normal/muted/faint` — text hierarchy
- `--text-accent`, `--interactive-accent` — call-to-action magenta
- `--h1-color` through `--h6-color` — heading color cascade (magenta → orange → yellow → green → cyan → purple)
- `--link-color`, `--link-external-color` — cyan links
- `--blockquote-border-color` — green border
- `--code-*` — code syntax token colors
- `--nav-item-*`, `--ribbon-background` — navigation chrome
- Custom `--monokai-*` variables — internal bridge variables for filters and user overrides
## Constraints
- **No `@import` of external resources** — Obsidian's review process rejects themes that fetch remote fonts/assets at runtime. All assets must be inlined (the icon font is Base64-encoded in `_file-icons.generated.scss`).
- **No `!important`** — Obsidian's CSS cascade relies on specificity; `!important` breaks user CSS snippets.
- **No ID selectors** — enforced by Stylelint; use classes only.
- The theme targets Obsidian 1.0.0+ (`minAppVersion` in manifest.json), which introduced the CSS variable system.

132
ERRORS.md Normal file
View file

@ -0,0 +1,132 @@
# 错误记录
## 2026-05-06Vite 构建读取 `dist/theme.css` 失败
### 触发命令
```powershell
npm run build
```
### 错误信息
```text
Error: ENOENT: no such file or directory, open 'D:\Projects\Monokai Syntax\dist\theme.css'
plugin: obsidian-theme-bundle
hook: closeBundle
```
### 根因分析
`vite.config.js` 中的自定义插件在 `closeBundle` 钩子里读取 `dist/theme.css`。当前 Vite/Rolldown 构建流程中,该钩子执行时不能保证 `dist/theme.css` 已经写入磁盘,因此读取文件失败,并导致整个构建中断。
### 解决方案
将插件逻辑从 `closeBundle` 改为 `generateBundle`:直接在 Vite/Rolldown 的内存 bundle 中寻找 CSS 资产,修改其内容并强制输出为 `theme.css`,同时拼接许可证头部与 Style Settings 元数据。
### 后续发现
第一次修复后再次运行 `npm run build`,构建失败并提示:
```text
[plugin obsidian-theme-bundle]
Error: 未找到可写入的 CSS 构建资产。
```
这说明当前 Vite 版本在将纯 `.scss` 文件作为构建入口时,没有在 `generateBundle` 阶段生成可供插件处理的 CSS 资产。进一步修复方向是改用标准 JS 入口导入 SCSS让 Vite 正常生成 CSS 资产,然后在插件中删除 Obsidian 不需要的 JS chunk。
### 最终修复
最小隔离构建显示,改用 JS 入口后 Vite 会生成 `dist/theme.css`,但该 CSS 资产仍不会出现在自定义插件的 `generateBundle` 参数中。最终方案改为:
1. 使用 `src/main.js` 导入 `src/scss/index.scss`,保证 Vite 完整执行 SCSS 构建。
2. 在 `closeBundle` 阶段读取已经落盘的 `dist/theme.css`
3. 拼接 `src/css/license.css`、主题 CSS 与 Style Settings 元数据。
4. 删除 Obsidian 主题不需要的空 JS 构建产物。
## 2026-05-06SCSS 架构初始模块验证失败
### 触发命令
```powershell
npm run lint:css
npm run build
```
### 错误信息
Stylelint 报错:
```text
src/scss/_variables.scss
Unexpected empty line before $-variable
Expected "Arial" to be "arial"
Expected "Consolas" to be "consolas"
src/scss/components/_editor.scss
Empty source
```
Sass 构建报错:
```text
Error: [sass] Undefined variable.
src\scss\_base.scss 2:24
```
### 根因分析
Sass `@use` 采用模块作用域,`index.scss` 中先 `@use "variables"` 并不会把变量自动暴露给 `_base.scss`。因此 `_base.scss` 必须显式 `@use "variables" as *;` 或使用命名空间访问变量。
Stylelint 报错来自标准 SCSS 规则:变量声明之间默认不允许空行,空的占位模块也会触发 `no-empty-source`
### 解决方案
1. 在 `_base.scss` 中显式引入 `variables` 模块。
2. 移除 `_variables.scss` 中变量分组之间的空行,并将字体回退名写为字符串。
3. 为暂未实现的组件和插件模块添加 Sass placeholder避免空源文件。
## 2026-05-06浅色模式链接色对比度不足
### 触发命令
```powershell
npm run check:contrast
```
### 错误信息
```text
浅色链接: 3.73:1要求 >= 4.5:1
```
### 根因分析
浅色模式链接色 `#1c8ca8` 在暖白背景 `#fdf9f3` 上对比度不足,无法达到正文级链接文本推荐的 `4.5:1` 对比度要求。
### 解决方案
将浅色模式链接主色压暗为 `#14748a`,并同步更新对比度检查脚本中的浅色链接基准值。
## 2026-05-06编辑器样式未通过 Stylelint
### 触发命令
```powershell
npm run lint:css
```
### 错误信息
```text
Expected shorthand property "margin-block"
Expected class selector ".HyperMD-codeblock" to be kebab-case
```
### 根因分析
`_editor.scss` 中标题上下边距使用了可合并的 longhand 写法;同时 Obsidian 内部类名 `.HyperMD-codeblock` 不符合项目启用的 `selector-class-pattern` kebab-case 规则。
### 解决方案
使用 `margin-block` 简写替代上下 longhand并移除对 `.HyperMD-codeblock` 的直接选择,改为保留对阅读视图 `pre` 的等宽字体设置。

9
LICENSE.md Normal file
View file

@ -0,0 +1,9 @@
# MIT 许可证
版权所有 2026 lat3ncy
特此免费授予任何获得本软件及相关文档文件副本的人不受限制地处理本软件的权利,包括但不限于使用、复制、修改、合并、发布、分发、再授权和销售本软件副本的权利,并允许获得本软件的人这样做,但须符合以下条件:
上述版权声明和本许可声明应包含在本软件的所有副本或主要部分中。
本软件按“原样”提供,不作任何明示或暗示的保证,包括但不限于适销性、特定用途适用性和非侵权保证。在任何情况下,作者或版权持有人均不对因本软件或本软件的使用或其他交易而产生、引起或相关的任何索赔、损害或其他责任负责,无论该责任是在合同诉讼、侵权行为还是其他方面。

57
README.md Normal file
View file

@ -0,0 +1,57 @@
# Monokai Syntax
Monokai Syntax 是一个面向 Obsidian 的社区主题,目标是在知识管理场景中复现 Monokai Pro 的深色与浅色视觉语言。
## 项目状态
当前项目处于早期开发阶段已经完成基础工程化脚手架、Vite 构建、Stylelint 检查和测试 Vault 同步流程。
## 安装依赖
```powershell
npm install
```
## 常用命令
检查 SCSS
```powershell
npm run lint:css
```
构建主题:
```powershell
npm run build
```
构建并同步到测试 Vault
```powershell
npm run build:vault
```
## 本地测试
本项目当前使用以下 Obsidian Vault 作为测试环境:
```text
C:\Users\Jie\iCloudDrive\iCloud~md~obsidian\SecondBrain
```
运行 `npm run build:vault` 后,主题文件会同步到:
```text
C:\Users\Jie\iCloudDrive\iCloud~md~obsidian\SecondBrain\.obsidian\themes\Monokai Syntax
```
之后可在 Obsidian 的外观设置中启用 `Monokai Syntax`
## 远程资源约束
主题运行时 CSS 不得加载远程资源。禁止在最终主题 CSS 中使用远程 `@import`、远程字体 URL 或远程图片 URL。需要使用的字体、图标或图片资源必须本地化并确认许可证。
## 许可证
本项目使用 MIT 许可证。详情见 [LICENSE.md](./LICENSE.md)。

464
TODO.md Normal file
View file

@ -0,0 +1,464 @@
# Monokai Syntax 项目 TODO
> 来源:`C:\Users\Jie\iCloudDrive\iCloud~md~obsidian\SecondBrain\project\Monokai Syntax\_RoadMap\原始开发规划.md`
> 目标:构建并发布一个名为 **Monokai Syntax** 的 Obsidian 社区主题。主题参考 Monokai Pro 深色与浅色调色板,采用模块化 SCSS、Style Settings 配置扩展,并具备合规的发布流程。
## 已确认项目参数
- 作者名称:`lat3ncy`
- 初始版本号:`1.0.0`
- 包管理器:`npm`
- 测试 Obsidian Vault 路径:`C:\Users\Jie\iCloudDrive\iCloud~md~obsidian\SecondBrain`
- Vault 验证结果:路径存在,且包含 `.obsidian` 配置目录。
## 阶段 0项目初始化
- [x] 创建项目仓库结构。
- [x] 添加 `package.json`
- [x] 添加 `manifest.json`
- [x] 添加 `versions.json`
- [x] 添加 `.github/` 工作流目录。
- [x] 添加 `src/scss/`
- [x] 添加 `src/css/license.css`
- [x] 添加 `src/css/style-settings/`
- [x] 添加 `test/` 输出目录或 Vault 链接目录。
- [x] 配置 npm 脚本。
- [x] 添加开发构建脚本。
- [x] 添加生产构建脚本。
- [x] 添加样式检查脚本。
- [x] 添加发布打包脚本。
- [x] 配置 Vite 作为 CSS 构建引擎。
- [x] 将 `src/scss/index.scss` 编译为最终主题 CSS。
- [x] 注入静态许可证头部。
- [x] 将生成后的 Style Settings 元数据追加到最终 CSS。
- [x] 配置 Stylelint。
- [x] 禁止在常规主题 CSS 中使用 ID 选择器。
- [x] 限制过深的选择器嵌套。
- [x] 统一空格与格式规则。
- [x] 标记不必要的 `!important` 用法。
- [x] 确定本地开发工作流。
- [x] 将构建输出链接到测试 Obsidian Vault。
- [x] 安装或记录 Obsidian 主题热重载支持方式。
## 阶段 1主题元数据
- [x] 创建 `manifest.json`
- [x] 将 `name` 设置为 `Monokai Syntax`
- [x] 设置初始语义化版本号。
- [x] 将 `minAppVersion` 设置为 `1.0.0`
- [x] 添加作者元数据。
- [x] 添加深色与浅色模式支持声明。
- [x] 创建 `versions.json`
- [x] 将初始主题版本映射到最低支持的 Obsidian 版本。
- [x] 添加仓库文档。
- [x] 添加 `README.md`,包含安装、开发与截图说明。
- [x] 添加许可证信息。
- [x] 说明运行时 CSS 不得加载远程资源。
## 阶段 2SCSS 架构
- [x] 创建 `src/scss/index.scss`
- [x] 首先导入变量文件。
- [x] 其次导入 mixin 文件。
- [x] 导入基础主题变量。
- [x] 导入组件模块。
- [x] 导入插件模块。
- [x] 导入 Style Settings 类名覆盖逻辑。
- [x] 创建 `src/scss/_variables.scss`
- [x] 定义深色调色板基础色。
- [x] 定义浅色调色板基础色。
- [x] 定义强调色、链接色、警告色、成功色、代码色、边框色与弱化色等语义别名。
- [x] 定义排版变量。
- [x] 定义间距与圆角变量。
- [x] 创建 `src/scss/_mixins.scss`
- [x] 按需添加主题模式辅助 mixin。
- [x] 添加焦点环辅助 mixin。
- [x] 添加紧凑密度辅助 mixin。
- [x] 添加减少动画辅助 mixin。
- [x] 创建 `src/scss/_base.scss`
- [x] 挂载全局 `:root` 变量。
- [x] 挂载 `.theme-dark` 变量。
- [x] 挂载 `.theme-light` 变量。
- [x] 设置基础字体与行高变量。
- [x] 设置 Obsidian 核心背景与文本变量。
## 阶段 3深浅色调色板映射
- [x] 实现深色模式基础颜色。
- [x] 将 `#272822` 映射到 `--background-primary`
- [x] 将 `#1e1f1c` 映射到 `--background-secondary`
- [x] 将 `#f8f8f2` 映射到 `--text-normal`
- [x] 将低饱和灰色映射到弱化文本、图标、边框与悬停状态。
- [x] 实现深色模式语义强调色。
- [x] 将洋红/红色映射到强调色与一级标题。
- [x] 将橙色映射到二级标题与元数据标签。
- [x] 将黄色映射到三级标题与文本高亮。
- [x] 将绿色映射到块引用边框与已完成复选框。
- [x] 将青色/蓝色映射到内部链接与外部链接。
- [x] 将紫色映射到列表标记与行内代码点缀。
- [x] 实现浅色模式基础颜色。
- [x] 将 `#fdf9f3` 映射到 `--background-primary`
- [x] 将 `#f5f0e6` 映射到 `--background-secondary`
- [x] 将 `#3d3d3d` 映射到 `--text-normal`
- [x] 确保非激活图标与弱化文本在浅色模式下仍清晰可见。
- [x] 实现浅色模式语义强调色。
- [x] 将浅色洋红映射到强调色与一级标题。
- [x] 将浅色橙色映射到二级标题与元数据标签。
- [x] 将较深黄橙色映射到三级标题与文本高亮。
- [x] 将森林绿映射到块引用边框与已完成复选框。
- [x] 将较深青蓝色映射到链接。
- [x] 将浅紫色映射到列表标记与行内代码点缀。
- [x] 检查核心对比度。
- [x] 验证两种模式下的正文文本对比度。
- [x] 验证两种模式下的链接对比度。
- [x] 验证浅色模式下的图标可见性。
- [x] 验证高亮文本可读性。
## 阶段 4Markdown 与编辑器样式
- [x] 创建 `src/scss/components/_editor.scss`
- [x] 设置标题样式。
- [x] 为 H1、H2、H3 应用不同颜色。
- [x] 保持编辑视图与阅读视图中的标题间距易读。
- [x] 设置强调文本样式。
- [x] 让粗体文本颜色接近正文颜色。
- [x] 增加粗体字重,避免制造不必要的视觉噪音。
- [x] 设置斜体样式并保持可读性。
- [x] 设置链接样式。
- [x] 应用青色/蓝色链接颜色。
- [x] 仅在确有价值时区分内部链接与外部链接。
- [x] 添加可访问的悬停与焦点状态。
- [x] 设置块引用样式。
- [x] 使用绿色左边框。
- [x] 保持引用文本颜色可读。
- [x] 调整内边距与间距。
- [x] 设置列表与任务列表样式。
- [x] 应用紫色列表标记。
- [x] 使用绿色表示已完成复选框。
- [x] 验证多级嵌套列表仍然清晰。
- [x] 设置行内代码与代码块。
- [x] 使用类似编辑器的代码块背景。
- [x] 调整内边距、圆角与边框。
- [x] 应用等宽字体栈。
- [x] 设置 Markdown 高亮。
- [x] 使用带模式区分透明度的黄色高亮。
- [x] 确保高亮文本仍然可读。
- [x] 添加 CodeMirror 6 语法颜色。
- [x] 将字符串映射为黄色。
- [x] 将关键字映射为洋红色。
- [x] 将函数映射为绿色或青色。
- [x] 将常量与数字映射为紫色或橙色。
- [x] 将注释映射为弱化灰色。
- [x] 验证 Live Preview 与阅读视图的一致性。
## 阶段 5Obsidian UI 组件
- [x] 创建 `src/scss/components/_ribbon.scss`
- [x] 让 Ribbon 背景与侧边栏背景保持一致。
- [x] 设置非激活图标样式。
- [x] 设置悬停图标样式。
- [x] 设置激活图标样式。
- [x] 创建 `src/scss/components/_tabs.scss`
- [x] 设置工作区标签页样式。
- [x] 设置激活标签页状态。
- [x] 设置标签页悬停状态。
- [x] 确保标题文本在两种模式下都可读。
- [x] 创建 `src/scss/components/_modals.scss`
- [x] 设置命令面板样式。
- [x] 设置建议列表激活项样式。
- [x] 设置弹窗背景与阴影。
- [x] 设置输入框焦点状态。
- [x] 设置导航与文件浏览器样式。
- [x] 调整文件树悬停状态。
- [x] 调整已选中文件状态。
- [x] 设置折叠指示器与图标。
- [x] 验证侧边栏层级清晰。
- [x] 设置元数据与属性样式。
- [x] 为元数据标签应用橙色。
- [x] 保持属性值可读。
- [x] 验证编辑控件可见。
## 阶段 6核心插件样式
- [x] 创建 `src/scss/plugins/_graph.scss`
- [x] 设置关系图谱背景。
- [x] 设置图谱节点样式。
- [x] 设置图谱连线样式。
- [x] 设置悬停与聚焦图谱状态。
- [x] 避免密集图谱造成视觉拥堵。
- [x] 创建 `src/scss/plugins/_canvas.scss`
- [x] 将 Canvas 颜色预设 `color-1``color-6` 映射到 Monokai 调色板。
- [x] 验证卡片在深色模式下可读。
- [x] 验证卡片在浅色模式下可读。
- [x] 设置 Canvas 选中与边线状态。
- [x] 检查常用核心插件界面。
- [x] 搜索。
- [x] 反向链接。
- [x] 出链。
- [x] 标签。
- [x] 书签。
## 阶段 7Style Settings 集成
- [x] 创建 `src/scss/plugins/_style-settings.scss`
- [x] 在 `src/css/style-settings/` 下创建模块化 Style Settings 文件。
- [x] 添加总览或根设置模块。
- [x] 添加调色板滤镜设置模块。
- [x] 添加密度设置模块。
- [x] 添加图标设置模块。
- [x] 添加排版与行宽设置模块。
- [x] 添加强调色覆盖设置模块。
- [x] 实现滤镜类名选择。
- [x] 添加 Classic 滤镜类。
- [x] 添加 Machine 滤镜类。
- [x] 添加 Octagon 滤镜类。
- [x] 添加 Ristretto 滤镜类。
- [x] 添加 Spectrum 滤镜类。
- [x] 确认每个滤镜只覆盖必要且风格一致的一组变量。
- [x] 实现紧凑模式。
- [x] 减小文件树行高。
- [x] 减小 Ribbon 间距。
- [x] 减小代码块内边距。
- [x] 保留可用的点击目标尺寸。
- [x] 实现单色图标模式。
- [x] 将文件树图标颜色转换为弱化灰阶。
- [x] 保持激活状态可辨识。
- [x] 实现行宽控制。
- [x] 添加可读行宽变量滑块。
- [x] 支持从 `40rem``75rem` 的范围。
- [x] 实现强调色覆盖。
- [x] 为强调色、链接色、成功色、警告色与代码色添加颜色变量控件。
- [x] 验证覆盖值同时影响编辑器与 UI 变量。
- [x] 添加 Style Settings 元数据拼接构建步骤。
- [x] 定义确定性的文件顺序。
- [x] 将元数据追加到最终主题 CSS。
- [x] 验证生成后的 CSS 注释语法。
## 阶段 8资产与合规检查
- [x] 审计运行时 CSS 是否存在远程网络资源。
- [x] 移除外部 `@import` 引用。
- [x] 移除远程字体 URL。
- [x] 移除远程图片 URL。
- [x] 确定字体策略。
- [x] 默认使用系统字体栈。
- [x] 如需打包字体,则本地化字体文件并记录许可证。
- [x] 审计 `!important` 用法。
- [x] 移除可避免的声明。
- [x] 仅保留覆盖 Obsidian 或插件行为时确实必要的声明。
- [x] 审计选择器特异性。
- [x] 优先使用 Obsidian 变量。
- [x] 仅在变量不足时使用定向选择器。
- [x] 尽量避免脆弱的深层 DOM 选择器。
## 阶段 9测试与视觉 QA
- [x] 创建测试 Obsidian Vault。
- [x] 添加包含标题、列表、任务列表、链接、标签、Callout、表格、图片、嵌入、代码块与高亮的笔记。
- [x] 添加长段落笔记,用于检查阅读舒适度。
- [x] 添加关系图谱与 Canvas 示例内容。
- [ ] 测试深色模式。
- [x] 验证编辑器视图。
- [x] 验证阅读视图。
- [ ] 验证侧边栏。
- [ ] 验证命令面板。
- [ ] 验证设置弹窗。
- [ ] 验证关系图谱。
- [ ] 验证 Canvas。
- [ ] 测试浅色模式。
- [x] 验证编辑器视图。
- [x] 验证阅读视图。
- [ ] 验证侧边栏。
- [ ] 验证命令面板。
- [ ] 验证设置弹窗。
- [ ] 验证关系图谱。
- [ ] 验证 Canvas。
- [x] 测试 Style Settings 控件。
- [x] 验证每个滤镜类。
- [x] 验证紧凑模式。
- [x] 验证单色图标。
- [x] 验证行宽滑块。
- [x] 验证强调色覆盖。
- [x] 测试基础可访问性。
- [x] 检查文本对比度。
- [x] 检查焦点可见性。
- [x] 检查悬停与激活状态。
- [x] 检查浅色模式图标可见性。
- [x] 测试构建输出。
- [x] 运行 lint。
- [x] 运行开发构建。
- [x] 运行生产构建。
- [x] 确认生成的主题 CSS 可在 Obsidian 中加载。
## 阶段 9.5PIP 优化实现顺序
- [x] 实现 PIP 排版与高亮修复。
- [x] 在 `src/scss/components/_editor.scss` 中强化行内代码样式。
- [x] 修复 Live Preview 块引用 `>` 与左边框双线冲突。
- [x] 设置浅色模式 H1-H3 字重为 `700`
- [x] 设置深色模式 H1-H3 字重为 `600`
- [x] 加入高分屏文本边缘平滑策略。
- [x] 保持 Stylelint 规则,不使用 `!important`
- [x] 实现零插件图标系统。
- [x] 新增 `scripts/generate-icon-theme.js`
- [x] 读取 `icon-themes/Monokai Pro icon-theme.json``monokai-pro-icons.woff`
- [x] 提取 `iconDefinitions`、`fileExtensions`、`fileNames` 与文件夹图标映射。
- [x] 将 `monokai-pro-icons.woff` 转为 base64 字体数据。
- [x] 生成 `src/scss/components/_file-icons.generated.scss`
- [x] 新增 `src/scss/components/_file-icons.scss` 并从组件入口导入。
- [x] 使用 `[data-path$="..."]` 选择器为文件树扩展名匹配图标。
- [x] 使用文件名选择器覆盖 `package.json`、`README.md`、`TODO.md` 等特殊文件。
- [x] 实现文件夹、展开文件夹、根文件夹图标。
- [x] 支持单色图标模式与彩色图标模式。
- [x] 更新合规与构建检查。
- [x] 调整 `audit:css`,允许 `data:font/woff;base64` 本地内联字体。
- [x] 继续禁止远程 `http://`、`https://`、外部 `@import``!important`
- [x] 将图标生成脚本接入构建或发布前流程。
- [x] 运行 `npm run lint:css`
- [x] 运行 `npm run check:contrast`
- [x] 运行 `npm run build:vault`
- [x] 运行 `npm run audit:css`
- [x] 优化关系图谱交互响应。
- [x] 为图谱颜色、背景和高亮状态添加轻微过渡。
- [x] 为图谱控制按钮添加 hover 上移与点击复位动画。
- [x] 为图谱控制按钮添加点击时的轻微横向晃动反馈。
- [x] 为全局图谱与局部图谱面板添加聚焦反馈。
- [x] 移除关系图谱节点光晕,保持视觉 QA 前的干净底色。
- [x] 移除关系图谱容器空闲呼吸光晕。
- [x] 将关系图谱默认黑底调整为经典 Monokai Pro 深暖灰背景。
- [x] 将关系图谱底色对齐导航栏与标题栏背景。
- [x] 为关系图谱节点、连线、聚焦点、标签点与附件点应用经典 Monokai Pro 配色。
- [x] 支持 `prefers-reduced-motion`,减少动画偏好下关闭过渡与位移。
- [x] 完成深浅色阅读视图视觉 QA 微调。
- [x] 降低 Callout 背景视觉权重。
- [x] 增强表格边框清晰度。
- [x] 提高已完成任务状态可见性。
- [x] 为阅读视图块引用添加克制背景与更舒适的左侧间距。
## 阶段 9.6Active 视觉问题修复
> 来源:`C:\Users\Jie\iCloudDrive\iCloud~md~obsidian\SecondBrain\project\Monokai Syntax\Active\主题视觉问题汇总_ThemeVisualIssues.md`
- [x] 修复导航栏文件夹折叠图标重复问题。
- [x] 移除导致双折叠箭头的额外文件夹图标。
- [x] 第一层保留 Obsidian 默认折叠图标。
- [x] 第二层使用修改后的 Monokai 折叠图标。
- [x] 修复属性面板视觉不一致问题。
- [x] 调整笔记属性中属性值的底色。
- [x] 确保属性值背景与 Monokai Syntax 整体配色一致。
- [x] 重构编辑模式 Callout 样式。
- [x] 拉开绿色长条框体与文本之间的距离。
- [x] 使用基于 `#414339` 的低透明度内容背景。
- [x] 保持 Callout 内容背景与主背景 `#272822` 有区分但不过度抢眼。
- [x] 将 `note``info` 映射到蓝青色 `#66d9ef`
- [x] 将 `warning``caution` 映射到橙色 `#fd971f` 或黄色 `#e6db74`
- [x] 将 `error``danger` 映射到洋红色 `#f92672`
- [x] 将 `success``check` 映射到荧光绿 `#a6e22e`
- [x] 隐藏文档编辑区域最上方的冗余标题。
- [x] 确认编辑区域顶部不再重复显示当前文档标题。
- [x] 确认不影响标签页标题、文件列表标题与阅读视图标题。
- [x] 校准 Dark Mode 核心配色。
- [x] 对齐正文区域与 VS Code Monokai Pro 的主背景观感。
- [x] 对齐标题栏与 VS Code Monokai Pro 的界面层级。
- [x] 对齐导航栏与 VS Code Monokai Pro 的界面层级。
- [x] 优化 Terminal 插件面板样式。
- [x] 调整终端面板边距。
- [x] 让 Terminal 插件面板与主题整体间距系统一致。
- [x] 复核行内代码样式。
- [x] 暗色主题使用比正文背景稍浅的行内代码背景。
- [x] 亮色主题使用比正文背景稍深的行内代码背景。
- [x] 行内代码文字颜色统一使用 Monokai Pro 绿色。
- [x] 为特殊 Markdown 文件名添加专属图标。
- [x] `AGENTS.md` 显示专属图标。
- [x] `CLAUDE.md` 显示专属图标。
- [x] 确认专属图标与普通 Markdown 文件图标可区分。
## 阶段 9.79.6 视觉返工项
- [x] 调整行内代码样式。
- [x] 移除行内代码边框着色。
- [x] 保留行内代码与正文的背景区分。
- [x] 保留行内代码文字使用 Monokai Pro 绿色。
- [x] 移除笔记属性值背景色。
- [x] 去掉属性值容器的额外背景色。
- [x] 保持属性值文字、输入态和焦点态清晰可读。
- [x] 调整块引用样式。
- [x] 块引用左侧竖条改用 Monokai Pro 黄色。
- [x] 减小块引用左侧竖条粗细。
- [x] 加大块引用文字与左侧竖条之间的间距。
- [x] 恢复 Callout 到最开始版本的蓝色背景方案。
- [x] 使用早期蓝色背景变量作为 Callout 面板背景。
- [x] 保留必要的内容间距优化。
- [x] 复核编辑视图与阅读视图中的 Callout 可读性。
## 阶段 9.8:视觉反馈二次返工
- [x] 调整行内代码背景。
- [x] 行内代码背景色与当前明亮/黑暗模式下代码块背景色相同。
- [x] 保留行内代码文字使用 Monokai Pro 绿色。
- [x] 调整块引用样式。
- [x] 块引用左侧竖条改回 Monokai Pro 绿色。
- [x] 继续保持较细竖条。
- [x] 进一步加大块引用文字与竖条之间的距离。
- [x] 按截图调整 Callout 样式。
- [x] 使用 Monokai Pro 蓝青色作为标题、图标和边框语义色。
- [x] 使用同色低透明度背景形成浅蓝面板。
- [x] 保持边框、标题、正文在深色和浅色模式下都清晰。
- [x] 调整复选框颜色。
- [x] 未选中状态使用 Monokai Pro 黄色。
- [x] 选中状态使用 Monokai Pro 绿色。
- [x] 所有颜色均来自 Monokai Pro 调色板。
## 阶段 9.9Callout 与块引用间距微调
- [x] 调整 Callout 内边距。
- [x] Callout 边框内的标题与正文都保留清晰 padding。
- [x] Callout 正文与标题之间保留舒适间距。
- [x] 优化块引用文字间距。
- [x] 保留当前绿色背景与绿色细竖条方向。
- [x] 明显拉开块引用文字与左侧竖条距离。
- [x] 保持块引用正文可读,不让绿色背景过重。
## 阶段 10CI 与发布自动化
- [ ] 创建 GitHub Actions 工作流。
- [ ] 安装依赖。
- [ ] 运行 Stylelint。
- [ ] 运行生产构建。
- [ ] 打包 `theme.css`、`manifest.json` 与所需发布资产。
- [ ] 添加由语义化版本标签触发的发布工作流。
- [ ] 使用 `v1.0.0` 等标签触发。
- [ ] 将构建后的 CSS 与 manifest 附加到 GitHub Release。
- [ ] 构建产物缺失时让发布失败。
- [ ] 添加发布前检查清单。
- [ ] 确认各元数据文件中的版本号一致。
- [ ] 确认存在变更日志条目。
- [ ] 确认截图为最新版本。
- [ ] 确认没有引用远程资源。
## 阶段 11社区主题提交
- [ ] 创建官方要求的主题截图。
- [ ] 使用 512 x 288 分辨率。
- [ ] 使用 16:9 宽高比。
- [ ] 展示深浅两种模式,或制作具有代表性的高质量预览。
- [ ] 准备官方提交元数据。
- [ ] 主题名称:`Monokai Syntax`。
- [ ] 作者名称或 ID。
- [ ] 仓库 URL。
- [ ] 支持模式:`dark`、`light`。
- [ ] Fork `obsidianmd/obsidian-releases`
- [ ] 更新 `community-css-themes.json`
- [ ] 提交主题收录 Pull Request。
- [ ] 根据审核反馈进行调整。
- [ ] 审核通过后发布第一个稳定版本。
## 里程碑顺序
- [ ] 里程碑 1仓库脚手架可以构建空主题 CSS。
- [ ] 里程碑 2深色与浅色基础调色板可在 Obsidian 中正常工作。
- [ ] 里程碑 3Markdown 编辑器与阅读视图完成打磨。
- [ ] 里程碑 4Obsidian UI 界面符合 Monokai Syntax 视觉语言。
- [ ] 里程碑 5关系图谱、Canvas 与 Style Settings 可正常使用。
- [ ] 里程碑 6Lint、构建与发布自动化通过。
- [ ] 里程碑 7社区主题提交材料准备完成。

137
dist/theme.css vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,589 @@
# Callout 与块引用 Monokai Pro 风格重设计 — 实施计划
> **对于 agentic 执行者:** 推荐使用 superpowers:subagent-driven-development 或 superpowers:executing-plans 按任务逐步实施。步骤使用 `- [ ]` checkbox 跟踪。
**目标:** 将 Callout 和块引用样式重设计为 Monokai Pro 装订线式风格——彩色左竖线 + 极微背景 + 代码符号图标 + 宽松间距。
**架构:** 纯 SCSS/CSS 变量改动。新增 2 个块引用颜色 SCSS 变量8 个 callout 语义色 CSS 变量(深/浅各 4 组)。重写 `_editor.scss` 中 blockquote 和 callout 的阅读视图与实时预览样式。Callout 图标用 CSS `::before` + Unicode 代码符号替换默认 Lucide SVG。
**技术栈:** SCSS, Vite, Stylelint
---
## 文件结构
| 文件 | 职责 | 改动类型 |
|---|---|---|
| `src/scss/_variables.scss` | 新增 `$color-dark-blockquote-border`、`$color-light-blockquote-border` | 新增 2 行 |
| `src/scss/components/_editor.scss` | 新增 CSS 自定义属性 + 重写 blockquote/callout 全部样式 + callout 图标 | 重写 ~12 个规则块 |
---
### Task 1: 新增块引用暖金色 SCSS 变量
**文件:**
- 修改:`src/scss/_variables.scss:50`(在最后一个 `$spacing-4` 之后)
- [ ] **Step 1: 在 `_variables.scss` 末尾添加变量**
```scss
$color-dark-blockquote-border: #e6db74;
$color-light-blockquote-border: #cc7a0a;
```
- [ ] **Step 2: 提交**
```bash
git add src/scss/_variables.scss
git commit -m "feat: add blockquote warm-gold color tokens"
```
---
### Task 2: 替换编辑器 CSS 自定义属性(深色 + 浅色模式)
**文件:**
- 修改:`src/scss/components/_editor.scss:3-46``.markdown-source-view` 根块 + `.theme-dark` + `.theme-light` 块)
- [ ] **Step 1: 重写 `.markdown-source-view` 公共变量块**
`:3-22` 替换为:
```scss
.markdown-source-view,
.markdown-preview-view {
--bold-color: var(--text-normal);
--bold-weight: 900;
--italic-color: var(--text-normal);
--heading-spacing: #{$spacing-4};
--monokai-heading-weight: 600;
--monokai-inline-code-color: #a6e22e;
--monokai-inline-code-background: var(--code-background);
--blockquote-border-thickness: 2px;
--blockquote-background-color: transparent;
--callout-padding: 1.25rem;
--callout-content-padding: var(--spacing-4) 0 0 0;
--callout-border-width: 0;
--callout-radius: var(--radius-s);
--callout-blend-mode: normal;
--callout-icon-size: 1em;
--p-spacing: #{$spacing-3};
-webkit-font-smoothing: antialiased;
text-rendering: optimizelegibility;
}
```
关键变化:
- `--blockquote-background-color`: `var(--monokai-blockquote-background)``transparent`
- `--callout-padding`: `calc(...)``1.25rem`
- `--callout-border-width`: `1px``0`
- 新增 `--callout-icon-size: 1em`
- 移除 `--monokai-blockquote-background` 引用
- [ ] **Step 2: 重写 `.theme-dark` 块**
`:24-34` 替换为:
```scss
.theme-dark {
--monokai-heading-weight: 600;
--monokai-blockquote-border: #{$color-dark-blockquote-border};
--monokai-callout-note-bg: rgb(102 217 239 / 5%);
--monokai-callout-note-border: #66d9ef;
--monokai-callout-warning-bg: rgb(253 151 31 / 5%);
--monokai-callout-warning-border: #fd971f;
--monokai-callout-error-bg: rgb(249 38 114 / 5%);
--monokai-callout-error-border: #f92672;
--monokai-callout-success-bg: rgb(166 226 46 / 5%);
--monokai-callout-success-border: #a6e22e;
--monokai-inline-code-color: #a6e22e;
--monokai-inline-code-background: var(--code-background);
--monokai-table-border: rgb(248 248 242 / 18%);
--monokai-task-done-color: rgb(248 248 242 / 68%);
}
```
- [ ] **Step 3: 重写 `.theme-light` 块**
`:36-46` 替换为:
```scss
.theme-light {
--monokai-heading-weight: 700;
--monokai-blockquote-border: #{$color-light-blockquote-border};
--monokai-callout-note-bg: rgb(20 116 138 / 6%);
--monokai-callout-note-border: #14748a;
--monokai-callout-warning-bg: rgb(225 96 50 / 6%);
--monokai-callout-warning-border: #e16032;
--monokai-callout-error-bg: rgb(225 71 117 / 6%);
--monokai-callout-error-border: #e14775;
--monokai-callout-success-bg: rgb(38 157 105 / 6%);
--monokai-callout-success-border: #269d69;
--monokai-inline-code-color: #a6e22e;
--monokai-inline-code-background: var(--code-background);
--monokai-table-border: rgb(61 61 61 / 18%);
--monokai-task-done-color: rgb(61 61 61 / 66%);
}
```
- [ ] **Step 4: 提交**
```bash
git add src/scss/components/_editor.scss
git commit -m "feat: add Monokai Pro callout & blockquote CSS custom properties"
```
---
### Task 3: 重写阅读视图 blockquote 样式
**文件:**
- 修改:`src/scss/components/_editor.scss:77-83``.markdown-rendered blockquote`
- [ ] **Step 1: 替换 blockquote 规则**
将当前:
```scss
blockquote {
background-color: var(--monokai-blockquote-background);
border-inline-start: 2px solid var(--blockquote-border-color);
border-radius: 0 var(--radius-s) var(--radius-s) 0;
color: var(--text-normal);
padding: var(--spacing-4) var(--spacing-5) var(--spacing-4) calc(var(--spacing-5) + var(--spacing-5) + var(--spacing-3));
}
```
替换为:
```scss
blockquote {
background-color: transparent;
border-inline-start: 2px solid var(--monokai-blockquote-border);
border-radius: 0 var(--radius-s) var(--radius-s) 0;
color: var(--text-normal);
padding: var(--spacing-4) var(--spacing-4) var(--spacing-4) 1.5rem;
}
```
- [ ] **Step 2: 提交**
```bash
git add src/scss/components/_editor.scss
git commit -m "feat: redesign reading-view blockquote — warm gold border, no background"
```
---
### Task 4: 重写实时预览 blockquote 样式
**文件:**
- 修改:`src/scss/components/_editor.scss:214-230``.HyperMD-quote`、`.cm-quote`
- [ ] **Step 1: 替换 `.HyperMD-quote` 规则**
`:214-218`
```scss
.HyperMD-quote {
background-color: var(--monokai-blockquote-background);
border-inline-start: 2px solid var(--blockquote-border-color);
border-radius: 0 var(--radius-s) var(--radius-s) 0;
padding-inline-start: calc(var(--spacing-5) + var(--spacing-5) + var(--spacing-3));
}
```
替换为:
```scss
.HyperMD-quote {
background-color: transparent;
border-inline-start: 2px solid var(--monokai-blockquote-border);
border-radius: 0 var(--radius-s) var(--radius-s) 0;
padding-inline-start: 1.5rem;
}
```
- [ ] **Step 2: `.cm-quote` 和嵌套规则保持不变**
`.cm-quote``border-inline-start: 0` 保留。`.HyperMD-quote .cm-quote` 的 `padding-inline-start` 保留。
- [ ] **Step 3: 提交**
```bash
git add src/scss/components/_editor.scss
git commit -m "feat: redesign live-preview blockquote — warm gold border, no background"
```
---
### Task 5: 重写阅读视图 callout 容器 + 标题 + 内容
**文件:**
- 修改:`src/scss/components/_editor.scss:85-126``.markdown-rendered .callout` 相关规则)
- [ ] **Step 1: 重写 `.callout` 容器**
`:85-91`
```scss
.callout {
background-color: var(--monokai-callout-background);
border: 1px solid var(--monokai-callout-border);
border-inline-start: 0;
border-radius: var(--radius-s);
padding: var(--callout-padding);
}
```
替换为:
```scss
.callout {
background-color: var(--callout-background, transparent);
border: 0;
border-inline-start: 2px solid var(--callout-border, var(--monokai-callout-note-border));
border-radius: 0 var(--radius-s) var(--radius-s) 0;
padding: var(--callout-padding);
}
```
- [ ] **Step 2: 替换 `.callout-title`**
`:93-98`:
```scss
.callout-title {
color: var(--callout-color, #66d9ef);
font-weight: 700;
gap: var(--spacing-2);
padding-inline-start: 0;
}
```
替换为:
```scss
.callout-title {
color: var(--callout-border, var(--monokai-callout-note-border));
font-weight: 700;
gap: var(--spacing-2);
padding-inline-start: 0;
}
```
- [ ] **Step 3: 替换 `.callout-content`**
`:100-106`
```scss
.callout-content {
background-color: var(--monokai-callout-content-background);
border-radius: 0;
margin-block-start: var(--spacing-4);
padding: var(--callout-content-padding);
padding-inline-start: 0;
}
```
替换为:
```scss
.callout-content {
background-color: transparent;
border-radius: 0;
margin-block-start: var(--spacing-4);
padding: var(--callout-content-padding);
padding-inline-start: 0;
}
```
- [ ] **Step 4: 重写 callout 语义色分组映射**
`:108-126` 替换为:
```scss
.callout[data-callout="note"],
.callout[data-callout="info"] {
--callout-background: var(--monokai-callout-note-bg);
--callout-border: var(--monokai-callout-note-border);
}
.callout[data-callout="warning"],
.callout[data-callout="caution"] {
--callout-background: var(--monokai-callout-warning-bg);
--callout-border: var(--monokai-callout-warning-border);
}
.callout[data-callout="error"],
.callout[data-callout="danger"] {
--callout-background: var(--monokai-callout-error-bg);
--callout-border: var(--monokai-callout-error-border);
}
.callout[data-callout="success"],
.callout[data-callout="check"] {
--callout-background: var(--monokai-callout-success-bg);
--callout-border: var(--monokai-callout-success-border);
}
```
- [ ] **Step 5: 提交**
```bash
git add src/scss/components/_editor.scss
git commit -m "feat: redesign reading-view callout — semantic left border, micro-tinted background"
```
---
### Task 6: 重写实时预览 callout 容器 + 标题 + 内容
**文件:**
- 修改:`src/scss/components/_editor.scss:236-256``.markdown-source-view.mod-cm6 .callout` 相关规则)
- [ ] **Step 1: 替换实时预览 callout 容器**
`:236-242`:
```scss
.callout {
background-color: var(--monokai-callout-background);
border: 1px solid var(--monokai-callout-border);
border-inline-start: 0;
border-radius: var(--radius-s);
padding: var(--callout-padding);
}
```
替换为:
```scss
.callout {
background-color: var(--callout-background, transparent);
border: 0;
border-inline-start: 2px solid var(--callout-border, var(--monokai-callout-note-border));
border-radius: 0 var(--radius-s) var(--radius-s) 0;
padding: var(--callout-padding);
}
```
- [ ] **Step 2: 替换实时预览 callout-title**
`:244-249`:
```scss
.callout-title {
color: var(--callout-color, #66d9ef);
gap: var(--spacing-2);
padding-inline-start: 0;
font-weight: 700;
}
```
替换为:
```scss
.callout-title {
color: var(--callout-border, var(--monokai-callout-note-border));
gap: var(--spacing-2);
padding-inline-start: 0;
font-weight: 700;
}
```
- [ ] **Step 3: 替换实时预览 callout-content**
`:251-256`:
```scss
.callout-content {
background-color: var(--monokai-callout-content-background);
margin-block-start: var(--spacing-4);
padding: var(--callout-content-padding);
padding-inline-start: 0;
}
```
替换为:
```scss
.callout-content {
background-color: transparent;
margin-block-start: var(--spacing-4);
padding: var(--callout-content-padding);
padding-inline-start: 0;
}
```
- [ ] **Step 4: 提交**
```bash
git add src/scss/components/_editor.scss
git commit -m "feat: redesign live-preview callout container — semantic left border"
```
---
### Task 7: 重写 callout 图标 — 隐藏 Lucide SVG + 代码符号样式
**文件:**
- 修改:`src/scss/components/_editor.scss` — 在阅读视图 callout 语义色映射之后、在实时预览 callout 块之前插入
- [ ] **Step 1: 隐藏 Obsidian 默认图标 SVG**
`.markdown-rendered` 块内callout 语义色映射之后,添加:
```scss
.callout-icon svg {
display: none;
}
.callout-icon::before {
display: inline-block;
width: var(--callout-icon-size);
height: var(--callout-icon-size);
line-height: var(--callout-icon-size);
text-align: center;
font-family: var(--font-monospace-theme);
font-weight: 700;
font-size: var(--callout-icon-size);
color: var(--callout-border, var(--monokai-callout-note-border));
-webkit-font-smoothing: antialiased;
}
```
- [ ] **Step 2: 按 callout 分组设置代码符号**
紧接着添加:
```scss
.callout[data-callout="note"] .callout-icon::before,
.callout[data-callout="info"] .callout-icon::before {
content: "#[i]";
}
.callout[data-callout="warning"] .callout-icon::before,
.callout[data-callout="caution"] .callout-icon::before {
content: "<!>";
}
.callout[data-callout="error"] .callout-icon::before,
.callout[data-callout="danger"] .callout-icon::before {
content: "[\00D7]";
}
.callout[data-callout="success"] .callout-icon::before,
.callout[data-callout="check"] .callout-icon::before {
content: "{\2713}";
}
```
- [ ] **Step 3: 提交**
```bash
git add src/scss/components/_editor.scss
git commit -m "feat: replace callout icons with code-symbol style — #[i], <!>, [x], {v}"
```
---
### Task 8: 在实时预览块中应用相同的 callout 图标样式
**文件:**
- 修改:`src/scss/components/_editor.scss` — `.markdown-source-view.mod-cm6` 块内
- [ ] **Step 1: 在实时预览 callout 规则末尾添加图标样式**
`.markdown-source-view.mod-cm6` 块中callout 语义色映射之后,添加与 Task 7 相同的图标规则(复制隐藏 SVG + 基础 `::before` + 四个分组符号规则)。
- [ ] **Step 2: 提交**
```bash
git add src/scss/components/_editor.scss
git commit -m "feat: apply code-symbol callout icons to live preview"
```
---
### Task 9: 构建验证与 QA
**文件:** 无(只读检查)
- [ ] **Step 1: 运行 Stylelint**
```bash
npm run lint:css
```
预期PASS无错误。
- [ ] **Step 2: 运行生产构建**
```bash
npm run build
```
预期PASS`dist/theme.css` 生成成功。
- [ ] **Step 3: 运行 CSS 审计**
```bash
npm run audit:css
```
预期PASS无远程资源、无 `!important`
- [ ] **Step 4: 运行对比度检查**
```bash
npm run check:contrast
```
预期PASS。
- [ ] **Step 5: 构建到测试 Vault**
```bash
npm run build:vault
```
预期PASS主题同步到 Obsidian Vault。
- [ ] **Step 6: 手动 QA 检查清单**
在 Obsidian 中打开测试笔记,验证:
**暗色模式 — 块引用:**
- [ ] 暖金色竖线,无背景
- [ ] 文字与竖线间距舒适
- [ ] 嵌套块引用层级清晰
**暗色模式 — Callout 图标:**
- [ ] note/info显示青色 `#[i]`,等宽字体
- [ ] warning/caution显示橙色 `<!>`
- [ ] error/danger显示洋红 `[×]`
- [ ] success/check显示绿色 `{✓}`
- [ ] 默认 Lucide 图标已隐藏
**暗色模式 — Callout 容器:**
- [ ] note/info青色竖线 + 极微青色背景
- [ ] warning/caution橙色竖线 + 极微橙色背景
- [ ] error/danger洋红竖线 + 极微洋红背景
- [ ] success/check绿色竖线 + 极微绿色背景
- [ ] 标题颜色跟随语义色
**浅色模式:**
- [ ] 同上所有项
**实时预览:**
- [ ] 块引用样式与阅读模式一致
- [ ] Callout 容器 + 图标 + 标题 + 内容与阅读模式一致
- [ ] **Step 7: 提交**(如有 QA 发现修复)
```bash
git add src/scss/components/_editor.scss
git commit -m "fix: QA adjustments for callout & blockquote"
```

View file

@ -0,0 +1,142 @@
# Callout 与块引用 Monokai Pro 风格重设计
**日期:** 2026-05-08
**状态:** 已确认
## 动机
当前 Callout 和块引用样式在五个方面偏离 Monokai Pro 设计语言:
1. **背景色太重** — 11%-18% 透明度的着色叠加层像独立面板
2. **缺少左竖线强调** — VS Code Monokai Pro 使用装订线式彩色强调,而非四边描边的卡片
3. **颜色语义不对** — 块引用用了绿色而非 Monokai Pro 标志性暖金Callout 标题统一青色,无视类型差异
4. **太"卡片化"** — 四边圆角描边使元素与正文割裂
5. **间距太紧** — 边框到内容的 padding 不足
此外Callout 图标使用 Obsidian 默认的 Lucide 图标,未与 Monokai Pro 代码编辑器主题风格对齐。
## 设计目标
- 元素与正文**融合**,而非漂浮的独立卡片
- 颜色作为**精确强调**(左侧竖线),而非整片铺底
- 语义色对齐 Monokai Pro 调色板
- 间距宽松舒适
- Callout 图标采用**代码符号风格**,匹配 Monokai Pro 作为代码编辑器主题的血统
## 涉及范围
三个文件:
| 文件 | 改动 |
|---|---|
| `src/scss/_variables.scss` | 新增块引用暖金色 SCSS 变量(深浅模式各一) |
| `src/scss/components/_editor.scss` | 重写 `.HyperMD-quote`、`.cm-quote`、`.callout` 样式块,新增 callout 图标样式 |
## 颜色映射
### 块引用
| 模式 | 竖线颜色 | 背景 |
|---|---|---|
| 暗色 | `#e6db74`Monokai Pro 黄) | 无 |
| 浅色 | `#cc7a0a`Monokai Pro 黄) | 无 |
### Callout按语义分组
| 分组 | 颜色 | 暗色 Hex | 浅色 Hex |
|---|---|---|---|
| note / info | 青 Cyan | `#66d9ef` | `#14748a` |
| warning / caution | 橙 Orange | `#fd971f` | `#e16032` |
| error / danger | 洋红 Magenta | `#f92672` | `#e14775` |
| success / check | 绿 Green | `#a6e22e` | `#269d69` |
## 元素规格
### 块引用
- **边框:** 左侧 2px 实线暖金色;其余三边无边框
- **背景:** 无(透明)
- **圆角:** 右侧 `var(--radius-s)`,左侧直角
- **内边距:** `1rem 1rem 1rem 1.5rem`——文字与竖线之间留足间距
- **文字颜色:** `var(--text-normal)`
### Callout 容器
- **边框:** 左侧 2px 实线语义色;其余三边无边框
- **背景:** 与语义色同色相的极微着色
- 暗色:`rgb(<color> / 5%)`
- 浅色:`rgb(<color> / 6%)`
- **圆角:** 右侧 `var(--radius-s)`,左侧直角
- **内边距:** `1.25rem`
- **标题:** `font-weight: 700`,颜色跟随语义分组
- **内容区间距:** 标题与正文之间 1rem 间距
### Callout 图标(代码符号风格)
隐藏 Obsidian 默认的 Lucide SVG 图标,改用 `::before` 伪元素渲染代码风格符号:
| Callout 分组 | 符号 | 含义 |
|---|---|---|
| note / info | `#[i]` | 预处理指令风格,`#` + 信息标识 `i` |
| warning / caution | `<!>` | HTML 警告标记风格,`!` 居中于尖括号 |
| error / danger | `[×]` | 断言失败风格,`×` 居中于方括号 |
| success / check | `{✓}` | 返回成功风格,`✓` 居中于花括号 |
- **字体:** `var(--font-monospace-theme)` 等宽字体,确保符号对齐
- **颜色:** 跟随各分组的语义色
- **大小:** 与 callout 标题文字大小一致
- **渲染行为:** 纯 Unicode 字符 + CSS `content`,零外部依赖,无需修改 woff 字体文件
- **图标 SVG 处理:** `.callout-icon` 内的 `svg` 元素 `display: none`,改用 `.callout-icon::before` 显示符号
## 暗色模式 CSS 变量
```css
--monokai-blockquote-border: #e6db74;
--monokai-callout-note-bg: rgb(102 217 239 / 5%);
--monokai-callout-note-border: #66d9ef;
--monokai-callout-warning-bg: rgb(253 151 31 / 5%);
--monokai-callout-warning-border: #fd971f;
--monokai-callout-error-bg: rgb(249 38 114 / 5%);
--monokai-callout-error-border: #f92672;
--monokai-callout-success-bg: rgb(166 226 46 / 5%);
--monokai-callout-success-border: #a6e22e;
```
## 浅色模式 CSS 变量
```css
--monokai-blockquote-border: #cc7a0a;
--monokai-callout-note-bg: rgb(20 116 138 / 6%);
--monokai-callout-note-border: #14748a;
--monokai-callout-warning-bg: rgb(225 96 50 / 6%);
--monokai-callout-warning-border: #e16032;
--monokai-callout-error-bg: rgb(225 71 117 / 6%);
--monokai-callout-error-border: #e14775;
--monokai-callout-success-bg: rgb(38 157 105 / 6%);
--monokai-callout-success-border: #269d69;
```
## 需修改的选择器
### 阅读视图(`.markdown-rendered`
- `blockquote` — 竖线颜色改为 `var(--monokai-blockquote-border)`,移除背景
- `.callout` — 四边描边替换为仅左侧语义色竖线
- `.callout-title` — 继承语义色作为文字颜色
- `.callout-content` — 继承语义色背景
- `.callout-icon svg``display: none` 隐藏默认图标
- `.callout-icon::before` — 显示代码风格符号,使用 `content` + 等宽字体 + 语义色
- `.callout[data-callout="..."] .callout-icon::before` — 按分组设置不同符号
### 实时预览(`HyperMD-quote`、`.cm-quote`
- `.HyperMD-quote` — 与阅读视图一致:暖金竖线,无背景
- `.cm-quote` — 继承样式
- `.callout` — 与阅读视图一致(容器 + 图标 + 标题 + 内容)
## 不在范围内
- Callout 折叠/展开行为(保持不变)
- Style Settings 集成(无需新增开关)
- Canvas 内嵌 Callout非现有功能
- 修改 `monokai-pro-icons.woff` 字体文件(不需要,用 Unicode 字符实现)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

6
manifest.json Normal file
View file

@ -0,0 +1,6 @@
{
"name": "Monokai Syntax",
"version": "1.0.0",
"minAppVersion": "1.0.0",
"author": "lat3ncy"
}

16
node_modules/.bin/cssesc generated vendored Normal file
View file

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../cssesc/bin/cssesc" "$@"
else
exec node "$basedir/../cssesc/bin/cssesc" "$@"
fi

17
node_modules/.bin/cssesc.cmd generated vendored Normal file
View file

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\cssesc\bin\cssesc" %*

28
node_modules/.bin/cssesc.ps1 generated vendored Normal file
View file

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../cssesc/bin/cssesc" $args
} else {
& "$basedir/node$exe" "$basedir/../cssesc/bin/cssesc" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../cssesc/bin/cssesc" $args
} else {
& "node$exe" "$basedir/../cssesc/bin/cssesc" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/js-yaml generated vendored Normal file
View file

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../js-yaml/bin/js-yaml.js" "$@"
else
exec node "$basedir/../js-yaml/bin/js-yaml.js" "$@"
fi

17
node_modules/.bin/js-yaml.cmd generated vendored Normal file
View file

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\js-yaml\bin\js-yaml.js" %*

28
node_modules/.bin/js-yaml.ps1 generated vendored Normal file
View file

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../js-yaml/bin/js-yaml.js" $args
} else {
& "$basedir/node$exe" "$basedir/../js-yaml/bin/js-yaml.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../js-yaml/bin/js-yaml.js" $args
} else {
& "node$exe" "$basedir/../js-yaml/bin/js-yaml.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/nanoid generated vendored Normal file
View file

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../nanoid/bin/nanoid.cjs" "$@"
else
exec node "$basedir/../nanoid/bin/nanoid.cjs" "$@"
fi

17
node_modules/.bin/nanoid.cmd generated vendored Normal file
View file

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\nanoid\bin\nanoid.cjs" %*

28
node_modules/.bin/nanoid.ps1 generated vendored Normal file
View file

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
} else {
& "$basedir/node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
} else {
& "node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/rolldown generated vendored Normal file
View file

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../rolldown/bin/cli.mjs" "$@"
else
exec node "$basedir/../rolldown/bin/cli.mjs" "$@"
fi

17
node_modules/.bin/rolldown.cmd generated vendored Normal file
View file

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\rolldown\bin\cli.mjs" %*

28
node_modules/.bin/rolldown.ps1 generated vendored Normal file
View file

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../rolldown/bin/cli.mjs" $args
} else {
& "$basedir/node$exe" "$basedir/../rolldown/bin/cli.mjs" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../rolldown/bin/cli.mjs" $args
} else {
& "node$exe" "$basedir/../rolldown/bin/cli.mjs" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/sass generated vendored Normal file
View file

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../sass/sass.js" "$@"
else
exec node "$basedir/../sass/sass.js" "$@"
fi

17
node_modules/.bin/sass.cmd generated vendored Normal file
View file

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\sass\sass.js" %*

28
node_modules/.bin/sass.ps1 generated vendored Normal file
View file

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../sass/sass.js" $args
} else {
& "$basedir/node$exe" "$basedir/../sass/sass.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../sass/sass.js" $args
} else {
& "node$exe" "$basedir/../sass/sass.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/stylelint generated vendored Normal file
View file

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../stylelint/bin/stylelint.mjs" "$@"
else
exec node "$basedir/../stylelint/bin/stylelint.mjs" "$@"
fi

17
node_modules/.bin/stylelint.cmd generated vendored Normal file
View file

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\stylelint\bin\stylelint.mjs" %*

28
node_modules/.bin/stylelint.ps1 generated vendored Normal file
View file

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../stylelint/bin/stylelint.mjs" $args
} else {
& "$basedir/node$exe" "$basedir/../stylelint/bin/stylelint.mjs" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../stylelint/bin/stylelint.mjs" $args
} else {
& "node$exe" "$basedir/../stylelint/bin/stylelint.mjs" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/vite generated vendored Normal file
View file

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../vite/bin/vite.js" "$@"
else
exec node "$basedir/../vite/bin/vite.js" "$@"
fi

17
node_modules/.bin/vite.cmd generated vendored Normal file
View file

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\vite\bin\vite.js" %*

28
node_modules/.bin/vite.ps1 generated vendored Normal file
View file

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../vite/bin/vite.js" $args
} else {
& "$basedir/node$exe" "$basedir/../vite/bin/vite.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../vite/bin/vite.js" $args
} else {
& "node$exe" "$basedir/../vite/bin/vite.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/which generated vendored Normal file
View file

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../which/bin/which" "$@"
else
exec node "$basedir/../which/bin/which" "$@"
fi

17
node_modules/.bin/which.cmd generated vendored Normal file
View file

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\which\bin\which" %*

28
node_modules/.bin/which.ps1 generated vendored Normal file
View file

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../which/bin/which" $args
} else {
& "$basedir/node$exe" "$basedir/../which/bin/which" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../which/bin/which" $args
} else {
& "node$exe" "$basedir/../which/bin/which" $args
}
$ret=$LASTEXITCODE
}
exit $ret

2114
node_modules/.package-lock.json generated vendored Normal file

File diff suppressed because it is too large Load diff

22
node_modules/@babel/code-frame/LICENSE generated vendored Normal file
View file

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

19
node_modules/@babel/code-frame/README.md generated vendored Normal file
View file

@ -0,0 +1,19 @@
# @babel/code-frame
> Generate errors that contain a code frame that point to source locations.
See our website [@babel/code-frame](https://babeljs.io/docs/babel-code-frame) for more information.
## Install
Using npm:
```sh
npm install --save-dev @babel/code-frame
```
or using yarn:
```sh
yarn add @babel/code-frame --dev
```

217
node_modules/@babel/code-frame/lib/index.js generated vendored Normal file
View file

@ -0,0 +1,217 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var picocolors = require('picocolors');
var jsTokens = require('js-tokens');
var helperValidatorIdentifier = require('@babel/helper-validator-identifier');
function isColorSupported() {
return (typeof process === "object" && (process.env.FORCE_COLOR === "0" || process.env.FORCE_COLOR === "false") ? false : picocolors.isColorSupported
);
}
const compose = (f, g) => v => f(g(v));
function buildDefs(colors) {
return {
keyword: colors.cyan,
capitalized: colors.yellow,
jsxIdentifier: colors.yellow,
punctuator: colors.yellow,
number: colors.magenta,
string: colors.green,
regex: colors.magenta,
comment: colors.gray,
invalid: compose(compose(colors.white, colors.bgRed), colors.bold),
gutter: colors.gray,
marker: compose(colors.red, colors.bold),
message: compose(colors.red, colors.bold),
reset: colors.reset
};
}
const defsOn = buildDefs(picocolors.createColors(true));
const defsOff = buildDefs(picocolors.createColors(false));
function getDefs(enabled) {
return enabled ? defsOn : defsOff;
}
const sometimesKeywords = new Set(["as", "async", "from", "get", "of", "set"]);
const NEWLINE$1 = /\r\n|[\n\r\u2028\u2029]/;
const BRACKET = /^[()[\]{}]$/;
let tokenize;
const JSX_TAG = /^[a-z][\w-]*$/i;
const getTokenType = function (token, offset, text) {
if (token.type === "name") {
const tokenValue = token.value;
if (helperValidatorIdentifier.isKeyword(tokenValue) || helperValidatorIdentifier.isStrictReservedWord(tokenValue, true) || sometimesKeywords.has(tokenValue)) {
return "keyword";
}
if (JSX_TAG.test(tokenValue) && (text[offset - 1] === "<" || text.slice(offset - 2, offset) === "</")) {
return "jsxIdentifier";
}
const firstChar = String.fromCodePoint(tokenValue.codePointAt(0));
if (firstChar !== firstChar.toLowerCase()) {
return "capitalized";
}
}
if (token.type === "punctuator" && BRACKET.test(token.value)) {
return "bracket";
}
if (token.type === "invalid" && (token.value === "@" || token.value === "#")) {
return "punctuator";
}
return token.type;
};
tokenize = function* (text) {
let match;
while (match = jsTokens.default.exec(text)) {
const token = jsTokens.matchToToken(match);
yield {
type: getTokenType(token, match.index, text),
value: token.value
};
}
};
function highlight(text) {
if (text === "") return "";
const defs = getDefs(true);
let highlighted = "";
for (const {
type,
value
} of tokenize(text)) {
if (type in defs) {
highlighted += value.split(NEWLINE$1).map(str => defs[type](str)).join("\n");
} else {
highlighted += value;
}
}
return highlighted;
}
let deprecationWarningShown = false;
const NEWLINE = /\r\n|[\n\r\u2028\u2029]/;
function getMarkerLines(loc, source, opts, startLineBaseZero) {
const startLoc = Object.assign({
column: 0,
line: -1
}, loc.start);
const endLoc = Object.assign({}, startLoc, loc.end);
const {
linesAbove = 2,
linesBelow = 3
} = opts || {};
const startLine = startLoc.line - startLineBaseZero;
const startColumn = startLoc.column;
const endLine = endLoc.line - startLineBaseZero;
const endColumn = endLoc.column;
let start = Math.max(startLine - (linesAbove + 1), 0);
let end = Math.min(source.length, endLine + linesBelow);
if (startLine === -1) {
start = 0;
}
if (endLine === -1) {
end = source.length;
}
const lineDiff = endLine - startLine;
const markerLines = {};
if (lineDiff) {
for (let i = 0; i <= lineDiff; i++) {
const lineNumber = i + startLine;
if (!startColumn) {
markerLines[lineNumber] = true;
} else if (i === 0) {
const sourceLength = source[lineNumber - 1].length;
markerLines[lineNumber] = [startColumn, sourceLength - startColumn + 1];
} else if (i === lineDiff) {
markerLines[lineNumber] = [0, endColumn];
} else {
const sourceLength = source[lineNumber - i].length;
markerLines[lineNumber] = [0, sourceLength];
}
}
} else {
if (startColumn === endColumn) {
if (startColumn) {
markerLines[startLine] = [startColumn, 0];
} else {
markerLines[startLine] = true;
}
} else {
markerLines[startLine] = [startColumn, endColumn - startColumn];
}
}
return {
start,
end,
markerLines
};
}
function codeFrameColumns(rawLines, loc, opts = {}) {
const shouldHighlight = opts.forceColor || isColorSupported() && opts.highlightCode;
const startLineBaseZero = (opts.startLine || 1) - 1;
const defs = getDefs(shouldHighlight);
const lines = rawLines.split(NEWLINE);
const {
start,
end,
markerLines
} = getMarkerLines(loc, lines, opts, startLineBaseZero);
const hasColumns = loc.start && typeof loc.start.column === "number";
const numberMaxWidth = String(end + startLineBaseZero).length;
const highlightedLines = shouldHighlight ? highlight(rawLines) : rawLines;
let frame = highlightedLines.split(NEWLINE, end).slice(start, end).map((line, index) => {
const number = start + 1 + index;
const paddedNumber = ` ${number + startLineBaseZero}`.slice(-numberMaxWidth);
const gutter = ` ${paddedNumber} |`;
const hasMarker = markerLines[number];
const lastMarkerLine = !markerLines[number + 1];
if (hasMarker) {
let markerLine = "";
if (Array.isArray(hasMarker)) {
const markerSpacing = line.slice(0, Math.max(hasMarker[0] - 1, 0)).replace(/[^\t]/g, " ");
const numberOfMarkers = hasMarker[1] || 1;
markerLine = ["\n ", defs.gutter(gutter.replace(/\d/g, " ")), " ", markerSpacing, defs.marker("^").repeat(numberOfMarkers)].join("");
if (lastMarkerLine && opts.message) {
markerLine += " " + defs.message(opts.message);
}
}
return [defs.marker(">"), defs.gutter(gutter), line.length > 0 ? ` ${line}` : "", markerLine].join("");
} else {
return ` ${defs.gutter(gutter)}${line.length > 0 ? ` ${line}` : ""}`;
}
}).join("\n");
if (opts.message && !hasColumns) {
frame = `${" ".repeat(numberMaxWidth + 1)}${opts.message}\n${frame}`;
}
if (shouldHighlight) {
return defs.reset(frame);
} else {
return frame;
}
}
function index (rawLines, lineNumber, colNumber, opts = {}) {
if (!deprecationWarningShown) {
deprecationWarningShown = true;
const message = "Passing lineNumber and colNumber is deprecated to @babel/code-frame. Please use `codeFrameColumns`.";
if (process.emitWarning) {
process.emitWarning(message, "DeprecationWarning");
} else {
const deprecationError = new Error(message);
deprecationError.name = "DeprecationWarning";
console.warn(new Error(message));
}
}
colNumber = Math.max(colNumber, 0);
const location = {
start: {
column: colNumber,
line: lineNumber
}
};
return codeFrameColumns(rawLines, location, opts);
}
exports.codeFrameColumns = codeFrameColumns;
exports.default = index;
exports.highlight = highlight;
//# sourceMappingURL=index.js.map

1
node_modules/@babel/code-frame/lib/index.js.map generated vendored Normal file

File diff suppressed because one or more lines are too long

32
node_modules/@babel/code-frame/package.json generated vendored Normal file
View file

@ -0,0 +1,32 @@
{
"name": "@babel/code-frame",
"version": "7.29.0",
"description": "Generate errors that contain a code frame that point to source locations.",
"author": "The Babel Team (https://babel.dev/team)",
"homepage": "https://babel.dev/docs/en/next/babel-code-frame",
"bugs": "https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-code-frame"
},
"main": "./lib/index.js",
"dependencies": {
"@babel/helper-validator-identifier": "^7.28.5",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
"devDependencies": {
"charcodes": "^0.2.0",
"import-meta-resolve": "^4.1.0",
"strip-ansi": "^4.0.0"
},
"engines": {
"node": ">=6.9.0"
},
"type": "commonjs"
}

View file

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,19 @@
# @babel/helper-validator-identifier
> Validate identifier/keywords name
See our website [@babel/helper-validator-identifier](https://babeljs.io/docs/babel-helper-validator-identifier) for more information.
## Install
Using npm:
```sh
npm install --save @babel/helper-validator-identifier
```
or using yarn:
```sh
yarn add @babel/helper-validator-identifier
```

View file

@ -0,0 +1,70 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.isIdentifierChar = isIdentifierChar;
exports.isIdentifierName = isIdentifierName;
exports.isIdentifierStart = isIdentifierStart;
let nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u037f\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u052f\u0531-\u0556\u0559\u0560-\u0588\u05d0-\u05ea\u05ef-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u0860-\u086a\u0870-\u0887\u0889-\u088f\u08a0-\u08c9\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u09fc\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0af9\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c39\u0c3d\u0c58-\u0c5a\u0c5c\u0c5d\u0c60\u0c61\u0c80\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cdc-\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d04-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d54-\u0d56\u0d5f-\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e86-\u0e8a\u0e8c-\u0ea3\u0ea5\u0ea7-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f5\u13f8-\u13fd\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f8\u1700-\u1711\u171f-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1878\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191e\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19b0-\u19c9\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4c\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1c80-\u1c8a\u1c90-\u1cba\u1cbd-\u1cbf\u1ce9-\u1cec\u1cee-\u1cf3\u1cf5\u1cf6\u1cfa\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2118-\u211d\u2124\u2126\u2128\u212a-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309b-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312f\u3131-\u318e\u31a0-\u31bf\u31f0-\u31ff\u3400-\u4dbf\u4e00-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua69d\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua7dc\ua7f1-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua8fd\ua8fe\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\ua9e0-\ua9e4\ua9e6-\ua9ef\ua9fa-\ua9fe\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa7e-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uab30-\uab5a\uab5c-\uab69\uab70-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc";
let nonASCIIidentifierChars = "\xb7\u0300-\u036f\u0387\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u0669\u0670\u06d6-\u06dc\u06df-\u06e4\u06e7\u06e8\u06ea-\u06ed\u06f0-\u06f9\u0711\u0730-\u074a\u07a6-\u07b0\u07c0-\u07c9\u07eb-\u07f3\u07fd\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0859-\u085b\u0897-\u089f\u08ca-\u08e1\u08e3-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09cb-\u09cd\u09d7\u09e2\u09e3\u09e6-\u09ef\u09fe\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2\u0ae3\u0ae6-\u0aef\u0afa-\u0aff\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b55-\u0b57\u0b62\u0b63\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c00-\u0c04\u0c3c\u0c3e-\u0c44\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0c66-\u0c6f\u0c81-\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0ce6-\u0cef\u0cf3\u0d00-\u0d03\u0d3b\u0d3c\u0d3e-\u0d44\u0d46-\u0d48\u0d4a-\u0d4d\u0d57\u0d62\u0d63\u0d66-\u0d6f\u0d81-\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0de6-\u0def\u0df2\u0df3\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0e50-\u0e59\u0eb1\u0eb4-\u0ebc\u0ec8-\u0ece\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f3e\u0f3f\u0f71-\u0f84\u0f86\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u102b-\u103e\u1040-\u1049\u1056-\u1059\u105e-\u1060\u1062-\u1064\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u1369-\u1371\u1712-\u1715\u1732-\u1734\u1752\u1753\u1772\u1773\u17b4-\u17d3\u17dd\u17e0-\u17e9\u180b-\u180d\u180f-\u1819\u18a9\u1920-\u192b\u1930-\u193b\u1946-\u194f\u19d0-\u19da\u1a17-\u1a1b\u1a55-\u1a5e\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1ab0-\u1abd\u1abf-\u1add\u1ae0-\u1aeb\u1b00-\u1b04\u1b34-\u1b44\u1b50-\u1b59\u1b6b-\u1b73\u1b80-\u1b82\u1ba1-\u1bad\u1bb0-\u1bb9\u1be6-\u1bf3\u1c24-\u1c37\u1c40-\u1c49\u1c50-\u1c59\u1cd0-\u1cd2\u1cd4-\u1ce8\u1ced\u1cf4\u1cf7-\u1cf9\u1dc0-\u1dff\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2cef-\u2cf1\u2d7f\u2de0-\u2dff\u302a-\u302f\u3099\u309a\u30fb\ua620-\ua629\ua66f\ua674-\ua67d\ua69e\ua69f\ua6f0\ua6f1\ua802\ua806\ua80b\ua823-\ua827\ua82c\ua880\ua881\ua8b4-\ua8c5\ua8d0-\ua8d9\ua8e0-\ua8f1\ua8ff-\ua909\ua926-\ua92d\ua947-\ua953\ua980-\ua983\ua9b3-\ua9c0\ua9d0-\ua9d9\ua9e5\ua9f0-\ua9f9\uaa29-\uaa36\uaa43\uaa4c\uaa4d\uaa50-\uaa59\uaa7b-\uaa7d\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uaaeb-\uaaef\uaaf5\uaaf6\uabe3-\uabea\uabec\uabed\uabf0-\uabf9\ufb1e\ufe00-\ufe0f\ufe20-\ufe2f\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f\uff65";
const nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]");
const nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]");
nonASCIIidentifierStartChars = nonASCIIidentifierChars = null;
const astralIdentifierStartCodes = [0, 11, 2, 25, 2, 18, 2, 1, 2, 14, 3, 13, 35, 122, 70, 52, 268, 28, 4, 48, 48, 31, 14, 29, 6, 37, 11, 29, 3, 35, 5, 7, 2, 4, 43, 157, 19, 35, 5, 35, 5, 39, 9, 51, 13, 10, 2, 14, 2, 6, 2, 1, 2, 10, 2, 14, 2, 6, 2, 1, 4, 51, 13, 310, 10, 21, 11, 7, 25, 5, 2, 41, 2, 8, 70, 5, 3, 0, 2, 43, 2, 1, 4, 0, 3, 22, 11, 22, 10, 30, 66, 18, 2, 1, 11, 21, 11, 25, 7, 25, 39, 55, 7, 1, 65, 0, 16, 3, 2, 2, 2, 28, 43, 28, 4, 28, 36, 7, 2, 27, 28, 53, 11, 21, 11, 18, 14, 17, 111, 72, 56, 50, 14, 50, 14, 35, 39, 27, 10, 22, 251, 41, 7, 1, 17, 5, 57, 28, 11, 0, 9, 21, 43, 17, 47, 20, 28, 22, 13, 52, 58, 1, 3, 0, 14, 44, 33, 24, 27, 35, 30, 0, 3, 0, 9, 34, 4, 0, 13, 47, 15, 3, 22, 0, 2, 0, 36, 17, 2, 24, 20, 1, 64, 6, 2, 0, 2, 3, 2, 14, 2, 9, 8, 46, 39, 7, 3, 1, 3, 21, 2, 6, 2, 1, 2, 4, 4, 0, 19, 0, 13, 4, 31, 9, 2, 0, 3, 0, 2, 37, 2, 0, 26, 0, 2, 0, 45, 52, 19, 3, 21, 2, 31, 47, 21, 1, 2, 0, 185, 46, 42, 3, 37, 47, 21, 0, 60, 42, 14, 0, 72, 26, 38, 6, 186, 43, 117, 63, 32, 7, 3, 0, 3, 7, 2, 1, 2, 23, 16, 0, 2, 0, 95, 7, 3, 38, 17, 0, 2, 0, 29, 0, 11, 39, 8, 0, 22, 0, 12, 45, 20, 0, 19, 72, 200, 32, 32, 8, 2, 36, 18, 0, 50, 29, 113, 6, 2, 1, 2, 37, 22, 0, 26, 5, 2, 1, 2, 31, 15, 0, 24, 43, 261, 18, 16, 0, 2, 12, 2, 33, 125, 0, 80, 921, 103, 110, 18, 195, 2637, 96, 16, 1071, 18, 5, 26, 3994, 6, 582, 6842, 29, 1763, 568, 8, 30, 18, 78, 18, 29, 19, 47, 17, 3, 32, 20, 6, 18, 433, 44, 212, 63, 33, 24, 3, 24, 45, 74, 6, 0, 67, 12, 65, 1, 2, 0, 15, 4, 10, 7381, 42, 31, 98, 114, 8702, 3, 2, 6, 2, 1, 2, 290, 16, 0, 30, 2, 3, 0, 15, 3, 9, 395, 2309, 106, 6, 12, 4, 8, 8, 9, 5991, 84, 2, 70, 2, 1, 3, 0, 3, 1, 3, 3, 2, 11, 2, 0, 2, 6, 2, 64, 2, 3, 3, 7, 2, 6, 2, 27, 2, 3, 2, 4, 2, 0, 4, 6, 2, 339, 3, 24, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 7, 1845, 30, 7, 5, 262, 61, 147, 44, 11, 6, 17, 0, 322, 29, 19, 43, 485, 27, 229, 29, 3, 0, 208, 30, 2, 2, 2, 1, 2, 6, 3, 4, 10, 1, 225, 6, 2, 3, 2, 1, 2, 14, 2, 196, 60, 67, 8, 0, 1205, 3, 2, 26, 2, 1, 2, 0, 3, 0, 2, 9, 2, 3, 2, 0, 2, 0, 7, 0, 5, 0, 2, 0, 2, 0, 2, 2, 2, 1, 2, 0, 3, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 1, 2, 0, 3, 3, 2, 6, 2, 3, 2, 3, 2, 0, 2, 9, 2, 16, 6, 2, 2, 4, 2, 16, 4421, 42719, 33, 4381, 3, 5773, 3, 7472, 16, 621, 2467, 541, 1507, 4938, 6, 8489];
const astralIdentifierCodes = [509, 0, 227, 0, 150, 4, 294, 9, 1368, 2, 2, 1, 6, 3, 41, 2, 5, 0, 166, 1, 574, 3, 9, 9, 7, 9, 32, 4, 318, 1, 78, 5, 71, 10, 50, 3, 123, 2, 54, 14, 32, 10, 3, 1, 11, 3, 46, 10, 8, 0, 46, 9, 7, 2, 37, 13, 2, 9, 6, 1, 45, 0, 13, 2, 49, 13, 9, 3, 2, 11, 83, 11, 7, 0, 3, 0, 158, 11, 6, 9, 7, 3, 56, 1, 2, 6, 3, 1, 3, 2, 10, 0, 11, 1, 3, 6, 4, 4, 68, 8, 2, 0, 3, 0, 2, 3, 2, 4, 2, 0, 15, 1, 83, 17, 10, 9, 5, 0, 82, 19, 13, 9, 214, 6, 3, 8, 28, 1, 83, 16, 16, 9, 82, 12, 9, 9, 7, 19, 58, 14, 5, 9, 243, 14, 166, 9, 71, 5, 2, 1, 3, 3, 2, 0, 2, 1, 13, 9, 120, 6, 3, 6, 4, 0, 29, 9, 41, 6, 2, 3, 9, 0, 10, 10, 47, 15, 199, 7, 137, 9, 54, 7, 2, 7, 17, 9, 57, 21, 2, 13, 123, 5, 4, 0, 2, 1, 2, 6, 2, 0, 9, 9, 49, 4, 2, 1, 2, 4, 9, 9, 55, 9, 266, 3, 10, 1, 2, 0, 49, 6, 4, 4, 14, 10, 5350, 0, 7, 14, 11465, 27, 2343, 9, 87, 9, 39, 4, 60, 6, 26, 9, 535, 9, 470, 0, 2, 54, 8, 3, 82, 0, 12, 1, 19628, 1, 4178, 9, 519, 45, 3, 22, 543, 4, 4, 5, 9, 7, 3, 6, 31, 3, 149, 2, 1418, 49, 513, 54, 5, 49, 9, 0, 15, 0, 23, 4, 2, 14, 1361, 6, 2, 16, 3, 6, 2, 1, 2, 4, 101, 0, 161, 6, 10, 9, 357, 0, 62, 13, 499, 13, 245, 1, 2, 9, 233, 0, 3, 0, 8, 1, 6, 0, 475, 6, 110, 6, 6, 9, 4759, 9, 787719, 239];
function isInAstralSet(code, set) {
let pos = 0x10000;
for (let i = 0, length = set.length; i < length; i += 2) {
pos += set[i];
if (pos > code) return false;
pos += set[i + 1];
if (pos >= code) return true;
}
return false;
}
function isIdentifierStart(code) {
if (code < 65) return code === 36;
if (code <= 90) return true;
if (code < 97) return code === 95;
if (code <= 122) return true;
if (code <= 0xffff) {
return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code));
}
return isInAstralSet(code, astralIdentifierStartCodes);
}
function isIdentifierChar(code) {
if (code < 48) return code === 36;
if (code < 58) return true;
if (code < 65) return false;
if (code <= 90) return true;
if (code < 97) return code === 95;
if (code <= 122) return true;
if (code <= 0xffff) {
return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code));
}
return isInAstralSet(code, astralIdentifierStartCodes) || isInAstralSet(code, astralIdentifierCodes);
}
function isIdentifierName(name) {
let isFirst = true;
for (let i = 0; i < name.length; i++) {
let cp = name.charCodeAt(i);
if ((cp & 0xfc00) === 0xd800 && i + 1 < name.length) {
const trail = name.charCodeAt(++i);
if ((trail & 0xfc00) === 0xdc00) {
cp = 0x10000 + ((cp & 0x3ff) << 10) + (trail & 0x3ff);
}
}
if (isFirst) {
isFirst = false;
if (!isIdentifierStart(cp)) {
return false;
}
} else if (!isIdentifierChar(cp)) {
return false;
}
}
return !isFirst;
}
//# sourceMappingURL=identifier.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,57 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "isIdentifierChar", {
enumerable: true,
get: function () {
return _identifier.isIdentifierChar;
}
});
Object.defineProperty(exports, "isIdentifierName", {
enumerable: true,
get: function () {
return _identifier.isIdentifierName;
}
});
Object.defineProperty(exports, "isIdentifierStart", {
enumerable: true,
get: function () {
return _identifier.isIdentifierStart;
}
});
Object.defineProperty(exports, "isKeyword", {
enumerable: true,
get: function () {
return _keyword.isKeyword;
}
});
Object.defineProperty(exports, "isReservedWord", {
enumerable: true,
get: function () {
return _keyword.isReservedWord;
}
});
Object.defineProperty(exports, "isStrictBindOnlyReservedWord", {
enumerable: true,
get: function () {
return _keyword.isStrictBindOnlyReservedWord;
}
});
Object.defineProperty(exports, "isStrictBindReservedWord", {
enumerable: true,
get: function () {
return _keyword.isStrictBindReservedWord;
}
});
Object.defineProperty(exports, "isStrictReservedWord", {
enumerable: true,
get: function () {
return _keyword.isStrictReservedWord;
}
});
var _identifier = require("./identifier.js");
var _keyword = require("./keyword.js");
//# sourceMappingURL=index.js.map

View file

@ -0,0 +1 @@
{"version":3,"names":["_identifier","require","_keyword"],"sources":["../src/index.ts"],"sourcesContent":["export {\n isIdentifierName,\n isIdentifierChar,\n isIdentifierStart,\n} from \"./identifier.ts\";\nexport {\n isReservedWord,\n isStrictBindOnlyReservedWord,\n isStrictBindReservedWord,\n isStrictReservedWord,\n isKeyword,\n} from \"./keyword.ts\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,WAAA,GAAAC,OAAA;AAKA,IAAAC,QAAA,GAAAD,OAAA","ignoreList":[]}

View file

@ -0,0 +1,35 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.isKeyword = isKeyword;
exports.isReservedWord = isReservedWord;
exports.isStrictBindOnlyReservedWord = isStrictBindOnlyReservedWord;
exports.isStrictBindReservedWord = isStrictBindReservedWord;
exports.isStrictReservedWord = isStrictReservedWord;
const reservedWords = {
keyword: ["break", "case", "catch", "continue", "debugger", "default", "do", "else", "finally", "for", "function", "if", "return", "switch", "throw", "try", "var", "const", "while", "with", "new", "this", "super", "class", "extends", "export", "import", "null", "true", "false", "in", "instanceof", "typeof", "void", "delete"],
strict: ["implements", "interface", "let", "package", "private", "protected", "public", "static", "yield"],
strictBind: ["eval", "arguments"]
};
const keywords = new Set(reservedWords.keyword);
const reservedWordsStrictSet = new Set(reservedWords.strict);
const reservedWordsStrictBindSet = new Set(reservedWords.strictBind);
function isReservedWord(word, inModule) {
return inModule && word === "await" || word === "enum";
}
function isStrictReservedWord(word, inModule) {
return isReservedWord(word, inModule) || reservedWordsStrictSet.has(word);
}
function isStrictBindOnlyReservedWord(word) {
return reservedWordsStrictBindSet.has(word);
}
function isStrictBindReservedWord(word, inModule) {
return isStrictReservedWord(word, inModule) || isStrictBindOnlyReservedWord(word);
}
function isKeyword(word) {
return keywords.has(word);
}
//# sourceMappingURL=keyword.js.map

View file

@ -0,0 +1 @@
{"version":3,"names":["reservedWords","keyword","strict","strictBind","keywords","Set","reservedWordsStrictSet","reservedWordsStrictBindSet","isReservedWord","word","inModule","isStrictReservedWord","has","isStrictBindOnlyReservedWord","isStrictBindReservedWord","isKeyword"],"sources":["../src/keyword.ts"],"sourcesContent":["const reservedWords = {\n keyword: [\n \"break\",\n \"case\",\n \"catch\",\n \"continue\",\n \"debugger\",\n \"default\",\n \"do\",\n \"else\",\n \"finally\",\n \"for\",\n \"function\",\n \"if\",\n \"return\",\n \"switch\",\n \"throw\",\n \"try\",\n \"var\",\n \"const\",\n \"while\",\n \"with\",\n \"new\",\n \"this\",\n \"super\",\n \"class\",\n \"extends\",\n \"export\",\n \"import\",\n \"null\",\n \"true\",\n \"false\",\n \"in\",\n \"instanceof\",\n \"typeof\",\n \"void\",\n \"delete\",\n ],\n strict: [\n \"implements\",\n \"interface\",\n \"let\",\n \"package\",\n \"private\",\n \"protected\",\n \"public\",\n \"static\",\n \"yield\",\n ],\n strictBind: [\"eval\", \"arguments\"],\n};\nconst keywords = new Set(reservedWords.keyword);\nconst reservedWordsStrictSet = new Set(reservedWords.strict);\nconst reservedWordsStrictBindSet = new Set(reservedWords.strictBind);\n\n/**\n * Checks if word is a reserved word in non-strict mode\n */\nexport function isReservedWord(word: string, inModule: boolean): boolean {\n return (inModule && word === \"await\") || word === \"enum\";\n}\n\n/**\n * Checks if word is a reserved word in non-binding strict mode\n *\n * Includes non-strict reserved words\n */\nexport function isStrictReservedWord(word: string, inModule: boolean): boolean {\n return isReservedWord(word, inModule) || reservedWordsStrictSet.has(word);\n}\n\n/**\n * Checks if word is a reserved word in binding strict mode, but it is allowed as\n * a normal identifier.\n */\nexport function isStrictBindOnlyReservedWord(word: string): boolean {\n return reservedWordsStrictBindSet.has(word);\n}\n\n/**\n * Checks if word is a reserved word in binding strict mode\n *\n * Includes non-strict reserved words and non-binding strict reserved words\n */\nexport function isStrictBindReservedWord(\n word: string,\n inModule: boolean,\n): boolean {\n return (\n isStrictReservedWord(word, inModule) || isStrictBindOnlyReservedWord(word)\n );\n}\n\nexport function isKeyword(word: string): boolean {\n return keywords.has(word);\n}\n"],"mappings":";;;;;;;;;;AAAA,MAAMA,aAAa,GAAG;EACpBC,OAAO,EAAE,CACP,OAAO,EACP,MAAM,EACN,OAAO,EACP,UAAU,EACV,UAAU,EACV,SAAS,EACT,IAAI,EACJ,MAAM,EACN,SAAS,EACT,KAAK,EACL,UAAU,EACV,IAAI,EACJ,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,KAAK,EACL,KAAK,EACL,OAAO,EACP,OAAO,EACP,MAAM,EACN,KAAK,EACL,MAAM,EACN,OAAO,EACP,OAAO,EACP,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,MAAM,EACN,OAAO,EACP,IAAI,EACJ,YAAY,EACZ,QAAQ,EACR,MAAM,EACN,QAAQ,CACT;EACDC,MAAM,EAAE,CACN,YAAY,EACZ,WAAW,EACX,KAAK,EACL,SAAS,EACT,SAAS,EACT,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,OAAO,CACR;EACDC,UAAU,EAAE,CAAC,MAAM,EAAE,WAAW;AAClC,CAAC;AACD,MAAMC,QAAQ,GAAG,IAAIC,GAAG,CAACL,aAAa,CAACC,OAAO,CAAC;AAC/C,MAAMK,sBAAsB,GAAG,IAAID,GAAG,CAACL,aAAa,CAACE,MAAM,CAAC;AAC5D,MAAMK,0BAA0B,GAAG,IAAIF,GAAG,CAACL,aAAa,CAACG,UAAU,CAAC;AAK7D,SAASK,cAAcA,CAACC,IAAY,EAAEC,QAAiB,EAAW;EACvE,OAAQA,QAAQ,IAAID,IAAI,KAAK,OAAO,IAAKA,IAAI,KAAK,MAAM;AAC1D;AAOO,SAASE,oBAAoBA,CAACF,IAAY,EAAEC,QAAiB,EAAW;EAC7E,OAAOF,cAAc,CAACC,IAAI,EAAEC,QAAQ,CAAC,IAAIJ,sBAAsB,CAACM,GAAG,CAACH,IAAI,CAAC;AAC3E;AAMO,SAASI,4BAA4BA,CAACJ,IAAY,EAAW;EAClE,OAAOF,0BAA0B,CAACK,GAAG,CAACH,IAAI,CAAC;AAC7C;AAOO,SAASK,wBAAwBA,CACtCL,IAAY,EACZC,QAAiB,EACR;EACT,OACEC,oBAAoB,CAACF,IAAI,EAAEC,QAAQ,CAAC,IAAIG,4BAA4B,CAACJ,IAAI,CAAC;AAE9E;AAEO,SAASM,SAASA,CAACN,IAAY,EAAW;EAC/C,OAAOL,QAAQ,CAACQ,GAAG,CAACH,IAAI,CAAC;AAC3B","ignoreList":[]}

View file

@ -0,0 +1,31 @@
{
"name": "@babel/helper-validator-identifier",
"version": "7.28.5",
"description": "Validate identifier/keywords name",
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-helper-validator-identifier"
},
"license": "MIT",
"publishConfig": {
"access": "public"
},
"main": "./lib/index.js",
"exports": {
".": {
"types": "./lib/index.d.ts",
"default": "./lib/index.js"
},
"./package.json": "./package.json"
},
"devDependencies": {
"@unicode/unicode-17.0.0": "^1.6.10",
"charcodes": "^0.2.0"
},
"engines": {
"node": ">=6.9.0"
},
"author": "The Babel Team (https://babel.dev/team)",
"type": "commonjs"
}

19
node_modules/@cacheable/memory/LICENSE generated vendored Normal file
View file

@ -0,0 +1,19 @@
MIT License & © Jared Wray
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

340
node_modules/@cacheable/memory/README.md generated vendored Normal file
View file

@ -0,0 +1,340 @@
[<img align="center" src="https://cacheable.org/logo.svg" alt="Cacheable" />](https://github.com/jaredwray/cacheable)
> High Performance Layer 1 / Layer 2 Caching with Keyv Storage
[![codecov](https://codecov.io/gh/jaredwray/cacheable/branch/main/graph/badge.svg?token=lWZ9OBQ7GM)](https://codecov.io/gh/jaredwray/cacheable)
[![tests](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml/badge.svg)](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml)
[![npm](https://img.shields.io/npm/dm/@cacheable/memory.svg)](https://www.npmjs.com/package/@cacheable/memory)
[![npm](https://img.shields.io/npm/v/@cacheable/memory.svg)](https://www.npmjs.com/package/@cacheable/memory)
[![license](https://img.shields.io/github/license/jaredwray/cacheable)](https://github.com/jaredwray/cacheable/blob/main/LICENSE)
You can use `CacheableMemory` as a standalone cache or as a primary store for `cacheable`. You can also set the `useClones` property to `false` if you want to use the same reference for the values. This is useful if you are using large objects and want to save memory. The `lruSize` property is the size of the LRU cache and is set to `0` by default which is unlimited. When setting the `lruSize` property it will limit the number of keys in the cache.
This simple in-memory cache uses multiple Map objects and a with `expiration` and `lru` policies if set to manage the in memory cache at scale.
By default we use lazy expiration deletion which means on `get` and `getMany` type functions we look if it is expired and then delete it. If you want to have a more aggressive expiration policy you can set the `checkInterval` property to a value greater than `0` which will check for expired keys at the interval you set.
Here are some of the main features of `CacheableMemory`:
* High performance in-memory cache with a robust API and feature set. 🚀
* Can scale past the `16,777,216 (2^24) keys` limit of a single `Map` via `hashStoreSize`. Default is `16` Map objects.
* LRU (Least Recently Used) cache feature to limit the number of keys in the cache via `lruSize`. Limit to `16,777,216 (2^24) keys` total.
* Expiration policy to delete expired keys with lazy deletion or aggressive deletion via `checkInterval`.
* `Wrap` feature to memoize `sync` and `async` functions with stampede protection.
* Ability to do many operations at once such as `setMany`, `getMany`, `deleteMany`, and `takeMany`.
* Supports `raw` data retrieval with `getRaw` and `getManyRaw` methods to get the full metadata of the cache entry.
# Table of Contents
* [Getting Started](#getting-started)
* [CacheableMemory - In-Memory Cache](#cacheablememory---in-memory-cache)
* [CacheableMemory Store Hashing](#cacheablememory-store-hashing)
* [CacheableMemory LRU Feature](#cacheablememory-lru-feature)
* [CacheableMemory Performance](#cacheablememory-performance)
* [CacheableMemory Options](#cacheablememory-options)
* [CacheableMemory - API](#cacheablememory---api)
* [Keyv Storage Adapter - KeyvCacheableMemory](#keyv-storage-adapter---keyvcacheablememory)
* [Wrap / Memoization for Sync and Async Functions](#wrap--memoization-for-sync-and-async-functions)
* [Get Or Set Memoization Function](#get-or-set-memoization-function)
* [How to Contribute](#how-to-contribute)
* [License and Copyright](#license-and-copyright)
# Getting Started
```bash
npm install @cacheable/memory
```
# Basic Usage
```javascript
import { CacheableMemory } from '@cacheable/memory';
const cacheable = new CacheableMemory();
await cacheable.set('key', 'value', 1000);
const value = await cacheable.get('key');
```
In this example, the primary store we will use `lru-cache` and the secondary store is Redis. You can also set multiple stores in the options:
```javascript
import { CacheableMemory } from '@cacheable/memory';
// we set the storeHashSize to 1 so that we only use a single Map object as the lru is limited to a single Map size
const cache = new CacheableMemory({storeHashSize: 1, lruSize: 80000});
cache.set('key1', 'value1');
const result = cache.get('key1');
console.log(result); // 'value1'
```
This is a more advanced example and not needed for most use cases.
# Shorthand for Time to Live (ttl)
By default `Cacheable` and `CacheableMemory` the `ttl` is in milliseconds but you can use shorthand for the time to live. Here are the following shorthand values:
* `ms`: Milliseconds such as (1ms = 1)
* `s`: Seconds such as (1s = 1000)
* `m`: Minutes such as (1m = 60000)
* `h` or `hr`: Hours such as (1h = 3600000)
* `d`: Days such as (1d = 86400000)
Here is an example of how to use the shorthand for the `ttl`:
```javascript
import { CacheableMemory } from 'cacheable';
const cache = new CacheableMemory({ ttl: '15m' }); //sets the default ttl to 15 minutes (900000 ms)
cache.set('key', 'value', '1h'); //sets the ttl to 1 hour (3600000 ms) and overrides the default
```
if you want to disable the `ttl` you can set it to `0` or `undefined`:
```javascript
import { CacheableMemory } from 'cacheable';
const cache = new CacheableMemory({ ttl: 0 }); //sets the default ttl to 0 which is disabled
cache.set('key', 'value', 0); //sets the ttl to 0 which is disabled
```
If you set the ttl to anything below `0` or `undefined` it will disable the ttl for the cache and the value that returns will be `undefined`. With no ttl set the value will be stored `indefinitely`.
```javascript
import { CacheableMemory } from 'cacheable';
const cache = new CacheableMemory({ ttl: 0 }); //sets the default ttl to 0 which is disabled
console.log(cache.ttl); // undefined
cache.ttl = '1h'; // sets the default ttl to 1 hour (3600000 ms)
console.log(cache.ttl); // '1h'
cache.ttl = -1; // sets the default ttl to 0 which is disabled
console.log(cache.ttl); // undefined
```
## Retrieving raw cache entries
The `getRaw` and `getManyRaw` methods return the full stored metadata (`StoredDataRaw<T>`) instead of just the value:
```typescript
import { CacheableMemory } from 'cacheable';
const cache = new CacheableMemory();
// store a value
await cache.set('user:1', { name: 'Alice' }, '1h'); // 1 hour
// default: only the value
const user = await cache.get<{ name: string }>('user:1');
console.log(user); // { name: 'Alice' }
// with raw: full record including expiration
const raw = await cache.getRaw('user:1');
console.log(raw.value); // { name: 'Alice' }
console.log(raw.expires); // e.g. 1677628495000 or null
```
## CacheableMemory Store Hashing
`CacheableMemory` uses `Map` objects to store the keys and values. To make this scale past the `16,777,216 (2^24) keys` limit of a single `Map` we use a hash to balance the data across multiple `Map` objects. This is done by hashing the key and using the hash to determine which `Map` object to use. The default hashing algorithm is `djb2` but you can change it by setting the `storeHashAlgorithm` property in the options. Supported algorithms include DJB2, FNV1, MURMER, and CRC32. By default we set the amount of `Map` objects to `16`.
NOTE: if you are using the LRU cache feature the `lruSize` no matter how many `Map` objects you have it will be limited to the `16,777,216 (2^24) keys` limit of a single `Map` object. This is because we use a double linked list to manage the LRU cache and it is not possible to have more than `16,777,216 (2^24) keys` in a single `Map` object.
Here is an example of how to set the number of `Map` objects and the hashing algorithm:
```javascript
import { CacheableMemory } from '@cacheable/memory';
const cache = new CacheableMemory({
storeSize: 32, // set the number of Map objects to 32
});
cache.set('key', 'value');
const value = cache.get('key'); // value
```
Here is an example of how to use the `storeHashAlgorithm` property with supported algorithms:
```javascript
import { CacheableMemory, HashAlgorithm } from '@cacheable/memory';
// Using DJB2 (default)
const cache = new CacheableMemory({ storeHashAlgorithm: HashAlgorithm.DJB2 });
// Or other non-cryptographic algorithms for better performance
const cache2 = new CacheableMemory({ storeHashAlgorithm: HashAlgorithm.FNV1 });
const cache3 = new CacheableMemory({ storeHashAlgorithm: HashAlgorithm.MURMER });
const cache4 = new CacheableMemory({ storeHashAlgorithm: HashAlgorithm.CRC32 });
cache.set('key', 'value');
const value = cache.get('key'); // value
```
**Available algorithms:** DJB2 (default), FNV1, MURMER, CRC32. Note: Cryptographic algorithms (SHA-256, SHA-384, SHA-512) are not recommended for store hashing due to performance overhead.
If you want to provide your own hashing function you can set the `storeHashAlgorithm` property to a function that takes an object and returns a `number` that is in the range of the amount of `Map` stores you have.
```javascript
import { CacheableMemory } from 'cacheable';
/**
* Custom hash function that takes a key and the size of the store
* and returns a number between 0 and storeHashSize - 1.
* @param {string} key - The key to hash.
* @param {number} storeHashSize - The size of the store (number of Map objects).
* @returns {number} - A number between 0 and storeHashSize - 1.
*/
const customHash = (key, storeHashSize) => {
// custom hashing logic
return key.length % storeHashSize; // returns a number between 0 and 31 for 32 Map objects
};
const cache = new CacheableMemory({ storeHashAlgorithm: customHash, storeSize: 32 });
cache.set('key', 'value');
const value = cache.get('key'); // value
```
## CacheableMemory LRU Feature
You can enable the LRU (Least Recently Used) feature in `CacheableMemory` by setting the `lruSize` property in the options. This will limit the number of keys in the cache to the size you set. When the cache reaches the limit it will remove the least recently used keys from the cache. This is useful if you want to limit the memory usage of the cache.
When you set the `lruSize` we use a double linked list to manage the LRU cache and also set the `hashStoreSize` to `1` which means we will only use a single `Map` object for the LRU cache. This is because the LRU cache is managed by the double linked list and it is not possible to have more than `16,777,216 (2^24) keys` in a single `Map` object.
```javascript
import { CacheableMemory } from 'cacheable';
const cache = new CacheableMemory({ lruSize: 1 }); // sets the LRU cache size to 1000 keys and hashStoreSize to 1
cache.set('key1', 'value1');
cache.set('key2', 'value2');
const value1 = cache.get('key1');
console.log(value1); // undefined if the cache is full and key1 is the least recently used
const value2 = cache.get('key2');
console.log(value2); // value2 if key2 is still in the cache
console.log(cache.size()); // 1
```
NOTE: if you set the `lruSize` property to `0` after it was enabled it will disable the LRU cache feature and will not limit the number of keys in the cache. This will remove the `16,777,216 (2^24) keys` limit of a single `Map` object and will allow you to store more keys in the cache.
## CacheableMemory Performance
Our goal with `cacheable` and `CacheableMemory` is to provide a high performance caching engine that is simple to use and has a robust API. We test it against other cacheing engines such that are less feature rich to make sure there is little difference. Here are some of the benchmarks we have run:
*Memory Benchmark Results:*
| name | summary | ops/sec | time/op | margin | samples |
|------------------------------------------|:---------:|----------:|----------:|:--------:|----------:|
| Cacheable Memory (v1.10.0) - set / get | 🥇 | 152K | 7µs | ±0.94% | 147K |
| Map (v22) - set / get | -1.1% | 151K | 7µs | ±0.69% | 145K |
| Node Cache - set / get | -4.3% | 146K | 7µs | ±1.13% | 142K |
| bentocache (v1.4.0) - set / get | -20% | 121K | 8µs | ±0.40% | 119K |
*Memory LRU Benchmark Results:*
| name | summary | ops/sec | time/op | margin | samples |
|------------------------------------------|:---------:|----------:|----------:|:--------:|----------:|
| quick-lru (v7.0.1) - set / get | 🥇 | 118K | 9µs | ±0.85% | 112K |
| Map (v22) - set / get | -0.56% | 117K | 9µs | ±1.35% | 110K |
| lru.min (v1.1.2) - set / get | -1.7% | 116K | 9µs | ±0.90% | 110K |
| Cacheable Memory (v1.10.0) - set / get | -3.3% | 114K | 9µs | ±1.16% | 108K |
As you can see from the benchmarks `CacheableMemory` is on par with other caching engines such as `Map`, `Node Cache`, and `bentocache`. We have also tested it against other LRU caching engines such as `quick-lru` and `lru.min` and it performs well against them too.
## CacheableMemory Options
* `ttl`: The time to live for the cache in milliseconds. Default is `undefined` which is means indefinitely.
* `useClones`: If the cache should use clones for the values. Default is `true`.
* `lruSize`: The size of the LRU cache. Default is `0` which is unlimited.
* `checkInterval`: The interval to check for expired keys in milliseconds. Default is `0` which is disabled.
* `storeHashSize`: The number of `Map` objects to use for the cache. Default is `16`.
* `storeHashAlgorithm`: The hashing algorithm to use for the cache. Default is `djb2`. Supported: DJB2, FNV1, MURMER, CRC32.
## CacheableMemory - API
* `set(key, value, ttl?)`: Sets a value in the cache.
* `setMany([{key, value, ttl?}])`: Sets multiple values in the cache from `CacheableItem`.
* `get(key)`: Gets a value from the cache.
* `getMany([keys])`: Gets multiple values from the cache.
* `getRaw(key)`: Gets a value from the cache as `CacheableStoreItem`.
* `getManyRaw([keys])`: Gets multiple values from the cache as `CacheableStoreItem`.
* `has(key)`: Checks if a value exists in the cache.
* `hasMany([keys])`: Checks if multiple values exist in the cache.
* `delete(key)`: Deletes a value from the cache.
* `deleteMany([keys])`: Deletes multiple values from the cache.
* `take(key)`: Takes a value from the cache and deletes it.
* `takeMany([keys])`: Takes multiple values from the cache and deletes them.
* `wrap(function, WrapSyncOptions)`: Wraps a `sync` function in a cache.
* `clear()`: Clears the cache.
* `ttl`: The default time to live for the cache in milliseconds. Default is `undefined` which is disabled.
* `useClones`: If the cache should use clones for the values. Default is `true`.
* `lruSize`: The size of the LRU cache. Default is `0` which is unlimited.
* `size`: The number of keys in the cache.
* `checkInterval`: The interval to check for expired keys in milliseconds. Default is `0` which is disabled.
* `storeHashSize`: The number of `Map` objects to use for the cache. Default is `16`.
* `storeHashAlgorithm`: The hashing algorithm to use for the cache. Default is `djb2`. Supported: DJB2, FNV1, MURMER, CRC32.
* `keys`: Get the keys in the cache. Not able to be set.
* `items`: Get the items in the cache as `CacheableStoreItem` example `{ key, value, expires? }`.
* `store`: The hash store for the cache which is an array of `Map` objects.
* `checkExpired()`: Checks for expired keys in the cache. This is used by the `checkInterval` property.
* `startIntervalCheck()`: Starts the interval check for expired keys if `checkInterval` is above 0 ms.
* `stopIntervalCheck()`: Stops the interval check for expired keys.
# Keyv Storage Adapter - KeyvCacheableMemory
`cacheable` comes with a built-in storage adapter for Keyv called `KeyvCacheableMemory`. This takes `CacheableMemory` and creates a storage adapter for Keyv. This is useful if you want to use `CacheableMemory` as a storage adapter for Keyv. Here is an example of how to use `KeyvCacheableMemory`:
```javascript
import { Keyv } from 'keyv';
import { KeyvCacheableMemory } from 'cacheable';
const keyv = new Keyv({ store: new KeyvCacheableMemory() });
await keyv.set('foo', 'bar');
const value = await keyv.get('foo');
console.log(value); // bar
```
# Wrap / Memoization for Sync and Async Functions
`CacheableMemory` has a feature called `wrap` that allows you to wrap a function in a cache. This is useful for memoization and caching the results of a function. You can wrap a `sync` function in a cache. Here is an example of how to use the `wrap` function:
```javascript
import { CacheableMemory } from 'cacheable';
const syncFunction = (value: number) => {
return value * 2;
};
const cache = new CacheableMemory();
const wrappedFunction = cache.wrap(syncFunction, { ttl: '1h', key: 'syncFunction' });
console.log(wrappedFunction(2)); // 4
console.log(wrappedFunction(2)); // 4 from cache
```
In this example we are wrapping a `sync` function in a cache with a `ttl` of `1 hour`. This will cache the result of the function for `1 hour` and then expire the value. You can also set the `key` property in the `wrap()` options to set a custom key for the cache.
When an error occurs in the function it will not cache the value and will return the error. This is useful if you want to cache the results of a function but not cache the error. If you want it to cache the error you can set the `cacheError` property to `true` in the `wrap()` options. This is disabled by default.
```javascript
import { CacheableMemory } from 'cacheable';
const syncFunction = (value: number) => {
throw new Error('error');
};
const cache = new CacheableMemory();
const wrappedFunction = cache.wrap(syncFunction, { ttl: '1h', key: 'syncFunction', cacheError: true });
console.log(wrappedFunction()); // error
console.log(wrappedFunction()); // error from cache
```
If you would like to generate your own key for the wrapped function you can set the `createKey` property in the `wrap()` options. This is useful if you want to generate a key based on the arguments of the function or any other criteria.
```javascript
const cache = new CacheableMemory();
const options: WrapOptions = {
cache,
keyPrefix: 'test',
createKey: (function_, arguments_, options: WrapOptions) => `customKey:${options?.keyPrefix}:${arguments_[0]}`,
};
const wrapped = wrap((argument: string) => `Result for ${argument}`, options);
const result1 = wrapped('arg1');
const result2 = wrapped('arg1'); // Should hit the cache
console.log(result1); // Result for arg1
console.log(result2); // Result for arg1 (from cache)
```
We will pass in the `function` that is being wrapped, the `arguments` passed to the function, and the `options` used to wrap the function. You can then use these to generate a custom key for the cache.
# How to Contribute
You can contribute by forking the repo and submitting a pull request. Please make sure to add tests and update the documentation. To learn more about how to contribute go to our main README [https://github.com/jaredwray/cacheable](https://github.com/jaredwray/cacheable). This will talk about how to `Open a Pull Request`, `Ask a Question`, or `Post an Issue`.
# License and Copyright
[MIT © Jared Wray](./LICENSE)

830
node_modules/@cacheable/memory/dist/index.cjs generated vendored Normal file
View file

@ -0,0 +1,830 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
CacheableMemory: () => CacheableMemory,
HashAlgorithm: () => import_utils2.HashAlgorithm,
KeyvCacheableMemory: () => KeyvCacheableMemory,
createKeyv: () => createKeyv,
defaultStoreHashSize: () => defaultStoreHashSize,
hash: () => import_utils2.hash,
hashToNumber: () => import_utils2.hashToNumber,
maximumMapSize: () => maximumMapSize
});
module.exports = __toCommonJS(index_exports);
var import_utils = require("@cacheable/utils");
var import_hookified = require("hookified");
// src/memory-lru.ts
var ListNode = class {
value;
prev = void 0;
next = void 0;
constructor(value) {
this.value = value;
}
};
var DoublyLinkedList = class {
head = void 0;
tail = void 0;
nodesMap = /* @__PURE__ */ new Map();
// Add a new node to the front (most recently used)
addToFront(value) {
const newNode = new ListNode(value);
if (this.head) {
newNode.next = this.head;
this.head.prev = newNode;
this.head = newNode;
} else {
this.head = this.tail = newNode;
}
this.nodesMap.set(value, newNode);
}
// Move an existing node to the front (most recently used)
moveToFront(value) {
const node = this.nodesMap.get(value);
if (!node || this.head === node) {
return;
}
if (node.prev) {
node.prev.next = node.next;
}
if (node.next) {
node.next.prev = node.prev;
}
if (node === this.tail) {
this.tail = node.prev;
}
node.prev = void 0;
node.next = this.head;
if (this.head) {
this.head.prev = node;
}
this.head = node;
this.tail ??= node;
}
// Get the oldest node (tail)
getOldest() {
return this.tail ? this.tail.value : void 0;
}
// Remove the oldest node (tail)
removeOldest() {
if (!this.tail) {
return void 0;
}
const oldValue = this.tail.value;
if (this.tail.prev) {
this.tail = this.tail.prev;
this.tail.next = void 0;
} else {
this.head = this.tail = void 0;
}
this.nodesMap.delete(oldValue);
return oldValue;
}
// Remove a specific node by value
remove(value) {
const node = this.nodesMap.get(value);
if (!node) {
return false;
}
if (node.prev) {
node.prev.next = node.next;
} else {
this.head = node.next;
if (this.head) {
this.head.prev = void 0;
}
}
if (node.next) {
node.next.prev = node.prev;
} else {
this.tail = node.prev;
if (this.tail) {
this.tail.next = void 0;
}
}
this.nodesMap.delete(value);
return true;
}
get size() {
return this.nodesMap.size;
}
};
// src/index.ts
var import_utils2 = require("@cacheable/utils");
// src/keyv-memory.ts
var import_keyv = require("keyv");
var KeyvCacheableMemory = class {
opts = {
ttl: 0,
useClone: true,
lruSize: 0,
checkInterval: 0
};
_defaultCache = new CacheableMemory();
_nCache = /* @__PURE__ */ new Map();
_namespace;
constructor(options) {
if (options) {
this.opts = options;
this._defaultCache = new CacheableMemory(options);
if (options.namespace) {
this._namespace = options.namespace;
this._nCache.set(this._namespace, new CacheableMemory(options));
}
}
}
get namespace() {
return this._namespace;
}
set namespace(value) {
this._namespace = value;
}
get store() {
return this.getStore(this._namespace);
}
async get(key) {
const result = this.getStore(this._namespace).get(key);
if (result) {
return result;
}
return void 0;
}
async getMany(keys) {
const result = this.getStore(this._namespace).getMany(keys);
return result;
}
// biome-ignore lint/suspicious/noExplicitAny: type format
async set(key, value, ttl) {
this.getStore(this._namespace).set(key, value, ttl);
}
async setMany(values) {
this.getStore(this._namespace).setMany(values);
}
async delete(key) {
this.getStore(this._namespace).delete(key);
return true;
}
async deleteMany(key) {
this.getStore(this._namespace).deleteMany(key);
return true;
}
async clear() {
this.getStore(this._namespace).clear();
}
async has(key) {
return this.getStore(this._namespace).has(key);
}
// biome-ignore lint/suspicious/noExplicitAny: type format
on(event, listener) {
this.getStore(this._namespace).on(event, listener);
return this;
}
getStore(namespace) {
if (!namespace) {
return this._defaultCache;
}
if (!this._nCache.has(namespace)) {
this._nCache.set(namespace, new CacheableMemory(this.opts));
}
return this._nCache.get(namespace);
}
};
function createKeyv(options) {
const store = new KeyvCacheableMemory(options);
const namespace = options?.namespace;
let ttl;
if (options?.ttl && Number.isInteger(options.ttl)) {
ttl = options?.ttl;
}
const keyv = new import_keyv.Keyv({ store, namespace, ttl });
keyv.serialize = void 0;
keyv.deserialize = void 0;
return keyv;
}
// src/index.ts
var defaultStoreHashSize = 16;
var maximumMapSize = 16777216;
var CacheableMemory = class extends import_hookified.Hookified {
_lru = new DoublyLinkedList();
_storeHashSize = defaultStoreHashSize;
_storeHashAlgorithm = import_utils.HashAlgorithm.DJB2;
// Default is djb2Hash
_store = Array.from(
{ length: this._storeHashSize },
() => /* @__PURE__ */ new Map()
);
_ttl;
// Turned off by default
_useClone = true;
// Turned on by default
_lruSize = 0;
// Turned off by default
_checkInterval = 0;
// Turned off by default
_interval = 0;
// Turned off by default
/**
* @constructor
* @param {CacheableMemoryOptions} [options] - The options for the CacheableMemory
*/
constructor(options) {
super();
if (options?.ttl) {
this.setTtl(options.ttl);
}
if (options?.useClone !== void 0) {
this._useClone = options.useClone;
}
if (options?.storeHashSize && options.storeHashSize > 0) {
this._storeHashSize = options.storeHashSize;
}
if (options?.lruSize) {
if (options.lruSize > maximumMapSize) {
this.emit(
"error",
new Error(
`LRU size cannot be larger than ${maximumMapSize} due to Map limitations.`
)
);
} else {
this._lruSize = options.lruSize;
}
}
if (options?.checkInterval) {
this._checkInterval = options.checkInterval;
}
if (options?.storeHashAlgorithm) {
this._storeHashAlgorithm = options.storeHashAlgorithm;
}
this._store = Array.from(
{ length: this._storeHashSize },
() => /* @__PURE__ */ new Map()
);
this.startIntervalCheck();
}
/**
* Gets the time-to-live
* @returns {number|string|undefined} - The time-to-live in miliseconds or a human-readable format. If undefined, it will not have a time-to-live.
*/
get ttl() {
return this._ttl;
}
/**
* Sets the time-to-live
* @param {number|string|undefined} value - The time-to-live in miliseconds or a human-readable format (example '1s' = 1 second, '1h' = 1 hour). If undefined, it will not have a time-to-live.
*/
set ttl(value) {
this.setTtl(value);
}
/**
* Gets whether to use clone
* @returns {boolean} - If true, it will clone the value before returning it. If false, it will return the value directly. Default is true.
*/
get useClone() {
return this._useClone;
}
/**
* Sets whether to use clone
* @param {boolean} value - If true, it will clone the value before returning it. If false, it will return the value directly. Default is true.
*/
set useClone(value) {
this._useClone = value;
}
/**
* Gets the size of the LRU cache
* @returns {number} - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. If you are using LRU then the limit is based on Map() size 17mm.
*/
get lruSize() {
return this._lruSize;
}
/**
* Sets the size of the LRU cache
* @param {number} value - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. If you are using LRU then the limit is based on Map() size 17mm.
*/
set lruSize(value) {
if (value > maximumMapSize) {
this.emit(
"error",
new Error(
`LRU size cannot be larger than ${maximumMapSize} due to Map limitations.`
)
);
return;
}
this._lruSize = value;
if (this._lruSize === 0) {
this._lru = new DoublyLinkedList();
return;
}
this.lruResize();
}
/**
* Gets the check interval
* @returns {number} - The interval to check for expired items. If set to 0, it will not check for expired items. Default is 0.
*/
get checkInterval() {
return this._checkInterval;
}
/**
* Sets the check interval
* @param {number} value - The interval to check for expired items. If set to 0, it will not check for expired items. Default is 0.
*/
set checkInterval(value) {
this._checkInterval = value;
}
/**
* Gets the size of the cache
* @returns {number} - The size of the cache
*/
get size() {
let size = 0;
for (const store of this._store) {
size += store.size;
}
return size;
}
/**
* Gets the number of hash stores
* @returns {number} - The number of hash stores
*/
get storeHashSize() {
return this._storeHashSize;
}
/**
* Sets the number of hash stores. This will recreate the store and all data will be cleared
* @param {number} value - The number of hash stores
*/
set storeHashSize(value) {
if (value === this._storeHashSize) {
return;
}
this._storeHashSize = value;
this._store = Array.from(
{ length: this._storeHashSize },
() => /* @__PURE__ */ new Map()
);
}
/**
* Gets the store hash algorithm
* @returns {HashAlgorithm | StoreHashAlgorithmFunction} - The store hash algorithm
*/
get storeHashAlgorithm() {
return this._storeHashAlgorithm;
}
/**
* Sets the store hash algorithm. This will recreate the store and all data will be cleared
* @param {HashAlgorithm | HashAlgorithmFunction} value - The store hash algorithm
*/
set storeHashAlgorithm(value) {
this._storeHashAlgorithm = value;
}
/**
* Gets the keys
* @returns {IterableIterator<string>} - The keys
*/
get keys() {
const keys = [];
for (const store of this._store) {
for (const key of store.keys()) {
const item = store.get(key);
if (item && this.hasExpired(item)) {
store.delete(key);
this.lruRemove(key);
continue;
}
keys.push(key);
}
}
return keys.values();
}
/**
* Gets the items
* @returns {IterableIterator<CacheableStoreItem>} - The items
*/
get items() {
const items = [];
for (const store of this._store) {
for (const item of store.values()) {
if (this.hasExpired(item)) {
store.delete(item.key);
this.lruRemove(item.key);
continue;
}
items.push(item);
}
}
return items.values();
}
/**
* Gets the store
* @returns {Array<Map<string, CacheableStoreItem>>} - The store
*/
get store() {
return this._store;
}
/**
* Gets the value of the key
* @param {string} key - The key to get the value
* @returns {T | undefined} - The value of the key
*/
get(key) {
const store = this.getStore(key);
const item = store.get(key);
if (!item) {
return void 0;
}
if (item.expires && Date.now() > item.expires) {
store.delete(key);
this.lruRemove(key);
return void 0;
}
this.lruMoveToFront(key);
if (!this._useClone) {
return item.value;
}
return this.clone(item.value);
}
/**
* Gets the values of the keys
* @param {string[]} keys - The keys to get the values
* @returns {T[]} - The values of the keys
*/
getMany(keys) {
const result = [];
for (const key of keys) {
result.push(this.get(key));
}
return result;
}
/**
* Gets the raw value of the key
* @param {string} key - The key to get the value
* @returns {CacheableStoreItem | undefined} - The raw value of the key
*/
getRaw(key) {
const store = this.getStore(key);
const item = store.get(key);
if (!item) {
return void 0;
}
if (item.expires && item.expires && Date.now() > item.expires) {
store.delete(key);
this.lruRemove(key);
return void 0;
}
this.lruMoveToFront(key);
return item;
}
/**
* Gets the raw values of the keys
* @param {string[]} keys - The keys to get the values
* @returns {CacheableStoreItem[]} - The raw values of the keys
*/
getManyRaw(keys) {
const result = [];
for (const key of keys) {
result.push(this.getRaw(key));
}
return result;
}
/**
* Sets the value of the key
* @param {string} key - The key to set the value
* @param {any} value - The value to set
* @param {number|string|SetOptions} [ttl] - Time to Live - If you set a number it is miliseconds, if you set a string it is a human-readable.
* If you want to set expire directly you can do that by setting the expire property in the SetOptions.
* If you set undefined, it will use the default time-to-live. If both are undefined then it will not have a time-to-live.
* @returns {void}
*/
set(key, value, ttl) {
const store = this.getStore(key);
let expires;
if (ttl !== void 0 || this._ttl !== void 0) {
if (typeof ttl === "object") {
if (ttl.expire) {
expires = typeof ttl.expire === "number" ? ttl.expire : ttl.expire.getTime();
}
if (ttl.ttl) {
const finalTtl = (0, import_utils.shorthandToTime)(ttl.ttl);
if (finalTtl !== void 0) {
expires = finalTtl;
}
}
} else {
const finalTtl = (0, import_utils.shorthandToTime)(ttl ?? this._ttl);
if (finalTtl !== void 0) {
expires = finalTtl;
}
}
}
if (this._lruSize > 0) {
if (store.has(key)) {
this.lruMoveToFront(key);
} else {
this.lruAddToFront(key);
if (this._lru.size > this._lruSize) {
const oldestKey = this._lru.getOldest();
if (oldestKey) {
this._lru.removeOldest();
this.delete(oldestKey);
}
}
}
}
const item = { key, value, expires };
store.set(key, item);
}
/**
* Sets the values of the keys
* @param {CacheableItem[]} items - The items to set
* @returns {void}
*/
setMany(items) {
for (const item of items) {
this.set(item.key, item.value, item.ttl);
}
}
/**
* Checks if the key exists
* @param {string} key - The key to check
* @returns {boolean} - If true, the key exists. If false, the key does not exist.
*/
has(key) {
const item = this.get(key);
return Boolean(item);
}
/**
* @function hasMany
* @param {string[]} keys - The keys to check
* @returns {boolean[]} - If true, the key exists. If false, the key does not exist.
*/
hasMany(keys) {
const result = [];
for (const key of keys) {
const item = this.get(key);
result.push(Boolean(item));
}
return result;
}
/**
* Take will get the key and delete the entry from cache
* @param {string} key - The key to take
* @returns {T | undefined} - The value of the key
*/
take(key) {
const item = this.get(key);
if (!item) {
return void 0;
}
this.delete(key);
return item;
}
/**
* TakeMany will get the keys and delete the entries from cache
* @param {string[]} keys - The keys to take
* @returns {T[]} - The values of the keys
*/
takeMany(keys) {
const result = [];
for (const key of keys) {
result.push(this.take(key));
}
return result;
}
/**
* Delete the key
* @param {string} key - The key to delete
* @returns {void}
*/
delete(key) {
const store = this.getStore(key);
store.delete(key);
this.lruRemove(key);
}
/**
* Delete the keys
* @param {string[]} keys - The keys to delete
* @returns {void}
*/
deleteMany(keys) {
for (const key of keys) {
this.delete(key);
}
}
/**
* Clear the cache
* @returns {void}
*/
clear() {
this._store = Array.from(
{ length: this._storeHashSize },
() => /* @__PURE__ */ new Map()
);
this._lru = new DoublyLinkedList();
}
/**
* Get the store based on the key (internal use)
* @param {string} key - The key to get the store
* @returns {CacheableHashStore} - The store
*/
getStore(key) {
const hash2 = this.getKeyStoreHash(key);
this._store[hash2] ||= /* @__PURE__ */ new Map();
return this._store[hash2];
}
/**
* Hash the key for which store to go to (internal use)
* @param {string} key - The key to hash
* Available algorithms are: SHA256, SHA1, MD5, and djb2Hash.
* @returns {number} - The hashed key as a number
*/
getKeyStoreHash(key) {
if (this._store.length === 1) {
return 0;
}
if (typeof this._storeHashAlgorithm === "function") {
return this._storeHashAlgorithm(key, this._storeHashSize);
}
const storeHashSize = this._storeHashSize - 1;
const hash2 = (0, import_utils.hashToNumberSync)(key, {
min: 0,
max: storeHashSize,
algorithm: this._storeHashAlgorithm
});
return hash2;
}
/**
* Clone the value. This is for internal use
* @param {any} value - The value to clone
* @returns {any} - The cloned value
*/
// biome-ignore lint/suspicious/noExplicitAny: type format
clone(value) {
if (this.isPrimitive(value)) {
return value;
}
return structuredClone(value);
}
/**
* Add to the front of the LRU cache. This is for internal use
* @param {string} key - The key to add to the front
* @returns {void}
*/
lruAddToFront(key) {
if (this._lruSize === 0) {
return;
}
this._lru.addToFront(key);
}
/**
* Move to the front of the LRU cache. This is for internal use
* @param {string} key - The key to move to the front
* @returns {void}
*/
lruMoveToFront(key) {
if (this._lruSize === 0) {
return;
}
this._lru.moveToFront(key);
}
/**
* Remove a key from the LRU cache. This is for internal use
* @param {string} key - The key to remove
* @returns {void}
*/
lruRemove(key) {
if (this._lruSize === 0) {
return;
}
this._lru.remove(key);
}
/**
* Resize the LRU cache. This is for internal use.
* @returns {void}
*/
lruResize() {
while (this._lru.size > this._lruSize) {
const oldestKey = this._lru.getOldest();
if (oldestKey) {
this._lru.removeOldest();
this.delete(oldestKey);
}
}
}
/**
* Check for expiration. This is for internal use
* @returns {void}
*/
checkExpiration() {
for (const store of this._store) {
for (const item of store.values()) {
if (item.expires && Date.now() > item.expires) {
store.delete(item.key);
this.lruRemove(item.key);
}
}
}
}
/**
* Start the interval check. This is for internal use
* @returns {void}
*/
startIntervalCheck() {
if (this._checkInterval > 0) {
if (this._interval) {
clearInterval(this._interval);
}
this._interval = setInterval(() => {
this.checkExpiration();
}, this._checkInterval).unref();
}
}
/**
* Stop the interval check. This is for internal use
* @returns {void}
*/
stopIntervalCheck() {
if (this._interval) {
clearInterval(this._interval);
}
this._interval = 0;
this._checkInterval = 0;
}
/**
* Wrap the function for caching
* @param {Function} function_ - The function to wrap
* @param {Object} [options] - The options to wrap
* @returns {Function} - The wrapped function
*/
// biome-ignore lint/suspicious/noExplicitAny: type format
wrap(function_, options) {
const wrapOptions = {
ttl: options?.ttl ?? this._ttl,
keyPrefix: options?.keyPrefix,
createKey: options?.createKey,
cache: this
};
return (0, import_utils.wrapSync)(function_, wrapOptions);
}
// biome-ignore lint/suspicious/noExplicitAny: type format
isPrimitive(value) {
const result = false;
if (value === null || value === void 0) {
return true;
}
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
return true;
}
return result;
}
setTtl(ttl) {
if (typeof ttl === "string" || ttl === void 0) {
this._ttl = ttl;
} else if (ttl > 0) {
this._ttl = ttl;
} else {
this._ttl = void 0;
}
}
hasExpired(item) {
if (item.expires && Date.now() > item.expires) {
return true;
}
return false;
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
CacheableMemory,
HashAlgorithm,
KeyvCacheableMemory,
createKeyv,
defaultStoreHashSize,
hash,
hashToNumber,
maximumMapSize
});
/* v8 ignore next -- @preserve */

310
node_modules/@cacheable/memory/dist/index.d.cts generated vendored Normal file
View file

@ -0,0 +1,310 @@
import { HashAlgorithm, CacheableStoreItem, CacheableItem, WrapFunctionOptions } from '@cacheable/utils';
export { CacheableItem, CacheableStoreItem, HashAlgorithm, hash, hashToNumber } from '@cacheable/utils';
import { Hookified } from 'hookified';
import { KeyvStoreAdapter, StoredData, Keyv } from 'keyv';
type KeyvCacheableMemoryOptions = CacheableMemoryOptions & {
namespace?: string;
};
declare class KeyvCacheableMemory implements KeyvStoreAdapter {
opts: CacheableMemoryOptions;
private readonly _defaultCache;
private readonly _nCache;
private _namespace?;
constructor(options?: KeyvCacheableMemoryOptions);
get namespace(): string | undefined;
set namespace(value: string | undefined);
get store(): CacheableMemory;
get<Value>(key: string): Promise<StoredData<Value> | undefined>;
getMany<Value>(keys: string[]): Promise<Array<StoredData<Value | undefined>>>;
set(key: string, value: any, ttl?: number): Promise<void>;
setMany(values: Array<{
key: string;
value: any;
ttl?: number;
}>): Promise<void>;
delete(key: string): Promise<boolean>;
deleteMany?(key: string[]): Promise<boolean>;
clear(): Promise<void>;
has?(key: string): Promise<boolean>;
on(event: string, listener: (...arguments_: any[]) => void): this;
getStore(namespace?: string): CacheableMemory;
}
/**
* Creates a new Keyv instance with a new KeyvCacheableMemory store. This also removes the serialize/deserialize methods from the Keyv instance for optimization.
* @param options
* @returns
*/
declare function createKeyv(options?: KeyvCacheableMemoryOptions): Keyv;
type StoreHashAlgorithmFunction = (key: string, storeHashSize: number) => number;
/**
* @typedef {Object} CacheableMemoryOptions
* @property {number|string} [ttl] - Time to Live - If you set a number it is miliseconds, if you set a string it is a human-readable
* format such as `1s` for 1 second or `1h` for 1 hour. Setting undefined means that it will use the default time-to-live. If both are
* undefined then it will not have a time-to-live.
* @property {boolean} [useClone] - If true, it will clone the value before returning it. If false, it will return the value directly. Default is true.
* @property {number} [lruSize] - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. If you are using LRU then the limit is based on Map() size 17mm.
* @property {number} [checkInterval] - The interval to check for expired items. If set to 0, it will not check for expired items. Default is 0.
* @property {number} [storeHashSize] - The number of how many Map stores we have for the hash. Default is 10.
*/
type CacheableMemoryOptions = {
ttl?: number | string;
useClone?: boolean;
lruSize?: number;
checkInterval?: number;
storeHashSize?: number;
storeHashAlgorithm?: HashAlgorithm | ((key: string, storeHashSize: number) => number);
};
type SetOptions = {
ttl?: number | string;
expire?: number | Date;
};
declare const defaultStoreHashSize = 16;
declare const maximumMapSize = 16777216;
declare class CacheableMemory extends Hookified {
private _lru;
private _storeHashSize;
private _storeHashAlgorithm;
private _store;
private _ttl;
private _useClone;
private _lruSize;
private _checkInterval;
private _interval;
/**
* @constructor
* @param {CacheableMemoryOptions} [options] - The options for the CacheableMemory
*/
constructor(options?: CacheableMemoryOptions);
/**
* Gets the time-to-live
* @returns {number|string|undefined} - The time-to-live in miliseconds or a human-readable format. If undefined, it will not have a time-to-live.
*/
get ttl(): number | string | undefined;
/**
* Sets the time-to-live
* @param {number|string|undefined} value - The time-to-live in miliseconds or a human-readable format (example '1s' = 1 second, '1h' = 1 hour). If undefined, it will not have a time-to-live.
*/
set ttl(value: number | string | undefined);
/**
* Gets whether to use clone
* @returns {boolean} - If true, it will clone the value before returning it. If false, it will return the value directly. Default is true.
*/
get useClone(): boolean;
/**
* Sets whether to use clone
* @param {boolean} value - If true, it will clone the value before returning it. If false, it will return the value directly. Default is true.
*/
set useClone(value: boolean);
/**
* Gets the size of the LRU cache
* @returns {number} - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. If you are using LRU then the limit is based on Map() size 17mm.
*/
get lruSize(): number;
/**
* Sets the size of the LRU cache
* @param {number} value - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. If you are using LRU then the limit is based on Map() size 17mm.
*/
set lruSize(value: number);
/**
* Gets the check interval
* @returns {number} - The interval to check for expired items. If set to 0, it will not check for expired items. Default is 0.
*/
get checkInterval(): number;
/**
* Sets the check interval
* @param {number} value - The interval to check for expired items. If set to 0, it will not check for expired items. Default is 0.
*/
set checkInterval(value: number);
/**
* Gets the size of the cache
* @returns {number} - The size of the cache
*/
get size(): number;
/**
* Gets the number of hash stores
* @returns {number} - The number of hash stores
*/
get storeHashSize(): number;
/**
* Sets the number of hash stores. This will recreate the store and all data will be cleared
* @param {number} value - The number of hash stores
*/
set storeHashSize(value: number);
/**
* Gets the store hash algorithm
* @returns {HashAlgorithm | StoreHashAlgorithmFunction} - The store hash algorithm
*/
get storeHashAlgorithm(): HashAlgorithm | StoreHashAlgorithmFunction;
/**
* Sets the store hash algorithm. This will recreate the store and all data will be cleared
* @param {HashAlgorithm | HashAlgorithmFunction} value - The store hash algorithm
*/
set storeHashAlgorithm(value: HashAlgorithm | StoreHashAlgorithmFunction);
/**
* Gets the keys
* @returns {IterableIterator<string>} - The keys
*/
get keys(): IterableIterator<string>;
/**
* Gets the items
* @returns {IterableIterator<CacheableStoreItem>} - The items
*/
get items(): IterableIterator<CacheableStoreItem>;
/**
* Gets the store
* @returns {Array<Map<string, CacheableStoreItem>>} - The store
*/
get store(): Array<Map<string, CacheableStoreItem>>;
/**
* Gets the value of the key
* @param {string} key - The key to get the value
* @returns {T | undefined} - The value of the key
*/
get<T>(key: string): T | undefined;
/**
* Gets the values of the keys
* @param {string[]} keys - The keys to get the values
* @returns {T[]} - The values of the keys
*/
getMany<T>(keys: string[]): T[];
/**
* Gets the raw value of the key
* @param {string} key - The key to get the value
* @returns {CacheableStoreItem | undefined} - The raw value of the key
*/
getRaw(key: string): CacheableStoreItem | undefined;
/**
* Gets the raw values of the keys
* @param {string[]} keys - The keys to get the values
* @returns {CacheableStoreItem[]} - The raw values of the keys
*/
getManyRaw(keys: string[]): Array<CacheableStoreItem | undefined>;
/**
* Sets the value of the key
* @param {string} key - The key to set the value
* @param {any} value - The value to set
* @param {number|string|SetOptions} [ttl] - Time to Live - If you set a number it is miliseconds, if you set a string it is a human-readable.
* If you want to set expire directly you can do that by setting the expire property in the SetOptions.
* If you set undefined, it will use the default time-to-live. If both are undefined then it will not have a time-to-live.
* @returns {void}
*/
set(key: string, value: any, ttl?: number | string | SetOptions): void;
/**
* Sets the values of the keys
* @param {CacheableItem[]} items - The items to set
* @returns {void}
*/
setMany(items: CacheableItem[]): void;
/**
* Checks if the key exists
* @param {string} key - The key to check
* @returns {boolean} - If true, the key exists. If false, the key does not exist.
*/
has(key: string): boolean;
/**
* @function hasMany
* @param {string[]} keys - The keys to check
* @returns {boolean[]} - If true, the key exists. If false, the key does not exist.
*/
hasMany(keys: string[]): boolean[];
/**
* Take will get the key and delete the entry from cache
* @param {string} key - The key to take
* @returns {T | undefined} - The value of the key
*/
take<T>(key: string): T | undefined;
/**
* TakeMany will get the keys and delete the entries from cache
* @param {string[]} keys - The keys to take
* @returns {T[]} - The values of the keys
*/
takeMany<T>(keys: string[]): T[];
/**
* Delete the key
* @param {string} key - The key to delete
* @returns {void}
*/
delete(key: string): void;
/**
* Delete the keys
* @param {string[]} keys - The keys to delete
* @returns {void}
*/
deleteMany(keys: string[]): void;
/**
* Clear the cache
* @returns {void}
*/
clear(): void;
/**
* Get the store based on the key (internal use)
* @param {string} key - The key to get the store
* @returns {CacheableHashStore} - The store
*/
getStore(key: string): Map<string, CacheableStoreItem>;
/**
* Hash the key for which store to go to (internal use)
* @param {string} key - The key to hash
* Available algorithms are: SHA256, SHA1, MD5, and djb2Hash.
* @returns {number} - The hashed key as a number
*/
getKeyStoreHash(key: string): number;
/**
* Clone the value. This is for internal use
* @param {any} value - The value to clone
* @returns {any} - The cloned value
*/
clone(value: any): any;
/**
* Add to the front of the LRU cache. This is for internal use
* @param {string} key - The key to add to the front
* @returns {void}
*/
lruAddToFront(key: string): void;
/**
* Move to the front of the LRU cache. This is for internal use
* @param {string} key - The key to move to the front
* @returns {void}
*/
lruMoveToFront(key: string): void;
/**
* Remove a key from the LRU cache. This is for internal use
* @param {string} key - The key to remove
* @returns {void}
*/
lruRemove(key: string): void;
/**
* Resize the LRU cache. This is for internal use.
* @returns {void}
*/
lruResize(): void;
/**
* Check for expiration. This is for internal use
* @returns {void}
*/
checkExpiration(): void;
/**
* Start the interval check. This is for internal use
* @returns {void}
*/
startIntervalCheck(): void;
/**
* Stop the interval check. This is for internal use
* @returns {void}
*/
stopIntervalCheck(): void;
/**
* Wrap the function for caching
* @param {Function} function_ - The function to wrap
* @param {Object} [options] - The options to wrap
* @returns {Function} - The wrapped function
*/
wrap<T, Arguments extends any[]>(function_: (...arguments_: Arguments) => T, options?: WrapFunctionOptions): (...arguments_: Arguments) => T;
private isPrimitive;
private setTtl;
private hasExpired;
}
export { CacheableMemory, type CacheableMemoryOptions, KeyvCacheableMemory, type KeyvCacheableMemoryOptions, type SetOptions, type StoreHashAlgorithmFunction, createKeyv, defaultStoreHashSize, maximumMapSize };

310
node_modules/@cacheable/memory/dist/index.d.ts generated vendored Normal file
View file

@ -0,0 +1,310 @@
import { HashAlgorithm, CacheableStoreItem, CacheableItem, WrapFunctionOptions } from '@cacheable/utils';
export { CacheableItem, CacheableStoreItem, HashAlgorithm, hash, hashToNumber } from '@cacheable/utils';
import { Hookified } from 'hookified';
import { KeyvStoreAdapter, StoredData, Keyv } from 'keyv';
type KeyvCacheableMemoryOptions = CacheableMemoryOptions & {
namespace?: string;
};
declare class KeyvCacheableMemory implements KeyvStoreAdapter {
opts: CacheableMemoryOptions;
private readonly _defaultCache;
private readonly _nCache;
private _namespace?;
constructor(options?: KeyvCacheableMemoryOptions);
get namespace(): string | undefined;
set namespace(value: string | undefined);
get store(): CacheableMemory;
get<Value>(key: string): Promise<StoredData<Value> | undefined>;
getMany<Value>(keys: string[]): Promise<Array<StoredData<Value | undefined>>>;
set(key: string, value: any, ttl?: number): Promise<void>;
setMany(values: Array<{
key: string;
value: any;
ttl?: number;
}>): Promise<void>;
delete(key: string): Promise<boolean>;
deleteMany?(key: string[]): Promise<boolean>;
clear(): Promise<void>;
has?(key: string): Promise<boolean>;
on(event: string, listener: (...arguments_: any[]) => void): this;
getStore(namespace?: string): CacheableMemory;
}
/**
* Creates a new Keyv instance with a new KeyvCacheableMemory store. This also removes the serialize/deserialize methods from the Keyv instance for optimization.
* @param options
* @returns
*/
declare function createKeyv(options?: KeyvCacheableMemoryOptions): Keyv;
type StoreHashAlgorithmFunction = (key: string, storeHashSize: number) => number;
/**
* @typedef {Object} CacheableMemoryOptions
* @property {number|string} [ttl] - Time to Live - If you set a number it is miliseconds, if you set a string it is a human-readable
* format such as `1s` for 1 second or `1h` for 1 hour. Setting undefined means that it will use the default time-to-live. If both are
* undefined then it will not have a time-to-live.
* @property {boolean} [useClone] - If true, it will clone the value before returning it. If false, it will return the value directly. Default is true.
* @property {number} [lruSize] - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. If you are using LRU then the limit is based on Map() size 17mm.
* @property {number} [checkInterval] - The interval to check for expired items. If set to 0, it will not check for expired items. Default is 0.
* @property {number} [storeHashSize] - The number of how many Map stores we have for the hash. Default is 10.
*/
type CacheableMemoryOptions = {
ttl?: number | string;
useClone?: boolean;
lruSize?: number;
checkInterval?: number;
storeHashSize?: number;
storeHashAlgorithm?: HashAlgorithm | ((key: string, storeHashSize: number) => number);
};
type SetOptions = {
ttl?: number | string;
expire?: number | Date;
};
declare const defaultStoreHashSize = 16;
declare const maximumMapSize = 16777216;
declare class CacheableMemory extends Hookified {
private _lru;
private _storeHashSize;
private _storeHashAlgorithm;
private _store;
private _ttl;
private _useClone;
private _lruSize;
private _checkInterval;
private _interval;
/**
* @constructor
* @param {CacheableMemoryOptions} [options] - The options for the CacheableMemory
*/
constructor(options?: CacheableMemoryOptions);
/**
* Gets the time-to-live
* @returns {number|string|undefined} - The time-to-live in miliseconds or a human-readable format. If undefined, it will not have a time-to-live.
*/
get ttl(): number | string | undefined;
/**
* Sets the time-to-live
* @param {number|string|undefined} value - The time-to-live in miliseconds or a human-readable format (example '1s' = 1 second, '1h' = 1 hour). If undefined, it will not have a time-to-live.
*/
set ttl(value: number | string | undefined);
/**
* Gets whether to use clone
* @returns {boolean} - If true, it will clone the value before returning it. If false, it will return the value directly. Default is true.
*/
get useClone(): boolean;
/**
* Sets whether to use clone
* @param {boolean} value - If true, it will clone the value before returning it. If false, it will return the value directly. Default is true.
*/
set useClone(value: boolean);
/**
* Gets the size of the LRU cache
* @returns {number} - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. If you are using LRU then the limit is based on Map() size 17mm.
*/
get lruSize(): number;
/**
* Sets the size of the LRU cache
* @param {number} value - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. If you are using LRU then the limit is based on Map() size 17mm.
*/
set lruSize(value: number);
/**
* Gets the check interval
* @returns {number} - The interval to check for expired items. If set to 0, it will not check for expired items. Default is 0.
*/
get checkInterval(): number;
/**
* Sets the check interval
* @param {number} value - The interval to check for expired items. If set to 0, it will not check for expired items. Default is 0.
*/
set checkInterval(value: number);
/**
* Gets the size of the cache
* @returns {number} - The size of the cache
*/
get size(): number;
/**
* Gets the number of hash stores
* @returns {number} - The number of hash stores
*/
get storeHashSize(): number;
/**
* Sets the number of hash stores. This will recreate the store and all data will be cleared
* @param {number} value - The number of hash stores
*/
set storeHashSize(value: number);
/**
* Gets the store hash algorithm
* @returns {HashAlgorithm | StoreHashAlgorithmFunction} - The store hash algorithm
*/
get storeHashAlgorithm(): HashAlgorithm | StoreHashAlgorithmFunction;
/**
* Sets the store hash algorithm. This will recreate the store and all data will be cleared
* @param {HashAlgorithm | HashAlgorithmFunction} value - The store hash algorithm
*/
set storeHashAlgorithm(value: HashAlgorithm | StoreHashAlgorithmFunction);
/**
* Gets the keys
* @returns {IterableIterator<string>} - The keys
*/
get keys(): IterableIterator<string>;
/**
* Gets the items
* @returns {IterableIterator<CacheableStoreItem>} - The items
*/
get items(): IterableIterator<CacheableStoreItem>;
/**
* Gets the store
* @returns {Array<Map<string, CacheableStoreItem>>} - The store
*/
get store(): Array<Map<string, CacheableStoreItem>>;
/**
* Gets the value of the key
* @param {string} key - The key to get the value
* @returns {T | undefined} - The value of the key
*/
get<T>(key: string): T | undefined;
/**
* Gets the values of the keys
* @param {string[]} keys - The keys to get the values
* @returns {T[]} - The values of the keys
*/
getMany<T>(keys: string[]): T[];
/**
* Gets the raw value of the key
* @param {string} key - The key to get the value
* @returns {CacheableStoreItem | undefined} - The raw value of the key
*/
getRaw(key: string): CacheableStoreItem | undefined;
/**
* Gets the raw values of the keys
* @param {string[]} keys - The keys to get the values
* @returns {CacheableStoreItem[]} - The raw values of the keys
*/
getManyRaw(keys: string[]): Array<CacheableStoreItem | undefined>;
/**
* Sets the value of the key
* @param {string} key - The key to set the value
* @param {any} value - The value to set
* @param {number|string|SetOptions} [ttl] - Time to Live - If you set a number it is miliseconds, if you set a string it is a human-readable.
* If you want to set expire directly you can do that by setting the expire property in the SetOptions.
* If you set undefined, it will use the default time-to-live. If both are undefined then it will not have a time-to-live.
* @returns {void}
*/
set(key: string, value: any, ttl?: number | string | SetOptions): void;
/**
* Sets the values of the keys
* @param {CacheableItem[]} items - The items to set
* @returns {void}
*/
setMany(items: CacheableItem[]): void;
/**
* Checks if the key exists
* @param {string} key - The key to check
* @returns {boolean} - If true, the key exists. If false, the key does not exist.
*/
has(key: string): boolean;
/**
* @function hasMany
* @param {string[]} keys - The keys to check
* @returns {boolean[]} - If true, the key exists. If false, the key does not exist.
*/
hasMany(keys: string[]): boolean[];
/**
* Take will get the key and delete the entry from cache
* @param {string} key - The key to take
* @returns {T | undefined} - The value of the key
*/
take<T>(key: string): T | undefined;
/**
* TakeMany will get the keys and delete the entries from cache
* @param {string[]} keys - The keys to take
* @returns {T[]} - The values of the keys
*/
takeMany<T>(keys: string[]): T[];
/**
* Delete the key
* @param {string} key - The key to delete
* @returns {void}
*/
delete(key: string): void;
/**
* Delete the keys
* @param {string[]} keys - The keys to delete
* @returns {void}
*/
deleteMany(keys: string[]): void;
/**
* Clear the cache
* @returns {void}
*/
clear(): void;
/**
* Get the store based on the key (internal use)
* @param {string} key - The key to get the store
* @returns {CacheableHashStore} - The store
*/
getStore(key: string): Map<string, CacheableStoreItem>;
/**
* Hash the key for which store to go to (internal use)
* @param {string} key - The key to hash
* Available algorithms are: SHA256, SHA1, MD5, and djb2Hash.
* @returns {number} - The hashed key as a number
*/
getKeyStoreHash(key: string): number;
/**
* Clone the value. This is for internal use
* @param {any} value - The value to clone
* @returns {any} - The cloned value
*/
clone(value: any): any;
/**
* Add to the front of the LRU cache. This is for internal use
* @param {string} key - The key to add to the front
* @returns {void}
*/
lruAddToFront(key: string): void;
/**
* Move to the front of the LRU cache. This is for internal use
* @param {string} key - The key to move to the front
* @returns {void}
*/
lruMoveToFront(key: string): void;
/**
* Remove a key from the LRU cache. This is for internal use
* @param {string} key - The key to remove
* @returns {void}
*/
lruRemove(key: string): void;
/**
* Resize the LRU cache. This is for internal use.
* @returns {void}
*/
lruResize(): void;
/**
* Check for expiration. This is for internal use
* @returns {void}
*/
checkExpiration(): void;
/**
* Start the interval check. This is for internal use
* @returns {void}
*/
startIntervalCheck(): void;
/**
* Stop the interval check. This is for internal use
* @returns {void}
*/
stopIntervalCheck(): void;
/**
* Wrap the function for caching
* @param {Function} function_ - The function to wrap
* @param {Object} [options] - The options to wrap
* @returns {Function} - The wrapped function
*/
wrap<T, Arguments extends any[]>(function_: (...arguments_: Arguments) => T, options?: WrapFunctionOptions): (...arguments_: Arguments) => T;
private isPrimitive;
private setTtl;
private hasExpired;
}
export { CacheableMemory, type CacheableMemoryOptions, KeyvCacheableMemory, type KeyvCacheableMemoryOptions, type SetOptions, type StoreHashAlgorithmFunction, createKeyv, defaultStoreHashSize, maximumMapSize };

807
node_modules/@cacheable/memory/dist/index.js generated vendored Normal file
View file

@ -0,0 +1,807 @@
// src/index.ts
import {
HashAlgorithm,
hashToNumberSync,
shorthandToTime,
wrapSync
} from "@cacheable/utils";
import { Hookified } from "hookified";
// src/memory-lru.ts
var ListNode = class {
value;
prev = void 0;
next = void 0;
constructor(value) {
this.value = value;
}
};
var DoublyLinkedList = class {
head = void 0;
tail = void 0;
nodesMap = /* @__PURE__ */ new Map();
// Add a new node to the front (most recently used)
addToFront(value) {
const newNode = new ListNode(value);
if (this.head) {
newNode.next = this.head;
this.head.prev = newNode;
this.head = newNode;
} else {
this.head = this.tail = newNode;
}
this.nodesMap.set(value, newNode);
}
// Move an existing node to the front (most recently used)
moveToFront(value) {
const node = this.nodesMap.get(value);
if (!node || this.head === node) {
return;
}
if (node.prev) {
node.prev.next = node.next;
}
if (node.next) {
node.next.prev = node.prev;
}
if (node === this.tail) {
this.tail = node.prev;
}
node.prev = void 0;
node.next = this.head;
if (this.head) {
this.head.prev = node;
}
this.head = node;
this.tail ??= node;
}
// Get the oldest node (tail)
getOldest() {
return this.tail ? this.tail.value : void 0;
}
// Remove the oldest node (tail)
removeOldest() {
if (!this.tail) {
return void 0;
}
const oldValue = this.tail.value;
if (this.tail.prev) {
this.tail = this.tail.prev;
this.tail.next = void 0;
} else {
this.head = this.tail = void 0;
}
this.nodesMap.delete(oldValue);
return oldValue;
}
// Remove a specific node by value
remove(value) {
const node = this.nodesMap.get(value);
if (!node) {
return false;
}
if (node.prev) {
node.prev.next = node.next;
} else {
this.head = node.next;
if (this.head) {
this.head.prev = void 0;
}
}
if (node.next) {
node.next.prev = node.prev;
} else {
this.tail = node.prev;
if (this.tail) {
this.tail.next = void 0;
}
}
this.nodesMap.delete(value);
return true;
}
get size() {
return this.nodesMap.size;
}
};
// src/index.ts
import {
HashAlgorithm as HashAlgorithm2,
hash,
hashToNumber
} from "@cacheable/utils";
// src/keyv-memory.ts
import { Keyv } from "keyv";
var KeyvCacheableMemory = class {
opts = {
ttl: 0,
useClone: true,
lruSize: 0,
checkInterval: 0
};
_defaultCache = new CacheableMemory();
_nCache = /* @__PURE__ */ new Map();
_namespace;
constructor(options) {
if (options) {
this.opts = options;
this._defaultCache = new CacheableMemory(options);
if (options.namespace) {
this._namespace = options.namespace;
this._nCache.set(this._namespace, new CacheableMemory(options));
}
}
}
get namespace() {
return this._namespace;
}
set namespace(value) {
this._namespace = value;
}
get store() {
return this.getStore(this._namespace);
}
async get(key) {
const result = this.getStore(this._namespace).get(key);
if (result) {
return result;
}
return void 0;
}
async getMany(keys) {
const result = this.getStore(this._namespace).getMany(keys);
return result;
}
// biome-ignore lint/suspicious/noExplicitAny: type format
async set(key, value, ttl) {
this.getStore(this._namespace).set(key, value, ttl);
}
async setMany(values) {
this.getStore(this._namespace).setMany(values);
}
async delete(key) {
this.getStore(this._namespace).delete(key);
return true;
}
async deleteMany(key) {
this.getStore(this._namespace).deleteMany(key);
return true;
}
async clear() {
this.getStore(this._namespace).clear();
}
async has(key) {
return this.getStore(this._namespace).has(key);
}
// biome-ignore lint/suspicious/noExplicitAny: type format
on(event, listener) {
this.getStore(this._namespace).on(event, listener);
return this;
}
getStore(namespace) {
if (!namespace) {
return this._defaultCache;
}
if (!this._nCache.has(namespace)) {
this._nCache.set(namespace, new CacheableMemory(this.opts));
}
return this._nCache.get(namespace);
}
};
function createKeyv(options) {
const store = new KeyvCacheableMemory(options);
const namespace = options?.namespace;
let ttl;
if (options?.ttl && Number.isInteger(options.ttl)) {
ttl = options?.ttl;
}
const keyv = new Keyv({ store, namespace, ttl });
keyv.serialize = void 0;
keyv.deserialize = void 0;
return keyv;
}
// src/index.ts
var defaultStoreHashSize = 16;
var maximumMapSize = 16777216;
var CacheableMemory = class extends Hookified {
_lru = new DoublyLinkedList();
_storeHashSize = defaultStoreHashSize;
_storeHashAlgorithm = HashAlgorithm.DJB2;
// Default is djb2Hash
_store = Array.from(
{ length: this._storeHashSize },
() => /* @__PURE__ */ new Map()
);
_ttl;
// Turned off by default
_useClone = true;
// Turned on by default
_lruSize = 0;
// Turned off by default
_checkInterval = 0;
// Turned off by default
_interval = 0;
// Turned off by default
/**
* @constructor
* @param {CacheableMemoryOptions} [options] - The options for the CacheableMemory
*/
constructor(options) {
super();
if (options?.ttl) {
this.setTtl(options.ttl);
}
if (options?.useClone !== void 0) {
this._useClone = options.useClone;
}
if (options?.storeHashSize && options.storeHashSize > 0) {
this._storeHashSize = options.storeHashSize;
}
if (options?.lruSize) {
if (options.lruSize > maximumMapSize) {
this.emit(
"error",
new Error(
`LRU size cannot be larger than ${maximumMapSize} due to Map limitations.`
)
);
} else {
this._lruSize = options.lruSize;
}
}
if (options?.checkInterval) {
this._checkInterval = options.checkInterval;
}
if (options?.storeHashAlgorithm) {
this._storeHashAlgorithm = options.storeHashAlgorithm;
}
this._store = Array.from(
{ length: this._storeHashSize },
() => /* @__PURE__ */ new Map()
);
this.startIntervalCheck();
}
/**
* Gets the time-to-live
* @returns {number|string|undefined} - The time-to-live in miliseconds or a human-readable format. If undefined, it will not have a time-to-live.
*/
get ttl() {
return this._ttl;
}
/**
* Sets the time-to-live
* @param {number|string|undefined} value - The time-to-live in miliseconds or a human-readable format (example '1s' = 1 second, '1h' = 1 hour). If undefined, it will not have a time-to-live.
*/
set ttl(value) {
this.setTtl(value);
}
/**
* Gets whether to use clone
* @returns {boolean} - If true, it will clone the value before returning it. If false, it will return the value directly. Default is true.
*/
get useClone() {
return this._useClone;
}
/**
* Sets whether to use clone
* @param {boolean} value - If true, it will clone the value before returning it. If false, it will return the value directly. Default is true.
*/
set useClone(value) {
this._useClone = value;
}
/**
* Gets the size of the LRU cache
* @returns {number} - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. If you are using LRU then the limit is based on Map() size 17mm.
*/
get lruSize() {
return this._lruSize;
}
/**
* Sets the size of the LRU cache
* @param {number} value - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. If you are using LRU then the limit is based on Map() size 17mm.
*/
set lruSize(value) {
if (value > maximumMapSize) {
this.emit(
"error",
new Error(
`LRU size cannot be larger than ${maximumMapSize} due to Map limitations.`
)
);
return;
}
this._lruSize = value;
if (this._lruSize === 0) {
this._lru = new DoublyLinkedList();
return;
}
this.lruResize();
}
/**
* Gets the check interval
* @returns {number} - The interval to check for expired items. If set to 0, it will not check for expired items. Default is 0.
*/
get checkInterval() {
return this._checkInterval;
}
/**
* Sets the check interval
* @param {number} value - The interval to check for expired items. If set to 0, it will not check for expired items. Default is 0.
*/
set checkInterval(value) {
this._checkInterval = value;
}
/**
* Gets the size of the cache
* @returns {number} - The size of the cache
*/
get size() {
let size = 0;
for (const store of this._store) {
size += store.size;
}
return size;
}
/**
* Gets the number of hash stores
* @returns {number} - The number of hash stores
*/
get storeHashSize() {
return this._storeHashSize;
}
/**
* Sets the number of hash stores. This will recreate the store and all data will be cleared
* @param {number} value - The number of hash stores
*/
set storeHashSize(value) {
if (value === this._storeHashSize) {
return;
}
this._storeHashSize = value;
this._store = Array.from(
{ length: this._storeHashSize },
() => /* @__PURE__ */ new Map()
);
}
/**
* Gets the store hash algorithm
* @returns {HashAlgorithm | StoreHashAlgorithmFunction} - The store hash algorithm
*/
get storeHashAlgorithm() {
return this._storeHashAlgorithm;
}
/**
* Sets the store hash algorithm. This will recreate the store and all data will be cleared
* @param {HashAlgorithm | HashAlgorithmFunction} value - The store hash algorithm
*/
set storeHashAlgorithm(value) {
this._storeHashAlgorithm = value;
}
/**
* Gets the keys
* @returns {IterableIterator<string>} - The keys
*/
get keys() {
const keys = [];
for (const store of this._store) {
for (const key of store.keys()) {
const item = store.get(key);
if (item && this.hasExpired(item)) {
store.delete(key);
this.lruRemove(key);
continue;
}
keys.push(key);
}
}
return keys.values();
}
/**
* Gets the items
* @returns {IterableIterator<CacheableStoreItem>} - The items
*/
get items() {
const items = [];
for (const store of this._store) {
for (const item of store.values()) {
if (this.hasExpired(item)) {
store.delete(item.key);
this.lruRemove(item.key);
continue;
}
items.push(item);
}
}
return items.values();
}
/**
* Gets the store
* @returns {Array<Map<string, CacheableStoreItem>>} - The store
*/
get store() {
return this._store;
}
/**
* Gets the value of the key
* @param {string} key - The key to get the value
* @returns {T | undefined} - The value of the key
*/
get(key) {
const store = this.getStore(key);
const item = store.get(key);
if (!item) {
return void 0;
}
if (item.expires && Date.now() > item.expires) {
store.delete(key);
this.lruRemove(key);
return void 0;
}
this.lruMoveToFront(key);
if (!this._useClone) {
return item.value;
}
return this.clone(item.value);
}
/**
* Gets the values of the keys
* @param {string[]} keys - The keys to get the values
* @returns {T[]} - The values of the keys
*/
getMany(keys) {
const result = [];
for (const key of keys) {
result.push(this.get(key));
}
return result;
}
/**
* Gets the raw value of the key
* @param {string} key - The key to get the value
* @returns {CacheableStoreItem | undefined} - The raw value of the key
*/
getRaw(key) {
const store = this.getStore(key);
const item = store.get(key);
if (!item) {
return void 0;
}
if (item.expires && item.expires && Date.now() > item.expires) {
store.delete(key);
this.lruRemove(key);
return void 0;
}
this.lruMoveToFront(key);
return item;
}
/**
* Gets the raw values of the keys
* @param {string[]} keys - The keys to get the values
* @returns {CacheableStoreItem[]} - The raw values of the keys
*/
getManyRaw(keys) {
const result = [];
for (const key of keys) {
result.push(this.getRaw(key));
}
return result;
}
/**
* Sets the value of the key
* @param {string} key - The key to set the value
* @param {any} value - The value to set
* @param {number|string|SetOptions} [ttl] - Time to Live - If you set a number it is miliseconds, if you set a string it is a human-readable.
* If you want to set expire directly you can do that by setting the expire property in the SetOptions.
* If you set undefined, it will use the default time-to-live. If both are undefined then it will not have a time-to-live.
* @returns {void}
*/
set(key, value, ttl) {
const store = this.getStore(key);
let expires;
if (ttl !== void 0 || this._ttl !== void 0) {
if (typeof ttl === "object") {
if (ttl.expire) {
expires = typeof ttl.expire === "number" ? ttl.expire : ttl.expire.getTime();
}
if (ttl.ttl) {
const finalTtl = shorthandToTime(ttl.ttl);
if (finalTtl !== void 0) {
expires = finalTtl;
}
}
} else {
const finalTtl = shorthandToTime(ttl ?? this._ttl);
if (finalTtl !== void 0) {
expires = finalTtl;
}
}
}
if (this._lruSize > 0) {
if (store.has(key)) {
this.lruMoveToFront(key);
} else {
this.lruAddToFront(key);
if (this._lru.size > this._lruSize) {
const oldestKey = this._lru.getOldest();
if (oldestKey) {
this._lru.removeOldest();
this.delete(oldestKey);
}
}
}
}
const item = { key, value, expires };
store.set(key, item);
}
/**
* Sets the values of the keys
* @param {CacheableItem[]} items - The items to set
* @returns {void}
*/
setMany(items) {
for (const item of items) {
this.set(item.key, item.value, item.ttl);
}
}
/**
* Checks if the key exists
* @param {string} key - The key to check
* @returns {boolean} - If true, the key exists. If false, the key does not exist.
*/
has(key) {
const item = this.get(key);
return Boolean(item);
}
/**
* @function hasMany
* @param {string[]} keys - The keys to check
* @returns {boolean[]} - If true, the key exists. If false, the key does not exist.
*/
hasMany(keys) {
const result = [];
for (const key of keys) {
const item = this.get(key);
result.push(Boolean(item));
}
return result;
}
/**
* Take will get the key and delete the entry from cache
* @param {string} key - The key to take
* @returns {T | undefined} - The value of the key
*/
take(key) {
const item = this.get(key);
if (!item) {
return void 0;
}
this.delete(key);
return item;
}
/**
* TakeMany will get the keys and delete the entries from cache
* @param {string[]} keys - The keys to take
* @returns {T[]} - The values of the keys
*/
takeMany(keys) {
const result = [];
for (const key of keys) {
result.push(this.take(key));
}
return result;
}
/**
* Delete the key
* @param {string} key - The key to delete
* @returns {void}
*/
delete(key) {
const store = this.getStore(key);
store.delete(key);
this.lruRemove(key);
}
/**
* Delete the keys
* @param {string[]} keys - The keys to delete
* @returns {void}
*/
deleteMany(keys) {
for (const key of keys) {
this.delete(key);
}
}
/**
* Clear the cache
* @returns {void}
*/
clear() {
this._store = Array.from(
{ length: this._storeHashSize },
() => /* @__PURE__ */ new Map()
);
this._lru = new DoublyLinkedList();
}
/**
* Get the store based on the key (internal use)
* @param {string} key - The key to get the store
* @returns {CacheableHashStore} - The store
*/
getStore(key) {
const hash2 = this.getKeyStoreHash(key);
this._store[hash2] ||= /* @__PURE__ */ new Map();
return this._store[hash2];
}
/**
* Hash the key for which store to go to (internal use)
* @param {string} key - The key to hash
* Available algorithms are: SHA256, SHA1, MD5, and djb2Hash.
* @returns {number} - The hashed key as a number
*/
getKeyStoreHash(key) {
if (this._store.length === 1) {
return 0;
}
if (typeof this._storeHashAlgorithm === "function") {
return this._storeHashAlgorithm(key, this._storeHashSize);
}
const storeHashSize = this._storeHashSize - 1;
const hash2 = hashToNumberSync(key, {
min: 0,
max: storeHashSize,
algorithm: this._storeHashAlgorithm
});
return hash2;
}
/**
* Clone the value. This is for internal use
* @param {any} value - The value to clone
* @returns {any} - The cloned value
*/
// biome-ignore lint/suspicious/noExplicitAny: type format
clone(value) {
if (this.isPrimitive(value)) {
return value;
}
return structuredClone(value);
}
/**
* Add to the front of the LRU cache. This is for internal use
* @param {string} key - The key to add to the front
* @returns {void}
*/
lruAddToFront(key) {
if (this._lruSize === 0) {
return;
}
this._lru.addToFront(key);
}
/**
* Move to the front of the LRU cache. This is for internal use
* @param {string} key - The key to move to the front
* @returns {void}
*/
lruMoveToFront(key) {
if (this._lruSize === 0) {
return;
}
this._lru.moveToFront(key);
}
/**
* Remove a key from the LRU cache. This is for internal use
* @param {string} key - The key to remove
* @returns {void}
*/
lruRemove(key) {
if (this._lruSize === 0) {
return;
}
this._lru.remove(key);
}
/**
* Resize the LRU cache. This is for internal use.
* @returns {void}
*/
lruResize() {
while (this._lru.size > this._lruSize) {
const oldestKey = this._lru.getOldest();
if (oldestKey) {
this._lru.removeOldest();
this.delete(oldestKey);
}
}
}
/**
* Check for expiration. This is for internal use
* @returns {void}
*/
checkExpiration() {
for (const store of this._store) {
for (const item of store.values()) {
if (item.expires && Date.now() > item.expires) {
store.delete(item.key);
this.lruRemove(item.key);
}
}
}
}
/**
* Start the interval check. This is for internal use
* @returns {void}
*/
startIntervalCheck() {
if (this._checkInterval > 0) {
if (this._interval) {
clearInterval(this._interval);
}
this._interval = setInterval(() => {
this.checkExpiration();
}, this._checkInterval).unref();
}
}
/**
* Stop the interval check. This is for internal use
* @returns {void}
*/
stopIntervalCheck() {
if (this._interval) {
clearInterval(this._interval);
}
this._interval = 0;
this._checkInterval = 0;
}
/**
* Wrap the function for caching
* @param {Function} function_ - The function to wrap
* @param {Object} [options] - The options to wrap
* @returns {Function} - The wrapped function
*/
// biome-ignore lint/suspicious/noExplicitAny: type format
wrap(function_, options) {
const wrapOptions = {
ttl: options?.ttl ?? this._ttl,
keyPrefix: options?.keyPrefix,
createKey: options?.createKey,
cache: this
};
return wrapSync(function_, wrapOptions);
}
// biome-ignore lint/suspicious/noExplicitAny: type format
isPrimitive(value) {
const result = false;
if (value === null || value === void 0) {
return true;
}
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
return true;
}
return result;
}
setTtl(ttl) {
if (typeof ttl === "string" || ttl === void 0) {
this._ttl = ttl;
} else if (ttl > 0) {
this._ttl = ttl;
} else {
this._ttl = void 0;
}
}
hasExpired(item) {
if (item.expires && Date.now() > item.expires) {
return true;
}
return false;
}
};
export {
CacheableMemory,
HashAlgorithm2 as HashAlgorithm,
KeyvCacheableMemory,
createKeyv,
defaultStoreHashSize,
hash,
hashToNumber,
maximumMapSize
};
/* v8 ignore next -- @preserve */

69
node_modules/@cacheable/memory/package.json generated vendored Normal file
View file

@ -0,0 +1,69 @@
{
"name": "@cacheable/memory",
"version": "2.0.8",
"description": "High Performance In-Memory Cache for Node.js",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/jaredwray/cacheable.git",
"directory": "packages/cacheable"
},
"author": "Jared Wray <me@jaredwray.com>",
"license": "MIT",
"private": false,
"devDependencies": {
"@faker-js/faker": "^10.3.0",
"@types/node": "^25.3.0",
"rimraf": "^6.1.3",
"tsup": "^8.5.1",
"typescript": "^5.9.3"
},
"dependencies": {
"@keyv/bigmap": "^1.3.1",
"hookified": "^1.15.1",
"keyv": "^5.6.0",
"@cacheable/utils": "^2.4.0"
},
"keywords": [
"cacheable",
"high performance",
"distributed caching",
"Keyv storage engine",
"keyv",
"memory caching",
"LRU cache",
"memory",
"in-memory",
"scalable cache",
"in-memory cache",
"lruSize",
"lru"
],
"files": [
"dist",
"LICENSE"
],
"scripts": {
"build": "rimraf ./dist && tsup src/index.ts --format cjs,esm --dts --clean",
"prepublish": "pnpm build",
"lint": "biome check --write --error-on-warnings",
"test": "pnpm lint && vitest run --coverage",
"test:ci": "biome check --error-on-warnings && vitest run --coverage",
"clean": "rimraf ./dist ./coverage ./node_modules"
}
}

19
node_modules/@cacheable/utils/LICENSE generated vendored Normal file
View file

@ -0,0 +1,19 @@
MIT License & © Jared Wray
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

520
node_modules/@cacheable/utils/README.md generated vendored Normal file
View file

@ -0,0 +1,520 @@
[<img align="center" src="https://cacheable.org/logo.svg" alt="Cacheable" />](https://github.com/jaredwray/cacheable)
> Cacheble Utils
[![codecov](https://codecov.io/gh/jaredwray/cacheable/branch/main/graph/badge.svg?token=lWZ9OBQ7GM)](https://codecov.io/gh/jaredwray/cacheable)
[![tests](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml/badge.svg)](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml)
[![npm](https://img.shields.io/npm/dm/@cacheable/utils.svg)](https://www.npmjs.com/package/@cacheable/utils)
[![npm](https://img.shields.io/npm/v/@cacheable/utils)](https://www.npmjs.com/package/@cacheable/utils)
[![license](https://img.shields.io/github/license/jaredwray/cacheable)](https://github.com/jaredwray/cacheable/blob/main/LICENSE)
`@cacheable/utils` is a collecton of utility functions, helpers, and types for `cacheable` and other caching libraries. It provides a robust set of features to enhance caching capabilities, including:
* Data Types for Caching Items
* Hash Functions for Key Generation
* Coalesce Async for Handling Multiple Promises
* Stats Helpers for Caching Statistics
* Sleep / Delay for Testing and Timing
* Memoization for wraping or get / set options
* Time to Live (TTL) Helpers
# Table of Contents
* [Getting Started](#getting-started)
* [Cacheable Types](#cacheable-types)
* [Coalesce Async](#coalesce-async)
* [Hash Functions](#hash-functions)
* [Shorthand Time Helpers](#shorthand-time-helpers)
* [Sleep Helper](#sleep-helper)
* [Stats Helpers](#stats-helpers)
* [Time to Live (TTL) Helpers](#time-to-live-ttl-helpers)
* [Run if Function Helper](#run-if-function-helper)
* [Less Than Helper](#less-than-helper)
* [Is Object Helper](#is-object-helper)
* [Wrap / Memoization for Sync and Async Functions](#wrap--memoization-for-sync-and-async-functions)
* [Get Or Set Memoization Function](#get-or-set-memoization-function)
* [How to Contribute](#how-to-contribute)
* [License and Copyright](#license-and-copyright)
# Getting Started
```bash
npm install @cacheable/utils --save
```
# Cacheable Types
The `@cacheable/utils` package provides various types that are used throughout the caching library. These types help in defining the structure of cached items, ensuring type safety and consistency across your caching operations.
```typescript
/**
* CacheableItem
* @typedef {Object} CacheableItem
* @property {string} key - The key of the cacheable item
* @property {any} value - The value of the cacheable item
* @property {number|string} [ttl] - Time to Live - If you set a number it is miliseconds, if you set a string it is a human-readable
* format such as `1s` for 1 second or `1h` for 1 hour. Setting undefined means that it will use the default time-to-live. If both are
* undefined then it will not have a time-to-live.
*/
export type CacheableItem = {
key: string;
value: any;
ttl?: number | string;
};
/**
* CacheableStoreItem
* @typedef {Object} CacheableStoreItem
* @property {string} key - The key of the cacheable store item
* @property {any} value - The value of the cacheable store item
* @property {number} [expires] - The expiration time in milliseconds since epoch. If not set, the item does not expire.
*/
export type CacheableStoreItem = {
key: string;
value: any;
expires?: number;
};
```
# Coalesce Async
The `coalesceAsync` function is a utility that allows you to handle multiple asynchronous operations efficiently. It was designed by `Douglas Cayers` https://github.com/douglascayers/promise-coalesce. It helps in coalescing multiple promises into a single promise, ensuring that only one operation is executed at a time for the same key.
```typescript
import { coalesceAsync } from '@cacheable/utils';
const fetchData = async (key: string) => {
// Simulate an asynchronous operation
return new Promise((resolve) => setTimeout(() => resolve(`Data for ${key}`), 1000));
};
const result = await Promise.all([
coalesceAsync('my-key', fetchData),
coalesceAsync('my-key', fetchData),
coalesceAsync('my-key', fetchData),
]);
console.log(result); // Data for my-key only executed once
```
# Hash Functions
The `@cacheable/utils` package provides hash functions that can be used to generate unique keys for caching operations. These functions are useful for creating consistent and unique identifiers for cached items.
The hashing API provides both **async** (for cryptographic algorithms) and **sync** (for non-cryptographic algorithms) methods.
## Async Hashing (Cryptographic Algorithms)
Use `hash()` and `hashToNumber()` for cryptographic algorithms like SHA-256, SHA-384, and SHA-512:
```typescript
import { hash, hashToNumber, HashAlgorithm } from '@cacheable/utils';
// Hash using SHA-256 (default)
const key = await hash('my-cache-key');
console.log(key); // Unique hash for 'my-cache-key'
// Hash with specific algorithm
const sha512Hash = await hash('my-data', { algorithm: HashAlgorithm.SHA512 });
// Convert hash to number within range
const min = 0;
const max = 10;
const result = await hashToNumber({foo: 'bar'}, { min, max, algorithm: HashAlgorithm.SHA256 });
console.log(result); // A number between 0 and 10 based on the hash value
```
## Sync Hashing (Non-Cryptographic Algorithms)
Use `hashSync()` and `hashToNumberSync()` for faster, non-cryptographic algorithms like DJB2, FNV1, MURMER, and CRC32:
```typescript
import { hashSync, hashToNumberSync, HashAlgorithm } from '@cacheable/utils';
// Hash using DJB2 (default for sync)
const key = hashSync('my-cache-key');
console.log(key); // Unique hash for 'my-cache-key'
// Hash with specific algorithm
const fnv1Hash = hashSync('my-data', { algorithm: HashAlgorithm.FNV1 });
// Convert hash to number within range
const min = 0;
const max = 10;
const result = hashToNumberSync({foo: 'bar'}, { min, max, algorithm: HashAlgorithm.DJB2 });
console.log(result); // A number between 0 and 10 based on the hash value
```
## Available Hash Algorithms
**Cryptographic (Async):**
- `HashAlgorithm.SHA256` - SHA-256 (default for async methods)
- `HashAlgorithm.SHA384` - SHA-384
- `HashAlgorithm.SHA512` - SHA-512
**Non-Cryptographic (Sync):**
- `HashAlgorithm.DJB2` - DJB2 (default for sync methods)
- `HashAlgorithm.FNV1` - FNV-1
- `HashAlgorithm.MURMER` - Murmur hash
- `HashAlgorithm.CRC32` - CRC32
# Shorthand Time Helpers
The `@cacheable/utils` package provides a shorthand function to convert human-readable time strings into milliseconds. This is useful for setting time-to-live (TTL) values in caching operations.
You can also use the `shorthandToMilliseconds` function:
```typescript
import { shorthandToMilliseconds } from '@cacheable/utils';
const milliseconds = shorthandToMilliseconds('1h');
console.log(milliseconds); // 3600000
```
You can also use the `shorthandToTime` function to get the current date plus the shorthand time:
```typescript
import { shorthandToTime } from '@cacheable/utils';
const currentDate = new Date();
const timeInMs = shorthandToTime('1h', currentDate);
console.log(timeInMs); // Current date + 1 hour in milliseconds since epoch
```
# Sleep Helper
The `sleep` function is a utility that allows you to pause execution for a specified duration. This can be useful in testing scenarios or when you need to introduce delays in your code.
```typescript
import { sleep } from '@cacheable/utils';
await sleep(1000); // Pause for 1 second
console.log('Execution resumed after 1 second');
```
# Stats Helpers
The `@cacheable/utils` package provides statistics helpers that can be used to track and analyze caching operations. These helpers can be used to gather metrics such as hit rates, miss rates, and other performance-related statistics.
```typescript
import { stats } from '@cacheable/utils';
const cacheStats = stats();
cacheStats.incrementHits();
console.log(cacheStats.hits); // Get the hit rate of the cache
```
# Time to Live (TTL) Helpers
The `@cacheable/utils` package provides helpers for managing time-to-live (TTL) values for cached items.
You can use the `calculateTtlFromExpiration` function to calculate the TTL based on an expiration date:
```typescript
import { calculateTtlFromExpiration } from '@cacheable/utils';
const expirationDate = new Date(Date.now() + 1000 * 60 * 5); // 5 minutes from now
const ttl = calculateTtlFromExpiration(Date.now(), expirationDate);
console.log(ttl); // 300000
```
You can also use `getTtlFromExpires` to get the TTL from an expiration date:
```typescript
import { getTtlFromExpires } from '@cacheable/utils';
const expirationDate = new Date(Date.now() + 1000 * 60 * 5); // 5 minutes from now
const ttl = getTtlFromExpires(expirationDate);
console.log(ttl); // 300000
```
You can use `getCascadingTtl` to get the TTL for cascading cache operations:
```typescript
import { getCascadingTtl } from '@cacheable/utils';
const cacheableTtl = 1000 * 60 * 5; // 5 minutes
const primaryTtl = 1000 * 60 * 2; // 2 minutes
const secondaryTtl = 1000 * 60; // 1 minute
const ttl = getCascadingTtl(cacheableTtl, primaryTtl, secondaryTtl);
```
# Run if Function Helper
The `runIfFn` utility function provides a convenient way to conditionally execute functions or return values based on whether the input is a function or not. This pattern is commonly used in UI libraries and configuration systems where values can be either static or computed.
```typescript
import { runIfFn } from '@cacheable/utils';
// Static value - returns the value as-is
const staticValue = runIfFn('hello world');
console.log(staticValue); // 'hello world'
// Function with no arguments - executes the function
const dynamicValue = runIfFn(() => new Date().toISOString());
console.log(dynamicValue); // Current timestamp
// Function with arguments - executes with provided arguments
const sum = runIfFn((a: number, b: number) => a + b, 5, 10);
console.log(sum); // 15
// Complex example with conditional logic
const getConfig = (isDevelopment: boolean) => ({
apiUrl: isDevelopment ? 'http://localhost:3000' : 'https://api.example.com',
timeout: isDevelopment ? 5000 : 30000
});
const config = runIfFn(getConfig, true);
console.log(config); // { apiUrl: 'http://localhost:3000', timeout: 5000 }
```
# Less Than Helper
The `lessThan` utility function provides a safe way to compare two values and determine if the first value is less than the second. It only performs the comparison if both values are valid numbers, returning `false` for any non-number inputs.
```typescript
import { lessThan } from '@cacheable/utils';
// Basic number comparisons
console.log(lessThan(1, 2)); // true
console.log(lessThan(2, 1)); // false
console.log(lessThan(1, 1)); // false
// Works with negative numbers
console.log(lessThan(-1, 0)); // true
console.log(lessThan(-2, -1)); // true
// Works with decimal numbers
console.log(lessThan(1.5, 2.5)); // true
console.log(lessThan(2.7, 2.7)); // false
// Safe handling of non-number values
console.log(lessThan("1", 2)); // false
console.log(lessThan(1, "2")); // false
console.log(lessThan(null, 1)); // false
console.log(lessThan(undefined, 1)); // false
console.log(lessThan(NaN, 1)); // false
// Useful in filtering and sorting operations
const numbers = [5, 2, 8, 1, 9];
const lessThanFive = numbers.filter(n => lessThan(n, 5));
console.log(lessThanFive); // [2, 1]
// Safe comparison in conditional logic
function processValue(a?: number, b?: number) {
if (lessThan(a, b)) {
return `${a} is less than ${b}`;
}
return 'Invalid comparison or a >= b';
}
```
This utility is particularly useful when dealing with potentially undefined or invalid numeric values, ensuring type safety in comparison operations.
# Is Object Helper
The `isObject` utility function provides a type-safe way to determine if a value is a plain object. It returns `true` for objects but `false` for arrays, `null`, functions, and primitive types. This function also serves as a TypeScript type guard.
```typescript
import { isObject } from '@cacheable/utils';
// Basic object detection
console.log(isObject({})); // true
console.log(isObject({ name: 'John', age: 30 })); // true
console.log(isObject(Object.create(null))); // true
// Arrays are not considered objects
console.log(isObject([])); // false
console.log(isObject([1, 2, 3])); // false
// null is not considered an object (despite typeof null === 'object')
console.log(isObject(null)); // false
// Primitive types return false
console.log(isObject('string')); // false
console.log(isObject(123)); // false
console.log(isObject(true)); // false
console.log(isObject(undefined)); // false
// Functions return false
console.log(isObject(() => {})); // false
console.log(isObject(Date)); // false
// Built-in object types return true
console.log(isObject(new Date())); // true
console.log(isObject(/regex/)); // true
console.log(isObject(new Error('test'))); // true
console.log(isObject(new Map())); // true
// TypeScript type guard usage
function processValue(value: unknown) {
if (isObject<{ name: string; age: number }>(value)) {
// TypeScript now knows value is an object with name and age properties
console.log(`Name: ${value.name}, Age: ${value.age}`);
}
}
// Useful for configuration validation
function validateConfig(config: unknown) {
if (!isObject(config)) {
throw new Error('Configuration must be an object');
}
// Safe to access object properties
return config;
}
// Filtering arrays for objects only
const mixedArray = [1, 'string', {}, [], null, { valid: true }];
const objectsOnly = mixedArray.filter(isObject);
console.log(objectsOnly); // [{}', { valid: true }]
```
This utility is particularly useful for:
- **Type validation** - Ensuring values are objects before accessing properties
- **TypeScript type guarding** - Narrowing types in conditional blocks
- **Configuration parsing** - Validating that configuration values are objects
- **Data filtering** - Separating objects from other data types
# Wrap / Memoization for Sync and Async Functions
The `@cacheable/utils` package provides two main functions: `wrap` and `wrapSync`. These functions are used to memoize asynchronous and synchronous functions, respectively.
```javascript
import { Cacheable } from 'cacheable';
const asyncFunction = async (value: number) => {
return Math.random() * value;
};
const cache = new Cacheable();
const options = {
ttl: '1h', // 1 hour
keyPrefix: 'p1', // key prefix. This is used if you have multiple functions and need to set a unique prefix.
cache,
}
const wrappedFunction = wrap(asyncFunction, options);
console.log(await wrappedFunction(2)); // 4
console.log(await wrappedFunction(2)); // 4 from cache
```
With `wrap` we have also included stampede protection so that a `Promise` based call will only be called once if multiple requests of the same are executed at the same time. Here is an example of how to test for stampede protection:
```javascript
import { Cacheable } from 'cacheable';
const asyncFunction = async (value: number) => {
return value;
};
const cache = new Cacheable();
const options = {
ttl: '1h', // 1 hour
keyPrefix: 'p1', // key prefix. This is used if you have multiple functions and need to set a unique prefix.
cache,
}
const wrappedFunction = wrap(asyncFunction, options);
const promises = [];
for (let i = 0; i < 10; i++) {
promises.push(wrappedFunction(i));
}
const results = await Promise.all(promises); // all results should be the same
console.log(results); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
```
In this example we are wrapping an `async` function in a cache with a `ttl` of `1 hour`. This will cache the result of the function for `1 hour` and then expire the value. You can also wrap a `sync` function in a cache:
```javascript
import { CacheableMemory } from 'cacheable';
const syncFunction = (value: number) => {
return value * 2;
};
const cache = new CacheableMemory();
const wrappedFunction = wrap(syncFunction, { ttl: '1h', key: 'syncFunction', cache });
console.log(wrappedFunction(2)); // 4
console.log(wrappedFunction(2)); // 4 from cache
```
In this example we are wrapping a `sync` function in a cache with a `ttl` of `1 hour`. This will cache the result of the function for `1 hour` and then expire the value. You can also set the `key` property in the `wrap()` options to set a custom key for the cache.
When an error occurs in the function it will not cache the value and will return the error. This is useful if you want to cache the results of a function but not cache the error. If you want it to cache the error you can set the `cacheError` property to `true` in the `wrap()` options. This is disabled by default.
```javascript
import { CacheableMemory } from 'cacheable';
const syncFunction = (value: number) => {
throw new Error('error');
};
const cache = new CacheableMemory();
const wrappedFunction = wrap(syncFunction, { ttl: '1h', key: 'syncFunction', cacheError: true, cache });
console.log(wrappedFunction()); // error
console.log(wrappedFunction()); // error from cache
```
If you would like to generate your own key for the wrapped function you can set the `createKey` property in the `wrap()` options. This is useful if you want to generate a key based on the arguments of the function or any other criteria.
```javascript
const cache = new Cacheable();
const options: WrapOptions = {
cache,
keyPrefix: 'test',
createKey: (function_, arguments_, options: WrapOptions) => `customKey:${options?.keyPrefix}:${arguments_[0]}`,
};
const wrapped = wrap((argument: string) => `Result for ${argument}`, options);
const result1 = await wrapped('arg1');
const result2 = await wrapped('arg1'); // Should hit the cache
console.log(result1); // Result for arg1
console.log(result2); // Result for arg1 (from cache)
```
We will pass in the `function` that is being wrapped, the `arguments` passed to the function, and the `options` used to wrap the function. You can then use these to generate a custom key for the cache.
# Get Or Set Memoization Function
The `getOrSet` method provides a convenient way to implement the cache-aside pattern. It attempts to retrieve a value from cache, and if not found, calls the provided function to compute the value and store it in cache before returning it. Here are the options:
```typescript
export type GetOrSetFunctionOptions = {
ttl?: number | string;
cacheErrors?: boolean;
throwErrors?: boolean;
nonBlocking?: boolean;
};
```
The `nonBlocking` option allows you to override the instance-level `nonBlocking` setting for the `get` call within `getOrSet`. When set to `false`, the `get` will block and wait for a response from the secondary store before deciding whether to call the provided function. When set to `true`, the primary store returns immediately and syncs from secondary in the background.
Here is an example of how to use the `getOrSet` method:
```javascript
import { Cacheable } from 'cacheable';
const cache = new Cacheable();
// Use getOrSet to fetch user data
const function_ = async () => Math.random() * 100;
const value = await getOrSet('randomValue', function_, { ttl: '1h', cache });
console.log(value); // e.g. 42.123456789
```
You can also use a function to compute the key for the function:
```javascript
import { Cacheable, GetOrSetOptions } from 'cacheable';
const cache = new Cacheable();
// Function to generate a key based on options
const generateKey = (options?: GetOrSetOptions) => {
return `custom_key_:${options?.cacheId || 'default'}`;
};
const function_ = async () => Math.random() * 100;
const value = await getOrSet(generateKey(), function_, { ttl: '1h', cache });
```
# How to Contribute
You can contribute by forking the repo and submitting a pull request. Please make sure to add tests and update the documentation. To learn more about how to contribute go to our main README [https://github.com/jaredwray/cacheable](https://github.com/jaredwray/cacheable). This will talk about how to `Open a Pull Request`, `Ask a Question`, or `Post an Issue`.
# License and Copyright
[MIT © Jared Wray](./LICENSE)

677
node_modules/@cacheable/utils/dist/index.cjs generated vendored Normal file
View file

@ -0,0 +1,677 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
HashAlgorithm: () => HashAlgorithm,
Stats: () => Stats,
calculateTtlFromExpiration: () => calculateTtlFromExpiration,
coalesceAsync: () => coalesceAsync,
createWrapKey: () => createWrapKey,
getCascadingTtl: () => getCascadingTtl,
getOrSet: () => getOrSet,
getTtlFromExpires: () => getTtlFromExpires,
hash: () => hash,
hashSync: () => hashSync,
hashToNumber: () => hashToNumber,
hashToNumberSync: () => hashToNumberSync,
isKeyvInstance: () => isKeyvInstance,
isObject: () => isObject,
lessThan: () => lessThan,
runIfFn: () => runIfFn,
shorthandToMilliseconds: () => shorthandToMilliseconds,
shorthandToTime: () => shorthandToTime,
sleep: () => sleep,
wrap: () => wrap,
wrapSync: () => wrapSync
});
module.exports = __toCommonJS(index_exports);
// src/shorthand-time.ts
var shorthandToMilliseconds = (shorthand) => {
let milliseconds;
if (shorthand === void 0) {
return void 0;
}
if (typeof shorthand === "number") {
milliseconds = shorthand;
} else {
if (typeof shorthand !== "string") {
return void 0;
}
shorthand = shorthand.trim();
if (Number.isNaN(Number(shorthand))) {
const match = /^([\d.]+)\s*(ms|s|m|h|hr|d)$/i.exec(shorthand);
if (!match) {
throw new Error(
`Unsupported time format: "${shorthand}". Use 'ms', 's', 'm', 'h', 'hr', or 'd'.`
);
}
const [, value, unit] = match;
const numericValue = Number.parseFloat(value);
const unitLower = unit.toLowerCase();
switch (unitLower) {
case "ms": {
milliseconds = numericValue;
break;
}
case "s": {
milliseconds = numericValue * 1e3;
break;
}
case "m": {
milliseconds = numericValue * 1e3 * 60;
break;
}
case "h": {
milliseconds = numericValue * 1e3 * 60 * 60;
break;
}
case "hr": {
milliseconds = numericValue * 1e3 * 60 * 60;
break;
}
case "d": {
milliseconds = numericValue * 1e3 * 60 * 60 * 24;
break;
}
/* v8 ignore next -- @preserve */
default: {
milliseconds = Number(shorthand);
}
}
} else {
milliseconds = Number(shorthand);
}
}
return milliseconds;
};
var shorthandToTime = (shorthand, fromDate) => {
fromDate ??= /* @__PURE__ */ new Date();
const milliseconds = shorthandToMilliseconds(shorthand);
if (milliseconds === void 0) {
return fromDate.getTime();
}
return fromDate.getTime() + milliseconds;
};
// src/coalesce-async.ts
var callbacks = /* @__PURE__ */ new Map();
function hasKey(key) {
return callbacks.has(key);
}
function addKey(key) {
callbacks.set(key, []);
}
function removeKey(key) {
callbacks.delete(key);
}
function addCallbackToKey(key, callback) {
const stash = getCallbacksByKey(key);
stash.push(callback);
callbacks.set(key, stash);
}
function getCallbacksByKey(key) {
return callbacks.get(key) ?? [];
}
async function enqueue(key) {
return new Promise((resolve, reject) => {
const callback = { resolve, reject };
addCallbackToKey(key, callback);
});
}
function dequeue(key) {
const stash = getCallbacksByKey(key);
removeKey(key);
return stash;
}
function coalesce(options) {
const { key, error, result } = options;
for (const callback of dequeue(key)) {
if (error) {
callback.reject(error);
} else {
callback.resolve(result);
}
}
}
async function coalesceAsync(key, fnc) {
if (!hasKey(key)) {
addKey(key);
try {
const result = await Promise.resolve(fnc());
coalesce({ key, result });
return result;
} catch (error) {
coalesce({ key, error });
throw error;
}
}
return enqueue(key);
}
// src/hash.ts
var import_hashery = require("hashery");
var HashAlgorithm = /* @__PURE__ */ ((HashAlgorithm2) => {
HashAlgorithm2["SHA256"] = "SHA-256";
HashAlgorithm2["SHA384"] = "SHA-384";
HashAlgorithm2["SHA512"] = "SHA-512";
HashAlgorithm2["DJB2"] = "djb2";
HashAlgorithm2["FNV1"] = "fnv1";
HashAlgorithm2["MURMER"] = "murmer";
HashAlgorithm2["CRC32"] = "crc32";
return HashAlgorithm2;
})(HashAlgorithm || {});
async function hash(object, options = {
algorithm: "SHA-256" /* SHA256 */,
serialize: JSON.stringify
}) {
const algorithm = options?.algorithm ?? "SHA-256" /* SHA256 */;
const serialize = options?.serialize ?? JSON.stringify;
const objectString = serialize(object);
const hashery = new import_hashery.Hashery();
return hashery.toHash(objectString, { algorithm });
}
function hashSync(object, options = {
algorithm: "djb2" /* DJB2 */,
serialize: JSON.stringify
}) {
const algorithm = options?.algorithm ?? "djb2" /* DJB2 */;
const serialize = options?.serialize ?? JSON.stringify;
const objectString = serialize(object);
const hashery = new import_hashery.Hashery();
return hashery.toHashSync(objectString, { algorithm });
}
async function hashToNumber(object, options = {
min: 0,
max: 10,
algorithm: "SHA-256" /* SHA256 */,
serialize: JSON.stringify
}) {
const min = options?.min ?? 0;
const max = options?.max ?? 10;
const algorithm = options?.algorithm ?? "SHA-256" /* SHA256 */;
const serialize = options?.serialize ?? JSON.stringify;
const hashLength = options?.hashLength ?? 16;
if (min >= max) {
throw new Error(
`Invalid range: min (${min}) must be less than max (${max})`
);
}
const objectString = serialize(object);
const hashery = new import_hashery.Hashery();
return hashery.toNumber(objectString, {
algorithm,
min,
max,
hashLength
});
}
function hashToNumberSync(object, options = {
min: 0,
max: 10,
algorithm: "djb2" /* DJB2 */,
serialize: JSON.stringify
}) {
const min = options?.min ?? 0;
const max = options?.max ?? 10;
const algorithm = options?.algorithm ?? "djb2" /* DJB2 */;
const serialize = options?.serialize ?? JSON.stringify;
const hashLength = options?.hashLength ?? 16;
if (min >= max) {
throw new Error(
`Invalid range: min (${min}) must be less than max (${max})`
);
}
const objectString = serialize(object);
const hashery = new import_hashery.Hashery();
return hashery.toNumberSync(objectString, {
algorithm,
min,
max,
hashLength
});
}
// src/is-keyv-instance.ts
var import_keyv = require("keyv");
function isKeyvInstance(keyv) {
if (keyv === null || keyv === void 0) {
return false;
}
if (keyv instanceof import_keyv.Keyv) {
return true;
}
const keyvMethods = [
"generateIterator",
"get",
"getMany",
"set",
"setMany",
"delete",
"deleteMany",
"has",
"hasMany",
"clear",
"disconnect",
"serialize",
"deserialize"
];
return keyvMethods.every((method) => typeof keyv[method] === "function");
}
// src/is-object.ts
function isObject(value) {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
// src/less-than.ts
function lessThan(number1, number2) {
return typeof number1 === "number" && typeof number2 === "number" ? number1 < number2 : false;
}
// src/memoize.ts
function wrapSync(function_, options) {
const { ttl, keyPrefix, cache, serialize } = options;
return (...arguments_) => {
let cacheKey = createWrapKey(function_, arguments_, {
keyPrefix,
serialize
});
if (options.createKey) {
cacheKey = options.createKey(function_, arguments_, options);
}
let value = cache.get(cacheKey);
if (value === void 0) {
try {
value = function_(...arguments_);
cache.set(cacheKey, value, ttl);
} catch (error) {
cache.emit("error", error);
if (options.cacheErrors) {
cache.set(cacheKey, error, ttl);
}
}
}
return value;
};
}
async function getOrSet(key, function_, options) {
const keyString = typeof key === "function" ? key(options) : key;
let value;
try {
value = await options.cache.get(keyString);
} catch (error) {
options.cache.emit("error", error);
if (options.throwErrors === true || options.throwErrors === "store") {
throw error;
}
}
if (value === void 0) {
const cacheId = options.cacheId ?? "default";
const coalesceKey = `${cacheId}::${keyString}`;
value = await coalesceAsync(coalesceKey, async () => {
let result;
try {
try {
result = await function_();
} catch (error) {
throw new ErrorEnvelope(
error,
"function"
);
}
try {
await options.cache.set(keyString, result, options.ttl);
} catch (error) {
throw new ErrorEnvelope(error, "store");
}
return result;
} catch (caught) {
const errorType = caught instanceof ErrorEnvelope ? caught.context : (
/* c8 ignore next 1 */
void 0
);
const error = caught instanceof ErrorEnvelope ? caught.error : caught;
options.cache.emit("error", error);
if (options.cacheErrors) {
await options.cache.set(keyString, error, options.ttl);
}
if (options.throwErrors === true || options.throwErrors === errorType) {
throw error;
}
}
return result;
});
}
return value;
}
function wrap(function_, options) {
const { keyPrefix, serialize } = options;
return async (...arguments_) => {
let cacheKey = createWrapKey(function_, arguments_, {
keyPrefix,
serialize
});
if (options.createKey) {
cacheKey = options.createKey(function_, arguments_, options);
}
return getOrSet(
cacheKey,
async () => function_(...arguments_),
options
);
};
}
function createWrapKey(function_, arguments_, options) {
const { keyPrefix, serialize } = options || {};
if (!keyPrefix) {
return `${function_.name}::${hashSync(arguments_, { serialize })}`;
}
return `${keyPrefix}::${function_.name}::${hashSync(arguments_, { serialize })}`;
}
var ErrorEnvelope = class {
constructor(error, context) {
this.error = error;
this.context = context;
}
};
// src/run-if-fn.ts
function runIfFn(valueOrFunction, ...arguments_) {
return typeof valueOrFunction === "function" ? valueOrFunction(...arguments_) : valueOrFunction;
}
// src/sleep.ts
var sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// src/stats.ts
var Stats = class {
_hits = 0;
_misses = 0;
_gets = 0;
_sets = 0;
_deletes = 0;
_clears = 0;
_vsize = 0;
_ksize = 0;
_count = 0;
_enabled = false;
constructor(options) {
if (options?.enabled) {
this._enabled = options.enabled;
}
}
/**
* @returns {boolean} - Whether the stats are enabled
*/
get enabled() {
return this._enabled;
}
/**
* @param {boolean} enabled - Whether to enable the stats
*/
set enabled(enabled) {
this._enabled = enabled;
}
/**
* @returns {number} - The number of hits
* @readonly
*/
get hits() {
return this._hits;
}
/**
* @returns {number} - The number of misses
* @readonly
*/
get misses() {
return this._misses;
}
/**
* @returns {number} - The number of gets
* @readonly
*/
get gets() {
return this._gets;
}
/**
* @returns {number} - The number of sets
* @readonly
*/
get sets() {
return this._sets;
}
/**
* @returns {number} - The number of deletes
* @readonly
*/
get deletes() {
return this._deletes;
}
/**
* @returns {number} - The number of clears
* @readonly
*/
get clears() {
return this._clears;
}
/**
* @returns {number} - The vsize (value size) of the cache instance
* @readonly
*/
get vsize() {
return this._vsize;
}
/**
* @returns {number} - The ksize (key size) of the cache instance
* @readonly
*/
get ksize() {
return this._ksize;
}
/**
* @returns {number} - The count of the cache instance
* @readonly
*/
get count() {
return this._count;
}
incrementHits() {
if (!this._enabled) {
return;
}
this._hits++;
}
incrementMisses() {
if (!this._enabled) {
return;
}
this._misses++;
}
incrementGets() {
if (!this._enabled) {
return;
}
this._gets++;
}
incrementSets() {
if (!this._enabled) {
return;
}
this._sets++;
}
incrementDeletes() {
if (!this._enabled) {
return;
}
this._deletes++;
}
incrementClears() {
if (!this._enabled) {
return;
}
this._clears++;
}
incrementVSize(value) {
if (!this._enabled) {
return;
}
this._vsize += this.roughSizeOfObject(value);
}
decreaseVSize(value) {
if (!this._enabled) {
return;
}
this._vsize -= this.roughSizeOfObject(value);
}
incrementKSize(key) {
if (!this._enabled) {
return;
}
this._ksize += this.roughSizeOfString(key);
}
decreaseKSize(key) {
if (!this._enabled) {
return;
}
this._ksize -= this.roughSizeOfString(key);
}
incrementCount() {
if (!this._enabled) {
return;
}
this._count++;
}
decreaseCount() {
if (!this._enabled) {
return;
}
this._count--;
}
setCount(count) {
if (!this._enabled) {
return;
}
this._count = count;
}
roughSizeOfString(value) {
return value.length * 2;
}
roughSizeOfObject(object) {
const objectList = [];
const stack = [object];
let bytes = 0;
while (stack.length > 0) {
const value = stack.pop();
if (typeof value === "boolean") {
bytes += 4;
} else if (typeof value === "string") {
bytes += value.length * 2;
} else if (typeof value === "number") {
bytes += 8;
} else {
if (value === null || value === void 0) {
bytes += 4;
continue;
}
if (objectList.includes(value)) {
continue;
}
objectList.push(value);
for (const key in value) {
bytes += key.length * 2;
stack.push(value[key]);
}
}
}
return bytes;
}
reset() {
this._hits = 0;
this._misses = 0;
this._gets = 0;
this._sets = 0;
this._deletes = 0;
this._clears = 0;
this._vsize = 0;
this._ksize = 0;
this._count = 0;
}
resetStoreValues() {
this._vsize = 0;
this._ksize = 0;
this._count = 0;
}
};
// src/ttl.ts
function getTtlFromExpires(expires) {
if (expires === void 0 || expires === null) {
return void 0;
}
const now = Date.now();
if (expires < now) {
return void 0;
}
return expires - now;
}
function getCascadingTtl(cacheableTtl, primaryTtl, secondaryTtl) {
return secondaryTtl ?? primaryTtl ?? shorthandToMilliseconds(cacheableTtl);
}
function calculateTtlFromExpiration(ttl, expires) {
const ttlFromExpires = getTtlFromExpires(expires);
const expiresFromTtl = ttl ? Date.now() + ttl : void 0;
if (ttlFromExpires === void 0) {
return ttl;
}
if (expiresFromTtl === void 0) {
return ttlFromExpires;
}
if (expires && expires > expiresFromTtl) {
return ttl;
}
return ttlFromExpires;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
HashAlgorithm,
Stats,
calculateTtlFromExpiration,
coalesceAsync,
createWrapKey,
getCascadingTtl,
getOrSet,
getTtlFromExpires,
hash,
hashSync,
hashToNumber,
hashToNumberSync,
isKeyvInstance,
isObject,
lessThan,
runIfFn,
shorthandToMilliseconds,
shorthandToTime,
sleep,
wrap,
wrapSync
});
/* v8 ignore next -- @preserve */

307
node_modules/@cacheable/utils/dist/index.d.cts generated vendored Normal file
View file

@ -0,0 +1,307 @@
/**
* Converts a shorthand time string or number into milliseconds.
* The shorthand can be a string like '1s', '2m', '3h', '4d', or a number representing milliseconds.
* If the input is undefined, it returns undefined.
* If the input is a string that does not match the expected format, it throws an error.
* @param shorthand - A shorthand time string or number representing milliseconds.
* @returns The equivalent time in milliseconds or undefined.
*/
declare const shorthandToMilliseconds: (shorthand?: string | number) => number | undefined;
/**
* Converts a shorthand time string or number into a timestamp.
* If the shorthand is undefined, it returns the current date's timestamp.
* If the shorthand is a valid time format, it adds that duration to the current date's timestamp.
* @param shorthand - A shorthand time string or number representing milliseconds.
* @param fromDate - An optional Date object to calculate from. Defaults to the current date if not provided.
* @returns The timestamp in milliseconds since epoch.
*/
declare const shorthandToTime: (shorthand?: string | number, fromDate?: Date) => number;
/**
* CacheableItem
* @typedef {Object} CacheableItem
* @property {string} key - The key of the cacheable item
* @property {any} value - The value of the cacheable item
* @property {number|string} [ttl] - Time to Live - If you set a number it is miliseconds, if you set a string it is a human-readable
* format such as `1s` for 1 second or `1h` for 1 hour. Setting undefined means that it will use the default time-to-live. If both are
* undefined then it will not have a time-to-live.
*/
type CacheableItem = {
key: string;
value: any;
ttl?: number | string;
};
/**
* CacheableStoreItem
* @typedef {Object} CacheableStoreItem
* @property {string} key - The key of the cacheable store item
* @property {any} value - The value of the cacheable store item
* @property {number} [expires] - The expiration time in milliseconds since epoch. If not set, the item does not expire.
*/
type CacheableStoreItem = {
key: string;
value: any;
expires?: number;
};
/**
* Enqueue a promise for the group identified by `key`.
*
* All requests received for the same key while a request for that key
* is already being executed will wait. Once the running request settles
* then all the waiting requests in the group will settle, too.
* This minimizes how many times the function itself runs at the same time.
* This function resolves or rejects according to the given function argument.
*
* @url https://github.com/douglascayers/promise-coalesce
*/
declare function coalesceAsync<T>(
/**
* Any identifier to group requests together.
*/
key: string,
/**
* The function to run.
*/
fnc: () => T | PromiseLike<T>): Promise<T>;
declare enum HashAlgorithm {
SHA256 = "SHA-256",
SHA384 = "SHA-384",
SHA512 = "SHA-512",
DJB2 = "djb2",
FNV1 = "fnv1",
MURMER = "murmer",
CRC32 = "crc32"
}
type HashOptions = {
algorithm?: HashAlgorithm;
serialize?: (object: any) => string;
};
type HashToNumberOptions = HashOptions & {
min?: number;
max?: number;
hashLength?: number;
};
/**
* Hashes an object asynchronously using the specified cryptographic algorithm.
* This method should be used for cryptographic algorithms (SHA-256, SHA-384, SHA-512).
* For non-cryptographic algorithms, use hashSync() for better performance.
* @param object The object to hash
* @param options The hash options to use
* @returns {Promise<string>} The hash of the object
*/
declare function hash(object: any, options?: HashOptions): Promise<string>;
/**
* Hashes an object synchronously using the specified non-cryptographic algorithm.
* This method should be used for non-cryptographic algorithms (DJB2, FNV1, MURMER, CRC32).
* For cryptographic algorithms, use hash() instead.
* @param object The object to hash
* @param options The hash options to use
* @returns {string} The hash of the object
*/
declare function hashSync(object: any, options?: HashOptions): string;
/**
* Hashes an object asynchronously and converts it to a number within a specified range.
* This method should be used for cryptographic algorithms (SHA-256, SHA-384, SHA-512).
* For non-cryptographic algorithms, use hashToNumberSync() for better performance.
* @param object The object to hash
* @param options The hash options to use including min/max range
* @returns {Promise<number>} A number within the specified range
*/
declare function hashToNumber(object: any, options?: HashToNumberOptions): Promise<number>;
/**
* Hashes an object synchronously and converts it to a number within a specified range.
* This method should be used for non-cryptographic algorithms (DJB2, FNV1, MURMER, CRC32).
* For cryptographic algorithms, use hashToNumber() instead.
* @param object The object to hash
* @param options The hash options to use including min/max range
* @returns {number} A number within the specified range
*/
declare function hashToNumberSync(object: any, options?: HashToNumberOptions): number;
declare function isKeyvInstance(keyv: any): boolean;
declare function isObject<T = Record<string, unknown>>(value: unknown): value is T;
declare function lessThan(number1?: number, number2?: number): boolean;
type CacheInstance = {
get: (key: string) => Promise<any | undefined>;
has: (key: string) => Promise<boolean>;
set: (key: string, value: any, ttl?: number | string) => Promise<void>;
on: (event: string, listener: (...args: any[]) => void) => void;
emit: (event: string, ...args: any[]) => boolean;
};
type CacheSyncInstance = {
get: (key: string) => any | undefined;
has: (key: string) => boolean;
set: (key: string, value: any, ttl?: number | string) => void;
on: (event: string, listener: (...args: any[]) => void) => void;
emit: (event: string, ...args: any[]) => boolean;
};
type GetOrSetKey = string | ((options?: GetOrSetOptions) => string);
type GetOrSetThrowErrorsContext = "function" | "store";
type GetOrSetFunctionOptions = {
ttl?: number | string;
cacheErrors?: boolean;
/** Whether or not to throw errors:
* - `false` (default) - do not throw any errors
* - `true` - throw any error
* - `"function"` - only throw errors that occur in the provided function / setter
* - `"store"` - only throw errors that occur when getting/setting the cache
*/
throwErrors?: boolean | GetOrSetThrowErrorsContext;
/**
* If set, this will bypass the instances nonBlocking setting for the get call.
* @type {boolean}
*/
nonBlocking?: boolean;
};
type GetOrSetOptions = GetOrSetFunctionOptions & {
cacheId?: string;
cache: CacheInstance;
};
type CreateWrapKey = (function_: AnyFunction, arguments_: any[], options?: WrapFunctionOptions) => string;
type WrapFunctionOptions = {
ttl?: number | string;
keyPrefix?: string;
createKey?: CreateWrapKey;
cacheErrors?: boolean;
cacheId?: string;
serialize?: (object: any) => string;
};
type WrapOptions = WrapFunctionOptions & {
cache: CacheInstance;
serialize?: (object: any) => string;
};
type WrapSyncOptions = WrapFunctionOptions & {
cache: CacheSyncInstance;
serialize?: (object: any) => string;
};
type AnyFunction = (...arguments_: any[]) => any;
declare function wrapSync<T>(function_: AnyFunction, options: WrapSyncOptions): AnyFunction;
declare function getOrSet<T>(key: GetOrSetKey, function_: () => Promise<T>, options: GetOrSetOptions): Promise<T | undefined>;
declare function wrap<T>(function_: AnyFunction, options: WrapOptions): AnyFunction;
type CreateWrapKeyOptions = {
keyPrefix?: string;
serialize?: (object: any) => string;
};
declare function createWrapKey(function_: AnyFunction, arguments_: any[], options?: CreateWrapKeyOptions): string;
type Function_<P, T> = (...arguments_: P[]) => T;
declare function runIfFn<T, P>(valueOrFunction: T | Function_<P, T>, ...arguments_: P[]): T;
declare const sleep: (ms: number) => Promise<unknown>;
type StatsOptions = {
enabled?: boolean;
};
declare class Stats {
private _hits;
private _misses;
private _gets;
private _sets;
private _deletes;
private _clears;
private _vsize;
private _ksize;
private _count;
private _enabled;
constructor(options?: StatsOptions);
/**
* @returns {boolean} - Whether the stats are enabled
*/
get enabled(): boolean;
/**
* @param {boolean} enabled - Whether to enable the stats
*/
set enabled(enabled: boolean);
/**
* @returns {number} - The number of hits
* @readonly
*/
get hits(): number;
/**
* @returns {number} - The number of misses
* @readonly
*/
get misses(): number;
/**
* @returns {number} - The number of gets
* @readonly
*/
get gets(): number;
/**
* @returns {number} - The number of sets
* @readonly
*/
get sets(): number;
/**
* @returns {number} - The number of deletes
* @readonly
*/
get deletes(): number;
/**
* @returns {number} - The number of clears
* @readonly
*/
get clears(): number;
/**
* @returns {number} - The vsize (value size) of the cache instance
* @readonly
*/
get vsize(): number;
/**
* @returns {number} - The ksize (key size) of the cache instance
* @readonly
*/
get ksize(): number;
/**
* @returns {number} - The count of the cache instance
* @readonly
*/
get count(): number;
incrementHits(): void;
incrementMisses(): void;
incrementGets(): void;
incrementSets(): void;
incrementDeletes(): void;
incrementClears(): void;
incrementVSize(value: any): void;
decreaseVSize(value: any): void;
incrementKSize(key: string): void;
decreaseKSize(key: string): void;
incrementCount(): void;
decreaseCount(): void;
setCount(count: number): void;
roughSizeOfString(value: string): number;
roughSizeOfObject(object: any): number;
reset(): void;
resetStoreValues(): void;
}
/**
* Converts a exspires value to a TTL value.
* @param expires - The expires value to convert.
* @returns {number | undefined} The TTL value in milliseconds, or undefined if the expires value is not valid.
*/
declare function getTtlFromExpires(expires: number | undefined): number | undefined;
/**
* Get the TTL value from the cacheableTtl, primaryTtl, and secondaryTtl values.
* @param cacheableTtl - The cacheableTtl value to use.
* @param primaryTtl - The primaryTtl value to use.
* @param secondaryTtl - The secondaryTtl value to use.
* @returns {number | undefined} The TTL value in milliseconds, or undefined if all values are undefined.
*/
declare function getCascadingTtl(cacheableTtl?: number | string, primaryTtl?: number, secondaryTtl?: number): number | undefined;
/**
* Calculate the TTL value from the expires value. If the ttl is undefined, it will be set to the expires value. If the
* expires value is undefined, it will be set to the ttl value. If both values are defined, the smaller of the two will be used.
* @param ttl
* @param expires
* @returns
*/
declare function calculateTtlFromExpiration(ttl: number | undefined, expires: number | undefined): number | undefined;
export { type AnyFunction, type CacheInstance, type CacheSyncInstance, type CacheableItem, type CacheableStoreItem, type CreateWrapKey, type CreateWrapKeyOptions, type GetOrSetFunctionOptions, type GetOrSetKey, type GetOrSetOptions, HashAlgorithm, type HashOptions, type HashToNumberOptions, Stats, type StatsOptions, type WrapFunctionOptions, type WrapOptions, type WrapSyncOptions, calculateTtlFromExpiration, coalesceAsync, createWrapKey, getCascadingTtl, getOrSet, getTtlFromExpires, hash, hashSync, hashToNumber, hashToNumberSync, isKeyvInstance, isObject, lessThan, runIfFn, shorthandToMilliseconds, shorthandToTime, sleep, wrap, wrapSync };

307
node_modules/@cacheable/utils/dist/index.d.ts generated vendored Normal file
View file

@ -0,0 +1,307 @@
/**
* Converts a shorthand time string or number into milliseconds.
* The shorthand can be a string like '1s', '2m', '3h', '4d', or a number representing milliseconds.
* If the input is undefined, it returns undefined.
* If the input is a string that does not match the expected format, it throws an error.
* @param shorthand - A shorthand time string or number representing milliseconds.
* @returns The equivalent time in milliseconds or undefined.
*/
declare const shorthandToMilliseconds: (shorthand?: string | number) => number | undefined;
/**
* Converts a shorthand time string or number into a timestamp.
* If the shorthand is undefined, it returns the current date's timestamp.
* If the shorthand is a valid time format, it adds that duration to the current date's timestamp.
* @param shorthand - A shorthand time string or number representing milliseconds.
* @param fromDate - An optional Date object to calculate from. Defaults to the current date if not provided.
* @returns The timestamp in milliseconds since epoch.
*/
declare const shorthandToTime: (shorthand?: string | number, fromDate?: Date) => number;
/**
* CacheableItem
* @typedef {Object} CacheableItem
* @property {string} key - The key of the cacheable item
* @property {any} value - The value of the cacheable item
* @property {number|string} [ttl] - Time to Live - If you set a number it is miliseconds, if you set a string it is a human-readable
* format such as `1s` for 1 second or `1h` for 1 hour. Setting undefined means that it will use the default time-to-live. If both are
* undefined then it will not have a time-to-live.
*/
type CacheableItem = {
key: string;
value: any;
ttl?: number | string;
};
/**
* CacheableStoreItem
* @typedef {Object} CacheableStoreItem
* @property {string} key - The key of the cacheable store item
* @property {any} value - The value of the cacheable store item
* @property {number} [expires] - The expiration time in milliseconds since epoch. If not set, the item does not expire.
*/
type CacheableStoreItem = {
key: string;
value: any;
expires?: number;
};
/**
* Enqueue a promise for the group identified by `key`.
*
* All requests received for the same key while a request for that key
* is already being executed will wait. Once the running request settles
* then all the waiting requests in the group will settle, too.
* This minimizes how many times the function itself runs at the same time.
* This function resolves or rejects according to the given function argument.
*
* @url https://github.com/douglascayers/promise-coalesce
*/
declare function coalesceAsync<T>(
/**
* Any identifier to group requests together.
*/
key: string,
/**
* The function to run.
*/
fnc: () => T | PromiseLike<T>): Promise<T>;
declare enum HashAlgorithm {
SHA256 = "SHA-256",
SHA384 = "SHA-384",
SHA512 = "SHA-512",
DJB2 = "djb2",
FNV1 = "fnv1",
MURMER = "murmer",
CRC32 = "crc32"
}
type HashOptions = {
algorithm?: HashAlgorithm;
serialize?: (object: any) => string;
};
type HashToNumberOptions = HashOptions & {
min?: number;
max?: number;
hashLength?: number;
};
/**
* Hashes an object asynchronously using the specified cryptographic algorithm.
* This method should be used for cryptographic algorithms (SHA-256, SHA-384, SHA-512).
* For non-cryptographic algorithms, use hashSync() for better performance.
* @param object The object to hash
* @param options The hash options to use
* @returns {Promise<string>} The hash of the object
*/
declare function hash(object: any, options?: HashOptions): Promise<string>;
/**
* Hashes an object synchronously using the specified non-cryptographic algorithm.
* This method should be used for non-cryptographic algorithms (DJB2, FNV1, MURMER, CRC32).
* For cryptographic algorithms, use hash() instead.
* @param object The object to hash
* @param options The hash options to use
* @returns {string} The hash of the object
*/
declare function hashSync(object: any, options?: HashOptions): string;
/**
* Hashes an object asynchronously and converts it to a number within a specified range.
* This method should be used for cryptographic algorithms (SHA-256, SHA-384, SHA-512).
* For non-cryptographic algorithms, use hashToNumberSync() for better performance.
* @param object The object to hash
* @param options The hash options to use including min/max range
* @returns {Promise<number>} A number within the specified range
*/
declare function hashToNumber(object: any, options?: HashToNumberOptions): Promise<number>;
/**
* Hashes an object synchronously and converts it to a number within a specified range.
* This method should be used for non-cryptographic algorithms (DJB2, FNV1, MURMER, CRC32).
* For cryptographic algorithms, use hashToNumber() instead.
* @param object The object to hash
* @param options The hash options to use including min/max range
* @returns {number} A number within the specified range
*/
declare function hashToNumberSync(object: any, options?: HashToNumberOptions): number;
declare function isKeyvInstance(keyv: any): boolean;
declare function isObject<T = Record<string, unknown>>(value: unknown): value is T;
declare function lessThan(number1?: number, number2?: number): boolean;
type CacheInstance = {
get: (key: string) => Promise<any | undefined>;
has: (key: string) => Promise<boolean>;
set: (key: string, value: any, ttl?: number | string) => Promise<void>;
on: (event: string, listener: (...args: any[]) => void) => void;
emit: (event: string, ...args: any[]) => boolean;
};
type CacheSyncInstance = {
get: (key: string) => any | undefined;
has: (key: string) => boolean;
set: (key: string, value: any, ttl?: number | string) => void;
on: (event: string, listener: (...args: any[]) => void) => void;
emit: (event: string, ...args: any[]) => boolean;
};
type GetOrSetKey = string | ((options?: GetOrSetOptions) => string);
type GetOrSetThrowErrorsContext = "function" | "store";
type GetOrSetFunctionOptions = {
ttl?: number | string;
cacheErrors?: boolean;
/** Whether or not to throw errors:
* - `false` (default) - do not throw any errors
* - `true` - throw any error
* - `"function"` - only throw errors that occur in the provided function / setter
* - `"store"` - only throw errors that occur when getting/setting the cache
*/
throwErrors?: boolean | GetOrSetThrowErrorsContext;
/**
* If set, this will bypass the instances nonBlocking setting for the get call.
* @type {boolean}
*/
nonBlocking?: boolean;
};
type GetOrSetOptions = GetOrSetFunctionOptions & {
cacheId?: string;
cache: CacheInstance;
};
type CreateWrapKey = (function_: AnyFunction, arguments_: any[], options?: WrapFunctionOptions) => string;
type WrapFunctionOptions = {
ttl?: number | string;
keyPrefix?: string;
createKey?: CreateWrapKey;
cacheErrors?: boolean;
cacheId?: string;
serialize?: (object: any) => string;
};
type WrapOptions = WrapFunctionOptions & {
cache: CacheInstance;
serialize?: (object: any) => string;
};
type WrapSyncOptions = WrapFunctionOptions & {
cache: CacheSyncInstance;
serialize?: (object: any) => string;
};
type AnyFunction = (...arguments_: any[]) => any;
declare function wrapSync<T>(function_: AnyFunction, options: WrapSyncOptions): AnyFunction;
declare function getOrSet<T>(key: GetOrSetKey, function_: () => Promise<T>, options: GetOrSetOptions): Promise<T | undefined>;
declare function wrap<T>(function_: AnyFunction, options: WrapOptions): AnyFunction;
type CreateWrapKeyOptions = {
keyPrefix?: string;
serialize?: (object: any) => string;
};
declare function createWrapKey(function_: AnyFunction, arguments_: any[], options?: CreateWrapKeyOptions): string;
type Function_<P, T> = (...arguments_: P[]) => T;
declare function runIfFn<T, P>(valueOrFunction: T | Function_<P, T>, ...arguments_: P[]): T;
declare const sleep: (ms: number) => Promise<unknown>;
type StatsOptions = {
enabled?: boolean;
};
declare class Stats {
private _hits;
private _misses;
private _gets;
private _sets;
private _deletes;
private _clears;
private _vsize;
private _ksize;
private _count;
private _enabled;
constructor(options?: StatsOptions);
/**
* @returns {boolean} - Whether the stats are enabled
*/
get enabled(): boolean;
/**
* @param {boolean} enabled - Whether to enable the stats
*/
set enabled(enabled: boolean);
/**
* @returns {number} - The number of hits
* @readonly
*/
get hits(): number;
/**
* @returns {number} - The number of misses
* @readonly
*/
get misses(): number;
/**
* @returns {number} - The number of gets
* @readonly
*/
get gets(): number;
/**
* @returns {number} - The number of sets
* @readonly
*/
get sets(): number;
/**
* @returns {number} - The number of deletes
* @readonly
*/
get deletes(): number;
/**
* @returns {number} - The number of clears
* @readonly
*/
get clears(): number;
/**
* @returns {number} - The vsize (value size) of the cache instance
* @readonly
*/
get vsize(): number;
/**
* @returns {number} - The ksize (key size) of the cache instance
* @readonly
*/
get ksize(): number;
/**
* @returns {number} - The count of the cache instance
* @readonly
*/
get count(): number;
incrementHits(): void;
incrementMisses(): void;
incrementGets(): void;
incrementSets(): void;
incrementDeletes(): void;
incrementClears(): void;
incrementVSize(value: any): void;
decreaseVSize(value: any): void;
incrementKSize(key: string): void;
decreaseKSize(key: string): void;
incrementCount(): void;
decreaseCount(): void;
setCount(count: number): void;
roughSizeOfString(value: string): number;
roughSizeOfObject(object: any): number;
reset(): void;
resetStoreValues(): void;
}
/**
* Converts a exspires value to a TTL value.
* @param expires - The expires value to convert.
* @returns {number | undefined} The TTL value in milliseconds, or undefined if the expires value is not valid.
*/
declare function getTtlFromExpires(expires: number | undefined): number | undefined;
/**
* Get the TTL value from the cacheableTtl, primaryTtl, and secondaryTtl values.
* @param cacheableTtl - The cacheableTtl value to use.
* @param primaryTtl - The primaryTtl value to use.
* @param secondaryTtl - The secondaryTtl value to use.
* @returns {number | undefined} The TTL value in milliseconds, or undefined if all values are undefined.
*/
declare function getCascadingTtl(cacheableTtl?: number | string, primaryTtl?: number, secondaryTtl?: number): number | undefined;
/**
* Calculate the TTL value from the expires value. If the ttl is undefined, it will be set to the expires value. If the
* expires value is undefined, it will be set to the ttl value. If both values are defined, the smaller of the two will be used.
* @param ttl
* @param expires
* @returns
*/
declare function calculateTtlFromExpiration(ttl: number | undefined, expires: number | undefined): number | undefined;
export { type AnyFunction, type CacheInstance, type CacheSyncInstance, type CacheableItem, type CacheableStoreItem, type CreateWrapKey, type CreateWrapKeyOptions, type GetOrSetFunctionOptions, type GetOrSetKey, type GetOrSetOptions, HashAlgorithm, type HashOptions, type HashToNumberOptions, Stats, type StatsOptions, type WrapFunctionOptions, type WrapOptions, type WrapSyncOptions, calculateTtlFromExpiration, coalesceAsync, createWrapKey, getCascadingTtl, getOrSet, getTtlFromExpires, hash, hashSync, hashToNumber, hashToNumberSync, isKeyvInstance, isObject, lessThan, runIfFn, shorthandToMilliseconds, shorthandToTime, sleep, wrap, wrapSync };

630
node_modules/@cacheable/utils/dist/index.js generated vendored Normal file
View file

@ -0,0 +1,630 @@
// src/shorthand-time.ts
var shorthandToMilliseconds = (shorthand) => {
let milliseconds;
if (shorthand === void 0) {
return void 0;
}
if (typeof shorthand === "number") {
milliseconds = shorthand;
} else {
if (typeof shorthand !== "string") {
return void 0;
}
shorthand = shorthand.trim();
if (Number.isNaN(Number(shorthand))) {
const match = /^([\d.]+)\s*(ms|s|m|h|hr|d)$/i.exec(shorthand);
if (!match) {
throw new Error(
`Unsupported time format: "${shorthand}". Use 'ms', 's', 'm', 'h', 'hr', or 'd'.`
);
}
const [, value, unit] = match;
const numericValue = Number.parseFloat(value);
const unitLower = unit.toLowerCase();
switch (unitLower) {
case "ms": {
milliseconds = numericValue;
break;
}
case "s": {
milliseconds = numericValue * 1e3;
break;
}
case "m": {
milliseconds = numericValue * 1e3 * 60;
break;
}
case "h": {
milliseconds = numericValue * 1e3 * 60 * 60;
break;
}
case "hr": {
milliseconds = numericValue * 1e3 * 60 * 60;
break;
}
case "d": {
milliseconds = numericValue * 1e3 * 60 * 60 * 24;
break;
}
/* v8 ignore next -- @preserve */
default: {
milliseconds = Number(shorthand);
}
}
} else {
milliseconds = Number(shorthand);
}
}
return milliseconds;
};
var shorthandToTime = (shorthand, fromDate) => {
fromDate ??= /* @__PURE__ */ new Date();
const milliseconds = shorthandToMilliseconds(shorthand);
if (milliseconds === void 0) {
return fromDate.getTime();
}
return fromDate.getTime() + milliseconds;
};
// src/coalesce-async.ts
var callbacks = /* @__PURE__ */ new Map();
function hasKey(key) {
return callbacks.has(key);
}
function addKey(key) {
callbacks.set(key, []);
}
function removeKey(key) {
callbacks.delete(key);
}
function addCallbackToKey(key, callback) {
const stash = getCallbacksByKey(key);
stash.push(callback);
callbacks.set(key, stash);
}
function getCallbacksByKey(key) {
return callbacks.get(key) ?? [];
}
async function enqueue(key) {
return new Promise((resolve, reject) => {
const callback = { resolve, reject };
addCallbackToKey(key, callback);
});
}
function dequeue(key) {
const stash = getCallbacksByKey(key);
removeKey(key);
return stash;
}
function coalesce(options) {
const { key, error, result } = options;
for (const callback of dequeue(key)) {
if (error) {
callback.reject(error);
} else {
callback.resolve(result);
}
}
}
async function coalesceAsync(key, fnc) {
if (!hasKey(key)) {
addKey(key);
try {
const result = await Promise.resolve(fnc());
coalesce({ key, result });
return result;
} catch (error) {
coalesce({ key, error });
throw error;
}
}
return enqueue(key);
}
// src/hash.ts
import { Hashery } from "hashery";
var HashAlgorithm = /* @__PURE__ */ ((HashAlgorithm2) => {
HashAlgorithm2["SHA256"] = "SHA-256";
HashAlgorithm2["SHA384"] = "SHA-384";
HashAlgorithm2["SHA512"] = "SHA-512";
HashAlgorithm2["DJB2"] = "djb2";
HashAlgorithm2["FNV1"] = "fnv1";
HashAlgorithm2["MURMER"] = "murmer";
HashAlgorithm2["CRC32"] = "crc32";
return HashAlgorithm2;
})(HashAlgorithm || {});
async function hash(object, options = {
algorithm: "SHA-256" /* SHA256 */,
serialize: JSON.stringify
}) {
const algorithm = options?.algorithm ?? "SHA-256" /* SHA256 */;
const serialize = options?.serialize ?? JSON.stringify;
const objectString = serialize(object);
const hashery = new Hashery();
return hashery.toHash(objectString, { algorithm });
}
function hashSync(object, options = {
algorithm: "djb2" /* DJB2 */,
serialize: JSON.stringify
}) {
const algorithm = options?.algorithm ?? "djb2" /* DJB2 */;
const serialize = options?.serialize ?? JSON.stringify;
const objectString = serialize(object);
const hashery = new Hashery();
return hashery.toHashSync(objectString, { algorithm });
}
async function hashToNumber(object, options = {
min: 0,
max: 10,
algorithm: "SHA-256" /* SHA256 */,
serialize: JSON.stringify
}) {
const min = options?.min ?? 0;
const max = options?.max ?? 10;
const algorithm = options?.algorithm ?? "SHA-256" /* SHA256 */;
const serialize = options?.serialize ?? JSON.stringify;
const hashLength = options?.hashLength ?? 16;
if (min >= max) {
throw new Error(
`Invalid range: min (${min}) must be less than max (${max})`
);
}
const objectString = serialize(object);
const hashery = new Hashery();
return hashery.toNumber(objectString, {
algorithm,
min,
max,
hashLength
});
}
function hashToNumberSync(object, options = {
min: 0,
max: 10,
algorithm: "djb2" /* DJB2 */,
serialize: JSON.stringify
}) {
const min = options?.min ?? 0;
const max = options?.max ?? 10;
const algorithm = options?.algorithm ?? "djb2" /* DJB2 */;
const serialize = options?.serialize ?? JSON.stringify;
const hashLength = options?.hashLength ?? 16;
if (min >= max) {
throw new Error(
`Invalid range: min (${min}) must be less than max (${max})`
);
}
const objectString = serialize(object);
const hashery = new Hashery();
return hashery.toNumberSync(objectString, {
algorithm,
min,
max,
hashLength
});
}
// src/is-keyv-instance.ts
import { Keyv } from "keyv";
function isKeyvInstance(keyv) {
if (keyv === null || keyv === void 0) {
return false;
}
if (keyv instanceof Keyv) {
return true;
}
const keyvMethods = [
"generateIterator",
"get",
"getMany",
"set",
"setMany",
"delete",
"deleteMany",
"has",
"hasMany",
"clear",
"disconnect",
"serialize",
"deserialize"
];
return keyvMethods.every((method) => typeof keyv[method] === "function");
}
// src/is-object.ts
function isObject(value) {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
// src/less-than.ts
function lessThan(number1, number2) {
return typeof number1 === "number" && typeof number2 === "number" ? number1 < number2 : false;
}
// src/memoize.ts
function wrapSync(function_, options) {
const { ttl, keyPrefix, cache, serialize } = options;
return (...arguments_) => {
let cacheKey = createWrapKey(function_, arguments_, {
keyPrefix,
serialize
});
if (options.createKey) {
cacheKey = options.createKey(function_, arguments_, options);
}
let value = cache.get(cacheKey);
if (value === void 0) {
try {
value = function_(...arguments_);
cache.set(cacheKey, value, ttl);
} catch (error) {
cache.emit("error", error);
if (options.cacheErrors) {
cache.set(cacheKey, error, ttl);
}
}
}
return value;
};
}
async function getOrSet(key, function_, options) {
const keyString = typeof key === "function" ? key(options) : key;
let value;
try {
value = await options.cache.get(keyString);
} catch (error) {
options.cache.emit("error", error);
if (options.throwErrors === true || options.throwErrors === "store") {
throw error;
}
}
if (value === void 0) {
const cacheId = options.cacheId ?? "default";
const coalesceKey = `${cacheId}::${keyString}`;
value = await coalesceAsync(coalesceKey, async () => {
let result;
try {
try {
result = await function_();
} catch (error) {
throw new ErrorEnvelope(
error,
"function"
);
}
try {
await options.cache.set(keyString, result, options.ttl);
} catch (error) {
throw new ErrorEnvelope(error, "store");
}
return result;
} catch (caught) {
const errorType = caught instanceof ErrorEnvelope ? caught.context : (
/* c8 ignore next 1 */
void 0
);
const error = caught instanceof ErrorEnvelope ? caught.error : caught;
options.cache.emit("error", error);
if (options.cacheErrors) {
await options.cache.set(keyString, error, options.ttl);
}
if (options.throwErrors === true || options.throwErrors === errorType) {
throw error;
}
}
return result;
});
}
return value;
}
function wrap(function_, options) {
const { keyPrefix, serialize } = options;
return async (...arguments_) => {
let cacheKey = createWrapKey(function_, arguments_, {
keyPrefix,
serialize
});
if (options.createKey) {
cacheKey = options.createKey(function_, arguments_, options);
}
return getOrSet(
cacheKey,
async () => function_(...arguments_),
options
);
};
}
function createWrapKey(function_, arguments_, options) {
const { keyPrefix, serialize } = options || {};
if (!keyPrefix) {
return `${function_.name}::${hashSync(arguments_, { serialize })}`;
}
return `${keyPrefix}::${function_.name}::${hashSync(arguments_, { serialize })}`;
}
var ErrorEnvelope = class {
constructor(error, context) {
this.error = error;
this.context = context;
}
};
// src/run-if-fn.ts
function runIfFn(valueOrFunction, ...arguments_) {
return typeof valueOrFunction === "function" ? valueOrFunction(...arguments_) : valueOrFunction;
}
// src/sleep.ts
var sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// src/stats.ts
var Stats = class {
_hits = 0;
_misses = 0;
_gets = 0;
_sets = 0;
_deletes = 0;
_clears = 0;
_vsize = 0;
_ksize = 0;
_count = 0;
_enabled = false;
constructor(options) {
if (options?.enabled) {
this._enabled = options.enabled;
}
}
/**
* @returns {boolean} - Whether the stats are enabled
*/
get enabled() {
return this._enabled;
}
/**
* @param {boolean} enabled - Whether to enable the stats
*/
set enabled(enabled) {
this._enabled = enabled;
}
/**
* @returns {number} - The number of hits
* @readonly
*/
get hits() {
return this._hits;
}
/**
* @returns {number} - The number of misses
* @readonly
*/
get misses() {
return this._misses;
}
/**
* @returns {number} - The number of gets
* @readonly
*/
get gets() {
return this._gets;
}
/**
* @returns {number} - The number of sets
* @readonly
*/
get sets() {
return this._sets;
}
/**
* @returns {number} - The number of deletes
* @readonly
*/
get deletes() {
return this._deletes;
}
/**
* @returns {number} - The number of clears
* @readonly
*/
get clears() {
return this._clears;
}
/**
* @returns {number} - The vsize (value size) of the cache instance
* @readonly
*/
get vsize() {
return this._vsize;
}
/**
* @returns {number} - The ksize (key size) of the cache instance
* @readonly
*/
get ksize() {
return this._ksize;
}
/**
* @returns {number} - The count of the cache instance
* @readonly
*/
get count() {
return this._count;
}
incrementHits() {
if (!this._enabled) {
return;
}
this._hits++;
}
incrementMisses() {
if (!this._enabled) {
return;
}
this._misses++;
}
incrementGets() {
if (!this._enabled) {
return;
}
this._gets++;
}
incrementSets() {
if (!this._enabled) {
return;
}
this._sets++;
}
incrementDeletes() {
if (!this._enabled) {
return;
}
this._deletes++;
}
incrementClears() {
if (!this._enabled) {
return;
}
this._clears++;
}
incrementVSize(value) {
if (!this._enabled) {
return;
}
this._vsize += this.roughSizeOfObject(value);
}
decreaseVSize(value) {
if (!this._enabled) {
return;
}
this._vsize -= this.roughSizeOfObject(value);
}
incrementKSize(key) {
if (!this._enabled) {
return;
}
this._ksize += this.roughSizeOfString(key);
}
decreaseKSize(key) {
if (!this._enabled) {
return;
}
this._ksize -= this.roughSizeOfString(key);
}
incrementCount() {
if (!this._enabled) {
return;
}
this._count++;
}
decreaseCount() {
if (!this._enabled) {
return;
}
this._count--;
}
setCount(count) {
if (!this._enabled) {
return;
}
this._count = count;
}
roughSizeOfString(value) {
return value.length * 2;
}
roughSizeOfObject(object) {
const objectList = [];
const stack = [object];
let bytes = 0;
while (stack.length > 0) {
const value = stack.pop();
if (typeof value === "boolean") {
bytes += 4;
} else if (typeof value === "string") {
bytes += value.length * 2;
} else if (typeof value === "number") {
bytes += 8;
} else {
if (value === null || value === void 0) {
bytes += 4;
continue;
}
if (objectList.includes(value)) {
continue;
}
objectList.push(value);
for (const key in value) {
bytes += key.length * 2;
stack.push(value[key]);
}
}
}
return bytes;
}
reset() {
this._hits = 0;
this._misses = 0;
this._gets = 0;
this._sets = 0;
this._deletes = 0;
this._clears = 0;
this._vsize = 0;
this._ksize = 0;
this._count = 0;
}
resetStoreValues() {
this._vsize = 0;
this._ksize = 0;
this._count = 0;
}
};
// src/ttl.ts
function getTtlFromExpires(expires) {
if (expires === void 0 || expires === null) {
return void 0;
}
const now = Date.now();
if (expires < now) {
return void 0;
}
return expires - now;
}
function getCascadingTtl(cacheableTtl, primaryTtl, secondaryTtl) {
return secondaryTtl ?? primaryTtl ?? shorthandToMilliseconds(cacheableTtl);
}
function calculateTtlFromExpiration(ttl, expires) {
const ttlFromExpires = getTtlFromExpires(expires);
const expiresFromTtl = ttl ? Date.now() + ttl : void 0;
if (ttlFromExpires === void 0) {
return ttl;
}
if (expiresFromTtl === void 0) {
return ttlFromExpires;
}
if (expires && expires > expiresFromTtl) {
return ttl;
}
return ttlFromExpires;
}
export {
HashAlgorithm,
Stats,
calculateTtlFromExpiration,
coalesceAsync,
createWrapKey,
getCascadingTtl,
getOrSet,
getTtlFromExpires,
hash,
hashSync,
hashToNumber,
hashToNumberSync,
isKeyvInstance,
isObject,
lessThan,
runIfFn,
shorthandToMilliseconds,
shorthandToTime,
sleep,
wrap,
wrapSync
};
/* v8 ignore next -- @preserve */

56
node_modules/@cacheable/utils/package.json generated vendored Normal file
View file

@ -0,0 +1,56 @@
{
"name": "@cacheable/utils",
"version": "2.4.1",
"description": "Cacheable Utilities for Caching Libraries",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/jaredwray/cacheable.git",
"directory": "packages/utils"
},
"author": "Jared Wray <me@jaredwray.com>",
"license": "MIT",
"private": false,
"dependencies": {
"hashery": "^1.5.1",
"keyv": "^5.6.0"
},
"devDependencies": {
"tsup": "^8.5.1",
"typescript": "^5.9.3"
},
"keywords": [
"cacheable",
"caching",
"utilities",
"hashing",
"keyv",
"cache utils"
],
"files": [
"dist",
"LICENSE"
],
"scripts": {
"build": "rimraf ./dist && tsup src/index.ts --format cjs,esm --dts --clean",
"lint": "biome check --write --error-on-warnings",
"test": "pnpm lint && vitest run --coverage",
"test:ci": "biome check --error-on-warnings && vitest run --coverage",
"clean": "rimraf ./dist ./coverage ./node_modules"
}
}

10
node_modules/@csstools/css-calc/CHANGELOG.md generated vendored Normal file
View file

@ -0,0 +1,10 @@
# Changes to CSS Calc
### 3.2.0
_April 12, 2026_
- Add support for `round(line-width, 1.2345px)`
- Add `devicePixelLength` option
[Full CHANGELOG](https://github.com/csstools/postcss-plugins/tree/main/packages/css-calc/CHANGELOG.md)

20
node_modules/@csstools/css-calc/LICENSE.md generated vendored Normal file
View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright 2022 Romain Menke, Antonio Laguna <antonio@laguna.es>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

132
node_modules/@csstools/css-calc/README.md generated vendored Normal file
View file

@ -0,0 +1,132 @@
# CSS Calc <img src="https://cssdb.org/images/css.svg" alt="for CSS" width="90" height="90" align="right">
[<img alt="npm version" src="https://img.shields.io/npm/v/@csstools/css-calc.svg" height="20">][npm-url]
[<img alt="Build Status" src="https://github.com/csstools/postcss-plugins/actions/workflows/test.yml/badge.svg?branch=main" height="20">][cli-url]
[<img alt="Discord" src="https://shields.io/badge/Discord-5865F2?logo=discord&logoColor=white">][discord]
Implemented from : https://drafts.csswg.org/css-values-4/ on 2023-02-17
## Usage
Add [CSS calc] to your project:
```bash
npm install @csstools/css-calc @csstools/css-parser-algorithms @csstools/css-tokenizer --save-dev
```
### With string values :
```mjs
import { calc } from '@csstools/css-calc';
// '20'
console.log(calc('calc(10 * 2)'));
```
### With component values :
```mjs
import { stringify, tokenizer } from '@csstools/css-tokenizer';
import { parseCommaSeparatedListOfComponentValues } from '@csstools/css-parser-algorithms';
import { calcFromComponentValues } from '@csstools/css-calc';
const t = tokenizer({
css: 'calc(10 * 2)',
});
const tokens = [];
{
while (!t.endOfFile()) {
tokens.push(t.nextToken());
}
tokens.push(t.nextToken()); // EOF-token
}
const result = parseCommaSeparatedListOfComponentValues(tokens, {});
// filter or mutate the component values
const calcResult = calcFromComponentValues(result, { precision: 5, toCanonicalUnits: true });
// filter or mutate the component values even further
const calcResultStr = calcResult.map((componentValues) => {
return componentValues.map((x) => stringify(...x.tokens())).join('');
}).join(',');
// '20'
console.log(calcResultStr);
```
### Options
#### `precision` :
The default precision is fairly high.
It aims to be high enough to make rounding unnoticeable in the browser.
You can set it to a lower number to suit your needs.
```mjs
import { calc } from '@csstools/css-calc';
// '0.3'
console.log(calc('calc(1 / 3)', { precision: 1 }));
// '0.33'
console.log(calc('calc(1 / 3)', { precision: 2 }));
```
#### `globals` :
Pass global values as a map of key value pairs.
> Example : Relative color syntax (`lch(from pink calc(l / 2) c h)`) exposes color channel information as ident tokens.
> By passing globals for `l`, `c` and `h` it is possible to solve nested `calc()`'s.
```mjs
import { calc } from '@csstools/css-calc';
const globals = new Map([
['a', '10px'],
['b', '2rem'],
]);
// '20px'
console.log(calc('calc(a * 2)', { globals: globals }));
// '6rem'
console.log(calc('calc(b * 3)', { globals: globals }));
```
#### `toCanonicalUnits` :
By default this package will try to preserve units.
The heuristic to do this is very simplistic.
We take the first unit we encounter and try to convert other dimensions to that unit.
This better matches what users expect from a CSS dev tool.
If you want to have outputs that are closes to CSS serialized values you can pass `toCanonicalUnits: true`.
```mjs
import { calc } from '@csstools/css-calc';
// '20hz'
console.log(calc('calc(0.01khz + 10hz)', { toCanonicalUnits: true }));
// '20hz'
console.log(calc('calc(10hz + 0.01khz)', { toCanonicalUnits: true }));
// '0.02khz' !!!
console.log(calc('calc(0.01khz + 10hz)', { toCanonicalUnits: false }));
// '20hz'
console.log(calc('calc(10hz + 0.01khz)', { toCanonicalUnits: false }));
```
[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test
[discord]: https://discord.gg/bUadyRwkJS
[npm-url]: https://www.npmjs.com/package/@csstools/css-calc
[CSS calc]: https://github.com/csstools/postcss-plugins/tree/main/packages/css-calc

106
node_modules/@csstools/css-calc/dist/index.d.ts generated vendored Normal file
View file

@ -0,0 +1,106 @@
import { ComponentValue } from '@csstools/css-parser-algorithms';
import type { TokenDimension } from '@csstools/css-tokenizer';
import type { TokenNumber } from '@csstools/css-tokenizer';
import type { TokenPercentage } from '@csstools/css-tokenizer';
export declare function calc(css: string, options?: conversionOptions): string;
export declare function calcFromComponentValues(componentValuesList: Array<Array<ComponentValue>>, options?: conversionOptions): Array<Array<ComponentValue>>;
export declare type conversionOptions = {
/**
* If a calc expression can not be solved the parse error might be reported through this callback.
* Not all cases are covered. Open an issue if you need specific errors reported.
*
* Values are recursively visited and at each nesting level an attempt is made to solve the expression.
* Errors can be reported multiple times as a result of this.
*/
onParseError?: (error: ParseError) => void;
/**
* Pass global values as a map of key value pairs.
*/
globals?: GlobalsWithStrings;
/**
* The default precision is fairly high.
* It aims to be high enough to make rounding unnoticeable in the browser.
* You can set it to a lower number to suite your needs.
*/
precision?: number;
/**
* The CSS pixel length of one device pixel.
* Used when rounding to `line-width` and similar features
*/
devicePixelLength?: number;
/**
* By default this package will try to preserve units.
* The heuristic to do this is very simplistic.
* We take the first unit we encounter and try to convert other dimensions to that unit.
*
* This better matches what users expect from a CSS dev tool.
*
* If you want to have outputs that are closes to CSS serialized values you can set `true`.
*/
toCanonicalUnits?: boolean;
/**
* Convert NaN, Infinity, ... into standard representable values.
*/
censorIntoStandardRepresentableValues?: boolean;
/**
* Some percentages resolve against other values and might be negative or positive depending on context.
* Raw percentages are more likely to be safe to simplify outside of a browser context
*
* @see https://drafts.csswg.org/css-values-4/#calc-simplification
*/
rawPercentages?: boolean;
/**
* The values used to generate random value cache keys.
*/
randomCaching?: {
/**
* The name of the property the random function is used in.
*/
propertyName: string;
/**
* N is the index of the random function among other random functions in the same property value.
*/
propertyN: number;
/**
* An element ID identifying the element the style is being applied to.
* When omitted any `random()` call will not be computed.
*/
elementID: string;
/**
* A document ID identifying the Document the styles are from.
* When omitted any `random()` call will not be computed.
*/
documentID: string;
};
};
export declare type GlobalsWithStrings = Map<string, TokenDimension | TokenNumber | TokenPercentage | string>;
export declare const mathFunctionNames: Set<string>;
/**
* Any errors are reported through the `onParseError` callback.
*/
export declare class ParseError extends Error {
/** The index of the start character of the current token. */
sourceStart: number;
/** The index of the end character of the current token. */
sourceEnd: number;
constructor(message: string, sourceStart: number, sourceEnd: number);
}
export declare const ParseErrorMessage: {
UnexpectedAdditionOfDimensionOrPercentageWithNumber: string;
UnexpectedSubtractionOfDimensionOrPercentageWithNumber: string;
};
export declare class ParseErrorWithComponentValues extends ParseError {
/** The associated component values. */
componentValues: Array<ComponentValue>;
constructor(message: string, componentValues: Array<ComponentValue>);
}
export { }

1
node_modules/@csstools/css-calc/dist/index.mjs generated vendored Normal file

File diff suppressed because one or more lines are too long

59
node_modules/@csstools/css-calc/package.json generated vendored Normal file
View file

@ -0,0 +1,59 @@
{
"name": "@csstools/css-calc",
"description": "Solve CSS math expressions",
"version": "3.2.0",
"contributors": [
{
"name": "Antonio Laguna",
"email": "antonio@laguna.es",
"url": "https://antonio.laguna.es"
},
{
"name": "Romain Menke",
"email": "romainmenke@gmail.com"
}
],
"license": "MIT",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"engines": {
"node": ">=20.19.0"
},
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.mjs"
}
},
"files": [
"CHANGELOG.md",
"LICENSE.md",
"README.md",
"dist"
],
"peerDependencies": {
"@csstools/css-parser-algorithms": "^4.0.0",
"@csstools/css-tokenizer": "^4.0.0"
},
"scripts": {},
"homepage": "https://github.com/csstools/postcss-plugins/tree/main/packages/css-calc#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/csstools/postcss-plugins.git",
"directory": "packages/css-calc"
},
"bugs": "https://github.com/csstools/postcss-plugins/issues",
"keywords": [
"calc",
"css"
]
}

View file

@ -0,0 +1,11 @@
# Changes to CSS Parser Algorithms
### 4.0.0
_January 14, 2026_
- Updated: Support for Node `20.19.0` or later (major).
- Removed: `commonjs` API. In supported Node versions `require(esm)` will work without needing to make code changes.
- Updated [`@csstools/css-tokenizer`](https://github.com/csstools/postcss-plugins/tree/main/packages/css-tokenizer) to [`4.0.0`](https://github.com/csstools/postcss-plugins/tree/main/packages/css-tokenizer/CHANGELOG.md#400) (major)
[Full CHANGELOG](https://github.com/csstools/postcss-plugins/tree/main/packages/css-parser-algorithms/CHANGELOG.md)

View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright 2022 Romain Menke, Antonio Laguna <antonio@laguna.es>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

119
node_modules/@csstools/css-parser-algorithms/README.md generated vendored Normal file
View file

@ -0,0 +1,119 @@
# CSS Parser Algorithms <img src="https://cssdb.org/images/css.svg" alt="for CSS" width="90" height="90" align="right">
[<img alt="npm version" src="https://img.shields.io/npm/v/@csstools/css-parser-algorithms.svg" height="20">][npm-url]
[<img alt="Build Status" src="https://github.com/csstools/postcss-plugins/actions/workflows/test.yml/badge.svg?branch=main" height="20">][cli-url]
[<img alt="Discord" src="https://shields.io/badge/Discord-5865F2?logo=discord&logoColor=white">][discord]
Implemented from : https://www.w3.org/TR/2021/CRD-css-syntax-3-20211224/
## API
[Read the API docs](./docs/css-parser-algorithms.md)
## Usage
Add [CSS Parser Algorithms] to your project:
```bash
npm install @csstools/css-parser-algorithms @csstools/css-tokenizer --save-dev
```
[CSS Parser Algorithms] only accepts tokenized CSS.
It must be used together with `@csstools/css-tokenizer`.
```js
import { tokenizer, TokenType } from '@csstools/css-tokenizer';
import { parseComponentValue } from '@csstools/css-parser-algorithms';
const myCSS = `@media only screen and (min-width: 768rem) {
.foo {
content: 'Some content!' !important;
}
}
`;
const t = tokenizer({
css: myCSS,
});
const tokens = [];
{
while (!t.endOfFile()) {
tokens.push(t.nextToken());
}
tokens.push(t.nextToken()); // EOF-token
}
const options = {
onParseError: ((err) => {
throw err;
}),
};
const result = parseComponentValue(tokens, options);
console.log(result);
```
### Available functions
- [`parseComponentValue`](https://www.w3.org/TR/css-syntax-3/#parse-component-value)
- [`parseListOfComponentValues`](https://www.w3.org/TR/css-syntax-3/#parse-list-of-component-values)
- [`parseCommaSeparatedListOfComponentValues`](https://www.w3.org/TR/css-syntax-3/#parse-comma-separated-list-of-component-values)
### Utilities
#### `gatherNodeAncestry`
The AST does not expose the entire ancestry of each node.
The walker methods do provide access to the current parent, but also not the entire ancestry.
To gather the entire ancestry for a a given sub tree of the AST you can use `gatherNodeAncestry`.
The result is a `Map` with the child nodes as keys and the parents as values.
This allows you to lookup any ancestor of any node.
```js
import { parseComponentValue } from '@csstools/css-parser-algorithms';
const result = parseComponentValue(tokens, options);
const ancestry = gatherNodeAncestry(result);
```
### Options
```ts
{
onParseError?: (error: ParseError) => void
}
```
#### `onParseError`
The parser algorithms are forgiving and won't stop when a parse error is encountered.
Parse errors also aren't tokens.
To receive parsing error information you can set a callback.
Parser errors will try to inform you about the point in the parsing logic the error happened.
This tells you the kind of error.
## Goals and non-goals
Things this package aims to be:
- specification compliant CSS parser
- a reliable low level package to be used in CSS sub-grammars
What it is not:
- opinionated
- fast
- small
- a replacement for PostCSS (PostCSS is fast and also an ecosystem)
[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test
[discord]: https://discord.gg/bUadyRwkJS
[npm-url]: https://www.npmjs.com/package/@csstools/css-parser-algorithms
[CSS Parser Algorithms]: https://github.com/csstools/postcss-plugins/tree/main/packages/css-parser-algorithms

View file

@ -0,0 +1,604 @@
/**
* Parse CSS following the {@link https://drafts.csswg.org/css-syntax/#parsing | CSS Syntax Level 3 specification}.
*
* @remarks
* The tokenizing and parsing tools provided by CSS Tools are designed to be low level and generic with strong ties to their respective specifications.
*
* Any analysis or mutation of CSS source code should be done with the least powerful tool that can accomplish the task.
* For many applications it is sufficient to work with tokens.
* For others you might need to use {@link https://github.com/csstools/postcss-plugins/tree/main/packages/css-parser-algorithms | @csstools/css-parser-algorithms} or a more specific parser.
*
* The implementation of the AST nodes is kept lightweight and simple.
* Do not expect magic methods, instead assume that arrays and class instances behave like any other JavaScript.
*
* @example
* Parse a string of CSS into a component value:
* ```js
* import { tokenize } from '@csstools/css-tokenizer';
* import { parseComponentValue } from '@csstools/css-parser-algorithms';
*
* const myCSS = `calc(1px * 2)`;
*
* const componentValue = parseComponentValue(tokenize({
* css: myCSS,
* }));
*
* console.log(componentValue);
* ```
*
* @example
* Use the right algorithm for the job.
*
* Algorithms that can parse larger structures (comma-separated lists, ...) can also parse smaller structures.
* However, the opposite is not true.
*
* If your context allows a list of component values, use {@link parseListOfComponentValues}:
* ```js
* import { tokenize } from '@csstools/css-tokenizer';
* import { parseListOfComponentValues } from '@csstools/css-parser-algorithms';
*
* parseListOfComponentValues(tokenize({ css: `10x 20px` }));
* ```
*
* If your context allows a comma-separated list of component values, use {@link parseCommaSeparatedListOfComponentValues}:
* ```js
* import { tokenize } from '@csstools/css-tokenizer';
* import { parseCommaSeparatedListOfComponentValues } from '@csstools/css-parser-algorithms';
*
* parseCommaSeparatedListOfComponentValues(tokenize({ css: `20deg, 50%, 30%` }));
* ```
*
* @example
* Use the stateful walkers to keep track of the context of a given component value.
*
* ```js
* import { tokenize } from '@csstools/css-tokenizer';
* import { parseComponentValue, isSimpleBlockNode } from '@csstools/css-parser-algorithms';
*
* const myCSS = `calc(1px * (5 / 2))`;
*
* const componentValue = parseComponentValue(tokenize({ css: myCSS }));
*
* let state = { inSimpleBlock: false };
* componentValue.walk((entry) => {
* if (isSimpleBlockNode(entry)) {
* entry.state.inSimpleBlock = true;
* return;
* }
*
* if (entry.state.inSimpleBlock) {
* console.log(entry.node.toString()); // `5`, ...
* }
* }, state);
* ```
*
* @packageDocumentation
*/
import type { CSSToken } from '@csstools/css-tokenizer';
import { ParseError } from '@csstools/css-tokenizer';
import type { TokenFunction } from '@csstools/css-tokenizer';
export declare class CommentNode {
/**
* The node type, always `ComponentValueType.Comment`
*/
type: ComponentValueType;
/**
* The comment token.
*/
value: CSSToken;
constructor(value: CSSToken);
/**
* Retrieve the tokens for the current comment.
* This is the inverse of parsing from a list of tokens.
*/
tokens(): Array<CSSToken>;
/**
* Convert the current comment to a string.
* This is not a true serialization.
* It is purely a concatenation of the string representation of the tokens.
*/
toString(): string;
/**
* @internal
*
* A debug helper to convert the current object to a JSON representation.
* This is useful in asserts and to store large ASTs in files.
*/
toJSON(): Record<string, unknown>;
/**
* @internal
*/
isCommentNode(): this is CommentNode;
/**
* @internal
*/
static isCommentNode(x: unknown): x is CommentNode;
}
export declare type ComponentValue = FunctionNode | SimpleBlockNode | WhitespaceNode | CommentNode | TokenNode;
export declare enum ComponentValueType {
Function = "function",
SimpleBlock = "simple-block",
Whitespace = "whitespace",
Comment = "comment",
Token = "token"
}
export declare type ContainerNode = FunctionNode | SimpleBlockNode;
export declare abstract class ContainerNodeBaseClass {
/**
* The contents of the `Function` or `Simple Block`.
* This is a list of component values.
*/
value: Array<ComponentValue>;
/**
* Retrieve the index of the given item in the current node.
* For most node types this will be trivially implemented as `this.value.indexOf(item)`.
*/
indexOf(item: ComponentValue): number | string;
/**
* Retrieve the item at the given index in the current node.
* For most node types this will be trivially implemented as `this.value[index]`.
*/
at(index: number | string): ComponentValue | undefined;
/**
* Iterates over each item in the `value` array of the current node.
*
* @param cb - The callback function to execute for each item.
* The function receives an object containing the current node (`node`), its parent (`parent`),
* and an optional `state` object.
* A second parameter is the index of the current node.
* The function can return `false` to stop the iteration.
*
* @param state - An optional state object that can be used to pass additional information to the callback function.
* The state object is cloned for each iteration. This means that changes to the state object are not reflected in the next iteration.
*
* @returns `false` if the iteration was halted, `undefined` otherwise.
*/
forEach<T extends Record<string, unknown>, U extends ContainerNode>(this: U, cb: (entry: {
node: ComponentValue;
parent: ContainerNode;
state?: T;
}, index: number | string) => boolean | void, state?: T): false | undefined;
/**
* Walks the current node and all its children.
*
* @param cb - The callback function to execute for each item.
* The function receives an object containing the current node (`node`), its parent (`parent`),
* and an optional `state` object.
* A second parameter is the index of the current node.
* The function can return `false` to stop the iteration.
*
* @param state - An optional state object that can be used to pass additional information to the callback function.
* The state object is cloned for each iteration. This means that changes to the state object are not reflected in the next iteration.
* However changes are passed down to child node iterations.
*
* @returns `false` if the iteration was halted, `undefined` otherwise.
*/
walk<T extends Record<string, unknown>, U extends ContainerNode>(this: U, cb: (entry: {
node: ComponentValue;
parent: ContainerNode;
state?: T;
}, index: number | string) => boolean | void, state?: T): false | undefined;
}
/**
* Iterates over each item in a list of component values.
*
* @param cb - The callback function to execute for each item.
* The function receives an object containing the current node (`node`), its parent (`parent`),
* and an optional `state` object.
* A second parameter is the index of the current node.
* The function can return `false` to stop the iteration.
*
* @param state - An optional state object that can be used to pass additional information to the callback function.
* The state object is cloned for each iteration. This means that changes to the state object are not reflected in the next iteration.
*
* @returns `false` if the iteration was halted, `undefined` otherwise.
*/
export declare function forEach<T extends Record<string, unknown>>(componentValues: Array<ComponentValue>, cb: (entry: {
node: ComponentValue;
parent: ContainerNode | {
value: Array<ComponentValue>;
};
state?: T;
}, index: number | string) => boolean | void, state?: T): false | undefined;
/**
* A function node.
*
* @example
* ```js
* const node = parseComponentValue(tokenize('calc(1 + 1)'));
*
* isFunctionNode(node); // true
* node.getName(); // 'calc'
* ```
*/
export declare class FunctionNode extends ContainerNodeBaseClass {
/**
* The node type, always `ComponentValueType.Function`
*/
type: ComponentValueType;
/**
* The token for the name of the function.
*/
name: TokenFunction;
/**
* The token for the closing parenthesis of the function.
* If the function is unclosed, this will be an EOF token.
*/
endToken: CSSToken;
constructor(name: TokenFunction, endToken: CSSToken, value: Array<ComponentValue>);
/**
* Retrieve the name of the current function.
* This is the parsed and unescaped name of the function.
*/
getName(): string;
/**
* Normalize the current function:
* 1. if the "endToken" is EOF, replace with a ")-token"
*/
normalize(): void;
/**
* Retrieve the tokens for the current function.
* This is the inverse of parsing from a list of tokens.
*/
tokens(): Array<CSSToken>;
/**
* Convert the current function to a string.
* This is not a true serialization.
* It is purely a concatenation of the string representation of the tokens.
*/
toString(): string;
/**
* @internal
*
* A debug helper to convert the current object to a JSON representation.
* This is useful in asserts and to store large ASTs in files.
*/
toJSON(): unknown;
/**
* @internal
*/
isFunctionNode(): this is FunctionNode;
/**
* @internal
*/
static isFunctionNode(x: unknown): x is FunctionNode;
}
/**
* AST nodes do not have a `parent` property or method.
* This makes it harder to traverse the AST upwards.
* This function builds a `Map<Child, Parent>` that can be used to lookup ancestors of a node.
*
* @remarks
* There is no magic behind this or the map it returns.
* Mutating the AST will not update the map.
*
* Types are erased and any content of the map has type `unknown`.
* If someone knows a clever way to type this, please let us know.
*
* @example
* ```js
* const ancestry = gatherNodeAncestry(mediaQuery);
* mediaQuery.walk((entry) => {
* const node = entry.node; // directly exposed
* const parent = entry.parent; // directly exposed
* const grandParent: unknown = ancestry.get(parent); // lookup
*
* console.log('node', node);
* console.log('parent', parent);
* console.log('grandParent', grandParent);
* });
* ```
*/
export declare function gatherNodeAncestry(node: {
walk(cb: (entry: {
node: unknown;
parent: unknown;
}, index: number | string) => boolean | void): false | undefined;
}): Map<unknown, unknown>;
/**
* Check if the current object is a `CommentNode`.
* This is a type guard.
*/
export declare function isCommentNode(x: unknown): x is CommentNode;
/**
* Check if the current object is a `FunctionNode`.
* This is a type guard.
*/
export declare function isFunctionNode(x: unknown): x is FunctionNode;
/**
* Check if the current object is a `SimpleBlockNode`.
* This is a type guard.
*/
export declare function isSimpleBlockNode(x: unknown): x is SimpleBlockNode;
/**
* Check if the current object is a `TokenNode`.
* This is a type guard.
*/
export declare function isTokenNode(x: unknown): x is TokenNode;
/**
* Check if the current object is a `WhitespaceNode`.
* This is a type guard.
*/
export declare function isWhitespaceNode(x: unknown): x is WhitespaceNode;
/**
* Check if the current object is a `WhiteSpaceNode` or a `CommentNode`.
* This is a type guard.
*/
export declare function isWhiteSpaceOrCommentNode(x: unknown): x is WhitespaceNode | CommentNode;
/**
* Parse a comma-separated list of component values.
*
* @example
* ```js
* import { tokenize } from '@csstools/css-tokenizer';
* import { parseCommaSeparatedListOfComponentValues } from '@csstools/css-parser-algorithms';
*
* parseCommaSeparatedListOfComponentValues(tokenize({ css: `20deg, 50%, 30%` }));
* ```
*/
export declare function parseCommaSeparatedListOfComponentValues(tokens: Array<CSSToken>, options?: {
onParseError?: (error: ParseError) => void;
}): Array<Array<ComponentValue>>;
/**
* Parse a single component value.
*
* @example
* ```js
* import { tokenize } from '@csstools/css-tokenizer';
* import { parseComponentValue } from '@csstools/css-parser-algorithms';
*
* parseComponentValue(tokenize({ css: `10px` }));
* parseComponentValue(tokenize({ css: `calc((10px + 1x) * 4)` }));
* ```
*/
export declare function parseComponentValue(tokens: Array<CSSToken>, options?: {
onParseError?: (error: ParseError) => void;
}): ComponentValue | undefined;
/**
* Parse a list of component values.
*
* @example
* ```js
* import { tokenize } from '@csstools/css-tokenizer';
* import { parseListOfComponentValues } from '@csstools/css-parser-algorithms';
*
* parseListOfComponentValues(tokenize({ css: `20deg 30%` }));
* ```
*/
export declare function parseListOfComponentValues(tokens: Array<CSSToken>, options?: {
onParseError?: (error: ParseError) => void;
}): Array<ComponentValue>;
/**
* Replace specific component values in a list of component values.
* A helper for the most common and simplistic cases when mutating an AST.
*/
export declare function replaceComponentValues(componentValuesList: Array<Array<ComponentValue>>, replaceWith: (componentValue: ComponentValue) => Array<ComponentValue> | ComponentValue | void): Array<Array<ComponentValue>>;
/**
* A simple block node.
*
* @example
* ```js
* const node = parseComponentValue(tokenize('[foo=bar]'));
*
* isSimpleBlockNode(node); // true
* node.startToken; // [TokenType.OpenSquare, '[', 0, 0, undefined]
* ```
*/
export declare class SimpleBlockNode extends ContainerNodeBaseClass {
/**
* The node type, always `ComponentValueType.SimpleBlock`
*/
type: ComponentValueType;
/**
* The token for the opening token of the block.
*/
startToken: CSSToken;
/**
* The token for the closing token of the block.
* If the block is closed it will be the mirror variant of the `startToken`.
* If the block is unclosed, this will be an EOF token.
*/
endToken: CSSToken;
constructor(startToken: CSSToken, endToken: CSSToken, value: Array<ComponentValue>);
/**
* Normalize the current simple block
* 1. if the "endToken" is EOF, replace with the mirror token of the "startToken"
*/
normalize(): void;
/**
* Retrieve the tokens for the current simple block.
* This is the inverse of parsing from a list of tokens.
*/
tokens(): Array<CSSToken>;
/**
* Convert the current simple block to a string.
* This is not a true serialization.
* It is purely a concatenation of the string representation of the tokens.
*/
toString(): string;
/**
* @internal
*
* A debug helper to convert the current object to a JSON representation.
* This is useful in asserts and to store large ASTs in files.
*/
toJSON(): unknown;
/**
* @internal
*/
isSimpleBlockNode(): this is SimpleBlockNode;
/**
* @internal
*/
static isSimpleBlockNode(x: unknown): x is SimpleBlockNode;
}
/**
* Returns the start and end index of a node in the CSS source string.
*/
export declare function sourceIndices(x: {
tokens(): Array<CSSToken>;
} | Array<{
tokens(): Array<CSSToken>;
}>): [number, number];
/**
* Concatenate the string representation of a collection of component values.
* This is not a proper serializer that will handle escaping and whitespace.
* It only produces valid CSS for token lists that are also valid.
*/
export declare function stringify(componentValueLists: Array<Array<ComponentValue>>): string;
export declare class TokenNode {
/**
* The node type, always `ComponentValueType.Token`
*/
type: ComponentValueType;
/**
* The token.
*/
value: CSSToken;
constructor(value: CSSToken);
/**
* This is the inverse of parsing from a list of tokens.
*/
tokens(): [CSSToken];
/**
* Convert the current token to a string.
* This is not a true serialization.
* It is purely the string representation of token.
*/
toString(): string;
/**
* @internal
*
* A debug helper to convert the current object to a JSON representation.
* This is useful in asserts and to store large ASTs in files.
*/
toJSON(): Record<string, unknown>;
/**
* @internal
*/
isTokenNode(): this is TokenNode;
/**
* @internal
*/
static isTokenNode(x: unknown): x is TokenNode;
}
/**
* Walks each item in a list of component values all of their children.
*
* @param cb - The callback function to execute for each item.
* The function receives an object containing the current node (`node`), its parent (`parent`),
* and an optional `state` object.
* A second parameter is the index of the current node.
* The function can return `false` to stop the iteration.
*
* @param state - An optional state object that can be used to pass additional information to the callback function.
* The state object is cloned for each iteration. This means that changes to the state object are not reflected in the next iteration.
* However changes are passed down to child node iterations.
*
* @returns `false` if the iteration was halted, `undefined` otherwise.
*
* @example
* ```js
* import { tokenize } from '@csstools/css-tokenizer';
* import { parseListOfComponentValues, isSimpleBlockNode } from '@csstools/css-parser-algorithms';
*
* const myCSS = `calc(1px * (5 / 2)) 10px`;
*
* const componentValues = parseListOfComponentValues(tokenize({ css: myCSS }));
*
* let state = { inSimpleBlock: false };
* walk(componentValues, (entry) => {
* if (isSimpleBlockNode(entry)) {
* entry.state.inSimpleBlock = true;
* return;
* }
*
* if (entry.state.inSimpleBlock) {
* console.log(entry.node.toString()); // `5`, ...
* }
* }, state);
* ```
*/
export declare function walk<T extends Record<string, unknown>>(componentValues: Array<ComponentValue>, cb: (entry: {
node: ComponentValue;
parent: ContainerNode | {
value: Array<ComponentValue>;
};
state?: T;
}, index: number | string) => boolean | void, state?: T): false | undefined;
/**
* Generate a function that finds the next element that should be visited when walking an AST.
* Rules :
* 1. the previous iteration is used as a reference, so any checks are relative to the start of the current iteration.
* 2. the next element always appears after the current index.
* 3. the next element always exists in the list.
* 4. replacing an element does not cause the replaced element to be visited.
* 5. removing an element does not cause elements to be skipped.
* 6. an element added later in the list will be visited.
*/
export declare function walkerIndexGenerator<T>(initialList: Array<T>): (list: Array<T>, child: T, index: number) => number;
export declare class WhitespaceNode {
/**
* The node type, always `ComponentValueType.WhiteSpace`
*/
type: ComponentValueType;
/**
* The list of consecutive whitespace tokens.
*/
value: Array<CSSToken>;
constructor(value: Array<CSSToken>);
/**
* Retrieve the tokens for the current whitespace.
* This is the inverse of parsing from a list of tokens.
*/
tokens(): Array<CSSToken>;
/**
* Convert the current whitespace to a string.
* This is not a true serialization.
* It is purely a concatenation of the string representation of the tokens.
*/
toString(): string;
/**
* @internal
*
* A debug helper to convert the current object to a JSON representation.
* This is useful in asserts and to store large ASTs in files.
*/
toJSON(): Record<string, unknown>;
/**
* @internal
*/
isWhitespaceNode(): this is WhitespaceNode;
/**
* @internal
*/
static isWhitespaceNode(x: unknown): x is WhitespaceNode;
}
export { }

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,58 @@
{
"name": "@csstools/css-parser-algorithms",
"description": "Algorithms to help you parse CSS from an array of tokens.",
"version": "4.0.0",
"contributors": [
{
"name": "Antonio Laguna",
"email": "antonio@laguna.es",
"url": "https://antonio.laguna.es"
},
{
"name": "Romain Menke",
"email": "romainmenke@gmail.com"
}
],
"license": "MIT",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"engines": {
"node": ">=20.19.0"
},
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.mjs"
}
},
"files": [
"CHANGELOG.md",
"LICENSE.md",
"README.md",
"dist"
],
"peerDependencies": {
"@csstools/css-tokenizer": "^4.0.0"
},
"scripts": {},
"homepage": "https://github.com/csstools/postcss-plugins/tree/main/packages/css-parser-algorithms#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/csstools/postcss-plugins.git",
"directory": "packages/css-parser-algorithms"
},
"bugs": "https://github.com/csstools/postcss-plugins/issues",
"keywords": [
"css",
"parser"
]
}

View file

@ -0,0 +1,10 @@
# Changes to CSS Syntax Patches For CSSTree
### 1.1.3
_April 12, 2026_
- Update `@webref/css` to [`v8.5.3`](https://github.com/w3c/webref/releases/tag/%40webref%2Fcss%408.5.3)
[Full CHANGELOG](https://github.com/csstools/postcss-plugins/tree/main/packages/css-syntax-patches-for-csstree/CHANGELOG.md)

View file

@ -0,0 +1,18 @@
MIT No Attribution (MIT-0)
Copyright © CSSTools Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the “Software”), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,43 @@
# CSS Syntax Patches For CSSTree <img src="https://cssdb.org/images/css.svg" alt="for CSS" width="90" height="90" align="right">
[<img alt="npm version" src="https://img.shields.io/npm/v/@csstools/css-syntax-patches-for-csstree.svg" height="20">][npm-url]
[<img alt="Build Status" src="https://github.com/csstools/postcss-plugins/actions/workflows/test.yml/badge.svg?branch=main" height="20">][cli-url]
Patch [csstree](https://github.com/csstree/csstree) syntax definitions with the latest data from CSS specifications.
## Usage
```bash
npm install @csstools/css-syntax-patches-for-csstree
```
```js
import { fork } from 'css-tree';
import syntax_patches from '@csstools/css-syntax-patches-for-csstree' with { type: 'json' };
const forkedLexer = fork({
atrules: syntax_patches.next.atrules,
properties: syntax_patches.next.properties,
types: syntax_patches.next.types,
}).lexer;
```
## `next`
```js
import syntax_patches from '@csstools/css-syntax-patches-for-csstree' with { type: 'json' };
console.log(syntax_patches.next);
// ^^^^
```
CSS specifications are often still in flux and various parts might change or disappear altogether.
Specifications also contains parts that haven't been implemented yet in a browser.
Only CSS that is widely adopted can be expected to be stable.
The `next` grouping contains a combination of what is currently valid in browsers and the progress in various specifications.
_In the future more groupings might be added._
[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test
[npm-url]: https://www.npmjs.com/package/@csstools/css-syntax-patches-for-csstree

View file

@ -0,0 +1,5 @@
export const next: {
atrules: Record<string, { descriptors: Record<string, string> }>,
properties: Record<string, string>,
types: Record<string, string>,
}

Some files were not shown because too many files have changed in this diff Show more