@spacefast/common 0.0.21 → 0.0.24
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/agents/connect-targets.d.ts +9 -6
- package/dist/agents/connect-targets.js +9 -6
- package/dist/config/domains.d.ts +1 -1
- package/dist/config/domains.js +1 -0
- package/dist/config/index.d.ts +0 -1
- package/dist/config/index.js +0 -1
- package/dist/contracts/abuse.d.ts +2 -0
- package/dist/contracts/abuse.js +12 -0
- package/dist/contracts/access.d.ts +121 -9
- package/dist/contracts/access.js +73 -9
- package/dist/contracts/activity.d.ts +1 -1
- package/dist/contracts/activity.js +0 -9
- package/dist/contracts/api-keys.d.ts +21 -21
- package/dist/contracts/beta.js +1 -1
- package/dist/contracts/billing.d.ts +1 -34
- package/dist/contracts/billing.js +4 -53
- package/dist/contracts/builds.d.ts +8 -8
- package/dist/contracts/builds.js +10 -3
- package/dist/contracts/cli.d.ts +10 -0
- package/dist/contracts/cli.js +10 -0
- package/dist/contracts/collab-public.d.ts +63 -0
- package/dist/contracts/collab-public.js +46 -0
- package/dist/contracts/comments.d.ts +283 -54
- package/dist/contracts/comments.js +173 -52
- package/dist/contracts/common.d.ts +3 -3
- package/dist/contracts/device-auth.d.ts +6 -6
- package/dist/contracts/device-auth.js +1 -1
- package/dist/contracts/docs.d.ts +5 -5
- package/dist/contracts/domains.d.ts +36 -36
- package/dist/contracts/enums.d.ts +33 -30
- package/dist/contracts/enums.js +52 -38
- package/dist/contracts/error-code-meta.d.ts +37 -126
- package/dist/contracts/error-code-meta.js +12 -41
- package/dist/contracts/error-codes.d.ts +1 -1
- package/dist/contracts/error-codes.js +10 -32
- package/dist/contracts/execution.d.ts +35 -57
- package/dist/contracts/execution.js +41 -40
- package/dist/contracts/feature-lifecycle-core.d.ts +0 -6
- package/dist/contracts/feature-lifecycle-core.js +2 -31
- package/dist/contracts/feature-lifecycle.d.ts +8 -38
- package/dist/contracts/feature-lifecycle.js +16 -48
- package/dist/contracts/features.d.ts +15 -13
- package/dist/contracts/features.js +39 -38
- package/dist/contracts/functions.d.ts +97 -70
- package/dist/contracts/functions.js +101 -34
- package/dist/contracts/git.d.ts +1 -1
- package/dist/contracts/grants.d.ts +7 -3
- package/dist/contracts/grants.js +18 -9
- package/dist/contracts/internal.d.ts +14 -1
- package/dist/contracts/internal.js +17 -7
- package/dist/contracts/oauth-resources.d.ts +4 -3
- package/dist/contracts/oauth-resources.js +12 -3
- package/dist/contracts/operations.d.ts +90 -5
- package/dist/contracts/operations.js +75 -1
- package/dist/contracts/pages.d.ts +6 -1
- package/dist/contracts/pages.js +24 -1
- package/dist/contracts/plan-policy.d.ts +1 -3
- package/dist/contracts/plan-policy.js +1 -2
- package/dist/contracts/platform.d.ts +61 -148
- package/dist/contracts/platform.js +70 -126
- package/dist/contracts/publish-archive.d.ts +498 -0
- package/dist/contracts/publish-archive.js +25 -0
- package/dist/contracts/resources.d.ts +12 -24
- package/dist/contracts/resources.js +32 -12
- package/dist/contracts/runtime-api.d.ts +369 -175
- package/dist/contracts/runtime-api.js +372 -348
- package/dist/contracts/runtime-app.d.ts +127 -120
- package/dist/contracts/runtime-app.js +19 -10
- package/dist/contracts/runtime-db.d.ts +1 -1
- package/dist/contracts/runtime-services.d.ts +219 -0
- package/dist/contracts/runtime-services.js +437 -0
- package/dist/contracts/runtime-storage.d.ts +45 -32
- package/dist/contracts/runtime-storage.js +30 -8
- package/dist/contracts/space-config.d.ts +10 -16
- package/dist/contracts/space-config.js +14 -10
- package/dist/contracts/spaces.d.ts +241 -436
- package/dist/contracts/spaces.js +128 -145
- package/dist/contracts/superadmin-activity.d.ts +57 -4
- package/dist/contracts/superadmin-activity.js +48 -2
- package/dist/contracts/superadmin-search.d.ts +126 -0
- package/dist/contracts/superadmin-search.js +61 -0
- package/dist/contracts/superadmin-spaces.d.ts +358 -89
- package/dist/contracts/superadmin-spaces.js +97 -24
- package/dist/contracts/superadmin-teams.d.ts +28 -10
- package/dist/contracts/superadmin-teams.js +5 -8
- package/dist/contracts/superadmin.d.ts +0 -32
- package/dist/contracts/superadmin.js +3 -11
- package/dist/contracts/tags.d.ts +9 -9
- package/dist/contracts/teams.d.ts +44 -2
- package/dist/contracts/teams.js +43 -23
- package/dist/contracts/transfers.d.ts +0 -49
- package/dist/contracts/transfers.js +2 -6
- package/dist/contracts/usage.d.ts +61 -0
- package/dist/contracts/usage.js +64 -0
- package/dist/contracts/variables.d.ts +2 -7
- package/dist/contracts/variables.js +6 -5
- package/dist/contracts/webhooks.d.ts +2 -2
- package/dist/contracts/zero.d.ts +87 -56
- package/dist/contracts/zero.js +82 -20
- package/dist/dashboard-paths/index.d.ts +1 -1
- package/dist/dashboard-paths/index.js +1 -0
- package/dist/docs/agent-prose.d.ts +1 -1
- package/dist/docs/agent-prose.js +62 -66
- package/dist/docs/agent-setup.js +49 -23
- package/dist/docs/agent-solutions.d.ts +4 -4
- package/dist/docs/agent-solutions.js +8 -8
- package/dist/docs/catalog.d.ts +19 -19
- package/dist/docs/catalog.js +30 -19
- package/dist/docs/error-docs.js +63 -151
- package/dist/docs/skills.d.ts +3 -3
- package/dist/docs/skills.js +1 -1
- package/dist/test-helpers/fetch-stub.d.ts +16 -0
- package/dist/test-helpers/fetch-stub.js +24 -0
- package/dist/utils/build-settings.js +9 -3
- package/dist/utils/canonical-json.d.ts +10 -0
- package/dist/utils/canonical-json.js +21 -0
- package/dist/utils/cast-transport.d.ts +28 -0
- package/dist/utils/cast-transport.js +52 -0
- package/dist/utils/claim-token.d.ts +0 -8
- package/dist/utils/claim-token.js +3 -15
- package/dist/utils/credential-policy.d.ts +1 -1
- package/dist/utils/env-file.d.ts +17 -0
- package/dist/utils/env-file.js +96 -0
- package/dist/utils/error-code.d.ts +2 -0
- package/dist/utils/error-code.js +7 -0
- package/dist/utils/generate-space-name.d.ts +8 -10
- package/dist/utils/generate-space-name.js +206 -21
- package/dist/utils/grant-decision.fixtures.json +2938 -0
- package/dist/utils/grants.d.ts +2 -0
- package/dist/utils/grants.js +25 -2
- package/dist/utils/gravatar.d.ts +9 -0
- package/dist/utils/gravatar.js +31 -0
- package/dist/utils/idempotency.d.ts +6 -0
- package/dist/utils/idempotency.js +28 -0
- package/dist/utils/local-space-state.d.ts +1 -5
- package/dist/utils/local-space-state.js +7 -26
- package/dist/utils/page-fonts.d.ts +2 -2
- package/dist/utils/page-fonts.js +22 -4
- package/dist/utils/pages.d.ts +1 -2
- package/dist/utils/pages.js +21 -5
- package/dist/utils/publish-form-data.d.ts +12 -0
- package/dist/utils/publish-form-data.js +10 -0
- package/dist/utils/publish-policy.d.ts +20 -0
- package/dist/utils/publish-policy.fixtures.json +397 -0
- package/dist/utils/publish-policy.js +36 -19
- package/dist/utils/push-new.d.ts +7 -0
- package/dist/utils/push-new.js +7 -0
- package/dist/utils/query-keys.d.ts +0 -1
- package/dist/utils/query-keys.js +0 -1
- package/dist/utils/runtime-paths.d.ts +5 -0
- package/dist/utils/runtime-paths.js +5 -0
- package/dist/utils/runtime-upload-batch.d.ts +0 -5
- package/dist/utils/runtime-upload-batch.js +1 -27
- package/dist/utils/runtime-upload.d.ts +3 -13
- package/dist/utils/runtime-upload.js +4 -14
- package/dist/utils/space-config.d.ts +0 -33
- package/dist/utils/space-config.js +5 -326
- package/dist/utils/storage-policy.d.ts +1 -1
- package/dist/utils/storage-policy.js +3 -1
- package/dist/utils/structured-error.d.ts +14 -0
- package/dist/utils/structured-error.js +22 -0
- package/dist/utils/version-path.js +12 -3
- package/dist/vocabulary.d.ts +5 -3
- package/dist/vocabulary.js +13 -10
- package/package.json +10 -5
- package/dist/config/postgres-budget.d.ts +0 -129
- package/dist/config/postgres-budget.js +0 -140
- package/dist/config/postgres-budget.json +0 -51
- package/dist/contracts/archives.d.ts +0 -331
- package/dist/contracts/archives.js +0 -120
- package/dist/contracts/deployments.d.ts +0 -574
- package/dist/contracts/deployments.js +0 -97
- package/dist/contracts/sites.d.ts +0 -416
- package/dist/contracts/sites.js +0 -168
- package/dist/contracts/superadmin-queues.d.ts +0 -414
- package/dist/contracts/superadmin-queues.js +0 -202
- package/dist/contracts/superadmin-runtime.d.ts +0 -27
- package/dist/contracts/superadmin-runtime.js +0 -38
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/utils/api.d.ts +0 -4
- package/dist/utils/api.js +0 -13
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform services that tenant code reaches through the runtime rather than
|
|
3
|
+
* through a credential of its own: Gravatar, spam checking, and mail.
|
|
4
|
+
*
|
|
5
|
+
* Three properties hold for all three, and they are the reason this file
|
|
6
|
+
* exists instead of three unrelated ones:
|
|
7
|
+
*
|
|
8
|
+
* - **The credential is never in tenant reach.** An Akismet key or a Gravatar
|
|
9
|
+
* token in a bundle is a published secret, and a wp.cloud `SMTP_PASS` in a
|
|
10
|
+
* capsule is worse. Every call below is brokered: the runtime attaches
|
|
11
|
+
* authority outside the code that asked for the call.
|
|
12
|
+
* - **The interface is the contract, not the accessor.** Zero hands these to a
|
|
13
|
+
* handler as `ctx.gravatar` / `ctx.spam` / `ctx.email`; Functions binds them
|
|
14
|
+
* as `env.GRAVATAR` / `env.SPAM` / `env.EMAIL`. Method names and semantic
|
|
15
|
+
* fields stay shared. Zero's `check` narrows the input because its HTTP host
|
|
16
|
+
* can add request metadata; a detached Functions call supplies that metadata
|
|
17
|
+
* explicitly.
|
|
18
|
+
* - **The client is the same source on both tiers.** See
|
|
19
|
+
* `RUNTIME_SERVICE_CLIENT_SOURCE`: normalization, validation and the pure
|
|
20
|
+
* avatar-URL construction are one implementation injected into both runtimes,
|
|
21
|
+
* so the two tiers cannot drift into behaving differently.
|
|
22
|
+
*
|
|
23
|
+
* What differs between tiers is the transport under `call()`, and only that.
|
|
24
|
+
*/
|
|
25
|
+
/* -------------------------------------------------------------------------- */
|
|
26
|
+
/* Gravatar */
|
|
27
|
+
/* -------------------------------------------------------------------------- */
|
|
28
|
+
/**
|
|
29
|
+
* What Gravatar serves when an address has no image. `404` is here so callers
|
|
30
|
+
* can detect absence by status instead of receiving a generated face they then
|
|
31
|
+
* have to recognise as a placeholder.
|
|
32
|
+
*/
|
|
33
|
+
export const GRAVATAR_DEFAULTS = [
|
|
34
|
+
"404",
|
|
35
|
+
"mp",
|
|
36
|
+
"identicon",
|
|
37
|
+
"monsterid",
|
|
38
|
+
"wavatar",
|
|
39
|
+
"retro",
|
|
40
|
+
"robohash",
|
|
41
|
+
"blank",
|
|
42
|
+
];
|
|
43
|
+
export const GRAVATAR_RATINGS = ["g", "pg", "r", "x"];
|
|
44
|
+
/** Gravatar's own ceiling. Larger values are refused rather than clamped. */
|
|
45
|
+
export const GRAVATAR_AVATAR_MAX_SIZE = 2048;
|
|
46
|
+
/* -------------------------------------------------------------------------- */
|
|
47
|
+
/* Spam */
|
|
48
|
+
/* -------------------------------------------------------------------------- */
|
|
49
|
+
/**
|
|
50
|
+
* Akismet's recognised content types. The vendor is not in the name of
|
|
51
|
+
* anything an author touches — `ctx.spam` outlives whichever service answers
|
|
52
|
+
* it — but the vocabulary is Akismet's because the classifier is trained on
|
|
53
|
+
* it, and inventing synonyms would only lose signal.
|
|
54
|
+
*/
|
|
55
|
+
export const SPAM_CONTENT_TYPES = [
|
|
56
|
+
"comment",
|
|
57
|
+
"reply",
|
|
58
|
+
"forum-post",
|
|
59
|
+
"contact-form",
|
|
60
|
+
"signup",
|
|
61
|
+
"message",
|
|
62
|
+
];
|
|
63
|
+
/* -------------------------------------------------------------------------- */
|
|
64
|
+
/* Upstreams */
|
|
65
|
+
/* -------------------------------------------------------------------------- */
|
|
66
|
+
/**
|
|
67
|
+
* The hosts a broker may reach on a tenant's behalf are deliberately NOT listed
|
|
68
|
+
* here. That table lives in `stattic-runtime-core`'s egress policy, where it is
|
|
69
|
+
* generated into both the runtime's PHP constants and the Rust broker, so one
|
|
70
|
+
* declaration is enforced at both places a connection is actually made. A copy
|
|
71
|
+
* in this file would be a third statement of the same fact that nothing checks.
|
|
72
|
+
*
|
|
73
|
+
* The `gravatar.com` in the avatar URL below is not that table: it is a public
|
|
74
|
+
* URL handed to a browser, not an egress target.
|
|
75
|
+
*/
|
|
76
|
+
/* -------------------------------------------------------------------------- */
|
|
77
|
+
/* The shared client */
|
|
78
|
+
/* -------------------------------------------------------------------------- */
|
|
79
|
+
/**
|
|
80
|
+
* The client both runtimes inject, as source.
|
|
81
|
+
*
|
|
82
|
+
* It is a string rather than a module because neither runtime can import from
|
|
83
|
+
* npm at the point it needs this: Zero compiles a capsule to QuickJS bytecode
|
|
84
|
+
* with a generated prelude, and the Functions host injects modules into a
|
|
85
|
+
* tenant isolate it does not build. Shipping one source to both is what makes
|
|
86
|
+
* "the same API" a fact about the code rather than a promise in a doc.
|
|
87
|
+
*
|
|
88
|
+
* It closes over nothing of ours. The host defines `call(service, operation,
|
|
89
|
+
* payload)` and everything else — normalization, the pure avatar URL, the
|
|
90
|
+
* shapes returned to tenant code — is here, identical on both tiers.
|
|
91
|
+
*
|
|
92
|
+
* The SHA-256 is hand-rolled for the same reason: QuickJS has no WebCrypto,
|
|
93
|
+
* and Cloudflare's `crypto.subtle` is async, which would force `avatarUrl` to
|
|
94
|
+
* return a promise on one tier and a string on the other. A synchronous
|
|
95
|
+
* implementation is the only way the signature can be identical.
|
|
96
|
+
*/
|
|
97
|
+
export const RUNTIME_SERVICE_CLIENT_SOURCE = String.raw `
|
|
98
|
+
const SERVICE_DEFAULTS = ${JSON.stringify(GRAVATAR_DEFAULTS)};
|
|
99
|
+
const SERVICE_RATINGS = ${JSON.stringify(GRAVATAR_RATINGS)};
|
|
100
|
+
const SPAM_TYPES = ${JSON.stringify(SPAM_CONTENT_TYPES)};
|
|
101
|
+
const GRAVATAR_MAX_SIZE = ${GRAVATAR_AVATAR_MAX_SIZE};
|
|
102
|
+
|
|
103
|
+
class ServiceError extends Error {
|
|
104
|
+
constructor(code, message) {
|
|
105
|
+
super(message);
|
|
106
|
+
this.name = "SpacefastServiceError";
|
|
107
|
+
this.code = code;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function invalid(message) {
|
|
112
|
+
return new ServiceError("service_payload_invalid", message);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// UTF-8 by hand: TextEncoder is not guaranteed in QuickJS, and the digest below
|
|
116
|
+
// must agree byte for byte with the one the other tier computes.
|
|
117
|
+
function utf8Bytes(value) {
|
|
118
|
+
const bytes = [];
|
|
119
|
+
for (let index = 0; index < value.length; index++) {
|
|
120
|
+
let point = value.charCodeAt(index);
|
|
121
|
+
if (point >= 0xd800 && point <= 0xdbff && index + 1 < value.length) {
|
|
122
|
+
const low = value.charCodeAt(index + 1);
|
|
123
|
+
if (low >= 0xdc00 && low <= 0xdfff) {
|
|
124
|
+
point = ((point - 0xd800) << 10) + (low - 0xdc00) + 0x10000;
|
|
125
|
+
index++;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (point < 0x80) {
|
|
129
|
+
bytes.push(point);
|
|
130
|
+
} else if (point < 0x800) {
|
|
131
|
+
bytes.push(0xc0 | (point >> 6), 0x80 | (point & 0x3f));
|
|
132
|
+
} else if (point < 0x10000) {
|
|
133
|
+
bytes.push(0xe0 | (point >> 12), 0x80 | ((point >> 6) & 0x3f), 0x80 | (point & 0x3f));
|
|
134
|
+
} else {
|
|
135
|
+
bytes.push(
|
|
136
|
+
0xf0 | (point >> 18),
|
|
137
|
+
0x80 | ((point >> 12) & 0x3f),
|
|
138
|
+
0x80 | ((point >> 6) & 0x3f),
|
|
139
|
+
0x80 | (point & 0x3f),
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return bytes;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const SHA256_K = [
|
|
147
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
148
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
149
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
150
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
151
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
152
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
153
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
154
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
function sha256Hex(value) {
|
|
158
|
+
const bytes = utf8Bytes(value);
|
|
159
|
+
const bitLength = bytes.length * 8;
|
|
160
|
+
bytes.push(0x80);
|
|
161
|
+
while (bytes.length % 64 !== 56) bytes.push(0);
|
|
162
|
+
// Length is 64-bit big-endian; inputs here are addresses, so the high word is
|
|
163
|
+
// always zero and is written as such rather than computed.
|
|
164
|
+
bytes.push(0, 0, 0, 0);
|
|
165
|
+
bytes.push((bitLength >>> 24) & 0xff, (bitLength >>> 16) & 0xff, (bitLength >>> 8) & 0xff, bitLength & 0xff);
|
|
166
|
+
|
|
167
|
+
const hash = [
|
|
168
|
+
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
|
169
|
+
];
|
|
170
|
+
const words = new Array(64);
|
|
171
|
+
|
|
172
|
+
for (let offset = 0; offset < bytes.length; offset += 64) {
|
|
173
|
+
for (let index = 0; index < 16; index++) {
|
|
174
|
+
const at = offset + index * 4;
|
|
175
|
+
words[index] =
|
|
176
|
+
((bytes[at] << 24) | (bytes[at + 1] << 16) | (bytes[at + 2] << 8) | bytes[at + 3]) >>> 0;
|
|
177
|
+
}
|
|
178
|
+
for (let index = 16; index < 64; index++) {
|
|
179
|
+
const a = words[index - 15];
|
|
180
|
+
const b = words[index - 2];
|
|
181
|
+
const s0 = ((a >>> 7) | (a << 25)) ^ ((a >>> 18) | (a << 14)) ^ (a >>> 3);
|
|
182
|
+
const s1 = ((b >>> 17) | (b << 15)) ^ ((b >>> 19) | (b << 13)) ^ (b >>> 10);
|
|
183
|
+
words[index] = (words[index - 16] + s0 + words[index - 7] + s1) >>> 0;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
let [a, b, c, d, e, f, g, h] = hash;
|
|
187
|
+
for (let index = 0; index < 64; index++) {
|
|
188
|
+
const s1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7));
|
|
189
|
+
const ch = (e & f) ^ (~e & g);
|
|
190
|
+
const temp1 = (h + s1 + ch + SHA256_K[index] + words[index]) >>> 0;
|
|
191
|
+
const s0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10));
|
|
192
|
+
const maj = (a & b) ^ (a & c) ^ (b & c);
|
|
193
|
+
const temp2 = (s0 + maj) >>> 0;
|
|
194
|
+
h = g;
|
|
195
|
+
g = f;
|
|
196
|
+
f = e;
|
|
197
|
+
e = (d + temp1) >>> 0;
|
|
198
|
+
d = c;
|
|
199
|
+
c = b;
|
|
200
|
+
b = a;
|
|
201
|
+
a = (temp1 + temp2) >>> 0;
|
|
202
|
+
}
|
|
203
|
+
hash[0] = (hash[0] + a) >>> 0;
|
|
204
|
+
hash[1] = (hash[1] + b) >>> 0;
|
|
205
|
+
hash[2] = (hash[2] + c) >>> 0;
|
|
206
|
+
hash[3] = (hash[3] + d) >>> 0;
|
|
207
|
+
hash[4] = (hash[4] + e) >>> 0;
|
|
208
|
+
hash[5] = (hash[5] + f) >>> 0;
|
|
209
|
+
hash[6] = (hash[6] + g) >>> 0;
|
|
210
|
+
hash[7] = (hash[7] + h) >>> 0;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let hex = "";
|
|
214
|
+
for (const word of hash) {
|
|
215
|
+
hex += ("00000000" + (word >>> 0).toString(16)).slice(-8);
|
|
216
|
+
}
|
|
217
|
+
return hex;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Gravatar's own identifier rule. Trim and lower-case only: it deliberately
|
|
221
|
+
// does not strip dots or plus-tags, because two addresses that differ there are
|
|
222
|
+
// two accounts as far as Gravatar is concerned.
|
|
223
|
+
function gravatarHash(email) {
|
|
224
|
+
if (typeof email !== "string" || email.trim() === "") {
|
|
225
|
+
throw invalid("An email address is required.");
|
|
226
|
+
}
|
|
227
|
+
return sha256Hex(email.trim().toLowerCase());
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function makeGravatar(call) {
|
|
231
|
+
return Object.freeze({
|
|
232
|
+
avatarUrl(email, options) {
|
|
233
|
+
const hash = gravatarHash(email);
|
|
234
|
+
const settings = options && typeof options === "object" ? options : {};
|
|
235
|
+
const query = [];
|
|
236
|
+
if (settings.size !== undefined) {
|
|
237
|
+
const size = Number(settings.size);
|
|
238
|
+
if (!Number.isInteger(size) || size < 1 || size > GRAVATAR_MAX_SIZE) {
|
|
239
|
+
throw invalid("Avatar size must be an integer between 1 and " + GRAVATAR_MAX_SIZE + ".");
|
|
240
|
+
}
|
|
241
|
+
query.push("s=" + size);
|
|
242
|
+
}
|
|
243
|
+
if (settings.default !== undefined) {
|
|
244
|
+
if (!SERVICE_DEFAULTS.includes(settings.default)) {
|
|
245
|
+
throw invalid("Avatar default must be one of: " + SERVICE_DEFAULTS.join(", ") + ".");
|
|
246
|
+
}
|
|
247
|
+
query.push("d=" + encodeURIComponent(settings.default));
|
|
248
|
+
}
|
|
249
|
+
if (settings.rating !== undefined) {
|
|
250
|
+
if (!SERVICE_RATINGS.includes(settings.rating)) {
|
|
251
|
+
throw invalid("Avatar rating must be one of: " + SERVICE_RATINGS.join(", ") + ".");
|
|
252
|
+
}
|
|
253
|
+
query.push("r=" + settings.rating);
|
|
254
|
+
}
|
|
255
|
+
if (settings.forceDefault === true) {
|
|
256
|
+
query.push("f=y");
|
|
257
|
+
}
|
|
258
|
+
return (
|
|
259
|
+
"https://gravatar.com/avatar/" + hash + (query.length > 0 ? "?" + query.join("&") : "")
|
|
260
|
+
);
|
|
261
|
+
},
|
|
262
|
+
async profile(email) {
|
|
263
|
+
return call("gravatar", "profile", { hash: gravatarHash(email) });
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function spamPayload(submission) {
|
|
269
|
+
if (!submission || typeof submission !== "object") {
|
|
270
|
+
throw invalid("A submission object is required.");
|
|
271
|
+
}
|
|
272
|
+
if (typeof submission.content !== "string" || submission.content === "") {
|
|
273
|
+
throw invalid("Submission content is required.");
|
|
274
|
+
}
|
|
275
|
+
if (typeof submission.userIp !== "string" || submission.userIp.trim() === "") {
|
|
276
|
+
throw invalid("Submission userIp is required: a verdict without it is unreliable.");
|
|
277
|
+
}
|
|
278
|
+
if (submission.type !== undefined && !SPAM_TYPES.includes(submission.type)) {
|
|
279
|
+
throw invalid("Submission type must be one of: " + SPAM_TYPES.join(", ") + ".");
|
|
280
|
+
}
|
|
281
|
+
if (submission.authorRole !== undefined && submission.authorRole !== "administrator") {
|
|
282
|
+
throw invalid('Submission authorRole may only be "administrator".');
|
|
283
|
+
}
|
|
284
|
+
const payload = {
|
|
285
|
+
content: submission.content,
|
|
286
|
+
userIp: submission.userIp.trim(),
|
|
287
|
+
type: submission.type || "comment",
|
|
288
|
+
};
|
|
289
|
+
for (const field of [
|
|
290
|
+
"userAgent",
|
|
291
|
+
"referrer",
|
|
292
|
+
"permalink",
|
|
293
|
+
"authorName",
|
|
294
|
+
"authorEmail",
|
|
295
|
+
"authorUrl",
|
|
296
|
+
"authorRole",
|
|
297
|
+
"createdAt",
|
|
298
|
+
]) {
|
|
299
|
+
if (typeof submission[field] === "string" && submission[field] !== "") {
|
|
300
|
+
payload[field] = submission[field];
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (Array.isArray(submission.languages) && submission.languages.length > 0) {
|
|
304
|
+
payload.languages = submission.languages.filter((entry) => typeof entry === "string");
|
|
305
|
+
}
|
|
306
|
+
return payload;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function requestSpamPayload(submission, requestContext) {
|
|
310
|
+
const userIp =
|
|
311
|
+
requestContext && typeof requestContext.userIp === "string"
|
|
312
|
+
? requestContext.userIp.trim()
|
|
313
|
+
: "";
|
|
314
|
+
if (userIp === "") {
|
|
315
|
+
throw new ServiceError(
|
|
316
|
+
"service_context_unavailable",
|
|
317
|
+
"The runtime did not receive a trusted visitor IP for this spam check.",
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
// Rebuild the network fields from host-owned context even when untyped
|
|
321
|
+
// JavaScript smuggles same-named properties into the author submission.
|
|
322
|
+
const payload = spamPayload({
|
|
323
|
+
...submission,
|
|
324
|
+
userIp,
|
|
325
|
+
userAgent: undefined,
|
|
326
|
+
referrer: undefined,
|
|
327
|
+
permalink: undefined,
|
|
328
|
+
});
|
|
329
|
+
for (const field of ["userAgent", "referrer", "permalink"]) {
|
|
330
|
+
if (typeof requestContext[field] === "string" && requestContext[field] !== "") {
|
|
331
|
+
payload[field] = requestContext[field];
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return payload;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function makeSpam(call, requestContext) {
|
|
338
|
+
return Object.freeze({
|
|
339
|
+
async check(submission) {
|
|
340
|
+
const payload =
|
|
341
|
+
requestContext === undefined
|
|
342
|
+
? spamPayload(submission)
|
|
343
|
+
: requestSpamPayload(submission, requestContext);
|
|
344
|
+
const result = await call("spam", "check", payload);
|
|
345
|
+
return Object.freeze({ spam: result.spam === true, discard: result.discard === true });
|
|
346
|
+
},
|
|
347
|
+
async reportSpam(submission) {
|
|
348
|
+
await call("spam", "report_spam", spamPayload(submission));
|
|
349
|
+
},
|
|
350
|
+
async reportHam(submission) {
|
|
351
|
+
await call("spam", "report_ham", spamPayload(submission));
|
|
352
|
+
},
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function emailAddress(value, field) {
|
|
357
|
+
if (typeof value === "string") {
|
|
358
|
+
if (value.trim() === "") throw invalid(field + " must not be empty.");
|
|
359
|
+
return { email: value.trim() };
|
|
360
|
+
}
|
|
361
|
+
if (value && typeof value === "object" && typeof value.email === "string" && value.email.trim() !== "") {
|
|
362
|
+
const address = { email: value.email.trim() };
|
|
363
|
+
if (typeof value.name === "string" && value.name !== "") address.name = value.name;
|
|
364
|
+
return address;
|
|
365
|
+
}
|
|
366
|
+
throw invalid(field + " must be an address or an { email, name } object.");
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function emailAddressList(value, field) {
|
|
370
|
+
const entries = Array.isArray(value) ? value : [value];
|
|
371
|
+
if (entries.length === 0) throw invalid(field + " must name at least one recipient.");
|
|
372
|
+
return entries.map((entry) => emailAddress(entry, field));
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function emailPayload(message) {
|
|
376
|
+
if (!message || typeof message !== "object") {
|
|
377
|
+
throw invalid("A message object is required.");
|
|
378
|
+
}
|
|
379
|
+
if (typeof message.subject !== "string" || message.subject === "") {
|
|
380
|
+
throw invalid("Message subject is required.");
|
|
381
|
+
}
|
|
382
|
+
const hasText = typeof message.text === "string" && message.text !== "";
|
|
383
|
+
const hasHtml = typeof message.html === "string" && message.html !== "";
|
|
384
|
+
if (!hasText && !hasHtml) {
|
|
385
|
+
throw invalid("Message must carry text, html, or both.");
|
|
386
|
+
}
|
|
387
|
+
const payload = {
|
|
388
|
+
from: emailAddress(message.from, "from"),
|
|
389
|
+
to: emailAddressList(message.to, "to"),
|
|
390
|
+
subject: message.subject,
|
|
391
|
+
};
|
|
392
|
+
if (hasText) payload.text = message.text;
|
|
393
|
+
if (hasHtml) payload.html = message.html;
|
|
394
|
+
if (message.cc !== undefined) payload.cc = emailAddressList(message.cc, "cc");
|
|
395
|
+
if (message.bcc !== undefined) payload.bcc = emailAddressList(message.bcc, "bcc");
|
|
396
|
+
if (message.replyTo !== undefined) payload.replyTo = emailAddress(message.replyTo, "replyTo");
|
|
397
|
+
if (message.headers !== undefined) {
|
|
398
|
+
if (!message.headers || typeof message.headers !== "object") {
|
|
399
|
+
throw invalid("Message headers must be an object.");
|
|
400
|
+
}
|
|
401
|
+
const headers = {};
|
|
402
|
+
for (const name of Object.keys(message.headers)) {
|
|
403
|
+
const value = message.headers[name];
|
|
404
|
+
if (typeof value !== "string") throw invalid("Header " + name + " must be a string.");
|
|
405
|
+
// Refused here rather than at the broker so the author sees the field
|
|
406
|
+
// name they wrote. The broker refuses again — this is convenience, not
|
|
407
|
+
// the boundary.
|
|
408
|
+
if (/[^\x21-\x39\x3b-\x7e]/.test(name)) {
|
|
409
|
+
throw invalid("Header " + name + " is not a valid header name.");
|
|
410
|
+
}
|
|
411
|
+
if (/[\r\n\x00]/.test(value)) {
|
|
412
|
+
throw invalid("Header " + name + " must not contain line breaks or NUL.");
|
|
413
|
+
}
|
|
414
|
+
headers[name] = value;
|
|
415
|
+
}
|
|
416
|
+
payload.headers = headers;
|
|
417
|
+
}
|
|
418
|
+
return payload;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function makeEmail(call) {
|
|
422
|
+
return Object.freeze({
|
|
423
|
+
async send(message) {
|
|
424
|
+
const result = await call("email", "send", emailPayload(message));
|
|
425
|
+
return Object.freeze({ messageId: result.messageId });
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function makeRuntimeServices(call, requestContext) {
|
|
431
|
+
return {
|
|
432
|
+
gravatar: makeGravatar(call),
|
|
433
|
+
spam: makeSpam(call, requestContext),
|
|
434
|
+
email: makeEmail(call),
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
`;
|
|
@@ -1,79 +1,70 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
|
|
3
|
-
public: "public";
|
|
4
|
-
private: "private";
|
|
5
|
-
}>;
|
|
6
|
-
export type RuntimeStorageVisibility = z.infer<typeof runtimeStorageVisibilitySchema>;
|
|
2
|
+
/** Objects are addressed by a bare 128-bit random id — there is no key and no visibility. */
|
|
7
3
|
export declare const runtimeStorageObjectIdSchema: z.ZodString;
|
|
8
|
-
|
|
4
|
+
/** The runtime-scoped, revocable read key that makes a `/__stattic/u/<id>` URL readable. */
|
|
5
|
+
export declare const runtimeStorageReadKeySchema: z.ZodString;
|
|
6
|
+
export type UploadedObject = {
|
|
7
|
+
id: string;
|
|
8
|
+
contentType: string;
|
|
9
|
+
size: number;
|
|
10
|
+
url: string;
|
|
11
|
+
};
|
|
12
|
+
export type StorageClient = {
|
|
13
|
+
upload(file: Blob): Promise<UploadedObject>;
|
|
14
|
+
get(id: string): Promise<Response | null>;
|
|
15
|
+
delete(id: string): Promise<void>;
|
|
16
|
+
};
|
|
9
17
|
export declare const runtimeStorageListQuerySchema: z.ZodObject<{
|
|
10
18
|
cursor: z.ZodOptional<z.ZodString>;
|
|
11
19
|
limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
12
|
-
visibility: z.ZodOptional<z.ZodEnum<{
|
|
13
|
-
public: "public";
|
|
14
|
-
private: "private";
|
|
15
|
-
}>>;
|
|
16
20
|
}, z.core.$strict>;
|
|
17
21
|
export declare const runtimeStorageObjectSchema: z.ZodObject<{
|
|
18
|
-
|
|
19
|
-
visibility: z.ZodEnum<{
|
|
20
|
-
public: "public";
|
|
21
|
-
private: "private";
|
|
22
|
-
}>;
|
|
22
|
+
id: z.ZodString;
|
|
23
23
|
contentType: z.ZodString;
|
|
24
24
|
createdAt: z.ZodString;
|
|
25
25
|
size: z.ZodNumber;
|
|
26
26
|
uploaderId: z.ZodString;
|
|
27
27
|
sha256: z.ZodString;
|
|
28
|
+
url: z.ZodString;
|
|
28
29
|
}, z.core.$strict>;
|
|
29
30
|
export type RuntimeStorageObject = z.infer<typeof runtimeStorageObjectSchema>;
|
|
30
31
|
export declare const runtimeStorageListResponseSchema: z.ZodObject<{
|
|
31
32
|
objects: z.ZodArray<z.ZodObject<{
|
|
32
|
-
|
|
33
|
-
visibility: z.ZodEnum<{
|
|
34
|
-
public: "public";
|
|
35
|
-
private: "private";
|
|
36
|
-
}>;
|
|
33
|
+
id: z.ZodString;
|
|
37
34
|
contentType: z.ZodString;
|
|
38
35
|
createdAt: z.ZodString;
|
|
39
36
|
size: z.ZodNumber;
|
|
40
37
|
uploaderId: z.ZodString;
|
|
41
38
|
sha256: z.ZodString;
|
|
39
|
+
url: z.ZodString;
|
|
42
40
|
}, z.core.$strict>>;
|
|
43
41
|
cursor: z.ZodNullable<z.ZodString>;
|
|
44
42
|
hasMore: z.ZodBoolean;
|
|
45
|
-
usedBytes: z.ZodNumber;
|
|
46
|
-
quotaBytes: z.ZodNumber;
|
|
47
43
|
}, z.core.$strict>;
|
|
48
44
|
export type RuntimeStorageListResponse = z.infer<typeof runtimeStorageListResponseSchema>;
|
|
49
45
|
export declare const runtimeStorageListPublicResponseSchema: z.ZodObject<{
|
|
50
46
|
data: z.ZodObject<{
|
|
51
47
|
objects: z.ZodArray<z.ZodObject<{
|
|
52
|
-
|
|
53
|
-
visibility: z.ZodEnum<{
|
|
54
|
-
public: "public";
|
|
55
|
-
private: "private";
|
|
56
|
-
}>;
|
|
48
|
+
id: z.ZodString;
|
|
57
49
|
contentType: z.ZodString;
|
|
58
50
|
createdAt: z.ZodString;
|
|
59
51
|
size: z.ZodNumber;
|
|
60
52
|
uploaderId: z.ZodString;
|
|
61
53
|
sha256: z.ZodString;
|
|
54
|
+
url: z.ZodString;
|
|
62
55
|
}, z.core.$strict>>;
|
|
63
56
|
cursor: z.ZodNullable<z.ZodString>;
|
|
64
57
|
hasMore: z.ZodBoolean;
|
|
65
|
-
usedBytes: z.ZodNumber;
|
|
66
|
-
quotaBytes: z.ZodNumber;
|
|
67
58
|
}, z.core.$strict>;
|
|
68
59
|
}, z.core.$strip>;
|
|
69
60
|
export declare const runtimeStorageDeleteResponseSchema: z.ZodObject<{
|
|
70
|
-
|
|
61
|
+
id: z.ZodString;
|
|
71
62
|
deleted: z.ZodBoolean;
|
|
72
63
|
}, z.core.$strict>;
|
|
73
64
|
export type RuntimeStorageDeleteResponse = z.infer<typeof runtimeStorageDeleteResponseSchema>;
|
|
74
65
|
export declare const runtimeStorageDeletePublicResponseSchema: z.ZodObject<{
|
|
75
66
|
data: z.ZodObject<{
|
|
76
|
-
|
|
67
|
+
id: z.ZodString;
|
|
77
68
|
deleted: z.ZodBoolean;
|
|
78
69
|
}, z.core.$strict>;
|
|
79
70
|
operation: z.ZodNullable<z.ZodObject<{
|
|
@@ -99,8 +90,8 @@ export declare const runtimeStorageDeletePublicResponseSchema: z.ZodObject<{
|
|
|
99
90
|
code: z.ZodString;
|
|
100
91
|
severity: z.ZodEnum<{
|
|
101
92
|
info: "info";
|
|
102
|
-
error: "error";
|
|
103
93
|
warning: "warning";
|
|
94
|
+
error: "error";
|
|
104
95
|
}>;
|
|
105
96
|
message: z.ZodString;
|
|
106
97
|
path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -118,3 +109,25 @@ export declare const runtimeStorageDeletePublicResponseSchema: z.ZodObject<{
|
|
|
118
109
|
finishedAt: z.ZodOptional<z.ZodString>;
|
|
119
110
|
}, z.core.$strip>>;
|
|
120
111
|
}, z.core.$strip>;
|
|
112
|
+
export declare const runtimeStorageReadKeyResponseSchema: z.ZodObject<{
|
|
113
|
+
key: z.ZodString;
|
|
114
|
+
}, z.core.$strict>;
|
|
115
|
+
export type RuntimeStorageReadKeyResponse = z.infer<typeof runtimeStorageReadKeyResponseSchema>;
|
|
116
|
+
export declare const runtimeStorageReadKeyRotateResponseSchema: z.ZodObject<{
|
|
117
|
+
key: z.ZodString;
|
|
118
|
+
rotatedAt: z.ZodString;
|
|
119
|
+
purge: z.ZodObject<{
|
|
120
|
+
status: z.ZodEnum<{
|
|
121
|
+
failed: "failed";
|
|
122
|
+
pending: "pending";
|
|
123
|
+
ok: "ok";
|
|
124
|
+
}>;
|
|
125
|
+
mode: z.ZodEnum<{
|
|
126
|
+
domain: "domain";
|
|
127
|
+
none: "none";
|
|
128
|
+
urls: "urls";
|
|
129
|
+
}>;
|
|
130
|
+
urls: z.ZodOptional<z.ZodNumber>;
|
|
131
|
+
}, z.core.$strict>;
|
|
132
|
+
}, z.core.$strict>;
|
|
133
|
+
export type RuntimeStorageReadKeyRotateResponse = z.infer<typeof runtimeStorageReadKeyRotateResponseSchema>;
|
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { dataEnvelope, mutationEnvelope } from "./common.js";
|
|
3
|
-
|
|
3
|
+
/** Objects are addressed by a bare 128-bit random id — there is no key and no visibility. */
|
|
4
4
|
export const runtimeStorageObjectIdSchema = z.string().regex(/^[a-f0-9]{32}$/);
|
|
5
|
-
|
|
5
|
+
/** The runtime-scoped, revocable read key that makes a `/__stattic/u/<id>` URL readable. */
|
|
6
|
+
export const runtimeStorageReadKeySchema = z.string().regex(/^[a-f0-9]{32}$/);
|
|
6
7
|
export const runtimeStorageListQuerySchema = z
|
|
7
8
|
.object({
|
|
8
9
|
cursor: z.string().min(1).max(2_048).optional(),
|
|
9
10
|
limit: z.coerce.number().int().min(1).max(100).default(50),
|
|
10
|
-
visibility: runtimeStorageVisibilitySchema.optional(),
|
|
11
11
|
})
|
|
12
12
|
.strict();
|
|
13
13
|
export const runtimeStorageObjectSchema = z
|
|
14
14
|
.object({
|
|
15
|
-
|
|
16
|
-
visibility: runtimeStorageVisibilitySchema,
|
|
15
|
+
id: runtimeStorageObjectIdSchema,
|
|
17
16
|
contentType: z.string().min(1).max(255),
|
|
18
17
|
createdAt: z.string().datetime(),
|
|
19
18
|
size: z.number().int().nonnegative(),
|
|
20
19
|
uploaderId: z.string().min(1).max(255),
|
|
21
20
|
sha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
21
|
+
// Path-relative and composed from the read key that was current at response
|
|
22
|
+
// time: `/__stattic/u/<id>?k=<key>`. The caller absolutizes it against
|
|
23
|
+
// whichever of the space's hostnames it is presenting.
|
|
24
|
+
url: z.string().min(1),
|
|
22
25
|
})
|
|
23
26
|
.strict();
|
|
24
27
|
export const runtimeStorageListResponseSchema = z
|
|
@@ -26,15 +29,34 @@ export const runtimeStorageListResponseSchema = z
|
|
|
26
29
|
objects: z.array(runtimeStorageObjectSchema),
|
|
27
30
|
cursor: z.string().nullable(),
|
|
28
31
|
hasMore: z.boolean(),
|
|
29
|
-
usedBytes: z.number().int().nonnegative(),
|
|
30
|
-
quotaBytes: z.number().int().positive(),
|
|
31
32
|
})
|
|
32
33
|
.strict();
|
|
33
34
|
export const runtimeStorageListPublicResponseSchema = dataEnvelope(runtimeStorageListResponseSchema);
|
|
34
35
|
export const runtimeStorageDeleteResponseSchema = z
|
|
35
36
|
.object({
|
|
36
|
-
|
|
37
|
+
id: runtimeStorageObjectIdSchema,
|
|
37
38
|
deleted: z.boolean(),
|
|
38
39
|
})
|
|
39
40
|
.strict();
|
|
40
41
|
export const runtimeStorageDeletePublicResponseSchema = mutationEnvelope(runtimeStorageDeleteResponseSchema);
|
|
42
|
+
export const runtimeStorageReadKeyResponseSchema = z
|
|
43
|
+
.object({ key: runtimeStorageReadKeySchema })
|
|
44
|
+
.strict();
|
|
45
|
+
// Rotation is the emergency lever: every URL minted under the old key answers
|
|
46
|
+
// 404 afterwards, so the runtime purges every hostname on the box and reports
|
|
47
|
+
// how that purge landed.
|
|
48
|
+
export const runtimeStorageReadKeyRotateResponseSchema = z
|
|
49
|
+
.object({
|
|
50
|
+
key: runtimeStorageReadKeySchema,
|
|
51
|
+
rotatedAt: z.string().datetime({ offset: true }),
|
|
52
|
+
// `none` is the receipt for a runtime that serves no hostname yet, so there
|
|
53
|
+
// was nothing to purge.
|
|
54
|
+
purge: z
|
|
55
|
+
.object({
|
|
56
|
+
status: z.enum(["ok", "failed", "pending"]),
|
|
57
|
+
mode: z.enum(["urls", "domain", "none"]),
|
|
58
|
+
urls: z.number().int().nonnegative().optional(),
|
|
59
|
+
})
|
|
60
|
+
.strict(),
|
|
61
|
+
})
|
|
62
|
+
.strict();
|