Skip to content

Instantly share code, notes, and snippets.

@laphilosophia
Last active February 6, 2026 11:39
Show Gist options
  • Select an option

  • Save laphilosophia/57c4eb41a08cea40d79179c327607af4 to your computer and use it in GitHub Desktop.

Select an option

Save laphilosophia/57c4eb41a08cea40d79179c327607af4 to your computer and use it in GitHub Desktop.
SafeList - Defensive collection for untrusted user code
/**
* @fileoverview SafeList v4.0 - Production-Grade Immutable Chunked List
* @description Zero-cast implementation, strict type safety, power-of-2 optimizations
*/
// =============================================================================
// BRANDED TYPES & CONFIGURATION
// =============================================================================
declare const ListIdBrand: unique symbol
export type ListId = string & { readonly [ListIdBrand]: true }
declare const TimestampBrand: unique symbol
type Timestamp = number & { readonly [TimestampBrand]: true }
export type ErrorCode =
| 'CAPACITY_EXCEEDED'
| 'CIRCULAR_REFERENCE'
| 'IMMUTABLE_MODIFICATION'
| 'OPERATION_TIMEOUT'
| 'VALIDATION_FAILURE'
| 'INDEX_OUT_OF_BOUNDS'
| 'MEMORY_PRESSURE'
export interface SafeListConfig {
readonly id?: ListId
readonly capacity?: number
/** Must be power of 2, otherwise rounded to nearest lower power of 2 */
readonly chunkSize?: number
readonly timeoutMs?: number
readonly allowNull?: boolean
readonly strictMode?: boolean
readonly enableCache?: boolean
}
interface NormalizedConfig {
readonly id: ListId
readonly capacity: number
readonly chunkSize: number
readonly chunkShift: number // log2(chunkSize) for bitwise operations
readonly timeoutMs: number
readonly allowNull: boolean
readonly strictMode: boolean
readonly enableCache: boolean
}
// =============================================================================
// ERROR HANDLING - Rich Context
// =============================================================================
export class SafeListError extends Error {
constructor(
message: string,
public readonly code: ErrorCode,
public readonly context?: Readonly<{
listId?: ListId
operation?: string
index?: number
actualIndex?: number
chunkIdx?: number
offset?: number
chunkLength?: number
expected?: number
actual?: number
cause?: unknown
}>,
) {
super(`[SafeList:${code}] ${message}`)
this.name = 'SafeListError'
// Satır 68-69 FIX: Runtime check ekle
if (typeof (Error as any).captureStackTrace === 'function') {
;(Error as any).captureStackTrace(this, SafeListError)
}
}
toJSON() {
return {
name: this.name,
code: this.code,
message: this.message,
context: this.context,
}
}
}
// =============================================================================
// RESULT MONAD - Enhanced Functional API
// =============================================================================
export class Result<T, E = SafeListError> {
private constructor(
private readonly _ok: boolean,
private readonly _value?: T,
private readonly _error?: E,
public readonly meta?: OperationMeta,
) {}
static ok<T>(value: T, meta?: OperationMeta): Result<T, never> {
return new Result(true, value, undefined, meta) as Result<T, never>
}
static err<E = SafeListError>(error: E, meta?: OperationMeta): Result<never, E> {
return new Result(false, undefined, error, meta) as Result<never, E>
}
get isOk(): boolean {
return this._ok
}
get isErr(): boolean {
return !this._ok
}
value(): T | undefined {
return this._value
}
error(): E | undefined {
return this._error
}
unwrap(): T {
if (!this._ok) {
throw this._error instanceof Error ? this._error : new Error(String(this._error))
}
return this._value!
}
expect(msg: string): T {
if (!this._ok) {
throw new SafeListError(msg, 'VALIDATION_FAILURE', {
cause: this._error,
})
}
return this._value!
}
map<U>(fn: (v: T) => U): Result<U, E> {
if (!this._ok) return Result.err(this._error!, this.meta)
try {
return Result.ok(fn(this._value!), this.meta)
} catch (e) {
return Result.err(
new SafeListError('Map transformation failed', 'VALIDATION_FAILURE', {
cause: e,
}) as unknown as E,
this.meta,
)
}
}
flatMap<U>(fn: (v: T) => Result<U, E>): Result<U, E> {
if (!this._ok) return Result.err(this._error!, this.meta)
return fn(this._value!)
}
tap(fn: (v: T) => void): Result<T, E> {
if (this._ok) {
try {
fn(this._value!)
} catch {
/* ignore tap errors */
}
}
return this
}
orElse(fallback: T): T {
return this._ok ? this._value! : fallback
}
match<U>(onOk: (v: T) => U, onErr: (e: E) => U): U {
return this._ok ? onOk(this._value!) : onErr(this._error!)
}
/** Convert to Promise for async workflows */
toPromise(): Promise<T> {
return this._ok ? Promise.resolve(this._value!) : Promise.reject(this._error!)
}
}
export interface OperationMeta {
readonly durationMs: number
readonly processedCount: number
readonly memoryDelta?: number
readonly errors?: ReadonlyArray<{ index: number; message: string }>
}
// =============================================================================
// INTERNAL DATA STRUCTURES - Zero Cast Design
// =============================================================================
/** Internal mutable view for efficient operations */
interface ChunkNode<T> {
readonly chunks: ReadonlyArray<ReadonlyArray<T>>
readonly size: number
readonly owner: ListId
readonly frozen: boolean
readonly config: NormalizedConfig
}
/** Mutable chunk for internal transformations */
type MutableChunk<T> = T[]
/** Generator-friendly iterator state */
interface IteratorState<T> {
chunks: ReadonlyArray<ReadonlyArray<T>>
chunkIdx: number
itemIdx: number
}
// =============================================================================
// UTILITY FUNCTIONS
// =============================================================================
const isPowerOf2 = (n: number): boolean => n > 0 && (n & (n - 1)) === 0
const nextPowerOf2 = (n: number): number => {
if (isPowerOf2(n)) return n
return 1 << (32 - Math.clz32(n - 1))
}
const log2 = (n: number): number => Math.log2(n)
/** Secure ID generation without global counter */
const generateId = (): ListId => {
const random =
typeof crypto !== 'undefined' && crypto.randomUUID
? crypto.randomUUID()
: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`
return `list:${random}` as ListId
}
const normalizeConfig = (cfg: SafeListConfig): NormalizedConfig => {
const rawChunkSize = cfg.chunkSize ?? 1024
const chunkSize = Math.min(nextPowerOf2(rawChunkSize), 32768) // Max 32k chunks
return {
id: cfg.id ?? generateId(),
capacity: Math.min(cfg.capacity ?? 100_000, 1_000_000), // Hard limit 1M
chunkSize,
chunkShift: log2(chunkSize),
timeoutMs: cfg.timeoutMs ?? 5000,
allowNull: cfg.allowNull ?? false,
strictMode: cfg.strictMode ?? false,
enableCache: cfg.enableCache ?? false,
}
}
// =============================================================================
// MAIN CLASS - SafeList v4.0
// =============================================================================
export class SafeList<out T> {
private readonly root: ChunkNode<T>
private cachedArray: ReadonlyArray<T> | null = null
private constructor(root: ChunkNode<T>) {
this.root = root
}
// ===========================================================================
// FACTORY METHODS
// ===========================================================================
static create<T>(cfg: SafeListConfig = {}): SafeList<T> {
const config = normalizeConfig(cfg)
if (config.capacity <= 0) {
throw new SafeListError('Capacity must be positive', 'CAPACITY_EXCEEDED')
}
return new SafeList({
chunks: Object.freeze([]),
size: 0,
owner: config.id,
frozen: false,
config,
})
}
static fromArray<T>(items: readonly T[], cfg: SafeListConfig = {}): Result<SafeList<T>> {
const startTime = performance.now()
const config = normalizeConfig(cfg)
if (items.length > config.capacity) {
return Result.err(
new SafeListError(
`Batch size ${items.length} exceeds capacity ${config.capacity}`,
'CAPACITY_EXCEEDED',
{ expected: config.capacity, actual: items.length },
),
)
}
const chunks: MutableChunk<T>[] = []
const { chunkSize, id } = config
// Pre-allocate chunks for better memory locality
const numChunks = Math.ceil(items.length / chunkSize)
chunks.length = numChunks
for (let i = 0; i < numChunks; i++) {
const start = i * chunkSize
const end = Math.min(start + chunkSize, items.length)
chunks[i] = items.slice(start, end)
}
const duration = performance.now() - startTime
return Result.ok(
new SafeList({
chunks: Object.freeze(chunks.map((c) => Object.freeze(c))),
size: items.length,
owner: id,
frozen: false,
config,
}),
{
durationMs: duration,
processedCount: items.length,
},
)
}
static of<T>(...items: T[]): Result<SafeList<T>> {
return SafeList.fromArray(items)
}
static empty<T>(): SafeList<T> {
return SafeList.create<T>()
}
// ===========================================================================
// ACCESSORS & PROPERTIES
// ===========================================================================
get size(): number {
return this.root.size
}
get id(): ListId {
return this.root.owner
}
get isFrozen(): boolean {
return this.root.frozen
}
get capacity(): number {
return this.root.config.capacity
}
/** Bitwise optimized index calculation */
private locate(index: number): { chunkIdx: number; offset: number } {
return {
chunkIdx: index >>> this.root.config.chunkShift,
offset: index & (this.root.config.chunkSize - 1), // Faster than modulo for power-of-2
}
}
// ===========================================================================
// SAFE ACCESS
// ===========================================================================
at(index: number): Result<T> {
if (!Number.isFinite(index)) {
return Result.err(
new SafeListError('Index must be finite number', 'VALIDATION_FAILURE', { actual: index }),
)
}
const len = this.root.size
const actualIndex = index < 0 ? len + index : index
if (actualIndex < 0 || actualIndex >= len) {
return Result.err(
new SafeListError(`Index ${index} out of bounds`, 'INDEX_OUT_OF_BOUNDS', {
index,
actualIndex,
expected: len,
listId: this.root.owner,
}),
)
}
const { chunkIdx, offset } = this.locate(actualIndex)
const chunk = this.root.chunks[chunkIdx]
if (!chunk || offset >= chunk.length) {
return Result.err(
new SafeListError('Internal chunk structure corrupted', 'VALIDATION_FAILURE', {
chunkIdx,
offset,
chunkLength: chunk?.length,
}),
)
}
return Result.ok(chunk[offset])
}
/** Unsafe but fast access - returns undefined on bounds violation */
get(index: number): T | undefined {
const len = this.root.size
const i = index < 0 ? len + index : index
if (i < 0 || i >= len) return undefined
const { chunkIdx, offset } = this.locate(i)
return this.root.chunks[chunkIdx]?.[offset]
}
first(): T | undefined {
return this.get(0)
}
last(): T | undefined {
return this.get(this.root.size - 1)
}
// ===========================================================================
// MODIFIERS (Immutable)
// ===========================================================================
add(item: T): Result<SafeList<T>> {
if (this.root.frozen) {
return Result.err(
new SafeListError('Cannot modify frozen list', 'IMMUTABLE_MODIFICATION', {
listId: this.root.owner,
}),
)
}
if (!this.root.config.allowNull && item == null) {
return Result.err(
new SafeListError('Null values not allowed', 'VALIDATION_FAILURE', {
listId: this.root.owner,
}),
)
}
if (this.root.size >= this.root.config.capacity) {
return Result.err(
new SafeListError(`Capacity ${this.root.config.capacity} exceeded`, 'CAPACITY_EXCEEDED', {
actual: this.root.size + 1,
expected: this.root.config.capacity,
}),
)
}
const startTime = performance.now()
const { chunks, size, config } = this.root
const lastChunk = chunks[chunks.length - 1] as MutableChunk<T> | undefined
let newChunks: MutableChunk<T>[]
if (!lastChunk || lastChunk.length >= config.chunkSize) {
newChunks = [...(chunks as MutableChunk<T>[]), [item]]
} else {
// Copy-on-write: Mutate only the last chunk
newChunks = chunks.slice(0, -1) as MutableChunk<T>[]
newChunks.push([...lastChunk, item])
}
const duration = performance.now() - startTime
return Result.ok(
new SafeList({
chunks: Object.freeze(newChunks.map((c) => Object.freeze(c))),
size: size + 1,
owner: config.id,
frozen: false,
config,
}),
{ durationMs: duration, processedCount: 1 },
)
}
addMany(items: readonly T[], timeoutMs?: number): Result<SafeList<T>> {
if (this.root.frozen) {
return Result.err(new SafeListError('Cannot modify frozen list', 'IMMUTABLE_MODIFICATION'))
}
const startTime = performance.now()
const deadline = startTime + (timeoutMs ?? this.root.config.timeoutMs)
const { capacity, allowNull, strictMode, chunkSize, id } = this.root.config
if (items.length === 0) {
return Result.ok(this, { durationMs: 0, processedCount: 0 })
}
const remainingCapacity = capacity - this.root.size
if (items.length > remainingCapacity) {
return Result.err(
new SafeListError(
`Cannot add ${items.length} items, remaining capacity: ${remainingCapacity}`,
'CAPACITY_EXCEEDED',
{ actual: items.length, expected: remainingCapacity },
),
)
}
// Validation pass with timeout checks
const validItems: T[] = []
const errors: Array<{ index: number; message: string }> = []
for (let i = 0; i < items.length; i++) {
// Check timeout every 128 items (bitwise & 127)
if ((i & 127) === 0 && performance.now() > deadline) {
return Result.err(new SafeListError('Batch validation timeout', 'OPERATION_TIMEOUT'), {
durationMs: timeoutMs ?? this.root.config.timeoutMs,
processedCount: i,
})
}
const item = items[i]
if (!allowNull && item == null) {
const error = { index: i, message: 'Null value rejected' }
if (strictMode) {
return Result.err(
new SafeListError(
`Null value at index ${i} rejected in strict mode`,
'VALIDATION_FAILURE',
),
{ durationMs: performance.now() - startTime, processedCount: i, errors: [error] },
)
}
errors.push(error)
continue
}
validItems.push(item)
}
if (validItems.length === 0) {
return Result.ok(this, {
durationMs: performance.now() - startTime,
processedCount: 0,
errors: errors.length ? errors : undefined,
})
}
// Efficient chunk construction
const currentChunks = this.root.chunks as MutableChunk<T>[]
const newChunks: MutableChunk<T>[] = [...currentChunks]
let currentChunk: MutableChunk<T> | undefined = newChunks[newChunks.length - 1]
if (!currentChunk || currentChunk.length >= chunkSize) {
currentChunk = []
newChunks.push(currentChunk)
}
for (const item of validItems) {
if (currentChunk.length >= chunkSize) {
currentChunk = []
newChunks.push(currentChunk)
}
currentChunk.push(item)
}
const duration = performance.now() - startTime
return Result.ok(
new SafeList({
chunks: Object.freeze(newChunks.map((c) => Object.freeze(c))),
size: this.root.size + validItems.length,
owner: id,
frozen: false,
config: this.root.config,
}),
{
durationMs: duration,
processedCount: validItems.length,
errors: errors.length ? errors : undefined,
},
)
}
// ===========================================================================
// TRANSFORMATIONS
// ===========================================================================
map<U>(fn: (item: T, index: number) => U, timeoutMs?: number): Result<SafeList<U>> {
const startTime = performance.now()
const limit = timeoutMs ?? this.root.config.timeoutMs
const deadline = startTime + limit
const { chunks, config, size } = this.root
const { chunkSize, chunkShift } = config
const newChunks: MutableChunk<U>[] = []
let processed = 0
for (let c = 0; c < chunks.length; c++) {
const chunk = chunks[c]
const baseIndex = c << chunkShift // c * chunkSize via bitwise
const newChunk: MutableChunk<U> = new Array(chunk.length)
for (let i = 0; i < chunk.length; i++) {
// Check timeout every 64 items
if ((processed & 63) === 0 && performance.now() > deadline) {
return Result.err(
new SafeListError(
`Map timeout at chunk ${c}, index ${baseIndex + i}`,
'OPERATION_TIMEOUT',
),
{ durationMs: limit, processedCount: processed },
)
}
try {
newChunk[i] = fn(chunk[i], baseIndex + i)
} catch (e) {
return Result.err(
new SafeListError(
`Map failed at index ${baseIndex + i}: ${e instanceof Error ? e.message : 'Unknown'}`,
'VALIDATION_FAILURE',
{ index: baseIndex + i, cause: e },
),
{ durationMs: performance.now() - startTime, processedCount: processed },
)
}
processed++
}
newChunks.push(newChunk)
}
const newId = generateId()
const duration = performance.now() - startTime
return Result.ok(
new SafeList<U>({
chunks: Object.freeze(newChunks.map((c) => Object.freeze(c))),
size,
owner: newId,
frozen: false,
config: { ...config, id: newId },
}),
{ durationMs: duration, processedCount: processed },
)
}
filter(predicate: (item: T, index: number) => boolean, timeoutMs?: number): Result<SafeList<T>> {
const startTime = performance.now()
const limit = timeoutMs ?? this.root.config.timeoutMs
const deadline = startTime + limit
const { strictMode, chunkSize, chunkShift } = this.root.config
const newChunks: MutableChunk<T>[] = []
let currentChunk: MutableChunk<T> = []
let processed = 0
let totalSize = 0
for (let c = 0; c < this.root.chunks.length; c++) {
const chunk = this.root.chunks[c]
const baseIndex = c << chunkShift
for (let i = 0; i < chunk.length; i++) {
if ((processed & 63) === 0 && performance.now() > deadline) {
return Result.err(new SafeListError('Filter operation timeout', 'OPERATION_TIMEOUT'), {
durationMs: limit,
processedCount: processed,
})
}
try {
if (predicate(chunk[i], baseIndex + i)) {
if (currentChunk.length >= chunkSize) {
newChunks.push(currentChunk)
currentChunk = []
}
currentChunk.push(chunk[i])
totalSize++
}
} catch (e) {
const errorMsg = `Filter predicate error at index ${baseIndex + i}`
if (strictMode) {
return Result.err(
new SafeListError(errorMsg, 'VALIDATION_FAILURE', { index: baseIndex + i, cause: e }),
{ durationMs: performance.now() - startTime, processedCount: processed },
)
}
console.warn(errorMsg, e)
}
processed++
}
}
if (currentChunk.length > 0) {
newChunks.push(currentChunk)
}
const newId = generateId()
const duration = performance.now() - startTime
return Result.ok(
new SafeList({
chunks: Object.freeze(newChunks.map((c) => Object.freeze(c))),
size: totalSize,
owner: newId,
frozen: false,
config: { ...this.root.config, id: newId },
}),
{ durationMs: duration, processedCount: totalSize },
)
}
slice(start?: number, end?: number): Result<SafeList<T>> {
const len = this.root.size
const s = start == null ? 0 : start < 0 ? Math.max(0, len + start) : start
const e = end == null ? len : end < 0 ? Math.max(0, len + end) : Math.min(end, len)
if (s >= e || s >= len) {
return Result.ok(SafeList.create<T>(this.root.config))
}
const { chunkSize, chunkShift } = this.root.config
const startChunk = s >>> chunkShift
const endChunk = (e - 1) >>> chunkShift
const newChunks: MutableChunk<T>[] = []
for (let i = startChunk; i <= endChunk; i++) {
const chunk = this.root.chunks[i] as MutableChunk<T>
const isFirst = i === startChunk
const isLast = i === endChunk
const chunkStart = isFirst ? s & (chunkSize - 1) : 0
const chunkEnd = isLast ? ((e - 1) & (chunkSize - 1)) + 1 : chunk.length
newChunks.push(chunk.slice(chunkStart, chunkEnd))
}
const newId = generateId()
return Result.ok(
new SafeList({
chunks: Object.freeze(newChunks.map((c) => Object.freeze(c))),
size: e - s,
owner: newId,
frozen: this.root.frozen,
config: { ...this.root.config, id: newId },
}),
{ durationMs: 0, processedCount: e - s },
)
}
// ===========================================================================
// CONCATENATION - Efficient multi-list operations
// ===========================================================================
concat(other: SafeList<T>): Result<SafeList<T>> {
if (this.root.size + other.root.size > this.root.config.capacity) {
return Result.err(
new SafeListError('Concatenated size exceeds capacity', 'CAPACITY_EXCEEDED', {
actual: this.root.size + other.root.size,
expected: this.root.config.capacity,
}),
)
}
// Fast path: If either list is empty
if (other.root.size === 0) return Result.ok(this)
if (this.root.size === 0) return Result.ok(other)
const newChunks = [
...(this.root.chunks as MutableChunk<T>[]),
...(other.root.chunks as MutableChunk<T>[]),
]
// Flatten if last chunk of first and first chunk of second can fit together
const lastThis = newChunks[newChunks.length - 2]
const firstOther = newChunks[newChunks.length - 1]
if (
lastThis &&
firstOther &&
lastThis.length + firstOther.length <= this.root.config.chunkSize
) {
newChunks[newChunks.length - 2] = [...lastThis, ...firstOther]
newChunks.pop()
}
const newId = generateId()
return Result.ok(
new SafeList({
chunks: Object.freeze(newChunks.map((c) => Object.freeze(c))),
size: this.root.size + other.root.size,
owner: newId,
frozen: false,
config: { ...this.root.config, id: newId },
}),
{ durationMs: 0, processedCount: other.root.size },
)
}
// ===========================================================================
// QUERY & SEARCH
// ===========================================================================
find(predicate: (item: T) => boolean, timeoutMs?: number): T | undefined {
const limit = timeoutMs ?? this.root.config.timeoutMs
const deadline = performance.now() + limit
let count = 0
for (const chunk of this.root.chunks) {
for (const item of chunk) {
if ((count++ & 63) === 0 && performance.now() > deadline) {
throw new SafeListError('Find operation timeout', 'OPERATION_TIMEOUT')
}
try {
if (predicate(item)) return item
} catch {
continue
}
}
}
return undefined
}
findIndex(predicate: (item: T) => boolean, timeoutMs?: number): number {
const limit = timeoutMs ?? this.root.config.timeoutMs
const deadline = performance.now() + limit
const { chunkShift } = this.root.config
let globalIdx = 0
for (let c = 0; c < this.root.chunks.length; c++) {
const chunk = this.root.chunks[c]
for (let i = 0; i < chunk.length; i++) {
if ((globalIdx & 63) === 0 && performance.now() > deadline) {
return -1 // Timeout returns -1 instead of throwing for easier handling
}
try {
if (predicate(chunk[i])) return (c << chunkShift) + i
} catch {
// Continue search
}
globalIdx++
}
}
return -1
}
includes(item: T, fromIndex = 0): boolean {
return this.findIndex((x) => Object.is(x, item)) !== -1
}
some(predicate: (item: T) => boolean, timeoutMs?: number): boolean {
return this.find(predicate, timeoutMs) !== undefined
}
every(predicate: (item: T) => boolean, timeoutMs?: number): boolean {
try {
return !this.find((item) => !predicate(item), timeoutMs)
} catch {
return false
}
}
// ===========================================================================
// ITERATION - Optimized for performance
// ===========================================================================
*[Symbol.iterator](): Iterator<T> {
for (const chunk of this.root.chunks) {
for (const item of chunk) {
yield item
}
}
}
/** Manual iterator for better control than generator */
iterate(): { next: () => IteratorResult<T>; [Symbol.iterator]: () => IterableIterator<T> } {
const state: IteratorState<T> = {
chunks: this.root.chunks,
chunkIdx: 0,
itemIdx: 0,
}
const next = (): IteratorResult<T> => {
if (state.chunkIdx >= state.chunks.length) {
return { done: true, value: undefined }
}
const chunk = state.chunks[state.chunkIdx]
if (state.itemIdx >= chunk.length) {
state.chunkIdx++
state.itemIdx = 0
return next()
}
return { done: false, value: chunk[state.itemIdx++] }
}
return {
next,
[Symbol.iterator]: function* () {
while (true) {
const result = this.next()
if (result.done) break
yield result.value
}
},
}
}
forEach(fn: (item: T, index: number) => void): void {
let idx = 0
const { strictMode } = this.root.config
for (const chunk of this.root.chunks) {
for (const item of chunk) {
try {
fn(item, idx++)
} catch (e) {
if (strictMode) {
throw new SafeListError(`forEach error at index ${idx}`, 'VALIDATION_FAILURE', {
index: idx,
cause: e,
})
}
console.error(`forEach error at ${idx}:`, e)
}
}
}
}
// ===========================================================================
// CONVERSION & SERIALIZATION
// ===========================================================================
toArray(): ReadonlyArray<T> {
// Return cached version if available and frozen
if (this.cachedArray && this.root.frozen) {
return this.cachedArray
}
const result: T[] = new Array(this.root.size)
let idx = 0
// Fast copy using chunk boundaries
for (const chunk of this.root.chunks) {
for (let i = 0; i < chunk.length; i++) {
result[idx++] = chunk[i]
}
}
const frozen = Object.freeze(result)
// Cache if frozen and caching enabled
if (this.root.frozen && this.root.config.enableCache) {
this.cachedArray = frozen
}
return frozen
}
toJSON(): unknown {
try {
return this.toArray()
} catch (e) {
if (e instanceof Error && e.message.includes('circular')) {
throw new SafeListError(
'Circular reference detected during serialization',
'CIRCULAR_REFERENCE',
{ listId: this.root.owner },
)
}
throw e
}
}
// ===========================================================================
// IMMUTABILITY UTILITIES
// ===========================================================================
freeze(): SafeList<T> {
if (this.root.frozen) return this
const frozenChunks = this.root.chunks.map((chunk) =>
Object.isFrozen(chunk) ? chunk : Object.freeze([...chunk]),
)
return new SafeList({
...this.root,
chunks: Object.freeze(frozenChunks),
frozen: true,
})
}
clone(deep = false): Result<SafeList<T>> {
const newId = generateId()
const startTime = performance.now()
if (!deep) {
return Result.ok(
new SafeList({
chunks: Object.freeze(
[...(this.root.chunks as MutableChunk<T>[])].map((c) => Object.freeze([...c])),
),
size: this.root.size,
owner: newId,
frozen: false,
config: { ...this.root.config, id: newId },
}),
{ durationMs: performance.now() - startTime, processedCount: this.root.size },
)
}
try {
let clonedChunks: MutableChunk<T>[]
if (typeof structuredClone === 'function') {
clonedChunks = structuredClone(this.root.chunks as MutableChunk<T>[])
} else {
// Fallback with circular reference protection
const cache = new WeakMap()
clonedChunks = JSON.parse(
JSON.stringify(this.root.chunks, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (cache.has(value)) {
throw new SafeListError('Circular reference detected', 'CIRCULAR_REFERENCE')
}
cache.set(value, true)
}
return value
}),
)
}
return Result.ok(
new SafeList({
chunks: Object.freeze(clonedChunks.map((c) => Object.freeze(c))),
size: this.root.size,
owner: newId,
frozen: false,
config: { ...this.root.config, id: newId },
}),
{ durationMs: performance.now() - startTime, processedCount: this.root.size },
)
} catch (e) {
if (e instanceof SafeListError) return Result.err(e)
return Result.err(new SafeListError('Deep clone failed', 'CIRCULAR_REFERENCE', { cause: e }))
}
}
// ===========================================================================
// TYPE GUARDS & UTILITIES
// ===========================================================================
static isSafeList<T>(obj: unknown): obj is SafeList<T> {
return obj instanceof SafeList
}
toString(): string {
return `SafeList(id=${this.root.owner}, size=${this.root.size}, frozen=${this.root.frozen})`
}
[Symbol.for('nodejs.util.inspect.custom')](): string {
return this.toString()
}
}
// =============================================================================
// EXPORT FACTORY
// =============================================================================
export const createList = SafeList.create
export const listOf = SafeList.of
export const emptyList = SafeList.empty
@laphilosophia
Copy link
Author

How Not To Be A Villainous Developer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment