@bleedingdev/modern-js-runtime-extensions 3.9.0-ultramodern.9 → 3.9.3-ultramodern.10

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 (126) hide show
  1. package/dist/cjs/{reactContext.js → localisedUrls/config.js} +14 -10
  2. package/dist/cjs/localisedUrls/index.js +95 -0
  3. package/dist/cjs/localisedUrls/normalise.js +87 -0
  4. package/dist/cjs/localisedUrls/pathname.js +68 -0
  5. package/dist/cjs/localisedUrls/patterns.js +214 -0
  6. package/dist/cjs/localisedUrls/redirect.js +104 -0
  7. package/dist/cjs/localisedUrls/resolve.js +78 -0
  8. package/dist/cjs/localisedUrls/routeIdentity.js +92 -0
  9. package/dist/cjs/localisedUrls/routes.js +217 -0
  10. package/dist/cjs/localisedUrls/types.js +18 -0
  11. package/dist/cjs/rendererHead.js +18 -5
  12. package/dist/cjs/request-policy/policyCore.js +1 -1
  13. package/dist/cjs/request-policy/transport.js +2 -2
  14. package/dist/cjs/routerState.js +85 -77
  15. package/dist/cjs/routerStatePlugin.js +63 -9
  16. package/dist/cjs/server-config/serverSource.js +18 -0
  17. package/dist/esm/localisedUrls/config.mjs +11 -0
  18. package/dist/esm/localisedUrls/index.mjs +8 -0
  19. package/dist/esm/localisedUrls/normalise.mjs +37 -0
  20. package/dist/esm/localisedUrls/pathname.mjs +24 -0
  21. package/dist/esm/localisedUrls/patterns.mjs +152 -0
  22. package/dist/esm/localisedUrls/redirect.mjs +57 -0
  23. package/dist/esm/localisedUrls/resolve.mjs +37 -0
  24. package/dist/esm/localisedUrls/routeIdentity.mjs +54 -0
  25. package/dist/esm/localisedUrls/routes.mjs +176 -0
  26. package/dist/esm/localisedUrls/types.mjs +0 -0
  27. package/dist/esm/rendererHead.mjs +14 -5
  28. package/dist/esm/request-policy/policyCore.mjs +1 -1
  29. package/dist/esm/request-policy/transport.mjs +2 -2
  30. package/dist/esm/routerState.mjs +81 -73
  31. package/dist/esm/routerStatePlugin.mjs +62 -11
  32. package/dist/esm/server-config/serverSource.mjs +0 -0
  33. package/dist/esm-node/localisedUrls/config.mjs +12 -0
  34. package/dist/esm-node/localisedUrls/index.mjs +9 -0
  35. package/dist/esm-node/localisedUrls/normalise.mjs +38 -0
  36. package/dist/esm-node/localisedUrls/pathname.mjs +25 -0
  37. package/dist/esm-node/localisedUrls/patterns.mjs +153 -0
  38. package/dist/esm-node/localisedUrls/redirect.mjs +58 -0
  39. package/dist/esm-node/localisedUrls/resolve.mjs +38 -0
  40. package/dist/esm-node/localisedUrls/routeIdentity.mjs +55 -0
  41. package/dist/esm-node/localisedUrls/routes.mjs +177 -0
  42. package/dist/esm-node/localisedUrls/types.mjs +1 -0
  43. package/dist/esm-node/rendererHead.mjs +14 -5
  44. package/dist/esm-node/request-policy/policyCore.mjs +1 -1
  45. package/dist/esm-node/request-policy/transport.mjs +2 -2
  46. package/dist/esm-node/routerState.mjs +81 -73
  47. package/dist/esm-node/routerStatePlugin.mjs +62 -11
  48. package/dist/esm-node/server-config/serverSource.mjs +1 -0
  49. package/dist/types/helmetCompat.d.ts +1 -1
  50. package/dist/types/localisedUrls/config.d.ts +9 -0
  51. package/dist/types/localisedUrls/index.d.ts +10 -0
  52. package/dist/types/localisedUrls/normalise.d.ts +10 -0
  53. package/dist/types/localisedUrls/pathname.d.ts +4 -0
  54. package/dist/types/localisedUrls/patterns.d.ts +11 -0
  55. package/dist/types/localisedUrls/redirect.d.ts +19 -0
  56. package/dist/types/localisedUrls/resolve.d.ts +7 -0
  57. package/dist/types/localisedUrls/routeIdentity.d.ts +22 -0
  58. package/dist/types/localisedUrls/routes.d.ts +3 -0
  59. package/dist/types/localisedUrls/types.d.ts +14 -0
  60. package/dist/types/rendererHead.d.ts +5 -3
  61. package/dist/types/request-policy/browser.d.ts +5 -1
  62. package/dist/types/request-policy/node.d.ts +5 -1
  63. package/dist/types/request-policy/types.d.ts +1 -1
  64. package/dist/types/routeHydration.d.ts +2 -2
  65. package/dist/types/routerProvider.d.ts +1 -3
  66. package/dist/types/routerState.d.ts +11 -6
  67. package/dist/types/routerStatePlugin.d.ts +12 -0
  68. package/dist/types/routerStateTypes.d.ts +47 -17
  69. package/dist/types/server-config/index.d.ts +1 -0
  70. package/dist/types/server-config/serverSource.d.ts +16 -0
  71. package/package.json +43 -45
  72. package/src/helmetCompat.ts +1 -1
  73. package/src/localisedUrls/config.ts +18 -0
  74. package/src/localisedUrls/index.ts +25 -0
  75. package/src/localisedUrls/normalise.ts +62 -0
  76. package/src/localisedUrls/pathname.ts +60 -0
  77. package/src/localisedUrls/patterns.ts +283 -0
  78. package/src/localisedUrls/redirect.ts +110 -0
  79. package/src/localisedUrls/resolve.ts +106 -0
  80. package/src/localisedUrls/routeIdentity.ts +83 -0
  81. package/src/localisedUrls/routes.ts +451 -0
  82. package/src/localisedUrls/types.ts +18 -0
  83. package/src/rendererHead.ts +25 -8
  84. package/src/request-policy/policyCore.ts +1 -1
  85. package/src/request-policy/transport.ts +2 -2
  86. package/src/request-policy/types.ts +1 -1
  87. package/src/routeHydration.ts +2 -2
  88. package/src/routerProvider.ts +1 -1
  89. package/src/routerState.ts +127 -115
  90. package/src/routerStatePlugin.ts +106 -10
  91. package/src/routerStateTypes.ts +47 -17
  92. package/src/server-config/index.ts +1 -0
  93. package/src/server-config/serverSource.ts +23 -0
  94. package/dist/cjs/buildIdentity.js +0 -73
  95. package/dist/esm/buildIdentity.mjs +0 -35
  96. package/dist/esm/reactContext.mjs +0 -7
  97. package/dist/esm-node/buildIdentity.mjs +0 -36
  98. package/dist/esm-node/reactContext.mjs +0 -8
  99. package/dist/types/buildIdentity.d.ts +0 -37
  100. package/dist/types/reactContext.d.ts +0 -6
  101. package/rslib.config.mts +0 -19
  102. package/rstest.config.mts +0 -38
  103. package/src/buildIdentity.ts +0 -61
  104. package/src/reactContext.ts +0 -16
  105. package/tests/buildIdentity.test.ts +0 -51
  106. package/tests/contextExtensions.test.ts +0 -27
  107. package/tests/headRuntime.test.tsx +0 -229
  108. package/tests/rendererHead.test.ts +0 -187
  109. package/tests/request-policy/browser-policy.test.ts +0 -108
  110. package/tests/request-policy/client-isolation.test.ts +0 -109
  111. package/tests/request-policy/node-policy.test.ts +0 -447
  112. package/tests/request-policy/policyCore.test.ts +0 -129
  113. package/tests/request-policy/requestContext.test.ts +0 -66
  114. package/tests/request-policy/requestFactory.outbound-contract.test.ts +0 -290
  115. package/tests/request-policy/traceparent.test.ts +0 -52
  116. package/tests/request-policy/transport-retry.test.ts +0 -367
  117. package/tests/routeHydration.test.ts +0 -180
  118. package/tests/routerPrefetchPolicy.test.ts +0 -102
  119. package/tests/routerProvider.test.ts +0 -29
  120. package/tests/routerState.test.ts +0 -78
  121. package/tests/routerStatePlugin.test.ts +0 -63
  122. package/tests/runtimeContextProjection.test.ts +0 -83
  123. package/tests/safeFailure.test.ts +0 -136
  124. package/tests/shellStream.test.ts +0 -122
  125. package/tests/workspaceEvents.test.ts +0 -221
  126. package/tsconfig.json +0 -10
