@fabrika/operations-contract 0.0.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/package.json +64 -0
- package/src/access.ts +16 -0
- package/src/catalog.ts +103 -0
- package/src/index.ts +120 -0
- package/src/ingest.ts +130 -0
- package/src/operator-api.ts +250 -0
- package/src/operator.ts +123 -0
- package/src/releases.ts +118 -0
- package/src/rpc.ts +86 -0
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fabrika/operations-contract",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/contember/fabrika-platform.git",
|
|
8
|
+
"directory": "packages/operations-contract"
|
|
9
|
+
},
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"src"
|
|
15
|
+
],
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"bun": "./src/index.ts",
|
|
19
|
+
"types": "./src/index.ts",
|
|
20
|
+
"default": "./src/index.ts"
|
|
21
|
+
},
|
|
22
|
+
"./ingest": {
|
|
23
|
+
"bun": "./src/ingest.ts",
|
|
24
|
+
"types": "./src/ingest.ts",
|
|
25
|
+
"default": "./src/ingest.ts"
|
|
26
|
+
},
|
|
27
|
+
"./operator": {
|
|
28
|
+
"bun": "./src/operator.ts",
|
|
29
|
+
"types": "./src/operator.ts",
|
|
30
|
+
"default": "./src/operator.ts"
|
|
31
|
+
},
|
|
32
|
+
"./operator-api": {
|
|
33
|
+
"bun": "./src/operator-api.ts",
|
|
34
|
+
"types": "./src/operator-api.ts",
|
|
35
|
+
"default": "./src/operator-api.ts"
|
|
36
|
+
},
|
|
37
|
+
"./access": {
|
|
38
|
+
"bun": "./src/access.ts",
|
|
39
|
+
"types": "./src/access.ts",
|
|
40
|
+
"default": "./src/access.ts"
|
|
41
|
+
},
|
|
42
|
+
"./catalog": {
|
|
43
|
+
"bun": "./src/catalog.ts",
|
|
44
|
+
"types": "./src/catalog.ts",
|
|
45
|
+
"default": "./src/catalog.ts"
|
|
46
|
+
},
|
|
47
|
+
"./releases": {
|
|
48
|
+
"bun": "./src/releases.ts",
|
|
49
|
+
"types": "./src/releases.ts",
|
|
50
|
+
"default": "./src/releases.ts"
|
|
51
|
+
},
|
|
52
|
+
"./rpc": {
|
|
53
|
+
"bun": "./src/rpc.ts",
|
|
54
|
+
"types": "./src/rpc.ts",
|
|
55
|
+
"default": "./src/rpc.ts"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"typecheck": "tsc --noEmit"
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"@fabrika/app": "^0.0.1"
|
|
63
|
+
}
|
|
64
|
+
}
|
package/src/access.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** IAM vocabulary shared by the Operations runtime and Fabrika's Access schema. */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The app id every composition names the Operations HOST by — the Cloudflare proxy Worker's manifest,
|
|
5
|
+
* the shared platform manifest, and `defineApp`. One constant because a shared manifest may not name
|
|
6
|
+
* one app twice, so the two compositions cannot disagree here and stay generalisable.
|
|
7
|
+
*/
|
|
8
|
+
export const OPERATIONS_APP_ID = 'operations'
|
|
9
|
+
|
|
10
|
+
export const OPERATIONS_ACTIONS = {
|
|
11
|
+
READ: 'operations.read',
|
|
12
|
+
TRIAGE: 'operations.triage',
|
|
13
|
+
MANAGE: 'operations.manage',
|
|
14
|
+
} as const
|
|
15
|
+
|
|
16
|
+
export type OperationsAction = (typeof OPERATIONS_ACTIONS)[keyof typeof OPERATIONS_ACTIONS]
|
package/src/catalog.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/** Versioned private Control → Operations source-catalog protocol. */
|
|
2
|
+
|
|
3
|
+
export const OPERATIONS_CATALOG_PROTOCOL_VERSION = 2
|
|
4
|
+
export const DEFAULT_OPERATIONS_SERVICE_KEY = 'default'
|
|
5
|
+
|
|
6
|
+
export interface OperationsSourceCoordinateV1 {
|
|
7
|
+
appId: string
|
|
8
|
+
environment: string
|
|
9
|
+
/** Omitted by Control while an app environment has only its default service. */
|
|
10
|
+
serviceKey?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface CanonicalOperationsSourceCoordinateV1 {
|
|
14
|
+
appId: string
|
|
15
|
+
environment: string
|
|
16
|
+
serviceKey: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface OperationsCatalogSourceV1 {
|
|
20
|
+
coordinate: OperationsSourceCoordinateV1
|
|
21
|
+
displayName: string
|
|
22
|
+
/** Canonical externally reachable origin when Control owns one; null when it does not. */
|
|
23
|
+
publicOrigin?: string | null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface CanonicalOperationsCatalogSourceV1 {
|
|
27
|
+
coordinate: CanonicalOperationsSourceCoordinateV1
|
|
28
|
+
displayName: string
|
|
29
|
+
publicOrigin: string | null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Caller-owned write credential transported only over the private catalog channel. */
|
|
33
|
+
export interface OperationsCatalogIngestCredentialV2 {
|
|
34
|
+
id: string
|
|
35
|
+
publicKey: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface OperationsCatalogSourceV2 extends OperationsCatalogSourceV1 {
|
|
39
|
+
ingestCredential: OperationsCatalogIngestCredentialV2
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface CanonicalOperationsCatalogSourceV2 extends CanonicalOperationsCatalogSourceV1 {
|
|
43
|
+
ingestCredential: OperationsCatalogIngestCredentialV2
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface OperationsCatalogReconcileRequestV2 {
|
|
47
|
+
protocolVersion: typeof OPERATIONS_CATALOG_PROTOCOL_VERSION
|
|
48
|
+
revision: number
|
|
49
|
+
snapshotHash: string
|
|
50
|
+
sources: OperationsCatalogSourceV2[]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type OperationsCatalogReconcileOutcome = 'applied' | 'unchanged' | 'stale'
|
|
54
|
+
|
|
55
|
+
export interface OperationsCatalogIngestResultV2 {
|
|
56
|
+
coordinate: CanonicalOperationsSourceCoordinateV1
|
|
57
|
+
credentialId: string
|
|
58
|
+
ingestProjectId: string
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface OperationsCatalogReconcileResponseV2 {
|
|
62
|
+
protocolVersion: typeof OPERATIONS_CATALOG_PROTOCOL_VERSION
|
|
63
|
+
revision: number
|
|
64
|
+
outcome: OperationsCatalogReconcileOutcome
|
|
65
|
+
created: number
|
|
66
|
+
updated: number
|
|
67
|
+
disabled: number
|
|
68
|
+
reenabled: number
|
|
69
|
+
unchanged: number
|
|
70
|
+
ingest: OperationsCatalogIngestResultV2[]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function canonicalOperationsServiceKey(serviceKey: string | undefined): string {
|
|
74
|
+
return serviceKey ?? DEFAULT_OPERATIONS_SERVICE_KEY
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function canonicalOperationsCatalogSources(
|
|
78
|
+
sources: readonly OperationsCatalogSourceV2[],
|
|
79
|
+
): CanonicalOperationsCatalogSourceV2[] {
|
|
80
|
+
return sources.map((source) => ({
|
|
81
|
+
coordinate: {
|
|
82
|
+
appId: source.coordinate.appId,
|
|
83
|
+
environment: source.coordinate.environment,
|
|
84
|
+
serviceKey: canonicalOperationsServiceKey(source.coordinate.serviceKey),
|
|
85
|
+
},
|
|
86
|
+
displayName: source.displayName,
|
|
87
|
+
publicOrigin: source.publicOrigin ?? null,
|
|
88
|
+
ingestCredential: source.ingestCredential,
|
|
89
|
+
})).sort((left, right) => {
|
|
90
|
+
const app = left.coordinate.appId.localeCompare(right.coordinate.appId)
|
|
91
|
+
if (app !== 0) return app
|
|
92
|
+
const environment = left.coordinate.environment.localeCompare(right.coordinate.environment)
|
|
93
|
+
if (environment !== 0) return environment
|
|
94
|
+
return left.coordinate.serviceKey.localeCompare(right.coordinate.serviceKey)
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Hash the canonical source array only; revision is transport ordering, not catalog content. */
|
|
99
|
+
export async function operationsCatalogSnapshotHash(sources: readonly OperationsCatalogSourceV2[]): Promise<string> {
|
|
100
|
+
const bytes = new TextEncoder().encode(JSON.stringify(canonicalOperationsCatalogSources(sources)))
|
|
101
|
+
const digest = await crypto.subtle.digest('SHA-256', bytes)
|
|
102
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, '0')).join('')
|
|
103
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
export { OPERATIONS_ACTIONS, OPERATIONS_APP_ID } from './access.js'
|
|
2
|
+
export type { OperationsAction } from './access.js'
|
|
3
|
+
export {
|
|
4
|
+
canonicalOperationsCatalogSources,
|
|
5
|
+
canonicalOperationsServiceKey,
|
|
6
|
+
DEFAULT_OPERATIONS_SERVICE_KEY,
|
|
7
|
+
OPERATIONS_CATALOG_PROTOCOL_VERSION,
|
|
8
|
+
operationsCatalogSnapshotHash,
|
|
9
|
+
} from './catalog.js'
|
|
10
|
+
export type {
|
|
11
|
+
CanonicalOperationsCatalogSourceV1,
|
|
12
|
+
CanonicalOperationsCatalogSourceV2,
|
|
13
|
+
CanonicalOperationsSourceCoordinateV1,
|
|
14
|
+
OperationsCatalogIngestCredentialV2,
|
|
15
|
+
OperationsCatalogIngestResultV2,
|
|
16
|
+
OperationsCatalogReconcileOutcome,
|
|
17
|
+
OperationsCatalogReconcileRequestV2,
|
|
18
|
+
OperationsCatalogReconcileResponseV2,
|
|
19
|
+
OperationsCatalogSourceV1,
|
|
20
|
+
OperationsCatalogSourceV2,
|
|
21
|
+
OperationsSourceCoordinateV1,
|
|
22
|
+
} from './catalog.js'
|
|
23
|
+
export {
|
|
24
|
+
buildOperationsDsn,
|
|
25
|
+
FABRIKA_APP_ID,
|
|
26
|
+
FABRIKA_ENVIRONMENT,
|
|
27
|
+
FABRIKA_OPERATIONS_DSN,
|
|
28
|
+
FABRIKA_SERVICE_KEY,
|
|
29
|
+
OPERATIONS_MANAGED_ENVIRONMENT_KEYS,
|
|
30
|
+
operationsEnvelopeUrl,
|
|
31
|
+
operationsManagedEnvironment,
|
|
32
|
+
operationsManagedEnvironmentCollisions,
|
|
33
|
+
} from './ingest.js'
|
|
34
|
+
export type {
|
|
35
|
+
IngestMessage,
|
|
36
|
+
IngestRejectReason,
|
|
37
|
+
OperationsIngestConfiguration,
|
|
38
|
+
OperationsManagedEnvironmentKey,
|
|
39
|
+
ParsedEvent,
|
|
40
|
+
ParsedException,
|
|
41
|
+
StackFrame,
|
|
42
|
+
} from './ingest.js'
|
|
43
|
+
export type {
|
|
44
|
+
OperationsAlertKind,
|
|
45
|
+
OperationsAlertRuleDto,
|
|
46
|
+
OperationsAlertRuleRequestDto,
|
|
47
|
+
OperationsAlertSettingsResponseDto,
|
|
48
|
+
OperationsAssigneeListResponseDto,
|
|
49
|
+
OperationsBulkIssueStatusRequestDto,
|
|
50
|
+
OperationsBulkIssueStatusResponseDto,
|
|
51
|
+
OperationsCurrentHttpHealthDto,
|
|
52
|
+
OperationsEventDetailResponseDto,
|
|
53
|
+
OperationsHealthCheckDto,
|
|
54
|
+
OperationsHealthCheckUpsertRequestDto,
|
|
55
|
+
OperationsHealthObservationDto,
|
|
56
|
+
OperationsHealthResponseDto,
|
|
57
|
+
OperationsHealthState,
|
|
58
|
+
OperationsIssueDetailDto,
|
|
59
|
+
OperationsIssueDetailResponseDto,
|
|
60
|
+
OperationsIssueListResponseDto,
|
|
61
|
+
OperationsIssueMutationRequestDto,
|
|
62
|
+
OperationsIssueOccurrenceDto,
|
|
63
|
+
OperationsIssueOccurrenceListResponseDto,
|
|
64
|
+
OperationsIssueSummaryDto,
|
|
65
|
+
OperationsNotificationAttemptDto,
|
|
66
|
+
OperationsNotificationChannelDto,
|
|
67
|
+
OperationsNotificationChannelRequestDto,
|
|
68
|
+
OperationsNotificationDeliveryDto,
|
|
69
|
+
OperationsReleaseDetailResponseDto,
|
|
70
|
+
OperationsReleaseListResponseDto,
|
|
71
|
+
OperationsReleaseSummaryDto,
|
|
72
|
+
OperationsSourceDetailResponseDto,
|
|
73
|
+
OperationsSourceHealthDto,
|
|
74
|
+
OperationsSourceListResponseDto,
|
|
75
|
+
OperationsSourceSummaryDto,
|
|
76
|
+
OperationsSpikeAlertRequestDto,
|
|
77
|
+
} from './operator-api.js'
|
|
78
|
+
export type {
|
|
79
|
+
ActivityDraft,
|
|
80
|
+
ActivityItem,
|
|
81
|
+
ActivityKind,
|
|
82
|
+
DisplayFrame,
|
|
83
|
+
EventBreadcrumb,
|
|
84
|
+
EventDetail,
|
|
85
|
+
EventException,
|
|
86
|
+
IssueAlertType,
|
|
87
|
+
IssueListItem,
|
|
88
|
+
IssueMutation,
|
|
89
|
+
IssueMutationDecision,
|
|
90
|
+
IssueStatus,
|
|
91
|
+
Occurrence,
|
|
92
|
+
OccurrenceTransition,
|
|
93
|
+
PriorIssueState,
|
|
94
|
+
RawEventFrame,
|
|
95
|
+
SourceContext,
|
|
96
|
+
} from './operator.js'
|
|
97
|
+
export {
|
|
98
|
+
FABRIKA_RELEASE,
|
|
99
|
+
normalizeOperationsCommit,
|
|
100
|
+
OPERATIONS_ARTIFACT_HEADERS,
|
|
101
|
+
OPERATIONS_RELEASE_PROTOCOL_VERSION,
|
|
102
|
+
OPERATIONS_RELEASE_RECONCILE_PATH,
|
|
103
|
+
OPERATIONS_SOURCE_MAP_UPLOAD_PATH,
|
|
104
|
+
operationsReleaseName,
|
|
105
|
+
operationsSourceMapUploadUrl,
|
|
106
|
+
} from './releases.js'
|
|
107
|
+
export type {
|
|
108
|
+
OperationsArtifactState,
|
|
109
|
+
OperationsArtifactUploadConfiguration,
|
|
110
|
+
OperationsArtifactUploadCredentialV1,
|
|
111
|
+
OperationsReleaseAvailabilityV1,
|
|
112
|
+
OperationsReleaseCoordinateV1,
|
|
113
|
+
OperationsReleaseOutcome,
|
|
114
|
+
OperationsReleasePhase,
|
|
115
|
+
OperationsReleaseReconcileOutcome,
|
|
116
|
+
OperationsReleaseReconcileRequestV1,
|
|
117
|
+
OperationsReleaseReconcileResponseV1,
|
|
118
|
+
OperationsReleaseUnavailableReason,
|
|
119
|
+
} from './releases.js'
|
|
120
|
+
export type { OperationsIssueQuery, OperationsReleaseQuery, OperationsRpcContract } from './rpc.js'
|
package/src/ingest.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/** Browser- and runtime-safe shapes at the direct Sentry-compatible ingest boundary. */
|
|
2
|
+
|
|
3
|
+
export const FABRIKA_OPERATIONS_DSN = 'FABRIKA_OPERATIONS_DSN'
|
|
4
|
+
export const FABRIKA_APP_ID = 'FABRIKA_APP_ID'
|
|
5
|
+
export const FABRIKA_ENVIRONMENT = 'FABRIKA_ENVIRONMENT'
|
|
6
|
+
export const FABRIKA_SERVICE_KEY = 'FABRIKA_SERVICE_KEY'
|
|
7
|
+
|
|
8
|
+
export type OperationsManagedEnvironmentKey =
|
|
9
|
+
| typeof FABRIKA_OPERATIONS_DSN
|
|
10
|
+
| typeof FABRIKA_APP_ID
|
|
11
|
+
| typeof FABRIKA_ENVIRONMENT
|
|
12
|
+
| typeof FABRIKA_SERVICE_KEY
|
|
13
|
+
|
|
14
|
+
export const OPERATIONS_MANAGED_ENVIRONMENT_KEYS: readonly OperationsManagedEnvironmentKey[] = [
|
|
15
|
+
FABRIKA_OPERATIONS_DSN,
|
|
16
|
+
FABRIKA_APP_ID,
|
|
17
|
+
FABRIKA_ENVIRONMENT,
|
|
18
|
+
FABRIKA_SERVICE_KEY,
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
export interface OperationsIngestConfiguration {
|
|
22
|
+
dsn: string
|
|
23
|
+
appId: string
|
|
24
|
+
environment: string
|
|
25
|
+
serviceKey: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const PUBLIC_KEY_PATTERN = /^[0-9a-f]{32}$/
|
|
29
|
+
const INGEST_PROJECT_ID_PATTERN = /^[1-9][0-9]{0,18}$/
|
|
30
|
+
|
|
31
|
+
export function buildOperationsDsn(origin: string, publicKey: string, ingestProjectId: string): string {
|
|
32
|
+
if (!PUBLIC_KEY_PATTERN.test(publicKey)) throw new Error('invalid Operations ingest public key')
|
|
33
|
+
if (!INGEST_PROJECT_ID_PATTERN.test(ingestProjectId)) throw new Error('invalid Operations ingest project id')
|
|
34
|
+
const url = new URL(origin)
|
|
35
|
+
if (
|
|
36
|
+
(url.protocol !== 'https:' && url.protocol !== 'http:')
|
|
37
|
+
|| url.origin !== origin
|
|
38
|
+
|| url.username !== ''
|
|
39
|
+
|| url.password !== ''
|
|
40
|
+
) {
|
|
41
|
+
throw new Error('Operations ingest origin must be an HTTP origin')
|
|
42
|
+
}
|
|
43
|
+
url.username = publicKey
|
|
44
|
+
url.pathname = `/${ingestProjectId}`
|
|
45
|
+
return url.toString()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function operationsEnvelopeUrl(origin: string, ingestProjectId: string): string {
|
|
49
|
+
if (!INGEST_PROJECT_ID_PATTERN.test(ingestProjectId)) throw new Error('invalid Operations ingest project id')
|
|
50
|
+
const url = new URL(origin)
|
|
51
|
+
if (
|
|
52
|
+
(url.protocol !== 'https:' && url.protocol !== 'http:')
|
|
53
|
+
|| url.origin !== origin
|
|
54
|
+
|| url.username !== ''
|
|
55
|
+
|| url.password !== ''
|
|
56
|
+
) {
|
|
57
|
+
throw new Error('Operations ingest origin must be an HTTP origin')
|
|
58
|
+
}
|
|
59
|
+
url.pathname = `/api/${ingestProjectId}/envelope/`
|
|
60
|
+
return url.toString()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function operationsManagedEnvironment(input: OperationsIngestConfiguration): Record<OperationsManagedEnvironmentKey, string> {
|
|
64
|
+
return {
|
|
65
|
+
[FABRIKA_OPERATIONS_DSN]: input.dsn,
|
|
66
|
+
[FABRIKA_APP_ID]: input.appId,
|
|
67
|
+
[FABRIKA_ENVIRONMENT]: input.environment,
|
|
68
|
+
[FABRIKA_SERVICE_KEY]: input.serviceKey,
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function operationsManagedEnvironmentCollisions(keys: Iterable<string>): OperationsManagedEnvironmentKey[] {
|
|
73
|
+
const occupied = new Set(keys)
|
|
74
|
+
return OPERATIONS_MANAGED_ENVIRONMENT_KEYS.filter((key) => occupied.has(key))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface StackFrame {
|
|
78
|
+
function?: string
|
|
79
|
+
filename?: string
|
|
80
|
+
module?: string
|
|
81
|
+
absPath?: string
|
|
82
|
+
line?: number
|
|
83
|
+
column?: number
|
|
84
|
+
inApp?: boolean
|
|
85
|
+
preContext?: string[]
|
|
86
|
+
contextLine?: string
|
|
87
|
+
postContext?: string[]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface ParsedException {
|
|
91
|
+
type: string
|
|
92
|
+
value: string
|
|
93
|
+
frames: StackFrame[]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface ParsedEvent {
|
|
97
|
+
eventId: string
|
|
98
|
+
projectId: string
|
|
99
|
+
exception: ParsedException
|
|
100
|
+
level: string
|
|
101
|
+
release?: string
|
|
102
|
+
environment?: string
|
|
103
|
+
platform?: string
|
|
104
|
+
tags: Record<string, string>
|
|
105
|
+
sdkFingerprint: string[] | null
|
|
106
|
+
receivedAt: number
|
|
107
|
+
raw: Record<string, unknown>
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface IngestMessage {
|
|
111
|
+
projectId: string
|
|
112
|
+
fingerprint: string
|
|
113
|
+
eventId: string
|
|
114
|
+
title: string
|
|
115
|
+
culprit: string | null
|
|
116
|
+
level: string
|
|
117
|
+
release?: string
|
|
118
|
+
environment?: string
|
|
119
|
+
receivedAt: number
|
|
120
|
+
payload: Record<string, unknown>
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export type IngestRejectReason =
|
|
124
|
+
| 'unauthorized'
|
|
125
|
+
| 'forbidden'
|
|
126
|
+
| 'too_large'
|
|
127
|
+
| 'malformed'
|
|
128
|
+
| 'unsupported'
|
|
129
|
+
| 'rate_limited'
|
|
130
|
+
| 'unavailable'
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import type { ActivityItem, EventDetail, IssueStatus } from './operator.js'
|
|
2
|
+
import type { OperationsArtifactState } from './releases.js'
|
|
3
|
+
|
|
4
|
+
export type OperationsAlertKind =
|
|
5
|
+
| 'new_issue'
|
|
6
|
+
| 'regression'
|
|
7
|
+
| 'spike'
|
|
8
|
+
| 'failed_check'
|
|
9
|
+
| 'recovery'
|
|
10
|
+
| 'unhealthy_telemetry'
|
|
11
|
+
|
|
12
|
+
export type OperationsHealthState = 'healthy' | 'degraded' | 'failed' | 'stale' | 'unavailable'
|
|
13
|
+
|
|
14
|
+
export interface OperationsSourceSummaryDto {
|
|
15
|
+
id: string
|
|
16
|
+
appId: string
|
|
17
|
+
environment: string
|
|
18
|
+
serviceKey: string
|
|
19
|
+
displayName: string
|
|
20
|
+
publicOrigin: string | null
|
|
21
|
+
enabled: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface OperationsSourceListResponseDto {
|
|
25
|
+
items: OperationsSourceSummaryDto[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface OperationsSourceDetailResponseDto {
|
|
29
|
+
source: OperationsSourceSummaryDto
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface OperationsIssueSummaryDto {
|
|
33
|
+
id: string
|
|
34
|
+
source: OperationsSourceSummaryDto
|
|
35
|
+
title: string
|
|
36
|
+
culprit: string | null
|
|
37
|
+
level: string
|
|
38
|
+
status: IssueStatus
|
|
39
|
+
assignedTo: { id: string; label: string | null } | null
|
|
40
|
+
regressedAt: number | null
|
|
41
|
+
firstSeen: number
|
|
42
|
+
lastSeen: number
|
|
43
|
+
count: number
|
|
44
|
+
trend: number[]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface OperationsIssueOccurrenceDto {
|
|
48
|
+
id: string
|
|
49
|
+
eventId: string
|
|
50
|
+
receivedAt: number
|
|
51
|
+
release: string | null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface OperationsIssueListResponseDto {
|
|
55
|
+
items: OperationsIssueSummaryDto[]
|
|
56
|
+
nextCursor: string | null
|
|
57
|
+
summary: {
|
|
58
|
+
total: number
|
|
59
|
+
open: number
|
|
60
|
+
resolved: number
|
|
61
|
+
ignored: number
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface OperationsIssueDetailDto extends OperationsIssueSummaryDto {
|
|
66
|
+
snoozeUntil: number | null
|
|
67
|
+
snoozeUntilCount: number | null
|
|
68
|
+
resolvedInRelease: { id: string; name: string } | null
|
|
69
|
+
mergedIntoIssueId: string | null
|
|
70
|
+
activity: ActivityItem[]
|
|
71
|
+
latestOccurrence: OperationsIssueOccurrenceDto | null
|
|
72
|
+
occurrences: OperationsIssueOccurrenceDto[]
|
|
73
|
+
occurrencesNextCursor: string | null
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface OperationsIssueDetailResponseDto {
|
|
77
|
+
issue: OperationsIssueDetailDto
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface OperationsEventDetailResponseDto {
|
|
81
|
+
occurrenceId: string
|
|
82
|
+
receivedAt: number
|
|
83
|
+
detail: EventDetail
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface OperationsIssueOccurrenceListResponseDto {
|
|
87
|
+
items: OperationsIssueOccurrenceDto[]
|
|
88
|
+
nextCursor: string | null
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export type OperationsIssueMutationRequestDto =
|
|
92
|
+
| { kind: 'status'; status: IssueStatus }
|
|
93
|
+
| { kind: 'comment'; text: string }
|
|
94
|
+
| { kind: 'assign'; principalId: string | null }
|
|
95
|
+
| { kind: 'snooze_until'; until: number }
|
|
96
|
+
| { kind: 'snooze_count'; additional: number }
|
|
97
|
+
| { kind: 'resolve_in_release'; releaseId: string | null }
|
|
98
|
+
| { kind: 'merge'; targetIssueId: string }
|
|
99
|
+
|
|
100
|
+
export interface OperationsBulkIssueStatusRequestDto {
|
|
101
|
+
issueIds: string[]
|
|
102
|
+
status: IssueStatus
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface OperationsBulkIssueStatusResponseDto {
|
|
106
|
+
items: OperationsIssueSummaryDto[]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface OperationsAssigneeListResponseDto {
|
|
110
|
+
items: {
|
|
111
|
+
id: string
|
|
112
|
+
label: string
|
|
113
|
+
}[]
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface OperationsReleaseSummaryDto {
|
|
117
|
+
id: string
|
|
118
|
+
source: OperationsSourceSummaryDto
|
|
119
|
+
runId: string
|
|
120
|
+
commitSha: string
|
|
121
|
+
state: string
|
|
122
|
+
artifactState: OperationsArtifactState
|
|
123
|
+
releaseName: string
|
|
124
|
+
createdAt: number
|
|
125
|
+
finishedAt: number | null
|
|
126
|
+
newIssueCount: number
|
|
127
|
+
regressionCount: number
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface OperationsReleaseListResponseDto {
|
|
131
|
+
items: OperationsReleaseSummaryDto[]
|
|
132
|
+
nextCursor: string | null
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface OperationsReleaseDetailResponseDto {
|
|
136
|
+
release: OperationsReleaseSummaryDto
|
|
137
|
+
issues: OperationsIssueSummaryDto[]
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface OperationsCurrentHttpHealthDto {
|
|
141
|
+
state: Exclude<OperationsHealthState, 'unavailable'>
|
|
142
|
+
observedAt: number
|
|
143
|
+
latencyMs: number | null
|
|
144
|
+
detailCode: string
|
|
145
|
+
consecutiveFailures: number
|
|
146
|
+
consecutiveSuccesses: number
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface OperationsHealthObservationDto {
|
|
150
|
+
id: string
|
|
151
|
+
state: OperationsHealthState
|
|
152
|
+
observedAt: number
|
|
153
|
+
latencyMs: number | null
|
|
154
|
+
detailCode: string | null
|
|
155
|
+
successful: boolean
|
|
156
|
+
statusCode: number | null
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface OperationsHealthCheckDto {
|
|
160
|
+
id: string
|
|
161
|
+
sourceId: string
|
|
162
|
+
path: string
|
|
163
|
+
enabled: boolean
|
|
164
|
+
intervalMs: number
|
|
165
|
+
timeoutMs: number
|
|
166
|
+
expectedStatus: number
|
|
167
|
+
failureThreshold: number
|
|
168
|
+
recoveryThreshold: number
|
|
169
|
+
staleAfterMs: number
|
|
170
|
+
current: OperationsCurrentHttpHealthDto | null
|
|
171
|
+
history: OperationsHealthObservationDto[]
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export interface OperationsSourceHealthDto {
|
|
175
|
+
source: OperationsSourceSummaryDto
|
|
176
|
+
httpChecks: OperationsHealthCheckDto[]
|
|
177
|
+
telemetryState: OperationsHealthState
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export interface OperationsHealthResponseDto {
|
|
181
|
+
sources: OperationsSourceHealthDto[]
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface OperationsHealthCheckUpsertRequestDto {
|
|
185
|
+
path: string
|
|
186
|
+
enabled: boolean
|
|
187
|
+
intervalMs: number
|
|
188
|
+
timeoutMs: number
|
|
189
|
+
expectedStatus: number
|
|
190
|
+
failureThreshold: number
|
|
191
|
+
recoveryThreshold: number
|
|
192
|
+
staleAfterMs: number
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export interface OperationsAlertRuleDto {
|
|
196
|
+
kind: OperationsAlertKind
|
|
197
|
+
enabled: boolean
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export interface OperationsNotificationChannelDto {
|
|
201
|
+
id: string
|
|
202
|
+
scope: OperationsAlertKind
|
|
203
|
+
type: 'webhook'
|
|
204
|
+
targetDisplay: string
|
|
205
|
+
hasTarget: boolean
|
|
206
|
+
enabled: boolean
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export interface OperationsNotificationAttemptDto {
|
|
210
|
+
id: string
|
|
211
|
+
attemptedAt: number
|
|
212
|
+
delivered: boolean
|
|
213
|
+
errorCode: string | null
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export interface OperationsNotificationDeliveryDto {
|
|
217
|
+
id: string
|
|
218
|
+
channelId: string
|
|
219
|
+
kind: OperationsAlertKind
|
|
220
|
+
createdAt: number
|
|
221
|
+
attemptCount: number
|
|
222
|
+
maxAttempts: number
|
|
223
|
+
status: 'pending' | 'delivered' | 'abandoned'
|
|
224
|
+
deliveredAt: number | null
|
|
225
|
+
abandonedAt: number | null
|
|
226
|
+
attempts: OperationsNotificationAttemptDto[]
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export interface OperationsAlertSettingsResponseDto {
|
|
230
|
+
spike: { threshold: number; enabled: boolean } | null
|
|
231
|
+
rules: OperationsAlertRuleDto[]
|
|
232
|
+
channels: OperationsNotificationChannelDto[]
|
|
233
|
+
deliveries: OperationsNotificationDeliveryDto[]
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export interface OperationsSpikeAlertRequestDto {
|
|
237
|
+
threshold: number
|
|
238
|
+
enabled: boolean
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export interface OperationsAlertRuleRequestDto {
|
|
242
|
+
enabled: boolean
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export interface OperationsNotificationChannelRequestDto {
|
|
246
|
+
scope: OperationsAlertKind
|
|
247
|
+
type: 'webhook'
|
|
248
|
+
target?: string
|
|
249
|
+
enabled: boolean
|
|
250
|
+
}
|
package/src/operator.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { StackFrame } from './ingest.js'
|
|
2
|
+
|
|
3
|
+
export type IssueStatus = 'open' | 'resolved' | 'ignored'
|
|
4
|
+
export type ActivityKind = 'comment' | 'status' | 'assigned' | 'regressed' | 'snoozed' | 'unsnoozed' | 'merged'
|
|
5
|
+
export type IssueAlertType = 'new_issue' | 'regression'
|
|
6
|
+
|
|
7
|
+
export interface IssueListItem {
|
|
8
|
+
fingerprint: string
|
|
9
|
+
projectId: string
|
|
10
|
+
title: string
|
|
11
|
+
culprit: string | null
|
|
12
|
+
level: string
|
|
13
|
+
status: IssueStatus
|
|
14
|
+
assignedTo: string | null
|
|
15
|
+
regressedAt: number | null
|
|
16
|
+
firstSeen: number
|
|
17
|
+
lastSeen: number
|
|
18
|
+
count: number
|
|
19
|
+
trend: number[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ActivityItem {
|
|
23
|
+
id: string
|
|
24
|
+
kind: ActivityKind
|
|
25
|
+
actorLabel: string | null
|
|
26
|
+
data: Record<string, unknown> | null
|
|
27
|
+
at: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SourceContext {
|
|
31
|
+
lines: string[]
|
|
32
|
+
errorIndex: number
|
|
33
|
+
startLine: number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface DisplayFrame {
|
|
37
|
+
file: string
|
|
38
|
+
function: string | null
|
|
39
|
+
line: number | null
|
|
40
|
+
column: number | null
|
|
41
|
+
inApp: boolean
|
|
42
|
+
resolved: boolean
|
|
43
|
+
source?: SourceContext
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface EventException {
|
|
47
|
+
type: string | null
|
|
48
|
+
value: string | null
|
|
49
|
+
handled: boolean | null
|
|
50
|
+
frames: DisplayFrame[]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface EventBreadcrumb {
|
|
54
|
+
timestamp?: number | string
|
|
55
|
+
type?: string
|
|
56
|
+
category?: string
|
|
57
|
+
level?: string
|
|
58
|
+
message?: string
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface EventDetail {
|
|
62
|
+
eventId: string | null
|
|
63
|
+
platform: string | null
|
|
64
|
+
release: string | null
|
|
65
|
+
environment: string | null
|
|
66
|
+
tags: { key: string; value: string }[]
|
|
67
|
+
request: { url: string | null; method: string | null } | null
|
|
68
|
+
user: { id: string | null; email: string | null; username: string | null } | null
|
|
69
|
+
breadcrumbs: EventBreadcrumb[]
|
|
70
|
+
runtime: string | null
|
|
71
|
+
os: string | null
|
|
72
|
+
serverName: string | null
|
|
73
|
+
traceId: string | null
|
|
74
|
+
exceptions: EventException[]
|
|
75
|
+
rawEvent: string
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface Occurrence {
|
|
79
|
+
release?: string
|
|
80
|
+
receivedAt: number
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface PriorIssueState {
|
|
84
|
+
status: IssueStatus
|
|
85
|
+
resolvedInRelease: string | null
|
|
86
|
+
snoozeUntil: number | null
|
|
87
|
+
snoozeUntilCount: number | null
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface OccurrenceTransition {
|
|
91
|
+
reopen: boolean
|
|
92
|
+
regression: boolean
|
|
93
|
+
at: number | null
|
|
94
|
+
release: string | null
|
|
95
|
+
activity: ActivityKind | null
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export type IssueMutation =
|
|
99
|
+
| { kind: 'status'; status: IssueStatus }
|
|
100
|
+
| { kind: 'comment'; text: string }
|
|
101
|
+
| { kind: 'assign'; principalId: string | null; principalLabel: string | null }
|
|
102
|
+
| { kind: 'snooze_until'; until: number }
|
|
103
|
+
| { kind: 'snooze_count'; additional: number; currentCount: number }
|
|
104
|
+
| { kind: 'resolve_in_release'; release: string | null }
|
|
105
|
+
| { kind: 'merge'; target: string }
|
|
106
|
+
|
|
107
|
+
export interface ActivityDraft {
|
|
108
|
+
kind: ActivityKind
|
|
109
|
+
data: Record<string, unknown>
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface IssueMutationDecision {
|
|
113
|
+
status: IssueStatus
|
|
114
|
+
assignedTo?: string | null
|
|
115
|
+
assignedToLabel?: string | null
|
|
116
|
+
snoozeUntil?: number | null
|
|
117
|
+
snoozeUntilCount?: number | null
|
|
118
|
+
resolvedInRelease?: string | null
|
|
119
|
+
mergedInto?: string
|
|
120
|
+
activity: ActivityDraft | null
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export type RawEventFrame = StackFrame
|
package/src/releases.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/** Versioned private Delivery → Operations release and artifact protocol. */
|
|
2
|
+
|
|
3
|
+
import { DEFAULT_OPERATIONS_SERVICE_KEY } from './catalog.js'
|
|
4
|
+
|
|
5
|
+
export const OPERATIONS_RELEASE_PROTOCOL_VERSION = 1
|
|
6
|
+
export const FABRIKA_RELEASE = 'FABRIKA_RELEASE'
|
|
7
|
+
export const OPERATIONS_RELEASE_RECONCILE_PATH = '/private/releases/reconcile'
|
|
8
|
+
export const OPERATIONS_SOURCE_MAP_UPLOAD_PATH = '/api/artifacts/source-maps/'
|
|
9
|
+
|
|
10
|
+
export const OPERATIONS_ARTIFACT_HEADERS = {
|
|
11
|
+
appId: 'X-Fabrika-App-Id',
|
|
12
|
+
environment: 'X-Fabrika-Environment',
|
|
13
|
+
serviceKey: 'X-Fabrika-Service-Key',
|
|
14
|
+
release: 'X-Fabrika-Release',
|
|
15
|
+
runId: 'X-Fabrika-Run-Id',
|
|
16
|
+
logicalPath: 'X-Fabrika-Artifact-Path',
|
|
17
|
+
digest: 'X-Fabrika-Artifact-Sha256',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type OperationsReleasePhase = 'started' | 'provider_accepted' | 'terminal'
|
|
21
|
+
export type OperationsReleaseOutcome = 'succeeded' | 'failed' | null
|
|
22
|
+
export type OperationsArtifactState = 'pending' | 'complete' | 'incomplete' | 'not_applicable'
|
|
23
|
+
export type OperationsReleaseUnavailableReason = 'dry_run' | 'missing_commit'
|
|
24
|
+
|
|
25
|
+
export interface OperationsReleaseCoordinateV1 {
|
|
26
|
+
appId: string
|
|
27
|
+
environment: string
|
|
28
|
+
serviceKey?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type OperationsReleaseAvailabilityV1 =
|
|
32
|
+
| {
|
|
33
|
+
kind: 'available'
|
|
34
|
+
name: string
|
|
35
|
+
commitSha: string
|
|
36
|
+
}
|
|
37
|
+
| {
|
|
38
|
+
kind: 'unavailable'
|
|
39
|
+
reason: OperationsReleaseUnavailableReason
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface OperationsArtifactUploadCredentialV1 {
|
|
43
|
+
/** SHA-256 verifier. The bearer itself never crosses the private projection boundary. */
|
|
44
|
+
verifier: string
|
|
45
|
+
expiresAt: number
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface OperationsReleaseReconcileRequestV1 {
|
|
49
|
+
protocolVersion: typeof OPERATIONS_RELEASE_PROTOCOL_VERSION
|
|
50
|
+
revision: number
|
|
51
|
+
runId: string
|
|
52
|
+
coordinate: OperationsReleaseCoordinateV1
|
|
53
|
+
phase: OperationsReleasePhase
|
|
54
|
+
providerRunId: string | null
|
|
55
|
+
outcome: OperationsReleaseOutcome
|
|
56
|
+
artifactState: OperationsArtifactState
|
|
57
|
+
release: OperationsReleaseAvailabilityV1
|
|
58
|
+
uploadCredential?: OperationsArtifactUploadCredentialV1
|
|
59
|
+
observedAt: number
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type OperationsReleaseReconcileOutcome = 'applied' | 'unchanged' | 'stale'
|
|
63
|
+
|
|
64
|
+
export interface OperationsReleaseReconcileResponseV1 {
|
|
65
|
+
protocolVersion: typeof OPERATIONS_RELEASE_PROTOCOL_VERSION
|
|
66
|
+
revision: number
|
|
67
|
+
outcome: OperationsReleaseReconcileOutcome
|
|
68
|
+
releaseId: string | null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface OperationsArtifactUploadConfiguration {
|
|
72
|
+
url: string
|
|
73
|
+
bearer: string
|
|
74
|
+
appId: string
|
|
75
|
+
environment: string
|
|
76
|
+
serviceKey: string
|
|
77
|
+
release: string
|
|
78
|
+
runId: string
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const COMMIT_PATTERN = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/
|
|
82
|
+
|
|
83
|
+
export function normalizeOperationsCommit(value: string | null | undefined): string | null {
|
|
84
|
+
if (value === null || value === undefined) return null
|
|
85
|
+
const normalized = value.toLowerCase()
|
|
86
|
+
return COMMIT_PATTERN.test(normalized) ? normalized : null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function operationsReleaseName(input: {
|
|
90
|
+
appId: string
|
|
91
|
+
environment: string
|
|
92
|
+
serviceKey?: string
|
|
93
|
+
commitSha: string
|
|
94
|
+
}): string {
|
|
95
|
+
const commitSha = normalizeOperationsCommit(input.commitSha)
|
|
96
|
+
if (commitSha === null) throw new Error('release commit must be a complete immutable git object id')
|
|
97
|
+
return [
|
|
98
|
+
'fabrika',
|
|
99
|
+
encodeURIComponent(input.appId),
|
|
100
|
+
encodeURIComponent(input.environment),
|
|
101
|
+
encodeURIComponent(input.serviceKey ?? DEFAULT_OPERATIONS_SERVICE_KEY),
|
|
102
|
+
commitSha,
|
|
103
|
+
].join('/')
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function operationsSourceMapUploadUrl(origin: string): string {
|
|
107
|
+
const url = new URL(origin)
|
|
108
|
+
if (
|
|
109
|
+
(url.protocol !== 'https:' && url.protocol !== 'http:')
|
|
110
|
+
|| url.origin !== origin
|
|
111
|
+
|| url.username !== ''
|
|
112
|
+
|| url.password !== ''
|
|
113
|
+
) {
|
|
114
|
+
throw new Error('Operations artifact origin must be an HTTP origin')
|
|
115
|
+
}
|
|
116
|
+
url.pathname = OPERATIONS_SOURCE_MAP_UPLOAD_PATH
|
|
117
|
+
return url.toString()
|
|
118
|
+
}
|
package/src/rpc.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { RpcProcedureContract } from '@fabrika/app'
|
|
2
|
+
import type {
|
|
3
|
+
OperationsAlertKind,
|
|
4
|
+
OperationsAlertRuleRequestDto,
|
|
5
|
+
OperationsAlertSettingsResponseDto,
|
|
6
|
+
OperationsAssigneeListResponseDto,
|
|
7
|
+
OperationsBulkIssueStatusRequestDto,
|
|
8
|
+
OperationsBulkIssueStatusResponseDto,
|
|
9
|
+
OperationsEventDetailResponseDto,
|
|
10
|
+
OperationsHealthCheckUpsertRequestDto,
|
|
11
|
+
OperationsHealthResponseDto,
|
|
12
|
+
OperationsIssueDetailResponseDto,
|
|
13
|
+
OperationsIssueListResponseDto,
|
|
14
|
+
OperationsIssueMutationRequestDto,
|
|
15
|
+
OperationsIssueOccurrenceListResponseDto,
|
|
16
|
+
OperationsNotificationChannelRequestDto,
|
|
17
|
+
OperationsReleaseDetailResponseDto,
|
|
18
|
+
OperationsReleaseListResponseDto,
|
|
19
|
+
OperationsSourceDetailResponseDto,
|
|
20
|
+
OperationsSourceHealthDto,
|
|
21
|
+
OperationsSourceListResponseDto,
|
|
22
|
+
OperationsSpikeAlertRequestDto,
|
|
23
|
+
} from './operator-api.js'
|
|
24
|
+
|
|
25
|
+
export interface OperationsIssueQuery {
|
|
26
|
+
sourceId?: string
|
|
27
|
+
status?: 'open' | 'resolved' | 'ignored'
|
|
28
|
+
level?: 'fatal' | 'error' | 'warning' | 'info'
|
|
29
|
+
window?: 'all' | '24h' | '7d' | '30d'
|
|
30
|
+
query?: string
|
|
31
|
+
assignee?: 'all' | 'me' | 'none'
|
|
32
|
+
sort?: 'recent' | 'new' | 'frequency'
|
|
33
|
+
cursor?: string
|
|
34
|
+
limit?: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface OperationsReleaseQuery {
|
|
38
|
+
sourceId?: string
|
|
39
|
+
cursor?: string
|
|
40
|
+
limit?: number
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Portable operator API implemented by Operations and consumed by its console. */
|
|
44
|
+
export interface OperationsRpcContract {
|
|
45
|
+
sources: RpcProcedureContract<void, OperationsSourceListResponseDto>
|
|
46
|
+
source: RpcProcedureContract<{ sourceId: string }, OperationsSourceDetailResponseDto>
|
|
47
|
+
issues: RpcProcedureContract<OperationsIssueQuery, OperationsIssueListResponseDto>
|
|
48
|
+
issue: RpcProcedureContract<{ issueId: string }, OperationsIssueDetailResponseDto>
|
|
49
|
+
issueOccurrences: RpcProcedureContract<{ issueId: string; cursor?: string; limit?: number }, OperationsIssueOccurrenceListResponseDto>
|
|
50
|
+
latestEvent: RpcProcedureContract<{ issueId: string }, OperationsEventDetailResponseDto>
|
|
51
|
+
event: RpcProcedureContract<{ issueId: string; occurrenceId: string }, OperationsEventDetailResponseDto>
|
|
52
|
+
mutateIssue: RpcProcedureContract<{ issueId: string; mutation: OperationsIssueMutationRequestDto }, OperationsIssueDetailResponseDto>
|
|
53
|
+
bulkIssueStatus: RpcProcedureContract<OperationsBulkIssueStatusRequestDto, OperationsBulkIssueStatusResponseDto>
|
|
54
|
+
assignees: RpcProcedureContract<{ sourceId: string }, OperationsAssigneeListResponseDto>
|
|
55
|
+
releases: RpcProcedureContract<OperationsReleaseQuery, OperationsReleaseListResponseDto>
|
|
56
|
+
release: RpcProcedureContract<{ releaseId: string }, OperationsReleaseDetailResponseDto>
|
|
57
|
+
health: RpcProcedureContract<void, OperationsHealthResponseDto>
|
|
58
|
+
sourceHealth: RpcProcedureContract<{ sourceId: string }, OperationsSourceHealthDto>
|
|
59
|
+
createHealthCheck: RpcProcedureContract<
|
|
60
|
+
{ sourceId: string; input: OperationsHealthCheckUpsertRequestDto },
|
|
61
|
+
{ id: string }
|
|
62
|
+
>
|
|
63
|
+
updateHealthCheck: RpcProcedureContract<
|
|
64
|
+
{ sourceId: string; checkId: string; input: OperationsHealthCheckUpsertRequestDto },
|
|
65
|
+
{ id: string }
|
|
66
|
+
>
|
|
67
|
+
deleteHealthCheck: RpcProcedureContract<{ sourceId: string; checkId: string }, null>
|
|
68
|
+
alerts: RpcProcedureContract<{ sourceId: string }, OperationsAlertSettingsResponseDto>
|
|
69
|
+
updateSpikeAlert: RpcProcedureContract<
|
|
70
|
+
{ sourceId: string; input: OperationsSpikeAlertRequestDto },
|
|
71
|
+
{ threshold: number; enabled: boolean }
|
|
72
|
+
>
|
|
73
|
+
updateAlertRule: RpcProcedureContract<
|
|
74
|
+
{ sourceId: string; kind: OperationsAlertKind; input: OperationsAlertRuleRequestDto },
|
|
75
|
+
{ kind: OperationsAlertKind; enabled: boolean }
|
|
76
|
+
>
|
|
77
|
+
createAlertChannel: RpcProcedureContract<
|
|
78
|
+
{ sourceId: string; input: OperationsNotificationChannelRequestDto },
|
|
79
|
+
{ id: string }
|
|
80
|
+
>
|
|
81
|
+
updateAlertChannel: RpcProcedureContract<
|
|
82
|
+
{ sourceId: string; channelId: string; input: OperationsNotificationChannelRequestDto },
|
|
83
|
+
{ id: string }
|
|
84
|
+
>
|
|
85
|
+
deleteAlertChannel: RpcProcedureContract<{ sourceId: string; channelId: string }, null>
|
|
86
|
+
}
|