@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
package/README.md CHANGED
@@ -1,14 +1,13 @@
1
1
  # @owlmeans/entrypoint
2
2
 
3
- Entrypoint system — the URL unit abstraction shared between server and client in OwlMeans apps.
4
-
5
- ## Overview
6
-
7
- - An **entrypoint** is a URL unit: an alias + a route declaration + optional guards/gates/filters
8
- - On the server, entrypoints become API routes with attached handlers
9
- - On the client, entrypoints provide URL generation and navigation
10
- - All AJV validation schemas are defined at entrypoint level, keeping data contracts consistent fullstack
11
- - Most commonly used via re-exports in `@owlmeans/server-app` or `@owlmeans/client-entrypoint`
3
+ Entrypoint protocol system — the typed route contract shared between server and client in OwlMeans
4
+ apps. An application uses it in its shared (`common`) package to declare every addressable unit —
5
+ an HTTP API route, a socket or a screen — once, as an immutable protocol with typed
6
+ request sections, a response type, AJV schemas and access rules. Runtime behaviour is not added
7
+ here: the server attaches handlers with `@owlmeans/server-entrypoint` / `@owlmeans/server-api`, the
8
+ client binds calls and screens with `@owlmeans/client-entrypoint`, and route shapes themselves come
9
+ from `@owlmeans/route`. Code that only calls or handles an already declared protocol imports it from
10
+ the shared package rather than declaring anything here.
12
11
 
13
12
  The route declaration an entrypoint carries is immutable: its `path` always stays the segment this
14
13
  entrypoint contributes under its parent. Addresses are computed on demand against the context the
@@ -18,99 +17,284 @@ entrypoint is registered in — `path()` walks the parent chain, `mount()` adds
18
17
  ## Installation
19
18
 
20
19
  ```bash
21
- bun add @owlmeans/entrypoint
20
+ bun add @owlmeans/entrypoint@^0.1.18
22
21
  ```
23
22
 
24
- ## Usage
23
+ ## Concepts
24
+
25
+ - **Protocol** — one immutable URL unit: an alias + route + typed contract + access options, made by
26
+ `protocol(route, contract, options?)`. The frozen object is the only cross-layer reference;
27
+ runtime code passes it (`context.entrypoint(protocols.x)`), never its alias string.
28
+ - **Contract** — the compile-time request/response pair plus the runtime AJV schemas, made by
29
+ `contract(...)` or `contract.request({ body, params, query, headers }, response)` from `typed<T>()`
30
+ and `schema<T>()` sources. All validation lives at protocol level, keeping data contracts
31
+ consistent fullstack.
32
+ - **Protocol tree** — an exported `*Protocols` object whose property names describe the contract.
33
+ Aliases stay private to the declaring module; the tree is flattened with `protocols(tree)` only at
34
+ registration.
35
+ - **Access options** — `guards`, `gate` and `sticky` in `EntrypointOptions`. Guards and gates are
36
+ inherited through the route parent when a runtime binds the protocol.
37
+ - **Binding / materialization** — a side-specific package turns a declaration into a context-bound
38
+ `CommonEntrypoint` (`materializeEntrypoint`) and adds a handler, a call or a screen. The declaration
39
+ itself is never mutated.
40
+ - **Transport** — the route's protocol picks a built-in or package-owned carrier through a service
41
+ registered under `transportAlias(protocol)`; callers write `call()` and never branch on it.
25
42
 
26
- Define an entrypoint with body validation and a guard:
43
+ ## Usage
27
44
 
28
- ```typescript
29
- import { entrypoint, guard, filter, body, params } from '@owlmeans/server-app'
30
- import { route } from '@owlmeans/route'
45
+ ### 1. Declare a typed protocol tree
46
+
47
+ ```ts
48
+ import { contract, protocol, schema, typed } from '@owlmeans/entrypoint'
49
+ import { backend, route, RouteMethod } from '@owlmeans/route'
50
+ import { DEFAULT_GUARD } from '@owlmeans/auth-common'
51
+ import type { JSONSchemaType } from 'ajv'
52
+
53
+ export interface Story { id: string, title: string }
54
+ export interface CreateStory { title: string }
55
+
56
+ export const CreateStorySchema = schema<CreateStory>({
57
+ type: 'object',
58
+ properties: { title: { type: 'string', minLength: 1 } },
59
+ required: ['title'],
60
+ additionalProperties: false,
61
+ } as JSONSchemaType<CreateStory>)
62
+
63
+ // Private wire names — never exported.
64
+ const aliases = {
65
+ base: 'my-app:story:base',
66
+ create: 'my-app:story:create',
67
+ get: 'my-app:story:get',
68
+ } as const
69
+
70
+ const base = protocol(route(aliases.base, '/stories', backend()), contract(), {
71
+ guards: DEFAULT_GUARD,
72
+ })
31
73
 
