m14i-blogging 0.3.0 → 0.4.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.
@@ -325,6 +325,158 @@ function createBlogClient(supabase, config = {}) {
325
325
  postCount
326
326
  })).sort((a, b) => b.postCount - a.postCount);
327
327
  }
328
+ },
329
+ /**
330
+ * Category management operations (v0.4.0+)
331
+ */
332
+ categories: {
333
+ /**
334
+ * List all categories
335
+ */
336
+ async list() {
337
+ const { data, error } = await supabase.from("blog.categories").select("*").order("display_order", { ascending: true });
338
+ if (error) {
339
+ throw new Error(`Failed to fetch categories: ${error.message}`);
340
+ }
341
+ return data || [];
342
+ },
343
+ /**
344
+ * List categories with post counts
345
+ */
346
+ async listWithCounts() {
347
+ const { data, error } = await supabase.rpc("blog.get_categories_with_counts");
348
+ if (error) {
349
+ throw new Error(`Failed to fetch categories with counts: ${error.message}`);
350
+ }
351
+ return data || [];
352
+ },
353
+ /**
354
+ * Get category by ID
355
+ */
356
+ async getById(id) {
357
+ const { data, error } = await supabase.from("blog.categories").select("*").eq("id", id).single();
358
+ if (error) {
359
+ if (error.code === "PGRST116") return null;
360
+ throw new Error(`Failed to fetch category: ${error.message}`);
361
+ }
362
+ return data;
363
+ },
364
+ /**
365
+ * Get category by slug
366
+ */
367
+ async getBySlug(slug) {
368
+ const { data, error } = await supabase.from("blog.categories").select("*").eq("slug", slug).single();
369
+ if (error) {
370
+ if (error.code === "PGRST116") return null;
371
+ throw new Error(`Failed to fetch category: ${error.message}`);
372
+ }
373
+ return data;
374
+ },
375
+ /**
376
+ * Create a new category
377
+ */
378
+ async create(category) {
379
+ const { data, error } = await supabase.from("blog.categories").insert(category).select().single();
380
+ if (error) {
381
+ throw new Error(`Failed to create category: ${error.message}`);
382
+ }
383
+ return data;
384
+ },
385
+ /**
386
+ * Update an existing category
387
+ */
388
+ async update(id, updates) {
389
+ const { data, error } = await supabase.from("blog.categories").update(updates).eq("id", id).select().single();
390
+ if (error) {
391
+ throw new Error(`Failed to update category: ${error.message}`);
392
+ }
393
+ return data;
394
+ },
395
+ /**
396
+ * Delete a category
397
+ */
398
+ async delete(id) {
399
+ const { error } = await supabase.from("blog.categories").delete().eq("id", id);
400
+ if (error) {
401
+ throw new Error(`Failed to delete category: ${error.message}`);
402
+ }
403
+ }
404
+ },
405
+ /**
406
+ * Tag management operations (v0.4.0+)
407
+ */
408
+ tags: {
409
+ /**
410
+ * List all tags
411
+ */
412
+ async list() {
413
+ const { data, error } = await supabase.from("blog.tags").select("*").order("name", { ascending: true });
414
+ if (error) {
415
+ throw new Error(`Failed to fetch tags: ${error.message}`);
416
+ }
417
+ return data || [];
418
+ },
419
+ /**
420
+ * List tags with post counts
421
+ */
422
+ async listWithCounts() {
423
+ const { data, error } = await supabase.rpc("blog.get_tags_with_counts");
424
+ if (error) {
425
+ throw new Error(`Failed to fetch tags with counts: ${error.message}`);
426
+ }
427
+ return data || [];
428
+ },
429
+ /**
430
+ * Get tag by ID
431
+ */
432
+ async getById(id) {
433
+ const { data, error } = await supabase.from("blog.tags").select("*").eq("id", id).single();
434
+ if (error) {
435
+ if (error.code === "PGRST116") return null;
436
+ throw new Error(`Failed to fetch tag: ${error.message}`);
437
+ }
438
+ return data;
439
+ },
440
+ /**
441
+ * Get tag by slug
442
+ */
443
+ async getBySlug(slug) {
444
+ const { data, error } = await supabase.from("blog.tags").select("*").eq("slug", slug).single();
445
+ if (error) {
446
+ if (error.code === "PGRST116") return null;
447
+ throw new Error(`Failed to fetch tag: ${error.message}`);
448
+ }
449
+ return data;
450
+ },
451
+ /**
452
+ * Create a new tag
453
+ */
454
+ async create(tag) {
455
+ const { data, error } = await supabase.from("blog.tags").insert(tag).select().single();
456
+ if (error) {
457
+ throw new Error(`Failed to create tag: ${error.message}`);
458
+ }
459
+ return data;
460
+ },
461
+ /**
462
+ * Update an existing tag
463
+ */
464
+ async update(id, updates) {
465
+ const { data, error } = await supabase.from("blog.tags").update(updates).eq("id", id).select().single();
466
+ if (error) {
467
+ throw new Error(`Failed to update tag: ${error.message}`);
468
+ }
469
+ return data;
470
+ },
471
+ /**
472
+ * Delete a tag
473
+ */
474
+ async delete(id) {
475
+ const { error } = await supabase.from("blog.tags").delete().eq("id", id);
476
+ if (error) {
477
+ throw new Error(`Failed to delete tag: ${error.message}`);
478
+ }
479
+ }
328
480
  }
329
481
  };
