@cedarjs/web 7.0.1-next.314 → 7.0.1-next.466

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.
@@ -1487,7 +1487,7 @@ var global_default = (
1487
1487
  );
1488
1488
 
1489
1489
  // ../../node_modules/@apollo/client/version.js
1490
- var version = "4.3.1";
1490
+ var version = "4.3.3";
1491
1491
 
1492
1492
  // ../../node_modules/@apollo/client/utilities/internal/makeUniqueId.js
1493
1493
  var prefixCounts = /* @__PURE__ */ new Map();
@@ -0,0 +1,49 @@
1
+ export interface UseNewVersionAvailableOptions {
2
+ /**
3
+ * How often to check for a new version, in milliseconds.
4
+ *
5
+ * @default 60000
6
+ */
7
+ intervalMs?: number;
8
+ }
9
+ /**
10
+ * Detects that the server is serving a newer build of the app than the one
11
+ * running in the current tab, so you can ask the user to reload.
12
+ *
13
+ * The running build is identified by the `<script type="module" src="...">`
14
+ * elements in the current document, which Vite gives content-hashed file
15
+ * names. The hook periodically fetches the URL the tab was loaded from (and
16
+ * also checks when the tab becomes visible again or the browser comes back
17
+ * online) and compares the module scripts in the response with the running
18
+ * ones. The request is sent without cookies, so hosts with skew protection
19
+ * (like Netlify) answer with the latest deploy. Once a new version is
20
+ * detected the hook returns `true` and stops checking.
21
+ *
22
+ * The hook always returns `false` during server rendering, in development
23
+ * and when the current document has no module scripts.
24
+ *
25
+ * Components using the hook with the same options share a single polling
26
+ * loop.
27
+ *
28
+ * @example
29
+ * ```jsx
30
+ * import { useNewVersionAvailable } from '@cedarjs/web'
31
+ *
32
+ * const NewVersionBanner = () => {
33
+ * const newVersionAvailable = useNewVersionAvailable()
34
+ *
35
+ * if (!newVersionAvailable) {
36
+ * return null
37
+ * }
38
+ *
39
+ * return (
40
+ * <div role="status">
41
+ * A new version of the app is available.
42
+ * <button onClick={() => window.location.reload()}>Reload</button>
43
+ * </div>
44
+ * )
45
+ * }
46
+ * ```
47
+ */
48
+ export declare function useNewVersionAvailable(options?: UseNewVersionAvailableOptions): boolean;
49
+ //# sourceMappingURL=useNewVersionAvailable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useNewVersionAvailable.d.ts","sourceRoot":"","sources":["../../src/components/useNewVersionAvailable.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,6BAA6B;IAC5C;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAwPD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,6BAAkC,GAC1C,OAAO,CAST"}
@@ -0,0 +1,169 @@
1
+ import { useSyncExternalStore } from "react";
2
+ const DEFAULT_INTERVAL_MS = 6e4;
3
+ const CHECK_TIMEOUT_MS = 3e4;
4
+ const initialDocumentUrl = typeof document === "undefined" ? void 0 : document.URL;
5
+ let runningModuleScripts;
6
+ const checkers = /* @__PURE__ */ new Map();
7
+ function getModuleScriptPathnames(doc, baseUrl) {
8
+ const pathnames = /* @__PURE__ */ new Set();
9
+ doc.querySelectorAll('script[type="module"][src]').forEach((script) => {
10
+ const src = script.getAttribute("src");
11
+ if (!src) {
12
+ return;
13
+ }
14
+ try {
15
+ pathnames.add(new URL(src, baseUrl).pathname);
16
+ } catch {
17
+ }
18
+ });
19
+ return pathnames;
20
+ }
21
+ function setsAreEqual(a, b) {
22
+ if (a.size !== b.size) {
23
+ return false;
24
+ }
25
+ for (const value of a) {
26
+ if (!b.has(value)) {
27
+ return false;
28
+ }
29
+ }
30
+ return true;
31
+ }
32
+ function createVersionChecker(url, intervalMs, runningScripts, lifecycle) {
33
+ const listeners = /* @__PURE__ */ new Set();
34
+ let newVersionAvailable = false;
35
+ let checkInFlight = false;
36
+ let intervalId;
37
+ const notify = () => {
38
+ listeners.forEach((listener) => listener());
39
+ };
40
+ const check = async () => {
41
+ if (newVersionAvailable || checkInFlight) {
42
+ return;
43
+ }
44
+ checkInFlight = true;
45
+ const controller = new AbortController();
46
+ const timeoutId = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS);
47
+ try {
48
+ const requestUrl = new URL(url);
49
+ requestUrl.hash = "";
50
+ const response = await fetch(requestUrl.href, {
51
+ cache: "no-store",
52
+ credentials: "omit",
53
+ signal: controller.signal
54
+ });
55
+ if (response.redirected) {
56
+ return;
57
+ }
58
+ const html = await response.text();
59
+ const fetchedDocument = new DOMParser().parseFromString(html, "text/html");
60
+ const fetchedScripts = getModuleScriptPathnames(
61
+ fetchedDocument,
62
+ response.url || requestUrl.href
63
+ );
64
+ if (fetchedScripts.size === 0) {
65
+ return;
66
+ }
67
+ if (!setsAreEqual(runningScripts, fetchedScripts)) {
68
+ newVersionAvailable = true;
69
+ stop();
70
+ notify();
71
+ }
72
+ } catch {
73
+ } finally {
74
+ clearTimeout(timeoutId);
75
+ checkInFlight = false;
76
+ }
77
+ };
78
+ const onVisibilityChange = () => {
79
+ if (document.visibilityState === "visible") {
80
+ void check();
81
+ }
82
+ };
83
+ const onOnline = () => {
84
+ void check();
85
+ };
86
+ function start() {
87
+ intervalId = setInterval(() => void check(), intervalMs);
88
+ document.addEventListener("visibilitychange", onVisibilityChange);
89
+ window.addEventListener("online", onOnline);
90
+ }
91
+ function stop() {
92
+ if (intervalId !== void 0) {
93
+ clearInterval(intervalId);
94
+ intervalId = void 0;
95
+ }
96
+ document.removeEventListener("visibilitychange", onVisibilityChange);
97
+ window.removeEventListener("online", onOnline);
98
+ }
99
+ return {
100
+ subscribe: (listener) => {
101
+ listeners.add(listener);
102
+ if (listeners.size === 1) {
103
+ lifecycle.onActive();
104
+ if (!newVersionAvailable) {
105
+ start();
106
+ }
107
+ }
108
+ return () => {
109
+ listeners.delete(listener);
110
+ if (listeners.size === 0) {
111
+ stop();
112
+ lifecycle.onIdle();
113
+ }
114
+ };
115
+ },
116
+ getSnapshot: () => newVersionAvailable
117
+ };
118
+ }
119
+ function getVersionChecker(intervalMs) {
120
+ if (typeof window === "undefined" || !initialDocumentUrl) {
121
+ return void 0;
122
+ }
123
+ if (process.env.NODE_ENV === "development") {
124
+ return void 0;
125
+ }
126
+ runningModuleScripts ??= getModuleScriptPathnames(document, document.baseURI);
127
+ if (runningModuleScripts.size === 0) {
128
+ return void 0;
129
+ }
130
+ const existingChecker = checkers.get(intervalMs);
131
+ if (existingChecker) {
132
+ return existingChecker;
133
+ }
134
+ const checker = createVersionChecker(
135
+ initialDocumentUrl,
136
+ intervalMs,
137
+ runningModuleScripts,
138
+ {
139
+ onActive: () => {
140
+ checkers.set(intervalMs, checker);
141
+ },
142
+ // A checker that found a new version is kept, so components mounting
143
+ // later see `true` right away. Idle checkers are dropped, so intervals
144
+ // that are no longer used don't accumulate
145
+ onIdle: () => {
146
+ if (!checker.getSnapshot()) {
147
+ checkers.delete(intervalMs);
148
+ }
149
+ }
150
+ }
151
+ );
152
+ checkers.set(intervalMs, checker);
153
+ return checker;
154
+ }
155
+ const subscribeNoop = () => () => {
156
+ };
157
+ const getFalse = () => false;
158
+ function useNewVersionAvailable(options = {}) {
159
+ const intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;
160
+ const checker = getVersionChecker(intervalMs);
161
+ return useSyncExternalStore(
162
+ checker?.subscribe ?? subscribeNoop,
163
+ checker?.getSnapshot ?? getFalse,
164
+ getFalse
165
+ );
166
+ }
167
+ export {
168
+ useNewVersionAvailable
169
+ };
package/dist/index.d.ts CHANGED
@@ -3,6 +3,8 @@ import './apollo/typeOverrides.js';
3
3
  import './config.js';