32
- const createStoryEntrypoint = entrypoint(
33
- route('story-create', '/stories', { method: 'POST', parent: 'api' }),
34
- filter(body({
35
- type: 'object',
36
- properties: { story: { type: 'string' }, projectId: { type: 'string' } },
37
- required: ['story', 'projectId']
38
- }), guard('authenticated'))
39
- )
74
+ export const storyProtocols = {
75
+ base,
76
+ create: protocol(
77
+ route(aliases.create, '/', backend({ parent: base, method: RouteMethod.POST })),
78
+ contract(CreateStorySchema, typed<Story>()),
79
+ ),
80
+ get: protocol(
81
+ route(aliases.get, '/:id', backend({ parent: base })),
82
+ contract.request({ params: typed<{ id: string }>() }, typed<Story>()),
83
+ ),
84
+ } as const
40
85
  ```
41
86
 
42
- Use `AbstractRequest` type in a handler:
87
+ `contract(body, response)` is a body-only contract; `contract.request(...)` types each request
88
+ section independently. The children inherit `DEFAULT_GUARD` from `base`.
43
89
 
44
- ```typescript
45
- import type { AbstractRequest } from '@owlmeans/entrypoint'
90
+ ### 2. Bind handlers on the server
46
91
 
47
- export const create = handleBody(async (body, context, request) => {
48
- const req = request as AbstractRequest<{ id: string }>
49
- const projectId = req.params.id
50
- })
51
- ```
92
+ ```ts
93
+ import { handlers } from '@owlmeans/server-api'
94
+ import { bind } from '@owlmeans/server-entrypoint'
95
+ import { makeEntityScope } from '@owlmeans/auth-common'
96
+ import { storyProtocols } from 'my-app-common'
97
+ import type { Context } from 'my-app-backend'
52
98
 
53
- ## API
99
+ const api = handlers<Context>()
54
100
 
55
- ### `entrypoint(route, opts?): CommonEntrypoint`
101
+ const create = api.body(storyProtocols.create, async (body, context, request) =>
102
+ context.story().create(makeEntityScope(request).requireEntityKey(), body))
56
103
 
57
- Creates an entrypoint. `opts` is typically produced by `filter()`, `guard()`, or `gate()`.
104
+ const get = api.params(storyProtocols.get, async ({ id }, context) =>
105
+ context.story().get(id))
58
106
 
59
- ### `guard(guard, opts?): CommonEntrypointOptions`
107
+ export const serverBindings = [
108
+ bind(storyProtocols.base),
109
+ bind(storyProtocols.create, create),
110
+ bind(storyProtocols.get, get),
111
+ ]
112
+ ```
60
113
 
61
- Adds an authentication guard requirement.
114
+ `body`, `params` and `id` are inferred from the protocol; no generic is named. The organization
115
+ record id comes from the request (`makeEntityScope(request).requireEntityKey()`), never from the payload.
62
116
 
63
- ### `gate(gate, params, opts?): CommonEntrypointOptions`
117
+ ### 3. Call the protocol from a client or another service
64
118
 
65
- Adds an authorization gate with parameters.
119
+ ```ts
120
+ import { bindAll } from '@owlmeans/client-entrypoint'
121
+ import { EntrypointOutcome } from '@owlmeans/entrypoint'
122
+ import { storyProtocols } from 'my-app-common'
66
123
 
67
- ### `filter(filter, opts?): CommonEntrypointOptions`
124
+ export const clientBindings = bindAll(storyProtocols)
68
125
 
69
- Attaches validation schemas (AJV format) to the entrypoint.
126
+ // Later, with a context the bindings were registered in:
127
+ const story = await context.entrypoint(storyProtocols.create).call({ body: { title: 'Invoices' } })
70
128
 
71
- ### `body(schema, filter?) / query(schema, filter?) / params(schema, filter?)`
129
+ const { value, outcome } = await context.entrypoint(storyProtocols.get)
130
+ .invoke({ params: { id: story.id }, timeout: 5_000 })
131
+ if (outcome !== EntrypointOutcome.Ok) {
132
+ throw new Error(`Story ${story.id} was not loaded`)
133
+ }
72
134
 
