@nimpl/getters 1.3.0

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/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ The MIT License (MIT)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # @nimpl/getters
2
+
3
+ Implementation of server getters and server contexts in React Server Components without switching to SSR
4
+
5
+ Before using the library, read the [Possible Issues](https://nimpl.tech/getters/possible-issues)
6
+
7
+ ## Installation
8
+
9
+ **Using npm:**
10
+ ```bash
11
+ npm i @nimpl/getters
12
+ ```
13
+
14
+ **Using yarn:**
15
+ ```bash
16
+ yarn add @nimpl/getters
17
+ ```
18
+
19
+ ## Current Getters
20
+
21
+ * [get-pathname](https://nimpl.tech/getters/current-getters/get-pathname)
22
+ * [server-contexts](https://nimpl.tech/getters/current-getters/server-contexts)
23
+ * [get-page-config](https://nimpl.tech/getters/current-getters/get-page-config)
24
+ * [get-params](https://nimpl.tech/getters/current-getters/get-params)
25
+ * [get-search-params](https://nimpl.tech/getters/current-getters/get-search-params)
26
+
27
+ ## Stability
28
+
29
+ All getters are covered with tests. Tests are run on every release and every 6 hours on the latest **Canary** version of `Next.js`.
30
+
31
+ In this way, you can be sure not only of the stability of the code, but also that if there is a breaking change in `Next.js`, this will immediately become known. *Even before the release of a stable version of `Next.js`.*
32
+
33
+ ## Examples
34
+
35
+ You can see examples in the [directory](https://github.com/vordgi/nimpl-getters/tree/main/examples) of the repository.
36
+
37
+ ## Additional
38
+
39
+ Please consider giving a star if you like it, it will help promote the implementation in the eyes of the next.js team.
40
+
41
+ Create tasks for identified issues, desired getters, or various improvements.
@@ -0,0 +1,3 @@
1
+ import type { ServerContext } from './types';
2
+ declare function createServerContext<T>(defaultValue?: T): ServerContext<T>;
3
+ export default createServerContext;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const async_hooks_1 = require("async_hooks");
7
+ const server_getter_in_client_component_error_1 = require("./server-getter-in-client-component-error");
8
+ const react_1 = __importDefault(require("react"));
9
+ const Enter = ({ storage, value }) => {
10
+ storage.enterWith({ value });
11
+ return react_1.default.createElement(react_1.default.Fragment, null);
12
+ };
13
+ function createServerContext(defaultValue) {
14
+ (0, server_getter_in_client_component_error_1.serverGetterInClientComponentError)('createServerContext');
15
+ const storage = new async_hooks_1.AsyncLocalStorage();
16
+ return {
17
+ Provider: async ({ children, value }) => {
18
+ return (react_1.default.createElement(react_1.default.Fragment, null,
19
+ react_1.default.createElement(Enter, { storage: storage, value: value }),
20
+ children));
21
+ },
22
+ Consumer: ({ children }) => {
23
+ const store = storage.getStore();
24
+ return children(store ? store.value : defaultValue);
25
+ },
26
+ _storage: storage,
27
+ _defaultValue: defaultValue,
28
+ };
29
+ }
30
+ exports.default = createServerContext;
@@ -0,0 +1,11 @@
1
+ export declare const getPageConfig: () => {
2
+ pagePath?: undefined;
3
+ dynamic?: undefined;
4
+ revalidate?: undefined;
5
+ basePath?: undefined;
6
+ } | {
7
+ pagePath: string | undefined;
8
+ dynamic: string;
9
+ revalidate: import("next/dist/server/lib/revalidate").Revalidate | undefined;
10
+ basePath: string;
11
+ };
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getPageConfig = void 0;
4
+ const static_generation_async_storage_external_1 = require("next/dist/client/components/static-generation-async-storage.external");
5
+ const server_getter_in_client_component_error_1 = require("./server-getter-in-client-component-error");
6
+ const getPageConfig = () => {
7
+ (0, server_getter_in_client_component_error_1.serverGetterInClientComponentError)('getPageConfig');
8
+ const store = static_generation_async_storage_external_1.staticGenerationAsyncStorage.getStore();
9
+ if (!store)
10
+ return {};
11
+ const basePath = process.env.__NEXT_ROUTER_BASEPATH || '';
12
+ const { pagePath, forceDynamic, forceStatic, dynamicShouldError, revalidate } = store || {};
13
+ let dynamic = 'auto';
14
+ if (forceDynamic) {
15
+ dynamic = 'force-dynamic';
16
+ }
17
+ else if (forceStatic) {
18
+ dynamic = 'force-static';
19
+ }
20
+ else if (dynamicShouldError) {
21
+ dynamic = 'error';
22
+ }
23
+ return { pagePath, dynamic, revalidate, basePath };
24
+ };
25
+ exports.getPageConfig = getPageConfig;
@@ -0,0 +1,3 @@
1
+ export declare const getParams: () => {
2
+ [key: string]: string | string[];
3
+ };
package/get-params.js ADDED
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getParams = void 0;
4
+ const static_generation_async_storage_external_1 = require("next/dist/client/components/static-generation-async-storage.external");
5
+ const utils_1 = require("./utils");
6
+ const server_getter_in_client_component_error_1 = require("./server-getter-in-client-component-error");
7
+ const getParams = () => {
8
+ (0, server_getter_in_client_component_error_1.serverGetterInClientComponentError)('getParams');
9
+ const store = static_generation_async_storage_external_1.staticGenerationAsyncStorage.getStore();
10
+ if (!store)
11
+ return {};
12
+ const { urlPathname, pagePath = '/' } = store;
13
+ const cleanUrlPathname = (0, utils_1.normalizePathname)(urlPathname);
14
+ const cleanPagePath = (0, utils_1.normalizePagePath)(pagePath);
15
+ const pagePathParts = cleanPagePath.split('/').slice(1).filter(part => !part.match(/^(\([^)]+\)|\@.+)$/));
16
+ const pagePathInterceptedParts = (0, utils_1.normalizeInterceptingRoutes)(pagePathParts);
17
+ const pathnameParts = cleanUrlPathname.split('/').slice(1);
18
+ const isRootPage = cleanUrlPathname === '' && cleanPagePath === '';
19
+ const isNotFoundPage = pagePath.match(/\/_not-found\/?$/);
20
+ const isValidCatchALl = cleanPagePath.match(/\/\[\.\.\.[^\]]+\]/) && pathnameParts.length >= pagePathInterceptedParts.length;
21
+ const isValidOptionalCatchALl = cleanPagePath.match(/\/\[\[\.\.\.[^\]]+\]\]/) && pathnameParts.length >= pagePathInterceptedParts.length - 1;
22
+ const isCorrectMatched = isRootPage || isNotFoundPage || pagePathInterceptedParts.length === pathnameParts.length || isValidCatchALl || isValidOptionalCatchALl;
23
+ if (!isCorrectMatched) {
24
+ const createIssueUrl = new URL('https://github.com/vordgi/nimpl-getters/issues/new');
25
+ createIssueUrl.searchParams.set('title', 'Error parsing segments in get-params');
26
+ createIssueUrl.searchParams.set('body', `urlPathname: \`${urlPathname}\`;\n\npagePath: \`${pagePath}\`;`);
27
+ createIssueUrl.searchParams.append('labels', 'bug');
28
+ throw new Error(`Something went wrong. Please create an issue on Github: ${createIssueUrl}`);
29
+ }
30
+ const query = (0, utils_1.parseSegments)(pagePathInterceptedParts, pathnameParts);
31
+ return query;
32
+ };
33
+ exports.getParams = getParams;
@@ -0,0 +1 @@
1
+ export declare function getPathname(): string | null;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getPathname = void 0;
4
+ const static_generation_async_storage_external_1 = require("next/dist/client/components/static-generation-async-storage.external");
5
+ const has_base_path_1 = require("next/dist/client/has-base-path");
6
+ const remove_base_path_1 = require("next/dist/client/remove-base-path");
7
+ const server_getter_in_client_component_error_1 = require("./server-getter-in-client-component-error");
8
+ function getPathname() {
9
+ (0, server_getter_in_client_component_error_1.serverGetterInClientComponentError)('getPathname');
10
+ const store = static_generation_async_storage_external_1.staticGenerationAsyncStorage.getStore();
11
+ if (!store)
12
+ return null;
13
+ const { urlPathname } = store;
14
+ const url = new URL(urlPathname, 'http://n');
15
+ const pathname = (0, has_base_path_1.hasBasePath)(url.pathname)
16
+ ? (0, remove_base_path_1.removeBasePath)(url.pathname)
17
+ : url.pathname;
18
+ return pathname;
19
+ }
20
+ exports.getPathname = getPathname;
@@ -0,0 +1,5 @@
1
+ import { ReadonlyURLSearchParams } from 'next/navigation';
2
+ /** @deprecated getSearchParams is deprecated. [Read more](https://nimpl.tech/getters/current-getters/get-search-params) */
3
+ export declare function getSearchParams(opts?: {
4
+ ignoreDynamicOptionErrors?: boolean;
5
+ }): URLSearchParams | ReadonlyURLSearchParams;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getSearchParams = void 0;
4
+ const static_generation_async_storage_external_1 = require("next/dist/client/components/static-generation-async-storage.external");
5
+ const server_getter_in_client_component_error_1 = require("./server-getter-in-client-component-error");
6
+ const navigation_1 = require("next/navigation");
7
+ const internal_utils_1 = require("next/dist/server/internal-utils");
8
+ /** @deprecated getSearchParams is deprecated. [Read more](https://nimpl.tech/getters/current-getters/get-search-params) */
9
+ function getSearchParams(opts) {
10
+ (0, server_getter_in_client_component_error_1.serverGetterInClientComponentError)('getSearchParams');
11
+ console.error('getSearchParams is deprecated. Read more - https://nimpl.tech/getters/current-getters/get-search-params');
12
+ const store = static_generation_async_storage_external_1.staticGenerationAsyncStorage.getStore();
13
+ if (!store)
14
+ return new URLSearchParams();
15
+ const { urlPathname, forceStatic, forceDynamic, dynamicShouldError } = store;
16
+ if (!opts?.ignoreDynamicOptionErrors) {
17
+ if (forceStatic) {
18
+ throw new Error('Сannot get client search parameters with dynamic=force-static setting');
19
+ }
20
+ else if (dynamicShouldError) {
21
+ throw new Error('Сannot get client search parameters with dynamic=error setting');
22
+ }
23
+ else if (!forceDynamic) {
24
+ console.warn('Do not use getSearchParams with unselected dynamic setting, use force-dynamic instead');
25
+ }
26
+ }
27
+ const url = new URL(urlPathname, 'http://n');
28
+ const strippedUrl = (0, internal_utils_1.stripInternalSearchParams)(url, true);
29
+ const readonlySearchParams = new navigation_1.ReadonlyURLSearchParams(strippedUrl.searchParams);
30
+ return readonlySearchParams;
31
+ }
32
+ exports.getSearchParams = getSearchParams;
@@ -0,0 +1,3 @@
1
+ import type { ServerContext } from './types';
2
+ declare function getServerContext<T>({ _storage, _defaultValue }: ServerContext<T>): T | undefined;
3
+ export default getServerContext;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const server_getter_in_client_component_error_1 = require("./server-getter-in-client-component-error");
4
+ function getServerContext({ _storage, _defaultValue }) {
5
+ (0, server_getter_in_client_component_error_1.serverGetterInClientComponentError)('getServerContext');
6
+ const store = _storage.getStore();
7
+ if (!store)
8
+ return _defaultValue;
9
+ return store.value;
10
+ }
11
+ exports.default = getServerContext;
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@nimpl/getters",
3
+ "version": "1.3.0",
4
+ "description": "Implementation of server getters and server contexts in React Server Components without switching to SSR",
5
+ "files": [
6
+ "**/*.js",
7
+ "**/*.d.ts"
8
+ ],
9
+ "scripts": {
10
+ "build": "tsc"
11
+ },
12
+ "keywords": [
13
+ "next",
14
+ "next.js",
15
+ "server components",
16
+ "react.js",
17
+ "get-pathname",
18
+ "getters",
19
+ "implementation",
20
+ "get-params",
21
+ "rsc",
22
+ "server context"
23
+ ],
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git://github.com/vordgi/nimpl-getters.git"
27
+ },
28
+ "author": {
29
+ "name": "Savelyev Alexander",
30
+ "email": "vordgi1@gmail.com",
31
+ "url": "https://github.com/vordgi/"
32
+ },
33
+ "license": "MIT",
34
+ "devDependencies": {
35
+ "@types/node": "20.0.0",
36
+ "@types/react": "18.2.43",
37
+ "react": "18.2.0",
38
+ "react-dom": "18.2.0",
39
+ "next": "14.0.4",
40
+ "typescript": "5.3.2"
41
+ },
42
+ "peerDependencies": {
43
+ "react": ">= 18.2.0",
44
+ "react-dom": ">= 18.2.0",
45
+ "next": ">= 14.0.0"
46
+ }
47
+ }
@@ -0,0 +1 @@
1
+ export declare function serverGetterInClientComponentError(getterName: string): void | never;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.serverGetterInClientComponentError = void 0;
7
+ const react_1 = __importDefault(require("react"));
8
+ function serverGetterInClientComponentError(getterName) {
9
+ if (process.env.NODE_ENV !== 'production') {
10
+ // If useState is defined we're in a client component
11
+ if (Boolean(react_1.default.useState)) {
12
+ throw new Error(`${getterName} only works in Server Components`);
13
+ }
14
+ }
15
+ }
16
+ exports.serverGetterInClientComponentError = serverGetterInClientComponentError;
package/types.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ /// <reference types="react" />
2
+ /// <reference types="node" />
3
+ import type { AsyncLocalStorage } from "async_hooks";
4
+ export type ServerContext<T> = {
5
+ Provider: ({ children, value }: {
6
+ children: React.ReactNode;
7
+ value: T;
8
+ }) => React.ReactNode;
9
+ Consumer: ({ children }: {
10
+ children: (context: T | undefined) => React.ReactNode;
11
+ }) => React.ReactNode;
12
+ _storage: AsyncLocalStorage<{
13
+ value: T;
14
+ }>;
15
+ _defaultValue: T | undefined;
16
+ };
package/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/utils.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ export declare const normalizePathname: (pathname: string) => string;
2
+ export declare const normalizePagePath: (pagePath: string) => string;
3
+ export declare const parseSegments: (pagePathParts: string[], pathnameParts: string[]) => {
4
+ [key: string]: string | string[];
5
+ };
6
+ export declare const normalizeInterceptingRoutes: (pageParts: string[]) => string[];
package/utils.js ADDED
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeInterceptingRoutes = exports.parseSegments = exports.normalizePagePath = exports.normalizePathname = void 0;
4
+ const normalizePathname = (pathname) => {
5
+ const cleanPathname = pathname && new URL(pathname, 'http://n').pathname;
6
+ const pathnameWithoutTrailingSlash = cleanPathname?.replace(/^(\/.*)\/$/, '$1');
7
+ const pathnameWithoutFileType = pathnameWithoutTrailingSlash?.replace(/\/_not-found$/, '');
8
+ return pathnameWithoutFileType || '/';
9
+ };
10
+ exports.normalizePathname = normalizePathname;
11
+ const normalizePagePath = (pagePath) => {
12
+ const cleanPagePath = pagePath && new URL(pagePath, 'http://n').pathname;
13
+ const pagePathWithoutFileType = cleanPagePath?.replace(/(\/page|\/_not-found)$/, '');
14
+ return pagePathWithoutFileType || '/';
15
+ };
16
+ exports.normalizePagePath = normalizePagePath;
17
+ const parseSegments = (pagePathParts, pathnameParts) => {
18
+ const query = pagePathParts.reduce((acc, cur, index) => {
19
+ const optionalCatchAllSegment = cur.match(/^\[\[\.\.\.([^\]]+)\]\]$/);
20
+ if (optionalCatchAllSegment) {
21
+ const key = optionalCatchAllSegment[1];
22
+ const segmentParts = pathnameParts.slice(index);
23
+ if (segmentParts.length) {
24
+ acc[key] = segmentParts;
25
+ }
26
+ return acc;
27
+ }
28
+ const catchAllSegment = cur.match(/^\[\.\.\.([^\]]+)\]$/);
29
+ if (catchAllSegment) {
30
+ const key = catchAllSegment[1];
31
+ acc[key] = pathnameParts.slice(index);
32
+ return acc;
33
+ }
34
+ const dynamicSegment = cur.match(/^\[([^\]]+)\]$/);
35
+ if (dynamicSegment) {
36
+ const key = dynamicSegment[1];
37
+ acc[key] = pathnameParts[index];
38
+ return acc;
39
+ }
40
+ return acc;
41
+ }, {});
42
+ return query;
43
+ };
44
+ exports.parseSegments = parseSegments;
45
+ const normalizeInterceptingRoutes = (pageParts) => {
46
+ let skip = 0;
47
+ const normilizedParts = [];
48
+ for (const pagepart of [...pageParts].reverse()) {
49
+ if (skip) {
50
+ skip -= 1;
51
+ continue;
52
+ }
53
+ if (pagepart.startsWith('(...)')) {
54
+ normilizedParts.push(pagepart.replace(/^\(\.\.\.\)/, ''));
55
+ break;
56
+ }
57
+ else if (pagepart.startsWith('(.)')) {
58
+ normilizedParts.push(pagepart.replace(/^\(\.\)/, ''));
59
+ }
60
+ else if (pagepart.startsWith('(..)')) {
61
+ const skipLeafs = pagepart.match(/\(\.\.\)/g);
62
+ skip += skipLeafs?.length || 0;
63
+ normilizedParts.push(pagepart.replace(/^(\(\.\.\))+/, ''));
64
+ }
65
+ else {
66
+ normilizedParts.push(pagepart);
67
+ }
68
+ }
69
+ return normilizedParts.reverse();
70
+ };
71
+ exports.normalizeInterceptingRoutes = normalizeInterceptingRoutes;