@reckona/mreact-compat 0.0.66 → 0.0.67

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.
package/src/fiber.ts ADDED
@@ -0,0 +1,148 @@
1
+ import { NoFlags, type Flags } from "./fiber-flags.js";
2
+ import { NoLanes, type Lane, type Lanes } from "./fiber-lanes.js";
3
+
4
+ export type FiberTag =
5
+ | "host-root"
6
+ | "host-component"
7
+ | "host-text"
8
+ | "fragment"
9
+ | "function-component"
10
+ | "forward-ref"
11
+ | "class-component"
12
+ | "context-provider"
13
+ | "context-consumer"
14
+ | "portal"
15
+ | "memo"
16
+ | "lazy"
17
+ | "profiler"
18
+ | "strict-mode"
19
+ | "suspense"
20
+ | "suspense-list"
21
+ | "error-boundary";
22
+
23
+ export interface Fiber {
24
+ tag: FiberTag;
25
+ type: unknown;
26
+ key: string | undefined;
27
+ pendingProps: unknown;
28
+ memoizedProps: unknown;
29
+ memoizedState: unknown;
30
+ return: Fiber | undefined;
31
+ child: Fiber | undefined;
32
+ sibling: Fiber | undefined;
33
+ alternate: Fiber | undefined;
34
+ stateNode: unknown;
35
+ flags: Flags;
36
+ subtreeFlags: Flags;
37
+ deletions: Fiber[] | undefined;
38
+ lanes: Lanes;
39
+ childLanes: Lanes;
40
+ hydrateExisting: boolean;
41
+ }
42
+
43
+ export interface FiberRoot {
44
+ container: Element;
45
+ current: Fiber;
46
+ finishedWork: Fiber | undefined;
47
+ pendingLanes: Lanes;
48
+ suspendedLanes: Lanes;
49
+ pingedLanes: Lanes;
50
+ expiredLanes: Lanes;
51
+ entangledLanes: Lanes;
52
+ callbackNode: unknown;
53
+ callbackPriority: Lane;
54
+ workInProgress: Fiber | undefined;
55
+ workInProgressRootRenderLanes: Lanes;
56
+ workInProgressElement: unknown;
57
+ hydrationState: FiberHydrationState | undefined;
58
+ }
59
+
60
+ export interface FiberHydrationState {
61
+ parent: ParentNode;
62
+ nextHydratableNode: Node | null;
63
+ before: ChildNode | null;
64
+ after: ChildNode | null;
65
+ resumeId?: string;
66
+ }
67
+
68
+ export function createFiber(
69
+ tag: FiberTag,
70
+ pendingProps: unknown = undefined,
71
+ key?: string,
72
+ ): Fiber {
73
+ return {
74
+ tag,
75
+ type: undefined,
76
+ key,
77
+ pendingProps,
78
+ memoizedProps: undefined,
79
+ memoizedState: undefined,
80
+ return: undefined,
81
+ child: undefined,
82
+ sibling: undefined,
83
+ alternate: undefined,
84
+ stateNode: undefined,
85
+ flags: NoFlags,
86
+ subtreeFlags: NoFlags,
87
+ deletions: undefined,
88
+ lanes: NoLanes,
89
+ childLanes: NoLanes,
90
+ hydrateExisting: false,
91
+ };
92
+ }
93
+
94
+ export function createHostRootFiber(): Fiber {
95
+ return createFiber("host-root", undefined);
96
+ }
97
+
98
+ export function createFiberRoot(container: Element): FiberRoot {
99
+ const current = createHostRootFiber();
100
+ const root: FiberRoot = {
101
+ container,
102
+ current,
103
+ finishedWork: undefined,
104
+ pendingLanes: NoLanes,
105
+ suspendedLanes: NoLanes,
106
+ pingedLanes: NoLanes,
107
+ expiredLanes: NoLanes,
108
+ entangledLanes: NoLanes,
109
+ callbackNode: undefined,
110
+ callbackPriority: NoLanes,
111
+ workInProgress: undefined,
112
+ workInProgressRootRenderLanes: NoLanes,
113
+ workInProgressElement: undefined,
114
+ hydrationState: undefined,
115
+ };
116
+ current.stateNode = root;
117
+ return root;
118
+ }
119
+
120
+ export function createWorkInProgress(
121
+ current: Fiber,
122
+ pendingProps: unknown,
123
+ ): Fiber {
124
+ let workInProgress = current.alternate;
125
+
126
+ if (workInProgress === undefined) {
127
+ workInProgress = createFiber(current.tag, pendingProps, current.key);
128
+ workInProgress.type = current.type;
129
+ workInProgress.stateNode = current.stateNode;
130
+ workInProgress.alternate = current;
131
+ current.alternate = workInProgress;
132
+ } else {
133
+ workInProgress.pendingProps = pendingProps;
134
+ workInProgress.flags = NoFlags;
135
+ workInProgress.subtreeFlags = NoFlags;
136
+ workInProgress.deletions = undefined;
137
+ }
138
+
139
+ workInProgress.child = current.child;
140
+ workInProgress.sibling = current.sibling;
141
+ workInProgress.return = current.return;
142
+ workInProgress.memoizedProps = current.memoizedProps;
143
+ workInProgress.memoizedState = current.memoizedState;
144
+ workInProgress.lanes = current.lanes;
145
+ workInProgress.childLanes = current.childLanes;
146
+ workInProgress.hydrateExisting = false;
147
+ return workInProgress;
148
+ }
@@ -0,0 +1,205 @@
1
+ import type { ElementType } from "./element.js";
2
+ import {
3
+ createFlightServerReferenceStub,
4
+ decodeFlightElementModel,
5
+ } from "./flight-element-builder.js";
6
+ import type {
7
+ FlightClientReference,
8
+ FlightModel,
9
+ FlightResponse,
10
+ FlightServerReference,
11
+ FlightTypedArrayName,
12
+ } from "./flight-types.js";
13
+
14
+ export interface DecodeFlightOptions {
15
+ loadClientReference(reference: FlightClientReference): ElementType<Record<string, unknown>>;
16
+ callServerReference?(
17
+ reference: FlightServerReference,
18
+ args: unknown[],
19
+ ): unknown | Promise<unknown>;
20
+ }
21
+
22
+ // Issue 079: cap recursion depth so a deeply-nested Flight payload
23
+ // cannot stack-overflow the client decoder.
24
+ const MAX_FLIGHT_DECODE_DEPTH = 256;
25
+
26
+ class FlightDecodeError extends Error {
27
+ constructor(message: string) {
28
+ super(message);
29
+ this.name = "FlightDecodeError";
30
+ }
31
+ }
32
+
33
+ export function decodeFlightModel(
34
+ model: FlightModel,
35
+ response: FlightResponse,
36
+ options: DecodeFlightOptions,
37
+ depth = 0,
38
+ ): unknown {
39
+ if (depth > MAX_FLIGHT_DECODE_DEPTH) {
40
+ throw new FlightDecodeError(
41
+ `MR_FLIGHT_TOO_DEEP: nested deeper than ${MAX_FLIGHT_DECODE_DEPTH} levels`,
42
+ );
43
+ }
44
+ if (
45
+ model === null ||
46
+ typeof model === "string" ||
47
+ typeof model === "number" ||
48
+ typeof model === "boolean"
49
+ ) {
50
+ return model;
51
+ }
52
+
53
+ if (Array.isArray(model)) {
54
+ return model.map((item) => decodeFlightModel(item, response, options, depth + 1));
55
+ }
56
+
57
+ if (model.kind === "undefined") {
58
+ return undefined;
59
+ }
60
+
61
+ if (model.kind === "date") {
62
+ return new Date(model.value);
63
+ }
64
+
65
+ if (model.kind === "bigint") {
66
+ return BigInt(model.value);
67
+ }
68
+
69
+ if (model.kind === "number") {
70
+ switch (model.value) {
71
+ case "Infinity":
72
+ return Infinity;
73
+ case "-Infinity":
74
+ return -Infinity;
75
+ case "-0":
76
+ return -0;
77
+ case "NaN":
78
+ return Number.NaN;
79
+ }
80
+ }
81
+
82
+ if (model.kind === "symbol") {
83
+ return Symbol.for(model.name);
84
+ }
85
+
86
+ if (model.kind === "map") {
87
+ return new Map(
88
+ model.entries.map(([key, value]) => [
89
+ decodeFlightModel(key, response, options, depth + 1),
90
+ decodeFlightModel(value, response, options, depth + 1),
91
+ ]),
92
+ );
93
+ }
94
+
95
+ if (model.kind === "set") {
96
+ return new Set(
97
+ model.values.map((value) => decodeFlightModel(value, response, options, depth + 1)),
98
+ );
99
+ }
100
+
101
+ if (model.kind === "form-data") {
102
+ const formData = new FormData();
103
+
104
+ for (const [name, value] of model.entries) {
105
+ const decoded = decodeFlightModel(value, response, options, depth + 1);
106
+ formData.append(name, decoded instanceof Blob ? decoded : String(decoded ?? ""));
107
+ }
108
+
109
+ return formData;
110
+ }
111
+
112
+ if (model.kind === "iterable") {
113
+ return model.values.map((value) => decodeFlightModel(value, response, options, depth + 1));
114
+ }
115
+
116
+ if (model.kind === "array-buffer") {
117
+ return createArrayBuffer(model.bytes);
118
+ }
119
+
120
+ if (model.kind === "typed-array") {
121
+ return createTypedArray(model.arrayType, model.bytes);
122
+ }
123
+
124
+ if (model.kind === "data-view") {
125
+ return new DataView(createArrayBuffer(model.bytes));
126
+ }
127
+
128
+ if (model.kind === "error") {
129
+ const error = new Error(model.message);
130
+ error.name = model.name;
131
+ if (model.digest !== undefined) {
132
+ (error as Error & { digest?: string }).digest = model.digest;
133
+ }
134
+ throw error;
135
+ }
136
+
137
+ if (model.kind === "promise") {
138
+ throw new Error(`React Flight chunk ${model.id} is still pending.`);
139
+ }
140
+
141
+ if (model.kind === "element") {
142
+ return decodeFlightElementModel(
143
+ model,
144
+ response,
145
+ options,
146
+ (value, childDepth = 0) => decodeFlightModel(value, response, options, childDepth),
147
+ depth,
148
+ assertFlightDecodeDepth,
149
+ );
150
+ }
151
+
152
+ if (model.kind === "server-reference") {
153
+ return createFlightServerReferenceStub(
154
+ model.id,
155
+ response,
156
+ options,
157
+ (value, childDepth = 0) => decodeFlightModel(value, response, options, childDepth),
158
+ );
159
+ }
160
+
161
+ throw new Error(`Unexpected Flight model kind: ${model.kind}`);
162
+ }
163
+
164
+ function assertFlightDecodeDepth(depth: number): void {
165
+ if (depth > MAX_FLIGHT_DECODE_DEPTH) {
166
+ throw new FlightDecodeError(
167
+ `MR_FLIGHT_TOO_DEEP: nested deeper than ${MAX_FLIGHT_DECODE_DEPTH} levels`,
168
+ );
169
+ }
170
+ }
171
+
172
+ function createArrayBuffer(bytes: number[]): ArrayBuffer {
173
+ const array = Uint8Array.from(bytes);
174
+
175
+ return array.buffer.slice(array.byteOffset, array.byteOffset + array.byteLength);
176
+ }
177
+
178
+ function createTypedArray(arrayType: FlightTypedArrayName, bytes: number[]): unknown {
179
+ const buffer = createArrayBuffer(bytes);
180
+
181
+ switch (arrayType) {
182
+ case "Int8Array":
183
+ return new Int8Array(buffer);
184
+ case "Uint8Array":
185
+ return new Uint8Array(buffer);
186
+ case "Uint8ClampedArray":
187
+ return new Uint8ClampedArray(buffer);
188
+ case "Int16Array":
189
+ return new Int16Array(buffer);
190
+ case "Uint16Array":
191
+ return new Uint16Array(buffer);
192
+ case "Int32Array":
193
+ return new Int32Array(buffer);
194
+ case "Uint32Array":
195
+ return new Uint32Array(buffer);
196
+ case "Float32Array":
197
+ return new Float32Array(buffer);
198
+ case "Float64Array":
199
+ return new Float64Array(buffer);
200
+ case "BigInt64Array":
201
+ return new BigInt64Array(buffer);
202
+ case "BigUint64Array":
203
+ return new BigUint64Array(buffer);
204
+ }
205
+ }
@@ -0,0 +1,110 @@
1
+ import { createElement, Fragment } from "./element.js";
2
+ import type { ElementType } from "./element.js";
3
+ import type { DecodeFlightOptions } from "./flight-decoder.js";
4
+ import type {
5
+ FlightClientReference,
6
+ FlightElementModel,
7
+ FlightModel,
8
+ FlightResponse,
9
+ FlightServerReference,
10
+ FlightServerReferenceModel,
11
+ } from "./flight-types.js";
12
+
13
+ export type FlightModelDecoder = (model: FlightModel, depth?: number) => unknown;
14
+
15
+ export function decodeFlightElementModel(
16
+ model: FlightElementModel,
17
+ response: FlightResponse,
18
+ options: DecodeFlightOptions,
19
+ decodeModel: FlightModelDecoder,
20
+ depth = 0,
21
+ assertDepth?: (depth: number) => void,
22
+ ): unknown {
23
+ const type = decodeElementType(model.type, response, options);
24
+ const propsDepth = depth + 1;
25
+ assertDepth?.(propsDepth);
26
+ const props = decodeProps(model.props, response, options, decodeModel, propsDepth);
27
+
28
+ return createElement(type, { ...props, key: model.key });
29
+ }
30
+
31
+ export function createFlightServerReferenceStub(
32
+ id: number,
33
+ response: FlightResponse,
34
+ options: DecodeFlightOptions,
35
+ decodeModel: FlightModelDecoder,
36
+ ): (...args: unknown[]) => unknown {
37
+ const reference = getServerReference(id, response);
38
+
39
+ return (...args: unknown[]) => {
40
+ if (options.callServerReference === undefined) {
41
+ throw new Error(`No server reference caller configured for ${reference.moduleId}.`);
42
+ }
43
+
44
+ const boundArgs = reference.bound?.map((value) => decodeModel(value)) ?? [];
45
+
46
+ return options.callServerReference(reference, [...boundArgs, ...args]);
47
+ };
48
+ }
49
+
50
+ function decodeElementType(
51
+ type: FlightElementModel["type"],
52
+ response: FlightResponse,
53
+ options: DecodeFlightOptions,
54
+ ): ElementType<Record<string, unknown>> {
55
+ if (typeof type === "string") {
56
+ return type;
57
+ }
58
+
59
+ if (type.kind === "fragment") {
60
+ return Fragment as ElementType<Record<string, unknown>>;
61
+ }
62
+
63
+ return options.loadClientReference(getClientReference(type.id, response));
64
+ }
65
+
66
+ function decodeProps(
67
+ props: Record<string, FlightModel>,
68
+ response: FlightResponse,
69
+ options: DecodeFlightOptions,
70
+ decodeModel: FlightModelDecoder,
71
+ depth: number,
72
+ ): Record<string, unknown> {
73
+ return Object.fromEntries(
74
+ Object.entries(props).map(([key, value]) => [
75
+ key,
76
+ valueIsServerReference(value)
77
+ ? createFlightServerReferenceStub(value.id, response, options, decodeModel)
78
+ : decodeModel(value, depth + 1),
79
+ ]),
80
+ );
81
+ }
82
+
83
+ function getClientReference(id: number, response: FlightResponse): FlightClientReference {
84
+ const reference = response.clientReferences.find((entry) => entry.id === id);
85
+
86
+ if (reference === undefined) {
87
+ throw new Error(`Unknown Flight client reference: ${id}`);
88
+ }
89
+
90
+ return reference;
91
+ }
92
+
93
+ function getServerReference(id: number, response: FlightResponse): FlightServerReference {
94
+ const reference = response.serverReferences.find((entry) => entry.id === id);
95
+
96
+ if (reference === undefined) {
97
+ throw new Error(`Unknown Flight server reference: ${id}`);
98
+ }
99
+
100
+ return reference;
101
+ }
102
+
103
+ function valueIsServerReference(value: FlightModel): value is FlightServerReferenceModel {
104
+ return (
105
+ typeof value === "object" &&
106
+ value !== null &&
107
+ !Array.isArray(value) &&
108
+ value.kind === "server-reference"
109
+ );
110
+ }