73
- Build a `Filter` object with the given AJV schema applied to the corresponding request part.
135
+ const link = await context.entrypoint(storyProtocols.get).url({ params: { id: value.id } })
136
+ ```
74
137
 
75
- ### Parentship
138
+ `call()` resolves the value, `invoke()` also returns the `EntrypointOutcome`, `url()` builds the
139
+ address. `CallOptions` (`auth`, `host`, `base`, `unsecure`, `timeout`, `signal`) ride alongside the
140
+ request sections and are never part of the payload contract.
76
141
 
77
- A child names its parent in the route declaration (`route('story-create', '/stories', { parent: 'api' })`).
78
- `path()` prefixes the parent's segments, and `getGuards()` / `getGates()` collect the parent's on
79
- every call — so a guard added to a parent later still applies.
142
+ ### 4. Gates, socket carriers, derived types
80
143
 
81
- ### `provideResponse<T>(): AbstractResponse<T>`
144
+ ```ts
145
+ import { contract, openProtocol, protocol, typed } from '@owlmeans/entrypoint'
146
+ import type { HandlerRequest, RequestOf, ResponseOf } from '@owlmeans/entrypoint'
147
+ import { backend, route, socket } from '@owlmeans/route'
148
+ import { DEFAULT_GUARD } from '@owlmeans/auth-common'
149
+ import { PROJECT_GATE } from './consts.js'
82
150
 
83
- Creates a response object for use in non-elevated handlers.
151
+ const aliases = {
152
+ base: 'my-app:project:base', update: 'my-app:project:update',
153
+ watch: 'my-app:project:watch',
154
+ } as const
84
155
 
85
- ### Transport
156
+ const base = protocol(route(aliases.base, '/projects', backend()), contract(), {
157
+ guards: DEFAULT_GUARD,
158
+ gate: { alias: PROJECT_GATE, params: 'my-app-project-{entity}' },
159
+ })
160
+ const updates = openProtocol(route(aliases.update, '/update'))
161
+
162
+ export const projectProtocols = {
163
+ base,
164
+ updates,
165
+ // Carried over a WebSocket: the handler receives a connection, not a single reply.
166
+ watch: protocol(
167
+ route(aliases.watch, '/project/:id', socket({ parent: updates })),
168
+ contract.request({ params: typed<{ id: string }>() }, typed<void>()),
169
+ ),
170
+ } as const
171
+
172
+ type WatchRequest = HandlerRequest<RequestOf<typeof projectProtocols.watch>>
173
+ ```
86
174
 
87
- A route's protocol picks the carrier. Register a service under `transportAlias(protocol)` —
88
- `transport:<protocol>` — implementing `EntrypointTransport { protocol, handle }`, and every call to
89
- an entrypoint on that protocol goes through it. A consumer writes `ep.call(...)` and never learns
90
- whether that became an HTTP request, a socket message or a queued job. Without a registered
91
- transport the call goes over HTTP.
175
+ ### 5. Decorate a whole tree without flattening it
92
176
 
93
- ### `EntrypointOutcome`
177
+ ```ts
178
+ import { decorateEntrypoint, gatesOf, mapProtocols } from '@owlmeans/entrypoint'
179
+ import { AUDIT_GUARD } from './consts.js'
180
+ import { projectProtocols } from './protocols.js'
94
181
 
95
- ```typescript
96
- enum EntrypointOutcome { Ok, Accepted, Created, Finished }
182
+ // Add a guard to every declaration while keeping the tree paths consumers bind by.
183
+ export const auditedProtocols = mapProtocols(projectProtocols, declaration =>
184
+ decorateEntrypoint(declaration, { guards: [...declaration.guards, AUDIT_GUARD] }))
185
+
186
+ // Resolve inherited gates without materializing anything (e.g. to hide a menu item).
187
+ const gates = gatesOf(auditedProtocols.build, auditedProtocols)
97
188
  ```
98
189
 
99
- ### Types
190
+ `decorateEntrypoint` returns a new frozen protocol with the same request/response pair; the input
191
+ tree and its protocols are left untouched.
192
+
193
+ ## API
100
194
 
