typespec-hono 0.17.0 → 0.18.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/dist/src/app.js +52 -27
- package/package.json +2 -2
package/dist/src/app.js
CHANGED
|
@@ -359,6 +359,23 @@ securityFor) {
|
|
|
359
359
|
const slot = `${entry.route.verb} ${entry.route.path}`;
|
|
360
360
|
grouped.set(slot, [...(grouped.get(slot) ?? []), entry]);
|
|
361
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* The input type of an operation that declares no input.
|
|
364
|
+
*
|
|
365
|
+
* **Every operation takes `(ctx, input)`, including this one.** A surface written once against
|
|
366
|
+
* `Operations` - an RPC entrypoint, a proxy, any uniform dispatch - is typed `(ctx, input)`, and
|
|
367
|
+
* TypeScript refuses a function with MORE parameters than the signature it is assigned to. So a
|
|
368
|
+
* single parameterless operation emitting `(ctx)` made the whole surface unassignable, measured as
|
|
369
|
+
* `TS2322: Target signature provides too few arguments. Expected 2 or more, but got 1.`
|
|
370
|
+
*
|
|
371
|
+
* **Adding the parameter breaks nothing**, which is what makes this the right shape rather than a
|
|
372
|
+
* trade: FEWER parameters is always assignable, so a handler already written `(ctx) => ...` keeps
|
|
373
|
+
* compiling untouched. `test/openmodel/treaty.test.ts` holds both halves.
|
|
374
|
+
*
|
|
375
|
+
* `Record<string, never>` rather than `{}`, because `{}` in TypeScript means "anything but null",
|
|
376
|
+
* which is the opposite of the claim being made.
|
|
377
|
+
*/
|
|
378
|
+
const EMPTY_INPUT = "Record<string, never>";
|
|
362
379
|
const methods = entries.map((entry) => {
|
|
363
380
|
const { route, names } = entry;
|
|
364
381
|
/**
|
|
@@ -376,21 +393,20 @@ securityFor) {
|
|
|
376
393
|
? negotiated
|
|
377
394
|
: `${validated} & ${negotiated}`;
|
|
378
395
|
/**
|
|
379
|
-
* **The RETURN type
|
|
396
|
+
* **The RETURN type is the producer's view; the input type is left exactly as it arrives.**
|
|
380
397
|
*
|
|
381
|
-
* A handler receives whatever the validator let through,
|
|
382
|
-
*
|
|
383
|
-
* value in hand. Returning is the opposite direction: the handler supplies
|
|
384
|
-
* application already holds, and
|
|
385
|
-
* description.
|
|
386
|
-
* could not satisfy it without a spread at every level of the tree.
|
|
398
|
+
* A handler receives whatever the validator let through, so an input carrying
|
|
399
|
+
* `[key: string]: unknown`, `T | undefined` on an optional, and mutable arrays is an honest
|
|
400
|
+
* description of the value in hand. Returning is the opposite direction: the handler supplies
|
|
401
|
+
* something the application already holds, and each of those becomes an obligation rather than
|
|
402
|
+
* a description. See `Produced` below for what that cost, measured.
|
|
387
403
|
*
|
|
388
|
-
* `typespec-http-zod
|
|
389
|
-
*
|
|
390
|
-
* types - which is exactly the half-fix `test/openmodel/`
|
|
404
|
+
* `typespec-http-zod` fixes the same three things on its contract types. None of it reaches
|
|
405
|
+
* here on its own, because this signature is derived from `z.infer` rather than from those
|
|
406
|
+
* types - which is exactly the half-fix `test/openmodel/` exists to catch.
|
|
391
407
|
*/
|
|
392
|
-
const output = names.response === undefined ? "void" : `
|
|
393
|
-
const signature =
|
|
408
|
+
const output = names.response === undefined ? "void" : `Produced<z.infer<typeof ${names.response}>>`;
|
|
409
|
+
const signature = `ctx: Ctx, input: ${input ?? EMPTY_INPUT}`;
|
|
394
410
|
const doc = route.summary === undefined ? "" : `\t/** ${route.summary} */\n`;
|
|
395
411
|
return `${doc}\t${route.operationId}(${signature}): Awaitable<Result<${output}>>;`;
|
|
396
412
|
});
|
|
@@ -406,32 +422,40 @@ securityFor) {
|
|
|
406
422
|
const returnsAnything = entries.some((entry) => entry.names.response !== undefined);
|
|
407
423
|
const declaredHelper = returnsAnything
|
|
408
424
|
? `/**
|
|
409
|
-
*
|
|
425
|
+
* What a handler must SUPPLY, as opposed to what it receives.
|
|
426
|
+
*
|
|
427
|
+
* **Three things that are true of an inferred type are not obligations on a producer**, and each one
|
|
428
|
+
* made a value the application already held unreturnable:
|
|
429
|
+
*
|
|
430
|
+
* - an index signature, which an open model infers because its validator really does pass unknown
|
|
431
|
+
* keys through. TypeScript gives an interface no implicit index signature, so returning one meant
|
|
432
|
+
* spreading every level of the tree - a structural deep copy per response on one real service;
|
|
433
|
+
* - \`readonly\`, which a codebase commonly puts on the views its layers hand back. \`readonly T[]\`
|
|
434
|
+
* and \`T[]\` serialise to identical bytes, so mutability says nothing about a payload.
|
|
435
|
+
*
|
|
436
|
+
* The input types above keep both, deliberately: they describe the value in hand.
|
|
410
437
|
*
|
|
411
|
-
* An
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
*
|
|
415
|
-
* every level of the tree, which on one real service meant a structural deep copy per response.
|
|
438
|
+
* **An explicit \`undefined\` on an optional property is NOT removed, and that was tried.** It looks
|
|
439
|
+
* like the same class and is the opposite: dropping an index signature or adding \`readonly\` makes
|
|
440
|
+
* MORE values assignable, and removing \`| undefined\` makes fewer. Measured, it broke
|
|
441
|
+
* \`(ctx, input) => ok(input)\` - return what you were given - because what arrives carries it.
|
|
416
442
|
*
|
|
417
|
-
* Returning extra properties still works - excess-property checks apply to object literals, not to
|
|
418
|
-
*
|
|
443
|
+
* Returning extra properties still works - excess-property checks apply to object literals, not to a
|
|
444
|
+
* value the application already holds.
|
|
419
445
|
*/
|
|
420
|
-
type
|
|
446
|
+
type Produced<T> = T extends (...args: never[]) => unknown
|
|
421
447
|
? T
|
|
422
448
|
: T extends readonly (infer Element)[]
|
|
423
|
-
?
|
|
424
|
-
? Declared<Element>[]
|
|
425
|
-
: readonly Declared<Element>[]
|
|
449
|
+
? readonly Produced<Element>[]
|
|
426
450
|
: T extends object
|
|
427
451
|
? {
|
|
428
|
-
[K in keyof T as string extends K
|
|
452
|
+
readonly [K in keyof T as string extends K
|
|
429
453
|
? never
|
|
430
454
|
: number extends K
|
|
431
455
|
? never
|
|
432
456
|
: symbol extends K
|
|
433
457
|
? never
|
|
434
|
-
: K]:
|
|
458
|
+
: K]: Produced<T[K]>;
|
|
435
459
|
}
|
|
436
460
|
: T;
|
|
437
461
|
|
|
@@ -643,7 +667,8 @@ type Declared<T> = T extends (...args: never[]) => unknown
|
|
|
643
667
|
* properties at six.
|
|
644
668
|
*/
|
|
645
669
|
const call = input.length === 0
|
|
646
|
-
? `
|
|
670
|
+
? // An empty object, because every operation takes `(ctx, input)`. See `EMPTY_INPUT`.
|
|
671
|
+
`handlersFor(c).${member.route.operationId}(ctx, {})`
|
|
647
672
|
: [
|
|
648
673
|
`handlersFor(c).${member.route.operationId}(ctx, {`,
|
|
649
674
|
...input.map((piece) => `\t\t\t\t\t\t${piece},`),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "typespec-hono",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "TypeSpec emitter: generate a Hono server, and the Zod validators it enforces, from an HTTP service definition, agreeing with the OpenAPI document @typespec/openapi3 publishes from the same source.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare-workers",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"provenance": true
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"typespec-http-zod": "^0.
|
|
47
|
+
"typespec-http-zod": "^0.19.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@hono/zod-openapi": "^1.4.0",
|