@plumile/relay 0.1.200 → 0.1.205
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 +42 -0
- package/lib/esm/envHelpers.js +30 -22
- package/lib/esm/envHelpers.js.map +1 -1
- package/lib/esm/environment.js +242 -136
- package/lib/esm/environment.js.map +1 -1
- package/lib/types/envHelpers.d.ts.map +1 -1
- package/lib/types/environment.d.ts +41 -1
- package/lib/types/environment.d.ts.map +1 -1
- package/package.json +3 -5
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# @plumile/relay
|
|
2
|
+
|
|
3
|
+
Shared Relay environment, transport, provider, and operation-activity helpers
|
|
4
|
+
for Plumile applications.
|
|
5
|
+
|
|
6
|
+
## HTTP authentication ownership
|
|
7
|
+
|
|
8
|
+
`configureShortAccessTokenRelayEnvironment()` injects an auth-aware GraphQL
|
|
9
|
+
fetch into this package. The injected fetch owns the single refresh and retry
|
|
10
|
+
after `401`; this transport owns multipart encoding, timeouts, and retries for
|
|
11
|
+
network, `408`, `429`, and `5xx` failures. It does not patch
|
|
12
|
+
`globalThis.fetch`.
|
|
13
|
+
|
|
14
|
+
## WebSocket transport contract
|
|
15
|
+
|
|
16
|
+
Subscriptions use the `graphql-transport-ws` subprotocol through the package's
|
|
17
|
+
owned client. The client deliberately remains local because the deployed server
|
|
18
|
+
contract includes authentication rotation beyond the standard protocol:
|
|
19
|
+
|
|
20
|
+
- the client sends `connection_init` with the current authentication headers and
|
|
21
|
+
waits for `connection_ack` before subscribing;
|
|
22
|
+
- subscriptions use `subscribe`, `next`, `error`, and `complete`, while
|
|
23
|
+
keep-alive messages use `ping` and `pong`;
|
|
24
|
+
- credential rotation sends `connection_auth_update` and requires
|
|
25
|
+
`connection_auth_update_ack` within five seconds;
|
|
26
|
+
- an unexpected disconnect retries with bounded exponential backoff (at most ten
|
|
27
|
+
attempts) and re-subscribes every still-active operation after the next ack;
|
|
28
|
+
- invalid or unsupported server messages close the socket with code `4400`;
|
|
29
|
+
normal environment disposal closes it with code `1000`.
|
|
30
|
+
|
|
31
|
+
Do not replace this client with a generic GraphQL WebSocket dependency unless
|
|
32
|
+
that implementation proves the same authentication-update and reconnection
|
|
33
|
+
contract without an adapter of comparable complexity.
|
|
34
|
+
|
|
35
|
+
## Validation
|
|
36
|
+
|
|
37
|
+
From the repository root, run the Relay tests and build:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx vitest run packages/relay
|
|
41
|
+
npm run --workspace @plumile/relay build:package
|
|
42
|
+
```
|
package/lib/esm/envHelpers.js
CHANGED
|
@@ -1,55 +1,63 @@
|
|
|
1
1
|
//#region src/envHelpers.ts
|
|
2
|
-
|
|
2
|
+
function e(e) {
|
|
3
|
+
return typeof e == "object" && !!e;
|
|
4
|
+
}
|
|
5
|
+
var t = (() => {
|
|
3
6
|
try {
|
|
4
|
-
let
|
|
5
|
-
|
|
7
|
+
let t = import.meta, n;
|
|
8
|
+
if ("env" in t && (n = t.env), e(n)) {
|
|
9
|
+
let e = {};
|
|
10
|
+
for (let [t, r] of Object.entries(n)) (typeof r == "string" || typeof r == "boolean" || r === void 0) && (e[t] = r);
|
|
11
|
+
return e;
|
|
12
|
+
}
|
|
13
|
+
return {};
|
|
6
14
|
} catch {
|
|
7
15
|
return {};
|
|
8
16
|
}
|
|
9
|
-
})(),
|
|
17
|
+
})(), n = (e, t) => {
|
|
10
18
|
let n = e[t];
|
|
11
19
|
if (typeof n != "string") return;
|
|
12
20
|
let r = n.trim();
|
|
13
21
|
if (r !== "") return r;
|
|
14
|
-
},
|
|
22
|
+
}, r = (e, t) => {
|
|
15
23
|
let n = e[t];
|
|
16
24
|
if (n === !0 || n === !1) return n;
|
|
17
25
|
if (typeof n == "string") {
|
|
18
26
|
if (n === "true") return !0;
|
|
19
27
|
if (n === "false") return !1;
|
|
20
28
|
}
|
|
21
|
-
},
|
|
22
|
-
function
|
|
23
|
-
return t
|
|
29
|
+
}, i = (e) => !!r(e, "DEV");
|
|
30
|
+
function a(e) {
|
|
31
|
+
return n(t, e);
|
|
24
32
|
}
|
|
25
|
-
function
|
|
26
|
-
return
|
|
33
|
+
function o(e) {
|
|
34
|
+
return r(t, e);
|
|
27
35
|
}
|
|
28
|
-
function
|
|
29
|
-
return
|
|
36
|
+
function s() {
|
|
37
|
+
return i(t);
|
|
30
38
|
}
|
|
31
|
-
function
|
|
32
|
-
let { envKeys: t, fallback: n, read: r =
|
|
39
|
+
function c(e) {
|
|
40
|
+
let { envKeys: t, fallback: n, read: r = a } = e;
|
|
33
41
|
for (let e of t) {
|
|
34
42
|
let t = r(e);
|
|
35
43
|
if (t != null) return t;
|
|
36
44
|
}
|
|
37
45
|
return n;
|
|
38
46
|
}
|
|
39
|
-
function
|
|
40
|
-
let { envKeys: t, fallback: n, httpEndpoint: r, read:
|
|
47
|
+
function l(e) {
|
|
48
|
+
let { envKeys: t, fallback: n, httpEndpoint: r, read: i = a } = e;
|
|
41
49
|
for (let e of t) {
|
|
42
|
-
let t =
|
|
50
|
+
let t = i(e);
|
|
43
51
|
if (t != null) return t;
|
|
44
52
|
}
|
|
45
53
|
return r.startsWith("http") ? r.replace(/^http/, "ws") : r.startsWith("/") ? r.replace(/\/graphql$/, "/ws") : n;
|
|
46
54
|
}
|
|
47
|
-
var
|
|
48
|
-
isDevEnvFromRecord:
|
|
49
|
-
readBooleanEnvFromRecord:
|
|
50
|
-
readEnvFromRecord:
|
|
55
|
+
var u = {
|
|
56
|
+
isDevEnvFromRecord: i,
|
|
57
|
+
readBooleanEnvFromRecord: r,
|
|
58
|
+
readEnvFromRecord: n
|
|
51
59
|
};
|
|
52
60
|
//#endregion
|
|
53
|
-
export {
|
|
61
|
+
export { u as __test, s as isDevEnv, o as readBooleanEnv, a as readEnv, c as resolveHttpEndpoint, l as resolveWebsocketEndpoint };
|
|
54
62
|
|
|
55
63
|
//# sourceMappingURL=envHelpers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"envHelpers.js","names":[],"sources":["../../src/envHelpers.ts"],"sourcesContent":["type EnvRecord = Record<string, string | boolean | undefined>;\n\nconst viteEnv: EnvRecord = (() => {\n try {\n const meta = import.meta
|
|
1
|
+
{"version":3,"file":"envHelpers.js","names":[],"sources":["../../src/envHelpers.ts"],"sourcesContent":["type EnvRecord = Record<string, string | boolean | undefined>;\n\n/** Narrows external environment objects before enumerating their values. */\nfunction isUnknownRecord(value: unknown): value is Record<string, unknown> {\n return value != null && typeof value === 'object';\n}\n\nconst viteEnv: EnvRecord = (() => {\n try {\n const meta: object = import.meta;\n let env: unknown;\n if ('env' in meta) {\n env = meta.env;\n }\n if (isUnknownRecord(env)) {\n const result: EnvRecord = {};\n for (const [key, value] of Object.entries(env)) {\n if (\n typeof value === 'string' ||\n typeof value === 'boolean' ||\n value === undefined\n ) {\n result[key] = value;\n }\n }\n return result;\n }\n return {};\n } catch {\n // `import.meta` is not defined outside of ESM environments; fall back to an empty record.\n return {};\n }\n})();\n\nexport type ReadEnvFn = (key: string) => string | undefined;\n\nconst readEnvFromRecord = (env: EnvRecord, key: string): string | undefined => {\n const value = env[key];\n if (typeof value !== 'string') {\n return undefined;\n }\n\n const trimmed = value.trim();\n if (trimmed === '') {\n return undefined;\n }\n\n return trimmed;\n};\n\nconst readBooleanEnvFromRecord = (\n env: EnvRecord,\n key: string,\n): boolean | undefined => {\n const value = env[key];\n if (value === true || value === false) {\n return value;\n }\n if (typeof value === 'string') {\n if (value === 'true') {\n return true;\n }\n if (value === 'false') {\n return false;\n }\n }\n return undefined;\n};\n\nconst isDevEnvFromRecord = (env: EnvRecord): boolean => {\n return Boolean(readBooleanEnvFromRecord(env, 'DEV'));\n};\n\n/**\n * Read a string environment variable from Vite's `import.meta.env` (if available).\n * Returns `undefined` when the value is missing or empty.\n */\nexport function readEnv(key: string): string | undefined {\n return readEnvFromRecord(viteEnv, key);\n}\n\n/**\n * Reads an environment variable and coerces common string values to booleans.\n */\nexport function readBooleanEnv(key: string): boolean | undefined {\n return readBooleanEnvFromRecord(viteEnv, key);\n}\n\n/**\n * Indicates whether the current build is running in development mode.\n */\nexport function isDevEnv(): boolean {\n return isDevEnvFromRecord(viteEnv);\n}\n\ninterface ResolveEndpointOptions {\n envKeys: readonly string[];\n fallback: string;\n read?: ReadEnvFn;\n}\n\n/**\n * Resolve HTTP endpoint from environment variables.\n */\nexport function resolveHttpEndpoint(options: ResolveEndpointOptions): string {\n const { envKeys, fallback, read = readEnv } = options;\n for (const key of envKeys) {\n const value = read(key);\n if (value != null) {\n return value;\n }\n }\n return fallback;\n}\n\ninterface ResolveWebsocketEndpointOptions extends ResolveEndpointOptions {\n httpEndpoint: string;\n}\n\n/**\n * Resolve websocket endpoint from environment variables or derive it from the HTTP endpoint.\n */\nexport function resolveWebsocketEndpoint(\n options: ResolveWebsocketEndpointOptions,\n): string {\n const { envKeys, fallback, httpEndpoint, read = readEnv } = options;\n\n for (const key of envKeys) {\n const value = read(key);\n if (value != null) {\n return value;\n }\n }\n\n if (httpEndpoint.startsWith('http')) {\n return httpEndpoint.replace(/^http/, 'ws');\n }\n\n if (httpEndpoint.startsWith('/')) {\n return httpEndpoint.replace(/\\/graphql$/, '/ws');\n }\n\n return fallback;\n}\n\nexport const __test = {\n isDevEnvFromRecord,\n readBooleanEnvFromRecord,\n readEnvFromRecord,\n};\n"],"mappings":";AAGA,SAAS,EAAgB,GAAkD;CACzE,OAAwB,OAAO,KAAU,cAAlC;AACT;AAEA,IAAM,WAA4B;CAChC,IAAI;EACF,IAAM,IAAe,aACjB;EAIJ,IAHI,SAAS,MACX,IAAM,EAAK,MAET,EAAgB,CAAG,GAAG;GACxB,IAAM,IAAoB,CAAC;GAC3B,KAAK,IAAM,CAAC,GAAK,MAAU,OAAO,QAAQ,CAAG,GAC3C,CACE,OAAO,KAAU,YACjB,OAAO,KAAU,aACjB,MAAU,KAAA,OAEV,EAAO,KAAO;GAGlB,OAAO;EACT;EACA,OAAO,CAAC;CACV,QAAQ;EAEN,OAAO,CAAC;CACV;AACF,EAAA,CAAG,GAIG,KAAqB,GAAgB,MAAoC;CAC7E,IAAM,IAAQ,EAAI;CAClB,IAAI,OAAO,KAAU,UACnB;CAGF,IAAM,IAAU,EAAM,KAAK;CACvB,UAAY,IAIhB,OAAO;AACT,GAEM,KACJ,GACA,MACwB;CACxB,IAAM,IAAQ,EAAI;CAClB,IAAI,MAAU,MAAQ,MAAU,IAC9B,OAAO;CAET,IAAI,OAAO,KAAU,UAAU;EAC7B,IAAI,MAAU,QACZ,OAAO;EAET,IAAI,MAAU,SACZ,OAAO;CAEX;AAEF,GAEM,KAAsB,MACnB,EAAQ,EAAyB,GAAK,KAAK;AAOpD,SAAgB,EAAQ,GAAiC;CACvD,OAAO,EAAkB,GAAS,CAAG;AACvC;AAKA,SAAgB,EAAe,GAAkC;CAC/D,OAAO,EAAyB,GAAS,CAAG;AAC9C;AAKA,SAAgB,IAAoB;CAClC,OAAO,EAAmB,CAAO;AACnC;AAWA,SAAgB,EAAoB,GAAyC;CAC3E,IAAM,EAAE,YAAS,aAAU,UAAO,MAAY;CAC9C,KAAK,IAAM,KAAO,GAAS;EACzB,IAAM,IAAQ,EAAK,CAAG;EACtB,IAAI,KAAS,MACX,OAAO;CAEX;CACA,OAAO;AACT;AASA,SAAgB,EACd,GACQ;CACR,IAAM,EAAE,YAAS,aAAU,iBAAc,UAAO,MAAY;CAE5D,KAAK,IAAM,KAAO,GAAS;EACzB,IAAM,IAAQ,EAAK,CAAG;EACtB,IAAI,KAAS,MACX,OAAO;CAEX;CAUA,OARI,EAAa,WAAW,MAAM,IACzB,EAAa,QAAQ,SAAS,IAAI,IAGvC,EAAa,WAAW,GAAG,IACtB,EAAa,QAAQ,cAAc,KAAK,IAG1C;AACT;AAEA,IAAa,IAAS;CACpB;CACA;CACA;AACF"}
|
package/lib/esm/environment.js
CHANGED
|
@@ -1,63 +1,123 @@
|
|
|
1
1
|
import { isDevEnv as e } from "./envHelpers.js";
|
|
2
2
|
import { Environment as t, Network as n, Observable as r, RecordSource as i, Store as a } from "relay-runtime";
|
|
3
3
|
//#region src/environment.ts
|
|
4
|
-
var o = "/api/graphql", s = "/api/ws", c, l, u, d, f =
|
|
4
|
+
var o = "/api/graphql", s = "/api/ws", c, l, u = 0, d = /* @__PURE__ */ new Set(), f, p = async (e, t) => globalThis.fetch(e, t), m = p, h, g = "require-preflight", ee = "true", te = (e) => typeof e == "object" && !!e && !Array.isArray(e), _ = (e) => {
|
|
5
|
+
if (!te(e) || typeof e.type != "string") return {
|
|
6
|
+
ok: !1,
|
|
7
|
+
reason: "invalid"
|
|
8
|
+
};
|
|
9
|
+
let { payload: t } = e;
|
|
10
|
+
switch (e.type) {
|
|
11
|
+
case "connection_ack":
|
|
12
|
+
case "connection_auth_update_ack":
|
|
13
|
+
case "ping":
|
|
14
|
+
case "pong": return {
|
|
15
|
+
ok: !0,
|
|
16
|
+
value: {
|
|
17
|
+
type: e.type,
|
|
18
|
+
payload: t
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
case "next": return typeof e.id == "string" ? {
|
|
22
|
+
ok: !0,
|
|
23
|
+
value: {
|
|
24
|
+
id: e.id,
|
|
25
|
+
type: e.type,
|
|
26
|
+
payload: t
|
|
27
|
+
}
|
|
28
|
+
} : {
|
|
29
|
+
ok: !1,
|
|
30
|
+
reason: "invalid"
|
|
31
|
+
};
|
|
32
|
+
case "error": return e.id != null && typeof e.id != "string" ? {
|
|
33
|
+
ok: !1,
|
|
34
|
+
reason: "invalid"
|
|
35
|
+
} : e.id == null ? {
|
|
36
|
+
ok: !0,
|
|
37
|
+
value: {
|
|
38
|
+
type: e.type,
|
|
39
|
+
payload: t
|
|
40
|
+
}
|
|
41
|
+
} : {
|
|
42
|
+
ok: !0,
|
|
43
|
+
value: {
|
|
44
|
+
id: e.id,
|
|
45
|
+
type: e.type,
|
|
46
|
+
payload: t
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
case "complete": return typeof e.id == "string" ? {
|
|
50
|
+
ok: !0,
|
|
51
|
+
value: {
|
|
52
|
+
id: e.id,
|
|
53
|
+
type: e.type
|
|
54
|
+
}
|
|
55
|
+
} : {
|
|
56
|
+
ok: !1,
|
|
57
|
+
reason: "invalid"
|
|
58
|
+
};
|
|
59
|
+
default: return {
|
|
60
|
+
ok: !1,
|
|
61
|
+
reason: "unsupported"
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}, ne = "graphql-transport-ws", re = 5e3, ie = 1e4, ae = 10, oe = 500, v = "Relay operation cancelled after environment rotation", y = {
|
|
5
65
|
generation: 0,
|
|
6
66
|
reason: null,
|
|
7
67
|
status: "idle"
|
|
8
|
-
},
|
|
68
|
+
}, b = /* @__PURE__ */ new Set(), x = {
|
|
9
69
|
generation: 0,
|
|
10
70
|
pendingCount: 0,
|
|
11
71
|
pendingOperations: []
|
|
12
|
-
},
|
|
13
|
-
function
|
|
14
|
-
var
|
|
15
|
-
e() && (
|
|
72
|
+
}, S = 0, C = /* @__PURE__ */ new Map(), w = /* @__PURE__ */ new Set(), T = !1;
|
|
73
|
+
function E() {}
|
|
74
|
+
var D = E;
|
|
75
|
+
e() && (D = (e) => {
|
|
16
76
|
console.log("relayFieldLogger: ", e);
|
|
17
77
|
});
|
|
18
|
-
var
|
|
78
|
+
var O = (e) => {
|
|
19
79
|
if (typeof e != "object" || !e) return null;
|
|
20
80
|
let { id: t } = e;
|
|
21
81
|
return typeof t != "string" || t.trim() === "" ? null : t;
|
|
22
|
-
},
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
generation:
|
|
82
|
+
}, k = (e, t) => h?.(e, t) ?? O(e, t);
|
|
83
|
+
function A(e, t) {
|
|
84
|
+
y = {
|
|
85
|
+
generation: y.generation + 1,
|
|
26
86
|
reason: t,
|
|
27
87
|
status: e
|
|
28
88
|
};
|
|
29
|
-
for (let e of
|
|
30
|
-
}
|
|
31
|
-
function
|
|
32
|
-
|
|
33
|
-
generation:
|
|
34
|
-
pendingCount:
|
|
35
|
-
pendingOperations: [...
|
|
36
|
-
}, !
|
|
37
|
-
|
|
38
|
-
for (let e of
|
|
89
|
+
for (let e of b) e();
|
|
90
|
+
}
|
|
91
|
+
function j() {
|
|
92
|
+
x = {
|
|
93
|
+
generation: x.generation + 1,
|
|
94
|
+
pendingCount: C.size,
|
|
95
|
+
pendingOperations: [...C.values()]
|
|
96
|
+
}, !T && (T = !0, queueMicrotask(() => {
|
|
97
|
+
T = !1;
|
|
98
|
+
for (let e of w) e();
|
|
39
99
|
}));
|
|
40
100
|
}
|
|
41
|
-
function
|
|
42
|
-
|
|
101
|
+
function se() {
|
|
102
|
+
C.size !== 0 && (C.clear(), j());
|
|
43
103
|
}
|
|
44
|
-
function
|
|
104
|
+
function ce(e) {
|
|
45
105
|
return e === "query" || e === "mutation" ? e : "unknown";
|
|
46
106
|
}
|
|
47
|
-
function
|
|
48
|
-
let t =
|
|
49
|
-
|
|
107
|
+
function le(e) {
|
|
108
|
+
let t = S;
|
|
109
|
+
S += 1, C.set(t, {
|
|
50
110
|
id: t,
|
|
51
|
-
kind:
|
|
111
|
+
kind: ce(e.operationKind),
|
|
52
112
|
name: e.name ?? "AnonymousGraphQLOperation",
|
|
53
113
|
startedAt: Date.now()
|
|
54
|
-
}),
|
|
114
|
+
}), j();
|
|
55
115
|
let n = !1;
|
|
56
116
|
return () => {
|
|
57
|
-
n || (n = !0,
|
|
117
|
+
n || (n = !0, C.delete(t) && j());
|
|
58
118
|
};
|
|
59
119
|
}
|
|
60
|
-
function
|
|
120
|
+
function ue() {
|
|
61
121
|
if (l != null) {
|
|
62
122
|
try {
|
|
63
123
|
l.dispose();
|
|
@@ -65,36 +125,66 @@ function N() {
|
|
|
65
125
|
l = void 0;
|
|
66
126
|
}
|
|
67
127
|
}
|
|
68
|
-
function
|
|
69
|
-
|
|
128
|
+
function de() {
|
|
129
|
+
if (l != null) {
|
|
130
|
+
try {
|
|
131
|
+
l.terminate();
|
|
132
|
+
} catch {}
|
|
133
|
+
l = void 0;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function fe() {
|
|
137
|
+
for (let e of d) e.abort();
|
|
138
|
+
d.clear();
|
|
139
|
+
}
|
|
140
|
+
function pe(e) {
|
|
141
|
+
let t = e.getStore(), n = t.getSource(), r = new i();
|
|
142
|
+
for (let e of n.getRecordIDs()) r.delete(e);
|
|
143
|
+
try {
|
|
144
|
+
r.size() > 0 && (t.publish(r), t.notify());
|
|
145
|
+
} finally {
|
|
146
|
+
n instanceof i && n.clear();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function M(e, t) {
|
|
150
|
+
let n = c;
|
|
151
|
+
c = void 0, u += 1, fe(), de();
|
|
152
|
+
try {
|
|
153
|
+
n != null && pe(n);
|
|
154
|
+
} finally {
|
|
155
|
+
se(), A(e, t);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function me() {
|
|
159
|
+
return y;
|
|
70
160
|
}
|
|
71
|
-
function
|
|
72
|
-
return
|
|
73
|
-
|
|
161
|
+
function he(e) {
|
|
162
|
+
return b.add(e), () => {
|
|
163
|
+
b.delete(e);
|
|
74
164
|
};
|
|
75
165
|
}
|
|
76
|
-
function
|
|
77
|
-
return
|
|
166
|
+
function N() {
|
|
167
|
+
return x;
|
|
78
168
|
}
|
|
79
|
-
function
|
|
80
|
-
return
|
|
81
|
-
|
|
169
|
+
function P(e) {
|
|
170
|
+
return w.add(e), () => {
|
|
171
|
+
w.delete(e);
|
|
82
172
|
};
|
|
83
173
|
}
|
|
84
|
-
function
|
|
85
|
-
|
|
174
|
+
function F(e = {}) {
|
|
175
|
+
ue(), A("reconnecting", e.reason ?? "manual_reconnect");
|
|
86
176
|
}
|
|
87
|
-
async function
|
|
177
|
+
async function I(e = {}) {
|
|
88
178
|
let t = l;
|
|
89
|
-
t?.hasActiveConnection() && (await t.refreshAuthentication() ||
|
|
179
|
+
t?.hasActiveConnection() && (await t.refreshAuthentication() || F({ reason: e.reason ?? "auth_refresh_fallback" }));
|
|
90
180
|
}
|
|
91
|
-
function
|
|
92
|
-
let { httpUrl: t, wsUrl: n, logEvents: r, getDataId: i, getAuthHeaders: a } = e;
|
|
93
|
-
typeof t == "string" && t !== "" && (o = t), typeof n == "string" && n !== "" && (s = n), r != null && (
|
|
181
|
+
function L(e = {}) {
|
|
182
|
+
let { httpUrl: t, wsUrl: n, logEvents: r, getDataId: i, getAuthHeaders: a, fetchImpl: c } = e;
|
|
183
|
+
typeof t == "string" && t !== "" && (o = t), typeof n == "string" && n !== "" && (s = n), r != null && (D = r ? (e) => {
|
|
94
184
|
console.log("relayFieldLogger: ", e);
|
|
95
|
-
} :
|
|
185
|
+
} : E), h = i, f = a, m = c ?? p, M("configured", "configure");
|
|
96
186
|
}
|
|
97
|
-
function
|
|
187
|
+
function ge() {
|
|
98
188
|
let e = s;
|
|
99
189
|
if (e.startsWith("ws")) return e;
|
|
100
190
|
if (e.startsWith("http")) return e.replace(/^http/, "ws");
|
|
@@ -105,13 +195,13 @@ function F() {
|
|
|
105
195
|
}
|
|
106
196
|
return e;
|
|
107
197
|
}
|
|
108
|
-
function
|
|
198
|
+
function R(...t) {
|
|
109
199
|
e() && console.log("[RelayNetwork]", ...t);
|
|
110
200
|
}
|
|
111
|
-
async function
|
|
112
|
-
return
|
|
201
|
+
async function z() {
|
|
202
|
+
return f == null ? {} : f();
|
|
113
203
|
}
|
|
114
|
-
var
|
|
204
|
+
var B = class {
|
|
115
205
|
url;
|
|
116
206
|
socket = null;
|
|
117
207
|
connectPromise = null;
|
|
@@ -133,13 +223,19 @@ var R = class {
|
|
|
133
223
|
let { socket: e } = this;
|
|
134
224
|
this.socket = null, this.connectPromise = null, this.acknowledged = !1, e != null && e.readyState !== WebSocket.CLOSED && e.close(1e3, "Normal Closure");
|
|
135
225
|
}
|
|
226
|
+
terminate() {
|
|
227
|
+
for (let e of this.subscriptions.values()) try {
|
|
228
|
+
e.sink.complete();
|
|
229
|
+
} catch {}
|
|
230
|
+
this.subscriptions.clear(), this.dispose();
|
|
231
|
+
}
|
|
136
232
|
subscribe(e, t) {
|
|
137
233
|
let n = String(this.nextId);
|
|
138
234
|
return this.nextId += 1, this.subscriptions.set(n, {
|
|
139
235
|
payload: e,
|
|
140
236
|
sink: t
|
|
141
237
|
}), this.connectAndSubscribe(n).catch((e) => {
|
|
142
|
-
|
|
238
|
+
R("subscription websocket connect failed", e), this.scheduleReconnect();
|
|
143
239
|
}), () => {
|
|
144
240
|
this.unsubscribe(n);
|
|
145
241
|
};
|
|
@@ -151,14 +247,14 @@ var R = class {
|
|
|
151
247
|
if (this.pendingAuthUpdate != null) return !1;
|
|
152
248
|
let t;
|
|
153
249
|
try {
|
|
154
|
-
t = await
|
|
250
|
+
t = await z();
|
|
155
251
|
} catch {
|
|
156
252
|
return !1;
|
|
157
253
|
}
|
|
158
254
|
return Object.keys(t).length !== 0 && new Promise((e) => {
|
|
159
255
|
let n = setTimeout(() => {
|
|
160
256
|
this.pendingAuthUpdate = null, e(!1);
|
|
161
|
-
},
|
|
257
|
+
}, re);
|
|
162
258
|
this.pendingAuthUpdate = {
|
|
163
259
|
resolve: e,
|
|
164
260
|
timeout: n
|
|
@@ -199,9 +295,9 @@ var R = class {
|
|
|
199
295
|
n || (n = !0, e());
|
|
200
296
|
}, i = (e) => {
|
|
201
297
|
n || (n = !0, t(e));
|
|
202
|
-
}, a = new WebSocket(this.url,
|
|
298
|
+
}, a = new WebSocket(this.url, ne);
|
|
203
299
|
this.socket = a, this.acknowledged = !1, a.onopen = () => {
|
|
204
|
-
|
|
300
|
+
z().catch(() => ({})).then((e) => {
|
|
205
301
|
if (a.readyState === WebSocket.OPEN) {
|
|
206
302
|
if (Object.keys(e).length > 0) {
|
|
207
303
|
this.send({
|
|
@@ -232,7 +328,14 @@ var R = class {
|
|
|
232
328
|
n(/* @__PURE__ */ Error("Invalid GraphQL websocket message")), this.socket?.close(4400, "Invalid websocket message");
|
|
233
329
|
return;
|
|
234
330
|
}
|
|
235
|
-
|
|
331
|
+
let i = _(r);
|
|
332
|
+
if (!i.ok) {
|
|
333
|
+
let e = i.reason === "unsupported", t = "Invalid GraphQL websocket message", r = "Invalid websocket message";
|
|
334
|
+
e && (t = "Unsupported GraphQL websocket message", r = "Unsupported websocket message"), n(Error(t)), this.socket?.close(4400, r);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
let a = i.value;
|
|
338
|
+
switch (a.type) {
|
|
236
339
|
case "connection_ack":
|
|
237
340
|
this.acknowledged = !0, this.retryCount = 0, this.startKeepAlive(), t(), this.resubscribeActiveOperations();
|
|
238
341
|
return;
|
|
@@ -240,37 +343,33 @@ var R = class {
|
|
|
240
343
|
this.resolvePendingAuthUpdate(!0);
|
|
241
344
|
return;
|
|
242
345
|
case "next":
|
|
243
|
-
this.subscriptions.get(
|
|
346
|
+
this.subscriptions.get(a.id)?.sink.next(a.payload);
|
|
244
347
|
return;
|
|
245
348
|
case "error":
|
|
246
|
-
|
|
349
|
+
a.id == null ? this.resolvePendingAuthUpdate(!1) : this.failSubscription(a.id, a.payload);
|
|
247
350
|
return;
|
|
248
351
|
case "complete":
|
|
249
|
-
this.completeSubscription(
|
|
250
|
-
return;
|
|
251
|
-
case "ping":
|
|
252
|
-
this.send({
|
|
253
|
-
type: "pong",
|
|
254
|
-
payload: r.payload
|
|
255
|
-
});
|
|
352
|
+
this.completeSubscription(a.id);
|
|
256
353
|
return;
|
|
257
|
-
case "
|
|
258
|
-
|
|
354
|
+
case "ping": this.send({
|
|
355
|
+
type: "pong",
|
|
356
|
+
payload: a.payload
|
|
357
|
+
});
|
|
259
358
|
}
|
|
260
359
|
}
|
|
261
360
|
handleClose() {
|
|
262
361
|
this.acknowledged = !1, this.socket = null, this.connectPromise = null, this.stopKeepAlive(), this.resolvePendingAuthUpdate(!1), !this.disposed && this.subscriptions.size > 0 && this.scheduleReconnect();
|
|
263
362
|
}
|
|
264
363
|
scheduleReconnect() {
|
|
265
|
-
if (this.retryCount >=
|
|
364
|
+
if (this.retryCount >= ae) {
|
|
266
365
|
let e = /* @__PURE__ */ Error("GraphQL subscription websocket retry exhausted");
|
|
267
366
|
for (let t of [...this.subscriptions.keys()]) this.failSubscription(t, e);
|
|
268
367
|
return;
|
|
269
368
|
}
|
|
270
|
-
let e = Math.min(8e3,
|
|
369
|
+
let e = Math.min(8e3, oe * 2 ** this.retryCount) + Math.random() * 200;
|
|
271
370
|
this.retryCount += 1, setTimeout(() => {
|
|
272
371
|
this.disposed || this.subscriptions.size === 0 || this.ensureConnected().catch((e) => {
|
|
273
|
-
this.scheduleReconnect(),
|
|
372
|
+
this.scheduleReconnect(), R("subscription websocket reconnect failed", e);
|
|
274
373
|
});
|
|
275
374
|
}, e);
|
|
276
375
|
}
|
|
@@ -298,7 +397,7 @@ var R = class {
|
|
|
298
397
|
startKeepAlive() {
|
|
299
398
|
this.stopKeepAlive(), this.keepAliveTimer = setInterval(() => {
|
|
300
399
|
this.socket?.readyState === WebSocket.OPEN && this.send({ type: "ping" });
|
|
301
|
-
},
|
|
400
|
+
}, ie);
|
|
302
401
|
}
|
|
303
402
|
stopKeepAlive() {
|
|
304
403
|
this.keepAliveTimer != null && (clearInterval(this.keepAliveTimer), this.keepAliveTimer = null);
|
|
@@ -312,29 +411,29 @@ var R = class {
|
|
|
312
411
|
t?.readyState === WebSocket.OPEN && t.send(JSON.stringify(e));
|
|
313
412
|
}
|
|
314
413
|
};
|
|
315
|
-
function
|
|
414
|
+
function _e() {
|
|
316
415
|
if (l != null) return l;
|
|
317
416
|
if (typeof window > "u") throw Error("GraphQL subscriptions unavailable in non-browser environment");
|
|
318
|
-
return l = new
|
|
417
|
+
return l = new B(ge()), l;
|
|
319
418
|
}
|
|
320
|
-
function
|
|
419
|
+
function V(e) {
|
|
321
420
|
return typeof File < "u" && e instanceof File || typeof Blob < "u" && e instanceof Blob;
|
|
322
421
|
}
|
|
323
|
-
function
|
|
324
|
-
if (
|
|
422
|
+
function H(e, t, n) {
|
|
423
|
+
if (V(e)) return n.push({
|
|
325
424
|
path: t,
|
|
326
425
|
file: e
|
|
327
426
|
}), null;
|
|
328
|
-
if (Array.isArray(e)) return e.map((e, r) =>
|
|
427
|
+
if (Array.isArray(e)) return e.map((e, r) => H(e, `${t}.${r}`, n));
|
|
329
428
|
if (typeof e == "object" && e) {
|
|
330
429
|
let r = {};
|
|
331
|
-
for (let [i, a] of Object.entries(e)) r[i] =
|
|
430
|
+
for (let [i, a] of Object.entries(e)) r[i] = H(a, `${t}.${i}`, n);
|
|
332
431
|
return r;
|
|
333
432
|
}
|
|
334
433
|
return e;
|
|
335
434
|
}
|
|
336
|
-
function
|
|
337
|
-
let n = [], r =
|
|
435
|
+
function U(e, t) {
|
|
436
|
+
let n = [], r = H(t, "variables", n), i = new FormData(), a = {
|
|
338
437
|
query: e,
|
|
339
438
|
variables: r
|
|
340
439
|
};
|
|
@@ -346,13 +445,13 @@ function H(e, t) {
|
|
|
346
445
|
i.append(String(t), e.file);
|
|
347
446
|
}), i;
|
|
348
447
|
}
|
|
349
|
-
var
|
|
448
|
+
var W = {
|
|
350
449
|
maxAttempts: 3,
|
|
351
450
|
baseDelayMs: 300,
|
|
352
451
|
maxDelayMs: 4e3,
|
|
353
452
|
fetchTimeoutMs: 6e5
|
|
354
453
|
};
|
|
355
|
-
async function
|
|
454
|
+
async function ve(e) {
|
|
356
455
|
await new Promise((t) => {
|
|
357
456
|
setTimeout(() => {
|
|
358
457
|
t();
|
|
@@ -379,7 +478,7 @@ var J = class extends Error {
|
|
|
379
478
|
function Y(e) {
|
|
380
479
|
return e == null ? !1 : typeof e != "object" || Object.values(e).some((e) => e != null);
|
|
381
480
|
}
|
|
382
|
-
function
|
|
481
|
+
function ye(e) {
|
|
383
482
|
let { data: t, errors: n } = e;
|
|
384
483
|
if (typeof t != "object" || !t || !Array.isArray(n) || n.length === 0) return !1;
|
|
385
484
|
let r = t;
|
|
@@ -389,72 +488,78 @@ function ce(e) {
|
|
|
389
488
|
});
|
|
390
489
|
}
|
|
391
490
|
function X(e) {
|
|
392
|
-
return Array.isArray(e.errors) && e.errors.length > 0 && !Y(e.data) && !
|
|
491
|
+
return Array.isArray(e.errors) && e.errors.length > 0 && !Y(e.data) && !ye(e);
|
|
393
492
|
}
|
|
394
493
|
async function Z(e, t, n, r) {
|
|
395
|
-
let i = e.text;
|
|
396
|
-
if (
|
|
397
|
-
let
|
|
398
|
-
async function
|
|
399
|
-
|
|
494
|
+
let i = u, a = e.text;
|
|
495
|
+
if (a == null) throw Error("Missing GraphQL query text");
|
|
496
|
+
let s = a, c = le(e);
|
|
497
|
+
async function l(e) {
|
|
498
|
+
if (i !== u) throw Error(v);
|
|
499
|
+
let n = new AbortController();
|
|
500
|
+
d.add(n);
|
|
501
|
+
let r = setTimeout(() => {
|
|
400
502
|
n.abort();
|
|
401
|
-
},
|
|
503
|
+
}, W.fetchTimeoutMs);
|
|
402
504
|
try {
|
|
403
|
-
let r,
|
|
404
|
-
if (
|
|
405
|
-
Object.assign(
|
|
505
|
+
let r, a = {};
|
|
506
|
+
if (f != null) try {
|
|
507
|
+
Object.assign(a, await f());
|
|
406
508
|
} catch {}
|
|
509
|
+
if (i !== u) throw Error(v);
|
|
407
510
|
(() => {
|
|
408
511
|
try {
|
|
409
512
|
let e = [t];
|
|
410
513
|
for (; e.length > 0;) {
|
|
411
514
|
let t = e.pop();
|
|
412
|
-
if (
|
|
515
|
+
if (V(t)) return !0;
|
|
413
516
|
Array.isArray(t) ? e.push(...t) : typeof t == "object" && t && e.push(...Object.values(t));
|
|
414
517
|
}
|
|
415
518
|
} catch {}
|
|
416
519
|
return !1;
|
|
417
|
-
})() ? (r =
|
|
418
|
-
query:
|
|
520
|
+
})() ? (r = U(s, t), a[g] = ee) : (a["Content-Type"] = "application/json", r = JSON.stringify({
|
|
521
|
+
query: s,
|
|
419
522
|
variables: t
|
|
420
523
|
}));
|
|
421
|
-
let
|
|
524
|
+
let c = await m(o, {
|
|
422
525
|
method: "POST",
|
|
423
526
|
body: r,
|
|
424
|
-
headers:
|
|
527
|
+
headers: a,
|
|
425
528
|
credentials: "include",
|
|
426
529
|
signal: n.signal
|
|
427
530
|
});
|
|
428
|
-
if (
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
531
|
+
if (i !== u) throw Error(v);
|
|
532
|
+
if (!c.ok) {
|
|
533
|
+
if (e < W.maxAttempts - 1 && K(c.status)) throw Error(`Retryable HTTP status ${c.status}`);
|
|
534
|
+
let t = await c.text().catch(() => "");
|
|
535
|
+
throw Error(`GraphQL HTTP error ${c.status}: ${t}`);
|
|
432
536
|
}
|
|
433
|
-
let
|
|
434
|
-
if (Array.isArray(
|
|
435
|
-
return
|
|
537
|
+
let l = await c.json();
|
|
538
|
+
if (Array.isArray(l.errors) && l.errors.length > 0 && R("GraphQL errors", l.errors), X(l)) throw new J(l.errors ?? [], l);
|
|
539
|
+
return l;
|
|
436
540
|
} finally {
|
|
437
|
-
clearTimeout(r);
|
|
541
|
+
clearTimeout(r), d.delete(n);
|
|
438
542
|
}
|
|
439
543
|
}
|
|
440
544
|
try {
|
|
441
|
-
for (let e = 0; e <
|
|
442
|
-
return await
|
|
545
|
+
for (let e = 0; e < W.maxAttempts; e += 1) try {
|
|
546
|
+
return await l(e);
|
|
443
547
|
} catch (t) {
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
548
|
+
if (i !== u) throw Error(v, { cause: t });
|
|
549
|
+
let n = e === W.maxAttempts - 1;
|
|
550
|
+
if (!(q(t) || t instanceof Error && t.message.includes("Retryable HTTP status")) || n) throw R("GraphQL fetch error", t), t;
|
|
551
|
+
await ve(G(e, W));
|
|
447
552
|
}
|
|
448
553
|
throw Error("Exhausted retries without returning a result");
|
|
449
554
|
} finally {
|
|
450
|
-
|
|
555
|
+
c();
|
|
451
556
|
}
|
|
452
557
|
}
|
|
453
|
-
function
|
|
454
|
-
return
|
|
558
|
+
function Q(e, t) {
|
|
559
|
+
return R("subscription:start", e.name, t), r.create((n) => {
|
|
455
560
|
let r = e.text;
|
|
456
561
|
if (r == null || r === "") return n.error(/* @__PURE__ */ Error("Subscription operation text is empty")), () => {};
|
|
457
|
-
let i =
|
|
562
|
+
let i = _e().subscribe({
|
|
458
563
|
query: r,
|
|
459
564
|
variables: t,
|
|
460
565
|
operationName: e.name
|
|
@@ -475,35 +580,36 @@ function le(e, t) {
|
|
|
475
580
|
};
|
|
476
581
|
});
|
|
477
582
|
}
|
|
478
|
-
function Q() {
|
|
479
|
-
return n.create(Z, le);
|
|
480
|
-
}
|
|
481
583
|
function $() {
|
|
584
|
+
return n.create(Z, Q);
|
|
585
|
+
}
|
|
586
|
+
function be() {
|
|
482
587
|
return c ??= new t({
|
|
483
|
-
getDataID:
|
|
484
|
-
relayFieldLogger:
|
|
485
|
-
network:
|
|
588
|
+
getDataID: k,
|
|
589
|
+
relayFieldLogger: D,
|
|
590
|
+
network: $(),
|
|
486
591
|
store: new a(new i())
|
|
487
592
|
}), c;
|
|
488
593
|
}
|
|
489
|
-
function
|
|
490
|
-
|
|
491
|
-
e.getStore().publish(t), e.getStore().notify();
|
|
594
|
+
function xe() {
|
|
595
|
+
M("configured", "session_reset");
|
|
492
596
|
}
|
|
493
|
-
var
|
|
494
|
-
RelayWebSocketClient:
|
|
495
|
-
buildFormData:
|
|
597
|
+
var Se = {
|
|
598
|
+
RelayWebSocketClient: B,
|
|
599
|
+
buildFormData: U,
|
|
496
600
|
calcBackoff: G,
|
|
497
|
-
collectFiles:
|
|
601
|
+
collectFiles: H,
|
|
602
|
+
decodeGraphqlWsMessage: _,
|
|
498
603
|
fetchFn: Z,
|
|
499
|
-
getDataId:
|
|
604
|
+
getDataId: k,
|
|
500
605
|
hasUsableTopLevelData: Y,
|
|
501
606
|
isNetworkError: q,
|
|
502
607
|
isRetryableStatus: K,
|
|
503
|
-
isUploadable:
|
|
504
|
-
shouldThrowGraphQLResponseError: X
|
|
608
|
+
isUploadable: V,
|
|
609
|
+
shouldThrowGraphQLResponseError: X,
|
|
610
|
+
subscribeFn: Q
|
|
505
611
|
};
|
|
506
612
|
//#endregion
|
|
507
|
-
export { J as GraphQLResponseError,
|
|
613
|
+
export { J as GraphQLResponseError, Se as __test, L as configureRelayEnvironment, be as getEnvironment, $ as getNetwork, N as getRelayOperationActivitySnapshot, me as getRelayTransportSnapshot, F as reconnectRelayWebSocket, I as refreshRelayWebSocketAuthentication, xe as resetRelayStore, P as subscribeRelayOperationActivity, he as subscribeRelayTransport };
|
|
508
614
|
|
|
509
615
|
//# sourceMappingURL=environment.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"environment.js","names":[],"sources":["../../src/environment.ts"],"sourcesContent":["import {\n Environment,\n Network,\n Observable,\n RecordSource,\n Store,\n type FetchFunction,\n type SubscribeFunction,\n type RelayFieldLogger,\n} from 'relay-runtime';\nimport { isDevEnv } from './envHelpers.js';\n\nlet graphqlHttpEndpoint = '/api/graphql';\nlet graphqlWsEndpoint = '/api/ws';\n\n// (anonymousEnvironment reserved if needed later for public access / logout scenarios)\n// Removed until actually required to avoid lint errors.\n// let anonymousEnvironment: Environment | undefined;\nlet environment: Environment | undefined;\nlet wsClient: RelayWebSocketClient | undefined;\nlet getAuthHeaders:\n (() => Record<string, string> | Promise<Record<string, string>>) | undefined;\nlet customDataIdResolver:\n ((fieldValue: unknown, typeName: string) => string | null) | undefined;\n\nconst GRAPHQL_CSRF_PREFLIGHT_HEADER = 'require-preflight';\nconst GRAPHQL_CSRF_PREFLIGHT_HEADER_VALUE = 'true';\n\nexport type RelayTransportStatus = 'idle' | 'configured' | 'reconnecting';\n\nexport interface RelayTransportSnapshot {\n generation: number;\n reason: string | null;\n status: RelayTransportStatus;\n}\n\nexport type RelayOperationKind = 'mutation' | 'query' | 'unknown';\n\nexport type RelayPendingOperation = {\n id: number;\n kind: RelayOperationKind;\n name: string;\n startedAt: number;\n};\n\nexport interface RelayOperationActivitySnapshot {\n generation: number;\n pendingCount: number;\n pendingOperations: readonly RelayPendingOperation[];\n}\n\nexport interface RelayReconnectOptions {\n reason?: string;\n}\n\nexport interface RelayAuthRefreshOptions {\n reason?: string;\n}\n\ntype RelayTransportListener = () => void;\ntype RelayOperationActivityListener = () => void;\n\ntype GraphqlWsPayload = Record<string, unknown>;\n\ntype GraphqlWsMessage =\n | { type: 'connection_ack'; payload?: unknown }\n | { type: 'connection_auth_update_ack'; payload?: unknown }\n | { id: string; type: 'next'; payload: unknown }\n | { id?: string; type: 'error'; payload: unknown }\n | { id: string; type: 'complete' }\n | { type: 'ping'; payload?: unknown }\n | { type: 'pong'; payload?: unknown };\n\ntype SubscriptionSink = {\n next: (value: unknown) => void;\n error: (error: unknown) => void;\n complete: () => void;\n};\n\ntype ActiveSubscription = {\n payload: {\n query: string;\n variables: Record<string, unknown>;\n operationName: string;\n };\n sink: SubscriptionSink;\n};\n\nconst GRAPHQL_TRANSPORT_WS_PROTOCOL = 'graphql-transport-ws';\nconst AUTH_UPDATE_TIMEOUT_MS = 5_000;\nconst WS_KEEP_ALIVE_MS = 10_000;\nconst WS_MAX_RETRY_ATTEMPTS = 10;\nconst WS_BASE_RETRY_DELAY_MS = 500;\n\nlet relayTransportSnapshot: RelayTransportSnapshot = {\n generation: 0,\n reason: null,\n status: 'idle',\n};\nconst relayTransportListeners = new Set<RelayTransportListener>();\n\nlet relayOperationActivitySnapshot: RelayOperationActivitySnapshot = {\n generation: 0,\n pendingCount: 0,\n pendingOperations: [],\n};\nlet relayOperationActivityId = 0;\nconst relayPendingOperations = new Map<number, RelayPendingOperation>();\nconst relayOperationActivityListeners =\n new Set<RelayOperationActivityListener>();\nlet relayOperationActivityNotifyScheduled = false;\n\n/**\n * No-op Relay field logger used to satisfy @required(action: LOG).\n */\nfunction noopRelayFieldLogger(): void {}\n\nlet relayFieldLogger: RelayFieldLogger = noopRelayFieldLogger;\n\nif (isDevEnv()) {\n relayFieldLogger = (event) => {\n // eslint-disable-next-line no-console\n console.log('relayFieldLogger: ', event);\n };\n}\n\nconst defaultGetDataId: (\n fieldValue: unknown,\n typeName: string,\n) => string | null = (fieldValue) => {\n if (fieldValue == null || typeof fieldValue !== 'object') {\n return null;\n }\n const { id } = fieldValue as { id?: unknown };\n if (typeof id !== 'string' || id.trim() === '') {\n return null;\n }\n return id;\n};\n\nconst getDataId = (fieldValue: unknown, typeName: string): string | null => {\n const customId = customDataIdResolver?.(fieldValue, typeName);\n if (customId != null) {\n return customId;\n }\n return defaultGetDataId(fieldValue, typeName);\n};\n\n/**\n * Notify subscribers that the Relay transport state changed.\n */\nfunction emitRelayTransportChange(\n status: RelayTransportStatus,\n reason: string | null,\n): void {\n relayTransportSnapshot = {\n generation: relayTransportSnapshot.generation + 1,\n reason,\n status,\n };\n for (const listener of relayTransportListeners) {\n listener();\n }\n}\n\n/**\n * Publish the latest Relay HTTP operation activity snapshot.\n */\nfunction emitRelayOperationActivityChange(): void {\n relayOperationActivitySnapshot = {\n generation: relayOperationActivitySnapshot.generation + 1,\n pendingCount: relayPendingOperations.size,\n pendingOperations: [...relayPendingOperations.values()],\n };\n if (relayOperationActivityNotifyScheduled) {\n return;\n }\n relayOperationActivityNotifyScheduled = true;\n queueMicrotask(() => {\n relayOperationActivityNotifyScheduled = false;\n for (const listener of relayOperationActivityListeners) {\n listener();\n }\n });\n}\n\n/**\n * Clear pending Relay HTTP operations after environment reconfiguration.\n */\nfunction resetRelayOperationActivity(): void {\n if (relayPendingOperations.size === 0) {\n return;\n }\n relayPendingOperations.clear();\n emitRelayOperationActivityChange();\n}\n\n/**\n * Normalize Relay operation kind values from runtime requests.\n */\nfunction resolveRelayOperationKind(operationKind: unknown): RelayOperationKind {\n if (operationKind === 'query' || operationKind === 'mutation') {\n return operationKind;\n }\n return 'unknown';\n}\n\n/**\n * Track a Relay HTTP operation until its returned disposer is called.\n */\nfunction startRelayOperationActivity(request: {\n name?: string | null;\n operationKind?: unknown;\n}): () => void {\n const id = relayOperationActivityId;\n relayOperationActivityId += 1;\n relayPendingOperations.set(id, {\n id,\n kind: resolveRelayOperationKind(request.operationKind),\n name: request.name ?? 'AnonymousGraphQLOperation',\n startedAt: Date.now(),\n });\n emitRelayOperationActivityChange();\n\n let isSettled = false;\n return () => {\n if (isSettled) {\n return;\n }\n isSettled = true;\n if (!relayPendingOperations.delete(id)) {\n return;\n }\n emitRelayOperationActivityChange();\n };\n}\n\n/**\n * Dispose the active websocket client and ignore shutdown failures.\n */\nfunction disposeWsClient(): void {\n if (wsClient == null) {\n return;\n }\n try {\n wsClient.dispose();\n } catch {\n // ignore errors during dispose\n }\n wsClient = undefined;\n}\n\n/**\n * Return the current Relay transport state used by subscription hooks.\n */\nexport function getRelayTransportSnapshot(): RelayTransportSnapshot {\n return relayTransportSnapshot;\n}\n\n/**\n * Subscribe to Relay transport changes.\n */\nexport function subscribeRelayTransport(\n listener: RelayTransportListener,\n): () => void {\n relayTransportListeners.add(listener);\n return () => {\n relayTransportListeners.delete(listener);\n };\n}\n\n/**\n * Return the current Relay HTTP operation activity snapshot.\n */\nexport function getRelayOperationActivitySnapshot(): RelayOperationActivitySnapshot {\n return relayOperationActivitySnapshot;\n}\n\n/**\n * Subscribe to Relay HTTP operation activity changes.\n */\nexport function subscribeRelayOperationActivity(\n listener: RelayOperationActivityListener,\n): () => void {\n relayOperationActivityListeners.add(listener);\n return () => {\n relayOperationActivityListeners.delete(listener);\n };\n}\n\n/**\n * Force the websocket transport to reconnect and notify mounted subscriptions.\n */\nexport function reconnectRelayWebSocket(\n options: RelayReconnectOptions = {},\n): void {\n disposeWsClient();\n emitRelayTransportChange(\n 'reconnecting',\n options.reason ?? 'manual_reconnect',\n );\n}\n\n/**\n * Refresh the active websocket authentication without recreating subscriptions.\n */\nexport async function refreshRelayWebSocketAuthentication(\n options: RelayAuthRefreshOptions = {},\n): Promise<void> {\n const client = wsClient;\n if (!client?.hasActiveConnection()) {\n return;\n }\n\n const refreshed = await client.refreshAuthentication();\n if (!refreshed) {\n reconnectRelayWebSocket({\n reason: options.reason ?? 'auth_refresh_fallback',\n });\n }\n}\n\nexport interface RelayEnvironmentConfiguration {\n httpUrl?: string;\n wsUrl?: string;\n logEvents?: boolean;\n getDataId?: (fieldValue: unknown, typeName: string) => string | null;\n getAuthHeaders?: () =>\n Record<string, string> | Promise<Record<string, string>>;\n}\n\n/**\n * Configure the endpoints and logging behavior used by the shared Relay environment.\n */\nexport function configureRelayEnvironment(\n options: RelayEnvironmentConfiguration = {},\n): void {\n const {\n httpUrl,\n wsUrl,\n logEvents,\n getDataId: nextGetDataId,\n getAuthHeaders: nextGetAuthHeaders,\n } = options;\n\n if (typeof httpUrl === 'string' && httpUrl !== '') {\n graphqlHttpEndpoint = httpUrl;\n }\n\n if (typeof wsUrl === 'string' && wsUrl !== '') {\n graphqlWsEndpoint = wsUrl;\n }\n\n if (logEvents != null) {\n if (logEvents) {\n relayFieldLogger = (event) => {\n // eslint-disable-next-line no-console\n console.log('relayFieldLogger: ', event);\n };\n } else {\n relayFieldLogger = noopRelayFieldLogger;\n }\n }\n\n customDataIdResolver = nextGetDataId;\n getAuthHeaders = nextGetAuthHeaders;\n\n disposeWsClient();\n environment = undefined;\n resetRelayOperationActivity();\n emitRelayTransportChange('configured', 'configure');\n}\n\n/**\n * Resolve the websocket endpoint into an absolute URL suitable for graphql-ws.\n */\nfunction resolveWebsocketUrl(): string {\n const endpoint = graphqlWsEndpoint;\n\n if (endpoint.startsWith('ws')) {\n return endpoint;\n }\n\n if (endpoint.startsWith('http')) {\n return endpoint.replace(/^http/, 'ws');\n }\n\n if (endpoint.startsWith('/')) {\n if (typeof window === 'undefined') {\n return endpoint;\n }\n let protocol = 'ws:';\n if (window.location.protocol === 'https:') {\n protocol = 'wss:';\n }\n return `${protocol}//${window.location.host}${endpoint}`;\n }\n\n return endpoint;\n}\n\n/** Debug log helper (no-op en production) */\nfunction debugLog(...args: unknown[]): void {\n if (isDevEnv()) {\n // eslint-disable-next-line no-console\n console.log('[RelayNetwork]', ...args);\n }\n}\n\n/** Resolve authentication headers for Relay HTTP and websocket transports. */\nasync function resolveAuthHeaders(): Promise<Record<string, string>> {\n if (getAuthHeaders == null) {\n return {};\n }\n return getAuthHeaders();\n}\n\nclass RelayWebSocketClient {\n private socket: WebSocket | null = null;\n\n private connectPromise: Promise<void> | null = null;\n\n private disposed = false;\n\n private acknowledged = false;\n\n private retryCount = 0;\n\n private nextId = 1;\n\n private keepAliveTimer: ReturnType<typeof setInterval> | null = null;\n\n private readonly subscriptions = new Map<string, ActiveSubscription>();\n\n private pendingAuthUpdate: {\n resolve: (value: boolean) => void;\n timeout: ReturnType<typeof setTimeout>;\n } | null = null;\n\n public constructor(private readonly url: string) {}\n\n public hasActiveConnection(): boolean {\n return this.socket?.readyState === WebSocket.OPEN && this.acknowledged;\n }\n\n public dispose(): void {\n this.disposed = true;\n this.resolvePendingAuthUpdate(false);\n this.stopKeepAlive();\n const { socket } = this;\n this.socket = null;\n this.connectPromise = null;\n this.acknowledged = false;\n if (socket != null && socket.readyState !== WebSocket.CLOSED) {\n socket.close(1000, 'Normal Closure');\n }\n }\n\n public subscribe(\n payload: ActiveSubscription['payload'],\n sink: SubscriptionSink,\n ): () => void {\n const id = String(this.nextId);\n this.nextId += 1;\n this.subscriptions.set(id, { payload, sink });\n this.connectAndSubscribe(id).catch((error: unknown) => {\n debugLog('subscription websocket connect failed', error);\n this.scheduleReconnect();\n });\n\n return () => {\n this.unsubscribe(id);\n };\n }\n\n public async refreshAuthentication(): Promise<boolean> {\n if (!this.hasActiveConnection()) {\n return true;\n }\n const { socket } = this;\n if (socket == null) {\n return true;\n }\n if (this.pendingAuthUpdate != null) {\n return false;\n }\n\n let payload: Record<string, string>;\n try {\n payload = await resolveAuthHeaders();\n } catch {\n return false;\n }\n if (Object.keys(payload).length === 0) {\n return false;\n }\n\n return new Promise<boolean>((resolve) => {\n const timeout = setTimeout(() => {\n this.pendingAuthUpdate = null;\n resolve(false);\n }, AUTH_UPDATE_TIMEOUT_MS);\n this.pendingAuthUpdate = { resolve, timeout };\n this.send({\n type: 'connection_auth_update',\n payload,\n });\n });\n }\n\n private async connectAndSubscribe(id: string): Promise<void> {\n const wasConnected = this.hasActiveConnection();\n await this.ensureConnected();\n if (!wasConnected) {\n return;\n }\n const subscription = this.subscriptions.get(id);\n if (subscription == null) {\n return;\n }\n this.send({\n id,\n type: 'subscribe',\n payload: subscription.payload,\n });\n }\n\n private async ensureConnected(): Promise<void> {\n if (this.hasActiveConnection()) {\n return;\n }\n if (this.connectPromise != null) {\n await this.connectPromise;\n return;\n }\n\n this.connectPromise = this.openSocket();\n try {\n await this.connectPromise;\n } finally {\n this.connectPromise = null;\n }\n }\n\n private async openSocket(): Promise<void> {\n if (this.disposed) {\n throw new Error('WebSocket client is disposed');\n }\n\n await new Promise<void>((resolve, reject) => {\n let settled = false;\n const resolveConnected = (): void => {\n if (settled) {\n return;\n }\n settled = true;\n resolve();\n };\n const rejectConnection = (error: unknown): void => {\n if (settled) {\n return;\n }\n settled = true;\n reject(error);\n };\n const socket = new WebSocket(this.url, GRAPHQL_TRANSPORT_WS_PROTOCOL);\n this.socket = socket;\n this.acknowledged = false;\n\n socket.onopen = () => {\n resolveAuthHeaders()\n .catch(() => {\n return {};\n })\n .then((payload) => {\n if (socket.readyState !== WebSocket.OPEN) {\n return;\n }\n if (Object.keys(payload).length > 0) {\n this.send({ type: 'connection_init', payload });\n return;\n }\n this.send({ type: 'connection_init' });\n })\n .catch((error: unknown) => {\n rejectConnection(error);\n });\n };\n\n socket.onerror = () => {\n rejectConnection(new Error('GraphQL subscription websocket error'));\n };\n\n socket.onmessage = (event) => {\n this.handleMessage(event.data, resolveConnected, rejectConnection);\n };\n\n socket.onclose = () => {\n rejectConnection(new Error('GraphQL subscription websocket closed'));\n this.handleClose();\n };\n });\n }\n\n private handleMessage(\n data: unknown,\n connected: () => void,\n rejected: (error: unknown) => void,\n ): void {\n let message: GraphqlWsMessage;\n try {\n message = JSON.parse(String(data)) as GraphqlWsMessage;\n } catch {\n rejected(new Error('Invalid GraphQL websocket message'));\n this.socket?.close(4400, 'Invalid websocket message');\n return;\n }\n\n switch (message.type) {\n case 'connection_ack':\n this.acknowledged = true;\n this.retryCount = 0;\n this.startKeepAlive();\n connected();\n this.resubscribeActiveOperations();\n return;\n case 'connection_auth_update_ack':\n this.resolvePendingAuthUpdate(true);\n return;\n case 'next':\n this.subscriptions.get(message.id)?.sink.next(message.payload);\n return;\n case 'error':\n if (message.id != null) {\n this.failSubscription(message.id, message.payload);\n } else {\n this.resolvePendingAuthUpdate(false);\n }\n return;\n case 'complete':\n this.completeSubscription(message.id);\n return;\n case 'ping':\n this.send({ type: 'pong', payload: message.payload });\n return;\n case 'pong':\n return;\n default:\n rejected(new Error('Unsupported GraphQL websocket message'));\n this.socket?.close(4400, 'Unsupported websocket message');\n }\n }\n\n private handleClose(): void {\n this.acknowledged = false;\n this.socket = null;\n this.connectPromise = null;\n this.stopKeepAlive();\n this.resolvePendingAuthUpdate(false);\n if (!this.disposed && this.subscriptions.size > 0) {\n this.scheduleReconnect();\n }\n }\n\n private scheduleReconnect(): void {\n if (this.retryCount >= WS_MAX_RETRY_ATTEMPTS) {\n const error = new Error('GraphQL subscription websocket retry exhausted');\n for (const id of [...this.subscriptions.keys()]) {\n this.failSubscription(id, error);\n }\n return;\n }\n const delay =\n Math.min(8_000, WS_BASE_RETRY_DELAY_MS * 2 ** this.retryCount) +\n Math.random() * 200;\n this.retryCount += 1;\n setTimeout(() => {\n if (this.disposed || this.subscriptions.size === 0) {\n return;\n }\n this.ensureConnected().catch((error: unknown) => {\n this.scheduleReconnect();\n debugLog('subscription websocket reconnect failed', error);\n });\n }, delay);\n }\n\n private resubscribeActiveOperations(): void {\n for (const [id, subscription] of this.subscriptions) {\n this.send({\n id,\n type: 'subscribe',\n payload: subscription.payload,\n });\n }\n }\n\n private unsubscribe(id: string): void {\n const hadSubscription = this.subscriptions.delete(id);\n if (hadSubscription && this.socket?.readyState === WebSocket.OPEN) {\n this.send({ id, type: 'complete' });\n }\n }\n\n private failSubscription(id: string, error: unknown): void {\n const subscription = this.subscriptions.get(id);\n if (subscription == null) {\n return;\n }\n this.subscriptions.delete(id);\n subscription.sink.error(error);\n }\n\n private completeSubscription(id: string): void {\n const subscription = this.subscriptions.get(id);\n if (subscription == null) {\n return;\n }\n this.subscriptions.delete(id);\n subscription.sink.complete();\n }\n\n private startKeepAlive(): void {\n this.stopKeepAlive();\n this.keepAliveTimer = setInterval(() => {\n if (this.socket?.readyState === WebSocket.OPEN) {\n this.send({ type: 'ping' });\n }\n }, WS_KEEP_ALIVE_MS);\n }\n\n private stopKeepAlive(): void {\n if (this.keepAliveTimer != null) {\n clearInterval(this.keepAliveTimer);\n this.keepAliveTimer = null;\n }\n }\n\n private resolvePendingAuthUpdate(value: boolean): void {\n const pending = this.pendingAuthUpdate;\n if (pending == null) {\n return;\n }\n clearTimeout(pending.timeout);\n this.pendingAuthUpdate = null;\n pending.resolve(value);\n }\n\n private send(message: GraphqlWsPayload): void {\n const { socket } = this;\n if (socket?.readyState !== WebSocket.OPEN) {\n return;\n }\n socket.send(JSON.stringify(message));\n }\n}\n\n/**\n * Build or get a singleton graphql-transport-ws client with auth refresh support.\n */\nfunction getWsClient(): RelayWebSocketClient {\n if (wsClient != null) {\n return wsClient;\n }\n if (typeof window === 'undefined') {\n throw new Error(\n 'GraphQL subscriptions unavailable in non-browser environment',\n );\n }\n\n const url = resolveWebsocketUrl();\n wsClient = new RelayWebSocketClient(url);\n\n return wsClient;\n}\n\n/**\n * Get organization slug from current window location\n */\n// export function getOrganizationSlug(): string | null {\n// const { pathname } = window.location;\n// const slug = pathname.split('/')[1];\n// if (slug == null) {\n// return null;\n// }\n\n// return slug;\n// }\n\n// -------------------------\n// Upload detection helpers\n// -------------------------\n/** Test if a value is an uploadable (File/Blob) */\nfunction isUploadable(value: unknown): value is File | Blob {\n return (\n (typeof File !== 'undefined' && value instanceof File) ||\n (typeof Blob !== 'undefined' && value instanceof Blob)\n );\n}\n\ninterface FileRef {\n path: string; // JSON pointer-like (e.g. variables.input.file)\n file: File | Blob;\n}\n\n/** Recursively clone a structure replacing files with null and collecting them */\nfunction collectFiles(value: unknown, path: string, acc: FileRef[]): unknown {\n if (isUploadable(value)) {\n acc.push({ path, file: value });\n return null; // placeholder per multipart spec\n }\n if (Array.isArray(value)) {\n return value.map((v, i) => {\n return collectFiles(v, `${path}.${i}`, acc);\n });\n }\n if (value != null && typeof value === 'object') {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n out[k] = collectFiles(v, `${path}.${k}`, acc);\n }\n return out;\n }\n return value; // primitives unchanged\n}\n\n/** Build GraphQL multipart form-data payload */\nfunction buildFormData(\n query: string,\n variables: Record<string, unknown>,\n): FormData {\n const files: FileRef[] = [];\n // Deep clone variables while replacing files with null\n const clonedVars = collectFiles(variables, 'variables', files) as Record<\n string,\n unknown\n >;\n\n const form = new FormData();\n const operations = { query, variables: clonedVars };\n form.append('operations', JSON.stringify(operations));\n\n const map: Record<string, string[]> = {};\n files.forEach((ref, idx) => {\n map[idx] = [ref.path];\n });\n form.append('map', JSON.stringify(map));\n files.forEach((ref, idx) => {\n form.append(String(idx), ref.file);\n });\n return form;\n}\n\n// -------------------------\n// Retry / timeout helpers\n// -------------------------\ninterface RetryOptions {\n maxAttempts: number; // total attempts including initial\n baseDelayMs: number;\n maxDelayMs: number;\n fetchTimeoutMs: number;\n}\n\nconst RETRY_OPTIONS: RetryOptions = {\n maxAttempts: 3,\n baseDelayMs: 300,\n maxDelayMs: 4000,\n fetchTimeoutMs: 600_000, // 10 minutes (parité avec ancien middleware)\n};\n\n/** Sleep helper with Promise */\nasync function sleep(ms: number): Promise<void> {\n await new Promise<void>((res) => {\n setTimeout(() => {\n res();\n }, ms);\n });\n}\n\n/** Exponential backoff with jitter */\nfunction calcBackoff(attempt: number, opts: RetryOptions): number {\n const exp = Math.min(opts.maxDelayMs, opts.baseDelayMs * 2 ** attempt);\n return exp + Math.random() * 0.2 * exp; // jitter 0-20%\n}\n\n/** Determine if an HTTP status is retryable */\nfunction isRetryableStatus(status: number): boolean {\n return status === 408 || status === 429 || (status >= 500 && status < 600);\n}\n\n/** Determine if an error should be treated as transient network issue */\nfunction isNetworkError(err: unknown): boolean {\n return (\n err instanceof TypeError ||\n (err instanceof Error && err.name === 'AbortError')\n );\n}\n\n// -------------------------\n// fetchFn implementation\n// -------------------------\ninterface GraphQLResponseErrorItem {\n [key: string]: unknown;\n message: string;\n path?: readonly unknown[];\n}\n\ninterface GraphQLResponseShape<T = unknown> {\n data?: T;\n errors?: GraphQLResponseErrorItem[];\n extensions?: Record<string, unknown>;\n}\n\nexport class GraphQLResponseError extends Error {\n public constructor(\n public readonly errors: readonly GraphQLResponseErrorItem[],\n public readonly response: GraphQLResponseShape,\n ) {\n super(errors[0]?.message ?? 'GraphQL response error');\n this.name = 'GraphQLResponseError';\n }\n}\n\n/** Determine whether a GraphQL payload includes non-null top-level data. */\nfunction hasUsableTopLevelData(data: unknown): boolean {\n if (data == null) {\n return false;\n }\n if (typeof data !== 'object') {\n return true;\n }\n return Object.values(data as Record<string, unknown>).some((value) => {\n return value != null;\n });\n}\n\n/** Determine whether all errors are child-field errors under top-level null data. */\nfunction hasOnlyErrorsBelowNullTopLevelFields(\n response: GraphQLResponseShape,\n): boolean {\n const { data, errors } = response;\n if (\n data == null ||\n typeof data !== 'object' ||\n !Array.isArray(errors) ||\n errors.length === 0\n ) {\n return false;\n }\n\n const dataRecord = data as Record<string, unknown>;\n return errors.every((error) => {\n const [topLevelField, ...childPath] = error.path ?? [];\n if (typeof topLevelField !== 'string' || childPath.length === 0) {\n return false;\n }\n return (\n Object.prototype.hasOwnProperty.call(dataRecord, topLevelField) &&\n dataRecord[topLevelField] == null\n );\n });\n}\n\n/** Determine whether GraphQL errors should be surfaced as a thrown response error. */\nfunction shouldThrowGraphQLResponseError(\n response: GraphQLResponseShape,\n): boolean {\n return (\n Array.isArray(response.errors) &&\n response.errors.length > 0 &&\n !hasUsableTopLevelData(response.data) &&\n !hasOnlyErrorsBelowNullTopLevelFields(response)\n );\n}\n\n/** Fetch GraphQL (with retry + upload) */\nasync function fetchFn(\n request: {\n name?: string | null;\n operationKind?: unknown;\n text: string | null | undefined;\n },\n variables: Record<string, unknown>,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n _cacheConfig: unknown,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n _uploadables: unknown,\n): Promise<GraphQLResponseShape> {\n const queryText: string | null | undefined = request.text; // no persisted queries here (yet)\n if (queryText == null) {\n throw new Error('Missing GraphQL query text');\n }\n const safeQueryText: string = queryText; // narrowed non-null\n const stopRelayOperationActivity = startRelayOperationActivity(request);\n\n /** Execute one network attempt */\n async function execOnce(attempt: number): Promise<GraphQLResponseShape> {\n const controller = new AbortController();\n const timeout = setTimeout(() => {\n controller.abort();\n }, RETRY_OPTIONS.fetchTimeoutMs);\n try {\n let body: BodyInit;\n const headers: Record<string, string> = {};\n if (getAuthHeaders != null) {\n try {\n Object.assign(headers, await getAuthHeaders());\n } catch {\n // ignore auth header failures\n }\n }\n const hasFiles = ((): boolean => {\n try {\n const stack: unknown[] = [variables];\n while (stack.length > 0) {\n const v = stack.pop();\n if (isUploadable(v)) {\n return true;\n }\n if (Array.isArray(v)) {\n stack.push(...v);\n } else if (v != null && typeof v === 'object') {\n stack.push(...Object.values(v as Record<string, unknown>));\n }\n }\n } catch {\n // ignore traversal errors\n }\n return false;\n })();\n\n if (hasFiles) {\n body = buildFormData(safeQueryText, variables);\n headers[GRAPHQL_CSRF_PREFLIGHT_HEADER] =\n GRAPHQL_CSRF_PREFLIGHT_HEADER_VALUE;\n } else {\n headers['Content-Type'] = 'application/json';\n body = JSON.stringify({ query: safeQueryText, variables });\n }\n\n const response = await fetch(graphqlHttpEndpoint, {\n method: 'POST',\n body,\n headers,\n credentials: 'include',\n signal: controller.signal,\n });\n\n if (!response.ok) {\n if (\n attempt < RETRY_OPTIONS.maxAttempts - 1 &&\n isRetryableStatus(response.status)\n ) {\n throw new Error(`Retryable HTTP status ${response.status}`);\n }\n const text = await response.text().catch(() => {\n return '';\n });\n throw new Error(`GraphQL HTTP error ${response.status}: ${text}`);\n }\n\n const json: GraphQLResponseShape = await response.json();\n if (Array.isArray(json.errors) && json.errors.length > 0) {\n debugLog('GraphQL errors', json.errors);\n }\n if (shouldThrowGraphQLResponseError(json)) {\n throw new GraphQLResponseError(json.errors ?? [], json);\n }\n return json;\n } finally {\n clearTimeout(timeout);\n }\n }\n\n try {\n for (let attempt = 0; attempt < RETRY_OPTIONS.maxAttempts; attempt += 1) {\n try {\n return await execOnce(attempt);\n } catch (err) {\n const last = attempt === RETRY_OPTIONS.maxAttempts - 1;\n const retryable =\n isNetworkError(err) ||\n (err instanceof Error &&\n err.message.includes('Retryable HTTP status'));\n if (!retryable || last) {\n debugLog('GraphQL fetch error', err);\n throw err;\n }\n const delay = calcBackoff(attempt, RETRY_OPTIONS);\n await sleep(delay);\n }\n }\n throw new Error('Exhausted retries without returning a result');\n } finally {\n stopRelayOperationActivity();\n }\n}\n\n// -------------------------\n// subscribeFn implementation (wrap existing logic in Relay Observable)\n// -------------------------\n/** Subscription function bridging graphql-ws client to Relay */\nfunction subscribeFn(\n operation: { text: string | null | undefined; name: string },\n variables: Record<string, unknown>,\n): Observable<unknown> {\n debugLog('subscription:start', operation.name, variables);\n return Observable.create<unknown>((sink) => {\n const query = operation.text;\n if (query == null || query === '') {\n sink.error(new Error('Subscription operation text is empty'));\n return () => {\n // noop\n };\n }\n const client = getWsClient();\n const dispose = client.subscribe(\n { query, variables, operationName: operation.name },\n {\n next: (data) => {\n sink.next(data);\n },\n error: (err) => {\n let errorObj: Error;\n if (err instanceof Error) {\n errorObj = err;\n } else {\n errorObj = new Error('Subscription error');\n }\n sink.error(errorObj);\n },\n complete: () => {\n sink.complete();\n },\n },\n );\n return () => {\n dispose();\n };\n });\n}\n\n/**\n * Create native Relay network layer (fetch + subscribe)\n */\nexport function getNetwork(): ReturnType<typeof Network.create> {\n return Network.create(\n fetchFn as FetchFunction,\n subscribeFn as SubscribeFunction,\n );\n}\n\n/**\n * Get anonymous relay environment\n */\nexport function getEnvironment(): Environment {\n environment ??= new Environment({\n getDataID: getDataId,\n relayFieldLogger,\n network: getNetwork(),\n store: new Store(new RecordSource()),\n });\n\n return environment;\n}\n\n/**\n * Reset the Relay store by creating a new empty source\n */\nexport function resetRelayStore(): void {\n const environment = getEnvironment();\n\n // Create a new store with an empty source\n const source = new RecordSource();\n\n // Replace the data in the store with our empty source\n environment.getStore().publish(source);\n\n // Notify subscribers that the data has changed\n environment.getStore().notify();\n}\n\nexport const __test = {\n RelayWebSocketClient,\n buildFormData,\n calcBackoff,\n collectFiles,\n fetchFn,\n getDataId,\n hasUsableTopLevelData,\n isNetworkError,\n isRetryableStatus,\n isUploadable,\n shouldThrowGraphQLResponseError,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"environment.js","names":[],"sources":["../../src/environment.ts"],"sourcesContent":["import {\n Environment,\n Network,\n Observable,\n RecordSource,\n Store,\n type FetchFunction,\n type SubscribeFunction,\n type RelayFieldLogger,\n} from 'relay-runtime';\nimport { isDevEnv } from './envHelpers.js';\n\nlet graphqlHttpEndpoint = '/api/graphql';\nlet graphqlWsEndpoint = '/api/ws';\n\n// (anonymousEnvironment reserved if needed later for public access / logout scenarios)\n// Removed until actually required to avoid lint errors.\n// let anonymousEnvironment: Environment | undefined;\nlet environment: Environment | undefined;\nlet wsClient: RelayWebSocketClient | undefined;\nlet environmentGeneration = 0;\nconst activeHttpControllers = new Set<AbortController>();\nlet getAuthHeaders:\n (() => Record<string, string> | Promise<Record<string, string>>) | undefined;\nconst defaultGraphqlFetch: typeof fetch = async (input, init) => {\n return globalThis.fetch(input, init);\n};\nlet graphqlFetch: typeof fetch = defaultGraphqlFetch;\nlet customDataIdResolver:\n ((fieldValue: unknown, typeName: string) => string | null) | undefined;\n\nconst GRAPHQL_CSRF_PREFLIGHT_HEADER = 'require-preflight';\nconst GRAPHQL_CSRF_PREFLIGHT_HEADER_VALUE = 'true';\n\nexport type RelayTransportStatus = 'idle' | 'configured' | 'reconnecting';\n\nexport interface RelayTransportSnapshot {\n generation: number;\n reason: string | null;\n status: RelayTransportStatus;\n}\n\nexport type RelayOperationKind = 'mutation' | 'query' | 'unknown';\n\nexport type RelayPendingOperation = {\n id: number;\n kind: RelayOperationKind;\n name: string;\n startedAt: number;\n};\n\nexport interface RelayOperationActivitySnapshot {\n generation: number;\n pendingCount: number;\n pendingOperations: readonly RelayPendingOperation[];\n}\n\nexport interface RelayReconnectOptions {\n reason?: string;\n}\n\nexport interface RelayAuthRefreshOptions {\n reason?: string;\n}\n\ntype RelayTransportListener = () => void;\ntype RelayOperationActivityListener = () => void;\n\ntype GraphqlWsPayload = Record<string, unknown>;\n\ntype GraphqlWsMessage =\n | { type: 'connection_ack'; payload?: unknown }\n | { type: 'connection_auth_update_ack'; payload?: unknown }\n | { id: string; type: 'next'; payload: unknown }\n | { id?: string; type: 'error'; payload: unknown }\n | { id: string; type: 'complete' }\n | { type: 'ping'; payload?: unknown }\n | { type: 'pong'; payload?: unknown };\n\ntype GraphqlWsMessageDecodeResult =\n | { readonly ok: true; readonly value: GraphqlWsMessage }\n | {\n readonly ok: false;\n readonly reason: 'invalid' | 'unsupported';\n };\n\nconst isUnknownRecord = (\n value: unknown,\n): value is Readonly<Record<string, unknown>> => {\n return value != null && typeof value === 'object' && !Array.isArray(value);\n};\n\nconst decodeGraphqlWsMessage = (\n value: unknown,\n): GraphqlWsMessageDecodeResult => {\n if (!isUnknownRecord(value) || typeof value.type !== 'string') {\n return { ok: false, reason: 'invalid' };\n }\n const { payload } = value;\n switch (value.type) {\n case 'connection_ack':\n case 'connection_auth_update_ack':\n case 'ping':\n case 'pong':\n return { ok: true, value: { type: value.type, payload } };\n case 'next':\n if (typeof value.id !== 'string') {\n return { ok: false, reason: 'invalid' };\n }\n return { ok: true, value: { id: value.id, type: value.type, payload } };\n case 'error':\n if (value.id != null && typeof value.id !== 'string') {\n return { ok: false, reason: 'invalid' };\n }\n if (value.id == null) {\n return { ok: true, value: { type: value.type, payload } };\n }\n return {\n ok: true,\n value: { id: value.id, type: value.type, payload },\n };\n case 'complete':\n if (typeof value.id !== 'string') {\n return { ok: false, reason: 'invalid' };\n }\n return { ok: true, value: { id: value.id, type: value.type } };\n default:\n return { ok: false, reason: 'unsupported' };\n }\n};\n\ntype SubscriptionSink = {\n next: (value: unknown) => void;\n error: (error: unknown) => void;\n complete: () => void;\n};\n\ntype ActiveSubscription = {\n payload: {\n query: string;\n variables: Record<string, unknown>;\n operationName: string;\n };\n sink: SubscriptionSink;\n};\n\nconst GRAPHQL_TRANSPORT_WS_PROTOCOL = 'graphql-transport-ws';\nconst AUTH_UPDATE_TIMEOUT_MS = 5_000;\nconst WS_KEEP_ALIVE_MS = 10_000;\nconst WS_MAX_RETRY_ATTEMPTS = 10;\nconst WS_BASE_RETRY_DELAY_MS = 500;\nconst ENVIRONMENT_ROTATED_ERROR =\n 'Relay operation cancelled after environment rotation';\n\nlet relayTransportSnapshot: RelayTransportSnapshot = {\n generation: 0,\n reason: null,\n status: 'idle',\n};\nconst relayTransportListeners = new Set<RelayTransportListener>();\n\nlet relayOperationActivitySnapshot: RelayOperationActivitySnapshot = {\n generation: 0,\n pendingCount: 0,\n pendingOperations: [],\n};\nlet relayOperationActivityId = 0;\nconst relayPendingOperations = new Map<number, RelayPendingOperation>();\nconst relayOperationActivityListeners =\n new Set<RelayOperationActivityListener>();\nlet relayOperationActivityNotifyScheduled = false;\n\n/**\n * No-op Relay field logger used to satisfy @required(action: LOG).\n */\nfunction noopRelayFieldLogger(): void {}\n\nlet relayFieldLogger: RelayFieldLogger = noopRelayFieldLogger;\n\nif (isDevEnv()) {\n relayFieldLogger = (event) => {\n // eslint-disable-next-line no-console\n console.log('relayFieldLogger: ', event);\n };\n}\n\nconst defaultGetDataId: (\n fieldValue: unknown,\n typeName: string,\n) => string | null = (fieldValue) => {\n if (fieldValue == null || typeof fieldValue !== 'object') {\n return null;\n }\n const { id } = fieldValue as { id?: unknown };\n if (typeof id !== 'string' || id.trim() === '') {\n return null;\n }\n return id;\n};\n\nconst getDataId = (fieldValue: unknown, typeName: string): string | null => {\n const customId = customDataIdResolver?.(fieldValue, typeName);\n if (customId != null) {\n return customId;\n }\n return defaultGetDataId(fieldValue, typeName);\n};\n\n/**\n * Notify subscribers that the Relay transport state changed.\n */\nfunction emitRelayTransportChange(\n status: RelayTransportStatus,\n reason: string | null,\n): void {\n relayTransportSnapshot = {\n generation: relayTransportSnapshot.generation + 1,\n reason,\n status,\n };\n for (const listener of relayTransportListeners) {\n listener();\n }\n}\n\n/**\n * Publish the latest Relay HTTP operation activity snapshot.\n */\nfunction emitRelayOperationActivityChange(): void {\n relayOperationActivitySnapshot = {\n generation: relayOperationActivitySnapshot.generation + 1,\n pendingCount: relayPendingOperations.size,\n pendingOperations: [...relayPendingOperations.values()],\n };\n if (relayOperationActivityNotifyScheduled) {\n return;\n }\n relayOperationActivityNotifyScheduled = true;\n queueMicrotask(() => {\n relayOperationActivityNotifyScheduled = false;\n for (const listener of relayOperationActivityListeners) {\n listener();\n }\n });\n}\n\n/**\n * Clear pending Relay HTTP operations after environment reconfiguration.\n */\nfunction resetRelayOperationActivity(): void {\n if (relayPendingOperations.size === 0) {\n return;\n }\n relayPendingOperations.clear();\n emitRelayOperationActivityChange();\n}\n\n/**\n * Normalize Relay operation kind values from runtime requests.\n */\nfunction resolveRelayOperationKind(operationKind: unknown): RelayOperationKind {\n if (operationKind === 'query' || operationKind === 'mutation') {\n return operationKind;\n }\n return 'unknown';\n}\n\n/**\n * Track a Relay HTTP operation until its returned disposer is called.\n */\nfunction startRelayOperationActivity(request: {\n name?: string | null;\n operationKind?: unknown;\n}): () => void {\n const id = relayOperationActivityId;\n relayOperationActivityId += 1;\n relayPendingOperations.set(id, {\n id,\n kind: resolveRelayOperationKind(request.operationKind),\n name: request.name ?? 'AnonymousGraphQLOperation',\n startedAt: Date.now(),\n });\n emitRelayOperationActivityChange();\n\n let isSettled = false;\n return () => {\n if (isSettled) {\n return;\n }\n isSettled = true;\n if (!relayPendingOperations.delete(id)) {\n return;\n }\n emitRelayOperationActivityChange();\n };\n}\n\n/**\n * Dispose the active websocket client and ignore shutdown failures.\n */\nfunction disposeWsClient(): void {\n if (wsClient == null) {\n return;\n }\n try {\n wsClient.dispose();\n } catch {\n // ignore errors during dispose\n }\n wsClient = undefined;\n}\n\n/** End every subscription owned by an environment that is being replaced. */\nfunction terminateWsClient(): void {\n if (wsClient == null) {\n return;\n }\n try {\n wsClient.terminate();\n } catch {\n // ignore errors during termination\n }\n wsClient = undefined;\n}\n\n/** Abort every HTTP operation owned by the current Relay environment. */\nfunction abortActiveHttpOperations(): void {\n for (const controller of activeHttpControllers) {\n controller.abort();\n }\n activeHttpControllers.clear();\n}\n\n/** Remove all records from an environment while notifying its active selectors. */\nfunction clearEnvironmentStore(currentEnvironment: Environment): void {\n const store = currentEnvironment.getStore();\n const source = store.getSource();\n const deletedRecords = new RecordSource();\n\n for (const dataId of source.getRecordIDs()) {\n deletedRecords.delete(dataId);\n }\n try {\n if (deletedRecords.size() > 0) {\n store.publish(deletedRecords);\n store.notify();\n }\n } finally {\n if (source instanceof RecordSource) {\n source.clear();\n }\n }\n}\n\n/** Replace the shared environment and terminate work owned by its generation. */\nfunction rotateRelayEnvironment(\n status: RelayTransportStatus,\n reason: string,\n): void {\n const previousEnvironment = environment;\n environment = undefined;\n environmentGeneration += 1;\n abortActiveHttpOperations();\n terminateWsClient();\n try {\n if (previousEnvironment != null) {\n clearEnvironmentStore(previousEnvironment);\n }\n } finally {\n resetRelayOperationActivity();\n emitRelayTransportChange(status, reason);\n }\n}\n\n/**\n * Return the current Relay transport state used by subscription hooks.\n */\nexport function getRelayTransportSnapshot(): RelayTransportSnapshot {\n return relayTransportSnapshot;\n}\n\n/**\n * Subscribe to Relay transport changes.\n */\nexport function subscribeRelayTransport(\n listener: RelayTransportListener,\n): () => void {\n relayTransportListeners.add(listener);\n return () => {\n relayTransportListeners.delete(listener);\n };\n}\n\n/**\n * Return the current Relay HTTP operation activity snapshot.\n */\nexport function getRelayOperationActivitySnapshot(): RelayOperationActivitySnapshot {\n return relayOperationActivitySnapshot;\n}\n\n/**\n * Subscribe to Relay HTTP operation activity changes.\n */\nexport function subscribeRelayOperationActivity(\n listener: RelayOperationActivityListener,\n): () => void {\n relayOperationActivityListeners.add(listener);\n return () => {\n relayOperationActivityListeners.delete(listener);\n };\n}\n\n/**\n * Force the websocket transport to reconnect and notify mounted subscriptions.\n */\nexport function reconnectRelayWebSocket(\n options: RelayReconnectOptions = {},\n): void {\n disposeWsClient();\n emitRelayTransportChange(\n 'reconnecting',\n options.reason ?? 'manual_reconnect',\n );\n}\n\n/**\n * Refresh the active websocket authentication without recreating subscriptions.\n */\nexport async function refreshRelayWebSocketAuthentication(\n options: RelayAuthRefreshOptions = {},\n): Promise<void> {\n const client = wsClient;\n if (!client?.hasActiveConnection()) {\n return;\n }\n\n const refreshed = await client.refreshAuthentication();\n if (!refreshed) {\n reconnectRelayWebSocket({\n reason: options.reason ?? 'auth_refresh_fallback',\n });\n }\n}\n\nexport interface RelayEnvironmentConfiguration {\n httpUrl?: string;\n wsUrl?: string;\n logEvents?: boolean;\n getDataId?: (fieldValue: unknown, typeName: string) => string | null;\n getAuthHeaders?: () =>\n Record<string, string> | Promise<Record<string, string>>;\n fetchImpl?: typeof fetch;\n}\n\n/**\n * Configure the endpoints and logging behavior used by the shared Relay environment.\n */\nexport function configureRelayEnvironment(\n options: RelayEnvironmentConfiguration = {},\n): void {\n const {\n httpUrl,\n wsUrl,\n logEvents,\n getDataId: nextGetDataId,\n getAuthHeaders: nextGetAuthHeaders,\n fetchImpl: nextFetchImpl,\n } = options;\n\n if (typeof httpUrl === 'string' && httpUrl !== '') {\n graphqlHttpEndpoint = httpUrl;\n }\n\n if (typeof wsUrl === 'string' && wsUrl !== '') {\n graphqlWsEndpoint = wsUrl;\n }\n\n if (logEvents != null) {\n if (logEvents) {\n relayFieldLogger = (event) => {\n // eslint-disable-next-line no-console\n console.log('relayFieldLogger: ', event);\n };\n } else {\n relayFieldLogger = noopRelayFieldLogger;\n }\n }\n\n customDataIdResolver = nextGetDataId;\n getAuthHeaders = nextGetAuthHeaders;\n graphqlFetch = nextFetchImpl ?? defaultGraphqlFetch;\n\n rotateRelayEnvironment('configured', 'configure');\n}\n\n/**\n * Resolve the websocket endpoint into an absolute GraphQL WebSocket URL.\n */\nfunction resolveWebsocketUrl(): string {\n const endpoint = graphqlWsEndpoint;\n\n if (endpoint.startsWith('ws')) {\n return endpoint;\n }\n\n if (endpoint.startsWith('http')) {\n return endpoint.replace(/^http/, 'ws');\n }\n\n if (endpoint.startsWith('/')) {\n if (typeof window === 'undefined') {\n return endpoint;\n }\n let protocol = 'ws:';\n if (window.location.protocol === 'https:') {\n protocol = 'wss:';\n }\n return `${protocol}//${window.location.host}${endpoint}`;\n }\n\n return endpoint;\n}\n\n/** Debug log helper (no-op en production) */\nfunction debugLog(...args: unknown[]): void {\n if (isDevEnv()) {\n // eslint-disable-next-line no-console\n console.log('[RelayNetwork]', ...args);\n }\n}\n\n/** Resolve authentication headers for Relay HTTP and websocket transports. */\nasync function resolveAuthHeaders(): Promise<Record<string, string>> {\n if (getAuthHeaders == null) {\n return {};\n }\n return getAuthHeaders();\n}\n\nclass RelayWebSocketClient {\n private socket: WebSocket | null = null;\n\n private connectPromise: Promise<void> | null = null;\n\n private disposed = false;\n\n private acknowledged = false;\n\n private retryCount = 0;\n\n private nextId = 1;\n\n private keepAliveTimer: ReturnType<typeof setInterval> | null = null;\n\n private readonly subscriptions = new Map<string, ActiveSubscription>();\n\n private pendingAuthUpdate: {\n resolve: (value: boolean) => void;\n timeout: ReturnType<typeof setTimeout>;\n } | null = null;\n\n public constructor(private readonly url: string) {}\n\n public hasActiveConnection(): boolean {\n return this.socket?.readyState === WebSocket.OPEN && this.acknowledged;\n }\n\n public dispose(): void {\n this.disposed = true;\n this.resolvePendingAuthUpdate(false);\n this.stopKeepAlive();\n const { socket } = this;\n this.socket = null;\n this.connectPromise = null;\n this.acknowledged = false;\n if (socket != null && socket.readyState !== WebSocket.CLOSED) {\n socket.close(1000, 'Normal Closure');\n }\n }\n\n public terminate(): void {\n for (const subscription of this.subscriptions.values()) {\n try {\n subscription.sink.complete();\n } catch {\n // ignore subscriber failures while ending an environment\n }\n }\n this.subscriptions.clear();\n this.dispose();\n }\n\n public subscribe(\n payload: ActiveSubscription['payload'],\n sink: SubscriptionSink,\n ): () => void {\n const id = String(this.nextId);\n this.nextId += 1;\n this.subscriptions.set(id, { payload, sink });\n this.connectAndSubscribe(id).catch((error: unknown) => {\n debugLog('subscription websocket connect failed', error);\n this.scheduleReconnect();\n });\n\n return () => {\n this.unsubscribe(id);\n };\n }\n\n public async refreshAuthentication(): Promise<boolean> {\n if (!this.hasActiveConnection()) {\n return true;\n }\n const { socket } = this;\n if (socket == null) {\n return true;\n }\n if (this.pendingAuthUpdate != null) {\n return false;\n }\n\n let payload: Record<string, string>;\n try {\n payload = await resolveAuthHeaders();\n } catch {\n return false;\n }\n if (Object.keys(payload).length === 0) {\n return false;\n }\n\n return new Promise<boolean>((resolve) => {\n const timeout = setTimeout(() => {\n this.pendingAuthUpdate = null;\n resolve(false);\n }, AUTH_UPDATE_TIMEOUT_MS);\n this.pendingAuthUpdate = { resolve, timeout };\n this.send({\n type: 'connection_auth_update',\n payload,\n });\n });\n }\n\n private async connectAndSubscribe(id: string): Promise<void> {\n const wasConnected = this.hasActiveConnection();\n await this.ensureConnected();\n if (!wasConnected) {\n return;\n }\n const subscription = this.subscriptions.get(id);\n if (subscription == null) {\n return;\n }\n this.send({\n id,\n type: 'subscribe',\n payload: subscription.payload,\n });\n }\n\n private async ensureConnected(): Promise<void> {\n if (this.hasActiveConnection()) {\n return;\n }\n if (this.connectPromise != null) {\n await this.connectPromise;\n return;\n }\n\n this.connectPromise = this.openSocket();\n try {\n await this.connectPromise;\n } finally {\n this.connectPromise = null;\n }\n }\n\n private async openSocket(): Promise<void> {\n if (this.disposed) {\n throw new Error('WebSocket client is disposed');\n }\n\n await new Promise<void>((resolve, reject) => {\n let settled = false;\n const resolveConnected = (): void => {\n if (settled) {\n return;\n }\n settled = true;\n resolve();\n };\n const rejectConnection = (error: unknown): void => {\n if (settled) {\n return;\n }\n settled = true;\n reject(error);\n };\n const socket = new WebSocket(this.url, GRAPHQL_TRANSPORT_WS_PROTOCOL);\n this.socket = socket;\n this.acknowledged = false;\n\n socket.onopen = () => {\n resolveAuthHeaders()\n .catch(() => {\n return {};\n })\n .then((payload) => {\n if (socket.readyState !== WebSocket.OPEN) {\n return;\n }\n if (Object.keys(payload).length > 0) {\n this.send({ type: 'connection_init', payload });\n return;\n }\n this.send({ type: 'connection_init' });\n })\n .catch((error: unknown) => {\n rejectConnection(error);\n });\n };\n\n socket.onerror = () => {\n rejectConnection(new Error('GraphQL subscription websocket error'));\n };\n\n socket.onmessage = (event) => {\n this.handleMessage(event.data, resolveConnected, rejectConnection);\n };\n\n socket.onclose = () => {\n rejectConnection(new Error('GraphQL subscription websocket closed'));\n this.handleClose();\n };\n });\n }\n\n private handleMessage(\n data: unknown,\n connected: () => void,\n rejected: (error: unknown) => void,\n ): void {\n let parsed: unknown;\n try {\n parsed = JSON.parse(String(data));\n } catch {\n rejected(new Error('Invalid GraphQL websocket message'));\n this.socket?.close(4400, 'Invalid websocket message');\n return;\n }\n\n const decoded = decodeGraphqlWsMessage(parsed);\n if (!decoded.ok) {\n const unsupported = decoded.reason === 'unsupported';\n let errorMessage = 'Invalid GraphQL websocket message';\n let closeMessage = 'Invalid websocket message';\n if (unsupported) {\n errorMessage = 'Unsupported GraphQL websocket message';\n closeMessage = 'Unsupported websocket message';\n }\n rejected(new Error(errorMessage));\n this.socket?.close(4400, closeMessage);\n return;\n }\n const message = decoded.value;\n\n switch (message.type) {\n case 'connection_ack':\n this.acknowledged = true;\n this.retryCount = 0;\n this.startKeepAlive();\n connected();\n this.resubscribeActiveOperations();\n return;\n case 'connection_auth_update_ack':\n this.resolvePendingAuthUpdate(true);\n return;\n case 'next':\n this.subscriptions.get(message.id)?.sink.next(message.payload);\n return;\n case 'error':\n if (message.id != null) {\n this.failSubscription(message.id, message.payload);\n } else {\n this.resolvePendingAuthUpdate(false);\n }\n return;\n case 'complete':\n this.completeSubscription(message.id);\n return;\n case 'ping':\n this.send({ type: 'pong', payload: message.payload });\n break;\n case 'pong':\n break;\n default:\n break;\n }\n }\n\n private handleClose(): void {\n this.acknowledged = false;\n this.socket = null;\n this.connectPromise = null;\n this.stopKeepAlive();\n this.resolvePendingAuthUpdate(false);\n if (!this.disposed && this.subscriptions.size > 0) {\n this.scheduleReconnect();\n }\n }\n\n private scheduleReconnect(): void {\n if (this.retryCount >= WS_MAX_RETRY_ATTEMPTS) {\n const error = new Error('GraphQL subscription websocket retry exhausted');\n for (const id of [...this.subscriptions.keys()]) {\n this.failSubscription(id, error);\n }\n return;\n }\n const delay =\n Math.min(8_000, WS_BASE_RETRY_DELAY_MS * 2 ** this.retryCount) +\n Math.random() * 200;\n this.retryCount += 1;\n setTimeout(() => {\n if (this.disposed || this.subscriptions.size === 0) {\n return;\n }\n this.ensureConnected().catch((error: unknown) => {\n this.scheduleReconnect();\n debugLog('subscription websocket reconnect failed', error);\n });\n }, delay);\n }\n\n private resubscribeActiveOperations(): void {\n for (const [id, subscription] of this.subscriptions) {\n this.send({\n id,\n type: 'subscribe',\n payload: subscription.payload,\n });\n }\n }\n\n private unsubscribe(id: string): void {\n const hadSubscription = this.subscriptions.delete(id);\n if (hadSubscription && this.socket?.readyState === WebSocket.OPEN) {\n this.send({ id, type: 'complete' });\n }\n }\n\n private failSubscription(id: string, error: unknown): void {\n const subscription = this.subscriptions.get(id);\n if (subscription == null) {\n return;\n }\n this.subscriptions.delete(id);\n subscription.sink.error(error);\n }\n\n private completeSubscription(id: string): void {\n const subscription = this.subscriptions.get(id);\n if (subscription == null) {\n return;\n }\n this.subscriptions.delete(id);\n subscription.sink.complete();\n }\n\n private startKeepAlive(): void {\n this.stopKeepAlive();\n this.keepAliveTimer = setInterval(() => {\n if (this.socket?.readyState === WebSocket.OPEN) {\n this.send({ type: 'ping' });\n }\n }, WS_KEEP_ALIVE_MS);\n }\n\n private stopKeepAlive(): void {\n if (this.keepAliveTimer != null) {\n clearInterval(this.keepAliveTimer);\n this.keepAliveTimer = null;\n }\n }\n\n private resolvePendingAuthUpdate(value: boolean): void {\n const pending = this.pendingAuthUpdate;\n if (pending == null) {\n return;\n }\n clearTimeout(pending.timeout);\n this.pendingAuthUpdate = null;\n pending.resolve(value);\n }\n\n private send(message: GraphqlWsPayload): void {\n const { socket } = this;\n if (socket?.readyState !== WebSocket.OPEN) {\n return;\n }\n socket.send(JSON.stringify(message));\n }\n}\n\n/**\n * Build or get a singleton graphql-transport-ws client with auth refresh support.\n */\nfunction getWsClient(): RelayWebSocketClient {\n if (wsClient != null) {\n return wsClient;\n }\n if (typeof window === 'undefined') {\n throw new Error(\n 'GraphQL subscriptions unavailable in non-browser environment',\n );\n }\n\n const url = resolveWebsocketUrl();\n wsClient = new RelayWebSocketClient(url);\n\n return wsClient;\n}\n\n/**\n * Get organization slug from current window location\n */\n// export function getOrganizationSlug(): string | null {\n// const { pathname } = window.location;\n// const slug = pathname.split('/')[1];\n// if (slug == null) {\n// return null;\n// }\n\n// return slug;\n// }\n\n// -------------------------\n// Upload detection helpers\n// -------------------------\n/** Test if a value is an uploadable (File/Blob) */\nfunction isUploadable(value: unknown): value is File | Blob {\n return (\n (typeof File !== 'undefined' && value instanceof File) ||\n (typeof Blob !== 'undefined' && value instanceof Blob)\n );\n}\n\ninterface FileRef {\n path: string; // JSON pointer-like (e.g. variables.input.file)\n file: File | Blob;\n}\n\n/** Recursively clone a structure replacing files with null and collecting them */\nfunction collectFiles(value: unknown, path: string, acc: FileRef[]): unknown {\n if (isUploadable(value)) {\n acc.push({ path, file: value });\n return null; // placeholder per multipart spec\n }\n if (Array.isArray(value)) {\n return value.map((v, i) => {\n return collectFiles(v, `${path}.${i}`, acc);\n });\n }\n if (value != null && typeof value === 'object') {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n out[k] = collectFiles(v, `${path}.${k}`, acc);\n }\n return out;\n }\n return value; // primitives unchanged\n}\n\n/** Build GraphQL multipart form-data payload */\nfunction buildFormData(\n query: string,\n variables: Record<string, unknown>,\n): FormData {\n const files: FileRef[] = [];\n // Deep clone variables while replacing files with null\n const clonedVars = collectFiles(variables, 'variables', files) as Record<\n string,\n unknown\n >;\n\n const form = new FormData();\n const operations = { query, variables: clonedVars };\n form.append('operations', JSON.stringify(operations));\n\n const map: Record<string, string[]> = {};\n files.forEach((ref, idx) => {\n map[idx] = [ref.path];\n });\n form.append('map', JSON.stringify(map));\n files.forEach((ref, idx) => {\n form.append(String(idx), ref.file);\n });\n return form;\n}\n\n// -------------------------\n// Retry / timeout helpers\n// -------------------------\ninterface RetryOptions {\n maxAttempts: number; // total attempts including initial\n baseDelayMs: number;\n maxDelayMs: number;\n fetchTimeoutMs: number;\n}\n\nconst RETRY_OPTIONS: RetryOptions = {\n maxAttempts: 3,\n baseDelayMs: 300,\n maxDelayMs: 4000,\n fetchTimeoutMs: 600_000, // 10 minutes (parité avec ancien middleware)\n};\n\n/** Sleep helper with Promise */\nasync function sleep(ms: number): Promise<void> {\n await new Promise<void>((res) => {\n setTimeout(() => {\n res();\n }, ms);\n });\n}\n\n/** Exponential backoff with jitter */\nfunction calcBackoff(attempt: number, opts: RetryOptions): number {\n const exp = Math.min(opts.maxDelayMs, opts.baseDelayMs * 2 ** attempt);\n return exp + Math.random() * 0.2 * exp; // jitter 0-20%\n}\n\n/** Determine if an HTTP status is retryable */\nfunction isRetryableStatus(status: number): boolean {\n return status === 408 || status === 429 || (status >= 500 && status < 600);\n}\n\n/** Determine if an error should be treated as transient network issue */\nfunction isNetworkError(err: unknown): boolean {\n return (\n err instanceof TypeError ||\n (err instanceof Error && err.name === 'AbortError')\n );\n}\n\n// -------------------------\n// fetchFn implementation\n// -------------------------\ninterface GraphQLResponseErrorItem {\n [key: string]: unknown;\n message: string;\n path?: readonly unknown[];\n}\n\ninterface GraphQLResponseShape<T = unknown> {\n data?: T;\n errors?: GraphQLResponseErrorItem[];\n extensions?: Record<string, unknown>;\n}\n\nexport class GraphQLResponseError extends Error {\n public constructor(\n public readonly errors: readonly GraphQLResponseErrorItem[],\n public readonly response: GraphQLResponseShape,\n ) {\n super(errors[0]?.message ?? 'GraphQL response error');\n this.name = 'GraphQLResponseError';\n }\n}\n\n/** Determine whether a GraphQL payload includes non-null top-level data. */\nfunction hasUsableTopLevelData(data: unknown): boolean {\n if (data == null) {\n return false;\n }\n if (typeof data !== 'object') {\n return true;\n }\n return Object.values(data as Record<string, unknown>).some((value) => {\n return value != null;\n });\n}\n\n/** Determine whether all errors are child-field errors under top-level null data. */\nfunction hasOnlyErrorsBelowNullTopLevelFields(\n response: GraphQLResponseShape,\n): boolean {\n const { data, errors } = response;\n if (\n data == null ||\n typeof data !== 'object' ||\n !Array.isArray(errors) ||\n errors.length === 0\n ) {\n return false;\n }\n\n const dataRecord = data as Record<string, unknown>;\n return errors.every((error) => {\n const [topLevelField, ...childPath] = error.path ?? [];\n if (typeof topLevelField !== 'string' || childPath.length === 0) {\n return false;\n }\n return (\n Object.prototype.hasOwnProperty.call(dataRecord, topLevelField) &&\n dataRecord[topLevelField] == null\n );\n });\n}\n\n/** Determine whether GraphQL errors should be surfaced as a thrown response error. */\nfunction shouldThrowGraphQLResponseError(\n response: GraphQLResponseShape,\n): boolean {\n return (\n Array.isArray(response.errors) &&\n response.errors.length > 0 &&\n !hasUsableTopLevelData(response.data) &&\n !hasOnlyErrorsBelowNullTopLevelFields(response)\n );\n}\n\n/** Fetch GraphQL (with retry + upload) */\nasync function fetchFn(\n request: {\n name?: string | null;\n operationKind?: unknown;\n text: string | null | undefined;\n },\n variables: Record<string, unknown>,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n _cacheConfig: unknown,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n _uploadables: unknown,\n): Promise<GraphQLResponseShape> {\n const operationEnvironmentGeneration = environmentGeneration;\n const queryText: string | null | undefined = request.text; // no persisted queries here (yet)\n if (queryText == null) {\n throw new Error('Missing GraphQL query text');\n }\n const safeQueryText: string = queryText; // narrowed non-null\n const stopRelayOperationActivity = startRelayOperationActivity(request);\n\n /** Execute one network attempt */\n async function execOnce(attempt: number): Promise<GraphQLResponseShape> {\n if (operationEnvironmentGeneration !== environmentGeneration) {\n throw new Error(ENVIRONMENT_ROTATED_ERROR);\n }\n const controller = new AbortController();\n activeHttpControllers.add(controller);\n const timeout = setTimeout(() => {\n controller.abort();\n }, RETRY_OPTIONS.fetchTimeoutMs);\n try {\n let body: BodyInit;\n const headers: Record<string, string> = {};\n if (getAuthHeaders != null) {\n try {\n Object.assign(headers, await getAuthHeaders());\n } catch {\n // ignore auth header failures\n }\n }\n if (operationEnvironmentGeneration !== environmentGeneration) {\n throw new Error(ENVIRONMENT_ROTATED_ERROR);\n }\n const hasFiles = ((): boolean => {\n try {\n const stack: unknown[] = [variables];\n while (stack.length > 0) {\n const v = stack.pop();\n if (isUploadable(v)) {\n return true;\n }\n if (Array.isArray(v)) {\n stack.push(...v);\n } else if (v != null && typeof v === 'object') {\n stack.push(...Object.values(v as Record<string, unknown>));\n }\n }\n } catch {\n // ignore traversal errors\n }\n return false;\n })();\n\n if (hasFiles) {\n body = buildFormData(safeQueryText, variables);\n headers[GRAPHQL_CSRF_PREFLIGHT_HEADER] =\n GRAPHQL_CSRF_PREFLIGHT_HEADER_VALUE;\n } else {\n headers['Content-Type'] = 'application/json';\n body = JSON.stringify({ query: safeQueryText, variables });\n }\n\n const response = await graphqlFetch(graphqlHttpEndpoint, {\n method: 'POST',\n body,\n headers,\n credentials: 'include',\n signal: controller.signal,\n });\n\n if (operationEnvironmentGeneration !== environmentGeneration) {\n throw new Error(ENVIRONMENT_ROTATED_ERROR);\n }\n\n if (!response.ok) {\n if (\n attempt < RETRY_OPTIONS.maxAttempts - 1 &&\n isRetryableStatus(response.status)\n ) {\n throw new Error(`Retryable HTTP status ${response.status}`);\n }\n const text = await response.text().catch(() => {\n return '';\n });\n throw new Error(`GraphQL HTTP error ${response.status}: ${text}`);\n }\n\n const json: GraphQLResponseShape = await response.json();\n if (Array.isArray(json.errors) && json.errors.length > 0) {\n debugLog('GraphQL errors', json.errors);\n }\n if (shouldThrowGraphQLResponseError(json)) {\n throw new GraphQLResponseError(json.errors ?? [], json);\n }\n return json;\n } finally {\n clearTimeout(timeout);\n activeHttpControllers.delete(controller);\n }\n }\n\n try {\n for (let attempt = 0; attempt < RETRY_OPTIONS.maxAttempts; attempt += 1) {\n try {\n return await execOnce(attempt);\n } catch (err) {\n if (operationEnvironmentGeneration !== environmentGeneration) {\n throw new Error(ENVIRONMENT_ROTATED_ERROR, { cause: err });\n }\n const last = attempt === RETRY_OPTIONS.maxAttempts - 1;\n const retryable =\n isNetworkError(err) ||\n (err instanceof Error &&\n err.message.includes('Retryable HTTP status'));\n if (!retryable || last) {\n debugLog('GraphQL fetch error', err);\n throw err;\n }\n const delay = calcBackoff(attempt, RETRY_OPTIONS);\n await sleep(delay);\n }\n }\n throw new Error('Exhausted retries without returning a result');\n } finally {\n stopRelayOperationActivity();\n }\n}\n\n// -------------------------\n// subscribeFn implementation (wrap existing logic in Relay Observable)\n// -------------------------\n/** Subscription function bridging the owned GraphQL WebSocket client to Relay. */\nfunction subscribeFn(\n operation: { text: string | null | undefined; name: string },\n variables: Record<string, unknown>,\n): Observable<unknown> {\n debugLog('subscription:start', operation.name, variables);\n return Observable.create<unknown>((sink) => {\n const query = operation.text;\n if (query == null || query === '') {\n sink.error(new Error('Subscription operation text is empty'));\n return () => {\n // noop\n };\n }\n const client = getWsClient();\n const dispose = client.subscribe(\n { query, variables, operationName: operation.name },\n {\n next: (data) => {\n sink.next(data);\n },\n error: (err) => {\n let errorObj: Error;\n if (err instanceof Error) {\n errorObj = err;\n } else {\n errorObj = new Error('Subscription error');\n }\n sink.error(errorObj);\n },\n complete: () => {\n sink.complete();\n },\n },\n );\n return () => {\n dispose();\n };\n });\n}\n\n/**\n * Create native Relay network layer (fetch + subscribe)\n */\nexport function getNetwork(): ReturnType<typeof Network.create> {\n return Network.create(\n fetchFn as FetchFunction,\n subscribeFn as SubscribeFunction,\n );\n}\n\n/**\n * Get anonymous relay environment\n */\nexport function getEnvironment(): Environment {\n environment ??= new Environment({\n getDataID: getDataId,\n relayFieldLogger,\n network: getNetwork(),\n store: new Store(new RecordSource()),\n });\n\n return environment;\n}\n\n/**\n * End the current Relay session and expose a new empty environment.\n */\nexport function resetRelayStore(): void {\n rotateRelayEnvironment('configured', 'session_reset');\n}\n\nexport const __test = {\n RelayWebSocketClient,\n buildFormData,\n calcBackoff,\n collectFiles,\n decodeGraphqlWsMessage,\n fetchFn,\n getDataId,\n hasUsableTopLevelData,\n isNetworkError,\n isRetryableStatus,\n isUploadable,\n shouldThrowGraphQLResponseError,\n subscribeFn,\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"envHelpers.d.ts","sourceRoot":"","sources":["../../src/envHelpers.ts"],"names":[],"mappings":"AAAA,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"envHelpers.d.ts","sourceRoot":"","sources":["../../src/envHelpers.ts"],"names":[],"mappings":"AAAA,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC;AAkC9D,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;AAE5D,QAAA,MAAM,iBAAiB,QAAS,SAAS,OAAO,MAAM,KAAG,MAAM,GAAG,SAYjE,CAAC;AAEF,QAAA,MAAM,wBAAwB,QACvB,SAAS,OACT,MAAM,KACV,OAAO,GAAG,SAcZ,CAAC;AAEF,QAAA,MAAM,kBAAkB,QAAS,SAAS,KAAG,OAE5C,CAAC;AAMF,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEvD;AAKD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAE/D;AAKD,wBAAgB,QAAQ,IAAI,OAAO,CAElC;AAED,UAAU,sBAAsB;IAC9B,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AAKD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,CAS3E;AAED,UAAU,+BAAgC,SAAQ,sBAAsB;IACtE,YAAY,EAAE,MAAM,CAAC;CACtB;AAKD,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,+BAA+B,GACvC,MAAM,CAmBR;AAED,eAAO,MAAM,MAAM;;;;CAIlB,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Environment, Network } from 'relay-runtime';
|
|
1
|
+
import { Environment, Network, Observable } from 'relay-runtime';
|
|
2
2
|
export type RelayTransportStatus = 'idle' | 'configured' | 'reconnecting';
|
|
3
3
|
export interface RelayTransportSnapshot {
|
|
4
4
|
generation: number;
|
|
@@ -25,6 +25,38 @@ export interface RelayAuthRefreshOptions {
|
|
|
25
25
|
}
|
|
26
26
|
type RelayTransportListener = () => void;
|
|
27
27
|
type RelayOperationActivityListener = () => void;
|
|
28
|
+
type GraphqlWsMessage = {
|
|
29
|
+
type: 'connection_ack';
|
|
30
|
+
payload?: unknown;
|
|
31
|
+
} | {
|
|
32
|
+
type: 'connection_auth_update_ack';
|
|
33
|
+
payload?: unknown;
|
|
34
|
+
} | {
|
|
35
|
+
id: string;
|
|
36
|
+
type: 'next';
|
|
37
|
+
payload: unknown;
|
|
38
|
+
} | {
|
|
39
|
+
id?: string;
|
|
40
|
+
type: 'error';
|
|
41
|
+
payload: unknown;
|
|
42
|
+
} | {
|
|
43
|
+
id: string;
|
|
44
|
+
type: 'complete';
|
|
45
|
+
} | {
|
|
46
|
+
type: 'ping';
|
|
47
|
+
payload?: unknown;
|
|
48
|
+
} | {
|
|
49
|
+
type: 'pong';
|
|
50
|
+
payload?: unknown;
|
|
51
|
+
};
|
|
52
|
+
type GraphqlWsMessageDecodeResult = {
|
|
53
|
+
readonly ok: true;
|
|
54
|
+
readonly value: GraphqlWsMessage;
|
|
55
|
+
} | {
|
|
56
|
+
readonly ok: false;
|
|
57
|
+
readonly reason: 'invalid' | 'unsupported';
|
|
58
|
+
};
|
|
59
|
+
declare const decodeGraphqlWsMessage: (value: unknown) => GraphqlWsMessageDecodeResult;
|
|
28
60
|
type SubscriptionSink = {
|
|
29
61
|
next: (value: unknown) => void;
|
|
30
62
|
error: (error: unknown) => void;
|
|
@@ -51,6 +83,7 @@ export interface RelayEnvironmentConfiguration {
|
|
|
51
83
|
logEvents?: boolean;
|
|
52
84
|
getDataId?: (fieldValue: unknown, typeName: string) => string | null;
|
|
53
85
|
getAuthHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
86
|
+
fetchImpl?: typeof fetch;
|
|
54
87
|
}
|
|
55
88
|
export declare function configureRelayEnvironment(options?: RelayEnvironmentConfiguration): void;
|
|
56
89
|
declare class RelayWebSocketClient {
|
|
@@ -67,6 +100,7 @@ declare class RelayWebSocketClient {
|
|
|
67
100
|
constructor(url: string);
|
|
68
101
|
hasActiveConnection(): boolean;
|
|
69
102
|
dispose(): void;
|
|
103
|
+
terminate(): void;
|
|
70
104
|
subscribe(payload: ActiveSubscription['payload'], sink: SubscriptionSink): () => void;
|
|
71
105
|
refreshAuthentication(): Promise<boolean>;
|
|
72
106
|
private connectAndSubscribe;
|
|
@@ -122,6 +156,10 @@ declare function fetchFn(request: {
|
|
|
122
156
|
operationKind?: unknown;
|
|
123
157
|
text: string | null | undefined;
|
|
124
158
|
}, variables: Record<string, unknown>, _cacheConfig: unknown, _uploadables: unknown): Promise<GraphQLResponseShape>;
|
|
159
|
+
declare function subscribeFn(operation: {
|
|
160
|
+
text: string | null | undefined;
|
|
161
|
+
name: string;
|
|
162
|
+
}, variables: Record<string, unknown>): Observable<unknown>;
|
|
125
163
|
export declare function getNetwork(): ReturnType<typeof Network.create>;
|
|
126
164
|
export declare function getEnvironment(): Environment;
|
|
127
165
|
export declare function resetRelayStore(): void;
|
|
@@ -130,6 +168,7 @@ export declare const __test: {
|
|
|
130
168
|
buildFormData: typeof buildFormData;
|
|
131
169
|
calcBackoff: typeof calcBackoff;
|
|
132
170
|
collectFiles: typeof collectFiles;
|
|
171
|
+
decodeGraphqlWsMessage: typeof decodeGraphqlWsMessage;
|
|
133
172
|
fetchFn: typeof fetchFn;
|
|
134
173
|
getDataId: typeof getDataId;
|
|
135
174
|
hasUsableTopLevelData: typeof hasUsableTopLevelData;
|
|
@@ -137,6 +176,7 @@ export declare const __test: {
|
|
|
137
176
|
isRetryableStatus: typeof isRetryableStatus;
|
|
138
177
|
isUploadable: typeof isUploadable;
|
|
139
178
|
shouldThrowGraphQLResponseError: typeof shouldThrowGraphQLResponseError;
|
|
179
|
+
subscribeFn: typeof subscribeFn;
|
|
140
180
|
};
|
|
141
181
|
export {};
|
|
142
182
|
//# sourceMappingURL=environment.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"environment.d.ts","sourceRoot":"","sources":["../../src/environment.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,OAAO,
|
|
1
|
+
{"version":3,"file":"environment.d.ts","sourceRoot":"","sources":["../../src/environment.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,OAAO,EACP,UAAU,EAMX,MAAM,eAAe,CAAC;AAyBvB,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,YAAY,GAAG,cAAc,CAAC;AAE1E,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,oBAAoB,CAAC;CAC9B;AAED,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,CAAC;AAElE,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,kBAAkB,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,WAAW,8BAA8B;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,SAAS,qBAAqB,EAAE,CAAC;CACrD;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,KAAK,sBAAsB,GAAG,MAAM,IAAI,CAAC;AACzC,KAAK,8BAA8B,GAAG,MAAM,IAAI,CAAC;AAIjD,KAAK,gBAAgB,GACjB;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,4BAA4B,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACzD;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAC9C;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAChD;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAExC,KAAK,4BAA4B,GAC7B;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAA;CAAE,GACvD;IACE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,aAAa,CAAC;CAC5C,CAAC;AAQN,QAAA,MAAM,sBAAsB,UACnB,OAAO,KACb,4BAmCF,CAAC;AAEF,KAAK,gBAAgB,GAAG;IACtB,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAC/B,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAChC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB,OAAO,EAAE;QACP,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACnC,aAAa,EAAE,MAAM,CAAC;KACvB,CAAC;IACF,IAAI,EAAE,gBAAgB,CAAC;CACxB,CAAC;AAwDF,QAAA,MAAM,SAAS,eAAgB,OAAO,YAAY,MAAM,KAAG,MAAM,GAAG,IAMnE,CAAC;AA2KF,wBAAgB,yBAAyB,IAAI,sBAAsB,CAElE;AAKD,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,sBAAsB,GAC/B,MAAM,IAAI,CAKZ;AAKD,wBAAgB,iCAAiC,IAAI,8BAA8B,CAElF;AAKD,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,8BAA8B,GACvC,MAAM,IAAI,CAKZ;AAKD,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,qBAA0B,GAClC,IAAI,CAMN;AAKD,wBAAsB,mCAAmC,CACvD,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,IAAI,CAAC,CAYf;AAED,MAAM,WAAW,6BAA6B;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACrE,cAAc,CAAC,EAAE,MACf,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3D,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAKD,wBAAgB,yBAAyB,CACvC,OAAO,GAAE,6BAAkC,GAC1C,IAAI,CAkCN;AA8CD,cAAM,oBAAoB;IAsBL,OAAO,CAAC,QAAQ,CAAC,GAAG;IArBvC,OAAO,CAAC,MAAM,CAA0B;IAExC,OAAO,CAAC,cAAc,CAA8B;IAEpD,OAAO,CAAC,QAAQ,CAAS;IAEzB,OAAO,CAAC,YAAY,CAAS;IAE7B,OAAO,CAAC,UAAU,CAAK;IAEvB,OAAO,CAAC,MAAM,CAAK;IAEnB,OAAO,CAAC,cAAc,CAA+C;IAErE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAyC;IAEvE,OAAO,CAAC,iBAAiB,CAGT;IAEhB,YAAoC,GAAG,EAAE,MAAM,EAAI;IAE5C,mBAAmB,IAAI,OAAO,CAEpC;IAEM,OAAO,IAAI,IAAI,CAWrB;IAEM,SAAS,IAAI,IAAI,CAUvB;IAEM,SAAS,CACd,OAAO,EAAE,kBAAkB,CAAC,SAAS,CAAC,EACtC,IAAI,EAAE,gBAAgB,GACrB,MAAM,IAAI,CAYZ;IAEY,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAiCrD;YAEa,mBAAmB;YAiBnB,eAAe;YAiBf,UAAU;IA4DxB,OAAO,CAAC,aAAa;IA+DrB,OAAO,CAAC,WAAW;IAWnB,OAAO,CAAC,iBAAiB;IAuBzB,OAAO,CAAC,2BAA2B;IAUnC,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,oBAAoB;IAS5B,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,wBAAwB;IAUhC,OAAO,CAAC,IAAI;CAOb;AAsCD,iBAAS,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,IAAI,GAAG,IAAI,CAK1D;AAED,UAAU,OAAO;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;CACnB;AAGD,iBAAS,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,OAAO,CAkB3E;AAGD,iBAAS,aAAa,CACpB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,QAAQ,CAqBV;AAKD,UAAU,YAAY;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;CACxB;AAmBD,iBAAS,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,MAAM,CAGhE;AAGD,iBAAS,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAElD;AAGD,iBAAS,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAK7C;AAKD,UAAU,wBAAwB;IAChC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;CAC3B;AAED,UAAU,oBAAoB,CAAC,CAAC,GAAG,OAAO;IACxC,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,MAAM,CAAC,EAAE,wBAAwB,EAAE,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,qBAAa,oBAAqB,SAAQ,KAAK;aAE3B,MAAM,EAAE,SAAS,wBAAwB,EAAE;aAC3C,QAAQ,EAAE,oBAAoB;IAFhD,YACkB,MAAM,EAAE,SAAS,wBAAwB,EAAE,EAC3C,QAAQ,EAAE,oBAAoB,EAI/C;CACF;AAGD,iBAAS,qBAAqB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAUrD;AA8BD,iBAAS,+BAA+B,CACtC,QAAQ,EAAE,oBAAoB,GAC7B,OAAO,CAOT;AAGD,iBAAe,OAAO,CACpB,OAAO,EAAE;IACP,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACjC,EACD,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAElC,YAAY,EAAE,OAAO,EAErB,YAAY,EAAE,OAAO,GACpB,OAAO,CAAC,oBAAoB,CAAC,CA6H/B;AAMD,iBAAS,WAAW,CAClB,SAAS,EAAE;IAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAC5D,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,UAAU,CAAC,OAAO,CAAC,CAmCrB;AAKD,wBAAgB,UAAU,IAAI,UAAU,CAAC,OAAO,OAAO,CAAC,MAAM,CAAC,CAK9D;AAKD,wBAAgB,cAAc,IAAI,WAAW,CAS5C;AAKD,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAED,eAAO,MAAM,MAAM;;;;;;;;;;;;;;CAclB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plumile/relay",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.205",
|
|
4
4
|
"description": "Shared Relay runtime helpers for Plumile applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -56,12 +56,11 @@
|
|
|
56
56
|
"test:build-package": "./tools/test-build-package.sh"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"graphql-ws": "6.2.1",
|
|
60
59
|
"relay-runtime": "^21.0.1",
|
|
61
60
|
"tslib": "^2.8.1"
|
|
62
61
|
},
|
|
63
62
|
"devDependencies": {
|
|
64
|
-
"@plumile/auth": "^0.1.
|
|
63
|
+
"@plumile/auth": "^0.1.205",
|
|
65
64
|
"@types/react": "19.2.18",
|
|
66
65
|
"@types/react-dom": "19.2.4",
|
|
67
66
|
"@types/relay-runtime": "20.1.1",
|
|
@@ -73,9 +72,8 @@
|
|
|
73
72
|
"rimraf": "6.1.3"
|
|
74
73
|
},
|
|
75
74
|
"peerDependencies": {
|
|
76
|
-
"@plumile/auth": "^0.1.
|
|
75
|
+
"@plumile/auth": "^0.1.201",
|
|
77
76
|
"react": "^19.2.8",
|
|
78
|
-
"react-dom": "^19.2.8",
|
|
79
77
|
"react-relay": "^21.0.1",
|
|
80
78
|
"relay-runtime": "^21.0.1"
|
|
81
79
|
},
|