maxserver 0.8.4 → 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/bin/init.js DELETED
@@ -1,86 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import fs from "fs";
4
- import path from "path";
5
- import { fileURLToPath } from "url";
6
- import { execSync } from "child_process";
7
-
8
-
9
- function main() {
10
- const { projectName, targetDir, templateDir } = resolveArgs();
11
-
12
- if (fs.existsSync(targetDir)) {
13
- console.error(`❌ Error: Directory "${projectName}" already exists.`);
14
- process.exit(1);
15
- }
16
-
17
- try {
18
- console.log(`🚀 Setting up "${projectName}"`);
19
- fs.cpSync(templateDir, targetDir, { recursive: true });
20
-
21
- fixDotfiles(targetDir);
22
- patchPackageJson(targetDir, projectName);
23
-
24
- process.chdir(targetDir);
25
- console.log("📦 Installing maxserver");
26
- execSync("npm install maxserver@latest", { stdio: "inherit" });
27
-
28
- console.log(`\n✅ Install complete\n\n->\n\tcd ${projectName}\n\tnpm run dev\n`);
29
- } catch (err) {
30
- console.error("❌ Init failed:", err.message);
31
- process.exit(1);
32
- }
33
- }
34
-
35
-
36
- function resolveArgs() {
37
- const base = path.dirname(fileURLToPath(import.meta.url));
38
- const name = process.argv[2] || "maxserver";
39
-
40
- return {
41
- projectName: name,
42
- targetDir: path.resolve(process.cwd(), name),
43
- templateDir: path.resolve(base, "../templates")
44
- };
45
- }
46
-
47
-
48
- /**
49
- * Renames template files to dotfiles and clones vscode settings.
50
- */
51
- function fixDotfiles(dir) {
52
- // 1. Standard dotfile renames
53
- for (const f of ["env", "gitignore"]) {
54
- const src = path.join(dir, f);
55
- if (fs.existsSync(src)) fs.renameSync(src, path.join(dir, "." + f));
56
- }
57
-
58
- // 2. Specialized vscode handling (Root and src)
59
- const vscodeSrc = path.join(dir, "vscode");
60
-
61
- if (fs.existsSync(vscodeSrc)) {
62
- const srcDir = path.join(dir, "src");
63
-
64
- // Ensure src/ exists before cloning settings into it
65
- if (!fs.existsSync(srcDir)) fs.mkdirSync(srcDir);
66
-
67
- // Copy to src/.vscode
68
- fs.cpSync(vscodeSrc, path.join(srcDir, ".vscode"), { recursive: true });
69
-
70
- // Rename root vscode to .vscode
71
- fs.renameSync(vscodeSrc, path.join(dir, ".vscode"));
72
- }
73
- }
74
-
75
-
76
- function patchPackageJson(dir, name) {
77
- const pkgPath = path.join(dir, "package.json");
78
- if (!fs.existsSync(pkgPath)) return;
79
-
80
- let content = fs.readFileSync(pkgPath, "utf8");
81
- content = content.replace(/__NAME__/g, name);
82
- fs.writeFileSync(pkgPath, content);
83
- }
84
-
85
-
86
- main();
package/bin/watcher.js DELETED
@@ -1,48 +0,0 @@
1
- #!/usr/bin/env node
2
- import { spawn } from "node:child_process";
3
- import fs from "node:fs";
4
-
5
-
6
- /**
7
- * maxserver-watcher
8
- *
9
- * A zero-config minimal development server watcher.
10
- * - Watches the current project root recursively.
11
- * - Restarts the node process on .js/.json changes.
12
- * - Automatically loads .env if present via --env-file.
13
- * - Ignores node_modules and hidden files.
14
- */
15
-
16
-
17
- // Get entry file from args or default to server.js
18
- const entry = process.argv[2] || "server.js";
19
- let child;
20
-
21
- function start() {
22
- const args = [];
23
- if (fs.existsSync(".env")) args.push("--env-file=.env");
24
- args.push(entry);
25
-
26
- // Spawn the node process and share the console
27
- child = spawn("node", args, { stdio: "inherit" });
28
- }
29
-
30
- function restart() {
31
- console.clear();
32
- //console.log(`devserver restart`);
33
- if (child) child.kill();
34
- start();
35
- }
36
-
37
-
38
- fs.watch(".", { recursive: true }, (event, file) => {
39
- if (!file) return;
40
- if (file.includes("node_modules")) return;
41
- if (file.startsWith(".")) return;
42
- if (!file.endsWith(".js") && !file.endsWith(".json")) return;
43
-
44
- //console.log("\nupdated: ", file);
45
- restart();
46
- });
47
-
48
- restart();
package/index.d.ts DELETED
@@ -1,68 +0,0 @@
1
- import "fastify";
2
- import "@fastify/jwt";
3
-
4
- declare function maxserver(...args: any[]): any;
5
- export default maxserver;
6
-
7
- declare global {
8
-
9
- var global: typeof globalThis;
10
-
11
- var ENV: NodeJS.ProcessEnv & {
12
- development: boolean;
13
- production: boolean;
14
- };
15
-
16
- /** Casts a string ID to a MongoDB ObjectId using the global helper. */
17
- var oid: (id?: string) => import("mongodb").ObjectId;
18
-
19
- /** Access to the global MongoDB database instance. */
20
- var db: import("mongodb").Db;
21
-
22
- /**
23
- * Creates an Error object with an attached HTTP status code.
24
- * Fastify catches this to return a structured JSON response.
25
- * @param code The HTTP status code (e.g., 400, 401, 403, 404).
26
- * @param message The specific failure reason returned in the JSON body.
27
- */
28
- var createError: (code: number, message: string) => Error;
29
- }
30
-
31
- declare module "fastify" {
32
- interface FastifyInstance {
33
- maxserver: {
34
- env?: string;
35
- cors?: string | boolean | RegExp | Array<string | RegExp>;
36
- secret?: string;
37
- mongodb?: string;
38
- static?: string;
39
- errorLogger?: boolean;
40
- };
41
- }
42
-
43
- interface FastifyRequest {
44
- userId: string | null;
45
- }
46
-
47
- interface FastifyContextConfig {
48
- auth?: boolean;
49
- }
50
-
51
- interface RouteShorthandOptions {
52
- config?: FastifyContextConfig;
53
- }
54
- }
55
-
56
- declare module "@fastify/jwt" {
57
- interface FastifyJWT {
58
- user: {
59
- sub?: string;
60
- userId?: string;
61
- userid?: string;
62
- id?: string;
63
- [key: string]: unknown;
64
- };
65
- }
66
- }
67
-
68
- export { };
package/src/devSounds.js DELETED
@@ -1,37 +0,0 @@
1
- // Development sounds to make api development even more fun 😃
2
-
3
-
4
- import { exec } from 'child_process';
5
-
6
- export async function setupDevSounds(app) {
7
- if (!app.maxserver.sounds) return;
8
- if (app.maxserver.env === 'production' || process.env.CI) return;
9
-
10
- // Using here some builtin macos sounds see file paths
11
- // For windows we should add alternative
12
- if (process.platform !== 'darwin') return;
13
- const ok = '/System/Library/Sounds/Glass.aiff';
14
- const err = '/System/Library/Sounds/Submarine.aiff';
15
-
16
-
17
-
18
-
19
- const play = file => exec(`afplay "${file}" >/dev/null 2>&1 &`);
20
-
21
- app.addHook('onResponse', async (req, reply) => {
22
-
23
- // Dev sounds only for api requests - not for static served files 😃
24
- const ct = String(reply.getHeader('content-type') || '').toLowerCase();
25
- const disabled = req.routeOptions?.config?.devSound === false;
26
- let trigger = ct && ct.includes('application/json') && !disabled;
27
-
28
- // Though this is route is auto registered by scalar, and we can't set sounds false on it...
29
- if (req.url === "/docs/openapi.json") return;
30
-
31
- if (trigger) {
32
- const code = reply.statusCode;
33
- if (code < 400) play(ok);
34
- else play(err);
35
- }
36
- });
37
- }
package/src/getAddress.js DELETED
@@ -1,46 +0,0 @@
1
- import os from "node:os";
2
-
3
-
4
- /**
5
- * When listening on all interfaces (0.0.0.0), Node returns "0.0.0.0".
6
- * This is a setting, not a valid URL for other devices.
7
- * So we try hunt down the actual LAN IP to print the real address on which server can be accessed
8
- */
9
-
10
-
11
- function getLanIp() {
12
- const nets = os.networkInterfaces();
13
-
14
- for (const name of Object.keys(nets)) {
15
- for (const net of (nets[name] || [])) {
16
- if (net?.family === "IPv4" && !net.internal) return net.address;
17
- }
18
- }
19
-
20
- return null;
21
- }
22
-
23
-
24
- export function getAddress(app) {
25
-
26
- const addr = app.server?.address();
27
- const protocol = app.initialConfig?.https ? "https" : "http";
28
-
29
- if (!addr) return null;
30
- if (typeof addr === "string") return addr;
31
-
32
- const isPublicBind = addr.address === "0.0.0.0" || addr.address === "::";
33
- const isLoopback = addr.address === "127.0.0.1" || addr.address === "::1";
34
-
35
- const envIp = String(process.env.PUBLIC_IP || "").trim() || null;
36
- const detectedIp = envIp || getLanIp();
37
-
38
- const ip = (isPublicBind && !isLoopback)
39
- ? (detectedIp || addr.address)
40
- : "localhost";
41
-
42
- const host = ip.includes(":") ? `[${ip}]` : ip;
43
-
44
- return `${protocol}://${host}:${addr.port}`;
45
- }
46
-
package/src/setup.js DELETED
@@ -1,104 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
-
4
- import cors from "@fastify/cors";
5
- import jwt from "@fastify/jwt";
6
- import cookie from "@fastify/cookie";
7
- import mongodb from "@fastify/mongodb";
8
- import fastifyStatic from "@fastify/static";
9
- import helmet from "@fastify/helmet";
10
-
11
- export async function setupHelmet(app) {
12
- await app.register(helmet, {
13
- contentSecurityPolicy: false,
14
- frameguard: false,
15
- crossOriginResourcePolicy: {
16
- policy: "cross-origin"
17
- }
18
- });
19
- }
20
-
21
- export async function setupCors(app) {
22
- const isProd = app.maxserver.env === "production";
23
- let origin = app.maxserver.cors ?? "*";
24
-
25
- if (origin === "*" && !isProd)
26
- origin = true;
27
-
28
- if (isProd && (origin === "*" || origin === true))
29
- app.log.warn("CORS: allowing all origins in production with credentials is risky");
30
-
31
- await app.register(cors, {
32
- origin,
33
- credentials: true,
34
- methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
35
- });
36
- }
37
-
38
- export async function setupCookie(app) {
39
- await app.register(cookie, {
40
- secret: app.maxserver.secret,
41
- hook: "onRequest"
42
- });
43
- }
44
-
45
- export async function setupMongo(app) {
46
- const url = app.maxserver.mongodb;
47
- if (!url) return;
48
- await app.register(mongodb, { url });
49
-
50
- const { ObjectId, db } = app.mongo;
51
- global.oid = id => new ObjectId(id);
52
- global.db = db;
53
- }
54
-
55
- export async function setupJwt(app) {
56
- await app.register(jwt, {
57
- secret: app.maxserver.secret,
58
- cookie: { cookieName: "token" }
59
- });
60
-
61
- app.addHook("preHandler", async function (req) {
62
- if (req.method === "OPTIONS") return;
63
-
64
- const auth = req.routeOptions?.config?.auth;
65
- if (!auth) return;
66
-
67
- await req.jwtVerify();
68
- const u = req.user;
69
- req.userId = u?.sub || u?.userId || u?.userid || u?.id || null;
70
- });
71
- }
72
-
73
- export async function setupStatic(app) {
74
- const dir = app.maxserver.static;
75
- if (!dir) return;
76
-
77
- if (typeof dir !== "string") {
78
- console.error("❌ maxserver.static must be a string path");
79
- return;
80
- }
81
-
82
- const abs = path.resolve(dir);
83
- if (!fs.existsSync(abs)) {
84
- console.error(`❌ maxserver.static not found: ${abs}`);
85
- return;
86
- }
87
-
88
- await app.register(fastifyStatic, { root: abs });
89
- }
90
-
91
- export async function setupErrorLogger(app) {
92
- if (!app.maxserver.errorLogger) return;
93
-
94
- app.addHook("onError", async (req, res, error) => {
95
- console.log("\n‼️ ERROR ‼️");
96
-
97
- const stackLine = error.stack.split("\n")[1];
98
- const match = stackLine.match(/([^\s()]+):(\d+):\d+/);
99
- const file = match?.[1].replace("file://" + process.cwd(), "");
100
-
101
- if (file) console.log(`${file} -> line ${match[2]}`);
102
- console.log(error.message);
103
- });
104
- }
package/src/setupDocs.js DELETED
@@ -1,78 +0,0 @@
1
- import swagger from "@fastify/swagger";
2
- import apiReference from "@scalar/fastify-api-reference";
3
-
4
-
5
- export async function setupDocs(app) {
6
-
7
- const info = app.maxserver.openapiInfo || {
8
- title: "API",
9
- version: "1.0.0",
10
- };
11
-
12
- await app.register(swagger, {
13
- openapi: {
14
- info,
15
- // OpenAPI 3.x: securitySchemes must be defined globally here not per route
16
- // Routes only add `security: [...]` that references these scheme names
17
- components: {
18
- securitySchemes: {
19
- bearerAuth: { type: "http", scheme: "bearer" },
20
- cookieAuth: { type: "apiKey", in: "cookie", name: "token" },
21
- },
22
- },
23
- },
24
- // Only consider for documentation routes which have a schema
25
- transform: ({ schema, url }) => {
26
- const isDefined = schema && (schema.summary || schema.response || schema.body);
27
- if (!isDefined) return { schema: { hide: true }, url };
28
- return { schema, url };
29
- }
30
- });
31
-
32
- // not needed, the scalar extension provides it by default on /docs/openapi.json
33
- // app.get("/openapi.json", {schema}, () => app.swagger());
34
-
35
-
36
- if (app.maxserver.docs !== false)
37
- await app.register(apiReference, {
38
- "routePrefix": "/docs",
39
- "openapi": true,
40
- "configuration": {
41
- hideSearch: true,
42
- hiddenClients: true,
43
- hideClientButton: true,
44
- telemetry: false,
45
- persistAuth: true,
46
- showDeveloperTools: "never",
47
- //"expandAllModelSections": true,
48
- // operationsSorter: "alpha",
49
- orderSchemaPropertiesBy: "preserve",
50
- metaData: {
51
- title: "API Docs 👨‍💻",
52
- },
53
- authentication: {
54
- preferredSecurityScheme: 'bearerAuth',
55
- apiKey: { token: "" }
56
- },
57
- customCss: `
58
- .darklight-reference a[href*="scalar.com"] {
59
- display: none !important;
60
- },
61
- .text-sidebar-c-2 {
62
- display: none !important;
63
- }`,
64
- ...app.maxserver.scalar
65
- }
66
- });
67
-
68
- app.addHook("onRoute", (route) => {
69
- const auth = route.config?.auth;
70
- if (!auth) return;
71
- route.schema ||= {};
72
- route.schema.security ||= [{ bearerAuth: [] }, { cookieAuth: [] }];
73
- });
74
- }
75
-
76
-
77
-
78
-
@@ -1,164 +0,0 @@
1
- /**
2
- * 🚀 AUTO-LOADER
3
- * Scans src/ for files with "// METHOD /url" comments.
4
- * Automatically registers them as Fastify routes.
5
- * Also registers lonely .schema.js files as global shared schemas.
6
- */
7
-
8
- import fs from "fs";
9
- import path from "path";
10
- import { pathToFileURL } from "url";
11
-
12
- // Matches lines like: // GET /api/v1/users
13
- const ROUTE_REGEX = /^\/\/\s*(GET|POST|PUT|PATCH|DELETE)\s+(.+)$/gm;
14
-
15
- /**
16
- * Recursively finds all .js files in a directory.
17
- */
18
- function walk(dir, out = []) {
19
- if (!fs.existsSync(dir)) return out;
20
-
21
- for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
22
- if (e.name === "node_modules" || e.name.startsWith(".")) continue;
23
-
24
- const full = path.join(dir, e.name);
25
- if (e.isDirectory()) {
26
- walk(full, out);
27
- continue;
28
- }
29
-
30
- if (e.name.endsWith(".js")) out.push(full);
31
- }
32
-
33
- return out;
34
- }
35
-
36
- /**
37
- * Extracts method and URL from the file's "magic comment".
38
- */
39
- function getRoute(file) {
40
- const text = fs.readFileSync(file, "utf8");
41
- const matches = [...text.matchAll(ROUTE_REGEX)];
42
-
43
- if (matches.length === 0) return null;
44
-
45
- if (matches.length > 1) {
46
- console.warn(`⚠️ Ignored "${file}": Only 1 route allowed per file.`);
47
- return null;
48
- }
49
-
50
- const m = matches[0];
51
- return {
52
- method: m[1].toLowerCase(),
53
- url: "/" + m[2].trim().replace(/^\/+/, "")
54
- };
55
- }
56
-
57
- export async function setupRoutes(app) {
58
- const root = path.resolve(app.maxserver.routesDir || "src");
59
- const files = walk(root);
60
-
61
- // 1. Pass One: Register Named Exports Globally
62
- const globals = new Map();
63
- for (const file of files) {
64
- if (file.endsWith(".schema.js")) continue;
65
-
66
- const mod = await import(pathToFileURL(file).href);
67
- for (const [key, val] of Object.entries(mod)) {
68
- if (key === "default" || key.startsWith("autoregister_")) continue;
69
-
70
- if (globals.has(key)) {
71
- console.error("\n❌ Global Identifier Conflict!");
72
- console.error(`The export "${key}" is defined in multiple files:`);
73
- console.error(` -> ${globals.get(key)}`);
74
- console.error(` -> ${file}\n`);
75
- throw new Error(`Duplicate global identifier "${key}"`);
76
- }
77
-
78
- globals.set(key, file);
79
- global[key] = val;
80
- }
81
- }
82
-
83
- // 2. Pass Two: Register Global Schemas (Lonely .schema.js files)
84
- for (const file of files) {
85
- const isLonely = file.endsWith(".schema.js") &&
86
- !fs.existsSync(file.replace(".schema.js", ".js"));
87
-
88
- if (!isLonely) continue;
89
-
90
- const mod = await import(pathToFileURL(file).href);
91
- for (const schema of Object.values(mod))
92
- if (schema?.$id) app.addSchema(schema);
93
- }
94
-
95
- // 3. Pass Three: Auto-register hooks
96
- for (const file of files) {
97
- if (file.endsWith(".schema.js")) continue;
98
-
99
- const mod = await import(pathToFileURL(file).href);
100
- for (const [key, fn] of Object.entries(mod))
101
- if (key.startsWith("autoregister_") && typeof fn === "function") await fn(app);
102
- }
103
-
104
- // 4. Pass Four: Collect and Group Routes by Directory
105
- const groups = new Map();
106
- for (const file of files) {
107
- if (file.endsWith(".schema.js")) continue;
108
-
109
- const info = getRoute(file);
110
- if (!info) continue;
111
-
112
- const mod = await import(pathToFileURL(file).href);
113
- const handler = mod.default;
114
- if (typeof handler !== "function") {
115
- throw new Error(`Route in "${file}" must export a default function.`);
116
- }
117
-
118
- const schemaFile = file.replace(/\.js$/, ".schema.js");
119
- let raw = {};
120
-
121
- if (fs.existsSync(schemaFile)) {
122
- const loaded = (await import(pathToFileURL(schemaFile).href)).default;
123
- if (loaded && typeof loaded === "object") raw = loaded;
124
- }
125
-
126
- let { auth, order = 999, routeOptions = {}, ...schema } = raw;
127
-
128
- if (auth !== undefined) {
129
- routeOptions = {
130
- ...routeOptions,
131
- config: { ...(routeOptions.config || {}), auth: !!auth },
132
- };
133
- }
134
-
135
- const dir = path.dirname(file);
136
- if (!groups.has(dir)) groups.set(dir, []);
137
-
138
- groups.get(dir).push({
139
- method: info.method,
140
- url: info.url,
141
- options: { ...routeOptions, schema },
142
- handler,
143
- order,
144
- file
145
- });
146
- }
147
-
148
- // Sort routes locally within each directory
149
- const routes = [];
150
- for (const list of groups.values()) {
151
- list.sort((a, b) => a.order - b.order);
152
- routes.push(...list);
153
- }
154
-
155
- // 5. Pass Five: Register Sorted Routes
156
- const seen = new Map();
157
- for (const r of routes) {
158
- const key = `${r.method} ${r.url}`;
159
- if (seen.has(key)) throw new Error(`Duplicate route "${key}" detected.`);
160
- seen.set(key, r.file);
161
-
162
- app[r.method](r.url, r.options, r.handler);
163
- }
164
- }
package/templates/ai/BASE DELETED
@@ -1,2 +0,0 @@
1
- @node
2
- @js
@@ -1 +0,0 @@
1
- node_modules
@@ -1,31 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "checkJs": true,
4
- "module": "ES2020",
5
- "target": "ES2020",
6
- "moduleResolution": "bundler",
7
- "jsx": "react-jsx",
8
- "strict": false,
9
- "noUnusedParameters": false,
10
- "noUnusedLocals": false,
11
- "noImplicitAny": false,
12
- // new attempt to reduce noise
13
- "skipLibCheck": true,
14
- //"baseUrl": ".",
15
- "paths": {
16
- "*": [
17
- "./src/*"
18
- ],
19
- },
20
- "types": [
21
- "node",
22
- "maxserver"
23
- ]
24
- },
25
- "include": [
26
- "src"
27
- ],
28
- "exclude": [
29
- "node_modules",
30
- ],
31
- }
@@ -1,13 +0,0 @@
1
- {
2
- "name": "__NAME__",
3
- "version": "1.0.0",
4
- "description": "",
5
- "main": "server.js",
6
- "scripts": {
7
- "start": "node server.js",
8
- "dev": "maxserver-watcher"
9
- },
10
- "type": "module",
11
- "private": "true",
12
- "dependencies": {}
13
- }