@unhandled/instrumentation-kysely 0.0.0-stage → 0.0.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/README.md CHANGED
@@ -1,3 +1,85 @@
1
- # Temporary Holding Version
1
+ # @unhandled/instrumentation-kysely
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Traces [Kysely](https://kysely.dev) queries, streams and transactions, and exports `pg` pool gauges. Supports Kysely `^0.28.16` and `^0.29.6` with `PostgresDialect` (`pg`) and `PostgresJSDialect` (`kysely-postgres-js`).
4
+
5
+ This package is manual only. It has no `/integration` entry, so the AdonisJS agent never enables it for you.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm i @unhandled/instrumentation-kysely
11
+ ```
12
+
13
+ ## Use
14
+
15
+ Create the dialect after OpenTelemetry is initialised and pass the wrapped dialect to Kysely:
16
+
17
+ ```ts
18
+ import { Kysely, PostgresDialect } from 'kysely'
19
+ import pg from 'pg'
20
+ import { observeDialect } from '@unhandled/instrumentation-kysely'
21
+
22
+ const pool = new pg.Pool({ host, port, database, user, password, max: 10 })
23
+
24
+ export const db = new Kysely<Database>({
25
+ dialect: observeDialect({
26
+ dialect: new PostgresDialect({ pool }),
27
+ dbSystem: 'postgresql',
28
+ connection: { host, port, database },
29
+ }),
30
+ })
31
+ ```
32
+
33
+ ## Options
34
+
35
+ | Option | Default | Effect |
36
+ | ------------------- | -------- | --------------------------------------------------------------------------------------------- |
37
+ | `dialect` | required | The dialect to wrap. |
38
+ | `dbSystem` | required | Value of `db.system.name`, for example `postgresql`. |
39
+ | `recordQueryText` | `true` | `false` leaves `db.query.text` off every span. |
40
+ | `recordErrors` | `true` | `false` keeps `ERROR` status but drops the status message and the exception event. |
41
+ | `requireParentSpan` | `false` | Only create spans when a span is already active. |
42
+ | `connection` | none | `{ host, port, database }`, reported as `server.address`, `server.port` and `db.namespace`. |
43
+
44
+ SQL text and error messages can contain sensitive data. Query parameters are never recorded, but literals inlined in SQL are. Turn off `recordQueryText` and `recordErrors` if that matters.
45
+
46
+ ## Spans
47
+
48
+ Every span is `CLIENT` kind. The name is `<OPERATION> <table>` (`SELECT users`), or just the operation when no table can be parsed, with the operation and table read from the SQL text. A transaction produces `BEGIN`, one span per query, then `COMMIT` or `ROLLBACK`. A stream is one span that lasts until the stream is drained, closed or fails.
49
+
50
+ | Attribute | Value |
51
+ | -------------------- | ----------------------------------------------------------------------- |
52
+ | `db.system.name` | `dbSystem` |
53
+ | `db.query.text` | SQL with placeholders, when `recordQueryText` is on |
54
+ | `db.operation.name` | Upper-cased first keyword, for example `SELECT` |
55
+ | `db.collection.name` | Target table when it can be parsed |
56
+ | `db.namespace` | `connection.database` |
57
+ | `server.address` | `connection.host` |
58
+ | `server.port` | `connection.port` |
59
+ | `db.transaction` | `true` on `BEGIN`, `COMMIT`, `ROLLBACK` and queries inside a transaction |
60
+ | `error.type` | Error class name on failures |
61
+
62
+ ## Pool metrics
63
+
64
+ ```ts
65
+ import { registerPgPoolMetrics } from '@unhandled/instrumentation-kysely'
66
+
67
+ const stop = registerPgPoolMetrics({ pool, max: 10, poolName: 'primary' })
68
+ // later, before pool.end()
69
+ stop()
70
+ ```
71
+
72
+ Call it after the metrics provider is set up. Only `pg.Pool` is supported; anything else throws a `TypeError`. postgres.js exposes no pool counters.
73
+
74
+ | Gauge | Meaning |
75
+ | ---------------------------------------- | ---------------------------------------- |
76
+ | `db.client.connection.used` | Connections checked out |
77
+ | `db.client.connection.idle` | Open connections sitting idle |
78
+ | `db.client.connection.pending_requests` | Callers waiting for a connection |
79
+ | `db.client.connection.max` | The `max` you passed |
80
+
81
+ Each point carries `db.client.connection.pool.name` set to `poolName`.
82
+
83
+ ## Duplicate spans
84
+
85
+ If you also enable OpenTelemetry's `pg` instrumentation, every query is traced twice, once here and once at the driver. Keep one of them. postgres.js has no OpenTelemetry coverage, so wrapping its dialect here causes no duplicates.
@@ -0,0 +1,5 @@
1
+ export { observeDialect } from "./observe_dialect.ts";
2
+ export { POOL_METRIC_NAMES, registerPgPoolMetrics } from "./pool_metrics.ts";
3
+ export type { ParsedSql } from "./sql_parser.ts";
4
+ export { parseSql } from "./sql_parser.ts";
5
+ export type { ConnectionInfo, ObserveDialectOptions, PgPoolLike, RegisterPgPoolMetricsOptions, } from "./types.ts";
package/build/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { observeDialect } from "./observe_dialect.js";
2
+ export { POOL_METRIC_NAMES, registerPgPoolMetrics } from "./pool_metrics.js";
3
+ export { parseSql } from "./sql_parser.js";
@@ -0,0 +1,3 @@
1
+ import type { Dialect } from "kysely";
2
+ import type { ObserveDialectOptions } from "./types.ts";
3
+ export declare const observeDialect: (options: ObserveDialectOptions) => Dialect;
@@ -0,0 +1,194 @@
1
+ import { context, SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
2
+ import { ATTR } from "@unhandled/protocol";
3
+ import { parseSql } from "./sql_parser.js";
4
+ const INSTRUMENTATION_NAME = "@unhandled/instrumentation-kysely";
5
+ const INSTRUMENTATION_VERSION = "0.1.0";
6
+ const startSpan = (settings, state, sql) => {
7
+ const parent = context.active();
8
+ if (settings.requireParentSpan && !trace.getSpan(parent)) {
9
+ return undefined;
10
+ }
11
+ const { operation, collection } = parseSql(sql);
12
+ const { connection } = settings;
13
+ const attributes = { [ATTR.dbSystemName]: settings.dbSystem };
14
+ if (settings.recordQueryText) {
15
+ attributes[ATTR.dbQueryText] = sql;
16
+ }
17
+ if (operation) {
18
+ attributes[ATTR.dbOperationName] = operation;
19
+ }
20
+ if (collection) {
21
+ attributes[ATTR.dbCollectionName] = collection;
22
+ }
23
+ if (connection.database) {
24
+ attributes[ATTR.dbNamespace] = connection.database;
25
+ }
26
+ if (connection.host) {
27
+ attributes[ATTR.serverAddress] = connection.host;
28
+ }
29
+ if (connection.port !== undefined) {
30
+ attributes[ATTR.serverPort] = connection.port;
31
+ }
32
+ if (state.inTransaction) {
33
+ attributes[ATTR.dbTransaction] = true;
34
+ }
35
+ let name = settings.dbSystem;
36
+ if (operation) {
37
+ name = collection ? `${operation} ${collection}` : operation;
38
+ }
39
+ const span = trace
40
+ .getTracer(INSTRUMENTATION_NAME, INSTRUMENTATION_VERSION)
41
+ .startSpan(name, { kind: SpanKind.CLIENT, attributes }, parent);
42
+ return { span, context: trace.setSpan(parent, span), ended: false };
43
+ };
44
+ const finishSpan = (settings, active, error) => {
45
+ if (!active || active.ended) {
46
+ return;
47
+ }
48
+ active.ended = true;
49
+ if (error !== undefined) {
50
+ active.span.setAttribute(ATTR.errorType, error instanceof Error ? error.name : "Error");
51
+ if (settings.recordErrors) {
52
+ active.span.recordException(error instanceof Error ? error : String(error));
53
+ active.span.setStatus({
54
+ code: SpanStatusCode.ERROR,
55
+ message: error instanceof Error ? error.message : String(error),
56
+ });
57
+ }
58
+ else {
59
+ active.span.setStatus({ code: SpanStatusCode.ERROR });
60
+ }
61
+ }
62
+ active.span.end();
63
+ };
64
+ const observeCall = async (settings, state, sql, run) => {
65
+ const active = startSpan(settings, state, sql);
66
+ if (!active) {
67
+ return run();
68
+ }
69
+ try {
70
+ const result = await context.with(active.context, run);
71
+ finishSpan(settings, active);
72
+ return result;
73
+ }
74
+ catch (error) {
75
+ finishSpan(settings, active, error);
76
+ throw error;
77
+ }
78
+ };
79
+ const observeStream = (settings, state, sql, open) => {
80
+ let iterator;
81
+ let active;
82
+ const current = () => {
83
+ if (!iterator) {
84
+ active = startSpan(settings, state, sql);
85
+ iterator = active ? context.with(active.context, open) : open();
86
+ }
87
+ return iterator;
88
+ };
89
+ const within = (run) => (active ? context.with(active.context, run) : run());
90
+ const stream = {
91
+ next: async () => {
92
+ const source = current();
93
+ try {
94
+ const result = await within(() => source.next());
95
+ if (result.done) {
96
+ finishSpan(settings, active);
97
+ }
98
+ return result;
99
+ }
100
+ catch (error) {
101
+ finishSpan(settings, active, error);
102
+ throw error;
103
+ }
104
+ },
105
+ return: async (value) => {
106
+ try {
107
+ return (await iterator?.return?.(value)) ?? { done: true, value };
108
+ }
109
+ finally {
110
+ finishSpan(settings, active);
111
+ }
112
+ },
113
+ throw: async (error) => {
114
+ try {
115
+ if (iterator?.throw) {
116
+ return await iterator.throw(error);
117
+ }
118
+ throw error;
119
+ }
120
+ catch (thrown) {
121
+ finishSpan(settings, active, thrown);
122
+ throw thrown;
123
+ }
124
+ },
125
+ [Symbol.asyncIterator]: () => stream,
126
+ };
127
+ return stream;
128
+ };
129
+ const observeConnection = (inner, settings, registry) => {
130
+ const state = { inTransaction: false };
131
+ const observed = {
132
+ ...(inner.cancelQuery && { cancelQuery: inner.cancelQuery.bind(inner) }),
133
+ ...(inner.collectSessionInfo && { collectSessionInfo: inner.collectSessionInfo.bind(inner) }),
134
+ ...(inner.killSession && { killSession: inner.killSession.bind(inner) }),
135
+ executeQuery: (...args) => observeCall(settings, state, args[0].sql, () => inner.executeQuery(...args)),
136
+ streamQuery: (...args) => observeStream(settings, state, args[0].sql, () => inner.streamQuery(...args)),
137
+ };
138
+ registry.set(observed, { inner, state });
139
+ return observed;
140
+ };
141
+ const observeDriver = (inner, settings) => {
142
+ const registry = new WeakMap();
143
+ const resolve = (connection) => registry.get(connection) ?? { inner: connection, state: { inTransaction: false } };
144
+ const lifecycle = async (statement, connection, run, inTransactionAfter) => {
145
+ const { inner: raw, state } = resolve(connection);
146
+ const wasInTransaction = state.inTransaction;
147
+ state.inTransaction = true;
148
+ try {
149
+ await observeCall(settings, state, statement, () => run(raw));
150
+ state.inTransaction = inTransactionAfter;
151
+ }
152
+ catch (error) {
153
+ state.inTransaction = statement === "begin" ? wasInTransaction : false;
154
+ throw error;
155
+ }
156
+ };
157
+ const savepoint = inner.savepoint?.bind(inner);
158
+ const rollbackToSavepoint = inner.rollbackToSavepoint?.bind(inner);
159
+ const releaseSavepoint = inner.releaseSavepoint?.bind(inner);
160
+ return {
161
+ init: (options) => inner.init(options),
162
+ acquireConnection: async (options) => observeConnection(await inner.acquireConnection(options), settings, registry),
163
+ beginTransaction: (connection, transactionSettings) => lifecycle("begin", connection, (raw) => inner.beginTransaction(raw, transactionSettings), true),
164
+ commitTransaction: (connection) => lifecycle("commit", connection, (raw) => inner.commitTransaction(raw), false),
165
+ rollbackTransaction: (connection) => lifecycle("rollback", connection, (raw) => inner.rollbackTransaction(raw), false),
166
+ ...(savepoint && {
167
+ savepoint: (connection, name, compile) => savepoint(resolve(connection).inner, name, compile),
168
+ }),
169
+ ...(rollbackToSavepoint && {
170
+ rollbackToSavepoint: (connection, name, compile) => rollbackToSavepoint(resolve(connection).inner, name, compile),
171
+ }),
172
+ ...(releaseSavepoint && {
173
+ releaseSavepoint: (connection, name, compile) => releaseSavepoint(resolve(connection).inner, name, compile),
174
+ }),
175
+ releaseConnection: (connection, options) => inner.releaseConnection(resolve(connection).inner, options),
176
+ destroy: (options) => inner.destroy(options),
177
+ };
178
+ };
179
+ export const observeDialect = (options) => {
180
+ const { dialect } = options;
181
+ const settings = {
182
+ dbSystem: options.dbSystem,
183
+ recordQueryText: options.recordQueryText ?? true,
184
+ recordErrors: options.recordErrors ?? true,
185
+ requireParentSpan: options.requireParentSpan ?? false,
186
+ connection: options.connection ?? {},
187
+ };
188
+ return {
189
+ createDriver: () => observeDriver(dialect.createDriver(), settings),
190
+ createQueryCompiler: () => dialect.createQueryCompiler(),
191
+ createAdapter: () => dialect.createAdapter(),
192
+ createIntrospector: (db) => dialect.createIntrospector(db),
193
+ };
194
+ };
@@ -0,0 +1,8 @@
1
+ import type { RegisterPgPoolMetricsOptions } from "./types.ts";
2
+ export declare const POOL_METRIC_NAMES: {
3
+ readonly used: "db.client.connection.used";
4
+ readonly idle: "db.client.connection.idle";
5
+ readonly pending: "db.client.connection.pending_requests";
6
+ readonly limit: "db.client.connection.max";
7
+ };
8
+ export declare const registerPgPoolMetrics: (options: RegisterPgPoolMetricsOptions) => () => void;
@@ -0,0 +1,52 @@
1
+ import { metrics } from "@opentelemetry/api";
2
+ const METER_NAME = "@unhandled/instrumentation-kysely";
3
+ const METER_VERSION = "0.1.0";
4
+ export const POOL_METRIC_NAMES = {
5
+ used: "db.client.connection.used",
6
+ idle: "db.client.connection.idle",
7
+ pending: "db.client.connection.pending_requests",
8
+ limit: "db.client.connection.max",
9
+ };
10
+ const ATTR_POOL_NAME = "db.client.connection.pool.name";
11
+ const isPgPool = (value) => typeof value === "object" &&
12
+ value !== null &&
13
+ "connect" in value &&
14
+ typeof value.connect === "function" &&
15
+ "totalCount" in value &&
16
+ typeof value.totalCount === "number" &&
17
+ "idleCount" in value &&
18
+ typeof value.idleCount === "number" &&
19
+ "waitingCount" in value &&
20
+ typeof value.waitingCount === "number";
21
+ export const registerPgPoolMetrics = (options) => {
22
+ const { pool, max, poolName } = options;
23
+ if (!isPgPool(pool)) {
24
+ throw new TypeError("registerPgPoolMetrics() only supports a pg.Pool (an object with totalCount, idleCount, waitingCount and connect). postgres.js and other drivers expose no pool counters.");
25
+ }
26
+ const meter = metrics.getMeter(METER_NAME, METER_VERSION);
27
+ const used = meter.createObservableGauge(POOL_METRIC_NAMES.used, {
28
+ description: "Connections currently checked out of the pool",
29
+ unit: "{connection}",
30
+ });
31
+ const idle = meter.createObservableGauge(POOL_METRIC_NAMES.idle, {
32
+ description: "Connections open and idle in the pool",
33
+ unit: "{connection}",
34
+ });
35
+ const pending = meter.createObservableGauge(POOL_METRIC_NAMES.pending, {
36
+ description: "Requests waiting for a connection",
37
+ unit: "{request}",
38
+ });
39
+ const limit = meter.createObservableGauge(POOL_METRIC_NAMES.limit, {
40
+ description: "Maximum number of connections the pool allows",
41
+ unit: "{connection}",
42
+ });
43
+ const attributes = { [ATTR_POOL_NAME]: poolName };
44
+ const observe = (result) => {
45
+ result.observe(used, pool.totalCount - pool.idleCount, attributes);
46
+ result.observe(idle, pool.idleCount, attributes);
47
+ result.observe(pending, pool.waitingCount, attributes);
48
+ result.observe(limit, max, attributes);
49
+ };
50
+ meter.addBatchObservableCallback(observe, [used, idle, pending, limit]);
51
+ return () => meter.removeBatchObservableCallback(observe, [used, idle, pending, limit]);
52
+ };
@@ -0,0 +1,5 @@
1
+ export interface ParsedSql {
2
+ collection?: string;
3
+ operation?: string;
4
+ }
5
+ export declare const parseSql: (sql: string) => ParsedSql;
@@ -0,0 +1,151 @@
1
+ const WHITESPACE_CHAR_PATTERN = /\s/;
2
+ const STATEMENTS = new Set(["SELECT", "INSERT", "UPDATE", "DELETE", "MERGE"]);
3
+ const DDL = new Set(["CREATE", "ALTER", "DROP", "TRUNCATE"]);
4
+ const WORD = /[A-Za-z_]/;
5
+ const WORD_PART = /[\w$]/;
6
+ const skipBlanks = (sql, from) => {
7
+ let index = from;
8
+ while (index < sql.length) {
9
+ if (WHITESPACE_CHAR_PATTERN.test(sql[index])) {
10
+ index += 1;
11
+ }
12
+ else if (sql.startsWith("--", index)) {
13
+ const end = sql.indexOf("\n", index);
14
+ index = end === -1 ? sql.length : end + 1;
15
+ }
16
+ else if (sql.startsWith("/*", index)) {
17
+ const end = sql.indexOf("*/", index + 2);
18
+ index = end === -1 ? sql.length : end + 2;
19
+ }
20
+ else {
21
+ break;
22
+ }
23
+ }
24
+ return index;
25
+ };
26
+ const skipQuoted = (sql, from, close) => {
27
+ let index = from + 1;
28
+ while (index < sql.length) {
29
+ if (sql[index] === close) {
30
+ if (sql[index + 1] === close) {
31
+ index += 2;
32
+ continue;
33
+ }
34
+ return index + 1;
35
+ }
36
+ index += 1;
37
+ }
38
+ return sql.length;
39
+ };
40
+ const readWord = (sql, from) => {
41
+ if (!WORD.test(sql[from] ?? "")) {
42
+ return undefined;
43
+ }
44
+ let end = from + 1;
45
+ while (end < sql.length && WORD_PART.test(sql[end])) {
46
+ end += 1;
47
+ }
48
+ return { text: sql.slice(from, end), end };
49
+ };
50
+ const findKeyword = (sql, keywords, from) => {
51
+ let depth = 0;
52
+ let index = from;
53
+ while (index < sql.length) {
54
+ const char = sql[index];
55
+ if (char === "'" || char === '"' || char === "`") {
56
+ index = skipQuoted(sql, index, char);
57
+ }
58
+ else if (char === "[") {
59
+ index = skipQuoted(sql, index, "]");
60
+ }
61
+ else if (char === "(") {
62
+ depth += 1;
63
+ index += 1;
64
+ }
65
+ else if (char === ")") {
66
+ depth -= 1;
67
+ index += 1;
68
+ }
69
+ else if (WORD.test(char) && (index === 0 || !WORD_PART.test(sql[index - 1]))) {
70
+ const word = readWord(sql, index);
71
+ if (word && depth === 0 && keywords.has(word.text.toUpperCase())) {
72
+ return word;
73
+ }
74
+ index = word ? word.end : index + 1;
75
+ }
76
+ else {
77
+ index += 1;
78
+ }
79
+ }
80
+ return undefined;
81
+ };
82
+ const readIdentifier = (sql, from) => {
83
+ const char = sql[from];
84
+ if (char === '"' || char === "`" || char === "[") {
85
+ const end = skipQuoted(sql, from, char === "[" ? "]" : char);
86
+ return { text: sql.slice(from + 1, end - 1), end };
87
+ }
88
+ return readWord(sql, from);
89
+ };
90
+ const readQualifiedName = (sql, from) => {
91
+ const parts = [];
92
+ let index = skipBlanks(sql, from);
93
+ for (;;) {
94
+ const part = readIdentifier(sql, index);
95
+ if (!part) {
96
+ break;
97
+ }
98
+ parts.push(part.text);
99
+ const next = skipBlanks(sql, part.end);
100
+ if (sql[next] !== ".") {
101
+ break;
102
+ }
103
+ index = skipBlanks(sql, next + 1);
104
+ }
105
+ return parts.length > 0 ? parts.join(".") : undefined;
106
+ };
107
+ const SKIPPED_MODIFIERS = new Set(["ONLY", "IF", "NOT", "EXISTS"]);
108
+ const readTarget = (sql, from) => {
109
+ let index = skipBlanks(sql, from);
110
+ for (;;) {
111
+ const word = readWord(sql, index);
112
+ if (!(word && SKIPPED_MODIFIERS.has(word.text.toUpperCase()))) {
113
+ break;
114
+ }
115
+ index = skipBlanks(sql, word.end);
116
+ }
117
+ return readQualifiedName(sql, index);
118
+ };
119
+ const statementStart = (sql, from) => {
120
+ const first = readWord(sql, from);
121
+ if (first?.text.toUpperCase() !== "WITH") {
122
+ return from;
123
+ }
124
+ const main = findKeyword(sql, STATEMENTS, first.end);
125
+ return main ? main.end - main.text.length : from;
126
+ };
127
+ export const parseSql = (sql) => {
128
+ const start = statementStart(sql, skipBlanks(sql, 0));
129
+ const first = readWord(sql, start);
130
+ if (!first) {
131
+ return {};
132
+ }
133
+ const operation = first.text.toUpperCase();
134
+ const after = first.end;
135
+ if (operation === "SELECT" || operation === "DELETE") {
136
+ const from = findKeyword(sql, new Set(["FROM"]), after);
137
+ return { operation, collection: from ? readTarget(sql, from.end) : undefined };
138
+ }
139
+ if (operation === "INSERT" || operation === "MERGE") {
140
+ const into = findKeyword(sql, new Set(["INTO"]), after);
141
+ return { operation, collection: into ? readTarget(sql, into.end) : undefined };
142
+ }
143
+ if (operation === "UPDATE") {
144
+ return { operation, collection: readTarget(sql, after) };
145
+ }
146
+ if (DDL.has(operation)) {
147
+ const table = findKeyword(sql, new Set(["TABLE"]), after);
148
+ return { operation, collection: table ? readTarget(sql, table.end) : undefined };
149
+ }
150
+ return { operation };
151
+ };
@@ -0,0 +1,25 @@
1
+ import type { Dialect } from "kysely";
2
+ export interface ConnectionInfo {
3
+ database?: string;
4
+ host?: string;
5
+ port?: number;
6
+ }
7
+ export interface ObserveDialectOptions {
8
+ connection?: ConnectionInfo;
9
+ dbSystem: string;
10
+ dialect: Dialect;
11
+ recordErrors?: boolean;
12
+ recordQueryText?: boolean;
13
+ requireParentSpan?: boolean;
14
+ }
15
+ export interface PgPoolLike {
16
+ connect: (...args: never[]) => unknown;
17
+ idleCount: number;
18
+ totalCount: number;
19
+ waitingCount: number;
20
+ }
21
+ export interface RegisterPgPoolMetricsOptions {
22
+ max: number;
23
+ pool: unknown;
24
+ poolName: string;
25
+ }
package/build/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,57 @@
1
1
  {
2
2
  "name": "@unhandled/instrumentation-kysely",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.0.1",
4
+ "description": "Kysely query tracing and pg pool metrics for Unhandled",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/treboryx/unhandled.git",
8
+ "directory": "packages/instrumentation-kysely"
9
+ },
10
+ "license": "MIT",
11
+ "type": "module",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./build/index.d.ts",
15
+ "default": "./build/index.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "build"
20
+ ],
21
+ "dependencies": {
22
+ "@opentelemetry/api": "~1.9.1",
23
+ "@opentelemetry/core": "^2.12.0",
24
+ "@unhandled/protocol": "0.0.1"
25
+ },
26
+ "devDependencies": {
27
+ "@adonisjs/tsconfig": "^2.0.0",
28
+ "@opentelemetry/context-async-hooks": "^2.12.0",
29
+ "@opentelemetry/exporter-metrics-otlp-http": "^0.223.0",
30
+ "@opentelemetry/exporter-trace-otlp-http": "^0.223.0",
31
+ "@opentelemetry/sdk-metrics": "^2.12.0",
32
+ "@opentelemetry/sdk-trace-base": "^2.12.0",
33
+ "@types/node": "^25.9.9",
34
+ "@types/pg": "^8.23.1",
35
+ "@unhandled/otlp-receiver": "0.0.0",
36
+ "kysely": "^0.29.7",
37
+ "kysely-postgres-js": "^5.0.1",
38
+ "pg": "^8.23.1",
39
+ "postgres": "^3.4.9",
40
+ "typescript": "~6.0.2"
41
+ },
42
+ "peerDependencies": {
43
+ "kysely": "^0.28.16 || ^0.29.6"
44
+ },
45
+ "engines": {
46
+ "node": ">=24.0.0"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "scripts": {
52
+ "build": "tsc --project tsconfig.build.json",
53
+ "test": "node --test 'tests/**/*.test.ts'",
54
+ "typecheck": "tsrs --noEmit",
55
+ "typecheck:tsc": "tsc --noEmit"
56
+ }
6
57
  }