@dooboostore/core

NPM versionBuild and TestLicense: MIT

A zero-dependency TypeScript utility library providing 30+ modules for the @dooboostore ecosystem — a reactive messaging system (RxJS-like, but self-contained), DOM-oriented form validators, 2D/3D geometry, an extensible HTTP fetcher, cron-style scheduling, transaction coordination, and a large collection of array/object/string/date/math/convert/random helpers.


✨ Key Features

  • 🎯 RxJS-Like Reactive System:Observable,Subject,BehaviorSubject,ReplaySubject,ReplayForwardSubjectwith 25+ pipeable operators — without an RxJS dependency
  • ⚡ Advanced Async Utilities:Promises.sleep/settle/settles/retry/loop,Promises.Result.wrapstate tracking, concurrency-limited batch execution,AbortablePromise
  • 📋 DOM-Bound Validation Framework: composableValidatorclasses that read/write a DOM element'svalue/checkeddirectly, plus checkbox-group combinators
  • 📐 2D/3D Geometry Engine:Point2D/Point3D,Rect,Polygon,Ellipse,Vector(p5.js-style), with containment/overlap checks and percent/ratio layout helpers
  • 🌐 Extensible HTTP Client:Fetcher/HttpFetcher/HttpJsonFetcher— a config-object request API with per-request callbacks and overridable lifecycle hooks
  • 📦 Data Utilities:ArrayUtils,ObjectUtils,StringUtils,DateUtils,MathUtil,ConvertUtils,RandomUtils
  • ⏰ Cron-Shaped Scheduling:ScheduleBasetracks run history/state/counts around yourexecute()implementation
  • 🛡️ Transaction Coordination:TransactionManagercascadescatch()/finally()across registered transactions
  • 🎨 Type-Safe: full TypeScript support, strict typing throughout
  • 🪶 Zero Dependencies: no external runtime dependencies (only areflect-metadatapeer dependency); ESM/CJS/UMD builds

Installation

Install@dooboostore/coreusing your favorite package manager:

# pnpmpnpm add @dooboostore/core# npmnpm install @dooboostore/core# yarnyarn add @dooboostore/core

Import from the package root.src/index.tsre-exports everything, and the package explicitly doesnotsupport arbitrary subpath imports like@dooboostore/core/message—package.json'sexportsmap only defines.,./message/operators, and./bundle-entry. Alwaysimport { ... } from '@dooboostore/core'(operators are the one exception — see below).


📚 Module Organization

@dooboostore/coreis organized into30+ modules, all re-exported from the package root (src/index.ts).

Tier 1: Foundation (Data & Async)

ModuleDescriptionKey Exports
messageRxJS-like reactive systemObservable,Subject,BehaviorSubject,ReplaySubject,ReplayForwardSubject,Subscription,debounce/throttlehelpers
promiseAdvanced async utilitiesPromises,AbortablePromise
validType guardsValidUtils(isString,isNumber,isObject,isEmpty,isNullish, …)
arrayArray manipulationArrayUtils(split,toShuffle,pick,has/hasAll,relation, …)
objectObject introspection & path accessObjectUtils(keys,values,entries,deepCopy,ObjectUtils.Path.get/set, …)
stringString utilitiesStringUtils(trim,lpad/rpad,ellipsis,appendPostposition, …)
dateDate/time operationsDateUtils(add,toSeconds/fromSeconds,relativeTime,formatDuration, …)
mathMathematical operationsMathUtil(hypot,pythagorean,radians/degrees,bezier,rotatePoint, …)
convertType conversionConvertUtils(objToMap/mapToObj,toObject,toHexColor,escapeHTML, …)
randomRandomizationRandomUtils(int,float,uuid/uuid4,hex,rgb/rgba,alphabet)

Tier 2: Data Structures & Network

ModuleDescriptionKey Exports
validatorsDOM-bound validation frameworkValidator,FormValidator,RequiredValidator,RegExpTestValidator, checkbox-array combinators
fetchHTTP client frameworkHttpFetcher,HttpJsonFetcher,Fetcher(base, for non-HTTP transports)
entity2D/3D geometryPoint2D,Point3D,Polygon,Rect,Ellipse,Vector,Scalar
runsRunnable patternRunnable<R, PR>,AroundRunnable,AutoStartRunner,RunnableLoader,StoreRunnableLoader

