kaelum 1.2.0 → 1.3.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.
@@ -1,15 +1,26 @@
1
+ // app.js - example project generated by Kaelum (Web template)
1
2
  const kaelum = require("kaelum");
3
+
2
4
  const app = kaelum();
3
5
 
4
- // SetConfig para aplicar configurações de segurança e middlewares
6
+ // Enable basic security + static serving via setConfig (uses Kaelum internals)
5
7
  app.setConfig({
6
8
  cors: true,
7
9
  helmet: true,
10
+ static: "public", // will serve ./public
11
+ bodyParser: true, // default enabled — explicit for clarity
8
12
  });
9
13
 
10
- // Importa e registra as rotas
14
+ // Register routes (routes.js uses Kaelum helpers)
11
15
  const routes = require("./routes");
12
16
  routes(app);
13
17
 
14
- // Inicia o servidor
15
- app.start(3000);
18
+ // optional: health check endpoint
19
+ app.healthCheck("/health");
20
+
21
+ // install Kaelum default error handler (returns JSON on errors)
22
+ app.useErrorHandler({ exposeStack: false });
23
+
24
+ // Start server (explicit port for template demo)
25
+ const PORT = process.env.PORT || 3000;
26
+ app.start(PORT);
@@ -1,6 +1,6 @@
1
- function logger(req, res, next) {
2
- console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
1
+ // example middleware: simple request logger
2
+ module.exports = function (req, res, next) {
3
+ const now = new Date();
4
+ console.log(`[${now.toISOString()}] ${req.method} ${req.originalUrl}`);
3
5
  next();
4
- }
5
-
6
- module.exports = logger;
6
+ };
@@ -1,13 +1,23 @@
1
1
  {
2
- "name": "my-web-app",
3
- "version": "1.0.0",
4
- "description": "Project generated with Kaelum framework.",
5
- "main": "app.js",
2
+ "name": "kaelum-web-app",
3
+ "version": "0.1.0",
4
+ "description": "Exemplo de app gerado pela CLI Kaelum (web template)",
6
5
  "scripts": {
7
- "start": "node app.js"
6
+ "start": "node app.js",
7
+ "dev": "nodemon app.js"
8
8
  },
9
+ "keywords": [
10
+ "kaelum",
11
+ "web"
12
+ ],
13
+ "author": "",
14
+ "license": "MIT",
9
15
  "dependencies": {
10
- "kaelum": "^1.1.0"
16
+ "kaelum": "^1.3.0",
17
+ "cors": "^2.8.5",
18
+ "helmet": "^6.0.0"
11
19
  },
12
- "license": "MIT"
20
+ "devDependencies": {
21
+ "nodemon": "^2.0.22"
22
+ }
13
23
  }
@@ -1,54 +1,61 @@
1
+ :root {
2
+ --bg: #f6fbff;
3
+ --card: #ffffff;
4
+ --accent: #6fa8dc;
5
+ --text: #243746;
6
+ --muted: #6b7a86;
7
+ }
8
+
9
+ * {
10
+ box-sizing: border-box;
11
+ }
1
12
  body {
2
13
  margin: 0;
3
- font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
4
- background-color: #f9f9f9;
5
- color: #333;
14
+ font-family: Inter, system-ui, -apple-system, "Segoe UI", Roboto,
15
+ "Helvetica Neue", Arial;
16
+ background: var(--bg);
17
+ color: var(--text);
18
+ -webkit-font-smoothing: antialiased;
19
+ -moz-osx-font-smoothing: grayscale;
6
20
  }
7
21
 
8
22
  .container {
9
- padding: 40px;
10
- max-width: 800px;
11
- margin: 0 auto;
12
- text-align: center;
13
- }
14
-
15
- h1 {
16
- font-size: 2.5rem;
17
- margin-bottom: 10px;
18
- color: #4a90e2;
23
+ max-width: 900px;
24
+ margin: 48px auto;
25
+ padding: 24px;
19
26
  }
20
27
 
