nitrostack 1.0.0 → 1.0.2

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 (164) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/cli/index.js +4 -1
  3. package/dist/cli/index.js.map +1 -1
  4. package/package.json +1 -1
  5. package/src/studio/README.md +140 -0
  6. package/src/studio/app/api/auth/fetch-metadata/route.ts +71 -0
  7. package/src/studio/app/api/auth/register-client/route.ts +67 -0
  8. package/src/studio/app/api/chat/route.ts +123 -0
  9. package/src/studio/app/api/health/checks/route.ts +42 -0
  10. package/src/studio/app/api/health/route.ts +13 -0
  11. package/src/studio/app/api/init/route.ts +85 -0
  12. package/src/studio/app/api/ping/route.ts +13 -0
  13. package/src/studio/app/api/prompts/[name]/route.ts +21 -0
  14. package/src/studio/app/api/prompts/route.ts +13 -0
  15. package/src/studio/app/api/resources/[...uri]/route.ts +18 -0
  16. package/src/studio/app/api/resources/route.ts +13 -0
  17. package/src/studio/app/api/roots/route.ts +13 -0
  18. package/src/studio/app/api/sampling/route.ts +14 -0
  19. package/src/studio/app/api/tools/[name]/call/route.ts +41 -0
  20. package/src/studio/app/api/tools/route.ts +23 -0
  21. package/src/studio/app/api/widget-examples/route.ts +44 -0
  22. package/src/studio/app/auth/callback/page.tsx +160 -0
  23. package/src/studio/app/auth/page.tsx +543 -0
  24. package/src/studio/app/chat/page.tsx +530 -0
  25. package/src/studio/app/chat/page.tsx.backup +390 -0
  26. package/src/studio/app/globals.css +410 -0
  27. package/src/studio/app/health/page.tsx +177 -0
  28. package/src/studio/app/layout.tsx +48 -0
  29. package/src/studio/app/page.tsx +337 -0
  30. package/src/studio/app/page.tsx.backup +346 -0
  31. package/src/studio/app/ping/page.tsx +204 -0
  32. package/src/studio/app/prompts/page.tsx +228 -0
  33. package/src/studio/app/resources/page.tsx +313 -0
  34. package/src/studio/components/EnlargeModal.tsx +116 -0
  35. package/src/studio/components/Sidebar.tsx +133 -0
  36. package/src/studio/components/ToolCard.tsx +108 -0
  37. package/src/studio/components/WidgetRenderer.tsx +99 -0
  38. package/src/studio/lib/api.ts +207 -0
  39. package/src/studio/lib/llm-service.ts +361 -0
  40. package/src/studio/lib/mcp-client.ts +168 -0
  41. package/src/studio/lib/store.ts +192 -0
  42. package/src/studio/lib/theme-provider.tsx +50 -0
  43. package/src/studio/lib/types.ts +107 -0
  44. package/src/studio/lib/widget-loader.ts +90 -0
  45. package/src/studio/middleware.ts +27 -0
  46. package/src/studio/next.config.js +16 -0
  47. package/src/studio/package-lock.json +2696 -0
  48. package/src/studio/package.json +34 -0
  49. package/src/studio/postcss.config.mjs +10 -0
  50. package/src/studio/tailwind.config.ts +67 -0
  51. package/src/studio/tsconfig.json +41 -0
  52. package/templates/typescript-auth/.env.example +23 -0
  53. package/templates/typescript-auth/src/app.module.ts +103 -0
  54. package/templates/typescript-auth/src/db/database.ts +163 -0
  55. package/templates/typescript-auth/src/db/seed.ts +374 -0
  56. package/templates/typescript-auth/src/db/setup.ts +87 -0
  57. package/templates/typescript-auth/src/events/analytics.service.ts +52 -0
  58. package/templates/typescript-auth/src/events/notification.service.ts +40 -0
  59. package/templates/typescript-auth/src/filters/global-exception.filter.ts +28 -0
  60. package/templates/typescript-auth/src/guards/README.md +75 -0
  61. package/templates/typescript-auth/src/guards/jwt.guard.ts +105 -0
  62. package/templates/typescript-auth/src/health/database.health.ts +41 -0
  63. package/templates/typescript-auth/src/index.ts +26 -0
  64. package/templates/typescript-auth/src/interceptors/transform.interceptor.ts +24 -0
  65. package/templates/typescript-auth/src/middleware/logging.middleware.ts +42 -0
  66. package/templates/typescript-auth/src/modules/addresses/addresses.module.ts +16 -0
  67. package/templates/typescript-auth/src/modules/addresses/addresses.prompts.ts +114 -0
  68. package/templates/typescript-auth/src/modules/addresses/addresses.resources.ts +40 -0
  69. package/templates/typescript-auth/src/modules/addresses/addresses.tools.ts +241 -0
  70. package/templates/typescript-auth/src/modules/auth/auth.module.ts +16 -0
  71. package/templates/typescript-auth/src/modules/auth/auth.prompts.ts +147 -0
  72. package/templates/typescript-auth/src/modules/auth/auth.resources.ts +84 -0
  73. package/templates/typescript-auth/src/modules/auth/auth.tools.ts +139 -0
  74. package/templates/typescript-auth/src/modules/cart/cart.module.ts +16 -0
  75. package/templates/typescript-auth/src/modules/cart/cart.prompts.ts +95 -0
  76. package/templates/typescript-auth/src/modules/cart/cart.resources.ts +44 -0
  77. package/templates/typescript-auth/src/modules/cart/cart.tools.ts +281 -0
  78. package/templates/typescript-auth/src/modules/orders/orders.module.ts +16 -0
  79. package/templates/typescript-auth/src/modules/orders/orders.prompts.ts +88 -0
  80. package/templates/typescript-auth/src/modules/orders/orders.resources.ts +48 -0
  81. package/templates/typescript-auth/src/modules/orders/orders.tools.ts +281 -0
  82. package/templates/typescript-auth/src/modules/products/products.module.ts +16 -0
  83. package/templates/typescript-auth/src/modules/products/products.prompts.ts +146 -0
  84. package/templates/typescript-auth/src/modules/products/products.resources.ts +98 -0
  85. package/templates/typescript-auth/src/modules/products/products.tools.ts +266 -0
  86. package/templates/typescript-auth/src/pipes/validation.pipe.ts +42 -0
  87. package/templates/typescript-auth/src/services/database.service.ts +90 -0
  88. package/templates/typescript-auth/src/widgets/app/add-to-cart/page.tsx +122 -0
  89. package/templates/typescript-auth/src/widgets/app/address-added/page.tsx +116 -0
  90. package/templates/typescript-auth/src/widgets/app/address-deleted/page.tsx +105 -0
  91. package/templates/typescript-auth/src/widgets/app/address-list/page.tsx +139 -0
  92. package/templates/typescript-auth/src/widgets/app/address-updated/page.tsx +153 -0
  93. package/templates/typescript-auth/src/widgets/app/cart-cleared/page.tsx +86 -0
  94. package/templates/typescript-auth/src/widgets/app/cart-updated/page.tsx +116 -0
  95. package/templates/typescript-auth/src/widgets/app/categories/page.tsx +134 -0
  96. package/templates/typescript-auth/src/widgets/app/layout.tsx +21 -0
  97. package/templates/typescript-auth/src/widgets/app/login-result/page.tsx +129 -0
  98. package/templates/typescript-auth/src/widgets/app/order-confirmation/page.tsx +206 -0
  99. package/templates/typescript-auth/src/widgets/app/order-details/page.tsx +225 -0
  100. package/templates/typescript-auth/src/widgets/app/order-history/page.tsx +218 -0
  101. package/templates/typescript-auth/src/widgets/app/product-card/page.tsx +121 -0
  102. package/templates/typescript-auth/src/widgets/app/products-grid/page.tsx +173 -0
  103. package/templates/typescript-auth/src/widgets/app/shopping-cart/page.tsx +187 -0
  104. package/templates/typescript-auth/src/widgets/app/whoami/page.tsx +165 -0
  105. package/templates/typescript-auth/src/widgets/next.config.js +38 -0
  106. package/templates/typescript-auth/src/widgets/package.json +18 -0
  107. package/templates/typescript-auth/src/widgets/styles/ecommerce.ts +169 -0
  108. package/templates/typescript-auth/src/widgets/tsconfig.json +28 -0
  109. package/templates/typescript-auth/src/widgets/types/tool-data.ts +141 -0
  110. package/templates/typescript-auth/src/widgets/widget-manifest.json +464 -0
  111. package/templates/typescript-auth/tsconfig.json +27 -0
  112. package/templates/typescript-auth-api-key/.env +15 -0
  113. package/templates/typescript-auth-api-key/.env.example +4 -0
  114. package/templates/typescript-auth-api-key/src/app.module.ts +38 -0
  115. package/templates/typescript-auth-api-key/src/guards/apikey.guard.ts +47 -0
  116. package/templates/typescript-auth-api-key/src/guards/multi-auth.guard.ts +157 -0
  117. package/templates/typescript-auth-api-key/src/health/system.health.ts +55 -0
  118. package/templates/typescript-auth-api-key/src/index.ts +47 -0
  119. package/templates/typescript-auth-api-key/src/modules/calculator/calculator.module.ts +12 -0
  120. package/templates/typescript-auth-api-key/src/modules/calculator/calculator.prompts.ts +73 -0
  121. package/templates/typescript-auth-api-key/src/modules/calculator/calculator.resources.ts +60 -0
  122. package/templates/typescript-auth-api-key/src/modules/calculator/calculator.tools.ts +71 -0
  123. package/templates/typescript-auth-api-key/src/modules/demo/demo.module.ts +18 -0
  124. package/templates/typescript-auth-api-key/src/modules/demo/demo.tools.ts +155 -0
  125. package/templates/typescript-auth-api-key/src/modules/demo/multi-auth.tools.ts +123 -0
  126. package/templates/typescript-auth-api-key/src/widgets/app/calculator-operations/page.tsx +133 -0
  127. package/templates/typescript-auth-api-key/src/widgets/app/calculator-result/page.tsx +134 -0
  128. package/templates/typescript-auth-api-key/src/widgets/app/layout.tsx +14 -0
  129. package/templates/typescript-auth-api-key/src/widgets/next.config.js +37 -0
  130. package/templates/typescript-auth-api-key/src/widgets/package.json +24 -0
  131. package/templates/typescript-auth-api-key/src/widgets/tsconfig.json +28 -0
  132. package/templates/typescript-auth-api-key/src/widgets/widget-manifest.json +48 -0
  133. package/templates/typescript-auth-api-key/tsconfig.json +23 -0
  134. package/templates/typescript-oauth/.env.example +91 -0
  135. package/templates/typescript-oauth/src/app.module.ts +89 -0
  136. package/templates/typescript-oauth/src/guards/oauth.guard.ts +127 -0
  137. package/templates/typescript-oauth/src/index.ts +74 -0
  138. package/templates/typescript-oauth/src/modules/demo/demo.module.ts +16 -0
  139. package/templates/typescript-oauth/src/modules/demo/demo.tools.ts +190 -0
  140. package/templates/typescript-oauth/src/widgets/app/calculator-operations/page.tsx +133 -0
  141. package/templates/typescript-oauth/src/widgets/app/calculator-result/page.tsx +134 -0
  142. package/templates/typescript-oauth/src/widgets/app/layout.tsx +14 -0
  143. package/templates/typescript-oauth/src/widgets/next.config.js +37 -0
  144. package/templates/typescript-oauth/src/widgets/package.json +24 -0
  145. package/templates/typescript-oauth/src/widgets/tsconfig.json +28 -0
  146. package/templates/typescript-oauth/src/widgets/widget-manifest.json +48 -0
  147. package/templates/typescript-oauth/tsconfig.json +23 -0
  148. package/templates/typescript-starter/.env.example +4 -0
  149. package/templates/typescript-starter/src/app.module.ts +34 -0
  150. package/templates/typescript-starter/src/health/system.health.ts +55 -0
  151. package/templates/typescript-starter/src/index.ts +27 -0
  152. package/templates/typescript-starter/src/modules/calculator/calculator.module.ts +12 -0
  153. package/templates/typescript-starter/src/modules/calculator/calculator.prompts.ts +73 -0
  154. package/templates/typescript-starter/src/modules/calculator/calculator.resources.ts +60 -0
  155. package/templates/typescript-starter/src/modules/calculator/calculator.tools.ts +71 -0
  156. package/templates/typescript-starter/src/widgets/app/calculator-operations/page.tsx +133 -0
  157. package/templates/typescript-starter/src/widgets/app/calculator-result/page.tsx +134 -0
  158. package/templates/typescript-starter/src/widgets/app/layout.tsx +14 -0
  159. package/templates/typescript-starter/src/widgets/next.config.js +37 -0
  160. package/templates/typescript-starter/src/widgets/package.json +24 -0
  161. package/templates/typescript-starter/src/widgets/tsconfig.json +28 -0
  162. package/templates/typescript-starter/src/widgets/widget-manifest.json +48 -0
  163. package/templates/typescript-starter/tsconfig.json +23 -0
  164. package/LICENSE_URLS_UPDATE_COMPLETE.md +0 -388
