lakebed 0.0.14 → 0.0.16
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/README.md +32 -3
- package/package.json +1 -1
- package/src/anonymous-server.js +1868 -139
- package/src/anonymous.js +25 -5
- package/src/cli.js +132 -18
- package/src/source-runtime.js +2 -1
- package/src/version.js +1 -1
package/src/anonymous.js
CHANGED
|
@@ -17,12 +17,15 @@ export const SERVER_ENV_LIMITS = {
|
|
|
17
17
|
export const DEFAULT_ANONYMOUS_LIMITS = {
|
|
18
18
|
artifactBytes: 1024 * 1024,
|
|
19
19
|
stateBytes: 1024 * 1024,
|
|
20
|
+
stateRows: 16384,
|
|
20
21
|
requestsPerDay: 10000,
|
|
21
22
|
mutationsPerDay: 1000,
|
|
22
23
|
rowsReturned: 1000,
|
|
23
24
|
instructionBudget: 50000,
|
|
24
25
|
maxValueBytes: 65536,
|
|
25
|
-
logEntries: 1000
|
|
26
|
+
logEntries: 1000,
|
|
27
|
+
logBytes: 256 * 1024,
|
|
28
|
+
logEntryBytes: 16 * 1024
|
|
26
29
|
};
|
|
27
30
|
|
|
28
31
|
const expressionOps = new Set(["arg", "auth", "call", "row"]);
|
|
@@ -63,10 +66,27 @@ export function stableStringify(value) {
|
|
|
63
66
|
.join(",")}}`;
|
|
64
67
|
}
|
|
65
68
|
|
|
66
|
-
function byteLength(value) {
|
|
69
|
+
export function byteLength(value) {
|
|
67
70
|
return Buffer.byteLength(typeof value === "string" ? value : stableStringify(value), "utf8");
|
|
68
71
|
}
|
|
69
72
|
|
|
73
|
+
export function stateRowsLimitForLimits(limits = DEFAULT_ANONYMOUS_LIMITS) {
|
|
74
|
+
const explicit = limits.stateRows ?? DEFAULT_ANONYMOUS_LIMITS.stateRows;
|
|
75
|
+
if (Number.isSafeInteger(explicit) && explicit > 0) {
|
|
76
|
+
return explicit;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const stateBytes = limits.stateBytes ?? DEFAULT_ANONYMOUS_LIMITS.stateBytes;
|
|
80
|
+
return Math.max(1, Math.floor(stateBytes / 64));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function mutationTransactionOptions(limits = DEFAULT_ANONYMOUS_LIMITS) {
|
|
84
|
+
return {
|
|
85
|
+
stateBytesLimit: limits.stateBytes ?? DEFAULT_ANONYMOUS_LIMITS.stateBytes,
|
|
86
|
+
stateRowsLimit: stateRowsLimitForLimits(limits)
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
70
90
|
function cloneJson(value) {
|
|
71
91
|
return JSON.parse(JSON.stringify(value));
|
|
72
92
|
}
|
|
@@ -953,7 +973,7 @@ export function createSlug() {
|
|
|
953
973
|
const adjectives = ["frosted", "quiet", "bright", "lucky", "silver", "rapid", "clear", "open"];
|
|
954
974
|
const nouns = ["river", "field", "ridge", "harbor", "garden", "signal", "meadow", "orbit"];
|
|
955
975
|
const bytes = randomBytes(4);
|
|
956
|
-
return `${adjectives[bytes[0] % adjectives.length]}-${nouns[bytes[1] % nouns.length]}-${bytes.toString("
|
|
976
|
+
return `${adjectives[bytes[0] % adjectives.length]}-${nouns[bytes[1] % nouns.length]}-${bytes.toString("hex").slice(0, 6)}`;
|
|
957
977
|
}
|
|
958
978
|
|
|
959
979
|
export function hashClaimToken(token) {
|
|
@@ -1295,7 +1315,7 @@ export async function executeAnonymousMutation({ args = [], artifact, auth, depl
|
|
|
1295
1315
|
const result = await handler(source.ctx, ...args);
|
|
1296
1316
|
await source.flush(tx);
|
|
1297
1317
|
return result ?? null;
|
|
1298
|
-
});
|
|
1318
|
+
}, mutationTransactionOptions(limits));
|
|
1299
1319
|
}
|
|
1300
1320
|
|
|
1301
1321
|
const mutation = artifact.server.mutations?.[name];
|
|
@@ -1341,5 +1361,5 @@ export async function executeAnonymousMutation({ args = [], artifact, auth, depl
|
|
|
1341
1361
|
}
|
|
1342
1362
|
|
|
1343
1363
|
return null;
|
|
1344
|
-
});
|
|
1364
|
+
}, mutationTransactionOptions(limits));
|
|
1345
1365
|
}
|
package/src/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "nod
|
|
|
7
7
|
import { clearLine, cursorTo } from "node:readline";
|
|
8
8
|
import { createInterface } from "node:readline/promises";
|
|
9
9
|
import { promisify } from "node:util";
|
|
10
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
10
|
+
import { domainToASCII, fileURLToPath, pathToFileURL } from "node:url";
|
|
11
11
|
import * as esbuild from "esbuild";
|
|
12
12
|
import { WebSocketServer } from "ws";
|
|
13
13
|
import {
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
SERVER_ENV_FILE,
|
|
18
18
|
createAnonymousArtifact,
|
|
19
19
|
createClaimedArtifact,
|
|
20
|
-
parseTtlSeconds,
|
|
21
20
|
stableStringify,
|
|
22
21
|
validateServerEnvValues
|
|
23
22
|
} from "./anonymous.js";
|
|
@@ -41,8 +40,9 @@ Usage:
|
|
|
41
40
|
lakebed create [name] [--template todo] [--no-git]
|
|
42
41
|
lakebed dev [capsule-dir] [--port 3000]
|
|
43
42
|
lakebed build [capsule-dir] --target anonymous [--out .lakebed/artifacts/app.json] [--json]
|
|
44
|
-
lakebed deploy [capsule-dir] [--
|
|
43
|
+
lakebed deploy [capsule-dir] [--api <url>] [--json]
|
|
45
44
|
lakebed claim [capsule-dir] [--api <url>] [--json]
|
|
45
|
+
lakebed domains add <subdomain.lakebed.app> [--api <url>] [--json]
|
|
46
46
|
lakebed anonymous-server [--port 8787] [--public-root-url <url>] [--app-base-domain <domain>]
|
|
47
47
|
lakebed inspect <deploy-id-or-url> [--api <url>] [--json]
|
|
48
48
|
lakebed run-many [capsule-dir] [--count 20] [--base-port 4000]
|
|
@@ -72,8 +72,7 @@ const optionsWithValues = new Set([
|
|
|
72
72
|
"--port",
|
|
73
73
|
"--public-root-url",
|
|
74
74
|
"--target",
|
|
75
|
-
"--template"
|
|
76
|
-
"--ttl"
|
|
75
|
+
"--template"
|
|
77
76
|
]);
|
|
78
77
|
|
|
79
78
|
function positionals(args) {
|
|
@@ -948,12 +947,67 @@ function claimUrlFromDeployMetadata(metadata) {
|
|
|
948
947
|
return `${String(metadata.api).replace(/\/+$/g, "")}/claim/${encodeURIComponent(metadata.deployId)}/${encodeURIComponent(metadata.claimToken)}`;
|
|
949
948
|
}
|
|
950
949
|
|
|
951
|
-
function
|
|
950
|
+
function claimCommandText({ api, capsuleArg }) {
|
|
951
|
+
const parts = ["lakebed", "claim"];
|
|
952
|
+
if (capsuleArg) {
|
|
953
|
+
parts.push(capsuleArg);
|
|
954
|
+
}
|
|
955
|
+
if (api !== defaultDeployApiUrl) {
|
|
956
|
+
parts.push("--api", api);
|
|
957
|
+
}
|
|
958
|
+
return parts.map(shellQuote).join(" ");
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function normalizeDomainCommandHostname(value) {
|
|
962
|
+
const raw = String(value ?? "").trim();
|
|
963
|
+
if (!raw) {
|
|
964
|
+
throw new Error("Domain is required.");
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(raw) || raw.includes("/") || raw.includes("\\") || raw.includes(":")) {
|
|
968
|
+
throw new Error("Enter a domain like my-app.lakebed.app, without a scheme, port, or path.");
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
const hostname = domainToASCII(raw.replace(/\.$/, "").toLowerCase());
|
|
972
|
+
if (!hostname) {
|
|
973
|
+
throw new Error("Domain is not a valid hostname.");
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
if (hostname.length > 253) {
|
|
977
|
+
throw new Error("Domain must be 253 characters or fewer.");
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
for (const label of hostname.split(".")) {
|
|
981
|
+
if (!/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(label)) {
|
|
982
|
+
throw new Error("Domain labels must use letters, numbers, or hyphens.");
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
return hostname;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
function browserOpenInvocation(url) {
|
|
990
|
+
if (process.platform === "darwin") {
|
|
991
|
+
return { command: "open", args: [url] };
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
if (process.platform === "win32") {
|
|
995
|
+
return { command: "cmd", args: ["/c", "start", "", url] };
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
return { command: "xdg-open", args: [url] };
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
async function openUrlInBrowser(url) {
|
|
1002
|
+
const invocation = browserOpenInvocation(url);
|
|
1003
|
+
await execFileAsync(invocation.command, invocation.args, { windowsHide: true });
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
function deployRequestBody(envelope, { serverEnv } = {}) {
|
|
952
1007
|
const body = {
|
|
953
1008
|
artifact: envelope.artifact,
|
|
954
1009
|
clientBundle: envelope.clientBundle,
|
|
955
|
-
clientVersion: LAKEBED_VERSION
|
|
956
|
-
requestedTtlSeconds: ttl
|
|
1010
|
+
clientVersion: LAKEBED_VERSION
|
|
957
1011
|
};
|
|
958
1012
|
if (serverEnv !== undefined) {
|
|
959
1013
|
body.serverEnv = {
|
|
@@ -1012,6 +1066,10 @@ async function readResponseJson(response) {
|
|
|
1012
1066
|
}
|
|
1013
1067
|
|
|
1014
1068
|
async function deployCommand(args) {
|
|
1069
|
+
if (args.some((arg) => arg === "--ttl" || arg.startsWith("--ttl="))) {
|
|
1070
|
+
throw new Error("lakebed deploy no longer accepts --ttl. Deploy expiry is set by the server.");
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1015
1073
|
const [capsuleArg] = positionals(args);
|
|
1016
1074
|
const capsuleDir = resolveCapsuleDir(capsuleArg);
|
|
1017
1075
|
const sourceStore = await createMemorySourceStoreFromDirectory(capsuleDir);
|
|
@@ -1019,7 +1077,6 @@ async function deployCommand(args) {
|
|
|
1019
1077
|
const serverEnv = await readCapsuleServerEnv(sourceStore);
|
|
1020
1078
|
const serverEnvKeys = Object.keys(serverEnv).sort();
|
|
1021
1079
|
const hasServerEnvValues = serverEnvKeys.length > 0;
|
|
1022
|
-
const ttl = parseTtlSeconds(readArg(args, "--ttl", "7d"));
|
|
1023
1080
|
const api = deployApiUrl(args);
|
|
1024
1081
|
const metadata = await readDeployMetadata(capsuleDir);
|
|
1025
1082
|
const canUpdate =
|
|
@@ -1071,7 +1128,7 @@ async function deployCommand(args) {
|
|
|
1071
1128
|
|
|
1072
1129
|
if (canUpdate) {
|
|
1073
1130
|
response = await fetch(`${api}/v1/deploys/${encodeURIComponent(metadata.deployId)}`, {
|
|
1074
|
-
body: deployRequestBody(envelope,
|
|
1131
|
+
body: deployRequestBody(envelope, { serverEnv: serverEnvForUpdate }),
|
|
1075
1132
|
headers: {
|
|
1076
1133
|
"Authorization": `Bearer ${metadata.claimToken}`,
|
|
1077
1134
|
"Content-Type": "application/json"
|
|
@@ -1092,7 +1149,7 @@ async function deployCommand(args) {
|
|
|
1092
1149
|
}
|
|
1093
1150
|
|
|
1094
1151
|
response ??= await fetch(`${api}/v1/anonymous-deploys`, {
|
|
1095
|
-
body: deployRequestBody(envelope
|
|
1152
|
+
body: deployRequestBody(envelope),
|
|
1096
1153
|
headers: {
|
|
1097
1154
|
"Content-Type": "application/json"
|
|
1098
1155
|
},
|
|
@@ -1133,7 +1190,7 @@ async function deployCommand(args) {
|
|
|
1133
1190
|
console.log(`Updated: ${formatOptionalTimestamp(deployed.updatedAt, "unknown")}`);
|
|
1134
1191
|
console.log(`Expires: ${formatOptionalTimestamp(deployed.expiresAt)}`);
|
|
1135
1192
|
if (deployed.claimUrl) {
|
|
1136
|
-
console.log(`Claim: ${
|
|
1193
|
+
console.log(`Claim: ${claimCommandText({ api, capsuleArg })}`);
|
|
1137
1194
|
}
|
|
1138
1195
|
console.log(`Inspect: lakebed inspect ${deployed.deployId}`);
|
|
1139
1196
|
console.log("\nLimits:");
|
|
@@ -1150,7 +1207,7 @@ async function deployCommand(args) {
|
|
|
1150
1207
|
}
|
|
1151
1208
|
if (envelope.claimRequired) {
|
|
1152
1209
|
console.log("\nThis app needs a claimed deploy before server-side fetch or server env can run.");
|
|
1153
|
-
console.log(
|
|
1210
|
+
console.log(`Run ${claimCommandText({ api, capsuleArg })}, then run lakebed deploy again.`);
|
|
1154
1211
|
}
|
|
1155
1212
|
}
|
|
1156
1213
|
|
|
@@ -1203,8 +1260,56 @@ async function claimCommand(args) {
|
|
|
1203
1260
|
return;
|
|
1204
1261
|
}
|
|
1205
1262
|
|
|
1206
|
-
|
|
1207
|
-
|
|
1263
|
+
try {
|
|
1264
|
+
await openUrlInBrowser(claimUrl);
|
|
1265
|
+
} catch (error) {
|
|
1266
|
+
throw new Error(
|
|
1267
|
+
`Unable to open the claim page in your browser: ${error instanceof Error ? error.message : String(error)}\n\nRun ${claimCommandText({ api, capsuleArg })} --json to read the claim URL.`
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
console.log(`Opened claim page for deploy ${result.deployId} in your browser.`);
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
async function domainsCommand(args) {
|
|
1275
|
+
const [subcommand, hostname] = positionals(args);
|
|
1276
|
+
if (subcommand !== "add" || !hostname) {
|
|
1277
|
+
usage();
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
const capsuleDir = resolveCapsuleDir(undefined);
|
|
1282
|
+
const api = deployApiUrl(args);
|
|
1283
|
+
const metadata = await readDeployMetadata(capsuleDir);
|
|
1284
|
+
if (!metadata) {
|
|
1285
|
+
throw new Error(`No Lakebed deploy metadata found at ${deployMetadataPath(capsuleDir)}. Run lakebed deploy from this project first.`);
|
|
1286
|
+
}
|
|
1287
|
+
if (metadata.api !== api) {
|
|
1288
|
+
throw new Error(`Saved deploy metadata is for ${metadata.api}, but this command is using ${api}. Pass --api ${metadata.api} to use it.`);
|
|
1289
|
+
}
|
|
1290
|
+
if (!metadata.deployId || !metadata.claimToken) {
|
|
1291
|
+
throw new Error("This project does not have a saved deploy token. Redeploy to refresh Lakebed deploy metadata.");
|
|
1292
|
+
}
|
|
1293
|
+
const normalizedHostname = normalizeDomainCommandHostname(hostname);
|
|
1294
|
+
|
|
1295
|
+
const response = await fetch(`${api}/v1/deploys/${encodeURIComponent(metadata.deployId)}/domains`, {
|
|
1296
|
+
body: JSON.stringify({ hostname: normalizedHostname }),
|
|
1297
|
+
headers: {
|
|
1298
|
+
"Authorization": `Bearer ${metadata.claimToken}`,
|
|
1299
|
+
"Content-Type": "application/json"
|
|
1300
|
+
},
|
|
1301
|
+
method: "POST"
|
|
1302
|
+
});
|
|
1303
|
+
const domain = await readResponseJson(response);
|
|
1304
|
+
|
|
1305
|
+
if (hasFlag(args, "--json")) {
|
|
1306
|
+
console.log(JSON.stringify(domain, null, 2));
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
console.log("Registered Lakebed subdomain.");
|
|
1311
|
+
console.log(`Domain: ${domain.url}`);
|
|
1312
|
+
console.log(`Deploy: ${domain.deployId}`);
|
|
1208
1313
|
}
|
|
1209
1314
|
|
|
1210
1315
|
async function anonymousServerCommand(args) {
|
|
@@ -1252,6 +1357,9 @@ async function inspectCommand(args) {
|
|
|
1252
1357
|
console.log(`URL: ${manifest.url}`);
|
|
1253
1358
|
console.log(`Updated: ${formatOptionalTimestamp(manifest.updatedAt, "unknown")}`);
|
|
1254
1359
|
console.log(`Expires: ${formatOptionalTimestamp(manifest.expiresAt)}`);
|
|
1360
|
+
if (Array.isArray(manifest.domains) && manifest.domains.length > 0) {
|
|
1361
|
+
console.log(`Domains: ${manifest.domains.map((domain) => domain.hostname).join(", ")}`);
|
|
1362
|
+
}
|
|
1255
1363
|
console.log(`Artifact: ${manifest.artifactHash}`);
|
|
1256
1364
|
console.log(`Queries: ${manifest.queries.join(", ") || "(none)"}`);
|
|
1257
1365
|
console.log(`Mutations: ${manifest.mutations.join(", ") || "(none)"}`);
|
|
@@ -1565,11 +1673,12 @@ async function isInsideGitWorkTree(cwd) {
|
|
|
1565
1673
|
}
|
|
1566
1674
|
|
|
1567
1675
|
function shellQuote(value) {
|
|
1568
|
-
|
|
1569
|
-
|
|
1676
|
+
const text = String(value);
|
|
1677
|
+
if (/^[A-Za-z0-9_./:=,@%+-]+$/.test(text)) {
|
|
1678
|
+
return text;
|
|
1570
1679
|
}
|
|
1571
1680
|
|
|
1572
|
-
return `'${
|
|
1681
|
+
return `'${text.replaceAll("'", "'\\''")}'`;
|
|
1573
1682
|
}
|
|
1574
1683
|
|
|
1575
1684
|
async function promptForCapsuleName() {
|
|
@@ -1657,6 +1766,11 @@ async function main() {
|
|
|
1657
1766
|
return;
|
|
1658
1767
|
}
|
|
1659
1768
|
|
|
1769
|
+
if (command === "domains") {
|
|
1770
|
+
await domainsCommand(args);
|
|
1771
|
+
return;
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1660
1774
|
if (command === "anonymous-server") {
|
|
1661
1775
|
await anonymousServerCommand(args);
|
|
1662
1776
|
return;
|
package/src/source-runtime.js
CHANGED
|
@@ -7,6 +7,7 @@ import { dirname, resolve } from "node:path";
|
|
|
7
7
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
8
|
import {
|
|
9
9
|
DEFAULT_ANONYMOUS_LIMITS,
|
|
10
|
+
mutationTransactionOptions,
|
|
10
11
|
prepareAnonymousPatch,
|
|
11
12
|
stableStringify
|
|
12
13
|
} from "./anonymous.js";
|
|
@@ -551,7 +552,7 @@ export class ChildProcessSourceRuntime {
|
|
|
551
552
|
tx
|
|
552
553
|
});
|
|
553
554
|
return response.result ?? null;
|
|
554
|
-
},
|
|
555
|
+
}, mutationTransactionOptions(limits));
|
|
555
556
|
}
|
|
556
557
|
|
|
557
558
|
runWorker(request) {
|
package/src/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const LAKEBED_VERSION = "0.0.
|
|
1
|
+
export const LAKEBED_VERSION = "0.0.16";
|