@reproapp/node-sdk 0.0.30 → 0.0.31
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/dist/index.js +66 -6
- package/package.json +2 -2
- package/src/index.ts +71 -6
- package/test/mongodb-esm-import-hook.test.js +207 -0
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ const async_hooks_1 = require("async_hooks");
|
|
|
6
6
|
const crypto_1 = require("crypto");
|
|
7
7
|
const module_1 = require("module");
|
|
8
8
|
const path_1 = require("path");
|
|
9
|
+
const url_1 = require("url");
|
|
9
10
|
const client_1 = require("./ingest/client");
|
|
10
11
|
const privacy_1 = require("./privacy");
|
|
11
12
|
/**
|
|
@@ -7599,6 +7600,8 @@ function patchRedis(cfg) {
|
|
|
7599
7600
|
exports.patchRedis = patchRedis;
|
|
7600
7601
|
const mongoDbPatchConfigs = [];
|
|
7601
7602
|
let mongoDbRequireHookInstalled = false;
|
|
7603
|
+
let mongoDbImportHookInstalled = false;
|
|
7604
|
+
let mongoDbImportHookReady = null;
|
|
7602
7605
|
const MONGODB_PROMISE_OPS = [
|
|
7603
7606
|
'insertOne',
|
|
7604
7607
|
'insertMany',
|
|
@@ -7618,6 +7621,11 @@ const MONGODB_PROMISE_OPS = [
|
|
|
7618
7621
|
];
|
|
7619
7622
|
const MONGODB_CURSOR_OPS = ['find', 'aggregate'];
|
|
7620
7623
|
const MONGODB_CURSOR_TERMINALS = ['toArray', 'next', 'forEach'];
|
|
7624
|
+
function registerMongoDbPatchConfig(cfg) {
|
|
7625
|
+
if (!mongoDbPatchConfigs.includes(cfg)) {
|
|
7626
|
+
mongoDbPatchConfigs.push(cfg);
|
|
7627
|
+
}
|
|
7628
|
+
}
|
|
7621
7629
|
function resolveMongoDbRuntimeContext(cfg) {
|
|
7622
7630
|
const ctx = getCtx();
|
|
7623
7631
|
const fallback = cfg.resolveContext?.() ?? {};
|
|
@@ -7627,6 +7635,18 @@ function resolveMongoDbRuntimeContext(cfg) {
|
|
|
7627
7635
|
aid: ctx.aid ?? fallback.aid ?? null,
|
|
7628
7636
|
};
|
|
7629
7637
|
}
|
|
7638
|
+
function resolveMongoDbCaptureContext() {
|
|
7639
|
+
for (let i = mongoDbPatchConfigs.length - 1; i >= 0; i -= 1) {
|
|
7640
|
+
const cfg = mongoDbPatchConfigs[i];
|
|
7641
|
+
if (!cfg)
|
|
7642
|
+
continue;
|
|
7643
|
+
const { sid, aid } = resolveMongoDbRuntimeContext(cfg);
|
|
7644
|
+
if (sid) {
|
|
7645
|
+
return { cfg, sid, aid };
|
|
7646
|
+
}
|
|
7647
|
+
}
|
|
7648
|
+
return null;
|
|
7649
|
+
}
|
|
7630
7650
|
function resolveMongoCollectionName(collection) {
|
|
7631
7651
|
const candidates = [
|
|
7632
7652
|
collection?.collectionName,
|
|
@@ -7911,10 +7931,11 @@ function patchMongoCollectionMethod(proto, op, cursorOp, cfg) {
|
|
|
7911
7931
|
if (isNativeMongoSuppressed(collection, op)) {
|
|
7912
7932
|
return original.apply(this, args);
|
|
7913
7933
|
}
|
|
7914
|
-
const
|
|
7915
|
-
if (!
|
|
7934
|
+
const captureContext = resolveMongoDbCaptureContext();
|
|
7935
|
+
if (!captureContext) {
|
|
7916
7936
|
return original.apply(this, args);
|
|
7917
7937
|
}
|
|
7938
|
+
const { cfg: activeCfg, sid, aid } = captureContext;
|
|
7918
7939
|
const spanContext = captureSpanContextFromTracer(this);
|
|
7919
7940
|
const t0 = Date.now();
|
|
7920
7941
|
const dbName = resolveMongoDbName(this);
|
|
@@ -7925,7 +7946,7 @@ function patchMongoCollectionMethod(proto, op, cursorOp, cfg) {
|
|
|
7925
7946
|
if (emitted)
|
|
7926
7947
|
return;
|
|
7927
7948
|
emitted = true;
|
|
7928
|
-
emitDbQuery(
|
|
7949
|
+
emitDbQuery(activeCfg, sid, aid ?? undefined, {
|
|
7929
7950
|
type: 'mongo',
|
|
7930
7951
|
collection,
|
|
7931
7952
|
op,
|
|
@@ -7943,7 +7964,7 @@ function patchMongoCollectionMethod(proto, op, cursorOp, cfg) {
|
|
|
7943
7964
|
if (emitted)
|
|
7944
7965
|
return;
|
|
7945
7966
|
emitted = true;
|
|
7946
|
-
emitDbQuery(
|
|
7967
|
+
emitDbQuery(activeCfg, sid, aid ?? undefined, {
|
|
7947
7968
|
type: 'mongo',
|
|
7948
7969
|
collection,
|
|
7949
7970
|
op,
|
|
@@ -7968,7 +7989,7 @@ function patchMongoCollectionMethod(proto, op, cursorOp, cfg) {
|
|
|
7968
7989
|
const result = original.apply(this, actualArgs);
|
|
7969
7990
|
if (cursorOp) {
|
|
7970
7991
|
return patchMongoCursorInstance(result, {
|
|
7971
|
-
cfg,
|
|
7992
|
+
cfg: activeCfg,
|
|
7972
7993
|
sid,
|
|
7973
7994
|
aid,
|
|
7974
7995
|
collection,
|
|
@@ -8053,6 +8074,7 @@ function patchMongoClientPrototype(proto, cfg) {
|
|
|
8053
8074
|
};
|
|
8054
8075
|
}
|
|
8055
8076
|
function patchMongoDbFromModuleExports(mongoModule, cfg) {
|
|
8077
|
+
registerMongoDbPatchConfig(cfg);
|
|
8056
8078
|
if (!mongoModule)
|
|
8057
8079
|
return;
|
|
8058
8080
|
const constructors = new Set();
|
|
@@ -8108,9 +8130,46 @@ function installMongoDbRequireHook() {
|
|
|
8108
8130
|
}
|
|
8109
8131
|
catch { }
|
|
8110
8132
|
}
|
|
8133
|
+
function installMongoDbImportHook() {
|
|
8134
|
+
if (mongoDbImportHookInstalled)
|
|
8135
|
+
return mongoDbImportHookReady ?? Promise.resolve();
|
|
8136
|
+
mongoDbImportHookInstalled = true;
|
|
8137
|
+
mongoDbImportHookReady = (async () => {
|
|
8138
|
+
try {
|
|
8139
|
+
const nodeModule = requireOptionalHostModule('node:module') ?? requireOptionalHostModule('module');
|
|
8140
|
+
if (typeof nodeModule?.register !== 'function')
|
|
8141
|
+
return;
|
|
8142
|
+
const iitm = requireOptionalHostModule('import-in-the-middle');
|
|
8143
|
+
const Hook = iitm?.Hook ?? iitm;
|
|
8144
|
+
if (typeof Hook !== 'function')
|
|
8145
|
+
return;
|
|
8146
|
+
let waitForAllMessagesAcknowledged;
|
|
8147
|
+
let registerOptions = { data: { include: ['mongodb'] } };
|
|
8148
|
+
if (typeof iitm?.createAddHookMessageChannel === 'function') {
|
|
8149
|
+
const channel = iitm.createAddHookMessageChannel();
|
|
8150
|
+
registerOptions = channel.registerOptions;
|
|
8151
|
+
waitForAllMessagesAcknowledged = channel.waitForAllMessagesAcknowledged;
|
|
8152
|
+
}
|
|
8153
|
+
nodeModule.register('import-in-the-middle/hook.mjs', (0, url_1.pathToFileURL)(__filename), registerOptions);
|
|
8154
|
+
// eslint-disable-next-line no-new
|
|
8155
|
+
new Hook(['mongodb'], (exports) => {
|
|
8156
|
+
for (const cfg of mongoDbPatchConfigs) {
|
|
8157
|
+
patchMongoDbFromModuleExports(exports, cfg);
|
|
8158
|
+
}
|
|
8159
|
+
return exports;
|
|
8160
|
+
});
|
|
8161
|
+
if (waitForAllMessagesAcknowledged) {
|
|
8162
|
+
await waitForAllMessagesAcknowledged();
|
|
8163
|
+
}
|
|
8164
|
+
}
|
|
8165
|
+
catch { }
|
|
8166
|
+
})();
|
|
8167
|
+
return mongoDbImportHookReady;
|
|
8168
|
+
}
|
|
8111
8169
|
function patchMongoDb(cfg) {
|
|
8112
|
-
|
|
8170
|
+
registerMongoDbPatchConfig(cfg);
|
|
8113
8171
|
installMongoDbRequireHook();
|
|
8172
|
+
void installMongoDbImportHook();
|
|
8114
8173
|
const mongoModules = new Set();
|
|
8115
8174
|
for (const loadedMongoModule of getLoadedModuleExportsByPackage('mongodb')) {
|
|
8116
8175
|
mongoModules.add(loadedMongoModule);
|
|
@@ -8628,6 +8687,7 @@ async function initRepro(cfg) {
|
|
|
8628
8687
|
patchAmqplib(integrationCfg);
|
|
8629
8688
|
patchRedis(integrationCfg);
|
|
8630
8689
|
patchMongoDb(integrationCfg);
|
|
8690
|
+
await installMongoDbImportHook();
|
|
8631
8691
|
patchSendgridMail(integrationCfg);
|
|
8632
8692
|
patchMongooseIntegration(reproAutoMiddlewareConfig);
|
|
8633
8693
|
await initializeRuntimePrivacyPolicyAtStartup(reproAutoMiddlewareConfig);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reproapp/node-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.31",
|
|
4
4
|
"description": "Repro Nest SDK",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"build": "tsc -p tsconfig.json",
|
|
13
13
|
"dev": "tsc -p tsconfig.json --watch --preserveWatchOutput",
|
|
14
14
|
"prepublishOnly": "npm run build",
|
|
15
|
-
"test": "npm run build && node test/unawaited.test.js && node test/integration-unawaited.js && node test/request-flush-timing.test.js && node test/request-capture-background.test.js && node test/init-auto-http-server.test.js && node test/client-performance-regression.test.js && node test/request-manifest-priority.test.js && node test/runtime-no-double-invoke.test.js && node test/express-trace-http-args.test.js && node test/trace-batch-size.test.js && node test/sdk-background-priority.test.js && node test/ingest-queue-retention.test.js && node test/ingest-high-priority-retention.test.js && node test/ingest-critical-retention.test.js && node test/ingest-critical-retry-retention.test.js && node test/ingest-timeout.test.js && node -r ./tracer/register test/promise-map.test.js && node test/disable-subtree.test.js && node test/circular-capture.test.js && node test/wrap-plugin-arrow-args.test.js && node test/wrap-plugin-super-call.test.js && node test/function-arg-identity.test.js && node test/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js && node test/amqplib-integration.test.js && node test/mongodb-integration.test.js",
|
|
15
|
+
"test": "npm run build && node test/unawaited.test.js && node test/integration-unawaited.js && node test/request-flush-timing.test.js && node test/request-capture-background.test.js && node test/init-auto-http-server.test.js && node test/client-performance-regression.test.js && node test/request-manifest-priority.test.js && node test/runtime-no-double-invoke.test.js && node test/express-trace-http-args.test.js && node test/trace-batch-size.test.js && node test/sdk-background-priority.test.js && node test/ingest-queue-retention.test.js && node test/ingest-high-priority-retention.test.js && node test/ingest-critical-retention.test.js && node test/ingest-critical-retry-retention.test.js && node test/ingest-timeout.test.js && node -r ./tracer/register test/promise-map.test.js && node test/disable-subtree.test.js && node test/circular-capture.test.js && node test/wrap-plugin-arrow-args.test.js && node test/wrap-plugin-super-call.test.js && node test/function-arg-identity.test.js && node test/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js && node test/amqplib-integration.test.js && node test/mongodb-integration.test.js && node test/mongodb-esm-import-hook.test.js",
|
|
16
16
|
"test:containers": "npm run build && node test/runtime-no-double-invoke.testcontainers.test.js && node test/wrap-plugin-super-call.testcontainers.test.js && node test/function-arg-identity.testcontainers.test.js"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { AsyncLocalStorage } from 'async_hooks';
|
|
|
5
5
|
import { randomUUID } from 'crypto';
|
|
6
6
|
import { createRequire } from 'module';
|
|
7
7
|
import { join } from 'path';
|
|
8
|
+
import { pathToFileURL } from 'url';
|
|
8
9
|
import {
|
|
9
10
|
configureIngestQueueDrain,
|
|
10
11
|
enqueueIngestEntries,
|
|
@@ -8810,6 +8811,8 @@ export type MongoDbPatchConfig = ReproMiddlewareConfig & {
|
|
|
8810
8811
|
|
|
8811
8812
|
const mongoDbPatchConfigs: MongoDbPatchConfig[] = [];
|
|
8812
8813
|
let mongoDbRequireHookInstalled = false;
|
|
8814
|
+
let mongoDbImportHookInstalled = false;
|
|
8815
|
+
let mongoDbImportHookReady: Promise<void> | null = null;
|
|
8813
8816
|
|
|
8814
8817
|
const MONGODB_PROMISE_OPS = [
|
|
8815
8818
|
'insertOne',
|
|
@@ -8832,6 +8835,12 @@ const MONGODB_PROMISE_OPS = [
|
|
|
8832
8835
|
const MONGODB_CURSOR_OPS = ['find', 'aggregate'] as const;
|
|
8833
8836
|
const MONGODB_CURSOR_TERMINALS = ['toArray', 'next', 'forEach'] as const;
|
|
8834
8837
|
|
|
8838
|
+
function registerMongoDbPatchConfig(cfg: MongoDbPatchConfig): void {
|
|
8839
|
+
if (!mongoDbPatchConfigs.includes(cfg)) {
|
|
8840
|
+
mongoDbPatchConfigs.push(cfg);
|
|
8841
|
+
}
|
|
8842
|
+
}
|
|
8843
|
+
|
|
8835
8844
|
function resolveMongoDbRuntimeContext(cfg: MongoDbPatchConfig) {
|
|
8836
8845
|
const ctx = getCtx();
|
|
8837
8846
|
const fallback = cfg.resolveContext?.() ?? {};
|
|
@@ -8842,6 +8851,18 @@ function resolveMongoDbRuntimeContext(cfg: MongoDbPatchConfig) {
|
|
|
8842
8851
|
};
|
|
8843
8852
|
}
|
|
8844
8853
|
|
|
8854
|
+
function resolveMongoDbCaptureContext(): { cfg: MongoDbPatchConfig; sid: string; aid: string | null } | null {
|
|
8855
|
+
for (let i = mongoDbPatchConfigs.length - 1; i >= 0; i -= 1) {
|
|
8856
|
+
const cfg = mongoDbPatchConfigs[i];
|
|
8857
|
+
if (!cfg) continue;
|
|
8858
|
+
const { sid, aid } = resolveMongoDbRuntimeContext(cfg);
|
|
8859
|
+
if (sid) {
|
|
8860
|
+
return { cfg, sid, aid };
|
|
8861
|
+
}
|
|
8862
|
+
}
|
|
8863
|
+
return null;
|
|
8864
|
+
}
|
|
8865
|
+
|
|
8845
8866
|
function resolveMongoCollectionName(collection: any): string {
|
|
8846
8867
|
const candidates = [
|
|
8847
8868
|
collection?.collectionName,
|
|
@@ -9142,10 +9163,11 @@ function patchMongoCollectionMethod(proto: any, op: string, cursorOp: boolean, c
|
|
|
9142
9163
|
return original.apply(this, args);
|
|
9143
9164
|
}
|
|
9144
9165
|
|
|
9145
|
-
const
|
|
9146
|
-
if (!
|
|
9166
|
+
const captureContext = resolveMongoDbCaptureContext();
|
|
9167
|
+
if (!captureContext) {
|
|
9147
9168
|
return original.apply(this, args);
|
|
9148
9169
|
}
|
|
9170
|
+
const { cfg: activeCfg, sid, aid } = captureContext;
|
|
9149
9171
|
|
|
9150
9172
|
const spanContext = captureSpanContextFromTracer(this);
|
|
9151
9173
|
const t0 = Date.now();
|
|
@@ -9157,7 +9179,7 @@ function patchMongoCollectionMethod(proto: any, op: string, cursorOp: boolean, c
|
|
|
9157
9179
|
const emitSuccess = (result: any) => {
|
|
9158
9180
|
if (emitted) return;
|
|
9159
9181
|
emitted = true;
|
|
9160
|
-
emitDbQuery(
|
|
9182
|
+
emitDbQuery(activeCfg, sid, aid ?? undefined, {
|
|
9161
9183
|
type: 'mongo',
|
|
9162
9184
|
collection,
|
|
9163
9185
|
op,
|
|
@@ -9174,7 +9196,7 @@ function patchMongoCollectionMethod(proto: any, op: string, cursorOp: boolean, c
|
|
|
9174
9196
|
const emitFailure = (error: any) => {
|
|
9175
9197
|
if (emitted) return;
|
|
9176
9198
|
emitted = true;
|
|
9177
|
-
emitDbQuery(
|
|
9199
|
+
emitDbQuery(activeCfg, sid, aid ?? undefined, {
|
|
9178
9200
|
type: 'mongo',
|
|
9179
9201
|
collection,
|
|
9180
9202
|
op,
|
|
@@ -9199,7 +9221,7 @@ function patchMongoCollectionMethod(proto: any, op: string, cursorOp: boolean, c
|
|
|
9199
9221
|
const result = original.apply(this, actualArgs);
|
|
9200
9222
|
if (cursorOp) {
|
|
9201
9223
|
return patchMongoCursorInstance(result, {
|
|
9202
|
-
cfg,
|
|
9224
|
+
cfg: activeCfg,
|
|
9203
9225
|
sid,
|
|
9204
9226
|
aid,
|
|
9205
9227
|
collection,
|
|
@@ -9283,6 +9305,7 @@ function patchMongoClientPrototype(proto: any, cfg: MongoDbPatchConfig): void {
|
|
|
9283
9305
|
}
|
|
9284
9306
|
|
|
9285
9307
|
function patchMongoDbFromModuleExports(mongoModule: any, cfg: MongoDbPatchConfig): void {
|
|
9308
|
+
registerMongoDbPatchConfig(cfg);
|
|
9286
9309
|
if (!mongoModule) return;
|
|
9287
9310
|
const constructors = new Set<any>();
|
|
9288
9311
|
const addCtor = (candidate: any) => {
|
|
@@ -9343,9 +9366,50 @@ function installMongoDbRequireHook(): void {
|
|
|
9343
9366
|
} catch {}
|
|
9344
9367
|
}
|
|
9345
9368
|
|
|
9369
|
+
function installMongoDbImportHook(): Promise<void> {
|
|
9370
|
+
if (mongoDbImportHookInstalled) return mongoDbImportHookReady ?? Promise.resolve();
|
|
9371
|
+
mongoDbImportHookInstalled = true;
|
|
9372
|
+
|
|
9373
|
+
mongoDbImportHookReady = (async () => {
|
|
9374
|
+
try {
|
|
9375
|
+
const nodeModule = requireOptionalHostModule('node:module') ?? requireOptionalHostModule('module');
|
|
9376
|
+
if (typeof nodeModule?.register !== 'function') return;
|
|
9377
|
+
|
|
9378
|
+
const iitm = requireOptionalHostModule('import-in-the-middle');
|
|
9379
|
+
const Hook = iitm?.Hook ?? iitm;
|
|
9380
|
+
if (typeof Hook !== 'function') return;
|
|
9381
|
+
|
|
9382
|
+
let waitForAllMessagesAcknowledged: undefined | (() => Promise<void>);
|
|
9383
|
+
let registerOptions: any = { data: { include: ['mongodb'] } };
|
|
9384
|
+
if (typeof iitm?.createAddHookMessageChannel === 'function') {
|
|
9385
|
+
const channel = iitm.createAddHookMessageChannel();
|
|
9386
|
+
registerOptions = channel.registerOptions;
|
|
9387
|
+
waitForAllMessagesAcknowledged = channel.waitForAllMessagesAcknowledged;
|
|
9388
|
+
}
|
|
9389
|
+
|
|
9390
|
+
nodeModule.register('import-in-the-middle/hook.mjs', pathToFileURL(__filename), registerOptions);
|
|
9391
|
+
|
|
9392
|
+
// eslint-disable-next-line no-new
|
|
9393
|
+
new Hook(['mongodb'], (exports: any) => {
|
|
9394
|
+
for (const cfg of mongoDbPatchConfigs) {
|
|
9395
|
+
patchMongoDbFromModuleExports(exports, cfg);
|
|
9396
|
+
}
|
|
9397
|
+
return exports;
|
|
9398
|
+
});
|
|
9399
|
+
|
|
9400
|
+
if (waitForAllMessagesAcknowledged) {
|
|
9401
|
+
await waitForAllMessagesAcknowledged();
|
|
9402
|
+
}
|
|
9403
|
+
} catch {}
|
|
9404
|
+
})();
|
|
9405
|
+
|
|
9406
|
+
return mongoDbImportHookReady;
|
|
9407
|
+
}
|
|
9408
|
+
|
|
9346
9409
|
export function patchMongoDb(cfg: MongoDbPatchConfig): void {
|
|
9347
|
-
|
|
9410
|
+
registerMongoDbPatchConfig(cfg);
|
|
9348
9411
|
installMongoDbRequireHook();
|
|
9412
|
+
void installMongoDbImportHook();
|
|
9349
9413
|
|
|
9350
9414
|
const mongoModules = new Set<any>();
|
|
9351
9415
|
for (const loadedMongoModule of getLoadedModuleExportsByPackage('mongodb')) {
|
|
@@ -9867,6 +9931,7 @@ export async function initRepro(cfg: ReproInitConfig): Promise<void> {
|
|
|
9867
9931
|
patchAmqplib(integrationCfg);
|
|
9868
9932
|
patchRedis(integrationCfg);
|
|
9869
9933
|
patchMongoDb(integrationCfg);
|
|
9934
|
+
await installMongoDbImportHook();
|
|
9870
9935
|
patchSendgridMail(integrationCfg);
|
|
9871
9936
|
patchMongooseIntegration(reproAutoMiddlewareConfig);
|
|
9872
9937
|
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
process.env.REPRO_INGEST_REQUEST_TIMEOUT_MS = '50';
|
|
2
|
+
process.env.REPRO_SDK_BACKGROUND_MAX_DEFER_MS = '25';
|
|
3
|
+
process.env.REPRO_SDK_BACKGROUND_IDLE_DELAY_MS = '0';
|
|
4
|
+
process.env.REPRO_SDK_BACKGROUND_BUSY_IDLE_DELAY_MS = '0';
|
|
5
|
+
process.env.TRACE_IDLE_FLUSH_MS = '10';
|
|
6
|
+
process.env.TRACE_LINGER_AFTER_FINISH_MS = '0';
|
|
7
|
+
|
|
8
|
+
const assert = require('assert');
|
|
9
|
+
const { EventEmitter } = require('events');
|
|
10
|
+
const { pathToFileURL } = require('url');
|
|
11
|
+
const { join } = require('path');
|
|
12
|
+
const { mkdtempSync, mkdirSync, rmSync, writeFileSync } = require('fs');
|
|
13
|
+
const { tmpdir } = require('os');
|
|
14
|
+
const { initRepro, reproMiddleware, __reproTestHooks } = require('../dist');
|
|
15
|
+
const { flushIngestQueue } = require('../dist/ingest/client');
|
|
16
|
+
|
|
17
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
18
|
+
|
|
19
|
+
async function waitFor(predicate, timeoutMs = 3000) {
|
|
20
|
+
const deadline = Date.now() + timeoutMs;
|
|
21
|
+
while (Date.now() < deadline) {
|
|
22
|
+
if (predicate()) return;
|
|
23
|
+
await sleep(20);
|
|
24
|
+
}
|
|
25
|
+
assert(predicate(), 'timed out waiting for expected ingest payload');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function flattenEvents(posts) {
|
|
29
|
+
return posts.flatMap((body) => Array.isArray(body?.events) ? body.events : []);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function dbChanges(events) {
|
|
33
|
+
return events
|
|
34
|
+
.filter((event) => event.event_type === 'db_change')
|
|
35
|
+
.flatMap((event) => Array.isArray(event.payload?.db) ? event.payload.db : []);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function makeReqRes() {
|
|
39
|
+
const req = new EventEmitter();
|
|
40
|
+
req.method = 'POST';
|
|
41
|
+
req.url = '/native-mongodb-esm';
|
|
42
|
+
req.originalUrl = '/native-mongodb-esm';
|
|
43
|
+
req.headers = {
|
|
44
|
+
'x-bug-session-id': 'S_mongo_esm',
|
|
45
|
+
'x-bug-action-id': 'A_mongo_esm',
|
|
46
|
+
};
|
|
47
|
+
req.query = {};
|
|
48
|
+
req.params = {};
|
|
49
|
+
req.body = undefined;
|
|
50
|
+
|
|
51
|
+
const res = new EventEmitter();
|
|
52
|
+
res.statusCode = 200;
|
|
53
|
+
res.getHeader = () => undefined;
|
|
54
|
+
res.getHeaders = () => ({});
|
|
55
|
+
res.write = () => true;
|
|
56
|
+
res.end = function end() {
|
|
57
|
+
this.emit('finish');
|
|
58
|
+
return this;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return { req, res };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function createEsmMongoFixture() {
|
|
65
|
+
const root = mkdtempSync(join(tmpdir(), 'repro-node-sdk-esm-mongodb-'));
|
|
66
|
+
const packageDir = join(root, 'node_modules', 'mongodb');
|
|
67
|
+
mkdirSync(packageDir, { recursive: true });
|
|
68
|
+
writeFileSync(join(packageDir, 'package.json'), JSON.stringify({
|
|
69
|
+
name: 'mongodb',
|
|
70
|
+
version: '99.0.0-repro-fixture',
|
|
71
|
+
type: 'module',
|
|
72
|
+
exports: './index.mjs',
|
|
73
|
+
}, null, 2));
|
|
74
|
+
writeFileSync(join(packageDir, 'index.mjs'), `
|
|
75
|
+
export class Collection {
|
|
76
|
+
constructor(dbName, collectionName) {
|
|
77
|
+
this.dbName = dbName;
|
|
78
|
+
this.collectionName = collectionName;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async insertOne(doc, options) {
|
|
82
|
+
if (options?.session?.id !== 'esm-native-session-1') {
|
|
83
|
+
throw new Error(\`unexpected session \${options?.session?.id}\`);
|
|
84
|
+
}
|
|
85
|
+
doc._id = doc._id ?? 'subject-esm-1';
|
|
86
|
+
return { acknowledged: true, insertedId: doc._id };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export class Db {
|
|
91
|
+
constructor(dbName) {
|
|
92
|
+
this.databaseName = dbName;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
collection(collectionName) {
|
|
96
|
+
return new Collection(this.databaseName, collectionName);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export class MongoClient {
|
|
101
|
+
constructor(uri) {
|
|
102
|
+
this.uri = uri;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
db(dbName) {
|
|
106
|
+
return new Db(dbName);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
`);
|
|
110
|
+
const appFile = join(root, 'app.mjs');
|
|
111
|
+
writeFileSync(appFile, `
|
|
112
|
+
import { MongoClient } from 'mongodb';
|
|
113
|
+
|
|
114
|
+
const dbClient = new MongoClient('mongodb://127.0.0.1:27017/clinical');
|
|
115
|
+
|
|
116
|
+
export async function insertSubject() {
|
|
117
|
+
const subjectsCollection = dbClient.db('clinical').collection('subjects');
|
|
118
|
+
return subjectsCollection.insertOne(
|
|
119
|
+
{ subjectNumber: '10013038', status: 'screened' },
|
|
120
|
+
{
|
|
121
|
+
session: {
|
|
122
|
+
id: 'esm-native-session-1',
|
|
123
|
+
hasEnded: false,
|
|
124
|
+
inTransaction: () => true,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
`);
|
|
130
|
+
return { root, appFile };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function main() {
|
|
134
|
+
const capturedBodies = [];
|
|
135
|
+
const originalFetch = global.fetch;
|
|
136
|
+
let fixtureRoot = null;
|
|
137
|
+
global.fetch = async (_url, init) => {
|
|
138
|
+
capturedBodies.push(JSON.parse(String(init?.body ?? '{}')));
|
|
139
|
+
return {
|
|
140
|
+
ok: true,
|
|
141
|
+
status: 202,
|
|
142
|
+
json: async () => ({ ok: true }),
|
|
143
|
+
text: async () => '{"ok":true}',
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const cfg = {
|
|
149
|
+
tenantId: 'TENANT_test',
|
|
150
|
+
appId: 'APP_test',
|
|
151
|
+
appSecret: 'secret',
|
|
152
|
+
captureHeaders: false,
|
|
153
|
+
privacy: { environment: 'dev', captureRaw: true },
|
|
154
|
+
ingestBase: 'http://127.0.0.1:65535',
|
|
155
|
+
};
|
|
156
|
+
await initRepro(cfg);
|
|
157
|
+
|
|
158
|
+
const fixture = createEsmMongoFixture();
|
|
159
|
+
fixtureRoot = fixture.root;
|
|
160
|
+
const fixtureUrl = pathToFileURL(fixture.appFile).href;
|
|
161
|
+
const { insertSubject } = await import(`${fixtureUrl}?run=${Date.now()}`);
|
|
162
|
+
|
|
163
|
+
const middleware = reproMiddleware(cfg);
|
|
164
|
+
const { req, res } = makeReqRes();
|
|
165
|
+
await new Promise((resolve, reject) => {
|
|
166
|
+
middleware(req, res, (err) => {
|
|
167
|
+
if (err) {
|
|
168
|
+
reject(err);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
Promise.resolve(insertSubject()).then(resolve, reject);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
res.end();
|
|
175
|
+
|
|
176
|
+
await waitFor(() => dbChanges(flattenEvents(capturedBodies)).length === 1);
|
|
177
|
+
await __reproTestHooks.drainSdkBackgroundQueueForTest();
|
|
178
|
+
await flushIngestQueue();
|
|
179
|
+
|
|
180
|
+
const [change] = dbChanges(flattenEvents(capturedBodies));
|
|
181
|
+
assert.strictEqual(change.type, 'mongo');
|
|
182
|
+
assert.strictEqual(change.collection, 'subjects');
|
|
183
|
+
assert.strictEqual(change.op, 'insertOne');
|
|
184
|
+
assert.strictEqual(change.pk._id, 'subject-esm-1');
|
|
185
|
+
assert.strictEqual(change.query.driver, 'mongodb');
|
|
186
|
+
assert.strictEqual(change.query.db, 'clinical');
|
|
187
|
+
assert.strictEqual(change.query.doc.subjectNumber, '10013038');
|
|
188
|
+
assert.strictEqual(change.query.options.session.__kind, 'mongodb_client_session');
|
|
189
|
+
assert.strictEqual(change.query.options.session.id, 'esm-native-session-1');
|
|
190
|
+
assert.strictEqual(change.after.subjectNumber, '10013038');
|
|
191
|
+
assert.strictEqual(change.resultMeta.insertedId, 'subject-esm-1');
|
|
192
|
+
|
|
193
|
+
console.log('mongodb esm import hook OK');
|
|
194
|
+
} finally {
|
|
195
|
+
await __reproTestHooks.drainSdkBackgroundQueueForTest();
|
|
196
|
+
await flushIngestQueue();
|
|
197
|
+
if (fixtureRoot) {
|
|
198
|
+
try { rmSync(fixtureRoot, { recursive: true, force: true }); } catch {}
|
|
199
|
+
}
|
|
200
|
+
global.fetch = originalFetch;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
main().catch((error) => {
|
|
205
|
+
console.error(error);
|
|
206
|
+
process.exit(1);
|
|
207
|
+
});
|