autoworkflow 3.1.5 → 3.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.
Files changed (124) hide show
  1. package/.claude/commands/analyze.md +19 -0
  2. package/.claude/commands/audit.md +26 -0
  3. package/.claude/commands/build.md +39 -0
  4. package/.claude/commands/commit.md +25 -0
  5. package/.claude/commands/fix.md +23 -0
  6. package/.claude/commands/plan.md +18 -0
  7. package/.claude/commands/suggest.md +23 -0
  8. package/.claude/commands/verify.md +18 -0
  9. package/.claude/hooks/post-bash-router.sh +20 -0
  10. package/.claude/hooks/post-commit.sh +140 -0
  11. package/.claude/hooks/post-edit.sh +190 -17
  12. package/.claude/hooks/pre-edit.sh +221 -0
  13. package/.claude/hooks/session-check.sh +90 -0
  14. package/.claude/settings.json +56 -6
  15. package/.claude/settings.local.json +5 -1
  16. package/.claude/skills/actix.md +337 -0
  17. package/.claude/skills/alembic.md +504 -0
  18. package/.claude/skills/angular.md +237 -0
  19. package/.claude/skills/api-design.md +187 -0
  20. package/.claude/skills/aspnet-core.md +377 -0
  21. package/.claude/skills/astro.md +245 -0
  22. package/.claude/skills/auth-clerk.md +327 -0
  23. package/.claude/skills/auth-firebase.md +367 -0
  24. package/.claude/skills/auth-nextauth.md +359 -0
  25. package/.claude/skills/auth-supabase.md +368 -0
  26. package/.claude/skills/axum.md +386 -0
  27. package/.claude/skills/blazor.md +456 -0
  28. package/.claude/skills/chi.md +348 -0
  29. package/.claude/skills/code-review.md +133 -0
  30. package/.claude/skills/csharp.md +296 -0
  31. package/.claude/skills/css-modules.md +325 -0
  32. package/.claude/skills/cypress.md +343 -0
  33. package/.claude/skills/debugging.md +133 -0
  34. package/.claude/skills/diesel.md +392 -0
  35. package/.claude/skills/django.md +301 -0
  36. package/.claude/skills/docker.md +319 -0
  37. package/.claude/skills/doctrine.md +473 -0
  38. package/.claude/skills/documentation.md +182 -0
  39. package/.claude/skills/dotnet.md +409 -0
  40. package/.claude/skills/drizzle.md +293 -0
  41. package/.claude/skills/echo.md +321 -0
  42. package/.claude/skills/eloquent.md +256 -0
  43. package/.claude/skills/emotion.md +426 -0
  44. package/.claude/skills/entity-framework.md +370 -0
  45. package/.claude/skills/express.md +316 -0
  46. package/.claude/skills/fastapi.md +329 -0
  47. package/.claude/skills/fastify.md +299 -0
  48. package/.claude/skills/fiber.md +315 -0
  49. package/.claude/skills/flask.md +322 -0
  50. package/.claude/skills/gin.md +342 -0
  51. package/.claude/skills/git.md +116 -0
  52. package/.claude/skills/github-actions.md +353 -0
  53. package/.claude/skills/go.md +377 -0
  54. package/.claude/skills/gorm.md +409 -0
  55. package/.claude/skills/graphql.md +478 -0
  56. package/.claude/skills/hibernate.md +379 -0
  57. package/.claude/skills/hono.md +306 -0
  58. package/.claude/skills/java.md +400 -0
  59. package/.claude/skills/jest.md +313 -0
  60. package/.claude/skills/jpa.md +282 -0
  61. package/.claude/skills/kotlin.md +347 -0
  62. package/.claude/skills/kubernetes.md +363 -0
  63. package/.claude/skills/laravel.md +414 -0
  64. package/.claude/skills/mcp-browser.md +320 -0
  65. package/.claude/skills/mcp-database.md +219 -0
  66. package/.claude/skills/mcp-fetch.md +241 -0
  67. package/.claude/skills/mcp-filesystem.md +204 -0
  68. package/.claude/skills/mcp-github.md +217 -0
  69. package/.claude/skills/mcp-memory.md +240 -0
  70. package/.claude/skills/mcp-search.md +218 -0
  71. package/.claude/skills/mcp-slack.md +262 -0
  72. package/.claude/skills/micronaut.md +388 -0
  73. package/.claude/skills/mongodb.md +319 -0
  74. package/.claude/skills/mongoose.md +355 -0
  75. package/.claude/skills/mysql.md +281 -0
  76. package/.claude/skills/nestjs.md +335 -0
  77. package/.claude/skills/nextjs-app-router.md +260 -0
  78. package/.claude/skills/nextjs-pages.md +172 -0
  79. package/.claude/skills/nuxt.md +202 -0
  80. package/.claude/skills/openapi.md +489 -0
  81. package/.claude/skills/performance.md +199 -0
  82. package/.claude/skills/php.md +398 -0
  83. package/.claude/skills/playwright.md +371 -0
  84. package/.claude/skills/postgresql.md +257 -0
  85. package/.claude/skills/prisma.md +293 -0
  86. package/.claude/skills/pydantic.md +304 -0
  87. package/.claude/skills/pytest.md +313 -0
  88. package/.claude/skills/python.md +272 -0
  89. package/.claude/skills/quarkus.md +377 -0
  90. package/.claude/skills/react.md +230 -0
  91. package/.claude/skills/redis.md +391 -0
  92. package/.claude/skills/refactoring.md +143 -0
  93. package/.claude/skills/remix.md +246 -0
  94. package/.claude/skills/rest-api.md +490 -0
  95. package/.claude/skills/rocket.md +366 -0
  96. package/.claude/skills/rust.md +341 -0
  97. package/.claude/skills/sass.md +380 -0
  98. package/.claude/skills/sea-orm.md +382 -0
  99. package/.claude/skills/security.md +167 -0
  100. package/.claude/skills/sequelize.md +395 -0
  101. package/.claude/skills/spring-boot.md +416 -0
  102. package/.claude/skills/sqlalchemy.md +269 -0
  103. package/.claude/skills/sqlx-rust.md +408 -0
  104. package/.claude/skills/state-jotai.md +346 -0
  105. package/.claude/skills/state-mobx.md +353 -0
  106. package/.claude/skills/state-pinia.md +431 -0
  107. package/.claude/skills/state-redux.md +337 -0
  108. package/.claude/skills/state-tanstack-query.md +434 -0
  109. package/.claude/skills/state-zustand.md +340 -0
  110. package/.claude/skills/styled-components.md +403 -0
  111. package/.claude/skills/svelte.md +238 -0
  112. package/.claude/skills/sveltekit.md +207 -0
  113. package/.claude/skills/symfony.md +437 -0
  114. package/.claude/skills/tailwind.md +279 -0
  115. package/.claude/skills/terraform.md +394 -0
  116. package/.claude/skills/testing-library.md +371 -0
  117. package/.claude/skills/trpc.md +426 -0
  118. package/.claude/skills/typeorm.md +368 -0
  119. package/.claude/skills/vitest.md +330 -0
  120. package/.claude/skills/vue.md +202 -0
  121. package/.claude/skills/warp.md +365 -0
  122. package/README.md +163 -52
  123. package/package.json +1 -1
  124. package/system/triggers.md +256 -17
