@lego-box/shell 1.0.5

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 ADDED
@@ -0,0 +1,67 @@
1
+ # @lego-box/shell
2
+
3
+ Piral microfrontend shell providing authentication, PocketBase integration, RBAC, and shared UI components for the Lego Box ecosystem.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @lego-box/shell
9
+ # or
10
+ pnpm add @lego-box/shell
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ### Creating a Pilet
16
+
17
+ ```bash
18
+ pilet new @lego-box/shell --target my-pilet
19
+ cd my-pilet
20
+ pnpm install
21
+ pnpm start
22
+ ```
23
+
24
+ ### Pilet Development
25
+
26
+ ```typescript
27
+ import type { PiletApi } from '@lego-box/shell/pilet';
28
+
29
+ export function setup(app: PiletApi) {
30
+ // Access authentication
31
+ const user = app.auth.getUser();
32
+
33
+ // Access PocketBase
34
+ const records = await app.pocketbase.collection('items').getList();
35
+
36
+ // Register protected pages
37
+ app.registerProtectedPage('/my-route', MyPage, {
38
+ action: 'read',
39
+ subject: 'items'
40
+ });
41
+
42
+ // Register sidebar menu
43
+ app.registerSidebarMenu({
44
+ id: 'my-menu',
45
+ label: 'My Feature',
46
+ href: '/my-route',
47
+ action: 'read',
48
+ subject: 'items'
49
+ });
50
+ }
51
+ ```
52
+
53
+ ## Shared Dependencies
54
+
55
+ The shell provides these dependencies to all pilets:
56
+ - `@lego-box/ui-kit` - Reusable UI components
57
+ - `pocketbase` - Backend SDK
58
+ - `piral-auth` - Authentication plugin
59
+ - `react` & `react-dom` - UI framework
60
+
61
+ ## API Reference
62
+
63
+ See [full documentation](link-to-docs) for complete API reference.
64
+
65
+ ## License
66
+
67
+ MIT
package/package.json ADDED
@@ -0,0 +1,116 @@
1
+ {
2
+ "name": "@lego-box/shell",
3
+ "version": "1.0.5",
4
+ "description": "Piral microfrontend shell for Lego Box - provides authentication, PocketBase integration, RBAC, and shared UI components",
5
+ "keywords": [
6
+ "piral",
7
+ "microfrontend",
8
+ "shell",
9
+ "pocketbase",
10
+ "react",
11
+ "typescript",
12
+ "rbac",
13
+ "authentication"
14
+ ],
15
+ "license": "MIT",
16
+ "author": "Lego Box",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/lego-box/lego-box.git"
20
+ },
21
+ "homepage": "https://github.com/lego-box/lego-box#readme",
22
+ "bugs": {
23
+ "url": "https://github.com/lego-box/lego-box/issues"
24
+ },
25
+ "files": [
26
+ "dist/emulator",
27
+ "pilet.ts",
28
+ "src/types.ts",
29
+ "src/auth/ability.ts",
30
+ "README.md"
31
+ ],
32
+ "main": "dist/emulator/index.js",
33
+ "types": "pilet.ts",
34
+ "exports": {
35
+ ".": {
36
+ "types": "./pilet.ts",
37
+ "default": "./dist/emulator/index.js"
38
+ },
39
+ "./pilet": {
40
+ "types": "./pilet.ts",
41
+ "default": "./pilet.ts"
42
+ },
43
+ "./package.json": "./package.json"
44
+ },
45
+ "publishConfig": {
46
+ "access": "public",
47
+ "registry": "https://registry.npmjs.org/"
48
+ },
49
+ "peerDependencies": {
50
+ "react": "^18.2.0",
51
+ "react-dom": "^18.2.0"
52
+ },
53
+ "app": "./src/index.html",
54
+ "piral": {
55
+ "name": "@lego-box/shell",
56
+ "externals": [
57
+ "@lego-box/ui-kit",
58
+ "pocketbase",
59
+ "piral-auth",
60
+ "react",
61
+ "react-dom"
62
+ ],
63
+ "sharedDependencies": [
64
+ "@lego-box/ui-kit",
65
+ "pocketbase",
66
+ "piral-auth",
67
+ "react",
68
+ "react-dom"
69
+ ]
70
+ },
71
+ "dependencies": {
72
+ "@casl/ability": "^6.8.0",
73
+ "@casl/react": "^5.0.1",
74
+ "clsx": "^2.1.1",
75
+ "dotenv": "^17.2.3",
76
+ "framer-motion": "^12.29.2",
77
+ "lucide-react": "^0.400.0",
78
+ "piral": "1.9.2",
79
+ "piral-auth": "^1.5.0",
80
+ "pocketbase": "^0.21.0",
81
+ "process": "^0.11.10",
82
+ "react": "^18.2.0",
83
+ "react-dom": "^18.2.0",
84
+ "react-router": "^6.28.1",
85
+ "react-router-dom": "^6.28.1",
86
+ "sonner": "^2.0.7",
87
+ "tailwind-merge": "^2.6.0",
88
+ "tslib": "^2.8.1",
89
+ "zod": "^3.22.4",
90
+ "@lego-box/ui-kit": "0.1.0"
91
+ },
92
+ "devDependencies": {
93
+ "@types/node": "^20.19.30",
94
+ "@types/react": "^18.2.0",
95
+ "@types/react-dom": "^18.2.0",
96
+ "autoprefixer": "^10.4.16",
97
+ "piral-cli": "1.9.2",
98
+ "piral-cli-webpack5": "^1.9.2",
99
+ "piral-core": "^1.9.2",
100
+ "postcss": "^8.4.32",
101
+ "postcss-loader": "^8.2.0",
102
+ "tailwindcss": "^3.4.0",
103
+ "tailwindcss-animate": "^1.0.7",
104
+ "ts-node": "^10.9.2",
105
+ "typescript": "^5.3.3"
106
+ },
107
+ "scripts": {
108
+ "start": "piral debug",
109
+ "build": "piral build --type emulator-package",
110
+ "build:all": "piral build --type all",
111
+ "migrate": "ts-node src/migrations/run-migrations.ts",
112
+ "migrate:status": "ts-node src/migrations/run-migrations.ts --status",
113
+ "migrate:rollback": "ts-node src/migrations/run-migrations.ts --rollback",
114
+ "migrate:clean": "ts-node src/migrations/run-migrations.ts --clean"
115
+ }
116
+ }
package/pilet.ts ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Pilet API types for Lego Box shell.
3
+ * Import these in pilets to get proper typing for the setup context and page props.
4
+ *
5
+ * @example
6
+ * import type { PiletApi, PiletMenuItem, ProtectedPageOptions } from '@lego-box/shell/pilet';
7
+ *
8
+ * export function setup(context: PiletApi) {
9
+ * context.auth.getId();
10
+ * context.registerProtectedPage('/demo', DemoPage, { action: 'read', subject: 'users' });
11
+ * context.registerSidebarMenu({ id: 'demo', label: 'Demo', href: '/demo', action: 'read', subject: 'users' });
12
+ * }
13
+ *
14
+ * function DemoPage({ api }: { api: PiletApi }) {
15
+ * return <div>{api.auth.getEmail()}</div>;
16
+ * }
17
+ */
18
+
19
+ // Load shell types so PiletCustomApi augmentation is applied
20
+ import type {} from './src/types';
21
+
22
+ // Re-export PiletApi from Piral (includes our augmented PiletCustomApi)
23
+ export type { PiletApi } from 'piral';
24
+
25
+ // Re-export shell-specific types for pilets
26
+ export type {
27
+ PiletMenuItem,
28
+ AuthUser,
29
+ ProtectedPageOptions,
30
+ PiletAuthObject,
31
+ PiletCaslApi,
32
+ PermissionString,
33
+ } from './src/types';
@@ -0,0 +1,91 @@
1
+ import { AbilityBuilder, createMongoAbility, type MongoAbility } from '@casl/ability';
2
+
3
+ /**
4
+ * AppAbility - CASL ability type for the application.
5
+ * Subjects are collection/resource names (e.g., 'users', 'tickets').
6
+ * Actions are CRUD + custom actions (e.g., 'create', 'read', 'update', 'delete').
7
+ */
8
+ export type AppAbility = MongoAbility<[string, string]>;
9
+
10
+ export interface UserWithPermissions {
11
+ id?: string;
12
+ is_superuser?: boolean;
13
+ role?: string;
14
+ }
15
+
16
+ /**
17
+ * Parses a permission string in "action:collection" format into { action, subject }.
18
+ * Handles malformed or empty strings.
19
+ */
20
+ export function parsePermissionString(
21
+ permissionName: string
22
+ ): { action: string; subject: string } | null {
23
+ if (!permissionName || typeof permissionName !== 'string') {
24
+ return null;
25
+ }
26
+ const trimmed = permissionName.trim();
27
+ if (!trimmed) return null;
28
+
29
+ const colonIndex = trimmed.indexOf(':');
30
+ if (colonIndex === -1) return null;
31
+
32
+ const action = trimmed.slice(0, colonIndex).trim();
33
+ const subject = trimmed.slice(colonIndex + 1).trim();
34
+
35
+ if (!action || !subject) return null;
36
+
37
+ return { action, subject };
38
+ }
39
+
40
+ /** Permission for CASL rule: action + subject (collection). */
41
+ export interface PermissionDefinition {
42
+ action: string;
43
+ subject: string;
44
+ }
45
+
46
+ /**
47
+ * Creates a CASL ability instance for the given user and permissions.
48
+ *
49
+ * - For superusers: grants all permissions using can('manage', 'all')
50
+ * - For regular users: iterates through permission definitions and adds
51
+ * rules using can(action, subject)
52
+ * - Accepts permission objects with action/subject or "action:collection" strings
53
+ * - Handles empty or malformed permissions gracefully
54
+ */
55
+ export function defineAbilityFor(
56
+ user: UserWithPermissions | null | undefined,
57
+ permissions: (PermissionDefinition | string)[] = []
58
+ ): AppAbility {
59
+ const { can, build } = new AbilityBuilder<AppAbility>(createMongoAbility);
60
+
61
+ if (!user) {
62
+ return build();
63
+ }
64
+
65
+ if (user.is_superuser === true) {
66
+ can('manage', 'all');
67
+ return build();
68
+ }
69
+
70
+ const permissionArray = Array.isArray(permissions) ? permissions : [];
71
+ for (const perm of permissionArray) {
72
+ let action: string;
73
+ let subject: string;
74
+ if (typeof perm === 'object' && perm !== null && 'action' in perm && 'subject' in perm) {
75
+ action = String(perm.action).trim();
76
+ subject = String(perm.subject).trim();
77
+ } else {
78
+ const parsed = parsePermissionString(
79
+ typeof perm === 'string' ? perm : String(perm)
80
+ );
81
+ if (!parsed) continue;
82
+ action = parsed.action;
83
+ subject = parsed.subject;
84
+ }
85
+ if (action && subject) {
86
+ can(action, subject);
87
+ }
88
+ }
89
+
90
+ return build();
91
+ }
package/src/types.ts ADDED
@@ -0,0 +1,282 @@
1
+ import type PocketBase from 'pocketbase';
2
+ import type * as React from 'react';
3
+
4
+ /** Re-export CASL ability type for convenience. */
5
+ export type { AppAbility } from './auth/ability';
6
+
7
+ /** Result of a permission check. */
8
+ export interface PermissionCheck {
9
+ action: string;
10
+ subject: string;
11
+ allowed: boolean;
12
+ }
13
+
14
+ /**
15
+ * Menu item structure for sidebar navigation.
16
+ * Supports both single items and nested children.
17
+ * When action and subject are provided, the shell will hide the menu item
18
+ * if the user does not have the required CASL permission.
19
+ */
20
+ export interface PiletMenuItem {
21
+ /** Unique identifier for the menu item */
22
+ id: string;
23
+ /** Display label for the menu */
24
+ label: string;
25
+ /** Route path to navigate to when clicked */
26
+ href?: string;
27
+ /** Optional icon component */
28
+ icon?: React.ReactNode;
29
+ /** Child menu items for nested menus */
30
+ children?: PiletMenuItem[];
31
+ /** For flat structure: parent menu ID (alternative to nested children) */
32
+ parent?: string;
33
+ /** CASL action required to show this menu (e.g. 'read'). When set with subject, menu is hidden if user lacks permission. */
34
+ action?: string;
35
+ /** CASL subject required to show this menu (e.g. 'users'). When set with action, menu is hidden if user lacks permission. */
36
+ subject?: string;
37
+ }
38
+
39
+ /**
40
+ * Custom test API extension for demonstration.
41
+ * Use app.test.getTesting() in pilets to verify plugin connectivity.
42
+ */
43
+ export interface TestApi {
44
+ /**
45
+ * Returns a greeting message to verify the PocketBase plugin is working.
46
+ */
47
+ getTesting(): string;
48
+ }
49
+
50
+ /**
51
+ * Extended API exposed to all pilets via the PocketBase plugin.
52
+ *
53
+ * Usage in pilet:
54
+ * ```tsx
55
+ * export function setup(app: PiletContext) {
56
+ * const users = await app.pocketbase.collection('users').getList(1, 20);
57
+ * const msg = app.test.getTesting();
58
+ * }
59
+ * ```
60
+ */
61
+ export interface PiletPocketBaseApi {
62
+ /** Full PocketBase SDK instance – use for all PocketBase operations */
63
+ pocketbase: PocketBase;
64
+ /** Custom wrapper for testing and common operations */
65
+ test: TestApi;
66
+ }
67
+
68
+ /** User representation for auth (PocketBase user or admin). */
69
+ export interface AuthUser {
70
+ id: string;
71
+ email: string;
72
+ name: string;
73
+ avatar?: string;
74
+ }
75
+
76
+ /** Extended RBAC User with role information. */
77
+ export interface RBACUser extends AuthUser {
78
+ role: string;
79
+ roleName?: string;
80
+ is_superuser: boolean;
81
+ isActive: boolean;
82
+ verified?: boolean;
83
+ permissions: string[];
84
+ created?: string;
85
+ updated?: string;
86
+ }
87
+
88
+ /** Distinguishes regular user vs PocketBase admin. */
89
+ export type UserType = 'user' | 'admin' | null;
90
+
91
+ /** RBAC Permission action types. */
92
+ export type PermissionAction = 'create' | 'read' | 'update' | 'delete';
93
+
94
+ /** RBAC Permission definition. */
95
+ export interface Permission {
96
+ id: string;
97
+ name: string;
98
+ action: PermissionAction;
99
+ collection: string;
100
+ description?: string;
101
+ resourceType: 'collection' | 'field' | 'custom';
102
+ resourceName?: string;
103
+ }
104
+
105
+ /** RBAC Role definition. */
106
+ export interface Role {
107
+ id: string;
108
+ name: string;
109
+ description?: string;
110
+ permissions: string[] | Permission[];
111
+ isDefault?: boolean;
112
+ permissionCount?: number;
113
+ userCount?: number;
114
+ created?: string;
115
+ updated?: string;
116
+ }
117
+
118
+ /** Audit log action types. */
119
+ export type AuditLogAction = 'create' | 'update' | 'delete' | 'login' | 'logout';
120
+
121
+ /** Audit log entry for tracking user actions. */
122
+ export interface AuditLog {
123
+ id: string;
124
+ action: AuditLogAction;
125
+ resource: string;
126
+ resourceId: string;
127
+ resourceName: string;
128
+ userId: string;
129
+ userName: string;
130
+ userEmail: string;
131
+ details?: Record<string, any>;
132
+ ipAddress?: string;
133
+ userAgent?: string;
134
+ timestamp: string;
135
+ created?: string;
136
+ }
137
+
138
+ /** Migration tracking entry. */
139
+ export interface Migration {
140
+ id: string;
141
+ filename: string;
142
+ timestamp: number;
143
+ name: string;
144
+ status: 'pending' | 'applied' | 'failed' | 'rolled_back';
145
+ appliedAt: string | null;
146
+ errorMessage?: string;
147
+ checksum: string;
148
+ batch: number;
149
+ }
150
+
151
+ /** Migration history entry for audit trail. */
152
+ export interface MigrationHistory {
153
+ id: string;
154
+ migrationId: string;
155
+ action: 'apply' | 'rollback' | 'fail';
156
+ executedAt: string;
157
+ executedBy: string;
158
+ errorMessage?: string;
159
+ duration: number;
160
+ }
161
+
162
+ /** Ticket category types for support system. */
163
+ export type TicketCategory = 'bug' | 'feature' | 'inquiry';
164
+
165
+ /** Ticket priority levels. */
166
+ export type TicketPriority = 'low' | 'medium' | 'high';
167
+
168
+ /** Ticket status types. */
169
+ export type TicketStatus = 'open' | 'in_progress' | 'resolved' | 'closed';
170
+
171
+ /** Support ticket definition for centralized ticketing system. */
172
+ export interface Ticket {
173
+ id: string;
174
+ app_name: string;
175
+ app_identifier: string;
176
+ title: string;
177
+ description: string;
178
+ category: TicketCategory;
179
+ priority: TicketPriority;
180
+ status: TicketStatus;
181
+ created_by?: string;
182
+ assigned_to?: string;
183
+ resolved_at?: string;
184
+ closed_at?: string;
185
+ created: string;
186
+ updated: string;
187
+ }
188
+
189
+ /** Permission string in "action:subject" format (e.g. "read:users"). */
190
+ export type PermissionString = string;
191
+
192
+ /**
193
+ * Auth API exposed to pilets (getUser from piral-auth; login/logout/getUserType from our plugin).
194
+ */
195
+ export interface PiletAuthApi {
196
+ getUser(): AuthUser | undefined;
197
+ getUserId(): string | null;
198
+ isAuthenticated(): boolean;
199
+ getUserType(): UserType;
200
+ login(email: string, password: string): Promise<void>;
201
+ logout(): void;
202
+ }
203
+
204
+ /**
205
+ * Unified auth object exposed to pilets via app.auth.
206
+ * Provides convenient access to user info, role, permissions, and authorization.
207
+ */
208
+ export interface PiletAuthObject {
209
+ /** User ID. */
210
+ getId(): string | null;
211
+ /** User email. */
212
+ getEmail(): string | null;
213
+ /** User role name (e.g. "Admin"). Returns null if not loaded or no role. */
214
+ getRole(): string | null;
215
+ /** All permissions assigned to the user's role (e.g. ["read:users", "create:users"]). */
216
+ getPermissions(): PermissionString[];
217
+ /** CASL check: returns true if user can perform action on subject. */
218
+ can(action: string, subject: string): boolean;
219
+ getUser(): AuthUser | undefined;
220
+ isAuthenticated(): boolean;
221
+ login(email: string, password: string): Promise<void>;
222
+ logout(): void;
223
+ }
224
+
225
+ /**
226
+ * CASL authorization API exposed to pilets via app.casl.
227
+ * Use for permission checks in pilet components and pages.
228
+ */
229
+ export interface PiletCaslApi {
230
+ /** Returns true if the current user can perform the given action on the subject. */
231
+ can(action: string, subject: string): boolean;
232
+ }
233
+
234
+ /** Options for registerProtectedPage – route-level permission guard. */
235
+ export interface ProtectedPageOptions {
236
+ /** CASL action (e.g. "read") */
237
+ action: string;
238
+ /** CASL subject (e.g. "users") */
239
+ subject: string;
240
+ }
241
+
242
+ /**
243
+ * Extended API with auth, casl, and protected page registration.
244
+ */
245
+ export interface PiletAuthCaslApi {
246
+ auth: PiletAuthObject;
247
+ casl: PiletCaslApi;
248
+ /**
249
+ * Register a page route with permission guard. User must have can(action, subject) to view.
250
+ * Shows AccessDenied when permission is lacking (same as core ProtectedPage).
251
+ */
252
+ registerProtectedPage(
253
+ route: string,
254
+ Component: React.ComponentType,
255
+ options: ProtectedPageOptions
256
+ ): void;
257
+ }
258
+
259
+ /**
260
+ * Menu API exposed to pilets for dynamic sidebar menu registration.
261
+ * Pilets can register single or multilevel menu items.
262
+ */
263
+ export interface PiletMenuApi {
264
+ /**
265
+ * Register a menu item in the sidebar.
266
+ * @param menuItem - The menu item to register
267
+ */
268
+ registerSidebarMenu(menuItem: PiletMenuItem): void;
269
+ /**
270
+ * Unregister a menu item from the sidebar.
271
+ * @param id - The unique ID of the menu item to remove
272
+ */
273
+ unregisterSidebarMenu(id: string): void;
274
+ }
275
+
276
+ /**
277
+ * Module augmentation: extends Piral's PiletCustomApi so pilets get
278
+ * app.pocketbase, app.test, app.auth, app.casl, and menu registration with full TypeScript IntelliSense.
279
+ */
280
+ declare module 'piral-core/lib/types/custom' {
281
+ interface PiletCustomApi extends PiletPocketBaseApi, PiletAuthApi, PiletMenuApi, PiletAuthCaslApi {}
282
+ }