ginskill-init 1.0.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 +77 -0
- package/agents/developer.md +56 -0
- package/agents/frontend-design.md +69 -0
- package/agents/mobile-reviewer.md +36 -0
- package/agents/review-code.md +49 -0
- package/agents/security-scanner.md +50 -0
- package/agents/tester.md +72 -0
- package/bin/cli.js +226 -0
- package/package.json +20 -0
- package/skills/ai-asset-generator/SKILL.md +255 -0
- package/skills/ai-asset-generator/docs/gen-image.md +274 -0
- package/skills/ai-asset-generator/docs/genvideo.md +341 -0
- package/skills/ai-asset-generator/docs/remove-background.md +19 -0
- package/skills/ai-asset-generator/generate-credit-assets.mjs +180 -0
- package/skills/ai-asset-generator/generate-ginbrowser-assets.mjs +242 -0
- package/skills/ai-asset-generator/generate-sty-icon.mjs +149 -0
- package/skills/ai-asset-generator/lib/bg-remove.mjs +34 -0
- package/skills/ai-asset-generator/lib/env.mjs +38 -0
- package/skills/ai-asset-generator/lib/kie-client.mjs +88 -0
- package/skills/ai-asset-generator/scripts/scaffold-generator.mjs +203 -0
- package/skills/ai-build-ai/SKILL.md +124 -0
- package/skills/ai-build-ai/docs/agent-teams.md +293 -0
- package/skills/ai-build-ai/docs/checkpointing.md +161 -0
- package/skills/ai-build-ai/docs/create-agent.md +399 -0
- package/skills/ai-build-ai/docs/create-mcp.md +395 -0
- package/skills/ai-build-ai/docs/create-skill.md +299 -0
- package/skills/ai-build-ai/docs/headless-mode.md +614 -0
- package/skills/ai-build-ai/docs/hooks.md +578 -0
- package/skills/ai-build-ai/docs/memory-claude-md.md +375 -0
- package/skills/ai-build-ai/docs/output-styles.md +208 -0
- package/skills/ai-build-ai/docs/overview.md +162 -0
- package/skills/ai-build-ai/docs/permissions.md +391 -0
- package/skills/ai-build-ai/docs/plugins.md +396 -0
- package/skills/ai-build-ai/docs/sandbox.md +262 -0
- package/skills/ai-build-ai/scripts/load-tutorial.sh +54 -0
- package/skills/icon-generator/SKILL.md +270 -0
- package/skills/mobile-app-review/SKILL.md +321 -0
- package/skills/mobile-app-review/references/apple-review.md +132 -0
- package/skills/mobile-app-review/references/google-play-review.md +203 -0
- package/skills/mongodb/SKILL.md +667 -0
- package/skills/mongodb/references/mongoose-patterns.md +368 -0
- package/skills/nestjs-architecture/SKILL.md +1086 -0
- package/skills/nestjs-architecture/references/advanced-patterns.md +590 -0
- package/skills/performance/SKILL.md +509 -0
- package/skills/react-fsd-architecture/SKILL.md +693 -0
- package/skills/react-fsd-architecture/references/fsd-patterns.md +747 -0
- package/skills/react-query/SKILL.md +685 -0
- package/skills/react-query/references/query-patterns.md +365 -0
- package/skills/review-code/SKILL.md +321 -0
- package/skills/review-code/references/clean-code-principles.md +395 -0
- package/skills/review-code/references/frontend-patterns.md +136 -0
- package/skills/review-code/references/nestjs-patterns.md +184 -0
- package/skills/review-code/scripts/check-module.sh +201 -0
- package/skills/review-code/scripts/deep-scan.sh +604 -0
- package/skills/review-code/scripts/dep-check.sh +522 -0
- package/skills/review-code/scripts/detect-duplicates.sh +466 -0
- package/skills/review-code/scripts/format-check.sh +577 -0
- package/skills/review-code/scripts/run-review.sh +167 -0
- package/skills/review-code/scripts/scan-codebase.sh +152 -0
- package/skills/security-scanner/SKILL.md +327 -0
- package/skills/security-scanner/references/nestjs-security.md +260 -0
- package/skills/security-scanner/references/nextjs-security.md +201 -0
- package/skills/security-scanner/references/react-native-security.md +199 -0
- package/skills/security-scanner/scripts/security-scan.sh +478 -0
- package/skills/ui-ux-pro-max/SKILL.md +377 -0
- package/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/skills/ui-ux-pro-max/scripts/search.py +114 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
# Advanced Mongoose Patterns
|
|
2
|
+
|
|
3
|
+
Detailed reference for advanced patterns beyond the main SKILL.md. Load this when working on specific advanced scenarios.
|
|
4
|
+
|
|
5
|
+
## Pre/Post Hooks (Middleware)
|
|
6
|
+
|
|
7
|
+
### Pre-save: Hash Password
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
UserSchema.pre<UserDocument>('save', async function (next) {
|
|
11
|
+
if (!this.isModified('password')) return next()
|
|
12
|
+
this.password = await bcrypt.hash(this.password, 12)
|
|
13
|
+
next()
|
|
14
|
+
})
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Pre-find: Auto-exclude soft-deleted
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
ProductSchema.pre('find', function () {
|
|
21
|
+
this.where({ deletedAt: null })
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
ProductSchema.pre('findOne', function () {
|
|
25
|
+
this.where({ deletedAt: null })
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
ProductSchema.pre('countDocuments', function () {
|
|
29
|
+
this.where({ deletedAt: null })
|
|
30
|
+
})
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Post-save: Side Effects
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
OrderSchema.post<OrderDocument>('save', async function (doc) {
|
|
37
|
+
// Send notification after order created
|
|
38
|
+
await notificationService.send(doc.userId, `Order ${doc._id} placed`)
|
|
39
|
+
})
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Hook Gotchas
|
|
43
|
+
|
|
44
|
+
- `findOneAndUpdate` does NOT trigger `save` hooks — use `pre('findOneAndUpdate')` instead
|
|
45
|
+
- `insertMany` does NOT trigger `save` hooks — use `pre('insertMany')` if needed
|
|
46
|
+
- Arrow functions lose `this` context — always use `function` keyword in hooks
|
|
47
|
+
|
|
48
|
+
## Custom Methods & Statics
|
|
49
|
+
|
|
50
|
+
### Instance Methods
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// Schema definition
|
|
54
|
+
UserSchema.methods.comparePassword = async function (candidate: string): Promise<boolean> {
|
|
55
|
+
return bcrypt.compare(candidate, this.password)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
UserSchema.methods.toPublicJSON = function () {
|
|
59
|
+
const { password, __v, ...rest } = this.toObject()
|
|
60
|
+
return rest
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Usage
|
|
64
|
+
const user = await User.findOne({ email })
|
|
65
|
+
const isValid = await user.comparePassword(inputPassword)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Static Methods
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// Schema definition
|
|
72
|
+
UserSchema.statics.findByEmail = function (email: string) {
|
|
73
|
+
return this.findOne({ email: email.toLowerCase() }).lean()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
UserSchema.statics.findActiveByOrg = function (orgId: string) {
|
|
77
|
+
return this.find({ organization: orgId, status: 'active' }).lean()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Usage
|
|
81
|
+
const user = await User.findByEmail('john@example.com')
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### TypeScript Typing for Methods/Statics
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// Define interfaces
|
|
88
|
+
interface IUserMethods {
|
|
89
|
+
comparePassword(candidate: string): Promise<boolean>
|
|
90
|
+
toPublicJSON(): Omit<User, 'password'>
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interface UserModel extends Model<User, {}, IUserMethods> {
|
|
94
|
+
findByEmail(email: string): Promise<User | null>
|
|
95
|
+
findActiveByOrg(orgId: string): Promise<User[]>
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// In NestJS module
|
|
99
|
+
@InjectModel(User.name) private userModel: UserModel
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Mongoose Plugins
|
|
103
|
+
|
|
104
|
+
### Global Plugins
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// Apply to all schemas
|
|
108
|
+
import mongooseLeanVirtuals from 'mongoose-lean-virtuals'
|
|
109
|
+
import mongooseLeanGetters from 'mongoose-lean-getters'
|
|
110
|
+
|
|
111
|
+
mongoose.plugin(mongooseLeanVirtuals)
|
|
112
|
+
mongoose.plugin(mongooseLeanGetters)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Soft Delete Plugin
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
function softDeletePlugin(schema: Schema) {
|
|
119
|
+
schema.add({ deletedAt: { type: Date, default: null } })
|
|
120
|
+
|
|
121
|
+
schema.methods.softDelete = function () {
|
|
122
|
+
this.deletedAt = new Date()
|
|
123
|
+
return this.save()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
schema.methods.restore = function () {
|
|
127
|
+
this.deletedAt = null
|
|
128
|
+
return this.save()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Auto-filter soft-deleted docs
|
|
132
|
+
schema.pre('find', function () { this.where({ deletedAt: null }) })
|
|
133
|
+
schema.pre('findOne', function () { this.where({ deletedAt: null }) })
|
|
134
|
+
schema.pre('countDocuments', function () { this.where({ deletedAt: null }) })
|
|
135
|
+
|
|
136
|
+
// Index for queries that include deleted docs
|
|
137
|
+
schema.index({ deletedAt: 1 })
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Apply
|
|
141
|
+
UserSchema.plugin(softDeletePlugin)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Pagination Plugin
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
interface PaginateResult<T> {
|
|
148
|
+
docs: T[]
|
|
149
|
+
totalDocs: number
|
|
150
|
+
page: number
|
|
151
|
+
totalPages: number
|
|
152
|
+
hasNextPage: boolean
|
|
153
|
+
hasPrevPage: boolean
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function paginatePlugin(schema: Schema) {
|
|
157
|
+
schema.statics.paginate = async function (
|
|
158
|
+
filter: FilterQuery<any> = {},
|
|
159
|
+
options: { page?: number; limit?: number; sort?: any; select?: string; lean?: boolean } = {},
|
|
160
|
+
): Promise<PaginateResult<any>> {
|
|
161
|
+
const page = Math.max(1, options.page || 1)
|
|
162
|
+
const limit = Math.min(100, Math.max(1, options.limit || 20))
|
|
163
|
+
const skip = (page - 1) * limit
|
|
164
|
+
|
|
165
|
+
const [docs, totalDocs] = await Promise.all([
|
|
166
|
+
this.find(filter)
|
|
167
|
+
.sort(options.sort || { createdAt: -1 })
|
|
168
|
+
.skip(skip)
|
|
169
|
+
.limit(limit)
|
|
170
|
+
.select(options.select || '')
|
|
171
|
+
.lean(options.lean ?? true)
|
|
172
|
+
.exec(),
|
|
173
|
+
this.countDocuments(filter),
|
|
174
|
+
])
|
|
175
|
+
|
|
176
|
+
const totalPages = Math.ceil(totalDocs / limit)
|
|
177
|
+
return {
|
|
178
|
+
docs,
|
|
179
|
+
totalDocs,
|
|
180
|
+
page,
|
|
181
|
+
totalPages,
|
|
182
|
+
hasNextPage: page < totalPages,
|
|
183
|
+
hasPrevPage: page > 1,
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Change Streams
|
|
190
|
+
|
|
191
|
+
Watch real-time changes to a collection (requires replica set).
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
// Watch for new orders
|
|
195
|
+
const changeStream = this.orderModel.watch([
|
|
196
|
+
{ $match: { operationType: 'insert' } },
|
|
197
|
+
])
|
|
198
|
+
|
|
199
|
+
changeStream.on('change', (change) => {
|
|
200
|
+
console.log('New order:', change.fullDocument)
|
|
201
|
+
// Trigger notifications, update dashboards, etc.
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
// Watch for specific field updates
|
|
205
|
+
const priceStream = this.productModel.watch([
|
|
206
|
+
{ $match: { 'updateDescription.updatedFields.price': { $exists: true } } },
|
|
207
|
+
])
|
|
208
|
+
|
|
209
|
+
// Cleanup
|
|
210
|
+
process.on('SIGTERM', () => changeStream.close())
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Resume After Disconnect
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
let resumeToken: unknown
|
|
217
|
+
|
|
218
|
+
const stream = this.orderModel.watch([], {
|
|
219
|
+
fullDocument: 'updateLookup', // include full doc on updates
|
|
220
|
+
...(resumeToken ? { resumeAfter: resumeToken } : {}),
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
stream.on('change', (change) => {
|
|
224
|
+
resumeToken = change._id // save for resume
|
|
225
|
+
handleChange(change)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
stream.on('error', (err) => {
|
|
229
|
+
logger.error('Change stream error:', err)
|
|
230
|
+
// Reconnect with resumeToken after delay
|
|
231
|
+
setTimeout(() => startChangeStream(), 5000)
|
|
232
|
+
})
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Cursor-Based Streaming
|
|
236
|
+
|
|
237
|
+
Process large datasets without loading everything into memory.
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
// Process 1M documents with constant memory usage
|
|
241
|
+
const cursor = this.userModel.find({ status: 'active' }).lean().cursor()
|
|
242
|
+
|
|
243
|
+
for await (const user of cursor) {
|
|
244
|
+
await processUser(user)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Or with batch size control
|
|
248
|
+
const cursor = this.orderModel.find().lean().cursor({ batchSize: 100 })
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Schema Versioning
|
|
252
|
+
|
|
253
|
+
Handle schema evolution in existing data.
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
@Schema({ timestamps: true })
|
|
257
|
+
export class User {
|
|
258
|
+
@Prop({ default: 2 })
|
|
259
|
+
schemaVersion: number
|
|
260
|
+
|
|
261
|
+
@Prop({ required: true })
|
|
262
|
+
name: string
|
|
263
|
+
|
|
264
|
+
// v2: contact methods as array (replaces v1 phone/email fields)
|
|
265
|
+
@Prop({ type: [{ type: String, value: String }], default: [] })
|
|
266
|
+
contactMethods: Array<{ type: string; value: string }>
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Migration on read
|
|
270
|
+
async findUser(id: string) {
|
|
271
|
+
const doc = await this.userModel.findById(id)
|
|
272
|
+
if (!doc.schemaVersion || doc.schemaVersion < 2) {
|
|
273
|
+
return this.migrateToV2(doc)
|
|
274
|
+
}
|
|
275
|
+
return doc.toObject()
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Multi-Tenant Patterns
|
|
280
|
+
|
|
281
|
+
### Database-per-Tenant
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
const connectionMap = new Map<string, mongoose.Connection>()
|
|
285
|
+
|
|
286
|
+
async getTenantConnection(tenantId: string): Promise<mongoose.Connection> {
|
|
287
|
+
if (connectionMap.has(tenantId)) return connectionMap.get(tenantId)!
|
|
288
|
+
|
|
289
|
+
const conn = await mongoose.createConnection(`${baseUri}/${tenantId}`, {
|
|
290
|
+
maxPoolSize: 5,
|
|
291
|
+
})
|
|
292
|
+
connectionMap.set(tenantId, conn)
|
|
293
|
+
return conn
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Get model for tenant
|
|
297
|
+
async getTenantModel<T>(tenantId: string, name: string, schema: Schema): Promise<Model<T>> {
|
|
298
|
+
const conn = await this.getTenantConnection(tenantId)
|
|
299
|
+
return conn.model<T>(name, schema)
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Collection-per-Tenant (simpler, same database)
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
async getTenantModel(tenantId: string) {
|
|
307
|
+
return this.connection.model('Order', OrderSchema, `orders_${tenantId}`)
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Shared Collection with Tenant Field
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
@Schema({ timestamps: true })
|
|
315
|
+
export class Order {
|
|
316
|
+
@Prop({ required: true, index: true })
|
|
317
|
+
tenantId: string
|
|
318
|
+
|
|
319
|
+
// ... other fields
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Always include tenantId in queries
|
|
323
|
+
OrderSchema.index({ tenantId: 1, createdAt: -1 })
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Explain & Debug
|
|
327
|
+
|
|
328
|
+
### Analyze Query Performance
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
// In development only
|
|
332
|
+
const explanation = await this.userModel
|
|
333
|
+
.find({ email: 'test@example.com' })
|
|
334
|
+
.explain('executionStats')
|
|
335
|
+
|
|
336
|
+
// Key metrics:
|
|
337
|
+
// executionStats.totalDocsExamined — docs scanned (lower is better)
|
|
338
|
+
// executionStats.totalKeysExamined — index keys scanned
|
|
339
|
+
// executionStats.executionTimeMillis — query time
|
|
340
|
+
// winningPlan.stage — IXSCAN (good) vs COLLSCAN (bad)
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Enable Mongoose Debug Logging
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
// Log all queries in development
|
|
347
|
+
mongoose.set('debug', true)
|
|
348
|
+
|
|
349
|
+
// Custom logger
|
|
350
|
+
mongoose.set('debug', (collectionName, method, query, doc) => {
|
|
351
|
+
logger.debug(`${collectionName}.${method}`, JSON.stringify(query))
|
|
352
|
+
})
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Monitor Connection Pool
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
const pool = mongoose.connection.getClient().options
|
|
359
|
+
// Check: pool.maxPoolSize, pool.minPoolSize
|
|
360
|
+
|
|
361
|
+
// Monitor pool events
|
|
362
|
+
mongoose.connection.getClient().on('connectionPoolCreated', (event) => {
|
|
363
|
+
logger.debug('Pool created:', event)
|
|
364
|
+
})
|
|
365
|
+
mongoose.connection.getClient().on('connectionCheckedOut', (event) => {
|
|
366
|
+
logger.debug('Connection checked out:', event)
|
|
367
|
+
})
|
|
368
|
+
```
|