@oxy.so/contracts 4.0.0 → 4.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.
@@ -0,0 +1,273 @@
1
+ "use strict";
2
+ /**
3
+ * Wire contract for the Inbox read API (`/email/*` on oxy-api).
4
+ *
5
+ * These schemas describe what a client RECEIVES — the JSON-serialised DTOs of
6
+ * `packages/api/src/services/email.service.ts` — so every timestamp is an ISO
7
+ * string and every nullable column is `null`, never absent. oxy-api asserts at
8
+ * the type level that its DTOs serialise to exactly these shapes, and a
9
+ * database-backed test parses real responses with them; a client parses with
10
+ * the same schemas. One declaration, two sides, no drift.
11
+ *
12
+ * Why this exists: the Inbox client used to declare its own copy, with
13
+ * `contentId: z.string().optional()`. oxy-api sends `null` for an attachment
14
+ * without a Content-ID, the client's parse failed, and the list silently
15
+ * dropped the whole message — a verification-code mail that appeared for a
16
+ * second (the realtime placeholder) and then vanished (the refetch).
17
+ *
18
+ * Unknown keys are stripped, not rejected, so the server may ADD a field
19
+ * without breaking an older client. Removing or re-typing one is breaking.
20
+ *
21
+ * Platform-agnostic — zod only, no react/react-native/expo.
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.rfcMessageIdSchema = exports.RFC_MESSAGE_ID_PATTERN = exports.emailOutboxSchema = exports.emailContactSchema = exports.emailBundledInboxSchema = exports.emailBundleSchema = exports.emailFilterSchema = exports.emailFilterActionSchema = exports.emailFilterConditionSchema = exports.emailLabelSchema = exports.emailSystemLabelSchema = exports.emailUserLabelSchema = exports.emailMailboxSchema = exports.emailMessageSchema = exports.emailMessageHighlightSchema = exports.emailMessageCardSchema = exports.emailMessageFlagsSchema = exports.emailAttachmentSchema = exports.emailMessageAddressSchema = exports.EMAIL_OUTBOX_STATUSES = exports.EMAIL_FILTER_ACTION_TYPES = exports.EMAIL_FILTER_CONDITION_OPERATORS = exports.EMAIL_FILTER_CONDITION_FIELDS = exports.MESSAGE_CARD_TYPES = void 0;
25
+ const zod_1 = require("zod");
26
+ /** An ISO-8601 instant as `Date.prototype.toJSON` writes it. */
27
+ const isoInstant = zod_1.z.string().datetime();
28
+ // ─── Vocabularies ───────────────────────────────────────────────────
29
+ /** Structured data cards the AI extractor can emit. */
30
+ exports.MESSAGE_CARD_TYPES = ['trip', 'purchase', 'event', 'bill', 'package'];
31
+ /** What a mail-rule condition looks at. */
32
+ exports.EMAIL_FILTER_CONDITION_FIELDS = ['from', 'to', 'subject', 'has-attachment', 'size'];
33
+ /** How a mail-rule condition compares. */
34
+ exports.EMAIL_FILTER_CONDITION_OPERATORS = [
35
+ 'contains',
36
+ 'equals',
37
+ 'not-contains',
38
+ 'starts-with',
39
+ 'ends-with',
40
+ 'greater-than',
41
+ 'less-than',
42
+ ];
43
+ /** What a mail rule does. */
44
+ exports.EMAIL_FILTER_ACTION_TYPES = [
45
+ 'move',
46
+ 'label',
47
+ 'star',
48
+ 'mark-read',
49
+ 'archive',
50
+ 'delete',
51
+ 'forward',
52
+ ];
53
+ /** Lifecycle of a durable outbound delivery. */
54
+ exports.EMAIL_OUTBOX_STATUSES = ['pending', 'processing', 'sent', 'failed', 'cancelled'];
55
+ // ─── Messages ───────────────────────────────────────────────────────
56
+ /** One addressee. A header without a display name carries `name: ''`. */
57
+ exports.emailMessageAddressSchema = zod_1.z.object({
58
+ name: zod_1.z.string(),
59
+ address: zod_1.z.string(),
60
+ });
61
+ /**
62
+ * One attached file. `contentId` is `null` — present, not absent — when the
63
+ * part had no Content-ID, which is most attachments.
64
+ */
65
+ exports.emailAttachmentSchema = zod_1.z.object({
66
+ fileId: zod_1.z.string(),
67
+ name: zod_1.z.string(),
68
+ contentType: zod_1.z.string(),
69
+ size: zod_1.z.number(),
70
+ contentId: zod_1.z.string().nullable(),
71
+ isInline: zod_1.z.boolean(),
72
+ });
73
+ exports.emailMessageFlagsSchema = zod_1.z.object({
74
+ seen: zod_1.z.boolean(),
75
+ starred: zod_1.z.boolean(),
76
+ answered: zod_1.z.boolean(),
77
+ forwarded: zod_1.z.boolean(),
78
+ draft: zod_1.z.boolean(),
79
+ pinned: zod_1.z.boolean(),
80
+ });
81
+ /** The AI-extracted card. Every field but `type` may be unknown. */
82
+ exports.emailMessageCardSchema = zod_1.z.object({
83
+ type: zod_1.z.enum(exports.MESSAGE_CARD_TYPES),
84
+ data: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).nullable(),
85
+ confidence: zod_1.z.number().nullable(),
86
+ extractedAt: isoInstant.nullable(),
87
+ });
88
+ /** One extracted key/value rendered as a chip. */
89
+ exports.emailMessageHighlightSchema = zod_1.z.object({
90
+ type: zod_1.z.string(),
91
+ value: zod_1.z.string(),
92
+ label: zod_1.z.string(),
93
+ });
94
+ /**
95
+ * A stored message as every `/email` read returns it.
96
+ *
97
+ * `_id` and `id` are the same row id (see the "Wire shapes" note in oxy-api's
98
+ * email service). `messageId` is the RFC 5322 `Message-ID` header — NOT the row
99
+ * id — and it is what `inReplyTo` / `references` of a reply must name.
100
+ */
101
+ exports.emailMessageSchema = zod_1.z.object({
102
+ _id: zod_1.z.string(),
103
+ id: zod_1.z.string(),
104
+ userId: zod_1.z.string(),
105
+ mailboxId: zod_1.z.string(),
106
+ messageId: zod_1.z.string(),
107
+ threadId: zod_1.z.string(),
108
+ from: exports.emailMessageAddressSchema,
109
+ to: zod_1.z.array(exports.emailMessageAddressSchema),
110
+ cc: zod_1.z.array(exports.emailMessageAddressSchema),
111
+ bcc: zod_1.z.array(exports.emailMessageAddressSchema),
112
+ replyTo: exports.emailMessageAddressSchema.optional(),
113
+ subject: zod_1.z.string(),
114
+ attachments: zod_1.z.array(exports.emailAttachmentSchema),
115
+ flags: exports.emailMessageFlagsSchema,
116
+ labels: zod_1.z.array(zod_1.z.string()),
117
+ card: exports.emailMessageCardSchema.optional(),
118
+ highlights: zod_1.z.array(exports.emailMessageHighlightSchema),
119
+ encrypted: zod_1.z.boolean(),
120
+ spamScore: zod_1.z.number().nullable(),
121
+ spamAction: zod_1.z.string().nullable(),
122
+ size: zod_1.z.number(),
123
+ inReplyTo: zod_1.z.string().nullable(),
124
+ references: zod_1.z.array(zod_1.z.string()),
125
+ aliasTag: zod_1.z.string().nullable(),
126
+ snoozedUntil: isoInstant.nullable(),
127
+ snoozedFromMailbox: zod_1.z.string().nullable(),
128
+ scheduledAt: isoInstant.nullable(),
129
+ readReceiptRequested: zod_1.z.boolean(),
130
+ readReceiptSent: zod_1.z.boolean(),
131
+ date: isoInstant,
132
+ receivedAt: isoInstant,
133
+ createdAt: isoInstant,
134
+ updatedAt: isoInstant,
135
+ draftRevision: zod_1.z.number().int().min(1),
136
+ /** Present only on the reads that return bodies (single message, thread). */
137
+ text: zod_1.z.string().nullable().optional(),
138
+ html: zod_1.z.string().nullable().optional(),
139
+ headers: zod_1.z.record(zod_1.z.string(), zod_1.z.string()).optional(),
140
+ senderAvatarPath: zod_1.z.string().nullable().optional(),
141
+ /** Present only on list reads that walked the thread. */
142
+ threadCount: zod_1.z.number().int().optional(),
143
+ threadParticipants: zod_1.z.array(zod_1.z.string()).optional(),
144
+ });
145
+ // ─── Mailboxes and labels ───────────────────────────────────────────
146
+ exports.emailMailboxSchema = zod_1.z.object({
147
+ _id: zod_1.z.string(),
148
+ id: zod_1.z.string(),
149
+ userId: zod_1.z.string(),
150
+ name: zod_1.z.string(),
151
+ path: zod_1.z.string(),
152
+ specialUse: zod_1.z.string().nullable(),
153
+ retentionDays: zod_1.z.number().int().nullable(),
154
+ totalMessages: zod_1.z.number().int(),
155
+ unseenMessages: zod_1.z.number().int(),
156
+ size: zod_1.z.number(),
157
+ createdAt: isoInstant,
158
+ updatedAt: isoInstant,
159
+ });
160
+ /** A label the user made. */
161
+ exports.emailUserLabelSchema = zod_1.z.object({
162
+ _id: zod_1.z.string(),
163
+ id: zod_1.z.string(),
164
+ userId: zod_1.z.string(),
165
+ name: zod_1.z.string(),
166
+ color: zod_1.z.string(),
167
+ order: zod_1.z.number().int(),
168
+ system: zod_1.z.literal(false),
169
+ createdAt: isoInstant,
170
+ updatedAt: isoInstant,
171
+ });
172
+ /** One of the product's built-in labels; `_id` is `system:<name>`. */
173
+ exports.emailSystemLabelSchema = zod_1.z.object({
174
+ _id: zod_1.z.string(),
175
+ name: zod_1.z.string(),
176
+ color: zod_1.z.string(),
177
+ order: zod_1.z.number().int(),
178
+ system: zod_1.z.literal(true),
179
+ });
180
+ exports.emailLabelSchema = zod_1.z.discriminatedUnion('system', [exports.emailUserLabelSchema, exports.emailSystemLabelSchema]);
181
+ // ─── Rules, bundles, contacts, outbox ───────────────────────────────
182
+ exports.emailFilterConditionSchema = zod_1.z.object({
183
+ field: zod_1.z.enum(exports.EMAIL_FILTER_CONDITION_FIELDS),
184
+ operator: zod_1.z.enum(exports.EMAIL_FILTER_CONDITION_OPERATORS),
185
+ value: zod_1.z.string(),
186
+ });
187
+ /** `value` is absent for the actions that take none. */
188
+ exports.emailFilterActionSchema = zod_1.z.object({
189
+ type: zod_1.z.enum(exports.EMAIL_FILTER_ACTION_TYPES),
190
+ value: zod_1.z.string().optional(),
191
+ });
192
+ exports.emailFilterSchema = zod_1.z.object({
193
+ _id: zod_1.z.string(),
194
+ id: zod_1.z.string(),
195
+ userId: zod_1.z.string(),
196
+ name: zod_1.z.string(),
197
+ enabled: zod_1.z.boolean(),
198
+ matchAll: zod_1.z.boolean(),
199
+ order: zod_1.z.number().int(),
200
+ conditions: zod_1.z.array(exports.emailFilterConditionSchema),
201
+ actions: zod_1.z.array(exports.emailFilterActionSchema),
202
+ createdAt: isoInstant,
203
+ updatedAt: isoInstant,
204
+ });
205
+ exports.emailBundleSchema = zod_1.z.object({
206
+ _id: zod_1.z.string(),
207
+ id: zod_1.z.string(),
208
+ userId: zod_1.z.string(),
209
+ name: zod_1.z.string(),
210
+ icon: zod_1.z.string(),
211
+ color: zod_1.z.string(),
212
+ matchLabels: zod_1.z.array(zod_1.z.string()),
213
+ enabled: zod_1.z.boolean(),
214
+ collapsed: zod_1.z.boolean(),
215
+ order: zod_1.z.number().int(),
216
+ createdAt: isoInstant,
217
+ updatedAt: isoInstant,
218
+ });
219
+ /** `GET /email/messages?bundled=true` — the inbox split into primary and bundles. */
220
+ exports.emailBundledInboxSchema = zod_1.z.object({
221
+ primary: zod_1.z.array(exports.emailMessageSchema),
222
+ bundles: zod_1.z.array(zod_1.z.object({
223
+ bundle: exports.emailBundleSchema,
224
+ messages: zod_1.z.array(exports.emailMessageSchema),
225
+ unreadCount: zod_1.z.number().int(),
226
+ })),
227
+ total: zod_1.z.number().int(),
228
+ });
229
+ /** An address-book entry. `company` and `notes` are `null` when unset. */
230
+ exports.emailContactSchema = zod_1.z.object({
231
+ _id: zod_1.z.string(),
232
+ id: zod_1.z.string(),
233
+ userId: zod_1.z.string(),
234
+ name: zod_1.z.string(),
235
+ email: zod_1.z.string(),
236
+ company: zod_1.z.string().nullable(),
237
+ notes: zod_1.z.string().nullable(),
238
+ starred: zod_1.z.boolean(),
239
+ autoCollected: zod_1.z.boolean(),
240
+ lastContactedAt: isoInstant.nullable(),
241
+ createdAt: isoInstant,
242
+ updatedAt: isoInstant,
243
+ });
244
+ /**
245
+ * One durable outbound delivery. `terminal` means no further attempt will
246
+ * happen on its own — present that differently from "still trying".
247
+ */
248
+ exports.emailOutboxSchema = zod_1.z.object({
249
+ id: zod_1.z.string(),
250
+ messageId: zod_1.z.string(),
251
+ status: zod_1.z.enum(exports.EMAIL_OUTBOX_STATUSES),
252
+ attempts: zod_1.z.number().int(),
253
+ maxAttempts: zod_1.z.number().int(),
254
+ terminal: zod_1.z.boolean(),
255
+ nextAttemptAt: isoInstant,
256
+ lastError: zod_1.z.string().nullable(),
257
+ sentAt: isoInstant.nullable(),
258
+ createdAt: isoInstant,
259
+ updatedAt: isoInstant,
260
+ });
261
+ // ─── Replies ────────────────────────────────────────────────────────
262
+ /**
263
+ * One RFC 5322 `msg-id`: `<left@right>`, no whitespace, no nested brackets.
264
+ *
265
+ * `In-Reply-To` and `References` carry these and nothing else. A reply that
266
+ * names a database row id instead (`01a0…` or `<01a0…>`) breaks threading for
267
+ * every recipient — it happened, which is why it is refused at the edge.
268
+ */
269
+ exports.RFC_MESSAGE_ID_PATTERN = /^<[^<>\s]+@[^<>\s]+>$/;
270
+ exports.rfcMessageIdSchema = zod_1.z
271
+ .string()
272
+ .trim()
273
+ .regex(exports.RFC_MESSAGE_ID_PATTERN, 'Must be an RFC 5322 Message-ID such as <id@host>');
package/dist/cjs/index.js CHANGED
@@ -702,3 +702,4 @@ __exportStar(require("./externalIdentity"), exports);
702
702
  __exportStar(require("./linkedAccounts"), exports);
703
703
  __exportStar(require("./federationInstanceFetch"), exports);
704
704
  __exportStar(require("./notifications"), exports);
705
+ __exportStar(require("./email/messages"), exports);