101
- - `AbstractRequest<T>` — request with `params`, `body`, `query`, `headers`, `auth`
102
- - `AbstractResponse<T>` — response with `resolve(value, outcome?)` and `reject(error)`
103
- - `CommonEntrypoint` — entrypoint with `alias`, `route`, `handle`, and the context-computed
104
- `segment()`, `path()`, `mount()`, `service()`, `address()`, `isLocal()`, `parent()`,
105
- `getGuards()`, `getGates()`
106
- - `EntrypointTransport` — `{ protocol, handle }`, the service that carries a call for one protocol
195
+ ### Protocol declaration (`.`)
196
+
197
+ | Symbol | Kind | Purpose |
198
+ |---|---|---|
199
+ | `protocol(route, contract, options?)` | function | Create an immutable, typed `EntrypointProtocol` |
200
+ | `openProtocol(route, options?)` | function | Create an intentionally untyped protocol (`OpenRequest` / `OpenValue`) for escape hatches |
201
+ | `contract(...)` | function | Body-only contract: `contract()`, `contract(response)`, `contract(body, response)` |
202
+ | `contract.request(sections, response)` | function | Contract with independently typed `body`, `params`, `query`, `headers` |
203
+ | `typed<T>(schema?)` | function | Type-only source, optionally paired with an AJV schema |
204
+ | `schema<T>(jsonSchema)` | function | Brand a reusable AJV schema with its model type (`EntrypointSchema<T>`) |
205
+ | `protocols(tree)` | function | Flatten an `EntrypointTree` to a declaration array for registration |
206
+ | `mapProtocols(tree, mapper)` | function | Rebuild a frozen tree, transforming each declaration in place of its path |
207
+ | `decorateEntrypoint(protocol, options)` | function | Clone a protocol with replaced `guards` / `gate` / `sticky` |
208
+ | `gatesOf(protocol, tree)` | function | Gates a protocol inherits through its route parents, without materializing |
209
+ | `isEntrypointProtocol(value)` | function | Type guard for a protocol declaration |
210
+ | `entrypointRef<Request, Response>(alias)` | function | Typed reference for a dynamic remote address whose declaration cannot be imported |
211
+ | `aliasOf(reference)` | function | Alias of a reference or string, for registry/transport adapters |
212
+ | `materializeEntrypoint(protocol)` | function | Make a context-bindable `CommonEntrypoint` from a declaration (used by binding packages) |
213
+ | `provideResponse<T>(original?)` | function | Create an `AbstractResponse<T>` for invoking a handler or guard outside a transport |
214
+ | `transportAlias(protocol = 'http')` | function | Service alias `transport:<protocol>` a transport registers under |
215
+ | `EntrypointOutcome` | enum | `Ok`, `Accepted`, `Created`, `Finished` — returned by `invoke()` and `resolve()` |
216
+
217
+ ### Contract and protocol types
218
+
219
+ | Symbol | Kind | Purpose |
220
+ |---|---|---|
221
+ | `EntrypointProtocol<Request, Response>` | interface | Typed immutable declaration; also a context reference to its registered entrypoint |
222
+ | `EntrypointProtocolDeclaration` | interface | Runtime fields common to every protocol (`alias`, `route`, `contract`, `sticky`, `guards`, `gate`) |
223
+ | `EntrypointContract<Request, Response>` | interface | Runtime schemas plus the compile-time pair |
224
+ | `EntrypointOptions` | interface | `sticky`, `guards`, `gate: { alias, params }` |
225
+ | `EntrypointGate` | interface | A resolved `{ alias, params }` gate |
226
+ | `EntrypointTree` | interface | Nested object of protocols |
227
+ | `RequestShape`, `OpenRequest`, `OpenValue` | types | Request section shape; the untyped request and value |
228
+ | `Typed<T>`, `EntrypointSchema<T>`, `ShapeSource<T>` | types | Contract sources |
229
+ | `RequestSources`, `RuntimeRequestSchemas`, `RuntimeResponseSchemas` | interfaces | Contract internals |
230
+ | `RequestOf`, `ResponseOf`, `BodyOf`, `ParamsOf`, `QueryOf`, `HeadersOf`, `CallRequestOf` | types | Derive contract types from a protocol |
231
+ | `HandlerRequest<Request>` | type | Typed request at a handler boundary, including transport metadata |
232
+ | `EntrypointRequestMeta` | interface | `alias`, `auth`, `entity`, `path`, `canceled`, `cancel` |
233
+ | `RegisteredEntrypoint<Request, Response>` | interface | Context-bound entrypoint with `call`, `invoke`, `url`, `validate` |
234
+ | `CallOptions`, `CallArguments`, `UrlArguments` | types | Addressing/transport controls and argument tuples of `call` / `url` |
235
+ | `EntrypointResult<Response>` | interface | `{ value, outcome }` returned by `invoke()` |
236
+ | `MaterializedEntrypoint<Protocol>` | type | `CommonEntrypoint` carrying its `protocol` |
237
+
238
+ ### Runtime types
239
+
240
+ | Symbol | Kind | Purpose |
241
+ |---|---|---|
242
+ | `CommonEntrypoint` | interface | Bound entrypoint: `alias`, `route`, `sticky`, `guards`, `gate`, `gateParams`, `filter`, `handle`, and context-computed `segment()`, `path()`, `mount()`, `service()`, `address()`, `isLocal()`, `parent()`, `getGuards()`, `getGates()` |
243
+ | `CommonEntrypointOptions` | interface | Partial `CommonEntrypoint` options |
244
+ | `AbstractRequest<T>` | interface | Request with `alias`, `auth`, `entity`, `params`, `body`, `query`, `headers`, `path`, `timeout`, `signal` |
245
+ | `AbstractResponse<T>` | interface | Response with `value`, `outcome`, `error`, `resolve(value, outcome?)`, `reject(error)` |
246
+ | `ResolvedEntity` | interface | Organization as handlers see it: `id` (stable key), `slug`, `iamKey` |
247
+ | `EntrypointHandler`, `EntrypointMatch`, `EntrypointAssert` | interfaces | Handler, guard-match and gate-assert signatures |
248
+ | `GuardService` | interface | Guard service: `match`, `handle`, and client-side `authenticated`, `token` |
249
+ | `GateService` | interface | Lazy gate service with `assert(req, res, params)` |
250
+ | `Filter` | interface | AJV schemas by section (`query`, `params`, `body`, `response`, `headers`) |
251
+ | `EntrypointTransport` | interface | `{ protocol, handle }` — the service that carries a call for one route protocol |
252
+
253
+ ### `@owlmeans/entrypoint/utils`
254
+
255
+ | Symbol | Kind | Purpose |
256
+ |---|---|---|
257
+ | `isEntrypoint(obj)` | function | Type guard for a materialized `CommonEntrypoint` |
258
+ | `CreateEntrypointSignature<M>` | interface | `(route, opts?) => M` factory signature |
107
259
 
