@owlmeans/client-entrypoint 0.1.18-rc.5 → 0.1.18-rc.50

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