@beignet/core 0.0.34 → 0.0.36

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.
Files changed (120) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +145 -15
  3. package/dist/agent-capabilities/index.d.ts +197 -0
  4. package/dist/agent-capabilities/index.d.ts.map +1 -0
  5. package/dist/agent-capabilities/index.js +282 -0
  6. package/dist/agent-capabilities/index.js.map +1 -0
  7. package/dist/application/index.d.ts +2 -2
  8. package/dist/application/index.d.ts.map +1 -1
  9. package/dist/application/index.js +13 -3
  10. package/dist/application/index.js.map +1 -1
  11. package/dist/client/client.d.ts +2 -0
  12. package/dist/client/client.d.ts.map +1 -1
  13. package/dist/client/client.js +33 -16
  14. package/dist/client/client.js.map +1 -1
  15. package/dist/contracts/contract-builder.d.ts +5 -3
  16. package/dist/contracts/contract-builder.d.ts.map +1 -1
  17. package/dist/contracts/contract-builder.js +3 -12
  18. package/dist/contracts/contract-builder.js.map +1 -1
  19. package/dist/contracts/contract-group.d.ts +2 -2
  20. package/dist/contracts/contract-group.d.ts.map +1 -1
  21. package/dist/contracts/contract-group.js +2 -1
  22. package/dist/contracts/contract-group.js.map +1 -1
  23. package/dist/contracts/metadata.d.ts +3 -0
  24. package/dist/contracts/metadata.d.ts.map +1 -0
  25. package/dist/contracts/metadata.js +14 -0
  26. package/dist/contracts/metadata.js.map +1 -0
  27. package/dist/contracts/types.d.ts +20 -0
  28. package/dist/contracts/types.d.ts.map +1 -1
  29. package/dist/contracts/types.js.map +1 -1
  30. package/dist/events/index.d.ts +4 -4
  31. package/dist/events/index.d.ts.map +1 -1
  32. package/dist/events/index.js +5 -5
  33. package/dist/events/index.js.map +1 -1
  34. package/dist/idempotency/index.d.ts.map +1 -1
  35. package/dist/idempotency/index.js +14 -9
  36. package/dist/idempotency/index.js.map +1 -1
  37. package/dist/jobs/index.d.ts.map +1 -1
  38. package/dist/jobs/index.js +10 -4
  39. package/dist/jobs/index.js.map +1 -1
  40. package/dist/openapi/index.js +15 -8
  41. package/dist/openapi/index.js.map +1 -1
  42. package/dist/ports/auth.d.ts +2 -0
  43. package/dist/ports/auth.d.ts.map +1 -1
  44. package/dist/ports/auth.js +2 -0
  45. package/dist/ports/auth.js.map +1 -1
  46. package/dist/providers/index.d.ts +1 -1
  47. package/dist/providers/index.d.ts.map +1 -1
  48. package/dist/providers/index.js.map +1 -1
  49. package/dist/providers/provider.d.ts +9 -1
  50. package/dist/providers/provider.d.ts.map +1 -1
  51. package/dist/providers/provider.js.map +1 -1
  52. package/dist/schedules/index.d.ts +6 -13
  53. package/dist/schedules/index.d.ts.map +1 -1
  54. package/dist/schedules/index.js +24 -44
  55. package/dist/schedules/index.js.map +1 -1
  56. package/dist/server/index.d.ts +2 -2
  57. package/dist/server/index.d.ts.map +1 -1
  58. package/dist/server/index.js +1 -1
  59. package/dist/server/index.js.map +1 -1
  60. package/dist/server/request-executor.d.ts +72 -0
  61. package/dist/server/request-executor.d.ts.map +1 -0
  62. package/dist/server/request-executor.js +781 -0
  63. package/dist/server/request-executor.js.map +1 -0
  64. package/dist/server/request-preparation.d.ts +39 -0
  65. package/dist/server/request-preparation.d.ts.map +1 -0
  66. package/dist/server/request-preparation.js +248 -0
  67. package/dist/server/request-preparation.js.map +1 -0
  68. package/dist/server/response-finalization.d.ts +34 -0
  69. package/dist/server/response-finalization.d.ts.map +1 -0
  70. package/dist/server/response-finalization.js +279 -0
  71. package/dist/server/response-finalization.js.map +1 -0
  72. package/dist/server/route-definitions.d.ts +139 -0
  73. package/dist/server/route-definitions.d.ts.map +1 -0
  74. package/dist/server/route-definitions.js +94 -0
  75. package/dist/server/route-definitions.js.map +1 -0
  76. package/dist/server/route-matching.d.ts +15 -0
  77. package/dist/server/route-matching.d.ts.map +1 -0
  78. package/dist/server/route-matching.js +46 -0
  79. package/dist/server/route-matching.js.map +1 -0
  80. package/dist/server/server.d.ts +6 -147
  81. package/dist/server/server.d.ts.map +1 -1
  82. package/dist/server/server.js +29 -1350
  83. package/dist/server/server.js.map +1 -1
  84. package/dist/server/use-case-route.d.ts +1 -1
  85. package/dist/tracing/index.d.ts +1 -1
  86. package/dist/tracing/index.d.ts.map +1 -1
  87. package/dist/tracing/index.js.map +1 -1
  88. package/dist/uploads/client.js +4 -7
  89. package/dist/uploads/client.js.map +1 -1
  90. package/dist/uploads/index.d.ts.map +1 -1
  91. package/dist/uploads/index.js +59 -12
  92. package/dist/uploads/index.js.map +1 -1
  93. package/package.json +5 -1
  94. package/skills/app-architecture/SKILL.md +27 -1
  95. package/src/agent-capabilities/index.ts +643 -0
  96. package/src/application/index.ts +19 -5
  97. package/src/client/client.ts +41 -17
  98. package/src/contracts/contract-builder.ts +8 -16
  99. package/src/contracts/contract-group.ts +4 -6
  100. package/src/contracts/metadata.ts +20 -0
  101. package/src/contracts/types.ts +39 -0
  102. package/src/events/index.ts +16 -20
  103. package/src/idempotency/index.ts +17 -9
  104. package/src/jobs/index.ts +8 -3
  105. package/src/openapi/index.ts +24 -12
  106. package/src/ports/auth.ts +2 -0
  107. package/src/providers/index.ts +1 -0
  108. package/src/providers/provider.ts +17 -2
  109. package/src/schedules/index.ts +57 -63
  110. package/src/server/index.ts +4 -2
  111. package/src/server/request-executor.ts +1170 -0
  112. package/src/server/request-preparation.ts +409 -0
  113. package/src/server/response-finalization.ts +385 -0
  114. package/src/server/route-definitions.ts +338 -0
  115. package/src/server/route-matching.ts +64 -0
  116. package/src/server/server.ts +63 -2139
  117. package/src/server/use-case-route.ts +1 -1
  118. package/src/tracing/index.ts +1 -0
  119. package/src/uploads/client.ts +4 -7
  120. package/src/uploads/index.ts +70 -12
