
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
ts-tiny-activerecord
Advanced tools
A type-safe model persistence library for TypeScript that provides a flexible way to manage data models with database persistence.
npm install [TBD]
Start by creating a type (not an interface) that describes the fields of your model. Then, define your model by extending the base Model class and using the @Persistence decorator, passing in an adapter:
type PersonAttrs = {
id?: string;
firstName: string;
lastName: string;
age: number;
}
@Persistence<PersonAttrs>(createSomeAdapter(...))
class Person extends Model<PersonAttrs> {
public fullName() {
return `${this.get("firstName")} ${this.get("lastName")}`;
}
}
// Create a new person
const person = new Person({
firstName: "John",
lastName: "Doe",
age: 30
});
// Save to database
await person.save();
// Update fields
person.set("firstName", "Jane");
await person.save();
// Bulk update fields
person.set({
firstName: "Jane",
lastName: "Smith"
});
await person.save();
// Get a single field
const firstName = person.get("firstName");
// Set a single field (marks as changed)
person.set("firstName", "Jane");
// Set multiple fields (marks all as changed)
person.set({
firstName: "Jane",
lastName: "Smith"
});
// Set fields without marking as changed
person.put("firstName", "Jane");
person.put({
firstName: "Jane",
lastName: "Smith"
});
// Load by primary key
const person = await Person.get("some-id");
// Find first match by criteria
const jane = await Person.getBy({ firstName: "Jane" });
// Get all records
const allPeople = await Person.all();
// Get all matching criteria
const adults = await Person.all({ age: 18 });
// Get all with SQL query (if adapter supports it)
const adults = await Person.all(
"SELECT * FROM people WHERE age > ?", [18]
);
// Delete a model
const success = await person.del();
// Check if model is persisted
console.log(person.persisted);
// Get array of changed field names
const changes = person.getChangedFields();
// Manually mark fields as changed/unchanged
person.markChanged("firstName");
person.markUnchanged("firstName");
// Clear all change tracking
person.clearChangedFields();
The library is built with TypeScript type safety in mind. Model fields are strictly typed based on the interface you provide.
// This will cause a TypeScript error
person.get("nonexistentField");
// This will also cause a TypeScript error
person.set("age", "thirty");
You can control how fields are persisted passing field specifications as the second argument to the @Persistence decorator:
@Persistence<MyAttrs>(adapter, {
secretField: { persist: false },
jsonField: {
encoder: {
encode: (value: object) => JSON.stringify(value),
decode: (value: string) => JSON.parse(value)
}
}
})
class AdvancedModel extends Model<MyAttrs> {
// ...
}
Add global hooks for pre/post save and post load operations by passing a third argument to the @Persistence decorator:
@Persistence(adapter, fieldSpecs, {
preSave: async (context, model, type) => {
// Modify model before saving
// type will be "insert" or "update"
},
postSave: async (context, model, type) => {
// Process model after saving
// type will be "insert" or "update"
},
postLoad: async (context, model) => {
// Process model after loading
},
postDelete: async (context, model) => {
// Process model after deleting
}
})
Note the preSave hook will not be called if no fields are changed. It is possible to modify the model in the hook to add additional fields to the save operation.
Create custom adapters for different databases by implementing the AdapterConfig interface:
interface AdapterConfig<C, T> {
// Get the name of the primary key field
getPrimaryKeyField(): string;
// Get the database context
getContext(): Promise<C>;
// Query methods
get(context: C, id: any): Promise<T | null>;
getBy(context: C, matchOrQuery: Partial<T> | string, bindValues?: any[]): Promise<T | null>;
all(context: C, matchOrQuery?: Partial<T> | string, bindValues?: any[]): Promise<T[]>;
// Persistence methods
insert(context: C, data: Partial<T>): Promise<SaveResult>;
update(context: C, model: Model<T>, data: Partial<T>): Promise<SaveResult>;
del(context: C, model: Model<T>): Promise<boolean>;
}
// The SaveResult interface for insert/update operations
interface SaveResult {
success: boolean;
inserted: boolean;
id?: any;
rows: number;
}
Each adapter method serves a specific purpose:
getPrimaryKeyField(): Returns the name of the primary key fieldgetContext(): Establishes the database connection or contextget(): Retrieves a single record by IDgetBy(): Retrieves first record matching criteria or SQL queryall(): Retrieves all records, optionally filtered by criteria or SQL queryinsert(): Creates a new recordupdate(): Updates an existing recorddel(): Deletes a recordThe SaveResult interface provides detailed information about save operations:
success: Whether the operation succeededinserted: Whether a new record was insertedid: The ID of the newly inserted record (for inserts)rows: Number of rows affectedFAQs
A tiny Active Record implementation for TypeScript
We found that ts-tiny-activerecord demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.