@observa/sdk 2.4.0 → 2.4.5

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.
Files changed (79) hide show
  1. package/README.md +140 -176
  2. package/dist/apis/ingestApi.d.ts +1 -15
  3. package/dist/apis/ingestApi.d.ts.map +1 -1
  4. package/dist/apis/ingestApi.js +2 -151
  5. package/dist/apis/ingestApi.js.map +1 -1
  6. package/dist/apis/uptimeApi.js +1 -1
  7. package/dist/apis/uptimeApi.js.map +1 -1
  8. package/dist/domain/ingest.d.ts +4 -33
  9. package/dist/domain/ingest.d.ts.map +1 -1
  10. package/dist/http/httpClient.d.ts +5 -2
  11. package/dist/http/httpClient.d.ts.map +1 -1
  12. package/dist/http/httpClient.js +44 -8
  13. package/dist/http/httpClient.js.map +1 -1
  14. package/dist/index.d.ts +18 -507
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +17 -582
  17. package/dist/index.js.map +1 -1
  18. package/dist/sdk.d.ts +4 -9
  19. package/dist/sdk.d.ts.map +1 -1
  20. package/dist/sdk.js +7 -19
  21. package/dist/sdk.js.map +1 -1
  22. package/package.json +9 -16
  23. package/dist/src/apis/ingestApi.d.ts +0 -22
  24. package/dist/src/apis/ingestApi.js +0 -167
  25. package/dist/src/apis/ingestApi.js.map +0 -1
  26. package/dist/src/apis/uptimeApi.d.ts +0 -11
  27. package/dist/src/apis/uptimeApi.js +0 -39
  28. package/dist/src/apis/uptimeApi.js.map +0 -1
  29. package/dist/src/domain/ingest.d.ts +0 -47
  30. package/dist/src/domain/ingest.js +0 -2
  31. package/dist/src/domain/ingest.js.map +0 -1
  32. package/dist/src/domain/uptime.d.ts +0 -23
  33. package/dist/src/domain/uptime.js +0 -2
  34. package/dist/src/domain/uptime.js.map +0 -1
  35. package/dist/src/http/errors.d.ts +0 -33
  36. package/dist/src/http/errors.js +0 -54
  37. package/dist/src/http/errors.js.map +0 -1
  38. package/dist/src/http/httpClient.d.ts +0 -45
  39. package/dist/src/http/httpClient.js +0 -165
  40. package/dist/src/http/httpClient.js.map +0 -1
  41. package/dist/src/index.d.ts +0 -12
  42. package/dist/src/index.js +0 -7
  43. package/dist/src/index.js.map +0 -1
  44. package/dist/src/sdk.d.ts +0 -23
  45. package/dist/src/sdk.js +0 -40
  46. package/dist/src/sdk.js.map +0 -1
  47. package/dist/src/utils/processContext.d.ts +0 -34
  48. package/dist/src/utils/processContext.js +0 -47
  49. package/dist/src/utils/processContext.js.map +0 -1
  50. package/dist/src/utils/validate.d.ts +0 -2
  51. package/dist/src/utils/validate.js +0 -12
  52. package/dist/src/utils/validate.js.map +0 -1
  53. package/dist/tests/httpClient.test.d.ts +0 -1
  54. package/dist/tests/httpClient.test.js +0 -47
  55. package/dist/tests/httpClient.test.js.map +0 -1
  56. package/dist/tests/ingestApi.test.d.ts +0 -1
  57. package/dist/tests/ingestApi.test.js +0 -65
  58. package/dist/tests/ingestApi.test.js.map +0 -1
  59. package/dist/tests/observaSdk.integration.test.d.ts +0 -1
  60. package/dist/tests/observaSdk.integration.test.js +0 -51
  61. package/dist/tests/observaSdk.integration.test.js.map +0 -1
  62. package/dist/tests/sdk.test.d.ts +0 -1
  63. package/dist/tests/sdk.test.js +0 -104
  64. package/dist/tests/sdk.test.js.map +0 -1
  65. package/dist/tsconfig.build.tsbuildinfo +0 -1
  66. package/dist/utils/processContext.d.ts +0 -35
  67. package/dist/utils/processContext.d.ts.map +0 -1
  68. package/dist/utils/processContext.js +0 -47
  69. package/dist/utils/processContext.js.map +0 -1
  70. package/src/apis/ingestApi.ts +0 -199
  71. package/src/apis/uptimeApi.ts +0 -58
  72. package/src/domain/ingest.ts +0 -93
  73. package/src/domain/uptime.ts +0 -86
  74. package/src/http/errors.ts +0 -88
  75. package/src/http/httpClient.ts +0 -284
  76. package/src/index.ts +0 -68
  77. package/src/sdk.ts +0 -107
  78. package/src/utils/processContext.ts +0 -84
  79. package/src/utils/validate.ts +0 -19