4
4
  export { FatalErrorBoundary } from './components/FatalErrorBoundary.js';
5
5
  export { FetchConfigProvider, useFetchConfig, } from './components/FetchConfigProvider.js';
6
+ export { useNewVersionAvailable } from './components/useNewVersionAvailable.js';
7
+ export type { UseNewVersionAvailableOptions } from './components/useNewVersionAvailable.js';
6
8
  export { useQuery, useMutation, useSubscription } from '@apollo/client/react';
7
9
  export { useFragment } from './apollo/fragmentRegistry.js';
8
10
  export type { FragmentHookOptions, FragmentHookResult, } from './apollo/fragmentRegistry.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,8BAA8B,CAAA;AACrC,OAAO,2BAA2B,CAAA;AAClC,OAAO,aAAa,CAAA;AAEpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAA;AAEvE,OAAO,EACL,mBAAmB,EACnB,cAAc,GACf,MAAM,qCAAqC,CAAA;AAC5C,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC7E,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC1D,YAAY,EACV,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,8BAA8B,CAAA;AAErC,cAAc,uCAAuC,CAAA;AAErD,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAA;AAE5E,YAAY,EACV,SAAS,EACT,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,sBAAsB,EACtB,qBAAqB,EACrB,UAAU,GACX,MAAM,gCAAgC,CAAA;AAEvC,cAAc,cAAc,CAAA;AAE5B,cAAc,+BAA+B,CAAA;AAE7C,cAAc,0BAA0B,CAAA;AACxC,cAAc,0BAA0B,CAAA;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAE,MAAM,IAAI,IAAI,EAAE,MAAM,EAAE,CAAA;AAEjC,cAAc,0BAA0B,CAAA;AACxC,cAAc,uBAAuB,CAAA;AAErC,cAAc,8BAA8B,CAAA;AAE5C,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,8BAA8B,CAAA;AACrC,OAAO,2BAA2B,CAAA;AAClC,OAAO,aAAa,CAAA;AAEpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAA;AAEvE,OAAO,EACL,mBAAmB,EACnB,cAAc,GACf,MAAM,qCAAqC,CAAA;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAA;AAC/E,YAAY,EAAE,6BAA6B,EAAE,MAAM,wCAAwC,CAAA;AAC3F,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC7E,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC1D,YAAY,EACV,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,8BAA8B,CAAA;AAErC,cAAc,uCAAuC,CAAA;AAErD,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAA;AAE5E,YAAY,EACV,SAAS,EACT,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,sBAAsB,EACtB,qBAAqB,EACrB,UAAU,GACX,MAAM,gCAAgC,CAAA;AAEvC,cAAc,cAAc,CAAA;AAE5B,cAAc,+BAA+B,CAAA;AAE7C,cAAc,0BAA0B,CAAA;AACxC,cAAc,0BAA0B,CAAA;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAE,MAAM,IAAI,IAAI,EAAE,MAAM,EAAE,CAAA;AAEjC,cAAc,0BAA0B,CAAA;AACxC,cAAc,uBAAuB,CAAA;AAErC,cAAc,8BAA8B,CAAA;AAE5C,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA"}
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  FetchConfigProvider,
7
7
  useFetchConfig
