@c7-digital/ledger 0.0.2
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/BUILD.md +125 -0
- package/LICENSE +17 -0
- package/README.md +97 -0
- package/lib/scripts/build.d.ts +9 -0
- package/lib/scripts/build.js +366 -0
- package/lib/scripts/enhance-types.d.ts +3 -0
- package/lib/scripts/enhance-types.js +174 -0
- package/lib/scripts/generate-asyncapi-types.d.ts +9 -0
- package/lib/scripts/generate-asyncapi-types.js +129 -0
- package/lib/scripts/generate-schema-modules.d.ts +3 -0
- package/lib/scripts/generate-schema-modules.js +33 -0
- package/lib/src/TemplateEmitterMap.d.ts +40 -0
- package/lib/src/TemplateEmitterMap.js +57 -0
- package/lib/src/client.d.ts +55 -0
- package/lib/src/client.js +62 -0
- package/lib/src/generated/api.d.ts +7147 -0
- package/lib/src/generated/api.js +1 -0
- package/lib/src/generated/async-api.d.ts +1665 -0
- package/lib/src/generated/async-api.js +1 -0
- package/lib/src/generated/asyncapi-schema.d.ts +1 -0
- package/lib/src/generated/asyncapi-schema.js +2233 -0
- package/lib/src/generated/openapi-schema.d.ts +1 -0
- package/lib/src/generated/openapi-schema.js +7321 -0
- package/lib/src/generated/sdk-version.d.ts +1 -0
- package/lib/src/generated/sdk-version.js +3 -0
- package/lib/src/index.d.ts +8 -0
- package/lib/src/index.js +8 -0
- package/lib/src/ledger.d.ts +188 -0
- package/lib/src/ledger.js +849 -0
- package/lib/src/ledger.test.d.ts +1 -0
- package/lib/src/ledger.test.js +23 -0
- package/lib/src/logger.d.ts +41 -0
- package/lib/src/logger.js +56 -0
- package/lib/src/multistream.d.ts +47 -0
- package/lib/src/multistream.js +123 -0
- package/lib/src/translate.d.ts +5 -0
- package/lib/src/translate.js +30 -0
- package/lib/src/types.d.ts +201 -0
- package/lib/src/types.js +1 -0
- package/lib/src/util.d.ts +3 -0
- package/lib/src/util.js +7 -0
- package/lib/src/validation.d.ts +27 -0
- package/lib/src/validation.js +182 -0
- package/lib/src/valueTypes.d.ts +34 -0
- package/lib/src/valueTypes.js +76 -0
- package/lib/src/websocket.d.ts +69 -0
- package/lib/src/websocket.js +125 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/lib-lite/scripts/build.d.ts +9 -0
- package/lib-lite/scripts/build.js +366 -0
- package/lib-lite/scripts/enhance-types.d.ts +3 -0
- package/lib-lite/scripts/enhance-types.js +174 -0
- package/lib-lite/scripts/generate-asyncapi-types.d.ts +9 -0
- package/lib-lite/scripts/generate-asyncapi-types.js +129 -0
- package/lib-lite/scripts/generate-schema-modules.d.ts +3 -0
- package/lib-lite/scripts/generate-schema-modules.js +33 -0
- package/lib-lite/src/TemplateEmitterMap.d.ts +40 -0
- package/lib-lite/src/TemplateEmitterMap.js +57 -0
- package/lib-lite/src/client.d.ts +55 -0
- package/lib-lite/src/client.js +62 -0
- package/lib-lite/src/generated/api.d.ts +7147 -0
- package/lib-lite/src/generated/api.js +1 -0
- package/lib-lite/src/generated/async-api.d.ts +1665 -0
- package/lib-lite/src/generated/async-api.js +1 -0
- package/lib-lite/src/generated/asyncapi-schema.d.ts +1 -0
- package/lib-lite/src/generated/asyncapi-schema.js +2 -0
- package/lib-lite/src/generated/openapi-schema.d.ts +1 -0
- package/lib-lite/src/generated/openapi-schema.js +2 -0
- package/lib-lite/src/generated/sdk-version.d.ts +1 -0
- package/lib-lite/src/generated/sdk-version.js +3 -0
- package/lib-lite/src/index.d.ts +8 -0
- package/lib-lite/src/index.js +8 -0
- package/lib-lite/src/ledger.d.ts +188 -0
- package/lib-lite/src/ledger.js +849 -0
- package/lib-lite/src/ledger.test.d.ts +1 -0
- package/lib-lite/src/ledger.test.js +23 -0
- package/lib-lite/src/logger.d.ts +41 -0
- package/lib-lite/src/logger.js +56 -0
- package/lib-lite/src/multistream.d.ts +47 -0
- package/lib-lite/src/multistream.js +123 -0
- package/lib-lite/src/translate.d.ts +5 -0
- package/lib-lite/src/translate.js +30 -0
- package/lib-lite/src/types.d.ts +201 -0
- package/lib-lite/src/types.js +1 -0
- package/lib-lite/src/util.d.ts +3 -0
- package/lib-lite/src/util.js +7 -0
- package/lib-lite/src/validation.d.ts +14 -0
- package/lib-lite/src/validation.js +31 -0
- package/lib-lite/src/valueTypes.d.ts +34 -0
- package/lib-lite/src/valueTypes.js +76 -0
- package/lib-lite/src/websocket.d.ts +69 -0
- package/lib-lite/src/websocket.js +125 -0
- package/lib-lite/tsconfig.temp.tsbuildinfo +1 -0
- package/package.json +72 -0
- package/scripts/build.ts +456 -0
- package/scripts/enhance-types.ts +223 -0
- package/scripts/generate-asyncapi-types.ts +158 -0
- package/scripts/generate-schema-modules.ts +52 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime validation utilities for API responses using JSON Schema validation
|
|
3
|
+
*
|
|
4
|
+
* Uses embedded schema strings instead of file system access for browser compatibility.
|
|
5
|
+
*/
|
|
6
|
+
import * as Ajv from "ajv";
|
|
7
|
+
import { logger } from "./logger.js";
|
|
8
|
+
export class SchemaValidator {
|
|
9
|
+
constructor(schemaType = "openapi", validation) {
|
|
10
|
+
this.schemaCount = 0;
|
|
11
|
+
this.instanceId = Math.random().toString(36).substring(2, 8);
|
|
12
|
+
this.validation = validation;
|
|
13
|
+
if (!this.validation) {
|
|
14
|
+
logger.debug(`Validation disabled for instance ${this.instanceId} - schemas not loaded (validation mode: undefined)`);
|
|
15
|
+
this.schemaReady = Promise.resolve();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
this.ajv = new Ajv.default({
|
|
19
|
+
allErrors: true,
|
|
20
|
+
verbose: true,
|
|
21
|
+
addUsedSchema: false, // Don't add schemas automatically
|
|
22
|
+
});
|
|
23
|
+
logger.log(`Creating SchemaValidator instance ${this.instanceId} for ${schemaType} schema`);
|
|
24
|
+
// Add support for Canton/OpenAPI formats
|
|
25
|
+
this.addCustomFormats();
|
|
26
|
+
// Load schemas asynchronously to enable tree shaking
|
|
27
|
+
this.schemaReady = this.loadSchemasAsync(schemaType);
|
|
28
|
+
}
|
|
29
|
+
async loadSchemasAsync(schemaType) {
|
|
30
|
+
try {
|
|
31
|
+
switch (schemaType) {
|
|
32
|
+
case "openapi":
|
|
33
|
+
const { OPENAPI_SCHEMA } = await import("./generated/openapi-schema.js");
|
|
34
|
+
await this.loadSchemasFromContent(OPENAPI_SCHEMA);
|
|
35
|
+
break;
|
|
36
|
+
case "asyncapi":
|
|
37
|
+
const { ASYNCAPI_SCHEMA } = await import("./generated/asyncapi-schema.js");
|
|
38
|
+
await this.loadSchemasFromContent(ASYNCAPI_SCHEMA);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
logger.warn(` Failed to load ${schemaType} schema for validation:`, error);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Add custom format validators for Canton API types
|
|
48
|
+
*/
|
|
49
|
+
addCustomFormats() {
|
|
50
|
+
if (!this.ajv)
|
|
51
|
+
return;
|
|
52
|
+
// int64 format - just validate it's a valid number or string that can be parsed as number
|
|
53
|
+
this.ajv.addFormat("int64", {
|
|
54
|
+
type: "number",
|
|
55
|
+
validate: (data) => {
|
|
56
|
+
if (typeof data === "number") {
|
|
57
|
+
return Number.isInteger(data);
|
|
58
|
+
}
|
|
59
|
+
if (typeof data === "string") {
|
|
60
|
+
const num = parseInt(data, 10);
|
|
61
|
+
return !isNaN(num) && num.toString() === data;
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
// int32 format
|
|
67
|
+
this.ajv.addFormat("int32", {
|
|
68
|
+
type: "number",
|
|
69
|
+
validate: (data) => {
|
|
70
|
+
if (typeof data === "number") {
|
|
71
|
+
return Number.isInteger(data) && data >= -2147483648 && data <= 2147483647;
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
// date-time format (if not already supported)
|
|
77
|
+
this.ajv.addFormat("date-time", {
|
|
78
|
+
type: "string",
|
|
79
|
+
validate: (dateTimeString) => {
|
|
80
|
+
if (typeof dateTimeString !== "string")
|
|
81
|
+
return false;
|
|
82
|
+
return !isNaN(Date.parse(dateTimeString));
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Helper function to handle errors based on the validation mode
|
|
88
|
+
*/
|
|
89
|
+
handleError(message, error) {
|
|
90
|
+
if (this.validation === "logErrors") {
|
|
91
|
+
logger.warn(`Warning: ${message}`, error);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
throw error instanceof Error ? error : new Error(message);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async loadSchemasFromContent(yamlContent) {
|
|
99
|
+
if (!this.ajv)
|
|
100
|
+
return;
|
|
101
|
+
try {
|
|
102
|
+
// Dynamically import yaml only when needed for tree-shaking
|
|
103
|
+
const yaml = await import("yaml");
|
|
104
|
+
const spec = yaml.parse(yamlContent);
|
|
105
|
+
// Load the complete schema document first to enable reference resolution
|
|
106
|
+
if (spec?.components?.schemas) {
|
|
107
|
+
// Add the full schema document with an ID so references can be resolved
|
|
108
|
+
this.ajv.addSchema(spec, "#");
|
|
109
|
+
logger.debug(`Added full schema document with ID "#"`);
|
|
110
|
+
// Then add individual schemas for direct validation with full reference paths
|
|
111
|
+
for (const [schemaName, schemaDefinition] of Object.entries(spec.components.schemas)) {
|
|
112
|
+
const fullSchemaPath = `#/components/schemas/${schemaName}`;
|
|
113
|
+
this.ajv.addSchema(schemaDefinition, fullSchemaPath);
|
|
114
|
+
logger.debug(`Added schema: "${fullSchemaPath}"`);
|
|
115
|
+
this.schemaCount++;
|
|
116
|
+
}
|
|
117
|
+
// Debug: Show schema count
|
|
118
|
+
logger.debug(`Instance ${this.instanceId} - Loaded ${this.schemaCount} schemas`);
|
|
119
|
+
}
|
|
120
|
+
logger.debug(`Loaded ${this.schemaCount} JSON schemas from embedded content`);
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
this.handleError(`Could not load schemas from embedded content:`, error);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Validate an array of items against a schema
|
|
128
|
+
*/
|
|
129
|
+
async validateArraySchema(data, itemSchemaName) {
|
|
130
|
+
// If validation is disabled, just return data as-is
|
|
131
|
+
if (!this.validation) {
|
|
132
|
+
return data;
|
|
133
|
+
}
|
|
134
|
+
// Wait for schema to be loaded
|
|
135
|
+
await this.schemaReady;
|
|
136
|
+
if (!Array.isArray(data)) {
|
|
137
|
+
this.handleError(`Expected array but got ${typeof data}`);
|
|
138
|
+
return data;
|
|
139
|
+
}
|
|
140
|
+
// Validate each item in the array
|
|
141
|
+
for (let i = 0; i < data.length; i++) {
|
|
142
|
+
await this.validateSchema(data[i], itemSchemaName);
|
|
143
|
+
}
|
|
144
|
+
return data;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Validate data against a schema and return typed result
|
|
148
|
+
*/
|
|
149
|
+
async validateSchema(data, schemaName) {
|
|
150
|
+
// If validation is disabled, just return data as-is
|
|
151
|
+
if (!this.validation) {
|
|
152
|
+
return data;
|
|
153
|
+
}
|
|
154
|
+
// Wait for schema to be loaded
|
|
155
|
+
await this.schemaReady;
|
|
156
|
+
if (!this.ajv) {
|
|
157
|
+
this.handleError(`Validation not available - ajv not initialized`);
|
|
158
|
+
return data;
|
|
159
|
+
}
|
|
160
|
+
logger.debug(`🔍 Instance ${this.instanceId} - Attempting to validate against schema: "${schemaName}",`, { data });
|
|
161
|
+
try {
|
|
162
|
+
// Check if schema exists
|
|
163
|
+
const schema = this.ajv.getSchema(schemaName);
|
|
164
|
+
if (!schema) {
|
|
165
|
+
logger.warn(`Schema "${schemaName}" not found in AJV`);
|
|
166
|
+
this.handleError(`Schema "${schemaName}" not found`);
|
|
167
|
+
return data;
|
|
168
|
+
}
|
|
169
|
+
const isValid = this.ajv.validate(schemaName, data);
|
|
170
|
+
if (!isValid) {
|
|
171
|
+
this.handleError(`Schema validation failed for ${schemaName}:`, new Error(`Schema validation failed for ${schemaName}: ${this.ajv.errorsText()}`));
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
logger.debug(`Validation successful for "${schemaName}"`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
this.handleError(`Validation error for ${schemaName}: ${error}`);
|
|
179
|
+
}
|
|
180
|
+
return data;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
declare const __brand: unique symbol;
|
|
2
|
+
type Brand<T, TBrand> = T & {
|
|
3
|
+
[__brand]: TBrand;
|
|
4
|
+
};
|
|
5
|
+
export type NameString = Brand<string, "NameString">;
|
|
6
|
+
export type PackageIdString = Brand<string, "PackageIdString">;
|
|
7
|
+
export type PartyIdString = Brand<string, "PartyIdString">;
|
|
8
|
+
export type LedgerString = Brand<string, "LedgerString">;
|
|
9
|
+
export type UserIdString = Brand<string, "UserIdString">;
|
|
10
|
+
export declare function isValidNameString(value: string): value is NameString;
|
|
11
|
+
export declare function isValidPackageIdString(value: string): value is PackageIdString;
|
|
12
|
+
export declare function isValidPartyIdString(value: string): value is PartyIdString;
|
|
13
|
+
export declare function isValidLedgerString(value: string): value is LedgerString;
|
|
14
|
+
export declare function isValidUserIdString(value: string): value is UserIdString;
|
|
15
|
+
export declare function createNameString(value: string): NameString;
|
|
16
|
+
export declare function createPackageIdString(value: string): PackageIdString;
|
|
17
|
+
export declare function createPartyIdString(value: string): PartyIdString;
|
|
18
|
+
export declare function createLedgerString(value: string): LedgerString;
|
|
19
|
+
export declare function createUserIdString(value: string): UserIdString;
|
|
20
|
+
export declare const ValueStringValidators: {
|
|
21
|
+
readonly isValidNameString: typeof isValidNameString;
|
|
22
|
+
readonly isValidPackageIdString: typeof isValidPackageIdString;
|
|
23
|
+
readonly isValidPartyIdString: typeof isValidPartyIdString;
|
|
24
|
+
readonly isValidLedgerString: typeof isValidLedgerString;
|
|
25
|
+
readonly isValidUserIdString: typeof isValidUserIdString;
|
|
26
|
+
};
|
|
27
|
+
export declare const ValueStringCreators: {
|
|
28
|
+
readonly createNameString: typeof createNameString;
|
|
29
|
+
readonly createPackageIdString: typeof createPackageIdString;
|
|
30
|
+
readonly createPartyIdString: typeof createPartyIdString;
|
|
31
|
+
readonly createLedgerString: typeof createLedgerString;
|
|
32
|
+
readonly createUserIdString: typeof createUserIdString;
|
|
33
|
+
};
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Branded string types based on value.proto from Daml gRPC API
|
|
2
|
+
// Source: https://github.com/digital-asset/daml/blob/main/sdk/daml-lf/ledger-api-value/src/main/protobuf/com/daml/ledger/api/v2/value.proto
|
|
3
|
+
// Validation patterns from value.proto
|
|
4
|
+
const NAME_STRING_PATTERN = /^[A-Za-z\$_][A-Za-z0-9\$_]*$/;
|
|
5
|
+
const PACKAGE_ID_STRING_PATTERN = /^[A-Za-z0-9\-_ ]+$/;
|
|
6
|
+
const PARTY_ID_STRING_PATTERN = /^[A-Za-z0-9:\-_ ]+$/;
|
|
7
|
+
const LEDGER_STRING_PATTERN = /^[A-Za-z0-9#:\-_/ ]+$/;
|
|
8
|
+
const USER_ID_STRING_PATTERN = /^[a-zA-Z0-9@^$.!\`\-#+'~_|:]+$/;
|
|
9
|
+
// Length constraints from value.proto
|
|
10
|
+
const MAX_NAME_STRING_LENGTH = 1000;
|
|
11
|
+
const MAX_PACKAGE_ID_STRING_LENGTH = 64;
|
|
12
|
+
const MAX_PARTY_ID_STRING_LENGTH = 255;
|
|
13
|
+
const MAX_LEDGER_STRING_LENGTH = 255;
|
|
14
|
+
const MAX_USER_ID_STRING_LENGTH = 128;
|
|
15
|
+
// Validation functions
|
|
16
|
+
export function isValidNameString(value) {
|
|
17
|
+
return value.length <= MAX_NAME_STRING_LENGTH && NAME_STRING_PATTERN.test(value);
|
|
18
|
+
}
|
|
19
|
+
export function isValidPackageIdString(value) {
|
|
20
|
+
return value.length <= MAX_PACKAGE_ID_STRING_LENGTH && PACKAGE_ID_STRING_PATTERN.test(value);
|
|
21
|
+
}
|
|
22
|
+
export function isValidPartyIdString(value) {
|
|
23
|
+
return value.length <= MAX_PARTY_ID_STRING_LENGTH && PARTY_ID_STRING_PATTERN.test(value);
|
|
24
|
+
}
|
|
25
|
+
export function isValidLedgerString(value) {
|
|
26
|
+
return value.length <= MAX_LEDGER_STRING_LENGTH && LEDGER_STRING_PATTERN.test(value);
|
|
27
|
+
}
|
|
28
|
+
export function isValidUserIdString(value) {
|
|
29
|
+
return value.length <= MAX_USER_ID_STRING_LENGTH && USER_ID_STRING_PATTERN.test(value);
|
|
30
|
+
}
|
|
31
|
+
// Creation functions with validation
|
|
32
|
+
export function createNameString(value) {
|
|
33
|
+
if (!isValidNameString(value)) {
|
|
34
|
+
throw new Error(`Invalid NameString: "${value}". Must match [A-Za-z\$_][A-Za-z0-9\$_]* and be ≤ ${MAX_NAME_STRING_LENGTH} chars`);
|
|
35
|
+
}
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
export function createPackageIdString(value) {
|
|
39
|
+
if (!isValidPackageIdString(value)) {
|
|
40
|
+
throw new Error(`Invalid PackageIdString: "${value}". Must match [A-Za-z0-9\-_ ]+ and be ≤ ${MAX_PACKAGE_ID_STRING_LENGTH} chars`);
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
export function createPartyIdString(value) {
|
|
45
|
+
if (!isValidPartyIdString(value)) {
|
|
46
|
+
throw new Error(`Invalid PartyIdString: "${value}". Must match [A-Za-z0-9:\-_ ]+ and be ≤ ${MAX_PARTY_ID_STRING_LENGTH} chars`);
|
|
47
|
+
}
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
export function createLedgerString(value) {
|
|
51
|
+
if (!isValidLedgerString(value)) {
|
|
52
|
+
throw new Error(`Invalid LedgerString: "${value}". Must match [A-Za-z0-9#:\-_/ ]+ and be ≤ ${MAX_LEDGER_STRING_LENGTH} chars`);
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
export function createUserIdString(value) {
|
|
57
|
+
if (!isValidUserIdString(value)) {
|
|
58
|
+
throw new Error(`Invalid UserIdString: "${value}". Must match [a-zA-Z0-9@^$.!\`\-#+'~_|:]+ and be ≤ ${MAX_USER_ID_STRING_LENGTH} chars`);
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
// Utility type guards for runtime checking
|
|
63
|
+
export const ValueStringValidators = {
|
|
64
|
+
isValidNameString,
|
|
65
|
+
isValidPackageIdString,
|
|
66
|
+
isValidPartyIdString,
|
|
67
|
+
isValidLedgerString,
|
|
68
|
+
isValidUserIdString,
|
|
69
|
+
};
|
|
70
|
+
export const ValueStringCreators = {
|
|
71
|
+
createNameString,
|
|
72
|
+
createPackageIdString,
|
|
73
|
+
createPartyIdString,
|
|
74
|
+
createLedgerString,
|
|
75
|
+
createUserIdString,
|
|
76
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket client for Canton's AsyncAPI v2 streaming endpoints
|
|
3
|
+
*
|
|
4
|
+
* Provides real-time event streaming for completions, active contracts,
|
|
5
|
+
* and ledger updates using WebSocket connections with proper authentication
|
|
6
|
+
* and typed message handling.
|
|
7
|
+
*/
|
|
8
|
+
import type { channels, components } from "./generated/async-api.js";
|
|
9
|
+
import { ValidationMode } from "./validation.js";
|
|
10
|
+
export interface StreamConfig {
|
|
11
|
+
token: string;
|
|
12
|
+
wsBaseUrl: string;
|
|
13
|
+
validation?: ValidationMode;
|
|
14
|
+
asyncApiSchemaPath?: string;
|
|
15
|
+
}
|
|
16
|
+
type CompletionChannel = channels["/v2/commands/completions"];
|
|
17
|
+
type ActiveContractsChannel = channels["/v2/state/active-contracts"];
|
|
18
|
+
type UpdatesChannel = channels["/v2/updates/flats"];
|
|
19
|
+
export type CompletionStreamRequest = CompletionChannel["publish"]["message"];
|
|
20
|
+
export type CompletionStreamMessage = CompletionChannel["subscribe"]["message"];
|
|
21
|
+
export type ActiveContractsStreamRequest = ActiveContractsChannel["publish"]["message"];
|
|
22
|
+
export type ActiveContractsStreamMessage = ActiveContractsChannel["subscribe"]["message"];
|
|
23
|
+
export type UpdatesStreamRequest = UpdatesChannel["publish"]["message"];
|
|
24
|
+
export type UpdatesStreamMessage = UpdatesChannel["subscribe"]["message"];
|
|
25
|
+
export type JsCantonError = components["schemas"]["JsCantonError"];
|
|
26
|
+
export type Response<T> = {
|
|
27
|
+
status: "success";
|
|
28
|
+
data: T;
|
|
29
|
+
} | {
|
|
30
|
+
status: "error";
|
|
31
|
+
error: JsCantonError;
|
|
32
|
+
};
|
|
33
|
+
export type CompletionResponse = Response<components["schemas"]["CompletionStreamResponse"]>;
|
|
34
|
+
export type ActiveContractsResponse = Response<components["schemas"]["JsGetActiveContractsResponse"]>;
|
|
35
|
+
export type UpdatesResponse = Response<components["schemas"]["JsGetUpdatesResponse"]>;
|
|
36
|
+
export interface StopClient {
|
|
37
|
+
(): void;
|
|
38
|
+
}
|
|
39
|
+
export declare function isCantonError(response: unknown): response is JsCantonError;
|
|
40
|
+
export declare function parseStreamResponse<T>(response: T | JsCantonError): Response<T>;
|
|
41
|
+
export declare function isTransaction(response: unknown): response is {
|
|
42
|
+
Transaction: components["schemas"]["Transaction"];
|
|
43
|
+
};
|
|
44
|
+
export declare function isJsTransaction(response: unknown): response is components["schemas"]["JsTransaction"];
|
|
45
|
+
export declare class WebSocketClient {
|
|
46
|
+
private token;
|
|
47
|
+
private wsBaseUrl;
|
|
48
|
+
private validator?;
|
|
49
|
+
constructor(config: StreamConfig);
|
|
50
|
+
/**
|
|
51
|
+
* Generic streaming method with full type safety
|
|
52
|
+
* This method enforces that request/response types match the endpoint
|
|
53
|
+
*/
|
|
54
|
+
private stream;
|
|
55
|
+
private createWebSocket;
|
|
56
|
+
/**
|
|
57
|
+
* Stream command completions in real-time
|
|
58
|
+
*/
|
|
59
|
+
streamCompletions(request: CompletionStreamRequest, onMessage: (message: CompletionResponse) => void, onError?: (error: Error) => void, onClose?: (code: number, reason: string) => void): StopClient;
|
|
60
|
+
/**
|
|
61
|
+
* Stream active contracts
|
|
62
|
+
*/
|
|
63
|
+
streamActiveContracts(request: ActiveContractsStreamRequest, onMessage: (message: ActiveContractsResponse) => void, onError?: (error: Error) => void, onClose?: (code: number, reason: string) => void): StopClient;
|
|
64
|
+
/**
|
|
65
|
+
* Stream all ledger updates
|
|
66
|
+
*/
|
|
67
|
+
streamUpdates(request: UpdatesStreamRequest, onMessage: (message: UpdatesResponse) => void, onError?: (error: Error) => void, onClose?: (code: number, reason: string) => void): StopClient;
|
|
68
|
+
}
|
|
69
|
+
export {};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { SchemaValidator } from "./validation.js";
|
|
2
|
+
import { logger } from "./logger.js";
|
|
3
|
+
import WebSocket from "isomorphic-ws";
|
|
4
|
+
// Constant mapping from endpoints to their JSON Schema validation paths
|
|
5
|
+
const SCHEMA_MAP = {
|
|
6
|
+
"/v2/commands/completions": "#/components/schemas/Either_JsCantonError_CompletionStreamResponse",
|
|
7
|
+
"/v2/state/active-contracts": "#/components/schemas/Either_JsCantonError_JsGetActiveContractsResponse",
|
|
8
|
+
"/v2/updates": "#/components/schemas/Either_JsCantonError_JsGetUpdatesResponse",
|
|
9
|
+
"/v2/updates/flats": "#/components/schemas/Either_JsCantonError_JsGetUpdatesResponse",
|
|
10
|
+
"/v2/updates/trees": "#/components/schemas/Either_JsCantonError_JsGetUpdateTreesResponse",
|
|
11
|
+
};
|
|
12
|
+
// Type guard to check if a response is a JsCantonError
|
|
13
|
+
export function isCantonError(response) {
|
|
14
|
+
return (typeof response === "object" &&
|
|
15
|
+
response !== null &&
|
|
16
|
+
"code" in response &&
|
|
17
|
+
"cause" in response &&
|
|
18
|
+
"context" in response);
|
|
19
|
+
}
|
|
20
|
+
export function parseStreamResponse(response) {
|
|
21
|
+
if (isCantonError(response)) {
|
|
22
|
+
return { status: "error", error: response };
|
|
23
|
+
}
|
|
24
|
+
return { status: "success", data: response };
|
|
25
|
+
}
|
|
26
|
+
export function isTransaction(response) {
|
|
27
|
+
if (typeof response === "object" &&
|
|
28
|
+
response !== null &&
|
|
29
|
+
"Transaction" in response &&
|
|
30
|
+
typeof response.Transaction === "object" &&
|
|
31
|
+
response.Transaction !== null &&
|
|
32
|
+
"value" in response.Transaction) {
|
|
33
|
+
const transaction = response.Transaction;
|
|
34
|
+
return isJsTransaction(transaction.value);
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
export function isJsTransaction(response) {
|
|
39
|
+
return (typeof response === "object" &&
|
|
40
|
+
response !== null &&
|
|
41
|
+
"updateId" in response &&
|
|
42
|
+
"commandId" in response &&
|
|
43
|
+
"workflowId" in response &&
|
|
44
|
+
"effectiveAt" in response &&
|
|
45
|
+
"offset" in response &&
|
|
46
|
+
"synchronizerId" in response &&
|
|
47
|
+
"recordTime" in response);
|
|
48
|
+
}
|
|
49
|
+
export class WebSocketClient {
|
|
50
|
+
constructor(config) {
|
|
51
|
+
this.token = config.token;
|
|
52
|
+
this.wsBaseUrl = config.wsBaseUrl;
|
|
53
|
+
this.validator = config.validation
|
|
54
|
+
? new SchemaValidator("asyncapi", config.validation)
|
|
55
|
+
: undefined;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Generic streaming method with full type safety
|
|
59
|
+
* This method enforces that request/response types match the endpoint
|
|
60
|
+
*/
|
|
61
|
+
stream(endpoint, request, onMessage, onError, onClose) {
|
|
62
|
+
const ws = this.createWebSocket(endpoint);
|
|
63
|
+
ws.onopen = () => {
|
|
64
|
+
logger.debug(`Opening websocket for ${endpoint}: `, { request });
|
|
65
|
+
ws.send(JSON.stringify(request));
|
|
66
|
+
};
|
|
67
|
+
ws.onmessage = async (event) => {
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(event.data.toString());
|
|
70
|
+
logger.debug(`Received message for ${endpoint}: `, { parsed });
|
|
71
|
+
let message = this.validator
|
|
72
|
+
? await this.validator.validateSchema(parsed, SCHEMA_MAP[endpoint])
|
|
73
|
+
: parsed;
|
|
74
|
+
onMessage(message);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
onError?.(new Error(`Failed to parse message for ${endpoint}: ${error}`));
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
ws.onerror = (event) => {
|
|
81
|
+
const errorMsg = `WebSocket error ${event.type}, message: ${event.message}`;
|
|
82
|
+
onError?.(new Error(`WebSocket error: ${errorMsg}`));
|
|
83
|
+
};
|
|
84
|
+
ws.onclose = (closeEvent) => {
|
|
85
|
+
logger.debug(`WebSocket closed for ${endpoint}: `, {
|
|
86
|
+
code: closeEvent.code,
|
|
87
|
+
reason: closeEvent.reason,
|
|
88
|
+
});
|
|
89
|
+
onClose?.(closeEvent.code, closeEvent.reason);
|
|
90
|
+
};
|
|
91
|
+
return () => {
|
|
92
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
93
|
+
ws.close();
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
createWebSocket(endpoint) {
|
|
98
|
+
const baseUrl = this.wsBaseUrl.endsWith("/") ? this.wsBaseUrl : `${this.wsBaseUrl}/`;
|
|
99
|
+
const url = endpoint.startsWith("/")
|
|
100
|
+
? `${baseUrl}${endpoint.slice(1)}`
|
|
101
|
+
: `${baseUrl}/${endpoint}`;
|
|
102
|
+
// WebSocket authentication via Sec-WebSocket-Protocol header
|
|
103
|
+
const protocols = [`jwt.token.${this.token}`, "daml.ws.auth"];
|
|
104
|
+
logger.debug(`Connecting websocket to ${url} with protocols: ${protocols}`);
|
|
105
|
+
return new WebSocket(url, protocols);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Stream command completions in real-time
|
|
109
|
+
*/
|
|
110
|
+
streamCompletions(request, onMessage, onError, onClose) {
|
|
111
|
+
return this.stream("/v2/commands/completions", request, rawMessage => onMessage(parseStreamResponse(rawMessage)), onError, onClose);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Stream active contracts
|
|
115
|
+
*/
|
|
116
|
+
streamActiveContracts(request, onMessage, onError, onClose) {
|
|
117
|
+
return this.stream("/v2/state/active-contracts", request, rawMessage => onMessage(parseStreamResponse(rawMessage)), onError, onClose);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Stream all ledger updates
|
|
121
|
+
*/
|
|
122
|
+
streamUpdates(request, onMessage, onError, onClose) {
|
|
123
|
+
return this.stream("/v2/updates", request, rawMessage => onMessage(parseStreamResponse(rawMessage)), onError, onClose);
|
|
124
|
+
}
|
|
125
|
+
}
|