js-routes 1.4.9 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintrc.js +15 -0
  3. data/.gitignore +5 -0
  4. data/.nvmrc +1 -0
  5. data/.travis.yml +2 -1
  6. data/CHANGELOG.md +85 -0
  7. data/Rakefile +5 -2
  8. data/Readme.md +274 -98
  9. data/VERSION_2_UPGRADE.md +66 -0
  10. data/js-routes.gemspec +8 -5
  11. data/lib/js_routes/engine.rb +0 -1
  12. data/lib/js_routes/generators/middleware.rb +33 -0
  13. data/lib/js_routes/generators/webpacker.rb +32 -0
  14. data/lib/js_routes/middleware.rb +36 -0
  15. data/lib/js_routes/version.rb +1 -1
  16. data/lib/js_routes.rb +341 -171
  17. data/lib/routes.d.ts +79 -0
  18. data/lib/routes.js +501 -519
  19. data/lib/routes.ts +737 -0
  20. data/lib/tasks/js_routes.rake +8 -2
  21. data/lib/templates/erb.js +11 -0
  22. data/lib/templates/initializer.rb +5 -0
  23. data/lib/templates/routes.js.erb +1 -0
  24. data/package.json +37 -0
  25. data/spec/dummy/app/assets/config/manifest.js +2 -0
  26. data/spec/js_routes/default_serializer_spec.rb +19 -3
  27. data/spec/js_routes/{amd_compatibility_spec.rb → module_types/amd_spec.rb} +7 -14
  28. data/spec/js_routes/module_types/cjs_spec.rb +15 -0
  29. data/spec/js_routes/module_types/dts/routes.spec.d.ts +114 -0
  30. data/spec/js_routes/module_types/dts/test.spec.ts +56 -0
  31. data/spec/js_routes/module_types/dts_spec.rb +111 -0
  32. data/spec/js_routes/module_types/esm_spec.rb +45 -0
  33. data/spec/js_routes/module_types/nil_spec.rb +86 -0
  34. data/spec/js_routes/{generated_javascript_spec.rb → module_types/umd_spec.rb} +33 -27
  35. data/spec/js_routes/options_spec.rb +67 -56
  36. data/spec/js_routes/rails_routes_compatibility_spec.rb +69 -25
  37. data/spec/js_routes/zzz_last_post_rails_init_spec.rb +4 -4
  38. data/spec/spec_helper.rb +34 -17
  39. data/spec/support/routes.rb +10 -4
  40. data/spec/tsconfig.json +4 -0
  41. data/tsconfig.json +28 -0
  42. data/yarn.lock +2145 -0
  43. metadata +42 -22
  44. data/lib/routes.js.coffee +0 -411
