typespec-hono 0.17.0 → 0.18.1
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 +75 -34
- 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,33 +422,57 @@ securityFor) {
|
|
|
406
422
|
const returnsAnything = entries.some((entry) => entry.names.response !== undefined);
|
|
407
423
|
const declaredHelper = returnsAnything
|
|
408
424
|
? `/**
|
|
409
|
-
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
*
|
|
415
|
-
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
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
|
+
* **Only where the shape has declared keys beside it**: an index signature also arrives as a
|
|
434
|
+
* property's own declared type (\`Record<unknown>\`), where it is stated identically in both
|
|
435
|
+
* artefacts and stripping it would destroy the property. A string indexer means "dictionary" only
|
|
436
|
+
* when nothing is declared next to it, which is the same rule the contract types use;
|
|
437
|
+
* - \`readonly\`, which a codebase commonly puts on the views its layers hand back. \`readonly T[]\`
|
|
438
|
+
* and \`T[]\` serialise to identical bytes, so mutability says nothing about a payload.
|
|
439
|
+
*
|
|
440
|
+
* The input types above keep both, deliberately: they describe the value in hand.
|
|
441
|
+
*
|
|
442
|
+
* **An explicit \`undefined\` on an optional property is NOT removed, and that was tried.** It looks
|
|
443
|
+
* like the same class and is the opposite: dropping an index signature or adding \`readonly\` makes
|
|
444
|
+
* MORE values assignable, and removing \`| undefined\` makes fewer. Measured, it broke
|
|
445
|
+
* \`(ctx, input) => ok(input)\` - return what you were given - because what arrives carries it.
|
|
446
|
+
*
|
|
447
|
+
* Returning extra properties still works - excess-property checks apply to object literals, not to a
|
|
448
|
+
* value the application already holds.
|
|
419
449
|
*/
|
|
420
|
-
type
|
|
450
|
+
type ProducedKeyOf<T> = keyof {
|
|
451
|
+
[K in keyof T as string extends K
|
|
452
|
+
? never
|
|
453
|
+
: number extends K
|
|
454
|
+
? never
|
|
455
|
+
: symbol extends K
|
|
456
|
+
? never
|
|
457
|
+
: K]: 0;
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
type Produced<T> = T extends (...args: never[]) => unknown
|
|
421
461
|
? T
|
|
422
462
|
: T extends readonly (infer Element)[]
|
|
423
|
-
?
|
|
424
|
-
? Declared<Element>[]
|
|
425
|
-
: readonly Declared<Element>[]
|
|
463
|
+
? readonly Produced<Element>[]
|
|
426
464
|
: T extends object
|
|
427
|
-
?
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
465
|
+
? [ProducedKeyOf<T>] extends [never]
|
|
466
|
+
? { readonly [K in keyof T]: Produced<T[K]> }
|
|
467
|
+
: {
|
|
468
|
+
readonly [K in keyof T as string extends K
|
|
431
469
|
? never
|
|
432
|
-
:
|
|
470
|
+
: number extends K
|
|
433
471
|
? never
|
|
434
|
-
:
|
|
435
|
-
|
|
472
|
+
: symbol extends K
|
|
473
|
+
? never
|
|
474
|
+
: K]: Produced<T[K]>;
|
|
475
|
+
}
|
|
436
476
|
: T;
|
|
437
477
|
|
|
438
478
|
`
|
|
@@ -643,7 +683,8 @@ type Declared<T> = T extends (...args: never[]) => unknown
|
|
|
643
683
|
* properties at six.
|
|
644
684
|
*/
|
|
645
685
|
const call = input.length === 0
|
|
646
|
-
? `
|
|
686
|
+
? // An empty object, because every operation takes `(ctx, input)`. See `EMPTY_INPUT`.
|
|
687
|
+
`handlersFor(c).${member.route.operationId}(ctx, {})`
|
|
647
688
|
: [
|
|
648
689
|
`handlersFor(c).${member.route.operationId}(ctx, {`,
|
|
649
690
|
...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.1",
|
|
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.1"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@hono/zod-openapi": "^1.4.0",
|