21
- p {
22
- font-size: 1.1rem;
23
- margin-bottom: 30px;
28
+ header h1 {
29
+ margin: 0;
30
+ color: var(--accent);
31
+ font-size: 2.1rem;
24
32
  }
25
33
 
26
- .info {
27
- background-color: #fff;
28
- padding: 20px;
29
- border-radius: 8px;
30
- box-shadow: 0 0 10px rgba(0, 0, 0, 0.05);
34
+ .subtitle {
35
+ margin: 6px 0 18px 0;
36
+ color: var(--muted);
31
37
  }
32
38
 
33
- ul {
34
- list-style-type: none;
35
- padding: 0;
39
+ .content {
40
+ margin-top: 18px;
36
41
  }
37
42
 
38
- li {
39
- text-align: left;
40
- margin-bottom: 10px;
43
+ .cards {
44
+ display: flex;
45
+ gap: 16px;
46
+ margin-top: 12px;
41
47
  }
42
48
 
43
- code {
44
- background-color: #eee;
45
- padding: 2px 6px;
46
- border-radius: 4px;
47
- font-family: monospace;
49
+ .card {
50
+ background: var(--card);
51
+ border-radius: 8px;
52
+ padding: 16px;
53
+ box-shadow: 0 6px 18px rgba(37, 57, 99, 0.06);
54
+ flex: 1;
48
55
  }
49
56
 
50
- footer {
51
- margin-top: 40px;
57
+ .small {
58
+ margin-top: 20px;
59
+ color: var(--muted);
52
60
  font-size: 0.9rem;
53
- color: #999;
54
61
  }
@@ -1,27 +1,32 @@
1
- const logger = require("./middlewares/logger");
1
+ // routes.js - example route declarations using Kaelum API
2
+ const path = require("path");
2
3
 
3
- function Routes(app) {
4
+ module.exports = function (app) {
5
+ // Home: serve the index.html from /views
4
6
  app.addRoute("/", {
5
7
  get: (req, res) => {
6
- res.sendFile("views/index.html", { root: __dirname });
8
+ // send the static HTML file from the views folder
9
+ res.sendFile(path.join(process.cwd(), "views", "index.html"));
7
10
  },
8
- post: (req, res) => res.send("POST: Dados recebidos na página inicial."),
9
11
  });
10
12
 
13
+ // About page - simple text
11
14
  app.addRoute("/about", {
12
- get: (req, res) => res.send("About page"),
15
+ get: (req, res) => {
16
+ res.send("About Kaelum — a minimal framework scaffolded by the CLI.");
17
+ },
13
18
  });
14
19
 
15
- // Rota "/secure" com middleware aplicado diretamente
16
- app.addRoute("/secure", {
17
- get: [
18
- logger,
19
- (req, res) => {
20
- res.send("GET: Área segura! Middleware foi executado.");
21
- },
22
- ],
23
- });
24
-
25
- }
20
+ // Example route using per-path middleware (logger)
21
+ // The middleware is mounted on '/protected' and the route uses it.
22
+ const logger = require("./middlewares/logger");
23
+ app.setMiddleware("/protected", logger);
26
24
 
27
- module.exports = Routes;
25
+ app.addRoute("/protected", {
26
+ get: (req, res) => {
27
+ res.send(
28
+ "This route is protected by a simple request logger middleware."
29
+ );
30
+ },
31
+ });
32
+ };
@@ -1,25 +1,45 @@
1
- <html lang="en">
1
+ <!DOCTYPE html>
2
+ <html lang="pt-BR">
2
3
  <head>
3
- <meta charset="UTF-8" />
4
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
5
- <title>Kaelum.js - Welcome</title>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
6
+ <title>Kaelum Hello</title>
6
7
  <link rel="stylesheet" href="/style.css" />
7
8
  </head>
8
9
  <body>
9
- <div class="container">
10
- <h1>🚀 Welcome to Kaelum.js</h1>
11
- <p>Your minimalist framework to build APIs and web pages easily.</p>
12
- <div class="info">
13
- <h2>🧭 Start exploring</h2>
14
- <ul>
15
- <li><strong>Global Middlewares:</strong> See <code>app.js</code></li>
16
- <li><strong>Routing:</strong> Edit <code>routes.js</code></li>
17
- <li><strong>Static Files:</strong> Inside <code>public/</code></li>
18
- </ul>
19
- </div>
20
- <footer>
21
- <p>Made with ❤️ using Kaelum.js</p>
22
- </footer>
23
- </div>
10
+ <main class="container">
11
+ <header>
12
+ <h1>Kaelum.js</h1>
13
+ <p class="subtitle">
14
+ Framework simplificada para Web e APIs — Template de demonstração
15
+ </p>
16
+ </header>
17
+
18
+ <section class="content">
19
+ <h2>Bem-vindo(a) 👋</h2>
20
+ <p>
21
+ Este é um template gerado automaticamente pela CLI do
22
+ <strong>Kaelum</strong>.
23
+ </p>
24
+
25
+ <div class="cards">
26
+ <div class="card">
27
+ <h3>Rotas</h3>
28
+ <p>
29
+ Veja as rotas: <code>/</code>, <code>/about</code>,
30
+ <code>/protected</code>, <code>/health</code>.
31
+ </p>
32
+ </div>
33
+ <div class="card">
34
+ <h3>Segurança</h3>
35
+ <p>CORS e Helmet foram ativados via <code>app.setConfig</code>.</p>
36
+ </div>
37
+ </div>
38
+
39
+ <p class="small">
40
+ Gerado pela Kaelum CLI • Versão do template para Kaelum ^1.3.0
41
+ </p>
42
+ </section>
43
+ </main>
24
44
  </body>
25
45
  </html>
package/cli/utils.js CHANGED
@@ -1,12 +1,58 @@
1
1
  const fs = require("fs-extra");
2
2
  const path = require("path");
3
3
 
4
- async function copyTemplate(sourceDir, targetDir) {
4
+ /**
5
+ * Copy a template directory to target and update package.json with projectName.
6
+ * @param {string} sourceDir
7
+ * @param {string} targetDir
8
+ * @param {string} projectName (optional) - will override package.json name if present
9
+ */
10
+ async function copyTemplate(sourceDir, targetDir, projectName) {
5
11
  try {
6
- await fs.copy(sourceDir, targetDir);
12
+ if (!sourceDir || !targetDir) {
13
+ throw new Error("Source and target directories are required.");
14
+ }
15
+
16
+ // ensure source exists
17
+ const exists = await fs.pathExists(sourceDir);
18
+ if (!exists) {
19
+ throw new Error(`Template not found: ${sourceDir}`);
20
+ }
21
+
22
+ // copy
23
+ await fs.copy(sourceDir, targetDir, {
24
+ overwrite: false,
25
+ errorOnExist: true,
26
+ });
27
+
28
+ // try update package.json in the copied template
29
+ const pkgPath = path.join(targetDir, "package.json");
30
+ const pkgExists = await fs.pathExists(pkgPath);
31
+ if (pkgExists && projectName) {
32
+ try {
33
+ const raw = await fs.readFile(pkgPath, "utf8");
34
+ const pkg = JSON.parse(raw);
35
+ // set sensible defaults for created project
36
+ pkg.name = projectName;
37
+ if (!pkg.version) pkg.version = "0.1.0";
38
+ if (!pkg.description)
39
+ pkg.description = `${projectName} - generated by Kaelum CLI`;
40
+ // ensure type commonjs by default for templates that expect require/module.exports
41
+ if (!pkg.type) pkg.type = "commonjs";
42
+ await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2), "utf8");
43
+ } catch (e) {
44
+ // non-fatal: warn but continue
45
+ console.warn(
46
+ "Warning: failed to update package.json in the template:",
47
+ e.message
48
+ );
49
+ }
50
+ }
51
+
52
+ return { ok: true };
7
53
  } catch (err) {
8
- console.error("Erro ao copiar o template:", err);
54
+ return { ok: false, error: err.message || String(err) };
9
55
  }
10
56
  }
11
57
 
12
- module.exports = { copyTemplate };
58
+ module.exports = { copyTemplate };
package/core/addRoute.js CHANGED
@@ -1,10 +1,161 @@
1
- function addRoute(app, path, handlers = {}) {
2
- const supportedMethods = ['get', 'post', 'put', 'delete', 'patch'];
1
+ // core/addRoute.js
2
+ // Adds routes to an Express app using a flexible handlers object.
3
+ // Supports:
4
+ // - handlers as a single function (assumed GET)
5
+ // - handlers as an object with HTTP methods (get, post, put, delete, patch, all)
6
+ // - nested subpaths as keys beginning with '/' (e.g. '/:id': { get: fn })
7
+ // - handlers as arrays of functions (middleware chains)
3
8
 
4
- for (const method of supportedMethods) {
5
- if (handlers[method]) {
6
- app[method](path, handlers[method]);
9
+ const supportedMethods = ["get", "post", "put", "delete", "patch", "all"];
10
+
11
+ function isPlainObject(v) {
12
+ return v && typeof v === "object" && !Array.isArray(v);
13
+ }
14
+
15
+ /**
16
+ * Normalize a handler entry into an array of functions.
17
+ * Acceptable inputs: function | [function, function, ...]
18
+ * @param {Function|Function[]} h
19
+ * @returns {Function[]}
20
+ */
21
+ function normalizeHandlersToArray(h) {
22
+ if (typeof h === "function") return [h];
23
+ if (Array.isArray(h)) {
24
+ const invalid = h.find((fn) => typeof fn !== "function");
25
+ if (invalid) {
26
+ throw new Error("Each element in handler array must be a function");
27
+ }
28
+ return h;
29
+ }
30
+ throw new Error("Handler must be a function or an array of functions");
31
+ }
32
+
33
+ /**
34
+ * Wrap a handler function to catch thrown errors / rejected promises
35
+ * and forward them to next(err).
36
+ * @param {Function} fn
37
+ * @returns {Function} async wrapper (req, res, next)
38
+ */
39
+ function wrapHandler(fn) {
40
+ return async function wrapped(req, res, next) {
41
+ try {
42
+ // support handlers that expect next as third argument
43
+ const maybePromise = fn(req, res, next);
44
+ // if handler returns a promise, await it to catch rejections
45
+ if (maybePromise && typeof maybePromise.then === "function") {
46
+ await maybePromise;
47
+ }
48
+ } catch (err) {
49
+ next(err);
50
+ }
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Join basePath and subKey in a safe manner (avoid duplicate slashes).
56
+ * @param {string} basePath
57
+ * @param {string} key
58
+ * @returns {string}
59
+ */
60
+ function joinPaths(basePath, key) {
61
+ if (!basePath.endsWith("/")) basePath = basePath;
62
+ // remove trailing slash from basePath (except if basePath === '/')
63
+ if (basePath !== "/" && basePath.endsWith("/")) {
64
+ basePath = basePath.slice(0, -1);
65
+ }
66
+ // ensure key starts with '/'
67
+ const k = key.startsWith("/") ? key : "/" + key;
68
+ // special case: basePath === '/' -> result is key
69
+ return basePath === "/" ? k : basePath + k;
70
+ }
71
+
72
+ /**
73
+ * Registers routes on the provided Express app.
74
+ * @param {Object} app - Express app instance
75
+ * @param {string} basePath - base route path (e.g. '/users')
76
+ * @param {Function|Object} handlers - single handler function or object map of handlers
77
+ */
78
+ function addRoute(app, basePath, handlers = {}) {
79
+ if (!app || typeof app.use !== "function") {
80
+ throw new Error("Invalid app instance: cannot register routes");
81
+ }
82
+
83
+ if (typeof basePath !== "string") {
84
+ throw new Error("Invalid path: basePath must be a string");
85
+ }
86
+
87
+ // If handlers is a single function, register it as GET
88
+ if (typeof handlers === "function" || Array.isArray(handlers)) {
89
+ const fns = normalizeHandlersToArray(handlers);
90
+ const wrapped = fns.map(wrapHandler);
91
+ app.get(basePath, ...wrapped);
92
+ return;
93
+ }
94
+
95
+ if (!isPlainObject(handlers)) {
96
+ throw new Error(
97
+ "Handlers must be a function, an array of functions, or a plain object"
98
+ );
99
+ }
100
+
101
+ // Iterate keys of handlers
102
+ for (const key of Object.keys(handlers)) {
103
+ const value = handlers[key];
104
+
105
+ // Nested subpath (key starts with '/')
106
+ if (key.startsWith("/")) {
107
+ const subPath = joinPaths(basePath, key);
108
+
109
+ // If nested value is a function or array -> assume GET
110
+ if (typeof value === "function" || Array.isArray(value)) {
111
+ const fns = normalizeHandlersToArray(value);
112
+ const wrapped = fns.map(wrapHandler);
113
+ app.get(subPath, ...wrapped);
114
+ continue;
115
+ }
116
+
117
+ // If nested value is object -> iterate methods
118
+ if (isPlainObject(value)) {
119
+ for (const method of Object.keys(value)) {
120
+ const handlerFn = value[method];
121
+ const m = method.toLowerCase();
122
+ if (!supportedMethods.includes(m)) {
123
+ throw new Error(
124
+ `Unsupported HTTP method "${method}" for route "${subPath}"`
125
+ );
126
+ }
127
+ // allow single function or array for handlerFn
128
+ if (typeof handlerFn !== "function" && !Array.isArray(handlerFn)) {
129
+ throw new Error(
130
+ `Handler for ${method} ${subPath} must be a function or array of functions`
131
+ );
132
+ }
133
+ const fns = normalizeHandlersToArray(handlerFn);
134
+ const wrapped = fns.map(wrapHandler);
135
+ app[m](subPath, ...wrapped);
136
+ }
137
+ continue;
138
+ }
139
+
140
+ throw new Error(`Invalid handler for nested path "${subPath}"`);
141
+ }
142
+
143
+ // Top-level method keys (like 'get', 'post', 'all', etc.)
144
+ const m = key.toLowerCase();
145
+ if (!supportedMethods.includes(m)) {
146
+ throw new Error(
147
+ `Unsupported key "${key}" in handlers for path "${basePath}"`
148
+ );
149
+ }
150
+ const fn = handlers[key];
151
+ if (typeof fn !== "function" && !Array.isArray(fn)) {
152
+ throw new Error(
153
+ `Handler for ${m.toUpperCase()} ${basePath} must be a function or array of functions`
154
+ );
7
155
  }
156
+ const fns = normalizeHandlersToArray(fn);
157
+ const wrapped = fns.map(wrapHandler);
158
+ app[m](basePath, ...wrapped);
8
159
  }
9
160
  }
10
161
 
@@ -0,0 +1,135 @@
1
+ // core/apiRoute.js
2
+ // Provide a simple helper to create RESTful resource routes.
3
+ // Internally uses addRoute(app, basePath, handlers).
4
+ // Supports:
5
+ // - handlers as a single function (assumed GET on collection)
6
+ // - handlers as an object mapping methods and/or nested subpaths
7
+ // - shorthand CRUD generation: pass { crud: true } or { crud: { list, create, show, update, remove } }
8
+
9
+ const addRoute = require("./addRoute");
10
+
11
+ /**
12
+ * Normalize resource into a base path string.
13
+ * @param {string} resource
14
+ * @returns {string} normalized path starting with '/'
15
+ */
16
+ function normalizeResource(resource) {
17
+ if (!resource) return "/";
18
+ if (typeof resource !== "string") {
19
+ throw new Error("resource must be a string");
20
+ }
21
+ let r = resource.trim();
22
+ if (!r.startsWith("/")) r = "/" + r;
23
+ if (r.length > 1 && r.endsWith("/")) r = r.slice(0, -1);
24
+ return r;
25
+ }
26
+
27
+ /**
28
+ * Create a stub handler returning 501 Not Implemented.
29
+ * @param {string} actionName
30
+ * @returns {Function} express handler
31
+ */
32
+ function notImplementedHandler(actionName) {
33
+ return (req, res) => {
34
+ res
35
+ .status(501)
36
+ .json({ error: "Not Implemented", action: actionName || "unknown" });
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Build handlers object for CRUD shorthand.
42
+ * Accepts optional mapping of handler functions.
43
+ *
44
+ * Expected keys supported in `h` (any subset):
45
+ * - list / index -> GET /resource
46
+ * - create -> POST /resource
47
+ * - show / get / getById -> GET /resource/:id
48
+ * - update -> PUT /resource/:id
49
+ * - remove / delete -> DELETE /resource/:id
50
+ *
51
+ * @param {Object|boolean} h - handlers or true to auto-generate stubs
52
+ * @returns {Object} handlers object consumable by addRoute
53
+ */
54
+ function buildCrudHandlers(h) {
55
+ const provided = h && typeof h === "object" ? h : {};
56
+ const pick = (keys) => {
57
+ for (const k of keys) {
58
+ if (typeof provided[k] === "function") return provided[k];
59
+ }
60
+ return null;
61
+ };
62
+
63
+ const listFn = pick(["list", "index"]);
64
+ const createFn = pick(["create"]);
65
+ const showFn = pick(["show", "get", "getById"]);
66
+ const updateFn = pick(["update"]);
67
+ const removeFn = pick(["remove", "delete"]);
68
+
69
+ const handlers = {};
70
+
71
+ // collection endpoints
72
+ handlers.get = listFn || notImplementedHandler("list");
73
+ handlers.post = createFn || notImplementedHandler("create");
74
+
75
+ // member endpoints under '/:id'
76
+ handlers["/:id"] = {
77
+ get: showFn || notImplementedHandler("show"),
78
+ put: updateFn || notImplementedHandler("update"),
79
+ delete: removeFn || notImplementedHandler("remove"),
80
+ };
81
+
82
+ return handlers;
83
+ }
84
+
85
+ /**
86
+ * apiRoute(app, resource, handlers)
87
+ * @param {Object} app - Express app (Kaelum app)
88
+ * @param {string} resource - resource name or path (e.g. 'users' or '/users')
89
+ * @param {Function|Object|boolean} handlers - function, object or shorthand (see README)
90
+ */
91
+ function apiRoute(app, resource, handlers = {}) {
92
+ if (!app || typeof app.use !== "function") {
93
+ throw new Error("Invalid app instance: cannot register apiRoute");
94
+ }
95
+
96
+ const basePath = normalizeResource(resource);
97
+
98
+ // If handlers is a function, assume it's a GET on basePath
99
+ if (typeof handlers === "function") {
100
+ addRoute(app, basePath, handlers);
101
+ return;
102
+ }
103
+
104
+ // If handlers is boolean true -> auto CRUD stubs
105
+ if (handlers === true) {
106
+ const crudHandlers = buildCrudHandlers(true);
107
+ addRoute(app, basePath, crudHandlers);
108
+ return;
109
+ }
110
+
111
+ // If handlers is an object and has a 'crud' key, use it
112
+ if (
113
+ handlers &&
114
+ typeof handlers === "object" &&
115
+ handlers.hasOwnProperty("crud")
116
+ ) {
117
+ const crudSpec = handlers.crud;
118
+ const crudHandlers = buildCrudHandlers(crudSpec);
119
+ addRoute(app, basePath, crudHandlers);
120
+ return;
121
+ }
122
+
123
+ // If handlers is an object, assume it's the direct handlers map for addRoute
124
+ if (handlers && typeof handlers === "object") {
125
+ addRoute(app, basePath, handlers);
126
+ return;
127
+ }
128
+
129
+ // anything else is invalid
130
+ throw new Error(
131
+ "Handlers must be a function, an object, or boolean true for CRUD shorthand"
132
+ );
133
+ }
134
+
135
+ module.exports = apiRoute;