@joshroy/relay-nextjs 3.1.0 → 3.1.2
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/app.d.ts +9 -0
- package/app.js +43 -0
- package/component.d.ts +40 -0
- package/component.js +203 -0
- package/index.d.ts +2 -0
- package/index.js +5 -0
- package/json_meta.d.ts +25 -0
- package/json_meta.js +92 -0
- package/package.json +1 -1
package/app.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Environment } from 'react-relay';
|
|
2
|
+
import type { AnyPreloadedQuery, UseRelayNextJsProps } from './component';
|
|
3
|
+
export declare function useRelayNextjs(props: UseRelayNextJsProps, opts: {
|
|
4
|
+
createClientEnvironment: () => Environment;
|
|
5
|
+
}): {
|
|
6
|
+
env: Environment;
|
|
7
|
+
preloadedQuery?: AnyPreloadedQuery;
|
|
8
|
+
CSN: boolean;
|
|
9
|
+
};
|
package/app.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useRelayNextjs = useRelayNextjs;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const react_relay_1 = require("react-relay");
|
|
6
|
+
const json_meta_1 = require("./json_meta");
|
|
7
|
+
function useRelayNextjs(props, opts) {
|
|
8
|
+
const hydratedRef = (0, react_1.useRef)(false);
|
|
9
|
+
const [relayEnvironment] = (0, react_1.useState)(() => {
|
|
10
|
+
var _a;
|
|
11
|
+
if ((_a = props.preloadedQuery) === null || _a === void 0 ? void 0 : _a.environment) {
|
|
12
|
+
return props.preloadedQuery.environment;
|
|
13
|
+
}
|
|
14
|
+
const env = opts.createClientEnvironment();
|
|
15
|
+
if (!hydratedRef.current &&
|
|
16
|
+
props.payload &&
|
|
17
|
+
props.payloadMeta &&
|
|
18
|
+
props.operationDescriptor) {
|
|
19
|
+
hydratedRef.current = true;
|
|
20
|
+
(0, json_meta_1.hydrateObject)(props.payloadMeta, props.payload);
|
|
21
|
+
env.commitPayload(props.operationDescriptor, props.payload.data);
|
|
22
|
+
}
|
|
23
|
+
return env;
|
|
24
|
+
});
|
|
25
|
+
const preloadedQuery = (0, react_1.useMemo)(() => {
|
|
26
|
+
if (props.preloadedQuery) {
|
|
27
|
+
return props.preloadedQuery;
|
|
28
|
+
}
|
|
29
|
+
else if (props.operationDescriptor) {
|
|
30
|
+
return (0, react_relay_1.loadQuery)(relayEnvironment, props.operationDescriptor.request.node, props.operationDescriptor.request.variables, { fetchPolicy: 'store-or-network' });
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}, [props.preloadedQuery, props.operationDescriptor, relayEnvironment]);
|
|
34
|
+
const prevQueryRef = (0, react_1.useRef)(undefined);
|
|
35
|
+
(0, react_1.useEffect)(() => {
|
|
36
|
+
const prev = prevQueryRef.current;
|
|
37
|
+
prevQueryRef.current = preloadedQuery;
|
|
38
|
+
if (prev && prev !== preloadedQuery) {
|
|
39
|
+
prev.dispose();
|
|
40
|
+
}
|
|
41
|
+
}, [preloadedQuery]);
|
|
42
|
+
return { env: relayEnvironment, preloadedQuery, CSN: props.CSN };
|
|
43
|
+
}
|
package/component.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { NextPageContext, Redirect } from 'next';
|
|
2
|
+
import { NextRouter } from 'next/router';
|
|
3
|
+
import { ComponentType, ReactNode } from 'react';
|
|
4
|
+
import { LoadQueryOptions, PreloadedQuery } from 'react-relay';
|
|
5
|
+
import { Environment, GraphQLResponse, GraphQLTaggedNode, OperationDescriptor, OperationType } from 'relay-runtime';
|
|
6
|
+
import { HydrationMeta } from './json_meta';
|
|
7
|
+
export type AnyPreloadedQuery = PreloadedQuery<OperationType>;
|
|
8
|
+
export type RelayProps<P extends {} = {}, Q extends OperationType = OperationType> = P & Required<Pick<UseRelayNextJsProps<P, Q>, 'CSN' | 'preloadedQuery'>>;
|
|
9
|
+
export type UseRelayNextJsProps<P extends {} = {}, Q extends OperationType = OperationType> = P & {
|
|
10
|
+
/** If this page rendering resulted from a client-side navigation. */
|
|
11
|
+
CSN: boolean;
|
|
12
|
+
/** Undefined during initial hydration, but defined for SSR and subsequent renders */
|
|
13
|
+
preloadedQuery?: PreloadedQuery<Q>;
|
|
14
|
+
operationDescriptor?: OperationDescriptor;
|
|
15
|
+
payload?: GraphQLResponse;
|
|
16
|
+
payloadMeta?: HydrationMeta;
|
|
17
|
+
};
|
|
18
|
+
export type OrRedirect<T> = T | {
|
|
19
|
+
redirect: Redirect;
|
|
20
|
+
};
|
|
21
|
+
export interface RelayOptions<Props extends RelayProps, ServerSideProps extends {} = {}> {
|
|
22
|
+
/** Fallback rendered when the page suspends. */
|
|
23
|
+
fallback?: ReactNode;
|
|
24
|
+
variablesFromContext?: (ctx: NextPageContext | NextRouter) => Props['preloadedQuery']['variables'];
|
|
25
|
+
queryOptionsFromContext?: (ctx: NextPageContext | NextRouter) => LoadQueryOptions;
|
|
26
|
+
/** Called when creating a Relay environment on the client. Should be idempotent. */
|
|
27
|
+
createClientEnvironment: () => Environment;
|
|
28
|
+
/** Props passed to the component when rendering on the client. */
|
|
29
|
+
clientSideProps?: (ctx: NextPageContext) => OrRedirect<Partial<ServerSideProps>>;
|
|
30
|
+
/** Called when creating a Relay environment on the server. */
|
|
31
|
+
createServerEnvironment: (ctx: NextPageContext, props: ServerSideProps) => Promise<Environment>;
|
|
32
|
+
/** Props passed to the component when rendering on the server. */
|
|
33
|
+
serverSideProps?: (ctx: NextPageContext) => Promise<OrRedirect<ServerSideProps>>;
|
|
34
|
+
/** Runs after a query has been executed on the server. */
|
|
35
|
+
serverSidePostQuery?: (queryResult: GraphQLResponse | undefined, ctx: NextPageContext) => Promise<unknown> | unknown;
|
|
36
|
+
}
|
|
37
|
+
export declare function withRelay<Props extends RelayProps, ServerSideProps extends {}>(Component: ComponentType<Props>, query: GraphQLTaggedNode, opts: RelayOptions<Props, ServerSideProps>): {
|
|
38
|
+
(props: Props): import("react/jsx-runtime").JSX.Element;
|
|
39
|
+
getInitialProps: (ctx: NextPageContext) => Promise<UseRelayNextJsProps>;
|
|
40
|
+
};
|
package/component.js
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
45
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
46
|
+
};
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
exports.withRelay = withRelay;
|
|
49
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
50
|
+
const lodash_isequal_1 = __importDefault(require("lodash.isequal"));
|
|
51
|
+
const router_1 = __importStar(require("next/router"));
|
|
52
|
+
const react_1 = require("react");
|
|
53
|
+
const react_relay_1 = require("react-relay");
|
|
54
|
+
const relay_runtime_1 = require("relay-runtime");
|
|
55
|
+
const json_meta_1 = require("./json_meta");
|
|
56
|
+
// Enabling this feature flag to determine if a page should 404 on the server.
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
58
|
+
relay_runtime_1.RelayFeatureFlags.ENABLE_REQUIRED_DIRECTIVES = true;
|
|
59
|
+
function defaultVariablesFromContext(ctx) {
|
|
60
|
+
return ctx.query;
|
|
61
|
+
}
|
|
62
|
+
function defaultQueryOptionsFromContext(_ctx) {
|
|
63
|
+
return { fetchPolicy: 'store-or-network' };
|
|
64
|
+
}
|
|
65
|
+
function useStableIdentity(nextValue) {
|
|
66
|
+
const lastValue = (0, react_1.useRef)(nextValue);
|
|
67
|
+
return (0, react_1.useMemo)(() => {
|
|
68
|
+
if (!(0, lodash_isequal_1.default)(lastValue.current, nextValue)) {
|
|
69
|
+
lastValue.current = nextValue;
|
|
70
|
+
}
|
|
71
|
+
return lastValue.current;
|
|
72
|
+
}, [nextValue]);
|
|
73
|
+
}
|
|
74
|
+
function withRelay(Component, query, opts) {
|
|
75
|
+
const { queryOptionsFromContext = defaultQueryOptionsFromContext, variablesFromContext = defaultVariablesFromContext, } = opts;
|
|
76
|
+
function useLoadedQuery(initialPreloadedQuery) {
|
|
77
|
+
const router = (0, router_1.useRouter)();
|
|
78
|
+
const queryOptions = useStableIdentity((0, react_1.useMemo)(() => queryOptionsFromContext(router), [router]));
|
|
79
|
+
const queryVariables = useStableIdentity((0, react_1.useMemo)(() => variablesFromContext(router), [router]));
|
|
80
|
+
const [preloadedQuery, setPreloadedQuery] = (0, react_1.useState)(initialPreloadedQuery);
|
|
81
|
+
const prevInitialRef = (0, react_1.useRef)(initialPreloadedQuery);
|
|
82
|
+
(0, react_1.useEffect)(() => {
|
|
83
|
+
if (initialPreloadedQuery &&
|
|
84
|
+
initialPreloadedQuery !== prevInitialRef.current) {
|
|
85
|
+
prevInitialRef.current = initialPreloadedQuery;
|
|
86
|
+
setPreloadedQuery((prev) => {
|
|
87
|
+
if (prev && prev !== initialPreloadedQuery) {
|
|
88
|
+
prev.dispose();
|
|
89
|
+
}
|
|
90
|
+
return initialPreloadedQuery;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}, [initialPreloadedQuery]);
|
|
94
|
+
const isMountedRef = (0, react_1.useRef)(false);
|
|
95
|
+
const env = (0, react_relay_1.useRelayEnvironment)();
|
|
96
|
+
(0, react_1.useEffect)(() => {
|
|
97
|
+
if (!isMountedRef.current) {
|
|
98
|
+
isMountedRef.current = true;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const nextPreloadedQuery = (0, react_relay_1.loadQuery)(env, query, queryVariables, queryOptions);
|
|
102
|
+
setPreloadedQuery((prev) => {
|
|
103
|
+
if (prev) {
|
|
104
|
+
prev.dispose();
|
|
105
|
+
}
|
|
106
|
+
return nextPreloadedQuery;
|
|
107
|
+
});
|
|
108
|
+
return () => nextPreloadedQuery.dispose();
|
|
109
|
+
}, [env, queryVariables, queryOptions]);
|
|
110
|
+
return preloadedQuery;
|
|
111
|
+
}
|
|
112
|
+
function RelayComponent(props) {
|
|
113
|
+
var _a;
|
|
114
|
+
const preloadedQuery = useLoadedQuery(props.preloadedQuery);
|
|
115
|
+
return ((0, jsx_runtime_1.jsx)(react_1.Suspense, { fallback: (_a = opts.fallback) !== null && _a !== void 0 ? _a : 'Loading...', children: (0, jsx_runtime_1.jsx)(Component, Object.assign({}, props, { preloadedQuery: preloadedQuery })) }));
|
|
116
|
+
}
|
|
117
|
+
RelayComponent.getInitialProps = relayInitialProps(query, opts);
|
|
118
|
+
return RelayComponent;
|
|
119
|
+
}
|
|
120
|
+
function relayInitialProps(query, opts) {
|
|
121
|
+
return (ctx) => __awaiter(this, void 0, void 0, function* () {
|
|
122
|
+
if (typeof window === 'undefined') {
|
|
123
|
+
return getServerInitialProps(ctx, query, opts);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
return getClientInitialProps(ctx, query, opts);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function getServerInitialProps(ctx, query, opts) {
|
|
131
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
132
|
+
var _a;
|
|
133
|
+
const { variablesFromContext = defaultVariablesFromContext, queryOptionsFromContext = defaultQueryOptionsFromContext, } = opts;
|
|
134
|
+
const serverSideProps = opts.serverSideProps
|
|
135
|
+
? yield opts.serverSideProps(ctx)
|
|
136
|
+
: {};
|
|
137
|
+
if ('redirect' in serverSideProps) {
|
|
138
|
+
const { redirect } = serverSideProps;
|
|
139
|
+
let statusCode = 302;
|
|
140
|
+
if ('statusCode' in redirect) {
|
|
141
|
+
statusCode = redirect.statusCode;
|
|
142
|
+
}
|
|
143
|
+
else if ('permanent' in redirect) {
|
|
144
|
+
statusCode = redirect.permanent ? 308 : 307;
|
|
145
|
+
}
|
|
146
|
+
ctx
|
|
147
|
+
.res.writeHead(statusCode, {
|
|
148
|
+
Location: redirect.destination,
|
|
149
|
+
})
|
|
150
|
+
.end();
|
|
151
|
+
return { CSN: false };
|
|
152
|
+
}
|
|
153
|
+
const env = yield opts.createServerEnvironment(ctx, serverSideProps);
|
|
154
|
+
const variables = variablesFromContext(ctx);
|
|
155
|
+
const queryOptions = queryOptionsFromContext(ctx);
|
|
156
|
+
const preloadedQuery = (0, react_relay_1.loadQuery)(env, query, variables, queryOptions);
|
|
157
|
+
const payload = yield ensureQueryFlushed(preloadedQuery);
|
|
158
|
+
yield ((_a = opts.serverSidePostQuery) === null || _a === void 0 ? void 0 : _a.call(opts, payload, ctx));
|
|
159
|
+
const payloadSerializationMetadata = (0, json_meta_1.collectMeta)(payload);
|
|
160
|
+
const request = query;
|
|
161
|
+
const operationDescriptor = (0, relay_runtime_1.createOperationDescriptor)('default' in request ? request.default : request, variables);
|
|
162
|
+
const props = Object.assign(Object.assign({}, serverSideProps), { CSN: false, operationDescriptor,
|
|
163
|
+
payload, payloadMeta: payloadSerializationMetadata });
|
|
164
|
+
// This will only be available during SSR, not during client side render or hydration.
|
|
165
|
+
Object.defineProperty(props, 'preloadedQuery', {
|
|
166
|
+
enumerable: false,
|
|
167
|
+
value: preloadedQuery,
|
|
168
|
+
});
|
|
169
|
+
return props;
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
function getClientInitialProps(ctx, query, opts) {
|
|
173
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
174
|
+
const { variablesFromContext = defaultVariablesFromContext, queryOptionsFromContext = defaultQueryOptionsFromContext, } = opts;
|
|
175
|
+
const clientSideProps = opts.clientSideProps
|
|
176
|
+
? opts.clientSideProps(ctx)
|
|
177
|
+
: {};
|
|
178
|
+
if ('redirect' in clientSideProps) {
|
|
179
|
+
void router_1.default.push(clientSideProps.redirect.destination);
|
|
180
|
+
return { CSN: true };
|
|
181
|
+
}
|
|
182
|
+
const env = opts.createClientEnvironment();
|
|
183
|
+
const variables = variablesFromContext(ctx);
|
|
184
|
+
const queryOptions = queryOptionsFromContext(ctx);
|
|
185
|
+
const preloadedQuery = (0, react_relay_1.loadQuery)(env, query, variables, queryOptions);
|
|
186
|
+
return Object.assign(Object.assign({}, clientSideProps), { CSN: true, preloadedQuery });
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
function ensureQueryFlushed(query) {
|
|
190
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
191
|
+
return new Promise((resolve, reject) => {
|
|
192
|
+
if (query.source == null) {
|
|
193
|
+
resolve({ data: {} });
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
query.source.subscribe({
|
|
197
|
+
next: resolve,
|
|
198
|
+
error: reject,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
}
|
package/index.d.ts
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withRelay = void 0;
|
|
4
|
+
var component_1 = require("./component");
|
|
5
|
+
Object.defineProperty(exports, "withRelay", { enumerable: true, get: function () { return component_1.withRelay; } });
|
package/json_meta.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Functions for extracting and applying serialization metadata
|
|
3
|
+
* to JSON. When a JS object is serialized to JSON it will call the `toJSON`
|
|
4
|
+
* and `toString` methods on objects. When deserializing the original types of
|
|
5
|
+
* those values are lost. This library exports `collectMeta` which returns a
|
|
6
|
+
* JSON-serializable representation of how the original object will be
|
|
7
|
+
* serialized. This can be passed to `hydrateObject` to restore the
|
|
8
|
+
* original object. Currently supports `Date` and `URL` values.
|
|
9
|
+
*/
|
|
10
|
+
/** Type of value that can be restored. */
|
|
11
|
+
export declare enum EncodedType {
|
|
12
|
+
DATE = "date",
|
|
13
|
+
URL = "url"
|
|
14
|
+
}
|
|
15
|
+
export type HydrationMeta = Record<string, EncodedType>;
|
|
16
|
+
/**
|
|
17
|
+
* Restores the type of values after deserializing a JSON object. Meta must be
|
|
18
|
+
* the object returned from `collectMeta`. Mutates the object in place.
|
|
19
|
+
*/
|
|
20
|
+
export declare function hydrateObject(meta: HydrationMeta, value: any): void;
|
|
21
|
+
/**
|
|
22
|
+
* Walks an object and extracts information for hydrating it after
|
|
23
|
+
* deserialization.
|
|
24
|
+
*/
|
|
25
|
+
export declare function collectMeta(obj: unknown): HydrationMeta;
|
package/json_meta.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Functions for extracting and applying serialization metadata
|
|
4
|
+
* to JSON. When a JS object is serialized to JSON it will call the `toJSON`
|
|
5
|
+
* and `toString` methods on objects. When deserializing the original types of
|
|
6
|
+
* those values are lost. This library exports `collectMeta` which returns a
|
|
7
|
+
* JSON-serializable representation of how the original object will be
|
|
8
|
+
* serialized. This can be passed to `hydrateObject` to restore the
|
|
9
|
+
* original object. Currently supports `Date` and `URL` values.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.EncodedType = void 0;
|
|
13
|
+
exports.hydrateObject = hydrateObject;
|
|
14
|
+
exports.collectMeta = collectMeta;
|
|
15
|
+
/** Type of value that can be restored. */
|
|
16
|
+
var EncodedType;
|
|
17
|
+
(function (EncodedType) {
|
|
18
|
+
EncodedType["DATE"] = "date";
|
|
19
|
+
EncodedType["URL"] = "url";
|
|
20
|
+
})(EncodedType || (exports.EncodedType = EncodedType = {}));
|
|
21
|
+
/**
|
|
22
|
+
* Restores the type of values after deserializing a JSON object. Meta must be
|
|
23
|
+
* the object returned from `collectMeta`. Mutates the object in place.
|
|
24
|
+
*/
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
|
+
function hydrateObject(meta, value) {
|
|
27
|
+
for (const [path, encoding] of Object.entries(meta)) {
|
|
28
|
+
let updatingValue = value;
|
|
29
|
+
const parts = path.split('.');
|
|
30
|
+
for (let i = 0; i < parts.length - 1; ++i) {
|
|
31
|
+
let accessor = Number(parts[i]);
|
|
32
|
+
if (Number.isNaN(accessor)) {
|
|
33
|
+
accessor = parts[i];
|
|
34
|
+
}
|
|
35
|
+
updatingValue = updatingValue[accessor];
|
|
36
|
+
}
|
|
37
|
+
let lastAccessor = Number(parts[parts.length - 1]);
|
|
38
|
+
if (Number.isNaN(lastAccessor)) {
|
|
39
|
+
lastAccessor = parts[parts.length - 1];
|
|
40
|
+
}
|
|
41
|
+
switch (encoding) {
|
|
42
|
+
case EncodedType.DATE:
|
|
43
|
+
updatingValue[lastAccessor] = new Date(updatingValue[lastAccessor]);
|
|
44
|
+
break;
|
|
45
|
+
case EncodedType.URL:
|
|
46
|
+
updatingValue[lastAccessor] = new URL(updatingValue[lastAccessor]);
|
|
47
|
+
break;
|
|
48
|
+
default:
|
|
49
|
+
checkExhaustive(encoding);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function* walk() {
|
|
54
|
+
const path = [];
|
|
55
|
+
do {
|
|
56
|
+
const { parent, key, value } = yield path.map(([key]) => key).join('.');
|
|
57
|
+
while (path.length > 0 && path[path.length - 1][1] !== parent) {
|
|
58
|
+
path.pop();
|
|
59
|
+
}
|
|
60
|
+
path.push([key, value]);
|
|
61
|
+
} while (path.length > 0);
|
|
62
|
+
}
|
|
63
|
+
function createReplacer() {
|
|
64
|
+
const meta = {};
|
|
65
|
+
const walker = walk();
|
|
66
|
+
function replacer(key, value) {
|
|
67
|
+
const path = walker.next({ parent: this, key, value }).value;
|
|
68
|
+
if (path && this[key] instanceof Date) {
|
|
69
|
+
meta[path] = EncodedType.DATE;
|
|
70
|
+
}
|
|
71
|
+
else if (path && this[key] instanceof URL) {
|
|
72
|
+
meta[path] = EncodedType.URL;
|
|
73
|
+
}
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
return [replacer, () => meta];
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Walks an object and extracts information for hydrating it after
|
|
80
|
+
* deserialization.
|
|
81
|
+
*/
|
|
82
|
+
function collectMeta(obj) {
|
|
83
|
+
const [replacer, dumpMeta] = createReplacer();
|
|
84
|
+
JSON.stringify(obj, replacer);
|
|
85
|
+
return dumpMeta();
|
|
86
|
+
}
|
|
87
|
+
function checkExhaustive(cased) {
|
|
88
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
89
|
+
throw new Error(`Unexpected condition: ${cased}`);
|
|
90
|
+
}
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|