🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@nxtedition/slice

Package Overview
Dependencies
Maintainers
12
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@nxtedition/slice - npm Package Compare versions

Comparing version
1.1.9
to
1.1.10
+1
-1
lib/index.d.ts

@@ -7,3 +7,3 @@ import util from 'node:util';

};
export declare class Slice {
export declare class Slice implements SliceLike {
buffer: Buffer;

@@ -10,0 +10,0 @@ byteOffset: number;

@@ -11,3 +11,3 @@ import util from 'node:util'

export class Slice {
export class Slice {
buffer = EMPTY_BUF

@@ -78,2 +78,8 @@ byteOffset = 0

} else {
// Start offsets are validated (not clamped) so a negative offset can
// never resolve to a position before the slice and read adjacent
// (pool) memory.
if (sourceStart < 0 || sourceStart > this.byteLength || !Number.isInteger(sourceStart)) {
throw new RangeError(`Invalid sourceStart: ${sourceStart}`)
}
sourceStart += this.byteOffset

@@ -88,7 +94,10 @@ }

// Clamp against the logical slice length so copy() cannot read past
// the slice's own end and leak adjacent (pool) memory.
// Clamp the end against the logical slice length so copy() cannot read
// past the slice's own end and leak adjacent (pool) memory.
if (sourceEnd > sliceEnd) {
sourceEnd = sliceEnd
}
if (sourceEnd < sourceStart) {
sourceEnd = sourceStart
}

@@ -100,2 +109,5 @@ if (target instanceof Slice) {

} else {
if (targetStart < 0 || targetStart > target.byteLength || !Number.isInteger(targetStart)) {
throw new RangeError(`Invalid targetStart: ${targetStart}`)
}
targetStart += target.byteOffset

@@ -145,2 +157,5 @@ }

} else {
if (targetStart < 0 || targetStart > target.byteLength || !Number.isInteger(targetStart)) {
throw new RangeError(`Invalid targetStart: ${targetStart}`)
}
targetStart += target.byteOffset

@@ -158,2 +173,5 @@ }

}
if (targetEnd < targetStart) {
targetEnd = targetStart
}
target = target.buffer

@@ -165,2 +183,5 @@ }

} else {
if (sourceStart < 0 || sourceStart > this.byteLength || !Number.isInteger(sourceStart)) {
throw new RangeError(`Invalid sourceStart: ${sourceStart}`)
}
sourceStart += this.byteOffset

@@ -178,2 +199,5 @@ }

}
if (sourceEnd < sourceStart) {
sourceEnd = sourceStart
}

@@ -189,3 +213,3 @@ return this.buffer.compare(target, targetStart, targetEnd, sourceStart, sourceEnd)

} else {
if (offset < 0 || offset > this.byteLength) {
if (offset < 0 || offset > this.byteLength || !Number.isInteger(offset)) {
throw new RangeError(`Invalid offset: ${offset}`)

@@ -199,4 +223,9 @@ }

length = available
} else if (length > available) {
length = available
} else {
if (length < 0 || !Number.isInteger(length)) {
throw new RangeError(`Invalid length: ${length}`)
}
if (length > available) {
length = available
}
}

@@ -215,2 +244,7 @@

} else {
// Validate (not clamp): a negative offset would resolve to a position
// before the slice and corrupt adjacent (pool) memory.
if (offset < 0 || offset > this.byteLength || !Number.isInteger(offset)) {
throw new RangeError(`Invalid offset: ${offset}`)
}
offset += this.byteOffset

@@ -229,3 +263,3 @@ }

at(index ) {
if (index >= this.byteLength || index < -this.byteLength) {
if (!Number.isInteger(index) || index >= this.byteLength || index < -this.byteLength) {
throw new RangeError(`Index out of range: ${index}`)

@@ -250,2 +284,5 @@ }

} else {
if (start < 0 || start > this.byteLength || !Number.isInteger(start)) {
throw new RangeError(`Invalid start: ${start}`)
}
start += this.byteOffset

@@ -263,2 +300,5 @@ }

}
if (end < start) {
end = start
}

@@ -274,2 +314,5 @@ return this.buffer.toString(encoding, start, end)

} else {
if (start < 0 || start > this.byteLength || !Number.isInteger(start)) {
throw new RangeError(`Invalid start: ${start}`)
}
start += this.byteOffset

@@ -287,2 +330,5 @@ }

}
if (end < start) {
end = start
}

@@ -301,4 +347,10 @@ return start === 0 && end === this.buffer.byteLength

const len = this.byteLength
const shown = len < MAX_BYTES ? len : MAX_BYTES
// Never read past the underlying buffer. A malformed slice (byteOffset /
// byteLength extending beyond the buffer) is exactly the state a developer
// inspects while debugging pool corruption — inspect must not throw on it.
const available = this.buffer.byteLength - this.byteOffset
const safeLen = available > 0 ? (len < available ? len : available) : 0
const shown = safeLen < MAX_BYTES ? safeLen : MAX_BYTES
let hex = ''

