slicejs-web-framework 4.0.0 → 4.0.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 (70) hide show
  1. package/.opencode/opencode.json +14 -14
  2. package/LICENSE +21 -21
  3. package/README.md +97 -97
  4. package/Slice/Components/Structural/ContextManager/ContextManager.js +422 -422
  5. package/Slice/Components/Structural/ContextManager/ContextManagerDebugger.js +470 -470
  6. package/Slice/Components/Structural/Controller/Controller.js +1406 -1406
  7. package/Slice/Components/Structural/Controller/allowedValuesValidation.js +52 -52
  8. package/Slice/Components/Structural/Debugger/Debugger.html +72 -72
  9. package/Slice/Components/Structural/Debugger/Debugger.js +1505 -1505
  10. package/Slice/Components/Structural/EventManager/EventManager.js +651 -651
  11. package/Slice/Components/Structural/EventManager/EventManagerDebugger.js +985 -985
  12. package/Slice/Components/Structural/LeakInspector/LeakInspector.js +296 -296
  13. package/Slice/Components/Structural/Logger/Log.js +10 -10
  14. package/Slice/Components/Structural/Logger/LogViewer/LogViewer.js +662 -662
  15. package/Slice/Components/Structural/Logger/Logger.js +225 -225
  16. package/Slice/Components/Structural/Router/Router.js +793 -793
  17. package/Slice/Components/Structural/StylesManager/StylesManager.js +82 -82
  18. package/Slice/Components/Structural/StylesManager/ThemeManager/ThemeManager.js +162 -162
  19. package/Slice/Slice.js +686 -697
  20. package/Slice/tests/build-bundled-component-without-category.test.js +103 -103
  21. package/Slice/tests/build-js-only-visual-components.test.js +144 -144
  22. package/Slice/tests/build-singleton.test.js +244 -244
  23. package/Slice/tests/bundle-v2-runtime-contract.test.js +738 -738
  24. package/Slice/tests/context-patch-use.test.js +95 -95
  25. package/Slice/tests/destroy-cascade.test.js +215 -215
  26. package/Slice/tests/event-emitters.test.js +79 -79
  27. package/Slice/tests/event-registry.test.js +141 -141
  28. package/Slice/tests/fixtures/real-runtime-loader.mjs +21 -21
  29. package/Slice/tests/leak-orphans.test.js +131 -131
  30. package/Slice/tests/props-allowed-values-validation.test.js +119 -119
  31. package/Slice/tests/public-env-runtime-accessors.test.js +44 -44
  32. package/Slice/tests/public-env-typed-accessors.test.js +66 -66
  33. package/Slice/tests/router-loading-finally.test.js +68 -68
  34. package/api/framework/devDepsOptimizer.js +335 -335
  35. package/api/framework/express.js +3 -3
  36. package/api/framework/precompressedStatic.js +93 -93
  37. package/api/framework/publicEnvResolver.js +123 -123
  38. package/api/framework/securityMiddleware.js +197 -197
  39. package/api/framework/server.js +411 -411
  40. package/api/index.js +7 -7
  41. package/api/tests/dev-deps-optimizer.test.js +137 -137
  42. package/api/tests/dev-server-external.test.js +56 -56
  43. package/api/tests/precompressed-static.test.js +119 -119
  44. package/api/tests/public-env-resolver.test.js +193 -193
  45. package/package.json +49 -49
  46. package/sliceConfig.schema.json +122 -122
  47. package/src/App/index.html +69 -69
  48. package/src/App/index.js +20 -20
  49. package/src/App/style.css +11 -11
  50. package/src/Components/AppComponents/AboutSection/AboutSection.css +9 -9
  51. package/src/Components/AppComponents/AboutSection/AboutSection.html +8 -8
  52. package/src/Components/AppComponents/AboutSection/AboutSection.js +12 -12
  53. package/src/Components/AppComponents/AppShell/AppShell.css +10 -10
  54. package/src/Components/AppComponents/AppShell/AppShell.html +4 -4
  55. package/src/Components/AppComponents/AppShell/AppShell.js +36 -36
  56. package/src/Components/AppComponents/HomeSection/HomeSection.css +20 -20
  57. package/src/Components/AppComponents/HomeSection/HomeSection.html +10 -10
  58. package/src/Components/AppComponents/HomeSection/HomeSection.js +19 -19
  59. package/src/Components/components.js +8 -8
  60. package/src/public/Styles/sliceStyles.css +34 -34
  61. package/src/public/Themes/Dark.css +42 -42
  62. package/src/public/Themes/Light.css +31 -31
  63. package/src/public/Themes/Slice.css +47 -47
  64. package/src/public/manifest.json +15 -15
  65. package/src/public/robots.txt +4 -4
  66. package/src/public/sitemap.xml +8 -8
  67. package/src/routes.js +9 -9
  68. package/src/sliceConfig.json +72 -72
  69. package/tests/router-emit-ordering.test.js +63 -63
  70. package/types/index.d.ts +219 -219