108
- ## Related Packages
260
+ ### Transport
109
261
 
110
- - [`@owlmeans/route`](../route) — `route()` factory used in `entrypoint(route(...), ...)`
111
- - [`@owlmeans/server-entrypoint`](../server-entrypoint) — server-side `elevate()` to attach handlers
112
- - [`@owlmeans/client-entrypoint`](../client-entrypoint) — client-side entrypoint with API call support
113
- - [`@owlmeans/server-app`](../server-app) — re-exports everything from this package
262
+ Register a service under `transportAlias(protocol)` implementing `EntrypointTransport` and every
263
+ call to an entrypoint on that route protocol goes through it. The package defining a custom
264
+ protocol owns its transport and option validation. Without a registered transport the call goes
265
+ over HTTP.
266
+
267
+ ## Common pitfalls
268
+
269
+ - Do not export or import alias strings as an API. Keep them private to the declaration module and
270
+ pass protocol objects; raw aliases belong only in dynamic registry or transport adapters.
271
+ - Do not use `openProtocol` just to skip declaring input or output types — it is for intentionally
272
+ untyped boundaries.
273
+ - Use `typed<Model>(schema)` or `schema<Model>(...)` rather than a bare `JSONSchemaType<Model>`; a bare
274
+ AJV generic can widen a section to `OpenValue`.
275
+ - Never mutate guards, gates, schemas or a declaration collection, and never replace an item in an
276
+ entrypoint array. Derive with `decorateEntrypoint` / `mapProtocols` and bind that exact object.
277
+ - Reference a parent by its protocol object (`backend({ parent: base })`), not by its alias.
278
+ - Do not construct contextual compatibility entrypoints or look a protocol up by alias with a generic;
279
+ use `entrypointRef` only when the declaration truly cannot be imported.
280
+ - Callers use `call` / `invoke` / `url` and do not branch on the carrier — the route protocol and its
281
+ transport decide.
282
+ - Inherited gates are deduplicated by gate service: a child declaring a gate under the same service
283
+ replaces its ancestor's gate for that service rather than adding to it.
284
+ - `request.entity` is set only where authentication ran `makeEntityScope(request).attachEntity(context)`; handlers key records by
285
+ `entity.id` (via `makeEntityScope(request).requireEntityKey()` / `.requireEntity()` from `@owlmeans/auth-common`), never by the
286
+ token's `entitySlug`.
287
+
288
+ ## Related packages
289
+
290
+ - [`@owlmeans/route`](../route) — `route()`, `backend()`, `socket()`, `frontend()` used in `protocol(route(...), ...)`
291
+ - [`@owlmeans/context`](../context) — `BasicEntrypoint`, `EntrypointReference` and `context.entrypoint(...)`
292
+ - [`@owlmeans/server-entrypoint`](../server-entrypoint) — server-side `bind()` / `bindAll()` to attach handlers
293
+ - [`@owlmeans/server-api`](../server-api) — `handlers<Context>()` with protocol-inferred `body` / `params` / `request`
294
+ - [`@owlmeans/server-socket`](../server-socket) — `connection(protocol, handler)` for socket protocols
295
+ - [`@owlmeans/client-entrypoint`](../client-entrypoint) — client `bind()`, `bindAll()`, `bindScreen()` with typed calls
296
+ - [`@owlmeans/auth-common`](../auth-common) — guard aliases and entity helpers used in access options and handlers
297
+ - [`@owlmeans/server-app`](../server-app) — re-exports `contract`, `protocol`, `typed`, `EntrypointOutcome` and the request/response types
114
298
 
115
299
  <!-- owlmeans:agent-guidance:start -->
116
300
  ## Agent guidance
@@ -120,7 +304,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
120
304
  your project's skill store (`.agents/skills/`):
121
305
 
