ras-stack 0.3.5 → 0.5.0
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 +45 -15
- package/dist/realtime/client.d.ts +29 -0
- package/dist/realtime/client.js +133 -0
- package/dist/realtime/client.js.map +1 -0
- package/dist/realtime/index.d.ts +1 -0
- package/dist/realtime/index.js +1 -0
- package/dist/realtime/index.js.map +1 -1
- package/dist/tanstack/query.d.ts +3 -0
- package/dist/tanstack/query.js +8 -0
- package/dist/tanstack/query.js.map +1 -0
- package/dist/tanstack/server.d.ts +8 -0
- package/dist/tanstack/server.js +10 -0
- package/dist/tanstack/server.js.map +1 -0
- package/package.json +24 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ The package contains independent helpers for common application infrastructure:
|
|
|
14
14
|
|
|
15
15
|
- **Authentication:** secure defaults and utilities for sessions, rate limits, secrets, social providers, tokens, and trusted origins.
|
|
16
16
|
- **Server requests:** same-origin mutation guards, error-normalizing RPC wrappers, canonical-host redirects, and health responses.
|
|
17
|
-
- **Realtime:** Centrifugo token signing and a bounded publisher with retries and graceful shutdown.
|
|
17
|
+
- **Realtime:** Centrifugo client lifecycle, token signing, presence synchronization, and a bounded publisher with retries and graceful shutdown.
|
|
18
18
|
- **Email:** SMTP environment parsing and a small Nodemailer delivery interface.
|
|
19
19
|
- **Uploads:** a promise-based wrapper around resumable `tus-js-client` uploads.
|
|
20
20
|
- **Project configuration:** shared TypeScript and Oxlint bases.
|
|
@@ -35,10 +35,11 @@ The libraries underneath remain available normally. Applications still configure
|
|
|
35
35
|
pnpm add ras-stack
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
Nodemailer and `tus-js-client` are optional peer dependencies. Install one only when using its
|
|
38
|
+
Nodemailer, Centrifuge, and `tus-js-client` are optional peer dependencies. Install one only when using its integration:
|
|
39
39
|
|
|
40
40
|
```sh
|
|
41
41
|
pnpm add nodemailer
|
|
42
|
+
pnpm add centrifuge
|
|
42
43
|
pnpm add tus-js-client
|
|
43
44
|
```
|
|
44
45
|
|
|
@@ -63,31 +64,44 @@ const auth = betterAuth({
|
|
|
63
64
|
})
|
|
64
65
|
```
|
|
65
66
|
|
|
66
|
-
|
|
67
|
+
The optional TanStack entrypoints bind the shared primitives to TanStack Start's ambient request and provide the common Query client default:
|
|
67
68
|
|
|
68
69
|
```ts
|
|
69
|
-
import {
|
|
70
|
-
import {
|
|
71
|
-
import { createRpc } from 'ras-stack/server'
|
|
70
|
+
import { createStackQueryClient } from 'ras-stack/tanstack/query'
|
|
71
|
+
import { createTanStackRpc, requireTanStackMutationOrigin } from 'ras-stack/tanstack/server'
|
|
72
72
|
|
|
73
|
-
export const { rpc, mutationRpc } =
|
|
74
|
-
getRequest,
|
|
73
|
+
export const { rpc, mutationRpc } = createTanStackRpc({
|
|
75
74
|
requireMutation: (request) =>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
requireTanStackMutationOrigin(
|
|
76
|
+
{
|
|
77
|
+
configured: [process.env.APP_URL],
|
|
78
|
+
trustForwardedHeaders: true,
|
|
79
|
+
},
|
|
80
|
+
request,
|
|
81
|
+
),
|
|
80
82
|
})
|
|
83
|
+
|
|
84
|
+
export const queryClient = createStackQueryClient()
|
|
81
85
|
```
|
|
82
86
|
|
|
87
|
+
Applications still own their auth clients, authorization, routes, router, logging, health checks, and Query configuration. Both integrations remain optional, and their upstream libraries remain directly accessible.
|
|
88
|
+
|
|
83
89
|
Only enable `trustForwardedHeaders` behind a proxy that replaces incoming forwarded headers. Otherwise a client could choose the origin used by the check.
|
|
84
90
|
|
|
85
91
|
## Realtime updates
|
|
86
92
|
|
|
87
|
-
Applications choose their channel names, authorize subscriptions, and define payloads. `ras-stack` handles Centrifugo's HTTP publication and
|
|
93
|
+
Applications choose their channel names, authorize subscriptions, and define payloads. `ras-stack` handles Centrifugo's HTTP publication, signed tokens, and repeated browser lifecycle mechanics:
|
|
88
94
|
|
|
89
95
|
```ts
|
|
90
|
-
import {
|
|
96
|
+
import {
|
|
97
|
+
CentrifugoPublisher,
|
|
98
|
+
connectRealtimeClient,
|
|
99
|
+
createSameOriginRealtimeClient,
|
|
100
|
+
openRealtimeSubscription,
|
|
101
|
+
requestRealtimeTicket,
|
|
102
|
+
signRealtimeToken,
|
|
103
|
+
watchSubscriptionPresence,
|
|
104
|
+
} from 'ras-stack/realtime'
|
|
91
105
|
|
|
92
106
|
const publisher = new CentrifugoPublisher({
|
|
93
107
|
apiUrl,
|
|
@@ -102,9 +116,25 @@ publisher.publish(`battle:${battle.id}`, { type: 'change' })
|
|
|
102
116
|
const token = signRealtimeToken(user.id, { channel: `battle:${battle.id}`, info: presence }, { secret })
|
|
103
117
|
|
|
104
118
|
await publisher.close()
|
|
119
|
+
|
|
120
|
+
const client = createSameOriginRealtimeClient({
|
|
121
|
+
getToken: () => requestRealtimeTicket('/api/realtime/token', { parse: (value) => (value as { token: string }).token }),
|
|
122
|
+
})
|
|
123
|
+
const channelToken = (channel: string) =>
|
|
124
|
+
requestRealtimeTicket('/api/realtime/token', {
|
|
125
|
+
init: { method: 'POST', body: JSON.stringify({ channel }) },
|
|
126
|
+
parse: (value) => (value as { token: string }).token,
|
|
127
|
+
})
|
|
128
|
+
const disconnect = connectRealtimeClient(client)
|
|
129
|
+
const live = openRealtimeSubscription(client, channel, { getToken: ({ channel }) => channelToken(channel) }, (subscription) =>
|
|
130
|
+
watchSubscriptionPresence(subscription, setClients),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
live.close()
|
|
134
|
+
disconnect()
|
|
105
135
|
```
|
|
106
136
|
|
|
107
|
-
`publish()` returns `false` when the publisher is closed, disabled, or at capacity. `close()` rejects new work and waits for accepted publications and their bounded retries to finish.
|
|
137
|
+
The client helpers return the underlying Centrifuge client and subscription. React ownership, channel conventions, ticket validation, event parsing, presence models, and query invalidation remain application code. `publish()` returns `false` when the publisher is closed, disabled, or at capacity. `close()` rejects new work and waits for accepted publications and their bounded retries to finish.
|
|
108
138
|
|
|
109
139
|
## Email and uploads
|
|
110
140
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Centrifuge, type ClientInfo, type Options, type ServerPublicationContext, type ServerSubscribedContext, type Subscription, type SubscriptionOptions } from 'centrifuge';
|
|
2
|
+
type BrowserLocation = Pick<Location, 'host' | 'protocol'>;
|
|
3
|
+
export declare function sameOriginWebSocketUrl(location: BrowserLocation, path?: string): string;
|
|
4
|
+
export type RealtimeTicketOptions<T> = {
|
|
5
|
+
fetch?: typeof fetch;
|
|
6
|
+
init?: RequestInit;
|
|
7
|
+
parse: (value: unknown) => T;
|
|
8
|
+
unauthorizedStatuses?: readonly number[];
|
|
9
|
+
errorMessage?: (status: number) => string;
|
|
10
|
+
};
|
|
11
|
+
export declare function requestRealtimeTicket<T>(input: RequestInfo | URL, options: RealtimeTicketOptions<T>): Promise<T>;
|
|
12
|
+
export declare function createSameOriginRealtimeClient(options: Partial<Options>, config?: {
|
|
13
|
+
location?: BrowserLocation;
|
|
14
|
+
path?: string;
|
|
15
|
+
}): Centrifuge;
|
|
16
|
+
export declare function connectRealtimeClient(client: Centrifuge): () => void;
|
|
17
|
+
export type RealtimeSubscription = {
|
|
18
|
+
subscription: Subscription;
|
|
19
|
+
close: () => void;
|
|
20
|
+
};
|
|
21
|
+
export declare function openRealtimeSubscription(client: Centrifuge, channel: string, options?: SubscriptionOptions, configure?: (subscription: Subscription) => void | (() => void)): RealtimeSubscription;
|
|
22
|
+
export declare function watchServerChannel(client: Centrifuge, channel: string, handlers: {
|
|
23
|
+
publication?: (context: ServerPublicationContext) => void;
|
|
24
|
+
unrecovered?: (context: ServerSubscribedContext) => void;
|
|
25
|
+
}): () => void;
|
|
26
|
+
export declare function watchSubscriptionPresence(subscription: Subscription, update: (clients: Record<string, ClientInfo>) => void, options?: {
|
|
27
|
+
onError?: (error: unknown) => void;
|
|
28
|
+
}): () => void;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Centrifuge, UnauthorizedError, } from 'centrifuge';
|
|
2
|
+
export function sameOriginWebSocketUrl(location, path = '/connection/websocket') {
|
|
3
|
+
if (!path.startsWith('/') || path.startsWith('//'))
|
|
4
|
+
throw new Error('WebSocket path must be same-origin');
|
|
5
|
+
const url = new URL(path, `${location.protocol}//${location.host}`);
|
|
6
|
+
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
7
|
+
return url.toString();
|
|
8
|
+
}
|
|
9
|
+
export async function requestRealtimeTicket(input, options) {
|
|
10
|
+
const response = await (options.fetch ?? fetch)(input, options.init);
|
|
11
|
+
if ((options.unauthorizedStatuses ?? [401, 403]).includes(response.status))
|
|
12
|
+
throw new UnauthorizedError('unauthorized');
|
|
13
|
+
if (!response.ok)
|
|
14
|
+
throw new Error(options.errorMessage?.(response.status) ?? `Realtime authentication failed with status ${response.status}`);
|
|
15
|
+
return options.parse(await response.json());
|
|
16
|
+
}
|
|
17
|
+
export function createSameOriginRealtimeClient(options, config = {}) {
|
|
18
|
+
const location = config.location ?? window.location;
|
|
19
|
+
return new Centrifuge(sameOriginWebSocketUrl(location, config.path), options);
|
|
20
|
+
}
|
|
21
|
+
export function connectRealtimeClient(client) {
|
|
22
|
+
client.connect();
|
|
23
|
+
return () => client.disconnect();
|
|
24
|
+
}
|
|
25
|
+
export function openRealtimeSubscription(client, channel, options = {}, configure) {
|
|
26
|
+
const subscription = client.newSubscription(channel, options);
|
|
27
|
+
let cleanup;
|
|
28
|
+
try {
|
|
29
|
+
cleanup = configure?.(subscription);
|
|
30
|
+
subscription.subscribe();
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
client.removeSubscription(subscription);
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
let closed = false;
|
|
37
|
+
return {
|
|
38
|
+
subscription,
|
|
39
|
+
close: () => {
|
|
40
|
+
if (closed)
|
|
41
|
+
return;
|
|
42
|
+
closed = true;
|
|
43
|
+
try {
|
|
44
|
+
cleanup?.();
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
client.removeSubscription(subscription);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export function watchServerChannel(client, channel, handlers) {
|
|
53
|
+
const publication = (context) => {
|
|
54
|
+
if (context.channel === channel)
|
|
55
|
+
handlers.publication?.(context);
|
|
56
|
+
};
|
|
57
|
+
const subscribed = (context) => {
|
|
58
|
+
if (context.channel === channel && context.wasRecovering && !context.recovered)
|
|
59
|
+
handlers.unrecovered?.(context);
|
|
60
|
+
};
|
|
61
|
+
client.on('publication', publication);
|
|
62
|
+
client.on('subscribed', subscribed);
|
|
63
|
+
return () => {
|
|
64
|
+
client.off('publication', publication);
|
|
65
|
+
client.off('subscribed', subscribed);
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export function watchSubscriptionPresence(subscription, update, options = {}) {
|
|
69
|
+
const clients = new Map();
|
|
70
|
+
let active = true;
|
|
71
|
+
let revision = 0;
|
|
72
|
+
let refreshing;
|
|
73
|
+
const render = () => update(Object.fromEntries(clients));
|
|
74
|
+
const sync = async () => {
|
|
75
|
+
const requestedAt = revision;
|
|
76
|
+
try {
|
|
77
|
+
const snapshot = await subscription.presence();
|
|
78
|
+
if (!active)
|
|
79
|
+
return;
|
|
80
|
+
if (revision !== requestedAt)
|
|
81
|
+
return sync();
|
|
82
|
+
clients.clear();
|
|
83
|
+
for (const [id, info] of Object.entries(snapshot.clients))
|
|
84
|
+
clients.set(id, info);
|
|
85
|
+
render();
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
if (!active)
|
|
89
|
+
return;
|
|
90
|
+
if (revision !== requestedAt)
|
|
91
|
+
return sync();
|
|
92
|
+
clients.clear();
|
|
93
|
+
render();
|
|
94
|
+
options.onError?.(error);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const refresh = () => {
|
|
98
|
+
if (refreshing)
|
|
99
|
+
return refreshing;
|
|
100
|
+
refreshing = sync().finally(() => {
|
|
101
|
+
refreshing = undefined;
|
|
102
|
+
});
|
|
103
|
+
return refreshing;
|
|
104
|
+
};
|
|
105
|
+
const subscribed = () => {
|
|
106
|
+
revision++;
|
|
107
|
+
void refresh();
|
|
108
|
+
};
|
|
109
|
+
const join = ({ info }) => {
|
|
110
|
+
revision++;
|
|
111
|
+
clients.set(info.client, info);
|
|
112
|
+
render();
|
|
113
|
+
};
|
|
114
|
+
const leave = ({ info }) => {
|
|
115
|
+
revision++;
|
|
116
|
+
clients.delete(info.client);
|
|
117
|
+
render();
|
|
118
|
+
};
|
|
119
|
+
subscription.on('subscribed', subscribed);
|
|
120
|
+
subscription.on('join', join);
|
|
121
|
+
subscription.on('leave', leave);
|
|
122
|
+
return () => {
|
|
123
|
+
if (!active)
|
|
124
|
+
return;
|
|
125
|
+
active = false;
|
|
126
|
+
subscription.off('subscribed', subscribed);
|
|
127
|
+
subscription.off('join', join);
|
|
128
|
+
subscription.off('leave', leave);
|
|
129
|
+
clients.clear();
|
|
130
|
+
render();
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/realtime/client.ts"],"names":[],"mappings":"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"}
|
package/dist/realtime/index.d.ts
CHANGED
package/dist/realtime/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/realtime/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/realtime/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { QueryClient } from '@tanstack/react-query';
|
|
2
|
+
export function createStackQueryClient(config = {}) {
|
|
3
|
+
return new QueryClient({ defaultOptions: { queries: { staleTime: 1000 } }, ...config });
|
|
4
|
+
}
|
|
5
|
+
export function queryErrorMessage(error, fallback = 'Something went wrong. Try again.') {
|
|
6
|
+
return error instanceof Error && error.message ? error.message : fallback;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/tanstack/query.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAA0B,MAAM,uBAAuB,CAAA;AAE3E,MAAM,UAAU,sBAAsB,CAAC,MAAM,GAAsB,EAAE;IACnE,OAAO,IAAI,WAAW,CAAC,EAAE,cAAc,EAAE,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,MAAM,EAAE,CAAC,CAAA;AACzF,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAc,EAAE,QAAQ,GAAG,kCAAkC;IAC7F,OAAO,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAA;AAC3E,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type OriginOptions } from '../auth/index.js';
|
|
2
|
+
import { type RpcOptions } from '../server/index.js';
|
|
3
|
+
export type TanStackRpcOptions = Omit<RpcOptions, 'getRequest'> & Pick<RpcOptions, 'getRequest'>;
|
|
4
|
+
export declare function createTanStackRpc(options?: TanStackRpcOptions): {
|
|
5
|
+
rpc: <T>(work: () => Promise<T> | T) => Promise<T>;
|
|
6
|
+
mutationRpc: <T>(work: () => Promise<T> | T, request?: Request | undefined) => Promise<T>;
|
|
7
|
+
};
|
|
8
|
+
export declare function requireTanStackMutationOrigin(options?: OriginOptions, request?: Request): void;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { getRequest } from '@tanstack/react-start/server';
|
|
2
|
+
import { requireSameOrigin } from '../auth/index.js';
|
|
3
|
+
import { createRpc } from '../server/index.js';
|
|
4
|
+
export function createTanStackRpc(options = {}) {
|
|
5
|
+
return createRpc({ ...options, getRequest: options.getRequest ?? getRequest });
|
|
6
|
+
}
|
|
7
|
+
export function requireTanStackMutationOrigin(options = {}, request = getRequest()) {
|
|
8
|
+
return requireSameOrigin(request, options);
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/tanstack/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAA;AACzD,OAAO,EAAE,iBAAiB,EAAsB,MAAM,kBAAkB,CAAA;AACxE,OAAO,EAAE,SAAS,EAAmB,MAAM,oBAAoB,CAAA;AAI/D,MAAM,UAAU,iBAAiB,CAAC,OAAO,GAAuB,EAAE;IAChE,OAAO,SAAS,CAAC,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,UAAU,EAAE,CAAC,CAAA;AAChF,CAAC;AAED,MAAM,UAAU,6BAA6B,CAAC,OAAO,GAAkB,EAAE,EAAE,OAAO,GAAG,UAAU,EAAE;IAC/F,OAAO,iBAAiB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;AAC5C,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ras-stack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Composable full-stack primitives shared across Richard Solomou's applications.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"authentication",
|
|
@@ -39,6 +39,14 @@
|
|
|
39
39
|
"types": "./dist/server/index.d.ts",
|
|
40
40
|
"default": "./dist/server/index.js"
|
|
41
41
|
},
|
|
42
|
+
"./tanstack/query": {
|
|
43
|
+
"types": "./dist/tanstack/query.d.ts",
|
|
44
|
+
"default": "./dist/tanstack/query.js"
|
|
45
|
+
},
|
|
46
|
+
"./tanstack/server": {
|
|
47
|
+
"types": "./dist/tanstack/server.d.ts",
|
|
48
|
+
"default": "./dist/tanstack/server.js"
|
|
49
|
+
},
|
|
42
50
|
"./uploads": {
|
|
43
51
|
"types": "./dist/uploads/index.d.ts",
|
|
44
52
|
"default": "./dist/uploads/index.js"
|
|
@@ -64,8 +72,11 @@
|
|
|
64
72
|
},
|
|
65
73
|
"devDependencies": {
|
|
66
74
|
"@changesets/cli": "^2.31.1",
|
|
75
|
+
"@tanstack/react-query": "^5.101.4",
|
|
76
|
+
"@tanstack/react-start": "^1.168.32",
|
|
67
77
|
"@types/node": "^26.0.0",
|
|
68
78
|
"@types/nodemailer": "^8.0.1",
|
|
79
|
+
"centrifuge": "5.7.0",
|
|
69
80
|
"nodemailer": "^9.0.3",
|
|
70
81
|
"oxfmt": "^0.59.0",
|
|
71
82
|
"oxlint": "^1.74.0",
|
|
@@ -75,10 +86,22 @@
|
|
|
75
86
|
"vitest": "^4.1.10"
|
|
76
87
|
},
|
|
77
88
|
"peerDependencies": {
|
|
89
|
+
"@tanstack/react-query": ">=5 <6",
|
|
90
|
+
"@tanstack/react-start": ">=1 <2",
|
|
91
|
+
"centrifuge": ">=5 <6",
|
|
78
92
|
"nodemailer": ">=9 <10",
|
|
79
93
|
"tus-js-client": ">=4 <5"
|
|
80
94
|
},
|
|
81
95
|
"peerDependenciesMeta": {
|
|
96
|
+
"@tanstack/react-query": {
|
|
97
|
+
"optional": true
|
|
98
|
+
},
|
|
99
|
+
"@tanstack/react-start": {
|
|
100
|
+
"optional": true
|
|
101
|
+
},
|
|
102
|
+
"centrifuge": {
|
|
103
|
+
"optional": true
|
|
104
|
+
},
|
|
82
105
|
"nodemailer": {
|
|
83
106
|
"optional": true
|
|
84
107
|
},
|