@lib-acr/core 0.0.0-stage → 0.0.2
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/LICENSE +21 -0
- package/README.md +97 -2
- package/dist/cjs/backoff.js +9 -0
- package/dist/cjs/backoff.js.map +1 -0
- package/dist/cjs/buffer.js +50 -0
- package/dist/cjs/buffer.js.map +1 -0
- package/dist/cjs/client.js +594 -0
- package/dist/cjs/client.js.map +1 -0
- package/dist/cjs/config.js +65 -0
- package/dist/cjs/config.js.map +1 -0
- package/dist/cjs/control/bucketing.js +66 -0
- package/dist/cjs/control/bucketing.js.map +1 -0
- package/dist/cjs/control/commands.js +45 -0
- package/dist/cjs/control/commands.js.map +1 -0
- package/dist/cjs/control/control-client.js +170 -0
- package/dist/cjs/control/control-client.js.map +1 -0
- package/dist/cjs/control/flags.js +44 -0
- package/dist/cjs/control/flags.js.map +1 -0
- package/dist/cjs/control/semver.js +63 -0
- package/dist/cjs/control/semver.js.map +1 -0
- package/dist/cjs/control/snapshot.js +177 -0
- package/dist/cjs/control/snapshot.js.map +1 -0
- package/dist/cjs/control/state.js +121 -0
- package/dist/cjs/control/state.js.map +1 -0
- package/dist/cjs/control/types.js +3 -0
- package/dist/cjs/control/types.js.map +1 -0
- package/dist/cjs/errors.js +28 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/ids.js +65 -0
- package/dist/cjs/ids.js.map +1 -0
- package/dist/cjs/index.js +70 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/redact.js +74 -0
- package/dist/cjs/redact.js.map +1 -0
- package/dist/cjs/sdk-info.js +6 -0
- package/dist/cjs/sdk-info.js.map +1 -0
- package/dist/cjs/transport.js +111 -0
- package/dist/cjs/transport.js.map +1 -0
- package/dist/cjs/types.js +14 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/cjs/version.js +8 -0
- package/dist/cjs/version.js.map +1 -0
- package/dist/esm/backoff.js +6 -0
- package/dist/esm/backoff.js.map +1 -0
- package/dist/esm/buffer.js +46 -0
- package/dist/esm/buffer.js.map +1 -0
- package/dist/esm/client.js +588 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/config.js +62 -0
- package/dist/esm/config.js.map +1 -0
- package/dist/esm/control/bucketing.js +60 -0
- package/dist/esm/control/bucketing.js.map +1 -0
- package/dist/esm/control/commands.js +41 -0
- package/dist/esm/control/commands.js.map +1 -0
- package/dist/esm/control/control-client.js +166 -0
- package/dist/esm/control/control-client.js.map +1 -0
- package/dist/esm/control/flags.js +40 -0
- package/dist/esm/control/flags.js.map +1 -0
- package/dist/esm/control/semver.js +58 -0
- package/dist/esm/control/semver.js.map +1 -0
- package/dist/esm/control/snapshot.js +172 -0
- package/dist/esm/control/snapshot.js.map +1 -0
- package/dist/esm/control/state.js +112 -0
- package/dist/esm/control/state.js.map +1 -0
- package/dist/esm/control/types.js +2 -0
- package/dist/esm/control/types.js.map +1 -0
- package/dist/esm/errors.js +25 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/ids.js +58 -0
- package/dist/esm/ids.js.map +1 -0
- package/dist/esm/index.js +18 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/redact.js +68 -0
- package/dist/esm/redact.js.map +1 -0
- package/dist/esm/sdk-info.js +3 -0
- package/dist/esm/sdk-info.js.map +1 -0
- package/dist/esm/transport.js +105 -0
- package/dist/esm/transport.js.map +1 -0
- package/dist/esm/types.js +10 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/version.js +4 -0
- package/dist/esm/version.js.map +1 -0
- package/dist/types/backoff.d.ts +2 -0
- package/dist/types/buffer.d.ts +21 -0
- package/dist/types/client.d.ts +187 -0
- package/dist/types/config.d.ts +27 -0
- package/dist/types/control/bucketing.d.ts +11 -0
- package/dist/types/control/commands.d.ts +37 -0
- package/dist/types/control/control-client.d.ts +69 -0
- package/dist/types/control/flags.d.ts +19 -0
- package/dist/types/control/semver.d.ts +15 -0
- package/dist/types/control/snapshot.d.ts +12 -0
- package/dist/types/control/state.d.ts +39 -0
- package/dist/types/control/types.d.ts +126 -0
- package/dist/types/errors.d.ts +7 -0
- package/dist/types/ids.d.ts +14 -0
- package/dist/types/index.d.ts +28 -0
- package/dist/types/redact.d.ts +11 -0
- package/dist/types/sdk-info.d.ts +4 -0
- package/dist/types/transport.d.ts +35 -0
- package/dist/types/types.d.ts +87 -0
- package/dist/types/version.d.ts +3 -0
- package/package.json +37 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Application Control Room
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,98 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @lib-acr/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Shared telemetry client used by every ACR SDK: batching, retry with exponential backoff and jitter, a bounded buffer, sampling, offline buffering, redaction and the ingestion protocol.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @lib-acr/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { AcrClient } from '@lib-acr/core';
|
|
15
|
+
|
|
16
|
+
const acr = new AcrClient({
|
|
17
|
+
apiKey: process.env.ACR_API_KEY!, // acr_live_… or acr_test_…
|
|
18
|
+
apiSecret: process.env.ACR_API_SECRET, // server only
|
|
19
|
+
endpoint: 'https://acr.example.com/v1',
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
acr.trackEvent('checkout_started', { cartSize: 3 }, { userId: 'user_42' });
|
|
23
|
+
acr.log('info', 'order created', { orderId: 'o-1' });
|
|
24
|
+
acr.captureError(new Error('payment declined'), { handled: true });
|
|
25
|
+
await acr.flush(); // or await acr.shutdown(2000) before the process exits
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Notes
|
|
29
|
+
|
|
30
|
+
Options: `flushIntervalMs`, `maxBatchSize` (1–500), `maxBufferSize`, `maxRetries`, `retryBaseMs`, `requestTimeoutMs`, `sampleRate` (events and logs only), `minLogLevel` (`debug` by default; lower logs are dropped before buffering), `enabled`, `isOnline`, `onError`, `remoteControl` (default true), `configPollIntervalMs` (default 60 000). Invalid options throw at construction.
|
|
31
|
+
|
|
32
|
+
- **Sampling** is per session: items with a session id are kept or skipped as a whole session
|
|
33
|
+
(`fnv1a32('sample:' + sessionId) % 10000 < sampleRate * 10000`); items without one are sampled individually.
|
|
34
|
+
Errors and heartbeats are never sampled.
|
|
35
|
+
- **Enable state:** `setEnabled(false)` stops collection and the background flush timer; `setEnabled(true)` starts
|
|
36
|
+
the timer again.
|
|
37
|
+
- **Offline:** when the device goes offline during a flush, every undelivered item of the batch goes back into the
|
|
38
|
+
buffer in order. `stats()` reports `queued`, `sent`, `rejected`, `dropped`, `bufferOverflow`, `requeued`,
|
|
39
|
+
`sampledOut` and `belowMinLevel`.
|
|
40
|
+
- **Rate limits:** a 429 waits for the `Retry-After` header, or `retryAfterSeconds` from the JSON error body.
|
|
41
|
+
- **Free-text redaction:** log messages, error messages and stack traces have e-mail addresses, `Bearer`/`Basic`
|
|
42
|
+
credentials, JWTs, `acr_secret_…` values and `token=`/`password=` pairs masked before buffering (`redactString`).
|
|
43
|
+
- `sendAllNow()` starts one request per kind at once, without retries (used on page hide); `clearQueue()` discards
|
|
44
|
+
the buffer.
|
|
45
|
+
|
|
46
|
+
## Remote control
|
|
47
|
+
|
|
48
|
+
Unless `remoteControl: false` is set, the client polls `GET {endpoint}/sdk/config` with the same key headers as
|
|
49
|
+
ingestion and `If-None-Match: "<revision>"` (a `304` keeps the current snapshot). It polls at start-up and every
|
|
50
|
+
`configPollIntervalMs` (default 60 000, at least 15 000). The server can ask for a longer interval with
|
|
51
|
+
`pollAfterSeconds`, and while the kill switch is on the client polls every 10 minutes. On failure it keeps the last
|
|
52
|
+
good snapshot and retries with backoff; the first failure in a row is reported to `onError` as `CONTROL_FETCH_FAILED`.
|
|
53
|
+
Timers are unref'd, and `shutdown()` stops polling.
|
|
54
|
+
|
|
55
|
+
- **Kill switch:** `sdk.enabled: false` stops collection and flushing (buffered items wait) until it is true again.
|
|
56
|
+
It is separate from `setEnabled()`: enabling locally does not override it.
|
|
57
|
+
- `sdk.sampleRate` and `sdk.logLevel` override `sampleRate` and `minLogLevel` while the snapshot sets them.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
const state = acr.getControlState(); // { ready, revision, sdkEnabled, maintenance, announcement, release, flags, … }
|
|
61
|
+
const off = acr.onControlChange((s) => render(s));
|
|
62
|
+
await acr.refreshControl(); // poll now
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`getControlState()` returns the same frozen object until something changes (new revision, `identify()`, an
|
|
66
|
+
announcement window opening or closing), so it works directly with `useSyncExternalStore`. `maintenance.active` is
|
|
67
|
+
false for subjects in `allowUserIds`. `release.updateAvailable` and `release.belowMinimum` compare `appVersion`
|
|
68
|
+
(semver) with `latestVersion` and `minimumVersion`; versions that are not valid semver never prompt.
|
|
69
|
+
|
|
70
|
+
Building blocks for other SDKs and frameworks: `parseSnapshot`, `planCommands`, `evaluateFlag`, `compareSemver`,
|
|
71
|
+
`deriveControlState`, `maintenanceResponse` and `isMaintenanceExemptPath`.
|
|
72
|
+
|
|
73
|
+
## Feature flags
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
acr.flag('new-checkout', false); // current subject: userId if identified, else anonymousId
|
|
77
|
+
acr.flag('new-checkout', false, { userId: 'user_42' }); // servers: evaluate for the request's user
|
|
78
|
+
acr.flags(); // every flag, evaluated for the current subject
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Flags are evaluated locally from the snapshot; there is no request per flag. A missing or disabled flag returns the
|
|
82
|
+
fallback. A subject in `allowUserIds` gets `value`. Otherwise `FNV-1a-32(flagKey + ":" + subject) % 100 < rollout`
|
|
83
|
+
gets `value` and everyone else gets the flag's `default`. Without a subject (a server call with no user) only a
|
|
84
|
+
100 % rollout serves `value`. When the served value has a different type from the fallback, the fallback is returned.
|
|
85
|
+
The vectors in `packages/conformance/fixtures` pin the algorithm for every language.
|
|
86
|
+
|
|
87
|
+
## Wire protocol
|
|
88
|
+
|
|
89
|
+
Every SDK sends the same protocol: `POST {endpoint}/ingest/{events|logs|errors|heartbeat}` with the `X-ACR-Api-Key`
|
|
90
|
+
header (and `X-ACR-Secret-Key` for server keys). The credential securely determines the app and its single
|
|
91
|
+
environment; the body is `{ schemaVersion: 1, sdk, items[1..500] }`.
|
|
92
|
+
Each item has a client `id`, which lets the server drop duplicates when a batch is retried.
|
|
93
|
+
|
|
94
|
+
Retries use exponential backoff with jitter for network errors, 429 and 5xx responses. Other 4xx responses are not retried.
|
|
95
|
+
Telemetry is buffered in a bounded in-memory queue. When it is full, the oldest items are dropped and counted in `stats()`.
|
|
96
|
+
SDK code never throws into the host application.
|
|
97
|
+
|
|
98
|
+
Metrics (`increment`, `gauge`, `histogram`) and traces are not yet available.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.backoffMs = backoffMs;
|
|
4
|
+
/** Exponential backoff with equal jitter: half fixed, half random. Capped at 30 s. */
|
|
5
|
+
function backoffMs(attempt, baseMs) {
|
|
6
|
+
const exp = Math.min(30000, baseMs * 2 ** attempt);
|
|
7
|
+
return exp / 2 + Math.random() * (exp / 2);
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=backoff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backoff.js","sourceRoot":"","sources":["../../src/backoff.ts"],"names":[],"mappings":";;AACA,8BAGC;AAJD,sFAAsF;AACtF,SAAgB,SAAS,CAAC,OAAe,EAAE,MAAc;IACvD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,KAAM,EAAE,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,CAAC;IACpD,OAAO,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;AAC7C,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BoundedBuffer = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Bounded FIFO. When full, the oldest entries are dropped so the newest telemetry survives and memory stays flat.
|
|
6
|
+
* Dropped entries are counted, never thrown.
|
|
7
|
+
*/
|
|
8
|
+
class BoundedBuffer {
|
|
9
|
+
constructor(maxSize) {
|
|
10
|
+
this.maxSize = maxSize;
|
|
11
|
+
this.items = [];
|
|
12
|
+
this.droppedCount = 0;
|
|
13
|
+
}
|
|
14
|
+
get size() {
|
|
15
|
+
return this.items.length;
|
|
16
|
+
}
|
|
17
|
+
get dropped() {
|
|
18
|
+
return this.droppedCount;
|
|
19
|
+
}
|
|
20
|
+
/** Adds one entry, dropping the oldest when full. */
|
|
21
|
+
push(item) {
|
|
22
|
+
if (this.items.length >= this.maxSize) {
|
|
23
|
+
this.items.shift();
|
|
24
|
+
this.droppedCount += 1;
|
|
25
|
+
}
|
|
26
|
+
this.items.push(item);
|
|
27
|
+
}
|
|
28
|
+
/** Removes up to `max` entries from the front, in order. */
|
|
29
|
+
take(max) {
|
|
30
|
+
return this.items.splice(0, Math.max(0, max));
|
|
31
|
+
}
|
|
32
|
+
/** Puts entries back at the front (after a failed send). Keeps the bound by dropping the newest overflow. */
|
|
33
|
+
requeue(items) {
|
|
34
|
+
this.items = [...items, ...this.items];
|
|
35
|
+
if (this.items.length > this.maxSize) {
|
|
36
|
+
const overflow = this.items.length - this.maxSize;
|
|
37
|
+
this.items.splice(this.maxSize, overflow);
|
|
38
|
+
this.droppedCount += overflow;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Copy of the current entries, oldest first. The buffer is not changed. */
|
|
42
|
+
toArray() {
|
|
43
|
+
return [...this.items];
|
|
44
|
+
}
|
|
45
|
+
clear() {
|
|
46
|
+
this.items = [];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.BoundedBuffer = BoundedBuffer;
|
|
50
|
+
//# sourceMappingURL=buffer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buffer.js","sourceRoot":"","sources":["../../src/buffer.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACH,MAAa,aAAa;IAIxB,YAA6B,OAAe;QAAf,YAAO,GAAP,OAAO,CAAQ;QAHpC,UAAK,GAAQ,EAAE,CAAC;QAChB,iBAAY,GAAG,CAAC,CAAC;IAEsB,CAAC;IAEhD,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;IAC3B,CAAC;IAED,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAED,qDAAqD;IACrD,IAAI,CAAC,IAAO;QACV,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACtC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YACnB,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC;QACzB,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC;IAED,4DAA4D;IAC5D,IAAI,CAAC,GAAW;QACd,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAChD,CAAC;IAED,6GAA6G;IAC7G,OAAO,CAAC,KAAU;QAChB,IAAI,CAAC,KAAK,GAAG,CAAC,GAAG,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QACvC,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;YACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC;YAClD,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YAC1C,IAAI,CAAC,YAAY,IAAI,QAAQ,CAAC;QAChC,CAAC;IACH,CAAC;IAED,4EAA4E;IAC5E,OAAO;QACL,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;IACzB,CAAC;IAED,KAAK;QACH,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;IAClB,CAAC;CACF;AA9CD,sCA8CC"}
|