@owlmeans/entrypoint 0.1.18-rc.19 → 0.1.18-rc.20

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.
package/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # @owlmeans/entrypoint
2
2
 
3
- Entrypoint protocol system — the typed route contract shared between server and client in OwlMeans apps.
4
-
5
- ## Overview
6
-
7
- - A **protocol** is one immutable URL unit: an alias + route + typed contract + access options
8
- - On the server and client, corresponding bindings add handlers, screens or calls to that protocol
9
- - All AJV validation schemas live at protocol level, keeping data contracts consistent fullstack
10
- - Most commonly used with `@owlmeans/server-entrypoint` and `@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, a queued job 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.
11
11
 
12
12
  The route declaration an entrypoint carries is immutable: its `path` always stays the segment this
13
13
  entrypoint contributes under its parent. Addresses are computed on demand against the context the
@@ -17,93 +17,294 @@ entrypoint is registered in — `path()` walks the parent chain, `mount()` adds
17
17
  ## Installation
18
18
 
19
19
  ```bash
20
- bun add @owlmeans/entrypoint@^0.1.18-rc.10
20
+ bun add @owlmeans/entrypoint@^0.1.18-rc.19
21
21
  ```
22
22
 
23
- ## Usage
24
-
25
- Define a protocol with a typed request and guard:
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 (`http`, `ws`, `queue`) picks the carrier through a service
41
+ registered under `transportAlias(protocol)`; callers write `call()` and never branch on it.
26
42
 
27
- ```typescript
28
- import { contract, protocol, typed } from '@owlmeans/entrypoint'
29
- import { route } from '@owlmeans/route'
43
+ ## Usage
30
44
 
31
- const createStoryProtocol = protocol(
32
- route('story-create', '/stories', { method: 'POST', parent: 'api' }),
33
- contract.request({ body: typed<CreateStory>(CreateStorySchema) }, typed<Story>()),
34
- { guards: ['authenticated'] },
35
- )
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
+ })
73
+
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
36
85
  ```
37
86
 
38
- Bind the protocol to a typed server 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`.
89
+
90
+ ### 2. Bind handlers on the server
39
91
 
40
- ```typescript
92
+ ```ts
41
93
  import { handlers } from '@owlmeans/server-api'
42
94
  import { bind } from '@owlmeans/server-entrypoint'
95
+ import { requireEntityKey } from '@owlmeans/auth-common'
96
+ import { storyProtocols } from 'my-app-common'
43
97
  import type { Context } from 'my-app-backend'
44
98
 
45
99
  const api = handlers<Context>()
46
- const create = api.params(createStoryProtocol, async ({ id }, context) =>
47
- context.project().get(id))
48
- export const entrypoints = [bind(createStoryProtocol, create)]
49
- ```
50
-
51
- ## API
52
100
 
53
- ### `protocol(route, contract, options?): EntrypointProtocol`
101
+ const create = api.body(storyProtocols.create, async (body, context, request) =>
102
+ context.story().create(requireEntityKey(request), body))
54
103
 
55
- Creates an immutable typed protocol declaration.
104
+ const get = api.params(storyProtocols.get, async ({ id }, context) =>
105
+ context.story().get(id))
56
106
 
57
- ### `openProtocol(route, options?): EntrypointProtocol`
107
+ export const serverBindings = [
108
+ bind(storyProtocols.base),
109
+ bind(storyProtocols.create, create),
110
+ bind(storyProtocols.get, get),
111
+ ]
112
+ ```
58
113
 
59
- Creates an intentionally untyped protocol, used only for framework escape hatches.
114
+ `body`, `params` and `id` are inferred from the protocol; no generic is named. The organization
115
+ record id comes from the request (`requireEntityKey`), never from the payload.
60
116
 
61
- ### `contract(...)`, `contract.request(...)`, `typed<T>(schema?)`
117
+ ### 3. Call the protocol from a client or another service
62
118
 
63
- Describe typed body, params, query, headers and response sections.
119
+ ```ts
120
+ import { bindAll } from '@owlmeans/client-entrypoint'
121
+ import { EntrypointOutcome } from '@owlmeans/entrypoint'
122
+ import { storyProtocols } from 'my-app-common'
64
123
 
65
- Access is declared directly in `EntrypointOptions` as `guards`, `gate` and `sticky`; runtime
66
- behaviour is added by `bind()`/`bindAll()`/`bindScreen()` in the side-specific package.
124
+ export const clientBindings = bindAll(storyProtocols)
67
125
 
68
- ### Parentship
126
+ // Later, with a context the bindings were registered in:
127
+ const story = await context.entrypoint(storyProtocols.create).call({ body: { title: 'Invoices' } })
69
128
 
70
- A child names its parent in the route declaration (`route('story-create', '/stories', { parent: 'api' })`).
71
- `path()` prefixes the parent's segments, and inherited guards/gates are collected when a runtime
72
- binds the protocol.
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
+ }
73
134
 
74
- ### `provideResponse<T>(): AbstractResponse<T>`
135
+ const link = await context.entrypoint(storyProtocols.get).url({ params: { id: value.id } })
136
+ ```
75
137
 
