@owlmeans/entrypoint 0.1.18-rc.9 → 0.1.18

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 (53) hide show
  1. package/README.md +254 -70
  2. package/agent-meta/manifest.json +3 -3
  3. package/agent-meta/skills/entrypoint/SKILL.md +77 -130
  4. package/build/consts.d.ts +0 -5
  5. package/build/consts.d.ts.map +1 -1
  6. package/build/consts.js +0 -5
  7. package/build/consts.js.map +1 -1
  8. package/build/entrypoint.d.ts.map +1 -1
  9. package/build/entrypoint.js +18 -6
  10. package/build/entrypoint.js.map +1 -1
  11. package/build/filter/types.d.ts +16 -0
  12. package/build/filter/types.d.ts.map +1 -0
  13. package/build/filter/types.js +2 -0
  14. package/build/filter/types.js.map +1 -0
  15. package/build/filter.d.ts +3 -7
  16. package/build/filter.d.ts.map +1 -1
  17. package/build/filter.js +22 -18
  18. package/build/filter.js.map +1 -1
  19. package/build/helper.d.ts.map +1 -1
  20. package/build/index.d.ts +5 -3
  21. package/build/index.d.ts.map +1 -1
  22. package/build/index.js +4 -3
  23. package/build/index.js.map +1 -1
  24. package/build/materialize.d.ts +7 -0
  25. package/build/materialize.d.ts.map +1 -0
  26. package/build/materialize.js +34 -0
  27. package/build/materialize.js.map +1 -0
  28. package/build/protocol.d.ts +44 -0
  29. package/build/protocol.d.ts.map +1 -0
  30. package/build/protocol.js +124 -0
  31. package/build/protocol.js.map +1 -0
  32. package/build/types.d.ts +169 -5
  33. package/build/types.d.ts.map +1 -1
  34. package/build/types.local.d.ts +27 -0
  35. package/build/types.local.d.ts.map +1 -0
  36. package/build/types.local.js +2 -0
  37. package/build/types.local.js.map +1 -0
  38. package/build/utils/entrypoint.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 +6 -6
  44. package/src/consts.ts +0 -5
  45. package/src/entrypoint.ts +18 -8
  46. package/src/filter/types.ts +16 -0
  47. package/src/filter.ts +24 -17
  48. package/src/index.ts +5 -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 +200 -5
  53. package/src/utils.ts +5 -0
@@ -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,9 +1,11 @@
1
- import type { ResolvedServiceRoute, RouteAddress, RouteModel, RouteProtocols } 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
+
7
9
  /**
8
10
  * A URL unit: an immutable route declaration plus what guards, gates and schemas it answers under.
9
11
  *
@@ -144,10 +146,203 @@ export interface Filter {
144
146
  * How a call to an entrypoint is actually carried.
145
147
  *
146
148
  * The transport is chosen by the route's protocol, so a consumer always writes `ep.call(...)` and
147
- * never learns whether that became an HTTP request, a socket message or a queued job. An
149
+ * never learns which built-in or package-owned carrier handled it. An
148
150
  * application binds a protocol by registering a transport service under `transportAlias(protocol)`.
149
151
  */
150
152
  export interface EntrypointTransport extends InitializedService {
151
- readonly protocol: RouteProtocols
153
+ readonly protocol: RouteProtocol
152
154
  handle: EntrypointHandler
153
155
  }
