js-routes 1.4.1 → 2.1.0

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