@remix-run/router 1.2.1 → 1.3.0-pre.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/dist/utils.d.ts CHANGED
@@ -26,6 +26,8 @@ export interface SuccessResult {
26
26
  export interface DeferredResult {
27
27
  type: ResultType.deferred;
28
28
  deferredData: DeferredData;
29
+ statusCode?: number;
30
+ headers?: Headers;
29
31
  }
30
32
  /**
31
33
  * Redirect result from a loader or action
@@ -158,7 +160,7 @@ export declare type AgnosticDataNonIndexRouteObject = AgnosticNonIndexRouteObjec
158
160
  * A data route object, which is just a RouteObject with a required unique ID
159
161
  */
160
162
  export declare type AgnosticDataRouteObject = AgnosticDataIndexRouteObject | AgnosticDataNonIndexRouteObject;
161
- declare type _PathParam<Path extends string> = Path extends `${infer L}/${infer R}` ? _PathParam<L> | _PathParam<R> : Path extends `:${infer Param}` ? Param : never;
163
+ declare type _PathParam<Path extends string> = Path extends `${infer L}/${infer R}` ? _PathParam<L> | _PathParam<R> : Path extends `:${infer Param}` ? Param extends `${infer Optional}?` ? Optional : Param : never;
162
164
  /**
163
165
  * Examples:
164
166
  * "/a/b/*" -> "*"
@@ -214,7 +216,7 @@ export declare function matchRoutes<RouteObjectType extends AgnosticRouteObject
214
216
  * @see https://reactrouter.com/utils/generate-path
215
217
  */
216
218
  export declare function generatePath<Path extends string>(originalPath: Path, params?: {
217
- [key in PathParam<Path>]: string;
219
+ [key in PathParam<Path>]: string | null;
218
220
  }): string;
219
221
  /**
220
222
  * A PathPattern is used to match on some portion of a URL pathname.
@@ -340,22 +342,27 @@ export interface TrackedPromise extends Promise<any> {
340
342
  export declare class AbortedDeferredError extends Error {
341
343
  }
342
344
  export declare class DeferredData {
343
- private pendingKeys;
345
+ private pendingKeysSet;
344
346
  private controller;
345
347
  private abortPromise;
346
348
  private unlistenAbortSignal;
347
- private subscriber?;
349
+ private subscribers;
348
350
  data: Record<string, unknown>;
349
- constructor(data: Record<string, unknown>);
351
+ init?: ResponseInit;
352
+ deferredKeys: string[];
353
+ constructor(data: Record<string, unknown>, responseInit?: ResponseInit);
350
354
  private trackPromise;
351
355
  private onSettle;
352
- subscribe(fn: (aborted: boolean) => void): void;
356
+ private emit;
357
+ subscribe(fn: (aborted: boolean, settledKey?: string) => void): () => boolean;
353
358
  cancel(): void;
354
359
  resolveData(signal: AbortSignal): Promise<boolean>;
355
360
  get done(): boolean;
356
361
  get unwrappedData(): {};
362
+ get pendingKeys(): string[];
357
363
  }
358
- export declare function defer(data: Record<string, unknown>): DeferredData;
364
+ export declare type DeferFunction = (data: Record<string, unknown>, init?: number | ResponseInit) => DeferredData;
365
+ export declare const defer: DeferFunction;
359
366
  export declare type RedirectFunction = (url: string, init?: number | ResponseInit) => Response;
360
367
  /**
361
368
  * A redirect response. Sets the status code and the `Location` header.
package/history.ts CHANGED
@@ -125,6 +125,13 @@ export interface History {
125
125
  */
126
126
  createHref(to: To): string;
127
127
 
128
+ /**
129
+ * Returns a URL for the given `to` value
130
+ *
131
+ * @param to - The destination URL
132
+ */
133
+ createURL(to: To): URL;
134
+
128
135
  /**
129
136
  * Encode a location the same way window.history would do (no-op for memory
130
137
  * history) so we ensure our PUSH/REPLACE navigations for data routers
@@ -255,6 +262,10 @@ export function createMemoryHistory(
255
262
  return location;
256
263
  }
257
264
 
265
+ function createHref(to: To) {
266
+ return typeof to === "string" ? to : createPath(to);
267
+ }
268
+
258
269
  let history: MemoryHistory = {
259
270
  get index() {
260
271
  return index;
@@ -265,8 +276,9 @@ export function createMemoryHistory(
265
276
  get location() {
266
277
  return getCurrentLocation();
267
278
  },
268
- createHref(to) {
269
- return typeof to === "string" ? to : createPath(to);
279
+ createHref,
280
+ createURL(to) {
281
+ return new URL(createHref(to), "http://localhost");
270
282
  },
271
283
  encodeLocation(to: To) {
272
284
  let path = typeof to === "string" ? parsePath(to) : to;
@@ -558,24 +570,6 @@ export function parsePath(path: string): Partial<Path> {
558
570
  return parsedPath;
559
571
  }
560
572
 
561
- export function createClientSideURL(location: Location | string): URL {
562
- // window.location.origin is "null" (the literal string value) in Firefox
563
- // under certain conditions, notably when serving from a local HTML file
564
- // See https://bugzilla.mozilla.org/show_bug.cgi?id=878297
565
- let base =
566
- typeof window !== "undefined" &&
567
- typeof window.location !== "undefined" &&
568
- window.location.origin !== "null"
569
- ? window.location.origin
570
- : window.location.href;
571
- let href = typeof location === "string" ? location : createPath(location);
572
- invariant(
573
- base,
574
- `No window.location.(origin|href) available to create URL for href: ${href}`
575
- );
576
- return new URL(href, base);
577
- }
578
-
579
573
  export interface UrlHistory extends History {}
580
574
 
581
575
  export type UrlHistoryOptions = {
@@ -637,6 +631,23 @@ function getUrlBasedHistory(
637
631
  }
638
632
  }
639
633
 
634
+ function createURL(to: To): URL {
635
+ // window.location.origin is "null" (the literal string value) in Firefox
636
+ // under certain conditions, notably when serving from a local HTML file
637
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=878297
638
+ let base =
639
+ window.location.origin !== "null"
640
+ ? window.location.origin
641
+ : window.location.href;
642
+
643
+ let href = typeof to === "string" ? to : createPath(to);
644
+ invariant(
645
+ base,
646
+ `No window.location.(origin|href) available to create URL for href: ${href}`
647
+ );
648
+ return new URL(href, base);
649
+ }
650
+
640
651
  let history: History = {
641
652
  get action() {
642
653
  return action;
@@ -659,11 +670,10 @@ function getUrlBasedHistory(
659
670
  createHref(to) {
660
671
  return createHref(window, to);
661
672
  },
673
+ createURL,
662
674
  encodeLocation(to) {
663
675
  // Encode a Location the same way window.location would
664
- let url = createClientSideURL(
665
- typeof to === "string" ? to : createPath(to)
666
- );
676
+ let url = createURL(to);
667
677
  return {
668
678
  pathname: url.pathname,
669
679
  search: url.search,
package/index.ts CHANGED
@@ -1,5 +1,3 @@
1
- import { convertRoutesToDataRoutes, getPathContributingMatches } from "./utils";
2
-
3
1
  export type {
4
2
  ActionFunction,
5
3
  ActionFunctionArgs,
@@ -58,6 +56,7 @@ export type {
58
56
  Path,
59
57
  To,
60
58
  } from "./history";
59
+
61
60
  export {
62
61
  Action,
63
62
  createBrowserHistory,
@@ -79,6 +78,7 @@ export * from "./router";
79
78
 
80
79
  /** @internal */
81
80
  export {
81
+ DeferredData as UNSAFE_DeferredData,
82
82
  convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes,
83
83
  getPathContributingMatches as UNSAFE_getPathContributingMatches,
84
- };
84
+ } from "./utils";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remix-run/router",
3
- "version": "1.2.1",
3
+ "version": "1.3.0-pre.0",
4
4
  "description": "Nested/Data-driven/Framework-agnostic Routing",
