@gallopsystems/agent-skills 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 +137 -0
- package/package.json +26 -0
- package/plugins/doctl/.claude-plugin/plugin.json +8 -0
- package/plugins/doctl/skills/doctl/SKILL.md +93 -0
- package/plugins/kysely-postgres/.claude-plugin/plugin.json +8 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/SKILL.md +1101 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/aggregations.ts +167 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/ctes.ts +165 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/expressions.ts +272 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/joins.ts +206 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/json-arrays.ts +398 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/mutations.ts +199 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/orderby-pagination.ts +117 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/relations.ts +176 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/select-where.ts +146 -0
- package/plugins/linear/.claude-plugin/plugin.json +8 -0
- package/plugins/linear/skills/linear/SKILL.md +1040 -0
- package/plugins/linear/skills/linear/bin/linear.mjs +1228 -0
- package/plugins/linear/skills/linear/tech-stack.md +273 -0
- package/plugins/nitro-testing/.claude-plugin/plugin.json +8 -0
- package/plugins/nitro-testing/skills/nitro-testing/SKILL.md +497 -0
- package/plugins/nitro-testing/skills/nitro-testing/async-testing.md +270 -0
- package/plugins/nitro-testing/skills/nitro-testing/ci-setup.md +226 -0
- package/plugins/nitro-testing/skills/nitro-testing/examples/global-setup.ts +90 -0
- package/plugins/nitro-testing/skills/nitro-testing/examples/handler.test.ts +167 -0
- package/plugins/nitro-testing/skills/nitro-testing/examples/setup.ts +29 -0
- package/plugins/nitro-testing/skills/nitro-testing/examples/test-utils-index.ts +297 -0
- package/plugins/nitro-testing/skills/nitro-testing/examples/vitest.config.ts +42 -0
- package/plugins/nitro-testing/skills/nitro-testing/factories.md +278 -0
- package/plugins/nitro-testing/skills/nitro-testing/frontend-testing.md +512 -0
- package/plugins/nitro-testing/skills/nitro-testing/test-utils.md +262 -0
- package/plugins/nitro-testing/skills/nitro-testing/transaction-rollback.md +183 -0
- package/plugins/nitro-testing/skills/nitro-testing/vitest-config.md +236 -0
- package/plugins/nuxt-nitro-api/.claude-plugin/plugin.json +8 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/SKILL.md +260 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/auth-patterns.md +228 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/composables-utils.md +174 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/deep-linking.md +190 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/auth-middleware.ts +32 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/auth-utils.ts +51 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/deep-link-page.vue +61 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/service-util.ts +63 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/sse-endpoint.ts +59 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/validation-endpoint.ts +38 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/fetch-patterns.md +178 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/nitro-tasks.md +243 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/page-structure.md +162 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/server-services.md +238 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/sse.md +221 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/ssr-client.md +166 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/validation.md +131 -0
- package/scripts/link-skills.mjs +252 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# Factory Pattern
|
|
2
|
+
|
|
3
|
+
> **Example:** [test-utils-index.ts](./examples/test-utils-index.ts)
|
|
4
|
+
|
|
5
|
+
Transaction-bound factories for creating test data with sensible defaults.
|
|
6
|
+
|
|
7
|
+
## Core Pattern
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import type { Transaction } from "kysely";
|
|
11
|
+
import type { DB } from "../db/db";
|
|
12
|
+
|
|
13
|
+
function createFactories(trx: Transaction<DB>) {
|
|
14
|
+
return {
|
|
15
|
+
async user(data: Partial<{
|
|
16
|
+
email: string;
|
|
17
|
+
name: string;
|
|
18
|
+
role: string;
|
|
19
|
+
}> = {}) {
|
|
20
|
+
const num = Math.floor(Math.random() * 10000);
|
|
21
|
+
return trx
|
|
22
|
+
.insertInto("user")
|
|
23
|
+
.values({
|
|
24
|
+
email: data.email ?? `test${num}@example.com`,
|
|
25
|
+
name: data.name ?? "Test User",
|
|
26
|
+
role: data.role ?? "user",
|
|
27
|
+
})
|
|
28
|
+
.returningAll()
|
|
29
|
+
.executeTakeFirstOrThrow();
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
async project(data: Partial<{
|
|
33
|
+
name: string;
|
|
34
|
+
ownerId: number;
|
|
35
|
+
status: string;
|
|
36
|
+
}> = {}) {
|
|
37
|
+
// Auto-create owner if not provided
|
|
38
|
+
let ownerId = data.ownerId;
|
|
39
|
+
if (!ownerId) {
|
|
40
|
+
const owner = await this.user();
|
|
41
|
+
ownerId = owner.id;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return trx
|
|
45
|
+
.insertInto("project")
|
|
46
|
+
.values({
|
|
47
|
+
name: data.name ?? `Test Project ${Date.now()}`,
|
|
48
|
+
owner_id: ownerId,
|
|
49
|
+
status: data.status ?? "active",
|
|
50
|
+
})
|
|
51
|
+
.returningAll()
|
|
52
|
+
.executeTakeFirstOrThrow();
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
async task(data: {
|
|
56
|
+
projectId: number; // Required - explicit dependency
|
|
57
|
+
title?: string;
|
|
58
|
+
status?: string;
|
|
59
|
+
assigneeId?: number | null;
|
|
60
|
+
}) {
|
|
61
|
+
return trx
|
|
62
|
+
.insertInto("task")
|
|
63
|
+
.values({
|
|
64
|
+
project_id: data.projectId,
|
|
65
|
+
title: data.title ?? "Test Task",
|
|
66
|
+
status: data.status ?? "pending",
|
|
67
|
+
assignee_id: data.assigneeId ?? null,
|
|
68
|
+
})
|
|
69
|
+
.returningAll()
|
|
70
|
+
.executeTakeFirstOrThrow();
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type Factories = ReturnType<typeof createFactories>;
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Design Principles
|
|
79
|
+
|
|
80
|
+
### 1. Sensible Defaults
|
|
81
|
+
|
|
82
|
+
Every field has a default so you only specify what matters:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// Minimal - uses all defaults
|
|
86
|
+
const user = await factories.user();
|
|
87
|
+
|
|
88
|
+
// Override just what you need
|
|
89
|
+
const admin = await factories.user({ role: "admin" });
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 2. Required vs Optional Dependencies
|
|
93
|
+
|
|
94
|
+
**Optional dependencies** are auto-created:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
async project(data: Partial<{...}> = {}) {
|
|
98
|
+
let ownerId = data.ownerId;
|
|
99
|
+
if (!ownerId) {
|
|
100
|
+
const owner = await this.user(); // Auto-create
|
|
101
|
+
ownerId = owner.id;
|
|
102
|
+
}
|
|
103
|
+
// ...
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Usage - owner created automatically
|
|
107
|
+
const project = await factories.project();
|
|
108
|
+
|
|
109
|
+
// Or specify one
|
|
110
|
+
const project = await factories.project({ ownerId: existingUser.id });
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Required dependencies** must be passed:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
async task(data: {
|
|
117
|
+
projectId: number; // Required
|
|
118
|
+
// ...
|
|
119
|
+
}) {
|
|
120
|
+
// ...
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Usage - must create project first
|
|
124
|
+
const project = await factories.project();
|
|
125
|
+
const task = await factories.task({ projectId: project.id });
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 3. Unique Values
|
|
129
|
+
|
|
130
|
+
Avoid collisions with random/unique values:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
async user(data: Partial<{...}> = {}) {
|
|
134
|
+
const num = Math.floor(Math.random() * 10000);
|
|
135
|
+
return trx.insertInto("user").values({
|
|
136
|
+
email: data.email ?? `test${num}@example.com`,
|
|
137
|
+
// ...
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async project(data: Partial<{...}> = {}) {
|
|
142
|
+
return trx.insertInto("project").values({
|
|
143
|
+
name: data.name ?? `Test Project ${Date.now()}`,
|
|
144
|
+
// ...
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 4. Use `this` for Composition
|
|
150
|
+
|
|
151
|
+
Factories can call each other:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
async task(data: {...}) {
|
|
155
|
+
return trx.insertInto("task").values({...});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async taskWithAssignee(data: {...}) {
|
|
159
|
+
const assignee = await this.user({ role: "member" });
|
|
160
|
+
return this.task({ ...data, assigneeId: assignee.id });
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Complex Relationships
|
|
165
|
+
|
|
166
|
+
### Many-to-Many
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
async projectMember(data: {
|
|
170
|
+
projectId: number;
|
|
171
|
+
userId: number;
|
|
172
|
+
role?: string;
|
|
173
|
+
}) {
|
|
174
|
+
return trx
|
|
175
|
+
.insertInto("project_member")
|
|
176
|
+
.values({
|
|
177
|
+
project_id: data.projectId,
|
|
178
|
+
user_id: data.userId,
|
|
179
|
+
role: data.role ?? "member",
|
|
180
|
+
})
|
|
181
|
+
.returningAll()
|
|
182
|
+
.executeTakeFirstOrThrow();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Usage
|
|
186
|
+
const project = await factories.project();
|
|
187
|
+
const user = await factories.user();
|
|
188
|
+
await factories.projectMember({
|
|
189
|
+
projectId: project.id,
|
|
190
|
+
userId: user.id,
|
|
191
|
+
role: "admin",
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### JSON Fields
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
async organization(data: Partial<{
|
|
199
|
+
name: string;
|
|
200
|
+
metadata: Record<string, any>;
|
|
201
|
+
contacts: { email?: string; phone?: string };
|
|
202
|
+
}> = {}) {
|
|
203
|
+
return trx
|
|
204
|
+
.insertInto("organization")
|
|
205
|
+
.values({
|
|
206
|
+
name: data.name ?? `Org ${Date.now()}`,
|
|
207
|
+
metadata: data.metadata ?? {},
|
|
208
|
+
contacts: data.contacts ?? {},
|
|
209
|
+
})
|
|
210
|
+
.returningAll()
|
|
211
|
+
.executeTakeFirstOrThrow();
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Self-Referential (Parent/Child)
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
async category(data: Partial<{
|
|
219
|
+
name: string;
|
|
220
|
+
parentId: number | null;
|
|
221
|
+
}> = {}) {
|
|
222
|
+
return trx
|
|
223
|
+
.insertInto("category")
|
|
224
|
+
.values({
|
|
225
|
+
name: data.name ?? "Test Category",
|
|
226
|
+
parent_id: data.parentId ?? null,
|
|
227
|
+
})
|
|
228
|
+
.returningAll()
|
|
229
|
+
.executeTakeFirstOrThrow();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Usage - create hierarchy
|
|
233
|
+
const parent = await factories.category({ name: "Electronics" });
|
|
234
|
+
const child = await factories.category({
|
|
235
|
+
name: "Phones",
|
|
236
|
+
parentId: parent.id,
|
|
237
|
+
});
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Usage in Tests
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
describe("GET /api/projects/[id]/tasks", () => {
|
|
244
|
+
test("returns tasks for project", async ({ factories }) => {
|
|
245
|
+
const project = await factories.project();
|
|
246
|
+
const task1 = await factories.task({ projectId: project.id });
|
|
247
|
+
const task2 = await factories.task({ projectId: project.id });
|
|
248
|
+
|
|
249
|
+
const event = mockGet({ id: project.id });
|
|
250
|
+
const result = await handler(event);
|
|
251
|
+
|
|
252
|
+
expect(result).toHaveLength(2);
|
|
253
|
+
expect(result.map((t: any) => t.id)).toContain(task1.id);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("excludes other project's tasks", async ({ factories }) => {
|
|
257
|
+
const myProject = await factories.project();
|
|
258
|
+
const otherProject = await factories.project();
|
|
259
|
+
|
|
260
|
+
await factories.task({ projectId: myProject.id });
|
|
261
|
+
await factories.task({ projectId: otherProject.id }); // Different project
|
|
262
|
+
|
|
263
|
+
const event = mockGet({ id: myProject.id });
|
|
264
|
+
const result = await handler(event);
|
|
265
|
+
|
|
266
|
+
expect(result).toHaveLength(1); // Only our project's task
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Factory Best Practices
|
|
272
|
+
|
|
273
|
+
1. **Make required FKs explicit** - Force caller to think about relationships
|
|
274
|
+
2. **Make optional FKs auto-created** - Reduce boilerplate for simple tests
|
|
275
|
+
3. **Use random/unique values** - Avoid collisions even if tests somehow overlap
|
|
276
|
+
4. **Return full entity** - Use `returningAll()` for flexibility
|
|
277
|
+
5. **Minimal defaults** - Only set what's needed for valid inserts
|
|
278
|
+
6. **Keep factories simple** - No business logic, just data creation
|