zero-http 0.2.5 → 0.3.1

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 (89) hide show
  1. package/README.md +1250 -283
  2. package/documentation/config/db.js +25 -0
  3. package/documentation/config/middleware.js +44 -0
  4. package/documentation/config/tls.js +12 -0
  5. package/documentation/controllers/cookies.js +34 -0
  6. package/documentation/controllers/tasks.js +108 -0
  7. package/documentation/full-server.js +25 -184
  8. package/documentation/models/Task.js +21 -0
  9. package/documentation/public/data/api.json +404 -24
  10. package/documentation/public/data/docs.json +1139 -0
  11. package/documentation/public/data/examples.json +80 -2
  12. package/documentation/public/data/options.json +23 -8
  13. package/documentation/public/index.html +138 -99
  14. package/documentation/public/scripts/app.js +1 -3
  15. package/documentation/public/scripts/custom-select.js +189 -0
  16. package/documentation/public/scripts/data-sections.js +233 -250
  17. package/documentation/public/scripts/playground.js +270 -0
  18. package/documentation/public/scripts/ui.js +4 -3
  19. package/documentation/public/styles.css +56 -5
  20. package/documentation/public/vendor/icons/compress.svg +17 -17
  21. package/documentation/public/vendor/icons/database.svg +21 -0
  22. package/documentation/public/vendor/icons/env.svg +21 -0
  23. package/documentation/public/vendor/icons/fetch.svg +11 -14
  24. package/documentation/public/vendor/icons/security.svg +15 -0
  25. package/documentation/public/vendor/icons/sse.svg +12 -13
  26. package/documentation/public/vendor/icons/static.svg +12 -26
  27. package/documentation/public/vendor/icons/stream.svg +7 -13
  28. package/documentation/public/vendor/icons/validate.svg +17 -0
  29. package/documentation/routes/api.js +41 -0
  30. package/documentation/routes/core.js +20 -0
  31. package/documentation/routes/playground.js +29 -0
  32. package/documentation/routes/realtime.js +49 -0
  33. package/documentation/routes/uploads.js +71 -0
  34. package/index.js +62 -1
  35. package/lib/app.js +200 -8
  36. package/lib/body/json.js +28 -5
  37. package/lib/body/multipart.js +29 -1
  38. package/lib/body/raw.js +1 -1
  39. package/lib/body/sendError.js +1 -0
  40. package/lib/body/text.js +1 -1
  41. package/lib/body/typeMatch.js +6 -2
  42. package/lib/body/urlencoded.js +5 -2
  43. package/lib/debug.js +345 -0
  44. package/lib/env/index.js +440 -0
  45. package/lib/errors.js +231 -0
  46. package/lib/http/request.js +219 -1
  47. package/lib/http/response.js +410 -6
  48. package/lib/middleware/compress.js +39 -6
  49. package/lib/middleware/cookieParser.js +237 -0
  50. package/lib/middleware/cors.js +13 -2
  51. package/lib/middleware/csrf.js +135 -0
  52. package/lib/middleware/errorHandler.js +90 -0
  53. package/lib/middleware/helmet.js +176 -0
  54. package/lib/middleware/index.js +7 -2
  55. package/lib/middleware/rateLimit.js +12 -1
  56. package/lib/middleware/requestId.js +54 -0
  57. package/lib/middleware/static.js +95 -11
  58. package/lib/middleware/timeout.js +72 -0
  59. package/lib/middleware/validator.js +257 -0
  60. package/lib/orm/adapters/json.js +215 -0
  61. package/lib/orm/adapters/memory.js +383 -0
  62. package/lib/orm/adapters/mongo.js +444 -0
  63. package/lib/orm/adapters/mysql.js +272 -0
  64. package/lib/orm/adapters/postgres.js +394 -0
  65. package/lib/orm/adapters/sql-base.js +142 -0
  66. package/lib/orm/adapters/sqlite.js +311 -0
  67. package/lib/orm/index.js +276 -0
  68. package/lib/orm/model.js +895 -0
  69. package/lib/orm/query.js +807 -0
  70. package/lib/orm/schema.js +172 -0
  71. package/lib/router/index.js +136 -47
  72. package/lib/sse/stream.js +15 -3
  73. package/lib/ws/connection.js +19 -3
  74. package/lib/ws/handshake.js +3 -0
  75. package/lib/ws/index.js +3 -1
  76. package/lib/ws/room.js +222 -0
  77. package/package.json +15 -5
  78. package/types/app.d.ts +120 -0
  79. package/types/env.d.ts +80 -0
  80. package/types/errors.d.ts +147 -0
  81. package/types/fetch.d.ts +43 -0
  82. package/types/index.d.ts +135 -0
  83. package/types/middleware.d.ts +292 -0
  84. package/types/orm.d.ts +610 -0
  85. package/types/request.d.ts +99 -0
  86. package/types/response.d.ts +142 -0
  87. package/types/router.d.ts +78 -0
  88. package/types/sse.d.ts +78 -0
  89. package/types/websocket.d.ts +119 -0
