@fungi.computer/carrier 0.0.0-stage → 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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +468 -2
  3. package/dist/cloudflare.d.ts +32 -0
  4. package/dist/cloudflare.js +80 -0
  5. package/dist/email.d.ts +2 -0
  6. package/dist/email.js +2 -0
  7. package/dist/index.d.ts +21 -0
  8. package/dist/index.js +28 -0
  9. package/dist/inspection.d.ts +108 -0
  10. package/dist/inspection.js +188 -0
  11. package/dist/internal/cloudflare-send.d.ts +9 -0
  12. package/dist/internal/cloudflare-send.js +79 -0
  13. package/dist/internal/contracts.d.ts +94 -0
  14. package/dist/internal/contracts.js +7 -0
  15. package/dist/internal/owner.d.ts +24 -0
  16. package/dist/internal/owner.js +419 -0
  17. package/dist/internal/policy.d.ts +25 -0
  18. package/dist/internal/policy.js +18 -0
  19. package/dist/internal/post-contract.d.ts +27 -0
  20. package/dist/internal/post-contract.js +22 -0
  21. package/dist/internal/post-execution.d.ts +45 -0
  22. package/dist/internal/post-execution.js +73 -0
  23. package/dist/internal/post-sql.d.ts +51 -0
  24. package/dist/internal/post-sql.js +259 -0
  25. package/dist/internal/receipt-schema.d.ts +3 -0
  26. package/dist/internal/receipt-schema.js +12 -0
  27. package/dist/post-d1.d.ts +36 -0
  28. package/dist/post-d1.js +55 -0
  29. package/dist/post-outbox.d.ts +52 -0
  30. package/dist/post-outbox.js +152 -0
  31. package/dist/post-postgres.d.ts +25 -0
  32. package/dist/post-postgres.js +79 -0
  33. package/dist/post.d.ts +189 -0
  34. package/dist/post.js +437 -0
  35. package/dist/testing.d.ts +16 -0
  36. package/dist/testing.js +20 -0
  37. package/docs/api/README.md +20 -0
  38. package/docs/api/cloudflare.md +100 -0
  39. package/docs/api/email.md +87 -0
  40. package/docs/api/index.md +343 -0
  41. package/docs/api/inspection.md +281 -0
  42. package/docs/api/post-d1.md +184 -0
  43. package/docs/api/post-postgres.md +108 -0
  44. package/docs/api/post.md +599 -0
  45. package/docs/api/testing.md +46 -0
  46. package/package.json +95 -3
