@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.
Files changed (168) hide show
  1. package/README.md +5 -0
  2. package/dist/api/createAPI.d.ts +20 -0
  3. package/dist/api/createAPI.js +319 -0
  4. package/dist/api/createAPI.js.map +1 -0
  5. package/dist/api/createAPIv2.d.ts +7 -0
  6. package/dist/api/createAPIv2.js +686 -0
  7. package/dist/api/createAPIv2.js.map +1 -0
  8. package/dist/components/createComponent.d.ts +13 -0
  9. package/dist/components/createComponent.js +216 -0
  10. package/dist/components/createComponent.js.map +1 -0
  11. package/dist/components/createElement.d.ts +3 -0
  12. package/dist/components/createElement.js +208 -0
  13. package/dist/components/createElement.js.map +1 -0
  14. package/dist/components/createNode.d.ts +22 -0
  15. package/dist/components/createNode.js +272 -0
  16. package/dist/components/createNode.js.map +1 -0
  17. package/dist/components/createSlot.d.ts +3 -0
  18. package/dist/components/createSlot.js +49 -0
  19. package/dist/components/createSlot.js.map +1 -0
  20. package/dist/components/createText.d.ts +23 -0
  21. package/dist/components/createText.js +68 -0
  22. package/dist/components/createText.js.map +1 -0
  23. package/dist/components/createText.test.d.ts +1 -0
  24. package/dist/components/createText.test.js +113 -0
  25. package/dist/components/createText.test.js.map +1 -0
  26. package/dist/components/renderComponent.d.ts +34 -0
  27. package/dist/components/renderComponent.js +66 -0
  28. package/dist/components/renderComponent.js.map +1 -0
  29. package/dist/context/isContextProvider.d.ts +2 -0
  30. package/dist/context/isContextProvider.js +5 -0
  31. package/dist/context/isContextProvider.js.map +1 -0
  32. package/dist/context/subscribeToContext.d.ts +4 -0
  33. package/dist/context/subscribeToContext.js +93 -0
  34. package/dist/context/subscribeToContext.js.map +1 -0
  35. package/dist/custom-components/components.d.ts +1 -0
  36. package/dist/custom-components/components.js +2 -0
  37. package/dist/custom-components/components.js.map +1 -0
  38. package/dist/custom-components/toddle-portal.d.ts +6 -0
  39. package/dist/custom-components/toddle-portal.js +20 -0
  40. package/dist/custom-components/toddle-portal.js.map +1 -0
  41. package/dist/custom-element/ToddleComponent.d.ts +37 -0
  42. package/dist/custom-element/ToddleComponent.js +244 -0
  43. package/dist/custom-element/ToddleComponent.js.map +1 -0
  44. package/dist/custom-element/defineComponents.d.ts +26 -0
  45. package/dist/custom-element/defineComponents.js +42 -0
  46. package/dist/custom-element/defineComponents.js.map +1 -0
  47. package/dist/custom-element.main.d.ts +3 -0
  48. package/dist/custom-element.main.esm.js +266 -0
  49. package/dist/custom-element.main.esm.js.map +7 -0
  50. package/dist/custom-element.main.js +14 -0
  51. package/dist/custom-element.main.js.map +1 -0
  52. package/dist/debug/logState.d.ts +4 -0
  53. package/dist/debug/logState.js +19 -0
  54. package/dist/debug/logState.js.map +1 -0
  55. package/dist/editor/drag-drop/dragEnded.d.ts +2 -0
  56. package/dist/editor/drag-drop/dragEnded.js +56 -0
  57. package/dist/editor/drag-drop/dragEnded.js.map +1 -0
  58. package/dist/editor/drag-drop/dragMove.d.ts +3 -0
  59. package/dist/editor/drag-drop/dragMove.js +74 -0
  60. package/dist/editor/drag-drop/dragMove.js.map +1 -0
  61. package/dist/editor/drag-drop/dragReorder.d.ts +3 -0
  62. package/dist/editor/drag-drop/dragReorder.js +92 -0
  63. package/dist/editor/drag-drop/dragReorder.js.map +1 -0
  64. package/dist/editor/drag-drop/dragStarted.d.ts +9 -0
  65. package/dist/editor/drag-drop/dragStarted.js +100 -0
  66. package/dist/editor/drag-drop/dragStarted.js.map +1 -0
  67. package/dist/editor/drag-drop/dropHighlight.d.ts +16 -0
  68. package/dist/editor/drag-drop/dropHighlight.js +50 -0
  69. package/dist/editor/drag-drop/dropHighlight.js.map +1 -0
  70. package/dist/editor/drag-drop/getInsertAreas.d.ts +20 -0
  71. package/dist/editor/drag-drop/getInsertAreas.js +220 -0
  72. package/dist/editor/drag-drop/getInsertAreas.js.map +1 -0
  73. package/dist/editor-preview.main.d.ts +19 -0
  74. package/dist/editor-preview.main.js +1303 -0
  75. package/dist/editor-preview.main.js.map +1 -0
  76. package/dist/events/handleAction.d.ts +3 -0
  77. package/dist/events/handleAction.js +307 -0
  78. package/dist/events/handleAction.js.map +1 -0
  79. package/dist/page.main.d.ts +7 -0
  80. package/dist/page.main.esm.js +8 -0
  81. package/dist/page.main.esm.js.map +7 -0
  82. package/dist/page.main.js +395 -0
  83. package/dist/page.main.js.map +1 -0
  84. package/dist/signal/signal.d.ts +19 -0
  85. package/dist/signal/signal.js +65 -0
  86. package/dist/signal/signal.js.map +1 -0
  87. package/dist/styles/style.d.ts +4 -0
  88. package/dist/styles/style.js +196 -0
  89. package/dist/styles/style.js.map +1 -0
  90. package/dist/utils/BatchQueue.d.ts +10 -0
  91. package/dist/utils/BatchQueue.js +25 -0
  92. package/dist/utils/BatchQueue.js.map +1 -0
  93. package/dist/utils/createFormulaCache.d.ts +3 -0
  94. package/dist/utils/createFormulaCache.js +81 -0
  95. package/dist/utils/createFormulaCache.js.map +1 -0
  96. package/dist/utils/findNearestLine.d.ts +13 -0
  97. package/dist/utils/findNearestLine.js +74 -0
  98. package/dist/utils/findNearestLine.js.map +1 -0
  99. package/dist/utils/findNearestLine.test.d.ts +1 -0
  100. package/dist/utils/findNearestLine.test.js +59 -0
  101. package/dist/utils/findNearestLine.test.js.map +1 -0
  102. package/dist/utils/getDragData.d.ts +1 -0
  103. package/dist/utils/getDragData.js +10 -0
  104. package/dist/utils/getDragData.js.map +1 -0
  105. package/dist/utils/getElementTagName.d.ts +3 -0
  106. package/dist/utils/getElementTagName.js +7 -0
  107. package/dist/utils/getElementTagName.js.map +1 -0
  108. package/dist/utils/nodes.d.ts +21 -0
  109. package/dist/utils/nodes.js +89 -0
  110. package/dist/utils/nodes.js.map +1 -0
  111. package/dist/utils/omitStyle.d.ts +2 -0
  112. package/dist/utils/omitStyle.js +13 -0
  113. package/dist/utils/omitStyle.js.map +1 -0
  114. package/dist/utils/rectHasPoint.d.ts +2 -0
  115. package/dist/utils/rectHasPoint.js +4 -0
  116. package/dist/utils/rectHasPoint.js.map +1 -0
  117. package/dist/utils/setAttribute.d.ts +4 -0
  118. package/dist/utils/setAttribute.js +57 -0
  119. package/dist/utils/setAttribute.js.map +1 -0
  120. package/dist/utils/tryStartViewTransition.d.ts +5 -0
  121. package/dist/utils/tryStartViewTransition.js +14 -0
  122. package/dist/utils/tryStartViewTransition.js.map +1 -0
  123. package/dist/utils/url.d.ts +2 -0
  124. package/dist/utils/url.js +36 -0
  125. package/dist/utils/url.js.map +1 -0
  126. package/package.json +25 -0
  127. package/src/api/createAPI.ts +375 -0
  128. package/src/api/createAPIv2.ts +931 -0
  129. package/src/components/createComponent.ts +280 -0
  130. package/src/components/createElement.ts +240 -0
  131. package/src/components/createNode.ts +381 -0
  132. package/src/components/createSlot.ts +61 -0
  133. package/src/components/createText.test.ts +117 -0
  134. package/src/components/createText.ts +104 -0
  135. package/src/components/renderComponent.ts +145 -0
  136. package/src/context/isContextProvider.ts +12 -0
  137. package/src/context/subscribeToContext.ts +135 -0
  138. package/src/custom-components/components.ts +1 -0
  139. package/src/custom-components/toddle-portal.ts +19 -0
  140. package/src/custom-element/ToddleComponent.ts +315 -0
  141. package/src/custom-element/defineComponents.ts +65 -0
  142. package/src/custom-element.main.ts +24 -0
  143. package/src/debug/logState.ts +30 -0
  144. package/src/editor/drag-drop/dragEnded.ts +75 -0
  145. package/src/editor/drag-drop/dragMove.ts +95 -0
  146. package/src/editor/drag-drop/dragReorder.ts +137 -0
  147. package/src/editor/drag-drop/dragStarted.ts +145 -0
  148. package/src/editor/drag-drop/dropHighlight.ts +82 -0
  149. package/src/editor/drag-drop/getInsertAreas.ts +235 -0
  150. package/src/editor/types.d.ts +36 -0
  151. package/src/editor-preview.main.ts +1782 -0
  152. package/src/events/handleAction.ts +387 -0
  153. package/src/page.main.ts +489 -0
  154. package/src/signal/signal.ts +74 -0
  155. package/src/styles/style.ts +254 -0
  156. package/src/types.d.ts +93 -0
  157. package/src/utils/BatchQueue.ts +24 -0
  158. package/src/utils/createFormulaCache.ts +96 -0
  159. package/src/utils/findNearestLine.test.ts +65 -0
  160. package/src/utils/findNearestLine.ts +92 -0
  161. package/src/utils/getDragData.ts +11 -0
  162. package/src/utils/getElementTagName.ts +14 -0
  163. package/src/utils/nodes.ts +125 -0
  164. package/src/utils/omitStyle.ts +19 -0
  165. package/src/utils/rectHasPoint.ts +5 -0
  166. package/src/utils/setAttribute.ts +56 -0
  167. package/src/utils/tryStartViewTransition.ts +32 -0
  168. 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