fastscript 1.0.0 → 2.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/CHANGELOG.md +32 -7
- package/LICENSE +33 -21
- package/README.md +567 -73
- package/node_modules/@fastscript/core-private/BOUNDARY.json +15 -0
- package/node_modules/@fastscript/core-private/README.md +5 -0
- package/node_modules/@fastscript/core-private/package.json +34 -0
- package/node_modules/@fastscript/core-private/src/asset-optimizer.mjs +67 -0
- package/node_modules/@fastscript/core-private/src/audit-log.mjs +50 -0
- package/node_modules/@fastscript/core-private/src/auth-flows.mjs +29 -0
- package/node_modules/@fastscript/core-private/src/auth.mjs +115 -0
- package/node_modules/@fastscript/core-private/src/bench.mjs +45 -0
- package/node_modules/@fastscript/core-private/src/build.mjs +670 -0
- package/node_modules/@fastscript/core-private/src/cache.mjs +248 -0
- package/node_modules/@fastscript/core-private/src/check.mjs +22 -0
- package/node_modules/@fastscript/core-private/src/cli.mjs +95 -0
- package/node_modules/@fastscript/core-private/src/compat.mjs +128 -0
- package/node_modules/@fastscript/core-private/src/create.mjs +278 -0
- package/node_modules/@fastscript/core-private/src/csp.mjs +26 -0
- package/node_modules/@fastscript/core-private/src/db-cli.mjs +185 -0
- package/node_modules/@fastscript/core-private/src/db-postgres-collection.mjs +110 -0
- package/node_modules/@fastscript/core-private/src/db-postgres.mjs +40 -0
- package/node_modules/@fastscript/core-private/src/db.mjs +103 -0
- package/node_modules/@fastscript/core-private/src/deploy.mjs +662 -0
- package/node_modules/@fastscript/core-private/src/dev.mjs +5 -0
- package/node_modules/@fastscript/core-private/src/docs-search.mjs +35 -0
- package/node_modules/@fastscript/core-private/src/env.mjs +118 -0
- package/node_modules/@fastscript/core-private/src/export.mjs +83 -0
- package/node_modules/@fastscript/core-private/src/fs-diagnostics.mjs +70 -0
- package/node_modules/@fastscript/core-private/src/fs-error-codes.mjs +141 -0
- package/node_modules/@fastscript/core-private/src/fs-formatter.mjs +66 -0
- package/node_modules/@fastscript/core-private/src/fs-linter.mjs +274 -0
- package/node_modules/@fastscript/core-private/src/fs-normalize.mjs +91 -0
- package/node_modules/@fastscript/core-private/src/fs-parser.mjs +980 -0
- package/node_modules/@fastscript/core-private/src/generated/docs-search-index.mjs +3182 -0
- package/node_modules/@fastscript/core-private/src/i18n.mjs +25 -0
- package/node_modules/@fastscript/core-private/src/interop.mjs +16 -0
- package/node_modules/@fastscript/core-private/src/jobs.mjs +378 -0
- package/node_modules/@fastscript/core-private/src/logger.mjs +27 -0
- package/node_modules/@fastscript/core-private/src/metrics.mjs +45 -0
- package/node_modules/@fastscript/core-private/src/middleware.mjs +14 -0
- package/node_modules/@fastscript/core-private/src/migrate.mjs +81 -0
- package/node_modules/@fastscript/core-private/src/migration-wizard.mjs +16 -0
- package/node_modules/@fastscript/core-private/src/module-loader.mjs +46 -0
- package/node_modules/@fastscript/core-private/src/oauth-providers.mjs +103 -0
- package/node_modules/@fastscript/core-private/src/observability.mjs +21 -0
- package/node_modules/@fastscript/core-private/src/plugins.mjs +194 -0
- package/node_modules/@fastscript/core-private/src/retention.mjs +57 -0
- package/node_modules/@fastscript/core-private/src/routes.mjs +178 -0
- package/node_modules/@fastscript/core-private/src/scheduler.mjs +104 -0
- package/node_modules/@fastscript/core-private/src/security.mjs +233 -0
- package/node_modules/@fastscript/core-private/src/server-runtime.mjs +849 -0
- package/node_modules/@fastscript/core-private/src/serverless-handler.mjs +20 -0
- package/node_modules/@fastscript/core-private/src/session-policy.mjs +38 -0
- package/node_modules/@fastscript/core-private/src/start.mjs +10 -0
- package/node_modules/@fastscript/core-private/src/storage.mjs +155 -0
- package/node_modules/@fastscript/core-private/src/style-primitives.mjs +538 -0
- package/node_modules/@fastscript/core-private/src/style-system.mjs +461 -0
- package/node_modules/@fastscript/core-private/src/tenant.mjs +55 -0
- package/node_modules/@fastscript/core-private/src/typecheck.mjs +1464 -0
- package/node_modules/@fastscript/core-private/src/validate.mjs +22 -0
- package/node_modules/@fastscript/core-private/src/validation.mjs +88 -0
- package/node_modules/@fastscript/core-private/src/webhook.mjs +81 -0
- package/node_modules/@fastscript/core-private/src/worker.mjs +24 -0
- package/package.json +86 -13
- package/src/asset-optimizer.mjs +67 -0
- package/src/audit-log.mjs +50 -0
- package/src/auth.mjs +1 -115
- package/src/bench.mjs +20 -7
- package/src/build.mjs +1 -234
- package/src/cache.mjs +210 -20
- package/src/cli.mjs +29 -5
- package/src/compat.mjs +8 -10
- package/src/create.mjs +71 -17
- package/src/csp.mjs +26 -0
- package/src/db-cli.mjs +152 -8
- package/src/db-postgres-collection.mjs +110 -0
- package/src/deploy.mjs +1 -65
- package/src/docs-search.mjs +35 -0
- package/src/env.mjs +34 -5
- package/src/fs-diagnostics.mjs +70 -0
- package/src/fs-error-codes.mjs +126 -0
- package/src/fs-formatter.mjs +66 -0
- package/src/fs-linter.mjs +274 -0
- package/src/fs-normalize.mjs +21 -238
- package/src/fs-parser.mjs +1 -0
- package/src/generated/docs-search-index.mjs +3220 -0
- package/src/i18n.mjs +25 -0
- package/src/jobs.mjs +283 -32
- package/src/metrics.mjs +45 -0
- package/src/migration-wizard.mjs +16 -0
- package/src/module-loader.mjs +11 -12
- package/src/oauth-providers.mjs +103 -0
- package/src/plugins.mjs +194 -0
- package/src/retention.mjs +57 -0
- package/src/routes.mjs +178 -0
- package/src/scheduler.mjs +104 -0
- package/src/security.mjs +197 -19
- package/src/server-runtime.mjs +1 -339
- package/src/serverless-handler.mjs +20 -0
- package/src/session-policy.mjs +38 -0
- package/src/storage.mjs +1 -56
- package/src/style-system.mjs +461 -0
- package/src/tenant.mjs +55 -0
- package/src/typecheck.mjs +1 -0
- package/src/validate.mjs +5 -1
- package/src/validation.mjs +14 -5
- package/src/webhook.mjs +1 -71
- package/src/worker.mjs +23 -4
- package/src/language-spec.mjs +0 -58
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { runServer } from "./server-runtime.mjs";
|
|
2
|
+
|
|
3
|
+
let serverPromise = null;
|
|
4
|
+
|
|
5
|
+
async function getServer() {
|
|
6
|
+
if (!serverPromise) {
|
|
7
|
+
serverPromise = runServer({
|
|
8
|
+
mode: process.env.NODE_ENV || "production",
|
|
9
|
+
watchMode: false,
|
|
10
|
+
buildOnStart: false,
|
|
11
|
+
listen: false,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
return serverPromise;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default async function handler(req, res) {
|
|
18
|
+
const server = await getServer();
|
|
19
|
+
server.emit("request", req, res);
|
|
20
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
function parseBool(value, fallback = false) {
|
|
2
|
+
if (value === undefined || value === null || value === "") return fallback;
|
|
3
|
+
if (value === true || value === "true" || value === "1") return true;
|
|
4
|
+
if (value === false || value === "false" || value === "0") return false;
|
|
5
|
+
return fallback;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function resolveSessionPolicy({ env = process.env, mode = env.NODE_ENV || "development" } = {}) {
|
|
9
|
+
const production = mode === "production";
|
|
10
|
+
const secret = String(env.SESSION_SECRET || "");
|
|
11
|
+
const cookieName = String(env.SESSION_COOKIE_NAME || "fs_session");
|
|
12
|
+
const secure = parseBool(env.SESSION_COOKIE_SECURE, production);
|
|
13
|
+
const sameSite = String(env.SESSION_COOKIE_SAMESITE || "Lax");
|
|
14
|
+
const maxAgeSec = Number(env.SESSION_MAX_AGE_SEC || 60 * 60 * 24 * 7);
|
|
15
|
+
const rotateOnRead = parseBool(env.SESSION_ROTATE_ON_READ, production);
|
|
16
|
+
|
|
17
|
+
if (production) {
|
|
18
|
+
if (!secret) throw new Error("SESSION_SECRET is required in production.");
|
|
19
|
+
if (secret.length < 32) throw new Error("SESSION_SECRET must be at least 32 characters in production.");
|
|
20
|
+
if (!secure) throw new Error("SESSION_COOKIE_SECURE must be enabled in production.");
|
|
21
|
+
if (!["Lax", "Strict", "None"].includes(sameSite)) {
|
|
22
|
+
throw new Error("SESSION_COOKIE_SAMESITE must be one of: Lax, Strict, None.");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
secret: secret || "fastscript-dev-secret",
|
|
28
|
+
cookie: {
|
|
29
|
+
name: cookieName,
|
|
30
|
+
secure,
|
|
31
|
+
sameSite,
|
|
32
|
+
maxAgeSec: Number.isFinite(maxAgeSec) && maxAgeSec > 0 ? maxAgeSec : 60 * 60 * 24 * 7,
|
|
33
|
+
httpOnly: true,
|
|
34
|
+
path: "/",
|
|
35
|
+
},
|
|
36
|
+
rotateOnRead,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { runServer } from "./server-runtime.mjs";
|
|
3
|
+
|
|
4
|
+
export async function runStart() {
|
|
5
|
+
if (!existsSync("dist/fastscript-manifest.json")) {
|
|
6
|
+
throw new Error("Missing production build. Run: fastscript build");
|
|
7
|
+
}
|
|
8
|
+
const port = Number(process.env.PORT || 4173);
|
|
9
|
+
await runServer({ mode: process.env.NODE_ENV || "production", watchMode: false, buildOnStart: false, port });
|
|
10
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { createHash, createHmac } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join, resolve, sep } from "node:path";
|
|
4
|
+
|
|
5
|
+
function sha(input) {
|
|
6
|
+
return createHash("sha1").update(input).digest("hex");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function sign(payload, secret) {
|
|
10
|
+
return createHmac("sha256", secret).update(payload).digest("hex");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function createKeyResolver(root) {
|
|
14
|
+
const rootPrefix = root.endsWith(sep) ? root : `${root}${sep}`;
|
|
15
|
+
return function safePathFor(key) {
|
|
16
|
+
const normalized = String(key || "").replace(/\\/g, "/").replace(/^\/+/, "");
|
|
17
|
+
if (!normalized || normalized.includes("\0")) {
|
|
18
|
+
const error = new Error("Invalid storage key");
|
|
19
|
+
error.status = 400;
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
const abs = resolve(root, normalized);
|
|
23
|
+
if (abs !== root && !abs.startsWith(rootPrefix)) {
|
|
24
|
+
const error = new Error("Invalid storage key path");
|
|
25
|
+
error.status = 400;
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
return { abs, normalized };
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function readJson(path, fallback) {
|
|
33
|
+
if (!existsSync(path)) return fallback;
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
36
|
+
} catch {
|
|
37
|
+
return fallback;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function createLocalStorage({ dir = ".fastscript/storage", secret = process.env.STORAGE_SIGNING_SECRET || process.env.SESSION_SECRET || "fastscript-dev-storage-secret" } = {}) {
|
|
42
|
+
const root = resolve(dir);
|
|
43
|
+
mkdirSync(root, { recursive: true });
|
|
44
|
+
const safePathFor = createKeyResolver(root);
|
|
45
|
+
const metaPath = join(root, ".meta.json");
|
|
46
|
+
const state = readJson(metaPath, { files: {} });
|
|
47
|
+
|
|
48
|
+
function persistMeta() {
|
|
49
|
+
writeFileSync(metaPath, JSON.stringify(state, null, 2), "utf8");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function setMeta(key, patch = {}) {
|
|
53
|
+
state.files[key] = {
|
|
54
|
+
acl: "public",
|
|
55
|
+
createdAt: Date.now(),
|
|
56
|
+
updatedAt: Date.now(),
|
|
57
|
+
...state.files[key],
|
|
58
|
+
...patch,
|
|
59
|
+
updatedAt: Date.now(),
|
|
60
|
+
};
|
|
61
|
+
persistMeta();
|
|
62
|
+
return state.files[key];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getMeta(key) {
|
|
66
|
+
return state.files[key] || { acl: "public" };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function parseSignedToken(token) {
|
|
70
|
+
const [dataPart, sig] = String(token || "").split(".");
|
|
71
|
+
if (!dataPart || !sig) return null;
|
|
72
|
+
const expected = sign(dataPart, secret);
|
|
73
|
+
if (expected !== sig) return null;
|
|
74
|
+
const json = Buffer.from(dataPart, "base64url").toString("utf8");
|
|
75
|
+
const payload = JSON.parse(json);
|
|
76
|
+
if (payload.exp < Date.now()) return null;
|
|
77
|
+
return payload;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
type: "local",
|
|
82
|
+
put(key, content, opts = {}) {
|
|
83
|
+
const { abs, normalized } = safePathFor(key);
|
|
84
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
85
|
+
writeFileSync(abs, content);
|
|
86
|
+
const meta = setMeta(normalized, { acl: opts.acl || "public" });
|
|
87
|
+
return {
|
|
88
|
+
key: normalized,
|
|
89
|
+
acl: meta.acl,
|
|
90
|
+
etag: sha(Buffer.isBuffer(content) ? content : Buffer.from(String(content))),
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
get(key) {
|
|
94
|
+
const { abs, normalized } = safePathFor(key);
|
|
95
|
+
if (!existsSync(abs)) return null;
|
|
96
|
+
return readFileSync(abs);
|
|
97
|
+
},
|
|
98
|
+
meta(key) {
|
|
99
|
+
const { normalized } = safePathFor(key);
|
|
100
|
+
return { ...getMeta(normalized), key: normalized };
|
|
101
|
+
},
|
|
102
|
+
delete(key) {
|
|
103
|
+
const { abs, normalized } = safePathFor(key);
|
|
104
|
+
rmSync(abs, { force: true });
|
|
105
|
+
delete state.files[normalized];
|
|
106
|
+
persistMeta();
|
|
107
|
+
},
|
|
108
|
+
setAcl(key, acl = "private") {
|
|
109
|
+
const { normalized } = safePathFor(key);
|
|
110
|
+
return setMeta(normalized, { acl });
|
|
111
|
+
},
|
|
112
|
+
url(key) {
|
|
113
|
+
const { normalized } = safePathFor(key);
|
|
114
|
+
return `/__storage/${normalized}`;
|
|
115
|
+
},
|
|
116
|
+
signedUrl(key, { action = "get", expiresInSec = 300 } = {}) {
|
|
117
|
+
const { normalized } = safePathFor(key);
|
|
118
|
+
const payload = {
|
|
119
|
+
key: normalized,
|
|
120
|
+
action,
|
|
121
|
+
exp: Date.now() + Math.max(1, expiresInSec) * 1000,
|
|
122
|
+
};
|
|
123
|
+
const dataPart = Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
124
|
+
const sig = sign(dataPart, secret);
|
|
125
|
+
const token = `${dataPart}.${sig}`;
|
|
126
|
+
return `/__storage/signed?token=${encodeURIComponent(token)}`;
|
|
127
|
+
},
|
|
128
|
+
verifySignedUrl(token) {
|
|
129
|
+
return parseSignedToken(token);
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function createS3CompatibleStorage({ bucket, endpoint, region = "auto", presignBaseUrl } = {}) {
|
|
135
|
+
return {
|
|
136
|
+
type: "s3-compatible",
|
|
137
|
+
bucket,
|
|
138
|
+
endpoint,
|
|
139
|
+
region,
|
|
140
|
+
async putWithPresignedUrl(url, content, contentType = "application/octet-stream") {
|
|
141
|
+
const res = await fetch(url, { method: "PUT", headers: { "content-type": contentType }, body: content });
|
|
142
|
+
if (!res.ok) throw new Error(`S3 upload failed: ${res.status}`);
|
|
143
|
+
return true;
|
|
144
|
+
},
|
|
145
|
+
async getWithPresignedUrl(url) {
|
|
146
|
+
const res = await fetch(url);
|
|
147
|
+
if (!res.ok) throw new Error(`S3 download failed: ${res.status}`);
|
|
148
|
+
return Buffer.from(await res.arrayBuffer());
|
|
149
|
+
},
|
|
150
|
+
presignPath(key, action = "get", acl = "private", expiresInSec = 300) {
|
|
151
|
+
if (!presignBaseUrl) throw new Error("presignBaseUrl is required for presignPath");
|
|
152
|
+
return `${presignBaseUrl}?bucket=${encodeURIComponent(bucket)}&key=${encodeURIComponent(key)}&action=${encodeURIComponent(action)}&acl=${encodeURIComponent(acl)}&exp=${encodeURIComponent(String(expiresInSec))}`;
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|