@nordcraft/runtime 1.0.0
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 +5 -0
- package/dist/api/createAPI.d.ts +20 -0
- package/dist/api/createAPI.js +319 -0
- package/dist/api/createAPI.js.map +1 -0
- package/dist/api/createAPIv2.d.ts +7 -0
- package/dist/api/createAPIv2.js +686 -0
- package/dist/api/createAPIv2.js.map +1 -0
- package/dist/components/createComponent.d.ts +13 -0
- package/dist/components/createComponent.js +216 -0
- package/dist/components/createComponent.js.map +1 -0
- package/dist/components/createElement.d.ts +3 -0
- package/dist/components/createElement.js +208 -0
- package/dist/components/createElement.js.map +1 -0
- package/dist/components/createNode.d.ts +22 -0
- package/dist/components/createNode.js +272 -0
- package/dist/components/createNode.js.map +1 -0
- package/dist/components/createSlot.d.ts +3 -0
- package/dist/components/createSlot.js +49 -0
- package/dist/components/createSlot.js.map +1 -0
- package/dist/components/createText.d.ts +23 -0
- package/dist/components/createText.js +68 -0
- package/dist/components/createText.js.map +1 -0
- package/dist/components/createText.test.d.ts +1 -0
- package/dist/components/createText.test.js +113 -0
- package/dist/components/createText.test.js.map +1 -0
- package/dist/components/renderComponent.d.ts +34 -0
- package/dist/components/renderComponent.js +66 -0
- package/dist/components/renderComponent.js.map +1 -0
- package/dist/context/isContextProvider.d.ts +2 -0
- package/dist/context/isContextProvider.js +5 -0
- package/dist/context/isContextProvider.js.map +1 -0
- package/dist/context/subscribeToContext.d.ts +4 -0
- package/dist/context/subscribeToContext.js +93 -0
- package/dist/context/subscribeToContext.js.map +1 -0
- package/dist/custom-components/components.d.ts +1 -0
- package/dist/custom-components/components.js +2 -0
- package/dist/custom-components/components.js.map +1 -0
- package/dist/custom-components/toddle-portal.d.ts +6 -0
- package/dist/custom-components/toddle-portal.js +20 -0
- package/dist/custom-components/toddle-portal.js.map +1 -0
- package/dist/custom-element/ToddleComponent.d.ts +37 -0
- package/dist/custom-element/ToddleComponent.js +244 -0
- package/dist/custom-element/ToddleComponent.js.map +1 -0
- package/dist/custom-element/defineComponents.d.ts +26 -0
- package/dist/custom-element/defineComponents.js +42 -0
- package/dist/custom-element/defineComponents.js.map +1 -0
- package/dist/custom-element.main.d.ts +3 -0
- package/dist/custom-element.main.esm.js +266 -0
- package/dist/custom-element.main.esm.js.map +7 -0
- package/dist/custom-element.main.js +14 -0
- package/dist/custom-element.main.js.map +1 -0
- package/dist/debug/logState.d.ts +4 -0
- package/dist/debug/logState.js +19 -0
- package/dist/debug/logState.js.map +1 -0
- package/dist/editor/drag-drop/dragEnded.d.ts +2 -0
- package/dist/editor/drag-drop/dragEnded.js +56 -0
- package/dist/editor/drag-drop/dragEnded.js.map +1 -0
- package/dist/editor/drag-drop/dragMove.d.ts +3 -0
- package/dist/editor/drag-drop/dragMove.js +74 -0
- package/dist/editor/drag-drop/dragMove.js.map +1 -0
- package/dist/editor/drag-drop/dragReorder.d.ts +3 -0
- package/dist/editor/drag-drop/dragReorder.js +92 -0
- package/dist/editor/drag-drop/dragReorder.js.map +1 -0
- package/dist/editor/drag-drop/dragStarted.d.ts +9 -0
- package/dist/editor/drag-drop/dragStarted.js +100 -0
- package/dist/editor/drag-drop/dragStarted.js.map +1 -0
- package/dist/editor/drag-drop/dropHighlight.d.ts +16 -0
- package/dist/editor/drag-drop/dropHighlight.js +50 -0
- package/dist/editor/drag-drop/dropHighlight.js.map +1 -0
- package/dist/editor/drag-drop/getInsertAreas.d.ts +20 -0
- package/dist/editor/drag-drop/getInsertAreas.js +220 -0
- package/dist/editor/drag-drop/getInsertAreas.js.map +1 -0
- package/dist/editor-preview.main.d.ts +19 -0
- package/dist/editor-preview.main.js +1303 -0
- package/dist/editor-preview.main.js.map +1 -0
- package/dist/events/handleAction.d.ts +3 -0
- package/dist/events/handleAction.js +307 -0
- package/dist/events/handleAction.js.map +1 -0
- package/dist/page.main.d.ts +7 -0
- package/dist/page.main.esm.js +8 -0
- package/dist/page.main.esm.js.map +7 -0
- package/dist/page.main.js +395 -0
- package/dist/page.main.js.map +1 -0
- package/dist/signal/signal.d.ts +19 -0
- package/dist/signal/signal.js +65 -0
- package/dist/signal/signal.js.map +1 -0
- package/dist/styles/style.d.ts +4 -0
- package/dist/styles/style.js +196 -0
- package/dist/styles/style.js.map +1 -0
- package/dist/utils/BatchQueue.d.ts +10 -0
- package/dist/utils/BatchQueue.js +25 -0
- package/dist/utils/BatchQueue.js.map +1 -0
- package/dist/utils/createFormulaCache.d.ts +3 -0
- package/dist/utils/createFormulaCache.js +81 -0
- package/dist/utils/createFormulaCache.js.map +1 -0
- package/dist/utils/findNearestLine.d.ts +13 -0
- package/dist/utils/findNearestLine.js +74 -0
- package/dist/utils/findNearestLine.js.map +1 -0
- package/dist/utils/findNearestLine.test.d.ts +1 -0
- package/dist/utils/findNearestLine.test.js +59 -0
- package/dist/utils/findNearestLine.test.js.map +1 -0
- package/dist/utils/getDragData.d.ts +1 -0
- package/dist/utils/getDragData.js +10 -0
- package/dist/utils/getDragData.js.map +1 -0
- package/dist/utils/getElementTagName.d.ts +3 -0
- package/dist/utils/getElementTagName.js +7 -0
- package/dist/utils/getElementTagName.js.map +1 -0
- package/dist/utils/nodes.d.ts +21 -0
- package/dist/utils/nodes.js +89 -0
- package/dist/utils/nodes.js.map +1 -0
- package/dist/utils/omitStyle.d.ts +2 -0
- package/dist/utils/omitStyle.js +13 -0
- package/dist/utils/omitStyle.js.map +1 -0
- package/dist/utils/rectHasPoint.d.ts +2 -0
- package/dist/utils/rectHasPoint.js +4 -0
- package/dist/utils/rectHasPoint.js.map +1 -0
- package/dist/utils/setAttribute.d.ts +4 -0
- package/dist/utils/setAttribute.js +57 -0
- package/dist/utils/setAttribute.js.map +1 -0
- package/dist/utils/tryStartViewTransition.d.ts +5 -0
- package/dist/utils/tryStartViewTransition.js +14 -0
- package/dist/utils/tryStartViewTransition.js.map +1 -0
- package/dist/utils/url.d.ts +2 -0
- package/dist/utils/url.js +36 -0
- package/dist/utils/url.js.map +1 -0
- package/package.json +25 -0
- package/src/api/createAPI.ts +375 -0
- package/src/api/createAPIv2.ts +931 -0
- package/src/components/createComponent.ts +280 -0
- package/src/components/createElement.ts +240 -0
- package/src/components/createNode.ts +381 -0
- package/src/components/createSlot.ts +61 -0
- package/src/components/createText.test.ts +117 -0
- package/src/components/createText.ts +104 -0
- package/src/components/renderComponent.ts +145 -0
- package/src/context/isContextProvider.ts +12 -0
- package/src/context/subscribeToContext.ts +135 -0
- package/src/custom-components/components.ts +1 -0
- package/src/custom-components/toddle-portal.ts +19 -0
- package/src/custom-element/ToddleComponent.ts +315 -0
- package/src/custom-element/defineComponents.ts +65 -0
- package/src/custom-element.main.ts +24 -0
- package/src/debug/logState.ts +30 -0
- package/src/editor/drag-drop/dragEnded.ts +75 -0
- package/src/editor/drag-drop/dragMove.ts +95 -0
- package/src/editor/drag-drop/dragReorder.ts +137 -0
- package/src/editor/drag-drop/dragStarted.ts +145 -0
- package/src/editor/drag-drop/dropHighlight.ts +82 -0
- package/src/editor/drag-drop/getInsertAreas.ts +235 -0
- package/src/editor/types.d.ts +36 -0
- package/src/editor-preview.main.ts +1782 -0
- package/src/events/handleAction.ts +387 -0
- package/src/page.main.ts +489 -0
- package/src/signal/signal.ts +74 -0
- package/src/styles/style.ts +254 -0
- package/src/types.d.ts +93 -0
- package/src/utils/BatchQueue.ts +24 -0
- package/src/utils/createFormulaCache.ts +96 -0
- package/src/utils/findNearestLine.test.ts +65 -0
- package/src/utils/findNearestLine.ts +92 -0
- package/src/utils/getDragData.ts +11 -0
- package/src/utils/getElementTagName.ts +14 -0
- package/src/utils/nodes.ts +125 -0
- package/src/utils/omitStyle.ts +19 -0
- package/src/utils/rectHasPoint.ts +5 -0
- package/src/utils/setAttribute.ts +56 -0
- package/src/utils/tryStartViewTransition.ts +32 -0
- package/src/utils/url.ts +45 -0
|
@@ -0,0 +1,686 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-floating-promises */
|
|
2
|
+
import { createApiEvent, createApiRequest, isApiError, requestHash, } from '@nordcraft/core/dist/api/api';
|
|
3
|
+
import { isEventStreamHeader, isImageHeader, isJsonHeader, isJsonStreamHeader, isTextHeader, } from '@nordcraft/core/dist/api/headers';
|
|
4
|
+
import { applyFormula } from '@nordcraft/core/dist/formula/formula';
|
|
5
|
+
import { omitPaths, sortObjectEntries, } from '@nordcraft/core/dist/utils/collections';
|
|
6
|
+
import { PROXY_URL_HEADER, validateUrl } from '@nordcraft/core/dist/utils/url';
|
|
7
|
+
import { handleAction } from '../events/handleAction';
|
|
8
|
+
/**
|
|
9
|
+
* Set up an api v2 for a component.
|
|
10
|
+
*/
|
|
11
|
+
export function createAPI(apiRequest, ctx) {
|
|
12
|
+
// If `__toddle` isn't found it is in a web component context. We behave as if the page isn't loaded.
|
|
13
|
+
let timer = null;
|
|
14
|
+
let api = { ...apiRequest };
|
|
15
|
+
function constructRequest(api) {
|
|
16
|
+
// Get baseUrl and validate it. (It wont be in web component context)
|
|
17
|
+
let baseUrl = window.origin;
|
|
18
|
+
try {
|
|
19
|
+
new URL(baseUrl);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
baseUrl = undefined;
|
|
23
|
+
}
|
|
24
|
+
return createApiRequest({
|
|
25
|
+
api,
|
|
26
|
+
formulaContext: getFormulaContext(api),
|
|
27
|
+
baseUrl,
|
|
28
|
+
defaultHeaders: undefined,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
// Create the formula context for the api
|
|
32
|
+
function getFormulaContext(api) {
|
|
33
|
+
// Use the general formula context to evaluate the arguments of the api
|
|
34
|
+
const formulaContext = {
|
|
35
|
+
data: ctx.dataSignal.get(),
|
|
36
|
+
component: ctx.component,
|
|
37
|
+
formulaCache: ctx.formulaCache,
|
|
38
|
+
root: ctx.root,
|
|
39
|
+
package: ctx.package,
|
|
40
|
+
toddle: ctx.toddle,
|
|
41
|
+
env: ctx.env,
|
|
42
|
+
};
|
|
43
|
+
// Make sure inputs are also available in the formula context
|
|
44
|
+
const evaluatedInputs = Object.entries(api.inputs).reduce((acc, [key, value]) => {
|
|
45
|
+
acc[key] = applyFormula(value.formula, formulaContext);
|
|
46
|
+
return acc;
|
|
47
|
+
}, {});
|
|
48
|
+
const data = {
|
|
49
|
+
...formulaContext.data,
|
|
50
|
+
ApiInputs: {
|
|
51
|
+
...evaluatedInputs,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
component: ctx.component,
|
|
56
|
+
formulaCache: ctx.formulaCache,
|
|
57
|
+
root: ctx.root,
|
|
58
|
+
package: ctx.package,
|
|
59
|
+
data,
|
|
60
|
+
toddle: ctx.toddle,
|
|
61
|
+
env: ctx.env,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function handleRedirectRules(api) {
|
|
65
|
+
for (const [ruleName, rule] of sortObjectEntries(api.redirectRules ?? {}, ([_, rule]) => rule.index)) {
|
|
66
|
+
const location = applyFormula(rule.formula, {
|
|
67
|
+
...getFormulaContext(api),
|
|
68
|
+
data: {
|
|
69
|
+
...getFormulaContext(api).data,
|
|
70
|
+
Apis: {
|
|
71
|
+
[api.name]: ctx.dataSignal.get().Apis?.[api.name],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
if (typeof location === 'string') {
|
|
76
|
+
const url = validateUrl(location, window.location.href);
|
|
77
|
+
if (url) {
|
|
78
|
+
if (ctx.env.runtime === 'preview') {
|
|
79
|
+
// Attempt to notify the parent about the failed navigation attempt
|
|
80
|
+
window.parent?.postMessage({ type: 'blockedNavigation', url: url.href }, '*');
|
|
81
|
+
return { name: ruleName, index: rule.index, url };
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
window.location.replace(url.href);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function triggerActions(eventName, api, data) {
|
|
91
|
+
switch (eventName) {
|
|
92
|
+
case 'message': {
|
|
93
|
+
const event = createApiEvent('message', data.body);
|
|
94
|
+
api.client?.onMessage?.actions?.forEach((action) => {
|
|
95
|
+
handleAction(action, {
|
|
96
|
+
...getFormulaContext(api).data,
|
|
97
|
+
...ctx.dataSignal.get(),
|
|
98
|
+
Event: event,
|
|
99
|
+
}, ctx, event);
|
|
100
|
+
});
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
case 'success': {
|
|
104
|
+
const event = createApiEvent('success', data.body);
|
|
105
|
+
api.client?.onCompleted?.actions?.forEach((action) => {
|
|
106
|
+
handleAction(action, {
|
|
107
|
+
...getFormulaContext(api).data,
|
|
108
|
+
...ctx.dataSignal.get(),
|
|
109
|
+
Event: event,
|
|
110
|
+
}, ctx, event);
|
|
111
|
+
});
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
case 'failed': {
|
|
115
|
+
const event = createApiEvent('failed', {
|
|
116
|
+
error: data.body,
|
|
117
|
+
status: data.status,
|
|
118
|
+
});
|
|
119
|
+
api.client?.onFailed?.actions?.forEach((action) => {
|
|
120
|
+
handleAction(action, {
|
|
121
|
+
...getFormulaContext(api).data,
|
|
122
|
+
...ctx.dataSignal.get(),
|
|
123
|
+
Event: event,
|
|
124
|
+
}, ctx, event);
|
|
125
|
+
});
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function apiSuccess(api, data, performance) {
|
|
131
|
+
const latestRequestStart = ctx.dataSignal.get().Apis?.[api.name]?.response?.performance?.requestStart;
|
|
132
|
+
if (typeof latestRequestStart === 'number' &&
|
|
133
|
+
latestRequestStart > (performance.requestStart ?? 0)) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
ctx.dataSignal.set({
|
|
137
|
+
...ctx.dataSignal.get(),
|
|
138
|
+
Apis: {
|
|
139
|
+
...ctx.dataSignal.get().Apis,
|
|
140
|
+
[api.name]: {
|
|
141
|
+
isLoading: false,
|
|
142
|
+
data: data.body,
|
|
143
|
+
error: null,
|
|
144
|
+
response: {
|
|
145
|
+
status: data.status,
|
|
146
|
+
headers: data.headers,
|
|
147
|
+
performance,
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
const appliedRedirectRule = handleRedirectRules(api);
|
|
153
|
+
if (appliedRedirectRule) {
|
|
154
|
+
ctx.dataSignal.set({
|
|
155
|
+
...ctx.dataSignal.get(),
|
|
156
|
+
Apis: {
|
|
157
|
+
...ctx.dataSignal.get().Apis,
|
|
158
|
+
[api.name]: {
|
|
159
|
+
isLoading: false,
|
|
160
|
+
data: data.body,
|
|
161
|
+
error: null,
|
|
162
|
+
response: {
|
|
163
|
+
status: data.status,
|
|
164
|
+
headers: data.headers,
|
|
165
|
+
performance,
|
|
166
|
+
...(ctx.env.runtime === 'preview'
|
|
167
|
+
? { debug: { appliedRedirectRule } }
|
|
168
|
+
: {}),
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function apiError(api, data, performance) {
|
|
176
|
+
const latestRequestStart = ctx.dataSignal.get().Apis?.[api.name]?.response?.performance?.requestStart;
|
|
177
|
+
if (typeof latestRequestStart === 'number' &&
|
|
178
|
+
latestRequestStart > (performance.requestStart ?? 0)) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
ctx.dataSignal.set({
|
|
182
|
+
...ctx.dataSignal.get(),
|
|
183
|
+
Apis: {
|
|
184
|
+
...ctx.dataSignal.get().Apis,
|
|
185
|
+
[api.name]: {
|
|
186
|
+
isLoading: false,
|
|
187
|
+
data: null,
|
|
188
|
+
error: data.body,
|
|
189
|
+
response: {
|
|
190
|
+
status: data.status,
|
|
191
|
+
headers: data.headers,
|
|
192
|
+
performance,
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
const appliedRedirectRule = handleRedirectRules(api);
|
|
198
|
+
if (appliedRedirectRule) {
|
|
199
|
+
ctx.dataSignal.set({
|
|
200
|
+
...ctx.dataSignal.get(),
|
|
201
|
+
Apis: {
|
|
202
|
+
...ctx.dataSignal.get().Apis,
|
|
203
|
+
[api.name]: {
|
|
204
|
+
isLoading: false,
|
|
205
|
+
data: null,
|
|
206
|
+
error: data.body,
|
|
207
|
+
response: {
|
|
208
|
+
status: data.status,
|
|
209
|
+
headers: data.headers,
|
|
210
|
+
performance,
|
|
211
|
+
...(ctx.env.runtime === 'preview'
|
|
212
|
+
? { debug: { appliedRedirectRule } }
|
|
213
|
+
: {}),
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Execute the request - potentially to the cloudflare Query proxy
|
|
221
|
+
async function execute(api, url, requestSettings) {
|
|
222
|
+
const run = async () => {
|
|
223
|
+
const performance = {
|
|
224
|
+
requestStart: Date.now(),
|
|
225
|
+
responseStart: null,
|
|
226
|
+
responseEnd: null,
|
|
227
|
+
};
|
|
228
|
+
ctx.dataSignal.set({
|
|
229
|
+
...ctx.dataSignal.get(),
|
|
230
|
+
Apis: {
|
|
231
|
+
...ctx.dataSignal.get().Apis,
|
|
232
|
+
[api.name]: {
|
|
233
|
+
isLoading: true,
|
|
234
|
+
data: ctx.dataSignal.get().Apis?.[api.name]?.data ?? null,
|
|
235
|
+
error: null,
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
let response;
|
|
240
|
+
try {
|
|
241
|
+
const proxy = api.server?.proxy
|
|
242
|
+
? (applyFormula(api.server.proxy.enabled.formula, getFormulaContext(api)) ?? false)
|
|
243
|
+
: false;
|
|
244
|
+
if (proxy === false) {
|
|
245
|
+
response = await fetch(url, requestSettings);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
const proxyUrl = `/.toddle/omvej/components/${encodeURIComponent(ctx.component.name)}/apis/${encodeURIComponent(ctx.component.name)}:${encodeURIComponent(api.name)}`;
|
|
249
|
+
const headers = new Headers(requestSettings.headers);
|
|
250
|
+
headers.set(PROXY_URL_HEADER, decodeURIComponent(url.href.replace(/\+/g, ' ')));
|
|
251
|
+
requestSettings.headers = headers;
|
|
252
|
+
response = await fetch(proxyUrl, requestSettings);
|
|
253
|
+
}
|
|
254
|
+
performance.responseStart = Date.now();
|
|
255
|
+
await handleResponse(api, response, performance);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
const body = error.cause
|
|
260
|
+
? { message: error.message, data: error.cause }
|
|
261
|
+
: error.message;
|
|
262
|
+
apiError(api, { body }, { ...performance, responseEnd: Date.now() });
|
|
263
|
+
triggerActions('failed', api, { body });
|
|
264
|
+
return Promise.reject(error);
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
// Debounce the request if needed
|
|
268
|
+
if (api.client?.debounce?.formula) {
|
|
269
|
+
return new Promise((resolve, reject) => {
|
|
270
|
+
if (typeof timer === 'number') {
|
|
271
|
+
clearTimeout(timer);
|
|
272
|
+
}
|
|
273
|
+
timer = setTimeout(() => {
|
|
274
|
+
run().then(resolve, reject);
|
|
275
|
+
}, applyFormula(api.client?.debounce?.formula, getFormulaContext(api)));
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
return run();
|
|
279
|
+
}
|
|
280
|
+
function handleResponse(api, res, performance) {
|
|
281
|
+
let parserMode = api.client?.parserMode ?? 'auto';
|
|
282
|
+
if (parserMode === 'auto') {
|
|
283
|
+
const contentType = res.headers.get('content-type');
|
|
284
|
+
if (isEventStreamHeader(contentType)) {
|
|
285
|
+
parserMode = 'event-stream';
|
|
286
|
+
}
|
|
287
|
+
else if (isJsonHeader(contentType)) {
|
|
288
|
+
parserMode = 'json';
|
|
289
|
+
}
|
|
290
|
+
else if (isTextHeader(contentType)) {
|
|
291
|
+
parserMode = 'text';
|
|
292
|
+
}
|
|
293
|
+
else if (isJsonStreamHeader(contentType)) {
|
|
294
|
+
parserMode = 'json-stream';
|
|
295
|
+
}
|
|
296
|
+
else if (isImageHeader(contentType)) {
|
|
297
|
+
parserMode = 'blob';
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
parserMode = 'text';
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
switch (parserMode) {
|
|
304
|
+
case 'text':
|
|
305
|
+
return textStreamResponse(api, res, performance);
|
|
306
|
+
case 'json':
|
|
307
|
+
return jsonResponse(api, res, performance);
|
|
308
|
+
case 'event-stream':
|
|
309
|
+
return eventStreamingResponse(api, res, performance);
|
|
310
|
+
case 'json-stream':
|
|
311
|
+
return jsonStreamResponse(api, res, performance);
|
|
312
|
+
case 'blob':
|
|
313
|
+
return blobResponse(api, res, performance);
|
|
314
|
+
default:
|
|
315
|
+
return textStreamResponse(api, res, performance);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function textStreamResponse(api, res, performance) {
|
|
319
|
+
return handleStreaming({
|
|
320
|
+
api,
|
|
321
|
+
res,
|
|
322
|
+
performance,
|
|
323
|
+
streamType: 'text',
|
|
324
|
+
useTextDecoder: true,
|
|
325
|
+
parseChunk: (chunk) => chunk,
|
|
326
|
+
parseChunksForData: (chunks) => chunks.join(''),
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
function jsonStreamResponse(api, res, performance) {
|
|
330
|
+
const parseChunk = (chunk) => {
|
|
331
|
+
let parsedData = chunk;
|
|
332
|
+
try {
|
|
333
|
+
parsedData = JSON.parse(chunk);
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
throw new Error('Error occurred while parsing the json chunk.', {
|
|
337
|
+
cause: parsedData,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
return parsedData;
|
|
341
|
+
};
|
|
342
|
+
return handleStreaming({
|
|
343
|
+
api,
|
|
344
|
+
res,
|
|
345
|
+
performance,
|
|
346
|
+
streamType: 'json',
|
|
347
|
+
useTextDecoder: true,
|
|
348
|
+
parseChunk,
|
|
349
|
+
parseChunksForData: (chunks) => [...chunks],
|
|
350
|
+
delimiters: ['\r\n', '\n'],
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
async function jsonResponse(api, res, performance) {
|
|
354
|
+
const body = await res.json();
|
|
355
|
+
const status = {
|
|
356
|
+
data: body,
|
|
357
|
+
isLoading: false,
|
|
358
|
+
error: null,
|
|
359
|
+
response: {
|
|
360
|
+
status: res.status,
|
|
361
|
+
headers: Object.fromEntries(res.headers.entries()),
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
return endResponse(api, status, performance);
|
|
365
|
+
}
|
|
366
|
+
async function blobResponse(api, res, performance) {
|
|
367
|
+
const blob = await res.blob();
|
|
368
|
+
const status = {
|
|
369
|
+
isLoading: false,
|
|
370
|
+
data: URL.createObjectURL(blob),
|
|
371
|
+
error: null,
|
|
372
|
+
response: {
|
|
373
|
+
status: res.status,
|
|
374
|
+
headers: Object.fromEntries(res.headers.entries()),
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
return endResponse(api, status, performance);
|
|
378
|
+
}
|
|
379
|
+
function eventStreamingResponse(api, res, performance) {
|
|
380
|
+
const parseChunk = (chunk) => {
|
|
381
|
+
const event = chunk.match(/event: (.*)/)?.[1] ?? 'message';
|
|
382
|
+
const data = chunk.match(/data: (.*)/)?.[1] ?? '';
|
|
383
|
+
const id = chunk.match(/id: (.*)/)?.[1];
|
|
384
|
+
const retry = chunk.match(/retry: (.*)/)?.[1];
|
|
385
|
+
let parsedData = data;
|
|
386
|
+
try {
|
|
387
|
+
parsedData = JSON.parse(data ?? '');
|
|
388
|
+
// eslint-disable-next-line no-empty
|
|
389
|
+
}
|
|
390
|
+
catch { }
|
|
391
|
+
const returnData = {
|
|
392
|
+
event,
|
|
393
|
+
data: parsedData,
|
|
394
|
+
...(id ? { id } : {}),
|
|
395
|
+
...(retry ? { retry } : {}),
|
|
396
|
+
};
|
|
397
|
+
return returnData;
|
|
398
|
+
};
|
|
399
|
+
return handleStreaming({
|
|
400
|
+
api,
|
|
401
|
+
res,
|
|
402
|
+
performance,
|
|
403
|
+
streamType: 'event',
|
|
404
|
+
useTextDecoder: true,
|
|
405
|
+
parseChunk,
|
|
406
|
+
parseChunksForData: (chunks) => [...chunks],
|
|
407
|
+
delimiters: ['\n\n', '\r\n\r\n'],
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
async function handleStreaming({ api, res, performance, streamType, useTextDecoder, parseChunk, parseChunksForData, delimiters, // There can be various delimiters for the same stream. SSE might use both \n\n and \r\n\r\n
|
|
411
|
+
}) {
|
|
412
|
+
const chunks = {
|
|
413
|
+
chunks: [],
|
|
414
|
+
currentChunk: '',
|
|
415
|
+
// Function to add a chunk to the chunks array and emits the data to the onMessage event
|
|
416
|
+
add(chunk) {
|
|
417
|
+
const parsedChunk = parseChunk(chunk);
|
|
418
|
+
this.chunks.push(parsedChunk);
|
|
419
|
+
// Only emit the data if there are any listeners
|
|
420
|
+
if (parsedChunk) {
|
|
421
|
+
ctx.dataSignal.set({
|
|
422
|
+
...ctx.dataSignal.get(),
|
|
423
|
+
Apis: {
|
|
424
|
+
...ctx.dataSignal.get().Apis,
|
|
425
|
+
[api.name]: {
|
|
426
|
+
isLoading: true,
|
|
427
|
+
data: parseChunksForData(this.chunks),
|
|
428
|
+
error: null,
|
|
429
|
+
response: {
|
|
430
|
+
headers: Object.fromEntries(res.headers.entries()),
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
if ((api.client?.onMessage?.actions ?? []).length > 0) {
|
|
436
|
+
triggerActions('message', api, { body: parsedChunk });
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
// Function to process a chunk and split it by the delimiter.
|
|
441
|
+
processChunk(chunk) {
|
|
442
|
+
const delimiter = delimiters?.find((d) => chunk.includes(d));
|
|
443
|
+
const concatenated = this.currentChunk + chunk;
|
|
444
|
+
const split = delimiter ? concatenated.split(delimiter) : [concatenated];
|
|
445
|
+
this.currentChunk = split.pop() ?? '';
|
|
446
|
+
split.forEach((c) => this.add(c));
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
const reader = useTextDecoder
|
|
450
|
+
? res.body?.pipeThrough(new TextDecoderStream()).getReader()
|
|
451
|
+
: res.body?.getReader();
|
|
452
|
+
while (reader) {
|
|
453
|
+
const { done, value } = await reader.read();
|
|
454
|
+
if (done) {
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
if (delimiters) {
|
|
458
|
+
chunks.processChunk(value);
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
chunks.add(value);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// First make sure theres no remaining chunk
|
|
465
|
+
if (chunks.currentChunk) {
|
|
466
|
+
chunks.add(chunks.currentChunk);
|
|
467
|
+
}
|
|
468
|
+
const status = {
|
|
469
|
+
isLoading: false,
|
|
470
|
+
data: chunks.chunks,
|
|
471
|
+
error: null,
|
|
472
|
+
response: {
|
|
473
|
+
status: res.status,
|
|
474
|
+
headers: Object.fromEntries(res.headers.entries()),
|
|
475
|
+
},
|
|
476
|
+
};
|
|
477
|
+
try {
|
|
478
|
+
if (streamType === 'json') {
|
|
479
|
+
const parsed = JSON.parse(chunks.chunks.join(''));
|
|
480
|
+
status.data = parsed;
|
|
481
|
+
}
|
|
482
|
+
else if (streamType === 'text') {
|
|
483
|
+
status.data = chunks.chunks.join('');
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
throw new Error('Error occurred while parsing the json chunk.', {
|
|
488
|
+
cause: chunks.chunks.join(''),
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
return endResponse(api, status, performance);
|
|
492
|
+
}
|
|
493
|
+
function endResponse(api, apiStatus, performance) {
|
|
494
|
+
performance.responseEnd = Date.now();
|
|
495
|
+
const data = {
|
|
496
|
+
body: apiStatus.data,
|
|
497
|
+
status: apiStatus.response?.status,
|
|
498
|
+
headers: apiStatus.response?.headers ?? undefined,
|
|
499
|
+
};
|
|
500
|
+
const isError = isApiError({
|
|
501
|
+
apiName: api.name,
|
|
502
|
+
response: {
|
|
503
|
+
body: data.body,
|
|
504
|
+
ok: Boolean(!apiStatus.error &&
|
|
505
|
+
apiStatus.response?.status &&
|
|
506
|
+
apiStatus.response.status < 400),
|
|
507
|
+
status: data.status,
|
|
508
|
+
headers: data.headers,
|
|
509
|
+
},
|
|
510
|
+
formulaContext: getFormulaContext(api),
|
|
511
|
+
errorFormula: api.isError,
|
|
512
|
+
performance,
|
|
513
|
+
});
|
|
514
|
+
if (isError) {
|
|
515
|
+
if (!data.body && apiStatus.error) {
|
|
516
|
+
data.body = apiStatus.error;
|
|
517
|
+
}
|
|
518
|
+
apiError(api, data, performance);
|
|
519
|
+
triggerActions('failed', api, data);
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
apiSuccess(api, data, performance);
|
|
523
|
+
triggerActions('success', api, data);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
function getApiForComparison(api) {
|
|
527
|
+
return omitPaths(api, [
|
|
528
|
+
['client', 'onCompleted'],
|
|
529
|
+
['client', 'onFailed'],
|
|
530
|
+
['client', 'onMessage'],
|
|
531
|
+
['service'],
|
|
532
|
+
['server', 'ssr'],
|
|
533
|
+
]);
|
|
534
|
+
}
|
|
535
|
+
let payloadSignal;
|
|
536
|
+
// eslint-disable-next-line prefer-const
|
|
537
|
+
payloadSignal = ctx.dataSignal.map((_) => {
|
|
538
|
+
const payloadContext = getFormulaContext(api);
|
|
539
|
+
return {
|
|
540
|
+
request: constructRequest(api),
|
|
541
|
+
api: getApiForComparison(api),
|
|
542
|
+
autoFetch: api.autoFetch
|
|
543
|
+
? applyFormula(api.autoFetch, payloadContext)
|
|
544
|
+
: false,
|
|
545
|
+
proxy: applyFormula(api.server?.proxy?.enabled.formula, payloadContext),
|
|
546
|
+
};
|
|
547
|
+
});
|
|
548
|
+
payloadSignal.subscribe(async (_) => {
|
|
549
|
+
if (api.autoFetch && applyFormula(api.autoFetch, getFormulaContext(api))) {
|
|
550
|
+
// Ensure we only use caching if the page is currently loading
|
|
551
|
+
if ((window?.__toddle?.isPageLoaded ?? false) === false) {
|
|
552
|
+
const { url, requestSettings } = constructRequest(api);
|
|
553
|
+
const cacheKey = requestHash(url, requestSettings);
|
|
554
|
+
const cacheMatch = ctx.toddle.pageState.Apis?.[cacheKey];
|
|
555
|
+
if (cacheMatch) {
|
|
556
|
+
if (cacheMatch.error) {
|
|
557
|
+
apiError(api, {
|
|
558
|
+
body: cacheMatch.error,
|
|
559
|
+
status: cacheMatch.response?.status,
|
|
560
|
+
headers: cacheMatch.response?.headers ?? undefined,
|
|
561
|
+
}, {
|
|
562
|
+
requestStart: cacheMatch.response?.performance?.requestStart ?? null,
|
|
563
|
+
responseStart: cacheMatch.response?.performance?.responseStart ?? null,
|
|
564
|
+
responseEnd: cacheMatch.response?.performance?.responseEnd ?? null,
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
apiSuccess(api, {
|
|
569
|
+
body: cacheMatch.data,
|
|
570
|
+
status: cacheMatch.response?.status,
|
|
571
|
+
headers: cacheMatch.response?.headers ?? undefined,
|
|
572
|
+
}, {
|
|
573
|
+
requestStart: cacheMatch.response?.performance?.requestStart ?? null,
|
|
574
|
+
responseStart: cacheMatch.response?.performance?.responseStart ?? null,
|
|
575
|
+
responseEnd: cacheMatch.response?.performance?.responseEnd ?? null,
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
else {
|
|
580
|
+
// Execute will set the initial status of the api in the dataSignal
|
|
581
|
+
await execute(api, url, requestSettings);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
else {
|
|
585
|
+
// Execute will set the initial status of the api in the dataSignal
|
|
586
|
+
const { url, requestSettings } = constructRequest(api);
|
|
587
|
+
await execute(api, url, requestSettings);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
ctx.dataSignal.update((data) => {
|
|
592
|
+
return {
|
|
593
|
+
...data,
|
|
594
|
+
Apis: {
|
|
595
|
+
...(data.Apis ?? {}),
|
|
596
|
+
[api.name]: {
|
|
597
|
+
isLoading: false,
|
|
598
|
+
data: null,
|
|
599
|
+
error: null,
|
|
600
|
+
},
|
|
601
|
+
},
|
|
602
|
+
};
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
return {
|
|
607
|
+
fetch: ({ actionInputs, actionModels, }) => {
|
|
608
|
+
// Inputs might already be evaluated. If they are we add them as a value formula to be evaluated later.
|
|
609
|
+
const inputs = Object.entries(actionInputs ?? {}).reduce((acc, [inputName, input]) => {
|
|
610
|
+
if (input !== null && typeof input === 'object' && 'formula' in input) {
|
|
611
|
+
acc[inputName] = input;
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
acc[inputName] = {
|
|
615
|
+
formula: { type: 'value', value: input },
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
return acc;
|
|
619
|
+
}, {});
|
|
620
|
+
const apiWithInputsAndActions = {
|
|
621
|
+
...api,
|
|
622
|
+
inputs: { ...api.inputs, ...inputs },
|
|
623
|
+
client: {
|
|
624
|
+
...api.client,
|
|
625
|
+
parserMode: api.client?.parserMode ?? 'auto',
|
|
626
|
+
onCompleted: {
|
|
627
|
+
trigger: api.client?.onCompleted?.trigger ?? 'success',
|
|
628
|
+
actions: [
|
|
629
|
+
...(api.client?.onCompleted?.actions ?? []),
|
|
630
|
+
...(actionModels?.onCompleted ?? []),
|
|
631
|
+
],
|
|
632
|
+
},
|
|
633
|
+
onFailed: {
|
|
634
|
+
trigger: api.client?.onFailed?.trigger ?? 'failed',
|
|
635
|
+
actions: [
|
|
636
|
+
...(api.client?.onFailed?.actions ?? []),
|
|
637
|
+
...(actionModels?.onFailed ?? []),
|
|
638
|
+
],
|
|
639
|
+
},
|
|
640
|
+
onMessage: {
|
|
641
|
+
trigger: api.client?.onMessage?.trigger ?? 'message',
|
|
642
|
+
actions: [
|
|
643
|
+
...(api.client?.onMessage?.actions ?? []),
|
|
644
|
+
...(actionModels?.onMessage ?? []),
|
|
645
|
+
],
|
|
646
|
+
},
|
|
647
|
+
},
|
|
648
|
+
};
|
|
649
|
+
const { url, requestSettings } = constructRequest(apiWithInputsAndActions);
|
|
650
|
+
return execute(apiWithInputsAndActions, url, requestSettings);
|
|
651
|
+
},
|
|
652
|
+
update: (newApi) => {
|
|
653
|
+
api = newApi;
|
|
654
|
+
const updateContext = getFormulaContext(api);
|
|
655
|
+
const autoFetch = api.autoFetch && applyFormula(api.autoFetch, updateContext);
|
|
656
|
+
if (autoFetch) {
|
|
657
|
+
payloadSignal?.set({
|
|
658
|
+
request: constructRequest(newApi),
|
|
659
|
+
api: getApiForComparison(newApi),
|
|
660
|
+
autoFetch,
|
|
661
|
+
proxy: applyFormula(newApi.server?.proxy?.enabled.formula, updateContext),
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
},
|
|
665
|
+
triggerActions: () => {
|
|
666
|
+
const apiData = ctx.dataSignal.get().Apis?.[api.name];
|
|
667
|
+
if (apiData === undefined ||
|
|
668
|
+
(apiData.data === null && apiData.error === null)) {
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
if (apiData.error) {
|
|
672
|
+
triggerActions('failed', api, {
|
|
673
|
+
body: apiData.error,
|
|
674
|
+
status: apiData.response?.status,
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
triggerActions('success', api, {
|
|
679
|
+
body: apiData.data,
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
},
|
|
683
|
+
destroy: () => payloadSignal?.destroy(),
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
//# sourceMappingURL=createAPIv2.js.map
|