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.
Files changed (109) hide show
  1. package/CHANGELOG.md +32 -7
  2. package/LICENSE +33 -21
  3. package/README.md +567 -73
  4. package/node_modules/@fastscript/core-private/BOUNDARY.json +15 -0
  5. package/node_modules/@fastscript/core-private/README.md +5 -0
  6. package/node_modules/@fastscript/core-private/package.json +34 -0
  7. package/node_modules/@fastscript/core-private/src/asset-optimizer.mjs +67 -0
  8. package/node_modules/@fastscript/core-private/src/audit-log.mjs +50 -0
  9. package/node_modules/@fastscript/core-private/src/auth-flows.mjs +29 -0
  10. package/node_modules/@fastscript/core-private/src/auth.mjs +115 -0
  11. package/node_modules/@fastscript/core-private/src/bench.mjs +45 -0
  12. package/node_modules/@fastscript/core-private/src/build.mjs +670 -0
  13. package/node_modules/@fastscript/core-private/src/cache.mjs +248 -0
  14. package/node_modules/@fastscript/core-private/src/check.mjs +22 -0
  15. package/node_modules/@fastscript/core-private/src/cli.mjs +95 -0
  16. package/node_modules/@fastscript/core-private/src/compat.mjs +128 -0
  17. package/node_modules/@fastscript/core-private/src/create.mjs +278 -0
  18. package/node_modules/@fastscript/core-private/src/csp.mjs +26 -0
  19. package/node_modules/@fastscript/core-private/src/db-cli.mjs +185 -0
  20. package/node_modules/@fastscript/core-private/src/db-postgres-collection.mjs +110 -0
  21. package/node_modules/@fastscript/core-private/src/db-postgres.mjs +40 -0
  22. package/node_modules/@fastscript/core-private/src/db.mjs +103 -0
  23. package/node_modules/@fastscript/core-private/src/deploy.mjs +662 -0
  24. package/node_modules/@fastscript/core-private/src/dev.mjs +5 -0
  25. package/node_modules/@fastscript/core-private/src/docs-search.mjs +35 -0
  26. package/node_modules/@fastscript/core-private/src/env.mjs +118 -0
  27. package/node_modules/@fastscript/core-private/src/export.mjs +83 -0
  28. package/node_modules/@fastscript/core-private/src/fs-diagnostics.mjs +70 -0
  29. package/node_modules/@fastscript/core-private/src/fs-error-codes.mjs +141 -0
  30. package/node_modules/@fastscript/core-private/src/fs-formatter.mjs +66 -0
  31. package/node_modules/@fastscript/core-private/src/fs-linter.mjs +274 -0
  32. package/node_modules/@fastscript/core-private/src/fs-normalize.mjs +91 -0
  33. package/node_modules/@fastscript/core-private/src/fs-parser.mjs +980 -0
  34. package/node_modules/@fastscript/core-private/src/generated/docs-search-index.mjs +3182 -0
  35. package/node_modules/@fastscript/core-private/src/i18n.mjs +25 -0
  36. package/node_modules/@fastscript/core-private/src/interop.mjs +16 -0
  37. package/node_modules/@fastscript/core-private/src/jobs.mjs +378 -0
  38. package/node_modules/@fastscript/core-private/src/logger.mjs +27 -0
  39. package/node_modules/@fastscript/core-private/src/metrics.mjs +45 -0
  40. package/node_modules/@fastscript/core-private/src/middleware.mjs +14 -0
  41. package/node_modules/@fastscript/core-private/src/migrate.mjs +81 -0
  42. package/node_modules/@fastscript/core-private/src/migration-wizard.mjs +16 -0
  43. package/node_modules/@fastscript/core-private/src/module-loader.mjs +46 -0
  44. package/node_modules/@fastscript/core-private/src/oauth-providers.mjs +103 -0
  45. package/node_modules/@fastscript/core-private/src/observability.mjs +21 -0
  46. package/node_modules/@fastscript/core-private/src/plugins.mjs +194 -0
  47. package/node_modules/@fastscript/core-private/src/retention.mjs +57 -0
  48. package/node_modules/@fastscript/core-private/src/routes.mjs +178 -0
  49. package/node_modules/@fastscript/core-private/src/scheduler.mjs +104 -0
  50. package/node_modules/@fastscript/core-private/src/security.mjs +233 -0
  51. package/node_modules/@fastscript/core-private/src/server-runtime.mjs +849 -0
  52. package/node_modules/@fastscript/core-private/src/serverless-handler.mjs +20 -0
  53. package/node_modules/@fastscript/core-private/src/session-policy.mjs +38 -0
  54. package/node_modules/@fastscript/core-private/src/start.mjs +10 -0
  55. package/node_modules/@fastscript/core-private/src/storage.mjs +155 -0
  56. package/node_modules/@fastscript/core-private/src/style-primitives.mjs +538 -0
  57. package/node_modules/@fastscript/core-private/src/style-system.mjs +461 -0
  58. package/node_modules/@fastscript/core-private/src/tenant.mjs +55 -0
  59. package/node_modules/@fastscript/core-private/src/typecheck.mjs +1464 -0
  60. package/node_modules/@fastscript/core-private/src/validate.mjs +22 -0
  61. package/node_modules/@fastscript/core-private/src/validation.mjs +88 -0
  62. package/node_modules/@fastscript/core-private/src/webhook.mjs +81 -0
  63. package/node_modules/@fastscript/core-private/src/worker.mjs +24 -0
  64. package/package.json +86 -13
  65. package/src/asset-optimizer.mjs +67 -0
  66. package/src/audit-log.mjs +50 -0
  67. package/src/auth.mjs +1 -115
  68. package/src/bench.mjs +20 -7
  69. package/src/build.mjs +1 -234
  70. package/src/cache.mjs +210 -20
  71. package/src/cli.mjs +29 -5
  72. package/src/compat.mjs +8 -10
  73. package/src/create.mjs +71 -17
  74. package/src/csp.mjs +26 -0
  75. package/src/db-cli.mjs +152 -8
  76. package/src/db-postgres-collection.mjs +110 -0
  77. package/src/deploy.mjs +1 -65
  78. package/src/docs-search.mjs +35 -0
  79. package/src/env.mjs +34 -5
  80. package/src/fs-diagnostics.mjs +70 -0
  81. package/src/fs-error-codes.mjs +126 -0
  82. package/src/fs-formatter.mjs +66 -0
  83. package/src/fs-linter.mjs +274 -0
  84. package/src/fs-normalize.mjs +21 -238
  85. package/src/fs-parser.mjs +1 -0
  86. package/src/generated/docs-search-index.mjs +3220 -0
  87. package/src/i18n.mjs +25 -0
  88. package/src/jobs.mjs +283 -32
  89. package/src/metrics.mjs +45 -0
  90. package/src/migration-wizard.mjs +16 -0
  91. package/src/module-loader.mjs +11 -12
  92. package/src/oauth-providers.mjs +103 -0
  93. package/src/plugins.mjs +194 -0
  94. package/src/retention.mjs +57 -0
  95. package/src/routes.mjs +178 -0
  96. package/src/scheduler.mjs +104 -0
  97. package/src/security.mjs +197 -19
  98. package/src/server-runtime.mjs +1 -339
  99. package/src/serverless-handler.mjs +20 -0
  100. package/src/session-policy.mjs +38 -0
  101. package/src/storage.mjs +1 -56
  102. package/src/style-system.mjs +461 -0
  103. package/src/tenant.mjs +55 -0
  104. package/src/typecheck.mjs +1 -0
  105. package/src/validate.mjs +5 -1
  106. package/src/validation.mjs +14 -5
  107. package/src/webhook.mjs +1 -71
  108. package/src/worker.mjs +23 -4
  109. package/src/language-spec.mjs +0 -58