76
- Creates a response object for use in unbound handlers.
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.
141
+
142
+ ### 4. Gates, socket and queue carriers, derived types
143
+
144
+ ```ts
145
+ import { contract, openProtocol, protocol, typed } from '@owlmeans/entrypoint'
146
+ import type { HandlerRequest, RequestOf, ResponseOf } from '@owlmeans/entrypoint'
147
+ import { backend, job, route, RouteMethod, socket } from '@owlmeans/route'
148
+ import { DEFAULT_GUARD, GUARD_ED25519 } from '@owlmeans/auth-common'
149
+ import { PROJECT_GATE, WORKER, WORK_QUEUE } from './consts.js'
150
+
151
+ const aliases = {
152
+ base: 'my-app:project:base', update: 'my-app:project:update',
153
+ watch: 'my-app:project:watch', build: 'my-app:project:build',
154
+ } as const
155
+
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
+ // Carried as a queued job for a worker service; callers still write `call()`.
171
+ build: protocol(
172
+ route(aliases.build, '/:id/build', job({
173
+ parent: base, method: RouteMethod.POST, service: WORKER, queue: WORK_QUEUE,
174
+ })),
175
+ contract.request({ params: typed<{ id: string }>() }, typed<{ jobId: string }>()),
176
+ { guards: GUARD_ED25519 },
177
+ ),
178
+ } as const
179
+
180
+ type BuildRequest = RequestOf<typeof projectProtocols.build> // { params: { id: string } }
181
+ type BuildReply = ResponseOf<typeof projectProtocols.build> // { jobId: string }
182
+ type WatchRequest = HandlerRequest<RequestOf<typeof projectProtocols.watch>>
183
+ ```
77
184
 
78
- ### Transport
185
+ ### 5. Decorate a whole tree without flattening it
79
186
 
