@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.
Files changed (98) hide show
  1. package/BUILD.md +125 -0
  2. package/LICENSE +17 -0
  3. package/README.md +97 -0
  4. package/lib/scripts/build.d.ts +9 -0
  5. package/lib/scripts/build.js +366 -0
  6. package/lib/scripts/enhance-types.d.ts +3 -0
  7. package/lib/scripts/enhance-types.js +174 -0
  8. package/lib/scripts/generate-asyncapi-types.d.ts +9 -0
  9. package/lib/scripts/generate-asyncapi-types.js +129 -0
  10. package/lib/scripts/generate-schema-modules.d.ts +3 -0
  11. package/lib/scripts/generate-schema-modules.js +33 -0
  12. package/lib/src/TemplateEmitterMap.d.ts +40 -0
  13. package/lib/src/TemplateEmitterMap.js +57 -0
  14. package/lib/src/client.d.ts +55 -0
  15. package/lib/src/client.js +62 -0
  16. package/lib/src/generated/api.d.ts +7147 -0
  17. package/lib/src/generated/api.js +1 -0
  18. package/lib/src/generated/async-api.d.ts +1665 -0
  19. package/lib/src/generated/async-api.js +1 -0
  20. package/lib/src/generated/asyncapi-schema.d.ts +1 -0
  21. package/lib/src/generated/asyncapi-schema.js +2233 -0
  22. package/lib/src/generated/openapi-schema.d.ts +1 -0
  23. package/lib/src/generated/openapi-schema.js +7321 -0
  24. package/lib/src/generated/sdk-version.d.ts +1 -0
  25. package/lib/src/generated/sdk-version.js +3 -0
  26. package/lib/src/index.d.ts +8 -0
  27. package/lib/src/index.js +8 -0
  28. package/lib/src/ledger.d.ts +188 -0
  29. package/lib/src/ledger.js +849 -0
  30. package/lib/src/ledger.test.d.ts +1 -0
  31. package/lib/src/ledger.test.js +23 -0
  32. package/lib/src/logger.d.ts +41 -0
  33. package/lib/src/logger.js +56 -0
  34. package/lib/src/multistream.d.ts +47 -0
  35. package/lib/src/multistream.js +123 -0
  36. package/lib/src/translate.d.ts +5 -0
  37. package/lib/src/translate.js +30 -0
  38. package/lib/src/types.d.ts +201 -0
  39. package/lib/src/types.js +1 -0
  40. package/lib/src/util.d.ts +3 -0
  41. package/lib/src/util.js +7 -0
  42. package/lib/src/validation.d.ts +27 -0
  43. package/lib/src/validation.js +182 -0
  44. package/lib/src/valueTypes.d.ts +34 -0
  45. package/lib/src/valueTypes.js +76 -0
  46. package/lib/src/websocket.d.ts +69 -0
  47. package/lib/src/websocket.js +125 -0
  48. package/lib/tsconfig.tsbuildinfo +1 -0
  49. package/lib-lite/scripts/build.d.ts +9 -0
  50. package/lib-lite/scripts/build.js +366 -0
  51. package/lib-lite/scripts/enhance-types.d.ts +3 -0
  52. package/lib-lite/scripts/enhance-types.js +174 -0
  53. package/lib-lite/scripts/generate-asyncapi-types.d.ts +9 -0
  54. package/lib-lite/scripts/generate-asyncapi-types.js +129 -0
  55. package/lib-lite/scripts/generate-schema-modules.d.ts +3 -0
  56. package/lib-lite/scripts/generate-schema-modules.js +33 -0
  57. package/lib-lite/src/TemplateEmitterMap.d.ts +40 -0
  58. package/lib-lite/src/TemplateEmitterMap.js +57 -0
  59. package/lib-lite/src/client.d.ts +55 -0
  60. package/lib-lite/src/client.js +62 -0
  61. package/lib-lite/src/generated/api.d.ts +7147 -0
  62. package/lib-lite/src/generated/api.js +1 -0
  63. package/lib-lite/src/generated/async-api.d.ts +1665 -0
  64. package/lib-lite/src/generated/async-api.js +1 -0
  65. package/lib-lite/src/generated/asyncapi-schema.d.ts +1 -0
  66. package/lib-lite/src/generated/asyncapi-schema.js +2 -0
  67. package/lib-lite/src/generated/openapi-schema.d.ts +1 -0
  68. package/lib-lite/src/generated/openapi-schema.js +2 -0
  69. package/lib-lite/src/generated/sdk-version.d.ts +1 -0
  70. package/lib-lite/src/generated/sdk-version.js +3 -0
  71. package/lib-lite/src/index.d.ts +8 -0
  72. package/lib-lite/src/index.js +8 -0
  73. package/lib-lite/src/ledger.d.ts +188 -0
  74. package/lib-lite/src/ledger.js +849 -0
  75. package/lib-lite/src/ledger.test.d.ts +1 -0
  76. package/lib-lite/src/ledger.test.js +23 -0
  77. package/lib-lite/src/logger.d.ts +41 -0
  78. package/lib-lite/src/logger.js +56 -0
  79. package/lib-lite/src/multistream.d.ts +47 -0
  80. package/lib-lite/src/multistream.js +123 -0
  81. package/lib-lite/src/translate.d.ts +5 -0
  82. package/lib-lite/src/translate.js +30 -0
  83. package/lib-lite/src/types.d.ts +201 -0
  84. package/lib-lite/src/types.js +1 -0
  85. package/lib-lite/src/util.d.ts +3 -0
  86. package/lib-lite/src/util.js +7 -0
  87. package/lib-lite/src/validation.d.ts +14 -0
  88. package/lib-lite/src/validation.js +31 -0
  89. package/lib-lite/src/valueTypes.d.ts +34 -0
  90. package/lib-lite/src/valueTypes.js +76 -0
  91. package/lib-lite/src/websocket.d.ts +69 -0
  92. package/lib-lite/src/websocket.js +125 -0
  93. package/lib-lite/tsconfig.temp.tsbuildinfo +1 -0
  94. package/package.json +72 -0
  95. package/scripts/build.ts +456 -0
  96. package/scripts/enhance-types.ts +223 -0
  97. package/scripts/generate-asyncapi-types.ts +158 -0
  98. 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
+ }