@monoflake/sdk 0.0.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/LICENSE +22 -0
- package/dist/artifacts/src/anchors.d.ts +34 -0
- package/dist/artifacts/src/anchors.js +64 -0
- package/dist/artifacts/src/api.d.ts +14 -0
- package/dist/artifacts/src/api.js +20 -0
- package/dist/artifacts/src/batch.d.ts +105 -0
- package/dist/artifacts/src/batch.js +81 -0
- package/dist/artifacts/src/engagement.d.ts +61 -0
- package/dist/artifacts/src/engagement.js +67 -0
- package/dist/artifacts/src/feed.d.ts +42 -0
- package/dist/artifacts/src/feed.js +89 -0
- package/dist/artifacts/src/index.d.ts +4224 -0
- package/dist/artifacts/src/index.js +219 -0
- package/dist/artifacts/src/picture.d.ts +116 -0
- package/dist/artifacts/src/picture.js +161 -0
- package/dist/artifacts/src/resource.d.ts +1391 -0
- package/dist/artifacts/src/resource.js +477 -0
- package/dist/artifacts/src/schema.d.ts +5 -0
- package/dist/artifacts/src/schema.js +18 -0
- package/dist/artifacts/src/types.d.ts +396 -0
- package/dist/artifacts/src/types.js +0 -0
- package/dist/cache/src/index.d.ts +67 -0
- package/dist/cache/src/index.js +58 -0
- package/dist/imgsrc/src/index.d.ts +16 -0
- package/dist/imgsrc/src/index.js +89 -0
- package/dist/limits/src/bucket.d.ts +28 -0
- package/dist/limits/src/bucket.js +23 -0
- package/dist/limits/src/index.d.ts +29 -0
- package/dist/limits/src/index.js +62 -0
- package/dist/limits/src/key.d.ts +37 -0
- package/dist/limits/src/key.js +64 -0
- package/dist/robots/src/index.d.ts +98 -0
- package/dist/robots/src/index.js +196 -0
- package/dist/security/src/agents.d.ts +10 -0
- package/dist/security/src/agents.js +95 -0
- package/dist/security/src/index.d.ts +12 -0
- package/dist/security/src/index.js +37 -0
- package/dist/src/index.d.ts +218 -0
- package/dist/src/index.js +219 -0
- package/dist/store/src/index.d.ts +92 -0
- package/dist/store/src/index.js +264 -0
- package/dist/symlink/src/index.d.ts +24 -0
- package/dist/symlink/src/index.js +89 -0
- package/package.json +85 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { GITHUB_OWNER, LOOPBACK_HOST, Normalized, PORT_OFFSET, isDevHost, loopbackUrl, normalizePath, normalizedLocation } from "@canmi/me/urls";
|
|
2
|
+
//#region src/index.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* The repositories whose deploy runs the hook passes to the nodes, as GitHub names them: the two
|
|
5
|
+
* whose apps a node runs. Each node holds its own list too, in `DEPLOY_SOURCES`, and that one
|
|
6
|
+
* decides. See infra's spec/architecture/host.md, "The machine pulls; nothing pushes into it".
|
|
7
|
+
*/
|
|
8
|
+
export declare const DEPLOY_SOURCES: readonly string[];
|
|
9
|
+
/**
|
|
10
|
+
* The ports each app answers on in development.
|
|
11
|
+
*
|
|
12
|
+
* Pinned, and bound by exactly one checkout at a time. The gaps are the inspector ports, which
|
|
13
|
+
* wrangler takes as port + 1, and they keep clear of LOCAL_PORT (mise.toml). A second copy of an
|
|
14
|
+
* app collides here rather than drifting to a free port, which is the cheapest mutex there is.
|
|
15
|
+
* See spec/toolchain.md.
|
|
16
|
+
*/
|
|
17
|
+
export declare const PINNED_PORTS: {
|
|
18
|
+
readonly site: 26511;
|
|
19
|
+
readonly api: 26512;
|
|
20
|
+
readonly alias: 26514;
|
|
21
|
+
readonly cdn: 26516;
|
|
22
|
+
readonly panel: 26519;
|
|
23
|
+
};
|
|
24
|
+
/** Workers reached by binding alone: a port that is the mutex, and no address. */
|
|
25
|
+
export declare const BOUND_PORTS: {
|
|
26
|
+
readonly quota: 26523;
|
|
27
|
+
};
|
|
28
|
+
/** The ports this checkout's servers bind: the pinned ones, shifted in the sandbox. */
|
|
29
|
+
export declare const DEVELOPMENT_PORTS: { readonly [App in keyof typeof PINNED_PORTS | keyof typeof BOUND_PORTS]: number; };
|
|
30
|
+
export type AppName = keyof typeof PINNED_PORTS;
|
|
31
|
+
export type DevelopmentUrls = Readonly<Record<AppName | 'symlink', string>>;
|
|
32
|
+
/**
|
|
33
|
+
* Where the alias layer and the CDN are reached *from a page* in development: through the site.
|
|
34
|
+
*
|
|
35
|
+
* A page carries no host of its own for either prefix -- see spec/toolchain.md, "They bind
|
|
36
|
+
* every interface, and the other two are reached through the site", for why that is what
|
|
37
|
+
* makes the site work from a phone on the same network. The site's API needs no proxy: the
|
|
38
|
+
* site's Worker answers it under `/api/` itself.
|
|
39
|
+
*/
|
|
40
|
+
export declare const DEVELOPMENT_PROXY_PATHS: {
|
|
41
|
+
readonly alias: '/alias';
|
|
42
|
+
readonly symlink: '/symlink';
|
|
43
|
+
readonly cdn: '/cdn';
|
|
44
|
+
};
|
|
45
|
+
export declare function developmentUrl(app: AppName): string;
|
|
46
|
+
/**
|
|
47
|
+
* Every app's address in development. The service layer is reached through the development gateway,
|
|
48
|
+
* as it is in production, so its CORS and its lifetimes hold here too; the gateway has no short
|
|
49
|
+
* hosts here, so each is a service of the API host, at the version its short host pins. See
|
|
50
|
+
* spec/architecture/gateway.md and spec/architecture/services.md, "Development goes through the
|
|
51
|
+
* gateway too".
|
|
52
|
+
*/
|
|
53
|
+
export declare function developmentUrls(): DevelopmentUrls;
|
|
54
|
+
export declare const URLS: {
|
|
55
|
+
readonly apps: {
|
|
56
|
+
readonly development: Readonly<Record<"alias" | "api" | "cdn" | "panel" | "site" | "symlink", string>>;
|
|
57
|
+
readonly production: {
|
|
58
|
+
readonly site: "https://canmi.net";
|
|
59
|
+
readonly api: "https://api.monoflake.com/v1/site";
|
|
60
|
+
readonly alias: "https://ill.li";
|
|
61
|
+
readonly symlink: 'https://symlink.si';
|
|
62
|
+
readonly cdn: 'https://cdn.monoflake.com';
|
|
63
|
+
readonly panel: "https://infra.internal.ixc.one";
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
readonly source: "https://github.com/canmi21/web";
|
|
67
|
+
readonly internal: {
|
|
68
|
+
readonly panel: 'https://infra.internal.ixc.one';
|
|
69
|
+
readonly keeper: 'https://keeper.internal.ixc.one';
|
|
70
|
+
readonly host: 'http://host:11011';
|
|
71
|
+
readonly app: 'https://canmi.app';
|
|
72
|
+
readonly infra: 'https://ffoni.com';
|
|
73
|
+
readonly alias: 'https://ill.li';
|
|
74
|
+
readonly ledger: "https://api.internal.ixc.one/ledger";
|
|
75
|
+
readonly cron: "https://api.internal.ixc.one/cron";
|
|
76
|
+
readonly shot: "https://api.monoflake.com/v1/shot";
|
|
77
|
+
readonly api: {
|
|
78
|
+
readonly private: 'https://api.internal.ixc.one';
|
|
79
|
+
readonly public: 'https://api.monoflake.com';
|
|
80
|
+
};
|
|
81
|
+
readonly status: {
|
|
82
|
+
readonly canonical: 'https://status.canmi.app';
|
|
83
|
+
readonly mirror: 'https://canmi.vercel.app';
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
readonly contact: {
|
|
87
|
+
readonly security: 'mailto:security@canmi.net';
|
|
88
|
+
};
|
|
89
|
+
readonly external: {
|
|
90
|
+
readonly github: {
|
|
91
|
+
readonly web: 'https://github.com';
|
|
92
|
+
readonly api: 'https://api.github.com';
|
|
93
|
+
readonly raw: 'https://raw.githubusercontent.com';
|
|
94
|
+
readonly avatars: 'https://avatars.githubusercontent.com';
|
|
95
|
+
readonly cdn: 'https://cdn.jsdelivr.net/gh';
|
|
96
|
+
};
|
|
97
|
+
readonly google: {
|
|
98
|
+
readonly sourcePreferences: 'https://www.google.com/preferences/source';
|
|
99
|
+
};
|
|
100
|
+
readonly registries: {
|
|
101
|
+
readonly npm: 'https://www.npmjs.com';
|
|
102
|
+
readonly cargo: 'https://crates.io';
|
|
103
|
+
readonly cargoIndex: 'https://index.crates.io';
|
|
104
|
+
};
|
|
105
|
+
readonly spdx: 'https://spdx.org/licenses';
|
|
106
|
+
readonly robotstxt: 'https://www.robotstxt.org/robotstxt.html';
|
|
107
|
+
readonly contentSignals: 'https://contentsignals.org';
|
|
108
|
+
readonly contentUsage: 'https://datatracker.ietf.org/doc/draft-ietf-aipref-attach/';
|
|
109
|
+
readonly agentIncident: 'https://openai.com/index/hugging-face-incident-and-the-road-ahead/';
|
|
110
|
+
readonly sentry: {
|
|
111
|
+
readonly site: 'https://a7f2f790ed2fa4f8e0c4310d26d9c39f@o4511131162116096.ingest.us.sentry.io/4511380121976832';
|
|
112
|
+
readonly status: string | undefined;
|
|
113
|
+
};
|
|
114
|
+
readonly feedsmith: 'https://feedsmith.dev';
|
|
115
|
+
readonly indexnow: 'https://api.indexnow.org/IndexNow';
|
|
116
|
+
readonly social: {
|
|
117
|
+
readonly telegram: 'https://t.me';
|
|
118
|
+
readonly twitter: 'https://twitter.com';
|
|
119
|
+
readonly twitterIntent: 'https://twitter.com/intent/follow';
|
|
120
|
+
readonly fediverse: 'https://nya.one';
|
|
121
|
+
readonly bluesky: 'https://bsky.app/profile';
|
|
122
|
+
};
|
|
123
|
+
readonly rust: {
|
|
124
|
+
readonly docs: 'https://docs.rs';
|
|
125
|
+
readonly lib: 'https://lib.rs';
|
|
126
|
+
};
|
|
127
|
+
readonly webring: {
|
|
128
|
+
readonly travellings: 'https://www.travellings.cn/go.html';
|
|
129
|
+
readonly moe: 'https://travel.moe/go?travel=on';
|
|
130
|
+
};
|
|
131
|
+
readonly icpmoe: 'https://icp.gov.moe';
|
|
132
|
+
readonly umami: 'https://cloud.umami.is/script.js';
|
|
133
|
+
readonly umamiGateway: 'https://gateway.umami.is';
|
|
134
|
+
readonly openpanel: 'https://api.openpanel.dev';
|
|
135
|
+
readonly googleFonts: {
|
|
136
|
+
readonly css: 'https://fonts.googleapis.com';
|
|
137
|
+
readonly static: 'https://fonts.gstatic.com';
|
|
138
|
+
};
|
|
139
|
+
readonly doh: {
|
|
140
|
+
readonly cloudflare: 'https://1.1.1.1/dns-query';
|
|
141
|
+
readonly google: 'https://8.8.8.8/resolve';
|
|
142
|
+
};
|
|
143
|
+
readonly geolite: {
|
|
144
|
+
readonly city: 'https://github.com/P3TERX/GeoLite.mmdb/releases/latest/download/GeoLite2-City.mmdb';
|
|
145
|
+
readonly asn: 'https://github.com/P3TERX/GeoLite.mmdb/releases/latest/download/GeoLite2-ASN.mmdb';
|
|
146
|
+
readonly maxmind: 'https://www.maxmind.com';
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* The service layer's hostnames, every one bound to the gateway, and the codes a deployment's own
|
|
152
|
+
* hostname is spelled from: `{service}-{region}-{provider}` under `deployments`. A provider or a
|
|
153
|
+
* region is added here before anything is placed on it. See spec/architecture/gateway.md.
|
|
154
|
+
*/
|
|
155
|
+
export declare const GATEWAY: {
|
|
156
|
+
readonly domains: readonly ['monoflake.com', 'monoflake.net'];
|
|
157
|
+
readonly api: 'api.monoflake.com';
|
|
158
|
+
readonly cdn: 'cdn.monoflake.com';
|
|
159
|
+
readonly deployments: 'ixc.one';
|
|
160
|
+
readonly alias: 'ill.li';
|
|
161
|
+
readonly symlink: 'symlink.si';
|
|
162
|
+
readonly retired: {
|
|
163
|
+
readonly api: 'api.ffoni.com';
|
|
164
|
+
readonly cdn: 'cdn.ffoni.com';
|
|
165
|
+
};
|
|
166
|
+
readonly providers: {
|
|
167
|
+
readonly int: 'our own machines';
|
|
168
|
+
readonly cf: 'Cloudflare';
|
|
169
|
+
readonly vcl: 'Vercel';
|
|
170
|
+
};
|
|
171
|
+
readonly regions: {
|
|
172
|
+
readonly rdu: 'the machine at home, by Raleigh-Durham';
|
|
173
|
+
readonly glo: 'everywhere, as a Worker runs';
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Every hostname the gateway answers at home, as a certificate and a router name them: a wildcard
|
|
178
|
+
* over each zone it owns below the apex, and the two apexes it is. The retired hosts are left out:
|
|
179
|
+
* the house never asks them. See infra's spec/architecture/host.md, "The inside side answers the
|
|
180
|
+
* internal gateway alone".
|
|
181
|
+
*/
|
|
182
|
+
export declare const GATEWAY_HOSTS: readonly string[];
|
|
183
|
+
/**
|
|
184
|
+
* The names the gateway answers at home, exactly, as the house's resolver answers them: the API and
|
|
185
|
+
* CDN hosts of each domain and the two apexes, and a deployment's own name, read as the profiles
|
|
186
|
+
* read it, from the registered regions and providers. Nothing else in those zones is the gateway's,
|
|
187
|
+
* so nothing else is answered with the node. See infra's spec/architecture/host.md, "The resolver
|
|
188
|
+
* answers the gateway's names, and passes the rest on".
|
|
189
|
+
*/
|
|
190
|
+
export declare const GATEWAY_NAMES: {
|
|
191
|
+
readonly exact: readonly [...string[], "ill.li", "symlink.si"];
|
|
192
|
+
readonly deployments: {
|
|
193
|
+
readonly zone: "ixc.one";
|
|
194
|
+
readonly regions: string[];
|
|
195
|
+
readonly providers: string[];
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
/**
|
|
199
|
+
* Where each consumer's pages are served, by its service code: what a declaration's `cors.origins`
|
|
200
|
+
* names, so no `service.toml` spells an origin. The status page has three doors, the platform's
|
|
201
|
+
* own among them. See spec/architecture/gateway.md, "A route names who may call it by service
|
|
202
|
+
* code".
|
|
203
|
+
*/
|
|
204
|
+
export declare const PAGE_ORIGINS: Readonly<Record<string, readonly string[]>>;
|
|
205
|
+
export type UrlEnvironment = keyof typeof URLS.apps;
|
|
206
|
+
export type UrlMap = (typeof URLS.apps)[UrlEnvironment];
|
|
207
|
+
export declare function pickUrls(isDev: boolean): UrlMap;
|
|
208
|
+
/**
|
|
209
|
+
* The same map as `pickUrls`, as a page served by the site should ask for it.
|
|
210
|
+
*
|
|
211
|
+
* Two functions because two consumers want opposite things from the development entry: a
|
|
212
|
+
* worker wants origins it can put in a CORS list or a redirect, while a page wants paths, since
|
|
213
|
+
* the host it should ask is whichever one it was opened from and need not be `localhost`.
|
|
214
|
+
* Identical to `pickUrls` in production, where nothing is proxied.
|
|
215
|
+
*/
|
|
216
|
+
export declare function pageUrls(isDev: boolean): UrlMap;
|
|
217
|
+
//#endregion
|
|
218
|
+
export { GITHUB_OWNER, LOOPBACK_HOST, type Normalized, PORT_OFFSET, isDevHost, loopbackUrl, normalizePath, normalizedLocation };
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { CONTACT, EXTERNAL, GITHUB_OWNER, LOOPBACK_HOST, PORT_OFFSET, SITE, SITE_PORT, SOURCE, isDevHost, loopbackUrl, normalizePath, normalizedLocation } from "@canmi/me/urls";
|
|
2
|
+
import { INFRA, PANEL_PORT } from "@monoflake/urls";
|
|
3
|
+
//#region src/index.ts
|
|
4
|
+
/**
|
|
5
|
+
* The platform's addresses, and the whole map as everything above infra reads it: the platform's
|
|
6
|
+
* own declared here, the author's from `canmi` and infra's from `@monoflake/urls`, composed into
|
|
7
|
+
* one shape so a caller asks one place and the Rust mirror has one source. See
|
|
8
|
+
* spec/architecture/layers.md, "Addresses are split by who owns the name".
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* The repositories whose deploy runs the hook passes to the nodes, as GitHub names them: the two
|
|
12
|
+
* whose apps a node runs. Each node holds its own list too, in `DEPLOY_SOURCES`, and that one
|
|
13
|
+
* decides. See infra's spec/architecture/host.md, "The machine pulls; nothing pushes into it".
|
|
14
|
+
*/
|
|
15
|
+
const DEPLOY_SOURCES = ["monoflake/infra", "monoflake/platform"];
|
|
16
|
+
/**
|
|
17
|
+
* The ports each app answers on in development.
|
|
18
|
+
*
|
|
19
|
+
* Pinned, and bound by exactly one checkout at a time. The gaps are the inspector ports, which
|
|
20
|
+
* wrangler takes as port + 1, and they keep clear of LOCAL_PORT (mise.toml). A second copy of an
|
|
21
|
+
* app collides here rather than drifting to a free port, which is the cheapest mutex there is.
|
|
22
|
+
* See spec/toolchain.md.
|
|
23
|
+
*/
|
|
24
|
+
const PINNED_PORTS = {
|
|
25
|
+
site: SITE_PORT,
|
|
26
|
+
api: 26512,
|
|
27
|
+
alias: 26514,
|
|
28
|
+
cdn: 26516,
|
|
29
|
+
panel: PANEL_PORT
|
|
30
|
+
};
|
|
31
|
+
/** Workers reached by binding alone: a port that is the mutex, and no address. */
|
|
32
|
+
const BOUND_PORTS = { quota: 26523 };
|
|
33
|
+
/** The ports this checkout's servers bind: the pinned ones, shifted in the sandbox. */
|
|
34
|
+
const DEVELOPMENT_PORTS = Object.fromEntries(Object.entries({
|
|
35
|
+
...PINNED_PORTS,
|
|
36
|
+
...BOUND_PORTS
|
|
37
|
+
}).map(([app, port]) => [app, port + PORT_OFFSET]));
|
|
38
|
+
/** The site's scope of the API host, which is the service's name, `site`. */
|
|
39
|
+
const SITE_SCOPE = "site";
|
|
40
|
+
/**
|
|
41
|
+
* Where the alias layer and the CDN are reached *from a page* in development: through the site.
|
|
42
|
+
*
|
|
43
|
+
* A page carries no host of its own for either prefix -- see spec/toolchain.md, "They bind
|
|
44
|
+
* every interface, and the other two are reached through the site", for why that is what
|
|
45
|
+
* makes the site work from a phone on the same network. The site's API needs no proxy: the
|
|
46
|
+
* site's Worker answers it under `/api/` itself.
|
|
47
|
+
*/
|
|
48
|
+
const DEVELOPMENT_PROXY_PATHS = {
|
|
49
|
+
alias: "/alias",
|
|
50
|
+
symlink: "/symlink",
|
|
51
|
+
cdn: "/cdn"
|
|
52
|
+
};
|
|
53
|
+
function developmentUrl(app) {
|
|
54
|
+
return `http://localhost:${DEVELOPMENT_PORTS[app]}`;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Every app's address in development. The service layer is reached through the development gateway,
|
|
58
|
+
* as it is in production, so its CORS and its lifetimes hold here too; the gateway has no short
|
|
59
|
+
* hosts here, so each is a service of the API host, at the version its short host pins. See
|
|
60
|
+
* spec/architecture/gateway.md and spec/architecture/services.md, "Development goes through the
|
|
61
|
+
* gateway too".
|
|
62
|
+
*/
|
|
63
|
+
function developmentUrls() {
|
|
64
|
+
const gateway = developmentUrl("api");
|
|
65
|
+
return {
|
|
66
|
+
site: developmentUrl("site"),
|
|
67
|
+
api: `${gateway}/v1/${SITE_SCOPE}`,
|
|
68
|
+
alias: `${gateway}/v1/aka`,
|
|
69
|
+
symlink: `${gateway}/v1/aka/symlink`,
|
|
70
|
+
cdn: `${gateway}/v3/cdn`,
|
|
71
|
+
panel: developmentUrl("panel")
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const development = developmentUrls();
|
|
75
|
+
/** The API host's two sides: private, where every container asks, and public, past the gateway. */
|
|
76
|
+
const API = {
|
|
77
|
+
private: "https://api.internal.ixc.one",
|
|
78
|
+
public: "https://api.monoflake.com"
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* The domains owned here, which the production map below reads rather than spelling twice. `infra`
|
|
82
|
+
* is the retired apex api and cdn hung off; `alias` the alias layer's. `app` is the suffix every
|
|
83
|
+
* interface is on behind Access, see spec/architecture/services.md; `panel`, `keeper` and `host`
|
|
84
|
+
* are infra's, see infra's spec/architecture/host.md; `ledger` is where every service records its
|
|
85
|
+
* tasks, see ledger.md; `shot` is the public scope a capture's pictures are named under, see
|
|
86
|
+
* shot.md.
|
|
87
|
+
*/
|
|
88
|
+
const INTERNAL = {
|
|
89
|
+
app: "https://canmi.app",
|
|
90
|
+
infra: "https://ffoni.com",
|
|
91
|
+
alias: "https://ill.li",
|
|
92
|
+
...INFRA,
|
|
93
|
+
ledger: `${API.private}/ledger`,
|
|
94
|
+
cron: `${API.private}/cron`,
|
|
95
|
+
shot: `${API.public}/v1/shot`,
|
|
96
|
+
api: API,
|
|
97
|
+
status: {
|
|
98
|
+
canonical: "https://status.canmi.app",
|
|
99
|
+
mirror: "https://canmi.vercel.app"
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const URLS = {
|
|
103
|
+
apps: {
|
|
104
|
+
development,
|
|
105
|
+
production: {
|
|
106
|
+
site: SITE,
|
|
107
|
+
api: `${API.public}/v1/${SITE_SCOPE}`,
|
|
108
|
+
alias: INTERNAL.alias,
|
|
109
|
+
symlink: "https://symlink.si",
|
|
110
|
+
cdn: "https://cdn.monoflake.com",
|
|
111
|
+
panel: INTERNAL.panel
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
source: SOURCE,
|
|
115
|
+
internal: INTERNAL,
|
|
116
|
+
contact: CONTACT,
|
|
117
|
+
external: {
|
|
118
|
+
...EXTERNAL,
|
|
119
|
+
doh: {
|
|
120
|
+
cloudflare: "https://1.1.1.1/dns-query",
|
|
121
|
+
google: "https://8.8.8.8/resolve"
|
|
122
|
+
},
|
|
123
|
+
geolite: {
|
|
124
|
+
city: "https://github.com/P3TERX/GeoLite.mmdb/releases/latest/download/GeoLite2-City.mmdb",
|
|
125
|
+
asn: "https://github.com/P3TERX/GeoLite.mmdb/releases/latest/download/GeoLite2-ASN.mmdb",
|
|
126
|
+
maxmind: "https://www.maxmind.com"
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* The service layer's hostnames, every one bound to the gateway, and the codes a deployment's own
|
|
132
|
+
* hostname is spelled from: `{service}-{region}-{provider}` under `deployments`. A provider or a
|
|
133
|
+
* region is added here before anything is placed on it. See spec/architecture/gateway.md.
|
|
134
|
+
*/
|
|
135
|
+
const GATEWAY = {
|
|
136
|
+
domains: ["monoflake.com", "monoflake.net"],
|
|
137
|
+
api: "api.monoflake.com",
|
|
138
|
+
cdn: "cdn.monoflake.com",
|
|
139
|
+
deployments: "ixc.one",
|
|
140
|
+
alias: "ill.li",
|
|
141
|
+
symlink: "symlink.si",
|
|
142
|
+
retired: {
|
|
143
|
+
api: "api.ffoni.com",
|
|
144
|
+
cdn: "cdn.ffoni.com"
|
|
145
|
+
},
|
|
146
|
+
providers: {
|
|
147
|
+
int: "our own machines",
|
|
148
|
+
cf: "Cloudflare",
|
|
149
|
+
vcl: "Vercel"
|
|
150
|
+
},
|
|
151
|
+
regions: {
|
|
152
|
+
rdu: "the machine at home, by Raleigh-Durham",
|
|
153
|
+
glo: "everywhere, as a Worker runs"
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* Every hostname the gateway answers at home, as a certificate and a router name them: a wildcard
|
|
158
|
+
* over each zone it owns below the apex, and the two apexes it is. The retired hosts are left out:
|
|
159
|
+
* the house never asks them. See infra's spec/architecture/host.md, "The inside side answers the
|
|
160
|
+
* internal gateway alone".
|
|
161
|
+
*/
|
|
162
|
+
const GATEWAY_HOSTS = [
|
|
163
|
+
...GATEWAY.domains.map((domain) => `*.${domain}`),
|
|
164
|
+
`*.${GATEWAY.deployments}`,
|
|
165
|
+
GATEWAY.alias,
|
|
166
|
+
GATEWAY.symlink
|
|
167
|
+
];
|
|
168
|
+
/**
|
|
169
|
+
* The names the gateway answers at home, exactly, as the house's resolver answers them: the API and
|
|
170
|
+
* CDN hosts of each domain and the two apexes, and a deployment's own name, read as the profiles
|
|
171
|
+
* read it, from the registered regions and providers. Nothing else in those zones is the gateway's,
|
|
172
|
+
* so nothing else is answered with the node. See infra's spec/architecture/host.md, "The resolver
|
|
173
|
+
* answers the gateway's names, and passes the rest on".
|
|
174
|
+
*/
|
|
175
|
+
const GATEWAY_NAMES = {
|
|
176
|
+
exact: [
|
|
177
|
+
...GATEWAY.domains.flatMap((domain) => [`api.${domain}`, `cdn.${domain}`]),
|
|
178
|
+
GATEWAY.alias,
|
|
179
|
+
GATEWAY.symlink
|
|
180
|
+
],
|
|
181
|
+
deployments: {
|
|
182
|
+
zone: GATEWAY.deployments,
|
|
183
|
+
regions: Object.keys(GATEWAY.regions),
|
|
184
|
+
providers: Object.keys(GATEWAY.providers)
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
/**
|
|
188
|
+
* Where each consumer's pages are served, by its service code: what a declaration's `cors.origins`
|
|
189
|
+
* names, so no `service.toml` spells an origin. The status page has three doors, the platform's
|
|
190
|
+
* own among them. See spec/architecture/gateway.md, "A route names who may call it by service
|
|
191
|
+
* code".
|
|
192
|
+
*/
|
|
193
|
+
const PAGE_ORIGINS = {
|
|
194
|
+
site: [URLS.apps.production.site],
|
|
195
|
+
status: [
|
|
196
|
+
INTERNAL.status.canonical,
|
|
197
|
+
INTERNAL.status.mirror,
|
|
198
|
+
INTERNAL.app
|
|
199
|
+
]
|
|
200
|
+
};
|
|
201
|
+
function pickUrls(isDev) {
|
|
202
|
+
return isDev ? URLS.apps.development : URLS.apps.production;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* The same map as `pickUrls`, as a page served by the site should ask for it.
|
|
206
|
+
*
|
|
207
|
+
* Two functions because two consumers want opposite things from the development entry: a
|
|
208
|
+
* worker wants origins it can put in a CORS list or a redirect, while a page wants paths, since
|
|
209
|
+
* the host it should ask is whichever one it was opened from and need not be `localhost`.
|
|
210
|
+
* Identical to `pickUrls` in production, where nothing is proxied.
|
|
211
|
+
*/
|
|
212
|
+
function pageUrls(isDev) {
|
|
213
|
+
return isDev ? {
|
|
214
|
+
...URLS.apps.development,
|
|
215
|
+
...DEVELOPMENT_PROXY_PATHS
|
|
216
|
+
} : URLS.apps.production;
|
|
217
|
+
}
|
|
218
|
+
//#endregion
|
|
219
|
+
export { BOUND_PORTS, DEPLOY_SOURCES, DEVELOPMENT_PORTS, DEVELOPMENT_PROXY_PATHS, GATEWAY, GATEWAY_HOSTS, GATEWAY_NAMES, GITHUB_OWNER, LOOPBACK_HOST, PAGE_ORIGINS, PINNED_PORTS, PORT_OFFSET, URLS, developmentUrl, developmentUrls, isDevHost, loopbackUrl, normalizePath, normalizedLocation, pageUrls, pickUrls };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { recordKey, storageKey } from "../../artifacts/src/index.js";
|
|
2
|
+
import { Fetcher, R2Bucket } from "@cloudflare/workers-types";
|
|
3
|
+
//#region store/src/index.d.ts
|
|
4
|
+
export type Bindings = {
|
|
5
|
+
STORE?: R2Bucket;
|
|
6
|
+
/** Present only under `wrangler dev --assets`; see the dev task in mise.toml. */
|
|
7
|
+
ASSETS?: Fetcher;
|
|
8
|
+
};
|
|
9
|
+
export type Found = {
|
|
10
|
+
body: ReadableStream;
|
|
11
|
+
contentType: string;
|
|
12
|
+
etag?: string;
|
|
13
|
+
/** What was served, when a range was asked for and satisfied. Absent for a whole object. */
|
|
14
|
+
partial?: {
|
|
15
|
+
offset: number;
|
|
16
|
+
length: number;
|
|
17
|
+
total: number;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* A range that names nothing inside the object.
|
|
22
|
+
*
|
|
23
|
+
* Its own result rather than a null, because the two mean opposite things to a caller: a missing
|
|
24
|
+
* object is 404 and a range past the end of a present one is 416, and 416 has to report the size
|
|
25
|
+
* so the client can ask again. Answering 404 for the second would send a browser looking for a
|
|
26
|
+
* file it already found.
|
|
27
|
+
*/
|
|
28
|
+
export type Unsatisfiable = {
|
|
29
|
+
unsatisfiable: true;
|
|
30
|
+
total: number;
|
|
31
|
+
};
|
|
32
|
+
export declare function isUnsatisfiable(value: Found | Unsatisfiable | null): value is Unsatisfiable;
|
|
33
|
+
/**
|
|
34
|
+
* Read an object, or the part of one a `Range` header asks for.
|
|
35
|
+
*
|
|
36
|
+
* `range` is the header verbatim, parsed here in the one place with a grammar to obey -- see
|
|
37
|
+
* web's spec/architecture/data.md, "Assets are addressed by their content", for why the worker
|
|
38
|
+
* resolves it rather than the bucket, and what an unparseable or multipart request gets instead.
|
|
39
|
+
*/
|
|
40
|
+
export declare function read(env: Bindings, key: string): Promise<Found | null>;
|
|
41
|
+
export declare function read(env: Bindings, key: string, range: string | null | undefined): Promise<Found | Unsatisfiable | null>;
|
|
42
|
+
/** What a head reports. `uploaded` is null only where the store keeps no date; see `measure`. */
|
|
43
|
+
export type Measured = {
|
|
44
|
+
size: number;
|
|
45
|
+
uploaded: Date | null;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* What is known about an object under a key, without reading a byte of it.
|
|
49
|
+
*
|
|
50
|
+
* A caller that has to refuse work before paying for it needs the size first: an isolate gets
|
|
51
|
+
* 128 MB for its heap and its WebAssembly together, so a length that arrives alongside the bytes
|
|
52
|
+
* arrives too late to be a limit. Null is no such object, which makes this a presence check too.
|
|
53
|
+
* The date rides along because the same head already carries it, and a caller wanting both
|
|
54
|
+
* should not spend a second round trip on the second one.
|
|
55
|
+
*/
|
|
56
|
+
export declare function measure(env: Bindings, key: string): Promise<Measured | null>;
|
|
57
|
+
export declare function isContentId(value: string): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* A stored object as an HTTP response, with ETag only when the store supplied one.
|
|
60
|
+
*
|
|
61
|
+
* `Accept-Ranges` on every one of them, because every object here is served through a route that
|
|
62
|
+
* can answer a range -- a player seeking, a `<video>` element probing for duration, a resumed
|
|
63
|
+
* download. The header is what tells a client it may ask; without it a browser fetches whole
|
|
64
|
+
* files to read a byte near the end of them.
|
|
65
|
+
*/
|
|
66
|
+
export declare function toResponse(found: Found): Response;
|
|
67
|
+
/**
|
|
68
|
+
* The answer to a range that names nothing inside the object.
|
|
69
|
+
*
|
|
70
|
+
* 416 carries the size so the client can ask again knowing it, which is the whole reason this is
|
|
71
|
+
* not a 404: the object is there, the question was wrong.
|
|
72
|
+
*/
|
|
73
|
+
export declare function unsatisfiableResponse(total: number): Response;
|
|
74
|
+
/**
|
|
75
|
+
* Bytes already in hand, answered as a `Range` asked for them.
|
|
76
|
+
*
|
|
77
|
+
* For a body that was derived rather than stored: there is no object to ask the bucket for a part
|
|
78
|
+
* of, so the artifact is produced whole and the slice is taken here. The grammar stays in this
|
|
79
|
+
* module -- one parser, one set of spellings -- and a header it does not implement is served
|
|
80
|
+
* whole, which is what a recipient is allowed to do. See spec/architecture/delivery.md.
|
|
81
|
+
*/
|
|
82
|
+
export declare function rangedResponse(bytes: Uint8Array, contentType: string, range: string | null | undefined): Response;
|
|
83
|
+
/**
|
|
84
|
+
* Content type from the key, for objects stored without one.
|
|
85
|
+
*
|
|
86
|
+
* R2 keeps whatever `httpMetadata` was set at upload, and rclone does set it, but an object
|
|
87
|
+
* put by hand through the dashboard has none. Serving those as `application/octet-stream`
|
|
88
|
+
* makes a browser download a favicon instead of drawing it.
|
|
89
|
+
*/
|
|
90
|
+
export declare function contentTypeFor(key: string): string;
|
|
91
|
+
//#endregion
|
|
92
|
+
export { recordKey, storageKey };
|