@@ -9,6 +9,7 @@
9
9
  function sendError(res, status, message)
10
10
  {
11
11
  const raw = res.raw || res;
12
+ if (raw.headersSent) return;
12
13
  raw.statusCode = status;
13
14
  raw.setHeader('Content-Type', 'application/json');
14
15
  raw.end(JSON.stringify({ error: message }));
package/lib/body/text.js CHANGED
@@ -20,7 +20,7 @@ const sendError = require('./sendError');
20
20
  function text(options = {})
21
21
  {
22
22
  const opts = options || {};
23
- const limit = opts.limit || null;
23
+ const limit = opts.limit !== undefined ? opts.limit : '1mb';
24
24
  const encoding = opts.encoding || 'utf8';
25
25
  const typeOpt = opts.type || 'text/*';
26
26
  const requireSecure = !!opts.requireSecure;
@@ -12,11 +12,15 @@ function isTypeMatch(contentType, typeOpt)
12
12
  if (typeof typeOpt === 'function') return !!typeOpt(contentType);
13
13
  if (!contentType) return false;
14
14
  if (typeOpt === '*/*') return true;
15
+ // Strip charset/parameters from content-type for proper matching
16
+ const semiIdx = contentType.indexOf(';');
17
+ const baseType = semiIdx !== -1 ? contentType.substring(0, semiIdx).trim() : contentType;
15
18
  if (typeOpt.endsWith('/*'))
16
19
  {
17
- return contentType.startsWith(typeOpt.slice(0, -1));
20
+ return baseType.startsWith(typeOpt.slice(0, -1));
18
21
  }
19
- return contentType.indexOf(typeOpt) !== -1;
22
+ // Exact or substring match against the base type only
23
+ return baseType.indexOf(typeOpt) !== -1;
20
24
  }
21
25
 
22
26
  module.exports = isTypeMatch;
@@ -36,7 +36,7 @@ function appendValue(prev, val)
36
36
  function urlencoded(options = {})
37
37
  {
38
38
  const opts = options || {};
39
- const limit = opts.limit || null;
39
+ const limit = opts.limit !== undefined ? opts.limit : '1mb';
40
40
  const typeOpt = opts.type || 'application/x-www-form-urlencoded';
41
41
  const extended = !!opts.extended;
42
42
  const requireSecure = !!opts.requireSecure;
@@ -73,7 +73,10 @@ function urlencoded(options = {})
73
73
  let m;
74
74
  while ((m = re.exec(k)) !== null)
75
75
  {
76
- parts.push(m[1] || m[2]);
76
+ const part = m[1] || m[2];
77
+ // Prevent prototype pollution
78
+ if (part === '__proto__' || part === 'constructor' || part === 'prototype') continue;
79
+ parts.push(part);
77
80
  }
78
81
 
79
82
  // set value into out following parts
package/lib/debug.js ADDED
@@ -0,0 +1,345 @@
1
+ /**
2
+ * @module debug
3
+ * @description Lightweight namespaced debug logger with levels, colors, and timestamps.
4
+ * Enable via DEBUG env variable: DEBUG=app:*,router (supports glob patterns).
5
+ * Each namespace gets a unique color for easy visual scanning.
6
+ *
7
+ * Levels: trace (0), debug (1), info (2), warn (3), error (4), fatal (5), silent (6).
8
+ * Set level via DEBUG_LEVEL env var or programmatically.
9
+ *
10
+ * @example
11
+ * const debug = require('zero-http').debug;
12
+ * const log = debug('app:routes');
13
+ * log.info('server started on port %d', 3000);
14
+ * log.error('failed to connect', err);
15
+ * log('shorthand for debug level');
16
+ */
17
+
18
+ const LEVELS = { trace: 0, debug: 1, info: 2, warn: 3, error: 4, fatal: 5, silent: 6 };
19
+ const LEVEL_NAMES = ['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'];
20
+ const LEVEL_COLORS = ['\x1b[2m', '\x1b[36m', '\x1b[32m', '\x1b[33m', '\x1b[31m', '\x1b[35;1m'];
21
+
22
+ // Namespace colors (rotate through these)
23
+ const NS_COLORS = [
24
+ '\x1b[36m', '\x1b[33m', '\x1b[32m', '\x1b[35m', '\x1b[34m',
25
+ '\x1b[36;1m', '\x1b[33;1m', '\x1b[32;1m', '\x1b[35;1m', '\x1b[34;1m',
26
+ ];
27
+
28
+ const RESET = '\x1b[0m';
29
+ const DIM = '\x1b[2m';
30
+
31
+ let _colorIdx = 0;
32
+ const _nsColorMap = new Map();
33
+
34
+ /**
35
+ * Global state
36
+ */
37
+ let _globalLevel = LEVELS[process.env.DEBUG_LEVEL] !== undefined
38
+ ? LEVELS[process.env.DEBUG_LEVEL]
39
+ : LEVELS.debug;
40
+
41
+ let _enabledPatterns = null;
42
+ let _output = process.stderr;
43
+ let _useColors = process.stderr.isTTY || false;
44
+ let _timestamps = true;
45
+ let _jsonMode = false;
46
+
47
+ /**
48
+ * Parse DEBUG env var into patterns.
49
+ */
50
+ function _parsePatterns()
51
+ {
52
+ const raw = process.env.DEBUG;
53
+ if (!raw) return null;
54
+ return raw.split(/[\s,]+/).filter(Boolean).map(p =>
55
+ {
56
+ const neg = p.startsWith('-');
57
+ const pat = neg ? p.slice(1) : p;
58
+ // Convert glob to regex: * => .*, ? => .
59
+ const re = new RegExp('^' + pat.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.') + '$');
60
+ return { neg, re };
61
+ });
62
+ }
63
+
64
+ _enabledPatterns = _parsePatterns();
65
+
66
+ /**
67
+ * Check if a namespace is enabled.
68
+ * @param {string} ns
69
+ * @returns {boolean}
70
+ */
71
+ function _isEnabled(ns)
72
+ {
73
+ if (!_enabledPatterns) return true; // No DEBUG set — enable all
74
+ let enabled = false;
75
+ for (const { neg, re } of _enabledPatterns)
76
+ {
77
+ if (re.test(ns)) enabled = !neg;
78
+ }
79
+ return enabled;
80
+ }
81
+
82
+ /**
83
+ * Get a color for a namespace.
84
+ */
85
+ function _nsColor(ns)
86
+ {
87
+ if (!_nsColorMap.has(ns))
88
+ {
89
+ _nsColorMap.set(ns, NS_COLORS[_colorIdx % NS_COLORS.length]);
90
+ _colorIdx++;
91
+ }
92
+ return _nsColorMap.get(ns);
93
+ }
94
+
95
+ /**
96
+ * Format a timestamp.
97
+ */
98
+ function _ts()
99
+ {
100
+ const d = new Date();
101
+ const pad = (n) => String(n).padStart(2, '0');
102
+ return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${String(d.getMilliseconds()).padStart(3, '0')}`;
103
+ }
104
+
105
+ /**
106
+ * Format arguments (like console.log — supports %s, %d, %j, %o).
107
+ */
108
+ function _format(args)
109
+ {
110
+ if (args.length === 0) return '';
111
+ if (typeof args[0] === 'string' && args.length > 1)
112
+ {
113
+ let i = 1;
114
+ const str = args[0].replace(/%([sdjo%])/g, (_, f) =>
115
+ {
116
+ if (f === '%') return '%';
117
+ if (i >= args.length) return `%${f}`;
118
+ const v = args[i++];
119
+ if (f === 's') return String(v);
120
+ if (f === 'd') return Number(v);
121
+ if (f === 'j' || f === 'o')
122
+ {
123
+ try { return JSON.stringify(v); }
124
+ catch { return String(v); }
125
+ }
126
+ return String(v);
127
+ });
128
+ const rest = args.slice(i).map(a =>
129
+ typeof a === 'object' ? JSON.stringify(a) : String(a)
130
+ );
131
+ return rest.length > 0 ? str + ' ' + rest.join(' ') : str;
132
+ }
133
+ return args.map(a =>
134
+ {
135
+ if (a instanceof Error) return a.stack || a.message;
136
+ if (typeof a === 'object')
137
+ {
138
+ try { return JSON.stringify(a); }
139
+ catch { return String(a); }
140
+ }
141
+ return String(a);
142
+ }).join(' ');
143
+ }
144
+
145
+ /**
146
+ * Write a log entry.
147
+ */
148
+ function _write(ns, level, args)
149
+ {
150
+ const msg = _format(args);
151
+
152
+ if (_jsonMode)
153
+ {
154
+ const entry = {
155
+ timestamp: new Date().toISOString(),
156
+ level: LEVEL_NAMES[level],
157
+ namespace: ns,
158
+ message: msg,
159
+ };
160
+ // If last arg is an Error, attach stack
161
+ const last = args[args.length - 1];
162
+ if (last instanceof Error)
163
+ {
164
+ entry.error = { message: last.message, stack: last.stack, code: last.code };
165
+ }
166
+ _output.write(JSON.stringify(entry) + '\n');
167
+ return;
168
+ }
169
+
170
+ // Pretty text output
171
+ const parts = [];
172
+ if (_timestamps)
173
+ {
174
+ parts.push(_useColors ? `${DIM}${_ts()}${RESET}` : _ts());
175
+ }
176
+ // Level
177
+ const lvlName = LEVEL_NAMES[level];
178
+ if (_useColors)
179
+ {
180
+ parts.push(`${LEVEL_COLORS[level]}${lvlName.padEnd(5)}${RESET}`);
181
+ }
182
+ else
183
+ {
184
+ parts.push(lvlName.padEnd(5));
185
+ }
186
+ // Namespace
187
+ if (_useColors)
188
+ {
189
+ parts.push(`${_nsColor(ns)}${ns}${RESET}`);
190
+ }
191
+ else
192
+ {
193
+ parts.push(ns);
194
+ }
195
+ parts.push(msg);
196
+
197
+ _output.write(parts.join(' ') + '\n');
198
+ }
199
+
200
+ /**
201
+ * Create a namespaced debug logger.
202
+ *
203
+ * @param {string} namespace - Logger namespace (e.g. 'app:routes', 'db:queries').
204
+ * @returns {Function & { trace, debug, info, warn, error, fatal, enabled }} Logger function.
205
+ */
206
+ function debug(namespace)
207
+ {
208
+ const enabled = _isEnabled(namespace);
209
+
210
+ // Default call = debug level
211
+ const logger = (...args) =>
212
+ {
213
+ if (!enabled || _globalLevel > LEVELS.debug) return;
214
+ _write(namespace, LEVELS.debug, args);
215
+ };
216
+
217
+ logger.trace = (...args) =>
218
+ {
219
+ if (!enabled || _globalLevel > LEVELS.trace) return;
220
+ _write(namespace, LEVELS.trace, args);
221
+ };
222
+
223
+ logger.debug = logger;
224
+
225
+ logger.info = (...args) =>
226
+ {
227
+ if (!enabled || _globalLevel > LEVELS.info) return;
228
+ _write(namespace, LEVELS.info, args);
229
+ };
230
+
231
+ logger.warn = (...args) =>
232
+ {
233
+ if (!enabled || _globalLevel > LEVELS.warn) return;
234
+ _write(namespace, LEVELS.warn, args);
235
+ };
236
+
237
+ logger.error = (...args) =>
238
+ {
239
+ if (!enabled || _globalLevel > LEVELS.error) return;
240
+ _write(namespace, LEVELS.error, args);
241
+ };
242
+
243
+ logger.fatal = (...args) =>
244
+ {
245
+ if (!enabled || _globalLevel > LEVELS.fatal) return;
246
+ _write(namespace, LEVELS.fatal, args);
247
+ };
248
+
249
+ /** Whether this namespace is active. */
250
+ logger.enabled = enabled;
251
+
252
+ /** The namespace string */
253
+ logger.namespace = namespace;
254
+
255
+ return logger;
256
+ }
257
+
258
+ // --- Configuration API -------------------------------------------
259
+
260
+ /**
261
+ * Set the minimum log level globally.
262
+ * @param {string|number} level - Level name or number.
263
+ */
264
+ debug.level = function(level)
265
+ {
266
+ if (typeof level === 'string') _globalLevel = LEVELS[level.toLowerCase()] ?? LEVELS.debug;
267
+ else _globalLevel = level;
268
+ };
269
+
270
+ /**
271
+ * Enable/disable namespaces programmatically (same syntax as DEBUG env var).
272
+ * @param {string} patterns - Comma-separated patterns. Use '-ns' to exclude.
273
+ */
274
+ debug.enable = function(patterns)
275
+ {
276
+ process.env.DEBUG = patterns;
277
+ _enabledPatterns = _parsePatterns();
278
+ };
279
+
280
+ /**
281
+ * Disable all debug output.
282
+ */
283
+ debug.disable = function()
284
+ {
285
+ process.env.DEBUG = '';
286
+ _enabledPatterns = [{ neg: false, re: /^$/ }]; // match nothing
287
+ };
288
+
289
+ /**
290
+ * Enable structured JSON output.
291
+ * @param {boolean} [on=true]
292
+ */
293
+ debug.json = function(on = true)
294
+ {
295
+ _jsonMode = on;
296
+ };
297
+
298
+ /**
299
+ * Enable/disable timestamps.
300
+ * @param {boolean} [on=true]
301
+ */
302
+ debug.timestamps = function(on = true)
303
+ {
304
+ _timestamps = on;
305
+ };
306
+
307
+ /**
308
+ * Enable/disable colors.
309
+ * @param {boolean} [on=true]
310
+ */
311
+ debug.colors = function(on = true)
312
+ {
313
+ _useColors = on;
314
+ };
315
+
316
+ /**
317
+ * Set custom output stream.
318
+ * @param {object} stream - Writable stream with write() method.
319
+ */
320
+ debug.output = function(stream)
321
+ {
322
+ _output = stream;
323
+ };
324
+
325
+ /**
326
+ * Reset all settings to defaults.
327
+ */
328
+ debug.reset = function()
329
+ {
330
+ _globalLevel = LEVELS[process.env.DEBUG_LEVEL] !== undefined
331
+ ? LEVELS[process.env.DEBUG_LEVEL]
332
+ : LEVELS.debug;
333
+ _enabledPatterns = _parsePatterns();
334
+ _output = process.stderr;
335
+ _useColors = process.stderr.isTTY || false;
336
+ _timestamps = true;
337
+ _jsonMode = false;
338
+ _colorIdx = 0;
339
+ _nsColorMap.clear();
340
+ };
341
+
342
+ /** Expose level constants. */
343
+ debug.LEVELS = LEVELS;
344
+
345
+ module.exports = debug;