@qazuor/claude-code-config 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -41
- package/dist/bin.cjs +963 -84
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +963 -84
- package/dist/bin.js.map +1 -1
- package/dist/index.cjs +73 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +73 -56
- package/dist/index.js.map +1 -1
- package/package.json +23 -24
- package/templates/CLAUDE.md.template +60 -5
- package/templates/agents/README.md +58 -39
- package/templates/agents/_registry.json +43 -202
- package/templates/agents/engineering/{hono-engineer.md → api-engineer.md} +61 -70
- package/templates/agents/engineering/database-engineer.md +253 -0
- package/templates/agents/engineering/frontend-engineer.md +302 -0
- package/templates/hooks/on-notification.sh +0 -0
- package/templates/scripts/add-changelogs.sh +0 -0
- package/templates/scripts/generate-code-registry.ts +0 -0
- package/templates/scripts/health-check.sh +0 -0
- package/templates/scripts/sync-registry.sh +0 -0
- package/templates/scripts/telemetry-report.ts +0 -0
- package/templates/scripts/validate-docs.sh +0 -0
- package/templates/scripts/validate-registry.sh +0 -0
- package/templates/scripts/validate-structure.sh +0 -0
- package/templates/scripts/worktree-cleanup.sh +0 -0
- package/templates/scripts/worktree-create.sh +0 -0
- package/templates/skills/README.md +99 -90
- package/templates/skills/_registry.json +323 -16
- package/templates/skills/api-frameworks/express-patterns.md +411 -0
- package/templates/skills/api-frameworks/fastify-patterns.md +419 -0
- package/templates/skills/api-frameworks/hono-patterns.md +388 -0
- package/templates/skills/api-frameworks/nestjs-patterns.md +497 -0
- package/templates/skills/database/drizzle-patterns.md +449 -0
- package/templates/skills/database/mongoose-patterns.md +503 -0
- package/templates/skills/database/prisma-patterns.md +487 -0
- package/templates/skills/frontend-frameworks/astro-patterns.md +415 -0
- package/templates/skills/frontend-frameworks/nextjs-patterns.md +470 -0
- package/templates/skills/frontend-frameworks/react-patterns.md +516 -0
- package/templates/skills/frontend-frameworks/tanstack-start-patterns.md +469 -0
- package/templates/skills/patterns/atdd-methodology.md +364 -0
- package/templates/skills/patterns/bdd-methodology.md +281 -0
- package/templates/skills/patterns/clean-architecture.md +444 -0
- package/templates/skills/patterns/hexagonal-architecture.md +567 -0
- package/templates/skills/patterns/vertical-slice-architecture.md +502 -0
- package/templates/agents/engineering/astro-engineer.md +0 -293
- package/templates/agents/engineering/db-drizzle-engineer.md +0 -360
- package/templates/agents/engineering/express-engineer.md +0 -316
- package/templates/agents/engineering/fastify-engineer.md +0 -399
- package/templates/agents/engineering/mongoose-engineer.md +0 -473
- package/templates/agents/engineering/nestjs-engineer.md +0 -429
- package/templates/agents/engineering/nextjs-engineer.md +0 -451
- package/templates/agents/engineering/prisma-engineer.md +0 -432
- package/templates/agents/engineering/react-senior-dev.md +0 -394
- package/templates/agents/engineering/tanstack-start-engineer.md +0 -447
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
# Mongoose Patterns
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Mongoose is an ODM (Object Document Mapper) for MongoDB. This skill provides patterns for database operations with Mongoose.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Schema Definition
|
|
10
|
+
|
|
11
|
+
### Basic Schema with Validation
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import mongoose, { Schema, Document, Model } from 'mongoose';
|
|
15
|
+
|
|
16
|
+
// Interface
|
|
17
|
+
export interface IUser {
|
|
18
|
+
email: string;
|
|
19
|
+
name?: string;
|
|
20
|
+
passwordHash: string;
|
|
21
|
+
role: 'user' | 'admin';
|
|
22
|
+
items: mongoose.Types.ObjectId[];
|
|
23
|
+
createdAt: Date;
|
|
24
|
+
updatedAt: Date;
|
|
25
|
+
deletedAt?: Date;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface IUserDocument extends IUser, Document {}
|
|
29
|
+
|
|
30
|
+
// Schema
|
|
31
|
+
const userSchema = new Schema<IUserDocument>(
|
|
32
|
+
{
|
|
33
|
+
email: {
|
|
34
|
+
type: String,
|
|
35
|
+
required: true,
|
|
36
|
+
unique: true,
|
|
37
|
+
lowercase: true,
|
|
38
|
+
trim: true,
|
|
39
|
+
index: true,
|
|
40
|
+
},
|
|
41
|
+
name: {
|
|
42
|
+
type: String,
|
|
43
|
+
trim: true,
|
|
44
|
+
maxlength: 255,
|
|
45
|
+
},
|
|
46
|
+
passwordHash: {
|
|
47
|
+
type: String,
|
|
48
|
+
required: true,
|
|
49
|
+
select: false, // Don't include in queries by default
|
|
50
|
+
},
|
|
51
|
+
role: {
|
|
52
|
+
type: String,
|
|
53
|
+
enum: ['user', 'admin'],
|
|
54
|
+
default: 'user',
|
|
55
|
+
},
|
|
56
|
+
items: [{
|
|
57
|
+
type: Schema.Types.ObjectId,
|
|
58
|
+
ref: 'Item',
|
|
59
|
+
}],
|
|
60
|
+
deletedAt: {
|
|
61
|
+
type: Date,
|
|
62
|
+
default: null,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
timestamps: true, // Adds createdAt and updatedAt
|
|
67
|
+
toJSON: {
|
|
68
|
+
virtuals: true,
|
|
69
|
+
transform: (doc, ret) => {
|
|
70
|
+
delete ret.__v;
|
|
71
|
+
delete ret.passwordHash;
|
|
72
|
+
return ret;
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
export const User = mongoose.model<IUserDocument>('User', userSchema);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Schema with Relations
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
export interface IItem {
|
|
85
|
+
title: string;
|
|
86
|
+
description?: string;
|
|
87
|
+
status: 'active' | 'archived';
|
|
88
|
+
price: number;
|
|
89
|
+
author: mongoose.Types.ObjectId;
|
|
90
|
+
tags: mongoose.Types.ObjectId[];
|
|
91
|
+
createdAt: Date;
|
|
92
|
+
updatedAt: Date;
|
|
93
|
+
deletedAt?: Date;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface IItemDocument extends IItem, Document {}
|
|
97
|
+
|
|
98
|
+
const itemSchema = new Schema<IItemDocument>(
|
|
99
|
+
{
|
|
100
|
+
title: {
|
|
101
|
+
type: String,
|
|
102
|
+
required: true,
|
|
103
|
+
trim: true,
|
|
104
|
+
maxlength: 255,
|
|
105
|
+
},
|
|
106
|
+
description: {
|
|
107
|
+
type: String,
|
|
108
|
+
trim: true,
|
|
109
|
+
},
|
|
110
|
+
status: {
|
|
111
|
+
type: String,
|
|
112
|
+
enum: ['active', 'archived'],
|
|
113
|
+
default: 'active',
|
|
114
|
+
index: true,
|
|
115
|
+
},
|
|
116
|
+
price: {
|
|
117
|
+
type: Number,
|
|
118
|
+
required: true,
|
|
119
|
+
min: [0, 'Price must be positive'],
|
|
120
|
+
},
|
|
121
|
+
author: {
|
|
122
|
+
type: Schema.Types.ObjectId,
|
|
123
|
+
ref: 'User',
|
|
124
|
+
required: true,
|
|
125
|
+
index: true,
|
|
126
|
+
},
|
|
127
|
+
tags: [{
|
|
128
|
+
type: Schema.Types.ObjectId,
|
|
129
|
+
ref: 'Tag',
|
|
130
|
+
}],
|
|
131
|
+
deletedAt: {
|
|
132
|
+
type: Date,
|
|
133
|
+
default: null,
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
timestamps: true,
|
|
138
|
+
}
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// Compound index
|
|
142
|
+
itemSchema.index({ author: 1, status: 1 });
|
|
143
|
+
|
|
144
|
+
// Virtual for URL
|
|
145
|
+
itemSchema.virtual('url').get(function () {
|
|
146
|
+
return `/items/${this._id}`;
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
export const Item = mongoose.model<IItemDocument>('Item', itemSchema);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Query Patterns
|
|
155
|
+
|
|
156
|
+
### Basic CRUD
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// Create
|
|
160
|
+
const item = await Item.create({
|
|
161
|
+
title: 'New Item',
|
|
162
|
+
price: 100,
|
|
163
|
+
author: userId,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Read one with population
|
|
167
|
+
const item = await Item.findById(itemId)
|
|
168
|
+
.populate('author', 'name email')
|
|
169
|
+
.populate('tags', 'name')
|
|
170
|
+
.lean();
|
|
171
|
+
|
|
172
|
+
// Read many with filters
|
|
173
|
+
const items = await Item.find({
|
|
174
|
+
status: 'active',
|
|
175
|
+
deletedAt: null,
|
|
176
|
+
})
|
|
177
|
+
.populate('author', 'name')
|
|
178
|
+
.sort({ createdAt: -1 })
|
|
179
|
+
.limit(10)
|
|
180
|
+
.lean();
|
|
181
|
+
|
|
182
|
+
// Update
|
|
183
|
+
const updated = await Item.findByIdAndUpdate(
|
|
184
|
+
itemId,
|
|
185
|
+
{ title: 'Updated Title' },
|
|
186
|
+
{ new: true, runValidators: true }
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
// Delete
|
|
190
|
+
await Item.findByIdAndDelete(itemId);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Pagination
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
async function findPaginated(input: {
|
|
197
|
+
page: number;
|
|
198
|
+
pageSize: number;
|
|
199
|
+
status?: string;
|
|
200
|
+
}) {
|
|
201
|
+
const { page, pageSize, status } = input;
|
|
202
|
+
const skip = (page - 1) * pageSize;
|
|
203
|
+
|
|
204
|
+
const filter = {
|
|
205
|
+
deletedAt: null,
|
|
206
|
+
...(status && { status }),
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const [items, total] = await Promise.all([
|
|
210
|
+
Item.find(filter)
|
|
211
|
+
.populate('author', 'name')
|
|
212
|
+
.sort({ createdAt: -1 })
|
|
213
|
+
.skip(skip)
|
|
214
|
+
.limit(pageSize)
|
|
215
|
+
.lean(),
|
|
216
|
+
Item.countDocuments(filter),
|
|
217
|
+
]);
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
data: items,
|
|
221
|
+
pagination: {
|
|
222
|
+
total,
|
|
223
|
+
page,
|
|
224
|
+
pageSize,
|
|
225
|
+
totalPages: Math.ceil(total / pageSize),
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Aggregation
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
// Items count by status
|
|
235
|
+
const stats = await Item.aggregate([
|
|
236
|
+
{ $match: { deletedAt: null } },
|
|
237
|
+
{
|
|
238
|
+
$group: {
|
|
239
|
+
_id: '$status',
|
|
240
|
+
count: { $sum: 1 },
|
|
241
|
+
avgPrice: { $avg: '$price' },
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
]);
|
|
245
|
+
|
|
246
|
+
// Items with author details
|
|
247
|
+
const itemsWithAuthors = await Item.aggregate([
|
|
248
|
+
{ $match: { status: 'active' } },
|
|
249
|
+
{
|
|
250
|
+
$lookup: {
|
|
251
|
+
from: 'users',
|
|
252
|
+
localField: 'author',
|
|
253
|
+
foreignField: '_id',
|
|
254
|
+
as: 'authorDetails',
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
{ $unwind: '$authorDetails' },
|
|
258
|
+
{
|
|
259
|
+
$project: {
|
|
260
|
+
title: 1,
|
|
261
|
+
price: 1,
|
|
262
|
+
'authorDetails.name': 1,
|
|
263
|
+
'authorDetails.email': 1,
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
]);
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Soft Delete
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
// Soft delete
|
|
273
|
+
await Item.findByIdAndUpdate(itemId, {
|
|
274
|
+
deletedAt: new Date(),
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Restore
|
|
278
|
+
await Item.findByIdAndUpdate(itemId, {
|
|
279
|
+
deletedAt: null,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Query middleware for automatic filtering
|
|
283
|
+
itemSchema.pre(/^find/, function (next) {
|
|
284
|
+
// Skip if explicitly including deleted
|
|
285
|
+
if (this.getOptions().includeDeleted) {
|
|
286
|
+
return next();
|
|
287
|
+
}
|
|
288
|
+
this.where({ deletedAt: null });
|
|
289
|
+
next();
|
|
290
|
+
});
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Transactions
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
const session = await mongoose.startSession();
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
session.startTransaction();
|
|
300
|
+
|
|
301
|
+
const item = await Item.create(
|
|
302
|
+
[{ title: 'New', price: 100, author: userId }],
|
|
303
|
+
{ session }
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
await User.findByIdAndUpdate(
|
|
307
|
+
userId,
|
|
308
|
+
{ $push: { items: item[0]._id } },
|
|
309
|
+
{ session }
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
await session.commitTransaction();
|
|
313
|
+
return item[0];
|
|
314
|
+
} catch (error) {
|
|
315
|
+
await session.abortTransaction();
|
|
316
|
+
throw error;
|
|
317
|
+
} finally {
|
|
318
|
+
session.endSession();
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Model Methods
|
|
325
|
+
|
|
326
|
+
### Static Methods
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
// Add static methods to schema
|
|
330
|
+
itemSchema.statics.findByAuthor = function (authorId: string) {
|
|
331
|
+
return this.find({ author: authorId, deletedAt: null })
|
|
332
|
+
.sort({ createdAt: -1 });
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
itemSchema.statics.findActive = function () {
|
|
336
|
+
return this.find({ status: 'active', deletedAt: null });
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
// Interface for model with statics
|
|
340
|
+
interface IItemModel extends Model<IItemDocument> {
|
|
341
|
+
findByAuthor(authorId: string): Promise<IItemDocument[]>;
|
|
342
|
+
findActive(): Promise<IItemDocument[]>;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export const Item = mongoose.model<IItemDocument, IItemModel>('Item', itemSchema);
|
|
346
|
+
|
|
347
|
+
// Usage
|
|
348
|
+
const items = await Item.findByAuthor(userId);
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Instance Methods
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
// Add instance methods
|
|
355
|
+
itemSchema.methods.softDelete = function () {
|
|
356
|
+
this.deletedAt = new Date();
|
|
357
|
+
return this.save();
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
itemSchema.methods.restore = function () {
|
|
361
|
+
this.deletedAt = null;
|
|
362
|
+
return this.save();
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
itemSchema.methods.archive = function () {
|
|
366
|
+
this.status = 'archived';
|
|
367
|
+
return this.save();
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// Usage
|
|
371
|
+
const item = await Item.findById(itemId);
|
|
372
|
+
await item.softDelete();
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
## Connection Setup
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
// db/connection.ts
|
|
381
|
+
import mongoose from 'mongoose';
|
|
382
|
+
|
|
383
|
+
const MONGODB_URI = process.env.MONGODB_URI!;
|
|
384
|
+
|
|
385
|
+
let cached = global as typeof globalThis & {
|
|
386
|
+
mongoose: { conn: typeof mongoose | null; promise: Promise<typeof mongoose> | null };
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
if (!cached.mongoose) {
|
|
390
|
+
cached.mongoose = { conn: null, promise: null };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export async function connectDB() {
|
|
394
|
+
if (cached.mongoose.conn) {
|
|
395
|
+
return cached.mongoose.conn;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (!cached.mongoose.promise) {
|
|
399
|
+
cached.mongoose.promise = mongoose.connect(MONGODB_URI, {
|
|
400
|
+
bufferCommands: false,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
cached.mongoose.conn = await cached.mongoose.promise;
|
|
405
|
+
return cached.mongoose.conn;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Graceful shutdown
|
|
409
|
+
process.on('SIGINT', async () => {
|
|
410
|
+
await mongoose.connection.close();
|
|
411
|
+
process.exit(0);
|
|
412
|
+
});
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## Testing
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
import { describe, it, expect, beforeAll, beforeEach, afterAll } from 'vitest';
|
|
421
|
+
import mongoose from 'mongoose';
|
|
422
|
+
import { MongoMemoryServer } from 'mongodb-memory-server';
|
|
423
|
+
import { Item } from '../models/item.model';
|
|
424
|
+
import { User } from '../models/user.model';
|
|
425
|
+
|
|
426
|
+
let mongoServer: MongoMemoryServer;
|
|
427
|
+
|
|
428
|
+
beforeAll(async () => {
|
|
429
|
+
mongoServer = await MongoMemoryServer.create();
|
|
430
|
+
await mongoose.connect(mongoServer.getUri());
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
afterAll(async () => {
|
|
434
|
+
await mongoose.disconnect();
|
|
435
|
+
await mongoServer.stop();
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
beforeEach(async () => {
|
|
439
|
+
await Item.deleteMany({});
|
|
440
|
+
await User.deleteMany({});
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
describe('Item Model', () => {
|
|
444
|
+
it('should create item with valid data', async () => {
|
|
445
|
+
const user = await User.create({
|
|
446
|
+
email: 'test@example.com',
|
|
447
|
+
passwordHash: 'hash',
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
const item = await Item.create({
|
|
451
|
+
title: 'Test Item',
|
|
452
|
+
price: 100,
|
|
453
|
+
author: user._id,
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
expect(item._id).toBeDefined();
|
|
457
|
+
expect(item.title).toBe('Test Item');
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it('should fail without required fields', async () => {
|
|
461
|
+
await expect(
|
|
462
|
+
Item.create({ title: 'No Price' })
|
|
463
|
+
).rejects.toThrow();
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it('should populate author', async () => {
|
|
467
|
+
const user = await User.create({
|
|
468
|
+
email: 'test@example.com',
|
|
469
|
+
passwordHash: 'hash',
|
|
470
|
+
name: 'Test User',
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
const item = await Item.create({
|
|
474
|
+
title: 'Test',
|
|
475
|
+
price: 100,
|
|
476
|
+
author: user._id,
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
const populated = await Item.findById(item._id).populate('author', 'name');
|
|
480
|
+
expect(populated?.author.name).toBe('Test User');
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
## Best Practices
|
|
488
|
+
|
|
489
|
+
### Good
|
|
490
|
+
|
|
491
|
+
- Use lean() for read-only queries (better performance)
|
|
492
|
+
- Use indexes for frequently queried fields
|
|
493
|
+
- Use timestamps option instead of manual dates
|
|
494
|
+
- Use virtuals for computed properties
|
|
495
|
+
- Use select: false for sensitive fields
|
|
496
|
+
|
|
497
|
+
### Bad
|
|
498
|
+
|
|
499
|
+
- Not using indexes (poor query performance)
|
|
500
|
+
- Populating everything (performance issues)
|
|
501
|
+
- Manual _id generation (use ObjectId)
|
|
502
|
+
- Not handling connection errors
|
|
503
|
+
- Storing references when embedding is better
|