@serhii.mazur/directus-gu-logs 1.0.8 → 1.0.10

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
@@ -1,12 +1,12 @@
1
1
  # 📦 Custom Directus Logs Utility
2
2
 
3
- A lightweight utility class for logging errors, creating activity logs, and sending notifications in Directus extensions.
3
+ A modular utility for logging errors, tracking activity, and sending notifications (Directus in-app + Slack) from Directus extensions.
4
4
 
5
- ## 📋 Requirements:
5
+ ## 📋 Requirements
6
6
 
7
- ### 1. Logs Collection (logs)
7
+ ### 1. Logs Collection (`logs`)
8
8
 
9
- You need to create a collection named `logs` with the following fields:
9
+ Create a collection named `logs` with the following fields:
10
10
 
11
11
  | Field Name | Type | Description |
12
12
  | --------------- | --------- | ---------------------------- |
@@ -16,24 +16,38 @@ You need to create a collection named `logs` with the following fields:
16
16
  | `function_name` | string | Name of the function |
17
17
  | `error` | text/code | Error message or stack trace |
18
18
 
19
- ### 2. Global Collection (global)
19
+ ### 2. Global Collection (`global`)
20
20
 
21
- Add the following field to the `global` collection:
21
+ Add the following field to the `global` singleton collection:
22
22
 
23
- | Field Name | Type | Description |
24
- | ------------------------ | ------------------------------- | ---------------------------------- |
25
- | `error_notice_recipient` | m2m → directus_users (junction) | Recipients for error notifications |
23
+ | Field Name | Type | Description |
24
+ | ------------------------ | ------------------------------- | -------------------------------------------- |
25
+ | `error_notice_recipient` | m2m → directus_users (junction) | Recipients for error notifications |
26
+ | `slack_webhook_url` | input | Incoming webhook URL for Slack notifications |
27
+ | `slack_notifications` | boolen | Toggle Slack notifications |
26
28
 
27
- All linked users from this field will receive internal notifications when errors occur.
29
+ All linked users will receive internal Directus notifications when errors occur (unless a recipient override is passed).
30
+
31
+ ### 3. Environment Variables
32
+
33
+ | Variable | Required | Description |
34
+ | ------------- | -------- | ------------------------------------------------------------- |
35
+ | `BACKEND_URL` | Optional | Shown in notifications; falls back to `PUBLIC_URL` |
36
+ | `BRANCH` | Optional | Environment label (e.g. `main`, `staging`); defaults to `dev` |
37
+
38
+ ---
28
39
 
29
40
  ## 🚀 Features
30
41
 
31
- - Save structured error logs to a collection
42
+ - Persist structured error logs to a Directus collection
43
+ - Send Slack Block Kit notifications via incoming webhook
44
+ - Send Directus in-app notifications with automatic recipient resolution
32
45
  - Create Directus activity records
33
- - Send internal Directus notifications
34
- - Automatic recipient resolution from global settings
35
- - Support for manual recipient override
36
- - Includes project metadata in notifications (environment, URL, timestamp)
46
+ - Shared project metadata (name, URL, environment) injected into all notifications
47
+ - Recipient deduplication
48
+ - Graceful fallbacks all methods catch and log their own errors
49
+
50
+ ---
37
51
 
38
52
  ## 📦 Installation
39
53
 
@@ -41,36 +55,84 @@ All linked users from this field will receive internal notifications when errors
41
55
  npm i @serhii.mazur/directus-gu-logs
42
56
  ```
43
57
 
58
+ ---
59
+
60
+ ## 🗂 Architecture
61
+
62
+ The package is split into three focused classes:
63
+
64
+ ```
65
+ Logs ← main entry point; orchestrates everything
66
+ ├── SlackNotifier ← formats and sends Slack Block Kit payloads
67
+ └── DirectusNotifier ← resolves recipients, sends in-app notifications
68
+ ```
69
+
70
+ All three are exported and can be used independently if needed.
71
+
72
+ ---
73
+
44
74
  ## 🧩 Usage
45
75
 
76
+ ### Basic (via `Logs`)
77
+
46
78
  ```ts
47
79
  import { Logs } from "@serhii.mazur/directus-gu-logs";
48
80
 
49
81
  const logs = new Logs(context, "my-extension");
50
82
 
51
- // Save error log
52
- await logs.printLogs("myFunction", "Something went wrong");
83
+ // Persist error log to DB + notify Slack
84
+ await logs.logError("myFunction", "Something went wrong", true);
53
85
 
