@logickernel/bridge 0.8.0 → 0.8.2

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 CHANGED
@@ -1,6 +1,6 @@
1
- # Bridge
1
+ # @logickernel/bridge
2
2
 
3
- Framework-agnostic micro-frontend authentication library for AuthJS/NextAuth JWT tokens.
3
+ Framework-agnostic micro-frontend authentication library for AuthJS/NextAuth JWT tokens with built-in Next.js components.
4
4
 
5
5
  ## Installation
6
6
 
@@ -12,72 +12,135 @@ pnpm add @logickernel/bridge
12
12
  yarn add @logickernel/bridge
13
13
  ```
14
14
 
15
- ## Features
16
-
17
- - **Framework-agnostic core** - JWT decoding and validation works anywhere
18
- - **Next.js adapter** - First-class support for Next.js App Router
19
- - **Role-based access control** - Check user roles against organization permissions
20
- - **TypeScript-first** - Full type safety with detailed type exports
21
- - **Dual ESM/CJS** - Works in any JavaScript environment
15
+ ## Quick Start (Next.js)
22
16
 
23
- ## Quick Start
17
+ ### 1. Environment Variables
24
18
 
25
- ### Environment Variables
19
+ Add these to your `.env` file:
26
20
 
27
21
  ```bash
28
22
  AUTH_SECRET=your-nextauth-secret # Required: Same secret used by your kernel/auth server
29
- AUTH_URL=http://localhost:3000 # Required: Used internally for API calls to the kernel
30
- BASE_URL=http://localhost:7001 # Optional: Facade URL for user-facing redirects (used by getBaseUrl())
31
- AUTH_COOKIE=authjs.session-token # Optional: Cookie name for session token (defaults to authjs.session-token)
23
+ AUTH_URL=http://localhost:3000 # Required: Base URL of your kernel/auth server
24
+ BASE_URL=http://localhost:7001 # Optional: Your facade URL for user-facing redirects
32
25
  ```
33
26
 
34
- ### Core Usage (Framework-Agnostic)
27
+ ### 2. Configure Next.js
28
+
29
+ In your `next.config.ts`, add the bridge package to `transpilePackages`:
35
30
 
36
31
  ```typescript
37
- import {
38
- decodeSessionToken,
39
- extractSessionCookie,
40
- fetchUserRoles,
41
- hasAnyRole,
42
- } from "@logickernel/bridge"
32
+ import type { NextConfig } from "next"
43
33
 
