@spacefast/wpcloud-sdk 0.0.11 → 0.0.12
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/fake/router.d.ts.map +1 -1
- package/dist/fake/router.js +13 -9
- package/dist/fake/server.d.ts.map +1 -1
- package/dist/fake/server.js +22 -2
- package/dist/fake/store.d.ts +7 -0
- package/dist/fake/store.d.ts.map +1 -1
- package/dist/fake/store.js +17 -1
- package/dist/fake/test-fixtures/isolation-target.d.ts +2 -0
- package/dist/fake/test-fixtures/isolation-target.d.ts.map +1 -0
- package/dist/fake/test-fixtures/isolation-target.js +1 -0
- package/dist/runtime.d.ts +11 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +174 -4
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/fake/router.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,MAAM,UAAU,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/fake/router.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,MAAM,UAAU,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAiD3D;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,eAAe,EACtB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,UAAU,CA2NZ"}
|
package/dist/fake/router.js
CHANGED
|
@@ -11,6 +11,9 @@ const fail = (status, message) => ({
|
|
|
11
11
|
body: { message, data: [] },
|
|
12
12
|
});
|
|
13
13
|
const RESTRICTED_DOMAINS = new Set(["wordpress.com", "wp.com", "automattic.com"]);
|
|
14
|
+
function asString(value, fallback = "") {
|
|
15
|
+
return typeof value === "string" ? value : fallback;
|
|
16
|
+
}
|
|
14
17
|
function asStringMap(value) {
|
|
15
18
|
const out = {};
|
|
16
19
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
@@ -93,16 +96,17 @@ export function routeFakeRequest(store, method, path, body) {
|
|
|
93
96
|
// POST /create-site/{client}
|
|
94
97
|
if (method === "POST" && head === "create-site") {
|
|
95
98
|
const meta = asStringMap(body.meta);
|
|
96
|
-
const domainName =
|
|
99
|
+
const domainName = asString(body.domain_name, `fake-${store.nextJobId()}.wpcomstaging.com`);
|
|
97
100
|
return ok(store.createSite({ domainName, meta }));
|
|
98
101
|
}
|
|
99
102
|
// POST /delete-site/{service}/{identifier}
|
|
100
103
|
if (method === "POST" && head === "delete-site") {
|
|
101
104
|
return ok(store.delete(seg[2] ?? ""));
|
|
102
105
|
}
|
|
103
|
-
// GET /get-site/{site}[/extra]
|
|
106
|
+
// GET /get-site/{site}[/extra] — `{site}` is a site id OR any hostname the site
|
|
107
|
+
// answers to: its own domain, or one of its aliases (see `FakeAtomicStore#resolve`).
|
|
104
108
|
if (method === "GET" && head === "get-site") {
|
|
105
|
-
const site = store.
|
|
109
|
+
const site = store.resolve(decodeURIComponent(seg[1] ?? ""));
|
|
106
110
|
return site ? ok(store.siteExtra(site)) : fail(404, "Site not found.");
|
|
107
111
|
}
|
|
108
112
|
// GET /job-completion/{id} , GET /job-status/{id} — jobs settle immediately in the fake
|
|
@@ -131,7 +135,7 @@ export function routeFakeRequest(store, method, path, body) {
|
|
|
131
135
|
return ok(store.getMeta(id ?? "", key ?? "") ?? null);
|
|
132
136
|
}
|
|
133
137
|
if (method === "POST" && (action === "update" || action === "add")) {
|
|
134
|
-
store.setMeta(id ?? "", key ?? "",
|
|
138
|
+
store.setMeta(id ?? "", key ?? "", asString(body.value));
|
|
135
139
|
return ok([]);
|
|
136
140
|
}
|
|
137
141
|
}
|
|
@@ -158,8 +162,8 @@ export function routeFakeRequest(store, method, path, body) {
|
|
|
158
162
|
const id = seg[1] ?? "";
|
|
159
163
|
const action = seg[2];
|
|
160
164
|
if (method === "POST" && action === "add") {
|
|
161
|
-
const schedule =
|
|
162
|
-
const command =
|
|
165
|
+
const schedule = asString(body.schedule);
|
|
166
|
+
const command = asString(body.command);
|
|
163
167
|
if (!schedule || !command)
|
|
164
168
|
return fail(400, "Invalid or empty schedule or command.");
|
|
165
169
|
const cron = store.addCron(id, { schedule, command });
|
|
@@ -179,7 +183,7 @@ export function routeFakeRequest(store, method, path, body) {
|
|
|
179
183
|
}
|
|
180
184
|
if (method === "POST" && action === "update") {
|
|
181
185
|
const cronId = Number(seg[3] ?? "");
|
|
182
|
-
const command =
|
|
186
|
+
const command = asString(body.command);
|
|
183
187
|
if (!command)
|
|
184
188
|
return fail(400, "Invalid or empty command.");
|
|
185
189
|
return store.updateCron(id, cronId, command)
|
|
@@ -198,7 +202,7 @@ export function routeFakeRequest(store, method, path, body) {
|
|
|
198
202
|
if (op === "on" || op === "off" || op === "purge")
|
|
199
203
|
return ok([]);
|
|
200
204
|
if (op === "get")
|
|
201
|
-
return ok({
|
|
205
|
+
return ok({ status: 1, status_name: "Enabled", ddos_until: 0 });
|
|
202
206
|
}
|
|
203
207
|
// /ssl-info/{host} — lean: not provisioned (loadWpCloudSslInfo tolerates null). SSL = direct suites.
|
|
204
208
|
if (head === "ssl-info")
|
|
@@ -244,7 +248,7 @@ export function routeFakeRequest(store, method, path, body) {
|
|
|
244
248
|
if (action === "update" && store.getClientMeta(key) === undefined) {
|
|
245
249
|
return fail(404, "Not Found");
|
|
246
250
|
}
|
|
247
|
-
store.setClientMeta(key,
|
|
251
|
+
store.setClientMeta(key, asString(body.value));
|
|
248
252
|
return ok([]);
|
|
249
253
|
}
|
|
250
254
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/fake/server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/fake/server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AA+D7C;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,GAAE;IAAE,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG;IACrF,KAAK,EAAE,gBAAgB,CAAC;IACxB,KAAK,EAAE,eAAe,CAAC;CACxB,CAoBA;AAED,wBAAgB,cAAc,CAAC,OAAO,GAAE;IAAE,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,gBAAgB,CAE7F;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,EAAE,gBAE7B,CAAC"}
|
package/dist/fake/server.js
CHANGED
|
@@ -3,9 +3,27 @@
|
|
|
3
3
|
* `makeFakeAtomic()` gives a fresh, isolated store (per-suite use via `setWpCloudFakeFetch`);
|
|
4
4
|
* `defaultFakeFetch` is a process-global instance for env-driven `WP_CLOUD_PROVIDER_MODE=fake`.
|
|
5
5
|
*/
|
|
6
|
-
import { randomInt } from "node:crypto";
|
|
6
|
+
import { createHash, randomInt } from "node:crypto";
|
|
7
7
|
import { routeFakeRequest } from "./router.js";
|
|
8
8
|
import { FakeAtomicStore } from "./store.js";
|
|
9
|
+
let fakeStoreOrdinal = 0;
|
|
10
|
+
function defaultSiteSequenceStart() {
|
|
11
|
+
// Bun's isolated test worker updates `process.argv[1]` to the active test
|
|
12
|
+
// file. Hash that real file identity plus the process and store ordinal so
|
|
13
|
+
// DB-backed files sharing Postgres never restart at the same provider ref.
|
|
14
|
+
// This is deterministic input, not random test data; explicit starts remain
|
|
15
|
+
// available for tests that assert exact captured provider IDs.
|
|
16
|
+
const identity = [
|
|
17
|
+
process.env.GITHUB_RUN_ID ?? "local",
|
|
18
|
+
process.env.GITHUB_RUN_ATTEMPT ?? "0",
|
|
19
|
+
process.ppid,
|
|
20
|
+
process.pid,
|
|
21
|
+
process.argv[1] ?? "unknown",
|
|
22
|
+
fakeStoreOrdinal++,
|
|
23
|
+
].join("\0");
|
|
24
|
+
const digest = createHash("sha256").update(identity).digest();
|
|
25
|
+
return 1_000_000_000_000 + digest.readUIntBE(0, 6);
|
|
26
|
+
}
|
|
9
27
|
/** Decode an `application/x-www-form-urlencoded` body, un-nesting `key[child]` / `key[]` keys. */
|
|
10
28
|
function parseNestedForm(raw) {
|
|
11
29
|
const out = {};
|
|
@@ -55,7 +73,9 @@ async function readBody(input, init) {
|
|
|
55
73
|
* persistent data.
|
|
56
74
|
*/
|
|
57
75
|
export function makeFakeAtomicWithStore(options = {}) {
|
|
58
|
-
const store = new FakeAtomicStore(
|
|
76
|
+
const store = new FakeAtomicStore({
|
|
77
|
+
siteSequenceStart: options.siteSequenceStart ?? defaultSiteSequenceStart(),
|
|
78
|
+
});
|
|
59
79
|
const fetch = async (input, init) => {
|
|
60
80
|
const rawUrl = input instanceof Request ? input.url : String(input);
|
|
61
81
|
const url = new URL(rawUrl);
|
package/dist/fake/store.d.ts
CHANGED
|
@@ -65,6 +65,13 @@ export declare class FakeAtomicStore {
|
|
|
65
65
|
}): FakeSite;
|
|
66
66
|
snapshot(): FakeSiteSnapshot[];
|
|
67
67
|
get(id: string): FakeSite | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* Atomic's `get_site_by_site_id_or_domain`: an argument containing a dot is
|
|
70
|
+
* matched against `atomic_site.domain_name` first, then against the alias
|
|
71
|
+
* table (`atomic_domain` joined onto `atomic_site`); anything else is a site
|
|
72
|
+
* id. This is what lets one `get-site` call answer "who holds this hostname?".
|
|
73
|
+
*/
|
|
74
|
+
resolve(idOrDomain: string): FakeSite | undefined;
|
|
68
75
|
delete(id: string): {
|
|
69
76
|
job_id: number;
|
|
70
77
|
};
|
package/dist/fake/store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/fake/store.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC3C,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACrB,KAAK,EAAE,aAAa,EAAE,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC3C,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B,CAAC;AASF,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA+B;IACrD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA6B;IACxD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA8C;IAC/E,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA4B;IAC7D,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,OAAO,CAAW;gBAEd,OAAO,GAAE;QAAE,iBAAiB,CAAC,EAAE,MAAM,CAAA;KAAO;IAIxD,WAAW,IAAI,MAAM,EAAE;IAGvB,WAAW,IAAI,MAAM,EAAE;IAGvB,SAAS,IAAI,MAAM;IAInB,UAAU,CAAC,KAAK,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE;;;;;IAkBtE,UAAU,CAAC,KAAK,EAAE;QAChB,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC9B,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACxC,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC5C,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;KAC5B,GAAG,QAAQ;IAiBZ,QAAQ,IAAI,gBAAgB,EAAE;IAW9B,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS;
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/fake/store.ts"],"names":[],"mappings":"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"}
|
package/dist/fake/store.js
CHANGED
|
@@ -76,6 +76,20 @@ export class FakeAtomicStore {
|
|
|
76
76
|
get(id) {
|
|
77
77
|
return this.sites.get(id);
|
|
78
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Atomic's `get_site_by_site_id_or_domain`: an argument containing a dot is
|
|
81
|
+
* matched against `atomic_site.domain_name` first, then against the alias
|
|
82
|
+
* table (`atomic_domain` joined onto `atomic_site`); anything else is a site
|
|
83
|
+
* id. This is what lets one `get-site` call answer "who holds this hostname?".
|
|
84
|
+
*/
|
|
85
|
+
resolve(idOrDomain) {
|
|
86
|
+
if (!idOrDomain.includes(".")) {
|
|
87
|
+
return this.sites.get(idOrDomain);
|
|
88
|
+
}
|
|
89
|
+
const sites = [...this.sites.values()];
|
|
90
|
+
return (sites.find((site) => site.domainName === idOrDomain) ??
|
|
91
|
+
sites.find((site) => site.aliases.has(idOrDomain)));
|
|
92
|
+
}
|
|
79
93
|
delete(id) {
|
|
80
94
|
this.sites.delete(id);
|
|
81
95
|
return { job_id: this.nextJobId() };
|
|
@@ -181,7 +195,9 @@ export class FakeAtomicStore {
|
|
|
181
195
|
delete site.persistentDataEnv[key];
|
|
182
196
|
continue;
|
|
183
197
|
}
|
|
184
|
-
|
|
198
|
+
// The wire form is `application/x-www-form-urlencoded`, so a scalar value
|
|
199
|
+
// always decodes to a string; anything else was never a persistable value.
|
|
200
|
+
site.persistentData[key] = typeof record.value === "string" ? record.value : "";
|
|
185
201
|
site.persistentDataEnv[key] = formFlag(record.env);
|
|
186
202
|
}
|
|
187
203
|
return this.persistentData(id);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isolation-target.d.ts","sourceRoot":"","sources":["../../../src/fake/test-fixtures/isolation-target.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,KAAK,SAAS,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const value = "real";
|
package/dist/runtime.d.ts
CHANGED
|
@@ -4,9 +4,19 @@ export type { Client } from "./generated/client";
|
|
|
4
4
|
export declare class WpCloudRequestError extends Error {
|
|
5
5
|
status: number;
|
|
6
6
|
payload: unknown;
|
|
7
|
-
|
|
7
|
+
retryAfterSeconds: number | null;
|
|
8
|
+
constructor(status: number, message: string, payload: unknown, retryAfterSeconds?: number | null);
|
|
8
9
|
}
|
|
10
|
+
export declare function parseRetryAfterSeconds(value: string | null): number | null;
|
|
9
11
|
export type WpCloudFakeFetch = (input: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]) => Promise<Response>;
|
|
12
|
+
export type WpCloudCallObservation = {
|
|
13
|
+
method: string;
|
|
14
|
+
path: string;
|
|
15
|
+
startedAt: number;
|
|
16
|
+
endedAt: number;
|
|
17
|
+
ok: boolean;
|
|
18
|
+
};
|
|
19
|
+
export declare function setWpCloudCallObserver(fn: ((observation: WpCloudCallObservation) => void) | null): void;
|
|
10
20
|
/**
|
|
11
21
|
* Test-only seam: route every wp.cloud call to an in-process fake instead of the network. Set in a
|
|
12
22
|
* suite's `beforeAll`, clear with `null` in `afterAll`. Used when the provider mode is `fake`; with
|
package/dist/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,MAAM,EAA8B,MAAM,oBAAoB,CAAC;AAC7E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAGjE,YAAY,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,MAAM,EAA8B,MAAM,oBAAoB,CAAC;AAC7E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAGjE,YAAY,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAgBjD,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IAIjB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;gBAG/B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,OAAO,EAChB,iBAAiB,GAAE,MAAM,GAAG,IAAW;CAQ1C;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAa1E;AAED,MAAM,MAAM,gBAAgB,GAAG,CAC7B,KAAK,EAAE,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,EAClC,IAAI,CAAC,EAAE,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,KAC/B,OAAO,CAAC,QAAQ,CAAC,CAAC;AAOvB,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,OAAO,CAAC;CACb,CAAC;AAIF,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,sBAAsB,KAAK,IAAI,CAAC,GAAG,IAAI,GACzD,IAAI,CAEN;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,gBAAgB,GAAG,IAAI,QAE9D;AAED,eAAO,MAAM,kBAAkB,EAAE,kBAO/B,CAAC;AAOH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAUvF;AAmMD,wBAAsB,eAAe,CACnC,KAAK,EAAE,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,EAClC,IAAI,CAAC,EAAE,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,qBAkBnC"}
|
package/dist/runtime.js
CHANGED
|
@@ -4,19 +4,43 @@ import path from "node:path";
|
|
|
4
4
|
import { createClient, createConfig } from "./generated/client";
|
|
5
5
|
import { mergeHeaders } from "./generated/client/utils.gen";
|
|
6
6
|
const WP_CLOUD_REQUEST_TIMEOUT_MS = 30_000;
|
|
7
|
+
const WP_CLOUD_METRICS_MAX_ATTEMPTS = 3;
|
|
7
8
|
const OPTIONAL_PATH_SEGMENT_PATTERN = /\[\/([^\]]+)\]/g;
|
|
8
9
|
const SNAPSHOT_VERSION = 1;
|
|
9
10
|
export class WpCloudRequestError extends Error {
|
|
10
11
|
status;
|
|
11
12
|
payload;
|
|
12
|
-
|
|
13
|
+
// Seconds the provider asked us to wait, parsed from its `Retry-After`
|
|
14
|
+
// header (delta-seconds or HTTP date). null when the provider sent none.
|
|
15
|
+
// Callers back off on what the provider actually says, never on a guess.
|
|
16
|
+
retryAfterSeconds;
|
|
17
|
+
constructor(status, message, payload, retryAfterSeconds = null) {
|
|
13
18
|
super(message);
|
|
14
19
|
this.name = "WpCloudRequestError";
|
|
15
20
|
this.status = status;
|
|
16
21
|
this.payload = payload;
|
|
22
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
17
23
|
}
|
|
18
24
|
}
|
|
25
|
+
export function parseRetryAfterSeconds(value) {
|
|
26
|
+
if (!value) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const trimmed = value.trim();
|
|
30
|
+
if (/^\d+$/.test(trimmed)) {
|
|
31
|
+
return Number(trimmed);
|
|
32
|
+
}
|
|
33
|
+
const date = Date.parse(trimmed);
|
|
34
|
+
if (Number.isNaN(date)) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return Math.max(0, Math.ceil((date - Date.now()) / 1000));
|
|
38
|
+
}
|
|
19
39
|
let fakeFetchOverride = null;
|
|
40
|
+
let wpCloudCallObserver = null;
|
|
41
|
+
export function setWpCloudCallObserver(fn) {
|
|
42
|
+
wpCloudCallObserver = fn;
|
|
43
|
+
}
|
|
20
44
|
/**
|
|
21
45
|
* Test-only seam: route every wp.cloud call to an in-process fake instead of the network. Set in a
|
|
22
46
|
* suite's `beforeAll`, clear with `null` in `afterAll`. Used when the provider mode is `fake`; with
|
|
@@ -46,8 +70,153 @@ export function createWpCloudClient(options) {
|
|
|
46
70
|
})));
|
|
47
71
|
}
|
|
48
72
|
async function wpCloudFetch(input, init) {
|
|
49
|
-
const
|
|
50
|
-
|
|
73
|
+
const request = wpCloudRequestIdentity(input, init);
|
|
74
|
+
const startedAt = performance.now();
|
|
75
|
+
let ok = false;
|
|
76
|
+
try {
|
|
77
|
+
const response = await performWpCloudFetch(input, init, request);
|
|
78
|
+
ok = true;
|
|
79
|
+
return response;
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
const observer = wpCloudCallObserver;
|
|
83
|
+
if (observer) {
|
|
84
|
+
try {
|
|
85
|
+
observer({ ...request, startedAt, endedAt: performance.now(), ok });
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Instrumentation must not affect provider calls.
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function performWpCloudFetch(input, init, request) {
|
|
94
|
+
const isMetricsRequest = request.method === "POST" &&
|
|
95
|
+
/\/metrics\/(?:site|client)\/[^/]+(?:\/summarize)?\/?$/.test(request.path);
|
|
96
|
+
const maxAttempts = isMetricsRequest && wpCloudProviderMode() !== "replay" ? WP_CLOUD_METRICS_MAX_ATTEMPTS : 1;
|
|
97
|
+
const failures = [];
|
|
98
|
+
/* eslint-disable no-await-in-loop -- metrics retries must complete sequentially and stop at the first atomic response. */
|
|
99
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
100
|
+
const attemptInput = input instanceof Request ? input.clone() : input;
|
|
101
|
+
const response = await wpCloudRawFetch(attemptInput, init);
|
|
102
|
+
const handledResponse = await handleWpCloudResponse(response);
|
|
103
|
+
if (!isMetricsRequest) {
|
|
104
|
+
return handledResponse;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
return await validateWpCloudMetricsResponse(handledResponse);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
if (!(error instanceof WpCloudResponseIntegrityError)) {
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
failures.push({ attempt, ...error.failure });
|
|
114
|
+
if (attempt === maxAttempts) {
|
|
115
|
+
throw new WpCloudRequestError(502, error.message, {
|
|
116
|
+
operation: "metrics",
|
|
117
|
+
request,
|
|
118
|
+
maxAttempts,
|
|
119
|
+
failures,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/* eslint-enable no-await-in-loop */
|
|
125
|
+
throw new WpCloudRequestError(502, "wp_cloud_metrics_response_unavailable", {
|
|
126
|
+
operation: "metrics",
|
|
127
|
+
request,
|
|
128
|
+
maxAttempts,
|
|
129
|
+
failures,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
class WpCloudResponseIntegrityError extends Error {
|
|
133
|
+
failure;
|
|
134
|
+
constructor(message, failure) {
|
|
135
|
+
super(message);
|
|
136
|
+
this.name = "WpCloudResponseIntegrityError";
|
|
137
|
+
this.failure = failure;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function wpCloudRequestIdentity(input, init) {
|
|
141
|
+
const url = new URL(input instanceof Request ? input.url : String(input));
|
|
142
|
+
return {
|
|
143
|
+
method: (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase(),
|
|
144
|
+
path: url.pathname,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
async function validateWpCloudMetricsResponse(response) {
|
|
148
|
+
const contentType = response.headers.get("content-type");
|
|
149
|
+
const rawContentLength = response.headers.get("content-length");
|
|
150
|
+
const parsedContentLength = rawContentLength !== null && /^\d+$/.test(rawContentLength) ? Number(rawContentLength) : null;
|
|
151
|
+
let body;
|
|
152
|
+
try {
|
|
153
|
+
body = Buffer.from(await response.arrayBuffer());
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
throw new WpCloudResponseIntegrityError("wp_cloud_metrics_response_read_failed", {
|
|
157
|
+
reason: "body_read_failed",
|
|
158
|
+
status: response.status,
|
|
159
|
+
contentType,
|
|
160
|
+
declaredContentLength: parsedContentLength,
|
|
161
|
+
bodyBytes: null,
|
|
162
|
+
bodySha256: null,
|
|
163
|
+
detail: errorMessage(error),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
const bodyMetadata = {
|
|
167
|
+
status: response.status,
|
|
168
|
+
contentType,
|
|
169
|
+
declaredContentLength: parsedContentLength,
|
|
170
|
+
bodyBytes: body.byteLength,
|
|
171
|
+
bodySha256: sha256(body),
|
|
172
|
+
};
|
|
173
|
+
if (parsedContentLength !== null && parsedContentLength !== body.byteLength) {
|
|
174
|
+
throw new WpCloudResponseIntegrityError("wp_cloud_metrics_response_length_mismatch", {
|
|
175
|
+
reason: "content_length_mismatch",
|
|
176
|
+
...bodyMetadata,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (!contentType ||
|
|
180
|
+
!(contentType.toLowerCase().startsWith("application/json") ||
|
|
181
|
+
contentType.toLowerCase().split(";", 1)[0]?.trim().endsWith("+json"))) {
|
|
182
|
+
throw new WpCloudResponseIntegrityError("wp_cloud_metrics_response_invalid_content_type", {
|
|
183
|
+
reason: "invalid_content_type",
|
|
184
|
+
...bodyMetadata,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
let parsed;
|
|
188
|
+
try {
|
|
189
|
+
parsed = JSON.parse(body.toString("utf8"));
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
throw new WpCloudResponseIntegrityError("wp_cloud_metrics_response_invalid_json", {
|
|
193
|
+
reason: "invalid_json",
|
|
194
|
+
...bodyMetadata,
|
|
195
|
+
detail: errorMessage(error),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
if (!parsed ||
|
|
199
|
+
typeof parsed !== "object" ||
|
|
200
|
+
Array.isArray(parsed) ||
|
|
201
|
+
typeof parsed.message !== "string" ||
|
|
202
|
+
!(parsed.data &&
|
|
203
|
+
typeof parsed.data === "object" &&
|
|
204
|
+
!Array.isArray(parsed.data) &&
|
|
205
|
+
Array.isArray(parsed.data.periods))) {
|
|
206
|
+
throw new WpCloudResponseIntegrityError("wp_cloud_metrics_response_invalid_envelope", {
|
|
207
|
+
reason: "invalid_metrics_envelope",
|
|
208
|
+
...bodyMetadata,
|
|
209
|
+
detail: "expected_atomic_data_periods",
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
return new Response(Uint8Array.from(body).buffer, {
|
|
213
|
+
status: response.status,
|
|
214
|
+
statusText: response.statusText,
|
|
215
|
+
headers: response.headers,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
function errorMessage(error) {
|
|
219
|
+
return error instanceof Error ? error.message : String(error);
|
|
51
220
|
}
|
|
52
221
|
export async function wpCloudRawFetch(input, init) {
|
|
53
222
|
const mode = wpCloudProviderMode();
|
|
@@ -396,7 +565,7 @@ async function handleWpCloudResponse(response) {
|
|
|
396
565
|
typeof payload.message === "string"
|
|
397
566
|
? payload.message
|
|
398
567
|
: `wp_cloud_request_failed:${response.status}`;
|
|
399
|
-
throw new WpCloudRequestError(response.status, message, payload);
|
|
568
|
+
throw new WpCloudRequestError(response.status, message, payload, parseRetryAfterSeconds(response.headers.get("retry-after")));
|
|
400
569
|
}
|
|
401
570
|
async function readErrorPayload(response) {
|
|
402
571
|
const text = await response.text();
|
|
@@ -464,6 +633,7 @@ async function prepareWpCloudRequest(options) {
|
|
|
464
633
|
request.path ??= {};
|
|
465
634
|
request.url = request.url.replace(OPTIONAL_PATH_SEGMENT_PATTERN, (_segment, name) => {
|
|
466
635
|
const value = request.path?.[name];
|
|
636
|
+
// oxlint-disable-next-line typescript/no-base-to-string -- generated path values are URL-encoded primitives at runtime.
|
|
467
637
|
return value === undefined || value === null ? "" : `/${encodeURIComponent(String(value))}`;
|
|
468
638
|
});
|
|
469
639
|
}
|