sdk-garden-js 0.1.1 → 0.2.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 CHANGED
@@ -61,14 +61,35 @@ Requires Node.js 18+ (for the global `fetch`) or any modern browser.
61
61
  | `batchSize` | `number` | `10` | Maximum events per request, from 1 to 100; a full batch is sent right away |
62
62
  | `flushInterval` | `number` | `5000` | Milliseconds between sends of whatever is queued, at least 100 |
63
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 |
64
+ | `methods` | `string[]` | `[]` | Public methods of your SDK that are not wrapped with `trackMethod()`, so the dashboard's API Surface panel can show them when nobody calls them |
65
+ | `customer` | `{ id: string; name?: string }` | _(none)_ | The app or customer account this session runs for (see [Customers](#customers)) |
66
+
67
+ ### API surface
68
+
69
+ Every name passed to `trackMethod()` is declared as part of your SDK's API surface, once per session (in the `sdk_initialized` event, or in one `api_surface` event for methods wrapped after `init()`). The dashboard lists those methods next to the ones people call, so the methods nobody uses show up with zero calls. Add methods you don't wrap with the `methods` option. Up to 500 names are declared.
70
+
71
+ ### Customers
72
+
73
+ Tell SDK Garden which of your customers a session belongs to, and the dashboard's Customers page shows who is on an old version, who still calls a deprecated method and who hits errors, so you know who to contact.
74
+
75
+ ```typescript
76
+ // When your SDK knows which account it runs for, for example after resolving its API key:
77
+ SdkGarden.setCustomer({ id: account.id, name: account.displayName });
78
+
79
+ // Or at init, if you already know it:
80
+ SdkGarden.init({ apiKey: 'sgk_...', sdkName: 'my-sdk', sdkVersion: '1.0.0', customer: { id: 'acct_123' } });
81
+ ```
82
+
83
+ Use an ID from your own system, such as an account, project or app ID, and optionally a display name. Nothing is attributed unless you call this. Don't pass email addresses or other personal data: an ID that contains an email address is ignored, and so is a name that contains one. IDs are cut to 128 characters and names to 200. `setCustomer(null)` stops attributing; the customer lasts until the next `init()` or `shutdown()`.
64
84
 
65
85
  ## What gets sent
66
86
 
67
87
  Each event carries your SDK's name and version, an event type, a random session ID created when `init()` runs, and a timestamp. Depending on the event:
68
88
 
69
- - **`sdk_initialized`**: runtime (`node` + Node version, or `browser` + screen/viewport size, pixel ratio, timezone and language) and `time_to_init_ms`.
89
+ - **`sdk_initialized`**: runtime (`node` + Node version, or `browser` + screen/viewport size, pixel ratio, timezone and language), `time_to_init_ms`, and the names of your SDK's methods (see [API surface](#api-surface)).
70
90
  - **`method_called` / `method_success`**: method name, duration, sync/async, and the *number* of arguments.
71
91
  - **`sdk_error`**: error type and message (email addresses are redacted) plus the method and duration.
92
+ - **If you set a customer**, every event carries its ID and name (see [Customers](#customers)).
72
93
  - **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.
73
94
 
74
95
  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`.
package/dist/index.d.mts CHANGED
@@ -46,6 +46,27 @@ interface SdkGardenConfig {
46
46
  * Default: when sdk-garden-js was first loaded.
47
47
  */
48
48
  startedAt?: number;
49
+ /**
50
+ * Names of your SDK's public methods that are not wrapped with
51
+ * trackMethod(). Sent once per session with every name passed to
52
+ * trackMethod(), so the dashboard can list the methods nobody calls.
53
+ */
54
+ methods?: string[];
55
+ /**
56
+ * The app or customer account this session runs for, so the dashboard can
57
+ * show which customers are on old versions, call deprecated methods or hit
58
+ * errors. Use an ID from your own system (an account or project ID), never
59
+ * an email address or other personal data. Can also be set later with
60
+ * setCustomer(). Default: none.
61
+ */
62
+ customer?: SdkGardenCustomer;
63
+ }
64
+ /** The app or customer account a session is attributed to (see setCustomer()). */
65
+ interface SdkGardenCustomer {
66
+ /** An opaque ID from your system, up to 128 characters, e.g. "acct_123". */
67
+ id: string;
68
+ /** A display name for the dashboard, up to 200 characters, e.g. "Acme Corp". */
69
+ name?: string;
49
70
  }
50
71
  /**
51
72
  * Class method decorator returned by SdkGarden.trackMethod(name), for Stage 3
@@ -74,6 +95,11 @@ interface SdkGardenObject {
74
95
  /** Wrapper form — pass an existing function, receive the instrumented version. */
75
96
  trackMethod<T extends (...args: any[]) => any>(methodName: string, fn: T): T;
76
97
  trackError(error: Error, context?: Record<string, unknown>): void;
98
+ /**
99
+ * Attribute this session's events from now on to a customer, or pass null
100
+ * to stop. No-op before init().
101
+ */
102
+ setCustomer(customer: SdkGardenCustomer | null): void;
77
103
  /**
78
104
  * Send every queued event now. Resolves once they (and any requests already
79
105
  * in flight) have finished, or after `timeoutMs` (default 2000), whichever
@@ -114,4 +140,4 @@ interface SdkGardenObject {
114
140
  */
115
141
  declare const SdkGarden: SdkGardenObject;
116
142
 
117
- export { type SdkGardenConfig, type SdkMethodDecorator, SdkGarden as default };
143
+ export { type SdkGardenConfig, type SdkGardenCustomer, type SdkMethodDecorator, SdkGarden as default };
package/dist/index.d.ts CHANGED
@@ -46,6 +46,27 @@ interface SdkGardenConfig {
46
46
  * Default: when sdk-garden-js was first loaded.
47
47
  */
48
48
  startedAt?: number;
49
+ /**
50
+ * Names of your SDK's public methods that are not wrapped with
51
+ * trackMethod(). Sent once per session with every name passed to
52
+ * trackMethod(), so the dashboard can list the methods nobody calls.
53
+ */
54
+ methods?: string[];
55
+ /**
56
+ * The app or customer account this session runs for, so the dashboard can
57
+ * show which customers are on old versions, call deprecated methods or hit
58
+ * errors. Use an ID from your own system (an account or project ID), never
59
+ * an email address or other personal data. Can also be set later with
60
+ * setCustomer(). Default: none.
61
+ */
62
+ customer?: SdkGardenCustomer;
63
+ }
64
+ /** The app or customer account a session is attributed to (see setCustomer()). */
65
+ interface SdkGardenCustomer {
66
+ /** An opaque ID from your system, up to 128 characters, e.g. "acct_123". */
67
+ id: string;
68
+ /** A display name for the dashboard, up to 200 characters, e.g. "Acme Corp". */
69
+ name?: string;
49
70
  }
50
71
  /**
51
72
  * Class method decorator returned by SdkGarden.trackMethod(name), for Stage 3
@@ -74,6 +95,11 @@ interface SdkGardenObject {
74
95
  /** Wrapper form — pass an existing function, receive the instrumented version. */
75
96
  trackMethod<T extends (...args: any[]) => any>(methodName: string, fn: T): T;
76
97
  trackError(error: Error, context?: Record<string, unknown>): void;
98
+ /**
99
+ * Attribute this session's events from now on to a customer, or pass null
100
+ * to stop. No-op before init().
101
+ */
102
+ setCustomer(customer: SdkGardenCustomer | null): void;
77
103
  /**
78
104
  * Send every queued event now. Resolves once they (and any requests already
79
105
  * in flight) have finished, or after `timeoutMs` (default 2000), whichever
@@ -114,4 +140,4 @@ interface SdkGardenObject {
114
140
  */
115
141
  declare const SdkGarden: SdkGardenObject;
116
142
 
117
- export { type SdkGardenConfig, type SdkMethodDecorator, SdkGarden as default };
143
+ export { type SdkGardenConfig, type SdkGardenCustomer, type SdkMethodDecorator, SdkGarden as default };
package/dist/index.js CHANGED
@@ -26,6 +26,7 @@ module.exports = __toCommonJS(index_exports);
26
26
  var _config = null;
27
27
  var _queue = [];
28
28
  var _sessionId = "";
29
+ var _customer = null;
29
30
  var _flushTimer = null;
30
31
  var _exiting = false;
31
32
  var _exitDrains = 0;
@@ -37,6 +38,9 @@ var _exitTurnCount = 0;
37
38
  var _exitProbe = null;
38
39
  var _exitProbeDueAt = Infinity;
39
40
  var _pending = /* @__PURE__ */ new Set();
41
+ var _registered = /* @__PURE__ */ new Set();
42
+ var _undeclared = [];
43
+ var _declareScheduled = false;
40
44
  var DEFAULT_ENDPOINT = "https://ingest.sdkgarden.com/track";
41
45
  var MAX_BATCH_SIZE = 100;
42
46
  var MAX_QUEUE_SIZE = 1e3;
@@ -46,6 +50,8 @@ var MAX_REQUEST_BYTES = 512 * 1024;
46
50
  var MAX_ERROR_MESSAGE_BYTES = 1e3;
47
51
  var MAX_EMAIL_LENGTH = 254;
48
52
  var MAX_EVENT_NAME_LENGTH = 100;
53
+ var MAX_CUSTOMER_ID_LENGTH = 128;
54
+ var MAX_CUSTOMER_NAME_LENGTH = 200;
49
55
  var MAX_EXIT_DRAINS = 3;
50
56
  var NEXT_BATCH_DELAY_MS = 10;
51
57
  var EXIT_PROBE_MS = 100;
@@ -55,6 +61,9 @@ var KEEPALIVE_BUDGET_BYTES = 32 * 1024;
55
61
  var WAIT_TIMEOUT_MS = 2e3;
56
62
  var MAX_TIMER_MS = 2147483647;
57
63
  var MODULE_LOADED_AT = Date.now();
64
+ var MAX_SURFACE_METHODS = 500;
65
+ var MAX_METHOD_NAME_LENGTH = 255;
66
+ var MAX_SURFACE_CHARS = 32 * 1024;
58
67
  function _clamp(value, min, max, fallback) {
59
68
  if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
60
69
  return Math.min(max, Math.max(min, Math.floor(value)));
@@ -115,6 +124,29 @@ function _getMetadata() {
115
124
  return meta;
116
125
  }
117
126
  var EMAIL_PATTERN = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/;
127
+ function _customerField(value, maxLength) {
128
+ if (typeof value !== "string") return "";
129
+ return value.replace(/[\u0000-\u001f\u007f-\u009f]/g, "").trim().slice(0, maxLength).trim();
130
+ }
131
+ function _cleanCustomer(customer) {
132
+ if (customer === null || typeof customer !== "object") return null;
133
+ const c = customer;
134
+ const id = _customerField(c["id"], MAX_CUSTOMER_ID_LENGTH);
135
+ if (id === "") {
136
+ _log("Ignored a customer without an id");
137
+ return null;
138
+ }
139
+ if (EMAIL_PATTERN.test(id)) {
140
+ _log("Ignored a customer id that contains an email address");
141
+ return null;
142
+ }
143
+ const name = _customerField(c["name"], MAX_CUSTOMER_NAME_LENGTH);
144
+ if (name !== "" && EMAIL_PATTERN.test(name)) {
145
+ _log("Left out a customer name that contains an email address");
146
+ return { id };
147
+ }
148
+ return name !== "" ? { id, name } : { id };
149
+ }
118
150
  function _truncateUtf8(str, maxBytes) {
119
151
  let bytes = 0;
120
152
  for (let i = 0; i < str.length; i++) {
@@ -208,6 +240,7 @@ function _enqueue(event) {
208
240
  _log("Queue full; dropped event", event.event);
209
241
  return;
210
242
  }
243
+ if (_customer) event.customer = _customer;
211
244
  let json;
212
245
  try {
213
246
  json = JSON.stringify(event);
@@ -305,6 +338,37 @@ function _createWrapper(methodName, fn) {
305
338
  });
306
339
  return wrapped;
307
340
  }
341
+ function _isMethodName(name) {
342
+ return typeof name === "string" && name.trim() !== "" && name.length <= MAX_METHOD_NAME_LENGTH;
343
+ }
344
+ function _surfaceList(names) {
345
+ const out = /* @__PURE__ */ new Set();
346
+ let chars = 0;
347
+ for (const name of names) {
348
+ if (out.size >= MAX_SURFACE_METHODS) break;
349
+ if (!_isMethodName(name) || out.has(name)) continue;
350
+ if (chars + name.length > MAX_SURFACE_CHARS) break;
351
+ out.add(name);
352
+ chars += name.length;
353
+ }
354
+ return Array.from(out);
355
+ }
356
+ function _register(methodName) {
357
+ if (!_isMethodName(methodName) || _registered.has(methodName) || _registered.size >= MAX_SURFACE_METHODS) return;
358
+ _registered.add(methodName);
359
+ if (!_config) return;
360
+ _undeclared.push(methodName);
361
+ if (_declareScheduled) return;
362
+ _declareScheduled = true;
363
+ Promise.resolve().then(
364
+ () => _safely(() => {
365
+ _declareScheduled = false;
366
+ const methods = _surfaceList(_undeclared);
367
+ _undeclared = [];
368
+ if (methods.length > 0) SdkGarden.track("api_surface", { api_surface: methods });
369
+ })
370
+ );
371
+ }
308
372
  function _keepaliveBytesInFlight() {
309
373
  let total = 0;
310
374
  _pending.forEach((request) => {
@@ -586,6 +650,7 @@ function _teardown() {
586
650
  _config = null;
587
651
  _queue = [];
588
652
  _sessionId = "";
653
+ _customer = null;
589
654
  _endExit();
590
655
  }
591
656
  function _start(config) {
@@ -607,6 +672,12 @@ function _start(config) {
607
672
  _config = _mergeDefaults(config);
608
673
  _queue = carried;
609
674
  _sessionId = _generateSessionId();
675
+ _customer = config.customer === void 0 ? null : _cleanCustomer(config.customer);
676
+ _undeclared = [];
677
+ const apiSurface = _surfaceList([
678
+ ...Array.isArray(config.methods) ? config.methods : [],
679
+ ..._registered
680
+ ]);
610
681
  _log("Initialised", _sanitizeConfig(_config));
611
682
  _enqueue({
612
683
  event: "sdk_initialized",
@@ -615,7 +686,8 @@ function _start(config) {
615
686
  timestamp: Date.now(),
616
687
  properties: {
617
688
  environment: _getEnvironment(),
618
- time_to_init_ms: Math.max(0, Date.now() - initStartedAt)
689
+ time_to_init_ms: Math.max(0, Date.now() - initStartedAt),
690
+ ...apiSurface.length > 0 ? { api_surface: apiSurface } : {}
619
691
  },
620
692
  meta: _getMetadata()
621
693
  });
@@ -678,6 +750,7 @@ var SdkGarden = {
678
750
  * ```
679
751
  */
680
752
  trackMethod(methodName, fn) {
753
+ _safely(() => _register(methodName));
681
754
  if (fn !== void 0) {
682
755
  return _createWrapper(methodName, fn);
683
756
  }
@@ -712,6 +785,25 @@ var SdkGarden = {
712
785
  })
713
786
  );
714
787
  },
788
+ /**
789
+ * Attribute the session to one of your customers: an app or account, by an
790
+ * ID from your own system. Every event tracked afterwards carries it, so the
791
+ * dashboard can show which customers are on old versions, still call
792
+ * deprecated methods or hit errors. Pass null to stop. Lasts until the next
793
+ * init() or shutdown(); no-op before init().
794
+ *
795
+ * Never pass an email address or other personal data: an ID containing an
796
+ * email address is ignored.
797
+ *
798
+ * @example
799
+ * SdkGarden.setCustomer({ id: account.id, name: account.displayName });
800
+ */
801
+ setCustomer(customer) {
802
+ if (!_config) return;
803
+ _safely(() => {
804
+ _customer = customer === null ? null : _cleanCustomer(customer);
805
+ });
806
+ },
715
807
  /**
716
808
  * Send every queued event now and wait for the requests to finish.
717
809
  * Useful at the end of a serverless handler or a test.
@@ -746,12 +838,14 @@ var SdkGarden = {
746
838
  /**
747
839
  * Reset all internal state.
748
840
  * FOR UNIT TESTS ONLY — not documented in README.
749
- * Clears queue, config, session ID, flush timer, listeners and the record
750
- * of requests in flight, without sending or cancelling anything.
841
+ * Clears queue, config, session ID, flush timer, listeners, registered
842
+ * method names and the record of requests in flight, without sending or cancelling anything.
751
843
  */
752
844
  _reset() {
753
845
  _teardown();
754
846
  _pending.clear();
847
+ _registered.clear();
848
+ _undeclared = [];
755
849
  }
756
850
  };
757
851
  var index_default = SdkGarden;
package/dist/index.mjs CHANGED
@@ -2,6 +2,7 @@
2
2
  var _config = null;
3
3
  var _queue = [];
4
4
  var _sessionId = "";
5
+ var _customer = null;
5
6
  var _flushTimer = null;
6
7
  var _exiting = false;
7
8
  var _exitDrains = 0;
@@ -13,6 +14,9 @@ var _exitTurnCount = 0;
13
14
  var _exitProbe = null;
14
15
  var _exitProbeDueAt = Infinity;
15
16
  var _pending = /* @__PURE__ */ new Set();
17
+ var _registered = /* @__PURE__ */ new Set();
18
+ var _undeclared = [];
19
+ var _declareScheduled = false;
16
20
  var DEFAULT_ENDPOINT = "https://ingest.sdkgarden.com/track";
17
21
  var MAX_BATCH_SIZE = 100;
18
22
  var MAX_QUEUE_SIZE = 1e3;
@@ -22,6 +26,8 @@ var MAX_REQUEST_BYTES = 512 * 1024;
22
26
  var MAX_ERROR_MESSAGE_BYTES = 1e3;
23
27
  var MAX_EMAIL_LENGTH = 254;
24
28
  var MAX_EVENT_NAME_LENGTH = 100;
29
+ var MAX_CUSTOMER_ID_LENGTH = 128;
30
+ var MAX_CUSTOMER_NAME_LENGTH = 200;
25
31
  var MAX_EXIT_DRAINS = 3;
26
32
  var NEXT_BATCH_DELAY_MS = 10;
27
33
  var EXIT_PROBE_MS = 100;
@@ -31,6 +37,9 @@ var KEEPALIVE_BUDGET_BYTES = 32 * 1024;
31
37
  var WAIT_TIMEOUT_MS = 2e3;
32
38
  var MAX_TIMER_MS = 2147483647;
33
39
  var MODULE_LOADED_AT = Date.now();
40
+ var MAX_SURFACE_METHODS = 500;
41
+ var MAX_METHOD_NAME_LENGTH = 255;
42
+ var MAX_SURFACE_CHARS = 32 * 1024;
34
43
  function _clamp(value, min, max, fallback) {
35
44
  if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
36
45
  return Math.min(max, Math.max(min, Math.floor(value)));
@@ -91,6 +100,29 @@ function _getMetadata() {
91
100
  return meta;
92
101
  }
93
102
  var EMAIL_PATTERN = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/;
103
+ function _customerField(value, maxLength) {
104
+ if (typeof value !== "string") return "";
105
+ return value.replace(/[\u0000-\u001f\u007f-\u009f]/g, "").trim().slice(0, maxLength).trim();
106
+ }
107
+ function _cleanCustomer(customer) {
108
+ if (customer === null || typeof customer !== "object") return null;
109
+ const c = customer;
110
+ const id = _customerField(c["id"], MAX_CUSTOMER_ID_LENGTH);
111
+ if (id === "") {
112
+ _log("Ignored a customer without an id");
113
+ return null;
114
+ }
115
+ if (EMAIL_PATTERN.test(id)) {
116
+ _log("Ignored a customer id that contains an email address");
117
+ return null;
118
+ }
119
+ const name = _customerField(c["name"], MAX_CUSTOMER_NAME_LENGTH);
120
+ if (name !== "" && EMAIL_PATTERN.test(name)) {
121
+ _log("Left out a customer name that contains an email address");
122
+ return { id };
123
+ }
124
+ return name !== "" ? { id, name } : { id };
125
+ }
94
126
  function _truncateUtf8(str, maxBytes) {
95
127
  let bytes = 0;
96
128
  for (let i = 0; i < str.length; i++) {
@@ -184,6 +216,7 @@ function _enqueue(event) {
184
216
  _log("Queue full; dropped event", event.event);
185
217
  return;
186
218
  }
219
+ if (_customer) event.customer = _customer;
187
220
  let json;
188
221
  try {
189
222
  json = JSON.stringify(event);
@@ -281,6 +314,37 @@ function _createWrapper(methodName, fn) {
281
314
  });
282
315
  return wrapped;
283
316
  }
317
+ function _isMethodName(name) {
318
+ return typeof name === "string" && name.trim() !== "" && name.length <= MAX_METHOD_NAME_LENGTH;
319
+ }
320
+ function _surfaceList(names) {
321
+ const out = /* @__PURE__ */ new Set();
322
+ let chars = 0;
323
+ for (const name of names) {
324
+ if (out.size >= MAX_SURFACE_METHODS) break;
325
+ if (!_isMethodName(name) || out.has(name)) continue;
326
+ if (chars + name.length > MAX_SURFACE_CHARS) break;
327
+ out.add(name);
328
+ chars += name.length;
329
+ }
330
+ return Array.from(out);
331
+ }
332
+ function _register(methodName) {
333
+ if (!_isMethodName(methodName) || _registered.has(methodName) || _registered.size >= MAX_SURFACE_METHODS) return;
334
+ _registered.add(methodName);
335
+ if (!_config) return;
336
+ _undeclared.push(methodName);
337
+ if (_declareScheduled) return;
338
+ _declareScheduled = true;
339
+ Promise.resolve().then(
340
+ () => _safely(() => {
341
+ _declareScheduled = false;
342
+ const methods = _surfaceList(_undeclared);
343
+ _undeclared = [];
344
+ if (methods.length > 0) SdkGarden.track("api_surface", { api_surface: methods });
345
+ })
346
+ );
347
+ }
284
348
  function _keepaliveBytesInFlight() {
285
349
  let total = 0;
286
350
  _pending.forEach((request) => {
@@ -562,6 +626,7 @@ function _teardown() {
562
626
  _config = null;
563
627
  _queue = [];
564
628
  _sessionId = "";
629
+ _customer = null;
565
630
  _endExit();
566
631
  }
567
632
  function _start(config) {
@@ -583,6 +648,12 @@ function _start(config) {
583
648
  _config = _mergeDefaults(config);
584
649
  _queue = carried;
585
650
  _sessionId = _generateSessionId();
651
+ _customer = config.customer === void 0 ? null : _cleanCustomer(config.customer);
652
+ _undeclared = [];
653
+ const apiSurface = _surfaceList([
654
+ ...Array.isArray(config.methods) ? config.methods : [],
655
+ ..._registered
656
+ ]);
586
657
  _log("Initialised", _sanitizeConfig(_config));
587
658
  _enqueue({
588
659
  event: "sdk_initialized",
@@ -591,7 +662,8 @@ function _start(config) {
591
662
  timestamp: Date.now(),
592
663
  properties: {
593
664
  environment: _getEnvironment(),
594
- time_to_init_ms: Math.max(0, Date.now() - initStartedAt)
665
+ time_to_init_ms: Math.max(0, Date.now() - initStartedAt),
666
+ ...apiSurface.length > 0 ? { api_surface: apiSurface } : {}
595
667
  },
596
668
  meta: _getMetadata()
597
669
  });
@@ -654,6 +726,7 @@ var SdkGarden = {
654
726
  * ```
655
727
  */
656
728
  trackMethod(methodName, fn) {
729
+ _safely(() => _register(methodName));
657
730
  if (fn !== void 0) {
658
731
  return _createWrapper(methodName, fn);
659
732
  }
@@ -688,6 +761,25 @@ var SdkGarden = {
688
761
  })
689
762
  );
690
763
  },
764
+ /**
765
+ * Attribute the session to one of your customers: an app or account, by an
766
+ * ID from your own system. Every event tracked afterwards carries it, so the
767
+ * dashboard can show which customers are on old versions, still call
768
+ * deprecated methods or hit errors. Pass null to stop. Lasts until the next
769
+ * init() or shutdown(); no-op before init().
770
+ *
771
+ * Never pass an email address or other personal data: an ID containing an
772
+ * email address is ignored.
773
+ *
774
+ * @example
775
+ * SdkGarden.setCustomer({ id: account.id, name: account.displayName });
776
+ */
777
+ setCustomer(customer) {
778
+ if (!_config) return;
779
+ _safely(() => {
780
+ _customer = customer === null ? null : _cleanCustomer(customer);
781
+ });
782
+ },
691
783
  /**
692
784
  * Send every queued event now and wait for the requests to finish.
693
785
  * Useful at the end of a serverless handler or a test.
@@ -722,12 +814,14 @@ var SdkGarden = {
722
814
  /**
723
815
  * Reset all internal state.
724
816
  * FOR UNIT TESTS ONLY — not documented in README.
725
- * Clears queue, config, session ID, flush timer, listeners and the record
726
- * of requests in flight, without sending or cancelling anything.
817
+ * Clears queue, config, session ID, flush timer, listeners, registered
818
+ * method names and the record of requests in flight, without sending or cancelling anything.
727
819
  */
728
820
  _reset() {
729
821
  _teardown();
730
822
  _pending.clear();
823
+ _registered.clear();
824
+ _undeclared = [];
731
825
  }
732
826
  };
733
827
  var index_default = SdkGarden;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sdk-garden-js",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Lightweight SDK observability client for JavaScript/TypeScript \u2014 zero runtime dependencies",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",