Tier 3: Coordination & Async Patterns

ModuleDescriptionKey Exports
transactionTransaction coordinationTransactionManager,Transactioninterface
scheduleCron-shaped schedulingScheduleBase(tracks state/history/counts)
storeStore loader patternStoreLoader

Tier 4: Utilities & Helpers

ModuleDescriptionKey Exports
urlURL utilitiesUrlUtils
parserParsing utilitiesCssParser
loggerStructured loggingLogger
storageStorage abstractionStorage,MemoryStorage,StorageUtils
reflectMetadata reflectionReflectUtils
functionFunction utilitiesFunctionUtils(dynamic script execution)
optionalOptional/Maybe monadOptional<T>,OptionalUtils
iteratorsRange iteratorsRange
queuesAsync queuesAsyncBlockingQueue<T>
adviceAOP-style exception handlingAdvice
codeCode constantsCode<D>type, ISO3166-1 country codes (IOS3166_1_Codeenum,IOS3166_1_CodeDescription)
imageImage utilitiesImageUtils
expressionExpression evaluationActionExpression,Expression
hashHashingHashUtils(hash53)
typesShared type helpers (src/types.ts)ClassType,ConstructorType,OptionalType,Mutable,MethodKeys, …

🎯 Core Modules Deep Dive

1️⃣Message & Reactive System(Observable, Subject)

The reactive messaging system provides an RxJS-like APIwithout the RxJS dependency.

Core Classes

// Base observableObservable<T, E>
  .pipe(...operators):Observable.subscribe(observer | callback, errorCallback?, completeCallback?):Subscription.toPromise():Promise<T |undefined>// Subject variantsSubject<T, E>// Multicast observableBehaviorSubject<T, E>// With current valueReplaySubject<T, E>// Replays historical values to new subscribersReplayForwardSubject<T>// ReplaySubject; getForwardOnly() returns an Observable of only values emitted after subscribing (no replay)Subscription// returned by subscribe(); call unsubscribe()

Pipeable Operators (message/operators)

Operators live in their own subpath export —@dooboostore/core/message/operators— since there are many of them and most consumers only need a handful:

import{Subject}from'@dooboostore/core';import{ debounceTime, map, switchMap, filter, catchError }from'@dooboostore/core/message/operators';

Available operators (one file per operator undersrc/message/operators/):map,filter,reduce,scan,switchMap,mergeMap,concatMap,tap,find,first,single,take,takeLast,takeUntil,takeWhile,skip,skipWhile,distinctUntilChanged,debounceTime,throttle,throttleTime,delay,bufferTime,timeout,catchError,retry,finalize,share,from,interval.

Standalone creation/combination helpers (not.pipe()operators, imported from the package root) live undersrc/message/internal/:of,range,fromArray,fromPromise,fromEvent,merge,concat,defer,empty,never,throwError,interval,timer,firstValueFrom,lastValueFrom,toPromise. The root also exports the pipeablestartWith,skip,take,takeWhile, and plain function helpersdebounce(fn, delay),throttle(fn, delay),throttleTime(fn, delay),debounceTimeIntervalLock(unlockChecker, unlocked, intervalTime).

Example: Reactive Search with Debounce

import{Subject}from'@dooboostore/core';import{ debounceTime, map, filter, switchMap }from'@dooboostore/core/message/operators';constsearchInput$ =newSubject<string>();

searchInput$.pipe(debounceTime(300),// wait 300ms after the last keystrokemap(q=>q.trim()),filter(q=>q.length>0),switchMap(q=>searchApi(q))// your own async lookup).subscribe({next:results=>displayResults(results),error:err=>showError(err)
});

searchInput$.next('typescript');

2️⃣Promise Utilities(Advanced Async)

Everything lives on thePromisesnamespace, imported from the package root.