122
306
  ```sh
123
- npx @owlmeans/agent-skills
307
+ npx @owlmeans/agent-skills@^0.1.18
124
308
  ```
125
309
 
126
310
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
- "schemaVersion": 2,
2
+ "schemaVersion": 3,
3
3
  "package": "@owlmeans/entrypoint",
4
- "version": "0.1.18-rc.9",
5
- "generatedAt": "2026-09-01T16:28:56.997Z",
4
+ "version": "0.1.18",
5
+ "generatedAt": "2026-10-28T00:31:34.766Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -1,151 +1,98 @@
1
1
  ---
2
2
  name: entrypoint
3
- description: How to use @owlmeans/entrypoint — declarative entrypoint definitions over immutable route declarations, with entrypoint(), guard(), gate(), filter(), body(), params(), query() builders, the address accessors, and the transport seam. Auto-invoked when importing from this package or defining a service entrypoint declaration.
3
+ description: How to declare immutable, typed @owlmeans/entrypoint protocols with protocol(), contract(), typed(), protocol trees, guards, gates, and route transports. Load before defining a shared API, socket, or screen contract.
4
4
  user-invocable: false
5
5
  ---
6
6
  <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
7
7
 
8
8
  # @owlmeans/entrypoint
9
9
 
10
- **Layer:** Core
11
- **Install:** `"@owlmeans/entrypoint": "^0.1.18-rc.9"` in `dependencies`
12
-
13
- An entrypoint is a **URL unit**: an immutable route declaration plus the guards, gates and schemas
14
- it answers under. It is the single concept an application declares once in a shared package and
15
- elevates on either side.
16
-
17
- ## Key Exports
18
-
19
- | Export | Description |
20
- |--------|-------------|
21
- | `entrypoint(route, opts?)` | Declare an entrypoint on a route model |
22
- | `guard(alias, opts?)` | Require a guard; returns options, so it wraps rather than takes them |
23
- | `gate(alias, params, opts?)` | Require a gate; passed as the `opts` of `guard(...)` |
24
- | `filter(filter, opts?)` | Attach request validators |
25
- | `body(Schema)` / `params(Schema)` / `query(Schema)` / `headers(Schema)` / `response(Schema, code?)` | AJV validators composed into a `Filter` |
26
- | `CommonEntrypoint` | The entrypoint interface — declaration, guards/gates, address accessors |
27
- | `AbstractRequest` / `AbstractResponse` | The request and reply shapes every handler sees |
28
- | `provideResponse(original?)` | Build a reply object a handler resolves or rejects |
29
- | `GuardService` / `GateService` | What a guard and a gate implement |
30
- | `EntrypointOutcome` | Enum: Ok, Accepted, Created, Finished |
31
- | `EntrypointTransport` | `{ protocol, handle }` — a carrier bound to a route protocol |
32
- | `transportAlias(protocol?)` | The service alias a transport registers under (`transport:<protocol>`) |
33
-
34
- `guard`, `gate` and `filter` are **options-object combinators, not variadic composers**. Each
35
- returns a `CommonEntrypointOptions` and takes the next one as its final argument, so they nest:
36
-
37
- ```typescript
38
- entrypoint(
39
- route(app.api.item.remove, '/:id', { parent: app.api.item, method: RouteMethod.DELETE }),
40
- guard(DEFAULT_GUARD, gate(OIDC_GATE, ['item--delete@id']))
41
- )
42
- ```
43
-
44
- Guards and gates are **inherited by child entrypoints** and enforced by the framework before a
45
- handler runs — a handler that re-checks them is duplicating an enforced rule, and is wrong even
46
- when it agrees.
47
-
48
- ## Subpath Exports
49
-
50
- - `./utils` — entrypoint construction helpers (`isEntrypoint`, `CreateEntrypointSignature`)
51
-
52
- ## Declaration and model
10
+ **Install:** `bun add @owlmeans/entrypoint@^0.1.18`
53
11
 
54
- The `RouteDeclaration` an entrypoint carries is plain, immutable data: its `path` is the **segment**
55
- this entrypoint contributes under its parent, and nothing rewrites it. `RouteModel` only wraps that
56
- declaration. Every address question is therefore a question about the declaration asked **against
57
- the context that asks it** — computed on demand, never stored — so the same declaration answers one
58
- way in a server and another in a client without being touched.
12
+ **Layer:** Core
59
13
 
