@alchemy/common 0.0.0-alpha.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.
- package/LICENSE +21 -0
- package/dist/esm/actions/addBreadCrumb.d.ts +14 -0
- package/dist/esm/actions/addBreadCrumb.js +27 -0
- package/dist/esm/actions/addBreadCrumb.js.map +1 -0
- package/dist/esm/chains.d.ts +234 -0
- package/dist/esm/chains.js +113 -0
- package/dist/esm/chains.js.map +1 -0
- package/dist/esm/errors/AccountNotFoundError.d.ts +10 -0
- package/dist/esm/errors/AccountNotFoundError.js +19 -0
- package/dist/esm/errors/AccountNotFoundError.js.map +1 -0
- package/dist/esm/errors/BaseError.d.ts +23 -0
- package/dist/esm/errors/BaseError.js +40 -0
- package/dist/esm/errors/BaseError.js.map +1 -0
- package/dist/esm/errors/ChainNotFoundError.d.ts +11 -0
- package/dist/esm/errors/ChainNotFoundError.js +19 -0
- package/dist/esm/errors/ChainNotFoundError.js.map +1 -0
- package/dist/esm/errors/ConnectionConfigError.d.ts +13 -0
- package/dist/esm/errors/ConnectionConfigError.js +25 -0
- package/dist/esm/errors/ConnectionConfigError.js.map +1 -0
- package/dist/esm/errors/FetchError.d.ts +15 -0
- package/dist/esm/errors/FetchError.js +25 -0
- package/dist/esm/errors/FetchError.js.map +1 -0
- package/dist/esm/errors/InvalidRequestError.d.ts +13 -0
- package/dist/esm/errors/InvalidRequestError.js +22 -0
- package/dist/esm/errors/InvalidRequestError.js.map +1 -0
- package/dist/esm/errors/MethodUnsupportedError.d.ts +13 -0
- package/dist/esm/errors/MethodUnsupportedError.js +21 -0
- package/dist/esm/errors/MethodUnsupportedError.js.map +1 -0
- package/dist/esm/errors/ServerError.d.ts +15 -0
- package/dist/esm/errors/ServerError.js +25 -0
- package/dist/esm/errors/ServerError.js.map +1 -0
- package/dist/esm/index.d.ts +29 -0
- package/dist/esm/index.js +27 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/logging/config.d.ts +190 -0
- package/dist/esm/logging/config.js +279 -0
- package/dist/esm/logging/config.js.map +1 -0
- package/dist/esm/logging/index.d.ts +6 -0
- package/dist/esm/logging/index.js +5 -0
- package/dist/esm/logging/index.js.map +1 -0
- package/dist/esm/logging/local.d.ts +10 -0
- package/dist/esm/logging/local.js +35 -0
- package/dist/esm/logging/local.js.map +1 -0
- package/dist/esm/logging/logger.d.ts +80 -0
- package/dist/esm/logging/logger.js +111 -0
- package/dist/esm/logging/logger.js.map +1 -0
- package/dist/esm/logging/noop.d.ts +6 -0
- package/dist/esm/logging/noop.js +12 -0
- package/dist/esm/logging/noop.js.map +1 -0
- package/dist/esm/logging/sinks.d.ts +90 -0
- package/dist/esm/logging/sinks.js +111 -0
- package/dist/esm/logging/sinks.js.map +1 -0
- package/dist/esm/logging/types.d.ts +96 -0
- package/dist/esm/logging/types.js +2 -0
- package/dist/esm/logging/types.js.map +1 -0
- package/dist/esm/logging/utils.d.ts +7 -0
- package/dist/esm/logging/utils.js +21 -0
- package/dist/esm/logging/utils.js.map +1 -0
- package/dist/esm/rest/restClient.d.ts +34 -0
- package/dist/esm/rest/restClient.js +55 -0
- package/dist/esm/rest/restClient.js.map +1 -0
- package/dist/esm/rest/types.d.ts +24 -0
- package/dist/esm/rest/types.js +2 -0
- package/dist/esm/rest/types.js.map +1 -0
- package/dist/esm/tracing/traceHeader.d.ts +82 -0
- package/dist/esm/tracing/traceHeader.js +145 -0
- package/dist/esm/tracing/traceHeader.js.map +1 -0
- package/dist/esm/tracing/updateHeaders.d.ts +24 -0
- package/dist/esm/tracing/updateHeaders.js +61 -0
- package/dist/esm/tracing/updateHeaders.js.map +1 -0
- package/dist/esm/transport/alchemy.d.ts +110 -0
- package/dist/esm/transport/alchemy.js +164 -0
- package/dist/esm/transport/alchemy.js.map +1 -0
- package/dist/esm/transport/chainRegistry.d.ts +31 -0
- package/dist/esm/transport/chainRegistry.js +95 -0
- package/dist/esm/transport/chainRegistry.js.map +1 -0
- package/dist/esm/transport/connection.d.ts +20 -0
- package/dist/esm/transport/connection.js +2 -0
- package/dist/esm/transport/connection.js.map +1 -0
- package/dist/esm/transport/connectionSchema.d.ts +124 -0
- package/dist/esm/transport/connectionSchema.js +121 -0
- package/dist/esm/transport/connectionSchema.js.map +1 -0
- package/dist/esm/utils/assertNever.d.ts +8 -0
- package/dist/esm/utils/assertNever.js +12 -0
- package/dist/esm/utils/assertNever.js.map +1 -0
- package/dist/esm/utils/bigint.d.ts +24 -0
- package/dist/esm/utils/bigint.js +37 -0
- package/dist/esm/utils/bigint.js.map +1 -0
- package/dist/esm/utils/createEip1193HandlerFactory.d.ts +18 -0
- package/dist/esm/utils/createEip1193HandlerFactory.js +11 -0
- package/dist/esm/utils/createEip1193HandlerFactory.js.map +1 -0
- package/dist/esm/utils/headers.d.ts +7 -0
- package/dist/esm/utils/headers.js +29 -0
- package/dist/esm/utils/headers.js.map +1 -0
- package/dist/esm/utils/lowerAddress.d.ts +8 -0
- package/dist/esm/utils/lowerAddress.js +9 -0
- package/dist/esm/utils/lowerAddress.js.map +1 -0
- package/dist/esm/utils/raise.d.ts +8 -0
- package/dist/esm/utils/raise.js +14 -0
- package/dist/esm/utils/raise.js.map +1 -0
- package/dist/esm/utils/types.d.ts +10 -0
- package/dist/esm/utils/types.js +2 -0
- package/dist/esm/utils/types.js.map +1 -0
- package/dist/esm/version.d.ts +1 -0
- package/dist/esm/version.js +4 -0
- package/dist/esm/version.js.map +1 -0
- package/dist/types/actions/addBreadCrumb.d.ts +15 -0
- package/dist/types/actions/addBreadCrumb.d.ts.map +1 -0
- package/dist/types/chains.d.ts +235 -0
- package/dist/types/chains.d.ts.map +1 -0
- package/dist/types/errors/AccountNotFoundError.d.ts +11 -0
- package/dist/types/errors/AccountNotFoundError.d.ts.map +1 -0
- package/dist/types/errors/BaseError.d.ts +24 -0
- package/dist/types/errors/BaseError.d.ts.map +1 -0
- package/dist/types/errors/ChainNotFoundError.d.ts +12 -0
- package/dist/types/errors/ChainNotFoundError.d.ts.map +1 -0
- package/dist/types/errors/ConnectionConfigError.d.ts +14 -0
- package/dist/types/errors/ConnectionConfigError.d.ts.map +1 -0
- package/dist/types/errors/FetchError.d.ts +16 -0
- package/dist/types/errors/FetchError.d.ts.map +1 -0
- package/dist/types/errors/InvalidRequestError.d.ts +14 -0
- package/dist/types/errors/InvalidRequestError.d.ts.map +1 -0
- package/dist/types/errors/MethodUnsupportedError.d.ts +14 -0
- package/dist/types/errors/MethodUnsupportedError.d.ts.map +1 -0
- package/dist/types/errors/ServerError.d.ts +16 -0
- package/dist/types/errors/ServerError.d.ts.map +1 -0
- package/dist/types/index.d.ts +30 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/logging/config.d.ts +191 -0
- package/dist/types/logging/config.d.ts.map +1 -0
- package/dist/types/logging/index.d.ts +7 -0
- package/dist/types/logging/index.d.ts.map +1 -0
- package/dist/types/logging/local.d.ts +11 -0
- package/dist/types/logging/local.d.ts.map +1 -0
- package/dist/types/logging/logger.d.ts +81 -0
- package/dist/types/logging/logger.d.ts.map +1 -0
- package/dist/types/logging/noop.d.ts +7 -0
- package/dist/types/logging/noop.d.ts.map +1 -0
- package/dist/types/logging/sinks.d.ts +91 -0
- package/dist/types/logging/sinks.d.ts.map +1 -0
- package/dist/types/logging/types.d.ts +97 -0
- package/dist/types/logging/types.d.ts.map +1 -0
- package/dist/types/logging/utils.d.ts +8 -0
- package/dist/types/logging/utils.d.ts.map +1 -0
- package/dist/types/rest/restClient.d.ts +35 -0
- package/dist/types/rest/restClient.d.ts.map +1 -0
- package/dist/types/rest/types.d.ts +25 -0
- package/dist/types/rest/types.d.ts.map +1 -0
- package/dist/types/tracing/traceHeader.d.ts +83 -0
- package/dist/types/tracing/traceHeader.d.ts.map +1 -0
- package/dist/types/tracing/updateHeaders.d.ts +25 -0
- package/dist/types/tracing/updateHeaders.d.ts.map +1 -0
- package/dist/types/transport/alchemy.d.ts +111 -0
- package/dist/types/transport/alchemy.d.ts.map +1 -0
- package/dist/types/transport/chainRegistry.d.ts +32 -0
- package/dist/types/transport/chainRegistry.d.ts.map +1 -0
- package/dist/types/transport/connection.d.ts +21 -0
- package/dist/types/transport/connection.d.ts.map +1 -0
- package/dist/types/transport/connectionSchema.d.ts +125 -0
- package/dist/types/transport/connectionSchema.d.ts.map +1 -0
- package/dist/types/utils/assertNever.d.ts +9 -0
- package/dist/types/utils/assertNever.d.ts.map +1 -0
- package/dist/types/utils/bigint.d.ts +25 -0
- package/dist/types/utils/bigint.d.ts.map +1 -0
- package/dist/types/utils/createEip1193HandlerFactory.d.ts +19 -0
- package/dist/types/utils/createEip1193HandlerFactory.d.ts.map +1 -0
- package/dist/types/utils/headers.d.ts +8 -0
- package/dist/types/utils/headers.d.ts.map +1 -0
- package/dist/types/utils/lowerAddress.d.ts +9 -0
- package/dist/types/utils/lowerAddress.d.ts.map +1 -0
- package/dist/types/utils/raise.d.ts +9 -0
- package/dist/types/utils/raise.d.ts.map +1 -0
- package/dist/types/utils/types.d.ts +11 -0
- package/dist/types/utils/types.d.ts.map +1 -0
- package/dist/types/version.d.ts +2 -0
- package/dist/types/version.d.ts.map +1 -0
- package/package.json +67 -0
- package/src/actions/addBreadCrumb.ts +38 -0
- package/src/chains.ts +118 -0
- package/src/errors/AccountNotFoundError.ts +16 -0
- package/src/errors/BaseError.ts +51 -0
- package/src/errors/ChainNotFoundError.ts +15 -0
- package/src/errors/ConnectionConfigError.ts +22 -0
- package/src/errors/FetchError.ts +21 -0
- package/src/errors/InvalidRequestError.ts +19 -0
- package/src/errors/MethodUnsupportedError.ts +17 -0
- package/src/errors/ServerError.ts +21 -0
- package/src/index.ts +60 -0
- package/src/logging/config.ts +365 -0
- package/src/logging/index.ts +20 -0
- package/src/logging/local.ts +39 -0
- package/src/logging/logger.ts +194 -0
- package/src/logging/noop.ts +13 -0
- package/src/logging/sinks.ts +115 -0
- package/src/logging/types.ts +111 -0
- package/src/logging/utils.ts +31 -0
- package/src/rest/restClient.ts +64 -0
- package/src/rest/types.ts +42 -0
- package/src/tracing/traceHeader.ts +154 -0
- package/src/tracing/updateHeaders.ts +66 -0
- package/src/transport/alchemy.ts +242 -0
- package/src/transport/chainRegistry.ts +115 -0
- package/src/transport/connection.ts +19 -0
- package/src/transport/connectionSchema.ts +145 -0
- package/src/utils/assertNever.ts +12 -0
- package/src/utils/bigint.ts +58 -0
- package/src/utils/createEip1193HandlerFactory.ts +25 -0
- package/src/utils/headers.ts +48 -0
- package/src/utils/lowerAddress.ts +10 -0
- package/src/utils/raise.ts +14 -0
- package/src/utils/types.ts +14 -0
- package/src/version.ts +3 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { LogEntry } from "./config.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* In-memory sink for testing purposes.
|
|
5
|
+
* Captures all log entries in an array that can be inspected in tests.
|
|
6
|
+
*
|
|
7
|
+
* **Note:** This is primarily intended for testing SDK integrations.
|
|
8
|
+
* Most applications should use the default console sink.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { InMemorySink, setGlobalLoggerConfig, createLogger } from "@alchemy/common";
|
|
13
|
+
*
|
|
14
|
+
* // In tests
|
|
15
|
+
* const sink = new InMemorySink();
|
|
16
|
+
*
|
|
17
|
+
* setGlobalLoggerConfig({
|
|
18
|
+
* sinks: [sink.sink]
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* const logger = createLogger({
|
|
22
|
+
* package: "@alchemy/aa-infra",
|
|
23
|
+
* version: "1.0.0",
|
|
24
|
+
* namespace: "aa-infra"
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* logger.info("test message");
|
|
28
|
+
*
|
|
29
|
+
* // Inspect captured logs
|
|
30
|
+
* expect(sink.count).toBe(1);
|
|
31
|
+
* expect(sink.latest()?.message).toBe("test message");
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export class InMemorySink {
|
|
35
|
+
/**
|
|
36
|
+
* Array of captured log entries
|
|
37
|
+
*/
|
|
38
|
+
public entries: LogEntry[] = [];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Creates a new InMemorySink instance.
|
|
42
|
+
* Binds the sink method to the instance for use as a callback.
|
|
43
|
+
*/
|
|
44
|
+
constructor() {
|
|
45
|
+
this.sink = this.sink.bind(this);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The sink function to pass to setGlobalLoggerConfig.
|
|
50
|
+
* Captures log entries into the internal array.
|
|
51
|
+
*
|
|
52
|
+
* @param {LogEntry} entry - The log entry to capture
|
|
53
|
+
* @returns {void}
|
|
54
|
+
*/
|
|
55
|
+
sink(entry: LogEntry): void {
|
|
56
|
+
this.entries.push(entry);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Clear all captured entries
|
|
61
|
+
*
|
|
62
|
+
* @returns {void}
|
|
63
|
+
*/
|
|
64
|
+
clear(): void {
|
|
65
|
+
this.entries = [];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get entries filtered by log level
|
|
70
|
+
*
|
|
71
|
+
* @param {number} level - The log level to filter by (LogLevel.ERROR, LogLevel.INFO, etc.)
|
|
72
|
+
* @returns {LogEntry[]} Array of log entries matching the specified level
|
|
73
|
+
*/
|
|
74
|
+
getByLevel(level: number): LogEntry[] {
|
|
75
|
+
return this.entries.filter((e) => e.level === level);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get entries filtered by namespace
|
|
80
|
+
*
|
|
81
|
+
* @param {string} namespace - The namespace to filter by (e.g., "aa-infra", "wallet-apis")
|
|
82
|
+
* @returns {LogEntry[]} Array of log entries matching the specified namespace
|
|
83
|
+
*/
|
|
84
|
+
getByNamespace(namespace: string): LogEntry[] {
|
|
85
|
+
return this.entries.filter((e) => e.namespace === namespace);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get entries filtered by message substring
|
|
90
|
+
*
|
|
91
|
+
* @param {string} substring - The substring to search for in log messages
|
|
92
|
+
* @returns {LogEntry[]} Array of log entries containing the substring in their message
|
|
93
|
+
*/
|
|
94
|
+
getByMessage(substring: string): LogEntry[] {
|
|
95
|
+
return this.entries.filter((e) => e.message.includes(substring));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get the most recent log entry
|
|
100
|
+
*
|
|
101
|
+
* @returns {LogEntry | undefined} The latest log entry, or undefined if no entries have been captured
|
|
102
|
+
*/
|
|
103
|
+
latest(): LogEntry | undefined {
|
|
104
|
+
return this.entries[this.entries.length - 1];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get the number of captured entries
|
|
109
|
+
*
|
|
110
|
+
* @returns {number} The total count of captured log entries
|
|
111
|
+
*/
|
|
112
|
+
get count(): number {
|
|
113
|
+
return this.entries.length;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema definition for event logging.
|
|
3
|
+
* An array of event definitions with their names and optional data structures.
|
|
4
|
+
*/
|
|
5
|
+
export type EventsSchema = readonly {
|
|
6
|
+
EventName: string;
|
|
7
|
+
EventData?: Record<string, any>;
|
|
8
|
+
}[];
|
|
9
|
+
|
|
10
|
+
type Prettify<T> = {
|
|
11
|
+
[K in keyof T]: T[K];
|
|
12
|
+
} & {};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Type-safe parameters for tracking events based on the provided schema.
|
|
16
|
+
* Ensures event names and data structures match the schema definition.
|
|
17
|
+
* When no schema is provided, allows any event with optional data.
|
|
18
|
+
*
|
|
19
|
+
* @template Schema - The events schema to validate against
|
|
20
|
+
*/
|
|
21
|
+
export type TrackEventParameters<Schema extends EventsSchema> =
|
|
22
|
+
Schema extends readonly []
|
|
23
|
+
? { name: string; data?: any }
|
|
24
|
+
: {
|
|
25
|
+
[K in keyof Schema]: Prettify<
|
|
26
|
+
{ name: Schema[K]["EventName"] } & ([undefined] extends [
|
|
27
|
+
Schema[K]["EventData"],
|
|
28
|
+
]
|
|
29
|
+
? { data?: undefined }
|
|
30
|
+
: { data: Schema[K]["EventData"] })
|
|
31
|
+
>;
|
|
32
|
+
}[number];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Main event logger interface for type-safe event tracking and performance profiling.
|
|
36
|
+
*
|
|
37
|
+
* @template Schema - The events schema defining allowed events and their data structures
|
|
38
|
+
*/
|
|
39
|
+
export interface EventLogger<Schema extends EventsSchema = []> {
|
|
40
|
+
/**
|
|
41
|
+
* Tracks an event with type-safe validation against the schema.
|
|
42
|
+
*
|
|
43
|
+
* @param params - Event parameters including name and optional data
|
|
44
|
+
* @returns Promise that resolves when the event is tracked
|
|
45
|
+
*/
|
|
46
|
+
trackEvent(
|
|
47
|
+
params: TrackEventParameters<
|
|
48
|
+
Schema extends readonly [] ? [] : [...Schema, PerformanceEvent]
|
|
49
|
+
>,
|
|
50
|
+
): Promise<void>;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Wraps a function to automatically track its execution time as a performance event.
|
|
54
|
+
*
|
|
55
|
+
* @template TArgs - Function argument types
|
|
56
|
+
* @template TRet - Function return type
|
|
57
|
+
* @param name - Name identifier for the profiled function
|
|
58
|
+
* @param func - Function to wrap with performance tracking
|
|
59
|
+
* @returns Wrapped function that tracks execution time
|
|
60
|
+
*/
|
|
61
|
+
profiled<TArgs extends any[], TRet>(
|
|
62
|
+
name: string,
|
|
63
|
+
func: (...args: TArgs) => TRet,
|
|
64
|
+
): (...args: TArgs) => TRet;
|
|
65
|
+
|
|
66
|
+
/** Internal properties for logger state and configuration */
|
|
67
|
+
_internal: {
|
|
68
|
+
/** Promise that resolves when logger is ready for use */
|
|
69
|
+
ready: Promise<unknown>;
|
|
70
|
+
/** Anonymous identifier for this logger instance */
|
|
71
|
+
anonId: string;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Internal logger interface without the profiled method.
|
|
77
|
+
* Used internally by different logger implementations.
|
|
78
|
+
*
|
|
79
|
+
* @template Schema - The events schema defining allowed events and their data structures
|
|
80
|
+
*/
|
|
81
|
+
export type InnerLogger<Schema extends EventsSchema> = Omit<
|
|
82
|
+
EventLogger<Schema>,
|
|
83
|
+
"profiled"
|
|
84
|
+
>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Context information attached to all logged events.
|
|
88
|
+
* Provides metadata about the package and version generating events.
|
|
89
|
+
*/
|
|
90
|
+
export type LoggerContext = {
|
|
91
|
+
/** Name of the package generating events */
|
|
92
|
+
package: string;
|
|
93
|
+
/** Version of the package generating events */
|
|
94
|
+
version: string;
|
|
95
|
+
/** Additional context properties as key-value pairs */
|
|
96
|
+
[key: string]: string;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Built-in performance event schema for tracking function execution times.
|
|
101
|
+
* Automatically included in all event schemas for profiled function tracking.
|
|
102
|
+
*/
|
|
103
|
+
export type PerformanceEvent = {
|
|
104
|
+
EventName: "performance";
|
|
105
|
+
EventData: {
|
|
106
|
+
/** Execution time in milliseconds */
|
|
107
|
+
executionTimeMs: number;
|
|
108
|
+
/** Name of the function being profiled */
|
|
109
|
+
functionName: string;
|
|
110
|
+
};
|
|
111
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// just in case we're in a setting that doesn't have these types defined
|
|
2
|
+
declare const __DEV__: boolean | undefined;
|
|
3
|
+
declare const window: Window | undefined;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Detects if the current environment is in development mode.
|
|
7
|
+
* Checks multiple common development environment indicators.
|
|
8
|
+
*
|
|
9
|
+
* @returns {boolean} True if running in development mode
|
|
10
|
+
*/
|
|
11
|
+
export function isClientDevMode() {
|
|
12
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (
|
|
17
|
+
typeof process !== "undefined" &&
|
|
18
|
+
process.env.NODE_ENV === "development"
|
|
19
|
+
) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (
|
|
24
|
+
typeof window !== "undefined" &&
|
|
25
|
+
window.location?.hostname?.includes("localhost")
|
|
26
|
+
) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { FetchError } from "../errors/FetchError.js";
|
|
2
|
+
import { ServerError } from "../errors/ServerError.js";
|
|
3
|
+
import { withAlchemyHeaders } from "../utils/headers.js";
|
|
4
|
+
import type { RestRequestFn, RestRequestSchema } from "./types.js";
|
|
5
|
+
|
|
6
|
+
const ALCHEMY_API_URL = "https://api.g.alchemy.com";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Parameters for creating an AlchemyRestClient instance.
|
|
10
|
+
*/
|
|
11
|
+
export type AlchemyRestClientParams = {
|
|
12
|
+
/** API key for Alchemy authentication */
|
|
13
|
+
apiKey?: string;
|
|
14
|
+
/** JWT token for Alchemy authentication */
|
|
15
|
+
jwt?: string;
|
|
16
|
+
/** Custom URL (optional - defaults to Alchemy's chain-agnostic URL, but can be used to override it) */
|
|
17
|
+
url?: string;
|
|
18
|
+
/** Custom headers to be sent with requests */
|
|
19
|
+
headers?: HeadersInit;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A client for making requests to Alchemy's non-JSON-RPC endpoints.
|
|
24
|
+
*/
|
|
25
|
+
export class AlchemyRestClient<Schema extends RestRequestSchema> {
|
|
26
|
+
private readonly url: string;
|
|
27
|
+
private readonly headers: Headers;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Creates a new instance of AlchemyRestClient.
|
|
31
|
+
*
|
|
32
|
+
* @param {AlchemyRestClientParams} params - The parameters for configuring the client, including API key, JWT, custom URL, and headers.
|
|
33
|
+
*/
|
|
34
|
+
constructor({ apiKey, jwt, url, headers }: AlchemyRestClientParams) {
|
|
35
|
+
this.url = url ?? ALCHEMY_API_URL;
|
|
36
|
+
this.headers = new Headers(withAlchemyHeaders({ headers, apiKey, jwt }));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Makes an HTTP request to an Alchemy non-JSON-RPC endpoint.
|
|
41
|
+
*
|
|
42
|
+
* @param {RestRequestFn<Schema>} params - The parameters for the request
|
|
43
|
+
* @returns {Promise<unknown>} The response from the request
|
|
44
|
+
*/
|
|
45
|
+
public request: RestRequestFn<Schema> = async (params) => {
|
|
46
|
+
const response = await fetch(`${this.url}/${params.route}`, {
|
|
47
|
+
method: params.method,
|
|
48
|
+
body: params.body ? JSON.stringify(params.body) : undefined,
|
|
49
|
+
headers: this.headers,
|
|
50
|
+
}).catch((error) => {
|
|
51
|
+
throw new FetchError(params.route, params.method, error);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
throw new ServerError(
|
|
56
|
+
await response.text(),
|
|
57
|
+
response.status,
|
|
58
|
+
new Error(response.statusText),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return response.json();
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Prettify } from "viem";
|
|
2
|
+
|
|
3
|
+
export type RestRequestSchema = readonly {
|
|
4
|
+
Route: string;
|
|
5
|
+
Method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS" | "HEAD";
|
|
6
|
+
Body?: unknown | undefined;
|
|
7
|
+
Response: unknown;
|
|
8
|
+
}[];
|
|
9
|
+
|
|
10
|
+
export type RestRequestParams<
|
|
11
|
+
Schema extends RestRequestSchema | undefined = undefined,
|
|
12
|
+
> = Schema extends RestRequestSchema
|
|
13
|
+
? {
|
|
14
|
+
[K in keyof Schema]: Prettify<
|
|
15
|
+
{
|
|
16
|
+
route: Schema[K] extends Schema[number] ? Schema[K]["Route"] : never;
|
|
17
|
+
method: Schema[K] extends Schema[number]
|
|
18
|
+
? Schema[K]["Method"]
|
|
19
|
+
: never;
|
|
20
|
+
} & (Schema[K] extends Schema[number]
|
|
21
|
+
? Schema[K]["Body"] extends undefined
|
|
22
|
+
? { body?: undefined }
|
|
23
|
+
: { body: Schema[K]["Body"] }
|
|
24
|
+
: never)
|
|
25
|
+
>;
|
|
26
|
+
}[number]
|
|
27
|
+
: {
|
|
28
|
+
route: string;
|
|
29
|
+
method: RestRequestSchema[number]["Method"];
|
|
30
|
+
body?: unknown | undefined;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type RestRequestFn<
|
|
34
|
+
Schema extends RestRequestSchema | undefined = undefined,
|
|
35
|
+
> = <
|
|
36
|
+
_parameters extends RestRequestParams<Schema> = RestRequestParams<Schema>,
|
|
37
|
+
_returnType = Schema extends RestRequestSchema
|
|
38
|
+
? Extract<Schema[number], { Route: _parameters["route"] }>["Response"]
|
|
39
|
+
: unknown,
|
|
40
|
+
>(
|
|
41
|
+
params: _parameters,
|
|
42
|
+
) => Promise<_returnType>;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
function generateRandomHexString(numBytes: number) {
|
|
2
|
+
const hexPairs = new Array(numBytes).fill(0).map(() =>
|
|
3
|
+
Math.floor(Math.random() * 16)
|
|
4
|
+
.toString(16)
|
|
5
|
+
.padStart(2, "0"),
|
|
6
|
+
);
|
|
7
|
+
return hexPairs.join("");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* These are the headers that are used in the trace headers, could be found in the spec
|
|
12
|
+
*
|
|
13
|
+
* @see https://www.w3.org/TR/trace-context/#design-overview
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export const TRACE_HEADER_NAME = "traceparent";
|
|
17
|
+
/**
|
|
18
|
+
* These are the headers that are used in the trace headers, could be found in the spec
|
|
19
|
+
*
|
|
20
|
+
* @see https://www.w3.org/TR/trace-context/#design-overview
|
|
21
|
+
*/
|
|
22
|
+
export const TRACE_HEADER_STATE = "tracestate";
|
|
23
|
+
|
|
24
|
+
const clientTraceId = generateRandomHexString(16);
|
|
25
|
+
/**
|
|
26
|
+
* Some tools that are useful when dealing with the values
|
|
27
|
+
* of the trace header. Follows the W3C trace context standard.
|
|
28
|
+
*
|
|
29
|
+
* @see https://www.w3.org/TR/trace-context/
|
|
30
|
+
*/
|
|
31
|
+
export class TraceHeader {
|
|
32
|
+
readonly traceId: string;
|
|
33
|
+
readonly parentId: string;
|
|
34
|
+
readonly traceFlags: string;
|
|
35
|
+
readonly traceState: Record<string, string>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Initializes a new instance with the provided trace identifiers and state information.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} traceId The unique identifier for the trace
|
|
41
|
+
* @param {string} parentId The identifier of the parent trace
|
|
42
|
+
* @param {string} traceFlags Flags containing trace-related options
|
|
43
|
+
* @param {TraceHeader["traceState"]} traceState The trace state information for additional trace context
|
|
44
|
+
*/
|
|
45
|
+
constructor(
|
|
46
|
+
traceId: string,
|
|
47
|
+
parentId: string,
|
|
48
|
+
traceFlags: string,
|
|
49
|
+
traceState: TraceHeader["traceState"],
|
|
50
|
+
) {
|
|
51
|
+
this.traceId = traceId;
|
|
52
|
+
this.parentId = parentId;
|
|
53
|
+
this.traceFlags = traceFlags;
|
|
54
|
+
this.traceState = traceState;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Creating a default trace id that is a random setup for both trace id and parent id
|
|
59
|
+
*
|
|
60
|
+
* @example ```ts
|
|
61
|
+
* const traceHeader = TraceHeader.fromTraceHeader(headers) || TraceHeader.default();
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* @returns {TraceHeader} A default trace header
|
|
65
|
+
*/
|
|
66
|
+
static default() {
|
|
67
|
+
return new TraceHeader(
|
|
68
|
+
clientTraceId,
|
|
69
|
+
generateRandomHexString(8),
|
|
70
|
+
"00", //Means no flag have been set, and no sampled state https://www.w3.org/TR/trace-context/#trace-flags
|
|
71
|
+
{},
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Should be able to consume a trace header from the headers of an http request
|
|
76
|
+
*
|
|
77
|
+
* @example ```ts
|
|
78
|
+
* const traceHeader = TraceHeader.fromTraceHeader(headers);
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* @param {Record<string,string>} headers The headers from the http request
|
|
82
|
+
* @returns {TraceHeader | undefined} The trace header object, or nothing if not found
|
|
83
|
+
*/
|
|
84
|
+
static fromTraceHeader(
|
|
85
|
+
headers: Record<string, string>,
|
|
86
|
+
): TraceHeader | undefined {
|
|
87
|
+
if (!headers[TRACE_HEADER_NAME]) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
const [version, traceId, parentId, traceFlags] =
|
|
91
|
+
headers[TRACE_HEADER_NAME]?.split("-");
|
|
92
|
+
|
|
93
|
+
const traceState =
|
|
94
|
+
headers[TRACE_HEADER_STATE]?.split(",").reduce(
|
|
95
|
+
(acc, curr) => {
|
|
96
|
+
const [key, value] = curr.split("=");
|
|
97
|
+
acc[key] = value;
|
|
98
|
+
return acc;
|
|
99
|
+
},
|
|
100
|
+
{} as Record<string, string>,
|
|
101
|
+
) || {};
|
|
102
|
+
if (version !== "00") {
|
|
103
|
+
console.debug(
|
|
104
|
+
new Error(
|
|
105
|
+
`Invalid version for traceheader: ${headers[TRACE_HEADER_NAME]}`,
|
|
106
|
+
),
|
|
107
|
+
);
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
return new TraceHeader(traceId, parentId, traceFlags, traceState);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Should be able to convert the trace header to the format that is used in the headers of an http request
|
|
115
|
+
*
|
|
116
|
+
* @example ```ts
|
|
117
|
+
* const traceHeader = TraceHeader.fromTraceHeader(headers) || TraceHeader.default();
|
|
118
|
+
* const headers = traceHeader.toTraceHeader();
|
|
119
|
+
* ```
|
|
120
|
+
*
|
|
121
|
+
* @returns {{traceparent: string, tracestate: string}} The trace header in the format of a record, used in our http client
|
|
122
|
+
*/
|
|
123
|
+
toTraceHeader() {
|
|
124
|
+
return {
|
|
125
|
+
[TRACE_HEADER_NAME]: `00-${this.traceId}-${this.parentId}-${this.traceFlags}`,
|
|
126
|
+
[TRACE_HEADER_STATE]: Object.entries(this.traceState)
|
|
127
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
128
|
+
.join(","),
|
|
129
|
+
} as const;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Should be able to create a new trace header with a new event in the trace state,
|
|
134
|
+
* as the key of the eventName as breadcrumbs appending onto previous breadcrumbs with the - infix if exists. And the
|
|
135
|
+
* trace parent gets updated as according to the docs
|
|
136
|
+
*
|
|
137
|
+
* @example ```ts
|
|
138
|
+
* const traceHeader = TraceHeader.fromTraceHeader(headers) || TraceHeader.default();
|
|
139
|
+
* const newTraceHeader = traceHeader.withEvent("newEvent");
|
|
140
|
+
* ```
|
|
141
|
+
*
|
|
142
|
+
* @param {string} eventName The key of the new event
|
|
143
|
+
* @returns {TraceHeader} The new trace header
|
|
144
|
+
*/
|
|
145
|
+
withEvent(eventName: string): TraceHeader {
|
|
146
|
+
const breadcrumbs = this.traceState.breadcrumbs
|
|
147
|
+
? `${this.traceState.breadcrumbs}-${eventName}`
|
|
148
|
+
: eventName;
|
|
149
|
+
return new TraceHeader(this.traceId, this.parentId, this.traceFlags, {
|
|
150
|
+
...this.traceState,
|
|
151
|
+
breadcrumbs,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TRACE_HEADER_NAME,
|
|
3
|
+
TRACE_HEADER_STATE,
|
|
4
|
+
TraceHeader,
|
|
5
|
+
} from "./traceHeader.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The header that is used to track the trace id.
|
|
9
|
+
* We use a client specific one to not mess with the span tracing of the servers.
|
|
10
|
+
*
|
|
11
|
+
* @see headersUpdate
|
|
12
|
+
*/
|
|
13
|
+
const TRACKER_HEADER = "X-Alchemy-Client-Trace-Id";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The header that is used to track the breadcrumb.
|
|
17
|
+
*
|
|
18
|
+
* @see headersUpdate
|
|
19
|
+
*/
|
|
20
|
+
const TRACKER_BREADCRUMB = "X-Alchemy-Client-Breadcrumb";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Remove the tracking headers. This is used in our split transport to ensure that we remove the headers that
|
|
24
|
+
* are not used by the other systems.
|
|
25
|
+
*
|
|
26
|
+
* @param {unknown} x The headers to remove the tracking headers from
|
|
27
|
+
*/
|
|
28
|
+
export function mutateRemoveTrackingHeaders(x?: unknown) {
|
|
29
|
+
if (!x) return;
|
|
30
|
+
if (Array.isArray(x)) return;
|
|
31
|
+
if (typeof x !== "object") return;
|
|
32
|
+
TRACKER_HEADER in x && delete x[TRACKER_HEADER];
|
|
33
|
+
TRACKER_BREADCRUMB in x && delete x[TRACKER_BREADCRUMB];
|
|
34
|
+
TRACE_HEADER_NAME in x && delete x[TRACE_HEADER_NAME];
|
|
35
|
+
TRACE_HEADER_STATE in x && delete x[TRACE_HEADER_STATE];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function addCrumb(previous: string | undefined, crumb: string): string {
|
|
39
|
+
if (!previous) return crumb;
|
|
40
|
+
return `${previous} > ${crumb}`;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Update the headers with the trace header and breadcrumb.
|
|
44
|
+
*
|
|
45
|
+
* These trace headers are used in the imply ingestion pipeline to trace the request.
|
|
46
|
+
* And the breadcrumb is used to get finer grain details in the trace.
|
|
47
|
+
*
|
|
48
|
+
* Then there are the trace headers that are part of the W3C trace context standard.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} crumb The crumb to add to the breadcrumb
|
|
51
|
+
* @returns {Function} A function that updates the headers
|
|
52
|
+
*/
|
|
53
|
+
export function headersUpdate(crumb: string) {
|
|
54
|
+
const headerUpdate_ = (x: Record<string, string>) => {
|
|
55
|
+
const traceHeader = (
|
|
56
|
+
TraceHeader.fromTraceHeader(x) || TraceHeader.default()
|
|
57
|
+
).withEvent(crumb);
|
|
58
|
+
return {
|
|
59
|
+
[TRACKER_HEADER]: traceHeader.parentId,
|
|
60
|
+
...x,
|
|
61
|
+
[TRACKER_BREADCRUMB]: addCrumb(x[TRACKER_BREADCRUMB], crumb),
|
|
62
|
+
...traceHeader.toTraceHeader(),
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
return headerUpdate_;
|
|
66
|
+
}
|