cantonjs 0.2.0 → 0.3.0

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 (117) hide show
  1. package/README.md +149 -59
  2. package/dist/cjs/auth/index.d.ts +2 -0
  3. package/dist/cjs/auth/index.d.ts.map +1 -0
  4. package/dist/cjs/auth/index.js +3 -0
  5. package/dist/cjs/auth/index.js.map +1 -0
  6. package/dist/cjs/auth/types.d.ts +27 -0
  7. package/dist/cjs/auth/types.d.ts.map +1 -0
  8. package/dist/cjs/auth/types.js +3 -0
  9. package/dist/cjs/auth/types.js.map +1 -0
  10. package/dist/cjs/chains/definitions.d.ts +5 -11
  11. package/dist/cjs/chains/definitions.d.ts.map +1 -1
  12. package/dist/cjs/chains/definitions.js +59 -13
  13. package/dist/cjs/chains/definitions.js.map +1 -1
  14. package/dist/cjs/chains/index.d.ts +2 -1
  15. package/dist/cjs/chains/index.d.ts.map +1 -1
  16. package/dist/cjs/chains/index.js +19 -6
  17. package/dist/cjs/chains/index.js.map +1 -1
  18. package/dist/cjs/chains/presets.d.ts +59 -0
  19. package/dist/cjs/chains/presets.d.ts.map +1 -0
  20. package/dist/cjs/chains/presets.js +55 -0
  21. package/dist/cjs/chains/presets.js.map +1 -0
  22. package/dist/cjs/errors/auth.d.ts +6 -0
  23. package/dist/cjs/errors/auth.d.ts.map +1 -1
  24. package/dist/cjs/errors/auth.js +17 -1
  25. package/dist/cjs/errors/auth.js.map +1 -1
  26. package/dist/cjs/errors/index.d.ts +1 -1
  27. package/dist/cjs/errors/index.d.ts.map +1 -1
  28. package/dist/cjs/errors/index.js +2 -1
  29. package/dist/cjs/errors/index.js.map +1 -1
  30. package/dist/cjs/index.d.ts +5 -4
  31. package/dist/cjs/index.d.ts.map +1 -1
  32. package/dist/cjs/index.js +9 -6
  33. package/dist/cjs/index.js.map +1 -1
  34. package/dist/cjs/testing/setupSandbox.d.ts +11 -1
  35. package/dist/cjs/testing/setupSandbox.d.ts.map +1 -1
  36. package/dist/cjs/testing/setupSandbox.js +25 -13
  37. package/dist/cjs/testing/setupSandbox.js.map +1 -1
  38. package/dist/cjs/transport/grpc.d.ts +2 -4
  39. package/dist/cjs/transport/grpc.d.ts.map +1 -1
  40. package/dist/cjs/transport/grpc.js +28 -16
  41. package/dist/cjs/transport/grpc.js.map +1 -1
  42. package/dist/cjs/transport/index.d.ts +1 -1
  43. package/dist/cjs/transport/index.d.ts.map +1 -1
  44. package/dist/cjs/transport/json-api.d.ts +1 -1
  45. package/dist/cjs/transport/json-api.d.ts.map +1 -1
  46. package/dist/cjs/transport/json-api.js +19 -5
  47. package/dist/cjs/transport/json-api.js.map +1 -1
  48. package/dist/cjs/transport/types.d.ts +13 -3
  49. package/dist/cjs/transport/types.d.ts.map +1 -1
  50. package/dist/cjs/transport/types.js +40 -0
  51. package/dist/cjs/transport/types.js.map +1 -1
  52. package/dist/esm/auth/index.d.ts +2 -0
  53. package/dist/esm/auth/index.d.ts.map +1 -0
  54. package/dist/esm/auth/index.js +2 -0
  55. package/dist/esm/auth/index.js.map +1 -0
  56. package/dist/esm/auth/types.d.ts +27 -0
  57. package/dist/esm/auth/types.d.ts.map +1 -0
  58. package/dist/esm/auth/types.js +2 -0
  59. package/dist/esm/auth/types.js.map +1 -0
  60. package/dist/esm/chains/definitions.d.ts +5 -11
  61. package/dist/esm/chains/definitions.d.ts.map +1 -1
  62. package/dist/esm/chains/definitions.js +59 -13
  63. package/dist/esm/chains/definitions.js.map +1 -1
  64. package/dist/esm/chains/index.d.ts +2 -1
  65. package/dist/esm/chains/index.d.ts.map +1 -1
  66. package/dist/esm/chains/index.js +2 -1
  67. package/dist/esm/chains/index.js.map +1 -1
  68. package/dist/esm/chains/presets.d.ts +59 -0
  69. package/dist/esm/chains/presets.d.ts.map +1 -0
  70. package/dist/esm/chains/presets.js +51 -0
  71. package/dist/esm/chains/presets.js.map +1 -0
  72. package/dist/esm/errors/auth.d.ts +6 -0
  73. package/dist/esm/errors/auth.d.ts.map +1 -1
  74. package/dist/esm/errors/auth.js +15 -0
  75. package/dist/esm/errors/auth.js.map +1 -1
  76. package/dist/esm/errors/index.d.ts +1 -1
  77. package/dist/esm/errors/index.d.ts.map +1 -1
  78. package/dist/esm/errors/index.js +1 -1
  79. package/dist/esm/errors/index.js.map +1 -1
  80. package/dist/esm/index.d.ts +5 -4
  81. package/dist/esm/index.d.ts.map +1 -1
  82. package/dist/esm/index.js +2 -2
  83. package/dist/esm/index.js.map +1 -1
  84. package/dist/esm/testing/setupSandbox.d.ts +11 -1
  85. package/dist/esm/testing/setupSandbox.d.ts.map +1 -1
  86. package/dist/esm/testing/setupSandbox.js +25 -14
  87. package/dist/esm/testing/setupSandbox.js.map +1 -1
  88. package/dist/esm/transport/grpc.d.ts +2 -4
  89. package/dist/esm/transport/grpc.d.ts.map +1 -1
  90. package/dist/esm/transport/grpc.js +28 -16
  91. package/dist/esm/transport/grpc.js.map +1 -1
  92. package/dist/esm/transport/index.d.ts +1 -1
  93. package/dist/esm/transport/index.d.ts.map +1 -1
  94. package/dist/esm/transport/json-api.d.ts +1 -1
  95. package/dist/esm/transport/json-api.d.ts.map +1 -1
  96. package/dist/esm/transport/json-api.js +19 -5
  97. package/dist/esm/transport/json-api.js.map +1 -1
  98. package/dist/esm/transport/types.d.ts +13 -3
  99. package/dist/esm/transport/types.d.ts.map +1 -1
  100. package/dist/esm/transport/types.js +38 -1
  101. package/dist/esm/transport/types.js.map +1 -1
  102. package/dist/tsconfig.build.tsbuildinfo +1 -1
  103. package/dist/tsconfig.cjs.tsbuildinfo +1 -1
  104. package/package.json +12 -2
  105. package/src/auth/index.ts +7 -0
  106. package/src/auth/types.ts +30 -0
  107. package/src/chains/definitions.ts +77 -22
  108. package/src/chains/index.ts +2 -1
  109. package/src/chains/presets.ts +121 -0
  110. package/src/errors/auth.ts +16 -0
  111. package/src/errors/index.ts +1 -1
  112. package/src/index.ts +34 -7
  113. package/src/testing/setupSandbox.ts +45 -17
  114. package/src/transport/grpc.ts +33 -19
  115. package/src/transport/index.ts +7 -1
  116. package/src/transport/json-api.ts +24 -7
  117. package/src/transport/types.ts +63 -3