@@ -485,9 +485,11 @@ async function validateHeaderSchemas(
485
485
  type PrimitiveParam = string | number | boolean;
486
486
  type QueryParamValue =
487
487
  | PrimitiveParam
488
+ | Date
489
+ | Record<string, unknown>
488
490
  | null
489
491
  | undefined
490
- | Array<PrimitiveParam>;
492
+ | Array<PrimitiveParam | Date | Record<string, unknown>>;
491
493
 
492
494
  type PathParams = Record<string, PrimitiveParam>;
493
495
  type QueryParams = Record<string, QueryParamValue>;
@@ -946,26 +948,12 @@ export class Endpoint<
946
948
  const normalizedQuery = queryToSerialize ?? query;
947
949
  for (const [key, value] of Object.entries(normalizedQuery)) {
948
950
  if (value !== undefined && value !== null) {
949
- if (typeof value === "object" && !Array.isArray(value)) {
950
- throw new ContractError({
951
- source: "client",
952
- code: "INVALID_QUERY_PARAM",
953
- message: `Query parameter "${key}" contains a non-serializable object. Use primitive values or arrays of primitives.`,
954
- });
955
- }
956
951
  if (Array.isArray(value)) {
957
952
  for (const v of value) {
958
- if (typeof v === "object" && v !== null) {
959
- throw new ContractError({
960
- source: "client",
961
- code: "INVALID_QUERY_PARAM",
962
- message: `Query parameter "${key}" contains a non-serializable array element. Use primitive values only.`,
963
- });
964
- }
965
- params.append(key, String(v));
953
+ params.append(key, this.serializeQueryParam(key, v));
966
954
  }
967
955
  } else {
968
- params.append(key, String(value));
956
+ params.append(key, this.serializeQueryParam(key, value));
969
957
  }
970
958
  }
971
959
  }
@@ -983,6 +971,42 @@ export class Endpoint<
983
971
  return baseUrl + url;
984
972
  }
