kaddidlehopper 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/CONTEXT.md +139 -0
  2. package/README.md +47 -0
  3. package/add-ons/ai/README.md +34 -0
  4. package/add-ons/ai/assets/_dot_env.local.append +13 -0
  5. package/add-ons/ai/assets/src/components/AIAssistant.tsx +149 -0
  6. package/add-ons/ai/assets/src/lib/ai-hook.ts +21 -0
  7. package/add-ons/ai/assets/src/lib/weather-tools.ts +30 -0
  8. package/add-ons/ai/assets/src/routes/api.chat.ts +94 -0
  9. package/add-ons/ai/assets/src/routes/chat.css +175 -0
  10. package/add-ons/ai/assets/src/routes/chat.tsx +141 -0
  11. package/add-ons/ai/info.json +27 -0
  12. package/add-ons/ai/package.json +17 -0
  13. package/add-ons/ai/small-logo.svg +8 -0
  14. package/dist/cli.js +251 -0
  15. package/dist/index.js +33 -0
  16. package/dist/types/cli.d.ts +8 -0
  17. package/dist/types/index.d.ts +2 -0
  18. package/dist/types/types.d.ts +14 -0
  19. package/dist/types.js +1 -0
  20. package/examples/blog/README.md +60 -0
  21. package/examples/blog/assets/content/posts/beach.md +12 -0
  22. package/examples/blog/assets/content/posts/jungle.md.ejs +12 -0
  23. package/examples/blog/assets/content/posts/mountains.md.ejs +12 -0
  24. package/examples/blog/assets/content/posts/snorkeling.md.ejs +12 -0
  25. package/examples/blog/assets/content/posts/waterfall.md.ejs +12 -0
  26. package/examples/blog/assets/content-collections.ts +30 -0
  27. package/examples/blog/assets/public/beach.jpg +0 -0
  28. package/examples/blog/assets/public/jungle.jpg +0 -0
  29. package/examples/blog/assets/public/mountains.jpg +0 -0
  30. package/examples/blog/assets/public/snorkeling.jpg +0 -0
  31. package/examples/blog/assets/public/waterfall.jpg +0 -0
  32. package/examples/blog/assets/src/components/Header.tsx +52 -0
  33. package/examples/blog/assets/src/components/VacayAssistant.tsx +205 -0
  34. package/examples/blog/assets/src/components/blog-posts.tsx +78 -0
  35. package/examples/blog/assets/src/components/ui/card.tsx +92 -0
  36. package/examples/blog/assets/src/lib/blog-ai-hook.ts +25 -0
  37. package/examples/blog/assets/src/lib/blog-tools.ts +111 -0
  38. package/examples/blog/assets/src/lib/utils.ts +6 -0
  39. package/examples/blog/assets/src/routes/__root.tsx +57 -0
  40. package/examples/blog/assets/src/routes/api.blog-chat.ts +117 -0
  41. package/examples/blog/assets/src/routes/category.$category.tsx +19 -0
  42. package/examples/blog/assets/src/routes/index.tsx +19 -0
  43. package/examples/blog/assets/src/routes/posts.$slug.tsx +63 -0
  44. package/examples/blog/assets/src/styles.css +138 -0
  45. package/examples/blog/info.json +43 -0
  46. package/examples/blog/package.json +23 -0
  47. package/examples/events/README.md +110 -0
  48. package/examples/events/assets/content/speakers/andre-costa.md +22 -0
  49. package/examples/events/assets/content/speakers/hans-mueller.md.ejs +22 -0
  50. package/examples/events/assets/content/speakers/isabella-martinez.md.ejs +22 -0
  51. package/examples/events/assets/content/speakers/kenji-nakamura.md.ejs +22 -0
  52. package/examples/events/assets/content/speakers/marie-dubois.md.ejs +20 -0
  53. package/examples/events/assets/content/speakers/priya-sharma.md.ejs +22 -0
  54. package/examples/events/assets/content/talks/croissant-lamination-secrets.md +39 -0
  55. package/examples/events/assets/content/talks/french-macaron-mastery.md.ejs +39 -0
  56. package/examples/events/assets/content/talks/neapolitan-pizza-tradition-meets-innovation.md.ejs +39 -0
  57. package/examples/events/assets/content/talks/savory-breads-of-the-mediterranean.md.ejs +39 -0
  58. package/examples/events/assets/content/talks/sourdough-from-starter-to-masterpiece.md.ejs +36 -0
  59. package/examples/events/assets/content/talks/the-art-of-the-perfect-tart.md.ejs +32 -0
  60. package/examples/events/assets/content/talks/the-science-of-sugar.md.ejs +39 -0
  61. package/examples/events/assets/content/talks/umami-in-pastry-east-meets-west.md.ejs +39 -0
  62. package/examples/events/assets/content-collections.ts +56 -0
  63. package/examples/events/assets/public/background-1.jpg +0 -0
  64. package/examples/events/assets/public/background-2.jpg +0 -0
  65. package/examples/events/assets/public/background-3.jpg +0 -0
  66. package/examples/events/assets/public/background-4.jpg +0 -0
  67. package/examples/events/assets/public/conference-logo.png +0 -0
  68. package/examples/events/assets/public/favicon.ico +0 -0
  69. package/examples/events/assets/public/speakers/andre-costa.jpg +0 -0
  70. package/examples/events/assets/public/speakers/hans-mueller.jpg +0 -0
  71. package/examples/events/assets/public/speakers/isabella-martinez.jpg +0 -0
  72. package/examples/events/assets/public/speakers/kenji-nakamura.jpg +0 -0
  73. package/examples/events/assets/public/speakers/marie-dubois.jpg +0 -0
  74. package/examples/events/assets/public/speakers/priya-sharma.jpg +0 -0
  75. package/examples/events/assets/public/talks/croissant-lamination-secrets.jpg +0 -0
  76. package/examples/events/assets/public/talks/french-macaron-mastery.jpg +0 -0
  77. package/examples/events/assets/public/talks/neapolitan-pizza-tradition-meets-innovation.jpg +0 -0
  78. package/examples/events/assets/public/talks/savory-breads-of-the-mediterranean.jpg +0 -0
  79. package/examples/events/assets/public/talks/sourdough-from-starter-to-masterpiece.jpg +0 -0
  80. package/examples/events/assets/public/talks/the-art-of-the-perfect-tart.jpg +0 -0
  81. package/examples/events/assets/public/talks/the-science-of-sugar.jpg +0 -0
  82. package/examples/events/assets/public/talks/umami-in-pastry-east-meets-west.jpg +0 -0
  83. package/examples/events/assets/public/tanstack-circle-logo.png +0 -0
  84. package/examples/events/assets/public/tanstack-word-logo-white.svg +1 -0
  85. package/examples/events/assets/src/components/Header.tsx +59 -0
  86. package/examples/events/assets/src/components/HeaderNav.tsx +67 -0
  87. package/examples/events/assets/src/components/HeroCarousel.tsx +61 -0
  88. package/examples/events/assets/src/components/RemyAssistant.tsx +207 -0
  89. package/examples/events/assets/src/components/SpeakerCard.tsx +67 -0
  90. package/examples/events/assets/src/components/TalkCard.tsx +77 -0
  91. package/examples/events/assets/src/components/ui/card.tsx +92 -0
  92. package/examples/events/assets/src/lib/conference-ai-hook.ts +26 -0
  93. package/examples/events/assets/src/lib/conference-tools.ts +210 -0
  94. package/examples/events/assets/src/lib/model-selection.ts +1 -0
  95. package/examples/events/assets/src/lib/utils.ts +6 -0
  96. package/examples/events/assets/src/routes/__root.tsx +70 -0
  97. package/examples/events/assets/src/routes/api.remy-chat.ts +119 -0
  98. package/examples/events/assets/src/routes/index.tsx +192 -0
  99. package/examples/events/assets/src/routes/schedule.index.tsx +274 -0
  100. package/examples/events/assets/src/routes/speakers.$slug.tsx +122 -0
  101. package/examples/events/assets/src/routes/speakers.index.tsx +40 -0
  102. package/examples/events/assets/src/routes/talks.$slug.tsx +116 -0
  103. package/examples/events/assets/src/routes/talks.index.tsx +40 -0
  104. package/examples/events/assets/src/styles.css +182 -0
  105. package/examples/events/info.json +74 -0
  106. package/examples/events/package.json +23 -0
  107. package/examples/marketing/README.md +60 -0
  108. package/examples/marketing/assets/public/logo.png +0 -0
  109. package/examples/marketing/assets/public/motorcycle-adventure.jpg +0 -0
  110. package/examples/marketing/assets/public/motorcycle-cruiser.jpg +0 -0
  111. package/examples/marketing/assets/public/motorcycle-scooter.jpg +0 -0
  112. package/examples/marketing/assets/public/motorcycle-sport.jpg +0 -0
  113. package/examples/marketing/assets/public/motorcycle-supersport.jpg +0 -0
  114. package/examples/marketing/assets/src/components/Header.tsx +36 -0
  115. package/examples/marketing/assets/src/components/MotorcycleAIAssistant.tsx +162 -0
  116. package/examples/marketing/assets/src/components/MotorcycleRecommendation.tsx +53 -0
  117. package/examples/marketing/assets/src/data/motorcycles.ts.ejs +77 -0
  118. package/examples/marketing/assets/src/lib/motorcycle-ai-hook.ts +24 -0
  119. package/examples/marketing/assets/src/lib/motorcycle-tools.ts +42 -0
  120. package/examples/marketing/assets/src/routes/__root.tsx +57 -0
  121. package/examples/marketing/assets/src/routes/api.motorcycle-chat.ts +78 -0
  122. package/examples/marketing/assets/src/routes/index.tsx +72 -0
  123. package/examples/marketing/assets/src/routes/motorcycles/$motorcycleId.tsx +56 -0
  124. package/examples/marketing/assets/src/store/motorcycle-assistant.ts +3 -0
  125. package/examples/marketing/assets/src/styles.css +212 -0
  126. package/examples/marketing/info.json +38 -0
  127. package/examples/marketing/package.json +14 -0
  128. package/examples/resume/README.md +82 -0
  129. package/examples/resume/assets/content/education/code-school.md +17 -0
  130. package/examples/resume/assets/content/jobs/freelance.md.ejs +13 -0
  131. package/examples/resume/assets/content/jobs/initech-junior.md +20 -0
  132. package/examples/resume/assets/content/jobs/initech-lead.md.ejs +29 -0
  133. package/examples/resume/assets/content/jobs/initrode-senior.md.ejs +28 -0
  134. package/examples/resume/assets/content-collections.ts +36 -0
  135. package/examples/resume/assets/public/headshot-on-white.jpg +0 -0
  136. package/examples/resume/assets/src/components/Header.tsx +33 -0
  137. package/examples/resume/assets/src/components/ResumeAssistant.tsx +193 -0
  138. package/examples/resume/assets/src/components/ui/badge.tsx +46 -0
  139. package/examples/resume/assets/src/components/ui/card.tsx +92 -0
  140. package/examples/resume/assets/src/components/ui/checkbox.tsx +30 -0
  141. package/examples/resume/assets/src/components/ui/hover-card.tsx +44 -0
  142. package/examples/resume/assets/src/components/ui/separator.tsx +26 -0
  143. package/examples/resume/assets/src/lib/resume-ai-hook.ts +21 -0
  144. package/examples/resume/assets/src/lib/resume-tools.ts +165 -0
  145. package/examples/resume/assets/src/lib/utils.ts +6 -0
  146. package/examples/resume/assets/src/routes/api.resume-chat.ts +110 -0
  147. package/examples/resume/assets/src/routes/index.tsx +220 -0
  148. package/examples/resume/assets/src/styles.css +138 -0
  149. package/examples/resume/info.json +25 -0
  150. package/examples/resume/package.json +26 -0
  151. package/package.json +39 -0
  152. package/project/base/_dot_claude/skills/content-collections/SKILL.md +505 -0
  153. package/project/base/_dot_claude/skills/netlify-blobs/SKILL.md +410 -0
  154. package/project/base/_dot_claude/skills/netlify-db/SKILL.md +424 -0
  155. package/project/base/_dot_claude/skills/netlify-debugging/SKILL.md +419 -0
  156. package/project/base/_dot_claude/skills/netlify-forms/SKILL.md +243 -0
  157. package/project/base/_dot_claude/skills/netlify-functions/SKILL.md +372 -0
  158. package/project/base/_dot_claude/skills/tanstack-start-api-routes/SKILL.md +421 -0
  159. package/project/base/_dot_claude/skills/tanstack-start-loaders/SKILL.md +426 -0
  160. package/project/base/_dot_claude/skills/tanstack-start-project-setup/SKILL.md +493 -0
  161. package/project/base/_dot_claude/skills/tanstack-start-routes/SKILL.md +430 -0
  162. package/project/base/_dot_claude/skills/tanstack-start-server-functions/SKILL.md +445 -0
  163. package/project/base/_dot_claude/skills/tanstack-start-typesafe-routing/SKILL.md +494 -0
  164. package/project/base/_dot_gitignore +8 -0
  165. package/project/base/netlify.toml +7 -0
  166. package/project/base/package.json +33 -0
  167. package/project/base/public/favicon.ico +0 -0
  168. package/project/base/public/tanstack-circle-logo.png +0 -0
  169. package/project/base/public/tanstack-word-logo-white.svg +1 -0
  170. package/project/base/src/components/Header.tsx +17 -0
  171. package/project/base/src/components/HeaderNav.tsx.ejs +179 -0
  172. package/project/base/src/router.tsx +15 -0
  173. package/project/base/src/routes/__root.tsx +57 -0
  174. package/project/base/src/routes/index.tsx +48 -0
  175. package/project/base/src/styles.css +15 -0
  176. package/project/base/tsconfig.json +28 -0
  177. package/project/base/vite.config.ts.ejs +25 -0
  178. package/project/packages.json +22 -0
  179. package/scripts/check-outdated-packages.js +421 -0
  180. package/src/cli.ts +343 -0
  181. package/src/index.ts +49 -0
  182. package/src/types.ts +15 -0
  183. package/tsconfig.json +17 -0
