@peanut-admin/admin 0.1.0-alpha.4 → 0.1.0-alpha.6

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,37 @@
1
+ import { hasPermission } from './access'
2
+ import { defineAdminOverrideSlot } from '../runtime/overrides'
3
+
4
+ export type PermissionEvaluator = (
5
+ permissions: ReadonlySet<string>,
6
+ permission: string,
7
+ ) => boolean
8
+
9
+ export const PERMISSION_EVALUATOR_OVERRIDE_KEY =
10
+ 'authorization.permission.service.evaluator' as const
11
+
12
+ const isPermissionEvaluator = (candidate: unknown): candidate is PermissionEvaluator => (
13
+ typeof candidate === 'function'
14
+ )
15
+
16
+ export const permissionEvaluatorSlot = defineAdminOverrideSlot({
17
+ key: PERMISSION_EVALUATOR_OVERRIDE_KEY,
18
+ kind: 'service' as const,
19
+ contractVersion: '1.0.0',
20
+ defaultValue: hasPermission as PermissionEvaluator,
21
+ validate: isPermissionEvaluator,
22
+ })
23
+
24
+ export const evaluateRequiredPermissions = (
25
+ requiredPermissions: string | string[],
26
+ grantedPermissions: readonly string[],
27
+ evaluator: PermissionEvaluator,
28
+ ): boolean => {
29
+ const required = (Array.isArray(requiredPermissions) ? requiredPermissions : [requiredPermissions])
30
+ .filter(Boolean)
31
+ if (required.length === 0) return true
32
+
33
+ const permissionSet = new Set(grantedPermissions)
34
+ return permissionSet.has('*') || required.some(
35
+ permission => permission !== '*' && evaluator(permissionSet, permission),
36
+ )
37
+ }
@@ -0,0 +1,32 @@
1
+ export interface TenantChoice {
2
+ tenant_id: number
3
+ tenant_code: string
4
+ tenant_name: string
5
+ member_id: number
6
+ member_display_name: string
7
+ }
8
+
9
+ export interface TenantSelection {
10
+ state: 'tenant_selection_required'
11
+ challenge_token: string
12
+ expires_at: string
13
+ tenants: TenantChoice[]
14
+ }
15
+
16
+ export interface TenantAuthentication {
17
+ state: 'authenticated'
18
+ access_token: string
19
+ token_type: 'Bearer'
20
+ expires_in: number
21
+ context: {
22
+ tenant_id: string
23
+ account_id: string
24
+ tenant_member_id: string
25
+ }
26
+ }
27
+
28
+ export type TenantSessionOutcome = TenantSelection | TenantAuthentication
29
+
30
+ export const isMultiTenantDeployment = (value: unknown): boolean => value === 'multi-tenant'
31
+
32
+ export const isTenantAccessToken = (token: string | null): boolean => token?.startsWith('pa_tat_') === true
@@ -14,6 +14,12 @@ export { isProblemCode, parseProblemDetails } from './api/problem'
14
14
  export type { ProblemDetails, ProblemFieldError } from './api/problem'
15
15
  export { hasAllPermissions, hasPermission, useAccess } from './access/access'
16
16
  export type { AccessHints } from './access/access'
