or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

associations.mddata-types.mddatabase-connection.mderror-handling.mdhooks.mdindex.mdmodel-definition.mdquery-operators.mdquerying.mdtransactions.md

associations.mddocs/

0

# Associations

1

2

Model relationship definitions and operations including one-to-one, one-to-many, and many-to-many associations with eager loading.

3

4

## Capabilities

5

6

### BelongsTo Association

7

8

Defines a belongs-to relationship where the current model has a foreign key pointing to the target model.

9

10

```typescript { .api }

11

/**

12

* Define belongs-to association

13

* @param target - Target model class

14

* @param options - Association options

15

* @returns BelongsTo association instance

16

*/

17

static belongsTo(target: ModelCtor, options?: BelongsToOptions): BelongsTo;

18

19

interface BelongsToOptions {

20

/** Foreign key column name */

21

foreignKey?: string | ForeignKeyOptions;

22

/** Target key (usually primary key) */

23

targetKey?: string;

24

/** Association alias */

25

as?: string;

26

/** Association scope */

27

scope?: AssociationScope;

28

/** Include hooks */

29

hooks?: boolean;

30

/** On update action */

31

onUpdate?: string;

32

/** On delete action */

33

onDelete?: string;

34

/** Constraint options */

35

constraints?: boolean;

36

}

37

38

interface ForeignKeyOptions {

39

/** Column name */

40

name?: string;

41

/** Allow null values */

42

allowNull?: boolean;

43

}

44

```

45

46

**Usage Examples:**

47

48

```typescript

49

// Basic belongs-to

50

class Post extends Model {}

51

class User extends Model {}

52

53

Post.belongsTo(User); // Creates userId foreign key on Post

54

55

// With custom foreign key

56

Post.belongsTo(User, {

57

foreignKey: 'authorId',

58

as: 'author'

59

});

60

61

// With constraints

62

Post.belongsTo(User, {

63

foreignKey: {

64

name: 'userId',

65

allowNull: false

66

},

67

onDelete: 'CASCADE',

68

onUpdate: 'CASCADE'

69

});

70

71

// Usage in queries

72

const post = await Post.findOne({

73

include: [{ model: User, as: 'author' }]

74

});

75

76

// Instance methods (automatically created)

77

const post = await Post.findByPk(1);

78

const user = await post.getUser(); // or getAuthor() if aliased

79

await post.setUser(newUser);

80

const newUser = await post.createUser({ name: 'John' });

81

```

82

83

### HasOne Association

84

85

Defines a has-one relationship where the target model has a foreign key pointing to the current model.

86

87

```typescript { .api }

88

/**

89

* Define has-one association

90

* @param target - Target model class

91

* @param options - Association options

92

* @returns HasOne association instance

93

*/

94

static hasOne(target: ModelCtor, options?: HasOneOptions): HasOne;

95

96

interface HasOneOptions {

97

/** Foreign key column name on target */

98

foreignKey?: string | ForeignKeyOptions;

99

/** Source key (usually primary key) */

100

sourceKey?: string;

101

/** Association alias */

102

as?: string;

103

/** Association scope */

104

scope?: AssociationScope;

105

/** Include hooks */

106

hooks?: boolean;

107

/** On update action */

108

onUpdate?: string;

109

/** On delete action */

110

onDelete?: string;

111

/** Constraint options */

112

constraints?: boolean;

113

}

114

```

115

116

**Usage Examples:**

117

118

```typescript

119

// Basic has-one

120

class User extends Model {}

121

class Profile extends Model {}

122

123

User.hasOne(Profile); // Creates userId foreign key on Profile

124

125

// With options

126

User.hasOne(Profile, {

127

foreignKey: 'ownerId',

128

as: 'userProfile',

129

onDelete: 'CASCADE'

130

});

131

132

// Usage in queries

133

const user = await User.findOne({

134

include: [Profile] // or { model: Profile, as: 'userProfile' }

135

});

136

137

// Instance methods

138

const user = await User.findByPk(1);

139

const profile = await user.getProfile();

140

await user.setProfile(newProfile);

141

const profile = await user.createProfile({ bio: 'Hello' });

142

```

143

144

### HasMany Association

145

146

Defines a has-many relationship where the target model has a foreign key pointing to the current model.

147

148