@@ -0,0 +1,371 @@
1
+ # Playwright Skill
2
+
3
+ ## Test Structure
4
+ \`\`\`typescript
5
+ import { test, expect } from '@playwright/test';
6
+
7
+ test.describe('Authentication', () => {
8
+ test.beforeEach(async ({ page }) => {
9
+ await page.goto('/login');
10
+ });
11
+
12
+ test('should login successfully', async ({ page }) => {
13
+ // Use accessible selectors (recommended)
14
+ await page.getByLabel('Email').fill('test@example.com');
15
+ await page.getByLabel('Password').fill('password123');
16
+ await page.getByRole('button', { name: 'Sign in' }).click();
17
+
18
+ // Web-first assertions (auto-wait)
19
+ await expect(page).toHaveURL('/dashboard');
20
+ await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
21
+ });
22
+
23
+ test('should show error for invalid credentials', async ({ page }) => {
24
+ await page.getByLabel('Email').fill('wrong@example.com');
25
+ await page.getByLabel('Password').fill('wrongpassword');
26
+ await page.getByRole('button', { name: 'Sign in' }).click();
27
+
28
+ await expect(page.getByText('Invalid credentials')).toBeVisible();
29
+ await expect(page).toHaveURL('/login');
30
+ });
31
+ });
32
+ \`\`\`
33
+
34
+ ## Locators (Selectors)
35
+ \`\`\`typescript
36
+ // Recommended: Accessible selectors
37
+ page.getByRole('button', { name: 'Submit' });
38
+ page.getByRole('textbox', { name: 'Email' });
39
+ page.getByRole('checkbox', { name: 'Remember me' });
40
+ page.getByRole('link', { name: 'Sign up' });
41
+ page.getByRole('heading', { name: 'Welcome', level: 1 });
42
+
43
+ page.getByLabel('Email'); // Form labels
44
+ page.getByPlaceholder('Enter email');
45
+ page.getByText('Submit'); // Exact text
46
+ page.getByText(/submit/i); // Regex
47
+ page.getByAltText('Logo'); // Image alt
48
+ page.getByTitle('Close'); // Title attribute
49
+
50
+ // Test IDs (when accessibility isn't possible)
51
+ page.getByTestId('submit-button'); // data-testid="submit-button"
52
+
53
+ // CSS/XPath (use sparingly)
54
+ page.locator('.submit-btn');
55
+ page.locator('#email');
56
+ page.locator('css=button.primary');
57
+ page.locator('xpath=//button[@type="submit"]');
58
+
59
+ // Chaining and filtering
60
+ page.getByRole('listitem').filter({ hasText: 'Product 1' });
61
+ page.getByRole('listitem').filter({ has: page.getByRole('button') });
62
+ page.locator('article').filter({ hasText: 'Playwright' }).getByRole('link');
63
+
64
+ // Nth element
65
+ page.getByRole('listitem').nth(2);
66
+ page.getByRole('listitem').first();
67
+ page.getByRole('listitem').last();
68
+ \`\`\`
69
+
70
+ ## Actions
71
+ \`\`\`typescript
72
+ // Click actions
73
+ await page.getByRole('button').click();
74
+ await page.getByRole('button').dblclick();
75
+ await page.getByRole('button').click({ button: 'right' });
76
+ await page.getByRole('button').click({ modifiers: ['Shift'] });
77
+ await page.getByRole('link').click({ force: true }); // Skip actionability checks
78
+
79
+ // Input actions
80
+ await page.getByLabel('Name').fill('John Doe');
81
+ await page.getByLabel('Name').clear();
82
+ await page.getByLabel('Name').type('John', { delay: 100 }); // Simulate typing
83
+ await page.getByLabel('Name').press('Enter');
84
+ await page.getByLabel('Name').pressSequentially('Hello'); // Key by key
85
+
86
+ // Select and checkbox
87
+ await page.getByLabel('Country').selectOption('US');
88
+ await page.getByLabel('Country').selectOption({ label: 'United States' });
89
+ await page.getByRole('checkbox').check();
90
+ await page.getByRole('checkbox').uncheck();
91
+ await page.getByRole('radio', { name: 'Option 1' }).check();
92
+
93
+ // File upload
94
+ await page.getByLabel('Upload').setInputFiles('file.pdf');
95
+ await page.getByLabel('Upload').setInputFiles(['file1.pdf', 'file2.pdf']);
96
+
97
+ // Drag and drop
98
+ await page.getByText('Drag me').dragTo(page.getByText('Drop here'));
99
+
100
+ // Hover
101
+ await page.getByRole('button').hover();
102
+
103
+ // Focus
104
+ await page.getByLabel('Email').focus();
105
+ \`\`\`
106
+
107
+ ## Assertions
108
+ \`\`\`typescript
109
+ // Page assertions
110
+ await expect(page).toHaveURL('/dashboard');
111
+ await expect(page).toHaveURL(/dashboard/);
112
+ await expect(page).toHaveTitle('Dashboard');
113
+
114
+ // Element assertions (auto-wait)
115
+ await expect(locator).toBeVisible();
116
+ await expect(locator).toBeHidden();
117
+ await expect(locator).toBeEnabled();
118
+ await expect(locator).toBeDisabled();
119
+ await expect(locator).toBeChecked();
120
+ await expect(locator).toBeFocused();
121
+ await expect(locator).toBeEditable();
122
+
123
+ // Content assertions
124
+ await expect(locator).toHaveText('Hello');
125
+ await expect(locator).toHaveText(/hello/i);
126
+ await expect(locator).toContainText('Hello');
127
+ await expect(locator).toHaveValue('input value');
128
+ await expect(locator).toHaveAttribute('href', '/home');
129
+ await expect(locator).toHaveClass(/active/);
130
+ await expect(locator).toHaveCSS('color', 'rgb(0, 0, 0)');
131
+ await expect(locator).toHaveCount(5);
132
+
133
+ // Negation
134
+ await expect(locator).not.toBeVisible();
135
+
136
+ // Soft assertions (don't stop test on failure)
137
+ await expect.soft(locator).toHaveText('Hello');
138
+ \`\`\`
139
+
140
+ ## Page Object Model
141
+ \`\`\`typescript
142
+ // pages/LoginPage.ts
143
+ import { Page, Locator, expect } from '@playwright/test';
144
+
145
+ export class LoginPage {
146
+ readonly page: Page;
147
+ readonly emailInput: Locator;
148
+ readonly passwordInput: Locator;
149
+ readonly submitButton: Locator;
150
+ readonly errorMessage: Locator;
151
+
152
+ constructor(page: Page) {
153
+ this.page = page;
154
+ this.emailInput = page.getByLabel('Email');
155
+ this.passwordInput = page.getByLabel('Password');
156
+ this.submitButton = page.getByRole('button', { name: 'Sign in' });
157
+ this.errorMessage = page.getByRole('alert');
158
+ }
159
+
160
+ async goto() {
161
+ await this.page.goto('/login');
162
+ }
163
+
164
+ async login(email: string, password: string) {
165
+ await this.emailInput.fill(email);
166
+ await this.passwordInput.fill(password);
167
+ await this.submitButton.click();
168
+ }
169
+
170
+ async expectError(message: string) {
171
+ await expect(this.errorMessage).toHaveText(message);
172
+ }
173
+ }
174
+
175
+ // tests/login.spec.ts
176
+ import { test, expect } from '@playwright/test';
177
+ import { LoginPage } from '../pages/LoginPage';
178
+
179
+ test('should login', async ({ page }) => {
180
+ const loginPage = new LoginPage(page);
181
+ await loginPage.goto();
182
+ await loginPage.login('user@example.com', 'password123');
183
+ await expect(page).toHaveURL('/dashboard');
184
+ });
185
+ \`\`\`
186
+
187
+ ## Fixtures
188
+ \`\`\`typescript
189
+ // fixtures.ts
190
+ import { test as base, expect } from '@playwright/test';
191
+ import { LoginPage } from './pages/LoginPage';
192
+ import { DashboardPage } from './pages/DashboardPage';
193
+
194
+ type MyFixtures = {
195
+ loginPage: LoginPage;
196
+ dashboardPage: DashboardPage;
197
+ authenticatedPage: void;
198
+ };
199
+
200
+ export const test = base.extend<MyFixtures>({
201
+ loginPage: async ({ page }, use) => {
202
+ const loginPage = new LoginPage(page);
203
+ await use(loginPage);
204
+ },
205
+
206
+ dashboardPage: async ({ page }, use) => {
207
+ await use(new DashboardPage(page));
208
+ },
209
+
210
+ // Auto-login fixture
211
+ authenticatedPage: async ({ page }, use) => {
212
+ // Set auth cookies/storage
213
+ await page.context().addCookies([{
214
+ name: 'auth-token',
215
+ value: 'test-token',
216
+ domain: 'localhost',
217
+ path: '/',
218
+ }]);
219
+ await use();
220
+ },
221
+ });
222
+
223
+ // Usage in tests
224
+ test('should show dashboard', async ({ dashboardPage, authenticatedPage }) => {
225
+ await dashboardPage.goto();
226
+ await dashboardPage.expectWelcomeMessage();
227
+ });
228
+ \`\`\`
229
+
230
+ ## API Testing
231
+ \`\`\`typescript
232
+ import { test, expect } from '@playwright/test';
233
+
234
+ test('API: should create user', async ({ request }) => {
235
+ const response = await request.post('/api/users', {
236
+ data: { email: 'test@example.com', name: 'Test' },
237
+ });
238
+
239
+ expect(response.ok()).toBeTruthy();
240
+ expect(response.status()).toBe(201);
241
+
242
+ const user = await response.json();
243
+ expect(user).toMatchObject({
244
+ email: 'test@example.com',
245
+ name: 'Test',
246
+ });
247
+ });
248
+
249
+ test('API: should handle authentication', async ({ request }) => {
250
+ // Login and get token
251
+ const loginResponse = await request.post('/api/login', {
252
+ data: { email: 'test@example.com', password: 'password123' },
253
+ });
254
+ const { token } = await loginResponse.json();
255
+
256
+ // Use token in subsequent requests
257
+ const response = await request.get('/api/profile', {
258
+ headers: { Authorization: \`Bearer \${token}\` },
259
+ });
260
+
261
+ expect(response.ok()).toBeTruthy();
262
+ });
263
+ \`\`\`
264
+
265
+ ## Network Mocking
266
+ \`\`\`typescript
267
+ test('should mock API response', async ({ page }) => {
268
+ // Mock API response
269
+ await page.route('/api/users', async (route) => {
270
+ await route.fulfill({
271
+ status: 200,
272
+ contentType: 'application/json',
273
+ body: JSON.stringify([{ id: 1, name: 'Mocked User' }]),
274
+ });
275
+ });
276
+
277
+ await page.goto('/users');
278
+ await expect(page.getByText('Mocked User')).toBeVisible();
279
+ });
280
+
281
+ test('should intercept and modify response', async ({ page }) => {
282
+ await page.route('/api/users', async (route) => {
283
+ const response = await route.fetch();
284
+ const json = await response.json();
285
+ json.push({ id: 999, name: 'Injected User' });
286
+ await route.fulfill({ response, json });
287
+ });
288
+ });
289
+
290
+ test('should abort request', async ({ page }) => {
291
+ await page.route('**/*.{png,jpg,jpeg}', route => route.abort());
292
+ await page.goto('/page'); // Images won't load
293
+ });
294
+
295
+ // Wait for specific request
296
+ const responsePromise = page.waitForResponse('/api/users');
297
+ await page.getByRole('button', { name: 'Load' }).click();
298
+ const response = await responsePromise;
299
+ \`\`\`
300
+
301
+ ## Visual Testing
302
+ \`\`\`typescript
303
+ test('should match screenshot', async ({ page }) => {
304
+ await page.goto('/');
305
+
306
+ // Full page screenshot
307
+ await expect(page).toHaveScreenshot('homepage.png');
308
+
309
+ // Element screenshot
310
+ await expect(page.getByRole('main')).toHaveScreenshot('main-content.png');
311
+
312
+ // With options
313
+ await expect(page).toHaveScreenshot('homepage.png', {
314
+ maxDiffPixels: 100,
315
+ threshold: 0.2,
316
+ animations: 'disabled',
317
+ });
318
+ });
319
+
320
+ // Update screenshots: npx playwright test --update-snapshots
321
+ \`\`\`
322
+
323
+ ## Configuration (playwright.config.ts)
324
+ \`\`\`typescript
325
+ import { defineConfig, devices } from '@playwright/test';
326
+
327
+ export default defineConfig({
328
+ testDir: './tests',
329
+ fullyParallel: true,
330
+ forbidOnly: !!process.env.CI,
331
+ retries: process.env.CI ? 2 : 0,
332
+ workers: process.env.CI ? 1 : undefined,
333
+ reporter: [['html'], ['list']],
334
+
335
+ use: {
336
+ baseURL: 'http://localhost:3000',
337
+ trace: 'on-first-retry',
338
+ screenshot: 'only-on-failure',
339
+ video: 'on-first-retry',
340
+ },
341
+
342
+ projects: [
343
+ { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
344
+ { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
345
+ { name: 'webkit', use: { ...devices['Desktop Safari'] } },
346
+ { name: 'Mobile Chrome', use: { ...devices['Pixel 5'] } },
347
+ ],
348
+
349
+ webServer: {
350
+ command: 'npm run dev',
351
+ url: 'http://localhost:3000',
352
+ reuseExistingServer: !process.env.CI,
353
+ },
354
+ });
355
+ \`\`\`
356
+
357
+ ## ❌ DON'T
358
+ - Use hard-coded waits (\`page.waitForTimeout\`)
359
+ - Use CSS selectors when accessible selectors work
360
+ - Test implementation details
361
+ - Share state between tests
362
+ - Forget to handle dynamic content
363
+
364
+ ## ✅ DO
365
+ - Use web-first assertions (auto-wait)
366
+ - Use accessible selectors (getByRole, getByLabel)
367
+ - Use Page Object Model for large apps
368
+ - Run tests in parallel
369
+ - Use fixtures for shared setup
370
+ - Use API testing for backend validation
371
+ - Use trace viewer for debugging
@@ -0,0 +1,257 @@
1
+ # PostgreSQL Skill
2
+
3
+ ## Schema Design
4
+ \`\`\`sql
5
+ -- Table with constraints
6
+ CREATE TABLE users (
7
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
8
+ email VARCHAR(255) NOT NULL UNIQUE,
9
+ name VARCHAR(100) NOT NULL,
10
+ password_hash VARCHAR(255) NOT NULL,
11
+ role VARCHAR(20) DEFAULT 'user' CHECK (role IN ('user', 'admin', 'moderator')),
12
+ settings JSONB DEFAULT '{}',
13
+ created_at TIMESTAMPTZ DEFAULT NOW(),
14
+ updated_at TIMESTAMPTZ DEFAULT NOW(),
15
+ deleted_at TIMESTAMPTZ
16
+ );
17
+
18
+ -- Foreign key with cascade
19
+ CREATE TABLE posts (
20
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
21
+ title VARCHAR(255) NOT NULL,
22
+ content TEXT,
23
+ published BOOLEAN DEFAULT FALSE,
24
+ author_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
25
+ created_at TIMESTAMPTZ DEFAULT NOW()
26
+ );
27
+
28
+ -- Many-to-many junction table
29
+ CREATE TABLE post_tags (
30
+ post_id UUID REFERENCES posts(id) ON DELETE CASCADE,
31
+ tag_id UUID REFERENCES tags(id) ON DELETE CASCADE,
32
+ PRIMARY KEY (post_id, tag_id)
33
+ );
34
+
35
+ -- Enum type (alternative to CHECK constraint)
36
+ CREATE TYPE user_status AS ENUM ('active', 'inactive', 'suspended');
37
+ ALTER TABLE users ADD COLUMN status user_status DEFAULT 'active';
38
+ \`\`\`
39
+
40
+ ## Indexes
41
+ \`\`\`sql
42
+ -- B-tree index (default, good for equality and range)
43
+ CREATE INDEX idx_users_email ON users(email);
44
+
45
+ -- Compound index (order matters!)
46
+ CREATE INDEX idx_posts_author_created ON posts(author_id, created_at DESC);
47
+
48
+ -- Partial index (smaller, more efficient)
49
+ CREATE INDEX idx_active_users ON users(email) WHERE deleted_at IS NULL;
50
+
51
+ -- GIN index for JSONB
52
+ CREATE INDEX idx_users_settings ON users USING GIN (settings);
53
+
54
+ -- GIN index for full-text search
55
+ CREATE INDEX idx_posts_content_search ON posts USING GIN (to_tsvector('english', content));
56
+
57
+ -- Unique index with condition
58
+ CREATE UNIQUE INDEX idx_unique_email_active ON users(email) WHERE deleted_at IS NULL;
59
+
60
+ -- Covering index (includes non-key columns)
61
+ CREATE INDEX idx_posts_author ON posts(author_id) INCLUDE (title, created_at);
62
+ \`\`\`
63
+
64
+ ## Common Queries
65
+ \`\`\`sql
66
+ -- Pagination (offset-based)
67
+ SELECT * FROM posts
68
+ WHERE published = true
69
+ ORDER BY created_at DESC
70
+ LIMIT 20 OFFSET 40;
71
+
72
+ -- Pagination (cursor-based, more efficient)
73
+ SELECT * FROM posts
74
+ WHERE published = true AND created_at < '2024-01-15T10:00:00Z'
75
+ ORDER BY created_at DESC
76
+ LIMIT 20;
77
+
78
+ -- Upsert (INSERT ... ON CONFLICT)
79
+ INSERT INTO users (email, name)
80
+ VALUES ('user@example.com', 'John')
81
+ ON CONFLICT (email)
82
+ DO UPDATE SET name = EXCLUDED.name, updated_at = NOW();
83
+
84
+ -- Returning inserted/updated data
85
+ INSERT INTO users (email, name) VALUES ('new@example.com', 'New User')
86
+ RETURNING id, email, created_at;
87
+
88
+ -- Conditional update
89
+ UPDATE users
90
+ SET role = 'admin', updated_at = NOW()
91
+ WHERE id = '...'
92
+ RETURNING *;
93
+ \`\`\`
94
+
95
+ ## JSONB Operations
96
+ \`\`\`sql
97
+ -- Query JSONB field
98
+ SELECT * FROM users WHERE settings->>'theme' = 'dark';
99
+ SELECT * FROM users WHERE settings @> '{"notifications": true}';
100
+
101
+ -- Update JSONB field
102
+ UPDATE users
103
+ SET settings = settings || '{"theme": "light"}'
104
+ WHERE id = '...';
105
+
106
+ -- Remove JSONB key
107
+ UPDATE users
108
+ SET settings = settings - 'oldKey'
109
+ WHERE id = '...';
110
+
111
+ -- JSONB array operations
112
+ SELECT * FROM users WHERE settings->'tags' ? 'premium';
113
+ \`\`\`
114
+
115
+ ## CTEs (Common Table Expressions)
116
+ \`\`\`sql
117
+ -- Basic CTE
118
+ WITH active_users AS (
119
+ SELECT * FROM users WHERE deleted_at IS NULL
120
+ )
121
+ SELECT u.*, COUNT(p.id) as post_count
122
+ FROM active_users u
123
+ LEFT JOIN posts p ON u.id = p.author_id
124
+ GROUP BY u.id;
125
+
126
+ -- Recursive CTE (hierarchical data)
127
+ WITH RECURSIVE category_tree AS (
128
+ -- Base case
129
+ SELECT id, name, parent_id, 0 as depth
130
+ FROM categories
131
+ WHERE parent_id IS NULL
132
+
133
+ UNION ALL
134
+
135
+ -- Recursive case
136
+ SELECT c.id, c.name, c.parent_id, ct.depth + 1
137
+ FROM categories c
138
+ JOIN category_tree ct ON c.parent_id = ct.id
139
+ )
140
+ SELECT * FROM category_tree ORDER BY depth, name;
141
+
142
+ -- CTE for data modification
143
+ WITH deleted AS (
144
+ DELETE FROM users WHERE last_login < NOW() - INTERVAL '1 year'
145
+ RETURNING *
146
+ )
147
+ INSERT INTO deleted_users SELECT * FROM deleted;
148
+ \`\`\`
149
+
150
+ ## Window Functions
151
+ \`\`\`sql
152
+ -- Row number for pagination
153
+ SELECT *, ROW_NUMBER() OVER (ORDER BY created_at DESC) as row_num
154
+ FROM posts;
155
+
156
+ -- Running total
157
+ SELECT
158
+ date,
159
+ amount,
160
+ SUM(amount) OVER (ORDER BY date) as running_total
161
+ FROM transactions;
162
+
163
+ -- Rank within groups
164
+ SELECT
165
+ author_id,
166
+ title,
167
+ created_at,
168
+ ROW_NUMBER() OVER (PARTITION BY author_id ORDER BY created_at DESC) as post_rank
169
+ FROM posts;
170
+
171
+ -- Get previous/next values
172
+ SELECT
173
+ title,
174
+ created_at,
175
+ LAG(title) OVER (ORDER BY created_at) as prev_title,
176
+ LEAD(title) OVER (ORDER BY created_at) as next_title
177
+ FROM posts;
178
+ \`\`\`
179
+
180
+ ## Full-Text Search
181
+ \`\`\`sql
182
+ -- Basic full-text search
183
+ SELECT * FROM posts
184
+ WHERE to_tsvector('english', title || ' ' || content) @@ plainto_tsquery('english', 'search terms');
185
+
186
+ -- With ranking
187
+ SELECT
188
+ title,
189
+ ts_rank(to_tsvector('english', content), query) as rank
190
+ FROM posts, plainto_tsquery('english', 'search terms') query
191
+ WHERE to_tsvector('english', content) @@ query
192
+ ORDER BY rank DESC;
193
+
194
+ -- Generated tsvector column (for performance)
195
+ ALTER TABLE posts ADD COLUMN search_vector tsvector
196
+ GENERATED ALWAYS AS (to_tsvector('english', title || ' ' || COALESCE(content, ''))) STORED;
197
+ CREATE INDEX idx_posts_search ON posts USING GIN (search_vector);
198
+ \`\`\`
199
+
200
+ ## Query Optimization
201
+ \`\`\`sql
202
+ -- Analyze query plan
203
+ EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'test@example.com';
204
+
205
+ -- Analyze with buffers and timing
206
+ EXPLAIN (ANALYZE, BUFFERS, TIMING) SELECT * FROM posts WHERE author_id = '...';
207
+
208
+ -- Update table statistics
209
+ ANALYZE users;
210
+
211
+ -- Check index usage
212
+ SELECT
213
+ schemaname, tablename, indexname, idx_scan, idx_tup_read
214
+ FROM pg_stat_user_indexes
215
+ ORDER BY idx_scan DESC;
216
+
217
+ -- Find slow queries (enable pg_stat_statements)
218
+ SELECT query, calls, mean_exec_time, total_exec_time
219
+ FROM pg_stat_statements
220
+ ORDER BY total_exec_time DESC
221
+ LIMIT 10;
222
+ \`\`\`
223
+
224
+ ## Transactions & Locking
225
+ \`\`\`sql
226
+ -- Transaction with isolation level
227
+ BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
228
+ -- ... operations ...
229
+ COMMIT;
230
+
231
+ -- Advisory locks (application-level)
232
+ SELECT pg_advisory_lock(123);
233
+ -- ... do work ...
234
+ SELECT pg_advisory_unlock(123);
235
+
236
+ -- Row-level lock
237
+ SELECT * FROM accounts WHERE id = '...' FOR UPDATE;
238
+ -- Now other transactions wait for this lock
239
+ \`\`\`
240
+
241
+ ## ❌ DON'T
242
+ - Use SELECT * in production queries
243
+ - Create indexes on every column (maintain costs)
244
+ - Use OFFSET for deep pagination
245
+ - Store large blobs in PostgreSQL (use object storage)
246
+ - Skip EXPLAIN ANALYZE for slow queries
247
+ - Use implicit type conversions in WHERE clauses
248
+
249
+ ## ✅ DO
250
+ - Use UUIDs or BIGSERIAL for primary keys
251
+ - Create partial indexes for filtered queries
252
+ - Use JSONB for flexible schema parts
253
+ - Use CTEs for complex queries
254
+ - Use cursor-based pagination for large datasets
255
+ - Run ANALYZE after bulk data changes
256
+ - Use connection pooling (PgBouncer)
257
+ - Set appropriate work_mem for complex queries