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.
- package/dist/client/index.cjs +152 -0
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +161 -20
- package/dist/client/index.d.ts +161 -20
- package/dist/client/index.mjs +152 -0
- package/dist/client/index.mjs.map +1 -1
- package/dist/index.cjs +152 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +341 -200
- package/dist/index.d.ts +341 -200
- package/dist/index.mjs +152 -0
- package/dist/index.mjs.map +1 -1
- package/dist/m14i-blogging-0.4.0.tgz +0 -0
- package/package.json +1 -1
- package/supabase/migrations/20260405000001_add_taxonomy_tables.sql +334 -0
- package/dist/m14i-blogging-0.3.0.tgz +0 -0
package/dist/client/index.cjs
CHANGED
|
@@ -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":"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","names":[]}
|
package/dist/client/index.d.cts
CHANGED
|
@@ -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
|
-
*
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
*
|
|
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
|