lumpiajs 1.0.6 → 1.0.8

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/lib/core/DB.js ADDED
@@ -0,0 +1,139 @@
1
+ import mysql from 'mysql2/promise';
2
+ import { loadEnv } from './Env.js';
3
+
4
+ let pool = null;
5
+
6
+ export class DB {
7
+ static async connect() {
8
+ if (pool) return pool;
9
+
10
+ const env = loadEnv(process.cwd());
11
+
12
+ if (!env.DB_HOST || !env.DB_USER || !env.DB_NAME) {
13
+ return null;
14
+ }
15
+
16
+ try {
17
+ pool = mysql.createPool({
18
+ host: env.DB_HOST,
19
+ user: env.DB_USER,
20
+ password: env.DB_PASSWORD || '',
21
+ database: env.DB_NAME,
22
+ waitForConnections: true,
23
+ connectionLimit: 10,
24
+ queueLimit: 0
25
+ });
26
+ return pool;
27
+ } catch (e) {
28
+ console.error("❌ Gagal connect database:", e.message);
29
+ return null;
30
+ }
31
+ }
32
+
33
+ static async query(sql, params = []) {
34
+ const p = await this.connect();
35
+ if (!p) throw new Error("Database durung disetting nang .env!");
36
+ try {
37
+ const [rows, fields] = await p.execute(sql, params);
38
+ return rows;
39
+ } catch (e) {
40
+ console.error("❌ SQL Error:", e.message);
41
+ throw e;
42
+ }
43
+ }
44
+
45
+ static table(tableName) {
46
+ return new QueryBuilder(tableName);
47
+ }
48
+ }
49
+
50
+ class QueryBuilder {
51
+ constructor(table) {
52
+ this.tableName = table;
53
+ this.conditions = [];
54
+ this.bindings = [];
55
+ this.selects = '*';
56
+ this.limitVal = null;
57
+ this.orderByRaw = null;
58
+ this.executed = false; // Flag to prevent double execution if method chaining is weird
59
+ }
60
+
61
+ // SELECT logic
62
+ select(fields) {
63
+ this.selects = fields;
64
+ return this;
65
+ }
66
+
67
+ // WHERE logic
68
+ where(col, op, val) {
69
+ if (val === undefined) { val = op; op = '='; }
70
+ this.conditions.push(`${col} ${op} ?`);
71
+ this.bindings.push(val);
72
+ return this;
73
+ }
74
+
75
+ orderBy(col, direction = 'ASC') {
76
+ this.orderByRaw = `${col} ${direction}`;
77
+ return this;
78
+ }
79
+
80
+ take(n) { // Alias for limit, Laravel style
81
+ this.limitVal = n;
82
+ return this;
83
+ }
84
+
85
+ // EKSEKUTOR (Disini logic query dijalankan)
86
+ // Ubah nama method 'get' jadi 'get()' yang mengembalikan Promise<Array>
87
+ async get() {
88
+ let sql = `SELECT ${this.selects} FROM ${this.tableName}`;
89
+
90
+ if (this.conditions.length > 0) {
91
+ sql += ' WHERE ' + this.conditions.join(' AND ');
92
+ }
93
+ if (this.orderByRaw) {
94
+ sql += ' ORDER BY ' + this.orderByRaw;
95
+ }
96
+ if (this.limitVal) {
97
+ sql += ' LIMIT ' + this.limitVal;
98
+ }
99
+ return await DB.query(sql, this.bindings);
100
+ }
101
+
102
+ // Alias first() -> Ambil 1 data
103
+ async first() {
104
+ this.take(1);
105
+ const rows = await this.get();
106
+ return rows.length > 0 ? rows[0] : null;
107
+ }
108
+
109
+ // Insert
110
+ async insert(data) {
111
+ const keys = Object.keys(data);
112
+ const values = Object.values(data);
113
+ const placeholders = keys.map(() => '?').join(', ');
114
+ const sql = `INSERT INTO ${this.tableName} (${keys.join(', ')}) VALUES (${placeholders})`;
115
+ return await DB.query(sql, values);
116
+ }
117
+
118
+ // Update
119
+ async update(data) {
120
+ const keys = Object.keys(data);
121
+ const values = Object.values(data);
122
+ const setClause = keys.map(k => `${k} = ?`).join(', ');
123
+
124
+ let sql = `UPDATE ${this.tableName} SET ${setClause}`;
125
+ if (this.conditions.length > 0) {
126
+ sql += ' WHERE ' + this.conditions.join(' AND ');
127
+ }
128
+ return await DB.query(sql, [...values, ...this.bindings]);
129
+ }
130
+
131
+ // Delete
132
+ async delete() {
133
+ let sql = `DELETE FROM ${this.tableName}`;
134
+ if (this.conditions.length > 0) {
135
+ sql += ' WHERE ' + this.conditions.join(' AND ');
136
+ }
137
+ return await DB.query(sql, this.bindings);
138
+ }
139
+ }
@@ -0,0 +1,23 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ export function loadEnv(root) {
5
+ const envPath = path.join(root, '.env');
6
+ const env = {};
7
+
8
+ if (fs.existsSync(envPath)) {
9
+ const content = fs.readFileSync(envPath, 'utf-8');
10
+ content.split('\n').forEach(line => {
11
+ if(line.includes('=')) {
12
+ const [key, val] = line.split('=');
13
+ env[key.trim()] = val.trim().replace(/"/g, '');
14
+ }
15
+ });
16
+ } else {
17
+ // Default values if .env not found
18
+ env.BASE_URL = "http://localhost:3000";
19
+ env.APP_ENV = "local";
20
+ env.APP_DEBUG = "true";
21
+ }
22
+ return env;
23
+ }
@@ -3,12 +3,19 @@
3
3
  export const routes = [];
4
4
 
5
5
  export const Jalan = {
6
- gawe: (path, action) => routes.push({ path, action, method: 'GET' }), // GET
7
- jupuk: (path, action) => routes.push({ path, action, method: 'GET' }), // Alias GET
6
+ // Semarangan
7
+ gawe: (path, action) => routes.push({ path, action, method: 'GET' }),
8
+ jupuk: (path, action) => routes.push({ path, action, method: 'GET' }),
8
9
  kirim: (path, action) => routes.push({ path, action, method: 'POST' }),
9
10
  anyar: (path, action) => routes.push({ path, action, method: 'PUT' }),
10
11
  bucak: (path, action) => routes.push({ path, action, method: 'DELETE' }),
12
+
13
+ // Internasional (Laravel-like)
14
+ get: (path, action) => routes.push({ path, action, method: 'GET' }),
15
+ post: (path, action) => routes.push({ path, action, method: 'POST' }),
16
+ put: (path, action) => routes.push({ path, action, method: 'PUT' }),
17
+ delete: (path, action) => routes.push({ path, action, method: 'DELETE' }),
11
18
  };
12
19
 
13
- // Global Router Container (for easy access in runtime)
20
+ // Global Router Container
14
21
  global.LumpiaRouter = routes;
package/lib/core/View.js CHANGED
@@ -1,20 +1,21 @@
1
1
  import fs from 'fs';
2
+ import path from 'path';
3
+ import { loadConfig } from './Config.js';
2
4
 
3
5
  // --- KAMUS BAHASA SEMARANG (Blade-like Template Engine) ---
4
- // Mengubah sintaks .lmp ke HTML siap render dengan interpolasi data
5
6
  export function renderLumpia(viewPath, data = {}) {
6
7
  let content = fs.readFileSync(viewPath, 'utf-8');
7
8
 
8
9
  // 1. Ekstrak bagian <klambi>, <kulit>, <isi>
9
10
  const matchKulit = content.match(/<kulit>([\s\S]*?)<\/kulit>/);
10
- const matchIsi = content.match(/<isi>([\s\S]*?)<\/isi>/); // Client-side JS
11
+ const matchIsi = content.match(/<isi>([\s\S]*?)<\/isi>/);
11
12
  const matchKlambi = content.match(/<klambi>([\s\S]*?)<\/klambi>/);
12
13
 
13
14
  let htmlBody = matchKulit ? matchKulit[1] : '';
14
15
  let clientScript = matchIsi ? matchIsi[1] : '';
15
16
  let cssStyle = matchKlambi ? matchKlambi[1] : '';
16
17
 
17
- // 2. Transpile Client-side JS (Semarangan -> JS Standard)
18
+ // 2. Transpile Client-side JS
18
19
  const dictionary = [
19
20
  { asal: /ono\s/g, jadi: 'let ' },
20
21
  { asal: /paten\s/g, jadi: 'const ' },
@@ -27,26 +28,38 @@ export function renderLumpia(viewPath, data = {}) {
27
28
  ];
28
29
  dictionary.forEach(kata => clientScript = clientScript.replace(kata.asal, kata.jadi));
29
30
 
30
- // 3. Templating Engine (Mirip Blade {{ variable }})
31
- // Mengganti {{ variable }} dengan value dari `data`
31
+ // 3. Templating Engine
32
32
  for (const [key, value] of Object.entries(data)) {
33
33
  const regex = new RegExp(`{{\\s*${key}\\s*}}`, 'g');
34
- // Simple XSS protection could be added here
35
34
  htmlBody = htmlBody.replace(regex, value);
36
35
  }
37
36
 
37
+ // --- 4. AUTO INJECT ASSETS BASED ON CONFIG ---
38
+ // Kita baca config dari PROJECT_ROOT (asumsi process.cwd())
39
+ let headInjection = '';
40
+ const config = loadConfig(process.cwd());
41
+
42
+ // Inject main.css (Tailwind build result or Custom CSS)
43
+ headInjection += `<link rel="stylesheet" href="/css/style.css">`;
44
+
45
+ // Inject Vendor Specific
46
+ if (config.klambi === 'bootstrap') {
47
+ headInjection += `<link rel="stylesheet" href="/vendor/bootstrap/css/bootstrap.min.css">`;
48
+ // Optional JS injection could be added here
49
+ }
50
+
38
51
  // 4. Rakit Akhir
39
52
  return `<!DOCTYPE html>
40
53
  <html>
41
54
  <head>
42
55
  <title>Lumpia App</title>
43
- <style>body{font-family:sans-serif;padding:20px;} ${cssStyle}</style>
56
+ ${headInjection}
57
+ <style> ${cssStyle}</style>
44
58
  </head>
45
59
  <body>
46
60
  ${htmlBody}
47
61
  <div id="output-lumpia"></div>
48
62
  <script>
49
- // Runtime Helper
50
63
  function tampil(txt) {
51
64
  let el = document.getElementById('output-lumpia');
52
65
  if(el) el.innerText = txt;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lumpiajs",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Bahasa Pemrograman Semarangan",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -8,6 +8,8 @@
8
8
  "lumpia": "./bin/lumpia.js"
9
9
  },
10
10
  "dependencies": {
11
- "fs-extra": "^11.1.1"
11
+ "fs-extra": "^11.1.1",
12
+ "mysql2": "^3.16.0",
13
+ "prompts": "^2.4.2"
12
14
  }
13
15
  }
@@ -0,0 +1,6 @@
1
+
2
+ BASE_URL="http://localhost:3000"
3
+ APP_ENV="local"
4
+ # Pilihan: 'local', 'development', 'production'
5
+ APP_DEBUG="true"
6
+ # Pilihan: 'true', 'false'
@@ -0,0 +1,3 @@
1
+ .lumpia
2
+ node_modules
3
+ .env