@beignet/core 0.0.33 → 0.0.35

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 (142) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +207 -30
  3. package/dist/agent-capabilities/index.d.ts +167 -0
  4. package/dist/agent-capabilities/index.d.ts.map +1 -0
  5. package/dist/agent-capabilities/index.js +275 -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 -7
  16. package/dist/contracts/contract-builder.d.ts.map +1 -1
  17. package/dist/contracts/contract-builder.js +3 -18
  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/flags/index.d.ts +0 -4
  35. package/dist/flags/index.d.ts.map +1 -1
  36. package/dist/flags/index.js +0 -4
  37. package/dist/flags/index.js.map +1 -1
  38. package/dist/idempotency/index.d.ts.map +1 -1
  39. package/dist/idempotency/index.js +14 -9
  40. package/dist/idempotency/index.js.map +1 -1
  41. package/dist/jobs/index.d.ts.map +1 -1
  42. package/dist/jobs/index.js +10 -4
  43. package/dist/jobs/index.js.map +1 -1
  44. package/dist/notifications/index.d.ts +194 -10
  45. package/dist/notifications/index.d.ts.map +1 -1
  46. package/dist/notifications/index.js +397 -59
  47. package/dist/notifications/index.js.map +1 -1
  48. package/dist/openapi/index.js +15 -8
  49. package/dist/openapi/index.js.map +1 -1
  50. package/dist/ports/auth.d.ts +2 -0
  51. package/dist/ports/auth.d.ts.map +1 -1
  52. package/dist/ports/auth.js +2 -0
  53. package/dist/ports/auth.js.map +1 -1
  54. package/dist/ports/events.d.ts +2 -2
  55. package/dist/ports/index.d.ts +1 -1
  56. package/dist/ports/index.d.ts.map +1 -1
  57. package/dist/ports/index.js +1 -1
  58. package/dist/ports/index.js.map +1 -1
  59. package/dist/providers/index.d.ts +1 -1
  60. package/dist/providers/index.d.ts.map +1 -1
  61. package/dist/providers/index.js.map +1 -1
  62. package/dist/providers/provider.d.ts +10 -2
  63. package/dist/providers/provider.d.ts.map +1 -1
  64. package/dist/providers/provider.js.map +1 -1
  65. package/dist/schedules/index.d.ts +6 -13
  66. package/dist/schedules/index.d.ts.map +1 -1
  67. package/dist/schedules/index.js +24 -44
  68. package/dist/schedules/index.js.map +1 -1
  69. package/dist/server/index.d.ts +2 -2
  70. package/dist/server/index.d.ts.map +1 -1
  71. package/dist/server/index.js +1 -1
  72. package/dist/server/index.js.map +1 -1
  73. package/dist/server/request-executor.d.ts +72 -0
  74. package/dist/server/request-executor.d.ts.map +1 -0
  75. package/dist/server/request-executor.js +781 -0
  76. package/dist/server/request-executor.js.map +1 -0
  77. package/dist/server/request-preparation.d.ts +39 -0
  78. package/dist/server/request-preparation.d.ts.map +1 -0
  79. package/dist/server/request-preparation.js +248 -0
  80. package/dist/server/request-preparation.js.map +1 -0
  81. package/dist/server/response-finalization.d.ts +34 -0
  82. package/dist/server/response-finalization.d.ts.map +1 -0
  83. package/dist/server/response-finalization.js +279 -0
  84. package/dist/server/response-finalization.js.map +1 -0
  85. package/dist/server/route-definitions.d.ts +139 -0
  86. package/dist/server/route-definitions.d.ts.map +1 -0
  87. package/dist/server/route-definitions.js +94 -0
  88. package/dist/server/route-definitions.js.map +1 -0
  89. package/dist/server/route-matching.d.ts +15 -0
  90. package/dist/server/route-matching.d.ts.map +1 -0
  91. package/dist/server/route-matching.js +46 -0
  92. package/dist/server/route-matching.js.map +1 -0
  93. package/dist/server/server.d.ts +6 -157
  94. package/dist/server/server.d.ts.map +1 -1
  95. package/dist/server/server.js +29 -1340
  96. package/dist/server/server.js.map +1 -1
  97. package/dist/server/use-case-route.d.ts +1 -1
  98. package/dist/testing/index.d.ts +1 -1
  99. package/dist/testing/index.d.ts.map +1 -1
  100. package/dist/testing/index.js +1 -0
  101. package/dist/testing/index.js.map +1 -1
  102. package/dist/tracing/index.d.ts +1 -1
  103. package/dist/tracing/index.d.ts.map +1 -1
  104. package/dist/tracing/index.js.map +1 -1
  105. package/dist/uploads/client.js +4 -7
  106. package/dist/uploads/client.js.map +1 -1
  107. package/dist/uploads/index.d.ts.map +1 -1
  108. package/dist/uploads/index.js +59 -12
  109. package/dist/uploads/index.js.map +1 -1
  110. package/package.json +5 -5
  111. package/skills/app-architecture/SKILL.md +31 -1
  112. package/src/agent-capabilities/index.ts +578 -0
  113. package/src/application/index.ts +19 -5
  114. package/src/client/client.ts +41 -17
  115. package/src/contracts/contract-builder.ts +8 -23
  116. package/src/contracts/contract-group.ts +4 -6
  117. package/src/contracts/metadata.ts +20 -0
  118. package/src/contracts/types.ts +39 -0
  119. package/src/events/index.ts +16 -20
  120. package/src/flags/index.ts +0 -5
  121. package/src/idempotency/index.ts +17 -9
  122. package/src/jobs/index.ts +8 -3
  123. package/src/notifications/index.ts +678 -70
  124. package/src/openapi/index.ts +24 -12
  125. package/src/ports/auth.ts +2 -0
  126. package/src/ports/events.ts +2 -2
  127. package/src/ports/index.ts +0 -1
  128. package/src/providers/index.ts +1 -0
  129. package/src/providers/provider.ts +18 -3
  130. package/src/schedules/index.ts +57 -63
  131. package/src/server/index.ts +4 -2
  132. package/src/server/request-executor.ts +1170 -0
  133. package/src/server/request-preparation.ts +409 -0
  134. package/src/server/response-finalization.ts +385 -0
  135. package/src/server/route-definitions.ts +338 -0
  136. package/src/server/route-matching.ts +64 -0
  137. package/src/server/server.ts +63 -2171
  138. package/src/server/use-case-route.ts +1 -1
  139. package/src/testing/index.ts +2 -0
  140. package/src/tracing/index.ts +1 -0
  141. package/src/uploads/client.ts +4 -7
  142. 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,
