@logtape/drizzle-orm 1.3.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/mod.ts ADDED
@@ -0,0 +1,188 @@
1
+ import {
2
+ getLogger as getLogTapeLogger,
3
+ type Logger as LogTapeLogger,
4
+ type LogLevel,
5
+ } from "@logtape/logtape";
6
+
7
+ export type { LogLevel } from "@logtape/logtape";
8
+
9
+ /**
10
+ * Options for configuring the Drizzle ORM LogTape logger.
11
+ * @since 1.3.0
12
+ */
13
+ export interface DrizzleLoggerOptions {
14
+ /**
15
+ * The LogTape category to use for logging.
16
+ * @default ["drizzle-orm"]
17
+ */
18
+ readonly category?: string | readonly string[];
19
+
20
+ /**
21
+ * The log level to use for query logging.
22
+ * @default "debug"
23
+ */
24
+ readonly level?: LogLevel;
25
+ }
26
+
27
+ /**
28
+ * Drizzle ORM's Logger interface.
29
+ * @since 1.3.0
30
+ */
31
+ export interface Logger {
32
+ logQuery(query: string, params: unknown[]): void;
33
+ }
34
+
35
+ /**
36
+ * A Drizzle ORM-compatible logger that wraps LogTape.
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * import { drizzle } from "drizzle-orm/postgres-js";
41
+ * import { getLogger } from "@logtape/drizzle-orm";
42
+ * import postgres from "postgres";
43
+ *
44
+ * const client = postgres(process.env.DATABASE_URL!);
45
+ * const db = drizzle(client, {
46
+ * logger: getLogger(),
47
+ * });
48
+ * ```
49
+ *
50
+ * @since 1.3.0
51
+ */
52
+ export class DrizzleLogger implements Logger {
53
+ readonly #logger: LogTapeLogger;
54
+ readonly #level: LogLevel;
55
+
56
+ /**
57
+ * Creates a new DrizzleLogger instance.
58
+ * @param logger The LogTape logger to use.
59
+ * @param level The log level to use for query logging.
60
+ */
61
+ constructor(logger: LogTapeLogger, level: LogLevel = "debug") {
62
+ this.#logger = logger;
63
+ this.#level = level;
64
+ }
65
+
66
+ /**
67
+ * Logs a database query with its parameters.
68
+ *
69
+ * The log output includes:
70
+ * - `formattedQuery`: The query with parameter placeholders replaced with
71
+ * actual values (for readability)
72
+ * - `query`: The original query string with placeholders
73
+ * - `params`: The original parameters array
74
+ *
75
+ * @param query The SQL query string with parameter placeholders.
76
+ * @param params The parameter values.
77
+ */
78
+ logQuery(query: string, params: unknown[]): void {
79
+ const stringifiedParams = params.map(serialize);
80
+ const formattedQuery = query.replace(/\$(\d+)/g, (match) => {
81
+ const index = Number.parseInt(match.slice(1), 10);
82
+ return stringifiedParams[index - 1] ?? match;
83
+ });
84
+
85
+ const logMethod = this.#logger[this.#level].bind(this.#logger);
86
+ logMethod("Query: {formattedQuery}", {
87
+ formattedQuery,
88
+ query,
89
+ params,
90
+ });
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Serializes a parameter value to a SQL-safe string representation.
96
+ *
97
+ * @param value The value to serialize.
98
+ * @returns The serialized string representation.
99
+ * @since 1.3.0
100
+ */
101
+ export function serialize(value: unknown): string {
102
+ if (typeof value === "undefined" || value === null) return "NULL";
103
+ if (typeof value === "string") return stringLiteral(value);
104
+ if (typeof value === "number" || typeof value === "bigint") {
105
+ return value.toString();
106
+ }
107
+ if (typeof value === "boolean") return value ? "'t'" : "'f'";
108
+ if (value instanceof Date) return stringLiteral(value.toISOString());
109
+ if (Array.isArray(value)) {
110
+ return `ARRAY[${value.map(serialize).join(", ")}]`;
111
+ }
112
+ if (typeof value === "object") {
113
+ // Assume it's a JSON object
114
+ return stringLiteral(JSON.stringify(value));
115
+ }
116
+ return stringLiteral(String(value));
117
+ }
118
+
119
+ /**
120
+ * Converts a string to a SQL string literal with proper escaping.
121
+ *
122
+ * @param str The string to convert.
123
+ * @returns The escaped SQL string literal.
124
+ * @since 1.3.0
125
+ */
126
+ export function stringLiteral(str: string): string {
127
+ if (/[\\'\n\r\t\b\f]/.test(str)) {
128
+ let escaped = str;
129
+ escaped = escaped.replaceAll("\\", "\\\\");
130
+ escaped = escaped.replaceAll("'", "\\'");
131
+ escaped = escaped.replaceAll("\n", "\\n");
132
+ escaped = escaped.replaceAll("\r", "\\r");
133
+ escaped = escaped.replaceAll("\t", "\\t");
134
+ escaped = escaped.replaceAll("\b", "\\b");
135
+ escaped = escaped.replaceAll("\f", "\\f");
136
+ return `E'${escaped}'`;
137
+ }
138
+ return `'${str}'`;
139
+ }
140
+
141
+ /**
142
+ * Normalize category to array format.
143
+ */
144
+ function normalizeCategory(
145
+ category: string | readonly string[],
146
+ ): readonly string[] {
147
+ return typeof category === "string" ? [category] : category;
148
+ }
149
+
150
+ /**
151
+ * Creates a Drizzle ORM-compatible logger that wraps LogTape.
152
+ *
153
+ * @example Basic usage
154
+ * ```typescript
155
+ * import { drizzle } from "drizzle-orm/postgres-js";
156
+ * import { configure } from "@logtape/logtape";
157
+ * import { getLogger } from "@logtape/drizzle-orm";
158
+ * import postgres from "postgres";
159
+ *
160
+ * await configure({
161
+ * // ... LogTape configuration
162
+ * });
163
+ *
164
+ * const client = postgres(process.env.DATABASE_URL!);
165
+ * const db = drizzle(client, {
166
+ * logger: getLogger(),
167
+ * });
168
+ * ```
169
+ *
170
+ * @example With custom category and level
171
+ * ```typescript
172
+ * const db = drizzle(client, {
173
+ * logger: getLogger({
174
+ * category: ["my-app", "database"],
175
+ * level: "info",
176
+ * }),
177
+ * });
178
+ * ```
179
+ *
180
+ * @param options Configuration options for the logger.
181
+ * @returns A Drizzle ORM-compatible logger wrapping LogTape.
182
+ * @since 1.3.0
183
+ */
184
+ export function getLogger(options: DrizzleLoggerOptions = {}): DrizzleLogger {
185
+ const category = normalizeCategory(options.category ?? ["drizzle-orm"]);
186
+ const logger = getLogTapeLogger(category);
187
+ return new DrizzleLogger(logger, options.level ?? "debug");
188
+ }
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from "tsdown";
2
+
3
+ export default defineConfig({
4
+ entry: "src/mod.ts",
5
+ dts: {
6
+ sourcemap: true,
7
+ },
8
+ format: ["esm", "cjs"],
9
+ platform: "neutral",
10
+ unbundle: true,
11
+ });