@fungi.computer/carrier 0.2.0 → 0.2.2
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 +48 -73
- package/dist/inspection.js +12 -5
- package/dist/internal/owner.js +3 -1
- package/dist/internal/post-contract.d.ts +3 -3
- package/dist/internal/post-contract.js +1 -1
- package/dist/internal/post-policy.d.ts +5 -0
- package/dist/internal/post-policy.js +4 -0
- package/dist/internal/post-sql.d.ts +5 -10
- package/dist/internal/post-sql.js +65 -96
- package/dist/post-d1.d.ts +1 -1
- package/dist/post-d1.js +1 -7
- package/dist/post-postgres.js +0 -17
- package/dist/post.d.ts +7 -19
- package/dist/post.js +78 -209
- package/docs/api/README.md +0 -1
- package/docs/api/inspection.md +1 -1
- package/docs/api/post-d1.md +6 -15
- package/docs/api/post-postgres.md +3 -3
- package/docs/api/post.md +15 -132
- package/package.json +3 -7
- package/dist/post-outbox.d.ts +0 -52
- package/dist/post-outbox.js +0 -152
- package/dist/testing.d.ts +0 -16
- package/dist/testing.js +0 -20
- package/docs/api/testing.md +0 -46
package/docs/api/post.md
CHANGED
|
@@ -12,12 +12,13 @@
|
|
|
12
12
|
|
|
13
13
|
### PostError
|
|
14
14
|
|
|
15
|
-
Post rejected an unknown route, conflicting key,
|
|
15
|
+
Post rejected an unknown route, conflicting key, unsupported policy or store.
|
|
16
16
|
|
|
17
17
|
#### Extends
|
|
18
18
|
|
|
19
19
|
- `YieldableError`\<`this`\> & `object` & `Readonly`\<\{ `message`: `string`;
|
|
20
|
-
`reason?`: `"relational-deadline-unsupported"`;
|
|
20
|
+
`reason?`: `"relational-deadline-unsupported"` \| `"pre-scheduler-store"`;
|
|
21
|
+
\}\>
|
|
21
22
|
|
|
22
23
|
#### Constructors
|
|
23
24
|
|
|
@@ -29,10 +30,10 @@ new PostError(message, reason?): PostError;
|
|
|
29
30
|
|
|
30
31
|
###### Parameters
|
|
31
32
|
|
|
32
|
-
| Parameter | Type
|
|
33
|
-
| --------- |
|
|
34
|
-
| `message` | `string`
|
|
35
|
-
| `reason?` | `"relational-deadline-unsupported"` |
|
|
33
|
+
| Parameter | Type |
|
|
34
|
+
| --------- | -------------------------------------------------------------- |
|
|
35
|
+
| `message` | `string` |
|
|
36
|
+
| `reason?` | `"relational-deadline-unsupported"` \| `"pre-scheduler-store"` |
|
|
36
37
|
|
|
37
38
|
###### Returns
|
|
38
39
|
|
|
@@ -43,7 +44,7 @@ new PostError(message, reason?): PostError;
|
|
|
43
44
|
```ts
|
|
44
45
|
Data.TaggedError("PostError")<{
|
|
45
46
|
readonly message: string;
|
|
46
|
-
readonly reason?: "relational-deadline-unsupported";
|
|
47
|
+
readonly reason?: "relational-deadline-unsupported" | "pre-scheduler-store";
|
|
47
48
|
}>.constructor
|
|
48
49
|
```
|
|
49
50
|
|
|
@@ -52,7 +53,7 @@ Data.TaggedError("PostError")<{
|
|
|
52
53
|
##### reason?
|
|
53
54
|
|
|
54
55
|
```ts
|
|
55
|
-
readonly optional reason?: "relational-deadline-unsupported";
|
|
56
|
+
readonly optional reason?: "relational-deadline-unsupported" | "pre-scheduler-store";
|
|
56
57
|
```
|
|
57
58
|
|
|
58
59
|
###### Inherited from
|
|
@@ -96,58 +97,6 @@ Receiver's idempotent admission decision.
|
|
|
96
97
|
|
|
97
98
|
---
|
|
98
99
|
|
|
99
|
-
### PostOutboxStore
|
|
100
|
-
|
|
101
|
-
```ts
|
|
102
|
-
type PostOutboxStore<E> = Readonly<{
|
|
103
|
-
put: (key, value) => Effect.Effect<void, E>;
|
|
104
|
-
read: (key) => Effect.Effect<string | undefined, E>;
|
|
105
|
-
list: (
|
|
106
|
-
limit,
|
|
107
|
-
afterKey?,
|
|
108
|
-
) => Effect.Effect<
|
|
109
|
-
readonly Readonly<{
|
|
110
|
-
key: string;
|
|
111
|
-
value: string;
|
|
112
|
-
}>[],
|
|
113
|
-
E
|
|
114
|
-
>;
|
|
115
|
-
delete: (key) => Effect.Effect<void, E>;
|
|
116
|
-
}>;
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
Durable, string-backed rows. A delivered receipt must outlive source replay.
|
|
120
|
-
|
|
121
|
-
#### Type Parameters
|
|
122
|
-
|
|
123
|
-
| Type Parameter |
|
|
124
|
-
| -------------- |
|
|
125
|
-
| `E` |
|
|
126
|
-
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
### PostOutbox
|
|
130
|
-
|
|
131
|
-
```ts
|
|
132
|
-
type PostOutbox<M, E> = Readonly<{
|
|
133
|
-
enqueue: (message, at?) => Effect.Effect<number | undefined, E>;
|
|
134
|
-
drain: () => Effect.Effect<void, E>;
|
|
135
|
-
nextWakeAt: () => Effect.Effect<number | undefined, E>;
|
|
136
|
-
deadLetters: (limit, afterKey?) => Effect.Effect<M[], E>;
|
|
137
|
-
}>;
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
Bounded generic delivery owned by Post.
|
|
141
|
-
|
|
142
|
-
#### Type Parameters
|
|
143
|
-
|
|
144
|
-
| Type Parameter |
|
|
145
|
-
| -------------- |
|
|
146
|
-
| `M` |
|
|
147
|
-
| `E` |
|
|
148
|
-
|
|
149
|
-
---
|
|
150
|
-
|
|
151
100
|
### PostEnvelope
|
|
152
101
|
|
|
153
102
|
```ts
|
|
@@ -243,12 +192,8 @@ Runtime operations that preserve the route's schema evidence.
|
|
|
243
192
|
```ts
|
|
244
193
|
type PostRuntime<R> = Readonly<{
|
|
245
194
|
send: <K>(input) => void;
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
kind: "completed";
|
|
249
|
-
}>
|
|
250
|
-
>;
|
|
251
|
-
enqueueDue: (limit?, afterKey?) => Promise<string | undefined>;
|
|
195
|
+
retryPolicy: (job) => Promise<WatchdogRetryPolicy>;
|
|
196
|
+
execute: (job, attempt, signal?) => Promise<WatchdogExecutionDecision>;
|
|
252
197
|
releaseNow: (limit?, afterKey?) => Promise<string | undefined>;
|
|
253
198
|
deadLetters: (
|
|
254
199
|
limit?,
|
|
@@ -273,7 +218,6 @@ type PostRuntime<R> = Readonly<{
|
|
|
273
218
|
| undefined;
|
|
274
219
|
retry: (key) => Promise<void>;
|
|
275
220
|
read: (key) => PostRow | undefined;
|
|
276
|
-
nextWake: () => number | null;
|
|
277
221
|
}>;
|
|
278
222
|
```
|
|
279
223
|
|
|
@@ -338,10 +282,8 @@ const PostRowSchema: fromJsonString<
|
|
|
338
282
|
address: Unknown;
|
|
339
283
|
event: Unknown;
|
|
340
284
|
}>;
|
|
341
|
-
attempts: Int;
|
|
342
|
-
nextAttemptAt: Number;
|
|
343
285
|
deadlineAt: optional<Number>;
|
|
344
|
-
jobId:
|
|
286
|
+
jobId: String;
|
|
345
287
|
delivery: optional<
|
|
346
288
|
Struct<{
|
|
347
289
|
key: String;
|
|
@@ -350,12 +292,10 @@ const PostRowSchema: fromJsonString<
|
|
|
350
292
|
event: Unknown;
|
|
351
293
|
}>
|
|
352
294
|
>;
|
|
353
|
-
nextKey: optional<String>;
|
|
354
295
|
}>,
|
|
355
296
|
Struct<{
|
|
356
297
|
state: Literal<"delivered">;
|
|
357
298
|
receipt: optional<Codec<Json, Json, never, never>>;
|
|
358
|
-
nextKey: optional<String>;
|
|
359
299
|
}>,
|
|
360
300
|
Struct<{
|
|
361
301
|
state: Literal<"dead">;
|
|
@@ -377,7 +317,6 @@ const PostRowSchema: fromJsonString<
|
|
|
377
317
|
event: Unknown;
|
|
378
318
|
}>
|
|
379
319
|
>;
|
|
380
|
-
nextKey: optional<String>;
|
|
381
320
|
}>,
|
|
382
321
|
]
|
|
383
322
|
>
|
|
@@ -388,62 +327,6 @@ Persisted Post row parser.
|
|
|
388
327
|
|
|
389
328
|
## Functions
|
|
390
329
|
|
|
391
|
-
### makePostOutbox()
|
|
392
|
-
|
|
393
|
-
```ts
|
|
394
|
-
function makePostOutbox<M, E>(options): PostOutbox<M, E>;
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
The same bounded delivery and receipt machinery serves Post and BirdDog.
|
|
398
|
-
|
|
399
|
-
#### Type Parameters
|
|
400
|
-
|
|
401
|
-
| Type Parameter |
|
|
402
|
-
| -------------- |
|
|
403
|
-
| `M` |
|
|
404
|
-
| `E` |
|
|
405
|
-
|
|
406
|
-
#### Parameters
|
|
407
|
-
|
|
408
|
-
| Parameter | Type |
|
|
409
|
-
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
410
|
-
| `options` | `Readonly`\<\{ `message`: `Schema.Codec`\<`M`\>; `key`: (`message`) => `string`; `outbox`: [`PostOutboxStore`](#postoutboxstore)\<`E`\>; `deliver`: (`message`) => `Effect.Effect`\<`boolean`, `E`\>; `scheduleWake`: (`at`) => `Effect.Effect`\<`void`, `E`\>; `now`: () => `number`; `invalidRow`: () => `E`; `encodingFailed`: () => `E`; \}\> |
|
|
411
|
-
|
|
412
|
-
#### Returns
|
|
413
|
-
|
|
414
|
-
[`PostOutbox`](#postoutbox)\<`M`, `E`\>
|
|
415
|
-
|
|
416
|
-
---
|
|
417
|
-
|
|
418
|
-
### postOutboxPending()
|
|
419
|
-
|
|
420
|
-
```ts
|
|
421
|
-
function postOutboxPending<M, E>(options): Effect<boolean, E>;
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
Whether any message in an outbox still awaits a delivery attempt. Delivered
|
|
425
|
-
receipts and exhausted dead letters are retained but owe nothing more, so a host
|
|
426
|
-
may let their owner rest.
|
|
427
|
-
|
|
428
|
-
#### Type Parameters
|
|
429
|
-
|
|
430
|
-
| Type Parameter |
|
|
431
|
-
| -------------- |
|
|
432
|
-
| `M` |
|
|
433
|
-
| `E` |
|
|
434
|
-
|
|
435
|
-
#### Parameters
|
|
436
|
-
|
|
437
|
-
| Parameter | Type |
|
|
438
|
-
| --------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
439
|
-
| `options` | `Readonly`\<\{ `message`: `Schema.Codec`\<`M`\>; `outbox`: `Pick`\<[`PostOutboxStore`](#postoutboxstore)\<`E`\>, `"list"`\>; \}\> |
|
|
440
|
-
|
|
441
|
-
#### Returns
|
|
442
|
-
|
|
443
|
-
`Effect`\<`boolean`, `E`\>
|
|
444
|
-
|
|
445
|
-
---
|
|
446
|
-
|
|
447
330
|
### definePostRoute()
|
|
448
331
|
|
|
449
332
|
#### Call Signature
|
|
@@ -584,9 +467,9 @@ Decode at both persisted and DO boundaries with the route's Effect Schema.
|
|
|
584
467
|
|
|
585
468
|
#### Parameters
|
|
586
469
|
|
|
587
|
-
| Parameter | Type
|
|
588
|
-
| --------- |
|
|
589
|
-
| `options` | `Readonly`\<\{ `routes`: `R`; `owner`: `WatchdogSqliteOwner`; `transactional`: `WatchdogTransactionalProjection`; `
|
|
470
|
+
| Parameter | Type |
|
|
471
|
+
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
472
|
+
| `options` | `Readonly`\<\{ `routes`: `R`; `owner`: `WatchdogSqliteOwner`; `transactional`: `WatchdogTransactionalProjection`; `now`: () => `number`; `scheduleWake`: (`at`) => `Promise`\<`void`\>; \}\> |
|
|
590
473
|
|
|
591
474
|
#### Returns
|
|
592
475
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fungi.computer/carrier",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"author": "Levi <levi@technomancy.dev>",
|
|
5
5
|
"description": "Durable email submission and recipient delivery tracking",
|
|
6
6
|
"type": "module",
|
|
@@ -34,10 +34,6 @@
|
|
|
34
34
|
"./post-postgres": {
|
|
35
35
|
"types": "./dist/post-postgres.d.ts",
|
|
36
36
|
"import": "./dist/post-postgres.js"
|
|
37
|
-
},
|
|
38
|
-
"./testing": {
|
|
39
|
-
"types": "./dist/testing.d.ts",
|
|
40
|
-
"import": "./dist/testing.js"
|
|
41
37
|
}
|
|
42
38
|
},
|
|
43
39
|
"files": [
|
|
@@ -48,9 +44,9 @@
|
|
|
48
44
|
],
|
|
49
45
|
"dependencies": {
|
|
50
46
|
"@cloudflare/workers-types": "5.20260828.1",
|
|
51
|
-
"@fungi.computer/almanac": "0.2.
|
|
47
|
+
"@fungi.computer/almanac": "0.2.1",
|
|
52
48
|
"@fungi.computer/cairn": "0.1.0",
|
|
53
|
-
"@fungi.computer/watchdog": "0.1.
|
|
49
|
+
"@fungi.computer/watchdog": "0.1.1",
|
|
54
50
|
"effect": "4.0.2",
|
|
55
51
|
"pg": "8.23.0",
|
|
56
52
|
"@types/pg": "8.23.1"
|
package/dist/post-outbox.d.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import * as Effect from "effect/Effect";
|
|
2
|
-
import * as Schema from "effect/Schema";
|
|
3
|
-
import { type RetryPolicy } from "@fungi.computer/almanac";
|
|
4
|
-
/** Maximum receiver attempts retained from BirdDog. */
|
|
5
|
-
export declare const maxDeliveryAttempts = 20;
|
|
6
|
-
/** BirdDog's retained receiver retry policy, shared by every Post adapter. */
|
|
7
|
-
export declare const postRetryPolicy: RetryPolicy;
|
|
8
|
-
/** Durable, string-backed rows. A delivered receipt must outlive source replay. */
|
|
9
|
-
export type PostOutboxStore<E> = Readonly<{
|
|
10
|
-
/** Commit one pending row or completion receipt. */
|
|
11
|
-
put: (key: string, value: string) => Effect.Effect<void, E>;
|
|
12
|
-
/** Inspect one durable row by key. */
|
|
13
|
-
read: (key: string) => Effect.Effect<string | undefined, E>;
|
|
14
|
-
/** Read an ordered bounded page. */
|
|
15
|
-
list: (limit: number, afterKey?: string) => Effect.Effect<readonly Readonly<{
|
|
16
|
-
key: string;
|
|
17
|
-
value: string;
|
|
18
|
-
}>[], E>;
|
|
19
|
-
/** Discard one malformed row. */
|
|
20
|
-
delete: (key: string) => Effect.Effect<void, E>;
|
|
21
|
-
}>;
|
|
22
|
-
/** Bounded generic delivery owned by Post. */
|
|
23
|
-
export type PostOutbox<M, E> = Readonly<{
|
|
24
|
-
/** Queue a message and return its wake hint so a fan-out can schedule once. */
|
|
25
|
-
enqueue: (message: M, at?: number) => Effect.Effect<number | undefined, E>;
|
|
26
|
-
/** Attempt one bounded page and arrange the next wake. */
|
|
27
|
-
drain: () => Effect.Effect<void, E>;
|
|
28
|
-
/** Earliest owed delivery, including a future retry; absent when none remain. */
|
|
29
|
-
nextWakeAt: () => Effect.Effect<number | undefined, E>;
|
|
30
|
-
/** Inspect exhausted messages in an ordered page. */
|
|
31
|
-
deadLetters: (limit: number, afterKey?: string) => Effect.Effect<M[], E>;
|
|
32
|
-
}>;
|
|
33
|
-
/** The same bounded delivery and receipt machinery serves Post and BirdDog. */
|
|
34
|
-
export declare function makePostOutbox<M, E>(options: Readonly<{
|
|
35
|
-
message: Schema.Codec<M>;
|
|
36
|
-
key: (message: M) => string;
|
|
37
|
-
outbox: PostOutboxStore<E>;
|
|
38
|
-
deliver: (message: M) => Effect.Effect<boolean, E>;
|
|
39
|
-
scheduleWake: (at: number) => Effect.Effect<void, E>;
|
|
40
|
-
now: () => number;
|
|
41
|
-
invalidRow: () => E;
|
|
42
|
-
encodingFailed: () => E;
|
|
43
|
-
}>): PostOutbox<M, E>;
|
|
44
|
-
/**
|
|
45
|
-
* Whether any message in an outbox still awaits a delivery attempt. Delivered
|
|
46
|
-
* receipts and exhausted dead letters are retained but owe nothing more, so a
|
|
47
|
-
* host may let their owner rest.
|
|
48
|
-
*/
|
|
49
|
-
export declare function postOutboxPending<M, E>(options: Readonly<{
|
|
50
|
-
message: Schema.Codec<M>;
|
|
51
|
-
outbox: Pick<PostOutboxStore<E>, "list">;
|
|
52
|
-
}>): Effect.Effect<boolean, E>;
|
package/dist/post-outbox.js
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
import * as Effect from "effect/Effect";
|
|
2
|
-
import * as Schema from "effect/Schema";
|
|
3
|
-
import { backoffDelay } from "@fungi.computer/almanac";
|
|
4
|
-
const PageSize = 64;
|
|
5
|
-
/** Maximum receiver attempts retained from BirdDog. */
|
|
6
|
-
export const maxDeliveryAttempts = 20;
|
|
7
|
-
/** BirdDog's retained receiver retry policy, shared by every Post adapter. */
|
|
8
|
-
export const postRetryPolicy = {
|
|
9
|
-
baseMs: 2_000,
|
|
10
|
-
maxMs: 300_000,
|
|
11
|
-
};
|
|
12
|
-
const earlier = (a, b) => b === undefined ? a : a === undefined ? b : Math.min(a, b);
|
|
13
|
-
const logDeliveryFailure = (cause) => {
|
|
14
|
-
console.error("Carrier Post outbox delivery failed", {
|
|
15
|
-
failureKind: cause instanceof Error ? "Error" : "NonErrorThrown",
|
|
16
|
-
});
|
|
17
|
-
};
|
|
18
|
-
/** One durable row: a message still owed delivery, or its delivered receipt. */
|
|
19
|
-
function postRowText(message) {
|
|
20
|
-
return Schema.fromJsonString(Schema.Union([
|
|
21
|
-
Schema.Struct({
|
|
22
|
-
mail: message,
|
|
23
|
-
attempts: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
24
|
-
nextAttemptAt: Schema.optional(Schema.Number.check(Schema.isGreaterThanOrEqualTo(0))),
|
|
25
|
-
}),
|
|
26
|
-
Schema.Struct({ state: Schema.Literal("delivered") }),
|
|
27
|
-
]));
|
|
28
|
-
}
|
|
29
|
-
/** The same bounded delivery and receipt machinery serves Post and BirdDog. */
|
|
30
|
-
export function makePostOutbox(options) {
|
|
31
|
-
const Text = postRowText(options.message);
|
|
32
|
-
const decode = (value) => Schema.decodeUnknownEffect(Text)(value).pipe(Effect.mapError(options.invalidRow));
|
|
33
|
-
const encode = (row) => Effect.try({
|
|
34
|
-
try: () => Schema.encodeSync(Text)(row),
|
|
35
|
-
catch: options.encodingFailed,
|
|
36
|
-
});
|
|
37
|
-
const enqueue = (message, at = options.now()) => Effect.gen(function* () {
|
|
38
|
-
const key = options.key(message);
|
|
39
|
-
const existing = yield* options.outbox.read(key);
|
|
40
|
-
if (existing !== undefined) {
|
|
41
|
-
const row = yield* decode(existing);
|
|
42
|
-
if ("state" in row || row.attempts >= maxDeliveryAttempts)
|
|
43
|
-
return;
|
|
44
|
-
return Math.max(at, row.nextAttemptAt ?? at);
|
|
45
|
-
}
|
|
46
|
-
const value = yield* encode({ mail: message, attempts: 0 });
|
|
47
|
-
yield* options.outbox.put(key, value);
|
|
48
|
-
return at;
|
|
49
|
-
});
|
|
50
|
-
const process = (stored, now) => Effect.gen(function* () {
|
|
51
|
-
const decoded = yield* Effect.result(decode(stored.value));
|
|
52
|
-
if (decoded._tag === "Failure") {
|
|
53
|
-
yield* options.outbox.delete(stored.key);
|
|
54
|
-
return undefined;
|
|
55
|
-
}
|
|
56
|
-
const row = decoded.success;
|
|
57
|
-
if ("state" in row || row.attempts >= maxDeliveryAttempts)
|
|
58
|
-
return undefined;
|
|
59
|
-
if (row.nextAttemptAt !== undefined && row.nextAttemptAt > now)
|
|
60
|
-
return row.nextAttemptAt;
|
|
61
|
-
const delivered = yield* options.deliver(row.mail).pipe(Effect.catch((cause) => Effect.sync(() => {
|
|
62
|
-
logDeliveryFailure(cause);
|
|
63
|
-
}).pipe(Effect.as(false))));
|
|
64
|
-
if (delivered) {
|
|
65
|
-
yield* options.outbox.put(stored.key, yield* encode({ state: "delivered" }));
|
|
66
|
-
return undefined;
|
|
67
|
-
}
|
|
68
|
-
const attempts = row.attempts + 1;
|
|
69
|
-
const nextAttemptAt = now + backoffDelay(attempts, postRetryPolicy);
|
|
70
|
-
yield* options.outbox.put(stored.key, yield* encode({ mail: row.mail, attempts, nextAttemptAt }));
|
|
71
|
-
return attempts < maxDeliveryAttempts ? nextAttemptAt : undefined;
|
|
72
|
-
});
|
|
73
|
-
let cursor;
|
|
74
|
-
let earliestFuture;
|
|
75
|
-
const drain = () => Effect.gen(function* () {
|
|
76
|
-
const now = options.now();
|
|
77
|
-
const rows = yield* options.outbox.list(PageSize, cursor);
|
|
78
|
-
for (const row of rows)
|
|
79
|
-
earliestFuture = earlier(earliestFuture, yield* process(row, now));
|
|
80
|
-
if (rows.length === PageSize) {
|
|
81
|
-
const lastKey = rows[rows.length - 1]?.key;
|
|
82
|
-
if (lastKey !== undefined &&
|
|
83
|
-
(yield* options.outbox.list(1, lastKey)).length > 0) {
|
|
84
|
-
cursor = lastKey;
|
|
85
|
-
yield* options.scheduleWake(now);
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
cursor = undefined;
|
|
90
|
-
if (earliestFuture !== undefined)
|
|
91
|
-
yield* options.scheduleWake(Math.max(now, earliestFuture));
|
|
92
|
-
earliestFuture = undefined;
|
|
93
|
-
});
|
|
94
|
-
const deadLetters = (limit, afterKey) => Effect.gen(function* () {
|
|
95
|
-
const rows = yield* options.outbox.list(limit, afterKey);
|
|
96
|
-
const dead = [];
|
|
97
|
-
for (const stored of rows) {
|
|
98
|
-
const row = yield* decode(stored.value);
|
|
99
|
-
if (!("state" in row) && row.attempts >= maxDeliveryAttempts)
|
|
100
|
-
dead.push(row.mail);
|
|
101
|
-
}
|
|
102
|
-
return dead;
|
|
103
|
-
});
|
|
104
|
-
const nextWakeAt = () => Effect.gen(function* () {
|
|
105
|
-
let afterKey;
|
|
106
|
-
let due;
|
|
107
|
-
for (;;) {
|
|
108
|
-
const rows = yield* options.outbox.list(PageSize, afterKey);
|
|
109
|
-
for (const stored of rows) {
|
|
110
|
-
// Malformed rows are discarded by drain, not deliverable work.
|
|
111
|
-
const decoded = yield* Effect.result(decode(stored.value));
|
|
112
|
-
if (decoded._tag === "Failure")
|
|
113
|
-
continue;
|
|
114
|
-
const row = decoded.success;
|
|
115
|
-
if (!("state" in row) && row.attempts < maxDeliveryAttempts)
|
|
116
|
-
due = earlier(due, row.nextAttemptAt ?? options.now());
|
|
117
|
-
}
|
|
118
|
-
const lastKey = rows.at(-1)?.key;
|
|
119
|
-
if (rows.length < PageSize || lastKey === undefined)
|
|
120
|
-
return due;
|
|
121
|
-
afterKey = lastKey;
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
return { enqueue, drain, deadLetters, nextWakeAt };
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Whether any message in an outbox still awaits a delivery attempt. Delivered
|
|
128
|
-
* receipts and exhausted dead letters are retained but owe nothing more, so a
|
|
129
|
-
* host may let their owner rest.
|
|
130
|
-
*/
|
|
131
|
-
export function postOutboxPending(options) {
|
|
132
|
-
const Text = postRowText(options.message);
|
|
133
|
-
return Effect.gen(function* () {
|
|
134
|
-
let afterKey;
|
|
135
|
-
for (;;) {
|
|
136
|
-
const rows = yield* options.outbox.list(PageSize, afterKey);
|
|
137
|
-
for (const stored of rows) {
|
|
138
|
-
// A malformed row is not deliverable; the next drain discards it.
|
|
139
|
-
const decoded = yield* Effect.result(Schema.decodeUnknownEffect(Text)(stored.value));
|
|
140
|
-
if (decoded._tag === "Failure")
|
|
141
|
-
continue;
|
|
142
|
-
const row = decoded.success;
|
|
143
|
-
if (!("state" in row) && row.attempts < maxDeliveryAttempts)
|
|
144
|
-
return true;
|
|
145
|
-
}
|
|
146
|
-
const lastKey = rows.at(-1)?.key;
|
|
147
|
-
if (rows.length < PageSize || lastKey === undefined)
|
|
148
|
-
return false;
|
|
149
|
-
afterKey = lastKey;
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
}
|
package/dist/testing.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/** String-row outbox for deterministic tests and quick starts; no persistence. */
|
|
2
|
-
export type MemoryOutbox = Readonly<{
|
|
3
|
-
/** Store or replace one encoded row. */
|
|
4
|
-
put: (key: string, value: string) => Promise<void>;
|
|
5
|
-
/** Read a retained row, if present. */
|
|
6
|
-
read: (key: string) => Promise<string | undefined>;
|
|
7
|
-
/** Read a key-ordered page strictly after the supplied cursor. */
|
|
8
|
-
list: (limit: number, afterKey?: string) => Promise<readonly Readonly<{
|
|
9
|
-
key: string;
|
|
10
|
-
value: string;
|
|
11
|
-
}>[]>;
|
|
12
|
-
/** Remove one row. */
|
|
13
|
-
delete: (key: string) => Promise<void>;
|
|
14
|
-
}>;
|
|
15
|
-
/** Create an independent in-process outbox for trusted tests and examples. */
|
|
16
|
-
export declare function createMemoryOutbox(): MemoryOutbox;
|
package/dist/testing.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/** Create an independent in-process outbox for trusted tests and examples. */
|
|
2
|
-
export function createMemoryOutbox() {
|
|
3
|
-
const rows = new Map();
|
|
4
|
-
return {
|
|
5
|
-
put: (key, value) => {
|
|
6
|
-
rows.set(key, value);
|
|
7
|
-
return Promise.resolve();
|
|
8
|
-
},
|
|
9
|
-
read: (key) => Promise.resolve(rows.get(key)),
|
|
10
|
-
list: (limit, afterKey) => Promise.resolve([...rows]
|
|
11
|
-
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
12
|
-
.filter(([key]) => afterKey === undefined || key > afterKey)
|
|
13
|
-
.slice(0, limit)
|
|
14
|
-
.map(([key, value]) => ({ key, value }))),
|
|
15
|
-
delete: (key) => {
|
|
16
|
-
rows.delete(key);
|
|
17
|
-
return Promise.resolve();
|
|
18
|
-
},
|
|
19
|
-
};
|
|
20
|
-
}
|
package/docs/api/testing.md
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
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) / testing
|
|
8
|
-
|
|
9
|
-
# testing
|
|
10
|
-
|
|
11
|
-
## Type Aliases
|
|
12
|
-
|
|
13
|
-
### MemoryOutbox
|
|
14
|
-
|
|
15
|
-
```ts
|
|
16
|
-
type MemoryOutbox = Readonly<{
|
|
17
|
-
put: (key, value) => Promise<void>;
|
|
18
|
-
read: (key) => Promise<string | undefined>;
|
|
19
|
-
list: (
|
|
20
|
-
limit,
|
|
21
|
-
afterKey?,
|
|
22
|
-
) => Promise<
|
|
23
|
-
readonly Readonly<{
|
|
24
|
-
key: string;
|
|
25
|
-
value: string;
|
|
26
|
-
}>[]
|
|
27
|
-
>;
|
|
28
|
-
delete: (key) => Promise<void>;
|
|
29
|
-
}>;
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
String-row outbox for deterministic tests and quick starts; no persistence.
|
|
33
|
-
|
|
34
|
-
## Functions
|
|
35
|
-
|
|
36
|
-
### createMemoryOutbox()
|
|
37
|
-
|
|
38
|
-
```ts
|
|
39
|
-
function createMemoryOutbox(): MemoryOutbox;
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Create an independent in-process outbox for trusted tests and examples.
|
|
43
|
-
|
|
44
|
-
#### Returns
|
|
45
|
-
|
|
46
|
-
[`MemoryOutbox`](#memoryoutbox)
|