@owlmeans/entrypoint 0.1.18-rc.4 → 0.1.18-rc.40

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 (55) hide show
  1. package/README.md +262 -60
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/entrypoint/SKILL.md +84 -48
  4. package/build/entrypoint.d.ts +0 -1
  5. package/build/entrypoint.d.ts.map +1 -1
  6. package/build/entrypoint.js +31 -57
  7. package/build/entrypoint.js.map +1 -1
  8. package/build/filter.d.ts +20 -5
  9. package/build/filter.d.ts.map +1 -1
  10. package/build/filter.js +7 -5
  11. package/build/filter.js.map +1 -1
  12. package/build/helper.d.ts +1 -2
  13. package/build/helper.d.ts.map +1 -1
  14. package/build/helper.js +0 -9
  15. package/build/helper.js.map +1 -1
  16. package/build/index.d.ts +4 -3
  17. package/build/index.d.ts.map +1 -1
  18. package/build/index.js +4 -3
  19. package/build/index.js.map +1 -1
  20. package/build/materialize.d.ts +7 -0
  21. package/build/materialize.d.ts.map +1 -0
  22. package/build/materialize.js +34 -0
  23. package/build/materialize.js.map +1 -0
  24. package/build/protocol.d.ts +44 -0
  25. package/build/protocol.d.ts.map +1 -0
  26. package/build/protocol.js +124 -0
  27. package/build/protocol.js.map +1 -0
  28. package/build/types.d.ts +228 -12
  29. package/build/types.d.ts.map +1 -1
  30. package/build/types.local.d.ts +27 -0
  31. package/build/types.local.d.ts.map +1 -0
  32. package/build/types.local.js +2 -0
  33. package/build/types.local.js.map +1 -0
  34. package/build/utils/entrypoint.d.ts.map +1 -1
  35. package/build/utils/entrypoint.js +1 -1
  36. package/build/utils/entrypoint.js.map +1 -1
  37. package/build/utils/types.d.ts +2 -2
  38. package/build/utils/types.d.ts.map +1 -1
  39. package/build/utils.d.ts +6 -0
  40. package/build/utils.d.ts.map +1 -0
  41. package/build/utils.js +6 -0
  42. package/build/utils.js.map +1 -0
  43. package/package.json +5 -5
  44. package/src/consts.ts +1 -0
  45. package/src/entrypoint.ts +33 -65
  46. package/src/filter.ts +9 -5
  47. package/src/helper.ts +1 -12
  48. package/src/index.ts +4 -3
  49. package/src/materialize.ts +43 -0
  50. package/src/protocol.ts +191 -0
  51. package/src/types.local.ts +24 -0
  52. package/src/types.ts +262 -12
  53. package/src/utils/entrypoint.ts +1 -1
  54. package/src/utils/types.ts +2 -2
  55. package/src/utils.ts +5 -0
