@zapier/zapier-sdk 0.32.0 → 0.32.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @zapier/zapier-sdk
2
2
 
3
+ ## 0.32.2
4
+
5
+ ### Patch Changes
6
+
7
+ - a6dec83: Fix base URL with trailing slash bypassing sdkapi.
8
+
9
+ ## 0.32.1
10
+
11
+ ### Patch Changes
12
+
13
+ - c7be13e: Fix bug where `add` command hangs after completing successfully
14
+
3
15
  ## 0.32.0
4
16
 
5
17
  ### Minor Changes
@@ -373,7 +373,7 @@ class ZapierApiClient {
373
373
  // Let's remain compatible with a base URL that is set to a Zapier-inferred
374
374
  // domain, rather than requiring the base URL to go to our proxy. Later, the
375
375
  // proxy will be removed, so this should make that transition easier.
376
- if (zapierBaseUrl === this.options.baseUrl) {
376
+ if (zapierBaseUrl === this.options.baseUrl.replace(/\/$/, "")) {
377
377
  // If baseUrl is already the Zapier base URL, use sdkapi subdomain.
378
378
  const originalBaseUrl = new URL(this.options.baseUrl);
379
379
  const finalBaseUrl = `https://sdkapi.${originalBaseUrl.hostname}`;
@@ -219,6 +219,15 @@ describe("ApiClient", () => {
219
219
  await client.get("/relay");
220
220
  expect(mockFetch).toHaveBeenCalledWith("http://localhost:3000/api/v0/sdk/relay/", expect.any(Object));
221
221
  });
222
+ it("should use sdkapi subdomain when Zapier base URL has trailing slash", async () => {
223
+ const client = createZapierApi({
224
+ baseUrl: "https://zapier-staging.com/",
225
+ credentials: "test-token",
226
+ debug: false,
227
+ });
228
+ await client.get("/zapier/api/v4/profile/");
229
+ expect(mockFetch).toHaveBeenCalledWith("https://sdkapi.zapier-staging.com/api/v0/sdk/zapier/api/v4/profile/", expect.any(Object));
230
+ });
222
231
  it("should preserve base URL path for localhost with path component", async () => {
223
232
  const client = createZapierApi({
224
233
  baseUrl: "http://localhost:3000/a/b/c",
package/dist/index.cjs CHANGED
@@ -4701,7 +4701,7 @@ var ZapierApiClient = class {
4701
4701
  finalPath = `${config.pathPrefix}${pathWithoutPrefix}`;
4702
4702
  }
4703
4703
  const zapierBaseUrl = getZapierBaseUrl(this.options.baseUrl);
4704
- if (zapierBaseUrl === this.options.baseUrl) {
4704
+ if (zapierBaseUrl === this.options.baseUrl.replace(/\/$/, "")) {
4705
4705
  const originalBaseUrl = new URL(this.options.baseUrl);
4706
4706
  const finalBaseUrl = `https://sdkapi.${originalBaseUrl.hostname}`;
4707
4707
  return {
@@ -5503,7 +5503,7 @@ function getCpuTime() {
5503
5503
 
5504
5504
  // package.json
5505
5505
  var package_default = {
5506
- version: "0.32.0"};
5506
+ version: "0.32.2"};
5507
5507
 
5508
5508
  // src/plugins/eventEmission/builders.ts
5509
5509
  function createBaseEvent(context = {}) {
@@ -5627,6 +5627,20 @@ var APPLICATION_LIFECYCLE_EVENT_SUBJECT = "platform.sdk.ApplicationLifecycleEven
5627
5627
  var ERROR_OCCURRED_EVENT_SUBJECT = "platform.sdk.ErrorOccurredEvent";
5628
5628
  var METHOD_CALLED_EVENT_SUBJECT = "platform.sdk.MethodCalledEvent";
5629
5629
  var transportStates = /* @__PURE__ */ new WeakMap();
5630
+ async function emitWithTimeout(transport, subject, event) {
5631
+ try {
5632
+ await Promise.race([
5633
+ transport.emit(subject, event),
5634
+ new Promise((resolve2) => {
5635
+ const timer = setTimeout(resolve2, TELEMETRY_EMIT_TIMEOUT_MS);
5636
+ if (typeof timer.unref === "function") {
5637
+ timer.unref();
5638
+ }
5639
+ })
5640
+ ]);
5641
+ } catch {
5642
+ }
5643
+ }
5630
5644
  async function silentEmit(transport, subject, event, userContextPromise) {
5631
5645
  try {
5632
5646
  let state = transportStates.get(transport);
@@ -5705,6 +5719,7 @@ var eventEmissionPlugin = ({ context }) => {
5705
5719
  })();
5706
5720
  const startupTime = Date.now();
5707
5721
  let shutdownStartTime = null;
5722
+ let closed = false;
5708
5723
  if (!config.enabled) {
5709
5724
  return {
5710
5725
  context: {
@@ -5724,6 +5739,8 @@ var eventEmissionPlugin = ({ context }) => {
5724
5739
  correlation_id: null
5725
5740
  }),
5726
5741
  emitMethodCalled: () => {
5742
+ },
5743
+ close: async () => {
5727
5744
  }
5728
5745
  }
5729
5746
  }
@@ -5766,6 +5783,7 @@ var eventEmissionPlugin = ({ context }) => {
5766
5783
  if (typeof process?.on === "function") {
5767
5784
  removeExistingListeners();
5768
5785
  const exitHandler = (code) => {
5786
+ if (closed) return;
5769
5787
  const uptime = Date.now() - startupTime;
5770
5788
  const shutdownDuration = shutdownStartTime ? Date.now() - shutdownStartTime : null;
5771
5789
  const exitEvent = buildApplicationLifecycleEvent({
@@ -5799,18 +5817,11 @@ var eventEmissionPlugin = ({ context }) => {
5799
5817
  errorEvent = { ...errorEvent, ...userContext };
5800
5818
  } catch {
5801
5819
  }
5802
- try {
5803
- await Promise.race([
5804
- transport.emit(ERROR_OCCURRED_EVENT_SUBJECT, errorEvent),
5805
- new Promise((resolve2) => {
5806
- const timer = setTimeout(resolve2, TELEMETRY_EMIT_TIMEOUT_MS);
5807
- if (typeof timer.unref === "function") {
5808
- timer.unref();
5809
- }
5810
- })
5811
- ]);
5812
- } catch {
5813
- }
5820
+ await emitWithTimeout(
5821
+ transport,
5822
+ ERROR_OCCURRED_EVENT_SUBJECT,
5823
+ errorEvent
5824
+ );
5814
5825
  };
5815
5826
  registeredListeners.uncaughtException = uncaughtExceptionHandler;
5816
5827
  process.on("uncaughtException", uncaughtExceptionHandler);
@@ -5834,18 +5845,11 @@ var eventEmissionPlugin = ({ context }) => {
5834
5845
  errorEvent = { ...errorEvent, ...userContext };
5835
5846
  } catch {
5836
5847
  }
5837
- try {
5838
- await Promise.race([
5839
- transport.emit(ERROR_OCCURRED_EVENT_SUBJECT, errorEvent),
5840
- new Promise((resolve2) => {
5841
- const timer = setTimeout(resolve2, TELEMETRY_EMIT_TIMEOUT_MS);
5842
- if (typeof timer.unref === "function") {
5843
- timer.unref();
5844
- }
5845
- })
5846
- ]);
5847
- } catch {
5848
- }
5848
+ await emitWithTimeout(
5849
+ transport,
5850
+ ERROR_OCCURRED_EVENT_SUBJECT,
5851
+ errorEvent
5852
+ );
5849
5853
  };
5850
5854
  registeredListeners.unhandledRejection = unhandledRejectionHandler;
5851
5855
  process.on("unhandledRejection", unhandledRejectionHandler);
@@ -5863,18 +5867,11 @@ var eventEmissionPlugin = ({ context }) => {
5863
5867
  signalEvent = { ...signalEvent, ...userContext };
5864
5868
  } catch {
5865
5869
  }
5866
- try {
5867
- await Promise.race([
5868
- transport.emit(APPLICATION_LIFECYCLE_EVENT_SUBJECT, signalEvent),
5869
- new Promise((resolve2) => {
5870
- const timer = setTimeout(resolve2, TELEMETRY_EMIT_TIMEOUT_MS);
5871
- if (typeof timer.unref === "function") {
5872
- timer.unref();
5873
- }
5874
- })
5875
- ]);
5876
- } catch {
5877
- }
5870
+ await emitWithTimeout(
5871
+ transport,
5872
+ APPLICATION_LIFECYCLE_EVENT_SUBJECT,
5873
+ signalEvent
5874
+ );
5878
5875
  const exitCode = signal === "SIGINT" ? 130 : 143;
5879
5876
  process.exit(exitCode);
5880
5877
  };
@@ -5886,6 +5883,30 @@ var eventEmissionPlugin = ({ context }) => {
5886
5883
  process.on("SIGTERM", sigtermHandler);
5887
5884
  }
5888
5885
  }
5886
+ const close = async (exitCode) => {
5887
+ if (closed) return;
5888
+ closed = true;
5889
+ const uptime = Date.now() - startupTime;
5890
+ const shutdownDuration = shutdownStartTime ? Date.now() - shutdownStartTime : null;
5891
+ let exitEvent = buildApplicationLifecycleEvent({
5892
+ lifecycle_event_type: "exit",
5893
+ exit_code: exitCode ?? 0,
5894
+ uptime_ms: uptime,
5895
+ is_graceful_shutdown: (exitCode ?? 0) === 0,
5896
+ shutdown_duration_ms: shutdownDuration
5897
+ });
5898
+ try {
5899
+ const userContext = await getUserContext;
5900
+ exitEvent = { ...exitEvent, ...userContext };
5901
+ } catch {
5902
+ }
5903
+ await emitWithTimeout(
5904
+ transport,
5905
+ APPLICATION_LIFECYCLE_EVENT_SUBJECT,
5906
+ exitEvent
5907
+ );
5908
+ removeExistingListeners();
5909
+ };
5889
5910
  return {
5890
5911
  context: {
5891
5912
  eventEmission: {
@@ -5906,7 +5927,8 @@ var eventEmissionPlugin = ({ context }) => {
5906
5927
  event,
5907
5928
  getUserContext
5908
5929
  );
5909
- }
5930
+ },
5931
+ close
5910
5932
  }
5911
5933
  }
5912
5934
  };
package/dist/index.d.mts CHANGED
@@ -388,6 +388,7 @@ interface EventEmissionContext {
388
388
  emit<T extends any>(subject: string, event: T): void;
389
389
  createBaseEvent(): Promise<BaseEvent>;
390
390
  emitMethodCalled(data: MethodCalledEventData): void;
391
+ close(exitCode?: number): Promise<void>;
391
392
  };
392
393
  }
393
394
  interface EventEmissionProvides {
package/dist/index.mjs CHANGED
@@ -4679,7 +4679,7 @@ var ZapierApiClient = class {
4679
4679
  finalPath = `${config.pathPrefix}${pathWithoutPrefix}`;
4680
4680
  }
4681
4681
  const zapierBaseUrl = getZapierBaseUrl(this.options.baseUrl);
4682
- if (zapierBaseUrl === this.options.baseUrl) {
4682
+ if (zapierBaseUrl === this.options.baseUrl.replace(/\/$/, "")) {
4683
4683
  const originalBaseUrl = new URL(this.options.baseUrl);
4684
4684
  const finalBaseUrl = `https://sdkapi.${originalBaseUrl.hostname}`;
4685
4685
  return {
@@ -5481,7 +5481,7 @@ function getCpuTime() {
5481
5481
 
5482
5482
  // package.json
5483
5483
  var package_default = {
5484
- version: "0.32.0"};
5484
+ version: "0.32.2"};
5485
5485
 
5486
5486
  // src/plugins/eventEmission/builders.ts
5487
5487
  function createBaseEvent(context = {}) {
@@ -5605,6 +5605,20 @@ var APPLICATION_LIFECYCLE_EVENT_SUBJECT = "platform.sdk.ApplicationLifecycleEven
5605
5605
  var ERROR_OCCURRED_EVENT_SUBJECT = "platform.sdk.ErrorOccurredEvent";
5606
5606
  var METHOD_CALLED_EVENT_SUBJECT = "platform.sdk.MethodCalledEvent";
5607
5607
  var transportStates = /* @__PURE__ */ new WeakMap();
5608
+ async function emitWithTimeout(transport, subject, event) {
5609
+ try {
5610
+ await Promise.race([
5611
+ transport.emit(subject, event),
5612
+ new Promise((resolve2) => {
5613
+ const timer = setTimeout(resolve2, TELEMETRY_EMIT_TIMEOUT_MS);
5614
+ if (typeof timer.unref === "function") {
5615
+ timer.unref();
5616
+ }
5617
+ })
5618
+ ]);
5619
+ } catch {
5620
+ }
5621
+ }
5608
5622
  async function silentEmit(transport, subject, event, userContextPromise) {
5609
5623
  try {
5610
5624
  let state = transportStates.get(transport);
@@ -5683,6 +5697,7 @@ var eventEmissionPlugin = ({ context }) => {
5683
5697
  })();
5684
5698
  const startupTime = Date.now();
5685
5699
  let shutdownStartTime = null;
5700
+ let closed = false;
5686
5701
  if (!config.enabled) {
5687
5702
  return {
5688
5703
  context: {
@@ -5702,6 +5717,8 @@ var eventEmissionPlugin = ({ context }) => {
5702
5717
  correlation_id: null
5703
5718
  }),
5704
5719
  emitMethodCalled: () => {
5720
+ },
5721
+ close: async () => {
5705
5722
  }
5706
5723
  }
5707
5724
  }
@@ -5744,6 +5761,7 @@ var eventEmissionPlugin = ({ context }) => {
5744
5761
  if (typeof process?.on === "function") {
5745
5762
  removeExistingListeners();
5746
5763
  const exitHandler = (code) => {
5764
+ if (closed) return;
5747
5765
  const uptime = Date.now() - startupTime;
5748
5766
  const shutdownDuration = shutdownStartTime ? Date.now() - shutdownStartTime : null;
5749
5767
  const exitEvent = buildApplicationLifecycleEvent({
@@ -5777,18 +5795,11 @@ var eventEmissionPlugin = ({ context }) => {
5777
5795
  errorEvent = { ...errorEvent, ...userContext };
5778
5796
  } catch {
5779
5797
  }
5780
- try {
5781
- await Promise.race([
5782
- transport.emit(ERROR_OCCURRED_EVENT_SUBJECT, errorEvent),
5783
- new Promise((resolve2) => {
5784
- const timer = setTimeout(resolve2, TELEMETRY_EMIT_TIMEOUT_MS);
5785
- if (typeof timer.unref === "function") {
5786
- timer.unref();
5787
- }
5788
- })
5789
- ]);
5790
- } catch {
5791
- }
5798
+ await emitWithTimeout(
5799
+ transport,
5800
+ ERROR_OCCURRED_EVENT_SUBJECT,
5801
+ errorEvent
5802
+ );
5792
5803
  };
5793
5804
  registeredListeners.uncaughtException = uncaughtExceptionHandler;
5794
5805
  process.on("uncaughtException", uncaughtExceptionHandler);
@@ -5812,18 +5823,11 @@ var eventEmissionPlugin = ({ context }) => {
5812
5823
  errorEvent = { ...errorEvent, ...userContext };
5813
5824
  } catch {
5814
5825
  }
5815
- try {
5816
- await Promise.race([
5817
- transport.emit(ERROR_OCCURRED_EVENT_SUBJECT, errorEvent),
5818
- new Promise((resolve2) => {
5819
- const timer = setTimeout(resolve2, TELEMETRY_EMIT_TIMEOUT_MS);
5820
- if (typeof timer.unref === "function") {
5821
- timer.unref();
5822
- }
5823
- })
5824
- ]);
5825
- } catch {
5826
- }
5826
+ await emitWithTimeout(
5827
+ transport,
5828
+ ERROR_OCCURRED_EVENT_SUBJECT,
5829
+ errorEvent
5830
+ );
5827
5831
  };
5828
5832
  registeredListeners.unhandledRejection = unhandledRejectionHandler;
5829
5833
  process.on("unhandledRejection", unhandledRejectionHandler);
@@ -5841,18 +5845,11 @@ var eventEmissionPlugin = ({ context }) => {
5841
5845
  signalEvent = { ...signalEvent, ...userContext };
5842
5846
  } catch {
5843
5847
  }
5844
- try {
5845
- await Promise.race([
5846
- transport.emit(APPLICATION_LIFECYCLE_EVENT_SUBJECT, signalEvent),
5847
- new Promise((resolve2) => {
5848
- const timer = setTimeout(resolve2, TELEMETRY_EMIT_TIMEOUT_MS);
5849
- if (typeof timer.unref === "function") {
5850
- timer.unref();
5851
- }
5852
- })
5853
- ]);
5854
- } catch {
5855
- }
5848
+ await emitWithTimeout(
5849
+ transport,
5850
+ APPLICATION_LIFECYCLE_EVENT_SUBJECT,
5851
+ signalEvent
5852
+ );
5856
5853
  const exitCode = signal === "SIGINT" ? 130 : 143;
5857
5854
  process.exit(exitCode);
5858
5855
  };
@@ -5864,6 +5861,30 @@ var eventEmissionPlugin = ({ context }) => {
5864
5861
  process.on("SIGTERM", sigtermHandler);
5865
5862
  }
5866
5863
  }
5864
+ const close = async (exitCode) => {
5865
+ if (closed) return;
5866
+ closed = true;
5867
+ const uptime = Date.now() - startupTime;
5868
+ const shutdownDuration = shutdownStartTime ? Date.now() - shutdownStartTime : null;
5869
+ let exitEvent = buildApplicationLifecycleEvent({
5870
+ lifecycle_event_type: "exit",
5871
+ exit_code: exitCode ?? 0,
5872
+ uptime_ms: uptime,
5873
+ is_graceful_shutdown: (exitCode ?? 0) === 0,
5874
+ shutdown_duration_ms: shutdownDuration
5875
+ });
5876
+ try {
5877
+ const userContext = await getUserContext;
5878
+ exitEvent = { ...exitEvent, ...userContext };
5879
+ } catch {
5880
+ }
5881
+ await emitWithTimeout(
5882
+ transport,
5883
+ APPLICATION_LIFECYCLE_EVENT_SUBJECT,
5884
+ exitEvent
5885
+ );
5886
+ removeExistingListeners();
5887
+ };
5867
5888
  return {
5868
5889
  context: {
5869
5890
  eventEmission: {
@@ -5884,7 +5905,8 @@ var eventEmissionPlugin = ({ context }) => {
5884
5905
  event,
5885
5906
  getUserContext
5886
5907
  );
5887
- }
5908
+ },
5909
+ close
5888
5910
  }
5889
5911
  }
5890
5912
  };
@@ -25,6 +25,7 @@ export interface EventEmissionContext {
25
25
  emit<T extends any>(subject: string, event: T): void;
26
26
  createBaseEvent(): Promise<BaseEvent>;
27
27
  emitMethodCalled(data: MethodCalledEventData): void;
28
+ close(exitCode?: number): Promise<void>;
28
29
  };
29
30
  }
30
31
  export interface EventEmissionProvides {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/eventEmission/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAWnE,OAAO,KAAK,EAAgB,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAsCnE;;;GAGG;AACH,wBAAgB,qBAAqB,SAEpC;AAGD,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,WAAW,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;CACrC;AAGD,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE;QACb,SAAS,EAAE,cAAc,CAAC;QAC1B,MAAM,EAAE,mBAAmB,CAAC;QAE5B,IAAI,CAAC,CAAC,SAAS,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;QAErD,eAAe,IAAI,OAAO,CAAC,SAAS,CAAC,CAAC;QAEtC,gBAAgB,CAAC,IAAI,EAAE,qBAAqB,GAAG,IAAI,CAAC;KACrD,CAAC;CACH;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,oBAAoB,CAAC;CAC/B;AA6FD,eAAO,MAAM,mBAAmB,EAAE,MAAM,CACtC,EAAE,EACF;IACE,OAAO,EAAE;QACP,aAAa,CAAC,EAAE,mBAAmB,CAAC;QACpC,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;CACH,EACD,qBAAqB,CA8StB,CAAC;AAGF,YAAY,EACV,YAAY,EACZ,6BAA6B,EAC7B,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,8BAA8B,EAC9B,0BAA0B,EAC1B,eAAe,EACf,eAAe,EACf,sBAAsB,GACvB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAC9D,cAAc,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/eventEmission/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAWnE,OAAO,KAAK,EAAgB,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAsCnE;;;GAGG;AACH,wBAAgB,qBAAqB,SAEpC;AAGD,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,WAAW,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;CACrC;AAGD,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE;QACb,SAAS,EAAE,cAAc,CAAC;QAC1B,MAAM,EAAE,mBAAmB,CAAC;QAE5B,IAAI,CAAC,CAAC,SAAS,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;QAErD,eAAe,IAAI,OAAO,CAAC,SAAS,CAAC,CAAC;QAEtC,gBAAgB,CAAC,IAAI,EAAE,qBAAqB,GAAG,IAAI,CAAC;QAIpD,KAAK,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KACzC,CAAC;CACH;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,oBAAoB,CAAC;CAC/B;AAiHD,eAAO,MAAM,mBAAmB,EAAE,MAAM,CACtC,EAAE,EACF;IACE,OAAO,EAAE;QACP,aAAa,CAAC,EAAE,mBAAmB,CAAC;QACpC,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;CACH,EACD,qBAAqB,CAwTtB,CAAC;AAGF,YAAY,EACV,YAAY,EACZ,6BAA6B,EAC7B,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,8BAA8B,EAC9B,0BAA0B,EAC1B,eAAe,EACf,eAAe,EACf,sBAAsB,GACvB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAC9D,cAAc,SAAS,CAAC"}
@@ -41,6 +41,22 @@ const ERROR_OCCURRED_EVENT_SUBJECT = "platform.sdk.ErrorOccurredEvent";
41
41
  const METHOD_CALLED_EVENT_SUBJECT = "platform.sdk.MethodCalledEvent";
42
42
  // Track transport success/failure so we only log failure once.
43
43
  const transportStates = new WeakMap();
44
+ async function emitWithTimeout(transport, subject, event) {
45
+ try {
46
+ await Promise.race([
47
+ transport.emit(subject, event),
48
+ new Promise((resolve) => {
49
+ const timer = setTimeout(resolve, TELEMETRY_EMIT_TIMEOUT_MS);
50
+ if (typeof timer.unref === "function") {
51
+ timer.unref();
52
+ }
53
+ }),
54
+ ]);
55
+ }
56
+ catch {
57
+ // Silently ignore telemetry failures
58
+ }
59
+ }
44
60
  // Silent emission wrapper with smart first-failure logging
45
61
  async function silentEmit(transport, subject, event, userContextPromise) {
46
62
  try {
@@ -135,6 +151,7 @@ export const eventEmissionPlugin = ({ context }) => {
135
151
  })();
136
152
  const startupTime = Date.now();
137
153
  let shutdownStartTime = null;
154
+ let closed = false;
138
155
  // If disabled, return noop implementations
139
156
  if (!config.enabled) {
140
157
  return {
@@ -154,6 +171,7 @@ export const eventEmissionPlugin = ({ context }) => {
154
171
  correlation_id: null,
155
172
  }),
156
173
  emitMethodCalled: () => { },
174
+ close: async () => { },
157
175
  },
158
176
  },
159
177
  };
@@ -201,6 +219,8 @@ export const eventEmissionPlugin = ({ context }) => {
201
219
  removeExistingListeners();
202
220
  // Handle normal process exit
203
221
  const exitHandler = (code) => {
222
+ if (closed)
223
+ return;
204
224
  const uptime = Date.now() - startupTime;
205
225
  const shutdownDuration = shutdownStartTime
206
226
  ? Date.now() - shutdownStartTime
@@ -235,21 +255,7 @@ export const eventEmissionPlugin = ({ context }) => {
235
255
  catch {
236
256
  // Continue with original event if user context fails
237
257
  }
238
- // Wait up to 300ms for telemetry to send before allowing process to exit
239
- try {
240
- await Promise.race([
241
- transport.emit(ERROR_OCCURRED_EVENT_SUBJECT, errorEvent),
242
- new Promise((resolve) => {
243
- const timer = setTimeout(resolve, TELEMETRY_EMIT_TIMEOUT_MS);
244
- if (typeof timer.unref === "function") {
245
- timer.unref();
246
- }
247
- }),
248
- ]);
249
- }
250
- catch {
251
- // Silently ignore telemetry failures
252
- }
258
+ await emitWithTimeout(transport, ERROR_OCCURRED_EVENT_SUBJECT, errorEvent);
253
259
  };
254
260
  registeredListeners.uncaughtException = uncaughtExceptionHandler;
255
261
  process.on("uncaughtException", uncaughtExceptionHandler);
@@ -281,21 +287,7 @@ export const eventEmissionPlugin = ({ context }) => {
281
287
  catch {
282
288
  // Continue with original event if user context fails
283
289
  }
284
- // Wait up to 300ms for telemetry to send
285
- try {
286
- await Promise.race([
287
- transport.emit(ERROR_OCCURRED_EVENT_SUBJECT, errorEvent),
288
- new Promise((resolve) => {
289
- const timer = setTimeout(resolve, TELEMETRY_EMIT_TIMEOUT_MS);
290
- if (typeof timer.unref === "function") {
291
- timer.unref();
292
- }
293
- }),
294
- ]);
295
- }
296
- catch {
297
- // Silently ignore telemetry failures
298
- }
290
+ await emitWithTimeout(transport, ERROR_OCCURRED_EVENT_SUBJECT, errorEvent);
299
291
  };
300
292
  registeredListeners.unhandledRejection = unhandledRejectionHandler;
301
293
  process.on("unhandledRejection", unhandledRejectionHandler);
@@ -317,21 +309,7 @@ export const eventEmissionPlugin = ({ context }) => {
317
309
  catch {
318
310
  // Continue with original event if user context fails
319
311
  }
320
- // Wait up to 300ms for telemetry to send
321
- try {
322
- await Promise.race([
323
- transport.emit(APPLICATION_LIFECYCLE_EVENT_SUBJECT, signalEvent),
324
- new Promise((resolve) => {
325
- const timer = setTimeout(resolve, TELEMETRY_EMIT_TIMEOUT_MS);
326
- if (typeof timer.unref === "function") {
327
- timer.unref();
328
- }
329
- }),
330
- ]);
331
- }
332
- catch {
333
- // Silently ignore telemetry failures
334
- }
312
+ await emitWithTimeout(transport, APPLICATION_LIFECYCLE_EVENT_SUBJECT, signalEvent);
335
313
  // Exit with appropriate code (128 + signal number)
336
314
  const exitCode = signal === "SIGINT" ? 130 : 143;
337
315
  process.exit(exitCode);
@@ -345,6 +323,31 @@ export const eventEmissionPlugin = ({ context }) => {
345
323
  process.on("SIGTERM", sigtermHandler);
346
324
  }
347
325
  }
326
+ const close = async (exitCode) => {
327
+ if (closed)
328
+ return;
329
+ closed = true;
330
+ const uptime = Date.now() - startupTime;
331
+ const shutdownDuration = shutdownStartTime
332
+ ? Date.now() - shutdownStartTime
333
+ : null;
334
+ let exitEvent = buildApplicationLifecycleEvent({
335
+ lifecycle_event_type: "exit",
336
+ exit_code: exitCode ?? 0,
337
+ uptime_ms: uptime,
338
+ is_graceful_shutdown: (exitCode ?? 0) === 0,
339
+ shutdown_duration_ms: shutdownDuration,
340
+ });
341
+ try {
342
+ const userContext = await getUserContext;
343
+ exitEvent = { ...exitEvent, ...userContext };
344
+ }
345
+ catch {
346
+ // Continue with original event if user context fails
347
+ }
348
+ await emitWithTimeout(transport, APPLICATION_LIFECYCLE_EVENT_SUBJECT, exitEvent);
349
+ removeExistingListeners();
350
+ };
348
351
  return {
349
352
  context: {
350
353
  eventEmission: {
@@ -361,6 +364,7 @@ export const eventEmissionPlugin = ({ context }) => {
361
364
  };
362
365
  silentEmit(transport, METHOD_CALLED_EVENT_SUBJECT, event, getUserContext);
363
366
  },
367
+ close,
364
368
  },
365
369
  },
366
370
  };
@@ -661,6 +661,98 @@ describe("emitMethodCalled call_context", () => {
661
661
  expect(mockTransport.emit).toHaveBeenCalledWith("platform.sdk.MethodCalledEvent", expect.objectContaining({ call_context: "mcp" }));
662
662
  });
663
663
  });
664
+ describe("close()", () => {
665
+ beforeEach(() => {
666
+ vi.clearAllMocks();
667
+ cleanupEventListeners();
668
+ mockGetToken.mockResolvedValue(undefined);
669
+ });
670
+ afterEach(() => {
671
+ cleanupEventListeners();
672
+ });
673
+ it("should emit exit lifecycle event", async () => {
674
+ const plugin = eventEmissionPlugin({
675
+ sdk: {},
676
+ context: {
677
+ meta: {},
678
+ options: {
679
+ eventEmission: {
680
+ enabled: true,
681
+ transport: { type: "console" },
682
+ },
683
+ },
684
+ },
685
+ });
686
+ await plugin.context.eventEmission.close(0);
687
+ expect(mockTransport.emit).toHaveBeenCalledWith("platform.sdk.ApplicationLifecycleEvent", expect.objectContaining({
688
+ lifecycle_event_type: "exit",
689
+ exit_code: 0,
690
+ is_graceful_shutdown: true,
691
+ }));
692
+ });
693
+ it("should be idempotent — second call is a no-op", async () => {
694
+ const plugin = eventEmissionPlugin({
695
+ sdk: {},
696
+ context: {
697
+ meta: {},
698
+ options: {
699
+ eventEmission: {
700
+ enabled: true,
701
+ transport: { type: "console" },
702
+ },
703
+ },
704
+ },
705
+ });
706
+ await plugin.context.eventEmission.close(0);
707
+ const callCountAfterFirst = mockTransport.emit.mock.calls.filter((call) => call[0] === "platform.sdk.ApplicationLifecycleEvent" &&
708
+ call[1]?.lifecycle_event_type === "exit").length;
709
+ await plugin.context.eventEmission.close(0);
710
+ const callCountAfterSecond = mockTransport.emit.mock.calls.filter((call) => call[0] === "platform.sdk.ApplicationLifecycleEvent" &&
711
+ call[1]?.lifecycle_event_type === "exit").length;
712
+ expect(callCountAfterSecond).toBe(callCountAfterFirst);
713
+ });
714
+ it("should remove process listeners after close", async () => {
715
+ const initialExitCount = process.listenerCount("exit");
716
+ const plugin = eventEmissionPlugin({
717
+ sdk: {},
718
+ context: {
719
+ meta: {},
720
+ options: {
721
+ eventEmission: {
722
+ enabled: true,
723
+ transport: { type: "console" },
724
+ },
725
+ },
726
+ },
727
+ });
728
+ expect(process.listenerCount("exit")).toBe(initialExitCount + 1);
729
+ await plugin.context.eventEmission.close(0);
730
+ expect(process.listenerCount("exit")).toBe(initialExitCount);
731
+ });
732
+ it("should handle transport failures silently", async () => {
733
+ const failingTransport = {
734
+ emit: vi.fn().mockRejectedValue(new Error("Network error")),
735
+ close: vi.fn().mockResolvedValue(undefined),
736
+ };
737
+ vi.mocked(createTransport).mockReturnValueOnce(failingTransport);
738
+ const plugin = eventEmissionPlugin({
739
+ sdk: {},
740
+ context: {
741
+ meta: {},
742
+ options: {
743
+ eventEmission: {
744
+ enabled: true,
745
+ transport: {
746
+ type: "http",
747
+ endpoint: "https://example.com",
748
+ },
749
+ },
750
+ },
751
+ },
752
+ });
753
+ await expect(plugin.context.eventEmission.close(1)).resolves.toBeUndefined();
754
+ });
755
+ });
664
756
  describe("Process Listener Cleanup", () => {
665
757
  beforeEach(() => {
666
758
  // Clean up any listeners from previous tests
@@ -437,6 +437,7 @@ describe("request plugin", () => {
437
437
  emit: () => { },
438
438
  createBaseEvent: (() => ({})),
439
439
  emitMethodCalled: emitSpy,
440
+ close: async () => { },
440
441
  },
441
442
  },
442
443
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zapier/zapier-sdk",
3
- "version": "0.32.0",
3
+ "version": "0.32.2",
4
4
  "description": "Complete Zapier SDK - combines all Zapier SDK packages",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.mjs",