@jaypie/mcp 0.3.1 → 0.3.4

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 (42) hide show
  1. package/dist/aws-B3dW_-bD.js +1202 -0
  2. package/dist/aws-B3dW_-bD.js.map +1 -0
  3. package/dist/index.js +357 -1200
  4. package/dist/index.js.map +1 -1
  5. package/dist/suite.d.ts +1 -0
  6. package/dist/suite.js +1252 -0
  7. package/dist/suite.js.map +1 -0
  8. package/package.json +11 -2
  9. package/prompts/Jaypie_Fabric_MCP.md +22 -2
  10. package/prompts/Jaypie_Fabric_Package.md +86 -0
  11. package/prompts/Jaypie_MCP_Package.md +34 -2
  12. package/release-notes/constructs/1.2.17.md +11 -0
  13. package/release-notes/fabric/0.1.1.md +17 -0
  14. package/release-notes/fabric/0.1.2.md +11 -0
  15. package/release-notes/mcp/0.3.2.md +14 -0
  16. package/release-notes/mcp/0.3.3.md +12 -0
  17. package/release-notes/mcp/0.3.4.md +36 -0
  18. package/skills/agents.md +25 -0
  19. package/skills/aws.md +107 -0
  20. package/skills/cdk.md +141 -0
  21. package/skills/cicd.md +152 -0
  22. package/skills/datadog.md +129 -0
  23. package/skills/debugging.md +148 -0
  24. package/skills/dns.md +134 -0
  25. package/skills/dynamodb.md +140 -0
  26. package/skills/errors.md +142 -0
  27. package/skills/fabric.md +164 -0
  28. package/skills/index.md +7 -0
  29. package/skills/jaypie.md +100 -0
  30. package/skills/legacy.md +97 -0
  31. package/skills/logs.md +160 -0
  32. package/skills/mocks.md +174 -0
  33. package/skills/models.md +195 -0
  34. package/skills/releasenotes.md +94 -0
  35. package/skills/secrets.md +155 -0
  36. package/skills/services.md +175 -0
  37. package/skills/style.md +190 -0
  38. package/skills/tests.md +209 -0
  39. package/skills/tools.md +127 -0
  40. package/skills/topics.md +116 -0
  41. package/skills/variables.md +146 -0
  42. package/skills/writing.md +153 -0