@@ -0,0 +1,424 @@
1
+ ---
2
+ name: netlify-db
3
+ description: Use Netlify DB, a managed Postgres database powered by Neon. Use when you need relational database capabilities, SQL queries, or persistent structured data storage on Netlify.
4
+ license: Apache-2.0
5
+ metadata:
6
+ author: netlify
7
+ version: "1.0"
8
+ ---
9
+
10
+ # Netlify DB
11
+
12
+ Netlify DB provides instant Postgres database instances powered by Neon. It auto-connects to your functions with zero configuration.
13
+
14
+ ## When to Use
15
+
16
+ - Relational data storage
17
+ - Complex queries with joins
18
+ - ACID transactions
19
+ - Structured data with schemas
20
+ - SQL-based data access
21
+
22
+ ## Quick Setup
23
+
24
+ ```bash
25
+ # Initialize Netlify DB for your project
26
+ netlify db:init
27
+
28
+ # This creates:
29
+ # - A Neon Postgres database
30
+ # - DATABASE_URL environment variable
31
+ # - drizzle.config.ts (if using Drizzle)
32
+ ```
33
+
34
+ ## Using with Drizzle ORM (Recommended)
35
+
36
+ ### Installation
37
+
38
+ ```bash
39
+ npm install drizzle-orm @neondatabase/serverless
40
+ npm install -D drizzle-kit
41
+ ```
42
+
43
+ ### Schema Definition
44
+
45
+ ```typescript
46
+ // src/db/schema.ts
47
+ import { pgTable, serial, text, timestamp, integer, boolean } from "drizzle-orm/pg-core";
48
+
49
+ export const users = pgTable("users", {
50
+ id: serial("id").primaryKey(),
51
+ email: text("email").notNull().unique(),
52
+ name: text("name").notNull(),
53
+ createdAt: timestamp("created_at").defaultNow().notNull(),
54
+ });
55
+
56
+ export const posts = pgTable("posts", {
57
+ id: serial("id").primaryKey(),
58
+ title: text("title").notNull(),
59
+ content: text("content"),
60
+ published: boolean("published").default(false),
61
+ authorId: integer("author_id").references(() => users.id),
62
+ createdAt: timestamp("created_at").defaultNow().notNull(),
63
+ updatedAt: timestamp("updated_at").defaultNow().notNull(),
64
+ });
65
+ ```
66
+
67
+ ### Drizzle Configuration
68
+
69
+ ```typescript
70
+ // drizzle.config.ts
71
+ import { defineConfig } from "drizzle-kit";
72
+
73
+ export default defineConfig({
74
+ schema: "./src/db/schema.ts",
75
+ out: "./drizzle",
76
+ dialect: "postgresql",
77
+ dbCredentials: {
78
+ url: process.env.DATABASE_URL!,
79
+ },
80
+ });
81
+ ```
82
+
83
+ ### Database Client
84
+
85
+ ```typescript
86
+ // src/db/index.ts
87
+ import { drizzle } from "drizzle-orm/neon-http";
88
+ import { neon } from "@neondatabase/serverless";
89
+ import * as schema from "./schema";
90
+
91
+ const sql = neon(process.env.DATABASE_URL!);
92
+ export const db = drizzle(sql, { schema });
93
+
94
+ export { schema };
95
+ ```
96
+
97
+ ### Migrations
98
+
99
+ ```bash
100
+ # Generate migration from schema changes
101
+ npx drizzle-kit generate
102
+
103
+ # Push schema directly to database (development)
104
+ npx drizzle-kit push
105
+
106
+ # Run migrations
107
+ npx drizzle-kit migrate
108
+ ```
109
+
110
+ ## Basic Queries with Drizzle
111
+
112
+ ### Create
113
+
114
+ ```typescript
115
+ import { db, schema } from "./db";
116
+
117
+ // Insert single record
118
+ const newUser = await db.insert(schema.users).values({
119
+ email: "alice@example.com",
120
+ name: "Alice",
121
+ }).returning();
122
+
123
+ // Insert multiple records
124
+ await db.insert(schema.posts).values([
125
+ { title: "First Post", authorId: newUser[0].id },
126
+ { title: "Second Post", authorId: newUser[0].id },
127
+ ]);
128
+ ```
129
+
130
+ ### Read
131
+
132
+ ```typescript
133
+ import { db, schema } from "./db";
134
+ import { eq, and, like, desc } from "drizzle-orm";
135
+
136
+ // Get all users
137
+ const allUsers = await db.select().from(schema.users);
138
+
139
+ // Get user by ID
140
+ const user = await db
141
+ .select()
142
+ .from(schema.users)
143
+ .where(eq(schema.users.id, 1));
144
+
145
+ // Get with conditions
146
+ const publishedPosts = await db
147
+ .select()
148
+ .from(schema.posts)
149
+ .where(
150
+ and(
151
+ eq(schema.posts.published, true),
152
+ like(schema.posts.title, "%Tutorial%")
153
+ )
154
+ )
155
+ .orderBy(desc(schema.posts.createdAt))
156
+ .limit(10);
157
+
158
+ // Join tables
159
+ const postsWithAuthors = await db
160
+ .select({
161
+ postTitle: schema.posts.title,
162
+ authorName: schema.users.name,
163
+ })
164
+ .from(schema.posts)
165
+ .leftJoin(schema.users, eq(schema.posts.authorId, schema.users.id));
166
+ ```
167
+
168
+ ### Update
169
+
170
+ ```typescript
171
+ import { db, schema } from "./db";
172
+ import { eq } from "drizzle-orm";
173
+
174
+ // Update single field
175
+ await db
176
+ .update(schema.posts)
177
+ .set({ published: true })
178
+ .where(eq(schema.posts.id, 1));
179
+
180
+ // Update multiple fields
181
+ await db
182
+ .update(schema.users)
183
+ .set({
184
+ name: "Alice Smith",
185
+ updatedAt: new Date(),
186
+ })
187
+ .where(eq(schema.users.email, "alice@example.com"));
188
+ ```
189
+
190
+ ### Delete
191
+
192
+ ```typescript
193
+ import { db, schema } from "./db";
194
+ import { eq, lt } from "drizzle-orm";
195
+
196
+ // Delete single record
197
+ await db.delete(schema.posts).where(eq(schema.posts.id, 1));
198
+
199
+ // Delete with condition
200
+ await db
201
+ .delete(schema.posts)
202
+ .where(
203
+ and(
204
+ eq(schema.posts.published, false),
205
+ lt(schema.posts.createdAt, thirtyDaysAgo)
206
+ )
207
+ );
208
+ ```
209
+
210
+ ## Using in Netlify Functions
211
+
212
+ ```typescript
213
+ // netlify/functions/users.ts
214
+ import type { Context } from "@netlify/functions";
215
+ import { db, schema } from "../../src/db";
216
+ import { eq } from "drizzle-orm";
217
+
218
+ export default async (request: Request, context: Context) => {
219
+ const url = new URL(request.url);
220
+
221
+ switch (request.method) {
222
+ case "GET": {
223
+ const id = url.searchParams.get("id");
224
+
225
+ if (id) {
226
+ const user = await db
227
+ .select()
228
+ .from(schema.users)
229
+ .where(eq(schema.users.id, parseInt(id)));
230
+
231
+ if (!user.length) {
232
+ return new Response("User not found", { status: 404 });
233
+ }
234
+ return Response.json(user[0]);
235
+ }
236
+
237
+ const users = await db.select().from(schema.users);
238
+ return Response.json(users);
239
+ }
240
+
241
+ case "POST": {
242
+ const body = await request.json();
243
+ const newUser = await db
244
+ .insert(schema.users)
245
+ .values(body)
246
+ .returning();
247
+
248
+ return Response.json(newUser[0], { status: 201 });
249
+ }
250
+
251
+ case "DELETE": {
252
+ const id = url.searchParams.get("id");
253
+ if (!id) {
254
+ return new Response("ID required", { status: 400 });
255
+ }
256
+
257
+ await db.delete(schema.users).where(eq(schema.users.id, parseInt(id)));
258
+ return new Response(null, { status: 204 });
259
+ }
260
+
261
+ default:
262
+ return new Response("Method not allowed", { status: 405 });
263
+ }
264
+ };
265
+ ```
266
+
267
+ ## Using Raw SQL (Without ORM)
268
+
269
+ ```typescript
270
+ import { neon } from "@neondatabase/serverless";
271
+
272
+ const sql = neon(process.env.DATABASE_URL!);
273
+
274
+ // Simple query
275
+ const users = await sql`SELECT * FROM users`;
276
+
277
+ // Parameterized query (safe from SQL injection)
278
+ const user = await sql`
279
+ SELECT * FROM users WHERE id = ${userId}
280
+ `;
281
+
282
+ // Insert
283
+ await sql`
284
+ INSERT INTO users (email, name)
285
+ VALUES (${email}, ${name})
286
+ `;
287
+
288
+ // Transaction
289
+ const result = await sql.transaction([
290
+ sql`INSERT INTO users (email, name) VALUES (${email}, ${name}) RETURNING id`,
291
+ sql`INSERT INTO profiles (user_id, bio) VALUES (${userId}, ${bio})`,
292
+ ]);
293
+ ```
294
+
295
+ ## Transactions with Drizzle
296
+
297
+ ```typescript
298
+ import { db, schema } from "./db";
299
+
300
+ // Transaction ensures all operations succeed or none do
301
+ await db.transaction(async (tx) => {
302
+ const [user] = await tx
303
+ .insert(schema.users)
304
+ .values({ email: "bob@example.com", name: "Bob" })
305
+ .returning();
306
+
307
+ await tx.insert(schema.posts).values({
308
+ title: "Bob's First Post",
309
+ authorId: user.id,
310
+ });
311
+
312
+ // If any operation fails, all are rolled back
313
+ });
314
+ ```
315
+
316
+ ## Common Patterns
317
+
318
+ ### Soft Deletes
319
+
320
+ ```typescript
321
+ // Schema with soft delete
322
+ export const posts = pgTable("posts", {
323
+ id: serial("id").primaryKey(),
324
+ title: text("title").notNull(),
325
+ deletedAt: timestamp("deleted_at"),
326
+ });
327
+
328
+ // Query only non-deleted
329
+ const activePosts = await db
330
+ .select()
331
+ .from(schema.posts)
332
+ .where(isNull(schema.posts.deletedAt));
333
+
334
+ // Soft delete
335
+ await db
336
+ .update(schema.posts)
337
+ .set({ deletedAt: new Date() })
338
+ .where(eq(schema.posts.id, postId));
339
+ ```
340
+
341
+ ### Pagination
342
+
343
+ ```typescript
344
+ async function getPaginatedPosts(page: number, pageSize: number = 10) {
345
+ const offset = (page - 1) * pageSize;
346
+
347
+ const [posts, countResult] = await Promise.all([
348
+ db
349
+ .select()
350
+ .from(schema.posts)
351
+ .orderBy(desc(schema.posts.createdAt))
352
+ .limit(pageSize)
353
+ .offset(offset),
354
+ db
355
+ .select({ count: sql<number>`count(*)` })
356
+ .from(schema.posts),
357
+ ]);
358
+
359
+ return {
360
+ data: posts,
361
+ page,
362
+ pageSize,
363
+ total: countResult[0].count,
364
+ totalPages: Math.ceil(countResult[0].count / pageSize),
365
+ };
366
+ }
367
+ ```
368
+
369
+ ### Full-Text Search
370
+
371
+ ```typescript
372
+ // Use PostgreSQL's full-text search
373
+ const results = await sql`
374
+ SELECT * FROM posts
375
+ WHERE to_tsvector('english', title || ' ' || content)
376
+ @@ plainto_tsquery('english', ${searchQuery})
377
+ ORDER BY ts_rank(
378
+ to_tsvector('english', title || ' ' || content),
379
+ plainto_tsquery('english', ${searchQuery})
380
+ ) DESC
381
+ LIMIT 20
382
+ `;
383
+ ```
384
+
385
+ ## CLI Commands
386
+
387
+ ```bash
388
+ # Initialize database
389
+ netlify db:init
390
+
391
+ # Open database in Neon console
392
+ netlify db:open
393
+
394
+ # Run migrations
395
+ netlify db:migrate
396
+
397
+ # Pull schema from existing database
398
+ netlify db:pull
399
+
400
+ # Push schema to database
401
+ netlify db:push
402
+ ```
403
+
404
+ ## Environment Variables
405
+
406
+ - `DATABASE_URL` - Automatically set by Netlify DB
407
+ - Format: `postgres://user:password@host/database?sslmode=require`
408
+
409
+ ## Local Development
410
+
411
+ ```bash
412
+ # Run with Netlify Dev (auto-connects to database)
413
+ netlify dev
414
+
415
+ # Or set DATABASE_URL in .env for local testing
416
+ DATABASE_URL=postgres://user:pass@localhost:5432/mydb
417
+ ```
418
+
419
+ ## Limits
420
+
421
+ - Based on Neon's Postgres offering
422
+ - Connection pooling handled automatically
423
+ - Serverless-optimized (connections close between requests)
424
+ - See Neon documentation for specific limits