@product-intelligence-hub/sdk-node 0.3.0

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/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # @product-intelligence-hub/sdk-node
2
+
3
+ Node.js Server SDK for [Product Intelligence Hub](https://github.com/product-intelligence-hub/product-intelligence-hub).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @product-intelligence-hub/sdk-node
9
+ # or
10
+ pnpm add @product-intelligence-hub/sdk-node
11
+ # or
12
+ yarn add @product-intelligence-hub/sdk-node
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```typescript
18
+ import PIHNode from "@product-intelligence-hub/sdk-node";
19
+
20
+ // Initialize (once at server startup)
21
+ const pih = PIHNode.init({
22
+ apiKey: "your-environment-api-key",
23
+ projectId: "your-project-id",
24
+ environment: "prod",
25
+ tenantId: "tenant_abc", // optional, can also use setTenant()
26
+ });
27
+
28
+ // Track an event
29
+ pih.capture("user-42", "purchase_completed", {
30
+ amount: 99.99,
31
+ currency: "USD",
32
+ plan: "pro",
33
+ });
34
+
35
+ // Identify a user with traits
36
+ await pih.identify("user-42", {
37
+ name: "Jane Doe",
38
+ email: "jane@example.com",
39
+ plan: "pro",
40
+ });
41
+
42
+ // Force flush queued events
43
+ await pih.flush();
44
+
45
+ // Graceful shutdown (flush + cleanup)
46
+ await pih.shutdown();
47
+ ```
48
+
49
+ ## API
50
+
51
+ ### `PIHNode.init(config)`
52
+
53
+ Initialize the SDK singleton. Returns a `NodePIHClient` instance.
54
+
55
+ | Option | Type | Default | Description |
56
+ |---|---|---|---|
57
+ | `apiKey` | `string` | **required** | Environment API key |
58
+ | `projectId` | `string` | **required** | Project identifier |
59
+ | `environment` | `"dev" \| "staging" \| "prod"` | **required** | Environment name |
60
+ | `tenantId` | `string` | `""` | Tenant identifier |
61
+ | `apiUrl` | `string` | `"https://repoingest-production.up.railway.app"` | Ingest API URL |
62
+ | `appVersion` | `string` | `undefined` | Application version |
63
+ | `debug` | `boolean` | `false` | Enable debug logging |
64
+ | `flushAt` | `number` | `20` | Events before auto-flush |
65
+ | `flushInterval` | `number` | `10000` | Ms between auto-flushes |
66
+
67
+ ### `client.capture(distinctId, event, properties?)`
68
+
69
+ Track a server-side event. Queues the event internally (non-blocking).
70
+
71
+ ### `client.identify(userId, traits?)`
72
+
73
+ Identify a user and merge traits. Sends an identify call to the server.
74
+
75
+ ### `client.flush()`
76
+
77
+ Force flush all queued events to the ingest API.
78
+
79
+ ### `client.shutdown()`
80
+
81
+ Graceful shutdown: flushes the queue, detaches process listeners, and destroys the client. Call this before your server process exits.
82
+
83
+ ### `PIHNode.getInstance()`
84
+
85
+ Returns the current singleton instance, or `null` if not initialized.
86
+
87
+ ### `PIHNode.resetInstance()`
88
+
89
+ Destroys and resets the singleton. Useful for testing.
90
+
91
+ ## Graceful Shutdown
92
+
93
+ The SDK automatically listens for the `beforeExit` process event and flushes remaining events. For explicit control, call `shutdown()` in your server's cleanup handler:
94
+
95
+ ```typescript
96
+ process.on("SIGTERM", async () => {
97
+ await pih.shutdown();
98
+ process.exit(0);
99
+ });
100
+ ```
101
+
102
+ ## License
103
+
104
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict';Object.defineProperty(exports,'__esModule',{value:true});var sdkCore=require('@product-intelligence-hub/sdk-core');var r=class{store=new Map;async get(e){return this.store.get(e)??null}async set(e,i){this.store.set(e,i);}async remove(e){this.store.delete(e);}};var a="0.3.0",s=class extends sdkCore.PIHClient{processListenerAttached=false;shutdownInProgress=false;boundBeforeExit;constructor(e){let i={...e,platform:"server",flushAt:e.flushAt??20};super(i,new r),this.transport.setSDKMeta({name:"pih-sdk-node",version:a}),this.boundBeforeExit=()=>{this.flush().catch(o=>{this.log("beforeExit flush error:",o);});};}async initialize(){await super.initialize(),this.attachProcessListeners();}capture(e,i,o){let d={event_id:sdkCore.generateUUID(),timestamp:sdkCore.getTimestamp(),project_id:this.config.projectId,environment:this.config.environment,tenant_id:this.tenantId,event_name:i,anonymous_id:this.identity.getAnonymousId(),user_id:e,session_id:null,platform:"server",app_version:this.config.appVersion??null,properties:o??{},user_traits:this.identity.getUserTraits(),context:this.getContext()};this.queue.enqueue(d).catch(c=>{this.log("capture enqueue error:",c);});}async identify(e,i){await this.ensureInitialized(),await super.identify(e,i);}getContext(){return {library:{name:"pih-sdk-node",version:a},runtime:"node",nodeVersion:process.version}}async flush(){await this.ensureInitialized(),await this.queue.flush();}async shutdown(){if(!this.shutdownInProgress){this.shutdownInProgress=true;try{await this.flush();}catch(e){this.log("shutdown flush error:",e);}this.detachProcessListeners(),this.destroy();}}log(...e){this.config.debug&&console.log("[PIH Node]",...e);}attachProcessListeners(){this.processListenerAttached||(process.on("beforeExit",this.boundBeforeExit),this.processListenerAttached=true);}detachProcessListeners(){this.processListenerAttached&&(process.removeListener("beforeExit",this.boundBeforeExit),this.processListenerAttached=false);}},t=null;function p(n){return t?(console.warn("[PIH Node] SDK already initialized. Returning existing instance."),t):(t=new s(n),t.initialize().catch(e=>{console.error("[PIH Node] Failed to initialize:",e);}),t)}function f(){return t}function g(){t&&(t.destroy(),t=null);}var v={init:p,getInstance:f,resetInstance:g,NodePIHClient:s},y=v;exports.MemoryStorage=r;exports.NodePIHClient=s;exports.default=y;exports.getInstance=f;exports.init=p;exports.resetInstance=g;//# sourceMappingURL=index.cjs.map
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/storage.ts","../src/index.ts"],"names":["MemoryStorage","key","value","SDK_VERSION","NodePIHClient","PIHClient","config","nodeConfig","err","distinctId","event","properties","trackEvent","generateUUID","getTimestamp","userId","traits","args","instance","init","error","getInstance","resetInstance","PIHNode","index_default"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { StorageAdapter } from \"@product-intelligence-hub/sdk-core\";\n\n/**\n * In-memory storage adapter for server environments.\n * No persistence needed -- server processes manage their own lifecycle.\n */\nexport class MemoryStorage implements StorageAdapter {\n private store = new Map<string, string>();\n\n async get(key: string): Promise<string | null> {\n return this.store.get(key) ?? null;\n }\n\n async set(key: string, value: string): Promise<void> {\n this.store.set(key, value);\n }\n\n async remove(key: string): Promise<void> {\n this.store.delete(key);\n }\n}\n","import {\n PIHClient,\n type PIHConfig,\n type TrackEvent,\n generateUUID,\n getTimestamp,\n} from \"@product-intelligence-hub/sdk-core\";\n\nimport { MemoryStorage } from \"./storage.js\";\n\nconst SDK_VERSION = \"0.3.0\";\n\n/**\n * Node.js-specific config -- omits platform (always \"server\")\n */\nexport interface NodePIHConfig extends Omit<PIHConfig, \"platform\"> {\n platform?: \"server\";\n}\n\n/**\n * Node.js PIH Client for server-side event tracking\n */\nexport class NodePIHClient extends PIHClient {\n private processListenerAttached = false;\n private shutdownInProgress = false;\n private boundBeforeExit: () => void;\n\n constructor(config: NodePIHConfig) {\n const nodeConfig: PIHConfig = {\n ...config,\n platform: \"server\",\n flushAt: config.flushAt ?? 20,\n };\n\n super(nodeConfig, new MemoryStorage());\n\n // Set SDK metadata for transport headers\n this.transport.setSDKMeta({\n name: \"pih-sdk-node\",\n version: SDK_VERSION,\n });\n\n this.boundBeforeExit = () => {\n this.flush().catch((err) => {\n this.log(\"beforeExit flush error:\", err);\n });\n };\n }\n\n /**\n * Initialize the client and attach process lifecycle listeners\n */\n override async initialize(): Promise<void> {\n await super.initialize();\n this.attachProcessListeners();\n }\n\n /**\n * Track an event (synchronous -- queues internally)\n */\n capture(\n distinctId: string,\n event: string,\n properties?: Record<string, unknown>\n ): void {\n const trackEvent: TrackEvent = {\n event_id: generateUUID(),\n timestamp: getTimestamp(),\n project_id: this.config.projectId,\n environment: this.config.environment,\n tenant_id: this.tenantId,\n event_name: event,\n anonymous_id: this.identity.getAnonymousId(),\n user_id: distinctId,\n session_id: null,\n platform: \"server\",\n app_version: this.config.appVersion ?? null,\n properties: properties ?? {},\n user_traits: this.identity.getUserTraits(),\n context: this.getContext(),\n };\n\n this.queue.enqueue(trackEvent).catch((err) => {\n this.log(\"capture enqueue error:\", err);\n });\n }\n\n /**\n * Identify user and merge traits\n */\n override async identify(\n userId: string,\n traits?: Record<string, unknown>\n ): Promise<void> {\n await this.ensureInitialized();\n await super.identify(userId, traits);\n }\n\n /**\n * Return server context for every event\n */\n protected override getContext(): Record<string, unknown> {\n return {\n library: {\n name: \"pih-sdk-node\",\n version: SDK_VERSION,\n },\n runtime: \"node\",\n nodeVersion: process.version,\n };\n }\n\n /**\n * Force flush the event queue\n */\n override async flush(): Promise<void> {\n await this.ensureInitialized();\n await this.queue.flush();\n }\n\n /**\n * Graceful shutdown: flush, detach listeners, destroy\n */\n async shutdown(): Promise<void> {\n if (this.shutdownInProgress) return;\n this.shutdownInProgress = true;\n\n try {\n await this.flush();\n } catch (err) {\n this.log(\"shutdown flush error:\", err);\n }\n\n this.detachProcessListeners();\n this.destroy();\n }\n\n protected override log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log(\"[PIH Node]\", ...args);\n }\n }\n\n private attachProcessListeners(): void {\n if (this.processListenerAttached) return;\n process.on(\"beforeExit\", this.boundBeforeExit);\n this.processListenerAttached = true;\n }\n\n private detachProcessListeners(): void {\n if (!this.processListenerAttached) return;\n process.removeListener(\"beforeExit\", this.boundBeforeExit);\n this.processListenerAttached = false;\n }\n}\n\n// ========================================\n// Singleton API\n// ========================================\n\nlet instance: NodePIHClient | null = null;\n\n/**\n * Initialize the PIH Node SDK\n */\nfunction init(config: NodePIHConfig): NodePIHClient {\n if (instance) {\n console.warn(\"[PIH Node] SDK already initialized. Returning existing instance.\");\n return instance;\n }\n\n instance = new NodePIHClient(config);\n\n // Auto-initialize (non-blocking)\n instance.initialize().catch((error) => {\n console.error(\"[PIH Node] Failed to initialize:\", error);\n });\n\n return instance;\n}\n\n/**\n * Get the current instance\n */\nfunction getInstance(): NodePIHClient | null {\n return instance;\n}\n\n/**\n * Reset the singleton (for testing)\n */\nfunction resetInstance(): void {\n if (instance) {\n instance.destroy();\n instance = null;\n }\n}\n\nconst PIHNode = {\n init,\n getInstance,\n resetInstance,\n NodePIHClient,\n};\n\nexport default PIHNode;\nexport { init, getInstance, resetInstance, MemoryStorage };\n\n// Re-export types from core\nexport type {\n PIHConfig,\n TrackEvent,\n TrackOptions,\n TransportOptions,\n PIHError,\n PIHErrorCode,\n} from \"@product-intelligence-hub/sdk-core\";\n"]}
@@ -0,0 +1,76 @@
1
+ import { StorageAdapter, PIHClient, PIHConfig } from '@product-intelligence-hub/sdk-core';
2
+ export { PIHConfig, PIHError, PIHErrorCode, TrackEvent, TrackOptions, TransportOptions } from '@product-intelligence-hub/sdk-core';
3
+
4
+ /**
5
+ * In-memory storage adapter for server environments.
6
+ * No persistence needed -- server processes manage their own lifecycle.
7
+ */
8
+ declare class MemoryStorage implements StorageAdapter {
9
+ private store;
10
+ get(key: string): Promise<string | null>;
11
+ set(key: string, value: string): Promise<void>;
12
+ remove(key: string): Promise<void>;
13
+ }
14
+
15
+ /**
16
+ * Node.js-specific config -- omits platform (always "server")
17
+ */
18
+ interface NodePIHConfig extends Omit<PIHConfig, "platform"> {
19
+ platform?: "server";
20
+ }
21
+ /**
22
+ * Node.js PIH Client for server-side event tracking
23
+ */
24
+ declare class NodePIHClient extends PIHClient {
25
+ private processListenerAttached;
26
+ private shutdownInProgress;
27
+ private boundBeforeExit;
28
+ constructor(config: NodePIHConfig);
29
+ /**
30
+ * Initialize the client and attach process lifecycle listeners
31
+ */
32
+ initialize(): Promise<void>;
33
+ /**
34
+ * Track an event (synchronous -- queues internally)
35
+ */
36
+ capture(distinctId: string, event: string, properties?: Record<string, unknown>): void;
37
+ /**
38
+ * Identify user and merge traits
39
+ */
40
+ identify(userId: string, traits?: Record<string, unknown>): Promise<void>;
41
+ /**
42
+ * Return server context for every event
43
+ */
44
+ protected getContext(): Record<string, unknown>;
45
+ /**
46
+ * Force flush the event queue
47
+ */
48
+ flush(): Promise<void>;
49
+ /**
50
+ * Graceful shutdown: flush, detach listeners, destroy
51
+ */
52
+ shutdown(): Promise<void>;
53
+ protected log(...args: unknown[]): void;
54
+ private attachProcessListeners;
55
+ private detachProcessListeners;
56
+ }
57
+ /**
58
+ * Initialize the PIH Node SDK
59
+ */
60
+ declare function init(config: NodePIHConfig): NodePIHClient;
61
+ /**
62
+ * Get the current instance
63
+ */
64
+ declare function getInstance(): NodePIHClient | null;
65
+ /**
66
+ * Reset the singleton (for testing)
67
+ */
68
+ declare function resetInstance(): void;
69
+ declare const PIHNode: {
70
+ init: typeof init;
71
+ getInstance: typeof getInstance;
72
+ resetInstance: typeof resetInstance;
73
+ NodePIHClient: typeof NodePIHClient;
74
+ };
75
+
76
+ export { MemoryStorage, NodePIHClient, type NodePIHConfig, PIHNode as default, getInstance, init, resetInstance };
@@ -0,0 +1,76 @@
1
+ import { StorageAdapter, PIHClient, PIHConfig } from '@product-intelligence-hub/sdk-core';
2
+ export { PIHConfig, PIHError, PIHErrorCode, TrackEvent, TrackOptions, TransportOptions } from '@product-intelligence-hub/sdk-core';
3
+
4
+ /**
5
+ * In-memory storage adapter for server environments.
6
+ * No persistence needed -- server processes manage their own lifecycle.
7
+ */
8
+ declare class MemoryStorage implements StorageAdapter {
9
+ private store;
10
+ get(key: string): Promise<string | null>;
11
+ set(key: string, value: string): Promise<void>;
12
+ remove(key: string): Promise<void>;
13
+ }
14
+
15
+ /**
16
+ * Node.js-specific config -- omits platform (always "server")
17
+ */
18
+ interface NodePIHConfig extends Omit<PIHConfig, "platform"> {
19
+ platform?: "server";
20
+ }
21
+ /**
22
+ * Node.js PIH Client for server-side event tracking
23
+ */
24
+ declare class NodePIHClient extends PIHClient {
25
+ private processListenerAttached;
26
+ private shutdownInProgress;
27
+ private boundBeforeExit;
28
+ constructor(config: NodePIHConfig);
29
+ /**
30
+ * Initialize the client and attach process lifecycle listeners
31
+ */
32
+ initialize(): Promise<void>;
33
+ /**
34
+ * Track an event (synchronous -- queues internally)
35
+ */
36
+ capture(distinctId: string, event: string, properties?: Record<string, unknown>): void;
37
+ /**
38
+ * Identify user and merge traits
39
+ */
40
+ identify(userId: string, traits?: Record<string, unknown>): Promise<void>;
41
+ /**
42
+ * Return server context for every event
43
+ */
44
+ protected getContext(): Record<string, unknown>;
45
+ /**
46
+ * Force flush the event queue
47
+ */
48
+ flush(): Promise<void>;
49
+ /**
50
+ * Graceful shutdown: flush, detach listeners, destroy
51
+ */
52
+ shutdown(): Promise<void>;
53
+ protected log(...args: unknown[]): void;
54
+ private attachProcessListeners;
55
+ private detachProcessListeners;
56
+ }
57
+ /**
58
+ * Initialize the PIH Node SDK
59
+ */
60
+ declare function init(config: NodePIHConfig): NodePIHClient;
61
+ /**
62
+ * Get the current instance
63
+ */
64
+ declare function getInstance(): NodePIHClient | null;
65
+ /**
66
+ * Reset the singleton (for testing)
67
+ */
68
+ declare function resetInstance(): void;
69
+ declare const PIHNode: {
70
+ init: typeof init;
71
+ getInstance: typeof getInstance;
72
+ resetInstance: typeof resetInstance;
73
+ NodePIHClient: typeof NodePIHClient;
74
+ };
75
+
76
+ export { MemoryStorage, NodePIHClient, type NodePIHConfig, PIHNode as default, getInstance, init, resetInstance };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import {PIHClient,getTimestamp,generateUUID}from'@product-intelligence-hub/sdk-core';var r=class{store=new Map;async get(e){return this.store.get(e)??null}async set(e,i){this.store.set(e,i);}async remove(e){this.store.delete(e);}};var a="0.3.0",s=class extends PIHClient{processListenerAttached=false;shutdownInProgress=false;boundBeforeExit;constructor(e){let i={...e,platform:"server",flushAt:e.flushAt??20};super(i,new r),this.transport.setSDKMeta({name:"pih-sdk-node",version:a}),this.boundBeforeExit=()=>{this.flush().catch(o=>{this.log("beforeExit flush error:",o);});};}async initialize(){await super.initialize(),this.attachProcessListeners();}capture(e,i,o){let d={event_id:generateUUID(),timestamp:getTimestamp(),project_id:this.config.projectId,environment:this.config.environment,tenant_id:this.tenantId,event_name:i,anonymous_id:this.identity.getAnonymousId(),user_id:e,session_id:null,platform:"server",app_version:this.config.appVersion??null,properties:o??{},user_traits:this.identity.getUserTraits(),context:this.getContext()};this.queue.enqueue(d).catch(c=>{this.log("capture enqueue error:",c);});}async identify(e,i){await this.ensureInitialized(),await super.identify(e,i);}getContext(){return {library:{name:"pih-sdk-node",version:a},runtime:"node",nodeVersion:process.version}}async flush(){await this.ensureInitialized(),await this.queue.flush();}async shutdown(){if(!this.shutdownInProgress){this.shutdownInProgress=true;try{await this.flush();}catch(e){this.log("shutdown flush error:",e);}this.detachProcessListeners(),this.destroy();}}log(...e){this.config.debug&&console.log("[PIH Node]",...e);}attachProcessListeners(){this.processListenerAttached||(process.on("beforeExit",this.boundBeforeExit),this.processListenerAttached=true);}detachProcessListeners(){this.processListenerAttached&&(process.removeListener("beforeExit",this.boundBeforeExit),this.processListenerAttached=false);}},t=null;function p(n){return t?(console.warn("[PIH Node] SDK already initialized. Returning existing instance."),t):(t=new s(n),t.initialize().catch(e=>{console.error("[PIH Node] Failed to initialize:",e);}),t)}function f(){return t}function g(){t&&(t.destroy(),t=null);}var v={init:p,getInstance:f,resetInstance:g,NodePIHClient:s},y=v;export{r as MemoryStorage,s as NodePIHClient,y as default,f as getInstance,p as init,g as resetInstance};//# sourceMappingURL=index.js.map
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/storage.ts","../src/index.ts"],"names":["MemoryStorage","key","value","SDK_VERSION","NodePIHClient","PIHClient","config","nodeConfig","err","distinctId","event","properties","trackEvent","generateUUID","getTimestamp","userId","traits","args","instance","init","error","getInstance","resetInstance","PIHNode","index_default"],"mappings":"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","file":"index.js","sourcesContent":["import type { StorageAdapter } from \"@product-intelligence-hub/sdk-core\";\n\n/**\n * In-memory storage adapter for server environments.\n * No persistence needed -- server processes manage their own lifecycle.\n */\nexport class MemoryStorage implements StorageAdapter {\n private store = new Map<string, string>();\n\n async get(key: string): Promise<string | null> {\n return this.store.get(key) ?? null;\n }\n\n async set(key: string, value: string): Promise<void> {\n this.store.set(key, value);\n }\n\n async remove(key: string): Promise<void> {\n this.store.delete(key);\n }\n}\n","import {\n PIHClient,\n type PIHConfig,\n type TrackEvent,\n generateUUID,\n getTimestamp,\n} from \"@product-intelligence-hub/sdk-core\";\n\nimport { MemoryStorage } from \"./storage.js\";\n\nconst SDK_VERSION = \"0.3.0\";\n\n/**\n * Node.js-specific config -- omits platform (always \"server\")\n */\nexport interface NodePIHConfig extends Omit<PIHConfig, \"platform\"> {\n platform?: \"server\";\n}\n\n/**\n * Node.js PIH Client for server-side event tracking\n */\nexport class NodePIHClient extends PIHClient {\n private processListenerAttached = false;\n private shutdownInProgress = false;\n private boundBeforeExit: () => void;\n\n constructor(config: NodePIHConfig) {\n const nodeConfig: PIHConfig = {\n ...config,\n platform: \"server\",\n flushAt: config.flushAt ?? 20,\n };\n\n super(nodeConfig, new MemoryStorage());\n\n // Set SDK metadata for transport headers\n this.transport.setSDKMeta({\n name: \"pih-sdk-node\",\n version: SDK_VERSION,\n });\n\n this.boundBeforeExit = () => {\n this.flush().catch((err) => {\n this.log(\"beforeExit flush error:\", err);\n });\n };\n }\n\n /**\n * Initialize the client and attach process lifecycle listeners\n */\n override async initialize(): Promise<void> {\n await super.initialize();\n this.attachProcessListeners();\n }\n\n /**\n * Track an event (synchronous -- queues internally)\n */\n capture(\n distinctId: string,\n event: string,\n properties?: Record<string, unknown>\n ): void {\n const trackEvent: TrackEvent = {\n event_id: generateUUID(),\n timestamp: getTimestamp(),\n project_id: this.config.projectId,\n environment: this.config.environment,\n tenant_id: this.tenantId,\n event_name: event,\n anonymous_id: this.identity.getAnonymousId(),\n user_id: distinctId,\n session_id: null,\n platform: \"server\",\n app_version: this.config.appVersion ?? null,\n properties: properties ?? {},\n user_traits: this.identity.getUserTraits(),\n context: this.getContext(),\n };\n\n this.queue.enqueue(trackEvent).catch((err) => {\n this.log(\"capture enqueue error:\", err);\n });\n }\n\n /**\n * Identify user and merge traits\n */\n override async identify(\n userId: string,\n traits?: Record<string, unknown>\n ): Promise<void> {\n await this.ensureInitialized();\n await super.identify(userId, traits);\n }\n\n /**\n * Return server context for every event\n */\n protected override getContext(): Record<string, unknown> {\n return {\n library: {\n name: \"pih-sdk-node\",\n version: SDK_VERSION,\n },\n runtime: \"node\",\n nodeVersion: process.version,\n };\n }\n\n /**\n * Force flush the event queue\n */\n override async flush(): Promise<void> {\n await this.ensureInitialized();\n await this.queue.flush();\n }\n\n /**\n * Graceful shutdown: flush, detach listeners, destroy\n */\n async shutdown(): Promise<void> {\n if (this.shutdownInProgress) return;\n this.shutdownInProgress = true;\n\n try {\n await this.flush();\n } catch (err) {\n this.log(\"shutdown flush error:\", err);\n }\n\n this.detachProcessListeners();\n this.destroy();\n }\n\n protected override log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log(\"[PIH Node]\", ...args);\n }\n }\n\n private attachProcessListeners(): void {\n if (this.processListenerAttached) return;\n process.on(\"beforeExit\", this.boundBeforeExit);\n this.processListenerAttached = true;\n }\n\n private detachProcessListeners(): void {\n if (!this.processListenerAttached) return;\n process.removeListener(\"beforeExit\", this.boundBeforeExit);\n this.processListenerAttached = false;\n }\n}\n\n// ========================================\n// Singleton API\n// ========================================\n\nlet instance: NodePIHClient | null = null;\n\n/**\n * Initialize the PIH Node SDK\n */\nfunction init(config: NodePIHConfig): NodePIHClient {\n if (instance) {\n console.warn(\"[PIH Node] SDK already initialized. Returning existing instance.\");\n return instance;\n }\n\n instance = new NodePIHClient(config);\n\n // Auto-initialize (non-blocking)\n instance.initialize().catch((error) => {\n console.error(\"[PIH Node] Failed to initialize:\", error);\n });\n\n return instance;\n}\n\n/**\n * Get the current instance\n */\nfunction getInstance(): NodePIHClient | null {\n return instance;\n}\n\n/**\n * Reset the singleton (for testing)\n */\nfunction resetInstance(): void {\n if (instance) {\n instance.destroy();\n instance = null;\n }\n}\n\nconst PIHNode = {\n init,\n getInstance,\n resetInstance,\n NodePIHClient,\n};\n\nexport default PIHNode;\nexport { init, getInstance, resetInstance, MemoryStorage };\n\n// Re-export types from core\nexport type {\n PIHConfig,\n TrackEvent,\n TrackOptions,\n TransportOptions,\n PIHError,\n PIHErrorCode,\n} from \"@product-intelligence-hub/sdk-core\";\n"]}
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@product-intelligence-hub/sdk-node",
3
+ "version": "0.3.0",
4
+ "description": "Node.js Server SDK for Product Intelligence Hub",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "require": {
17
+ "types": "./dist/index.d.cts",
18
+ "default": "./dist/index.cjs"
19
+ }
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "README.md"
25
+ ],
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/product-intelligence-hub/product-intelligence-hub",
32
+ "directory": "packages/sdk-node"
33
+ },
34
+ "keywords": [
35
+ "analytics",
36
+ "product-intelligence",
37
+ "sdk",
38
+ "tracking",
39
+ "events",
40
+ "node",
41
+ "server"
42
+ ],
43
+ "dependencies": {
44
+ "@product-intelligence-hub/sdk-core": "0.3.0"
45
+ },
46
+ "devDependencies": {
47
+ "@types/node": "^20.12.0",
48
+ "tsup": "^8.0.0",
49
+ "typescript": "^5.4.0",
50
+ "vitest": "^1.6.0"
51
+ },
52
+ "scripts": {
53
+ "build": "tsup",
54
+ "dev": "tsup --watch",
55
+ "typecheck": "tsc --noEmit",
56
+ "lint": "eslint src --ext .ts",
57
+ "test": "vitest run",
58
+ "test:watch": "vitest"
59
+ }
60
+ }