@l4yercak3/cli 1.1.12 → 1.2.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.
@@ -0,0 +1,894 @@
1
+ /**
2
+ * Code Generation Domain Tools
3
+ *
4
+ * Tools for generating API clients, sync adapters, and other
5
+ * integration code for Next.js projects.
6
+ *
7
+ * @module mcp/registry/domains/codegen
8
+ */
9
+
10
+ // Note: backendClient not currently used but reserved for future API-based code generation
11
+ // const backendClient = require('../../../api/backend-client');
12
+
13
+ /**
14
+ * CodeGen domain definition
15
+ */
16
+ module.exports = {
17
+ name: 'codegen',
18
+ description: 'Code generation - API clients, sync adapters, webhooks',
19
+ tools: [
20
+ // ========================================
21
+ // Schema Analysis Tools
22
+ // ========================================
23
+ {
24
+ name: 'l4yercak3_analyze_schema',
25
+ description: `Analyze a database schema and suggest mappings to L4YERCAK3 types.
26
+ Supports Prisma schema, SQL, Convex schema, and JSON model definitions.
27
+
28
+ Use this to help users understand how their existing models map to L4YERCAK3's
29
+ contact, event, form, and transaction types.`,
30
+ inputSchema: {
31
+ type: 'object',
32
+ properties: {
33
+ schema: {
34
+ type: 'string',
35
+ description: 'The database schema content (Prisma, SQL, Convex, or JSON)',
36
+ },
37
+ schemaType: {
38
+ type: 'string',
39
+ enum: ['prisma', 'sql', 'convex', 'json'],
40
+ description: 'Schema format (auto-detected if not specified)',
41
+ },
42
+ },
43
+ required: ['schema'],
44
+ },
45
+ requiresAuth: true,
46
+ handler: async (params, authContext) => {
47
+ // Parse schema and extract models
48
+ const models = parseSchema(params.schema, params.schemaType);
49
+
50
+ // Suggest mappings
51
+ const mappings = models.map(model => ({
52
+ localModel: model.name,
53
+ fields: model.fields,
54
+ suggestedMappings: suggestMappings(model),
55
+ }));
56
+
57
+ return {
58
+ models: models.length,
59
+ mappings,
60
+ layerCakeTypes: [
61
+ {
62
+ type: 'crm_contact',
63
+ description: 'Customer/lead/prospect contacts',
64
+ fields: ['email', 'firstName', 'lastName', 'phone', 'company', 'jobTitle', 'tags'],
65
+ },
66
+ {
67
+ type: 'crm_organization',
68
+ description: 'Companies/businesses',
69
+ fields: ['name', 'website', 'industry', 'size', 'phone', 'address'],
70
+ },
71
+ {
72
+ type: 'event',
73
+ description: 'Events (conferences, workshops, meetups)',
74
+ fields: ['name', 'description', 'startDate', 'endDate', 'location'],
75
+ },
76
+ {
77
+ type: 'form',
78
+ description: 'Forms (registration, surveys)',
79
+ fields: ['name', 'fields', 'status'],
80
+ },
81
+ {
82
+ type: 'product',
83
+ description: 'Products/tickets for sale',
84
+ fields: ['name', 'description', 'priceInCents', 'currency'],
85
+ },
86
+ {
87
+ type: 'transaction',
88
+ description: 'Financial transactions',
89
+ fields: ['amount', 'currency', 'status', 'contactId'],
90
+ },
91
+ ],
92
+ };
93
+ },
94
+ },
95
+
96
+ // ========================================
97
+ // API Client Generation
98
+ // ========================================
99
+ {
100
+ name: 'l4yercak3_generate_api_client',
101
+ description: `Generate a TypeScript API client for L4YERCAK3.
102
+ Creates a type-safe client with methods for the selected features.
103
+
104
+ Returns the code to write to your project.`,
105
+ inputSchema: {
106
+ type: 'object',
107
+ properties: {
108
+ features: {
109
+ type: 'array',
110
+ items: {
111
+ type: 'string',
112
+ enum: ['crm', 'events', 'forms', 'invoicing', 'checkout'],
113
+ },
114
+ description: 'Features to include in the client',
115
+ },
116
+ outputPath: {
117
+ type: 'string',
118
+ description: 'Suggested output path (default: src/lib/l4yercak3/client.ts)',
119
+ },
120
+ includeTypes: {
121
+ type: 'boolean',
122
+ description: 'Include TypeScript type definitions (default: true)',
123
+ },
124
+ },
125
+ required: ['features'],
126
+ },
127
+ requiresAuth: true,
128
+ handler: async (params, authContext) => {
129
+ const features = params.features || ['crm'];
130
+ const outputPath = params.outputPath || 'src/lib/l4yercak3/client.ts';
131
+ const includeTypes = params.includeTypes !== false;
132
+
133
+ // Generate client code
134
+ const code = generateApiClient(features, includeTypes);
135
+
136
+ return {
137
+ outputPath,
138
+ code,
139
+ features,
140
+ instructions: [
141
+ `1. Create the file: ${outputPath}`,
142
+ '2. Add L4YERCAK3_API_KEY to your .env.local',
143
+ '3. Import and use: import { l4yercak3 } from "@/lib/l4yercak3/client"',
144
+ ],
145
+ envVariables: [
146
+ { name: 'L4YERCAK3_API_KEY', description: 'Your L4YERCAK3 API key' },
147
+ { name: 'L4YERCAK3_BACKEND_URL', description: 'Backend URL (optional, defaults to production)' },
148
+ ],
149
+ };
150
+ },
151
+ },
152
+
153
+ // ========================================
154
+ // Sync Adapter Generation
155
+ // ========================================
156
+ {
157
+ name: 'l4yercak3_generate_sync_adapter',
158
+ description: `Generate a sync adapter to keep local models in sync with L4YERCAK3.
159
+ Creates code for bidirectional or one-way syncing.`,
160
+ inputSchema: {
161
+ type: 'object',
162
+ properties: {
163
+ localModel: {
164
+ type: 'string',
165
+ description: 'Name of the local model (e.g., "User", "Customer")',
166
+ },
167
+ layerCakeType: {
168
+ type: 'string',
169
+ enum: ['crm_contact', 'crm_organization', 'event', 'form', 'product'],
170
+ description: 'L4YERCAK3 type to sync with',
171
+ },
172
+ fieldMappings: {
173
+ type: 'array',
174
+ items: {
175
+ type: 'object',
176
+ properties: {
177
+ local: {
178
+ type: 'string',
179
+ description: 'Local field name',
180
+ },
181
+ layerCake: {
182
+ type: 'string',
183
+ description: 'L4YERCAK3 field name',
184
+ },
185
+ transform: {
186
+ type: 'string',
187
+ description: 'Optional transformation (e.g., "uppercase", "lowercase")',
188
+ },
189
+ },
190
+ required: ['local', 'layerCake'],
191
+ },
192
+ description: 'Field mappings between local and L4YERCAK3',
193
+ },
194
+ syncDirection: {
195
+ type: 'string',
196
+ enum: ['push', 'pull', 'bidirectional'],
197
+ description: 'Sync direction (default: bidirectional)',
198
+ },
199
+ outputPath: {
200
+ type: 'string',
201
+ description: 'Output path for the adapter',
202
+ },
203
+ },
204
+ required: ['localModel', 'layerCakeType', 'fieldMappings'],
205
+ },
206
+ requiresAuth: true,
207
+ handler: async (params, authContext) => {
208
+ const {
209
+ localModel,
210
+ layerCakeType,
211
+ fieldMappings,
212
+ syncDirection = 'bidirectional',
213
+ outputPath = `src/lib/l4yercak3/sync/${localModel.toLowerCase()}-adapter.ts`,
214
+ } = params;
215
+
216
+ const code = generateSyncAdapter(
217
+ localModel,
218
+ layerCakeType,
219
+ fieldMappings,
220
+ syncDirection
221
+ );
222
+
223
+ return {
224
+ outputPath,
225
+ code,
226
+ localModel,
227
+ layerCakeType,
228
+ syncDirection,
229
+ instructions: [
230
+ `1. Create the file: ${outputPath}`,
231
+ `2. Import the adapter in your ${localModel} service`,
232
+ '3. Call sync methods when creating/updating/deleting local records',
233
+ '4. Set up a webhook endpoint to receive updates from L4YERCAK3',
234
+ ],
235
+ };
236
+ },
237
+ },
238
+
239
+ // ========================================
240
+ // Webhook Handler Generation
241
+ // ========================================
242
+ {
243
+ name: 'l4yercak3_generate_webhook_handler',
244
+ description: `Generate a webhook handler for receiving L4YERCAK3 events.
245
+ Creates a Next.js API route that handles L4YERCAK3 webhooks.`,
246
+ inputSchema: {
247
+ type: 'object',
248
+ properties: {
249
+ events: {
250
+ type: 'array',
251
+ items: {
252
+ type: 'string',
253
+ enum: [
254
+ 'contact.created',
255
+ 'contact.updated',
256
+ 'contact.deleted',
257
+ 'event.created',
258
+ 'event.updated',
259
+ 'event.published',
260
+ 'form.submitted',
261
+ 'ticket.purchased',
262
+ 'payment.completed',
263
+ ],
264
+ },
265
+ description: 'Events to handle',
266
+ },
267
+ routerType: {
268
+ type: 'string',
269
+ enum: ['app', 'pages'],
270
+ description: 'Next.js router type (default: app)',
271
+ },
272
+ outputPath: {
273
+ type: 'string',
274
+ description: 'Output path for the webhook handler',
275
+ },
276
+ },
277
+ required: ['events'],
278
+ },
279
+ requiresAuth: true,
280
+ handler: async (params, authContext) => {
281
+ const events = params.events || [];
282
+ const routerType = params.routerType || 'app';
283
+ const outputPath =
284
+ params.outputPath ||
285
+ (routerType === 'app'
286
+ ? 'app/api/webhooks/l4yercak3/route.ts'
287
+ : 'pages/api/webhooks/l4yercak3.ts');
288
+
289
+ const code = generateWebhookHandler(events, routerType);
290
+
291
+ return {
292
+ outputPath,
293
+ code,
294
+ events,
295
+ routerType,
296
+ instructions: [
297
+ `1. Create the file: ${outputPath}`,
298
+ '2. Add L4YERCAK3_WEBHOOK_SECRET to your .env.local',
299
+ '3. Register the webhook URL in your L4YERCAK3 dashboard',
300
+ '4. Implement the event handlers for your specific use case',
301
+ ],
302
+ webhookUrl: 'https://your-domain.com/api/webhooks/l4yercak3',
303
+ };
304
+ },
305
+ },
306
+
307
+ // ========================================
308
+ // Environment File Generation
309
+ // ========================================
310
+ {
311
+ name: 'l4yercak3_generate_env_template',
312
+ description: `Generate environment variable template for L4YERCAK3 integration.`,
313
+ inputSchema: {
314
+ type: 'object',
315
+ properties: {
316
+ features: {
317
+ type: 'array',
318
+ items: {
319
+ type: 'string',
320
+ enum: ['crm', 'events', 'forms', 'invoicing', 'checkout', 'webhooks'],
321
+ },
322
+ description: 'Features to include env vars for',
323
+ },
324
+ },
325
+ required: ['features'],
326
+ },
327
+ requiresAuth: true,
328
+ handler: async (params, authContext) => {
329
+ const features = params.features || [];
330
+
331
+ let template = `# L4YERCAK3 Integration
332
+ # Add these to your .env.local file
333
+
334
+ # Core (Required)
335
+ L4YERCAK3_API_KEY=your-api-key-here
336
+ L4YERCAK3_BACKEND_URL=https://agreeable-lion-828.convex.site
337
+ `;
338
+
339
+ if (features.includes('webhooks')) {
340
+ template += `
341
+ # Webhooks
342
+ L4YERCAK3_WEBHOOK_SECRET=your-webhook-secret
343
+ `;
344
+ }
345
+
346
+ if (features.includes('checkout')) {
347
+ template += `
348
+ # Checkout / Stripe
349
+ STRIPE_SECRET_KEY=sk_live_xxx
350
+ STRIPE_WEBHOOK_SECRET=whsec_xxx
351
+ `;
352
+ }
353
+
354
+ template += `
355
+ # Organization ID (auto-set by CLI)
356
+ L4YERCAK3_ORGANIZATION_ID=${authContext.organizationId}
357
+ `;
358
+
359
+ return {
360
+ template,
361
+ outputPath: '.env.local.example',
362
+ instructions: [
363
+ '1. Copy this to .env.local.example',
364
+ '2. Copy .env.local.example to .env.local',
365
+ '3. Fill in your actual values',
366
+ '4. Never commit .env.local to git',
367
+ ],
368
+ };
369
+ },
370
+ },
371
+ ],
372
+ };
373
+
374
+ // ============================================
375
+ // Helper Functions
376
+ // ============================================
377
+
378
+ /**
379
+ * Parse schema and extract models
380
+ */
381
+ function parseSchema(schema, schemaType) {
382
+ // Auto-detect schema type if not specified
383
+ if (!schemaType) {
384
+ if (schema.includes('model ') && schema.includes('@')) {
385
+ schemaType = 'prisma';
386
+ } else if (schema.includes('CREATE TABLE') || schema.includes('create table')) {
387
+ schemaType = 'sql';
388
+ } else if (schema.includes('defineTable')) {
389
+ schemaType = 'convex';
390
+ } else {
391
+ schemaType = 'json';
392
+ }
393
+ }
394
+
395
+ const models = [];
396
+
397
+ if (schemaType === 'prisma') {
398
+ // Parse Prisma schema
399
+ const modelRegex = /model\s+(\w+)\s*\{([^}]+)\}/g;
400
+ let match;
401
+ while ((match = modelRegex.exec(schema)) !== null) {
402
+ const name = match[1];
403
+ const body = match[2];
404
+ const fields = [];
405
+
406
+ const fieldRegex = /(\w+)\s+(\w+)(\[\])?\s*(@.*)?/g;
407
+ let fieldMatch;
408
+ while ((fieldMatch = fieldRegex.exec(body)) !== null) {
409
+ fields.push({
410
+ name: fieldMatch[1],
411
+ type: fieldMatch[2] + (fieldMatch[3] || ''),
412
+ });
413
+ }
414
+
415
+ models.push({ name, fields });
416
+ }
417
+ } else if (schemaType === 'json') {
418
+ // Parse JSON model definition
419
+ try {
420
+ const parsed = JSON.parse(schema);
421
+ if (Array.isArray(parsed)) {
422
+ return parsed;
423
+ } else if (parsed.models) {
424
+ return parsed.models;
425
+ }
426
+ } catch {
427
+ // Not valid JSON, return empty
428
+ }
429
+ }
430
+
431
+ return models;
432
+ }
433
+
434
+ /**
435
+ * Suggest L4YERCAK3 type mappings based on model fields
436
+ */
437
+ function suggestMappings(model) {
438
+ const mappings = [];
439
+ const fieldNames = model.fields.map(f => f.name.toLowerCase());
440
+
441
+ // Check for contact-like model
442
+ if (
443
+ fieldNames.includes('email') ||
444
+ fieldNames.includes('firstname') ||
445
+ fieldNames.includes('first_name') ||
446
+ fieldNames.includes('phone')
447
+ ) {
448
+ mappings.push({
449
+ type: 'crm_contact',
450
+ confidence: 0.8,
451
+ reason: 'Contains contact fields (email, name, phone)',
452
+ });
453
+ }
454
+
455
+ // Check for company-like model
456
+ if (
457
+ fieldNames.includes('company') ||
458
+ fieldNames.includes('website') ||
459
+ fieldNames.includes('industry')
460
+ ) {
461
+ mappings.push({
462
+ type: 'crm_organization',
463
+ confidence: 0.7,
464
+ reason: 'Contains organization fields (company, website)',
465
+ });
466
+ }
467
+
468
+ // Check for event-like model
469
+ if (
470
+ (fieldNames.includes('startdate') || fieldNames.includes('start_date')) &&
471
+ (fieldNames.includes('enddate') || fieldNames.includes('end_date'))
472
+ ) {
473
+ mappings.push({
474
+ type: 'event',
475
+ confidence: 0.8,
476
+ reason: 'Contains event date fields',
477
+ });
478
+ }
479
+
480
+ // Check for product-like model
481
+ if (fieldNames.includes('price') || fieldNames.includes('priceinCents')) {
482
+ mappings.push({
483
+ type: 'product',
484
+ confidence: 0.7,
485
+ reason: 'Contains pricing fields',
486
+ });
487
+ }
488
+
489
+ return mappings;
490
+ }
491
+
492
+ /**
493
+ * Generate API client code
494
+ */
495
+ function generateApiClient(features, includeTypes) {
496
+ let code = `/**
497
+ * L4YERCAK3 API Client
498
+ * Generated by L4YERCAK3 CLI
499
+ */
500
+
501
+ const L4YERCAK3_API_KEY = process.env.L4YERCAK3_API_KEY;
502
+ const BACKEND_URL = process.env.L4YERCAK3_BACKEND_URL || 'https://agreeable-lion-828.convex.site';
503
+
504
+ class L4yercak3Client {
505
+ constructor() {
506
+ if (!L4YERCAK3_API_KEY) {
507
+ console.warn('L4YERCAK3_API_KEY not set');
508
+ }
509
+ }
510
+
511
+ async request(method, endpoint, data = null) {
512
+ const url = \`\${BACKEND_URL}\${endpoint}\`;
513
+ const options = {
514
+ method,
515
+ headers: {
516
+ 'Content-Type': 'application/json',
517
+ 'Authorization': \`Bearer \${L4YERCAK3_API_KEY}\`,
518
+ },
519
+ };
520
+
521
+ if (data && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
522
+ options.body = JSON.stringify(data);
523
+ }
524
+
525
+ const response = await fetch(url, options);
526
+ const responseData = await response.json();
527
+
528
+ if (!response.ok) {
529
+ throw new Error(responseData.error || \`API request failed: \${response.status}\`);
530
+ }
531
+
532
+ return responseData;
533
+ }
534
+ `;
535
+
536
+ if (features.includes('crm')) {
537
+ code += `
538
+ // ==================
539
+ // CRM Methods
540
+ // ==================
541
+
542
+ async listContacts(params = {}) {
543
+ const query = new URLSearchParams(params).toString();
544
+ return this.request('GET', \`/api/v1/crm/contacts?\${query}\`);
545
+ }
546
+
547
+ async createContact(data) {
548
+ return this.request('POST', '/api/v1/crm/contacts', data);
549
+ }
550
+
551
+ async getContact(contactId) {
552
+ return this.request('GET', \`/api/v1/crm/contacts/\${contactId}\`);
553
+ }
554
+
555
+ async updateContact(contactId, data) {
556
+ return this.request('PATCH', \`/api/v1/crm/contacts/\${contactId}\`, data);
557
+ }
558
+
559
+ async deleteContact(contactId) {
560
+ return this.request('DELETE', \`/api/v1/crm/contacts/\${contactId}\`);
561
+ }
562
+ `;
563
+ }
564
+
565
+ if (features.includes('events')) {
566
+ code += `
567
+ // ==================
568
+ // Events Methods
569
+ // ==================
570
+
571
+ async listEvents(params = {}) {
572
+ const query = new URLSearchParams(params).toString();
573
+ return this.request('GET', \`/api/v1/events?\${query}\`);
574
+ }
575
+
576
+ async createEvent(data) {
577
+ return this.request('POST', '/api/v1/events', data);
578
+ }
579
+
580
+ async getEvent(eventId) {
581
+ return this.request('GET', \`/api/v1/events/\${eventId}\`);
582
+ }
583
+
584
+ async updateEvent(eventId, data) {
585
+ return this.request('PATCH', \`/api/v1/events/\${eventId}\`, data);
586
+ }
587
+
588
+ async getEventAttendees(eventId) {
589
+ return this.request('GET', \`/api/v1/events/\${eventId}/attendees\`);
590
+ }
591
+ `;
592
+ }
593
+
594
+ if (features.includes('forms')) {
595
+ code += `
596
+ // ==================
597
+ // Forms Methods
598
+ // ==================
599
+
600
+ async listForms(params = {}) {
601
+ const query = new URLSearchParams(params).toString();
602
+ return this.request('GET', \`/api/v1/forms?\${query}\`);
603
+ }
604
+
605
+ async createForm(data) {
606
+ return this.request('POST', '/api/v1/forms', data);
607
+ }
608
+
609
+ async getForm(formId) {
610
+ return this.request('GET', \`/api/v1/forms/\${formId}\`);
611
+ }
612
+
613
+ async getFormResponses(formId) {
614
+ return this.request('GET', \`/api/v1/forms/\${formId}/responses\`);
615
+ }
616
+
617
+ async submitFormResponse(formId, data) {
618
+ return this.request('POST', \`/api/v1/forms/\${formId}/responses\`, data);
619
+ }
620
+ `;
621
+ }
622
+
623
+ code += `}
624
+
625
+ export const l4yercak3 = new L4yercak3Client();
626
+ export default l4yercak3;
627
+ `;
628
+
629
+ return code;
630
+ }
631
+
632
+ /**
633
+ * Generate sync adapter code
634
+ */
635
+ function generateSyncAdapter(localModel, layerCakeType, fieldMappings, syncDirection) {
636
+ const modelLower = localModel.toLowerCase();
637
+
638
+ return `/**
639
+ * ${localModel} <-> L4YERCAK3 ${layerCakeType} Sync Adapter
640
+ * Generated by L4YERCAK3 CLI
641
+ */
642
+
643
+ import { l4yercak3 } from './client';
644
+
645
+ // Field mappings
646
+ const FIELD_MAPPINGS = ${JSON.stringify(fieldMappings, null, 2)};
647
+
648
+ /**
649
+ * Transform local model to L4YERCAK3 format
650
+ */
651
+ export function toLayerCake(local${localModel}) {
652
+ const data = {};
653
+
654
+ for (const mapping of FIELD_MAPPINGS) {
655
+ let value = local${localModel}[mapping.local];
656
+
657
+ // Apply transformations
658
+ if (value && mapping.transform) {
659
+ switch (mapping.transform) {
660
+ case 'uppercase':
661
+ value = value.toUpperCase();
662
+ break;
663
+ case 'lowercase':
664
+ value = value.toLowerCase();
665
+ break;
666
+ }
667
+ }
668
+
669
+ data[mapping.layerCake] = value;
670
+ }
671
+
672
+ return data;
673
+ }
674
+
675
+ /**
676
+ * Transform L4YERCAK3 format to local model
677
+ */
678
+ export function toLocal(layerCakeData) {
679
+ const data = {};
680
+
681
+ for (const mapping of FIELD_MAPPINGS) {
682
+ data[mapping.local] = layerCakeData[mapping.layerCake];
683
+ }
684
+
685
+ return data;
686
+ }
687
+
688
+ ${
689
+ syncDirection === 'push' || syncDirection === 'bidirectional'
690
+ ? `
691
+ /**
692
+ * Push local ${localModel} to L4YERCAK3
693
+ */
694
+ export async function push${localModel}(local${localModel}, layerCakeId = null) {
695
+ const data = toLayerCake(local${localModel});
696
+
697
+ if (layerCakeId) {
698
+ // Update existing
699
+ return l4yercak3.request('PATCH', \`/api/v1/${layerCakeType}/\${layerCakeId}\`, data);
700
+ } else {
701
+ // Create new
702
+ return l4yercak3.request('POST', '/api/v1/${layerCakeType}', data);
703
+ }
704
+ }
705
+ `
706
+ : ''
707
+ }
708
+
709
+ ${
710
+ syncDirection === 'pull' || syncDirection === 'bidirectional'
711
+ ? `
712
+ /**
713
+ * Pull ${localModel} from L4YERCAK3
714
+ */
715
+ export async function pull${localModel}(layerCakeId) {
716
+ const response = await l4yercak3.request('GET', \`/api/v1/${layerCakeType}/\${layerCakeId}\`);
717
+ return toLocal(response);
718
+ }
719
+
720
+ /**
721
+ * Pull all ${localModel}s from L4YERCAK3
722
+ */
723
+ export async function pullAll${localModel}s(params = {}) {
724
+ const response = await l4yercak3.request('GET', '/api/v1/${layerCakeType}', params);
725
+ return (response.items || []).map(toLocal);
726
+ }
727
+ `
728
+ : ''
729
+ }
730
+
731
+ /**
732
+ * Handle webhook event
733
+ */
734
+ export function handleWebhookEvent(event) {
735
+ switch (event.type) {
736
+ case '${layerCakeType}.created':
737
+ return { action: 'create', data: toLocal(event.data) };
738
+ case '${layerCakeType}.updated':
739
+ return { action: 'update', data: toLocal(event.data) };
740
+ case '${layerCakeType}.deleted':
741
+ return { action: 'delete', id: event.data.id };
742
+ default:
743
+ return null;
744
+ }
745
+ }
746
+ `;
747
+ }
748
+
749
+ /**
750
+ * Generate webhook handler code
751
+ */
752
+ function generateWebhookHandler(events, routerType) {
753
+ if (routerType === 'app') {
754
+ return `/**
755
+ * L4YERCAK3 Webhook Handler
756
+ * Generated by L4YERCAK3 CLI
757
+ *
758
+ * Route: app/api/webhooks/l4yercak3/route.ts
759
+ */
760
+
761
+ import { NextRequest, NextResponse } from 'next/server';
762
+ import crypto from 'crypto';
763
+
764
+ const WEBHOOK_SECRET = process.env.L4YERCAK3_WEBHOOK_SECRET;
765
+
766
+ /**
767
+ * Verify webhook signature
768
+ */
769
+ function verifySignature(payload: string, signature: string): boolean {
770
+ if (!WEBHOOK_SECRET) return false;
771
+
772
+ const expected = crypto
773
+ .createHmac('sha256', WEBHOOK_SECRET)
774
+ .update(payload)
775
+ .digest('hex');
776
+
777
+ return crypto.timingSafeEqual(
778
+ Buffer.from(signature),
779
+ Buffer.from(expected)
780
+ );
781
+ }
782
+
783
+ export async function POST(request: NextRequest) {
784
+ try {
785
+ const payload = await request.text();
786
+ const signature = request.headers.get('x-l4yercak3-signature');
787
+
788
+ // Verify signature
789
+ if (!signature || !verifySignature(payload, signature)) {
790
+ return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
791
+ }
792
+
793
+ const event = JSON.parse(payload);
794
+
795
+ // Handle events
796
+ switch (event.type) {
797
+ ${events
798
+ .map(
799
+ e => ` case '${e}':
800
+ await handle${e.replace(/\./g, '_')}(event);
801
+ break;`
802
+ )
803
+ .join('\n')}
804
+ default:
805
+ console.log('Unhandled event type:', event.type);
806
+ }
807
+
808
+ return NextResponse.json({ received: true });
809
+ } catch (error) {
810
+ console.error('Webhook error:', error);
811
+ return NextResponse.json({ error: 'Webhook handler failed' }, { status: 500 });
812
+ }
813
+ }
814
+
815
+ // ==================
816
+ // Event Handlers
817
+ // ==================
818
+
819
+ ${events
820
+ .map(
821
+ e => `async function handle${e.replace(/\./g, '_')}(event: any) {
822
+ // TODO: Implement ${e} handler
823
+ console.log('Handling ${e}:', event.data);
824
+ }`
825
+ )
826
+ .join('\n\n')}
827
+ `;
828
+ }
829
+
830
+ // Pages router
831
+ return `/**
832
+ * L4YERCAK3 Webhook Handler
833
+ * Generated by L4YERCAK3 CLI
834
+ *
835
+ * Route: pages/api/webhooks/l4yercak3.ts
836
+ */
837
+
838
+ import type { NextApiRequest, NextApiResponse } from 'next';
839
+ import crypto from 'crypto';
840
+
841
+ const WEBHOOK_SECRET = process.env.L4YERCAK3_WEBHOOK_SECRET;
842
+
843
+ function verifySignature(payload: string, signature: string): boolean {
844
+ if (!WEBHOOK_SECRET) return false;
845
+
846
+ const expected = crypto
847
+ .createHmac('sha256', WEBHOOK_SECRET)
848
+ .update(payload)
849
+ .digest('hex');
850
+
851
+ return crypto.timingSafeEqual(
852
+ Buffer.from(signature),
853
+ Buffer.from(expected)
854
+ );
855
+ }
856
+
857
+ export default async function handler(
858
+ req: NextApiRequest,
859
+ res: NextApiResponse
860
+ ) {
861
+ if (req.method !== 'POST') {
862
+ return res.status(405).json({ error: 'Method not allowed' });
863
+ }
864
+
865
+ try {
866
+ const payload = JSON.stringify(req.body);
867
+ const signature = req.headers['x-l4yercak3-signature'] as string;
868
+
869
+ if (!signature || !verifySignature(payload, signature)) {
870
+ return res.status(401).json({ error: 'Invalid signature' });
871
+ }
872
+
873
+ const event = req.body;
874
+
875
+ switch (event.type) {
876
+ ${events.map(e => ` case '${e}': await handle${e.replace(/\./g, '_')}(event); break;`).join('\n')}
877
+ }
878
+
879
+ return res.json({ received: true });
880
+ } catch (error) {
881
+ console.error('Webhook error:', error);
882
+ return res.status(500).json({ error: 'Webhook handler failed' });
883
+ }
884
+ }
885
+
886
+ ${events
887
+ .map(
888
+ e => `async function handle${e.replace(/\./g, '_')}(event: any) {
889
+ console.log('Handling ${e}:', event.data);
890
+ }`
891
+ )
892
+ .join('\n\n')}
893
+ `;
894
+ }