@morscherlab/mint-sdk 1.0.41 → 1.0.42

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.
@@ -52,4 +52,4 @@ export { getBioTemplateComponentProps, getBioTemplateComponentBindings, toBioTem
52
52
  export { useBioTemplateWorkspace, type BioTemplateRendererBinding, type BioTemplateWorkspaceBindings, type BioTemplateWorkspaceTarget, type UseBioTemplateWorkspaceReturn, } from './useBioTemplateWorkspace';
53
53
  export { useBioTemplatePresetWorkspace, type BioTemplatePresetWorkspaceBindings, type UseBioTemplatePresetWorkspaceOptions, type UseBioTemplatePresetWorkspaceReturn, } from './useBioTemplatePresetWorkspace';
54
54
  export { useBioTemplatePackWorkspace, type UseBioTemplatePackWorkspaceOptions, type UseBioTemplatePackWorkspaceReturn, } from './useBioTemplatePackWorkspace';
55
- export { buildPluginEndpointUrl, createPluginClient, downloadBlob, downloadPluginEndpoint, getPluginPageSelectorItems, pluginFormDataFromPayload, resolvePluginBaseUrl, uploadPluginEndpoint, usePluginClient, usePluginSettings, useCurrentExperiment, type BuildPluginEndpointUrlOptions, type DownloadBlobOptions, type DownloadPluginEndpointOptions, type PluginContract, type PluginEndpointContract, type PluginEndpointDefinition, type PluginHttpMethod, type PluginNavItemContract, type PluginEndpointRequestOptions, type PluginFormDataPayload, type PluginFormDataValue, type CreatePluginClientOptions, type UseCurrentExperimentOptions, type UseCurrentExperimentReturn, } from './usePluginClient';
55
+ export { buildPluginEndpointUrl, createPluginClient, downloadBlob, downloadPluginEndpoint, getPluginPageSelectorItems, pluginFormDataFromPayload, resolvePluginBaseUrl, uploadPluginEndpoint, usePluginClient, usePluginEventStream, usePluginSettings, useCurrentExperiment, type BuildPluginEndpointUrlOptions, type DownloadBlobOptions, type DownloadPluginEndpointOptions, type PluginContract, type PluginEndpointContract, type PluginEndpointDefinition, type PluginHttpMethod, type PluginNavItemContract, type PluginEndpointRequestOptions, type PluginEventStreamMessage, type PluginEventStreamOptions, type PluginFormDataPayload, type PluginFormDataValue, type CreatePluginClientOptions, type UseCurrentExperimentOptions, type UseCurrentExperimentReturn, type UsePluginEventStreamReturn, type UsePluginSettingsOptions, type UsePluginSettingsReturn, } from './usePluginClient';
@@ -6,5 +6,5 @@ import { i as usePlatformContext, n as evaluateCondition, r as useForm, t as use
6
6
  import { a as SORT_OPTIONS, c as formatExperimentStatus, i as EXPERIMENT_STATUS_VARIANT_MAP, l as getExperimentStatusVariant, n as EXPERIMENT_STATUS_LABELS, o as datePresetToISO, r as EXPERIMENT_STATUS_OPTIONS, s as formatExperimentDate, t as DATE_PRESET_OPTIONS, u as resolveExperimentCode } from "../experiment-utils-Bfa7CwPU.js";
7
7
  import { i as useApi, n as useDebouncedWatch, r as useRequestSyncState, t as useExperimentSelector } from "../useExperimentSelector-DdCy5VNv.js";
8
8
  import { B as toBioTemplateComponentProps, H as toBioTemplateComponentPropsById, L as toBioTemplateComponentBindings, P as getBioTemplateComponentBindings, R as toBioTemplateComponentBindingsById, l as requireBioTemplateControlSchema, o as getBioTemplateControlSchema } from "../templates-CNbPQNID.js";
9
- import { A as useCommandHistory, C as parseDelimitedText, D as createPluginResourceClient, E as validateImportFile, F as useAuth, M as useAsync, N as useAsyncBatch, O as useResourceCrud, P as usePasskey, S as detectDelimiter, T as useFileImport, _ as useWellPlateAdapter, a as pluginFormDataFromPayload, b as wellIdToCoordinate, c as usePluginClient, d as resolvePluginBaseUrl, f as runWellPlateValidation, g as createWellPlateWells, h as createRowConditions, i as getPluginPageSelectorItems, j as usePluginConfig, k as useOptimisticMutation, l as usePluginSettings, m as createColumnConditions, n as downloadBlob, o as uploadPluginEndpoint, p as useWellPlateValidation, r as downloadPluginEndpoint, s as useCurrentExperiment, t as createPluginClient, u as buildPluginEndpointUrl, v as coordinateToWellId, w as readFileAsText, x as wellIdsInRectangle, y as useWellPainting } from "../composables-DrE6OcZZ.js";
10
- export { APP_EXPERIMENT_KEY, ATOMIC_WEIGHTS, DATE_PRESET_OPTIONS, DEFAULT_COLORS, DEFAULT_MOBILE_VIEWPORT_QUERY, DEFAULT_PRESETS, DEFAULT_UNITS, EXPERIMENT_STATUS_LABELS, EXPERIMENT_STATUS_OPTIONS, EXPERIMENT_STATUS_VARIANT_MAP, SORT_OPTIONS, addMinutes, buildPluginEndpointUrl, candidateMatchesSearch, compareSortValues, compareTime, controlValuesToComponentBindings, controlValuesToComponentBindingsById, controlValuesToComponentProps, controlsToFormSchema, controlsToSectionFormSchema, controlsToSectionFormSchemas, controlsToSettingsSchema, controlsToSidebarPanels, controlsToTopBarSettingsConfig, controlsToViewIds, controlsToViewItems, coordinateToWellId, createColumnConditions, createPluginClient, createPluginResourceClient, createRowConditions, createWellPlateWells, datePresetToISO, defineControlComponentBindings, defineControlModel, defineControls, defineDoseCalculatorControlProps, defineDoseDesignControlModel, defineWellPlateControlProps, defineWellPlateDoseComponentBindings, defineWellPlateDoseControlProps, detectDelimiter, downloadBlob, downloadPluginEndpoint, durationMinutes, evaluateCondition, extractSampleNamesFromDesignData, extractSampleOptionsFromDesignData, extractSamplesFromDesignData, findAvailableSlots, findNearestTimeSlotIndex, formatDuration, formatExperimentDate, formatExperimentStatus, formatTime, formatTimeSlot, fromMinutes, generateDilutionSeries, generateTimeSlots, getBioTemplateComponentBindings, getBioTemplateComponentProps, getBioTemplateControlSchema, getControlDefaults, getDefaultControlView, getExperimentStatusVariant, getFieldRegistryEntry, getPluginPageSelectorItems, getTypeDefault, isTimeInRange, normalizeSearchQuery, parseDelimitedText, parseTime, pluginFormDataFromPayload, rangesOverlap, readFileAsText, requireBioTemplateControlSchema, resolveExperimentCode, resolvePluginBaseUrl, runWellPlateValidation, snapToSlot, toBioTemplateComponentBindings, toBioTemplateComponentBindingsById, toBioTemplateComponentProps, toBioTemplateComponentPropsByComponent, toBioTemplateComponentPropsById, toMinutes, unwrapExperimentDesignData, uploadPluginEndpoint, useApi, useAppExperiment, useAsync, useAsyncBatch, useAuth, useAutoGroup, useBioTemplateComponents, useBioTemplateControls, useBioTemplatePackWorkspace, useBioTemplatePresetWorkspace, useBioTemplateWorkspace, useChemicalFormula, useCommandHistory, useConcentrationUnits, useControlSchema, useControlWorkspace, useCurrentExperiment, useDebouncedWatch, useDoseCalculator, useEventListener, useExpansionSet, useExperimentData, useExperimentSamples, useExperimentSave, useExperimentSelector, useFileImport, useForm, useFormBuilder, useGroupAssignment, useListSelection, useMobileSupportGate, useOptimisticMutation, usePasskey, usePlatformContext, usePluginClient, usePluginConfig, usePluginSettings, useProtocolTemplates, useRackEditor, useReagentSeries, useRequestSyncState, useResourceCrud, useSampleGroups, useScheduleDrag, useSelectionLimit, useSequenceUtils, useSortedItems, useTemplateCollection, useTextSearch, useTheme, useTimeUtils, useToast, useWellPainting, useWellPlateAdapter, useWellPlateEditor, useWellPlateValidation, validateImportFile, wellIdToCoordinate, wellIdsInRectangle };
9
+ import { A as useOptimisticMutation, C as detectDelimiter, D as validateImportFile, E as useFileImport, F as usePasskey, I as useAuth, M as usePluginConfig, N as useAsync, O as createPluginResourceClient, P as useAsyncBatch, S as wellIdsInRectangle, T as readFileAsText, _ as createWellPlateWells, a as pluginFormDataFromPayload, b as useWellPainting, c as usePluginClient, d as buildPluginEndpointUrl, f as resolvePluginBaseUrl, g as createRowConditions, h as createColumnConditions, i as getPluginPageSelectorItems, j as useCommandHistory, k as useResourceCrud, l as usePluginEventStream, m as useWellPlateValidation, n as downloadBlob, o as uploadPluginEndpoint, p as runWellPlateValidation, r as downloadPluginEndpoint, s as useCurrentExperiment, t as createPluginClient, u as usePluginSettings, v as useWellPlateAdapter, w as parseDelimitedText, x as wellIdToCoordinate, y as coordinateToWellId } from "../composables-C_hPF0Gn.js";
10
+ export { APP_EXPERIMENT_KEY, ATOMIC_WEIGHTS, DATE_PRESET_OPTIONS, DEFAULT_COLORS, DEFAULT_MOBILE_VIEWPORT_QUERY, DEFAULT_PRESETS, DEFAULT_UNITS, EXPERIMENT_STATUS_LABELS, EXPERIMENT_STATUS_OPTIONS, EXPERIMENT_STATUS_VARIANT_MAP, SORT_OPTIONS, addMinutes, buildPluginEndpointUrl, candidateMatchesSearch, compareSortValues, compareTime, controlValuesToComponentBindings, controlValuesToComponentBindingsById, controlValuesToComponentProps, controlsToFormSchema, controlsToSectionFormSchema, controlsToSectionFormSchemas, controlsToSettingsSchema, controlsToSidebarPanels, controlsToTopBarSettingsConfig, controlsToViewIds, controlsToViewItems, coordinateToWellId, createColumnConditions, createPluginClient, createPluginResourceClient, createRowConditions, createWellPlateWells, datePresetToISO, defineControlComponentBindings, defineControlModel, defineControls, defineDoseCalculatorControlProps, defineDoseDesignControlModel, defineWellPlateControlProps, defineWellPlateDoseComponentBindings, defineWellPlateDoseControlProps, detectDelimiter, downloadBlob, downloadPluginEndpoint, durationMinutes, evaluateCondition, extractSampleNamesFromDesignData, extractSampleOptionsFromDesignData, extractSamplesFromDesignData, findAvailableSlots, findNearestTimeSlotIndex, formatDuration, formatExperimentDate, formatExperimentStatus, formatTime, formatTimeSlot, fromMinutes, generateDilutionSeries, generateTimeSlots, getBioTemplateComponentBindings, getBioTemplateComponentProps, getBioTemplateControlSchema, getControlDefaults, getDefaultControlView, getExperimentStatusVariant, getFieldRegistryEntry, getPluginPageSelectorItems, getTypeDefault, isTimeInRange, normalizeSearchQuery, parseDelimitedText, parseTime, pluginFormDataFromPayload, rangesOverlap, readFileAsText, requireBioTemplateControlSchema, resolveExperimentCode, resolvePluginBaseUrl, runWellPlateValidation, snapToSlot, toBioTemplateComponentBindings, toBioTemplateComponentBindingsById, toBioTemplateComponentProps, toBioTemplateComponentPropsByComponent, toBioTemplateComponentPropsById, toMinutes, unwrapExperimentDesignData, uploadPluginEndpoint, useApi, useAppExperiment, useAsync, useAsyncBatch, useAuth, useAutoGroup, useBioTemplateComponents, useBioTemplateControls, useBioTemplatePackWorkspace, useBioTemplatePresetWorkspace, useBioTemplateWorkspace, useChemicalFormula, useCommandHistory, useConcentrationUnits, useControlSchema, useControlWorkspace, useCurrentExperiment, useDebouncedWatch, useDoseCalculator, useEventListener, useExpansionSet, useExperimentData, useExperimentSamples, useExperimentSave, useExperimentSelector, useFileImport, useForm, useFormBuilder, useGroupAssignment, useListSelection, useMobileSupportGate, useOptimisticMutation, usePasskey, usePlatformContext, usePluginClient, usePluginConfig, usePluginEventStream, usePluginSettings, useProtocolTemplates, useRackEditor, useReagentSeries, useRequestSyncState, useResourceCrud, useSampleGroups, useScheduleDrag, useSelectionLimit, useSequenceUtils, useSortedItems, useTemplateCollection, useTextSearch, useTheme, useTimeUtils, useToast, useWellPainting, useWellPlateAdapter, useWellPlateEditor, useWellPlateValidation, validateImportFile, wellIdToCoordinate, wellIdsInRectangle };
@@ -1,5 +1,5 @@
1
1
  import { ComputedRef, Ref } from 'vue';
2
- import { ExperimentSummary, PageSelectorItem } from '../types';
2
+ import { ExperimentSummary, PageSelectorItem, SettingsModalSchema, TopBarSettingsConfig } from '../types';
3
3
  export { buildPluginEndpointUrl, resolvePluginBaseUrl, } from './pluginEndpointBuilder';
4
4
  export type PluginHttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';
5
5
  export interface PluginEndpointContract {
@@ -44,6 +44,10 @@ export interface PluginContract {
44
44
  analysisResultReaders?: string[];
45
45
  capabilities?: Record<string, unknown>;
46
46
  };
47
+ settings?: {
48
+ modelName?: string | null;
49
+ schema?: SettingsModalSchema | null;
50
+ };
47
51
  endpoints: PluginEndpointContract[];
48
52
  hash: string;
49
53
  }
@@ -84,6 +88,78 @@ export interface DownloadBlobOptions {
84
88
  /** Delay before revoking the object URL after a browser download is triggered. */
85
89
  revokeObjectUrlDelayMs?: number;
86
90
  }
91
+ export type PluginSettingsValues<TSettings> = Partial<TSettings> & Record<string, unknown>;
92
+ export interface UsePluginSettingsOptions {
93
+ /** Plugin name for platform persistence. Defaults to the integrated platform plugin name. */
94
+ pluginName?: string;
95
+ /** Plugin API base URL for standalone/local settings routes. Generated clients pass pluginApiPrefix. */
96
+ apiBaseUrl?: string;
97
+ /** Platform API base URL for /plugins/{name}/config. Defaults to the injected platform API URL. */
98
+ platformApiBaseUrl?: string;
99
+ /** SettingsModal schema generated from the backend settings_model. */
100
+ schema?: SettingsModalSchema | null;
101
+ /** SettingsModal title when using settingsConfig. */
102
+ title?: string;
103
+ /** Whether SettingsModal should include SDK appearance controls. */
104
+ showAppearance?: boolean;
105
+ /** Load persisted values on component mount. */
106
+ loadOnMount?: boolean;
107
+ }
108
+ export interface UsePluginSettingsReturn<TSettings = Record<string, unknown>> {
109
+ /** Current typed settings values. */
110
+ settings: ComputedRef<PluginSettingsValues<TSettings>>;
111
+ /** Editable settings values for SettingsModal. */
112
+ values: Ref<PluginSettingsValues<TSettings>>;
113
+ /** Alias for values, kept for callers that think in platform config terms. */
114
+ config: Ref<PluginSettingsValues<TSettings>>;
115
+ /** Ready-to-pass AppTopBar/PluginWorkspaceView settingsConfig. */
116
+ settingsConfig: ComputedRef<TopBarSettingsConfig>;
117
+ isLoading: Ref<boolean>;
118
+ isSaving: Ref<boolean>;
119
+ error: Ref<string | null>;
120
+ lastLoadedAt: Ref<Date | null>;
121
+ lastSavedAt: Ref<Date | null>;
122
+ isDirty: ComputedRef<boolean>;
123
+ setValues: (values: Record<string, unknown>) => void;
124
+ load: () => Promise<void>;
125
+ save: (values?: Record<string, unknown>) => Promise<boolean>;
126
+ reset: () => void;
127
+ }
128
+ export interface PluginEventStreamMessage<TData = string> {
129
+ event: string;
130
+ data: TData;
131
+ id?: string;
132
+ retry?: number;
133
+ raw: string;
134
+ }
135
+ export interface PluginEventStreamOptions<TData = string> {
136
+ /** Override the generated or platform-injected API base URL. */
137
+ baseUrl?: string;
138
+ /** Start the stream immediately. Defaults to true. */
139
+ immediate?: boolean;
140
+ /** Reconnect after errors or server closes. Defaults to true. */
141
+ reconnect?: boolean;
142
+ /** Delay before reconnecting after a stream failure or close. Defaults to 2000ms. */
143
+ reconnectDelayMs?: number;
144
+ /** Parse `data:` as JSON before calling onMessage. */
145
+ parseJson?: boolean;
146
+ /** Fetch credentials mode. Defaults to same-origin. */
147
+ credentials?: RequestCredentials;
148
+ /** Extra request headers. Authorization is injected unless already supplied. */
149
+ headers?: Record<string, string>;
150
+ onOpen?: () => void;
151
+ onMessage?: (message: PluginEventStreamMessage<TData>) => void;
152
+ onError?: (error: unknown) => void;
153
+ }
154
+ export interface UsePluginEventStreamReturn<TData = string> {
155
+ isConnected: Ref<boolean>;
156
+ isConnecting: Ref<boolean>;
157
+ error: Ref<string | null>;
158
+ lastMessage: Ref<PluginEventStreamMessage<TData> | null>;
159
+ lastEventId: Ref<string | null>;
160
+ start: () => void;
161
+ stop: () => void;
162
+ }
87
163
  export type PluginFormDataValue = string | number | boolean | Blob | null | undefined | Record<string, unknown>;
88
164
  export type PluginFormDataPayload = Record<string, PluginFormDataValue | PluginFormDataValue[]>;
89
165
  export interface UseCurrentExperimentOptions {
@@ -126,9 +202,10 @@ export declare function uploadPluginEndpoint<TResponse = unknown>(contract: Plug
126
202
  export declare function downloadPluginEndpoint(contract: PluginContract, endpoint: PluginEndpointDefinition, payload?: unknown, options?: DownloadPluginEndpointOptions): Promise<Blob>;
127
203
  /** Return a generated plugin client from setup code with a stable typed identity. */
128
204
  export declare function usePluginClient<TClient>(client: TClient): TClient;
129
- /** Read plugin settings exposed through the platform context. */
130
- export declare function usePluginSettings<TSettings = Record<string, unknown>>(): {
131
- settings: ComputedRef<TSettings | undefined>;
132
- };
205
+ /** Load, edit, and persist plugin settings from platform config or the plugin-local settings router. */
206
+ export declare function usePluginSettings<TSettings = Record<string, unknown>>(options?: UsePluginSettingsOptions | string): UsePluginSettingsReturn<TSettings>;
207
+ /** Consume a generated plugin SSE endpoint with auth headers, reconnect, and teardown. */
208
+ export declare function usePluginEventStream<TData = string>(contract: PluginContract, endpoint: PluginEndpointDefinition, options?: PluginEventStreamOptions<TData>): UsePluginEventStreamReturn<TData>;
209
+ export declare function usePluginEventStream<TData = string>(contract: PluginContract, endpoint: PluginEndpointDefinition, payload?: unknown, options?: PluginEventStreamOptions<TData>): UsePluginEventStreamReturn<TData>;
133
210
  /** Read and optionally load the current platform experiment for integrated plugin views. */
134
211
  export declare function useCurrentExperiment<TExperiment = ExperimentSummary>(options?: UseCurrentExperimentOptions): UseCurrentExperimentReturn<TExperiment>;
@@ -1545,8 +1545,7 @@ function getPluginPageSelectorItems(contract) {
1545
1545
  id: item.id || pluginPageIdFromPath(item.path, index),
1546
1546
  label: item.label,
1547
1547
  to: normalizePluginNavPath(item.path),
1548
- icon: item.icon || pluginIcon || void 0,
1549
- hint: item.description || contract.plugin.name
1548
+ icon: item.icon || pluginIcon || void 0
1550
1549
  }));
1551
1550
  }
1552
1551
  /** Create a typed plugin API client from a generated MINT plugin contract. */
@@ -1605,10 +1604,258 @@ async function downloadPluginEndpoint(contract, endpoint, payload, options = {})
1605
1604
  function usePluginClient(client) {
1606
1605
  return client;
1607
1606
  }
1608
- /** Read plugin settings exposed through the platform context. */
1609
- function usePluginSettings() {
1610
- const { plugin } = usePlatformContext();
1611
- return { settings: computed(() => plugin.value?.settings) };
1607
+ function normalizePluginSettingsOptions(options = {}) {
1608
+ return typeof options === "string" ? { pluginName: options } : options;
1609
+ }
1610
+ function cloneRecord(value) {
1611
+ return { ...value };
1612
+ }
1613
+ function responseSettingsPayload(response) {
1614
+ if (!response || typeof response !== "object") return {};
1615
+ const data = response;
1616
+ if (data.config && typeof data.config === "object") return cloneRecord(data.config);
1617
+ if (data.settings && typeof data.settings === "object") return cloneRecord(data.settings);
1618
+ return {};
1619
+ }
1620
+ /** Load, edit, and persist plugin settings from platform config or the plugin-local settings router. */
1621
+ function usePluginSettings(options = {}) {
1622
+ const resolvedOptions = normalizePluginSettingsOptions(options);
1623
+ const injectedContext = getInjectedPlatformContext();
1624
+ const { plugin, isIntegrated } = usePlatformContext();
1625
+ const platformApi = useApi({ baseUrl: resolvedOptions.platformApiBaseUrl ?? injectedContext?.platformApiUrl });
1626
+ const pluginApi = useApi({ baseUrl: resolvedOptions.apiBaseUrl ?? injectedContext?.plugin?.api_prefix });
1627
+ const values = shallowRef({});
1628
+ const savedValues = shallowRef({});
1629
+ const request = useRequestSyncState("Plugin settings request failed.");
1630
+ const isLoading = ref(false);
1631
+ const isSaving = ref(false);
1632
+ const resolvedPluginName = computed(() => resolvedOptions.pluginName ?? plugin.value?.name ?? injectedContext?.plugin?.name ?? "");
1633
+ const usesPlatformConfig = computed(() => Boolean((isIntegrated.value || injectedContext?.isIntegrated) && resolvedPluginName.value));
1634
+ const settings = computed(() => values.value);
1635
+ const isDirty = computed(() => JSON.stringify(values.value) !== JSON.stringify(savedValues.value));
1636
+ const settingsConfig = computed(() => ({
1637
+ title: resolvedOptions.title,
1638
+ schema: resolvedOptions.schema ?? void 0,
1639
+ showAppearance: resolvedOptions.showAppearance,
1640
+ values: values.value
1641
+ }));
1642
+ function setValues(nextValues) {
1643
+ values.value = cloneRecord(nextValues);
1644
+ }
1645
+ function applyLoaded(nextValues) {
1646
+ const cloned = cloneRecord(nextValues);
1647
+ values.value = cloned;
1648
+ savedValues.value = cloneRecord(cloned);
1649
+ }
1650
+ async function load() {
1651
+ isLoading.value = true;
1652
+ try {
1653
+ applyLoaded(responseSettingsPayload(usesPlatformConfig.value ? await request.run(() => platformApi.get(`/plugins/${encodeURIComponent(resolvedPluginName.value)}/config`), {
1654
+ success: "load",
1655
+ errorMessage: "Failed to load plugin settings"
1656
+ }) : await request.run(() => pluginApi.get("/settings"), {
1657
+ success: "load",
1658
+ errorMessage: "Failed to load plugin settings"
1659
+ })));
1660
+ } catch {} finally {
1661
+ isLoading.value = false;
1662
+ }
1663
+ }
1664
+ async function save(nextValues) {
1665
+ if (nextValues) setValues(nextValues);
1666
+ isSaving.value = true;
1667
+ try {
1668
+ const payload = cloneRecord(values.value);
1669
+ applyLoaded(responseSettingsPayload(usesPlatformConfig.value ? await request.run(() => platformApi.patch(`/plugins/${encodeURIComponent(resolvedPluginName.value)}/config`, { config: payload }), {
1670
+ success: "save",
1671
+ errorMessage: "Failed to save plugin settings"
1672
+ }) : await request.run(() => pluginApi.put("/settings", payload), {
1673
+ success: "save",
1674
+ errorMessage: "Failed to save plugin settings"
1675
+ })));
1676
+ return true;
1677
+ } catch {
1678
+ return false;
1679
+ } finally {
1680
+ isSaving.value = false;
1681
+ }
1682
+ }
1683
+ function reset() {
1684
+ values.value = cloneRecord(savedValues.value);
1685
+ request.clearError();
1686
+ }
1687
+ onMounted(() => {
1688
+ if (resolvedOptions.loadOnMount !== false) load();
1689
+ });
1690
+ return {
1691
+ settings,
1692
+ values,
1693
+ config: values,
1694
+ settingsConfig,
1695
+ isLoading,
1696
+ isSaving,
1697
+ error: request.error,
1698
+ lastLoadedAt: request.lastLoadedAt,
1699
+ lastSavedAt: request.lastSavedAt,
1700
+ isDirty,
1701
+ setValues,
1702
+ load,
1703
+ save,
1704
+ reset
1705
+ };
1706
+ }
1707
+ function streamHeaders(options) {
1708
+ const headers = new Headers(options.headers);
1709
+ const authStore = useAuthStore();
1710
+ if (!authStore.isInitialized) authStore.initialize();
1711
+ if (authStore.token && !headers.has("Authorization")) headers.set("Authorization", `Bearer ${authStore.token}`);
1712
+ headers.set("Accept", "text/event-stream");
1713
+ return headers;
1714
+ }
1715
+ function parseSseEvent(block, parseJson) {
1716
+ let event = "message";
1717
+ let id;
1718
+ let retry;
1719
+ const dataLines = [];
1720
+ for (const line of block.split(/\r?\n/)) {
1721
+ if (!line || line.startsWith(":")) continue;
1722
+ const separator = line.indexOf(":");
1723
+ const field = separator === -1 ? line : line.slice(0, separator);
1724
+ const value = separator === -1 ? "" : line.slice(separator + 1).replace(/^ /, "");
1725
+ if (field === "event") event = value || "message";
1726
+ if (field === "data") dataLines.push(value);
1727
+ if (field === "id") id = value;
1728
+ if (field === "retry") {
1729
+ const parsed = Number(value);
1730
+ if (Number.isFinite(parsed)) retry = parsed;
1731
+ }
1732
+ }
1733
+ if (!dataLines.length && !id && retry === void 0) return null;
1734
+ const data = dataLines.join("\n");
1735
+ return {
1736
+ event,
1737
+ data: parseJson && data ? JSON.parse(data) : data,
1738
+ id,
1739
+ retry,
1740
+ raw: block
1741
+ };
1742
+ }
1743
+ function looksLikeEventStreamOptions(value) {
1744
+ if (!value || typeof value !== "object") return false;
1745
+ const candidate = value;
1746
+ return [
1747
+ "baseUrl",
1748
+ "immediate",
1749
+ "reconnect",
1750
+ "reconnectDelayMs",
1751
+ "parseJson",
1752
+ "credentials",
1753
+ "headers",
1754
+ "onOpen",
1755
+ "onMessage",
1756
+ "onError"
1757
+ ].some((key) => key in candidate);
1758
+ }
1759
+ function usePluginEventStream(contract, endpoint, payloadOrOptions, maybeOptions) {
1760
+ const payload = maybeOptions === void 0 && looksLikeEventStreamOptions(payloadOrOptions) ? void 0 : payloadOrOptions;
1761
+ const options = maybeOptions === void 0 && looksLikeEventStreamOptions(payloadOrOptions) ? payloadOrOptions : maybeOptions ?? {};
1762
+ const isConnected = ref(false);
1763
+ const isConnecting = ref(false);
1764
+ const error = ref(null);
1765
+ const lastMessage = shallowRef(null);
1766
+ const lastEventId = ref(null);
1767
+ let controller = null;
1768
+ let reconnectTimer = null;
1769
+ let stopped = true;
1770
+ function clearReconnectTimer() {
1771
+ if (reconnectTimer !== null) {
1772
+ clearTimeout(reconnectTimer);
1773
+ reconnectTimer = null;
1774
+ }
1775
+ }
1776
+ function scheduleReconnect() {
1777
+ if (stopped || options.reconnect === false) return;
1778
+ clearReconnectTimer();
1779
+ reconnectTimer = setTimeout(() => {
1780
+ connect();
1781
+ }, options.reconnectDelayMs ?? 2e3);
1782
+ }
1783
+ async function connect() {
1784
+ if (endpoint.method !== "get") throw new Error(`[MINT SDK] Plugin event streams must use GET; got ${endpoint.method}`);
1785
+ if (typeof fetch !== "function") throw new Error("[MINT SDK] fetch is required for plugin event streams.");
1786
+ controller?.abort();
1787
+ controller = new AbortController();
1788
+ isConnecting.value = true;
1789
+ error.value = null;
1790
+ try {
1791
+ const url = buildPluginEndpointUrl(contract, endpoint, payload, { baseUrl: options.baseUrl });
1792
+ const response = await fetch(url, {
1793
+ method: "GET",
1794
+ headers: streamHeaders(options),
1795
+ credentials: options.credentials ?? "same-origin",
1796
+ signal: controller.signal
1797
+ });
1798
+ if (!response.ok) throw new Error(`Plugin event stream failed with HTTP ${response.status}`);
1799
+ if (!response.body) throw new Error("Plugin event stream response did not include a readable body.");
1800
+ options.onOpen?.();
1801
+ isConnected.value = true;
1802
+ const reader = response.body.getReader();
1803
+ const decoder = new TextDecoder();
1804
+ let buffer = "";
1805
+ for (;;) {
1806
+ const { value, done } = await reader.read();
1807
+ if (done) break;
1808
+ buffer += decoder.decode(value, { stream: true });
1809
+ let boundary = buffer.search(/\r?\n\r?\n/);
1810
+ while (boundary !== -1) {
1811
+ const block = buffer.slice(0, boundary);
1812
+ buffer = buffer.slice(buffer[boundary] === "\r" ? boundary + 4 : boundary + 2);
1813
+ const message = parseSseEvent(block, options.parseJson === true);
1814
+ if (message) {
1815
+ lastMessage.value = message;
1816
+ if (message.id !== void 0) lastEventId.value = message.id;
1817
+ if (message.retry !== void 0) options.reconnectDelayMs = message.retry;
1818
+ options.onMessage?.(message);
1819
+ }
1820
+ boundary = buffer.search(/\r?\n\r?\n/);
1821
+ }
1822
+ }
1823
+ } catch (err) {
1824
+ if (!stopped && !(err instanceof DOMException && err.name === "AbortError")) {
1825
+ error.value = err instanceof Error ? err.message : "Plugin event stream failed";
1826
+ options.onError?.(err);
1827
+ }
1828
+ } finally {
1829
+ isConnecting.value = false;
1830
+ isConnected.value = false;
1831
+ scheduleReconnect();
1832
+ }
1833
+ }
1834
+ function start() {
1835
+ stopped = false;
1836
+ clearReconnectTimer();
1837
+ connect();
1838
+ }
1839
+ function stop() {
1840
+ stopped = true;
1841
+ clearReconnectTimer();
1842
+ controller?.abort();
1843
+ controller = null;
1844
+ isConnecting.value = false;
1845
+ isConnected.value = false;
1846
+ }
1847
+ if (options.immediate !== false && typeof window === "undefined") onMounted(start);
1848
+ onUnmounted(stop);
1849
+ if (options.immediate !== false && typeof window !== "undefined") start();
1850
+ return {
1851
+ isConnected,
1852
+ isConnecting,
1853
+ error,
1854
+ lastMessage,
1855
+ lastEventId,
1856
+ start,
1857
+ stop
1858
+ };
1612
1859
  }
1613
1860
  /** Read and optionally load the current platform experiment for integrated plugin views. */
1614
1861
  function useCurrentExperiment(options = {}) {
@@ -1668,6 +1915,6 @@ function useCurrentExperiment(options = {}) {
1668
1915
  };
1669
1916
  }
1670
1917
  //#endregion
1671
- export { useCommandHistory as A, parseDelimitedText as C, createPluginResourceClient as D, validateImportFile as E, useAuth as F, useAsync as M, useAsyncBatch as N, useResourceCrud as O, usePasskey as P, detectDelimiter as S, useFileImport as T, useWellPlateAdapter as _, pluginFormDataFromPayload as a, wellIdToCoordinate as b, usePluginClient as c, resolvePluginBaseUrl as d, runWellPlateValidation as f, createWellPlateWells as g, createRowConditions as h, getPluginPageSelectorItems as i, usePluginConfig as j, useOptimisticMutation as k, usePluginSettings as l, createColumnConditions as m, downloadBlob as n, uploadPluginEndpoint as o, useWellPlateValidation as p, downloadPluginEndpoint as r, useCurrentExperiment as s, createPluginClient as t, buildPluginEndpointUrl as u, coordinateToWellId as v, readFileAsText as w, wellIdsInRectangle as x, useWellPainting as y };
1918
+ export { useOptimisticMutation as A, detectDelimiter as C, validateImportFile as D, useFileImport as E, usePasskey as F, useAuth as I, usePluginConfig as M, useAsync as N, createPluginResourceClient as O, useAsyncBatch as P, wellIdsInRectangle as S, readFileAsText as T, createWellPlateWells as _, pluginFormDataFromPayload as a, useWellPainting as b, usePluginClient as c, buildPluginEndpointUrl as d, resolvePluginBaseUrl as f, createRowConditions as g, createColumnConditions as h, getPluginPageSelectorItems as i, useCommandHistory as j, useResourceCrud as k, usePluginEventStream as l, useWellPlateValidation as m, downloadBlob as n, uploadPluginEndpoint as o, runWellPlateValidation as p, downloadPluginEndpoint as r, useCurrentExperiment as s, createPluginClient as t, usePluginSettings as u, useWellPlateAdapter as v, parseDelimitedText as w, wellIdToCoordinate as x, coordinateToWellId as y };
1672
1919
 
1673
- //# sourceMappingURL=composables-DrE6OcZZ.js.map
1920
+ //# sourceMappingURL=composables-C_hPF0Gn.js.map