{"_id":"@active-record-ts/active-record","_rev":"2-470913ed6be257fa20581b39481f564b","name":"@active-record-ts/active-record","dist-tags":{"latest":"1.1.0"},"versions":{"1.0.0":{"name":"@active-record-ts/active-record","version":"1.0.0","author":"Alexander Stathis <stathis.alexanderj@gmail.com> (github.com/stathis-alexander)","license":"MIT","_id":"@active-record-ts/active-record@1.0.0","maintainers":[{"name":"astathis","email":"stathis.alexanderj@gmail.com"}],"homepage":"https://github.com/stathis-alexander/active-record-ts/tree/main/packages/active-record#readme","bugs":"https://github.com/stathis-alexander/active-record-ts/issues","bin":{"active-record":"bin/active-record.ts"},"dist":{"shasum":"9ed41c3ed2d2d93f35de50e69f7a7c4ac1dbe60c","tarball":"https://registry.npmjs.org/@active-record-ts/active-record/-/active-record-1.0.0.tgz","fileCount":33,"integrity":"sha512-OzWDq+mfrzzCiGhd9hAWw6GMDTvXpMVcehleqNXWv2QNwlWoSeHO7B5/xZOAKGBG/nacB7Yq7GYMO2GxMECboQ==","signatures":[{"sig":"MEUCIQCDd9YxWwRnTxpi+LwQavtuwS4lGpZbtn2xZLgJsN0LJAIgUpMzBiC6VDKXszAnIA2Qwyc9PXJ1Y1xJcygkJy/u39I=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":243470},"type":"module","module":"src/index.ts","shasum":"9ed41c3ed2d2d93f35de50e69f7a7c4ac1dbe60c","exports":{".":{"types":"./src/index.ts","import":"./src/index.ts","default":"./src/index.ts"}},"scripts":{"test":"bun test","db:drop":"bun bin/active-record.ts db:drop","db:create":"bun bin/active-record.ts db:create","test:unit":"bun test test/unit","typecheck":"bun tsc --noEmit","db:migrate":"bun bin/active-record.ts db:migrate","db:rollback":"bun bin/active-record.ts db:rollback","db:schema:dump":"bun bin/active-record.ts db:schema:dump","db:schema:load":"bun bin/active-record.ts db:schema:load","test:integration":"bun test test/integration"},"_npmUser":{"name":"astathis","email":"stathis.alexanderj@gmail.com"},"_integrity":"sha512-OzWDq+mfrzzCiGhd9hAWw6GMDTvXpMVcehleqNXWv2QNwlWoSeHO7B5/xZOAKGBG/nacB7Yq7GYMO2GxMECboQ==","repository":{"url":"git+https://github.com/stathis-alexander/active-record-ts.git","type":"git","directory":"packages/active-record"},"_npmVersion":"10.8.3","description":"Rails style ActiveRecord in TypeScript","directories":{},"_nodeVersion":"24.3.0","dependencies":{"@active-record-ts/arel":"1.0.0","@active-record-ts/active-model":"1.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"peerDependencies":{"typescript":"^5"},"optionalDependencies":{"mysql2":"^3.11.0","postgres":"^3.4.4"},"_npmOperationalInternal":{"tmp":"tmp/active-record_1.0.0_1779230263855_0.1560001074030235","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@active-record-ts/active-record","version":"1.1.0","description":"Rails style ActiveRecord in TypeScript","author":{"name":"Alexander Stathis","email":"stathis.alexanderj@gmail.com","url":"github.com/stathis-alexander"},"license":"MIT","type":"module","main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","bin":{"active-record":"dist/cli.js"},"repository":{"type":"git","url":"git+https://github.com/stathis-alexander/active-record-ts.git","directory":"packages/active-record"},"bugs":{"url":"https://github.com/stathis-alexander/active-record-ts/issues"},"homepage":"https://github.com/stathis-alexander/active-record-ts/tree/main/packages/active-record#readme","publishConfig":{"access":"public","provenance":true},"exports":{".":{"bun":"./src/index.ts","types":"./dist/index.d.ts","import":"./dist/index.js"}},"dependencies":{"@active-record-ts/active-model":"workspace:*","@active-record-ts/arel":"workspace:*"},"optionalDependencies":{"mysql2":"^3.11.0","postgres":"^3.4.4"},"peerDependencies":{"typescript":"^5"},"scripts":{"build":"tsup","prepack":"tsup","test":"bun test","test:unit":"bun test test/unit","test:integration":"bun test test/integration","typecheck":"bun tsc --noEmit","db:create":"bun bin/active-record.ts db:create","db:drop":"bun bin/active-record.ts db:drop","db:migrate":"bun bin/active-record.ts db:migrate","db:rollback":"bun bin/active-record.ts db:rollback","db:schema:dump":"bun bin/active-record.ts db:schema:dump","db:schema:load":"bun bin/active-record.ts db:schema:load"},"gitHead":"395b20f425235a16b0bf0319b77e45b31db1c1e1","_id":"@active-record-ts/active-record@1.1.0","_nodeVersion":"24.15.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-V5ldY+cQgRlynyGQYCz6lkdMv9xPEhBxmKK3OLbVqf/dG8d8RpD6+U3xHnxrvE+rmAypDM/O81HhbpAOSL6Nfw==","shasum":"281d780be27610a1357d89643e3b999cb03ac79b","tarball":"https://registry.npmjs.org/@active-record-ts/active-record/-/active-record-1.1.0.tgz","fileCount":22,"unpackedSize":568516,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@active-record-ts%2factive-record@1.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQC3Kjk1A5dQWullvHUQkob6epzUHjtjL3d3CxzRoa0ToQIhAOIy+SaZqyHWyIqkHAHzSGExc6cstUAVrtt0BI4cbq36"}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:d0e6aba6-ff58-4359-b1f9-adc9b6ea243a"}},"directories":{},"maintainers":[{"name":"astathis","email":"stathis.alexanderj@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/active-record_1.1.0_1779234788357_0.5346642921587841"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-19T22:37:43.717Z","modified":"2026-05-19T23:53:09.198Z","1.0.0":"2026-05-19T22:37:44.076Z","1.1.0":"2026-05-19T23:53:08.499Z"},"bugs":{"url":"https://github.com/stathis-alexander/active-record-ts/issues"},"author":{"name":"Alexander Stathis","email":"stathis.alexanderj@gmail.com","url":"github.com/stathis-alexander"},"license":"MIT","homepage":"https://github.com/stathis-alexander/active-record-ts/tree/main/packages/active-record#readme","repository":{"type":"git","url":"git+https://github.com/stathis-alexander/active-record-ts.git","directory":"packages/active-record"},"description":"Rails style ActiveRecord in TypeScript","maintainers":[{"name":"astathis","email":"stathis.alexanderj@gmail.com"}],"readme":"# `@active-record-ts/active-record`\n\nA TypeScript port of Rails' [`ActiveRecord`](https://github.com/rails/rails/tree/main/activerecord). Subclass `Base`, point it at a database, get a chainable `Relation` API plus persistence, callbacks, associations, and migrations.\n\nBuilt on [`@active-record-ts/arel`](../arel) for SQL generation and [`@active-record-ts/active-model`](../active-model) for attributes, dirty tracking, validations, and callbacks.\n\n## Install\n\n```bash\nbun add @active-record-ts/active-record\n# pick a driver:\nbun add postgres        # postgres\nbun add mysql2          # mysql\n# sqlite uses bun:sqlite (built-in)\n```\n\n## Quickstart\n\n```ts\nimport { Base } from '@active-record-ts/active-record';\n\nclass User extends Base {\n  static override tableName = 'users';\n  declare id: number;\n  declare name: string;\n  declare email: string;\n  declare age: number;\n}\n\nawait User.establishConnection({ adapter: 'sqlite', database: ':memory:' });\nawait User.loadSchema();  // introspect columns -> attributes\n\nawait User.create({ name: 'Alex', email: 'a@b.c', age: 30 });\nconst u = await User.find(1);\nu.name = 'Sandy';\nawait u.save();\n```\n\n## Connection adapters\n\n```ts\n// SQLite (built into Bun)\nawait Model.establishConnection({ adapter: 'sqlite', database: ':memory:' });\n\n// Postgres (via node-postgres-compatible `postgres` package)\nawait Model.establishConnection({ adapter: 'postgres', url: process.env.DATABASE_URL });\n\n// MySQL (via mysql2)\nawait Model.establishConnection({ adapter: 'mysql', url: process.env.DATABASE_URL });\n```\n\nMulti-role / multi-database via `connectsTo` + `connectedTo`:\n\n```ts\nawait AppRecord.connectsTo({\n  writing: { adapter: 'postgres', url: PRIMARY_URL },\n  reading: { adapter: 'postgres', url: REPLICA_URL },\n});\n\nawait AppRecord.connectedTo({ role: 'reading' }, async () => {\n  return User.where({ active: true }).count();\n});\n```\n\n## Features\n\n### CRUD\n\n```ts\nconst u  = await User.create({ name: 'Alex' });\nconst u2 = await User.find(1);                   // throws RecordNotFound on miss\nconst u3 = await User.findBy({ email: 'a@b.c' }); // null on miss\nawait u.update({ name: 'Sandy' });\nawait u.destroy();\n```\n\n### Relations\n\n`where`, `order`, `limit`, `offset`, `select`, `joins`, `includes`, `group`, `having`, `distinct`, `none`, `readonly` — all chainable, all return a `Relation`. `await` a relation directly to execute it.\n\n```ts\nconst adults = await User\n  .where({ active: true })\n  .where({ age: [21, 22, 23] })   // IN\n  .order({ age: 'desc' })\n  .limit(10);\n\nawait User.where({ age: 30 }).count();\nawait User.exists({ email: 'a@b.c' });\nawait User.order({ age: 'asc' }).pluck<string>('name');\n```\n\n### Bulk operations\n\n```ts\nawait User.updateAll({ active: false });           // UPDATE users SET active = 0\nawait User.where({ age: 0 }).deleteAll();          // DELETE without callbacks\nawait User.where({ flagged: true }).destroyAll();  // instantiate + run callbacks\n```\n\n### Validations\n\n```ts\nclass User extends Base {\n  static override tableName = 'users';\n  declare name: string;\n  declare email: string;\n}\n\nUser.validates('name', { presence: true, length: { minimum: 2 } });\nUser.validates('email', { presence: true, format: { with: /@/ } });\n\nconst u = new User({ name: '' });\nawait u.save();                  // false\nu.errors.fullMessages;           // [\"Name can't be blank\", ...]\n```\n\n### Callbacks\n\n```ts\nUser.beforeSave((u) => { u.email = u.email.toLowerCase(); });\nUser.afterCreate(async (u) => { await sendWelcomeEmail(u.email); });\nUser.beforeDestroy(async (u) => {\n  if (u.protected) throw new Error('cannot destroy');\n});\n```\n\nAvailable: `beforeValidation`, `afterValidation`, `beforeSave`, `afterSave`, `aroundSave`, `beforeCreate`, `afterCreate`, `beforeUpdate`, `afterUpdate`, `beforeDestroy`, `afterDestroy`.\n\n### Dirty tracking\n\n```ts\nconst u = await User.find(1);\nu.name = 'Sandy';\nu.changed();          // ['name']\nu.changes();          // { name: ['Alex', 'Sandy'] }\nawait u.save();\nu.savedChanges();     // { name: ['Alex', 'Sandy'] }\nawait u.reload();     // restore from DB\n```\n\n### Associations\n\n```ts\nclass User extends Base { static override tableName = 'users'; }\nclass Post extends Base { static override tableName = 'posts'; }\n\nPost.belongsTo('user',  { class: () => User });\nUser.hasMany('posts',   { class: () => Post, dependent: 'destroy' });\nUser.hasOne('profile',  { class: () => Profile });\n\nconst post = await Post.find(1);\nconst author = await post.user;          // User | null\nconst posts  = await user.posts.where({ published: true }).order({ created_at: 'desc' });\n```\n\n`through:` and polymorphic associations are supported:\n\n```ts\nUser.hasMany('memberships', { class: () => Membership });\nUser.hasMany('teams', { through: 'memberships' });\n\nComment.belongsTo('commentable', { polymorphic: true });\n```\n\n### Transactions\n\n```ts\nawait User.transaction(async () => {\n  await User.create({ name: 'Alex' });\n  await User.create({ name: 'Sandy' });\n  // throw -> rollback. Throw `Rollback` to roll back silently.\n});\n\n// With isolation level:\nawait User.transaction(async () => { /* ... */ }, { isolation: 'serializable' });\n```\n\n### Migrations\n\n```ts\nimport { Migration, Migrator } from '@active-record-ts/active-record';\n\nclass CreateUsers extends Migration {\n  static override version = '20260101000001';\n  override async up() {\n    await this.createTable('users', (t) => {\n      t.string('name', { null: false });\n      t.string('email', { index: { unique: true } });\n      t.timestamps();\n    });\n  }\n  override async down() {\n    await this.dropTable('users');\n  }\n}\n\nconst migrator = new Migrator(adapter, [CreateUsers]);\nawait migrator.up();        // apply pending\nawait migrator.rollback();  // unwind last\n```\n\nSchema DSL: `createTable`, `dropTable`, `addColumn`, `removeColumn`, `addIndex`, `addForeignKey`, plus column-type shorthands (`t.string`, `t.integer`, `t.boolean`, `t.timestamps`, etc.).\n\n### Read-only relations\n\n```ts\nconst u = await User.readonly().find(1);\nawait u.save();  // throws ReadOnlyRecord\n```\n\n## Test\n\n```bash\nbun run test:active-record\nbun run db:up    # start Postgres + MySQL for integration tests\n```\n","readmeFilename":"README.md"}