156
+
157
+ /** A deliberately broad value used only by declarations without an I/O contract. */
158
+ export type OpenValue = object | string | number | boolean | bigint | null | undefined
159
+
160
+ /** The four independent request sections an entrypoint may declare. */
161
+ export interface RequestShape {
162
+ body?: OpenValue
163
+ params?: object
164
+ query?: object
165
+ headers?: object
166
+ }
167
+
168
+ export interface RuntimeRequestSchemas {
169
+ body?: AnySchemaObject
170
+ params?: AnySchemaObject
171
+ query?: AnySchemaObject
172
+ headers?: AnySchemaObject
173
+ }
174
+
175
+ export interface RuntimeResponseSchemas {
176
+ default?: AnySchemaObject
177
+ byStatus?: Readonly<Record<number, AnySchemaObject>>
178
+ }
179
+
180
+ /** The runtime schemas and compile-time request/response pair attached to one protocol. */
181
+ export interface EntrypointContract<Request extends RequestShape, Response> {
182
+ readonly kind: 'entrypoint-contract'
183
+ readonly requestSchemas: RuntimeRequestSchemas
184
+ readonly responseSchemas?: RuntimeResponseSchemas
185
+ readonly requestType?: Request
186
+ readonly responseType?: Response
187
+ }
188
+
189
+ /** Runtime fields common to every protocol, regardless of its compile-time request/reply pair. */
190
+ export interface EntrypointProtocolDeclaration {
191
+ readonly kind: 'entrypoint-protocol'
192
+ readonly alias: string
193
+ readonly route: RouteModel
194
+ readonly contract?: Pick<EntrypointContract<RequestShape, OpenValue>,
195
+ 'kind' | 'requestSchemas' | 'responseSchemas'>
196
+ readonly sticky: boolean
197
+ readonly guards: readonly string[]
198
+ readonly gate?: {
199
+ readonly alias: string
200
+ readonly params: readonly string[]
201
+ }
202
+ }
203
+
204
+ /** A context-bound entrypoint made from an immutable protocol declaration. */
205
+ export interface MaterializedEntrypoint<Protocol extends EntrypointProtocolDeclaration> extends CommonEntrypoint { readonly protocol: Protocol }
206
+
207
+ /** The request accepted by a declaration that intentionally supplies no contract. */
208
+ export interface OpenRequest {
209
+ body?: object
210
+ params?: object
211
+ query?: object
212
+ headers?: object
213
+ }
214
+
215
+ /** Addressing and transport controls are never part of an entrypoint's payload contract. */
216
+ export interface CallOptions {
217
+ auth?: Auth
218
+ host?: string
219
+ base?: string | boolean
220
+ unsecure?: boolean
221
+ timeout?: number
222
+ signal?: AbortSignal
223
+ }
224
+
225
+ /** A type-only contract source, optionally paired with an AJV schema. */
226
+ export interface Typed<T> extends TypeToken { readonly value?: T }
227
+
228
+ // Kept as a type: `JSONSchemaType` is a conditional type; an interface cannot extend it.
229
+ /**
230
+ * A JSON schema carrying the model type it validates. Use `schema<Model>(...)` once beside the
231
+ * model; every protocol that consumes it then infers the model without another generic.
232
+ */
233
+ export type EntrypointSchema<T> = JSONSchemaType<T> & {
234
+ readonly [entrypointSchemaType]: T
235
+ }
236
+
237
+ /** A schema or an explicit type-only source for one request/response value. */
238
+ export type ShapeSource<T> = EntrypointSchema<T> | JSONSchemaType<T> | Typed<T>
239
+
240
+ export interface RequestSources {
241
+ body?: AnyShapeSource
242
+ params?: AnyShapeSource
243
+ query?: AnyShapeSource
244
+ headers?: AnyShapeSource
245
+ }
246
+
247
+ /** An access gate declared by a protocol, including inherited parent declarations. */
248
+ export interface EntrypointGate {
249
+ readonly alias: string
250
+ readonly params: readonly string[]
251
+ }
252
+
253
+ export type RequestFromSources<Sources extends RequestSources> =
254
+ (Sources extends { body: infer Source } ? { body: SourceValue<Source> } : {})
255
+ & (Sources extends { params: infer Source } ? { params: SourceValue<Source> } : {})
256
+ & (Sources extends { query: infer Source } ? { query: SourceValue<Source> } : {})
257
+ & (Sources extends { headers: infer Source } ? { headers: SourceValue<Source> } : {})
258
+
259
+ export interface EntrypointOptions {
260
+ sticky?: boolean
261
+ guards?: string | readonly string[]
262
+ gate?: {
263
+ alias: string
264
+ params?: string | readonly string[]
265
+ }
266
+ }
267
+
268
+ export interface EntrypointResult<Response> {
269
+ value: Response
270
+ outcome: EntrypointOutcome
271
+ }
272
+
273
+ export type CallArguments<Request extends RequestShape> = {} extends Request
274
+ ? [request?: Request & CallOptions]
275
+ : [request: Request & CallOptions]
276
+
277
+ export type UrlArguments<Request extends RequestShape> = {} extends UrlRequest<Request>
278
+ ? [request?: UrlRequest<Request> & CallOptions]
279
+ : [request: UrlRequest<Request> & CallOptions]
280
+
281
+ export interface EntrypointRequestMeta {
282
+ alias: string
283
+ auth?: Auth
284
+ entity?: ResolvedEntity
285
+ path: string
286
+ canceled?: boolean
287
+ cancel?: () => void
288
+ }
289
+
290
+ // Kept as a type: it intersects the `Request` type parameter, which an interface cannot extend.
291
+ /**
292
+ * A protocol request at an implementation boundary.
293
+ *
294
+ * Transport metadata and empty request sections remain available to handlers; a declared section
295
+ * then refines that base shape to its protocol contract.
296
+ */
297
+ export type HandlerRequest<Request extends RequestShape> = AbstractRequest & Request & EntrypointRequestMeta
298
+
299
+ /**
300
+ * An immutable shared declaration. Its inherited reference type is what gives a context lookup
301
+ * the exact registered entrypoint type without a consumer-supplied generic.
302
+ */
303
+ export interface EntrypointProtocol<
304
+ Request extends RequestShape = RequestShape,
305
+ Response = OpenValue,
306
+ > extends EntrypointProtocolDeclaration, EntrypointReference<RegisteredEntrypoint<Request, Response>> {
307
+ readonly contract?: EntrypointContract<Request, Response>
308
+ }
309
+
310
+ /** The context-bound counterpart of an immutable protocol declaration. */
311
+ export interface RegisteredEntrypoint<Request extends RequestShape, Response> extends BasicEntrypoint {
312
+ readonly protocol: EntrypointProtocol<Request, Response>
313
+ call: (...args: CallArguments<Request>) => Promise<Response>
314
+ invoke: (...args: CallArguments<Request>) => Promise<EntrypointResult<Response>>
315
+ url: (...args: UrlArguments<Request>) => Promise<string>
316
+ validate: (...args: CallArguments<Request>) => Promise<boolean>
317
+ }
318
+
319
+ export type RequestOf<Protocol extends EntrypointProtocolDeclaration> = Protocol extends EntrypointProtocol<infer Request, infer _Response>
320
+ ? Request
321
+ : OpenRequest
322
+
323
+ export type ResponseOf<Protocol extends EntrypointProtocolDeclaration> = Protocol extends EntrypointProtocol<infer _Request, infer Response>
324
+ ? Response
325
+ : OpenValue
326
+
327
+ export type BodyOf<Protocol extends EntrypointProtocolDeclaration> = RequestOf<Protocol> extends { body: infer Body }
328
+ ? Body
329
+ : OpenValue
330
+
331
+ export type ParamsOf<Protocol extends EntrypointProtocolDeclaration> = RequestOf<Protocol> extends { params: infer Params }
332
+ ? Params
333
+ : object
334
+
335
+ export type QueryOf<Protocol extends EntrypointProtocolDeclaration> = RequestOf<Protocol> extends { query: infer Query }
336
+ ? Query
337
+ : object
338
+
339
+ export type HeadersOf<Protocol extends EntrypointProtocolDeclaration> = RequestOf<Protocol> extends { headers: infer Headers }
340
+ ? Headers
341
+ : object
342
+
343
+ // Kept as a type: `RequestOf` is a conditional type; an interface cannot extend it.
344
+ export type CallRequestOf<Protocol extends EntrypointProtocolDeclaration> = RequestOf<Protocol> & CallOptions
345
+
346
+ export interface EntrypointTree {
347
+ readonly [key: string]: EntrypointProtocolDeclaration | EntrypointTree
348
+ }
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}`