@owlmeans/entrypoint 0.1.18-rc.2 → 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 +272 -60
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/entrypoint/SKILL.md +84 -48
- package/build/consts.d.ts +5 -0
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +5 -0
- package/build/consts.js.map +1 -1
- package/build/entrypoint.d.ts +0 -1
- package/build/entrypoint.d.ts.map +1 -1
- package/build/entrypoint.js +31 -57
- package/build/entrypoint.js.map +1 -1
- package/build/helper.d.ts +1 -2
- package/build/helper.d.ts.map +1 -1
- package/build/helper.js +0 -9
- package/build/helper.js.map +1 -1
- package/build/index.d.ts +3 -3
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3 -3
- package/build/index.js.map +1 -1
- package/build/materialize.d.ts +12 -0
- package/build/materialize.d.ts.map +1 -0
- package/build/materialize.js +34 -0
- package/build/materialize.js.map +1 -0
- package/build/protocol.d.ts +231 -0
- package/build/protocol.d.ts.map +1 -0
- package/build/protocol.js +124 -0
- package/build/protocol.js.map +1 -0
- package/build/types.d.ts +60 -10
- package/build/types.d.ts.map +1 -1
- package/build/utils/entrypoint.d.ts.map +1 -1
- package/build/utils/entrypoint.js +1 -1
- package/build/utils/entrypoint.js.map +1 -1
- package/build/utils/types.d.ts +2 -2
- package/build/utils/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/consts.ts +6 -0
- package/src/entrypoint.ts +33 -65
- package/src/helper.ts +1 -12
- package/src/index.ts +3 -3
- package/src/materialize.ts +49 -0
- package/src/protocol.ts +402 -0
- package/src/types.ts +63 -10
- package/src/utils/entrypoint.ts +1 -1
- package/src/utils/types.ts +2 -2
package/README.md
CHANGED
|
@@ -1,98 +1,310 @@
|
|
|
1
1
|
# @owlmeans/entrypoint
|
|
2
2
|
|
|
3
|
-
Entrypoint system — the
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
+
|
|
12
|
+
The route declaration an entrypoint carries is immutable: its `path` always stays the segment this
|
|
13
|
+
entrypoint contributes under its parent. Addresses are computed on demand against the context the
|
|
14
|
+
entrypoint is registered in — `path()` walks the parent chain, `mount()` adds the service base,
|
|
15
|
+
`address()` picks host, port and scheme — so one declaration answers correctly on both sides.
|
|
12
16
|
|
|
13
17
|
## Installation
|
|
14
18
|
|
|
15
19
|
```bash
|
|
16
|
-
bun add @owlmeans/entrypoint
|
|
20
|
+
bun add @owlmeans/entrypoint@^0.1.18-rc.19
|
|
17
21
|
```
|
|
18
22
|
|
|
19
|
-
##
|
|
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.
|
|
20
42
|
|
|
21
|
-
|
|
43
|
+
## Usage
|
|
22
44
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
+
})
|
|
26
73
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
)
|
|
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
|
|
35
85
|
```
|
|
36
86
|
|
|
37
|
-
|
|
87
|
+
`contract(body, response)` is a body-only contract; `contract.request(...)` types each request
|
|
88
|
+
section independently. The children inherit `DEFAULT_GUARD` from `base`.
|
|
38
89
|
|
|
39
|
-
|
|
40
|
-
import type { AbstractRequest } from '@owlmeans/entrypoint'
|
|
90
|
+
### 2. Bind handlers on the server
|
|
41
91
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
## API
|
|
92
|
+
```ts
|
|
93
|
+
import { handlers } from '@owlmeans/server-api'
|
|
94
|
+
import { bind } from '@owlmeans/server-entrypoint'
|
|
95
|
+
import { requireEntityKey } from '@owlmeans/auth-common'
|
|
96
|
+
import { storyProtocols } from 'my-app-common'
|
|
97
|
+
import type { Context } from 'my-app-backend'
|
|
49
98
|
|
|
50
|
-
|
|
99
|
+
const api = handlers<Context>()
|
|
51
100
|
|
|
52
|
-
|
|
101
|
+
const create = api.body(storyProtocols.create, async (body, context, request) =>
|
|
102
|
+
context.story().create(requireEntityKey(request), body))
|
|
53
103
|
|
|
54
|
-
|
|
104
|
+
const get = api.params(storyProtocols.get, async ({ id }, context) =>
|
|
105
|
+
context.story().get(id))
|
|
55
106
|
|
|
56
|
-
|
|
107
|
+
export const serverBindings = [
|
|
108
|
+
bind(storyProtocols.base),
|
|
109
|
+
bind(storyProtocols.create, create),
|
|
110
|
+
bind(storyProtocols.get, get),
|
|
111
|
+
]
|
|
112
|
+
```
|
|
57
113
|
|
|
58
|
-
|
|
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.
|
|
59
116
|
|
|
60
|
-
|
|
117
|
+
### 3. Call the protocol from a client or another service
|
|
61
118
|
|
|
62
|
-
|
|
119
|
+
```ts
|
|
120
|
+
import { bindAll } from '@owlmeans/client-entrypoint'
|
|
121
|
+
import { EntrypointOutcome } from '@owlmeans/entrypoint'
|
|
122
|
+
import { storyProtocols } from 'my-app-common'
|
|
63
123
|
|
|
64
|
-
|
|
124
|
+
export const clientBindings = bindAll(storyProtocols)
|
|
65
125
|
|
|
66
|
-
|
|
126
|
+
// Later, with a context the bindings were registered in:
|
|
127
|
+
const story = await context.entrypoint(storyProtocols.create).call({ body: { title: 'Invoices' } })
|
|
67
128
|
|
|
68
|
-
|
|
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
|
+
}
|
|
69
134
|
|
|
70
|
-
|
|
135
|
+
const link = await context.entrypoint(storyProtocols.get).url({ params: { id: value.id } })
|
|
136
|
+
```
|
|
71
137
|
|
|
72
|
-
|
|
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.
|
|
73
141
|
|
|
74
|
-
###
|
|
142
|
+
### 4. Gates, socket and queue carriers, derived types
|
|
75
143
|
|
|
76
|
-
|
|
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'
|
|
77
150
|
|
|
78
|
-
|
|
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
|
|
79
155
|
|
|
80
|
-
|
|
81
|
-
|
|
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>>
|
|
82
183
|
```
|
|
83
184
|
|
|
84
|
-
###
|
|
185
|
+
### 5. Decorate a whole tree without flattening it
|
|
186
|
+
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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] }))
|
|
89
195
|
|
|
90
|
-
|
|
196
|
+
// Resolve inherited gates without materializing anything (e.g. to hide a menu item).
|
|
197
|
+
const gates = gatesOf(auditedProtocols.build, auditedProtocols)
|
|
198
|
+
```
|
|
199
|
+
|
|
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
|
|
91
204
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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 |
|
|
269
|
+
|
|
270
|
+
### Transport
|
|
271
|
+
|
|
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
|
|
96
308
|
|
|
97
309
|
<!-- owlmeans:agent-guidance:start -->
|
|
98
310
|
## Agent guidance
|
|
@@ -102,7 +314,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
102
314
|
your project's skill store (`.agents/skills/`):
|
|
103
315
|
|
|
104
316
|
```sh
|
|
105
|
-
npx @owlmeans/agent-skills
|
|
317
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.21
|
|
106
318
|
```
|
|
107
319
|
|
|
108
320
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/entrypoint",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-
|
|
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
|
{
|
|
@@ -1,62 +1,98 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: entrypoint
|
|
3
|
-
description: How to
|
|
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, queue, 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
|
+
**Install:** `bun add @owlmeans/entrypoint@^0.1.18-rc.19`
|
|
11
|
+
|
|
10
12
|
**Layer:** Core
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
## Usage
|
|
33
|
-
|
|
34
|
-
Define entrypoints in a shared `common` package, then `elevate()` them with handlers in server/web packages:
|
|
35
|
-
|
|
36
|
-
```typescript
|
|
37
|
-
import { entrypoint, guard, gate, filter, body } from '@owlmeans/entrypoint'
|
|
38
|
-
import { route, RouteMethod } from '@owlmeans/route'
|
|
39
|
-
import { DEFAULT_GUARD } from '@owlmeans/auth-common'
|
|
40
|
-
import { OIDC_GATE } from '@owlmeans/oidc'
|
|
41
|
-
import { CreateProjectSchema } from './schemas.js'
|
|
42
|
-
|
|
43
|
-
export const managerModules = [
|
|
44
|
-
entrypoint(
|
|
45
|
-
route(manager.back.account.base, '/account'),
|
|
46
|
-
guard(DEFAULT_GUARD, gate(OIDC_GATE, [`my-service-account-{entity}`]))
|
|
13
|
+
|
|
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.
|
|
17
|
+
|
|
18
|
+
## Declare a named tree
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { contract, protocol, typed } from '@owlmeans/entrypoint'
|
|
22
|
+
import { backend, route, RouteMethod } from '@owlmeans/route'
|
|
23
|
+
|
|
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,
|
|
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 }
|
|
47
33
|
),
|
|
48
|
-
|
|
49
|
-
route(
|
|
50
|
-
|
|
51
|
-
method: RouteMethod.POST,
|
|
52
|
-
}),
|
|
53
|
-
filter(body(CreateProjectSchema))
|
|
34
|
+
get: protocol(
|
|
35
|
+
route(aliases.get, '/:id', backend({ parent: projectBase })),
|
|
36
|
+
contract.request({ params: typed<{ id: string }>() }, typed<Project>())
|
|
54
37
|
),
|
|
55
|
-
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
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 broker 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
|
+
})
|
|
56
82
|
```
|
|
57
83
|
|
|
58
|
-
|
|
84
|
+
Guards and gates are inherited through the parent route by the bound runtime entrypoint. A route's
|
|
85
|
+
protocol chooses the carrier (HTTP, socket, or queue); callers only use `call`, `invoke`, or `url`
|
|
86
|
+
on the client-bound protocol and do not branch on a transport.
|
|
87
|
+
|
|
88
|
+
## Bind, never replace
|
|
89
|
+
|
|
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)`.
|
|
59
95
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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,4 +4,9 @@ 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;
|
|
7
12
|
//# sourceMappingURL=consts.d.ts.map
|
package/build/consts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,oBAAY,iBAAiB;IAC3B,EAAE,OAAO;IACT,QAAQ,aAAa;IACrB,OAAO,YAAY;IACnB,QAAQ,aAAa;CACtB"}
|
|
1
|
+
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,oBAAY,iBAAiB;IAC3B,EAAE,OAAO;IACT,QAAQ,aAAa;IACrB,OAAO,YAAY;IACnB,QAAQ,aAAa;CACtB;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,cAAc,MAAM,WAAqC,CAAA"}
|
package/build/consts.js
CHANGED
|
@@ -5,4 +5,9 @@ export var EntrypointOutcome;
|
|
|
5
5
|
EntrypointOutcome["Created"] = "created";
|
|
6
6
|
EntrypointOutcome["Finished"] = "finished";
|
|
7
7
|
})(EntrypointOutcome || (EntrypointOutcome = {}));
|
|
8
|
+
/**
|
|
9
|
+
* Service alias a transport registers under. The default protocol is the web one, so an application
|
|
10
|
+
* that binds nothing keeps talking HTTP.
|
|
11
|
+
*/
|
|
12
|
+
export const transportAlias = (protocol = 'http') => `transport:${protocol}`;
|
|
8
13
|
//# sourceMappingURL=consts.js.map
|
package/build/consts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,MAAM,CAAN,IAAY,iBAKX;AALD,WAAY,iBAAiB;IAC3B,8BAAS,CAAA;IACT,0CAAqB,CAAA;IACrB,wCAAmB,CAAA;IACnB,0CAAqB,CAAA;AACvB,CAAC,EALW,iBAAiB,KAAjB,iBAAiB,QAK5B"}
|
|
1
|
+
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,MAAM,CAAN,IAAY,iBAKX;AALD,WAAY,iBAAiB;IAC3B,8BAAS,CAAA;IACT,0CAAqB,CAAA;IACrB,wCAAmB,CAAA;IACnB,0CAAqB,CAAA;AACvB,CAAC,EALW,iBAAiB,KAAjB,iBAAiB,QAK5B;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,QAAQ,GAAW,MAAM,EAAE,EAAE,CAAC,aAAa,QAAQ,EAAE,CAAA"}
|
package/build/entrypoint.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { CommonEntrypoint } from './types.js';
|
|
2
2
|
import type { CreateEntrypointSignature } from './utils/types.js';
|
|
3
3
|
export declare const entrypoint: CreateEntrypointSignature<CommonEntrypoint>;
|
|
4
|
-
export declare const parent: <T extends CommonEntrypoint | CommonEntrypoint[]>(ep: T, aliasOrParent: string, _parent?: string) => T;
|
|
5
4
|
//# sourceMappingURL=entrypoint.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entrypoint.d.ts","sourceRoot":"","sources":["../src/entrypoint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"entrypoint.d.ts","sourceRoot":"","sources":["../src/entrypoint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAKlD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAA;AAEjE,eAAO,MAAM,UAAU,EAAE,yBAAyB,CAAC,gBAAgB,CAkElE,CAAA"}
|