985
973
 
974
+ private serializeQueryParam(key: string, value: unknown): string {
975
+ if (
976
+ typeof value === "string" ||
977
+ typeof value === "number" ||
978
+ typeof value === "boolean"
979
+ ) {
980
+ return String(value);
981
+ }
982
+
983
+ if (value instanceof Date) {
984
+ if (!Number.isFinite(value.getTime())) {
985
+ throw this.invalidQueryParam(key, "contains an invalid Date");
986
+ }
987
+ return value.toISOString();
988
+ }
989
+
990
+ if (typeof value === "object" && value !== null) {
991
+ try {
992
+ const serialized = JSON.stringify(value);
993
+ if (serialized !== undefined) return serialized;
994
+ } catch {
995
+ // Report the contract/query key below instead of leaking JSON internals.
996
+ }
997
+ }
998
+
999
+ throw this.invalidQueryParam(key, "contains a non-serializable value");
1000
+ }
1001
+
1002
+ private invalidQueryParam(key: string, reason: string): ContractError {
1003
+ return new ContractError({
1004
+ source: "client",
1005
+ code: "INVALID_QUERY_PARAM",
1006
+ message: `Query parameter "${key}" ${reason}. Use primitive values, Dates, JSON-serializable objects, or arrays of those values.`,
1007
+ });
1008
+ }
1009
+
986
1010
  /**
987
1011
  * Build request headers
988
1012
  */
@@ -5,6 +5,7 @@ import {
5
5
  mergeCatalogErrors,
6
6
  responsesFromErrors,
7
7
  } from "./catalog-errors.js";
8
+ import { mergeContractMeta } from "./metadata.js";
8
9
  import type { OpenAPIOperationMeta } from "./openapi-meta.js";
9
10
  import { parsePathTemplate } from "./path-template.js";