@@ -0,0 +1,343 @@
1
+ <!-- Generated by Botanical API docs (TypeDoc + typedoc-plugin-markdown). Do not edit. -->
2
+
3
+ [**@fungi.computer/carrier**](README.md)
4
+
5
+ ---
6
+
7
+ [@fungi.computer/carrier](README.md) / index
8
+
9
+ # index
10
+
11
+ ## Classes
12
+
13
+ ### CarrierError
14
+
15
+ Stable failures omit email bodies, tokens and raw provider errors.
16
+
17
+ #### Extends
18
+
19
+ - `YieldableError`\<`this`\> & `object` & `Readonly`\<\{ `code`: `"conflict"` \|
20
+ `"invalid_input"` \| `"unavailable"`; `message`: `string`; \}\>
21
+
22
+ #### Constructors
23
+
24
+ ##### Constructor
25
+
26
+ ```ts
27
+ new CarrierError(code): CarrierError;
28
+ ```
29
+
30
+ ###### Parameters
31
+
32
+ | Parameter | Type |
33
+ | --------- | ---------------------------------------------------- |
34
+ | `code` | `"conflict"` \| `"invalid_input"` \| `"unavailable"` |
35
+
36
+ ###### Returns
37
+
38
+ [`CarrierError`](#carriererror)
39
+
40
+ ###### Overrides
41
+
42
+ ```ts
43
+ Data.TaggedError("CarrierError")<{
44
+ readonly code: "invalid_input" | "conflict" | "unavailable";
45
+ readonly message: string;
46
+ }>.constructor
47
+ ```
48
+
49
+ #### Properties
50
+
51
+ ##### code
52
+
53
+ ```ts
54
+ readonly code: "conflict" | "invalid_input" | "unavailable";
55
+ ```
56
+
57
+ ###### Inherited from
58
+
59
+ ```ts
60
+ Data.TaggedError("CarrierError").code;
61
+ ```
62
+
63
+ ##### message
64
+
65
+ ```ts
66
+ message: string;
67
+ ```
68
+
69
+ ###### Inherited from
70
+
71
+ ```ts
72
+ Data.TaggedError("CarrierError").message;
73
+ ```
74
+
75
+ ## Type Aliases
76
+
77
+ ### Carrier
78
+
79
+ ```ts
80
+ type Carrier = Readonly<{
81
+ send: (submission) => Promise<Receipt>;
82
+ get: (id) => Promise<Receipt | undefined>;
83
+ tick: () => Promise<void>;
84
+ hasPendingWork: () => Promise<boolean>;
85
+ }>;
86
+ ```
87
+
88
+ Durable email submission and its host-driven recovery door.
89
+
90
+ ---
91
+
92
+ ### Address
93
+
94
+ ```ts
95
+ type Address =
96
+ | string
97
+ | Readonly<{
98
+ email: string;
99
+ name: string;
100
+ }>;
101
+ ```
102
+
103
+ One recipient address; display names remain part of the requested envelope.
104
+
105
+ ---
106
+
107
+ ### Email
108
+
109
+ ```ts
110
+ type Email = Readonly<{
111
+ from: Address;
112
+ to?: readonly Address[];
113
+ cc?: readonly Address[];
114
+ bcc?: readonly Address[];
115
+ subject: string;
116
+ html?: string;
117
+ text?: string;
118
+ headers?: Readonly<Record<string, string>>;
119
+ }>;
120
+ ```
121
+
122
+ Common content and envelope for one Email.
123
+
124
+ ---
125
+
126
+ ### Submission
127
+
128
+ ```ts
129
+ type Submission = Email &
130
+ Readonly<{
131
+ id: string;
132
+ sendBefore?: number;
133
+ }>;
134
+ ```
135
+
136
+ Source identity and expiry belong to the notification's producer.
137
+
138
+ ---
139
+
140
+ ### Message
141
+
142
+ ```ts
143
+ type Message = Readonly<{
144
+ id: string;
145
+ recipient: string;
146
+ roles: readonly ("to" | "cc" | "bcc")[];
147
+ status: "unconfirmed";
148
+ }>;
149
+ ```
150
+
151
+ A recipient Message is distinct from its Email and provider request.
152
+
153
+ ---
154
+
155
+ ### ProviderRejection
156
+
157
+ ```ts
158
+ type ProviderRejection = Readonly<{
159
+ code: string;
160
+ class: "permanent" | "temporary";
161
+ }>;
162
+ ```
163
+
164
+ A provider's definite refusal of a whole request: retryable or final.
165
+
166
+ ---
167
+
168
+ ### Receipt
169
+
170
+ ```ts
171
+ type Receipt = Readonly<{
172
+ id: string;
173
+ status: Schema.Schema.Type<typeof CarrierStatusSchema>;
174
+ providerMessageId?: string;
175
+ providerRejection?: ProviderRejection;
176
+ messages: readonly Message[];
177
+ }>;
178
+ ```
179
+
180
+ Durable submission and aggregate dispatch evidence, without credential content.
181
+ `refused` means Carrier's per-recipient cap declined the submission before any
182
+ provider attempt; `rejected` means the provider definitely refused it (a
183
+ permanent rejection, or temporary rejections until retries ran out).
184
+ `providerRejection` is the last provider refusal, also while a temporary
185
+ rejection waits `queued` for its retry.
186
+
187
+ ---
188
+
189
+ ### SendEvidence
190
+
191
+ ```ts
192
+ type SendEvidence =
193
+ | Readonly<{
194
+ kind: "provider_accepted";
195
+ providerMessageId: string;
196
+ }>
197
+ | (Readonly<{
198
+ kind: "provider_rejected";
199
+ }> &
200
+ ProviderRejection)
201
+ | Readonly<{
202
+ kind: "outcome_unknown";
203
+ cause?: string;
204
+ }>;
205
+ ```
206
+
207
+ One transport invocation reports only the evidence it obtained. Report
208
+ `provider_rejected` only for a documented refusal of the whole request before
209
+ acceptance; `cause` is an optional sanitized provider code for logs.
210
+
211
+ ---
212
+
213
+ ### Host
214
+
215
+ ```ts
216
+ type Host = Readonly<{
217
+ commit: <A>(mutation) => Promise<A>;
218
+ requestWake: () => Promise<void>;
219
+ }>;
220
+ ```
221
+
222
+ Mechanical host capabilities; commit must durably arm a wake with its mutation.
223
+
224
+ ---
225
+
226
+ ### Options
227
+
228
+ ```ts
229
+ type Options = Readonly<{
230
+ storage: WatchdogSqliteOwner;
231
+ host: Host;
232
+ now: () => number;
233
+ canSend?: (emailId) => Promise<boolean>;
234
+ transport: (email) => Promise<SendEvidence>;
235
+ }>;
236
+ ```
237
+
238
+ Configure a durable owner; business validity remains with the source.
239
+
240
+ ## Variables
241
+
242
+ ### CarrierStatusSchema
243
+
244
+ ```ts
245
+ const CarrierStatusSchema: Literals<
246
+ readonly [
247
+ "queued",
248
+ "dispatching",
249
+ "provider_accepted",
250
+ "outcome_unknown",
251
+ "rejected",
252
+ "refused",
253
+ "expired",
254
+ "superseded",
255
+ ]
256
+ >;
257
+ ```
258
+
259
+ Carrier's aggregate dispatch states at both storage and read boundaries.
260
+
261
+ ## Functions
262
+
263
+ ### createCarrier()
264
+
265
+ ```ts
266
+ function createCarrier(options): Promise<Readonly<{
267
+ send: (submission) => Promise<Readonly<{
268
+ id: string;
269
+ status: | "expired"
270
+ | "rejected"
271
+ | "queued"
272
+ | "outcome_unknown"
273
+ | "dispatching"
274
+ | "provider_accepted"
275
+ | "refused"
276
+ | "superseded";
277
+ providerMessageId?: string;
278
+ providerRejection?: Readonly<{
279
+ code: string;
280
+ class: ... | ...;
281
+ }>;
282
+ messages: readonly Readonly<{
283
+ id: string;
284
+ recipient: string;
285
+ roles: readonly ...[];
286
+ status: "unconfirmed";
287
+ }>[];
288
+ }>>;
289
+ get: (id) => Promise<
290
+ | Readonly<{
291
+ id: string;
292
+ status: | "expired"
293
+ | "rejected"
294
+ | "queued"
295
+ | "outcome_unknown"
296
+ | "dispatching"
297
+ | "provider_accepted"
298
+ | "refused"
299
+ | "superseded";
300
+ providerMessageId?: string;
301
+ providerRejection?: Readonly<{
302
+ code: ...;
303
+ class: ...;
304
+ }>;
305
+ messages: readonly Readonly<{
306
+ id: ...;
307
+ recipient: ...;
308
+ roles: ...;
309
+ status: ...;
310
+ }>[];
311
+ }>
312
+ | undefined>;
313
+ tick: () => Promise<void>;
314
+ hasPendingWork: () => Promise<boolean>;
315
+ }>>;
316
+ ```
317
+
318
+ Construct exactly one owner per store/runtime lifetime. A replacement may be
319
+ opened only after its predecessor has stopped; simultaneous owners of the same
320
+ store are unsupported. Native adapters supply their runtime ownership.
321
+
322
+ #### Parameters
323
+
324
+ | Parameter | Type |
325
+ | --------- | --------------------- |
326
+ | `options` | [`Options`](#options) |
327
+
328
+ #### Returns
329
+
330
+ `Promise`\<`Readonly`\<\{ `send`: (`submission`) => `Promise`\<`Readonly`\<\{
331
+ `id`: `string`; `status`: \| `"expired"` \| `"rejected"` \| `"queued"` \|
332
+ `"outcome_unknown"` \| `"dispatching"` \| `"provider_accepted"` \| `"refused"`
333
+ \| `"superseded"`; `providerMessageId?`: `string`; `providerRejection?`:
334
+ `Readonly`\<\{ `code`: `string`; `class`: ... \| ...; \}\>; `messages`: readonly
335
+ `Readonly`\<\{ `id`: `string`; `recipient`: `string`; `roles`: readonly ...[];
336
+ `status`: `"unconfirmed"`; \}\>[]; \}\>\>; `get`: (`id`) => `Promise`\< \|
337
+ `Readonly`\<\{ `id`: `string`; `status`: \| `"expired"` \| `"rejected"` \|
338
+ `"queued"` \| `"outcome_unknown"` \| `"dispatching"` \| `"provider_accepted"` \|
339
+ `"refused"` \| `"superseded"`; `providerMessageId?`: `string`;
340
+ `providerRejection?`: `Readonly`\<\{ `code`: ...; `class`: ...; \}\>;
341
+ `messages`: readonly `Readonly`\<\{ `id`: ...; `recipient`: ...; `roles`: ...;
342
+ `status`: ...; \}\>[]; \}\> \| `undefined`\>; `tick`: () => `Promise`\<`void`\>;
343
+ `hasPendingWork`: () => `Promise`\<`boolean`\>; \}\>\>
@@ -0,0 +1,281 @@
1
+ <!-- Generated by Botanical API docs (TypeDoc + typedoc-plugin-markdown). Do not edit. -->
2
+
3
+ [**@fungi.computer/carrier**](README.md)
4
+
5
+ ---
6
+
7
+ [@fungi.computer/carrier](README.md) / inspection
8
+
9
+ # inspection
10
+
11
+ ## Classes
12
+
13
+ ### DeliveryInspectionError
14
+
15
+ Stable inspection errors never include stored cargo or raw SQL failures.
16
+
17
+ #### Extends
18
+
19
+ - `Error`
20
+
21
+ #### Constructors
22
+
23
+ ##### Constructor
24
+
25
+ ```ts
26
+ new DeliveryInspectionError(code): DeliveryInspectionError;
27
+ ```
28
+
29
+ ###### Parameters
30
+
31
+ | Parameter | Type |
32
+ | --------- | ------------------------------------ |
33
+ | `code` | `"unavailable"` \| `"invalid_query"` |
34
+
35
+ ###### Returns
36
+
37
+ [`DeliveryInspectionError`](#deliveryinspectionerror)
38
+
39
+ ###### Overrides
40
+
41
+ ```ts
42
+ Error.constructor;
43
+ ```
44
+
45
+ #### Properties
46
+
47
+ ##### code
48
+
49
+ ```ts
50
+ readonly code: "unavailable" | "invalid_query";
51
+ ```
52
+
53
+ ## Type Aliases
54
+
55
+ ### DeliveryInspectionPageQuery
56
+
57
+ ```ts
58
+ type DeliveryInspectionPageQuery = Schema.Codec.Encoded<
59
+ typeof DeliveryInspectionPageQuerySchema
60
+ >;
61
+ ```
62
+
63
+ An email receipt page uses the same producer-key pagination.
64
+
65
+ ---
66
+
67
+ ### DeliveryInspectionQuery
68
+
69
+ ```ts
70
+ type DeliveryInspectionQuery = Schema.Codec.Encoded<
71
+ typeof DeliveryInspectionQuerySchema
72
+ >;
73
+ ```
74
+
75
+ Input to one producer-local inspection. Cursors are producer keys, without
76
+ storage prefixes.
77
+
78
+ ---
79
+
80
+ ### PostReceiptSummary
81
+
82
+ ```ts
83
+ type PostReceiptSummary = Schema.Schema.Type<typeof PostReceiptSummarySchema>;
84
+ ```
85
+
86
+ Metadata for one retained Post row.
87
+
88
+ ---
89
+
90
+ ### PostInspectionPage
91
+
92
+ ```ts
93
+ type PostInspectionPage = Schema.Schema.Type<typeof PostInspectionPageSchema>;
94
+ ```
95
+
96
+ One read of producer-local Post dispatch evidence.
97
+
98
+ ---
99
+
100
+ ### CarrierInspectionPage
101
+
102
+ ```ts
103
+ type CarrierInspectionPage = Schema.Schema.Type<
104
+ typeof CarrierInspectionPageSchema
105
+ >;
106
+ ```
107
+
108
+ One read of aggregate email dispatch evidence.
109
+
110
+ ## Variables
111
+
112
+ ### DeliveryInspectionPageQuerySchema
113
+
114
+ ```ts
115
+ const DeliveryInspectionPageQuerySchema: Struct<{
116
+ limit: withDecodingDefaultType<Int, never>;
117
+ after: optional<String>;
118
+ }>;
119
+ ```
120
+
121
+ One bounded scan; an empty filtered page can still have a continuation.
122
+
123
+ ---
124
+
125
+ ### DeliveryInspectionQuerySchema
126
+
127
+ ```ts
128
+ const DeliveryInspectionQuerySchema: Struct<{
129
+ limit: withDecodingDefaultType<Int, never>;
130
+ after: optional<String>;
131
+ view: Literals<readonly ["receipts", "deadLetters"]>;
132
+ }>;
133
+ ```
134
+
135
+ Post can filter dead letters while scanning at most one bounded page.
136
+
137
+ ---
138
+
139
+ ### PostReceiptSummarySchema
140
+
141
+ ```ts
142
+ const PostReceiptSummarySchema: Union<
143
+ readonly [
144
+ Struct<{
145
+ key: String;
146
+ state: Literal<"delivered">;
147
+ }>,
148
+ Struct<{
149
+ key: String;
150
+ state: Literal<"pending">;
151
+ kind: String;
152
+ attempts: Int;
153
+ nextAttemptAt: Number;
154
+ }>,
155
+ Struct<{
156
+ key: String;
157
+ state: Literal<"dead">;
158
+ kind: String;
159
+ attempts: Int;
160
+ reason: Literals<readonly ["refused", "exhausted"]>;
161
+ }>,
162
+ ]
163
+ >;
164
+ ```
165
+
166
+ Dispatch evidence deliberately omits the address and event.
167
+
168
+ ---
169
+
170
+ ### PostInspectionPageSchema
171
+
172
+ ```ts
173
+ const PostInspectionPageSchema: Union<readonly [Struct<{
174
+ status: Literal<"uninitialized">;
175
+ }>, Struct<{
176
+ status: Literal<"available">;
177
+ records: $Array<Union<readonly [Struct<{
178
+ key: String;
179
+ state: Literal<"delivered">;
180
+ }>, Struct<{
181
+ key: String;
182
+ state: Literal<"pending">;
183
+ kind: String;
184
+ attempts: Int;
185
+ nextAttemptAt: Number;
186
+ }>, Struct<{
187
+ key: String;
188
+ state: Literal<"dead">;
189
+ kind: String;
190
+ attempts: Int;
191
+ reason: Literals<readonly [..., ...]>;
192
+ }>]>>;
193
+ nextCursor: NullOr<String>;
194
+ }>]>;
195
+ ```
196
+
197
+ Bounded evidence with an explicit distinction between empty and uninitialized
198
+ storage.
199
+
200
+ ---
201
+
202
+ ### CarrierReceiptSummarySchema
203
+
204
+ ```ts
205
+ const CarrierReceiptSummarySchema: Struct<{
206
+ id: String;
207
+ status: Literals<
208
+ readonly [
209
+ "queued",
210
+ "dispatching",
211
+ "provider_accepted",
212
+ "outcome_unknown",
213
+ "rejected",
214
+ "refused",
215
+ "expired",
216
+ "superseded",
217
+ ]
218
+ >;
219
+ attempts: Int;
220
+ retryAt: NullOr<Number>;
221
+ providerMessageId: optional<NonEmptyString>;
222
+ providerRejection: optional<
223
+ Struct<{
224
+ code: NonEmptyString;
225
+ class: Literals<readonly ["permanent", "temporary"]>;
226
+ }>
227
+ >;
228
+ }>;
229
+ ```
230
+
231
+ Aggregate email evidence without bodies, headers or recipient addresses.
232
+
233
+ ---
234
+
235
+ ### CarrierInspectionPageSchema
236
+
237
+ ```ts
238
+ const CarrierInspectionPageSchema: Union<readonly [Struct<{
239
+ status: Literal<"uninitialized">;
240
+ }>, Struct<{
241
+ status: Literal<"available">;
242
+ records: $Array<Struct<{
243
+ id: String;
244
+ status: Literals<readonly ["queued", "dispatching", "provider_accepted", "outcome_unknown", "rejected", "refused", "expired", "superseded"]>;
245
+ attempts: Int;
246
+ retryAt: NullOr<Number>;
247
+ providerMessageId: optional<NonEmptyString>;
248
+ providerRejection: optional<Struct<{
249
+ code: NonEmptyString;
250
+ class: Literals<...>;
251
+ }>>;
252
+ }>>;
253
+ nextCursor: NullOr<String>;
254
+ }>]>;
255
+ ```
256
+
257
+ Carrier's bounded metadata page; unavailable storage is never reported as empty.
258
+
259
+ ## Functions
260
+
261
+ ### createDeliveryInspection()
262
+
263
+ ```ts
264
+ function createDeliveryInspection(sql): object;
265
+ ```
266
+
267
+ Read existing storage without opening a sender, initializing tables, repairing
268
+ jobs or arming a wake. The trusted host must admit an operator before calling.
269
+
270
+ #### Parameters
271
+
272
+ | Parameter | Type |
273
+ | --------- | ----------------------------------------------------------------------------------- |
274
+ | `sql` | `Readonly`\<\{ `exec`: (`statement`, ...`bindings`) => `WatchdogSqliteCursor`; \}\> |
275
+
276
+ #### Returns
277
+
278
+ | Name | Type | Description |
279
+ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
280
+ | `email()` | (`input`) => \| \{ `status`: `"uninitialized"`; \} \| \{ `status`: `"available"`; `records`: readonly `object`[]; `nextCursor`: `string` \| `null`; \} | Read aggregate dispatch evidence without email cargo or recipient addresses. |
281
+ | `post()` | (`input`) => \| \{ `status`: `"uninitialized"`; \} \| \{ `status`: `"available"`; `records`: readonly ( \| \{ `key`: `string`; `state`: `"delivered"`; \} \| \{ `key`: `string`; `state`: `"pending"`; `kind`: `string`; `attempts`: `number`; `nextAttemptAt`: `number`; \} \| \{ `key`: `string`; `state`: `"dead"`; `kind`: `string`; `attempts`: `number`; `reason`: `"refused"` \| `"exhausted"`; \})[]; `nextCursor`: `string` \| `null`; \} | Read Post metadata; filtered pages retain their continuation through other states. |