@betternotify/selligent 1.0.0-beta.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/LICENSE +21 -0
- package/README.md +104 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.js +205 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Better-Notify contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# @betternotify/selligent
|
|
2
|
+
|
|
3
|
+
[Selligent (Marigold Engage)](https://www.marigold.com/products/marigold-engage) transactional email transport for [Better-Notify](https://github.com/better-notify/better-notify). Delivers rendered emails through the Selligent Delivery Cloud (SDC) `POST /email/v1/messages/send` API.
|
|
4
|
+
|
|
5
|
+
<p>
|
|
6
|
+
<a href="https://better-notify.com">Website</a> ·
|
|
7
|
+
<a href="https://better-notify.com/docs">Docs</a> ·
|
|
8
|
+
<a href="https://github.com/better-notify/better-notify">GitHub</a> ·
|
|
9
|
+
<a href="https://x.com/better_notify">X</a>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm install @betternotify/selligent @betternotify/core @betternotify/email
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { createNotify, createClient } from '@betternotify/core';
|
|
22
|
+
import { emailChannel } from '@betternotify/email';
|
|
23
|
+
import { selligentTransport } from '@betternotify/selligent';
|
|
24
|
+
|
|
25
|
+
const email = emailChannel({
|
|
26
|
+
defaults: { from: { name: 'My App', email: 'noreply@example.com' } },
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const rpc = createNotify({ channels: { email } });
|
|
30
|
+
const catalog = rpc.catalog({
|
|
31
|
+
/* routes */
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const mail = createClient({
|
|
35
|
+
catalog,
|
|
36
|
+
channels: { email },
|
|
37
|
+
transportsByChannel: {
|
|
38
|
+
email: selligentTransport({
|
|
39
|
+
clientId: Number(process.env.SELLIGENT_CLIENT_ID!),
|
|
40
|
+
clientSecret: process.env.SELLIGENT_CLIENT_SECRET!,
|
|
41
|
+
accountId: process.env.SELLIGENT_ACCOUNT_ID!,
|
|
42
|
+
}),
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Alternatively, if you already manage OAuth tokens externally:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
selligentTransport({
|
|
51
|
+
getAccessToken: () => myTokenManager.getToken(),
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Options
|
|
56
|
+
|
|
57
|
+
| Field | Type | Description |
|
|
58
|
+
| -------------- | ---------- | ------------------------------------------------------------------------------------ |
|
|
59
|
+
| `clientId` | `number` | Selligent OAuth client ID. Required (unless using `getAccessToken`). |
|
|
60
|
+
| `clientSecret` | `string` | Selligent OAuth client secret. Required (unless using `getAccessToken`). |
|
|
61
|
+
| `accountId` | `string` | Selligent account ID. Required (unless using `getAccessToken`). |
|
|
62
|
+
| `getAccessToken` | `() => Promise<string>` | Provide your own token. Mutually exclusive with OAuth credentials. |
|
|
63
|
+
| `baseUrl` | `string` | Override the SDC API base URL. Defaults to `https://sdc.slgnt.eu`. |
|
|
64
|
+
| `authUrl` | `string` | Override the OAuth token endpoint. Defaults to `https://auth.slgnt.eu/oauth/token`. |
|
|
65
|
+
| `audience` | `string` | Override the OAuth audience. Defaults to `https://sdc.slgnt.eu`. |
|
|
66
|
+
| `logger` | `object` | Optional `LoggerLike`. Defaults to `consoleLogger()`. |
|
|
67
|
+
| `http` | `object` | HTTP behavior options (retry, timeout, hooks). |
|
|
68
|
+
|
|
69
|
+
## Authentication
|
|
70
|
+
|
|
71
|
+
Unlike API-key-based transports, Selligent uses **OAuth 2.0 client credentials**. The transport handles token management automatically — it fetches a token before the first send, caches it, and refreshes it when it's about to expire (within 60 seconds of expiry).
|
|
72
|
+
|
|
73
|
+
The `verify()` method tests credentials by attempting a token fetch without sending any email.
|
|
74
|
+
|
|
75
|
+
## Per-send overrides
|
|
76
|
+
|
|
77
|
+
Pass Selligent-specific fields per-send via the `transport` key in `.send()`:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
await mail.welcome.send({
|
|
81
|
+
to: 'user@example.com',
|
|
82
|
+
input: { name: 'Jane' },
|
|
83
|
+
transport: {
|
|
84
|
+
selligent: {
|
|
85
|
+
profile: 'crm-id-123',
|
|
86
|
+
tags: ['campaign-spring'],
|
|
87
|
+
metadata: '{"ref": 123}',
|
|
88
|
+
list_unsubscribe: '<https://example.com/unsub>',
|
|
89
|
+
custom_send_time: '2025-09-02T12:00:00',
|
|
90
|
+
time_to_live: 'P2D',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Caveats
|
|
97
|
+
|
|
98
|
+
SDC sends message content without any modifications — it does not inject tracking pixels or rewrite links. Open/click tracking must be implemented by the sender.
|
|
99
|
+
|
|
100
|
+
The following `RenderedMessage` fields are not part of SDC's send schema and are silently dropped: `cc`, `bcc`, custom `headers`, `tags`, `priority`, and `inlineAssets`.
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { LoggerLike } from "@betternotify/core";
|
|
2
|
+
import { HttpClientBehaviorOptions } from "@betternotify/core/transports";
|
|
3
|
+
import * as _betternotify_email0 from "@betternotify/email";
|
|
4
|
+
|
|
5
|
+
//#region src/types.d.ts
|
|
6
|
+
type SelligentOAuthCredentials = {
|
|
7
|
+
clientId: number;
|
|
8
|
+
clientSecret: string;
|
|
9
|
+
accountId: string;
|
|
10
|
+
};
|
|
11
|
+
type SelligentTransportOptions = {
|
|
12
|
+
baseUrl?: string;
|
|
13
|
+
authUrl?: string;
|
|
14
|
+
audience?: string;
|
|
15
|
+
logger?: LoggerLike;
|
|
16
|
+
http?: HttpClientBehaviorOptions;
|
|
17
|
+
} & (SelligentOAuthCredentials | {
|
|
18
|
+
getAccessToken: () => Promise<string>;
|
|
19
|
+
});
|
|
20
|
+
type SelligentAddress = {
|
|
21
|
+
alias: string;
|
|
22
|
+
address: string;
|
|
23
|
+
};
|
|
24
|
+
type SelligentAttachment = {
|
|
25
|
+
file_name: string;
|
|
26
|
+
data: string;
|
|
27
|
+
mime_type: string;
|
|
28
|
+
};
|
|
29
|
+
type SelligentMessageContext = {
|
|
30
|
+
profile: string;
|
|
31
|
+
tags?: string[];
|
|
32
|
+
metadata?: string;
|
|
33
|
+
};
|
|
34
|
+
type SelligentMessageItem = {
|
|
35
|
+
reference: string;
|
|
36
|
+
content: {
|
|
37
|
+
html: string;
|
|
38
|
+
text?: string;
|
|
39
|
+
attachments?: SelligentAttachment[];
|
|
40
|
+
};
|
|
41
|
+
headers: {
|
|
42
|
+
subject: string;
|
|
43
|
+
to: SelligentAddress;
|
|
44
|
+
from: SelligentAddress;
|
|
45
|
+
reply?: SelligentAddress;
|
|
46
|
+
list_unsubscribe?: string;
|
|
47
|
+
};
|
|
48
|
+
context: SelligentMessageContext;
|
|
49
|
+
custom_send_time?: string;
|
|
50
|
+
time_to_live?: string;
|
|
51
|
+
};
|
|
52
|
+
type SelligentTokenResponse = {
|
|
53
|
+
access_token: string;
|
|
54
|
+
token_type: string;
|
|
55
|
+
refresh_token: string;
|
|
56
|
+
scope: string;
|
|
57
|
+
expires_in: number;
|
|
58
|
+
};
|
|
59
|
+
type SelligentErrorResponse = {
|
|
60
|
+
error_message?: string;
|
|
61
|
+
};
|
|
62
|
+
type SelligentPerSendData = {
|
|
63
|
+
profile?: string;
|
|
64
|
+
tags?: string[];
|
|
65
|
+
metadata?: string;
|
|
66
|
+
reference?: string;
|
|
67
|
+
list_unsubscribe?: string;
|
|
68
|
+
custom_send_time?: string;
|
|
69
|
+
time_to_live?: string;
|
|
70
|
+
};
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/is-retriable.d.ts
|
|
73
|
+
declare const isSelligentRetriable: (err: unknown) => boolean;
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/index.d.ts
|
|
76
|
+
declare module '@betternotify/core' {
|
|
77
|
+
interface TransportDataMap {
|
|
78
|
+
selligent: SelligentPerSendData;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** @experimental Selligent (Marigold Engage) transport using the SDC transactional email API. */
|
|
82
|
+
declare const selligentTransport: (opts: SelligentTransportOptions) => _betternotify_email0.Transport;
|
|
83
|
+
//#endregion
|
|
84
|
+
export { type SelligentAddress, type SelligentAttachment, type SelligentErrorResponse, type SelligentMessageContext, type SelligentMessageItem, type SelligentOAuthCredentials, type SelligentPerSendData, type SelligentTokenResponse, type SelligentTransportOptions, isSelligentRetriable, selligentTransport };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { createTransport, normalizeAddress } from "@betternotify/email/transports";
|
|
2
|
+
import { NotifyRpcError, NotifyRpcProviderError, consoleLogger, handlePromise } from "@betternotify/core";
|
|
3
|
+
import { createHttpClient, mapHttpStatus } from "@betternotify/core/transports";
|
|
4
|
+
//#region src/is-retriable.ts
|
|
5
|
+
const isSelligentRetriable = (err) => {
|
|
6
|
+
if (err instanceof NotifyRpcProviderError) return err.retriable;
|
|
7
|
+
return true;
|
|
8
|
+
};
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/index.ts
|
|
11
|
+
const DEFAULT_BASE_URL = "https://sdc.slgnt.eu";
|
|
12
|
+
const DEFAULT_AUTH_URL = "https://auth.slgnt.eu/oauth/token";
|
|
13
|
+
const DEFAULT_AUDIENCE = "https://sdc.slgnt.eu";
|
|
14
|
+
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
15
|
+
const TOKEN_REFRESH_BUFFER_MS = 6e4;
|
|
16
|
+
const toBase64 = (content) => {
|
|
17
|
+
if (Buffer.isBuffer(content)) return content.toString("base64");
|
|
18
|
+
return Buffer.from(content).toString("base64");
|
|
19
|
+
};
|
|
20
|
+
const toSelligentAddress = (addr) => {
|
|
21
|
+
if (typeof addr === "string") return {
|
|
22
|
+
alias: "",
|
|
23
|
+
address: addr
|
|
24
|
+
};
|
|
25
|
+
return {
|
|
26
|
+
alias: addr.name ?? "",
|
|
27
|
+
address: addr.email
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
const toAttachments = (attachments) => attachments.map((att) => ({
|
|
31
|
+
file_name: att.filename,
|
|
32
|
+
data: toBase64(att.content),
|
|
33
|
+
mime_type: att.contentType ?? "application/octet-stream"
|
|
34
|
+
}));
|
|
35
|
+
const hasOAuthCredentials = (opts) => "clientId" in opts;
|
|
36
|
+
const createTokenManager = (opts) => {
|
|
37
|
+
if (!hasOAuthCredentials(opts)) return { getToken: opts.getAccessToken };
|
|
38
|
+
const authUrl = opts.authUrl ?? DEFAULT_AUTH_URL;
|
|
39
|
+
const audience = opts.audience ?? DEFAULT_AUDIENCE;
|
|
40
|
+
let cachedToken;
|
|
41
|
+
let expiresAt = 0;
|
|
42
|
+
const getToken = async () => {
|
|
43
|
+
if (cachedToken && Date.now() < expiresAt - TOKEN_REFRESH_BUFFER_MS) return cachedToken;
|
|
44
|
+
const res = await fetch(authUrl, {
|
|
45
|
+
method: "POST",
|
|
46
|
+
headers: { "Content-Type": "application/json" },
|
|
47
|
+
body: JSON.stringify({
|
|
48
|
+
client_id: opts.clientId,
|
|
49
|
+
client_secret: opts.clientSecret,
|
|
50
|
+
account_id: opts.accountId,
|
|
51
|
+
grant_type: "client_credentials",
|
|
52
|
+
audience
|
|
53
|
+
})
|
|
54
|
+
});
|
|
55
|
+
if (!res.ok) {
|
|
56
|
+
const [, body] = await handlePromise(res.text());
|
|
57
|
+
throw new NotifyRpcProviderError({
|
|
58
|
+
message: `Selligent transport: OAuth token request failed [${res.status}]${body ? `: ${body}` : ""}`,
|
|
59
|
+
code: "CONFIG",
|
|
60
|
+
provider: "selligent",
|
|
61
|
+
httpStatus: res.status,
|
|
62
|
+
retriable: res.status >= 500
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
const data = await res.json();
|
|
66
|
+
cachedToken = data.access_token;
|
|
67
|
+
expiresAt = Date.now() + data.expires_in * 1e3;
|
|
68
|
+
return cachedToken;
|
|
69
|
+
};
|
|
70
|
+
return { getToken };
|
|
71
|
+
};
|
|
72
|
+
const buildMessageItems = (message, from, ctx, perSend) => message.to.map((to, i) => {
|
|
73
|
+
const item = {
|
|
74
|
+
reference: perSend?.reference ?? `${ctx.messageId}-${i}`,
|
|
75
|
+
content: { html: message.html },
|
|
76
|
+
headers: {
|
|
77
|
+
subject: message.subject,
|
|
78
|
+
to: toSelligentAddress(to),
|
|
79
|
+
from: toSelligentAddress(from)
|
|
80
|
+
},
|
|
81
|
+
context: { profile: perSend?.profile ?? normalizeAddress(to) }
|
|
82
|
+
};
|
|
83
|
+
if (message.text) item.content.text = message.text;
|
|
84
|
+
if (message.attachments?.length) item.content.attachments = toAttachments(message.attachments);
|
|
85
|
+
if (message.replyTo) item.headers.reply = toSelligentAddress(message.replyTo);
|
|
86
|
+
if (perSend?.list_unsubscribe) item.headers.list_unsubscribe = perSend.list_unsubscribe;
|
|
87
|
+
if (perSend?.tags) item.context.tags = perSend.tags;
|
|
88
|
+
if (perSend?.metadata) item.context.metadata = perSend.metadata;
|
|
89
|
+
if (perSend?.custom_send_time) item.custom_send_time = perSend.custom_send_time;
|
|
90
|
+
if (perSend?.time_to_live) item.time_to_live = perSend.time_to_live;
|
|
91
|
+
return item;
|
|
92
|
+
});
|
|
93
|
+
/** @experimental Selligent (Marigold Engage) transport using the SDC transactional email API. */
|
|
94
|
+
const selligentTransport = (opts) => {
|
|
95
|
+
const url = `${(opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "")}/email/v1/messages/send`;
|
|
96
|
+
const log = (opts.logger ?? consoleLogger()).child({ component: "selligent" });
|
|
97
|
+
const http = createHttpClient({
|
|
98
|
+
...opts.http,
|
|
99
|
+
timeoutMs: opts.http?.timeoutMs ?? DEFAULT_TIMEOUT_MS
|
|
100
|
+
});
|
|
101
|
+
const tokenManager = createTokenManager(opts);
|
|
102
|
+
return createTransport({
|
|
103
|
+
name: "selligent",
|
|
104
|
+
async verify() {
|
|
105
|
+
const [err, token] = await handlePromise(tokenManager.getToken());
|
|
106
|
+
if (err) return {
|
|
107
|
+
ok: false,
|
|
108
|
+
details: err.message
|
|
109
|
+
};
|
|
110
|
+
return {
|
|
111
|
+
ok: true,
|
|
112
|
+
details: { tokenLength: token.length }
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
async send(message, ctx) {
|
|
116
|
+
if (!message.from) throw new NotifyRpcError({
|
|
117
|
+
message: "Selligent transport: no \"from\" address. Set it on the channel default, route `.from()` resolver, or per-call args.",
|
|
118
|
+
code: "CONFIG",
|
|
119
|
+
route: ctx.route,
|
|
120
|
+
messageId: ctx.messageId
|
|
121
|
+
});
|
|
122
|
+
const [tokenErr, token] = await handlePromise(tokenManager.getToken());
|
|
123
|
+
if (tokenErr) {
|
|
124
|
+
if (tokenErr instanceof NotifyRpcProviderError) return {
|
|
125
|
+
ok: false,
|
|
126
|
+
error: tokenErr
|
|
127
|
+
};
|
|
128
|
+
return {
|
|
129
|
+
ok: false,
|
|
130
|
+
error: new NotifyRpcProviderError({
|
|
131
|
+
message: `Selligent transport: failed to obtain access token: ${tokenErr.message}`,
|
|
132
|
+
code: "CONFIG",
|
|
133
|
+
provider: "selligent",
|
|
134
|
+
retriable: true,
|
|
135
|
+
route: ctx.route,
|
|
136
|
+
messageId: ctx.messageId,
|
|
137
|
+
cause: tokenErr
|
|
138
|
+
})
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const perSend = ctx.transport?.selligent;
|
|
142
|
+
const items = buildMessageItems(message, message.from, ctx, perSend);
|
|
143
|
+
const result = await http.request(url, {
|
|
144
|
+
method: "POST",
|
|
145
|
+
headers: {
|
|
146
|
+
Authorization: `Bearer ${token}`,
|
|
147
|
+
"Content-Type": "application/json"
|
|
148
|
+
},
|
|
149
|
+
body: JSON.stringify(items)
|
|
150
|
+
});
|
|
151
|
+
if (!result.ok) {
|
|
152
|
+
if (result.kind === "network") {
|
|
153
|
+
log.error("Selligent fetch failed", {
|
|
154
|
+
err: result.cause,
|
|
155
|
+
route: ctx.route
|
|
156
|
+
});
|
|
157
|
+
return {
|
|
158
|
+
ok: false,
|
|
159
|
+
error: new NotifyRpcProviderError({
|
|
160
|
+
message: `Selligent transport: ${result.timedOut ? "request timed out" : `network error: ${result.cause.message}`}`,
|
|
161
|
+
code: result.timedOut ? "TIMEOUT" : "PROVIDER",
|
|
162
|
+
provider: "selligent",
|
|
163
|
+
retriable: true,
|
|
164
|
+
route: ctx.route,
|
|
165
|
+
messageId: ctx.messageId,
|
|
166
|
+
cause: result.cause
|
|
167
|
+
})
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const errData = result.body ?? {};
|
|
171
|
+
const { code, retriable } = mapHttpStatus(result.status);
|
|
172
|
+
const errorMessage = `Selligent transport: [${result.status}] ${errData.error_message ?? "request failed"}`;
|
|
173
|
+
log.error(errorMessage, {
|
|
174
|
+
err: {
|
|
175
|
+
status: result.status,
|
|
176
|
+
body: errData
|
|
177
|
+
},
|
|
178
|
+
route: ctx.route
|
|
179
|
+
});
|
|
180
|
+
return {
|
|
181
|
+
ok: false,
|
|
182
|
+
error: new NotifyRpcProviderError({
|
|
183
|
+
message: errorMessage,
|
|
184
|
+
code,
|
|
185
|
+
provider: "selligent",
|
|
186
|
+
httpStatus: result.status,
|
|
187
|
+
retriable,
|
|
188
|
+
route: ctx.route,
|
|
189
|
+
messageId: ctx.messageId
|
|
190
|
+
})
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
ok: true,
|
|
195
|
+
data: {
|
|
196
|
+
accepted: message.to.map(normalizeAddress),
|
|
197
|
+
rejected: [],
|
|
198
|
+
raw: items
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
};
|
|
204
|
+
//#endregion
|
|
205
|
+
export { isSelligentRetriable, selligentTransport };
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@betternotify/selligent",
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
|
+
"description": "Selligent (Marigold Engage) transactional email transport for Better-Notify.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/better-notify/better-notify",
|
|
9
|
+
"directory": "packages/selligent"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@betternotify/core": "1.0.0-beta.4",
|
|
26
|
+
"@betternotify/email": "1.0.0-beta.2"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"rolldown": "1.0.0",
|
|
30
|
+
"typescript": "6.0.3",
|
|
31
|
+
"vitest": "2.1.9",
|
|
32
|
+
"@internal/rolldown-config": "0.0.0",
|
|
33
|
+
"@internal/tsconfig": "0.0.0"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=22"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "NODE_OPTIONS='--import tsx/esm' rolldown -c",
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"test": "vitest run"
|
|
42
|
+
}
|
|
43
|
+
}
|