@morscherlab/mint-sdk 1.0.63 → 1.1.0-beta.10

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.
Files changed (181) hide show
  1. package/README.md +119 -6
  2. package/dist/EmptyState-DgBRoYAy.js +647 -0
  3. package/dist/EmptyState-DgBRoYAy.js.map +1 -0
  4. package/dist/{ExperimentPopover-BbPkIFsI.js → ExperimentPopover-BFw3QX8L.js} +10 -9
  5. package/dist/{ExperimentPopover-BbPkIFsI.js.map → ExperimentPopover-BFw3QX8L.js.map} +1 -1
  6. package/dist/{ExperimentSelectorModal-DZeSd-yr.js → ExperimentSelectorModal-BBWXC7KW.js} +12 -216
  7. package/dist/ExperimentSelectorModal-BBWXC7KW.js.map +1 -0
  8. package/dist/{SettingsModal-CJgLadsN.js → SettingsModal-Dpg-X1OM.js} +12 -9
  9. package/dist/{SettingsModal-CJgLadsN.js.map → SettingsModal-Dpg-X1OM.js.map} +1 -1
  10. package/dist/__tests__/components/BaseToggle.test.d.ts +1 -0
  11. package/dist/__tests__/components/GeneratedPathInput.test.d.ts +1 -0
  12. package/dist/__tests__/components/GeneratedPlotlyResult.test.d.ts +1 -0
  13. package/dist/__tests__/components/GeneratedPluginView.test.d.ts +1 -0
  14. package/dist/__tests__/components/GeneratedResultRenderer.test.d.ts +1 -0
  15. package/dist/__tests__/components/InstrumentAlertLog.test.d.ts +1 -0
  16. package/dist/__tests__/components/SectionCard.test.d.ts +1 -0
  17. package/dist/__tests__/composables/useGeneratedAnalysis.test.d.ts +1 -0
  18. package/dist/__tests__/generatedUi.test.d.ts +1 -0
  19. package/dist/__tests__/types/GeneratedJsonSchema.test.d.ts +1 -0
  20. package/dist/{analysisArtifactTypes-Cu40dzSG.js → analysisArtifactTypes-ejqR8MX3.js} +1 -1
  21. package/dist/{analysisArtifactTypes-Cu40dzSG.js.map → analysisArtifactTypes-ejqR8MX3.js.map} +1 -1
  22. package/dist/{auth-DZAwEeis.js → auth-B87xM--s.js} +1 -1
  23. package/dist/{auth-DZAwEeis.js.map → auth-B87xM--s.js.map} +1 -1
  24. package/dist/chunk-BoAXSpZd.js +33 -0
  25. package/dist/components/AppSidebar.vue.d.ts +1 -1
  26. package/dist/components/AutoGroupModal.vue.d.ts +2 -2
  27. package/dist/components/BaseSelect.vue.d.ts +1 -1
  28. package/dist/components/BaseToggle.vue.d.ts +2 -0
  29. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +2 -2
  30. package/dist/components/CollapsibleCard.vue.d.ts +1 -1
  31. package/dist/components/ComponentBindingRenderer.vue.d.ts +1 -1
  32. package/dist/components/ConfirmDialog.vue.d.ts +1 -1
  33. package/dist/components/ExperimentDataViewer.vue.d.ts +1 -1
  34. package/dist/components/ExperimentTimeline.vue.d.ts +1 -1
  35. package/dist/components/FileUploader.vue.d.ts +4 -2
  36. package/dist/components/FitPanel.vue.d.ts +2 -2
  37. package/dist/components/FormActions.vue.d.ts +1 -1
  38. package/dist/components/FormBuilder.vue.d.ts +1 -1
  39. package/dist/components/GeneratedPathInput.vue.d.ts +12 -0
  40. package/dist/components/GeneratedPlotlyResult.vue.d.ts +11 -0
  41. package/dist/components/GeneratedPluginView.vue.d.ts +10 -0
  42. package/dist/components/GeneratedResultRenderer.vue.d.ts +6 -0
  43. package/dist/components/JobsStatusTray.vue.d.ts +5 -1
  44. package/dist/components/LcmsSequenceTable.vue.d.ts +1 -1
  45. package/dist/components/PluginWorkspaceView.props.d.ts +1 -1
  46. package/dist/components/SampleLegend.vue.d.ts +1 -1
  47. package/dist/components/SampleSelector.vue.d.ts +2 -2
  48. package/dist/components/SectionCard.vue.d.ts +47 -0
  49. package/dist/components/SettingsModal.vue.d.ts +1 -1
  50. package/dist/components/SmartGroupFieldRecipe.vue.d.ts +2 -2
  51. package/dist/components/index.d.ts +5 -0
  52. package/dist/components/index.js +8 -9
  53. package/dist/components/internal/PlateMapEditorToolbarInternal.vue.d.ts +2 -2
  54. package/dist/{components-DdzHh3a_.js → components-Ci_BTx00.js} +2322 -2292
  55. package/dist/components-Ci_BTx00.js.map +1 -0
  56. package/dist/composables/index.d.ts +3 -2
  57. package/dist/composables/index.js +10 -11
  58. package/dist/composables/useGeneratedAnalysis.d.ts +62 -0
  59. package/dist/composables/useJobsStatusTray.d.ts +48 -4
  60. package/dist/composables/usePlatformContext.d.ts +3 -0
  61. package/dist/composables/usePluginClient.d.ts +17 -8
  62. package/dist/{composables-BUUphBqg.js → composables-C0VEJ73O.js} +154 -38
  63. package/dist/composables-C0VEJ73O.js.map +1 -0
  64. package/dist/experiment-utils-CxkruZ3G.js +392 -0
  65. package/dist/experiment-utils-CxkruZ3G.js.map +1 -0
  66. package/dist/generated/schema.d.ts +7 -0
  67. package/dist/generated-ui/main.d.ts +0 -0
  68. package/dist/index.d.ts +1 -0
  69. package/dist/index.js +15 -18
  70. package/dist/index.js.map +1 -1
  71. package/dist/install.js +7 -8
  72. package/dist/install.js.map +1 -1
  73. package/dist/plotly.min-BR53hja4.js +180634 -0
  74. package/dist/plotly.min-BR53hja4.js.map +1 -0
  75. package/dist/stores/auth.d.ts +2 -2
  76. package/dist/stores/index.js +1 -1
  77. package/dist/styles.css +8657 -8217
  78. package/dist/tailwind.preset.d.ts +5 -0
  79. package/dist/tailwind.preset.js +7 -2
  80. package/dist/tailwind.preset.js.map +1 -1
  81. package/dist/templates/index.js +4 -3
  82. package/dist/templates/packs.d.ts +0 -1
  83. package/dist/{templates-CNbPQNID.js → templates-Cdj93S9C.js} +2 -4
  84. package/dist/templates-Cdj93S9C.js.map +1 -0
  85. package/dist/types/components.d.ts +2 -0
  86. package/dist/types/form-builder.d.ts +1 -1
  87. package/dist/types/generated-ui.d.ts +183 -0
  88. package/dist/types/index.d.ts +2 -1
  89. package/dist/types/index.js +1 -1
  90. package/dist/types/instrument.d.ts +1 -0
  91. package/dist/types/jobs.d.ts +7 -1
  92. package/dist/types/platform.d.ts +2 -0
  93. package/dist/{useControlSchema-BZNdalmL.js → useControlSchema-B_qgU4rW.js} +1882 -172
  94. package/dist/useControlSchema-B_qgU4rW.js.map +1 -0
  95. package/dist/{useDropdownState-Bb-c_PeE.js → useDropdownState-gVaMXtwW.js} +2 -2
  96. package/dist/{useDropdownState-Bb-c_PeE.js.map → useDropdownState-gVaMXtwW.js.map} +1 -1
  97. package/dist/{useExperimentSelector-DRbj6vse.js → useExperimentSelector-BuQZRgMt.js} +4 -255
  98. package/dist/useExperimentSelector-BuQZRgMt.js.map +1 -0
  99. package/dist/{useFormBuilder-DKekvXRs.js → useFormBuilder-D1G5J5Z5.js} +2 -2
  100. package/dist/{useFormBuilder-DKekvXRs.js.map → useFormBuilder-D1G5J5Z5.js.map} +1 -1
  101. package/dist/{useAnalysisArtifacts-C3l0a0vu.js → useProtocolTemplates-DeaOmMaw.js} +307 -475
  102. package/dist/useProtocolTemplates-DeaOmMaw.js.map +1 -0
  103. package/package.json +7 -3
  104. package/src/__tests__/components/BaseSelect.test.ts +52 -0
  105. package/src/__tests__/components/BaseToggle.test.ts +40 -0
  106. package/src/__tests__/components/GeneratedPathInput.test.ts +132 -0
  107. package/src/__tests__/components/GeneratedPlotlyResult.test.ts +117 -0
  108. package/src/__tests__/components/GeneratedPluginView.test.ts +240 -0
  109. package/src/__tests__/components/GeneratedResultRenderer.test.ts +93 -0
  110. package/src/__tests__/components/InstrumentAlertLog.test.ts +59 -0
  111. package/src/__tests__/components/PluginWorkspaceView.test.ts +68 -14
  112. package/src/__tests__/components/SectionCard.test.ts +181 -0
  113. package/src/__tests__/composables/formBuilderRegistry.test.ts +5 -1
  114. package/src/__tests__/composables/useApi.test.ts +8 -0
  115. package/src/__tests__/composables/useGeneratedAnalysis.test.ts +1742 -0
  116. package/src/__tests__/composables/useJobsStatusTray.test.ts +179 -1
  117. package/src/__tests__/composables/usePlatformContext.test.ts +28 -0
  118. package/src/__tests__/composables/usePluginClient.test.ts +402 -21
  119. package/src/__tests__/composables/usePluginConfig.test.ts +323 -7
  120. package/src/__tests__/docs/extractDocsTemplates.test.ts +0 -1
  121. package/src/__tests__/generatedUi.test.ts +119 -0
  122. package/src/__tests__/templates/templates.test.ts +1 -1
  123. package/src/__tests__/types/GeneratedJsonSchema.test.ts +62 -0
  124. package/src/components/BaseSelect.vue +16 -2
  125. package/src/components/BaseToggle.vue +3 -1
  126. package/src/components/FileUploader.vue +49 -11
  127. package/src/components/GeneratedPathInput.vue +218 -0
  128. package/src/components/GeneratedPlotlyResult.vue +87 -0
  129. package/src/components/GeneratedPluginView.vue +279 -0
  130. package/src/components/GeneratedResultRenderer.vue +137 -0
  131. package/src/components/InstrumentAlertLog.vue +2 -2
  132. package/src/components/JobsStatusTray.vue +14 -8
  133. package/src/components/PluginWorkspaceView.props.ts +1 -1
  134. package/src/components/PluginWorkspaceView.vue +1 -5
  135. package/src/components/SectionCard.story.vue +163 -0
  136. package/src/components/SectionCard.vue +98 -0
  137. package/src/components/index.ts +5 -0
  138. package/src/composables/formBuilderRegistry.ts +2 -0
  139. package/src/composables/index.ts +19 -0
  140. package/src/composables/useApi.ts +1 -0
  141. package/src/composables/useGeneratedAnalysis.ts +1270 -0
  142. package/src/composables/useJobsStatusTray.ts +234 -45
  143. package/src/composables/usePluginClient.ts +164 -30
  144. package/src/composables/usePluginConfig.ts +156 -20
  145. package/src/generated/schema.ts +130 -0
  146. package/src/generated-ui/main.ts +21 -0
  147. package/src/index.ts +3 -0
  148. package/src/jobs.ts +5 -0
  149. package/src/plotly-dist.d.ts +3 -0
  150. package/src/styles/components/section-card.css +161 -0
  151. package/src/styles/index.css +1 -0
  152. package/src/styles/variables.css +25 -0
  153. package/src/tailwind.preset.ts +5 -0
  154. package/src/templates/packs.ts +0 -3
  155. package/src/types/components.ts +12 -0
  156. package/src/types/form-builder.ts +1 -0
  157. package/src/types/generated-ui.ts +189 -0
  158. package/src/types/index.ts +4 -0
  159. package/src/types/instrument.ts +1 -0
  160. package/src/types/jobs.ts +10 -1
  161. package/src/types/platform.ts +2 -0
  162. package/dist/BaseButton-Dgqrze41.js +0 -74
  163. package/dist/BaseButton-Dgqrze41.js.map +0 -1
  164. package/dist/BaseModal-B9UA8Y_I.js +0 -165
  165. package/dist/BaseModal-B9UA8Y_I.js.map +0 -1
  166. package/dist/BaseSelect-ekgr9fDo.js +0 -179
  167. package/dist/BaseSelect-ekgr9fDo.js.map +0 -1
  168. package/dist/ExperimentPopover-Ryusjrhv.js +0 -4
  169. package/dist/ExperimentSelectorModal-Cg4-LXBD.js +0 -5
  170. package/dist/ExperimentSelectorModal-DZeSd-yr.js.map +0 -1
  171. package/dist/SettingsModal-Bl_9w2Y3.js +0 -5
  172. package/dist/components-DdzHh3a_.js.map +0 -1
  173. package/dist/composables-BUUphBqg.js.map +0 -1
  174. package/dist/experiment-utils-Bfa7CwPU.js +0 -108
  175. package/dist/experiment-utils-Bfa7CwPU.js.map +0 -1
  176. package/dist/templates-CNbPQNID.js.map +0 -1
  177. package/dist/useAnalysisArtifacts-C3l0a0vu.js.map +0 -1
  178. package/dist/useControlSchema-BZNdalmL.js.map +0 -1
  179. package/dist/useEventListener-CfVkP9Xz.js +0 -57
  180. package/dist/useEventListener-CfVkP9Xz.js.map +0 -1
  181. package/dist/useExperimentSelector-DRbj6vse.js.map +0 -1
