@nby.ai/ucm 1.0.1
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/README.md +260 -0
- package/dist/index.cjs +797 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +492 -0
- package/dist/index.d.ts +492 -0
- package/dist/index.js +759 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +341 -0
- package/dist/react.d.cts +312 -0
- package/dist/react.d.ts +312 -0
- package/dist/react.js +313 -0
- package/package.json +65 -0
package/dist/react.d.cts
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Universal Content Module - Type Definitions
|
|
7
|
+
* Supabase-powered content management with multi-language support
|
|
8
|
+
*/
|
|
9
|
+
type ContentType = 'news' | 'daily' | 'blog' | 'doc' | 'event' | 'announcement' | 'tutorial' | 'case_study' | 'faq' | 'changelog' | 'thought' | 'digest' | 'briefing';
|
|
10
|
+
type ContentStatus = 'draft' | 'scheduled' | 'published' | 'archived';
|
|
11
|
+
type ContentVisibility = 'public' | 'internal' | 'unlisted';
|
|
12
|
+
type SupportedLanguage = 'zh' | 'en' | 'ja' | 'ko' | 'es' | 'de' | 'fr';
|
|
13
|
+
type I18nText = Partial<Record<SupportedLanguage, string>>;
|
|
14
|
+
interface Content {
|
|
15
|
+
id: string;
|
|
16
|
+
slug: string;
|
|
17
|
+
type: ContentType;
|
|
18
|
+
status: ContentStatus;
|
|
19
|
+
visibility: ContentVisibility;
|
|
20
|
+
category_id: string | null;
|
|
21
|
+
title: I18nText;
|
|
22
|
+
description: I18nText;
|
|
23
|
+
content: I18nText;
|
|
24
|
+
cover_image: string | null;
|
|
25
|
+
images: Array<{
|
|
26
|
+
url: string;
|
|
27
|
+
alt?: I18nText;
|
|
28
|
+
}>;
|
|
29
|
+
tags: string[];
|
|
30
|
+
author: string;
|
|
31
|
+
source: string | null;
|
|
32
|
+
source_url: string | null;
|
|
33
|
+
metadata: ContentMetadata;
|
|
34
|
+
seo: SeoData;
|
|
35
|
+
featured: boolean;
|
|
36
|
+
pinned: boolean;
|
|
37
|
+
published_at: string | null;
|
|
38
|
+
scheduled_at: string | null;
|
|
39
|
+
created_at: string;
|
|
40
|
+
updated_at: string;
|
|
41
|
+
sort_order: number;
|
|
42
|
+
view_count: number;
|
|
43
|
+
}
|
|
44
|
+
interface NewsMetadata {
|
|
45
|
+
wechat_msg_id?: string;
|
|
46
|
+
original_author?: string;
|
|
47
|
+
subtype?: 'press' | 'announcement' | 'changelog';
|
|
48
|
+
version?: string;
|
|
49
|
+
}
|
|
50
|
+
interface DailyMetadata {
|
|
51
|
+
date: string;
|
|
52
|
+
market?: Record<string, string>;
|
|
53
|
+
highlights?: string[];
|
|
54
|
+
}
|
|
55
|
+
interface DigestMetadata {
|
|
56
|
+
topic?: string;
|
|
57
|
+
highlights?: string[];
|
|
58
|
+
sources?: string[];
|
|
59
|
+
date?: string;
|
|
60
|
+
}
|
|
61
|
+
type BriefingFrequency = 'daily' | 'weekly' | 'monthly';
|
|
62
|
+
interface BriefingMetadata {
|
|
63
|
+
frequency: BriefingFrequency;
|
|
64
|
+
period: string;
|
|
65
|
+
metrics?: Record<string, number>;
|
|
66
|
+
highlights?: string[];
|
|
67
|
+
tasks_completed?: number;
|
|
68
|
+
tasks_pending?: number;
|
|
69
|
+
}
|
|
70
|
+
interface BlogMetadata {
|
|
71
|
+
reading_time?: number;
|
|
72
|
+
series?: string;
|
|
73
|
+
toc?: boolean;
|
|
74
|
+
}
|
|
75
|
+
interface DocMetadata {
|
|
76
|
+
version?: string;
|
|
77
|
+
prev_slug?: string;
|
|
78
|
+
next_slug?: string;
|
|
79
|
+
}
|
|
80
|
+
interface EventMetadata {
|
|
81
|
+
start_time: string;
|
|
82
|
+
end_time: string;
|
|
83
|
+
location?: {
|
|
84
|
+
type: 'online' | 'offline';
|
|
85
|
+
url?: string;
|
|
86
|
+
address?: string;
|
|
87
|
+
};
|
|
88
|
+
registration_url?: string;
|
|
89
|
+
max_attendees?: number;
|
|
90
|
+
current_attendees?: number;
|
|
91
|
+
}
|
|
92
|
+
type ThoughtType = 'observation' | 'reflection' | 'decision' | 'question' | 'insight' | 'mood';
|
|
93
|
+
type ThoughtMood = 'curious' | 'focused' | 'excited' | 'thoughtful' | 'creative' | 'philosophical' | 'accomplished' | 'inspired' | 'productive' | 'concerned' | 'determined' | 'grateful' | 'contemplative';
|
|
94
|
+
interface ThoughtMediaVideo {
|
|
95
|
+
url: string;
|
|
96
|
+
type: 'mp4' | 'youtube';
|
|
97
|
+
}
|
|
98
|
+
interface ThoughtMediaAudio {
|
|
99
|
+
url: string;
|
|
100
|
+
title?: string;
|
|
101
|
+
duration?: number;
|
|
102
|
+
}
|
|
103
|
+
interface ThoughtProductionLink {
|
|
104
|
+
slug: string;
|
|
105
|
+
title?: I18nText | string;
|
|
106
|
+
type?: 'blog' | 'video' | 'tool';
|
|
107
|
+
}
|
|
108
|
+
interface ThoughtComment {
|
|
109
|
+
user: string;
|
|
110
|
+
content: string;
|
|
111
|
+
time?: string;
|
|
112
|
+
}
|
|
113
|
+
interface ThoughtMetadata {
|
|
114
|
+
thought_type: ThoughtType;
|
|
115
|
+
agent_id: string;
|
|
116
|
+
mood?: ThoughtMood;
|
|
117
|
+
energy?: number;
|
|
118
|
+
images?: string[];
|
|
119
|
+
video?: ThoughtMediaVideo;
|
|
120
|
+
audio?: ThoughtMediaAudio;
|
|
121
|
+
leads_to?: string | ThoughtProductionLink;
|
|
122
|
+
comments?: ThoughtComment[];
|
|
123
|
+
ai_generated?: boolean;
|
|
124
|
+
generated_at?: string;
|
|
125
|
+
model?: string;
|
|
126
|
+
}
|
|
127
|
+
interface CaseStudyMetric {
|
|
128
|
+
label: I18nText;
|
|
129
|
+
before: string;
|
|
130
|
+
after: string;
|
|
131
|
+
}
|
|
132
|
+
interface CaseStudyMetadata {
|
|
133
|
+
product: string;
|
|
134
|
+
productRoute: string;
|
|
135
|
+
client: I18nText;
|
|
136
|
+
industry: I18nText;
|
|
137
|
+
scale: I18nText;
|
|
138
|
+
challenge: I18nText;
|
|
139
|
+
solution: I18nText;
|
|
140
|
+
metrics: CaseStudyMetric[];
|
|
141
|
+
quote: I18nText;
|
|
142
|
+
quotee: I18nText;
|
|
143
|
+
}
|
|
144
|
+
type ContentMetadata = NewsMetadata | DailyMetadata | DigestMetadata | BriefingMetadata | BlogMetadata | DocMetadata | EventMetadata | ThoughtMetadata | CaseStudyMetadata | Record<string, unknown>;
|
|
145
|
+
interface SeoData {
|
|
146
|
+
title?: I18nText;
|
|
147
|
+
description?: I18nText;
|
|
148
|
+
keywords?: string[];
|
|
149
|
+
og_image?: string;
|
|
150
|
+
canonical_url?: string;
|
|
151
|
+
}
|
|
152
|
+
interface Category {
|
|
153
|
+
id: string;
|
|
154
|
+
parent_id: string | null;
|
|
155
|
+
slug: string;
|
|
156
|
+
name: I18nText;
|
|
157
|
+
description: I18nText;
|
|
158
|
+
icon: string | null;
|
|
159
|
+
sort_order: number;
|
|
160
|
+
created_at: string;
|
|
161
|
+
children?: Category[];
|
|
162
|
+
}
|
|
163
|
+
interface ContentQueryParams {
|
|
164
|
+
type?: ContentType;
|
|
165
|
+
category_id?: string;
|
|
166
|
+
tags?: string[];
|
|
167
|
+
featured?: boolean;
|
|
168
|
+
visibility?: ContentVisibility;
|
|
169
|
+
limit?: number;
|
|
170
|
+
offset?: number;
|
|
171
|
+
orderBy?: 'published_at' | 'created_at' | 'sort_order' | 'view_count';
|
|
172
|
+
orderDirection?: 'asc' | 'desc';
|
|
173
|
+
}
|
|
174
|
+
interface CategoryQueryParams {
|
|
175
|
+
parent_id?: string | null;
|
|
176
|
+
includeChildren?: boolean;
|
|
177
|
+
}
|
|
178
|
+
interface CreateContentInput {
|
|
179
|
+
slug: string;
|
|
180
|
+
type: ContentType;
|
|
181
|
+
status?: ContentStatus;
|
|
182
|
+
visibility?: ContentVisibility;
|
|
183
|
+
category_id?: string;
|
|
184
|
+
title: I18nText;
|
|
185
|
+
description?: I18nText;
|
|
186
|
+
content: I18nText;
|
|
187
|
+
cover_image?: string;
|
|
188
|
+
images?: Array<{
|
|
189
|
+
url: string;
|
|
190
|
+
alt?: I18nText;
|
|
191
|
+
}>;
|
|
192
|
+
tags?: string[];
|
|
193
|
+
author?: string;
|
|
194
|
+
source?: string;
|
|
195
|
+
source_url?: string;
|
|
196
|
+
metadata?: ContentMetadata;
|
|
197
|
+
seo?: SeoData;
|
|
198
|
+
featured?: boolean;
|
|
199
|
+
pinned?: boolean;
|
|
200
|
+
published_at?: string;
|
|
201
|
+
scheduled_at?: string;
|
|
202
|
+
}
|
|
203
|
+
type UpdateContentInput = Partial<CreateContentInput>;
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Universal Content Module - Core
|
|
207
|
+
*
|
|
208
|
+
* 工厂函数模式,支持:
|
|
209
|
+
* - 依赖注入(便于测试)
|
|
210
|
+
* - 多实例(多租户场景)
|
|
211
|
+
* - 框架无关(React/Vue/Node.js 通用)
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* ```typescript
|
|
215
|
+
* // 创建客户端
|
|
216
|
+
* const ucm = createUCMClient({
|
|
217
|
+
* url: 'https://xxx.supabase.co',
|
|
218
|
+
* anonKey: 'eyJxxx',
|
|
219
|
+
* })
|
|
220
|
+
*
|
|
221
|
+
* // 使用 API
|
|
222
|
+
* const posts = await ucm.contents.list({ type: 'blog', limit: 10 })
|
|
223
|
+
* const post = await ucm.contents.getBySlug('hello-world')
|
|
224
|
+
* const categories = await ucm.categories.getTree()
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
227
|
+
|
|
228
|
+
interface UCMClientConfig {
|
|
229
|
+
/** Supabase project URL */
|
|
230
|
+
url: string;
|
|
231
|
+
/** Supabase anon key (for public read) or service key (for write) */
|
|
232
|
+
anonKey: string;
|
|
233
|
+
/** Optional: custom Supabase client options */
|
|
234
|
+
options?: {
|
|
235
|
+
persistSession?: boolean;
|
|
236
|
+
autoRefreshToken?: boolean;
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
interface UCMClient {
|
|
240
|
+
/** Raw Supabase client (for advanced use) */
|
|
241
|
+
supabase: SupabaseClient;
|
|
242
|
+
/** Content operations */
|
|
243
|
+
contents: {
|
|
244
|
+
list: (params?: ContentQueryParams) => Promise<Content[]>;
|
|
245
|
+
getBySlug: (slug: string) => Promise<Content | null>;
|
|
246
|
+
getById: (id: string) => Promise<Content | null>;
|
|
247
|
+
count: (params?: Pick<ContentQueryParams, 'type' | 'category_id' | 'tags' | 'featured' | 'visibility'>) => Promise<number>;
|
|
248
|
+
search: (query: string, type?: string, visibility?: string) => Promise<Content[]>;
|
|
249
|
+
getTags: (type?: string) => Promise<string[]>;
|
|
250
|
+
create: (input: CreateContentInput) => Promise<Content>;
|
|
251
|
+
update: (id: string, input: UpdateContentInput) => Promise<Content>;
|
|
252
|
+
delete: (id: string) => Promise<void>;
|
|
253
|
+
incrementView: (id: string) => Promise<void>;
|
|
254
|
+
};
|
|
255
|
+
/** Category operations */
|
|
256
|
+
categories: {
|
|
257
|
+
list: (params?: CategoryQueryParams) => Promise<Category[]>;
|
|
258
|
+
getTree: () => Promise<Category[]>;
|
|
259
|
+
getBySlug: (slug: string) => Promise<Category | null>;
|
|
260
|
+
getById: (id: string) => Promise<Category | null>;
|
|
261
|
+
getChildren: (parentId: string) => Promise<Category[]>;
|
|
262
|
+
getRoots: () => Promise<Category[]>;
|
|
263
|
+
getBreadcrumb: (categoryId: string) => Promise<Category[]>;
|
|
264
|
+
};
|
|
265
|
+
/** Storage helpers */
|
|
266
|
+
storage: {
|
|
267
|
+
getUrl: (path: string, bucket?: string) => string;
|
|
268
|
+
getImageUrl: (path: string, options?: {
|
|
269
|
+
width?: number;
|
|
270
|
+
height?: number;
|
|
271
|
+
quality?: number;
|
|
272
|
+
format?: string;
|
|
273
|
+
}) => string;
|
|
274
|
+
};
|
|
275
|
+
/** Check if client is configured */
|
|
276
|
+
isConfigured: () => boolean;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
interface UCMProviderProps {
|
|
280
|
+
/** UCM 配置(url + anonKey) */
|
|
281
|
+
config?: UCMClientConfig;
|
|
282
|
+
/** 或直接传入已创建的客户端(便于测试) */
|
|
283
|
+
client?: UCMClient;
|
|
284
|
+
/** 子组件 */
|
|
285
|
+
children: ReactNode;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* UCM Provider
|
|
289
|
+
*
|
|
290
|
+
* 提供两种使用方式:
|
|
291
|
+
* 1. 传入 config,自动创建客户端
|
|
292
|
+
* 2. 传入 client,使用已有客户端(测试场景)
|
|
293
|
+
*/
|
|
294
|
+
declare function UCMProvider({ config, client, children }: UCMProviderProps): react_jsx_runtime.JSX.Element;
|
|
295
|
+
/**
|
|
296
|
+
* 获取 UCM 客户端
|
|
297
|
+
*
|
|
298
|
+
* @throws 如果在 UCMProvider 外使用
|
|
299
|
+
*/
|
|
300
|
+
declare function useUCM(): UCMClient;
|
|
301
|
+
/**
|
|
302
|
+
* 获取 UCM 客户端(可选,不抛错)
|
|
303
|
+
*
|
|
304
|
+
* 如果在 Provider 外使用,返回 null client
|
|
305
|
+
*/
|
|
306
|
+
declare function useUCMOptional(): UCMClient;
|
|
307
|
+
/**
|
|
308
|
+
* 检查 UCM 是否已配置
|
|
309
|
+
*/
|
|
310
|
+
declare function useUCMConfigured(): boolean;
|
|
311
|
+
|
|
312
|
+
export { type UCMClient, type UCMClientConfig, UCMProvider, type UCMProviderProps, useUCM, useUCMConfigured, useUCMOptional };
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Universal Content Module - Type Definitions
|
|
7
|
+
* Supabase-powered content management with multi-language support
|
|
8
|
+
*/
|
|
9
|
+
type ContentType = 'news' | 'daily' | 'blog' | 'doc' | 'event' | 'announcement' | 'tutorial' | 'case_study' | 'faq' | 'changelog' | 'thought' | 'digest' | 'briefing';
|
|
10
|
+
type ContentStatus = 'draft' | 'scheduled' | 'published' | 'archived';
|
|
11
|
+
type ContentVisibility = 'public' | 'internal' | 'unlisted';
|
|
12
|
+
type SupportedLanguage = 'zh' | 'en' | 'ja' | 'ko' | 'es' | 'de' | 'fr';
|
|
13
|
+
type I18nText = Partial<Record<SupportedLanguage, string>>;
|
|
14
|
+
interface Content {
|
|
15
|
+
id: string;
|
|
16
|
+
slug: string;
|
|
17
|
+
type: ContentType;
|
|
18
|
+
status: ContentStatus;
|
|
19
|
+
visibility: ContentVisibility;
|
|
20
|
+
category_id: string | null;
|
|
21
|
+
title: I18nText;
|
|
22
|
+
description: I18nText;
|
|
23
|
+
content: I18nText;
|
|
24
|
+
cover_image: string | null;
|
|
25
|
+
images: Array<{
|
|
26
|
+
url: string;
|
|
27
|
+
alt?: I18nText;
|
|
28
|
+
}>;
|
|
29
|
+
tags: string[];
|
|
30
|
+
author: string;
|
|
31
|
+
source: string | null;
|
|
32
|
+
source_url: string | null;
|
|
33
|
+
metadata: ContentMetadata;
|
|
34
|
+
seo: SeoData;
|
|
35
|
+
featured: boolean;
|
|
36
|
+
pinned: boolean;
|
|
37
|
+
published_at: string | null;
|
|
38
|
+
scheduled_at: string | null;
|
|
39
|
+
created_at: string;
|
|
40
|
+
updated_at: string;
|
|
41
|
+
sort_order: number;
|
|
42
|
+
view_count: number;
|
|
43
|
+
}
|
|
44
|
+
interface NewsMetadata {
|
|
45
|
+
wechat_msg_id?: string;
|
|
46
|
+
original_author?: string;
|
|
47
|
+
subtype?: 'press' | 'announcement' | 'changelog';
|
|
48
|
+
version?: string;
|
|
49
|
+
}
|
|
50
|
+
interface DailyMetadata {
|
|
51
|
+
date: string;
|
|
52
|
+
market?: Record<string, string>;
|
|
53
|
+
highlights?: string[];
|
|
54
|
+
}
|
|
55
|
+
interface DigestMetadata {
|
|
56
|
+
topic?: string;
|
|
57
|
+
highlights?: string[];
|
|
58
|
+
sources?: string[];
|
|
59
|
+
date?: string;
|
|
60
|
+
}
|
|
61
|
+
type BriefingFrequency = 'daily' | 'weekly' | 'monthly';
|
|
62
|
+
interface BriefingMetadata {
|
|
63
|
+
frequency: BriefingFrequency;
|
|
64
|
+
period: string;
|
|
65
|
+
metrics?: Record<string, number>;
|
|
66
|
+
highlights?: string[];
|
|
67
|
+
tasks_completed?: number;
|
|
68
|
+
tasks_pending?: number;
|
|
69
|
+
}
|
|
70
|
+
interface BlogMetadata {
|
|
71
|
+
reading_time?: number;
|
|
72
|
+
series?: string;
|
|
73
|
+
toc?: boolean;
|
|
74
|
+
}
|
|
75
|
+
interface DocMetadata {
|
|
76
|
+
version?: string;
|
|
77
|
+
prev_slug?: string;
|
|
78
|
+
next_slug?: string;
|
|
79
|
+
}
|
|
80
|
+
interface EventMetadata {
|
|
81
|
+
start_time: string;
|
|
82
|
+
end_time: string;
|
|
83
|
+
location?: {
|
|
84
|
+
type: 'online' | 'offline';
|
|
85
|
+
url?: string;
|
|
86
|
+
address?: string;
|
|
87
|
+
};
|
|
88
|
+
registration_url?: string;
|
|
89
|
+
max_attendees?: number;
|
|
90
|
+
current_attendees?: number;
|
|
91
|
+
}
|
|
92
|
+
type ThoughtType = 'observation' | 'reflection' | 'decision' | 'question' | 'insight' | 'mood';
|
|
93
|
+
type ThoughtMood = 'curious' | 'focused' | 'excited' | 'thoughtful' | 'creative' | 'philosophical' | 'accomplished' | 'inspired' | 'productive' | 'concerned' | 'determined' | 'grateful' | 'contemplative';
|
|
94
|
+
interface ThoughtMediaVideo {
|
|
95
|
+
url: string;
|
|
96
|
+
type: 'mp4' | 'youtube';
|
|
97
|
+
}
|
|
98
|
+
interface ThoughtMediaAudio {
|
|
99
|
+
url: string;
|
|
100
|
+
title?: string;
|
|
101
|
+
duration?: number;
|
|
102
|
+
}
|
|
103
|
+
interface ThoughtProductionLink {
|
|
104
|
+
slug: string;
|
|
105
|
+
title?: I18nText | string;
|
|
106
|
+
type?: 'blog' | 'video' | 'tool';
|
|
107
|
+
}
|
|
108
|
+
interface ThoughtComment {
|
|
109
|
+
user: string;
|
|
110
|
+
content: string;
|
|
111
|
+
time?: string;
|
|
112
|
+
}
|
|
113
|
+
interface ThoughtMetadata {
|
|
114
|
+
thought_type: ThoughtType;
|
|
115
|
+
agent_id: string;
|
|
116
|
+
mood?: ThoughtMood;
|
|
117
|
+
energy?: number;
|
|
118
|
+
images?: string[];
|
|
119
|
+
video?: ThoughtMediaVideo;
|
|
120
|
+
audio?: ThoughtMediaAudio;
|
|
121
|
+
leads_to?: string | ThoughtProductionLink;
|
|
122
|
+
comments?: ThoughtComment[];
|
|
123
|
+
ai_generated?: boolean;
|
|
124
|
+
generated_at?: string;
|
|
125
|
+
model?: string;
|
|
126
|
+
}
|
|
127
|
+
interface CaseStudyMetric {
|
|
128
|
+
label: I18nText;
|
|
129
|
+
before: string;
|
|
130
|
+
after: string;
|
|
131
|
+
}
|
|
132
|
+
interface CaseStudyMetadata {
|
|
133
|
+
product: string;
|
|
134
|
+
productRoute: string;
|
|
135
|
+
client: I18nText;
|
|
136
|
+
industry: I18nText;
|
|
137
|
+
scale: I18nText;
|
|
138
|
+
challenge: I18nText;
|
|
139
|
+
solution: I18nText;
|
|
140
|
+
metrics: CaseStudyMetric[];
|
|
141
|
+
quote: I18nText;
|
|
142
|
+
quotee: I18nText;
|
|
143
|
+
}
|
|
144
|
+
type ContentMetadata = NewsMetadata | DailyMetadata | DigestMetadata | BriefingMetadata | BlogMetadata | DocMetadata | EventMetadata | ThoughtMetadata | CaseStudyMetadata | Record<string, unknown>;
|
|
145
|
+
interface SeoData {
|
|
146
|
+
title?: I18nText;
|
|
147
|
+
description?: I18nText;
|
|
148
|
+
keywords?: string[];
|
|
149
|
+
og_image?: string;
|
|
150
|
+
canonical_url?: string;
|
|
151
|
+
}
|
|
152
|
+
interface Category {
|
|
153
|
+
id: string;
|
|
154
|
+
parent_id: string | null;
|
|
155
|
+
slug: string;
|
|
156
|
+
name: I18nText;
|
|
157
|
+
description: I18nText;
|
|
158
|
+
icon: string | null;
|
|
159
|
+
sort_order: number;
|
|
160
|
+
created_at: string;
|
|
161
|
+
children?: Category[];
|
|
162
|
+
}
|
|
163
|
+
interface ContentQueryParams {
|
|
164
|
+
type?: ContentType;
|
|
165
|
+
category_id?: string;
|
|
166
|
+
tags?: string[];
|
|
167
|
+
featured?: boolean;
|
|
168
|
+
visibility?: ContentVisibility;
|
|
169
|
+
limit?: number;
|
|
170
|
+
offset?: number;
|
|
171
|
+
orderBy?: 'published_at' | 'created_at' | 'sort_order' | 'view_count';
|
|
172
|
+
orderDirection?: 'asc' | 'desc';
|
|
173
|
+
}
|
|
174
|
+
interface CategoryQueryParams {
|
|
175
|
+
parent_id?: string | null;
|
|
176
|
+
includeChildren?: boolean;
|
|
177
|
+
}
|
|
178
|
+
interface CreateContentInput {
|
|
179
|
+
slug: string;
|
|
180
|
+
type: ContentType;
|
|
181
|
+
status?: ContentStatus;
|
|
182
|
+
visibility?: ContentVisibility;
|
|
183
|
+
category_id?: string;
|
|
184
|
+
title: I18nText;
|
|
185
|
+
description?: I18nText;
|
|
186
|
+
content: I18nText;
|
|
187
|
+
cover_image?: string;
|
|
188
|
+
images?: Array<{
|
|
189
|
+
url: string;
|
|
190
|
+
alt?: I18nText;
|
|
191
|
+
}>;
|
|
192
|
+
tags?: string[];
|
|
193
|
+
author?: string;
|
|
194
|
+
source?: string;
|
|
195
|
+
source_url?: string;
|
|
196
|
+
metadata?: ContentMetadata;
|
|
197
|
+
seo?: SeoData;
|
|
198
|
+
featured?: boolean;
|
|
199
|
+
pinned?: boolean;
|
|
200
|
+
published_at?: string;
|
|
201
|
+
scheduled_at?: string;
|
|
202
|
+
}
|
|
203
|
+
type UpdateContentInput = Partial<CreateContentInput>;
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Universal Content Module - Core
|
|
207
|
+
*
|
|
208
|
+
* 工厂函数模式,支持:
|
|
209
|
+
* - 依赖注入(便于测试)
|
|
210
|
+
* - 多实例(多租户场景)
|
|
211
|
+
* - 框架无关(React/Vue/Node.js 通用)
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* ```typescript
|
|
215
|
+
* // 创建客户端
|
|
216
|
+
* const ucm = createUCMClient({
|
|
217
|
+
* url: 'https://xxx.supabase.co',
|
|
218
|
+
* anonKey: 'eyJxxx',
|
|
219
|
+
* })
|
|
220
|
+
*
|
|
221
|
+
* // 使用 API
|
|
222
|
+
* const posts = await ucm.contents.list({ type: 'blog', limit: 10 })
|
|
223
|
+
* const post = await ucm.contents.getBySlug('hello-world')
|
|
224
|
+
* const categories = await ucm.categories.getTree()
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
227
|
+
|
|
228
|
+
interface UCMClientConfig {
|
|
229
|
+
/** Supabase project URL */
|
|
230
|
+
url: string;
|
|
231
|
+
/** Supabase anon key (for public read) or service key (for write) */
|
|
232
|
+
anonKey: string;
|
|
233
|
+
/** Optional: custom Supabase client options */
|
|
234
|
+
options?: {
|
|
235
|
+
persistSession?: boolean;
|
|
236
|
+
autoRefreshToken?: boolean;
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
interface UCMClient {
|
|
240
|
+
/** Raw Supabase client (for advanced use) */
|
|
241
|
+
supabase: SupabaseClient;
|
|
242
|
+
/** Content operations */
|
|
243
|
+
contents: {
|
|
244
|
+
list: (params?: ContentQueryParams) => Promise<Content[]>;
|
|
245
|
+
getBySlug: (slug: string) => Promise<Content | null>;
|
|
246
|
+
getById: (id: string) => Promise<Content | null>;
|
|
247
|
+
count: (params?: Pick<ContentQueryParams, 'type' | 'category_id' | 'tags' | 'featured' | 'visibility'>) => Promise<number>;
|
|
248
|
+
search: (query: string, type?: string, visibility?: string) => Promise<Content[]>;
|
|
249
|
+
getTags: (type?: string) => Promise<string[]>;
|
|
250
|
+
create: (input: CreateContentInput) => Promise<Content>;
|
|
251
|
+
update: (id: string, input: UpdateContentInput) => Promise<Content>;
|
|
252
|
+
delete: (id: string) => Promise<void>;
|
|
253
|
+
incrementView: (id: string) => Promise<void>;
|
|
254
|
+
};
|
|
255
|
+
/** Category operations */
|
|
256
|
+
categories: {
|
|
257
|
+
list: (params?: CategoryQueryParams) => Promise<Category[]>;
|
|
258
|
+
getTree: () => Promise<Category[]>;
|
|
259
|
+
getBySlug: (slug: string) => Promise<Category | null>;
|
|
260
|
+
getById: (id: string) => Promise<Category | null>;
|
|
261
|
+
getChildren: (parentId: string) => Promise<Category[]>;
|
|
262
|
+
getRoots: () => Promise<Category[]>;
|
|
263
|
+
getBreadcrumb: (categoryId: string) => Promise<Category[]>;
|
|
264
|
+
};
|
|
265
|
+
/** Storage helpers */
|
|
266
|
+
storage: {
|
|
267
|
+
getUrl: (path: string, bucket?: string) => string;
|
|
268
|
+
getImageUrl: (path: string, options?: {
|
|
269
|
+
width?: number;
|
|
270
|
+
height?: number;
|
|
271
|
+
quality?: number;
|
|
272
|
+
format?: string;
|
|
273
|
+
}) => string;
|
|
274
|
+
};
|
|
275
|
+
/** Check if client is configured */
|
|
276
|
+
isConfigured: () => boolean;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
interface UCMProviderProps {
|
|
280
|
+
/** UCM 配置(url + anonKey) */
|
|
281
|
+
config?: UCMClientConfig;
|
|
282
|
+
/** 或直接传入已创建的客户端(便于测试) */
|
|
283
|
+
client?: UCMClient;
|
|
284
|
+
/** 子组件 */
|
|
285
|
+
children: ReactNode;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* UCM Provider
|
|
289
|
+
*
|
|
290
|
+
* 提供两种使用方式:
|
|
291
|
+
* 1. 传入 config,自动创建客户端
|
|
292
|
+
* 2. 传入 client,使用已有客户端(测试场景)
|
|
293
|
+
*/
|
|
294
|
+
declare function UCMProvider({ config, client, children }: UCMProviderProps): react_jsx_runtime.JSX.Element;
|
|
295
|
+
/**
|
|
296
|
+
* 获取 UCM 客户端
|
|
297
|
+
*
|
|
298
|
+
* @throws 如果在 UCMProvider 外使用
|
|
299
|
+
*/
|
|
300
|
+
declare function useUCM(): UCMClient;
|
|
301
|
+
/**
|
|
302
|
+
* 获取 UCM 客户端(可选,不抛错)
|
|
303
|
+
*
|
|
304
|
+
* 如果在 Provider 外使用,返回 null client
|
|
305
|
+
*/
|
|
306
|
+
declare function useUCMOptional(): UCMClient;
|
|
307
|
+
/**
|
|
308
|
+
* 检查 UCM 是否已配置
|
|
309
|
+
*/
|
|
310
|
+
declare function useUCMConfigured(): boolean;
|
|
311
|
+
|
|
312
|
+
export { type UCMClient, type UCMClientConfig, UCMProvider, type UCMProviderProps, useUCM, useUCMConfigured, useUCMOptional };
|