Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 28 additions & 8 deletions src/instrumentation/libraries/pg/Instrumentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ export class PgInstrumentation extends TdInstrumentationBase {
// Submittable queries use EventEmitter pattern (row, end, error events)
// and return the Query object itself, not a Promise
if (self.isSubmittable(args[0])) {
logger.debug(`[PgInstrumentation] Submittable query detected, passing through uninstrumented`);
logger.debug(
`[PgInstrumentation] Submittable query detected, passing through uninstrumented`,
);
return originalQuery.apply(this, args);
}

Expand Down Expand Up @@ -365,9 +367,25 @@ export class PgInstrumentation extends TdInstrumentationBase {
return originalQuery.apply(context, args);
} else {
// Promise-based query
const promise = originalQuery.apply(context, args);
const result = originalQuery.apply(context, args);

// ORMs like Prisma, TypeORM, etc. may call pg.query() internally in ways
// that return undefined instead of a Promise (e.g. Prisma's driver adapter).
// Without this guard, we'd crash with "Cannot read properties of undefined
// (reading 'then')".
if (!result || typeof result.then !== "function") {
logger.debug(
`[PgInstrumentation] originalQuery returned non-thenable, passing through (${SpanUtils.getTraceInfo()})`,
);
try {
SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.OK });
} catch (error) {
logger.error(`[PgInstrumentation] error ending span:`, error);
}
return result;
}

return promise
return result
.then((result: PgResult) => {
logger.debug(
`[PgInstrumentation] PG query completed successfully (${SpanUtils.getTraceInfo()})`,
Expand Down Expand Up @@ -497,23 +515,25 @@ export class PgInstrumentation extends TdInstrumentationBase {
*
* Reference for data type IDs: https://jdbc.postgresql.org/documentation/publicapi/constant-values.html
*/
private convertPostgresTypes(result: any, rowMode?: 'array'): any {
private convertPostgresTypes(result: any, rowMode?: "array"): any {
// Handle multi-statement results (wrapped format from _addOutputAttributesToSpan)
if (result && result.isMultiStatement && Array.isArray(result.results)) {
return result.results.map((singleResult: any) => this.convertPostgresTypes(singleResult, rowMode));
return result.results.map((singleResult: any) =>
this.convertPostgresTypes(singleResult, rowMode),
);
}

// Handle multi-statement results (array of Results - legacy/direct format)
if (Array.isArray(result)) {
return result.map(singleResult => this.convertPostgresTypes(singleResult, rowMode));
return result.map((singleResult) => this.convertPostgresTypes(singleResult, rowMode));
}

if (!result || !result.fields || !result.rows) {
return result;
}

// If rowMode is 'array', handle arrays differently
if (rowMode === 'array') {
if (rowMode === "array") {
// For array mode, rows are arrays of values indexed by column position
const convertedRows = result.rows.map((row: any) => {
if (!Array.isArray(row)) return row; // Safety check
Expand Down Expand Up @@ -655,7 +675,7 @@ export class PgInstrumentation extends TdInstrumentationBase {
// Wrap in object with 'results' key to ensure CLI can handle it properly
outputValue = {
isMultiStatement: true,
results: result.map(r => ({
results: result.map((r) => ({
command: r.command,
rowCount: r.rowCount,
oid: r.oid,
Expand Down
15 changes: 12 additions & 3 deletions src/nextjs/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,25 @@ import type { ParsedVersion } from "./types";
* @returns The Next.js version string, or undefined if not found
*/
export function getNextjsVersion(): string | undefined {
// Try cwd-based lookup first (works for standard project layouts)
try {
// Try to read from node_modules/next/package.json
const nextPackageJsonPath = path.join(process.cwd(), "node_modules", "next", "package.json");

if (fs.existsSync(nextPackageJsonPath)) {
const packageJson = JSON.parse(fs.readFileSync(nextPackageJsonPath, "utf-8"));
return packageJson.version;
}
} catch (error) {
// Silent failure - we'll warn the user in the main function
} catch {
// Fall through to require.resolve
}

// Fallback: use Node's module resolution, which handles hoisted packages in monorepos
try {
const resolvedPath = require.resolve("next/package.json", { paths: [process.cwd()] });
const packageJson = JSON.parse(fs.readFileSync(resolvedPath, "utf-8"));
return packageJson.version;
} catch {
// next is not resolvable
}

return undefined;
Expand Down
4 changes: 4 additions & 0 deletions src/nextjs/withTuskDrift.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ export function withTuskDrift(
// This ensures packages remain external regardless of which bundler is used
serverExternalPackages: [...(config.serverExternalPackages || []), ...coreExternals],

// Preserve user's turbopack config (or set empty) so Next.js 16+ doesn't error
// when it sees our webpack config without a turbopack counterpart
turbopack: (config as any).turbopack || {},

webpack: (webpackConfig: any, webpackOptions: any) => {
if (webpackOptions.isServer) {
// Safely handle different externals formats (array, function, object, or undefined)
Expand Down
Loading