80
- A route's protocol picks the carrier. Register a service under `transportAlias(protocol)` —
81
- `transport:<protocol>` — implementing `EntrypointTransport { protocol, handle }`, and every call to
82
- an entrypoint on that protocol goes through it. A consumer writes `ep.call(...)` and never learns
83
- whether that became an HTTP request, a socket message or a queued job. Without a registered
84
- transport the call goes over HTTP.
187
+ ```ts
188
+ import { decorateEntrypoint, gatesOf, mapProtocols } from '@owlmeans/entrypoint'
189
+ import { AUDIT_GUARD } from './consts.js'
190
+ import { projectProtocols } from './protocols.js'
85
191
 
86
- ### `EntrypointOutcome`
192
+ // Add a guard to every declaration while keeping the tree paths consumers bind by.
193
+ export const auditedProtocols = mapProtocols(projectProtocols, declaration =>
194
+ decorateEntrypoint(declaration, { guards: [...declaration.guards, AUDIT_GUARD] }))
87
195
 
88
- ```typescript
89
- enum EntrypointOutcome { Ok, Accepted, Created, Finished }
196
+ // Resolve inherited gates without materializing anything (e.g. to hide a menu item).
197
+ const gates = gatesOf(auditedProtocols.build, auditedProtocols)
90
198
  ```
91
199
 
92
- ### Types
200
+ `decorateEntrypoint` returns a new frozen protocol with the same request/response pair; the input
201
+ tree and its protocols are left untouched.
202
+
203
+ ## API
93
204
 
94
- - `AbstractRequest<T>` — request with `params`, `body`, `query`, `headers`, `auth`
95
- - `AbstractResponse<T>` — response with `resolve(value, outcome?)` and `reject(error)`
96
- - `CommonEntrypoint` — entrypoint with `alias`, `route`, `handle`, and the context-computed
97
- `segment()`, `path()`, `mount()`, `service()`, `address()`, `isLocal()`, `parent()`,
98
- `getGuards()`, `getGates()`
99
- - `EntrypointTransport` — `{ protocol, handle }`, the service that carries a call for one protocol
205
+ ### Protocol declaration (`.`)
206
+
207
+ | Symbol | Kind | Purpose |
208
+ |---|---|---|
209
+ | `protocol(route, contract, options?)` | function | Create an immutable, typed `EntrypointProtocol` |
210
+ | `openProtocol(route, options?)` | function | Create an intentionally untyped protocol (`OpenRequest` / `OpenValue`) for escape hatches |
211
+ | `contract(...)` | function | Body-only contract: `contract()`, `contract(response)`, `contract(body, response)` |
212
+ | `contract.request(sections, response)` | function | Contract with independently typed `body`, `params`, `query`, `headers` |
213
+ | `typed<T>(schema?)` | function | Type-only source, optionally paired with an AJV schema |
214
+ | `schema<T>(jsonSchema)` | function | Brand a reusable AJV schema with its model type (`EntrypointSchema<T>`) |
215
+ | `protocols(tree)` | function | Flatten an `EntrypointTree` to a declaration array for registration |
216
+ | `mapProtocols(tree, mapper)` | function | Rebuild a frozen tree, transforming each declaration in place of its path |
217
+ | `decorateEntrypoint(protocol, options)` | function | Clone a protocol with replaced `guards` / `gate` / `sticky` |
218
+ | `gatesOf(protocol, tree)` | function | Gates a protocol inherits through its route parents, without materializing |
219
+ | `isEntrypointProtocol(value)` | function | Type guard for a protocol declaration |
220
+ | `entrypointRef<Request, Response>(alias)` | function | Typed reference for a dynamic remote address whose declaration cannot be imported |
221
+ | `aliasOf(reference)` | function | Alias of a reference or string, for registry/broker adapters |
222
+ | `materializeEntrypoint(protocol)` | function | Make a context-bindable `CommonEntrypoint` from a declaration (used by binding packages) |
223
+ | `provideResponse<T>(original?)` | function | Create an `AbstractResponse<T>` for invoking a handler or guard outside a transport |
224
+ | `transportAlias(protocol = 'http')` | function | Service alias `transport:<protocol>` a transport registers under |
225
+ | `EntrypointOutcome` | enum | `Ok`, `Accepted`, `Created`, `Finished` — returned by `invoke()` and `resolve()` |
226
+
227
+ ### Contract and protocol types
228
+
229
+ | Symbol | Kind | Purpose |
230
+ |---|---|---|
231
+ | `EntrypointProtocol<Request, Response>` | interface | Typed immutable declaration; also a context reference to its registered entrypoint |
232
+ | `EntrypointProtocolDeclaration` | interface | Runtime fields common to every protocol (`alias`, `route`, `contract`, `sticky`, `guards`, `gate`) |
233
+ | `EntrypointContract<Request, Response>` | interface | Runtime schemas plus the compile-time pair |
234
+ | `EntrypointOptions` | interface | `sticky`, `guards`, `gate: { alias, params }` |
235
+ | `EntrypointGate` | interface | A resolved `{ alias, params }` gate |
236
+ | `EntrypointTree` | interface | Nested object of protocols |
237
+ | `RequestShape`, `OpenRequest`, `OpenValue` | types | Request section shape; the untyped request and value |
238
+ | `Typed<T>`, `EntrypointSchema<T>`, `ShapeSource<T>` | types | Contract sources |
239
+ | `RequestSources`, `RuntimeRequestSchemas`, `RuntimeResponseSchemas` | interfaces | Contract internals |
240
+ | `RequestOf`, `ResponseOf`, `BodyOf`, `ParamsOf`, `QueryOf`, `HeadersOf`, `CallRequestOf` | types | Derive contract types from a protocol |
241
+ | `HandlerRequest<Request>` | type | Typed request at a handler boundary, including transport metadata |
242
+ | `EntrypointRequestMeta` | interface | `alias`, `auth`, `entity`, `path`, `canceled`, `cancel` |
243
+ | `RegisteredEntrypoint<Request, Response>` | interface | Context-bound entrypoint with `call`, `invoke`, `url`, `validate` |
244
+ | `CallOptions`, `CallArguments`, `UrlArguments` | types | Addressing/transport controls and argument tuples of `call` / `url` |
245
+ | `EntrypointResult<Response>` | interface | `{ value, outcome }` returned by `invoke()` |
246
+ | `MaterializedEntrypoint<Protocol>` | type | `CommonEntrypoint` carrying its `protocol` |
247
+
248
+ ### Runtime types
249
+
250
+ | Symbol | Kind | Purpose |
251
+ |---|---|---|
252
+ | `CommonEntrypoint` | interface | Bound entrypoint: `alias`, `route`, `sticky`, `guards`, `gate`, `gateParams`, `filter`, `handle`, and context-computed `segment()`, `path()`, `mount()`, `service()`, `address()`, `isLocal()`, `parent()`, `getGuards()`, `getGates()` |
253
+ | `CommonEntrypointOptions` | interface | Partial `CommonEntrypoint` options |
254
+ | `AbstractRequest<T>` | interface | Request with `alias`, `auth`, `entity`, `params`, `body`, `query`, `headers`, `path`, `timeout`, `signal` |
255
+ | `AbstractResponse<T>` | interface | Response with `value`, `outcome`, `error`, `resolve(value, outcome?)`, `reject(error)` |
256
+ | `ResolvedEntity` | interface | Organization as handlers see it: `id` (stable key), `slug`, `iamKey` |
257
+ | `EntrypointHandler`, `EntrypointMatch`, `EntrypointAssert` | interfaces | Handler, guard-match and gate-assert signatures |
258
+ | `GuardService` | interface | Guard service: `match`, `handle`, and client-side `authenticated`, `token` |
259
+ | `GateService` | interface | Lazy gate service with `assert(req, res, params)` |
260
+ | `Filter` | interface | AJV schemas by section (`query`, `params`, `body`, `response`, `headers`) |
261
+ | `EntrypointTransport` | interface | `{ protocol, handle }` — the service that carries a call for one route protocol |
262
+
263
+ ### `@owlmeans/entrypoint/utils`
264
+
265
+ | Symbol | Kind | Purpose |
266
+ |---|---|---|
267
+ | `isEntrypoint(obj)` | function | Type guard for a materialized `CommonEntrypoint` |
268
+ | `CreateEntrypointSignature<M>` | interface | `(route, opts?) => M` factory signature |
100
269
 
101
- ## Related Packages
270
+ ### Transport
102
271
 
103
- - [`@owlmeans/route`](../route) — `route()` factory used in `protocol(route(...), ... )`
104
- - [`@owlmeans/server-entrypoint`](../server-entrypoint) — server-side `bind()`/`bindAll()` to attach handlers
105
- - [`@owlmeans/client-entrypoint`](../client-entrypoint) — client-side entrypoint with API call support
106
- - [`@owlmeans/server-app`](../server-app) — re-exports everything from this package
272
+ Register a service under `transportAlias(protocol)` implementing `EntrypointTransport` and every
273
+ call to an entrypoint on that route protocol goes through it — `@owlmeans/queue` does this for
274
+ `RouteProtocols.QUEUE`. Without a registered transport the call goes over HTTP.
275
+
276
+ ## Common pitfalls
277
+
278
+ - Do not export or import alias strings as an API. Keep them private to the declaration module and
279
+ pass protocol objects; raw aliases belong only in dynamic registry or broker adapters.
280
+ - Do not use `openProtocol` just to skip declaring input or output types — it is for intentionally
281
+ untyped boundaries.
282
+ - Use `typed<Model>(schema)` or `schema<Model>(...)` rather than a bare `JSONSchemaType<Model>`; a bare
283
+ AJV generic can widen a section to `OpenValue`.
284
+ - Never mutate guards, gates, schemas or a declaration collection, and never replace an item in an
285
+ entrypoint array. Derive with `decorateEntrypoint` / `mapProtocols` and bind that exact object.
286
+ - Reference a parent by its protocol object (`backend({ parent: base })`), not by its alias.
287
+ - Do not construct contextual compatibility entrypoints or look a protocol up by alias with a generic;
288
+ use `entrypointRef` only when the declaration truly cannot be imported.
289
+ - Callers use `call` / `invoke` / `url` and do not branch on the carrier — the route protocol and its
290
+ transport decide.
291
+ - Inherited gates are deduplicated by gate service: a child declaring a gate under the same service
292
+ replaces its ancestor's gate for that service rather than adding to it.
293
+ - `request.entity` is set only where authentication ran `attachEntity`; handlers key records by
294
+ `entity.id` (via `requireEntityKey` / `requireEntity` from `@owlmeans/auth-common`), never by the
295
+ token's `entitySlug`.
296
+
297
+ ## Related packages
298
+
299
+ - [`@owlmeans/route`](../route) — `route()`, `backend()`, `socket()`, `job()`, `frontend()` used in `protocol(route(...), ...)`
300
+ - [`@owlmeans/context`](../context) — `BasicEntrypoint`, `EntrypointReference` and `context.entrypoint(...)`
301
+ - [`@owlmeans/server-entrypoint`](../server-entrypoint) — server-side `bind()` / `bindAll()` to attach handlers
302
+ - [`@owlmeans/server-api`](../server-api) — `handlers<Context>()` with protocol-inferred `body` / `params` / `request`
303
+ - [`@owlmeans/server-socket`](../server-socket) — `connection(protocol, handler)` for socket protocols
304
+ - [`@owlmeans/client-entrypoint`](../client-entrypoint) — client `bind()`, `bindAll()`, `bindScreen()` with typed calls
305
+ - [`@owlmeans/queue`](../queue) — the QUEUE transport for `job()` routes
306
+ - [`@owlmeans/auth-common`](../auth-common) — guard aliases and entity helpers used in access options and handlers
307
+ - [`@owlmeans/server-app`](../server-app) — re-exports `contract`, `protocol`, `typed`, `EntrypointOutcome` and the request/response types
107
308
 
108
309
  <!-- owlmeans:agent-guidance:start -->
109
310
  ## Agent guidance
@@ -113,7 +314,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
113
314
  your project's skill store (`.agents/skills/`):
114
315
 
115
316
  ```sh