60
- | Accessor | Answers |
61
- |----------|---------|
62
- | `segment()` | The segment declared under the parent |
63
- | `path()` | Every ancestor's segment, then this one |
64
- | `mount()` | `base` + `path()` — what a server registers and a client requests |
65
- | `service()` | The resolved service route this entrypoint answers on |
66
- | `address()` | `{ host, port, base, secure, protocol }` — where it actually answers |
67
- | `isLocal()` | Does it belong to the service the asking context IS? |
68
- | `parent()` | The parent entrypoint, or `null` |
69
- | `getGuards()` | Own guards plus every ancestor's, deduped |
70
- | `getGates()` | Own gate plus every ancestor's, as `[gate, params]` |
14
+ An entrypoint protocol is the immutable shared contract for one addressable route. It owns the
15
+ route, typed request sections, response type, schemas, guards, gate, and sticky flag. Server and
16
+ client behaviour is added only by the corresponding binding package.
71
17
 
72
- `getGuards()` and `getGates()` walk the chain afresh on every call and are never memoised: a guard
73
- attached to an ancestor after this entrypoint was first asked still has to count.
18
+ ## Declare a named tree
74
19
 
75
- ## Calling an entrypoint
20
+ ```ts
21
+ import { contract, protocol, typed } from '@owlmeans/entrypoint'
22
+ import { backend, route, RouteMethod } from '@owlmeans/route'
76
23
 
77
- Three explicit verbs, so the caller says which answer it wants:
24
+ const aliases = { base: 'project', create: 'project:create', get: 'project:get' } as const
25
+ const projectBase = protocol(route(aliases.base, '/projects', backend()), contract(typed<void>()))
78
26
 
79
- ```typescript
80
- const project = await ep.call({ params: { id } }) // the VALUE; the reply error is thrown
81
- const { value, outcome } = await ep.invoke({ body }) // value AND outcome
82
- const href = await ep.url({ params: { id } }, { absolute: true }) // the URL string
27
+ export const projectProtocols = {
28
+ base: projectBase,
29
+ create: protocol(
30
+ route(aliases.create, '/', backend({ parent: projectBase, method: RouteMethod.POST })),
31
+ contract.request({ body: typed<CreateProject>(CreateProjectSchema) }, typed<Project>()),
32
+ { guards: DEFAULT_GUARD }
33
+ ),
34
+ get: protocol(
35
+ route(aliases.get, '/:id', backend({ parent: projectBase })),
36
+ contract.request({ params: typed<{ id: string }>() }, typed<Project>())
37
+ ),
38
+ }
83
39
  ```
84
40
 
85
- Use `invoke` only where the outcome decides what happens next; `call` covers everything else. An
86
- entrypoint that **renders a screen** is addressed by URL and never over the wire — it throws from
87
- `call()`/`invoke()` telling the caller to use `url()`.
88
-
89
- The three verbs sit on the callable entrypoint that `@owlmeans/client-entrypoint` produces; the
90
- declaration built here is what they address.
91
-
92
- ## Elevation
93
-
94
- `elevate` replaces the declaration in the entrypoint list with its elevated counterpart, so it is
95
- **idempotent**: elevating the same alias again simply replaces the element once more. Guards passed
96
- at elevation are **unioned** with the ones the entrypoint declared — elevating adds guards, it never
97
- swaps them. Server-side elevation lives in `@owlmeans/server-entrypoint`, client-side callability is
98
- an explicit opt-in through `@owlmeans/client-entrypoint`.
99
-
100
- ## Transport seam
101
-
102
- A route names the protocol it answers on, and a protocol may be carried by something other than
103
- HTTP. A service registered under `transportAlias(protocol)` implementing `EntrypointTransport` takes
104
- the call, so a consumer writes `ep.call(...)` and never learns whether that became an HTTP request,
105
- a socket message or a queued job. Bind nothing and the call goes over HTTP.
106
-
107
- ```typescript
108
- import { transportAlias } from '@owlmeans/entrypoint'
109
- import type { EntrypointTransport } from '@owlmeans/entrypoint'
110
- import { RouteProtocols } from '@owlmeans/route'
111
- import { createService } from '@owlmeans/context'
112
-
113
- const transport = createService<EntrypointTransport>(transportAlias(RouteProtocols.SOCKET), {
114
- protocol: RouteProtocols.SOCKET,
115
- handle: async (req, res) => { /* carry the call, resolve or reject res */ }
116
- }, service => async () => { service.initialized = true })
117
-
118
- context.registerService(transport)
41
+ - Export a `*Protocols` object whose property names describe the contract. Its protocol values are
42
+ the only cross-layer references. Flatten it with `protocols(projectProtocols)` only at registration.
43
+ - Use `protocol(route, contract, options?)` for every typed boundary.
44
+ - `openProtocol(route, options?)` is an intentional untyped escape hatch; do not use it merely to
45
+ avoid declaring an input or output type.
46
+ - Use `decorateEntrypoint(protocol, options)` only when deriving an immutable decoration; never
47
+ mutate guards, gates, schemas, or a declaration collection.
48
+
49
+ Keep aliases private to this module. A child should use its parent protocol object
50
+ (`backend({ parent: projectBase })`); route creation stores its alias. Normal application code
51
+ imports protocol objects, while raw aliases are limited to dynamic registry or transport adapters.
52
+
53
+ ## Contract sources
54
+
55
+ | Source | Use |
56
+ |---|---|
57
+ | `typed<Model>(schema)` | A type paired with its AJV runtime validator. Prefer at declaration boundaries. |
58
+ | `typed<Model>()` | A type-only request section or response. |
59
+ | `schema<Model>(schema)` | A reusable named typed AJV schema. |
60
+ | `contract(body, response)` | A body-only contract. |
61
+ | `contract.request({ body, params, query, headers }, response)` | Independently typed request sections. |
62
+
63
+ Use `typed<Model>(schema)` rather than a bare `JSONSchemaType<Model>` when the model must remain
64
+ exact. A bare AJV generic can widen a protocol section to `OpenValue`.
65
+
66
+ `RequestOf<Protocol>`, `ResponseOf<Protocol>`, `BodyOf<Protocol>`, `ParamsOf<Protocol>`,
67
+ `QueryOf<Protocol>`, and `HeadersOf<Protocol>` derive the contract types. A handler receives
68
+ `HandlerRequest<RequestOf<Protocol>>`, which includes the transport metadata as well as the typed
69
+ sections. Use `entrypointRef<Request, Response>(alias)` only for a dynamic remote address whose
70
+ declaration is unavailable to import.
71
+
72
+ ## Authorization and addressing
73
+
74
+ Add `guards`, `gate`, and `sticky` directly in `EntrypointOptions`:
75
+
76
+ ```ts
77
+ protocol(route(...), contract(...), {
78
+ guards: [DEFAULT_GUARD, AUDIT_GUARD],
79
+ gate: { alias: PROJECT_GATE, params: ['project--read@id'] },
80
+ sticky: true,
81
+ })
119
82
  ```
