go-duck-cli 1.0.8 → 1.1.1

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 (70) hide show
  1. package/README.md +30 -15
  2. package/generators/ai_docs.js +130 -0
  3. package/generators/broker.js +63 -0
  4. package/generators/config.js +149 -7
  5. package/generators/devops.js +210 -43
  6. package/generators/docs.js +23 -4
  7. package/generators/elasticsearch.js +263 -0
  8. package/generators/kratos.js +229 -41
  9. package/generators/metering.js +280 -48
  10. package/generators/migrations.js +92 -198
  11. package/generators/mqtt.js +2 -39
  12. package/generators/multitenancy.js +274 -71
  13. package/generators/nats.js +39 -0
  14. package/generators/outbox.js +171 -0
  15. package/generators/postgrest.js +7 -3
  16. package/generators/postman.js +405 -0
  17. package/generators/repository.js +27 -0
  18. package/generators/router.js +27 -0
  19. package/generators/security.js +95 -14
  20. package/generators/serverless.js +147 -0
  21. package/generators/storage.js +589 -0
  22. package/generators/swagger.js +84 -60
  23. package/generators/telemetry.js +23 -32
  24. package/generators/websocket.js +55 -21
  25. package/index.js +481 -116
  26. package/package.json +6 -4
  27. package/parser/gdl.js +163 -24
  28. package/templates/docs/index.html.hbs +5 -5
  29. package/templates/docs/layout.hbs +221 -62
  30. package/templates/docs/pages/audit.hbs +83 -35
  31. package/templates/docs/pages/cli.hbs +18 -0
  32. package/templates/docs/pages/configuration.hbs +241 -0
  33. package/templates/docs/pages/datadog.hbs +46 -0
  34. package/templates/docs/pages/elasticsearch.hbs +121 -0
  35. package/templates/docs/pages/federation.hbs +241 -0
  36. package/templates/docs/pages/gdl-advanced.hbs +91 -0
  37. package/templates/docs/pages/gdl-annotations.hbs +137 -0
  38. package/templates/docs/pages/gdl-entities.hbs +134 -0
  39. package/templates/docs/pages/gdl-relationships.hbs +80 -0
  40. package/templates/docs/pages/gdl.hbs +60 -204
  41. package/templates/docs/pages/graphql.hbs +58 -44
  42. package/templates/docs/pages/grpc.hbs +53 -90
  43. package/templates/docs/pages/hybrid-store.hbs +127 -0
  44. package/templates/docs/pages/index.hbs +418 -149
  45. package/templates/docs/pages/keycloak.hbs +43 -0
  46. package/templates/docs/pages/legend.hbs +116 -0
  47. package/templates/docs/pages/mosquitto.hbs +39 -0
  48. package/templates/docs/pages/multitenancy.hbs +139 -71
  49. package/templates/docs/pages/otel.hbs +40 -0
  50. package/templates/docs/pages/realtime.hbs +38 -12
  51. package/templates/docs/pages/redis.hbs +40 -0
  52. package/templates/docs/pages/rest.hbs +120 -202
  53. package/templates/docs/pages/saga.hbs +94 -0
  54. package/templates/docs/pages/security.hbs +150 -44
  55. package/templates/docs/pages/serverless.hbs +157 -0
  56. package/templates/docs/pages/storage.hbs +127 -0
  57. package/templates/docs/pages/wizard.hbs +683 -0
  58. package/templates/docs/triple_identity_registry.png +0 -0
  59. package/templates/go/controller.go.hbs +287 -283
  60. package/templates/go/entity.go.hbs +17 -15
  61. package/templates/go/main.go.hbs +47 -180
  62. package/templates/go/migrator.go.hbs +65 -0
  63. package/templates/go/router.go.hbs +272 -0
  64. package/templates/graphql/resolver.go.hbs +53 -34
  65. package/templates/graphql/schema.graphql.hbs +17 -5
  66. package/templates/kratos/service.go.hbs +169 -34
  67. package/templates/proto/entity.proto.hbs +10 -14
  68. package/test_nested.gdl +21 -0
  69. package/templates/docs/intro.mp4 +0 -0
  70. package/test_parser.js +0 -9
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "go-duck-cli",
3
- "version": "1.0.8",
4
- "description": "go function generator",
3
+ "version": "1.1.1",
4
+ "description": "The Ultimate Evolutionary Go Microservice Scaffolder.",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "bin": {
@@ -15,9 +15,11 @@
15
15
  "dependencies": {
16
16
  "chalk": "^4.1.2",
17
17
  "commander": "^14.0.3",
18
+ "express": "^5.2.1",
18
19
  "fs-extra": "^11.3.4",
19
20
  "handlebars": "^4.7.8",
20
21
  "inquirer": "^8.2.7",
21
- "js-yaml": "^4.1.1"
22
+ "js-yaml": "^4.1.1",
23
+ "open": "^11.0.0"
22
24
  }
23
- }
25
+ }
package/parser/gdl.js CHANGED
@@ -21,7 +21,10 @@ import path from 'path';
21
21
  */
22
22
 
