@theholocron/sentry-client 1.53.1 → 1.54.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/README.md CHANGED
@@ -40,6 +40,35 @@ const newProject = await sentry.projects.create("my-org", "my-team", {
40
40
  // Retrieve client keys (DSNs) for a project
41
41
  const keys = await sentry.projects.keys("my-org", newProject.slug);
42
42
  const dsn = keys[0].dsn.public;
43
+
44
+ // Custom (internal) integrations, and the alert rules (workflows) that send to them
45
+ const apps = await sentry.sentryApps.list("my-org");
46
+ const app = await sentry.sentryApps.create("my-org", {
47
+ name: "Comlink",
48
+ scopes: ["event:read"],
49
+ webhookUrl: "https://comlink.example.dev/sentry",
50
+ isAlertable: true,
51
+ });
52
+ const installs = await sentry.sentryApps.installations("my-org"); // installs[0]?.app.sentryAppId is what an alert action points at
53
+ const detectors = await sentry.workflows.detectors("my-org"); // an `issue_stream` detector per project
54
+ await sentry.workflows.create("my-org", {
55
+ name: "Errors to Discord",
56
+ enabled: true,
57
+ environment: null,
58
+ config: { frequency: 5 },
59
+ detectorIds: detectors.filter((d) => d.type === "issue_stream").map((d) => d.id),
60
+ triggers: {
61
+ logicType: "any-short",
62
+ conditions: [] /* e.g. a "first seen" condition */,
63
+ },
64
+ actionFilters: [
65
+ {
66
+ logicType: "any-short",
67
+ conditions: [],
68
+ actions: [] /* a sentry_app action */,
69
+ },
70
+ ],
71
+ });
43
72
  ```
44
73
 
45
74
  ## Auth
@@ -47,11 +76,16 @@ const dsn = keys[0].dsn.public;
47
76
  Generate an [auth token](https://sentry.io/settings/account/api/auth-tokens/)
48
77
  with the required scopes:
49
78
 
50
- | Operation | Required scopes |
51
- | ------------------------ | --------------- |
52
- | List orgs / verify token | `org:read` |
53
- | List / get projects | `project:read` |
54
- | Create projects | `project:write` |
79
+ | Operation | Required scopes |
80
+ | ------------------------ | ---------------------------------------------------------------------------------------- |
81
+ | List orgs / verify token | `org:read` |
82
+ | List / get projects | `project:read` |
83
+ | Create projects | `project:write` |
84
+ | List / get integrations | `org:read` |
85
+ | Create / update one | `org:write`, and every scope you give the integration |
86
+ | Read its `clientSecret` | every scope the integration itself has (Sentry hides the secret from a token with fewer) |
87
+ | List / get alert rules | `alerts:read` |
88
+ | Create / update one | `alerts:write` |
55
89
 
56
90
  The token is passed directly or read from `SENTRY_AUTH_TOKEN`.
57
91
 
package/dist/index.d.mts CHANGED
@@ -45,6 +45,105 @@ interface CreateSentryProjectInput {
45
45
  platform?: string;
46
46
  }
47
47
  //#endregion
48
+ //#region ../../../../../src/sentry-apps/sentry-apps.d.ts
49
+ /**
50
+ * A custom (internal) integration. `clientSecret` is returned only to a caller whose token holds every scope the
51
+ * integration has, so it is absent for a token with fewer. It signs the webhooks Sentry sends to `webhookUrl`.
52
+ */
53
+ interface SentryApp {
54
+ name: string;
55
+ slug: string;
56
+ uuid: string;
57
+ status: string;
58
+ scopes: string[];
59
+ webhookUrl: string | null;
60
+ isAlertable: boolean;
61
+ clientId?: string;
62
+ clientSecret?: string;
63
+ }
64
+ interface SentryAppInput {
65
+ name: string;
66
+ /** What the integration may read or write, e.g. `event:read`. The token making the call must hold each. */
67
+ scopes: string[];
68
+ webhookUrl?: string | null;
69
+ /** Offer the integration as an action in alert rules. */
70
+ isAlertable?: boolean;
71
+ }
72
+ /** An integration installed on the organization. For an internal one this happens when it is created. */
73
+ interface SentryAppInstallation {
74
+ uuid: string;
75
+ status: string;
76
+ app: {
77
+ uuid: string;
78
+ slug: string;
79
+ sentryAppId: number;
80
+ };
81
+ }
82
+ //#endregion
83
+ //#region ../../../../../src/workflows/workflows.d.ts
84
+ interface SentryWorkflowCondition {
85
+ type: string;
86
+ comparison: unknown;
87
+ conditionResult: unknown;
88
+ }
89
+ interface SentryWorkflowAction {
90
+ type: string;
91
+ config: Record<string, unknown>;
92
+ data: Record<string, unknown>;
93
+ }
94
+ interface SentryWorkflowActionFilter {
95
+ logicType: string;
96
+ conditions: SentryWorkflowCondition[];
97
+ actions: SentryWorkflowAction[];
98
+ }
99
+ /** What a workflow (an alert rule in Sentry's Monitors → Alerts) is made of, as sent when creating or updating one. */
100
+ interface SentryWorkflowInput {
101
+ name: string;
102
+ enabled: boolean;
103
+ environment: string | null;
104
+ /** `frequency` is minutes between notifications for one issue. */
105
+ config: {
106
+ frequency: number;
107
+ };
108
+ /** The detectors it is attached to; an issue stream detector per project covers that project's issues. */
109
+ detectorIds: string[];
110
+ triggers: {
111
+ logicType: string;
112
+ conditions: SentryWorkflowCondition[];
113
+ };
114
+ actionFilters: SentryWorkflowActionFilter[];
115
+ }
116
+ /** A condition or action as a read returns it: the same shape, with the id Sentry gave it. */
117
+ type SentryWorkflowConditionRead = SentryWorkflowCondition & {
118
+ id?: string;
119
+ };
120
+ type SentryWorkflowActionRead = SentryWorkflowAction & {
121
+ id?: string;
122
+ };
123
+ interface SentryWorkflow extends SentryWorkflowInput {
124
+ id: string;
125
+ triggers: {
126
+ id?: string;
127
+ logicType: string;
128
+ conditions: SentryWorkflowConditionRead[];
129
+ };
130
+ actionFilters: Array<{
131
+ id?: string;
132
+ logicType: string;
133
+ conditions: SentryWorkflowConditionRead[];
134
+ actions: SentryWorkflowActionRead[];
135
+ }>;
136
+ }
137
+ interface SentryDetector {
138
+ id: string;
139
+ /** Null for an "all projects" detector. */
140
+ projectId: string | null;
141
+ name: string;
142
+ /** `issue_stream` receives every issue of its project; `error` is the error monitor. */
143
+ type: string;
144
+ workflowIds: string[];
145
+ }
146
+ //#endregion
48
147
  //#region ../../../../../src/index.d.ts
49
148
  declare function createSentryClient(opts: SentryClientOptions): {
50
149
  auth: {
@@ -57,7 +156,21 @@ declare function createSentryClient(opts: SentryClientOptions): {
57
156
  create: (org: string, team: string, input: CreateSentryProjectInput) => Promise<SentryProject>;
58
157
  keys: (org: string, slug: string) => Promise<SentryKey[]>;
59
158
  };
159
+ sentryApps: {
160
+ list: (org: string) => Promise<SentryApp[]>;
161
+ get: (slug: string) => Promise<SentryApp>;
162
+ create: (org: string, input: SentryAppInput) => Promise<SentryApp>;
163
+ update: (slug: string, input: SentryAppInput) => Promise<SentryApp>;
164
+ installations: (org: string) => Promise<SentryAppInstallation[]>;
165
+ };
166
+ workflows: {
167
+ list: (org: string) => Promise<SentryWorkflow[]>;
168
+ get: (org: string, id: string) => Promise<SentryWorkflow>;
169
+ create: (org: string, input: SentryWorkflowInput) => Promise<SentryWorkflow>;
170
+ update: (org: string, id: string, input: SentryWorkflowInput) => Promise<SentryWorkflow>;
171
+ detectors: (org: string) => Promise<SentryDetector[]>;
172
+ };
60
173
  };
61
174
  type SentryClient = ReturnType<typeof createSentryClient>;
62
175
  //#endregion
63
- export { type CreateSentryProjectInput, SentryClient, type SentryClientOptions, type SentryKey, type SentryKeyDsn, type SentryOrganization, type SentryProject, createSentryClient };
176
+ export { type CreateSentryProjectInput, type SentryApp, type SentryAppInput, type SentryAppInstallation, SentryClient, type SentryClientOptions, type SentryDetector, type SentryKey, type SentryKeyDsn, type SentryOrganization, type SentryProject, type SentryWorkflow, type SentryWorkflowAction, type SentryWorkflowActionFilter, type SentryWorkflowCondition, type SentryWorkflowInput, createSentryClient };
package/dist/index.mjs CHANGED
@@ -32,6 +32,51 @@ function projects(rest) {
32
32
  };
33
33
  }
34
34
  //#endregion
35
+ //#region ../../../../../src/sentry-apps/sentry-apps.ts
36
+ const seg$1 = encodeURIComponent;
37
+ function sentryApps(rest) {
38
+ return {
39
+ /** The custom integrations an organization has made. */
40
+ list: (org) => rest.request(`/organizations/${seg$1(org)}/sentry-apps/`),
41
+ /** One integration by slug or id. */
42
+ get: (slug) => rest.request(`/sentry-apps/${seg$1(slug)}/`),
43
+ /** Create an internal integration. It is installed on the organization at once. */
44
+ create: (org, input) => rest.request("/sentry-apps/", {
45
+ method: "POST",
46
+ body: {
47
+ name: input.name,
48
+ organization: org,
49
+ scopes: input.scopes,
50
+ isInternal: true,
51
+ isAlertable: input.isAlertable ?? false,
52
+ webhookUrl: input.webhookUrl ?? null,
53
+ events: [],
54
+ schema: {},
55
+ verifyInstall: false
56
+ }
57
+ }),
58
+ /**
59
+ * Update an integration (Sentry requires `name` and `scopes` on every update, and may reset a setting a PUT leaves
60
+ * out). A setting not given keeps its current value: it is read first, only when one is missing.
61
+ */
62
+ update: async (slug, input) => {
63
+ const current = input.isAlertable === void 0 || input.webhookUrl === void 0 ? await rest.request(`/sentry-apps/${seg$1(slug)}/`) : void 0;
64
+ return rest.request(`/sentry-apps/${seg$1(slug)}/`, {
65
+ method: "PUT",
66
+ body: {
67
+ name: input.name,
68
+ scopes: input.scopes,
69
+ isInternal: true,
70
+ isAlertable: input.isAlertable ?? current?.isAlertable ?? false,
71
+ webhookUrl: input.webhookUrl !== void 0 ? input.webhookUrl : current?.webhookUrl ?? null
72
+ }
73
+ });
74
+ },
75
+ /** The integrations installed on an organization, with the numeric app id that alert actions point at. */
76
+ installations: (org) => rest.request(`/organizations/${seg$1(org)}/sentry-app-installations/`)
77
+ };
78
+ }
79
+ //#endregion
35
80
  //#region ../../../../../src/utils.ts
36
81
  function createSentryRestClient(opts) {
37
82
  return createRestClient({
@@ -44,12 +89,38 @@ function createSentryRestClient(opts) {
44
89
  });
45
90
  }
46
91
  //#endregion
92
+ //#region ../../../../../src/workflows/workflows.ts
93
+ const seg = encodeURIComponent;
94
+ function workflows(rest) {
95
+ return {
96
+ /** The alert rules (workflows) of an organization. */
97
+ list: (org) => rest.request(`/organizations/${seg(org)}/workflows/`),
98
+ get: (org, id) => rest.request(`/organizations/${seg(org)}/workflows/${seg(id)}/`),
99
+ create: (org, input) => rest.request(`/organizations/${seg(org)}/workflows/`, {
100
+ method: "POST",
101
+ body: input
102
+ }),
103
+ /**
104
+ * Replace a workflow with the given body. Sentry offers no version or ETag to make this conditional, so a concurrent
105
+ * edit is overwritten: read, change and write back without another writer in between.
106
+ */
107
+ update: (org, id, input) => rest.request(`/organizations/${seg(org)}/workflows/${seg(id)}/`, {
108
+ method: "PUT",
109
+ body: input
110
+ }),
111
+ /** The detectors of an organization: what a workflow is attached to. */
112
+ detectors: (org) => rest.request(`/organizations/${seg(org)}/detectors/`)
113
+ };
114
+ }
115
+ //#endregion
47
116
  //#region ../../../../../src/index.ts
48
117
  function createSentryClient(opts) {
49
118
  const rest = createSentryRestClient(opts);
50
119
  return {
51
120
  auth: auth(rest),
52
- projects: projects(rest)
121
+ projects: projects(rest),
122
+ sentryApps: sentryApps(rest),
123
+ workflows: workflows(rest)
53
124
  };
54
125
  }
55
126
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/sentry-client",
3
- "version": "1.53.1",
3
+ "version": "1.54.1",
4
4
  "description": "A TypeScript client for the Sentry management API",
5
5
  "keywords": [
6
6
  "sentry",
@@ -33,7 +33,7 @@
33
33
  ],
34
34
  "dependencies": {
35
35
  "@theholocron/observability": "^0.3.0",
36
- "@theholocron/http-client": "^1.53.1"
36
+ "@theholocron/http-client": "^1.54.1"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@theholocron/cli": "5.0.0-alpha.79",