@@ -1,7 +1,7 @@
1
- import { A as resolveCurrentExperimentId, O as currentExperimentFromContext, k as getInjectedPlatformContext } from "./useAnalysisArtifacts-C3l0a0vu.js";
2
- import { r as useSettingsStore, t as useAuthStore } from "./auth-DZAwEeis.js";
3
- import { i as usePlatformContext } from "./useFormBuilder-DKekvXRs.js";
4
- import { a as authenticationRequiredError, i as AuthenticationRequiredError, o as useApi, r as useRequestSyncState } from "./useExperimentSelector-DRbj6vse.js";
1
+ import { a as authenticationRequiredError, i as AuthenticationRequiredError, o as useApi, r as useRequestSyncState } from "./EmptyState-DgBRoYAy.js";
2
+ import { H as getInjectedPlatformContext, V as currentExperimentFromContext, W as resolveCurrentExperimentId } from "./useControlSchema-B_qgU4rW.js";
3
+ import { r as useSettingsStore, t as useAuthStore } from "./auth-B87xM--s.js";
4
+ import { i as usePlatformContext } from "./useFormBuilder-D1G5J5Z5.js";
5
5
  import { computed, getCurrentInstance, onMounted, onUnmounted, ref, shallowRef, toValue, watch } from "vue";