116
- npx @owlmeans/agent-skills@^0.1.18-rc.20
317
+ npx @owlmeans/agent-skills@^0.1.18-rc.21
117
318
  ```
118
319
 
119
320
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/entrypoint",
4
- "version": "0.1.18-rc.19",
5
- "generatedAt": "2026-09-12T14:21:25.469Z",
4
+ "version": "0.1.18-rc.20",
5
+ "generatedAt": "2026-09-15T12:21:31.948Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -7,7 +7,7 @@ user-invocable: false
7
7
 
8
8
  # @owlmeans/entrypoint
9
9
 
10
- **Install:** `bun add @owlmeans/entrypoint@^0.1.18-rc.17`
10
+ **Install:** `bun add @owlmeans/entrypoint@^0.1.18-rc.19`
11
11
 
12
12
  **Layer:** Core
13
13
 
@@ -21,28 +21,35 @@ client behaviour is added only by the corresponding binding package.
21
21
  import { contract, protocol, typed } from '@owlmeans/entrypoint'
22
22
  import { backend, route, RouteMethod } from '@owlmeans/route'
23
23
 
24
- export const projectEntrypoints = {
25
- base: protocol(route('project', '/projects', backend()), contract(typed<void>())),
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>()))
26
+
27
+ export const projectProtocols = {
28
+ base: projectBase,
26
29
  create: protocol(
27
- route('project:create', '/', backend('project', RouteMethod.POST)),
30
+ route(aliases.create, '/', backend({ parent: projectBase, method: RouteMethod.POST })),
28
31
  contract.request({ body: typed<CreateProject>(CreateProjectSchema) }, typed<Project>()),
29
32
  { guards: DEFAULT_GUARD }
30
33
  ),
31
34
  get: protocol(
32
- route('project:get', '/:id', backend('project')),
35
+ route(aliases.get, '/:id', backend({ parent: projectBase })),
33
36
  contract.request({ params: typed<{ id: string }>() }, typed<Project>())
34
37
  ),
35
38
  }
36
39
  ```