120
83
 
121
- ## Usage
84
+ Guards and gates are inherited through the parent route by the bound runtime entrypoint. A route's
85
+ protocol chooses the built-in or package-owned carrier; callers only use `call`, `invoke`, or `url`
86
+ on the client-bound protocol and do not branch on a transport.
122
87
 
123
- Define entrypoints in a shared `common` package, then `elevate()` them with handlers in server/web packages:
124
-
125
- ```typescript
126
- import { entrypoint, guard, gate, filter, body } from '@owlmeans/entrypoint'
127
- import { route, RouteMethod } from '@owlmeans/route'
128
- import { DEFAULT_GUARD } from '@owlmeans/auth-common'
129
- import { OIDC_GATE } from '@owlmeans/oidc'
130
- import { CreateProjectSchema } from './schemas.js'
131
-
132
- export const managerEntrypoints = [
133
- entrypoint(
134
- route(manager.back.account.base, '/account'),
135
- guard(DEFAULT_GUARD, gate(OIDC_GATE, [`my-service-account-{entity}`]))
136
- ),
137
- entrypoint(
138
- route(manager.back.project.create, '/create', {
139
- parent: manager.back.project.base,
140
- method: RouteMethod.POST,
141
- }),
142
- filter(body(CreateProjectSchema))
143
- ),
144
- ]
145
- ```
88
+ ## Bind, never replace
146
89
 
147
- ## Depends On
90
+ - Server: `bind(protocol, handlers<Context>().body|params|request(...))` from
91
+ `@owlmeans/server-entrypoint` / `@owlmeans/server-api`.
92
+ - Socket: `bind(protocol, connection(protocol, handler))`.
93
+ - Client API route: `bind(protocol)` or `bindAll(tree)` from `@owlmeans/client-entrypoint`.
94
+ - Client screen: `bindScreen(protocol, componentHandler)`.
148
95
 
149
- - `@owlmeans/route` — `route()`, `RouteMethod`, `RouteProtocols`, and the address helpers under `/utils`
150
- - `@owlmeans/auth-common` — guard aliases (`DEFAULT_GUARD`)
151
- - `@owlmeans/error`, `@owlmeans/i18n`
96
+ Do not construct contextual compatibility entrypoints, look a protocol up by alias with a generic,
97
+ or replace an item in an entrypoint array. Import the declaration and bind that exact object. This
98
+ keeps declarations immutable and lets TypeScript infer requests and replies end to end.
package/build/consts.d.ts CHANGED
@@ -4,9 +4,4 @@ export declare enum EntrypointOutcome {
4
4
  Created = "created",
5
5
  Finished = "finished"
6
6
  }
7
- /**
8
- * Service alias a transport registers under. The default protocol is the web one, so an application
9
- * that binds nothing keeps talking HTTP.
10
- */
11
- export declare const transportAlias: (protocol?: string) => string;
12
7
  //# sourceMappingURL=consts.d.ts.map