@odvi/create-dtt-framework 0.1.3 → 0.1.6
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.
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +16 -13
- package/dist/commands/create.js.map +1 -1
- package/package.json +3 -2
- package/template/.env.example +106 -0
- package/template/components.json +22 -0
- package/template/docs/framework/01-overview.md +289 -0
- package/template/docs/framework/02-techstack.md +503 -0
- package/template/docs/framework/api-layer.md +681 -0
- package/template/docs/framework/clerk-authentication.md +649 -0
- package/template/docs/framework/cli-installation.md +564 -0
- package/template/docs/framework/deployment/ci-cd.md +907 -0
- package/template/docs/framework/deployment/digitalocean.md +991 -0
- package/template/docs/framework/deployment/domain-setup.md +972 -0
- package/template/docs/framework/deployment/environment-variables.md +862 -0
- package/template/docs/framework/deployment/monitoring.md +927 -0
- package/template/docs/framework/deployment/production-checklist.md +649 -0
- package/template/docs/framework/deployment/vercel.md +791 -0
- package/template/docs/framework/environment-variables.md +646 -0
- package/template/docs/framework/health-check-system.md +583 -0
- package/template/docs/framework/implementation.md +559 -0
- package/template/docs/framework/snowflake-integration.md +594 -0
- package/template/docs/framework/state-management.md +615 -0
- package/template/docs/framework/supabase-integration.md +582 -0
- package/template/docs/framework/testing-guide.md +544 -0
- package/template/docs/framework/what-did-i-miss.md +526 -0
- package/template/drizzle.config.ts +11 -0
- package/template/next.config.js +21 -0
- package/template/postcss.config.js +5 -0
- package/template/prettier.config.js +4 -0
- package/template/public/favicon.ico +0 -0
- package/template/src/app/(auth)/layout.tsx +4 -0
- package/template/src/app/(auth)/sign-in/[[...sign-in]]/page.tsx +10 -0
- package/template/src/app/(auth)/sign-up/[[...sign-up]]/page.tsx +10 -0
- package/template/src/app/(dashboard)/dashboard/page.tsx +8 -0
- package/template/src/app/(dashboard)/health/page.tsx +16 -0
- package/template/src/app/(dashboard)/layout.tsx +17 -0
- package/template/src/app/api/[[...route]]/route.ts +11 -0
- package/template/src/app/api/debug-files/route.ts +33 -0
- package/template/src/app/api/webhooks/clerk/route.ts +112 -0
- package/template/src/app/layout.tsx +28 -0
- package/template/src/app/page.tsx +12 -0
- package/template/src/app/providers.tsx +20 -0
- package/template/src/components/layouts/navbar.tsx +14 -0
- package/template/src/components/shared/loading-spinner.tsx +6 -0
- package/template/src/components/ui/badge.tsx +46 -0
- package/template/src/components/ui/button.tsx +62 -0
- package/template/src/components/ui/card.tsx +92 -0
- package/template/src/components/ui/collapsible.tsx +33 -0
- package/template/src/components/ui/scroll-area.tsx +58 -0
- package/template/src/components/ui/sheet.tsx +139 -0
- package/template/src/config/__tests__/env.test.ts +164 -0
- package/template/src/config/__tests__/site.test.ts +46 -0
- package/template/src/config/env.ts +36 -0
- package/template/src/config/site.ts +10 -0
- package/template/src/env.js +44 -0
- package/template/src/features/__tests__/health-check-config.test.ts +142 -0
- package/template/src/features/__tests__/health-check-types.test.ts +201 -0
- package/template/src/features/documentation/components/doc-sidebar.tsx +109 -0
- package/template/src/features/documentation/components/doc-viewer.tsx +70 -0
- package/template/src/features/documentation/index.tsx +92 -0
- package/template/src/features/documentation/utils/doc-loader.ts +177 -0
- package/template/src/features/health-check/components/health-dashboard.tsx +374 -0
- package/template/src/features/health-check/config.ts +71 -0
- package/template/src/features/health-check/index.ts +4 -0
- package/template/src/features/health-check/stores/health-store.ts +14 -0
- package/template/src/features/health-check/types.ts +18 -0
- package/template/src/hooks/__tests__/use-debounce.test.tsx +28 -0
- package/template/src/hooks/queries/use-health-checks.ts +16 -0
- package/template/src/hooks/utils/use-debounce.ts +20 -0
- package/template/src/lib/__tests__/utils.test.ts +52 -0
- package/template/src/lib/__tests__/validators.test.ts +114 -0
- package/template/src/lib/nextbank/client.ts +67 -0
- package/template/src/lib/snowflake/client.ts +102 -0
- package/template/src/lib/supabase/admin.ts +7 -0
- package/template/src/lib/supabase/client.ts +7 -0
- package/template/src/lib/supabase/server.ts +23 -0
- package/template/src/lib/utils.ts +6 -0
- package/template/src/lib/validators.ts +9 -0
- package/template/src/middleware.ts +22 -0
- package/template/src/server/api/index.ts +22 -0
- package/template/src/server/api/middleware/auth.ts +19 -0
- package/template/src/server/api/middleware/logger.ts +4 -0
- package/template/src/server/api/routes/health/clerk.ts +214 -0
- package/template/src/server/api/routes/health/database.ts +141 -0
- package/template/src/server/api/routes/health/edge-functions.ts +107 -0
- package/template/src/server/api/routes/health/framework.ts +48 -0
- package/template/src/server/api/routes/health/index.ts +102 -0
- package/template/src/server/api/routes/health/nextbank.ts +46 -0
- package/template/src/server/api/routes/health/snowflake.ts +83 -0
- package/template/src/server/api/routes/health/storage.ts +177 -0
- package/template/src/server/api/routes/users.ts +79 -0
- package/template/src/server/db/index.ts +17 -0
- package/template/src/server/db/queries/users.ts +8 -0
- package/template/src/server/db/schema/__tests__/health-checks.test.ts +31 -0
- package/template/src/server/db/schema/__tests__/users.test.ts +46 -0
- package/template/src/server/db/schema/health-checks.ts +11 -0
- package/template/src/server/db/schema/index.ts +2 -0
- package/template/src/server/db/schema/users.ts +16 -0
- package/template/src/server/db/schema.ts +1 -0
- package/template/src/stores/__tests__/ui-store.test.ts +87 -0
- package/template/src/stores/ui-store.ts +14 -0
- package/template/src/styles/globals.css +129 -0
- package/template/src/test/mocks/clerk.ts +35 -0
- package/template/src/test/mocks/snowflake.ts +28 -0
- package/template/src/test/mocks/supabase.ts +37 -0
- package/template/src/test/setup.ts +69 -0
- package/template/src/test/utils/test-helpers.ts +158 -0
- package/template/src/types/index.ts +14 -0
- package/template/tsconfig.json +43 -0
- package/template/vitest.config.ts +44 -0
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
# DTT Framework - Snowflake Integration
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The DTT Framework includes integration with [Snowflake](https://www.snowflake.com/), a cloud-based data warehousing platform. Snowflake is used for analytics, reporting, and business intelligence workloads.
|
|
6
|
+
|
|
7
|
+
### Why Snowflake?
|
|
8
|
+
|
|
9
|
+
- **Cloud-Native**: Fully managed data warehouse
|
|
10
|
+
- **Separation of Compute and Storage**: Scale independently
|
|
11
|
+
- **Performance**: Fast query execution with automatic optimization
|
|
12
|
+
- **SQL Support**: Standard SQL with extensions
|
|
13
|
+
- **Data Sharing**: Secure data sharing with other Snowflake accounts
|
|
14
|
+
- **Ecosystem**: Rich partner ecosystem and integrations
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Snowflake Connection Setup
|
|
19
|
+
|
|
20
|
+
### 1. Create a Snowflake Account
|
|
21
|
+
|
|
22
|
+
1. Go to [snowflake.com](https://www.snowflake.com/) and sign up
|
|
23
|
+
2. Create a new account or use an existing one
|
|
24
|
+
3. Note down your account identifier (e.g., `xy12345.us-east-1`)
|
|
25
|
+
|
|
26
|
+
### 2. Install Dependencies
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pnpm add snowflake-sdk
|
|
30
|
+
pnpm add -D @types/snowflake-sdk
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 3. Configure Environment Variables
|
|
34
|
+
|
|
35
|
+
Add the following to your [`.env`](./environment-variables.md) file:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Snowflake
|
|
39
|
+
SNOWFLAKE_ACCOUNT=ef19411.ap-southeast-1
|
|
40
|
+
SNOWFLAKE_AUTHENTICATOR=SNOWFLAKE_JWT
|
|
41
|
+
SNOWFLAKE_USERNAME=APP_USER_WITH_KEY_AUTH
|
|
42
|
+
SNOWFLAKE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
|
|
43
|
+
-----END PRIVATE KEY-----"
|
|
44
|
+
SNOWFLAKE_PRIVATE_KEY_PASSPHRASE="<password>"
|
|
45
|
+
SNOWFLAKE_WAREHOUSE=COMPUTE_WH
|
|
46
|
+
SNOWFLAKE_ROLE=ACCOUNTADMIN
|
|
47
|
+
SNOWFLAKE_LOGGING=true
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Where to find these values:**
|
|
51
|
+
|
|
52
|
+
- **Account**: Snowflake URL (e.g., `ef19411.ap-southeast-1` from `https://ef19411.ap-southeast-1.snowflakecomputing.com`)
|
|
53
|
+
- **Authenticator**: `SNOWFLAKE_JWT` for Key Pair Authentication
|
|
54
|
+
- **Username**: Your Snowflake username (must be configured with public key)
|
|
55
|
+
- **Private Key**: Your private key in PEM format
|
|
56
|
+
- **Passphrase**: Password for your private key (if encrypted)
|
|
57
|
+
- **Warehouse**: Compute warehouse name
|
|
58
|
+
- **Role**: Role with appropriate permissions (e.g., `ACCOUNTADMIN` or custom role)
|
|
59
|
+
- **Logging**: Enable verbose logging (optional)
|
|
60
|
+
|
|
61
|
+
### 4. Create Snowflake Client
|
|
62
|
+
|
|
63
|
+
Create [`src/lib/snowflake/client.ts`](../../src/lib/snowflake/client.ts):
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import snowflake from 'snowflake-sdk'
|
|
67
|
+
import type { Bind } from 'snowflake-sdk'
|
|
68
|
+
import { env } from '@/config/env'
|
|
69
|
+
|
|
70
|
+
const config = {
|
|
71
|
+
account: env.SNOWFLAKE_ACCOUNT,
|
|
72
|
+
username: env.SNOWFLAKE_USERNAME,
|
|
73
|
+
authenticator: env.SNOWFLAKE_AUTHENTICATOR,
|
|
74
|
+
privateKey: env.SNOWFLAKE_PRIVATE_KEY,
|
|
75
|
+
privateKeyPass: env.SNOWFLAKE_PRIVATE_KEY_PASSPHRASE,
|
|
76
|
+
warehouse: env.SNOWFLAKE_WAREHOUSE,
|
|
77
|
+
role: env.SNOWFLAKE_ROLE,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function createSnowflakeConnection() {
|
|
81
|
+
return snowflake.createConnection(config)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function connectSnowflake(): Promise<snowflake.Connection> {
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
const connection = createSnowflakeConnection()
|
|
87
|
+
connection.connect((err, conn) => {
|
|
88
|
+
if (err) reject(err)
|
|
89
|
+
else resolve(conn)
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function executeQuery<T = unknown>(
|
|
95
|
+
connection: snowflake.Connection,
|
|
96
|
+
sqlText: string,
|
|
97
|
+
binds?: Bind[]
|
|
98
|
+
): Promise<T[]> {
|
|
99
|
+
return new Promise((resolve, reject) => {
|
|
100
|
+
connection.execute({
|
|
101
|
+
sqlText,
|
|
102
|
+
binds: binds as any,
|
|
103
|
+
complete: (err, stmt, rows) => {
|
|
104
|
+
if (err) reject(err)
|
|
105
|
+
else resolve((rows || []) as T[])
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function destroyConnection(connection: snowflake.Connection): Promise<void> {
|
|
112
|
+
return new Promise((resolve, reject) => {
|
|
113
|
+
connection.destroy((err) => {
|
|
114
|
+
if (err) reject(err)
|
|
115
|
+
else resolve()
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Query Execution Patterns
|
|
124
|
+
|
|
125
|
+
### Basic Query Execution
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { connectSnowflake, executeQuery, destroyConnection } from '@/lib/snowflake/client'
|
|
129
|
+
|
|
130
|
+
async function runQuery() {
|
|
131
|
+
const connection = await connectSnowflake()
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const results = await executeQuery<{ NAME: string; VALUE: number }>(
|
|
135
|
+
connection,
|
|
136
|
+
'SELECT NAME, VALUE FROM MY_TABLE LIMIT 10'
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
console.log(results)
|
|
140
|
+
return results
|
|
141
|
+
} finally {
|
|
142
|
+
await destroyConnection(connection)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Parameterized Queries
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
async function runParameterizedQuery(userId: string) {
|
|
151
|
+
const connection = await connectSnowflake()
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
const results = await executeQuery<{ ID: string; NAME: string }>(
|
|
155
|
+
connection,
|
|
156
|
+
'SELECT ID, NAME FROM USERS WHERE ID = ?',
|
|
157
|
+
[userId]
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
return results
|
|
161
|
+
} finally {
|
|
162
|
+
await destroyConnection(connection)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Multiple Queries in Sequence
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
async function runMultipleQueries() {
|
|
171
|
+
const connection = await connectSnowflake()
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
const users = await executeQuery(connection, 'SELECT * FROM USERS LIMIT 10')
|
|
175
|
+
const products = await executeQuery(connection, 'SELECT * FROM PRODUCTS LIMIT 10')
|
|
176
|
+
const orders = await executeQuery(connection, 'SELECT * FROM ORDERS LIMIT 10')
|
|
177
|
+
|
|
178
|
+
return { users, products, orders }
|
|
179
|
+
} finally {
|
|
180
|
+
await destroyConnection(connection)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Warehouse Configuration
|
|
188
|
+
|
|
189
|
+
### Snowflake Warehouse Concepts
|
|
190
|
+
|
|
191
|
+
A warehouse in Snowflake is a compute resource that executes queries:
|
|
192
|
+
|
|
193
|
+
| Warehouse Type | Description | Use Case |
|
|
194
|
+
|----------------|-------------|----------|
|
|
195
|
+
| **X-Small** | 1 credit/hour | Testing, development |
|
|
196
|
+
| **Small** | 2 credits/hour | Light analytics |
|
|
197
|
+
| **Medium** | 4 credits/hour | Standard analytics |
|
|
198
|
+
| **Large** | 8 credits/hour | Heavy analytics |
|
|
199
|
+
| **X-Large** | 16 credits/hour | Large datasets |
|
|
200
|
+
| **2X-Large** | 32 credits/hour | Very large datasets |
|
|
201
|
+
| **3X-Large** | 64 credits/hour | Enterprise workloads |
|
|
202
|
+
| **4X-Large** | 128 credits/hour | Maximum performance |
|
|
203
|
+
|
|
204
|
+
### Creating a Warehouse
|
|
205
|
+
|
|
206
|
+
```sql
|
|
207
|
+
CREATE WAREHOUSE COMPUTE_WH
|
|
208
|
+
WAREHOUSE_SIZE = 'X-SMALL'
|
|
209
|
+
AUTO_SUSPEND = 300
|
|
210
|
+
AUTO_RESUME = TRUE
|
|
211
|
+
MIN_CLUSTER_COUNT = 1
|
|
212
|
+
MAX_CLUSTER_COUNT = 1
|
|
213
|
+
SCALING_POLICY = 'STANDARD';
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Warehouse Best Practices
|
|
217
|
+
|
|
218
|
+
1. **Auto-Suspend**: Enable auto-suspend to save credits when not in use
|
|
219
|
+
2. **Auto-Resume**: Enable auto-resume for instant availability
|
|
220
|
+
3. **Right-Size**: Choose the appropriate size for your workload
|
|
221
|
+
4. **Multi-Cluster**: Use multi-cluster warehouses for high concurrency
|
|
222
|
+
5. **Monitor Usage**: Regularly review warehouse usage and optimize
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Best Practices
|
|
227
|
+
|
|
228
|
+
### Connection Management
|
|
229
|
+
|
|
230
|
+
**Always close connections:**
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
// ✅ Good - Always close connection
|
|
234
|
+
async function goodPattern() {
|
|
235
|
+
const connection = await connectSnowflake()
|
|
236
|
+
try {
|
|
237
|
+
return await executeQuery(connection, 'SELECT * FROM TABLE')
|
|
238
|
+
} finally {
|
|
239
|
+
await destroyConnection(connection)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ❌ Bad - Connection not closed
|
|
244
|
+
async function badPattern() {
|
|
245
|
+
const connection = await connectSnowflake()
|
|
246
|
+
return await executeQuery(connection, 'SELECT * FROM TABLE')
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**Use connection pooling for high-frequency queries:**
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// For high-frequency queries, consider implementing connection pooling
|
|
254
|
+
// Snowflake SDK doesn't have built-in pooling, but you can implement it
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Query Optimization
|
|
258
|
+
|
|
259
|
+
**Use LIMIT for large datasets:**
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
// ✅ Good - Limit results
|
|
263
|
+
const results = await executeQuery(connection, 'SELECT * FROM LARGE_TABLE LIMIT 1000')
|
|
264
|
+
|
|
265
|
+
// ❌ Bad - Could return millions of rows
|
|
266
|
+
const results = await executeQuery(connection, 'SELECT * FROM LARGE_TABLE')
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Select only needed columns:**
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
// ✅ Good - Select only needed columns
|
|
273
|
+
const results = await executeQuery(
|
|
274
|
+
connection,
|
|
275
|
+
'SELECT ID, NAME FROM USERS'
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
// ❌ Bad - Select all columns
|
|
279
|
+
const results = await executeQuery(
|
|
280
|
+
connection,
|
|
281
|
+
'SELECT * FROM USERS'
|
|
282
|
+
)
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**Use WHERE clauses to filter data:**
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
// ✅ Good - Filter data
|
|
289
|
+
const results = await executeQuery(
|
|
290
|
+
connection,
|
|
291
|
+
'SELECT * FROM ORDERS WHERE CREATED_AT >= DATEADD(day, -30, CURRENT_DATE())'
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
// ❌ Bad - No filtering
|
|
295
|
+
const results = await executeQuery(
|
|
296
|
+
connection,
|
|
297
|
+
'SELECT * FROM ORDERS'
|
|
298
|
+
)
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Error Handling
|
|
302
|
+
|
|
303
|
+
**Handle connection errors:**
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
async function safeQuery() {
|
|
307
|
+
try {
|
|
308
|
+
const connection = await connectSnowflake()
|
|
309
|
+
try {
|
|
310
|
+
return await executeQuery(connection, 'SELECT * FROM TABLE')
|
|
311
|
+
} finally {
|
|
312
|
+
await destroyConnection(connection)
|
|
313
|
+
}
|
|
314
|
+
} catch (error) {
|
|
315
|
+
console.error('Snowflake connection error:', error)
|
|
316
|
+
throw new Error('Failed to execute Snowflake query')
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**Handle query errors:**
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
async function safeQueryWithHandling() {
|
|
325
|
+
const connection = await connectSnowflake()
|
|
326
|
+
|
|
327
|
+
try {
|
|
328
|
+
const results = await executeQuery(connection, 'SELECT * FROM NON_EXISTENT_TABLE')
|
|
329
|
+
return results
|
|
330
|
+
} catch (error: any) {
|
|
331
|
+
if (error.code === '002003') {
|
|
332
|
+
// Table does not exist
|
|
333
|
+
console.error('Table not found:', error.message)
|
|
334
|
+
} else {
|
|
335
|
+
console.error('Query error:', error.message)
|
|
336
|
+
}
|
|
337
|
+
throw error
|
|
338
|
+
} finally {
|
|
339
|
+
await destroyConnection(connection)
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Security
|
|
345
|
+
|
|
346
|
+
**Never hardcode credentials:**
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
// ❌ Bad - Hardcoded credentials
|
|
350
|
+
const connection = snowflake.createConnection({
|
|
351
|
+
account: 'xy12345.us-east-1',
|
|
352
|
+
username: 'myuser',
|
|
353
|
+
password: 'mypassword', // Never do this!
|
|
354
|
+
// ...
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
// ✅ Good - Use environment variables
|
|
358
|
+
const connection = snowflake.createConnection({
|
|
359
|
+
account: env.SNOWFLAKE_ACCOUNT,
|
|
360
|
+
username: env.SNOWFLAKE_USERNAME,
|
|
361
|
+
password: env.SNOWFLAKE_PASSWORD,
|
|
362
|
+
// ...
|
|
363
|
+
})
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**Use least privilege principle:**
|
|
367
|
+
|
|
368
|
+
- Create roles with minimal required permissions
|
|
369
|
+
- Grant only necessary privileges to roles
|
|
370
|
+
- Regularly audit role permissions
|
|
371
|
+
|
|
372
|
+
**Encrypt sensitive data:**
|
|
373
|
+
|
|
374
|
+
- Use Snowflake's built-in encryption
|
|
375
|
+
- Encrypt data at rest and in transit
|
|
376
|
+
- Use secure key management
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## Health Check Endpoints
|
|
381
|
+
|
|
382
|
+
The framework includes health check endpoints for verifying Snowflake integration:
|
|
383
|
+
|
|
384
|
+
### Snowflake Health Checks
|
|
385
|
+
|
|
386
|
+
| Endpoint | Method | Description |
|
|
387
|
+
|----------|--------|-------------|
|
|
388
|
+
| `/api/health/snowflake/connect` | GET | Test Snowflake connection |
|
|
389
|
+
| `/api/health/snowflake/query` | GET | Execute test query |
|
|
390
|
+
|
|
391
|
+
### Connection Check
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
// GET /api/health/snowflake/connect
|
|
395
|
+
{
|
|
396
|
+
"status": "healthy",
|
|
397
|
+
"responseTimeMs": 234,
|
|
398
|
+
"message": "Successfully connected to Snowflake"
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### Query Check
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
// GET /api/health/snowflake/query
|
|
406
|
+
{
|
|
407
|
+
"status": "healthy",
|
|
408
|
+
"responseTimeMs": 312,
|
|
409
|
+
"message": "Query executed successfully",
|
|
410
|
+
"data": {
|
|
411
|
+
"timestamp": "2025-12-29 07:50:00.000"
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## Advanced Patterns
|
|
419
|
+
|
|
420
|
+
### Streaming Large Results
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
async function streamLargeResults() {
|
|
424
|
+
const connection = await connectSnowflake()
|
|
425
|
+
|
|
426
|
+
try {
|
|
427
|
+
const statement = connection.execute({
|
|
428
|
+
sqlText: 'SELECT * FROM LARGE_TABLE',
|
|
429
|
+
streamResult: true,
|
|
430
|
+
complete: (err, stmt, rows) => {
|
|
431
|
+
if (err) {
|
|
432
|
+
console.error('Error:', err)
|
|
433
|
+
return
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Process rows in batches
|
|
437
|
+
rows?.forEach((row) => {
|
|
438
|
+
// Process each row
|
|
439
|
+
console.log(row)
|
|
440
|
+
})
|
|
441
|
+
},
|
|
442
|
+
})
|
|
443
|
+
} finally {
|
|
444
|
+
await destroyConnection(connection)
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### Transaction Management
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
async function transactionExample() {
|
|
453
|
+
const connection = await connectSnowflake()
|
|
454
|
+
|
|
455
|
+
try {
|
|
456
|
+
// Start transaction
|
|
457
|
+
await executeQuery(connection, 'BEGIN')
|
|
458
|
+
|
|
459
|
+
try {
|
|
460
|
+
// Execute multiple statements
|
|
461
|
+
await executeQuery(connection, 'INSERT INTO TABLE1 VALUES (1, 2, 3)')
|
|
462
|
+
await executeQuery(connection, 'UPDATE TABLE2 SET VALUE = 10 WHERE ID = 1')
|
|
463
|
+
|
|
464
|
+
// Commit transaction
|
|
465
|
+
await executeQuery(connection, 'COMMIT')
|
|
466
|
+
} catch (error) {
|
|
467
|
+
// Rollback on error
|
|
468
|
+
await executeQuery(connection, 'ROLLBACK')
|
|
469
|
+
throw error
|
|
470
|
+
}
|
|
471
|
+
} finally {
|
|
472
|
+
await destroyConnection(connection)
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### Batch Operations
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
async function batchInsert() {
|
|
481
|
+
const connection = await connectSnowflake()
|
|
482
|
+
|
|
483
|
+
try {
|
|
484
|
+
const values = [
|
|
485
|
+
[1, 'Alice', 25],
|
|
486
|
+
[2, 'Bob', 30],
|
|
487
|
+
[3, 'Charlie', 35],
|
|
488
|
+
]
|
|
489
|
+
|
|
490
|
+
// Build multi-row INSERT
|
|
491
|
+
const valuesStr = values.map(v => `(${v.join(', ')})`).join(', ')
|
|
492
|
+
const sql = `INSERT INTO USERS (ID, NAME, AGE) VALUES ${valuesStr}`
|
|
493
|
+
|
|
494
|
+
await executeQuery(connection, sql)
|
|
495
|
+
} finally {
|
|
496
|
+
await destroyConnection(connection)
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
## Snowflake SQL Tips
|
|
504
|
+
|
|
505
|
+
### Common Snowflake Functions
|
|
506
|
+
|
|
507
|
+
**Date Functions:**
|
|
508
|
+
|
|
509
|
+
```sql
|
|
510
|
+
-- Current date/time
|
|
511
|
+
SELECT CURRENT_DATE();
|
|
512
|
+
SELECT CURRENT_TIMESTAMP();
|
|
513
|
+
|
|
514
|
+
-- Date arithmetic
|
|
515
|
+
SELECT DATEADD(day, -7, CURRENT_DATE());
|
|
516
|
+
SELECT DATEDIFF(day, '2025-01-01', CURRENT_DATE());
|
|
517
|
+
|
|
518
|
+
-- Date truncation
|
|
519
|
+
SELECT DATE_TRUNC('month', CURRENT_DATE());
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
**String Functions:**
|
|
523
|
+
|
|
524
|
+
```sql
|
|
525
|
+
-- String manipulation
|
|
526
|
+
SELECT UPPER('hello');
|
|
527
|
+
SELECT LOWER('HELLO');
|
|
528
|
+
SELECT TRIM(' hello ');
|
|
529
|
+
SELECT CONCAT('Hello', ' ', 'World');
|
|
530
|
+
|
|
531
|
+
-- String matching
|
|
532
|
+
SELECT * FROM TABLE WHERE NAME LIKE '%test%';
|
|
533
|
+
SELECT REGEXP_SUBSTR('hello-world', '[^-]+');
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
**Aggregate Functions:**
|
|
537
|
+
|
|
538
|
+
```sql
|
|
539
|
+
-- Basic aggregates
|
|
540
|
+
SELECT COUNT(*), SUM(VALUE), AVG(VALUE), MIN(VALUE), MAX(VALUE) FROM TABLE;
|
|
541
|
+
|
|
542
|
+
-- Group by
|
|
543
|
+
SELECT CATEGORY, COUNT(*), AVG(VALUE) FROM TABLE GROUP BY CATEGORY;
|
|
544
|
+
|
|
545
|
+
-- Window functions
|
|
546
|
+
SELECT
|
|
547
|
+
NAME,
|
|
548
|
+
VALUE,
|
|
549
|
+
AVG(VALUE) OVER (PARTITION BY CATEGORY) AS CATEGORY_AVG
|
|
550
|
+
FROM TABLE;
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
---
|
|
554
|
+
|
|
555
|
+
## Troubleshooting
|
|
556
|
+
|
|
557
|
+
### Common Issues
|
|
558
|
+
|
|
559
|
+
**Issue: Connection timeout**
|
|
560
|
+
|
|
561
|
+
- Verify account identifier is correct
|
|
562
|
+
- Check network connectivity
|
|
563
|
+
- Verify firewall allows Snowflake traffic
|
|
564
|
+
- Check if warehouse is suspended
|
|
565
|
+
|
|
566
|
+
**Issue: Authentication failed**
|
|
567
|
+
|
|
568
|
+
- Verify username and password are correct
|
|
569
|
+
- Check if account is locked
|
|
570
|
+
- Verify role has necessary permissions
|
|
571
|
+
- Check if password has expired
|
|
572
|
+
|
|
573
|
+
**Issue: Query timeout**
|
|
574
|
+
|
|
575
|
+
- Increase warehouse size
|
|
576
|
+
- Optimize query (add WHERE clause, use LIMIT)
|
|
577
|
+
- Check if warehouse is auto-suspended
|
|
578
|
+
- Verify query is not blocked by a lock
|
|
579
|
+
|
|
580
|
+
**Issue: "Object does not exist"**
|
|
581
|
+
|
|
582
|
+
- Verify database, schema, and table names are correct
|
|
583
|
+
- Check if you're using the correct role
|
|
584
|
+
- Verify you have access to the object
|
|
585
|
+
- Check if object exists in the correct schema
|
|
586
|
+
|
|
587
|
+
---
|
|
588
|
+
|
|
589
|
+
## Related Documentation
|
|
590
|
+
|
|
591
|
+
- [Environment Variables](./environment-variables.md) - Snowflake environment variables
|
|
592
|
+
- [API Layer](./api-layer.md) - Snowflake API routes
|
|
593
|
+
- [Health Check System](./health-check-system.md) - Health check endpoints
|
|
594
|
+
- [Supabase Integration](./supabase-integration.md) - Primary database setup
|