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.
Observable,Subject,BehaviorSubject,ReplaySubject,ReplayForwardSubjectwith 25+ pipeable operators — without an RxJS dependencyPromises.sleep/settle/settles/retry/loop,Promises.Result.wrapstate tracking, concurrency-limited batch execution,AbortablePromiseValidatorclasses that read/write a DOM element'svalue/checkeddirectly, plus checkbox-group combinatorsPoint2D/Point3D,Rect,Polygon,Ellipse,Vector(p5.js-style), with containment/overlap checks and percent/ratio layout helpersFetcher/HttpFetcher/HttpJsonFetcher— a config-object request API with per-request callbacks and overridable lifecycle hooksArrayUtils,ObjectUtils,StringUtils,DateUtils,MathUtil,ConvertUtils,RandomUtilsScheduleBasetracks run history/state/counts around yourexecute()implementationTransactionManagercascadescatch()/finally()across registered transactionsreflect-metadatapeer dependency); ESM/CJS/UMD buildsInstall@dooboostore/coreusing your favorite package manager:
# pnpmpnpm add @dooboostore/core# npmnpm install @dooboostore/core# yarnyarn add @dooboostore/coreImport 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).
@dooboostore/coreis organized into30+ modules, all re-exported from the package root (src/index.ts).
| Module | Description | Key Exports |
|---|---|---|
| message | RxJS-like reactive system | Observable,Subject,BehaviorSubject,ReplaySubject,ReplayForwardSubject,Subscription,debounce/throttlehelpers |
| promise | Advanced async utilities | Promises,AbortablePromise |
| valid | Type guards | ValidUtils(isString,isNumber,isObject,isEmpty,isNullish, …) |
| array | Array manipulation | ArrayUtils(split,toShuffle,pick,has/hasAll,relation, …) |
| object | Object introspection & path access | ObjectUtils(keys,values,entries,deepCopy,ObjectUtils.Path.get/set, …) |
| string | String utilities | StringUtils(trim,lpad/rpad,ellipsis,appendPostposition, …) |
| date | Date/time operations | DateUtils(add,toSeconds/fromSeconds,relativeTime,formatDuration, …) |
| math | Mathematical operations | MathUtil(hypot,pythagorean,radians/degrees,bezier,rotatePoint, …) |
| convert | Type conversion | ConvertUtils(objToMap/mapToObj,toObject,toHexColor,escapeHTML, …) |
| random | Randomization | RandomUtils(int,float,uuid/uuid4,hex,rgb/rgba,alphabet) |
| Module | Description | Key Exports |
|---|---|---|
| validators | DOM-bound validation framework | Validator,FormValidator,RequiredValidator,RegExpTestValidator, checkbox-array combinators |
| fetch | HTTP client framework | HttpFetcher,HttpJsonFetcher,Fetcher(base, for non-HTTP transports) |
| entity | 2D/3D geometry | Point2D,Point3D,Polygon,Rect,Ellipse,Vector,Scalar |
| runs | Runnable pattern | Runnable<R, PR>,AroundRunnable,AutoStartRunner,RunnableLoader,StoreRunnableLoader |
| Module | Description | Key Exports |
|---|---|---|
| transaction | Transaction coordination | TransactionManager,Transactioninterface |
| schedule | Cron-shaped scheduling | ScheduleBase(tracks state/history/counts) |
| store | Store loader pattern | StoreLoader |
| Module | Description | Key Exports |
|---|---|---|
| url | URL utilities | UrlUtils |
| parser | Parsing utilities | CssParser |
| logger | Structured logging | Logger |
| storage | Storage abstraction | Storage,MemoryStorage,StorageUtils |
| reflect | Metadata reflection | ReflectUtils |
| function | Function utilities | FunctionUtils(dynamic script execution) |
| optional | Optional/Maybe monad | Optional<T>,OptionalUtils |
| iterators | Range iterators | Range |
| queues | Async queues | AsyncBlockingQueue<T> |
| advice | AOP-style exception handling | Advice |
| code | Code constants | Code<D>type, ISO3166-1 country codes (IOS3166_1_Codeenum,IOS3166_1_CodeDescription) |
| image | Image utilities | ImageUtils |
| expression | Expression evaluation | ActionExpression,Expression |
| hash | Hashing | HashUtils(hash53) |
| types | Shared type helpers (src/types.ts) | ClassType,ConstructorType,OptionalType,Mutable,MethodKeys, … |
The reactive messaging system provides an RxJS-like APIwithout the RxJS dependency.
// 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()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).
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');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 finishesimport{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();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) }
);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.
| Class | valid()semantics |
|---|---|
RequiredValidator | value is notundefined/null |
EmptyValidator/NotEmptyValidator | value is empty (null/undefined/length <= 0) / not empty |
RegExpTestValidator(regexp)/NotRegExpTestValidator(regexp) | regexp.test(value)/ its negation |
ValueEqualsValidator(equalsValue)/ValueNotEqualsValidator(equalsValue) | value === equalsValue/!== |
CheckedValidator/UnCheckedValidator | target.checkedistrue/false |
PassValidator/NonPassValidator | alwaystrue/ 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/AllUnCheckedValidatorArray | every item in the array is checked / unchecked |
CountEqualsCheckedValidatorArray(count),CountGreaterThanCheckedValidatorArray(count),CountGreaterThanEqualsCheckedValidatorArray(count),CountLessThanCheckedValidatorArray(count),CountLessThanEqualsCheckedValidatorArray(count),CountUnCheckedValidatorArray(count), and the matching*UnCheckedValidatorArrayset | the 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 |
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);
}// 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}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!');
}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).
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}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);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.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.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.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.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.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.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)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)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>();
}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);
}
});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');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();
}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();┌─────────────────────────────────────────────┐
│ @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 │
└─────────────────────────────────────────────┘| Aspect | Characteristic |
|---|---|
| Runtime dependencies | none (only areflect-metadatapeer dependency) |
| Module formats | ESM, CJS, and a UMD/ESM bundle (seebuild.mjstargets) |
| TypeScript | full native support, strict typing |
| Import surface | root import only —.,./message/operators,./bundle-entryare the only definedexportsentries |
import{ValidUtils}from'@dooboostore/core';functionprocessData(data:unknown) {if(ValidUtils.isObject(data) &&'id'indata) {// safe to access data.id}
}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]);searchInput$.pipe(debounceTime(500),switchMap(q=>searchApi(q))
).subscribe(results=>updateUI(results));constsubscription = observable.subscribe(...);
subscription.unsubscribe();getTransaction()participate incatch()/finally()See theTransaction Coordinationsection —setTransactionalone does not wire a transaction into the manager's rollback cascade.
The detailed API documentation, including all modules and usage examples, is available on our documentation website.
This package is licensed under theMIT License.