@@ -1,199 +0,0 @@
1
- import { randomUUID } from 'crypto'
2
- import type { IngestEvent, IngestRequest, IngestResponse, StacktraceFrame } from '../domain/ingest'
3
- import { ValidationError } from '../http/errors'
4
- import { getProcessContextDynamic, getProcessContextStatic } from '../utils/processContext'
5
- import { ensureDefined, ensureNonEmpty } from '../utils/validate'
6
- import type { HttpClient } from '../http/httpClient'
7
-
8
- export type IngestNormalizationOptions = {
9
- schemaVersion?: number
10
- includeContext?: boolean
11
- includeSystemContext?: boolean
12
- includeRuntimeContext?: boolean
13
- maxEventBytes?: number
14
- maxFrames?: number
15
- maxMessageLength?: number
16
- maxExceptionValueLength?: number
17
- }
18
-
19
- const DEFAULT_NORMALIZATION: Required<IngestNormalizationOptions> = {
20
- schemaVersion: 1,
21
- includeContext: true,
22
- includeSystemContext: true,
23
- includeRuntimeContext: true,
24
- maxEventBytes: 64 * 1024,
25
- maxFrames: 60,
26
- maxMessageLength: 4000,
27
- maxExceptionValueLength: 4000,
28
- }
29
-
30
- /**
31
- * Event ingestion API.
32
- */
33
- export class IngestApi {
34
- /**
35
- * Creates the ingestion client with an optional default DSN and PublicKey.
36
- */
37
- constructor(
38
- private readonly http: HttpClient,
39
- private readonly defaultDsnKey?: string,
40
- private readonly normalization?: IngestNormalizationOptions,
41
- private readonly defaultPublicKey?: string
42
- ) { }
43
-
44
- /**
45
- * Sends an event to the ingestion backend.
46
- */
47
- async event(input: IngestRequest): Promise<IngestResponse> {
48
- ensureDefined(input, 'input')
49
- const dsnKey = input.dsnKey ?? this.defaultDsnKey
50
- const publicKey = input.publicKey ?? this.defaultPublicKey
51
-
52
- ensureDefined(dsnKey, 'dsnKey')
53
- ensureNonEmpty(dsnKey, 'dsnKey')
54
- ensureDefined(input.event, 'event')
55
-
56
- if (!this.http.hasApiKey() && !publicKey) {
57
- throw new ValidationError('publicKey is required when apiKey is not provided')
58
- }
59
-
60
- if (input.idempotencyKey && input.idempotencyKey.length > 128) {
61
- throw new ValidationError('idempotencyKey must be at most 128 characters')
62
- }
63
- const headers: Record<string, string> = {}
64
- if (input.idempotencyKey) headers['x-idempotency-key'] = input.idempotencyKey
65
- if (input.sdkVersion) headers['x-sdk-version'] = input.sdkVersion
66
- const { idempotencyKey, sdkVersion, event, ...body } = input
67
- const normalizedEvent = normalizeEvent(event, this.normalization)
68
-
69
- const authMode = this.http.hasApiKey() ? 'apiKey' : 'none'
70
- return this.http.post<IngestResponse>('/ingest/events', { ...body, dsnKey, publicKey, event: normalizedEvent }, { auth: authMode, headers })
71
- }
72
-
73
- async health(dsnKey?: string, publicKey?: string): Promise<{ ok: boolean }> {
74
- const resolvedDsnKey = dsnKey ?? this.defaultDsnKey
75
- const resolvedPublicKey = publicKey ?? this.defaultPublicKey
76
-
77
- ensureDefined(resolvedDsnKey, 'dsnKey')
78
- ensureNonEmpty(resolvedDsnKey, 'dsnKey')
79
-
80
- const authMode = this.http.hasApiKey() ? 'apiKey' : 'none'
81
- return this.http.post<{ ok: boolean }>('/ingest/health', { dsnKey: resolvedDsnKey, publicKey: resolvedPublicKey }, { auth: authMode })
82
- }
83
- }
84
-
85
- function normalizeEvent(event: IngestEvent, options?: IngestNormalizationOptions): IngestEvent {
86
- const config = { ...DEFAULT_NORMALIZATION, ...options }
87
- const normalizedTimestamp = normalizeTimestamp(event.timestamp)
88
- const normalizedLevel = event.level ? event.level.toLowerCase() : undefined
89
- const normalizedMessage = event.message ? truncate(event.message, config.maxMessageLength) : undefined
90
- const normalizedException = normalizeException(event.exception, config)
91
- if (!normalizedMessage && !normalizedException) {
92
- throw new ValidationError('event message or exception is required')
93
- }
94
- const normalizedContext = normalizeContext(event.context, config)
95
- const normalizedEvent: IngestEvent = {
96
- ...event,
97
- event_id: event.event_id ?? randomUUID(),
98
- timestamp: normalizedTimestamp,
99
- schema_version: event.schema_version ?? config.schemaVersion,
100
- level: normalizedLevel as IngestEvent['level'],
101
- message: normalizedMessage,
102
- exception: normalizedException,
103
- context: normalizedContext,
104
- }
105
- return enforceSizeLimit(normalizedEvent, config)
106
- }
107
-
108
- function normalizeTimestamp(timestamp?: string) {
109
- if (!timestamp) return new Date().toISOString()
110
- const parsed = new Date(timestamp)
111
- if (Number.isNaN(parsed.getTime())) {
112
- throw new ValidationError('timestamp must be a valid ISO date')
113
- }
114
- return parsed.toISOString()
115
- }
116
-
117
- function normalizeException(exception: IngestEvent['exception'], config: Required<IngestNormalizationOptions>) {
118
- if (!exception) return undefined
119
- if (!exception.type || !exception.value) {
120
- throw new ValidationError('exception.type and exception.value are required')
121
- }
122
- return {
123
- ...exception,
124
- value: truncate(exception.value, config.maxExceptionValueLength),
125
- stacktrace: normalizeStacktrace(exception.stacktrace, config.maxFrames),
126
- }
127
- }
128
-
129
- function normalizeStacktrace(stacktrace: { frames?: StacktraceFrame[] } | undefined, maxFrames: number) {
130
- if (!stacktrace || !Array.isArray(stacktrace.frames)) return undefined
131
- const frames = stacktrace.frames
132
- const normalizedFrames = frames.slice(0, maxFrames).map((frame) => {
133
- const filename = typeof frame?.filename === 'string' ? frame.filename : undefined
134
- const functionName = typeof frame?.function === 'string' ? frame.function : undefined
135
- const lineno = typeof frame?.lineno === 'number' ? frame.lineno : undefined
136
- const colno = typeof frame?.colno === 'number' ? frame.colno : undefined
137
- const inferredInApp = filename ? !filename.includes('node_modules') : false
138
- return {
139
- filename,
140
- function: functionName,
141
- lineno,
142
- colno,
143
- in_app: typeof frame?.in_app === 'boolean' ? frame.in_app : inferredInApp,
144
- }
145
- })
146
- return { frames: normalizedFrames }
147
- }
148
-
149
- function normalizeContext(context: IngestEvent['context'] | undefined, config: Required<IngestNormalizationOptions>) {
150
- if (!config.includeContext) return context
151
- const systemContext = config.includeSystemContext ? getProcessContextDynamic() : undefined
152
- const runtimeContext = config.includeRuntimeContext ? getProcessContextStatic({ includeVersions: false }) : undefined
153
- const mergedContext = {
154
- ...context,
155
- system: context?.system ?? systemContext,
156
- runtime: context?.runtime ?? runtimeContext,
157
- }
158
- return mergedContext
159
- }
160
-
161
- function enforceSizeLimit(event: IngestEvent, config: Required<IngestNormalizationOptions>) {
162
- let normalized = event
163
- if (getSize(normalized) <= config.maxEventBytes) return normalized
164
- if (normalized.extra) {
165
- normalized = { ...normalized, extra: undefined }
166
- }
167
- if (getSize(normalized) <= config.maxEventBytes) return normalized
168
- if (normalized.tags) {
169
- normalized = { ...normalized, tags: undefined }
170
- }
171
- if (getSize(normalized) <= config.maxEventBytes) return normalized
172
- if (normalized.exception?.stacktrace) {
173
- normalized = {
174
- ...normalized,
175
- exception: { ...normalized.exception, stacktrace: undefined },
176
- }
177
- }
178
- if (getSize(normalized) <= config.maxEventBytes) return normalized
179
- if (normalized.message) {
180
- normalized = { ...normalized, message: truncate(normalized.message, config.maxMessageLength) }
181
- }
182
- if (normalized.exception?.value) {
183
- normalized = {
184
- ...normalized,
185
- exception: { ...normalized.exception, value: truncate(normalized.exception.value, config.maxExceptionValueLength) },
186
- }
187
- }
188
- if (getSize(normalized) <= config.maxEventBytes) return normalized
189
- throw new ValidationError('event payload exceeds size limit')
190
- }
191
-
192
- function getSize(value: unknown) {
193
- return Buffer.byteLength(JSON.stringify(value), 'utf8')
194
- }
195
-
196
- function truncate(value: string, maxLength: number) {
197
- if (value.length <= maxLength) return value
198
- return value.slice(0, maxLength)
199
- }
@@ -1,58 +0,0 @@
1
- import type { UptimeEvent, UptimeHeartbeatInput, UptimeSummary } from '../domain/uptime'
2
- import { ensureDefined, ensureNonEmpty } from '../utils/validate'
3
- import type { HttpClient } from '../http/httpClient'
4
-
5
- /**
6
- * Uptime API for heartbeats and public reads.
7
- */
8
- export class UptimeApi {
9
- /**
10
- * Creates the uptime client with an optional default DSN.
11
- */
12
- constructor(private readonly http: HttpClient, private readonly defaultDsnKey?: string) { }
13
-
14
- /**
15
- * Records an uptime heartbeat.
16
- */
17
- async recordHeartbeat(input: UptimeHeartbeatInput): Promise<UptimeEvent> {
18
- ensureDefined(input, 'input')
19
- const dsnKey = input.dsnKey ?? this.defaultDsnKey
20
- ensureDefined(dsnKey, 'dsnKey')
21
- ensureNonEmpty(dsnKey, 'dsnKey')
22
- ensureNonEmpty(input.status, 'status')
23
- return this.http.post<UptimeEvent>('/uptime/heartbeats', { ...input, dsnKey }, { auth: 'apiKey' })
24
- }
25
-
26
- /**
27
- * Lists a project's daily uptime history.
28
- */
29
- async history(projectId: string, date: string): Promise<UptimeEvent[]> {
30
- ensureNonEmpty(projectId, 'projectId')
31
- ensureNonEmpty(date, 'date')
32
- return this.http.get<UptimeEvent[]>(`/projects/${encodeURIComponent(projectId)}/uptime/history`, {
33
- query: { date },
34
- auth: 'none',
35
- })
36
- }
37
-
38
- /**
39
- * Gets the latest uptime event for a project.
40
- */
41
- async latest(projectId: string): Promise<UptimeEvent | null> {
42
- ensureNonEmpty(projectId, 'projectId')
43
- return this.http.get<UptimeEvent | null>(`/projects/${encodeURIComponent(projectId)}/uptime/latest`, {
44
- auth: 'none',
45
- })
46
- }
47
-
48
- /**
49
- * Summarizes daily uptime.
50
- */
51
- async summary(projectId: string, days?: number, delayThresholdMinutes?: number): Promise<UptimeSummary[]> {
52
- ensureNonEmpty(projectId, 'projectId')
53
- return this.http.get<UptimeSummary[]>(`/projects/${encodeURIComponent(projectId)}/uptime/summary`, {
54
- query: { days, delayThresholdMinutes },
55
- auth: 'none',
56
- })
57
- }
58
- }
@@ -1,93 +0,0 @@
1
- import type { ProcessContextDynamic, ProcessContextStatic } from '../utils/processContext'
2
-
3
- /**
4
- * Severity levels for events.
5
- */
6
- export type IngestLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal'
7
-
8
- export type StacktraceFrame = {
9
- filename?: string
10
- function?: string
11
- lineno?: number
12
- colno?: number
13
- in_app?: boolean
14
- }
15
-
16
- export type Stacktrace = {
17
- frames: StacktraceFrame[]
18
- }
19
-
20
- export type IngestException = {
21
- type: string
22
- value: string
23
- stacktrace?: Stacktrace
24
- }
25
-
26
- export type RequestContext = {
27
- requestId?: string
28
- userId?: string
29
- [key: string]: unknown
30
- }
31
-
32
- export type IngestEventContext = {
33
- system?: ProcessContextDynamic
34
- runtime?: ProcessContextStatic
35
- request?: RequestContext
36
- }
37
-
38
- /**
39
- * Ingestion event.
40
- */
41
- export type IngestEvent = {
42
- /**
43
- * Unique event identifier.
44
- */
45
- event_id?: string
46
- /**
47
- * Event ISO timestamp.
48
- */
49
- timestamp?: string
50
- schema_version?: number
51
- /**
52
- * Severity level.
53
- */
54
- level?: IngestLevel
55
- /**
56
- * Main message.
57
- */
58
- message?: string
59
- exception?: IngestException
60
- context?: IngestEventContext
61
- tags?: Record<string, string>
62
- extra?: Record<string, unknown>
63
- }
64
-
65
- /**
66
- * Payload for sending an event to the backend.
67
- */
68
- export type IngestRequest = {
69
- /**
70
- * Project DSN. Uses the SDK default when omitted.
71
- */
72
- dsnKey?: string
73
- /**
74
- * Public key for frontend/mobile projects.
75
- */
76
- publicKey?: string
77
- /**
78
- * Event to record.
79
- */
80
- event: IngestEvent
81
- idempotencyKey?: string
82
- sdkVersion?: string
83
- }
84
-
85
- /**
86
- * Response after sending an event.
87
- */
88
- export type IngestResponse = {
89
- /**
90
- * Assigned event identifier.
91
- */
92
- event_id: string
93
- }
@@ -1,86 +0,0 @@
1
- /**
2
- * Possible uptime statuses.
3
- */
4
- export type UptimeStatus = 'up' | 'down' | 'degraded'
5
-
6
- /**
7
- * Recorded uptime event.
8
- */
9
- export type UptimeEvent = {
10
- /**
11
- * Event identifier.
12
- */
13
- id: string
14
- /**
15
- * Project the event belongs to.
16
- */
17
- projectId: string
18
- /**
19
- * Uptime status.
20
- */
21
- status: UptimeStatus
22
- /**
23
- * Optional message for the event.
24
- */
25
- message?: string
26
- /**
27
- * Response time in milliseconds.
28
- */
29
- responseTimeMs?: number
30
- /**
31
- * ISO timestamp of the check.
32
- */
33
- checkedAt?: string
34
- /**
35
- * ISO timestamp of backend creation.
36
- */
37
- createdAt?: string
38
- }
39
-
40
- /**
41
- * Payload for sending an uptime heartbeat.
42
- */
43
- export type UptimeHeartbeatInput = {
44
- /**
45
- * Project DSN. Uses the SDK default when omitted.
46
- */
47
- dsnKey?: string
48
- /**
49
- * Reported status.
50
- */
51
- status: UptimeStatus
52
- /**
53
- * Response time in milliseconds.
54
- */
55
- responseTimeMs?: number
56
- /**
57
- * ISO timestamp of the check.
58
- */
59
- checkedAt?: string
60
- /**
61
- * Optional message.
62
- */
63
- message?: string
64
- }
65
-
66
- /**
67
- * Daily uptime summary.
68
- */
69
- export type UptimeSummary = {
70
- /**
71
- * ISO date of the summary.
72
- */
73
- date: string
74
- /**
75
- * Hours without data.
76
- */
77
- missingHours?: number
78
- /**
79
- * Accumulated delay minutes.
80
- */
81
- delayMinutes?: number
82
- /**
83
- * Number of delayed events.
84
- */
85
- delayedCount?: number
86
- }
@@ -1,88 +0,0 @@
1
- /**
2
- * Additional data to enrich SDK errors.
3
- */
4
- export type ErrorDetails = {
5
- status?: number
6
- code?: string
7
- details?: unknown
8
- retryAfter?: number
9
- }
10
-
11
- /**
12
- * Base SDK error.
13
- */
14
- export class SdkError extends Error {
15
- readonly status?: number
16
- readonly code?: string
17
- readonly details?: unknown
18
-
19
- /**
20
- * Creates an error with optional metadata.
21
- */
22
- constructor(message: string, details?: ErrorDetails) {
23
- super(message)
24
- this.name = new.target.name
25
- this.status = details?.status
26
- this.code = details?.code
27
- this.details = details?.details
28
- }
29
- }
30
-
31
- /**
32
- * Input validation error.
33
- */
34
- export class ValidationError extends SdkError { }
35
- /**
36
- * Authentication error.
37
- */
38
- export class AuthError extends SdkError { }
39
- /**
40
- * Error caused by insufficient permissions.
41
- */
42
- export class ForbiddenError extends SdkError { }
43
- /**
44
- * Error when a resource does not exist.
45
- */
46
- export class NotFoundError extends SdkError { }
47
- /**
48
- * Error caused by a state conflict.
49
- */
50
- export class ConflictError extends SdkError { }
51
- /**
52
- * Error caused by rate limiting.
53
- */
54
- export class RateLimitError extends SdkError {
55
- readonly retryAfter?: number
56
-
57
- constructor(message: string, details?: ErrorDetails) {
58
- super(message, details)
59
- this.retryAfter = details?.retryAfter
60
- }
61
- }
62
- /**
63
- * Server-side error.
64
- */
65
- export class ServerError extends SdkError { }
66
- /**
67
- * Network error.
68
- */
69
- export class NetworkError extends SdkError { }
70
- /**
71
- * Request timeout error.
72
- */
73
- export class TimeoutError extends SdkError { }
74
-
75
- /**
76
- * Maps HTTP status codes to typed SDK errors.
77
- */
78
- export function mapHttpError(status: number, details?: unknown, retryAfter?: number): SdkError {
79
- const info = { status, details, retryAfter }
80
- if (status === 400) return new ValidationError('Invalid request', info)
81
- if (status === 401) return new AuthError('Unauthorized', info)
82
- if (status === 403) return new ForbiddenError('Forbidden', info)
83
- if (status === 404) return new NotFoundError('Resource not found', info)
84
- if (status === 409) return new ConflictError('Conflict', info)
85
- if (status === 429) return new RateLimitError('Rate limit exceeded', info)
86
- if (status >= 500) return new ServerError('Server error', info)
87
- return new SdkError('HTTP error', info)
88
- }