```typescript { .api }

149

/**

150

* Define has-many association

151

* @param target - Target model class

152

* @param options - Association options

153

* @returns HasMany association instance

154

*/

155

static hasMany(target: ModelCtor, options?: HasManyOptions): HasMany;

156

157

interface HasManyOptions {

158

/** Foreign key column name on target */

159

foreignKey?: string | ForeignKeyOptions;

160

/** Source key (usually primary key) */

161

sourceKey?: string;

162

/** Association alias */

163

as?: string;

164

/** Association scope */

165

scope?: AssociationScope;

166

/** Include hooks */

167

hooks?: boolean;

168

/** On update action */

169

onUpdate?: string;

170

/** On delete action */

171

onDelete?: string;

172

/** Constraint options */

173

constraints?: boolean;

174

}

175

```

176

177

**Usage Examples:**

178

179

```typescript

180

// Basic has-many

181

class User extends Model {}

182

class Post extends Model {}

183

184

User.hasMany(Post); // Creates userId foreign key on Post

185

186

// With options

187

User.hasMany(Post, {

188

foreignKey: 'authorId',

189

as: 'articles',

190

scope: { status: 'published' }

191

});

192

193

// Usage in queries

194

const user = await User.findOne({

195

include: [{

196

model: Post,

197

as: 'articles',

198

where: { status: 'published' }

199

}]

200

});

201

202

// Instance methods

203

const user = await User.findByPk(1);

204

const posts = await user.getPosts(); // or getArticles()

205

await user.setPosts([post1, post2]);

206

await user.addPost(newPost);

207

await user.addPosts([post1, post2]);

208

await user.removePost(post);

209

await user.removePosts([post1, post2]);

210

const newPost = await user.createPost({ title: 'Hello' });

211

const hasPost = await user.hasPost(post);

212

const postCount = await user.countPosts();

213

```

214

215

### BelongsToMany Association

216

217

Defines a many-to-many relationship through a junction table.

218

219

```typescript { .api }

220

/**

221

* Define belongs-to-many association

222

* @param target - Target model class

223

* @param options - Association options

224

* @returns BelongsToMany association instance

225

*/

226

static belongsToMany(target: ModelCtor, options: BelongsToManyOptions): BelongsToMany;

227

228

interface BelongsToManyOptions {

229

/** Junction table name or model */

230

through: string | ModelCtor | ThroughOptions;

231

/** Foreign key for source model */

232

foreignKey?: string | ForeignKeyOptions;

233

/** Foreign key for target model */

234

otherKey?: string | ForeignKeyOptions;

235

/** Source key (usually primary key) */

236

sourceKey?: string;

237

/** Target key (usually primary key) */

238

targetKey?: string;

239

/** Association alias */

240

as?: string;

241

/** Association scope */

242

scope?: AssociationScope;

243

/** Include hooks */

244

hooks?: boolean;

245

/** Constraint options */

246

constraints?: boolean;

247

}

248

249

interface ThroughOptions {

250

/** Junction model */

251

model: ModelCtor;

252

/** Unique constraint on junction */

253

unique?: boolean;

254

/** Additional attributes on junction */

255

scope?: any;

256

}

257

```

258

259

**Usage Examples:**

260

261

```typescript

262

// Basic many-to-many

263

class User extends Model {}

264

class Role extends Model {}

265

266

User.belongsToMany(Role, { through: 'UserRoles' });

267

Role.belongsToMany(User, { through: 'UserRoles' });

268

269

// With custom keys

270

User.belongsToMany(Role, {

271

through: 'UserRoles',

272

foreignKey: 'userId',

273

otherKey: 'roleId',

274

as: 'permissions'

275

});

276

277

// With junction model

278

class UserRole extends Model {}

279

UserRole.init({

280

userId: DataTypes.INTEGER,

281

roleId: DataTypes.INTEGER,

282

assignedAt: DataTypes.DATE,

283

assignedBy: DataTypes.INTEGER

284

});

285

286

User.belongsToMany(Role, {

287

through: UserRole,

288

foreignKey: 'userId',

289

otherKey: 'roleId'

290

});

291

292

// Usage in queries

293

const user = await User.findOne({

294

include: [{

295

model: Role,

296

through: { attributes: ['assignedAt'] } // Include junction attributes

297

}]

298

});

299

300

// Instance methods

301

const user = await User.findByPk(1);

302

const roles = await user.getRoles();

303

await user.setRoles([role1, role2]);

304

await user.addRole(newRole);

305

await user.addRoles([role1, role2]);

306

await user.removeRole(role);

307

await user.removeRoles([role1, role2]);

308

309

// With junction attributes

310

await user.addRole(role, { through: { assignedBy: adminId } });

311

```

