@owlmeans/client-entrypoint 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,12 +1,12 @@
1
1
  import type { ApiClient } from '@owlmeans/api'
2
2
  import { DEFAULT_KEY } from '@owlmeans/client-config'
3
3
  import type { ClientConfig, ClientContext } from '@owlmeans/client-context'
4
- import type { AbstractRequest, EntrypointHandler } from '@owlmeans/entrypoint'
5
- import { EntrypointOutcome, provideResponse } from '@owlmeans/entrypoint'
6
- import type { ClientEntrypoint, EntrypointCall, ClientEntrypointOptions, EntrypointRef, ClientRequest } from '../types.js'
4
+ import type { AbstractRequest, EntrypointHandler, EntrypointTransport } from '@owlmeans/entrypoint'
5
+ import { EntrypointOutcome, provideResponse, transportAlias } from '@owlmeans/entrypoint'
6
+ import type { ClientEntrypoint, EntrypointInvoke, EntrypointUrlOptions, ClientEntrypointOptions, EntrypointRef, ClientRequest } from '../types.js'
7
7
  import { validate } from './entrypoint.js'
8
8
  import { extractParams } from '@owlmeans/client-route'
9
- import { PARAM } from '@owlmeans/route'
9
+ import { PARAM, RouteMethod, RouteProtocols } from '@owlmeans/route'
10
10
  import { stringify } from 'qs'
11
11
  import { assertContext } from '@owlmeans/context'
12
12
  import { makeSecurityHelper } from '@owlmeans/config'
@@ -25,13 +25,24 @@ export const apiHandler: <
25
25
  }
26
26
 
27
27
  const ep = context.entrypoint<ClientEntrypoint>(req.alias)
28
- const route = await ep.route.resolve<Config, Context>(context)
29
28
 
30
- let alias: string | undefined = typeof context.cfg.webService === 'string'
29
+ // A route names the protocol it answers on, and a protocol may be carried by something other than
30
+ // HTTP — a queue, say. Whatever is bound under this protocol takes the call, so a consumer writes
31
+ // `ep.call(...)` and never learns which of them ran.
32
+ const transport = transportAlias(ep.route.route.protocol)
33
+ if (context.hasService(transport)) {
34
+ req.path = ep.path()
35
+
36
+ return context.service<EntrypointTransport>(transport).handle(req, res)
37
+ }
38
+
39
+ // Which web client carries this call is decided by the service the route answers on — the one it
40
+ // names, or the one the context picks for its app type when it names none.
41
+ const route = ep.service()
42
+
43
+ const alias: string | undefined = typeof context.cfg.webService === 'string'
31
44
  ? context.cfg.webService
32
- : (route.service != null
33
- ? context.cfg.webService[route.service] ?? context.cfg.webService[DEFAULT_KEY]
34
- : context.cfg.webService[DEFAULT_KEY])
45
+ : context.cfg.webService[route.service] ?? context.cfg.webService[DEFAULT_KEY]
35
46
 
36
47
  if (alias == null) {
37
48
  throw new SyntaxError(`Can't cast web service alias for ${ep.alias} entrypoint`)
@@ -39,15 +50,15 @@ export const apiHandler: <
39
50
 
40
51
  const service: ApiClient = context.service(alias)
41
52
 
42
- req.path = ep.getPath()
53
+ req.path = ep.path()
43
54
 
44
55
  return service.handler(req, res as any)
45
56
  }
46
57
 
47
- export const apiCall: <
58
+ export const apiInvoke: <
48
59
  T, R extends AbstractRequest = AbstractRequest