6
6
  import axios from "axios";
7
7
  //#region src/composables/useAuth.ts
@@ -614,13 +614,39 @@ function useAsyncBatch(asyncFns) {
614
614
  }
615
615
  //#endregion
616
616
  //#region src/composables/usePluginConfig.ts
617
+ var PLATFORM_OWNED_SETTINGS_KEY$1 = "allowed_experiment_types";
618
+ function cloneConfig(config) {
619
+ return JSON.parse(JSON.stringify(config));
620
+ }
621
+ function hasOwnConfig(config, key) {
622
+ return Object.prototype.hasOwnProperty.call(config, key);
623
+ }
624
+ function changedTopLevelKeys(current, saved) {
625
+ return new Set([...new Set([...Object.keys(current), ...Object.keys(saved)])].filter((key) => JSON.stringify(current[key]) !== JSON.stringify(saved[key])));
626
+ }
627
+ function topLevelConfigPatch(current, keys) {
628
+ return Object.fromEntries([...keys].map((key) => [key, hasOwnConfig(current, key) ? current[key] : null]));
629
+ }
630
+ function platformOwnedConfigPatch(config) {
631
+ return { [PLATFORM_OWNED_SETTINGS_KEY$1]: hasOwnConfig(config, PLATFORM_OWNED_SETTINGS_KEY$1) ? config[PLATFORM_OWNED_SETTINGS_KEY$1] : null };
632
+ }
633
+ function responseConfigPayload(response) {
634
+ return response.config ?? response.settings ?? {};
635
+ }
617
636
  /** Loads, saves, and tracks dirty state for a plugin's persistent configuration via the platform API. */
