js-routes 2.0.7 → 2.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/routes.ts CHANGED
@@ -3,15 +3,51 @@
3
3
  * Based on Rails RubyVariables.RAILS_VERSION routes of RubyVariables.APP_CLASS
4
4
  */
5
5
 
6
- type RouteParameter = unknown;
7
- type RouteParameters = Record<string, RouteParameter>;
8
- type Serializer = (value: unknown) => string;
9
- type RouteHelper = {
10
- (...args: RouteParameter[]): string;
6
+ type Optional<T> = { [P in keyof T]?: T[P] | null };
7
+ type BaseRouteParameter = string | boolean | Date | number;
8
+ type MethodRouteParameter = BaseRouteParameter | (() => BaseRouteParameter);
9
+ type ModelRouteParameter =
10
+ | { id: MethodRouteParameter }
11
+ | { to_param: MethodRouteParameter }
12
+ | { toParam: MethodRouteParameter };
13
+ type RequiredRouteParameter = BaseRouteParameter | ModelRouteParameter;
14
+ type OptionalRouteParameter = undefined | null | RequiredRouteParameter;
15
+ type QueryRouteParameter =
16
+ | OptionalRouteParameter
17
+ | QueryRouteParameter[]
18
+ | { [k: string]: QueryRouteParameter };
19
+ type RouteParameters = Record<string, QueryRouteParameter>;
20
+
21
+ type Serializable = Record<string, unknown>;
22
+ type Serializer = (value: Serializable) => string;
23
+ type RouteHelperExtras = {
11
24
  requiredParams(): string[];
12
25
  toString(): string;
13
26
  };
14
27
 
28
+ type RequiredParameters<T extends number> = T extends 1
29
+ ? [RequiredRouteParameter]
30
+ : T extends 2
31
+ ? [RequiredRouteParameter, RequiredRouteParameter]
32
+ : T extends 3
33
+ ? [RequiredRouteParameter, RequiredRouteParameter, RequiredRouteParameter]
34
+ : T extends 4
35
+ ? [
36
+ RequiredRouteParameter,
37
+ RequiredRouteParameter,
38
+ RequiredRouteParameter,
39
+ RequiredRouteParameter
40
+ ]
41
+ : RequiredRouteParameter[];
42
+
43
+ type RouteHelperOptions<T extends string> = RouteOptions &
44
+ Optional<Record<T, OptionalRouteParameter>>;
45
+
46
+ type RouteHelper<T extends number = number, U extends string = string> = ((
47
+ ...args: [...RequiredParameters<T>, RouteHelperOptions<U>]
48
+ ) => string) &
49
+ RouteHelperExtras;
50
+
15
51
  type RouteHelpers = Record<string, RouteHelper>;
16
52
 
17
53
  type Configuration = {
@@ -21,7 +57,6 @@ type Configuration = {
21
57
  serializer: Serializer;
22
58
  };
23
59
 
24
- type Optional<T> = { [P in keyof T]?: T[P] | null };
25
60
  interface RouterExposedMethods {
26
61
  config(): Configuration;
27
62
  configure(arg: Partial<Configuration>): Configuration;
@@ -32,14 +67,16 @@ type KeywordUrlOptions = Optional<{
32
67
  host: string;
33
68
  protocol: string;
34
69
  subdomain: string;
35
- port: string;
70
+ port: string | number;
36
71
  anchor: string;
37
72
  trailing_slash: boolean;
38
73
  }>;
39
74
 
40
- type PartsTable = Record<string, { r?: boolean; d?: unknown }>;
75
+ type RouteOptions = KeywordUrlOptions & RouteParameters;
41
76
 
42
- type ModuleType = "CJS" | "AMD" | "UMD" | "ESM";
77
+ type PartsTable = Record<string, { r?: boolean; d?: OptionalRouteParameter }>;
78
+
79
+ type ModuleType = "CJS" | "AMD" | "UMD" | "ESM" | "DTS" | "NIL";
43
80
 
44
81
  declare const RubyVariables: {
45
82
  PREFIX: string;
@@ -49,7 +86,7 @@ declare const RubyVariables: {
49
86
  SERIALIZER: Serializer;
50
87
  NAMESPACE: string;
51
88
  ROUTES_OBJECT: RouteHelpers;
52
- MODULE_TYPE: ModuleType | null;
89
+ MODULE_TYPE: ModuleType;
53
90
  WRAPPER: <T>(callback: T) => T;
54
91
  };
55
92
 
@@ -92,30 +129,38 @@ RubyVariables.WRAPPER(
92
129
  }[keyof RouteNodes];
93
130
 
94
131
  const Root = that;
132
+ const isBroswer = typeof window !== "undefined";
95
133
  type ModuleDefinition = {
96
- define: (routes: unknown) => void;
97
- support: () => boolean;
134
+ define: (routes: RouterExposedMethods) => void;
135
+ isSupported: () => boolean;
98
136
  };
99
137
  const ModuleReferences: Record<ModuleType, ModuleDefinition> = {
100
138
  CJS: {
101
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
102
- define: (routes) => (module!.exports = routes),
103
- support: () => typeof module === "object",
139
+ define(routes) {
140
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
141
+ module!.exports = routes;
142
+ },
143
+ isSupported() {
144
+ return typeof module === "object";
145
+ },
104
146
  },
105
147
  AMD: {
106
- define: (routes) =>
148
+ define(routes) {
107
149
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
108
150
  define!([], function () {
109
151
  return routes;
110
- }),
111
- support: () => typeof define === "function" && !!define.amd,
152
+ });
153
+ },
154
+ isSupported() {
155
+ return typeof define === "function" && !!define.amd;
156
+ },
112
157
  },
113
158
  UMD: {
114
- define: (routes) => {
115
- if (ModuleReferences.AMD.support()) {
159
+ define(routes) {
160
+ if (ModuleReferences.AMD.isSupported()) {
116
161
  ModuleReferences.AMD.define(routes);
117
162
  } else {
118
- if (ModuleReferences.CJS.support()) {
163
+ if (ModuleReferences.CJS.isSupported()) {
119
164
  try {
120
165
  ModuleReferences.CJS.define(routes);
121
166
  } catch (error) {
@@ -124,12 +169,38 @@ RubyVariables.WRAPPER(
124
169
  }
125
170
  }
126
171
  },
127
- support: () =>
128
- ModuleReferences.AMD.support() || ModuleReferences.CJS.support(),
172
+ isSupported() {
173
+ return (
174
+ ModuleReferences.AMD.isSupported() ||
175
+ ModuleReferences.CJS.isSupported()
176
+ );
177
+ },
129
178
  },
130
179
  ESM: {
131
- define: () => null,
132
- support: () => true,
180
+ define() {
181
+ // Module can only be defined using ruby code generation
182
+ },
183
+ isSupported() {
184
+ // Its impossible to check if "export" keyword is supported
185
+ return true;
186
+ },
187
+ },
188
+ NIL: {
189
+ define(routes) {
190
+ Utils.namespace(Root, RubyVariables.NAMESPACE, routes);
191
+ },
192
+ isSupported() {
193
+ return !!Root;
194
+ },
195
+ },
196
+ DTS: {
197
+ // Acts the same as ESM
198
+ define(routes) {
199
+ ModuleReferences.ESM.define(routes);
200
+ },
201
+ isSupported() {
202
+ return ModuleReferences.ESM.isSupported();
203
+ },
133
204
  },
134
205
  };
135
206
 
@@ -143,9 +214,6 @@ RubyVariables.WRAPPER(
143
214
  }
144
215
  }
145
216
 
146
- const DeprecatedGlobbingBehavior =
147
- RubyVariables.DEPRECATED_GLOBBING_BEHAVIOR;
148
-
149
217
  const UriEncoderSegmentRegex = /[^a-zA-Z0-9\-._~!$&'()*+,;=:@]/g;
150
218
 
151
219
  const ReservedOptions = [
@@ -204,16 +272,16 @@ RubyVariables.WRAPPER(
204
272
  return result.join("&");
205
273
  }
206
274
 
207
- serialize(object: unknown): string {
275
+ serialize(object: Serializable): string {
208
276
  return this.configuration.serializer(object);
209
277
  }
210
278
 
211
279
  extract_options(
212
280
  number_of_params: number,
213
- args: RouteParameter[]
281
+ args: OptionalRouteParameter[]
214
282
  ): {
215
- args: RouteParameter[];
216
- options: KeywordUrlOptions & RouteParameters;
283
+ args: OptionalRouteParameter[];
284
+ options: RouteOptions;
217
285
  } {
218
286
  const last_el = args[args.length - 1];
219
287
  if (
@@ -226,32 +294,27 @@ RubyVariables.WRAPPER(
226
294
  }
227
295
  return {
228
296
  args: args.slice(0, args.length - 1),
229
- options: last_el as KeywordUrlOptions & RouteParameters,
297
+ options: (last_el as any) as RouteOptions,
230
298
  };
231
299
  } else {
232
300
  return { args, options: {} };
233
301
  }
234
302
  }
235
303
 
236
- looks_like_serialized_model(
237
- object: any
238
- ): object is
239
- | { id: unknown }
240
- | { to_param: unknown }
241
- | { toParam: unknown } {
304
+ looks_like_serialized_model(object: any): object is ModelRouteParameter {
242
305
  return (
243
306
  this.is_object(object) &&
244
- !object[this.configuration.special_options_key] &&
307
+ !(this.configuration.special_options_key in object) &&
245
308
  ("id" in object || "to_param" in object || "toParam" in object)
246
309
  );
247
310
  }
248
311
 
249
- path_identifier(object: unknown): string {
312
+ path_identifier(object: QueryRouteParameter): string {
250
313
  const result = this.unwrap_path_identifier(object);
251
314
  return this.is_nullable(result) || result === false ? "" : "" + result;
252
315
  }
253
316
 
254
- unwrap_path_identifier(object: any): unknown {
317
+ unwrap_path_identifier(object: QueryRouteParameter): unknown {
255
318
  let result: any = object;
256
319
  if (!this.is_object(object)) {
257
320
  return object;
@@ -272,7 +335,7 @@ RubyVariables.WRAPPER(
272
335
  parts: string[],
273
336
  required_params: string[],
274
337
  default_options: RouteParameters,
275
- call_arguments: RouteParameter[]
338
+ call_arguments: OptionalRouteParameter[]
276
339
  ): {
277
340
  keyword_parameters: KeywordUrlOptions;
278
341
  query_parameters: RouteParameters;
@@ -336,7 +399,7 @@ RubyVariables.WRAPPER(
336
399
  default_options: RouteParameters,
337
400
  route: RouteTree,
338
401
  absolute: boolean,
339
- args: RouteParameter[]
402
+ args: OptionalRouteParameter[]
340
403
  ): string {
341
404
  const {
342
405
  keyword_parameters,
@@ -446,9 +509,9 @@ RubyVariables.WRAPPER(
446
509
  }
447
510
 
448
511
  encode_segment(segment: string): string {
449
- return segment.replace(UriEncoderSegmentRegex, function (str) {
450
- return encodeURIComponent(str);
451
- });
512
+ return segment.replace(UriEncoderSegmentRegex, (str) =>
513
+ encodeURIComponent(str)
514
+ );
452
515
  }
453
516
 
454
517
  is_optional_node(node: NodeTypes): boolean {
@@ -498,7 +561,9 @@ RubyVariables.WRAPPER(
498
561
  value = value.join("/");
499
562
  }
500
563
  const result = this.path_identifier(value as any);
501
- return DeprecatedGlobbingBehavior ? result : encodeURI(result);
564
+ return RubyVariables.DEPRECATED_GLOBBING_BEHAVIOR
565
+ ? result
566
+ : encodeURI(result);
502
567
  }
503
568
 
504
569
  get_prefix(): string {
@@ -527,7 +592,7 @@ RubyVariables.WRAPPER(
527
592
  default_options[part] = value;
528
593
  }
529
594
  }
530
- const result = (...args: RouteParameter[]): string => {
595
+ const result = (...args: OptionalRouteParameter[]): string => {
531
596
  return this.build_route(
532
597
  parts,
533
598
  required_params,
@@ -541,7 +606,7 @@ RubyVariables.WRAPPER(
541
606
  result.toString = () => {
542
607
  return this.build_path_spec(route_spec);
543
608
  };
544
- return result;
609
+ return result as any;
545
610
  }
546
611
 
547
612
  route_url(route_defaults: KeywordUrlOptions): string {
@@ -560,31 +625,18 @@ RubyVariables.WRAPPER(
560
625
  return protocol + "://" + subdomain + hostname + port;
561
626
  }
562
627
 
563
- has_location(): boolean {
564
- return this.is_not_nullable(window) && !!window.location;
565
- }
566
-
567
- current_host(): string | null {
568
- if (this.has_location()) {
569
- return window.location.hostname;
570
- } else {
571
- return null;
572
- }
628
+ current_host(): string {
629
+ return (isBroswer && window?.location?.hostname) || "";
573
630
  }
574
631
 
575
632
  current_protocol(): string {
576
- if (this.has_location() && window.location.protocol !== "") {
577
- return window.location.protocol.replace(/:$/, "");
578
- } else {
579
- return "http";
580
- }
633
+ return (
634
+ (isBroswer && window?.location?.protocol?.replace(/:$/, "")) || "http"
635
+ );
581
636
  }
637
+
582
638
  current_port(): string {
583
- if (this.has_location() && window.location.port !== "") {
584
- return window.location.port;
585
- } else {
586
- return "";
587
- }
639
+ return (isBroswer && window?.location?.port) || "";
588
640
  }
589
641
 
590
642
  is_object(value: unknown): value is Record<string, unknown> {
@@ -635,7 +687,7 @@ RubyVariables.WRAPPER(
635
687
  }
636
688
 
637
689
  is_module_supported(name: ModuleType): boolean {
638
- return ModuleReferences[name].support();
690
+ return ModuleReferences[name].isSupported();
639
691
  }
640
692
 
641
693
  ensure_module_supported(name: ModuleType): void {
@@ -644,13 +696,7 @@ RubyVariables.WRAPPER(
644
696
  }
645
697
  }
646
698
 
647
- define_module(
648
- name: ModuleType | null,
649
- module: RouterExposedMethods
650
- ): void {
651
- if (!name) {
652
- return;
653
- }
699
+ define_module(name: ModuleType, module: RouterExposedMethods): void {
654
700
  this.ensure_module_supported(name);
655
701
  ModuleReferences[name].define(module);
656
702
  }
@@ -677,17 +723,13 @@ RubyVariables.WRAPPER(
677
723
  config: (): Configuration => {
678
724
  return Utils.config();
679
725
  },
680
- serialize: (object: unknown): string => {
726
+ serialize: (object: Serializable): string => {
681
727
  return Utils.serialize(object);
682
728
  },
683
729
  ...RubyVariables.ROUTES_OBJECT,
684
730
  };
685
731
 
686
- Utils.namespace(Root, RubyVariables.NAMESPACE, result);
687
-
688
- if (RubyVariables.MODULE_TYPE) {
689
- Utils.define_module(RubyVariables.MODULE_TYPE, result);
690
- }
732
+ Utils.define_module(RubyVariables.MODULE_TYPE, result);
691
733
  return result;
692
734
  }
693
735
  )(this);
@@ -1,8 +1,14 @@
1
1
  namespace :js do
2
- desc "Make a js file that will have functions that will return restful routes/urls."
2
+ desc "Make a js file with all rails route URL helpers"
3
3
  task routes: :environment do
4
4
  require "js-routes"
5
-
6
5
  JsRoutes.generate!
7
6
  end
7
+
8
+ namespace :routes do
9
+ desc "Make a js file with all rails route URL helpers and typescript definitions for them"
10
+ task typescript: "js:routes" do
11
+ JsRoutes.definitions!
12
+ end
13
+ end
8
14
  end
@@ -0,0 +1,11 @@
1
+ module.exports = {
2
+ module: {
3
+ rules: [
4
+ {
5
+ test: /\.erb$/,
6
+ enforce: 'pre',
7
+ loader: 'rails-erb-loader'
8
+ },
9
+ ]
10
+ }
11
+ };
@@ -0,0 +1,5 @@
1
+ JsRoutes.setup do |c|
2
+ # Setup your JS module system:
3
+ # ESM, CJS, AMD, UMD or nil
4
+ # c.module_type = "ESM"
5
+ end
@@ -0,0 +1 @@
1
+ <%= JsRoutes.generate %>
@@ -0,0 +1,114 @@
1
+ /**
2
+ * File generated by js-routes RubyVariables.GEM_VERSION
3
+ * Based on Rails RubyVariables.RAILS_VERSION routes of RubyVariables.APP_CLASS
4
+ */
5
+ declare type Optional<T> = {
6
+ [P in keyof T]?: T[P] | null;
7
+ };
8
+ declare type BaseRouteParameter = string | boolean | Date | number;
9
+ declare type MethodRouteParameter = BaseRouteParameter | (() => BaseRouteParameter);
10
+ declare type ModelRouteParameter = {
11
+ id: MethodRouteParameter;
12
+ } | {
13
+ to_param: MethodRouteParameter;
14
+ } | {
15
+ toParam: MethodRouteParameter;
16
+ };
17
+ declare type RequiredRouteParameter = BaseRouteParameter | ModelRouteParameter;
18
+ declare type OptionalRouteParameter = undefined | null | RequiredRouteParameter;
19
+ declare type QueryRouteParameter = OptionalRouteParameter | QueryRouteParameter[] | {
20
+ [k: string]: QueryRouteParameter;
21
+ };
22
+ declare type RouteParameters = Record<string, QueryRouteParameter>;
23
+ declare type Serializable = Record<string, unknown>;
24
+ declare type Serializer = (value: Serializable) => string;
25
+ declare type RouteHelperExtras = {
26
+ requiredParams(): string[];
27
+ toString(): string;
28
+ };
29
+ declare type RequiredParameters<T extends number> = T extends 1 ? [RequiredRouteParameter] : T extends 2 ? [RequiredRouteParameter, RequiredRouteParameter] : T extends 3 ? [RequiredRouteParameter, RequiredRouteParameter, RequiredRouteParameter] : T extends 4 ? [
30
+ RequiredRouteParameter,
31
+ RequiredRouteParameter,
32
+ RequiredRouteParameter,
33
+ RequiredRouteParameter
34
+ ] : RequiredRouteParameter[];
35
+ declare type RouteHelperOptions<T extends string> = RouteOptions & Optional<Record<T, OptionalRouteParameter>>;
36
+ declare type RouteHelper<T extends number = number, U extends string = string> = ((...args: [...RequiredParameters<T>, RouteHelperOptions<U>]) => string) & RouteHelperExtras;
37
+ declare type RouteHelpers = Record<string, RouteHelper>;
38
+ declare type Configuration = {
39
+ prefix: string;
40
+ default_url_options: RouteParameters;
41
+ special_options_key: string;
42
+ serializer: Serializer;
43
+ };
44
+ interface RouterExposedMethods {
45
+ config(): Configuration;
46
+ configure(arg: Partial<Configuration>): Configuration;
47
+ serialize: Serializer;
48
+ }
49
+ declare type KeywordUrlOptions = Optional<{
50
+ host: string;
51
+ protocol: string;
52
+ subdomain: string;
53
+ port: string | number;
54
+ anchor: string;
55
+ trailing_slash: boolean;
56
+ }>;
57
+ declare type RouteOptions = KeywordUrlOptions & RouteParameters;
58
+ declare type PartsTable = Record<string, {
59
+ r?: boolean;
60
+ d?: OptionalRouteParameter;
61
+ }>;
62
+ declare type ModuleType = "CJS" | "AMD" | "UMD" | "ESM" | "DTS" | "NIL";
63
+ declare const RubyVariables: {
64
+ PREFIX: string;
65
+ DEPRECATED_GLOBBING_BEHAVIOR: boolean;
66
+ SPECIAL_OPTIONS_KEY: string;
67
+ DEFAULT_URL_OPTIONS: RouteParameters;
68
+ SERIALIZER: Serializer;
69
+ NAMESPACE: string;
70
+ ROUTES_OBJECT: RouteHelpers;
71
+ MODULE_TYPE: ModuleType;
72
+ WRAPPER: <T>(callback: T) => T;
73
+ };
74
+ declare const define: undefined | (((arg: unknown[], callback: () => unknown) => void) & {
75
+ amd?: unknown;
76
+ });
77
+ declare const module: {
78
+ exports: any;
79
+ } | undefined;
80
+ export const configure: RouterExposedMethods['configure'];
81
+
82
+ export const config: RouterExposedMethods['config'];
83
+
84
+ export const serialize: RouterExposedMethods['serialize'];
85
+
86
+ /**
87
+ * Generates rails route to
88
+ * /inboxes/:inbox_id/messages/:message_id/attachments/:id(.:format)
89
+ * @param {any} inbox_id
90
+ * @param {any} message_id
91
+ * @param {any} id
92
+ * @param {object | undefined} options
93
+ * @returns {string} route path
94
+ */
95
+ export const inbox_message_attachment_path: ((
96
+ inbox_id: RequiredRouteParameter,
97
+ message_id: RequiredRouteParameter,
98
+ id: RequiredRouteParameter,
99
+ options?: {format?: OptionalRouteParameter} & RouteOptions
100
+ ) => string) & RouteHelperExtras;
101
+
102
+ /**
103
+ * Generates rails route to
104
+ * /inboxes(.:format)
105
+ * @param {object | undefined} options
106
+ * @returns {string} route path
107
+ */
108
+ export const inboxes_path: ((
109
+ options?: {format?: OptionalRouteParameter} & RouteOptions
110
+ ) => string) & RouteHelperExtras;
111
+
112
+ // By some reason this line prevents all types in a file
113
+ // from being automatically exported
114
+ export {};
@@ -0,0 +1,56 @@
1
+ import {
2
+ inbox_message_attachment_path,
3
+ inboxes_path,
4
+ serialize,
5
+ configure,
6
+ config,
7
+ } from "./routes.spec";
8
+
9
+ // Route Helpers
10
+ inboxes_path();
11
+ inboxes_path({
12
+ locale: "en",
13
+ search: {
14
+ q: "ukraine",
15
+ page: 3,
16
+ keywords: ["large", "small", { advanced: true }],
17
+ },
18
+ });
19
+
20
+ inbox_message_attachment_path(1, "2", true);
21
+ inbox_message_attachment_path(
22
+ { id: 1 },
23
+ { to_param: () => "2" },
24
+ { toParam: () => true }
25
+ );
26
+ inbox_message_attachment_path(1, "2", true, { format: "json" });
27
+ inboxes_path.toString();
28
+ inboxes_path.requiredParams();
29
+
30
+ // serialize test
31
+ const SerializerArgument = {
32
+ locale: "en",
33
+ search: {
34
+ q: "ukraine",
35
+ page: 3,
36
+ keywords: ["large", "small", { advanced: true }],
37
+ },
38
+ };
39
+ serialize(SerializerArgument);
40
+ config().serializer(SerializerArgument);
41
+
42
+ // configure test
43
+ configure({
44
+ default_url_options: { port: 1, host: null },
45
+ prefix: "",
46
+ special_options_key: "_options",
47
+ serializer: (value) => JSON.stringify(value),
48
+ });
49
+
50
+ // config tests
51
+ const Config = config();
52
+ console.log(
53
+ Config.prefix,
54
+ Config.default_url_options,
55
+ Config.special_options_key
56
+ );