Traits for TypeScript Classes: Standard Library
npm install @traits-ts/stdlib@traits-ts/stdlib
=================
Traits for TypeScript Classes (Standard Library)





About
-----
This is a TypeScript library providing a set of standard,
reusable, generic, typed traits (aka mixins), based on the
@traits-ts/core library.
Currently,
this standard library consists of the reusable traits Identifiable,
Configurable, Bindable, Subscribable, Hookable, Finalizable,
Traceable, and Serializable. All traits try to avoid any namespace
conflicts with application code by prefixing their exposed functionality
with the name prefix $.
See also @traits-ts/core for
more details on the underlying core traits mechanism.
Installation
------------
``sh`
$ npm install --save @traits-ts/core @traits-ts/stdlib
Trait: Identifiable
---------------------
The reusable generic trait Identifiable allows you to attach a
Universally Unique Identifier (UUID, version 1) to the objects of a
target class. You can then retrieve the UUID with the (read-only) $id
property. Apply an Identifiable for unique identification.
`ts
import { derive } from "@traits-ts/core"
import { Identifiable } from "@traits-ts/stdlib"
class Sample extends derive(Identifiable) {}
const sample = new Sample()
sample.$id // -> e.g. cbd8f4a0-f115-11ef-8f81-38f9d30d57c1
`
Trait: Configurable
---------------------
The reusable generic trait Configurable allows you to attach a fully
typed and mergable configuration T of Configurable to the objects$configuration
of a target class. You have to initialize the configuration through
property and later you can retrieve the current$configure
configuration through it again. You can change the configuration with
the method by passing any subset (aka a "deep partial") ofT and this way "merge" your changes into the configuration. Apply a
Configurable for incrementally changing a configuration.
`ts
import { derive } from "@traits-ts/core"
import { Configurable } from "@traits-ts/stdlib"
type Config = {
foo: number,
bar: string
baz: {
quux: boolean
}
}
class Sample extends derive(Configurable
$configuration = {
foo: 42,
bar: "bar",
baz: {
quux: true
}
}
}
const sample = new Sample()
sample.$configuration // -> { foo: 42, bar: "bar", baz: { quux: true } }
sample.$configure({ bar: "baz", baz: { quux: false } })
sample.$configuration // -> { foo: 42, bar: "baz", baz: { quux: false } }
`
Trait: Bindable
-----------------
The reusable generic trait Bindable allows you to bind to properties
of a target class. The properties have to be defined in an interface
T of Bindable and defined as @bindable accessor on the target$bind
class. You can then bind to those properties with method and
observe all changes to the property. Apply a Bindable for allowing
the external observing and reacting to property changes.
`ts
import { derive } from "@traits-ts/core"
import { Bindable, bindable } from "@traits-ts/stdlib"
interface Props { foo: number, bar: string }
class Sample extends derive(Bindable
@bindable accessor foo = 42
@bindable accessor bar = "bar"
}
const sample = new Sample()
const b1 = sample.$bind("foo", (val, old) => { console.log("foo:", val, old) })
const b2 = sample.$bind("bar", (val, old) => { console.log("bar:", val, old) })
sample.foo += 1 // -> foo: 43 42
sample.bar = "baz" // -> bar: baz bar
b1.unbind()
b2.unbind()
`
Trait: Subscribable
---------------------
The reusable generic trait Subscribable allows you to subscribe to
typed events emitted on a target class. The events have to be defined
in an interface T of Subscribable. You can then subscribe to$subscribe
those events with method and emit those events with method$emit. Apply a Subscribable for allowing the external observing and
reacting to logical events.
`ts
import { derive } from "@traits-ts/core"
import { Subscribable } from "@traits-ts/stdlib"
interface Events { "foo": number, "bar": string }
class Sample extends derive(Subscribable
const sample = new Sample()
const s1 = sample.$subscribe("foo", { limit: 1 }, (val) => {
console.log("foo:", val)
})
const s2 = sample.$subscribe("bar", (val) => {
console.log("bar:", val)
})
sample.$emit("foo", 42) // -> foo: 42
sample.$emit("foo", 7) // -> (none)
sample.$emit("bar", "quux") // -> bar: quux
sample.$emit("bar", "baz") // -> bar: baz
s1.unsubscribe()
s2.unsubscribe()
`
Trait: Hookable
-----------------
The reusable generic trait Hookable allows you to attach hooks to
a target class. The hooks have to be defined in an interface T ofHookable. You can then latch into those hooks with method $latch$hook
and call those hooks with method . Apply a Hookable for
allowing the external extension of functionality.
`ts
import { derive } from "@traits-ts/core"
import { Hookable } from "@traits-ts/stdlib"
interface Hooks {
"foo": { arg: string },
"bar": { arg: number },
"quux": undefined
}
class Sample extends derive(Hookable
const sample = new Sample()
const l1 = sample.$latch("foo", { limit: 2 }, async (h, data) => {
console.log("foo:", data)
return Hookable.CONTINUE
})
const l2 = sample.$latch("bar", { pos: "late" }, async (h, data) => {
console.log("bar: start:", data)
return new Promise((resolve) => {
console.log("bar: end:", data)
resolve(Hookable.FINISH)
})
})
sample.$hook("foo", { arg: "foo1" }) // -> foo: { arg: foo1 }
sample.$hook("foo", { arg: "foo2" }) // -> foo: { arg: foo2 }
sample.$hook("foo", { arg: "foo3" }) // -> (none)
sample.$hook("bar", { arg: 42 }) // -> bar: start: { arg: 42 }, bar: end: { arg: 42 }
l1.unlatch()
l2.unlatch()
l3.unlatch()
`
Trait: Finalizable
-------------------
The reusable generic trait Finalizable allows you to potentially react when a
target class is finalized (disposed by garbage collection). You can observe the
finalization event by overriding method $finalize. Apply a Finalizable
for allowing you to take optional action before garbage collection.
NOTICE: internally, the standard mechanism FinalizationRegistry is$finalize
used and there is no guarantee that the method is called soon$finalize
after garbage collection of the target class or even called at all. Hence,
use the method just for fully optional memory deallocation
tasks only, please.
`ts
import { derive } from "@traits-ts/core"
import { Finalizable } from "@traits-ts/stdlib"
class Sample extends derive(Finalizable) {
$finalize () {
console.log("finalized")
}
}
const sample = new Sample()
`
Trait: Traceable
------------------
The reusable generic trait Traceable allows you to perform
simple log-level based logging from within a target class. The
available log-levels have to be defined in a string-union type T ofTraceable and the corresponding string-array property $logLevels$logLevel
on the target class. The current log-level has to be defined with
the string property . The generated output is by default$logOutput
suppressed, but can be output by defining the function property. The log entries can be generated by the method $log.
Apply a Traceable for allowing a target class to perform simple
logging.
`ts
import { derive } from "@traits-ts/core"
import { Traceable } from "@traits-ts/stdlib"
type LogLevelsType = "ERROR" | "WARNING" | "INFO"
const LogLevelsDefine = [ "ERROR", "WARNING", "INFO" ] satisfies LogLevelsType[]
class Sample extends derive(Traceable
$logLevels = LogLevelsDefine
$logLevel = "INFO" as LogLevelsType
$logOutput = (line: string) => { console.log(line) }
action () {
this.$log("INFO", "sample", { foo: "bar", baz: 42 })
}
}
const sample = new Sample()
sample.action() // ->
`
Trait: Serializable
---------------------
The reusable generic trait Serializable allows you to export and
import the state of a target class. For this, the target class and its
state properties have to be decorated with @serializable. The class$serialize
and the graph of its dependencies then can be serialized/exported
with method . The serialized/exported state can be$unserialize
deserialized/imported again with the static function .
Apply a Serializable for allowing a target class and its dependencies
to be exported and imported.
`ts
import { derive } from "@traits-ts/core"
import { Serializable, serializable } from "@traits-ts/stdlib"
@serializable
class Sample extends derive(Serializable) {
@serializable foo = true
@serializable bar = 42
@serializable baz = new Set
@serializable sub?: Sample = undefined
constructor (sub?: Sample) {
super()
if (sub) {
this.sub = sub
this.baz.add(sub)
}
}
}
const sample1 = new Sample()
const sample2 = new Sample(sample1)
const state = sample2.serialize() // -> { ... }
const sample1New = Sample.$unserialize(state)
const sample2New = sample1New.sub
``
History
-------
The @traits-ts/stdlib library was developed in February 2025 by Dr.
Ralf S. Engelschall. It is heavily inspired by the necessary base class
functionalities which Dr. Ralf S. Engelschall had to develop over the
years in various projects.
Support
-------
The work on this Open Source Software was financially supported by the
german non-profit organisation SEA Software Engineering Academy gGmbH.
License
-------
Copyright © 2025 Dr. Ralf S. Engelschall (http://engelschall.com/)
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.