@flightdev/cms 0.2.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.
@@ -0,0 +1,221 @@
1
+ /**
2
+ * @flightdev/cms
3
+ *
4
+ * Unified CMS adapters for Flight Framework.
5
+ * One API to rule them all - Strapi, Contentful, Sanity, and more.
6
+ *
7
+ * Philosophy: Flight doesn't impose - you choose your CMS.
8
+ * All adapters are optional, swap providers without code changes.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { createCMS } from '@flightdev/cms';
13
+ * import { strapi } from '@flightdev/cms/strapi';
14
+ *
15
+ * const cms = createCMS(strapi({
16
+ * url: process.env.STRAPI_URL,
17
+ * token: process.env.STRAPI_TOKEN,
18
+ * }));
19
+ *
20
+ * const posts = await cms.findMany('posts', {
21
+ * limit: 10,
22
+ * populate: ['author', 'cover'],
23
+ * });
24
+ * ```
25
+ */
26
+ /**
27
+ * Options for finding a single entity.
28
+ */
29
+ interface FindOneOptions {
30
+ /** Filter conditions */
31
+ where?: Record<string, unknown>;
32
+ /** Relations to populate */
33
+ populate?: string[] | Record<string, PopulateOptions>;
34
+ /** Locale for i18n content */
35
+ locale?: string;
36
+ /** Select specific fields */
37
+ fields?: string[];
38
+ /** Enable draft/preview mode */
39
+ preview?: boolean;
40
+ }
41
+ /**
42
+ * Nested populate configuration.
43
+ */
44
+ interface PopulateOptions {
45
+ fields?: string[];
46
+ populate?: string[] | Record<string, PopulateOptions>;
47
+ }
48
+ /**
49
+ * Options for finding multiple entities.
50
+ */
51
+ interface FindManyOptions extends FindOneOptions {
52
+ /** Maximum number of results */
53
+ limit?: number;
54
+ /** Offset for pagination */
55
+ offset?: number;
56
+ /** Page number (alternative to offset) */
57
+ page?: number;
58
+ /** Page size (used with page) */
59
+ pageSize?: number;
60
+ /** Sort order */
61
+ sort?: Record<string, 'asc' | 'desc'> | string[];
62
+ }
63
+ /**
64
+ * Paginated result from CMS queries.
65
+ */
66
+ interface CMSResult<T> {
67
+ /** Array of entities */
68
+ data: T[];
69
+ /** Pagination metadata */
70
+ meta: {
71
+ total: number;
72
+ page: number;
73
+ pageSize: number;
74
+ pageCount: number;
75
+ };
76
+ }
77
+ /**
78
+ * CMS adapter interface.
79
+ * All CMS adapters must implement this interface.
80
+ */
81
+ interface CMSAdapter {
82
+ /** Adapter name for identification */
83
+ name: string;
84
+ /**
85
+ * Find a single entity by ID or filter.
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * // By ID
90
+ * const post = await cms.findOne('posts', { where: { id: '123' } });
91
+ *
92
+ * // By slug
93
+ * const post = await cms.findOne('posts', { where: { slug: 'hello-world' } });
94
+ * ```
95
+ */
96
+ findOne<T = unknown>(collection: string, options?: FindOneOptions): Promise<T | null>;
97
+ /**
98
+ * Find multiple entities with pagination.
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * const { data, meta } = await cms.findMany('posts', {
103
+ * limit: 10,
104
+ * sort: { publishedAt: 'desc' },
105
+ * populate: ['author'],
106
+ * });
107
+ * ```
108
+ */
109
+ findMany<T = unknown>(collection: string, options?: FindManyOptions): Promise<CMSResult<T>>;
110
+ /**
111
+ * Find entity by unique identifier.
112
+ *
113
+ * @example
114
+ * ```typescript
115
+ * const post = await cms.findById('posts', '123');
116
+ * ```
117
+ */
118
+ findById<T = unknown>(collection: string, id: string | number, options?: Omit<FindOneOptions, 'where'>): Promise<T | null>;
119
+ /**
120
+ * Create a new entity.
121
+ * Only available if the adapter supports write operations.
122
+ *
123
+ * @example
124
+ * ```typescript
125
+ * const post = await cms.create('posts', {
126
+ * title: 'New Post',
127
+ * content: 'Hello world',
128
+ * });
129
+ * ```
130
+ */
131
+ create?<T = unknown>(collection: string, data: Partial<T>): Promise<T>;
132
+ /**
133
+ * Update an existing entity.
134
+ *
135
+ * @example
136
+ * ```typescript
137
+ * const updated = await cms.update('posts', '123', {
138
+ * title: 'Updated Title',
139
+ * });
140
+ * ```
141
+ */
142
+ update?<T = unknown>(collection: string, id: string | number, data: Partial<T>): Promise<T>;
143
+ /**
144
+ * Delete an entity.
145
+ *
146
+ * @example
147
+ * ```typescript
148
+ * await cms.delete('posts', '123');
149
+ * ```
150
+ */
151
+ delete?(collection: string, id: string | number): Promise<void>;
152
+ /**
153
+ * Get raw client for advanced operations.
154
+ * Type depends on the adapter.
155
+ */
156
+ getClient?(): unknown;
157
+ }
158
+ /**
159
+ * CMS instance with fully typed adapter methods.
160
+ */
161
+ interface CMSInstance extends CMSAdapter {
162
+ /** The underlying adapter */
163
+ adapter: CMSAdapter;
164
+ }
165
+ /**
166
+ * Create a CMS instance from an adapter.
167
+ *
168
+ * @param adapter - CMS adapter (strapi, contentful, sanity, etc.)
169
+ * @returns CMS instance with unified API
170
+ *
171
+ * @example
172
+ * ```typescript
173
+ * import { createCMS } from '@flightdev/cms';
174
+ * import { strapi } from '@flightdev/cms/strapi';
175
+ *
176
+ * const cms = createCMS(strapi({
177
+ * url: 'http://localhost:1337',
178
+ * token: 'your-api-token',
179
+ * }));
180
+ *
181
+ * // Same API regardless of CMS
182
+ * const posts = await cms.findMany('posts');
183
+ * ```
184
+ */
185
+ declare function createCMS(adapter: CMSAdapter): CMSInstance;
186
+ /**
187
+ * Type helper for CMS entities.
188
+ */
189
+ type CMSEntity<T = Record<string, unknown>> = T & {
190
+ id: string | number;
191
+ createdAt?: string;
192
+ updatedAt?: string;
193
+ };
194
+ /**
195
+ * Type helper for media/asset fields.
196
+ */
197
+ interface CMSMedia {
198
+ id: string | number;
199
+ url: string;
200
+ alternativeText?: string;
201
+ caption?: string;
202
+ width?: number;
203
+ height?: number;
204
+ formats?: Record<string, {
205
+ url: string;
206
+ width: number;
207
+ height: number;
208
+ }>;
209
+ mime?: string;
210
+ size?: number;
211
+ }
212
+ /**
213
+ * Helper to check if adapter supports write operations.
214
+ */
215
+ declare function isWritable(cms: CMSInstance): boolean;
216
+ /**
217
+ * Helper to build where clauses from filters.
218
+ */
219
+ declare function buildWhere(filters: Record<string, unknown>): Record<string, unknown>;
220
+
221
+ export { type CMSAdapter as Adapter, type CMSAdapter, type CMSEntity, type CMSInstance, type CMSMedia, type CMSResult, type FindManyOptions, type FindOneOptions, type CMSInstance as Instance, type PopulateOptions, type CMSResult as Result, buildWhere, createCMS, isWritable };
package/dist/index.js ADDED
@@ -0,0 +1,29 @@
1
+ // src/index.ts
2
+ function createCMS(adapter) {
3
+ return {
4
+ name: adapter.name,
5
+ adapter,
6
+ findOne: adapter.findOne.bind(adapter),
7
+ findMany: adapter.findMany.bind(adapter),
8
+ findById: adapter.findById.bind(adapter),
9
+ create: adapter.create?.bind(adapter),
10
+ update: adapter.update?.bind(adapter),
11
+ delete: adapter.delete?.bind(adapter),
12
+ getClient: adapter.getClient?.bind(adapter)
13
+ };
14
+ }
15
+ function isWritable(cms) {
16
+ return typeof cms.create === "function";
17
+ }
18
+ function buildWhere(filters) {
19
+ return Object.entries(filters).reduce((acc, [key, value]) => {
20
+ if (value !== void 0 && value !== null) {
21
+ acc[key] = value;
22
+ }
23
+ return acc;
24
+ }, {});
25
+ }
26
+
27
+ export { buildWhere, createCMS, isWritable };
28
+ //# sourceMappingURL=index.js.map
29
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";AA+NO,SAAS,UAAU,OAAA,EAAkC;AACxD,EAAA,OAAO;AAAA,IACH,MAAM,OAAA,CAAQ,IAAA;AAAA,IACd,OAAA;AAAA,IAEA,OAAA,EAAS,OAAA,CAAQ,OAAA,CAAQ,IAAA,CAAK,OAAO,CAAA;AAAA,IACrC,QAAA,EAAU,OAAA,CAAQ,QAAA,CAAS,IAAA,CAAK,OAAO,CAAA;AAAA,IACvC,QAAA,EAAU,OAAA,CAAQ,QAAA,CAAS,IAAA,CAAK,OAAO,CAAA;AAAA,IACvC,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAQ,IAAA,CAAK,OAAO,CAAA;AAAA,IACpC,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAQ,IAAA,CAAK,OAAO,CAAA;AAAA,IACpC,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAQ,IAAA,CAAK,OAAO,CAAA;AAAA,IACpC,SAAA,EAAW,OAAA,CAAQ,SAAA,EAAW,IAAA,CAAK,OAAO;AAAA,GAC9C;AACJ;AAqCO,SAAS,WAAW,GAAA,EAA2B;AAClD,EAAA,OAAO,OAAO,IAAI,MAAA,KAAW,UAAA;AACjC;AAKO,SAAS,WACZ,OAAA,EACuB;AACvB,EAAA,OAAO,MAAA,CAAO,OAAA,CAAQ,OAAO,CAAA,CAAE,MAAA,CAAO,CAAC,GAAA,EAAK,CAAC,GAAA,EAAK,KAAK,CAAA,KAAM;AACzD,IAAA,IAAI,KAAA,KAAU,MAAA,IAAa,KAAA,KAAU,IAAA,EAAM;AACvC,MAAA,GAAA,CAAI,GAAG,CAAA,GAAI,KAAA;AAAA,IACf;AACA,IAAA,OAAO,GAAA;AAAA,EACX,CAAA,EAAG,EAA6B,CAAA;AACpC","file":"index.js","sourcesContent":["/**\r\n * @flightdev/cms\r\n * \r\n * Unified CMS adapters for Flight Framework.\r\n * One API to rule them all - Strapi, Contentful, Sanity, and more.\r\n * \r\n * Philosophy: Flight doesn't impose - you choose your CMS.\r\n * All adapters are optional, swap providers without code changes.\r\n * \r\n * @example\r\n * ```typescript\r\n * import { createCMS } from '@flightdev/cms';\r\n * import { strapi } from '@flightdev/cms/strapi';\r\n * \r\n * const cms = createCMS(strapi({\r\n * url: process.env.STRAPI_URL,\r\n * token: process.env.STRAPI_TOKEN,\r\n * }));\r\n * \r\n * const posts = await cms.findMany('posts', {\r\n * limit: 10,\r\n * populate: ['author', 'cover'],\r\n * });\r\n * ```\r\n */\r\n\r\n// =============================================================================\r\n// Types\r\n// =============================================================================\r\n\r\n/**\r\n * Options for finding a single entity.\r\n */\r\nexport interface FindOneOptions {\r\n /** Filter conditions */\r\n where?: Record<string, unknown>;\r\n /** Relations to populate */\r\n populate?: string[] | Record<string, PopulateOptions>;\r\n /** Locale for i18n content */\r\n locale?: string;\r\n /** Select specific fields */\r\n fields?: string[];\r\n /** Enable draft/preview mode */\r\n preview?: boolean;\r\n}\r\n\r\n/**\r\n * Nested populate configuration.\r\n */\r\nexport interface PopulateOptions {\r\n fields?: string[];\r\n populate?: string[] | Record<string, PopulateOptions>;\r\n}\r\n\r\n/**\r\n * Options for finding multiple entities.\r\n */\r\nexport interface FindManyOptions extends FindOneOptions {\r\n /** Maximum number of results */\r\n limit?: number;\r\n /** Offset for pagination */\r\n offset?: number;\r\n /** Page number (alternative to offset) */\r\n page?: number;\r\n /** Page size (used with page) */\r\n pageSize?: number;\r\n /** Sort order */\r\n sort?: Record<string, 'asc' | 'desc'> | string[];\r\n}\r\n\r\n/**\r\n * Paginated result from CMS queries.\r\n */\r\nexport interface CMSResult<T> {\r\n /** Array of entities */\r\n data: T[];\r\n /** Pagination metadata */\r\n meta: {\r\n total: number;\r\n page: number;\r\n pageSize: number;\r\n pageCount: number;\r\n };\r\n}\r\n\r\n/**\r\n * CMS adapter interface.\r\n * All CMS adapters must implement this interface.\r\n */\r\nexport interface CMSAdapter {\r\n /** Adapter name for identification */\r\n name: string;\r\n\r\n /**\r\n * Find a single entity by ID or filter.\r\n * \r\n * @example\r\n * ```typescript\r\n * // By ID\r\n * const post = await cms.findOne('posts', { where: { id: '123' } });\r\n * \r\n * // By slug\r\n * const post = await cms.findOne('posts', { where: { slug: 'hello-world' } });\r\n * ```\r\n */\r\n findOne<T = unknown>(\r\n collection: string,\r\n options?: FindOneOptions\r\n ): Promise<T | null>;\r\n\r\n /**\r\n * Find multiple entities with pagination.\r\n * \r\n * @example\r\n * ```typescript\r\n * const { data, meta } = await cms.findMany('posts', {\r\n * limit: 10,\r\n * sort: { publishedAt: 'desc' },\r\n * populate: ['author'],\r\n * });\r\n * ```\r\n */\r\n findMany<T = unknown>(\r\n collection: string,\r\n options?: FindManyOptions\r\n ): Promise<CMSResult<T>>;\r\n\r\n /**\r\n * Find entity by unique identifier.\r\n * \r\n * @example\r\n * ```typescript\r\n * const post = await cms.findById('posts', '123');\r\n * ```\r\n */\r\n findById<T = unknown>(\r\n collection: string,\r\n id: string | number,\r\n options?: Omit<FindOneOptions, 'where'>\r\n ): Promise<T | null>;\r\n\r\n /**\r\n * Create a new entity.\r\n * Only available if the adapter supports write operations.\r\n * \r\n * @example\r\n * ```typescript\r\n * const post = await cms.create('posts', {\r\n * title: 'New Post',\r\n * content: 'Hello world',\r\n * });\r\n * ```\r\n */\r\n create?<T = unknown>(\r\n collection: string,\r\n data: Partial<T>\r\n ): Promise<T>;\r\n\r\n /**\r\n * Update an existing entity.\r\n * \r\n * @example\r\n * ```typescript\r\n * const updated = await cms.update('posts', '123', {\r\n * title: 'Updated Title',\r\n * });\r\n * ```\r\n */\r\n update?<T = unknown>(\r\n collection: string,\r\n id: string | number,\r\n data: Partial<T>\r\n ): Promise<T>;\r\n\r\n /**\r\n * Delete an entity.\r\n * \r\n * @example\r\n * ```typescript\r\n * await cms.delete('posts', '123');\r\n * ```\r\n */\r\n delete?(collection: string, id: string | number): Promise<void>;\r\n\r\n /**\r\n * Get raw client for advanced operations.\r\n * Type depends on the adapter.\r\n */\r\n getClient?(): unknown;\r\n}\r\n\r\n/**\r\n * CMS instance with fully typed adapter methods.\r\n */\r\nexport interface CMSInstance extends CMSAdapter {\r\n /** The underlying adapter */\r\n adapter: CMSAdapter;\r\n}\r\n\r\n// =============================================================================\r\n// Factory\r\n// =============================================================================\r\n\r\n/**\r\n * Create a CMS instance from an adapter.\r\n * \r\n * @param adapter - CMS adapter (strapi, contentful, sanity, etc.)\r\n * @returns CMS instance with unified API\r\n * \r\n * @example\r\n * ```typescript\r\n * import { createCMS } from '@flightdev/cms';\r\n * import { strapi } from '@flightdev/cms/strapi';\r\n * \r\n * const cms = createCMS(strapi({\r\n * url: 'http://localhost:1337',\r\n * token: 'your-api-token',\r\n * }));\r\n * \r\n * // Same API regardless of CMS\r\n * const posts = await cms.findMany('posts');\r\n * ```\r\n */\r\nexport function createCMS(adapter: CMSAdapter): CMSInstance {\r\n return {\r\n name: adapter.name,\r\n adapter,\r\n\r\n findOne: adapter.findOne.bind(adapter),\r\n findMany: adapter.findMany.bind(adapter),\r\n findById: adapter.findById.bind(adapter),\r\n create: adapter.create?.bind(adapter),\r\n update: adapter.update?.bind(adapter),\r\n delete: adapter.delete?.bind(adapter),\r\n getClient: adapter.getClient?.bind(adapter),\r\n };\r\n}\r\n\r\n// =============================================================================\r\n// Utilities\r\n// =============================================================================\r\n\r\n/**\r\n * Type helper for CMS entities.\r\n */\r\nexport type CMSEntity<T = Record<string, unknown>> = T & {\r\n id: string | number;\r\n createdAt?: string;\r\n updatedAt?: string;\r\n};\r\n\r\n/**\r\n * Type helper for media/asset fields.\r\n */\r\nexport interface CMSMedia {\r\n id: string | number;\r\n url: string;\r\n alternativeText?: string;\r\n caption?: string;\r\n width?: number;\r\n height?: number;\r\n formats?: Record<string, {\r\n url: string;\r\n width: number;\r\n height: number;\r\n }>;\r\n mime?: string;\r\n size?: number;\r\n}\r\n\r\n/**\r\n * Helper to check if adapter supports write operations.\r\n */\r\nexport function isWritable(cms: CMSInstance): boolean {\r\n return typeof cms.create === 'function';\r\n}\r\n\r\n/**\r\n * Helper to build where clauses from filters.\r\n */\r\nexport function buildWhere(\r\n filters: Record<string, unknown>\r\n): Record<string, unknown> {\r\n return Object.entries(filters).reduce((acc, [key, value]) => {\r\n if (value !== undefined && value !== null) {\r\n acc[key] = value;\r\n }\r\n return acc;\r\n }, {} as Record<string, unknown>);\r\n}\r\n\r\n// Re-export types for convenience\r\nexport type {\r\n CMSAdapter as Adapter,\r\n CMSResult as Result,\r\n CMSInstance as Instance,\r\n};\r\n"]}
@@ -0,0 +1,165 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { Instance as CMSInstance, FindOneOptions, FindManyOptions, Result as CMSResult } from '../index.js';
4
+
5
+ interface CMSContextValue {
6
+ cms: CMSInstance;
7
+ }
8
+ /**
9
+ * CMS Provider component.
10
+ * Provides CMS instance to all child components.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * <CMSProvider cms={cms}>
15
+ * <App />
16
+ * </CMSProvider>
17
+ * ```
18
+ */
19
+ declare function CMSProvider({ cms, children, }: {
20
+ cms: CMSInstance;
21
+ children: ReactNode;
22
+ }): react.FunctionComponentElement<react.ProviderProps<CMSContextValue | null>>;
23
+ /**
24
+ * Get CMS instance from context.
25
+ * Must be used within a CMSProvider.
26
+ */
27
+ declare function useCMS(): CMSInstance;
28
+ interface QueryState<T> {
29
+ data: T | null;
30
+ loading: boolean;
31
+ error: Error | null;
32
+ }
33
+ interface QueryResult<T> extends QueryState<T> {
34
+ refetch: () => Promise<void>;
35
+ }
36
+ interface ListQueryResult<T> extends Omit<QueryResult<T[]>, 'data'> {
37
+ data: T[];
38
+ meta: CMSResult<T>['meta'] | null;
39
+ }
40
+ /**
41
+ * Query a single entity from the CMS.
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * function PostPage({ slug }) {
46
+ * const { data: post, loading, error } = useCMSOne('posts', {
47
+ * where: { slug },
48
+ * populate: ['author'],
49
+ * });
50
+ *
51
+ * if (loading) return <Skeleton />;
52
+ * if (error || !post) return <NotFound />;
53
+ *
54
+ * return <Post post={post} />;
55
+ * }
56
+ * ```
57
+ */
58
+ declare function useCMSOne<T = unknown>(collection: string, options?: FindOneOptions): QueryResult<T>;
59
+ /**
60
+ * Query multiple entities from the CMS with pagination.
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * function PostList() {
65
+ * const { data: posts, meta, loading, refetch } = useCMSQuery('posts', {
66
+ * limit: 10,
67
+ * sort: { publishedAt: 'desc' },
68
+ * populate: ['author', 'cover'],
69
+ * });
70
+ *
71
+ * return (
72
+ * <div>
73
+ * {posts.map(post => <PostCard key={post.id} post={post} />)}
74
+ * {meta && <p>Total: {meta.total}</p>}
75
+ * <button onClick={refetch}>Refresh</button>
76
+ * </div>
77
+ * );
78
+ * }
79
+ * ```
80
+ */
81
+ declare function useCMSQuery<T = unknown>(collection: string, options?: FindManyOptions): ListQueryResult<T>;
82
+ /**
83
+ * Query an entity by ID.
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * function PostPage({ id }) {
88
+ * const { data: post, loading } = useCMSById('posts', id, {
89
+ * populate: ['author'],
90
+ * });
91
+ *
92
+ * if (loading) return <Skeleton />;
93
+ * return <Post post={post} />;
94
+ * }
95
+ * ```
96
+ */
97
+ declare function useCMSById<T = unknown>(collection: string, id: string | number, options?: Omit<FindOneOptions, 'where'>): QueryResult<T>;
98
+ interface MutationState<T> {
99
+ data: T | null;
100
+ loading: boolean;
101
+ error: Error | null;
102
+ }
103
+ interface MutationResult<T, TInput> extends MutationState<T> {
104
+ mutate: (input: TInput) => Promise<T>;
105
+ reset: () => void;
106
+ }
107
+ /**
108
+ * Create a new entity in the CMS.
109
+ *
110
+ * @example
111
+ * ```typescript
112
+ * function CreatePostForm() {
113
+ * const { mutate, loading, error } = useCMSCreate<Post>('posts');
114
+ *
115
+ * const handleSubmit = async (data) => {
116
+ * const post = await mutate(data);
117
+ * router.push(`/posts/${post.id}`);
118
+ * };
119
+ *
120
+ * return <form onSubmit={handleSubmit}>...</form>;
121
+ * }
122
+ * ```
123
+ */
124
+ declare function useCMSCreate<T = unknown, TInput = Partial<T>>(collection: string): MutationResult<T, TInput>;
125
+ /**
126
+ * Update an entity in the CMS.
127
+ *
128
+ * @example
129
+ * ```typescript
130
+ * function EditPostForm({ post }) {
131
+ * const { mutate, loading } = useCMSUpdate<Post>('posts');
132
+ *
133
+ * const handleSubmit = async (data) => {
134
+ * await mutate(post.id, data);
135
+ * toast('Post updated!');
136
+ * };
137
+ *
138
+ * return <form onSubmit={handleSubmit}>...</form>;
139
+ * }
140
+ * ```
141
+ */
142
+ declare function useCMSUpdate<T = unknown, TInput = Partial<T>>(collection: string): MutationResult<T, {
143
+ id: string | number;
144
+ data: TInput;
145
+ }>;
146
+ /**
147
+ * Delete an entity from the CMS.
148
+ *
149
+ * @example
150
+ * ```typescript
151
+ * function DeleteButton({ post }) {
152
+ * const { mutate, loading } = useCMSDelete('posts');
153
+ *
154
+ * const handleDelete = async () => {
155
+ * await mutate(post.id);
156
+ * router.push('/posts');
157
+ * };
158
+ *
159
+ * return <button onClick={handleDelete} disabled={loading}>Delete</button>;
160
+ * }
161
+ * ```
162
+ */
163
+ declare function useCMSDelete(collection: string): MutationResult<void, string | number>;
164
+
165
+ export { CMSProvider, useCMS, useCMSById, useCMSCreate, useCMSDelete, useCMSOne, useCMSQuery, useCMSUpdate };
@@ -0,0 +1,175 @@
1
+ import { createContext, useMemo, createElement, useContext, useState, useCallback, useEffect } from 'react';
2
+
3
+ // src/react/index.ts
4
+ var CMSContext = createContext(null);
5
+ function CMSProvider({
6
+ cms,
7
+ children
8
+ }) {
9
+ const value = useMemo(() => ({ cms }), [cms]);
10
+ return createElement(CMSContext.Provider, { value }, children);
11
+ }
12
+ function useCMS() {
13
+ const context = useContext(CMSContext);
14
+ if (!context) {
15
+ throw new Error("useCMS must be used within a CMSProvider");
16
+ }
17
+ return context.cms;
18
+ }
19
+ function useCMSOne(collection, options) {
20
+ const cms = useCMS();
21
+ const [state, setState] = useState({
22
+ data: null,
23
+ loading: true,
24
+ error: null
25
+ });
26
+ const optionsKey = JSON.stringify(options);
27
+ const fetch = useCallback(async () => {
28
+ setState((prev) => ({ ...prev, loading: true, error: null }));
29
+ try {
30
+ const data = await cms.findOne(collection, options);
31
+ setState({ data, loading: false, error: null });
32
+ } catch (error) {
33
+ setState({ data: null, loading: false, error });
34
+ }
35
+ }, [cms, collection, optionsKey]);
36
+ useEffect(() => {
37
+ fetch();
38
+ }, [fetch]);
39
+ return { ...state, refetch: fetch };
40
+ }
41
+ function useCMSQuery(collection, options) {
42
+ const cms = useCMS();
43
+ const [state, setState] = useState({
44
+ data: [],
45
+ meta: null,
46
+ loading: true,
47
+ error: null
48
+ });
49
+ const optionsKey = JSON.stringify(options);
50
+ const fetch = useCallback(async () => {
51
+ setState((prev) => ({ ...prev, loading: true, error: null }));
52
+ try {
53
+ const result = await cms.findMany(collection, options);
54
+ setState({
55
+ data: result.data,
56
+ meta: result.meta,
57
+ loading: false,
58
+ error: null
59
+ });
60
+ } catch (error) {
61
+ setState((prev) => ({
62
+ ...prev,
63
+ loading: false,
64
+ error
65
+ }));
66
+ }
67
+ }, [cms, collection, optionsKey]);
68
+ useEffect(() => {
69
+ fetch();
70
+ }, [fetch]);
71
+ return { ...state, refetch: fetch };
72
+ }
73
+ function useCMSById(collection, id, options) {
74
+ const cms = useCMS();
75
+ const [state, setState] = useState({
76
+ data: null,
77
+ loading: true,
78
+ error: null
79
+ });
80
+ const optionsKey = JSON.stringify(options);
81
+ const fetch = useCallback(async () => {
82
+ setState((prev) => ({ ...prev, loading: true, error: null }));
83
+ try {
84
+ const data = await cms.findById(collection, id, options);
85
+ setState({ data, loading: false, error: null });
86
+ } catch (error) {
87
+ setState({ data: null, loading: false, error });
88
+ }
89
+ }, [cms, collection, id, optionsKey]);
90
+ useEffect(() => {
91
+ fetch();
92
+ }, [fetch]);
93
+ return { ...state, refetch: fetch };
94
+ }
95
+ function useCMSCreate(collection) {
96
+ const cms = useCMS();
97
+ const [state, setState] = useState({
98
+ data: null,
99
+ loading: false,
100
+ error: null
101
+ });
102
+ const mutate = useCallback(async (input) => {
103
+ if (!cms.create) {
104
+ throw new Error("CMS adapter does not support create operations");
105
+ }
106
+ setState({ data: null, loading: true, error: null });
107
+ try {
108
+ const data = await cms.create(collection, input);
109
+ setState({ data, loading: false, error: null });
110
+ return data;
111
+ } catch (error) {
112
+ setState({ data: null, loading: false, error });
113
+ throw error;
114
+ }
115
+ }, [cms, collection]);
116
+ const reset = useCallback(() => {
117
+ setState({ data: null, loading: false, error: null });
118
+ }, []);
119
+ return { ...state, mutate, reset };
120
+ }
121
+ function useCMSUpdate(collection) {
122
+ const cms = useCMS();
123
+ const [state, setState] = useState({
124
+ data: null,
125
+ loading: false,
126
+ error: null
127
+ });
128
+ const mutate = useCallback(async ({ id, data }) => {
129
+ if (!cms.update) {
130
+ throw new Error("CMS adapter does not support update operations");
131
+ }
132
+ setState({ data: null, loading: true, error: null });
133
+ try {
134
+ const result = await cms.update(collection, id, data);
135
+ setState({ data: result, loading: false, error: null });
136
+ return result;
137
+ } catch (error) {
138
+ setState({ data: null, loading: false, error });
139
+ throw error;
140
+ }
141
+ }, [cms, collection]);
142
+ const reset = useCallback(() => {
143
+ setState({ data: null, loading: false, error: null });
144
+ }, []);
145
+ return { ...state, mutate, reset };
146
+ }
147
+ function useCMSDelete(collection) {
148
+ const cms = useCMS();
149
+ const [state, setState] = useState({
150
+ data: null,
151
+ loading: false,
152
+ error: null
153
+ });
154
+ const mutate = useCallback(async (id) => {
155
+ if (!cms.delete) {
156
+ throw new Error("CMS adapter does not support delete operations");
157
+ }
158
+ setState({ data: null, loading: true, error: null });
159
+ try {
160
+ await cms.delete(collection, id);
161
+ setState({ data: void 0, loading: false, error: null });
162
+ } catch (error) {
163
+ setState({ data: null, loading: false, error });
164
+ throw error;
165
+ }
166
+ }, [cms, collection]);
167
+ const reset = useCallback(() => {
168
+ setState({ data: null, loading: false, error: null });
169
+ }, []);
170
+ return { ...state, mutate, reset };
171
+ }
172
+
173
+ export { CMSProvider, useCMS, useCMSById, useCMSCreate, useCMSDelete, useCMSOne, useCMSQuery, useCMSUpdate };
174
+ //# sourceMappingURL=index.js.map
175
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * React hooks for @flightdev/cms\r\n * \r\n * @example\r\n * ```typescript\r\n * import { CMSProvider, useCMS, useCMSQuery } from '@flightdev/cms/react';\r\n * import { createCMS } from '@flightdev/cms';\r\n * import { strapi } from '@flightdev/cms/strapi';\r\n * \r\n * const cms = createCMS(strapi({ url: '...', token: '...' }));\r\n * \r\n * function App() {\r\n * return (\r\n * <CMSProvider cms={cms}>\r\n * <PostList />\r\n * </CMSProvider>\r\n * );\r\n * }\r\n * \r\n * function PostList() {\r\n * const { data, loading, error } = useCMSQuery('posts', { limit: 10 });\r\n * \r\n * if (loading) return <div>Loading...</div>;\r\n * if (error) return <div>Error: {error.message}</div>;\r\n * \r\n * return <ul>{data.map(post => <li key={post.id}>{post.title}</li>)}</ul>;\r\n * }\r\n * ```\r\n */\r\n\r\nimport {\r\n createContext,\r\n useContext,\r\n useState,\r\n useEffect,\r\n useCallback,\r\n useMemo,\r\n createElement,\r\n type ReactNode,\r\n} from 'react';\r\nimport type { CMSInstance, CMSResult, FindManyOptions, FindOneOptions } from '../index';\r\n\r\n// =============================================================================\r\n// Context\r\n// =============================================================================\r\n\r\ninterface CMSContextValue {\r\n cms: CMSInstance;\r\n}\r\n\r\nconst CMSContext = createContext<CMSContextValue | null>(null);\r\n\r\n/**\r\n * CMS Provider component.\r\n * Provides CMS instance to all child components.\r\n * \r\n * @example\r\n * ```typescript\r\n * <CMSProvider cms={cms}>\r\n * <App />\r\n * </CMSProvider>\r\n * ```\r\n */\r\nexport function CMSProvider({\r\n cms,\r\n children,\r\n}: {\r\n cms: CMSInstance;\r\n children: ReactNode;\r\n}) {\r\n const value = useMemo(() => ({ cms }), [cms]);\r\n\r\n return createElement(CMSContext.Provider, { value }, children);\r\n}\r\n\r\n/**\r\n * Get CMS instance from context.\r\n * Must be used within a CMSProvider.\r\n */\r\nexport function useCMS(): CMSInstance {\r\n const context = useContext(CMSContext);\r\n\r\n if (!context) {\r\n throw new Error('useCMS must be used within a CMSProvider');\r\n }\r\n\r\n return context.cms;\r\n}\r\n\r\n// =============================================================================\r\n// Query Hooks\r\n// =============================================================================\r\n\r\ninterface QueryState<T> {\r\n data: T | null;\r\n loading: boolean;\r\n error: Error | null;\r\n}\r\n\r\ninterface QueryResult<T> extends QueryState<T> {\r\n refetch: () => Promise<void>;\r\n}\r\n\r\ninterface ListQueryResult<T> extends Omit<QueryResult<T[]>, 'data'> {\r\n data: T[];\r\n meta: CMSResult<T>['meta'] | null;\r\n}\r\n\r\n/**\r\n * Query a single entity from the CMS.\r\n * \r\n * @example\r\n * ```typescript\r\n * function PostPage({ slug }) {\r\n * const { data: post, loading, error } = useCMSOne('posts', {\r\n * where: { slug },\r\n * populate: ['author'],\r\n * });\r\n * \r\n * if (loading) return <Skeleton />;\r\n * if (error || !post) return <NotFound />;\r\n * \r\n * return <Post post={post} />;\r\n * }\r\n * ```\r\n */\r\nexport function useCMSOne<T = unknown>(\r\n collection: string,\r\n options?: FindOneOptions\r\n): QueryResult<T> {\r\n const cms = useCMS();\r\n const [state, setState] = useState<QueryState<T>>({\r\n data: null,\r\n loading: true,\r\n error: null,\r\n });\r\n\r\n // Memoize options to prevent infinite loops\r\n const optionsKey = JSON.stringify(options);\r\n\r\n const fetch = useCallback(async () => {\r\n setState(prev => ({ ...prev, loading: true, error: null }));\r\n\r\n try {\r\n const data = await cms.findOne<T>(collection, options);\r\n setState({ data, loading: false, error: null });\r\n } catch (error) {\r\n setState({ data: null, loading: false, error: error as Error });\r\n }\r\n }, [cms, collection, optionsKey]);\r\n\r\n useEffect(() => {\r\n fetch();\r\n }, [fetch]);\r\n\r\n return { ...state, refetch: fetch };\r\n}\r\n\r\n/**\r\n * Query multiple entities from the CMS with pagination.\r\n * \r\n * @example\r\n * ```typescript\r\n * function PostList() {\r\n * const { data: posts, meta, loading, refetch } = useCMSQuery('posts', {\r\n * limit: 10,\r\n * sort: { publishedAt: 'desc' },\r\n * populate: ['author', 'cover'],\r\n * });\r\n * \r\n * return (\r\n * <div>\r\n * {posts.map(post => <PostCard key={post.id} post={post} />)}\r\n * {meta && <p>Total: {meta.total}</p>}\r\n * <button onClick={refetch}>Refresh</button>\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useCMSQuery<T = unknown>(\r\n collection: string,\r\n options?: FindManyOptions\r\n): ListQueryResult<T> {\r\n const cms = useCMS();\r\n const [state, setState] = useState<{\r\n data: T[];\r\n meta: CMSResult<T>['meta'] | null;\r\n loading: boolean;\r\n error: Error | null;\r\n }>({\r\n data: [],\r\n meta: null,\r\n loading: true,\r\n error: null,\r\n });\r\n\r\n const optionsKey = JSON.stringify(options);\r\n\r\n const fetch = useCallback(async () => {\r\n setState(prev => ({ ...prev, loading: true, error: null }));\r\n\r\n try {\r\n const result = await cms.findMany<T>(collection, options);\r\n setState({\r\n data: result.data,\r\n meta: result.meta,\r\n loading: false,\r\n error: null,\r\n });\r\n } catch (error) {\r\n setState(prev => ({\r\n ...prev,\r\n loading: false,\r\n error: error as Error,\r\n }));\r\n }\r\n }, [cms, collection, optionsKey]);\r\n\r\n useEffect(() => {\r\n fetch();\r\n }, [fetch]);\r\n\r\n return { ...state, refetch: fetch };\r\n}\r\n\r\n/**\r\n * Query an entity by ID.\r\n * \r\n * @example\r\n * ```typescript\r\n * function PostPage({ id }) {\r\n * const { data: post, loading } = useCMSById('posts', id, {\r\n * populate: ['author'],\r\n * });\r\n * \r\n * if (loading) return <Skeleton />;\r\n * return <Post post={post} />;\r\n * }\r\n * ```\r\n */\r\nexport function useCMSById<T = unknown>(\r\n collection: string,\r\n id: string | number,\r\n options?: Omit<FindOneOptions, 'where'>\r\n): QueryResult<T> {\r\n const cms = useCMS();\r\n const [state, setState] = useState<QueryState<T>>({\r\n data: null,\r\n loading: true,\r\n error: null,\r\n });\r\n\r\n const optionsKey = JSON.stringify(options);\r\n\r\n const fetch = useCallback(async () => {\r\n setState(prev => ({ ...prev, loading: true, error: null }));\r\n\r\n try {\r\n const data = await cms.findById<T>(collection, id, options);\r\n setState({ data, loading: false, error: null });\r\n } catch (error) {\r\n setState({ data: null, loading: false, error: error as Error });\r\n }\r\n }, [cms, collection, id, optionsKey]);\r\n\r\n useEffect(() => {\r\n fetch();\r\n }, [fetch]);\r\n\r\n return { ...state, refetch: fetch };\r\n}\r\n\r\n// =============================================================================\r\n// Mutation Hooks\r\n// =============================================================================\r\n\r\ninterface MutationState<T> {\r\n data: T | null;\r\n loading: boolean;\r\n error: Error | null;\r\n}\r\n\r\ninterface MutationResult<T, TInput> extends MutationState<T> {\r\n mutate: (input: TInput) => Promise<T>;\r\n reset: () => void;\r\n}\r\n\r\n/**\r\n * Create a new entity in the CMS.\r\n * \r\n * @example\r\n * ```typescript\r\n * function CreatePostForm() {\r\n * const { mutate, loading, error } = useCMSCreate<Post>('posts');\r\n * \r\n * const handleSubmit = async (data) => {\r\n * const post = await mutate(data);\r\n * router.push(`/posts/${post.id}`);\r\n * };\r\n * \r\n * return <form onSubmit={handleSubmit}>...</form>;\r\n * }\r\n * ```\r\n */\r\nexport function useCMSCreate<T = unknown, TInput = Partial<T>>(\r\n collection: string\r\n): MutationResult<T, TInput> {\r\n const cms = useCMS();\r\n const [state, setState] = useState<MutationState<T>>({\r\n data: null,\r\n loading: false,\r\n error: null,\r\n });\r\n\r\n const mutate = useCallback(async (input: TInput): Promise<T> => {\r\n if (!cms.create) {\r\n throw new Error('CMS adapter does not support create operations');\r\n }\r\n\r\n setState({ data: null, loading: true, error: null });\r\n\r\n try {\r\n const data = await cms.create<T>(collection, input as Partial<T>);\r\n setState({ data, loading: false, error: null });\r\n return data;\r\n } catch (error) {\r\n setState({ data: null, loading: false, error: error as Error });\r\n throw error;\r\n }\r\n }, [cms, collection]);\r\n\r\n const reset = useCallback(() => {\r\n setState({ data: null, loading: false, error: null });\r\n }, []);\r\n\r\n return { ...state, mutate, reset };\r\n}\r\n\r\n/**\r\n * Update an entity in the CMS.\r\n * \r\n * @example\r\n * ```typescript\r\n * function EditPostForm({ post }) {\r\n * const { mutate, loading } = useCMSUpdate<Post>('posts');\r\n * \r\n * const handleSubmit = async (data) => {\r\n * await mutate(post.id, data);\r\n * toast('Post updated!');\r\n * };\r\n * \r\n * return <form onSubmit={handleSubmit}>...</form>;\r\n * }\r\n * ```\r\n */\r\nexport function useCMSUpdate<T = unknown, TInput = Partial<T>>(\r\n collection: string\r\n): MutationResult<T, { id: string | number; data: TInput }> {\r\n const cms = useCMS();\r\n const [state, setState] = useState<MutationState<T>>({\r\n data: null,\r\n loading: false,\r\n error: null,\r\n });\r\n\r\n const mutate = useCallback(async ({ id, data }: { id: string | number; data: TInput }): Promise<T> => {\r\n if (!cms.update) {\r\n throw new Error('CMS adapter does not support update operations');\r\n }\r\n\r\n setState({ data: null, loading: true, error: null });\r\n\r\n try {\r\n const result = await cms.update<T>(collection, id, data as Partial<T>);\r\n setState({ data: result, loading: false, error: null });\r\n return result;\r\n } catch (error) {\r\n setState({ data: null, loading: false, error: error as Error });\r\n throw error;\r\n }\r\n }, [cms, collection]);\r\n\r\n const reset = useCallback(() => {\r\n setState({ data: null, loading: false, error: null });\r\n }, []);\r\n\r\n return { ...state, mutate, reset };\r\n}\r\n\r\n/**\r\n * Delete an entity from the CMS.\r\n * \r\n * @example\r\n * ```typescript\r\n * function DeleteButton({ post }) {\r\n * const { mutate, loading } = useCMSDelete('posts');\r\n * \r\n * const handleDelete = async () => {\r\n * await mutate(post.id);\r\n * router.push('/posts');\r\n * };\r\n * \r\n * return <button onClick={handleDelete} disabled={loading}>Delete</button>;\r\n * }\r\n * ```\r\n */\r\nexport function useCMSDelete(\r\n collection: string\r\n): MutationResult<void, string | number> {\r\n const cms = useCMS();\r\n const [state, setState] = useState<MutationState<void>>({\r\n data: null,\r\n loading: false,\r\n error: null,\r\n });\r\n\r\n const mutate = useCallback(async (id: string | number): Promise<void> => {\r\n if (!cms.delete) {\r\n throw new Error('CMS adapter does not support delete operations');\r\n }\r\n\r\n setState({ data: null, loading: true, error: null });\r\n\r\n try {\r\n await cms.delete(collection, id);\r\n setState({ data: undefined, loading: false, error: null });\r\n } catch (error) {\r\n setState({ data: null, loading: false, error: error as Error });\r\n throw error;\r\n }\r\n }, [cms, collection]);\r\n\r\n const reset = useCallback(() => {\r\n setState({ data: null, loading: false, error: null });\r\n }, []);\r\n\r\n return { ...state, mutate, reset };\r\n}\r\n"]}