slicejs-web-framework 4.0.2 → 4.1.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.
Files changed (73) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +97 -97
  3. package/Slice/Components/Structural/ContextManager/ContextManager.js +422 -422
  4. package/Slice/Components/Structural/ContextManager/ContextManagerDebugger.js +470 -470
  5. package/Slice/Components/Structural/Controller/Controller.js +1406 -1406
  6. package/Slice/Components/Structural/Controller/allowedValuesValidation.js +52 -52
  7. package/Slice/Components/Structural/Debugger/Debugger.html +72 -72
  8. package/Slice/Components/Structural/Debugger/Debugger.js +1505 -1505
  9. package/Slice/Components/Structural/EventManager/EventManager.js +651 -651
  10. package/Slice/Components/Structural/EventManager/EventManagerDebugger.js +985 -985
  11. package/Slice/Components/Structural/LeakInspector/LeakInspector.js +296 -296
  12. package/Slice/Components/Structural/Logger/Log.js +10 -10
  13. package/Slice/Components/Structural/Logger/LogViewer/LogViewer.js +662 -662
  14. package/Slice/Components/Structural/Logger/Logger.js +225 -225
  15. package/Slice/Components/Structural/Router/Router.js +820 -810
  16. package/Slice/Components/Structural/Router/applyRouteMetadata.js +39 -0
  17. package/Slice/Components/Structural/StylesManager/StylesManager.js +82 -82
  18. package/Slice/Components/Structural/StylesManager/ThemeManager/ThemeManager.js +175 -175
  19. package/Slice/Slice.js +711 -710
  20. package/api/framework/devDepsOptimizer.js +335 -335
  21. package/api/framework/express.js +3 -3
  22. package/api/framework/precompressedStatic.js +102 -93
  23. package/api/framework/publicEnvResolver.js +123 -123
  24. package/api/framework/securityMiddleware.js +197 -197
  25. package/api/framework/seo.js +387 -0
  26. package/api/framework/server.js +777 -410
  27. package/api/index.js +14 -4
  28. package/package.json +6 -2
  29. package/sliceConfig.schema.json +122 -122
  30. package/src/App/index.html +69 -69
  31. package/src/App/index.js +20 -20
  32. package/src/App/style.css +11 -11
  33. package/src/Components/AppComponents/AboutSection/AboutSection.css +9 -9
  34. package/src/Components/AppComponents/AboutSection/AboutSection.html +8 -8
  35. package/src/Components/AppComponents/AboutSection/AboutSection.js +12 -12
  36. package/src/Components/AppComponents/AppShell/AppShell.css +10 -10
  37. package/src/Components/AppComponents/AppShell/AppShell.html +4 -4
  38. package/src/Components/AppComponents/AppShell/AppShell.js +36 -36
  39. package/src/Components/AppComponents/HomeSection/HomeSection.css +20 -20
  40. package/src/Components/AppComponents/HomeSection/HomeSection.html +10 -10
  41. package/src/Components/AppComponents/HomeSection/HomeSection.js +19 -19
  42. package/src/Components/components.js +8 -8
  43. package/src/public/Styles/sliceStyles.css +34 -34
  44. package/src/public/Themes/Dark.css +42 -42
  45. package/src/public/Themes/Light.css +31 -31
  46. package/src/public/Themes/Slice.css +47 -47
  47. package/src/public/manifest.json +15 -15
  48. package/src/public/robots.txt +4 -4
  49. package/src/public/sitemap.xml +8 -8
  50. package/src/routes.js +9 -9
  51. package/src/sliceConfig.json +72 -72
  52. package/types/index.d.ts +219 -219
  53. package/.opencode/opencode.json +0 -15
  54. package/Slice/tests/build-bundled-component-without-category.test.js +0 -103
  55. package/Slice/tests/build-js-only-visual-components.test.js +0 -144
  56. package/Slice/tests/build-singleton.test.js +0 -244
  57. package/Slice/tests/bundle-v2-runtime-contract.test.js +0 -747
  58. package/Slice/tests/context-patch-use.test.js +0 -95
  59. package/Slice/tests/destroy-cascade.test.js +0 -215
  60. package/Slice/tests/event-emitters.test.js +0 -79
  61. package/Slice/tests/event-registry.test.js +0 -141
  62. package/Slice/tests/fixtures/real-runtime-loader.mjs +0 -21
  63. package/Slice/tests/leak-orphans.test.js +0 -131
  64. package/Slice/tests/props-allowed-values-validation.test.js +0 -119
  65. package/Slice/tests/public-env-runtime-accessors.test.js +0 -44
  66. package/Slice/tests/public-env-typed-accessors.test.js +0 -66
  67. package/Slice/tests/router-loading-finally.test.js +0 -68
  68. package/api/tests/dev-deps-optimizer.test.js +0 -137
  69. package/api/tests/dev-server-external.test.js +0 -56
  70. package/api/tests/precompressed-static.test.js +0 -119
  71. package/api/tests/public-env-resolver.test.js +0 -193
  72. package/tests/router-build-failure.test.js +0 -125
  73. package/tests/router-emit-ordering.test.js +0 -63
