@owlmeans/route 0.1.18-rc.2 → 0.1.18-rc.21

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.
@@ -1,121 +1,151 @@
1
1
  import type { BasicConfig, BasicContext, Contextual } from '@owlmeans/context'
2
- import type { CommonRoute, CommonRouteModel, CommonServiceRoute } from '../types.js'
2
+ import type { RouteAddress, RouteDeclaration, RouteModel, CommonServiceRoute, ResolvedServiceRoute } from '../types.js'
3
3
  import { isServiceRoute, isServiceRouteResolved } from './service.js'
4
4
  import { normalizePath } from '../helper.js'
5
- import { SEP } from '../consts.js'
5
+ import { RouteProtocols, SEP } from '../consts.js'
6
6
 
7
7
  type Config = BasicConfig
8
8
 
9
- export const getParentRoute = async <C extends Config, T extends BasicContext<C>>(context: T, route: CommonRoute): Promise<CommonRoute | null> => {
10
- if (route.parent != null) {
11
- const parent = context.module<Contextual & { _module: true, route: CommonRouteModel, resolve?: () => Promise<void> }>(route.parent)
12
- assertCycle<C, T>(context, route, parent.route.route)
13
- if (parent.route == null) {
14
- throw new SyntaxError('Parent module doesn\'t provide a route')
15
- }
16
- if (!parent.route.route.resolved) {
17
- if (parent.resolve != null) {
18
- await parent.resolve()
19
- } else {
20
- return await parent.route.resolve<C, T>(context)
21
- }
22
- }
23
- return parent.route.route
9
+ /** What an entrypoint looks like from the route layer: something that carries a route model. */
10
+ interface RouteCarrier extends Contextual {
11
+ _entrypoint: true
12
+ route: RouteModel
13
+ }
14
+
15
+ const carrier = <C extends Config, T extends BasicContext<C>>(context: T, alias: string): RouteCarrier =>
16
+ context.entrypoint<RouteCarrier>(alias)
17
+
18
+ export const getParentRoute = <C extends Config, T extends BasicContext<C>>(
19
+ context: T, route: RouteDeclaration
20
+ ): RouteDeclaration | null => {
21
+ if (route.parent == null) {
22
+ return null
24
23
  }
24
+ const parent = carrier<C, T>(context, route.parent)
25
+ if (parent.route == null) {
26
+ throw new SyntaxError('Parent entrypoint doesn\'t provide a route')
27
+ }
28
+ assertCycle<C, T>(context, route, parent.route.route)
25
29
 
26
- return null
30
+ return parent.route.route
27
31
  }
28
32
 
29
- export const overrideParams = (route: CommonRoute, overrides?: Partial<CommonRoute>, filter?: string[]) => {
33
+ export const overrideParams = (route: RouteDeclaration, overrides?: Partial<RouteDeclaration>, filter?: string[]) => {
30
34
  Object.entries(overrides ?? {}).forEach(([key, value]) => {
31
- if (route[key as keyof CommonRoute] == null
35
+ if (route[key as keyof RouteDeclaration] == null
32
36
  && (filter == null || filter.includes(key))) {
33
- (route[key as keyof CommonRoute] as CommonRoute[keyof CommonRoute]) = value
37
+ (route[key as keyof RouteDeclaration] as RouteDeclaration[keyof RouteDeclaration]) = value
34
38
  }
35
39
  })
36
40
  }
37
41
 
38
- export const resolve = <C extends Config, T extends BasicContext<C>>(route: CommonRoute) => async (context: T) => {
39
- if (route.resolved) return route
40
- route.resolved = true
41
-
42
+ /**
43
+ * Pick the service route this declaration answers on: the one it names, else the default of its
44
+ * app type, else the first of its app type.
45
+ *
46
+ * @throws {SyntaxError}
47
+ */
48
+ export const resolveService = <C extends Config, T extends BasicContext<C>>(
49
+ context: T, route: RouteDeclaration
50
+ ): ResolvedServiceRoute => {
42
51
  if (context.cfg.services == null) {
43
52
  throw new SyntaxError('Services aren\'t configured to resolve routes')
44
53
  }
45
54
 
46
- const firstGuessService = context.cfg.services[route.service ?? context.cfg.service]
47
- if (firstGuessService != null && !isServiceRoute(firstGuessService)) {
55
+ const named = context.cfg.services[route.service ?? context.cfg.service]
56
+ if (named != null && !isServiceRoute(named)) {
48
57
  throw new SyntaxError('Service is not a valid service route')
49
58
  }
50
- const service = firstGuessService?.type === route.type ? firstGuessService
59
+
60
+ const service = named?.type === route.type ? named
51
61
  : Object.values(context.cfg.services).find<CommonServiceRoute>(
52
- (service): service is CommonServiceRoute => {
53
- const _service = service as CommonServiceRoute
54
- return _service.default === true && _service.type === route.type
62
+ (candidate): candidate is CommonServiceRoute => {
63
+ const _candidate = candidate as CommonServiceRoute
64
+ return _candidate.default === true && _candidate.type === route.type
55
65
  }
56
66
  ) ?? Object.values(context.cfg.services).find<CommonServiceRoute>(
57
- (service): service is CommonServiceRoute => {
58
- const _service = service as CommonServiceRoute
59
- return _service.type === route.type
60
- }
67
+ (candidate): candidate is CommonServiceRoute => (candidate as CommonServiceRoute).type === route.type
61
68
  )
69
+
62
70
  if (!isServiceRoute(service)) {
63
71
  throw new SyntaxError('Service is not a valid service route')
64
72
  }
65
- if (!service.resolved && service.host != null) {
66
- service.resolved = true
67
- }
68
73
  if (!isServiceRouteResolved(service)) {
69
74
  throw new SyntaxError('Service route is not resolved')
70
75
  }
71
76
 
72
- overrideParams(route, service, ['host', 'port', 'service', 'base'])
77
+ return service
78
+ }
73
79
 
74
- // Internal host access is non secure only
75
- // it's required for server side client routes
76
- if ("internalHost" in route) {
77
- if (route.internalHost === route.host) {
78
- route.secure = false
79
- }
80
- } else if ("internalHost" in service) {
81
- if (service.internalHost === route.host) {
82
- route.secure = false
83
- }
80
+ /**
81
+ * The full path: every ancestor's declared segment, then this one. Walks the parent chain through
82
+ * the context, so a declaration always states only what it contributes.
83
+ */
84
+ export const resolvePath = <C extends Config, T extends BasicContext<C>>(
85
+ context: T, route: RouteDeclaration
86
+ ): string => {
87
+ const parent = getParentRoute<C, T>(context, route)
88
+ if (parent == null) {
89
+ return route.path
84
90
  }
85
-
91
+ const parentPath = resolvePath<C, T>(context, parent)
86
92
 
87
- const parent = await getParentRoute<C, T>(context, route)
88
- if (parent != null) {
89
- route.path = (parent.path.startsWith(SEP) ? SEP : '')
90
- + normalizePath(normalizePath(parent.path) + SEP + normalizePath(route.path))
91
- }
93
+ return (parentPath.startsWith(SEP) ? SEP : '')
94
+ + normalizePath(normalizePath(parentPath) + SEP + normalizePath(route.path))
95
+ }
92
96
 
93
- // Moved base resolution to the final service
94
- // if (parent == null && route.base != null && route.base.trim() !== '') {
95
- // route.path = SEP + normalizePath(route.base) + SEP + normalizePath(route.path)
96
- // // route.path = route.base.startsWith(SEP) ? SEP + route.path : route.path
97
- // }
97
+ /** `base` + the full path — what a server mounts and a client requests. */
98
+ export const resolveMount = <C extends Config, T extends BasicContext<C>>(
99
+ context: T, route: RouteDeclaration
100
+ ): string => {
101
+ const path = resolvePath<C, T>(context, route)
102
+ const base = route.base ?? resolveService<C, T>(context, route).base
98
103
 
99
- return route
104
+ return base != null && base.trim() !== ''
105
+ ? SEP + normalizePath(base) + SEP + normalizePath(path)
106
+ : path
100
107
  }
101
108
 
109
+ /**
110
+ * Where the route answers. A backend caller reaching a service over its INTERNAL host is inside the
111
+ * cluster, so that hop is never TLS.
112
+ */
113
+ export const resolveAddress = <C extends Config, T extends BasicContext<C>>(
114
+ context: T, route: RouteDeclaration
115
+ ): RouteAddress => {
116
+ const service = resolveService<C, T>(context, route)
117
+ const host = route.host ?? service.host
118
+ const internalHost = route.internalHost ?? service.internalHost
119
+
120
+ return {
121
+ host,
122
+ port: route.port ?? service.port,
123
+ base: route.base ?? service.base,
124
+ secure: internalHost != null && internalHost === host ? false : route.secure ?? true,
125
+ protocol: route.protocol ?? RouteProtocols.WEB,
126
+ }
127
+ }
128
+
129
+ /** Does this route belong to the service the asking context IS? */
130
+ export const isLocalRoute = <C extends Config, T extends BasicContext<C>>(
131
+ context: T, route: RouteDeclaration
132
+ ): boolean => (route.service ?? context.cfg.service) === context.cfg.service
133
+
102
134
  /**
103
135
  * @throws {SyntaxError}
104
136
  */
105
- const assertCycle = <C extends BasicConfig, T extends BasicContext<C>>(context: T, route: CommonRoute, parent: CommonRoute) => {
137
+ const assertCycle = <C extends BasicConfig, T extends BasicContext<C>>(
138
+ context: T, route: RouteDeclaration, parent: RouteDeclaration
139
+ ) => {
106
140
  while (parent.parent != null) {
107
141
  if (parent.parent === route.alias) {
108
142
  throw new SyntaxError(`Route parentship cycle detected. Parent: ${parent.alias} has his child as ancestor ${route.alias}`)
109
143
  }
110
- parent = context.module<Contextual & { _module: true, route: CommonRouteModel }>(parent.parent).route.route
144
+ parent = carrier<C, T>(context, parent.parent).route.route
111
145
  }
112
146
  }
113
147
 
114
- export const prependBase = (route: CommonRoute) => {
115
- let path = route.path
116
- if (route.base != null && route.base.trim() !== '') {
117
- path = SEP + normalizePath(route.base) + SEP + normalizePath(route.path)
118
- // route.path = route.base.startsWith(SEP) ? SEP + route.path : route.path
119
- }
120
- return path
121
- }
148
+ export const prependBase = (route: RouteDeclaration, path: string) =>
149
+ route.base != null && route.base.trim() !== ''
150
+ ? SEP + normalizePath(route.base) + SEP + normalizePath(path)
151
+ : path
@@ -5,5 +5,6 @@ export const isServiceRoute = (obj?: Object): obj is CommonServiceRoute =>
5
5
  obj != null && ('type' in obj) && ('service' in obj)
6
6
  && (Object.values(AppType).includes(obj.type as AppType))
7
7
 
8
+ /** A service route is usable exactly when it knows the host it answers on. */
8
9
  export const isServiceRouteResolved = (route: CommonServiceRoute): route is ResolvedServiceRoute =>
9
- route.resolved === true
10
+ route.host != null && route.host.trim() !== ''
package/build/.gitkeep DELETED
File without changes