54
- // Create activity record
86
+ // Persist error log without Slack notification
87
+ await logs.logError("myFunction", "Something went wrong");
88
+
89
+ // Create a Directus activity record
55
90
  await logs.createActivity("create", "collection_name", "item_id");
56
91
 
57
- // Send notification (auto recipients from global settings)
92
+ // Send Directus in-app notification (recipients from global settings)
58
93
  await logs.createNotification("An error occurred");
59
94
 
60
- // Send notification with custom subject
95
+ // With custom subject
61
96
  await logs.createNotification("An error occurred", "Custom Subject");
62
97
 
63
- // Send notification to specific recipient
98
+ // Override recipient
64
99
  await logs.createNotification("An error occurred", "Custom Subject", "user_id");
65
100
 
66
- // Send notification with related collection + item
101
+ // With linked collection + item
67
102
  await logs.createNotification("An error occurred", "Custom Subject", null, "collection_name", "item_id");
103
+
104
+ // Send Slack notification directly
105
+ await logs.notifySlack("Deploy failed on main", "CI Alert");
106
+ ```
107
+
108
+ ### Advanced (direct class usage)
109
+
110
+ ```ts
111
+ import { SlackNotifier, DirectusNotifier } from "@serhii.mazur/directus-gu-logs";
112
+
113
+ // Use SlackNotifier standalone
114
+ const slack = new SlackNotifier(context, "my-extension");
115
+ await slack.notify("Custom message", "Alert Title", projectMeta);
116
+
117
+ // Send a raw Slack payload
118
+ await slack.send({ text: "plain fallback", blocks: [...] });
119
+
120
+ // Use DirectusNotifier standalone
121
+ const directus = new DirectusNotifier(context, "my-extension");
122
+ await directus.notify("Message", projectMeta, {
123
+ subject: "Custom Subject",
124
+ recipientOverride: "user-uuid",
125
+ collection: "pages",
126
+ item: "42",
127
+ });
68
128
  ```
69
129
 
70
130
  ---
71
131
 
72
132
  ## ⚙️ Constructor
73
133
 
134
+ ### `Logs`
135
+
74
136
  ```ts
