@venizia/ignis-worker 0.2.0-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/applications/index.d.ts +2 -0
- package/dist/cjs/applications/index.d.ts.map +1 -0
- package/dist/cjs/applications/index.js +18 -0
- package/dist/cjs/applications/index.js.map +1 -0
- package/dist/cjs/applications/worker.d.ts +67 -0
- package/dist/cjs/applications/worker.d.ts.map +1 -0
- package/dist/cjs/applications/worker.js +175 -0
- package/dist/cjs/applications/worker.js.map +1 -0
- package/dist/cjs/envelope/encode.d.ts +61 -0
- package/dist/cjs/envelope/encode.d.ts.map +1 -0
- package/dist/cjs/envelope/encode.js +116 -0
- package/dist/cjs/envelope/encode.js.map +1 -0
- package/dist/cjs/envelope/index.d.ts +3 -0
- package/dist/cjs/envelope/index.d.ts.map +1 -0
- package/dist/cjs/envelope/index.js +19 -0
- package/dist/cjs/envelope/index.js.map +1 -0
- package/dist/cjs/envelope/types.d.ts +37 -0
- package/dist/cjs/envelope/types.d.ts.map +1 -0
- package/dist/cjs/envelope/types.js +3 -0
- package/dist/cjs/envelope/types.js.map +1 -0
- package/dist/cjs/index.d.ts +5 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +21 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/transport/common/index.d.ts +2 -0
- package/dist/cjs/transport/common/index.d.ts.map +1 -0
- package/dist/cjs/transport/common/index.js +18 -0
- package/dist/cjs/transport/common/index.js.map +1 -0
- package/dist/cjs/transport/common/types.d.ts +6 -0
- package/dist/cjs/transport/common/types.d.ts.map +1 -0
- package/dist/cjs/transport/common/types.js +3 -0
- package/dist/cjs/transport/common/types.js.map +1 -0
- package/dist/cjs/transport/in-process.d.ts +22 -0
- package/dist/cjs/transport/in-process.d.ts.map +1 -0
- package/dist/cjs/transport/in-process.js +45 -0
- package/dist/cjs/transport/in-process.js.map +1 -0
- package/dist/cjs/transport/index.d.ts +4 -0
- package/dist/cjs/transport/index.d.ts.map +1 -0
- package/dist/cjs/transport/index.js +20 -0
- package/dist/cjs/transport/index.js.map +1 -0
- package/dist/cjs/transport/worker.d.ts +48 -0
- package/dist/cjs/transport/worker.d.ts.map +1 -0
- package/dist/cjs/transport/worker.js +165 -0
- package/dist/cjs/transport/worker.js.map +1 -0
- package/dist/esm/applications/index.d.ts +2 -0
- package/dist/esm/applications/index.d.ts.map +1 -0
- package/dist/esm/applications/index.js +2 -0
- package/dist/esm/applications/index.js.map +1 -0
- package/dist/esm/applications/worker.d.ts +67 -0
- package/dist/esm/applications/worker.d.ts.map +1 -0
- package/dist/esm/applications/worker.js +171 -0
- package/dist/esm/applications/worker.js.map +1 -0
- package/dist/esm/envelope/encode.d.ts +61 -0
- package/dist/esm/envelope/encode.d.ts.map +1 -0
- package/dist/esm/envelope/encode.js +112 -0
- package/dist/esm/envelope/encode.js.map +1 -0
- package/dist/esm/envelope/index.d.ts +3 -0
- package/dist/esm/envelope/index.d.ts.map +1 -0
- package/dist/esm/envelope/index.js +3 -0
- package/dist/esm/envelope/index.js.map +1 -0
- package/dist/esm/envelope/types.d.ts +37 -0
- package/dist/esm/envelope/types.d.ts.map +1 -0
- package/dist/esm/envelope/types.js +2 -0
- package/dist/esm/envelope/types.js.map +1 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/transport/common/index.d.ts +2 -0
- package/dist/esm/transport/common/index.d.ts.map +1 -0
- package/dist/esm/transport/common/index.js +2 -0
- package/dist/esm/transport/common/index.js.map +1 -0
- package/dist/esm/transport/common/types.d.ts +6 -0
- package/dist/esm/transport/common/types.d.ts.map +1 -0
- package/dist/esm/transport/common/types.js +2 -0
- package/dist/esm/transport/common/types.js.map +1 -0
- package/dist/esm/transport/in-process.d.ts +22 -0
- package/dist/esm/transport/in-process.d.ts.map +1 -0
- package/dist/esm/transport/in-process.js +41 -0
- package/dist/esm/transport/in-process.js.map +1 -0
- package/dist/esm/transport/index.d.ts +4 -0
- package/dist/esm/transport/index.d.ts.map +1 -0
- package/dist/esm/transport/index.js +4 -0
- package/dist/esm/transport/index.js.map +1 -0
- package/dist/esm/transport/worker.d.ts +48 -0
- package/dist/esm/transport/worker.d.ts.map +1 -0
- package/dist/esm/transport/worker.js +161 -0
- package/dist/esm/transport/worker.js.map +1 -0
- package/package.json +94 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { ApplicationError } from '@venizia/ignis-helpers/core';
|
|
2
|
+
import type { IBffErrorEnvelope, IBffRequestEnvelope, IBffResponseEnvelope } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Every request built inside the Worker uses this origin. Never `self.location.href`: a `blob:`
|
|
5
|
+
* worker origin is opaque and `new URL(path, blobUrl)` throws, and Hono's `getPath()` mis-parses
|
|
6
|
+
* `file://`. Hono never resolves the host - it string-scans from `indexOf(':') + 4`.
|
|
7
|
+
*/
|
|
8
|
+
export declare const BFF_SYNTHETIC_ORIGIN = "http://ignis.internal";
|
|
9
|
+
/** Turns `Request`/`Response`/`Error` into plain structured-cloneable shapes and back - none of the three survives `postMessage` as itself. */
|
|
10
|
+
export declare class BffEnvelope {
|
|
11
|
+
/**
|
|
12
|
+
* Statuses the Fetch spec forbids a body on - `new Response(body, { status })` throws if `body`
|
|
13
|
+
* is anything but `null` for one of these, even an empty `ArrayBuffer`.
|
|
14
|
+
*/
|
|
15
|
+
private static readonly NULL_BODY_STATUSES;
|
|
16
|
+
/**
|
|
17
|
+
* Moves a URL onto the synthetic origin, keeping everything below it. A caller builds a request
|
|
18
|
+
* against the page's real origin (or a relative path resolved against it); `decodeRequest` on the
|
|
19
|
+
* far side always reconstructs on `BFF_SYNTHETIC_ORIGIN`.
|
|
20
|
+
*/
|
|
21
|
+
static toSyntheticUrl(opts: {
|
|
22
|
+
url: string;
|
|
23
|
+
}): string;
|
|
24
|
+
/**
|
|
25
|
+
* `url` overrides the request's own, so the origin rewrite costs nothing: rebuilding the `Request`
|
|
26
|
+
* to change its URL would read the body a second time, and a 10 MB upload would hold 20 MB on the
|
|
27
|
+
* UI thread. Reading the body here disturbs the caller's request exactly as `globalThis.fetch`
|
|
28
|
+
* does with the one it is handed.
|
|
29
|
+
*/
|
|
30
|
+
static encodeRequest(opts: {
|
|
31
|
+
request: Request;
|
|
32
|
+
id: string;
|
|
33
|
+
url?: string;
|
|
34
|
+
}): Promise<IBffRequestEnvelope>;
|
|
35
|
+
static decodeRequest(opts: {
|
|
36
|
+
envelope: IBffRequestEnvelope;
|
|
37
|
+
}): Request;
|
|
38
|
+
static encodeResponse(opts: {
|
|
39
|
+
response: Response;
|
|
40
|
+
id: string;
|
|
41
|
+
}): Promise<IBffResponseEnvelope>;
|
|
42
|
+
static decodeResponse(opts: {
|
|
43
|
+
envelope: IBffResponseEnvelope;
|
|
44
|
+
}): Response;
|
|
45
|
+
/**
|
|
46
|
+
* Builds the shape an error crosses `postMessage` as. An `ApplicationError` cannot cross as itself
|
|
47
|
+
* - a custom `Error` subclass loses its name, its own properties and its prototype through
|
|
48
|
+
* structured clone - so it crosses normalised and `decodeError` rebuilds it on the far side.
|
|
49
|
+
*
|
|
50
|
+
* A foreign error gets `500`, matching what the framework's error middleware gives an UNEXPECTED
|
|
51
|
+
* one on the server. Anything reaching here already escaped the whole HTTP layer.
|
|
52
|
+
*/
|
|
53
|
+
static encodeError(opts: {
|
|
54
|
+
id: string;
|
|
55
|
+
error: unknown;
|
|
56
|
+
}): IBffErrorEnvelope;
|
|
57
|
+
static decodeError(opts: {
|
|
58
|
+
envelope: IBffErrorEnvelope;
|
|
59
|
+
}): ApplicationError;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=encode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encode.d.ts","sourceRoot":"","sources":["../../../src/envelope/encode.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAGpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAE5F;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,0BAA0B,CAAC;AAE5D,+IAA+I;AAC/I,qBAAa,WAAW;IACtB;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAA4B;IAEtE;;;;OAIG;IACH,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM;IAKpD;;;;;OAKG;WACU,aAAa,CAAC,IAAI,EAAE;QAC/B,OAAO,EAAE,OAAO,CAAC;QACjB,EAAE,EAAE,MAAM,CAAC;QACX,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAahC,MAAM,CAAC,aAAa,CAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,mBAAmB,CAAA;KAAE,GAAG,OAAO;WAUzD,cAAc,CAAC,IAAI,EAAE;QAChC,QAAQ,EAAE,QAAQ,CAAC;QACnB,EAAE,EAAE,MAAM,CAAC;KACZ,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAajC,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,oBAAoB,CAAA;KAAE,GAAG,QAAQ;IAazE;;;;;;;OAOG;IACH,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,GAAG,iBAAiB;IAoB3E,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,iBAAiB,CAAA;KAAE,GAAG,gBAAgB;CAc5E"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { fromError, getError, isApplicationError } from '@venizia/ignis-helpers/core';
|
|
2
|
+
import { HTTP } from '@venizia/ignis-helpers/common';
|
|
3
|
+
/**
|
|
4
|
+
* Every request built inside the Worker uses this origin. Never `self.location.href`: a `blob:`
|
|
5
|
+
* worker origin is opaque and `new URL(path, blobUrl)` throws, and Hono's `getPath()` mis-parses
|
|
6
|
+
* `file://`. Hono never resolves the host - it string-scans from `indexOf(':') + 4`.
|
|
7
|
+
*/
|
|
8
|
+
export const BFF_SYNTHETIC_ORIGIN = 'http://ignis.internal';
|
|
9
|
+
/** Turns `Request`/`Response`/`Error` into plain structured-cloneable shapes and back - none of the three survives `postMessage` as itself. */
|
|
10
|
+
export class BffEnvelope {
|
|
11
|
+
/**
|
|
12
|
+
* Statuses the Fetch spec forbids a body on - `new Response(body, { status })` throws if `body`
|
|
13
|
+
* is anything but `null` for one of these, even an empty `ArrayBuffer`.
|
|
14
|
+
*/
|
|
15
|
+
static { this.NULL_BODY_STATUSES = new Set([204, 205, 304]); }
|
|
16
|
+
/**
|
|
17
|
+
* Moves a URL onto the synthetic origin, keeping everything below it. A caller builds a request
|
|
18
|
+
* against the page's real origin (or a relative path resolved against it); `decodeRequest` on the
|
|
19
|
+
* far side always reconstructs on `BFF_SYNTHETIC_ORIGIN`.
|
|
20
|
+
*/
|
|
21
|
+
static toSyntheticUrl(opts) {
|
|
22
|
+
const url = new URL(opts.url);
|
|
23
|
+
return `${BFF_SYNTHETIC_ORIGIN}${url.pathname}${url.search}${url.hash}`;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* `url` overrides the request's own, so the origin rewrite costs nothing: rebuilding the `Request`
|
|
27
|
+
* to change its URL would read the body a second time, and a 10 MB upload would hold 20 MB on the
|
|
28
|
+
* UI thread. Reading the body here disturbs the caller's request exactly as `globalThis.fetch`
|
|
29
|
+
* does with the one it is handed.
|
|
30
|
+
*/
|
|
31
|
+
static async encodeRequest(opts) {
|
|
32
|
+
const { request, id, url } = opts;
|
|
33
|
+
const hasBody = request.method !== 'GET' && request.method !== 'HEAD';
|
|
34
|
+
return {
|
|
35
|
+
id,
|
|
36
|
+
method: request.method,
|
|
37
|
+
url: url ?? request.url,
|
|
38
|
+
headers: [...request.headers.entries()],
|
|
39
|
+
body: hasBody ? await request.arrayBuffer() : undefined,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
static decodeRequest(opts) {
|
|
43
|
+
const { envelope } = opts;
|
|
44
|
+
return new Request(envelope.url, {
|
|
45
|
+
method: envelope.method,
|
|
46
|
+
headers: envelope.headers,
|
|
47
|
+
body: envelope.body,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
static async encodeResponse(opts) {
|
|
51
|
+
const { response, id } = opts;
|
|
52
|
+
const hasBody = !BffEnvelope.NULL_BODY_STATUSES.has(response.status);
|
|
53
|
+
return {
|
|
54
|
+
id,
|
|
55
|
+
status: response.status,
|
|
56
|
+
statusText: response.statusText,
|
|
57
|
+
headers: [...response.headers.entries()],
|
|
58
|
+
body: hasBody ? await response.arrayBuffer() : undefined,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
static decodeResponse(opts) {
|
|
62
|
+
const { envelope } = opts;
|
|
63
|
+
const body = BffEnvelope.NULL_BODY_STATUSES.has(envelope.status)
|
|
64
|
+
? null
|
|
65
|
+
: (envelope.body ?? null);
|
|
66
|
+
return new Response(body, {
|
|
67
|
+
status: envelope.status,
|
|
68
|
+
statusText: envelope.statusText,
|
|
69
|
+
headers: envelope.headers,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Builds the shape an error crosses `postMessage` as. An `ApplicationError` cannot cross as itself
|
|
74
|
+
* - a custom `Error` subclass loses its name, its own properties and its prototype through
|
|
75
|
+
* structured clone - so it crosses normalised and `decodeError` rebuilds it on the far side.
|
|
76
|
+
*
|
|
77
|
+
* A foreign error gets `500`, matching what the framework's error middleware gives an UNEXPECTED
|
|
78
|
+
* one on the server. Anything reaching here already escaped the whole HTTP layer.
|
|
79
|
+
*/
|
|
80
|
+
static encodeError(opts) {
|
|
81
|
+
const { id, error } = opts;
|
|
82
|
+
const applicationError = isApplicationError(error)
|
|
83
|
+
? error
|
|
84
|
+
: getError({
|
|
85
|
+
statusCode: HTTP.ResultCodes.RS_5.InternalServerError,
|
|
86
|
+
message: error instanceof Error ? error.message : String(error),
|
|
87
|
+
});
|
|
88
|
+
return {
|
|
89
|
+
id,
|
|
90
|
+
statusCode: applicationError.statusCode,
|
|
91
|
+
error: {
|
|
92
|
+
text: applicationError.normalized.text,
|
|
93
|
+
code: applicationError.normalized.code,
|
|
94
|
+
args: applicationError.normalized.args,
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
static decodeError(opts) {
|
|
99
|
+
const { envelope } = opts;
|
|
100
|
+
return fromError({
|
|
101
|
+
error: {
|
|
102
|
+
statusCode: envelope.statusCode,
|
|
103
|
+
normalized: {
|
|
104
|
+
text: envelope.error.text,
|
|
105
|
+
code: envelope.error.code,
|
|
106
|
+
args: envelope.error.args,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=encode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encode.js","sourceRoot":"","sources":["../../../src/envelope/encode.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/envelope/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/envelope/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Headers are carried as tuples: a `Headers` instance is not structured-cloneable. */
|
|
2
|
+
export type TEnvelopeHeaders = Array<[string, string]>;
|
|
3
|
+
export interface IBffRequestEnvelope {
|
|
4
|
+
id: string;
|
|
5
|
+
method: string;
|
|
6
|
+
url: string;
|
|
7
|
+
headers: TEnvelopeHeaders;
|
|
8
|
+
/** Buffered, never a stream - `ReadableStream` does not transfer in Safari. */
|
|
9
|
+
body?: ArrayBuffer;
|
|
10
|
+
}
|
|
11
|
+
export interface IBffResponseEnvelope {
|
|
12
|
+
id: string;
|
|
13
|
+
status: number;
|
|
14
|
+
statusText?: string;
|
|
15
|
+
headers: TEnvelopeHeaders;
|
|
16
|
+
body?: ArrayBuffer;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A custom `Error` subclass loses its name, its own properties and its prototype through structured
|
|
20
|
+
* clone, so an `ApplicationError` cannot cross as itself. It crosses as its normalised shape and is
|
|
21
|
+
* rebuilt with `fromError` on the far side.
|
|
22
|
+
*/
|
|
23
|
+
export interface IBffErrorEnvelope {
|
|
24
|
+
id: string;
|
|
25
|
+
/**
|
|
26
|
+
* Carried explicitly, because `fromError` defaults a missing one to 400. This envelope is only
|
|
27
|
+
* ever produced when something escaped Hono's `onError` - the 500-class failures a UI must not
|
|
28
|
+
* classify as its own bad request.
|
|
29
|
+
*/
|
|
30
|
+
statusCode: number;
|
|
31
|
+
error: {
|
|
32
|
+
text: string;
|
|
33
|
+
code?: string;
|
|
34
|
+
args?: unknown;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/envelope/types.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,MAAM,MAAM,gBAAgB,GAAG,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAEvD,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,gBAAgB,CAAC;IAC1B,+EAA+E;IAC/E,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACxD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/envelope/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/transport/common/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/transport/common/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/transport/common/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACtD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/transport/common/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { IBffTransport } from './common/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* No worker, no browser - the seam that makes a BFF testable in-process. It still runs the full
|
|
4
|
+
* envelope round trip, and that is the whole point: a bare pass-through lets a route pass here and
|
|
5
|
+
* hang over a real Worker, because only the envelope applies the synthetic origin, buffers a
|
|
6
|
+
* streaming body, and reduces a thrown error to `{ text, code, args }`. A test is worth what its
|
|
7
|
+
* differences from production are worth.
|
|
8
|
+
*
|
|
9
|
+
* What it deliberately does NOT mirror is the timeout: in-process there is no message that can be
|
|
10
|
+
* lost, so a handler either settles or the caller's own test timeout fires.
|
|
11
|
+
*/
|
|
12
|
+
export declare class InProcessBffTransport implements IBffTransport {
|
|
13
|
+
private readonly handler;
|
|
14
|
+
private readonly requestIdGenerator;
|
|
15
|
+
constructor(opts: {
|
|
16
|
+
handler: (request: Request) => Promise<Response>;
|
|
17
|
+
});
|
|
18
|
+
fetch(opts: {
|
|
19
|
+
request: Request;
|
|
20
|
+
}): Promise<Response>;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=in-process.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"in-process.d.ts","sourceRoot":"","sources":["../../../src/transport/in-process.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD;;;;;;;;;GASG;AACH,qBAAa,qBAAsB,YAAW,aAAa;IACzD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0C;IAClE,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAEhC;gBAES,IAAI,EAAE;QAAE,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;KAAE;IAIhE,KAAK,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,QAAQ,CAAC;CAuB3D"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { RequestIdGenerator } from '@venizia/ignis-helpers/core';
|
|
2
|
+
import { BffEnvelope } from '../envelope/encode.js';
|
|
3
|
+
/**
|
|
4
|
+
* No worker, no browser - the seam that makes a BFF testable in-process. It still runs the full
|
|
5
|
+
* envelope round trip, and that is the whole point: a bare pass-through lets a route pass here and
|
|
6
|
+
* hang over a real Worker, because only the envelope applies the synthetic origin, buffers a
|
|
7
|
+
* streaming body, and reduces a thrown error to `{ text, code, args }`. A test is worth what its
|
|
8
|
+
* differences from production are worth.
|
|
9
|
+
*
|
|
10
|
+
* What it deliberately does NOT mirror is the timeout: in-process there is no message that can be
|
|
11
|
+
* lost, so a handler either settles or the caller's own test timeout fires.
|
|
12
|
+
*/
|
|
13
|
+
export class InProcessBffTransport {
|
|
14
|
+
constructor(opts) {
|
|
15
|
+
this.requestIdGenerator = new RequestIdGenerator({
|
|
16
|
+
scope: InProcessBffTransport.name,
|
|
17
|
+
});
|
|
18
|
+
this.handler = opts.handler;
|
|
19
|
+
}
|
|
20
|
+
async fetch(opts) {
|
|
21
|
+
const id = this.requestIdGenerator.nextId();
|
|
22
|
+
const requestEnvelope = await BffEnvelope.encodeRequest({
|
|
23
|
+
request: opts.request,
|
|
24
|
+
id,
|
|
25
|
+
url: BffEnvelope.toSyntheticUrl({ url: opts.request.url }),
|
|
26
|
+
});
|
|
27
|
+
let responseEnvelope;
|
|
28
|
+
try {
|
|
29
|
+
const response = await this.handler(BffEnvelope.decodeRequest({ envelope: requestEnvelope }));
|
|
30
|
+
responseEnvelope = await BffEnvelope.encodeResponse({ response, id });
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
// Encoded and decoded rather than rethrown: what a UI receives over a real Worker is a
|
|
34
|
+
// rebuilt `ApplicationError` carrying only the normalised shape - never the original object,
|
|
35
|
+
// its stack or its `extra`.
|
|
36
|
+
throw BffEnvelope.decodeError({ envelope: BffEnvelope.encodeError({ id, error }) });
|
|
37
|
+
}
|
|
38
|
+
return BffEnvelope.decodeResponse({ envelope: responseEnvelope });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=in-process.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"in-process.js","sourceRoot":"","sources":["../../../src/transport/in-process.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIhD;;;;;;;;;GASG;AACH,MAAM,OAAO,qBAAqB;IAMhC,YAAY,IAA0D;QAJrD,uBAAkB,GAAG,IAAI,kBAAkB,CAAC;YAC3D,KAAK,EAAE,qBAAqB,CAAC,IAAI;SAClC,CAAC,CAAC;QAGD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,IAA0B;QACpC,MAAM,EAAE,GAAG,IAAI,CAAC,kBAAkB,CAAC,MAAM,EAAE,CAAC;QAE5C,MAAM,eAAe,GAAG,MAAM,WAAW,CAAC,aAAa,CAAC;YACtD,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,EAAE;YACF,GAAG,EAAE,WAAW,CAAC,cAAc,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;SAC3D,CAAC,CAAC;QAEH,IAAI,gBAAsC,CAAC;QAE3C,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,aAAa,CAAC,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC;YAC9F,gBAAgB,GAAG,MAAM,WAAW,CAAC,cAAc,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;QACxE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,uFAAuF;YACvF,6FAA6F;YAC7F,4BAA4B;YAC5B,MAAM,WAAW,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,WAAW,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;QACtF,CAAC;QAED,OAAO,WAAW,CAAC,cAAc,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,CAAC,CAAC;IACpE,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/transport/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/transport/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { BaseHelper } from '@venizia/ignis-helpers/core';
|
|
2
|
+
import type { IBffTransport } from './common/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* UI-side half of the BFF: posts an encoded request to a real `Worker` and resolves the matching
|
|
5
|
+
* response by `id` - the far side of `WorkerApplication.listen()`.
|
|
6
|
+
*
|
|
7
|
+
* The three worker listeners are attached ONCE, in the constructor, and every request is tracked in
|
|
8
|
+
* `pendingRequests` instead. Registering them per request looks equivalent and is not: they are
|
|
9
|
+
* scoped to the shared `Worker`, so a single `messageerror` - which means exactly one message
|
|
10
|
+
* failed to deserialise - would fire every in-flight request's handler, rejecting nine healthy
|
|
11
|
+
* requests that are about to be answered normally.
|
|
12
|
+
*/
|
|
13
|
+
export declare class WorkerBffTransport extends BaseHelper implements IBffTransport {
|
|
14
|
+
private readonly worker;
|
|
15
|
+
private readonly timeoutMs;
|
|
16
|
+
private readonly requestIdGenerator;
|
|
17
|
+
private readonly pendingRequests;
|
|
18
|
+
constructor(opts: {
|
|
19
|
+
worker: Worker;
|
|
20
|
+
timeoutMs?: number;
|
|
21
|
+
});
|
|
22
|
+
/** Removes a pending request from the map and releases its timer and abort listener, so nothing can settle it twice. */
|
|
23
|
+
private readonly takePending;
|
|
24
|
+
private readonly handleMessage;
|
|
25
|
+
/**
|
|
26
|
+
* A dead or crashed worker never posts anything back, so every in-flight request is lost - this
|
|
27
|
+
* is the one event that legitimately settles all of them, instead of leaving each to time out.
|
|
28
|
+
*/
|
|
29
|
+
private readonly handleWorkerError;
|
|
30
|
+
/**
|
|
31
|
+
* `messageerror` means exactly ONE posted message failed to deserialise, and the event carries
|
|
32
|
+
* nothing that says which. With a single request in flight that is unambiguous, so it settles
|
|
33
|
+
* now; with several, the lost answer belongs to one of them and rejecting the rest would be a
|
|
34
|
+
* lie - each then ends on its own timeout, under its own id.
|
|
35
|
+
*/
|
|
36
|
+
private readonly handleMessageError;
|
|
37
|
+
fetch(opts: {
|
|
38
|
+
request: Request;
|
|
39
|
+
}): Promise<Response>;
|
|
40
|
+
/** `signal.reason` first, the way `globalThis.fetch` rejects, so `controller.abort(myReason)` still reaches the caller. */
|
|
41
|
+
private toAbortError;
|
|
42
|
+
/**
|
|
43
|
+
* Detaches the three worker listeners and fails whatever is still in flight. The worker itself is
|
|
44
|
+
* left alone - this transport did not create it, and a page may hand the same one to another.
|
|
45
|
+
*/
|
|
46
|
+
close(): void;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../../src/transport/worker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAY,MAAM,6BAA6B,CAAC;AAInE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAiBpD;;;;;;;;;GASG;AACH,qBAAa,kBAAmB,SAAQ,UAAW,YAAW,aAAa;IACzE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAEhC;IACH,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAsC;gBAE1D,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;IAWxD,wHAAwH;IACxH,OAAO,CAAC,QAAQ,CAAC,WAAW,CAS1B;IAEF,OAAO,CAAC,QAAQ,CAAC,aAAa,CAgC5B;IAEF;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAQhC;IAEF;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAkBjC;IAEI,KAAK,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,QAAQ,CAAC;IAqD1D,2HAA2H;IAC3H,OAAO,CAAC,YAAY;IAOpB;;;OAGG;IACH,KAAK,IAAI,IAAI;CAad"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { BaseHelper, getError } from '@venizia/ignis-helpers/core';
|
|
2
|
+
import { RequestIdGenerator } from '@venizia/ignis-helpers/core';
|
|
3
|
+
import { BffEnvelope } from '../envelope/encode.js';
|
|
4
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
5
|
+
const isErrorEnvelope = (data) => {
|
|
6
|
+
return 'error' in data;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* UI-side half of the BFF: posts an encoded request to a real `Worker` and resolves the matching
|
|
10
|
+
* response by `id` - the far side of `WorkerApplication.listen()`.
|
|
11
|
+
*
|
|
12
|
+
* The three worker listeners are attached ONCE, in the constructor, and every request is tracked in
|
|
13
|
+
* `pendingRequests` instead. Registering them per request looks equivalent and is not: they are
|
|
14
|
+
* scoped to the shared `Worker`, so a single `messageerror` - which means exactly one message
|
|
15
|
+
* failed to deserialise - would fire every in-flight request's handler, rejecting nine healthy
|
|
16
|
+
* requests that are about to be answered normally.
|
|
17
|
+
*/
|
|
18
|
+
export class WorkerBffTransport extends BaseHelper {
|
|
19
|
+
constructor(opts) {
|
|
20
|
+
super({ scope: WorkerBffTransport.name });
|
|
21
|
+
this.requestIdGenerator = new RequestIdGenerator({
|
|
22
|
+
scope: WorkerBffTransport.name,
|
|
23
|
+
});
|
|
24
|
+
this.pendingRequests = new Map();
|
|
25
|
+
/** Removes a pending request from the map and releases its timer and abort listener, so nothing can settle it twice. */
|
|
26
|
+
this.takePending = (opts) => {
|
|
27
|
+
const pending = this.pendingRequests.get(opts.id);
|
|
28
|
+
if (!pending) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
this.pendingRequests.delete(opts.id);
|
|
32
|
+
pending.release();
|
|
33
|
+
return pending;
|
|
34
|
+
};
|
|
35
|
+
this.handleMessage = (event) => {
|
|
36
|
+
// `event.data` is untyped (`any`) until narrowed below - a worker posts to this page for its own
|
|
37
|
+
// reasons too (a dev-server ping, a library handshake), and none of those carry an `id` this
|
|
38
|
+
// transport is waiting on.
|
|
39
|
+
const data = event.data;
|
|
40
|
+
if (!data?.id) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const pending = this.takePending({ id: data.id });
|
|
44
|
+
if (!pending) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (isErrorEnvelope(data)) {
|
|
48
|
+
pending.reject(BffEnvelope.decodeError({ envelope: data }));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// Settled inside its own try: `decodeResponse` throws a `RangeError` for a status outside
|
|
52
|
+
// 200-599 and a `TypeError` on a malformed header tuple, and by this point the timer and the
|
|
53
|
+
// listeners are already gone - a throw that escaped here would leave the promise with nothing
|
|
54
|
+
// left to settle it, ever.
|
|
55
|
+
try {
|
|
56
|
+
pending.resolve(BffEnvelope.decodeResponse({ envelope: data }));
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
pending.reject(getError({
|
|
60
|
+
message: `[WorkerBffTransport] Failed to decode the response envelope | id: ${data.id} | error: ${error}`,
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* A dead or crashed worker never posts anything back, so every in-flight request is lost - this
|
|
66
|
+
* is the one event that legitimately settles all of them, instead of leaving each to time out.
|
|
67
|
+
*/
|
|
68
|
+
this.handleWorkerError = (event) => {
|
|
69
|
+
for (const id of [...this.pendingRequests.keys()]) {
|
|
70
|
+
this.takePending({ id })?.reject(getError({
|
|
71
|
+
message: `[WorkerBffTransport] Worker failed while awaiting a response | id: ${id} | event: ${String(event)}`,
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* `messageerror` means exactly ONE posted message failed to deserialise, and the event carries
|
|
77
|
+
* nothing that says which. With a single request in flight that is unambiguous, so it settles
|
|
78
|
+
* now; with several, the lost answer belongs to one of them and rejecting the rest would be a
|
|
79
|
+
* lie - each then ends on its own timeout, under its own id.
|
|
80
|
+
*/
|
|
81
|
+
this.handleMessageError = (event) => {
|
|
82
|
+
if (this.pendingRequests.size !== 1) {
|
|
83
|
+
this.logger
|
|
84
|
+
.for(this.handleMessageError.name)
|
|
85
|
+
.warn('A worker message could not be deserialised and no single request owns it | inFlight: %d | event: %s', this.pendingRequests.size, String(event));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const [id] = [...this.pendingRequests.keys()];
|
|
89
|
+
this.takePending({ id: id })?.reject(getError({
|
|
90
|
+
message: `[WorkerBffTransport] The worker's response could not be deserialised | id: ${id} | event: ${String(event)}`,
|
|
91
|
+
}));
|
|
92
|
+
};
|
|
93
|
+
this.worker = opts.worker;
|
|
94
|
+
this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
95
|
+
this.worker.addEventListener('message', this.handleMessage);
|
|
96
|
+
this.worker.addEventListener('error', this.handleWorkerError);
|
|
97
|
+
this.worker.addEventListener('messageerror', this.handleMessageError);
|
|
98
|
+
}
|
|
99
|
+
async fetch(opts) {
|
|
100
|
+
const { request } = opts;
|
|
101
|
+
const { signal } = request;
|
|
102
|
+
if (signal?.aborted) {
|
|
103
|
+
throw this.toAbortError({ signal });
|
|
104
|
+
}
|
|
105
|
+
const id = this.requestIdGenerator.nextId();
|
|
106
|
+
// The URL is overridden rather than the request rebuilt: rebuilding reads the body once here
|
|
107
|
+
// and once inside `encodeRequest`, and it silently drops `signal` on the way.
|
|
108
|
+
const requestEnvelope = await BffEnvelope.encodeRequest({
|
|
109
|
+
request,
|
|
110
|
+
id,
|
|
111
|
+
url: BffEnvelope.toSyntheticUrl({ url: request.url }),
|
|
112
|
+
});
|
|
113
|
+
return new Promise((resolve, reject) => {
|
|
114
|
+
const timer = setTimeout(() => {
|
|
115
|
+
this.takePending({ id })?.reject(getError({
|
|
116
|
+
message: `[WorkerBffTransport] Timed out waiting for a response | id: ${id} | timeoutMs: ${this.timeoutMs}`,
|
|
117
|
+
}));
|
|
118
|
+
}, this.timeoutMs);
|
|
119
|
+
const handleAbort = () => {
|
|
120
|
+
this.takePending({ id })?.reject(this.toAbortError({ signal }));
|
|
121
|
+
};
|
|
122
|
+
signal?.addEventListener('abort', handleAbort);
|
|
123
|
+
this.pendingRequests.set(id, {
|
|
124
|
+
resolve,
|
|
125
|
+
reject,
|
|
126
|
+
release: () => {
|
|
127
|
+
clearTimeout(timer);
|
|
128
|
+
signal?.removeEventListener('abort', handleAbort);
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
// Buffering the body took an await, and `addEventListener('abort')` never fires on a signal
|
|
132
|
+
// that aborted before it was attached - without this re-read that window loses the abort and
|
|
133
|
+
// the caller waits out the whole timeout.
|
|
134
|
+
if (signal?.aborted) {
|
|
135
|
+
handleAbort();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
this.worker.postMessage(requestEnvelope, requestEnvelope.body ? [requestEnvelope.body] : []);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/** `signal.reason` first, the way `globalThis.fetch` rejects, so `controller.abort(myReason)` still reaches the caller. */
|
|
142
|
+
toAbortError(opts) {
|
|
143
|
+
return (opts.signal?.reason ??
|
|
144
|
+
getError({ message: '[WorkerBffTransport] The request was aborted by its caller' }));
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Detaches the three worker listeners and fails whatever is still in flight. The worker itself is
|
|
148
|
+
* left alone - this transport did not create it, and a page may hand the same one to another.
|
|
149
|
+
*/
|
|
150
|
+
close() {
|
|
151
|
+
this.worker.removeEventListener('message', this.handleMessage);
|
|
152
|
+
this.worker.removeEventListener('error', this.handleWorkerError);
|
|
153
|
+
this.worker.removeEventListener('messageerror', this.handleMessageError);
|
|
154
|
+
for (const id of [...this.pendingRequests.keys()]) {
|
|
155
|
+
this.takePending({ id })?.reject(getError({
|
|
156
|
+
message: `[WorkerBffTransport] The transport was closed while awaiting a response | id: ${id}`,
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../../../src/transport/worker.ts"],"names":[],"mappings":"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"}
|