@smplkit/sdk 3.0.7 → 3.0.9
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/dist/index.cjs +269 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +99 -1
- package/dist/index.d.ts +99 -1
- package/dist/index.js +268 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,101 @@
|
|
|
1
1
|
import createClient from 'openapi-fetch';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Audit event resource types — surface exposed by `client.audit.events.*`.
|
|
5
|
+
*
|
|
6
|
+
* ADR-047 §2.3.1.
|
|
7
|
+
*/
|
|
8
|
+
interface AuditEvent {
|
|
9
|
+
id: string;
|
|
10
|
+
action: string;
|
|
11
|
+
resourceType: string;
|
|
12
|
+
resourceId: string;
|
|
13
|
+
occurredAt: string;
|
|
14
|
+
createdAt: string;
|
|
15
|
+
actorType: string;
|
|
16
|
+
actorId: string | null;
|
|
17
|
+
actorLabel: string;
|
|
18
|
+
snapshot: Record<string, unknown> | null;
|
|
19
|
+
data: Record<string, unknown>;
|
|
20
|
+
idempotencyKey: string;
|
|
21
|
+
}
|
|
22
|
+
interface CreateEventInput {
|
|
23
|
+
action: string;
|
|
24
|
+
resourceType: string;
|
|
25
|
+
resourceId: string;
|
|
26
|
+
/** Defaults to server-side now() if omitted. */
|
|
27
|
+
occurredAt?: Date | string;
|
|
28
|
+
snapshot?: Record<string, unknown>;
|
|
29
|
+
data?: Record<string, unknown>;
|
|
30
|
+
/** Optional caller-supplied idempotency key. Server derives one from event content if absent. */
|
|
31
|
+
idempotencyKey?: string;
|
|
32
|
+
}
|
|
33
|
+
interface ListEventsParams {
|
|
34
|
+
action?: string;
|
|
35
|
+
resourceType?: string;
|
|
36
|
+
resourceId?: string;
|
|
37
|
+
actorType?: string;
|
|
38
|
+
actorId?: string;
|
|
39
|
+
/** Range notation per ADR-014, e.g. `[2026-01-01T00:00:00Z,*)`. */
|
|
40
|
+
occurredAtRange?: string;
|
|
41
|
+
pageSize?: number;
|
|
42
|
+
pageAfter?: string;
|
|
43
|
+
}
|
|
44
|
+
interface ListEventsPage {
|
|
45
|
+
events: AuditEvent[];
|
|
46
|
+
/** Opaque cursor for the next page, or null if this is the last page. */
|
|
47
|
+
nextCursor: string | null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Audit namespace — `client.audit.events.{create,list,get}`.
|
|
52
|
+
*
|
|
53
|
+
* ADR-047 §2.6. Writes are fire-and-forget by default: `create` enqueues the
|
|
54
|
+
* event onto an in-memory bounded buffer and returns immediately. The buffer
|
|
55
|
+
* worker flushes on a timer or when depth crosses a watermark, and retries
|
|
56
|
+
* transient failures with exponential backoff. Reads are async and return
|
|
57
|
+
* Promises that resolve to typed `AuditEvent` instances.
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
declare class EventsClient {
|
|
61
|
+
private readonly _apiKey;
|
|
62
|
+
private readonly _baseUrl;
|
|
63
|
+
private readonly _timeoutMs;
|
|
64
|
+
private readonly _buffer;
|
|
65
|
+
/** @internal */
|
|
66
|
+
_fetch: typeof fetch;
|
|
67
|
+
constructor(opts: {
|
|
68
|
+
apiKey: string;
|
|
69
|
+
baseUrl: string;
|
|
70
|
+
timeoutMs?: number;
|
|
71
|
+
});
|
|
72
|
+
/**
|
|
73
|
+
* Enqueue an audit event for asynchronous delivery.
|
|
74
|
+
* Returns immediately. The actual POST happens on the buffer worker.
|
|
75
|
+
*
|
|
76
|
+
* Customers may not emit `smpl.*` resource types — the server will
|
|
77
|
+
* reject those with a 403 (the buffer logs and drops permanent
|
|
78
|
+
* failures, so a misuse will silently disappear from the queue).
|
|
79
|
+
*/
|
|
80
|
+
create(input: CreateEventInput): void;
|
|
81
|
+
list(params?: ListEventsParams): Promise<ListEventsPage>;
|
|
82
|
+
get(eventId: string): Promise<AuditEvent>;
|
|
83
|
+
/** Block until the in-memory buffer is drained or `timeoutMs` elapses. */
|
|
84
|
+
flush(timeoutMs?: number): Promise<void>;
|
|
85
|
+
/** @internal */
|
|
86
|
+
_close(): Promise<void>;
|
|
87
|
+
}
|
|
88
|
+
declare class AuditClient {
|
|
89
|
+
readonly events: EventsClient;
|
|
90
|
+
constructor(opts: {
|
|
91
|
+
apiKey: string;
|
|
92
|
+
baseUrl: string;
|
|
93
|
+
timeoutMs?: number;
|
|
94
|
+
});
|
|
95
|
+
/** @internal */
|
|
96
|
+
_close(): Promise<void>;
|
|
97
|
+
}
|
|
98
|
+
|
|
3
99
|
/**
|
|
4
100
|
* Internal SDK telemetry engine.
|
|
5
101
|
*
|
|
@@ -2748,6 +2844,8 @@ declare class SmplClient {
|
|
|
2748
2844
|
readonly flags: FlagsClient;
|
|
2749
2845
|
/** Client for logging management and runtime. */
|
|
2750
2846
|
readonly logging: LoggingClient;
|
|
2847
|
+
/** Client for the audit service — fire-and-forget event recording (ADR-047). */
|
|
2848
|
+
readonly audit: AuditClient;
|
|
2751
2849
|
/**
|
|
2752
2850
|
* Standalone management/CRUD entry point — mirrors Python's
|
|
2753
2851
|
* `client.manage`. Construction is side-effect-free; safe to use even
|
|
@@ -2901,4 +2999,4 @@ declare class SmplValidationError extends SmplError {
|
|
|
2901
2999
|
/** @deprecated Use {@link ApiErrorDetail}. */
|
|
2902
3000
|
type ApiErrorObject = ApiErrorDetail;
|
|
2903
3001
|
|
|
2904
|
-
export { AccountSettings, AccountSettingsClient, type ApiErrorDetail, type ApiErrorObject, BooleanFlag, Color, Config, ConfigChangeEvent, ConfigClient, ConfigEnvironment, ConfigItem, Context, ContextType, ContextTypesClient, ContextsClient, Environment, EnvironmentClassification, EnvironmentsClient, Flag, FlagChangeEvent, FlagDeclaration, FlagEnvironment, FlagRule, FlagStats, FlagValue, FlagsClient, ItemType, JsonFlag, LiveConfigProxy, LogGroup, LogLevel, Logger, LoggerChangeEvent, LoggerEnvironment, LoggerSource, type LoggingAdapter, LoggingClient, NumberFlag, Op, PinoAdapter, type PinoAdapterConfig, Rule, SharedWebSocket, SmplClient, type SmplClientOptions, SmplConflictError, SmplConnectionError, SmplError, SmplManagementClient, type SmplManagementClientOptions, SmplNotFoundError, SmplTimeoutError, SmplValidationError, SmplConflictError as SmplkitConflictError, SmplConnectionError as SmplkitConnectionError, SmplError as SmplkitError, SmplNotFoundError as SmplkitNotFoundError, SmplTimeoutError as SmplkitTimeoutError, SmplValidationError as SmplkitValidationError, StringFlag, WinstonAdapter, type WinstonAdapterConfig };
|
|
3002
|
+
export { AccountSettings, AccountSettingsClient, type ApiErrorDetail, type ApiErrorObject, AuditClient, type AuditEvent, type ListEventsPage as AuditEventListPage, type ListEventsParams as AuditEventListParams, BooleanFlag, Color, Config, ConfigChangeEvent, ConfigClient, ConfigEnvironment, ConfigItem, Context, ContextType, ContextTypesClient, ContextsClient, type CreateEventInput as CreateAuditEventInput, Environment, EnvironmentClassification, EnvironmentsClient, Flag, FlagChangeEvent, FlagDeclaration, FlagEnvironment, FlagRule, FlagStats, FlagValue, FlagsClient, ItemType, JsonFlag, LiveConfigProxy, LogGroup, LogLevel, Logger, LoggerChangeEvent, LoggerEnvironment, LoggerSource, type LoggingAdapter, LoggingClient, NumberFlag, Op, PinoAdapter, type PinoAdapterConfig, Rule, SharedWebSocket, SmplClient, type SmplClientOptions, SmplConflictError, SmplConnectionError, SmplError, SmplManagementClient, type SmplManagementClientOptions, SmplNotFoundError, SmplTimeoutError, SmplValidationError, SmplConflictError as SmplkitConflictError, SmplConnectionError as SmplkitConnectionError, SmplError as SmplkitError, SmplNotFoundError as SmplkitNotFoundError, SmplTimeoutError as SmplkitTimeoutError, SmplValidationError as SmplkitValidationError, StringFlag, WinstonAdapter, type WinstonAdapterConfig };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,101 @@
|
|
|
1
1
|
import createClient from 'openapi-fetch';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Audit event resource types — surface exposed by `client.audit.events.*`.
|
|
5
|
+
*
|
|
6
|
+
* ADR-047 §2.3.1.
|
|
7
|
+
*/
|
|
8
|
+
interface AuditEvent {
|
|
9
|
+
id: string;
|
|
10
|
+
action: string;
|
|
11
|
+
resourceType: string;
|
|
12
|
+
resourceId: string;
|
|
13
|
+
occurredAt: string;
|
|
14
|
+
createdAt: string;
|
|
15
|
+
actorType: string;
|
|
16
|
+
actorId: string | null;
|
|
17
|
+
actorLabel: string;
|
|
18
|
+
snapshot: Record<string, unknown> | null;
|
|
19
|
+
data: Record<string, unknown>;
|
|
20
|
+
idempotencyKey: string;
|
|
21
|
+
}
|
|
22
|
+
interface CreateEventInput {
|
|
23
|
+
action: string;
|
|
24
|
+
resourceType: string;
|
|
25
|
+
resourceId: string;
|
|
26
|
+
/** Defaults to server-side now() if omitted. */
|
|
27
|
+
occurredAt?: Date | string;
|
|
28
|
+
snapshot?: Record<string, unknown>;
|
|
29
|
+
data?: Record<string, unknown>;
|
|
30
|
+
/** Optional caller-supplied idempotency key. Server derives one from event content if absent. */
|
|
31
|
+
idempotencyKey?: string;
|
|
32
|
+
}
|
|
33
|
+
interface ListEventsParams {
|
|
34
|
+
action?: string;
|
|
35
|
+
resourceType?: string;
|
|
36
|
+
resourceId?: string;
|
|
37
|
+
actorType?: string;
|
|
38
|
+
actorId?: string;
|
|
39
|
+
/** Range notation per ADR-014, e.g. `[2026-01-01T00:00:00Z,*)`. */
|
|
40
|
+
occurredAtRange?: string;
|
|
41
|
+
pageSize?: number;
|
|
42
|
+
pageAfter?: string;
|
|
43
|
+
}
|
|
44
|
+
interface ListEventsPage {
|
|
45
|
+
events: AuditEvent[];
|
|
46
|
+
/** Opaque cursor for the next page, or null if this is the last page. */
|
|
47
|
+
nextCursor: string | null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Audit namespace — `client.audit.events.{create,list,get}`.
|
|
52
|
+
*
|
|
53
|
+
* ADR-047 §2.6. Writes are fire-and-forget by default: `create` enqueues the
|
|
54
|
+
* event onto an in-memory bounded buffer and returns immediately. The buffer
|
|
55
|
+
* worker flushes on a timer or when depth crosses a watermark, and retries
|
|
56
|
+
* transient failures with exponential backoff. Reads are async and return
|
|
57
|
+
* Promises that resolve to typed `AuditEvent` instances.
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
declare class EventsClient {
|
|
61
|
+
private readonly _apiKey;
|
|
62
|
+
private readonly _baseUrl;
|
|
63
|
+
private readonly _timeoutMs;
|
|
64
|
+
private readonly _buffer;
|
|
65
|
+
/** @internal */
|
|
66
|
+
_fetch: typeof fetch;
|
|
67
|
+
constructor(opts: {
|
|
68
|
+
apiKey: string;
|
|
69
|
+
baseUrl: string;
|
|
70
|
+
timeoutMs?: number;
|
|
71
|
+
});
|
|
72
|
+
/**
|
|
73
|
+
* Enqueue an audit event for asynchronous delivery.
|
|
74
|
+
* Returns immediately. The actual POST happens on the buffer worker.
|
|
75
|
+
*
|
|
76
|
+
* Customers may not emit `smpl.*` resource types — the server will
|
|
77
|
+
* reject those with a 403 (the buffer logs and drops permanent
|
|
78
|
+
* failures, so a misuse will silently disappear from the queue).
|
|
79
|
+
*/
|
|
80
|
+
create(input: CreateEventInput): void;
|
|
81
|
+
list(params?: ListEventsParams): Promise<ListEventsPage>;
|
|
82
|
+
get(eventId: string): Promise<AuditEvent>;
|
|
83
|
+
/** Block until the in-memory buffer is drained or `timeoutMs` elapses. */
|
|
84
|
+
flush(timeoutMs?: number): Promise<void>;
|
|
85
|
+
/** @internal */
|
|
86
|
+
_close(): Promise<void>;
|
|
87
|
+
}
|
|
88
|
+
declare class AuditClient {
|
|
89
|
+
readonly events: EventsClient;
|
|
90
|
+
constructor(opts: {
|
|
91
|
+
apiKey: string;
|
|
92
|
+
baseUrl: string;
|
|
93
|
+
timeoutMs?: number;
|
|
94
|
+
});
|
|
95
|
+
/** @internal */
|
|
96
|
+
_close(): Promise<void>;
|
|
97
|
+
}
|
|
98
|
+
|
|
3
99
|
/**
|
|
4
100
|
* Internal SDK telemetry engine.
|
|
5
101
|
*
|
|
@@ -2748,6 +2844,8 @@ declare class SmplClient {
|
|
|
2748
2844
|
readonly flags: FlagsClient;
|
|
2749
2845
|
/** Client for logging management and runtime. */
|
|
2750
2846
|
readonly logging: LoggingClient;
|
|
2847
|
+
/** Client for the audit service — fire-and-forget event recording (ADR-047). */
|
|
2848
|
+
readonly audit: AuditClient;
|
|
2751
2849
|
/**
|
|
2752
2850
|
* Standalone management/CRUD entry point — mirrors Python's
|
|
2753
2851
|
* `client.manage`. Construction is side-effect-free; safe to use even
|
|
@@ -2901,4 +2999,4 @@ declare class SmplValidationError extends SmplError {
|
|
|
2901
2999
|
/** @deprecated Use {@link ApiErrorDetail}. */
|
|
2902
3000
|
type ApiErrorObject = ApiErrorDetail;
|
|
2903
3001
|
|
|
2904
|
-
export { AccountSettings, AccountSettingsClient, type ApiErrorDetail, type ApiErrorObject, BooleanFlag, Color, Config, ConfigChangeEvent, ConfigClient, ConfigEnvironment, ConfigItem, Context, ContextType, ContextTypesClient, ContextsClient, Environment, EnvironmentClassification, EnvironmentsClient, Flag, FlagChangeEvent, FlagDeclaration, FlagEnvironment, FlagRule, FlagStats, FlagValue, FlagsClient, ItemType, JsonFlag, LiveConfigProxy, LogGroup, LogLevel, Logger, LoggerChangeEvent, LoggerEnvironment, LoggerSource, type LoggingAdapter, LoggingClient, NumberFlag, Op, PinoAdapter, type PinoAdapterConfig, Rule, SharedWebSocket, SmplClient, type SmplClientOptions, SmplConflictError, SmplConnectionError, SmplError, SmplManagementClient, type SmplManagementClientOptions, SmplNotFoundError, SmplTimeoutError, SmplValidationError, SmplConflictError as SmplkitConflictError, SmplConnectionError as SmplkitConnectionError, SmplError as SmplkitError, SmplNotFoundError as SmplkitNotFoundError, SmplTimeoutError as SmplkitTimeoutError, SmplValidationError as SmplkitValidationError, StringFlag, WinstonAdapter, type WinstonAdapterConfig };
|
|
3002
|
+
export { AccountSettings, AccountSettingsClient, type ApiErrorDetail, type ApiErrorObject, AuditClient, type AuditEvent, type ListEventsPage as AuditEventListPage, type ListEventsParams as AuditEventListParams, BooleanFlag, Color, Config, ConfigChangeEvent, ConfigClient, ConfigEnvironment, ConfigItem, Context, ContextType, ContextTypesClient, ContextsClient, type CreateEventInput as CreateAuditEventInput, Environment, EnvironmentClassification, EnvironmentsClient, Flag, FlagChangeEvent, FlagDeclaration, FlagEnvironment, FlagRule, FlagStats, FlagValue, FlagsClient, ItemType, JsonFlag, LiveConfigProxy, LogGroup, LogLevel, Logger, LoggerChangeEvent, LoggerEnvironment, LoggerSource, type LoggingAdapter, LoggingClient, NumberFlag, Op, PinoAdapter, type PinoAdapterConfig, Rule, SharedWebSocket, SmplClient, type SmplClientOptions, SmplConflictError, SmplConnectionError, SmplError, SmplManagementClient, type SmplManagementClientOptions, SmplNotFoundError, SmplTimeoutError, SmplValidationError, SmplConflictError as SmplkitConflictError, SmplConnectionError as SmplkitConnectionError, SmplError as SmplkitError, SmplNotFoundError as SmplkitNotFoundError, SmplTimeoutError as SmplkitTimeoutError, SmplValidationError as SmplkitValidationError, StringFlag, WinstonAdapter, type WinstonAdapterConfig };
|
package/dist/index.js
CHANGED
|
@@ -16616,6 +16616,264 @@ var require_pino = __commonJS({
|
|
|
16616
16616
|
// src/client.ts
|
|
16617
16617
|
import createClient5 from "openapi-fetch";
|
|
16618
16618
|
|
|
16619
|
+
// src/audit/buffer.ts
|
|
16620
|
+
var MAX_BUFFER_SIZE = 1e3;
|
|
16621
|
+
var PERIODIC_FLUSH_INTERVAL_MS = 5e3;
|
|
16622
|
+
var HIGH_WATERMARK = 50;
|
|
16623
|
+
var MAX_ATTEMPTS_PER_ITEM = 5;
|
|
16624
|
+
var INITIAL_BACKOFF_MS = 250;
|
|
16625
|
+
var MAX_BACKOFF_MS = 8e3;
|
|
16626
|
+
var AuditEventBuffer = class {
|
|
16627
|
+
_queue = [];
|
|
16628
|
+
_post;
|
|
16629
|
+
_maxSize;
|
|
16630
|
+
_watermark;
|
|
16631
|
+
_flushTimer;
|
|
16632
|
+
_draining = false;
|
|
16633
|
+
_closed = false;
|
|
16634
|
+
_droppedCount = 0;
|
|
16635
|
+
constructor(opts) {
|
|
16636
|
+
this._post = opts.post;
|
|
16637
|
+
this._maxSize = opts.maxSize ?? MAX_BUFFER_SIZE;
|
|
16638
|
+
this._watermark = opts.watermark ?? HIGH_WATERMARK;
|
|
16639
|
+
const interval = opts.flushIntervalMs ?? PERIODIC_FLUSH_INTERVAL_MS;
|
|
16640
|
+
this._flushTimer = setInterval(() => {
|
|
16641
|
+
void this._drainOnce();
|
|
16642
|
+
}, interval);
|
|
16643
|
+
if (typeof this._flushTimer.unref === "function") {
|
|
16644
|
+
this._flushTimer.unref();
|
|
16645
|
+
}
|
|
16646
|
+
}
|
|
16647
|
+
/** Enqueue a new event. May evict the oldest queued item if full. */
|
|
16648
|
+
enqueue(body, idempotencyKey = null) {
|
|
16649
|
+
if (this._closed) return;
|
|
16650
|
+
if (this._queue.length >= this._maxSize) {
|
|
16651
|
+
this._queue.shift();
|
|
16652
|
+
this._droppedCount += 1;
|
|
16653
|
+
console.warn(
|
|
16654
|
+
`[smplkit.audit] buffer full (size=${this._maxSize}); dropped oldest event (total dropped=${this._droppedCount})`
|
|
16655
|
+
);
|
|
16656
|
+
}
|
|
16657
|
+
this._queue.push({ body, idempotencyKey, attempts: 0, nextRetryAt: 0 });
|
|
16658
|
+
if (this._queue.length >= this._watermark) {
|
|
16659
|
+
void this._drainOnce();
|
|
16660
|
+
}
|
|
16661
|
+
}
|
|
16662
|
+
/** Block (cooperatively) until the buffer is empty or `timeoutMs` elapses. */
|
|
16663
|
+
async flush(timeoutMs = 5e3) {
|
|
16664
|
+
const deadline = Date.now() + timeoutMs;
|
|
16665
|
+
while (this._queue.length > 0) {
|
|
16666
|
+
if (Date.now() >= deadline) {
|
|
16667
|
+
console.warn(`[smplkit.audit] flush timed out after ${timeoutMs}ms`);
|
|
16668
|
+
return;
|
|
16669
|
+
}
|
|
16670
|
+
void this._drainOnce();
|
|
16671
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
16672
|
+
}
|
|
16673
|
+
}
|
|
16674
|
+
/** Stop the periodic timer, drain best-effort, and mark closed. */
|
|
16675
|
+
async close(timeoutMs = 5e3) {
|
|
16676
|
+
this._closed = true;
|
|
16677
|
+
await this.flush(timeoutMs);
|
|
16678
|
+
if (this._flushTimer !== null) {
|
|
16679
|
+
clearInterval(this._flushTimer);
|
|
16680
|
+
this._flushTimer = null;
|
|
16681
|
+
}
|
|
16682
|
+
}
|
|
16683
|
+
async _drainOnce() {
|
|
16684
|
+
if (this._draining) return;
|
|
16685
|
+
this._draining = true;
|
|
16686
|
+
try {
|
|
16687
|
+
const now = Date.now();
|
|
16688
|
+
while (this._queue.length > 0) {
|
|
16689
|
+
const head = this._queue[0];
|
|
16690
|
+
if (head.nextRetryAt > now) break;
|
|
16691
|
+
this._queue.shift();
|
|
16692
|
+
let outcome;
|
|
16693
|
+
try {
|
|
16694
|
+
outcome = await this._post(head);
|
|
16695
|
+
} catch (err) {
|
|
16696
|
+
outcome = { status: 0 };
|
|
16697
|
+
}
|
|
16698
|
+
const requeued = this._handleOutcome(head, outcome);
|
|
16699
|
+
if (requeued !== null) {
|
|
16700
|
+
this._queue.unshift(requeued);
|
|
16701
|
+
break;
|
|
16702
|
+
}
|
|
16703
|
+
}
|
|
16704
|
+
} finally {
|
|
16705
|
+
this._draining = false;
|
|
16706
|
+
}
|
|
16707
|
+
}
|
|
16708
|
+
_handleOutcome(item, outcome) {
|
|
16709
|
+
if (outcome.status >= 200 && outcome.status < 300) return null;
|
|
16710
|
+
if (outcome.status >= 400 && outcome.status < 500 && outcome.status !== 429) {
|
|
16711
|
+
console.warn(`[smplkit.audit] permanent failure status=${outcome.status}; event dropped`);
|
|
16712
|
+
return null;
|
|
16713
|
+
}
|
|
16714
|
+
item.attempts += 1;
|
|
16715
|
+
if (item.attempts >= MAX_ATTEMPTS_PER_ITEM) {
|
|
16716
|
+
console.warn(
|
|
16717
|
+
`[smplkit.audit] gave up after ${item.attempts} attempts (last_status=${outcome.status})`
|
|
16718
|
+
);
|
|
16719
|
+
return null;
|
|
16720
|
+
}
|
|
16721
|
+
const backoff = Math.min(MAX_BACKOFF_MS, INITIAL_BACKOFF_MS * 2 ** (item.attempts - 1));
|
|
16722
|
+
const jitter = Math.random() * backoff * 0.25;
|
|
16723
|
+
item.nextRetryAt = Date.now() + backoff + jitter;
|
|
16724
|
+
return item;
|
|
16725
|
+
}
|
|
16726
|
+
};
|
|
16727
|
+
|
|
16728
|
+
// src/audit/client.ts
|
|
16729
|
+
var JSONAPI_HEADERS = {
|
|
16730
|
+
"Content-Type": "application/vnd.api+json",
|
|
16731
|
+
Accept: "application/vnd.api+json"
|
|
16732
|
+
};
|
|
16733
|
+
function _attributesFromInput(input) {
|
|
16734
|
+
const attrs = {
|
|
16735
|
+
action: input.action,
|
|
16736
|
+
resource_type: input.resourceType,
|
|
16737
|
+
resource_id: input.resourceId
|
|
16738
|
+
};
|
|
16739
|
+
if (input.occurredAt !== void 0) {
|
|
16740
|
+
const ts = input.occurredAt instanceof Date ? input.occurredAt.toISOString() : input.occurredAt;
|
|
16741
|
+
attrs.occurred_at = ts;
|
|
16742
|
+
}
|
|
16743
|
+
if (input.snapshot !== void 0) attrs.snapshot = input.snapshot;
|
|
16744
|
+
if (input.data !== void 0) attrs.data = input.data;
|
|
16745
|
+
return attrs;
|
|
16746
|
+
}
|
|
16747
|
+
function _eventFromResource(resource) {
|
|
16748
|
+
const attrs = resource.attributes;
|
|
16749
|
+
return {
|
|
16750
|
+
id: resource.id,
|
|
16751
|
+
action: String(attrs.action ?? ""),
|
|
16752
|
+
resourceType: String(attrs.resource_type ?? ""),
|
|
16753
|
+
resourceId: String(attrs.resource_id ?? ""),
|
|
16754
|
+
occurredAt: String(attrs.occurred_at ?? ""),
|
|
16755
|
+
createdAt: String(attrs.created_at ?? ""),
|
|
16756
|
+
actorType: String(attrs.actor_type ?? ""),
|
|
16757
|
+
actorId: attrs.actor_id ?? null,
|
|
16758
|
+
actorLabel: String(attrs.actor_label ?? ""),
|
|
16759
|
+
snapshot: attrs.snapshot ?? null,
|
|
16760
|
+
data: attrs.data ?? {},
|
|
16761
|
+
idempotencyKey: String(attrs.idempotency_key ?? "")
|
|
16762
|
+
};
|
|
16763
|
+
}
|
|
16764
|
+
var EventsClient = class {
|
|
16765
|
+
_apiKey;
|
|
16766
|
+
_baseUrl;
|
|
16767
|
+
_timeoutMs;
|
|
16768
|
+
_buffer;
|
|
16769
|
+
/** @internal */
|
|
16770
|
+
_fetch = fetch;
|
|
16771
|
+
constructor(opts) {
|
|
16772
|
+
this._apiKey = opts.apiKey;
|
|
16773
|
+
this._baseUrl = opts.baseUrl.replace(/\/$/, "");
|
|
16774
|
+
this._timeoutMs = opts.timeoutMs ?? 1e4;
|
|
16775
|
+
this._buffer = new AuditEventBuffer({
|
|
16776
|
+
post: async (item) => {
|
|
16777
|
+
try {
|
|
16778
|
+
const headers = {
|
|
16779
|
+
...JSONAPI_HEADERS,
|
|
16780
|
+
Authorization: `Bearer ${this._apiKey}`
|
|
16781
|
+
};
|
|
16782
|
+
if (item.idempotencyKey !== null) headers["Idempotency-Key"] = item.idempotencyKey;
|
|
16783
|
+
const ctrl = new AbortController();
|
|
16784
|
+
const t = setTimeout(() => ctrl.abort(), this._timeoutMs);
|
|
16785
|
+
try {
|
|
16786
|
+
const resp = await this._fetch(`${this._baseUrl}/api/v1/events`, {
|
|
16787
|
+
method: "POST",
|
|
16788
|
+
headers,
|
|
16789
|
+
body: JSON.stringify(item.body),
|
|
16790
|
+
signal: ctrl.signal
|
|
16791
|
+
});
|
|
16792
|
+
return { status: resp.status };
|
|
16793
|
+
} finally {
|
|
16794
|
+
clearTimeout(t);
|
|
16795
|
+
}
|
|
16796
|
+
} catch {
|
|
16797
|
+
return { status: 0 };
|
|
16798
|
+
}
|
|
16799
|
+
}
|
|
16800
|
+
});
|
|
16801
|
+
}
|
|
16802
|
+
/**
|
|
16803
|
+
* Enqueue an audit event for asynchronous delivery.
|
|
16804
|
+
* Returns immediately. The actual POST happens on the buffer worker.
|
|
16805
|
+
*
|
|
16806
|
+
* Customers may not emit `smpl.*` resource types — the server will
|
|
16807
|
+
* reject those with a 403 (the buffer logs and drops permanent
|
|
16808
|
+
* failures, so a misuse will silently disappear from the queue).
|
|
16809
|
+
*/
|
|
16810
|
+
create(input) {
|
|
16811
|
+
const body = {
|
|
16812
|
+
data: { type: "event", attributes: _attributesFromInput(input) }
|
|
16813
|
+
};
|
|
16814
|
+
this._buffer.enqueue(body, input.idempotencyKey ?? null);
|
|
16815
|
+
}
|
|
16816
|
+
async list(params = {}) {
|
|
16817
|
+
const qs = new URLSearchParams();
|
|
16818
|
+
if (params.action !== void 0) qs.set("filter[action]", params.action);
|
|
16819
|
+
if (params.resourceType !== void 0) qs.set("filter[resource_type]", params.resourceType);
|
|
16820
|
+
if (params.resourceId !== void 0) qs.set("filter[resource_id]", params.resourceId);
|
|
16821
|
+
if (params.actorType !== void 0) qs.set("filter[actor_type]", params.actorType);
|
|
16822
|
+
if (params.actorId !== void 0) qs.set("filter[actor_id]", params.actorId);
|
|
16823
|
+
if (params.occurredAtRange !== void 0) qs.set("filter[occurred_at]", params.occurredAtRange);
|
|
16824
|
+
if (params.pageSize !== void 0) qs.set("page[size]", String(params.pageSize));
|
|
16825
|
+
if (params.pageAfter !== void 0) qs.set("page[after]", params.pageAfter);
|
|
16826
|
+
const resp = await this._fetch(`${this._baseUrl}/api/v1/events?${qs.toString()}`, {
|
|
16827
|
+
headers: {
|
|
16828
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
16829
|
+
Accept: "application/vnd.api+json"
|
|
16830
|
+
}
|
|
16831
|
+
});
|
|
16832
|
+
if (!resp.ok) {
|
|
16833
|
+
throw new Error(`audit list failed: ${resp.status} ${resp.statusText}`);
|
|
16834
|
+
}
|
|
16835
|
+
const body = await resp.json();
|
|
16836
|
+
const events = (body.data ?? []).map(_eventFromResource);
|
|
16837
|
+
let nextCursor = null;
|
|
16838
|
+
const nextLink = body.links?.next;
|
|
16839
|
+
if (typeof nextLink === "string" && nextLink.includes("page[after]=")) {
|
|
16840
|
+
nextCursor = nextLink.split("page[after]=")[1];
|
|
16841
|
+
}
|
|
16842
|
+
return { events, nextCursor };
|
|
16843
|
+
}
|
|
16844
|
+
async get(eventId) {
|
|
16845
|
+
const resp = await this._fetch(`${this._baseUrl}/api/v1/events/${eventId}`, {
|
|
16846
|
+
headers: {
|
|
16847
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
16848
|
+
Accept: "application/vnd.api+json"
|
|
16849
|
+
}
|
|
16850
|
+
});
|
|
16851
|
+
if (!resp.ok) {
|
|
16852
|
+
throw new Error(`audit get failed: ${resp.status} ${resp.statusText}`);
|
|
16853
|
+
}
|
|
16854
|
+
const body = await resp.json();
|
|
16855
|
+
return _eventFromResource(body.data);
|
|
16856
|
+
}
|
|
16857
|
+
/** Block until the in-memory buffer is drained or `timeoutMs` elapses. */
|
|
16858
|
+
async flush(timeoutMs = 5e3) {
|
|
16859
|
+
await this._buffer.flush(timeoutMs);
|
|
16860
|
+
}
|
|
16861
|
+
/** @internal */
|
|
16862
|
+
async _close() {
|
|
16863
|
+
await this._buffer.close();
|
|
16864
|
+
}
|
|
16865
|
+
};
|
|
16866
|
+
var AuditClient = class {
|
|
16867
|
+
events;
|
|
16868
|
+
constructor(opts) {
|
|
16869
|
+
this.events = new EventsClient(opts);
|
|
16870
|
+
}
|
|
16871
|
+
/** @internal */
|
|
16872
|
+
async _close() {
|
|
16873
|
+
await this.events._close();
|
|
16874
|
+
}
|
|
16875
|
+
};
|
|
16876
|
+
|
|
16619
16877
|
// src/config/client.ts
|
|
16620
16878
|
import createClient from "openapi-fetch";
|
|
16621
16879
|
|
|
@@ -21892,6 +22150,8 @@ var SmplClient = class {
|
|
|
21892
22150
|
flags;
|
|
21893
22151
|
/** Client for logging management and runtime. */
|
|
21894
22152
|
logging;
|
|
22153
|
+
/** Client for the audit service — fire-and-forget event recording (ADR-047). */
|
|
22154
|
+
audit;
|
|
21895
22155
|
/**
|
|
21896
22156
|
* Standalone management/CRUD entry point — mirrors Python's
|
|
21897
22157
|
* `client.manage`. Construction is side-effect-free; safe to use even
|
|
@@ -21922,6 +22182,7 @@ var SmplClient = class {
|
|
|
21922
22182
|
const configBaseUrl = serviceUrl(cfg.scheme, "config", cfg.baseDomain);
|
|
21923
22183
|
const flagsBaseUrl = serviceUrl(cfg.scheme, "flags", cfg.baseDomain);
|
|
21924
22184
|
const loggingBaseUrl = serviceUrl(cfg.scheme, "logging", cfg.baseDomain);
|
|
22185
|
+
const auditBaseUrl = serviceUrl(cfg.scheme, "audit", cfg.baseDomain);
|
|
21925
22186
|
this._appBaseUrl = appBaseUrl;
|
|
21926
22187
|
const maskedKey = cfg.apiKey.length > 14 ? cfg.apiKey.slice(0, 10) + "..." + cfg.apiKey.slice(-4) : cfg.apiKey.slice(0, Math.min(4, cfg.apiKey.length)) + "...";
|
|
21927
22188
|
debug(
|
|
@@ -21964,6 +22225,11 @@ var SmplClient = class {
|
|
|
21964
22225
|
this._timeout,
|
|
21965
22226
|
loggingBaseUrl
|
|
21966
22227
|
);
|
|
22228
|
+
this.audit = new AuditClient({
|
|
22229
|
+
apiKey: cfg.apiKey,
|
|
22230
|
+
baseUrl: auditBaseUrl,
|
|
22231
|
+
timeoutMs: this._timeout
|
|
22232
|
+
});
|
|
21967
22233
|
this.config._getSharedWs = () => this._ensureWs();
|
|
21968
22234
|
this.flags._parent = this;
|
|
21969
22235
|
this.config._parent = this;
|
|
@@ -22043,6 +22309,7 @@ var SmplClient = class {
|
|
|
22043
22309
|
}
|
|
22044
22310
|
this.flags._close();
|
|
22045
22311
|
this.logging._close();
|
|
22312
|
+
void this.audit._close();
|
|
22046
22313
|
if (this._wsManager !== null) {
|
|
22047
22314
|
this._wsManager.stop();
|
|
22048
22315
|
this._wsManager = null;
|
|
@@ -22276,6 +22543,7 @@ var PinoAdapter = class {
|
|
|
22276
22543
|
export {
|
|
22277
22544
|
AccountSettings,
|
|
22278
22545
|
AccountSettingsClient,
|
|
22546
|
+
AuditClient,
|
|
22279
22547
|
BooleanFlag,
|
|
22280
22548
|
Color,
|
|
22281
22549
|
Config,
|