17
+ export {
18
+ evaluateRequiredPermissions,
19
+ permissionEvaluatorSlot,
20
+ PERMISSION_EVALUATOR_OVERRIDE_KEY,
21
+ } from './access/permission-policy'
22
+ export type { PermissionEvaluator } from './access/permission-policy'
17
23
  export {
18
24
  usePlatformAuth,
19
25
  usePlatformContext,
@@ -21,10 +27,19 @@ export {
21
27
  useTenantContext,
22
28
  } from './auth/stores'
23
29
  export type { PlatformContextData, TenantContextData } from './auth/stores'
30
+ export { isMultiTenantDeployment, isTenantAccessToken } from './auth/tenant-session'
31
+ export type {
32
+ TenantAuthentication,
33
+ TenantChoice,
34
+ TenantSelection,
35
+ TenantSessionOutcome,
36
+ } from './auth/tenant-session'
24
37
  export { disposeTenantState, registerTenantDisposer } from './lifecycle/tenant'
25
38
  export { createTenantLifecycle } from './lifecycle/tenant'
26
39
  export type { TenantDisposer, TenantLifecycle, TenantLifecycleTicket } from './lifecycle/tenant'
27
40
  export { createMenuRouteRegistry, defineAdminModule } from './module/contribution'
41
+ export { collectPluginContributions, routesForTenantModules } from './module/plugin-contribution-policy'
42
+ export { enabledTenantModulesFromRoutes } from './module/tenant-modules'
28
43
  export type {
29
44
  AdminModuleContribution,
30
45
  AdminModuleLocaleContribution,
@@ -35,6 +50,8 @@ export type {
35
50
  AdminRouteAccess,
36
51
  MenuRouteRegistry,
37
52
  } from './module/contribution'
53
+ export type { PluginFrontendContribution, PluginFrontendRoute } from './module/plugin-contribution-policy'
54
+ export type { TenantModuleRoute } from './module/tenant-modules'
38
55
  export { defineAdminHostConfig } from './runtime/config'
39
56
  export type { AdminAudienceHostConfig, AdminHostConfig } from './runtime/config'
40
57
  export { mapAdminRuntimeError } from './runtime/errors'
@@ -0,0 +1,51 @@
1
+ import type { PermissionEvaluator } from '../access/permission-policy'
2
+ import { evaluateRequiredPermissions } from '../access/permission-policy'
3
+
4
+ /**
5
+ * Minimal router contract shared by host applications without coupling to a
6
+ * particular Vue Router major version.
7
+ */
8
+ export interface PluginFrontendRoute {
9
+ path: string
10
+ name?: unknown
11
+ component?: unknown
12
+ meta?: {
13
+ requiredPermissions?: string | string[]
14
+ [key: string]: unknown
15
+ }
16
+ children?: PluginFrontendRoute[]
17
+ }
18
+
19
+ export interface PluginFrontendContribution<T extends PluginFrontendRoute = PluginFrontendRoute> {
20
+ moduleKey: string
21
+ routes: T[]
22
+ }
23
+
24
+ export const collectPluginContributions = (
25
+ modules: Record<string, { default?: PluginFrontendContribution }>,
26
+ ): PluginFrontendContribution[] => Object.keys(modules)
27
+ .sort()
28
+ .map(path => modules[path]?.default)
29
+ .filter((contribution): contribution is PluginFrontendContribution => (
30
+ typeof contribution?.moduleKey === 'string'
31
+ && contribution.moduleKey.length > 0
32
+ && Array.isArray(contribution.routes)
33
+ ))
34
+
35
+ /** Deployment presence alone never exposes a Tenant Module route. */
36
+ export const routesForTenantModules = <T extends PluginFrontendRoute>(
37
+ contributions: PluginFrontendContribution<T>[],
38
+ enabledModules: readonly string[],
39
+ grantedPermissions: readonly string[],
40
+ evaluator: PermissionEvaluator,
41
+ ): T[] => {
42
+ const enabled = new Set(enabledModules)
43
+ return contributions.flatMap((contribution) => {
44
+ if (!enabled.has(contribution.moduleKey)) return []
45
+ return contribution.routes.filter((route) => {
46
+ const required = route.meta?.requiredPermissions
47
+ return (typeof required === 'string' || Array.isArray(required))
48
+ && evaluateRequiredPermissions(required, grantedPermissions, evaluator)
49
+ })
50
+ })
51
+ }
@@ -0,0 +1,20 @@
1
+ /** Minimal route shape needed to derive Tenant module availability. */
2
+ export interface TenantModuleRoute {
3
+ meta?: {
4
+ tenantModuleKey?: unknown
5
+ }
6
+ children?: readonly TenantModuleRoute[]
7
+ }
8
+
9
+ /**
10
+ * Derive the distinct Tenant module keys exposed by a server-authorized route
11
+ * tree. The host owns fetching and route-to-component resolution.
12
+ */
13
+ export const enabledTenantModulesFromRoutes = (
14
+ routes: readonly TenantModuleRoute[],
15
+ ): string[] => Array.from(new Set(
16
+ routes
17
+ .flatMap(route => [route, ...(route.children ?? [])])
18
+ .map(route => route.meta?.tenantModuleKey)
19
+ .filter((key): key is string => typeof key === 'string'),
20
+ ))
@@ -0,0 +1,36 @@
1
+ export type DeploymentMode = 'standalone' | 'multi-tenant'
2
+
3
+ /**
4
+ * Minimal router contract shared by host applications without coupling to a
5
+ * particular Vue Router major version.
6
+ */
7
+ export interface DeploymentRoute {
8
+ meta?: {
9
+ controlPlane?: unknown
10
+ instanceTool?: boolean
11
+ [key: string]: unknown
12
+ }
13
+ children?: DeploymentRoute[]
14
+ }
15
+
16
+ export const deploymentMode = (value: unknown): DeploymentMode => (
17
+ value === 'multi-tenant' ? 'multi-tenant' : 'standalone'
18
+ )
19
+
20
+ export const allowsInstanceTools = (value: unknown): boolean => value === 'standalone'
21
+
22
+ export const routesForDeployment = <T extends DeploymentRoute>(
23
+ routes: T[],
24
+ mode: DeploymentMode,
25
+ instanceToolsAllowed = mode === 'standalone',
26
+ ): T[] => routes.reduce<T[]>((visible, route) => {
27
+ if (mode !== 'multi-tenant' && route.meta?.controlPlane !== undefined) return visible
28
+ if (!instanceToolsAllowed && route.meta?.instanceTool === true) return visible
29
+ visible.push({
30
+ ...route,
31
+ children: route.children
32
+ ? routesForDeployment(route.children, mode, instanceToolsAllowed)
33
+ : route.children,
34
+ } as T)
35
+ return visible
36
+ }, [])
@@ -29,6 +29,10 @@ export { TargetScopeSummary, TargetSelector } from './targets'
29
29
  export type { TargetScopeMode } from './targets'
30
30
  export { SHELL_THEME_TOKENS } from './theme'
31
31
  export type { ShellSlotName, ShellThemeToken } from './theme'
32
+ export { allowsInstanceTools, deploymentMode, routesForDeployment } from './deployment-mode'
33
+ export type { DeploymentMode, DeploymentRoute } from './deployment-mode'
34
+ export { tabFromRoute } from './tabs'
35
+ export type { ShellTab, ShellTabRoute, ShellTabState } from './tabs'
32
36
  export {
33
37
  ADMIN_SHELL_OVERRIDE_SLOTS,
34
38
  resolveWorkspaceShell,
@@ -0,0 +1,31 @@
1
+ /** Framework-neutral route data required by an admin workspace tab. */
2
+ export interface ShellTabRoute {
3
+ name: unknown
4
+ fullPath: string
5
+ query?: any
6
+ meta?: {
7
+ locale?: string
8
+ ignoreCache?: boolean | undefined
9
+ }
10
+ }
11
+
12
+ export interface ShellTab {
13
+ title: string
14
+ name: string
15
+ fullPath: string
16
+ query?: any
17
+ ignoreCache?: boolean | undefined
18
+ }
19
+
20
+ export interface ShellTabState {
21
+ tagList: ShellTab[]
22
+ cacheTabList: Set<string>
23
+ }
24
+
25
+ export const tabFromRoute = (route: ShellTabRoute): ShellTab => ({
26
+ title: route.meta?.locale || '',
27
+ name: String(route.name),
28
+ fullPath: route.fullPath,
29
+ query: route.query,
30
+ ignoreCache: route.meta?.ignoreCache,
31
+ })
package/package.json CHANGED
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "@peanut-admin/admin",
3
- "version": "0.1.0-alpha.4",
3
+ "version": "0.1.0-alpha.6",
4
4
  "description": "Reusable Peanut Admin Web services and module contributions",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/peanut-opensource/peanut-admin-core"
10
+ },
7
11
  "sideEffects": ["*.vue"],
8
12
  "files": [
9
13
  "admin-core/src",
@@ -117,7 +121,8 @@
117
121
  "peerDependencies": {
118
122
  "element-plus": "^2.14.3",
119
123
  "pinia": ">=2.0.23 <5",
120
- "vue": "^3.4.21"
124
+ "vue": "^3.4.21",
125
+ "vue-router": ">=4.0.0 <6"
121
126
  },
122
127
  "peerDependenciesMeta": {
123
128
  "element-plus": {