@@ -0,0 +1,43 @@
1
+ import { entrypoint } from './entrypoint.js'
2
+ import type { CommonEntrypointOptions, Filter, EntrypointProtocolDeclaration, MaterializedEntrypoint } from './types.js'
3
+
4
+ const filterOf = (protocol: EntrypointProtocolDeclaration): Filter | undefined => {
5
+ const request = protocol.contract?.requestSchemas
6
+ const response = protocol.contract?.responseSchemas
7
+
8
+ if (request == null && response == null) return undefined
9
+
10
+ return {
11
+ body: request?.body == null ? undefined : structuredClone(request.body),
12
+ params: request?.params == null ? undefined : structuredClone(request.params),
13
+ query: request?.query == null ? undefined : structuredClone(request.query),
14
+ headers: request?.headers == null ? undefined : structuredClone(request.headers),
15
+ // Fastify requires response serializers to be keyed by a status code. A protocol's
16
+ // ordinary response has no explicit outcome, so it is the normal successful 200 reply.
17
+ response: response == null ? undefined : {
18
+ ...(response.default == null ? {} : { 200: structuredClone(response.default) }),
19
+ ...Object.fromEntries(Object.entries(response.byStatus ?? {}).map(([status, schema]) => [
20
+ status, structuredClone(schema),
21
+ ])),
22
+ },
23
+ }
24
+ }
25
+
26
+ const optionsOf = (protocol: EntrypointProtocolDeclaration): CommonEntrypointOptions => ({
27
+ sticky: protocol.sticky,
28
+ guards: [...protocol.guards],
29
+ gate: protocol.gate?.alias,
30
+ gateParams: protocol.gate == null ? undefined : [...protocol.gate.params],
31
+ filter: filterOf(protocol),
32
+ })
33
+
34
+ /**
35
+ * Make a local, mutable registration from a shared declaration. The protocol is never mutated;
36
+ * guards, route resolution and handlers belong exclusively to this materialized entrypoint.
37
+ */
38
+ export const materializeEntrypoint = <Protocol extends EntrypointProtocolDeclaration>(
39
+ protocol: Protocol,
40
+ ): MaterializedEntrypoint<Protocol> => Object.assign(
41
+ entrypoint({ route: { ...protocol.route.route } }, optionsOf(protocol)),
42
+ { protocol },
43
+ )
@@ -0,0 +1,191 @@
1
+ import type { EntrypointReference } from '@owlmeans/context'
2
+ import type { JSONSchemaType, AnySchemaObject } from 'ajv'
3
+ import type { RouteModel } from '@owlmeans/route'
4
+ import type { AnyShapeSource } from './types.local.js'
5
+ import type { EntrypointContract, EntrypointGate, EntrypointOptions, EntrypointProtocol, EntrypointProtocolDeclaration, EntrypointSchema, EntrypointTree, OpenRequest, OpenValue, RegisteredEntrypoint, RequestFromSources, RequestShape, RequestSources, RuntimeResponseSchemas, ShapeSource, Typed } from './types.js'
6
+
7
+ export const schema = <T>(value: JSONSchemaType<T>): EntrypointSchema<T> =>
8
+ value as EntrypointSchema<T>
9
+
10
+ /**
11
+ * Supplies type information where a response has no runtime schema, or attaches a type to an
12
+ * otherwise untyped schema literal at the declaration boundary.
13
+ */
14
+ export function typed<T>(): Typed<T>
15
+ export function typed<T>(schema: JSONSchemaType<T>): Typed<T>
16
+ export function typed<T>(schema?: JSONSchemaType<T>): Typed<T> {
17
+ return schema == null
18
+ ? { kind: 'entrypoint-type' }
19
+ : { kind: 'entrypoint-type', schema }
20
+ }
21
+
22
+ const schemaOf = (source: AnyShapeSource | undefined): AnySchemaObject | undefined => {
23
+ if (source == null) return undefined
24
+ const schema = 'kind' in source && source.kind === 'entrypoint-type'
25
+ ? source.schema
26
+ : source
27
+
28
+ return schema == null ? undefined : structuredClone(schema)
29
+ }
30
+
31
+ const responseSchemasOf = (source: AnyShapeSource | undefined): RuntimeResponseSchemas | undefined => {
32
+ const schema = schemaOf(source)
33
+ return schema == null ? undefined : { default: schema }
34
+ }
35
+
36
+ /**
37
+ * Declare an exact request and response. The primary overload means a body request; use
38
+ * `contract.request` for independently-shaped request sections.
39
+ */
40
+ export function contract(): EntrypointContract<{}, undefined>
41
+ export function contract<Response>(response: ShapeSource<Response>): EntrypointContract<{}, Response>
42
+ export function contract<Body extends OpenValue, Response>(
43
+ body: ShapeSource<Body>, response: ShapeSource<Response>
44
+ ): EntrypointContract<{ body: Body }, Response>
45
+ export function contract(
46
+ first?: AnyShapeSource, second?: AnyShapeSource
47
+ ): EntrypointContract<RequestShape, OpenValue> {
48
+ const body = second == null ? undefined : first
49
+ const response = second ?? first
50
+
51
+ return {
52
+ kind: 'entrypoint-contract',
53
+ requestSchemas: { body: schemaOf(body) },
54
+ responseSchemas: responseSchemasOf(response),
55
+ }
56
+ }
57
+
58
+ export namespace contract {
59
+ export function request<Sources extends RequestSources, Response>(
60
+ request: Sources,
61
+ response: ShapeSource<Response>
62
+ ): EntrypointContract<RequestFromSources<Sources>, Response> {
63
+ return {
64
+ kind: 'entrypoint-contract',
65
+ requestSchemas: {
66
+ body: schemaOf(request.body),
67
+ params: schemaOf(request.params),
68
+ query: schemaOf(request.query),
69
+ headers: schemaOf(request.headers),
70
+ },
71
+ responseSchemas: responseSchemasOf(response),
72
+ }
73
+ }
74
+ }
75
+
76
+ const guardsOf = (guards: EntrypointOptions['guards']): readonly string[] => guards == null
77
+ ? []
78
+ : typeof guards === 'string' ? [guards] : [...guards]
79
+
80
+ const gateOf = (gate: EntrypointOptions['gate']): EntrypointProtocol['gate'] => gate == null
81
+ ? undefined
82
+ : {
83
+ alias: gate.alias,
84
+ params: gate.params == null ? [] : typeof gate.params === 'string' ? [gate.params] : [...gate.params],
85
+ }
86
+
87
+ /** Create an immutable protocol declaration from a route and its contract. */
88
+ export const protocol = <Request extends RequestShape, Response>(
89
+ route: RouteModel,
90
+ entrypointContract: EntrypointContract<Request, Response>,
91
+ options?: EntrypointOptions
92
+ ): EntrypointProtocol<Request, Response> => Object.freeze({
93
+ kind: 'entrypoint-protocol' as const,
94
+ alias: route.route.alias,
95
+ route: Object.freeze({ route: Object.freeze({ ...route.route }) }),
96
+ contract: entrypointContract,
97
+ sticky: options?.sticky ?? false,
98
+ guards: Object.freeze(guardsOf(options?.guards)),
99
+ gate: gateOf(options?.gate),
100
+ })
101
+
102
+ export const openProtocol = (
103
+ route: RouteModel,
104
+ options?: EntrypointOptions
105
+ ): EntrypointProtocol<OpenRequest, OpenValue> => Object.freeze({
106
+ kind: 'entrypoint-protocol' as const,
107
+ alias: route.route.alias,
108
+ route: Object.freeze({ route: Object.freeze({ ...route.route }) }),
109
+ sticky: options?.sticky ?? false,
110
+ guards: Object.freeze(guardsOf(options?.guards)),
111
+ gate: gateOf(options?.gate),
112
+ })
113
+
114
+ export const entrypointRef = <Request extends RequestShape = OpenRequest, Response = OpenValue>(
115
+ alias: string
116
+ ): EntrypointReference<RegisteredEntrypoint<Request, Response>> => ({ alias })
117
+
118
+ export const aliasOf = (reference: EntrypointReference | string): string =>
119
+ typeof reference === 'string' ? reference : reference.alias
120
+
121
+ export const isEntrypointProtocol = (value: object): value is EntrypointProtocolDeclaration =>
122
+ 'kind' in value && value.kind === 'entrypoint-protocol'
123
+
124
+ /** Flatten an exported protocol tree for a layer-specific materializer. */
125
+ export const protocols = (tree: EntrypointTree): EntrypointProtocolDeclaration[] => {
126
+ const result: EntrypointProtocolDeclaration[] = []
127
+
128
+ const visit = (node: EntrypointTree): void => {
129
+ Object.values(node).forEach((entry) => {
130
+ if (isEntrypointProtocol(entry)) {
131
+ result.push(entry)
132
+ } else {
133
+ visit(entry)
134
+ }
135
+ })
136
+ }
137
+
138
+ visit(tree)
139
+ return result
140
+ }
141
+
142
+ /**
143
+ * Rebuild an immutable protocol tree while transforming its declarations.
144
+ *
145
+ * Protocol consumers keep addressing declarations by their exported tree path; a cross-cutting
146
+ * concern such as a coguard therefore never needs to flatten the tree and recover declarations
147
+ * by alias.
148
+ */
149
+ export const mapProtocols = <Tree extends EntrypointTree>(
150
+ tree: Tree,
151
+ mapper: (protocol: EntrypointProtocolDeclaration) => EntrypointProtocolDeclaration,
152
+ ): Tree => Object.freeze(Object.fromEntries(
153
+ Object.entries(tree).map(([key, entry]) => [
154
+ key,
155
+ isEntrypointProtocol(entry) ? mapper(entry) : mapProtocols(entry, mapper),
156
+ ])
157
+ )) as Tree
158
+
159
+ /** Resolve the gates a protocol inherits through its route parents without materializing it. */
160
+ export const gatesOf = (
161
+ protocol: EntrypointProtocolDeclaration,
162
+ tree: EntrypointTree,
163
+ ): readonly EntrypointGate[] => {
164
+ const byAlias = new Map(protocols(tree).map(candidate => [candidate.alias, candidate]))
165
+ const result: EntrypointGate[] = []
166
+ const visited = new Set<string>()
167
+ let current: EntrypointProtocolDeclaration | undefined = protocol
168
+
169
+ while (current != null && !visited.has(current.alias)) {
170
+ visited.add(current.alias)
171
+ if (current.gate != null && !result.some(gate => gate.alias === current!.gate!.alias)) {
172
+ result.push(current.gate)
173
+ }
174
+ current = current.route.route.parent == null
175
+ ? undefined
176
+ : byAlias.get(current.route.route.parent)
177
+ }
178
+
179
+ return result
180
+ }
181
+
182
+ /** Clone an immutable protocol while preserving its request/response pair. */
183
+ export const decorateEntrypoint = <Protocol extends EntrypointProtocolDeclaration>(
184
+ protocol: Protocol,
185
+ options: EntrypointOptions
186
+ ): Protocol => Object.freeze({
187
+ ...protocol,
188
+ sticky: options.sticky ?? protocol.sticky,
189
+ guards: options.guards == null ? protocol.guards : guardsOf(options.guards),
190
+ gate: options.gate == null ? protocol.gate : gateOf(options.gate),
191
+ })
@@ -0,0 +1,24 @@
1
+ import type { JSONSchemaType, AnySchemaObject } from 'ajv'
2
+ import type { EntrypointSchema, OpenValue, RequestShape, Typed } from './types.js'
3
+
4
+ export interface TypeToken {
5
+ readonly kind: 'entrypoint-type'
6
+ readonly schema?: AnySchemaObject
7
+ }
8
+
9
+ export declare const entrypointSchemaType: unique symbol
10
+
11
+ export type AnyShapeSource = AnySchemaObject | TypeToken
12
+
13
+ export type SourceValue<Source> =
14
+ Source extends Typed<infer Value> ? Value
15
+ : Source extends EntrypointSchema<infer Value> ? Value
16
+ : Source extends JSONSchemaType<infer Value>
17
+ ? Value extends OpenValue ? Value : OpenValue
18
+ : OpenValue
19
+
20
+ export type UrlRequest<Request extends RequestShape> =
21
+ (Request extends { params: infer Params } ? { params: Params }
22
+ : Request extends { params?: infer Params } ? { params?: Params } : {})
23
+ & (Request extends { query: infer Query } ? { query: Query }
24
+ : Request extends { query?: infer Query } ? { query?: Query } : {})
package/src/types.ts CHANGED
@@ -1,29 +1,47 @@
1
- import type { CommonRouteModel } from '@owlmeans/route'
2
- import type { InitializedService, LazyService, BasicEntrypoint } from '@owlmeans/context'
3
- import type { AnySchemaObject } from 'ajv'
1
+ import type { ResolvedServiceRoute, RouteAddress, RouteModel, RouteProtocol } from '@owlmeans/route'
2
+ import type { InitializedService, LazyService, BasicEntrypoint, EntrypointReference } from '@owlmeans/context'
3
+ import type { AnySchemaObject, JSONSchemaType } from 'ajv'
4
4
  import type { EntrypointOutcome } from './consts.js'
