@gurulu/node 0.1.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,49 @@
1
+ # @gurulu/node
2
+
3
+ Server-side analytics SDK for Gurulu.io. Tracks events, identifies users, and batches requests with automatic retries.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @gurulu/node
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { Gurulu } from '@gurulu/node';
15
+
16
+ const gurulu = new Gurulu({
17
+ siteId: 'your-site-id',
18
+ apiKey: 'your-api-key',
19
+ });
20
+
21
+ // Track an event
22
+ gurulu.track('purchase_completed', { amount: 49.99, currency: 'USD' }, {
23
+ userId: 'user-123',
24
+ });
25
+
26
+ // Identify a user
27
+ gurulu.identify('user-123', {
28
+ email: 'user@example.com',
29
+ plan: 'pro',
30
+ });
31
+
32
+ // Manually flush the event queue
33
+ await gurulu.flush();
34
+
35
+ // Graceful shutdown (flushes remaining events and stops the timer)
36
+ await gurulu.shutdown();
37
+ ```
38
+
39
+ ## Configuration
40
+
41
+ | Option | Type | Default | Description |
42
+ |---|---|---|---|
43
+ | `siteId` | `string` | **required** | Your Gurulu site ID |
44
+ | `apiKey` | `string` | **required** | Your Gurulu API key |
45
+ | `endpoint` | `string` | `https://app.gurulu.io/api/ingest/v1/server` | Custom ingest endpoint |
46
+ | `flushInterval` | `number` | `10000` | Auto-flush interval in ms |
47
+ | `maxBatchSize` | `number` | `50` | Max events per batch |
48
+ | `maxRetries` | `number` | `3` | Retry count for failed requests |
49
+ | `debug` | `boolean` | `false` | Enable debug logging |
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Business event emitters — Phase 10 W4.2.
3
+ *
4
+ * These are tiny factories that return envelope-shaped `ServerEvent` objects
5
+ * for common server-authored events (user signup, payment, subscription,
6
+ * order, lead capture). They stamp the canonical `event_tier = 'verified'`
7
+ * and `event_source = 'server_sdk'` fields so downstream readers can trust
8
+ * the provenance without guessing.
9
+ *
10
+ * The shape intentionally satisfies BOTH:
11
+ * - the simple `ServerEvent` contract the /api/ingest/v1/server route
12
+ * consumes (event_name, user_id, properties, timestamp,
13
+ * idempotency_key?, correlation_id?), AND
14
+ * - the richer canonical Envelope fields consumers may want to read
15
+ * (event_tier, event_source) — we attach these at the top level so they
16
+ * travel alongside the event without collapsing into properties.
17
+ *
18
+ * `createEnvelope` from @gurulu/shared-core is used to validate/normalize the
19
+ * shared envelope defaults where practical (event_type, timestamp,
20
+ * consent_level, etc.), but the ServerEvent-specific keys (user_id) remain at
21
+ * the root.
22
+ *
23
+ * Related docs: PHASE-10-ROADMAP.md §W4.2
24
+ */
25
+ import { type Envelope, type ServerEvent } from '@gurulu/shared-core';
26
+ /**
27
+ * Concrete shape returned by every business-event factory below:
28
+ * a ServerEvent with the canonical envelope provenance fields attached.
29
+ */
30
+ export type BusinessEvent = ServerEvent & {
31
+ event_tier: Envelope['event_tier'];
32
+ event_source: Envelope['event_source'];
33
+ event_type: Envelope['event_type'];
34
+ };
35
+ export interface UserCreatedArgs {
36
+ userId: string;
37
+ email?: string;
38
+ traits?: Record<string, unknown>;
39
+ }
40
+ export declare function userCreated(args: UserCreatedArgs): BusinessEvent;
41
+ export interface PaymentSucceededArgs {
42
+ userId: string;
43
+ amount: number;
44
+ currency: string;
45
+ orderId: string;
46
+ properties?: Record<string, unknown>;
47
+ }
48
+ export declare function paymentSucceeded(args: PaymentSucceededArgs): BusinessEvent;
49
+ export interface SubscriptionStartedArgs {
50
+ userId: string;
51
+ plan: string;
52
+ amount?: number;
53
+ currency?: string;
54
+ }
55
+ export declare function subscriptionStarted(args: SubscriptionStartedArgs): BusinessEvent;
56
+ export interface OrderPlacedArgs {
57
+ userId: string;
58
+ orderId: string;
59
+ total: number;
60
+ currency: string;
61
+ items?: Array<{
62
+ id: string;
63
+ qty: number;
64
+ price: number;
65
+ }>;
66
+ }
67
+ export declare function orderPlaced(args: OrderPlacedArgs): BusinessEvent;
68
+ export interface LeadCapturedArgs {
69
+ anonymousId: string;
70
+ email?: string;
71
+ source?: string;
72
+ }
73
+ export declare function leadCaptured(args: LeadCapturedArgs): BusinessEvent;
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ /**
3
+ * Business event emitters — Phase 10 W4.2.
4
+ *
5
+ * These are tiny factories that return envelope-shaped `ServerEvent` objects
6
+ * for common server-authored events (user signup, payment, subscription,
7
+ * order, lead capture). They stamp the canonical `event_tier = 'verified'`
8
+ * and `event_source = 'server_sdk'` fields so downstream readers can trust
9
+ * the provenance without guessing.
10
+ *
11
+ * The shape intentionally satisfies BOTH:
12
+ * - the simple `ServerEvent` contract the /api/ingest/v1/server route
13
+ * consumes (event_name, user_id, properties, timestamp,
14
+ * idempotency_key?, correlation_id?), AND
15
+ * - the richer canonical Envelope fields consumers may want to read
16
+ * (event_tier, event_source) — we attach these at the top level so they
17
+ * travel alongside the event without collapsing into properties.
18
+ *
19
+ * `createEnvelope` from @gurulu/shared-core is used to validate/normalize the
20
+ * shared envelope defaults where practical (event_type, timestamp,
21
+ * consent_level, etc.), but the ServerEvent-specific keys (user_id) remain at
22
+ * the root.
23
+ *
24
+ * Related docs: PHASE-10-ROADMAP.md §W4.2
25
+ */
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.userCreated = userCreated;
28
+ exports.paymentSucceeded = paymentSucceeded;
29
+ exports.subscriptionStarted = subscriptionStarted;
30
+ exports.orderPlaced = orderPlaced;
31
+ exports.leadCaptured = leadCaptured;
32
+ const shared_core_1 = require("@gurulu/shared-core");
33
+ const SDK_VERSION = 'node@0.1.0';
34
+ function baseEnvelope(eventName, anonymousId, properties) {
35
+ return (0, shared_core_1.createEnvelope)({
36
+ site_id: '', // supplied at transport time by the client
37
+ anonymous_id: anonymousId,
38
+ session_id: '', // server SDK has no browser session
39
+ event_name: eventName,
40
+ event_type: 'server',
41
+ event_tier: 'verified',
42
+ event_source: 'server_sdk',
43
+ consent_level: 'accepted',
44
+ sdk_version: SDK_VERSION,
45
+ timestamp: new Date(Date.now()).toISOString(),
46
+ properties,
47
+ });
48
+ }
49
+ function toBusinessEvent(envelope, userId, extras = {}) {
50
+ return {
51
+ event_name: envelope.event_name,
52
+ user_id: userId,
53
+ properties: envelope.properties,
54
+ timestamp: envelope.timestamp,
55
+ event_type: envelope.event_type,
56
+ event_tier: envelope.event_tier,
57
+ event_source: envelope.event_source,
58
+ ...extras,
59
+ };
60
+ }
61
+ function userCreated(args) {
62
+ const props = {
63
+ ...(args.traits ?? {}),
64
+ };
65
+ if (args.email !== undefined)
66
+ props.email = args.email;
67
+ const env = baseEnvelope('user_created', args.userId, props);
68
+ return toBusinessEvent(env, args.userId);
69
+ }
70
+ function paymentSucceeded(args) {
71
+ const props = {
72
+ ...(args.properties ?? {}),
73
+ amount: args.amount,
74
+ currency: args.currency,
75
+ order_id: args.orderId,
76
+ };
77
+ const env = baseEnvelope('payment_succeeded', args.userId, props);
78
+ return toBusinessEvent(env, args.userId);
79
+ }
80
+ function subscriptionStarted(args) {
81
+ const props = {
82
+ plan: args.plan,
83
+ };
84
+ if (args.amount !== undefined)
85
+ props.amount = args.amount;
86
+ if (args.currency !== undefined)
87
+ props.currency = args.currency;
88
+ const env = baseEnvelope('subscription_started', args.userId, props);
89
+ return toBusinessEvent(env, args.userId);
90
+ }
91
+ function orderPlaced(args) {
92
+ const props = {
93
+ order_id: args.orderId,
94
+ total: args.total,
95
+ currency: args.currency,
96
+ };
97
+ if (args.items !== undefined)
98
+ props.items = args.items;
99
+ const env = baseEnvelope('order_placed', args.userId, props);
100
+ return toBusinessEvent(env, args.userId);
101
+ }
102
+ function leadCaptured(args) {
103
+ const props = {};
104
+ if (args.email !== undefined)
105
+ props.email = args.email;
106
+ if (args.source !== undefined)
107
+ props.source = args.source;
108
+ const env = baseEnvelope('lead_captured', args.anonymousId, props);
109
+ // Lead capture uses anonymous_id as the user_id placeholder until the lead
110
+ // is resolved to a real account; the server route accepts any non-empty
111
+ // user_id string and will look up the canonical profile from it.
112
+ return toBusinessEvent(env, args.anonymousId);
113
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @gurulu/node — server-side SDK client.
3
+ *
4
+ * Responsibilities (Phase 10 W4.2):
5
+ * - Queue + batch server events.
6
+ * - Auto-flush at batchSize boundary and on a periodic timer.
7
+ * - Exponential-backoff retry (200ms, 600ms, 1800ms — 3 attempts total) on
8
+ * 5xx / network errors. No retry on 4xx.
9
+ * - Attach deterministic Idempotency-Key headers so the server can dedupe
10
+ * replayed batches across retries.
11
+ * - Dead-letter callback for batches that exhaust retries or are rejected
12
+ * with a client error.
13
+ * - Flush on process.beforeExit + SIGTERM.
14
+ *
15
+ * Event shapes are pulled from @gurulu/shared-core (W1.2). Only the transport
16
+ * config (`GuruluClientConfig`) is local.
17
+ *
18
+ * Related docs: PHASE-10-ROADMAP.md §W4.2
19
+ */
20
+ import type { ServerEvent } from '@gurulu/shared-core';
21
+ import type { GuruluClientConfig } from './types';
22
+ export interface GuruluClient {
23
+ track(event: ServerEvent): void;
24
+ flush(): Promise<void>;
25
+ shutdown(): Promise<void>;
26
+ /** Current queue length — exposed for tests and observability. */
27
+ readonly queueSize: number;
28
+ }
29
+ /**
30
+ * Deterministic idempotency key for a server event. Hashes the tuple
31
+ * (site_id, event_name, timestamp, anonymous_id/user_id) so replayed events
32
+ * across retries produce the same key and the server can dedupe.
33
+ */
34
+ export declare function createIdempotencyKey(event: ServerEvent, siteId: string): string;
35
+ export declare class Gurulu implements GuruluClient {
36
+ private readonly siteId;
37
+ private readonly apiKey;
38
+ private readonly endpoint;
39
+ private readonly flushInterval;
40
+ private readonly batchSize;
41
+ private readonly timeout;
42
+ private readonly onError?;
43
+ private readonly onDeadLetter?;
44
+ private readonly fetchImpl;
45
+ private readonly debug;
46
+ private queue;
47
+ private flushTimer;
48
+ private inFlight;
49
+ private shutdownHandlers;
50
+ constructor(config: GuruluClientConfig);
51
+ get queueSize(): number;
52
+ /**
53
+ * Enqueue a server event. Auto-flushes synchronously when the queue reaches
54
+ * `batchSize`. Each event is stamped with a timestamp if missing so the
55
+ * idempotency key is stable across retries.
56
+ */
57
+ track(event: ServerEvent): void;
58
+ /**
59
+ * POST all queued events in one batch. On 5xx / network error retries with
60
+ * exponential backoff (200ms, 600ms, 1800ms). On 4xx drops the batch and
61
+ * fires the dead-letter callback. Concurrent flush calls are serialized.
62
+ */
63
+ flush(): Promise<void>;
64
+ private sendBatch;
65
+ /**
66
+ * Stop the periodic flush timer, detach process listeners, and perform a
67
+ * final flush. Safe to call multiple times.
68
+ */
69
+ shutdown(): Promise<void>;
70
+ }
@@ -0,0 +1,255 @@
1
+ "use strict";
2
+ /**
3
+ * @gurulu/node — server-side SDK client.
4
+ *
5
+ * Responsibilities (Phase 10 W4.2):
6
+ * - Queue + batch server events.
7
+ * - Auto-flush at batchSize boundary and on a periodic timer.
8
+ * - Exponential-backoff retry (200ms, 600ms, 1800ms — 3 attempts total) on
9
+ * 5xx / network errors. No retry on 4xx.
10
+ * - Attach deterministic Idempotency-Key headers so the server can dedupe
11
+ * replayed batches across retries.
12
+ * - Dead-letter callback for batches that exhaust retries or are rejected
13
+ * with a client error.
14
+ * - Flush on process.beforeExit + SIGTERM.
15
+ *
16
+ * Event shapes are pulled from @gurulu/shared-core (W1.2). Only the transport
17
+ * config (`GuruluClientConfig`) is local.
18
+ *
19
+ * Related docs: PHASE-10-ROADMAP.md §W4.2
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.Gurulu = void 0;
23
+ exports.createIdempotencyKey = createIdempotencyKey;
24
+ const crypto_1 = require("crypto");
25
+ const DEFAULT_ENDPOINT = 'https://app.gurulu.io/api/ingest/v1/server';
26
+ const DEFAULT_FLUSH_INTERVAL = 5000;
27
+ const DEFAULT_BATCH_SIZE = 50;
28
+ const DEFAULT_TIMEOUT = 10000;
29
+ const RETRY_DELAYS_MS = [200, 600, 1800];
30
+ /**
31
+ * Deterministic idempotency key for a server event. Hashes the tuple
32
+ * (site_id, event_name, timestamp, anonymous_id/user_id) so replayed events
33
+ * across retries produce the same key and the server can dedupe.
34
+ */
35
+ function createIdempotencyKey(event, siteId) {
36
+ const parts = [
37
+ siteId,
38
+ event.event_name ?? '',
39
+ event.timestamp ?? '',
40
+ event.user_id ?? '',
41
+ ];
42
+ return (0, crypto_1.createHash)('sha256').update(parts.join('|')).digest('hex');
43
+ }
44
+ class Gurulu {
45
+ siteId;
46
+ apiKey;
47
+ endpoint;
48
+ flushInterval;
49
+ batchSize;
50
+ timeout;
51
+ onError;
52
+ onDeadLetter;
53
+ fetchImpl;
54
+ debug;
55
+ queue = [];
56
+ flushTimer = null;
57
+ inFlight = null;
58
+ shutdownHandlers = [];
59
+ constructor(config) {
60
+ if (!config.siteId)
61
+ throw new Error('siteId is required');
62
+ if (!config.apiKey)
63
+ throw new Error('apiKey is required');
64
+ this.siteId = config.siteId;
65
+ this.apiKey = config.apiKey;
66
+ this.endpoint = config.endpoint || DEFAULT_ENDPOINT;
67
+ this.flushInterval = config.flushInterval ?? DEFAULT_FLUSH_INTERVAL;
68
+ this.batchSize = config.batchSize ?? DEFAULT_BATCH_SIZE;
69
+ this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
70
+ this.onError = config.onError;
71
+ this.onDeadLetter = config.onDeadLetter;
72
+ this.debug = config.debug ?? false;
73
+ const injected = config.fetchImpl;
74
+ if (injected) {
75
+ this.fetchImpl = injected;
76
+ }
77
+ else {
78
+ const g = globalThis;
79
+ if (typeof g.fetch !== 'function') {
80
+ throw new Error('global fetch is not available; pass fetchImpl in config (Node 18+ required)');
81
+ }
82
+ this.fetchImpl = g.fetch.bind(globalThis);
83
+ }
84
+ if (this.flushInterval > 0) {
85
+ this.flushTimer = setInterval(() => {
86
+ // Fire and forget; errors surface via onError.
87
+ void this.flush();
88
+ }, this.flushInterval);
89
+ // Let the process exit even if the timer is pending.
90
+ const t = this.flushTimer;
91
+ if (typeof t.unref === 'function')
92
+ t.unref();
93
+ }
94
+ if (!config.disableAutoShutdown && typeof process !== 'undefined' && typeof process.on === 'function') {
95
+ const beforeExit = () => {
96
+ void this.flush();
97
+ };
98
+ const onSigterm = () => {
99
+ void this.shutdown();
100
+ };
101
+ process.on('beforeExit', beforeExit);
102
+ process.on('SIGTERM', onSigterm);
103
+ this.shutdownHandlers.push(() => {
104
+ process.off('beforeExit', beforeExit);
105
+ process.off('SIGTERM', onSigterm);
106
+ });
107
+ }
108
+ }
109
+ get queueSize() {
110
+ return this.queue.length;
111
+ }
112
+ /**
113
+ * Enqueue a server event. Auto-flushes synchronously when the queue reaches
114
+ * `batchSize`. Each event is stamped with a timestamp if missing so the
115
+ * idempotency key is stable across retries.
116
+ */
117
+ track(event) {
118
+ if (!event || !event.event_name) {
119
+ if (this.debug)
120
+ console.warn('[gurulu] event_name is required');
121
+ return;
122
+ }
123
+ if (!event.user_id) {
124
+ if (this.debug)
125
+ console.warn('[gurulu] user_id is required for server events');
126
+ return;
127
+ }
128
+ const stamped = {
129
+ ...event,
130
+ timestamp: event.timestamp ?? new Date().toISOString(),
131
+ };
132
+ this.queue.push(stamped);
133
+ if (this.debug) {
134
+ console.log(`[gurulu] queued: ${stamped.event_name} (${this.queue.length}/${this.batchSize})`);
135
+ }
136
+ if (this.queue.length >= this.batchSize) {
137
+ void this.flush();
138
+ }
139
+ }
140
+ /**
141
+ * POST all queued events in one batch. On 5xx / network error retries with
142
+ * exponential backoff (200ms, 600ms, 1800ms). On 4xx drops the batch and
143
+ * fires the dead-letter callback. Concurrent flush calls are serialized.
144
+ */
145
+ async flush() {
146
+ // Serialize concurrent flushes so retries don't interleave.
147
+ if (this.inFlight) {
148
+ await this.inFlight;
149
+ }
150
+ if (this.queue.length === 0)
151
+ return;
152
+ const batch = this.queue.splice(0, this.queue.length);
153
+ const run = this.sendBatch(batch);
154
+ this.inFlight = run.finally(() => {
155
+ this.inFlight = null;
156
+ });
157
+ await this.inFlight;
158
+ }
159
+ async sendBatch(batch) {
160
+ // Idempotency key for the whole batch — hashed over individual event keys.
161
+ const perEventKeys = batch.map((e) => createIdempotencyKey(e, this.siteId));
162
+ const batchKey = (0, crypto_1.createHash)('sha256').update(perEventKeys.join('|')).digest('hex');
163
+ const body = JSON.stringify({
164
+ site_id: this.siteId,
165
+ events: batch,
166
+ });
167
+ const headers = {
168
+ 'Content-Type': 'application/json',
169
+ Authorization: `Bearer ${this.apiKey}`,
170
+ 'Idempotency-Key': batchKey,
171
+ };
172
+ let lastError = null;
173
+ for (let attempt = 0; attempt < RETRY_DELAYS_MS.length; attempt++) {
174
+ try {
175
+ const res = await this.fetchImpl(this.endpoint, {
176
+ method: 'POST',
177
+ headers,
178
+ body,
179
+ });
180
+ if (res.ok) {
181
+ if (this.debug)
182
+ console.log(`[gurulu] flushed ${batch.length} events`);
183
+ return;
184
+ }
185
+ if (res.status >= 400 && res.status < 500) {
186
+ // Client error — don't retry. Drop + dead-letter.
187
+ const text = await safeText(res);
188
+ const err = new Error(`client_error ${res.status}: ${text}`);
189
+ if (this.debug)
190
+ console.error(`[gurulu] ${err.message}`);
191
+ this.onError?.(err);
192
+ this.onDeadLetter?.(batch, {
193
+ kind: 'client_error',
194
+ status: res.status,
195
+ message: text,
196
+ });
197
+ return;
198
+ }
199
+ lastError = new Error(`server_error ${res.status}`);
200
+ this.onError?.(lastError);
201
+ }
202
+ catch (err) {
203
+ lastError = err instanceof Error ? err : new Error(String(err));
204
+ this.onError?.(lastError);
205
+ }
206
+ // Not the last attempt — sleep with the fixed exponential schedule.
207
+ if (attempt < RETRY_DELAYS_MS.length - 1) {
208
+ await sleep(RETRY_DELAYS_MS[attempt + 1 - 1]);
209
+ // ^ Index arithmetic: attempt=0 -> use RETRY_DELAYS_MS[0]=200, etc.
210
+ }
211
+ }
212
+ // All 3 attempts failed.
213
+ if (this.debug) {
214
+ console.error(`[gurulu] dropping batch of ${batch.length} after retries: ${lastError?.message}`);
215
+ }
216
+ if (this.onDeadLetter) {
217
+ this.onDeadLetter(batch, {
218
+ kind: lastError?.message.startsWith('server_error') ? 'server_error' : 'network_error',
219
+ message: lastError?.message ?? 'unknown error',
220
+ });
221
+ }
222
+ else if (this.debug) {
223
+ console.error('[gurulu] no dead-letter callback configured; events lost');
224
+ }
225
+ }
226
+ /**
227
+ * Stop the periodic flush timer, detach process listeners, and perform a
228
+ * final flush. Safe to call multiple times.
229
+ */
230
+ async shutdown() {
231
+ if (this.flushTimer) {
232
+ clearInterval(this.flushTimer);
233
+ this.flushTimer = null;
234
+ }
235
+ for (const detach of this.shutdownHandlers.splice(0)) {
236
+ try {
237
+ detach();
238
+ }
239
+ catch { /* best-effort */ }
240
+ }
241
+ await this.flush();
242
+ }
243
+ }
244
+ exports.Gurulu = Gurulu;
245
+ async function safeText(res) {
246
+ try {
247
+ return await res.text();
248
+ }
249
+ catch {
250
+ return '';
251
+ }
252
+ }
253
+ function sleep(ms) {
254
+ return new Promise((resolve) => setTimeout(resolve, ms));
255
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @gurulu/node — public entry point.
3
+ *
4
+ * Phase 10 W4.2: server SDK hardening adds batch+retry, deterministic
5
+ * idempotency keys, dead-letter callbacks, and business event emitters.
6
+ */
7
+ export { Gurulu, createIdempotencyKey } from './client';
8
+ export type { GuruluClient } from './client';
9
+ export type { GuruluClientConfig, DeadLetterCallback, ErrorCallback, } from './types';
10
+ export type { Envelope, ServerEvent, EventTier, EventSource, ConsentLevel, } from '@gurulu/shared-core';
11
+ export { userCreated, paymentSucceeded, subscriptionStarted, orderPlaced, leadCaptured, } from './business-events';
12
+ export type { BusinessEvent, UserCreatedArgs, PaymentSucceededArgs, SubscriptionStartedArgs, OrderPlacedArgs, LeadCapturedArgs, } from './business-events';
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ /**
3
+ * @gurulu/node — public entry point.
4
+ *
5
+ * Phase 10 W4.2: server SDK hardening adds batch+retry, deterministic
6
+ * idempotency keys, dead-letter callbacks, and business event emitters.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.leadCaptured = exports.orderPlaced = exports.subscriptionStarted = exports.paymentSucceeded = exports.userCreated = exports.createIdempotencyKey = exports.Gurulu = void 0;
10
+ var client_1 = require("./client");
11
+ Object.defineProperty(exports, "Gurulu", { enumerable: true, get: function () { return client_1.Gurulu; } });
12
+ Object.defineProperty(exports, "createIdempotencyKey", { enumerable: true, get: function () { return client_1.createIdempotencyKey; } });
13
+ // Business event emitters (W4.2).
14
+ var business_events_1 = require("./business-events");
15
+ Object.defineProperty(exports, "userCreated", { enumerable: true, get: function () { return business_events_1.userCreated; } });
16
+ Object.defineProperty(exports, "paymentSucceeded", { enumerable: true, get: function () { return business_events_1.paymentSucceeded; } });
17
+ Object.defineProperty(exports, "subscriptionStarted", { enumerable: true, get: function () { return business_events_1.subscriptionStarted; } });
18
+ Object.defineProperty(exports, "orderPlaced", { enumerable: true, get: function () { return business_events_1.orderPlaced; } });
19
+ Object.defineProperty(exports, "leadCaptured", { enumerable: true, get: function () { return business_events_1.leadCaptured; } });
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Node SDK public types.
3
+ *
4
+ * Canonical event shapes (Envelope, ServerEvent, EventTier, EventSource,
5
+ * ConsentLevel, createEnvelope, normalizeLegacy, parseEnvelope) are re-exported
6
+ * from @gurulu/shared-core — single source of truth per W1.2.
7
+ *
8
+ * The only node-sdk-specific addition is the transport config
9
+ * (GuruluClientConfig) which wraps the shared ServerEvent with the knobs
10
+ * needed to actually ship events to the ingest endpoint.
11
+ *
12
+ * Related docs: PHASE-10-ROADMAP.md §W1.2, §W4.2
13
+ */
14
+ import type { ServerEvent } from '@gurulu/shared-core';
15
+ export * from '@gurulu/shared-core';
16
+ /**
17
+ * Dead-letter callback fired when a batch is permanently dropped (either
18
+ * because all retries failed against a 5xx/network error, or because the
19
+ * server replied 4xx).
20
+ */
21
+ export type DeadLetterCallback = (batch: ServerEvent[], reason: {
22
+ kind: 'client_error' | 'network_error' | 'server_error';
23
+ status?: number;
24
+ message: string;
25
+ }) => void;
26
+ export type ErrorCallback = (err: Error) => void;
27
+ export interface GuruluClientConfig {
28
+ siteId: string;
29
+ apiKey: string;
30
+ endpoint?: string;
31
+ /** Periodic flush interval in ms. Default 5000. */
32
+ flushInterval?: number;
33
+ /** Max events per batch. Auto-flushes when queue reaches this size. Default 50. */
34
+ batchSize?: number;
35
+ /** Per-request HTTP timeout in ms. Default 10000. */
36
+ timeout?: number;
37
+ /** Fired on every transport error (retryable or not). */
38
+ onError?: ErrorCallback;
39
+ /** Fired when a batch is permanently dropped. */
40
+ onDeadLetter?: DeadLetterCallback;
41
+ /** Inject fetch for testing (defaults to globalThis.fetch). */
42
+ fetchImpl?: typeof fetch;
43
+ /** Suppress automatic process.on('beforeExit'/'SIGTERM') hooks. */
44
+ disableAutoShutdown?: boolean;
45
+ debug?: boolean;
46
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ /**
3
+ * Node SDK public types.
4
+ *
5
+ * Canonical event shapes (Envelope, ServerEvent, EventTier, EventSource,
6
+ * ConsentLevel, createEnvelope, normalizeLegacy, parseEnvelope) are re-exported
7
+ * from @gurulu/shared-core — single source of truth per W1.2.
8
+ *
9
+ * The only node-sdk-specific addition is the transport config
10
+ * (GuruluClientConfig) which wraps the shared ServerEvent with the knobs
11
+ * needed to actually ship events to the ingest endpoint.
12
+ *
13
+ * Related docs: PHASE-10-ROADMAP.md §W1.2, §W4.2
14
+ */
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
27
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
28
+ };
29
+ Object.defineProperty(exports, "__esModule", { value: true });
30
+ __exportStar(require("@gurulu/shared-core"), exports);