From 2c365af9c87e046187f5fff61336d0d639d80715 Mon Sep 17 00:00:00 2001 From: Seth Silesky Date: Wed, 28 Sep 2022 22:34:52 -0500 Subject: [PATCH 01/19] add graceful shutdown Conflicts: packages/node/src/app/analytics-node.ts --- internal/test-helpers/.eslintrc.js | 7 + internal/test-helpers/.lintstagedrc.js | 1 + internal/test-helpers/README.md | 3 + internal/test-helpers/jest.config.js | 3 + internal/test-helpers/package.json | 21 ++ internal/test-helpers/tsconfig.json | 11 + packages/core/src/callback/index.ts | 17 +- packages/core/src/index.ts | 1 + packages/node/README.md | 38 +++- .../src/__tests__/graceful-shutdown.test.ts | 188 ++++++++++++++++++ .../src/__tests__/http-integration.test.ts | 4 +- .../node/src/__tests__/test-helpers/sleep.ts | 3 + packages/node/src/app/analytics-node.ts | 70 ++++++- packages/node/src/app/plugin.ts | 17 +- packages/node/src/app/settings.ts | 2 + yarn.lock | 9 + 16 files changed, 354 insertions(+), 41 deletions(-) create mode 100644 internal/test-helpers/.eslintrc.js create mode 100644 internal/test-helpers/.lintstagedrc.js create mode 100644 internal/test-helpers/README.md create mode 100644 internal/test-helpers/jest.config.js create mode 100644 internal/test-helpers/package.json create mode 100644 internal/test-helpers/tsconfig.json create mode 100644 packages/node/src/__tests__/graceful-shutdown.test.ts create mode 100644 packages/node/src/__tests__/test-helpers/sleep.ts diff --git a/internal/test-helpers/.eslintrc.js b/internal/test-helpers/.eslintrc.js new file mode 100644 index 000000000..7e8674ea7 --- /dev/null +++ b/internal/test-helpers/.eslintrc.js @@ -0,0 +1,7 @@ +/** @type { import('eslint').Linter.Config } */ +module.exports = { + extends: ['../../.eslintrc'], + env: { + node: true, + }, +} diff --git a/internal/test-helpers/.lintstagedrc.js b/internal/test-helpers/.lintstagedrc.js new file mode 100644 index 000000000..bc1f1c780 --- /dev/null +++ b/internal/test-helpers/.lintstagedrc.js @@ -0,0 +1 @@ +module.exports = require("@internal/config").lintStagedConfig diff --git a/internal/test-helpers/README.md b/internal/test-helpers/README.md new file mode 100644 index 000000000..7aff58836 --- /dev/null +++ b/internal/test-helpers/README.md @@ -0,0 +1,3 @@ +# This is for code that will used as part of testing + +There is no build step included because we use ts-jest, so this could gets compiled in-memory. diff --git a/internal/test-helpers/jest.config.js b/internal/test-helpers/jest.config.js new file mode 100644 index 000000000..43505965a --- /dev/null +++ b/internal/test-helpers/jest.config.js @@ -0,0 +1,3 @@ +const { createJestTSConfig } = require('@internal/config') + +module.exports = createJestTSConfig() diff --git a/internal/test-helpers/package.json b/internal/test-helpers/package.json new file mode 100644 index 000000000..88ddf7fcf --- /dev/null +++ b/internal/test-helpers/package.json @@ -0,0 +1,21 @@ +{ + "name": "@internal/test-helpers", + "version": "0.0.0", + "private": true, + "engines": { + "node": ">=12" + }, + "scripts": { + "lint": "yarn concurrently 'yarn:eslint .' 'yarn:tsc --noEmit'", + "tsc": "yarn run -T tsc", + "eslint": "yarn run -T eslint", + "concurrently": "yarn run -T concurrently" + }, + "dependencies": { + "tslib": "^2.4.0" + }, + "packageManager": "yarn@3.2.1", + "devDependencies": { + "@internal/config": "0.0.0" + } +} diff --git a/internal/test-helpers/tsconfig.json b/internal/test-helpers/tsconfig.json new file mode 100644 index 000000000..a79f0e174 --- /dev/null +++ b/internal/test-helpers/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "exclude": ["node_modules", "dist"], + "compilerOptions": { + "resolveJsonModule": true, + "module": "esnext", + "target": "ES5", + "moduleResolution": "node", + "lib": ["es2020"] + } +} diff --git a/packages/core/src/callback/index.ts b/packages/core/src/callback/index.ts index 1fca70a94..864f74802 100644 --- a/packages/core/src/callback/index.ts +++ b/packages/core/src/callback/index.ts @@ -1,23 +1,22 @@ import { CoreContext } from '../context' import type { Callback } from '../events' -export function pTimeout( - cb: Promise, - timeout: number -): Promise { +export function pTimeout(promise: Promise, timeout: number): Promise { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { reject(Error('Promise timed out')) }, timeout) - cb.then((val) => { - clearTimeout(timeoutId) - return resolve(val) - }).catch(reject) + promise + .then((val) => { + clearTimeout(timeoutId) + return resolve(val) + }) + .catch(reject) }) } -function sleep(timeoutInMs: number): Promise { +export function sleep(timeoutInMs: number): Promise { return new Promise((resolve) => setTimeout(resolve, timeoutInMs)) } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 525ff7d97..8e6a384d4 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -4,6 +4,7 @@ export * from './plugins' export * from './plugins/middleware' export * from './events/interfaces' export * from './events' +export * from './callback' export * from './priority-queue' export * from './context' export * from './queue/event-queue' diff --git a/packages/node/README.md b/packages/node/README.md index 0173ebc25..2f701800c 100644 --- a/packages/node/README.md +++ b/packages/node/README.md @@ -5,7 +5,7 @@ https://segment.com/docs/connections/sources/catalog/libraries/server/node/ NOTE: @segment/analytics-node is unstable! do not use. -## Basic Usage +## Quick Start ```ts // analytics.ts import { AnalyticsNode } from '@segment/analytics-node' @@ -21,7 +21,34 @@ analytics.track('hello world', {}, { userId: "123456" }) ``` -# Event Emitter (Advanced Usage) +## Graceful Shutdown +### Avoid losing events on exit! + * Call `.closeAndFlush()` to stop collecting new events and flush all existing events. + * If a callback on an event call is included, this also waits for all callbacks to be called, and any of their subsequent promises to be resolved. +```ts +await analytics.closeAndFlush() +``` +### Graceful Shutdown: Advanced Example +```ts +import express from 'express' +const app = express() + +const server = app.listen(3000) +app.get('/', (req, res) => res.send('Hello World!')); + +const onExit = async () => { + await analytics.closeAndFlush() // flush all existing events + setTimeout(() => { + server.close(() => process.exit()) + }, 0); +}; + +process.on('SIGINT', onExit) +process.on('SIGTERM', onExit); + +``` + +## Event Emitter ```ts import { analytics } from './analytics' import { ContextCancelation, CoreContext } from '@segment/analytics-node' @@ -31,13 +58,12 @@ analytics.on('identify', (ctx) => console.log(ctx.event)) // listen for errors (if needed) analytics.on('error', (err) => { - if (err instanceof ContextCancelation) { - console.error('event cancelled', err.logs()) - } else if (err instanceof CoreContext) { - console.error('event failed', err.logs()) + if (err.code === 'http_delivery') { + console.error(err.response) } else { console.error(err) } }) ``` + diff --git a/packages/node/src/__tests__/graceful-shutdown.test.ts b/packages/node/src/__tests__/graceful-shutdown.test.ts new file mode 100644 index 000000000..1fb7e8c19 --- /dev/null +++ b/packages/node/src/__tests__/graceful-shutdown.test.ts @@ -0,0 +1,188 @@ +import { createSuccess } from './test-helpers/factories' + +const fetcher = jest.fn().mockReturnValue(createSuccess()) +jest.mock('node-fetch', () => fetcher) + +import { AnalyticsNode } from '../app/analytics-node' +import { sleep } from './test-helpers/sleep' +import { CoreContext, CorePlugin } from '@segment/analytics-core' + +const testPlugin: CorePlugin = { + type: 'after', + load: () => Promise.resolve(), + name: 'foo', + version: 'bar', + isLoaded: () => true, +} + +describe('Ability for users to exit without losing events', () => { + let ajs!: AnalyticsNode + beforeEach(async () => { + jest.resetAllMocks() + ajs = new AnalyticsNode({ + writeKey: 'abc123', + drainedDelay: 200, + }) + await ajs.ready + }) + const _helpers = { + makeTrackCall: (analytics = ajs, cb?: (...args: any[]) => void) => { + analytics.track({ userId: 'foo', event: 'Thing Updated', callback: cb }) + }, + listenOnDrain: (): Promise => { + return new Promise((resolve) => { + ajs.once('drained', () => resolve(undefined)) + }) + }, + } + + describe('drained emitted event', () => { + test('Analytics should emit a drained event and respect the drained delay', async () => { + _helpers.makeTrackCall() + const startTime = Date.now() + const drainedCbArgs = await _helpers.listenOnDrain() + const drainedTime = Date.now() - startTime + expect(drainedTime).toBeGreaterThan(200) + expect(drainedTime).toBeLessThan(250) + + expect(drainedCbArgs).toBeUndefined() + }) + + test('delay should be customizable', async () => { + ajs = new AnalyticsNode({ + writeKey: 'abc123', + drainedDelay: 500, + }) + _helpers.makeTrackCall(undefined) + const startTime = Date.now() + await _helpers.listenOnDrain() + const drainedTime = Date.now() - startTime + expect(drainedTime).toBeGreaterThan(500) + expect(drainedTime).toBeLessThan(550) + }) + + test('every time a new event enters the queue, the timeout should be reset (like debounce)', async () => { + const DRAINED_DELAY = 250 + ajs = new AnalyticsNode({ + writeKey: 'abc123', + drainedDelay: DRAINED_DELAY, + }) + await ajs.register({ + ...testPlugin, + track: async (ctx) => { + await sleep(50) // should be + return ctx + }, + }) + await new Promise((resolve) => + _helpers.makeTrackCall(undefined, () => resolve(undefined)) + ) + _helpers.makeTrackCall() + + const startTime = Date.now() + await _helpers.listenOnDrain() + const drainedTime = Date.now() - startTime + expect(drainedTime).toBeGreaterThan(DRAINED_DELAY) + expect(drainedTime).toBeLessThan(DRAINED_DELAY + 200) + }) + + test('all callbacks should be called ', async () => { + const cb = jest.fn() + ajs.track({ userId: 'foo', event: 'bar', callback: cb }) + expect(cb).not.toHaveBeenCalled() + await ajs.closeAndFlush() + expect(cb).toBeCalled() + }) + + test('all async callbacks should be called', async () => { + const trackCall = new Promise((resolve) => + ajs.track({ + userId: 'abc', + event: 'def', + callback: (ctx) => { + return sleep(100).then(() => resolve(ctx)) + }, + }) + ) + const res = await Promise.race([ajs.closeAndFlush(), trackCall]) + expect(res instanceof CoreContext).toBe(true) + }) + }) + + describe('.closeAndFlush()', () => { + test('should auto resolve after a certain timeout', async () => { + await ajs.register({ + ...testPlugin, + track: async (ctx) => { + await sleep(1000) + return ctx + }, + }) + _helpers.makeTrackCall(ajs) + const startTime = Date.now() + await ajs.closeAndFlush({ timeout: 500 }) + const elapsedTime = Math.round(Date.now() - startTime) + expect(elapsedTime).toBeLessThanOrEqual(510) + expect(elapsedTime).toBeGreaterThan(490) + }) + + test('no new events should be accepted (but existing ones should be flushed)', async () => { + let trackCallCount = 0 + ajs.on('track', () => { + // track should only happen after successful dispatch + trackCallCount += 1 + }) + _helpers.makeTrackCall() + const closed = ajs.closeAndFlush() + _helpers.makeTrackCall() // should not trigger + _helpers.makeTrackCall() // should not trigger + await closed + expect(fetcher).toBeCalledTimes(1) + expect(trackCallCount).toBe(1) + }) + + test('if queue has multiple track events, all of those items should be dispatched, and drain and track events should be emitted', async () => { + let drainedCalls = 0 + ajs.on('drained', () => { + drainedCalls++ + }) + let trackCalls = 0 + ajs.on('track', () => { + trackCalls++ + }) + await ajs.register({ + ...testPlugin, + track: async (ctx) => { + await sleep(300) + return ctx + }, + }) + _helpers.makeTrackCall() + _helpers.makeTrackCall() + + await ajs.closeAndFlush() + + expect(fetcher.mock.calls.length).toBe(2) + + expect(trackCalls).toBe(2) + + expect(drainedCalls).toBe(1) + }) + + test('if no pending events, resolves immediately', async () => { + const startTime = Date.now() + await ajs.closeAndFlush() + const elapsedTime = startTime - Date.now() + expect(elapsedTime).toBeLessThan(20) + }) + + test('if no pending events, drained should not be emitted an extra time when close is called', async () => { + let called = false + ajs.on('drained', () => { + called = true + }) + await ajs.closeAndFlush() + expect(called).toBeFalsy() + }) + }) +}) diff --git a/packages/node/src/__tests__/http-integration.test.ts b/packages/node/src/__tests__/http-integration.test.ts index 0ef162210..8a7737b97 100644 --- a/packages/node/src/__tests__/http-integration.test.ts +++ b/packages/node/src/__tests__/http-integration.test.ts @@ -88,8 +88,8 @@ describe('Analytics Node', () => { }) test('Track: Fires http requests to the correct endoint', async () => { - ajs.track({ event: 'track', userId: 'foo' }) - ajs.track({ event: 'track', userId: 'foo', properties: { foo: 'bar' } }) + ajs.track({ event: 'foo', userId: 'foo' }) + ajs.track({ event: 'bar', userId: 'foo', properties: { foo: 'bar' } }) await resolveCtx(ajs, 'track') expect(fetcher).toHaveBeenCalledWith( 'https://api.segment.io/v1/track', diff --git a/packages/node/src/__tests__/test-helpers/sleep.ts b/packages/node/src/__tests__/test-helpers/sleep.ts new file mode 100644 index 000000000..44990d558 --- /dev/null +++ b/packages/node/src/__tests__/test-helpers/sleep.ts @@ -0,0 +1,3 @@ +export function sleep(timeoutInMs: number): Promise { + return new Promise((resolve) => setTimeout(resolve, timeoutInMs)) +} diff --git a/packages/node/src/app/analytics-node.ts b/packages/node/src/app/analytics-node.ts index 3baaae1df..04cb81735 100644 --- a/packages/node/src/app/analytics-node.ts +++ b/packages/node/src/app/analytics-node.ts @@ -14,9 +14,10 @@ import { bindAll, PriorityQueue, CoreEmitterContract, + pTimeout, } from '@segment/analytics-core' import { AnalyticsNodeSettings, validateSettings } from './settings' -import { analyticsNode, AnalyticsNodePluginSettings } from './plugin' +import { analyticsNode } from './plugin' import { version } from '../../package.json' import { NodeEmittedError } from './emitted-errors' @@ -42,6 +43,7 @@ export interface NodeSegmentEventOptions { */ type NodeEmitterEvents = CoreEmitterContract & { initialize: [AnalyticsNodeSettings] + drained: [] } class NodePriorityQueue extends PriorityQueue { @@ -70,25 +72,25 @@ export class AnalyticsNode implements CoreAnalytics { private _eventFactory: EventFactory + private _drainedTimeout?: ReturnType + private _drainedDelay: number + private _isClosed = false + private _pendingEvents = 0 queue: EventQueue ready: Promise constructor(settings: AnalyticsNodeSettings) { - validateSettings(settings) super() + validateSettings(settings) + this._drainedDelay = settings.drainedDelay ?? 500 this._eventFactory = new EventFactory() this.queue = new EventQueue(new NodePriorityQueue(3)) - const nodeSettings: AnalyticsNodePluginSettings = { - name: 'analytics-node-next', - type: 'after', - version: 'latest', - writeKey: settings.writeKey, - } - - this.ready = this.register(analyticsNode(nodeSettings, this)) + this.ready = this.register( + analyticsNode({ writeKey: settings.writeKey }, this) + ) .then(() => undefined) .catch((err) => { console.error(err) @@ -103,11 +105,57 @@ export class AnalyticsNode return version } + /** + * Call this method to stop collecting new events and flush all existing events. + * If a callback on an event call is incluced, this also waits for all callbacks to be called, and any of their subsequent promises to be resolved. + */ + public closeAndFlush({ + timeout, + }: { + /** Maximum time permitted to wait before resolving. */ + timeout?: number + } = {}): Promise { + this._isClosed = true + const promise = new Promise((resolve) => { + if (!this._pendingEvents) { + resolve() + } else { + this.once('drained', () => resolve()) + } + }) + if (timeout) { + return pTimeout(promise, timeout).catch(() => undefined) + } else { + return promise + } + } + private _dispatch(segmentEvent: CoreSegmentEvent, callback?: Callback) { + if (this._isClosed) { + return undefined + } + + this._pendingEvents++ + + if (this._drainedTimeout) { + clearTimeout(this._drainedTimeout) + } + dispatchAndEmit(segmentEvent, this.queue, this, { callback: callback, - }).catch((err) => err) // we ignore errors, since we have an event emitter + }) + .catch((ctx) => ctx) + .finally(() => { + this._pendingEvents-- + + if (!this._pendingEvents) { + this._drainedTimeout = setTimeout(() => { + this.emit('drained') + }, this._drainedDelay) + } + }) } + /** * Combines two unassociated user identities. * @link https://segment.com/docs/connections/sources/catalog/libraries/server/node/#alias diff --git a/packages/node/src/app/plugin.ts b/packages/node/src/app/plugin.ts index f0bf2c5c4..372d4123e 100644 --- a/packages/node/src/app/plugin.ts +++ b/packages/node/src/app/plugin.ts @@ -1,20 +1,12 @@ import { CorePlugin, CoreSegmentEvent, - PluginType, CoreContext, } from '@segment/analytics-core' import fetch, { Response } from 'node-fetch' import { version } from '../../package.json' import { AnalyticsNode } from './analytics-node' -export interface AnalyticsNodePluginSettings { - writeKey: string - name: string - type: PluginType - version: string -} - const btoa = (val: string): string => Buffer.from(val).toString('base64') export async function post( @@ -39,7 +31,7 @@ export async function post( } export function analyticsNode( - settings: AnalyticsNodePluginSettings, + settings: { writeKey: string }, analytics: AnalyticsNode ): CorePlugin { const send = async (ctx: CoreContext): Promise => { @@ -60,10 +52,9 @@ export function analyticsNode( } return { - name: settings.name, - type: settings.type, - version: settings.version, - + name: 'analytics-node-next', + type: 'after', + version: '1.0.0', load: (ctx) => Promise.resolve(ctx), isLoaded: () => true, diff --git a/packages/node/src/app/settings.ts b/packages/node/src/app/settings.ts index fc5a229b2..ef602e9e3 100644 --- a/packages/node/src/app/settings.ts +++ b/packages/node/src/app/settings.ts @@ -4,6 +4,8 @@ export interface AnalyticsNodeSettings { writeKey: string timeout?: number plugins?: CorePlugin[] + /** Number of ms to wait for the queue to be empty before emitting a 'drained' event */ + drainedDelay?: number } export const validateSettings = (settings: AnalyticsNodeSettings) => { diff --git a/yarn.lock b/yarn.lock index bfca2277a..6b3700e0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -955,6 +955,15 @@ __metadata: languageName: unknown linkType: soft +"@internal/test-helpers@workspace:internal/test-helpers": + version: 0.0.0-use.local + resolution: "@internal/test-helpers@workspace:internal/test-helpers" + dependencies: + "@internal/config": 0.0.0 + tslib: ^2.4.0 + languageName: unknown + linkType: soft + "@istanbuljs/load-nyc-config@npm:^1.0.0": version: 1.1.0 resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" From 745922f4de0e8994be9c57bc137e9931fd81b30b Mon Sep 17 00:00:00 2001 From: Seth Silesky Date: Wed, 28 Sep 2022 22:42:11 -0500 Subject: [PATCH 02/19] tweak README --- packages/node/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/node/README.md b/packages/node/README.md index 2f701800c..b49d90db1 100644 --- a/packages/node/README.md +++ b/packages/node/README.md @@ -36,9 +36,9 @@ const app = express() const server = app.listen(3000) app.get('/', (req, res) => res.send('Hello World!')); -const onExit = async () => { - await analytics.closeAndFlush() // flush all existing events - setTimeout(() => { +const onExit = () => { + setTimeout(async () => { + await analytics.closeAndFlush() // flush all existing events server.close(() => process.exit()) }, 0); }; From 69dd1f42ef2040b7be60c8d37d23f62bbfcc2ee6 Mon Sep 17 00:00:00 2001 From: Seth Silesky Date: Wed, 28 Sep 2022 22:52:58 -0500 Subject: [PATCH 03/19] don't await ready --- packages/node/src/__tests__/graceful-shutdown.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/node/src/__tests__/graceful-shutdown.test.ts b/packages/node/src/__tests__/graceful-shutdown.test.ts index 1fb7e8c19..755ae8d19 100644 --- a/packages/node/src/__tests__/graceful-shutdown.test.ts +++ b/packages/node/src/__tests__/graceful-shutdown.test.ts @@ -23,7 +23,6 @@ describe('Ability for users to exit without losing events', () => { writeKey: 'abc123', drainedDelay: 200, }) - await ajs.ready }) const _helpers = { makeTrackCall: (analytics = ajs, cb?: (...args: any[]) => void) => { From ec8c1190212b888dab737b7cfb60442758a4ec07 Mon Sep 17 00:00:00 2001 From: Seth Silesky Date: Wed, 28 Sep 2022 22:57:24 -0500 Subject: [PATCH 04/19] respect the drained delay --- .../src/__tests__/graceful-shutdown.test.ts | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/packages/node/src/__tests__/graceful-shutdown.test.ts b/packages/node/src/__tests__/graceful-shutdown.test.ts index 755ae8d19..0b22b4da1 100644 --- a/packages/node/src/__tests__/graceful-shutdown.test.ts +++ b/packages/node/src/__tests__/graceful-shutdown.test.ts @@ -20,8 +20,8 @@ describe('Ability for users to exit without losing events', () => { beforeEach(async () => { jest.resetAllMocks() ajs = new AnalyticsNode({ + drainedDelay: 100, writeKey: 'abc123', - drainedDelay: 200, }) }) const _helpers = { @@ -37,29 +37,21 @@ describe('Ability for users to exit without losing events', () => { describe('drained emitted event', () => { test('Analytics should emit a drained event and respect the drained delay', async () => { + const DRAINED_DELAY = 500 + ajs = new AnalyticsNode({ + writeKey: 'abc123', + drainedDelay: DRAINED_DELAY, + }) _helpers.makeTrackCall() const startTime = Date.now() const drainedCbArgs = await _helpers.listenOnDrain() const drainedTime = Date.now() - startTime - expect(drainedTime).toBeGreaterThan(200) - expect(drainedTime).toBeLessThan(250) + expect(drainedTime).toBeGreaterThan(DRAINED_DELAY) + expect(drainedTime).toBeLessThan(DRAINED_DELAY + 200) expect(drainedCbArgs).toBeUndefined() }) - test('delay should be customizable', async () => { - ajs = new AnalyticsNode({ - writeKey: 'abc123', - drainedDelay: 500, - }) - _helpers.makeTrackCall(undefined) - const startTime = Date.now() - await _helpers.listenOnDrain() - const drainedTime = Date.now() - startTime - expect(drainedTime).toBeGreaterThan(500) - expect(drainedTime).toBeLessThan(550) - }) - test('every time a new event enters the queue, the timeout should be reset (like debounce)', async () => { const DRAINED_DELAY = 250 ajs = new AnalyticsNode({ From 69a5db7d6a3a99ece6d575ce9665524e32b1343d Mon Sep 17 00:00:00 2001 From: Seth Silesky Date: Wed, 28 Sep 2022 23:00:25 -0500 Subject: [PATCH 05/19] make less brittle --- packages/node/src/__tests__/graceful-shutdown.test.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/node/src/__tests__/graceful-shutdown.test.ts b/packages/node/src/__tests__/graceful-shutdown.test.ts index 0b22b4da1..ac1faa4a2 100644 --- a/packages/node/src/__tests__/graceful-shutdown.test.ts +++ b/packages/node/src/__tests__/graceful-shutdown.test.ts @@ -101,7 +101,8 @@ describe('Ability for users to exit without losing events', () => { }) describe('.closeAndFlush()', () => { - test('should auto resolve after a certain timeout', async () => { + test('should force resolve if method call execution time exceeds specified timeout', async () => { + const TIMEOUT = 300 await ajs.register({ ...testPlugin, track: async (ctx) => { @@ -111,10 +112,10 @@ describe('Ability for users to exit without losing events', () => { }) _helpers.makeTrackCall(ajs) const startTime = Date.now() - await ajs.closeAndFlush({ timeout: 500 }) + await ajs.closeAndFlush({ timeout: TIMEOUT }) const elapsedTime = Math.round(Date.now() - startTime) - expect(elapsedTime).toBeLessThanOrEqual(510) - expect(elapsedTime).toBeGreaterThan(490) + expect(elapsedTime).toBeLessThanOrEqual(TIMEOUT + 10) + expect(elapsedTime).toBeGreaterThan(TIMEOUT - 10) }) test('no new events should be accepted (but existing ones should be flushed)', async () => { From b5df7e7e559317063ce9866b409f227aa9d2dea0 Mon Sep 17 00:00:00 2001 From: Seth Silesky Date: Wed, 28 Sep 2022 23:03:10 -0500 Subject: [PATCH 06/19] rename test file --- ...ful-shutdown.test.ts => graceful-shutdown-integration.test.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/node/src/__tests__/{graceful-shutdown.test.ts => graceful-shutdown-integration.test.ts} (100%) diff --git a/packages/node/src/__tests__/graceful-shutdown.test.ts b/packages/node/src/__tests__/graceful-shutdown-integration.test.ts similarity index 100% rename from packages/node/src/__tests__/graceful-shutdown.test.ts rename to packages/node/src/__tests__/graceful-shutdown-integration.test.ts From 21b2ea0be742ed273de198db97447387716dce72 Mon Sep 17 00:00:00 2001 From: Seth Silesky Date: Wed, 28 Sep 2022 23:09:19 -0500 Subject: [PATCH 07/19] tweak docs --- packages/node/src/app/analytics-node.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/node/src/app/analytics-node.ts b/packages/node/src/app/analytics-node.ts index 04cb81735..06c0ec21f 100644 --- a/packages/node/src/app/analytics-node.ts +++ b/packages/node/src/app/analytics-node.ts @@ -107,12 +107,13 @@ export class AnalyticsNode /** * Call this method to stop collecting new events and flush all existing events. - * If a callback on an event call is incluced, this also waits for all callbacks to be called, and any of their subsequent promises to be resolved. + * This method also waits for any event method-specific callbacks to be triggered, + * and any of their subsequent promises to be resolved/rejected. */ public closeAndFlush({ timeout, }: { - /** Maximum time permitted to wait before resolving. */ + /** Set a maximum time permitted to wait before resolving. Default = no maximum. */ timeout?: number } = {}): Promise { this._isClosed = true From 568625002e36b0504d9fbfbb2cc23a7b440da007 Mon Sep 17 00:00:00 2001 From: Seth Silesky Date: Wed, 28 Sep 2022 23:11:36 -0500 Subject: [PATCH 08/19] rename variable --- packages/node/src/app/analytics-node.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/node/src/app/analytics-node.ts b/packages/node/src/app/analytics-node.ts index 06c0ec21f..bc9243971 100644 --- a/packages/node/src/app/analytics-node.ts +++ b/packages/node/src/app/analytics-node.ts @@ -72,7 +72,7 @@ export class AnalyticsNode implements CoreAnalytics { private _eventFactory: EventFactory - private _drainedTimeout?: ReturnType + private _drainedEventEmitTimeout?: ReturnType private _drainedDelay: number private _isClosed = false private _pendingEvents = 0 @@ -138,8 +138,8 @@ export class AnalyticsNode this._pendingEvents++ - if (this._drainedTimeout) { - clearTimeout(this._drainedTimeout) + if (this._drainedEventEmitTimeout) { + clearTimeout(this._drainedEventEmitTimeout) } dispatchAndEmit(segmentEvent, this.queue, this, { @@ -150,7 +150,7 @@ export class AnalyticsNode this._pendingEvents-- if (!this._pendingEvents) { - this._drainedTimeout = setTimeout(() => { + this._drainedEventEmitTimeout = setTimeout(() => { this.emit('drained') }, this._drainedDelay) } From dec2a8f08d6cecb7174957174341b2fea8ee774b Mon Sep 17 00:00:00 2001 From: Seth Silesky Date: Wed, 28 Sep 2022 23:12:25 -0500 Subject: [PATCH 09/19] tweak --- packages/node/src/app/analytics-node.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/node/src/app/analytics-node.ts b/packages/node/src/app/analytics-node.ts index bc9243971..fddd72770 100644 --- a/packages/node/src/app/analytics-node.ts +++ b/packages/node/src/app/analytics-node.ts @@ -124,11 +124,7 @@ export class AnalyticsNode this.once('drained', () => resolve()) } }) - if (timeout) { - return pTimeout(promise, timeout).catch(() => undefined) - } else { - return promise - } + return timeout ? pTimeout(promise, timeout).catch(() => undefined) : promise } private _dispatch(segmentEvent: CoreSegmentEvent, callback?: Callback) { From efb08ac3f95864c4b7ad5e5ac1658f955dcbb627 Mon Sep 17 00:00:00 2001 From: Seth Silesky Date: Thu, 29 Sep 2022 12:28:44 -0500 Subject: [PATCH 10/19] get rid of drained delay because not needed for now --- .../graceful-shutdown-integration.test.ts | 49 +++++-------------- packages/node/src/app/analytics-node.ts | 11 +---- packages/node/src/app/settings.ts | 2 - 3 files changed, 14 insertions(+), 48 deletions(-) diff --git a/packages/node/src/__tests__/graceful-shutdown-integration.test.ts b/packages/node/src/__tests__/graceful-shutdown-integration.test.ts index ac1faa4a2..a4d9d666d 100644 --- a/packages/node/src/__tests__/graceful-shutdown-integration.test.ts +++ b/packages/node/src/__tests__/graceful-shutdown-integration.test.ts @@ -20,7 +20,6 @@ describe('Ability for users to exit without losing events', () => { beforeEach(async () => { jest.resetAllMocks() ajs = new AnalyticsNode({ - drainedDelay: 100, writeKey: 'abc123', }) }) @@ -36,45 +35,23 @@ describe('Ability for users to exit without losing events', () => { } describe('drained emitted event', () => { - test('Analytics should emit a drained event and respect the drained delay', async () => { - const DRAINED_DELAY = 500 - ajs = new AnalyticsNode({ - writeKey: 'abc123', - drainedDelay: DRAINED_DELAY, - }) + test('emits a drained event if only one event is dispatched', async () => { _helpers.makeTrackCall() - const startTime = Date.now() - const drainedCbArgs = await _helpers.listenOnDrain() - const drainedTime = Date.now() - startTime - expect(drainedTime).toBeGreaterThan(DRAINED_DELAY) - expect(drainedTime).toBeLessThan(DRAINED_DELAY + 200) - - expect(drainedCbArgs).toBeUndefined() + return expect( + new Promise((resolve) => ajs.once('drained', () => resolve(undefined))) + ).resolves.toBe(undefined) }) - test('every time a new event enters the queue, the timeout should be reset (like debounce)', async () => { - const DRAINED_DELAY = 250 - ajs = new AnalyticsNode({ - writeKey: 'abc123', - drainedDelay: DRAINED_DELAY, - }) - await ajs.register({ - ...testPlugin, - track: async (ctx) => { - await sleep(50) // should be - return ctx - }, + test('emits a drained event if multiple events are dispatched', async () => { + let drainedCalls = 0 + ajs.on('drained', () => { + drainedCalls++ }) - await new Promise((resolve) => - _helpers.makeTrackCall(undefined, () => resolve(undefined)) - ) _helpers.makeTrackCall() - - const startTime = Date.now() - await _helpers.listenOnDrain() - const drainedTime = Date.now() - startTime - expect(drainedTime).toBeGreaterThan(DRAINED_DELAY) - expect(drainedTime).toBeLessThan(DRAINED_DELAY + 200) + _helpers.makeTrackCall() + _helpers.makeTrackCall() + await sleep(200) + expect(drainedCalls).toBe(1) }) test('all callbacks should be called ', async () => { @@ -133,7 +110,7 @@ describe('Ability for users to exit without losing events', () => { expect(trackCallCount).toBe(1) }) - test('if queue has multiple track events, all of those items should be dispatched, and drain and track events should be emitted', async () => { + test('if queue has multiple track events, all of those items should be dispatched, and drain and track events should be emitted', async () => { let drainedCalls = 0 ajs.on('drained', () => { drainedCalls++ diff --git a/packages/node/src/app/analytics-node.ts b/packages/node/src/app/analytics-node.ts index fddd72770..b7937be8f 100644 --- a/packages/node/src/app/analytics-node.ts +++ b/packages/node/src/app/analytics-node.ts @@ -72,8 +72,6 @@ export class AnalyticsNode implements CoreAnalytics { private _eventFactory: EventFactory - private _drainedEventEmitTimeout?: ReturnType - private _drainedDelay: number private _isClosed = false private _pendingEvents = 0 @@ -84,7 +82,6 @@ export class AnalyticsNode constructor(settings: AnalyticsNodeSettings) { super() validateSettings(settings) - this._drainedDelay = settings.drainedDelay ?? 500 this._eventFactory = new EventFactory() this.queue = new EventQueue(new NodePriorityQueue(3)) @@ -134,10 +131,6 @@ export class AnalyticsNode this._pendingEvents++ - if (this._drainedEventEmitTimeout) { - clearTimeout(this._drainedEventEmitTimeout) - } - dispatchAndEmit(segmentEvent, this.queue, this, { callback: callback, }) @@ -146,9 +139,7 @@ export class AnalyticsNode this._pendingEvents-- if (!this._pendingEvents) { - this._drainedEventEmitTimeout = setTimeout(() => { - this.emit('drained') - }, this._drainedDelay) + this.emit('drained') } }) } diff --git a/packages/node/src/app/settings.ts b/packages/node/src/app/settings.ts index ef602e9e3..fc5a229b2 100644 --- a/packages/node/src/app/settings.ts +++ b/packages/node/src/app/settings.ts @@ -4,8 +4,6 @@ export interface AnalyticsNodeSettings { writeKey: string timeout?: number plugins?: CorePlugin[] - /** Number of ms to wait for the queue to be empty before emitting a 'drained' event */ - drainedDelay?: number } export const validateSettings = (settings: AnalyticsNodeSettings) => { From 20356759d393ddb26b744a15b9027aa919e582e5 Mon Sep 17 00:00:00 2001 From: Seth Silesky Date: Thu, 29 Sep 2022 12:33:10 -0500 Subject: [PATCH 11/19] update README --- packages/node/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/node/README.md b/packages/node/README.md index b49d90db1..4f9b04b8c 100644 --- a/packages/node/README.md +++ b/packages/node/README.md @@ -27,6 +27,8 @@ analytics.track('hello world', {}, { userId: "123456" }) * If a callback on an event call is included, this also waits for all callbacks to be called, and any of their subsequent promises to be resolved. ```ts await analytics.closeAndFlush() +// or +await analytics.closeAndFlush({ timeout: 5000 }) // automatically closes after 5000ms ``` ### Graceful Shutdown: Advanced Example ```ts From 7cd335662fd0e1a31572c6306528c2005c8490c7 Mon Sep 17 00:00:00 2001 From: Seth Silesky Date: Thu, 29 Sep 2022 22:52:56 -0500 Subject: [PATCH 12/19] update README --- packages/node/README.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/node/README.md b/packages/node/README.md index 4f9b04b8c..5e8c7e720 100644 --- a/packages/node/README.md +++ b/packages/node/README.md @@ -20,7 +20,6 @@ analytics.identify('Test User', { loggedIn: true }, { userId: "123456" }) analytics.track('hello world', {}, { userId: "123456" }) ``` - ## Graceful Shutdown ### Avoid losing events on exit! * Call `.closeAndFlush()` to stop collecting new events and flush all existing events. @@ -28,7 +27,7 @@ analytics.track('hello world', {}, { userId: "123456" }) ```ts await analytics.closeAndFlush() // or -await analytics.closeAndFlush({ timeout: 5000 }) // automatically closes after 5000ms +await analytics.closeAndFlush({ timeout: 5000 }) // force resolve after 5000ms ``` ### Graceful Shutdown: Advanced Example ```ts @@ -38,16 +37,22 @@ const app = express() const server = app.listen(3000) app.get('/', (req, res) => res.send('Hello World!')); -const onExit = () => { - setTimeout(async () => { - await analytics.closeAndFlush() // flush all existing events - server.close(() => process.exit()) - }, 0); +const onExit = async () => { + await analytics.closeAndFlush() // flush all existing events + console.log("Closing server ..."); + server.close(() => process.exit()); + setTimeout(() => { + console.log("Force closing!"); + process.exit(1); + }, 5000); // force close if connections are still open after 5 seconds }; +process.on("SIGINT", onExit); +process.on("SIGTERM", onExit); + process.on('SIGINT', onExit) process.on('SIGTERM', onExit); - +``` ``` ## Event Emitter From e695bf8612e75db52ad1877cb73281efb66a012d Mon Sep 17 00:00:00 2001 From: Seth Silesky Date: Thu, 29 Sep 2022 22:56:30 -0500 Subject: [PATCH 13/19] delete unused helper --- packages/node/README.md | 6 +----- .../src/__tests__/graceful-shutdown-integration.test.ts | 5 ----- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/node/README.md b/packages/node/README.md index 5e8c7e720..8fe05a325 100644 --- a/packages/node/README.md +++ b/packages/node/README.md @@ -38,7 +38,7 @@ const server = app.listen(3000) app.get('/', (req, res) => res.send('Hello World!')); const onExit = async () => { - await analytics.closeAndFlush() // flush all existing events + await analytics.closeAndFlush() // flush all existing events console.log("Closing server ..."); server.close(() => process.exit()); setTimeout(() => { @@ -49,10 +49,6 @@ const onExit = async () => { process.on("SIGINT", onExit); process.on("SIGTERM", onExit); - -process.on('SIGINT', onExit) -process.on('SIGTERM', onExit); -``` ``` ## Event Emitter diff --git a/packages/node/src/__tests__/graceful-shutdown-integration.test.ts b/packages/node/src/__tests__/graceful-shutdown-integration.test.ts index a4d9d666d..5334666f9 100644 --- a/packages/node/src/__tests__/graceful-shutdown-integration.test.ts +++ b/packages/node/src/__tests__/graceful-shutdown-integration.test.ts @@ -27,11 +27,6 @@ describe('Ability for users to exit without losing events', () => { makeTrackCall: (analytics = ajs, cb?: (...args: any[]) => void) => { analytics.track({ userId: 'foo', event: 'Thing Updated', callback: cb }) }, - listenOnDrain: (): Promise => { - return new Promise((resolve) => { - ajs.once('drained', () => resolve(undefined)) - }) - }, } describe('drained emitted event', () => { From 3eb0d3c68cd75c7570c2666fde4ff68a66547fcd Mon Sep 17 00:00:00 2001 From: Seth Silesky Date: Fri, 30 Sep 2022 15:48:20 -0500 Subject: [PATCH 14/19] update README --- packages/node/README.md | 11 +++++++++++ .../graceful-shutdown-integration.test.ts | 15 ++++++++++++++- packages/node/src/app/analytics-node.ts | 2 ++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/node/README.md b/packages/node/README.md index 8fe05a325..e629e8c1d 100644 --- a/packages/node/README.md +++ b/packages/node/README.md @@ -51,6 +51,17 @@ process.on("SIGINT", onExit); process.on("SIGTERM", onExit); ``` +### Graceful Shutdown: +If you absolutely need to preserve all possible events in the event of a forced timeout, even ones that came in after `analytics.closeAndFlush()` was called, you can collect those events. +```ts +const events = [] +ajs.on('call_after_close', (event) => events.push(events)) +await analytics.closeAndFlush() +console.log(events) // all events that came in after closeAndFlush was called + +``` + + ## Event Emitter ```ts import { analytics } from './analytics' diff --git a/packages/node/src/__tests__/graceful-shutdown-integration.test.ts b/packages/node/src/__tests__/graceful-shutdown-integration.test.ts index 5334666f9..856b8e5ec 100644 --- a/packages/node/src/__tests__/graceful-shutdown-integration.test.ts +++ b/packages/node/src/__tests__/graceful-shutdown-integration.test.ts @@ -3,7 +3,7 @@ import { createSuccess } from './test-helpers/factories' const fetcher = jest.fn().mockReturnValue(createSuccess()) jest.mock('node-fetch', () => fetcher) -import { AnalyticsNode } from '../app/analytics-node' +import { AnalyticsNode, NodeSegmentEvent } from '../app/analytics-node' import { sleep } from './test-helpers/sleep' import { CoreContext, CorePlugin } from '@segment/analytics-core' @@ -104,6 +104,19 @@ describe('Ability for users to exit without losing events', () => { expect(fetcher).toBeCalledTimes(1) expect(trackCallCount).toBe(1) }) + test('any events created after close should emit an error', async () => { + const events: NodeSegmentEvent[] = [] + ajs.on('call_after_close', (event) => { + events.push(event) + }) + _helpers.makeTrackCall() + const closed = ajs.closeAndFlush() + _helpers.makeTrackCall() // should be emitted + _helpers.makeTrackCall() // should be emitted + expect(events.length).toBe(2) + expect(events.every((e) => e.type === 'track')).toBeTruthy() + await closed + }) test('if queue has multiple track events, all of those items should be dispatched, and drain and track events should be emitted', async () => { let drainedCalls = 0 diff --git a/packages/node/src/app/analytics-node.ts b/packages/node/src/app/analytics-node.ts index b7937be8f..5d8f3760f 100644 --- a/packages/node/src/app/analytics-node.ts +++ b/packages/node/src/app/analytics-node.ts @@ -43,6 +43,7 @@ export interface NodeSegmentEventOptions { */ type NodeEmitterEvents = CoreEmitterContract & { initialize: [AnalyticsNodeSettings] + call_after_close: [NodeSegmentEvent] // any event that did not get dispatched due to close drained: [] } @@ -126,6 +127,7 @@ export class AnalyticsNode private _dispatch(segmentEvent: CoreSegmentEvent, callback?: Callback) { if (this._isClosed) { + this.emit('call_after_close', segmentEvent as NodeSegmentEvent) return undefined } From 307cc965610c2952ba82e8bd144b0649faa768fc Mon Sep 17 00:00:00 2001 From: Seth Silesky Date: Fri, 30 Sep 2022 15:50:38 -0500 Subject: [PATCH 15/19] update README --- packages/node/README.md | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/node/README.md b/packages/node/README.md index e629e8c1d..46e337c6b 100644 --- a/packages/node/README.md +++ b/packages/node/README.md @@ -31,33 +31,43 @@ await analytics.closeAndFlush({ timeout: 5000 }) // force resolve after 5000ms ``` ### Graceful Shutdown: Advanced Example ```ts +import { AnalyticsNode } from '@segment/analytics-node' import express from 'express' + +const analytics = new AnalyticsNode({ writeKey: '' }) + const app = express() +app.get('/', (req, res) => res.send('Hello World!')); const server = app.listen(3000) -app.get('/', (req, res) => res.send('Hello World!')); + const onExit = async () => { + console.log("Gracefully closing server..."); + await analytics.closeAndFlush() // flush all existing events - console.log("Closing server ..."); + server.close(() => process.exit()); + setTimeout(() => { - console.log("Force closing!"); + console.log("Force closing server!"); process.exit(1); - }, 5000); // force close if connections are still open after 5 seconds + }, 5000); }; process.on("SIGINT", onExit); process.on("SIGTERM", onExit); ``` -### Graceful Shutdown: +#### Collecting unflushed events If you absolutely need to preserve all possible events in the event of a forced timeout, even ones that came in after `analytics.closeAndFlush()` was called, you can collect those events. ```ts -const events = [] -ajs.on('call_after_close', (event) => events.push(events)) +const unflushedEvents = [] + +analytics.on('call_after_close', (event) => unflushedEvents.push(events)) await analytics.closeAndFlush() -console.log(events) // all events that came in after closeAndFlush was called + +console.log(unflushedEvents) // all events that came in after closeAndFlush was called ``` From 8d59e3f22733096774bc70119c01664f98d6f99e Mon Sep 17 00:00:00 2001 From: Seth Silesky Date: Fri, 30 Sep 2022 16:04:50 -0500 Subject: [PATCH 16/19] update README --- packages/core/src/plugins/middleware/index.ts | 1 + packages/node/README.md | 47 ++++++++++++++----- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/packages/core/src/plugins/middleware/index.ts b/packages/core/src/plugins/middleware/index.ts index c9ca33f0c..1574ddbfe 100644 --- a/packages/core/src/plugins/middleware/index.ts +++ b/packages/core/src/plugins/middleware/index.ts @@ -10,6 +10,7 @@ export interface MiddlewareParams { next: (payload: MiddlewareParams['payload'] | null) => void } +analytics.identify('Test User', { loggedIn: true }, { userId: '123456' }) export interface DestinationMiddlewareParams { payload: SegmentFacade integration: string diff --git a/packages/node/README.md b/packages/node/README.md index 46e337c6b..43b98d523 100644 --- a/packages/node/README.md +++ b/packages/node/README.md @@ -1,25 +1,42 @@ -# TODO: API Documentation is out of date -https://segment.com/docs/connections/sources/catalog/libraries/server/node/ +## Warning: Until 1.x release, use this library at your own risk! +While the API is very similar, the documentation for the legacy SDK (`analytics-node`) is here: https://segment.com/docs/connections/sources/catalog/libraries/server/node/ -NOTE: @segment/analytics-node is unstable! do not use. - ## Quick Start +### Install library +```bash +# npm +npm install @segment/analytics-node +# yarn +yarn add @segment/analytics-node +# pnpm +pnpm install @segment/analytics-node +``` + +### Usage (assuming some express-like web framework) ```ts -// analytics.ts import { AnalyticsNode } from '@segment/analytics-node' -export const analytics = new AnalyticsNode({ writeKey: '' }) - +const analytics = new AnalyticsNode({ writeKey: '' }) -// app.ts -import { analytics } from './analytics' -analytics.identify('Test User', { loggedIn: true }, { userId: "123456" }) -analytics.track('hello world', {}, { userId: "123456" }) +app.post('/login', (req, res) => { + analytics.identify({ + userId: req.body.userId, + previousId: req.body.previousId + }) +}) +app.post('/cart', (req, res) => { + analytics.track({ + userId: req.body.userId, + event: 'Add to cart', + properties: { productId: '123456' } + }) +}); ``` + ## Graceful Shutdown ### Avoid losing events on exit! * Call `.closeAndFlush()` to stop collecting new events and flush all existing events. @@ -37,7 +54,13 @@ import express from 'express' const analytics = new AnalyticsNode({ writeKey: '' }) const app = express() -app.get('/', (req, res) => res.send('Hello World!')); +app.post('/cart', (req, res) => { + analytics.track({ + userId: req.body.userId, + event: 'Add to cart', + properties: { productId: '123456' } + }) +}); const server = app.listen(3000) From cd04a0baf89fa56187de9db54321beef7eeb820d Mon Sep 17 00:00:00 2001 From: Seth Silesky <5115498+silesky@users.noreply.github.com> Date: Sat, 1 Oct 2022 00:43:37 -0500 Subject: [PATCH 17/19] oops --- packages/core/src/plugins/middleware/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/src/plugins/middleware/index.ts b/packages/core/src/plugins/middleware/index.ts index 1574ddbfe..c9ca33f0c 100644 --- a/packages/core/src/plugins/middleware/index.ts +++ b/packages/core/src/plugins/middleware/index.ts @@ -10,7 +10,6 @@ export interface MiddlewareParams { next: (payload: MiddlewareParams['payload'] | null) => void } -analytics.identify('Test User', { loggedIn: true }, { userId: '123456' }) export interface DestinationMiddlewareParams { payload: SegmentFacade integration: string From fbc5f3ca1adbc07b7d37237ec2b3b31ae9da2a56 Mon Sep 17 00:00:00 2001 From: Seth Silesky <5115498+silesky@users.noreply.github.com> Date: Sat, 1 Oct 2022 00:45:30 -0500 Subject: [PATCH 18/19] Update README.md --- packages/node/README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/node/README.md b/packages/node/README.md index 43b98d523..52711242b 100644 --- a/packages/node/README.md +++ b/packages/node/README.md @@ -67,15 +67,8 @@ const server = app.listen(3000) const onExit = async () => { console.log("Gracefully closing server..."); - await analytics.closeAndFlush() // flush all existing events - server.close(() => process.exit()); - - setTimeout(() => { - console.log("Force closing server!"); - process.exit(1); - }, 5000); }; process.on("SIGINT", onExit); From 4614ebf76ad4b0de07a5b02d846c9112360c2aa0 Mon Sep 17 00:00:00 2001 From: Seth Silesky <5115498+silesky@users.noreply.github.com> Date: Sat, 1 Oct 2022 00:48:21 -0500 Subject: [PATCH 19/19] Update graceful-shutdown-integration.test.ts --- .../node/src/__tests__/graceful-shutdown-integration.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node/src/__tests__/graceful-shutdown-integration.test.ts b/packages/node/src/__tests__/graceful-shutdown-integration.test.ts index 856b8e5ec..1a0cac06d 100644 --- a/packages/node/src/__tests__/graceful-shutdown-integration.test.ts +++ b/packages/node/src/__tests__/graceful-shutdown-integration.test.ts @@ -104,7 +104,7 @@ describe('Ability for users to exit without losing events', () => { expect(fetcher).toBeCalledTimes(1) expect(trackCallCount).toBe(1) }) - test('any events created after close should emit an error', async () => { + test('any events created after close should be emitted', async () => { const events: NodeSegmentEvent[] = [] ajs.on('call_after_close', (event) => { events.push(event)