5
5
  "keywords": [
6
6
  "remix",
package/router.ts CHANGED
@@ -3,7 +3,6 @@ import {
3
3
  Action as HistoryAction,
4
4
  createLocation,
5
5
  createPath,
6
- createClientSideURL,
7
6
  invariant,
8
7
  parsePath,
9
8
  } from "./history";
@@ -308,6 +307,7 @@ export interface StaticHandlerContext {
308
307
  statusCode: number;
309
308
  loaderHeaders: Record<string, Headers>;
310
309
  actionHeaders: Record<string, Headers>;
310
+ activeDeferreds: Record<string, DeferredData> | null;
311
311
  _deepestRenderedBoundaryId?: string | null;
312
312
  }
313
313
 
@@ -957,6 +957,7 @@ export function createRouter(init: RouterInit): Router {
957
957
  // Create a controller/Request for this navigation
958
958
  pendingNavigationController = new AbortController();
959
959
  let request = createClientSideRequest(
960
+ init.history,
960
961
  location,
961
962
  pendingNavigationController.signal,
962
963
  opts && opts.submission
@@ -1115,7 +1116,7 @@ export function createRouter(init: RouterInit): Router {
1115
1116
  }
1116
1117
 
1117
1118
  if (isDeferredResult(result)) {
1118
- throw new Error("defer() is not supported in actions");
1119
+ throw getInternalRouterError(400, { type: "defer-action" });
1119
1120
  }
1120
1121
 
1121
1122
  return {
@@ -1167,6 +1168,7 @@ export function createRouter(init: RouterInit): Router {
1167
1168
  : undefined;
1168
1169
 
1169
1170
  let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(
1171
+ init.history,
1170
1172
  state,
1171
1173
  matches,
1172
1174
  activeSubmission,
@@ -1380,6 +1382,7 @@ export function createRouter(init: RouterInit): Router {
1380
1382
  // Call the action for the fetcher
1381
1383
  let abortController = new AbortController();
1382
1384
  let fetchRequest = createClientSideRequest(
1385
+ init.history,
1383
1386
  path,
1384
1387
  abortController.signal,
1385
1388
  submission
@@ -1427,13 +1430,15 @@ export function createRouter(init: RouterInit): Router {
1427
1430
  }
1428
1431
 
1429
1432
  if (isDeferredResult(actionResult)) {
1430
- invariant(false, "defer() is not supported in actions");
1433
+ throw getInternalRouterError(400, { type: "defer-action" });
1431
1434
  }
1432
1435
 
1433
1436
  // Start the data load for current matches, or the next location if we're
1434
1437
  // in the middle of a navigation
1435
1438
  let nextLocation = state.navigation.location || state.location;
1436
1439
  let revalidationRequest = createClientSideRequest(
1440
+ init.history,
1441
+
1437
1442
  nextLocation,
1438
1443
  abortController.signal
1439
1444
  );
@@ -1456,6 +1461,7 @@ export function createRouter(init: RouterInit): Router {
1456
1461
  state.fetchers.set(key, loadFetcher);
1457
1462
 
1458
1463
  let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(
1464
+ init.history,
1459
1465
  state,
1460
1466
  matches,
1461
1467
  submission,
@@ -1599,7 +1605,11 @@ export function createRouter(init: RouterInit): Router {
1599
1605
 
1600
1606
  // Call the loader for this fetcher route match
1601
1607
  let abortController = new AbortController();
1602
- let fetchRequest = createClientSideRequest(path, abortController.signal);
1608
+ let fetchRequest = createClientSideRequest(
1609
+ init.history,
1610
+ path,
1611
+ abortController.signal
1612
+ );
1603
1613
  fetchControllers.set(key, abortController);
1604
1614
  let result: DataResult = await callLoaderOrAction(
1605
1615
  "loader",
@@ -1609,7 +1619,7 @@ export function createRouter(init: RouterInit): Router {
1609
1619
  router.basename
1610
1620
  );
1611
1621
 
1612
- // Deferred isn't supported or fetcher loads, await everything and treat it
1622
+ // Deferred isn't supported for fetcher loads, await everything and treat it
1613
1623
  // as a normal load. resolveDeferredData will return undefined if this
1614
1624
  // fetcher gets aborted, so we just leave result untouched and short circuit
1615
1625
  // below if that happens
@@ -1719,7 +1729,7 @@ export function createRouter(init: RouterInit): Router {
1719
1729
 
1720
1730
  // Check if this an external redirect that goes to a new origin
1721
1731
  if (typeof window?.location !== "undefined") {
1722
- let newOrigin = createClientSideURL(redirect.location).origin;
1732
+ let newOrigin = init.history.createURL(redirect.location).origin;
1723
1733
  if (window.location.origin !== newOrigin) {
1724
1734
  if (replace) {
1725
1735
  window.location.replace(redirect.location);
@@ -1796,7 +1806,7 @@ export function createRouter(init: RouterInit): Router {
1796
1806
  ...fetchersToLoad.map(([, href, match, fetchMatches]) =>
1797
1807
  callLoaderOrAction(
1798
1808
  "loader",
1799
- createClientSideRequest(href, request.signal),
1809
+ createClientSideRequest(init.history, href, request.signal),
1800
1810
  match,
1801
1811
  fetchMatches,
1802
1812
  router.basename
@@ -2027,6 +2037,8 @@ export function createRouter(init: RouterInit): Router {
2027
2037
  //#region createStaticHandler
2028
2038
  ////////////////////////////////////////////////////////////////////////////////
2029
2039
 
2040
+ export const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
2041
+
2030
2042
  export function createStaticHandler(
2031
2043
  routes: AgnosticRouteObject[],
2032
2044
  opts?: {
@@ -2086,6 +2098,7 @@ export function createStaticHandler(
2086
2098
  statusCode: error.status,
2087
2099
  loaderHeaders: {},
2088
2100
  actionHeaders: {},
2101
+ activeDeferreds: null,
2089
2102
  };
2090
2103
  } else if (!matches) {
2091
2104
  let error = getInternalRouterError(404, { pathname: location.pathname });
@@ -2103,6 +2116,7 @@ export function createStaticHandler(
2103
2116
  statusCode: error.status,
2104
2117
  loaderHeaders: {},
2105
2118
  actionHeaders: {},
2119
+ activeDeferreds: null,
2106
2120
  };
2107
2121
  }
2108
2122
 
@@ -2191,8 +2205,19 @@ export function createStaticHandler(
2191
2205
  }
2192
2206
 
2193
2207
  // Pick off the right state value to return
2194
- let routeData = [result.actionData, result.loaderData].find((v) => v);
2195
- return Object.values(routeData || {})[0];
2208
+ if (result.actionData) {
2209
+ return Object.values(result.actionData)[0];
2210
+ }
2211
+
2212
+ if (result.loaderData) {
2213
+ let data = Object.values(result.loaderData)[0];
2214
+ if (result.activeDeferreds?.[match.route.id]) {
2215
+ data[UNSAFE_DEFERRED_SYMBOL] = result.activeDeferreds[match.route.id];
2216
+ }
2217
+ return data;
2218
+ }
2219
+
2220
+ return undefined;
2196
2221
  }
2197
2222
 
2198
2223
  async function queryImpl(
@@ -2305,7 +2330,14 @@ export function createStaticHandler(
2305
2330
  }
2306
2331
 
2307
2332
  if (isDeferredResult(result)) {
2308
- throw new Error("defer() is not supported in actions");
2333
+ let error = getInternalRouterError(400, { type: "defer-action" });
2334
+ if (isRouteRequest) {
2335
+ throw error;
2336
+ }
2337
+ result = {
2338
+ type: ResultType.error,
2339
+ error,
2340
+ };
2309
2341
  }
2310
2342
 
2311
2343
  if (isRouteRequest) {
@@ -2325,6 +2357,7 @@ export function createStaticHandler(
2325
2357
  statusCode: 200,
2326
2358
  loaderHeaders: {},
2327
2359
  actionHeaders: {},
2360
+ activeDeferreds: null,
2328
2361
  };
2329
2362
  }
2330
2363
 
@@ -2420,6 +2453,7 @@ export function createStaticHandler(
2420
2453
  errors: pendingActionError || null,
2421
2454
  statusCode: 200,
2422
2455
  loaderHeaders: {},
2456
+ activeDeferreds: null,
2423
2457
  };
2424
2458
  }
2425
2459
 
@@ -2443,25 +2477,20 @@ export function createStaticHandler(
2443
2477
  throw new Error(`${method}() call aborted`);
2444
2478
  }
2445
2479
 
2446
- let executedLoaders = new Set<string>();
2447
- results.forEach((result, i) => {
2448
- executedLoaders.add(matchesToLoad[i].route.id);
2449
- // Can't do anything with these without the Remix side of things, so just
2450
- // cancel them for now
2451
- if (isDeferredResult(result)) {
2452
- result.deferredData.cancel();
2453
- }
2454
- });
2455
-
2456
2480
  // Process and commit output from loaders
2481
+ let activeDeferreds = new Map<string, DeferredData>();
2457
2482
  let context = processRouteLoaderData(
2458
2483
  matches,
2459
2484
  matchesToLoad,
2460
2485
  results,
2461
- pendingActionError
2486
+ pendingActionError,
2487
+ activeDeferreds
2462
2488
  );
2463
2489
 
2464
2490
  // Add a null for any non-loader matches for proper revalidation on the client
2491
+ let executedLoaders = new Set<string>(
2492
+ matchesToLoad.map((match) => match.route.id)
2493
+ );
2465
2494
  matches.forEach((match) => {
2466
2495
  if (!executedLoaders.has(match.route.id)) {
2467
2496
  context.loaderData[match.route.id] = null;
@@ -2471,6 +2500,10 @@ export function createStaticHandler(
2471
2500
  return {
2472
2501
  ...context,
2473
2502
  matches,
2503
+ activeDeferreds:
2504
+ activeDeferreds.size > 0
2505
+ ? Object.fromEntries(activeDeferreds.entries())
2506
+ : null,
2474
2507
  };
2475
2508
  }
2476
2509
 
@@ -2595,6 +2628,7 @@ function getLoaderMatchesUntilBoundary(
2595
2628
  }
2596
2629
 
2597
2630
  function getMatchesToLoad(
2631
+ history: History,
2598
2632
  state: RouterState,
2599
2633
  matches: AgnosticDataRouteMatch[],
2600
2634
  submission: Submission | undefined,
@@ -2622,6 +2656,7 @@ function getMatchesToLoad(
2622
2656
  // If this route had a pending deferred cancelled it must be revalidated
2623
2657
  cancelledDeferredRoutes.some((id) => id === match.route.id) ||
2624
2658
  shouldRevalidateLoader(
2659
+ history,
2625
2660
  state.location,
2626
2661
  state.matches[index],
2627
2662
  submission,
@@ -2641,6 +2676,7 @@ function getMatchesToLoad(
2641
2676
  revalidatingFetchers.push([key, href, match, fetchMatches]);
2642
2677
  } else if (isRevalidationRequired) {
2643
2678
  let shouldRevalidate = shouldRevalidateLoader(
2679
+ history,
2644
2680
  href,
2645
2681
  match,
2646
2682
  submission,
@@ -2694,6 +2730,7 @@ function isNewRouteInstance(
2694
2730
  }
2695
2731
 
2696
2732
  function shouldRevalidateLoader(
2733
+ history: History,
2697
2734
  currentLocation: string | Location,
2698
2735
  currentMatch: AgnosticDataRouteMatch,
2699
2736
  submission: Submission | undefined,
@@ -2702,9 +2739,9 @@ function shouldRevalidateLoader(
2702
2739
  isRevalidationRequired: boolean,
2703
2740
  actionResult: DataResult | undefined
2704
2741
  ) {
2705
- let currentUrl = createClientSideURL(currentLocation);
2742
+ let currentUrl = history.createURL(currentLocation);
2706
2743
  let currentParams = currentMatch.params;
2707
- let nextUrl = createClientSideURL(location);
2744
+ let nextUrl = history.createURL(location);
2708
2745
  let nextParams = match.params;
2709
2746
 
2710
2747
  // This is the default implementation as to when we revalidate. If the route
@@ -2795,8 +2832,7 @@ async function callLoaderOrAction(
2795
2832
  "Redirects returned/thrown from loaders/actions must have a Location header"
2796
2833
  );
2797
2834
 
2798
- let isAbsolute =
2799
- /^[a-z+]+:\/\//i.test(location) || location.startsWith("//");
2835
+ let isAbsolute = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(location);
2800
2836
 
2801
2837
  // Support relative routing in internal redirects
2802
2838
  if (!isAbsolute) {
@@ -2893,11 +2929,12 @@ async function callLoaderOrAction(
2893
2929
  // client-side navigations and fetches. During SSR we will always have a
2894
2930
  // Request instance from the static handler (query/queryRoute)
2895
2931
  function createClientSideRequest(
2932
+ history: History,
2896
2933
  location: string | Location,
2897
2934
  signal: AbortSignal,
2898
2935
  submission?: Submission
2899
2936
  ): Request {
2900
- let url = createClientSideURL(stripHashFromPath(location)).toString();
2937
+ let url = history.createURL(stripHashFromPath(location)).toString();
2901
2938
  let init: RequestInit = { signal };
2902
2939
 
2903
2940
  if (submission && isMutationMethod(submission.formMethod)) {
@@ -2933,7 +2970,7 @@ function processRouteLoaderData(
2933
2970
  matchesToLoad: AgnosticDataRouteMatch[],
2934
2971
  results: DataResult[],
2935
2972
  pendingError: RouteData | undefined,
2936
- activeDeferreds?: Map<string, DeferredData>
2973
+ activeDeferreds: Map<string, DeferredData>
2937
2974
  ): {
2938
2975
  loaderData: RouterState["loaderData"];
2939
2976
  errors: RouterState["errors"] | null;
@@ -2988,12 +3025,14 @@ function processRouteLoaderData(
2988
3025
  if (result.headers) {
2989
3026
  loaderHeaders[id] = result.headers;
2990
3027
  }
2991
- } else if (isDeferredResult(result)) {
2992
- activeDeferreds && activeDeferreds.set(id, result.deferredData);
2993
- loaderData[id] = result.deferredData.data;
2994
- // TODO: Add statusCode/headers once we wire up streaming in Remix
2995
3028
  } else {
2996
- loaderData[id] = result.data;
3029
+ if (isDeferredResult(result)) {
3030
+ activeDeferreds.set(id, result.deferredData);
3031
+ loaderData[id] = result.deferredData.data;
3032
+ } else {
3033
+ loaderData[id] = result.data;
3034
+ }
3035
+
2997
3036
  // Error status codes always override success status codes, but if all
2998
3037
  // loaders are successful we take the deepest status code.
2999
3038
  if (
@@ -3068,11 +3107,11 @@ function processLoaderData(
3068
3107
  } else if (isRedirectResult(result)) {
3069
3108
  // Should never get here, redirects should get processed above, but we
3070
3109
  // keep this to type narrow to a success result in the else
3071
- throw new Error("Unhandled fetcher revalidation redirect");
3110
+ invariant(false, "Unhandled fetcher revalidation redirect");
3072
3111
  } else if (isDeferredResult(result)) {
3073
3112
  // Should never get here, deferred data should be awaited for fetchers
3074
3113
  // in resolveDeferredResults
3075
- throw new Error("Unhandled fetcher deferred data");
3114
+ invariant(false, "Unhandled fetcher deferred data");
3076
3115
  } else {
3077
3116
  let doneFetcher: FetcherStates["Idle"] = {
3078
3117
  state: "idle",
@@ -3163,10 +3202,12 @@ function getInternalRouterError(
3163
3202
  pathname,
3164
3203
  routeId,
3165
3204
  method,
3205
+ type,
3166
3206
  }: {
3167
3207
  pathname?: string;
3168
3208
  routeId?: string;
3169
3209
  method?: string;
3210
+ type?: "defer-action";
3170
3211
  } = {}
3171
3212
  ) {
3172
3213
  let statusText = "Unknown Server Error";
@@ -3179,6 +3220,8 @@ function getInternalRouterError(
3179
3220
  `You made a ${method} request to "${pathname}" but ` +
3180
3221
  `did not provide a \`loader\` for route "${routeId}", ` +
3181
3222
  `so there is no way to handle the request.`;
3223
+ } else if (type === "defer-action") {
3224
+ errorMessage = "defer() is not supported in actions";
3182
3225
  } else {
3183
3226
  errorMessage = "Cannot submit binary form data using GET";
3184
3227
  }