@@ -315,4 +367,3 @@ for (let i = 0; i < shown; i++) {

const strEnd = shown < len ? this.byteOffset + shown : this.byteOffset + len
const str = this.buffer.toString('utf8', this.byteOffset, strEnd)
const str = this.buffer.toString('utf8', this.byteOffset, this.byteOffset + shown)
const truncated = shown < len ? '…' : ''

@@ -341,2 +392,5 @@

if (typeof poolTotalOrBuffer === 'number') {
if (!Number.isInteger(poolTotalOrBuffer) || poolTotalOrBuffer < 0) {
throw new RangeError(`Invalid pool size: ${poolTotalOrBuffer}`)
}
this.#poolBuffer = Buffer.allocUnsafeSlow(poolTotalOrBuffer)

@@ -343,0 +397,0 @@ } else if (poolTotalOrBuffer instanceof Buffer) {

{
"name": "@nxtedition/slice",
"version": "1.1.9",
"version": "1.1.10",
"type": "module",

@@ -33,3 +33,3 @@ "main": "lib/index.js",

},
"gitHead": "2c131da7ca8ea328fd681e975dd7caf57f9f4896"
"gitHead": "7c9c7457c885c644c7a1e70ef894d4727ce240d6"
}
+33
-11

@@ -13,3 +13,3 @@ # @nxtedition/slice

`PoolAllocator` takes this further. Like Node's internal pool, it has management overhead — but it rarely (if ever) allocates new backing stores, and because `Slice` is a plain object rather than a typed array, resizing or freeing a slice doesn't produce garbage for V8 to collect. It pre-allocates a large contiguous buffer and hands out regions using power-of-2 bucketing. When a slice is freed, its slot is recycled. When a slice is resized within the same bucket, no data moves at all — just a field update. This gives you `malloc`/`realloc`/`free` semantics with near-zero overhead per operation. The trade-off is upfront memory allocation and internal fragmentation from power-of-2 rounding — a 10-byte allocation uses a 16-byte slot. Buckets are also independent: a freed 16-byte slot cannot satisfy a 32-byte request, so the pool can become fragmented if allocation sizes are uneven. Use `stats` to monitor pool utilization and tune the pool size for your workload.
`PoolAllocator` takes this further. Like Node's internal pool, it has management overhead — but for in-pool sizes it never allocates new backing stores (only allocations larger than the 256 KB top bucket, or made once the contiguous pool is exhausted, fall back to a standalone `Buffer`), and because `Slice` is a plain object rather than a typed array, resizing or freeing a slice doesn't produce garbage for V8 to collect. It pre-allocates a large contiguous buffer and hands out regions using power-of-2 bucketing. When a slice is freed, its slot is recycled. When a slice is resized within the same bucket, no data moves at all — just a field update. This gives you `malloc`/`realloc`/`free` semantics with near-zero overhead per operation. The trade-off is upfront memory allocation and internal fragmentation from power-of-2 rounding — a 10-byte allocation uses a 16-byte slot. Buckets are also independent: a freed 16-byte slot cannot satisfy a 32-byte request, so the pool can become fragmented if allocation sizes are uneven. Use `stats` to monitor pool utilization and tune the pool size for your workload.

@@ -106,7 +106,7 @@ ## Install

- `reset(): void` — Clear the slice back to empty state. **Note:** this does not return the slot to the `PoolAllocator` — you must call `realloc(slice, 0)` to free pool memory.
- `copy(target: Buffer | Slice, targetStart?: number, sourceStart?: number, sourceEnd?: number): number` — Copy data to a `Buffer` or `Slice`. Returns bytes copied.
- `compare(target: Buffer | Slice, targetStart?: number, targetEnd?: number, sourceStart?: number, sourceEnd?: number): -1 | 0 | 1` — Compare with a `Buffer` or `Slice`
- `copy(target: Uint8Array | Slice, targetStart?: number, sourceStart?: number, sourceEnd?: number): number` — Copy data to a `Uint8Array`/`Buffer` or `Slice`. Returns bytes copied.
- `compare(target: Uint8Array | Slice, targetStart?: number, targetEnd?: number, sourceStart?: number, sourceEnd?: number): -1 | 0 | 1` — Compare with a `Uint8Array`/`Buffer` or `Slice`
- `write(string: string, offset?: number, length?: number, encoding?: BufferEncoding): number` — Write a string into the slice. Returns bytes written.
- `set(source: Buffer | Slice | null | undefined, offset?: number): void` — Copy from a `Buffer` or `Slice` into this slice
- `at(index: number): number` — Read byte at index (supports negative indexing)
- `set(source: Buffer | Slice | null | undefined, offset?: number): void` — Copy from a `Buffer` or `Slice` into this slice. (A plain `Uint8Array` source is not accepted — it has no `copy` method.)
- `at(index: number): number` — Read byte at integer index (supports negative indexing)
- `test(expr: { test(buffer: Buffer, byteOffset: number, byteLength: number): boolean }): boolean` — Test the slice against an expression object

@@ -116,2 +116,11 @@ - `toString(encoding?: BufferEncoding, start?: number, end?: number): string` — Convert to string

#### Validation & bounds
All offsets are **relative to the slice** (i.e. `0` is `byteOffset`). For a `Slice` target, target offsets are relative to that slice; for a raw `Buffer`/`Uint8Array` target they are absolute (passed straight through to the underlying `Buffer` method).
The rule is consistent across the API:
- **Start/offset arguments are validated** — `set`'s `offset`, `copy`/`compare`'s `sourceStart`/`targetStart`, `toString`/`toBuffer`'s `start`, `write`'s `offset`/`length`, and `at`'s `index` must be in-range integers. Out-of-range or non-integer values throw `RangeError`. This prevents a negative offset from resolving to a position **before** the slice and reading/writing adjacent (pool) memory.
- **End arguments are clamped** — `sourceEnd`/`targetEnd`/`end` are clamped to the slice's logical length (matching `Buffer`'s lenient end-of-range behavior), so over-long ranges never read past the slice's end.
#### Static