44
- // Decode a JWT session token
45
- const result = await decodeSessionToken(token, secret, isSecure)
46
- if (result.success) {
47
- console.log(result.session.user.email)
48
- console.log(result.session.user.id)
49
- } else {
50
- console.error(result.error) // "missing_token" | "missing_secret" | "decode_error" | "expired"
34
+ const nextConfig: NextConfig = {
35
+ transpilePackages: ["@logickernel/bridge"],
36
+ // ... other config
51
37
  }
52
38
 
53
- // Check user roles (uses AUTH_URL environment variable internally)
54
- // User ID is determined from the session token
55
- const roles = await fetchUserRoles({
56
- organizationId: "org-456",
57
- sessionToken: token,
58
- })
39
+ export default nextConfig
40
+ ```
41
+
42
+ ### 3. Use the Layout Component
43
+
44
+ The library provides a ready-to-use layout component with sidebar navigation. Import `AppLayoutWrapper` directly from the library:
45
+
46
+ In your `app/app/layout.tsx` (or wherever your app layout is):
47
+
48
+ ```typescript
49
+ import { redirect } from "next/navigation"
50
+ import { auth } from "@/lib/next-auth" // or your auth setup
51
+ import { AppLayoutWrapper } from "@logickernel/bridge/next-components"
52
+
53
+ export default async function Layout({
54
+ children,
55
+ }: {
56
+ children: React.ReactNode
57
+ }) {
58
+ const session = await auth()
59
59
 
60
- if (roles.success && hasAnyRole(roles.roles, ["organization.owner"])) {
61
- // User is an owner
60
+ if (!session?.user) {
61
+ redirect("/auth/signin?callbackUrl=/app")
62
+ }
63
+
64
+ return (
65
+ <AppLayoutWrapper>
66
+ {children}
67
+ </AppLayoutWrapper>
68
+ )
62
69
  }
63
70
  ```
64
71
 
65
- ### Next.js Usage
72
+ **Optional: Pass organization ID and API base URL**
66
73
 
67
74
  ```typescript
68
- import {
69
- getSession,
70
- getUserRoles,
71
- withAuth,
72
- checkPageAuth,
73
- createProxyHandler,
74
- } from "@logickernel/bridge/next"
75
+ <AppLayoutWrapper
76
+ organizationId={organizationId}
77
+ apiBaseUrl={apiBaseUrl}
78
+ >
79
+ {children}
80
+ </AppLayoutWrapper>
75
81
  ```
76
82
 
77
- #### Server Components / Pages
83
+ The `user` prop is **not needed** - the layout automatically fetches user information from the navigation API endpoint.
84
+
85
+ ### 4. Navigation API Endpoint
86
+
87
+ The layout component automatically loads navigation items from your API. The **Kernel Core application** provides this endpoint at `/api/navigation/[organization_id]`.
88
+
89
+ **Required Endpoint:** `GET /api/navigation/[organization_id]`
90
+
91
+ **Response Payload:**
92
+
93
+ ```typescript
94
+ {
95
+ items: NavigationItem[]
96
+ organizationId: string
97
+ organizations: NavigationOrganization[]
98
+ user: {
99
+ id: string
100
+ name: string | null
101
+ email: string
102
+ image: string | null
103
+ }
104
+ }
105
+
106
+ interface NavigationItem {
107
+ title: string
108
+ url?: string // If missing, item is treated as a section label
109
+ icon?: string // Lucide icon name (e.g., "LayoutDashboard", "Users")
110
+ isActive?: boolean // Whether the item should be highlighted
111
+ items?: { // Sub-items for collapsible navigation
112
+ title: string
113
+ url: string
114
+ }[]
115
+ }
116
+
117
+ interface NavigationOrganization {
118
+ id: string
119
+ name: string
120
+ logo?: string // Lucide icon name for the organization logo
121
+ plan?: string // Optional plan/badge text
122
+ }
123
+ ```
124
+
125
+ **Note:** The endpoint should be protected and only return data the authenticated user has access to. The `{organizationId}` placeholder in item URLs will be automatically replaced with the current organization ID.
126
+
127
+ ## Features
128
+
129
+ The `AppLayout` component provides:
130
+
131
+ - ✅ **Built-in sidebar navigation** with collapsible sections
132
+ - ✅ **Organization switcher** - Switch between organizations the user belongs to
133
+ - ✅ **User menu** with profile information and sign-out
134
+ - ✅ **Responsive design** - Works on mobile and desktop
135
+ - ✅ **Automatic navigation loading** - Fetches from your API endpoint
136
+ - ✅ **Consistent UI** - Same look and feel across all microfrontends using the library
137
+ - ✅ **No extra components required** - All UI components are included in the library
138
+
139
+ ## Next.js Utilities
140
+
141
+ ### Server Components / Pages
78
142
 
79
143
  ```typescript
80
- // app/dashboard/page.tsx
81
144
  import { getSession } from "@logickernel/bridge/next"
82
145
  import { redirect } from "next/navigation"
83
146
 
@@ -92,42 +155,39 @@ export default async function DashboardPage() {
92
155
  }
93
156
  ```
94
157
 
95
- #### Role-Based Page Protection
158
+ ### Role-Based Page Protection
96
159
 
97
160
  ```typescript
98
- // app/[org_id]/admin/page.tsx
99
161
  import { checkPageAuth } from "@logickernel/bridge/next"
100
162
  import { redirect, notFound } from "next/navigation"
101
163
 
102
164
  export default async function AdminPage({
103
165
  params,
104
166
  }: {
105
- params: Promise<{ org_id: string }>
167
+ params: Promise<{ organization_id: string }>
106
168
  }) {
107
- const { org_id } = await params
169
+ const { organization_id } = await params
108
170
 
109
171
  const auth = await checkPageAuth({
110
172
  requiredRoles: ["organization.owner", "organization.editor"],
111
- organizationId: org_id,
173
+ organizationId: organization_id,
112
174
  })
113
175
 
114
176
  if (!auth.authenticated) {
115
177
  redirect(auth.redirectUrl)
116
178
  }
117
179
 
118
- // User is authenticated but may not have required roles
119
- if (!auth.roles.some(r => ["organization.owner", "organization.editor"].includes(r))) {
180
+ if (!auth.hasRequiredRole) {
120
181
  notFound()
121
182
  }
122
183
 
123
- return <div>Admin Panel for {session.user.email}</div>
184
+ return <div>Admin Panel for {auth.session.user.email}</div>
124
185
  }
125
186
  ```
126
187
 
127
- #### API Routes
188
+ ### API Routes
128
189
 
129
190
  ```typescript
130
- // app/api/data/route.ts
131
191
  import { withAuth } from "@logickernel/bridge/next"
132
192
  import { NextResponse } from "next/server"
133
193
 
@@ -152,10 +212,10 @@ export const DELETE = withAuth(
152
212
  )
153
213
  ```
154
214
 
155
- #### Middleware / Proxy
215
+ ### Middleware / Proxy
156
216
 
157
217
  ```typescript
158
- // src/proxy.ts (or middleware.ts)
218
+ // middleware.ts or proxy.ts
159
219
  import { createProxyHandler, type ProxyOptions } from "@logickernel/bridge/next"
160
220
 
161
221
  const config: ProxyOptions = {
@@ -166,7 +226,7 @@ const config: ProxyOptions = {
166
226
  },
167
227
  dynamicRoutes: [
168
228
  {
169
- pattern: "/[organization_id]/admin",
229
+ pattern: "/app/[organization_id]/admin",
170
230
  requiredRoles: ["organization.owner"],
171
231
  },
172
232
  ],
@@ -174,174 +234,27 @@ const config: ProxyOptions = {
174
234
 
175
235
  export const proxy = createProxyHandler(config)
176
236
 
177
- // Static config for Next.js (must be in the same file)
178
- export const middlewareConfig = {
237
+ // Static config for Next.js middleware
238
+ export const config = {
179
239
  matcher: ["/((?!_next/static|_next/image|favicon.ico|public).*)"],
180
240
  }
181
241
  ```
182
242
 
183
- #### Layout Components
184
-
185
- The bridge library provides ready-to-use layout components with built-in navigation connected to the Core of the Kernel.
186
-
187
- ```typescript
188
- // app/app/layout.tsx (or your app layout)
189
- "use client"
190
-
191
- import { AppLayout, type User } from "@logickernel/bridge/next/components"
192
- import { useSession } from "next-auth/react"
193
-
194
- export default function Layout({ children }: { children: React.ReactNode }) {
195
- const { data: session } = useSession()
196
-
197
- if (!session?.user) {
198
- return <>{children}</>
199
- }
200
-
201
- const user: User = {
202
- name: session.user.name || null,
203
- email: session.user.email || "",
204
- image: session.user.image || null,
205
- }
206
-
207
- return (
208
- <AppLayout
209
- user={user}
210
- organizationId={organizationId} // Optional: Current organization ID
211
- apiBaseUrl={apiBaseUrl} // Optional: API base URL for navigation
212
- >
213
- {children}
214
- </AppLayout>
215
- )
216
- }
217
- ```
218
-
219
- **For Server Components:**
220
-
221
- ```typescript
222
- // app/app/layout.tsx
223
- import { AppLayout, type User } from "@logickernel/bridge/next/components"
224
- import { redirect } from "next/navigation"
225
- import { auth } from "@/lib/next-auth" // or your NextAuth setup
226
-
227
- export default async function Layout({
228
- children,
229
- params,
230
- }: {
231
- children: React.ReactNode
232
- params: Promise<{ organization_id?: string }>
233
- }) {
234
- const session = await auth()
235
-
236
- if (!session?.user) {
237
- redirect("/auth/signin")
238
- }
239
-
240
- const { organization_id } = await params
241
-
242
- const user: User = {
243
- name: session.user.name || null,
244
- email: session.user.email || "",
245
- image: session.user.image || null,
246
- }
247
-
248
- return (
249
- <AppLayout
250
- user={user}
251
- organizationId={organization_id}
252
- apiBaseUrl={process.env.AUTH_URL}
253
- >
254
- {children}
255
- </AppLayout>
256
- )
257
- }
258
- ```
259
-
260
- **Note:** You can also use `getSession()` from `@logickernel/bridge/next` instead of NextAuth's `auth()` function. Both will work, but NextAuth's `auth()` is recommended if you're using NextAuth v5.
261
-
262
- **Features:**
263
- - ✅ Built-in sidebar navigation with organization switcher
264
- - ✅ User menu with sign-out
265
- - ✅ Responsive design (mobile/desktop)
266
- - ✅ Automatic navigation loading from your API
267
- - ✅ No component passing required - UI components are included in the library
268
- - ✅ Consistent UI across all microfrontends using the bridge
243
+ ## Import Paths
269
244
 
270
- The `AppLayout` component automatically:
271
- - Loads navigation items from `/api/navigation/[organization_id]` endpoint
272
- - Displays organizations the user belongs to
273
- - Handles organization switching
274
- - Provides user profile menu with sign-out
245
+ The library uses top-level exports (no subpath exports) for better bundler compatibility:
275
246
 
276
- **Required API Endpoint:**
277
-
278
- The navigation requires an endpoint at `/api/navigation/[organization_id]` that returns:
279
247
  ```typescript
280
- {
281
- items: NavigationItem[],
282
- organizations: NavigationOrganization[]
283
- }
284
- ```
248
+ // Core utilities (framework-agnostic)
249
+ import { decodeSessionToken, fetchUserRoles } from "@logickernel/bridge"
285
250
 
286
- **Type Definitions:**
287
- ```typescript
288
- interface NavigationItem {
289
- title: string
290
- url?: string // If missing, item is treated as a section label
291
- icon?: string // Icon name (mapped using getIconComponent)
292
- isActive?: boolean // Whether the item should be highlighted
293
- items?: { // Sub-items for collapsible navigation
294
- title: string
295
- url: string
296
- }[]
297
- }
251
+ // Next.js utilities (server components, API routes, middleware)
252
+ import { getSession, withAuth, checkPageAuth } from "@logickernel/bridge/next"
298
253
 
299
- interface NavigationOrganization {
300
- id: string
301
- name: string
302
- logo?: string // Icon name for the organization logo
303
- plan?: string // Optional plan/badge text
304
- }
254
+ // Layout components (client components)
255
+ import { AppLayout, type User } from "@logickernel/bridge/next-components"
305
256
  ```
306
257
 
307
- **Example Response:**
308
- ```json
309
- {
310
- "items": [
311
- {
312
- "title": "Dashboard",
313
- "url": "/app/{organizationId}/home",
314
- "icon": "LayoutDashboard"
315
- },
316
- {
317
- "title": "Team",
318
- "icon": "Users" // Section label (no URL)
319
- },
320
- {
321
- "title": "Members",
322
- "url": "/app/{organizationId}/members",
323
- "icon": "User",
324
- "items": [
325
- {
326
- "title": "All Members",
327
- "url": "/app/{organizationId}/members"
328
- }
329
- ]
330
- }
331
- ],
332
- "organizations": [
333
- {
334
- "id": "org-123",
335
- "name": "Acme Corp",
336
- "logo": "Building",
337
- "plan": "Pro"
338
- }
339
- ]
340
- }
341
- ```
342
-
343
- **Note:** The `{organizationId}` placeholder in URLs will be automatically replaced with the current organization ID. The endpoint should be protected and only return data the current user has access to.
344
-
345
258
  ## API Reference
346
259
 
347
260
  ### Core Exports (`@logickernel/bridge`)
@@ -350,13 +263,10 @@ interface NavigationOrganization {
350
263
  |--------|-------------|
351
264
  | `decodeSessionToken(token, secret, isSecure)` | Decode and validate a JWT session token |
352
265
  | `extractSessionCookie(cookies)` | Extract session cookie from a cookies object |
353
- | `fetchUserRoles(options)` | Fetch user roles from the kernel API (uses AUTH_URL internally) |
266
+ | `fetchUserRoles(options)` | Fetch user roles from the kernel API |
354
267
  | `hasAnyRole(userRoles, requiredRoles)` | Check if user has at least one required role |
355
268
  | `hasAllRoles(userRoles, requiredRoles)` | Check if user has all required roles |
356
269
  | `hasRole(userRoles, role)` | Check if user has a specific role |
357
- | `matchRoute(pathname, config)` | Match a pathname against route configuration |
358
- | `buildSignInUrl(callbackUrl?)` | Build a sign-in redirect URL (uses AUTH_URL internally) |
359
- | `buildSignOutUrl(callbackUrl?)` | Build a sign-out redirect URL (uses AUTH_URL internally) |
360
270
 
361
271
  ### Next.js Exports (`@logickernel/bridge/next`)
362
272
 
@@ -364,63 +274,68 @@ interface NavigationOrganization {
364
274
  |--------|-------------|
365
275
  | `getSession()` | Get the current session from cookies |
366
276
  | `getSessionToken()` | Get the raw session token value |
367
- | `getUserRoles(orgId)` | Get user roles in an organization (user ID determined from session token) |
277
+ | `getUserRoles(orgId)` | Get user roles in an organization |
368
278
  | `withAuth(handler, options?)` | Wrap an API route with authentication |
369
- | `getSessionFromRequest(request)` | Get session from a NextRequest |
370
279
  | `checkPageAuth(options?)` | Check authentication for a page |
371
280
  | `requireAuth()` | Require authentication (throws if not authenticated) |
372
- | `hasRequiredRole(orgId, roles)` | Check if user has required roles (user ID determined from session token) |
281
+ | `hasRequiredRole(orgId, roles)` | Check if user has required roles |
373
282
  | `createProxyHandler(options)` | Create a middleware/proxy handler |
374
283
 
375
- ### Layout Components (`@logickernel/bridge/next/components`)
284
+ ### Layout Components (`@logickernel/bridge/next-components`)
376
285
 
377
286
  | Export | Description |
378
287
  |--------|-------------|
379
- | `AppLayout` | Main layout component with sidebar navigation |
380
- | `AppSidebar` | Sidebar component (used internally by AppLayout) |
381
- | `NavMain` | Main navigation component (used internally) |
382
- | `NavUser` | User menu component (used internally) |
383
- | `TeamSwitcher` | Organization switcher component (used internally) |
288
+ | `AppLayout` | Main layout component with sidebar navigation (use `AppLayoutWrapper` in server components) |
289
+ | `AppLayoutWrapper` | Wrapper component for use in server components (recommended) |
384
290
  | `useNavigation` | Hook to fetch navigation items from API |
385
291
  | `getIconComponent` | Utility to get icon component from icon name |
386
292
 
387
293
  **AppLayout Props:**
294
+
388
295
  ```typescript
389
296
  interface AppLayoutProps {
390
- user: User // User information (name, email, image)
391
- organizationId?: string // Optional: Current organization ID
392
- apiBaseUrl?: string // Optional: API base URL for navigation
393
- children: React.ReactNode // Page content
297
+ user?: User // Optional: User information (auto-fetched if not provided)
298
+ organizationId?: string // Optional: Current organization ID
299
+ apiBaseUrl?: string // Optional: API base URL (defaults to "/api")
300
+ children: React.ReactNode // Page content
394
301
  }
395
302
  ```
396
303
 
397
- ### Types
304
+ ## Why AppLayoutWrapper?
305
+
306
+ The library provides `AppLayoutWrapper` as a convenience wrapper for `AppLayout` because:
307
+
308
+ 1. **React Context Requirement**: `AppLayout` uses React Context internally (for sidebar state)
309
+ 2. **SSR Build Compatibility**: Next.js requires a clean client/server boundary when client components with Context are used in server components
310
+ 3. **Simplified Usage**: Using `AppLayoutWrapper` directly avoids React resolution issues during build-time analysis
311
+
312
+ You can use `AppLayoutWrapper` directly in server components without creating your own wrapper.
313
+
314
+ ## TypeScript Types
398
315
 
399
316
  ```typescript
317
+ // Core types
400
318
  import type {
401
319
  Session,
402
320
  SessionUser,
403
321
  DecodeResult,
404
- RouteConfig,
405
- RouteMatch,
406
- MicroFrontendConfig,
407
322
  } from "@logickernel/bridge"
408
323
 
324
+ // Next.js types
409
325
  import type {
410
326
  ProxyOptions,
411
- DynamicRoutePattern,
412
327
  AuthContext,
413
328
  AuthenticatedHandler,
414
- WithAuthOptions,
415
329
  PageAuthOptions,
416
330
  PageAuthResult,
417
331
  } from "@logickernel/bridge/next"
418
332
 
333
+ // Component types
419
334
  import type {
420
335
  User,
421
336
  NavigationItem,
422
337
  NavigationOrganization,
423
- } from "@logickernel/bridge/next/components"
338
+ } from "@logickernel/bridge/next-components"
424
339
  ```
425
340
 
426
341
  ## Architecture
@@ -428,26 +343,23 @@ import type {
428
343
  ```
429
344
  bridge/
430
345
  ├── src/
431
- │ ├── index.ts # Core exports
432
- │ ├── types.ts # Core type definitions
433
- │ ├── jwt.ts # JWT decoding utilities
434
- │ ├── roles.ts # Role checking utilities
435
- │ ├── routes.ts # Route matching utilities
346
+ │ ├── index.ts # Core exports (framework-agnostic)
347
+ │ ├── jwt.ts # JWT decoding utilities
348
+ │ ├── roles.ts # Role checking utilities
436
349
  │ └── next/
437
- │ ├── index.ts # Next.js adapter exports
438
- │ ├── types.ts # Next.js-specific types
439
- │ ├── session.ts # Session utilities
440
- │ ├── api.ts # API route utilities
441
- │ ├── page.ts # Page utilities
442
- │ ├── proxy.ts # Middleware/proxy utilities
350
+ │ ├── index.ts # Next.js adapter exports
351
+ │ ├── session.ts # Session utilities
352
+ │ ├── api.ts # API route utilities
353
+ │ ├── page.ts # Page utilities
354
+ │ ├── proxy.ts # Middleware/proxy utilities
443
355
  │ └── components/
444
- │ ├── app-layout.tsx # Main layout component
445
- │ ├── app-sidebar.tsx # Sidebar component
446
- │ ├── nav-main.tsx # Navigation component
447
- │ ├── nav-user.tsx # User menu component
448
- │ ├── team-switcher.tsx # Organization switcher
449
- │ ├── use-navigation.ts # Navigation hook
450
- │ └── ui/ # Internal UI components (shadcn)
356
+ │ ├── app-layout.tsx # Main layout component
357
+ │ ├── app-sidebar.tsx # Sidebar component
358
+ │ ├── nav-main.tsx # Navigation component
359
+ │ ├── nav-user.tsx # User menu component
360
+ │ ├── team-switcher.tsx # Organization switcher
361
+ │ ├── use-navigation.ts # Navigation hook
362
+ │ └── ui/ # Internal UI components (shadcn)
451
363
  ```
452
364
 
453
365
  ## Development
@@ -1335,13 +1335,28 @@ function AppLayout({
1335
1335
  ] })
1336
1336
  ] });
1337
1337
  }
1338
+ function AppLayoutWrapper({
1339
+ organizationId,
1340
+ apiBaseUrl,
1341
+ children
1342
+ }) {
1343
+ return /* @__PURE__ */ jsxRuntime.jsx(
1344
+ AppLayout,
1345
+ {
1346
+ organizationId,
1347
+ apiBaseUrl,
1348
+ children
1349
+ }
1350
+ );
1351
+ }
1338
1352
 
1339
1353
  exports.AppLayout = AppLayout;
1354
+ exports.AppLayoutWrapper = AppLayoutWrapper;
1340
1355
  exports.AppSidebar = AppSidebar;
1341
1356
  exports.NavMain = NavMain;
1342
1357
  exports.NavUser = NavUser;
1343
1358
  exports.TeamSwitcher = TeamSwitcher;
1344
1359
  exports.getIconComponent = getIconComponent;
1345
1360
  exports.useNavigation = useNavigation;
1346
- //# sourceMappingURL=components.cjs.map
1347
- //# sourceMappingURL=components.cjs.map
1361
+ //# sourceMappingURL=next-components.cjs.map
1362
+ //# sourceMappingURL=next-components.cjs.map