23
23
  export const parseGDL = async (filePath) => {
24
- const content = await fs.readFile(filePath, 'utf8');
24
+ const rawContent = await fs.readFile(filePath, 'utf8');
25
+ // Strip single-line (//) and multi-line (/* */) comments to prevent parsing artifacts
26
+ const content = rawContent.replace(/\/\/.*|\/\*[\s\S]*?\*\//g, '');
27
+
25
28
  const entities = [];
26
29
  const relationships = [];
27
30
  const enums = [];
@@ -35,39 +38,122 @@ export const parseGDL = async (filePath) => {
35
38
  enums.push({ name, values });
36
39
  }
37
40
 
38
- // Parse entity blocks
39
- const entityRegex = /(@\w+\s+)?entity\s+(\w+)\s*\{([\s\S]*?)\}/g;
40
- while ((match = entityRegex.exec(content)) !== null) {
41
- const annotation = match[1]?.trim();
42
- const name = match[2];
43
- const fieldBlock = match[3];
41
+ // 🦆 RECURSIVE BLOCK PARSER: Correctly handles nested braces for @Document entities
42
+ const parseEntityBlocks = (content) => {
43
+ const entityBlocks = [];
44
+ const entityStartRegex = /((?:@\w+\s*)*)entity\s+(\w+)\s*\{/g;
45
+ let match;
46
+
47
+ while ((match = entityStartRegex.exec(content)) !== null) {
48
+ const annotation = match[1]?.trim();
49
+ const name = match[2];
50
+ if (name === 'relationship' || name === 'enum') continue;
51
+
52
+ const startIndex = match.index + match[0].length;
53
+ let braceCount = 1;
54
+ let index = startIndex;
44
55
 
45
- if (name === 'relationship' || name === 'enum') continue;
56
+ while (braceCount > 0 && index < content.length) {
57
+ if (content[index] === '{') braceCount++;
58
+ if (content[index] === '}') braceCount--;
59
+ index++;
60
+ }
61
+
62
+ const fieldBlock = content.substring(startIndex, index - 1);
63
+ entityBlocks.push({ name, annotation, fieldBlock });
64
+ }
65
+ return entityBlocks;
66
+ };
46
67
 
68
+ const parseFields = (block) => {
47
69
  const fields = [];
48
- const fieldLines = fieldBlock.split('\n').map(l => l.trim()).filter(l => l.length > 0);
70
+ const lines = block.split('\n').map(l => l.trim()).filter(l => l.length > 0);
71
+ let i = 0;
72
+
73
+ while (i < lines.length) {
74
+ const line = lines[i];
75
+ const parts = line.split(/\s+/).filter(p => p.length > 0);
76
+ if (parts.length < 1) { i++; continue; }
77
+
78
+ // Extract field annotations
79
+ const fieldAnnotations = parts.filter(p => p.startsWith('@'));
80
+ const dataParts = parts.filter(p => !p.startsWith('@'));
49
81
 
50
- for (const line of fieldLines) {
51
- const parts = line.split(/\s+/);
52
- if (parts.length < 2) continue;
82
+ // Check if this line starts a nested block like "clinicalData {"
83
+ if (line.includes('{')) {
84
+ let fieldName = dataParts[0] || 'unknown';
85
+ if (line.includes('{')) {
86
+ const nestedBlockStart = line.indexOf('{');
87
+ fieldName = line.substring(0, nestedBlockStart).trim().split(/\s+/).pop();
88
+ }
89
+
90
+ // Extract the nested block by finding the matching brace in the block
91
+ let nestedBraceCount = 1;
92
+ let nestedContent = '';
93
+
94
+ const fullRemainingBlock = block.substring(block.indexOf(line) + line.indexOf('{') + 1);
95
+ let charIndex = 0;
96
+ while (nestedBraceCount > 0 && charIndex < fullRemainingBlock.length) {
97
+ if (fullRemainingBlock[charIndex] === '{') nestedBraceCount++;
98
+ if (fullRemainingBlock[charIndex] === '}') nestedBraceCount--;
99
+ if (nestedBraceCount > 0) nestedContent += fullRemainingBlock[charIndex];
100
+ charIndex++;
101
+ }
102
+
103
+ fields.push({
104
+ name: fieldName,
105
+ type: 'Object',
106
+ isNested: true,
107
+ annotations: fieldAnnotations,
108
+ children: parseFields(nestedContent)
109
+ });
110
+
111
+ const nestedLineCount = nestedContent.split('\n').length;
112
+ i += nestedLineCount + 1;
113
+ continue;
114
+ }
53
115
 
54
- const fieldName = parts[0];
55
- let rawType = parts[1];
116
+ if (dataParts.length < 2) { i++; continue; }
56
117
 
57
- // Support String(N) for custom VARCHAR sizes
118
+ let fieldName = dataParts[0];
119
+ let rawType = dataParts[1];
120
+
121
+ // Standard parsing logic for simple fields
122
+ const typeKeywords = ['string', 'text', 'int', 'long', 'float', 'bigdecimal', 'bool', 'boolean', 'datetime', 'localdate', 'json', 'jsonb', 'uuid', 'instant'];
123
+ const isKnownType = typeKeywords.some(tk => dataParts[0].toLowerCase().startsWith(tk));
124
+ const isKnownEnum = enums.some(e => e.name.toLowerCase() === dataParts[0].toLowerCase());
125
+
126
+ if (isKnownType || isKnownEnum) {
127
+ fieldName = dataParts[1];
128
+ rawType = dataParts[0];
129
+ }
130
+
131
+ // Standardize capitalization for the factory
132
+ rawType = rawType.charAt(0).toUpperCase() + rawType.slice(1).toLowerCase();
133
+ if (rawType.toLowerCase().startsWith('string(')) rawType = 'String' + rawType.slice(6);
134
+ if (rawType.startsWith('Float')) rawType = 'Float';
135
+ if (rawType === 'Int' || rawType.startsWith('Int(')) rawType = 'Integer';
136
+ if (rawType === 'Bool') rawType = 'Boolean';
137
+
138
+ // Support String(N) or Int(N) for custom sizing
58
139
  let varcharSize = 255;
59
- const sizeMatch = rawType.match(/^String\((\d+)\)$/);
140
+ const sizeMatch = rawType.match(/^(?:String|Integer)\((\d+)\)$/i);
60
141
  if (sizeMatch) {
61
142
  varcharSize = parseInt(sizeMatch[1], 10);
62
- rawType = 'String';
143
+ rawType = rawType.startsWith('String') ? 'String' : 'Integer';
63
144
  }
64
145
 
65
146
  const required = line.includes('required');
66
147
  const unique = line.includes('unique');
67
148
  const isText = rawType === 'Text';
149
+ const isVersion = fieldAnnotations.includes('@Version');
68
150
 
69
151
  // Check if type is an Enum
70
- const isEnum = enums.some(e => e.name === rawType);
152
+ const isEnum = enums.some(e => e.name.toLowerCase() === rawType.toLowerCase());
153
+ if (isEnum) {
154
+ const en = enums.find(e => e.name.toLowerCase() === rawType.toLowerCase());
155
+ rawType = en.name; // Use correct case
156
+ }
71
157
 
72
158
  fields.push({
73
159
  name: fieldName,
@@ -75,15 +161,41 @@ export const parseGDL = async (filePath) => {
75
161
  required,
76
162
  unique,
77
163
  isEnum,
164
+ isVersion,
165
+ annotations: fieldAnnotations,
78
166
  varcharSize: rawType === 'String' ? varcharSize : null,
79
167
  });
168
+ i++;
80
169
  }
170
+ return fields;
171
+ };
172
+ const entityBlocks = parseEntityBlocks(content);
173
+ for (const block of entityBlocks) {
174
+ const annotation = block.annotation;
175
+ const name = block.name;
81
176
 
82
- entities.push({ name, annotation, fields });
177
+ const isAudited = annotation?.includes('@Audited');
178
+ const isFederated = annotation?.includes('@Federated');
179
+ const isSearchable = annotation?.includes('@Searchable');
180
+ const isDocument = annotation?.includes('@Document') || annotation?.includes('@isDocument');
181
+ const isEmbedded = annotation?.includes('@Embed');
182
+
183
+ const fields = parseFields(block.fieldBlock);
184
+
185
+ entities.push({
186
+ name,
187
+ annotation,
188
+ isAudited,
189
+ isFederated,
190
+ isSearchable,
191
+ isDocument,
192
+ isEmbedded,
193
+ fields
194
+ });
83
195
  }
84
196
 
85
- // Parse relationship blocks
86
- const relRegex = /relationship\s+(\w+)\s*\{([\s\S]*?)\n\}/g;
197
+ // Parse relationship blocks (Robust whitespace handling)
198
+ const relRegex = /relationship\s+(\w+)\s*\{([\s\S]*?)\}/g;
87
199
  while ((match = relRegex.exec(content)) !== null) {
88
200
  const type = match[1];
89
201
  const relBlock = match[2];
@@ -94,8 +206,12 @@ export const parseGDL = async (filePath) => {
94
206
  if (relParts.length !== 2) continue;
95
207
 
96
208
  const parseRelPart = (p) => {
97
- const m = p.match(/(\w+)\s*\{\s*(\w+)\s*\}/);
98
- return m ? { entity: m[1], field: m[2] } : null;
209
+ // Support both "Parent{id}" and "Parent"
210
+ const mWithBraces = p.match(/(\w+)\s*\{\s*(\w+)\s*\}/);
211
+ if (mWithBraces) return { entity: mWithBraces[1], field: mWithBraces[2] };
212
+
213
+ const mSimple = p.match(/^(\w+)/);
214
+ return mSimple ? { entity: mSimple[1], field: null } : null;
99
215
  };
100
216
 
101
217
  // Support required FK: "required" keyword anywhere in the line
@@ -110,7 +226,30 @@ export const parseGDL = async (filePath) => {
110
226
  }
111
227
  }
112
228
 
113
- return { entities, relationships, enums };
229
+ // Parse open entities
230
+ const openEntities = [];
231
+ const openRegex = /open\s+(.*)/g;
232
+ while ((match = openRegex.exec(content)) !== null) {
233
+ const val = match[1].trim();
234
+ // Split by comma but ignore commas inside parentheses
235
+ const items = val.split(/,(?![^\(]*\))/).map(v => v.trim()).filter(v => v.length > 0);
236
+
237
+ for (const item of items) {
238
+ const itemMatch = item.match(/^([\w\*]+)(?:\s*\((.*?)\))?$/);
239
+ if (itemMatch) {
240
+ const name = itemMatch[1];
241
+ const actionStr = itemMatch[2];
242
+ let actions = ['read', 'create', 'update', 'delete'];
243
+ if (actionStr) {
244
+ actions = actionStr.split(/[\s,]+/).map(a => a.trim().toLowerCase()).filter(a => a.length > 0);
245
+ }
246
+
247
+ openEntities.push({ name, actions });
248
+ }
249
+ }
250
+ }
251
+
252
+ return { entities, relationships, enums, openEntities };
114
253
  };
115
254
 
116
255
  /**
@@ -41,7 +41,7 @@
41
41
  <li class="pt-2 pb-1"><span class="font-semibold text-gray-800">5. Observability</span></li>
42
42
  <li><a href="#observability" class="text-gray-600 hover:text-indigo-600 hover:font-medium transition-colors pl-4 block">OTel & Datadog</a></li>
43
43
  <li class="pt-2 pb-1"><span class="font-semibold text-gray-800">6. Migrations</span></li>
44
- <li><a href="#liquibase" class="text-gray-600 hover:text-indigo-600 hover:font-medium transition-colors pl-4 block">Liquibase & GDL</a></li>
44
+ <li><a href="#goose" class="text-gray-600 hover:text-indigo-600 hover:font-medium transition-colors pl-4 block">Goose SQL & GDL</a></li>
45
45
  </ul>
46
46
  </aside>
47
47
 
@@ -206,12 +206,12 @@ res, err := client.Get{{#if entities.length}}{{capitalize entities.[0].name}}{{e
206
206
  </ul>
207
207
  </section>
208
208
 
209
- <!-- Liquibase & GDL -->
210
- <section id="liquibase" class="content-section">
209
+ <!-- Goose & GDL -->
210
+ <section id="goose" class="content-section">
211
211
  <h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">6. Advanced Migrations & GDL</h2>
212
- <p class="mb-4">Running <code>go-duck import-gdl</code> calculates stateful differences using the `.go-duck/` snapshot folder. It generates atomic, timestamped Liquibase XML changelogs in <code>migrations/liquibase/changelogs/</code>.</p>
212
+ <p class="mb-4">Running <code>go-duck import-gdl</code> calculates stateful differences using the `.go-duck/` snapshot folder. It generates atomic, Go-embedded timestamped Goose SQL migrations in <code>migrations/sql/</code>.</p>
213
213
  <ul class="list-disc pl-6 space-y-2">
214
- <li>The application auto-generates <code>master.xml</code> dynamically without "ghost" references.</li>
214
+ <li>The application natively compiles SQL inside the Go binary via <code>go:embed</code>.</li>
215
215
  <li>Includes smart nullability and automatic indexing for Foreign Keys.</li>
216
216
  <li><strong>Needle Support:</strong> We inject `// go-duck-needle-*` markers in <code>main.go</code> and <code>grpc.go</code> for safe evolutionary code additions without destroying manual updates.</li>
217
217
  </ul>
@@ -1,105 +1,264 @@
1
1
  <!DOCTYPE html>
2
- <html lang="en">
2
+ <html lang="en" class="scroll-smooth">
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>{{appName}} - {{title}}</title>
7
7
  <script src="https://cdn.tailwindcss.com"></script>
8
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
9
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/vs2015.min.css">
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
11
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
10
12
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
11
13
  <script>
12
14
  tailwind.config = {
13
15
  theme: {
14
16
  extend: {
15
- fontFamily: { sans: ['Inter', 'sans-serif'] },
17
+ fontFamily: {
18
+ sans: ['Inter', 'sans-serif'],
19
+ mono: ['JetBrains Mono', 'monospace'],
20
+ },
16
21
  }
17
22
  }
18
23
  }
19
24
  </script>
20
25
  <script>hljs.highlightAll();</script>
21
26
  <style>
22
- .gradient-text { background: linear-gradient(90deg, #818cf8, #c084fc); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
27
+ .gradient-text { background: linear-gradient(135deg, #6366f1, #a855f7, #ec4899); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
23
28
  ::-webkit-scrollbar { width: 6px; }
24
- ::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }
29
+ ::-webkit-scrollbar-track { background: #f1f5f9; }
30
+ ::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; }
31
+ ::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
32
+
33
+ .sidebar-link-active {
34
+ background-color: #ebf5ff;
35
+ color: #1e40af;
36
+ border-right: 4px solid #3b82f6;
37
+ font-weight: 600;
38
+ }
39
+
40
+ #sidebar {
41
+ width: 280px;
42
+ height: calc(100vh - 64px);
43
+ position: fixed;
44
+ top: 64px;
45
+ left: 0;
46
+ overflow-y: auto;
47
+ border-right: 1px solid #e2e8f0;
48
+ background: #ffffff;
49
+ z-index: 30;
50
+ }
51
+
52
+ #content-wrapper {
53
+ margin-left: 280px;
54
+ padding-top: 64px;
55
+ min-height: 100vh;
56
+ background: #ffffff;
57
+ }
58
+
59
+ @media (max-width: 1024px) {
60
+ #sidebar {
61
+ transform: translateX(-100%);
62
+ transition: transform 0.3s ease;
63
+ }
64
+ #sidebar.open {
65
+ transform: translateX(0);
66
+ }
67
+ #content-wrapper {
68
+ margin-left: 0;
69
+ }
70
+ }
71
+
72
+ .prose pre {
73
+ background-color: #0f172a !important;
74
+ border-radius: 0.75rem;
75
+ border: 1px solid #1e293b;
76
+ color: #00ff41 !important;
77
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
78
+ }
79
+ .prose pre code {
80
+ color: inherit !important;
81
+ text-shadow: 0 0 5px rgba(0, 255, 65, 0.2);
82
+ }
83
+ .prose code:not(pre code) {
84
+ background-color: #f1f5f9;
85
+ color: #0f172a;
86
+ padding: 0.2rem 0.4rem;
87
+ border-radius: 0.375rem;
88
+ font-weight: 600;
89
+ }
90
+ @keyframes float {
91
+ 0% { transform: translateY(0px); }
92
+ 50% { transform: translateY(-15px); }
93
+ 100% { transform: translateY(0px); }
94
+ }
95
+ .animate-float {
96
+ animation: float 6s ease-in-out infinite;
97
+ }
25
98
  </style>
26
99
  </head>
27
- <body class="bg-slate-50 text-slate-800 font-sans antialiased selection:bg-indigo-300 selection:text-indigo-900">
28
- <!-- Navbar -->
29
- <nav class="bg-slate-900/95 backdrop-blur-md shadow-lg fixed w-full z-30 top-0 h-16 flex items-center px-6 border-b border-slate-800">
100
+ <body class="bg-white text-slate-900 font-sans antialiased selection:bg-blue-100 selection:text-blue-900 overflow-x-hidden">
101
+
102
+ <!-- Header Navbar -->
103
+ <nav class="fixed top-0 left-0 right-0 h-16 bg-white border-b border-slate-200 z-50 flex items-center px-6 justify-between">
30
104
  <div class="flex items-center">
31
- <img src="logo.png" alt="GO-DUCK Logo" class="h-10 w-auto mr-4 rounded-lg shadow-sm">
32
- <div class="text-xl font-bold text-white tracking-tight">GO-DUCK <span class="text-indigo-300 text-xs font-semibold ml-2 inline-flex items-center px-2 py-0.5 rounded-full bg-indigo-500/20 border border-indigo-500/30 uppercase tracking-wider hidden sm:inline-flex">Developer Guide</span></div>
105
+ <button id="mobile-menu-btn" class="lg:hidden mr-4 p-2 text-slate-600 hover:bg-slate-100 rounded-lg">
106
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path></svg>
107
+ </button>
108
+ <div class="relative group cursor-pointer mr-3">
109
+ <div class="absolute -inset-1.5 bg-gradient-to-tr from-indigo-500/20 to-purple-500/20 rounded-xl blur opacity-0 group-hover:opacity-100 transition-all duration-500"></div>
110
+ <img src="logo.png" alt="GO-DUCK Logo" class="relative h-10 w-auto transform hover:scale-105 transition-all duration-300 drop-shadow-sm">
111
+ </div>
112
+ <h1 class="text-xl font-bold text-slate-900 tracking-tight flex items-center">
113
+ GO-DUCK
114
+ <span class="ml-3 px-2 py-0.5 bg-slate-100 text-slate-500 text-[10px] uppercase tracking-widest font-bold rounded-md border border-slate-200">
115
+ V3.0
116
+ </span>
117
+ </h1>
118
+ </div>
119
+ <div class="hidden md:flex items-center space-x-6 text-sm font-medium text-slate-600">
120
+ <a href="https://github.com" target="_blank" class="hover:text-blue-600 transition-colors">GitHub</a>
121
+ <a href="swagger.json" target="_blank" class="hover:text-blue-600 transition-colors">API Docs</a>
33
122
  </div>
34
123
  </nav>
35
124
 
36
- <div class="flex max-w-[90rem] mx-auto pt-16">
37
- <!-- Sidebar -->
38
- <aside class="w-72 fixed h-[calc(100vh-4rem)] overflow-y-auto bg-slate-50 border-r border-slate-200 p-6 hidden lg:block scroll-smooth">
39
- <h3 class="font-bold text-slate-400 uppercase text-[10px] tracking-widest mb-4 flex items-center">
40
- <svg class="w-3 h-3 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7"></path></svg>
41
- Table of Contents
42
- </h3>
43
- <ul class="space-y-1 text-sm font-medium text-slate-600">
44
- <li><a href="index.html" class="flex items-center px-3 py-2 rounded-lg transition-all duration-200 {{#if (eq activePage 'index')}}bg-white shadow-sm ring-1 ring-slate-200 text-indigo-700{{else}}hover:bg-slate-200/50 hover:text-slate-900{{/if}}">Home & Quick Start</a></li>
45
-
46
- <li class="pt-5 pb-1 px-3"><span class="font-bold text-slate-900 uppercase text-[10px] tracking-widest">1. Core Concepts</span></li>
47
- <li><a href="gdl.html" class="flex items-center px-3 py-2 rounded-lg transition-all duration-200 {{#if (eq activePage 'gdl')}}bg-white shadow-sm ring-1 ring-slate-200 text-indigo-700{{else}}hover:bg-slate-200/50 hover:text-slate-900{{/if}}">GDL & Framework</a></li>
48
- <li><a href="cli.html" class="flex items-center px-3 py-2 rounded-lg transition-all duration-200 {{#if (eq activePage 'cli')}}bg-white shadow-sm ring-1 ring-slate-200 text-indigo-700{{else}}hover:bg-slate-200/50 hover:text-slate-900{{/if}}">CLI & Code Injection</a></li>
49
-
50
- <li class="pt-5 pb-1 px-3"><span class="font-bold text-slate-900 uppercase text-[10px] tracking-widest">2. API Endpoints</span></li>
51
- <li><a href="rest.html" class="flex items-center px-3 py-2 rounded-lg transition-all duration-200 {{#if (eq activePage 'rest')}}bg-white shadow-sm ring-1 ring-slate-200 text-indigo-700{{else}}hover:bg-slate-200/50 hover:text-slate-900{{/if}}">REST & Generic Search</a></li>
52
- <li><a href="graphql.html" class="flex items-center px-3 py-2 rounded-lg transition-all duration-200 {{#if (eq activePage 'graphql')}}bg-white shadow-sm ring-1 ring-slate-200 text-indigo-700{{else}}hover:bg-slate-200/50 hover:text-slate-900{{/if}}">GraphQL Integration</a></li>
53
- <li><a href="grpc.html" class="flex items-center px-3 py-2 rounded-lg transition-all duration-200 {{#if (eq activePage 'grpc')}}bg-white shadow-sm ring-1 ring-slate-200 text-indigo-700{{else}}hover:bg-slate-200/50 hover:text-slate-900{{/if}}">Secured gRPC (Kratos)</a></li>
54
-
55
- <li class="pt-5 pb-1 px-3"><span class="font-bold text-slate-900 uppercase text-[10px] tracking-widest">3. Real-Time & Audit</span></li>
56
- <li><a href="realtime.html" class="flex items-center px-3 py-2 rounded-lg transition-all duration-200 {{#if (eq activePage 'realtime')}}bg-white shadow-sm ring-1 ring-slate-200 text-indigo-700{{else}}hover:bg-slate-200/50 hover:text-slate-900{{/if}}">WebSockets & MQTT</a></li>
57
- <li><a href="audit.html" class="flex items-center px-3 py-2 rounded-lg transition-all duration-200 {{#if (eq activePage 'audit')}}bg-white shadow-sm ring-1 ring-slate-200 text-indigo-700{{else}}hover:bg-slate-200/50 hover:text-slate-900{{/if}}">Audit & Metering</a></li>
58
-
59
- <li class="pt-5 pb-1 px-3"><span class="font-bold text-slate-900 uppercase text-[10px] tracking-widest">4. Infrastructure</span></li>
60
- <li><a href="security.html" class="flex items-center px-3 py-2 rounded-lg transition-all duration-200 {{#if (eq activePage 'security')}}bg-white shadow-sm ring-1 ring-slate-200 text-indigo-700{{else}}hover:bg-slate-200/50 hover:text-slate-900{{/if}}">Security & Resilience</a></li>
61
- <li><a href="observability.html" class="flex items-center px-3 py-2 rounded-lg transition-all duration-200 {{#if (eq activePage 'observability')}}bg-white shadow-sm ring-1 ring-slate-200 text-indigo-700{{else}}hover:bg-slate-200/50 hover:text-slate-900{{/if}}">Observability</a></li>
62
-
63
- <li class="pt-5 pb-1 px-3"><span class="font-bold text-slate-900 uppercase text-[10px] tracking-widest">5. References</span></li>
64
- <li><a href="integrations.html" class="flex items-center px-3 py-2 rounded-lg transition-all duration-200 {{#if (eq activePage 'integrations')}}bg-white shadow-sm ring-1 ring-slate-200 text-indigo-700{{else}}hover:bg-slate-200/50 hover:text-slate-900{{/if}}">Client Integrations</a></li>
125
+ <!-- Sidebar Navigation -->
126
+ <aside id="sidebar" class="py-6 px-2">
127
+ <div class="mb-8 px-4">
128
+ <h3 class="text-[11px] font-bold text-slate-400 uppercase tracking-[0.2em] mb-4">Documentation</h3>
129
+ <ul class="space-y-1">
130
+ <li><a href="index.html" class="block px-4 py-2 rounded-lg text-sm transition-all {{#if (eq activePage 'index')}}sidebar-link-active{{else}}text-slate-600 hover:bg-slate-100 hover:text-slate-900{{/if}}">Introduction</a></li>
131
+ <li><a href="legend.html" class="block px-4 py-2 rounded-lg text-sm transition-all {{#if (eq activePage 'legend')}}sidebar-link-active{{else}}text-slate-600 hover:bg-slate-100 hover:text-slate-900{{/if}}">🦆 The Legend</a></li>
65
132
  </ul>
66
- </aside>
67
-
68
- <main class="flex-1 lg:ml-72 p-6 md:p-10 max-w-4xl xl:max-w-5xl">
69
- <!-- White Content Card -->
70
- <div class="bg-white rounded-2xl shadow-sm border border-slate-200 p-8 md:p-12 mb-10 relative overflow-hidden">
71
- <!-- Background Decoration -->
72
- <div class="absolute top-0 right-0 w-64 h-64 bg-slate-50 rounded-bl-full -z-0"></div>
73
-
74
- <!-- Content injected here (raised z-index) -->
75
- <div class="relative z-10">
76
- {{{body}}}
77
- </div>
133
+ </div>
134
+
135
+ <div class="mb-8 px-4">
136
+ <h3 class="text-[11px] font-bold text-slate-400 uppercase tracking-[0.2em] mb-4">GDL Language</h3>
137
+ <ul class="space-y-1 border-l-2 border-indigo-100 ml-2">
138
+ <li><a href="gdl.html" class="block px-4 py-2 rounded-r-lg text-sm transition-all {{#if (eq activePage 'gdl')}}sidebar-link-active{{else}}text-slate-600 hover:bg-slate-100 hover:text-slate-900{{/if}}">⚡ Getting Started</a></li>
139
+ <li><a href="gdl-entities.html" class="block px-4 py-2 rounded-r-lg text-sm transition-all {{#if (eq activePage 'gdl-entities')}}sidebar-link-active{{else}}text-slate-600 hover:bg-slate-100 hover:text-slate-900{{/if}}">💎 Entities & Fields</a></li>
140
+ <li><a href="gdl-relationships.html" class="block px-4 py-2 rounded-r-lg text-sm transition-all {{#if (eq activePage 'gdl-relationships')}}sidebar-link-active{{else}}text-slate-600 hover:bg-slate-100 hover:text-slate-900{{/if}}">🧬 Relationships</a></li>
141
+ <li><a href="gdl-annotations.html" class="block px-4 py-2 rounded-r-lg text-sm transition-all {{#if (eq activePage 'gdl-annotations')}}sidebar-link-active{{else}}text-slate-600 hover:bg-slate-100 hover:text-slate-900{{/if}}">🎯 Power-up Annotations</a></li>
142
+ <li><a href="gdl-advanced.html" class="block px-4 py-2 rounded-r-lg text-sm transition-all {{#if (eq activePage 'gdl-advanced')}}sidebar-link-active{{else}}text-slate-600 hover:bg-slate-100 hover:text-slate-900{{/if}}">🔮 Advanced Topics</a></li>
143
+ </ul>
144
+ </div>
145
+
146
+ <div class="mb-8 px-4">
147
+ <div class="flex items-center justify-between mb-4">
148
+ <h3 class="text-[11px] font-bold text-slate-400 uppercase tracking-[0.2em]">Generation & CLI</h3>
149
+ <span class="px-2 py-0.5 bg-indigo-600 text-white text-[9px] font-black rounded-lg animate-pulse shadow-lg shadow-indigo-200">NEW</span>
78
150
  </div>
79
-
80
- <footer class="mt-8 text-center text-slate-400 text-sm mb-8 flex justify-center items-center space-x-2">
81
- <span>Generated with</span>
82
- <span class="text-lg">🦆</span>
83
- <span>by <strong>GO-DUCK CLI</strong></span>
151
+ <ul class="space-y-1">
152
+ <li><a href="wizard.html" class="block px-4 py-2 rounded-lg text-sm transition-all {{#if (eq activePage 'wizard')}}sidebar-link-active font-black bg-indigo-50{{else}}text-slate-600 font-bold hover:bg-slate-100 hover:text-slate-900{{/if}}">✨ Config Wizard</a></li>
153
+ <li><a href="cli.html" class="block px-4 py-2 rounded-lg text-sm transition-all {{#if (eq activePage 'cli')}}sidebar-link-active{{else}}text-slate-600 hover:bg-slate-100 hover:text-slate-900{{/if}}">CLI Reference</a></li>
154
+ <li><a href="configuration.html" class="block px-4 py-2 rounded-lg text-sm transition-all {{#if (eq activePage 'configuration')}}sidebar-link-active{{else}}text-slate-600 hover:bg-slate-100 hover:text-slate-900{{/if}}">⚙️ Configuration</a></li>
155
+ </ul>
156
+ </div>
157
+
158
+ <div class="mb-8 px-4">
159
+ <h3 class="text-[11px] font-bold text-slate-400 uppercase tracking-[0.2em] mb-4">API Features</h3>
160
+ <ul class="space-y-1">
161
+ <li><a href="rest.html" class="block px-4 py-2 rounded-lg text-sm transition-all {{#if (eq activePage 'rest')}}sidebar-link-active{{else}}text-slate-600 hover:bg-slate-100 hover:text-slate-900{{/if}}">REST & Search</a></li>
162
+ <li><a href="graphql.html" class="block px-4 py-2 rounded-lg text-sm transition-all {{#if (eq activePage 'graphql')}}sidebar-link-active{{else}}text-slate-600 hover:bg-slate-100 hover:text-slate-900{{/if}}">GraphQL Integration</a></li>
163
+ <li><a href="multitenancy.html" class="block px-4 py-2 rounded-lg text-sm transition-all {{#if (eq activePage 'multitenancy')}}sidebar-link-active{{else}}text-slate-600 hover:bg-slate-100 hover:text-slate-900{{/if}}">Multi-Tenancy</a></li>
164
+ <li><a href="federation.html" class="block px-4 py-2 rounded-lg text-sm transition-all {{#if (eq activePage 'federation')}}sidebar-link-active{{else}}text-slate-600 hover:bg-slate-100 hover:text-slate-900{{/if}}">Federated Architecture</a></li>
165
+ <li><a href="hybrid-store.html" class="block px-4 py-2 rounded-lg text-sm transition-all {{#if (eq activePage 'hybrid-store')}}sidebar-link-active{{else}}text-slate-600 hover:bg-slate-100 hover:text-slate-900{{/if}}">🍀 Hybrid-Store</a></li>
166
+ <li><a href="grpc.html" class="block px-4 py-2 rounded-lg text-sm transition-all {{#if (eq activePage 'grpc')}}sidebar-link-active{{else}}text-slate-600 hover:bg-slate-100 hover:text-slate-900{{/if}}">gRPC Service</a></li>
167
+ </ul>
168
+ </div>
169
+
170
+ <div class="mb-8 px-4">
171
+ <h3 class="text-[11px] font-bold text-slate-400 uppercase tracking-[0.2em] mb-4">Monitoring</h3>
172
+ <ul class="space-y-1">
173
+ <li><a href="realtime.html" class="block px-4 py-2 rounded-lg text-sm transition-all {{#if (eq activePage 'realtime')}}sidebar-link-active{{else}}text-slate-600 hover:bg-slate-100 hover:text-slate-900{{/if}}">WebSockets</a></li>
174
+ <li><a href="audit.html" class="block px-4 py-2 rounded-lg text-sm transition-all {{#if (eq activePage 'audit')}}sidebar-link-active{{else}}text-slate-600 hover:bg-slate-100 hover:text-slate-900{{/if}}">Audit Logs</a></li>
175
+ <li><a href="observability.html" class="block px-4 py-2 rounded-lg text-sm transition-all {{#if (eq activePage 'observability')}}sidebar-link-active{{else}}text-slate-600 hover:bg-slate-100 hover:text-slate-900{{/if}}">Observability</a></li>
176
+ </ul>
177
+ </div>
178
+
179
+ <div class="mb-8 px-4">
180
+ <h3 class="text-[11px] font-bold text-slate-400 uppercase tracking-[0.2em] mb-4">Infrastructure</h3>
181
+ <ul class="space-y-1">
182
+ <li><a href="security.html" class="block px-4 py-2 rounded-lg text-sm transition-all {{#if (eq activePage 'security')}}sidebar-link-active{{else}}text-slate-600 hover:bg-slate-100 hover:text-slate-900{{/if}}">Security</a></li>
183
+ <li><a href="redis.html" class="block px-4 py-2 rounded-lg text-sm transition-all {{#if (eq activePage 'redis')}}sidebar-link-active{{else}}text-slate-600 hover:bg-slate-100 hover:text-slate-900{{/if}}">Redis</a></li>
184
+ <li><a href="keycloak.html" class="block px-4 py-2 rounded-lg text-sm transition-all {{#if (eq activePage 'keycloak')}}sidebar-link-active{{else}}text-slate-600 hover:bg-slate-100 hover:text-slate-900{{/if}}">Keycloak</a></li>
185
+ <li><a href="serverless.html" class="block px-4 py-2 rounded-lg text-sm transition-all {{#if (eq activePage 'serverless')}}sidebar-link-active{{else}}text-slate-600 hover:bg-slate-100 hover:text-slate-900{{/if}}">🚀 Serverless</a></li>
186
+ </ul>
187
+ </div>
188
+ </aside>
189
+
190
+ <!-- Main Content Wrapper -->
191
+ <div id="content-wrapper">
192
+ <main class="w-full px-6 md:px-12 py-10">
193
+ <article class="max-w-none prose prose-slate prose-blue">
194
+ {{{body}}}
195
+ </article>
196
+
197
+ <!-- Consolidated Elite Footer -->
198
+ <footer class="mt-24 py-12 border-t border-slate-100 flex flex-col md:flex-row justify-between items-center gap-12 text-slate-400 text-[10px] font-bold uppercase tracking-[0.3em]">
199
+ <div class="flex flex-col items-center md:items-start gap-4">
200
+ <p>© 2024-2026 GO-DUCK ELITE ECOSYSTEM. ALL RIGHTS RESERVED.</p>
201
+ <div class="flex gap-10">
202
+ <a href="security.html" class="hover:text-indigo-600 transition-colors">Digital Defense</a>
203
+ <a href="audit.html" class="hover:text-indigo-600 transition-colors">Privacy Shield</a>
204
+ <a href="configuration.html" class="hover:text-indigo-600 transition-colors">Architecture Audit</a>
205
+ </div>
206
+ </div>
207
+ <div class="flex items-center gap-6">
208
+ <div class="flex flex-col items-end gap-1">
209
+ <span class="text-[9px] text-slate-300 uppercase">System Architecture</span>
210
+ <span class="text-slate-400 font-black">INDUSTRIAL GRADE VR.1</span>
211
+ </div>
212
+ <span class="text-4xl filter grayscale opacity-20 transform hover:grayscale-0 hover:opacity-100 transition-all duration-700 cursor-alias">🦆</span>
213
+ </div>
84
214
  </footer>
85
215
  </main>
86
216
  </div>
217
+
87
218
  <!-- Lightbox Overlay -->
88
- <div id="lightbox" class="fixed inset-0 bg-slate-900/90 backdrop-blur-sm z-[100] hidden flex items-center justify-center p-4 md:p-10 cursor-zoom-out" onclick="this.classList.add('hidden')">
89
- <div class="relative max-w-5xl w-full h-full flex items-center justify-center">
90
- <img id="lightbox-img" src="" alt="Enlarged View" class="max-w-full max-h-full object-contain rounded-xl shadow-2xl transition-all duration-300">
91
- <button class="absolute top-0 right-0 m-4 text-white hover:text-indigo-300 transition-colors">
92
- <svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
219
+ <div id="lightbox" class="fixed inset-0 bg-slate-900/95 backdrop-blur-md z-[100] hidden flex items-center justify-center p-4 md:p-10 cursor-zoom-out transition-all duration-300 opacity-0" onclick="closeLightbox()">
220
+ <div class="relative max-w-6xl w-full h-full flex flex-col items-center justify-center transform scale-95 transition-transform duration-300" id="lightbox-content">
221
+ <img id="lightbox-img" src="" alt="Enlarged View" class="max-w-full max-h-full object-contain rounded-2xl shadow-2xl ring-1 ring-white/10">
222
+ <button class="absolute top-4 right-4 m-4 text-white/70 hover:text-white bg-black/20 hover:bg-black/40 rounded-full p-2 transition-all p-3" onclick="closeLightbox(event)">
223
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
93
224
  </button>
94
225
  </div>
95
226
  </div>
96
227
 
97
228
  <script>
229
+ const mobileMenuBtn = document.getElementById('mobile-menu-btn');
230
+ const sidebar = document.getElementById('sidebar');
231
+
232
+ if (mobileMenuBtn && sidebar) {
233
+ mobileMenuBtn.addEventListener('click', () => {
234
+ sidebar.classList.toggle('open');
235
+ });
236
+ }
237
+
98
238
  function openLightbox(src) {
99
239
  const lb = document.getElementById('lightbox');
240
+ const content = document.getElementById('lightbox-content');
100
241
  const img = document.getElementById('lightbox-img');
101
242
  img.src = src;
102
243
  lb.classList.remove('hidden');
244
+ void lb.offsetWidth;
245
+ lb.classList.remove('opacity-0');
246
+ lb.classList.add('opacity-100');
247
+ content.classList.remove('scale-95');
248
+ content.classList.add('scale-100');
249
+ }
250
+
251
+ function closeLightbox(e) {
252
+ if (e) e.stopPropagation();
253
+ const lb = document.getElementById('lightbox');
254
+ const content = document.getElementById('lightbox-content');
255
+ lb.classList.remove('opacity-100');
256
+ lb.classList.add('opacity-0');
257
+ content.classList.remove('scale-100');
258
+ content.classList.add('scale-95');
259
+ setTimeout(() => {
260
+ lb.classList.add('hidden');
261
+ }, 300);
103
262
  }
104
263
  </script>
105
264
  </body>