webspresso 0.0.99 → 0.0.101

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 (108) hide show
  1. package/.agents/ADMIN.md +47 -4
  2. package/.agents/AGENTS.md +4 -2
  3. package/.agents/AUTH.md +309 -0
  4. package/.agents/CLI.md +128 -0
  5. package/.agents/EMAIL.md +84 -2
  6. package/.agents/ERRORS.md +59 -0
  7. package/.agents/EVENTS.md +179 -0
  8. package/.agents/ORM.md +149 -0
  9. package/.agents/PLUGINS.md +44 -7
  10. package/.agents/QUEUE.md +18 -10
  11. package/.agents/ROUTING.md +97 -1
  12. package/.agents/STREAMING.md +35 -0
  13. package/AGENTS.md +2 -0
  14. package/README.md +145 -93
  15. package/bin/commands/add-docker.js +467 -0
  16. package/bin/commands/db-makemigrations.js +83 -0
  17. package/bin/commands/deploy-check.js +339 -0
  18. package/bin/commands/list.js +96 -0
  19. package/bin/commands/make-command.js +93 -0
  20. package/bin/commands/make-event.js +80 -0
  21. package/bin/commands/make-factory.js +72 -0
  22. package/bin/commands/make-mail.js +93 -0
  23. package/bin/commands/make-policy.js +106 -0
  24. package/bin/commands/run.js +195 -0
  25. package/bin/webspresso.js +21 -0
  26. package/core/auth/bouncer.js +196 -0
  27. package/core/auth/index.js +73 -1
  28. package/core/auth/magic-link.js +170 -0
  29. package/core/auth/manager.js +343 -3
  30. package/core/auth/middleware.js +45 -6
  31. package/core/auth/session-manager.js +255 -0
  32. package/core/auth/sessions.js +351 -0
  33. package/core/auth/tokens.js +1 -0
  34. package/core/auth/totp.js +4 -1
  35. package/core/auth/user-agent.js +161 -0
  36. package/core/commands/define-command.js +48 -0
  37. package/core/commands/index.js +18 -0
  38. package/core/commands/registry.js +156 -0
  39. package/core/commands/runner.js +76 -0
  40. package/core/compression/compressible.js +1 -1
  41. package/core/compression/middleware.js +35 -251
  42. package/core/content/service.js +8 -2
  43. package/core/events/discovery.js +142 -0
  44. package/core/events/dispatcher.js +552 -0
  45. package/core/events/index.js +23 -0
  46. package/core/events/signal.js +92 -0
  47. package/core/forms/flash.js +234 -0
  48. package/core/forms/form-builder.js +214 -0
  49. package/core/forms/index.js +16 -0
  50. package/core/mail/index.js +23 -0
  51. package/core/mail/mailable.js +250 -0
  52. package/core/mail/mailer.js +184 -0
  53. package/core/mail/transports.js +238 -0
  54. package/core/orm/factory.js +250 -0
  55. package/core/orm/index.js +8 -1
  56. package/core/orm/migrations/diff.js +185 -0
  57. package/core/orm/migrations/index.js +22 -4
  58. package/core/orm/model.js +55 -1
  59. package/core/orm/repository.js +283 -13
  60. package/core/orm/scopes.js +3 -2
  61. package/core/orm/types.js +6 -1
  62. package/core/queue/cron.js +252 -39
  63. package/core/queue/manager.js +20 -2
  64. package/core/queue/scheduler.js +115 -59
  65. package/core/ssr/stream.js +62 -2
  66. package/index.d.ts +540 -14
  67. package/index.js +44 -0
  68. package/package.json +11 -5
  69. package/plugins/admin-panel/api.js +509 -3
  70. package/plugins/admin-panel/index.js +17 -0
  71. package/plugins/admin-panel/modules/admin-users.js +1 -1
  72. package/plugins/audit-log/admin-component.js +54 -54
  73. package/plugins/audit-log/index.js +2 -2
  74. package/plugins/content/client/inline-edit.js +2 -46
  75. package/plugins/content/models/content-entry.js +4 -3
  76. package/plugins/content/models/content-type.js +4 -3
  77. package/plugins/cors.js +15 -4
  78. package/plugins/email/auth-bridge.js +66 -0
  79. package/plugins/email/bundled-templates.js +15 -0
  80. package/plugins/email/index.js +48 -0
  81. package/plugins/email/preview-handler.js +555 -0
  82. package/plugins/email/service.js +23 -12
  83. package/plugins/email/templates/auth-magic-link.mjml +13 -0
  84. package/plugins/email/transport.js +30 -11
  85. package/plugins/file-manager/admin-component.js +49 -49
  86. package/plugins/file-manager/index.js +4 -4
  87. package/plugins/index.js +2 -0
  88. package/plugins/redirect/index.js +1 -1
  89. package/plugins/webhooks/admin-component.js +608 -0
  90. package/plugins/webhooks/api-handlers.js +237 -0
  91. package/plugins/webhooks/crypto.js +144 -0
  92. package/plugins/webhooks/dispatcher.js +708 -0
  93. package/plugins/webhooks/index.js +263 -0
  94. package/plugins/webhooks/matcher.js +113 -0
  95. package/plugins/webhooks/migration-template.js +75 -0
  96. package/plugins/webhooks/models.js +98 -0
  97. package/plugins/webhooks/sender.js +176 -0
  98. package/src/api/api-loader.js +1 -1
  99. package/src/app-context.js +48 -6
  100. package/src/bundle/index.js +2 -1
  101. package/src/dev-reload/index.js +9 -1
  102. package/src/discovery/scan-routes.js +10 -1
  103. package/src/file-router.js +173 -4
  104. package/src/helpers.js +150 -1
  105. package/src/njk-frontmatter.js +3 -0
  106. package/src/pages/page-loader.js +1 -1
  107. package/src/routing/cascading.js +120 -4
  108. package/src/server.js +180 -26
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Webspresso Zero-Dependency User-Agent & Device Parser
3
+ * Extracts browser, operating system, and device category from User-Agent header
4
+ * @module core/auth/user-agent
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ /**
10
+ * @typedef {Object} DeviceInfo
11
+ * @property {string} browser - Browser name with major version (e.g. 'Chrome 128', 'Safari 17')
12
+ * @property {string} os - Operating system name (e.g. 'macOS 14', 'Windows 11', 'iOS 17')
13
+ * @property {'desktop'|'mobile'|'tablet'|'bot'|'unknown'} device - Device category
14
+ * @property {boolean} isMobile
15
+ * @property {boolean} isTablet
16
+ * @property {boolean} isDesktop
17
+ * @property {boolean} isBot
18
+ */
19
+
20
+ /**
21
+ * Extract client IP address handling proxies
22
+ * @param {Object} req - Express / Node.js HTTP request
23
+ * @returns {string}
24
+ */
25
+ function getClientIp(req) {
26
+ if (!req) return '127.0.0.1';
27
+
28
+ const forwarded = req.headers?.['x-forwarded-for'];
29
+ if (forwarded) {
30
+ const ips = typeof forwarded === 'string' ? forwarded.split(',') : forwarded;
31
+ const first = Array.isArray(ips) ? ips[0] : ips;
32
+ if (first) return first.trim();
33
+ }
34
+
35
+ return req.headers?.['x-real-ip'] ||
36
+ req.ip ||
37
+ req.connection?.remoteAddress ||
38
+ req.socket?.remoteAddress ||
39
+ '127.0.0.1';
40
+ }
41
+
42
+ /**
43
+ * Parse raw User-Agent header string into structured device info
44
+ * Zero external dependencies — pure regex extraction
45
+ *
46
+ * @param {string} [userAgent='']
47
+ * @returns {DeviceInfo}
48
+ */
49
+ function parseUserAgent(userAgent = '') {
50
+ const ua = String(userAgent || '').trim();
51
+
52
+ if (!ua) {
53
+ return {
54
+ browser: 'Unknown',
55
+ os: 'Unknown',
56
+ device: 'unknown',
57
+ isMobile: false,
58
+ isTablet: false,
59
+ isDesktop: false,
60
+ isBot: false,
61
+ };
62
+ }
63
+
64
+ // 1. Detect Bots / Crawlers
65
+ const botPattern = /googlebot|bingbot|yandex|baiduspider|duckduckbot|slurp|twitterbot|facebookexternalhit|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest\/|slackbot|vkshare|w3c_validator|whatsapp|curl|wget|postman/i;
66
+ const isBot = botPattern.test(ua);
67
+
68
+ // 2. Detect Operating System
69
+ let os = 'Unknown';
70
+ if (/iPad/i.test(ua)) {
71
+ const match = ua.match(/OS ([\d_\.]+)/i);
72
+ os = match ? `iPadOS ${match[1].replace(/_/g, '.')}` : 'iPadOS';
73
+ } else if (/iPhone|iPod/i.test(ua)) {
74
+ const match = ua.match(/OS ([\d_\.]+)/i);
75
+ os = match ? `iOS ${match[1].replace(/_/g, '.')}` : 'iOS';
76
+ } else if (/Android/i.test(ua)) {
77
+ const match = ua.match(/Android (\d+(\.\d+)?)/i);
78
+ os = match ? `Android ${match[1]}` : 'Android';
79
+ } else if (/Macintosh|Mac OS X/i.test(ua)) {
80
+ const match = ua.match(/Mac OS X ([\d_\.]+)/i);
81
+ if (match) {
82
+ const ver = match[1].replace(/_/g, '.');
83
+ os = `macOS ${ver}`;
84
+ } else {
85
+ os = 'macOS';
86
+ }
87
+ } else if (/Windows/i.test(ua)) {
88
+ if (/Windows NT 10\.0/i.test(ua)) os = 'Windows 10/11';
89
+ else if (/Windows NT 6\.3/i.test(ua)) os = 'Windows 8.1';
90
+ else if (/Windows NT 6\.2/i.test(ua)) os = 'Windows 8';
91
+ else if (/Windows NT 6\.1/i.test(ua)) os = 'Windows 7';
92
+ else os = 'Windows';
93
+ } else if (/CrOS/i.test(ua)) {
94
+ os = 'ChromeOS';
95
+ } else if (/Linux/i.test(ua)) {
96
+ if (/Ubuntu/i.test(ua)) os = 'Ubuntu Linux';
97
+ else if (/Fedora/i.test(ua)) os = 'Fedora Linux';
98
+ else os = 'Linux';
99
+ }
100
+
101
+ // 3. Detect Browser
102
+ let browser = 'Unknown';
103
+ if (/Edg(e|A|iOS)?\/(\d+)/i.test(ua)) {
104
+ const match = ua.match(/Edg(e|A|iOS)?\/(\d+)/i);
105
+ browser = `Edge ${match ? match[2] : ''}`.trim();
106
+ } else if (/OPR\/(\d+)/i.test(ua)) {
107
+ const match = ua.match(/OPR\/(\d+)/i);
108
+ browser = `Opera ${match ? match[1] : ''}`.trim();
109
+ } else if (/SamsungBrowser\/(\d+)/i.test(ua)) {
110
+ const match = ua.match(/SamsungBrowser\/(\d+)/i);
111
+ browser = `Samsung Internet ${match ? match[1] : ''}`.trim();
112
+ } else if (/Brave\/(\d+)/i.test(ua)) {
113
+ const match = ua.match(/Brave\/(\d+)/i);
114
+ browser = `Brave ${match ? match[1] : ''}`.trim();
115
+ } else if (/Vivaldi\/(\d+)/i.test(ua)) {
116
+ const match = ua.match(/Vivaldi\/(\d+)/i);
117
+ browser = `Vivaldi ${match ? match[1] : ''}`.trim();
118
+ } else if (/Chrome\/(\d+)/i.test(ua) && !/Chromium/i.test(ua)) {
119
+ const match = ua.match(/Chrome\/(\d+)/i);
120
+ browser = `Chrome ${match ? match[1] : ''}`.trim();
121
+ } else if (/Firefox\/(\d+)/i.test(ua) || /FxiOS\/(\d+)/i.test(ua)) {
122
+ const match = ua.match(/(?:Firefox|FxiOS)\/(\d+)/i);
123
+ browser = `Firefox ${match ? match[1] : ''}`.trim();
124
+ } else if (/Safari/i.test(ua) && /Version\/(\d+)/i.test(ua)) {
125
+ const match = ua.match(/Version\/(\d+)/i);
126
+ browser = `Safari ${match ? match[1] : ''}`.trim();
127
+ } else if (/Safari/i.test(ua) && !/Chrome/i.test(ua)) {
128
+ browser = 'Safari';
129
+ } else if (/MSIE (\d+)/i.test(ua) || (/Trident/i.test(ua) && /rv:(\d+)/i.test(ua))) {
130
+ browser = 'Internet Explorer';
131
+ } else if (isBot) {
132
+ const botNameMatch = ua.match(/(googlebot|bingbot|duckduckbot|slurp|twitterbot|slackbot|curl|wget)/i);
133
+ browser = botNameMatch ? botNameMatch[1] : 'Bot';
134
+ }
135
+
136
+ // 4. Detect Device Category
137
+ const isTablet = /iPad|tablet/i.test(ua) || (/android/i.test(ua) && !/mobile/i.test(ua));
138
+ const isMobile = !isTablet && /mobile|iphone|ipod|android|blackberry|opera mini|windows phone/i.test(ua);
139
+ const isDesktop = !isTablet && !isMobile && !isBot;
140
+
141
+ let device = 'unknown';
142
+ if (isBot) device = 'bot';
143
+ else if (isTablet) device = 'tablet';
144
+ else if (isMobile) device = 'mobile';
145
+ else if (isDesktop) device = 'desktop';
146
+
147
+ return {
148
+ browser,
149
+ os,
150
+ device,
151
+ isMobile,
152
+ isTablet,
153
+ isDesktop,
154
+ isBot,
155
+ };
156
+ }
157
+
158
+ module.exports = {
159
+ getClientIp,
160
+ parseUserAgent,
161
+ };
@@ -0,0 +1,48 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Webspresso - Custom Command Definition
5
+ * Inspired by Django BaseCommand & AdonisJS Ace CLI
6
+ * @module core/commands/define-command
7
+ */
8
+
9
+ /**
10
+ * Define a custom CLI management command
11
+ * @param {Object} definition
12
+ * @param {string} definition.name - Command identifier (e.g. 'users:sync' or 'send-invoices')
13
+ * @param {string} [definition.description] - Human-readable command description
14
+ * @param {Array<{flags: string, description?: string, defaultValue?: any}>} [definition.options] - CLI flags
15
+ * @param {Function} definition.handle - Execution function receiving ({ db, queue, services, mailer, events, logger, args, flags })
16
+ * @returns {Object} Validated command specification
17
+ */
18
+ function defineCommand(definition) {
19
+ if (!definition || typeof definition !== 'object') {
20
+ throw new TypeError('defineCommand requires a configuration object');
21
+ }
22
+ if (!definition.name || typeof definition.name !== 'string' || !definition.name.trim()) {
23
+ throw new TypeError('Command definition must specify a non-empty string "name"');
24
+ }
25
+ const handleFn = definition.handle || definition.run;
26
+ if (typeof handleFn !== 'function') {
27
+ throw new TypeError(`Command "${definition.name}" must provide an executable "handle" function`);
28
+ }
29
+
30
+ const options = Array.isArray(definition.options)
31
+ ? definition.options.map((opt) => ({
32
+ flags: String(opt.flags || opt.name || '').trim(),
33
+ description: opt.description || '',
34
+ defaultValue: opt.defaultValue !== undefined ? opt.defaultValue : opt.default,
35
+ })).filter((opt) => opt.flags.length > 0)
36
+ : [];
37
+
38
+ return {
39
+ name: definition.name.trim(),
40
+ description: definition.description || '',
41
+ options,
42
+ handle: handleFn,
43
+ };
44
+ }
45
+
46
+ module.exports = {
47
+ defineCommand,
48
+ };
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Webspresso - Management Commands Core Module
5
+ * @module core/commands
6
+ */
7
+
8
+ const { defineCommand } = require('./define-command');
9
+ const { createCommandRegistry, findCommandFiles, deriveCommandName } = require('./registry');
10
+ const { executeCommand } = require('./runner');
11
+
12
+ module.exports = {
13
+ defineCommand,
14
+ createCommandRegistry,
15
+ findCommandFiles,
16
+ deriveCommandName,
17
+ executeCommand,
18
+ };
@@ -0,0 +1,156 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Webspresso - Command Registry & Auto-Discovery
5
+ * Scans commands/ directory and registers custom management commands
6
+ * @module core/commands/registry
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const { defineCommand } = require('./define-command');
12
+
13
+ /**
14
+ * Recursively find command files in directory
15
+ * @param {string} dir
16
+ * @returns {string[]}
17
+ */
18
+ function findCommandFiles(dir) {
19
+ if (!fs.existsSync(dir)) return [];
20
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
21
+ const files = [];
22
+
23
+ for (const entry of entries) {
24
+ const fullPath = path.join(dir, entry.name);
25
+ if (entry.isDirectory()) {
26
+ files.push(...findCommandFiles(fullPath));
27
+ } else if (
28
+ entry.isFile() &&
29
+ entry.name.endsWith('.js') &&
30
+ !entry.name.startsWith('_') &&
31
+ !entry.name.endsWith('.test.js') &&
32
+ !entry.name.endsWith('.spec.js')
33
+ ) {
34
+ files.push(fullPath);
35
+ }
36
+ }
37
+
38
+ return files;
39
+ }
40
+
41
+ /**
42
+ * Derive command name from relative path
43
+ * e.g. "users/prune.js" -> "users:prune", "send-invoices.js" -> "send-invoices"
44
+ * @param {string} relPath
45
+ * @returns {string}
46
+ */
47
+ function deriveCommandName(relPath) {
48
+ const withoutExt = relPath.replace(/\.js$/, '');
49
+ const segments = withoutExt.split(/[\\/]/).filter(Boolean);
50
+ return segments.join(':');
51
+ }
52
+
53
+ /**
54
+ * Create a CommandRegistry instance
55
+ * @param {Object} [options]
56
+ * @param {string} [options.commandsDir] - Root directory for commands (default: ./commands)
57
+ * @param {boolean} [options.autoLoad=true] - Whether to discover files immediately
58
+ * @returns {Object} CommandRegistry
59
+ */
60
+ function createCommandRegistry(options = {}) {
61
+ const commandsDir = options.commandsDir
62
+ ? path.resolve(process.cwd(), options.commandsDir)
63
+ : path.resolve(process.cwd(), 'commands');
64
+
65
+ /** @type {Map<string, Object>} */
66
+ const commands = new Map();
67
+
68
+ function register(cmdDef) {
69
+ if (!cmdDef || typeof cmdDef !== 'object') {
70
+ throw new TypeError('Invalid command definition object');
71
+ }
72
+ const def = typeof cmdDef.handle === 'function' && defIsNormalized(cmdDef)
73
+ ? cmdDef
74
+ : defineCommand(cmdDef);
75
+
76
+ commands.set(def.name, def);
77
+ return def;
78
+ }
79
+
80
+ function defIsNormalized(obj) {
81
+ return typeof obj.name === 'string' && typeof obj.handle === 'function' && Array.isArray(obj.options);
82
+ }
83
+
84
+ function load() {
85
+ commands.clear();
86
+ const files = findCommandFiles(commandsDir);
87
+
88
+ for (const file of files) {
89
+ try {
90
+ delete require.cache[require.resolve(file)];
91
+ const exported = require(file);
92
+ const rel = path.relative(commandsDir, file);
93
+ const derivedName = deriveCommandName(rel);
94
+
95
+ let cmdDef = null;
96
+
97
+ if (typeof exported === 'function') {
98
+ cmdDef = defineCommand({
99
+ name: derivedName,
100
+ handle: exported,
101
+ });
102
+ } else if (exported && typeof exported === 'object') {
103
+ // Check if exported is an ES default or CJS module
104
+ const target = exported.default || exported;
105
+ const name = target.name || derivedName;
106
+ const handle = target.handle || target.run;
107
+
108
+ if (typeof handle === 'function') {
109
+ cmdDef = defineCommand({
110
+ name,
111
+ description: target.description || '',
112
+ options: target.options || [],
113
+ handle,
114
+ });
115
+ }
116
+ }
117
+
118
+ if (cmdDef) {
119
+ commands.set(cmdDef.name, cmdDef);
120
+ }
121
+ } catch (err) {
122
+ console.warn(`[commands] Failed to load command from "${file}":`, err.message);
123
+ }
124
+ }
125
+ }
126
+
127
+ if (options.autoLoad !== false) {
128
+ load();
129
+ }
130
+
131
+ return {
132
+ register,
133
+ reload: load,
134
+ has(name) {
135
+ return commands.has(name);
136
+ },
137
+ get(name) {
138
+ return commands.get(name) || null;
139
+ },
140
+ list() {
141
+ return Array.from(commands.values());
142
+ },
143
+ names() {
144
+ return Array.from(commands.keys());
145
+ },
146
+ get commandsDir() {
147
+ return commandsDir;
148
+ },
149
+ };
150
+ }
151
+
152
+ module.exports = {
153
+ createCommandRegistry,
154
+ findCommandFiles,
155
+ deriveCommandName,
156
+ };
@@ -0,0 +1,76 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Webspresso - Command Runner & Execution Engine
5
+ * Injects framework services, database, queue, mailer, events and manages execution lifecycle
6
+ * @module core/commands/runner
7
+ */
8
+
9
+ /**
10
+ * Execute a command definition with dependency injection
11
+ * @param {Object} commandDef - Command specification returned by defineCommand
12
+ * @param {Object} [executionOptions]
13
+ * @param {Array<string>} [executionOptions.args] - Positional CLI arguments
14
+ * @param {Object} [executionOptions.flags] - Named flags / options
15
+ * @param {Object} [executionOptions.db] - Knex / ORM database instance
16
+ * @param {Object} [executionOptions.queue] - Background job queue dispatcher
17
+ * @param {Object} [executionOptions.services] - Service registry or caller
18
+ * @param {Object} [executionOptions.mailer] - Mailer service
19
+ * @param {Object} [executionOptions.events] - Application event dispatcher
20
+ * @param {Object} [executionOptions.logger] - Logger instance (default: console)
21
+ * @returns {Promise<{ success: boolean, result?: any, error?: Error, durationMs: number }>}
22
+ */
23
+ async function executeCommand(commandDef, executionOptions = {}) {
24
+ if (!commandDef || typeof commandDef !== 'object') {
25
+ throw new TypeError('executeCommand requires a command definition');
26
+ }
27
+
28
+ const handleFn = commandDef.handle || commandDef.run;
29
+ if (typeof handleFn !== 'function') {
30
+ throw new TypeError(`Command "${commandDef.name || 'anonymous'}" has no executable handle function`);
31
+ }
32
+
33
+ const {
34
+ args = [],
35
+ flags = {},
36
+ db = null,
37
+ queue = null,
38
+ services = null,
39
+ mailer = null,
40
+ events = null,
41
+ logger = console,
42
+ } = executionOptions;
43
+
44
+ const start = Date.now();
45
+ const context = {
46
+ db,
47
+ queue,
48
+ services,
49
+ mailer,
50
+ events,
51
+ logger,
52
+ args: Array.isArray(args) ? args : [],
53
+ flags: flags && typeof flags === 'object' ? flags : {},
54
+ };
55
+
56
+ try {
57
+ const result = await handleFn(context);
58
+ const durationMs = Date.now() - start;
59
+ return {
60
+ success: true,
61
+ result,
62
+ durationMs,
63
+ };
64
+ } catch (error) {
65
+ const durationMs = Date.now() - start;
66
+ return {
67
+ success: false,
68
+ error,
69
+ durationMs,
70
+ };
71
+ }
72
+ }
73
+
74
+ module.exports = {
75
+ executeCommand,
76
+ };
@@ -8,7 +8,7 @@
8
8
  const COMPRESSIBLE_TYPE_REGEX = /^(?:text\/[a-z0-9.+_-]+|application\/(?:javascript|x-javascript|json|ld\+json|manifest\+json|graphql\+json|geo\+json|xml|atom\+xml|rss\+xml|xhtml\+xml|x-yaml|yaml|vnd\.api\+json|schema\+json|hal\+json)|image\/svg\+xml)$/i;
9
9
 
10
10
  // Regex patterns for already-compressed or non-compressible types
11
- const INCOMPRESSIBLE_TYPE_REGEX = /^(?:image\/(?:jpeg|png|gif|webp|avif|heic|heif)|video\/[a-z0-9.+_-]+|audio\/[a-z0-9.+_-]+|application\/(?:zip|gzip|x-gzip|x-bzip2|x-compress|x-7z-compressed|x-rar-compressed|pdf|wasm|octet-stream))$/i;
11
+ const INCOMPRESSIBLE_TYPE_REGEX = /^(?:text\/event-stream|image\/(?:jpeg|png|gif|webp|avif|heic|heif)|video\/[a-z0-9.+_-]+|audio\/[a-z0-9.+_-]+|application\/(?:zip|gzip|x-gzip|x-bzip2|x-compress|x-7z-compressed|x-rar-compressed|pdf|wasm|octet-stream))$/i;
12
12
 
13
13
  const compressibleCache = new Map();
14
14
  const MAX_MIME_CACHE_SIZE = 256;