@@ -0,0 +1,155 @@
1
+ ---
2
+ description: Secret management with AWS Secrets Manager
3
+ related: aws, cdk, variables
4
+ ---
5
+
6
+ # Secret Management
7
+
8
+ Jaypie uses AWS Secrets Manager for secure credential storage.
9
+
10
+ ## Basic Usage
11
+
12
+ ```typescript
13
+ import { getSecret } from "jaypie";
14
+
15
+ const apiKey = await getSecret("my-api-key");
16
+ const dbUri = await getSecret("mongodb-connection-string");
17
+ ```
18
+
19
+ ## Environment Variables
20
+
21
+ Reference secrets via environment variables in CDK:
22
+
23
+ ```typescript
24
+ const handler = new JaypieLambda(this, "Handler", {
25
+ environment: {
26
+ SECRET_MONGODB_URI: "mongodb-connection-string",
27
+ SECRET_API_KEY: "third-party-api-key",
28
+ },
29
+ });
30
+ ```
31
+
32
+ In code:
33
+
34
+ ```typescript
35
+ const secretName = process.env.SECRET_MONGODB_URI;
36
+ const mongoUri = await getSecret(secretName);
37
+ ```
38
+
39
+ ## Creating Secrets
40
+
41
+ ### Via CDK
42
+
43
+ ```typescript
44
+ import { Secret } from "aws-cdk-lib/aws-secretsmanager";
45
+
46
+ const secret = new Secret(this, "ApiKey", {
47
+ secretName: `${projectKey}/api-key`,
48
+ description: "Third-party API key",
49
+ });
50
+
51
+ // Grant read access
52
+ secret.grantRead(lambdaFunction);
53
+ ```
54
+
55
+ ### Via AWS CLI
56
+
57
+ ```bash
58
+ aws secretsmanager create-secret \
59
+ --name "my-project/api-key" \
60
+ --secret-string "sk_live_abc123"
61
+ ```
62
+
63
+ ## Secret Naming Convention
64
+
65
+ Use project-prefixed names:
66
+
67
+ ```
68
+ {project-key}/{secret-name}
69
+
70
+ Examples:
71
+ - my-api/mongodb-uri
72
+ - my-api/stripe-key
73
+ - my-api/auth0-secret
74
+ ```
75
+
76
+ ## JSON Secrets
77
+
78
+ Store structured data:
79
+
80
+ ```bash
81
+ aws secretsmanager create-secret \
82
+ --name "my-project/db-credentials" \
83
+ --secret-string '{"username":"admin","password":"secret123"}'
84
+ ```
85
+
86
+ Retrieve in code:
87
+
88
+ ```typescript
89
+ const credentialsJson = await getSecret("my-project/db-credentials");
90
+ const credentials = JSON.parse(credentialsJson);
91
+ ```
92
+
93
+ ## Caching
94
+
95
+ Secrets are cached by default to reduce API calls:
96
+
97
+ ```typescript
98
+ // First call: fetches from Secrets Manager
99
+ const key1 = await getSecret("api-key");
100
+
101
+ // Second call: returns cached value
102
+ const key2 = await getSecret("api-key");
103
+ ```
104
+
105
+ Cache is scoped to Lambda execution context (warm starts reuse cache).
106
+
107
+ ## Rotation
108
+
109
+ Configure automatic rotation for supported secrets:
110
+
111
+ ```typescript
112
+ const secret = new Secret(this, "DbPassword", {
113
+ secretName: "my-project/db-password",
114
+ generateSecretString: {
115
+ excludePunctuation: true,
116
+ passwordLength: 32,
117
+ },
118
+ });
119
+
120
+ secret.addRotationSchedule("Rotation", {
121
+ automaticallyAfter: Duration.days(30),
122
+ rotationLambda: rotationFunction,
123
+ });
124
+ ```
125
+
126
+ ## Local Development
127
+
128
+ For local development, use environment variables:
129
+
130
+ ```bash
131
+ # .env.local (not committed)
132
+ MONGODB_URI=mongodb://localhost:27017/dev
133
+ API_KEY=test_key_123
134
+ ```
135
+
136
+ In code, check for direct value first:
137
+
138
+ ```typescript
139
+ const mongoUri = process.env.MONGODB_URI || await getSecret(process.env.SECRET_MONGODB_URI);
140
+ ```
141
+
142
+ ## IAM Permissions
143
+
144
+ Lambda needs `secretsmanager:GetSecretValue`:
145
+
146
+ ```typescript
147
+ secret.grantRead(lambdaFunction);
148
+
149
+ // Or via policy
150
+ lambdaFunction.addToRolePolicy(new PolicyStatement({
151
+ actions: ["secretsmanager:GetSecretValue"],
152
+ resources: [secret.secretArn],
153
+ }));
154
+ ```
155
+
@@ -0,0 +1,175 @@
1
+ ---
2
+ description: Service layer patterns and architecture
3
+ related: fabric, models, tests
4
+ ---
5
+
6
+ # Service Layer Patterns
7
+
8
+ Organizing business logic in Jaypie applications.
9
+
10
+ ## Service Structure
11
+
12
+ Keep business logic in service modules:
13
+
14
+ ```
15
+ src/
16
+ ├── handlers/ # Lambda/Express handlers
17
+ ├── services/ # Business logic
18
+ │ ├── user.ts
19
+ │ └── order.ts
20
+ ├── models/ # Data models
21
+ └── utils/ # Utilities
22
+ ```
23
+
24
+ ## Basic Service Pattern
25
+
26
+ ```typescript
27
+ // services/user.ts
28
+ import { log, NotFoundError, BadRequestError } from "jaypie";
29
+ import { User } from "../models/user.js";
30
+
31
+ export async function getUser(userId: string) {
32
+ log.debug("Getting user", { userId });
33
+
34
+ const user = await User.findById(userId);
35
+ if (!user) {
36
+ throw new NotFoundError(`User ${userId} not found`);
37
+ }
38
+
39
+ return user;
40
+ }
41
+
42
+ export async function createUser(input: UserCreateInput) {
43
+ log.info("Creating user", { email: input.email });
44
+
45
+ const existing = await User.findOne({ email: input.email });
46
+ if (existing) {
47
+ throw new BadRequestError("Email already registered");
48
+ }
49
+
50
+ return User.create(input);
51
+ }
52
+ ```
53
+
54
+ ## Handler Integration
55
+
56
+ Handlers call services:
57
+
58
+ ```typescript
59
+ // handlers/user.ts
60
+ import { lambdaHandler } from "@jaypie/lambda";
61
+ import { getUser, createUser } from "../services/user.js";
62
+
63
+ export const getUserHandler = lambdaHandler(async (event) => {
64
+ const { userId } = event.pathParameters;
65
+ return getUser(userId);
66
+ });
67
+
68
+ export const createUserHandler = lambdaHandler(async (event) => {
69
+ const input = JSON.parse(event.body);
70
+ return createUser(input);
71
+ });
72
+ ```
73
+
74
+ ## Service Dependencies
75
+
76
+ Inject dependencies for testability:
77
+
78
+ ```typescript
79
+ // services/notification.ts
80
+ import { log } from "jaypie";
81
+
82
+ export interface NotificationService {
83
+ sendEmail(to: string, subject: string, body: string): Promise<void>;
84
+ sendSms(to: string, message: string): Promise<void>;
85
+ }
86
+
87
+ export function createNotificationService(
88
+ emailClient: EmailClient,
89
+ smsClient: SmsClient
90
+ ): NotificationService {
91
+ return {
92
+ async sendEmail(to, subject, body) {
93
+ log.info("Sending email", { to, subject });
94
+ await emailClient.send({ to, subject, body });
95
+ },
96
+ async sendSms(to, message) {
97
+ log.info("Sending SMS", { to });
98
+ await smsClient.send({ to, message });
99
+ },
100
+ };
101
+ }
102
+ ```
103
+
104
+ ## Transaction Patterns
105
+
106
+ For DynamoDB operations that must succeed together, use TransactWriteItems:
107
+
108
+ ```typescript
109
+ import { TransactWriteItemsCommand } from "@aws-sdk/client-dynamodb";
110
+
111
+ export async function transferFunds(fromId: string, toId: string, amount: number) {
112
+ const from = await getAccount(fromId);
113
+
114
+ if (from.balance < amount) {
115
+ throw new BadRequestError("Insufficient funds");
116
+ }
117
+
118
+ const command = new TransactWriteItemsCommand({
119
+ TransactItems: [
120
+ {
121
+ Update: {
122
+ TableName: TABLE_NAME,
123
+ Key: { pk: { S: `ACCOUNT#${fromId}` }, sk: { S: "BALANCE" } },
124
+ UpdateExpression: "SET balance = balance - :amount",
125
+ ExpressionAttributeValues: { ":amount": { N: String(amount) } },
126
+ },
127
+ },
128
+ {
129
+ Update: {
130
+ TableName: TABLE_NAME,
131
+ Key: { pk: { S: `ACCOUNT#${toId}` }, sk: { S: "BALANCE" } },
132
+ UpdateExpression: "SET balance = balance + :amount",
133
+ ExpressionAttributeValues: { ":amount": { N: String(amount) } },
134
+ },
135
+ },
136
+ ],
137
+ });
138
+
139
+ await dynamoClient.send(command);
140
+ log.info("Transfer completed", { fromId, toId, amount });
141
+ }
142
+ ```
143
+
144
+ ## Service Testing
145
+
146
+ ```typescript
147
+ import { describe, it, expect, vi, beforeEach } from "vitest";
148
+ import { getUser } from "./user.js";
149
+ import { User } from "../models/user.js";
150
+
151
+ vi.mock("../models/user.js");
152
+
153
+ describe("getUser", () => {
154
+ beforeEach(() => {
155
+ vi.clearAllMocks();
156
+ });
157
+
158
+ it("returns user when found", async () => {
159
+ const mockUser = { id: "123", name: "John" };
160
+ vi.mocked(User.findById).mockResolvedValue(mockUser);
161
+
162
+ const result = await getUser("123");
163
+
164
+ expect(result).toEqual(mockUser);
165
+ expect(User.findById).toHaveBeenCalledWith("123");
166
+ });
167
+
168
+ it("throws NotFoundError when missing", async () => {
169
+ vi.mocked(User.findById).mockResolvedValue(null);
170
+
171
+ await expect(getUser("123")).rejects.toThrow(NotFoundError);
172
+ });
173
+ });
174
+ ```
175
+
@@ -0,0 +1,190 @@
1
+ ---
2
+ description: Code style conventions and patterns
3
+ related: errors, tests
4
+ ---
5
+
6
+ # Code Style
7
+
8
+ Jaypie coding conventions and patterns.
9
+
10
+ ## General Rules
11
+
12
+ ### TypeScript Everywhere
13
+
14
+ Use TypeScript for all code:
15
+
16
+ ```typescript
17
+ // GOOD
18
+ function greet(name: string): string {
19
+ return `Hello, ${name}!`;
20
+ }
21
+
22
+ // BAD
23
+ function greet(name) {
24
+ return `Hello, ${name}!`;
25
+ }
26
+ ```
27
+
28
+ ### ESM Over CommonJS
29
+
30
+ Use ES modules:
31
+
32
+ ```typescript
33
+ // GOOD
34
+ import { log } from "jaypie";
35
+ export function myFunction() {}
36
+
37
+ // BAD
38
+ const { log } = require("jaypie");
39
+ module.exports = { myFunction };
40
+ ```
41
+
42
+ ### Alphabetize Everything
43
+
44
+ Alphabetize imports, object keys, exports:
45
+
46
+ ```typescript
47
+ // GOOD
48
+ import { ConfigurationError, log, NotFoundError } from "jaypie";
49
+
50
+ const config = {
51
+ apiKey: "...",
52
+ baseUrl: "...",
53
+ timeout: 5000,
54
+ };
55
+
56
+ // BAD
57
+ import { NotFoundError, log, ConfigurationError } from "jaypie";
58
+ ```
59
+
60
+ ## Function Signatures
61
+
62
+ ### Object Parameters
63
+
64
+ Use destructured objects for multiple parameters:
65
+
66
+ ```typescript
67
+ // GOOD
68
+ function createUser({ email, name, role }: CreateUserInput) {}
69
+
70
+ // GOOD (single required + optional config)
71
+ function fetchData(url: string, { timeout, retries }: FetchOptions = {}) {}
72
+
73
+ // BAD (multiple positional parameters)
74
+ function createUser(email: string, name: string, role: string) {}
75
+ ```
76
+
77
+ ### Allow Zero Arguments
78
+
79
+ Functions with optional config should allow no arguments:
80
+
81
+ ```typescript
82
+ // GOOD
83
+ function initialize(options: InitOptions = {}) {
84
+ const { timeout = 5000, retries = 3 } = options;
85
+ }
86
+
87
+ // Call with or without args
88
+ initialize();
89
+ initialize({ timeout: 10000 });
90
+ ```
91
+
92
+ ## Constants
93
+
94
+ ### File-Level Constants
95
+
96
+ Use SCREAMING_SNAKE_CASE for constants:
97
+
98
+ ```typescript
99
+ const DEFAULT_TIMEOUT = 5000;
100
+ const DATADOG_SITE = "datadoghq.com";
101
+ const HTTP_STATUS = {
102
+ OK: 200,
103
+ NOT_FOUND: 404,
104
+ } as const;
105
+ ```
106
+
107
+ ### Magic Numbers
108
+
109
+ Never use magic numbers inline:
110
+
111
+ ```typescript
112
+ // BAD
113
+ if (retries > 3) { ... }
114
+ await sleep(5000);
115
+
116
+ // GOOD
117
+ const MAX_RETRIES = 3;
118
+ const RETRY_DELAY_MS = 5000;
119
+
120
+ if (retries > MAX_RETRIES) { ... }
121
+ await sleep(RETRY_DELAY_MS);
122
+ ```
123
+
124
+ ## Error Handling
125
+
126
+ ### Never Vanilla Error
127
+
128
+ ```typescript
129
+ // BAD
130
+ throw new Error("Missing config");
131
+
132
+ // GOOD
133
+ import { ConfigurationError } from "jaypie";
134
+ throw new ConfigurationError("Missing config");
135
+ ```
136
+
137
+ ### Error Context
138
+
139
+ Include relevant context:
140
+
141
+ ```typescript
142
+ throw new NotFoundError("User not found", {
143
+ context: { userId, searchedAt: new Date() }
144
+ });
145
+ ```
146
+
147
+ ## Avoid Over-Engineering
148
+
149
+ ### No Premature Abstraction
150
+
151
+ ```typescript
152
+ // BAD - Unnecessary abstraction for one use
153
+ const formatName = (name) => name.toUpperCase();
154
+ const processUser = (user) => ({ ...user, name: formatName(user.name) });
155
+
156
+ // GOOD - Simple and direct
157
+ const processUser = (user) => ({ ...user, name: user.name.toUpperCase() });
158
+ ```
159
+
160
+ ### Minimal Changes
161
+
162
+ Only modify what's requested:
163
+
164
+ - Bug fix? Fix the bug, nothing else.
165
+ - Add feature? Add only that feature.
166
+ - Don't add docstrings to unchanged code.
167
+ - Don't refactor surrounding code.
168
+
169
+ ## Naming Conventions
170
+
171
+ | Type | Convention | Example |
172
+ |------|-----------|---------|
173
+ | Functions | camelCase | `getUser`, `createOrder` |
174
+ | Classes | PascalCase | `UserService`, `OrderModel` |
175
+ | Constants | SCREAMING_SNAKE | `MAX_RETRIES`, `API_URL` |
176
+ | Files | kebab-case | `user-service.ts`, `order-model.ts` |
177
+ | Types/Interfaces | PascalCase | `UserInput`, `IUserService` |
178
+
179
+ ## Lint Rules
180
+
181
+ Use `@jaypie/eslint`:
182
+
183
+ ```javascript
184
+ // eslint.config.mjs
185
+ import jaypie from "@jaypie/eslint";
186
+ export default [...jaypie];
187
+ ```
188
+
189
+ Always run `npm run format` before committing.
190
+
@@ -0,0 +1,209 @@
1
+ ---
2
+ description: Testing patterns with Vitest
3
+ related: mocks, errors
4
+ ---
5
+
6
+ # Testing Patterns
7
+
8
+ Jaypie uses Vitest for testing with specific patterns and mocks.
9
+
10
+ ## Setup
11
+
12
+ ### vitest.config.ts
13
+
14
+ ```typescript
15
+ import { defineConfig } from "vitest/config";
16
+
17
+ export default defineConfig({
18
+ test: {
19
+ coverage: {
20
+ reporter: ["text", "json", "html"],
21
+ },
22
+ globals: true,
23
+ setupFiles: ["./vitest.setup.ts"],
24
+ },
25
+ });
26
+ ```
27
+
28
+ ### vitest.setup.ts
29
+
30
+ ```typescript
31
+ import { vi } from "vitest";
32
+
33
+ vi.mock("jaypie", async () => {
34
+ const { mockJaypie } = await import("@jaypie/testkit");
35
+ return mockJaypie(vi);
36
+ });
37
+ ```
38
+
39
+ ## Test Structure
40
+
41
+ ### File Organization
42
+
43
+ ```
44
+ src/
45
+ ├── services/
46
+ │ └── user.ts
47
+ └── __tests__/
48
+ └── user.spec.ts
49
+
50
+ # Or co-located:
51
+ src/
52
+ └── services/
53
+ ├── user.ts
54
+ └── user.spec.ts
55
+ ```
56
+
57
+ ### Test File Pattern
58
+
59
+ ```typescript
60
+ import { describe, expect, it, vi, beforeEach } from "vitest";
61
+
62
+ describe("UserService", () => {
63
+ describe("getUser", () => {
64
+ it("returns user when found", async () => {
65
+ // Arrange
66
+ const mockUser = { id: "123", name: "John" };
67
+ vi.mocked(User.findById).mockResolvedValue(mockUser);
68
+
69
+ // Act
70
+ const result = await getUser("123");
71
+
72
+ // Assert
73
+ expect(result).toEqual(mockUser);
74
+ });
75
+ });
76
+ });
77
+ ```
78
+
79
+ ## Running Tests
80
+
81
+ ```bash
82
+ # Run all tests (non-watch mode)
83
+ npm test
84
+
85
+ # Run specific package
86
+ npm test -w packages/my-package
87
+
88
+ # Watch mode (development)
89
+ npx vitest
90
+
91
+ # With coverage
92
+ npm test -- --coverage
93
+ ```
94
+
95
+ **Important**: Always use `npm test` or `vitest run`, not bare `vitest` which runs in watch mode.
96
+
97
+ ## Mocking
98
+
99
+ ### Module Mocks
100
+
101
+ ```typescript
102
+ vi.mock("./user-service.js", () => ({
103
+ getUser: vi.fn(),
104
+ createUser: vi.fn(),
105
+ }));
106
+
107
+ // Access mocked functions
108
+ import { getUser } from "./user-service.js";
109
+
110
+ vi.mocked(getUser).mockResolvedValue({ id: "123" });
111
+ ```
112
+
113
+ ### Jaypie Mocks
114
+
115
+ ```typescript
116
+ import { log, getSecret } from "jaypie";
117
+
118
+ it("logs the operation", async () => {
119
+ await myFunction();
120
+ expect(log.info).toHaveBeenCalledWith("Operation started");
121
+ });
122
+
123
+ it("uses secrets", async () => {
124
+ vi.mocked(getSecret).mockResolvedValue("test-key");
125
+ await myFunction();
126
+ expect(getSecret).toHaveBeenCalledWith("api-key");
127
+ });
128
+ ```
129
+
130
+ ## Testing Errors
131
+
132
+ ```typescript
133
+ import { NotFoundError } from "jaypie";
134
+
135
+ it("throws NotFoundError when user missing", async () => {
136
+ vi.mocked(User.findById).mockResolvedValue(null);
137
+
138
+ await expect(getUser("invalid")).rejects.toThrow(NotFoundError);
139
+ });
140
+
141
+ it("includes error context", async () => {
142
+ vi.mocked(User.findById).mockResolvedValue(null);
143
+
144
+ try {
145
+ await getUser("invalid");
146
+ expect.fail("Should have thrown");
147
+ } catch (error) {
148
+ expect(error.context.userId).toBe("invalid");
149
+ }
150
+ });
151
+ ```
152
+
153
+ ## Async Testing
154
+
155
+ ```typescript
156
+ it("handles async operations", async () => {
157
+ const result = await asyncFunction();
158
+ expect(result).toBeDefined();
159
+ });
160
+
161
+ it("handles promises", () => {
162
+ return expect(asyncFunction()).resolves.toBeDefined();
163
+ });
164
+
165
+ it("handles rejections", () => {
166
+ return expect(failingFunction()).rejects.toThrow();
167
+ });
168
+ ```
169
+
170
+ ## Before/After Hooks
171
+
172
+ ```typescript
173
+ describe("UserService", () => {
174
+ beforeEach(() => {
175
+ vi.clearAllMocks();
176
+ });
177
+
178
+ afterEach(() => {
179
+ vi.resetAllMocks();
180
+ });
181
+
182
+ beforeAll(async () => {
183
+ // One-time setup
184
+ });
185
+
186
+ afterAll(async () => {
187
+ // One-time cleanup
188
+ });
189
+ });
190
+ ```
191
+
192
+ ## Snapshot Testing
193
+
194
+ ```typescript
195
+ it("renders expected output", () => {
196
+ const result = buildConfig({ env: "production" });
197
+ expect(result).toMatchSnapshot();
198
+ });
199
+ ```
200
+
201
+ ## Test Coverage
202
+
203
+ Aim for meaningful coverage, not 100%:
204
+
205
+ - Test business logic thoroughly
206
+ - Test error paths
207
+ - Skip trivial getters/setters
208
+ - Skip generated code
209
+