diff options
| author | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
|---|---|---|
| committer | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
| commit | bce557cc2dc767628bed6aac87301a1be7c5431b (patch) | |
| tree | b51a051228d01fe3306cd7626d4a96768aadb944 /node_modules/@jet-app/app-store/tmp/src/common/util/generate-routes.js | |
init commit
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/common/util/generate-routes.js')
| -rw-r--r-- | node_modules/@jet-app/app-store/tmp/src/common/util/generate-routes.js | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/util/generate-routes.js b/node_modules/@jet-app/app-store/tmp/src/common/util/generate-routes.js new file mode 100644 index 0000000..f370dbe --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/common/util/generate-routes.js @@ -0,0 +1,183 @@ +import { isSome } from "@jet/environment"; +import { URL } from "@jet/environment/util/urls"; +import { Path, Parameters } from "../../foundation/network/url-constants"; +import { normalizeLocale, deriveLocaleForUrl } from "../locale"; +/** + * Generate the routes property for a `RouteProvider` and a canonical URL factory + * function for a {@link RoutableIntent} (non-detail page). + * + * `makeIntent` should be a function that accepts a single parameter, + * `options: Omit<YourIntent, '$kind'>`, and returns the intent. + * + * `path` is the static part of the path. For example, in `'/us/browse?l=es'`, + * `'/browse'` is the static part of the path. Path can contain multiple + * segments like `'/foo/bar'`, but it does not support parameters like + * `'/foo/{id}'`. + * + * `expectedQueryParameters` are the names of the query parameters for the routes + * that also double as properties on the intent. This instructs the intent + * builder on which properties to provide to `makeIntent`. For example, + * `SearchResultsPageIntent`'s `RouteProvider` recognizes a `'term'` query param, which + * is then stored in the intent as `{ term: 'valueFromParam' }`. For that case, + * this array should be `['term']`. Put another way, this should be a list of the + * non-optional (non-nullable, etc.) string properties on the intent: + * + * ```typescript + * interface SearchResultsPageIntent extends RouteableIntent { + * $kind: 'SearchResultsPageIntent'; // not this one + * term: string; // this one should be included! + * facet: Opt<String>; // but it doesn't include this; it's optional + * } + * ``` + * + * NOTE: If you receive a type error like `Type 'string' is not assignable + * to type 'never'` for this argument, it means that your array of parameters + * doesn't match the intent definition. In the above example, if we added a + * `originalTerm: string` to the intent, then passing ['term'] would fail, + * because 'originalTerm' is missing. Adding it does mean, however, that you + * expect users to be able to pass it as a query parameter + * (ex. /search?term=foo&originalTerm=bar). If that's unintended, you may want + * to consider making this property Opt<> (and also maybe ?). + * + * Usage Example: For a contrived intent `FooIntent`, + * + * ```typescript + * interface FooIntent extends RouteableIntent { + * $kind: 'FooIntent'; + * bar: string; + * } + * ``` + * + * If we expect users to be able to route to this intent via `/us/foo?bar=baz` + * or `/foo?bar=baz&l=es`, then in the IntentController's file: + * + * ```typescript + * const { + * routes, + * makeCanonicalUrl, + * } = generateRoutes(makeFooIntent, '/foo', ['bar']); + * ``` + * + * And then, `routes` can be used in the `RouteProvider`: + * + * ```typescript + * export const FooIntentController: RouteProvider<FooIntent> & ... = { + * routes, + * + * // ... + * }; + * ``` + * + * And then in perform, when returning a `Page`, append the `canonicalURL`: + * + * ```typescript + * async perform(intent: FooIntent, objectGraph: ObjectGraph): Promise<Page> { + * // ... + * return { + * ...page, + * canonicalURL: makeCanonicalUrl(intent), + * }; + * }, + * ``` + */ +export function generateRoutes(makeIntent, path, requiredQueryParameters = [], extras) { + const ruleExtras = { exclusions: extras === null || extras === void 0 ? void 0 : extras.exclusions }; + const queryParameters = requiredQueryParameters.slice(); + if (extras === null || extras === void 0 ? void 0 : extras.optionalQuery) { + queryParameters.push(...extras.optionalQuery.map((p) => `${p}?`)); + } + return { + routes(objectGraph) { + var _a; + return [ + { + rules: [ + { + ...ruleExtras, + path, + query: [`${Parameters.language}?`, ...queryParameters], + }, + { + ...ruleExtras, + path: `/{${Path.storeFront}}${path}`, + query: [`${Parameters.language}?`, ...queryParameters], + }, + ...((_a = extras === null || extras === void 0 ? void 0 : extras.extraRules) !== null && _a !== void 0 ? _a : []), + ], + handler(_, parameters) { + const { [Path.storeFront]: storefront, [Parameters.language]: language, ...rest } = parameters; + const typedRestParams = rest; + return makeIntent({ + ...typedRestParams, + ...normalizeLocale(objectGraph, { + storefront, + language, + }), + }); + }, + }, + ]; + }, + makeCanonicalUrl(objectGraph, intent) { + const url = buildBaseUrl(objectGraph, path, intent); + // Replace dynamic segments with `Intent` properties + const pathComponents = url.pathComponents(); + for (const [i, pathComponent] of pathComponents.entries()) { + if (pathComponent.startsWith("{") && pathComponent.endsWith("}")) { + const variableInPathComponent = pathComponent.substring(1, pathComponent.length - 1); + pathComponents[i] = intent[variableInPathComponent]; + } + } + url.set("pathname", "/" + pathComponents.join("/")); + // Set query params + const sortedQueryParameters = requiredQueryParameters.slice(); + // Sorted for consistent order + sortedQueryParameters.sort((a, b) => a.localeCompare(b)); + for (const queryParameter of sortedQueryParameters) { + if (!(queryParameter in intent)) { + throw new Error(`expected queryParmeters to contain: ${queryParameter}`); + } + const value = intent[queryParameter]; + // NOTE: TypeScript is unfortunately not able to see that + // parameters[queryParameter] will always be string. We prove + // this in the type. queryParameters is required to contain the + // string names of all keys on I with a string value (see + // RequiredIntentOptions<I>). This escape hatch is preferable + // within this function as it means that the function is + // externally typesafe. If a user forgets to pass in params, + // there will be a type error. + url.param(queryParameter, value); + } + if (extras === null || extras === void 0 ? void 0 : extras.optionalQuery) { + const optionalParams = extras.optionalQuery.slice(); + optionalParams.sort((a, b) => a.localeCompare(b)); + optionalParams.forEach((query) => { + const value = intent[query]; + if (value) { + url.param(query, value); + } + }); + } + return url.toString(); + }, + }; +} +/** + * Builds the basis for a "canonical URL" given a {@linkcode path} and {@linkcode locale} + * + * The locale data will be encoded into the resulting {@linkcode URL}; other "dynamic segments" + * within {@linkcode path} are expected to be replaced separately + */ +function buildBaseUrl(objectGraph, path, locale) { + const url = new URL("https://apps.apple.com"); + const { storefront, language } = deriveLocaleForUrl(objectGraph, locale); + url.path(storefront); + // Actual path (ex. browse in https://apps.apple.com/us/browse) + // substr(1), because URL.path doesn't expect a leading slash + url.path(path.substr(1)); + if (isSome(language)) { + url.param("l", language); + } + return url; +} +//# sourceMappingURL=generate-routes.js.map
\ No newline at end of file |
