@venn-lang/notify 0.1.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/LICENSE +21 -0
- package/README.md +131 -0
- package/dist/index.d.ts +82 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +134 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
- package/src/actions/email.ts +33 -0
- package/src/actions/index.ts +7 -0
- package/src/actions/slack.ts +26 -0
- package/src/actions/webhook.ts +26 -0
- package/src/clients/fake-notifier.ts +18 -0
- package/src/clients/index.ts +2 -0
- package/src/clients/real-notifier.ts +20 -0
- package/src/index.ts +9 -0
- package/src/plugin.ts +19 -0
- package/src/port/index.ts +8 -0
- package/src/port/notifier.port.ts +14 -0
- package/src/port/notifier.types.ts +41 -0
- package/src/types.ts +16 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vinicius Borges
|
|
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,131 @@
|
|
|
1
|
+
# @venn-lang/notify
|
|
2
|
+
|
|
3
|
+
> The `notify` namespace: send a Slack message, a webhook or an email from a flow.
|
|
4
|
+
|
|
5
|
+
A run that fails at 3am is only useful if somebody hears about it. This plugin gives a flow three
|
|
6
|
+
dispatch verbs and routes all of them through the `Notifier` port, so the transport is chosen by the
|
|
7
|
+
host at startup. Under test that means a recorder you can assert against, with nothing leaving the
|
|
8
|
+
machine.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
Nothing to install. `@venn-lang/notify` is part of the standard library, and the CLI and the language
|
|
13
|
+
server both load [`@venn-lang/stdlib`](../stdlib), which lists every stdlib plugin. A file reaches the
|
|
14
|
+
namespace with one line.
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
use "venn/notify"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
module demo.checkout
|
|
24
|
+
|
|
25
|
+
use "venn/notify"
|
|
26
|
+
use "venn/assert"
|
|
27
|
+
|
|
28
|
+
flow "Checkout" {
|
|
29
|
+
step "place the order" {
|
|
30
|
+
expect true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
on failure {
|
|
34
|
+
notify.slack "#qa" { mention: "@oncall" }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Verbs
|
|
40
|
+
|
|
41
|
+
| Verb | Call | Options |
|
|
42
|
+
| --- | --- | --- |
|
|
43
|
+
| `notify.slack` | `notify.slack "#qa"` | `mention` |
|
|
44
|
+
| `notify.webhook` | `notify.webhook "https://hook.test/build"` | `json` |
|
|
45
|
+
| `notify.email` | `notify.email "qa@example.com"` | `subject`, `body` |
|
|
46
|
+
|
|
47
|
+
Each verb takes its destination as the one positional argument, the channel for Slack, the URL for a
|
|
48
|
+
webhook, the recipient for an email, and everything else as options:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
const posted = notify.slack "#builds" { mention: "@oncall" }
|
|
52
|
+
const hooked = notify.webhook "https://hook.test/build" { json: { status: "green" } }
|
|
53
|
+
const mailed = notify.email "qa@example.com" { subject: "Nightly", body: "All green." }
|
|
54
|
+
|
|
55
|
+
expect posted.delivered == true
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
All three return `notify.Receipt`.
|
|
59
|
+
|
|
60
|
+
## The type it publishes
|
|
61
|
+
|
|
62
|
+
`notify.Receipt` is a record of `delivered: bool` and `id: string`. `delivered` is the dispatch, not
|
|
63
|
+
the reading: nobody here knows whether a human saw it.
|
|
64
|
+
|
|
65
|
+
## The Notifier port
|
|
66
|
+
|
|
67
|
+
| | |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| id | `venn.port.notifier` |
|
|
70
|
+
| version | 1 |
|
|
71
|
+
| requires | `net` |
|
|
72
|
+
| methods | `send` |
|
|
73
|
+
|
|
74
|
+
Two implementations ship together, which is what makes this a port rather than a module with a good
|
|
75
|
+
interface:
|
|
76
|
+
|
|
77
|
+
- `createFakeNotifier()` records every notification in memory and returns
|
|
78
|
+
`{ delivered: true, id: "fake-<n>" }`. It is a `FakeNotifier`, so the recorded messages are
|
|
79
|
+
readable through `sent`. This is the one [`@venn-lang/stdlib`](../stdlib) binds.
|
|
80
|
+
- `createRealNotifier()` is a stub. Every call throws a `VennError` with code `VN8090`, because this
|
|
81
|
+
repository is the language and ships no live Slack, webhook or SMTP wiring.
|
|
82
|
+
|
|
83
|
+
Both run the same conformance suite, `notifierConformance` in `src/clients/notifier.suite.ts`: a
|
|
84
|
+
`send` resolves a receipt with a boolean `delivered` and a string `id`.
|
|
85
|
+
|
|
86
|
+
Asserting on what a flow sent, from TypeScript:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { createFakeNotifier } from "@venn-lang/notify";
|
|
90
|
+
|
|
91
|
+
const notifier = createFakeNotifier();
|
|
92
|
+
await notifier.send({ kind: "slack", channel: "#qa", mention: "@vini" });
|
|
93
|
+
|
|
94
|
+
expect(notifier.sent[0]).toMatchObject({ kind: "slack", channel: "#qa" });
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## API
|
|
98
|
+
|
|
99
|
+
| Export | What it is |
|
|
100
|
+
| --- | --- |
|
|
101
|
+
| `notifyPlugin` | The plugin definition: namespace `notify`, `requires: ["net"]`. Also the default export. |
|
|
102
|
+
| `notifyActions` | The three action definitions, in order: `slack`, `webhook`, `email`. |
|
|
103
|
+
| `NotifierPort` | The port descriptor. |
|
|
104
|
+
| `Notifier` | The port interface: `send(message)`. |
|
|
105
|
+
| `FakeNotifier` | A `Notifier` that also exposes `sent`, the notifications it recorded. |
|
|
106
|
+
| `Notification` | What is handed to the port: `kind`, `channel`, and the optional `subject`, `body`, `mention`, `json`. |
|
|
107
|
+
| `NotificationKind` | `"slack" \| "webhook" \| "email"`. |
|
|
108
|
+
| `NotifyReceipt` | What the port returns: `{ delivered, id }`. |
|
|
109
|
+
| `createFakeNotifier()` | The recording double. |
|
|
110
|
+
| `createRealNotifier()` | The real notifier, stubbed to throw `VN8090`. |
|
|
111
|
+
|
|
112
|
+
Binding a different notifier means one entry in the runner's port list:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { createRunner } from "@venn-lang/runtime";
|
|
116
|
+
import { createFakeNotifier, NotifierPort } from "@venn-lang/notify";
|
|
117
|
+
|
|
118
|
+
const runner = createRunner({
|
|
119
|
+
host,
|
|
120
|
+
plugins,
|
|
121
|
+
sink,
|
|
122
|
+
uri,
|
|
123
|
+
ports: [{ port: NotifierPort, impl: createFakeNotifier() }],
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## See also
|
|
128
|
+
|
|
129
|
+
- [`@venn-lang/sdk`](../sdk) for `definePlugin` and `defineAction`.
|
|
130
|
+
- [`@venn-lang/contracts`](../contracts) for `Port`, `assertPortShape` and `VennError`.
|
|
131
|
+
- [`@venn-lang/artifacts`](../std-artifacts) for the traces and videos a failure leaves behind.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { ActionDefinition, PluginDefinition } from "@venn-lang/sdk";
|
|
2
|
+
import { Port } from "@venn-lang/contracts";
|
|
3
|
+
//#region src/actions/index.d.ts
|
|
4
|
+
/** Every verb in the `notify` namespace, in the order the plugin registers them. */
|
|
5
|
+
declare const notifyActions: ActionDefinition[];
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/port/notifier.types.d.ts
|
|
8
|
+
/** The kind of channel a notification targets. */
|
|
9
|
+
type NotificationKind = "slack" | "webhook" | "email";
|
|
10
|
+
/**
|
|
11
|
+
* One message handed to the `Notifier`. `kind` picks the channel type and
|
|
12
|
+
* decides which of the optional fields carry meaning: `subject` and `body` for
|
|
13
|
+
* email, `mention` for Slack, `json` for a webhook.
|
|
14
|
+
*/
|
|
15
|
+
interface Notification {
|
|
16
|
+
kind: NotificationKind;
|
|
17
|
+
/** Where it goes, read in the terms of its `kind`: a Slack channel, a URL, an address. */
|
|
18
|
+
channel: string;
|
|
19
|
+
subject?: string;
|
|
20
|
+
body?: string;
|
|
21
|
+
mention?: string;
|
|
22
|
+
json?: unknown;
|
|
23
|
+
}
|
|
24
|
+
/** What a `Notifier` answers with once a message is on its way. */
|
|
25
|
+
interface NotifyReceipt {
|
|
26
|
+
/** The dispatch succeeded. Says nothing about whether anyone read it. */
|
|
27
|
+
delivered: boolean;
|
|
28
|
+
/** The notifier's own handle for the message. */
|
|
29
|
+
id: string;
|
|
30
|
+
}
|
|
31
|
+
/** The contract every notification goes through, whatever the channel. */
|
|
32
|
+
interface Notifier {
|
|
33
|
+
/**
|
|
34
|
+
* Dispatches one message.
|
|
35
|
+
*
|
|
36
|
+
* @returns the receipt for the dispatch.
|
|
37
|
+
* @throws {VennError} `VN8090` from an implementation that cannot deliver.
|
|
38
|
+
*/
|
|
39
|
+
send(message: Notification): Promise<NotifyReceipt>;
|
|
40
|
+
}
|
|
41
|
+
/** A `Notifier` that keeps every sent notification in memory, for assertions. */
|
|
42
|
+
interface FakeNotifier extends Notifier {
|
|
43
|
+
readonly sent: readonly Notification[];
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/port/notifier.port.d.ts
|
|
47
|
+
/**
|
|
48
|
+
* The port every `notify` verb dispatches through. Requires the `net`
|
|
49
|
+
* capability, so a host without it fails to load the plugin rather than
|
|
50
|
+
* failing mid-run.
|
|
51
|
+
*/
|
|
52
|
+
declare const NotifierPort: Port<Notifier>;
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/clients/fake-notifier.d.ts
|
|
55
|
+
/**
|
|
56
|
+
* The in-memory `Notifier`. Records what it is handed and touches no network.
|
|
57
|
+
*
|
|
58
|
+
* @returns a fresh notifier whose `sent` array starts empty and grows in
|
|
59
|
+
* dispatch order.
|
|
60
|
+
*/
|
|
61
|
+
declare function createFakeNotifier(): FakeNotifier;
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/clients/real-notifier.d.ts
|
|
64
|
+
/**
|
|
65
|
+
* The live `Notifier`. No Slack, webhook or email wiring in this build.
|
|
66
|
+
*
|
|
67
|
+
* @returns a notifier whose `send` throws, so the gap is a legible failure
|
|
68
|
+
* rather than a silent no-op.
|
|
69
|
+
* @throws {VennError} `VN8090` on every `send`.
|
|
70
|
+
*/
|
|
71
|
+
declare function createRealNotifier(): Notifier;
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/plugin.d.ts
|
|
74
|
+
/**
|
|
75
|
+
* The `@venn-lang/notify` plugin. Registers the `notify` namespace: the `slack`,
|
|
76
|
+
* `webhook` and `email` verbs and the nominal `notify.Receipt` type. Requires
|
|
77
|
+
* the `net` capability, since every verb dispatches over the network.
|
|
78
|
+
*/
|
|
79
|
+
declare const notifyPlugin: PluginDefinition;
|
|
80
|
+
//#endregion
|
|
81
|
+
export { type FakeNotifier, type Notification, type NotificationKind, type Notifier, NotifierPort, type NotifyReceipt, createFakeNotifier, createRealNotifier, notifyPlugin as default, notifyPlugin, notifyActions };
|
|
82
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/actions/index.ts","../src/port/notifier.types.ts","../src/port/notifier.port.ts","../src/clients/fake-notifier.ts","../src/clients/real-notifier.ts","../src/plugin.ts"],"mappings":";;;;cAMa,eAAe;;;;KCLhB;;;;;;UAOK;EACf,MAAM;;EAEN;EACA;EACA;EACA;EACA;;;UAIe;;EAEf;;EAEA;;;UAIe;;;;;;;EAOf,KAAK,SAAS,eAAe,QAAQ;;;UAItB,qBAAqB;WAC3B,eAAe;;;;;;;;;cC/Bb,cAAc,KAAK;;;;;;;;;iBCAhB,sBAAsB;;;;;;;;;;iBCEtB,sBAAsB;;;;;;;;cCDzB,cAAc"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { arg, defineAction, definePlugin, z } from "@venn-lang/sdk";
|
|
2
|
+
import { t } from "@venn-lang/types";
|
|
3
|
+
import { VennError } from "@venn-lang/contracts";
|
|
4
|
+
//#region src/port/notifier.port.ts
|
|
5
|
+
/**
|
|
6
|
+
* The port every `notify` verb dispatches through. Requires the `net`
|
|
7
|
+
* capability, so a host without it fails to load the plugin rather than
|
|
8
|
+
* failing mid-run.
|
|
9
|
+
*/
|
|
10
|
+
const NotifierPort = {
|
|
11
|
+
id: "venn.port.notifier",
|
|
12
|
+
version: 1,
|
|
13
|
+
requires: ["net"],
|
|
14
|
+
methods: ["send"]
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* `notify.email "qa@example.com" { subject: "Nightly failed", body: report }`.
|
|
18
|
+
*
|
|
19
|
+
* Sends one email. Both `subject` and `body` are optional; an empty recipient
|
|
20
|
+
* is passed straight through to the notifier, which decides what to do with it.
|
|
21
|
+
*
|
|
22
|
+
* @returns a `notify.Receipt`.
|
|
23
|
+
*/
|
|
24
|
+
const email = defineAction({
|
|
25
|
+
name: "email",
|
|
26
|
+
doc: "Send an email notification.",
|
|
27
|
+
params: z.object({
|
|
28
|
+
subject: z.string().optional(),
|
|
29
|
+
body: z.string().optional()
|
|
30
|
+
}).optional(),
|
|
31
|
+
args: [arg("to", t.string, "Who receives it.")],
|
|
32
|
+
result: t.ref("notify.Receipt"),
|
|
33
|
+
run: (ctx, input) => ctx.port(NotifierPort).send({
|
|
34
|
+
kind: "email",
|
|
35
|
+
channel: String(input.args[0] ?? ""),
|
|
36
|
+
subject: input.params?.subject,
|
|
37
|
+
body: input.params?.body
|
|
38
|
+
})
|
|
39
|
+
});
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/actions/index.ts
|
|
42
|
+
/** Every verb in the `notify` namespace, in the order the plugin registers them. */
|
|
43
|
+
const notifyActions = [
|
|
44
|
+
defineAction({
|
|
45
|
+
name: "slack",
|
|
46
|
+
doc: "Send a Slack notification to a channel.",
|
|
47
|
+
params: z.object({ mention: z.string().optional() }).optional(),
|
|
48
|
+
args: [arg("channel", t.string, "Where to post: `#builds`, or a user.")],
|
|
49
|
+
result: t.ref("notify.Receipt"),
|
|
50
|
+
run: (ctx, input) => ctx.port(NotifierPort).send({
|
|
51
|
+
kind: "slack",
|
|
52
|
+
channel: String(input.args[0] ?? ""),
|
|
53
|
+
mention: input.params?.mention
|
|
54
|
+
})
|
|
55
|
+
}),
|
|
56
|
+
defineAction({
|
|
57
|
+
name: "webhook",
|
|
58
|
+
doc: "Send a webhook notification with a JSON body.",
|
|
59
|
+
params: z.object({ json: z.unknown().optional() }).optional(),
|
|
60
|
+
args: [arg("url", t.string, "Where to post the JSON body.")],
|
|
61
|
+
result: t.ref("notify.Receipt"),
|
|
62
|
+
run: (ctx, input) => ctx.port(NotifierPort).send({
|
|
63
|
+
kind: "webhook",
|
|
64
|
+
channel: String(input.args[0] ?? ""),
|
|
65
|
+
json: input.params?.json
|
|
66
|
+
})
|
|
67
|
+
}),
|
|
68
|
+
email
|
|
69
|
+
];
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/clients/fake-notifier.ts
|
|
72
|
+
/**
|
|
73
|
+
* The in-memory `Notifier`. Records what it is handed and touches no network.
|
|
74
|
+
*
|
|
75
|
+
* @returns a fresh notifier whose `sent` array starts empty and grows in
|
|
76
|
+
* dispatch order.
|
|
77
|
+
*/
|
|
78
|
+
function createFakeNotifier() {
|
|
79
|
+
const sent = [];
|
|
80
|
+
return {
|
|
81
|
+
sent,
|
|
82
|
+
send: async (message) => {
|
|
83
|
+
sent.push(message);
|
|
84
|
+
return {
|
|
85
|
+
delivered: true,
|
|
86
|
+
id: `fake-${sent.length}`
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/clients/real-notifier.ts
|
|
93
|
+
/**
|
|
94
|
+
* The live `Notifier`. No Slack, webhook or email wiring in this build.
|
|
95
|
+
*
|
|
96
|
+
* @returns a notifier whose `send` throws, so the gap is a legible failure
|
|
97
|
+
* rather than a silent no-op.
|
|
98
|
+
* @throws {VennError} `VN8090` on every `send`.
|
|
99
|
+
*/
|
|
100
|
+
function createRealNotifier() {
|
|
101
|
+
return { send: async () => {
|
|
102
|
+
throw new VennError({
|
|
103
|
+
code: "VN8090",
|
|
104
|
+
message: "Notifier real client not implemented in this build"
|
|
105
|
+
});
|
|
106
|
+
} };
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
109
|
+
//#region src/plugin.ts
|
|
110
|
+
/**
|
|
111
|
+
* The `@venn-lang/notify` plugin. Registers the `notify` namespace: the `slack`,
|
|
112
|
+
* `webhook` and `email` verbs and the nominal `notify.Receipt` type. Requires
|
|
113
|
+
* the `net` capability, since every verb dispatches over the network.
|
|
114
|
+
*/
|
|
115
|
+
const notifyPlugin = definePlugin({
|
|
116
|
+
name: "venn/notify",
|
|
117
|
+
version: "0.0.0",
|
|
118
|
+
namespace: "notify",
|
|
119
|
+
requires: ["net"],
|
|
120
|
+
actions: notifyActions,
|
|
121
|
+
typeDefs: {
|
|
122
|
+
/**
|
|
123
|
+
* What the notifier answers with once a message is on its way. `delivered`
|
|
124
|
+
* reports the dispatch, not the reading: nobody here knows whether it was seen.
|
|
125
|
+
*/
|
|
126
|
+
Receipt: t.record({
|
|
127
|
+
delivered: t.bool,
|
|
128
|
+
id: t.string
|
|
129
|
+
}) }
|
|
130
|
+
});
|
|
131
|
+
//#endregion
|
|
132
|
+
export { NotifierPort, createFakeNotifier, createRealNotifier, notifyPlugin as default, notifyPlugin, notifyActions };
|
|
133
|
+
|
|
134
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["params","params"],"sources":["../src/port/notifier.port.ts","../src/actions/email.ts","../src/actions/slack.ts","../src/actions/webhook.ts","../src/actions/index.ts","../src/clients/fake-notifier.ts","../src/clients/real-notifier.ts","../src/types.ts","../src/plugin.ts"],"sourcesContent":["import type { Port } from \"@venn-lang/contracts\";\nimport type { Notifier } from \"./notifier.types.js\";\n\n/**\n * The port every `notify` verb dispatches through. Requires the `net`\n * capability, so a host without it fails to load the plugin rather than\n * failing mid-run.\n */\nexport const NotifierPort: Port<Notifier> = {\n id: \"venn.port.notifier\",\n version: 1,\n requires: [\"net\"],\n methods: [\"send\"],\n};\n","import { type ActionDefinition, arg, defineAction, z } from \"@venn-lang/sdk\";\nimport { t } from \"@venn-lang/types\";\nimport { NotifierPort } from \"../port/index.js\";\n\nconst params = z\n .object({\n subject: z.string().optional(),\n body: z.string().optional(),\n })\n .optional();\n\n/**\n * `notify.email \"qa@example.com\" { subject: \"Nightly failed\", body: report }`.\n *\n * Sends one email. Both `subject` and `body` are optional; an empty recipient\n * is passed straight through to the notifier, which decides what to do with it.\n *\n * @returns a `notify.Receipt`.\n */\nexport const email: ActionDefinition = defineAction({\n name: \"email\",\n doc: \"Send an email notification.\",\n params,\n args: [arg(\"to\", t.string, \"Who receives it.\")],\n result: t.ref(\"notify.Receipt\"),\n run: (ctx, input) =>\n ctx.port(NotifierPort).send({\n kind: \"email\",\n channel: String(input.args[0] ?? \"\"),\n subject: input.params?.subject,\n body: input.params?.body,\n }),\n});\n","import { type ActionDefinition, arg, defineAction, z } from \"@venn-lang/sdk\";\nimport { t } from \"@venn-lang/types\";\nimport { NotifierPort } from \"../port/index.js\";\n\nconst params = z.object({ mention: z.string().optional() }).optional();\n\n/**\n * `notify.slack \"#qa\" { mention: \"@oncall\" }`.\n *\n * Posts to a Slack channel or a user. `mention` pings someone alongside the post.\n *\n * @returns a `notify.Receipt`.\n */\nexport const slack: ActionDefinition = defineAction({\n name: \"slack\",\n doc: \"Send a Slack notification to a channel.\",\n params,\n args: [arg(\"channel\", t.string, \"Where to post: `#builds`, or a user.\")],\n result: t.ref(\"notify.Receipt\"),\n run: (ctx, input) =>\n ctx.port(NotifierPort).send({\n kind: \"slack\",\n channel: String(input.args[0] ?? \"\"),\n mention: input.params?.mention,\n }),\n});\n","import { type ActionDefinition, arg, defineAction, z } from \"@venn-lang/sdk\";\nimport { t } from \"@venn-lang/types\";\nimport { NotifierPort } from \"../port/index.js\";\n\nconst params = z.object({ json: z.unknown().optional() }).optional();\n\n/**\n * `notify.webhook \"https://hooks.example.com/build\" { json: { status: \"red\" } }`.\n *\n * Posts a JSON payload to a URL. The payload rides in `json` and is sent as is.\n *\n * @returns a `notify.Receipt`.\n */\nexport const webhook: ActionDefinition = defineAction({\n name: \"webhook\",\n doc: \"Send a webhook notification with a JSON body.\",\n params,\n args: [arg(\"url\", t.string, \"Where to post the JSON body.\")],\n result: t.ref(\"notify.Receipt\"),\n run: (ctx, input) =>\n ctx.port(NotifierPort).send({\n kind: \"webhook\",\n channel: String(input.args[0] ?? \"\"),\n json: input.params?.json,\n }),\n});\n","import type { ActionDefinition } from \"@venn-lang/sdk\";\nimport { email } from \"./email.js\";\nimport { slack } from \"./slack.js\";\nimport { webhook } from \"./webhook.js\";\n\n/** Every verb in the `notify` namespace, in the order the plugin registers them. */\nexport const notifyActions: ActionDefinition[] = [slack, webhook, email];\n","import type { FakeNotifier, Notification } from \"../port/index.js\";\n\n/**\n * The in-memory `Notifier`. Records what it is handed and touches no network.\n *\n * @returns a fresh notifier whose `sent` array starts empty and grows in\n * dispatch order.\n */\nexport function createFakeNotifier(): FakeNotifier {\n const sent: Notification[] = [];\n return {\n sent,\n send: async (message) => {\n sent.push(message);\n return { delivered: true, id: `fake-${sent.length}` };\n },\n };\n}\n","import { VennError } from \"@venn-lang/contracts\";\nimport type { Notifier } from \"../port/index.js\";\n\n/**\n * The live `Notifier`. No Slack, webhook or email wiring in this build.\n *\n * @returns a notifier whose `send` throws, so the gap is a legible failure\n * rather than a silent no-op.\n * @throws {VennError} `VN8090` on every `send`.\n */\nexport function createRealNotifier(): Notifier {\n return {\n send: async () => {\n throw new VennError({\n code: \"VN8090\",\n message: \"Notifier real client not implemented in this build\",\n });\n },\n };\n}\n","import { type TypeSpec, t } from \"@venn-lang/types\";\n\n/**\n * The types `@venn-lang/notify` publishes to the checker, under the `notify`\n * namespace. Kept as data, and mirroring `NotifyReceipt` in\n * `port/notifier.types.ts` by hand, so a generator reading the emitted `.d.ts`\n * can replace this file unnoticed. The name drops its prefix on the way in\n * because the namespace already says `notify`.\n */\nexport const notifyTypeDefs: Readonly<Record<string, TypeSpec>> = {\n /**\n * What the notifier answers with once a message is on its way. `delivered`\n * reports the dispatch, not the reading: nobody here knows whether it was seen.\n */\n Receipt: t.record({ delivered: t.bool, id: t.string }),\n};\n","import { definePlugin, type PluginDefinition } from \"@venn-lang/sdk\";\nimport { notifyActions } from \"./actions/index.js\";\nimport { notifyTypeDefs } from \"./types.js\";\n\n/**\n * The `@venn-lang/notify` plugin. Registers the `notify` namespace: the `slack`,\n * `webhook` and `email` verbs and the nominal `notify.Receipt` type. Requires\n * the `net` capability, since every verb dispatches over the network.\n */\nexport const notifyPlugin: PluginDefinition = definePlugin({\n name: \"venn/notify\",\n version: \"0.0.0\",\n namespace: \"notify\",\n requires: [\"net\"],\n actions: notifyActions,\n typeDefs: notifyTypeDefs,\n});\n\nexport { notifyPlugin as default };\n"],"mappings":";;;;;;;;;AAQA,MAAa,eAA+B;CAC1C,IAAI;CACJ,SAAS;CACT,UAAU,CAAC,KAAK;CAChB,SAAS,CAAC,MAAM;AAClB;;;;;;;;;ACMA,MAAa,QAA0B,aAAa;CAClD,MAAM;CACN,KAAK;CACL,QAlBa,EACZ,OAAO;EACN,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS;EAC7B,MAAM,EAAE,OAAO,CAAC,CAAC,SAAS;CAC5B,CAAC,CAAC,CACD,SAaD;CACA,MAAM,CAAC,IAAI,MAAM,EAAE,QAAQ,kBAAkB,CAAC;CAC9C,QAAQ,EAAE,IAAI,gBAAgB;CAC9B,MAAM,KAAK,UACT,IAAI,KAAK,YAAY,CAAC,CAAC,KAAK;EAC1B,MAAM;EACN,SAAS,OAAO,MAAM,KAAK,MAAM,EAAE;EACnC,SAAS,MAAM,QAAQ;EACvB,MAAM,MAAM,QAAQ;CACtB,CAAC;AACL,CAAC;;;;AG1BD,MAAa,gBAAoC;CFOV,aAAa;EAClD,MAAM;EACN,KAAK;EACL,QAZa,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,SAY1D;EACA,MAAM,CAAC,IAAI,WAAW,EAAE,QAAQ,sCAAsC,CAAC;EACvE,QAAQ,EAAE,IAAI,gBAAgB;EAC9B,MAAM,KAAK,UACT,IAAI,KAAK,YAAY,CAAC,CAAC,KAAK;GAC1B,MAAM;GACN,SAAS,OAAO,MAAM,KAAK,MAAM,EAAE;GACnC,SAAS,MAAM,QAAQ;EACzB,CAAC;CACL,CEnBkD;CDOT,aAAa;EACpD,MAAM;EACN,KAAK;EACL,QAZa,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,SAYxD;EACA,MAAM,CAAC,IAAI,OAAO,EAAE,QAAQ,8BAA8B,CAAC;EAC3D,QAAQ,EAAE,IAAI,gBAAgB;EAC9B,MAAM,KAAK,UACT,IAAI,KAAK,YAAY,CAAC,CAAC,KAAK;GAC1B,MAAM;GACN,SAAS,OAAO,MAAM,KAAK,MAAM,EAAE;GACnC,MAAM,MAAM,QAAQ;EACtB,CAAC;CACL,CCnByD;CAAS;AAAK;;;;;;;;;ACEvE,SAAgB,qBAAmC;CACjD,MAAM,OAAuB,CAAC;CAC9B,OAAO;EACL;EACA,MAAM,OAAO,YAAY;GACvB,KAAK,KAAK,OAAO;GACjB,OAAO;IAAE,WAAW;IAAM,IAAI,QAAQ,KAAK;GAAS;EACtD;CACF;AACF;;;;;;;;;;ACPA,SAAgB,qBAA+B;CAC7C,OAAO,EACL,MAAM,YAAY;EAChB,MAAM,IAAI,UAAU;GAClB,MAAM;GACN,SAAS;EACX,CAAC;CACH,EACF;AACF;;;;;;;;AEVA,MAAa,eAAiC,aAAa;CACzD,MAAM;CACN,SAAS;CACT,WAAW;CACX,UAAU,CAAC,KAAK;CAChB,SAAS;CACT,UAAU;;;;;ADDV,SAAS,EAAE,OAAO;EAAE,WAAW,EAAE;EAAM,IAAI,EAAE;CAAO,CAAC,ECC3C;AACZ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@venn-lang/notify",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The notify namespace: send a Slack message, a webhook or an email from a flow.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"venn",
|
|
7
|
+
"testing",
|
|
8
|
+
"e2e",
|
|
9
|
+
"notify"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://github.com/venn-lang/venn/tree/main/packages/std-notify#readme",
|
|
12
|
+
"bugs": "https://github.com/venn-lang/venn/issues",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/venn-lang/venn.git",
|
|
16
|
+
"directory": "packages/std-notify"
|
|
17
|
+
},
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"author": "Vinicius Borges",
|
|
20
|
+
"type": "module",
|
|
21
|
+
"sideEffects": false,
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"development": "./src/index.ts",
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"import": "./dist/index.js",
|
|
27
|
+
"default": "./dist/index.js"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"src",
|
|
33
|
+
"!src/**/*.test.ts",
|
|
34
|
+
"!src/**/*.suite.ts"
|
|
35
|
+
],
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@venn-lang/contracts": "0.1.0",
|
|
41
|
+
"@venn-lang/sdk": "0.1.0",
|
|
42
|
+
"@venn-lang/types": "0.1.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"tsdown": "^0.22.14",
|
|
46
|
+
"typescript": "^7.0.2",
|
|
47
|
+
"vitest": "^4.1.10"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsdown",
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"typecheck": "tsc --noEmit"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type ActionDefinition, arg, defineAction, z } from "@venn-lang/sdk";
|
|
2
|
+
import { t } from "@venn-lang/types";
|
|
3
|
+
import { NotifierPort } from "../port/index.js";
|
|
4
|
+
|
|
5
|
+
const params = z
|
|
6
|
+
.object({
|
|
7
|
+
subject: z.string().optional(),
|
|
8
|
+
body: z.string().optional(),
|
|
9
|
+
})
|
|
10
|
+
.optional();
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* `notify.email "qa@example.com" { subject: "Nightly failed", body: report }`.
|
|
14
|
+
*
|
|
15
|
+
* Sends one email. Both `subject` and `body` are optional; an empty recipient
|
|
16
|
+
* is passed straight through to the notifier, which decides what to do with it.
|
|
17
|
+
*
|
|
18
|
+
* @returns a `notify.Receipt`.
|
|
19
|
+
*/
|
|
20
|
+
export const email: ActionDefinition = defineAction({
|
|
21
|
+
name: "email",
|
|
22
|
+
doc: "Send an email notification.",
|
|
23
|
+
params,
|
|
24
|
+
args: [arg("to", t.string, "Who receives it.")],
|
|
25
|
+
result: t.ref("notify.Receipt"),
|
|
26
|
+
run: (ctx, input) =>
|
|
27
|
+
ctx.port(NotifierPort).send({
|
|
28
|
+
kind: "email",
|
|
29
|
+
channel: String(input.args[0] ?? ""),
|
|
30
|
+
subject: input.params?.subject,
|
|
31
|
+
body: input.params?.body,
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ActionDefinition } from "@venn-lang/sdk";
|
|
2
|
+
import { email } from "./email.js";
|
|
3
|
+
import { slack } from "./slack.js";
|
|
4
|
+
import { webhook } from "./webhook.js";
|
|
5
|
+
|
|
6
|
+
/** Every verb in the `notify` namespace, in the order the plugin registers them. */
|
|
7
|
+
export const notifyActions: ActionDefinition[] = [slack, webhook, email];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type ActionDefinition, arg, defineAction, z } from "@venn-lang/sdk";
|
|
2
|
+
import { t } from "@venn-lang/types";
|
|
3
|
+
import { NotifierPort } from "../port/index.js";
|
|
4
|
+
|
|
5
|
+
const params = z.object({ mention: z.string().optional() }).optional();
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* `notify.slack "#qa" { mention: "@oncall" }`.
|
|
9
|
+
*
|
|
10
|
+
* Posts to a Slack channel or a user. `mention` pings someone alongside the post.
|
|
11
|
+
*
|
|
12
|
+
* @returns a `notify.Receipt`.
|
|
13
|
+
*/
|
|
14
|
+
export const slack: ActionDefinition = defineAction({
|
|
15
|
+
name: "slack",
|
|
16
|
+
doc: "Send a Slack notification to a channel.",
|
|
17
|
+
params,
|
|
18
|
+
args: [arg("channel", t.string, "Where to post: `#builds`, or a user.")],
|
|
19
|
+
result: t.ref("notify.Receipt"),
|
|
20
|
+
run: (ctx, input) =>
|
|
21
|
+
ctx.port(NotifierPort).send({
|
|
22
|
+
kind: "slack",
|
|
23
|
+
channel: String(input.args[0] ?? ""),
|
|
24
|
+
mention: input.params?.mention,
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type ActionDefinition, arg, defineAction, z } from "@venn-lang/sdk";
|
|
2
|
+
import { t } from "@venn-lang/types";
|
|
3
|
+
import { NotifierPort } from "../port/index.js";
|
|
4
|
+
|
|
5
|
+
const params = z.object({ json: z.unknown().optional() }).optional();
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* `notify.webhook "https://hooks.example.com/build" { json: { status: "red" } }`.
|
|
9
|
+
*
|
|
10
|
+
* Posts a JSON payload to a URL. The payload rides in `json` and is sent as is.
|
|
11
|
+
*
|
|
12
|
+
* @returns a `notify.Receipt`.
|
|
13
|
+
*/
|
|
14
|
+
export const webhook: ActionDefinition = defineAction({
|
|
15
|
+
name: "webhook",
|
|
16
|
+
doc: "Send a webhook notification with a JSON body.",
|
|
17
|
+
params,
|
|
18
|
+
args: [arg("url", t.string, "Where to post the JSON body.")],
|
|
19
|
+
result: t.ref("notify.Receipt"),
|
|
20
|
+
run: (ctx, input) =>
|
|
21
|
+
ctx.port(NotifierPort).send({
|
|
22
|
+
kind: "webhook",
|
|
23
|
+
channel: String(input.args[0] ?? ""),
|
|
24
|
+
json: input.params?.json,
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { FakeNotifier, Notification } from "../port/index.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The in-memory `Notifier`. Records what it is handed and touches no network.
|
|
5
|
+
*
|
|
6
|
+
* @returns a fresh notifier whose `sent` array starts empty and grows in
|
|
7
|
+
* dispatch order.
|
|
8
|
+
*/
|
|
9
|
+
export function createFakeNotifier(): FakeNotifier {
|
|
10
|
+
const sent: Notification[] = [];
|
|
11
|
+
return {
|
|
12
|
+
sent,
|
|
13
|
+
send: async (message) => {
|
|
14
|
+
sent.push(message);
|
|
15
|
+
return { delivered: true, id: `fake-${sent.length}` };
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { VennError } from "@venn-lang/contracts";
|
|
2
|
+
import type { Notifier } from "../port/index.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The live `Notifier`. No Slack, webhook or email wiring in this build.
|
|
6
|
+
*
|
|
7
|
+
* @returns a notifier whose `send` throws, so the gap is a legible failure
|
|
8
|
+
* rather than a silent no-op.
|
|
9
|
+
* @throws {VennError} `VN8090` on every `send`.
|
|
10
|
+
*/
|
|
11
|
+
export function createRealNotifier(): Notifier {
|
|
12
|
+
return {
|
|
13
|
+
send: async () => {
|
|
14
|
+
throw new VennError({
|
|
15
|
+
code: "VN8090",
|
|
16
|
+
message: "Notifier real client not implemented in this build",
|
|
17
|
+
});
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@venn-lang/notify`: the verbs that tell someone what a run did (`slack`,
|
|
3
|
+
* `webhook`, `email`) and the `Notifier` port they dispatch through.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export * from "./actions/index.js";
|
|
7
|
+
export * from "./clients/index.js";
|
|
8
|
+
export { notifyPlugin, notifyPlugin as default } from "./plugin.js";
|
|
9
|
+
export * from "./port/index.js";
|
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { definePlugin, type PluginDefinition } from "@venn-lang/sdk";
|
|
2
|
+
import { notifyActions } from "./actions/index.js";
|
|
3
|
+
import { notifyTypeDefs } from "./types.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The `@venn-lang/notify` plugin. Registers the `notify` namespace: the `slack`,
|
|
7
|
+
* `webhook` and `email` verbs and the nominal `notify.Receipt` type. Requires
|
|
8
|
+
* the `net` capability, since every verb dispatches over the network.
|
|
9
|
+
*/
|
|
10
|
+
export const notifyPlugin: PluginDefinition = definePlugin({
|
|
11
|
+
name: "venn/notify",
|
|
12
|
+
version: "0.0.0",
|
|
13
|
+
namespace: "notify",
|
|
14
|
+
requires: ["net"],
|
|
15
|
+
actions: notifyActions,
|
|
16
|
+
typeDefs: notifyTypeDefs,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export { notifyPlugin as default };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Port } from "@venn-lang/contracts";
|
|
2
|
+
import type { Notifier } from "./notifier.types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The port every `notify` verb dispatches through. Requires the `net`
|
|
6
|
+
* capability, so a host without it fails to load the plugin rather than
|
|
7
|
+
* failing mid-run.
|
|
8
|
+
*/
|
|
9
|
+
export const NotifierPort: Port<Notifier> = {
|
|
10
|
+
id: "venn.port.notifier",
|
|
11
|
+
version: 1,
|
|
12
|
+
requires: ["net"],
|
|
13
|
+
methods: ["send"],
|
|
14
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** The kind of channel a notification targets. */
|
|
2
|
+
export type NotificationKind = "slack" | "webhook" | "email";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* One message handed to the `Notifier`. `kind` picks the channel type and
|
|
6
|
+
* decides which of the optional fields carry meaning: `subject` and `body` for
|
|
7
|
+
* email, `mention` for Slack, `json` for a webhook.
|
|
8
|
+
*/
|
|
9
|
+
export interface Notification {
|
|
10
|
+
kind: NotificationKind;
|
|
11
|
+
/** Where it goes, read in the terms of its `kind`: a Slack channel, a URL, an address. */
|
|
12
|
+
channel: string;
|
|
13
|
+
subject?: string;
|
|
14
|
+
body?: string;
|
|
15
|
+
mention?: string;
|
|
16
|
+
json?: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** What a `Notifier` answers with once a message is on its way. */
|
|
20
|
+
export interface NotifyReceipt {
|
|
21
|
+
/** The dispatch succeeded. Says nothing about whether anyone read it. */
|
|
22
|
+
delivered: boolean;
|
|
23
|
+
/** The notifier's own handle for the message. */
|
|
24
|
+
id: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The contract every notification goes through, whatever the channel. */
|
|
28
|
+
export interface Notifier {
|
|
29
|
+
/**
|
|
30
|
+
* Dispatches one message.
|
|
31
|
+
*
|
|
32
|
+
* @returns the receipt for the dispatch.
|
|
33
|
+
* @throws {VennError} `VN8090` from an implementation that cannot deliver.
|
|
34
|
+
*/
|
|
35
|
+
send(message: Notification): Promise<NotifyReceipt>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A `Notifier` that keeps every sent notification in memory, for assertions. */
|
|
39
|
+
export interface FakeNotifier extends Notifier {
|
|
40
|
+
readonly sent: readonly Notification[];
|
|
41
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type TypeSpec, t } from "@venn-lang/types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The types `@venn-lang/notify` publishes to the checker, under the `notify`
|
|
5
|
+
* namespace. Kept as data, and mirroring `NotifyReceipt` in
|
|
6
|
+
* `port/notifier.types.ts` by hand, so a generator reading the emitted `.d.ts`
|
|
7
|
+
* can replace this file unnoticed. The name drops its prefix on the way in
|
|
8
|
+
* because the namespace already says `notify`.
|
|
9
|
+
*/
|
|
10
|
+
export const notifyTypeDefs: Readonly<Record<string, TypeSpec>> = {
|
|
11
|
+
/**
|
|
12
|
+
* What the notifier answers with once a message is on its way. `delivered`
|
|
13
|
+
* reports the dispatch, not the reading: nobody here knows whether it was seen.
|
|
14
|
+
*/
|
|
15
|
+
Receipt: t.record({ delivered: t.bool, id: t.string }),
|
|
16
|
+
};
|