import{Promises}from'@dooboostore/core';Promises.sleep(ms:number):Promise<void>Promises.sleepReject(ms:number):Promise<never>// rejects after msPromises.delayExecute<T>(value: (() =>T) | (() =>Promise<T>),delay?:number):Promise<T>Promises.settle<T>(promise:Promise<T>):Promise<Promises.SettledResult<T>>Promises.settles<T>(...promises:Promise<T>[]):Promise<Promises.SettledResult<T>[]>Promises.isFulfilled(result) /Promises.isRejected(result)// narrow a SettledResultPromises.retry<T>(factory:(attempt:number,error?:any) =>Promise<T>,config: {retry:number;delay?: {initialDelay?:number;retryDelay?:number};onRetry?:(error, attempt) =>void}
):Promise<T>Promises.loop<T>(config: {factory:(loopInfo: { age:number}) =>Promise<T>;delay?:number;loopDelay?:number}): {subscribe:(callback: { then?,catch?, delayThen? }) =>Subscription// unsubscribe() stops the loop}Promises.filterCatch(promise, errorTypeOrPredicate):Promise<Error|undefined>Promises.abortable<T>(executor: (() =>Promise<T>) |Promise<T>,signal?:AbortSignal):AbortablePromise<T>// await directly; rejects with signal.reason (AbortError); a factory isn't called if already abortedPromises.withResolvers<T>(): { promise, resolve, reject }// like the stage-4 Promise.withResolvers// Wrap a promise (or a promise factory) so its state can be read synchronouslyPromises.Result.wrap<T, E>(promise |() =>promise)// the promise itself, plus status/isPending/isFulfilled/isRejected/value/reason (+ factory() to re-run when given a factory)Promises.Result.awaitWrap<T, E>(promise |() =>promise)// awaits it and resolves to a plain settled state object (never rejects)// Batch execution helpersPromises.executeInChunks<T>(factories, { chunkSize, sleepBetweenChunks }):Promise<T[]>Promises.executeSettledInChunks<T>(factories, { chunkSize, sleepBetweenChunks }):Promise<Promises.SettledResult<T>[]>Promises.executeWithConcurrency<T>(factories, concurrency =10):Promise<T[]>// fixed worker poolPromises.executeWithLimit<T>(factories, limit =5):Promise<T[]>// starts a new one as soon as one finishes

Example: A cancellable polling loop

import{Promises}from'@dooboostore/core';constsubscription =Promises.loop({factory:async({ age }) => {constres =awaitfetch('/api/status');if(!res.ok)thrownewError('not ready');returnres.json();
  },loopDelay:2000// wait 2s between each attempt (loops forever until unsubscribed)}).subscribe({then:(result, { age, duration }) =>console.log(`attempt #${age}ok in${duration}ms`, result),catch:(err, { age }) =>console.error(`attempt #${age}failed`, err)
});// latersubscription.unsubscribe();

Example: Retry with backoff

import{Promises}from'@dooboostore/core';constdata =awaitPromises.retry((attempt, prevError) =>fetch(`/api/data?attempt=${attempt}`).then(r=>r.json()),
  {retry:3,delay: {retryDelay:1000},onRetry:(e, attempt) =>console.log('retrying', attempt, e) }
);

3️⃣Validation Framework(DOM-bound Validators)

This isnotan asyncvalidate(value): Promise<Result>framework — it's a synchronous, DOM-element-boundValidatorhierarchy: eachValidatoroptionally holds atarget(a form element) andevent, reads/writes.value/.checkedon that target, and exposes a synchronousvalid(): boolean.

abstractclassValidator<T =any, E =Element> {constructor(value?: T,target?: E,event?:Event, autoValid =true, autoValidAction =true);
  get/setvalue: T |undefined;// falls back to target.value when unsetget/setchecked:boolean;// reads/writes target.checkedabstractvalid():boolean;validAction():boolean;// valid() + invokes the validAction callback set via setValidAction()// Composition: a Validator auto-discovers any of its OWN properties that are themselves ValidatorschildValidators(): [string,Validator][];childValid():boolean;// true if every child validator is valid}

FormValidator<E = HTMLFormElement>is the composite root: itsvalid()delegates tochildValid(), so anyValidator-typed property you assign on it is automatically included.

Built-in leaf validators

Classvalid()semantics
RequiredValidatorvalue is notundefined/null
EmptyValidator/NotEmptyValidatorvalue is empty (null/undefined/length <= 0) / not empty
RegExpTestValidator(regexp)/NotRegExpTestValidator(regexp)regexp.test(value)/ its negation
ValueEqualsValidator(equalsValue)/ValueNotEqualsValidator(equalsValue)value === equalsValue/!==
CheckedValidator/UnCheckedValidatortarget.checkedistrue/false
PassValidator/NonPassValidatoralwaystrue/ alwaysfalse
ValidValidator(callback)valid()'s result comes from your owncallback(value, target, event)
MultipleValidator(validators)all given validators are valid (AND)
ValidMultipleValidator(callback, validators)valid()'s result comes from your owncallback(validators, value, target, event)
ValidatorArray(abstract)base for validators whosevalueis aValidator[](e.g. a checkbox group)
ValidValidatorArray(callback)ValidatorArraywhosevalid()delegates to your callback
AllCheckedValidatorArray/AllUnCheckedValidatorArrayevery item in the array is checked / unchecked
CountEqualsCheckedValidatorArray(count),CountGreaterThanCheckedValidatorArray(count),CountGreaterThanEqualsCheckedValidatorArray(count),CountLessThanCheckedValidatorArray(count),CountLessThanEqualsCheckedValidatorArray(count),CountUnCheckedValidatorArray(count), and the matching*UnCheckedValidatorArraysetthe number of checked/unchecked items compares againstcountas the name says
IncludeCheckedValidatorArray(include, allRequired?)/ExcludeCheckedValidatorArray(include, allRequired?)at least one item is checked and every checked value is ininclude(withallRequired, everyincludevalue must be checked) / the same for unchecked items

Example: Composite form validation

import{FormValidator,RequiredValidator,RegExpTestValidator}from'@dooboostore/core';classSignupFormValidatorextendsFormValidator{
  email =newRegExpTestValidator(/^[^\s@]+@[^\s@]+\.[^\s@]+$/);
  password =newRequiredValidator<string>();
}constform =newSignupFormValidator(document.querySelector('form')!);
form.email.set('user@example.com', emailInputEl);
form.password.set('secret123', passwordInputEl);if(form.valid()) {submitForm();
}else{const[failedField] = form.childInValidValidator()[0] ?? [];console.log('first invalid field:', failedField);
}

4️⃣Geometry & Entity(2D/3D Operations)

// 2D point — just coordinates + copy(); no distance/angle/rotate methods on Point2D itselfPoint2D{x:number,y:number}
  .copy():Point2D// 3D point extends Point2D, adds zPoint3DextendsPoint2D{z:number}
  .copy():Point3D// Axis-aligned rectangle — many constructor overloads (x,y,w,h / two points / a SizeType / …)Rect.isIn(point | rect):boolean// fully contains.isOut(point | rect):boolean.isOverlap(point | rect):boolean// intersects.toRotate(angle, { rotatePoint?,type? }):Point2D[]// rotated corner points// plus a large family of percent/ratio-based layout helpers: setWidth/height, addXPercent,// getXByPercent, toScaleByWidth/Height, copyByPercent/Ratio, and Rect-to-Rect size "linking"// via addLink()/unLink() so resizing one Rect can proportionally resize its linked Rects.// Polygon — arbitrary point list, backed by Point2DsPolygon.isIn(point):boolean// ray-casting.isOut(point):boolean.isOverlap(point | rect | ellipse | polygon):boolean.toRect():Rect// axis-aligned bounding box// Ellipse extends Point2D (center) + radiusX/radiusY/rotation/startAngle/endAngleEllipse.isIn(point) / .isOut(point) / .isOverlap(point | rect | polygon | ellipse) / .toRect()// p5.js-style vector — `to*` methods are non-destructive (return a new Vector),// everything else (add/sub/mult/div/normalize/…) mutates the vector in placeVector{ x, y, z }// A simple mutable numeric boxScalar{ get/setvalue:number}

Example: Collision Detection

import{Rect,Point2D}from'@dooboostore/core';constplayer =newRect(100,100,50,50);// x, y, width, heightconstenemy =newRect(130,120,40,40);constprojectile =newPoint2D(140,110);if(player.isOverlap(enemy)) {console.log('Player hit enemy!');
}if(enemy.isIn(projectile)) {console.log('Projectile hit enemy!');
}

5️⃣HTTP Fetcher(Extensible Framework)

Fetcher<TARGET, RESPONSE, CONFIG, PIPE>is an abstract base whose single public entry point,fetch(request), runs yourbefore→execute→afterSuccess/error→finallyhooks (allprotected, meant to be overridden by subclassing) around one request.HttpFetcherimplementsexecuteon top of the nativefetchAPI and addsget/post/put/patch/head/deleteconvenience methods —each one still takes a single request-config object, not(url, options):

import{HttpFetcher}from'@dooboostore/core';constfetcher =newHttpFetcher();constres =awaitfetcher.get({target:'https://api.example.com/users',// string | URL | { url, searchParams }config: {fetch: {headers: {Authorization:'Bearer ...'},timeout:5000} },transform:data=>dataasUser[]// response is the raw fetch Response by default});

HttpJsonFetcherextends it: it auto-sets JSONContent-Type/Acceptheaders, JSON-stringifies a non-string request body, and by default parses the response body as JSON (setconfig.responseTransformto'text'or'response'to opt out, or pass your own function). It also addspostJson/patchJson/putJson, which accept a raw (non-serialized)bodyand stringify it for you.

import{HttpJsonFetcher}from'@dooboostore/core';constapi =newHttpJsonFetcher();constuser =awaitapi.get<User>({target:`/api/users/${id}`});constcreated =awaitapi.postJson<User>({target:'/api/users',config: {fetch: {body: {name:'Ada'} } } });

Per request,HttpFetcherConfigalso acceptsfetcher(a customfetchimplementation),allowedResponseNotOk(don't throw on non-2xx),beforeProxyFetch/afterProxyFetch(rewrite the request / response;skipGlobalBeforeProxyFetch/skipGlobalAfterProxyFetchbypass the class-level hooks),fetchResponseBeforeCallBack/fetchResponseAfterCallBack, andhasResponseErrorChecker(return a truthy value to throw it).fetch.timeoutaborts automatically and can be combined with your ownsignal.

For cross-cutting behavior (auth headers, 401 handling, logging), subclassHttpFetcher/HttpJsonFetcherand override the protected hooks:before,beforeFetch,afterFetch,afterSuccess,afterSuccessTransform,error,finally,beforeProxyFetch,afterProxyFetch,errorTransform. By defaulterrorTransformwraps failures inHttpResponseError(error,body,response; check withisHttpResponseError).


6️⃣Transaction Coordination

interfaceTransaction<I =any, T =any, C =any, F =any> {try(data: I):Promise<T>;catch(e:any):Promise<C>;finally():Promise<F>;
}classTransactionManager{setTransaction(key:string|Symbol|ConstructorType<any>,transaction:Transaction):void;hasTransaction(key):boolean;deleteTransaction(key):boolean;
  getTransaction<T>(key):Transaction|undefined;// marks it "active" for catch()/finally() belowcatch(e:any):Promise<void>;// calls .catch(e) on every transaction retrieved via getTransaction()finally():Promise<void>;// calls .finally() on every transaction retrieved via getTransaction(), then clears the map}

Important:TransactionManagerdoes not call each transaction'stry()for you — its owntry()is a no-op.catch()/finally()only cascade to transactions that were actually fetched viagetTransaction()(that call is what marks a transaction "in-flight"). The intended usage is: register withsetTransaction, look each one up withgetTransactionand run its owntry()yourself (typically inside your owntry { }block), then let the manager cascadecatch()/finally()for coordinated rollback/cleanup:

import{TransactionManager}from'@dooboostore/core';constmanager =newTransactionManager();
manager.setTransaction('payment', paymentTransaction);
manager.setTransaction('inventory', inventoryTransaction);try{awaitmanager.getTransaction('payment')!.try(order);awaitmanager.getTransaction('inventory')!.try(order);
}catch(e) {awaitmanager.catch(e);// rolls back every transaction that was getTransaction()'d above}finally{awaitmanager.finally();// cleans up the same set, then clears the registry}

7️⃣Scheduling(Cron-shaped)

ScheduleBase<T, R>doesn't parse or run cron expressions itself —specis just a conventional cron-format string field you (or your own scheduler/host) read to decide when to callrun(). What it actually provides is state/history/count tracking around yourexecute():

abstractclassScheduleBase<T =void, R =void> {abstractspec:string;// conventional cron string, e.g. "0 2 * * *" — not parsed by this classabstractname?:string;abstractdescription?:string;getstate():'INITIALIZE'|'RUNNING'|'DONE-AND-WAITING'|'STOPPED'|'ERROR'|'SUCCESS';gethistory(): { state, date, input, result? }[] |undefined;gettotalCount() /getsuccessCount() /geterrorCount():number;run(data: T):Promise<R>;// wraps execute(), updates state/history/counts — call this, not execute()abstractexecute(data: T):Promise<R>;
}
import{ScheduleBase}from'@dooboostore/core';classDailyBackupScheduleextendsScheduleBase<void, {fileCount:number}> {
  spec ='0 2 * * *';
  name ='Daily Database Backup';
  description ='Back up the database every night at 02:00';asyncexecute() {constbackup =awaitperformBackup();return{fileCount: backup.fileCount};
  }
}constschedule =newDailyBackupSchedule();awaitschedule.run();// updates state/history/successCount|errorCount as a side effectconsole.log(schedule.state, schedule.totalCount);

8️⃣Data Utilities

ArrayUtils

ArrayUtils.split(arr, length)// split into chunks of `length`ArrayUtils.toShuffle(arr)// returns a shuffled copyArrayUtils.pick(arr, count?)// pick one (no count) or `count` random itemsArrayUtils.popPick(arr)// pick-and-remove a random itemArrayUtils.chancePick(weightedArr)// weighted random pickArrayUtils.has(arr, ...items) /hasAll(...) /hasNot(...) /hasAllNot(...)ArrayUtils.relation(...arrays)// set-relation info (intersection/etc.) between arraysArrayUtils.create2DArray(rows, cols, initialValueOrFactory)ArrayUtils.maxLength(matrix)// { maxRows, maxCols, maxRowsCols, total }

ObjectUtils

ObjectUtils.keys(obj) /values(obj) /entries(obj) /keyLength(obj)ObjectUtils.deepCopy(obj)ObjectUtils.pickRandomKey(obj) /pickRandomValue(obj) /pickRandomKeyValue(obj)ObjectUtils.toDeleteUndefinedAndNull(obj, { deep? })ObjectUtils.constructorName(target) /allProtoTypeName(target) /ownPropertyNames(target)ObjectUtils.Path.get(obj, path, defaultValue?) /Path.set(obj, path, value) /Path.deletePath(obj, path)ObjectUtils.Script.evaluate(script, thisTarget) /Script.evaluateReturn(script, thisTarget)

StringUtils

StringUtils.trim(str)StringUtils.lpad(fill, len, str) /rpad(fill, len, str)StringUtils.lsubString(str, len) /rsubString(str, len)StringUtils.ellipsis(text, length)// truncate with "…"StringUtils.deleteEnter(str)// strip newlinesStringUtils.pickEmoji(str)StringUtils.appendPostposition(text, { vowel, consonant })// appends `consonant` if text ends in a final consonant (batchim), else `vowel`StringUtils.escapeSpecialCharacterRegExp(str)

(camelCase/kebab-case/snake_case-style conversions live onConvertUtils, notStringUtils— see below.)

DateUtils

DateUtils.add({ years?, months?, days?, minutes?, seconds?, milliSecond? }, date?)DateUtils.format(date, format ='yyyy-MM-dd HH:mm:ss')DateUtils.isSameDate(date1, date2) /compare(date1, date2)DateUtils.toSeconds(date?) /fromSeconds(seconds) /toMilliseconds(date?) /fromMilliseconds(ms)DateUtils.toISOString(date?) /fromISOString(isoString)DateUtils.age(dateOfBirth, until?) /countingAge(dateOfBirth, until?)// Korean "counting age" variantDateUtils.relativeTime(date, ...)// "3 minutes ago"-style stringDateUtils.formatDuration(seconds, locale?)DateUtils.weekCountOfYear(date)

MathUtil

MathUtil.hypot(...values) /pythagorean(a, b)MathUtil.sum(arr) /avg(arr) /max(arr) /min(arr) /minMax(value, { min, max })MathUtil.radians(deg, {type? }) /degrees(rad, {type? })MathUtil.ratio(numerator, denominator, multiplier?)MathUtil.getPercentByTotal(total, value) /getValueByTotalInPercent(total, percent)MathUtil.getRatioByTotal(total, value) /getValueByTotalInRatio(total, ratio)MathUtil.bezier(points, frame, idx) /beziers(points, frame) /cubicBezier(position, frameConfig, bezierConfig)MathUtil.rotatePoint(point, center, angleInRadians)MathUtil.distSqToLineSegment(point, segmentStart, segmentEnd)

ConvertUtils

ConvertUtils.objToMap(obj) /mapToObj(map) /mapToJson(map) /jsonToMap(json)ConvertUtils.toObject(urlSearchParamsOrFormDataOrEntries)ConvertUtils.snakeToCamelCase(str) /camelToSnakeCase(str)ConvertUtils.toHex(num) /hexToInt(hex) /toHexColor(rgba)ConvertUtils.escapeHTML(html) /decodeHTMLEscape(html)ConvertUtils.toArray(valueOrIterable) /flatArray(...values)

RandomUtils

RandomUtils.int(min?, max?) /float(min?, max?)RandomUtils.uuid(format ='xxxx-xxxx-xxxx-xxxx') /uuid4()RandomUtils.hex() /rgb() /rgba()RandomUtils.alphabet(len)RandomUtils.chance(percentage)// boolean weighted coin-flipRandomUtils.uniqueInts(min, max, count)

🔗 Type Guard Utilities (ValidUtils)

import{ValidUtils}from'@dooboostore/core';ValidUtils.isString(v) /isNumber(v) /isArray(v) /isObject(v) /isMap(v) /isSet(v)ValidUtils.isNull(v) /isUndefined(v) /isNullish(v) /isNotNullish(v) /isDefined(v)ValidUtils.isEmpty(v)ValidUtils.isFunctionType(v)// is v a plain callback (not a class constructor)ValidUtils.isClassType(v)// is v a `class`-declared constructorValidUtils.isPlainObject(v) /isUrl(str) /isStringNumber(v)ValidUtils.isFrozen(v) /isSealed(v) /isFrozenOrSealed(v) /isFrozenAndSealed(v) (and the`isNot*`negations)ValidUtils.isIterator(v)ValidUtils.includeValue(obj, value)

📖 Usage Examples

Example 1: Reactive search + validated form, together

import{Subject,FormValidator,RegExpTestValidator,RequiredValidator,HttpJsonFetcher}from'@dooboostore/core';import{ debounceTime, map, filter, switchMap }from'@dooboostore/core/message/operators';constfetcher =newHttpJsonFetcher();constsearchInput$ =newSubject<string>();

searchInput$.pipe(debounceTime(300),map(q=>q.trim()),filter(q=>q.length>0),switchMap(q=>fetcher.get<Item[]>({target:`/api/search?q=${encodeURIComponent(q)}`}))
).subscribe(results=>displayResults(results));classSearchFormValidatorextendsFormValidator{
  query =newRequiredValidator<string>();
}

Example 2: Coordinated batch requests

import{Promises,HttpJsonFetcher}from'@dooboostore/core';constapi =newHttpJsonFetcher();constresults =awaitPromises.settles(
  api.get({target:'/api/users'}),
  api.get({target:'/api/posts'}),
  api.get({target:'/api/comments'})
);

results.forEach((result, idx) =>{if(Promises.isFulfilled(result)) {console.log(`request${idx}ok:`, result.value);
  }else{console.log(`request${idx}failed:`, result.reason);
  }
});

Example 3: Geometry — collision detection

import{Rect,Point2D,Polygon}from'@dooboostore/core';constplayer =newRect(100,100,50,50);constenemy =newRect(200,140,40,40);constpowerUp =newPoint2D(150,180);if(player.isOverlap(enemy))console.log('Game Over!');if(player.isIn(powerUp))console.log('Power Up Activated!');constwalls =newPolygon([newPoint2D(0,0),newPoint2D(500,0),newPoint2D(500,500),newPoint2D(0,500)
]);if(walls.isIn(player.center))console.log('Player is inside the walls');

Example 4: Transaction rollback

import{TransactionManager,Transaction}from'@dooboostore/core';classPaymentTransactionimplementsTransaction<Order> {asynctry(order:Order) {awaitchargeCard(order.payment); }asynccatch(error:any) {awaitrefundCard(error); }asyncfinally() {awaitclosePaymentConnection(); }
}constmanager =newTransactionManager();
manager.setTransaction('payment',newPaymentTransaction());try{awaitmanager.getTransaction('payment')!.try(order);
}catch(error) {awaitmanager.catch(error);
}finally{awaitmanager.finally();
}

Example 5: A retried, cancellable poll

import{Promises}from'@dooboostore/core';constsub =Promises.loop({factory:() =>Promises.retry(() =>fetch('/api/status').then(r=>r.json()), {retry:2}),loopDelay:5000}).subscribe({then:status=>console.log(status) });// stop polling latersub.unsubscribe();

🏗️ Architecture Overview

┌─────────────────────────────────────────────┐
│       @dooboostore/core Modules             │
├─────────────────────────────────────────────┤
│  Tier 1 — Foundation                         │
│  • strings, arrays, objects, dates, math     │
│  • convert, random, valid (type guards)      │
│  • Observable/Subject (25+ operators)        │
│  • Promises (sleep, settle, retry, loop)     │
├─────────────────────────────────────────────┤
│  Tier 2 — Domain-Specific                    │
│  • Validators (DOM-bound composable checks)  │
│  • HttpFetcher (config-object HTTP client)   │
│  • Entity (2D/3D geometry)                   │
│  • Runs (Runnable pattern)                   │
├─────────────────────────────────────────────┤
│  Tier 3 — Async Coordination                 │
│  • Transaction (cascaded catch/finally)      │
│  • Schedule (state/history around execute)   │
│  • Store (dynamic loading)                   │
├─────────────────────────────────────────────┤
│  Tier 4 — Specialized Utilities              │
│  • URL/CSS parsing, Logger, Storage          │
│  • Reflect, Function, Optional, Iterators    │
└─────────────────────────────────────────────┘

📊 Characteristics

AspectCharacteristic
Runtime dependenciesnone (only areflect-metadatapeer dependency)
Module formatsESM, CJS, and a UMD/ESM bundle (seebuild.mjstargets)
TypeScriptfull native support, strict typing
Import surfaceroot import only —.,./message/operators,./bundle-entryare the only definedexportsentries

🎓 Best Practices

1. Use type guards for runtime safety

import{ValidUtils}from'@dooboostore/core';functionprocessData(data:unknown) {if(ValidUtils.isObject(data) &&'id'indata) {// safe to access data.id}
}

2. Compose validators instead of hand-rolling checks

import{MultipleValidator,RegExpTestValidator}from'@dooboostore/core';constpasswordValidator =newMultipleValidator([newRegExpTestValidator(/.{8,}/),// at least 8 charactersnewRegExpTestValidator(/[A-Z]/),// at least one uppercasenewRegExpTestValidator(/[0-9]/)// at least one digit]);

3. Debounce UI-driven streams

searchInput$.pipe(debounceTime(500),switchMap(q=>searchApi(q))
).subscribe(results=>updateUI(results));

4. Always unsubscribe

constsubscription = observable.subscribe(...);
subscription.unsubscribe();

5. Only transactions yougetTransaction()participate incatch()/finally()

See theTransaction Coordinationsection —setTransactionalone does not wire a transaction into the manager's rollback cascade.


📚 Learn More

The detailed API documentation, including all modules and usage examples, is available on our documentation website.

License

This package is licensed under theMIT License.