opencons 0.1.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 (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +382 -0
  3. package/opencons.d.ts +55 -0
  4. package/package.json +73 -0
  5. package/scripts/vendor-d3.js +22 -0
  6. package/src/core/context.js +44 -0
  7. package/src/core/index.js +198 -0
  8. package/src/core/tracer.js +252 -0
  9. package/src/drivers/db-language.js +207 -0
  10. package/src/drivers/detect.js +62 -0
  11. package/src/drivers/drizzle.js +87 -0
  12. package/src/drivers/index.js +43 -0
  13. package/src/drivers/mongoose.js +89 -0
  14. package/src/drivers/mysql2.js +116 -0
  15. package/src/drivers/pg.js +130 -0
  16. package/src/drivers/prisma.js +109 -0
  17. package/src/drivers/record.js +158 -0
  18. package/src/index.js +28 -0
  19. package/src/integrations/nest-lifecycle.js +357 -0
  20. package/src/integrations/nest.js +89 -0
  21. package/src/interceptors/express.js +270 -0
  22. package/src/interceptors/require-hook.js +109 -0
  23. package/src/lib/config.js +139 -0
  24. package/src/lib/errors.js +54 -0
  25. package/src/lib/http-response.js +37 -0
  26. package/src/lib/logger.js +69 -0
  27. package/src/lib/serialize.js +22 -0
  28. package/src/server/static.js +165 -0
  29. package/src/server/ws.js +62 -0
  30. package/src/store/source-cache.js +120 -0
  31. package/src/store/trace-store.js +117 -0
  32. package/src/transform/ast.js +255 -0
  33. package/src/transform/natural-language.js +146 -0
  34. package/src/transform/probe.js +161 -0
  35. package/src/transform/register.js +44 -0
  36. package/src/utils/label.js +26 -0
  37. package/src/utils/observable.js +103 -0
  38. package/widget/app.js +356 -0
  39. package/widget/db-language.js +90 -0
  40. package/widget/graph.js +1167 -0
  41. package/widget/index.html +132 -0
  42. package/widget/styles.css +773 -0
  43. package/widget/timeline.js +57 -0
  44. package/widget/vendor/d3.min.js +2 -0
@@ -0,0 +1,87 @@
1
+ 'use strict';
2
+
3
+ const { traceDbCall, truncateQuery } = require('./record');
4
+ const { extractTableFromSql } = require('./db-language');
5
+
6
+ let patched = false;
7
+
8
+ /**
9
+ * @param {Function} original
10
+ * @param {'node-postgres' | 'mysql2'} dialect
11
+ */
12
+ function wrapExecute(original, dialect) {
13
+ return async function OpenconsDrizzleExecute(placeholderValues = {}) {
14
+ const queryText =
15
+ this.rawQueryConfig?.text ||
16
+ this.rawQuery?.sql ||
17
+ this.query?.sql ||
18
+ this.queryString ||
19
+ '';
20
+
21
+ return traceDbCall(() => original.call(this, placeholderValues), {
22
+ driver: 'drizzle',
23
+ operation: dialect === 'mysql2' ? 'execute' : 'query',
24
+ query: truncateQuery(queryText),
25
+ params: this.params,
26
+ collection: extractTableFromSql(queryText),
27
+ });
28
+ };
29
+ }
30
+
31
+ /**
32
+ * @param {string} modulePath
33
+ * @param {string} className
34
+ * @param {'node-postgres' | 'mysql2'} dialect
35
+ */
36
+ function patchPreparedQuery(modulePath, className, dialect) {
37
+ let session;
38
+
39
+ try {
40
+ const { createRequire } = require('module');
41
+ const path = require('path');
42
+ const hostRequire = createRequire(path.join(process.cwd(), 'package.json'));
43
+ session = hostRequire(modulePath);
44
+ } catch {
45
+ try {
46
+ session = require(modulePath);
47
+ } catch {
48
+ return false;
49
+ }
50
+ }
51
+
52
+ const PreparedQuery = session[className];
53
+
54
+ if (!PreparedQuery?.prototype?.execute || PreparedQuery.prototype.execute.__openconsWrapped) {
55
+ return false;
56
+ }
57
+
58
+ const original = PreparedQuery.prototype.execute;
59
+ PreparedQuery.prototype.execute = wrapExecute(original, dialect);
60
+ PreparedQuery.prototype.execute.__openconsWrapped = true;
61
+
62
+ return true;
63
+ }
64
+
65
+ function patchDrizzle() {
66
+ if (patched) return [];
67
+
68
+ const backends = [];
69
+
70
+ if (patchPreparedQuery('drizzle-orm/node-postgres/session', 'NodePgPreparedQuery', 'node-postgres')) {
71
+ backends.push('node-postgres');
72
+ }
73
+
74
+ if (patchPreparedQuery('drizzle-orm/mysql2/session', 'MySql2PreparedQuery', 'mysql2')) {
75
+ backends.push('mysql2');
76
+ }
77
+
78
+ if (backends.length) {
79
+ patched = true;
80
+ }
81
+
82
+ return backends;
83
+ }
84
+
85
+ module.exports = {
86
+ patchDrizzle,
87
+ };
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ const { resolveDriverConfig } = require('./detect');
4
+ const { patchPg } = require('./pg');
5
+ const { patchMongoose } = require('./mongoose');
6
+ const { patchPrisma } = require('./prisma');
7
+ const { patchMysql2 } = require('./mysql2');
8
+ const { patchDrizzle } = require('./drizzle');
9
+
10
+ let installed = false;
11
+
12
+ /**
13
+ * @param {Partial<Record<'mongoose' | 'pg' | 'prisma' | 'mysql2' | 'drizzle', boolean>>} [config]
14
+ */
15
+ function installDrivers(config = {}) {
16
+ if (installed) return { patched: [] };
17
+
18
+ const resolved = resolveDriverConfig(config);
19
+ const patched = [];
20
+
21
+ if (resolved.drizzle) {
22
+ const backends = patchDrizzle();
23
+ if (backends.length) patched.push(`drizzle (${backends.join(', ')})`);
24
+ }
25
+
26
+ if (resolved.pg && patchPg()) patched.push('pg');
27
+ if (resolved.mongoose && patchMongoose()) patched.push('mongoose');
28
+ if (resolved.prisma && patchPrisma()) patched.push('prisma');
29
+ if (resolved.mysql2 && patchMysql2()) patched.push('mysql2');
30
+
31
+ installed = patched.length > 0;
32
+
33
+ if (patched.length) {
34
+ const { logger } = require('../lib/logger');
35
+ logger.info(`Database drivers patched: ${patched.join(', ')}`);
36
+ }
37
+
38
+ return { patched, resolved };
39
+ }
40
+
41
+ module.exports = {
42
+ installDrivers,
43
+ };
@@ -0,0 +1,89 @@
1
+ 'use strict';
2
+
3
+ const { traceDbCall, truncateQuery, safeParams } = require('./record');
4
+
5
+ let patched = false;
6
+
7
+ /**
8
+ * @param {import('mongoose').Query} query
9
+ */
10
+ function describeMongooseQuery(query) {
11
+ const op = query.op || 'query';
12
+ const collection = query.model?.collection?.name || query.mongooseCollection?.name;
13
+ const filter = typeof query.getFilter === 'function' ? query.getFilter() : {};
14
+ const update =
15
+ typeof query.getUpdate === 'function' ? query.getUpdate() : undefined;
16
+
17
+ let text = `${op} ${JSON.stringify(filter)}`;
18
+ if (update) {
19
+ text += ` update=${JSON.stringify(update)}`;
20
+ }
21
+
22
+ return {
23
+ operation: op,
24
+ collection,
25
+ query: truncateQuery(text),
26
+ params: safeParams(filter),
27
+ };
28
+ }
29
+
30
+ function patchMongoose() {
31
+ if (patched) return false;
32
+
33
+ let mongoose;
34
+
35
+ try {
36
+ const { createRequire } = require('module');
37
+ const path = require('path');
38
+ const hostRequire = createRequire(path.join(process.cwd(), 'package.json'));
39
+ mongoose = hostRequire('mongoose');
40
+ } catch {
41
+ try {
42
+ mongoose = require('mongoose');
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
47
+
48
+ if (!mongoose.Query?.prototype?.exec || mongoose.Query.prototype.exec.__openconsWrapped) {
49
+ return false;
50
+ }
51
+
52
+ const originalExec = mongoose.Query.prototype.exec;
53
+
54
+ mongoose.Query.prototype.exec = function OpenconsMongooseExec(...args) {
55
+ const meta = describeMongooseQuery(this);
56
+ return traceDbCall(() => originalExec.apply(this, args), {
57
+ driver: 'mongoose',
58
+ ...meta,
59
+ });
60
+ };
61
+
62
+ mongoose.Query.prototype.exec.__openconsWrapped = true;
63
+
64
+ if (mongoose.Aggregate?.prototype?.exec && !mongoose.Aggregate.prototype.exec.__openconsWrapped) {
65
+ const originalAggregateExec = mongoose.Aggregate.prototype.exec;
66
+
67
+ mongoose.Aggregate.prototype.exec = function OpenconsAggregateExec(...args) {
68
+ const collection = this._model?.collection?.name;
69
+ const pipeline = this.pipeline();
70
+
71
+ return traceDbCall(() => originalAggregateExec.apply(this, args), {
72
+ driver: 'mongoose',
73
+ operation: 'aggregate',
74
+ collection,
75
+ query: truncateQuery(`aggregate ${JSON.stringify(pipeline)}`),
76
+ params: safeParams(pipeline),
77
+ });
78
+ };
79
+
80
+ mongoose.Aggregate.prototype.exec.__openconsWrapped = true;
81
+ }
82
+
83
+ patched = true;
84
+ return true;
85
+ }
86
+
87
+ module.exports = {
88
+ patchMongoose,
89
+ };
@@ -0,0 +1,116 @@
1
+ 'use strict';
2
+
3
+ const { traceDbCall, truncateQuery } = require('./record');
4
+
5
+ let patched = false;
6
+
7
+ /**
8
+ * @param {Function} original
9
+ * @param {string} operation
10
+ */
11
+ function wrapMysqlMethod(original, operation) {
12
+ return function OpenconsMysql(...args) {
13
+ const sql = typeof args[0] === 'string' ? args[0] : args[0]?.sql;
14
+ const params = typeof args[0] === 'object' && args[0]?.sql ? args[0].values : args[1];
15
+ const callback = typeof args[args.length - 1] === 'function' ? args[args.length - 1] : null;
16
+
17
+ if (callback) {
18
+ const start = performance.now();
19
+ const wrapped = args.slice(0, -1);
20
+
21
+ wrapped.push((err, result) => {
22
+ const { recordDbQuery } = require('./record');
23
+
24
+ if (err) {
25
+ recordDbQuery({
26
+ driver: 'mysql2',
27
+ operation,
28
+ query: truncateQuery(sql || ''),
29
+ params,
30
+ duration_ms: Math.round((performance.now() - start) * 10) / 10,
31
+ error: err.message,
32
+ });
33
+ } else {
34
+ recordDbQuery({
35
+ driver: 'mysql2',
36
+ operation,
37
+ query: truncateQuery(sql || ''),
38
+ params,
39
+ rows: Array.isArray(result) ? result[0]?.affectedRows ?? result[0]?.length : result?.affectedRows,
40
+ duration_ms: Math.round((performance.now() - start) * 10) / 10,
41
+ });
42
+ }
43
+
44
+ callback(err, result);
45
+ });
46
+
47
+ return original.apply(this, wrapped);
48
+ }
49
+
50
+ const result = original.apply(this, args);
51
+
52
+ if (result && typeof result.then === 'function') {
53
+ return traceDbCall(() => result, {
54
+ driver: 'mysql2',
55
+ operation,
56
+ query: truncateQuery(sql || ''),
57
+ params,
58
+ });
59
+ }
60
+
61
+ return result;
62
+ };
63
+ }
64
+
65
+ /**
66
+ * @param {object} target
67
+ */
68
+ function patchConnectionLike(target) {
69
+ if (!target?.prototype) return;
70
+
71
+ if (target.prototype.query && !target.prototype.query.__openconsWrapped) {
72
+ target.prototype.query = wrapMysqlMethod(target.prototype.query, 'query');
73
+ target.prototype.query.__openconsWrapped = true;
74
+ }
75
+
76
+ if (target.prototype.execute && !target.prototype.execute.__openconsWrapped) {
77
+ target.prototype.execute = wrapMysqlMethod(target.prototype.execute, 'execute');
78
+ target.prototype.execute.__openconsWrapped = true;
79
+ }
80
+ }
81
+
82
+ function patchMysql2() {
83
+ if (patched) return false;
84
+
85
+ let mysql2;
86
+
87
+ try {
88
+ const { createRequire } = require('module');
89
+ const path = require('path');
90
+ const hostRequire = createRequire(path.join(process.cwd(), 'package.json'));
91
+ mysql2 = hostRequire('mysql2');
92
+ } catch {
93
+ try {
94
+ mysql2 = require('mysql2');
95
+ } catch {
96
+ return false;
97
+ }
98
+ }
99
+
100
+ patchConnectionLike(mysql2);
101
+ if (mysql2.createPool) {
102
+ const originalCreatePool = mysql2.createPool.bind(mysql2);
103
+ mysql2.createPool = function OpenconsCreatePool(...args) {
104
+ const pool = originalCreatePool(...args);
105
+ patchConnectionLike(pool.constructor);
106
+ return pool;
107
+ };
108
+ }
109
+
110
+ patched = true;
111
+ return true;
112
+ }
113
+
114
+ module.exports = {
115
+ patchMysql2,
116
+ };
@@ -0,0 +1,130 @@
1
+ 'use strict';
2
+
3
+ const { traceDbCall, truncateQuery } = require('./record');
4
+
5
+ let patched = false;
6
+
7
+ /**
8
+ * @param {unknown[]} args
9
+ */
10
+ function parsePgQueryArgs(args) {
11
+ if (!args.length) return { text: '', values: undefined };
12
+
13
+ if (typeof args[0] === 'string') {
14
+ return {
15
+ text: args[0],
16
+ values: args[1],
17
+ };
18
+ }
19
+
20
+ if (typeof args[0] === 'object' && args[0]) {
21
+ return {
22
+ text: args[0].text || args[0].name || '',
23
+ values: args[0].values,
24
+ };
25
+ }
26
+
27
+ return { text: '', values: undefined };
28
+ }
29
+
30
+ /**
31
+ * @param {Function} original
32
+ */
33
+ function wrapQuery(original) {
34
+ return function OpenconsPgQuery(...args) {
35
+ const { text, values } = parsePgQueryArgs(args);
36
+ const start = performance.now();
37
+ const lastArg = args[args.length - 1];
38
+
39
+ if (typeof lastArg === 'function') {
40
+ const callback = lastArg;
41
+ const wrappedArgs = args.slice(0, -1);
42
+
43
+ wrappedArgs.push((err, result) => {
44
+ const { recordDbQuery } = require('./record');
45
+
46
+ if (err) {
47
+ recordDbQuery({
48
+ driver: 'pg',
49
+ operation: 'query',
50
+ query: truncateQuery(text),
51
+ params: values,
52
+ duration_ms: Math.round((performance.now() - start) * 10) / 10,
53
+ error: err.message,
54
+ });
55
+ } else {
56
+ recordDbQuery({
57
+ driver: 'pg',
58
+ operation: 'query',
59
+ query: truncateQuery(text),
60
+ params: values,
61
+ rows: result?.rowCount,
62
+ duration_ms: Math.round((performance.now() - start) * 10) / 10,
63
+ });
64
+ }
65
+
66
+ callback(err, result);
67
+ });
68
+
69
+ return original.apply(this, wrappedArgs);
70
+ }
71
+
72
+ const result = original.apply(this, args);
73
+
74
+ if (result && typeof result.then === 'function') {
75
+ return traceDbCall(() => result, {
76
+ driver: 'pg',
77
+ operation: 'query',
78
+ query: truncateQuery(text),
79
+ params: values,
80
+ });
81
+ }
82
+
83
+ return result;
84
+ };
85
+ }
86
+
87
+ /**
88
+ * @param {object} client
89
+ */
90
+ function patchPgClientPrototype(client) {
91
+ if (!client?.prototype?.query || client.prototype.query.__openconsWrapped) {
92
+ return;
93
+ }
94
+
95
+ const original = client.prototype.query;
96
+ client.prototype.query = wrapQuery(original);
97
+ client.prototype.query.__openconsWrapped = true;
98
+ }
99
+
100
+ function patchPg() {
101
+ if (patched) return false;
102
+
103
+ let pg;
104
+
105
+ try {
106
+ const { createRequire } = require('module');
107
+ const path = require('path');
108
+ const hostRequire = createRequire(path.join(process.cwd(), 'package.json'));
109
+ pg = hostRequire('pg');
110
+ } catch {
111
+ try {
112
+ pg = require('pg');
113
+ } catch {
114
+ return false;
115
+ }
116
+ }
117
+
118
+ patchPgClientPrototype(pg.Client);
119
+
120
+ if (pg.Pool?.prototype) {
121
+ patchPgClientPrototype(pg.Pool);
122
+ }
123
+
124
+ patched = true;
125
+ return true;
126
+ }
127
+
128
+ module.exports = {
129
+ patchPg,
130
+ };
@@ -0,0 +1,109 @@
1
+ 'use strict';
2
+
3
+ const { traceDbCall, truncateQuery, safeParams } = require('./record');
4
+
5
+ /** @type {WeakSet<object>} */
6
+ const patchedClients = new WeakSet();
7
+
8
+ /**
9
+ * @param {object} client
10
+ */
11
+ function patchPrismaClient(client) {
12
+ if (!client || patchedClients.has(client)) return client;
13
+
14
+ if (typeof client.$use !== 'function') {
15
+ return client;
16
+ }
17
+
18
+ client.$use(async (params, next) => {
19
+ const query = `${params.model || 'raw'}.${params.action}`;
20
+ const start = performance.now();
21
+
22
+ try {
23
+ const result = await next(params);
24
+ const { recordDbQuery } = require('./record');
25
+
26
+ recordDbQuery({
27
+ driver: 'prisma',
28
+ operation: params.action,
29
+ collection: params.model,
30
+ query: truncateQuery(query),
31
+ params: safeParams(params.args),
32
+ rows: Array.isArray(result) ? result.length : result == null ? 0 : 1,
33
+ duration_ms: Math.round((performance.now() - start) * 10) / 10,
34
+ });
35
+
36
+ return result;
37
+ } catch (err) {
38
+ const { recordDbQuery } = require('./record');
39
+
40
+ recordDbQuery({
41
+ driver: 'prisma',
42
+ operation: params.action,
43
+ collection: params.model,
44
+ query: truncateQuery(query),
45
+ params: safeParams(params.args),
46
+ duration_ms: Math.round((performance.now() - start) * 10) / 10,
47
+ error: err && err.message ? err.message : String(err),
48
+ });
49
+
50
+ throw err;
51
+ }
52
+ });
53
+
54
+ patchedClients.add(client);
55
+ return client;
56
+ }
57
+
58
+ let prismaModulePatched = false;
59
+
60
+ function patchPrisma() {
61
+ if (prismaModulePatched) return false;
62
+
63
+ let PrismaClient;
64
+
65
+ try {
66
+ const { createRequire } = require('module');
67
+ const path = require('path');
68
+ const hostRequire = createRequire(path.join(process.cwd(), 'package.json'));
69
+ ({ PrismaClient } = hostRequire('@prisma/client'));
70
+ } catch {
71
+ try {
72
+ ({ PrismaClient } = require('@prisma/client'));
73
+ } catch {
74
+ return false;
75
+ }
76
+ }
77
+
78
+ if (!PrismaClient?.prototype || PrismaClient.prototype.__openconsWrapped) {
79
+ return false;
80
+ }
81
+
82
+ const Original = PrismaClient;
83
+
84
+ const WrappedPrismaClient = function OpenconsPrismaClient(...args) {
85
+ const client = new Original(...args);
86
+ return patchPrismaClient(client);
87
+ };
88
+
89
+ WrappedPrismaClient.prototype = Original.prototype;
90
+ Object.setPrototypeOf(WrappedPrismaClient, Original);
91
+ Object.assign(WrappedPrismaClient, Original);
92
+
93
+ try {
94
+ const clientModule = require('@prisma/client');
95
+ clientModule.PrismaClient = WrappedPrismaClient;
96
+ } catch {
97
+ // Host may load from a different path; constructor hook still helps new imports.
98
+ }
99
+
100
+ PrismaClient.prototype.constructor = WrappedPrismaClient;
101
+ PrismaClient.prototype.__openconsWrapped = true;
102
+ prismaModulePatched = true;
103
+ return true;
104
+ }
105
+
106
+ module.exports = {
107
+ patchPrisma,
108
+ patchPrismaClient,
109
+ };