@@ -1,103 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
-
4
- globalThis.alert = () => {};
5
-
6
- const { default: Slice } = await import('../Slice.js');
7
-
8
- class FakeController {
9
- constructor() {
10
- this.componentCategories = new Map();
11
- this.templates = new Map();
12
- this.classes = new Map();
13
- this.requestedStyles = new Set();
14
- this.loadedBundles = new Set();
15
- this.activeComponents = new Map();
16
- this.bundleLoads = 0;
17
- }
18
-
19
- getBundleForComponent(componentName) {
20
- return componentName === 'DocumentationPage' ? 'multiroute-DocumentationPage--p1' : null;
21
- }
22
-
23
- async loadBundle(bundleName) {
24
- this.bundleLoads += 1;
25
- this.loadedBundles.add(bundleName);
26
- this.classes.set(
27
- 'DocumentationPage',
28
- class DocumentationPage {
29
- constructor(props) {
30
- this.props = props;
31
- this.sliceId = 'DocumentationPage';
32
- }
33
- async init() {}
34
- }
35
- );
36
- }
37
-
38
- isComponentFromBundle(componentName) {
39
- return componentName === 'DocumentationPage' && this.loadedBundles.has('multiroute-DocumentationPage--p1');
40
- }
41
-
42
- verifyComponentIds() {
43
- return true;
44
- }
45
-
46
- registerComponent() {}
47
-
48
- registerComponentsRecursively() {}
49
- }
50
-
51
- class FakeStylesManager {
52
- registerComponentStyles() {}
53
- }
54
-
55
- function createSlice() {
56
- const instance = new Slice(
57
- {
58
- paths: {
59
- components: {
60
- AppComponents: {
61
- path: '/Components/AppComponents',
62
- type: 'Visual'
63
- }
64
- }
65
- },
66
- themeManager: {},
67
- stylesManager: {},
68
- logger: {},
69
- debugger: { enabled: false },
70
- loading: {},
71
- events: {}
72
- },
73
- {
74
- Controller: FakeController,
75
- StylesManager: FakeStylesManager
76
- }
77
- );
78
-
79
- instance.logger = {
80
- logError() {},
81
- logWarning() {},
82
- logInfo() {}
83
- };
84
-
85
- return instance;
86
- }
87
-
88
- test('build succeeds for bundled component even when componentCategories is missing entry', async () => {
89
- const originalSlice = globalThis.slice;
90
-
91
- try {
92
- const sliceInstance = createSlice();
93
- globalThis.slice = sliceInstance;
94
-
95
- const component = await sliceInstance.build('DocumentationPage', { from: 'route' });
96
-
97
- assert.ok(component);
98
- assert.equal(component.sliceId, 'DocumentationPage');
99
- assert.equal(sliceInstance.controller.bundleLoads, 1);
100
- } finally {
101
- globalThis.slice = originalSlice;
102
- }
103
- });
@@ -1,144 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
-
4
- globalThis.alert = () => {};
5
-
6
- const { default: Slice } = await import('../Slice.js');
7
-
8
- class FakeController {
9
- constructor() {
10
- this.componentCategories = new Map([
11
- ['MultiRoute', 'Visual'],
12
- ['Route', 'Visual'],
13
- ['Link', 'Visual'],
14
- ['Button', 'Visual']
15
- ]);
16
- this.templates = new Map();
17
- this.classes = new Map();
18
- this.requestedStyles = new Set();
19
- this.loadedBundles = new Set();
20
- this.activeComponents = new Map();
21
- this.fetchCalls = 0;
22
- }
23
-
24
- getBundleForComponent() {
25
- return null;
26
- }
27
-
28
- isComponentFromBundle() {
29
- return false;
30
- }
31
-
32
- async fetchText() {
33
- this.fetchCalls += 1;
34
- throw new Error('fetchText should not be called for js-only visual components');
35
- }
36
-
37
- verifyComponentIds() {
38
- return true;
39
- }
40
-
41
- registerComponent() {}
42
-
43
- registerComponentsRecursively() {}
44
- }
45
-
46
- class FakeStylesManager {
47
- registerComponentStyles() {}
48
- }
49
-
50
- function createSlice() {
51
- const instance = new Slice(
52
- {
53
- paths: {
54
- components: {
55
- Visual: {
56
- path: '/Components/Visual',
57
- type: 'Visual'
58
- }
59
- }
60
- },
61
- themeManager: {},
62
- stylesManager: {},
63
- logger: {},
64
- debugger: { enabled: false },
65
- loading: {},
66
- events: {}
67
- },
68
- {
69
- Controller: FakeController,
70
- StylesManager: FakeStylesManager
71
- }
72
- );
73
-
74
- instance.logger = {
75
- logError() {},
76
- logWarning() {},
77
- logInfo() {}
78
- };
79
-
80
- return instance;
81
- }
82
-
83
- test('build does not fetch html/css for MultiRoute and Route', async () => {
84
- const originalSlice = globalThis.slice;
85
-
86
- try {
87
- const sliceInstance = createSlice();
88
- globalThis.slice = sliceInstance;
89
-
90
- class MultiRouteComponent {
91
- constructor(props) {
92
- this.props = props;
93
- this.sliceId = 'MultiRoute';
94
- }
95
- async init() {}
96
- }
97
-
98
- class RouteComponent {
99
- constructor(props) {
100
- this.props = props;
101
- this.sliceId = 'Route';
102
- }
103
- async init() {}
104
- }
105
-
106
- sliceInstance.controller.classes.set('MultiRoute', MultiRouteComponent);
107
- sliceInstance.controller.classes.set('Route', RouteComponent);
108
-
109
- const builtMultiRoute = await sliceInstance.build('MultiRoute', {});
110
- const builtRoute = await sliceInstance.build('Route', {});
111
-
112
- assert.ok(builtMultiRoute, 'Expected MultiRoute instance to be created');
113
- assert.ok(builtRoute, 'Expected Route instance to be created');
114
- assert.equal(sliceInstance.controller.fetchCalls, 0);
115
- } finally {
116
- globalThis.slice = originalSlice;
117
- }
118
- });
119
-
120
- test('build still fetches html/css for Link', async () => {
121
- const originalSlice = globalThis.slice;
122
-
123
- try {
124
- const sliceInstance = createSlice();
125
- globalThis.slice = sliceInstance;
126
-
127
- class LinkComponent {
128
- constructor(props) {
129
- this.props = props;
130
- this.sliceId = 'Link';
131
- }
132
- async init() {}
133
- }
134
-
135
- sliceInstance.controller.classes.set('Link', LinkComponent);
136
-
137
- const builtLink = await sliceInstance.build('Link', {});
138
-
139
- assert.equal(builtLink, null);
140
- assert.ok(sliceInstance.controller.fetchCalls > 0, 'Expected Link to trigger resource fetches');
141
- } finally {
142
- globalThis.slice = originalSlice;
143
- }
144
- });
@@ -1,244 +0,0 @@
1
- // Real-runtime integration tests for build({ singleton: true }).
2
- //
3
- // These load the REAL Slice + REAL Controller (no FakeController): the only
4
- // fixture is the components map, injected via a resolve hook because Controller
5
- // imports a browser-absolute '/Components/components.js'. Singletons are
6
- // Service-only (no DOM), so the full build → getOrCreate → _build →
7
- // registerComponent path runs end-to-end under node, exercising the actual
8
- // shipped code.
9
- import { register } from 'node:module';
10
- import test from 'node:test';
11
- import assert from 'node:assert/strict';
12
-
13
- globalThis.alert = () => {};
14
- register(new URL('./fixtures/real-runtime-loader.mjs', import.meta.url));
15
-
16
- const { default: Slice } = await import('../Slice.js');
17
- const { default: Controller } = await import('../Components/Structural/Controller/Controller.js');
18
-
19
- // A real Service-shaped class. The components fixture registers 'Probe' as a
20
- // Service and 'ModalProbe' as a Visual.
21
- let constructCount = 0;
22
- let failNext = false;
23
-
24
- class Probe {
25
- constructor(props) {
26
- constructCount += 1;
27
- if (failNext) {
28
- failNext = false;
29
- throw new Error('boom'); // simulate a failed build on first attempt
30
- }
31
- this.props = props;
32
- }
33
- async init() {}
34
- }
35
-
36
- class FakeStylesManager {
37
- registerComponentStyles() {}
38
- }
39
-
40
- function createSlice() {
41
- const instance = new Slice(
42
- {
43
- paths: {
44
- components: {
45
- Service: { path: '/Components/Service', type: 'Service' },
46
- Visual: { path: '/Components/Visual', type: 'Visual' },
47
- // Custom category whose type is Service (e.g. an app's own services).
48
- AppServices: { path: '/Components/AppServices', type: 'Service' }
49
- }
50
- },
51
- themeManager: {},
52
- stylesManager: {},
53
- logger: {},
54
- debugger: { enabled: false },
55
- loading: {},
56
- events: {}
57
- },
58
- { Controller, StylesManager: FakeStylesManager }
59
- );
60
-
61
- instance.logger = { logError() {}, logWarning() {}, logInfo() {} };
62
- // Pre-seed the class so _build skips the network fetch (the real path for a
63
- // class already cached by the controller). Everything else is real.
64
- instance.controller.classes.set('Probe', Probe);
65
- // AppProbe is a Service-shaped class registered under the custom 'AppServices'
66
- // category; reuse the Probe class (same shape).
67
- instance.controller.classes.set('AppProbe', Probe);
68
- return instance;
69
- }
70
-
71
- function withSlice(fn) {
72
- return async () => {
73
- const original = globalThis.slice;
74
- constructCount = 0;
75
- failNext = false;
76
- const slice = createSlice();
77
- globalThis.slice = slice;
78
- try {
79
- await fn(slice);
80
- } finally {
81
- globalThis.slice = original;
82
- }
83
- };
84
- }
85
-
86
- test(
87
- 'singleton returns the same instance across awaited calls and builds once',
88
- withSlice(async (slice) => {
89
- const a = await slice.build('Probe', { singleton: true });
90
- const b = await slice.build('Probe', { singleton: true });
91
-
92
- assert.ok(a, 'first call returns an instance');
93
- assert.equal(a, b, 'same instance reference');
94
- assert.equal(constructCount, 1, 'constructed exactly once');
95
- assert.equal(a.sliceId, 'Probe', 'sliceId defaults to component name');
96
- assert.equal(slice.controller.activeComponents.get('Probe'), a, 'registered in activeComponents');
97
- })
98
- );
99
-
100
- test(
101
- 'concurrent singleton builds share one in-flight build (race-safe)',
102
- withSlice(async (slice) => {
103
- const [a, b] = await Promise.all([
104
- slice.build('Probe', { singleton: true }),
105
- slice.build('Probe', { singleton: true })
106
- ]);
107
-
108
- assert.equal(a, b, 'both callers get the same instance');
109
- assert.equal(constructCount, 1, 'deduped to a single construction');
110
- })
111
- );
112
-
113
- test(
114
- 'named singletons via distinct sliceId are separate instances',
115
- withSlice(async (slice) => {
116
- const a = await slice.build('Probe', { singleton: true, sliceId: 'probeA' });
117
- const b = await slice.build('Probe', { singleton: true, sliceId: 'probeB' });
118
-
119
- assert.notEqual(a, b);
120
- assert.equal(a.sliceId, 'probeA');
121
- assert.equal(b.sliceId, 'probeB');
122
- assert.equal(constructCount, 2);
123
- })
124
- );
125
-
126
- test(
127
- 'a failed singleton build is not cached and the next call retries',
128
- withSlice(async (slice) => {
129
- failNext = true;
130
- const first = await slice.build('Probe', { singleton: true });
131
- assert.equal(first, null, 'failed build resolves to null');
132
- assert.equal(
133
- slice.controller._pendingBuilds.has('Probe'),
134
- false,
135
- 'pending entry cleared after settle'
136
- );
137
- assert.equal(
138
- slice.controller.activeComponents.has('Probe'),
139
- false,
140
- 'failed build never registered'
141
- );
142
-
143
- const second = await slice.build('Probe', { singleton: true });
144
- assert.ok(second, 'retry succeeds');
145
- assert.equal(constructCount, 2, 'retried construction');
146
- })
147
- );
148
-
149
- test(
150
- 'singleton:true is rejected for non-Service components',
151
- withSlice(async (slice) => {
152
- let errored = false;
153
- slice.logger.logError = () => {
154
- errored = true;
155
- };
156
-
157
- const result = await slice.build('ModalProbe', { singleton: true });
158
-
159
- assert.equal(result, null, 'returns null for a Visual');
160
- assert.equal(errored, true, 'logs an error');
161
- assert.equal(constructCount, 0, 'never constructed');
162
- })
163
- );
164
-
165
- test(
166
- 'singleton:true is allowed for a custom Service-type category (AppServices)',
167
- withSlice(async (slice) => {
168
- let errored = false;
169
- slice.logger.logError = () => {
170
- errored = true;
171
- };
172
-
173
- const a = await slice.build('AppProbe', { singleton: true });
174
- const b = await slice.build('AppProbe', { singleton: true });
175
-
176
- assert.ok(a, 'builds the instance');
177
- assert.equal(a, b, 'returns the same singleton instance');
178
- assert.equal(errored, false, 'does not log an error for a Service-type category');
179
- assert.equal(constructCount, 1, 'constructed exactly once');
180
- })
181
- );
182
-
183
- test(
184
- 'default (non-singleton) build path is unchanged',
185
- withSlice(async (slice) => {
186
- const a = await slice.build('Probe');
187
- const b = await slice.build('Probe');
188
-
189
- assert.ok(a);
190
- assert.ok(b);
191
- assert.notEqual(a, b, 'each non-singleton build is a fresh instance');
192
- assert.equal(constructCount, 2);
193
- })
194
- );
195
-
196
- test(
197
- 'props only apply on the first (creating) call',
198
- withSlice(async (slice) => {
199
- const a = await slice.build('Probe', { singleton: true, tag: 'first' });
200
- const b = await slice.build('Probe', { singleton: true, tag: 'second' });
201
-
202
- assert.equal(a, b, 'same instance');
203
- assert.equal(constructCount, 1, 'not rebuilt');
204
- assert.equal(a.props.tag, 'first', 'later props are ignored');
205
- })
206
- );
207
-
208
- test(
209
- 'the singleton/sliceId directives are not forwarded to the component props',
210
- withSlice(async (slice) => {
211
- const a = await slice.build('Probe', { singleton: true, sliceId: 'probeX', tag: 'keep' });
212
-
213
- assert.equal(a.props.tag, 'keep', 'real props pass through');
214
- assert.equal('singleton' in a.props, false, 'singleton flag stripped');
215
- assert.equal('sliceId' in a.props, false, 'sliceId directive stripped');
216
- })
217
- );
218
-
219
- test(
220
- 'singleton is stripped from props even on a non-singleton build',
221
- withSlice(async (slice) => {
222
- const a = await slice.build('Probe', { singleton: false, tag: 'keep' });
223
-
224
- assert.ok(a);
225
- assert.equal(a.props.tag, 'keep', 'real props pass through');
226
- assert.equal('singleton' in a.props, false, 'singleton key never reaches the component');
227
- })
228
- );
229
-
230
- test(
231
- 'singleton:true on an unregistered component is rejected (unknown category)',
232
- withSlice(async (slice) => {
233
- let errored = false;
234
- slice.logger.logError = () => {
235
- errored = true;
236
- };
237
-
238
- const result = await slice.build('NotRegistered', { singleton: true });
239
-
240
- assert.equal(result, null, 'returns null');
241
- assert.equal(errored, true, 'logs an error');
242
- assert.equal(constructCount, 0, 'never constructed');
243
- })
244
- );