diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3813617..675a549 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -13,5 +13,15 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
+ - run: npm ci --ignore-scripts
- run: npm test
- run: npm pack
+
+ runtime-floor:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 6
+ - run: node test/runtime-smoke.js
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 7505d4c..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-language: node_js
-
-node_js:
- - "0.12"
- - "0.10"
- - "0.8"
- - "0.6"
- - "iojs"
-
-before_install:
- - '[ "${TRAVIS_NODE_VERSION}" != "0.8" ] || npm install -g npm@1.4.28'
- - '[ "${TRAVIS_NODE_VERSION}" != "0.6" ] || npm install -g npm@1.3.26'
- - '[ "${TRAVIS_NODE_VERSION}" != "0.1*" ] || npm install -g npm@latest'
-
-notifications:
- email:
- - "jhurliman@jhurliman.org"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 14f98b7..ab9a508 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,6 @@
# Changelog
-## Unreleased
+## 2.0.0 (release candidate)
- Count keys such as `__proto__`, `constructor` and `toString` without interacting with Object.prototype.
- Return independent top-k tuples so callers cannot modify internal sketch state.
@@ -11,7 +11,7 @@
### Release compatibility
-Runtime minimum becomes Node 6 because Buffer.alloc is now used; test development requires modern Node (CI: 22/24/26). Dropping previously advertised Node 0.6 support requires a major release. No version has been bumped yet. Top-k tie ordering is unspecified and may change. The follow-up below adds versioned serialization and validates malformed inputs. See SERIALIZATION.md for legacy import/export and the major-release migration.
+Runtime minimum becomes Node 6 because Buffer.alloc is now used; test development requires modern Node (CI: 22/24/26). Dropping previously advertised Node 0.6 support requires a major release. The package version is prepared as 2.0.0; publication is pending. Top-k tie ordering is unspecified and may change. The follow-up below adds versioned serialization and validates malformed inputs. See SERIALIZATION.md for legacy import/export and the major-release migration.
## Release validation follow-up
@@ -20,3 +20,5 @@ Runtime minimum becomes Node 6 because Buffer.alloc is now used; test developmen
- Reject overflowing counters atomically and validate HLL merges before mutation.
- Fix the signed-minimum hash bucket edge case and retain the existing mapping for all other hashes.
- Add legacy golden fixtures and tests for truncation, malformed metadata, invalid Unicode, capacity preservation and overflow.
+
+- Add complete root/deep-import TypeScript declarations, packed runtime/type consumer tests, a Node 6 runtime-floor check, and remove the obsolete Travis matrix identified in review.
diff --git a/README.md b/README.md
index 3fdf309..e74c1ac 100644
--- a/README.md
+++ b/README.md
@@ -112,7 +112,7 @@ __Arguments__
Returns the serialized size of a views counter (CountMinSketch) object in
bytes given an errFactor and failRate. __NOTE:__ This does not include the size
of the serialized MinHeap which includes the size of each unique ID (up to a
-max of topEntryCount) plus 5 bytes overhead per entry. __NOTE2:__ The memory
+max of topEntryCount) plus 8 bytes overhead per entry. __NOTE2:__ The memory
usage will be higher than this number since we serialize 32-bit integers but
JavaScript uses 64-bit numbers.
@@ -235,3 +235,11 @@ __Example__
```js
var pageCounts = CountMinSketch.deserialize(bufferData);
```
+
+## Version 2 migration and release checks
+
+Version 2 writes capacity-preserving CMS2 sketches by default. Old files remain readable; old readers need `serialize({ legacy: true })`. See [SERIALIZATION.md](SERIALIZATION.md) for layouts, allocation/input bounds and recovering the capacity of partially filled legacy sketches. Explicit zero/null/NaN options no longer silently select defaults.
+
+TypeScript declarations cover the package root and existing class/helper deep imports. Node TypeScript projects need `@types/node`. Runtime compatibility starts at Node 6; development and the complete test suite use Node 22 or newer. CI runs the full suite on 22/24/26 and a separate Node 6 runtime smoke test.
+
+Before releasing: `npm ci`, `npm test`, then review the archive produced by `npm pack`. The test suite itself installs the archive into an independent consumer, checks CommonJS/ESM and compiles NodeNext/Node16 TypeScript fixtures. `npm publish` runs the tests through `prepublishOnly`; publishing still requires the maintainer's authenticated release action. The version bump in this branch is preparation, not evidence of a published release.
diff --git a/index.d.ts b/index.d.ts
new file mode 100644
index 0000000..2a13e8d
--- /dev/null
+++ b/index.d.ts
@@ -0,0 +1,50 @@
+///
+export interface SerializeOptions { legacy?: boolean; }
+export interface DeserializeOptions { maxEntries?: number; }
+/** Constructors return structural counter objects, not instanceof-compatible instances. */
+export class CountMinSketch {
+ constructor(maxEntries: number, epsilon: number, delta: number);
+ increment(key: string): void;
+ getTopK(): Array<[count: number, key: string]>;
+ serialize(options?: SerializeOptions): Buffer;
+ static deserialize(buffer: Buffer, start?: number, length?: number, options?: DeserializeOptions): CountMinSketch;
+}
+export class HyperLogLog {
+ constructor(stdError: number);
+ M: number[];
+ add(key: string): void;
+ count(): number;
+ serialize(): Buffer;
+ merge(other: HyperLogLog): void;
+ static deserialize(buffer: Buffer, start?: number, length?: number): HyperLogLog;
+}
+export function createUniquesCounter(stdError?: number): HyperLogLog;
+export function createViewsCounter(topEntryCount: number, errFactor?: number, failRate?: number): CountMinSketch;
+export function getUniquesObjSize(stdError?: number): number;
+/** Excludes variable-sized heap entries; each adds 8 bytes plus its UTF-8 key. */
+export function getViewsObjSize(errFactor?: number, failRate?: number): number;
+export class MinHeap {
+ constructor(array?: T[], comparator?: (a: T, b: T) => number);
+ heap: T[];
+ compare: (a: T, b: T) => number;
+ heapify(index: number): void;
+ siftUp(index: number): void;
+ heapifyArray(): void;
+ push(item: T): void;
+ pop(): T | undefined;
+ getMin(): T | undefined;
+ size(): number;
+}
+export interface RandomGenerator {
+ (): number;
+ random(): number;
+ uint32(): number;
+ fract53(): number;
+ version: string;
+ args: Array;
+}
+export interface RandomConstructor {
+ (...seeds: Array): RandomGenerator;
+ new (...seeds: Array): RandomGenerator;
+}
+export const PRNG: RandomConstructor;
diff --git a/lib/countMinSketch.d.ts b/lib/countMinSketch.d.ts
new file mode 100644
index 0000000..19d36f1
--- /dev/null
+++ b/lib/countMinSketch.d.ts
@@ -0,0 +1,2 @@
+import { CountMinSketch } from "../index";
+export = CountMinSketch;
diff --git a/lib/hyperLogLog.d.ts b/lib/hyperLogLog.d.ts
new file mode 100644
index 0000000..832b2df
--- /dev/null
+++ b/lib/hyperLogLog.d.ts
@@ -0,0 +1,2 @@
+import { HyperLogLog } from "../index";
+export = HyperLogLog;
diff --git a/lib/minHeap.d.ts b/lib/minHeap.d.ts
new file mode 100644
index 0000000..0605852
--- /dev/null
+++ b/lib/minHeap.d.ts
@@ -0,0 +1,2 @@
+import { MinHeap } from "../index";
+export = MinHeap;
diff --git a/lib/prng.d.ts b/lib/prng.d.ts
new file mode 100644
index 0000000..194c5a9
--- /dev/null
+++ b/lib/prng.d.ts
@@ -0,0 +1,2 @@
+import { PRNG } from "../index";
+export = PRNG;
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..2329435
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,412 @@
+{
+ "name": "streamcount",
+ "version": "2.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "streamcount",
+ "version": "2.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "^24.13.4"
+ },
+ "devDependencies": {
+ "typescript": "^7.0.2"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "24.13.4",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.4.tgz",
+ "integrity": "sha512-YJ7EqCstVTzIr0fMr7qul/977en+pQHrfmuKIo6Zr9i75Be21dr3MovcfvGtyvi2HAUrRerWps5sMO9I7WaxDw==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.18.0"
+ }
+ },
+ "node_modules/@typescript/typescript-aix-ppc64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz",
+ "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-darwin-arm64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz",
+ "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-darwin-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz",
+ "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-freebsd-arm64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz",
+ "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-freebsd-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz",
+ "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-arm": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz",
+ "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-arm64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz",
+ "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-loong64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz",
+ "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-mips64el": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz",
+ "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-ppc64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz",
+ "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-riscv64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz",
+ "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-s390x": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz",
+ "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz",
+ "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-netbsd-arm64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz",
+ "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-netbsd-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz",
+ "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-openbsd-arm64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz",
+ "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-openbsd-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz",
+ "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-sunos-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz",
+ "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-win32-arm64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz",
+ "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-win32-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz",
+ "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz",
+ "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc"
+ },
+ "engines": {
+ "node": ">=16.20.0"
+ },
+ "optionalDependencies": {
+ "@typescript/typescript-aix-ppc64": "7.0.2",
+ "@typescript/typescript-darwin-arm64": "7.0.2",
+ "@typescript/typescript-darwin-x64": "7.0.2",
+ "@typescript/typescript-freebsd-arm64": "7.0.2",
+ "@typescript/typescript-freebsd-x64": "7.0.2",
+ "@typescript/typescript-linux-arm": "7.0.2",
+ "@typescript/typescript-linux-arm64": "7.0.2",
+ "@typescript/typescript-linux-loong64": "7.0.2",
+ "@typescript/typescript-linux-mips64el": "7.0.2",
+ "@typescript/typescript-linux-ppc64": "7.0.2",
+ "@typescript/typescript-linux-riscv64": "7.0.2",
+ "@typescript/typescript-linux-s390x": "7.0.2",
+ "@typescript/typescript-linux-x64": "7.0.2",
+ "@typescript/typescript-netbsd-arm64": "7.0.2",
+ "@typescript/typescript-netbsd-x64": "7.0.2",
+ "@typescript/typescript-openbsd-arm64": "7.0.2",
+ "@typescript/typescript-openbsd-x64": "7.0.2",
+ "@typescript/typescript-sunos-x64": "7.0.2",
+ "@typescript/typescript-win32-arm64": "7.0.2",
+ "@typescript/typescript-win32-x64": "7.0.2"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
+ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
+ "license": "MIT"
+ }
+ }
+}
diff --git a/package.json b/package.json
index 519fea3..81289c9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "streamcount",
- "version": "1.0.1",
+ "version": "2.0.0",
"author": "John Hurliman (http://jhurliman.org/)",
"description": "Provides implementations of sketch algorithms for real-time counting of stream data. Useful for real-time web analytics and other streaming or big data scenarios.",
"keywords": [
@@ -15,9 +15,12 @@
"main": "./index",
"scripts": {
"test": "node --test test/*-test.js",
- "bench": "node bench/repeated-updates.js"
+ "bench": "node bench/repeated-updates.js",
+ "prepublishOnly": "npm test"
+ },
+ "devDependencies": {
+ "typescript": "^7.0.2"
},
- "devDependencies": {},
"engines": {
"node": ">=6"
},
@@ -29,6 +32,12 @@
"files": [
"index.js",
"lib/",
- "CHANGELOG.md"
- ]
+ "CHANGELOG.md",
+ "index.d.ts",
+ "SERIALIZATION.md"
+ ],
+ "types": "./index.d.ts",
+ "dependencies": {
+ "@types/node": "^24.13.4"
+ }
}
diff --git a/test/package-test.js b/test/package-test.js
new file mode 100644
index 0000000..ddd6a3e
--- /dev/null
+++ b/test/package-test.js
@@ -0,0 +1,51 @@
+const { test } = require('node:test');
+const assert = require('node:assert/strict');
+const fs = require('node:fs');
+const os = require('node:os');
+const path = require('node:path');
+const { execFileSync } = require('node:child_process');
+
+test('distributed package resolves CommonJS, ESM, public and deep-import types', () => {
+ const root = path.resolve(__dirname, '..');
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'streamcount-package-'));
+ function run(command, args) { return execFileSync(command, args, {cwd: dir, encoding:'utf8', stdio:'pipe'}); }
+ try {
+ const pack = JSON.parse(execFileSync('npm', ['pack','--json','--pack-destination',dir],{cwd:root,encoding:'utf8'}))[0];
+ assert.ok(pack.files.some(f => f.path === 'SERIALIZATION.md'));
+ assert.ok(pack.files.every(f => !f.path.startsWith('test/') && !f.path.startsWith('node_modules/')));
+ run('npm',['install','--ignore-scripts','--no-audit','--no-fund',path.join(dir,pack.filename)]);
+ const smoke = "const c=api.createViewsCounter(3);c.increment('a');const d=api.CountMinSketch.deserialize(c.serialize());d.increment('b');if(d.getTopK().length!==2)throw Error('capacity');";
+ run(process.execPath,['-e',"const api=require('streamcount');"+smoke]);
+ run(process.execPath,['--input-type=module','-e',"import api from 'streamcount';"+smoke]);
+ const types = `import * as api from 'streamcount';
+import CMS = require('streamcount/lib/countMinSketch');
+import HLL = require('streamcount/lib/hyperLogLog');
+import Heap = require('streamcount/lib/minHeap');
+import Random = require('streamcount/lib/prng');
+const views: api.CountMinSketch = new CMS(10, .1, .1);
+views.increment('key');
+const rows: Array<[number,string]> = views.getTopK();
+const restored = CMS.deserialize(views.serialize({legacy:true}), undefined, undefined, {maxEntries:10});
+const uniques: api.HyperLogLog = new HLL(.1); uniques.merge(api.createUniquesCounter());
+const count: number = uniques.count();
+const heap = new Heap([],(a,b)=>a.localeCompare(b)); heap.push('a');
+const item: string | undefined = heap.pop();
+const rng = new Random(42); const number: number = rng.random() + Random('seed')();
+api.getViewsObjSize(); api.getUniquesObjSize(); api.createViewsCounter(5);
+// @ts-expect-error keys must be strings
+views.increment(123);
+// @ts-expect-error heap types propagate
+heap.push(42);
+// @ts-expect-error serialization requires a Buffer
+CMS.deserialize('bytes');
+`;
+ fs.writeFileSync(path.join(dir,'consumer.cts'),types);
+ fs.writeFileSync(path.join(dir,'consumer.mts'),types);
+ const tsc = path.join(root,'node_modules/typescript/bin/tsc');
+ const common = [tsc,'--strict','--noEmit','--target','es2022'];
+ run(process.execPath,common.concat(['--module','nodenext','--moduleResolution','nodenext','consumer.cts','consumer.mts']));
+ // Also exercise Node16 module resolution.
+ fs.writeFileSync(path.join(dir,'consumer.ts'),types);
+ run(process.execPath,common.concat(['--module','node16','--moduleResolution','node16','consumer.ts']));
+ } finally { fs.rmSync(dir,{recursive:true,force:true}); }
+});
diff --git a/test/runtime-smoke.js b/test/runtime-smoke.js
new file mode 100644
index 0000000..624204b
--- /dev/null
+++ b/test/runtime-smoke.js
@@ -0,0 +1,10 @@
+var assert = require('assert');
+var api = require('..');
+var sketch = api.createViewsCounter(10);
+sketch.increment('__proto__');
+var restored = api.CountMinSketch.deserialize(sketch.serialize());
+restored.increment('another');
+assert.strictEqual(restored.getTopK().length, 2);
+var hll = api.createUniquesCounter(); hll.add('key');
+assert.ok(api.HyperLogLog.deserialize(hll.serialize()).count() > 0);
+assert.throws(function() { api.createViewsCounter(0); });
diff --git a/test/validation-test.js b/test/validation-test.js
index 50911d4..94a29af 100644
--- a/test/validation-test.js
+++ b/test/validation-test.js
@@ -121,3 +121,15 @@ test('invalid merge sources reject before modifying the destination', () => {
const source=new HLL(.2);source.M[0]=2;source.M[source.M.length-1]=100;
assert.throws(()=>target.merge(source));assert.deepEqual(target.serialize(),before);
});
+
+test('signed-minimum hashes address valid buckets', () => {
+ const hashing = require('../lib/hashing');
+ const original = hashing.fnv1a;
+ try {
+ hashing.fnv1a = () => -2147483648;
+ const sketch = new CMS(2,.2,.1);
+ sketch.increment('edge'); sketch.increment('edge');
+ assert.deepEqual(sketch.getTopK(), [[2,'edge']]);
+ assert.deepEqual(CMS.deserialize(sketch.serialize()).getTopK(), [[2,'edge']]);
+ } finally { hashing.fnv1a = original; }
+});