data/lib/routes.ts ADDED
@@ -0,0 +1,737 @@
1
+ /**
2
+ * File generated by js-routes RubyVariables.GEM_VERSION
3
+ * Based on Rails RubyVariables.RAILS_VERSION routes of RubyVariables.APP_CLASS
4
+ */
5
+
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 = {
24
+ requiredParams(): string[];
25
+ toString(): string;
26
+ };
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
+
51
+ type RouteHelpers = Record<string, RouteHelper>;
52
+
53
+ type Configuration = {
54
+ prefix: string;
55
+ default_url_options: RouteParameters;
56
+ special_options_key: string;
57
+ serializer: Serializer;
58
+ };
59
+
60
+ interface RouterExposedMethods {
61
+ config(): Configuration;
62
+ configure(arg: Partial<Configuration>): Configuration;
63
+ serialize: Serializer;
64
+ }
65
+
66
+ type KeywordUrlOptions = Optional<{
67
+ host: string;
68
+ protocol: string;
69
+ subdomain: string;
70
+ port: string | number;
71
+ anchor: string;
72
+ trailing_slash: boolean;
73
+ }>;
74
+
75
+ type RouteOptions = KeywordUrlOptions & RouteParameters;
76
+
77
+ type PartsTable = Record<string, { r?: boolean; d?: OptionalRouteParameter }>;
78
+
79
+ type ModuleType = "CJS" | "AMD" | "UMD" | "ESM" | "DTS" | "NIL";
80
+
81
+ declare const RubyVariables: {
82
+ PREFIX: string;
83
+ DEPRECATED_GLOBBING_BEHAVIOR: boolean;
84
+ SPECIAL_OPTIONS_KEY: string;
85
+ DEFAULT_URL_OPTIONS: RouteParameters;
86
+ SERIALIZER: Serializer;
87
+ NAMESPACE: string;
88
+ ROUTES_OBJECT: RouteHelpers;
89
+ MODULE_TYPE: ModuleType;
90
+ WRAPPER: <T>(callback: T) => T;
91
+ };
92
+
93
+ declare const define:
94
+ | undefined
95
+ | (((arg: unknown[], callback: () => unknown) => void) & { amd?: unknown });
96
+
97
+ declare const module: { exports: any } | undefined;
98
+
99
+ RubyVariables.WRAPPER(
100
+ (that: unknown): RouterExposedMethods => {
101
+ const hasProp = (value: unknown, key: string) =>
102
+ Object.prototype.hasOwnProperty.call(value, key);
103
+ enum NodeTypes {
104
+ GROUP = 1,
105
+ CAT = 2,
106
+ SYMBOL = 3,
107
+ OR = 4,
108
+ STAR = 5,
109
+ LITERAL = 6,
110
+ SLASH = 7,
111
+ DOT = 8,
112
+ }
113
+ type RouteNodes = {
114
+ [NodeTypes.GROUP]: { left: RouteTree; right: never };
115
+ [NodeTypes.STAR]: { left: RouteTree; right: never };
116
+ [NodeTypes.LITERAL]: { left: string; right: never };
117
+ [NodeTypes.SLASH]: { left: "/"; right: never };
118
+ [NodeTypes.DOT]: { left: "."; right: never };
119
+ [NodeTypes.CAT]: { left: RouteTree; right: RouteTree };
120
+ [NodeTypes.SYMBOL]: { left: string; right: never };
121
+ };
122
+ type RouteNode<T extends keyof RouteNodes> = [
123
+ T,
124
+ RouteNodes[T]["left"],
125
+ RouteNodes[T]["right"]
126
+ ];
127
+ type RouteTree = {
128
+ [T in keyof RouteNodes]: RouteNode<T>;
129
+ }[keyof RouteNodes];
130
+
131
+ const Root = that;
132
+ const isBroswer = typeof window !== "undefined";
133
+ type ModuleDefinition = {
134
+ define: (routes: RouterExposedMethods) => void;
135
+ isSupported: () => boolean;
136
+ };
137
+ const ModuleReferences: Record<ModuleType, ModuleDefinition> = {
138
+ CJS: {
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
+ },
146
+ },
147
+ AMD: {
148
+ define(routes) {
149
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
150
+ define!([], function () {
151
+ return routes;
152
+ });
153
+ },
154
+ isSupported() {
155
+ return typeof define === "function" && !!define.amd;
156
+ },
157
+ },
158
+ UMD: {
159
+ define(routes) {
160
+ if (ModuleReferences.AMD.isSupported()) {
161
+ ModuleReferences.AMD.define(routes);
162
+ } else {
163
+ if (ModuleReferences.CJS.isSupported()) {
164
+ try {
165
+ ModuleReferences.CJS.define(routes);
166
+ } catch (error) {
167
+ if (error.name !== "TypeError") throw error;
168
+ }
169
+ }
170
+ }
171
+ },
172
+ isSupported() {
173
+ return (
174
+ ModuleReferences.AMD.isSupported() ||
175
+ ModuleReferences.CJS.isSupported()
176
+ );
177
+ },
178
+ },
179
+ ESM: {
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 !RubyVariables.NAMESPACE || !!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
+ },
204
+ },
205
+ };
206
+
207
+ class ParametersMissing extends Error {
208
+ readonly keys: string[];
209
+ constructor(...keys: string[]) {
210
+ super(`Route missing required keys: ${keys.join(", ")}`);
211
+ this.keys = keys;
212
+ Object.setPrototypeOf(this, Object.getPrototypeOf(this));
213
+ this.name = ParametersMissing.name;
214
+ }
215
+ }
216
+
217
+ const UriEncoderSegmentRegex = /[^a-zA-Z0-9\-._~!$&'()*+,;=:@]/g;
218
+
219
+ const ReservedOptions = [
220
+ "anchor",
221
+ "trailing_slash",
222
+ "subdomain",
223
+ "host",
224
+ "port",
225
+ "protocol",
226
+ ] as const;
227
+ type ReservedOption = typeof ReservedOptions[any];
228
+
229
+ class UtilsClass {
230
+ configuration: Configuration = {
231
+ prefix: RubyVariables.PREFIX,
232
+ default_url_options: RubyVariables.DEFAULT_URL_OPTIONS,
233
+ special_options_key: RubyVariables.SPECIAL_OPTIONS_KEY,
234
+ serializer:
235
+ RubyVariables.SERIALIZER || this.default_serializer.bind(this),
236
+ };
237
+
238
+ default_serializer(value: any, prefix?: string | null): string {
239
+ if (this.is_nullable(value)) {
240
+ return "";
241
+ }
242
+ if (!prefix && !this.is_object(value)) {
243
+ throw new Error("Url parameters should be a javascript hash");
244
+ }
245
+ prefix = prefix || "";
246
+ const result: string[] = [];
247
+ if (this.is_array(value)) {
248
+ for (const element of value) {
249
+ result.push(this.default_serializer(element, prefix + "[]"));
250
+ }
251
+ } else if (this.is_object(value)) {
252
+ for (let key in value) {
253
+ if (!hasProp(value, key)) continue;
254
+ let prop = value[key];
255
+ if (this.is_nullable(prop) && prefix) {
256
+ prop = "";
257
+ }
258
+ if (this.is_not_nullable(prop)) {
259
+ if (prefix) {
260
+ key = prefix + "[" + key + "]";
261
+ }
262
+ result.push(this.default_serializer(prop, key));
263
+ }
264
+ }
265
+ } else {
266
+ if (this.is_not_nullable(value)) {
267
+ result.push(
268
+ encodeURIComponent(prefix) + "=" + encodeURIComponent("" + value)
269
+ );
270
+ }
271
+ }
272
+ return result.join("&");
273
+ }
274
+
275
+ serialize(object: Serializable): string {
276
+ return this.configuration.serializer(object);
277
+ }
278
+
279
+ extract_options(
280
+ number_of_params: number,
281
+ args: OptionalRouteParameter[]
282
+ ): {
283
+ args: OptionalRouteParameter[];
284
+ options: RouteOptions;
285
+ } {
286
+ const last_el = args[args.length - 1];
287
+ if (
288
+ (args.length > number_of_params && last_el === 0) ||
289
+ (this.is_object(last_el) &&
290
+ !this.looks_like_serialized_model(last_el))
291
+ ) {
292
+ if (this.is_object(last_el)) {
293
+ delete last_el[this.configuration.special_options_key];
294
+ }
295
+ return {
296
+ args: args.slice(0, args.length - 1),
297
+ options: (last_el as any) as RouteOptions,
298
+ };
299
+ } else {
300
+ return { args, options: {} };
301
+ }
302
+ }
303
+
304
+ looks_like_serialized_model(object: any): object is ModelRouteParameter {
305
+ return (
306
+ this.is_object(object) &&
307
+ !(this.configuration.special_options_key in object) &&
308
+ ("id" in object || "to_param" in object || "toParam" in object)
309
+ );
310
+ }
311
+
312
+ path_identifier(object: QueryRouteParameter): string {
313
+ const result = this.unwrap_path_identifier(object);
314
+ return this.is_nullable(result) || result === false ? "" : "" + result;
315
+ }
316
+
317
+ unwrap_path_identifier(object: QueryRouteParameter): unknown {
318
+ let result: any = object;
319
+ if (!this.is_object(object)) {
320
+ return object;
321
+ }
322
+ if ("to_param" in object) {
323
+ result = object.to_param;
324
+ } else if ("toParam" in object) {
325
+ result = object.toParam;
326
+ } else if ("id" in object) {
327
+ result = object.id;
328
+ } else {
329
+ result = object;
330
+ }
331
+ return this.is_callable(result) ? result.call(object) : result;
332
+ }
333
+
334
+ partition_parameters(
335
+ parts: string[],
336
+ required_params: string[],
337
+ default_options: RouteParameters,
338
+ call_arguments: OptionalRouteParameter[]
339
+ ): {
340
+ keyword_parameters: KeywordUrlOptions;
341
+ query_parameters: RouteParameters;
342
+ } {
343
+ // eslint-disable-next-line prefer-const
344
+ let { args, options } = this.extract_options(
345
+ parts.length,
346
+ call_arguments
347
+ );
348
+ if (args.length > parts.length) {
349
+ throw new Error("Too many parameters provided for path");
350
+ }
351
+ let use_all_parts = args.length > required_params.length;
352
+ const parts_options: RouteParameters = {
353
+ ...this.configuration.default_url_options,
354
+ };
355
+ for (const key in options) {
356
+ const value = options[key];
357
+ if (!hasProp(options, key)) continue;
358
+ use_all_parts = true;
359
+ if (parts.includes(key)) {
360
+ parts_options[key] = value;
361
+ }
362
+ }
363
+ options = {
364
+ ...this.configuration.default_url_options,
365
+ ...default_options,
366
+ ...options,
367
+ };
368
+
369
+ const keyword_parameters: KeywordUrlOptions = {};
370
+ const query_parameters: RouteParameters = {};
371
+ for (const key in options) {
372
+ if (!hasProp(options, key)) continue;
373
+ const value = options[key];
374
+ if (this.is_reserved_option(key)) {
375
+ keyword_parameters[key] = value as any;
376
+ } else {
377
+ if (
378
+ !this.is_nullable(value) &&
379
+ (value !== default_options[key] || required_params.includes(key))
380
+ ) {
381
+ query_parameters[key] = value;
382
+ }
383
+ }
384
+ }
385
+ const route_parts = use_all_parts ? parts : required_params;
386
+ let i = 0;
387
+ for (const part of route_parts) {
388
+ if (i < args.length) {
389
+ const value = args[i];
390
+ if (!hasProp(parts_options, part)) {
391
+ query_parameters[part] = value;
392
+ ++i;
393
+ }
394
+ }
395
+ }
396
+ return { keyword_parameters, query_parameters };
397
+ }
398
+ build_route(
399
+ parts: string[],
400
+ required_params: string[],
401
+ default_options: RouteParameters,
402
+ route: RouteTree,
403
+ absolute: boolean,
404
+ args: OptionalRouteParameter[]
405
+ ): string {
406
+ const {
407
+ keyword_parameters,
408
+ query_parameters,
409
+ } = this.partition_parameters(
410
+ parts,
411
+ required_params,
412
+ default_options,
413
+ args
414
+ );
415
+ const missing_params = required_params.filter(
416
+ (param) =>
417
+ !hasProp(query_parameters, param) ||
418
+ this.is_nullable(query_parameters[param])
419
+ );
420
+ if (missing_params.length) {
421
+ throw new ParametersMissing(...missing_params);
422
+ }
423
+ let result = this.get_prefix() + this.visit(route, query_parameters);
424
+ if (keyword_parameters.trailing_slash) {
425
+ result = result.replace(/(.*?)[/]?$/, "$1/");
426
+ }
427
+ const url_params = this.serialize(query_parameters);
428
+ if (url_params.length) {
429
+ result += "?" + url_params;
430
+ }
431
+ result += keyword_parameters.anchor
432
+ ? "#" + keyword_parameters.anchor
433
+ : "";
434
+ if (absolute) {
435
+ result = this.route_url(keyword_parameters) + result;
436
+ }
437
+ return result;
438
+ }
439
+
440
+ visit(
441
+ route: RouteTree,
442
+ parameters: RouteParameters,
443
+ optional = false
444
+ ): string {
445
+ switch (route[0]) {
446
+ case NodeTypes.GROUP:
447
+ return this.visit(route[1], parameters, true);
448
+ case NodeTypes.CAT:
449
+ return this.visit_cat(route, parameters, optional);
450
+ case NodeTypes.SYMBOL:
451
+ return this.visit_symbol(route, parameters, optional);
452
+ case NodeTypes.STAR:
453
+ return this.visit_globbing(route[1], parameters, true);
454
+ case NodeTypes.LITERAL:
455
+ case NodeTypes.SLASH:
456
+ case NodeTypes.DOT:
457
+ return route[1];
458
+ default:
459
+ throw new Error("Unknown Rails node type");
460
+ }
461
+ }
462
+
463
+ is_not_nullable<T>(object: T): object is NonNullable<T> {
464
+ return !this.is_nullable(object);
465
+ }
466
+
467
+ is_nullable(object: unknown): object is null | undefined {
468
+ return object === undefined || object === null;
469
+ }
470
+
471
+ visit_cat(
472
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
473
+ [_type, left, right]: RouteNode<NodeTypes.CAT>,
474
+ parameters: RouteParameters,
475
+ optional: boolean
476
+ ): string {
477
+ const left_part = this.visit(left, parameters, optional);
478
+ let right_part = this.visit(right, parameters, optional);
479
+ if (
480
+ optional &&
481
+ ((this.is_optional_node(left[0]) && !left_part) ||
482
+ (this.is_optional_node(right[0]) && !right_part))
483
+ ) {
484
+ return "";
485
+ }
486
+ // if left_part ends on '/' and right_part starts on '/'
487
+ if (left_part[left_part.length - 1] === "/" && right_part[0] === "/") {
488
+ // strip slash from right_part
489
+ // to prevent double slash
490
+ right_part = right_part.substring(1);
491
+ }
492
+ return left_part + right_part;
493
+ }
494
+
495
+ visit_symbol(
496
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
497
+ [_type, key]: RouteNode<NodeTypes.SYMBOL>,
498
+ parameters: RouteParameters,
499
+ optional: boolean
500
+ ): string {
501
+ const value = this.path_identifier(parameters[key]);
502
+ delete parameters[key];
503
+ if (value.length) {
504
+ return this.encode_segment(value);
505
+ }
506
+ if (optional) {
507
+ return "";
508
+ } else {
509
+ throw new ParametersMissing(key);
510
+ }
511
+ }
512
+
513
+ encode_segment(segment: string): string {
514
+ return segment.replace(UriEncoderSegmentRegex, (str) =>
515
+ encodeURIComponent(str)
516
+ );
517
+ }
518
+
519
+ is_optional_node(node: NodeTypes): boolean {
520
+ return [NodeTypes.STAR, NodeTypes.SYMBOL, NodeTypes.CAT].includes(node);
521
+ }
522
+
523
+ build_path_spec(route: RouteTree, wildcard = false): string {
524
+ let key: string;
525
+ switch (route[0]) {
526
+ case NodeTypes.GROUP:
527
+ return "(" + this.build_path_spec(route[1]) + ")";
528
+ case NodeTypes.CAT:
529
+ return (
530
+ this.build_path_spec(route[1]) + this.build_path_spec(route[2])
531
+ );
532
+ case NodeTypes.STAR:
533
+ return this.build_path_spec(route[1], true);
534
+ case NodeTypes.SYMBOL:
535
+ key = route[1];
536
+ if (wildcard) {
537
+ return (key.startsWith("*") ? "" : "*") + key;
538
+ } else {
539
+ return ":" + key;
540
+ }
541
+ break;
542
+ case NodeTypes.SLASH:
543
+ case NodeTypes.DOT:
544
+ case NodeTypes.LITERAL:
545
+ return route[1];
546
+ default:
547
+ throw new Error("Unknown Rails node type");
548
+ }
549
+ }
550
+
551
+ visit_globbing(
552
+ route: RouteTree,
553
+ parameters: RouteParameters,
554
+ optional: boolean
555
+ ): string {
556
+ const key = route[1] as string;
557
+ let value = parameters[key];
558
+ delete parameters[key];
559
+ if (this.is_nullable(value)) {
560
+ return this.visit(route, parameters, optional);
561
+ }
562
+ if (this.is_array(value)) {
563
+ value = value.join("/");
564
+ }
565
+ const result = this.path_identifier(value as any);
566
+ return RubyVariables.DEPRECATED_GLOBBING_BEHAVIOR
567
+ ? result
568
+ : encodeURI(result);
569
+ }
570
+
571
+ get_prefix(): string {
572
+ const prefix = this.configuration.prefix;
573
+ return prefix.match("/$")
574
+ ? prefix.substring(0, prefix.length - 1)
575
+ : prefix;
576
+ }
577
+
578
+ route(
579
+ parts_table: PartsTable,
580
+ route_spec: RouteTree,
581
+ absolute = false
582
+ ): RouteHelper {
583
+ const required_params: string[] = [];
584
+ const parts: string[] = [];
585
+ const default_options: RouteParameters = {};
586
+ for (const [part, { r: required, d: value }] of Object.entries(
587
+ parts_table
588
+ )) {
589
+ parts.push(part);
590
+ if (required) {
591
+ required_params.push(part);
592
+ }
593
+ if (this.is_not_nullable(value)) {
594
+ default_options[part] = value;
595
+ }
596
+ }
597
+ const result = (...args: OptionalRouteParameter[]): string => {
598
+ return this.build_route(
599
+ parts,
600
+ required_params,
601
+ default_options,
602
+ route_spec,
603
+ absolute,
604
+ args
605
+ );
606
+ };
607
+ result.requiredParams = () => required_params;
608
+ result.toString = () => {
609
+ return this.build_path_spec(route_spec);
610
+ };
611
+ return result as any;
612
+ }
613
+
614
+ route_url(route_defaults: KeywordUrlOptions): string {
615
+ const hostname = route_defaults.host || this.current_host();
616
+ if (!hostname) {
617
+ return "";
618
+ }
619
+ const subdomain = route_defaults.subdomain
620
+ ? route_defaults.subdomain + "."
621
+ : "";
622
+ const protocol = route_defaults.protocol || this.current_protocol();
623
+ let port =
624
+ route_defaults.port ||
625
+ (!route_defaults.host ? this.current_port() : undefined);
626
+ port = port ? ":" + port : "";
627
+ return protocol + "://" + subdomain + hostname + port;
628
+ }
629
+
630
+ current_host(): string {
631
+ return (isBroswer && window?.location?.hostname) || "";
632
+ }
633
+
634
+ current_protocol(): string {
635
+ return (
636
+ (isBroswer && window?.location?.protocol?.replace(/:$/, "")) || "http"
637
+ );
638
+ }
639
+
640
+ current_port(): string {
641
+ return (isBroswer && window?.location?.port) || "";
642
+ }
643
+
644
+ is_object(value: unknown): value is Record<string, unknown> {
645
+ return (
646
+ typeof value === "object" &&
647
+ Object.prototype.toString.call(value) === "[object Object]"
648
+ );
649
+ }
650
+
651
+ is_array<T>(object: unknown | T[]): object is T[] {
652
+ return object instanceof Array;
653
+ }
654
+
655
+ is_callable(object: unknown): object is Function {
656
+ return typeof object === "function" && !!object.call;
657
+ }
658
+
659
+ is_reserved_option(key: unknown): key is ReservedOption {
660
+ return ReservedOptions.includes(key as any);
661
+ }
662
+
663
+ namespace(
664
+ object: any,
665
+ namespace: string | null | undefined,
666
+ routes: unknown
667
+ ): void {
668
+ const parts = namespace?.split(".") || [];
669
+ if (parts.length === 0) {
670
+ return;
671
+ }
672
+ for (let index = 0; index < parts.length; index++) {
673
+ const part = parts[index];
674
+ if (index < parts.length - 1) {
675
+ object = object[part] || (object[part] = {});
676
+ } else {
677
+ object[part] = routes;
678
+ }
679
+ }
680
+ }
681
+
682
+ configure(new_config: Partial<Configuration>): Configuration {
683
+ this.configuration = { ...this.configuration, ...new_config };
684
+ return this.configuration;
685
+ }
686
+
687
+ config(): Configuration {
688
+ return { ...this.configuration };
689
+ }
690
+
691
+ is_module_supported(name: ModuleType): boolean {
692
+ return ModuleReferences[name].isSupported();
693
+ }
694
+
695
+ ensure_module_supported(name: ModuleType): void {
696
+ if (!this.is_module_supported(name)) {
697
+ throw new Error(`${name} is not supported by runtime`);
698
+ }
699
+ }
700
+
701
+ define_module(name: ModuleType, module: RouterExposedMethods): void {
702
+ this.ensure_module_supported(name);
703
+ ModuleReferences[name].define(module);
704
+ }
705
+ }
706
+
707
+ const Utils = new UtilsClass();
708
+
709
+ // We want this helper name to be short
710
+ const __jsr = {
711
+ r(
712
+ parts_table: PartsTable,
713
+ route_spec: RouteTree,
714
+ absolute?: boolean
715
+ ): RouteHelper {
716
+ return Utils.route(parts_table, route_spec, absolute);
717
+ },
718
+ };
719
+
720
+ const result = {
721
+ ...__jsr,
722
+ configure: (config: Partial<Configuration>) => {
723
+ return Utils.configure(config);
724
+ },
725
+ config: (): Configuration => {
726
+ return Utils.config();
727
+ },
728
+ serialize: (object: Serializable): string => {
729
+ return Utils.serialize(object);
730
+ },
731
+ ...RubyVariables.ROUTES_OBJECT,
732
+ };
733
+
734
+ Utils.define_module(RubyVariables.MODULE_TYPE, result);
735
+ return result;
736
+ }
737
+ )(this);