@@ -125,15 +134,28 @@

#### `new PoolAllocator(poolTotal?: number)`
#### `new PoolAllocator(poolTotalOrBuffer?: number | Buffer | ArrayBufferView | ArrayBuffer | SharedArrayBuffer)`
Creates a pool allocator. Default pool size is 128 MB.
Creates a pool allocator. Pass a byte size to allocate a fresh backing buffer (default 128 MB, must be a non-negative integer), or pass an existing `Buffer`/`ArrayBufferView`/`ArrayBuffer`/`SharedArrayBuffer` to back the pool with caller-provided memory.
> **Single-owner.** The allocator's bookkeeping lives in the instance, not in the backing buffer. When you supply your own buffer, that buffer must be owned exclusively by this allocator: do not build your own `Slice` views over it, do not share it with a second `PoolAllocator`, and (for a `SharedArrayBuffer`) do not allocate from more than one thread — the metadata is not shared or atomic, so doing any of these silently produces overlapping allocations.
#### Methods
- `realloc(slice: Slice, byteLength: number): Slice` — Allocate, resize, or free a slice. Pass `0` to free.
- `isFromPool(slice: Slice | null | undefined): boolean` — Check if a slice was allocated from this pool
- `realloc(byteLength: number): Slice` — Allocate a fresh slice.
- `realloc(slice: Slice, byteLength: number): Slice` — Resize a slice, or free it by passing `0`. **Contents are not preserved** — `realloc` has `malloc` semantics, not C `realloc` semantics; after a resize the bytes are undefined (a same-bucket resize happens to keep them in place, but do not rely on it). Only call `realloc` with a slice that belongs to this allocator (or a fresh/empty `Slice`); passing a slice from another pool, or freeing the same slice twice, corrupts the allocator's accounting.
- `isFromPool(slice: Slice | null | undefined): boolean` — Check if a slice's buffer is this pool's backing buffer. Note this is an identity check; it returns `true` for any slice over the same buffer, not only ones this allocator handed out.
Allocations larger than 256 KB (the largest bucket), or made when the contiguous pool is exhausted, fall back to a fresh standalone `Buffer` (`isFromPool` returns `false`) and are excluded from `size`/`stats`.
#### Properties
- `size: number` — Total size of all active allocations
- `stats: { size: number, padding: number, ratio: number, poolTotal: number, poolUsed: number, poolSize: number, poolCount: number }` — Detailed allocation statistics
- `size: number` — Total reserved bytes of all active **pool** allocations (sum of bucket sizes; equals `stats.poolSize`).
- `stats` — Detailed allocation statistics:
- `size` — same as the `size` getter (active pool bytes, including power-of-2 padding).
- `padding` — bytes lost to power-of-2 rounding across active pool slices.
- `ratio` — `size / (size - padding)`; `1` when there is no padding.
- `poolTotal` — capacity of the backing buffer in bytes.
- `poolUsed` — bump-pointer high-water mark; monotonic, never decreases.
- `poolSize` — active pool bytes (same as `size`).
- `poolCount` — number of distinct slots ever bump-allocated (monotonic high-water count, not a live count).
- `buckets` — per power-of-2 bucket: `{ free, used, size }`.

@@ -140,0 +162,0 @@ ## License