@@ -1,3 +1,3 @@
1
- import express from 'express';
2
-
3
- export default express;
1
+ import express from 'express';
2
+
3
+ export default express;
@@ -1,93 +1,93 @@
1
- // api/framework/precompressedStatic.js
2
- //
3
- // Serves a precompressed `.br`/`.gz` sibling (produced by `slice build --compress`)
4
- // when the client accepts that encoding and the file exists, instead of
5
- // compressing on every request. Falls through to the normal static handlers
6
- // when there is no precompressed variant or the client doesn't accept one.
7
- //
8
- // Mounted before the runtime `compression` middleware short-circuits it: the
9
- // response ends here with Content-Encoding already set, so compression never
10
- // re-compresses an already-encoded body.
11
-
12
- import fs from 'node:fs';
13
- import path from 'node:path';
14
-
15
- const ENCODING_EXTENSION = { br: '.br', gzip: '.gz' };
16
-
17
- // Only known text types are served pre-encoded; the Content-Type is taken from
18
- // the ORIGINAL extension (the browser must not see `.br` as the type).
19
- const CONTENT_TYPES = {
20
- '.js': 'application/javascript; charset=utf-8',
21
- '.mjs': 'application/javascript; charset=utf-8',
22
- '.cjs': 'application/javascript; charset=utf-8',
23
- '.css': 'text/css; charset=utf-8',
24
- '.html': 'text/html; charset=utf-8',
25
- '.htm': 'text/html; charset=utf-8',
26
- '.json': 'application/json; charset=utf-8',
27
- '.svg': 'image/svg+xml',
28
- '.xml': 'application/xml; charset=utf-8',
29
- '.txt': 'text/plain; charset=utf-8',
30
- '.map': 'application/json; charset=utf-8',
31
- '.webmanifest': 'application/manifest+json; charset=utf-8'
32
- };
33
-
34
- /** Encodings the client accepts, most-preferred first (brotli over gzip). */
35
- function preferredEncodings(acceptEncoding) {
36
- const accepts = String(acceptEncoding || '').toLowerCase();
37
- const list = [];
38
- if (accepts.includes('br')) list.push('br');
39
- if (accepts.includes('gzip')) list.push('gzip');
40
- return list;
41
- }
42
-
43
- /**
44
- * @param {string[]} roots Absolute directories to resolve `req.path` against,
45
- * in priority order (e.g. [dist/public, dist]).
46
- * @returns {import('express').RequestHandler}
47
- */
48
- export function createPrecompressedStatic(roots = []) {
49
- const resolvedRoots = roots.filter(Boolean).map((r) => path.resolve(r));
50
-
51
- return function precompressedStatic(req, res, next) {
52
- if (req.method !== 'GET' && req.method !== 'HEAD') return next();
53
-
54
- const encodings = preferredEncodings(req.headers['accept-encoding']);
55
- if (encodings.length === 0) return next();
56
-
57
- let rel;
58
- try {
59
- rel = decodeURIComponent(req.path).replace(/^\/+/, '');
60
- } catch {
61
- return next();
62
- }
63
- if (!rel || rel.includes('\0')) return next();
64
-
65
- const ext = path.extname(rel).toLowerCase();
66
- const contentType = CONTENT_TYPES[ext];
67
- if (!contentType) return next();
68
-
69
- for (const root of resolvedRoots) {
70
- const target = path.normalize(path.join(root, rel));
71
- // Path traversal guard: the resolved file must stay under the root.
72
- if (target !== root && !target.startsWith(root + path.sep)) continue;
73
-
74
- for (const enc of encodings) {
75
- const encodedPath = target + ENCODING_EXTENSION[enc];
76
- try {
77
- if (fs.existsSync(encodedPath) && fs.statSync(encodedPath).isFile()) {
78
- res.setHeader('Content-Type', contentType);
79
- res.setHeader('Content-Encoding', enc);
80
- res.setHeader('Vary', 'Accept-Encoding');
81
- return res.send(fs.readFileSync(encodedPath));
82
- }
83
- } catch {
84
- // Unreadable candidate — try the next encoding/root.
85
- }
86
- }
87
- }
88
-
89
- return next();
90
- };
91
- }
92
-
93
- export default createPrecompressedStatic;
1
+ // api/framework/precompressedStatic.js
2
+ //
3
+ // Serves a precompressed `.br`/`.gz` sibling (produced by `slice build --compress`)
4
+ // when the client accepts that encoding and the file exists, instead of
5
+ // compressing on every request. Falls through to the normal static handlers
6
+ // when there is no precompressed variant or the client doesn't accept one.
7
+ //
8
+ // Mounted before the runtime `compression` middleware short-circuits it: the
9
+ // response ends here with Content-Encoding already set, so compression never
10
+ // re-compresses an already-encoded body.
11
+
12
+ import fs from 'node:fs';
13
+ import path from 'node:path';
14
+
15
+ const ENCODING_EXTENSION = { br: '.br', gzip: '.gz' };
16
+
17
+ // Only known text types are served pre-encoded; the Content-Type is taken from
18
+ // the ORIGINAL extension (the browser must not see `.br` as the type).
19
+ const CONTENT_TYPES = {
20
+ '.js': 'application/javascript; charset=utf-8',
21
+ '.mjs': 'application/javascript; charset=utf-8',
22
+ '.cjs': 'application/javascript; charset=utf-8',
23
+ '.css': 'text/css; charset=utf-8',
24
+ '.html': 'text/html; charset=utf-8',
25
+ '.htm': 'text/html; charset=utf-8',
26
+ '.json': 'application/json; charset=utf-8',
27
+ '.svg': 'image/svg+xml',
28
+ '.xml': 'application/xml; charset=utf-8',
29
+ '.txt': 'text/plain; charset=utf-8',
30
+ '.map': 'application/json; charset=utf-8',
31
+ '.webmanifest': 'application/manifest+json; charset=utf-8'
32
+ };
33
+
34
+ /** Encodings the client accepts, most-preferred first (brotli over gzip). */
35
+ function preferredEncodings(acceptEncoding) {
36
+ const accepts = String(acceptEncoding || '').toLowerCase();
37
+ const list = [];
38
+ if (accepts.includes('br')) list.push('br');
39
+ if (accepts.includes('gzip')) list.push('gzip');
40
+ return list;
41
+ }
42
+
43
+ /**
44
+ * @param {string[]} roots Absolute directories to resolve `req.path` against,
45
+ * in priority order (e.g. [dist/public, dist]).
46
+ * @returns {import('express').RequestHandler}
47
+ */
48
+ export function createPrecompressedStatic(roots = []) {
49
+ const resolvedRoots = roots.filter(Boolean).map((r) => path.resolve(r));
50
+
51
+ return function precompressedStatic(req, res, next) {
52
+ if (req.method !== 'GET' && req.method !== 'HEAD') return next();
53
+
54
+ const encodings = preferredEncodings(req.headers['accept-encoding']);
55
+ if (encodings.length === 0) return next();
56
+
57
+ let rel;
58
+ try {
59
+ rel = decodeURIComponent(req.path).replace(/^\/+/, '');
60
+ } catch {
61
+ return next();
62
+ }
63
+ if (!rel || rel.includes('\0')) return next();
64
+
65
+ const ext = path.extname(rel).toLowerCase();
66
+ const contentType = CONTENT_TYPES[ext];
67
+ if (!contentType) return next();
68
+
69
+ for (const root of resolvedRoots) {
70
+ const target = path.normalize(path.join(root, rel));
71
+ // Path traversal guard: the resolved file must stay under the root.
72
+ if (target !== root && !target.startsWith(root + path.sep)) continue;
73
+
74
+ for (const enc of encodings) {
75
+ const encodedPath = target + ENCODING_EXTENSION[enc];
76
+ try {
77
+ if (fs.existsSync(encodedPath) && fs.statSync(encodedPath).isFile()) {
78
+ res.setHeader('Content-Type', contentType);
79
+ res.setHeader('Content-Encoding', enc);
80
+ res.setHeader('Vary', 'Accept-Encoding');
81
+ return res.send(fs.readFileSync(encodedPath));
82
+ }
83
+ } catch {
84
+ // Unreadable candidate — try the next encoding/root.
85
+ }
86
+ }
87
+ }
88
+
89
+ return next();
90
+ };
91
+ }
92
+
93
+ export default createPrecompressedStatic;
@@ -1,123 +1,123 @@
1
- import { readFileSync, existsSync } from 'node:fs';
2
-
3
- const PUBLIC_PREFIX = 'SLICE_PUBLIC_';
4
- const SUSPICIOUS_TERMS = ['SECRET', 'TOKEN', 'PASSWORD', 'PRIVATE', 'API_KEY', 'ACCESS_KEY', 'CREDENTIAL'];
5
-
6
- function parseEnvFile(envFilePath) {
7
- if (!envFilePath || !existsSync(envFilePath)) {
8
- return {};
9
- }
10
-
11
- let fileContent;
12
- try {
13
- fileContent = readFileSync(envFilePath, 'utf8').replace(/^\uFEFF/, '');
14
- } catch (error) {
15
- console.error(`Error reading env file ${envFilePath}:`, error);
16
- return {};
17
- }
18
- const parsed = {};
19
- const lines = fileContent.split(/\r?\n/);
20
-
21
- for (const rawLine of lines) {
22
- const line = rawLine.trim();
23
-
24
- if (!line || line.startsWith('#')) {
25
- continue;
26
- }
27
-
28
- const equalsIndex = line.indexOf('=');
29
- if (equalsIndex === -1) {
30
- continue;
31
- }
32
-
33
- let key = line.slice(0, equalsIndex).trim();
34
- if (!key) {
35
- continue;
36
- }
37
-
38
- if (key.startsWith('export ')) {
39
- key = key.slice('export '.length).trim();
40
- }
41
-
42
- let value = line.slice(equalsIndex + 1).trim();
43
-
44
- const quotedWithOptionalCommentMatch = value.match(/^(["'])(.*?)\1(?:\s+#.*)?$/);
45
-
46
- if (quotedWithOptionalCommentMatch) {
47
- value = quotedWithOptionalCommentMatch[2];
48
- } else {
49
- value = value.replace(/\s+#.*$/, '').trimEnd();
50
- }
51
-
52
- parsed[key] = value;
53
- }
54
-
55
- return parsed;
56
- }
57
-
58
- function warnSuspiciousKey(key, logger, warnedKeys) {
59
- const upperKey = key.toUpperCase();
60
- const isSuspicious = SUSPICIOUS_TERMS.some((term) => upperKey.includes(term));
61
-
62
- if (isSuspicious && !warnedKeys.has(key) && logger && typeof logger.warn === 'function') {
63
- logger.warn(`[slice-env] Suspicious public environment key detected: ${key}`);
64
- warnedKeys.add(key);
65
- }
66
- }
67
-
68
- function buildPublicPayload({ envFromFile, processEnv, logger }) {
69
- const env = {};
70
- const warnedKeys = new Set();
71
-
72
- for (const [key, value] of Object.entries(envFromFile)) {
73
- if (key.startsWith(PUBLIC_PREFIX)) {
74
- env[key] = String(value ?? '');
75
- warnSuspiciousKey(key, logger, warnedKeys);
76
- }
77
- }
78
-
79
- for (const [key, value] of Object.entries(processEnv || {})) {
80
- if (key.startsWith(PUBLIC_PREFIX)) {
81
- env[key] = String(value ?? '');
82
- warnSuspiciousKey(key, logger, warnedKeys);
83
- }
84
- }
85
-
86
- return env;
87
- }
88
-
89
- export function resolvePublicEnv({ mode, envFilePath, processEnv = process.env, logger = console }) {
90
- const envFromFile = parseEnvFile(envFilePath);
91
- const env = buildPublicPayload({
92
- envFromFile,
93
- processEnv,
94
- logger,
95
- });
96
-
97
- return {
98
- mode,
99
- env,
100
- };
101
- }
102
-
103
- export function createPublicEnvProvider({ mode, envFilePath, processEnv = process.env, logger = console }) {
104
- if (mode === 'production') {
105
- let cachedPayload;
106
-
107
- return {
108
- getPayload() {
109
- if (!cachedPayload) {
110
- cachedPayload = resolvePublicEnv({ mode, envFilePath, processEnv, logger });
111
- }
112
-
113
- return cachedPayload;
114
- },
115
- };
116
- }
117
-
118
- return {
119
- getPayload() {
120
- return resolvePublicEnv({ mode, envFilePath, processEnv, logger });
121
- },
122
- };
123
- }
1
+ import { readFileSync, existsSync } from 'node:fs';
2
+
3
+ const PUBLIC_PREFIX = 'SLICE_PUBLIC_';
4
+ const SUSPICIOUS_TERMS = ['SECRET', 'TOKEN', 'PASSWORD', 'PRIVATE', 'API_KEY', 'ACCESS_KEY', 'CREDENTIAL'];
5
+
6
+ function parseEnvFile(envFilePath) {
7
+ if (!envFilePath || !existsSync(envFilePath)) {
8
+ return {};
9
+ }
10
+
11
+ let fileContent;
12
+ try {
13
+ fileContent = readFileSync(envFilePath, 'utf8').replace(/^\uFEFF/, '');
14
+ } catch (error) {
15
+ console.error(`Error reading env file ${envFilePath}:`, error);
16
+ return {};
17
+ }
18
+ const parsed = {};
19
+ const lines = fileContent.split(/\r?\n/);
20
+
21
+ for (const rawLine of lines) {
22
+ const line = rawLine.trim();
23
+
24
+ if (!line || line.startsWith('#')) {
25
+ continue;
26
+ }
27
+
28
+ const equalsIndex = line.indexOf('=');
29
+ if (equalsIndex === -1) {
30
+ continue;
31
+ }
32
+
33
+ let key = line.slice(0, equalsIndex).trim();
34
+ if (!key) {
35
+ continue;
36
+ }
37
+
38
+ if (key.startsWith('export ')) {
39
+ key = key.slice('export '.length).trim();
40
+ }
41
+
42
+ let value = line.slice(equalsIndex + 1).trim();
43
+
44
+ const quotedWithOptionalCommentMatch = value.match(/^(["'])(.*?)\1(?:\s+#.*)?$/);
45
+
46
+ if (quotedWithOptionalCommentMatch) {
47
+ value = quotedWithOptionalCommentMatch[2];
48
+ } else {
49
+ value = value.replace(/\s+#.*$/, '').trimEnd();
50
+ }
51
+
52
+ parsed[key] = value;
53
+ }
54
+
55
+ return parsed;
56
+ }
57
+
58
+ function warnSuspiciousKey(key, logger, warnedKeys) {
59
+ const upperKey = key.toUpperCase();
60
+ const isSuspicious = SUSPICIOUS_TERMS.some((term) => upperKey.includes(term));
61
+
62
+ if (isSuspicious && !warnedKeys.has(key) && logger && typeof logger.warn === 'function') {
63
+ logger.warn(`[slice-env] Suspicious public environment key detected: ${key}`);
64
+ warnedKeys.add(key);
65
+ }
66
+ }
67
+
68
+ function buildPublicPayload({ envFromFile, processEnv, logger }) {
69
+ const env = {};
70
+ const warnedKeys = new Set();
71
+
72
+ for (const [key, value] of Object.entries(envFromFile)) {
73
+ if (key.startsWith(PUBLIC_PREFIX)) {
74
+ env[key] = String(value ?? '');
75
+ warnSuspiciousKey(key, logger, warnedKeys);
76
+ }
77
+ }
78
+
79
+ for (const [key, value] of Object.entries(processEnv || {})) {
80
+ if (key.startsWith(PUBLIC_PREFIX)) {
81
+ env[key] = String(value ?? '');
82
+ warnSuspiciousKey(key, logger, warnedKeys);
83
+ }
84
+ }
85
+
86
+ return env;
87
+ }
88
+
89
+ export function resolvePublicEnv({ mode, envFilePath, processEnv = process.env, logger = console }) {
90
+ const envFromFile = parseEnvFile(envFilePath);
91
+ const env = buildPublicPayload({
92
+ envFromFile,
93
+ processEnv,
94
+ logger,
95
+ });
96
+
97
+ return {
98
+ mode,
99
+ env,
100
+ };
101
+ }
102
+
103
+ export function createPublicEnvProvider({ mode, envFilePath, processEnv = process.env, logger = console }) {
104
+ if (mode === 'production') {
105
+ let cachedPayload;
106
+
107
+ return {
108
+ getPayload() {
109
+ if (!cachedPayload) {
110
+ cachedPayload = resolvePublicEnv({ mode, envFilePath, processEnv, logger });
111
+ }
112
+
113
+ return cachedPayload;
114
+ },
115
+ };
116
+ }
117
+
118
+ return {
119
+ getPayload() {
120
+ return resolvePublicEnv({ mode, envFilePath, processEnv, logger });
121
+ },
122
+ };
123
+ }