@foir/sdk 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +222 -0
- package/README.md +10 -2
- package/dist/auth/ops.d.ts +8 -0
- package/dist/auth.js +91 -37
- package/dist/client.js +44 -13
- package/dist/codegen/preset.d.ts +3 -2
- package/dist/data/builtins.d.ts +17 -1
- package/dist/data/client.d.ts +10 -8
- package/dist/data/document.d.ts +13 -0
- package/dist/data/index.d.ts +13 -3
- package/dist/data/manifest.d.ts +31 -3
- package/dist/data/schema.d.ts +16 -9
- package/dist/data/selection.d.ts +15 -0
- package/dist/data/transport.d.ts +244 -22
- package/dist/data/ws-transport.d.ts +8 -2
- package/dist/data.js +236 -70
- package/dist/index.js +0 -9
- package/dist/live/drift.d.ts +9 -10
- package/dist/live.js +1 -1
- package/dist/next.js +88 -35
- package/dist/push.js +91 -37
- package/dist/react.js +88 -35
- package/dist/server/index.d.ts +1 -0
- package/dist/server/operation-types.d.ts +86 -58
- package/dist/server/read-dispatch.d.ts +53 -0
- package/dist/server/require-scoped-token.d.ts +19 -0
- package/dist/server.js +162 -22
- package/dist/transport/foir-http.d.ts +55 -5
- package/package.json +1 -1
package/dist/next.js
CHANGED
|
@@ -101,19 +101,46 @@ var FoirRequestError = class extends Error {
|
|
|
101
101
|
return typeof seconds === "number" && seconds > 0 ? seconds * 1e3 : void 0;
|
|
102
102
|
}
|
|
103
103
|
};
|
|
104
|
+
var DISPATCH_RESERVED_HEADERS = /* @__PURE__ */ new Set([
|
|
105
|
+
"x-api-key",
|
|
106
|
+
"authorization",
|
|
107
|
+
"x-foir-token",
|
|
108
|
+
"x-foir-context"
|
|
109
|
+
]);
|
|
110
|
+
function refuseCredentialHeaders(extra) {
|
|
111
|
+
if (!extra) return;
|
|
112
|
+
for (const name of Object.keys(extra)) {
|
|
113
|
+
if (DISPATCH_RESERVED_HEADERS.has(name.toLowerCase())) {
|
|
114
|
+
throw new FoirRequestError(
|
|
115
|
+
`[foir] a client built from a dispatch token cannot send "${name}" in \`headers\`. The dispatch token is the credential, and an api key or a bearer beside it would make the request two actors at once. Build a separate client for the other credential.`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
104
120
|
function buildFoirHeaders(input) {
|
|
105
121
|
const headers = {
|
|
106
|
-
"Content-Type": "application/json"
|
|
107
|
-
"x-api-key": input.apiKey
|
|
122
|
+
"Content-Type": "application/json"
|
|
108
123
|
};
|
|
109
|
-
if (input.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
headers["
|
|
124
|
+
if (input.dispatchToken !== void 0) {
|
|
125
|
+
refuseCredentialHeaders(input.extra);
|
|
126
|
+
headers["X-Foir-Token"] = input.dispatchToken;
|
|
127
|
+
if (input.dispatchContext) {
|
|
128
|
+
headers["X-Foir-Context"] = input.dispatchContext;
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
headers["x-api-key"] = input.apiKey;
|
|
132
|
+
if (input.draftChannel) {
|
|
133
|
+
headers["X-Foir-Schema-Channel"] = "draft";
|
|
134
|
+
}
|
|
135
|
+
if (input.bearerToken) {
|
|
136
|
+
headers["Authorization"] = `Bearer ${input.bearerToken}`;
|
|
137
|
+
}
|
|
138
|
+
if (input.orgId) {
|
|
139
|
+
headers["X-Org-Id"] = input.orgId;
|
|
140
|
+
}
|
|
141
|
+
if (input.orgUrl) {
|
|
142
|
+
headers["X-Org-Url"] = input.orgUrl;
|
|
143
|
+
}
|
|
117
144
|
}
|
|
118
145
|
if (input.extra) {
|
|
119
146
|
for (const [key, value] of Object.entries(input.extra)) {
|
|
@@ -157,12 +184,15 @@ function refusePrunedDocument(json, options) {
|
|
|
157
184
|
`[foir] selections stripped from this query: ${selections.map(describe).join(", ")}`
|
|
158
185
|
);
|
|
159
186
|
}
|
|
187
|
+
function answeredAnything(data) {
|
|
188
|
+
return typeof data === "object" && data !== null && Object.values(data).some((value) => value !== null && value !== void 0);
|
|
189
|
+
}
|
|
160
190
|
function readFoirEnvelope(json, options) {
|
|
161
191
|
reportAuthDowngrade(json);
|
|
162
192
|
refusePrunedDocument(json, options);
|
|
163
193
|
if (json.errors?.length) {
|
|
164
194
|
const msg = json.errors.map((e) => e.message).join("; ");
|
|
165
|
-
if (json.data && !options?.strict) {
|
|
195
|
+
if (answeredAnything(json.data) && !options?.strict) {
|
|
166
196
|
console.warn(`[foir] GraphQL partial errors (data returned): ${msg}`);
|
|
167
197
|
} else {
|
|
168
198
|
throw new FoirRequestError(`[foir] GraphQL errors: ${msg}`, {
|
|
@@ -681,10 +711,42 @@ async function resolveBearerToken(config) {
|
|
|
681
711
|
if (typeof source === "function") return await source() ?? void 0;
|
|
682
712
|
return source ?? void 0;
|
|
683
713
|
}
|
|
714
|
+
async function resolveDispatchToken(dispatch) {
|
|
715
|
+
const token = (await dispatch.token())?.trim();
|
|
716
|
+
if (!token) {
|
|
717
|
+
throw new FoirRequestError(
|
|
718
|
+
"[foir] the dispatch token provider returned no token. A client built from a dispatch calls Foir as that operation and holds no other credential. Build it from the token the endpoint was handed: `(await requireScopedToken(request)).dispatch`."
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
return token;
|
|
722
|
+
}
|
|
723
|
+
async function resolveFoirAuth(config) {
|
|
724
|
+
if (config.dispatch !== void 0) {
|
|
725
|
+
return {
|
|
726
|
+
headers: buildFoirHeaders({
|
|
727
|
+
dispatchToken: await resolveDispatchToken(config.dispatch),
|
|
728
|
+
dispatchContext: config.dispatch.context ?? void 0,
|
|
729
|
+
extra: config.headers
|
|
730
|
+
})
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
const bearerToken = await resolveBearerToken(config);
|
|
734
|
+
return {
|
|
735
|
+
headers: buildFoirHeaders({
|
|
736
|
+
apiKey: config.apiKey,
|
|
737
|
+
draftChannel: config.channel === "draft",
|
|
738
|
+
bearerToken,
|
|
739
|
+
orgId: config.orgId,
|
|
740
|
+
orgUrl: config.orgUrl,
|
|
741
|
+
extra: config.headers
|
|
742
|
+
}),
|
|
743
|
+
key: { apiKey: config.apiKey, bearerToken }
|
|
744
|
+
};
|
|
745
|
+
}
|
|
684
746
|
function foirRoute(input) {
|
|
685
747
|
if (!input.apiKey.startsWith("pk_")) return { lane: "origin", reason: "key" };
|
|
686
748
|
if (input.bearerToken) return { lane: "origin", reason: "bearer" };
|
|
687
|
-
if (input.orgId) return { lane: "origin", reason: "org" };
|
|
749
|
+
if (input.orgId || input.orgUrl) return { lane: "origin", reason: "org" };
|
|
688
750
|
if (input.mutation) return { lane: "origin", reason: "operation" };
|
|
689
751
|
if (input.writeToken) return { lane: "origin", reason: "floor" };
|
|
690
752
|
if (input.channel === "draft") return { lane: "origin", reason: "channel" };
|
|
@@ -702,22 +764,16 @@ async function executeFoirDocument(config, plan) {
|
|
|
702
764
|
async function runFoirDocument(config, document, mutation, variables, options) {
|
|
703
765
|
const doFetch = config.fetch ?? globalThis.fetch;
|
|
704
766
|
const endpoint = foirDataEndpoint(config.apiUrl, "/graphql");
|
|
705
|
-
const
|
|
706
|
-
const headers = buildFoirHeaders({
|
|
707
|
-
apiKey: config.apiKey,
|
|
708
|
-
draftChannel: config.channel === "draft",
|
|
709
|
-
bearerToken,
|
|
710
|
-
orgId: config.orgId,
|
|
711
|
-
extra: config.headers
|
|
712
|
-
});
|
|
767
|
+
const { headers, key } = await resolveFoirAuth(config);
|
|
713
768
|
const hash = await documentHash(document);
|
|
714
|
-
const
|
|
715
|
-
apiKey:
|
|
716
|
-
bearerToken,
|
|
769
|
+
const sharedKey = key !== void 0 && foirRoute({
|
|
770
|
+
apiKey: key.apiKey,
|
|
771
|
+
bearerToken: key.bearerToken,
|
|
717
772
|
orgId: config.orgId,
|
|
773
|
+
orgUrl: config.orgUrl,
|
|
718
774
|
mutation,
|
|
719
775
|
channel: config.channel
|
|
720
|
-
});
|
|
776
|
+
}).lane === "shared" ? key.apiKey : void 0;
|
|
721
777
|
const post = (body) => doFetch(endpoint, {
|
|
722
778
|
method: "POST",
|
|
723
779
|
headers,
|
|
@@ -727,8 +783,8 @@ async function runFoirDocument(config, document, mutation, variables, options) {
|
|
|
727
783
|
const extensions = persistedQueryExtension(hash);
|
|
728
784
|
let res;
|
|
729
785
|
try {
|
|
730
|
-
if (
|
|
731
|
-
res = await doFetch(buildCdnUrl(endpoint,
|
|
786
|
+
if (sharedKey !== void 0) {
|
|
787
|
+
res = await doFetch(buildCdnUrl(endpoint, sharedKey, hash, variables), {
|
|
732
788
|
method: "GET",
|
|
733
789
|
headers,
|
|
734
790
|
...config.requestInit
|
|
@@ -766,20 +822,16 @@ async function readErrorBody(res) {
|
|
|
766
822
|
}
|
|
767
823
|
|
|
768
824
|
// src/data/manifest.ts
|
|
769
|
-
async function fetchFoirManifest(config) {
|
|
770
|
-
const
|
|
825
|
+
async function fetchFoirManifest(config, options = {}) {
|
|
826
|
+
const base = foirDataEndpoint(config.apiUrl, "/v1/manifest");
|
|
827
|
+
const url = options.operation === void 0 ? base : `${base}?operation=${encodeURIComponent(options.operation)}`;
|
|
771
828
|
const doFetch = config.fetch ?? globalThis.fetch;
|
|
829
|
+
const { headers } = await resolveFoirAuth(config);
|
|
772
830
|
let res;
|
|
773
831
|
try {
|
|
774
832
|
res = await doFetch(url, {
|
|
775
833
|
method: "GET",
|
|
776
|
-
headers
|
|
777
|
-
apiKey: config.apiKey,
|
|
778
|
-
draftChannel: config.channel === "draft",
|
|
779
|
-
bearerToken: await resolveBearerToken(config),
|
|
780
|
-
orgId: config.orgId,
|
|
781
|
-
extra: config.headers
|
|
782
|
-
}),
|
|
834
|
+
headers,
|
|
783
835
|
...config.requestInit
|
|
784
836
|
});
|
|
785
837
|
} catch (err) {
|
|
@@ -851,6 +903,7 @@ function authClientConfig(env, options) {
|
|
|
851
903
|
channel: isFoirPreview(env) ? "draft" : "published",
|
|
852
904
|
bearerToken: options?.bearerToken,
|
|
853
905
|
orgId: options?.orgId,
|
|
906
|
+
orgUrl: options?.orgUrl,
|
|
854
907
|
headers: options?.headers,
|
|
855
908
|
fetch: options?.fetch
|
|
856
909
|
};
|
package/dist/push.js
CHANGED
|
@@ -103,19 +103,46 @@ function readFoirRetryAfterMs(headers) {
|
|
|
103
103
|
const seconds = Number(headers.get("Retry-After"));
|
|
104
104
|
return Number.isFinite(seconds) && seconds > 0 ? seconds * 1e3 : void 0;
|
|
105
105
|
}
|
|
106
|
+
var DISPATCH_RESERVED_HEADERS = /* @__PURE__ */ new Set([
|
|
107
|
+
"x-api-key",
|
|
108
|
+
"authorization",
|
|
109
|
+
"x-foir-token",
|
|
110
|
+
"x-foir-context"
|
|
111
|
+
]);
|
|
112
|
+
function refuseCredentialHeaders(extra) {
|
|
113
|
+
if (!extra) return;
|
|
114
|
+
for (const name of Object.keys(extra)) {
|
|
115
|
+
if (DISPATCH_RESERVED_HEADERS.has(name.toLowerCase())) {
|
|
116
|
+
throw new FoirRequestError(
|
|
117
|
+
`[foir] a client built from a dispatch token cannot send "${name}" in \`headers\`. The dispatch token is the credential, and an api key or a bearer beside it would make the request two actors at once. Build a separate client for the other credential.`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
106
122
|
function buildFoirHeaders(input) {
|
|
107
123
|
const headers = {
|
|
108
|
-
"Content-Type": "application/json"
|
|
109
|
-
"x-api-key": input.apiKey
|
|
124
|
+
"Content-Type": "application/json"
|
|
110
125
|
};
|
|
111
|
-
if (input.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
headers["
|
|
126
|
+
if (input.dispatchToken !== void 0) {
|
|
127
|
+
refuseCredentialHeaders(input.extra);
|
|
128
|
+
headers["X-Foir-Token"] = input.dispatchToken;
|
|
129
|
+
if (input.dispatchContext) {
|
|
130
|
+
headers["X-Foir-Context"] = input.dispatchContext;
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
headers["x-api-key"] = input.apiKey;
|
|
134
|
+
if (input.draftChannel) {
|
|
135
|
+
headers["X-Foir-Schema-Channel"] = "draft";
|
|
136
|
+
}
|
|
137
|
+
if (input.bearerToken) {
|
|
138
|
+
headers["Authorization"] = `Bearer ${input.bearerToken}`;
|
|
139
|
+
}
|
|
140
|
+
if (input.orgId) {
|
|
141
|
+
headers["X-Org-Id"] = input.orgId;
|
|
142
|
+
}
|
|
143
|
+
if (input.orgUrl) {
|
|
144
|
+
headers["X-Org-Url"] = input.orgUrl;
|
|
145
|
+
}
|
|
119
146
|
}
|
|
120
147
|
if (input.extra) {
|
|
121
148
|
for (const [key, value] of Object.entries(input.extra)) {
|
|
@@ -159,12 +186,15 @@ function refusePrunedDocument(json, options) {
|
|
|
159
186
|
`[foir] selections stripped from this query: ${selections.map(describe).join(", ")}`
|
|
160
187
|
);
|
|
161
188
|
}
|
|
189
|
+
function answeredAnything(data) {
|
|
190
|
+
return typeof data === "object" && data !== null && Object.values(data).some((value) => value !== null && value !== void 0);
|
|
191
|
+
}
|
|
162
192
|
function readFoirEnvelope(json, options) {
|
|
163
193
|
reportAuthDowngrade(json);
|
|
164
194
|
refusePrunedDocument(json, options);
|
|
165
195
|
if (json.errors?.length) {
|
|
166
196
|
const msg = json.errors.map((e) => e.message).join("; ");
|
|
167
|
-
if (json.data && !options?.strict) {
|
|
197
|
+
if (answeredAnything(json.data) && !options?.strict) {
|
|
168
198
|
console.warn(`[foir] GraphQL partial errors (data returned): ${msg}`);
|
|
169
199
|
} else {
|
|
170
200
|
throw new FoirRequestError(`[foir] GraphQL errors: ${msg}`, {
|
|
@@ -232,12 +262,13 @@ async function foirRequest(env, document, variables, options) {
|
|
|
232
262
|
apiKey,
|
|
233
263
|
draftChannel: isFoirPreview(env),
|
|
234
264
|
bearerToken: options?.bearerToken,
|
|
235
|
-
orgId: options?.orgId
|
|
265
|
+
orgId: options?.orgId,
|
|
266
|
+
orgUrl: options?.orgUrl
|
|
236
267
|
});
|
|
237
268
|
const documentText = print2(document);
|
|
238
269
|
let res;
|
|
239
270
|
try {
|
|
240
|
-
if (isCdnEligible(env, documentText, options?.bearerToken, options?.orgId)) {
|
|
271
|
+
if (isCdnEligible(env, documentText, options?.bearerToken, options?.orgId ?? options?.orgUrl)) {
|
|
241
272
|
const hash = await documentHash(documentText);
|
|
242
273
|
let shared = await fetch(buildCdnUrl(endpoint, apiKey, hash, variables), {
|
|
243
274
|
method: "GET",
|
|
@@ -793,10 +824,42 @@ async function resolveBearerToken(config) {
|
|
|
793
824
|
if (typeof source === "function") return await source() ?? void 0;
|
|
794
825
|
return source ?? void 0;
|
|
795
826
|
}
|
|
827
|
+
async function resolveDispatchToken(dispatch) {
|
|
828
|
+
const token = (await dispatch.token())?.trim();
|
|
829
|
+
if (!token) {
|
|
830
|
+
throw new FoirRequestError(
|
|
831
|
+
"[foir] the dispatch token provider returned no token. A client built from a dispatch calls Foir as that operation and holds no other credential. Build it from the token the endpoint was handed: `(await requireScopedToken(request)).dispatch`."
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
return token;
|
|
835
|
+
}
|
|
836
|
+
async function resolveFoirAuth(config) {
|
|
837
|
+
if (config.dispatch !== void 0) {
|
|
838
|
+
return {
|
|
839
|
+
headers: buildFoirHeaders({
|
|
840
|
+
dispatchToken: await resolveDispatchToken(config.dispatch),
|
|
841
|
+
dispatchContext: config.dispatch.context ?? void 0,
|
|
842
|
+
extra: config.headers
|
|
843
|
+
})
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
const bearerToken = await resolveBearerToken(config);
|
|
847
|
+
return {
|
|
848
|
+
headers: buildFoirHeaders({
|
|
849
|
+
apiKey: config.apiKey,
|
|
850
|
+
draftChannel: config.channel === "draft",
|
|
851
|
+
bearerToken,
|
|
852
|
+
orgId: config.orgId,
|
|
853
|
+
orgUrl: config.orgUrl,
|
|
854
|
+
extra: config.headers
|
|
855
|
+
}),
|
|
856
|
+
key: { apiKey: config.apiKey, bearerToken }
|
|
857
|
+
};
|
|
858
|
+
}
|
|
796
859
|
function foirRoute(input) {
|
|
797
860
|
if (!input.apiKey.startsWith("pk_")) return { lane: "origin", reason: "key" };
|
|
798
861
|
if (input.bearerToken) return { lane: "origin", reason: "bearer" };
|
|
799
|
-
if (input.orgId) return { lane: "origin", reason: "org" };
|
|
862
|
+
if (input.orgId || input.orgUrl) return { lane: "origin", reason: "org" };
|
|
800
863
|
if (input.mutation) return { lane: "origin", reason: "operation" };
|
|
801
864
|
if (input.writeToken) return { lane: "origin", reason: "floor" };
|
|
802
865
|
if (input.channel === "draft") return { lane: "origin", reason: "channel" };
|
|
@@ -814,22 +877,16 @@ async function executeFoirDocument(config, plan) {
|
|
|
814
877
|
async function runFoirDocument(config, document, mutation, variables, options) {
|
|
815
878
|
const doFetch = config.fetch ?? globalThis.fetch;
|
|
816
879
|
const endpoint = foirDataEndpoint(config.apiUrl, "/graphql");
|
|
817
|
-
const
|
|
818
|
-
const headers = buildFoirHeaders({
|
|
819
|
-
apiKey: config.apiKey,
|
|
820
|
-
draftChannel: config.channel === "draft",
|
|
821
|
-
bearerToken,
|
|
822
|
-
orgId: config.orgId,
|
|
823
|
-
extra: config.headers
|
|
824
|
-
});
|
|
880
|
+
const { headers, key } = await resolveFoirAuth(config);
|
|
825
881
|
const hash = await documentHash(document);
|
|
826
|
-
const
|
|
827
|
-
apiKey:
|
|
828
|
-
bearerToken,
|
|
882
|
+
const sharedKey = key !== void 0 && foirRoute({
|
|
883
|
+
apiKey: key.apiKey,
|
|
884
|
+
bearerToken: key.bearerToken,
|
|
829
885
|
orgId: config.orgId,
|
|
886
|
+
orgUrl: config.orgUrl,
|
|
830
887
|
mutation,
|
|
831
888
|
channel: config.channel
|
|
832
|
-
});
|
|
889
|
+
}).lane === "shared" ? key.apiKey : void 0;
|
|
833
890
|
const post = (body) => doFetch(endpoint, {
|
|
834
891
|
method: "POST",
|
|
835
892
|
headers,
|
|
@@ -839,8 +896,8 @@ async function runFoirDocument(config, document, mutation, variables, options) {
|
|
|
839
896
|
const extensions = persistedQueryExtension(hash);
|
|
840
897
|
let res;
|
|
841
898
|
try {
|
|
842
|
-
if (
|
|
843
|
-
res = await doFetch(buildCdnUrl(endpoint,
|
|
899
|
+
if (sharedKey !== void 0) {
|
|
900
|
+
res = await doFetch(buildCdnUrl(endpoint, sharedKey, hash, variables), {
|
|
844
901
|
method: "GET",
|
|
845
902
|
headers,
|
|
846
903
|
...config.requestInit
|
|
@@ -878,20 +935,16 @@ async function readErrorBody(res) {
|
|
|
878
935
|
}
|
|
879
936
|
|
|
880
937
|
// src/data/manifest.ts
|
|
881
|
-
async function fetchFoirManifest(config) {
|
|
882
|
-
const
|
|
938
|
+
async function fetchFoirManifest(config, options = {}) {
|
|
939
|
+
const base = foirDataEndpoint(config.apiUrl, "/v1/manifest");
|
|
940
|
+
const url = options.operation === void 0 ? base : `${base}?operation=${encodeURIComponent(options.operation)}`;
|
|
883
941
|
const doFetch = config.fetch ?? globalThis.fetch;
|
|
942
|
+
const { headers } = await resolveFoirAuth(config);
|
|
884
943
|
let res;
|
|
885
944
|
try {
|
|
886
945
|
res = await doFetch(url, {
|
|
887
946
|
method: "GET",
|
|
888
|
-
headers
|
|
889
|
-
apiKey: config.apiKey,
|
|
890
|
-
draftChannel: config.channel === "draft",
|
|
891
|
-
bearerToken: await resolveBearerToken(config),
|
|
892
|
-
orgId: config.orgId,
|
|
893
|
-
extra: config.headers
|
|
894
|
-
}),
|
|
947
|
+
headers,
|
|
895
948
|
...config.requestInit
|
|
896
949
|
});
|
|
897
950
|
} catch (err) {
|
|
@@ -963,6 +1016,7 @@ function authClientConfig(env, options) {
|
|
|
963
1016
|
channel: isFoirPreview(env) ? "draft" : "published",
|
|
964
1017
|
bearerToken: options?.bearerToken,
|
|
965
1018
|
orgId: options?.orgId,
|
|
1019
|
+
orgUrl: options?.orgUrl,
|
|
966
1020
|
headers: options?.headers,
|
|
967
1021
|
fetch: options?.fetch
|
|
968
1022
|
};
|
package/dist/react.js
CHANGED
|
@@ -95,19 +95,46 @@ var FoirRequestError = class extends Error {
|
|
|
95
95
|
return typeof seconds === "number" && seconds > 0 ? seconds * 1e3 : void 0;
|
|
96
96
|
}
|
|
97
97
|
};
|
|
98
|
+
var DISPATCH_RESERVED_HEADERS = /* @__PURE__ */ new Set([
|
|
99
|
+
"x-api-key",
|
|
100
|
+
"authorization",
|
|
101
|
+
"x-foir-token",
|
|
102
|
+
"x-foir-context"
|
|
103
|
+
]);
|
|
104
|
+
function refuseCredentialHeaders(extra) {
|
|
105
|
+
if (!extra) return;
|
|
106
|
+
for (const name of Object.keys(extra)) {
|
|
107
|
+
if (DISPATCH_RESERVED_HEADERS.has(name.toLowerCase())) {
|
|
108
|
+
throw new FoirRequestError(
|
|
109
|
+
`[foir] a client built from a dispatch token cannot send "${name}" in \`headers\`. The dispatch token is the credential, and an api key or a bearer beside it would make the request two actors at once. Build a separate client for the other credential.`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
98
114
|
function buildFoirHeaders(input) {
|
|
99
115
|
const headers = {
|
|
100
|
-
"Content-Type": "application/json"
|
|
101
|
-
"x-api-key": input.apiKey
|
|
116
|
+
"Content-Type": "application/json"
|
|
102
117
|
};
|
|
103
|
-
if (input.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
headers["
|
|
118
|
+
if (input.dispatchToken !== void 0) {
|
|
119
|
+
refuseCredentialHeaders(input.extra);
|
|
120
|
+
headers["X-Foir-Token"] = input.dispatchToken;
|
|
121
|
+
if (input.dispatchContext) {
|
|
122
|
+
headers["X-Foir-Context"] = input.dispatchContext;
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
headers["x-api-key"] = input.apiKey;
|
|
126
|
+
if (input.draftChannel) {
|
|
127
|
+
headers["X-Foir-Schema-Channel"] = "draft";
|
|
128
|
+
}
|
|
129
|
+
if (input.bearerToken) {
|
|
130
|
+
headers["Authorization"] = `Bearer ${input.bearerToken}`;
|
|
131
|
+
}
|
|
132
|
+
if (input.orgId) {
|
|
133
|
+
headers["X-Org-Id"] = input.orgId;
|
|
134
|
+
}
|
|
135
|
+
if (input.orgUrl) {
|
|
136
|
+
headers["X-Org-Url"] = input.orgUrl;
|
|
137
|
+
}
|
|
111
138
|
}
|
|
112
139
|
if (input.extra) {
|
|
113
140
|
for (const [key, value] of Object.entries(input.extra)) {
|
|
@@ -151,12 +178,15 @@ function refusePrunedDocument(json, options) {
|
|
|
151
178
|
`[foir] selections stripped from this query: ${selections.map(describe).join(", ")}`
|
|
152
179
|
);
|
|
153
180
|
}
|
|
181
|
+
function answeredAnything(data) {
|
|
182
|
+
return typeof data === "object" && data !== null && Object.values(data).some((value) => value !== null && value !== void 0);
|
|
183
|
+
}
|
|
154
184
|
function readFoirEnvelope(json, options) {
|
|
155
185
|
reportAuthDowngrade(json);
|
|
156
186
|
refusePrunedDocument(json, options);
|
|
157
187
|
if (json.errors?.length) {
|
|
158
188
|
const msg = json.errors.map((e) => e.message).join("; ");
|
|
159
|
-
if (json.data && !options?.strict) {
|
|
189
|
+
if (answeredAnything(json.data) && !options?.strict) {
|
|
160
190
|
console.warn(`[foir] GraphQL partial errors (data returned): ${msg}`);
|
|
161
191
|
} else {
|
|
162
192
|
throw new FoirRequestError(`[foir] GraphQL errors: ${msg}`, {
|
|
@@ -675,10 +705,42 @@ async function resolveBearerToken(config) {
|
|
|
675
705
|
if (typeof source === "function") return await source() ?? void 0;
|
|
676
706
|
return source ?? void 0;
|
|
677
707
|
}
|
|
708
|
+
async function resolveDispatchToken(dispatch) {
|
|
709
|
+
const token = (await dispatch.token())?.trim();
|
|
710
|
+
if (!token) {
|
|
711
|
+
throw new FoirRequestError(
|
|
712
|
+
"[foir] the dispatch token provider returned no token. A client built from a dispatch calls Foir as that operation and holds no other credential. Build it from the token the endpoint was handed: `(await requireScopedToken(request)).dispatch`."
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
return token;
|
|
716
|
+
}
|
|
717
|
+
async function resolveFoirAuth(config) {
|
|
718
|
+
if (config.dispatch !== void 0) {
|
|
719
|
+
return {
|
|
720
|
+
headers: buildFoirHeaders({
|
|
721
|
+
dispatchToken: await resolveDispatchToken(config.dispatch),
|
|
722
|
+
dispatchContext: config.dispatch.context ?? void 0,
|
|
723
|
+
extra: config.headers
|
|
724
|
+
})
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
const bearerToken = await resolveBearerToken(config);
|
|
728
|
+
return {
|
|
729
|
+
headers: buildFoirHeaders({
|
|
730
|
+
apiKey: config.apiKey,
|
|
731
|
+
draftChannel: config.channel === "draft",
|
|
732
|
+
bearerToken,
|
|
733
|
+
orgId: config.orgId,
|
|
734
|
+
orgUrl: config.orgUrl,
|
|
735
|
+
extra: config.headers
|
|
736
|
+
}),
|
|
737
|
+
key: { apiKey: config.apiKey, bearerToken }
|
|
738
|
+
};
|
|
739
|
+
}
|
|
678
740
|
function foirRoute(input) {
|
|
679
741
|
if (!input.apiKey.startsWith("pk_")) return { lane: "origin", reason: "key" };
|
|
680
742
|
if (input.bearerToken) return { lane: "origin", reason: "bearer" };
|
|
681
|
-
if (input.orgId) return { lane: "origin", reason: "org" };
|
|
743
|
+
if (input.orgId || input.orgUrl) return { lane: "origin", reason: "org" };
|
|
682
744
|
if (input.mutation) return { lane: "origin", reason: "operation" };
|
|
683
745
|
if (input.writeToken) return { lane: "origin", reason: "floor" };
|
|
684
746
|
if (input.channel === "draft") return { lane: "origin", reason: "channel" };
|
|
@@ -696,22 +758,16 @@ async function executeFoirDocument(config, plan) {
|
|
|
696
758
|
async function runFoirDocument(config, document2, mutation, variables, options) {
|
|
697
759
|
const doFetch = config.fetch ?? globalThis.fetch;
|
|
698
760
|
const endpoint = foirDataEndpoint(config.apiUrl, "/graphql");
|
|
699
|
-
const
|
|
700
|
-
const headers = buildFoirHeaders({
|
|
701
|
-
apiKey: config.apiKey,
|
|
702
|
-
draftChannel: config.channel === "draft",
|
|
703
|
-
bearerToken,
|
|
704
|
-
orgId: config.orgId,
|
|
705
|
-
extra: config.headers
|
|
706
|
-
});
|
|
761
|
+
const { headers, key } = await resolveFoirAuth(config);
|
|
707
762
|
const hash = await documentHash(document2);
|
|
708
|
-
const
|
|
709
|
-
apiKey:
|
|
710
|
-
bearerToken,
|
|
763
|
+
const sharedKey = key !== void 0 && foirRoute({
|
|
764
|
+
apiKey: key.apiKey,
|
|
765
|
+
bearerToken: key.bearerToken,
|
|
711
766
|
orgId: config.orgId,
|
|
767
|
+
orgUrl: config.orgUrl,
|
|
712
768
|
mutation,
|
|
713
769
|
channel: config.channel
|
|
714
|
-
});
|
|
770
|
+
}).lane === "shared" ? key.apiKey : void 0;
|
|
715
771
|
const post = (body) => doFetch(endpoint, {
|
|
716
772
|
method: "POST",
|
|
717
773
|
headers,
|
|
@@ -721,8 +777,8 @@ async function runFoirDocument(config, document2, mutation, variables, options)
|
|
|
721
777
|
const extensions = persistedQueryExtension(hash);
|
|
722
778
|
let res;
|
|
723
779
|
try {
|
|
724
|
-
if (
|
|
725
|
-
res = await doFetch(buildCdnUrl(endpoint,
|
|
780
|
+
if (sharedKey !== void 0) {
|
|
781
|
+
res = await doFetch(buildCdnUrl(endpoint, sharedKey, hash, variables), {
|
|
726
782
|
method: "GET",
|
|
727
783
|
headers,
|
|
728
784
|
...config.requestInit
|
|
@@ -760,20 +816,16 @@ async function readErrorBody(res) {
|
|
|
760
816
|
}
|
|
761
817
|
|
|
762
818
|
// src/data/manifest.ts
|
|
763
|
-
async function fetchFoirManifest(config) {
|
|
764
|
-
const
|
|
819
|
+
async function fetchFoirManifest(config, options = {}) {
|
|
820
|
+
const base = foirDataEndpoint(config.apiUrl, "/v1/manifest");
|
|
821
|
+
const url = options.operation === void 0 ? base : `${base}?operation=${encodeURIComponent(options.operation)}`;
|
|
765
822
|
const doFetch = config.fetch ?? globalThis.fetch;
|
|
823
|
+
const { headers } = await resolveFoirAuth(config);
|
|
766
824
|
let res;
|
|
767
825
|
try {
|
|
768
826
|
res = await doFetch(url, {
|
|
769
827
|
method: "GET",
|
|
770
|
-
headers
|
|
771
|
-
apiKey: config.apiKey,
|
|
772
|
-
draftChannel: config.channel === "draft",
|
|
773
|
-
bearerToken: await resolveBearerToken(config),
|
|
774
|
-
orgId: config.orgId,
|
|
775
|
-
extra: config.headers
|
|
776
|
-
}),
|
|
828
|
+
headers,
|
|
777
829
|
...config.requestInit
|
|
778
830
|
});
|
|
779
831
|
} catch (err) {
|
|
@@ -845,6 +897,7 @@ function authClientConfig(env, options) {
|
|
|
845
897
|
channel: isFoirPreview(env) ? "draft" : "published",
|
|
846
898
|
bearerToken: options?.bearerToken,
|
|
847
899
|
orgId: options?.orgId,
|
|
900
|
+
orgUrl: options?.orgUrl,
|
|
848
901
|
headers: options?.headers,
|
|
849
902
|
fetch: options?.fetch
|
|
850
903
|
};
|
package/dist/server/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { createServerClient, ServerClientError } from './server-client';
|
|
|
2
2
|
export { verifyWebhookSignature } from './webhook-helpers';
|
|
3
3
|
export { verifyScopedToken, parseContextHeader, parseSubject, recordsReadModels, recordsWriteModels, type ScopedTokenClaims, type ScopedTokenSubject, type VerifyScopedTokenOptions, } from './verify-scoped-token';
|
|
4
4
|
export { requireScopedToken, ScopedTokenError, type RequireScopedTokenOptions, type ScopedTokenIngress, } from './require-scoped-token';
|
|
5
|
+
export { readDispatch, OperationPayloadError, type BodyCarrier, } from './read-dispatch';
|
|
5
6
|
export type { ServerClient, ServerClientOptions, GraphQLResponse, EditorPlacement, } from './types';
|
|
6
7
|
export type { OperationPayload, OperationTrigger, OperationRecordContext, OperationContext, OperationResult, OperationCallback, } from './operation-types';
|
|
7
8
|
export { createCallbackClient } from './callback-client';
|