@@ -1,122 +0,0 @@
1
- import {
2
- beginHeadRender,
3
- collectHeadRecord,
4
- completeHeadRender,
5
- createConservingWebShellStream,
6
- } from '../src';
7
-
8
- const MARKER = '<!--<?- SHELL_STREAM_END ?>-->';
9
- const encoder = new TextEncoder();
10
- const headMarker = (props: Record<string, string>) =>
11
- `<template data-modern-helmet="${props['data-modern-helmet']}"></template>`;
12
-
13
- const streamFromChunks = (chunks: Uint8Array[]) =>
14
- new ReadableStream<Uint8Array>({
15
- start(controller) {
16
- for (const chunk of chunks) {
17
- controller.enqueue(chunk);
18
- }
19
- controller.close();
20
- },
21
- });
22
-
23
- const splitAt = (value: Uint8Array, offsets: number[]) => {
24
- const chunks: Uint8Array[] = [];
25
- let cursor = 0;
26
- for (const offset of offsets) {
27
- chunks.push(value.slice(cursor, offset));
28
- cursor = offset;
29
- }
30
- chunks.push(value.slice(cursor));
31
- return chunks;
32
- };
33
-
34
- describe('conserving web shell stream', () => {
35
- it('preserves a large multichunk shell and tail across UTF-8 splits', async () => {
36
- const shell = `α${'s'.repeat(9 * 1024)}`;
37
- const tail = `尾${'t'.repeat(9 * 1024)}🌐done`;
38
- const html = `${shell}${MARKER}${tail}`;
39
- const bytes = encoder.encode(html);
40
- const markerStart = encoder.encode(shell).length;
41
- const markerEnd = markerStart + encoder.encode(MARKER).length;
42
- const chunks = splitAt(bytes, [
43
- 1,
44
- 8192,
45
- markerStart + 7,
46
- markerEnd - 3,
47
- markerEnd + 1,
48
- bytes.length - 2,
49
- ]);
50
-
51
- const output = await new Response(
52
- createConservingWebShellStream(streamFromChunks(chunks), {}, MARKER),
53
- ).text();
54
-
55
- expect(output).toBe(html);
56
- });
57
-
58
- it('reports once and errors when the source closes without a marker', async () => {
59
- const onMissingMarker = rstest.fn();
60
- const context = {};
61
- let published: string[] = [];
62
- const publish = (records: string[]) => {
63
- published = records;
64
- };
65
- beginHeadRender(context);
66
- const previous = collectHeadRecord(context, () => 'previous', publish)!;
67
- completeHeadRender(context, headMarker(previous));
68
- beginHeadRender(context);
69
- collectHeadRecord(context, () => 'incomplete', publish);
70
- const bytes = encoder.encode(`α${'x'.repeat(9 * 1024)}尾`);
71
- const stream = createConservingWebShellStream(
72
- streamFromChunks(splitAt(bytes, [1, 8192, bytes.length - 1])),
73
- context,
74
- MARKER,
75
- onMissingMarker,
76
- );
77
-
78
- await expect(new Response(stream).text()).rejects.toThrow(
79
- 'React SSR stream ended before the shell marker',
80
- );
81
- expect(onMissingMarker).toHaveBeenCalledTimes(1);
82
- expect(onMissingMarker).toHaveBeenCalledWith(expect.any(Error));
83
- expect(published).toEqual(['previous']);
84
- });
85
- });
86
-
87
- test('cancellation waits for the owned source cancellation to finish once', async () => {
88
- let release!: () => void;
89
- let started!: () => void;
90
- const cancelling = new Promise<void>(resolve => {
91
- started = resolve;
92
- });
93
- const gate = new Promise<void>(resolve => {
94
- release = resolve;
95
- });
96
- const cancel = rstest.fn(async () => {
97
- started();
98
- await gate;
99
- });
100
- const source = new ReadableStream<Uint8Array>({ cancel });
101
- const reader = createConservingWebShellStream(source, {}, MARKER).getReader();
102
- let settled = false;
103
- const cancellation = reader.cancel('stop').then(() => {
104
- settled = true;
105
- });
106
- await cancelling;
107
- expect(settled).toBe(false);
108
- release();
109
- await cancellation;
110
- expect(cancel).toHaveBeenCalledExactlyOnceWith('stop');
111
- });
112
-
113
- test('cancellation propagates an owned source cleanup failure', async () => {
114
- const source = new ReadableStream<Uint8Array>({
115
- cancel() {
116
- throw new Error('cleanup failed');
117
- },
118
- });
119
- await expect(
120
- createConservingWebShellStream(source, {}, MARKER).cancel('stop'),
121
- ).rejects.toThrow('cleanup failed');
122
- });
@@ -1,221 +0,0 @@
1
- import * as contracts from '../src/workspaceEvents';
2
-
3
- const cases = [
4
- {
5
- name: 'ultramodern:navigate',
6
- valid: { to: '/dashboard', replace: false, state: { from: 'shell' } },
7
- invalid: [
8
- {},
9
- { to: '' },
10
- { to: ' ' },
11
- { to: 42 },
12
- { to: '/', replace: 'false' },
13
- { to: '/', state: [] },
14
- { to: '/', state: null },
15
- ],
16
- validate: contracts.isUltramodernNavigatePayload,
17
- dispatch: contracts.dispatchUltramodernNavigate,
18
- on: contracts.onUltramodernNavigate,
19
- },
20
- {
21
- name: 'ultramodern:route-settled',
22
- valid: { pathname: '/cs', locale: 'cs', title: 'Home' },
23
- invalid: [
24
- {},
25
- { pathname: ' ' },
26
- { pathname: '/', locale: 'de' },
27
- { pathname: '/', locale: null },
28
- { pathname: '/', title: '' },
29
- { pathname: '/', title: 42 },
30
- ],
31
- validate: contracts.isUltramodernRouteSettledPayload,
32
- dispatch: contracts.dispatchUltramodernRouteSettled,
33
- on: contracts.onUltramodernRouteSettled,
34
- },
35
- {
36
- name: 'ultramodern:remote-ready',
37
- valid: { appId: 'catalog', build: 'a', surface: 'ui', version: '1' },
38
- invalid: [
39
- {},
40
- { appId: 42 },
41
- { appId: ' ' },
42
- { appId: 'catalog', build: '' },
43
- { appId: 'catalog', surface: null },
44
- { appId: 'catalog', version: 1 },
45
- ],
46
- validate: contracts.isUltramodernRemoteReadyPayload,
47
- dispatch: contracts.dispatchUltramodernRemoteReady,
48
- on: contracts.onUltramodernRemoteReady,
49
- },
50
- {
51
- name: 'ultramodern:performance-signal',
52
- valid: {
53
- signalId: 'bfcache',
54
- status: 'pass',
55
- durationMs: 0,
56
- detail: { source: 'browser' },
57
- },
58
- invalid: [
59
- {},
60
- { signalId: 'unknown', status: 'pass' },
61
- { signalId: 'bfcache', status: 'unknown' },
62
- ...[-1, Number.NaN, Number.POSITIVE_INFINITY, '1', null].map(
63
- durationMs => ({ signalId: 'bfcache', status: 'pass', durationMs }),
64
- ),
65
- { signalId: 'bfcache', status: 'pass', detail: [] },
66
- { signalId: 'bfcache', status: 'pass', detail: null },
67
- ],
68
- validate: contracts.isUltramodernPerformanceSignalPayload,
69
- dispatch: contracts.dispatchUltramodernPerformanceSignal,
70
- on: contracts.onUltramodernPerformanceSignal,
71
- },
72
- ] as const;
73
-
74
- describe('workspace event contracts', () => {
75
- for (const { name, valid, invalid, validate, dispatch, on } of cases) {
76
- it(`${name} preserves schema validation and error data`, () => {
77
- expect(validate(valid)).toBe(true);
78
- expect(
79
- contracts.assertUltramodernWorkspaceEventPayload(name, valid),
80
- ).toBe(valid);
81
- for (const payload of [null, undefined, [], false, 'text', ...invalid]) {
82
- expect(validate(payload)).toBe(false);
83
- expect(
84
- contracts.isUltramodernWorkspaceEventPayload(name, payload),
85
- ).toBe(false);
86
- let error: unknown;
87
- try {
88
- contracts.assertUltramodernWorkspaceEventPayload(name, payload);
89
- } catch (caught) {
90
- error = caught;
91
- }
92
- expect(error).toBeInstanceOf(
93
- contracts.UltramodernWorkspaceEventValidationError,
94
- );
95
- expect(error).toMatchObject({
96
- name: 'UltramodernWorkspaceEventValidationError',
97
- eventName: name,
98
- payload,
99
- });
100
- }
101
- });
102
-
103
- it(`${name} uses native dispatch, preserves payload identity and cleans up`, () => {
104
- const target = new EventTarget();
105
- const received: unknown[] = [];
106
- const handler = (payload: unknown, event: CustomEvent<unknown>) => {
107
- expect(event).toBeInstanceOf(CustomEvent);
108
- expect(event.type).toBe(name);
109
- expect(event.bubbles).toBe(true);
110
- expect(event.composed).toBe(true);
111
- expect(event.cancelable).toBe(false);
112
- expect(event.target).toBe(target);
113
- expect(event.detail).toBe(payload);
114
- received.push(payload);
115
- };
116
- const unsubscribe = on(target, handler);
117
- // Each table row correlates the convenience dispatch with its own schema.
118
- const dispatchPayload = dispatch as (
119
- target: EventTarget,
120
- payload: typeof valid,
121
- ) => boolean;
122
- expect(dispatchPayload(target, valid)).toBe(true);
123
- expect(received).toEqual([valid]);
124
- expect(received[0]).toBe(valid);
125
- unsubscribe();
126
- unsubscribe();
127
- dispatchPayload(target, valid);
128
- expect(received).toEqual([valid]);
129
- });
130
- }
131
-
132
- it('accepts consumer-owned fields, explicit undefined optionals and every signal id', () => {
133
- const payload = {
134
- to: '/raw',
135
- replace: undefined,
136
- state: undefined,
137
- extension: 'consumer-owned',
138
- };
139
- expect(
140
- contracts.assertUltramodernWorkspaceEventPayload(
141
- 'ultramodern:navigate',
142
- payload,
143
- ),
144
- ).toBe(payload);
145
- expect(
146
- contracts.isUltramodernRouteSettledPayload({
147
- pathname: '/',
148
- locale: undefined,
149
- title: undefined,
150
- }),
151
- ).toBe(true);
152
- for (const signalId of [
153
- 'bfcache',
154
- 'core-web-vitals-rum',
155
- 'duplicate-prefetch-warmup',
156
- 'cache-policy-sanity',
157
- 'save-data-behavior',
158
- 'cloudflare-ssr-cache-hints',
159
- ]) {
160
- expect(
161
- contracts.isUltramodernPerformanceSignalPayload({
162
- signalId,
163
- status: 'warn',
164
- }),
165
- ).toBe(true);
166
- }
167
- });
168
-
169
- it('accepts detail-carrying events from another realm', () => {
170
- const target = new EventTarget();
171
- const add = rstest.spyOn(target, 'addEventListener');
172
- const handler = rstest.fn();
173
- const unsubscribe = contracts.onUltramodernNavigate(target, handler);
174
- const listener = add.mock.calls[0]?.[1] as EventListener;
175
- // A CustomEvent from an iframe/foreign realm fails `instanceof`; the
176
- // listener must duck-type on `detail` instead of rejecting it.
177
- const payload = { to: '/foreign' };
178
- listener({ type: 'ultramodern:navigate', detail: payload } as never);
179
- expect(handler).toHaveBeenCalledTimes(1);
180
- expect(handler.mock.calls[0]?.[0]).toBe(payload);
181
- unsubscribe();
182
- add.mockRestore();
183
- });
184
-
185
- it('rejects invalid dispatch before notifying listeners', () => {
186
- const target = new EventTarget();
187
- const handler = rstest.fn();
188
- const unsubscribe = contracts.onUltramodernNavigate(target, handler);
189
- expect(() =>
190
- contracts.dispatchUltramodernNavigate(target, { to: '' }),
191
- ).toThrow();
192
- expect(handler).not.toHaveBeenCalled();
193
- unsubscribe();
194
- });
195
-
196
- it('validates incoming event details and removes only its own listener', () => {
197
- const target = new EventTarget();
198
- const add = rstest.spyOn(target, 'addEventListener');
199
- const remove = rstest.spyOn(target, 'removeEventListener');
200
- const handler = rstest.fn();
201
- const first = contracts.onUltramodernNavigate(target, handler);
202
- const listener = add.mock.calls[0]?.[1] as EventListener;
203
- for (const event of [
204
- new Event('ultramodern:navigate'),
205
- new CustomEvent('ultramodern:navigate', { detail: { to: '' } }),
206
- ]) {
207
- // Native dispatch reports listener errors asynchronously; invoke the
208
- // registered listener directly to inspect the precise validation failure.
209
- expect(() => listener(event)).toThrow();
210
- }
211
- expect(handler).not.toHaveBeenCalled();
212
- const second = contracts.onUltramodernNavigate(target, handler);
213
- first();
214
- expect(remove).toHaveBeenCalledWith('ultramodern:navigate', listener);
215
- contracts.dispatchUltramodernNavigate(target, { to: '/still-subscribed' });
216
- expect(handler).toHaveBeenCalledTimes(1);
217
- second();
218
- add.mockRestore();
219
- remove.mockRestore();
220
- });
221
- });
package/tsconfig.json DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "extends": "@modern-js/tsconfig/base",
3
- "compilerOptions": {
4
- "declaration": false,
5
- "isolatedModules": true,
6
- "rootDir": "./src",
7
- "types": ["node"]
8
- },
9
- "include": ["src"]
10
- }