@centralping/ergo
Version [0.9.0]
Added
-
resolveTimingConfigexport fromlib/response-time. (#304) Pure factory-time resolver for thetimingoption (boolean | {header?, precision?}). Returns{header, precision}orundefinedwhen disabled. Consumed byhttp/handler.jsand ergo-routerauto-wrap.jsso option interpretation lives with the timing primitive. -
DEFAULT_HSTS_MAX_AGE_SECONDSexport fromlib/security-headers. (#284) Named constant for the default HSTSmax-age(one year =31_536_000seconds).buildHstsDirectiveand JSDoc reference the constant; ergo-router transport should import it instead of duplicating the magic number. -
STATUS_PROCESSING/STATUS_COMPLETEexports fromlib/idempotency. (#271) Named lifecycle constants forIdempotencyStoreentry status. Middleware and custom store consumers should compare against these instead of raw string literals. Wire values remain'processing'/'complete'(additive, non-breaking).
Changed
-
BREAKING:
buildSecurityHeaderTuplesvalidates option values at construction. (#283, #331) Constrained headers (xFrameOptions,referrerPolicy,xContentTypeOptions,xXssProtection) reject non-enum values withTypeError. Free-form directives (CSP, Permissions-Policy, HSTS string) require a non-empty string without CTL characters. HSTS object form requires a non-negative integermaxAge(empty{}no longer silently defaults). Enable guards are unified to Pattern A (value !== false && value);String()coercion onxXssProtectionis removed. Invalid undocumented inputs ('',0,null) throw instead of emitting or omitting. -
BREAKING: Rate-limit store
hit()must return absoluteresetAt. (#263) Pluggable stores now return{count, resetMs, resetAt}whereresetAtis the absolute window-reset time in milliseconds in the store’s own clock domain.checkRateLimitusesresetAtforX-RateLimit-Resetand no longer callsDate.now(). Custom stores that previously returned only{count, resetMs}must addresetAt(typicallynow + resetMsusing the same clock that producedresetMs). Non-finiteresetAt/resetMsfail fast withTypeError. -
BREAKING: Bearer authorizer failure
infouses RFC 6750 property names. (#256)type/desc/uriare renamed toerror/error_description/error_uri. The internalerrorPropMaptranslation layer is removed — wire attribute names match the authorizer contract. Update Bearer authorizers accordingly. -
lib/authorizationmulti-strategyauthenticateis always a string. (#290) When no matching scheme is found, challenges are joined with a comma and space (RFC 7235 §4.1) instead of returningstring[]. Aligns withhttp/authorizationheader tuples ([string, string][]) andsend.jssetHeadersemantics. -
lib/authorizationscheme dispatch uses a null-prototype map instead of Proxy. (#255) Fixed{basic, bearer, $default}lookup via??— same defaulting as the former ProxyObject.hasOwntrap, without Proxy overhead. -
lib/csrfvalidatessecret/uuidwith directtypeofchecks. (#269) Replaces TypeError-as-default-parameter sentinels and theutils/type.jsimport with the sametypeof x !== 'string'→throw new TypeError(...)pattern used byhttp/csrfand otherlib/constructors. Error messages for missing parameters are unchanged. Non-string values that previously reachedcreateHmacnow fail with the same TypeError. Empty-string secrets remain allowed at the lib primitive (the HTTP factory still rejects them at construction).
Removed
-
http/main.jsno longer provides a default export object. (#297) The manually mirroredexport default { ... }had drifted from named exports (createResponseAcc,mergeResponsewere named-only) and was already unreachable from the package entry (export *does not re-exportdefault). Named exports are unchanged. Affects only the./http/mainsubpath for any consumer ofimport x from '@centralping/ergo/http/main'. -
http/main.jsno longer declares@module @centralping/ergo. (#299) Canonical package module identity remains onhttp/index.jsonly;main.jsnow uses@module http/mainlike other internalhttp/modules.
Fixed
-
http/cache-controlvalidates structured options at construction and exportsDEFAULT_DIRECTIVES. (#296, #300, #302) Named constantDEFAULT_DIRECTIVES('private, no-cache') replaces the magic-string fallback. Factory throwsTypeErrorwhen a delta-seconds option (maxAge,sMaxAge,staleWhileRevalidate,staleIfError) is not a non-negative integer, whenpublicandprivateare both set, or whennoStoreis combined with freshness directives. Rawdirectivesstrings remain unvalidated. Matches the csrf / rate-limit factory-time value-validation pattern. -
http/rate-limitvalidates option values at construction and includesX-RateLimit-*on 429. (#264, #265, #327) Named defaultsDEFAULT_MAX_REQUESTS/DEFAULT_WINDOW_MSreplace magic-number destructuring. Factory throwsTypeErrorfor invalidmax,windowMs,store, orkeyGenerator(keys-onlyvalidateOptionswas previously the only check). Limited responses now return the same threeX-RateLimit-*header tuples as allowed responses (parity with transport rate-limit). -
http/idempotencyskip paths returnundefinedinstead of{}. (#319) Method-not-applicable and optional missing-key paths now use barereturnso compose-with leavesacc.idempotencyunset (DECISIONS:undefined/nullskip all merges). Previouslyreturn {}wrote an empty object viaextractReturn.IdempotencyResultno longer includesRecord<string, never>. -
http/idempotencyvalidatesmethodsandkeyGeneratorat construction. (#321) ThrowsTypeErrorwhenmethodsis not a non-empty Set or Array of non-empty strings (rejects thenew Set('POST')string footgun) or whenkeyGeneratoris provided but not a function. Matches the csrf /IdempotencyStorefactory-time value-validation pattern. Validatedmethodsare always copied into a newSetso caller mutation after construction cannot change middleware behavior. -
http/compressforwards theWritable.endcallback on the compression path. (#314) PreviouslycompressedEnddroppedcbwhen piping through zlib and the compressor'end'/'error'handlers calledorigEnd()with no callback. The patchedres.endnow normalizes Node overloads, captures the callback, invokes it on compressor finish, and passes the error to the callback on compressor failure. -
http/compressthreshold uses the string encoding argument for byte length. (#317)Buffer.byteLength(chunk)always measured UTF-8;res.end(str, 'latin1')(and other non-UTF-8 encodings) could compress or skip incorrectly. Size is nowBuffer.byteLength(chunk, encodingArg). -
CSRF UUID cookie locks
httpOnly: trueaftercookieOptionsspread. (#318) The token cookie already forcedhttpOnly: falseandsameSite: 'Strict'after the spread; the UUID cookie only forcedsameSite. Callers passingcookieOptions: {httpOnly: false}could make the UUID cookie JavaScript-readable. Both cookies now lock their httpOnly/sameSite attributes symmetrically. Regression tests cover UUIDhttpOnlyand both cookies’sameSitelocks. -
utils/setuses strict digit-only array-index detection. (#353) Intermediate nodes are Arrays only when the next path segment matches/^\d+$/. Previously, permissiveNumber()coercion treated'',-1,Infinity,0x1, and1e2as numeric and created Arrays with non-index properties (reachable via query paths likefields[a..b]=x). -
utils/setthrows a descriptive path-conflictTypeError. (#354) Reusing a primitive ornullintermediate now throws with message context and stablecode: 'ERGO_SET_PATH_TRAVERSE'instead of an opaque engine assignment error. NewtrySet()returnsfalsefor library-minted path conflicts only (identity-branded errors; rethrows unexpected errors including code-spoofed TypeErrors);lib/query.jsuses it for first-wins skip so inputs likea=42&a[b]=99no longer 500 throughurl()middleware. First-wins is bidirectional over path containers (#379, #380, #381, #382): nested-then-scalar preserves earlier nests; Array↔object shape is locked (non-index under Array and index under plain object both skip) while numeric indices under Arrays (role[0]=user&role[1]=admin) and sibling object keys remain allowed; scalar-then-[]also first-wins. -
utils/setrejects__proto__/prototype/constructorpath segments. (#383) Function intermediates remain valid for ordinary own properties (handler.timeout), but those three segments always throwERGO_SET_PATH_TRAVERSEso callers cannot write through.prototypeonto shared builtins. -
lib/query.jsaccumulates pairs in aMapso first-wins follows source order. (#384) Intermediate accumulation previously used a null-prototype object +Object.entries, which reorders integer-like keys (V8 integer-index order). That inverted1[a]=x&1=yinto a scalar win. Pair iteration now preserves insertion order; the returned accumulator remainsObject.create(null). -
lib/query.jsfirst-wins covers path aliases and occupied leaves. (#385) Distinct wire forms that normalize to the same dotted path (a.b=1&a[b]=2) no longer last-write viatrySet. Any own value already at the destination path wins (scalars, containers, and array slots), soa[]=1&a[0]=2also keeps the first slot value. -
utils/setrejects unsafe intrinsic intermediates and Arraylengthassignment. (#386–#389) Path root and reuse reject constructor.prototypeobjects, host objects reachable fromglobalThisat module load (depth-limited graph of own keys / prototype accessors —Intl,Proxy,crypto.subtle,process._events,Array.prototype.push,Intl.NumberFormat.prototype.format, …), and Proxies. Proxy and host checks run before Array/null-proto shortcuts soProxy(Array.prototype)and null-proto hosts cannot write through. Ordinary functions (including per-instance bound methods created after module load), non-host null-proto objects, Arrays, and user class instances remain valid intermediates. Host matching is identity-based within the JavaScript realm where the module initializes; callers must not pass host-owned globals or intrinsics from another realm (for example, anode:vmcontext) as roots or intermediates (#395). Assigning or creatinglengthon an Array, TypedArray, DataView, Buffer, orarguments(leaf or intermediate — TypedArray/Buffer/DataView expose non-ownlength, so intermediate create must reject before shadowing) is forbidden (#393); digit indices aboveMAX_ARRAY_INDEX(1024) are rejected by numeric value only when indexing an Array (leading zeros allowed when in range; plain-object / top-level digit keys unconstrained; sparse DoS bound; full numeric-bracket design remains #280). Missing intermediates are defined as own data properties so inherited setters cannot hijack traversal; existing own leaves keep ordinary assignment semantics. Path planning snapshots accessor results once so stateful getters cannot flip container type between Array-index bounds checks and writes. Plain-object.lengthremains allowed. -
lib/query.jsoptions use own properties only. (#392) Parser options are copied into a null-prototype viaObject.assignso pollutedObject.prototype.maxPairs/maxLength/splitcannot disable DoS caps or alter comma-split defaults. -
utils/setclarity refactor preserves return value. (#355) The dense parenthesized assignment body is split into explicit statements while still returning the assigned value (public@centralping/ergo/utils/setexport — non-breaking).