@gscdump/sdk 0.36.0 → 0.36.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +6 -0
- package/dist/index.mjs +55 -16
- package/package.json +5 -5
package/dist/index.d.mts
CHANGED
|
@@ -36,6 +36,8 @@ interface HostedFetchOptions {
|
|
|
36
36
|
headers?: HeadersInit;
|
|
37
37
|
query?: Record<string, unknown>;
|
|
38
38
|
body?: unknown;
|
|
39
|
+
/** Opt into in-flight de-dupe for safe read-style POST requests. */
|
|
40
|
+
dedupe?: boolean;
|
|
39
41
|
[key: string]: unknown;
|
|
40
42
|
}
|
|
41
43
|
interface HostedClientOptions {
|
|
@@ -43,6 +45,10 @@ interface HostedClientOptions {
|
|
|
43
45
|
fetch?: HostedFetch;
|
|
44
46
|
headers?: HostedHeaders;
|
|
45
47
|
validate?: boolean | 'request' | 'response';
|
|
48
|
+
/** Coalesce concurrent identical GET/HEAD requests within this client instance. */
|
|
49
|
+
dedupe?: boolean;
|
|
50
|
+
/** Optional shared in-flight map for de-duping across related client instances. */
|
|
51
|
+
dedupeScope?: Map<string, Promise<Result<unknown, PartnerApiError>>>;
|
|
46
52
|
}
|
|
47
53
|
type AnalyticsFetch = HostedFetch;
|
|
48
54
|
type AnalyticsHeaders = HostedHeaders;
|
package/dist/index.mjs
CHANGED
|
@@ -22,6 +22,11 @@ function withDefaultSearchType(value, searchType) {
|
|
|
22
22
|
function searchTypeQuery(searchType) {
|
|
23
23
|
return { searchType: searchType ?? "web" };
|
|
24
24
|
}
|
|
25
|
+
function stableJson$1(value) {
|
|
26
|
+
if (value == null || typeof value !== "object") return JSON.stringify(value) ?? "null";
|
|
27
|
+
if (Array.isArray(value)) return `[${value.map((item) => item === void 0 ? "null" : stableJson$1(item)).join(",")}]`;
|
|
28
|
+
return `{${Object.entries(value).filter(([, item]) => item !== void 0).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson$1(item)}`).join(",")}}`;
|
|
29
|
+
}
|
|
25
30
|
function dateRangeOptionsQuery(options) {
|
|
26
31
|
const query = {};
|
|
27
32
|
const start = options?.start ?? options?.startDate;
|
|
@@ -43,17 +48,17 @@ function tablesQuery(tablesOrOptions, options) {
|
|
|
43
48
|
...searchTypeQuery(source?.searchType),
|
|
44
49
|
...dateRangeOptionsQuery(source)
|
|
45
50
|
};
|
|
46
|
-
if (tables) query.tables = Array.isArray(tables) ? tables.join(",") : tables;
|
|
51
|
+
if (tables) query.tables = Array.isArray(tables) ? [...new Set(tables.filter(Boolean))].sort().join(",") : [...new Set(tables.split(",").map((t) => t.trim()).filter(Boolean))].sort().join(",");
|
|
47
52
|
return query;
|
|
48
53
|
}
|
|
49
54
|
function dataQuery(state, options) {
|
|
50
55
|
const opts = options;
|
|
51
56
|
const scoped = withDefaultSearchType(state, opts?.searchType);
|
|
52
57
|
const query = {
|
|
53
|
-
q:
|
|
58
|
+
q: stableJson$1(scoped),
|
|
54
59
|
searchType: scoped.searchType ?? "web"
|
|
55
60
|
};
|
|
56
|
-
if (opts?.comparison) query.qc =
|
|
61
|
+
if (opts?.comparison) query.qc = stableJson$1(withDefaultSearchType(opts.comparison, scoped.searchType));
|
|
57
62
|
if (opts?.filter) query.filter = opts.filter;
|
|
58
63
|
return query;
|
|
59
64
|
}
|
|
@@ -61,10 +66,10 @@ function dataDetailQuery(state, options) {
|
|
|
61
66
|
const opts = options;
|
|
62
67
|
const scoped = withDefaultSearchType(state, opts?.searchType);
|
|
63
68
|
const query = {
|
|
64
|
-
q:
|
|
69
|
+
q: stableJson$1(scoped),
|
|
65
70
|
searchType: scoped.searchType ?? "web"
|
|
66
71
|
};
|
|
67
|
-
if (opts?.comparison) query.qc =
|
|
72
|
+
if (opts?.comparison) query.qc = stableJson$1(withDefaultSearchType(opts.comparison, scoped.searchType));
|
|
68
73
|
return query;
|
|
69
74
|
}
|
|
70
75
|
function analysisQuery(params) {
|
|
@@ -98,7 +103,7 @@ function indexingUrlsQuery(params = {}) {
|
|
|
98
103
|
}
|
|
99
104
|
function indexingDiagnosticsQuery(params = {}) {
|
|
100
105
|
const query = {};
|
|
101
|
-
if (params.sampleIssues) query.sampleIssues = Array.isArray(params.sampleIssues) ? params.sampleIssues.join(",") : params.sampleIssues;
|
|
106
|
+
if (params.sampleIssues) query.sampleIssues = Array.isArray(params.sampleIssues) ? [...new Set(params.sampleIssues.map((item) => String(item).trim()).filter(Boolean))].sort().join(",") : params.sampleIssues;
|
|
102
107
|
if (params.sampleLimit != null) query.sampleLimit = params.sampleLimit;
|
|
103
108
|
return query;
|
|
104
109
|
}
|
|
@@ -206,20 +211,51 @@ function shouldValidate(options, phase) {
|
|
|
206
211
|
function parseWith(schema, value) {
|
|
207
212
|
return schema ? schema.parse(value) : value;
|
|
208
213
|
}
|
|
214
|
+
function stableJson(value) {
|
|
215
|
+
if (value == null || typeof value !== "object") return JSON.stringify(value) ?? "undefined";
|
|
216
|
+
if (Array.isArray(value)) return `[${value.map((item) => item === void 0 ? "undefined" : stableJson(item)).join(",")}]`;
|
|
217
|
+
return `{${Object.entries(value).filter(([, item]) => item !== void 0).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(",")}}`;
|
|
218
|
+
}
|
|
219
|
+
function headersKey(headers) {
|
|
220
|
+
return [...headers.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}:${value}`).join("\n");
|
|
221
|
+
}
|
|
222
|
+
function isDedupeable(init) {
|
|
223
|
+
if ("signal" in init && init.signal) return false;
|
|
224
|
+
const method = (init.method ?? "GET").toUpperCase();
|
|
225
|
+
return method === "GET" || method === "HEAD" || init.dedupe === true;
|
|
226
|
+
}
|
|
209
227
|
function createHostedRequester(options, defaults) {
|
|
210
228
|
const fetchImpl = options.fetch ?? ofetch;
|
|
211
229
|
const apiBase = trimApiBase(options.apiBase, defaults.apiBase);
|
|
230
|
+
const dedupe = options.dedupe !== false;
|
|
231
|
+
const inflight = options.dedupeScope ?? /* @__PURE__ */ new Map();
|
|
212
232
|
async function requestResult(path, init = {}, responseSchema) {
|
|
233
|
+
const { dedupe: _dedupe, ...fetchInit } = init;
|
|
213
234
|
const headers = mergeHeaders(await resolveHeaders(options), init.headers);
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
235
|
+
const fullPath = buildPath(apiBase, path);
|
|
236
|
+
const dedupeKey = dedupe && isDedupeable(init) ? `${(init.method ?? "GET").toUpperCase()} ${fullPath}\nq=${stableJson(init.query)}\nb=${stableJson(init.body)}\nh=${headersKey(headers)}` : null;
|
|
237
|
+
if (dedupeKey) {
|
|
238
|
+
const existing = inflight.get(dedupeKey);
|
|
239
|
+
if (existing) return existing;
|
|
240
|
+
}
|
|
241
|
+
const run = (async () => {
|
|
242
|
+
try {
|
|
243
|
+
const out = await fetchImpl(fullPath, {
|
|
244
|
+
...fetchInit,
|
|
245
|
+
headers
|
|
246
|
+
});
|
|
247
|
+
return ok(shouldValidate(options, "response") ? parseWith(responseSchema, out) : out);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
return err(toPartnerError(error));
|
|
250
|
+
}
|
|
251
|
+
})();
|
|
252
|
+
if (dedupeKey) {
|
|
253
|
+
inflight.set(dedupeKey, run);
|
|
254
|
+
run.finally(() => {
|
|
255
|
+
if (inflight.get(dedupeKey) === run) inflight.delete(dedupeKey);
|
|
218
256
|
});
|
|
219
|
-
return ok(shouldValidate(options, "response") ? parseWith(responseSchema, out) : out);
|
|
220
|
-
} catch (error) {
|
|
221
|
-
return err(toPartnerError(error));
|
|
222
257
|
}
|
|
258
|
+
return run;
|
|
223
259
|
}
|
|
224
260
|
async function request(path, init = {}, responseSchema) {
|
|
225
261
|
return unwrapResult(await requestResult(path, init, responseSchema), partnerErrorToException);
|
|
@@ -248,13 +284,15 @@ function createAnalyticsClient(options = {}) {
|
|
|
248
284
|
analyze(siteId, params) {
|
|
249
285
|
return request(analyticsRoutes.site.analyze(siteId), {
|
|
250
286
|
method: "POST",
|
|
251
|
-
body: withDefaultSearchType(params)
|
|
287
|
+
body: withDefaultSearchType(params),
|
|
288
|
+
dedupe: true
|
|
252
289
|
});
|
|
253
290
|
},
|
|
254
291
|
queryRows(siteId, state) {
|
|
255
292
|
return request(analyticsRoutes.site.rows(siteId), {
|
|
256
293
|
method: "POST",
|
|
257
|
-
body: withDefaultSearchType(state)
|
|
294
|
+
body: withDefaultSearchType(state),
|
|
295
|
+
dedupe: true
|
|
258
296
|
}, analyticsEndpointSchemas.analyticsRows.response);
|
|
259
297
|
},
|
|
260
298
|
getRollup(siteId, rollupId, params) {
|
|
@@ -632,7 +670,8 @@ function createPartnerClient(options = {}) {
|
|
|
632
670
|
const body = shouldValidate("request") ? partnerEndpointSchemas.getKeywordSparklines.body.parse(withSearchType) : withSearchType;
|
|
633
671
|
return request(partnerRoutes.sites.keywordSparklines(siteId), {
|
|
634
672
|
method: "POST",
|
|
635
|
-
body
|
|
673
|
+
body,
|
|
674
|
+
dedupe: true
|
|
636
675
|
}, partnerEndpointSchemas.getKeywordSparklines.response);
|
|
637
676
|
},
|
|
638
677
|
getQueryTrend(siteId, params) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gscdump/sdk",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.36.
|
|
4
|
+
"version": "0.36.2",
|
|
5
5
|
"description": "Consumer SDK for hosted gscdump.com integrations.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -44,10 +44,10 @@
|
|
|
44
44
|
"date-fns": "^4.4.0",
|
|
45
45
|
"ofetch": "^1.5.1",
|
|
46
46
|
"zod": "^4.4.3",
|
|
47
|
-
"@gscdump/
|
|
48
|
-
"gscdump": "0.36.
|
|
49
|
-
"
|
|
50
|
-
"@gscdump/
|
|
47
|
+
"@gscdump/engine": "0.36.2",
|
|
48
|
+
"@gscdump/analysis": "0.36.2",
|
|
49
|
+
"gscdump": "0.36.2",
|
|
50
|
+
"@gscdump/contracts": "0.36.2"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"typescript": "^6.0.3",
|