@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,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Pure route matching, driven by the panel registry rather than a hard-coded branch list.
|
|
3
|
+
*
|
|
4
|
+
* ## What changed from the ported original
|
|
5
|
+
*
|
|
6
|
+
* The reference implementation's `parseRoute` is a ~30-line if-chain with one branch per
|
|
7
|
+
* detail route — `/posts/:id`, `/menus/new`, `/widgets/regions/:key`,
|
|
8
|
+
* `/collections/:key/:entryId`, and so on. It works, and its fall-through-to-dashboard behaviour
|
|
9
|
+
* is deliberate. But it means a panel cannot own a URL without an edit to the router, which is
|
|
10
|
+
* fatal for a package whose whole point is that hosts and AI-generated code add panels.
|
|
11
|
+
*
|
|
12
|
+
* Here the panel declares its patterns and the matcher is generic. Behaviour that is preserved
|
|
13
|
+
* exactly, because each was a considered decision in the original:
|
|
14
|
+
*
|
|
15
|
+
* - **Unrecognized paths fall through** (`panelId: null`) rather than rendering a placeholder,
|
|
16
|
+
* so a typo behaves like a bad URL rather than an empty screen.
|
|
17
|
+
* - **A bare single segment only matches a registered panel.** Testing against the registry
|
|
18
|
+
* rather than accepting any single segment is what keeps `/typo` from rendering an empty shell.
|
|
19
|
+
* - **Trailing slashes are insignificant** — segments are split and empties dropped, so
|
|
20
|
+
* `/settings/` and `/settings` are the same screen.
|
|
21
|
+
* - **Query is parsed but never part of matching.** The reference implementation's widget-editor
|
|
22
|
+
* route reads `?type=` for its initial state; that is panel state, not routing.
|
|
23
|
+
*/
|
|
24
|
+
import type { AdminPanel } from '../manifest/types.js';
|
|
25
|
+
import type { AdminRoute } from './types.js';
|
|
26
|
+
/** Default mount point. Overridable per call — a product that mounts its admin elsewhere passes
|
|
27
|
+
* its own base rather than patching this package. */
|
|
28
|
+
export declare const DEFAULT_ADMIN_BASE = "/admin";
|
|
29
|
+
/** `"/admin/settings/"` -> `"/admin/settings"`; leaves a lone `"/"` alone. */
|
|
30
|
+
export declare function stripTrailingSlash(pathname: string): string;
|
|
31
|
+
/** Route path -> URL. `/settings` -> `/admin/settings`; `/` -> `/admin/`. */
|
|
32
|
+
export declare function adminHref(routePath: string, base?: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* URL pathname -> route path, base stripped. Always starts with `/`.
|
|
35
|
+
*
|
|
36
|
+
* A bare `/admin` (no trailing slash) and `/admin/` both mean the dashboard, so the base is
|
|
37
|
+
* removed by prefix rather than by assuming a trailing separator.
|
|
38
|
+
*
|
|
39
|
+
* Unlike the reference implementation's version this takes `pathname` as a required argument instead of defaulting to
|
|
40
|
+
* `window.location.pathname` — that default is what made the original untestable without a DOM,
|
|
41
|
+
* and it belongs in the browser layer, not here.
|
|
42
|
+
*/
|
|
43
|
+
export declare function currentRoutePath(pathname: string, base?: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* Matches a route path against the registered panels.
|
|
46
|
+
*
|
|
47
|
+
* Callers should pass the **resolved** panel set (see `resolvePanels`), not the raw one — routing
|
|
48
|
+
* to a panel whose capability is not wired would render a section the host deliberately withheld.
|
|
49
|
+
*
|
|
50
|
+
* @complexity O(p * r * s) — p panels, r patterns per panel, s segments per pattern. Bounded by
|
|
51
|
+
* the manifest, and the common case exits on the first segment comparison.
|
|
52
|
+
*/
|
|
53
|
+
export declare function matchRoute<T>(routePath: string, panels: readonly AdminPanel<T>[]): AdminRoute;
|
|
54
|
+
//# sourceMappingURL=rules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../src/core/routing/rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;sDACsD;AACtD,eAAO,MAAM,kBAAkB,WAAW,CAAC;AAE3C,8EAA8E;AAC9E,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,6EAA6E;AAC7E,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,GAAE,MAA2B,GAAG,MAAM,CAGtF;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,GAAE,MAA2B,GAAG,MAAM,CAM5F;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,UAAU,CAAC,CAAC,CAAC,EAAE,GAAG,UAAU,CA0B7F"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Pure route matching, driven by the panel registry rather than a hard-coded branch list.
|
|
3
|
+
*
|
|
4
|
+
* ## What changed from the ported original
|
|
5
|
+
*
|
|
6
|
+
* The reference implementation's `parseRoute` is a ~30-line if-chain with one branch per
|
|
7
|
+
* detail route — `/posts/:id`, `/menus/new`, `/widgets/regions/:key`,
|
|
8
|
+
* `/collections/:key/:entryId`, and so on. It works, and its fall-through-to-dashboard behaviour
|
|
9
|
+
* is deliberate. But it means a panel cannot own a URL without an edit to the router, which is
|
|
10
|
+
* fatal for a package whose whole point is that hosts and AI-generated code add panels.
|
|
11
|
+
*
|
|
12
|
+
* Here the panel declares its patterns and the matcher is generic. Behaviour that is preserved
|
|
13
|
+
* exactly, because each was a considered decision in the original:
|
|
14
|
+
*
|
|
15
|
+
* - **Unrecognized paths fall through** (`panelId: null`) rather than rendering a placeholder,
|
|
16
|
+
* so a typo behaves like a bad URL rather than an empty screen.
|
|
17
|
+
* - **A bare single segment only matches a registered panel.** Testing against the registry
|
|
18
|
+
* rather than accepting any single segment is what keeps `/typo` from rendering an empty shell.
|
|
19
|
+
* - **Trailing slashes are insignificant** — segments are split and empties dropped, so
|
|
20
|
+
* `/settings/` and `/settings` are the same screen.
|
|
21
|
+
* - **Query is parsed but never part of matching.** The reference implementation's widget-editor
|
|
22
|
+
* route reads `?type=` for its initial state; that is panel state, not routing.
|
|
23
|
+
*/
|
|
24
|
+
/** Default mount point. Overridable per call — a product that mounts its admin elsewhere passes
|
|
25
|
+
* its own base rather than patching this package. */
|
|
26
|
+
export const DEFAULT_ADMIN_BASE = '/admin';
|
|
27
|
+
/** `"/admin/settings/"` -> `"/admin/settings"`; leaves a lone `"/"` alone. */
|
|
28
|
+
export function stripTrailingSlash(pathname) {
|
|
29
|
+
return pathname.length > 1 && pathname.endsWith('/') ? pathname.slice(0, -1) : pathname;
|
|
30
|
+
}
|
|
31
|
+
/** Route path -> URL. `/settings` -> `/admin/settings`; `/` -> `/admin/`. */
|
|
32
|
+
export function adminHref(routePath, base = DEFAULT_ADMIN_BASE) {
|
|
33
|
+
const normalized = routePath.startsWith('/') ? routePath : `/${routePath}`;
|
|
34
|
+
return `${base}${normalized}`;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* URL pathname -> route path, base stripped. Always starts with `/`.
|
|
38
|
+
*
|
|
39
|
+
* A bare `/admin` (no trailing slash) and `/admin/` both mean the dashboard, so the base is
|
|
40
|
+
* removed by prefix rather than by assuming a trailing separator.
|
|
41
|
+
*
|
|
42
|
+
* Unlike the reference implementation's version this takes `pathname` as a required argument instead of defaulting to
|
|
43
|
+
* `window.location.pathname` — that default is what made the original untestable without a DOM,
|
|
44
|
+
* and it belongs in the browser layer, not here.
|
|
45
|
+
*/
|
|
46
|
+
export function currentRoutePath(pathname, base = DEFAULT_ADMIN_BASE) {
|
|
47
|
+
if (pathname === base)
|
|
48
|
+
return '/';
|
|
49
|
+
if (pathname.startsWith(`${base}/`))
|
|
50
|
+
return pathname.slice(base.length) || '/';
|
|
51
|
+
// Reached only if the app is mounted somewhere unexpected; treat the whole path as the route
|
|
52
|
+
// rather than silently returning the dashboard, so the mismatch is visible in the URL.
|
|
53
|
+
return pathname || '/';
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Matches a route path against the registered panels.
|
|
57
|
+
*
|
|
58
|
+
* Callers should pass the **resolved** panel set (see `resolvePanels`), not the raw one — routing
|
|
59
|
+
* to a panel whose capability is not wired would render a section the host deliberately withheld.
|
|
60
|
+
*
|
|
61
|
+
* @complexity O(p * r * s) — p panels, r patterns per panel, s segments per pattern. Bounded by
|
|
62
|
+
* the manifest, and the common case exits on the first segment comparison.
|
|
63
|
+
*/
|
|
64
|
+
export function matchRoute(routePath, panels) {
|
|
65
|
+
const [rawPath, rawQuery] = routePath.split('?');
|
|
66
|
+
const segments = (rawPath ?? '').split('/').filter(Boolean);
|
|
67
|
+
const query = new URLSearchParams(rawQuery ?? '');
|
|
68
|
+
const miss = (panelId) => ({ panelId, view: null, params: {}, query });
|
|
69
|
+
// Root is the dashboard by convention, and `dashboard` need not be registered for `/` to
|
|
70
|
+
// resolve — the shell's fallback handles an empty registry.
|
|
71
|
+
if (segments.length === 0)
|
|
72
|
+
return miss('dashboard');
|
|
73
|
+
const panel = panels.find((p) => p.id === segments[0]);
|
|
74
|
+
if (!panel)
|
|
75
|
+
return miss(null);
|
|
76
|
+
const rest = segments.slice(1);
|
|
77
|
+
// Bare panel segment: the index view.
|
|
78
|
+
if (rest.length === 0)
|
|
79
|
+
return miss(panel.id);
|
|
80
|
+
for (const route of panel.routes ?? []) {
|
|
81
|
+
const params = matchPattern(route.pattern, rest);
|
|
82
|
+
if (params)
|
|
83
|
+
return { panelId: panel.id, view: route.view, params, query };
|
|
84
|
+
}
|
|
85
|
+
// A registered panel with unmatched trailing segments. Falls through rather than rendering the
|
|
86
|
+
// panel's index view, so `/posts/abc/typo` is a bad URL rather than silently showing the list.
|
|
87
|
+
return miss(null);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Matches one pattern against already-split segments.
|
|
91
|
+
*
|
|
92
|
+
* @returns captured params, or `null` when the pattern does not match. An empty object is a
|
|
93
|
+
* successful match with no captures — which is why this returns `null` rather than `{}` for a
|
|
94
|
+
* miss, and why callers must check for `null` explicitly rather than truthiness of the size.
|
|
95
|
+
*/
|
|
96
|
+
function matchPattern(pattern, segments) {
|
|
97
|
+
const patternSegments = pattern.split('/').filter(Boolean);
|
|
98
|
+
if (patternSegments.length !== segments.length)
|
|
99
|
+
return null;
|
|
100
|
+
const params = {};
|
|
101
|
+
for (let i = 0; i < patternSegments.length; i += 1) {
|
|
102
|
+
const patternSegment = patternSegments[i] ?? '';
|
|
103
|
+
const segment = segments[i] ?? '';
|
|
104
|
+
if (patternSegment.startsWith(':')) {
|
|
105
|
+
params[patternSegment.slice(1)] = segment;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (patternSegment !== segment)
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
return params;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.js","sourceRoot":"","sources":["../../../src/core/routing/rules.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Route model types.
|
|
3
|
+
*
|
|
4
|
+
* ## Route paths vs URLs — the distinction this module exists to keep straight
|
|
5
|
+
*
|
|
6
|
+
* Carried over from the reference implementation's `lib/router.ts`, where collapsing these two is documented as the exact
|
|
7
|
+
* mistake that produced `/admin/#/section/settings` URLs:
|
|
8
|
+
*
|
|
9
|
+
* - a **route path** is base-agnostic and is what the matcher consumes: `/`, `/settings`,
|
|
10
|
+
* `/posts/abc`
|
|
11
|
+
* - a **URL** is what goes in an `href` or the address bar: `/admin/`, `/admin/settings`
|
|
12
|
+
*
|
|
13
|
+
* `adminHref` converts the first into the second; `currentRoutePath` recovers the first from a
|
|
14
|
+
* pathname. Nothing else should be doing string surgery on a location.
|
|
15
|
+
*/
|
|
16
|
+
/** A resolved route. */
|
|
17
|
+
export interface AdminRoute {
|
|
18
|
+
/**
|
|
19
|
+
* The matched panel's id, or `null` when nothing matched.
|
|
20
|
+
*
|
|
21
|
+
* `null` is not an error state the shell must handle specially — the documented behaviour, kept
|
|
22
|
+
* from the reference implementation, is that an unrecognized path falls through to the dashboard rather than rendering
|
|
23
|
+
* an empty placeholder for a typo.
|
|
24
|
+
*/
|
|
25
|
+
readonly panelId: string | null;
|
|
26
|
+
/**
|
|
27
|
+
* Which of the panel's views to render. `null` means the panel's own index view (`/users`), as
|
|
28
|
+
* opposed to one of its detail routes (`/users/abc`).
|
|
29
|
+
*/
|
|
30
|
+
readonly view: string | null;
|
|
31
|
+
/** Captured `:name` segments from the matched pattern. Empty for an index route. */
|
|
32
|
+
readonly params: Readonly<Record<string, string>>;
|
|
33
|
+
/** Parsed query string. Always present, possibly empty. */
|
|
34
|
+
readonly query: URLSearchParams;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/routing/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,wBAAwB;AACxB,MAAM,WAAW,UAAU;IACzB;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAEhC;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAE7B,oFAAoF;IACpF,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAElD,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;CACjC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Route model types.
|
|
3
|
+
*
|
|
4
|
+
* ## Route paths vs URLs — the distinction this module exists to keep straight
|
|
5
|
+
*
|
|
6
|
+
* Carried over from the reference implementation's `lib/router.ts`, where collapsing these two is documented as the exact
|
|
7
|
+
* mistake that produced `/admin/#/section/settings` URLs:
|
|
8
|
+
*
|
|
9
|
+
* - a **route path** is base-agnostic and is what the matcher consumes: `/`, `/settings`,
|
|
10
|
+
* `/posts/abc`
|
|
11
|
+
* - a **URL** is what goes in an `href` or the address bar: `/admin/`, `/admin/settings`
|
|
12
|
+
*
|
|
13
|
+
* `adminHref` converts the first into the second; `currentRoutePath` recovers the first from a
|
|
14
|
+
* pathname. Nothing else should be doing string surgery on a location.
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/routing/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The error contract every admin route group throws, host-supplied or Jini-shipped.
|
|
3
|
+
*
|
|
4
|
+
* Ported from the reference implementation's `lib/api.ts` (`ApiError`, `describeApiError`), which is the
|
|
5
|
+
* only implementation of this shape that has been through real operator use. Two decisions from
|
|
6
|
+
* that file are carried across deliberately, because both were arrived at by an audit and both
|
|
7
|
+
* look like oversights if you only read the code:
|
|
8
|
+
*
|
|
9
|
+
* 1. **`body` is kept raw.** A caller sometimes needs a route-specific field beyond
|
|
10
|
+
* `code`/`message` (a 409's `currentVersion`, a validation route's field list). Exposing the
|
|
11
|
+
* parsed body means that never requires an `AdminApiError` subclass per route.
|
|
12
|
+
*
|
|
13
|
+
* 2. **There is deliberately NO shared `code` -> message table.** The same `code` means
|
|
14
|
+
* genuinely different things in different domains — in the reference implementation,
|
|
15
|
+
* `RESOURCE_CONFLICT` is "still
|
|
16
|
+
* referenced elsewhere" on Roles, "that slug is already taken" on Workspace, and "that
|
|
17
|
+
* username is already in use" on Users. Three correct operator-facing sentences for one code.
|
|
18
|
+
* A shared table could only pick one, which is a regression dressed as consolidation. So
|
|
19
|
+
* `describeApiError` is only the *base case*: it decides what to show when the server sent no
|
|
20
|
+
* message, or when the thrown value is not an `AdminApiError` at all (network failure, a
|
|
21
|
+
* thrown non-Error). Panels layer their own per-code copy on top and fall through to this.
|
|
22
|
+
*/
|
|
23
|
+
/** A failed admin API call that reached the server and came back non-2xx. */
|
|
24
|
+
export declare class AdminApiError extends Error {
|
|
25
|
+
readonly status: number;
|
|
26
|
+
/**
|
|
27
|
+
* Canonical error `code` from the response body when the server sent one. Not enumerated here
|
|
28
|
+
* on purpose — the vocabulary belongs to the host's API, and a union type in this package would
|
|
29
|
+
* have to be widened by a Jini release every time a product added a code.
|
|
30
|
+
*/
|
|
31
|
+
readonly code?: string | undefined;
|
|
32
|
+
/** Raw parsed JSON error body, when present. See decision 1 in the file header. */
|
|
33
|
+
readonly body?: Record<string, unknown> | undefined;
|
|
34
|
+
constructor(message: string, status: number, code?: string, body?: Record<string, unknown>);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Base-case translation from a thrown request error to operator-facing copy.
|
|
38
|
+
*
|
|
39
|
+
* This is the "opt-out by default" fallback, not a complete message catalog — see decision 2 in
|
|
40
|
+
* the file header. A panel with real per-code handling checks its own codes first and falls
|
|
41
|
+
* through here for the rest:
|
|
42
|
+
*
|
|
43
|
+
* ```ts
|
|
44
|
+
* function describe(e: unknown, fallback: string): string {
|
|
45
|
+
* if (e instanceof AdminApiError && e.code === 'PANEL_SPECIFIC_CODE') return '...';
|
|
46
|
+
* return describeApiError(e, fallback);
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @complexity O(1) — two `instanceof` checks, no iteration.
|
|
51
|
+
*/
|
|
52
|
+
export declare function describeApiError(e: unknown, fallback: string): string;
|
|
53
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/core/transport/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,6EAA6E;AAC7E,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAEnC,mFAAmF;IACnF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;gBAExC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAO3F;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGrE"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The error contract every admin route group throws, host-supplied or Jini-shipped.
|
|
3
|
+
*
|
|
4
|
+
* Ported from the reference implementation's `lib/api.ts` (`ApiError`, `describeApiError`), which is the
|
|
5
|
+
* only implementation of this shape that has been through real operator use. Two decisions from
|
|
6
|
+
* that file are carried across deliberately, because both were arrived at by an audit and both
|
|
7
|
+
* look like oversights if you only read the code:
|
|
8
|
+
*
|
|
9
|
+
* 1. **`body` is kept raw.** A caller sometimes needs a route-specific field beyond
|
|
10
|
+
* `code`/`message` (a 409's `currentVersion`, a validation route's field list). Exposing the
|
|
11
|
+
* parsed body means that never requires an `AdminApiError` subclass per route.
|
|
12
|
+
*
|
|
13
|
+
* 2. **There is deliberately NO shared `code` -> message table.** The same `code` means
|
|
14
|
+
* genuinely different things in different domains — in the reference implementation,
|
|
15
|
+
* `RESOURCE_CONFLICT` is "still
|
|
16
|
+
* referenced elsewhere" on Roles, "that slug is already taken" on Workspace, and "that
|
|
17
|
+
* username is already in use" on Users. Three correct operator-facing sentences for one code.
|
|
18
|
+
* A shared table could only pick one, which is a regression dressed as consolidation. So
|
|
19
|
+
* `describeApiError` is only the *base case*: it decides what to show when the server sent no
|
|
20
|
+
* message, or when the thrown value is not an `AdminApiError` at all (network failure, a
|
|
21
|
+
* thrown non-Error). Panels layer their own per-code copy on top and fall through to this.
|
|
22
|
+
*/
|
|
23
|
+
/** A failed admin API call that reached the server and came back non-2xx. */
|
|
24
|
+
export class AdminApiError extends Error {
|
|
25
|
+
status;
|
|
26
|
+
/**
|
|
27
|
+
* Canonical error `code` from the response body when the server sent one. Not enumerated here
|
|
28
|
+
* on purpose — the vocabulary belongs to the host's API, and a union type in this package would
|
|
29
|
+
* have to be widened by a Jini release every time a product added a code.
|
|
30
|
+
*/
|
|
31
|
+
code;
|
|
32
|
+
/** Raw parsed JSON error body, when present. See decision 1 in the file header. */
|
|
33
|
+
body;
|
|
34
|
+
constructor(message, status, code, body) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.name = 'AdminApiError';
|
|
37
|
+
this.status = status;
|
|
38
|
+
this.code = code;
|
|
39
|
+
this.body = body;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Base-case translation from a thrown request error to operator-facing copy.
|
|
44
|
+
*
|
|
45
|
+
* This is the "opt-out by default" fallback, not a complete message catalog — see decision 2 in
|
|
46
|
+
* the file header. A panel with real per-code handling checks its own codes first and falls
|
|
47
|
+
* through here for the rest:
|
|
48
|
+
*
|
|
49
|
+
* ```ts
|
|
50
|
+
* function describe(e: unknown, fallback: string): string {
|
|
51
|
+
* if (e instanceof AdminApiError && e.code === 'PANEL_SPECIFIC_CODE') return '...';
|
|
52
|
+
* return describeApiError(e, fallback);
|
|
53
|
+
* }
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @complexity O(1) — two `instanceof` checks, no iteration.
|
|
57
|
+
*/
|
|
58
|
+
export function describeApiError(e, fallback) {
|
|
59
|
+
if (e instanceof AdminApiError)
|
|
60
|
+
return e.message || fallback;
|
|
61
|
+
return e instanceof Error ? e.message : fallback;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/core/transport/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,6EAA6E;AAC7E,MAAM,OAAO,aAAc,SAAQ,KAAK;IAC7B,MAAM,CAAS;IAExB;;;;OAIG;IACM,IAAI,CAAsB;IAEnC,mFAAmF;IAC1E,IAAI,CAAuC;IAEpD,YAAY,OAAe,EAAE,MAAc,EAAE,IAAa,EAAE,IAA8B;QACxF,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;QAC5B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,gBAAgB,CAAC,CAAU,EAAE,QAAgB;IAC3D,IAAI,CAAC,YAAY,aAAa;QAAE,OAAO,CAAC,CAAC,OAAO,IAAI,QAAQ,CAAC;IAC7D,OAAO,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC;AACnD,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The default HTTP `AdminTransport`, and the client assembler.
|
|
3
|
+
*
|
|
4
|
+
* Ported from the reference implementation's `lib/api.ts` `request()`, with the two
|
|
5
|
+
* hard-coded values it closed over — the base URL and the same-origin cookie policy — lifted into
|
|
6
|
+
* options. Nothing else about the request/response handling changed; the error-body parsing in
|
|
7
|
+
* particular is deliberately identical, including the `.catch(() => ({}))` on a body that is not
|
|
8
|
+
* JSON.
|
|
9
|
+
*/
|
|
10
|
+
import type { AdminClient, AdminRouteGroupFactory, AdminTransport } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* The subset of `fetch` this transport actually uses.
|
|
13
|
+
*
|
|
14
|
+
* Deliberately narrower than `typeof globalThis.fetch`: the transport only ever passes a string
|
|
15
|
+
* URL, never a `URL` or a `Request`. Declaring that narrows what a test double has to implement
|
|
16
|
+
* (the global signature forces a stub to handle three input shapes it will never receive), and
|
|
17
|
+
* the real `globalThis.fetch` is still assignable here because a function accepting a wider input
|
|
18
|
+
* satisfies one accepting a narrower one.
|
|
19
|
+
*/
|
|
20
|
+
export type AdminFetch = (input: string, init?: RequestInit) => Promise<Response>;
|
|
21
|
+
export interface HttpTransportOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Prefix for every path (`/api/admin/v1`). No trailing slash — paths always start with `/`.
|
|
24
|
+
*
|
|
25
|
+
* Unlike the reference implementation's module-level `const BASE`, this is per-transport so one
|
|
26
|
+
* process can talk to more than one admin API. A fleet orchestrator managing many site instances
|
|
27
|
+
* in one process is exactly the case where a single shared base is the thing that would have to
|
|
28
|
+
* be unpicked first.
|
|
29
|
+
*/
|
|
30
|
+
readonly baseUrl: string;
|
|
31
|
+
/**
|
|
32
|
+
* Injected `fetch`, defaulting to the global. Present so a test can supply a stub without
|
|
33
|
+
* `vi.stubGlobal`, which is what forced the reference implementation's own hook tests to assert
|
|
34
|
+
* URLs and HTTP methods when what they meant to describe was behaviour.
|
|
35
|
+
*/
|
|
36
|
+
readonly fetch?: AdminFetch;
|
|
37
|
+
/**
|
|
38
|
+
* Merged into every request. `Content-Type: application/json` is applied unless overridden;
|
|
39
|
+
* a per-call `headers` still wins over both.
|
|
40
|
+
*/
|
|
41
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
42
|
+
/**
|
|
43
|
+
* Defaults to `same-origin`, matching the cookie-session admin the reference implementation
|
|
44
|
+
* ships. A token-authenticated
|
|
45
|
+
* host sets `omit` and supplies an `Authorization` header instead.
|
|
46
|
+
*/
|
|
47
|
+
readonly credentials?: RequestCredentials;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The standard `fetch`-backed transport.
|
|
51
|
+
*
|
|
52
|
+
* Success bodies are parsed as JSON. A non-2xx response throws `AdminApiError` carrying the
|
|
53
|
+
* server's `error` string, HTTP status, canonical `code` when present, and the raw parsed body.
|
|
54
|
+
*/
|
|
55
|
+
export declare function createHttpTransport(options: HttpTransportOptions): AdminTransport;
|
|
56
|
+
/**
|
|
57
|
+
* Assembles route groups into one client.
|
|
58
|
+
*
|
|
59
|
+
* Jini-shipped and host-owned groups are indistinguishable here on purpose — both are just
|
|
60
|
+
* `(transport) => port`. See `types.ts`'s header for the worked example.
|
|
61
|
+
*/
|
|
62
|
+
export declare function createAdminClient<TGroups extends Record<string, AdminRouteGroupFactory<unknown>>>(transport: AdminTransport, groups: TGroups): AdminClient<TGroups>;
|
|
63
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../src/core/transport/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEtF;;;;;;;;GAQG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAElF,MAAM,WAAW,oBAAoB;IACnC;;;;;;;OAOG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC;IAE5B;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAEpD;;;;OAIG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,kBAAkB,CAAC;CAC3C;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,cAAc,CAkCjF;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,OAAO,CAAC,CAAC,EAC/F,SAAS,EAAE,cAAc,EACzB,MAAM,EAAE,OAAO,GACd,WAAW,CAAC,OAAO,CAAC,CAYtB"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The default HTTP `AdminTransport`, and the client assembler.
|
|
3
|
+
*
|
|
4
|
+
* Ported from the reference implementation's `lib/api.ts` `request()`, with the two
|
|
5
|
+
* hard-coded values it closed over — the base URL and the same-origin cookie policy — lifted into
|
|
6
|
+
* options. Nothing else about the request/response handling changed; the error-body parsing in
|
|
7
|
+
* particular is deliberately identical, including the `.catch(() => ({}))` on a body that is not
|
|
8
|
+
* JSON.
|
|
9
|
+
*/
|
|
10
|
+
import { AdminApiError } from './errors.js';
|
|
11
|
+
/**
|
|
12
|
+
* The standard `fetch`-backed transport.
|
|
13
|
+
*
|
|
14
|
+
* Success bodies are parsed as JSON. A non-2xx response throws `AdminApiError` carrying the
|
|
15
|
+
* server's `error` string, HTTP status, canonical `code` when present, and the raw parsed body.
|
|
16
|
+
*/
|
|
17
|
+
export function createHttpTransport(options) {
|
|
18
|
+
const { baseUrl, headers: baseHeaders, credentials = 'same-origin' } = options;
|
|
19
|
+
const doFetch = options.fetch ?? globalThis.fetch;
|
|
20
|
+
return {
|
|
21
|
+
async request(path, init = {}) {
|
|
22
|
+
const res = await doFetch(`${baseUrl}${path}`, {
|
|
23
|
+
credentials,
|
|
24
|
+
...init,
|
|
25
|
+
// Spread last so a per-call header overrides both the JSON default and the transport's
|
|
26
|
+
// own. The reference implementation's original spread `...init` last, which meant a call passing `headers` lost
|
|
27
|
+
// the `Content-Type` default rather than merging with it.
|
|
28
|
+
headers: {
|
|
29
|
+
'Content-Type': 'application/json',
|
|
30
|
+
...baseHeaders,
|
|
31
|
+
...init.headers,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
// A 204 or an empty body is not a JSON parse failure worth surfacing — it is a successful
|
|
35
|
+
// mutation with nothing to return. Same `.catch(() => ({}))` as the original.
|
|
36
|
+
const body = (await res.json().catch(() => ({})));
|
|
37
|
+
if (!res.ok) {
|
|
38
|
+
throw new AdminApiError(String(body?.error ?? `request failed (${res.status})`), res.status, typeof body?.code === 'string' ? body.code : undefined, body);
|
|
39
|
+
}
|
|
40
|
+
return body;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Assembles route groups into one client.
|
|
46
|
+
*
|
|
47
|
+
* Jini-shipped and host-owned groups are indistinguishable here on purpose — both are just
|
|
48
|
+
* `(transport) => port`. See `types.ts`'s header for the worked example.
|
|
49
|
+
*/
|
|
50
|
+
export function createAdminClient(transport, groups) {
|
|
51
|
+
const built = { transport };
|
|
52
|
+
for (const [name, factory] of Object.entries(groups)) {
|
|
53
|
+
// `transport` is a reserved key: shadowing it would silently replace the escape hatch every
|
|
54
|
+
// host relies on for un-wrapped routes. Loud failure beats a client whose `.transport` is a
|
|
55
|
+
// route group.
|
|
56
|
+
if (name === 'transport') {
|
|
57
|
+
throw new Error('createAdminClient: "transport" is a reserved route-group name');
|
|
58
|
+
}
|
|
59
|
+
built[name] = factory(transport);
|
|
60
|
+
}
|
|
61
|
+
return built;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/core/transport/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA8C5C;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAA6B;IAC/D,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,WAAW,GAAG,aAAa,EAAE,GAAG,OAAO,CAAC;IAC/E,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC;IAElD,OAAO;QACL,KAAK,CAAC,OAAO,CAAI,IAAY,EAAE,OAAoB,EAAE;YACnD,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,OAAO,GAAG,IAAI,EAAE,EAAE;gBAC7C,WAAW;gBACX,GAAG,IAAI;gBACP,uFAAuF;gBACvF,gHAAgH;gBAChH,0DAA0D;gBAC1D,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,GAAG,WAAW;oBACd,GAAI,IAAI,CAAC,OAA8C;iBACxD;aACF,CAAC,CAAC;YAEH,0FAA0F;YAC1F,8EAA8E;YAC9E,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAA4B,CAAC;YAE7E,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,MAAM,IAAI,aAAa,CACrB,MAAM,CAAC,IAAI,EAAE,KAAK,IAAI,mBAAmB,GAAG,CAAC,MAAM,GAAG,CAAC,EACvD,GAAG,CAAC,MAAM,EACV,OAAO,IAAI,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EACtD,IAAI,CACL,CAAC;YACJ,CAAC;YACD,OAAO,IAAS,CAAC;QACnB,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAC/B,SAAyB,EACzB,MAAe;IAEf,MAAM,KAAK,GAA4B,EAAE,SAAS,EAAE,CAAC;IACrD,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACrD,4FAA4F;QAC5F,4FAA4F;QAC5F,eAAe;QACf,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;QACnF,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,KAA6B,CAAC;AACvC,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { AdminApiError, describeApiError } from './errors.js';
|
|
2
|
+
export { createAdminClient, createHttpTransport } from './http.js';
|
|
3
|
+
export type { AdminFetch, HttpTransportOptions } from './http.js';
|
|
4
|
+
export type { AdminClient, AdminRouteGroupFactory, AdminTransport } from './types.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/transport/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACnE,YAAY,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAClE,YAAY,EAAE,WAAW,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/transport/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The transport seam — the single primitive every admin route group is built on.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this exists
|
|
5
|
+
*
|
|
6
|
+
* The reference implementation's `lib/api.ts` is 1,548 lines and 134 methods in one flat object literal,
|
|
7
|
+
* every one of them closing over a module-private `request()` and a module-private
|
|
8
|
+
* `BASE = "/api/admin/v1"`. That shape has two costs: the whole object is one unit (you cannot
|
|
9
|
+
* take the 67 generic methods without the 67 CMS-domain ones), and there is no way for a host to
|
|
10
|
+
* add a route group without editing the file.
|
|
11
|
+
*
|
|
12
|
+
* Inverting it — `request` becomes an injected `AdminTransport`, and each route group becomes a
|
|
13
|
+
* factory over it — makes both work:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* const transport = createHttpTransport({ baseUrl: '/api/admin/v1' });
|
|
17
|
+
*
|
|
18
|
+
* const client = createAdminClient(transport, {
|
|
19
|
+
* identity: createIdentityRoutes, // shipped by @jini-ai/admin
|
|
20
|
+
* media: createMediaRoutes, // shipped by @jini-ai/admin
|
|
21
|
+
* posts: createHostPostRoutes, // the host's own, same signature
|
|
22
|
+
* widgets: createHostWidgetRoutes, // the host's own
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* await client.identity.listUsers(); // Jini's
|
|
26
|
+
* await client.posts.list(); // the host's
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* Both halves share one auth policy, one error class, one place to add retries or tracing. A host
|
|
30
|
+
* adding a route is a factory plus one line in the map — it never edits a Jini file, and it never
|
|
31
|
+
* grows a second `fetch` wrapper with its own subtly different error handling, which is exactly
|
|
32
|
+
* how `api.ts` reached 1,548 lines.
|
|
33
|
+
*
|
|
34
|
+
* ## Why `AdminTransport` is an interface and not just a function
|
|
35
|
+
*
|
|
36
|
+
* A bare `request<T>(path, init)` function would have been enough for HTTP. It is an interface so
|
|
37
|
+
* a host can supply a non-HTTP implementation without any route group knowing: a fleet orchestrator
|
|
38
|
+
* drives many site instances in-process and can hand over a direct-dispatch transport that never opens a
|
|
39
|
+
* socket, and tests can supply a fake that returns fixtures. Both are the same seam, which is the
|
|
40
|
+
* point.
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* How an admin route group talks to its backend.
|
|
44
|
+
*
|
|
45
|
+
* Implementations must throw `AdminApiError` on a non-success response so panels can rely on one
|
|
46
|
+
* error shape regardless of which transport is wired. A transport that throws its own error type
|
|
47
|
+
* silently breaks every panel's `instanceof` check.
|
|
48
|
+
*/
|
|
49
|
+
export interface AdminTransport {
|
|
50
|
+
/**
|
|
51
|
+
* @param path Route-group-relative path, always starting with `/` (`/users`, `/media/abc`).
|
|
52
|
+
* The base is the transport's concern — a route group that embeds a base is not portable to
|
|
53
|
+
* a host that mounts the admin API somewhere else.
|
|
54
|
+
* @param init Standard `RequestInit`. A transport that is not HTTP-backed may ignore fields it
|
|
55
|
+
* cannot honour, but must respect `method` and `body`.
|
|
56
|
+
* @throws {AdminApiError} on any non-success response.
|
|
57
|
+
*/
|
|
58
|
+
request<T>(path: string, init?: RequestInit): Promise<T>;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A route group: turns a transport into a typed set of operations.
|
|
62
|
+
*
|
|
63
|
+
* Every port implementation in this package is one of these, and a host's own route groups use
|
|
64
|
+
* the identical signature — that symmetry is what lets Jini-shipped and host-owned groups sit in
|
|
65
|
+
* the same client with no adapter between them.
|
|
66
|
+
*/
|
|
67
|
+
export type AdminRouteGroupFactory<TPort> = (transport: AdminTransport) => TPort;
|
|
68
|
+
/**
|
|
69
|
+
* The assembled client: one object carrying every wired route group.
|
|
70
|
+
*
|
|
71
|
+
* `TGroups` is inferred from the factory map handed to `createAdminClient`, so a host's own
|
|
72
|
+
* groups are as type-safe as the shipped ones with no declaration merging or module augmentation.
|
|
73
|
+
*/
|
|
74
|
+
export type AdminClient<TGroups extends Record<string, AdminRouteGroupFactory<unknown>>> = {
|
|
75
|
+
readonly [K in keyof TGroups]: ReturnType<TGroups[K]>;
|
|
76
|
+
} & {
|
|
77
|
+
/** The transport every group was built over — exposed so a host can make one-off calls to a
|
|
78
|
+
* route it has not (or will never) wrap in a group, without reaching for a bare `fetch`. */
|
|
79
|
+
readonly transport: AdminTransport;
|
|
80
|
+
};
|
|
81
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/transport/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;;OAOG;IACH,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAC1D;AAED;;;;;;GAMG;AACH,MAAM,MAAM,sBAAsB,CAAC,KAAK,IAAI,CAAC,SAAS,EAAE,cAAc,KAAK,KAAK,CAAC;AAEjF;;;;;GAKG;AACH,MAAM,MAAM,WAAW,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,OAAO,CAAC,CAAC,IAAI;IACzF,QAAQ,EAAE,CAAC,IAAI,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;CACtD,GAAG;IACF;iGAC6F;IAC7F,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;CACpC,CAAC"}
|