37
40
 
38
- - Export an object whose property names describe the contract. Its protocol values are the only
39
- cross-layer references. Flatten it with `protocols(projectEntrypoints)` only at registration.
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.
40
43
  - Use `protocol(route, contract, options?)` for every typed boundary.
41
44
  - `openProtocol(route, options?)` is an intentional untyped escape hatch; do not use it merely to
42
45
  avoid declaring an input or output type.
43
46
  - Use `decorateEntrypoint(protocol, options)` only when deriving an immutable decoration; never
44
47
  mutate guards, gates, schemas, or a declaration collection.
45
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 broker adapters.
52
+
46
53
  ## Contract sources
47
54
 
48
55
  | Source | Use |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/entrypoint",
3
- "version": "0.1.18-rc.19",
3
+ "version": "0.1.18-rc.20",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -28,9 +28,9 @@
28
28
  }
29
29
  },
30
30
  "dependencies": {
31
- "@owlmeans/auth": "^0.1.18-rc.17",
32
- "@owlmeans/context": "^0.1.18-rc.16",
33
- "@owlmeans/route": "^0.1.18-rc.17",
31
+ "@owlmeans/auth": "^0.1.18-rc.18",
32
+ "@owlmeans/context": "^0.1.18-rc.17",
33
+ "@owlmeans/route": "^0.1.18-rc.18",
34
34
  "ajv": "^8.17.1"
35
35
  },
36
36
  "devDependencies": {