@pilely/simple-email 0.4.3 → 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 +112 -62
- package/dist/api.d.ts +63 -101
- package/dist/api.js +61 -95
- package/dist/hooks.d.ts +45 -37
- package/dist/hooks.js +46 -58
- package/dist/index.d.ts +7 -4
- package/dist/index.js +7 -2
- package/dist/mock.d.ts +7 -0
- package/dist/mock.js +165 -202
- package/dist/send.d.ts +75 -0
- package/dist/send.js +94 -0
- package/dist/types.d.ts +32 -52
- package/package.json +7 -3
package/dist/api.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
// The simple_email service's
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// The simple_email service's page-side POST routes, one function each: the
|
|
2
|
+
// template send and the owner's account and template management. This is
|
|
3
|
+
// the layer the hooks in hooks.ts call; the package exports the hooks and
|
|
4
|
+
// `sendTemplate`, not the rest. app_id is in the path on every route but
|
|
5
|
+
// `accounts/create` (body) and `accounts/list` (none). Every function takes
|
|
6
|
+
// `{ challenge }` — a bot-check answer, or a function asked once per
|
|
7
|
+
// request — and every request it sends carries it. The freestyle send is
|
|
8
|
+
// not here: only an app's backend may make it, so it lives in send.ts,
|
|
9
|
+
// which needs no browser runtime.
|
|
5
10
|
import { appId, call, collectPages, isMockMode, registerMockService } from "@pilely/core";
|
|
6
11
|
import { createSimpleEmailFake } from "./mock.js";
|
|
7
12
|
// Importing this package (its hooks import this module) is all an app does
|
|
@@ -25,65 +30,32 @@ function requireAppId() {
|
|
|
25
30
|
}
|
|
26
31
|
// ── Sending ──────────────────────────────────────────────────────────────
|
|
27
32
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
+
* The template send: `POST /accounts/{app_id}/templates/{template_id}/send`
|
|
34
|
+
* with the page's own app id. Sends that template, filled from `variables`,
|
|
35
|
+
* to the one recipient the owner stored on it — the caller can never point
|
|
36
|
+
* it anywhere else. `variables` is the body itself: each placeholder of the
|
|
37
|
+
* template's subject and html is read from the field of the same name, and
|
|
38
|
+
* every other field is ignored. A missing placeholder is a 400 and nothing
|
|
39
|
+
* is sent. The service admits the caller only when it is in the template's
|
|
40
|
+
* `send_group` and not in its `deny_group`; everyone else, an unknown
|
|
41
|
+
* template and an exhausted owner balance alike, gets the uniform 404.
|
|
33
42
|
*/
|
|
34
|
-
export async function
|
|
35
|
-
const app_id = requireAppId();
|
|
36
|
-
const idempotency_key = input.idempotency_key;
|
|
37
|
-
const body = input.to_account !== undefined
|
|
38
|
-
? { app_id, to_account: input.to_account, template_id: input.template_id, variables: input.variables, idempotency_key }
|
|
39
|
-
: "template_id" in input
|
|
40
|
-
? { app_id, to: input.to, template_id: input.template_id, variables: input.variables, idempotency_key }
|
|
41
|
-
: { app_id, to: input.to, subject: input.subject, html: input.html, idempotency_key };
|
|
42
|
-
return call({
|
|
43
|
-
service: "simple-email",
|
|
44
|
-
path: "/send",
|
|
45
|
-
body,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
/** `app_id` lives in the body here, like `send` — not in the path. The
|
|
49
|
-
* paged primitive; use `listAllSends` to walk to the end. */
|
|
50
|
-
export async function listSends(options = {}) {
|
|
43
|
+
export async function sendTemplate(templateId, variables = {}, { challenge } = {}) {
|
|
51
44
|
const json = await call({
|
|
52
45
|
service: "simple-email",
|
|
53
|
-
path:
|
|
54
|
-
body:
|
|
55
|
-
|
|
56
|
-
limit: options.limit,
|
|
57
|
-
after_created_time_stamp: options.after_created_time_stamp,
|
|
58
|
-
after_send_id: options.after_send_id,
|
|
59
|
-
},
|
|
60
|
-
});
|
|
61
|
-
return { sends: json.sends, next_cursor: json.next_cursor };
|
|
62
|
-
}
|
|
63
|
-
export async function listAllSends() {
|
|
64
|
-
return collectPages(async (cursor) => {
|
|
65
|
-
const page = await listSends({ limit: 100, ...(cursor ?? {}) });
|
|
66
|
-
return { rows: page.sends, nextCursor: page.next_cursor };
|
|
46
|
+
path: `/accounts/${requireAppId()}/templates/${templateId}/send`,
|
|
47
|
+
body: variables,
|
|
48
|
+
challenge,
|
|
67
49
|
});
|
|
50
|
+
return { send_id: json.send_id };
|
|
68
51
|
}
|
|
69
52
|
// ── Accounts ─────────────────────────────────────────────────────────────
|
|
70
|
-
/** `
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
* and omitted or `null` both mean no reply-to. */
|
|
77
|
-
export async function createAccount(sendGroup, accountSenders, replyTo) {
|
|
78
|
-
const body = {
|
|
79
|
-
app_id: requireAppId(),
|
|
80
|
-
};
|
|
81
|
-
if (sendGroup !== undefined) {
|
|
82
|
-
body.send_group = sendGroup;
|
|
83
|
-
}
|
|
84
|
-
if (accountSenders !== undefined) {
|
|
85
|
-
body.account_senders = accountSenders;
|
|
86
|
-
}
|
|
53
|
+
/** `replyTo` is optional: `undefined` leaves the key off the wire, and
|
|
54
|
+
* omitted or `null` both mean no reply-to. The account carries no send
|
|
55
|
+
* access of its own — who may send is each template's groups, and the
|
|
56
|
+
* app's backend. */
|
|
57
|
+
export async function createAccount(replyTo, { challenge } = {}) {
|
|
58
|
+
const body = { app_id: requireAppId() };
|
|
87
59
|
if (replyTo !== undefined) {
|
|
88
60
|
body.reply_to = replyTo;
|
|
89
61
|
}
|
|
@@ -91,13 +63,14 @@ export async function createAccount(sendGroup, accountSenders, replyTo) {
|
|
|
91
63
|
service: "simple-email",
|
|
92
64
|
path: "/accounts/create",
|
|
93
65
|
body,
|
|
66
|
+
challenge,
|
|
94
67
|
});
|
|
95
68
|
return json.account;
|
|
96
69
|
}
|
|
97
70
|
/** No `app_id` anywhere on this route — `after_app_id` is a cursor field,
|
|
98
71
|
* not a selector. The paged primitive; use `listAllAccounts` to walk to
|
|
99
72
|
* the end. */
|
|
100
|
-
export async function listAccounts(options = {}) {
|
|
73
|
+
export async function listAccounts(options = {}, { challenge } = {}) {
|
|
101
74
|
const json = await call({
|
|
102
75
|
service: "simple-email",
|
|
103
76
|
path: "/accounts/list",
|
|
@@ -106,58 +79,44 @@ export async function listAccounts(options = {}) {
|
|
|
106
79
|
after_created_time_stamp: options.after_created_time_stamp,
|
|
107
80
|
after_app_id: options.after_app_id,
|
|
108
81
|
},
|
|
82
|
+
challenge,
|
|
109
83
|
});
|
|
110
84
|
return { accounts: json.accounts, next_cursor: json.next_cursor };
|
|
111
85
|
}
|
|
112
|
-
export async function listAllAccounts() {
|
|
86
|
+
export async function listAllAccounts(request = {}) {
|
|
113
87
|
return collectPages(async (cursor) => {
|
|
114
|
-
const page = await listAccounts({ limit: 100, ...(cursor ?? {}) });
|
|
88
|
+
const page = await listAccounts({ limit: 100, ...(cursor ?? {}) }, request);
|
|
115
89
|
return { rows: page.accounts, nextCursor: page.next_cursor };
|
|
116
90
|
});
|
|
117
91
|
}
|
|
118
92
|
/** Returns the account plus its live operational state — the four extra
|
|
119
93
|
* fields are nullable siblings of `account`, not nested inside it. */
|
|
120
|
-
export async function accountInfo() {
|
|
94
|
+
export async function accountInfo({ challenge } = {}) {
|
|
121
95
|
const json = await call({
|
|
122
96
|
service: "simple-email",
|
|
123
97
|
path: `/accounts/${requireAppId()}/info`,
|
|
98
|
+
challenge,
|
|
124
99
|
});
|
|
125
100
|
return {
|
|
126
101
|
account: json.account,
|
|
127
102
|
daily_cap: json.daily_cap,
|
|
128
103
|
sent_today: json.sent_today,
|
|
129
104
|
credit_blocked: json.credit_blocked,
|
|
130
|
-
phone_verified: json.phone_verified,
|
|
131
105
|
};
|
|
132
106
|
}
|
|
133
|
-
export async function deleteAccount() {
|
|
134
|
-
await call({ service: "simple-email", path: `/accounts/${requireAppId()}/delete
|
|
135
|
-
}
|
|
136
|
-
/** `sendGroup` is REQUIRED here and its key is always sent, even `null` —
|
|
137
|
-
* the opposite of `createAccount`, where the key is optional. `null`
|
|
138
|
-
* means owner-only. `accountSenders` is optional: `undefined` leaves the
|
|
139
|
-
* key off the wire, which KEEPS the account's current value. */
|
|
140
|
-
export async function setAccountAccess(sendGroup, accountSenders) {
|
|
141
|
-
const body = { send_group: sendGroup };
|
|
142
|
-
if (accountSenders !== undefined) {
|
|
143
|
-
body.account_senders = accountSenders;
|
|
144
|
-
}
|
|
145
|
-
const json = await call({
|
|
146
|
-
service: "simple-email",
|
|
147
|
-
path: `/accounts/${requireAppId()}/access/set`,
|
|
148
|
-
body,
|
|
149
|
-
});
|
|
150
|
-
return json.account;
|
|
107
|
+
export async function deleteAccount({ challenge } = {}) {
|
|
108
|
+
await call({ service: "simple-email", path: `/accounts/${requireAppId()}/delete`, challenge });
|
|
151
109
|
}
|
|
152
110
|
/** `replyTo` is REQUIRED and its key is always sent, even `null` — the
|
|
153
111
|
* server refuses a missing key with a 400, so a typo can never silently
|
|
154
112
|
* clear it. An address sets where recipients' replies go (every send from
|
|
155
113
|
* the account carries it as its `Reply-To`); `null` clears it. */
|
|
156
|
-
export async function setReplyTo(replyTo) {
|
|
114
|
+
export async function setReplyTo(replyTo, { challenge } = {}) {
|
|
157
115
|
const json = await call({
|
|
158
116
|
service: "simple-email",
|
|
159
117
|
path: `/accounts/${requireAppId()}/set_reply_to`,
|
|
160
118
|
body: { reply_to: replyTo },
|
|
119
|
+
challenge,
|
|
161
120
|
});
|
|
162
121
|
return json.account;
|
|
163
122
|
}
|
|
@@ -165,20 +124,21 @@ export async function setReplyTo(replyTo) {
|
|
|
165
124
|
/** Returns metadata only — no `html`. Use `templateInfo` to read the body
|
|
166
125
|
* back.
|
|
167
126
|
*
|
|
168
|
-
* `name` is nullable because the service accepts a nameless template
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
export async function createTemplate(
|
|
127
|
+
* `name` is nullable because the service accepts a nameless template: pass
|
|
128
|
+
* an explicit `null`, and `TemplateMeta.name` comes back `null`. An
|
|
129
|
+
* omitted group is left off the wire (an empty group). */
|
|
130
|
+
export async function createTemplate(body, { challenge } = {}) {
|
|
172
131
|
const json = await call({
|
|
173
132
|
service: "simple-email",
|
|
174
133
|
path: `/accounts/${requireAppId()}/templates/create`,
|
|
175
|
-
body
|
|
134
|
+
body,
|
|
135
|
+
challenge,
|
|
176
136
|
});
|
|
177
137
|
return json.template;
|
|
178
138
|
}
|
|
179
139
|
/** The paged primitive; use `listAllTemplates` to walk to the end. Rows
|
|
180
140
|
* carry metadata only — no `html`. */
|
|
181
|
-
export async function listTemplates(options = {}) {
|
|
141
|
+
export async function listTemplates(options = {}, { challenge } = {}) {
|
|
182
142
|
const json = await call({
|
|
183
143
|
service: "simple-email",
|
|
184
144
|
path: `/accounts/${requireAppId()}/templates/list`,
|
|
@@ -187,39 +147,45 @@ export async function listTemplates(options = {}) {
|
|
|
187
147
|
after_created_time_stamp: options.after_created_time_stamp,
|
|
188
148
|
after_template_id: options.after_template_id,
|
|
189
149
|
},
|
|
150
|
+
challenge,
|
|
190
151
|
});
|
|
191
152
|
return { templates: json.templates, next_cursor: json.next_cursor };
|
|
192
153
|
}
|
|
193
|
-
export async function listAllTemplates() {
|
|
154
|
+
export async function listAllTemplates(request = {}) {
|
|
194
155
|
return collectPages(async (cursor) => {
|
|
195
|
-
const page = await listTemplates({ limit: 100, ...(cursor ?? {}) });
|
|
156
|
+
const page = await listTemplates({ limit: 100, ...(cursor ?? {}) }, request);
|
|
196
157
|
return { rows: page.templates, nextCursor: page.next_cursor };
|
|
197
158
|
});
|
|
198
159
|
}
|
|
199
160
|
/** The only template route whose answer carries `html`. */
|
|
200
|
-
export async function templateInfo(templateNanoid) {
|
|
161
|
+
export async function templateInfo(templateNanoid, { challenge } = {}) {
|
|
201
162
|
const json = await call({
|
|
202
163
|
service: "simple-email",
|
|
203
164
|
path: `/accounts/${requireAppId()}/templates/${templateNanoid}/info`,
|
|
165
|
+
challenge,
|
|
204
166
|
});
|
|
205
167
|
return json.template;
|
|
206
168
|
}
|
|
207
169
|
/**
|
|
208
|
-
* A FULL REPLACE of both `subject` and `html`
|
|
209
|
-
*
|
|
210
|
-
*
|
|
170
|
+
* A FULL REPLACE of both `subject` and `html`; `recipient`, `send_group` and
|
|
171
|
+
* `deny_group` change only when their key is sent. `name` is not updatable
|
|
172
|
+
* at all. Returns metadata only, like `createTemplate`. An oversize body is
|
|
173
|
+
* rejected by the server before any handler runs, so the thrown
|
|
174
|
+
* `PilelyError` carries a null `code`.
|
|
211
175
|
*/
|
|
212
|
-
export async function updateTemplate(templateNanoid, body) {
|
|
176
|
+
export async function updateTemplate(templateNanoid, body, { challenge } = {}) {
|
|
213
177
|
const json = await call({
|
|
214
178
|
service: "simple-email",
|
|
215
179
|
path: `/accounts/${requireAppId()}/templates/${templateNanoid}/update`,
|
|
216
180
|
body,
|
|
181
|
+
challenge,
|
|
217
182
|
});
|
|
218
183
|
return json.template;
|
|
219
184
|
}
|
|
220
|
-
export async function deleteTemplate(templateNanoid) {
|
|
185
|
+
export async function deleteTemplate(templateNanoid, { challenge } = {}) {
|
|
221
186
|
await call({
|
|
222
187
|
service: "simple-email",
|
|
223
188
|
path: `/accounts/${requireAppId()}/templates/${templateNanoid}/delete`,
|
|
189
|
+
challenge,
|
|
224
190
|
});
|
|
225
191
|
}
|
package/dist/hooks.d.ts
CHANGED
|
@@ -1,65 +1,73 @@
|
|
|
1
|
+
import type { ChallengeOption } from "@pilely/core";
|
|
1
2
|
import type { UseInfiniteQueryResult, UseMutationResult, UseQueryResult } from "@tanstack/react-query";
|
|
2
|
-
import type {
|
|
3
|
-
import type { Account, AccountInfoEnvelope,
|
|
4
|
-
export interface PageOptions {
|
|
3
|
+
import type { TemplateSendResult } from "./api.js";
|
|
4
|
+
import type { Account, AccountInfoEnvelope, Template, TemplateMeta } from "./types.js";
|
|
5
|
+
export interface PageOptions extends ChallengeOption {
|
|
5
6
|
/** Rows per page; the server defaults to 50 and caps at 100. */
|
|
6
7
|
limit?: number;
|
|
7
8
|
}
|
|
9
|
+
/** `recipient` is required: the ONE address the template's sends go to.
|
|
10
|
+
* An omitted (or `null`) group is empty: an empty `send_group` admits
|
|
11
|
+
* nobody, an empty `deny_group` refuses nobody. */
|
|
8
12
|
export interface CreateTemplateInput {
|
|
9
13
|
/** `null` for a nameless template. */
|
|
10
14
|
name: string | null;
|
|
11
15
|
subject: string;
|
|
12
16
|
html: string;
|
|
17
|
+
recipient: string;
|
|
18
|
+
/** An owner group, or the platform-wide anonymous or authenticated group. */
|
|
19
|
+
send_group?: string | null;
|
|
20
|
+
/** An owner group only. */
|
|
21
|
+
deny_group?: string | null;
|
|
13
22
|
}
|
|
14
|
-
/** `useCreateEmailAccount`'s object form.
|
|
15
|
-
*
|
|
16
|
-
* `"send_group"`, and an omitted (or `null`) `reply_to` means no reply-to. */
|
|
23
|
+
/** `useCreateEmailAccount`'s object form. An omitted (or `null`) `reply_to`
|
|
24
|
+
* means no reply-to. */
|
|
17
25
|
export interface CreateEmailAccountInput {
|
|
18
|
-
send_group?: string | null;
|
|
19
|
-
account_senders?: AccountSenders;
|
|
20
26
|
/** The address recipients' replies go to. */
|
|
21
27
|
reply_to?: string | null;
|
|
22
28
|
}
|
|
23
|
-
/** `
|
|
24
|
-
*
|
|
25
|
-
|
|
26
|
-
send_group: string | null;
|
|
27
|
-
account_senders?: AccountSenders;
|
|
28
|
-
}
|
|
29
|
-
/** A full replace of both `subject` and `html`; `name` is not updatable. */
|
|
29
|
+
/** A full replace of both `subject` and `html`; `name` is not updatable. An
|
|
30
|
+
* omitted `recipient`, `send_group` or `deny_group` keeps the stored value;
|
|
31
|
+
* `null` empties a group. */
|
|
30
32
|
export interface UpdateTemplateInput {
|
|
31
33
|
templateId: string;
|
|
32
34
|
subject: string;
|
|
33
35
|
html: string;
|
|
36
|
+
recipient?: string;
|
|
37
|
+
send_group?: string | null;
|
|
38
|
+
deny_group?: string | null;
|
|
39
|
+
}
|
|
40
|
+
/** `useSendTemplate`'s variables: the template and its placeholder values. */
|
|
41
|
+
export interface SendTemplateInput {
|
|
42
|
+
templateId: string;
|
|
43
|
+
/** One string per placeholder of the template's subject and html; omitted
|
|
44
|
+
* is `{}`, for a template without placeholders. */
|
|
45
|
+
variables?: Record<string, string>;
|
|
34
46
|
}
|
|
35
|
-
/**
|
|
36
|
-
*
|
|
37
|
-
* once the server accepted it.
|
|
38
|
-
*
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
export declare function useSends(options?: PageOptions): UseInfiniteQueryResult<SendRow[], Error>;
|
|
47
|
+
/** The template send, from a page: the template, filled from `variables`,
|
|
48
|
+
* to the one recipient its owner stored on it. Not optimistic: a send
|
|
49
|
+
* counts only once the server accepted it. The answer is `{ send_id }`,
|
|
50
|
+
* never the address. It counts toward the app's daily send cap, so it
|
|
51
|
+
* refreshes `useEmailAccount()`. */
|
|
52
|
+
export declare function useSendTemplate(options?: ChallengeOption): UseMutationResult<TemplateSendResult, Error, SendTemplateInput>;
|
|
42
53
|
/** The caller's email accounts across apps, as an infinite query. */
|
|
43
54
|
export declare function useEmailAccounts(options?: PageOptions): UseInfiniteQueryResult<Account[], Error>;
|
|
44
|
-
/** This app's account with its live state: daily cap, sent today
|
|
45
|
-
*
|
|
46
|
-
export declare function useEmailAccount(): UseQueryResult<AccountInfoEnvelope, Error>;
|
|
47
|
-
/** Creates this app's account; the variable is
|
|
48
|
-
*
|
|
49
|
-
export declare function useCreateEmailAccount(): UseMutationResult<Account, Error,
|
|
55
|
+
/** This app's account with its live state: daily cap, sent today and
|
|
56
|
+
* credit. */
|
|
57
|
+
export declare function useEmailAccount(options?: ChallengeOption): UseQueryResult<AccountInfoEnvelope, Error>;
|
|
58
|
+
/** Creates this app's account; the variable is optional, or
|
|
59
|
+
* `{ reply_to? }`. */
|
|
60
|
+
export declare function useCreateEmailAccount(options?: ChallengeOption): UseMutationResult<Account, Error, void | CreateEmailAccountInput>;
|
|
50
61
|
/** Deletes this app's account and the templates under it. */
|
|
51
|
-
export declare function useDeleteEmailAccount(): UseMutationResult<void, Error, void>;
|
|
52
|
-
/** Replaces this app's send group (`null` means owner-only); the object form
|
|
53
|
-
* `{ send_group, account_senders? }` also sets who may make account sends. */
|
|
54
|
-
export declare function useSetEmailAccountAccess(): UseMutationResult<Account, Error, string | null | SetEmailAccountAccessInput>;
|
|
62
|
+
export declare function useDeleteEmailAccount(options?: ChallengeOption): UseMutationResult<void, Error, void>;
|
|
55
63
|
/** Sets this app's reply-to address — where recipients' replies go; every
|
|
56
64
|
* send from the account carries it as its `Reply-To` — or clears it with
|
|
57
65
|
* `null`. The variable is required: there is no "keep" form. */
|
|
58
|
-
export declare function useSetEmailReplyTo(): UseMutationResult<Account, Error, string | null>;
|
|
66
|
+
export declare function useSetEmailReplyTo(options?: ChallengeOption): UseMutationResult<Account, Error, string | null>;
|
|
59
67
|
/** This app's templates (metadata only), as an infinite query. */
|
|
60
68
|
export declare function useTemplates(options?: PageOptions): UseInfiniteQueryResult<TemplateMeta[], Error>;
|
|
61
69
|
/** One template with its `html`. An `undefined` id disables the query. */
|
|
62
|
-
export declare function useTemplate(templateId: string | undefined): UseQueryResult<Template, Error>;
|
|
63
|
-
export declare function useCreateTemplate(): UseMutationResult<TemplateMeta, Error, CreateTemplateInput>;
|
|
64
|
-
export declare function useUpdateTemplate(): UseMutationResult<TemplateMeta, Error, UpdateTemplateInput>;
|
|
65
|
-
export declare function useDeleteTemplate(): UseMutationResult<void, Error, string>;
|
|
70
|
+
export declare function useTemplate(templateId: string | undefined, options?: ChallengeOption): UseQueryResult<Template, Error>;
|
|
71
|
+
export declare function useCreateTemplate(options?: ChallengeOption): UseMutationResult<TemplateMeta, Error, CreateTemplateInput>;
|
|
72
|
+
export declare function useUpdateTemplate(options?: ChallengeOption): UseMutationResult<TemplateMeta, Error, UpdateTemplateInput>;
|
|
73
|
+
export declare function useDeleteTemplate(options?: ChallengeOption): UseMutationResult<void, Error, string>;
|
package/dist/hooks.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { serviceQueryKey, useServiceInfiniteQuery, useServiceMutation, useServiceQuery } from "@pilely/core";
|
|
2
|
-
import { accountInfo, createAccount, createTemplate, deleteAccount, deleteTemplate, listAccounts,
|
|
3
|
-
/** `["pilely", "simple-email", "sends", options]` */
|
|
4
|
-
const sendsKey = () => serviceQueryKey("simple-email", "sends");
|
|
2
|
+
import { accountInfo, createAccount, createTemplate, deleteAccount, deleteTemplate, listAccounts, listTemplates, sendTemplate, setReplyTo, templateInfo, updateTemplate, } from "./api.js";
|
|
5
3
|
/** `["pilely", "simple-email", "accounts", options]` */
|
|
6
4
|
const accountsKey = () => serviceQueryKey("simple-email", "accounts");
|
|
7
5
|
/** `["pilely", "simple-email", "account"]` — this app's account info. */
|
|
@@ -11,115 +9,105 @@ const templatesKey = () => serviceQueryKey("simple-email", "templates");
|
|
|
11
9
|
/** `["pilely", "simple-email", "template", templateId]` */
|
|
12
10
|
const templateKey = (templateId) => serviceQueryKey("simple-email", "template", templateId);
|
|
13
11
|
// ── Sending ──────────────────────────────────────────────────────────────
|
|
14
|
-
/**
|
|
15
|
-
*
|
|
16
|
-
* once the server accepted it.
|
|
17
|
-
*
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
/** This app's outbox, newest first, as an infinite query. */
|
|
25
|
-
export function useSends(options = {}) {
|
|
26
|
-
const { limit } = options;
|
|
27
|
-
return useServiceInfiniteQuery("useSends", {
|
|
28
|
-
queryKey: [...sendsKey(), { limit }],
|
|
29
|
-
fetchPage: async (cursor) => {
|
|
30
|
-
const page = await listSends({ limit, ...(cursor ?? {}) });
|
|
31
|
-
return { rows: page.sends, nextCursor: page.next_cursor };
|
|
32
|
-
},
|
|
12
|
+
/** The template send, from a page: the template, filled from `variables`,
|
|
13
|
+
* to the one recipient its owner stored on it. Not optimistic: a send
|
|
14
|
+
* counts only once the server accepted it. The answer is `{ send_id }`,
|
|
15
|
+
* never the address. It counts toward the app's daily send cap, so it
|
|
16
|
+
* refreshes `useEmailAccount()`. */
|
|
17
|
+
export function useSendTemplate(options = {}) {
|
|
18
|
+
const { challenge } = options;
|
|
19
|
+
return useServiceMutation("useSendTemplate", {
|
|
20
|
+
mutationFn: ({ templateId, variables }) => sendTemplate(templateId, variables, { challenge }),
|
|
21
|
+
invalidates: () => [accountKey()],
|
|
33
22
|
});
|
|
34
23
|
}
|
|
35
24
|
// ── Accounts ─────────────────────────────────────────────────────────────
|
|
36
25
|
/** The caller's email accounts across apps, as an infinite query. */
|
|
37
26
|
export function useEmailAccounts(options = {}) {
|
|
38
|
-
const { limit } = options;
|
|
27
|
+
const { limit, challenge } = options;
|
|
39
28
|
return useServiceInfiniteQuery("useEmailAccounts", {
|
|
40
29
|
queryKey: [...accountsKey(), { limit }],
|
|
41
30
|
fetchPage: async (cursor) => {
|
|
42
|
-
const page = await listAccounts({ limit, ...(cursor ?? {}) });
|
|
31
|
+
const page = await listAccounts({ limit, ...(cursor ?? {}) }, { challenge });
|
|
43
32
|
return { rows: page.accounts, nextCursor: page.next_cursor };
|
|
44
33
|
},
|
|
45
34
|
});
|
|
46
35
|
}
|
|
47
|
-
/** This app's account with its live state: daily cap, sent today
|
|
48
|
-
*
|
|
49
|
-
export function useEmailAccount() {
|
|
50
|
-
|
|
36
|
+
/** This app's account with its live state: daily cap, sent today and
|
|
37
|
+
* credit. */
|
|
38
|
+
export function useEmailAccount(options = {}) {
|
|
39
|
+
const { challenge } = options;
|
|
40
|
+
return useServiceQuery("useEmailAccount", {
|
|
41
|
+
queryKey: accountKey(),
|
|
42
|
+
queryFn: () => accountInfo({ challenge }),
|
|
43
|
+
});
|
|
51
44
|
}
|
|
52
|
-
/** Creates this app's account; the variable is
|
|
53
|
-
*
|
|
54
|
-
export function useCreateEmailAccount() {
|
|
45
|
+
/** Creates this app's account; the variable is optional, or
|
|
46
|
+
* `{ reply_to? }`. */
|
|
47
|
+
export function useCreateEmailAccount(options = {}) {
|
|
48
|
+
const { challenge } = options;
|
|
55
49
|
return useServiceMutation("useCreateEmailAccount", {
|
|
56
|
-
mutationFn: (input) =>
|
|
57
|
-
? createAccount(input.send_group, input.account_senders, input.reply_to)
|
|
58
|
-
: createAccount(input ?? undefined),
|
|
50
|
+
mutationFn: (input) => createAccount(input ? input.reply_to : undefined, { challenge }),
|
|
59
51
|
invalidates: () => [accountsKey(), accountKey()],
|
|
60
52
|
});
|
|
61
53
|
}
|
|
62
54
|
/** Deletes this app's account and the templates under it. */
|
|
63
|
-
export function useDeleteEmailAccount() {
|
|
55
|
+
export function useDeleteEmailAccount(options = {}) {
|
|
56
|
+
const { challenge } = options;
|
|
64
57
|
return useServiceMutation("useDeleteEmailAccount", {
|
|
65
|
-
mutationFn: () => deleteAccount(),
|
|
58
|
+
mutationFn: () => deleteAccount({ challenge }),
|
|
66
59
|
invalidates: () => [accountsKey(), accountKey(), templatesKey(), serviceQueryKey("simple-email", "template")],
|
|
67
60
|
});
|
|
68
61
|
}
|
|
69
|
-
/** Replaces this app's send group (`null` means owner-only); the object form
|
|
70
|
-
* `{ send_group, account_senders? }` also sets who may make account sends. */
|
|
71
|
-
export function useSetEmailAccountAccess() {
|
|
72
|
-
return useServiceMutation("useSetEmailAccountAccess", {
|
|
73
|
-
mutationFn: (input) => typeof input === "object" && input !== null
|
|
74
|
-
? setAccountAccess(input.send_group, input.account_senders)
|
|
75
|
-
: setAccountAccess(input),
|
|
76
|
-
invalidates: () => [accountsKey(), accountKey()],
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
62
|
/** Sets this app's reply-to address — where recipients' replies go; every
|
|
80
63
|
* send from the account carries it as its `Reply-To` — or clears it with
|
|
81
64
|
* `null`. The variable is required: there is no "keep" form. */
|
|
82
|
-
export function useSetEmailReplyTo() {
|
|
65
|
+
export function useSetEmailReplyTo(options = {}) {
|
|
66
|
+
const { challenge } = options;
|
|
83
67
|
return useServiceMutation("useSetEmailReplyTo", {
|
|
84
|
-
mutationFn: (replyTo) => setReplyTo(replyTo),
|
|
68
|
+
mutationFn: (replyTo) => setReplyTo(replyTo, { challenge }),
|
|
85
69
|
invalidates: () => [accountsKey(), accountKey()],
|
|
86
70
|
});
|
|
87
71
|
}
|
|
88
72
|
// ── Templates ────────────────────────────────────────────────────────────
|
|
89
73
|
/** This app's templates (metadata only), as an infinite query. */
|
|
90
74
|
export function useTemplates(options = {}) {
|
|
91
|
-
const { limit } = options;
|
|
75
|
+
const { limit, challenge } = options;
|
|
92
76
|
return useServiceInfiniteQuery("useTemplates", {
|
|
93
77
|
queryKey: [...templatesKey(), { limit }],
|
|
94
78
|
fetchPage: async (cursor) => {
|
|
95
|
-
const page = await listTemplates({ limit, ...(cursor ?? {}) });
|
|
79
|
+
const page = await listTemplates({ limit, ...(cursor ?? {}) }, { challenge });
|
|
96
80
|
return { rows: page.templates, nextCursor: page.next_cursor };
|
|
97
81
|
},
|
|
98
82
|
});
|
|
99
83
|
}
|
|
100
84
|
/** One template with its `html`. An `undefined` id disables the query. */
|
|
101
|
-
export function useTemplate(templateId) {
|
|
85
|
+
export function useTemplate(templateId, options = {}) {
|
|
86
|
+
const { challenge } = options;
|
|
102
87
|
return useServiceQuery("useTemplate", {
|
|
103
88
|
queryKey: templateKey(templateId),
|
|
104
|
-
queryFn: () => templateInfo(templateId),
|
|
89
|
+
queryFn: () => templateInfo(templateId, { challenge }),
|
|
105
90
|
enabled: templateId !== undefined,
|
|
106
91
|
});
|
|
107
92
|
}
|
|
108
|
-
export function useCreateTemplate() {
|
|
93
|
+
export function useCreateTemplate(options = {}) {
|
|
94
|
+
const { challenge } = options;
|
|
109
95
|
return useServiceMutation("useCreateTemplate", {
|
|
110
|
-
mutationFn: (
|
|
96
|
+
mutationFn: (input) => createTemplate(input, { challenge }),
|
|
111
97
|
invalidates: () => [templatesKey()],
|
|
112
98
|
});
|
|
113
99
|
}
|
|
114
|
-
export function useUpdateTemplate() {
|
|
100
|
+
export function useUpdateTemplate(options = {}) {
|
|
101
|
+
const { challenge } = options;
|
|
115
102
|
return useServiceMutation("useUpdateTemplate", {
|
|
116
|
-
mutationFn: ({ templateId,
|
|
103
|
+
mutationFn: ({ templateId, ...body }) => updateTemplate(templateId, body, { challenge }),
|
|
117
104
|
invalidates: (_template, { templateId }) => [templatesKey(), templateKey(templateId)],
|
|
118
105
|
});
|
|
119
106
|
}
|
|
120
|
-
export function useDeleteTemplate() {
|
|
107
|
+
export function useDeleteTemplate(options = {}) {
|
|
108
|
+
const { challenge } = options;
|
|
121
109
|
return useServiceMutation("useDeleteTemplate", {
|
|
122
|
-
mutationFn: deleteTemplate,
|
|
110
|
+
mutationFn: (templateId) => deleteTemplate(templateId, { challenge }),
|
|
123
111
|
invalidates: (_none, templateId) => [templatesKey(), templateKey(templateId)],
|
|
124
112
|
});
|
|
125
113
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
export { useCreateEmailAccount, useCreateTemplate, useDeleteEmailAccount, useDeleteTemplate, useEmailAccount, useEmailAccounts,
|
|
2
|
-
export type { CreateEmailAccountInput, CreateTemplateInput, PageOptions,
|
|
3
|
-
export
|
|
4
|
-
export type {
|
|
1
|
+
export { useCreateEmailAccount, useCreateTemplate, useDeleteEmailAccount, useDeleteTemplate, useEmailAccount, useEmailAccounts, useSendTemplate, useSetEmailReplyTo, useTemplate, useTemplates, useUpdateTemplate, } from "./hooks.js";
|
|
2
|
+
export type { CreateEmailAccountInput, CreateTemplateInput, PageOptions, SendTemplateInput, UpdateTemplateInput, } from "./hooks.js";
|
|
3
|
+
export { sendTemplate } from "./api.js";
|
|
4
|
+
export type { TemplateSendResult } from "./api.js";
|
|
5
|
+
export { SIMPLE_EMAIL_ORIGIN, SendError, send } from "./send.js";
|
|
6
|
+
export type { SendContent, SendInput, SendKey, SendOptions, SendResult } from "./send.js";
|
|
7
|
+
export type { Account, SimpleEmailErrorCode, AccountCursor, AccountInfoEnvelope, Template, TemplateCursor, TemplateMeta, } from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
// @pilely/simple-email — React hooks over the simple_email service: a query
|
|
2
2
|
// hook for every read, a mutation hook for every write. Render them inside
|
|
3
|
-
// @pilely/core's <PilelyProvider>.
|
|
4
|
-
|
|
3
|
+
// @pilely/core's <PilelyProvider>. `sendTemplate` is the template send a
|
|
4
|
+
// page makes, as a plain function. `send` is the freestyle send for an
|
|
5
|
+
// app's backend only; a server imports it from `@pilely/simple-email/send`,
|
|
6
|
+
// which loads none of this entry's peers.
|
|
7
|
+
export { useCreateEmailAccount, useCreateTemplate, useDeleteEmailAccount, useDeleteTemplate, useEmailAccount, useEmailAccounts, useSendTemplate, useSetEmailReplyTo, useTemplate, useTemplates, useUpdateTemplate, } from "./hooks.js";
|
|
8
|
+
export { sendTemplate } from "./api.js";
|
|
9
|
+
export { SIMPLE_EMAIL_ORIGIN, SendError, send } from "./send.js";
|
package/dist/mock.d.ts
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
import type { MockServiceFactory } from "@pilely/core";
|
|
2
|
+
/** The fake's ids for the two platform-wide groups: fixed test values, not
|
|
3
|
+
* the production ids. `@pilely/simple-group`'s fake answers the same two
|
|
4
|
+
* from `groups/anonymous` and `groups/authenticated`, so in mock mode an id
|
|
5
|
+
* read from `anonymousGroupId()` / `authenticatedGroupId()` works as a
|
|
6
|
+
* template's `send_group` here. Not part of the package's public surface. */
|
|
7
|
+
export declare const MOCK_ANON_GROUP = "mockAnonymousGroup0001";
|
|
8
|
+
export declare const MOCK_AUTH_GROUP = "mockAuthenticatedGrp01";
|
|
2
9
|
export declare const createSimpleEmailFake: MockServiceFactory;
|