@reproapp/node-sdk 0.0.17 → 0.0.18

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reproapp/node-sdk",
3
- "version": "0.0.17",
3
+ "version": "0.0.18",
4
4
  "description": "Repro Nest SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -12,7 +12,8 @@
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-manifest-priority.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-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/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.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-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-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/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js",
16
+ "test:containers": "npm run build && node test/runtime-no-double-invoke.testcontainers.test.js"
16
17
  },
17
18
  "peerDependencies": {
18
19
  "express": "^5.1.0",
@@ -0,0 +1,68 @@
1
+ const assert = require('assert');
2
+
3
+ const { trace, SYM_BODY_TRACED } = require('../tracer/runtime');
4
+
5
+ function assertSyncThrowsOnceWithNoActiveTrace() {
6
+ let calls = 0;
7
+ function throwsOnce() {
8
+ calls += 1;
9
+ throw new Error('app failure');
10
+ }
11
+
12
+ assert.throws(
13
+ () => global.__repro_call(throwsOnce, null, [], __filename, 10, 'throwsOnce', false),
14
+ /app failure/,
15
+ );
16
+ assert.strictEqual(calls, 1);
17
+ }
18
+
19
+ function assertSyncThrowsOnceWithActiveTrace() {
20
+ let calls = 0;
21
+ function throwsOnce() {
22
+ calls += 1;
23
+ throw new Error('traced app failure');
24
+ }
25
+
26
+ assert.throws(
27
+ () => trace.withTrace('runtime-no-double-invoke', () => (
28
+ global.__repro_call(throwsOnce, null, [], __filename, 25, 'throwsOnce', false)
29
+ )),
30
+ /traced app failure/,
31
+ );
32
+ assert.strictEqual(calls, 1);
33
+ }
34
+
35
+ function assertBodyTracedThrowsOnceWithActiveTrace() {
36
+ let calls = 0;
37
+ function bodyTracedThrowsOnce() {
38
+ calls += 1;
39
+ throw new Error('body traced app failure');
40
+ }
41
+ Object.defineProperty(bodyTracedThrowsOnce, SYM_BODY_TRACED, {
42
+ value: true,
43
+ configurable: true,
44
+ });
45
+
46
+ assert.throws(
47
+ () => trace.withTrace('runtime-no-double-invoke-body', () => (
48
+ global.__repro_call(bodyTracedThrowsOnce, null, [], __filename, 45, 'bodyTracedThrowsOnce', false)
49
+ )),
50
+ /body traced app failure/,
51
+ );
52
+ assert.strictEqual(calls, 1);
53
+ }
54
+
55
+ async function main() {
56
+ assertSyncThrowsOnceWithNoActiveTrace();
57
+ assertSyncThrowsOnceWithActiveTrace();
58
+ assertBodyTracedThrowsOnceWithActiveTrace();
59
+
60
+ // eslint-disable-next-line no-console
61
+ console.log('runtime no double invoke OK');
62
+ }
63
+
64
+ main().catch((error) => {
65
+ // eslint-disable-next-line no-console
66
+ console.error(error);
67
+ process.exitCode = 1;
68
+ });
@@ -0,0 +1,70 @@
1
+ const assert = require('assert');
2
+ const mongoose = require('mongoose');
3
+ const { GenericContainer, Wait } = require('testcontainers');
4
+
5
+ const { trace } = require('../tracer/runtime');
6
+
7
+ async function startMongo() {
8
+ return new GenericContainer('mongo:7')
9
+ .withExposedPorts(27017)
10
+ .withTmpFs({ '/data/db': 'rw,noexec,nosuid,size=256m' })
11
+ .withCommand(['mongod', '--bind_ip_all', '--setParameter', 'diagnosticDataCollectionEnabled=false'])
12
+ .withWaitStrategy(Wait.forListeningPorts())
13
+ .start();
14
+ }
15
+
16
+ async function main() {
17
+ let container;
18
+ let connection;
19
+
20
+ try {
21
+ container = await startMongo();
22
+ const uri = `mongodb://${container.getHost()}:${container.getMappedPort(27017)}/repro_node_sdk_test`;
23
+ connection = await mongoose.createConnection(uri).asPromise();
24
+
25
+ const SideEffect = connection.model('SideEffect', new mongoose.Schema({
26
+ name: { type: String, required: true },
27
+ }));
28
+
29
+ let calls = 0;
30
+ async function writeThenThrow(name) {
31
+ calls += 1;
32
+ await SideEffect.create({ name });
33
+ throw new Error('intentional app failure');
34
+ }
35
+
36
+ await assert.rejects(
37
+ () => trace.withTrace('runtime-no-double-invoke-container', () => (
38
+ global.__repro_call(
39
+ writeThenThrow,
40
+ null,
41
+ ['single-write'],
42
+ __filename,
43
+ 35,
44
+ 'writeThenThrow',
45
+ false,
46
+ )
47
+ )),
48
+ /intentional app failure/,
49
+ );
50
+
51
+ assert.strictEqual(calls, 1);
52
+ assert.strictEqual(await SideEffect.countDocuments({ name: 'single-write' }), 1);
53
+
54
+ // eslint-disable-next-line no-console
55
+ console.log('runtime no double invoke testcontainers OK');
56
+ } finally {
57
+ if (connection) {
58
+ await connection.close().catch(() => {});
59
+ }
60
+ if (container) {
61
+ await container.stop().catch(() => {});
62
+ }
63
+ }
64
+ }
65
+
66
+ main().catch((error) => {
67
+ // eslint-disable-next-line no-console
68
+ console.error(error);
69
+ process.exitCode = 1;
70
+ });
package/tracer/runtime.js CHANGED
@@ -505,9 +505,14 @@ function forkAlsStoreForUnawaited(baseStore) {
505
505
  if (!global.__repro_call) {
506
506
  Object.defineProperty(global, '__repro_call', {
507
507
  value: function __repro_call(fn, thisArg, args, callFile, callLine, label, isUnawaitedCall) {
508
+ let didInvokeTarget = false;
509
+ const invokeTarget = (targetThis, targetArgs) => {
510
+ didInvokeTarget = true;
511
+ return fn.apply(targetThis, targetArgs);
512
+ };
508
513
  try {
509
514
  if (typeof fn !== 'function' || fn[SYM_SKIP_WRAP]) {
510
- return fn.apply(thisArg, args);
515
+ return invokeTarget(thisArg, args);
511
516
  }
512
517
 
513
518
  const currentStore = als.getStore();
@@ -516,13 +521,9 @@ if (!global.__repro_call) {
516
521
  // If no tracing context is active, bail out quickly to avoid touching app semantics
517
522
  // during module initialization or other untracked code paths.
518
523
  if (!currentStore) {
519
- try {
520
- const out = fn.apply(thisArg, args);
521
- if (isUnawaitedCall && isThenable(out)) markPromiseUnawaited(out);
522
- return out;
523
- } catch {
524
- return fn ? fn.apply(thisArg, args) : undefined;
525
- }
524
+ const out = invokeTarget(thisArg, args);
525
+ if (isUnawaitedCall && isThenable(out)) markPromiseUnawaited(out);
526
+ return out;
526
527
  }
527
528
 
528
529
  const sourceFile = fn[SYM_SRC_FILE];
@@ -643,7 +644,7 @@ if (!global.__repro_call) {
643
644
  const shouldWrap = !isAsyncLocalContextSetter(fn, thisArg);
644
645
  const argsForCall = shouldWrap ? wrapArgsWithStore(args, snapshotForArgs) : args;
645
646
  try {
646
- const out = fn.apply(thisArg, argsForCall);
647
+ const out = invokeTarget(thisArg, argsForCall);
647
648
 
648
649
  const isThenableValue = isThenable(out);
649
650
  const isQuery = isMongooseQuery(out);
@@ -755,10 +756,10 @@ if (!global.__repro_call) {
755
756
  let out;
756
757
  if (isolatedStore) {
757
758
  als.run(isolatedStore, () => {
758
- out = fn.apply(thisArg, argsForCall);
759
+ out = invokeTarget(thisArg, argsForCall);
759
760
  });
760
761
  } else {
761
- out = fn.apply(thisArg, argsForCall);
762
+ out = invokeTarget(thisArg, argsForCall);
762
763
  }
763
764
  if (isUnawaitedCall && isThenable(out)) markPromiseUnawaited(out);
764
765
  return out;
@@ -775,7 +776,10 @@ if (!global.__repro_call) {
775
776
  }
776
777
  return invokeWithTrace(forcedParentSpanId, callStoreForArgs);
777
778
  });
778
- } catch {
779
+ } catch (error) {
780
+ if (didInvokeTarget) {
781
+ throw error;
782
+ }
779
783
  return fn ? fn.apply(thisArg, args) : undefined;
780
784
  }
781
785
  },