@djangocfg/ext-base 1.0.2 → 1.0.4

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 (47) hide show
  1. package/README.md +186 -7
  2. package/dist/api.cjs +40 -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-UXTBBEO5.js +237 -0
  12. package/dist/chunk-VJEGYVBV.js +140 -0
  13. package/dist/cli.mjs +530 -0
  14. package/dist/hooks.cjs +437 -0
  15. package/dist/hooks.d.cts +97 -0
  16. package/dist/hooks.d.ts +97 -0
  17. package/dist/hooks.js +95 -0
  18. package/dist/index.cjs +345 -0
  19. package/dist/index.d.cts +363 -0
  20. package/dist/index.d.ts +363 -0
  21. package/dist/index.js +3 -0
  22. package/package.json +5 -2
  23. package/src/cli/index.ts +470 -35
  24. package/src/context/ExtensionProvider.tsx +67 -4
  25. package/src/extensionConfig.ts +114 -0
  26. package/src/index.ts +3 -0
  27. package/src/metadata.ts +1 -2
  28. package/src/types/context.ts +21 -15
  29. package/src/utils/createExtensionConfig.ts +34 -18
  30. package/templates/extension-template/README.md.template +37 -5
  31. package/templates/extension-template/package.json.template +13 -5
  32. package/templates/extension-template/playground/.gitignore.template +34 -0
  33. package/templates/extension-template/playground/CLAUDE.md +35 -0
  34. package/templates/extension-template/playground/README.md.template +76 -0
  35. package/templates/extension-template/playground/app/globals.css.template +19 -0
  36. package/templates/extension-template/playground/app/layout.tsx.template +30 -0
  37. package/templates/extension-template/playground/app/page.tsx.template +44 -0
  38. package/templates/extension-template/playground/next.config.ts.template +62 -0
  39. package/templates/extension-template/playground/package.json.template +33 -0
  40. package/templates/extension-template/playground/tsconfig.json.template +27 -0
  41. package/templates/extension-template/src/config.ts +1 -2
  42. package/templates/extension-template/src/contexts/__PROVIDER_NAME__Context.tsx +1 -1
  43. package/templates/extension-template/src/contexts/__PROVIDER_NAME__ExtensionProvider.tsx +1 -0
  44. package/templates/extension-template/src/hooks/index.ts +1 -1
  45. package/templates/extension-template/src/index.ts +12 -4
  46. package/templates/extension-template/src/utils/withSmartProvider.tsx +70 -0
  47. package/templates/extension-template/tsup.config.ts +36 -22
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  ![DjangoCFG Extension Preview](https://unpkg.com/@djangocfg/ext-base@latest/preview.png)
4
4
 
5
+ **[📦 View in Marketplace](https://hub.djangocfg.com/extensions/djangocfg-ext-base)** • **[📖 Documentation](https://djangocfg.com)** • **[⭐ GitHub](https://github.com/markolofsen/django-cfg)**
6
+
5
7
  </div>
6
8
 
7
9
  # @djangocfg/ext-base
@@ -37,12 +39,30 @@ The package includes a CLI tool for creating new DjangoCFG extensions:
37
39
  # Create a new extension with interactive wizard
38
40
  djangocfg-ext create
39
41
 
42
+ # Quick test extension (auto-cleanup + unique name)
43
+ djangocfg-ext test
44
+
45
+ # List all extensions in workspace
46
+ djangocfg-ext list
47
+
48
+ # Show extension info
49
+ djangocfg-ext info <extension-name>
50
+
40
51
  # Show help
41
52
  djangocfg-ext help
42
53
  ```
43
54
 
44
55
  The CLI will guide you through creating a new extension with proper structure and configuration using the `createExtensionConfig` helper.
45
56
 
57
+ **Features:**
58
+ - Interactive prompts for package name, description, icon, and category
59
+ - Automatic npm registry validation to prevent duplicate package names
60
+ - Support for both scoped (`@my-org/my-extension`) and unscoped (`my-extension`) package names
61
+ - Template generation with proper placeholder replacement
62
+ - **Playground environment** with Next.js 15 for rapid development
63
+ - Auto-dependency installation and type checking
64
+ - Smart workspace detection for monorepos
65
+
46
66
  ## Quick Start
47
67
 
48
68
  ### 1. Create extension metadata
@@ -107,7 +127,7 @@ export function MyExtensionProvider({ children }) {
107
127
  ### 3. Use in your app
108
128
 
109
129
  ```typescript
110
- import { MyExtensionProvider } from '@your-org/my-extension/hooks';
130
+ import { MyExtensionProvider } from '@your-org/my-extension';
111
131
 
112
132
  export default function RootLayout({ children }) {
113
133
  return (
@@ -118,6 +138,94 @@ export default function RootLayout({ children }) {
118
138
  }
119
139
  ```
120
140
 
141
+ ## Development Workflow
142
+
143
+ Every extension created with the CLI includes a **playground** - a Next.js 15 development environment for rapid testing and iteration.
144
+
145
+ ### Starting the Playground
146
+
147
+ ```bash
148
+ cd your-extension
149
+ pnpm dev:playground
150
+ ```
151
+
152
+ This command:
153
+ 1. ✅ Automatically builds your extension
154
+ 2. ✅ Starts Next.js dev server on port 3333
155
+ 3. ✅ Opens browser automatically
156
+ 4. ✅ Hot-reloads on source changes
157
+
158
+ ### Playground Features
159
+
160
+ - **Next.js 15 App Router** with React 19
161
+ - **Tailwind CSS v4** pre-configured
162
+ - **BaseApp** wrapper with theme, auth, and SWR
163
+ - **Auto-build** extension before starting (via `predev` script)
164
+ - **Auto-open browser** when dev server is ready
165
+ - **Hot reload** for instant feedback
166
+
167
+ ### Building for Production
168
+
169
+ ```bash
170
+ # Build extension
171
+ pnpm build
172
+
173
+ # Build playground
174
+ pnpm build:playground
175
+
176
+ # Type check
177
+ pnpm check
178
+ ```
179
+
180
+ ## Smart Provider Pattern
181
+
182
+ Extensions support a **Smart Provider Pattern** that allows components to work both standalone and with manual provider wrapping.
183
+
184
+ ### Using withSmartProvider
185
+
186
+ Create self-contained components that automatically wrap themselves with the provider when needed:
187
+
188
+ ```typescript
189
+ import { withSmartProvider } from '@your-org/my-extension';
190
+
191
+ // Your component
192
+ function MyComponent() {
193
+ const { data } = useMyExtension();
194
+ return <div>{data}</div>;
195
+ }
196
+
197
+ // Wrap with smart provider
198
+ export const MySmartComponent = withSmartProvider(MyComponent);
199
+ ```
200
+
201
+ **Usage:**
202
+
203
+ ```typescript
204
+ // Works standalone (auto-wrapped)
205
+ <MySmartComponent />
206
+
207
+ // Also works with manual provider (shares context)
208
+ <MyExtensionProvider>
209
+ <MySmartComponent />
210
+ <MySmartComponent />
211
+ </MyExtensionProvider>
212
+ ```
213
+
214
+ **Benefits:**
215
+ - ✅ Components work out-of-the-box without provider boilerplate
216
+ - ✅ Multiple components can still share context when manually wrapped
217
+ - ✅ Best of both worlds - simplicity and flexibility
218
+
219
+ **When to use:**
220
+ - Library components that should "just work"
221
+ - Components that might be used in isolation
222
+ - Reducing boilerplate for simple use cases
223
+
224
+ **When to use manual provider:**
225
+ - Multiple components need to share state
226
+ - Performance optimization (single provider instance)
227
+ - Explicit context boundaries
228
+
121
229
  ## Core Features
122
230
 
123
231
  ### Extension Config Helper
@@ -139,7 +247,6 @@ export const extensionConfig = createExtensionConfig(packageJson, {
139
247
  // Optional fields
140
248
  minVersion: '2.0.0',
141
249
  githubStars: 100,
142
- relatedExtensions: ['other-extension'],
143
250
  examples: [
144
251
  {
145
252
  title: 'Example title',
@@ -159,13 +266,20 @@ export const extensionConfig = createExtensionConfig(packageJson, {
159
266
  - `homepage` - Homepage URL
160
267
  - `githubUrl` - Repository URL
161
268
  - `keywords` - Keywords array
162
- - `peerDependencies` - Peer dependencies
269
+ - `peerDependencies` - Peer dependencies (workspace:* auto-replaced with latest)
270
+ - `packageDependencies` - Dependencies from package.json (workspace:* auto-replaced with latest)
163
271
 
164
272
  **Auto-generated:**
165
- - `npmUrl` - npm package URL
273
+ - `npmUrl` - npm package URL (`https://www.npmjs.com/package/[name]`)
274
+ - `marketplaceId` - URL-safe ID (@ and / replaced with -), e.g. `@djangocfg/ext-newsletter` → `djangocfg-ext-newsletter`
275
+ - `marketplaceUrl` - Marketplace URL (only for official @djangocfg extensions): `https://hub.djangocfg.com/extensions/[marketplaceId]`
166
276
  - `installCommand` - pnpm install command
277
+ - `downloadUrl` - npm tarball download URL
278
+ - `preview` - Preview image URL (`https://unpkg.com/[name]@latest/preview.png`)
167
279
  - `tags` - Same as keywords
168
280
 
281
+ > **Note:** All `workspace:*` dependencies are automatically replaced with `latest` for marketplace display.
282
+
169
283
  ### Environment Configuration
170
284
 
171
285
  ```typescript
@@ -264,6 +378,52 @@ function MyComponent() {
264
378
  | `@djangocfg/ext-base/auth` | Auth re-exports (useAuth, types) | Client components only |
265
379
  | `@djangocfg/ext-base/api` | API utilities (createExtensionAPI, getSharedAuthStorage) | Server & client components |
266
380
 
381
+ **For your own extensions:**
382
+
383
+ Extension templates automatically export:
384
+ - `extensionConfig` - Extension metadata
385
+ - `[Name]Provider` - Main provider component (wraps ExtensionProvider)
386
+ - `use[Name]` - Context hook (required provider)
387
+ - `use[Name]Optional` - Context hook (optional provider)
388
+ - `withSmartProvider` - HOC for self-contained components
389
+
390
+ **Server-safe imports:**
391
+ - Use `@your-org/ext-name/config` to import extension metadata without loading React components
392
+ - This is recommended for server components and build tools
393
+
394
+ ## Extension Categories
395
+
396
+ The package exports a standardized list of extension categories:
397
+
398
+ ```typescript
399
+ import { EXTENSION_CATEGORIES } from '@djangocfg/ext-base';
400
+
401
+ // Array of { title: string, value: ExtensionCategory }
402
+ EXTENSION_CATEGORIES.forEach(category => {
403
+ console.log(category.title, category.value);
404
+ // Forms, forms
405
+ // Payments, payments
406
+ // Content, content
407
+ // Support, support
408
+ // Utilities, utilities
409
+ // Analytics, analytics
410
+ // Security, security
411
+ // Integration, integration
412
+ // Other, other
413
+ });
414
+ ```
415
+
416
+ Available categories:
417
+ - `forms` - Forms, CRM, Lead Management
418
+ - `payments` - Payment Processing, Billing
419
+ - `content` - Content Management, Marketing
420
+ - `support` - Support, Helpdesk, Tickets
421
+ - `utilities` - Tools, Base, Infrastructure
422
+ - `analytics` - Analytics, Tracking, Monitoring
423
+ - `security` - Security, Authentication, Authorization
424
+ - `integration` - Third-party Integrations
425
+ - `other` - Other/Uncategorized
426
+
267
427
  ## TypeScript Types
268
428
 
269
429
  ```typescript
@@ -291,14 +451,33 @@ import type {
291
451
 
292
452
  ## Best Practices
293
453
 
454
+ ### Configuration & Metadata
294
455
  - Use `createExtensionConfig` helper to maintain Single Source of Truth from package.json
295
- - Always wrap your extension with `ExtensionProvider` for proper registration
296
456
  - Use Lucide icon names (not emoji) for `icon` field
297
457
  - Include comprehensive `features` list for marketplace visibility
298
458
  - Provide code `examples` with proper syntax highlighting
299
- - Use provided pagination hooks for consistent data fetching
459
+ - **Include a `preview.png` file** in your extension root (1200x630px recommended)
460
+ - List `preview.png` in your package.json `files` array for npm publication
461
+
462
+ ### Development
463
+ - Use the **playground** for rapid development (`pnpm dev:playground`)
464
+ - Test your components in isolation before integrating
465
+ - Run type checks before building (`pnpm check`)
300
466
  - Use `createExtensionLogger` with consistent tags for structured logging
301
- - Separate client-only code using `/hooks` entry point
467
+
468
+ ### Architecture
469
+ - Always wrap your extension with `ExtensionProvider` for proper registration
470
+ - Export main provider as `[Name]Provider` (not `[Name]ExtensionProvider`)
471
+ - Use `withSmartProvider` for components that should work standalone
472
+ - Prefer manual provider wrapping when components need shared state
473
+ - Use provided pagination hooks for consistent data fetching
474
+ - Separate client-only code appropriately
475
+
476
+ ### Publishing
477
+ - Test with `pnpm build` before publishing
478
+ - Verify preview image is accessible via unpkg
479
+ - Ensure all peer dependencies are correctly listed
480
+ - Include clear usage examples in README
302
481
 
303
482
  ## License
304
483
 
package/dist/api.cjs ADDED
@@ -0,0 +1,40 @@
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.ts
11
+ process.env.NODE_ENV === "development";
12
+ process.env.NODE_ENV === "production";
13
+ var isStaticBuild = process.env.STATIC_BUILD === "true";
14
+ function getApiUrl() {
15
+ return process.env.NEXT_PUBLIC_API_URL || "/api";
16
+ }
17
+
18
+ // src/api/createExtensionAPI.ts
19
+ function createExtensionAPI(APIClass) {
20
+ let storage;
21
+ try {
22
+ const { api: accountsApi } = __require("@djangocfg/api");
23
+ storage = accountsApi._storage;
24
+ } catch (error) {
25
+ storage = void 0;
26
+ }
27
+ const apiUrl = isStaticBuild ? "" : getApiUrl();
28
+ return new APIClass(apiUrl, storage ? { storage } : void 0);
29
+ }
30
+ function getSharedAuthStorage() {
31
+ try {
32
+ const { api: accountsApi } = __require("@djangocfg/api");
33
+ return accountsApi._storage;
34
+ } catch (error) {
35
+ return void 0;
36
+ }
37
+ }
38
+
39
+ exports.createExtensionAPI = createExtensionAPI;
40
+ 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-VJEGYVBV.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,237 @@
1
+ import { package_default } from './chunk-VJEGYVBV.js';
2
+ import { createConsola } from 'consola';
3
+
4
+ // src/types/context.ts
5
+ var EXTENSION_CATEGORIES = [
6
+ { title: "Forms", value: "forms" },
7
+ { title: "Payments", value: "payments" },
8
+ { title: "Content", value: "content" },
9
+ { title: "Support", value: "support" },
10
+ { title: "Utilities", value: "utilities" },
11
+ { title: "Analytics", value: "analytics" },
12
+ { title: "Security", value: "security" },
13
+ { title: "Integration", value: "integration" },
14
+ { title: "Other", value: "other" }
15
+ ];
16
+
17
+ // src/utils/createExtensionConfig.ts
18
+ function replaceWorkspaceDeps(deps) {
19
+ if (!deps) return void 0;
20
+ const result = {};
21
+ for (const [key, value] of Object.entries(deps)) {
22
+ result[key] = value === "workspace:*" ? "latest" : value;
23
+ }
24
+ return result;
25
+ }
26
+ function createExtensionConfig(packageJson, config) {
27
+ const author = typeof packageJson.author === "string" ? packageJson.author : packageJson.author?.name || "Unknown";
28
+ const githubUrl = typeof packageJson.repository === "string" ? packageJson.repository : packageJson.repository?.url;
29
+ const packageNameWithoutScope = packageJson.name.split("/").pop() || packageJson.name;
30
+ const downloadUrl = `https://registry.npmjs.org/${packageJson.name}/-/${packageNameWithoutScope}-${packageJson.version}.tgz`;
31
+ const marketplaceId = packageJson.name.replace("@", "").replace("/", "-");
32
+ return {
33
+ // From package.json
34
+ name: config.name,
35
+ version: packageJson.version,
36
+ author,
37
+ description: packageJson.description,
38
+ keywords: packageJson.keywords,
39
+ license: packageJson.license,
40
+ homepage: packageJson.homepage,
41
+ githubUrl,
42
+ packageDependencies: replaceWorkspaceDeps(packageJson.dependencies),
43
+ packagePeerDependencies: replaceWorkspaceDeps(packageJson.peerDependencies),
44
+ packageDevDependencies: replaceWorkspaceDeps(packageJson.devDependencies),
45
+ // From manual config
46
+ displayName: config.displayName,
47
+ category: config.category,
48
+ features: config.features,
49
+ examples: config.examples,
50
+ minVersion: config.minVersion,
51
+ githubStars: config.githubStars,
52
+ // Auto-generated
53
+ npmUrl: `https://www.npmjs.com/package/${packageJson.name}`,
54
+ marketplaceId,
55
+ // Only generate marketplace URL for official @djangocfg extensions
56
+ marketplaceUrl: packageJson.name.startsWith("@djangocfg/ext-") ? `https://hub.djangocfg.com/extensions/${marketplaceId}` : void 0,
57
+ installCommand: `pnpm add ${packageJson.name}`,
58
+ downloadUrl,
59
+ tags: packageJson.keywords,
60
+ preview: `https://unpkg.com/${packageJson.name}@latest/preview.png`
61
+ };
62
+ }
63
+
64
+ // src/extensionConfig.ts
65
+ var extensionConfig = createExtensionConfig(package_default, {
66
+ name: "base",
67
+ displayName: "Extension Base",
68
+ category: "utilities",
69
+ features: [
70
+ "CLI for creating extensions",
71
+ "Next.js 15 playground for rapid development",
72
+ "Smart Provider Pattern for flexible component usage",
73
+ "Extension context and provider system",
74
+ "Pagination hooks (standard & infinite scroll)",
75
+ "API client utilities with auth integration",
76
+ "Type-safe context creation helpers",
77
+ "Logging system with structured output",
78
+ "Environment detection (dev, prod, static)",
79
+ "TypeScript types and utilities"
80
+ ],
81
+ minVersion: "2.0.0",
82
+ examples: [
83
+ {
84
+ title: "Create Extension with CLI",
85
+ description: "Interactive wizard with playground environment",
86
+ code: `# Create extension with interactive wizard
87
+ pnpm dlx @djangocfg/ext-base create
88
+
89
+ # Quick test extension (auto-cleanup)
90
+ pnpm dlx @djangocfg/ext-base test
91
+
92
+ # List all extensions
93
+ pnpm dlx @djangocfg/ext-base list`,
94
+ language: "bash"
95
+ },
96
+ {
97
+ title: "Development with Playground",
98
+ description: "Start Next.js playground for rapid testing",
99
+ code: `cd your-extension
100
+
101
+ # Start playground (auto-builds extension + opens browser)
102
+ pnpm dev:playground
103
+
104
+ # Build for production
105
+ pnpm build
106
+
107
+ # Type check
108
+ pnpm check`,
109
+ language: "bash"
110
+ },
111
+ {
112
+ title: "Smart Provider Pattern",
113
+ description: "Components that work standalone or with shared context",
114
+ code: `import { withSmartProvider } from '@your-org/my-extension';
115
+
116
+ // Your component
117
+ function MyComponent() {
118
+ const { data } = useMyExtension();
119
+ return <div>{data}</div>;
120
+ }
121
+
122
+ // Wrap with smart provider
123
+ export const MySmartComponent = withSmartProvider(MyComponent);
124
+
125
+ // Usage 1: Standalone (auto-wrapped)
126
+ <MySmartComponent />
127
+
128
+ // Usage 2: Manual provider (shared context)
129
+ <MyExtensionProvider>
130
+ <MySmartComponent />
131
+ <MySmartComponent />
132
+ </MyExtensionProvider>`,
133
+ language: "tsx"
134
+ },
135
+ {
136
+ title: "Extension Provider",
137
+ description: "Register and wrap your extension",
138
+ code: `import { ExtensionProvider } from '@djangocfg/ext-base/hooks';
139
+ import { extensionConfig } from './config';
140
+
141
+ export function MyExtensionProvider({ children }) {
142
+ return (
143
+ <ExtensionProvider metadata={extensionConfig}>
144
+ {children}
145
+ </ExtensionProvider>
146
+ );
147
+ }`,
148
+ language: "tsx"
149
+ },
150
+ {
151
+ title: "Pagination Hooks",
152
+ description: "Built-in hooks for standard and infinite scroll",
153
+ code: `import { usePagination, useInfinitePagination } from '@djangocfg/ext-base/hooks';
154
+
155
+ // Standard pagination
156
+ const { items, page, totalPages, nextPage, prevPage } = usePagination({
157
+ keyPrefix: 'articles',
158
+ fetcher: async (page, pageSize) => api.articles.list({ page, page_size: pageSize }),
159
+ pageSize: 20,
160
+ });
161
+
162
+ // Infinite scroll
163
+ const { items, isLoading, hasMore, loadMore } = useInfinitePagination({
164
+ keyPrefix: 'articles',
165
+ fetcher: async (page, pageSize) => api.articles.list({ page, page_size: pageSize }),
166
+ pageSize: 20,
167
+ });`,
168
+ language: "tsx"
169
+ }
170
+ ]
171
+ });
172
+
173
+ // src/utils/errors.ts
174
+ function isExtensionError(error) {
175
+ return typeof error === "object" && error !== null && "message" in error && "timestamp" in error;
176
+ }
177
+ function createExtensionError(error, code, details) {
178
+ const message = error instanceof Error ? error.message : String(error);
179
+ return {
180
+ message,
181
+ code,
182
+ details,
183
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
184
+ };
185
+ }
186
+ function formatErrorMessage(error) {
187
+ if (isExtensionError(error)) {
188
+ return error.code ? `[${error.code}] ${error.message}` : error.message;
189
+ }
190
+ if (error instanceof Error) {
191
+ return error.message;
192
+ }
193
+ return String(error);
194
+ }
195
+ function handleExtensionError(error, logger, callback) {
196
+ const extensionError = isExtensionError(error) ? error : createExtensionError(error);
197
+ if (logger) {
198
+ logger.error("Extension error:", extensionError);
199
+ }
200
+ if (callback) {
201
+ callback(extensionError);
202
+ }
203
+ }
204
+ var isDevelopment = process.env.NODE_ENV === "development";
205
+ var LEVEL_MAP = {
206
+ debug: 4,
207
+ info: 3,
208
+ warn: 2,
209
+ error: 1
210
+ };
211
+ function createExtensionLogger(options) {
212
+ const { tag, level = "info", enabled = true } = options;
213
+ if (!enabled) {
214
+ const noop = () => {
215
+ };
216
+ return {
217
+ info: noop,
218
+ warn: noop,
219
+ error: noop,
220
+ debug: noop,
221
+ success: noop
222
+ };
223
+ }
224
+ const logLevel = isDevelopment ? LEVEL_MAP[level] : LEVEL_MAP.error;
225
+ const consola = createConsola({
226
+ level: logLevel
227
+ }).withTag(tag);
228
+ return {
229
+ info: (...args) => consola.info(...args),
230
+ warn: (...args) => consola.warn(...args),
231
+ error: (...args) => consola.error(...args),
232
+ debug: (...args) => consola.debug(...args),
233
+ success: (...args) => consola.success(...args)
234
+ };
235
+ }
236
+
237
+ export { EXTENSION_CATEGORIES, createExtensionConfig, createExtensionError, createExtensionLogger, extensionConfig, formatErrorMessage, handleExtensionError, isExtensionError };