@bleedingdev/modern-js-bff-core 3.5.0-ultramodern.4 → 3.5.0-ultramodern.41

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 (46) hide show
  1. package/dist/cjs/adapter-kit/index.js +6 -2
  2. package/dist/cjs/adapter-kit/parity-scenarios/cross-project-denial.js +55 -0
  3. package/dist/cjs/adapter-kit/parity-scenarios/envelope.js +128 -0
  4. package/dist/cjs/adapter-kit/parity-scenarios/operation-context.js +158 -0
  5. package/dist/cjs/adapter-kit/parity-scenarios/schema.js +109 -0
  6. package/dist/cjs/adapter-kit/parity-scenarios/shared.js +71 -0
  7. package/dist/cjs/adapter-kit/parity.js +26 -368
  8. package/dist/cjs/index.js +29 -48
  9. package/dist/cjs/operators/http.js +2 -2
  10. package/dist/cjs/security/crossProjectPolicy.js +123 -62
  11. package/dist/esm/adapter-kit/index.mjs +6 -2
  12. package/dist/esm/adapter-kit/parity-scenarios/cross-project-denial.mjs +17 -0
  13. package/dist/esm/adapter-kit/parity-scenarios/envelope.mjs +90 -0
  14. package/dist/esm/adapter-kit/parity-scenarios/operation-context.mjs +117 -0
  15. package/dist/esm/adapter-kit/parity-scenarios/schema.mjs +71 -0
  16. package/dist/esm/adapter-kit/parity-scenarios/shared.mjs +21 -0
  17. package/dist/esm/adapter-kit/parity.mjs +19 -361
  18. package/dist/esm/index.mjs +0 -1
  19. package/dist/esm/operators/http.mjs +2 -2
  20. package/dist/esm/security/crossProjectPolicy.mjs +123 -62
  21. package/dist/esm/security/operationContracts.mjs +2 -2
  22. package/dist/esm-node/adapter-kit/index.mjs +6 -2
  23. package/dist/esm-node/adapter-kit/parity-scenarios/cross-project-denial.mjs +18 -0
  24. package/dist/esm-node/adapter-kit/parity-scenarios/envelope.mjs +91 -0
  25. package/dist/esm-node/adapter-kit/parity-scenarios/operation-context.mjs +118 -0
  26. package/dist/esm-node/adapter-kit/parity-scenarios/schema.mjs +72 -0
  27. package/dist/esm-node/adapter-kit/parity-scenarios/shared.mjs +22 -0
  28. package/dist/esm-node/adapter-kit/parity.mjs +19 -361
  29. package/dist/esm-node/index.mjs +0 -1
  30. package/dist/esm-node/operators/http.mjs +2 -2
  31. package/dist/esm-node/security/crossProjectPolicy.mjs +123 -62
  32. package/dist/types/adapter-kit/index.d.ts +2 -2
  33. package/dist/types/adapter-kit/parity-scenarios/cross-project-denial.d.ts +2 -0
  34. package/dist/types/adapter-kit/parity-scenarios/envelope.d.ts +2 -0
  35. package/dist/types/adapter-kit/parity-scenarios/operation-context.d.ts +3 -0
  36. package/dist/types/adapter-kit/parity-scenarios/schema.d.ts +2 -0
  37. package/dist/types/adapter-kit/parity-scenarios/shared.d.ts +32 -0
  38. package/dist/types/adapter-kit/parity.d.ts +12 -71
  39. package/dist/types/index.d.ts +0 -1
  40. package/dist/types/operators/http.d.ts +1 -1
  41. package/dist/types/security/crossProjectPolicy.d.ts +17 -8
  42. package/package.json +9 -9
  43. package/dist/cjs/contracts/eventContracts.js +0 -72
  44. package/dist/esm/contracts/eventContracts.mjs +0 -28
  45. package/dist/esm-node/contracts/eventContracts.mjs +0 -29
  46. package/dist/types/contracts/eventContracts.d.ts +0 -22
@@ -3,7 +3,7 @@ import type { ResponseMeta } from '../operators/http';
3
3
  import type { APIHandlerInfo, ApiHandler } from '../router';
4
4
  import { type CrossProjectPolicyConfig, type CrossProjectPolicyViolation } from '../security/crossProjectPolicy';
5
5
  import { HttpMethod } from '../types';
6
- /** Lowercase route-registration method shared by express/koa/hono routers. */
6
+ /** Lowercase route-registration method shared by supported adapter routers. */
7
7
  export type ApiRouteMethod = Lowercase<`${HttpMethod}`>;