5
5
  import type { Auth } from '@owlmeans/auth'
6
6
 
7
+ import type { AnyShapeSource, entrypointSchemaType, SourceValue, TypeToken, UrlRequest } from './types.local.js'
8
+ import type { createFilterHelper } from './filter.js'
9
+
10
+ /**
11
+ * A URL unit: an immutable route declaration plus what guards, gates and schemas it answers under.
12
+ *
13
+ * Nothing here is rewritten once declared. Every address question — segment, path, mount, service,
14
+ * host — is a question about the declaration asked against the context the entrypoint is registered
15
+ * in, so the same declaration answers differently in a client and in a server without being touched.
16
+ */
7
17
  export interface CommonEntrypoint extends BasicEntrypoint {
8
- route: CommonRouteModel
18
+ route: RouteModel
9
19
  /**
10
20
  * @property {boolean} - if true — router attaches this entrypoint unconditionally
11
21
  * @default false
12
22
  */
13
23
  sticky: boolean
14
- filter?: Filter
24
+ /** The guards declared on this entrypoint alone — see `getGuards()` for the inherited set. */
15
25
  guards?: string[]
16
26
  gate?: string
17
27
  gateParams?: string | string[]
28
+ filter?: Filter
18
29
  handle?: EntrypointHandler
19
- getAlias: () => string
20
- getPath: () => string
21
- getParentAlias: () => string | null
22
- hasParent: () => boolean
23
- resolve: <M extends CommonEntrypoint>() => Promise<M>
24
- getParent: <M extends CommonEntrypoint>() => M
25
- setService: (service: string) => void
30
+
31
+ /** The segment this entrypoint contributes under its parent. */
32
+ segment: () => string
33
+ /** Every ancestor's segment, then this one. */
34
+ path: () => string
35
+ /** `base` + `path()` — what a server mounts and a client requests. */
36
+ mount: () => string
37
+ service: () => ResolvedServiceRoute
38
+ address: () => RouteAddress
39
+ /** Does this entrypoint belong to the service its context IS? */
40
+ isLocal: () => boolean
41
+ parent: () => CommonEntrypoint | null
42
+ /** Own guards and every ancestor's, deduped. Recomputed on each call. */
26
43
  getGuards: () => string[]
44
+ /** Own gate and every ancestor's, as `[gate, params]`. Recomputed on each call. */
27
45
  getGates: () => [string, string[]][]
28
46
  }