330
482
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/client/index.ts","../../src/client/supabase.ts"],"sourcesContent":["/**\n * Client exports for m14i-blogging\n *\n * Pre-built data access layer for Supabase integration\n *\n * @example\n * ```typescript\n * import { createBlogClient } from 'm14i-blogging/client';\n * import { createClient } from '@supabase/supabase-js';\n *\n * const supabase = createClient(url, key);\n * const blog = createBlogClient(supabase);\n *\n * // Use the client\n * const { posts } = await blog.posts.list({ status: 'published' });\n * ```\n */\n\nexport { createBlogClient } from './supabase';\nexport type { BlogClient, BlogClientConfig, SupabaseClient } from './supabase';\n","/**\n * Supabase Data Access Layer for m14i-blogging\n *\n * Provides pre-built CRUD operations for blog posts and media.\n * Works with the blog_posts and blog_media tables.\n *\n * @example\n * ```typescript\n * import { createBlogClient } from 'm14i-blogging/client';\n * import { createClient } from '@supabase/supabase-js';\n *\n * const supabase = createClient(url, key);\n * const blog = createBlogClient(supabase);\n *\n * // Use it\n * const posts = await blog.posts.list({ status: 'published' });\n * const post = await blog.posts.getBySlug('my-post');\n * ```\n */\n\nimport type {\n BlogPostRow,\n BlogPostInsert,\n BlogPostUpdate,\n BlogPostWithAuthor,\n BlogFilterParams,\n BlogPostListResponse,\n BlogMediaRow,\n BlogMediaInsert,\n BlogMediaUpdate,\n BlogStats,\n BlogCategory,\n BlogTag,\n} from \"../types/database\";\n\n// ============================================================================\n// Supabase Client Type\n// ============================================================================\n\nexport interface SupabaseClient {\n from(table: string): {\n select(query: string, options?: { count?: string }): any;\n insert(data: any): any;\n update(data: any): any;\n delete(): any;\n upsert(data: any): any;\n };\n rpc(functionName: string, params?: any): any;\n}\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nexport interface BlogClientConfig {\n /**\n * Table name for blog posts (default: \"blog_posts\")\n */\n postsTable?: string;\n /**\n * Table name for blog media (default: \"blog_media\")\n */\n mediaTable?: string;\n /**\n * Enable/disable author joins (default: true)\n */\n includeAuthor?: boolean;\n /**\n * Users table name for author joins (default: \"users\")\n */\n usersTable?: string;\n}\n\nconst DEFAULT_CONFIG: Required<BlogClientConfig> = {\n postsTable: \"blog_posts\",\n mediaTable: \"blog_media\",\n includeAuthor: true,\n usersTable: \"users\",\n};\n\n// ============================================================================\n// Blog Client Factory\n// ============================================================================\n\n/**\n * Creates a blog client with pre-built CRUD operations\n */\nexport function createBlogClient(\n supabase: SupabaseClient,\n config: BlogClientConfig = {}\n) {\n const cfg = { ...DEFAULT_CONFIG, ...config };\n\n return {\n /**\n * Blog post operations\n */\n posts: {\n /**\n * List blog posts with filtering and pagination\n */\n async list(\n params: BlogFilterParams = {}\n ): Promise<BlogPostListResponse> {\n const {\n page = 1,\n pageSize = 10,\n status,\n category,\n tag,\n search,\n orderBy = \"created_at\",\n orderDirection = \"desc\",\n } = params;\n\n const from = (page - 1) * pageSize;\n const to = from + pageSize - 1;\n\n let query = supabase\n .from(cfg.postsTable)\n .select(\n cfg.includeAuthor\n ? `\n *,\n author:${cfg.usersTable}!created_by(full_name, email, avatar_url)\n `\n : \"*\",\n { count: \"exact\" }\n )\n .range(from, to)\n .order(orderBy, { ascending: orderDirection === \"asc\" });\n\n if (status) {\n query = query.eq(\"status\", status);\n }\n\n if (category) {\n query = query.eq(\"category\", category);\n }\n\n if (tag) {\n query = query.contains(\"tags\", [tag]);\n }\n\n if (search) {\n query = query.or(\n `title.ilike.%${search}%,excerpt.ilike.%${search}%,category.ilike.%${search}%`\n );\n }\n\n const { data, error, count } = await query;\n\n if (error) {\n throw new Error(`Failed to fetch blog posts: ${error.message}`);\n }\n\n return {\n posts: (data as unknown as BlogPostWithAuthor[]) || [],\n total: count || 0,\n page,\n pageSize,\n totalPages: Math.ceil((count || 0) / pageSize),\n };\n },\n\n /**\n * Get a single post by slug\n */\n async getBySlug(slug: string): Promise<BlogPostWithAuthor | null> {\n const { data, error } = await supabase\n .from(cfg.postsTable)\n .select(\n cfg.includeAuthor\n ? `\n *,\n author:${cfg.usersTable}!created_by(full_name, email, avatar_url)\n `\n : \"*\"\n )\n .eq(\"slug\", slug)\n .single();\n\n if (error) {\n if (error.code === \"PGRST116\") {\n // Not found\n return null;\n }\n throw new Error(`Failed to fetch blog post: ${error.message}`);\n }\n\n return data as unknown as BlogPostWithAuthor;\n },\n\n /**\n * Get a single post by ID\n */\n async getById(id: string): Promise<BlogPostRow | null> {\n const { data, error} = await supabase\n .from(cfg.postsTable)\n .select(\"*\")\n .eq(\"id\", id)\n .single();\n\n if (error) {\n if (error.code === \"PGRST116\") {\n return null;\n }\n throw new Error(`Failed to fetch blog post: ${error.message}`);\n }\n\n return data as unknown as BlogPostRow;\n },\n\n /**\n * Create a new blog post\n */\n async create(post: BlogPostInsert): Promise<BlogPostRow> {\n const { data, error } = await supabase\n .from(cfg.postsTable)\n .insert(post)\n .select()\n .single();\n\n if (error) {\n throw new Error(`Failed to create blog post: ${error.message}`);\n }\n\n return data as unknown as BlogPostRow;\n },\n\n /**\n * Update an existing blog post\n */\n async update(\n id: string,\n updates: BlogPostUpdate\n ): Promise<BlogPostRow> {\n const { data, error } = await supabase\n .from(cfg.postsTable)\n .update(updates)\n .eq(\"id\", id)\n .select()\n .single();\n\n if (error) {\n throw new Error(`Failed to update blog post: ${error.message}`);\n }\n\n return data as unknown as BlogPostRow;\n },\n\n /**\n * Delete a blog post\n */\n async delete(id: string): Promise<void> {\n const { error } = await supabase\n .from(cfg.postsTable)\n .delete()\n .eq(\"id\", id);\n\n if (error) {\n throw new Error(`Failed to delete blog post: ${error.message}`);\n }\n },\n\n /**\n * Publish a draft post\n */\n async publish(id: string): Promise<BlogPostRow> {\n return this.update(id, { status: \"published\" });\n },\n\n /**\n * Unpublish (archive) a post\n */\n async archive(id: string): Promise<BlogPostRow> {\n return this.update(id, { status: \"archived\" });\n },\n\n /**\n * Search posts using full-text search\n */\n async search(query: string, limit: number = 10): Promise<BlogPostRow[]> {\n const { data, error } = await supabase\n .rpc(\"blog_search_posts\", { search_query: query })\n .limit(limit);\n\n if (error) {\n throw new Error(`Failed to search blog posts: ${error.message}`);\n }\n\n return (data as unknown as BlogPostRow[]) || [];\n },\n\n /**\n * Get posts by tag\n */\n async getByTag(tag: string, limit: number = 10): Promise<BlogPostRow[]> {\n const { data, error } = await supabase\n .rpc(\"blog_get_posts_by_tag\", { tag_name: tag })\n .limit(limit);\n\n if (error) {\n throw new Error(`Failed to get posts by tag: ${error.message}`);\n }\n\n return (data as unknown as BlogPostRow[]) || [];\n },\n\n /**\n * Get posts by category\n */\n async getByCategory(\n category: string,\n limit: number = 10\n ): Promise<BlogPostRow[]> {\n const { data, error } = await supabase\n .rpc(\"blog_get_posts_by_category\", { category_name: category })\n .limit(limit);\n\n if (error) {\n throw new Error(\n `Failed to get posts by category: ${error.message}`\n );\n }\n\n return (data as unknown as BlogPostRow[]) || [];\n },\n\n /**\n * Get related posts (by tags and category)\n */\n async getRelated(postId: string, limit: number = 3): Promise<BlogPostRow[]> {\n const post = await this.getById(postId);\n if (!post) return [];\n\n const { data, error } = await supabase\n .from(cfg.postsTable)\n .select(\"*\")\n .eq(\"status\", \"published\")\n .neq(\"id\", postId)\n .or(`category.eq.${post.category},tags.ov.{${post.tags.join(\",\")}}`)\n .limit(limit);\n\n if (error) {\n throw new Error(`Failed to get related posts: ${error.message}`);\n }\n\n return (data as unknown as BlogPostRow[]) || [];\n },\n },\n\n /**\n * Blog media operations\n */\n media: {\n /**\n * List all media files\n */\n async list(\n type?: string,\n limit: number = 50\n ): Promise<BlogMediaRow[]> {\n let query = supabase\n .from(cfg.mediaTable)\n .select(\"*\")\n .order(\"uploaded_at\", { ascending: false })\n .limit(limit);\n\n if (type) {\n query = query.eq(\"type\", type);\n }\n\n const { data, error } = await query;\n\n if (error) {\n throw new Error(`Failed to fetch blog media: ${error.message}`);\n }\n\n return (data as unknown as BlogMediaRow[]) || [];\n },\n\n /**\n * Create new media record\n */\n async create(media: BlogMediaInsert): Promise<BlogMediaRow> {\n const { data, error } = await supabase\n .from(cfg.mediaTable)\n .insert(media)\n .select()\n .single();\n\n if (error) {\n throw new Error(`Failed to create blog media: ${error.message}`);\n }\n\n return data as unknown as BlogMediaRow;\n },\n\n /**\n * Update media record\n */\n async update(id: string, updates: BlogMediaUpdate): Promise<BlogMediaRow> {\n const { data, error } = await supabase\n .from(cfg.mediaTable)\n .update(updates)\n .eq(\"id\", id)\n .select()\n .single();\n\n if (error) {\n throw new Error(`Failed to update blog media: ${error.message}`);\n }\n\n return data as unknown as BlogMediaRow;\n },\n\n /**\n * Delete media record\n */\n async delete(id: string): Promise<void> {\n const { error } = await supabase\n .from(cfg.mediaTable)\n .delete()\n .eq(\"id\", id);\n\n if (error) {\n throw new Error(`Failed to delete blog media: ${error.message}`);\n }\n },\n },\n\n /**\n * Statistics and analytics\n */\n stats: {\n /**\n * Get blog statistics\n */\n async getStats(): Promise<BlogStats> {\n const { data: posts, error } = await supabase\n .from(cfg.postsTable)\n .select(\"status, category, tags\");\n\n if (error) {\n throw new Error(`Failed to fetch blog stats: ${error.message}`);\n }\n\n const stats: BlogStats = {\n totalPosts: posts.length,\n publishedPosts: 0,\n draftPosts: 0,\n archivedPosts: 0,\n categoryCounts: {},\n tagCounts: {},\n };\n\n posts.forEach((post: any) => {\n if (post.status === \"published\") stats.publishedPosts++;\n if (post.status === \"draft\") stats.draftPosts++;\n if (post.status === \"archived\") stats.archivedPosts++;\n\n if (post.category) {\n stats.categoryCounts[post.category] =\n (stats.categoryCounts[post.category] || 0) + 1;\n }\n\n post.tags?.forEach((tag: string) => {\n stats.tagCounts[tag] = (stats.tagCounts[tag] || 0) + 1;\n });\n });\n\n return stats;\n },\n\n /**\n * Get all categories with post counts\n */\n async getCategories(): Promise<BlogCategory[]> {\n const { data, error } = await supabase\n .from(cfg.postsTable)\n .select(\"category\")\n .eq(\"status\", \"published\")\n .not(\"category\", \"is\", null);\n\n if (error) {\n throw new Error(`Failed to fetch categories: ${error.message}`);\n }\n\n const counts: Record<string, number> = {};\n data?.forEach((row: any) => {\n if (row.category) {\n counts[row.category] = (counts[row.category] || 0) + 1;\n }\n });\n\n return Object.entries(counts).map(([name, postCount]) => ({\n name,\n slug: name.toLowerCase().replace(/\\s+/g, \"-\"),\n postCount,\n }));\n },\n\n /**\n * Get all tags with post counts\n */\n async getTags(): Promise<BlogTag[]> {\n const { data, error } = await supabase\n .from(cfg.postsTable)\n .select(\"tags\")\n .eq(\"status\", \"published\");\n\n if (error) {\n throw new Error(`Failed to fetch tags: ${error.message}`);\n }\n\n const counts: Record<string, number> = {};\n data?.forEach((row: any) => {\n row.tags?.forEach((tag: string) => {\n counts[tag] = (counts[tag] || 0) + 1;\n });\n });\n\n return Object.entries(counts)\n .map(([name, postCount]) => ({\n name,\n slug: name.toLowerCase().replace(/\\s+/g, \"-\"),\n postCount,\n }))\n .sort((a, b) => b.postCount - a.postCount);\n },\n },\n };\n}\n\n/**\n * Type helper to extract the blog client type\n */\nexport type BlogClient = ReturnType<typeof createBlogClient>;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/client/index.ts","../../src/client/supabase.ts"],"sourcesContent":["/**\n * Client exports for m14i-blogging\n *\n * Pre-built data access layer for Supabase integration\n *\n * @example\n * ```typescript\n * import { createBlogClient } from 'm14i-blogging/client';\n * import { createClient } from '@supabase/supabase-js';\n *\n * const supabase = createClient(url, key);\n * const blog = createBlogClient(supabase);\n *\n * // Use the client\n * const { posts } = await blog.posts.list({ status: 'published' });\n * ```\n */\n\nexport { createBlogClient } from './supabase';\nexport type { BlogClient, BlogClientConfig, SupabaseClient } from './supabase';\n","/**\n * Supabase Data Access Layer for m14i-blogging\n *\n * Provides pre-built CRUD operations for blog posts and media.\n * Works with the blog_posts and blog_media tables.\n *\n * @example\n * ```typescript\n * import { createBlogClient } from 'm14i-blogging/client';\n * import { createClient } from '@supabase/supabase-js';\n *\n * const supabase = createClient(url, key);\n * const blog = createBlogClient(supabase);\n *\n * // Use it\n * const posts = await blog.posts.list({ status: 'published' });\n * const post = await blog.posts.getBySlug('my-post');\n * ```\n */\n\nimport type {\n BlogPostRow,\n BlogPostInsert,\n BlogPostUpdate,\n BlogPostWithAuthor,\n BlogFilterParams,\n BlogPostListResponse,\n BlogMediaRow,\n BlogMediaInsert,\n BlogMediaUpdate,\n BlogStats,\n BlogCategory,\n BlogTag,\n} from \"../types/database\";\n\n// ============================================================================\n// Supabase Client Type\n// ============================================================================\n\nexport interface SupabaseClient {\n from(table: string): {\n select(query: string, options?: { count?: string }): any;\n insert(data: any): any;\n update(data: any): any;\n delete(): any;\n upsert(data: any): any;\n };\n rpc(functionName: string, params?: any): any;\n}\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nexport interface BlogClientConfig {\n /**\n * Table name for blog posts (default: \"blog_posts\")\n */\n postsTable?: string;\n /**\n * Table name for blog media (default: \"blog_media\")\n */\n mediaTable?: string;\n /**\n * Enable/disable author joins (default: true)\n */\n includeAuthor?: boolean;\n /**\n * Users table name for author joins (default: \"users\")\n */\n usersTable?: string;\n}\n\nconst DEFAULT_CONFIG: Required<BlogClientConfig> = {\n postsTable: \"blog_posts\",\n mediaTable: \"blog_media\",\n includeAuthor: true,\n usersTable: \"users\",\n};\n\n// ============================================================================\n// Blog Client Factory\n// ============================================================================\n\n/**\n * Creates a blog client with pre-built CRUD operations\n */\nexport function createBlogClient(\n supabase: SupabaseClient,\n config: BlogClientConfig = {}\n) {\n const cfg = { ...DEFAULT_CONFIG, ...config };\n\n return {\n /**\n * Blog post operations\n */\n posts: {\n /**\n * List blog posts with filtering and pagination\n */\n async list(\n params: BlogFilterParams = {}\n ): Promise<BlogPostListResponse> {\n const {\n page = 1,\n pageSize = 10,\n status,\n category,\n tag,\n search,\n orderBy = \"created_at\",\n orderDirection = \"desc\",\n } = params;\n\n const from = (page - 1) * pageSize;\n const to = from + pageSize - 1;\n\n let query = supabase\n .from(cfg.postsTable)\n .select(\n cfg.includeAuthor\n ? `\n *,\n author:${cfg.usersTable}!created_by(full_name, email, avatar_url)\n `\n : \"*\",\n { count: \"exact\" }\n )\n .range(from, to)\n .order(orderBy, { ascending: orderDirection === \"asc\" });\n\n if (status) {\n query = query.eq(\"status\", status);\n }\n\n if (category) {\n query = query.eq(\"category\", category);\n }\n\n if (tag) {\n query = query.contains(\"tags\", [tag]);\n }\n\n if (search) {\n query = query.or(\n `title.ilike.%${search}%,excerpt.ilike.%${search}%,category.ilike.%${search}%`\n );\n }\n\n const { data, error, count } = await query;\n\n if (error) {\n throw new Error(`Failed to fetch blog posts: ${error.message}`);\n }\n\n return {\n posts: (data as unknown as BlogPostWithAuthor[]) || [],\n total: count || 0,\n page,\n pageSize,\n totalPages: Math.ceil((count || 0) / pageSize),\n };\n },\n\n /**\n * Get a single post by slug\n */\n async getBySlug(slug: string): Promise<BlogPostWithAuthor | null> {\n const { data, error } = await supabase\n .from(cfg.postsTable)\n .select(\n cfg.includeAuthor\n ? `\n *,\n author:${cfg.usersTable}!created_by(full_name, email, avatar_url)\n `\n : \"*\"\n )\n .eq(\"slug\", slug)\n .single();\n\n if (error) {\n if (error.code === \"PGRST116\") {\n // Not found\n return null;\n }\n throw new Error(`Failed to fetch blog post: ${error.message}`);\n }\n\n return data as unknown as BlogPostWithAuthor;\n },\n\n /**\n * Get a single post by ID\n */\n async getById(id: string): Promise<BlogPostRow | null> {\n const { data, error} = await supabase\n .from(cfg.postsTable)\n .select(\"*\")\n .eq(\"id\", id)\n .single();\n\n if (error) {\n if (error.code === \"PGRST116\") {\n return null;\n }\n throw new Error(`Failed to fetch blog post: ${error.message}`);\n }\n\n return data as unknown as BlogPostRow;\n },\n\n /**\n * Create a new blog post\n */\n async create(post: BlogPostInsert): Promise<BlogPostRow> {\n const { data, error } = await supabase\n .from(cfg.postsTable)\n .insert(post)\n .select()\n .single();\n\n if (error) {\n throw new Error(`Failed to create blog post: ${error.message}`);\n }\n\n return data as unknown as BlogPostRow;\n },\n\n /**\n * Update an existing blog post\n */\n async update(\n id: string,\n updates: BlogPostUpdate\n ): Promise<BlogPostRow> {\n const { data, error } = await supabase\n .from(cfg.postsTable)\n .update(updates)\n .eq(\"id\", id)\n .select()\n .single();\n\n if (error) {\n throw new Error(`Failed to update blog post: ${error.message}`);\n }\n\n return data as unknown as BlogPostRow;\n },\n\n /**\n * Delete a blog post\n */\n async delete(id: string): Promise<void> {\n const { error } = await supabase\n .from(cfg.postsTable)\n .delete()\n .eq(\"id\", id);\n\n if (error) {\n throw new Error(`Failed to delete blog post: ${error.message}`);\n }\n },\n\n /**\n * Publish a draft post\n */\n async publish(id: string): Promise<BlogPostRow> {\n return this.update(id, { status: \"published\" });\n },\n\n /**\n * Unpublish (archive) a post\n */\n async archive(id: string): Promise<BlogPostRow> {\n return this.update(id, { status: \"archived\" });\n },\n\n /**\n * Search posts using full-text search\n */\n async search(query: string, limit: number = 10): Promise<BlogPostRow[]> {\n const { data, error } = await supabase\n .rpc(\"blog_search_posts\", { search_query: query })\n .limit(limit);\n\n if (error) {\n throw new Error(`Failed to search blog posts: ${error.message}`);\n }\n\n return (data as unknown as BlogPostRow[]) || [];\n },\n\n /**\n * Get posts by tag\n */\n async getByTag(tag: string, limit: number = 10): Promise<BlogPostRow[]> {\n const { data, error } = await supabase\n .rpc(\"blog_get_posts_by_tag\", { tag_name: tag })\n .limit(limit);\n\n if (error) {\n throw new Error(`Failed to get posts by tag: ${error.message}`);\n }\n\n return (data as unknown as BlogPostRow[]) || [];\n },\n\n /**\n * Get posts by category\n */\n async getByCategory(\n category: string,\n limit: number = 10\n ): Promise<BlogPostRow[]> {\n const { data, error } = await supabase\n .rpc(\"blog_get_posts_by_category\", { category_name: category })\n .limit(limit);\n\n if (error) {\n throw new Error(\n `Failed to get posts by category: ${error.message}`\n );\n }\n\n return (data as unknown as BlogPostRow[]) || [];\n },\n\n /**\n * Get related posts (by tags and category)\n */\n async getRelated(postId: string, limit: number = 3): Promise<BlogPostRow[]> {\n const post = await this.getById(postId);\n if (!post) return [];\n\n const { data, error } = await supabase\n .from(cfg.postsTable)\n .select(\"*\")\n .eq(\"status\", \"published\")\n .neq(\"id\", postId)\n .or(`category.eq.${post.category},tags.ov.{${post.tags.join(\",\")}}`)\n .limit(limit);\n\n if (error) {\n throw new Error(`Failed to get related posts: ${error.message}`);\n }\n\n return (data as unknown as BlogPostRow[]) || [];\n },\n },\n\n /**\n * Blog media operations\n */\n media: {\n /**\n * List all media files\n */\n async list(\n type?: string,\n limit: number = 50\n ): Promise<BlogMediaRow[]> {\n let query = supabase\n .from(cfg.mediaTable)\n .select(\"*\")\n .order(\"uploaded_at\", { ascending: false })\n .limit(limit);\n\n if (type) {\n query = query.eq(\"type\", type);\n }\n\n const { data, error } = await query;\n\n if (error) {\n throw new Error(`Failed to fetch blog media: ${error.message}`);\n }\n\n return (data as unknown as BlogMediaRow[]) || [];\n },\n\n /**\n * Create new media record\n */\n async create(media: BlogMediaInsert): Promise<BlogMediaRow> {\n const { data, error } = await supabase\n .from(cfg.mediaTable)\n .insert(media)\n .select()\n .single();\n\n if (error) {\n throw new Error(`Failed to create blog media: ${error.message}`);\n }\n\n return data as unknown as BlogMediaRow;\n },\n\n /**\n * Update media record\n */\n async update(id: string, updates: BlogMediaUpdate): Promise<BlogMediaRow> {\n const { data, error } = await supabase\n .from(cfg.mediaTable)\n .update(updates)\n .eq(\"id\", id)\n .select()\n .single();\n\n if (error) {\n throw new Error(`Failed to update blog media: ${error.message}`);\n }\n\n return data as unknown as BlogMediaRow;\n },\n\n /**\n * Delete media record\n */\n async delete(id: string): Promise<void> {\n const { error } = await supabase\n .from(cfg.mediaTable)\n .delete()\n .eq(\"id\", id);\n\n if (error) {\n throw new Error(`Failed to delete blog media: ${error.message}`);\n }\n },\n },\n\n /**\n * Statistics and analytics\n */\n stats: {\n /**\n * Get blog statistics\n */\n async getStats(): Promise<BlogStats> {\n const { data: posts, error } = await supabase\n .from(cfg.postsTable)\n .select(\"status, category, tags\");\n\n if (error) {\n throw new Error(`Failed to fetch blog stats: ${error.message}`);\n }\n\n const stats: BlogStats = {\n totalPosts: posts.length,\n publishedPosts: 0,\n draftPosts: 0,\n archivedPosts: 0,\n categoryCounts: {},\n tagCounts: {},\n };\n\n posts.forEach((post: any) => {\n if (post.status === \"published\") stats.publishedPosts++;\n if (post.status === \"draft\") stats.draftPosts++;\n if (post.status === \"archived\") stats.archivedPosts++;\n\n if (post.category) {\n stats.categoryCounts[post.category] =\n (stats.categoryCounts[post.category] || 0) + 1;\n }\n\n post.tags?.forEach((tag: string) => {\n stats.tagCounts[tag] = (stats.tagCounts[tag] || 0) + 1;\n });\n });\n\n return stats;\n },\n\n /**\n * Get all categories with post counts\n */\n async getCategories(): Promise<BlogCategory[]> {\n const { data, error } = await supabase\n .from(cfg.postsTable)\n .select(\"category\")\n .eq(\"status\", \"published\")\n .not(\"category\", \"is\", null);\n\n if (error) {\n throw new Error(`Failed to fetch categories: ${error.message}`);\n }\n\n const counts: Record<string, number> = {};\n data?.forEach((row: any) => {\n if (row.category) {\n counts[row.category] = (counts[row.category] || 0) + 1;\n }\n });\n\n return Object.entries(counts).map(([name, postCount]) => ({\n name,\n slug: name.toLowerCase().replace(/\\s+/g, \"-\"),\n postCount,\n }));\n },\n\n /**\n * Get all tags with post counts\n */\n async getTags(): Promise<BlogTag[]> {\n const { data, error } = await supabase\n .from(cfg.postsTable)\n .select(\"tags\")\n .eq(\"status\", \"published\");\n\n if (error) {\n throw new Error(`Failed to fetch tags: ${error.message}`);\n }\n\n const counts: Record<string, number> = {};\n data?.forEach((row: any) => {\n row.tags?.forEach((tag: string) => {\n counts[tag] = (counts[tag] || 0) + 1;\n });\n });\n\n return Object.entries(counts)\n .map(([name, postCount]) => ({\n name,\n slug: name.toLowerCase().replace(/\\s+/g, \"-\"),\n postCount,\n }))\n .sort((a, b) => b.postCount - a.postCount);\n },\n },\n\n /**\n * Category management operations (v0.4.0+)\n */\n categories: {\n /**\n * List all categories\n */\n async list(): Promise<import(\"../types/database\").CategoryRow[]> {\n const { data, error } = await supabase\n .from(\"blog.categories\")\n .select(\"*\")\n .order(\"display_order\", { ascending: true });\n\n if (error) {\n throw new Error(`Failed to fetch categories: ${error.message}`);\n }\n\n return (data as unknown as import(\"../types/database\").CategoryRow[]) || [];\n },\n\n /**\n * List categories with post counts\n */\n async listWithCounts(): Promise<import(\"../types/database\").CategoryWithCount[]> {\n const { data, error } = await supabase\n .rpc(\"blog.get_categories_with_counts\");\n\n if (error) {\n throw new Error(`Failed to fetch categories with counts: ${error.message}`);\n }\n\n return (data as unknown as import(\"../types/database\").CategoryWithCount[]) || [];\n },\n\n /**\n * Get category by ID\n */\n async getById(id: string): Promise<import(\"../types/database\").CategoryRow | null> {\n const { data, error } = await supabase\n .from(\"blog.categories\")\n .select(\"*\")\n .eq(\"id\", id)\n .single();\n\n if (error) {\n if (error.code === \"PGRST116\") return null;\n throw new Error(`Failed to fetch category: ${error.message}`);\n }\n\n return data as unknown as import(\"../types/database\").CategoryRow;\n },\n\n /**\n * Get category by slug\n */\n async getBySlug(slug: string): Promise<import(\"../types/database\").CategoryRow | null> {\n const { data, error } = await supabase\n .from(\"blog.categories\")\n .select(\"*\")\n .eq(\"slug\", slug)\n .single();\n\n if (error) {\n if (error.code === \"PGRST116\") return null;\n throw new Error(`Failed to fetch category: ${error.message}`);\n }\n\n return data as unknown as import(\"../types/database\").CategoryRow;\n },\n\n /**\n * Create a new category\n */\n async create(category: import(\"../types/database\").CategoryInsert): Promise<import(\"../types/database\").CategoryRow> {\n const { data, error } = await supabase\n .from(\"blog.categories\")\n .insert(category)\n .select()\n .single();\n\n if (error) {\n throw new Error(`Failed to create category: ${error.message}`);\n }\n\n return data as unknown as import(\"../types/database\").CategoryRow;\n },\n\n /**\n * Update an existing category\n */\n async update(id: string, updates: import(\"../types/database\").CategoryUpdate): Promise<import(\"../types/database\").CategoryRow> {\n const { data, error } = await supabase\n .from(\"blog.categories\")\n .update(updates)\n .eq(\"id\", id)\n .select()\n .single();\n\n if (error) {\n throw new Error(`Failed to update category: ${error.message}`);\n }\n\n return data as unknown as import(\"../types/database\").CategoryRow;\n },\n\n /**\n * Delete a category\n */\n async delete(id: string): Promise<void> {\n const { error } = await supabase\n .from(\"blog.categories\")\n .delete()\n .eq(\"id\", id);\n\n if (error) {\n throw new Error(`Failed to delete category: ${error.message}`);\n }\n },\n },\n\n /**\n * Tag management operations (v0.4.0+)\n */\n tags: {\n /**\n * List all tags\n */\n async list(): Promise<import(\"../types/database\").TagRow[]> {\n const { data, error } = await supabase\n .from(\"blog.tags\")\n .select(\"*\")\n .order(\"name\", { ascending: true });\n\n if (error) {\n throw new Error(`Failed to fetch tags: ${error.message}`);\n }\n\n return (data as unknown as import(\"../types/database\").TagRow[]) || [];\n },\n\n /**\n * List tags with post counts\n */\n async listWithCounts(): Promise<import(\"../types/database\").TagWithCount[]> {\n const { data, error } = await supabase\n .rpc(\"blog.get_tags_with_counts\");\n\n if (error) {\n throw new Error(`Failed to fetch tags with counts: ${error.message}`);\n }\n\n return (data as unknown as import(\"../types/database\").TagWithCount[]) || [];\n },\n\n /**\n * Get tag by ID\n */\n async getById(id: string): Promise<import(\"../types/database\").TagRow | null> {\n const { data, error } = await supabase\n .from(\"blog.tags\")\n .select(\"*\")\n .eq(\"id\", id)\n .single();\n\n if (error) {\n if (error.code === \"PGRST116\") return null;\n throw new Error(`Failed to fetch tag: ${error.message}`);\n }\n\n return data as unknown as import(\"../types/database\").TagRow;\n },\n\n /**\n * Get tag by slug\n */\n async getBySlug(slug: string): Promise<import(\"../types/database\").TagRow | null> {\n const { data, error } = await supabase\n .from(\"blog.tags\")\n .select(\"*\")\n .eq(\"slug\", slug)\n .single();\n\n if (error) {\n if (error.code === \"PGRST116\") return null;\n throw new Error(`Failed to fetch tag: ${error.message}`);\n }\n\n return data as unknown as import(\"../types/database\").TagRow;\n },\n\n /**\n * Create a new tag\n */\n async create(tag: import(\"../types/database\").TagInsert): Promise<import(\"../types/database\").TagRow> {\n const { data, error } = await supabase\n .from(\"blog.tags\")\n .insert(tag)\n .select()\n .single();\n\n if (error) {\n throw new Error(`Failed to create tag: ${error.message}`);\n }\n\n return data as unknown as import(\"../types/database\").TagRow;\n },\n\n /**\n * Update an existing tag\n */\n async update(id: string, updates: import(\"../types/database\").TagUpdate): Promise<import(\"../types/database\").TagRow> {\n const { data, error } = await supabase\n .from(\"blog.tags\")\n .update(updates)\n .eq(\"id\", id)\n .select()\n .single();\n\n if (error) {\n throw new Error(`Failed to update tag: ${error.message}`);\n }\n\n return data as unknown as import(\"../types/database\").TagRow;\n },\n\n /**\n * Delete a tag\n */\n async delete(id: string): Promise<void> {\n const { error } = await supabase\n .from(\"blog.tags\")\n .delete()\n .eq(\"id\", id);\n\n if (error) {\n throw new Error(`Failed to delete tag: ${error.message}`);\n }\n },\n },\n };\n}\n\n/**\n * Type helper to extract the blog client type\n */\nexport type BlogClient = ReturnType<typeof createBlogClient>;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACyEA,IAAM,iBAA6C;AAAA,EACjD,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,YAAY;AACd;AASO,SAAS,iBACd,UACA,SAA2B,CAAC,GAC5B;AACA,QAAM,MAAM,EAAE,GAAG,gBAAgB,GAAG,OAAO;AAE3C,SAAO;AAAA;AAAA;AAAA;AAAA,IAIL,OAAO;AAAA;AAAA;AAAA;AAAA,MAIL,MAAM,KACJ,SAA2B,CAAC,GACG;AAC/B,cAAM;AAAA,UACJ,OAAO;AAAA,UACP,WAAW;AAAA,UACX;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,UAAU;AAAA,UACV,iBAAiB;AAAA,QACnB,IAAI;AAEJ,cAAM,QAAQ,OAAO,KAAK;AAC1B,cAAM,KAAK,OAAO,WAAW;AAE7B,YAAI,QAAQ,SACT,KAAK,IAAI,UAAU,EACnB;AAAA,UACC,IAAI,gBACA;AAAA;AAAA,uBAEO,IAAI,UAAU;AAAA,gBAErB;AAAA,UACJ,EAAE,OAAO,QAAQ;AAAA,QACnB,EACC,MAAM,MAAM,EAAE,EACd,MAAM,SAAS,EAAE,WAAW,mBAAmB,MAAM,CAAC;AAEzD,YAAI,QAAQ;AACV,kBAAQ,MAAM,GAAG,UAAU,MAAM;AAAA,QACnC;AAEA,YAAI,UAAU;AACZ,kBAAQ,MAAM,GAAG,YAAY,QAAQ;AAAA,QACvC;AAEA,YAAI,KAAK;AACP,kBAAQ,MAAM,SAAS,QAAQ,CAAC,GAAG,CAAC;AAAA,QACtC;AAEA,YAAI,QAAQ;AACV,kBAAQ,MAAM;AAAA,YACZ,gBAAgB,MAAM,oBAAoB,MAAM,qBAAqB,MAAM;AAAA,UAC7E;AAAA,QACF;AAEA,cAAM,EAAE,MAAM,OAAO,MAAM,IAAI,MAAM;AAErC,YAAI,OAAO;AACT,gBAAM,IAAI,MAAM,+BAA+B,MAAM,OAAO,EAAE;AAAA,QAChE;AAEA,eAAO;AAAA,UACL,OAAQ,QAA4C,CAAC;AAAA,UACrD,OAAO,SAAS;AAAA,UAChB;AAAA,UACA;AAAA,UACA,YAAY,KAAK,MAAM,SAAS,KAAK,QAAQ;AAAA,QAC/C;AAAA,MACF;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,UAAU,MAAkD;AAChE,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,IAAI,UAAU,EACnB;AAAA,UACC,IAAI,gBACA;AAAA;AAAA,uBAEO,IAAI,UAAU;AAAA,gBAErB;AAAA,QACN,EACC,GAAG,QAAQ,IAAI,EACf,OAAO;AAEV,YAAI,OAAO;AACT,cAAI,MAAM,SAAS,YAAY;AAE7B,mBAAO;AAAA,UACT;AACA,gBAAM,IAAI,MAAM,8BAA8B,MAAM,OAAO,EAAE;AAAA,QAC/D;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,QAAQ,IAAyC;AACrD,cAAM,EAAE,MAAM,MAAK,IAAI,MAAM,SAC1B,KAAK,IAAI,UAAU,EACnB,OAAO,GAAG,EACV,GAAG,MAAM,EAAE,EACX,OAAO;AAEV,YAAI,OAAO;AACT,cAAI,MAAM,SAAS,YAAY;AAC7B,mBAAO;AAAA,UACT;AACA,gBAAM,IAAI,MAAM,8BAA8B,MAAM,OAAO,EAAE;AAAA,QAC/D;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,OAAO,MAA4C;AACvD,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,IAAI,UAAU,EACnB,OAAO,IAAI,EACX,OAAO,EACP,OAAO;AAEV,YAAI,OAAO;AACT,gBAAM,IAAI,MAAM,+BAA+B,MAAM,OAAO,EAAE;AAAA,QAChE;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,OACJ,IACA,SACsB;AACtB,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,IAAI,UAAU,EACnB,OAAO,OAAO,EACd,GAAG,MAAM,EAAE,EACX,OAAO,EACP,OAAO;AAEV,YAAI,OAAO;AACT,gBAAM,IAAI,MAAM,+BAA+B,MAAM,OAAO,EAAE;AAAA,QAChE;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,OAAO,IAA2B;AACtC,cAAM,EAAE,MAAM,IAAI,MAAM,SACrB,KAAK,IAAI,UAAU,EACnB,OAAO,EACP,GAAG,MAAM,EAAE;AAEd,YAAI,OAAO;AACT,gBAAM,IAAI,MAAM,+BAA+B,MAAM,OAAO,EAAE;AAAA,QAChE;AAAA,MACF;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,QAAQ,IAAkC;AAC9C,eAAO,KAAK,OAAO,IAAI,EAAE,QAAQ,YAAY,CAAC;AAAA,MAChD;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,QAAQ,IAAkC;AAC9C,eAAO,KAAK,OAAO,IAAI,EAAE,QAAQ,WAAW,CAAC;AAAA,MAC/C;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,OAAO,OAAe,QAAgB,IAA4B;AACtE,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,IAAI,qBAAqB,EAAE,cAAc,MAAM,CAAC,EAChD,MAAM,KAAK;AAEd,YAAI,OAAO;AACT,gBAAM,IAAI,MAAM,gCAAgC,MAAM,OAAO,EAAE;AAAA,QACjE;AAEA,eAAQ,QAAqC,CAAC;AAAA,MAChD;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,SAAS,KAAa,QAAgB,IAA4B;AACtE,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,IAAI,yBAAyB,EAAE,UAAU,IAAI,CAAC,EAC9C,MAAM,KAAK;AAEd,YAAI,OAAO;AACT,gBAAM,IAAI,MAAM,+BAA+B,MAAM,OAAO,EAAE;AAAA,QAChE;AAEA,eAAQ,QAAqC,CAAC;AAAA,MAChD;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,cACJ,UACA,QAAgB,IACQ;AACxB,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,IAAI,8BAA8B,EAAE,eAAe,SAAS,CAAC,EAC7D,MAAM,KAAK;AAEd,YAAI,OAAO;AACT,gBAAM,IAAI;AAAA,YACR,oCAAoC,MAAM,OAAO;AAAA,UACnD;AAAA,QACF;AAEA,eAAQ,QAAqC,CAAC;AAAA,MAChD;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,WAAW,QAAgB,QAAgB,GAA2B;AAC1E,cAAM,OAAO,MAAM,KAAK,QAAQ,MAAM;AACtC,YAAI,CAAC,KAAM,QAAO,CAAC;AAEnB,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,IAAI,UAAU,EACnB,OAAO,GAAG,EACV,GAAG,UAAU,WAAW,EACxB,IAAI,MAAM,MAAM,EAChB,GAAG,eAAe,KAAK,QAAQ,aAAa,KAAK,KAAK,KAAK,GAAG,CAAC,GAAG,EAClE,MAAM,KAAK;AAEd,YAAI,OAAO;AACT,gBAAM,IAAI,MAAM,gCAAgC,MAAM,OAAO,EAAE;AAAA,QACjE;AAEA,eAAQ,QAAqC,CAAC;AAAA,MAChD;AAAA,IACF;AAAA;AAAA;AAAA;AAAA,IAKA,OAAO;AAAA;AAAA;AAAA;AAAA,MAIL,MAAM,KACJ,MACA,QAAgB,IACS;AACzB,YAAI,QAAQ,SACT,KAAK,IAAI,UAAU,EACnB,OAAO,GAAG,EACV,MAAM,eAAe,EAAE,WAAW,MAAM,CAAC,EACzC,MAAM,KAAK;AAEd,YAAI,MAAM;AACR,kBAAQ,MAAM,GAAG,QAAQ,IAAI;AAAA,QAC/B;AAEA,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM;AAE9B,YAAI,OAAO;AACT,gBAAM,IAAI,MAAM,+BAA+B,MAAM,OAAO,EAAE;AAAA,QAChE;AAEA,eAAQ,QAAsC,CAAC;AAAA,MACjD;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,OAAO,OAA+C;AAC1D,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,IAAI,UAAU,EACnB,OAAO,KAAK,EACZ,OAAO,EACP,OAAO;AAEV,YAAI,OAAO;AACT,gBAAM,IAAI,MAAM,gCAAgC,MAAM,OAAO,EAAE;AAAA,QACjE;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,OAAO,IAAY,SAAiD;AACxE,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,IAAI,UAAU,EACnB,OAAO,OAAO,EACd,GAAG,MAAM,EAAE,EACX,OAAO,EACP,OAAO;AAEV,YAAI,OAAO;AACT,gBAAM,IAAI,MAAM,gCAAgC,MAAM,OAAO,EAAE;AAAA,QACjE;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,OAAO,IAA2B;AACtC,cAAM,EAAE,MAAM,IAAI,MAAM,SACrB,KAAK,IAAI,UAAU,EACnB,OAAO,EACP,GAAG,MAAM,EAAE;AAEd,YAAI,OAAO;AACT,gBAAM,IAAI,MAAM,gCAAgC,MAAM,OAAO,EAAE;AAAA,QACjE;AAAA,MACF;AAAA,IACF;AAAA;AAAA;AAAA;AAAA,IAKA,OAAO;AAAA;AAAA;AAAA;AAAA,MAIL,MAAM,WAA+B;AACnC,cAAM,EAAE,MAAM,OAAO,MAAM,IAAI,MAAM,SAClC,KAAK,IAAI,UAAU,EACnB,OAAO,wBAAwB;AAElC,YAAI,OAAO;AACT,gBAAM,IAAI,MAAM,+BAA+B,MAAM,OAAO,EAAE;AAAA,QAChE;AAEA,cAAM,QAAmB;AAAA,UACvB,YAAY,MAAM;AAAA,UAClB,gBAAgB;AAAA,UAChB,YAAY;AAAA,UACZ,eAAe;AAAA,UACf,gBAAgB,CAAC;AAAA,UACjB,WAAW,CAAC;AAAA,QACd;AAEA,cAAM,QAAQ,CAAC,SAAc;AAC3B,cAAI,KAAK,WAAW,YAAa,OAAM;AACvC,cAAI,KAAK,WAAW,QAAS,OAAM;AACnC,cAAI,KAAK,WAAW,WAAY,OAAM;AAEtC,cAAI,KAAK,UAAU;AACjB,kBAAM,eAAe,KAAK,QAAQ,KAC/B,MAAM,eAAe,KAAK,QAAQ,KAAK,KAAK;AAAA,UACjD;AAEA,eAAK,MAAM,QAAQ,CAAC,QAAgB;AAClC,kBAAM,UAAU,GAAG,KAAK,MAAM,UAAU,GAAG,KAAK,KAAK;AAAA,UACvD,CAAC;AAAA,QACH,CAAC;AAED,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,gBAAyC;AAC7C,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,IAAI,UAAU,EACnB,OAAO,UAAU,EACjB,GAAG,UAAU,WAAW,EACxB,IAAI,YAAY,MAAM,IAAI;AAE7B,YAAI,OAAO;AACT,gBAAM,IAAI,MAAM,+BAA+B,MAAM,OAAO,EAAE;AAAA,QAChE;AAEA,cAAM,SAAiC,CAAC;AACxC,cAAM,QAAQ,CAAC,QAAa;AAC1B,cAAI,IAAI,UAAU;AAChB,mBAAO,IAAI,QAAQ,KAAK,OAAO,IAAI,QAAQ,KAAK,KAAK;AAAA,UACvD;AAAA,QACF,CAAC;AAED,eAAO,OAAO,QAAQ,MAAM,EAAE,IAAI,CAAC,CAAC,MAAM,SAAS,OAAO;AAAA,UACxD;AAAA,UACA,MAAM,KAAK,YAAY,EAAE,QAAQ,QAAQ,GAAG;AAAA,UAC5C;AAAA,QACF,EAAE;AAAA,MACJ;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,UAA8B;AAClC,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,IAAI,UAAU,EACnB,OAAO,MAAM,EACb,GAAG,UAAU,WAAW;AAE3B,YAAI,OAAO;AACT,gBAAM,IAAI,MAAM,yBAAyB,MAAM,OAAO,EAAE;AAAA,QAC1D;AAEA,cAAM,SAAiC,CAAC;AACxC,cAAM,QAAQ,CAAC,QAAa;AAC1B,cAAI,MAAM,QAAQ,CAAC,QAAgB;AACjC,mBAAO,GAAG,KAAK,OAAO,GAAG,KAAK,KAAK;AAAA,UACrC,CAAC;AAAA,QACH,CAAC;AAED,eAAO,OAAO,QAAQ,MAAM,EACzB,IAAI,CAAC,CAAC,MAAM,SAAS,OAAO;AAAA,UAC3B;AAAA,UACA,MAAM,KAAK,YAAY,EAAE,QAAQ,QAAQ,GAAG;AAAA,UAC5C;AAAA,QACF,EAAE,EACD,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;AAAA,MAC7C;AAAA,IACF;AAAA;AAAA;AAAA;AAAA,IAKA,YAAY;AAAA;AAAA;AAAA;AAAA,MAIV,MAAM,OAA2D;AAC/D,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,iBAAiB,EACtB,OAAO,GAAG,EACV,MAAM,iBAAiB,EAAE,WAAW,KAAK,CAAC;AAE7C,YAAI,OAAO;AACT,gBAAM,IAAI,MAAM,+BAA+B,MAAM,OAAO,EAAE;AAAA,QAChE;AAEA,eAAQ,QAAiE,CAAC;AAAA,MAC5E;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,iBAA2E;AAC/E,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,IAAI,iCAAiC;AAExC,YAAI,OAAO;AACT,gBAAM,IAAI,MAAM,2CAA2C,MAAM,OAAO,EAAE;AAAA,QAC5E;AAEA,eAAQ,QAAuE,CAAC;AAAA,MAClF;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,QAAQ,IAAqE;AACjF,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,iBAAiB,EACtB,OAAO,GAAG,EACV,GAAG,MAAM,EAAE,EACX,OAAO;AAEV,YAAI,OAAO;AACT,cAAI,MAAM,SAAS,WAAY,QAAO;AACtC,gBAAM,IAAI,MAAM,6BAA6B,MAAM,OAAO,EAAE;AAAA,QAC9D;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,UAAU,MAAuE;AACrF,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,iBAAiB,EACtB,OAAO,GAAG,EACV,GAAG,QAAQ,IAAI,EACf,OAAO;AAEV,YAAI,OAAO;AACT,cAAI,MAAM,SAAS,WAAY,QAAO;AACtC,gBAAM,IAAI,MAAM,6BAA6B,MAAM,OAAO,EAAE;AAAA,QAC9D;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,OAAO,UAAwG;AACnH,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,iBAAiB,EACtB,OAAO,QAAQ,EACf,OAAO,EACP,OAAO;AAEV,YAAI,OAAO;AACT,gBAAM,IAAI,MAAM,8BAA8B,MAAM,OAAO,EAAE;AAAA,QAC/D;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,OAAO,IAAY,SAAuG;AAC9H,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,iBAAiB,EACtB,OAAO,OAAO,EACd,GAAG,MAAM,EAAE,EACX,OAAO,EACP,OAAO;AAEV,YAAI,OAAO;AACT,gBAAM,IAAI,MAAM,8BAA8B,MAAM,OAAO,EAAE;AAAA,QAC/D;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,OAAO,IAA2B;AACtC,cAAM,EAAE,MAAM,IAAI,MAAM,SACrB,KAAK,iBAAiB,EACtB,OAAO,EACP,GAAG,MAAM,EAAE;AAEd,YAAI,OAAO;AACT,gBAAM,IAAI,MAAM,8BAA8B,MAAM,OAAO,EAAE;AAAA,QAC/D;AAAA,MACF;AAAA,IACF;AAAA;AAAA;AAAA;AAAA,IAKA,MAAM;AAAA;AAAA;AAAA;AAAA,MAIJ,MAAM,OAAsD;AAC1D,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,WAAW,EAChB,OAAO,GAAG,EACV,MAAM,QAAQ,EAAE,WAAW,KAAK,CAAC;AAEpC,YAAI,OAAO;AACT,gBAAM,IAAI,MAAM,yBAAyB,MAAM,OAAO,EAAE;AAAA,QAC1D;AAEA,eAAQ,QAA4D,CAAC;AAAA,MACvE;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,iBAAsE;AAC1E,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,IAAI,2BAA2B;AAElC,YAAI,OAAO;AACT,gBAAM,IAAI,MAAM,qCAAqC,MAAM,OAAO,EAAE;AAAA,QACtE;AAEA,eAAQ,QAAkE,CAAC;AAAA,MAC7E;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,QAAQ,IAAgE;AAC5E,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,WAAW,EAChB,OAAO,GAAG,EACV,GAAG,MAAM,EAAE,EACX,OAAO;AAEV,YAAI,OAAO;AACT,cAAI,MAAM,SAAS,WAAY,QAAO;AACtC,gBAAM,IAAI,MAAM,wBAAwB,MAAM,OAAO,EAAE;AAAA,QACzD;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,UAAU,MAAkE;AAChF,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,WAAW,EAChB,OAAO,GAAG,EACV,GAAG,QAAQ,IAAI,EACf,OAAO;AAEV,YAAI,OAAO;AACT,cAAI,MAAM,SAAS,WAAY,QAAO;AACtC,gBAAM,IAAI,MAAM,wBAAwB,MAAM,OAAO,EAAE;AAAA,QACzD;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,OAAO,KAAyF;AACpG,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,WAAW,EAChB,OAAO,GAAG,EACV,OAAO,EACP,OAAO;AAEV,YAAI,OAAO;AACT,gBAAM,IAAI,MAAM,yBAAyB,MAAM,OAAO,EAAE;AAAA,QAC1D;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,OAAO,IAAY,SAA6F;AACpH,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,WAAW,EAChB,OAAO,OAAO,EACd,GAAG,MAAM,EAAE,EACX,OAAO,EACP,OAAO;AAEV,YAAI,OAAO;AACT,gBAAM,IAAI,MAAM,yBAAyB,MAAM,OAAO,EAAE;AAAA,QAC1D;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,OAAO,IAA2B;AACtC,cAAM,EAAE,MAAM,IAAI,MAAM,SACrB,KAAK,WAAW,EAChB,OAAO,EACP,GAAG,MAAM,EAAE;AAEd,YAAI,OAAO;AACT,gBAAM,IAAI,MAAM,yBAAyB,MAAM,OAAO,EAAE;AAAA,QAC1D;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -231,7 +231,7 @@ interface BlogStats {
231
231
  tagCounts: Record<string, number>;
232
232
  }