8
8
  /**
9
9
  * Maps an `APIHandlerInfo.httpMethod` onto the lowercase router method.
@@ -74,7 +74,7 @@ export type ApiHandlerInput = {
74
74
  * Positional invocation convention for plain function handlers: route params
75
75
  * in declaration order followed by the full input object.
76
76
  */
77
- export declare const buildPositionalHandlerArgs: (input: ApiHandlerInput) => unknown[];
77
+ export declare const buildPositionalHandlerArgs: (input: ApiHandlerInput, routePath?: string) => unknown[];
78
78
  export type CrossProjectPolicyDenial = {
79
79
  status: number;
80
80
  body: {
@@ -0,0 +1,2 @@
1
+ import type { AdapterParityScenario } from './shared';
2
+ export declare const createCrossProjectDenialScenarios: () => AdapterParityScenario[];
@@ -0,0 +1,2 @@
1
+ import type { AdapterParityScenario } from './shared';
2
+ export declare const createEnvelopeParityScenarios: () => AdapterParityScenario[];
@@ -0,0 +1,3 @@
1
+ import type { AdapterParityScenario, AdapterParityScenarioContext } from './shared';
2
+ export declare const createOperationContextSuccessScenarios: ({ helloContract, validEnvelope, validOperationId, }: AdapterParityScenarioContext) => AdapterParityScenario[];
3
+ export declare const createOperationContextDenialScenarios: ({ helloContract, validEnvelope, validOperationId, }: AdapterParityScenarioContext) => AdapterParityScenario[];
@@ -0,0 +1,2 @@
1
+ import type { AdapterParityScenario } from './shared';
2
+ export declare const createSchemaParityScenarios: () => AdapterParityScenario[];
@@ -0,0 +1,32 @@
1
+ import type { CrossProjectOperationContract, CrossProjectPolicyViolationReason } from '../../security/crossProjectPolicy';
2
+ export declare const PARITY_REQUEST_ID = "crm";
3
+ export declare const PARITY_PRODUCER_REQUEST_ID = "crm.producer-a";
4
+ export type ParityExpectation = {
5
+ kind: 'payload';
6
+ status: number;
7
+ payload: unknown;
8
+ } | {
9
+ kind: 'denied';
10
+ status: number;
11
+ reason: CrossProjectPolicyViolationReason;
12
+ };
13
+ export type AdapterParityScenario = {
14
+ name: string;
15
+ /** Run against policy-enabled server instead of the open one. */
16
+ policy: boolean;
17
+ request: {
18
+ method: 'get' | 'post' | 'patch';
19
+ path: string;
20
+ headers?: Record<string, string>;
21
+ body?: unknown;
22
+ };
23
+ expected: ParityExpectation;
24
+ };
25
+ export type AdapterParityScenarioContext = {
26
+ helloContract: CrossProjectOperationContract;
27
+ validEnvelope: string;
28
+ validOperationId: string;
29
+ };
30
+ export declare const envelopeHeader: (requestId: unknown) => string;
31
+ export declare const detailHeader: (details: Record<string, unknown>) => string;
32
+ export declare const deniedScenario: (name: string, reason: CrossProjectPolicyViolationReason, headers: Record<string, string>) => AdapterParityScenario;
@@ -1,84 +1,25 @@
1
1
  import type { APIHandlerInfo } from '../router';
2
- import type { CrossProjectPolicyViolationReason } from '../security/crossProjectPolicy';
3
- /**
4
- * Adapter parity (conformance) kit.
5
- *
6
- * One shared table of scenarios executed against every BFF server adapter
7
- * in its own test harness. Each scenario asserts the adapters produce
8
- * identical observable results: HTTP status, payload value and
9
- * policy-rejection reason.
10
- *
11
- * The express/koa adapters were removed from the fork, and this is internal
12
- * test support rather than a package subpath. Their expectations are retained
13
- * in the per-adapter drift pins as documentation of the historical behavior.
14
- * The live executable consumer of this table is the hono lane test
15
- * (`@modern-js/plugin-bff` runs it against `createHonoRoutes` plus the
16
- * cross-project policy middleware), while bff-core tests validate the table
17
- * shape and assertion helpers.
18
- *
19
- * Transport details intentionally NOT asserted: express serialized scalar
20
- * bodies as JSON while koa sent `text/plain`; {@link toParityResult}
21
- * normalizes both to the decoded payload value before comparison.
22
- *
23
- * Intentionally OUT OF SCOPE (known, accepted adapter drift — do not add
24
- * scenarios without deciding the drift first):
25
- * - operator route-middlewares: express applied them, koa ignored them;
26
- * - multipart/form-data: payload shapes differ per body parser;
27
- * - undefined-returning plain handlers are pinned via a per-adapter
28
- * scenario below: express ended the response 200/empty, koa served its
29
- * stock 404 ("Not Found"), hono serves its stock "404 Not Found";
30
- * - farrow schema-mode handlers are pinned per-adapter below: express/koa
31
- * unwrapped the result envelope (200/400/500), the hono lane has no
32
- * schema-mode unwrapping and passes the raw envelope through.
33
- */
34
- export declare const PARITY_REQUEST_ID = "crm";
35
- export declare const PARITY_PRODUCER_REQUEST_ID = "crm.producer-a";
2
+ import type { AdapterParityScenario } from './parity-scenarios/shared';
3
+ export type { AdapterParityScenario, ParityExpectation, } from './parity-scenarios/shared';
4
+ export { PARITY_PRODUCER_REQUEST_ID, PARITY_REQUEST_ID, } from './parity-scenarios/shared';
36
5
  /**
37
6
  * Handler fixtures registered in both adapters before running the table.
38
7
  */
39
8
  export declare const createParityApiHandlerInfos: () => APIHandlerInfo[];
40
9
  /**
41
- * `bff` config slice for the policy-enabled parity server. All `require*`
42
- * switches stay at their strict defaults.
10
+ * `bff` config slice for policy-enabled parity server. All `require*`
11
+ * switches stay at strict defaults.
43
12
  */
44
13
  export declare const createParityBffConfig: () => {
45
14
  requestId: string;
46
15
  crossProjectPolicy: {
47
16
  enabled: boolean;
48
17
  allowedNamespaces: string[];
18
+ allowClientAssertedNamespace: boolean;
49
19
  };
50
20
  };
51
- export type ParityAdapterId = 'express' | 'koa' | 'hono';
52
- export type ParityExpectation = {
53
- kind: 'payload';
54
- status: number;
55
- payload: unknown;
56
- } | {
57
- kind: 'denied';
58
- status: number;
59
- reason: CrossProjectPolicyViolationReason;
60
- } | {
61
- /** Pinned, intentional adapter drift: each adapter has its own expectation. */
62
- kind: 'perAdapter';
63
- expectations: Record<ParityAdapterId, {
64
- status: number;
65
- payload: unknown;
66
- }>;
67
- };
68
- export type AdapterParityScenario = {
69
- name: string;
70
- /** Run against the policy-enabled server instead of the open one. */
71
- policy: boolean;
72
- request: {
73
- method: 'get' | 'post' | 'patch';
74
- path: string;
75
- headers?: Record<string, string>;
76
- body?: unknown;
77
- };
78
- expected: ParityExpectation;
79
- };
80
21
  export declare const createAdapterParityScenarios: () => AdapterParityScenario[];
81
- /** Structural slice of a supertest response used for normalization. */
22
+ /** Structural slice of supertest response used for normalization. */
82
23
  export type ParityHttpResponse = {
83
24
  status: number;
84
25
  /** Content-type mime, e.g. `application/json`. */
@@ -86,17 +27,17 @@ export type ParityHttpResponse = {
86
27
  body: unknown;
87
28
  text: string;
88
29
  };
89
- export type AdapterParityResult = {
30
+ type AdapterParityResult = {
90
31
  status: number;
91
32
  payload: unknown;
92
33
  };
93
34
  /**
94
- * Normalizes a raw HTTP response to the observable payload value so JSON
95
- * (express) and text (koa) encodings of the same scalar compare equal.
35
+ * Normalizes raw HTTP response into an observable payload value so JSON and
36
+ * text encodings of the same scalar compare equal.
96
37
  */
97
38
  export declare const toParityResult: (res: ParityHttpResponse) => AdapterParityResult;
98
39
  /**
99
- * Framework-agnostic assertion: throws a descriptive error when the adapter
40
+ * Framework-agnostic assertion: throws a descriptive error when adapter
100
41
  * response deviates from the scenario expectation.
101
42
  */
102
- export declare const assertParityResult: (scenario: AdapterParityScenario, res: ParityHttpResponse, adapter?: ParityAdapterId) => void;
43
+ export declare const assertParityResult: (scenario: AdapterParityScenario, res: ParityHttpResponse) => void;
@@ -2,7 +2,6 @@ export * from './adapter-kit';
2
2
  export { Api } from './api';
3
3
  export * from './client';
4
4
  export type * from './compatible';
5
- export * from './contracts/eventContracts';
6
5
  export { HttpError, ValidationError } from './errors/http';
7
6
  export * from './operators/http';
8
7
  export * from './router';
@@ -38,7 +38,7 @@ export declare const HttpCode: (statusCode: number) => Operator<void>;
38
38
  export declare const SetHeaders: (headers: Record<string, string>) => Operator<void>;
39
39
  export declare const Redirect: (url: string) => Operator<void>;
40
40
  export declare const Upload: <Schema extends z.ZodType>(urlPath: string, schema?: Schema) => Operator<{
41
- files: z.input<Schema>;
41
+ formData: z.input<Schema>;
42
42
  }, {
43
43
  formData: z.output<Schema>;
44
44
  }>;
@@ -10,14 +10,16 @@
10
10
  * authorization boundary: any caller can echo an allowed `requestId` and a
11
11
  * matching operation context.
12
12
  *
13
- * To turn `allowedNamespaces` into a real control, supply
13
+ * To use `allowedNamespaces`, supply
14
14
  * {@link CrossProjectPolicyConfig.verifyProducerIdentity}: a server-side
15
15
  * hook that derives the producer namespace from a VERIFIED channel (mTLS
16
16
  * peer identity, gateway-authenticated JWT claims, service-mesh headers
17
- * stripped at the edge, ...). When the hook is present, the client-asserted
18
- * namespace must match the verified namespace and the allowlist is checked
19
- * against the verified value the envelope is no longer trusted for the
20
- * authorization decision.
17
+ * stripped at the edge, ...). Requests fail closed when a namespace allowlist
18
+ * is configured without this hook unless `allowClientAssertedNamespace` is
19
+ * explicitly enabled. That escape hatch keeps legacy local/demo ergonomics:
20
+ * the client-asserted namespace must match the allowlist. When the hook is
21
+ * present, the client-asserted namespace must match the verified namespace
22
+ * and the allowlist is checked against the verified value.
21
23
  *
22
24
  * Client-side counterparts in `@modern-js/create-request` (identity binding,
23
25
  * operation contract validation) are developer-experience aids that fail
@@ -46,6 +48,12 @@ export interface CrossProjectPolicyConfig {
46
48
  requireOperationSchemaHash?: boolean;
47
49
  requireOperationVersion?: boolean;
48
50
  allowedNamespaces?: string[];
51
+ /**
52
+ * Explicit local/demo escape hatch for legacy clients that only provide
53
+ * client-built requestId namespaces. Defaults to false. Enabling this keeps
54
+ * `allowedNamespaces` advisory and must not be used as authorization.
55
+ */
56
+ allowClientAssertedNamespace?: boolean;
49
57
  envelopeHeader?: string;
50
58
  operationContextHeader?: string;
51
59
  operationContextDetailHeader?: string;
@@ -53,7 +61,7 @@ export interface CrossProjectPolicyConfig {
53
61
  allowUnknownOperations?: boolean;
54
62
  denyStatus?: number;
55
63
  /**
56
- * Server-side hook binding the producer namespace to a VERIFIED identity
64
+ * Server-side hook binding producer namespace to VERIFIED identity
57
65
  * channel (mTLS peer, gateway-authenticated JWT, mesh identity headers).
58
66
  *
59
67
  * When provided, the namespace asserted by the client envelope must match
@@ -61,8 +69,9 @@ export interface CrossProjectPolicyConfig {
61
69
  * against the verified value instead of the client-asserted one. Returning
62
70
  * `undefined` (identity could not be verified) denies the request.
63
71
  *
64
- * Without this hook the namespace checks are advisory only — see the
65
- * module-level threat model.
72
+ * By default, `allowedNamespaces` requires this hook and fails closed
73
+ * without it. Set `allowClientAssertedNamespace` only for local/demo
74
+ * ergonomics where advisory client-asserted namespace checks are acceptable.
66
75
  */
67
76
  verifyProducerIdentity?: (headers: Record<string, unknown>) => string | undefined;
68
77
  }
package/package.json CHANGED
@@ -17,7 +17,7 @@
17
17
  "modern",
18
18
  "modern.js"
19
19
  ],
20
- "version": "3.5.0-ultramodern.4",
20
+ "version": "3.5.0-ultramodern.41",
21
21
  "types": "./dist/types/index.d.ts",
22
22
  "main": "./dist/cjs/index.js",
23
23
  "exports": {
@@ -35,19 +35,19 @@
35
35
  "@swc/helpers": "^0.5.23",
36
36
  "koa-compose": "^4.1.0",
37
37
  "reflect-metadata": "^0.2.2",
38
- "type-fest": "5.7.0",
39
- "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.5.0-ultramodern.4"
38
+ "type-fest": "5.8.0",
39
+ "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.5.0-ultramodern.41"
40
40
  },
41
41
  "devDependencies": {
42
- "@rslib/core": "0.23.1",
42
+ "@rslib/core": "0.23.2",
43
43
  "@types/koa-compose": "^3.2.9",
44
- "@types/node": "^26.0.1",
45
- "@typescript/native-preview": "7.0.0-dev.20260628.1",
44
+ "@types/node": "^26.1.1",
45
+ "@typescript/native-preview": "7.0.0-dev.20260707.2",
46
46
  "tsconfig-paths": "^4.2.0",
47
47
  "zod": "^4.4.3",
48
- "@modern-js/bff-runtime": "npm:@bleedingdev/modern-js-bff-runtime@3.5.0-ultramodern.4",
49
- "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.5.0-ultramodern.4",
50
- "@scripts/rstest-config": "2.66.0"
48
+ "@modern-js/bff-runtime": "npm:@bleedingdev/modern-js-bff-runtime@3.5.0-ultramodern.41",
49
+ "@scripts/rstest-config": "2.66.0",
50
+ "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.5.0-ultramodern.41"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "tsconfig-paths": "^4.2.0",
@@ -1,72 +0,0 @@
1
- "use strict";
2
- var __webpack_require__ = {};
3
- (()=>{
4
- __webpack_require__.d = (exports1, getters, values)=>{
5
- var define = (defs, kind)=>{
6
- for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
7
- enumerable: true,
8
- [kind]: defs[key]
9
- });
10
- };
11
- define(getters, "get");
12
- define(values, "value");
13
- };
14
- })();
15
- (()=>{
16
- __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
17
- })();
18
- (()=>{
19
- __webpack_require__.r = (exports1)=>{
20
- if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
21
- value: 'Module'
22
- });
23
- Object.defineProperty(exports1, '__esModule', {
24
- value: true
25
- });
26
- };
27
- })();
28
- var __webpack_exports__ = {};
29
- __webpack_require__.r(__webpack_exports__);
30
- const defineEventContract = (input)=>{
31
- const normalizedName = input.name.trim();
32
- if (!normalizedName) throw new Error('Event contract name must be non-empty');
33
- if (!Number.isFinite(input.version) || input.version <= 0) throw new Error('Event contract version must be a positive number');
34
- if (!input.schemaHash || !input.schemaHash.trim()) throw new Error('Event contract schemaHash must be non-empty');
35
- return {
36
- name: normalizedName,
37
- version: Math.floor(input.version),
38
- schemaHash: input.schemaHash.trim(),
39
- description: input.description
40
- };
41
- };
42
- const createEventEnvelope = (contract, payload, meta)=>({
43
- name: contract.name,
44
- version: contract.version,
45
- schemaHash: contract.schemaHash,
46
- timestamp: Date.now(),
47
- payload,
48
- ...meta ? {
49
- meta
50
- } : {}
51
- });
52
- const isEventEnvelope = (value)=>{
53
- if (!value || 'object' != typeof value || Array.isArray(value)) return false;
54
- const candidate = value;
55
- return 'string' == typeof candidate.name && candidate.name.length > 0 && 'number' == typeof candidate.version && candidate.version > 0 && 'string' == typeof candidate.schemaHash && candidate.schemaHash.length > 0 && 'number' == typeof candidate.timestamp && Number.isFinite(candidate.timestamp) && 'payload' in candidate;
56
- };
57
- __webpack_require__.d(__webpack_exports__, {}, {
58
- createEventEnvelope: createEventEnvelope,
59
- defineEventContract: defineEventContract,
60
- isEventEnvelope: isEventEnvelope
61
- });
62
- exports.createEventEnvelope = __webpack_exports__.createEventEnvelope;
63
- exports.defineEventContract = __webpack_exports__.defineEventContract;
64
- exports.isEventEnvelope = __webpack_exports__.isEventEnvelope;
65
- for(var __rspack_i in __webpack_exports__)if (-1 === [
66
- "createEventEnvelope",
67
- "defineEventContract",
68
- "isEventEnvelope"
69
- ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
70
- Object.defineProperty(exports, '__esModule', {
71
- value: true
72
- });
@@ -1,28 +0,0 @@
1
- const defineEventContract = (input)=>{
2
- const normalizedName = input.name.trim();
3
- if (!normalizedName) throw new Error('Event contract name must be non-empty');
4
- if (!Number.isFinite(input.version) || input.version <= 0) throw new Error('Event contract version must be a positive number');
5
- if (!input.schemaHash || !input.schemaHash.trim()) throw new Error('Event contract schemaHash must be non-empty');
6
- return {
7
- name: normalizedName,
8
- version: Math.floor(input.version),
9
- schemaHash: input.schemaHash.trim(),
10
- description: input.description
11
- };
12
- };
13
- const createEventEnvelope = (contract, payload, meta)=>({
14
- name: contract.name,
15
- version: contract.version,
16
- schemaHash: contract.schemaHash,
17
- timestamp: Date.now(),
18
- payload,
19
- ...meta ? {
20
- meta
21
- } : {}
22
- });
23
- const isEventEnvelope = (value)=>{
24
- if (!value || 'object' != typeof value || Array.isArray(value)) return false;
25
- const candidate = value;
26
- return 'string' == typeof candidate.name && candidate.name.length > 0 && 'number' == typeof candidate.version && candidate.version > 0 && 'string' == typeof candidate.schemaHash && candidate.schemaHash.length > 0 && 'number' == typeof candidate.timestamp && Number.isFinite(candidate.timestamp) && 'payload' in candidate;
27
- };
28
- export { createEventEnvelope, defineEventContract, isEventEnvelope };
@@ -1,29 +0,0 @@
1
- import "node:module";
2
- const defineEventContract = (input)=>{
3
- const normalizedName = input.name.trim();
4
- if (!normalizedName) throw new Error('Event contract name must be non-empty');
5
- if (!Number.isFinite(input.version) || input.version <= 0) throw new Error('Event contract version must be a positive number');
6
- if (!input.schemaHash || !input.schemaHash.trim()) throw new Error('Event contract schemaHash must be non-empty');
7
- return {
8
- name: normalizedName,
9
- version: Math.floor(input.version),
10
- schemaHash: input.schemaHash.trim(),
11
- description: input.description
12
- };
13
- };
14
- const createEventEnvelope = (contract, payload, meta)=>({
15
- name: contract.name,
16
- version: contract.version,
17
- schemaHash: contract.schemaHash,
18
- timestamp: Date.now(),
19
- payload,
20
- ...meta ? {
21
- meta
22
- } : {}
23
- });
24
- const isEventEnvelope = (value)=>{
25
- if (!value || 'object' != typeof value || Array.isArray(value)) return false;
26
- const candidate = value;
27
- return 'string' == typeof candidate.name && candidate.name.length > 0 && 'number' == typeof candidate.version && candidate.version > 0 && 'string' == typeof candidate.schemaHash && candidate.schemaHash.length > 0 && 'number' == typeof candidate.timestamp && Number.isFinite(candidate.timestamp) && 'payload' in candidate;
28
- };
29
- export { createEventEnvelope, defineEventContract, isEventEnvelope };
@@ -1,22 +0,0 @@
1
- export type EventContract<Name extends string = string, Payload = unknown> = {
2
- name: Name;
3
- version: number;
4
- schemaHash: string;
5
- description?: string;
6
- };
7
- export type EventEnvelope<TContract extends EventContract = EventContract, TMeta extends Record<string, unknown> = Record<string, unknown>> = {
8
- name: TContract['name'];
9
- version: TContract['version'];
10
- schemaHash: TContract['schemaHash'];
11
- timestamp: number;
12
- payload: TContract extends EventContract<string, infer TPayload> ? TPayload : unknown;
13
- meta?: TMeta;
14
- };
15
- export declare const defineEventContract: <Name extends string, Payload = unknown>(input: {
16
- name: Name;
17
- version: number;
18
- schemaHash: string;
19
- description?: string;
20
- }) => EventContract<Name, Payload>;
21
- export declare const createEventEnvelope: <TContract extends EventContract, TMeta extends Record<string, unknown> = Record<string, unknown>>(contract: TContract, payload: TContract extends EventContract<string, infer TPayload> ? TPayload : unknown, meta?: TMeta) => EventEnvelope<TContract, TMeta>;
22
- export declare const isEventEnvelope: (value: unknown) => value is EventEnvelope<EventContract, Record<string, unknown>>;