dinou
Version:
Dinou is a modern React 19 framework with React Server Components, Server Functions, and streaming SSR.
1,370 lines (1,343 loc) • 93.2 kB
JavaScript
/**
* @license React
* react-server-dom-esm-client.browser.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import * as ReactDOM from 'react-dom';
function createStringDecoder() {
return new TextDecoder();
}
const decoderOptions = {
stream: true
};
function readPartialStringChunk(decoder, buffer) {
return decoder.decode(buffer, decoderOptions);
}
function readFinalStringChunk(decoder, buffer) {
return decoder.decode(buffer);
}
// Module root path
function resolveClientReference(bundlerConfig, metadata) {
const baseURL = bundlerConfig;
return {
specifier: baseURL + metadata[0],
name: metadata[1]
};
}
function resolveServerReference(config, id) {
const baseURL = config;
const idx = id.lastIndexOf('#');
const exportName = id.slice(idx + 1);
const fullURL = id.slice(0, idx);
if (!fullURL.startsWith(baseURL)) {
throw new Error('Attempted to load a Server Reference outside the hosted root.');
}
return {
specifier: fullURL,
name: exportName
};
}
const asyncModuleCache = new Map();
function preloadModule(metadata) {
const existingPromise = asyncModuleCache.get(metadata.specifier);
if (existingPromise) {
if (existingPromise.status === 'fulfilled') {
return null;
}
return existingPromise;
} else {
// $FlowFixMe[unsupported-syntax]
const modulePromise = import(metadata.specifier);
modulePromise.then(value => {
const fulfilledThenable = modulePromise;
fulfilledThenable.status = 'fulfilled';
fulfilledThenable.value = value;
}, reason => {
const rejectedThenable = modulePromise;
rejectedThenable.status = 'rejected';
rejectedThenable.reason = reason;
});
asyncModuleCache.set(metadata.specifier, modulePromise);
return modulePromise;
}
}
function requireModule(metadata) {
let moduleExports;
// We assume that preloadModule has been called before, which
// should have added something to the module cache.
const promise = asyncModuleCache.get(metadata.specifier);
if (promise.status === 'fulfilled') {
moduleExports = promise.value;
} else {
throw promise.reason;
}
return moduleExports[metadata.name];
}
const ReactDOMSharedInternals = ReactDOM.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
// This client file is in the shared folder because it applies to both SSR and browser contexts.
// It is the configuration of the FlightClient behavior which can run in either environment.
function dispatchHint(code, model) {
const dispatcher = ReactDOMSharedInternals.d; /* ReactDOMCurrentDispatcher */
switch (code) {
case 'D':
{
const refined = refineModel(code, model);
const href = refined;
dispatcher.D( /* prefetchDNS */href);
return;
}
case 'C':
{
const refined = refineModel(code, model);
if (typeof refined === 'string') {
const href = refined;
dispatcher.C( /* preconnect */href);
} else {
const href = refined[0];
const crossOrigin = refined[1];
dispatcher.C( /* preconnect */href, crossOrigin);
}
return;
}
case 'L':
{
const refined = refineModel(code, model);
const href = refined[0];
const as = refined[1];
if (refined.length === 3) {
const options = refined[2];
dispatcher.L( /* preload */href, as, options);
} else {
dispatcher.L( /* preload */href, as);
}
return;
}
case 'm':
{
const refined = refineModel(code, model);
if (typeof refined === 'string') {
const href = refined;
dispatcher.m( /* preloadModule */href);
} else {
const href = refined[0];
const options = refined[1];
dispatcher.m( /* preloadModule */href, options);
}
return;
}
case 'X':
{
const refined = refineModel(code, model);
if (typeof refined === 'string') {
const href = refined;
dispatcher.X( /* preinitScript */href);
} else {
const href = refined[0];
const options = refined[1];
dispatcher.X( /* preinitScript */href, options);
}
return;
}
case 'S':
{
const refined = refineModel(code, model);
if (typeof refined === 'string') {
const href = refined;
dispatcher.S( /* preinitStyle */href);
} else {
const href = refined[0];
const precedence = refined[1] === 0 ? undefined : refined[1];
const options = refined.length === 3 ? refined[2] : undefined;
dispatcher.S( /* preinitStyle */href, precedence, options);
}
return;
}
case 'M':
{
const refined = refineModel(code, model);
if (typeof refined === 'string') {
const href = refined;
dispatcher.M( /* preinitModuleScript */href);
} else {
const href = refined[0];
const options = refined[1];
dispatcher.M( /* preinitModuleScript */href, options);
}
return;
}
}
}
// Flow is having trouble refining the HintModels so we help it a bit.
// This should be compiled out in the production build.
function refineModel(code, model) {
return model;
}
const REACT_ELEMENT_TYPE = Symbol.for('react.transitional.element') ;
const REACT_LAZY_TYPE = Symbol.for('react.lazy');
const REACT_POSTPONE_TYPE = Symbol.for('react.postpone');
const MAYBE_ITERATOR_SYMBOL = Symbol.iterator;
const FAUX_ITERATOR_SYMBOL = '@@iterator';
function getIteratorFn(maybeIterable) {
if (maybeIterable === null || typeof maybeIterable !== 'object') {
return null;
}
const maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL];
if (typeof maybeIterator === 'function') {
return maybeIterator;
}
return null;
}
const ASYNC_ITERATOR = Symbol.asyncIterator;
const isArrayImpl = Array.isArray;
function isArray(a) {
return isArrayImpl(a);
}
const getPrototypeOf = Object.getPrototypeOf;
function createTemporaryReferenceSet() {
return new Map();
}
function writeTemporaryReference(set, reference, object) {
set.set(reference, object);
}
function readTemporaryReference(set, reference) {
return set.get(reference);
}
const ObjectPrototype = Object.prototype;
const knownServerReferences = new WeakMap();
// Serializable values
// Thenable<ReactServerValue>
function serializeByValueID(id) {
return '$' + id.toString(16);
}
function serializePromiseID(id) {
return '$@' + id.toString(16);
}
function serializeServerReferenceID(id) {
return '$F' + id.toString(16);
}
function serializeTemporaryReferenceMarker() {
return '$T';
}
function serializeFormDataReference(id) {
// Why K? F is "Function". D is "Date". What else?
return '$K' + id.toString(16);
}
function serializeNumber(number) {
if (Number.isFinite(number)) {
if (number === 0 && 1 / number === -Infinity) {
return '$-0';
} else {
return number;
}
} else {
if (number === Infinity) {
return '$Infinity';
} else if (number === -Infinity) {
return '$-Infinity';
} else {
return '$NaN';
}
}
}
function serializeUndefined() {
return '$undefined';
}
function serializeDateFromDateJSON(dateJSON) {
// JSON.stringify automatically calls Date.prototype.toJSON which calls toISOString.
// We need only tack on a $D prefix.
return '$D' + dateJSON;
}
function serializeBigInt(n) {
return '$n' + n.toString(10);
}
function serializeMapID(id) {
return '$Q' + id.toString(16);
}
function serializeSetID(id) {
return '$W' + id.toString(16);
}
function serializeBlobID(id) {
return '$B' + id.toString(16);
}
function serializeIteratorID(id) {
return '$i' + id.toString(16);
}
function escapeStringValue(value) {
if (value[0] === '$') {
// We need to escape $ prefixed strings since we use those to encode
// references to IDs and as special symbol values.
return '$' + value;
} else {
return value;
}
}
function processReply(root, formFieldPrefix, temporaryReferences, resolve, reject) {
let nextPartId = 1;
let pendingParts = 0;
let formData = null;
const writtenObjects = new WeakMap();
let modelRoot = root;
function serializeTypedArray(tag, typedArray) {
const blob = new Blob([
// We should be able to pass the buffer straight through but Node < 18 treat
// multi-byte array blobs differently so we first convert it to single-byte.
new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength)]);
const blobId = nextPartId++;
if (formData === null) {
formData = new FormData();
}
formData.append(formFieldPrefix + blobId, blob);
return '$' + tag + blobId.toString(16);
}
function serializeBinaryReader(reader) {
if (formData === null) {
// Upgrade to use FormData to allow us to stream this value.
formData = new FormData();
}
const data = formData;
pendingParts++;
const streamId = nextPartId++;
const buffer = [];
function progress(entry) {
if (entry.done) {
const blobId = nextPartId++;
data.append(formFieldPrefix + blobId, new Blob(buffer));
data.append(formFieldPrefix + streamId, '"$o' + blobId.toString(16) + '"');
data.append(formFieldPrefix + streamId, 'C'); // Close signal
pendingParts--;
if (pendingParts === 0) {
resolve(data);
}
} else {
buffer.push(entry.value);
reader.read(new Uint8Array(1024)).then(progress, reject);
}
}
reader.read(new Uint8Array(1024)).then(progress, reject);
return '$r' + streamId.toString(16);
}
function serializeReader(reader) {
if (formData === null) {
// Upgrade to use FormData to allow us to stream this value.
formData = new FormData();
}
const data = formData;
pendingParts++;
const streamId = nextPartId++;
function progress(entry) {
if (entry.done) {
data.append(formFieldPrefix + streamId, 'C'); // Close signal
pendingParts--;
if (pendingParts === 0) {
resolve(data);
}
} else {
try {
// $FlowFixMe[incompatible-type]: While plain JSON can return undefined we never do here.
const partJSON = JSON.stringify(entry.value, resolveToJSON);
data.append(formFieldPrefix + streamId, partJSON);
reader.read().then(progress, reject);
} catch (x) {
reject(x);
}
}
}
reader.read().then(progress, reject);
return '$R' + streamId.toString(16);
}
function serializeReadableStream(stream) {
// Detect if this is a BYOB stream. BYOB streams should be able to be read as bytes on the
// receiving side. For binary streams, we serialize them as plain Blobs.
let binaryReader;
try {
// $FlowFixMe[extra-arg]: This argument is accepted.
binaryReader = stream.getReader({
mode: 'byob'
});
} catch (x) {
return serializeReader(stream.getReader());
}
return serializeBinaryReader(binaryReader);
}
function serializeAsyncIterable(iterable, iterator) {
if (formData === null) {
// Upgrade to use FormData to allow us to stream this value.
formData = new FormData();
}
const data = formData;
pendingParts++;
const streamId = nextPartId++;
// Generators/Iterators are Iterables but they're also their own iterator
// functions. If that's the case, we treat them as single-shot. Otherwise,
// we assume that this iterable might be a multi-shot and allow it to be
// iterated more than once on the receiving server.
const isIterator = iterable === iterator;
// There's a race condition between when the stream is aborted and when the promise
// resolves so we track whether we already aborted it to avoid writing twice.
function progress(entry) {
if (entry.done) {
if (entry.value === undefined) {
data.append(formFieldPrefix + streamId, 'C'); // Close signal
} else {
// Unlike streams, the last value may not be undefined. If it's not
// we outline it and encode a reference to it in the closing instruction.
try {
// $FlowFixMe[incompatible-type]: While plain JSON can return undefined we never do here.
const partJSON = JSON.stringify(entry.value, resolveToJSON);
data.append(formFieldPrefix + streamId, 'C' + partJSON); // Close signal
} catch (x) {
reject(x);
return;
}
}
pendingParts--;
if (pendingParts === 0) {
resolve(data);
}
} else {
try {
// $FlowFixMe[incompatible-type]: While plain JSON can return undefined we never do here.
const partJSON = JSON.stringify(entry.value, resolveToJSON);
data.append(formFieldPrefix + streamId, partJSON);
iterator.next().then(progress, reject);
} catch (x) {
reject(x);
return;
}
}
}
iterator.next().then(progress, reject);
return '$' + (isIterator ? 'x' : 'X') + streamId.toString(16);
}
function resolveToJSON(key, value) {
const parent = this;
if (value === null) {
return null;
}
if (typeof value === 'object') {
switch (value.$$typeof) {
case REACT_ELEMENT_TYPE:
{
if (temporaryReferences !== undefined && key.indexOf(':') === -1) {
// TODO: If the property name contains a colon, we don't dedupe. Escape instead.
const parentReference = writtenObjects.get(parent);
if (parentReference !== undefined) {
// If the parent has a reference, we can refer to this object indirectly
// through the property name inside that parent.
const reference = parentReference + ':' + key;
// Store this object so that the server can refer to it later in responses.
writeTemporaryReference(temporaryReferences, reference, value);
return serializeTemporaryReferenceMarker();
}
}
throw new Error('React Element cannot be passed to Server Functions from the Client without a ' + 'temporary reference set. Pass a TemporaryReferenceSet to the options.' + (''));
}
case REACT_LAZY_TYPE:
{
// Resolve lazy as if it wasn't here. In the future this will be encoded as a Promise.
const lazy = value;
const payload = lazy._payload;
const init = lazy._init;
if (formData === null) {
// Upgrade to use FormData to allow us to stream this value.
formData = new FormData();
}
pendingParts++;
try {
const resolvedModel = init(payload);
// We always outline this as a separate part even though we could inline it
// because it ensures a more deterministic encoding.
const lazyId = nextPartId++;
const partJSON = serializeModel(resolvedModel, lazyId);
// $FlowFixMe[incompatible-type] We know it's not null because we assigned it above.
const data = formData;
data.append(formFieldPrefix + lazyId, partJSON);
return serializeByValueID(lazyId);
} catch (x) {
if (typeof x === 'object' && x !== null && typeof x.then === 'function') {
// Suspended
pendingParts++;
const lazyId = nextPartId++;
const thenable = x;
const retry = function () {
// While the first promise resolved, its value isn't necessarily what we'll
// resolve into because we might suspend again.
try {
const partJSON = serializeModel(value, lazyId);
// $FlowFixMe[incompatible-type] We know it's not null because we assigned it above.
const data = formData;
data.append(formFieldPrefix + lazyId, partJSON);
pendingParts--;
if (pendingParts === 0) {
resolve(data);
}
} catch (reason) {
reject(reason);
}
};
thenable.then(retry, retry);
return serializeByValueID(lazyId);
} else {
// In the future we could consider serializing this as an error
// that throws on the server instead.
reject(x);
return null;
}
} finally {
pendingParts--;
}
}
}
// $FlowFixMe[method-unbinding]
if (typeof value.then === 'function') {
// We assume that any object with a .then property is a "Thenable" type,
// or a Promise type. Either of which can be represented by a Promise.
if (formData === null) {
// Upgrade to use FormData to allow us to stream this value.
formData = new FormData();
}
pendingParts++;
const promiseId = nextPartId++;
const thenable = value;
thenable.then(partValue => {
try {
const partJSON = serializeModel(partValue, promiseId);
// $FlowFixMe[incompatible-type] We know it's not null because we assigned it above.
const data = formData;
data.append(formFieldPrefix + promiseId, partJSON);
pendingParts--;
if (pendingParts === 0) {
resolve(data);
}
} catch (reason) {
reject(reason);
}
},
// In the future we could consider serializing this as an error
// that throws on the server instead.
reject);
return serializePromiseID(promiseId);
}
const existingReference = writtenObjects.get(value);
if (existingReference !== undefined) {
if (modelRoot === value) {
// This is the ID we're currently emitting so we need to write it
// once but if we discover it again, we refer to it by id.
modelRoot = null;
} else {
// We've already emitted this as an outlined object, so we can
// just refer to that by its existing ID.
return existingReference;
}
} else if (key.indexOf(':') === -1) {
// TODO: If the property name contains a colon, we don't dedupe. Escape instead.
const parentReference = writtenObjects.get(parent);
if (parentReference !== undefined) {
// If the parent has a reference, we can refer to this object indirectly
// through the property name inside that parent.
const reference = parentReference + ':' + key;
writtenObjects.set(value, reference);
if (temporaryReferences !== undefined) {
// Store this object so that the server can refer to it later in responses.
writeTemporaryReference(temporaryReferences, reference, value);
}
}
}
if (isArray(value)) {
// $FlowFixMe[incompatible-return]
return value;
}
// TODO: Should we the Object.prototype.toString.call() to test for cross-realm objects?
if (value instanceof FormData) {
if (formData === null) {
// Upgrade to use FormData to allow us to use rich objects as its values.
formData = new FormData();
}
const data = formData;
const refId = nextPartId++;
// Copy all the form fields with a prefix for this reference.
// These must come first in the form order because we assume that all the
// fields are available before this is referenced.
const prefix = formFieldPrefix + refId + '_';
// $FlowFixMe[prop-missing]: FormData has forEach.
value.forEach((originalValue, originalKey) => {
// $FlowFixMe[incompatible-call]
data.append(prefix + originalKey, originalValue);
});
return serializeFormDataReference(refId);
}
if (value instanceof Map) {
const mapId = nextPartId++;
const partJSON = serializeModel(Array.from(value), mapId);
if (formData === null) {
formData = new FormData();
}
formData.append(formFieldPrefix + mapId, partJSON);
return serializeMapID(mapId);
}
if (value instanceof Set) {
const setId = nextPartId++;
const partJSON = serializeModel(Array.from(value), setId);
if (formData === null) {
formData = new FormData();
}
formData.append(formFieldPrefix + setId, partJSON);
return serializeSetID(setId);
}
if (value instanceof ArrayBuffer) {
const blob = new Blob([value]);
const blobId = nextPartId++;
if (formData === null) {
formData = new FormData();
}
formData.append(formFieldPrefix + blobId, blob);
return '$' + 'A' + blobId.toString(16);
}
if (value instanceof Int8Array) {
// char
return serializeTypedArray('O', value);
}
if (value instanceof Uint8Array) {
// unsigned char
return serializeTypedArray('o', value);
}
if (value instanceof Uint8ClampedArray) {
// unsigned clamped char
return serializeTypedArray('U', value);
}
if (value instanceof Int16Array) {
// sort
return serializeTypedArray('S', value);
}
if (value instanceof Uint16Array) {
// unsigned short
return serializeTypedArray('s', value);
}
if (value instanceof Int32Array) {
// long
return serializeTypedArray('L', value);
}
if (value instanceof Uint32Array) {
// unsigned long
return serializeTypedArray('l', value);
}
if (value instanceof Float32Array) {
// float
return serializeTypedArray('G', value);
}
if (value instanceof Float64Array) {
// double
return serializeTypedArray('g', value);
}
if (value instanceof BigInt64Array) {
// number
return serializeTypedArray('M', value);
}
if (value instanceof BigUint64Array) {
// unsigned number
// We use "m" instead of "n" since JSON can start with "null"
return serializeTypedArray('m', value);
}
if (value instanceof DataView) {
return serializeTypedArray('V', value);
}
// TODO: Blob is not available in old Node/browsers. Remove the typeof check later.
if (typeof Blob === 'function' && value instanceof Blob) {
if (formData === null) {
formData = new FormData();
}
const blobId = nextPartId++;
formData.append(formFieldPrefix + blobId, value);
return serializeBlobID(blobId);
}
const iteratorFn = getIteratorFn(value);
if (iteratorFn) {
const iterator = iteratorFn.call(value);
if (iterator === value) {
// Iterator, not Iterable
const iteratorId = nextPartId++;
const partJSON = serializeModel(Array.from(iterator), iteratorId);
if (formData === null) {
formData = new FormData();
}
formData.append(formFieldPrefix + iteratorId, partJSON);
return serializeIteratorID(iteratorId);
}
return Array.from(iterator);
}
// TODO: ReadableStream is not available in old Node. Remove the typeof check later.
if (typeof ReadableStream === 'function' && value instanceof ReadableStream) {
return serializeReadableStream(value);
}
const getAsyncIterator = value[ASYNC_ITERATOR];
if (typeof getAsyncIterator === 'function') {
// We treat AsyncIterables as a Fragment and as such we might need to key them.
return serializeAsyncIterable(value, getAsyncIterator.call(value));
}
// Verify that this is a simple plain object.
const proto = getPrototypeOf(value);
if (proto !== ObjectPrototype && (proto === null || getPrototypeOf(proto) !== null)) {
if (temporaryReferences === undefined) {
throw new Error('Only plain objects, and a few built-ins, can be passed to Server Functions. ' + 'Classes or null prototypes are not supported.' + (''));
}
// We will have written this object to the temporary reference set above
// so we can replace it with a marker to refer to this slot later.
return serializeTemporaryReferenceMarker();
}
// $FlowFixMe[incompatible-return]
return value;
}
if (typeof value === 'string') {
// TODO: Maybe too clever. If we support URL there's no similar trick.
if (value[value.length - 1] === 'Z') {
// Possibly a Date, whose toJSON automatically calls toISOString
// $FlowFixMe[incompatible-use]
const originalValue = parent[key];
if (originalValue instanceof Date) {
return serializeDateFromDateJSON(value);
}
}
return escapeStringValue(value);
}
if (typeof value === 'boolean') {
return value;
}
if (typeof value === 'number') {
return serializeNumber(value);
}
if (typeof value === 'undefined') {
return serializeUndefined();
}
if (typeof value === 'function') {
const referenceClosure = knownServerReferences.get(value);
if (referenceClosure !== undefined) {
const id = referenceClosure.id,
bound = referenceClosure.bound;
const referenceClosureJSON = JSON.stringify({
id,
bound
}, resolveToJSON);
if (formData === null) {
// Upgrade to use FormData to allow us to stream this value.
formData = new FormData();
}
// The reference to this function came from the same client so we can pass it back.
const refId = nextPartId++;
formData.set(formFieldPrefix + refId, referenceClosureJSON);
return serializeServerReferenceID(refId);
}
if (temporaryReferences !== undefined && key.indexOf(':') === -1) {
// TODO: If the property name contains a colon, we don't dedupe. Escape instead.
const parentReference = writtenObjects.get(parent);
if (parentReference !== undefined) {
// If the parent has a reference, we can refer to this object indirectly
// through the property name inside that parent.
const reference = parentReference + ':' + key;
// Store this object so that the server can refer to it later in responses.
writeTemporaryReference(temporaryReferences, reference, value);
return serializeTemporaryReferenceMarker();
}
}
throw new Error('Client Functions cannot be passed directly to Server Functions. ' + 'Only Functions passed from the Server can be passed back again.');
}
if (typeof value === 'symbol') {
if (temporaryReferences !== undefined && key.indexOf(':') === -1) {
// TODO: If the property name contains a colon, we don't dedupe. Escape instead.
const parentReference = writtenObjects.get(parent);
if (parentReference !== undefined) {
// If the parent has a reference, we can refer to this object indirectly
// through the property name inside that parent.
const reference = parentReference + ':' + key;
// Store this object so that the server can refer to it later in responses.
writeTemporaryReference(temporaryReferences, reference, value);
return serializeTemporaryReferenceMarker();
}
}
throw new Error('Symbols cannot be passed to a Server Function without a ' + 'temporary reference set. Pass a TemporaryReferenceSet to the options.' + (''));
}
if (typeof value === 'bigint') {
return serializeBigInt(value);
}
throw new Error("Type " + typeof value + " is not supported as an argument to a Server Function.");
}
function serializeModel(model, id) {
if (typeof model === 'object' && model !== null) {
const reference = serializeByValueID(id);
writtenObjects.set(model, reference);
if (temporaryReferences !== undefined) {
// Store this object so that the server can refer to it later in responses.
writeTemporaryReference(temporaryReferences, reference, model);
}
}
modelRoot = model;
// $FlowFixMe[incompatible-return] it's not going to be undefined because we'll encode it.
return JSON.stringify(model, resolveToJSON);
}
function abort(reason) {
if (pendingParts > 0) {
pendingParts = 0; // Don't resolve again later.
// Resolve with what we have so far, which may have holes at this point.
// They'll error when the stream completes on the server.
if (formData === null) {
resolve(json);
} else {
resolve(formData);
}
}
}
const json = serializeModel(root, 0);
if (formData === null) {
// If it's a simple data structure, we just use plain JSON.
resolve(json);
} else {
// Otherwise, we use FormData to let us stream in the result.
formData.set(formFieldPrefix + '0', json);
if (pendingParts === 0) {
// $FlowFixMe[incompatible-call] this has already been refined.
resolve(formData);
}
}
return abort;
}
function registerBoundServerReference(reference, id, bound, encodeFormAction) {
if (knownServerReferences.has(reference)) {
return;
}
knownServerReferences.set(reference, {
id,
originalBind: reference.bind,
bound
});
}
function registerServerReference(reference, id, encodeFormAction) {
registerBoundServerReference(reference, id, null);
return reference;
}
function createBoundServerReference(metaData, callServer, encodeFormAction, findSourceMapURL // DEV-only
) {
const id = metaData.id;
const bound = metaData.bound;
let action = function () {
// $FlowFixMe[method-unbinding]
const args = Array.prototype.slice.call(arguments);
const p = bound;
if (!p) {
return callServer(id, args);
}
if (p.status === 'fulfilled') {
const boundArgs = p.value;
return callServer(id, boundArgs.concat(args));
}
// Since this is a fake Promise whose .then doesn't chain, we have to wrap it.
// TODO: Remove the wrapper once that's fixed.
return Promise.resolve(p).then(function (boundArgs) {
return callServer(id, boundArgs.concat(args));
});
};
registerBoundServerReference(action, id, bound);
return action;
}
function createServerReference(id, callServer, encodeFormAction, findSourceMapURL,
// DEV-only
functionName) {
let action = function () {
// $FlowFixMe[method-unbinding]
const args = Array.prototype.slice.call(arguments);
return callServer(id, args);
};
registerBoundServerReference(action, id, null);
return action;
}
const ROW_ID = 0;
const ROW_TAG = 1;
const ROW_LENGTH = 2;
const ROW_CHUNK_BY_NEWLINE = 3;
const ROW_CHUNK_BY_LENGTH = 4;
const PENDING = 'pending';
const BLOCKED = 'blocked';
const RESOLVED_MODEL = 'resolved_model';
const RESOLVED_MODULE = 'resolved_module';
const INITIALIZED = 'fulfilled';
const ERRORED = 'rejected';
const HALTED = 'halted'; // DEV-only. Means it never resolves even if connection closes.
// $FlowFixMe[missing-this-annot]
function ReactPromise(status, value, reason) {
this.status = status;
this.value = value;
this.reason = reason;
}
// We subclass Promise.prototype so that we get other methods like .catch
ReactPromise.prototype = Object.create(Promise.prototype);
// TODO: This doesn't return a new Promise chain unlike the real .then
ReactPromise.prototype.then = function (resolve, reject) {
const chunk = this;
// If we have resolved content, we try to initialize it first which
// might put us back into one of the other states.
switch (chunk.status) {
case RESOLVED_MODEL:
initializeModelChunk(chunk);
break;
case RESOLVED_MODULE:
initializeModuleChunk(chunk);
break;
}
// The status might have changed after initialization.
switch (chunk.status) {
case INITIALIZED:
if (typeof resolve === 'function') {
resolve(chunk.value);
}
break;
case PENDING:
case BLOCKED:
if (typeof resolve === 'function') {
if (chunk.value === null) {
chunk.value = [];
}
chunk.value.push(resolve);
}
if (typeof reject === 'function') {
if (chunk.reason === null) {
chunk.reason = [];
}
chunk.reason.push(reject);
}
break;
case HALTED:
{
break;
}
default:
if (typeof reject === 'function') {
reject(chunk.reason);
}
break;
}
};
function unwrapWeakResponse(weakResponse) {
{
return weakResponse; // In prod we just use the real Response directly.
}
}
function getWeakResponse(response) {
{
return response; // In prod we just use the real Response directly.
}
}
function readChunk(chunk) {
// If we have resolved content, we try to initialize it first which
// might put us back into one of the other states.
switch (chunk.status) {
case RESOLVED_MODEL:
initializeModelChunk(chunk);
break;
case RESOLVED_MODULE:
initializeModuleChunk(chunk);
break;
}
// The status might have changed after initialization.
switch (chunk.status) {
case INITIALIZED:
return chunk.value;
case PENDING:
case BLOCKED:
case HALTED:
// eslint-disable-next-line no-throw-literal
throw chunk;
default:
throw chunk.reason;
}
}
function getRoot(weakResponse) {
const response = unwrapWeakResponse(weakResponse);
const chunk = getChunk(response, 0);
return chunk;
}
function createPendingChunk(response) {
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
return new ReactPromise(PENDING, null, null);
}
function createBlockedChunk(response) {
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
return new ReactPromise(BLOCKED, null, null);
}
function createErrorChunk(response, error) {
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
return new ReactPromise(ERRORED, null, error);
}
function wakeChunk(listeners, value) {
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
if (typeof listener === 'function') {
listener(value);
} else {
fulfillReference(listener, value);
}
}
}
function rejectChunk(listeners, error) {
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
if (typeof listener === 'function') {
listener(error);
} else {
rejectReference(listener, error);
}
}
}
function resolveBlockedCycle(resolvedChunk, reference) {
const referencedChunk = reference.handler.chunk;
if (referencedChunk === null) {
return null;
}
if (referencedChunk === resolvedChunk) {
// We found the cycle. We can resolve the blocked cycle now.
return reference.handler;
}
const resolveListeners = referencedChunk.value;
if (resolveListeners !== null) {
for (let i = 0; i < resolveListeners.length; i++) {
const listener = resolveListeners[i];
if (typeof listener !== 'function') {
const foundHandler = resolveBlockedCycle(resolvedChunk, listener);
if (foundHandler !== null) {
return foundHandler;
}
}
}
}
return null;
}
function wakeChunkIfInitialized(chunk, resolveListeners, rejectListeners) {
switch (chunk.status) {
case INITIALIZED:
wakeChunk(resolveListeners, chunk.value);
break;
case BLOCKED:
// It is possible that we're blocked on our own chunk if it's a cycle.
// Before adding back the listeners to the chunk, let's check if it would
// result in a cycle.
for (let i = 0; i < resolveListeners.length; i++) {
const listener = resolveListeners[i];
if (typeof listener !== 'function') {
const reference = listener;
const cyclicHandler = resolveBlockedCycle(chunk, reference);
if (cyclicHandler !== null) {
// This reference points back to this chunk. We can resolve the cycle by
// using the value from that handler.
fulfillReference(reference, cyclicHandler.value);
resolveListeners.splice(i, 1);
i--;
if (rejectListeners !== null) {
const rejectionIdx = rejectListeners.indexOf(reference);
if (rejectionIdx !== -1) {
rejectListeners.splice(rejectionIdx, 1);
}
}
}
}
}
// Fallthrough
case PENDING:
if (chunk.value) {
for (let i = 0; i < resolveListeners.length; i++) {
chunk.value.push(resolveListeners[i]);
}
} else {
chunk.value = resolveListeners;
}
if (chunk.reason) {
if (rejectListeners) {
for (let i = 0; i < rejectListeners.length; i++) {
chunk.reason.push(rejectListeners[i]);
}
}
} else {
chunk.reason = rejectListeners;
}
break;
case ERRORED:
if (rejectListeners) {
rejectChunk(rejectListeners, chunk.reason);
}
break;
}
}
function triggerErrorOnChunk(response, chunk, error) {
if (chunk.status !== PENDING && chunk.status !== BLOCKED) {
// If we get more data to an already resolved ID, we assume that it's
// a stream chunk since any other row shouldn't have more than one entry.
const streamChunk = chunk;
const controller = streamChunk.reason;
// $FlowFixMe[incompatible-call]: The error method should accept mixed.
controller.error(error);
return;
}
const listeners = chunk.reason;
const erroredChunk = chunk;
erroredChunk.status = ERRORED;
erroredChunk.reason = error;
if (listeners !== null) {
rejectChunk(listeners, error);
}
}
function createResolvedModelChunk(response, value) {
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
return new ReactPromise(RESOLVED_MODEL, value, response);
}
function createResolvedModuleChunk(response, value) {
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
return new ReactPromise(RESOLVED_MODULE, value, null);
}
function createInitializedTextChunk(response, value) {
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
return new ReactPromise(INITIALIZED, value, null);
}
function createInitializedBufferChunk(response, value) {
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
return new ReactPromise(INITIALIZED, value, null);
}
function createInitializedIteratorResultChunk(response, value, done) {
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
return new ReactPromise(INITIALIZED, {
done: done,
value: value
}, null);
}
function createInitializedStreamChunk(response, value, controller) {
// We use the reason field to stash the controller since we already have that
// field. It's a bit of a hack but efficient.
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
return new ReactPromise(INITIALIZED, value, controller);
}
function createResolvedIteratorResultChunk(response, value, done) {
// To reuse code as much code as possible we add the wrapper element as part of the JSON.
const iteratorResultJSON = (done ? '{"done":true,"value":' : '{"done":false,"value":') + value + '}';
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
return new ReactPromise(RESOLVED_MODEL, iteratorResultJSON, response);
}
function resolveIteratorResultChunk(response, chunk, value, done) {
// To reuse code as much code as possible we add the wrapper element as part of the JSON.
const iteratorResultJSON = (done ? '{"done":true,"value":' : '{"done":false,"value":') + value + '}';
resolveModelChunk(response, chunk, iteratorResultJSON);
}
function resolveModelChunk(response, chunk, value) {
if (chunk.status !== PENDING) {
// If we get more data to an already resolved ID, we assume that it's
// a stream chunk since any other row shouldn't have more than one entry.
const streamChunk = chunk;
const controller = streamChunk.reason;
controller.enqueueModel(value);
return;
}
const resolveListeners = chunk.value;
const rejectListeners = chunk.reason;
const resolvedChunk = chunk;
resolvedChunk.status = RESOLVED_MODEL;
resolvedChunk.value = value;
resolvedChunk.reason = response;
if (resolveListeners !== null) {
// This is unfortunate that we're reading this eagerly if
// we already have listeners attached since they might no
// longer be rendered or might not be the highest pri.
initializeModelChunk(resolvedChunk);
// The status might have changed after initialization.
wakeChunkIfInitialized(chunk, resolveListeners, rejectListeners);
}
}
function resolveModuleChunk(response, chunk, value) {
if (chunk.status !== PENDING && chunk.status !== BLOCKED) {
// We already resolved. We didn't expect to see this.
return;
}
const resolveListeners = chunk.value;
const rejectListeners = chunk.reason;
const resolvedChunk = chunk;
resolvedChunk.status = RESOLVED_MODULE;
resolvedChunk.value = value;
if (resolveListeners !== null) {
initializeModuleChunk(resolvedChunk);
wakeChunkIfInitialized(chunk, resolveListeners, rejectListeners);
}
}
let initializingHandler = null;
function initializeModelChunk(chunk) {
const prevHandler = initializingHandler;
initializingHandler = null;
const resolvedModel = chunk.value;
const response = chunk.reason;
// We go to the BLOCKED state until we've fully resolved this.
// We do this before parsing in case we try to initialize the same chunk
// while parsing the model. Such as in a cyclic reference.
const cyclicChunk = chunk;
cyclicChunk.status = BLOCKED;
cyclicChunk.value = null;
cyclicChunk.reason = null;
try {
const value = parseModel(response, resolvedModel);
// Invoke any listeners added while resolving this model. I.e. cyclic
// references. This may or may not fully resolve the model depending on
// if they were blocked.
const resolveListeners = cyclicChunk.value;
if (resolveListeners !== null) {
cyclicChunk.value = null;
cyclicChunk.reason = null;
wakeChunk(resolveListeners, value);
}
if (initializingHandler !== null) {
if (initializingHandler.errored) {
throw initializingHandler.reason;
}
if (initializingHandler.deps > 0) {
// We discovered new dependencies on modules that are not yet resolved.
// We have to keep the BLOCKED state until they're resolved.
initializingHandler.value = value;
initializingHandler.chunk = cyclicChunk;
return;
}
}
const initializedChunk = chunk;
initializedChunk.status = INITIALIZED;
initializedChunk.value = value;
} catch (error) {
const erroredChunk = chunk;
erroredChunk.status = ERRORED;
erroredChunk.reason = error;
} finally {
initializingHandler = prevHandler;
}
}
function initializeModuleChunk(chunk) {
try {
const value = requireModule(chunk.value);
const initializedChunk = chunk;
initializedChunk.status = INITIALIZED;
initializedChunk.value = value;
} catch (error) {
const erroredChunk = chunk;
erroredChunk.status = ERRORED;
erroredChunk.reason = error;
}
}
// Report that any missing chunks in the model is now going to throw this
// error upon read. Also notify any pending promises.
function reportGlobalError(weakResponse, error) {
const response = unwrapWeakResponse(weakResponse);
response._closed = true;
response._closedReason = error;
response._chunks.forEach(chunk => {
// If this chunk was already resolved or errored, it won't
// trigger an error but if it wasn't then we need to
// because we won't be getting any new data to resolve it.
if (chunk.status === PENDING) {
triggerErrorOnChunk(response, chunk, error);
}
});
}
function createElement(response, type, key, props, owner,
// DEV-only
stack,
// DEV-only
validated // DEV-only
) {
let element;
{
element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
type,
key,
ref: null,
props
};
}
if (initializingHandler !== null) {
const handler = initializingHandler;
// We pop the stack to the previous outer handler before leaving the Element.
// This is effectively the complete phase.
initializingHandler = handler.parent;
if (handler.errored) {
// Something errored inside this Element's props. We can turn this Element
// into a Lazy so that we can still render up until that Lazy is rendered.
const erroredChunk = createErrorChunk(response, handler.reason);
return createLazyChunkWrapper(erroredChunk);
}
if (handler.deps > 0) {
// We have blocked references inside this Element but we can turn this into
// a Lazy node referencing this Element to let everything around it proceed.
const blockedChunk = createBlockedChunk();
handler.value = element;
handler.chunk = blockedChunk;
return createLazyChunkWrapper(blockedChunk);
}
}
return element;
}
function createLazyChunkWrapper(chunk) {
const lazyType = {
$$typeof: REACT_LAZY_TYPE,
_payload: chunk,
_init: readChunk
};
return lazyType;
}
function getChunk(response, id) {
const chunks = response._chunks;
let chunk = chunks.get(id);
if (!chunk) {
if (response._closed) {
// We have already errored the response and we're not going to get
// anything more streaming in so this will immediately error.
chunk = createErrorChunk(response, response._closedReason);
} else {
chunk = createPendingChunk();
}
chunks.set(id, chunk);
}
return chunk;
}
function fulfillReference(reference, value) {
const response = reference.response,
handler = reference.handler,
parentObject = reference.parentObject,
key = reference.key,
map = reference.map,
path = reference.path;
for (let i = 1; i < path.length; i++) {
while (value.$$typeof === REACT_LAZY_TYPE) {
// We never expect to see a Lazy node on this path because we encode those as
// separate models. This must mean that we have inserted an extra lazy node
// e.g. to replace a blocked element. We must instead look for it inside.
const referencedChunk = value._payload;
if (referencedChunk === handler.chunk) {
// This is a reference to the thing we're currently blocking. We can peak
// inside of it to get the value.
value = handler.value;
continue;
} else {
switch (referencedChunk.status) {
case RESOLVED_MODEL:
initializeModelChunk(referencedChunk);
break;
case RESOLVED_MODULE:
initializeModuleChunk(referencedChunk);
break;
}
switch (referencedChunk.status) {
case INITIALIZED:
{
value = referencedChunk.value;
continue;
}
case BLOCKED:
{
// It is possible that we're blocked on our own chunk if it's a cycle.
// Before adding the listener to the inner chunk, let's check if it would
// result in a cycle.
const cyclicHandler = resolveBlockedCycle(referencedChunk, reference);
if (cyclicHandler !== null) {
// This reference points back to this chunk. We can resolve the cycle by