618
637
  function usePluginConfig(pluginName) {
619
- const api = useApi();
638
+ const injectedContext = getInjectedPlatformContext();
620
639
  const { plugin } = usePlatformContext();
621
- const resolvedName = computed(() => pluginName ?? plugin.value?.name ?? "");
640
+ const platformApi = useApi();
641
+ const pluginApi = useApi({ baseUrl: injectedContext?.plugin?.api_prefix ?? plugin.value?.api_prefix });
642
+ const resolvedName = computed(() => pluginName ?? plugin.value?.name ?? injectedContext?.plugin?.name ?? "");
643
+ const usesPluginSettingsApi = computed(() => {
644
+ const currentPlugin = injectedContext?.plugin ?? plugin.value;
645
+ return currentPlugin?.settings_api === "plugin" && resolvedName.value === currentPlugin.name;
646
+ });
622
647
  const config = ref({});
623
648
  const savedConfig = ref({});
649
+ const revision = ref(null);
624
650
  const request = useRequestSyncState("Plugin config request failed.");
625
651
  const isLoading = ref(false);
626
652
  const isSaving = ref(false);
@@ -635,12 +661,17 @@ function usePluginConfig(pluginName) {
635
661
  if (!name) return;
636
662
  isLoading.value = true;
637
663
  try {
638
- const response = await request.run(() => api.get(`/plugins/${encodeURIComponent(name)}/config`), {
664
+ const response = usesPluginSettingsApi.value ? await request.run(() => pluginApi.get("/settings"), {
665
+ success: "load",
666
+ errorMessage: "Failed to load plugin config"
667
+ }) : await request.run(() => platformApi.get(`/plugins/${encodeURIComponent(name)}/config`), {
639
668
  success: "load",
640
669
  errorMessage: "Failed to load plugin config"
641
670
  });
642
- config.value = { ...response.config };
643
- savedConfig.value = { ...response.config };
671
+ const responseConfig = responseConfigPayload(response);
672
+ config.value = cloneConfig(responseConfig);
673
+ savedConfig.value = cloneConfig(responseConfig);
674
+ revision.value = response.revision ?? null;
644
675
  } catch {} finally {
645
676
  isLoading.value = false;
646
677
  }
@@ -650,12 +681,39 @@ function usePluginConfig(pluginName) {
650
681
  if (!name) return false;
651
682
  isSaving.value = true;
652
683
  try {
653
- const response = await request.run(() => api.patch(`/plugins/${encodeURIComponent(name)}/config`, { config: config.value }), {
654
- success: "save",
655
- errorMessage: "Failed to save plugin config"
656
- });
657
- config.value = { ...response.config };
658
- savedConfig.value = { ...response.config };
684
+ const payload = cloneConfig(config.value);
685
+ let response;
686
+ let nextRevision = revision.value;
687
+ if (usesPluginSettingsApi.value) {
688
+ response = await request.run(() => pluginApi.put("/settings", payload, revision.value ? { headers: { "If-Match": `"${revision.value}"` } } : void 0), {
689
+ success: "save",
690
+ errorMessage: "Failed to save plugin config"
691
+ });
692
+ nextRevision = response.revision ?? nextRevision;
693
+ } else {
694
+ const dirtyKeys = changedTopLevelKeys(payload, savedConfig.value);
695
+ const platformOwnedChanged = dirtyKeys.has(PLATFORM_OWNED_SETTINGS_KEY$1);
696
+ const typedKeys = [...dirtyKeys].filter((key) => key !== PLATFORM_OWNED_SETTINGS_KEY$1);
697
+ if (typedKeys.length) {
698
+ response = await request.run(() => platformApi.patch(`/plugins/${encodeURIComponent(name)}/config`, { config: topLevelConfigPatch(payload, typedKeys) }), {
699
+ success: "save",
700
+ errorMessage: "Failed to save plugin config"
701
+ });
702
+ nextRevision = response.revision ?? nextRevision;
703
+ }
704
+ if (platformOwnedChanged) {
705
+ response = await request.run(() => platformApi.patch(`/plugins/${encodeURIComponent(name)}/config`, { config: platformOwnedConfigPatch(payload) }), {
706
+ success: "save",
707
+ errorMessage: "Failed to save plugin config"
708
+ });
709
+ nextRevision = response.revision ?? nextRevision;
710
+ }
711
+ }
712
+ if (!response) return true;
713
+ const responseConfig = responseConfigPayload(response);
714
+ config.value = cloneConfig(responseConfig);
715
+ savedConfig.value = cloneConfig(responseConfig);
716
+ revision.value = response.revision ?? nextRevision;
659
717
  return true;
660
718
  } catch {
661
719
  return false;
@@ -664,7 +722,7 @@ function usePluginConfig(pluginName) {
664
722
  }
665
723
  }
666
724
  function reset() {
667
- config.value = { ...savedConfig.value };
725
+ config.value = cloneConfig(savedConfig.value);
668
726
  request.clearError();
669
727
  }
670
728
  onMounted(() => {
@@ -1472,7 +1530,7 @@ function pluginPageIdFromPath(path, fallbackIndex) {
1472
1530
  if (normalizedPath === "/") return "dashboard";
1473
1531
  return normalizedPath.replace(/^\/+/, "").replace(/\/+/g, "-") || `page-${fallbackIndex + 1}`;
1474
1532
  }
1475
- /** Convert PluginMetadata.nav_items into AppTopBar pageSelector items for topbar page switches and homepage PluginCards. */
1533
+ /** Convert resolved @mint_plugin nav_items into page selectors for the topbar and homepage PluginCards. */
1476
1534
  function getPluginPageSelectorItems(contract) {
1477
1535
  const pluginIcon = contract.plugin.icon ?? "";
1478
1536
  return (contract.plugin.navItems ?? []).map((item, index) => ({
@@ -1490,7 +1548,10 @@ function createPluginClient(contract, options) {
1490
1548
  const payload = hasPayload ? payloadOrOptions : void 0;
1491
1549
  const callOptions = hasPayload ? explicitOptions : payloadOrOptions ?? explicitOptions;
1492
1550
  const parts = pluginEndpointRequestParts(contract, endpoint, payload, options.baseUrl);
1493
- const api = useApi({ baseUrl: parts.baseUrl });
1551
+ const api = useApi({
1552
+ baseUrl: parts.baseUrl,
1553
+ withAuth: endpoint.requiresAuth !== false
1554
+ });
1494
1555
  const config = endpointRequestConfig(endpoint, parts.query, callOptions);
1495
1556
  if (endpoint.method === "get") return api.get(parts.path, config);
1496
1557
  if (endpoint.method === "delete") {
@@ -1518,7 +1579,10 @@ async function uploadPluginEndpoint(contract, endpoint, payload, options = {}) {
1518
1579
  "patch"
1519
1580
  ].includes(endpoint.method)) throw new Error(`[MINT SDK] Plugin upload endpoints must use POST, PUT, or PATCH; got ${endpoint.method}`);
1520
1581
  const parts = pluginEndpointRequestParts(contract, endpoint, payload, options.baseUrl);
1521
- const api = useApi({ baseUrl: parts.baseUrl });
1582
+ const api = useApi({
1583
+ baseUrl: parts.baseUrl,
1584
+ withAuth: endpoint.requiresAuth !== false
1585
+ });
1522
1586
  const body = endpoint.hasBody ? requestBodyFromPayload(payload, parts.requestPayload, endpoint) : payload;
1523
1587
  if (endpoint.requestContentType === OCTET_STREAM) {
1524
1588
  const config = endpointRequestConfig(endpoint, parts.query, { signal: options.signal });
@@ -1540,7 +1604,10 @@ async function uploadPluginEndpoint(contract, endpoint, payload, options = {}) {
1540
1604
  async function downloadPluginEndpoint(contract, endpoint, payload, options = {}) {
1541
1605
  if (endpoint.requestContentType === MULTIPART_FORM_DATA) throw new Error("[MINT SDK] Plugin download endpoints do not support multipart request bodies");
1542
1606
  const parts = pluginEndpointRequestParts(contract, endpoint, payload, options.baseUrl);
1543
- const api = useApi({ baseUrl: parts.baseUrl });
1607
+ const api = useApi({
1608
+ baseUrl: parts.baseUrl,
1609
+ withAuth: endpoint.requiresAuth !== false
1610
+ });
1544
1611
  const config = {
1545
1612
  ...endpointRequestConfig(endpoint, parts.query, { signal: options.signal }) ?? {},
1546
1613
  responseType: "blob"
@@ -1567,8 +1634,12 @@ function usePluginClient(client) {
1567
1634
  function normalizePluginSettingsOptions(options = {}) {
1568
1635
  return typeof options === "string" ? { pluginName: options } : options;
1569
1636
  }
1637
+ var PLATFORM_OWNED_SETTINGS_KEY = "allowed_experiment_types";
1570
1638
  function cloneRecord(value) {
1571
- return { ...value };
1639
+ return JSON.parse(JSON.stringify(value));
1640
+ }
1641
+ function hasOwnSetting(settings, key) {
1642
+ return Object.prototype.hasOwnProperty.call(settings, key);
1572
1643
  }
1573
1644
  function responseSettingsPayload(response) {
1574
1645
  if (!response || typeof response !== "object") return {};
@@ -1577,6 +1648,20 @@ function responseSettingsPayload(response) {
1577
1648
  if (data.settings && typeof data.settings === "object") return cloneRecord(data.settings);
1578
1649
  return {};
1579
1650
  }
1651
+ function responseSettingsRevision(response) {
1652
+ if (!response || typeof response !== "object") return null;
1653
+ const revision = response.revision;
1654
+ return typeof revision === "string" && revision ? revision : null;
1655
+ }
1656
+ function topLevelSettingsPatch(current, keys) {
1657
+ return Object.fromEntries([...keys].map((key) => [key, hasOwnSetting(current, key) ? current[key] : null]));
1658
+ }
1659
+ function changedTopLevelSettingKeys(current, saved) {
1660
+ return new Set([...new Set([...Object.keys(current), ...Object.keys(saved)])].filter((key) => JSON.stringify(current[key]) !== JSON.stringify(saved[key])));
1661
+ }
1662
+ function platformOwnedSettingsPatch(settings) {
1663
+ return { [PLATFORM_OWNED_SETTINGS_KEY]: hasOwnSetting(settings, PLATFORM_OWNED_SETTINGS_KEY) ? settings[PLATFORM_OWNED_SETTINGS_KEY] : null };
1664
+ }
1580
1665
  /** Load, edit, and persist plugin settings from platform config or the plugin-local settings router. */
1581
1666
  function usePluginSettings(options = {}) {
1582
1667
  const resolvedOptions = normalizePluginSettingsOptions(options);
@@ -1584,13 +1669,18 @@ function usePluginSettings(options = {}) {
1584
1669
  const { plugin, isIntegrated } = usePlatformContext();
1585
1670
  const platformApi = useApi({ baseUrl: resolvedOptions.platformApiBaseUrl ?? injectedContext?.platformApiUrl });
1586
1671
  const pluginApi = useApi({ baseUrl: resolvedOptions.apiBaseUrl ?? injectedContext?.plugin?.api_prefix });
1587
- const values = shallowRef({});
1672
+ const values = ref({});
1588
1673
  const savedValues = shallowRef({});
1674
+ const revision = ref(null);
1589
1675
  const request = useRequestSyncState("Plugin settings request failed.");
1590
1676
  const isLoading = ref(false);
1591
1677
  const isSaving = ref(false);
1592
1678
  const resolvedPluginName = computed(() => resolvedOptions.pluginName ?? plugin.value?.name ?? injectedContext?.plugin?.name ?? "");
1593
- const usesPlatformConfig = computed(() => Boolean((isIntegrated.value || injectedContext?.isIntegrated) && resolvedPluginName.value));
1679
+ const usesPluginSettingsApi = computed(() => {
1680
+ const currentPlugin = injectedContext?.plugin ?? plugin.value;
1681
+ return currentPlugin?.settings_api === "plugin" && resolvedPluginName.value === currentPlugin.name;
1682
+ });
1683
+ const usesPlatformConfig = computed(() => Boolean((isIntegrated.value || injectedContext?.isIntegrated) && resolvedPluginName.value && !usesPluginSettingsApi.value));
1594
1684
  const settings = computed(() => values.value);
1595
1685
  const isDirty = computed(() => JSON.stringify(values.value) !== JSON.stringify(savedValues.value));
1596
1686
  const settingsConfig = computed(() => ({
@@ -1602,21 +1692,23 @@ function usePluginSettings(options = {}) {
1602
1692
  function setValues(nextValues) {
1603
1693
  values.value = cloneRecord(nextValues);
1604
1694
  }
1605
- function applyLoaded(nextValues) {
1695
+ function applyLoaded(nextValues, nextRevision) {
1606
1696
  const cloned = cloneRecord(nextValues);
1607
1697
  values.value = cloned;
1608
1698
  savedValues.value = cloneRecord(cloned);
1699
+ revision.value = nextRevision;
1609
1700
  }
1610
1701
  async function load() {
1611
1702
  isLoading.value = true;
1612
1703
  try {
1613
- applyLoaded(responseSettingsPayload(usesPlatformConfig.value ? await request.run(() => platformApi.get(`/plugins/${encodeURIComponent(resolvedPluginName.value)}/config`), {
1704
+ const response = usesPlatformConfig.value ? await request.run(() => platformApi.get(`/plugins/${encodeURIComponent(resolvedPluginName.value)}/config`), {
1614
1705
  success: "load",
1615
1706
  errorMessage: "Failed to load plugin settings"
1616
1707
  }) : await request.run(() => pluginApi.get("/settings"), {
1617
1708
  success: "load",
1618
1709
  errorMessage: "Failed to load plugin settings"
1619
- })));
1710
+ });
1711
+ applyLoaded(responseSettingsPayload(response), responseSettingsRevision(response));
1620
1712
  } catch {} finally {
1621
1713
  isLoading.value = false;
1622
1714
  }
@@ -1626,13 +1718,37 @@ function usePluginSettings(options = {}) {
1626
1718
  isSaving.value = true;
1627
1719
  try {
1628
1720
  const payload = cloneRecord(values.value);
1629
- applyLoaded(responseSettingsPayload(usesPlatformConfig.value ? await request.run(() => platformApi.patch(`/plugins/${encodeURIComponent(resolvedPluginName.value)}/config`, { config: payload }), {
1630
- success: "save",
1631
- errorMessage: "Failed to save plugin settings"
1632
- }) : await request.run(() => pluginApi.put("/settings", payload), {
1633
- success: "save",
1634
- errorMessage: "Failed to save plugin settings"
1635
- })));
1721
+ let response;
1722
+ if (usesPlatformConfig.value) {
1723
+ const dirtyKeys = changedTopLevelSettingKeys(payload, savedValues.value);
1724
+ const platformOwnedChanged = dirtyKeys.has(PLATFORM_OWNED_SETTINGS_KEY);
1725
+ const typedKeys = [...dirtyKeys].filter((key) => key !== PLATFORM_OWNED_SETTINGS_KEY);
1726
+ let nextRevision = revision.value;
1727
+ if (typedKeys.length) {
1728
+ response = await request.run(() => platformApi.patch(`/plugins/${encodeURIComponent(resolvedPluginName.value)}/config`, { config: topLevelSettingsPatch(payload, typedKeys) }), {
1729
+ success: "save",
1730
+ errorMessage: "Failed to save plugin settings"
1731
+ });
1732
+ nextRevision = responseSettingsRevision(response) ?? nextRevision;
1733
+ }
1734
+ if (platformOwnedChanged) {
1735
+ response = await request.run(() => platformApi.patch(`/plugins/${encodeURIComponent(resolvedPluginName.value)}/config`, { config: platformOwnedSettingsPatch(payload) }), {
1736
+ success: "save",
1737
+ errorMessage: "Failed to save plugin settings"
1738
+ });
1739
+ nextRevision = responseSettingsRevision(response) ?? nextRevision;
1740
+ }
1741
+ if (!response) return true;
1742
+ applyLoaded(responseSettingsPayload(response), nextRevision);
1743
+ return true;
1744
+ } else {
1745
+ const ifMatch = revision.value ? { headers: { "If-Match": `"${revision.value}"` } } : void 0;
1746
+ response = await request.run(() => pluginApi.put("/settings", payload, ifMatch), {
1747
+ success: "save",
1748
+ errorMessage: "Failed to save plugin settings"
1749
+ });
1750
+ }
1751
+ applyLoaded(responseSettingsPayload(response), responseSettingsRevision(response) ?? revision.value);
1636
1752
  return true;
1637
1753
  } catch {
1638
1754
  return false;
@@ -1664,13 +1780,13 @@ function usePluginSettings(options = {}) {
1664
1780
  reset
1665
1781
  };
1666
1782
  }
1667
- function streamRequestAuth(options) {
1783
+ function streamRequestAuth(options, policy) {
1668
1784
  const headers = new Headers(options.headers);
1669
1785
  const authStore = useAuthStore();
1670
1786
  if (!authStore.isInitialized) authStore.initialize();
1671
- if (authStore.token && !headers.has("Authorization")) headers.set("Authorization", `Bearer ${authStore.token}`);
1787
+ if (policy.requiresAuth && authStore.token && !headers.has("Authorization")) headers.set("Authorization", `Bearer ${authStore.token}`);
1672
1788
  headers.set("Accept", "text/event-stream");
1673
- const token = authStore.token;
1789
+ const token = policy.requiresAuth ? authStore.token : void 0;
1674
1790
  return {
1675
1791
  headers,
1676
1792
  token: token && headers.get("Authorization") === `Bearer ${token}` ? token : void 0
@@ -1766,7 +1882,7 @@ function usePluginEventStream(contract, endpoint, payloadOrOptions, maybeOptions
1766
1882
  let authenticationFailed = false;
1767
1883
  try {
1768
1884
  const url = buildPluginEndpointUrl(contract, endpoint, payload, { baseUrl: options.baseUrl });
1769
- const requestAuth = streamRequestAuth(options);
1885
+ const requestAuth = streamRequestAuth(options, { requiresAuth: endpoint.requiresAuth !== false });
1770
1886
  const response = await fetch(url, {
1771
1887
  method: "GET",
1772
1888
  headers: requestAuth.headers,
@@ -1925,4 +2041,4 @@ function useCurrentExperiment(options = {}) {
1925
2041
  //#endregion
1926
2042
  export { useAsync as A, createPluginResourceClient as C, buildAnalysisArtifactTargetQuery as D, useCommandHistory as E, resolvePluginFrontendBase as M, usePasskey as N, parseAnalysisArtifactTarget as O, useAuth as P, wellIdsInRectangle as S, useOptimisticMutation 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, useAsyncBatch as j, usePluginConfig 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, useResourceCrud as w, wellIdToCoordinate as x, coordinateToWellId as y };
1927
2043
 
1928
- //# sourceMappingURL=composables-BUUphBqg.js.map
2044
+ //# sourceMappingURL=composables-C0VEJ73O.js.map