@djangocfg/ext-base 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +346 -0
  2. package/dist/api.cjs +41 -0
  3. package/dist/api.d.cts +35 -0
  4. package/dist/api.d.ts +35 -0
  5. package/dist/api.js +2 -0
  6. package/dist/auth.cjs +10 -0
  7. package/dist/auth.d.cts +1 -0
  8. package/dist/auth.d.ts +1 -0
  9. package/dist/auth.js +2 -0
  10. package/dist/chunk-3RG5ZIWI.js +8 -0
  11. package/dist/chunk-MECBWZG4.js +44 -0
  12. package/dist/chunk-YQGNYUBX.js +67 -0
  13. package/dist/hooks.cjs +190 -0
  14. package/dist/hooks.d.cts +96 -0
  15. package/dist/hooks.d.ts +96 -0
  16. package/dist/hooks.js +65 -0
  17. package/dist/index.cjs +131 -0
  18. package/dist/index.d.cts +246 -0
  19. package/dist/index.d.ts +246 -0
  20. package/dist/index.js +3 -0
  21. package/package.json +80 -0
  22. package/src/api/createExtensionAPI.ts +63 -0
  23. package/src/api/index.ts +5 -0
  24. package/src/auth/index.ts +13 -0
  25. package/src/config/env.ts +59 -0
  26. package/src/config/index.ts +5 -0
  27. package/src/context/ExtensionProvider.tsx +102 -0
  28. package/src/context/createExtensionContext.tsx +78 -0
  29. package/src/context/index.ts +7 -0
  30. package/src/hooks/index.ts +6 -0
  31. package/src/hooks/useInfinitePagination.ts +117 -0
  32. package/src/hooks/usePagination.ts +155 -0
  33. package/src/hooks.ts +17 -0
  34. package/src/index.ts +21 -0
  35. package/src/logger/createExtensionLogger.ts +61 -0
  36. package/src/logger/index.ts +5 -0
  37. package/src/types/context.ts +93 -0
  38. package/src/types/error.ts +12 -0
  39. package/src/types/index.ts +17 -0
  40. package/src/types/logger.ts +17 -0
  41. package/src/types/pagination.ts +47 -0
  42. package/src/utils/errors.ts +71 -0
  43. package/src/utils/index.ts +10 -0
