@eventsdk/sdk 0.0.0-stage → 0.3.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/LICENSE +201 -0
- package/NOTICE +9 -0
- package/dist/compatibility-http/bridge/receive.d.ts +3 -0
- package/dist/compatibility-http/bridge/types.d.ts +44 -0
- package/dist/compatibility-http/index.d.ts +4 -0
- package/dist/compatibility-http/index.js +1 -0
- package/dist/compatibility-http/policy/validate-policy.d.ts +3 -0
- package/dist/conformance/admission/harness.d.ts +20 -0
- package/dist/conformance/admission/run-suite.d.ts +2 -0
- package/dist/conformance/consumer/harness.d.ts +24 -0
- package/dist/conformance/consumer/run-apply-suite.d.ts +2 -0
- package/dist/conformance/consumer/run-worker-suite.d.ts +2 -0
- package/dist/conformance/fanout/harness.d.ts +39 -0
- package/dist/conformance/fanout/run-suite.d.ts +2 -0
- package/dist/conformance/gap/harness.d.ts +19 -0
- package/dist/conformance/gap/run-suite.d.ts +2 -0
- package/dist/conformance/index.d.ts +20 -0
- package/dist/conformance/index.js +1 -0
- package/dist/conformance/producer/harness.d.ts +21 -0
- package/dist/conformance/producer/run-suite.d.ts +2 -0
- package/dist/conformance/receipt/harness.d.ts +12 -0
- package/dist/conformance/receipt/projection-harness.d.ts +9 -0
- package/dist/conformance/receipt/run-projection-suite.d.ts +2 -0
- package/dist/conformance/receipt/run-suite.d.ts +2 -0
- package/dist/conformance/reliability/checks.d.ts +4 -0
- package/dist/conformance/result/harness.d.ts +15 -0
- package/dist/conformance/result/run-suite.d.ts +2 -0
- package/dist/conformance/version/compatibility.d.ts +13 -0
- package/dist/contracts/canonical/canonical-json.d.ts +1 -0
- package/dist/contracts/contract-types.d.ts +34 -0
- package/dist/contracts/generated/business-receipt.d.ts +21 -0
- package/dist/contracts/generated/cloud-event-context.d.ts +32 -0
- package/dist/contracts/generated/cloud-event-fanout.d.ts +33 -0
- package/dist/contracts/generated/cloud-event.d.ts +24 -0
- package/dist/contracts/generated/event-origin-claims.d.ts +13 -0
- package/dist/contracts/generated/exchange-grant.d.ts +11 -0
- package/dist/contracts/generated/exchange-registration.d.ts +24 -0
- package/dist/contracts/generated/fanout-event-draft.d.ts +28 -0
- package/dist/contracts/generated/gap-baseline.d.ts +18 -0
- package/dist/contracts/generated/gap-recovery-command.d.ts +7 -0
- package/dist/contracts/generated/identity-context.d.ts +15 -0
- package/dist/contracts/generated/namespaced-reference.d.ts +4 -0
- package/dist/contracts/generated/native-event-catalog.d.ts +19 -0
- package/dist/contracts/generated/node-connection-profile.d.ts +217 -0
- package/dist/contracts/generated/node-worker-observation.d.ts +7 -0
- package/dist/contracts/generated/outbox-recovery-command.d.ts +8 -0
- package/dist/contracts/generated/portable-exchange-registration.d.ts +109 -0
- package/dist/contracts/generated/portable-gap-baseline.d.ts +93 -0
- package/dist/contracts/generated/portable-gap-recovery-command.d.ts +35 -0
- package/dist/contracts/generated/portable-outbox-recovery-command.d.ts +37 -0
- package/dist/contracts/generated/public-jwks-snapshot.d.ts +141 -0
- package/dist/contracts/generated/result-ref.d.ts +8 -0
- package/dist/contracts/generated/supported-profiles.d.ts +3 -0
- package/dist/contracts/generated/tenant-connection.d.ts +13 -0
- package/dist/contracts/generated/tenant-context.d.ts +4 -0
- package/dist/contracts/index.d.ts +26 -0
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/validation/contract-error.d.ts +16 -0
- package/dist/contracts/validation/contracts.d.ts +316 -0
- package/dist/contracts/validation/decode-cloud-event.d.ts +2 -0
- package/dist/contracts/validation/identity-context.d.ts +3 -0
- package/dist/contracts/validation/origin-proof-wire.d.ts +3 -0
- package/dist/contracts/validation/strict-json.d.ts +1 -0
- package/dist/core/inbox/accept-delivery.d.ts +26 -0
- package/dist/core/inbox/apply-delivery.d.ts +29 -0
- package/dist/core/inbox/domain-apply-result.d.ts +13 -0
- package/dist/core/inbox/inbox-worker.d.ts +2 -0
- package/dist/core/inbox/read-result.d.ts +12 -0
- package/dist/core/inbox/worker-types.d.ts +21 -0
- package/dist/core/index.d.ts +50 -0
- package/dist/core/index.js +1 -0
- package/dist/core/outbox/authorize-recovery.d.ts +75 -0
- package/dist/core/outbox/outbox-relay.d.ts +26 -0
- package/dist/core/outbox/record-event.d.ts +10 -0
- package/dist/core/outbox/record-fanout.d.ts +16 -0
- package/dist/core/ports/dead-letter.d.ts +29 -0
- package/dist/core/ports/inbox.d.ts +43 -0
- package/dist/core/ports/observability.d.ts +21 -0
- package/dist/core/ports/origin-refresh.d.ts +5 -0
- package/dist/core/ports/outbox.d.ts +46 -0
- package/dist/core/ports/result-reader.d.ts +14 -0
- package/dist/core/ports/transport.d.ts +13 -0
- package/dist/core/receipt/receipt-state.d.ts +3 -0
- package/dist/core/recovery/event-log.d.ts +28 -0
- package/dist/core/recovery/gap-baseline-binding.d.ts +2 -0
- package/dist/core/recovery/gap-recovery.d.ts +15 -0
- package/dist/core/recovery/receipt-identity-recovery.d.ts +8 -0
- package/dist/core/recovery/receipt-recovery.d.ts +14 -0
- package/dist/core/recovery/replay-dead-letter.d.ts +4 -0
- package/dist/core/runtime/dependencies.d.ts +19 -0
- package/dist/core/runtime/errors.d.ts +6 -0
- package/dist/core/runtime/event-identity.d.ts +14 -0
- package/dist/core/runtime/exchange-connection.d.ts +7 -0
- package/dist/core/runtime/fanout-identity.d.ts +6 -0
- package/dist/core/runtime/lease.d.ts +6 -0
- package/dist/core/runtime/observe-health.d.ts +3 -0
- package/dist/core/runtime/operation.d.ts +2 -0
- package/dist/core/runtime/retry.d.ts +14 -0
- package/dist/identity-contracts/authorization/authorization-port.d.ts +35 -0
- package/dist/identity-contracts/index.d.ts +2 -0
- package/dist/identity-contracts/index.js +0 -0
- package/dist/identity-contracts/origin/verified-origin.d.ts +28 -0
- package/dist/identity-verifier/index.d.ts +6 -0
- package/dist/identity-verifier/index.js +1 -0
- package/dist/identity-verifier/jwt/service-token-verifier.d.ts +15 -0
- package/dist/identity-verifier/origin/event-origin-policy.d.ts +36 -0
- package/dist/identity-verifier/origin/event-origin-signer.d.ts +6 -0
- package/dist/identity-verifier/origin/event-origin-verifier.d.ts +6 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +20 -0
- package/dist/node-config/configuration-error.d.ts +4 -0
- package/dist/node-config/diagnostics/check-configuration.d.ts +40 -0
- package/dist/node-config/identity/load-identity.d.ts +14 -0
- package/dist/node-config/identity/local-recovery-origin.d.ts +3 -0
- package/dist/node-config/identity/public-identity.d.ts +87 -0
- package/dist/node-config/identity/public-snapshot.d.ts +2 -0
- package/dist/node-config/identity/signing-identity.d.ts +5 -0
- package/dist/node-config/index.d.ts +12 -0
- package/dist/node-config/index.js +1 -0
- package/dist/node-config/main.js +3 -0
- package/dist/node-config/operations/export-own-public.d.ts +8 -0
- package/dist/node-config/operations/refresh-trust.d.ts +10 -0
- package/dist/node-config/profile/load-profile.d.ts +6 -0
- package/dist/node-config/profile/validate-profile.d.ts +2 -0
- package/dist/node-worker/index.d.ts +3 -0
- package/dist/node-worker/index.js +1 -0
- package/dist/node-worker/lifecycle.d.ts +19 -0
- package/dist/node-worker/route-scheduler.d.ts +17 -0
- package/dist/node-worker/session.d.ts +15 -0
- package/dist/provisioner/application/apply.d.ts +11 -0
- package/dist/provisioner/application/ports.d.ts +44 -0
- package/dist/provisioner/application/verify.d.ts +7 -0
- package/dist/provisioner/cli/arguments.d.ts +9 -0
- package/dist/provisioner/cli/live-command.d.ts +7 -0
- package/dist/provisioner/cli/main.js +2 -0
- package/dist/provisioner/cli/run.d.ts +2 -0
- package/dist/provisioner/cli/session-types.d.ts +26 -0
- package/dist/provisioner/conformance/policy.d.ts +9 -0
- package/dist/provisioner/index.d.ts +17 -0
- package/dist/provisioner/index.js +1 -0
- package/dist/provisioner/manifest/types.d.ts +55 -0
- package/dist/provisioner/manifest/validate.d.ts +3 -0
- package/dist/provisioner/nats/inspector.d.ts +13 -0
- package/dist/provisioner/nats/types.d.ts +38 -0
- package/dist/provisioner/nats/writer-types.d.ts +11 -0
- package/dist/provisioner/nats/writer.d.ts +18 -0
- package/dist/provisioner/planning/digest.d.ts +1 -0
- package/dist/provisioner/planning/plan.d.ts +18 -0
- package/dist/transport-nats/connection/delivery-session.d.ts +10 -0
- package/dist/transport-nats/connection/verified-connection.d.ts +12 -0
- package/dist/transport-nats/consume/delivery-handler.d.ts +34 -0
- package/dist/transport-nats/consume/nats-consumer.d.ts +18 -0
- package/dist/transport-nats/index.d.ts +12 -0
- package/dist/transport-nats/index.js +3 -0
- package/dist/transport-nats/publish/nats-publisher.d.ts +24 -0
- package/dist/transport-nats/transport-error.d.ts +5 -0
- package/licenses/NATS-LICENSE +201 -0
- package/package.json +75 -3
- package/README.md +0 -3
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { JSONWebKeySet } from 'jose';
|
|
2
|
+
import { type CloudEvent } from '@eventsdk/sdk/contracts';
|
|
3
|
+
import type { VerifiedOrigin } from '@eventsdk/sdk/identity-contracts';
|
|
4
|
+
export type EventOriginAlgorithm = 'RS256' | 'PS256' | 'ES256' | 'EdDSA';
|
|
5
|
+
export interface EventOriginBinding {
|
|
6
|
+
readonly issuer: string;
|
|
7
|
+
readonly subject: string;
|
|
8
|
+
readonly serviceRef: string;
|
|
9
|
+
readonly source: string;
|
|
10
|
+
readonly keysValidUntil: string;
|
|
11
|
+
readonly verificationTtlMs?: number;
|
|
12
|
+
readonly clock: () => Date;
|
|
13
|
+
}
|
|
14
|
+
export interface EventOriginSignerPolicy extends EventOriginBinding {
|
|
15
|
+
readonly keyId: string;
|
|
16
|
+
readonly algorithm: EventOriginAlgorithm;
|
|
17
|
+
readonly privateKeyPkcs8: string;
|
|
18
|
+
readonly audiences: readonly string[];
|
|
19
|
+
}
|
|
20
|
+
export interface EventOriginVerifierPolicy extends EventOriginBinding {
|
|
21
|
+
readonly audience: string;
|
|
22
|
+
readonly algorithms: readonly EventOriginAlgorithm[];
|
|
23
|
+
readonly jwks: JSONWebKeySet;
|
|
24
|
+
}
|
|
25
|
+
export declare class EventOriginError extends Error {
|
|
26
|
+
readonly code: 'INVALID_ORIGIN_POLICY' | 'ORIGIN_PROOF_REJECTED' | 'TRUST_SNAPSHOT_EXPIRED' | 'ORIGIN_PROOF_NOT_YET_VALID';
|
|
27
|
+
constructor(code: 'INVALID_ORIGIN_POLICY' | 'ORIGIN_PROOF_REJECTED' | 'TRUST_SNAPSHOT_EXPIRED' | 'ORIGIN_PROOF_NOT_YET_VALID');
|
|
28
|
+
}
|
|
29
|
+
export declare const eventOriginTyp = "eventsdk-origin+jws";
|
|
30
|
+
export declare const originAlgorithms: readonly string[];
|
|
31
|
+
export declare function validateOriginBinding(policy: EventOriginBinding): void;
|
|
32
|
+
export declare function validatePublicOriginKeys(jwks: JSONWebKeySet): void;
|
|
33
|
+
export declare function ref(value: unknown): value is string;
|
|
34
|
+
export declare function assertCurrentOriginTrust(policy: EventOriginBinding, now: Date): void;
|
|
35
|
+
export declare function assertOriginEvent(policy: EventOriginBinding, event: CloudEvent): void;
|
|
36
|
+
export declare function freshEventOrigin(policy: EventOriginBinding, audience: string): VerifiedOrigin;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type CloudEvent } from '@eventsdk/sdk/contracts';
|
|
2
|
+
import { type EventOriginSignerPolicy } from './event-origin-policy.js';
|
|
3
|
+
export declare function createEventOriginSigner(input: EventOriginSignerPolicy): Promise<{
|
|
4
|
+
currentOrigin: (audience: string) => import("@eventsdk/sdk/identity-contracts").VerifiedOrigin;
|
|
5
|
+
sign(raw: CloudEvent, signal: AbortSignal): Promise<string>;
|
|
6
|
+
}>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type CloudEvent } from '@eventsdk/sdk/contracts';
|
|
2
|
+
import type { OriginVerification } from '@eventsdk/sdk/identity-contracts';
|
|
3
|
+
import { type EventOriginVerifierPolicy } from './event-origin-policy.js';
|
|
4
|
+
export declare function createEventOriginVerifier(input: EventOriginVerifierPolicy): {
|
|
5
|
+
verify(eventInput: CloudEvent, proof: string, signal: AbortSignal): Promise<OriginVerification>;
|
|
6
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export * from './contracts/index.js';
|
|
2
|
+
export * as contracts from './contracts/index.js';
|
|
3
|
+
export * from './identity-contracts/index.js';
|
|
4
|
+
export * as identityContracts from './identity-contracts/index.js';
|
|
5
|
+
export * from './core/index.js';
|
|
6
|
+
export * as core from './core/index.js';
|
|
7
|
+
export * from './transport-nats/index.js';
|
|
8
|
+
export * as transportNats from './transport-nats/index.js';
|
|
9
|
+
export * from './identity-verifier/index.js';
|
|
10
|
+
export * as identityVerifier from './identity-verifier/index.js';
|
|
11
|
+
export * from './compatibility-http/index.js';
|
|
12
|
+
export * as compatibilityHttp from './compatibility-http/index.js';
|
|
13
|
+
export * from './provisioner/index.js';
|
|
14
|
+
export * as provisioner from './provisioner/index.js';
|
|
15
|
+
export * from './conformance/index.js';
|
|
16
|
+
export * as conformance from './conformance/index.js';
|
|
17
|
+
export * from './node-config/index.js';
|
|
18
|
+
export * as nodeConfig from './node-config/index.js';
|
|
19
|
+
export * from './node-worker/index.js';
|
|
20
|
+
export * as nodeWorker from './node-worker/index.js';
|
|
21
|
+
export type { ResultReadRequest } from './core/index.js';
|
|
22
|
+
export type { ResultReadRequest as IdentityResultReadRequest } from './identity-contracts/index.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export * from './contracts/index.js';
|
|
2
|
+
export * as contracts from './contracts/index.js';
|
|
3
|
+
export * from './identity-contracts/index.js';
|
|
4
|
+
export * as identityContracts from './identity-contracts/index.js';
|
|
5
|
+
export * from './core/index.js';
|
|
6
|
+
export * as core from './core/index.js';
|
|
7
|
+
export * from './transport-nats/index.js';
|
|
8
|
+
export * as transportNats from './transport-nats/index.js';
|
|
9
|
+
export * from './identity-verifier/index.js';
|
|
10
|
+
export * as identityVerifier from './identity-verifier/index.js';
|
|
11
|
+
export * from './compatibility-http/index.js';
|
|
12
|
+
export * as compatibilityHttp from './compatibility-http/index.js';
|
|
13
|
+
export * from './provisioner/index.js';
|
|
14
|
+
export * as provisioner from './provisioner/index.js';
|
|
15
|
+
export * from './conformance/index.js';
|
|
16
|
+
export * as conformance from './conformance/index.js';
|
|
17
|
+
export * from './node-config/index.js';
|
|
18
|
+
export * as nodeConfig from './node-config/index.js';
|
|
19
|
+
export * from './node-worker/index.js';
|
|
20
|
+
export * as nodeWorker from './node-worker/index.js';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export declare function checkNodeConnectionConfiguration(file: string | undefined, expectedServiceRef?: string, checkFiles?: boolean): Promise<{
|
|
2
|
+
status: "DISABLED";
|
|
3
|
+
configured: boolean;
|
|
4
|
+
connected: false;
|
|
5
|
+
serviceRef?: never;
|
|
6
|
+
mode?: never;
|
|
7
|
+
revision?: never;
|
|
8
|
+
roles?: never;
|
|
9
|
+
originProof?: never;
|
|
10
|
+
trustedSources?: never;
|
|
11
|
+
filesChecked?: never;
|
|
12
|
+
code?: never;
|
|
13
|
+
} | {
|
|
14
|
+
status: "DISABLED" | "ASSETS_VALID" | "PROFILE_VALID";
|
|
15
|
+
configured: boolean;
|
|
16
|
+
connected: false;
|
|
17
|
+
serviceRef: string;
|
|
18
|
+
mode: "DISABLED" | "SHADOW" | "ACTIVE";
|
|
19
|
+
revision: string;
|
|
20
|
+
roles: {
|
|
21
|
+
publisher: boolean;
|
|
22
|
+
consumer: boolean;
|
|
23
|
+
};
|
|
24
|
+
originProof: "eventsdk-origin/1";
|
|
25
|
+
trustedSources: number;
|
|
26
|
+
filesChecked: boolean;
|
|
27
|
+
code?: never;
|
|
28
|
+
} | {
|
|
29
|
+
status: "INVALID";
|
|
30
|
+
configured: boolean;
|
|
31
|
+
connected: false;
|
|
32
|
+
code: string;
|
|
33
|
+
serviceRef?: never;
|
|
34
|
+
mode?: never;
|
|
35
|
+
revision?: never;
|
|
36
|
+
roles?: never;
|
|
37
|
+
originProof?: never;
|
|
38
|
+
trustedSources?: never;
|
|
39
|
+
filesChecked?: never;
|
|
40
|
+
}>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { LoadedNodeProfile } from '../profile/load-profile.js';
|
|
2
|
+
export declare function loadNodeConnectionIdentity(loaded: LoadedNodeProfile, clock?: () => Date, inspectionOnly?: boolean): Promise<Readonly<{
|
|
3
|
+
profile: import("@eventsdk/sdk/contracts").NodeConnectionProfile;
|
|
4
|
+
revision: string;
|
|
5
|
+
signer: {
|
|
6
|
+
currentOrigin: (audience: string) => import("@eventsdk/sdk/identity-contracts").VerifiedOrigin;
|
|
7
|
+
sign(raw: import("@eventsdk/sdk/contracts").CloudEvent, signal: AbortSignal): Promise<string>;
|
|
8
|
+
} | null;
|
|
9
|
+
verifyOrigin: (event: import("@eventsdk/sdk/contracts").CloudEvent, proof: string, signal: AbortSignal) => Promise<import("@eventsdk/sdk/identity-contracts").OriginVerification>;
|
|
10
|
+
withBrokerCredentials<T>(run: (value: {
|
|
11
|
+
credential: Uint8Array;
|
|
12
|
+
ca?: string;
|
|
13
|
+
}) => Promise<T>): Promise<T>;
|
|
14
|
+
}>>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export declare function loadNodePublicIdentity(file: string | undefined, expectedServiceRef: string, now?: number): Promise<{
|
|
2
|
+
schemaVersion: 1;
|
|
3
|
+
profile: "eventsdk-origin/1";
|
|
4
|
+
serviceRef: string;
|
|
5
|
+
source: string;
|
|
6
|
+
issuer: string;
|
|
7
|
+
subject: string;
|
|
8
|
+
jwksUri: string;
|
|
9
|
+
validUntil: string;
|
|
10
|
+
jwks: {
|
|
11
|
+
keys: [({
|
|
12
|
+
kty: "RSA";
|
|
13
|
+
kid: string;
|
|
14
|
+
alg?: "RS256" | "PS256";
|
|
15
|
+
use?: "sig";
|
|
16
|
+
key_ops?: ["verify"];
|
|
17
|
+
ext?: boolean;
|
|
18
|
+
x5c?: [] | [string] | [string, string] | [string, string, string] | [string, string, string, string] | [string, string, string, string, string] | [string, string, string, string, string, string] | [string, string, string, string, string, string, string] | [string, string, string, string, string, string, string, string];
|
|
19
|
+
x5t?: string;
|
|
20
|
+
"x5t#S256"?: string;
|
|
21
|
+
n: string;
|
|
22
|
+
e: string;
|
|
23
|
+
} | {
|
|
24
|
+
kty: "EC";
|
|
25
|
+
kid: string;
|
|
26
|
+
alg?: "ES256";
|
|
27
|
+
use?: "sig";
|
|
28
|
+
key_ops?: ["verify"];
|
|
29
|
+
ext?: boolean;
|
|
30
|
+
x5c?: [] | [string] | [string, string] | [string, string, string] | [string, string, string, string] | [string, string, string, string, string] | [string, string, string, string, string, string] | [string, string, string, string, string, string, string] | [string, string, string, string, string, string, string, string];
|
|
31
|
+
x5t?: string;
|
|
32
|
+
"x5t#S256"?: string;
|
|
33
|
+
crv: "P-256";
|
|
34
|
+
x: string;
|
|
35
|
+
y: string;
|
|
36
|
+
} | {
|
|
37
|
+
kty: "OKP";
|
|
38
|
+
kid: string;
|
|
39
|
+
alg?: "EdDSA";
|
|
40
|
+
use?: "sig";
|
|
41
|
+
key_ops?: ["verify"];
|
|
42
|
+
ext?: boolean;
|
|
43
|
+
x5c?: [] | [string] | [string, string] | [string, string, string] | [string, string, string, string] | [string, string, string, string, string] | [string, string, string, string, string, string] | [string, string, string, string, string, string, string] | [string, string, string, string, string, string, string, string];
|
|
44
|
+
x5t?: string;
|
|
45
|
+
"x5t#S256"?: string;
|
|
46
|
+
crv: "Ed25519";
|
|
47
|
+
x: string;
|
|
48
|
+
}), ...({
|
|
49
|
+
kty: "RSA";
|
|
50
|
+
kid: string;
|
|
51
|
+
alg?: "RS256" | "PS256";
|
|
52
|
+
use?: "sig";
|
|
53
|
+
key_ops?: ["verify"];
|
|
54
|
+
ext?: boolean;
|
|
55
|
+
x5c?: [] | [string] | [string, string] | [string, string, string] | [string, string, string, string] | [string, string, string, string, string] | [string, string, string, string, string, string] | [string, string, string, string, string, string, string] | [string, string, string, string, string, string, string, string];
|
|
56
|
+
x5t?: string;
|
|
57
|
+
"x5t#S256"?: string;
|
|
58
|
+
n: string;
|
|
59
|
+
e: string;
|
|
60
|
+
} | {
|
|
61
|
+
kty: "EC";
|
|
62
|
+
kid: string;
|
|
63
|
+
alg?: "ES256";
|
|
64
|
+
use?: "sig";
|
|
65
|
+
key_ops?: ["verify"];
|
|
66
|
+
ext?: boolean;
|
|
67
|
+
x5c?: [] | [string] | [string, string] | [string, string, string] | [string, string, string, string] | [string, string, string, string, string] | [string, string, string, string, string, string] | [string, string, string, string, string, string, string] | [string, string, string, string, string, string, string, string];
|
|
68
|
+
x5t?: string;
|
|
69
|
+
"x5t#S256"?: string;
|
|
70
|
+
crv: "P-256";
|
|
71
|
+
x: string;
|
|
72
|
+
y: string;
|
|
73
|
+
} | {
|
|
74
|
+
kty: "OKP";
|
|
75
|
+
kid: string;
|
|
76
|
+
alg?: "EdDSA";
|
|
77
|
+
use?: "sig";
|
|
78
|
+
key_ops?: ["verify"];
|
|
79
|
+
ext?: boolean;
|
|
80
|
+
x5c?: [] | [string] | [string, string] | [string, string, string] | [string, string, string, string] | [string, string, string, string, string] | [string, string, string, string, string, string] | [string, string, string, string, string, string, string] | [string, string, string, string, string, string, string, string];
|
|
81
|
+
x5t?: string;
|
|
82
|
+
"x5t#S256"?: string;
|
|
83
|
+
crv: "Ed25519";
|
|
84
|
+
x: string;
|
|
85
|
+
})[]];
|
|
86
|
+
};
|
|
87
|
+
} | null>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { NodeConnectionProfile } from '@eventsdk/sdk/contracts';
|
|
2
|
+
export declare function loadSigningIdentity(profile: NodeConnectionProfile, clock: () => Date): Promise<{
|
|
3
|
+
currentOrigin: (audience: string) => import("@eventsdk/sdk/identity-contracts").VerifiedOrigin;
|
|
4
|
+
sign(raw: import("@eventsdk/sdk/contracts").CloudEvent, signal: AbortSignal): Promise<string>;
|
|
5
|
+
} | null>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { EventSdkConfigurationError } from './configuration-error.js';
|
|
2
|
+
export { validateNodeProfile } from './profile/validate-profile.js';
|
|
3
|
+
export { loadNodeConnectionProfile } from './profile/load-profile.js';
|
|
4
|
+
export type { LoadedNodeProfile } from './profile/load-profile.js';
|
|
5
|
+
export { loadPublicTrustSnapshot } from './identity/public-snapshot.js';
|
|
6
|
+
export { loadNodePublicIdentity } from './identity/public-identity.js';
|
|
7
|
+
export { loadNodeLocalRecoveryOrigin } from './identity/local-recovery-origin.js';
|
|
8
|
+
export { loadNodeConnectionIdentity } from './identity/load-identity.js';
|
|
9
|
+
export { loadSigningIdentity as loadNodeSigningIdentity } from './identity/signing-identity.js';
|
|
10
|
+
export { checkNodeConnectionConfiguration } from './diagnostics/check-configuration.js';
|
|
11
|
+
export { exportOwnPublicSnapshot } from './operations/export-own-public.js';
|
|
12
|
+
export { refreshTrustedPublicSnapshots, type TrustRefreshOptions, } from './operations/refresh-trust.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var EventSdkConfigurationError=class extends Error{constructor(code){super(code);this.code=code}code};import{isIP}from"node:net";import{isAbsolute}from"node:path";import{parseNodeConnectionProfile}from"@eventsdk/sdk/contracts";function validateNodeProfile(raw,expectedServiceRef){let profile;try{profile=parseNodeConnectionProfile(structuredClone(raw))}catch{throw new EventSdkConfigurationError("PROFILE_INVALID")}if(expectedServiceRef&&profile.serviceRef!==expectedServiceRef)fail("SERVICE_IDENTITY_MISMATCH");broker(profile);identity(profile);const source=new URL(profile.installationSource);if(source.username||source.password||source.search||source.hash)fail("INSTALLATION_SOURCE_INVALID");if(profile.publisher&&(!profile.identity.signer||!subject(profile.publisher.subject)||!entity(profile.publisher.expectedStream)))fail("PUBLISHER_IDENTITY_REQUIRED");if(profile.consumer&&(!profile.identity.trustedSources.length||!subject(profile.consumer.subject)||!entity(profile.consumer.stream)||!entity(profile.consumer.durableName)))fail("CONSUMER_IDENTITY_REQUIRED");if(!profile.publisher&&!profile.consumer)fail("CAPABILITY_ROLE_REQUIRED");routing(profile);return deepFreeze(profile)}function routing(profile){const publisher=profile.publisher;const audiences=profile.identity.signer?.audiences??[];if(publisher){const routes=publisher.audienceRoutes;if(!routes&&audiences.length!==1)fail("PUBLISHER_AUDIENCE_ROUTES_REQUIRED");if(routes&&(routes.length!==audiences.length||new Set(routes.map(r=>r.audience)).size!==routes.length||new Set(routes.map(r=>r.subject)).size!==routes.length||routes.some(r=>!audiences.includes(r.audience)||!subject(r.subject)||!entity(r.expectedStream))||routes[0]?.subject!==publisher.subject||routes[0]?.expectedStream!==publisher.expectedStream))fail("PUBLISHER_AUDIENCE_ROUTES_INVALID")}const consumer=profile.consumer;if(consumer){const bindings=[consumer,...consumer.additionalBindings??[]];if(new Set(bindings.map(b=>JSON.stringify([b.stream,b.durableName]))).size!==bindings.length||new Set(bindings.map(b=>b.subject)).size!==bindings.length||bindings.some(b=>!subject(b.subject)||!entity(b.stream)||!entity(b.durableName))||bindings.length*consumer.batchSize*profile.broker.timeoutMs>3e5)fail("CONSUMER_BINDINGS_INVALID")}}function broker(profile){const b=profile.broker,url=new URL(b.serverUrl);if(url.protocol!=="tls:"||url.username||url.password||url.search||url.hash||!["","/"].includes(url.pathname)||isIP(url.hostname.replace(/^\[|\]$/g,""))||url.hostname.length>253||url.hostname.split(".").some(label=>!/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(label))||b.expectedDnsName!==url.hostname)fail("BROKER_TLS_IDENTITY_REQUIRED");files([b.credentialsFile,...b.caFile?[b.caFile]:[]]);if(profile.consumer&&(profile.consumer.fetchExpiresMs>=b.timeoutMs||profile.consumer.batchSize*b.timeoutMs>3e5))fail("CONSUMER_BOUNDS_INVALID")}function identity(profile){const signer=profile.identity.signer;if(signer){https(signer.issuer);https(signer.jwksUri);files([signer.privateKeyFile,signer.publicSnapshotFile])}const seen=new Set;for(const trust of profile.identity.trustedSources){https(trust.issuer);https(trust.jwksUri);files([trust.publicSnapshotFile]);const key=JSON.stringify([trust.source,trust.serviceRef,trust.audience]);if(seen.has(key)||trust.audience!==profile.serviceRef)fail("TRUST_BINDING_AMBIGUOUS");seen.add(key)}}function https(value){const url=new URL(value);if(url.protocol!=="https:"||url.username||url.password||url.search||url.hash)fail("ISSUER_HTTPS_REQUIRED")}function files(values){if(values.some(path=>!isAbsolute(path)||path.includes("\0")))fail("FILE_REFERENCE_INVALID")}function subject(value){return/^[^\s.*>]+(?:\.[^\s.*>]+)*$/u.test(value)}function entity(value){return/^[^\s.*>/\\]+$/u.test(value)}function fail(code){throw new EventSdkConfigurationError(code)}function deepFreeze(value){if(value&&typeof value==="object"&&!Object.isFrozen(value)){Object.freeze(value);Object.values(value).forEach(deepFreeze)}return value}import{createHash}from"node:crypto";import{canonicalJson,parseJsonStrict}from"@eventsdk/sdk/contracts";import{constants}from"node:fs";import{open}from"node:fs/promises";import{isAbsolute as isAbsolute2}from"node:path";async function readConfigurationFile(path,privateFile=false){if(!isAbsolute2(path)||path.includes("\0"))throw new EventSdkConfigurationError("FILE_REFERENCE_INVALID");try{const file=await open(path,constants.O_RDONLY|constants.O_NOFOLLOW);try{const stat=await file.stat();if(!stat.isFile()||stat.nlink!==1||stat.size<1||stat.size>65536||(stat.mode&18)!==0||privateFile&&(stat.mode&63)!==0)throw new EventSdkConfigurationError("FILE_PROTECTION_REQUIRED");const bytes=Buffer.alloc(65537);const read=await file.read(bytes,0,bytes.length,0);if(read.bytesRead>65536||read.bytesRead!==stat.size)throw new EventSdkConfigurationError("FILE_CHANGED");const after=await file.stat();if(after.size!==stat.size||after.mtimeMs!==stat.mtimeMs||after.ctimeMs!==stat.ctimeMs||after.nlink!==1)throw new EventSdkConfigurationError("FILE_CHANGED");return new TextDecoder("utf-8",{fatal:true}).decode(bytes.subarray(0,read.bytesRead))}finally{await file.close()}}catch(error){if(error instanceof EventSdkConfigurationError)throw error;throw new EventSdkConfigurationError("FILE_UNAVAILABLE")}}async function loadNodeConnectionProfile(path,expectedServiceRef){const text=await readConfigurationFile(path);try{const profile=validateNodeProfile(parseJsonStrict(text),expectedServiceRef);return Object.freeze({profile,revision:`sha256:${createHash("sha256").update(canonicalJson(profile)).digest("hex")}`})}catch(error){if(error instanceof EventSdkConfigurationError)throw error;throw new EventSdkConfigurationError("PROFILE_INVALID")}}import{parsePublicJwksSnapshot,parseJsonStrict as parseJsonStrict2}from"@eventsdk/sdk/contracts";import{validatePublicOriginKeys}from"@eventsdk/sdk/identity-verifier";async function loadPublicTrustSnapshot(file,issuer,jwksUri,now=Date.now()){try{const snapshot=parsePublicJwksSnapshot(parseJsonStrict2(await readConfigurationFile(file)));const fetched=Date.parse(snapshot.fetchedAt),until=Date.parse(snapshot.validUntil);if(snapshot.issuer!==issuer||jwksUri&&snapshot.jwksUri!==jwksUri)fail2("TRUST_ISSUER_MISMATCH");const uri=new URL(snapshot.jwksUri);if(uri.protocol!=="https:"||uri.username||uri.password||uri.search||uri.hash)fail2("TRUST_URI_INVALID");if(!Number.isFinite(now)||fetched>now||now>=until||until<=fetched||until-fetched>864e5)fail2("TRUST_SNAPSHOT_EXPIRED");validatePublicOriginKeys(snapshot.jwks);return snapshot}catch(error){if(error instanceof EventSdkConfigurationError)throw error;throw new EventSdkConfigurationError("TRUST_SNAPSHOT_INVALID")}}function fail2(code){throw new EventSdkConfigurationError(code)}async function loadNodePublicIdentity(file,expectedServiceRef,now=Date.now()){if(!file)return null;const{profile}=await loadNodeConnectionProfile(file,expectedServiceRef),signer=profile.identity.signer;if(!signer)return null;const snapshot=await loadPublicTrustSnapshot(signer.publicSnapshotFile,signer.issuer,signer.jwksUri,now);return{schemaVersion:1,profile:profile.identity.originProof,serviceRef:profile.serviceRef,source:profile.installationSource,issuer:signer.issuer,subject:signer.subject,jwksUri:snapshot.jwksUri,validUntil:snapshot.validUntil,jwks:snapshot.jwks}}async function loadNodeLocalRecoveryOrigin(file,serviceRef,event){const now=Date.now(),{profile}=await loadNodeConnectionProfile(file,serviceRef);const identity2=await loadNodePublicIdentity(file,serviceRef,now);if(!identity2||profile.mode!=="ACTIVE"||identity2.source!==event.source||identity2.serviceRef!==event.serviceorigin||!profile.identity.signer?.audiences.includes(event.audience))throw new EventSdkConfigurationError("LOCAL_RECOVERY_IDENTITY_MISMATCH");return{serviceRef,issuer:identity2.issuer,subject:identity2.subject,audience:[event.audience],verifiedAt:new Date(now).toISOString(),expiresAt:identity2.validUntil}}import{createPublicKey}from"node:crypto";import{createEventOriginSigner}from"@eventsdk/sdk/identity-verifier";async function loadSigningIdentity(profile,clock){const config=profile.identity.signer;if(!config)return null;const snapshot=await loadPublicTrustSnapshot(config.publicSnapshotFile,config.issuer,config.jwksUri,clock().getTime());const privateKeyPkcs8=await readConfigurationFile(config.privateKeyFile,true);assertKeyPair(config,snapshot.jwks.keys,privateKeyPkcs8);try{return await createEventOriginSigner({issuer:config.issuer,subject:config.subject,serviceRef:profile.serviceRef,source:profile.installationSource,keyId:config.keyId,algorithm:config.algorithm,privateKeyPkcs8,audiences:config.audiences,keysValidUntil:snapshot.validUntil,clock})}catch{throw new EventSdkConfigurationError("SIGNING_IDENTITY_INVALID")}}function assertKeyPair(config,keys,pem){try{const expected=keys.find(key=>key.kid===config.keyId);const actual=createPublicKey(pem).export({format:"jwk"});if(!expected||expected.alg!==void 0&&expected.alg!==config.algorithm||expected.kty!==actual.kty)throw new Error("mismatch");const fields=actual.kty==="RSA"?["n","e"]:actual.kty==="EC"?["crv","x","y"]:["crv","x"];if(fields.some(field=>expected[field]!==actual[field]))throw new Error("mismatch")}catch{throw new EventSdkConfigurationError("SIGNING_KEY_PAIR_MISMATCH")}}import{createEventOriginVerifier}from"@eventsdk/sdk/identity-verifier";async function loadTrustedSources(profile,clock){for(const binding of profile.identity.trustedSources)await verifier(binding,clock);return async(event,proof,signal)=>{signal.throwIfAborted();const selected=profile.identity.trustedSources.find(binding=>binding.source===event.source&&binding.serviceRef===event.serviceorigin&&binding.audience===event.audience);if(!selected)return{verified:false,reasonCode:"SOURCE_NOT_ENROLLED"};try{return await(await verifier(selected,clock)).verify(event,proof,signal)}catch(error){return{verified:false,reasonCode:signal.aborted?"CANCELLED":error instanceof EventSdkConfigurationError?error.code:"TRUST_SNAPSHOT_INVALID"}}}}async function verifier(binding,clock){const snapshot=await loadPublicTrustSnapshot(binding.publicSnapshotFile,binding.issuer,binding.jwksUri,clock().getTime());return createEventOriginVerifier({source:binding.source,serviceRef:binding.serviceRef,issuer:binding.issuer,subject:binding.subject,audience:binding.audience,algorithms:binding.algorithms,jwks:snapshot.jwks,keysValidUntil:snapshot.validUntil,clock})}async function loadNodeConnectionIdentity(loaded,clock=()=>new Date,inspectionOnly=false){if(loaded.profile.mode==="DISABLED"&&!inspectionOnly)throw new EventSdkConfigurationError("PROFILE_DISABLED");const config=loaded.profile.broker;await readConfigurationFile(config.credentialsFile,true);if(config.caFile)await readConfigurationFile(config.caFile);const signer=await loadSigningIdentity(loaded.profile,clock);const verifyOrigin=await loadTrustedSources(loaded.profile,clock);return Object.freeze({profile:loaded.profile,revision:loaded.revision,signer,verifyOrigin,async withBrokerCredentials(run){const credential=await readConfigurationFile(config.credentialsFile,true);const ca=config.caFile?await readConfigurationFile(config.caFile):void 0;return run({credential:new TextEncoder().encode(credential),...ca?{ca}:{}})}})}async function checkNodeConnectionConfiguration(file,expectedServiceRef,checkFiles=false){if(!file)return{status:"DISABLED",configured:false,connected:false};try{const loaded=await loadNodeConnectionProfile(file,expectedServiceRef),p=loaded.profile;if(checkFiles)await loadNodeConnectionIdentity(loaded,()=>new Date,true);return{status:p.mode==="DISABLED"?"DISABLED":checkFiles?"ASSETS_VALID":"PROFILE_VALID",configured:true,connected:false,serviceRef:p.serviceRef,mode:p.mode,revision:loaded.revision,roles:{publisher:p.publisher!==null,consumer:p.consumer!==null},originProof:p.identity.originProof,trustedSources:p.identity.trustedSources.length,filesChecked:checkFiles}}catch(error){return{status:"INVALID",configured:true,connected:false,code:error instanceof EventSdkConfigurationError?error.code:"CONFIGURATION_UNAVAILABLE"}}}import{createPrivateKey,createPublicKey as createPublicKey2}from"node:crypto";import{parseJsonStrict as parseJsonStrict3,parsePublicJwksSnapshot as parsePublicJwksSnapshot3}from"@eventsdk/sdk/contracts";import{constants as constants2}from"node:fs";import{lstat,open as open2,rename,unlink}from"node:fs/promises";import{dirname,isAbsolute as isAbsolute3}from"node:path";import{randomUUID}from"node:crypto";async function updatePublicConfiguration(file,update){if(!isAbsolute3(file)||file.includes("\0"))throw new EventSdkConfigurationError("FILE_REFERENCE_INVALID");const lock=`${file}.lock`,temporary=`${file}.${randomUUID()}.tmp`;let locked=false;try{const parent=await lstat(dirname(file));if(!parent.isDirectory()||(parent.mode&18)!==0)throw new Error("directory");const handle=await open2(lock,constants2.O_CREAT|constants2.O_EXCL|constants2.O_WRONLY|constants2.O_NOFOLLOW,384);locked=true;await handle.close();const previous=await existing(file);const output=update(previous);if(Buffer.byteLength(output)>65536)throw new Error("size");const next=await open2(temporary,constants2.O_CREAT|constants2.O_EXCL|constants2.O_WRONLY|constants2.O_NOFOLLOW,384);try{await next.writeFile(output,"utf8");await next.sync()}finally{await next.close()}await rename(temporary,file);const directory=await open2(dirname(file),constants2.O_RDONLY);try{await directory.sync()}finally{await directory.close()}}catch(error){if(error instanceof EventSdkConfigurationError)throw error;throw new EventSdkConfigurationError("PUBLIC_SNAPSHOT_UPDATE_FAILED")}finally{await unlink(temporary).catch(()=>void 0);if(locked)await unlink(lock).catch(()=>void 0)}}async function existing(file){try{await lstat(file)}catch(error){if(error.code==="ENOENT")return null;throw error}return readConfigurationFile(file)}import{parsePublicJwksSnapshot as parsePublicJwksSnapshot2}from"@eventsdk/sdk/contracts";import{validatePublicOriginKeys as validatePublicOriginKeys2}from"@eventsdk/sdk/identity-verifier";function snapshotValue(issuer,jwksUri,jwks,now,ttlMs){if(!Number.isFinite(now)||!Number.isSafeInteger(ttlMs)||ttlMs<6e4||ttlMs>864e5)throw new EventSdkConfigurationError("SNAPSHOT_LIFETIME_INVALID");try{const value=parsePublicJwksSnapshot2({schemaVersion:1,issuer,jwksUri,fetchedAt:new Date(now).toISOString(),validUntil:new Date(now+ttlMs).toISOString(),jwks});validatePublicOriginKeys2(value.jwks);return value}catch{throw new EventSdkConfigurationError("PUBLIC_JWKS_INVALID")}}async function exportOwnPublicSnapshot(file,options={}){const{profile}=await loadNodeConnectionProfile(file);const config=profile.identity.signer;if(!config)throw new EventSdkConfigurationError("SIGNER_NOT_CONFIGURED");const pem=await readConfigurationFile(config.privateKeyFile,true);let key;try{const privateKey=createPrivateKey(pem);const publicKey=createPublicKey2(privateKey).export({format:"jwk"});const expected=config.algorithm==="EdDSA"?"OKP":config.algorithm==="ES256"?"EC":"RSA";if(!pem.startsWith("-----BEGIN PRIVATE KEY-----")||publicKey.kty!==expected||expected==="OKP"&&publicKey.crv!=="Ed25519"||expected==="EC"&&publicKey.crv!=="P-256"||expected==="RSA"&&(privateKey.asymmetricKeyDetails?.modulusLength??0)<2048)throw new Error("algorithm");key={...publicKey,kid:config.keyId,alg:config.algorithm,use:"sig"}}catch{throw new EventSdkConfigurationError("SIGNING_KEY_INVALID")}const now=options.now??Date.now(),ttl=options.ttlMs??36e5;await updatePublicConfiguration(config.publicSnapshotFile,previous=>{const keys=retained(previous,config.issuer,config.jwksUri);const same=keys.find(item=>item.kid===key.kid);if(same&&!samePublicKey(same,key))throw new EventSdkConfigurationError("KEY_ID_REUSE_REJECTED");const snapshot=snapshotValue(config.issuer,config.jwksUri,{keys:same?keys.map(item=>item===same?key:item):[...keys,key]},now,ttl);return JSON.stringify(snapshot,null,2)+"\n"});return{status:"PUBLIC_SNAPSHOT_EXPORTED",serviceRef:profile.serviceRef,keyId:config.keyId}}function samePublicKey(old,key){const fields=key.kty==="RSA"?["kty","n","e"]:key.kty==="EC"?["kty","crv","x","y"]:["kty","crv","x"];return fields.every(field=>old[field]===key[field])&&(old.alg===void 0||old.alg===key.alg)}function retained(previous,issuer,uri){if(!previous)return[];try{const parsed=parsePublicJwksSnapshot3(parseJsonStrict3(previous));if(parsed.issuer!==issuer||parsed.jwksUri!==uri)throw new Error("binding");return snapshotValue(issuer,uri,parsed.jwks,0,6e4).jwks.keys}catch{throw new EventSdkConfigurationError("EXISTING_PUBLIC_SNAPSHOT_INVALID")}}import{parseJsonStrict as parseJsonStrict4}from"@eventsdk/sdk/contracts";async function refreshTrustedPublicSnapshots(file,options={}){const{profile}=await loadNodeConnectionProfile(file);let refreshed=0;for(const binding of profile.identity.trustedSources){await refresh(binding,options);refreshed+=1}return{status:"TRUST_SNAPSHOTS_REFRESHED",serviceRef:profile.serviceRef,refreshed}}async function refresh(binding,options){try{const signal=AbortSignal.timeout(5e3);const response=await(options.fetch??globalThis.fetch)(binding.jwksUri,{method:"GET",redirect:"error",signal,headers:{accept:"application/json"}});if(!response.ok||response.redirected||!response.body)throw new Error("response");const reader=response.body.getReader(),chunks=[];let size=0;try{while(true){signal.throwIfAborted();const result=await reader.read();if(result.done)break;size+=result.value.byteLength;if(size>65536)throw new Error("size");chunks.push(result.value)}}finally{await reader.cancel().catch(()=>void 0);reader.releaseLock()}const raw=parseJsonStrict4(new TextDecoder("utf-8",{fatal:true}).decode(Buffer.concat(chunks)));const snapshot=snapshotValue(binding.issuer,binding.jwksUri,raw,options.now??Date.now(),options.ttlMs??36e5);await updatePublicConfiguration(binding.publicSnapshotFile,()=>JSON.stringify(snapshot,null,2)+"\n")}catch(error){if(error instanceof EventSdkConfigurationError)throw error;throw new EventSdkConfigurationError("TRUST_REFRESH_FAILED")}}export{EventSdkConfigurationError,checkNodeConnectionConfiguration,exportOwnPublicSnapshot,loadNodeConnectionIdentity,loadNodeConnectionProfile,loadNodeLocalRecoveryOrigin,loadNodePublicIdentity,loadSigningIdentity as loadNodeSigningIdentity,loadPublicTrustSnapshot,refreshTrustedPublicSnapshots,validateNodeProfile};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{createHash}from"node:crypto";import{canonicalJson,parseJsonStrict}from"@eventsdk/sdk/contracts";import{constants}from"node:fs";import{open}from"node:fs/promises";import{isAbsolute}from"node:path";var EventSdkConfigurationError=class extends Error{constructor(code){super(code);this.code=code}code};async function readConfigurationFile(path,privateFile=false){if(!isAbsolute(path)||path.includes("\0"))throw new EventSdkConfigurationError("FILE_REFERENCE_INVALID");try{const file=await open(path,constants.O_RDONLY|constants.O_NOFOLLOW);try{const stat=await file.stat();if(!stat.isFile()||stat.nlink!==1||stat.size<1||stat.size>65536||(stat.mode&18)!==0||privateFile&&(stat.mode&63)!==0)throw new EventSdkConfigurationError("FILE_PROTECTION_REQUIRED");const bytes=Buffer.alloc(65537);const read=await file.read(bytes,0,bytes.length,0);if(read.bytesRead>65536||read.bytesRead!==stat.size)throw new EventSdkConfigurationError("FILE_CHANGED");const after=await file.stat();if(after.size!==stat.size||after.mtimeMs!==stat.mtimeMs||after.ctimeMs!==stat.ctimeMs||after.nlink!==1)throw new EventSdkConfigurationError("FILE_CHANGED");return new TextDecoder("utf-8",{fatal:true}).decode(bytes.subarray(0,read.bytesRead))}finally{await file.close()}}catch(error){if(error instanceof EventSdkConfigurationError)throw error;throw new EventSdkConfigurationError("FILE_UNAVAILABLE")}}import{isIP}from"node:net";import{isAbsolute as isAbsolute2}from"node:path";import{parseNodeConnectionProfile}from"@eventsdk/sdk/contracts";function validateNodeProfile(raw,expectedServiceRef){let profile;try{profile=parseNodeConnectionProfile(structuredClone(raw))}catch{throw new EventSdkConfigurationError("PROFILE_INVALID")}if(expectedServiceRef&&profile.serviceRef!==expectedServiceRef)fail("SERVICE_IDENTITY_MISMATCH");broker(profile);identity(profile);const source=new URL(profile.installationSource);if(source.username||source.password||source.search||source.hash)fail("INSTALLATION_SOURCE_INVALID");if(profile.publisher&&(!profile.identity.signer||!subject(profile.publisher.subject)||!entity(profile.publisher.expectedStream)))fail("PUBLISHER_IDENTITY_REQUIRED");if(profile.consumer&&(!profile.identity.trustedSources.length||!subject(profile.consumer.subject)||!entity(profile.consumer.stream)||!entity(profile.consumer.durableName)))fail("CONSUMER_IDENTITY_REQUIRED");if(!profile.publisher&&!profile.consumer)fail("CAPABILITY_ROLE_REQUIRED");routing(profile);return deepFreeze(profile)}function routing(profile){const publisher=profile.publisher;const audiences=profile.identity.signer?.audiences??[];if(publisher){const routes=publisher.audienceRoutes;if(!routes&&audiences.length!==1)fail("PUBLISHER_AUDIENCE_ROUTES_REQUIRED");if(routes&&(routes.length!==audiences.length||new Set(routes.map(r=>r.audience)).size!==routes.length||new Set(routes.map(r=>r.subject)).size!==routes.length||routes.some(r=>!audiences.includes(r.audience)||!subject(r.subject)||!entity(r.expectedStream))||routes[0]?.subject!==publisher.subject||routes[0]?.expectedStream!==publisher.expectedStream))fail("PUBLISHER_AUDIENCE_ROUTES_INVALID")}const consumer=profile.consumer;if(consumer){const bindings=[consumer,...consumer.additionalBindings??[]];if(new Set(bindings.map(b=>JSON.stringify([b.stream,b.durableName]))).size!==bindings.length||new Set(bindings.map(b=>b.subject)).size!==bindings.length||bindings.some(b=>!subject(b.subject)||!entity(b.stream)||!entity(b.durableName))||bindings.length*consumer.batchSize*profile.broker.timeoutMs>3e5)fail("CONSUMER_BINDINGS_INVALID")}}function broker(profile){const b=profile.broker,url=new URL(b.serverUrl);if(url.protocol!=="tls:"||url.username||url.password||url.search||url.hash||!["","/"].includes(url.pathname)||isIP(url.hostname.replace(/^\[|\]$/g,""))||url.hostname.length>253||url.hostname.split(".").some(label=>!/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(label))||b.expectedDnsName!==url.hostname)fail("BROKER_TLS_IDENTITY_REQUIRED");files([b.credentialsFile,...b.caFile?[b.caFile]:[]]);if(profile.consumer&&(profile.consumer.fetchExpiresMs>=b.timeoutMs||profile.consumer.batchSize*b.timeoutMs>3e5))fail("CONSUMER_BOUNDS_INVALID")}function identity(profile){const signer=profile.identity.signer;if(signer){https(signer.issuer);https(signer.jwksUri);files([signer.privateKeyFile,signer.publicSnapshotFile])}const seen=new Set;for(const trust of profile.identity.trustedSources){https(trust.issuer);https(trust.jwksUri);files([trust.publicSnapshotFile]);const key=JSON.stringify([trust.source,trust.serviceRef,trust.audience]);if(seen.has(key)||trust.audience!==profile.serviceRef)fail("TRUST_BINDING_AMBIGUOUS");seen.add(key)}}function https(value){const url=new URL(value);if(url.protocol!=="https:"||url.username||url.password||url.search||url.hash)fail("ISSUER_HTTPS_REQUIRED")}function files(values){if(values.some(path=>!isAbsolute2(path)||path.includes("\0")))fail("FILE_REFERENCE_INVALID")}function subject(value){return/^[^\s.*>]+(?:\.[^\s.*>]+)*$/u.test(value)}function entity(value){return/^[^\s.*>/\\]+$/u.test(value)}function fail(code){throw new EventSdkConfigurationError(code)}function deepFreeze(value){if(value&&typeof value==="object"&&!Object.isFrozen(value)){Object.freeze(value);Object.values(value).forEach(deepFreeze)}return value}async function loadNodeConnectionProfile(path,expectedServiceRef){const text=await readConfigurationFile(path);try{const profile=validateNodeProfile(parseJsonStrict(text),expectedServiceRef);return Object.freeze({profile,revision:`sha256:${createHash("sha256").update(canonicalJson(profile)).digest("hex")}`})}catch(error){if(error instanceof EventSdkConfigurationError)throw error;throw new EventSdkConfigurationError("PROFILE_INVALID")}}import{createPublicKey}from"node:crypto";import{createEventOriginSigner}from"@eventsdk/sdk/identity-verifier";import{parsePublicJwksSnapshot,parseJsonStrict as parseJsonStrict2}from"@eventsdk/sdk/contracts";import{validatePublicOriginKeys}from"@eventsdk/sdk/identity-verifier";async function loadPublicTrustSnapshot(file,issuer,jwksUri,now=Date.now()){try{const snapshot=parsePublicJwksSnapshot(parseJsonStrict2(await readConfigurationFile(file)));const fetched=Date.parse(snapshot.fetchedAt),until=Date.parse(snapshot.validUntil);if(snapshot.issuer!==issuer||jwksUri&&snapshot.jwksUri!==jwksUri)fail2("TRUST_ISSUER_MISMATCH");const uri=new URL(snapshot.jwksUri);if(uri.protocol!=="https:"||uri.username||uri.password||uri.search||uri.hash)fail2("TRUST_URI_INVALID");if(!Number.isFinite(now)||fetched>now||now>=until||until<=fetched||until-fetched>864e5)fail2("TRUST_SNAPSHOT_EXPIRED");validatePublicOriginKeys(snapshot.jwks);return snapshot}catch(error){if(error instanceof EventSdkConfigurationError)throw error;throw new EventSdkConfigurationError("TRUST_SNAPSHOT_INVALID")}}function fail2(code){throw new EventSdkConfigurationError(code)}async function loadSigningIdentity(profile,clock){const config=profile.identity.signer;if(!config)return null;const snapshot=await loadPublicTrustSnapshot(config.publicSnapshotFile,config.issuer,config.jwksUri,clock().getTime());const privateKeyPkcs8=await readConfigurationFile(config.privateKeyFile,true);assertKeyPair(config,snapshot.jwks.keys,privateKeyPkcs8);try{return await createEventOriginSigner({issuer:config.issuer,subject:config.subject,serviceRef:profile.serviceRef,source:profile.installationSource,keyId:config.keyId,algorithm:config.algorithm,privateKeyPkcs8,audiences:config.audiences,keysValidUntil:snapshot.validUntil,clock})}catch{throw new EventSdkConfigurationError("SIGNING_IDENTITY_INVALID")}}function assertKeyPair(config,keys,pem){try{const expected=keys.find(key=>key.kid===config.keyId);const actual=createPublicKey(pem).export({format:"jwk"});if(!expected||expected.alg!==void 0&&expected.alg!==config.algorithm||expected.kty!==actual.kty)throw new Error("mismatch");const fields=actual.kty==="RSA"?["n","e"]:actual.kty==="EC"?["crv","x","y"]:["crv","x"];if(fields.some(field=>expected[field]!==actual[field]))throw new Error("mismatch")}catch{throw new EventSdkConfigurationError("SIGNING_KEY_PAIR_MISMATCH")}}import{createEventOriginVerifier}from"@eventsdk/sdk/identity-verifier";async function loadTrustedSources(profile,clock){for(const binding of profile.identity.trustedSources)await verifier(binding,clock);return async(event,proof,signal)=>{signal.throwIfAborted();const selected=profile.identity.trustedSources.find(binding=>binding.source===event.source&&binding.serviceRef===event.serviceorigin&&binding.audience===event.audience);if(!selected)return{verified:false,reasonCode:"SOURCE_NOT_ENROLLED"};try{return await(await verifier(selected,clock)).verify(event,proof,signal)}catch(error){return{verified:false,reasonCode:signal.aborted?"CANCELLED":error instanceof EventSdkConfigurationError?error.code:"TRUST_SNAPSHOT_INVALID"}}}}async function verifier(binding,clock){const snapshot=await loadPublicTrustSnapshot(binding.publicSnapshotFile,binding.issuer,binding.jwksUri,clock().getTime());return createEventOriginVerifier({source:binding.source,serviceRef:binding.serviceRef,issuer:binding.issuer,subject:binding.subject,audience:binding.audience,algorithms:binding.algorithms,jwks:snapshot.jwks,keysValidUntil:snapshot.validUntil,clock})}async function loadNodeConnectionIdentity(loaded,clock=()=>new Date,inspectionOnly=false){if(loaded.profile.mode==="DISABLED"&&!inspectionOnly)throw new EventSdkConfigurationError("PROFILE_DISABLED");const config=loaded.profile.broker;await readConfigurationFile(config.credentialsFile,true);if(config.caFile)await readConfigurationFile(config.caFile);const signer=await loadSigningIdentity(loaded.profile,clock);const verifyOrigin=await loadTrustedSources(loaded.profile,clock);return Object.freeze({profile:loaded.profile,revision:loaded.revision,signer,verifyOrigin,async withBrokerCredentials(run){const credential=await readConfigurationFile(config.credentialsFile,true);const ca=config.caFile?await readConfigurationFile(config.caFile):void 0;return run({credential:new TextEncoder().encode(credential),...ca?{ca}:{}})}})}async function checkNodeConnectionConfiguration(file,expectedServiceRef,checkFiles=false){if(!file)return{status:"DISABLED",configured:false,connected:false};try{const loaded=await loadNodeConnectionProfile(file,expectedServiceRef),p=loaded.profile;if(checkFiles)await loadNodeConnectionIdentity(loaded,()=>new Date,true);return{status:p.mode==="DISABLED"?"DISABLED":checkFiles?"ASSETS_VALID":"PROFILE_VALID",configured:true,connected:false,serviceRef:p.serviceRef,mode:p.mode,revision:loaded.revision,roles:{publisher:p.publisher!==null,consumer:p.consumer!==null},originProof:p.identity.originProof,trustedSources:p.identity.trustedSources.length,filesChecked:checkFiles}}catch(error){return{status:"INVALID",configured:true,connected:false,code:error instanceof EventSdkConfigurationError?error.code:"CONFIGURATION_UNAVAILABLE"}}}import{createPrivateKey,createPublicKey as createPublicKey2}from"node:crypto";import{parseJsonStrict as parseJsonStrict3,parsePublicJwksSnapshot as parsePublicJwksSnapshot3}from"@eventsdk/sdk/contracts";import{constants as constants2}from"node:fs";import{lstat,open as open2,rename,unlink}from"node:fs/promises";import{dirname,isAbsolute as isAbsolute3}from"node:path";import{randomUUID}from"node:crypto";async function updatePublicConfiguration(file,update){if(!isAbsolute3(file)||file.includes("\0"))throw new EventSdkConfigurationError("FILE_REFERENCE_INVALID");const lock=`${file}.lock`,temporary=`${file}.${randomUUID()}.tmp`;let locked=false;try{const parent=await lstat(dirname(file));if(!parent.isDirectory()||(parent.mode&18)!==0)throw new Error("directory");const handle=await open2(lock,constants2.O_CREAT|constants2.O_EXCL|constants2.O_WRONLY|constants2.O_NOFOLLOW,384);locked=true;await handle.close();const previous=await existing(file);const output=update(previous);if(Buffer.byteLength(output)>65536)throw new Error("size");const next=await open2(temporary,constants2.O_CREAT|constants2.O_EXCL|constants2.O_WRONLY|constants2.O_NOFOLLOW,384);try{await next.writeFile(output,"utf8");await next.sync()}finally{await next.close()}await rename(temporary,file);const directory=await open2(dirname(file),constants2.O_RDONLY);try{await directory.sync()}finally{await directory.close()}}catch(error){if(error instanceof EventSdkConfigurationError)throw error;throw new EventSdkConfigurationError("PUBLIC_SNAPSHOT_UPDATE_FAILED")}finally{await unlink(temporary).catch(()=>void 0);if(locked)await unlink(lock).catch(()=>void 0)}}async function existing(file){try{await lstat(file)}catch(error){if(error.code==="ENOENT")return null;throw error}return readConfigurationFile(file)}import{parsePublicJwksSnapshot as parsePublicJwksSnapshot2}from"@eventsdk/sdk/contracts";import{validatePublicOriginKeys as validatePublicOriginKeys2}from"@eventsdk/sdk/identity-verifier";function snapshotValue(issuer,jwksUri,jwks,now,ttlMs){if(!Number.isFinite(now)||!Number.isSafeInteger(ttlMs)||ttlMs<6e4||ttlMs>864e5)throw new EventSdkConfigurationError("SNAPSHOT_LIFETIME_INVALID");try{const value=parsePublicJwksSnapshot2({schemaVersion:1,issuer,jwksUri,fetchedAt:new Date(now).toISOString(),validUntil:new Date(now+ttlMs).toISOString(),jwks});validatePublicOriginKeys2(value.jwks);return value}catch{throw new EventSdkConfigurationError("PUBLIC_JWKS_INVALID")}}async function exportOwnPublicSnapshot(file,options={}){const{profile}=await loadNodeConnectionProfile(file);const config=profile.identity.signer;if(!config)throw new EventSdkConfigurationError("SIGNER_NOT_CONFIGURED");const pem=await readConfigurationFile(config.privateKeyFile,true);let key;try{const privateKey=createPrivateKey(pem);const publicKey=createPublicKey2(privateKey).export({format:"jwk"});const expected=config.algorithm==="EdDSA"?"OKP":config.algorithm==="ES256"?"EC":"RSA";if(!pem.startsWith("-----BEGIN PRIVATE KEY-----")||publicKey.kty!==expected||expected==="OKP"&&publicKey.crv!=="Ed25519"||expected==="EC"&&publicKey.crv!=="P-256"||expected==="RSA"&&(privateKey.asymmetricKeyDetails?.modulusLength??0)<2048)throw new Error("algorithm");key={...publicKey,kid:config.keyId,alg:config.algorithm,use:"sig"}}catch{throw new EventSdkConfigurationError("SIGNING_KEY_INVALID")}const now=options.now??Date.now(),ttl=options.ttlMs??36e5;await updatePublicConfiguration(config.publicSnapshotFile,previous=>{const keys=retained(previous,config.issuer,config.jwksUri);const same=keys.find(item=>item.kid===key.kid);if(same&&!samePublicKey(same,key))throw new EventSdkConfigurationError("KEY_ID_REUSE_REJECTED");const snapshot=snapshotValue(config.issuer,config.jwksUri,{keys:same?keys.map(item=>item===same?key:item):[...keys,key]},now,ttl);return JSON.stringify(snapshot,null,2)+"\n"});return{status:"PUBLIC_SNAPSHOT_EXPORTED",serviceRef:profile.serviceRef,keyId:config.keyId}}function samePublicKey(old,key){const fields=key.kty==="RSA"?["kty","n","e"]:key.kty==="EC"?["kty","crv","x","y"]:["kty","crv","x"];return fields.every(field=>old[field]===key[field])&&(old.alg===void 0||old.alg===key.alg)}function retained(previous,issuer,uri){if(!previous)return[];try{const parsed=parsePublicJwksSnapshot3(parseJsonStrict3(previous));if(parsed.issuer!==issuer||parsed.jwksUri!==uri)throw new Error("binding");return snapshotValue(issuer,uri,parsed.jwks,0,6e4).jwks.keys}catch{throw new EventSdkConfigurationError("EXISTING_PUBLIC_SNAPSHOT_INVALID")}}import{parseJsonStrict as parseJsonStrict4}from"@eventsdk/sdk/contracts";async function refreshTrustedPublicSnapshots(file,options={}){const{profile}=await loadNodeConnectionProfile(file);let refreshed=0;for(const binding of profile.identity.trustedSources){await refresh(binding,options);refreshed+=1}return{status:"TRUST_SNAPSHOTS_REFRESHED",serviceRef:profile.serviceRef,refreshed}}async function refresh(binding,options){try{const signal=AbortSignal.timeout(5e3);const response=await(options.fetch??globalThis.fetch)(binding.jwksUri,{method:"GET",redirect:"error",signal,headers:{accept:"application/json"}});if(!response.ok||response.redirected||!response.body)throw new Error("response");const reader=response.body.getReader(),chunks=[];let size=0;try{while(true){signal.throwIfAborted();const result2=await reader.read();if(result2.done)break;size+=result2.value.byteLength;if(size>65536)throw new Error("size");chunks.push(result2.value)}}finally{await reader.cancel().catch(()=>void 0);reader.releaseLock()}const raw=parseJsonStrict4(new TextDecoder("utf-8",{fatal:true}).decode(Buffer.concat(chunks)));const snapshot=snapshotValue(binding.issuer,binding.jwksUri,raw,options.now??Date.now(),options.ttlMs??36e5);await updatePublicConfiguration(binding.publicSnapshotFile,()=>JSON.stringify(snapshot,null,2)+"\n")}catch(error){if(error instanceof EventSdkConfigurationError)throw error;throw new EventSdkConfigurationError("TRUST_REFRESH_FAILED")}}async function runConfigurationCheck(args){if(args.length===2&&args[1]&&["export-own-public","refresh-trust"].includes(args[0]??"")){try{return args[0]==="export-own-public"?await exportOwnPublicSnapshot(args[1]):await refreshTrustedPublicSnapshots(args[1])}catch(error){return{status:"INVALID",code:error instanceof EventSdkConfigurationError?error.code:"CONFIGURATION_OPERATION_FAILED"}}}if(args[0]!=="check"||!args[1]||args.slice(2).some(arg=>arg!=="--check-files")||args.length>3)return{status:"INVALID",code:"USAGE",usage:"eventsdk-config check <absolute-profile-file> [--check-files] | export-own-public <file> | refresh-trust <file>"};return checkNodeConnectionConfiguration(args[1],void 0,args.includes("--check-files"))}var result=await runConfigurationCheck(process.argv.slice(2));process.stdout.write(`${JSON.stringify(result)}
|
|
3
|
+
`);if(result.status==="INVALID")process.exitCode=1;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface TrustRefreshOptions {
|
|
2
|
+
now?: number;
|
|
3
|
+
ttlMs?: number;
|
|
4
|
+
fetch?: typeof globalThis.fetch;
|
|
5
|
+
}
|
|
6
|
+
export declare function refreshTrustedPublicSnapshots(file: string, options?: TrustRefreshOptions): Promise<{
|
|
7
|
+
status: "TRUST_SNAPSHOTS_REFRESHED";
|
|
8
|
+
serviceRef: string;
|
|
9
|
+
refreshed: number;
|
|
10
|
+
}>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type NodeConnectionProfile } from '@eventsdk/sdk/contracts';
|
|
2
|
+
export interface LoadedNodeProfile {
|
|
3
|
+
readonly profile: NodeConnectionProfile;
|
|
4
|
+
readonly revision: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function loadNodeConnectionProfile(path: string, expectedServiceRef?: string): Promise<LoadedNodeProfile>;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { openNodeWorkerSession, type NodeWorkerSession } from './session.js';
|
|
2
|
+
export { createNativeRouteScheduler, type NativeRouteFault } from './route-scheduler.js';
|
|
3
|
+
export { runNodeEventWorker, type NodeWorkerState, type NodeWorkerTask, type NodeWorkerOptions, } from './lifecycle.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{loadNodeConnectionProfile,loadNodeConnectionIdentity,loadNodeSigningIdentity}from"@eventsdk/sdk/node-config";import{openNatsDeliverySession,credsAuthenticator,nkeyAuthenticator}from"@eventsdk/sdk/transport-nats";import{CoreError}from"@eventsdk/sdk/core";import{EventSdkConfigurationError}from"@eventsdk/sdk/node-config";function createNativeRouteScheduler(options={}){const now=options.now??Date.now,retryMs=options.retryMs??3e4,capacity=options.capacity??1024;if(!Number.isSafeInteger(retryMs)||retryMs<100||retryMs>3e5||!Number.isSafeInteger(capacity)||capacity<1||capacity>1e4)throw new Error("ROUTE_SCHEDULER_BOUNDS_INVALID");const retries=new Map;return{async run(routeRef,work,signal){signal.throwIfAborted();if((retries.get(routeRef)??0)>now())return false;try{await work();retries.delete(routeRef);return true}catch(error){signal.throwIfAborted();const retryAt=now()+retryMs;retries.delete(routeRef);while(retries.size>=capacity){const oldest=retries.keys().next().value;if(oldest===void 0)break;retries.delete(oldest)}retries.set(routeRef,retryAt);const code=error instanceof CoreError||error instanceof EventSdkConfigurationError?error.code:"ROUTE_UNAVAILABLE";options.fault?.({routeRef,reasonCode:/^[A-Z][A-Z0-9_]{0,63}$/.test(code)?code:"ROUTE_UNAVAILABLE",retryAt});return false}},held(){return[...retries].map(([routeRef,retryAt])=>({routeRef,retryAt}))}}}function routedPublisher(transport,profile,originProof){const publisher=profile.publisher;if(!publisher)return null;const audiences=profile.identity.signer?.audiences??[];const routes=publisher.audienceRoutes??audiences.map(audience=>({audience,subject:publisher.subject,expectedStream:publisher.expectedStream}));const ports=new Map(routes.map(route=>[route.audience,transport.publisher({subject:route.subject,expectedStream:route.expectedStream,timeoutMs:profile.broker.timeoutMs,maxPayloadBytes:profile.broker.maxPayloadBytes,...originProof?{originProof}:{}})]));return{async publish(event,context){const port=ports.get(event.audience);if(!port)throw Error("PUBLISH_AUDIENCE_NOT_CONFIGURED");return port.publish(event,context)}}}async function routedConsumer(transport,profile,signal){const consumer=profile.consumer;if(!consumer)return null;const bindings=[consumer,...consumer.additionalBindings??[]];const faults=new Map;const scheduler=createNativeRouteScheduler({capacity:bindings.length,retryMs:1e3,fault:value=>faults.set(value.routeRef,value)});const ports=[];for(const binding of bindings)ports.push(await openBinding(transport,profile,consumer,binding,signal));return{async consumeBatch(accept,child){let total=0;for(const[index,port]of ports.entries()){child.throwIfAborted();const binding=bindings[index];if(!binding)throw Error("CONSUMER_BINDING_MISSING");const ref=`${binding.stream}/${binding.durableName}`;if(ports.length===1){total+=await port.consumeBatch(accept,child);continue}if(await scheduler.run(ref,async()=>{total+=await port.consumeBatch(accept,child)},child))faults.delete(ref)}return total},heldRoutes(){return[...faults.values()]}}}function openBinding(transport,profile,consumer,binding,signal){return transport.consumer(binding.stream,{consumerName:binding.durableName,expectedStream:binding.stream,expectedSubject:binding.subject,deploymentDomainRef:profile.broker.deploymentDomainRef,timeoutMs:profile.broker.timeoutMs,maxPayloadBytes:profile.broker.maxPayloadBytes,retryDelayMs:1e3,batchSize:consumer.batchSize,fetchExpiresMs:consumer.fetchExpiresMs},signal)}async function openNodeWorkerSession(file,serviceRef,signal){const loaded=await loadNodeConnectionProfile(file,serviceRef);const identity=await loadNodeConnectionIdentity(loaded);const profile=loaded.profile;const transport=await openTransport(identity,signal);try{const signer=async()=>{const current=await loadNodeSigningIdentity(profile,()=>new Date);if(!current)throw new Error("SIGNER_NOT_CONFIGURED");return current};const publisher=routedPublisher(transport,profile,async(event,child)=>(await signer()).sign(event,child));const consumer=await routedConsumer(transport,profile,signal);return{profile,revision:loaded.revision,publisher,consumer,verifyOrigin:identity.verifyOrigin,refreshOrigin:async event=>(await signer()).currentOrigin(event.audience),isClosed:transport.isClosed,close:transport.close}}catch(error){await transport.close();throw error}}async function openTransport(identity,signal){const broker=identity.profile.broker;return identity.withBrokerCredentials(({credential,ca})=>openNatsDeliverySession({serverUrl:broker.serverUrl,expectedDnsName:broker.expectedDnsName,timeoutMs:broker.timeoutMs,signal,...ca?{caPem:ca}:{},authenticator:broker.authentication==="NATS_CREDS"?credsAuthenticator(credential):nkeyAuthenticator(new TextEncoder().encode(new TextDecoder().decode(credential).trim()))}))}import{loadNodeConnectionProfile as loadNodeConnectionProfile2,EventSdkConfigurationError as EventSdkConfigurationError2}from"@eventsdk/sdk/node-config";async function runNodeEventWorker(options,signal){const interval=bounded(options.intervalMs??1e3,100,3e4),retry=bounded(options.reconnectMs??2e3,100,3e4);const state=async(status,revision)=>options.state?.({schemaVersion:1,status,connected:status==="CONNECTED",at:new Date().toISOString(),...revision?{revision}:{}});try{while(!signal.aborted){const config=await loadNodeConnectionProfile2(options.file,options.serviceRef);if(config.profile.mode==="DISABLED"){await state("DISABLED",config.revision);return}try{await connected(options,signal,interval,state)}catch{if(signal.aborted)break;await state("RECONNECTING");await pause(retry,signal)}}}finally{await state("STOPPED")}}async function connected(options,signal,interval,state){const session=await(options.open??openNodeWorkerSession)(options.file,options.serviceRef,signal);let task;let closing;const close=()=>closing??=session.close();const abort=()=>{void close()};signal.addEventListener("abort",abort,{once:true});try{task=await options.tasks(session);if(session.isClosed())throw new Error("CONNECTION_CLOSED");await state("CONNECTED",session.revision);let heartbeatAt=Date.now();const heartbeatMs=bounded(options.heartbeatMs??3e4,100,3e5);while(!signal.aborted&&!session.isClosed()){const current=await loadNodeConnectionProfile2(options.file,options.serviceRef);if(current.revision!==session.revision)throw new EventSdkConfigurationError2("CONFIGURATION_CHANGED");await task.tick(signal);if(!signal.aborted&&!session.isClosed()&&Date.now()-heartbeatAt>=heartbeatMs){await state("CONNECTED",session.revision);heartbeatAt=Date.now()}await pause(interval,signal)}if(!signal.aborted)throw new Error("CONNECTION_CLOSED")}finally{signal.removeEventListener("abort",abort);await close();await task?.close()}}function bounded(value,min,max){if(!Number.isSafeInteger(value)||value<min||value>max)throw new EventSdkConfigurationError2("WORKER_BOUNDS_INVALID");return value}async function pause(ms,signal){if(signal.aborted)return;await new Promise(resolve=>{const finish=()=>{clearTimeout(timer);signal.removeEventListener("abort",finish);resolve()};const timer=setTimeout(finish,ms);signal.addEventListener("abort",finish,{once:true})})}export{createNativeRouteScheduler,openNodeWorkerSession,runNodeEventWorker};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { openNodeWorkerSession, type NodeWorkerSession } from './session.js';
|
|
2
|
+
import type { NodeWorkerObservation } from '@eventsdk/sdk/contracts';
|
|
3
|
+
export type NodeWorkerState = NodeWorkerObservation;
|
|
4
|
+
export interface NodeWorkerTask {
|
|
5
|
+
tick(signal: AbortSignal): Promise<void>;
|
|
6
|
+
close(): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export interface NodeWorkerOptions {
|
|
9
|
+
file: string;
|
|
10
|
+
serviceRef: string;
|
|
11
|
+
intervalMs?: number;
|
|
12
|
+
reconnectMs?: number;
|
|
13
|
+
heartbeatMs?: number;
|
|
14
|
+
tasks(session: NodeWorkerSession): Promise<NodeWorkerTask>;
|
|
15
|
+
state?(state: NodeWorkerState): Promise<void> | void;
|
|
16
|
+
open?: typeof openNodeWorkerSession;
|
|
17
|
+
}
|
|
18
|
+
export declare function runNodeEventWorker(options: NodeWorkerOptions, signal: AbortSignal): Promise<void>;
|
|
19
|
+
export declare function pause(ms: number, signal: AbortSignal): Promise<void>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface NativeRouteFault {
|
|
2
|
+
routeRef: string;
|
|
3
|
+
reasonCode: string;
|
|
4
|
+
retryAt: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function createNativeRouteScheduler(options?: {
|
|
7
|
+
now?: () => number;
|
|
8
|
+
retryMs?: number;
|
|
9
|
+
capacity?: number;
|
|
10
|
+
fault?: (value: NativeRouteFault) => void;
|
|
11
|
+
}): {
|
|
12
|
+
run(routeRef: string, work: () => Promise<unknown>, signal: AbortSignal): Promise<boolean>;
|
|
13
|
+
held(): {
|
|
14
|
+
routeRef: string;
|
|
15
|
+
retryAt: number;
|
|
16
|
+
}[];
|
|
17
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CloudEvent } from '@eventsdk/sdk/contracts';
|
|
2
|
+
export declare function openNodeWorkerSession(file: string, serviceRef: string, signal: AbortSignal): Promise<{
|
|
3
|
+
profile: import("@eventsdk/sdk/contracts").NodeConnectionProfile;
|
|
4
|
+
revision: string;
|
|
5
|
+
publisher: import("@eventsdk/sdk/core").PublishPort | null;
|
|
6
|
+
consumer: {
|
|
7
|
+
consumeBatch(accept: import("@eventsdk/sdk/transport-nats").AcceptDelivery, child: AbortSignal): Promise<number>;
|
|
8
|
+
heldRoutes(): import("./route-scheduler.js").NativeRouteFault[];
|
|
9
|
+
} | null;
|
|
10
|
+
verifyOrigin: (event: import("@eventsdk/sdk/contracts").CloudEvent, proof: string, signal: AbortSignal) => Promise<import("@eventsdk/sdk/identity-contracts").OriginVerification>;
|
|
11
|
+
refreshOrigin: (event: CloudEvent) => Promise<import("@eventsdk/sdk/identity-contracts").VerifiedOrigin>;
|
|
12
|
+
isClosed: () => boolean;
|
|
13
|
+
close: () => Promise<void>;
|
|
14
|
+
}>;
|
|
15
|
+
export type NodeWorkerSession = Awaited<ReturnType<typeof openNodeWorkerSession>>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ProvisioningPlan } from '../planning/plan.js';
|
|
2
|
+
import type { ApplyResult, DeploymentJournal, BrokerManagementPort, DeploymentAuthorization } from './ports.js';
|
|
3
|
+
export interface ApplyRequest {
|
|
4
|
+
readonly manifest: unknown;
|
|
5
|
+
readonly journal?: DeploymentJournal;
|
|
6
|
+
readonly plan: ProvisioningPlan;
|
|
7
|
+
readonly authorization: DeploymentAuthorization;
|
|
8
|
+
readonly broker: BrokerManagementPort;
|
|
9
|
+
readonly signal: AbortSignal;
|
|
10
|
+
}
|
|
11
|
+
export declare function applyProvisioning(input: ApplyRequest): Promise<ApplyResult>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ResourceConfig, ResourceSnapshot } from '../manifest/types.js';
|
|
2
|
+
export interface DeploymentAuthorization {
|
|
3
|
+
readonly role: 'deployment';
|
|
4
|
+
readonly subject: string;
|
|
5
|
+
readonly deploymentDomainRef: string;
|
|
6
|
+
readonly allowedResourceKeys: readonly string[];
|
|
7
|
+
readonly allowedSubjects: readonly string[];
|
|
8
|
+
}
|
|
9
|
+
export interface ManagementLease {
|
|
10
|
+
readonly generation: string;
|
|
11
|
+
release(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export interface BrokerReadPort {
|
|
14
|
+
read(signal: AbortSignal): Promise<readonly ResourceSnapshot[]>;
|
|
15
|
+
}
|
|
16
|
+
export interface BrokerManagementPort extends BrokerReadPort {
|
|
17
|
+
read(signal: AbortSignal): Promise<readonly ResourceSnapshot[]>;
|
|
18
|
+
acquireLease(domain: string, signal: AbortSignal): Promise<ManagementLease>;
|
|
19
|
+
create(config: ResourceConfig, lease: ManagementLease, signal: AbortSignal): Promise<void>;
|
|
20
|
+
update(config: ResourceConfig, expectedDigest: string, lease: ManagementLease, signal: AbortSignal): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
export interface OperationReceipt {
|
|
23
|
+
readonly resourceKey: string;
|
|
24
|
+
readonly action: string;
|
|
25
|
+
readonly outcome: 'VERIFIED' | 'UNKNOWN' | 'BLOCKED';
|
|
26
|
+
readonly reason: string;
|
|
27
|
+
readonly observedDigest?: string;
|
|
28
|
+
readonly nativeConfigDigest?: string;
|
|
29
|
+
readonly journalPersisted?: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface ApplyResult {
|
|
32
|
+
readonly planRef: string;
|
|
33
|
+
readonly state: 'VERIFIED' | 'PARTIAL' | 'BLOCKED';
|
|
34
|
+
readonly receipts: readonly OperationReceipt[];
|
|
35
|
+
readonly leaseReleased?: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface DeploymentJournal {
|
|
38
|
+
append(entry: {
|
|
39
|
+
readonly planRef: string;
|
|
40
|
+
readonly resourceKey: string;
|
|
41
|
+
readonly phase: 'INTENT' | 'RESULT';
|
|
42
|
+
readonly receipt?: OperationReceipt;
|
|
43
|
+
}, signal: AbortSignal): Promise<void>;
|
|
44
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ProvisioningPlan } from '../planning/plan.js';
|
|
2
|
+
import type { BrokerReadPort } from './ports.js';
|
|
3
|
+
export declare function verifyProvisioning(manifest: unknown, broker: BrokerReadPort, signal: AbortSignal): Promise<{
|
|
4
|
+
verified: boolean;
|
|
5
|
+
plan: ProvisioningPlan;
|
|
6
|
+
}>;
|
|
7
|
+
export declare function provisioningStatus(broker: BrokerReadPort, signal: AbortSignal): Promise<readonly import("../index.js").ResourceSnapshot[]>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface CliArguments {
|
|
2
|
+
readonly command: 'validate' | 'plan' | 'apply' | 'verify' | 'status' | 'conformance';
|
|
3
|
+
readonly manifestPath?: string;
|
|
4
|
+
readonly snapshotPath?: string;
|
|
5
|
+
readonly adapterPath?: string;
|
|
6
|
+
readonly planPath?: string;
|
|
7
|
+
readonly timeoutMs: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function parseArguments(args: readonly string[]): CliArguments;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CapabilityManifest } from '../manifest/types.js';
|
|
2
|
+
import type { CliArguments } from './arguments.js';
|
|
3
|
+
export interface CliResult {
|
|
4
|
+
readonly value: unknown;
|
|
5
|
+
readonly exitCode: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function runLiveCommand(args: CliArguments, manifest: CapabilityManifest | undefined, signal: AbortSignal): Promise<CliResult>;
|