@lovrozagar/honey 0.1.0
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 +51 -0
- package/docs/api-ref/go.md +399 -0
- package/docs/api-ref/python.md +399 -0
- package/docs/api-ref/rust.md +399 -0
- package/docs/api-ref/typescript.md +399 -0
- package/docs/invalidate-spec.md +303 -0
- package/docs/sdk.md +722 -0
- package/package.json +120 -0
- package/src/accepts.ts +78 -0
- package/src/body-limit.ts +100 -0
- package/src/build/index.ts +174 -0
- package/src/cli-go/auth.go +15 -0
- package/src/cli-go/config.go +165 -0
- package/src/cli-go/errors.go +57 -0
- package/src/cli-go/multipart.go +55 -0
- package/src/cli-go/output.go +109 -0
- package/src/cli-go/root.go +30 -0
- package/src/cli-go/stream.go +88 -0
- package/src/cli-go/version.go +5 -0
- package/src/cli.ts +204 -0
- package/src/client/error.ts +27 -0
- package/src/client/http.ts +502 -0
- package/src/client/index.ts +127 -0
- package/src/client/sdk.ts +265 -0
- package/src/client/sse.ts +102 -0
- package/src/client/types.ts +211 -0
- package/src/client/ws.ts +115 -0
- package/src/client-go/errors.go +207 -0
- package/src/client-go/invalidation.go +373 -0
- package/src/client-go/realtime.go +317 -0
- package/src/client-go/result.go +20 -0
- package/src/client-go/runtime.go +413 -0
- package/src/client-go/sse.go +149 -0
- package/src/client-go/transport.go +452 -0
- package/src/client-go/ws.go +104 -0
- package/src/client-mcp/README.md +10 -0
- package/src/client-mcp/server.ts +94 -0
- package/src/client-mcp/types.ts +19 -0
- package/src/client-python/_errors.py +115 -0
- package/src/client-python/_invalidation.py +578 -0
- package/src/client-python/_realtime.py +156 -0
- package/src/client-python/_runtime.py +469 -0
- package/src/client-python/_sse.py +99 -0
- package/src/client-python/_transport.py +335 -0
- package/src/client-python/_ws.py +131 -0
- package/src/client-python/py.typed +0 -0
- package/src/client-rust/errors.rs +200 -0
- package/src/client-rust/invalidation.rs +441 -0
- package/src/client-rust/invalidation_sync.rs +306 -0
- package/src/client-rust/realtime.rs +695 -0
- package/src/client-rust/result.rs +22 -0
- package/src/client-rust/runtime.rs +547 -0
- package/src/client-rust/runtime_sync.rs +452 -0
- package/src/client-rust/sse.rs +167 -0
- package/src/client-rust/ws.rs +143 -0
- package/src/cloudflare/realtime.ts +236 -0
- package/src/codegen-go-cli.ts +1180 -0
- package/src/codegen-go.ts +1010 -0
- package/src/codegen-hash.ts +8 -0
- package/src/codegen-ir.ts +517 -0
- package/src/codegen-loaders.ts +53 -0
- package/src/codegen-mcp.ts +364 -0
- package/src/codegen-python.ts +1028 -0
- package/src/codegen-rust.ts +1652 -0
- package/src/codegen-sanitize.ts +67 -0
- package/src/codegen-schema-naming.ts +146 -0
- package/src/codegen.ts +5015 -0
- package/src/context.ts +210 -0
- package/src/cookie-sign.ts +58 -0
- package/src/cookie.ts +89 -0
- package/src/cors.ts +96 -0
- package/src/crypto.ts +19 -0
- package/src/csrf.ts +54 -0
- package/src/curl-logger.ts +235 -0
- package/src/detect-runtime.ts +19 -0
- package/src/error.ts +52 -0
- package/src/errors.ts +87 -0
- package/src/etag.ts +74 -0
- package/src/feature-load.ts +23 -0
- package/src/go-type-emitter.ts +404 -0
- package/src/honey-response.ts +354 -0
- package/src/i18n-slot.ts +20 -0
- package/src/i18n.ts +234 -0
- package/src/index.ts +3843 -0
- package/src/init.ts +149 -0
- package/src/input.ts +8 -0
- package/src/ip-restrict.ts +192 -0
- package/src/logger.ts +149 -0
- package/src/middleware.ts +199 -0
- package/src/node-request.ts +240 -0
- package/src/node.ts +212 -0
- package/src/openapi/register.ts +19 -0
- package/src/openapi/scalar.ts +33 -0
- package/src/openapi/spec-factory.ts +46 -0
- package/src/openapi/spec.ts +31 -0
- package/src/openapi/swagger.ts +34 -0
- package/src/plugin.ts +771 -0
- package/src/powered-by.ts +17 -0
- package/src/pretty-json.ts +39 -0
- package/src/proxy.ts +155 -0
- package/src/python-type-emitter.ts +144 -0
- package/src/realtime/buffer.ts +101 -0
- package/src/realtime/bus.ts +93 -0
- package/src/realtime/longpoll.ts +148 -0
- package/src/realtime/protocol.ts +128 -0
- package/src/realtime/route.ts +104 -0
- package/src/realtime/runtime.ts +430 -0
- package/src/request-id.ts +23 -0
- package/src/request-to-curl.ts +34 -0
- package/src/response.ts +414 -0
- package/src/rust-type-emitter.ts +412 -0
- package/src/secure-headers.ts +69 -0
- package/src/serve-register.ts +8 -0
- package/src/serve-slot.ts +20 -0
- package/src/serve.ts +141 -0
- package/src/server-timing.ts +66 -0
- package/src/static.ts +60 -0
- package/src/telemetry/otel.ts +139 -0
- package/src/testing.ts +114 -0
- package/src/timeout.ts +36 -0
- package/src/tree.ts +464 -0
- package/src/ts-type-emitter.ts +106 -0
- package/src/type-emitter.ts +524 -0
- package/src/type-extractor.ts +749 -0
- package/src/types.ts +638 -0
- package/src/validation.ts +300 -0
- package/src/ws/bun.ts +89 -0
- package/src/ws/cloudflare.ts +133 -0
- package/src/ws/deno.ts +109 -0
- package/src/ws/node.ts +161 -0
- package/src/yaml.ts +80 -0
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
# TypeScript API Reference
|
|
2
|
+
|
|
3
|
+
Generated from OpenAPI via the shared codegen IR. Do not edit by hand —
|
|
4
|
+
run `bun run public/honey/core/scripts/gen-api-ref.ts` to regenerate.
|
|
5
|
+
|
|
6
|
+
## Typed Errors
|
|
7
|
+
|
|
8
|
+
Every operation's error channel surfaces one of:
|
|
9
|
+
|
|
10
|
+
| Canonical | Status | Lang symbol | Notes |
|
|
11
|
+
| --------- | ------ | ----------- | ----- |
|
|
12
|
+
| `BadRequest` | 400 | `BadRequest` | invalid request payload |
|
|
13
|
+
| `Unauthorized` | 401 | `Unauthorized` | missing/invalid auth; triggers onAuthExpired + 1 retry |
|
|
14
|
+
| `Forbidden` | 403 | `Forbidden` | authenticated but not permitted |
|
|
15
|
+
| `NotFound` | 404 | `NotFound` | resource does not exist |
|
|
16
|
+
| `Conflict` | 409 | `Conflict` | state conflict / unique violation |
|
|
17
|
+
| `UnprocessableEntity` | 422 | `UnprocessableEntity` | schema-valid but semantically rejected |
|
|
18
|
+
| `TooManyRequests` | 429 | `TooManyRequests` | rate-limited; consumer handles backoff via hooks |
|
|
19
|
+
| `InternalServerError` | 500 | `InternalServerError` | server crash |
|
|
20
|
+
| `BadGateway` | 502 | `BadGateway` | upstream failure |
|
|
21
|
+
| `ServiceUnavailable` | 503 | `ServiceUnavailable` | server temporarily unavailable |
|
|
22
|
+
| `GatewayTimeout` | 504 | `GatewayTimeout` | upstream timeout |
|
|
23
|
+
| `StatusError` | * | `StatusError` | fallback for undeclared/other statuses |
|
|
24
|
+
|
|
25
|
+
Operations with declared `responses: { 4xx: { schema } }` parse the response body into the error's typed `data` field.
|
|
26
|
+
|
|
27
|
+
## Operations
|
|
28
|
+
|
|
29
|
+
### refreshToken
|
|
30
|
+
|
|
31
|
+
`POST /auth/refresh`
|
|
32
|
+
|
|
33
|
+
**Signature**
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
refreshToken(body: { refresh_token: string }): Promise<{ access_token: string; expires_in: number }>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Request body**
|
|
40
|
+
|
|
41
|
+
- Content-type: `application/json`
|
|
42
|
+
- Required: yes
|
|
43
|
+
- Type: `{ refresh_token: string }`
|
|
44
|
+
|
|
45
|
+
**Responses**
|
|
46
|
+
|
|
47
|
+
| Status | Content-type | Type |
|
|
48
|
+
| ------ | ------------ | ---- |
|
|
49
|
+
| `200` | `application/json` | `{ access_token: string; expires_in: number }` |
|
|
50
|
+
| `401` | `application/json` | `{ status: number; message: string }` |
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
### getDeclaredError
|
|
55
|
+
|
|
56
|
+
`GET /declared-errors/{status}`
|
|
57
|
+
|
|
58
|
+
**Signature**
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
getDeclaredError(status: string): Promise<void>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Path params**
|
|
65
|
+
|
|
66
|
+
| Name | Type |
|
|
67
|
+
| ---- | ---- |
|
|
68
|
+
| `status` | `string` |
|
|
69
|
+
|
|
70
|
+
**Responses**
|
|
71
|
+
|
|
72
|
+
| Status | Content-type | Type |
|
|
73
|
+
| ------ | ------------ | ---- |
|
|
74
|
+
| `400` | `application/json` | `{ status: number; message: string }` |
|
|
75
|
+
| `404` | `application/json` | `{ status: number; message: string }` |
|
|
76
|
+
| `default` | `application/json` | `{ status: number; message: string }` |
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### getError
|
|
81
|
+
|
|
82
|
+
`GET /errors/{status}`
|
|
83
|
+
|
|
84
|
+
**Signature**
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
getError(status: number): Promise<void>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Path params**
|
|
91
|
+
|
|
92
|
+
| Name | Type |
|
|
93
|
+
| ---- | ---- |
|
|
94
|
+
| `status` | `number` |
|
|
95
|
+
|
|
96
|
+
**Responses**
|
|
97
|
+
|
|
98
|
+
| Status | Content-type | Type |
|
|
99
|
+
| ------ | ------------ | ---- |
|
|
100
|
+
| `default` | `application/json` | `{ status: number; message: string }` |
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
### idempotentCreate
|
|
105
|
+
|
|
106
|
+
`POST /idempotent-create`
|
|
107
|
+
|
|
108
|
+
**Signature**
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
idempotentCreate(): Promise<{ idempotencyKey: string }>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Responses**
|
|
115
|
+
|
|
116
|
+
| Status | Content-type | Type |
|
|
117
|
+
| ------ | ------------ | ---- |
|
|
118
|
+
| `200` | `application/json` | `{ idempotencyKey: string }` |
|
|
119
|
+
|
|
120
|
+
**Extensions**
|
|
121
|
+
|
|
122
|
+
- `x-idempotency-key`: yes — auto-sent if omitted
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
### connectRealtime
|
|
127
|
+
|
|
128
|
+
`GET /realtime`
|
|
129
|
+
|
|
130
|
+
**Signature**
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
connectRealtime(reconnect_token: string): ResumableConnection
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Query params**
|
|
137
|
+
|
|
138
|
+
| Name | Type |
|
|
139
|
+
| ---- | ---- |
|
|
140
|
+
| `reconnect_token` | `string` |
|
|
141
|
+
|
|
142
|
+
**Responses**
|
|
143
|
+
|
|
144
|
+
| Status | Content-type | Type |
|
|
145
|
+
| ------ | ------------ | ---- |
|
|
146
|
+
| `101` | `—` | — |
|
|
147
|
+
|
|
148
|
+
**Extensions**
|
|
149
|
+
|
|
150
|
+
- `x-realtime`: yes
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
### slow
|
|
155
|
+
|
|
156
|
+
`GET /slow`
|
|
157
|
+
|
|
158
|
+
**Signature**
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
slow(ms: number): Promise<{ ok: boolean }>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Query params**
|
|
165
|
+
|
|
166
|
+
| Name | Type |
|
|
167
|
+
| ---- | ---- |
|
|
168
|
+
| `ms` | `number` |
|
|
169
|
+
|
|
170
|
+
**Responses**
|
|
171
|
+
|
|
172
|
+
| Status | Content-type | Type |
|
|
173
|
+
| ------ | ------------ | ---- |
|
|
174
|
+
| `200` | `application/json` | `{ ok: boolean }` |
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
### streamEvents
|
|
179
|
+
|
|
180
|
+
`GET /stream`
|
|
181
|
+
|
|
182
|
+
**Signature**
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
streamEvents(Last-Event-ID: string): AsyncIterable<{ event?: string; data?: string; id?: string }>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Header params**
|
|
189
|
+
|
|
190
|
+
| Name | Type |
|
|
191
|
+
| ---- | ---- |
|
|
192
|
+
| `Last-Event-ID` | `string` |
|
|
193
|
+
|
|
194
|
+
**Responses**
|
|
195
|
+
|
|
196
|
+
| Status | Content-type | Type |
|
|
197
|
+
| ------ | ------------ | ---- |
|
|
198
|
+
| `200` | `text/event-stream` | `{ event?: string; data?: string; id?: string }` |
|
|
199
|
+
| `401` | `application/json` | `{ status: number; message: string }` |
|
|
200
|
+
|
|
201
|
+
**Extensions**
|
|
202
|
+
|
|
203
|
+
- SSE response detected (`text/event-stream`)
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
### uploadBlob
|
|
208
|
+
|
|
209
|
+
`POST /upload`
|
|
210
|
+
|
|
211
|
+
**Signature**
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
uploadBlob(body: ReadableStream | Blob): Promise<{ size: number; hash: string }>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**Request body**
|
|
218
|
+
|
|
219
|
+
- Content-type: `application/octet-stream`
|
|
220
|
+
- Required: yes
|
|
221
|
+
- Type: `ReadableStream | Blob`
|
|
222
|
+
|
|
223
|
+
**Responses**
|
|
224
|
+
|
|
225
|
+
| Status | Content-type | Type |
|
|
226
|
+
| ------ | ------------ | ---- |
|
|
227
|
+
| `200` | `application/json` | `{ size: number; hash: string }` |
|
|
228
|
+
| `401` | `application/json` | `{ status: number; message: string }` |
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
### listUsers
|
|
233
|
+
|
|
234
|
+
`GET /users`
|
|
235
|
+
|
|
236
|
+
**Signature**
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
listUsers(): Promise<{ items: { id: string; name: string; email: string }[]; total: number }>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**Responses**
|
|
243
|
+
|
|
244
|
+
| Status | Content-type | Type |
|
|
245
|
+
| ------ | ------------ | ---- |
|
|
246
|
+
| `200` | `application/json` | `{ items: { id: string; name: string; email: string }[]; total: number }` |
|
|
247
|
+
| `401` | `application/json` | `{ status: number; message: string }` |
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
### createUser
|
|
252
|
+
|
|
253
|
+
`POST /users`
|
|
254
|
+
|
|
255
|
+
**Signature**
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
createUser(body: { name: string; email: string }): Promise<{ id: string; name: string; email: string }>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Request body**
|
|
262
|
+
|
|
263
|
+
- Content-type: `application/json`
|
|
264
|
+
- Required: yes
|
|
265
|
+
- Type: `{ name: string; email: string }`
|
|
266
|
+
|
|
267
|
+
**Responses**
|
|
268
|
+
|
|
269
|
+
| Status | Content-type | Type |
|
|
270
|
+
| ------ | ------------ | ---- |
|
|
271
|
+
| `201` | `application/json` | `{ id: string; name: string; email: string }` |
|
|
272
|
+
| `401` | `application/json` | `{ status: number; message: string }` |
|
|
273
|
+
| `422` | `application/json` | `{ status: number; message: string }` |
|
|
274
|
+
|
|
275
|
+
**Extensions**
|
|
276
|
+
|
|
277
|
+
- `x-invalidate`: `GET /users`
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
### deleteUser
|
|
282
|
+
|
|
283
|
+
`DELETE /users/{id}`
|
|
284
|
+
|
|
285
|
+
**Signature**
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
deleteUser(id: string): Promise<void>
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**Path params**
|
|
292
|
+
|
|
293
|
+
| Name | Type |
|
|
294
|
+
| ---- | ---- |
|
|
295
|
+
| `id` | `string` |
|
|
296
|
+
|
|
297
|
+
**Responses**
|
|
298
|
+
|
|
299
|
+
| Status | Content-type | Type |
|
|
300
|
+
| ------ | ------------ | ---- |
|
|
301
|
+
| `204` | `—` | — |
|
|
302
|
+
| `401` | `application/json` | `{ status: number; message: string }` |
|
|
303
|
+
| `404` | `application/json` | `{ status: number; message: string }` |
|
|
304
|
+
|
|
305
|
+
**Extensions**
|
|
306
|
+
|
|
307
|
+
- `x-invalidate`: `GET /users`, `GET /users/{id}`
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
### getUser
|
|
312
|
+
|
|
313
|
+
`GET /users/{id}`
|
|
314
|
+
|
|
315
|
+
**Signature**
|
|
316
|
+
|
|
317
|
+
```
|
|
318
|
+
getUser(id: string): Promise<{ id: string; name: string; email: string }>
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**Path params**
|
|
322
|
+
|
|
323
|
+
| Name | Type |
|
|
324
|
+
| ---- | ---- |
|
|
325
|
+
| `id` | `string` |
|
|
326
|
+
|
|
327
|
+
**Responses**
|
|
328
|
+
|
|
329
|
+
| Status | Content-type | Type |
|
|
330
|
+
| ------ | ------------ | ---- |
|
|
331
|
+
| `200` | `application/json` | `{ id: string; name: string; email: string }` |
|
|
332
|
+
| `401` | `application/json` | `{ status: number; message: string }` |
|
|
333
|
+
| `404` | `application/json` | `{ status: number; message: string }` |
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
### updateUser
|
|
338
|
+
|
|
339
|
+
`PUT /users/{id}`
|
|
340
|
+
|
|
341
|
+
**Signature**
|
|
342
|
+
|
|
343
|
+
```
|
|
344
|
+
updateUser(id: string, body: { name?: string; email?: string }): Promise<{ id: string; name: string; email: string }>
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
**Path params**
|
|
348
|
+
|
|
349
|
+
| Name | Type |
|
|
350
|
+
| ---- | ---- |
|
|
351
|
+
| `id` | `string` |
|
|
352
|
+
|
|
353
|
+
**Request body**
|
|
354
|
+
|
|
355
|
+
- Content-type: `application/json`
|
|
356
|
+
- Required: yes
|
|
357
|
+
- Type: `{ name?: string; email?: string }`
|
|
358
|
+
|
|
359
|
+
**Responses**
|
|
360
|
+
|
|
361
|
+
| Status | Content-type | Type |
|
|
362
|
+
| ------ | ------------ | ---- |
|
|
363
|
+
| `200` | `application/json` | `{ id: string; name: string; email: string }` |
|
|
364
|
+
| `401` | `application/json` | `{ status: number; message: string }` |
|
|
365
|
+
| `404` | `application/json` | `{ status: number; message: string }` |
|
|
366
|
+
|
|
367
|
+
**Extensions**
|
|
368
|
+
|
|
369
|
+
- `x-invalidate`: `GET /users`, `GET /users/{id}`
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
### connectWs
|
|
374
|
+
|
|
375
|
+
`GET /ws`
|
|
376
|
+
|
|
377
|
+
**Signature**
|
|
378
|
+
|
|
379
|
+
```
|
|
380
|
+
connectWs(token: string): ResumableConnection
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
**Query params**
|
|
384
|
+
|
|
385
|
+
| Name | Type |
|
|
386
|
+
| ---- | ---- |
|
|
387
|
+
| `token` | `string` |
|
|
388
|
+
|
|
389
|
+
**Responses**
|
|
390
|
+
|
|
391
|
+
| Status | Content-type | Type |
|
|
392
|
+
| ------ | ------------ | ---- |
|
|
393
|
+
| `101` | `—` | — |
|
|
394
|
+
|
|
395
|
+
**Extensions**
|
|
396
|
+
|
|
397
|
+
- `x-websocket`: yes
|
|
398
|
+
|
|
399
|
+
---
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# Honey Invalidate Spec
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Draft
|
|
6
|
+
|
|
7
|
+
## Goal
|
|
8
|
+
|
|
9
|
+
Add first-class Honey metadata for declaring which route operations a mutation invalidates.
|
|
10
|
+
|
|
11
|
+
The design must:
|
|
12
|
+
|
|
13
|
+
- live in Honey base meta types
|
|
14
|
+
- be narrowed by generated route-aware types when available
|
|
15
|
+
- use canonical method-qualified route selectors
|
|
16
|
+
- remain simple to author and read
|
|
17
|
+
- serve as the source of truth for downstream SDK and query integrations
|
|
18
|
+
|
|
19
|
+
## Non-Goals
|
|
20
|
+
|
|
21
|
+
- a generic key DSL
|
|
22
|
+
- tuple-based metadata forms
|
|
23
|
+
- route-pattern inference from REST naming
|
|
24
|
+
- read-side key declarations
|
|
25
|
+
- transport-level `x-fresh` behavior in the first metadata contract
|
|
26
|
+
|
|
27
|
+
## Problem
|
|
28
|
+
|
|
29
|
+
Honey already knows the route graph and already generates route-aware types.
|
|
30
|
+
|
|
31
|
+
Mutations often make one or more read routes stale. We want a declarative way to express that relationship once, in Honey route metadata, so downstream tooling can use it for:
|
|
32
|
+
|
|
33
|
+
- query invalidation
|
|
34
|
+
- selective refetch behavior
|
|
35
|
+
- future SDK-level consistency helpers
|
|
36
|
+
|
|
37
|
+
The metadata should leverage Honey codegen rather than forcing authors to hand-maintain custom operation IDs, tuple formats, or synthetic cache keys.
|
|
38
|
+
|
|
39
|
+
## Proposed API
|
|
40
|
+
|
|
41
|
+
Add `invalidate` to Honey base meta:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
type InvalidateMeta = {
|
|
45
|
+
invalidate?: readonly string[] | null
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
This extends existing Honey base metadata rather than creating a separate metadata namespace.
|
|
50
|
+
|
|
51
|
+
## Authoring Form
|
|
52
|
+
|
|
53
|
+
`invalidate` entries must always use canonical method-qualified route selectors:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
"GET /v1/organizations/:organization_id/projects"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Not allowed in v1:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
"/v1/organizations/:organization_id/projects"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Reason:
|
|
66
|
+
|
|
67
|
+
- no ambiguity
|
|
68
|
+
- no fallback “all methods on this path” behavior
|
|
69
|
+
- no special-case parser rules
|
|
70
|
+
- codegen and runtime matching stay trivial
|
|
71
|
+
- editor autocomplete stays exact
|
|
72
|
+
|
|
73
|
+
## Examples
|
|
74
|
+
|
|
75
|
+
### Mutation Invalidating Collection Read
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
.meta({
|
|
79
|
+
operationId: "project.create",
|
|
80
|
+
invalidate: ["GET /v1/organizations/:organization_id/projects"],
|
|
81
|
+
})
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Mutation Invalidating Collection And Entity Reads
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
.meta({
|
|
88
|
+
operationId: "project.update",
|
|
89
|
+
invalidate: [
|
|
90
|
+
"GET /v1/organizations/:organization_id/projects",
|
|
91
|
+
"GET /v1/organizations/:organization_id/projects/:project_id",
|
|
92
|
+
],
|
|
93
|
+
})
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Mutation Invalidating Query-Like POST Reads
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
.meta({
|
|
100
|
+
operationId: "project.import",
|
|
101
|
+
invalidate: [
|
|
102
|
+
"POST /v1/organizations/:organization_id/projects/search",
|
|
103
|
+
"GET /v1/organizations/:organization_id/projects",
|
|
104
|
+
],
|
|
105
|
+
})
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
This is intentional. `invalidate` should not be artificially limited to `GET`. If a downstream query package treats a `POST` endpoint as queryable, it should be a valid invalidation target.
|
|
109
|
+
|
|
110
|
+
## Canonical Selector Format
|
|
111
|
+
|
|
112
|
+
Each selector must be:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
`${Uppercase<HttpMethod>} ${RoutePattern}`
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Examples:
|
|
119
|
+
|
|
120
|
+
- `GET /v1/users`
|
|
121
|
+
- `GET /v1/users/:id`
|
|
122
|
+
- `POST /v1/search`
|
|
123
|
+
|
|
124
|
+
Requirements:
|
|
125
|
+
|
|
126
|
+
- method must be uppercase
|
|
127
|
+
- path must be the Honey route pattern form
|
|
128
|
+
- path params must remain as route params, for example `:organization_id`
|
|
129
|
+
|
|
130
|
+
## Type Model
|
|
131
|
+
|
|
132
|
+
Honey base meta should define `invalidate` with a permissive fallback type:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
type DefaultInvalidate = readonly string[] | null
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Generated apps should narrow this to an exact union of valid selectors when route-aware generated types exist.
|
|
139
|
+
|
|
140
|
+
Illustrative generated type:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
type GeneratedInvalidateRoute =
|
|
144
|
+
| "GET /v1/organizations/:organization_id/projects"
|
|
145
|
+
| "GET /v1/organizations/:organization_id/projects/:project_id"
|
|
146
|
+
| "POST /v1/organizations/:organization_id/projects/search"
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Then the generated meta type becomes effectively:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
invalidate?: readonly GeneratedInvalidateRoute[] | null
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
If generated route-aware types are unavailable, the type falls back to:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
readonly string[] | null
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Honey Type Integration
|
|
162
|
+
|
|
163
|
+
This should extend existing base Honey metadata, not replace it.
|
|
164
|
+
|
|
165
|
+
Conceptually:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
type OpenApiMeta<TInvalidate extends readonly string[] | null = readonly string[] | null> = {
|
|
169
|
+
internal?: boolean
|
|
170
|
+
deprecated?: boolean
|
|
171
|
+
description?: string
|
|
172
|
+
invalidate?: TInvalidate
|
|
173
|
+
operationId?: string
|
|
174
|
+
security?: Array<Record<string, string[]>> | string | string[]
|
|
175
|
+
summary?: string
|
|
176
|
+
tags?: string | string[]
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
This snippet is conceptual, not a required literal implementation. Honey currently exposes non-generic `OpenApiMeta` and `DefaultMeta`, so the real implementation should preserve that public shape and use Honey's existing generated type or augmentation pattern to narrow `invalidate`.
|
|
181
|
+
|
|
182
|
+
The behavior must be:
|
|
183
|
+
|
|
184
|
+
- base Honey always exposes `invalidate`
|
|
185
|
+
- generated Honey types narrow `invalidate` to the route-selector union when available
|
|
186
|
+
|
|
187
|
+
## Codegen Responsibilities
|
|
188
|
+
|
|
189
|
+
Honey codegen should:
|
|
190
|
+
|
|
191
|
+
1. collect all canonical route selectors from the route tree
|
|
192
|
+
2. emit the selector union for generated type narrowing
|
|
193
|
+
3. preserve `invalidate` metadata into generated outputs needed by downstream tooling
|
|
194
|
+
4. validate that authored selectors exist in the route graph during generation
|
|
195
|
+
|
|
196
|
+
Canonical selectors must be generated from the same route inventory Honey already uses for route typing and OpenAPI emission. There must be one canonical string form for each route operation.
|
|
197
|
+
|
|
198
|
+
## Runtime / Consumer Model
|
|
199
|
+
|
|
200
|
+
Honey core only defines and emits the metadata contract.
|
|
201
|
+
|
|
202
|
+
Downstream packages consume it.
|
|
203
|
+
|
|
204
|
+
Examples:
|
|
205
|
+
|
|
206
|
+
- a query package can map invalidated selectors to query families
|
|
207
|
+
- a future SDK helper can use invalidated selectors to decide which subsequent reads need stronger consistency handling
|
|
208
|
+
|
|
209
|
+
The important constraint is:
|
|
210
|
+
|
|
211
|
+
- authors declare invalidation in terms of actual Honey routes
|
|
212
|
+
- downstream tooling works from normalized route selectors
|
|
213
|
+
|
|
214
|
+
## Why Route Selectors Instead Of Operation IDs
|
|
215
|
+
|
|
216
|
+
Operation IDs are useful but are a secondary naming layer.
|
|
217
|
+
|
|
218
|
+
Route selectors are better for this feature because:
|
|
219
|
+
|
|
220
|
+
- they are directly tied to the actual route graph
|
|
221
|
+
- Honey already understands them structurally
|
|
222
|
+
- they can be generated and validated without inventing another abstraction
|
|
223
|
+
- they naturally support query-like `POST` routes
|
|
224
|
+
- they are clearer in metadata than synthetic cache keys
|
|
225
|
+
|
|
226
|
+
## Why No Tuple Shapes
|
|
227
|
+
|
|
228
|
+
Tuple forms are shorter but harder to read and easier to misuse.
|
|
229
|
+
|
|
230
|
+
This feature is authored metadata, not a performance-critical wire format.
|
|
231
|
+
|
|
232
|
+
Clarity matters more than terseness.
|
|
233
|
+
|
|
234
|
+
## Why No Path-Only Form
|
|
235
|
+
|
|
236
|
+
Path-only selectors look convenient:
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
"/v1/organizations/:organization_id/projects"
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
but they create ambiguity:
|
|
243
|
+
|
|
244
|
+
- does it mean all methods?
|
|
245
|
+
- should `GET` and `HEAD` both match?
|
|
246
|
+
- should `POST` query-like reads match too?
|
|
247
|
+
|
|
248
|
+
Forcing the method in every selector avoids all of that.
|
|
249
|
+
|
|
250
|
+
## Validation Rules
|
|
251
|
+
|
|
252
|
+
Validation must happen in two places:
|
|
253
|
+
|
|
254
|
+
- generated typing should narrow authored values to the exact selector union when available
|
|
255
|
+
- codegen should validate raw authored `invalidate` strings against the route graph during generation
|
|
256
|
+
- duplicates should be deduplicated in generated normalized metadata
|
|
257
|
+
- invalid selectors should fail type-checking when possible and fail generation otherwise
|
|
258
|
+
|
|
259
|
+
## Backward Compatibility
|
|
260
|
+
|
|
261
|
+
This feature is fully opt-in.
|
|
262
|
+
|
|
263
|
+
If `invalidate` is not used:
|
|
264
|
+
|
|
265
|
+
- existing route metadata behavior is unchanged
|
|
266
|
+
- existing generated SDK behavior is unchanged
|
|
267
|
+
- existing query integrations are unchanged
|
|
268
|
+
|
|
269
|
+
## Proposed Implementation Phases
|
|
270
|
+
|
|
271
|
+
### Phase 1: Base Meta
|
|
272
|
+
|
|
273
|
+
- add `invalidate?: readonly string[] | null` to Honey base meta
|
|
274
|
+
|
|
275
|
+
### Phase 2: Generated Narrowing
|
|
276
|
+
|
|
277
|
+
- generate canonical selector unions
|
|
278
|
+
- narrow `invalidate` using generated types when available
|
|
279
|
+
|
|
280
|
+
### Phase 3: Metadata Emission
|
|
281
|
+
|
|
282
|
+
- preserve `invalidate` in generated artifacts needed by downstream consumers
|
|
283
|
+
|
|
284
|
+
### Phase 4: Query Package
|
|
285
|
+
|
|
286
|
+
- consume generated `invalidate` metadata
|
|
287
|
+
- map selectors to query invalidation behavior
|
|
288
|
+
- later add convenience helpers
|
|
289
|
+
|
|
290
|
+
### Phase 5: SDK Integration
|
|
291
|
+
|
|
292
|
+
- if needed, consume normalized invalidation metadata for stronger read-after-write handling
|
|
293
|
+
|
|
294
|
+
## Recommendation
|
|
295
|
+
|
|
296
|
+
Ship the narrow version:
|
|
297
|
+
|
|
298
|
+
- `invalidate`
|
|
299
|
+
- method-qualified route selectors only
|
|
300
|
+
- generated route-aware narrowing
|
|
301
|
+
- downstream packages consume the emitted metadata
|
|
302
|
+
|
|
303
|
+
Do not add tuple forms, key DSLs, or path-only shortcuts in v1.
|