@jini-ai/admin 0.1.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.
- package/LICENSE +201 -0
- package/README.md +138 -0
- package/dist/browser/index.d.ts +8 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +8 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/navigation.d.ts +39 -0
- package/dist/browser/navigation.d.ts.map +1 -0
- package/dist/browser/navigation.js +113 -0
- package/dist/browser/navigation.js.map +1 -0
- package/dist/core/gated/types.d.ts +87 -0
- package/dist/core/gated/types.d.ts.map +1 -0
- package/dist/core/gated/types.js +37 -0
- package/dist/core/gated/types.js.map +1 -0
- package/dist/core/index.d.ts +20 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +15 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/manifest/rules.d.ts +84 -0
- package/dist/core/manifest/rules.d.ts.map +1 -0
- package/dist/core/manifest/rules.js +130 -0
- package/dist/core/manifest/rules.js.map +1 -0
- package/dist/core/manifest/types.d.ts +154 -0
- package/dist/core/manifest/types.d.ts.map +1 -0
- package/dist/core/manifest/types.js +31 -0
- package/dist/core/manifest/types.js.map +1 -0
- package/dist/core/permissions/rules.d.ts +32 -0
- package/dist/core/permissions/rules.d.ts.map +1 -0
- package/dist/core/permissions/rules.js +34 -0
- package/dist/core/permissions/rules.js.map +1 -0
- package/dist/core/ports/analytics.d.ts +35 -0
- package/dist/core/ports/analytics.d.ts.map +1 -0
- package/dist/core/ports/analytics.js +14 -0
- package/dist/core/ports/analytics.js.map +1 -0
- package/dist/core/ports/auth.d.ts +49 -0
- package/dist/core/ports/auth.d.ts.map +1 -0
- package/dist/core/ports/auth.js +19 -0
- package/dist/core/ports/auth.js.map +1 -0
- package/dist/core/ports/comments.d.ts +90 -0
- package/dist/core/ports/comments.d.ts.map +1 -0
- package/dist/core/ports/comments.js +24 -0
- package/dist/core/ports/comments.js.map +1 -0
- package/dist/core/ports/database.d.ts +101 -0
- package/dist/core/ports/database.d.ts.map +1 -0
- package/dist/core/ports/database.js +2 -0
- package/dist/core/ports/database.js.map +1 -0
- package/dist/core/ports/forms.d.ts +130 -0
- package/dist/core/ports/forms.d.ts.map +1 -0
- package/dist/core/ports/forms.js +62 -0
- package/dist/core/ports/forms.js.map +1 -0
- package/dist/core/ports/identity.d.ts +114 -0
- package/dist/core/ports/identity.d.ts.map +1 -0
- package/dist/core/ports/identity.js +28 -0
- package/dist/core/ports/identity.js.map +1 -0
- package/dist/core/ports/index.d.ts +21 -0
- package/dist/core/ports/index.d.ts.map +1 -0
- package/dist/core/ports/index.js +6 -0
- package/dist/core/ports/index.js.map +1 -0
- package/dist/core/ports/integrations.d.ts +81 -0
- package/dist/core/ports/integrations.d.ts.map +1 -0
- package/dist/core/ports/integrations.js +21 -0
- package/dist/core/ports/integrations.js.map +1 -0
- package/dist/core/ports/media.d.ts +74 -0
- package/dist/core/ports/media.d.ts.map +1 -0
- package/dist/core/ports/media.js +28 -0
- package/dist/core/ports/media.js.map +1 -0
- package/dist/core/ports/members.d.ts +50 -0
- package/dist/core/ports/members.d.ts.map +1 -0
- package/dist/core/ports/members.js +17 -0
- package/dist/core/ports/members.js.map +1 -0
- package/dist/core/ports/menus.d.ts +179 -0
- package/dist/core/ports/menus.d.ts.map +1 -0
- package/dist/core/ports/menus.js +70 -0
- package/dist/core/ports/menus.js.map +1 -0
- package/dist/core/ports/plugins.d.ts +68 -0
- package/dist/core/ports/plugins.d.ts.map +1 -0
- package/dist/core/ports/plugins.js +27 -0
- package/dist/core/ports/plugins.js.map +1 -0
- package/dist/core/ports/recovery.d.ts +171 -0
- package/dist/core/ports/recovery.d.ts.map +1 -0
- package/dist/core/ports/recovery.js +2 -0
- package/dist/core/ports/recovery.js.map +1 -0
- package/dist/core/ports/redirects.d.ts +139 -0
- package/dist/core/ports/redirects.d.ts.map +1 -0
- package/dist/core/ports/redirects.js +55 -0
- package/dist/core/ports/redirects.js.map +1 -0
- package/dist/core/ports/seo.d.ts +147 -0
- package/dist/core/ports/seo.d.ts.map +1 -0
- package/dist/core/ports/seo.js +39 -0
- package/dist/core/ports/seo.js.map +1 -0
- package/dist/core/ports/settings.d.ts +87 -0
- package/dist/core/ports/settings.d.ts.map +1 -0
- package/dist/core/ports/settings.js +23 -0
- package/dist/core/ports/settings.js.map +1 -0
- package/dist/core/ports/workspace.d.ts +44 -0
- package/dist/core/ports/workspace.d.ts.map +1 -0
- package/dist/core/ports/workspace.js +29 -0
- package/dist/core/ports/workspace.js.map +1 -0
- package/dist/core/routing/rules.d.ts +54 -0
- package/dist/core/routing/rules.d.ts.map +1 -0
- package/dist/core/routing/rules.js +113 -0
- package/dist/core/routing/rules.js.map +1 -0
- package/dist/core/routing/types.d.ts +36 -0
- package/dist/core/routing/types.d.ts.map +1 -0
- package/dist/core/routing/types.js +17 -0
- package/dist/core/routing/types.js.map +1 -0
- package/dist/core/transport/errors.d.ts +53 -0
- package/dist/core/transport/errors.d.ts.map +1 -0
- package/dist/core/transport/errors.js +63 -0
- package/dist/core/transport/errors.js.map +1 -0
- package/dist/core/transport/http.d.ts +63 -0
- package/dist/core/transport/http.d.ts.map +1 -0
- package/dist/core/transport/http.js +63 -0
- package/dist/core/transport/http.js.map +1 -0
- package/dist/core/transport/index.d.ts +5 -0
- package/dist/core/transport/index.d.ts.map +1 -0
- package/dist/core/transport/index.js +3 -0
- package/dist/core/transport/index.js.map +1 -0
- package/dist/core/transport/types.d.ts +81 -0
- package/dist/core/transport/types.d.ts.map +1 -0
- package/dist/core/transport/types.js +43 -0
- package/dist/core/transport/types.js.map +1 -0
- package/dist/react/components/ConfirmButton/ConfirmButton.d.ts +40 -0
- package/dist/react/components/ConfirmButton/ConfirmButton.d.ts.map +1 -0
- package/dist/react/components/ConfirmButton/ConfirmButton.hooks.d.ts +22 -0
- package/dist/react/components/ConfirmButton/ConfirmButton.hooks.d.ts.map +1 -0
- package/dist/react/components/ConfirmButton/ConfirmButton.hooks.js +59 -0
- package/dist/react/components/ConfirmButton/ConfirmButton.hooks.js.map +1 -0
- package/dist/react/components/ConfirmButton/ConfirmButton.js +40 -0
- package/dist/react/components/ConfirmButton/ConfirmButton.js.map +1 -0
- package/dist/react/components/ConfirmButton.d.ts +39 -0
- package/dist/react/components/ConfirmButton.d.ts.map +1 -0
- package/dist/react/components/ConfirmButton.js +79 -0
- package/dist/react/components/ConfirmButton.js.map +1 -0
- package/dist/react/components/ConfirmDialog/ConfirmDialog.d.ts +90 -0
- package/dist/react/components/ConfirmDialog/ConfirmDialog.d.ts.map +1 -0
- package/dist/react/components/ConfirmDialog/ConfirmDialog.hooks.d.ts +49 -0
- package/dist/react/components/ConfirmDialog/ConfirmDialog.hooks.d.ts.map +1 -0
- package/dist/react/components/ConfirmDialog/ConfirmDialog.hooks.js +81 -0
- package/dist/react/components/ConfirmDialog/ConfirmDialog.hooks.js.map +1 -0
- package/dist/react/components/ConfirmDialog/ConfirmDialog.js +92 -0
- package/dist/react/components/ConfirmDialog/ConfirmDialog.js.map +1 -0
- package/dist/react/components/ConfirmDialog.d.ts +57 -0
- package/dist/react/components/ConfirmDialog.d.ts.map +1 -0
- package/dist/react/components/ConfirmDialog.js +85 -0
- package/dist/react/components/ConfirmDialog.js.map +1 -0
- package/dist/react/components/DataTable.d.ts +97 -0
- package/dist/react/components/DataTable.d.ts.map +1 -0
- package/dist/react/components/DataTable.js +20 -0
- package/dist/react/components/DataTable.js.map +1 -0
- package/dist/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.d.ts +25 -0
- package/dist/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.d.ts.map +1 -0
- package/dist/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.js +148 -0
- package/dist/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.js.map +1 -0
- package/dist/react/components/RowMenu/RowMenu.d.ts +77 -0
- package/dist/react/components/RowMenu/RowMenu.d.ts.map +1 -0
- package/dist/react/components/RowMenu/RowMenu.hooks.d.ts +30 -0
- package/dist/react/components/RowMenu/RowMenu.hooks.d.ts.map +1 -0
- package/dist/react/components/RowMenu/RowMenu.hooks.js +154 -0
- package/dist/react/components/RowMenu/RowMenu.hooks.js.map +1 -0
- package/dist/react/components/RowMenu/RowMenu.js +46 -0
- package/dist/react/components/RowMenu/RowMenu.js.map +1 -0
- package/dist/react/components/RowMenu.d.ts +44 -0
- package/dist/react/components/RowMenu.d.ts.map +1 -0
- package/dist/react/components/RowMenu.js +149 -0
- package/dist/react/components/RowMenu.js.map +1 -0
- package/dist/react/components/Sidebar.d.ts +128 -0
- package/dist/react/components/Sidebar.d.ts.map +1 -0
- package/dist/react/components/Sidebar.js +168 -0
- package/dist/react/components/Sidebar.js.map +1 -0
- package/dist/react/hooks/use-nav-sections.d.ts +50 -0
- package/dist/react/hooks/use-nav-sections.d.ts.map +1 -0
- package/dist/react/hooks/use-nav-sections.js +112 -0
- package/dist/react/hooks/use-nav-sections.js.map +1 -0
- package/dist/react/hooks/use-sidebar-rail.d.ts +39 -0
- package/dist/react/hooks/use-sidebar-rail.d.ts.map +1 -0
- package/dist/react/hooks/use-sidebar-rail.js +93 -0
- package/dist/react/hooks/use-sidebar-rail.js.map +1 -0
- package/dist/react/index.d.ts +53 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +53 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/types.d.ts +28 -0
- package/dist/react/types.d.ts.map +1 -0
- package/dist/react/types.js +23 -0
- package/dist/react/types.js.map +1 -0
- package/dist/server/composio/bounded-data.d.ts +36 -0
- package/dist/server/composio/bounded-data.d.ts.map +1 -0
- package/dist/server/composio/bounded-data.js +181 -0
- package/dist/server/composio/bounded-data.js.map +1 -0
- package/dist/server/composio/catalog.d.ts +147 -0
- package/dist/server/composio/catalog.d.ts.map +1 -0
- package/dist/server/composio/catalog.js +126 -0
- package/dist/server/composio/catalog.js.map +1 -0
- package/dist/server/composio/composio-config.d.ts +42 -0
- package/dist/server/composio/composio-config.d.ts.map +1 -0
- package/dist/server/composio/composio-config.js +145 -0
- package/dist/server/composio/composio-config.js.map +1 -0
- package/dist/server/composio/composio-descriptions.d.ts +16 -0
- package/dist/server/composio/composio-descriptions.d.ts.map +1 -0
- package/dist/server/composio/composio-descriptions.js +766 -0
- package/dist/server/composio/composio-descriptions.js.map +1 -0
- package/dist/server/composio/composio.d.ts +364 -0
- package/dist/server/composio/composio.d.ts.map +1 -0
- package/dist/server/composio/composio.js +1902 -0
- package/dist/server/composio/composio.js.map +1 -0
- package/dist/server/composio/errors.d.ts +15 -0
- package/dist/server/composio/errors.d.ts.map +1 -0
- package/dist/server/composio/errors.js +16 -0
- package/dist/server/composio/errors.js.map +1 -0
- package/dist/server/composio/file-lock.d.ts +13 -0
- package/dist/server/composio/file-lock.d.ts.map +1 -0
- package/dist/server/composio/file-lock.js +92 -0
- package/dist/server/composio/file-lock.js.map +1 -0
- package/dist/server/composio/index.d.ts +18 -0
- package/dist/server/composio/index.d.ts.map +1 -0
- package/dist/server/composio/index.js +7 -0
- package/dist/server/composio/index.js.map +1 -0
- package/dist/server/composio/json-schema.d.ts +93 -0
- package/dist/server/composio/json-schema.d.ts.map +1 -0
- package/dist/server/composio/json-schema.js +498 -0
- package/dist/server/composio/json-schema.js.map +1 -0
- package/dist/server/composio/output-protection.d.ts +32 -0
- package/dist/server/composio/output-protection.d.ts.map +1 -0
- package/dist/server/composio/output-protection.js +105 -0
- package/dist/server/composio/output-protection.js.map +1 -0
- package/dist/server/composio/service.d.ts +181 -0
- package/dist/server/composio/service.d.ts.map +1 -0
- package/dist/server/composio/service.js +758 -0
- package/dist/server/composio/service.js.map +1 -0
- package/dist/server/index.d.ts +15 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +15 -0
- package/dist/server/index.js.map +1 -0
- package/package.json +92 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The plan -> confirm -> execute protocol for destructive operations.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this is in core rather than in the panels that use it
|
|
5
|
+
*
|
|
6
|
+
* The reference implementation independently grew this same three-step shape in three unrelated places — database
|
|
7
|
+
* forward-migration (`planMigrateForward`/`confirmMigrateForward`/`executeMigrateForward`),
|
|
8
|
+
* point-in-time restore (`planRestore`/`confirmRestore`/`executeRestore`), and taxonomy term
|
|
9
|
+
* merge (`planMergeTerm`/`confirmMergeTerm`/`executeMergeTerm`) — sharing `GatedPlanResult<T>`
|
|
10
|
+
* and `GatedConfirmResult` types. Three independent arrivals at one shape is the signal that it
|
|
11
|
+
* is a protocol, not a coincidence, and any product doing irreversible operator actions will
|
|
12
|
+
* need it.
|
|
13
|
+
*
|
|
14
|
+
* ## What the three steps are for
|
|
15
|
+
*
|
|
16
|
+
* - **plan** is read-only and idempotent. It computes what *would* happen and returns a token.
|
|
17
|
+
* Safe to call on render; safe to call twice.
|
|
18
|
+
* - **confirm** records that the operator saw the plan. Separate from execute so the UI can show
|
|
19
|
+
* consequences the operator has to acknowledge, and so a stale plan (the world changed between
|
|
20
|
+
* plan and execute) can be rejected here rather than half-applied.
|
|
21
|
+
* - **execute** does the thing. Requires the token from confirm.
|
|
22
|
+
*
|
|
23
|
+
* The token is what makes this more than a confirmation dialog: it binds the execute call to a
|
|
24
|
+
* specific computed plan, so an operator cannot approve a small change and have a large one run.
|
|
25
|
+
*
|
|
26
|
+
* ## Warning: this protocol promises reversibility it cannot itself deliver
|
|
27
|
+
*
|
|
28
|
+
* A hard-won rule from the reference implementation's admin UX audit: **never promise an undo the
|
|
29
|
+
* product cannot deliver.** A `reversible: true` plan means the *backend* claims a revert path
|
|
30
|
+
* exists — it does not mean the admin has a reachable UI for it. The reference implementation's
|
|
31
|
+
* post/page delete is a genuine server-side
|
|
32
|
+
* soft delete with no restore path reachable from the admin. A panel rendering "you can undo
|
|
33
|
+
* this" off this flag alone would be lying to the operator. Check that the revert is reachable,
|
|
34
|
+
* not merely that it exists.
|
|
35
|
+
*/
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/gated/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file `@jini-ai/admin/core` — the universal half. No React, no DOM.
|
|
3
|
+
*
|
|
4
|
+
* This is the layer a panel author codes against. Anything that touches `window` lives in
|
|
5
|
+
* `@jini-ai/admin/browser`; anything that imports React lives in `@jini-ai/admin/react`. The
|
|
6
|
+
* boundary is enforced by this package's vitest config, which deliberately runs `src/core/**`
|
|
7
|
+
* without a DOM environment so a leak fails loudly rather than passing quietly.
|
|
8
|
+
*/
|
|
9
|
+
export type { AdminNavEntry, AdminPanel, AdminRoutePattern } from './manifest/types.js';
|
|
10
|
+
export { buildAgentPageMap, buildNav, panelHref, resolveAgentPageId, resolvePanels, } from './manifest/rules.js';
|
|
11
|
+
export type { AdminNavGroup, AdminNavItem, AdminRegistryContext } from './manifest/rules.js';
|
|
12
|
+
export type { AdminRoute } from './routing/types.js';
|
|
13
|
+
export { DEFAULT_ADMIN_BASE, adminHref, currentRoutePath, matchRoute, stripTrailingSlash, } from './routing/rules.js';
|
|
14
|
+
export { hasPermission } from './permissions/rules.js';
|
|
15
|
+
export { AdminApiError, createAdminClient, createHttpTransport, describeApiError, } from './transport/index.js';
|
|
16
|
+
export type { AdminClient, AdminRouteGroupFactory, AdminTransport, AdminFetch, HttpTransportOptions, } from './transport/index.js';
|
|
17
|
+
export type { GatedConfirmResult, GatedOperation, GatedPlanResult } from './gated/types.js';
|
|
18
|
+
export type { AdminAnalyticsHit, AdminAnalyticsPort, AdminAuthPort, AdminAuthUser, AdminComment, AdminCommentsPort, AdminCommentsQueuePage, AdminDatabasePort, AdminDegradedBanner, AdminDisclosureResult, AdminExtensionEnabledResult, AdminExtensionsPort, AdminIdentityPort, AdminIdentityUser, AdminIntegrationDelivery, AdminIntegrationDeliverySummary, AdminIntegrationSubscription, AdminIntegrationsPort, AdminLedgerRow, AdminMedia, AdminMediaPort, AdminMember, AdminMembersPort, AdminPlugin, AdminPolicy, AdminRecoveryDeepLinkResult, AdminRecoveryPort, AdminRecoveryStatus, AdminRestorePoint, AdminRestorePointSummary, AdminRole, AdminSettingsPort, AdminWorkspace, AdminWorkspacePort, CategoryCount, CommentModerationAction, CommentsSettings, CommentStatus, DatabaseContextEnvelope, DegradedBannerActionKind, DegradedBannerKind, MigrateForwardResult, RestoreConfirmInput, RestoreExecuteInput, RestoreExecuteResult, RestorePointCostClass, SettingResolvedValue, SettingResetResponse, SettingScope, SettingValueResponse, } from './ports/index.js';
|
|
19
|
+
export type { AdminAssignMenuLocationResult, AdminDeleteMenuResult, AdminFormCreateInput, AdminFormDefinition, AdminFormField, AdminFormNotifyConfig, AdminFormsPort, AdminFormSubmission, AdminFormSubmissionPage, AdminFormUpdatePatch, AdminMenu, AdminMenuBinding, AdminMenuCreateInput, AdminMenuCustomTarget, AdminMenuEntryTarget, AdminMenuItem, AdminMenuItemAttrs, AdminMenuRouteTarget, AdminMenusPort, AdminMenuTarget, AdminMenuTermTarget, AdminMenuUpdateTreeInput, AdminMenuUrlTarget, AdminRedirect, AdminRedirectCreateInput, AdminRedirectHitStats, AdminRedirectImportFailure, AdminRedirectImportResult, AdminRedirectListFilter, AdminRedirectsPort, AdminRedirectUpdatePatch, AdminSeoAnalysis, AdminSeoIssue, AdminSeoMeta, AdminSeoOpenGraph, AdminSeoOverrides, AdminSeoPort, AdminSeoRobotsDirective, AdminSeoRobotsRule, AdminSeoSettings, AdminSeoTwitterCard, FormDefinitionStatus, FormFieldType, MenuStatus, NavTargetKind, RedirectMatchType, RedirectSource, RedirectStatus, RedirectStatusCode, SeoIssueSeverity, SeoOpenGraphType, SeoTwitterCardKind, } from './ports/index.js';
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxF,OAAO,EACL,iBAAiB,EACjB,QAAQ,EACR,SAAS,EACT,kBAAkB,EAClB,aAAa,GACd,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAG7F,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EACL,kBAAkB,EAClB,SAAS,EACT,gBAAgB,EAChB,UAAU,EACV,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAGvD,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,WAAW,EACX,sBAAsB,EACtB,cAAc,EACd,UAAU,EACV,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAG9B,YAAY,EAAE,kBAAkB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAG5F,YAAY,EACV,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,EACb,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,sBAAsB,EACtB,iBAAiB,EACjB,mBAAmB,EACnB,qBAAqB,EACrB,2BAA2B,EAC3B,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,wBAAwB,EACxB,+BAA+B,EAC/B,4BAA4B,EAC5B,qBAAqB,EACrB,cAAc,EACd,UAAU,EACV,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,WAAW,EACX,WAAW,EACX,2BAA2B,EAC3B,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EACjB,wBAAwB,EACxB,SAAS,EACT,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,uBAAuB,EACvB,gBAAgB,EAChB,aAAa,EACb,uBAAuB,EACvB,wBAAwB,EACxB,kBAAkB,EAClB,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,YAAY,EACZ,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,6BAA6B,EAC7B,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,cAAc,EACd,mBAAmB,EACnB,uBAAuB,EACvB,oBAAoB,EACpB,SAAS,EACT,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,EACrB,oBAAoB,EACpB,aAAa,EACb,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,wBAAwB,EACxB,kBAAkB,EAClB,aAAa,EACb,wBAAwB,EACxB,qBAAqB,EACrB,0BAA0B,EAC1B,yBAAyB,EACzB,uBAAuB,EACvB,kBAAkB,EAClB,wBAAwB,EACxB,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,uBAAuB,EACvB,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EACpB,aAAa,EACb,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file `@jini-ai/admin/core` — the universal half. No React, no DOM.
|
|
3
|
+
*
|
|
4
|
+
* This is the layer a panel author codes against. Anything that touches `window` lives in
|
|
5
|
+
* `@jini-ai/admin/browser`; anything that imports React lives in `@jini-ai/admin/react`. The
|
|
6
|
+
* boundary is enforced by this package's vitest config, which deliberately runs `src/core/**`
|
|
7
|
+
* without a DOM environment so a leak fails loudly rather than passing quietly.
|
|
8
|
+
*/
|
|
9
|
+
export { buildAgentPageMap, buildNav, panelHref, resolveAgentPageId, resolvePanels, } from './manifest/rules.js';
|
|
10
|
+
export { DEFAULT_ADMIN_BASE, adminHref, currentRoutePath, matchRoute, stripTrailingSlash, } from './routing/rules.js';
|
|
11
|
+
// Permissions — affordance only, never an authorization boundary.
|
|
12
|
+
export { hasPermission } from './permissions/rules.js';
|
|
13
|
+
// Transport — the seam every route group is built on.
|
|
14
|
+
export { AdminApiError, createAdminClient, createHttpTransport, describeApiError, } from './transport/index.js';
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EACL,iBAAiB,EACjB,QAAQ,EACR,SAAS,EACT,kBAAkB,EAClB,aAAa,GACd,MAAM,qBAAqB,CAAC;AAK7B,OAAO,EACL,kBAAkB,EAClB,SAAS,EACT,gBAAgB,EAChB,UAAU,EACV,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAE5B,kEAAkE;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD,sDAAsD;AACtD,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Pure functions over a panel set: what mounts, what the sidebar shows, what an agent may
|
|
3
|
+
* reach. No DOM, no React, no I/O — every function here is a total function of its arguments,
|
|
4
|
+
* which is what makes the shell's behaviour testable without rendering anything.
|
|
5
|
+
*/
|
|
6
|
+
import type { AdminNavEntry, AdminPanel } from './types.js';
|
|
7
|
+
/** What the host has actually wired, as seen by the registry. */
|
|
8
|
+
export interface AdminRegistryContext {
|
|
9
|
+
/** Capability keys with a supplied port. Matched against each panel's `requires`. */
|
|
10
|
+
readonly capabilities?: readonly string[];
|
|
11
|
+
/** The operator's effective permissions, as returned by the host's auth endpoint. May contain
|
|
12
|
+
* the owner wildcard `"*"` — see `permissions/rules.ts`. */
|
|
13
|
+
readonly permissions?: readonly string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The panels that actually mount, in registration order.
|
|
17
|
+
*
|
|
18
|
+
* A panel is dropped when a capability it requires is not wired, or when the operator lacks one
|
|
19
|
+
* of its permissions. Both are *silent* by design: an unmet requirement means the section does
|
|
20
|
+
* not exist for this build or this operator, which is different from an error.
|
|
21
|
+
*
|
|
22
|
+
* @complexity O(p * (r + m)) — p panels, r requirements each, m permissions each. All three are
|
|
23
|
+
* small, bounded by what a developer typed into a manifest.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolvePanels<T>(panels: readonly AdminPanel<T>[], context?: AdminRegistryContext): readonly AdminPanel<T>[];
|
|
26
|
+
/** A sidebar group, ready to render. */
|
|
27
|
+
export interface AdminNavGroup {
|
|
28
|
+
/** Group heading; absent for the ungrouped top row. */
|
|
29
|
+
readonly label?: string;
|
|
30
|
+
readonly items: readonly AdminNavItem[];
|
|
31
|
+
}
|
|
32
|
+
/** A sidebar row. `href` is a **route path** (`/settings`), never a URL — the shell applies the
|
|
33
|
+
* base. Conflating the two is what produced the reference implementation's
|
|
34
|
+
* `/admin/#/section/settings` URLs. */
|
|
35
|
+
export interface AdminNavItem extends AdminNavEntry {
|
|
36
|
+
readonly id: string;
|
|
37
|
+
readonly href: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Derives the sidebar from the resolved panel set.
|
|
41
|
+
*
|
|
42
|
+
* Ordering is deterministic and has to be: groups appear in the order their first member was
|
|
43
|
+
* registered (with the ungrouped row always first), and items sort by `order` ascending with
|
|
44
|
+
* registration order as a stable tiebreak. A nav that reshuffles between builds because two
|
|
45
|
+
* panels share an `order` is a real bug — operators navigate by muscle memory.
|
|
46
|
+
*
|
|
47
|
+
* @complexity O(n log n) on the items within each group; O(n) otherwise.
|
|
48
|
+
*/
|
|
49
|
+
export declare function buildNav<T>(panels: readonly AdminPanel<T>[]): readonly AdminNavGroup[];
|
|
50
|
+
/** Panel id -> its **route path**. `dashboard` is the root, not `/dashboard`. */
|
|
51
|
+
export declare function panelHref(panelId: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* The agent-navigable page ids: panel id -> route path.
|
|
54
|
+
*
|
|
55
|
+
* Derived from `agentReachable`, which is left unset on most panels and falls back to
|
|
56
|
+
* `options.defaultReachable` (itself defaulting to `false`) — so with no options this is an
|
|
57
|
+
* allowlist populated only by deliberate opt-in, exactly as the reference implementation's
|
|
58
|
+
* hand-maintained page-map module was. An explicit `agentReachable: false` on a panel always
|
|
59
|
+
* excludes it regardless of `defaultReachable`, so a host that flips the default can still opt a
|
|
60
|
+
* specific panel back out. Detail routes are never gated by either — see `AdminPanel.agentReachable`.
|
|
61
|
+
*
|
|
62
|
+
* `defaultReachable: true` is for a host that has decided navigation-only reachability (getting an
|
|
63
|
+
* agent TO a page) carries no meaningful risk on its own — actually operating a page's controls is
|
|
64
|
+
* a separate, still per-element opt-in (`data-agent-element`) that this default has no effect on.
|
|
65
|
+
* A host for which that isn't true (a panel an agent should never even be told exists) should keep
|
|
66
|
+
* the default false and opt in per panel instead.
|
|
67
|
+
*
|
|
68
|
+
* Note the ordering dependency: callers must pass the **resolved** panel set, not the raw one, or
|
|
69
|
+
* an agent could navigate to a panel whose capability is not wired.
|
|
70
|
+
*/
|
|
71
|
+
export declare function buildAgentPageMap<T>(panels: readonly AdminPanel<T>[], options?: {
|
|
72
|
+
readonly defaultReachable?: boolean;
|
|
73
|
+
}): Readonly<Record<string, string>>;
|
|
74
|
+
/**
|
|
75
|
+
* The page id a resolved route reports through `data-agent-page`.
|
|
76
|
+
*
|
|
77
|
+
* Deliberately NOT the same question as "which nav row lights up" — that is the panel id, and
|
|
78
|
+
* conflating the two is the documented bug `AdminRoutePattern.agentPageId` exists to prevent.
|
|
79
|
+
*
|
|
80
|
+
* Falls back to the panel id so a detail route reports its list page (`/posts/abc` -> `posts`),
|
|
81
|
+
* which is the nearest id an agent can actually act on.
|
|
82
|
+
*/
|
|
83
|
+
export declare function resolveAgentPageId<T>(panels: readonly AdminPanel<T>[], panelId: string | null, view: string | null): string | null;
|
|
84
|
+
//# sourceMappingURL=rules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../src/core/manifest/rules.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE5D,iEAAiE;AACjE,MAAM,WAAW,oBAAoB;IACnC,qFAAqF;IACrF,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C;iEAC6D;IAC7D,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,MAAM,EAAE,SAAS,UAAU,CAAC,CAAC,CAAC,EAAE,EAChC,OAAO,GAAE,oBAAyB,GACjC,SAAS,UAAU,CAAC,CAAC,CAAC,EAAE,CAQ1B;AAED,wCAAwC;AACxC,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;CACzC;AAED;;wCAEwC;AACxC,MAAM,WAAW,YAAa,SAAQ,aAAa;IACjD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,UAAU,CAAC,CAAC,CAAC,EAAE,GAAG,SAAS,aAAa,EAAE,CA2BtF;AAQD,iFAAiF;AACjF,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EACjC,MAAM,EAAE,SAAS,UAAU,CAAC,CAAC,CAAC,EAAE,EAChC,OAAO,GAAE;IAAE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAAO,GACpD,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAkBlC;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAClC,MAAM,EAAE,SAAS,UAAU,CAAC,CAAC,CAAC,EAAE,EAChC,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,IAAI,EAAE,MAAM,GAAG,IAAI,GAClB,MAAM,GAAG,IAAI,CAMf"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Pure functions over a panel set: what mounts, what the sidebar shows, what an agent may
|
|
3
|
+
* reach. No DOM, no React, no I/O — every function here is a total function of its arguments,
|
|
4
|
+
* which is what makes the shell's behaviour testable without rendering anything.
|
|
5
|
+
*/
|
|
6
|
+
import { hasPermission } from '../permissions/rules.js';
|
|
7
|
+
/**
|
|
8
|
+
* The panels that actually mount, in registration order.
|
|
9
|
+
*
|
|
10
|
+
* A panel is dropped when a capability it requires is not wired, or when the operator lacks one
|
|
11
|
+
* of its permissions. Both are *silent* by design: an unmet requirement means the section does
|
|
12
|
+
* not exist for this build or this operator, which is different from an error.
|
|
13
|
+
*
|
|
14
|
+
* @complexity O(p * (r + m)) — p panels, r requirements each, m permissions each. All three are
|
|
15
|
+
* small, bounded by what a developer typed into a manifest.
|
|
16
|
+
*/
|
|
17
|
+
export function resolvePanels(panels, context = {}) {
|
|
18
|
+
const capabilities = context.capabilities ?? [];
|
|
19
|
+
const permissions = context.permissions ?? [];
|
|
20
|
+
return panels.filter((panel) => {
|
|
21
|
+
const capabilitiesMet = (panel.requires ?? []).every((key) => capabilities.includes(key));
|
|
22
|
+
if (!capabilitiesMet)
|
|
23
|
+
return false;
|
|
24
|
+
return (panel.permissions ?? []).every((p) => hasPermission(permissions, p));
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Derives the sidebar from the resolved panel set.
|
|
29
|
+
*
|
|
30
|
+
* Ordering is deterministic and has to be: groups appear in the order their first member was
|
|
31
|
+
* registered (with the ungrouped row always first), and items sort by `order` ascending with
|
|
32
|
+
* registration order as a stable tiebreak. A nav that reshuffles between builds because two
|
|
33
|
+
* panels share an `order` is a real bug — operators navigate by muscle memory.
|
|
34
|
+
*
|
|
35
|
+
* @complexity O(n log n) on the items within each group; O(n) otherwise.
|
|
36
|
+
*/
|
|
37
|
+
export function buildNav(panels) {
|
|
38
|
+
const groups = new Map();
|
|
39
|
+
const groupOrder = [];
|
|
40
|
+
// Sentinel for "no group". A panel cannot collide with it: `group` is `string | undefined`, and
|
|
41
|
+
// an explicit empty-string group would be a labelless heading, which is the same thing.
|
|
42
|
+
const UNGROUPED = '';
|
|
43
|
+
panels.forEach((panel) => {
|
|
44
|
+
if (!panel.nav)
|
|
45
|
+
return;
|
|
46
|
+
const key = panel.nav.group ?? UNGROUPED;
|
|
47
|
+
if (!groups.has(key)) {
|
|
48
|
+
groups.set(key, []);
|
|
49
|
+
groupOrder.push(key);
|
|
50
|
+
}
|
|
51
|
+
groups.get(key)?.push({ ...panel.nav, id: panel.id, href: panelHref(panel.id) });
|
|
52
|
+
});
|
|
53
|
+
// Ungrouped first regardless of when it was registered — it is the top row, not a peer group.
|
|
54
|
+
const ordered = [
|
|
55
|
+
...groupOrder.filter((k) => k === UNGROUPED),
|
|
56
|
+
...groupOrder.filter((k) => k !== UNGROUPED),
|
|
57
|
+
];
|
|
58
|
+
return ordered.map((key) => ({
|
|
59
|
+
...(key === UNGROUPED ? {} : { label: key }),
|
|
60
|
+
items: stableSortByOrder(groups.get(key) ?? []),
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
/** `Array.prototype.sort` is specified as stable, so equal `order` values keep registration
|
|
64
|
+
* order — the tiebreak `buildNav` documents. Missing `order` sorts last, not as 0. */
|
|
65
|
+
function stableSortByOrder(items) {
|
|
66
|
+
return [...items].sort((a, b) => (a.order ?? Number.MAX_SAFE_INTEGER) - (b.order ?? Number.MAX_SAFE_INTEGER));
|
|
67
|
+
}
|
|
68
|
+
/** Panel id -> its **route path**. `dashboard` is the root, not `/dashboard`. */
|
|
69
|
+
export function panelHref(panelId) {
|
|
70
|
+
return panelId === 'dashboard' ? '/' : `/${panelId}`;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The agent-navigable page ids: panel id -> route path.
|
|
74
|
+
*
|
|
75
|
+
* Derived from `agentReachable`, which is left unset on most panels and falls back to
|
|
76
|
+
* `options.defaultReachable` (itself defaulting to `false`) — so with no options this is an
|
|
77
|
+
* allowlist populated only by deliberate opt-in, exactly as the reference implementation's
|
|
78
|
+
* hand-maintained page-map module was. An explicit `agentReachable: false` on a panel always
|
|
79
|
+
* excludes it regardless of `defaultReachable`, so a host that flips the default can still opt a
|
|
80
|
+
* specific panel back out. Detail routes are never gated by either — see `AdminPanel.agentReachable`.
|
|
81
|
+
*
|
|
82
|
+
* `defaultReachable: true` is for a host that has decided navigation-only reachability (getting an
|
|
83
|
+
* agent TO a page) carries no meaningful risk on its own — actually operating a page's controls is
|
|
84
|
+
* a separate, still per-element opt-in (`data-agent-element`) that this default has no effect on.
|
|
85
|
+
* A host for which that isn't true (a panel an agent should never even be told exists) should keep
|
|
86
|
+
* the default false and opt in per panel instead.
|
|
87
|
+
*
|
|
88
|
+
* Note the ordering dependency: callers must pass the **resolved** panel set, not the raw one, or
|
|
89
|
+
* an agent could navigate to a panel whose capability is not wired.
|
|
90
|
+
*/
|
|
91
|
+
export function buildAgentPageMap(panels, options = {}) {
|
|
92
|
+
const defaultReachable = options.defaultReachable ?? false;
|
|
93
|
+
const map = {};
|
|
94
|
+
panels.forEach((panel) => {
|
|
95
|
+
if ((panel.agentReachable ?? defaultReachable) === true)
|
|
96
|
+
map[panel.id] = panelHref(panel.id);
|
|
97
|
+
// A param-free detail route carrying an `agentPageId` is a destination in its own right —
|
|
98
|
+
// the reference implementation publishes `widget-regions -> /widgets/regions` exactly this
|
|
99
|
+
// way. A route WITH params
|
|
100
|
+
// is skipped: an agent has no id to supply, so publishing it would hand out a path that
|
|
101
|
+
// cannot be navigated. See `AdminRoutePattern.agentPageId`.
|
|
102
|
+
(panel.routes ?? []).forEach((route) => {
|
|
103
|
+
if (!route.agentPageId)
|
|
104
|
+
return;
|
|
105
|
+
if (route.pattern.includes(':'))
|
|
106
|
+
return;
|
|
107
|
+
map[route.agentPageId] = `${panelHref(panel.id)}${route.pattern}`.replace('//', '/');
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
return map;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The page id a resolved route reports through `data-agent-page`.
|
|
114
|
+
*
|
|
115
|
+
* Deliberately NOT the same question as "which nav row lights up" — that is the panel id, and
|
|
116
|
+
* conflating the two is the documented bug `AdminRoutePattern.agentPageId` exists to prevent.
|
|
117
|
+
*
|
|
118
|
+
* Falls back to the panel id so a detail route reports its list page (`/posts/abc` -> `posts`),
|
|
119
|
+
* which is the nearest id an agent can actually act on.
|
|
120
|
+
*/
|
|
121
|
+
export function resolveAgentPageId(panels, panelId, view) {
|
|
122
|
+
if (panelId == null)
|
|
123
|
+
return null;
|
|
124
|
+
const panel = panels.find((p) => p.id === panelId);
|
|
125
|
+
if (!panel)
|
|
126
|
+
return null;
|
|
127
|
+
const matched = (panel.routes ?? []).find((r) => r.view === view);
|
|
128
|
+
return matched?.agentPageId ?? panel.id;
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.js","sourceRoot":"","sources":["../../../src/core/manifest/rules.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAYxD;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAC3B,MAAgC,EAChC,UAAgC,EAAE;IAElC,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC;IAChD,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC;IAC9C,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;QAC7B,MAAM,eAAe,GAAG,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;QAC1F,IAAI,CAAC,eAAe;YAAE,OAAO,KAAK,CAAC;QACnC,OAAO,CAAC,KAAK,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;AACL,CAAC;AAiBD;;;;;;;;;GASG;AACH,MAAM,UAAU,QAAQ,CAAI,MAAgC;IAC1D,MAAM,MAAM,GAAG,IAAI,GAAG,EAA0B,CAAC;IACjD,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,gGAAgG;IAChG,wFAAwF;IACxF,MAAM,SAAS,GAAG,EAAE,CAAC;IAErB,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;QACvB,IAAI,CAAC,KAAK,CAAC,GAAG;YAAE,OAAO;QACvB,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,IAAI,SAAS,CAAC;QACzC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACrB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACpB,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACvB,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,KAAK,CAAC,GAAG,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IACnF,CAAC,CAAC,CAAC;IAEH,8FAA8F;IAC9F,MAAM,OAAO,GAAG;QACd,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC;QAC5C,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC;KAC7C,CAAC;IAEF,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAC3B,GAAG,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;QAC5C,KAAK,EAAE,iBAAiB,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;KAChD,CAAC,CAAC,CAAC;AACN,CAAC;AAED;uFACuF;AACvF,SAAS,iBAAiB,CAAC,KAA8B;IACvD,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,MAAM,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC;AAChH,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,OAAO,OAAO,KAAK,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,CAAC;AACvD,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAgC,EAChC,UAAmD,EAAE;IAErD,MAAM,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,IAAI,KAAK,CAAC;IAC3D,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;QACvB,IAAI,CAAC,KAAK,CAAC,cAAc,IAAI,gBAAgB,CAAC,KAAK,IAAI;YAAE,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAE7F,0FAA0F;QAC1F,2FAA2F;QAC3F,2BAA2B;QAC3B,wFAAwF;QACxF,4DAA4D;QAC5D,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;YACrC,IAAI,CAAC,KAAK,CAAC,WAAW;gBAAE,OAAO;YAC/B,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;gBAAE,OAAO;YACxC,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,GAAG,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACvF,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IACH,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAgC,EAChC,OAAsB,EACtB,IAAmB;IAEnB,IAAI,OAAO,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IACjC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,CAAC;IACnD,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,OAAO,GAAG,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAClE,OAAO,OAAO,EAAE,WAAW,IAAI,KAAK,CAAC,EAAE,CAAC;AAC1C,CAAC"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file What an admin panel *is* — the contract a panel author codes against.
|
|
3
|
+
*
|
|
4
|
+
* This is the smallest surface in the package on purpose. A panel package (Jini-shipped,
|
|
5
|
+
* host-owned, third-party, or AI-generated) should need this module and nothing else: it has no
|
|
6
|
+
* React, no DOM, no transport, and no knowledge of the shell that will mount it.
|
|
7
|
+
*
|
|
8
|
+
* ## The three-file problem this replaces
|
|
9
|
+
*
|
|
10
|
+
* The reference implementation defines a section in three places — `App.tsx`'s `SECTIONS` map
|
|
11
|
+
* (routing allowlist + render dispatch), `nav.ts` (sidebar label/icon/group/order), and
|
|
12
|
+
* `agent-pages.ts` (which pages an AI agent may navigate to). Its own internal docs are emphatic
|
|
13
|
+
* that this is deliberate, and they are right about *why*:
|
|
14
|
+
*
|
|
15
|
+
* > "Do not derive this from `App.tsx`'s `SECTIONS` map. Doing so would make every new admin
|
|
16
|
+
* > screen agent-reachable simply by existing, which is the opposite of an allowlist."
|
|
17
|
+
*
|
|
18
|
+
* That reasoning survives here intact. What changes is only that the three facts live in one
|
|
19
|
+
* declaration instead of three files that must be kept in sync by hand — with `nav` optional
|
|
20
|
+
* (so a panel can be routable without a sidebar row, which the reference implementation's
|
|
21
|
+
* `appearance` and `settings-raw` panels both rely on) and `agentReachable` an explicit opt-in
|
|
22
|
+
* that defaults to false.
|
|
23
|
+
*
|
|
24
|
+
* The security property is strictly better than the three-file version: a panel author who forgets
|
|
25
|
+
* to think about agent reachability gets `false`, and an AI generating a panel manifest cannot
|
|
26
|
+
* make itself agent-reachable by omission. Under the old shape, forgetting to edit
|
|
27
|
+
* `agent-pages.ts` was also safe — but so was forgetting to edit `nav.ts`, and the two mistakes
|
|
28
|
+
* were indistinguishable from reading any one file.
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* A detail-route pattern owned by a panel.
|
|
32
|
+
*
|
|
33
|
+
* The reference implementation's `parseRoute` hard-codes every detail route as a branch in one 30-line if-chain
|
|
34
|
+
* (`/posts/:id`, `/menus/new`, `/widgets/regions/:key`, `/collections/:key/:entryId`, ...), so a
|
|
35
|
+
* panel cannot add a route without editing the router. Declaring patterns on the panel inverts
|
|
36
|
+
* that: the matcher is generic and the panel owns its own URL space.
|
|
37
|
+
*
|
|
38
|
+
* Patterns are matched segment-by-segment against the route path:
|
|
39
|
+
* - a literal segment matches itself exactly
|
|
40
|
+
* - `:name` captures one segment into `params.name`
|
|
41
|
+
*
|
|
42
|
+
* There is deliberately no wildcard/splat and no regex. Every route in the corpus this was ported
|
|
43
|
+
* from is expressible without one, and both features make it possible for two panels to claim
|
|
44
|
+
* overlapping URL space in ways that are hard to diagnose.
|
|
45
|
+
*/
|
|
46
|
+
export interface AdminRoutePattern {
|
|
47
|
+
/** Pattern relative to the panel's own id segment: `/:postId`, `/regions/:regionKey`, `/new`. */
|
|
48
|
+
readonly pattern: string;
|
|
49
|
+
/** Discriminator the panel's renderer switches on. Panel-local; never globally unique. */
|
|
50
|
+
readonly view: string;
|
|
51
|
+
/**
|
|
52
|
+
* The page id this route reports to an agent, when it differs from the panel's.
|
|
53
|
+
*
|
|
54
|
+
* Two distinct effects, split by whether the pattern captures params — and the split is what
|
|
55
|
+
* the reference implementation actually needs, not a generalization:
|
|
56
|
+
*
|
|
57
|
+
* - **Param-free pattern** (`/regions`): the route becomes a published agent *destination* in
|
|
58
|
+
* its own right, reachable by `page.navigate('widget-regions')`.
|
|
59
|
+
* - **Pattern with params** (`/regions/:regionKey`): the route is not a destination (an agent
|
|
60
|
+
* cannot supply the id), but it *reports* this page id, so an agent that navigated to
|
|
61
|
+
* `widget-regions` and landed on the editor is told it arrived where it asked.
|
|
62
|
+
*
|
|
63
|
+
* Omitted (the default) means the route reports its panel's id and publishes nothing — which is
|
|
64
|
+
* right for the common case: `/posts/abc` reports `posts`, the nearest id an agent can act on.
|
|
65
|
+
*
|
|
66
|
+
* This exists because collapsing the two questions is a documented past bug in the reference
|
|
67
|
+
* implementation's `App.tsx`: the sidebar wants a nav row to light up, an agent wants an id it can pass
|
|
68
|
+
* back to `page.navigate`, and widget regions is where they genuinely diverge — the sidebar has
|
|
69
|
+
* no regions row so it highlights `widgets`, while the agent must be told `widget-regions`.
|
|
70
|
+
* Sharing one function meant `page.navigate("widget-regions")` landed correctly and then
|
|
71
|
+
* reported `after: "widgets"`, telling the agent it had arrived somewhere it had not asked for.
|
|
72
|
+
* An agent's only correction for that is to navigate again.
|
|
73
|
+
*/
|
|
74
|
+
readonly agentPageId?: string;
|
|
75
|
+
}
|
|
76
|
+
/** Sidebar presence. Optional on a panel — absence means routable but not listed. */
|
|
77
|
+
export interface AdminNavEntry {
|
|
78
|
+
readonly label: string;
|
|
79
|
+
/** Inner SVG markup for an 18x18 stroked icon (`viewBox="0 0 18 18"`, `stroke="currentColor"`),
|
|
80
|
+
* matching the convention already used across the reference implementation's `nav.ts`. Optional so a panel can be
|
|
81
|
+
* listed before anyone has drawn it an icon. */
|
|
82
|
+
readonly icon?: string;
|
|
83
|
+
/** Group heading. Panels sharing a value are rendered together; omitted means the ungrouped
|
|
84
|
+
* top row. */
|
|
85
|
+
readonly group?: string;
|
|
86
|
+
/** Ascending sort within a group. Ties fall back to registration order, which is stable. */
|
|
87
|
+
readonly order?: number;
|
|
88
|
+
/** Renders as a disabled row — announced but not yet built. */
|
|
89
|
+
readonly soon?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* When `soon` is set, renders the row as a REAL link (not a disabled non-link) while still
|
|
92
|
+
* showing the `soon` badge, so an operator can click through and preview the unfinished screen
|
|
93
|
+
* instead of the row being inert. Ignored when `soon` is falsy — there is nothing to opt into on
|
|
94
|
+
* a shipped row. Opt-in rather than a `soon` behavior change: the existing "disabled non-link"
|
|
95
|
+
* shape stays the default for a panel author who has not deliberately decided the in-progress
|
|
96
|
+
* screen is safe to preview.
|
|
97
|
+
*/
|
|
98
|
+
readonly soonPreviewable?: boolean;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* A panel: one admin section, its URL space, and the conditions under which it mounts.
|
|
102
|
+
*
|
|
103
|
+
* `TRender` is whatever the host's rendering layer needs — a React element thunk for
|
|
104
|
+
* `@jini-ai/admin/react`, something else for another framework. Keeping it a type parameter is
|
|
105
|
+
* what allows this module to stay free of any framework import.
|
|
106
|
+
*/
|
|
107
|
+
export interface AdminPanel<TRender = unknown> {
|
|
108
|
+
/**
|
|
109
|
+
* Stable identifier, and the first URL segment (`users` -> `/users`). Also the value the shell
|
|
110
|
+
* reports as the current page, so it must be the vocabulary a human would guess — the reference
|
|
111
|
+
* implementation's `agent-pages.ts` notes that a separate agent-facing naming scheme would mean an agent reading
|
|
112
|
+
* the page could not act on it without a translation table.
|
|
113
|
+
*/
|
|
114
|
+
readonly id: string;
|
|
115
|
+
/** What the shell renders for this panel. */
|
|
116
|
+
readonly render: TRender;
|
|
117
|
+
/** Sidebar presence. Omit for a panel that is reachable by URL but not listed. */
|
|
118
|
+
readonly nav?: AdminNavEntry;
|
|
119
|
+
/**
|
|
120
|
+
* Whether an AI agent may navigate here via a `page.navigate`-style capability.
|
|
121
|
+
*
|
|
122
|
+
* **Defaults to false, and must stay an explicit opt-in.** See the file header — this is the
|
|
123
|
+
* allowlist property, and deriving it from anything else defeats the point.
|
|
124
|
+
*
|
|
125
|
+
* Detail routes are never agent-reachable regardless of this flag: they need an id the agent
|
|
126
|
+
* has to look up first, and the tool for that is a content catalog, not a navigation allowlist
|
|
127
|
+
* that would have to enumerate every row in the database (the reference implementation's
|
|
128
|
+
* `agent-pages.ts` hits exactly this case).
|
|
129
|
+
*/
|
|
130
|
+
readonly agentReachable?: boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Capability keys this panel needs wired before it can mount.
|
|
133
|
+
*
|
|
134
|
+
* This is the composability lever: a host does not ship or omit panel *code*, it supplies or
|
|
135
|
+
* withholds the *ports* a panel names here, and the registry drops any panel whose requirements
|
|
136
|
+
* are unmet — no dead nav row, no route that renders an error. A build that includes the
|
|
137
|
+
* payments panel but wires no payments port simply has no payments section.
|
|
138
|
+
*/
|
|
139
|
+
readonly requires?: readonly string[];
|
|
140
|
+
/** Detail routes below `/{id}`. See `AdminRoutePattern`. */
|
|
141
|
+
readonly routes?: readonly AdminRoutePattern[];
|
|
142
|
+
/**
|
|
143
|
+
* Permission strings the operator must hold for this panel to appear.
|
|
144
|
+
*
|
|
145
|
+
* **Affordance only, never an authorization boundary** — carried over verbatim from the
|
|
146
|
+
* reference implementation's `lib/permissions.ts`, whose header is unusually clear about this
|
|
147
|
+
* and worth repeating: a bug
|
|
148
|
+
* here "can only show or hide a control; it cannot grant or block the underlying operation,
|
|
149
|
+
* because every mutation and tool call is independently re-checked server-side". Do not let a
|
|
150
|
+
* passing check here stand in for server-side authorization.
|
|
151
|
+
*/
|
|
152
|
+
readonly permissions?: readonly string[];
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/manifest/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,iBAAiB;IAChC,iGAAiG;IACjG,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,0FAA0F;IAC1F,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,qFAAqF;AACrF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;qDAEiD;IACjD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;mBACe;IACf,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,4FAA4F;IAC5F,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;OAOG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;CACpC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,UAAU,CAAC,OAAO,GAAG,OAAO;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAEpB,6CAA6C;IAC7C,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IAEzB,kFAAkF;IAClF,QAAQ,CAAC,GAAG,CAAC,EAAE,aAAa,CAAC;IAE7B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAElC;;;;;;;OAOG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEtC,4DAA4D;IAC5D,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAE/C;;;;;;;;;OASG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file What an admin panel *is* — the contract a panel author codes against.
|
|
3
|
+
*
|
|
4
|
+
* This is the smallest surface in the package on purpose. A panel package (Jini-shipped,
|
|
5
|
+
* host-owned, third-party, or AI-generated) should need this module and nothing else: it has no
|
|
6
|
+
* React, no DOM, no transport, and no knowledge of the shell that will mount it.
|
|
7
|
+
*
|
|
8
|
+
* ## The three-file problem this replaces
|
|
9
|
+
*
|
|
10
|
+
* The reference implementation defines a section in three places — `App.tsx`'s `SECTIONS` map
|
|
11
|
+
* (routing allowlist + render dispatch), `nav.ts` (sidebar label/icon/group/order), and
|
|
12
|
+
* `agent-pages.ts` (which pages an AI agent may navigate to). Its own internal docs are emphatic
|
|
13
|
+
* that this is deliberate, and they are right about *why*:
|
|
14
|
+
*
|
|
15
|
+
* > "Do not derive this from `App.tsx`'s `SECTIONS` map. Doing so would make every new admin
|
|
16
|
+
* > screen agent-reachable simply by existing, which is the opposite of an allowlist."
|
|
17
|
+
*
|
|
18
|
+
* That reasoning survives here intact. What changes is only that the three facts live in one
|
|
19
|
+
* declaration instead of three files that must be kept in sync by hand — with `nav` optional
|
|
20
|
+
* (so a panel can be routable without a sidebar row, which the reference implementation's
|
|
21
|
+
* `appearance` and `settings-raw` panels both rely on) and `agentReachable` an explicit opt-in
|
|
22
|
+
* that defaults to false.
|
|
23
|
+
*
|
|
24
|
+
* The security property is strictly better than the three-file version: a panel author who forgets
|
|
25
|
+
* to think about agent reachability gets `false`, and an AI generating a panel manifest cannot
|
|
26
|
+
* make itself agent-reachable by omission. Under the old shape, forgetting to edit
|
|
27
|
+
* `agent-pages.ts` was also safe — but so was forgetting to edit `nav.ts`, and the two mistakes
|
|
28
|
+
* were indistinguishable from reading any one file.
|
|
29
|
+
*/
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/manifest/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Client-side permission-affordance helper.
|
|
3
|
+
*
|
|
4
|
+
* Ported verbatim in behaviour from the reference implementation's `lib/permissions.ts`. The bug that file
|
|
5
|
+
* exists to prevent is worth restating, because it is not obvious and it cost a real lockout:
|
|
6
|
+
*
|
|
7
|
+
* A host's "effective permissions" list is typically flattened to bare permission strings, and an
|
|
8
|
+
* unconstrained owner grant appears in it as the literal `"*"`. A plain
|
|
9
|
+
* `permissions.includes(permission)` check treats `"*"` as just another permission *name* and
|
|
10
|
+
* never matches it against a real permission like `"comments.read"` — which locked the workspace
|
|
11
|
+
* owner out of every permission-gated affordance in the admin. Every call site that checks a
|
|
12
|
+
* permission string should route through this helper rather than reintroducing a literal
|
|
13
|
+
* `.includes()`.
|
|
14
|
+
*
|
|
15
|
+
* ## NOT A SECURITY BOUNDARY
|
|
16
|
+
*
|
|
17
|
+
* This decides whether a button, section, or form renders. It never runs on the server and is
|
|
18
|
+
* never consulted by any route or command handler. A bug here — too permissive or too
|
|
19
|
+
* restrictive — can only show or hide a control; it cannot grant or block the underlying
|
|
20
|
+
* operation, because every mutation and tool call must be independently re-checked server-side.
|
|
21
|
+
* Do not import this into server code, and do not treat a passing call here as proof an action is
|
|
22
|
+
* actually allowed.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* True iff `permissions` grants `permission` — either an exact match, or the unconstrained owner
|
|
26
|
+
* wildcard `"*"`.
|
|
27
|
+
*
|
|
28
|
+
* @complexity O(n) array scan, n = `permissions.length` (a handful of grant strings for a real
|
|
29
|
+
* principal; never a caller-unbounded collection).
|
|
30
|
+
*/
|
|
31
|
+
export declare function hasPermission(permissions: readonly string[], permission: string): boolean;
|
|
32
|
+
//# sourceMappingURL=rules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../src/core/permissions/rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAEzF"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Client-side permission-affordance helper.
|
|
3
|
+
*
|
|
4
|
+
* Ported verbatim in behaviour from the reference implementation's `lib/permissions.ts`. The bug that file
|
|
5
|
+
* exists to prevent is worth restating, because it is not obvious and it cost a real lockout:
|
|
6
|
+
*
|
|
7
|
+
* A host's "effective permissions" list is typically flattened to bare permission strings, and an
|
|
8
|
+
* unconstrained owner grant appears in it as the literal `"*"`. A plain
|
|
9
|
+
* `permissions.includes(permission)` check treats `"*"` as just another permission *name* and
|
|
10
|
+
* never matches it against a real permission like `"comments.read"` — which locked the workspace
|
|
11
|
+
* owner out of every permission-gated affordance in the admin. Every call site that checks a
|
|
12
|
+
* permission string should route through this helper rather than reintroducing a literal
|
|
13
|
+
* `.includes()`.
|
|
14
|
+
*
|
|
15
|
+
* ## NOT A SECURITY BOUNDARY
|
|
16
|
+
*
|
|
17
|
+
* This decides whether a button, section, or form renders. It never runs on the server and is
|
|
18
|
+
* never consulted by any route or command handler. A bug here — too permissive or too
|
|
19
|
+
* restrictive — can only show or hide a control; it cannot grant or block the underlying
|
|
20
|
+
* operation, because every mutation and tool call must be independently re-checked server-side.
|
|
21
|
+
* Do not import this into server code, and do not treat a passing call here as proof an action is
|
|
22
|
+
* actually allowed.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* True iff `permissions` grants `permission` — either an exact match, or the unconstrained owner
|
|
26
|
+
* wildcard `"*"`.
|
|
27
|
+
*
|
|
28
|
+
* @complexity O(n) array scan, n = `permissions.length` (a handful of grant strings for a real
|
|
29
|
+
* principal; never a caller-unbounded collection).
|
|
30
|
+
*/
|
|
31
|
+
export function hasPermission(permissions, permission) {
|
|
32
|
+
return permissions.includes('*') || permissions.includes(permission);
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.js","sourceRoot":"","sources":["../../../src/core/permissions/rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,WAA8B,EAAE,UAAkB;IAC9E,OAAO,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;AACvE,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file `AdminAnalyticsPort` — a rolling window of recent traffic hits, for a live-ish activity
|
|
3
|
+
* view rather than aggregate reporting.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately the thinnest port in the set (one method, no mutation, no pagination cursor). The
|
|
6
|
+
* reference implementation's route returns a bounded recent-hits list, not a queryable analytics store — there is no
|
|
7
|
+
* date-range filter, no aggregation, and no historical query on the contract because the reference
|
|
8
|
+
* implementation has none of those either. A host that needs real analytics reporting (rollups,
|
|
9
|
+
* date ranges, funnels, ...) needs a different, dedicated port; stretching this one to cover that
|
|
10
|
+
* by adding options over time would blur "recent activity feed" into "analytics platform", which
|
|
11
|
+
* is a materially different feature with different performance and retention expectations.
|
|
12
|
+
*/
|
|
13
|
+
/** One traffic event — either a page view or a named custom event. */
|
|
14
|
+
export interface AdminAnalyticsHit {
|
|
15
|
+
readonly occurredAt: string;
|
|
16
|
+
readonly kind: "pageview" | "event";
|
|
17
|
+
readonly path: string;
|
|
18
|
+
/** Null when there was no referrer (direct navigation) or the host could not determine one. */
|
|
19
|
+
readonly referrerHost: string | null;
|
|
20
|
+
readonly deviceClass: "desktop" | "mobile" | "tablet" | "bot" | "unknown";
|
|
21
|
+
/** Null when the host could not classify the browser (e.g. an unrecognized or absent
|
|
22
|
+
* user-agent). */
|
|
23
|
+
readonly browserFamily: string | null;
|
|
24
|
+
/** Set only for `kind: "event"`; null for a plain pageview. */
|
|
25
|
+
readonly eventName: string | null;
|
|
26
|
+
}
|
|
27
|
+
export interface AdminAnalyticsPort {
|
|
28
|
+
/** `limit` bounds how many recent hits come back; omitting it defers to the host's own default
|
|
29
|
+
* bound rather than requesting an unbounded list — see the file header on why this is a recent
|
|
30
|
+
* activity feed, not a queryable store. */
|
|
31
|
+
listRecentAnalyticsHits(options?: {
|
|
32
|
+
limit?: number;
|
|
33
|
+
}): Promise<readonly AdminAnalyticsHit[]>;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=analytics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../../src/core/ports/analytics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,sEAAsE;AACtE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+FAA+F;IAC/F,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,WAAW,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,KAAK,GAAG,SAAS,CAAC;IAC1E;uBACmB;IACnB,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,+DAA+D;IAC/D,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,kBAAkB;IACjC;;gDAE4C;IAC5C,uBAAuB,CAAC,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CAAC;CAC9F"}
|