@burdenoff/fluidgrids-fe-sdk 2026.1111.15 → 2026.1111.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -0
- package/dist/AiStudioPage-kJHSsIcj.js +774 -0
- package/dist/ApprovalInboxPage-dbBDSJ-f.js +558 -0
- package/dist/DataSinkDashboardPage-1h2QsMzH.js +270 -0
- package/dist/DataTablesPage-BZy2z0LB.js +1690 -0
- package/dist/EntityTagPicker-BqRNsLCQ.js +135 -0
- package/dist/{ExecutionDetailPage-DO38vZ_F.js → ExecutionDetailPage-BdcZmLT4.js} +799 -4420
- package/dist/ExecutionDetailPage-DvQ667OE.d.ts +10 -0
- package/dist/ExecutionsListPage-BCUJoAIE.js +2742 -0
- package/dist/{FailureTriagePanel-BHiduL7A.js → FailureTriagePanel-B9hDQymi.js} +4 -97
- package/dist/{FluidGridsProvider-B9RR_CI_.js → FluidGridsProvider-dvZSNtSW.js} +28 -350
- package/dist/HistoryPage-BLoRpJ1R.d.ts +6 -0
- package/dist/{HistoryPage-CA42pU85.js → HistoryPage-U0tqupBN.js} +6 -4
- package/dist/HumanRequestCard-D-ooVPDn.js +439 -0
- package/dist/{InsightsPage-CBoN_Mj9.js → InsightsPage-CALo16eB.js} +17 -328
- package/dist/ManagedAiPolicyAuthority-BxwJ3ptd.js +40 -0
- package/dist/{ManagedAiPolicyAuthority-CGux_DaV.d.ts → ManagedAiPolicyAuthority-DM3t3wNr.d.ts} +1 -5
- package/dist/ManagedAiPolicyAuthorityContext-CNkxIOZ1.js +10 -0
- package/dist/{MissingNodesInstallModal-Dm0nMBXM.js → MissingNodesInstallModal-CMybsI8h.js} +3 -2
- package/dist/{NodesPage-ClvhUfbh.js → NodesPage-Bf5PhfBK.js} +7 -3
- package/dist/NodesPage-DEbnz9iU.d.ts +6 -0
- package/dist/{PublishCustomNodeAction-D2F7oN0S.js → PublishCustomNodeAction-5XXnSA3H.js} +3 -45
- package/dist/QuotaExhaustedDialog-7IFSaLyi.js +54 -0
- package/dist/RunnerStatusPill-Ctg4TJcP.js +187 -0
- package/dist/RunnersPage-CLCOCclc.js +467 -0
- package/dist/{ManagedAiPolicyAuthority-CKihmbJE.js → SettingsPage-CgkRlCM5.js} +19 -50
- package/dist/SettingsPage-DMHQFj5-.d.ts +9 -0
- package/dist/TagFilterSelect-DCBOH6Ou.js +41 -0
- package/dist/TaskQueuesPage-9VgSnLdL.js +951 -0
- package/dist/TemplateCard-DsC0KNH1.js +1416 -0
- package/dist/{TemplateConfigurationModal-Bs_V-E4u.js → TemplateConfigurationModal-BhGWIzpm.js} +3 -2
- package/dist/{TemplateDetailPanel-MlT39VLG.js → TemplateDetailPanel-D4cUVr-m.js} +3 -3
- package/dist/TemplatePreview-GthzCOSo.js +231 -0
- package/dist/TemplatesPage-BG3H-6v6.d.ts +10 -0
- package/dist/{TemplatesPage-CHVpWBuw.js → TemplatesPage-CsDNT54N.js} +11 -228
- package/dist/VariablesPage-67WR_lVE.js +949 -0
- package/dist/{VersionDiffDialog-Dq2xlPFI.js → VersionDiffDialog-DSN0mU0f.js} +2 -1
- package/dist/WorkflowBuilder-DEi_9YOb.js +555 -0
- package/dist/{WorkflowSearchRedirect-3gyHcYY2.js → WorkflowBuilder-iPqXgbYr.js} +19 -585
- package/dist/{WorkflowProposalCard-8Vr56kQa.js → WorkflowProposalCard-qrEeNyYG.js} +4 -405
- package/dist/WorkflowSearchRedirect-DwDNZ_q5.js +36 -0
- package/dist/{WorkflowShareDialog-0Ai1HED-.js → WorkflowShareDialog-B_gn968c.js} +6 -52
- package/dist/{WorkflowsList-CJ3KmMHX.js → WorkflowsList-CA20dVIl.js} +11 -6
- package/dist/WorkflowsList-CcNDQge5.d.ts +6 -0
- package/dist/agent-form-Bfvm32j1.js +83 -0
- package/dist/{agentTools-B1IK1JEO.js → agentTools-CkRJB7r5.js} +3 -2
- package/dist/{ai-usage-D5p7ZeRR.js → ai-usage-glCwHyQz.js} +1 -1
- package/dist/api-BrCHB5oA.js +224 -0
- package/dist/api-C8e0iV-Q.js +55 -0
- package/dist/button-DG7y_FVJ.js +48 -0
- package/dist/{canvasNoteTools-eewVHVuy.js → canvasNoteTools-CQIc7Oxg.js} +2 -2
- package/dist/client-C9XSzSXr.js +1147 -0
- package/dist/{codec-client-DstqyzTN.js → codec-client-CJ95Rmqo.js} +1 -1
- package/dist/constants-B2lhWNoN.js +202 -0
- package/dist/constants-XdvUxeWK.d.ts +162 -0
- package/dist/dialog-Cjb4lB4D.js +105 -0
- package/dist/executions.d.ts +1 -1
- package/dist/executions.js +2 -1
- package/dist/feature-flags-DM5myWaD.js +296 -0
- package/dist/flags.d.ts +3 -0
- package/dist/flags.js +4 -0
- package/dist/{flow-builder-C_3sY3pZ.js → flow-builder-y2Y1iG4Y.js} +182 -1821
- package/dist/{node-endpoint-configs-DHFGA5MQ.js → graphql-operation-types-kX-MMjgW.js} +1 -1194
- package/dist/{graphql-types-XcDLtFwU.js → graphql-types-CDKVWH2R.js} +2 -202
- package/dist/history.d.ts +1 -1
- package/dist/history.js +1 -1
- package/dist/index-72XFgvbn.d.ts +132 -0
- package/dist/index-BcdtKBM-.d.ts +56 -0
- package/dist/{index-B3BWqafN.d.ts → index-DjiFgEQl.d.ts} +5 -131
- package/dist/{index-DRf25-D3.d.ts → index-W2o__Nm6.d.ts} +32 -214
- package/dist/index.d.ts +13 -9
- package/dist/index.js +50 -5300
- package/dist/{node-detail-view-CAws5qjg.js → node-detail-view-B91kXzIy.js} +11 -8
- package/dist/node-endpoint-configs-B5bjNMDW.js +37 -0
- package/dist/node-registry-refresh-Dy0uRg9f.js +84 -0
- package/dist/nodes.d.ts +1 -1
- package/dist/nodes.js +1 -1
- package/dist/observability.d.ts +1 -1
- package/dist/observability.js +2 -1
- package/dist/{quick-config-panel-DOzNkSVV.js → quick-config-panel-DggPl1vY.js} +2 -2
- package/dist/rolldown-runtime-hDbaFkMy.js +18 -0
- package/dist/run-display-BycKYSU0.js +46 -0
- package/dist/run-list-links-ByYC0OlV.js +98 -0
- package/dist/settings.d.ts +2 -1
- package/dist/settings.js +2 -1
- package/dist/shared-CMfn30xT.js +196 -0
- package/dist/templates.d.ts +1 -1
- package/dist/templates.js +2 -1
- package/dist/theme-provider-B3mIuz2d.js +11 -0
- package/dist/{useAgentDefinitions-NFtLytDR.js → useAgentDefinitions-Dpz0HDTq.js} +2 -1
- package/dist/useDurableParity-CzUQMFJ3.js +415 -0
- package/dist/useEntityTags-DhnsZcTl.js +236 -0
- package/dist/{useExecutions-DkS0FTfw.js → useExecutions-9aLuOt1c.js} +4 -3
- package/dist/{useObservability-BkxhHZsX.js → useObservability-CfEw3ZFD.js} +2 -2
- package/dist/{useRunHistory-DHehG09Z.js → useRunHistory-B1xJDwl1.js} +4 -3
- package/dist/{useWorkflows-DyhtoVwp.js → useWorkflows-BlVVu8NM.js} +3 -2
- package/dist/utils-uaL5UVxb.js +65 -0
- package/dist/workflow-builder.d.ts +1 -1
- package/dist/workflow-builder.js +4 -2
- package/dist/workflow-host.d.ts +6 -0
- package/dist/workflow-host.js +9 -0
- package/dist/workflow-pages.d.ts +55 -0
- package/dist/workflow-pages.js +113 -0
- package/dist/workflows-list.d.ts +1 -1
- package/dist/workflows-list.js +1 -1
- package/package.json +13 -1
- package/dist/ExecutionDetailPage-MvsnOnE1.d.ts +0 -4
- package/dist/HistoryPage-BqUizaFv.d.ts +0 -3
- package/dist/NodesPage-C97-9K2Y.d.ts +0 -3
- package/dist/TemplatesPage-D0qnXzHh.d.ts +0 -4
- package/dist/WorkflowsList-0d5GSKdh.d.ts +0 -3
- package/dist/agent-form-BvMWhQ42.js +0 -496
|
@@ -0,0 +1,1147 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { t as __exportAll } from "./rolldown-runtime-hDbaFkMy.js";
|
|
3
|
+
import { ApolloClient, ApolloLink, InMemoryCache, Observable, createHttpLink, split } from "@apollo/client/core";
|
|
4
|
+
import { setContext } from "@apollo/client/link/context";
|
|
5
|
+
import { getMainDefinition } from "@apollo/client/utilities";
|
|
6
|
+
import { GraphQLWsLink } from "@apollo/client/link/subscriptions";
|
|
7
|
+
import { createClient } from "graphql-ws";
|
|
8
|
+
import { create } from "zustand";
|
|
9
|
+
import { CombinedGraphQLErrors, ServerError } from "@apollo/client/errors";
|
|
10
|
+
|
|
11
|
+
// src/modules/executions/store/executionsStore.ts
|
|
12
|
+
const DEFAULT_EXECUTIONS_FILTER = {
|
|
13
|
+
status: "all",
|
|
14
|
+
workflowKey: "",
|
|
15
|
+
dateRange: {
|
|
16
|
+
start: null,
|
|
17
|
+
end: null,
|
|
18
|
+
preset: "all"
|
|
19
|
+
},
|
|
20
|
+
search: "",
|
|
21
|
+
triggerType: "all",
|
|
22
|
+
durationMinSeconds: null,
|
|
23
|
+
durationMaxSeconds: null,
|
|
24
|
+
errorType: "all",
|
|
25
|
+
nodeId: "",
|
|
26
|
+
failedNodeType: "",
|
|
27
|
+
tagId: "",
|
|
28
|
+
workflowId: ""
|
|
29
|
+
};
|
|
30
|
+
const defaultPagination = {
|
|
31
|
+
page: 1,
|
|
32
|
+
pageSize: 25,
|
|
33
|
+
total: 0
|
|
34
|
+
};
|
|
35
|
+
const useExecutionsStore = create((set) => ({
|
|
36
|
+
viewMode: "table",
|
|
37
|
+
selectedExecution: null,
|
|
38
|
+
expandedRows: /* @__PURE__ */ new Set(),
|
|
39
|
+
selection: {
|
|
40
|
+
runIds: [],
|
|
41
|
+
allSelected: false
|
|
42
|
+
},
|
|
43
|
+
filter: DEFAULT_EXECUTIONS_FILTER,
|
|
44
|
+
pagination: defaultPagination,
|
|
45
|
+
isFilterPanelOpen: false,
|
|
46
|
+
isDetailsPanelOpen: false,
|
|
47
|
+
autoRefresh: true,
|
|
48
|
+
autoRefreshInterval: 5e3,
|
|
49
|
+
liveRunIds: /* @__PURE__ */ new Set(),
|
|
50
|
+
setViewMode: (mode) => set({ viewMode: mode }),
|
|
51
|
+
setSelectedExecution: (execution) => set({
|
|
52
|
+
selectedExecution: execution,
|
|
53
|
+
isDetailsPanelOpen: execution !== null
|
|
54
|
+
}),
|
|
55
|
+
toggleRowExpanded: (runId) => set((state) => {
|
|
56
|
+
const newExpanded = new Set(state.expandedRows);
|
|
57
|
+
if (newExpanded.has(runId)) newExpanded.delete(runId);
|
|
58
|
+
else newExpanded.add(runId);
|
|
59
|
+
return { expandedRows: newExpanded };
|
|
60
|
+
}),
|
|
61
|
+
setSelection: (selection) => set({ selection }),
|
|
62
|
+
toggleSelectRun: (runId) => set((state) => {
|
|
63
|
+
return { selection: {
|
|
64
|
+
runIds: state.selection.runIds.includes(runId) ? state.selection.runIds.filter((id) => id !== runId) : [...state.selection.runIds, runId],
|
|
65
|
+
allSelected: false
|
|
66
|
+
} };
|
|
67
|
+
}),
|
|
68
|
+
selectAll: (runIds) => set((state) => ({ selection: {
|
|
69
|
+
runIds: state.selection.allSelected ? [] : runIds,
|
|
70
|
+
allSelected: !state.selection.allSelected
|
|
71
|
+
} })),
|
|
72
|
+
clearSelection: () => set({ selection: {
|
|
73
|
+
runIds: [],
|
|
74
|
+
allSelected: false
|
|
75
|
+
} }),
|
|
76
|
+
updateFilter: (filterUpdate) => set((state) => ({
|
|
77
|
+
filter: {
|
|
78
|
+
...state.filter,
|
|
79
|
+
...filterUpdate
|
|
80
|
+
},
|
|
81
|
+
pagination: {
|
|
82
|
+
...state.pagination,
|
|
83
|
+
page: 1
|
|
84
|
+
}
|
|
85
|
+
})),
|
|
86
|
+
setStatusFilter: (status) => set((state) => ({
|
|
87
|
+
filter: {
|
|
88
|
+
...state.filter,
|
|
89
|
+
status
|
|
90
|
+
},
|
|
91
|
+
pagination: {
|
|
92
|
+
...state.pagination,
|
|
93
|
+
page: 1
|
|
94
|
+
}
|
|
95
|
+
})),
|
|
96
|
+
setWorkflowFilter: (workflowKey) => set((state) => ({
|
|
97
|
+
filter: {
|
|
98
|
+
...state.filter,
|
|
99
|
+
workflowKey
|
|
100
|
+
},
|
|
101
|
+
pagination: {
|
|
102
|
+
...state.pagination,
|
|
103
|
+
page: 1
|
|
104
|
+
}
|
|
105
|
+
})),
|
|
106
|
+
setDateRange: (dateRange) => set((state) => ({
|
|
107
|
+
filter: {
|
|
108
|
+
...state.filter,
|
|
109
|
+
dateRange
|
|
110
|
+
},
|
|
111
|
+
pagination: {
|
|
112
|
+
...state.pagination,
|
|
113
|
+
page: 1
|
|
114
|
+
}
|
|
115
|
+
})),
|
|
116
|
+
setSearchQuery: (search) => set((state) => ({
|
|
117
|
+
filter: {
|
|
118
|
+
...state.filter,
|
|
119
|
+
search
|
|
120
|
+
},
|
|
121
|
+
pagination: {
|
|
122
|
+
...state.pagination,
|
|
123
|
+
page: 1
|
|
124
|
+
}
|
|
125
|
+
})),
|
|
126
|
+
setTriggerTypeFilter: (triggerType) => set((state) => ({
|
|
127
|
+
filter: {
|
|
128
|
+
...state.filter,
|
|
129
|
+
triggerType
|
|
130
|
+
},
|
|
131
|
+
pagination: {
|
|
132
|
+
...state.pagination,
|
|
133
|
+
page: 1
|
|
134
|
+
}
|
|
135
|
+
})),
|
|
136
|
+
setDurationFilter: (durationMinSeconds, durationMaxSeconds) => set((state) => ({
|
|
137
|
+
filter: {
|
|
138
|
+
...state.filter,
|
|
139
|
+
durationMinSeconds,
|
|
140
|
+
durationMaxSeconds
|
|
141
|
+
},
|
|
142
|
+
pagination: {
|
|
143
|
+
...state.pagination,
|
|
144
|
+
page: 1
|
|
145
|
+
}
|
|
146
|
+
})),
|
|
147
|
+
setErrorTypeFilter: (errorType) => set((state) => ({
|
|
148
|
+
filter: {
|
|
149
|
+
...state.filter,
|
|
150
|
+
errorType
|
|
151
|
+
},
|
|
152
|
+
pagination: {
|
|
153
|
+
...state.pagination,
|
|
154
|
+
page: 1
|
|
155
|
+
}
|
|
156
|
+
})),
|
|
157
|
+
setNodeFilter: (nodeId) => set((state) => ({
|
|
158
|
+
filter: {
|
|
159
|
+
...state.filter,
|
|
160
|
+
nodeId
|
|
161
|
+
},
|
|
162
|
+
pagination: {
|
|
163
|
+
...state.pagination,
|
|
164
|
+
page: 1
|
|
165
|
+
}
|
|
166
|
+
})),
|
|
167
|
+
setFailedNodeTypeFilter: (failedNodeType) => set((state) => ({
|
|
168
|
+
filter: {
|
|
169
|
+
...state.filter,
|
|
170
|
+
failedNodeType
|
|
171
|
+
},
|
|
172
|
+
pagination: {
|
|
173
|
+
...state.pagination,
|
|
174
|
+
page: 1
|
|
175
|
+
}
|
|
176
|
+
})),
|
|
177
|
+
setTagFilter: (tagId) => set((state) => ({
|
|
178
|
+
filter: {
|
|
179
|
+
...state.filter,
|
|
180
|
+
tagId
|
|
181
|
+
},
|
|
182
|
+
pagination: {
|
|
183
|
+
...state.pagination,
|
|
184
|
+
page: 1
|
|
185
|
+
}
|
|
186
|
+
})),
|
|
187
|
+
setWorkflowIdFilter: (workflowId) => set((state) => ({
|
|
188
|
+
filter: {
|
|
189
|
+
...state.filter,
|
|
190
|
+
workflowId
|
|
191
|
+
},
|
|
192
|
+
pagination: {
|
|
193
|
+
...state.pagination,
|
|
194
|
+
page: 1
|
|
195
|
+
}
|
|
196
|
+
})),
|
|
197
|
+
resetFilters: () => set({
|
|
198
|
+
filter: DEFAULT_EXECUTIONS_FILTER,
|
|
199
|
+
pagination: { ...defaultPagination }
|
|
200
|
+
}),
|
|
201
|
+
setPage: (page) => set((state) => ({ pagination: {
|
|
202
|
+
...state.pagination,
|
|
203
|
+
page
|
|
204
|
+
} })),
|
|
205
|
+
setPageSize: (pageSize) => set((state) => ({ pagination: {
|
|
206
|
+
...state.pagination,
|
|
207
|
+
pageSize,
|
|
208
|
+
page: 1
|
|
209
|
+
} })),
|
|
210
|
+
setTotal: (total) => set((state) => ({ pagination: {
|
|
211
|
+
...state.pagination,
|
|
212
|
+
total
|
|
213
|
+
} })),
|
|
214
|
+
toggleFilterPanel: () => set((state) => ({ isFilterPanelOpen: !state.isFilterPanelOpen })),
|
|
215
|
+
toggleDetailsPanel: () => set((state) => ({ isDetailsPanelOpen: !state.isDetailsPanelOpen })),
|
|
216
|
+
setAutoRefresh: (enabled) => set({ autoRefresh: enabled }),
|
|
217
|
+
setAutoRefreshInterval: (interval) => set({ autoRefreshInterval: interval }),
|
|
218
|
+
addLiveRun: (runId) => set((state) => {
|
|
219
|
+
const newLiveRunIds = new Set(state.liveRunIds);
|
|
220
|
+
newLiveRunIds.add(runId);
|
|
221
|
+
return { liveRunIds: newLiveRunIds };
|
|
222
|
+
}),
|
|
223
|
+
removeLiveRun: (runId) => set((state) => {
|
|
224
|
+
const newLiveRunIds = new Set(state.liveRunIds);
|
|
225
|
+
newLiveRunIds.delete(runId);
|
|
226
|
+
return { liveRunIds: newLiveRunIds };
|
|
227
|
+
}),
|
|
228
|
+
clearLiveRuns: () => set({ liveRunIds: /* @__PURE__ */ new Set() })
|
|
229
|
+
}));
|
|
230
|
+
|
|
231
|
+
// src/theme/fluidgridsTheme.ts
|
|
232
|
+
const color = (...cssVariables) => ({
|
|
233
|
+
kind: "color",
|
|
234
|
+
cssVariables
|
|
235
|
+
});
|
|
236
|
+
const length = (...cssVariables) => ({
|
|
237
|
+
kind: "length",
|
|
238
|
+
cssVariables
|
|
239
|
+
});
|
|
240
|
+
const font = (...cssVariables) => ({
|
|
241
|
+
kind: "font",
|
|
242
|
+
cssVariables
|
|
243
|
+
});
|
|
244
|
+
/**
|
|
245
|
+
* Theme token → the fe-libs semantic CSS variables it sets. This table is the
|
|
246
|
+
* public contract: a token keeps its variables across minor releases.
|
|
247
|
+
*/
|
|
248
|
+
const FLUIDGRIDS_THEME_TOKEN_MAP = {
|
|
249
|
+
"colors.bgCanvas": color("--color-bg-canvas"),
|
|
250
|
+
"colors.bgSurface": color("--color-bg-surface"),
|
|
251
|
+
"colors.bgElevated": color("--color-bg-elevated"),
|
|
252
|
+
"colors.bgSunken": color("--color-bg-sunken"),
|
|
253
|
+
"colors.textPrimary": color("--color-text-primary"),
|
|
254
|
+
"colors.textSecondary": color("--color-text-secondary"),
|
|
255
|
+
"colors.textMuted": color("--color-text-muted"),
|
|
256
|
+
"colors.borderDefault": color("--color-border-default"),
|
|
257
|
+
"colors.borderSubtle": color("--color-border-subtle"),
|
|
258
|
+
"colors.borderStrong": color("--color-border-strong"),
|
|
259
|
+
"colors.primary": color("--color-action-primary-bg", "--color-action-primary-border"),
|
|
260
|
+
"colors.primaryHover": color("--color-action-primary-bgHover"),
|
|
261
|
+
"colors.primaryActive": color("--color-action-primary-bgActive"),
|
|
262
|
+
"colors.primaryText": color("--color-action-primary-text"),
|
|
263
|
+
"colors.success": color("--color-status-success-bg"),
|
|
264
|
+
"colors.warning": color("--color-status-warning-bg"),
|
|
265
|
+
"colors.error": color("--color-status-error-bg"),
|
|
266
|
+
"colors.info": color("--color-status-info-bg"),
|
|
267
|
+
"radius.base": length("--radius"),
|
|
268
|
+
"radius.card": length("--radius-card"),
|
|
269
|
+
"radius.button": length("--radius-button"),
|
|
270
|
+
"radius.input": length("--radius-input"),
|
|
271
|
+
"font.body": font("--font-family-body", "--font-sans"),
|
|
272
|
+
"font.heading": font("--font-family-heading"),
|
|
273
|
+
"font.mono": font("--font-family-mono", "--font-mono")
|
|
274
|
+
};
|
|
275
|
+
const MAX_VALUE_LENGTH = 200;
|
|
276
|
+
const FORBIDDEN_VALUE = /[;{}<>\\!@"`]|\/\*|\*\/|url\s*\(|expression\s*\(|image\s*\(|image-set\s*\(|javascript:|@import/i;
|
|
277
|
+
const HEX_COLOR = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
|
|
278
|
+
const NAMED_COLOR = /^[a-z]{3,30}$/i;
|
|
279
|
+
const COLOR_FUNCTION = /^(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color|color-mix)\([a-z0-9#%.,/\s()+-]*\)$/i;
|
|
280
|
+
const CSS_VAR = /^var\(--[a-z0-9-]+\)$/i;
|
|
281
|
+
const LENGTH = /^(?:0|\d*\.?\d+(?:px|rem|em|%|vh|vw|vmin|vmax|ch|ex|pt))$/i;
|
|
282
|
+
const CALC = /^calc\([a-z0-9%.\s()*/+-]*\)$/i;
|
|
283
|
+
const FONT_FAMILY_ITEM = /^(?:[a-z0-9][a-z0-9 _-]*|'[a-z0-9 _-]+')$/i;
|
|
284
|
+
function balancedParentheses(value) {
|
|
285
|
+
let depth = 0;
|
|
286
|
+
for (const char of value) {
|
|
287
|
+
if (char === "(") depth += 1;
|
|
288
|
+
if (char === ")") depth -= 1;
|
|
289
|
+
if (depth < 0) return false;
|
|
290
|
+
}
|
|
291
|
+
return depth === 0;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Validate one token value. Returns the trimmed value, or null when it is not
|
|
295
|
+
* a plain CSS value of the expected kind.
|
|
296
|
+
*/
|
|
297
|
+
function sanitizeThemeValue(kind, value) {
|
|
298
|
+
if (typeof value !== "string") return null;
|
|
299
|
+
const trimmed = value.trim();
|
|
300
|
+
if (!trimmed || trimmed.length > MAX_VALUE_LENGTH) return null;
|
|
301
|
+
if (FORBIDDEN_VALUE.test(trimmed) || !balancedParentheses(trimmed)) return null;
|
|
302
|
+
if (CSS_VAR.test(trimmed)) return trimmed;
|
|
303
|
+
switch (kind) {
|
|
304
|
+
case "color": return HEX_COLOR.test(trimmed) || NAMED_COLOR.test(trimmed) || COLOR_FUNCTION.test(trimmed) ? trimmed : null;
|
|
305
|
+
case "length": return LENGTH.test(trimmed) || CALC.test(trimmed) ? trimmed : null;
|
|
306
|
+
case "font": {
|
|
307
|
+
const items = trimmed.split(",").map((item) => item.trim());
|
|
308
|
+
return items.every((item) => FONT_FAMILY_ITEM.test(item)) ? items.join(", ") : null;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function warnDropped(key, value) {
|
|
313
|
+
console.warn(`[FluidGrids SDK] theme token "${key}" ignored: ${JSON.stringify(value)} is not a plain CSS value.`);
|
|
314
|
+
}
|
|
315
|
+
/** Relative luminance of a hex colour, or null for any other notation. */
|
|
316
|
+
function hexLuminance(value) {
|
|
317
|
+
if (!HEX_COLOR.test(value)) return null;
|
|
318
|
+
let hex = value.slice(1);
|
|
319
|
+
if (hex.length <= 4) hex = hex.split("").map((digit) => digit + digit).join("");
|
|
320
|
+
const [red = 0, green = 0, blue = 0] = [
|
|
321
|
+
0,
|
|
322
|
+
2,
|
|
323
|
+
4
|
|
324
|
+
].map((offset) => {
|
|
325
|
+
const channel = parseInt(hex.slice(offset, offset + 2), 16) / 255;
|
|
326
|
+
return channel <= .03928 ? channel / 12.92 : Math.pow((channel + .055) / 1.055, 2.4);
|
|
327
|
+
});
|
|
328
|
+
return .2126 * red + .7152 * green + .0722 * blue;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* The resolved mode of a theme: the shortcut itself, `theme.mode`, or
|
|
332
|
+
* undefined when the theme leaves the mode to the host.
|
|
333
|
+
*/
|
|
334
|
+
function themeMode(theme) {
|
|
335
|
+
if (theme === "light" || theme === "dark") return theme;
|
|
336
|
+
if (theme && typeof theme === "object") return theme.mode === "light" || theme.mode === "dark" ? theme.mode : void 0;
|
|
337
|
+
}
|
|
338
|
+
function groupEntries(theme, group) {
|
|
339
|
+
const values = theme[group];
|
|
340
|
+
if (!values || typeof values !== "object") return [];
|
|
341
|
+
return Object.entries(values);
|
|
342
|
+
}
|
|
343
|
+
function isTokenKey(key) {
|
|
344
|
+
return Object.prototype.hasOwnProperty.call(FLUIDGRIDS_THEME_TOKEN_MAP, key);
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Turn a theme into the CSS custom properties it sets. Pure: no DOM access.
|
|
348
|
+
*
|
|
349
|
+
* Values that are not plain CSS values of the token's kind (anything with
|
|
350
|
+
* `;`, `{`, `}`, `url(`, `expression(`, quotes other than single-quoted font
|
|
351
|
+
* names, …) are dropped with a console warning. Unknown keys are dropped the
|
|
352
|
+
* same way. When `colors.primary` is set, the hover / pressed shades and the
|
|
353
|
+
* text colour are derived from it unless they are set explicitly; the text
|
|
354
|
+
* colour is picked for contrast when `primary` is a hex colour.
|
|
355
|
+
*/
|
|
356
|
+
function themeToCssVariables(theme) {
|
|
357
|
+
const variables = {};
|
|
358
|
+
if (!theme || typeof theme !== "object") return variables;
|
|
359
|
+
const colors = {};
|
|
360
|
+
for (const group of [
|
|
361
|
+
"colors",
|
|
362
|
+
"radius",
|
|
363
|
+
"font"
|
|
364
|
+
]) for (const [name, raw] of groupEntries(theme, group)) {
|
|
365
|
+
if (raw === void 0) continue;
|
|
366
|
+
const key = `${group}.${name}`;
|
|
367
|
+
if (!isTokenKey(key)) {
|
|
368
|
+
warnDropped(key, raw);
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
const mapping = FLUIDGRIDS_THEME_TOKEN_MAP[key];
|
|
372
|
+
const value = sanitizeThemeValue(mapping.kind, raw);
|
|
373
|
+
if (value === null) {
|
|
374
|
+
warnDropped(key, raw);
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (group === "colors") colors[name] = value;
|
|
378
|
+
for (const cssVariable of mapping.cssVariables) variables[cssVariable] = value;
|
|
379
|
+
}
|
|
380
|
+
const primary = colors.primary;
|
|
381
|
+
if (primary) {
|
|
382
|
+
if (!colors.primaryHover) variables["--color-action-primary-bgHover"] = `color-mix(in oklab, ${primary} 84%, var(--palette-black))`;
|
|
383
|
+
if (!colors.primaryActive) variables["--color-action-primary-bgActive"] = `color-mix(in oklab, ${primary} 70%, var(--palette-black))`;
|
|
384
|
+
if (!colors.primaryText) {
|
|
385
|
+
const luminance = hexLuminance(primary);
|
|
386
|
+
if (luminance !== null) variables["--color-action-primary-text"] = luminance > .4 ? "var(--palette-black)" : "var(--palette-white)";
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return variables;
|
|
390
|
+
}
|
|
391
|
+
const stack = [];
|
|
392
|
+
let nextId = 1;
|
|
393
|
+
let observer = null;
|
|
394
|
+
const hostVariables = /* @__PURE__ */ new Map();
|
|
395
|
+
const appliedVariables = /* @__PURE__ */ new Map();
|
|
396
|
+
let hostMode = null;
|
|
397
|
+
function readHostMode(root) {
|
|
398
|
+
return {
|
|
399
|
+
dataTheme: root.getAttribute("data-theme"),
|
|
400
|
+
dark: root.classList.contains("dark")
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
function writeMode(root, mode) {
|
|
404
|
+
if (mode.dataTheme === null) root.removeAttribute("data-theme");
|
|
405
|
+
else if (root.getAttribute("data-theme") !== mode.dataTheme) root.setAttribute("data-theme", mode.dataTheme);
|
|
406
|
+
if (root.classList.contains("dark") !== mode.dark) root.classList.toggle("dark", mode.dark);
|
|
407
|
+
}
|
|
408
|
+
function modeDrifted(root, mode) {
|
|
409
|
+
return root.getAttribute("data-theme") !== mode || root.classList.contains("dark") !== (mode === "dark");
|
|
410
|
+
}
|
|
411
|
+
function restoreVariable(root, name) {
|
|
412
|
+
const previous = hostVariables.get(name) ?? "";
|
|
413
|
+
if (previous) root.style.setProperty(name, previous);
|
|
414
|
+
else root.style.removeProperty(name);
|
|
415
|
+
hostVariables.delete(name);
|
|
416
|
+
appliedVariables.delete(name);
|
|
417
|
+
}
|
|
418
|
+
function forceVariable(root, name, value) {
|
|
419
|
+
root.style.setProperty(name, value);
|
|
420
|
+
appliedVariables.set(name, root.style.getPropertyValue(name));
|
|
421
|
+
}
|
|
422
|
+
/** Re-force the winning theme where something else changed it. */
|
|
423
|
+
function reconcileDrift() {
|
|
424
|
+
const top = stack.at(-1);
|
|
425
|
+
if (!top || typeof document === "undefined") return;
|
|
426
|
+
const root = document.documentElement;
|
|
427
|
+
for (const [name, value] of Object.entries(top.variables)) {
|
|
428
|
+
const current = root.style.getPropertyValue(name);
|
|
429
|
+
if (current !== appliedVariables.get(name)) {
|
|
430
|
+
hostVariables.set(name, current);
|
|
431
|
+
forceVariable(root, name, value);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (top.mode && modeDrifted(root, top.mode)) {
|
|
435
|
+
hostMode = readHostMode(root);
|
|
436
|
+
writeMode(root, {
|
|
437
|
+
dataTheme: top.mode,
|
|
438
|
+
dark: top.mode === "dark"
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
/** Make the document match the top of the stack. */
|
|
443
|
+
function sync() {
|
|
444
|
+
if (typeof document === "undefined") return;
|
|
445
|
+
const root = document.documentElement;
|
|
446
|
+
const top = stack.at(-1);
|
|
447
|
+
const wanted = top?.variables ?? {};
|
|
448
|
+
for (const name of [...appliedVariables.keys()]) if (!(name in wanted)) restoreVariable(root, name);
|
|
449
|
+
for (const [name, value] of Object.entries(wanted)) {
|
|
450
|
+
if (!hostVariables.has(name)) hostVariables.set(name, root.style.getPropertyValue(name));
|
|
451
|
+
if (appliedVariables.get(name) !== value) forceVariable(root, name, value);
|
|
452
|
+
}
|
|
453
|
+
if (top?.mode) {
|
|
454
|
+
if (!hostMode) hostMode = readHostMode(root);
|
|
455
|
+
writeMode(root, {
|
|
456
|
+
dataTheme: top.mode,
|
|
457
|
+
dark: top.mode === "dark"
|
|
458
|
+
});
|
|
459
|
+
} else if (hostMode) {
|
|
460
|
+
writeMode(root, hostMode);
|
|
461
|
+
hostMode = null;
|
|
462
|
+
}
|
|
463
|
+
if (!top) {
|
|
464
|
+
observer?.disconnect();
|
|
465
|
+
observer = null;
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
if (!observer && typeof MutationObserver !== "undefined") {
|
|
469
|
+
observer = new MutationObserver(reconcileDrift);
|
|
470
|
+
observer.observe(root, {
|
|
471
|
+
attributes: true,
|
|
472
|
+
attributeFilter: [
|
|
473
|
+
"style",
|
|
474
|
+
"data-theme",
|
|
475
|
+
"class"
|
|
476
|
+
]
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Apply a theme to the document and return a function that removes it again.
|
|
482
|
+
*
|
|
483
|
+
* Sets each variable from {@link themeToCssVariables} as an inline custom
|
|
484
|
+
* property on `document.documentElement`, and for a mode sets `data-theme`
|
|
485
|
+
* and the `.dark` class. While applied, the values are re-forced if something
|
|
486
|
+
* else (a host ThemeProvider) rewrites them. Disposing restores the inline
|
|
487
|
+
* values and mode the document had (including any change the host made in
|
|
488
|
+
* between). When several themes are applied, the most recent one wins.
|
|
489
|
+
* Calling the returned function more than once is safe. Without a DOM
|
|
490
|
+
* (server rendering) it does nothing.
|
|
491
|
+
*/
|
|
492
|
+
function applyFluidGridsTheme(theme) {
|
|
493
|
+
if (typeof document === "undefined") return () => void 0;
|
|
494
|
+
const entry = {
|
|
495
|
+
id: nextId++,
|
|
496
|
+
variables: themeToCssVariables(theme),
|
|
497
|
+
mode: themeMode(theme)
|
|
498
|
+
};
|
|
499
|
+
stack.push(entry);
|
|
500
|
+
sync();
|
|
501
|
+
let disposed = false;
|
|
502
|
+
return () => {
|
|
503
|
+
if (disposed) return;
|
|
504
|
+
disposed = true;
|
|
505
|
+
const index = stack.findIndex((active) => active.id === entry.id);
|
|
506
|
+
if (index >= 0) stack.splice(index, 1);
|
|
507
|
+
sync();
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Stable key for a theme value, so callers can tell two theme objects with the
|
|
512
|
+
* same content apart from a real change (hosts often pass inline objects).
|
|
513
|
+
*/
|
|
514
|
+
function themeKey(theme) {
|
|
515
|
+
if (!theme) return "";
|
|
516
|
+
if (typeof theme === "string") return theme;
|
|
517
|
+
const sortedGroup = (group) => groupEntries(theme, group).filter(([, value]) => value !== void 0).sort(([a], [b]) => a.localeCompare(b));
|
|
518
|
+
return JSON.stringify([
|
|
519
|
+
theme.mode ?? null,
|
|
520
|
+
sortedGroup("colors"),
|
|
521
|
+
sortedGroup("radius"),
|
|
522
|
+
sortedGroup("font")
|
|
523
|
+
]);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// src/config/index.ts
|
|
527
|
+
var config_exports = /* @__PURE__ */ __exportAll({ getFluidGridsConfig: () => getFluidGridsConfig });
|
|
528
|
+
let runtimeConfig = {
|
|
529
|
+
apiGatewayUrl: "",
|
|
530
|
+
workspaceId: ""
|
|
531
|
+
};
|
|
532
|
+
/**
|
|
533
|
+
* Get current configuration via proxy for live access
|
|
534
|
+
*/
|
|
535
|
+
const config = new Proxy({}, { get(_, prop) {
|
|
536
|
+
return runtimeConfig[prop];
|
|
537
|
+
} });
|
|
538
|
+
/**
|
|
539
|
+
* Get the full configuration object
|
|
540
|
+
*/
|
|
541
|
+
function getFluidGridsConfig() {
|
|
542
|
+
return { ...runtimeConfig };
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Set runtime configuration
|
|
546
|
+
*/
|
|
547
|
+
function setFluidGridsConfig(newConfig) {
|
|
548
|
+
const previous = runtimeConfig;
|
|
549
|
+
runtimeConfig = {
|
|
550
|
+
...runtimeConfig,
|
|
551
|
+
...newConfig
|
|
552
|
+
};
|
|
553
|
+
if (previous.authToken !== runtimeConfig.authToken || previous.workspaceToken !== runtimeConfig.workspaceToken) tokenRevision += 1;
|
|
554
|
+
if ("theme" in newConfig) syncConfigTheme(newConfig.theme);
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Reset configuration to defaults
|
|
558
|
+
*/
|
|
559
|
+
function resetFluidGridsConfig() {
|
|
560
|
+
tokenRevision += 1;
|
|
561
|
+
runtimeConfig = {
|
|
562
|
+
apiGatewayUrl: "",
|
|
563
|
+
workspaceId: ""
|
|
564
|
+
};
|
|
565
|
+
syncConfigTheme(void 0);
|
|
566
|
+
}
|
|
567
|
+
let tokenRevision = 0;
|
|
568
|
+
/** Current token revision. Internal: used by the SDK's Apollo clients. */
|
|
569
|
+
function getFluidGridsTokenRevision() {
|
|
570
|
+
return tokenRevision;
|
|
571
|
+
}
|
|
572
|
+
let configTheme = null;
|
|
573
|
+
function syncConfigTheme(theme) {
|
|
574
|
+
const key = themeKey(theme);
|
|
575
|
+
if (configTheme?.key === key) return;
|
|
576
|
+
const previous = configTheme;
|
|
577
|
+
configTheme = key ? {
|
|
578
|
+
key,
|
|
579
|
+
dispose: applyFluidGridsTheme(theme)
|
|
580
|
+
} : null;
|
|
581
|
+
previous?.dispose();
|
|
582
|
+
}
|
|
583
|
+
function normalizeOrigin(origin) {
|
|
584
|
+
return origin.replace(/\/$/, "");
|
|
585
|
+
}
|
|
586
|
+
function validateTrustedOrigin(trustedOrigins) {
|
|
587
|
+
if (typeof window === "undefined" || !trustedOrigins?.length) return;
|
|
588
|
+
const currentOrigin = normalizeOrigin(window.location.origin);
|
|
589
|
+
if (!trustedOrigins.map(normalizeOrigin).includes(currentOrigin)) throw new Error(`FluidGrids SDK blocked untrusted origin: ${currentOrigin}`);
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Get the GraphQL endpoint URL
|
|
593
|
+
*/
|
|
594
|
+
function getGraphQLEndpoint(baseUrl) {
|
|
595
|
+
const url = baseUrl || runtimeConfig.apiGatewayUrl;
|
|
596
|
+
if (!url) throw new Error("FluidGrids SDK requires apiGatewayUrl to be configured");
|
|
597
|
+
const normalizedBase = url.endsWith("/") ? url.slice(0, -1) : url;
|
|
598
|
+
if (normalizedBase.endsWith("/graphql")) return normalizedBase;
|
|
599
|
+
return `${normalizedBase}/workspaces/graphql`;
|
|
600
|
+
}
|
|
601
|
+
/** Clear user execution state only when a host session ends or changes identity. */
|
|
602
|
+
function resetFluidGridsExecutionState() {
|
|
603
|
+
useExecutionsStore.setState(useExecutionsStore.getInitialState(), true);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// src/graphql/live-tokens.ts
|
|
607
|
+
/**
|
|
608
|
+
* Token reader for an SDK-created Apollo client (BOFF-9961).
|
|
609
|
+
*
|
|
610
|
+
* The client starts with the tokens it was created with. Once a token is set
|
|
611
|
+
* in the SDK config after that — a refresh through `auth.onTokenExpired`, or
|
|
612
|
+
* a host calling `setFluidGridsConfig({ authToken })` — every later request
|
|
613
|
+
* reads the configured value instead. Reading per request (never capturing
|
|
614
|
+
* the token in a closure) is what lets a refreshed token reach in-flight
|
|
615
|
+
* retries and websocket reconnects.
|
|
616
|
+
*/
|
|
617
|
+
function createLiveTokenReader(initial) {
|
|
618
|
+
const createdAt = getFluidGridsTokenRevision();
|
|
619
|
+
return () => {
|
|
620
|
+
if (getFluidGridsTokenRevision() === createdAt) return initial;
|
|
621
|
+
return {
|
|
622
|
+
authToken: config.authToken ?? initial.authToken,
|
|
623
|
+
workspaceToken: config.workspaceToken ?? initial.workspaceToken
|
|
624
|
+
};
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// src/graphql/errors.ts
|
|
629
|
+
/**
|
|
630
|
+
* Operation-error helpers.
|
|
631
|
+
*
|
|
632
|
+
* The SDK usually runs against a host-injected Apollo client (see
|
|
633
|
+
* `setFluidGridsClient`). App shells configure their shared client with
|
|
634
|
+
* `errorPolicy: 'all'`, so `client.mutate` / `client.query` RESOLVE on
|
|
635
|
+
* GraphQL errors — `result.data` is empty and the real failure sits in
|
|
636
|
+
* `result.error`. Callers that only check `result.data` silently swallow
|
|
637
|
+
* server errors (e.g. QUOTA_EXHAUSTED) and surface generic messages.
|
|
638
|
+
*
|
|
639
|
+
* Use `throwIfResultError(result)` right after a mutate/query so failures
|
|
640
|
+
* flow through the existing catch paths, and `describeOperationError(err)`
|
|
641
|
+
* to turn whatever was thrown into a user-presentable message.
|
|
642
|
+
*/
|
|
643
|
+
/** Well-known gateway error codes mapped to user-friendly messages. */
|
|
644
|
+
function describeGraphQLError(error) {
|
|
645
|
+
switch (typeof error.extensions?.code === "string" ? error.extensions.code : void 0) {
|
|
646
|
+
case "QUOTA_EXHAUSTED": {
|
|
647
|
+
const quota = /Quota exhausted for (\S+)/.exec(error.message)?.[1];
|
|
648
|
+
return `Quota exhausted — your workspace has used up its ${quota ? `"${quota}" quota` : "usage quota"}. Upgrade your plan or wait for the quota to reset.`;
|
|
649
|
+
}
|
|
650
|
+
case "QUOTA_NOT_PROVISIONED": return "This workspace's plan does not include this usage allowance yet. Contact your workspace admin or support to have it provisioned.";
|
|
651
|
+
case "UNAUTHENTICATED": return "Your session has expired. Please sign in again.";
|
|
652
|
+
case "FORBIDDEN": return error.message || "You don't have permission to perform this action.";
|
|
653
|
+
default: return error.message || "Unknown error";
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
/** The first GraphQL error of a raw JSON response body, if it carries one. */
|
|
657
|
+
function firstGraphQLErrorInBody(bodyText) {
|
|
658
|
+
try {
|
|
659
|
+
const parsed = JSON.parse(bodyText);
|
|
660
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
661
|
+
const errors = parsed.errors;
|
|
662
|
+
if (!Array.isArray(errors) || errors.length === 0) return null;
|
|
663
|
+
const first = errors[0];
|
|
664
|
+
if (typeof first !== "object" || first === null) return null;
|
|
665
|
+
if (typeof first.message !== "string") return null;
|
|
666
|
+
return first;
|
|
667
|
+
} catch {
|
|
668
|
+
return null;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Turn anything thrown (or found in `result.error`) into a message fit for
|
|
673
|
+
* a toast / run log. Unwraps Apollo `CombinedGraphQLErrors` so well-known
|
|
674
|
+
* gateway codes (quota, auth) read properly instead of a concatenated dump.
|
|
675
|
+
*/
|
|
676
|
+
function describeOperationError(error, fallback = "Unknown error") {
|
|
677
|
+
if (CombinedGraphQLErrors.is(error)) {
|
|
678
|
+
const first = error.errors[0];
|
|
679
|
+
return first ? describeGraphQLError(first) : error.message || fallback;
|
|
680
|
+
}
|
|
681
|
+
if (ServerError.is(error)) {
|
|
682
|
+
const first = firstGraphQLErrorInBody(error.bodyText);
|
|
683
|
+
if (first) return describeGraphQLError(first);
|
|
684
|
+
}
|
|
685
|
+
if (error instanceof Error) return error.message || fallback;
|
|
686
|
+
if (typeof error === "string" && error.length > 0) return error;
|
|
687
|
+
return fallback;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Throw `result.error` when the injected client resolved an errored
|
|
691
|
+
* operation (errorPolicy 'all'). Returns the result unchanged otherwise, so
|
|
692
|
+
* it can be chained inline:
|
|
693
|
+
*
|
|
694
|
+
* ```ts
|
|
695
|
+
* const result = throwIfResultError(await client.mutate({ ... }));
|
|
696
|
+
* ```
|
|
697
|
+
*/
|
|
698
|
+
function throwIfResultError(result) {
|
|
699
|
+
if (result.error) throw new Error(describeOperationError(result.error), { cause: result.error });
|
|
700
|
+
return result;
|
|
701
|
+
}
|
|
702
|
+
/** True when a GraphQL error list carries a FORBIDDEN code. */
|
|
703
|
+
function hasForbiddenCode(errors) {
|
|
704
|
+
return (errors ?? []).some((error) => error.extensions?.code === "FORBIDDEN");
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* True when an operation failed because the caller lacks the permission
|
|
708
|
+
* (gateway `@rbac` denial). Accepts the raw Apollo error, a `result.error`,
|
|
709
|
+
* or the Error `throwIfResultError` throws (the original sits in `cause`).
|
|
710
|
+
* Pages use it to render a no-access state instead of a load failure.
|
|
711
|
+
*/
|
|
712
|
+
function isForbiddenError(error) {
|
|
713
|
+
if (CombinedGraphQLErrors.is(error)) return hasForbiddenCode(error.errors);
|
|
714
|
+
if (ServerError.is(error)) {
|
|
715
|
+
if (error.statusCode === 403) return true;
|
|
716
|
+
const first = firstGraphQLErrorInBody(error.bodyText);
|
|
717
|
+
return first ? hasForbiddenCode([first]) : false;
|
|
718
|
+
}
|
|
719
|
+
if (error instanceof Error && error.cause !== void 0) return isForbiddenError(error.cause);
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
const REFRESHABLE_AUTH_FAILURES = /* @__PURE__ */ new Set(["CREDENTIAL_EXPIRED", "CREDENTIAL_REVOKED"]);
|
|
723
|
+
const EXPIRED_TOKEN_MESSAGE = /\b(?:jwt|token)\b[^.]{0,40}\bexpired\b|\bexpired\b[^.]{0,20}\b(?:jwt|token)\b/i;
|
|
724
|
+
function isUnauthenticatedGraphQLError(error) {
|
|
725
|
+
if (error.extensions?.code === "UNAUTHENTICATED") return true;
|
|
726
|
+
const authFailure = error.extensions?.authFailure;
|
|
727
|
+
if (typeof authFailure === "string" && REFRESHABLE_AUTH_FAILURES.has(authFailure)) return true;
|
|
728
|
+
return typeof error.message === "string" && EXPIRED_TOKEN_MESSAGE.test(error.message);
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* True when a GraphQL error list says the caller's credential expired or is
|
|
732
|
+
* not accepted: an `UNAUTHENTICATED` code, an expired / revoked
|
|
733
|
+
* `authFailure` category, or an expired-token message.
|
|
734
|
+
*/
|
|
735
|
+
function hasUnauthenticatedGraphQLError(errors) {
|
|
736
|
+
return (errors ?? []).some(isUnauthenticatedGraphQLError);
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* True when an operation failed because the session expired: HTTP 401, a
|
|
740
|
+
* GraphQL `UNAUTHENTICATED` error, or an expired / revoked credential.
|
|
741
|
+
* Accepts the raw Apollo error, a `result.error`, or the Error
|
|
742
|
+
* `throwIfResultError` throws (the original sits in `cause`). A 503
|
|
743
|
+
* `AUTH_SERVICE_UNAVAILABLE` is not an expired session and returns false.
|
|
744
|
+
*/
|
|
745
|
+
function isUnauthenticatedError(error) {
|
|
746
|
+
if (CombinedGraphQLErrors.is(error)) return hasUnauthenticatedGraphQLError(error.errors);
|
|
747
|
+
if (ServerError.is(error)) {
|
|
748
|
+
if (error.statusCode === 401) return true;
|
|
749
|
+
const first = firstGraphQLErrorInBody(error.bodyText);
|
|
750
|
+
return first ? isUnauthenticatedGraphQLError(first) : false;
|
|
751
|
+
}
|
|
752
|
+
if (error instanceof Error && error.cause !== void 0) return isUnauthenticatedError(error.cause);
|
|
753
|
+
return false;
|
|
754
|
+
}
|
|
755
|
+
/** The gateway error code of an operation failure, when it carries one. */
|
|
756
|
+
function operationErrorCode(error) {
|
|
757
|
+
const codeOf = (graphQLError) => {
|
|
758
|
+
const code = graphQLError?.extensions?.code;
|
|
759
|
+
return typeof code === "string" ? code : void 0;
|
|
760
|
+
};
|
|
761
|
+
if (CombinedGraphQLErrors.is(error)) return codeOf(error.errors[0]);
|
|
762
|
+
if (ServerError.is(error)) return codeOf(firstGraphQLErrorInBody(error.bodyText) ?? void 0);
|
|
763
|
+
if (error instanceof Error && error.cause !== void 0) return operationErrorCode(error.cause);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// src/events/eventBus.ts
|
|
767
|
+
/**
|
|
768
|
+
* Host event bus for embedders (BOFF-9961).
|
|
769
|
+
*
|
|
770
|
+
* The SDK reports what happens inside the embedded builder to the host page
|
|
771
|
+
* through one typed callback, `onEvent`, set on `FluidGridsConfig` (or the
|
|
772
|
+
* `onEvent` prop of `FluidGridsProvider`). Hosts use it to sync their own UI,
|
|
773
|
+
* analytics or backend: a workflow was saved or activated, a run started or
|
|
774
|
+
* finished, the user selected a node, something failed.
|
|
775
|
+
*
|
|
776
|
+
* This bus is separate from the product-analytics emitters in
|
|
777
|
+
* `analytics/fluidgridsEvents.ts`, which publish `fluidgrids.*` facts on the
|
|
778
|
+
* fe-libs shell bus for Rybbit/OTel. Those stay as they are; this one is the
|
|
779
|
+
* documented, versioned contract for third-party hosts.
|
|
780
|
+
*
|
|
781
|
+
* Versioning: {@link FLUIDGRIDS_EVENTS_VERSION} follows semver.
|
|
782
|
+
* - A new event name, or a new optional payload field, is a minor bump.
|
|
783
|
+
* - Removing or renaming an event, removing a payload field, or changing a
|
|
784
|
+
* field's type or meaning is a major bump (and a breaking SDK release).
|
|
785
|
+
*
|
|
786
|
+
* Delivery: events are synchronous and best effort. A handler that throws is
|
|
787
|
+
* caught and logged with a warning; it never breaks the SDK action that
|
|
788
|
+
* emitted the event.
|
|
789
|
+
*
|
|
790
|
+
* @module events/eventBus
|
|
791
|
+
*/
|
|
792
|
+
/** Version of the host event contract. See the module note for the rules. */
|
|
793
|
+
const FLUIDGRIDS_EVENTS_VERSION = "1.1.0";
|
|
794
|
+
/** Every host event name, in documentation order. */
|
|
795
|
+
const FLUIDGRIDS_EVENT_NAMES = [
|
|
796
|
+
"workflow:saved",
|
|
797
|
+
"workflow:activated",
|
|
798
|
+
"run:started",
|
|
799
|
+
"run:completed",
|
|
800
|
+
"node:selected",
|
|
801
|
+
"error",
|
|
802
|
+
"auth:failed",
|
|
803
|
+
"builder:fullscreen"
|
|
804
|
+
];
|
|
805
|
+
/** Drop undefined keys so hosts see only the fields that are known. */
|
|
806
|
+
function withoutUndefined(payload) {
|
|
807
|
+
return Object.fromEntries(Object.entries(payload).filter(([, value]) => value !== void 0));
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Deliver one host event. Never throws: a missing handler is a no-op and a
|
|
811
|
+
* handler that throws is logged, so SDK code can call this anywhere.
|
|
812
|
+
*/
|
|
813
|
+
function emitFluidGridsEvent(name, payload) {
|
|
814
|
+
let handler;
|
|
815
|
+
try {
|
|
816
|
+
handler = config.onEvent;
|
|
817
|
+
} catch {
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
if (typeof handler !== "function") return;
|
|
821
|
+
try {
|
|
822
|
+
const args = [name, withoutUndefined(payload)];
|
|
823
|
+
handler(...args);
|
|
824
|
+
} catch (error) {
|
|
825
|
+
console.warn(`[FluidGrids SDK] onEvent handler failed for ${name}`, error);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Emit an `error` event for a failed SDK operation: the user-presentable
|
|
830
|
+
* message and, when the gateway sent one, its error code.
|
|
831
|
+
*/
|
|
832
|
+
function emitOperationErrorEvent(source, error) {
|
|
833
|
+
emitFluidGridsEvent("error", {
|
|
834
|
+
source,
|
|
835
|
+
message: describeOperationError(error),
|
|
836
|
+
code: operationErrorCode(error)
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
const MAX_COMPLETED_RUNS = 500;
|
|
840
|
+
const completedRuns = /* @__PURE__ */ new Set();
|
|
841
|
+
/**
|
|
842
|
+
* Emit `run:completed` once per run id. Later calls for the same run (a
|
|
843
|
+
* second poll that sees the terminal status, a remounted builder following
|
|
844
|
+
* the same run) are ignored.
|
|
845
|
+
*/
|
|
846
|
+
function emitRunCompletedOnce(payload) {
|
|
847
|
+
if (!payload.runId || completedRuns.has(payload.runId)) return;
|
|
848
|
+
completedRuns.add(payload.runId);
|
|
849
|
+
if (completedRuns.size > MAX_COMPLETED_RUNS) {
|
|
850
|
+
const oldest = completedRuns.values().next().value;
|
|
851
|
+
if (oldest !== void 0) completedRuns.delete(oldest);
|
|
852
|
+
}
|
|
853
|
+
emitFluidGridsEvent("run:completed", payload);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// src/graphql/token-refresh-link.ts
|
|
857
|
+
/**
|
|
858
|
+
* Token refresh on expiry (BOFF-9961).
|
|
859
|
+
*
|
|
860
|
+
* Workspace tokens are short-lived, so a builder left open outlives the token
|
|
861
|
+
* it was mounted with. The gateway then answers 401 with a GraphQL
|
|
862
|
+
* `UNAUTHENTICATED` error (category `CREDENTIAL_EXPIRED` / `_REVOKED`). This
|
|
863
|
+
* link catches that, asks the host for a fresh token through
|
|
864
|
+
* `auth.onTokenExpired`, stores it in the SDK config, and retries the failed
|
|
865
|
+
* operation once with the new headers.
|
|
866
|
+
*
|
|
867
|
+
* Rules:
|
|
868
|
+
* - one refresh at a time: operations that fail while a refresh is running
|
|
869
|
+
* wait for the same promise instead of calling the host again, and an
|
|
870
|
+
* operation that was sent before a refresh finished retries with the
|
|
871
|
+
* token that refresh produced;
|
|
872
|
+
* - each operation is retried at most once, so a token the gateway keeps
|
|
873
|
+
* refusing can never loop;
|
|
874
|
+
* - when the host cannot refresh (the callback rejects or returns no token),
|
|
875
|
+
* `auth:failed` is emitted and the operation fails with its ORIGINAL error;
|
|
876
|
+
* - an operation sent before a refresh attempt that has since FAILED does
|
|
877
|
+
* not start a new one: its 401 is the same expired session that attempt
|
|
878
|
+
* already reported, so it fails with its original error and no second
|
|
879
|
+
* `auth:failed`. Only an operation sent after the failure (the host may
|
|
880
|
+
* have recovered, e.g. the user signed in again) may try a fresh refresh.
|
|
881
|
+
*
|
|
882
|
+
* @module graphql/token-refresh-link
|
|
883
|
+
*/
|
|
884
|
+
let inFlight = null;
|
|
885
|
+
let refreshGeneration = 0;
|
|
886
|
+
let lastRefreshed = null;
|
|
887
|
+
let settledAttempts = 0;
|
|
888
|
+
let lastFailedAttempt = 0;
|
|
889
|
+
function normalizeTokens(value) {
|
|
890
|
+
if (typeof value === "string") return value.trim() ? { authToken: value } : null;
|
|
891
|
+
if (!value || typeof value !== "object") return null;
|
|
892
|
+
const tokens = {};
|
|
893
|
+
if (typeof value.authToken === "string" && value.authToken.trim()) tokens.authToken = value.authToken;
|
|
894
|
+
if (typeof value.workspaceToken === "string" && value.workspaceToken.trim()) tokens.workspaceToken = value.workspaceToken;
|
|
895
|
+
return tokens.authToken || tokens.workspaceToken ? tokens : null;
|
|
896
|
+
}
|
|
897
|
+
function storeInConfig(tokens) {
|
|
898
|
+
setFluidGridsConfig(tokens);
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Run the host refresh callback, sharing one call between concurrent
|
|
902
|
+
* failures. Resolves with the fresh tokens, or null when the host could not
|
|
903
|
+
* provide any (after emitting `auth:failed`).
|
|
904
|
+
*/
|
|
905
|
+
function refreshTokens(callback, store) {
|
|
906
|
+
if (inFlight) return inFlight;
|
|
907
|
+
const fail = (message) => {
|
|
908
|
+
settledAttempts += 1;
|
|
909
|
+
lastFailedAttempt = settledAttempts;
|
|
910
|
+
emitFluidGridsEvent("auth:failed", { message });
|
|
911
|
+
return null;
|
|
912
|
+
};
|
|
913
|
+
const run = (async () => {
|
|
914
|
+
try {
|
|
915
|
+
const tokens = normalizeTokens(await callback());
|
|
916
|
+
if (!tokens) return fail("onTokenExpired returned no token.");
|
|
917
|
+
store(tokens);
|
|
918
|
+
settledAttempts += 1;
|
|
919
|
+
refreshGeneration += 1;
|
|
920
|
+
lastRefreshed = tokens;
|
|
921
|
+
return tokens;
|
|
922
|
+
} catch (error) {
|
|
923
|
+
return fail(describeOperationError(error, "Token refresh failed."));
|
|
924
|
+
}
|
|
925
|
+
})();
|
|
926
|
+
inFlight = run;
|
|
927
|
+
run.finally(() => {
|
|
928
|
+
if (inFlight === run) inFlight = null;
|
|
929
|
+
});
|
|
930
|
+
return run;
|
|
931
|
+
}
|
|
932
|
+
function withTokenHeaders(headers, tokens) {
|
|
933
|
+
return {
|
|
934
|
+
...headers,
|
|
935
|
+
...tokens.authToken && { authorization: `Bearer ${tokens.authToken}` },
|
|
936
|
+
...tokens.workspaceToken && { "x-workspace-authorization": `Bearer ${tokens.workspaceToken}` }
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
function resultIsUnauthenticated(result) {
|
|
940
|
+
return hasUnauthenticatedGraphQLError(result.errors);
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Apollo link that refreshes an expired token and retries once.
|
|
944
|
+
*
|
|
945
|
+
* The SDK adds it to the clients it creates (`createFluidGridsClient`,
|
|
946
|
+
* `createGlobalStoreClient`, `createFluidGridsClients`). Hosts that inject
|
|
947
|
+
* their own Apollo client add it to their chain, BEFORE the link that sets the
|
|
948
|
+
* `authorization` header, and either read the token from the SDK config in
|
|
949
|
+
* that link or pass `onTokenRefreshed`:
|
|
950
|
+
*
|
|
951
|
+
* ```ts
|
|
952
|
+
* const link = ApolloLink.from([
|
|
953
|
+
* createFluidGridsTokenRefreshLink(),
|
|
954
|
+
* authLink, // reads getFluidGridsConfig().authToken per request
|
|
955
|
+
* httpLink,
|
|
956
|
+
* ]);
|
|
957
|
+
* ```
|
|
958
|
+
*
|
|
959
|
+
* The retried operation also carries the new token in its own headers, so it
|
|
960
|
+
* works when the host's auth link runs first, too — as long as that link
|
|
961
|
+
* does not overwrite an `authorization` header it finds.
|
|
962
|
+
*/
|
|
963
|
+
function createFluidGridsTokenRefreshLink(options = {}) {
|
|
964
|
+
return new ApolloLink((operation, forward) => {
|
|
965
|
+
const store = options.onTokenRefreshed ?? storeInConfig;
|
|
966
|
+
return new Observable((observer) => {
|
|
967
|
+
const sentInGeneration = refreshGeneration;
|
|
968
|
+
const sentAfterAttempts = settledAttempts;
|
|
969
|
+
let retried = false;
|
|
970
|
+
let closed = false;
|
|
971
|
+
let current = null;
|
|
972
|
+
const pickRefresh = (callback) => {
|
|
973
|
+
if (refreshGeneration !== sentInGeneration && lastRefreshed) return Promise.resolve(lastRefreshed);
|
|
974
|
+
if (lastFailedAttempt > sentAfterAttempts) return Promise.resolve(null);
|
|
975
|
+
return refreshTokens(callback, store);
|
|
976
|
+
};
|
|
977
|
+
const retryOrFail = (fail) => {
|
|
978
|
+
const callback = options.onTokenExpired ?? config.auth?.onTokenExpired;
|
|
979
|
+
if (!callback) return false;
|
|
980
|
+
retried = true;
|
|
981
|
+
current?.unsubscribe();
|
|
982
|
+
current = null;
|
|
983
|
+
pickRefresh(callback).then((tokens) => {
|
|
984
|
+
if (closed) return;
|
|
985
|
+
if (!tokens) {
|
|
986
|
+
fail();
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
const headers = operation.getContext().headers;
|
|
990
|
+
operation.setContext({ headers: withTokenHeaders(headers && typeof headers === "object" ? headers : void 0, tokens) });
|
|
991
|
+
subscribe();
|
|
992
|
+
});
|
|
993
|
+
return true;
|
|
994
|
+
};
|
|
995
|
+
const subscribe = () => {
|
|
996
|
+
let refreshing = false;
|
|
997
|
+
current = forward(operation).subscribe({
|
|
998
|
+
next: (result) => {
|
|
999
|
+
if (!retried && resultIsUnauthenticated(result) && retryOrFail(() => {
|
|
1000
|
+
observer.next(result);
|
|
1001
|
+
observer.complete();
|
|
1002
|
+
})) {
|
|
1003
|
+
refreshing = true;
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
observer.next(result);
|
|
1007
|
+
},
|
|
1008
|
+
error: (error) => {
|
|
1009
|
+
if (!retried && isUnauthenticatedError(error) && retryOrFail(() => observer.error(error))) {
|
|
1010
|
+
refreshing = true;
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
observer.error(error);
|
|
1014
|
+
},
|
|
1015
|
+
complete: () => {
|
|
1016
|
+
if (!refreshing) observer.complete();
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
1019
|
+
};
|
|
1020
|
+
subscribe();
|
|
1021
|
+
return () => {
|
|
1022
|
+
closed = true;
|
|
1023
|
+
current?.unsubscribe();
|
|
1024
|
+
};
|
|
1025
|
+
});
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// src/graphql/client.ts
|
|
1030
|
+
let clientInstance = null;
|
|
1031
|
+
/**
|
|
1032
|
+
* Create a new Apollo client for FluidGrids SDK.
|
|
1033
|
+
*
|
|
1034
|
+
* Tokens are read per request: the client starts with `config.authToken` /
|
|
1035
|
+
* `config.workspaceToken` and follows any token set later in the SDK config
|
|
1036
|
+
* (see `auth.onTokenExpired`). Expired-token responses are refreshed and
|
|
1037
|
+
* retried once by `createFluidGridsTokenRefreshLink`; the websocket reads the
|
|
1038
|
+
* current token whenever it (re)connects.
|
|
1039
|
+
*/
|
|
1040
|
+
function createFluidGridsClient(config) {
|
|
1041
|
+
const graphqlUrl = getGraphQLUrl(config.apiGatewayUrl);
|
|
1042
|
+
const wsUrl = graphqlUrl.replace(/^http/, "ws");
|
|
1043
|
+
const readTokens = createLiveTokenReader({
|
|
1044
|
+
authToken: config.authToken,
|
|
1045
|
+
workspaceToken: config.workspaceToken
|
|
1046
|
+
});
|
|
1047
|
+
const httpLink = createHttpLink({ uri: graphqlUrl });
|
|
1048
|
+
const authLink = setContext((_, { headers }) => {
|
|
1049
|
+
const { authToken, workspaceToken } = readTokens();
|
|
1050
|
+
return { headers: {
|
|
1051
|
+
...headers,
|
|
1052
|
+
...authToken && { authorization: `Bearer ${authToken}` },
|
|
1053
|
+
...workspaceToken && { "x-workspace-authorization": `Bearer ${workspaceToken}` },
|
|
1054
|
+
"x-workspace-id": config.workspaceId,
|
|
1055
|
+
...config.tenantId && { "x-tenant-id": config.tenantId },
|
|
1056
|
+
...config.productId && { "x-product-id": config.productId }
|
|
1057
|
+
} };
|
|
1058
|
+
});
|
|
1059
|
+
const wsLink = new GraphQLWsLink(createClient({
|
|
1060
|
+
url: wsUrl,
|
|
1061
|
+
connectionParams: () => {
|
|
1062
|
+
const { authToken, workspaceToken } = readTokens();
|
|
1063
|
+
return {
|
|
1064
|
+
authorization: authToken ? `Bearer ${authToken}` : "",
|
|
1065
|
+
...workspaceToken && { "x-workspace-authorization": `Bearer ${workspaceToken}` },
|
|
1066
|
+
"x-workspace-id": config.workspaceId,
|
|
1067
|
+
...config.tenantId && { "x-tenant-id": config.tenantId },
|
|
1068
|
+
...config.productId && { "x-product-id": config.productId }
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
}));
|
|
1072
|
+
const splitLink = split(({ query }) => {
|
|
1073
|
+
const definition = getMainDefinition(query);
|
|
1074
|
+
return definition.kind === "OperationDefinition" && definition.operation === "subscription";
|
|
1075
|
+
}, wsLink, createFluidGridsTokenRefreshLink().concat(authLink).concat(httpLink));
|
|
1076
|
+
return new ApolloClient({
|
|
1077
|
+
link: splitLink,
|
|
1078
|
+
cache: new InMemoryCache(),
|
|
1079
|
+
defaultOptions: {
|
|
1080
|
+
watchQuery: { fetchPolicy: "cache-and-network" },
|
|
1081
|
+
query: { fetchPolicy: "network-only" }
|
|
1082
|
+
}
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Get the GraphQL URL from the API gateway URL
|
|
1087
|
+
*/
|
|
1088
|
+
function getGraphQLUrl(apiGatewayUrl) {
|
|
1089
|
+
const normalizedBase = apiGatewayUrl.endsWith("/") ? apiGatewayUrl.slice(0, -1) : apiGatewayUrl;
|
|
1090
|
+
if (normalizedBase.endsWith("/graphql")) return normalizedBase;
|
|
1091
|
+
return `${normalizedBase}/workspaces/graphql`;
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Set the module-level client (for SDK-managed mode or wrapper injection)
|
|
1095
|
+
*/
|
|
1096
|
+
function setFluidGridsClient(client) {
|
|
1097
|
+
clientInstance = client;
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* Get the module-level client
|
|
1101
|
+
* @throws Error if client is not initialized
|
|
1102
|
+
*/
|
|
1103
|
+
function getFluidGridsClient() {
|
|
1104
|
+
if (!clientInstance) throw new Error("FluidGrids client not initialized. Use FluidGridsProvider or call setFluidGridsClient().");
|
|
1105
|
+
return clientInstance;
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Get the client if available, or null if not initialized
|
|
1109
|
+
*/
|
|
1110
|
+
function getFluidGridsClientSafe() {
|
|
1111
|
+
return clientInstance;
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* Check if the client is ready
|
|
1115
|
+
*/
|
|
1116
|
+
function isFluidGridsClientReady() {
|
|
1117
|
+
return clientInstance !== null;
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Wait for the client to be initialized
|
|
1121
|
+
*/
|
|
1122
|
+
function waitForFluidGridsClient(timeoutMs = 1e4, intervalMs = 100) {
|
|
1123
|
+
return new Promise((resolve) => {
|
|
1124
|
+
if (clientInstance) {
|
|
1125
|
+
resolve(clientInstance);
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
const start = Date.now();
|
|
1129
|
+
const timer = setInterval(() => {
|
|
1130
|
+
if (clientInstance) {
|
|
1131
|
+
clearInterval(timer);
|
|
1132
|
+
resolve(clientInstance);
|
|
1133
|
+
} else if (Date.now() - start >= timeoutMs) {
|
|
1134
|
+
clearInterval(timer);
|
|
1135
|
+
resolve(null);
|
|
1136
|
+
}
|
|
1137
|
+
}, intervalMs);
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Reset the client reference (for cleanup on unmount)
|
|
1142
|
+
*/
|
|
1143
|
+
function resetFluidGridsClient() {
|
|
1144
|
+
clientInstance = null;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
export { themeKey as A, resetFluidGridsConfig as C, FLUIDGRIDS_THEME_TOKEN_MAP as D, validateTrustedOrigin as E, DEFAULT_EXECUTIONS_FILTER as M, useExecutionsStore as N, applyFluidGridsTheme as O, getGraphQLEndpoint as S, setFluidGridsConfig as T, throwIfResultError as _, resetFluidGridsClient as a, config_exports as b, createFluidGridsTokenRefreshLink as c, emitFluidGridsEvent as d, emitOperationErrorEvent as f, isUnauthenticatedError as g, isForbiddenError as h, isFluidGridsClientReady as i, themeToCssVariables as j, sanitizeThemeValue as k, FLUIDGRIDS_EVENTS_VERSION as l, describeOperationError as m, getFluidGridsClient as n, setFluidGridsClient as o, emitRunCompletedOnce as p, getFluidGridsClientSafe as r, waitForFluidGridsClient as s, createFluidGridsClient as t, FLUIDGRIDS_EVENT_NAMES as u, createLiveTokenReader as v, resetFluidGridsExecutionState as w, getFluidGridsConfig as x, config as y };
|