312

313

### Eager Loading

314

315

Loading associated models along with the main query.

316

317

```typescript { .api }

318

interface IncludeOptions {

319

/** Model to include */

320

model: ModelCtor;

321

/** Association alias */

322

as?: string;

323

/** Include attributes */

324

attributes?: FindAttributeOptions;

325

/** WHERE clause for included model */

326

where?: WhereOptions;

327

/** Required join (INNER vs LEFT JOIN) */

328

required?: boolean;

329

/** Include paranoid records */

330

paranoid?: boolean;

331

/** Nested includes */

332

include?: IncludeOptions | IncludeOptions[];

333

/** Separate queries for has-many */

334

separate?: boolean;

335

/** Limit included records */

336

limit?: number;

337

/** Through table options (for belongsToMany) */

338

through?: ThroughOptions;

339

/** Duplicate records handling */

340

duplicating?: boolean;

341

}

342

343

type Includeable = ModelCtor | IncludeOptions;

344

```

345

346

**Usage Examples:**

347

348

```typescript

349

// Basic include

350

const users = await User.findAll({

351

include: [Post]

352

});

353

354

// Include with alias

355

const users = await User.findAll({

356

include: [{ model: Post, as: 'articles' }]

357

});

358

359

// Include with conditions

360

const users = await User.findAll({

361

include: [{

362

model: Post,

363

where: { status: 'published' },

364

required: true // INNER JOIN instead of LEFT JOIN

365

}]

366

});

367

368

// Nested includes

369

const posts = await Post.findAll({

370

include: [{

371

model: User,

372

include: [Profile]

373

}]

374

});

375

376

// Many-to-many with junction attributes

377

const users = await User.findAll({

378

include: [{

379

model: Role,

380

through: {

381

attributes: ['assignedAt', 'assignedBy']

382

}

383

}]

384

});

385

386

// Separate queries for performance

387

const users = await User.findAll({

388

include: [{

389

model: Post,

390

separate: true, // Run separate query

391

limit: 5

392

}]

393

});

394

```

395

396

### Association Scopes

397

398

Define scopes that are automatically applied to associations.

399

400

```typescript { .api }

401

interface AssociationScope {

402

[key: string]: any;

403

}

404

```

405

406

**Usage Example:**

407

408

```typescript

409

// Define association with scope

410

User.hasMany(Post, {

411

as: 'publishedPosts',

412

scope: {

413

status: 'published'

414

}

415

});

416

417

// Scope is automatically applied

418

const user = await User.findByPk(1);

419

const publishedPosts = await user.getPublishedPosts();

420

// Only returns posts where status = 'published'

421

```

422

423

### Advanced Association Patterns

424

425

Complex association setups and patterns.

426

427

```typescript { .api }

428

// Self-referencing associations

429

User.hasMany(User, { as: 'Children', foreignKey: 'parentId' });

430

User.belongsTo(User, { as: 'Parent', foreignKey: 'parentId' });

431

432

// Multiple associations between same models

433

User.hasMany(Post, { as: 'AuthoredPosts', foreignKey: 'authorId' });

434

User.hasMany(Post, { as: 'EditedPosts', foreignKey: 'editorId' });

435

436

// Polymorphic associations (manual setup)

437

class Comment extends Model {}

438

Comment.belongsTo(Post, { foreignKey: 'commentableId', constraints: false, scope: { commentableType: 'post' } });

439

Comment.belongsTo(User, { foreignKey: 'commentableId', constraints: false, scope: { commentableType: 'user' } });

440

```

441

442

**Usage Examples:**

443

444

```typescript

445

// Self-referencing

446

const manager = await User.findOne({

447

include: [{ model: User, as: 'Children' }]

448

});

449

450

// Multiple associations

451

const user = await User.findOne({

452

include: [

453

{ model: Post, as: 'AuthoredPosts' },

454

{ model: Post, as: 'EditedPosts' }

455

]

456

});

457

458

// Polymorphic

459

const comments = await Comment.findAll({

460

include: [

461

{ model: Post, where: { commentableType: 'post' } },

462

{ model: User, where: { commentableType: 'user' } }

463

]

464

});

465

```