agent-avatars 1.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/LICENSE +21 -0
- package/README.md +220 -0
- package/SECURITY.md +21 -0
- package/dist/catalog-cache.cjs +31 -0
- package/dist/catalog-cache.mjs +29 -0
- package/dist/index.cjs +1399 -0
- package/dist/index.d.cts +197 -0
- package/dist/index.d.mts +197 -0
- package/dist/index.mjs +1402 -0
- package/dist/png-options.cjs +38 -0
- package/dist/png-options.mjs +38 -0
- package/dist/png.cjs +340 -0
- package/dist/png.d.cts +41 -0
- package/dist/png.d.mts +41 -0
- package/dist/png.mjs +340 -0
- package/dist/private.cjs +77 -0
- package/dist/private.d.cts +9 -0
- package/dist/private.d.mts +9 -0
- package/dist/private.mjs +82 -0
- package/dist/react.cjs +32 -0
- package/dist/react.d.cts +13 -0
- package/dist/react.d.mts +13 -0
- package/dist/react.mjs +29 -0
- package/dist/render-descriptor.cjs +43 -0
- package/dist/render-descriptor.mjs +40 -0
- package/dist/visual-distance.cjs +168 -0
- package/dist/visual-distance.mjs +168 -0
- package/examples/preview.png +0 -0
- package/package.json +148 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [1.0.0-rc.2] - 2026-07-12
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Resource limits for custom palettes, identity sets, manifests, and PNG sets.
|
|
12
|
+
- Runtime support verification for Node.js 18 and React 19.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Manifest compatibility now includes `seedMode`; manifests created by `1.0.0-rc.1` must be regenerated.
|
|
17
|
+
- `IdentitySetOptions` no longer exposes `collisionNonce`.
|
|
18
|
+
- PNG descriptor and React option types now include only effective inputs.
|
|
19
|
+
- Private HMAC helpers require at least 32 encoded secret bytes.
|
|
20
|
+
- Shape catalogs are initialized lazily, and visual-distance structures are reused through bounded caches.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- `allowLowContrast`, `includeSvg`, and `ensureUnique` now reject non-boolean values.
|
|
25
|
+
- Aggregate PNG rendering and large identity collections now fail before expensive processing.
|
|
26
|
+
|
|
27
|
+
## [1.0.0-rc.1] - 2026-07-12
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- Deterministic SVG and PNG avatar generation.
|
|
32
|
+
- Browser-safe root and React entry points.
|
|
33
|
+
- Node.js-only PNG and private HMAC entry points.
|
|
34
|
+
- Batch identity allocation with reusable manifests and optional visual-distance constraints.
|
|
35
|
+
- ESM, CommonJS, and format-specific TypeScript declarations.
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
- Public style-version export is named `STYLE_VERSION`.
|
|
40
|
+
- Catalog capacity is exposed as `signatureStates`.
|
|
41
|
+
- PNG byte-returning APIs are typed as `Uint8Array`.
|
|
42
|
+
|
|
43
|
+
### Removed
|
|
44
|
+
|
|
45
|
+
- Removed the ambiguous `createAvatarBitmap` alias before the initial public release.
|
|
46
|
+
- Removed private/HMAC helpers from the browser-visible root entry.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Felix Koba
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Deterministic Agent Avatars
|
|
2
|
+
|
|
3
|
+
Zero-dependency deterministic SVG and PNG avatars for AI agents, bots, services, and users.
|
|
4
|
+
|
|
5
|
+
## Preview
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
The GitHub Pages-ready demo is `index.html`. Run it locally through a static server:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm run demo
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Then open `http://localhost:4173/`. ES modules are not supported reliably when the file is opened directly with `file://`.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install agent-avatars
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Install from the npm registry. Direct Git URL installs are not supported because generated `dist` files are intentionally not tracked.
|
|
24
|
+
|
|
25
|
+
## JavaScript / TypeScript
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { createHashAvatar } from "agent-avatars";
|
|
29
|
+
|
|
30
|
+
const svg = createHashAvatar("research-assistant", {
|
|
31
|
+
namespace: "my-project",
|
|
32
|
+
theme: "dark",
|
|
33
|
+
size: 96,
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The same seed, namespace, and options always produce the same result.
|
|
38
|
+
|
|
39
|
+
## React
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { AgentAvatar } from "agent-avatars/react";
|
|
43
|
+
|
|
44
|
+
export function ResearchAssistant() {
|
|
45
|
+
return (
|
|
46
|
+
<AgentAvatar
|
|
47
|
+
seed="research-assistant"
|
|
48
|
+
size={64}
|
|
49
|
+
options={{ namespace: "my-project", theme: "dark" }}
|
|
50
|
+
alt="Research assistant"
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
React 18 or 19 is an optional peer dependency.
|
|
57
|
+
|
|
58
|
+
TypeScript consumers must use TypeScript 4.7 or newer with `Node16`, `NodeNext`, or `Bundler` module resolution. Legacy `node10` subpath resolution is not supported.
|
|
59
|
+
|
|
60
|
+
## Package entry points
|
|
61
|
+
|
|
62
|
+
| Entry | Environment | Purpose |
|
|
63
|
+
| --- | --- | --- |
|
|
64
|
+
| `agent-avatars` | Browser and Node.js | Descriptors, SVG/data URI rendering, catalog inspection, and identity sets. |
|
|
65
|
+
| `agent-avatars/react` | Browser and Node.js SSR | React 18/19 `<AgentAvatar>` component. |
|
|
66
|
+
| `agent-avatars/png` | Node.js | Synchronous PNG encoding and filesystem export. |
|
|
67
|
+
| `agent-avatars/private` | Node.js | HMAC-derived private seeds and avatars. |
|
|
68
|
+
|
|
69
|
+
## SVG and PNG export
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
import { writeFileSync } from "node:fs";
|
|
73
|
+
import { createHashAvatar } from "agent-avatars";
|
|
74
|
+
import { createAvatarPng, writeAvatarPngSet } from "agent-avatars/png";
|
|
75
|
+
|
|
76
|
+
const seed = "research-assistant";
|
|
77
|
+
const options = { namespace: "my-project", theme: "light" };
|
|
78
|
+
|
|
79
|
+
writeFileSync("avatar.svg", createHashAvatar(seed, { ...options, size: 200 }));
|
|
80
|
+
writeFileSync("avatar-64.png", createAvatarPng(seed, { ...options, size: 64 }));
|
|
81
|
+
|
|
82
|
+
writeAvatarPngSet(seed, "./avatars", {
|
|
83
|
+
...options,
|
|
84
|
+
sizes: [32, 64, 192, 200],
|
|
85
|
+
baseName: "research-assistant",
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
SVG generation works in browsers and Node.js. PNG generation and filesystem export use Node.js built-ins.
|
|
90
|
+
|
|
91
|
+
PNG rendering caps its high-resolution RGBA buffer at 64 MiB. When `supersample` is omitted, large outputs automatically use the highest safe value up to the default of 4. If an explicit `supersample` value exceeds that budget, the API throws a `RangeError`; lower the output size or the supersampling value.
|
|
92
|
+
|
|
93
|
+
PNG generation is synchronous. Do not forward an untrusted request size directly to the PNG API: apply an application-level allowlist (typically no more than 512 or 1024 pixels) and run unusually large exports in a worker or background job.
|
|
94
|
+
|
|
95
|
+
PNG-only options are `supersample` (integer `1..8`), `sizes` (unique integer output sizes), and `baseName` for filesystem export. `createAvatarPngFromDescriptor` accepts only `supersample`, because palette, namespace, and identity selection are already fixed by the descriptor.
|
|
96
|
+
|
|
97
|
+
## Options
|
|
98
|
+
|
|
99
|
+
| Option | Purpose |
|
|
100
|
+
| --- | --- |
|
|
101
|
+
| `namespace` | Separates the same seed across products, tenants, or environments. |
|
|
102
|
+
| `namespaceMode` | Uses normalized `"human"` or exact `"raw"` namespace input. |
|
|
103
|
+
| `theme` | Selects `"light"` or `"dark"`. |
|
|
104
|
+
| `size` | Sets the rendered SVG or PNG size. |
|
|
105
|
+
| `seedMode` | Uses normalized `"human"` input or exact `"raw"` input. |
|
|
106
|
+
| `palette` | Selects a built-in palette or supplies a custom light/dark palette. |
|
|
107
|
+
| `palettes` | Supplies a deterministic custom palette collection. |
|
|
108
|
+
| `collisionNonce` | Selects an alternate candidate for a single avatar; `createIdentitySet` does not accept it. |
|
|
109
|
+
| `minimumContrast` | Sets custom-palette contrast validation in the range `1..21`. |
|
|
110
|
+
| `allowLowContrast` | Explicitly permits lower contrast; only a boolean is accepted. |
|
|
111
|
+
| `minPixels`, `maxPixels` | Restrict the number of active cells in catalog shapes. |
|
|
112
|
+
| `minDensity`, `maxDensity` | Restrict active-cell density within shape bounds. |
|
|
113
|
+
| `maxDiagonalConnections`, `connectivity`, `maxHoles` | Configure structural shape constraints. |
|
|
114
|
+
|
|
115
|
+
Avatar generation uses the Standard shape catalog. The demo does not expose alternate shape presets.
|
|
116
|
+
|
|
117
|
+
Custom colors must be six-digit hexadecimal values. Contrast is validated by default.
|
|
118
|
+
|
|
119
|
+
Malformed types and unsupported enum values throw `TypeError`. Values outside documented capacity or resource bounds throw `RangeError`. Deterministic allocation exhaustion and incompatible persisted assignments throw `Error`; callers should treat manifests as versioned data and keep the prior valid snapshot when growth fails.
|
|
120
|
+
|
|
121
|
+
## Batch uniqueness
|
|
122
|
+
|
|
123
|
+
Batch allocation normally enforces exact signature uniqueness: two different identities cannot receive the same shape-and-palette signature. You can also opt in to **visual distinguishability**, which keeps assignments a requested shape or palette distance apart. This policy is opt-in only; omitting the distance options leaves the existing defaults and exact-uniqueness behavior unchanged.
|
|
124
|
+
|
|
125
|
+
```js
|
|
126
|
+
import { createIdentitySet } from "agent-avatars";
|
|
127
|
+
|
|
128
|
+
const team = createIdentitySet(["research", "support", "billing"], {
|
|
129
|
+
ensureUnique: true,
|
|
130
|
+
minimumShapeDistance: 4,
|
|
131
|
+
minimumPaletteDistance: 20,
|
|
132
|
+
distanceMode: "either",
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`minimumShapeDistance` is Hamming distance across the full rendered 5 × 4 bitmap: it counts cells that differ, so its range is `0..20`. `minimumPaletteDistance` uses the versioned `visual-distance/v1` metric. Colors are converted from sRGB to D65 Lab and compared with CIEDE2000. Within each theme, background and foreground differences are combined as a weighted RMS using 85% background and 15% foreground; the palette distance is `min(light, dark)`. Taking the minimum guarantees the threshold after switching to either theme. For example, the built-in rose and coral palettes have distance 0 because one theme is visually identical under this metric.
|
|
137
|
+
|
|
138
|
+
Each channel is disabled by a threshold of `0`. `minimumShapeDistance` must be an integer in `0..20`; `minimumPaletteDistance` must be a finite number in `0..100`; and `distanceMode` must be `"either"` or `"both"`. If both thresholds are zero, no visual-distance policy is stored or applied.
|
|
139
|
+
|
|
140
|
+
`ensureUnique` and `includeSvg` accept booleans only. `manifest` supplies a prior allocation snapshot and `maxAttempts` caps deterministic candidate attempts for each new identity.
|
|
141
|
+
|
|
142
|
+
| Enabled channels | `distanceMode` | A pair is accepted when |
|
|
143
|
+
| --- | --- | --- |
|
|
144
|
+
| Shape only | `either` or `both` | Shape distance meets its threshold |
|
|
145
|
+
| Palette only | `either` or `both` | Palette distance meets its threshold |
|
|
146
|
+
| Shape and palette | `either` | Shape **OR** palette meets its threshold |
|
|
147
|
+
| Shape and palette | `both` | Shape **AND** palette meets its threshold |
|
|
148
|
+
|
|
149
|
+
`either` is the default: one strong visual cue is sufficient, so two avatars may use the same palette when their shapes are distant enough. Use `both` when every pair must differ sufficiently in both channels; it is stricter and best suited to smaller sets. Exact signature uniqueness is still enforced with either policy. Repeating the same identity in the input reuses its one assignment rather than consuming another signature.
|
|
150
|
+
|
|
151
|
+
Without a visual-distance policy, `ensureUnique: false` permits repeated signatures and sets larger than the configured signature state space. The returned manifest remains reusable when subsequent calls also set `ensureUnique: false`. Switching such a manifest back to unique allocation is rejected if its historical entries contain duplicate signatures or exceed the state space. `getCatalogStats().signatureStates` reports this exact signature capacity; it is not a promise that every palette looks different in every theme.
|
|
152
|
+
|
|
153
|
+
### Growing a set with a manifest
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
import { createIdentitySet } from "agent-avatars";
|
|
157
|
+
|
|
158
|
+
const policy = {
|
|
159
|
+
namespace: "my-project",
|
|
160
|
+
ensureUnique: true,
|
|
161
|
+
minimumShapeDistance: 4,
|
|
162
|
+
minimumPaletteDistance: 20,
|
|
163
|
+
distanceMode: "either",
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const first = createIdentitySet(["research", "support", "billing"], policy);
|
|
167
|
+
|
|
168
|
+
const expanded = createIdentitySet(["research", "support", "billing", "release"], {
|
|
169
|
+
...policy,
|
|
170
|
+
manifest: first.manifest,
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The manifest stores the complete distinguishability policy, including its `visual-distance/v1` schema. Reuse the manifest when a set grows: historical assignments stay unchanged and constrain every new assignment. The requested policy must match the stored policy exactly, or hydration is rejected. A manifest is also checked pairwise, so a tampered set that no longer satisfies its stored policy is rejected.
|
|
175
|
+
|
|
176
|
+
Manifest compatibility also includes `seedMode`. A manifest created with `seedMode: "human"` is rejected under `"raw"`, and vice versa. Because `1.0.0-rc.2` adds this binding, manifests produced by `1.0.0-rc.1` are intentionally incompatible and must be regenerated.
|
|
177
|
+
|
|
178
|
+
### Capacity and allocation limits
|
|
179
|
+
|
|
180
|
+
Allocation uses a greedy deterministic allocator. It considers identities in a stable order and tries at most `maxAttempts` deterministic candidates for each new identity. It does not guarantee a maximum packing and failure does not prove that no valid assignment exists elsewhere in the state space. Capacity can fall sharply with higher thresholds, especially in `both` mode.
|
|
181
|
+
|
|
182
|
+
If deterministic allocation attempts are exhausted, the error reports the accepted count, thresholds, mode, and attempt limit. Try lowering one or both thresholds, adding palettes, or increasing `maxAttempts`. Allocation also fails early if the requested exact-unique set is larger than the configured signature state space.
|
|
183
|
+
|
|
184
|
+
To bound synchronous aggregate work, one call accepts at most 256 custom palettes, 10,000 seeds, and 10,000 manifest entries. A PNG set accepts at most 64 sizes and at most 16,777,216 high-resolution render pixels in total after supersampling. Inputs over these limits are rejected before entries are normalized or images are rendered.
|
|
185
|
+
|
|
186
|
+
Custom palettes participate in the same rules. Exact signatures use a 32-bit key derived from the palette colors; selected custom palettes with different colors but a colliding palette signature key are rejected rather than treated as interchangeable.
|
|
187
|
+
|
|
188
|
+
## Security limitations
|
|
189
|
+
|
|
190
|
+
- Deterministic output is not anonymization; a known seed and options can be reproduced.
|
|
191
|
+
- A namespace separates collections but is not a secret.
|
|
192
|
+
- For sensitive identifiers, import `derivePrivateSeed` or the private avatar helpers from the Node-only `agent-avatars/private` subpath.
|
|
193
|
+
- Never embed a private secret in browser code or commit it to a repository.
|
|
194
|
+
- Generate at least 32 random secret bytes, store them in a secret manager, attach a key identifier to persisted data, and plan key rotation. The private API rejects secrets shorter than 32 encoded bytes; length validation does not replace entropy. A long human-readable password is not a substitute for random entropy.
|
|
195
|
+
- Validate untrusted manifests before storing or reusing them; the library rejects incompatible manifest schemas and options.
|
|
196
|
+
|
|
197
|
+
```js
|
|
198
|
+
import { derivePrivateSeed } from "agent-avatars/private";
|
|
199
|
+
|
|
200
|
+
const privateSeed = await derivePrivateSeed("person@example.com", {
|
|
201
|
+
namespace: "my-project",
|
|
202
|
+
secret: process.env.AVATAR_HMAC_SECRET,
|
|
203
|
+
});
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Support
|
|
207
|
+
|
|
208
|
+
If this project is useful to you, you can support its development on Ko-fi:
|
|
209
|
+
|
|
210
|
+
[Support on Ko-fi](https://ko-fi.com/felixkoba)
|
|
211
|
+
|
|
212
|
+
## Development checks
|
|
213
|
+
|
|
214
|
+
Runtime consumers support Node.js 18 and newer. Repository development and releases require Node.js 24.8 or newer and npm 11.11.0; `.nvmrc` selects the tested Node 24 line.
|
|
215
|
+
|
|
216
|
+
`npm test` builds clean artifacts, runs runtime and strict declaration tests, validates the demo HTML, installs a real tarball into isolated ESM/CJS/TypeScript/browser-bundler consumers, renders the React component, and audits the packed package with publint and Are The Types Wrong. The default suite avoids maximum-size PNG allocation. Run `npm run test:stress` separately when the environment can accommodate the 4096 × 4096 PNG boundary case and its greater-than-128-MiB peak memory usage. `npm run release:dry-run` exercises the real publish lifecycle with the required prerelease dist-tag. After installing Chromium with `npx playwright install chromium`, `npm run test:release` runs the browser, stress, publication, and dependency-audit gates without publishing.
|
|
217
|
+
|
|
218
|
+
## MIT License
|
|
219
|
+
|
|
220
|
+
Copyright (c) 2026 Felix Koba. See [LICENSE](LICENSE).
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported versions
|
|
4
|
+
|
|
5
|
+
Security fixes are provided for the latest `1.x` release. Prerelease builds are supported only until the next prerelease or stable release is available.
|
|
6
|
+
|
|
7
|
+
## Security boundaries
|
|
8
|
+
|
|
9
|
+
- The root and `react` entries are browser-safe and must not import Node.js built-ins.
|
|
10
|
+
- The `private` entry is Node.js-only. Its secret is trusted server configuration, must contain at least 32 encoded bytes, and must not be supplied by an end user.
|
|
11
|
+
- Public rows, grids, colors, manifests, sizes, and avatar options may originate from untrusted application data and are validated at the library boundary.
|
|
12
|
+
- The PNG API is synchronous and enforces per-image and aggregate set budgets, but applications must still impose request quotas and a smaller allowlisted size for remotely supplied input.
|
|
13
|
+
- The demo must not execute third-party JavaScript automatically.
|
|
14
|
+
|
|
15
|
+
Deterministic avatars are not anonymization. A namespace is public domain separation, not a secret.
|
|
16
|
+
|
|
17
|
+
## Reporting a vulnerability
|
|
18
|
+
|
|
19
|
+
Use the [private security-advisory form](https://github.com/NotXf1le/agent-avatars/security/advisories/new). Do not include secrets, personal data, or exploit details in a public issue. Include affected versions, a minimal reproducer, impact, and any known mitigations.
|
|
20
|
+
|
|
21
|
+
You should receive an acknowledgement within seven days. Valid reports will be coordinated through a private advisory until a fix and disclosure timeline are ready.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
function createBoundedLruCache(maxEntries) {
|
|
2
|
+
if (!Number.isSafeInteger(maxEntries) || maxEntries < 1) {
|
|
3
|
+
throw new TypeError("maxEntries must be a positive safe integer.");
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const entries = new Map();
|
|
7
|
+
return Object.freeze({
|
|
8
|
+
get size() {
|
|
9
|
+
return entries.size;
|
|
10
|
+
},
|
|
11
|
+
get(key) {
|
|
12
|
+
if (!entries.has(key)) return undefined;
|
|
13
|
+
const value = entries.get(key);
|
|
14
|
+
entries.delete(key);
|
|
15
|
+
entries.set(key, value);
|
|
16
|
+
return value;
|
|
17
|
+
},
|
|
18
|
+
set(key, value) {
|
|
19
|
+
entries.delete(key);
|
|
20
|
+
entries.set(key, value);
|
|
21
|
+
while (entries.size > maxEntries) {
|
|
22
|
+
entries.delete(entries.keys().next().value);
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
createBoundedLruCache,
|
|
31
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
function createBoundedLruCache(maxEntries) {
|
|
2
|
+
if (!Number.isSafeInteger(maxEntries) || maxEntries < 1) {
|
|
3
|
+
throw new TypeError("maxEntries must be a positive safe integer.");
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const entries = new Map();
|
|
7
|
+
return Object.freeze({
|
|
8
|
+
get size() {
|
|
9
|
+
return entries.size;
|
|
10
|
+
},
|
|
11
|
+
get(key) {
|
|
12
|
+
if (!entries.has(key)) return undefined;
|
|
13
|
+
const value = entries.get(key);
|
|
14
|
+
entries.delete(key);
|
|
15
|
+
entries.set(key, value);
|
|
16
|
+
return value;
|
|
17
|
+
},
|
|
18
|
+
set(key, value) {
|
|
19
|
+
entries.delete(key);
|
|
20
|
+
entries.set(key, value);
|
|
21
|
+
while (entries.size > maxEntries) {
|
|
22
|
+
entries.delete(entries.keys().next().value);
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { createBoundedLruCache };
|