@@ -111,13 +113,6 @@ export class ContractBuilder<
111
113
  /**
112
114
  * URL path template for this contract.
113
115
  */
114
- get pathTemplate(): TPath {
115
- return this._path;
116
- }
117
-
118
- /**
119
- * URL path template alias.
120
- */
121
116
  get path(): TPath {
122
117
  return this._path;
123
118
  }
@@ -443,7 +438,7 @@ export class ContractBuilder<
443
438
  TBody,
444
439
  THeaders,
445
440
  TResponses,
446
- OmitMetaKeys<TMeta, keyof TNewMeta> & TNewMeta,
441
+ MergeContractMeta<TMeta, TNewMeta>,
447
442
  TPath
448
443
  > {
449
444
  return new ContractBuilder({
@@ -458,18 +453,15 @@ export class ContractBuilder<
458
453
  headers: this._headers,
459
454
  body: this._body,
460
455
  responses: this._responses,
461
- metadata: {
462
- ...this._meta,
463
- ...newMeta,
464
- } as unknown as OmitMetaKeys<TMeta, keyof TNewMeta> & TNewMeta,
456
+ metadata: mergeContractMeta(this._meta, newMeta),
465
457
  });
466
458
  }
467
459
 
468
460
  /**
469
461
  * Merge OpenAPI operation metadata into this contract.
470
462
  */
471
- openapi(
472
- patch: Partial<OpenAPIOperationMeta>,
463
+ openapi<TPatch extends Partial<OpenAPIOperationMeta>>(
464
+ patch: TPatch,
473
465
  ): ContractBuilder<
474
466
  TMethod,
475
467
  TPathParams,
@@ -477,10 +469,9 @@ export class ContractBuilder<
477
469
  TBody,
478
470
  THeaders,
479
471
  TResponses,
480
- TMeta,
472
+ MergeContractMeta<TMeta, { openapi: TPatch }>,
481
473
  TPath
482
474
  > {
483
- const existingOpenApi = this._meta.openapi ?? {};
484
475
  return new ContractBuilder({
485
476
  kind: this.kind,
486
477
  name: this.name,
@@ -493,13 +484,7 @@ export class ContractBuilder<
493
484
  headers: this._headers,
494
485
  body: this._body,
495
486
  responses: this._responses,
496
- metadata: {
497
- ...this._meta,
498
- openapi: {
499
- ...existingOpenApi,
500
- ...patch,
501
- },
502
- } as TMeta,
487
+ metadata: mergeContractMeta(this._meta, { openapi: patch }),
503
488
  });
504
489
  }
505
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
  }
@@ -406,11 +406,6 @@ export function defineFlags<const TRegistry extends FlagRegistry>(
406
406
  return registry;
407
407
  }
408
408
 
409
- /**
410
- * Alias for apps that prefer to name the collection explicitly.
411
- */
412
- export const defineFlagRegistry = defineFlags;
413
-
414
409
  /**
415
410
  * Evaluate a flag through any `FlagsPort`.
416
411
  */
@@ -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