@@ -0,0 +1,274 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
2
+ import { extname, join, resolve } from "node:path";
3
+ import { parseFastScript } from "./fs-parser.mjs";
4
+
5
+ function walk(dir) {
6
+ if (!existsSync(dir)) return [];
7
+ const out = [];
8
+ for (const entry of readdirSync(dir)) {
9
+ const full = join(dir, entry);
10
+ const st = statSync(full);
11
+ if (st.isDirectory()) out.push(...walk(full));
12
+ else if (st.isFile()) out.push(full);
13
+ }
14
+ return out;
15
+ }
16
+
17
+ function spanFromNode(node, sourceLength) {
18
+ const start = Math.max(0, Math.min(sourceLength, Number(node?.fsRange?.[0] ?? node?.start ?? 0)));
19
+ const end = Math.max(start, Math.min(sourceLength, Number(node?.fsRange?.[1] ?? node?.end ?? start)));
20
+ return { start, end };
21
+ }
22
+
23
+ function createIssue({ file, source, lineStarts, code, severity, message, span, fix = null }) {
24
+ const start = Math.max(0, Math.min(source.length, span?.start ?? 0));
25
+ const end = Math.max(start, Math.min(source.length, span?.end ?? start + 1));
26
+ const loc = lineFromOffset(lineStarts, start);
27
+ return {
28
+ file,
29
+ line: loc.line,
30
+ column: loc.column,
31
+ span: { start, end },
32
+ code,
33
+ severity,
34
+ message,
35
+ fix,
36
+ };
37
+ }
38
+
39
+ function createLineStarts(source) {
40
+ const out = [0];
41
+ for (let i = 0; i < source.length; i += 1) {
42
+ if (source[i] === "\n") out.push(i + 1);
43
+ }
44
+ return out;
45
+ }
46
+
47
+ function lineFromOffset(lineStarts, offset) {
48
+ let lo = 0;
49
+ let hi = lineStarts.length - 1;
50
+ while (lo <= hi) {
51
+ const mid = (lo + hi) >> 1;
52
+ const start = lineStarts[mid];
53
+ const next = lineStarts[mid + 1] ?? Number.POSITIVE_INFINITY;
54
+ if (offset < start) hi = mid - 1;
55
+ else if (offset >= next) lo = mid + 1;
56
+ else return { line: mid + 1, column: offset - start + 1 };
57
+ }
58
+ return { line: 1, column: 1 };
59
+ }
60
+
61
+ function traverse(node, visitor, parent = null) {
62
+ if (!node || typeof node !== "object") return;
63
+ visitor(node, parent);
64
+ for (const value of Object.values(node)) {
65
+ if (!value) continue;
66
+ if (Array.isArray(value)) {
67
+ for (const item of value) {
68
+ if (item && typeof item === "object") traverse(item, visitor, node);
69
+ }
70
+ } else if (typeof value === "object" && typeof value.type === "string") {
71
+ traverse(value, visitor, node);
72
+ }
73
+ }
74
+ }
75
+
76
+ function collectReassignedNames(estree) {
77
+ const names = new Set();
78
+ traverse(estree, (node) => {
79
+ if (node.type === "AssignmentExpression" && node.left?.type === "Identifier") {
80
+ names.add(node.left.name);
81
+ }
82
+ if (node.type === "UpdateExpression" && node.argument?.type === "Identifier") {
83
+ names.add(node.argument.name);
84
+ }
85
+ });
86
+ return names;
87
+ }
88
+
89
+ function declarationKeywordFixSpan(source, declaration, keyword) {
90
+ const span = spanFromNode(declaration, source.length);
91
+ const window = source.slice(span.start, Math.min(span.end, span.start + 64));
92
+ const pattern = new RegExp(`\\b${keyword}\\b`);
93
+ const match = pattern.exec(window);
94
+ if (!match) return null;
95
+ return {
96
+ start: span.start + match.index,
97
+ end: span.start + match.index + keyword.length,
98
+ };
99
+ }
100
+
101
+ function applyFixes(source, fixes) {
102
+ if (!fixes.length) return source;
103
+ const sorted = [...fixes]
104
+ .filter((fix) => fix && Number.isFinite(fix.start) && Number.isFinite(fix.end) && fix.start <= fix.end)
105
+ .sort((a, b) => {
106
+ if (a.start !== b.start) return a.start - b.start;
107
+ return a.end - b.end;
108
+ });
109
+
110
+ const nonOverlapping = [];
111
+ let cursor = -1;
112
+ for (const fix of sorted) {
113
+ if (fix.start < cursor) continue;
114
+ nonOverlapping.push(fix);
115
+ cursor = fix.end;
116
+ }
117
+
118
+ let output = source;
119
+ for (let i = nonOverlapping.length - 1; i >= 0; i -= 1) {
120
+ const fix = nonOverlapping[i];
121
+ output = `${output.slice(0, fix.start)}${fix.text}${output.slice(fix.end)}`;
122
+ }
123
+ return output;
124
+ }
125
+
126
+ function lintSource(source, { file }) {
127
+ const text = String(source ?? "");
128
+ const lineStarts = createLineStarts(text);
129
+ const parsed = parseFastScript(text, { file, mode: "lenient", recover: true });
130
+ const issues = [];
131
+ const fixes = [];
132
+
133
+ for (const diagnostic of parsed.diagnostics) {
134
+ issues.push(
135
+ createIssue({
136
+ file,
137
+ source: text,
138
+ lineStarts,
139
+ code: diagnostic.code,
140
+ severity: diagnostic.severity || "error",
141
+ message: diagnostic.message,
142
+ span: diagnostic.span,
143
+ }),
144
+ );
145
+ }
146
+
147
+ const todoPattern = /\bTODO_ERROR\b/g;
148
+ for (const match of text.matchAll(todoPattern)) {
149
+ const start = match.index ?? 0;
150
+ issues.push(
151
+ createIssue({
152
+ file,
153
+ source: text,
154
+ lineStarts,
155
+ code: "FS3001",
156
+ severity: "error",
157
+ message: "Remove TODO_ERROR token.",
158
+ span: { start, end: start + match[0].length },
159
+ }),
160
+ );
161
+ }
162
+
163
+ if (!parsed.estree) return { issues, fixes };
164
+
165
+ const reassignedNames = collectReassignedNames(parsed.estree);
166
+ traverse(parsed.estree, (node) => {
167
+ if (node.type === "VariableDeclaration") {
168
+ if (node.kind === "var") {
169
+ const fixSpan = declarationKeywordFixSpan(text, node, "var");
170
+ const fix = fixSpan ? { ...fixSpan, text: "let" } : null;
171
+ issues.push(
172
+ createIssue({
173
+ file,
174
+ source: text,
175
+ lineStarts,
176
+ code: "FS3002",
177
+ severity: "warning",
178
+ message: "Prefer `let` or `const` over `var`.",
179
+ span: spanFromNode(node, text.length),
180
+ fix,
181
+ }),
182
+ );
183
+ if (fix) fixes.push(fix);
184
+ }
185
+
186
+ if (node.kind === "let") {
187
+ const names = [];
188
+ for (const declaration of node.declarations || []) {
189
+ if (declaration.id?.type === "Identifier") names.push(declaration.id.name);
190
+ }
191
+ if (names.length > 0 && names.every((name) => !reassignedNames.has(name))) {
192
+ const fixSpan = declarationKeywordFixSpan(text, node, "let");
193
+ const fix = fixSpan ? { ...fixSpan, text: "const" } : null;
194
+ issues.push(
195
+ createIssue({
196
+ file,
197
+ source: text,
198
+ lineStarts,
199
+ code: "FS3004",
200
+ severity: "warning",
201
+ message: "Binding can be `const`.",
202
+ span: spanFromNode(node, text.length),
203
+ fix,
204
+ }),
205
+ );
206
+ if (fix) fixes.push(fix);
207
+ }
208
+ }
209
+ }
210
+
211
+ if (node.type === "TemplateElement" && /<script[\s>]/i.test(node.value?.raw || "")) {
212
+ issues.push(
213
+ createIssue({
214
+ file,
215
+ source: text,
216
+ lineStarts,
217
+ code: "FS3003",
218
+ severity: "warning",
219
+ message: "Avoid inline <script> tags in template literals.",
220
+ span: spanFromNode(node, text.length),
221
+ }),
222
+ );
223
+ }
224
+ });
225
+
226
+ return { issues, fixes };
227
+ }
228
+
229
+ export async function runLint(args = []) {
230
+ let target = "app";
231
+ let mode = "fail";
232
+ let fix = false;
233
+ for (let i = 0; i < args.length; i += 1) {
234
+ if (args[i] === "--path") target = args[i + 1] || target;
235
+ if (args[i] === "--mode") mode = (args[i + 1] || mode).toLowerCase();
236
+ if (args[i] === "--fix") fix = true;
237
+ }
238
+
239
+ const base = resolve(target);
240
+ const files = walk(base).filter((file) => extname(file) === ".fs");
241
+ const issues = [];
242
+
243
+ for (const file of files) {
244
+ const source = readFileSync(file, "utf8");
245
+ const result = lintSource(source, { file });
246
+ let next = source;
247
+ if (fix && result.fixes.length > 0) {
248
+ next = applyFixes(source, result.fixes);
249
+ if (next !== source) writeFileSync(file, next, "utf8");
250
+ }
251
+ issues.push(...result.issues);
252
+ }
253
+
254
+ issues.sort((a, b) => {
255
+ if (a.file !== b.file) return a.file.localeCompare(b.file);
256
+ if (a.span.start !== b.span.start) return a.span.start - b.span.start;
257
+ if (a.code !== b.code) return a.code.localeCompare(b.code);
258
+ return a.message.localeCompare(b.message);
259
+ });
260
+
261
+ for (const issue of issues) {
262
+ console.log(`${issue.file}:${issue.line}:${issue.column} ${issue.code} ${issue.severity} ${issue.message}`);
263
+ }
264
+
265
+ const blocking = issues.filter((issue) => issue.severity === "error");
266
+ if (blocking.length > 0 && mode !== "pass") {
267
+ const error = new Error(`lint failed: ${blocking.length} blocking issue(s)`);
268
+ error.status = 1;
269
+ error.details = blocking;
270
+ throw error;
271
+ }
272
+
273
+ console.log(`lint complete: ${files.length} file(s), ${issues.length} issue(s), mode=${mode}, fix=${fix}`);
274
+ }
@@ -0,0 +1,91 @@
1
+ import { compileFastScript } from "./fs-parser.mjs";
2
+
3
+ export function normalizeFastScript(source, options = {}) {
4
+ const { code } = compileFastScript(source, {
5
+ file: options.file || "",
6
+ mode: options.mode || "lenient",
7
+ recover: options.recover !== false,
8
+ inlineSourceMap: options.sourceMap === "inline",
9
+ });
10
+ return code;
11
+ }
12
+
13
+ export function normalizeFastScriptWithMetadata(source, options = {}) {
14
+ return compileFastScript(source, {
15
+ file: options.file || "",
16
+ mode: options.mode || "lenient",
17
+ recover: options.recover !== false,
18
+ inlineSourceMap: options.sourceMap === "inline",
19
+ });
20
+ }
21
+
22
+ export function stripTypeScriptHints(source) {
23
+ const lines = source.split(/\r?\n/);
24
+ const out = [];
25
+ let skippingBlock = false;
26
+ let blockDepth = 0;
27
+
28
+ for (let i = 0; i < lines.length; i += 1) {
29
+ const line = lines[i];
30
+ let next = line;
31
+
32
+ if (skippingBlock) {
33
+ const opens = (next.match(/{/g) || []).length;
34
+ const closes = (next.match(/}/g) || []).length;
35
+ blockDepth += opens - closes;
36
+ if (blockDepth <= 0) {
37
+ skippingBlock = false;
38
+ blockDepth = 0;
39
+ }
40
+ continue;
41
+ }
42
+
43
+ if (/^\s*interface\s+[A-Za-z_$][\w$]*\s*[{]/.test(next) || /^\s*enum\s+[A-Za-z_$][\w$]*\s*[{]/.test(next)) {
44
+ out.push(`// ${next.trim()} (removed by fastscript migrate)`);
45
+ skippingBlock = true;
46
+ const opens = (next.match(/{/g) || []).length;
47
+ const closes = (next.match(/}/g) || []).length;
48
+ blockDepth = Math.max(1, opens - closes);
49
+ continue;
50
+ }
51
+
52
+ if (/^\s*type\s+[A-Za-z_$][\w$]*\s*=/.test(next)) {
53
+ out.push(`// ${next.trim()} (removed by fastscript migrate)`);
54
+ if (!next.includes(";") && next.includes("{")) {
55
+ skippingBlock = true;
56
+ const opens = (next.match(/{/g) || []).length;
57
+ const closes = (next.match(/}/g) || []).length;
58
+ blockDepth = Math.max(1, opens - closes);
59
+ }
60
+ continue;
61
+ }
62
+
63
+ next = next.replace(/\bimport\s+type\b/g, "import");
64
+ next = next.replace(/\bexport\s+type\b/g, "export");
65
+
66
+ next = next.replace(
67
+ /^(\s*)(const|let|var)\s+([A-Za-z_$][\w$]*)\s*:\s*([^=;]+)([=;].*)$/,
68
+ "$1$2 $3 $5",
69
+ );
70
+
71
+ if (/\bfunction\b/.test(next) || /\)\s*=>/.test(next)) {
72
+ next = next.replace(/\(([^)]*)\)/, (_, params) => {
73
+ const cleaned = params.replace(
74
+ /([A-Za-z_$][\w$]*)\s*:\s*([A-Za-z_$][\w$<>\[\]\|&, ?.]*)/g,
75
+ "$1",
76
+ );
77
+ return `(${cleaned})`;
78
+ });
79
+ next = next.replace(/\)\s*:\s*([A-Za-z_$][\w$<>\[\]\|&, ?.]*)\s*\{/g, ") {");
80
+ next = next.replace(/\bfunction\s+([A-Za-z_$][\w$]*)\s*<[^>]+>\s*\(/g, "function $1(");
81
+ }
82
+
83
+ next = next.replace(/^\s*<([A-Za-z_$][\w$,\s]*)>\s*\(/, "(");
84
+ next = next.replace(/\)\s*=>\s*<[A-Za-z_$][\w$<>\[\]\|&, ?.]*>/g, ") =>");
85
+ next = next.replace(/\sas\s+const\b/g, "");
86
+ next = next.replace(/\s+satisfies\s+[A-Za-z_$][\w$<>\[\]\|&, ?.]*/g, "");
87
+ out.push(next);
88
+ }
89
+
90
+ return out.join("\n");
91
+ }