sdk-garden-js 0.1.0 → 0.1.1

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 CHANGED
@@ -32,6 +32,8 @@ class PaymentsSDK {
32
32
  export const trackedProcess = SdkGarden.trackMethod('processPayment', processPayment);
33
33
  ```
34
34
 
35
+ Both forms can run before `init()`, for example at module level: calls pass straight through until `init()` runs and are recorded from then on.
36
+
35
37
  **CommonJS (Node.js / require):**
36
38
 
37
39
  ```javascript
@@ -44,6 +46,8 @@ SdkGarden.init({
44
46
  });
45
47
  ```
46
48
 
49
+ Requires Node.js 18+ (for the global `fetch`) or any modern browser.
50
+
47
51
  ## Configuration
48
52
 
49
53
  | Parameter | Type | Default | Description |
@@ -51,11 +55,11 @@ SdkGarden.init({
51
55
  | `apiKey` | `string` | _(required)_ | Your SDK Garden API key (starts with `sgk_`) |
52
56
  | `sdkName` | `string` | _(required)_ | The name of your SDK (e.g. `"my-payments-sdk"`) |
53
57
  | `sdkVersion` | `string` | _(required)_ | The version of your SDK (e.g. `"1.2.3"`) |
54
- | `endpoint` | `string` | `"https://ingest.sdkgarden.dev/track"` | Override the ingest endpoint |
58
+ | `endpoint` | `string` | `"https://ingest.sdkgarden.com/track"` | Override the ingest endpoint (for self-hosted SDK Garden, `https://<your-host>/track`) |
55
59
  | `debug` | `boolean` | `false` | Enable debug logging to console |
56
60
  | `disabled` | `boolean` | `false` | When `true`, all methods become no-ops |
57
- | `batchSize` | `number` | `10` | Maximum events per flush batch |
58
- | `flushInterval` | `number` | `5000` | Flush interval in milliseconds |
61
+ | `batchSize` | `number` | `10` | Maximum events per request, from 1 to 100; a full batch is sent right away |
62
+ | `flushInterval` | `number` | `5000` | Milliseconds between sends of whatever is queued, at least 100 |
59
63
  | `startedAt` | `number` | module load time | `Date.now()` when your SDK began initializing; reported as `time_to_init_ms` for the dashboard's init timing |
60
64
 
61
65
  ## What gets sent
@@ -67,39 +71,45 @@ Each event carries your SDK's name and version, an event type, a random session
67
71
  - **`sdk_error`**: error type and message (email addresses are redacted) plus the method and duration.
68
72
  - **In browsers**, every event also carries the page's origin (for example `https://shop.example.com`), never the full URL, path, query string or referrer.
69
73
 
70
- Never sent: argument or return values, request or response bodies, cookies, stack traces, or user identifiers. Nothing is sent when `disabled: true`.
74
+ Never sent: argument or return values, request or response bodies, cookies or stack traces. Error messages go out as they are apart from email addresses, so anything your SDK puts in an error message, such as a hostname, a URL or an ID, goes with it. Nothing is sent when `disabled: true`.
71
75
 
72
76
  ### Telling your users
73
77
 
74
78
  Copy this into your SDK's README or privacy docs so your users know what's collected (edit the names to match):
75
79
 
76
- > **Telemetry.** This SDK reports anonymous usage data to its maintainers through [SDK Garden](https://github.com/tjb/SDKGarden): the SDK version, runtime version, which SDK methods are called, how long they take, and error types. It never sends your data, request contents or personal information. To opt out, set `YOUR_SDK_TELEMETRY_DISABLED=1` *(wire this environment variable to `disabled: true` in your SDK)*.
77
-
78
- ## Silent Failure Guarantee
79
-
80
- All network errors are swallowed silently. SDK Garden will never throw or reject in your caller's context.
80
+ > **Telemetry.** This SDK reports anonymous usage data to its maintainers through [SDK Garden](https://github.com/tjb/SDKGarden): the SDK version, runtime version, which SDK methods are called, how long they take, and the type and message of the errors they raise (with email addresses removed). It never sends arguments, return values or request contents. To opt out, set `YOUR_SDK_TELEMETRY_DISABLED=1` *(wire this environment variable to `disabled: true` in your SDK)*.
81
81
 
82
- Pass `disabled: true` in test environments to suppress all network calls.
82
+ ## Sending and shutting down
83
83
 
84
- ## Known Limitations
84
+ Routine sends go out one request at a time: every `flushInterval` milliseconds, or as soon as `batchSize` events are queued. At most 1,000 events wait to be sent; if the endpoint cannot keep up, newer events are dropped. Events are also sent:
85
85
 
86
- ### Async fire-and-forget calls (wrapper form only)
86
+ - **When a Node.js process runs out of work.** The client never keeps a process alive on its own. When your program finishes, the events still queued are sent before it exits, including any your own `beforeExit` handler tracks. In Node.js a request times out after 2 seconds, so an endpoint that does not answer delays exit by about 2 seconds (about 4 if a request was already running) but never keeps the process running. Only a DNS server that never answers can hold it longer, up to the system's DNS timeout.
87
+ - **When a page is hidden or unloaded**, in browsers, using `keepalive` so the request can outlive the page.
87
88
 
88
- When using the **wrapper form**, `trackMethod` records `method_success` only when the caller `await`s or chains `.then()` on the returned promise. If the promise is discarded, `method_called` fires but `method_success` is never enqueued.
89
+ Two methods let you control this yourself. Both return promises that never reject:
89
90
 
90
- **Affected pattern:**
91
91
  ```typescript
92
- // method_called fires, but method_success is NEVER recorded:
93
- SdkGarden.trackMethod('notify', notify)(...args); // promise discarded
94
- ```
92
+ // Send everything queued now and wait for it (at most 2 s by default), e.g. at the end of a serverless handler.
93
+ await SdkGarden.flush();
95
94
 
96
- **Not affected:**
97
- ```typescript
98
- // Both events recorded correctly:
99
- await SdkGarden.trackMethod('notify', notify)(...args);
95
+ // Send everything queued, then stop the client: no timer, no listeners, and tracking calls do nothing
96
+ // until init() runs again. Requests still running after 2 s (by default) are abandoned.
97
+ await SdkGarden.shutdown();
100
98
  ```
101
99
 
102
- The **decorator form** does not have this limitation — class method decorators always capture the returned promise correctly regardless of how the caller uses it. Synchronous functions are not affected in either form.
100
+ The automatic send does not run when the process ends through `process.exit()` or a signal such as Ctrl-C. If your SDK has a `close()` method or your users' code exits explicitly, `await SdkGarden.shutdown()` first.
101
+
102
+ Serverless functions and edge workers can be frozen as soon as a handler returns, so `await SdkGarden.flush()` before returning (in Cloudflare Workers, pass it to `ctx.waitUntil()`).
103
+
104
+ If a `beforeExit` handler of yours starts more work, such as the next job in a queue, `await SdkGarden.flush()` at the end of each job. While a process exits, the client sends only three times from `beforeExit`, so that a handler that tracks or flushes every time cannot keep the process alive; a `flush()` anywhere else always sends.
105
+
106
+ ## Silent Failure Guarantee
107
+
108
+ All network errors are swallowed silently. SDK Garden will never throw or reject in your caller's context (not even `init()`), and `trackMethod` never changes what your function returns or throws. Wrapped functions always return exactly what the original returned. A native promise, also one from another realm such as a `vm` context, is watched until it settles (so a rejection nothing else handles is no longer reported as unhandled); Promise subclasses, such as an API client's response promise, and other thenables, such as lazy query builders, are never touched.
109
+
110
+ `sdk-garden-js` is one client per process: if two SDKs in the same app share an installed copy, the last `init()` wins for both. Bundle it into your SDK's build to give your SDK its own copy.
111
+
112
+ Pass `disabled: true` in test environments to suppress all network calls.
103
113
 
104
114
  ## License
105
115
 
package/dist/index.d.mts CHANGED
@@ -7,7 +7,19 @@
7
7
  * Security:
8
8
  * - API key is never logged raw (see _sanitizeConfig).
9
9
  * - Error messages are PII-scrubbed (email addresses redacted) before enqueueing.
10
- * - All network errors in _flush() are swallowed silently — never propagated to callers.
10
+ * - All network errors are swallowed silently — never propagated to callers.
11
+ *
12
+ * Lifecycle:
13
+ * - Routine sends go out one request at a time and at most MAX_QUEUE_SIZE
14
+ * events wait, so a burst of events cannot flood the host's network or the
15
+ * endpoint. flush() and the sends at exit or page hide send up to
16
+ * MAX_DRAIN_REQUESTS requests at once; shutdown() sends everything queued.
17
+ * - Timers are unref'd, so the client never keeps a Node process alive.
18
+ * - Queued events are sent when a Node process runs out of work (beforeExit)
19
+ * and when a page is hidden or unloaded (visibilitychange / pagehide).
20
+ * While a Node process exits, sending is limited so that it still ends,
21
+ * until the process shows that it carried on instead (see _checkExit).
22
+ * - No method throws into the host, and flush() and shutdown() never reject.
11
23
  */
12
24
  /** Configuration for SdkGarden.init(). */
13
25
  interface SdkGardenConfig {
@@ -17,15 +29,15 @@ interface SdkGardenConfig {
17
29
  sdkName: string;
18
30
  /** The version of your SDK (e.g. "1.2.3"). */
19
31
  sdkVersion: string;
20
- /** Override the default ingest endpoint. Default: https://ingest.sdkgarden.dev/track */
32
+ /** Override the default ingest endpoint. Default: https://ingest.sdkgarden.com/track */
21
33
  endpoint?: string;
22
34
  /** Enable debug logging to console. Default: false */
23
35
  debug?: boolean;
24
36
  /** When true, all methods become no-ops — no events, no network calls. Default: false */
25
37
  disabled?: boolean;
26
- /** Maximum events per flush batch. Default: 10 */
38
+ /** Maximum events per request, from 1 to 100; a full batch is sent right away. Default: 10 */
27
39
  batchSize?: number;
28
- /** Flush interval in milliseconds. Default: 5000 */
40
+ /** Milliseconds between sends of whatever is queued, at least 100. Default: 5000 */
29
41
  flushInterval?: number;
30
42
  /**
31
43
  * When your SDK started initializing, as a `Date.now()` timestamp. Used for
@@ -36,7 +48,8 @@ interface SdkGardenConfig {
36
48
  startedAt?: number;
37
49
  }
38
50
  /**
39
- * Stage 3 class method decorator returned by SdkGarden.trackMethod(name).
51
+ * Class method decorator returned by SdkGarden.trackMethod(name), for Stage 3
52
+ * decorators and for TypeScript's experimentalDecorators.
40
53
  *
41
54
  * @example
42
55
  * class MySDK {
@@ -44,17 +57,10 @@ interface SdkGardenConfig {
44
57
  * processPayment(amount: number): Receipt { ... }
45
58
  * }
46
59
  */
47
- type SdkMethodDecorator = <This, Args extends unknown[], Return>(fn: (this: This, ...args: Args) => Return, context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>) => (this: This, ...args: Args) => Return;
48
- /**
49
- * Fire-and-forget flush to the ingest endpoint.
50
- * Ported from sdk/sdk-garden.js lines 232–270, simplified per plan:
51
- * - No retry on 429 (v1)
52
- * - No re-queue on failure (events are discarded)
53
- * - Silent failure: catch() swallows all errors
54
- *
55
- * NOTE: _flush() is also exposed on the SdkGarden object for test access.
56
- */
57
- declare function _flush(): void;
60
+ interface SdkMethodDecorator {
61
+ <This, Args extends unknown[], Return>(fn: (this: This, ...args: Args) => Return, context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>): (this: This, ...args: Args) => Return;
62
+ <T>(target: object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>): TypedPropertyDescriptor<T>;
63
+ }
58
64
  /**
59
65
  * Typed interface for the SdkGarden singleton.
60
66
  * Declared separately so that trackMethod can have proper overload signatures —
@@ -66,9 +72,25 @@ interface SdkGardenObject {
66
72
  /** Decorator form — use as @SdkGarden.trackMethod('name') on a class method. */
67
73
  trackMethod(methodName: string): SdkMethodDecorator;
68
74
  /** Wrapper form — pass an existing function, receive the instrumented version. */
69
- trackMethod<T extends (...args: unknown[]) => unknown>(methodName: string, fn: T): T;
75
+ trackMethod<T extends (...args: any[]) => any>(methodName: string, fn: T): T;
70
76
  trackError(error: Error, context?: Record<string, unknown>): void;
71
- _flush: typeof _flush;
77
+ /**
78
+ * Send every queued event now. Resolves once they (and any requests already
79
+ * in flight) have finished, or after `timeoutMs` (default 2000), whichever
80
+ * comes first. Never rejects.
81
+ */
82
+ flush(timeoutMs?: number): Promise<void>;
83
+ /**
84
+ * Send every queued event, then stop: no timer, no listeners, and tracking
85
+ * calls become no-ops until init() is called again. Resolves once the last
86
+ * requests finish; requests still running after `timeoutMs` (default 2000)
87
+ * are aborted so a Node process can exit. `0` stops at once without
88
+ * sending. Never rejects.
89
+ */
90
+ shutdown(timeoutMs?: number): Promise<void>;
91
+ /** @deprecated Use flush(), which returns a promise. Kept so 0.1.0 code keeps working. */
92
+ _flush(): void;
93
+ /** @internal For unit tests: drop all state without sending anything. */
72
94
  _reset(): void;
73
95
  }
74
96
  /**
@@ -84,8 +106,11 @@ interface SdkGardenObject {
84
106
  * processPayment(amount: number): Receipt { ... }
85
107
  * }
86
108
  *
87
- * // Wrapper form (standalone functions):
88
- * const tracked = SdkGarden.trackMethod('processPayment', processPayment);
109
+ * // Wrapper form (standalone functions), safe to run before init():
110
+ * export const tracked = SdkGarden.trackMethod('processPayment', processPayment);
111
+ *
112
+ * // Before your SDK's host process exits (for example in your close()):
113
+ * await SdkGarden.shutdown();
89
114
  */
90
115
  declare const SdkGarden: SdkGardenObject;
91
116
 
package/dist/index.d.ts CHANGED
@@ -7,7 +7,19 @@
7
7
  * Security:
8
8
  * - API key is never logged raw (see _sanitizeConfig).
9
9
  * - Error messages are PII-scrubbed (email addresses redacted) before enqueueing.
10
- * - All network errors in _flush() are swallowed silently — never propagated to callers.
10
+ * - All network errors are swallowed silently — never propagated to callers.
11
+ *
12
+ * Lifecycle:
13
+ * - Routine sends go out one request at a time and at most MAX_QUEUE_SIZE
14
+ * events wait, so a burst of events cannot flood the host's network or the
15
+ * endpoint. flush() and the sends at exit or page hide send up to
16
+ * MAX_DRAIN_REQUESTS requests at once; shutdown() sends everything queued.
17
+ * - Timers are unref'd, so the client never keeps a Node process alive.
18
+ * - Queued events are sent when a Node process runs out of work (beforeExit)
19
+ * and when a page is hidden or unloaded (visibilitychange / pagehide).
20
+ * While a Node process exits, sending is limited so that it still ends,
21
+ * until the process shows that it carried on instead (see _checkExit).
22
+ * - No method throws into the host, and flush() and shutdown() never reject.
11
23
  */
12
24
  /** Configuration for SdkGarden.init(). */
13
25
  interface SdkGardenConfig {
@@ -17,15 +29,15 @@ interface SdkGardenConfig {
17
29
  sdkName: string;
18
30
  /** The version of your SDK (e.g. "1.2.3"). */
19
31
  sdkVersion: string;
20
- /** Override the default ingest endpoint. Default: https://ingest.sdkgarden.dev/track */
32
+ /** Override the default ingest endpoint. Default: https://ingest.sdkgarden.com/track */
21
33
  endpoint?: string;
22
34
  /** Enable debug logging to console. Default: false */
23
35
  debug?: boolean;
24
36
  /** When true, all methods become no-ops — no events, no network calls. Default: false */
25
37
  disabled?: boolean;
26
- /** Maximum events per flush batch. Default: 10 */
38
+ /** Maximum events per request, from 1 to 100; a full batch is sent right away. Default: 10 */
27
39
  batchSize?: number;
28
- /** Flush interval in milliseconds. Default: 5000 */
40
+ /** Milliseconds between sends of whatever is queued, at least 100. Default: 5000 */
29
41
  flushInterval?: number;
30
42
  /**
31
43
  * When your SDK started initializing, as a `Date.now()` timestamp. Used for
@@ -36,7 +48,8 @@ interface SdkGardenConfig {
36
48
  startedAt?: number;
37
49
  }
38
50
  /**
39
- * Stage 3 class method decorator returned by SdkGarden.trackMethod(name).
51
+ * Class method decorator returned by SdkGarden.trackMethod(name), for Stage 3
52
+ * decorators and for TypeScript's experimentalDecorators.
40
53
  *
41
54
  * @example
42
55
  * class MySDK {
@@ -44,17 +57,10 @@ interface SdkGardenConfig {
44
57
  * processPayment(amount: number): Receipt { ... }
45
58
  * }
46
59
  */
47
- type SdkMethodDecorator = <This, Args extends unknown[], Return>(fn: (this: This, ...args: Args) => Return, context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>) => (this: This, ...args: Args) => Return;
48
- /**
49
- * Fire-and-forget flush to the ingest endpoint.
50
- * Ported from sdk/sdk-garden.js lines 232–270, simplified per plan:
51
- * - No retry on 429 (v1)
52
- * - No re-queue on failure (events are discarded)
53
- * - Silent failure: catch() swallows all errors
54
- *
55
- * NOTE: _flush() is also exposed on the SdkGarden object for test access.
56
- */
57
- declare function _flush(): void;
60
+ interface SdkMethodDecorator {
61
+ <This, Args extends unknown[], Return>(fn: (this: This, ...args: Args) => Return, context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>): (this: This, ...args: Args) => Return;
62
+ <T>(target: object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>): TypedPropertyDescriptor<T>;
63
+ }
58
64
  /**
59
65
  * Typed interface for the SdkGarden singleton.
60
66
  * Declared separately so that trackMethod can have proper overload signatures —
@@ -66,9 +72,25 @@ interface SdkGardenObject {
66
72
  /** Decorator form — use as @SdkGarden.trackMethod('name') on a class method. */
67
73
  trackMethod(methodName: string): SdkMethodDecorator;
68
74
  /** Wrapper form — pass an existing function, receive the instrumented version. */
69
- trackMethod<T extends (...args: unknown[]) => unknown>(methodName: string, fn: T): T;
75
+ trackMethod<T extends (...args: any[]) => any>(methodName: string, fn: T): T;
70
76
  trackError(error: Error, context?: Record<string, unknown>): void;
71
- _flush: typeof _flush;
77
+ /**
78
+ * Send every queued event now. Resolves once they (and any requests already
79
+ * in flight) have finished, or after `timeoutMs` (default 2000), whichever
80
+ * comes first. Never rejects.
81
+ */
82
+ flush(timeoutMs?: number): Promise<void>;
83
+ /**
84
+ * Send every queued event, then stop: no timer, no listeners, and tracking
85
+ * calls become no-ops until init() is called again. Resolves once the last
86
+ * requests finish; requests still running after `timeoutMs` (default 2000)
87
+ * are aborted so a Node process can exit. `0` stops at once without
88
+ * sending. Never rejects.
89
+ */
90
+ shutdown(timeoutMs?: number): Promise<void>;
91
+ /** @deprecated Use flush(), which returns a promise. Kept so 0.1.0 code keeps working. */
92
+ _flush(): void;
93
+ /** @internal For unit tests: drop all state without sending anything. */
72
94
  _reset(): void;
73
95
  }
74
96
  /**
@@ -84,8 +106,11 @@ interface SdkGardenObject {
84
106
  * processPayment(amount: number): Receipt { ... }
85
107
  * }
86
108
  *
87
- * // Wrapper form (standalone functions):
88
- * const tracked = SdkGarden.trackMethod('processPayment', processPayment);
109
+ * // Wrapper form (standalone functions), safe to run before init():
110
+ * export const tracked = SdkGarden.trackMethod('processPayment', processPayment);
111
+ *
112
+ * // Before your SDK's host process exits (for example in your close()):
113
+ * await SdkGarden.shutdown();
89
114
  */
90
115
  declare const SdkGarden: SdkGardenObject;
91
116