@morscherlab/mint-sdk 1.5.0-beta.15 → 1.5.0-beta.16
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/dist/{AlertBox-Cai0w7gF.js → AlertBox-BPiQwSlr.js} +2 -2
- package/dist/{AlertBox-Cai0w7gF.js.map → AlertBox-BPiQwSlr.js.map} +1 -1
- package/dist/{AppSidebar-B1g280Wq.js → AppSidebar-avUHlogX.js} +7 -7
- package/dist/{AppSidebar-B1g280Wq.js.map → AppSidebar-avUHlogX.js.map} +1 -1
- package/dist/{ExperimentPopover-DYy_s0kI.js → ExperimentPopover-DLRfd0ab.js} +2 -2
- package/dist/{ExperimentPopover-DYy_s0kI.js.map → ExperimentPopover-DLRfd0ab.js.map} +1 -1
- package/dist/{ExperimentSelectorModal-Bfh5bdkv.js → ExperimentSelectorModal-Brsf71MN.js} +4 -4
- package/dist/{ExperimentSelectorModal-Bfh5bdkv.js.map → ExperimentSelectorModal-Brsf71MN.js.map} +1 -1
- package/dist/{useRealtime-BLQEjbuq.js → JobsStatusTray-DWd8Hpmr.js} +4 -125
- package/dist/JobsStatusTray-DWd8Hpmr.js.map +1 -0
- package/dist/{SegmentedControl-B8KgyOjd.js → SegmentedControl-CEZbeh_h.js} +2 -2
- package/dist/{SegmentedControl-B8KgyOjd.js.map → SegmentedControl-CEZbeh_h.js.map} +1 -1
- package/dist/{SettingsModal-CeaQHyot.js → SettingsModal-kBAf-THw.js} +8 -8
- package/dist/{SettingsModal-CeaQHyot.js.map → SettingsModal-kBAf-THw.js.map} +1 -1
- package/dist/components/internal/AutoGroupModalInternal.adapter.d.ts +0 -10
- package/dist/composables/useRealtime.d.ts +19 -3
- package/dist/{controlSchemaModel-BtFNZj8t.js → controlSchemaModel-CORuwx9Z.js} +2 -2
- package/dist/{controlSchemaModel-BtFNZj8t.js.map → controlSchemaModel-CORuwx9Z.js.map} +1 -1
- package/dist/{data-CJztFns8.js → data-de4y8LO7.js} +12 -11
- package/dist/{data-CJztFns8.js.map → data-de4y8LO7.js.map} +1 -1
- package/dist/data.js +7 -7
- package/dist/{experiment-DyO54FQB.js → experiment-CNMuOEGc.js} +6 -3
- package/dist/experiment-CNMuOEGc.js.map +1 -0
- package/dist/host.d.ts +1 -0
- package/dist/host.js +2 -2
- package/dist/host.js.map +1 -1
- package/dist/index.js +14 -14
- package/dist/{install-rcT3eXD7.js → install-C6A34A9P.js} +12 -12
- package/dist/{install-rcT3eXD7.js.map → install-C6A34A9P.js.map} +1 -1
- package/dist/install.js +1 -1
- package/dist/{lab-BYhb_cJI.js → lab-CGnCGPf6.js} +9 -9
- package/dist/lab-CGnCGPf6.js.map +1 -0
- package/dist/lab.js +4 -4
- package/dist/{records-GtiVMlTQ.js → records-TEeD11BZ.js} +2 -2
- package/dist/{records-GtiVMlTQ.js.map → records-TEeD11BZ.js.map} +1 -1
- package/dist/{runtime-7_LcVjuP.js → runtime-CmgnbmnZ.js} +12 -14
- package/dist/{runtime-7_LcVjuP.js.map → runtime-CmgnbmnZ.js.map} +1 -1
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +7 -7
- package/dist/{shell-DnCwE5hq.js → shell-CHecDOIP.js} +4 -4
- package/dist/{shell-DnCwE5hq.js.map → shell-CHecDOIP.js.map} +1 -1
- package/dist/shell.js +4 -4
- package/dist/stores/index.js +2 -2
- package/dist/{useExperimentSamples-CpNXvpUn.js → useExperimentSamples-s2QY9Wn_.js} +4 -4
- package/dist/{useExperimentSamples-CpNXvpUn.js.map → useExperimentSamples-s2QY9Wn_.js.map} +1 -1
- package/dist/{useListReorder-Bp4EAI2w.js → useListReorder-CNAbJi9S.js} +4 -4
- package/dist/{useListReorder-Bp4EAI2w.js.map → useListReorder-CNAbJi9S.js.map} +1 -1
- package/dist/{useRackEditor-i2bdPUFf.js → useRackEditor-ldrpkIIQ.js} +8 -8
- package/dist/{useRackEditor-i2bdPUFf.js.map → useRackEditor-ldrpkIIQ.js.map} +1 -1
- package/dist/useRealtime-t7Qg2a5h.js +216 -0
- package/dist/useRealtime-t7Qg2a5h.js.map +1 -0
- package/dist/{useRequest-CXyPw3eh.js → useRequest-CcgNnT2V.js} +2 -2
- package/dist/{useRequest-CXyPw3eh.js.map → useRequest-CcgNnT2V.js.map} +1 -1
- package/package.json +1 -1
- package/src/__tests__/components/AutoGroupModal.adapter.test.ts +0 -31
- package/src/__tests__/composables/usePresence.test.ts +21 -0
- package/src/__tests__/composables/useRealtime.test.ts +194 -1
- package/src/components/internal/AutoGroupModalInternal.adapter.ts +0 -25
- package/src/composables/useApi.ts +5 -1
- package/src/composables/usePresence.ts +4 -4
- package/src/composables/useRealtime.ts +59 -6
- package/src/composables/useVersionedSave.ts +2 -1
- package/src/host.ts +1 -0
- package/src/runtime.ts +1 -0
- package/dist/auth-SliLnFre.js +0 -65
- package/dist/auth-SliLnFre.js.map +0 -1
- package/dist/experiment-DyO54FQB.js.map +0 -1
- package/dist/lab-BYhb_cJI.js.map +0 -1
- package/dist/useRealtime-BLQEjbuq.js.map +0 -1
|
@@ -74,8 +74,7 @@ export function usePresence(
|
|
|
74
74
|
const realtime = subscribeRealtime(() => [toValue(topic)], (event) => {
|
|
75
75
|
// ponytail: a resync after a queue overflow keeps the last viewers until
|
|
76
76
|
// the next change; read the presence over REST here if that shows.
|
|
77
|
-
if (event.type === '
|
|
78
|
-
else if (event.type === 'presence.changed' && event.topic === toValue(topic)) show(event)
|
|
77
|
+
if (event.type === 'presence.changed' && event.topic === toValue(topic)) show(event)
|
|
79
78
|
}, true)
|
|
80
79
|
|
|
81
80
|
// When the tab's open stream already views the topic, no open and no resync follow.
|
|
@@ -87,8 +86,9 @@ export function usePresence(
|
|
|
87
86
|
load()
|
|
88
87
|
const stopTopic = watch(() => toValue(topic), load)
|
|
89
88
|
// A closed stream shows nobody; the server sends the viewers again at the next open.
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
// Each open reports the label (a cursor open sends no resync), at once, so a later change reports after it.
|
|
90
|
+
const stopConnected = watch(realtime.connected, open => open ? report() : show(undefined), { flush: 'sync' })
|
|
91
|
+
// While the stream is closed the report waits for the next open.
|
|
92
92
|
const stopActivity = watch(() => toValue(activity), () => {
|
|
93
93
|
if (realtime.connected.value) report()
|
|
94
94
|
})
|
|
@@ -41,6 +41,19 @@ export interface RealtimeEvent {
|
|
|
41
41
|
|
|
42
42
|
export type RealtimeHandler = (event: RealtimeEvent) => void
|
|
43
43
|
|
|
44
|
+
export interface UseRealtimeOptions {
|
|
45
|
+
/**
|
|
46
|
+
* The caller loads all its data through a client that records
|
|
47
|
+
* `X-MINT-Event-Seq`: `useApi()` or the platform's own client. Responses of
|
|
48
|
+
* a `createClient` client (plain `fetch`) and of `getApiClient()` before the
|
|
49
|
+
* first `useApi()` are not recorded, so a caller that loads through them must
|
|
50
|
+
* not set it. When every caller of the tab sets it, the stream opens with the
|
|
51
|
+
* lowest recorded sequence since the page loaded, and the server sends
|
|
52
|
+
* `resync` only when a topic changed since then, instead of one at each open.
|
|
53
|
+
*/
|
|
54
|
+
cursor?: boolean
|
|
55
|
+
}
|
|
56
|
+
|
|
44
57
|
export interface UseRealtimeReturn {
|
|
45
58
|
/** True while the shared stream is open. */
|
|
46
59
|
connected: Readonly<Ref<boolean>>
|
|
@@ -60,6 +73,13 @@ interface Listener {
|
|
|
60
73
|
handler: RealtimeHandler
|
|
61
74
|
/** Receives `presence.changed`; a write listener would refetch on each viewer change. */
|
|
62
75
|
presence: boolean
|
|
76
|
+
/** Its data loads record their event cursor (see `UseRealtimeOptions.cursor`). */
|
|
77
|
+
cursor: boolean
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface EventCursor {
|
|
81
|
+
seq: number
|
|
82
|
+
boot: string
|
|
63
83
|
}
|
|
64
84
|
|
|
65
85
|
const listeners = new Set<Listener>()
|
|
@@ -71,6 +91,28 @@ let sourceTopics = ''
|
|
|
71
91
|
let failures = 0
|
|
72
92
|
let syncTimer: ReturnType<typeof setTimeout> | null = null
|
|
73
93
|
let retryTimer: ReturnType<typeof setTimeout> | null = null
|
|
94
|
+
/** The lowest `X-MINT-Event-Seq` of the responses since the page loaded, and their boot id (`''` once two differ). */
|
|
95
|
+
let tracked: EventCursor | null = null
|
|
96
|
+
/** The cursor the current stream was opened with. */
|
|
97
|
+
let streamCursor: EventCursor | null = null
|
|
98
|
+
|
|
99
|
+
/** @internal Record the `X-MINT-Event-Seq` / `X-MINT-Boot-Id` headers of one platform response. */
|
|
100
|
+
export function noteEventSeq(seq: string | null | undefined, bootId: string | null | undefined): void {
|
|
101
|
+
const value = Number(seq)
|
|
102
|
+
// No header: the response tells nothing (an older platform, another origin).
|
|
103
|
+
if (!seq || !bootId || !Number.isSafeInteger(value)) return
|
|
104
|
+
// A request that started before the stream's cursor, or on another process,
|
|
105
|
+
// may miss an event the server did not check: refetch.
|
|
106
|
+
if (streamCursor && (value < streamCursor.seq || bootId !== streamCursor.boot)) {
|
|
107
|
+
// Moved, so each late condition refetches once instead of on every refetch response.
|
|
108
|
+
streamCursor = { seq: value, boot: bootId }
|
|
109
|
+
dispatch(RESYNC)
|
|
110
|
+
}
|
|
111
|
+
// Sequences of two processes do not compare: '' never matches, so every later open resyncs.
|
|
112
|
+
tracked = tracked
|
|
113
|
+
? { seq: Math.min(tracked.seq, value), boot: tracked.boot === bootId ? tracked.boot : '' }
|
|
114
|
+
: { seq: value, boot: bootId }
|
|
115
|
+
}
|
|
74
116
|
|
|
75
117
|
function dispatch(event: RealtimeEvent): void {
|
|
76
118
|
const isPresence = event.type === 'presence.changed'
|
|
@@ -88,18 +130,24 @@ function close(): void {
|
|
|
88
130
|
source = null
|
|
89
131
|
sourceTopics = ''
|
|
90
132
|
presence.clear()
|
|
133
|
+
streamCursor = null
|
|
91
134
|
connected.value = false
|
|
92
135
|
}
|
|
93
136
|
|
|
94
137
|
function open(topics: string): void {
|
|
95
|
-
const
|
|
138
|
+
const cursor = tracked && [...listeners].every(listener => listener.cursor) ? tracked : null
|
|
139
|
+
const query = cursor ? `&since=${cursor.seq}&boot=${encodeURIComponent(cursor.boot)}` : ''
|
|
140
|
+
const stream = new EventSource(`${EVENTS_URL}?topics=${encodeURIComponent(topics)}${query}`)
|
|
96
141
|
source = stream
|
|
97
142
|
sourceTopics = topics
|
|
143
|
+
streamCursor = cursor
|
|
98
144
|
stream.onopen = () => {
|
|
99
145
|
connected.value = true
|
|
100
146
|
// The server subscribes only now: a write between a caller's REST load (or
|
|
101
|
-
// the close of the previous stream) and this point sent no event
|
|
102
|
-
|
|
147
|
+
// the close of the previous stream) and this point sent no event. With a
|
|
148
|
+
// cursor the server sends `resync` when needed; otherwise every caller refetches.
|
|
149
|
+
// A caller that did not opt in may have joined while the stream connected.
|
|
150
|
+
if (!cursor || ![...listeners].every(listener => listener.cursor)) dispatch(RESYNC)
|
|
103
151
|
failures = 0
|
|
104
152
|
}
|
|
105
153
|
stream.onmessage = (message) => {
|
|
@@ -144,7 +192,9 @@ function schedule(): void {
|
|
|
144
192
|
* to `usePresence`) plus every `resync`: one comes from the
|
|
145
193
|
* server when this tab fell behind, and one follows each open of the stream:
|
|
146
194
|
* the first open, each reconnect (backoff from 1 s to 30 s) and each reopen for
|
|
147
|
-
* a changed topic union.
|
|
195
|
+
* a changed topic union. With `options.cursor` set by every caller, the open
|
|
196
|
+
* sends no `resync` of its own; the server sends one when a topic changed since
|
|
197
|
+
* the oldest recorded REST load. On `resync`, refetch. The union of all callers' topics
|
|
148
198
|
* in a tab may hold at most 50 topics; the platform refuses a larger stream.
|
|
149
199
|
*
|
|
150
200
|
* The stream authenticates with the session cookie, so it works in the
|
|
@@ -164,8 +214,9 @@ function schedule(): void {
|
|
|
164
214
|
export function useRealtime(
|
|
165
215
|
topics: MaybeRefOrGetter<string[]>,
|
|
166
216
|
handler: RealtimeHandler,
|
|
217
|
+
options: UseRealtimeOptions = {},
|
|
167
218
|
): UseRealtimeReturn {
|
|
168
|
-
return subscribeRealtime(topics, handler, false)
|
|
219
|
+
return subscribeRealtime(topics, handler, false, options.cursor ?? false)
|
|
169
220
|
}
|
|
170
221
|
|
|
171
222
|
/** The last `presence.changed` of `topic` while the open stream views it (for `usePresence`). */
|
|
@@ -178,8 +229,10 @@ export function subscribeRealtime(
|
|
|
178
229
|
topics: MaybeRefOrGetter<string[]>,
|
|
179
230
|
handler: RealtimeHandler,
|
|
180
231
|
withPresence: boolean,
|
|
232
|
+
cursor = false,
|
|
181
233
|
): UseRealtimeReturn {
|
|
182
|
-
|
|
234
|
+
// A presence caller does not refetch on resync, so it never needs the client-side one.
|
|
235
|
+
const listener: Listener = { topics: () => toValue(topics), handler, presence: withPresence, cursor: cursor || withPresence }
|
|
183
236
|
listeners.add(listener)
|
|
184
237
|
const stopWatch = watch(() => listener.topics().join(','), () => schedule())
|
|
185
238
|
schedule()
|
|
@@ -168,7 +168,8 @@ export function useVersionedSave<T>(options: UseVersionedSaveOptions<T>): UseVer
|
|
|
168
168
|
useRealtime(() => {
|
|
169
169
|
const topic = toValue(options.topic)
|
|
170
170
|
return topic ? [topic] : []
|
|
171
|
-
|
|
171
|
+
// It ignores resync, so it never needs the stream's own one.
|
|
172
|
+
}, onEvent, { cursor: true })
|
|
172
173
|
// Another entity, or a closed form: the old writes and the old 412 no longer apply.
|
|
173
174
|
watch(() => toValue(options.topic), () => {
|
|
174
175
|
reloadToken += 1
|
package/src/host.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { AUTH_EXPIRES_STORAGE_KEY, AUTH_TOKEN_STORAGE_KEY, useAuthStore } from '
|
|
|
5
5
|
import type { UserInfo } from './types'
|
|
6
6
|
|
|
7
7
|
export { AUTH_EXPIRES_STORAGE_KEY, AUTH_TOKEN_STORAGE_KEY }
|
|
8
|
+
export { noteEventSeq } from './composables/useRealtime'
|
|
8
9
|
export type { ExperimentStoreBinding, MintIntegrationHost } from './utils/integrationHost'
|
|
9
10
|
|
|
10
11
|
/** Set the session token SDK clients send. `null` signs the bridge out. */
|
package/src/runtime.ts
CHANGED
package/dist/auth-SliLnFre.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { computed, ref } from "vue";
|
|
2
|
-
import { defineStore } from "pinia";
|
|
3
|
-
//#region src/stores/auth.ts
|
|
4
|
-
/** localStorage key of the platform session token. The platform owns the stored value. */
|
|
5
|
-
var AUTH_TOKEN_STORAGE_KEY = "mint-auth-token";
|
|
6
|
-
/** localStorage key of the platform session token expiry (ISO 8601). */
|
|
7
|
-
var AUTH_EXPIRES_STORAGE_KEY = "mint-auth-expires";
|
|
8
|
-
function readStoredItem(key) {
|
|
9
|
-
try {
|
|
10
|
-
const storage = globalThis.localStorage;
|
|
11
|
-
return typeof storage?.getItem === "function" ? storage.getItem(key) : null;
|
|
12
|
-
} catch {
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Read-only session bridge between the platform and SDK code.
|
|
18
|
-
*
|
|
19
|
-
* SDK clients read the session token from here. Only the platform writes the
|
|
20
|
-
* session, through `@morscherlab/mint-sdk/host`. The SDK never writes or removes
|
|
21
|
-
* the stored platform token.
|
|
22
|
-
*/
|
|
23
|
-
var useAuthStore = defineStore("mint-auth", () => {
|
|
24
|
-
const token = ref(null);
|
|
25
|
-
const tokenExpires = ref(null);
|
|
26
|
-
const userInfo = ref(null);
|
|
27
|
-
const authRequired = ref(true);
|
|
28
|
-
const isInitialized = ref(false);
|
|
29
|
-
const isAuthenticated = computed(() => {
|
|
30
|
-
if (!authRequired.value) return true;
|
|
31
|
-
if (!token.value) return false;
|
|
32
|
-
return !(tokenExpires.value && tokenExpires.value < /* @__PURE__ */ new Date());
|
|
33
|
-
});
|
|
34
|
-
/** Read the stored platform session token, once per page. */
|
|
35
|
-
function initialize() {
|
|
36
|
-
const storedToken = readStoredItem(AUTH_TOKEN_STORAGE_KEY);
|
|
37
|
-
const storedExpires = readStoredItem(AUTH_EXPIRES_STORAGE_KEY);
|
|
38
|
-
const expires = storedExpires ? new Date(storedExpires) : null;
|
|
39
|
-
if (storedToken && !(expires && expires <= /* @__PURE__ */ new Date())) {
|
|
40
|
-
token.value = storedToken;
|
|
41
|
-
tokenExpires.value = expires;
|
|
42
|
-
}
|
|
43
|
-
isInitialized.value = true;
|
|
44
|
-
}
|
|
45
|
-
/** Forget the session in memory after the server rejected its token. */
|
|
46
|
-
function clearToken() {
|
|
47
|
-
token.value = null;
|
|
48
|
-
tokenExpires.value = null;
|
|
49
|
-
userInfo.value = null;
|
|
50
|
-
}
|
|
51
|
-
return {
|
|
52
|
-
token,
|
|
53
|
-
tokenExpires,
|
|
54
|
-
userInfo,
|
|
55
|
-
authRequired,
|
|
56
|
-
isInitialized,
|
|
57
|
-
isAuthenticated,
|
|
58
|
-
initialize,
|
|
59
|
-
clearToken
|
|
60
|
-
};
|
|
61
|
-
});
|
|
62
|
-
//#endregion
|
|
63
|
-
export { AUTH_TOKEN_STORAGE_KEY as n, useAuthStore as r, AUTH_EXPIRES_STORAGE_KEY as t };
|
|
64
|
-
|
|
65
|
-
//# sourceMappingURL=auth-SliLnFre.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"auth-SliLnFre.js","names":[],"sources":["../src/stores/auth.ts"],"sourcesContent":["import { defineStore } from 'pinia'\nimport { ref, computed } from 'vue'\nimport type { UserInfo } from '../types'\n\n/** localStorage key of the platform session token. The platform owns the stored value. */\nexport const AUTH_TOKEN_STORAGE_KEY = 'mint-auth-token'\n/** localStorage key of the platform session token expiry (ISO 8601). */\nexport const AUTH_EXPIRES_STORAGE_KEY = 'mint-auth-expires'\n\nfunction readStoredItem(key: string): string | null {\n try {\n const storage = globalThis.localStorage\n return typeof storage?.getItem === 'function' ? storage.getItem(key) : null\n } catch {\n return null\n }\n}\n\n/**\n * Read-only session bridge between the platform and SDK code.\n *\n * SDK clients read the session token from here. Only the platform writes the\n * session, through `@morscherlab/mint-sdk/host`. The SDK never writes or removes\n * the stored platform token.\n */\nexport const useAuthStore = defineStore('mint-auth', () => {\n const token = ref<string | null>(null)\n const tokenExpires = ref<Date | null>(null)\n const userInfo = ref<UserInfo | null>(null)\n const authRequired = ref(true)\n const isInitialized = ref(false)\n\n const isAuthenticated = computed(() => {\n if (!authRequired.value) return true\n if (!token.value) return false\n return !(tokenExpires.value && tokenExpires.value < new Date())\n })\n\n /** Read the stored platform session token, once per page. */\n function initialize() {\n const storedToken = readStoredItem(AUTH_TOKEN_STORAGE_KEY)\n const storedExpires = readStoredItem(AUTH_EXPIRES_STORAGE_KEY)\n const expires = storedExpires ? new Date(storedExpires) : null\n\n if (storedToken && !(expires && expires <= new Date())) {\n token.value = storedToken\n tokenExpires.value = expires\n }\n\n isInitialized.value = true\n }\n\n /** Forget the session in memory after the server rejected its token. */\n function clearToken() {\n token.value = null\n tokenExpires.value = null\n userInfo.value = null\n }\n\n return {\n token,\n tokenExpires,\n userInfo,\n authRequired,\n isInitialized,\n isAuthenticated,\n initialize,\n clearToken,\n }\n})\n"],"mappings":";;;;AAKA,IAAa,yBAAyB;;AAEtC,IAAa,2BAA2B;AAExC,SAAS,eAAe,KAA4B;CAClD,IAAI;EACF,MAAM,UAAU,WAAW;EAC3B,OAAO,OAAO,SAAS,YAAY,aAAa,QAAQ,QAAQ,GAAG,IAAI;CACzE,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;AASA,IAAa,eAAe,YAAY,mBAAmB;CACzD,MAAM,QAAQ,IAAmB,IAAI;CACrC,MAAM,eAAe,IAAiB,IAAI;CAC1C,MAAM,WAAW,IAAqB,IAAI;CAC1C,MAAM,eAAe,IAAI,IAAI;CAC7B,MAAM,gBAAgB,IAAI,KAAK;CAE/B,MAAM,kBAAkB,eAAe;EACrC,IAAI,CAAC,aAAa,OAAO,OAAO;EAChC,IAAI,CAAC,MAAM,OAAO,OAAO;EACzB,OAAO,EAAE,aAAa,SAAS,aAAa,wBAAQ,IAAI,KAAK;CAC/D,CAAC;;CAGD,SAAS,aAAa;EACpB,MAAM,cAAc,eAAe,sBAAsB;EACzD,MAAM,gBAAgB,eAAe,wBAAwB;EAC7D,MAAM,UAAU,gBAAgB,IAAI,KAAK,aAAa,IAAI;EAE1D,IAAI,eAAe,EAAE,WAAW,2BAAW,IAAI,KAAK,IAAI;GACtD,MAAM,QAAQ;GACd,aAAa,QAAQ;EACvB;EAEA,cAAc,QAAQ;CACxB;;CAGA,SAAS,aAAa;EACpB,MAAM,QAAQ;EACd,aAAa,QAAQ;EACrB,SAAS,QAAQ;CACnB;CAEA,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"experiment-DyO54FQB.js","names":[],"sources":["../src/apiErrors.ts","../src/stores/settings.ts","../src/composables/useApi.ts","../src/utils/errors.ts","../src/utils/integrationHost.ts","../src/stores/experiment.ts"],"sourcesContent":["export interface MintApiErrorEnvelope {\n code: string\n message: string\n status: number\n request_id: string | null\n details: unknown\n detail?: unknown\n}\n\nconst STATUS_MESSAGES: Readonly<Record<number, string>> = {\n 400: 'Bad request',\n 401: 'Authentication required',\n 403: 'Permission denied',\n 404: 'Resource not found',\n 405: 'Method not allowed',\n 409: 'Conflict',\n 413: 'Payload too large',\n 415: 'Unsupported media type',\n 422: 'Validation error',\n 429: 'Rate limit exceeded',\n 500: 'Internal server error',\n 502: 'Bad gateway',\n 503: 'Service unavailable',\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | null {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return null\n return value as Record<string, unknown>\n}\n\n/** Public error thrown by typed MINT SDK requests. */\nexport class MintApiError extends Error {\n override readonly name: string = 'MintApiError'\n\n constructor(\n message: string,\n readonly code: string,\n readonly status: number,\n readonly requestId: string | null,\n readonly details: unknown,\n readonly detail: unknown,\n readonly body: unknown,\n readonly cause?: unknown,\n ) {\n super(message)\n }\n}\n\nexport interface MintApiErrorOptions {\n requestId?: string | null\n cause?: unknown\n}\n\n/** Normalize current typed envelopes and the previous FastAPI detail shape. */\nexport function mintApiErrorFromResponse(\n status: number,\n payload: unknown,\n options: MintApiErrorOptions = {},\n): MintApiError {\n const body = asRecord(payload)\n const legacyDetail = body?.detail\n const inner = asRecord(legacyDetail) ?? body\n const text = (value: unknown): string | undefined =>\n typeof value === 'string' && value ? value : undefined\n // Complete envelopes own the public code; older wrappers defer to the plugin payload.\n const canonical = typeof body?.status === 'number' && text(body.code) && text(body.message)\n const code = (canonical ? text(body.code) : text(inner?.error) ?? text(inner?.code))\n ?? text(body?.code) ?? 'unknown'\n const message = (canonical ? text(body.message) : text(inner?.message))\n ?? text(body?.message) ?? text(legacyDetail)\n ?? STATUS_MESSAGES[status] ?? `HTTP ${status}`\n const details = body?.details ?? inner?.details ?? null\n const requestId = text(body?.request_id) ?? text(inner?.request_id) ?? options.requestId ?? null\n\n return new MintApiError(\n message,\n code,\n status,\n requestId,\n details,\n legacyDetail,\n payload,\n options.cause,\n )\n}\n","import { defineStore, getActivePinia } from 'pinia'\nimport { ref, watch } from 'vue'\nimport type { ThemeMode, ColorPalette, TableDensity } from '../types'\n\ndeclare global {\n interface ImportMetaEnv {\n readonly VITE_API_PREFIX?: string\n }\n\n interface ImportMeta {\n readonly env: ImportMetaEnv\n }\n}\n\nexport interface SettingsState {\n serverHost: string\n serverPort: number\n requestTimeout: number\n wsAutoReconnect: boolean\n wsReconnectInterval: number\n theme: ThemeMode\n colorPalette: ColorPalette\n tableDensity: TableDensity\n}\n\nconst STORAGE_KEY = 'mint-settings'\nconst LEGACY_STORAGE_KEY = 'mld-settings'\n\nfunction getDefaultServerHost(): string {\n if (typeof window !== 'undefined' && window.location.hostname !== 'localhost') {\n return window.location.hostname\n }\n return 'localhost'\n}\n\nfunction getDefaultServerPort(): number {\n if (typeof window !== 'undefined') {\n if (window.location.port) {\n return parseInt(window.location.port, 10)\n }\n // Standard ports: 443 for HTTPS, 80 for HTTP (browser omits from location.port)\n return window.location.protocol === 'https:' ? 443 : 80\n }\n return 8000\n}\n\nconst defaultSettings: SettingsState = {\n serverHost: getDefaultServerHost(),\n serverPort: getDefaultServerPort(),\n requestTimeout: 120000,\n wsAutoReconnect: true,\n wsReconnectInterval: 5000,\n theme: 'system',\n colorPalette: 'default',\n tableDensity: 'normal',\n}\n\n/**\n * Accent palettes. `styles/variables.css` holds each palette's light and dark\n * tokens under `[data-palette]`; the store only writes the attribute.\n */\nexport const colorPalettes: Record<ColorPalette, { name: string }> = {\n default: { name: 'Teal' },\n colorblind: { name: 'Colour-blind safe' },\n purple: { name: 'Purple' },\n ocean: { name: 'Ocean' },\n forest: { name: 'Forest' },\n}\n\n/** Pre-v2 palette ids and the palette that replaces each one. */\nconst RETIRED_PALETTES: Record<string, ColorPalette> = { viridis: 'purple', pastel: 'default' }\n\nfunction knownPalette(value: unknown): ColorPalette {\n if (typeof value === 'string' && value in colorPalettes) return value as ColorPalette\n return (typeof value === 'string' && RETIRED_PALETTES[value]) || 'default'\n}\n\n/** `DataFrame`-style size that each table density maps onto. */\nconst DENSITY_TABLE_SIZE: Record<TableDensity, 'sm' | 'md' | 'lg'> = {\n compact: 'sm',\n normal: 'md',\n comfortable: 'lg',\n}\n\n/** Map a table density onto the `size` prop shared by the SDK table components. */\nexport function tableDensityToSize(density: TableDensity): 'sm' | 'md' | 'lg' {\n return DENSITY_TABLE_SIZE[density]\n}\n\nfunction loadSettings(): SettingsState {\n try {\n const stored = localStorage.getItem(STORAGE_KEY)\n if (stored) {\n const parsed = JSON.parse(stored)\n return { ...defaultSettings, ...parsed, colorPalette: knownPalette(parsed.colorPalette) }\n }\n\n const legacyStored = localStorage.getItem(LEGACY_STORAGE_KEY)\n if (legacyStored) {\n const parsed = JSON.parse(legacyStored)\n const migrated = { ...defaultSettings, ...parsed, colorPalette: knownPalette(parsed.colorPalette) }\n localStorage.setItem(STORAGE_KEY, JSON.stringify(migrated))\n localStorage.removeItem(LEGACY_STORAGE_KEY)\n return migrated\n }\n } catch (e) {\n console.warn('Failed to load settings from localStorage:', e)\n }\n return { ...defaultSettings }\n}\n\nfunction saveSettings(settings: SettingsState): void {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(settings))\n } catch (e) {\n console.warn('Failed to save settings to localStorage:', e)\n }\n}\n\nfunction getSystemPrefersDark(): boolean {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false\n return window.matchMedia('(prefers-color-scheme: dark)').matches\n}\n\nexport const useSettingsStore = defineStore('mint-settings', () => {\n // State\n const serverHost = ref(defaultSettings.serverHost)\n const serverPort = ref(defaultSettings.serverPort)\n const requestTimeout = ref(defaultSettings.requestTimeout)\n const wsAutoReconnect = ref(defaultSettings.wsAutoReconnect)\n const wsReconnectInterval = ref(defaultSettings.wsReconnectInterval)\n const theme = ref<ThemeMode>(defaultSettings.theme)\n const systemPrefersDark = ref(getSystemPrefersDark())\n const colorPalette = ref<ColorPalette>(defaultSettings.colorPalette)\n const tableDensity = ref<TableDensity>(defaultSettings.tableDensity)\n\n // API prefix - can be configured via env variable VITE_API_PREFIX\n const apiPrefix: string = (import.meta.env?.VITE_API_PREFIX as string | undefined) ?? '/api'\n\n function _isSameOrigin(): { same: boolean; currentHost: string; currentPort: string } {\n const currentHost = typeof window !== 'undefined' ? window.location.hostname : 'localhost'\n const currentPort = typeof window !== 'undefined' ? window.location.port : '8000'\n const effectivePort = currentPort || (window.location.protocol === 'https:' ? '443' : '80')\n const same =\n (serverHost.value === currentHost && String(serverPort.value) === effectivePort) ||\n (serverHost.value === 'localhost' && currentHost !== 'localhost')\n return { same, currentHost, currentPort }\n }\n\n function getApiBaseUrl(): string {\n const { same } = _isSameOrigin()\n if (same) return apiPrefix\n return `http://${serverHost.value}:${serverPort.value}${apiPrefix}`\n }\n\n function getWsBaseUrl(): string {\n const { same, currentHost, currentPort } = _isSameOrigin()\n const protocol = typeof window !== 'undefined' && window.location.protocol === 'https:' ? 'wss:' : 'ws:'\n if (same) return `${protocol}//${currentHost}${currentPort ? ':' + currentPort : ''}${apiPrefix}`\n return `ws://${serverHost.value}:${serverPort.value}${apiPrefix}`\n }\n\n let _initialized = false\n\n function initialize() {\n if (_initialized) return\n _initialized = true\n\n const loaded = loadSettings()\n serverHost.value = loaded.serverHost\n serverPort.value = loaded.serverPort\n requestTimeout.value = loaded.requestTimeout\n wsAutoReconnect.value = loaded.wsAutoReconnect\n wsReconnectInterval.value = loaded.wsReconnectInterval\n theme.value = loaded.theme\n colorPalette.value = loaded.colorPalette\n tableDensity.value = loaded.tableDensity\n\n applyTheme()\n applyPalette()\n applyDensity()\n }\n\n function applyTheme() {\n if (typeof document === 'undefined') return\n const dark = theme.value === 'system' ? systemPrefersDark.value : theme.value === 'dark'\n document.documentElement.classList.toggle('dark', dark)\n }\n\n /** Mirror the palette onto `<html data-palette>`; `styles/variables.css` maps it to tokens. */\n function applyPalette() {\n if (typeof document === 'undefined') return\n document.documentElement.dataset.palette = colorPalette.value\n }\n\n /** Mirror the table density onto `<html data-density>` for CSS consumers. */\n function applyDensity() {\n if (typeof document === 'undefined') return\n document.documentElement.dataset.density = tableDensity.value\n }\n\n watch(theme, () => {\n applyTheme()\n persistSettings()\n })\n\n watch(colorPalette, applyPalette)\n watch(tableDensity, applyDensity)\n\n if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {\n window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (event) => {\n systemPrefersDark.value = event.matches\n if (theme.value === 'system') {\n applyTheme()\n }\n })\n }\n\n function persistSettings() {\n saveSettings({\n serverHost: serverHost.value,\n serverPort: serverPort.value,\n requestTimeout: requestTimeout.value,\n wsAutoReconnect: wsAutoReconnect.value,\n wsReconnectInterval: wsReconnectInterval.value,\n theme: theme.value,\n colorPalette: colorPalette.value,\n tableDensity: tableDensity.value,\n })\n }\n\n watch([serverHost, serverPort, requestTimeout, wsAutoReconnect, wsReconnectInterval, colorPalette, tableDensity], () => {\n persistSettings()\n })\n\n function resetToDefaults() {\n serverHost.value = defaultSettings.serverHost\n serverPort.value = defaultSettings.serverPort\n requestTimeout.value = defaultSettings.requestTimeout\n wsAutoReconnect.value = defaultSettings.wsAutoReconnect\n wsReconnectInterval.value = defaultSettings.wsReconnectInterval\n theme.value = defaultSettings.theme\n colorPalette.value = defaultSettings.colorPalette\n tableDensity.value = defaultSettings.tableDensity\n }\n\n /** The `size` the SDK table components default to for the current density. */\n function getTableSize(): 'sm' | 'md' | 'lg' {\n return tableDensityToSize(tableDensity.value)\n }\n\n function isDark(): boolean {\n return theme.value === 'system' ? systemPrefersDark.value : theme.value === 'dark'\n }\n\n return {\n serverHost,\n serverPort,\n requestTimeout,\n wsAutoReconnect,\n wsReconnectInterval,\n theme,\n systemPrefersDark,\n colorPalette,\n tableDensity,\n initialize,\n applyTheme,\n applyPalette,\n applyDensity,\n persistSettings,\n resetToDefaults,\n getTableSize,\n isDark,\n getApiBaseUrl,\n getWsBaseUrl,\n }\n})\n\n/**\n * Read the settings store without requiring Pinia to be installed.\n *\n * SDK components are mounted standalone (unit tests, Histoire stories, plugin\n * previews) where no Pinia instance exists. They use this to pick up a user's\n * preferences when the platform is running and fall back to their own defaults\n * otherwise, instead of throwing.\n */\nexport function tryUseSettingsStore(): ReturnType<typeof useSettingsStore> | null {\n if (!getActivePinia()) return null\n try {\n return useSettingsStore()\n } catch {\n return null\n }\n}\n","import axios, { type AxiosInstance, type AxiosRequestConfig, type InternalAxiosRequestConfig } from 'axios'\nimport { useSettingsStore } from '../stores/settings'\nimport { useAuthStore } from '../stores/auth'\nimport { MintApiError, mintApiErrorFromResponse } from '../apiErrors'\n\nlet apiClientInstance: AxiosInstance | null = null\nlet interceptorAttached = false\n\ninterface MintAxiosRequestConfig extends AxiosRequestConfig {\n _mintSkipAuth?: boolean\n _mintAuthToken?: string\n}\n\ninterface MintInternalAxiosRequestConfig extends InternalAxiosRequestConfig {\n _mintSkipAuth?: boolean\n _mintAuthToken?: string\n}\n\ntype MutableHeaders = Record<string, unknown> & {\n has?: (header: string) => boolean\n set?: (header: string, value: string) => void\n delete?: (header: string) => void\n}\n\nfunction joinUrlPath(baseUrl: string, path: string): string {\n if (/^https?:\\/\\//i.test(path)) return path\n if (!path) return baseUrl\n if (path.startsWith('?') || path.startsWith('#')) return `${baseUrl.replace(/\\/+$/, '')}${path}`\n const normalizedBase = baseUrl.replace(/\\/+$/, '')\n const normalizedPath = path.replace(/^\\/+/, '/')\n return `${normalizedBase}${normalizedPath.startsWith('/') ? normalizedPath : `/${normalizedPath}`}`\n}\n\nfunction getBasePath(baseUrl: string): string {\n if (!baseUrl) return '/'\n\n try {\n const origin = typeof window !== 'undefined' ? window.location.origin : 'http://localhost'\n return new URL(baseUrl, origin).pathname.replace(/\\/+$/, '') || '/'\n } catch {\n const path = baseUrl.replace(/^https?:\\/\\/[^/]+/i, '')\n return path.replace(/\\/+$/, '') || '/'\n }\n}\n\nfunction normalizeRequestUrl(baseUrl: string, url: string): string {\n if (!url || /^https?:\\/\\//.test(url)) return url\n\n const basePath = getBasePath(baseUrl)\n if (basePath === '/') return url\n\n const normalizedUrl = url.startsWith('/') ? url : `/${url}`\n if (\n normalizedUrl === basePath\n || normalizedUrl.startsWith(`${basePath}?`)\n || normalizedUrl.startsWith(`${basePath}#`)\n ) {\n return normalizedUrl.slice(basePath.length)\n }\n if (normalizedUrl.startsWith(`${basePath}/`)) {\n return normalizedUrl.slice(basePath.length) || ''\n }\n return url\n}\n\nfunction asMutableHeaders(headers: AxiosRequestConfig['headers']): MutableHeaders | null {\n return headers ? (headers as MutableHeaders) : null\n}\n\nfunction hasAuthorizationHeader(headers: AxiosRequestConfig['headers']): boolean {\n const bag = asMutableHeaders(headers)\n if (!bag) return false\n if (typeof bag.has === 'function') return bag.has('Authorization')\n return Object.keys(bag).some((key) => key.toLowerCase() === 'authorization')\n}\n\nfunction setAuthorizationHeader(headers: AxiosRequestConfig['headers'], value: string): void {\n const bag = asMutableHeaders(headers)\n if (!bag) return\n if (typeof bag.set === 'function') {\n bag.set('Authorization', value)\n return\n }\n bag.Authorization = value\n}\n\nfunction deleteAuthorizationHeader(headers: AxiosRequestConfig['headers']): void {\n const bag = asMutableHeaders(headers)\n if (!bag) return\n if (typeof bag.delete === 'function') {\n bag.delete('Authorization')\n return\n }\n for (const key of Object.keys(bag)) {\n if (key.toLowerCase() === 'authorization') delete bag[key]\n }\n}\n\nfunction getAuthorizationHeader(headers: AxiosRequestConfig['headers']): unknown {\n const bag = asMutableHeaders(headers) as (MutableHeaders & { get?: (header: string) => unknown }) | null\n if (!bag) return undefined\n if (typeof bag.get === 'function') return bag.get('Authorization')\n const key = Object.keys(bag).find((candidate) => candidate.toLowerCase() === 'authorization')\n return key ? bag[key] : undefined\n}\n\n/**\n * Raised when an authenticated SDK request is rejected because the login is no\n * longer valid. It is also a `MintApiError` (status 401), so typed-error callers\n * and `instanceof AuthenticationRequiredError` callers both recognise it.\n */\nexport class AuthenticationRequiredError extends MintApiError {\n override readonly name: string = 'AuthenticationRequiredError'\n declare readonly code: 'AUTHENTICATION_REQUIRED'\n declare readonly status: 401\n\n constructor(cause?: unknown, envelope?: MintApiError) {\n super(\n 'Authentication required. Please sign in again.',\n 'AUTHENTICATION_REQUIRED',\n 401,\n envelope?.requestId ?? null,\n envelope?.details ?? null,\n envelope?.detail,\n envelope?.body ?? null,\n cause,\n )\n }\n}\n\n/** @internal Convert a rejected authenticated request into the SDK's public auth error. */\nexport function authenticationRequiredError(\n cause: unknown,\n rejectedToken?: string,\n envelope?: MintApiError,\n): AuthenticationRequiredError {\n const currentAuthStore = useAuthStore()\n if (rejectedToken && currentAuthStore.token === rejectedToken) {\n currentAuthStore.clearToken()\n }\n return new AuthenticationRequiredError(cause, envelope)\n}\n\nfunction getApiClient(): AxiosInstance {\n if (!apiClientInstance) {\n apiClientInstance = axios.create({\n headers: {\n 'Content-Type': 'application/json',\n },\n })\n }\n return apiClientInstance\n}\n\nexport interface ApiClientOptions {\n baseUrl?: string\n timeout?: number\n withAuth?: boolean\n}\n\nexport interface UseApiReturn {\n client: AxiosInstance\n get: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>\n post: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>\n put: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>\n patch: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>\n delete: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>\n upload: <T>(url: string, file: File, fieldName?: string, additionalData?: Record<string, unknown>) => Promise<T>\n download: (url: string, filename?: string) => Promise<string>\n buildUrl: (path: string) => string\n buildWsUrl: (path: string) => string\n}\n\n/** Axios-based API client that injects the plugin base URL and JWT auth header on every request. A failed response rejects with a `MintApiError`. */\nexport function useApi(options: ApiClientOptions = {}): UseApiReturn {\n const settingsStore = useSettingsStore()\n const authStore = useAuthStore()\n const apiClient = getApiClient()\n\n // Ensure auth store is initialized (reads token from localStorage)\n if (!authStore.isInitialized) {\n authStore.initialize()\n }\n\n // Attach auth interceptor only once (reads token dynamically, not from closure)\n if (!interceptorAttached) {\n apiClient.interceptors.request.use((config) => {\n const request = config as MintInternalAxiosRequestConfig\n if (request._mintSkipAuth) {\n deleteAuthorizationHeader(request.headers)\n return request\n }\n\n const currentAuthStore = useAuthStore()\n if (currentAuthStore.token && config.headers && !hasAuthorizationHeader(config.headers)) {\n setAuthorizationHeader(config.headers, `Bearer ${currentAuthStore.token}`)\n }\n if (getAuthorizationHeader(config.headers) === `Bearer ${currentAuthStore.token}`) {\n request._mintAuthToken = currentAuthStore.token ?? undefined\n }\n return config\n })\n apiClient.interceptors.response.use(\n (response) => response,\n async (error: unknown) => {\n if (axios.isAxiosError(error) && error.response) {\n const request = error.config as MintInternalAxiosRequestConfig | undefined\n let payload: unknown = error.response.data\n if (payload instanceof Blob) {\n try {\n payload = JSON.parse(await payload.text())\n } catch {\n // Keep non-JSON download errors available as the original body.\n }\n }\n const typed = mintApiErrorFromResponse(\n error.response.status,\n payload,\n {\n requestId: String(error.response.headers?.['x-request-id'] ?? '') || null,\n cause: error,\n },\n )\n if (error.response.status === 401 && request?._mintSkipAuth !== true) {\n return Promise.reject(authenticationRequiredError(error, request?._mintAuthToken, typed))\n }\n return Promise.reject(typed)\n }\n return Promise.reject(error)\n },\n )\n interceptorAttached = true\n }\n\n function getBaseUrl(): string {\n return options.baseUrl ?? settingsStore.getApiBaseUrl()\n }\n\n function normalizeUrl(url: string): string {\n return normalizeRequestUrl(getBaseUrl(), url)\n }\n\n // Build per-request config that applies this caller's options\n function requestConfig(config?: AxiosRequestConfig): MintAxiosRequestConfig {\n const base: MintAxiosRequestConfig = {\n baseURL: getBaseUrl(),\n timeout: options.timeout ?? settingsStore.requestTimeout,\n ...config,\n }\n // Strip auth header if explicitly disabled\n if (options.withAuth === false) {\n base._mintSkipAuth = true\n deleteAuthorizationHeader(base.headers)\n }\n return base\n }\n\n // Generic request methods\n async function get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.get<T>(normalizeUrl(url), requestConfig(config))\n return response.data\n }\n\n async function post<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.post<T>(normalizeUrl(url), data, requestConfig(config))\n return response.data\n }\n\n async function put<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.put<T>(normalizeUrl(url), data, requestConfig(config))\n return response.data\n }\n\n async function patch<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.patch<T>(normalizeUrl(url), data, requestConfig(config))\n return response.data\n }\n\n async function del<T>(url: string, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.delete<T>(normalizeUrl(url), requestConfig(config))\n return response.data\n }\n\n // File upload helper\n async function upload<T>(url: string, file: File, fieldName = 'file', additionalData?: Record<string, unknown>): Promise<T> {\n const formData = new FormData()\n formData.append(fieldName, file)\n\n if (additionalData) {\n Object.entries(additionalData).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n formData.append(key, typeof value === 'object' ? JSON.stringify(value) : String(value))\n }\n })\n }\n\n const response = await apiClient.post<T>(normalizeUrl(url), formData, requestConfig({\n // Let Axios set Content-Type with the correct multipart boundary\n headers: { 'Content-Type': undefined },\n }))\n return response.data\n }\n\n // Download helper - returns blob URL\n async function download(url: string, filename?: string): Promise<string> {\n const response = await apiClient.get(normalizeUrl(url), requestConfig({ responseType: 'blob' }))\n const blob = new Blob([response.data])\n const blobUrl = URL.createObjectURL(blob)\n\n // Optionally trigger download\n if (filename) {\n const link = document.createElement('a')\n link.href = blobUrl\n link.download = filename\n document.body.appendChild(link)\n link.click()\n document.body.removeChild(link)\n // Revoke after a short delay to allow download to start;\n // return empty string since the URL will be invalidated\n setTimeout(() => URL.revokeObjectURL(blobUrl), 100)\n return ''\n }\n\n // Caller is responsible for revoking when no filename is provided\n return blobUrl\n }\n\n // Build full URL for external use (e.g., <a href=\"...\">)\n function buildUrl(path: string): string {\n const baseUrl = getBaseUrl()\n return joinUrlPath(baseUrl, normalizeRequestUrl(baseUrl, path))\n }\n\n // WebSocket URL builder\n function buildWsUrl(path: string): string {\n return joinUrlPath(settingsStore.getWsBaseUrl(), path)\n }\n\n return {\n client: apiClient,\n get,\n post,\n put,\n patch,\n delete: del,\n upload,\n download,\n buildUrl,\n buildWsUrl,\n }\n}\n","/**\n * The one place a thrown value is turned into a developer-facing message.\n *\n * Five copies of this logic drifted apart across the composables and stores\n * (some checked a bare `{ message }` object, some did not); this export is the\n * union of what they all accepted, so every caller now reads the same message\n * from the same value.\n */\nexport function readErrorMessage(value: unknown, fallback = 'Request failed.'): string {\n if (value instanceof Error && value.message) {\n return value.message\n }\n\n if (typeof value === 'string' && value.trim()) {\n return value\n }\n\n if (\n typeof value === 'object'\n && value !== null\n && 'message' in value\n && typeof (value as { message?: unknown }).message === 'string'\n && (value as { message: string }).message.trim()\n ) {\n return (value as { message: string }).message\n }\n\n return fallback\n}\n","// The platform integration script (D7-B) sets `window.__MINT_INTEGRATION__` in\n// every plugin page the platform serves. SDK code calls into it and never\n// imports platform code; standalone plugins have no host.\n\n/** What the SDK experiment store gives the platform integration script. */\nexport interface ExperimentStoreBinding {\n /** The experiment id the store holds, or `null`. */\n currentId(): number | null\n /** Resolve the experiment from the platform and make it the current one. */\n selectById(id: number): Promise<unknown>\n /** Call `listener` with the new id each time the current experiment changes. */\n onChange(listener: (id: number | null) => void): void\n}\n\n/** The platform side of the plugin page integration (`window.__MINT_INTEGRATION__`). */\nexport interface MintIntegrationHost {\n /** Connect the experiment store: the host writes the experiment in the URL into it and writes its changes back to the URL. */\n bindExperimentStore(binding: ExperimentStoreBinding): void\n /** Render the platform page selector into `element`. Returns the function that removes it. */\n mountPageSelector(element: HTMLElement): () => void\n}\n\n/** The integration host of this page, or `undefined` outside a platform-served plugin page. */\nexport function integrationHost(): MintIntegrationHost | undefined {\n if (typeof window === 'undefined') return undefined\n return (window as Window & { __MINT_INTEGRATION__?: MintIntegrationHost }).__MINT_INTEGRATION__\n}\n","import { defineStore } from 'pinia'\nimport { ref, watch } from 'vue'\nimport { useApi } from '../composables/useApi'\nimport { readErrorMessage } from '../utils/errors'\nimport { integrationHost } from '../utils/integrationHost'\nimport type { ExperimentSummary, PlatformContext } from '../types'\n\nfunction getPlatformApiUrl(): string | undefined {\n if (typeof window === 'undefined') return undefined\n return (window as unknown as { __MINT_PLATFORM__?: PlatformContext }).__MINT_PLATFORM__\n ?.platformApiUrl\n}\n\n/**\n * The single source of truth for the experiment a plugin is currently working on.\n *\n * Both entry points land here: `ExperimentSelectorModal` calls `select()` with a\n * record the platform already returned, and deep links call `selectById()` with\n * nothing but an id. Because a name can only ever arrive from the platform,\n * there is no code path in which a caller can invent one.\n *\n * In a platform-served plugin page the platform integration script keeps the\n * store and the `experiment_id` URL parameter in step.\n */\nexport const useExperimentStore = defineStore('mint-experiment', () => {\n const current = ref<ExperimentSummary | null>(null)\n const currentId = ref<number | null>(null)\n const isResolving = ref(false)\n const error = ref<string | null>(null)\n\n // In-flight requests only. Resolved records are deliberately not memoised, so\n // a rename is picked up on the next resolve instead of persisting for the\n // lifetime of the page.\n const inFlight = new Map<number, Promise<ExperimentSummary>>()\n\n // Monotonic token guarding every write to the shared selection. Responses can\n // land in any order, and a late one must never overwrite a newer choice —\n // that would display a name belonging to a different experiment, which is the\n // failure this store exists to prevent.\n let selectionToken = 0\n\n function fetchExperiment(id: number): Promise<ExperimentSummary> {\n const pending = inFlight.get(id)\n if (pending) return pending\n\n const base = getPlatformApiUrl() ?? ''\n const request = useApi()\n .get<ExperimentSummary>(`${base}/experiments/${id}`)\n .finally(() => {\n inFlight.delete(id)\n })\n\n inFlight.set(id, request)\n return request\n }\n\n /** Adopt a record the platform already returned, without a further request. */\n function select(experiment: ExperimentSummary): void {\n selectionToken += 1\n current.value = experiment\n currentId.value = experiment.id\n error.value = null\n isResolving.value = false\n }\n\n /**\n * Bind to an experiment known only by id, resolving its record from the\n * platform. On failure the binding is remembered but `current` stays null, so\n * callers can distinguish \"not resolved yet\" from \"nothing selected\".\n */\n async function selectById(id: number): Promise<ExperimentSummary | null> {\n const joinsCurrentResolution = isResolving.value && currentId.value === id\n const token = joinsCurrentResolution ? selectionToken : (selectionToken += 1)\n if (!joinsCurrentResolution) {\n current.value = null\n currentId.value = id\n error.value = null\n isResolving.value = true\n }\n try {\n const experiment = await fetchExperiment(id)\n // A newer selection superseded this one while the request was in flight.\n // Returning null tells the caller its result is no longer the selection,\n // so it cannot be applied downstream either.\n if (token !== selectionToken) return null\n current.value = experiment\n return experiment\n } catch (err) {\n if (token !== selectionToken) return null\n current.value = null\n error.value = readErrorMessage(err, 'Failed to load experiment')\n return null\n } finally {\n // Only the newest request owns the loading flag; otherwise a superseded\n // response would clear it while the current one is still pending.\n if (token === selectionToken) isResolving.value = false\n }\n }\n\n /** Read the selected experiment again. The selection and its record stay in place. */\n async function refresh(): Promise<void> {\n const id = currentId.value\n if (id === null) return\n const token = selectionToken\n try {\n const experiment = await fetchExperiment(id)\n if (token !== selectionToken) return\n current.value = experiment\n error.value = null\n } catch (err) {\n if (token === selectionToken) error.value = readErrorMessage(err, 'Failed to load experiment')\n }\n }\n\n function clear(): void {\n // Supersede any in-flight resolve so a detached experiment cannot be\n // repopulated by a response that was already on its way.\n selectionToken += 1\n current.value = null\n currentId.value = null\n error.value = null\n isResolving.value = false\n }\n\n integrationHost()?.bindExperimentStore({\n currentId: () => currentId.value,\n selectById,\n onChange: (listener) => { watch(currentId, listener) },\n })\n\n return {\n current,\n currentId,\n isResolving,\n error,\n select,\n selectById,\n refresh,\n clear,\n }\n})\n"],"mappings":"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"}
|