@flow-conductor/core 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/build/index.js ADDED
@@ -0,0 +1,667 @@
1
+ // src/utils/url-validator.ts
2
+ var SSRFError = class extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "SSRFError";
6
+ }
7
+ };
8
+ var PRIVATE_IP_RANGES = [
9
+ // IPv4 private ranges
10
+ /^10\./,
11
+ /^172\.(1[6-9]|2[0-9]|3[01])\./,
12
+ /^192\.168\./,
13
+ // IPv6 private ranges
14
+ /^fc00:/i,
15
+ /^fe80:/i,
16
+ /^::1$/,
17
+ /^fd/
18
+ ];
19
+ function validateUrl(url, options = {}) {
20
+ const {
21
+ allowPrivateIPs = false,
22
+ allowLocalhost = false,
23
+ allowedProtocols = ["http:", "https:"],
24
+ disableValidation = false
25
+ } = options;
26
+ if (disableValidation) {
27
+ return;
28
+ }
29
+ if (!url || typeof url !== "string") {
30
+ throw new SSRFError("URL must be a non-empty string");
31
+ }
32
+ let parsedUrl;
33
+ try {
34
+ parsedUrl = new URL(url);
35
+ } catch {
36
+ throw new SSRFError(`Invalid URL format: ${url}`);
37
+ }
38
+ const protocol = parsedUrl.protocol.toLowerCase();
39
+ if (!allowedProtocols.includes(protocol)) {
40
+ throw new SSRFError(
41
+ `Protocol "${protocol}" is not allowed. Only ${allowedProtocols.join(", ")} are permitted.`
42
+ );
43
+ }
44
+ const hostname = parsedUrl.hostname.toLowerCase();
45
+ const normalizedHostname = hostname.replace(/^\[|\]$/g, "");
46
+ const isLocalhost = normalizedHostname === "localhost" || normalizedHostname === "127.0.0.1" || normalizedHostname === "::1" || normalizedHostname.startsWith("127.") || normalizedHostname === "0.0.0.0";
47
+ if (isLocalhost && !allowLocalhost) {
48
+ throw new SSRFError(
49
+ "Localhost addresses are not allowed for security reasons. Set allowLocalhost=true to override."
50
+ );
51
+ }
52
+ if (!allowPrivateIPs) {
53
+ if (/^\d+\.\d+\.\d+\.\d+$/.test(normalizedHostname)) {
54
+ const parts = normalizedHostname.split(".").map(Number);
55
+ const [a, b] = parts;
56
+ if (a === 10) {
57
+ throw new SSRFError(
58
+ "Private IP addresses (10.x.x.x) are not allowed for security reasons."
59
+ );
60
+ }
61
+ if (a === 172 && b >= 16 && b <= 31) {
62
+ throw new SSRFError(
63
+ "Private IP addresses (172.16-31.x.x) are not allowed for security reasons."
64
+ );
65
+ }
66
+ if (a === 192 && b === 168) {
67
+ throw new SSRFError(
68
+ "Private IP addresses (192.168.x.x) are not allowed for security reasons."
69
+ );
70
+ }
71
+ if (a === 169 && b === 254) {
72
+ throw new SSRFError(
73
+ "Link-local addresses (169.254.x.x) are not allowed for security reasons."
74
+ );
75
+ }
76
+ }
77
+ const isPrivateIP = PRIVATE_IP_RANGES.some(
78
+ (range) => range.test(normalizedHostname)
79
+ );
80
+ if (isPrivateIP) {
81
+ throw new SSRFError(
82
+ "Private/internal IP addresses are not allowed for security reasons. Set allowPrivateIPs=true to override."
83
+ );
84
+ }
85
+ }
86
+ }
87
+
88
+ // src/request-adapter.ts
89
+ var RequestAdapter = class {
90
+ /**
91
+ * Creates a new RequestAdapter instance.
92
+ *
93
+ * @param urlValidationOptions - Options for URL validation to prevent SSRF attacks
94
+ */
95
+ constructor(urlValidationOptions = {}) {
96
+ this.urlValidationOptions = urlValidationOptions;
97
+ }
98
+ /**
99
+ * Type-safe getter for the execution result.
100
+ * Allows casting the result to a specific type.
101
+ *
102
+ * @template T - The desired result type
103
+ * @param result - The execution result to cast
104
+ * @returns The result cast to type T
105
+ */
106
+ getResult(result) {
107
+ return result;
108
+ }
109
+ /**
110
+ * Executes a request with URL validation.
111
+ * Validates the URL to prevent SSRF attacks before creating the request.
112
+ *
113
+ * @param requestConfig - The request configuration object
114
+ * @returns A promise that resolves to the execution result
115
+ * @throws {SSRFError} If the URL is invalid or potentially dangerous
116
+ */
117
+ executeRequest(requestConfig) {
118
+ validateUrl(requestConfig.url, this.urlValidationOptions);
119
+ return this.createRequest(requestConfig);
120
+ }
121
+ };
122
+
123
+ // src/request-manager.ts
124
+ var RequestFlow = class {
125
+ constructor() {
126
+ /**
127
+ * List of pipeline stages to execute
128
+ */
129
+ this.requestList = [];
130
+ }
131
+ /**
132
+ * Sets the request adapter to use for executing HTTP requests.
133
+ *
134
+ * @param adapter - The request adapter instance
135
+ * @returns The current RequestFlow instance for method chaining
136
+ */
137
+ setRequestAdapter(adapter) {
138
+ this.adapter = adapter;
139
+ return this;
140
+ }
141
+ /**
142
+ * Adds multiple pipeline stages to the request list.
143
+ *
144
+ * @param requestList - Array of pipeline stages to add
145
+ * @returns The current RequestFlow instance for method chaining
146
+ */
147
+ addAll(requestList = []) {
148
+ this.requestList = this.requestList.concat(requestList);
149
+ return this;
150
+ }
151
+ /**
152
+ * Sets an error handler callback that will be called when an error occurs during execution.
153
+ *
154
+ * @param errorHandler - Function to handle errors
155
+ * @returns The current RequestFlow instance for method chaining
156
+ */
157
+ withErrorHandler(errorHandler) {
158
+ this.errorHandler = errorHandler;
159
+ return this;
160
+ }
161
+ /**
162
+ * Sets a result handler callback that will be called with the execution result.
163
+ *
164
+ * @param resultHandler - Function to handle results
165
+ * @returns The current RequestFlow instance for method chaining
166
+ */
167
+ withResultHandler(resultHandler) {
168
+ this.resultHandler = resultHandler;
169
+ return this;
170
+ }
171
+ /**
172
+ * Sets a finish handler callback that will be called after execution completes (success or failure).
173
+ *
174
+ * @param finishHandler - Function to execute on completion
175
+ * @returns The current RequestFlow instance for method chaining
176
+ */
177
+ withFinishHandler(finishHandler) {
178
+ this.finishHandler = finishHandler;
179
+ return this;
180
+ }
181
+ };
182
+
183
+ // src/utils/retry-utils.ts
184
+ function getErrorStatus(error) {
185
+ if (typeof error.response !== "undefined" && typeof error.response.status === "number") {
186
+ return error.response.status;
187
+ }
188
+ if (typeof error.status === "number") {
189
+ return error.status;
190
+ }
191
+ if (typeof error.statusCode === "number") {
192
+ return error.statusCode;
193
+ }
194
+ return void 0;
195
+ }
196
+ function isNetworkError(error) {
197
+ const networkErrorNames = [
198
+ "TypeError",
199
+ "NetworkError",
200
+ "TimeoutError",
201
+ "AbortError",
202
+ "ECONNREFUSED",
203
+ "ENOTFOUND",
204
+ "ETIMEDOUT"
205
+ ];
206
+ if (networkErrorNames.includes(error.name)) {
207
+ return true;
208
+ }
209
+ const networkKeywords = [
210
+ "network",
211
+ "connection",
212
+ "timeout",
213
+ "fetch",
214
+ "failed to fetch",
215
+ "ECONNREFUSED",
216
+ "ENOTFOUND",
217
+ "ETIMEDOUT"
218
+ ];
219
+ const errorMessage = error.message?.toLowerCase() || "";
220
+ return networkKeywords.some((keyword) => errorMessage.includes(keyword));
221
+ }
222
+ function defaultRetryCondition(error) {
223
+ return isNetworkError(error);
224
+ }
225
+ function retryOnStatusCodes(...statusCodes) {
226
+ return (error) => {
227
+ const status = getErrorStatus(error);
228
+ return status !== void 0 && statusCodes.includes(status);
229
+ };
230
+ }
231
+ function retryOnNetworkOrStatusCodes(...statusCodes) {
232
+ return (error) => {
233
+ if (isNetworkError(error)) {
234
+ return true;
235
+ }
236
+ const status = getErrorStatus(error);
237
+ return status !== void 0 && statusCodes.includes(status);
238
+ };
239
+ }
240
+
241
+ // src/utils/chunk-processor.ts
242
+ async function processStream(stream, config) {
243
+ const { chunkHandler, encoding = "utf-8", accumulate = false } = config;
244
+ const reader = stream.getReader();
245
+ const decoder = new TextDecoder(encoding);
246
+ let accumulatedData;
247
+ let chunkIndex = 0;
248
+ let totalBytesRead = 0;
249
+ try {
250
+ while (true) {
251
+ const { done, value } = await reader.read();
252
+ if (done) {
253
+ if (accumulatedData !== void 0 && accumulatedData.length > 0) {
254
+ await processChunk(
255
+ accumulatedData,
256
+ chunkHandler,
257
+ chunkIndex,
258
+ true,
259
+ totalBytesRead
260
+ );
261
+ }
262
+ break;
263
+ }
264
+ totalBytesRead += value.length;
265
+ if (accumulate) {
266
+ if (accumulatedData === void 0) {
267
+ accumulatedData = value;
268
+ } else if (typeof accumulatedData === "string") {
269
+ accumulatedData += decoder.decode(value, { stream: true });
270
+ } else {
271
+ const combined = new Uint8Array(
272
+ accumulatedData.length + value.length
273
+ );
274
+ combined.set(accumulatedData);
275
+ combined.set(value, accumulatedData.length);
276
+ accumulatedData = combined;
277
+ }
278
+ }
279
+ const isLast = false;
280
+ await processChunk(
281
+ value,
282
+ chunkHandler,
283
+ chunkIndex,
284
+ isLast,
285
+ totalBytesRead
286
+ );
287
+ chunkIndex++;
288
+ }
289
+ return accumulatedData;
290
+ } finally {
291
+ reader.releaseLock();
292
+ }
293
+ }
294
+ async function processTextStreamLineByLine(stream, config) {
295
+ const { chunkHandler, encoding = "utf-8", accumulate = false } = config;
296
+ const reader = stream.getReader();
297
+ const decoder = new TextDecoder(encoding);
298
+ let buffer = "";
299
+ let lineIndex = 0;
300
+ let totalBytesRead = 0;
301
+ try {
302
+ while (true) {
303
+ const { done, value } = await reader.read();
304
+ if (done) {
305
+ if (buffer.length > 0) {
306
+ await processChunk(
307
+ buffer,
308
+ chunkHandler,
309
+ lineIndex,
310
+ true,
311
+ totalBytesRead
312
+ );
313
+ }
314
+ break;
315
+ }
316
+ totalBytesRead += value.length;
317
+ buffer += decoder.decode(value, { stream: true });
318
+ const lines = buffer.split("\n");
319
+ buffer = lines.pop() || "";
320
+ for (const line of lines) {
321
+ if (line.length > 0) {
322
+ await processChunk(
323
+ line,
324
+ chunkHandler,
325
+ lineIndex,
326
+ false,
327
+ totalBytesRead
328
+ );
329
+ lineIndex++;
330
+ }
331
+ }
332
+ }
333
+ return accumulate ? buffer : void 0;
334
+ } finally {
335
+ reader.releaseLock();
336
+ }
337
+ }
338
+ async function processResponseStream(response, config) {
339
+ if (!response.body) {
340
+ throw new Error("Response body is not available for streaming");
341
+ }
342
+ const processed = await processStream(response.body, config);
343
+ if (config.accumulate && processed !== void 0) {
344
+ return processed;
345
+ }
346
+ return response;
347
+ }
348
+ async function processChunk(chunk, handler, index, isLast, totalBytesRead) {
349
+ const result = handler(chunk, {
350
+ index,
351
+ isLast,
352
+ totalBytesRead
353
+ });
354
+ if (result instanceof Promise) {
355
+ await result;
356
+ }
357
+ }
358
+ function isReadableStream(value) {
359
+ return value !== null && typeof value === "object" && "getReader" in value && typeof value.getReader === "function";
360
+ }
361
+ function hasReadableStream(response) {
362
+ return response.body !== null && isReadableStream(response.body);
363
+ }
364
+
365
+ // src/request-chain.ts
366
+ var _RequestChain = class _RequestChain extends RequestFlow {
367
+ constructor() {
368
+ super(...arguments);
369
+ /**
370
+ * Adds a new stage to the request chain and returns a new chain with updated types.
371
+ * This method enables type-safe chaining of requests.
372
+ *
373
+ * @template NewOut - The output type of the new stage
374
+ * @param stage - The pipeline stage to add (request or manager stage)
375
+ * @returns A new RequestChain instance with the added stage
376
+ */
377
+ this.next = (stage) => {
378
+ return this.addRequestEntity(stage);
379
+ };
380
+ /**
381
+ * Executes all stages in the chain sequentially and returns the final result.
382
+ * Handles errors and calls registered handlers appropriately.
383
+ *
384
+ * @returns A promise that resolves to the final output result
385
+ * @throws {Error} If an error occurs and no error handler is registered
386
+ */
387
+ this.execute = async () => {
388
+ try {
389
+ const results = await this.executeAllRequests(this.requestList);
390
+ const result = results[results.length - 1];
391
+ if (this.resultHandler && result) {
392
+ this.resultHandler(result);
393
+ }
394
+ return result;
395
+ } catch (error) {
396
+ if (this.errorHandler) {
397
+ this.errorHandler(error);
398
+ return Promise.reject(error);
399
+ } else {
400
+ throw error;
401
+ }
402
+ } finally {
403
+ if (this.finishHandler) {
404
+ this.finishHandler();
405
+ }
406
+ }
407
+ };
408
+ // #endregion
409
+ // #region Private methods
410
+ /**
411
+ * Adds a request entity (stage) to the internal request list.
412
+ *
413
+ * @template NewOut - The output type of the new stage
414
+ * @param stage - The pipeline stage to add
415
+ * @returns A new RequestChain instance with updated types
416
+ */
417
+ this.addRequestEntity = (stage) => {
418
+ this.requestList.push(stage);
419
+ return this;
420
+ };
421
+ /**
422
+ * Executes all request entities in sequence, handling preconditions and mappers.
423
+ * Stages with failed preconditions are skipped but preserve the previous result.
424
+ *
425
+ * @template Out - The output type
426
+ * @param requestEntityList - List of pipeline stages to execute
427
+ * @returns A promise that resolves to an array of all stage results
428
+ */
429
+ this.executeAllRequests = async (requestEntityList) => {
430
+ const results = [];
431
+ for (let i = 0; i < requestEntityList.length; i++) {
432
+ const requestEntity = requestEntityList[i];
433
+ if (requestEntity.precondition && !requestEntity.precondition()) {
434
+ const previousEntity2 = requestEntityList[i - 1];
435
+ const previousResult2 = previousEntity2?.result;
436
+ requestEntityList[i].result = previousResult2;
437
+ continue;
438
+ }
439
+ const previousEntity = requestEntityList[i - 1];
440
+ const previousResult = previousEntity?.result;
441
+ try {
442
+ const requestResult = await this.executeSingle(
443
+ requestEntity,
444
+ previousResult
445
+ );
446
+ let result = requestResult;
447
+ if (requestEntity.mapper) {
448
+ let mappedResult;
449
+ if (isPipelineRequestStage(requestEntity)) {
450
+ mappedResult = requestEntity.mapper(
451
+ requestResult,
452
+ previousResult
453
+ );
454
+ } else if (isPipelineManagerStage(requestEntity)) {
455
+ mappedResult = requestEntity.mapper(
456
+ requestResult,
457
+ previousResult
458
+ );
459
+ } else {
460
+ mappedResult = result;
461
+ }
462
+ result = mappedResult instanceof Promise ? await mappedResult : mappedResult;
463
+ }
464
+ if (requestEntity.resultInterceptor) {
465
+ await requestEntity.resultInterceptor(result);
466
+ }
467
+ requestEntityList[i].result = result;
468
+ results.push(result);
469
+ } catch (error) {
470
+ const requestConfig = isPipelineRequestStage(requestEntity) ? requestEntity.config : void 0;
471
+ error.cause = { ...error.cause, requestConfig };
472
+ if (requestEntity.errorHandler) {
473
+ await requestEntity.errorHandler(error);
474
+ }
475
+ throw error;
476
+ }
477
+ }
478
+ return results;
479
+ };
480
+ /**
481
+ * Executes a single request entity (stage).
482
+ * Handles both request stages and nested manager stages.
483
+ * Implements retry logic for request stages when retry configuration is provided.
484
+ * Supports progressive chunk processing for streaming responses.
485
+ *
486
+ * @template Out - The output type
487
+ * @param requestEntity - The pipeline stage to execute
488
+ * @param previousResult - The result from the previous stage (optional)
489
+ * @returns A promise that resolves to the stage result
490
+ * @throws {Error} If the stage type is unknown or all retries are exhausted
491
+ */
492
+ this.executeSingle = async (requestEntity, previousResult) => {
493
+ if (isPipelineRequestStage(requestEntity)) {
494
+ const { config, retry, chunkProcessing } = requestEntity;
495
+ const requestConfig = typeof config === "function" ? config(previousResult) : config;
496
+ if (retry) {
497
+ return this.executeWithRetry(
498
+ requestConfig,
499
+ retry,
500
+ chunkProcessing
501
+ );
502
+ }
503
+ const rawResult = await this.adapter.executeRequest(requestConfig);
504
+ return this.processResultWithChunks(rawResult, chunkProcessing);
505
+ } else if (isPipelineManagerStage(requestEntity)) {
506
+ const { request } = requestEntity;
507
+ const rawResult = await request.execute();
508
+ return rawResult;
509
+ } else {
510
+ throw new Error("Unknown type");
511
+ }
512
+ };
513
+ /**
514
+ * Executes a request with retry logic based on the provided retry configuration.
515
+ * Supports chunk processing for streaming responses.
516
+ *
517
+ * @template Out - The output type
518
+ * @param requestConfig - The request configuration
519
+ * @param retryConfig - The retry configuration
520
+ * @param chunkProcessing - Optional chunk processing configuration
521
+ * @returns A promise that resolves to the request result
522
+ * @throws {Error} If all retry attempts are exhausted
523
+ */
524
+ this.executeWithRetry = async (requestConfig, retryConfig, chunkProcessing) => {
525
+ const maxRetries = retryConfig.maxRetries ?? 3;
526
+ const retryCondition = retryConfig.retryCondition ?? defaultRetryCondition;
527
+ let lastError;
528
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
529
+ try {
530
+ const rawResult = await this.adapter.executeRequest(requestConfig);
531
+ return this.processResultWithChunks(rawResult, chunkProcessing);
532
+ } catch (error) {
533
+ lastError = error instanceof Error ? error : new Error(String(error));
534
+ const shouldRetry = attempt < maxRetries && retryCondition(lastError, attempt);
535
+ if (!shouldRetry) {
536
+ throw lastError;
537
+ }
538
+ const delay = this.calculateRetryDelay(
539
+ attempt + 1,
540
+ lastError,
541
+ retryConfig
542
+ );
543
+ if (delay > 0) {
544
+ await this.sleep(delay);
545
+ }
546
+ }
547
+ }
548
+ throw lastError || new Error("Retry failed");
549
+ };
550
+ /**
551
+ * Processes a result with chunk processing if enabled.
552
+ * Handles streaming responses by processing chunks progressively.
553
+ *
554
+ * @template Out - The output type
555
+ * @param rawResult - The raw result from the adapter
556
+ * @param chunkProcessing - Optional chunk processing configuration
557
+ * @returns A promise that resolves to the processed result
558
+ */
559
+ this.processResultWithChunks = async (rawResult, chunkProcessing) => {
560
+ if (chunkProcessing?.enabled && rawResult instanceof Response && hasReadableStream(rawResult)) {
561
+ const clonedResponse = rawResult.clone();
562
+ const processed = await processResponseStream(clonedResponse, {
563
+ ...chunkProcessing
564
+ });
565
+ if (chunkProcessing.accumulate && processed !== rawResult) {
566
+ return processed;
567
+ }
568
+ return this.adapter.getResult(rawResult);
569
+ }
570
+ return this.adapter.getResult(rawResult);
571
+ };
572
+ }
573
+ /**
574
+ * Executes all stages in the chain and returns all results as a tuple.
575
+ * Useful when you need access to intermediate results.
576
+ *
577
+ * @returns A promise that resolves to a tuple of all stage results
578
+ * @throws {Error} If an error occurs and no error handler is registered
579
+ */
580
+ async executeAll() {
581
+ try {
582
+ const results = await this.executeAllRequests(this.requestList);
583
+ if (this.resultHandler && results.length > 0) {
584
+ this.resultHandler(results);
585
+ }
586
+ return results;
587
+ } catch (error) {
588
+ if (this.errorHandler) {
589
+ this.errorHandler(error);
590
+ return Promise.reject(error);
591
+ } else {
592
+ throw error;
593
+ }
594
+ } finally {
595
+ if (this.finishHandler) {
596
+ this.finishHandler();
597
+ }
598
+ }
599
+ }
600
+ /**
601
+ * Calculates the delay before the next retry attempt.
602
+ *
603
+ * @param attempt - The current attempt number (1-indexed for retries)
604
+ * @param error - The error that occurred
605
+ * @param retryConfig - The retry configuration
606
+ * @returns The delay in milliseconds
607
+ */
608
+ calculateRetryDelay(attempt, error, retryConfig) {
609
+ const baseDelay = retryConfig.retryDelay ?? 1e3;
610
+ const maxDelay = retryConfig.maxDelay;
611
+ let delay;
612
+ if (typeof baseDelay === "function") {
613
+ delay = baseDelay(attempt, error);
614
+ } else if (retryConfig.exponentialBackoff) {
615
+ delay = baseDelay * Math.pow(2, attempt - 1);
616
+ if (maxDelay !== void 0 && delay > maxDelay) {
617
+ delay = maxDelay;
618
+ }
619
+ } else {
620
+ delay = baseDelay;
621
+ }
622
+ return Math.max(0, delay);
623
+ }
624
+ /**
625
+ * Sleeps for the specified number of milliseconds.
626
+ *
627
+ * @param ms - Milliseconds to sleep
628
+ * @returns A promise that resolves after the delay
629
+ */
630
+ sleep(ms) {
631
+ return new Promise((resolve) => setTimeout(resolve, ms));
632
+ }
633
+ // #endregion
634
+ };
635
+ // #region Public methods
636
+ /**
637
+ * Creates a new RequestChain with an initial stage.
638
+ * This is the entry point for building a request chain.
639
+ *
640
+ * @template Out - The output type of the initial stage
641
+ * @template AdapterExecutionResult - The type of result returned by the adapter
642
+ * @template AdapterRequestConfig - The type of request configuration
643
+ * @param stage - The initial pipeline stage (request or manager stage)
644
+ * @param adapter - The request adapter to use for HTTP requests
645
+ * @returns A new RequestChain instance with the initial stage
646
+ */
647
+ _RequestChain.begin = (stage, adapter) => {
648
+ const requestChain = new _RequestChain();
649
+ requestChain.setRequestAdapter(adapter);
650
+ return requestChain.next(stage);
651
+ };
652
+ var RequestChain = _RequestChain;
653
+ function begin(stage, adapter) {
654
+ const requestChain = new RequestChain();
655
+ requestChain.setRequestAdapter(adapter);
656
+ return requestChain.next(stage);
657
+ }
658
+ function isPipelineRequestStage(stage) {
659
+ return "config" in stage && !("request" in stage);
660
+ }
661
+ function isPipelineManagerStage(stage) {
662
+ return "request" in stage && !("config" in stage);
663
+ }
664
+
665
+ export { RequestAdapter, RequestChain, RequestFlow as RequestManager, SSRFError, begin, RequestChain as default, defaultRetryCondition, getErrorStatus, hasReadableStream, isNetworkError, isReadableStream, processResponseStream, processStream, processTextStreamLineByLine, retryOnNetworkOrStatusCodes, retryOnStatusCodes, validateUrl };
666
+ //# sourceMappingURL=index.js.map
667
+ //# sourceMappingURL=index.js.map