@geekmidas/manifest 10.0.0-alpha.17 → 10.0.0-alpha.171
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +736 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1087 -71
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +1087 -71
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +683 -23
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -1
package/dist/index.cjs
CHANGED
|
@@ -33,6 +33,31 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
33
33
|
* this.
|
|
34
34
|
*/
|
|
35
35
|
const DEFAULT_POSTGRES_VERSION = 18;
|
|
36
|
+
/** Whether a declaration is built into a public bundle — a site or an app. */
|
|
37
|
+
function isPublicDeclaration(declaration) {
|
|
38
|
+
return declaration.kind === "site" || declaration.kind === "mobile-app";
|
|
39
|
+
}
|
|
40
|
+
/** A site's subdomain that means every subdomain — see `SiteDeclaration`. */
|
|
41
|
+
const WILDCARD_SUBDOMAIN = "*";
|
|
42
|
+
/**
|
|
43
|
+
* Whether a declaration is a site answering on every subdomain.
|
|
44
|
+
*/
|
|
45
|
+
function isWildcardSite(declaration) {
|
|
46
|
+
return declaration?.kind === "site" && "subdomain" in declaration && declaration.subdomain === "*";
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* What a `Telemetry` node provides to each process with an edge to it.
|
|
50
|
+
*
|
|
51
|
+
* `OTEL_SERVICE_NAME` is resolved per process — the app's name — so it is
|
|
52
|
+
* listed here but composed by whichever target hands it over.
|
|
53
|
+
*/
|
|
54
|
+
const TELEMETRY_KEYS = [
|
|
55
|
+
"OTEL_EXPORTER_OTLP_ENDPOINT",
|
|
56
|
+
"OTEL_EXPORTER_OTLP_HEADERS",
|
|
57
|
+
"OTEL_SERVICE_NAME",
|
|
58
|
+
"OTEL_TRACES_SAMPLER",
|
|
59
|
+
"OTEL_TRACES_SAMPLER_ARG"
|
|
60
|
+
];
|
|
36
61
|
/**
|
|
37
62
|
* What each kind may derive from.
|
|
38
63
|
*
|
|
@@ -55,23 +80,29 @@ const DERIVES_FROM = {
|
|
|
55
80
|
* can legitimately use any of them. A bucket's `url` presigns and stays private.
|
|
56
81
|
*/
|
|
57
82
|
const PUBLIC = {
|
|
58
|
-
objects: [],
|
|
83
|
+
objects: ["signedOrigin"],
|
|
59
84
|
"file-server": ["url"],
|
|
60
|
-
email: [],
|
|
85
|
+
email: ["inboxUrl"],
|
|
86
|
+
inbox: [],
|
|
61
87
|
database: [],
|
|
62
88
|
"database-reader": [],
|
|
63
89
|
"database-schema": [],
|
|
64
90
|
cache: [],
|
|
65
91
|
secret: [],
|
|
66
92
|
credential: [],
|
|
93
|
+
encryption: [],
|
|
67
94
|
"rest-api": ["url"],
|
|
95
|
+
"websocket-api": ["url"],
|
|
68
96
|
queue: [],
|
|
69
97
|
topic: [],
|
|
70
98
|
oidc: ["issuer", "audience"],
|
|
99
|
+
"external-api": [],
|
|
71
100
|
function: [],
|
|
72
101
|
cron: [],
|
|
102
|
+
"mobile-app": [],
|
|
73
103
|
site: ["url"],
|
|
74
|
-
worker: []
|
|
104
|
+
worker: [],
|
|
105
|
+
telemetry: []
|
|
75
106
|
};
|
|
76
107
|
//#endregion
|
|
77
108
|
//#region src/errors.ts
|
|
@@ -846,7 +877,33 @@ function cookieDomain(urls) {
|
|
|
846
877
|
* cannot catch anything.
|
|
847
878
|
*/
|
|
848
879
|
function providedKeyFor(id, kind, role) {
|
|
849
|
-
|
|
880
|
+
if (kind === "secret") return environmentCase(id);
|
|
881
|
+
if (kind === "telemetry") return "OTEL_EXPORTER_OTLP_ENDPOINT";
|
|
882
|
+
if (kind === "file-server") return provideKey(fileServerName(id), role);
|
|
883
|
+
return provideKey(id, role);
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* What a file server's surface node adds to the construct's id. The bucket
|
|
887
|
+
* keeps the plain id, so serving a bucket you already have adds a node rather
|
|
888
|
+
* than renaming the one holding the data.
|
|
889
|
+
*/
|
|
890
|
+
const FILE_SERVER_SUFFIX = "Server";
|
|
891
|
+
/**
|
|
892
|
+
* A file server's surface node id, from the construct's id.
|
|
893
|
+
*
|
|
894
|
+
* @example fileServerSurfaceId('Assets') // 'AssetsServer'
|
|
895
|
+
*/
|
|
896
|
+
function fileServerSurfaceId(id) {
|
|
897
|
+
return `${id}${FILE_SERVER_SUFFIX}`;
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* The construct's id, from its surface node's: what its key and its host are
|
|
901
|
+
* named after.
|
|
902
|
+
*
|
|
903
|
+
* @example fileServerName('AssetsServer') // 'Assets'
|
|
904
|
+
*/
|
|
905
|
+
function fileServerName(surfaceId) {
|
|
906
|
+
return surfaceId.endsWith("Server") && surfaceId.length > 6 ? surfaceId.slice(0, -6) : surfaceId;
|
|
850
907
|
}
|
|
851
908
|
//#endregion
|
|
852
909
|
//#region src/derive.ts
|
|
@@ -907,12 +964,17 @@ function provisionOrder(manifest) {
|
|
|
907
964
|
*/
|
|
908
965
|
function dependenciesOf(declaration) {
|
|
909
966
|
const own = "dependencies" in declaration ? declaration.dependencies ?? [] : [];
|
|
910
|
-
const
|
|
911
|
-
const nested = declaration.kind === "rest-api" ? declaration.endpoints.flatMap((endpoint) => endpoint.dependencies) : [];
|
|
967
|
+
const auth = (declaration.kind === "rest-api" || declaration.kind === "websocket-api") && declaration.auth ? [declaration.auth] : [];
|
|
968
|
+
const nested = declaration.kind === "rest-api" ? declaration.endpoints.flatMap((endpoint) => endpoint.dependencies) : declaration.kind === "websocket-api" ? (declaration.routes ?? []).flatMap((route) => route.dependencies) : [];
|
|
969
|
+
const telemetry = "telemetry" in declaration && typeof declaration.telemetry === "string" ? [{
|
|
970
|
+
target: declaration.telemetry,
|
|
971
|
+
kind: "telemetry"
|
|
972
|
+
}] : [];
|
|
912
973
|
return [
|
|
913
974
|
...own,
|
|
914
|
-
...
|
|
915
|
-
...nested
|
|
975
|
+
...auth,
|
|
976
|
+
...nested,
|
|
977
|
+
...telemetry
|
|
916
978
|
];
|
|
917
979
|
}
|
|
918
980
|
/**
|
|
@@ -946,11 +1008,69 @@ function dependentsOf(manifest, id) {
|
|
|
946
1008
|
const PUBLIC_PREFIX = {
|
|
947
1009
|
static: "VITE_",
|
|
948
1010
|
tanstack: "VITE_",
|
|
949
|
-
next: "NEXT_PUBLIC_"
|
|
1011
|
+
next: "NEXT_PUBLIC_",
|
|
1012
|
+
expo: "EXPO_PUBLIC_"
|
|
950
1013
|
};
|
|
951
1014
|
/**
|
|
1015
|
+
* Every public value a site's or a mobile app's edges reach — the one walk of
|
|
1016
|
+
* its edges that every target makes, so what a bundle inlines cannot differ
|
|
1017
|
+
* between `gkm dev`, a compose build and the AWS target.
|
|
1018
|
+
*
|
|
1019
|
+
* Each edge's `PUBLIC` roles, and, because a bucket and the file server over
|
|
1020
|
+
* it are one construct to a page:
|
|
1021
|
+
*
|
|
1022
|
+
* - an edge to a bucket (a full-stack site's `.dependsOn([assets.server])`)
|
|
1023
|
+
* also reaches each file server over it — its objects are shown from there;
|
|
1024
|
+
* - an edge to a file server also reaches its bucket's public values — where
|
|
1025
|
+
* the bucket signs the links to its private objects.
|
|
1026
|
+
*
|
|
1027
|
+
* A bucket's connection is never one: `PUBLIC.objects` does not list it.
|
|
1028
|
+
*/
|
|
1029
|
+
function publicValues(declaration, manifest) {
|
|
1030
|
+
const values = /* @__PURE__ */ new Map();
|
|
1031
|
+
const reach = (id) => {
|
|
1032
|
+
const target = manifest[id];
|
|
1033
|
+
if (!target) return;
|
|
1034
|
+
for (const role of PUBLIC[target.kind] ?? []) {
|
|
1035
|
+
const key = providedKeyFor(id, target.kind, role);
|
|
1036
|
+
values.set(key, {
|
|
1037
|
+
id,
|
|
1038
|
+
kind: target.kind,
|
|
1039
|
+
role,
|
|
1040
|
+
key
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
for (const edge of declaration.dependencies) {
|
|
1045
|
+
const target = manifest[edge.target];
|
|
1046
|
+
if (!target) continue;
|
|
1047
|
+
reach(edge.target);
|
|
1048
|
+
if (target.kind === "objects") {
|
|
1049
|
+
for (const [id, other] of Object.entries(manifest)) if (other.kind === "file-server" && other.of === edge.target) reach(id);
|
|
1050
|
+
}
|
|
1051
|
+
if (target.kind === "file-server") reach(target.of);
|
|
1052
|
+
}
|
|
1053
|
+
return [...values.values()];
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Whether an edge hands its caller what a server holds of the target — a
|
|
1057
|
+
* bucket's connection and the key pair that signs for it — or only the
|
|
1058
|
+
* target's public values.
|
|
1059
|
+
*
|
|
1060
|
+
* A server process's edge always does: it presigns and writes. A site's or a
|
|
1061
|
+
* mobile app's does only when it named the server half
|
|
1062
|
+
* (`.dependsOn([documents.server])`), for a server half that uploads or
|
|
1063
|
+
* presigns. Its plain edge to a bucket is to where the bucket's links are
|
|
1064
|
+
* signed (`<T>_SIGNED_ORIGIN`): a page that shows presigned links needs to
|
|
1065
|
+
* allow their host, and nothing that writes to the bucket.
|
|
1066
|
+
*/
|
|
1067
|
+
function reachesServerHalf(caller, edge) {
|
|
1068
|
+
return !isPublicDeclaration(caller) || edge.server === true;
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
952
1071
|
* The keys a site's bundle needs, mapped to the key each value comes from —
|
|
953
|
-
* `{ VITE_API_URL: 'API_URL' }
|
|
1072
|
+
* `{ VITE_API_URL: 'API_URL' }` — its edges' public values, and a site's own
|
|
1073
|
+
* address (`{ VITE_WEB_URL: 'WEB_URL' }`).
|
|
954
1074
|
*
|
|
955
1075
|
* A rename, not a second derivation: `API_URL` is resolved once, by whatever
|
|
956
1076
|
* resolved it for the server, and the site reads the same value under the name
|
|
@@ -969,47 +1089,639 @@ const PUBLIC_PREFIX = {
|
|
|
969
1089
|
function publicEnvFor(declaration, manifest) {
|
|
970
1090
|
const prefix = PUBLIC_PREFIX[declaration.variant];
|
|
971
1091
|
const keys = {};
|
|
972
|
-
|
|
973
|
-
const
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
}
|
|
1092
|
+
if (declaration.kind === "site") {
|
|
1093
|
+
const own = providedKeyFor(declaration.id, "site", "url");
|
|
1094
|
+
keys[`${prefix}${own}`] = own;
|
|
1095
|
+
}
|
|
1096
|
+
if (isWildcardSite(declaration)) for (const role of ["domain", "protectedSubdomains"]) {
|
|
1097
|
+
const key = providedKeyFor(declaration.id, "site", role);
|
|
1098
|
+
keys[`${prefix}${key}`] = key;
|
|
979
1099
|
}
|
|
1100
|
+
for (const { key } of publicValues(declaration, manifest)) keys[`${prefix}${key}`] = key;
|
|
980
1101
|
return keys;
|
|
981
1102
|
}
|
|
1103
|
+
/**
|
|
1104
|
+
* The key, under a site's public prefix, that lists every origin its pages
|
|
1105
|
+
* load files from: `NEXT_PUBLIC_GKM_IMAGE_ORIGINS`. Comma-separated.
|
|
1106
|
+
*
|
|
1107
|
+
* One composed key rather than a rule over names, because a file server's
|
|
1108
|
+
* `<ID>_URL` and an API's look the same: which of a bundle's addresses serve
|
|
1109
|
+
* files is a fact about the graph, so the graph says it.
|
|
1110
|
+
*/
|
|
1111
|
+
const IMAGE_ORIGINS_KEY = "GKM_IMAGE_ORIGINS";
|
|
1112
|
+
/**
|
|
1113
|
+
* The keys whose values are the origins a site's pages load files from: each
|
|
1114
|
+
* file server it reaches, and where each bucket it reaches signs its links —
|
|
1115
|
+
* by either edge, read off {@link publicValues}. Sorted, so the
|
|
1116
|
+
* value composed from them does not churn.
|
|
1117
|
+
*/
|
|
1118
|
+
function imageOriginSources(declaration, manifest) {
|
|
1119
|
+
return publicValues(declaration, manifest).filter(({ kind, role }) => kind === "file-server" && role === "url" || kind === "objects" && role === "signedOrigin").map(({ key }) => key).sort();
|
|
1120
|
+
}
|
|
1121
|
+
/**
|
|
1122
|
+
* {@link IMAGE_ORIGINS_KEY}'s value: the origin of each source that resolved,
|
|
1123
|
+
* de-duplicated and comma-separated. `undefined` when none did, so a site
|
|
1124
|
+
* that shows no files is built without the key at all.
|
|
1125
|
+
*/
|
|
1126
|
+
function imageOrigins(sources, resolve) {
|
|
1127
|
+
const origins = /* @__PURE__ */ new Set();
|
|
1128
|
+
for (const source of sources) {
|
|
1129
|
+
const value = resolve(source);
|
|
1130
|
+
if (!value) continue;
|
|
1131
|
+
try {
|
|
1132
|
+
origins.add(new URL(value).origin);
|
|
1133
|
+
} catch {}
|
|
1134
|
+
}
|
|
1135
|
+
return origins.size > 0 ? [...origins].sort().join(",") : void 0;
|
|
1136
|
+
}
|
|
1137
|
+
//#endregion
|
|
1138
|
+
//#region src/derived.ts
|
|
1139
|
+
/** Where a processor keeps what it made, in the bucket it reads. */
|
|
1140
|
+
const DERIVED_PREFIX = ".gkm-derived/";
|
|
1141
|
+
/** How long a derivative is kept when its file server does not say. */
|
|
1142
|
+
const DEFAULT_DERIVED_DAYS = 30;
|
|
1143
|
+
/**
|
|
1144
|
+
* The days a bucket keeps what is under each prefix: what it declared, and —
|
|
1145
|
+
* when a file server over it has processors — the derived prefix, for the
|
|
1146
|
+
* longest any of those servers asked. `undefined` when it expires nothing.
|
|
1147
|
+
*/
|
|
1148
|
+
function bucketExpiry(manifest, bucketId) {
|
|
1149
|
+
const expire = { ...manifest[bucketId]?.expire ?? {} };
|
|
1150
|
+
const processed = Object.values(manifest).filter((d) => d.kind === "file-server" && d.of === bucketId && (d.processors?.length ?? 0) > 0);
|
|
1151
|
+
if (processed.length > 0 && expire[".gkm-derived/"] === void 0) expire[DERIVED_PREFIX] = Math.max(...processed.map((s) => s.derivedDays ?? 30));
|
|
1152
|
+
return Object.keys(expire).length > 0 ? expire : void 0;
|
|
1153
|
+
}
|
|
1154
|
+
//#endregion
|
|
1155
|
+
//#region src/email.ts
|
|
1156
|
+
/** The local part mail is sent from when the construct names none. */
|
|
1157
|
+
const DEFAULT_SENDER = "noreply";
|
|
1158
|
+
/** `<local part>@<domain>` — `info@shop.example.com`, else `noreply@…`. */
|
|
1159
|
+
function senderAddress(email, domain) {
|
|
1160
|
+
return `${email.from ?? "noreply"}@${domain}`;
|
|
1161
|
+
}
|
|
1162
|
+
//#endregion
|
|
1163
|
+
//#region src/encryption.ts
|
|
1164
|
+
/**
|
|
1165
|
+
* The `kms://` URL an `Encryption` construct reads on AWS.
|
|
1166
|
+
*
|
|
1167
|
+
* Here because two packages meet on it: `@geekmidas/cloud` composes it from
|
|
1168
|
+
* the keys it provisioned, and `@geekmidas/constructs` parses it to reach
|
|
1169
|
+
* them. One function, so the two cannot disagree about a parameter's name.
|
|
1170
|
+
*/
|
|
1171
|
+
const KMS_SCHEME = "kms:";
|
|
1172
|
+
/**
|
|
1173
|
+
* `kms://<region>?key=<key arn>&index=<hmac key arn>[&endpoint=<url>]`
|
|
1174
|
+
*
|
|
1175
|
+
* `endpoint` reaches an emulator instead of AWS — floci, locally and in tests —
|
|
1176
|
+
* the same parameter an `sns://` connection string carries for the same reason.
|
|
1177
|
+
*/
|
|
1178
|
+
function kmsUrl(input) {
|
|
1179
|
+
const query = new URLSearchParams({
|
|
1180
|
+
key: input.key,
|
|
1181
|
+
index: input.index,
|
|
1182
|
+
...input.endpoint ? { endpoint: input.endpoint } : {}
|
|
1183
|
+
});
|
|
1184
|
+
return `${KMS_SCHEME}//${input.region}?${query}`;
|
|
1185
|
+
}
|
|
1186
|
+
//#endregion
|
|
1187
|
+
//#region src/external.ts
|
|
1188
|
+
/** The key in a by-stage `url` that answers for every stage not listed. */
|
|
1189
|
+
const DEFAULT_STAGE_URL = "default";
|
|
1190
|
+
/**
|
|
1191
|
+
* Where an external API answers on a deployed stage.
|
|
1192
|
+
*
|
|
1193
|
+
* One URL answers every stage; a record answers by stage name, falling back
|
|
1194
|
+
* to `default`. Read at build and deploy, so a stage nobody gave a URL fails
|
|
1195
|
+
* there — with the stage named — rather than at the first request.
|
|
1196
|
+
*
|
|
1197
|
+
* @throws {NoUrlForStage} when a record names neither the stage nor a default.
|
|
1198
|
+
*/
|
|
1199
|
+
function externalApiUrl(declaration, stage) {
|
|
1200
|
+
const { url } = declaration;
|
|
1201
|
+
if (typeof url === "string") return url;
|
|
1202
|
+
const resolved = url[stage] ?? url["default"];
|
|
1203
|
+
if (!resolved) throw new NoUrlForStage(declaration.id, stage, Object.keys(url));
|
|
1204
|
+
return resolved;
|
|
1205
|
+
}
|
|
1206
|
+
/** An external API's `url` record has nothing for the stage being deployed. */
|
|
1207
|
+
var NoUrlForStage = class extends Error {
|
|
1208
|
+
id;
|
|
1209
|
+
stage;
|
|
1210
|
+
stages;
|
|
1211
|
+
constructor(id, stage, stages) {
|
|
1212
|
+
super(`'${id}' has no URL for the stage '${stage}': its url names ${stages.length ? stages.map((s) => `'${s}'`).join(", ") : "no stages"}. Add '${stage}', or a '${DEFAULT_STAGE_URL}' for every stage not listed.`);
|
|
1213
|
+
this.id = id;
|
|
1214
|
+
this.stage = stage;
|
|
1215
|
+
this.stages = stages;
|
|
1216
|
+
this.name = "NoUrlForStage";
|
|
1217
|
+
}
|
|
1218
|
+
};
|
|
1219
|
+
//#endregion
|
|
1220
|
+
//#region src/headers.ts
|
|
1221
|
+
/**
|
|
1222
|
+
* Headers gkm reserves for its own services' calls to each other.
|
|
1223
|
+
*
|
|
1224
|
+
* Shared by `@geekmidas/constructs`, which sends and reads them, and
|
|
1225
|
+
* `@geekmidas/cli`, whose edges strip them from every request that arrives
|
|
1226
|
+
* from outside — so the two cannot name them differently.
|
|
1227
|
+
*/
|
|
1228
|
+
/**
|
|
1229
|
+
* The address of the client a service-to-service call is made on behalf of.
|
|
1230
|
+
*
|
|
1231
|
+
* An API asking its auth server for a session sends the client's address in
|
|
1232
|
+
* this rather than `X-Forwarded-For`: a forwarding header marks a request as
|
|
1233
|
+
* outside traffic, which would start the auth server's span in a new trace,
|
|
1234
|
+
* and the address is still needed — Better Auth rate-limits per client.
|
|
1235
|
+
*
|
|
1236
|
+
* Reserved: the stack's edge (Caddy, Traefik, the `gkm dev` edge) removes it
|
|
1237
|
+
* from every request that does not carry the stack's token
|
|
1238
|
+
* ({@link EDGE_TOKEN_HEADER}), and the auth server ignores it on any request
|
|
1239
|
+
* that neither carries the token nor is an internal caller's, so it cannot be
|
|
1240
|
+
* spoofed from outside.
|
|
1241
|
+
*/
|
|
1242
|
+
const CLIENT_IP_HEADER = "x-gkm-client-ip";
|
|
1243
|
+
/**
|
|
1244
|
+
* The token a service of a stack sends beside {@link CLIENT_IP_HEADER} when
|
|
1245
|
+
* it calls a sibling through the stack's edge, at its public address.
|
|
1246
|
+
*
|
|
1247
|
+
* A request carries a forwarding header once it has passed an edge, so the
|
|
1248
|
+
* peer alone no longer says it came from inside the stack. The token does:
|
|
1249
|
+
* it is derived from the stage's seed, handed to every process of the stack
|
|
1250
|
+
* as {@link EDGE_TOKEN_KEY}, and known to the edge. The edge keeps
|
|
1251
|
+
* `x-gkm-client-ip` only on a request carrying it, and strips the header from
|
|
1252
|
+
* every other; the auth server honours the header only from an internal
|
|
1253
|
+
* caller or a request carrying its own token. A browser never has it.
|
|
1254
|
+
*/
|
|
1255
|
+
const EDGE_TOKEN_HEADER = "x-gkm-edge-token";
|
|
1256
|
+
/** Where a stack's processes read the token {@link EDGE_TOKEN_HEADER} carries. */
|
|
1257
|
+
const EDGE_TOKEN_KEY = "GKM_EDGE_TOKEN";
|
|
1258
|
+
//#endregion
|
|
1259
|
+
//#region src/hosts.ts
|
|
1260
|
+
/** Whether a declaration answers on a host of its own: a surface or a site. */
|
|
1261
|
+
function isHostedDeclaration(declaration) {
|
|
1262
|
+
return declaration?.kind === "rest-api" || declaration?.kind === "websocket-api" || declaration?.kind === "site";
|
|
1263
|
+
}
|
|
1264
|
+
/**
|
|
1265
|
+
* The one derivation of a construct's labels: its primary label, its
|
|
1266
|
+
* aliases and its redirects, in declared order.
|
|
1267
|
+
*/
|
|
1268
|
+
function hostLabels(source) {
|
|
1269
|
+
return {
|
|
1270
|
+
primary: source.subdomain ?? kebabCase(source.id),
|
|
1271
|
+
aliases: [...source.aliases ?? []],
|
|
1272
|
+
redirects: [...source.redirects ?? []]
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
/** One label of a DNS name: lowercase letters, digits and inner hyphens. */
|
|
1276
|
+
const LABEL$1 = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
1277
|
+
/**
|
|
1278
|
+
* A `subdomain`, alias or redirect that cannot be a label of its own under
|
|
1279
|
+
* the domain.
|
|
1280
|
+
*/
|
|
1281
|
+
var InvalidHostLabel = class extends Error {
|
|
1282
|
+
id;
|
|
1283
|
+
label;
|
|
1284
|
+
field;
|
|
1285
|
+
constructor(id, label, field) {
|
|
1286
|
+
super(field === "subdomain" ? `'${id}' has subdomain: '${label}', which is not a host label: it is one lowercase label under the stage's domain ('telemetry', 'logs') — letters, digits and inner hyphens, no '*' and no dots.` : `'${id}' lists '${label}' in ${field}, which is not a host label it can add: each entry is one lowercase label under the stage's domain ('rest', 'www') — no '*', no dots — listed once, and not the construct's own label. Fix or remove the entry.`);
|
|
1287
|
+
this.id = id;
|
|
1288
|
+
this.label = label;
|
|
1289
|
+
this.field = field;
|
|
1290
|
+
this.name = "InvalidHostLabel";
|
|
1291
|
+
}
|
|
1292
|
+
};
|
|
1293
|
+
/**
|
|
1294
|
+
* A construct's own `subdomain`, checked where it is constructed: one label,
|
|
1295
|
+
* never a wildcard — for a construct that cannot answer on every subdomain.
|
|
1296
|
+
*
|
|
1297
|
+
* @throws {InvalidHostLabel} for `'*'`, a dot, or anything not a DNS label
|
|
1298
|
+
*/
|
|
1299
|
+
function assertSubdomainLabel(id, subdomain) {
|
|
1300
|
+
if (!LABEL$1.test(subdomain)) throw new InvalidHostLabel(id, subdomain, "subdomain");
|
|
1301
|
+
}
|
|
1302
|
+
/** A wildcard site with aliases or redirects. */
|
|
1303
|
+
var WildcardSiteCannotHaveAliases = class extends Error {
|
|
1304
|
+
id;
|
|
1305
|
+
constructor(id) {
|
|
1306
|
+
super(`'${id}' answers on every subdomain, so it has no single host for an alias to repeat or a redirect to point at. Drop \`aliases\` and \`redirects\`, or give the site a subdomain of its own.`);
|
|
1307
|
+
this.id = id;
|
|
1308
|
+
this.name = "WildcardSiteCannotHaveAliases";
|
|
1309
|
+
}
|
|
1310
|
+
};
|
|
1311
|
+
/** One label listed as both an alias and a redirect. */
|
|
1312
|
+
var HostLabelBothAliasAndRedirect = class extends Error {
|
|
1313
|
+
id;
|
|
1314
|
+
label;
|
|
1315
|
+
constructor(id, label) {
|
|
1316
|
+
super(`'${id}' lists '${label}' in both aliases and redirects, but a host either serves the site or sends the browser elsewhere. Keep it in one of the two.`);
|
|
1317
|
+
this.id = id;
|
|
1318
|
+
this.label = label;
|
|
1319
|
+
this.name = "HostLabelBothAliasAndRedirect";
|
|
1320
|
+
}
|
|
1321
|
+
};
|
|
1322
|
+
/**
|
|
1323
|
+
* What a construct's aliases and redirects have to be, checked where it is
|
|
1324
|
+
* constructed.
|
|
1325
|
+
*
|
|
1326
|
+
* @throws {WildcardSiteCannotHaveAliases} on a wildcard site with either
|
|
1327
|
+
* @throws {HostLabelBothAliasAndRedirect} when a label is in both lists
|
|
1328
|
+
* @throws {InvalidHostLabel} for `'*'`, a dot, a repeat, or its own label
|
|
1329
|
+
*/
|
|
1330
|
+
function assertHostLabels(source) {
|
|
1331
|
+
const { primary, aliases, redirects } = hostLabels(source);
|
|
1332
|
+
if (aliases.length === 0 && redirects.length === 0) return;
|
|
1333
|
+
if (primary === "*") throw new WildcardSiteCannotHaveAliases(source.id);
|
|
1334
|
+
for (const label of aliases) if (redirects.includes(label)) throw new HostLabelBothAliasAndRedirect(source.id, label);
|
|
1335
|
+
const seen = /* @__PURE__ */ new Set([primary]);
|
|
1336
|
+
const check = (labels, field) => {
|
|
1337
|
+
for (const label of labels) {
|
|
1338
|
+
if (!LABEL$1.test(label) || seen.has(label)) throw new InvalidHostLabel(source.id, label, field);
|
|
1339
|
+
seen.add(label);
|
|
1340
|
+
}
|
|
1341
|
+
};
|
|
1342
|
+
check(aliases, "aliases");
|
|
1343
|
+
check(redirects, "redirects");
|
|
1344
|
+
}
|
|
1345
|
+
//#endregion
|
|
1346
|
+
//#region src/inbox.ts
|
|
1347
|
+
/** The event type a received message is announced as on its inbox's topic. */
|
|
1348
|
+
const INBOX_EVENT = "email.received";
|
|
1349
|
+
/**
|
|
1350
|
+
* Where an inbox's raw messages are kept in its bucket: SES's S3 action
|
|
1351
|
+
* writes `<prefix><message id>`, and `gkm dev` and the test mailbox write the
|
|
1352
|
+
* same layout.
|
|
1353
|
+
*/
|
|
1354
|
+
const INBOX_MESSAGES_PREFIX = "messages/";
|
|
1355
|
+
/**
|
|
1356
|
+
* The recipient `address` is to `inbox` on `domain`, or `undefined` when the
|
|
1357
|
+
* inbox does not receive at it.
|
|
1358
|
+
*
|
|
1359
|
+
* - The local part is a declared address, or one with a `+tag` after it.
|
|
1360
|
+
* - Without `subdomains`, the domain is the stage's domain itself; with it,
|
|
1361
|
+
* exactly one label in front of the stage's domain — `acme.fixwell.example`
|
|
1362
|
+
* — and never the bare domain, whose mail would belong to no tenant.
|
|
1363
|
+
*
|
|
1364
|
+
* Case-insensitive, as mail addresses are in practice.
|
|
1365
|
+
*/
|
|
1366
|
+
function inboxRecipient(address, inbox, domain) {
|
|
1367
|
+
const normalized = address.trim().toLowerCase();
|
|
1368
|
+
const at = normalized.lastIndexOf("@");
|
|
1369
|
+
if (at <= 0) return void 0;
|
|
1370
|
+
const localPart = normalized.slice(0, at);
|
|
1371
|
+
const host = normalized.slice(at + 1);
|
|
1372
|
+
const base = domain.toLowerCase();
|
|
1373
|
+
let subdomain;
|
|
1374
|
+
if (inbox.subdomains) {
|
|
1375
|
+
if (!host.endsWith(`.${base}`)) return void 0;
|
|
1376
|
+
subdomain = host.slice(0, -(base.length + 1));
|
|
1377
|
+
if (!subdomain || subdomain.includes(".")) return void 0;
|
|
1378
|
+
} else if (host !== base) return;
|
|
1379
|
+
const plus = localPart.indexOf("+");
|
|
1380
|
+
const local = plus === -1 ? localPart : localPart.slice(0, plus);
|
|
1381
|
+
const tag = plus === -1 ? void 0 : localPart.slice(plus + 1);
|
|
1382
|
+
if (!inbox.addresses.some((declared) => declared.toLowerCase() === local)) return;
|
|
1383
|
+
return {
|
|
1384
|
+
address: normalized,
|
|
1385
|
+
local,
|
|
1386
|
+
...tag ? { tag } : {},
|
|
1387
|
+
...subdomain ? { subdomain } : {}
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1390
|
+
/**
|
|
1391
|
+
* The recipients a deployed receipt rule names for `inbox` on `domain`.
|
|
1392
|
+
*
|
|
1393
|
+
* Without `subdomains`, each address on the domain: SES matches an address
|
|
1394
|
+
* condition's `+label` variants too, so `requests@` takes `requests+t_8f3k@`.
|
|
1395
|
+
* With it, every subdomain (`.fixwell.example`): a condition cannot say
|
|
1396
|
+
* "`requests` on any subdomain", so the rule takes the subdomains' mail and
|
|
1397
|
+
* the decoder in front of the handler drops what {@link inboxRecipient} does
|
|
1398
|
+
* not match.
|
|
1399
|
+
*/
|
|
1400
|
+
function receiptRecipients(inbox, domain) {
|
|
1401
|
+
const base = domain.toLowerCase();
|
|
1402
|
+
return inbox.subdomains ? [`.${base}`] : inbox.addresses.map((address) => `${address.toLowerCase()}@${base}`);
|
|
1403
|
+
}
|
|
1404
|
+
//#endregion
|
|
1405
|
+
//#region src/migrations.ts
|
|
1406
|
+
/** Where every construct's folder lives, from the project root. */
|
|
1407
|
+
const MIGRATIONS_ROOT = "db";
|
|
1408
|
+
/** The two folders a construct's folder holds, and nothing else. */
|
|
1409
|
+
const DATABASE_FOLDERS = ["migrations", "seeds"];
|
|
1410
|
+
/**
|
|
1411
|
+
* The constructs that take migrations, parents before the tenants inside them.
|
|
1412
|
+
*
|
|
1413
|
+
* A database and each schema tenant: every one has an owner role and a schema
|
|
1414
|
+
* of its own, and so a history of its own. A reader has neither — it reads its
|
|
1415
|
+
* parent's schema — and a cache's table is created by provisioning, not by a
|
|
1416
|
+
* migration anyone writes.
|
|
1417
|
+
*/
|
|
1418
|
+
function migrationTargets(manifest) {
|
|
1419
|
+
return provisionOrder(manifest).flatMap((id) => {
|
|
1420
|
+
const declaration = manifest[id];
|
|
1421
|
+
if (!declaration) return [];
|
|
1422
|
+
if (declaration.kind === "database") return [target(id)];
|
|
1423
|
+
if (declaration.kind === "database-schema") return [{
|
|
1424
|
+
...target(id),
|
|
1425
|
+
of: declaration.of
|
|
1426
|
+
}];
|
|
1427
|
+
return [];
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
/**
|
|
1431
|
+
* A construct's folder, relative to the project root.
|
|
1432
|
+
*
|
|
1433
|
+
* Named by the construct — `AuthDatabase` is `db/auth-database` — in the case
|
|
1434
|
+
* every other name outside TypeScript takes. Lowercase, because a folder that
|
|
1435
|
+
* differs from another only in case is two folders on Linux and one on a Mac;
|
|
1436
|
+
* and `canonicalId` reads it back to the construct exactly, which is how a
|
|
1437
|
+
* folder naming nothing is caught rather than skipped.
|
|
1438
|
+
*/
|
|
1439
|
+
function databaseFolder(id) {
|
|
1440
|
+
return `db/${kebabCase(id)}`;
|
|
1441
|
+
}
|
|
1442
|
+
/** Where a construct's migrations live: `db/auth-database/migrations`. */
|
|
1443
|
+
function migrationFolder(id) {
|
|
1444
|
+
return `${databaseFolder(id)}/migrations`;
|
|
1445
|
+
}
|
|
1446
|
+
/**
|
|
1447
|
+
* Where a construct's seeds live: `db/auth-database/seeds`.
|
|
1448
|
+
*
|
|
1449
|
+
* Reference data the app needs to run — a permission catalogue, roles, lookup
|
|
1450
|
+
* tables. Seeds are upserts run on every pass, after the migrations, so a
|
|
1451
|
+
* changed seed is applied by running it again. Sample data is factories'.
|
|
1452
|
+
*/
|
|
1453
|
+
function seedFolder(id) {
|
|
1454
|
+
return `${databaseFolder(id)}/seeds`;
|
|
1455
|
+
}
|
|
1456
|
+
function target(id) {
|
|
1457
|
+
return {
|
|
1458
|
+
id,
|
|
1459
|
+
folder: databaseFolder(id),
|
|
1460
|
+
migrations: migrationFolder(id),
|
|
1461
|
+
seeds: seedFolder(id)
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
//#endregion
|
|
1465
|
+
//#region src/mobile.ts
|
|
1466
|
+
/**
|
|
1467
|
+
* How a mobile app is reached: its URL scheme, and the origins a surface trusts
|
|
1468
|
+
* for it.
|
|
1469
|
+
*
|
|
1470
|
+
* Stated once, here, because three targets ask — the local one (`gkm dev`,
|
|
1471
|
+
* `gkm test`), Dokploy and AWS — and a scheme one of them derived differently
|
|
1472
|
+
* would be an app whose sign-in callback the auth server refuses on exactly one
|
|
1473
|
+
* of them.
|
|
1474
|
+
*/
|
|
1475
|
+
/**
|
|
1476
|
+
* An app's scheme: the one given, or the project's name made a valid scheme.
|
|
1477
|
+
*
|
|
1478
|
+
* One for every stage. A development build and the store build register the
|
|
1479
|
+
* same scheme, so the auth server of whichever stage a build talks to trusts
|
|
1480
|
+
* it, and nothing about the app differs between stages but its URLs.
|
|
1481
|
+
*
|
|
1482
|
+
* A scheme is a letter followed by letters, digits, `+`, `-` or `.` — so
|
|
1483
|
+
* `Corner Shop` becomes `corner-shop`, and a name that starts with a digit gets a
|
|
1484
|
+
* leading `app`.
|
|
1485
|
+
*/
|
|
1486
|
+
function schemeBase(project, given) {
|
|
1487
|
+
const raw = (given ?? project).toLowerCase().replace(/[^a-z0-9+.-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1488
|
+
if (!raw) return "app";
|
|
1489
|
+
return /^[a-z]/.test(raw) ? raw : `app${raw}`;
|
|
1490
|
+
}
|
|
1491
|
+
/**
|
|
1492
|
+
* The origins a surface trusts for a mobile caller.
|
|
1493
|
+
*
|
|
1494
|
+
* Better Auth's Expo plugin sends the app's scheme as its origin
|
|
1495
|
+
* (`shop://`), and a link can carry a path after it (`shop://*`).
|
|
1496
|
+
* In development, Expo Go sends `exp://<host>:<port>` instead — so a local
|
|
1497
|
+
* stage adds the hosts a device actually reaches Metro on, and Metro's port
|
|
1498
|
+
* when the target placed it: a whole subnet is not trusted because one phone
|
|
1499
|
+
* is on it.
|
|
1500
|
+
*/
|
|
1501
|
+
function mobileOrigins(scheme, metro) {
|
|
1502
|
+
return [
|
|
1503
|
+
`${scheme}://`,
|
|
1504
|
+
`${scheme}://*`,
|
|
1505
|
+
...(metro?.hosts ?? []).flatMap((host) => [`exp://${host}:${metro?.port ?? "*"}`, `exp://${host}:${metro?.port ?? "*"}/**`])
|
|
1506
|
+
];
|
|
1507
|
+
}
|
|
1508
|
+
/** Whether an origin is a web one — what a cookie domain is derived from. */
|
|
1509
|
+
function isWebOrigin(origin) {
|
|
1510
|
+
return origin.startsWith("http://") || origin.startsWith("https://");
|
|
1511
|
+
}
|
|
1512
|
+
//#endregion
|
|
1513
|
+
//#region src/origins.ts
|
|
1514
|
+
/**
|
|
1515
|
+
* Whether a request from `origin` comes from one of `trusted`.
|
|
1516
|
+
*
|
|
1517
|
+
* An entry matches exactly — scheme, host and port — or, written
|
|
1518
|
+
* `https://*.example.com`, matches any one label in place of the `*`:
|
|
1519
|
+
* `https://acme.example.com`, never `https://example.com` and never
|
|
1520
|
+
* `https://a.b.example.com`. That is the origin a wildcard site's tenants
|
|
1521
|
+
* call from, and it is as wide as a wildcard certificate, no wider.
|
|
1522
|
+
*
|
|
1523
|
+
* A bare `*` trusts nothing: a surface that answers any origin has not said
|
|
1524
|
+
* which sites are its own.
|
|
1525
|
+
*/
|
|
1526
|
+
function trustsOrigin(origin, trusted) {
|
|
1527
|
+
const request = parse(origin);
|
|
1528
|
+
if (!request) return false;
|
|
1529
|
+
return trusted.some((entry) => {
|
|
1530
|
+
const allowed = parse(entry);
|
|
1531
|
+
if (!allowed) return false;
|
|
1532
|
+
if (allowed.scheme !== request.scheme) return false;
|
|
1533
|
+
if (allowed.port !== request.port) return false;
|
|
1534
|
+
const domain = wildcardDomainOf(allowed.host);
|
|
1535
|
+
if (domain === void 0) return allowed.host === request.host;
|
|
1536
|
+
const dot = request.host.indexOf(".");
|
|
1537
|
+
return dot > 0 && LABEL.test(request.host.slice(0, dot)) && request.host.slice(dot + 1) === domain;
|
|
1538
|
+
});
|
|
1539
|
+
}
|
|
1540
|
+
/**
|
|
1541
|
+
* The domain under a wildcard host — `example.com` for `*.example.com` — or
|
|
1542
|
+
* `undefined` for a host that is not one.
|
|
1543
|
+
*/
|
|
1544
|
+
function wildcardDomainOf(host) {
|
|
1545
|
+
return host.startsWith("*.") ? host.slice(2) : void 0;
|
|
1546
|
+
}
|
|
1547
|
+
/** One DNS label, as a tenant's slug is. */
|
|
1548
|
+
const LABEL = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
1549
|
+
function parse(origin) {
|
|
1550
|
+
if (!origin || origin === "*") return void 0;
|
|
1551
|
+
try {
|
|
1552
|
+
const url = new URL(origin);
|
|
1553
|
+
if (url.origin === "null") return void 0;
|
|
1554
|
+
return {
|
|
1555
|
+
scheme: url.protocol,
|
|
1556
|
+
host: url.hostname.toLowerCase(),
|
|
1557
|
+
port: url.port
|
|
1558
|
+
};
|
|
1559
|
+
} catch {
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
982
1563
|
//#endregion
|
|
983
|
-
//#region src/
|
|
984
|
-
/**
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1564
|
+
//#region src/secret.ts
|
|
1565
|
+
/**
|
|
1566
|
+
* What a generated secret looks like, and the one way every stage draws it.
|
|
1567
|
+
*
|
|
1568
|
+
* A server deploy draws from `crypto`, `gkm dev` and `gkm test` from a hash of
|
|
1569
|
+
* the project, stage and id, and an AWS deploy asks Pulumi for the same
|
|
1570
|
+
* alphabet. Sharing this is what keeps a password that passes a third party's
|
|
1571
|
+
* rules on one stage from failing them on the next.
|
|
1572
|
+
*/
|
|
1573
|
+
/** 43 characters from 64 is 256 bits — as much as anything here needs. */
|
|
1574
|
+
const SECRET_LENGTH = 43;
|
|
1575
|
+
/**
|
|
1576
|
+
* Two symbols nothing has to escape — not a URL, a connection string, a shell
|
|
1577
|
+
* or a `.env` file — which with letters and digits is base64url's alphabet.
|
|
1578
|
+
*/
|
|
1579
|
+
const SECRET_SYMBOLS = "-_";
|
|
1580
|
+
const LETTERS_AND_DIGITS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
1581
|
+
/**
|
|
1582
|
+
* A secret in `format`, each character chosen by `pick(n)` — an integer in
|
|
1583
|
+
* `[0, n)` from whatever source the caller trusts.
|
|
1584
|
+
*
|
|
1585
|
+
* When there are symbols, one is guaranteed: a rule that demands a symbol is
|
|
1586
|
+
* the reason anyone names them, and leaving it to chance fails it often —
|
|
1587
|
+
* about one draw in seven at 24 characters.
|
|
1588
|
+
*/
|
|
1589
|
+
function drawSecret(pick, format = {}) {
|
|
1590
|
+
const length = format.length ?? 43;
|
|
1591
|
+
const symbols = [...new Set(format.symbols ?? "-_")].join("");
|
|
1592
|
+
const alphabet = LETTERS_AND_DIGITS + symbols;
|
|
1593
|
+
const chars = Array.from({ length }, () => alphabet[pick(alphabet.length)]);
|
|
1594
|
+
if (symbols && !chars.some((char) => symbols.includes(char))) chars[pick(length)] = symbols[pick(symbols.length)];
|
|
1595
|
+
return chars.join("");
|
|
1596
|
+
}
|
|
1597
|
+
//#endregion
|
|
1598
|
+
//#region src/websocket.ts
|
|
1599
|
+
/**
|
|
1600
|
+
* The `apigateway://` string a `WebSocketApi`'s publisher reads on AWS.
|
|
1601
|
+
*
|
|
1602
|
+
* Here because two packages meet on it: `@geekmidas/cloud` composes it from
|
|
1603
|
+
* the API Gateway WebSocket API and the connection table it provisioned, and
|
|
1604
|
+
* `@geekmidas/constructs` parses it to reach them. One codec, so the two
|
|
1605
|
+
* cannot disagree about a parameter's name.
|
|
1606
|
+
*
|
|
1607
|
+
* On a server stage the same key is a `redis://` string — whichever replica
|
|
1608
|
+
* holds the socket delivers. On AWS the gateway holds every socket: a send is
|
|
1609
|
+
* a call to its management API, and a broadcast reads the ids it would fan out
|
|
1610
|
+
* to from the table.
|
|
1611
|
+
*/
|
|
1612
|
+
const API_GATEWAY_WEBSOCKET_SCHEME = "apigateway:";
|
|
1613
|
+
/**
|
|
1614
|
+
* `apigateway://<region>?endpoint=<management url>&table=<name>[&tableEndpoint=<url>]`
|
|
1615
|
+
*/
|
|
1616
|
+
function apiGatewayWebSocketUrl(target) {
|
|
1617
|
+
const query = new URLSearchParams({
|
|
1618
|
+
endpoint: target.endpoint,
|
|
1619
|
+
table: target.table,
|
|
1620
|
+
...target.tableEndpoint ? { tableEndpoint: target.tableEndpoint } : {}
|
|
1621
|
+
});
|
|
1622
|
+
return `${API_GATEWAY_WEBSOCKET_SCHEME}//${target.region}?${query}`;
|
|
1623
|
+
}
|
|
1624
|
+
/** An `apigateway://` string missing what a publisher needs to reach a socket. */
|
|
1625
|
+
var InvalidApiGatewayWebSocketUrl = class extends Error {
|
|
1626
|
+
missing;
|
|
1627
|
+
constructor(missing) {
|
|
1628
|
+
super(`An apigateway:// WebSocket publisher string needs its ${missing}. gkm composes it as <ID>_PUBLISHER_CONNECTION_STRING on an AWS stage — read it from there rather than writing one by hand.`);
|
|
1629
|
+
this.missing = missing;
|
|
1630
|
+
this.name = "InvalidApiGatewayWebSocketUrl";
|
|
1631
|
+
}
|
|
1632
|
+
};
|
|
1633
|
+
/** What an `apigateway://` string names. */
|
|
1634
|
+
function parseApiGatewayWebSocketUrl(value) {
|
|
1635
|
+
const url = new URL(value);
|
|
1636
|
+
if (url.protocol !== "apigateway:" || !url.hostname) throw new InvalidApiGatewayWebSocketUrl("region");
|
|
1637
|
+
const endpoint = url.searchParams.get("endpoint");
|
|
1638
|
+
if (!endpoint) throw new InvalidApiGatewayWebSocketUrl("endpoint");
|
|
1639
|
+
const table = url.searchParams.get("table");
|
|
1640
|
+
if (!table) throw new InvalidApiGatewayWebSocketUrl("table");
|
|
1641
|
+
const tableEndpoint = url.searchParams.get("tableEndpoint");
|
|
1642
|
+
return {
|
|
1643
|
+
region: url.hostname,
|
|
1644
|
+
endpoint,
|
|
1645
|
+
table,
|
|
1646
|
+
...tableEndpoint ? { tableEndpoint } : {}
|
|
1647
|
+
};
|
|
988
1648
|
}
|
|
989
1649
|
//#endregion
|
|
1650
|
+
exports.API_GATEWAY_WEBSOCKET_SCHEME = API_GATEWAY_WEBSOCKET_SCHEME;
|
|
1651
|
+
exports.CLIENT_IP_HEADER = CLIENT_IP_HEADER;
|
|
1652
|
+
exports.DATABASE_FOLDERS = DATABASE_FOLDERS;
|
|
1653
|
+
exports.DEFAULT_DERIVED_DAYS = DEFAULT_DERIVED_DAYS;
|
|
990
1654
|
exports.DEFAULT_POSTGRES_VERSION = DEFAULT_POSTGRES_VERSION;
|
|
1655
|
+
exports.DEFAULT_SENDER = DEFAULT_SENDER;
|
|
1656
|
+
exports.DEFAULT_STAGE_URL = DEFAULT_STAGE_URL;
|
|
1657
|
+
exports.DERIVED_PREFIX = DERIVED_PREFIX;
|
|
991
1658
|
exports.DERIVES_FROM = DERIVES_FROM;
|
|
1659
|
+
exports.EDGE_TOKEN_HEADER = EDGE_TOKEN_HEADER;
|
|
1660
|
+
exports.EDGE_TOKEN_KEY = EDGE_TOKEN_KEY;
|
|
1661
|
+
exports.FILE_SERVER_SUFFIX = FILE_SERVER_SUFFIX;
|
|
1662
|
+
exports.HostLabelBothAliasAndRedirect = HostLabelBothAliasAndRedirect;
|
|
1663
|
+
exports.IMAGE_ORIGINS_KEY = IMAGE_ORIGINS_KEY;
|
|
1664
|
+
exports.INBOX_EVENT = INBOX_EVENT;
|
|
1665
|
+
exports.INBOX_MESSAGES_PREFIX = INBOX_MESSAGES_PREFIX;
|
|
992
1666
|
exports.IllegalDerivation = IllegalDerivation;
|
|
1667
|
+
exports.InvalidApiGatewayWebSocketUrl = InvalidApiGatewayWebSocketUrl;
|
|
993
1668
|
exports.InvalidConstructId = InvalidConstructId;
|
|
1669
|
+
exports.InvalidHostLabel = InvalidHostLabel;
|
|
1670
|
+
exports.KMS_SCHEME = KMS_SCHEME;
|
|
1671
|
+
exports.MIGRATIONS_ROOT = MIGRATIONS_ROOT;
|
|
1672
|
+
exports.NoUrlForStage = NoUrlForStage;
|
|
994
1673
|
exports.PUBLIC = PUBLIC;
|
|
995
1674
|
exports.PUBLIC_PREFIX = PUBLIC_PREFIX;
|
|
1675
|
+
exports.SECRET_LENGTH = SECRET_LENGTH;
|
|
1676
|
+
exports.SECRET_SYMBOLS = SECRET_SYMBOLS;
|
|
1677
|
+
exports.TELEMETRY_KEYS = TELEMETRY_KEYS;
|
|
996
1678
|
exports.UnknownParent = UnknownParent;
|
|
1679
|
+
exports.WILDCARD_SUBDOMAIN = WILDCARD_SUBDOMAIN;
|
|
1680
|
+
exports.WildcardSiteCannotHaveAliases = WildcardSiteCannotHaveAliases;
|
|
1681
|
+
exports.apiGatewayWebSocketUrl = apiGatewayWebSocketUrl;
|
|
997
1682
|
exports.assertDerivations = assertDerivations;
|
|
1683
|
+
exports.assertHostLabels = assertHostLabels;
|
|
1684
|
+
exports.assertSubdomainLabel = assertSubdomainLabel;
|
|
1685
|
+
exports.bucketExpiry = bucketExpiry;
|
|
998
1686
|
exports.cacheTable = cacheTable;
|
|
999
1687
|
exports.canonicalId = canonicalId;
|
|
1000
1688
|
exports.cloudName = cloudName;
|
|
1001
1689
|
exports.cookieDomain = cookieDomain;
|
|
1690
|
+
exports.databaseFolder = databaseFolder;
|
|
1002
1691
|
exports.dependenciesOf = dependenciesOf;
|
|
1003
1692
|
exports.dependentsOf = dependentsOf;
|
|
1693
|
+
exports.drawSecret = drawSecret;
|
|
1004
1694
|
exports.environmentCase = environmentCase;
|
|
1005
|
-
exports.
|
|
1695
|
+
exports.externalApiUrl = externalApiUrl;
|
|
1696
|
+
exports.fileServerName = fileServerName;
|
|
1697
|
+
exports.fileServerSurfaceId = fileServerSurfaceId;
|
|
1698
|
+
exports.hostLabels = hostLabels;
|
|
1699
|
+
exports.imageOriginSources = imageOriginSources;
|
|
1700
|
+
exports.imageOrigins = imageOrigins;
|
|
1701
|
+
exports.inboxRecipient = inboxRecipient;
|
|
1006
1702
|
exports.isDerived = isDerived;
|
|
1703
|
+
exports.isHostedDeclaration = isHostedDeclaration;
|
|
1704
|
+
exports.isPublicDeclaration = isPublicDeclaration;
|
|
1705
|
+
exports.isWebOrigin = isWebOrigin;
|
|
1706
|
+
exports.isWildcardSite = isWildcardSite;
|
|
1007
1707
|
exports.kebabCase = kebabCase;
|
|
1008
|
-
exports.
|
|
1708
|
+
exports.kmsUrl = kmsUrl;
|
|
1709
|
+
exports.migrationFolder = migrationFolder;
|
|
1710
|
+
exports.migrationTargets = migrationTargets;
|
|
1711
|
+
exports.mobileOrigins = mobileOrigins;
|
|
1712
|
+
exports.parseApiGatewayWebSocketUrl = parseApiGatewayWebSocketUrl;
|
|
1009
1713
|
exports.providedKeyFor = providedKeyFor;
|
|
1010
1714
|
exports.provisionOrder = provisionOrder;
|
|
1011
1715
|
exports.publicEnvFor = publicEnvFor;
|
|
1716
|
+
exports.publicValues = publicValues;
|
|
1717
|
+
exports.reachesServerHalf = reachesServerHalf;
|
|
1718
|
+
exports.receiptRecipients = receiptRecipients;
|
|
1719
|
+
exports.schemeBase = schemeBase;
|
|
1012
1720
|
exports.scopedName = scopedName;
|
|
1721
|
+
exports.seedFolder = seedFolder;
|
|
1722
|
+
exports.senderAddress = senderAddress;
|
|
1013
1723
|
exports.serviceKey = serviceKey;
|
|
1724
|
+
exports.trustsOrigin = trustsOrigin;
|
|
1725
|
+
exports.wildcardDomainOf = wildcardDomainOf;
|
|
1014
1726
|
|
|
1015
1727
|
//# sourceMappingURL=index.cjs.map
|