@@ -19,9 +19,10 @@
19
19
  * ```
20
20
  */
21
21
 
22
+ import type { AuthProvider, SessionProvider } from '../auth/types.js'
22
23
  import { jsonApi } from '../transport/json-api.js'
23
24
  import { createTestClient, type TestClient } from '../clients/createTestClient.js'
24
- import type { Transport } from '../transport/types.js'
25
+ import type { Transport, TransportConfig } from '../transport/types.js'
25
26
  import type { Party } from '../types/party.js'
26
27
 
27
28
  /** Configuration for sandbox setup. */
@@ -32,7 +33,11 @@ export type SandboxConfig = {
32
33
  readonly timeout?: number
33
34
  /** JWT token. If not provided, attempts to use cantonctl auth. */
34
35
  readonly token?: string
35
- /** Party for the TestClient. Default: allocated via sandbox. */
36
+ /** Per-request bearer token provider for participant calls. */
37
+ readonly auth?: AuthProvider
38
+ /** Per-request session provider for participant calls. */
39
+ readonly session?: SessionProvider
40
+ /** Party for the TestClient. Default: `test-party`. */
36
41
  readonly party?: Party
37
42
  /** Custom fetch implementation (for testing the fixture itself). */
38
43
  readonly fetchFn?: typeof fetch
@@ -53,12 +58,10 @@ export type SandboxContext = {
53
58
  }
54
59
 
55
60
  /** Default exec using child_process (Node.js only). */
56
- async function defaultExec(cmd: string): Promise<{ stdout: string; stderr: string }> {
57
- // Dynamic import to avoid bundling Node.js modules in browser builds
58
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
59
- const cp = await (Function('return import("node:child_process")')() as Promise<any>)
60
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
- const util = await (Function('return import("node:util")')() as Promise<any>)
61
+ export async function defaultExec(cmd: string): Promise<{ stdout: string; stderr: string }> {
62
+ // Dynamic import keeps Node-only modules out of browser-oriented consumers.
63
+ const cp = await import('node:child_process')
64
+ const util = await import('node:util')
62
65
  const execAsync = util.promisify(cp.exec)
63
66
  return execAsync(cmd) as Promise<{ stdout: string; stderr: string }>
64
67
  }
@@ -99,6 +102,37 @@ async function waitForHealth(
99
102
  )
100
103
  }
101
104
 
105
+ async function resolveSandboxTransportConfig(
106
+ url: string,
107
+ fetchFn: typeof fetch,
108
+ execFn: (cmd: string) => Promise<{ stdout: string; stderr: string }>,
109
+ config: SandboxConfig,
110
+ ): Promise<TransportConfig> {
111
+ const token = config.token?.trim() === '' ? undefined : config.token?.trim()
112
+
113
+ const transportConfig = {
114
+ url,
115
+ fetchFn,
116
+ token,
117
+ auth: config.auth,
118
+ session: config.session,
119
+ } satisfies TransportConfig
120
+
121
+ if (
122
+ transportConfig.token === undefined &&
123
+ transportConfig.auth === undefined &&
124
+ transportConfig.session === undefined
125
+ ) {
126
+ const result = await execFn('cantonctl auth token')
127
+ return {
128
+ ...transportConfig,
129
+ token: result.stdout.trim(),
130
+ }
131
+ }
132
+
133
+ return transportConfig
134
+ }
135
+
102
136
  /**
103
137
  * Set up a Canton sandbox for integration testing.
104
138
  *
@@ -135,15 +169,9 @@ export async function setupCantonSandbox(
135
169
  // Wait for health
136
170
  await waitForHealth(url, timeout, fetchFn)
137
171
 
138
- // Get or generate token
139
- let token = config.token
140
- if (!token) {
141
- const result = await execFn('cantonctl auth token')
142
- token = result.stdout.trim()
143
- }
144
-
145
- // Create transport and client
146
- const transport = jsonApi({ url, token, fetchFn })
172
+ const transport = jsonApi(
173
+ await resolveSandboxTransportConfig(url, fetchFn, execFn, config),
174
+ )
147
175
 
148
176
  const party = config.party ?? ('test-party' as Party)
149
177
  const client = createTestClient({ transport, party })
@@ -20,14 +20,17 @@
20
20
  * })
21
21
  */
22
22
 
23
- import type { Transport, TransportRequest } from './types.js'
23
+ import {
24
+ resolveTransportHeaders,
25
+ type Transport,
26
+ type TransportAuthConfig,
27
+ type TransportRequest,
28
+ } from './types.js'
24
29
 
25
30
  /** Configuration for the gRPC transport. */
26
- export type GrpcTransportConfig = {
31
+ export type GrpcTransportConfig = TransportAuthConfig & {
27
32
  /** Base URL of the Canton node. */
28
33
  readonly url: string
29
- /** JWT Bearer token for authentication. */
30
- readonly token?: string
31
34
  /**
32
35
  * A ConnectRPC transport instance (from @connectrpc/connect-web or connect-node).
33
36
  * Injected to keep cantonjs zero-dependency.
@@ -57,7 +60,7 @@ export type GrpcTransportLike = {
57
60
  * Maps Transport requests to gRPC unary calls.
58
61
  */
59
62
  export function grpc(config: GrpcTransportConfig): Transport {
60
- const { url, token, grpcTransport } = config
63
+ const { url, grpcTransport } = config
61
64
  const baseUrl = url.replace(/\/+$/, '')
62
65
 
63
66
  return {
@@ -65,9 +68,18 @@ export function grpc(config: GrpcTransportConfig): Transport {
65
68
  url: baseUrl,
66
69
 
67
70
  async request<TResponse = unknown>(args: TransportRequest): Promise<TResponse> {
68
- const headers: Record<string, string> = {}
69
- if (token !== undefined) {
70
- headers['Authorization'] = `Bearer ${token}`
71
+ const headers = {
72
+ ...args.headers,
73
+ ...(await resolveTransportHeaders(config, {
74
+ transport: 'grpc',
75
+ url: baseUrl,
76
+ request: {
77
+ method: args.method,
78
+ path: args.path,
79
+ headers: args.headers,
80
+ signal: args.signal,
81
+ },
82
+ })),
71
83
  }
72
84
 
73
85
  const result = await grpcTransport.unary(
@@ -92,18 +104,19 @@ export function grpc(config: GrpcTransportConfig): Transport {
92
104
  function pathToService(path: string): { typeName: string } {
93
105
  const segments = path.replace(/^\/v2\//, '').split('/')
94
106
  const serviceMap: Record<string, string> = {
95
- 'commands': 'com.daml.ledger.api.v2.CommandService',
107
+ commands: 'com.daml.ledger.api.v2.CommandService',
96
108
  'interactive-submission': 'com.daml.ledger.api.v2.InteractiveSubmissionService',
97
- 'state': 'com.daml.ledger.api.v2.StateService',
98
- 'updates': 'com.daml.ledger.api.v2.UpdateService',
99
- 'events': 'com.daml.ledger.api.v2.EventQueryService',
100
- 'parties': 'com.daml.ledger.api.v2.PartyManagementService',
101
- 'users': 'com.daml.ledger.api.v2.UserManagementService',
102
- 'packages': 'com.daml.ledger.api.v2.PackageService',
103
- 'dars': 'com.daml.ledger.api.v2.PackageManagementService',
104
- 'version': 'com.daml.ledger.api.v2.VersionService',
109
+ state: 'com.daml.ledger.api.v2.StateService',
110
+ updates: 'com.daml.ledger.api.v2.UpdateService',
111
+ events: 'com.daml.ledger.api.v2.EventQueryService',
112
+ parties: 'com.daml.ledger.api.v2.PartyManagementService',
113
+ users: 'com.daml.ledger.api.v2.UserManagementService',
114
+ packages: 'com.daml.ledger.api.v2.PackageService',
115
+ dars: 'com.daml.ledger.api.v2.PackageManagementService',
116
+ version: 'com.daml.ledger.api.v2.VersionService',
105
117
  }
106
- const serviceName = serviceMap[segments[0] ?? ''] ?? 'unknown'
118
+ const serviceKey = segments.find((segment) => segment.length > 0)
119
+ const serviceName = serviceKey === undefined ? 'unknown' : (serviceMap[serviceKey] ?? 'unknown')
107
120
  return { typeName: serviceName }
108
121
  }
109
122
 
@@ -125,6 +138,7 @@ function pathToMethod(path: string): { name: string } {
125
138
  '/v2/events/events-by-contract-id': 'GetEventsByContractId',
126
139
  '/v2/version': 'GetLedgerApiVersion',
127
140
  }
128
- const methodName = methodMap[path] ?? path.split('/').pop() ?? 'unknown'
141
+ const fallbackMethodName = path.split('/').filter((segment) => segment.length > 0).at(-1)
142
+ const methodName = methodMap[path] ?? fallbackMethodName ?? 'unknown'
129
143
  return { name: methodName }
130
144
  }
@@ -1,2 +1,8 @@
1
1
  export { jsonApi } from './json-api.js'
2
- export type { Transport, TransportConfig, TransportRequest, TransportFactory } from './types.js'
2
+ export type {
3
+ Transport,
4
+ TransportAuthConfig,
5
+ TransportConfig,
6
+ TransportRequest,
7
+ TransportFactory,
8
+ } from './types.js'
@@ -6,12 +6,17 @@
6
6
  */
7
7
 
8
8
  import { ConnectionError, HttpError, TimeoutError } from '../errors/transport.js'
9
- import type { Transport, TransportConfig, TransportRequest } from './types.js'
9
+ import {
10
+ resolveTransportHeaders,
11
+ type Transport,
12
+ type TransportConfig,
13
+ type TransportRequest,
14
+ } from './types.js'
10
15
 
11
16
  const DEFAULT_TIMEOUT = 30_000
12
17
 
13
18
  export function jsonApi(config: TransportConfig): Transport {
14
- const { url, token, timeout = DEFAULT_TIMEOUT, fetchFn = globalThis.fetch } = config
19
+ const { url, timeout = DEFAULT_TIMEOUT, fetchFn = globalThis.fetch } = config
15
20
 
16
21
  const baseUrl = url.replace(/\/+$/, '')
17
22
 
@@ -21,13 +26,21 @@ export function jsonApi(config: TransportConfig): Transport {
21
26
 
22
27
  async request<TResponse = unknown>(args: TransportRequest): Promise<TResponse> {
23
28
  const requestUrl = `${baseUrl}${args.path}`
29
+ const authHeaders = await resolveTransportHeaders(config, {
30
+ transport: 'json-api',
31
+ url: baseUrl,
32
+ request: {
33
+ method: args.method,
34
+ path: args.path,
35
+ headers: args.headers,
36
+ signal: args.signal,
37
+ },
38
+ })
39
+
24
40
  const headers: Record<string, string> = {
25
41
  'Content-Type': 'application/json',
26
42
  ...args.headers,
27
- }
28
-
29
- if (token !== undefined) {
30
- headers['Authorization'] = `Bearer ${token}`
43
+ ...authHeaders,
31
44
  }
32
45
 
33
46
  const controller = new AbortController()
@@ -36,7 +49,11 @@ export function jsonApi(config: TransportConfig): Transport {
36
49
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs)
37
50
 
38
51
  if (args.signal !== undefined) {
39
- args.signal.addEventListener('abort', () => controller.abort(), { once: true })
52
+ if (args.signal.aborted) {
53
+ controller.abort()
54
+ } else {
55
+ args.signal.addEventListener('abort', () => controller.abort(), { once: true })
56
+ }
40
57
  }
41
58
 
42
59
  try {
@@ -6,6 +6,10 @@
6
6
  * client logic from communication concerns.
7
7
  */
8
8
 
9
+ import type { AuthContext, AuthProvider, AuthSession, SessionProvider } from '../auth/types.js'
10
+ import { AuthProviderError } from '../errors/auth.js'
11
+ import { CantonjsError } from '../errors/base.js'
12
+
9
13
  /** A transport handles sending requests to a Canton node. */
10
14
  export type Transport = {
11
15
  readonly type: string
@@ -23,12 +27,20 @@ export type TransportRequest = {
23
27
  timeout?: number
24
28
  }
25
29
 
30
+ /** Authentication configuration shared by core transports. */
31
+ export type TransportAuthConfig = {
32
+ /** Static JWT Bearer token for authentication. */
33
+ readonly token?: string
34
+ /** Per-request bearer token provider. */
35
+ readonly auth?: AuthProvider
36
+ /** Per-request session provider for advanced auth/header injection. */
37
+ readonly session?: SessionProvider
38
+ }
39
+
26
40
  /** Configuration shared by all transport types. */
27
- export type TransportConfig = {
41
+ export type TransportConfig = TransportAuthConfig & {
28
42
  /** Base URL of the Canton node. */
29
43
  readonly url: string
30
- /** JWT Bearer token for authentication. */
31
- readonly token?: string
32
44
  /** Request timeout in milliseconds. Default: 30000. */
33
45
  readonly timeout?: number
34
46
  /** Custom fetch implementation (for testing or environments without global fetch). */
@@ -37,3 +49,51 @@ export type TransportConfig = {
37
49
 
38
50
  /** Factory function type for creating transports. */
39
51
  export type TransportFactory = (config: TransportConfig) => Transport
52
+
53
+ export async function resolveTransportSession(
54
+ config: TransportAuthConfig,
55
+ context: AuthContext,
56
+ ): Promise<AuthSession | undefined> {
57
+ if (config.session !== undefined) {
58
+ try {
59
+ return await config.session(context)
60
+ } catch (error) {
61
+ throw toAuthProviderError(context, error)
62
+ }
63
+ }
64
+
65
+ if (config.auth !== undefined) {
66
+ try {
67
+ const token = await config.auth(context)
68
+ return token === undefined ? undefined : { token }
69
+ } catch (error) {
70
+ throw toAuthProviderError(context, error)
71
+ }
72
+ }
73
+
74
+ return config.token === undefined ? undefined : { token: config.token }
75
+ }
76
+
77
+ export async function resolveTransportHeaders(
78
+ config: TransportAuthConfig,
79
+ context: AuthContext,
80
+ ): Promise<Record<string, string>> {
81
+ const session = await resolveTransportSession(config, context)
82
+ const headers = session?.headers === undefined ? {} : { ...session.headers }
83
+
84
+ if (session?.token !== undefined) {
85
+ headers['Authorization'] = `Bearer ${session.token}`
86
+ }
87
+
88
+ return headers
89
+ }
90
+
91
+ function toAuthProviderError(context: AuthContext, error: unknown): CantonjsError {
92
+ if (error instanceof CantonjsError) {
93
+ return error
94
+ }
95
+
96
+ return new AuthProviderError(context.transport, context.request.path, {
97
+ cause: error instanceof Error ? error : new Error(String(error)),
98
+ })
99
+ }