@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/utils.ts CHANGED
@@ -31,6 +31,8 @@ export interface SuccessResult {
31
31
  export interface DeferredResult {
32
32
  type: ResultType.deferred;
33
33
  deferredData: DeferredData;
34
+ statusCode?: number;
35
+ headers?: Headers;
34
36
  }
35
37
 
36
38
  /**
@@ -198,7 +200,9 @@ type _PathParam<Path extends string> =
198
200
  ? _PathParam<L> | _PathParam<R>
199
201
  : // find params after `:`
200
202
  Path extends `:${infer Param}`
201
- ? Param
203
+ ? Param extends `${infer Optional}?`
204
+ ? Optional
205
+ : Param
202
206
  : // otherwise, there aren't any params present
203
207
  never;
204
208
 
@@ -614,7 +618,7 @@ function matchRouteBranch<
614
618
  export function generatePath<Path extends string>(
615
619
  originalPath: Path,
616
620
  params: {
617
- [key in PathParam<Path>]: string;
621
+ [key in PathParam<Path>]: string | null;
618
622
  } = {} as any
619
623
  ): string {
620
624
  let path = originalPath;
@@ -629,27 +633,49 @@ export function generatePath<Path extends string>(
629
633
  path = path.replace(/\*$/, "/*") as Path;
630
634
  }
631
635
 
632
- return path
633
- .replace(/^:(\w+)/g, (_, key: PathParam<Path>) => {
634
- invariant(params[key] != null, `Missing ":${key}" param`);
635
- return params[key]!;
636
- })
637
- .replace(/\/:(\w+)/g, (_, key: PathParam<Path>) => {
638
- invariant(params[key] != null, `Missing ":${key}" param`);
639
- return `/${params[key]!}`;
640
- })
641
- .replace(/(\/?)\*/, (_, prefix, __, str) => {
642
- const star = "*" as PathParam<Path>;
643
-
644
- if (params[star] == null) {
645
- // If no splat was provided, trim the trailing slash _unless_ it's
646
- // the entire path
647
- return str === "/*" ? "/" : "";
648
- }
649
-
650
- // Apply the splat
651
- return `${prefix}${params[star]}`;
652
- });
636
+ return (
637
+ path
638
+ .replace(
639
+ /^:(\w+)(\??)/g,
640
+ (_, key: PathParam<Path>, optional: string | undefined) => {
641
+ let param = params[key];
642
+ if (optional === "?") {
643
+ return param == null ? "" : param;
644
+ }
645
+ if (param == null) {
646
+ invariant(false, `Missing ":${key}" param`);
647
+ }
648
+ return param;
649
+ }
650
+ )
651
+ .replace(
652
+ /\/:(\w+)(\??)/g,
653
+ (_, key: PathParam<Path>, optional: string | undefined) => {
654
+ let param = params[key];
655
+ if (optional === "?") {
656
+ return param == null ? "" : `/${param}`;
657
+ }
658
+ if (param == null) {
659
+ invariant(false, `Missing ":${key}" param`);
660
+ }
661
+ return `/${param}`;
662
+ }
663
+ )
664
+ // Remove any optional markers from optional static segments
665
+ .replace(/\?/g, "")
666
+ .replace(/(\/?)\*/, (_, prefix, __, str) => {
667
+ const star = "*" as PathParam<Path>;
668
+
669
+ if (params[star] == null) {
670
+ // If no splat was provided, trim the trailing slash _unless_ it's
671
+ // the entire path
672
+ return str === "/*" ? "/" : "";
673
+ }
674
+
675
+ // Apply the splat
676
+ return `${prefix}${params[star]}`;
677
+ })
678
+ );
653
679
  }
654
680
 
