UNPKG

blade

Version:
924 lines (906 loc) • 31.2 kB
import { r as __toESM, t as __commonJS } from "./chunk-DUEDWNxO.js"; import { a as populatePathSegments, c as DEFAULT_COOKIE_MAX_AGE, n as getCookieSetter, r as getOutputFile, s as CUSTOM_HEADERS } from "./utils-BIjILkWh.js"; import { t as RootServerContext } from "./context-BBeH3CVO.js"; import { createContext, useContext, useEffect, useMemo, useRef, useState } from "react"; import { hydrateRoot } from "react-dom/client"; //#region private/client/context.ts const RootClientContext = createContext(null); //#endregion //#region ../../node_modules/radash/dist/esm/typed.mjs const isArray = Array.isArray; const isObject = (value) => { return !!value && value.constructor === Object; }; const isPrimitive = (value) => { return value === void 0 || value === null || typeof value !== "object" && typeof value !== "function"; }; //#endregion //#region ../../node_modules/radash/dist/esm/number.mjs const toInt = (value, defaultValue) => { const def = defaultValue === void 0 ? 0 : defaultValue; if (value === null || value === void 0) return def; const result = parseInt(value); return isNaN(result) ? def : result; }; //#endregion //#region ../../node_modules/radash/dist/esm/object.mjs const clone = (obj) => { if (isPrimitive(obj)) return obj; if (typeof obj === "function") return obj.bind({}); const newObj = new obj.constructor(); Object.getOwnPropertyNames(obj).forEach((prop) => { newObj[prop] = obj[prop]; }); return newObj; }; const omit = (obj, keys2) => { if (!obj) return {}; if (!keys2 || keys2.length === 0) return obj; return keys2.reduce((acc, key) => { delete acc[key]; return acc; }, { ...obj }); }; const set = (initial, path, value) => { if (!initial) return {}; if (!path || value === void 0) return initial; const segments = path.split(/[\.\[\]]/g).filter((x) => !!x.trim()); const _set = (node) => { if (segments.length > 1) { const key = segments.shift(); const nextIsNum = toInt(segments[0], null) === null ? false : true; node[key] = node[key] === void 0 ? nextIsNum ? [] : {} : node[key]; _set(node[key]); } else node[segments[0]] = value; }; const cloned = clone(initial); _set(cloned); return cloned; }; const assign = (initial, override) => { if (!initial || !override) return initial ?? override ?? {}; return Object.entries({ ...initial, ...override }).reduce((acc, [key, value]) => { return { ...acc, [key]: (() => { if (isObject(initial[key])) return assign(initial[key], value); return value; })() }; }, {}); }; const construct = (obj) => { if (!obj) return {}; return Object.keys(obj).reduce((acc, path) => { return set(acc, path, obj[path]); }, {}); }; //#endregion //#region private/client/utils/constants.ts const IS_CLIENT_DEV = import.meta.env.BLADE_ENV === "development"; /** * A name prefix used to identify an input whose value should be used to resolve a target * record instead of being treated as a value to store in the record. * * We are using this prefix in order to enable progressive enhancement in the future, * meaning making it possible to submit forms even before their JavaScript is downloaded. */ const FORM_TARGET_PREFIX = "__blade_target_"; //#endregion //#region ../../node_modules/eventsource-parser/dist/index.js var ParseError = class extends Error { constructor(message, options) { super(message), this.name = "ParseError", this.type = options.type, this.field = options.field, this.value = options.value, this.line = options.line; } }; function noop(_arg) {} function createParser(callbacks) { if (typeof callbacks == "function") throw new TypeError("`callbacks` must be an object, got a function instead. Did you mean `{onEvent: fn}`?"); const { onEvent = noop, onError = noop, onRetry = noop, onComment } = callbacks; let incompleteLine = "", isFirstChunk = !0, id, data = "", eventType = ""; function feed(newChunk) { const chunk = isFirstChunk ? newChunk.replace(/^\xEF\xBB\xBF/, "") : newChunk, [complete, incomplete] = splitLines(`${incompleteLine}${chunk}`); for (const line of complete) parseLine(line); incompleteLine = incomplete, isFirstChunk = !1; } function parseLine(line) { if (line === "") { dispatchEvent(); return; } if (line.startsWith(":")) { onComment && onComment(line.slice(line.startsWith(": ") ? 2 : 1)); return; } const fieldSeparatorIndex = line.indexOf(":"); if (fieldSeparatorIndex !== -1) { const field = line.slice(0, fieldSeparatorIndex), offset = line[fieldSeparatorIndex + 1] === " " ? 2 : 1; processField(field, line.slice(fieldSeparatorIndex + offset), line); return; } processField(line, "", line); } function processField(field, value, line) { switch (field) { case "event": eventType = value; break; case "data": data = `${data}${value} `; break; case "id": id = value.includes("\0") ? void 0 : value; break; case "retry": /^\d+$/.test(value) ? onRetry(parseInt(value, 10)) : onError(new ParseError(`Invalid \`retry\` value: "${value}"`, { type: "invalid-retry", value, line })); break; default: onError(new ParseError(`Unknown field "${field.length > 20 ? `${field.slice(0, 20)}\u2026` : field}"`, { type: "unknown-field", field, value, line })); break; } } function dispatchEvent() { data.length > 0 && onEvent({ id, event: eventType || void 0, data: data.endsWith(` `) ? data.slice(0, -1) : data }), id = void 0, data = "", eventType = ""; } function reset(options = {}) { incompleteLine && options.consume && parseLine(incompleteLine), isFirstChunk = !0, id = void 0, data = "", eventType = "", incompleteLine = ""; } return { feed, reset }; } function splitLines(chunk) { const lines = []; let incompleteLine = "", searchIndex = 0; for (; searchIndex < chunk.length;) { const crIndex = chunk.indexOf("\r", searchIndex), lfIndex = chunk.indexOf(` `, searchIndex); let lineEnd = -1; if (crIndex !== -1 && lfIndex !== -1 ? lineEnd = Math.min(crIndex, lfIndex) : crIndex !== -1 ? lineEnd = crIndex : lfIndex !== -1 && (lineEnd = lfIndex), lineEnd === -1) { incompleteLine = chunk.slice(searchIndex); break; } else { const line = chunk.slice(searchIndex, lineEnd); lines.push(line), searchIndex = lineEnd + 1, chunk[searchIndex - 1] === "\r" && chunk[searchIndex] === ` ` && searchIndex++; } } return [lines, incompleteLine]; } //#endregion //#region ../../node_modules/retry/lib/retry_operation.js var require_retry_operation = /* @__PURE__ */ __commonJS({ "../../node_modules/retry/lib/retry_operation.js": ((exports, module) => { function RetryOperation$1(timeouts, options) { if (typeof options === "boolean") options = { forever: options }; this._originalTimeouts = JSON.parse(JSON.stringify(timeouts)); this._timeouts = timeouts; this._options = options || {}; this._maxRetryTime = options && options.maxRetryTime || Infinity; this._fn = null; this._errors = []; this._attempts = 1; this._operationTimeout = null; this._operationTimeoutCb = null; this._timeout = null; this._operationStart = null; this._timer = null; if (this._options.forever) this._cachedTimeouts = this._timeouts.slice(0); } module.exports = RetryOperation$1; RetryOperation$1.prototype.reset = function() { this._attempts = 1; this._timeouts = this._originalTimeouts.slice(0); }; RetryOperation$1.prototype.stop = function() { if (this._timeout) clearTimeout(this._timeout); if (this._timer) clearTimeout(this._timer); this._timeouts = []; this._cachedTimeouts = null; }; RetryOperation$1.prototype.retry = function(err) { if (this._timeout) clearTimeout(this._timeout); if (!err) return false; var currentTime = (/* @__PURE__ */ new Date()).getTime(); if (err && currentTime - this._operationStart >= this._maxRetryTime) { this._errors.push(err); this._errors.unshift(/* @__PURE__ */ new Error("RetryOperation timeout occurred")); return false; } this._errors.push(err); var timeout = this._timeouts.shift(); if (timeout === void 0) if (this._cachedTimeouts) { this._errors.splice(0, this._errors.length - 1); timeout = this._cachedTimeouts.slice(-1); } else return false; var self = this; this._timer = setTimeout(function() { self._attempts++; if (self._operationTimeoutCb) { self._timeout = setTimeout(function() { self._operationTimeoutCb(self._attempts); }, self._operationTimeout); if (self._options.unref) self._timeout.unref(); } self._fn(self._attempts); }, timeout); if (this._options.unref) this._timer.unref(); return true; }; RetryOperation$1.prototype.attempt = function(fn, timeoutOps) { this._fn = fn; if (timeoutOps) { if (timeoutOps.timeout) this._operationTimeout = timeoutOps.timeout; if (timeoutOps.cb) this._operationTimeoutCb = timeoutOps.cb; } var self = this; if (this._operationTimeoutCb) this._timeout = setTimeout(function() { self._operationTimeoutCb(); }, self._operationTimeout); this._operationStart = (/* @__PURE__ */ new Date()).getTime(); this._fn(this._attempts); }; RetryOperation$1.prototype.try = function(fn) { console.log("Using RetryOperation.try() is deprecated"); this.attempt(fn); }; RetryOperation$1.prototype.start = function(fn) { console.log("Using RetryOperation.start() is deprecated"); this.attempt(fn); }; RetryOperation$1.prototype.start = RetryOperation$1.prototype.try; RetryOperation$1.prototype.errors = function() { return this._errors; }; RetryOperation$1.prototype.attempts = function() { return this._attempts; }; RetryOperation$1.prototype.mainError = function() { if (this._errors.length === 0) return null; var counts = {}; var mainError = null; var mainErrorCount = 0; for (var i = 0; i < this._errors.length; i++) { var error = this._errors[i]; var message = error.message; var count = (counts[message] || 0) + 1; counts[message] = count; if (count >= mainErrorCount) { mainError = error; mainErrorCount = count; } } return mainError; }; }) }); //#endregion //#region ../../node_modules/retry/lib/retry.js var require_retry$1 = /* @__PURE__ */ __commonJS({ "../../node_modules/retry/lib/retry.js": ((exports) => { var RetryOperation = require_retry_operation(); exports.operation = function(options) { return new RetryOperation(exports.timeouts(options), { forever: options && (options.forever || options.retries === Infinity), unref: options && options.unref, maxRetryTime: options && options.maxRetryTime }); }; exports.timeouts = function(options) { if (options instanceof Array) return [].concat(options); var opts = { retries: 10, factor: 2, minTimeout: 1 * 1e3, maxTimeout: Infinity, randomize: false }; for (var key in options) opts[key] = options[key]; if (opts.minTimeout > opts.maxTimeout) throw new Error("minTimeout is greater than maxTimeout"); var timeouts = []; for (var i = 0; i < opts.retries; i++) timeouts.push(this.createTimeout(i, opts)); if (options && options.forever && !timeouts.length) timeouts.push(this.createTimeout(i, opts)); timeouts.sort(function(a, b) { return a - b; }); return timeouts; }; exports.createTimeout = function(attempt, opts) { var random = opts.randomize ? Math.random() + 1 : 1; var timeout = Math.round(random * Math.max(opts.minTimeout, 1) * Math.pow(opts.factor, attempt)); timeout = Math.min(timeout, opts.maxTimeout); return timeout; }; exports.wrap = function(obj, options, methods) { if (options instanceof Array) { methods = options; options = null; } if (!methods) { methods = []; for (var key in obj) if (typeof obj[key] === "function") methods.push(key); } for (var i = 0; i < methods.length; i++) { var method = methods[i]; var original = obj[method]; obj[method] = function retryWrapper(original$1) { var op = exports.operation(options); var args = Array.prototype.slice.call(arguments, 1); var callback = args.pop(); args.push(function(err) { if (op.retry(err)) return; if (err) arguments[0] = op.mainError(); callback.apply(this, arguments); }); op.attempt(function() { original$1.apply(obj, args); }); }.bind(obj, original); obj[method].options = options; } }; }) }); //#endregion //#region ../../node_modules/retry/index.js var require_retry = /* @__PURE__ */ __commonJS({ "../../node_modules/retry/index.js": ((exports, module) => { module.exports = require_retry$1(); }) }); //#endregion //#region ../../node_modules/async-retry/lib/index.js var require_lib = /* @__PURE__ */ __commonJS({ "../../node_modules/async-retry/lib/index.js": ((exports, module) => { var retrier = require_retry(); function retry$1(fn, opts) { function run(resolve, reject) { var options = opts || {}; var op; if (!("randomize" in options)) options.randomize = true; op = retrier.operation(options); function bail(err) { reject(err || /* @__PURE__ */ new Error("Aborted")); } function onError(err, num) { if (err.bail) { bail(err); return; } if (!op.retry(err)) reject(op.mainError()); else if (options.onRetry) options.onRetry(err, num); } function runAttempt(num) { var val; try { val = fn(bail, num); } catch (err) { onError(err, num); return; } Promise.resolve(val).then(resolve).catch(function catchIt(err) { onError(err, num); }); } op.attempt(runAttempt); } return new Promise(run); } module.exports = retry$1; }) }); //#endregion //#region private/client/utils/data.ts var import_lib = /* @__PURE__ */ __toESM(require_lib(), 1); const retryOptions = { minTimeout: 10, retries: 5, factor: 6, maxRetryAfter: 20 }; /** * Like `fetch`, except that failed requests are retried automatically. * * @param request - A URL or `Request` object. * @param requestInit - An optional `Request` or `RequestInit` object. * * @returns A native `fetch` response. */ const fetchRetry = async (request, requestInit) => { return (0, import_lib.default)(async (bail) => { try { const response = await fetch(request, requestInit); if (response.status >= 500 && response.status < 600 || response.status === 429) { const retryAfter = response.headers.has("retry-after") ? Number.parseInt(response.headers.get("retry-after"), 10) : null; if (retryAfter) { if (retryAfter > retryOptions.maxRetryAfter) return response; await new Promise((r) => { setTimeout(r, retryAfter * 1e3); }); } const error = new Error(response.statusText); error.body = await response.text(); error.statusCode = response.status; throw error; } return response; } catch (err) { const error = err; if (error.type === "aborted") return bail(error); throw err; } }); }; //#endregion //#region private/client/utils/parser.ts const REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"); const parseModel = (response, json) => JSON.parse(json, response._fromJSON); const createPendingChunk = () => { let resolve; const promise = new Promise((r) => { resolve = r; }); promise.resolve = resolve; return promise; }; const createElement = (type, key, props) => { const element = { $$typeof: REACT_ELEMENT_TYPE, type, key, ref: null, props, _owner: null }; Object.defineProperty(element, "_self", { configurable: false, enumerable: false, writable: false, value: null }); Object.defineProperty(element, "_source", { configurable: false, enumerable: false, writable: false, value: null }); return element; }; const parseModelString = (response, value) => { if (value === "$") return REACT_ELEMENT_TYPE; if (value[0] !== "$") return value; switch (value[1]) { case "$": return value.substring(1); case "S": return Symbol.for(value.substring(2)); default: { const id = Number.parseInt(value.substring(1), 16); const chunk = response._chunks.get(id); if (!chunk) throw new Error(`Missing chunk with id: ${id}`); if (chunk._result !== void 0) return chunk._result; throw chunk; } } }; const parseModelTuple = (value) => { return Array.isArray(value) && value[0] === REACT_ELEMENT_TYPE ? createElement(value[1], value[2], value[3]) : value; }; const resolveModel = (response, id, model) => { const chunk = response._chunks.get(id); if (chunk) { const value = parseModel(response, model); chunk._result = value; chunk.resolve(value); } }; const resolveModule = (response, id, model) => { const chunks = response._chunks; const chunk = createPendingChunk(); const moduleReference = parseModel(response, model); const value = window["BLADE_CHUNKS"][moduleReference.chunks[0]][moduleReference.name]; chunk._result = value; chunk.resolve(value); chunks.set(id, chunk); }; const processFullRow = (response, row) => { if (row === "") return; const colon = row.indexOf(":", 0); const id = Number.parseInt(row.substring(0, colon), 16); switch (row[colon + 1]) { case "I": resolveModule(response, id, row.substring(colon + 2)); return; default: resolveModel(response, id, row.substring(colon + 1)); return; } }; const processBinaryChunk = (response, defaultChunk) => { let chunk = defaultChunk; const stringDecoder = new TextDecoder(); let linebreak = chunk.indexOf(10); while (linebreak > -1) { processFullRow(response, response._partialRow + stringDecoder.decode(chunk.subarray(0, linebreak))); response._partialRow = ""; chunk = chunk.subarray(linebreak + 1); linebreak = chunk.indexOf(10); } response._partialRow += stringDecoder.decode(chunk, { stream: true }); }; const createFromJSONCallback = (response) => (_key, value) => { if (typeof value === "string") return parseModelString(response, value); if (typeof value === "object" && value !== null) return parseModelTuple(value); return value; }; const startReadingFromStream = (response, stream) => { const reader = stream.getReader(); const progress = (chunk) => { const { done, value } = chunk; if (done) return; processBinaryChunk(response, value); return reader.read().then(progress); }; reader.read().then(progress); }; const createFromReadableStream = (stream) => { const response = { _chunks: /* @__PURE__ */ new Map(), _partialRow: "" }; response._fromJSON = createFromJSONCallback(response); startReadingFromStream(response, stream); const chunk = createPendingChunk(); response._chunks.set(0, chunk); return chunk; }; //#endregion //#region private/client/utils/page.ts /** * Downloads an asset from the server, without evaluating it. * * @param bundleId - The ID of the bundle. * @param type - The type of the asset to download. * * @returns A promise that resolves once the asset is downloaded. */ const loadResource = async (bundleId, type) => { const extension = { "main-css": "css", "main-js": "js", shared: "js" }[type]; const assetPrefix = import.meta.env.__BLADE_ASSET_PREFIX; return new Promise((resolve, reject) => { const link = document.createElement("link"); link.rel = extension === "css" ? "preload" : "modulepreload"; link.as = extension === "css" ? "style" : "script"; link.onload = resolve; link.onerror = reject; link.href = `${assetPrefix}/${getOutputFile(bundleId, extension, type === "shared")}`; document.head.appendChild(link); }); }; let SUBSCRIPTIONS = new Array(); /** * Sends a request for rendering a page to the server, which opens a readable stream. * * @param url - The URL of the page to render. * @param body - The body of the outgoing request. * @param subscribe - Whether the stream should remain open for future server pushes. * * @returns A readable stream of events, with a new event getting submitted for every * server-side page render. */ const createStreamSource = async (url, body, subscribe) => { const headers = new Headers({ Accept: "text/plain", [CUSTOM_HEADERS.bundleId]: import.meta.env.__BLADE_BUNDLE_ID }); if (subscribe) headers.set(CUSTOM_HEADERS.subscribe, "1"); const response = await fetchRetry(url, { method: "POST", body, headers }); if (!response.ok) throw new Error(await response.text()); if (!response.body) throw new Error("Empty response body"); const reader = response.body.getReader(); if (subscribe) { SUBSCRIPTIONS.forEach((subscription) => { subscription.cancel(); subscription.releaseLock(); }); SUBSCRIPTIONS = []; SUBSCRIPTIONS.push(reader); } const listeners = /* @__PURE__ */ new Map(); const decoder = new TextDecoder(); const parser = createParser({ onEvent: (event) => dispatchStreamEvent(event.event, event.data, event.id) }); const dispatchStreamEvent = (type, data, id) => { return (listeners.get(type) || []).forEach((cb) => cb({ data, id })); }; const startReading = async () => { try { while (true) { const { value, done } = await reader.read(); if (done) break; if (subscribe && reader !== SUBSCRIPTIONS.at(-1)) { reader.cancel(); break; } const decoded = decoder.decode(value); parser.feed(decoded); } } catch (err) { if (!(err instanceof TypeError && /load failed/i.test(err.message))) throw err; } finally { reader.releaseLock(); } }; return { addEventListener: (type, callback) => { if (!listeners.has(type)) listeners.set(type, []); listeners.get(type).push(callback); }, subscribed: subscribe, startReading }; }; let BLADE_ROOT = null; /** * Receives a React node and renders it at the root of the page. * * @param content - The React node to render. * * @returns Nothing. */ const renderRoot = (content) => { if (BLADE_ROOT) { BLADE_ROOT.render(content); return; } BLADE_ROOT = hydrateRoot(document, content, { onRecoverableError(error, errorInfo) { console.error("Hydration error occurred:", error, errorInfo); } }); }; /** * Resolves a new page from the server-side of Blade. * * @param path - The path of the page. * @param subscribe - Whether to subscribe to subsequent updates from the server. * @param options - Additional options for how to resolve the page. * * @returns A promise that resolves to a page if `subscribe` is `false`. If it is `true`, * the promise will never resolve. Instead, the function will render the subsequent * updates directly. */ const fetchPage = async (path, subscribe, options) => { const body = new FormData(); if (options && Object.keys(options).length > 0) { body.append("options", JSON.stringify(omit(options, ["files"]))); if (options.files) for (const [identifier, value] of options.files.entries()) body.append("files", value, identifier); } const url = new URL(path, location.origin); if (url.hash) body.append("hash", url.hash); const stream = await createStreamSource(url.pathname + url.search, body, subscribe); return new Promise((resolve) => { stream.addEventListener("update", async (event) => { const content = await createFromReadableStream(new Blob([event.data]).stream()); if (stream.subscribed) return renderRoot(content); resolve(content); }); stream.addEventListener("update-bundle", (event) => { mountNewBundle(event.id.split("-").pop(), event.data); }); stream.startReading(); }); }; const mountNewBundle = async (bundleId, newMarkup) => { await Promise.all([ loadResource(bundleId, "main-css"), loadResource(bundleId, "main-js"), loadResource(bundleId, "shared") ]); BLADE_ROOT?.unmount(); BLADE_ROOT = null; const newDocument = new DOMParser().parseFromString(newMarkup, "text/html"); document.documentElement.innerHTML = newDocument.documentElement.innerHTML; [...Array.from(newDocument.documentElement.attributes)].forEach(({ name, value }) => { document.documentElement.setAttribute(name, value); }); for (const oldScript of Array.from(document.querySelectorAll("script.blade-script"))) { const newScript = document.createElement("script"); for (const attr of Array.from(oldScript.attributes)) newScript.setAttribute(attr.name, attr.value); document.head.appendChild(newScript); oldScript.remove(); } console.debug("Updated client bundles"); }; //#endregion //#region private/universal/context.ts /** * Picks the properties of the available server context that can be serialized and passed * on to the client-side. * * @param serverContext - A server context object. * * @returns All server context that can be passed to the client-side. */ const getSerializableContext = (serverContext) => ({ url: serverContext.url, params: serverContext.params, userAgent: serverContext.userAgent, geoLocation: serverContext.geoLocation, addressBarInSync: serverContext.addressBarInSync, languages: serverContext.languages, collected: { queries: serverContext.collected.queries.filter((details) => details.type === "write") } }); //#endregion //#region private/universal/hooks.ts const useUniversalContext = () => { const isNetlify = typeof Netlify !== "undefined"; if (typeof window === "undefined" || isNetlify) { const serverContext = useContext(RootServerContext); if (!serverContext) throw new Error("Missing server context in `useUniversalContext`"); return getSerializableContext(serverContext); } const clientContext = useContext(RootClientContext); if (!clientContext) throw new Error("Missing client context in `useUniversalContext`"); const { deferredPromises,...universalContext } = clientContext; return universalContext; }; const usePrivateLocation = () => { const url = new URL(useUniversalContext().url); const isNetlify = typeof Netlify !== "undefined"; if (typeof window === "undefined" || isNetlify) return url; return url; }; //#endregion //#region public/universal/hooks.ts const useParams = () => useUniversalContext().params; const useLocation = () => { const newLocation = usePrivateLocation(); newLocation.searchParams.delete("page"); newLocation.hash = ""; return newLocation; }; const useNavigator = () => { const universalContext = useUniversalContext(); return { userAgent: universalContext.userAgent, geoLocation: universalContext.geoLocation, languages: universalContext.languages }; }; const usePopulatePathname = () => { const params = useParams(); return (pathname, extraParams) => { return populatePathSegments(pathname, Object.assign({}, params, extraParams)); }; }; const useRedirect = () => { const populatePathname = usePopulatePathname(); const isNetlify = typeof Netlify !== "undefined"; if (typeof window === "undefined" || isNetlify) return (pathname, options) => { throw { __blade_redirect: populatePathname(pathname, options?.extraParams) }; }; const { transitionPage } = usePageTransition(); return (pathname, options) => { transitionPage(populatePathname(pathname, options?.extraParams), { immediatelyUpdateQueryParams: options?.immediatelyUpdateQueryParams }); }; }; const useCookie = (name) => { const isNetlify = typeof Netlify !== "undefined"; if (typeof window === "undefined" || isNetlify) { const serverContext = useContext(RootServerContext); if (!serverContext) throw new Error("Missing server context in `useCookie`"); const { cookies } = serverContext; const value$1 = cookies[name]; const setValue$1 = (value$2, options) => { return getCookieSetter(serverContext)(name, value$2, options); }; return [value$1, setValue$1]; } const value = document.cookie.match(`(^|;)\\s*${name}=([^;]*)`)?.pop() || null; const setValue = (value$1, options) => { const shouldDelete = value$1 === null; const encodedValue = shouldDelete ? "" : encodeURIComponent(value$1); const components = [`${encodeURIComponent(name)}=${encodedValue}`]; if (shouldDelete) components.push("expires=Thu, 01 Jan 1970 00:00:00 GMT"); else components.push(`max-age=${DEFAULT_COOKIE_MAX_AGE}`); const path = options?.path || "/"; components.push(`path=${path}`); document.cookie = components.join("; "); }; return [value, setValue]; }; //#endregion //#region private/client/hooks.ts const usePageTransition = () => { const cache = useRef(/* @__PURE__ */ new Map()); const clientContext = useContext(RootClientContext); if (!clientContext) throw new Error("Missing client context in `usePageTransition`"); const privateLocationRef = usePrivateLocationRef(); const getCacheEntry = (path) => { const cacheEntry = cache.current.get(path); const maxAge = Date.now() - 1e4; return cacheEntry && cacheEntry.time > maxAge ? cacheEntry : null; }; const primePageCache = (path) => { if (getCacheEntry(path)) return; const promise = fetchPage(path, false); cache.current.set(path, { body: promise, time: Date.now() }); }; const transitionPage = (path, options) => { const privateLocation = privateLocationRef.current; if (options?.acceptCache && !IS_CLIENT_DEV) getCacheEntry(path)?.body.then((page) => renderRoot(page)); if (options?.immediatelyUpdateQueryParams) { const url = new URL(path, privateLocation.origin); history.replaceState(history.state, "", url); clientContext.setClientQueryParams(url.search); } if (!window.navigator.onLine) throw new Error("Device is not online to perform manual page transition"); fetchPage(path, true, omit(options || {}, ["acceptCache", "immediatelyUpdateQueryParams"])); }; return { primePageCache, transitionPage }; }; /** * A React reference acting as a replacement for `window.location`. Due to being a * reference, the value is always up-to-date, even inside a callback. */ const usePrivateLocationRef = () => { const privateLocation = usePrivateLocation(); privateLocation.pathname = usePopulatePathname()(privateLocation.pathname); const ref = useRef(privateLocation); useEffect(() => { ref.current = privateLocation; }, [privateLocation, privateLocation.href]); return ref; }; /** * Behaves exactly like `useMemo`, except that the provided factory receives the * previously memorized value as an argument, which allows for accumulating state, just * like the `.reduce` method on an array would. * * Furthermore, in addition to returning the computed value (like `useMemo`), the hook * also returns a function for updating it from the outside. * * @param factory - A function for computing a value. * @param deps - A list of dependencies. If one of them changes, the factory function * will be called and the memorized value will be re-computed. * * @returns The computed value, and a function for setting it. */ const useReduce = (rootFactory, deps) => { const value = useRef(rootFactory(void 0)); const [renderingCount, setRenderingCount] = useState(0); const setValue = (childFactory) => { value.current = childFactory(value.current); setRenderingCount(renderingCount + 1); }; useMemo(() => { value.current = rootFactory(value.current); }, deps); return [value.current, setValue]; }; //#endregion export { RootClientContext as _, useLocation as a, usePopulatePathname as c, useUniversalContext as d, getSerializableContext as f, construct as g, assign as h, useCookie as i, useRedirect as l, FORM_TARGET_PREFIX as m, usePrivateLocationRef as n, useNavigator as o, fetchPage as p, useReduce as r, useParams as s, usePageTransition as t, usePrivateLocation as u };