@artemis-studio/plugin-sdk 2026.9.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/index.js +25 -0
- package/package.json +46 -0
- package/peers.json +8 -0
- package/shared.d.ts +3 -0
- package/shared.js +24 -0
- package/types/branding.d.ts +14 -0
- package/types/kernel/api/polling.d.ts +50 -0
- package/types/kernel/api/request.d.ts +45 -0
- package/types/kernel/api/schema.d.ts +9428 -0
- package/types/kernel/auth/Can.d.ts +9 -0
- package/types/kernel/auth/LoginView.d.ts +8 -0
- package/types/kernel/auth/api.d.ts +29 -0
- package/types/kernel/auth/useCan.d.ts +15 -0
- package/types/kernel/feature.d.ts +80 -0
- package/types/kernel/features.d.ts +5 -0
- package/types/kernel/manifest.d.ts +21 -0
- package/types/kernel/nav/groups.d.ts +22 -0
- package/types/kernel/plugins/PluginBoundary.d.ts +25 -0
- package/types/kernel/plugins/PluginUnavailable.d.ts +6 -0
- package/types/kernel/plugins/boot.d.ts +29 -0
- package/types/kernel/plugins/guarded.d.ts +3 -0
- package/types/kernel/plugins/usePluginsChanged.d.ts +6 -0
- package/types/kernel/plugins/validate.d.ts +19 -0
- package/types/kernel/registry.d.ts +12 -0
- package/types/kernel/routing/roots.d.ts +15 -0
- package/types/kernel/shell/AccountView.d.ts +9 -0
- package/types/kernel/shell/AdminView.d.ts +5 -0
- package/types/kernel/shell/ClusterLayout.d.ts +10 -0
- package/types/kernel/shell/ClusterViewNav.d.ts +12 -0
- package/types/kernel/shell/CommandPalette.d.ts +10 -0
- package/types/kernel/shell/FeatureDisabled.d.ts +13 -0
- package/types/kernel/shell/FeatureGate.d.ts +12 -0
- package/types/kernel/shell/FreshnessBar.d.ts +11 -0
- package/types/kernel/shell/HomeView.d.ts +5 -0
- package/types/kernel/shell/NavItem.d.ts +22 -0
- package/types/kernel/shell/NavToggle.d.ts +6 -0
- package/types/kernel/shell/RootLayout.d.ts +12 -0
- package/types/kernel/shell/UserMenu.d.ts +6 -0
- package/types/kernel/shell/useFreshness.d.ts +25 -0
- package/types/kernel/shell/useNavCollapsed.d.ts +10 -0
- package/types/kernel/slots.d.ts +130 -0
- package/types/kernel/stream/useClusterStream.d.ts +22 -0
- package/types/kernel/time/time.d.ts +89 -0
- package/types/kernel/time/timezone.d.ts +44 -0
- package/types/kernel/useDismissedNotice.d.ts +3 -0
- package/types/sdk/index.d.ts +44 -0
- package/types/ui/ConfirmByTyping.d.ts +15 -0
- package/types/ui/NodeOutcomeSummary.d.ts +69 -0
- package/types/ui/Pager.d.ts +19 -0
- package/types/ui/VirtualTable.d.ts +56 -0
- package/vite.d.ts +14 -0
- package/vite.js +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# @artemis-studio/plugin-sdk
|
|
2
|
+
|
|
3
|
+
Build the UI of an [Artemis Studio](https://github.com/sudoitir/artemis-studio) plugin.
|
|
4
|
+
|
|
5
|
+
- **Typings** for everything a plugin's UI may use from Studio: routes under Studio's own roots, slots,
|
|
6
|
+
navigation, the API client, permission checks and shared components. At runtime Studio provides
|
|
7
|
+
the SDK itself, so a plugin's bundle never contains a copy.
|
|
8
|
+
- **A Vite preset** that builds the UI as a Module Federation remote taking React, Mantine and
|
|
9
|
+
TanStack from Studio.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
// vite.config.ts
|
|
13
|
+
import { defineConfig } from 'vite';
|
|
14
|
+
import react from '@vitejs/plugin-react';
|
|
15
|
+
import { studioPlugin } from '@artemis-studio/plugin-sdk/vite';
|
|
16
|
+
|
|
17
|
+
export default defineConfig({ plugins: [react(), studioPlugin({ id: 'acme-notes' })] });
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
// src/feature.tsx
|
|
22
|
+
import { createRoute } from '@tanstack/react-router';
|
|
23
|
+
import { CONTRACT, clusterRoute, definePlugin, pluginPath, pluginView } from '@artemis-studio/plugin-sdk';
|
|
24
|
+
|
|
25
|
+
const notes = createRoute({
|
|
26
|
+
getParentRoute: () => clusterRoute,
|
|
27
|
+
path: pluginPath('acme-notes', 'notes'),
|
|
28
|
+
component: pluginView('acme-notes', NotesView),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export default definePlugin({ contract: CONTRACT, id: 'acme-notes', routes: { cluster: [notes] } });
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The SDK's version is the Studio version it was built from, and its peer dependencies are the exact
|
|
35
|
+
React, Mantine and TanStack versions that Studio ships. Build against the SDK of the oldest Studio
|
|
36
|
+
your plugin supports (`studio.since` in `plugin.json`).
|
|
37
|
+
|
|
38
|
+
The guide: <https://sudoitir.github.io/artemis-studio/guide/plugins>.
|
package/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Generated by build.mjs. Artemis Studio provides this module at runtime; these names exist so a
|
|
2
|
+
// plugin's build knows what it imports. Running a plugin outside Studio fails here, loudly.
|
|
3
|
+
const outsideStudio = (name) => () => {
|
|
4
|
+
throw new Error(`${name}: @artemis-studio/plugin-sdk is provided by Artemis Studio at runtime; load this plugin in Studio.`);
|
|
5
|
+
};
|
|
6
|
+
export const ApiError = outsideStudio('ApiError');
|
|
7
|
+
export const CONTRACT = outsideStudio('CONTRACT');
|
|
8
|
+
export const ConfirmByTyping = outsideStudio('ConfirmByTyping');
|
|
9
|
+
export const NAV_GROUPS = outsideStudio('NAV_GROUPS');
|
|
10
|
+
export const NodeOutcomeSummary = outsideStudio('NodeOutcomeSummary');
|
|
11
|
+
export const OutcomeSummary = outsideStudio('OutcomeSummary');
|
|
12
|
+
export const Pager = outsideStudio('Pager');
|
|
13
|
+
export const SETTINGS_GROUPS = outsideStudio('SETTINGS_GROUPS');
|
|
14
|
+
export const VirtualTable = outsideStudio('VirtualTable');
|
|
15
|
+
export const clusterKey = outsideStudio('clusterKey');
|
|
16
|
+
export const clusterRoute = outsideStudio('clusterRoute');
|
|
17
|
+
export const definePlugin = outsideStudio('definePlugin');
|
|
18
|
+
export const notify = outsideStudio('notify');
|
|
19
|
+
export const pluginApi = outsideStudio('pluginApi');
|
|
20
|
+
export const pluginPath = outsideStudio('pluginPath');
|
|
21
|
+
export const pluginView = outsideStudio('pluginView');
|
|
22
|
+
export const request = outsideStudio('request');
|
|
23
|
+
export const rootRoute = outsideStudio('rootRoute');
|
|
24
|
+
export const useCan = outsideStudio('useCan');
|
|
25
|
+
export const useMe = outsideStudio('useMe');
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@artemis-studio/plugin-sdk",
|
|
3
|
+
"version": "2026.9.37",
|
|
4
|
+
"description": "Build the UI of an Artemis Studio plugin: typings for the Studio APIs a plugin may use, and a Vite preset.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/sudoitir/artemis-studio.git",
|
|
9
|
+
"directory": "web/packages/plugin-sdk"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://sudoitir.github.io/artemis-studio/guide/plugins",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./types/sdk/index.d.ts",
|
|
16
|
+
"default": "./index.js"
|
|
17
|
+
},
|
|
18
|
+
"./vite": {
|
|
19
|
+
"types": "./vite.d.ts",
|
|
20
|
+
"default": "./vite.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"index.js",
|
|
25
|
+
"shared.js",
|
|
26
|
+
"shared.d.ts",
|
|
27
|
+
"vite.js",
|
|
28
|
+
"vite.d.ts",
|
|
29
|
+
"peers.json",
|
|
30
|
+
"types",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"sideEffects": false,
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"react": "19.3.0",
|
|
36
|
+
"react-dom": "19.3.0",
|
|
37
|
+
"@mantine/core": "9.6.1",
|
|
38
|
+
"@mantine/hooks": "9.6.1",
|
|
39
|
+
"@tanstack/react-query": "5.103.1",
|
|
40
|
+
"@tanstack/react-router": "1.170.38",
|
|
41
|
+
"vite": ">=8"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@module-federation/vite": "1.22.1"
|
|
45
|
+
}
|
|
46
|
+
}
|
package/peers.json
ADDED
package/shared.d.ts
ADDED
package/shared.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The libraries a plugin's UI takes from Studio instead of bundling (ADR-0100): the ones holding
|
|
3
|
+
* React context or module state, where a second copy breaks hooks, routing, theming or the query
|
|
4
|
+
* cache. Studio's own build (web/vite.config.ts) shares exactly these, so the list lives here, once.
|
|
5
|
+
*/
|
|
6
|
+
export const SHARED_LIBRARIES = [
|
|
7
|
+
'react',
|
|
8
|
+
'react/jsx-runtime',
|
|
9
|
+
'react-dom',
|
|
10
|
+
'react-dom/client',
|
|
11
|
+
'@mantine/core',
|
|
12
|
+
'@mantine/hooks',
|
|
13
|
+
'@tanstack/react-query',
|
|
14
|
+
'@tanstack/react-router',
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
/** The SDK itself: always the running Studio's copy. */
|
|
18
|
+
export const SDK = '@artemis-studio/plugin-sdk';
|
|
19
|
+
|
|
20
|
+
/** The package a shared specifier belongs to: `react-dom/client` → `react-dom`. */
|
|
21
|
+
export function packageOf(specifier) {
|
|
22
|
+
const parts = specifier.split('/');
|
|
23
|
+
return specifier.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
|
|
24
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of every user-visible product name on the web side.
|
|
3
|
+
* Mirror of `Branding.java`. See `docs/adr/0001-project-name-and-trademark-risk.md`.
|
|
4
|
+
*
|
|
5
|
+
* A rename is: this file, `Branding.java`, the Maven `artifactId`, the image
|
|
6
|
+
* coordinates, and `index.html` <title>. Nothing else.
|
|
7
|
+
*/
|
|
8
|
+
export declare const branding: {
|
|
9
|
+
readonly productName: "Artemis Studio";
|
|
10
|
+
readonly productShortName: "Studio";
|
|
11
|
+
readonly tagline: "Cluster-wide management and observability for Apache ActiveMQ Artemis";
|
|
12
|
+
readonly trademarkNotice: string;
|
|
13
|
+
readonly projectUrl: "https://github.com/sudoitir/artemis-studio";
|
|
14
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Query, QueryClient } from '@tanstack/react-query';
|
|
2
|
+
/**
|
|
3
|
+
* `refetchInterval` for a hook that should stop polling while paused.
|
|
4
|
+
*
|
|
5
|
+
* Returning `false` suspends the interval; TanStack Query calls this again on the
|
|
6
|
+
* next cycle, so resuming needs no remount.
|
|
7
|
+
*
|
|
8
|
+
* ponytail: the interval is re-resolved when the *current* timer fires, so one
|
|
9
|
+
* further poll can land up to `ms` after pausing. Closing that gap means
|
|
10
|
+
* cancelling in-flight fetches, which throws away work the operator did not ask
|
|
11
|
+
* to discard and can leave a screen mid-update. Accepted and recorded in
|
|
12
|
+
* ADR-0055; revisit only if an operator can actually observe it.
|
|
13
|
+
*/
|
|
14
|
+
export declare function poll(ms: number | false): () => number | false;
|
|
15
|
+
/**
|
|
16
|
+
* `refetchOnMount` for the QueryClient default, so pausing covers opening a view
|
|
17
|
+
* and not only the intervals.
|
|
18
|
+
*
|
|
19
|
+
* Pausing intervals but not mounts means an operator who pauses and then
|
|
20
|
+
* navigates has silently unpaused — every query the new screen observes is stale
|
|
21
|
+
* (the stream marks them so) and refetches on mount.
|
|
22
|
+
*
|
|
23
|
+
* A query that has never resolved is exempt: suspending its first fetch would hand
|
|
24
|
+
* the operator an empty screen. They paused a screen showing data to stop it
|
|
25
|
+
* moving, not to stop data existing.
|
|
26
|
+
*/
|
|
27
|
+
export declare function mountRefetch(): (query: Query) => boolean;
|
|
28
|
+
export declare function isPollingPaused(): boolean;
|
|
29
|
+
export declare function setPollingPaused(next: boolean): void;
|
|
30
|
+
/** Record that the server said something changed while refreshing was paused. */
|
|
31
|
+
export declare function markPendingChange(): void;
|
|
32
|
+
export declare function usePollingPaused(): boolean;
|
|
33
|
+
export declare function usePendingChange(): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Refetch everything the current screen is observing, and resolve when it is done.
|
|
36
|
+
*
|
|
37
|
+
* `refetchType: 'active'` is the point: it refetches what is on the display and
|
|
38
|
+
* leaves the rest of the cache alone — the same scope the freshness indicator
|
|
39
|
+
* reports on, so the button and the label can never disagree.
|
|
40
|
+
*
|
|
41
|
+
* `cancelRefetch: false` is the other point. TanStack's default aborts the
|
|
42
|
+
* in-flight fetch and starts another, which is right after a mutation (the
|
|
43
|
+
* in-flight response is known-stale) and wrong for a refresh control (the
|
|
44
|
+
* in-flight response is exactly what was asked for). With it, a second activation
|
|
45
|
+
* joins the first instead of restarting it.
|
|
46
|
+
*
|
|
47
|
+
* The returned promise is what lets the control show *the operator's* refresh
|
|
48
|
+
* rather than every background poll.
|
|
49
|
+
*/
|
|
50
|
+
export declare function refreshActiveQueries(qc: QueryClient): Promise<void>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport to the Artemis Studio API: `request<T>()` and the RFC 9457 `ApiError`.
|
|
3
|
+
*
|
|
4
|
+
* DTO types come from `schema.d.ts`, generated by `openapi-typescript` from the
|
|
5
|
+
* backend's committed OpenAPI snapshot (`web/openapi.json`) — ADR-0019. Each
|
|
6
|
+
* feature's `api.ts` names the DTOs it uses and holds its own hooks and query
|
|
7
|
+
* keys, every cluster-scoped key under {@link clusterKey}.
|
|
8
|
+
*/
|
|
9
|
+
export declare const BASE = "/api/v1";
|
|
10
|
+
/** A parsed `application/problem+json` body. */
|
|
11
|
+
export declare class ApiError extends Error {
|
|
12
|
+
readonly status: number;
|
|
13
|
+
readonly type: string;
|
|
14
|
+
readonly title: string;
|
|
15
|
+
readonly brokerErrorKind?: string;
|
|
16
|
+
readonly fieldErrors: {
|
|
17
|
+
field: string;
|
|
18
|
+
message: string;
|
|
19
|
+
}[];
|
|
20
|
+
/**
|
|
21
|
+
* The whole `problem+json` body. Handlers attach extra properties to a problem
|
|
22
|
+
* — the offending token on a syntax error, the estimate and ceiling on a
|
|
23
|
+
* refused query — and a message with the number left out is not something an
|
|
24
|
+
* operator can act on.
|
|
25
|
+
*/
|
|
26
|
+
readonly problem: Record<string, unknown>;
|
|
27
|
+
constructor(status: number, body: Record<string, unknown>);
|
|
28
|
+
}
|
|
29
|
+
export declare function request<T>(path: string, init?: RequestInit): Promise<T>;
|
|
30
|
+
/** The root every cluster-scoped query key starts from, so invalidating a cluster reaches all of it. */
|
|
31
|
+
export declare const clusterKey: (id: string, ...parts: unknown[]) => readonly ["clusters", string, ...unknown[]];
|
|
32
|
+
/** The path a cluster-wide command is issued under. */
|
|
33
|
+
export declare const lifecycleBase: (clusterId: string) => string;
|
|
34
|
+
/**
|
|
35
|
+
* A lifecycle command names the cluster, not a node, and comes back as a
|
|
36
|
+
* per-node outcome. `dryRun` previews without touching any broker; `override`
|
|
37
|
+
* clears the bulk cap on a destroy, which is the only kind the cap applies to.
|
|
38
|
+
* A defined `dryRun` is always sent, `false` included: a real run must never be
|
|
39
|
+
* left to the server's default.
|
|
40
|
+
*/
|
|
41
|
+
export interface LifecycleVars {
|
|
42
|
+
dryRun?: boolean;
|
|
43
|
+
override?: boolean;
|
|
44
|
+
}
|
|
45
|
+
export declare function lifecycleQuery(dryRun?: boolean, override?: boolean): string;
|