655
681
  /**
@@ -1131,14 +1157,17 @@ export interface TrackedPromise extends Promise<any> {
1131
1157
  export class AbortedDeferredError extends Error {}
1132
1158
 
1133
1159
  export class DeferredData {
1134
- private pendingKeys: Set<string | number> = new Set<string | number>();
1160
+ private pendingKeysSet: Set<string> = new Set<string>();
1135
1161
  private controller: AbortController;
1136
1162
  private abortPromise: Promise<void>;
1137
1163
  private unlistenAbortSignal: () => void;
1138
- private subscriber?: (aborted: boolean) => void = undefined;
1164
+ private subscribers: Set<(aborted: boolean, settledKey?: string) => void> =
1165
+ new Set();
1139
1166
  data: Record<string, unknown>;
1167
+ init?: ResponseInit;
1168
+ deferredKeys: string[] = [];
1140
1169
 
1141
- constructor(data: Record<string, unknown>) {
1170
+ constructor(data: Record<string, unknown>, responseInit?: ResponseInit) {
1142
1171
  invariant(
1143
1172
  data && typeof data === "object" && !Array.isArray(data),
1144
1173
  "defer() only accepts plain objects"
@@ -1162,17 +1191,20 @@ export class DeferredData {
1162
1191
  }),
1163
1192
  {}
1164
1193
  );
1194
+
1195
+ this.init = responseInit;
1165
1196
  }
1166
1197
 
1167
1198
  private trackPromise(
1168
- key: string | number,
1199
+ key: string,
1169
1200
  value: Promise<unknown> | unknown
1170
1201
  ): TrackedPromise | unknown {
1171
1202
  if (!(value instanceof Promise)) {
1172
1203
  return value;
1173
1204
  }
1174
1205
 
1175
- this.pendingKeys.add(key);
1206
+ this.deferredKeys.push(key);
1207
+ this.pendingKeysSet.add(key);
1176
1208
 
1177
1209
  // We store a little wrapper promise that will be extended with
1178
1210
  // _data/_error props upon resolve/reject
@@ -1191,7 +1223,7 @@ export class DeferredData {
1191
1223
 
1192
1224
  private onSettle(
1193
1225
  promise: TrackedPromise,
1194
- key: string | number,
1226
+ key: string,
1195
1227
  error: unknown,
1196
1228
  data?: unknown
1197
1229
  ): unknown {
@@ -1204,34 +1236,37 @@ export class DeferredData {
1204
1236
  return Promise.reject(error);
1205
1237
  }
1206
1238
 
1207
- this.pendingKeys.delete(key);
1239
+ this.pendingKeysSet.delete(key);
1208
1240
 
1209
1241
  if (this.done) {
1210
1242
  // Nothing left to abort!
1211
1243
  this.unlistenAbortSignal();
1212
1244
  }
1213
1245
 
1214
- const subscriber = this.subscriber;
1215
1246
  if (error) {
1216
1247
  Object.defineProperty(promise, "_error", { get: () => error });
1217
- subscriber && subscriber(false);
1248
+ this.emit(false, key);
1218
1249
  return Promise.reject(error);
1219
1250
  }
1220
1251
 
1221
1252
  Object.defineProperty(promise, "_data", { get: () => data });
1222
- subscriber && subscriber(false);
1253
+ this.emit(false, key);
1223
1254
  return data;
1224
1255
  }
1225
1256
 
1226
- subscribe(fn: (aborted: boolean) => void) {
1227
- this.subscriber = fn;
1257
+ private emit(aborted: boolean, settledKey?: string) {
1258
+ this.subscribers.forEach((subscriber) => subscriber(aborted, settledKey));
1259
+ }
1260
+
1261
+ subscribe(fn: (aborted: boolean, settledKey?: string) => void) {
1262
+ this.subscribers.add(fn);
1263
+ return () => this.subscribers.delete(fn);
1228
1264
  }
1229
1265
 
1230
1266
  cancel() {
1231
1267
  this.controller.abort();
1232
- this.pendingKeys.forEach((v, k) => this.pendingKeys.delete(k));
1233
- let subscriber = this.subscriber;
1234
- subscriber && subscriber(true);
1268
+ this.pendingKeysSet.forEach((v, k) => this.pendingKeysSet.delete(k));
1269
+ this.emit(true);
1235
1270
  }
1236
1271
 
1237
1272
  async resolveData(signal: AbortSignal) {
@@ -1252,7 +1287,7 @@ export class DeferredData {
1252
1287
  }
1253
1288
 
1254
1289
  get done() {
1255
- return this.pendingKeys.size === 0;
1290
+ return this.pendingKeysSet.size === 0;
1256
1291
  }
1257
1292
 
1258
1293
  get unwrappedData() {
@@ -1269,6 +1304,10 @@ export class DeferredData {
1269
1304
  {}
1270
1305
  );
1271
1306
  }
1307
+
1308
+ get pendingKeys() {
1309
+ return Array.from(this.pendingKeysSet);
1310
+ }
1272
1311
  }
1273
1312
 
1274
1313
  function isTrackedPromise(value: any): value is TrackedPromise {
@@ -1288,9 +1327,16 @@ function unwrapTrackedPromise(value: any) {
1288
1327
  return value._data;
1289
1328
  }
1290
1329
 
1291
- export function defer(data: Record<string, unknown>) {
1292
- return new DeferredData(data);
1293
- }
1330
+ export type DeferFunction = (
1331
+ data: Record<string, unknown>,
1332
+ init?: number | ResponseInit
1333
+ ) => DeferredData;
1334
+
1335
+ export const defer: DeferFunction = (data, init = {}) => {
1336
+ let responseInit = typeof init === "number" ? { status: init } : init;
1337
+
1338
+ return new DeferredData(data, responseInit);
1339
+ };
1294
1340
 
1295
1341
  export type RedirectFunction = (
1296
1342
  url: string,