29
47
 
@@ -47,6 +65,17 @@ export interface EntrypointAssert {
47
65
  export interface AbstractRequest<T extends {} = {}> {
48
66
  alias: string
49
67
  auth?: Auth
68
+ /**
69
+ * The organization entity this request acts for, resolved from `auth.entitySlug` once, at the
70
+ * server boundary.
71
+ *
72
+ * The slug on the token is renameable and therefore unusable as a key; `entity.id` is the stable
73
+ * value that records, grants and minted infrastructure names are keyed by. Resolving it here —
74
+ * rather than in each handler — is what keeps a rename from meaning a sweep of every call site,
75
+ * and it is absent whenever no resolver is registered (an implementation that has no notion of
76
+ * an organization store), so consumers must handle that rather than assume it.
77
+ */
78
+ entity?: ResolvedEntity
50
79
  params: Record<string, string | number | undefined | null> | Partial<T>
51
80
  body?: Record<string, any> | Partial<T>
52
81
  headers: Record<string, string[] | string | undefined>
@@ -66,6 +95,21 @@ export interface AbstractRequest<T extends {} = {}> {
66
95
  signal?: AbortSignal
67
96
  }
68
97
 
98
+ /**
99
+ * An organization entity as request handlers see it: the stable id to key by, the current slug to
100
+ * compose user-facing names from, and the frozen key that external systems already know it under.
101
+ */
102
+ export interface ResolvedEntity {
103
+ id: string
104
+ slug: string
105
+ /**
106
+ * The identifier this organization is known by in systems whose names cannot be rewritten —
107
+ * an IAM realm, an object-storage prefix, a cluster object. Minted once and never recomputed,
108
+ * so it stays valid across every later rename.
109
+ */
110
+ iamKey: string
111
+ }
112
+
69
113
  export interface AbstractResponse<T> {
70
114
  responseProvider?: any
71
115
  value?: T,
@@ -98,3 +142,209 @@ export interface Filter {
98
142
  response?: AnySchemaObject
99
143
  headers?: AnySchemaObject
100
144
  }
145
+
146
+ /**
147
+ * How a call to an entrypoint is actually carried.
148
+ *
149
+ * The transport is chosen by the route's protocol, so a consumer always writes `ep.call(...)` and
150
+ * never learns which built-in or package-owned carrier handled it. An
151
+ * application binds a protocol by registering a transport service under `transportAlias(protocol)`.
152
+ */
153
+ export interface EntrypointTransport extends InitializedService {
154
+ readonly protocol: RouteProtocol
155
+ handle: EntrypointHandler
156
+ }
157
+
158
+ /** A deliberately broad value used only by declarations without an I/O contract. */
159
+ export type OpenValue = object | string | number | boolean | bigint | null | undefined
160
+
161
+ /** The four independent request sections an entrypoint may declare. */
162
+ export interface RequestShape {
163
+ body?: OpenValue
164
+ params?: object
165
+ query?: object
166
+ headers?: object
167
+ }
168
+
169
+ export interface RuntimeRequestSchemas {
170
+ body?: AnySchemaObject
171
+ params?: AnySchemaObject
172
+ query?: AnySchemaObject
173
+ headers?: AnySchemaObject
174
+ }
175
+
176
+ export interface RuntimeResponseSchemas {
177
+ default?: AnySchemaObject
178
+ byStatus?: Readonly<Record<number, AnySchemaObject>>
179
+ }
180
+
181
+ /** The runtime schemas and compile-time request/response pair attached to one protocol. */
182
+ export interface EntrypointContract<Request extends RequestShape, Response> {
183
+ readonly kind: 'entrypoint-contract'
184
+ readonly requestSchemas: RuntimeRequestSchemas
185
+ readonly responseSchemas?: RuntimeResponseSchemas
186
+ readonly requestType?: Request
187
+ readonly responseType?: Response
188
+ }
189
+
190
+ /** Runtime fields common to every protocol, regardless of its compile-time request/reply pair. */
191
+ export interface EntrypointProtocolDeclaration {
192
+ readonly kind: 'entrypoint-protocol'
193
+ readonly alias: string
194
+ readonly route: RouteModel
195
+ readonly contract?: Pick<EntrypointContract<RequestShape, OpenValue>,
196
+ 'kind' | 'requestSchemas' | 'responseSchemas'>
197
+ readonly sticky: boolean
198
+ readonly guards: readonly string[]
199
+ readonly gate?: {
200
+ readonly alias: string
201
+ readonly params: readonly string[]
202
+ }
203
+ }
204
+
205
+ /** A context-bound entrypoint made from an immutable protocol declaration. */
206
+ export type MaterializedEntrypoint<Protocol extends EntrypointProtocolDeclaration> = CommonEntrypoint & {
207
+ readonly protocol: Protocol
208
+ }
209
+
210
+ /** The request accepted by a declaration that intentionally supplies no contract. */
211
+ export interface OpenRequest {
212
+ body?: object
213
+ params?: object
214
+ query?: object
215
+ headers?: object
216
+ }
217
+
218
+ /** Addressing and transport controls are never part of an entrypoint's payload contract. */
219
+ export interface CallOptions {
220
+ auth?: Auth
221
+ host?: string
222
+ base?: string | boolean
223
+ unsecure?: boolean
224
+ timeout?: number
225
+ signal?: AbortSignal
226
+ }
227
+
228
+ /** A type-only contract source, optionally paired with an AJV schema. */
229
+ export type Typed<T> = TypeToken & { readonly value?: T }
230
+
231
+ /**
232
+ * A JSON schema carrying the model type it validates. Use `schema<Model>(...)` once beside the
233
+ * model; every protocol that consumes it then infers the model without another generic.
234
+ */
235
+ export type EntrypointSchema<T> = JSONSchemaType<T> & {
236
+ readonly [entrypointSchemaType]: T
237
+ }
238
+
239
+ /** A schema or an explicit type-only source for one request/response value. */
240
+ export type ShapeSource<T> = EntrypointSchema<T> | JSONSchemaType<T> | Typed<T>
241
+
242
+ export interface RequestSources {
243
+ body?: AnyShapeSource
244
+ params?: AnyShapeSource
245
+ query?: AnyShapeSource
246
+ headers?: AnyShapeSource
247
+ }
248
+
249
+ /** An access gate declared by a protocol, including inherited parent declarations. */
250
+ export interface EntrypointGate {
251
+ readonly alias: string
252
+ readonly params: readonly string[]
253
+ }
254
+
255
+ export type RequestFromSources<Sources extends RequestSources> =
256
+ (Sources extends { body: infer Source } ? { body: SourceValue<Source> } : {})
257
+ & (Sources extends { params: infer Source } ? { params: SourceValue<Source> } : {})
258
+ & (Sources extends { query: infer Source } ? { query: SourceValue<Source> } : {})
259
+ & (Sources extends { headers: infer Source } ? { headers: SourceValue<Source> } : {})
260
+
261
+ export interface EntrypointOptions {
262
+ sticky?: boolean
263
+ guards?: string | readonly string[]
264
+ gate?: {
265
+ alias: string
266
+ params?: string | readonly string[]
267
+ }
268
+ }
269
+
270
+ export interface EntrypointResult<Response> {
271
+ value: Response
272
+ outcome: EntrypointOutcome
273
+ }
274
+
275
+ export type CallArguments<Request extends RequestShape> = {} extends Request
276
+ ? [request?: Request & CallOptions]
277
+ : [request: Request & CallOptions]
278
+
279
+ export type UrlArguments<Request extends RequestShape> = {} extends UrlRequest<Request>
280
+ ? [request?: UrlRequest<Request> & CallOptions]
281
+ : [request: UrlRequest<Request> & CallOptions]
282
+
283
+ export interface EntrypointRequestMeta {
284
+ alias: string
285
+ auth?: Auth
286
+ entity?: ResolvedEntity
287
+ path: string
288
+ canceled?: boolean
289
+ cancel?: () => void
290
+ }
291
+
292
+ /**
293
+ * A protocol request at an implementation boundary.
294
+ *
295
+ * Transport metadata and empty request sections remain available to handlers; a declared section
296
+ * then refines that base shape to its protocol contract.
297
+ */
298
+ export type HandlerRequest<Request extends RequestShape> = AbstractRequest & Request & EntrypointRequestMeta
299
+
300
+ /**
301
+ * An immutable shared declaration. Its inherited reference type is what gives a context lookup
302
+ * the exact registered entrypoint type without a consumer-supplied generic.
303
+ */
304
+ export interface EntrypointProtocol<
305
+ Request extends RequestShape = RequestShape,
306
+ Response = OpenValue,
307
+ > extends EntrypointProtocolDeclaration, EntrypointReference<RegisteredEntrypoint<Request, Response>> {
308
+ readonly contract?: EntrypointContract<Request, Response>
309
+ }
310
+
311
+ /** The context-bound counterpart of an immutable protocol declaration. */
312
+ export interface RegisteredEntrypoint<Request extends RequestShape, Response> extends BasicEntrypoint {
313
+ readonly protocol: EntrypointProtocol<Request, Response>
314
+ call: (...args: CallArguments<Request>) => Promise<Response>
315
+ invoke: (...args: CallArguments<Request>) => Promise<EntrypointResult<Response>>
316
+ url: (...args: UrlArguments<Request>) => Promise<string>
317
+ validate: (...args: CallArguments<Request>) => Promise<boolean>
318
+ }
319
+
320
+ export type RequestOf<Protocol extends EntrypointProtocolDeclaration> = Protocol extends EntrypointProtocol<infer Request, infer _Response>
321
+ ? Request
322
+ : OpenRequest
323
+
324
+ export type ResponseOf<Protocol extends EntrypointProtocolDeclaration> = Protocol extends EntrypointProtocol<infer _Request, infer Response>
325
+ ? Response
326
+ : OpenValue
327
+
328
+ export type BodyOf<Protocol extends EntrypointProtocolDeclaration> = RequestOf<Protocol> extends { body: infer Body }
329
+ ? Body
330
+ : OpenValue
331
+
332
+ export type ParamsOf<Protocol extends EntrypointProtocolDeclaration> = RequestOf<Protocol> extends { params: infer Params }
333
+ ? Params
334
+ : object
335
+
336
+ export type QueryOf<Protocol extends EntrypointProtocolDeclaration> = RequestOf<Protocol> extends { query: infer Query }
337
+ ? Query
338
+ : object
339
+
340
+ export type HeadersOf<Protocol extends EntrypointProtocolDeclaration> = RequestOf<Protocol> extends { headers: infer Headers }
341
+ ? Headers
342
+ : object
343
+
344
+ export type CallRequestOf<Protocol extends EntrypointProtocolDeclaration> = RequestOf<Protocol> & CallOptions
345
+
346
+ export interface EntrypointTree {
347
+ readonly [key: string]: EntrypointProtocolDeclaration | EntrypointTree
348
+ }
349
+
350
+ export type FilterHelper = ReturnType<typeof createFilterHelper>
@@ -1,4 +1,4 @@
1
1
  import { CommonEntrypoint } from '../types.js'
2
2
 
3
3
  export const isEntrypoint = (obj: object): obj is CommonEntrypoint =>
4
- '_entrypoint' in obj || '_module' in obj
4
+ '_entrypoint' in obj
@@ -1,6 +1,6 @@
1
- import { CommonRouteModel } from '@owlmeans/route'
1
+ import { RouteModel } from '@owlmeans/route'
2
2
  import { CommonEntrypointOptions } from '../types.js'
3
3
 
4
4
  export interface CreateEntrypointSignature<M> {
5
- (route: CommonRouteModel, opts?: CommonEntrypointOptions): M,
5
+ (route: RouteModel, opts?: CommonEntrypointOptions): M,
6
6
  }
package/src/utils.ts ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Service alias a transport registers under. The default protocol is the web one, so an application
3
+ * that binds nothing keeps talking HTTP.
4
+ */
5
+ export const transportAlias = (protocol: string = 'http') => `transport:${protocol}`