49
- >(ref: EntrypointRef<T, R>, opts?: ClientEntrypointOptions) => EntrypointCall<T, R> =
50
- (ref, opts) => (async (req, res) => {
60
+ >(ref: EntrypointRef<T, R>, opts?: ClientEntrypointOptions) => EntrypointInvoke<T, R> =
61
+ (ref, opts) => (async (req) => {
51
62
  const ep = ref.ref
52
63
  if (ep == null) {
53
64
  throw new SyntaxError('Try to make API call before the entrypoint is created')
@@ -56,21 +67,24 @@ export const apiCall: <
56
67
  if (ctx == null) {
57
68
  throw new SyntaxError(`No context provided in apiCall for ${ep.alias} entrypoint`)
58
69
  }
59
- await ep.route.resolve(ctx)
60
70
 
61
71
  if (req?.canceled) {
62
- return
72
+ return { value: null, outcome: EntrypointOutcome.Ok }
63
73
  }
64
74
 
65
75
  const request: AbstractRequest = {
66
76
  alias: ep.alias,
67
77
  params: req?.params ?? {},
68
- body: req?.body,
78
+ // JSON transports must send a body for mutation methods even when the contract has no
79
+ // payload. Keeping that rule here makes every typed caller use `call({ params })`, never
80
+ // an SDK-specific `{ body: {} }` workaround.
81
+ body: req?.body ?? ([RouteMethod.POST, RouteMethod.PUT, RouteMethod.PATCH]
82
+ .includes(ep.route.route.method ?? RouteMethod.GET) ? {} : undefined),
69
83
  headers: req?.headers ?? {},
70
84
  query: req?.query ?? {},
71
85
  host: req?.host,
72
86
  base: req?.base,
73
- path: ep.getPath(),
87
+ path: ep.path(),
74
88
  timeout: req?.timeout,
75
89
  signal: req?.signal,
76
90
  }
@@ -89,10 +103,6 @@ export const apiCall: <
89
103
  throw e
90
104
  }
91
105
  }
92
- if (res != null) {
93
- await apiHandler(ref)(request, res)
94
- return
95
- }
96
106
  const reply = provideResponse<unknown>()
97
107
  if (ctx == null && ep.ctx == null) {
98
108
  throw new SyntaxError(`Use entrypoint ${ep.alias} without context`)
@@ -102,37 +112,44 @@ export const apiCall: <
102
112
  throw reply.error
103
113
  }
104
114
 
105
- return [reply.value ?? null, reply.outcome ?? EntrypointOutcome.Ok]
106
- }) as EntrypointCall<any>
115
+ return { value: reply.value ?? null, outcome: reply.outcome ?? EntrypointOutcome.Ok }
116
+ }) as EntrypointInvoke<any>
107
117
 
108
- export const urlCall: <
118
+ export const entrypointUrl: <
109
119
  T, R extends ClientRequest = ClientRequest
110
- >(ref: EntrypointRef<T, R>, opts?: ClientEntrypointOptions) => EntrypointCall<T, R> = ref => async (req, res) => {
120
+ >(ref: EntrypointRef<T, R>, req?: Partial<R>, opts?: EntrypointUrlOptions) => Promise<string> = async (ref, req, opts) => {
111
121
  const ep = ref.ref
112
122
  if (ep == null) {
113
123
  throw new SyntaxError('Try to make URL before the entrypoint is created')
114
124
  }
115
125
  const ctx = ep.ctx
116
126
  if (ctx == null) {
117
- throw new SyntaxError(`No context provided in urlCall for ${ep.alias} entrypoint`)
127
+ throw new SyntaxError(`No context provided in entrypointUrl for ${ep.alias} entrypoint`)
118
128
  }
119
- await ep.route.resolve(ctx)
120
129
 
121
- const pathParams = extractParams(ep.getPath())
130
+ // The base is left off here: an in-service URL is addressed relative to it, and the absolute
131
+ // branch below has `makeUrl` prepend it.
132
+ const epPath = ep.path()
133
+ const pathParams = extractParams(epPath)
122
134
  let path = pathParams.reduce((p, param) => {
123
135
  return p.replace(`${PARAM}${param}`, `${req?.params?.[param as keyof typeof req.params]}`)
124
- }, ep.getPath()) + (req?.query != null ? `?${stringify(req?.query)}` : '')
125
-
126
- if (ep.route.route.service !== null && (
127
- ctx.cfg.service !== ep.route.route.service
128
- || req?.full === true)) {
136
+ }, epPath) + (req?.query != null ? `?${stringify(req?.query)}` : '')
137
+
138
+ // A socket address is always absolute, whoever owns the route. `new WebSocket(path)` resolves a
139
+ // relative value against the PAGE's origin, which in a split deployment is the web host and not
140
+ // the service that answers the upgrade — so the handshake goes somewhere that never speaks it
141
+ // and dies on a gateway timeout. Only a same-origin HTTP route benefits from staying relative.
142
+ const socket = ep.route.route.protocol === RouteProtocols.SOCKET
143
+
144
+ // Locality is a question about the RESOLVED service, not about what the declaration happens to
145
+ // name: a route that names no service belongs to the asking context, and addressing it absolutely
146
+ // would turn in-app navigation into a full page load.
147
+ if (socket || !ep.isLocal() || opts?.absolute === true) {
129
148
  const helper = makeSecurityHelper(ctx)
130
149
  path = helper.makeUrl(
131
- ep.route.route, path, { host: req?.host, base: req?.base, forceUnsecure: req?.unsecure }
150
+ ep.address(), path, { host: req?.host, base: req?.base, forceUnsecure: req?.unsecure }
132
151
  )
133
152
  }
134
153
 
135
- res?.resolve(path as any, EntrypointOutcome.Ok)
136
-
137
- return [path as any, EntrypointOutcome.Ok]
154
+ return path
138
155
  }