8
8
  } from "./components/FetchConfigProvider.js";
9
+ import { useNewVersionAvailable } from "./components/useNewVersionAvailable.js";
9
10
  import { useQuery, useMutation, useSubscription } from "@apollo/client/react";
10
11
  import { useFragment } from "./apollo/fragmentRegistry.js";
11
12
  export * from "./components/cell/CellCacheContext.js";
@@ -19,6 +20,8 @@ import { Helmet } from "@dr.pogodin/react-helmet";
19
20
  export * from "./components/htmlTags.js";
20
21
  export * from "./routeHooks.types.js";
21
22
  export * from "./components/ServerInject.js";
23
+ import { registerPreloadErrorReload } from "./reloadOnPreloadError.js";
24
+ registerPreloadErrorReload();
22
25
  export {
23
26
  FatalErrorBoundary,
24
27
  FetchConfigProvider,
@@ -29,6 +32,7 @@ export {
29
32
  useFetchConfig,
30
33
  useFragment,
31
34
  useMutation,
35
+ useNewVersionAvailable,
32
36
  useQuery,
33
37
  useSubscription
34
38
  };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * `sessionStorage` key that marks the next page load as one triggered by the
3
+ * `vite:preloadError` handler
4
+ */
5
+ export declare const PRELOAD_ERROR_RELOAD_KEY = "cedar:preload-error-reload";
6
+ /**
7
+ * On a page that the handler itself reloaded, a preload error within this many
8
+ * milliseconds of the page starting to load is left for the app to handle. A
9
+ * chunk that is missing even on the latest deploy then surfaces as an error
10
+ * instead of causing a reload loop, no matter how long each attempt takes to
11
+ * fail. After this long, the page is treated like any other, so a later deploy
12
+ * can reload it again.
13
+ */
14
+ export declare const RELOADED_PAGE_GRACE_MS = 60000;
15
+ /**
16
+ * Handles Vite's `vite:preloadError` event, which fires when a dynamically
17
+ * imported chunk fails to load. After a new deploy, a tab running the
18
+ * previous build requests hashed chunks that no longer exist on the server.
19
+ * Reloading the page fetches the latest build so the navigation can succeed.
20
+ *
21
+ * The handler does nothing if the event was already handled
22
+ * (`defaultPrevented`), if this page is the result of the handler's own
23
+ * reload and started loading less than `RELOADED_PAGE_GRACE_MS` ago, or if
24
+ * `sessionStorage` is unavailable (without it, a reload loop can't be ruled
25
+ * out). In those cases Vite rethrows the error and it surfaces through the
26
+ * app's normal error handling.
27
+ *
28
+ * See https://vite.dev/guide/build#load-error-handling
29
+ */
30
+ export declare function handlePreloadError(event: Event): void;
31
+ /**
32
+ * Registers `handlePreloadError` as a `vite:preloadError` listener on
33
+ * `window`. Runs at most once per page load, and does nothing outside the
34
+ * browser.
35
+ */
36
+ export declare function registerPreloadErrorReload(): void;
37
+ //# sourceMappingURL=reloadOnPreloadError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reloadOnPreloadError.d.ts","sourceRoot":"","sources":["../src/reloadOnPreloadError.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,wBAAwB,+BAA+B,CAAA;AAEpE;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,QAAS,CAAA;AA2B5C;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,KAAK,QAkB9C;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,SAYzC"}
@@ -0,0 +1,49 @@
1
+ const PRELOAD_ERROR_RELOAD_KEY = "cedar:preload-error-reload";
2
+ const RELOADED_PAGE_GRACE_MS = 6e4;
3
+ let pageWasReloadedForPreloadError = false;
4
+ function consumeReloadMarker() {
5
+ try {
6
+ pageWasReloadedForPreloadError = window.sessionStorage.getItem(PRELOAD_ERROR_RELOAD_KEY) !== null;
7
+ window.sessionStorage.removeItem(PRELOAD_ERROR_RELOAD_KEY);
8
+ } catch {
9
+ pageWasReloadedForPreloadError = false;
10
+ }
11
+ }
12
+ function setReloadMarker() {
13
+ try {
14
+ window.sessionStorage.setItem(PRELOAD_ERROR_RELOAD_KEY, "1");
15
+ return true;
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
20
+ function handlePreloadError(event) {
21
+ if (event.defaultPrevented) {
22
+ return;
23
+ }
24
+ if (pageWasReloadedForPreloadError && performance.now() < RELOADED_PAGE_GRACE_MS) {
25
+ return;
26
+ }
27
+ if (!setReloadMarker()) {
28
+ return;
29
+ }
30
+ event.preventDefault();
31
+ window.location.reload();
32
+ }
33
+ function registerPreloadErrorReload() {
34
+ if (typeof window === "undefined") {
35
+ return;
36
+ }
37
+ if (globalThis.__CEDAR__PRELOAD_ERROR_RELOAD_REGISTERED) {
38
+ return;
39
+ }
40
+ globalThis.__CEDAR__PRELOAD_ERROR_RELOAD_REGISTERED = true;
41
+ consumeReloadMarker();
42
+ window.addEventListener("vite:preloadError", handlePreloadError);
43
+ }
44
+ export {
45
+ PRELOAD_ERROR_RELOAD_KEY,
46
+ RELOADED_PAGE_GRACE_MS,
47
+ handlePreloadError,
48
+ registerPreloadErrorReload
49
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cedarjs/web",
3
- "version": "7.0.1-next.314",
3
+ "version": "7.0.1-next.466",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/cedarjs/cedar.git",
@@ -78,14 +78,14 @@
78
78
  "test:watch": "vitest watch"
79
79
  },
80
80
  "dependencies": {
81
- "@apollo/client": "4.3.1",
82
- "@cedarjs/server-store": "7.0.1-next.314",
81
+ "@apollo/client": "4.3.3",
82
+ "@cedarjs/server-store": "7.0.1-next.466",
83
83
  "@dr.pogodin/react-helmet": "2.0.4",
84
- "@whatwg-node/fetch": "0.12.0",
84
+ "@whatwg-node/fetch": "0.12.1",
85
85
  "apollo-upload-client": "19.0.0",
86
86
  "cookie": "1.1.1",
87
87
  "graphql": "16.14.2",
88
- "graphql-sse": "2.6.1",
88
+ "graphql-sse": "2.6.2",
89
89
  "graphql-tag": "2.12.7",
90
90
  "react-hot-toast": "2.6.1",
91
91
  "rxjs": "7.8.2",
@@ -95,18 +95,18 @@
95
95
  "devDependencies": {
96
96
  "@apollo/client-react-streaming": "0.14.5",
97
97
  "@arethetypeswrong/cli": "0.18.5",
98
- "@cedarjs/auth": "7.0.1-next.314",
99
- "@cedarjs/framework-tools": "7.0.1-next.314",
100
- "@cedarjs/internal": "7.0.1-next.314",
98
+ "@cedarjs/auth": "7.0.1-next.466",
99
+ "@cedarjs/framework-tools": "7.0.1-next.466",
100
+ "@cedarjs/internal": "7.0.1-next.466",
101
101
  "@testing-library/dom": "10.4.2",
102
102
  "@testing-library/jest-dom": "6.9.1",
103
103
  "@testing-library/react": "16.3.3",
104
104
  "@types/apollo-upload-client": "^19",
105
105
  "@types/react": "^19.2.0",
106
106
  "@types/react-dom": "^19.2.0",
107
- "concurrently": "9.2.4",
107
+ "concurrently": "9.2.5",
108
108
  "nodemon": "3.1.14",
109
- "publint": "0.3.24",
109
+ "publint": "0.3.25",
110
110
  "react": "19.3.0",
111
111
  "react-dom": "19.3.0",
112
112
  "tstyche": "5.0.2",
@@ -114,7 +114,7 @@
114
114
  "vitest": "4.1.11"
115
115
  },
116
116
  "peerDependencies": {
117
- "@cedarjs/auth": "7.0.1-next.314",
117
+ "@cedarjs/auth": "7.0.1-next.466",
118
118
  "react": "19.3.0",
119
119
  "react-dom": "19.3.0"
120
120
  },