10
11
  import type {
@@ -15,6 +16,7 @@ import type {
15
16
  ContractResponses,
16
17
  HttpContractConfig,
17
18
  HttpMethod,
19
+ MergeContractMeta,
18
20
  MergedContractErrorResponses,
19
21
  OmitMetaKeys,
20
22
  ResponsesFromErrorDefinitions,
@@ -436,7 +438,7 @@ export class ContractBuilder<
436
438
  TBody,
437
439
  THeaders,
438
440
  TResponses,
439
- OmitMetaKeys<TMeta, keyof TNewMeta> & TNewMeta,
441
+ MergeContractMeta<TMeta, TNewMeta>,
440
442
  TPath
441
443
  > {
442
444
  return new ContractBuilder({
@@ -451,18 +453,15 @@ export class ContractBuilder<
451
453
  headers: this._headers,
452
454
  body: this._body,
453
455
  responses: this._responses,
454
- metadata: {
455
- ...this._meta,
456
- ...newMeta,
457
- } as unknown as OmitMetaKeys<TMeta, keyof TNewMeta> & TNewMeta,
456
+ metadata: mergeContractMeta(this._meta, newMeta),
458
457
  });
459
458
  }
460
459
 
461
460
  /**
462
461
  * Merge OpenAPI operation metadata into this contract.
463
462
  */
464
- openapi(
465
- patch: Partial<OpenAPIOperationMeta>,
463
+ openapi<TPatch extends Partial<OpenAPIOperationMeta>>(
464
+ patch: TPatch,
466
465
  ): ContractBuilder<
467
466
  TMethod,
468
467
  TPathParams,
@@ -470,10 +469,9 @@ export class ContractBuilder<
470
469
  TBody,
471
470
  THeaders,
472
471
  TResponses,
473
- TMeta,
472
+ MergeContractMeta<TMeta, { openapi: TPatch }>,
474
473
  TPath
475
474
  > {
476
- const existingOpenApi = this._meta.openapi ?? {};
477
475
  return new ContractBuilder({
478
476
  kind: this.kind,
479
477
  name: this.name,
@@ -486,13 +484,7 @@ export class ContractBuilder<
486
484
  headers: this._headers,
487
485
  body: this._body,
488
486
  responses: this._responses,
489
- metadata: {
490
- ...this._meta,
491
- openapi: {
492
- ...existingOpenApi,
493
- ...patch,
494
- },
495
- } as TMeta,
487
+ metadata: mergeContractMeta(this._meta, { openapi: patch }),
496
488
  });
497
489
  }
498
490
  }
@@ -5,6 +5,7 @@ import {
5
5
  responsesFromErrors,
6
6
  } from "./catalog-errors.js";
7
7
  import { ContractBuilder } from "./contract-builder.js";
8
+ import { mergeContractMeta } from "./metadata.js";
8
9
  import { parsePathTemplate } from "./path-template.js";
9
10
  import type {
10
11
  ContractErrorResponses,
@@ -12,6 +13,7 @@ import type {
12
13
  ContractMeta,
13
14
  ContractResponses,
14
15
  HttpMethod,
16
+ MergeContractMeta,
15
17
  MergedContractErrorResponses,
16
18
  OmitMetaKeys,
17
19
  ResponsesFromErrorDefinitions,
@@ -149,17 +151,13 @@ export class ContractGroup<
149
151
  meta: TNewMeta,
150
152
  ): ContractGroup<
151
153
  TSharedResponses,
152
- OmitMetaKeys<TSharedMeta, keyof TNewMeta> & TNewMeta,
154
+ MergeContractMeta<TSharedMeta, TNewMeta>,
153
155
  TSharedHeaders,
154
156
  TPathPrefix
155
157
  > {
156
158
  return new ContractGroup({
157
159
  namespace: this._namespace,
158
- meta: { ...this._meta, ...meta } as unknown as OmitMetaKeys<
159
- TSharedMeta,
160
- keyof TNewMeta
161
- > &
162
- TNewMeta,
160
+ meta: mergeContractMeta(this._meta, meta),
163
161
  responses: this._responses,
164
162
  headers: this._headers,
165
163
  pathPrefix: this._pathPrefix,
@@ -0,0 +1,20 @@
1
+ import type { ContractMeta, MergeContractMeta } from "./types.js";
2
+
3
+ export function mergeContractMeta<
4
+ TCurrent extends ContractMeta,
5
+ TNext extends ContractMeta,
6
+ >(current: TCurrent, next: TNext): MergeContractMeta<TCurrent, TNext> {
7
+ const metadata: ContractMeta = {
8
+ ...current,
9
+ ...next,
10
+ };
11
+
12
+ if (next.openapi !== undefined) {
13
+ metadata.openapi = {
14
+ ...current.openapi,
15
+ ...next.openapi,
16
+ };
17
+ }
18
+
19
+ return metadata as MergeContractMeta<TCurrent, TNext>;
20
+ }
@@ -256,6 +256,45 @@ export type OmitMetaKeys<TMeta, K extends PropertyKey> = {
256
256
  [P in keyof TMeta as P extends K ? never : P]: TMeta[P];
257
257
  };
258
258
 
259
+ type DefinedOpenAPIMeta<T> = [Extract<T, OpenAPIOperationMeta>] extends [never]
260
+ ? Record<never, never>
261
+ : Extract<T, OpenAPIOperationMeta>;
262
+
263
+ type OpenAPIMetaFrom<TMeta extends ContractMeta> = TMeta extends {
264
+ openapi: infer TOpenAPI;
265
+ }
266
+ ? DefinedOpenAPIMeta<TOpenAPI>
267
+ : "openapi" extends keyof TMeta
268
+ ? NonNullable<TMeta["openapi"]>
269
+ : Record<never, never>;
270
+
271
+ /**
272
+ * Merge contract metadata while preserving accumulated OpenAPI fields.
273
+ *
274
+ * Top-level metadata uses last-write-wins semantics. OpenAPI operation
275
+ * metadata is merged one field deep so `meta({ openapi: ... })` and
276
+ * `openapi(...)` compose consistently.
277
+ */
278
+ export type MergeContractMeta<
279
+ TCurrent extends ContractMeta,
280
+ TNext extends ContractMeta,
281
+ > = ContractMeta &
282
+ OmitMetaKeys<TCurrent, keyof TNext> &
283
+ OmitMetaKeys<TNext, "openapi"> &
284
+ ("openapi" extends keyof TNext
285
+ ? TNext extends { openapi: infer TNextOpenAPI }
286
+ ? TNextOpenAPI extends OpenAPIOperationMeta
287
+ ? {
288
+ openapi: Omit<
289
+ OpenAPIMetaFrom<TCurrent>,
290
+ keyof OpenAPIMetaFrom<TNext>
291
+ > &
292
+ OpenAPIMetaFrom<TNext>;
293
+ }
294
+ : { openapi: TNextOpenAPI }
295
+ : { openapi?: OpenAPIOperationMeta }
296
+ : Record<never, never>);
297
+
259
298
  /**
260
299
  * Plain HTTP contract configuration consumed by server, client, and tooling.
261
300
  */
@@ -119,15 +119,11 @@ export interface ListenerDef<
119
119
  /**
120
120
  * Options for `defineListener(...)`.
121
121
  */
122
- export interface DefineListenerOptions<
123
- E extends EventDef,
124
- Ctx,
125
- Name extends string,
126
- > {
122
+ export interface DefineListenerOptions<E extends EventDef, Ctx> {
127
123
  /**
128
- * Stable listener name.
124
+ * Event this listener handles.
129
125
  */
130
- name: Name;
126
+ event: E;
131
127
  /**
132
128
  * Handle a parsed event payload.
133
129
  */
@@ -181,9 +177,9 @@ export interface Listeners<Ctx> {
181
177
  /**
182
178
  * Define a listener with the bound context type.
183
179
  */
184
- defineListener<E extends EventDef, Name extends string = string>(
185
- event: E,
186
- options: DefineListenerOptions<E, Ctx, Name>,
180
+ defineListener<Name extends string, E extends EventDef>(
181
+ name: Name,
182
+ options: DefineListenerOptions<E, Ctx>,
187
183
  ): ListenerDef<E, Ctx, Name>;
188
184
  }
189
185
 
@@ -269,17 +265,17 @@ export function defineEvent<
269
265
  }
270
266
 
271
267
  function defineListenerImpl<
272
- E extends EventDef,
273
- Ctx = unknown,
274
268
  Name extends string = string,
269
+ E extends EventDef = EventDef,
270
+ Ctx = unknown,
275
271
  >(
276
- event: E,
277
- options: DefineListenerOptions<E, Ctx, Name>,
272
+ name: Name,
273
+ options: DefineListenerOptions<E, Ctx>,
278
274
  ): ListenerDef<E, Ctx, Name> {
279
275
  return {
280
276
  kind: "listener",
281
- name: options.name,
282
- event,
277
+ name,
278
+ event: options.event,
283
279
  handle: options.handle,
284
280
  };
285
281
  }
@@ -369,11 +365,11 @@ export function registerListeners<Ctx>(
369
365
  */
370
366
  export function createListeners<Ctx>(): Listeners<Ctx> {
371
367
  return {
372
- defineListener<E extends EventDef, Name extends string = string>(
373
- event: E,
374
- options: DefineListenerOptions<E, Ctx, Name>,
368
+ defineListener<Name extends string, E extends EventDef>(
369
+ name: Name,
370
+ options: DefineListenerOptions<E, Ctx>,
375
371
  ): ListenerDef<E, Ctx, Name> {
376
- return defineListenerImpl(event, options);
372
+ return defineListenerImpl(name, options);
377
373
  },
378
374
  };
379
375
  }
@@ -644,16 +644,9 @@ export async function runIdempotently<Result>(
644
644
  throw new IdempotencyConflictError(reservation);
645
645
  }
646
646
  case "reserved": {
647
+ let result: Result;
647
648
  try {
648
- const result = await options.run();
649
- await idempotency.complete({
650
- namespace: operation.namespace,
651
- key: operation.key,
652
- scope: operation.scope,
653
- fingerprint: operation.fingerprint,
654
- result,
655
- });
656
- return result;
649
+ result = await options.run();
657
650
  } catch (error) {
658
651
  await idempotency.fail({
659
652
  namespace: operation.namespace,
@@ -664,6 +657,21 @@ export async function runIdempotently<Result>(
664
657
  });
665
658
  throw error;
666
659
  }
660
+
661
+ await idempotency.complete({
662
+ namespace: operation.namespace,
663
+ key: operation.key,
664
+ scope: operation.scope,
665
+ fingerprint: operation.fingerprint,
666
+ result,
667
+ });
668
+ return result;
669
+ }
670
+ default: {
671
+ const unsupported = reservation as { status?: unknown };
672
+ throw new Error(
673
+ `Idempotency port returned unsupported reservation status "${String(unsupported.status)}".`,
674
+ );
667
675
  }
668
676
  }
669
677
  }
package/src/jobs/index.ts CHANGED
@@ -1452,10 +1452,15 @@ export function createInlineJobDispatcher<Ctx>(
1452
1452
  payload: InferJobPayload<J>,
1453
1453
  retryEnabled: boolean,
1454
1454
  dispatchOptions: SingleAttemptJobDispatchOptions = {},
1455
+ propagateFailure = false,
1455
1456
  ): Promise<void> {
1456
1457
  const fail = (error: unknown): void => {
1457
- options.onError?.(error, job);
1458
- if (!options.onError) throw error;
1458
+ try {
1459
+ options.onError?.(error, job);
1460
+ } catch (observerError) {
1461
+ if (!propagateFailure) throw observerError;
1462
+ }
1463
+ if (propagateFailure || !options.onError) throw error;
1459
1464
  };
1460
1465
 
1461
1466
  let parsed: InferJobPayload<J>;
@@ -1522,7 +1527,7 @@ export function createInlineJobDispatcher<Ctx>(
1522
1527
  job: J,
1523
1528
  payload: InferJobPayload<J>,
1524
1529
  dispatchOptions?: SingleAttemptJobDispatchOptions,
1525
- ) => run(job, payload, false, dispatchOptions),
1530
+ ) => run(job, payload, false, dispatchOptions, true),
1526
1531
  enumerable: false,
1527
1532
  });
1528
1533
 
@@ -553,10 +553,13 @@ function addPathParams(
553
553
 
554
554
  const shape = state.introspector.getShape(contract.pathParams);
555
555
  if (!shape) {
556
- if (pathKeys.length > 0) {
557
- throw new Error(
558
- `Unable to introspect pathParams for contract "${contract.name}" at "${contract.path}". OpenAPI path parameters must match the path template.`,
559
- );
556
+ for (const key of pathKeys) {
557
+ addParameter(operation, {
558
+ name: key,
559
+ in: "path",
560
+ required: true,
561
+ schema: { type: "string" },
562
+ });
560
563
  }
561
564
  return;
562
565
  }
@@ -612,14 +615,8 @@ function addQueryParams(
612
615
  const originalField = shape[key];
613
616
  const optional = state.introspector.isOptional(originalField);
614
617
 
615
- let description = state.introspector.getDescription(originalField);
616
-
617
618
  const field = originalField;
618
-
619
- // If outer optional didn't have description, check inner type
620
- if (!description && optional) {
621
- description = state.introspector.getDescription(field);
622
- }
619
+ const description = parameterDescription(field, optional, state);
623
620
 
624
621
  const paramSchemaRef = schemaToConvertedSchemaRef(
625
622
  field,
@@ -656,7 +653,7 @@ function addHeaderParams(
656
653
  for (const key of Object.keys(shape)) {
657
654
  const originalField = shape[key];
658
655
  const optional = state.introspector.isOptional(originalField);
659
- const description = state.introspector.getDescription(originalField);
656
+ const description = parameterDescription(originalField, optional, state);
660
657
 
661
658
  const paramSchemaRef = schemaToConvertedSchemaRef(
662
659
  originalField,
@@ -676,6 +673,21 @@ function addHeaderParams(
676
673
  }
677
674
  }
678
675
 
676
+ function parameterDescription(
677
+ field: unknown,
678
+ optional: boolean,
679
+ state: GeneratorState,
680
+ ): string | undefined {
681
+ return (
682
+ state.introspector.getDescription(field) ??
683
+ (optional
684
+ ? state.introspector.getDescription(
685
+ state.introspector.unwrapOptional(field),
686
+ )
687
+ : undefined)
688
+ );
689
+ }
690
+
679
691
  /**
680
692
  * Add request body from contract to operation
681
693
  */
package/src/ports/auth.ts CHANGED
@@ -36,6 +36,8 @@ export interface AuthSession<User = unknown, Session = unknown> {
36
36
  */
37
37
  export class AuthUnauthorizedError extends Error {
38
38
  readonly code = "UNAUTHORIZED";
39
+ /** HTTP status used when the server maps this framework error. */
40
+ readonly status = 401;
39
41
 
40
42
  constructor(message = "Unauthorized") {
41
43
  super(message);
@@ -39,6 +39,7 @@ export {
39
39
  parseProviderPackageMetadata,
40
40
  } from "./metadata.js";
41
41
  export {
42
+ type AnyProviderConfigSchema,
42
43
  type AnyServiceProvider,
43
44
  createProvider,
44
45
  type InferOutput,
@@ -240,6 +240,22 @@ export interface ServiceProvider<
240
240
  readonly __providedPorts?: ProvidedPorts;
241
241
  }
242
242
 
243
+ /**
244
+ * A provider configuration schema whose concrete validation library and input
245
+ * shape are intentionally erased while its validated output may stay typed.
246
+ *
247
+ * Reusable provider packages use this in their named provider return types so
248
+ * internal Zod schemas do not become part of the package's public API.
249
+ */
250
+ export type AnyProviderConfigSchema<
251
+ // biome-ignore lint/suspicious/noExplicitAny: arbitrary provider consumers may erase config output
252
+ Output = any,
253
+ > = StandardSchemaV1<
254
+ // biome-ignore lint/suspicious/noExplicitAny: reusable provider return types intentionally erase config input
255
+ any,
256
+ Output
257
+ >;
258
+
243
259
  /**
244
260
  * Loosely typed service provider.
245
261
  *
@@ -250,8 +266,7 @@ export interface ServiceProvider<
250
266
  */
251
267
  export type AnyServiceProvider = ServiceProvider<
252
268
  unknown,
253
- // biome-ignore lint/suspicious/noExplicitAny: provider config types are erased at this level
254
- StandardSchemaV1<any, any>,
269
+ AnyProviderConfigSchema,
255
270
  ProviderPorts,
256
271
  // biome-ignore lint/suspicious/noExplicitAny: provider context types are erased at this level
257
272
  any,