@@ -0,0 +1,266 @@
1
+ import {
2
+ ToolDecorator as Tool,
3
+ Widget,
4
+ z,
5
+ ExecutionContext,
6
+ Injectable,
7
+ UseMiddleware,
8
+ UseInterceptors,
9
+ UsePipes,
10
+ UseFilters,
11
+ Cache,
12
+ RateLimit,
13
+ } from 'nitrostack';
14
+ import { DatabaseService } from '../../services/database.service.js';
15
+ import { LoggingMiddleware } from '../../middleware/logging.middleware.js';
16
+ import { TransformInterceptor } from '../../interceptors/transform.interceptor.js';
17
+ import { ValidationPipe } from '../../pipes/validation.pipe.js';
18
+ import { GlobalExceptionFilter } from '../../filters/global-exception.filter.js';
19
+
20
+ /**
21
+ * Products Tools
22
+ *
23
+ * Demonstrates all V3.1 features:
24
+ * - Dependency Injection (@Injectable)
25
+ * - Middleware (@UseMiddleware)
26
+ * - Interceptors (@UseInterceptors)
27
+ * - Pipes (@UsePipes)
28
+ * - Exception Filters (@UseFilters)
29
+ * - Caching (@Cache)
30
+ * - Rate Limiting (@RateLimit)
31
+ */
32
+ @Injectable()
33
+ export class ProductsTools {
34
+ constructor(private database: DatabaseService) {}
35
+
36
+ /**
37
+ * Browse products - No authentication required
38
+ * Demonstrates: Middleware, Interceptors, Pipes, Caching
39
+ */
40
+ @Tool({
41
+ name: 'browse_products',
42
+ description: 'Browse available products. Can filter by category, search by name, and paginate results.',
43
+ inputSchema: z.object({
44
+ category: z.enum(['Electronics', 'Clothing', 'Home', 'Books', 'Sports', 'All']).default('All').describe('Filter by product category'),
45
+ search: z.string().optional().describe('Search products by name'),
46
+ page: z.number().min(1).default(1).describe('Page number for pagination'),
47
+ limit: z.number().min(1).max(50).default(10).describe('Number of products per page'),
48
+ }),
49
+ examples: {
50
+ request: {
51
+ category: 'Beauty',
52
+ page: 1,
53
+ limit: 4
54
+ },
55
+ response: {
56
+ products: [
57
+ {
58
+ id: 'prod-1',
59
+ name: 'Essence Mascara Lash Princess',
60
+ description: 'The Essence Mascara Lash Princess is a popular mascara known for its volumizing and lengthening effects.',
61
+ price: 9.99,
62
+ category: 'Beauty',
63
+ stock: 99,
64
+ image_url: 'https://cdn.dummyjson.com/product-images/beauty/essence-mascara-lash-princess/thumbnail.webp'
65
+ },
66
+ {
67
+ id: 'prod-2',
68
+ name: 'Eyeshadow Palette with Mirror',
69
+ description: 'The Eyeshadow Palette offers a versatile range of eyeshadow shades for creating stunning eye looks.',
70
+ price: 19.99,
71
+ category: 'Beauty',
72
+ stock: 34,
73
+ image_url: 'https://cdn.dummyjson.com/product-images/beauty/eyeshadow-palette-with-mirror/thumbnail.webp'
74
+ },
75
+ {
76
+ id: 'prod-3',
77
+ name: 'Powder Canister',
78
+ description: 'The Powder Canister is a finely milled setting powder designed to set makeup and control shine.',
79
+ price: 14.99,
80
+ category: 'Beauty',
81
+ stock: 89,
82
+ image_url: 'https://cdn.dummyjson.com/product-images/beauty/powder-canister/thumbnail.webp'
83
+ },
84
+ {
85
+ id: 'prod-4',
86
+ name: 'Red Lipstick',
87
+ description: 'The Red Lipstick is a classic and bold choice for adding a pop of color to your lips.',
88
+ price: 12.99,
89
+ category: 'Beauty',
90
+ stock: 91,
91
+ image_url: 'https://cdn.dummyjson.com/product-images/beauty/red-lipstick/thumbnail.webp'
92
+ }
93
+ ],
94
+ pagination: {
95
+ page: 1,
96
+ limit: 4,
97
+ totalPages: 2,
98
+ totalResults: 5
99
+ }
100
+ }
101
+ }
102
+ })
103
+ @Widget('products-grid')
104
+ @UseMiddleware(LoggingMiddleware)
105
+ @UseInterceptors(TransformInterceptor)
106
+ @UsePipes(ValidationPipe)
107
+ @UseFilters(GlobalExceptionFilter)
108
+ @Cache({ ttl: 60, key: (input) => `products:${input.category}:${input.search}:${input.page}` })
109
+ async browseProducts(input: any, context: ExecutionContext) {
110
+ // Apply defaults if not provided
111
+ const category = input.category || 'All';
112
+ const page = input.page || 1;
113
+ const limit = input.limit || 10;
114
+ const search = input.search || '';
115
+
116
+ const offset = (page - 1) * limit;
117
+
118
+ let query = 'SELECT * FROM products WHERE 1=1';
119
+ const params: any[] = [];
120
+
121
+ // Filter by category
122
+ if (category && category !== 'All') {
123
+ query += ' AND category = ?';
124
+ params.push(category);
125
+ }
126
+
127
+ // Search by name
128
+ if (search) {
129
+ query += ' AND name LIKE ?';
130
+ params.push(`%${search}%`);
131
+ }
132
+
133
+ // Add pagination
134
+ query += ' ORDER BY name LIMIT ? OFFSET ?';
135
+ params.push(limit, offset);
136
+
137
+ const products = this.database.query(query, params);
138
+
139
+ // Get total count
140
+ let countQuery = 'SELECT COUNT(*) as count FROM products WHERE 1=1';
141
+ const countParams: any[] = [];
142
+
143
+ if (category && category !== 'All') {
144
+ countQuery += ' AND category = ?';
145
+ countParams.push(category);
146
+ }
147
+
148
+ if (search) {
149
+ countQuery += ' AND name LIKE ?';
150
+ countParams.push(`%${search}%`);
151
+ }
152
+
153
+ const result = this.database.queryOne<{ count: number }>(countQuery, countParams);
154
+ const count = result?.count || 0;
155
+ const totalPages = Math.ceil(count / limit);
156
+
157
+ context.logger.info(`Browsing products: category=${category}, search=${search}, page=${page}, found=${products.length} products`);
158
+
159
+ return {
160
+ products,
161
+ pagination: {
162
+ page,
163
+ limit,
164
+ total: count,
165
+ totalPages,
166
+ hasMore: page < totalPages,
167
+ },
168
+ };
169
+ }
170
+
171
+ /**
172
+ * Get product details - No authentication required
173
+ * Demonstrates: All features + Rate Limiting
174
+ */
175
+ @Tool({
176
+ name: 'get_product_details',
177
+ description: 'Get detailed information about a specific product by ID.',
178
+ inputSchema: z.object({
179
+ product_id: z.string().describe('The ID of the product to retrieve'),
180
+ }),
181
+ examples: {
182
+ request: {
183
+ product_id: 'prod-1'
184
+ },
185
+ response: {
186
+ product: {
187
+ id: 'prod-1',
188
+ name: 'Essence Mascara Lash Princess',
189
+ description: 'The Essence Mascara Lash Princess is a popular mascara known for its volumizing and lengthening effects.',
190
+ price: 9.99,
191
+ category: 'Beauty',
192
+ stock: 99,
193
+ image_url: 'https://cdn.dummyjson.com/product-images/beauty/essence-mascara-lash-princess/thumbnail.webp'
194
+ },
195
+ availability: 'In Stock',
196
+ stockMessage: 'Available'
197
+ }
198
+ }
199
+ })
200
+ @Widget('product-card')
201
+ @UseMiddleware(LoggingMiddleware)
202
+ @UseInterceptors(TransformInterceptor)
203
+ @UsePipes(ValidationPipe)
204
+ @UseFilters(GlobalExceptionFilter)
205
+ @Cache({ ttl: 300, key: (input) => `product:${input.product_id}` }) // Cache for 5 minutes
206
+ @RateLimit({ requests: 100, window: '1m' }) // 100 requests per minute
207
+ async getProductDetails(input: any, context: ExecutionContext) {
208
+ const product = this.database.queryOne(`SELECT * FROM products WHERE id = ?`, [input.product_id]);
209
+
210
+ if (!product) {
211
+ throw new Error(`Product with ID ${input.product_id} not found`);
212
+ }
213
+
214
+ context.logger.info(`Retrieved details for product: ${(product as any).name}`);
215
+
216
+ return {
217
+ product,
218
+ availability: (product as any).stock > 0 ? 'In Stock' : 'Out of Stock',
219
+ stockMessage: (product as any).stock > 10
220
+ ? 'Available'
221
+ : (product as any).stock > 0
222
+ ? `Only ${(product as any).stock} left!`
223
+ : 'Currently unavailable',
224
+ };
225
+ }
226
+
227
+ /**
228
+ * Get product categories - No authentication required
229
+ * Demonstrates: Simple caching
230
+ */
231
+ @Tool({
232
+ name: 'get_categories',
233
+ description: 'Get all available product categories.',
234
+ inputSchema: z.object({}),
235
+ examples: {
236
+ request: {},
237
+ response: {
238
+ categories: [
239
+ { name: 'Beauty', productCount: 5 },
240
+ { name: 'Fragrances', productCount: 5 },
241
+ { name: 'Furniture', productCount: 5 },
242
+ { name: 'Groceries', productCount: 5 }
243
+ ]
244
+ }
245
+ }
246
+ })
247
+ @Widget('categories')
248
+ @UseMiddleware(LoggingMiddleware)
249
+ @UseInterceptors(TransformInterceptor)
250
+ @Cache({ ttl: 300 }) // Cache for 5 minutes
251
+ async getCategories(input: any, context: ExecutionContext) {
252
+ const categories = this.database.query(`
253
+ SELECT DISTINCT category, COUNT(*) as count
254
+ FROM products
255
+ GROUP BY category
256
+ ORDER BY category
257
+ `);
258
+
259
+ return {
260
+ categories: categories.map((c: any) => ({
261
+ name: c.category,
262
+ productCount: c.count,
263
+ })),
264
+ };
265
+ }
266
+ }
@@ -0,0 +1,42 @@
1
+ import { Pipe, PipeInterface, ArgumentMetadata } from 'nitrostack';
2
+
3
+ /**
4
+ * Validation Pipe
5
+ *
6
+ * Validates and sanitizes input data
7
+ */
8
+ @Pipe()
9
+ export class ValidationPipe implements PipeInterface {
10
+ transform(value: any, metadata: ArgumentMetadata) {
11
+ if (!value || typeof value !== 'object') {
12
+ throw new Error('Input must be an object');
13
+ }
14
+
15
+ // Sanitize strings
16
+ const sanitized = this.sanitizeObject(value);
17
+
18
+ return sanitized;
19
+ }
20
+
21
+ private sanitizeObject(obj: any): any {
22
+ if (typeof obj !== 'object' || obj === null) {
23
+ return obj;
24
+ }
25
+
26
+ const sanitized: any = Array.isArray(obj) ? [] : {};
27
+
28
+ for (const key in obj) {
29
+ if (typeof obj[key] === 'string') {
30
+ // Trim and sanitize strings
31
+ sanitized[key] = obj[key].trim();
32
+ } else if (typeof obj[key] === 'object') {
33
+ sanitized[key] = this.sanitizeObject(obj[key]);
34
+ } else {
35
+ sanitized[key] = obj[key];
36
+ }
37
+ }
38
+
39
+ return sanitized;
40
+ }
41
+ }
42
+
@@ -0,0 +1,90 @@
1
+ import { Injectable } from 'nitrostack';
2
+ import Database from 'better-sqlite3';
3
+ import { getDatabase } from '../db/database.js';
4
+
5
+ /**
6
+ * Database Service
7
+ *
8
+ * Provides database access with dependency injection
9
+ */
10
+ @Injectable()
11
+ export class DatabaseService {
12
+ private db: Database.Database;
13
+
14
+ constructor() {
15
+ this.db = getDatabase();
16
+ }
17
+
18
+ /**
19
+ * Execute a query
20
+ */
21
+ query<T = any>(sql: string, params: any[] = []): T[] {
22
+ const stmt = this.db.prepare(sql);
23
+ // better-sqlite3 expects individual parameters, not an array
24
+ if (params.length === 0) {
25
+ return stmt.all() as T[];
26
+ }
27
+ return stmt.all(...params) as T[];
28
+ }
29
+
30
+ /**
31
+ * Execute a query and get first result
32
+ */
33
+ queryOne<T = any>(sql: string, params: any[] = []): T | null {
34
+ const stmt = this.db.prepare(sql);
35
+ // better-sqlite3 expects individual parameters, not an array
36
+ if (params.length === 0) {
37
+ return (stmt.get() as T) || null;
38
+ }
39
+ return (stmt.get(...params) as T) || null;
40
+ }
41
+
42
+ /**
43
+ * Insert a record
44
+ */
45
+ insert(table: string, data: Record<string, any>): { lastInsertRowid: number } {
46
+ const keys = Object.keys(data);
47
+ const values = Object.values(data);
48
+ const placeholders = keys.map(() => '?').join(', ');
49
+
50
+ const sql = `INSERT INTO ${table} (${keys.join(', ')}) VALUES (${placeholders})`;
51
+ const stmt = this.db.prepare(sql);
52
+ return stmt.run(...values) as any;
53
+ }
54
+
55
+ /**
56
+ * Update a record
57
+ */
58
+ update(table: string, data: Record<string, any>, where: Record<string, any>): { changes: number } {
59
+ const setClause = Object.keys(data).map(k => `${k} = ?`).join(', ');
60
+ const whereClause = Object.keys(where).map(k => `${k} = ?`).join(' AND ');
61
+
62
+ const sql = `UPDATE ${table} SET ${setClause} WHERE ${whereClause}`;
63
+ const stmt = this.db.prepare(sql);
64
+ return stmt.run(...Object.values(data), ...Object.values(where)) as any;
65
+ }
66
+
67
+ /**
68
+ * Delete a record
69
+ */
70
+ delete(table: string, where: Record<string, any>): { changes: number } {
71
+ const whereClause = Object.keys(where).map(k => `${k} = ?`).join(' AND ');
72
+
73
+ const sql = `DELETE FROM ${table} WHERE ${whereClause}`;
74
+ const stmt = this.db.prepare(sql);
75
+ return stmt.run(...Object.values(where)) as any;
76
+ }
77
+
78
+ /**
79
+ * Check database connectivity
80
+ */
81
+ async ping(): Promise<boolean> {
82
+ try {
83
+ this.db.prepare('SELECT 1').get();
84
+ return true;
85
+ } catch {
86
+ return false;
87
+ }
88
+ }
89
+ }
90
+
@@ -0,0 +1,122 @@
1
+ 'use client';
2
+
3
+ import { withToolData } from 'nitrostack/widgets';
4
+ import * as styles from '../../styles/ecommerce';
5
+
6
+ interface AddToCartData {
7
+ message: string;
8
+ product_id?: string;
9
+ product_name?: string;
10
+ price?: number;
11
+ quantity?: number;
12
+ image_url?: string;
13
+ cartItemCount?: number;
14
+ }
15
+
16
+ function AddToCartWidget({ data }: { data: AddToCartData }) {
17
+ const { message, product_name, price, quantity, image_url, cartItemCount } = data;
18
+
19
+ return (
20
+ <div style={styles.containerStyle}>
21
+ <div style={{
22
+ ...styles.cardStyle,
23
+ maxWidth: '500px',
24
+ margin: '0 auto',
25
+ }}>
26
+ <div style={{
27
+ textAlign: 'center',
28
+ marginBottom: styles.spacing.xl,
29
+ }}>
30
+ <div style={{
31
+ width: '60px',
32
+ height: '60px',
33
+ margin: '0 auto 16px',
34
+ borderRadius: styles.borderRadius.full,
35
+ background: styles.colors.success,
36
+ display: 'flex',
37
+ alignItems: 'center',
38
+ justifyContent: 'center',
39
+ fontSize: '28px',
40
+ }}>
41
+
42
+ </div>
43
+ <h2 style={{
44
+ fontSize: styles.typography.fontSize.xl,
45
+ fontWeight: styles.typography.fontWeight.bold,
46
+ color: styles.colors.gray[900],
47
+ marginBottom: styles.spacing.sm,
48
+ }}>
49
+ {message}
50
+ </h2>
51
+ <p style={{
52
+ fontSize: styles.typography.fontSize.base,
53
+ color: styles.colors.gray[600],
54
+ }}>
55
+ You now have {cartItemCount || 1} {cartItemCount === 1 ? 'item' : 'items'} in your cart
56
+ </p>
57
+ </div>
58
+
59
+ {product_name && (
60
+ <div style={{
61
+ display: 'flex',
62
+ gap: styles.spacing.md,
63
+ padding: styles.spacing.lg,
64
+ background: styles.colors.gray[50],
65
+ borderRadius: styles.borderRadius.md,
66
+ }}>
67
+ {image_url && (
68
+ <div style={{
69
+ width: '80px',
70
+ height: '80px',
71
+ flexShrink: 0,
72
+ borderRadius: styles.borderRadius.md,
73
+ overflow: 'hidden',
74
+ background: styles.colors.white,
75
+ }}>
76
+ <img
77
+ src={image_url}
78
+ alt={product_name}
79
+ style={{
80
+ width: '100%',
81
+ height: '100%',
82
+ objectFit: 'cover',
83
+ }}
84
+ />
85
+ </div>
86
+ )}
87
+
88
+ <div style={{ flex: 1 }}>
89
+ <div style={{
90
+ fontSize: styles.typography.fontSize.base,
91
+ fontWeight: styles.typography.fontWeight.semibold,
92
+ color: styles.colors.gray[900],
93
+ marginBottom: styles.spacing.xs,
94
+ }}>
95
+ {product_name}
96
+ </div>
97
+ <div style={{
98
+ display: 'flex',
99
+ alignItems: 'center',
100
+ gap: styles.spacing.md,
101
+ }}>
102
+ {price && (
103
+ <div style={styles.priceStyle}>
104
+ ${price.toFixed(2)}
105
+ </div>
106
+ )}
107
+ {quantity && (
108
+ <div style={styles.badgeStyle}>
109
+ Qty: {quantity}
110
+ </div>
111
+ )}
112
+ </div>
113
+ </div>
114
+ </div>
115
+ )}
116
+ </div>
117
+ </div>
118
+ );
119
+ }
120
+
121
+ export default withToolData<AddToCartData>(AddToCartWidget);
122
+
@@ -0,0 +1,116 @@
1
+ 'use client';
2
+
3
+ import { withToolData } from 'nitrostack/widgets';
4
+ import * as styles from '../../styles/ecommerce';
5
+
6
+ interface AddressAddedData {
7
+ message: string;
8
+ addressId: string;
9
+ isDefault?: boolean;
10
+ }
11
+
12
+ function AddressAddedWidget({ data }: { data: AddressAddedData }) {
13
+ const { message, addressId, isDefault } = data;
14
+
15
+ if (!addressId) {
16
+ return (
17
+ <div style={styles.containerStyle}>
18
+ <div style={styles.emptyStateStyle}>
19
+ <p style={styles.metaTextStyle}>Loading...</p>
20
+ </div>
21
+ </div>
22
+ );
23
+ }
24
+
25
+ return (
26
+ <div style={styles.containerStyle}>
27
+ <div style={{
28
+ ...styles.cardStyle,
29
+ maxWidth: '500px',
30
+ margin: '0 auto',
31
+ }}>
32
+ <div style={{
33
+ textAlign: 'center',
34
+ marginBottom: styles.spacing.xl,
35
+ }}>
36
+ <div style={{
37
+ width: '60px',
38
+ height: '60px',
39
+ margin: '0 auto 16px',
40
+ borderRadius: styles.borderRadius.full,
41
+ background: styles.colors.success,
42
+ display: 'flex',
43
+ alignItems: 'center',
44
+ justifyContent: 'center',
45
+ fontSize: '28px',
46
+ }}>
47
+
48
+ </div>
49
+ <h2 style={{
50
+ fontSize: styles.typography.fontSize.xl,
51
+ fontWeight: styles.typography.fontWeight.bold,
52
+ color: styles.colors.gray[900],
53
+ marginBottom: styles.spacing.sm,
54
+ }}>
55
+ {message}
56
+ </h2>
57
+ </div>
58
+
59
+ <div style={{
60
+ background: styles.colors.gray[50],
61
+ borderRadius: styles.borderRadius.md,
62
+ padding: styles.spacing.lg,
63
+ marginBottom: styles.spacing.md,
64
+ }}>
65
+ <div style={{
66
+ fontSize: styles.typography.fontSize.sm,
67
+ color: styles.colors.gray[500],
68
+ marginBottom: styles.spacing.xs,
69
+ textTransform: 'uppercase',
70
+ letterSpacing: '0.5px',
71
+ fontWeight: styles.typography.fontWeight.semibold,
72
+ }}>
73
+ Address Reference
74
+ </div>
75
+ <div style={{
76
+ fontSize: styles.typography.fontSize.sm,
77
+ fontWeight: styles.typography.fontWeight.medium,
78
+ color: styles.colors.gray[900],
79
+ fontFamily: 'monospace',
80
+ wordBreak: 'break-all',
81
+ }}>
82
+ {addressId}
83
+ </div>
84
+ </div>
85
+
86
+ {isDefault && (
87
+ <div style={{
88
+ textAlign: 'center',
89
+ padding: styles.spacing.md,
90
+ background: `linear-gradient(135deg, ${styles.colors.primary} 0%, ${styles.colors.primaryHover} 100%)`,
91
+ color: styles.colors.black,
92
+ borderRadius: styles.borderRadius.md,
93
+ fontWeight: styles.typography.fontWeight.bold,
94
+ fontSize: styles.typography.fontSize.sm,
95
+ }}>
96
+ ⭐ Default Shipping Address
97
+ </div>
98
+ )}
99
+
100
+ <div style={{
101
+ marginTop: styles.spacing.md,
102
+ padding: styles.spacing.md,
103
+ background: `${styles.colors.success}15`,
104
+ borderRadius: styles.borderRadius.md,
105
+ fontSize: styles.typography.fontSize.sm,
106
+ color: styles.colors.gray[700],
107
+ textAlign: 'center',
108
+ }}>
109
+ 📦 Ready for deliveries
110
+ </div>
111
+ </div>
112
+ </div>
113
+ );
114
+ }
115
+
116
+ export default withToolData<AddressAddedData>(AddressAddedWidget);