package/README.md ADDED
@@ -0,0 +1,346 @@
1
+ # @djangocfg/ext-base
2
+
3
+ Base utilities and common code for building DjangoCFG extensions.
4
+
5
+ **Part of [DjangoCFG](https://djangocfg.com)** — modern Django framework for production-ready SaaS applications.
6
+
7
+ ## What is this?
8
+
9
+ `@djangocfg/ext-base` is a foundation library for DjangoCFG extensions. It provides:
10
+
11
+ - **Extension registration system** - automatic extension metadata tracking
12
+ - **Common React hooks** - pagination, infinite scroll, data fetching patterns
13
+ - **Environment utilities** - isDevelopment, isProduction, isStaticBuild helpers
14
+ - **Type-safe context helpers** - factory functions for creating contexts
15
+ - **Logger utilities** - structured logging with tags
16
+ - **Auth integration** - convenient re-exports of useAuth
17
+ - **TypeScript types** - shared types for all extensions
18
+
19
+ This package is used internally by all official DjangoCFG extensions (newsletter, payments, support, leads, knowbase) and can be used to build your own custom extensions.
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ pnpm add @djangocfg/ext-base
25
+ ```
26
+
27
+ ## Quick Start
28
+
29
+ ### 1. Create extension metadata
30
+
31
+ ```typescript
32
+ // src/config.ts
33
+ import type { ExtensionMetadata } from '@djangocfg/ext-base';
34
+
35
+ export const extensionConfig: ExtensionMetadata = {
36
+ name: 'my-extension',
37
+ version: '1.0.0',
38
+ author: 'Your Name',
39
+ displayName: 'My Extension',
40
+ description: 'Amazing extension functionality',
41
+ icon: '🚀',
42
+ license: 'MIT',
43
+ keywords: ['extension', 'feature'],
44
+ };
45
+ ```
46
+
47
+ ### 2. Create your extension provider
48
+
49
+ ```typescript
50
+ // src/contexts/MyExtensionProvider.tsx
51
+ 'use client';
52
+
53
+ import { ExtensionProvider } from '@djangocfg/ext-base/hooks';
54
+ import { extensionConfig } from '../config';
55
+
56
+ export function MyExtensionProvider({ children }) {
57
+ return (
58
+ <ExtensionProvider metadata={extensionConfig}>
59
+ {/* Your extension contexts here */}
60
+ {children}
61
+ </ExtensionProvider>
62
+ );
63
+ }
64
+ ```
65
+
66
+ ### 3. Use in your app
67
+
68
+ ```typescript
69
+ import { MyExtensionProvider } from '@your-org/my-extension/hooks';
70
+
71
+ export default function RootLayout({ children }) {
72
+ return (
73
+ <MyExtensionProvider>
74
+ {children}
75
+ </MyExtensionProvider>
76
+ );
77
+ }
78
+ ```
79
+
80
+ The extension will automatically register itself and log in development mode:
81
+ ```
82
+ [ext-base] Extension registered: My Extension v1.0.0
83
+ ```
84
+
85
+ ## Core Features
86
+
87
+ ### Environment Configuration
88
+
89
+ ```typescript
90
+ import { isDevelopment, isProduction, isStaticBuild, isClient, isServer } from '@djangocfg/ext-base';
91
+
92
+ if (isDevelopment) {
93
+ console.log('Running in development mode');
94
+ }
95
+
96
+ if (isStaticBuild) {
97
+ // Special handling for Next.js static export
98
+ }
99
+ ```
100
+
101
+ ### API Factory
102
+
103
+ Create extension API instances with shared authentication in one line:
104
+
105
+ ```typescript
106
+ // src/api/index.ts
107
+ import { API } from './generated/ext_myextension';
108
+ import { createExtensionAPI } from '@djangocfg/ext-base/api';
109
+
110
+ // That's it! All configuration is handled automatically:
111
+ // - API URL from environment
112
+ // - Static build detection
113
+ // - Shared authentication storage from @djangocfg/api
114
+ export const apiMyExtension = createExtensionAPI(API);
115
+ ```
116
+
117
+ **Before (manual setup):**
118
+ ```typescript
119
+ import { API } from './generated/ext_myextension';
120
+ import { api as accountsApi } from '@djangocfg/api';
121
+
122
+ const isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === 'true';
123
+ const apiUrl = isStaticBuild ? '' : process.env.NEXT_PUBLIC_API_URL || '';
124
+ const storage = (accountsApi as any)._storage;
125
+
126
+ export const apiMyExtension = new API(apiUrl, { storage });
127
+ ```
128
+
129
+ **After (with factory):**
130
+ ```typescript
131
+ import { API } from './generated/ext_myextension';
132
+ import { createExtensionAPI } from '@djangocfg/ext-base/api';
133
+
134
+ export const apiMyExtension = createExtensionAPI(API);
135
+ ```
136
+
137
+ ### Pagination Hooks
138
+
139
+ ```typescript
140
+ import { usePagination, useInfinitePagination } from '@djangocfg/ext-base/hooks';
141
+
142
+ // Standard pagination
143
+ const { items, page, pageSize, totalPages, goToPage, nextPage, prevPage } = usePagination({
144
+ keyPrefix: 'articles',
145
+ fetcher: async (page, pageSize) => {
146
+ const response = await api.articles.list({ page, page_size: pageSize });
147
+ return response.data;
148
+ },
149
+ initialPage: 1,
150
+ pageSize: 20,
151
+ });
152
+
153
+ // Infinite scroll
154
+ const { items, isLoading, hasMore, loadMore } = useInfinitePagination({
155
+ keyPrefix: 'articles',
156
+ fetcher: async (page, pageSize) => {
157
+ const response = await api.articles.list({ page, page_size: pageSize });
158
+ return response.data;
159
+ },
160
+ pageSize: 20,
161
+ });
162
+ ```
163
+
164
+ ### Type-Safe Context Creation
165
+
166
+ ```typescript
167
+ import { createExtensionContext } from '@djangocfg/ext-base/hooks';
168
+
169
+ interface MyContextValue {
170
+ data: any[];
171
+ refresh: () => void;
172
+ }
173
+
174
+ const { Provider, useContext: useMyContext } = createExtensionContext<MyContextValue>({
175
+ displayName: 'MyContext',
176
+ errorMessage: 'useMyContext must be used within MyProvider',
177
+ });
178
+
179
+ // In components
180
+ export function MyComponent() {
181
+ const { data, refresh } = useMyContext();
182
+ return <div>{data.length} items</div>;
183
+ }
184
+ ```
185
+
186
+ ### Logger
187
+
188
+ ```typescript
189
+ import { createExtensionLogger } from '@djangocfg/ext-base';
190
+
191
+ const logger = createExtensionLogger({
192
+ tag: 'my-extension',
193
+ level: 'info',
194
+ });
195
+
196
+ logger.info('Extension initialized');
197
+ logger.error('Something went wrong', error);
198
+ logger.success('Operation completed!');
199
+ ```
200
+
201
+ ### Auth Integration
202
+
203
+ ```typescript
204
+ import { useAuth } from '@djangocfg/ext-base/auth';
205
+
206
+ function MyComponent() {
207
+ const { user, isAuthenticated, login, logout } = useAuth();
208
+
209
+ if (!isAuthenticated) {
210
+ return <button onClick={login}>Login</button>;
211
+ }
212
+
213
+ return <div>Welcome, {user?.email}</div>;
214
+ }
215
+ ```
216
+
217
+ ### Error Handling
218
+
219
+ ```typescript
220
+ import { handleError, formatError } from '@djangocfg/ext-base';
221
+
222
+ try {
223
+ await someOperation();
224
+ } catch (error) {
225
+ handleError(error, (formattedError) => {
226
+ logger.error('Operation failed:', formattedError);
227
+ });
228
+ }
229
+ ```
230
+
231
+ ## Package Exports
232
+
233
+ ### Main entry (`@djangocfg/ext-base`)
234
+ Server-safe exports - can be used in both client and server components:
235
+ - All TypeScript types
236
+ - Environment configuration (isDevelopment, isProduction, etc.)
237
+ - API factory (createExtensionAPI)
238
+ - Logger utilities
239
+ - Error handling utilities
240
+
241
+ ### Hooks entry (`@djangocfg/ext-base/hooks`)
242
+ Client-only exports:
243
+ - `ExtensionProvider` - main provider component
244
+ - `usePagination` - standard pagination hook
245
+ - `useInfinitePagination` - infinite scroll hook
246
+ - `createExtensionContext` - context factory
247
+
248
+ ### Auth entry (`@djangocfg/ext-base/auth`)
249
+ Auth re-exports:
250
+ - `useAuth` - authentication hook
251
+ - `UserProfile` type
252
+ - `AuthContextType` type
253
+
254
+ ### API entry (`@djangocfg/ext-base/api`)
255
+ API utilities:
256
+ - `createExtensionAPI` - API instance factory
257
+ - `getSharedAuthStorage` - Get shared auth storage
258
+
259
+ ## TypeScript Types
260
+
261
+ ```typescript
262
+ import type {
263
+ // Extension metadata
264
+ ExtensionMetadata,
265
+ ExtensionProviderProps,
266
+
267
+ // Pagination
268
+ PaginatedResponse,
269
+ PaginationParams,
270
+ PaginationState,
271
+ InfinitePaginationReturn,
272
+
273
+ // Logger
274
+ ExtensionLogger,
275
+ LoggerOptions,
276
+
277
+ // Errors
278
+ ExtensionError,
279
+ ErrorHandler,
280
+
281
+ // Context
282
+ ExtensionContextOptions,
283
+ } from '@djangocfg/ext-base';
284
+ ```
285
+
286
+ ## Example: Complete Extension
287
+
288
+ ```typescript
289
+ // config.ts
290
+ export const extensionConfig: ExtensionMetadata = {
291
+ name: 'blog',
292
+ version: '1.0.0',
293
+ author: 'Your Company',
294
+ displayName: 'Blog',
295
+ description: 'Blog management system',
296
+ icon: '📝',
297
+ keywords: ['blog', 'articles'],
298
+ };
299
+
300
+ // contexts/BlogProvider.tsx
301
+ 'use client';
302
+
303
+ import { ExtensionProvider } from '@djangocfg/ext-base/hooks';
304
+ import { useInfinitePagination } from '@djangocfg/ext-base/hooks';
305
+ import { createExtensionLogger } from '@djangocfg/ext-base';
306
+ import { extensionConfig } from '../config';
307
+
308
+ const logger = createExtensionLogger({ tag: 'blog' });
309
+
310
+ export function BlogProvider({ children }) {
311
+ const { items, loadMore, hasMore } = useInfinitePagination({
312
+ keyPrefix: 'blog-articles',
313
+ fetcher: async (page, pageSize) => {
314
+ const response = await api.articles.list({ page, page_size: pageSize });
315
+ return response.data;
316
+ },
317
+ });
318
+
319
+ logger.info('Blog provider initialized with', items.length, 'articles');
320
+
321
+ return (
322
+ <ExtensionProvider metadata={extensionConfig}>
323
+ {/* Your contexts here */}
324
+ {children}
325
+ </ExtensionProvider>
326
+ );
327
+ }
328
+ ```
329
+
330
+ ## Best Practices
331
+
332
+ 1. **Always use ExtensionProvider** - Wrap your extension with it for proper registration
333
+ 2. **Define metadata** - Create a `config.ts` file with complete metadata
334
+ 3. **Use provided hooks** - Leverage `usePagination` and `useInfinitePagination`
335
+ 4. **Structured logging** - Use `createExtensionLogger` with consistent tags
336
+ 5. **Type safety** - Import types from `@djangocfg/ext-base`
337
+ 6. **Separate entry points** - Use `/hooks` for client code, main entry for server-safe code
338
+
339
+ ## License
340
+
341
+ MIT
342
+
343
+ ## Links
344
+
345
+ - [DjangoCFG Documentation](https://djangocfg.com)
346
+ - [GitHub](https://github.com/markolofsen/django-cfg)
package/dist/api.cjs ADDED
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
+ }) : x)(function(x) {
6
+ if (typeof require !== "undefined") return require.apply(this, arguments);
7
+ throw Error('Dynamic require of "' + x + '" is not supported');
8
+ });
9
+
10
+ // src/config/env.ts
11
+ process.env.NODE_ENV === "development";
12
+ process.env.NODE_ENV === "production";
13
+ process.env.NODE_ENV === "test";
14
+ var isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === "true";
15
+ var getApiUrl = () => {
16
+ return process.env.NEXT_PUBLIC_API_URL || "";
17
+ };
18
+
19
+ // src/api/createExtensionAPI.ts
20
+ function createExtensionAPI(APIClass) {
21
+ let storage;
22
+ try {
23
+ const { api: accountsApi } = __require("@djangocfg/api");
24
+ storage = accountsApi._storage;
25
+ } catch (error) {
26
+ storage = void 0;
27
+ }
28
+ const apiUrl = isStaticBuild ? "" : getApiUrl();
29
+ return new APIClass(apiUrl, storage ? { storage } : void 0);
30
+ }
31
+ function getSharedAuthStorage() {
32
+ try {
33
+ const { api: accountsApi } = __require("@djangocfg/api");
34
+ return accountsApi._storage;
35
+ } catch (error) {
36
+ return void 0;
37
+ }
38
+ }
39
+
40
+ exports.createExtensionAPI = createExtensionAPI;
41
+ exports.getSharedAuthStorage = getSharedAuthStorage;
package/dist/api.d.cts ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Factory for creating extension API instances
3
+ *
4
+ * Provides consistent API setup across all extensions with shared authentication
5
+ */
6
+ /**
7
+ * Creates an extension API instance with shared authentication storage
8
+ *
9
+ * @param APIClass - The generated API class from your extension
10
+ * @returns Configured API instance with shared auth storage
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * // In your extension's api/index.ts
15
+ * import { API } from './generated/ext_newsletter';
16
+ * import { createExtensionAPI } from '@djangocfg/ext-base/api';
17
+ *
18
+ * export const apiNewsletter = createExtensionAPI(API);
19
+ * ```
20
+ */
21
+ declare function createExtensionAPI<T>(APIClass: new (url: string, options?: any) => T): T;
22
+ /**
23
+ * Get shared authentication storage from accounts API
24
+ *
25
+ * @returns Storage instance or undefined if not available
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const storage = getSharedAuthStorage();
30
+ * const api = new API(apiUrl, { storage });
31
+ * ```
32
+ */
33
+ declare function getSharedAuthStorage(): any | undefined;
34
+
35
+ export { createExtensionAPI, getSharedAuthStorage };
package/dist/api.d.ts ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Factory for creating extension API instances
3
+ *
4
+ * Provides consistent API setup across all extensions with shared authentication
5
+ */
6
+ /**
7
+ * Creates an extension API instance with shared authentication storage
8
+ *
9
+ * @param APIClass - The generated API class from your extension
10
+ * @returns Configured API instance with shared auth storage
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * // In your extension's api/index.ts
15
+ * import { API } from './generated/ext_newsletter';
16
+ * import { createExtensionAPI } from '@djangocfg/ext-base/api';
17
+ *
18
+ * export const apiNewsletter = createExtensionAPI(API);
19
+ * ```
20
+ */
21
+ declare function createExtensionAPI<T>(APIClass: new (url: string, options?: any) => T): T;
22
+ /**
23
+ * Get shared authentication storage from accounts API
24
+ *
25
+ * @returns Storage instance or undefined if not available
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const storage = getSharedAuthStorage();
30
+ * const api = new API(apiUrl, { storage });
31
+ * ```
32
+ */
33
+ declare function getSharedAuthStorage(): any | undefined;
34
+
35
+ export { createExtensionAPI, getSharedAuthStorage };
package/dist/api.js ADDED
@@ -0,0 +1,2 @@
1
+ export { createExtensionAPI, getSharedAuthStorage } from './chunk-MECBWZG4.js';
2
+ import './chunk-3RG5ZIWI.js';
package/dist/auth.cjs ADDED
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ var auth = require('@djangocfg/api/auth');
4
+
5
+
6
+
7
+ Object.defineProperty(exports, "useAuth", {
8
+ enumerable: true,
9
+ get: function () { return auth.useAuth; }
10
+ });
@@ -0,0 +1 @@
1
+ export { AuthContextType, UserProfile, useAuth } from '@djangocfg/api/auth';
package/dist/auth.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { AuthContextType, UserProfile, useAuth } from '@djangocfg/api/auth';
package/dist/auth.js ADDED
@@ -0,0 +1,2 @@
1
+ import './chunk-3RG5ZIWI.js';
2
+ export { useAuth } from '@djangocfg/api/auth';
@@ -0,0 +1,8 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ export { __require };
@@ -0,0 +1,44 @@
1
+ import { __require } from './chunk-3RG5ZIWI.js';
2
+
3
+ // src/config/env.ts
4
+ var isDevelopment = process.env.NODE_ENV === "development";
5
+ var isProduction = process.env.NODE_ENV === "production";
6
+ var isTest = process.env.NODE_ENV === "test";
7
+ var isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === "true";
8
+ var isClient = typeof window !== "undefined";
9
+ var isServer = !isClient;
10
+ var getApiUrl = () => {
11
+ return process.env.NEXT_PUBLIC_API_URL || "";
12
+ };
13
+ var env = {
14
+ isDevelopment,
15
+ isProduction,
16
+ isTest,
17
+ isStaticBuild,
18
+ isClient,
19
+ isServer,
20
+ getApiUrl
21
+ };
22
+
23
+ // src/api/createExtensionAPI.ts
24
+ function createExtensionAPI(APIClass) {
25
+ let storage;
26
+ try {
27
+ const { api: accountsApi } = __require("@djangocfg/api");
28
+ storage = accountsApi._storage;
29
+ } catch (error) {
30
+ storage = void 0;
31
+ }
32
+ const apiUrl = isStaticBuild ? "" : getApiUrl();
33
+ return new APIClass(apiUrl, storage ? { storage } : void 0);
34
+ }
35
+ function getSharedAuthStorage() {
36
+ try {
37
+ const { api: accountsApi } = __require("@djangocfg/api");
38
+ return accountsApi._storage;
39
+ } catch (error) {
40
+ return void 0;
41
+ }
42
+ }
43
+
44
+ export { createExtensionAPI, env, getApiUrl, getSharedAuthStorage, isClient, isDevelopment, isProduction, isServer, isStaticBuild, isTest };
@@ -0,0 +1,67 @@
1
+ import { createConsola } from 'consola';
2
+
3
+ // src/utils/errors.ts
4
+ function isExtensionError(error) {
5
+ return typeof error === "object" && error !== null && "message" in error && "timestamp" in error;
6
+ }
7
+ function createExtensionError(error, code, details) {
8
+ const message = error instanceof Error ? error.message : String(error);
9
+ return {
10
+ message,
11
+ code,
12
+ details,
13
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
14
+ };
15
+ }
16
+ function formatErrorMessage(error) {
17
+ if (isExtensionError(error)) {
18
+ return error.code ? `[${error.code}] ${error.message}` : error.message;
19
+ }
20
+ if (error instanceof Error) {
21
+ return error.message;
22
+ }
23
+ return String(error);
24
+ }
25
+ function handleExtensionError(error, logger, callback) {
26
+ const extensionError = isExtensionError(error) ? error : createExtensionError(error);
27
+ if (logger) {
28
+ logger.error("Extension error:", extensionError);
29
+ }
30
+ if (callback) {
31
+ callback(extensionError);
32
+ }
33
+ }
34
+ var isDevelopment = process.env.NODE_ENV === "development";
35
+ var LEVEL_MAP = {
36
+ debug: 4,
37
+ info: 3,
38
+ warn: 2,
39
+ error: 1
40
+ };
41
+ function createExtensionLogger(options) {
42
+ const { tag, level = "info", enabled = true } = options;
43
+ if (!enabled) {
44
+ const noop = () => {
45
+ };
46
+ return {
47
+ info: noop,
48
+ warn: noop,
49
+ error: noop,
50
+ debug: noop,
51
+ success: noop
52
+ };
53
+ }
54
+ const logLevel = isDevelopment ? LEVEL_MAP[level] : LEVEL_MAP.error;
55
+ const consola = createConsola({
56
+ level: logLevel
57
+ }).withTag(tag);
58
+ return {
59
+ info: (...args) => consola.info(...args),
60
+ warn: (...args) => consola.warn(...args),
61
+ error: (...args) => consola.error(...args),
62
+ debug: (...args) => consola.debug(...args),
63
+ success: (...args) => consola.success(...args)
64
+ };
65
+ }
66
+
67
+ export { createExtensionError, createExtensionLogger, formatErrorMessage, handleExtensionError, isExtensionError };