75
137
  constructor(
76
138
  context: ApiExtensionContext,
@@ -79,44 +141,42 @@ constructor(
79
141
  )
80
142
  ```
81
143
 
82
- | Param | Type | Description |
83
- | ---------------- | ------------------- | ------------------------------------ |
84
- | `context` | ApiExtensionContext | Directus extension context |
85
- | `extension` | string | Name of your extension |
86
- | `collectionName` | string (optional) | Logs collection name (default: logs) |
144
+ | Param | Type | Default | Description |
145
+ | ---------------- | ------------------- | ------- | -------------------------------- |
146
+ | `context` | ApiExtensionContext | — | Directus extension context |
147
+ | `extension` | string | — | Name of your extension |
148
+ | `collectionName` | string | `logs` | Target collection for error logs |
149
+
150
+ ---
87
151
 
88
152
  ## 🧠 How It Works
89
153
 
90
- ### Logging
154
+ ### `logError(functionName, error, notifySlack?)`
91
155
 
92
- - Saves structured logs into the logs collection
93
- - Automatically attaches timestamp and extension name
94
- - Fallback to console if DB write fails
156
+ 1. Writes a structured entry to the logs collection
157
+ 2. If `notifySlack` is `true` (default), sends a Slack Block Kit message with extension name, function, and error
158
+ 3. Always writes to `context.logger.error` as well
159
+ 4. Catches its own failure — a broken DB write won't throw
95
160
 
96
- ---
161
+ ### `createActivity(action, collection, id)`
97
162
 
98
- ### Activity Tracking
163
+ Creates a Directus activity record via `ActivityService`. User, IP, and user agent are set to `null` in extension hook context where no accountability is available.
99
164
 
100
- - Uses Directus ActivityService
101
- - Automatically includes:
102
- - user
103
- - IP address
104
- - user agent
105
- - origin
165
+ ### `createNotification(message, subject?, recipientOverride?, collection?, item?)`
106
166
 
107
- ---
167
+ Resolves recipients in this order:
168
+
169
+ 1. `recipientOverride` (if provided)
170
+ 2. `global.error_notice_recipient` linked users
171
+
172
+ Then sends one in-app notification per recipient. Each notification includes:
108
173
 
109
- ### Notifications
110
-
111
- - Resolves recipients in this order:
112
- 1. Explicit recipientOverride
113
- 2. global.notice_recipient
114
- 3. global.developer_notice_recipient
115
- - Automatically:
116
- - Deduplicates recipients
117
- - Includes project metadata:
118
- - Project name
119
- - Backend URL
120
- - Environment (branch)
121
- - UTC timestamp
122
- - Sends one notification per recipient
174
+ - Custom or default subject with the project name appended
175
+ - The message body
176
+ - Environment label, backend URL, and UTC timestamp
177
+
178
+ ### `notifySlack(message, subject?)`
179
+
180
+ Sends a formatted Slack Block Kit message via `slack_webhook_url`. Silently skips if the var is not set. The payload includes project name, environment, extension name, backend URL, and the message body.
181
+
182
+ ---
package/dist/index.d.ts CHANGED
@@ -1,13 +1,3 @@
1
- import { ApiExtensionContext } from "@directus/extensions";
2
- import type { PrimaryKey } from "@directus/types";
3
- export declare class Logs {
4
- protected context: ApiExtensionContext;
5
- protected extension: string;
6
- protected collectionName: string;
7
- constructor(context: ApiExtensionContext, extension: string, collectionName?: string);
8
- private getSchema;
9
- private createOne;
10
- printLogs(functionName: string, error: string): Promise<void>;
11
- createActivity(action: string, collection: string, id: PrimaryKey): Promise<void>;
12
- createNotification(message: string, customSubject?: string | null, recipientOverride?: string | null, collection?: string | null, item?: string | null): Promise<void>;
13
- }
1
+ export { Logs } from "./services/Logs";
2
+ export { SlackNotifier } from "./services/SlackNotifier";
3
+ export type { LogEntry, RecipientLinkRow, ProjectMeta, SlackBlock, SlackPayload, ApiExtensionContext, PrimaryKey, } from "./types";
package/dist/index.js CHANGED
@@ -1,133 +1,2 @@
1
- function extractRecipientIds(rows) {
2
- const recipients = rows
3
- ?.map(row => {
4
- const user = row?.directus_users_id;
5
- if (typeof user === "string")
6
- return user;
7
- return typeof user?.id === "string" ? user.id : null;
8
- })
9
- .filter((value) => Boolean(value)) ?? [];
10
- return Array.from(new Set(recipients));
11
- }
12
- export class Logs {
13
- constructor(context, extension, collectionName = "logs") {
14
- this.context = context;
15
- this.extension = extension;
16
- this.collectionName = collectionName;
17
- }
18
- async getSchema() {
19
- return await this.context.getSchema();
20
- }
21
- async createOne(data) {
22
- const schema = await this.getSchema();
23
- const itemsService = new this.context.services.ItemsService(this.collectionName, {
24
- database: this.context.database,
25
- schema,
26
- });
27
- return await itemsService.createOne(data);
28
- }
29
- async printLogs(functionName, error) {
30
- const data = {
31
- collection: this.collectionName,
32
- date_created: new Date().toISOString(),
33
- extension: this.extension,
34
- function_name: functionName,
35
- error,
36
- };
37
- try {
38
- await this.createOne(data);
39
- console.error(`🚀 [${this.extension}] ${functionName}:`, error);
40
- }
41
- catch (error) {
42
- console.error("❌ Failed to save logs:", error);
43
- }
44
- }
45
- async createActivity(action, collection, id) {
46
- try {
47
- const schema = await this.getSchema();
48
- const services = this.context.services;
49
- const accountability = services.accountability;
50
- const activityService = new services.ActivityService({
51
- schema: schema,
52
- accountability: accountability,
53
- knex: this.context.database,
54
- });
55
- await activityService.createOne({
56
- action,
57
- user: accountability?.user ?? null,
58
- collection,
59
- ip: accountability?.ip ?? null,
60
- user_agent: accountability?.userAgent ?? null,
61
- origin: accountability?.origin ?? null,
62
- item: id,
63
- });
64
- }
65
- catch (error) {
66
- console.error("❌ Failed to create activity log:", error);
67
- }
68
- }
69
- async createNotification(message, customSubject = null, recipientOverride = null, collection = null, item = null) {
70
- try {
71
- const schema = await this.getSchema();
72
- const { database, services } = this.context;
73
- const globalService = new services.ItemsService("global", {
74
- database,
75
- schema,
76
- });
77
- // Check for passed recipient, fallback to global settings
78
- let recipients = [];
79
- if (recipientOverride) {
80
- recipients = [recipientOverride];
81
- }
82
- else {
83
- const globalSettings = await globalService.readSingleton({
84
- fields: ["error_notice_recipient.directus_users_id.id"],
85
- });
86
- recipients = extractRecipientIds(globalSettings?.error_notice_recipient);
87
- }
88
- if (recipients.length === 0) {
89
- this.printLogs(this.extension, "No recipients defined (override or global settings)");
90
- return;
91
- }
92
- const notificationService = new services.NotificationsService({ schema });
93
- // Project Data
94
- const settings = await database.select("project_name").from("directus_settings").first();
95
- const projectName = settings?.project_name || "Unknown Project";
96
- const backendUrl = process.env.BACKEND_URL || this.context.env?.PUBLIC_URL || "Unknown URL";
97
- const environment = process.env.BRANCH || "dev";
98
- const now = new Date();
99
- const timestamp = new Intl.DateTimeFormat("en-US", {
100
- month: "2-digit",
101
- day: "2-digit",
102
- year: "numeric",
103
- hour: "2-digit",
104
- minute: "2-digit",
105
- hour12: false,
106
- timeZone: "UTC",
107
- }).format(now);
108
- // Compose subject & message
109
- const subject = customSubject
110
- ? `${customSubject} - ${projectName}`
111
- : `Directus Error Notification - ${projectName}`;
112
- const fullMessage = `
113
- ${message}<br><br>
114
- <strong>Environment:</strong> ${environment}<br>
115
- <strong>Backend URL:</strong> <a href="${backendUrl}" target="_blank">${backendUrl}</a><br>
116
- <strong>Date/Time (UTC):</strong> ${timestamp}
117
- `.trim();
118
- for (const recipient of recipients) {
119
- await notificationService.createOne({
120
- recipient,
121
- sender: recipient,
122
- subject,
123
- message: fullMessage,
124
- collection,
125
- item,
126
- });
127
- }
128
- }
129
- catch (error) {
130
- console.error("❌ Failed to create notification:", error);
131
- }
132
- }
133
- }
1
+ export { Logs } from "./services/Logs";
2
+ export { SlackNotifier } from "./services/SlackNotifier";
@@ -0,0 +1,3 @@
1
+ export { Logs } from "./services/Logs";
2
+ export { SlackNotifier } from "./services/SlackNotifier";
3
+ export type { LogEntry, RecipientLinkRow, ProjectMeta, SlackBlock, SlackPayload, ApiExtensionContext, PrimaryKey, } from "./types";
@@ -0,0 +1,2 @@
1
+ export { Logs } from "./services/Logs";
2
+ export { SlackNotifier } from "./services/SlackNotifier";
@@ -0,0 +1,13 @@
1
+ import type { ApiExtensionContext, ProjectMeta } from "../types";
2
+ export declare class DirectusNotifier {
3
+ private readonly context;
4
+ private readonly extension;
5
+ constructor(context: ApiExtensionContext, extension: string);
6
+ private getSchema;
7
+ notify(message: string, meta: ProjectMeta, options?: {
8
+ subject?: string | null;
9
+ recipientOverride?: string | null;
10
+ collection?: string | null;
11
+ item?: string | null;
12
+ }): Promise<void>;
13
+ }
@@ -0,0 +1,77 @@
1
+ function extractRecipientIds(rows) {
2
+ const recipients = rows
3
+ ?.map(row => {
4
+ const user = row?.directus_users_id;
5
+ if (typeof user === "string")
6
+ return user;
7
+ return typeof user?.id === "string" ? user.id : null;
8
+ })
9
+ .filter((v) => Boolean(v)) ?? [];
10
+ return Array.from(new Set(recipients));
11
+ }
12
+ export class DirectusNotifier {
13
+ constructor(context, extension) {
14
+ this.context = context;
15
+ this.extension = extension;
16
+ }
17
+ async getSchema() {
18
+ return this.context.getSchema();
19
+ }
20
+ async notify(message, meta, options = {}) {
21
+ const { subject = null, recipientOverride = null, collection = null, item = null } = options;
22
+ try {
23
+ const schema = await this.getSchema();
24
+ const { database, services } = this.context;
25
+ let recipients = [];
26
+ if (recipientOverride) {
27
+ recipients = [recipientOverride];
28
+ }
29
+ else {
30
+ const globalService = new services.ItemsService("global", { database, schema });
31
+ const globalSettings = await globalService.readSingleton({
32
+ fields: ["error_notice_recipient.directus_users_id.id"],
33
+ });
34
+ recipients = extractRecipientIds(globalSettings?.error_notice_recipient);
35
+ }
36
+ if (recipients.length === 0) {
37
+ this.context.logger.warn({
38
+ msg: `[${this.extension}] No notification recipients defined (override or global settings)`,
39
+ });
40
+ return;
41
+ }
42
+ const notificationService = new services.NotificationsService({ schema });
43
+ const now = new Date();
44
+ const timestamp = new Intl.DateTimeFormat("en-US", {
45
+ month: "2-digit",
46
+ day: "2-digit",
47
+ year: "numeric",
48
+ hour: "2-digit",
49
+ minute: "2-digit",
50
+ hour12: false,
51
+ timeZone: "UTC",
52
+ }).format(now);
53
+ const resolvedSubject = subject
54
+ ? `${subject} - ${meta.projectName}`
55
+ : `Directus Error Notification - ${meta.projectName}`;
56
+ const fullMessage = [
57
+ message,
58
+ `<strong>Environment:</strong> ${meta.environment}`,
59
+ `<strong>Backend URL:</strong> <a href="${meta.backendUrl}" target="_blank">${meta.backendUrl}</a>`,
60
+ `<strong>Date/Time (UTC):</strong> ${timestamp}`,
61
+ ].join("<br><br>");
62
+ for (const recipient of recipients) {
63
+ await notificationService.createOne({
64
+ recipient,
65
+ sender: recipient,
66
+ subject: resolvedSubject,
67
+ message: fullMessage,
68
+ collection,
69
+ item,
70
+ });
71
+ }
72
+ }
73
+ catch (error) {
74
+ this.context.logger.error({ msg: "❌ Failed to send Directus notification", error });
75
+ }
76
+ }
77
+ }
@@ -0,0 +1,23 @@
1
+ import type { ApiExtensionContext, PrimaryKey } from "../types";
2
+ import { SlackNotifier } from "../services/SlackNotifier";
3
+ import { DirectusNotifier } from "./DirectusNotifier";
4
+ export declare class Logs {
5
+ protected readonly context: ApiExtensionContext;
6
+ protected readonly extension: string;
7
+ protected readonly collectionName: string;
8
+ protected readonly slack: SlackNotifier;
9
+ protected readonly directus: DirectusNotifier;
10
+ constructor(context: ApiExtensionContext, extension: string, collectionName?: string);
11
+ private getSchema;
12
+ private getProjectMeta;
13
+ private createOne;
14
+ /**
15
+ * Persists an error entry to the logs collection, optionally notifying via Slack.
16
+ */
17
+ logError(functionName: string, error: string, notifySlack?: boolean): Promise<void>;
18
+ /** @deprecated Use logError() instead */
19
+ printLogs(functionName: string, error: string, notifySlack?: boolean): Promise<void>;
20
+ createActivity(action: string, collection: string, id: PrimaryKey): Promise<void>;
21
+ createNotification(message: string, customSubject?: string | null, recipientOverride?: string | null, collection?: string | null, item?: string | null, notifySlack?: boolean): Promise<void>;
22
+ notifySlack(message: string, customSubject?: string | null): Promise<void>;
23
+ }
@@ -0,0 +1,118 @@
1
+ import { SlackNotifier } from "../services/SlackNotifier";
2
+ import { DirectusNotifier } from "./DirectusNotifier";
3
+ export class Logs {
4
+ constructor(context, extension, collectionName = "logs") {
5
+ this.context = context;
6
+ this.extension = extension;
7
+ this.collectionName = collectionName;
8
+ this.slack = new SlackNotifier(context, extension);
9
+ this.directus = new DirectusNotifier(context, extension);
10
+ }
11
+ async getSchema() {
12
+ return this.context.getSchema();
13
+ }
14
+ async getProjectMeta() {
15
+ const settings = await this.context.database.select("project_name").from("directus_settings").first();
16
+ return {
17
+ projectName: settings?.project_name ?? "Unknown Project",
18
+ backendUrl: process.env.BACKEND_URL ?? this.context.env?.PUBLIC_URL ?? "Unknown URL",
19
+ environment: process.env.BRANCH ?? "dev",
20
+ timestamp: new Intl.DateTimeFormat("en-US", {
21
+ month: "2-digit",
22
+ day: "2-digit",
23
+ year: "numeric",
24
+ hour: "2-digit",
25
+ minute: "2-digit",
26
+ hour12: false,
27
+ timeZone: "UTC",
28
+ }).format(new Date()),
29
+ };
30
+ }
31
+ async createOne(data) {
32
+ const schema = await this.getSchema();
33
+ const itemsService = new this.context.services.ItemsService(this.collectionName, {
34
+ database: this.context.database,
35
+ schema,
36
+ });
37
+ return await itemsService.createOne(data);
38
+ }
39
+ /**
40
+ * Persists an error entry to the logs collection, optionally notifying via Slack.
41
+ */
42
+ async logError(functionName, error, notifySlack = false) {
43
+ const data = {
44
+ collection: this.collectionName,
45
+ date_created: new Date().toISOString(),
46
+ extension: this.extension,
47
+ function_name: functionName,
48
+ error,
49
+ };
50
+ try {
51
+ await this.createOne(data);
52
+ this.context.logger.error({ msg: `🚀 [${this.extension}] ${functionName}:`, error });
53
+ }
54
+ catch (err) {
55
+ this.context.logger.error({ msg: "❌ Failed to save log entry", error: err });
56
+ return;
57
+ }
58
+ try {
59
+ if (notifySlack) {
60
+ const meta = await this.getProjectMeta();
61
+ await this.slack.notify(`*Function:* ${functionName}\n*Error:* ${error}`, meta, "Extension Error");
62
+ }
63
+ }
64
+ catch (err) {
65
+ this.context.logger.error({ msg: "Slack failed", error: err });
66
+ }
67
+ }
68
+ /** @deprecated Use logError() instead */
69
+ async printLogs(functionName, error, notifySlack = false) {
70
+ return this.logError(functionName, error, notifySlack);
71
+ }
72
+ async createActivity(action, collection, id) {
73
+ try {
74
+ const schema = await this.getSchema();
75
+ const { services, database } = this.context;
76
+ const accountability = services.accountability;
77
+ const activityService = new services.ActivityService({
78
+ schema,
79
+ accountability,
80
+ knex: database,
81
+ });
82
+ await activityService.createOne({
83
+ action,
84
+ user: accountability?.user ?? null,
85
+ collection,
86
+ ip: accountability?.ip ?? null,
87
+ user_agent: accountability?.userAgent ?? null,
88
+ origin: accountability?.origin ?? null,
89
+ item: id,
90
+ });
91
+ }
92
+ catch (err) {
93
+ this.context.logger.error({ msg: "❌ Failed to create activity log", error: err });
94
+ }
95
+ }
96
+ async createNotification(message, customSubject = null, recipientOverride = null, collection = null, item = null, notifySlack = true) {
97
+ const meta = await this.getProjectMeta();
98
+ await this.directus.notify(message, meta, {
99
+ subject: customSubject,
100
+ recipientOverride,
101
+ collection,
102
+ item,
103
+ });
104
+ try {
105
+ if (notifySlack) {
106
+ const meta = await this.getProjectMeta();
107
+ await this.slack.notify(`*Error:* ${message}`, meta, customSubject);
108
+ }
109
+ }
110
+ catch (err) {
111
+ this.context.logger.error({ msg: "Slack failed", error: err });
112
+ }
113
+ }
114
+ async notifySlack(message, customSubject = null) {
115
+ const meta = await this.getProjectMeta();
116
+ await this.slack.notify(message, meta, customSubject);
117
+ }
118
+ }
@@ -0,0 +1,15 @@
1
+ import type { ApiExtensionContext, ProjectMeta, SlackPayload } from "../types";
2
+ export declare class SlackNotifier {
3
+ private readonly context;
4
+ private readonly extension;
5
+ private configCache;
6
+ private configCachedAt;
7
+ private readonly CONFIG_CACHE_TTL;
8
+ constructor(context: ApiExtensionContext, extension: string);
9
+ private getEnvironmentIcon;
10
+ private buildBlocks;
11
+ send(payload: SlackPayload): Promise<void>;
12
+ notify(message: string, meta: ProjectMeta, subject?: string | null): Promise<void>;
13
+ private getConfig;
14
+ clearConfigCache(): void;
15
+ }