233
233
  /**
234
- * Category with post count
234
+ * Category with post count (from stats)
235
235
  */
236
236
  interface BlogCategory {
237
237
  name: string;
@@ -239,33 +239,108 @@ interface BlogCategory {
239
239
  postCount: number;
240
240
  }
241
241
  /**
242
- * Tag with post count
242
+ * Tag with post count (from stats)
243
243
  */
244
244
  interface BlogTag {
245
245
  name: string;
246
246
  slug: string;
247
247
  postCount: number;
248
248
  }
249
-
250
249
  /**
251
- * Supabase Data Access Layer for m14i-blogging
252
- *
253
- * Provides pre-built CRUD operations for blog posts and media.
254
- * Works with the blog_posts and blog_media tables.
255
- *
256
- * @example
257
- * ```typescript
258
- * import { createBlogClient } from 'm14i-blogging/client';
259
- * import { createClient } from '@supabase/supabase-js';
260
- *
261
- * const supabase = createClient(url, key);
262
- * const blog = createBlogClient(supabase);
263
- *
264
- * // Use it
265
- * const posts = await blog.posts.list({ status: 'published' });
266
- * const post = await blog.posts.getBySlug('my-post');
267
- * ```
250
+ * Complete category from database
251
+ */
252
+ interface CategoryRow {
253
+ id: string;
254
+ name: string;
255
+ slug: string;
256
+ description: string | null;
257
+ color: string | null;
258
+ icon: string | null;
259
+ display_order: number;
260
+ created_at: string;
261
+ updated_at: string;
262
+ created_by: string | null;
263
+ }
264
+ /**
265
+ * Data required to insert a new category
266
+ */
267
+ interface CategoryInsert {
268
+ name: string;
269
+ slug: string;
270
+ description?: string | null;
271
+ color?: string | null;
272
+ icon?: string | null;
273
+ display_order?: number;
274
+ created_by?: string | null;
275
+ }
276
+ /**
277
+ * Data that can be updated in existing category
278
+ */
279
+ interface CategoryUpdate {
280
+ name?: string;
281
+ slug?: string;
282
+ description?: string | null;
283
+ color?: string | null;
284
+ icon?: string | null;
285
+ display_order?: number;
286
+ }
287
+ /**
288
+ * Category with post count (from database function)
289
+ */
290
+ interface CategoryWithCount {
291
+ id: string;
292
+ name: string;
293
+ slug: string;
294
+ description: string | null;
295
+ color: string | null;
296
+ icon: string | null;
297
+ display_order: number;
298
+ post_count: number;
299
+ }
300
+ /**
301
+ * Complete tag from database
302
+ */
303
+ interface TagRow {
304
+ id: string;
305
+ name: string;
306
+ slug: string;
307
+ description: string | null;
308
+ color: string | null;
309
+ usage_count: number;
310
+ created_at: string;
311
+ updated_at: string;
312
+ created_by: string | null;
313
+ }
314
+ /**
315
+ * Data required to insert a new tag
316
+ */
317
+ interface TagInsert {
318
+ name: string;
319
+ slug: string;
320
+ description?: string | null;
321
+ color?: string | null;
322
+ created_by?: string | null;
323
+ }
324
+ /**
325
+ * Data that can be updated in existing tag
326
+ */
327
+ interface TagUpdate {
328
+ name?: string;
329
+ slug?: string;
330
+ description?: string | null;
331
+ color?: string | null;
332
+ }
333
+ /**
334
+ * Tag with post count (from database function)
268
335
  */
336
+ interface TagWithCount {
337
+ id: string;
338
+ name: string;
339
+ slug: string;
340
+ description: string | null;
341
+ color: string | null;
342
+ post_count: number;
343
+ }
269
344
 
270
345
  interface SupabaseClient {
271
346
  from(table: string): {
@@ -392,6 +467,72 @@ declare function createBlogClient(supabase: SupabaseClient, config?: BlogClientC
392
467
  */
393
468
  getTags(): Promise<BlogTag[]>;
394
469
  };
470
+ /**
471
+ * Category management operations (v0.4.0+)
472
+ */
473
+ categories: {
474
+ /**
475
+ * List all categories
476
+ */
477
+ list(): Promise<CategoryRow[]>;
478
+ /**
479
+ * List categories with post counts
480
+ */
481
+ listWithCounts(): Promise<CategoryWithCount[]>;
482
+ /**
483
+ * Get category by ID
484
+ */
485
+ getById(id: string): Promise<CategoryRow | null>;
486
+ /**
487
+ * Get category by slug
488
+ */
489
+ getBySlug(slug: string): Promise<CategoryRow | null>;
490
+ /**
491
+ * Create a new category
492
+ */
493
+ create(category: CategoryInsert): Promise<CategoryRow>;
494
+ /**
495
+ * Update an existing category
496
+ */
497
+ update(id: string, updates: CategoryUpdate): Promise<CategoryRow>;
498
+ /**
499
+ * Delete a category
500
+ */
501
+ delete(id: string): Promise<void>;
502
+ };
503
+ /**
504
+ * Tag management operations (v0.4.0+)
505
+ */
506
+ tags: {
507
+ /**
508
+ * List all tags
509
+ */
510
+ list(): Promise<TagRow[]>;
511
+ /**
512
+ * List tags with post counts
513
+ */
514
+ listWithCounts(): Promise<TagWithCount[]>;
515
+ /**
516
+ * Get tag by ID
517
+ */
518
+ getById(id: string): Promise<TagRow | null>;
519
+ /**
520
+ * Get tag by slug
521
+ */
522
+ getBySlug(slug: string): Promise<TagRow | null>;
523
+ /**
524
+ * Create a new tag
525
+ */
526
+ create(tag: TagInsert): Promise<TagRow>;
527
+ /**
528
+ * Update an existing tag
529
+ */
530
+ update(id: string, updates: TagUpdate): Promise<TagRow>;
531
+ /**
532
+ * Delete a tag
533
+ */
534
+ delete(id: string): Promise<void>;
535
+ };
395
536
  };
396
537
  /**
397
538
  * Type helper to extract the blog client type