@fungi.computer/carrier 0.0.1 → 0.2.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 +35 -13
- package/dist/inspection.d.ts +2 -2
- package/dist/inspection.js +1 -1
- package/dist/internal/post-contract.d.ts +3 -2
- package/dist/internal/post-contract.js +3 -3
- package/dist/internal/post-execution.d.ts +16 -25
- package/dist/internal/post-execution.js +4 -2
- package/dist/internal/post-sql.d.ts +1 -1
- package/dist/internal/post-sql.js +10 -4
- package/dist/post-d1.d.ts +1 -1
- package/dist/post-d1.js +1 -1
- package/dist/post-outbox.d.ts +14 -2
- package/dist/post-outbox.js +67 -14
- package/dist/post-postgres.d.ts +1 -1
- package/dist/post-postgres.js +1 -1
- package/dist/post.d.ts +12 -4
- package/dist/post.js +90 -15
- package/docs/api/inspection.md +6 -6
- package/docs/api/post-d1.md +4 -5
- package/docs/api/post-postgres.md +5 -4
- package/docs/api/post.md +82 -82
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -97,7 +97,8 @@ In a Durable Object, `ctx.storage` is already the SQLite owner.
|
|
|
97
97
|
`createMemoryOutbox()` stores encoded string rows in process memory for tests
|
|
98
98
|
and quick starts. It provides `put`, `read`, key-ordered `list(limit, afterKey)`
|
|
99
99
|
and `delete`. Each instance is independent; it is not durable storage. BirdDog’s
|
|
100
|
-
[quick start](
|
|
100
|
+
[quick start](https://docs.fungi.computer/bird-dog/quickstart/) uses this
|
|
101
|
+
helper.
|
|
101
102
|
|
|
102
103
|
```ts
|
|
103
104
|
import { createMemoryOutbox } from "@fungi.computer/carrier/testing";
|
|
@@ -214,14 +215,26 @@ ambiguous first dispatch has zero. Post `attempts` counts receiver invocations.
|
|
|
214
215
|
|
|
215
216
|
## Post
|
|
216
217
|
|
|
218
|
+
Post's ordinary retry policy uses Almanac's shared pure backoff calculation: 2
|
|
219
|
+
seconds initially, doubling to a 5-minute cap, with at most 20 receiver
|
|
220
|
+
attempts. The synchronous and SQL adapters share that default policy; explicit
|
|
221
|
+
SQL retry policies retain their own delays. A SQLite route with `deadlineMs`
|
|
222
|
+
uses its finite lifetime instead of the attempt ceiling. Almanac supplies
|
|
223
|
+
arithmetic only, not a second scheduler or retry owner.
|
|
224
|
+
|
|
217
225
|
`@fungi.computer/carrier/post` delivers typed, point-to-point envelopes between
|
|
218
226
|
named Durable Objects. `definePostRoute` pairs each address schema and event
|
|
219
227
|
schema with its Durable Object namespace, name function and fixed `addressKind`
|
|
220
|
-
label.
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
228
|
+
label. A SQLite-backed route may opt into a retry lifetime of at most seven days
|
|
229
|
+
with `deadlineMs`; Post stores its absolute deadline at admission and settles an
|
|
230
|
+
undelivered row as an `expired` dead letter when the deadline is reached.
|
|
231
|
+
Deadline routes retry until expiry rather than using the ordinary attempt
|
|
232
|
+
ceiling. Omitted deadlines preserve the existing retry budget. Every event route
|
|
233
|
+
to the same namespace shares that label, so different event kinds for one
|
|
234
|
+
recipient keep producer order. The receiver implements `deliver(envelope)` and
|
|
235
|
+
calls `admitPost` inside its own SQLite transaction, so the event mutation and
|
|
236
|
+
admission receipt commit together. Keys must identify the producing event across
|
|
237
|
+
all senders to that receiver.
|
|
225
238
|
|
|
226
239
|
Construct `createPost` with the producer's structural SQLite `owner`, typed
|
|
227
240
|
`routes`, Watchdog's `transactional` projection and `readJob` operation, a
|
|
@@ -258,20 +271,29 @@ acknowledge a domain ledger in the same producer transaction as the Post
|
|
|
258
271
|
receipt. An acknowledgment failure leaves both pending for retry. Prepared facts
|
|
259
272
|
should carry sealed grants, never raw credentials.
|
|
260
273
|
|
|
261
|
-
A definite refusal
|
|
262
|
-
its reason and its attempt count. `deadLetters` returns
|
|
263
|
-
`nextCursor`, including a continuation through pages that contain
|
|
274
|
+
A definite refusal, an exhausted retry budget or a route deadline keeps a
|
|
275
|
+
dead-letter envelope, its reason and its attempt count. `deadLetters` returns
|
|
276
|
+
`letters` and `nextCursor`, including a continuation through pages that contain
|
|
277
|
+
only receipts.
|
|
264
278
|
|
|
265
279
|
The same retry and receipt machinery is exported as `makePostOutbox` for a
|
|
266
280
|
string-backed outbox. [BirdDog](../bird-dog/README.md) uses it for agent reply
|
|
267
|
-
delivery.
|
|
281
|
+
delivery. `postOutboxPending` reports whether such an outbox still owes any
|
|
282
|
+
message a delivery attempt; receipts and exhausted dead letters owe nothing. Its
|
|
283
|
+
`nextWakeAt()` reads the earliest owed delivery across the retained rows,
|
|
284
|
+
including future retries. Delivered receipts, exhausted dead letters and
|
|
285
|
+
malformed rows do not request a wake.
|
|
268
286
|
|
|
269
287
|
## Post transactions on D1 and PostgreSQL
|
|
270
288
|
|
|
271
289
|
`@fungi.computer/carrier/post-d1` and `/post-postgres` bind the ordinary Post
|
|
272
|
-
address and event registry to Watchdog's database adapters.
|
|
273
|
-
|
|
274
|
-
|
|
290
|
+
address and event registry to Watchdog's database adapters. These relational
|
|
291
|
+
adapters preserve their existing schema and retry contract and do not support
|
|
292
|
+
route deadlines. Constructing either adapter with a route that sets `deadlineMs`
|
|
293
|
+
throws `PostError` with `reason: "relational-deadline-unsupported"` before
|
|
294
|
+
database access. Use SQLite-backed Post for deadline routes. Apply
|
|
295
|
+
`postD1Schema` or `postPostgresSchema` alongside the matching Watchdog schema
|
|
296
|
+
through your migration tool.
|
|
275
297
|
|
|
276
298
|
On D1, `post.send(envelope)` returns the envelope and job statements. Include
|
|
277
299
|
both in the same `database.batch` as the producer fact. `sendFromSelect` accepts
|
package/dist/inspection.d.ts
CHANGED
|
@@ -30,7 +30,7 @@ export declare const PostReceiptSummarySchema: Schema.Union<readonly [Schema.Str
|
|
|
30
30
|
readonly state: Schema.Literal<"dead">;
|
|
31
31
|
readonly kind: Schema.String;
|
|
32
32
|
readonly attempts: Schema.Int;
|
|
33
|
-
readonly reason: Schema.Literals<readonly ["refused", "exhausted"]>;
|
|
33
|
+
readonly reason: Schema.Literals<readonly ["refused", "exhausted", "expired"]>;
|
|
34
34
|
}>]>;
|
|
35
35
|
/** Metadata for one retained Post row. */
|
|
36
36
|
export type PostReceiptSummary = Schema.Schema.Type<typeof PostReceiptSummarySchema>;
|
|
@@ -53,7 +53,7 @@ export declare const PostInspectionPageSchema: Schema.Union<readonly [Schema.Str
|
|
|
53
53
|
readonly state: Schema.Literal<"dead">;
|
|
54
54
|
readonly kind: Schema.String;
|
|
55
55
|
readonly attempts: Schema.Int;
|
|
56
|
-
readonly reason: Schema.Literals<readonly ["refused", "exhausted"]>;
|
|
56
|
+
readonly reason: Schema.Literals<readonly ["refused", "exhausted", "expired"]>;
|
|
57
57
|
}>]>>;
|
|
58
58
|
readonly nextCursor: Schema.NullOr<Schema.String>;
|
|
59
59
|
}>]>;
|
package/dist/inspection.js
CHANGED
|
@@ -29,7 +29,7 @@ export const PostReceiptSummarySchema = Schema.Union([
|
|
|
29
29
|
state: Schema.Literal("dead"),
|
|
30
30
|
kind: Key,
|
|
31
31
|
attempts: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
32
|
-
reason: Schema.Literals(["refused", "exhausted"]),
|
|
32
|
+
reason: Schema.Literals(["refused", "exhausted", "expired"]),
|
|
33
33
|
}),
|
|
34
34
|
]);
|
|
35
35
|
/** Bounded evidence with an explicit distinction between empty and uninitialized storage. */
|
|
@@ -18,10 +18,11 @@ export declare const PostDeliveryResultSchema: Schema.Union<readonly [Schema.Lit
|
|
|
18
18
|
declare const PostError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
19
19
|
readonly _tag: "PostError";
|
|
20
20
|
} & Readonly<A>;
|
|
21
|
-
/** Post rejected an unknown route or
|
|
21
|
+
/** Post rejected an unknown route, conflicting key, or unsupported policy. */
|
|
22
22
|
export declare class PostError extends PostError_base<{
|
|
23
23
|
readonly message: string;
|
|
24
|
+
readonly reason?: "relational-deadline-unsupported";
|
|
24
25
|
}> {
|
|
25
|
-
constructor(message: string);
|
|
26
|
+
constructor(message: string, reason?: "relational-deadline-unsupported");
|
|
26
27
|
}
|
|
27
28
|
export {};
|
|
@@ -14,9 +14,9 @@ export const PostDeliveryResultSchema = Schema.Union([
|
|
|
14
14
|
receipt: Schema.Json,
|
|
15
15
|
}),
|
|
16
16
|
]);
|
|
17
|
-
/** Post rejected an unknown route or
|
|
17
|
+
/** Post rejected an unknown route, conflicting key, or unsupported policy. */
|
|
18
18
|
export class PostError extends Data.TaggedError("PostError") {
|
|
19
|
-
constructor(message) {
|
|
20
|
-
super({ message });
|
|
19
|
+
constructor(message, reason) {
|
|
20
|
+
super(reason === undefined ? { message } : { message, reason });
|
|
21
21
|
}
|
|
22
22
|
}
|
|
@@ -6,6 +6,18 @@ export type DeliveryEvidence = Readonly<{
|
|
|
6
6
|
envelope: PostEnvelope | undefined;
|
|
7
7
|
failure?: unknown;
|
|
8
8
|
}>;
|
|
9
|
+
/** The settlement a delivery attempt resolves to; internal to the Post backends. */
|
|
10
|
+
export type PostDisposition = Readonly<{
|
|
11
|
+
state: "dead";
|
|
12
|
+
reason: "refused" | "exhausted" | "expired";
|
|
13
|
+
}> | Readonly<{
|
|
14
|
+
state: "pending";
|
|
15
|
+
}> | Readonly<{
|
|
16
|
+
state: "delivered";
|
|
17
|
+
cursor?: number;
|
|
18
|
+
}> | Readonly<{
|
|
19
|
+
state: "keep";
|
|
20
|
+
}>;
|
|
9
21
|
/** Shared preparation, bounded receiver I/O and response decoding for every Post backend. */
|
|
10
22
|
export declare function deliverPost(input: Readonly<{
|
|
11
23
|
attempt: Extract<PostRow, {
|
|
@@ -16,30 +28,9 @@ export declare function deliverPost(input: Readonly<{
|
|
|
16
28
|
savePrepared: (envelope: PostEnvelope) => void | Promise<void>;
|
|
17
29
|
}>): Promise<DeliveryEvidence>;
|
|
18
30
|
/** One settlement decision, shared by native storage adapters. */
|
|
19
|
-
export declare function postDisposition(evidence: DeliveryEvidence, route: PostRegistryRoute, attempts: number, maxAttempts: number
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
} | {
|
|
24
|
-
readonly state: "pending";
|
|
25
|
-
readonly reason?: never;
|
|
26
|
-
readonly cursor?: never;
|
|
27
|
-
} | {
|
|
28
|
-
readonly state: "dead";
|
|
29
|
-
readonly reason: "refused";
|
|
30
|
-
readonly cursor?: never;
|
|
31
|
-
} | {
|
|
32
|
-
readonly state: "keep";
|
|
33
|
-
readonly reason?: never;
|
|
34
|
-
readonly cursor?: never;
|
|
35
|
-
} | {
|
|
36
|
-
readonly state: "delivered";
|
|
37
|
-
readonly reason?: never;
|
|
38
|
-
readonly cursor?: never;
|
|
39
|
-
} | {
|
|
40
|
-
readonly state: "delivered";
|
|
41
|
-
readonly cursor: number;
|
|
42
|
-
readonly reason?: never;
|
|
43
|
-
};
|
|
31
|
+
export declare function postDisposition(evidence: DeliveryEvidence, route: PostRegistryRoute, attempts: number, maxAttempts: number, options?: Readonly<{
|
|
32
|
+
deadlineExpired?: boolean;
|
|
33
|
+
retryUntilDeadline?: boolean;
|
|
34
|
+
}>): PostDisposition;
|
|
44
35
|
/** Durable provider/domain receipt data, separate from the staged event. */
|
|
45
36
|
export declare function deliveryReceipt(evidence: DeliveryEvidence): Schema.Json | undefined;
|
|
@@ -38,10 +38,12 @@ export async function deliverPost(input) {
|
|
|
38
38
|
return { result: decoded.success, envelope };
|
|
39
39
|
}
|
|
40
40
|
/** One settlement decision, shared by native storage adapters. */
|
|
41
|
-
export function postDisposition(evidence, route, attempts, maxAttempts) {
|
|
41
|
+
export function postDisposition(evidence, route, attempts, maxAttempts, options = {}) {
|
|
42
42
|
const { result, envelope } = evidence;
|
|
43
|
+
if (result === "unavailable" && options.deadlineExpired === true)
|
|
44
|
+
return { state: "dead", reason: "expired" };
|
|
43
45
|
if (result === "unavailable")
|
|
44
|
-
return attempts >= maxAttempts
|
|
46
|
+
return !options.retryUntilDeadline && attempts >= maxAttempts
|
|
45
47
|
? { state: "dead", reason: "exhausted" }
|
|
46
48
|
: { state: "pending" };
|
|
47
49
|
if (envelope === undefined)
|
|
@@ -12,7 +12,7 @@ export type PostSql = Readonly<{
|
|
|
12
12
|
}>;
|
|
13
13
|
/** SQL native Carrier runtime configuration; route decisions remain in Post. */
|
|
14
14
|
export type SqlPostOptions<R extends Record<string, PostRegistryRoute>> = Readonly<{
|
|
15
|
-
/** The same
|
|
15
|
+
/** The same registry used by SQLite Post; routes with deadlines are rejected. */
|
|
16
16
|
routes: R;
|
|
17
17
|
/** Optional bounded, redacted family diagnostic; never persist raw provider errors. */
|
|
18
18
|
failureDetail?: (cause: unknown) => string;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as Schema from "effect/Schema";
|
|
2
|
+
import { backoffDelay } from "@fungi.computer/almanac";
|
|
2
3
|
import { defineIdFamily } from "@fungi.computer/cairn";
|
|
3
4
|
import { PostEnvelopeSchema, PostError, } from "../post.js";
|
|
4
5
|
import { deliverPost, postDisposition, deliveryReceipt, } from "./post-execution.js";
|
|
5
|
-
import { maxDeliveryAttempts,
|
|
6
|
+
import { maxDeliveryAttempts, postRetryPolicy } from "../post-outbox.js";
|
|
6
7
|
const ids = defineIdFamily({
|
|
7
8
|
family: "post",
|
|
8
9
|
version: 1,
|
|
@@ -127,16 +128,21 @@ function publicRow(stored) {
|
|
|
127
128
|
};
|
|
128
129
|
}
|
|
129
130
|
export function openSqlPost(database, options) {
|
|
131
|
+
if (Object.values(options.routes).some((route) => route.deadlineMs !== undefined))
|
|
132
|
+
throw new PostError("relational Post adapters do not support route deadlines", "relational-deadline-unsupported");
|
|
130
133
|
const policy = Schema.decodeUnknownSync(Schema.Struct({
|
|
131
134
|
maxAttempts: Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: maxDeliveryAttempts })),
|
|
132
135
|
baseDelayMs: Schema.Int.check(Schema.isGreaterThan(0)),
|
|
133
136
|
maxDelayMs: Schema.Int.check(Schema.isGreaterThan(0)),
|
|
134
137
|
}))(options.retry ?? {
|
|
135
138
|
maxAttempts: maxDeliveryAttempts,
|
|
136
|
-
baseDelayMs:
|
|
137
|
-
maxDelayMs:
|
|
139
|
+
baseDelayMs: backoffDelay(1, postRetryPolicy),
|
|
140
|
+
maxDelayMs: backoffDelay(maxDeliveryAttempts, postRetryPolicy),
|
|
141
|
+
});
|
|
142
|
+
const delay = (attempt) => backoffDelay(attempt, {
|
|
143
|
+
baseMs: policy.baseDelayMs,
|
|
144
|
+
maxMs: policy.maxDelayMs,
|
|
138
145
|
});
|
|
139
|
-
const delay = (attempt) => Math.min(policy.baseDelayMs * 2 ** Math.max(0, attempt - 1), policy.maxDelayMs);
|
|
140
146
|
const retrySql = Array.from({ length: policy.maxAttempts }, (_, index) => `WHEN ${String(index)} THEN ${String(delay(index + 1))}`).join(" ");
|
|
141
147
|
for (const route of Object.values(options.routes)) {
|
|
142
148
|
// Synchronous owner hooks cannot participate in an asynchronous transaction.
|
package/dist/post-d1.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export type D1Post = SqlPostRuntime & Readonly<{
|
|
|
19
19
|
}>;
|
|
20
20
|
/** Carrier-owned D1 schema, applied by the deployment migration owner. */
|
|
21
21
|
export declare const postD1Schema: readonly string[];
|
|
22
|
-
/** Bind the Post registry to
|
|
22
|
+
/** Bind the Post registry to D1; route deadlines are rejected with PostError. */
|
|
23
23
|
export declare function createD1Post<R extends Record<string, PostRegistryRoute>>(options: SqlPostOptions<R> & Readonly<{
|
|
24
24
|
database: D1Database;
|
|
25
25
|
watchdog: D1Watchdog;
|
package/dist/post-d1.js
CHANGED
|
@@ -2,7 +2,7 @@ import { purgeD1Queues, prepareD1Admission, } from "@fungi.computer/watchdog/d1"
|
|
|
2
2
|
import { admitPostSelect, jobSelect, openSqlPost, postSchema, preparePost, } from "./internal/post-sql.js";
|
|
3
3
|
/** Carrier-owned D1 schema, applied by the deployment migration owner. */
|
|
4
4
|
export const postD1Schema = postSchema(false);
|
|
5
|
-
/** Bind the Post registry to
|
|
5
|
+
/** Bind the Post registry to D1; route deadlines are rejected with PostError. */
|
|
6
6
|
export function createD1Post(options) {
|
|
7
7
|
// Only package-owned templates use $N; caller SELECTs remain native SQL.
|
|
8
8
|
const prepare = (statement) => options.database
|
package/dist/post-outbox.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as Effect from "effect/Effect";
|
|
2
2
|
import * as Schema from "effect/Schema";
|
|
3
|
+
import { type RetryPolicy } from "@fungi.computer/almanac";
|
|
3
4
|
/** Maximum receiver attempts retained from BirdDog. */
|
|
4
5
|
export declare const maxDeliveryAttempts = 20;
|
|
5
|
-
/** BirdDog's retained
|
|
6
|
-
export declare const
|
|
6
|
+
/** BirdDog's retained receiver retry policy, shared by every Post adapter. */
|
|
7
|
+
export declare const postRetryPolicy: RetryPolicy;
|
|
7
8
|
/** Durable, string-backed rows. A delivered receipt must outlive source replay. */
|
|
8
9
|
export type PostOutboxStore<E> = Readonly<{
|
|
9
10
|
/** Commit one pending row or completion receipt. */
|
|
@@ -24,6 +25,8 @@ export type PostOutbox<M, E> = Readonly<{
|
|
|
24
25
|
enqueue: (message: M, at?: number) => Effect.Effect<number | undefined, E>;
|
|
25
26
|
/** Attempt one bounded page and arrange the next wake. */
|
|
26
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>;
|
|
27
30
|
/** Inspect exhausted messages in an ordered page. */
|
|
28
31
|
deadLetters: (limit: number, afterKey?: string) => Effect.Effect<M[], E>;
|
|
29
32
|
}>;
|
|
@@ -38,3 +41,12 @@ export declare function makePostOutbox<M, E>(options: Readonly<{
|
|
|
38
41
|
invalidRow: () => E;
|
|
39
42
|
encodingFailed: () => E;
|
|
40
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
CHANGED
|
@@ -1,28 +1,34 @@
|
|
|
1
1
|
import * as Effect from "effect/Effect";
|
|
2
2
|
import * as Schema from "effect/Schema";
|
|
3
|
+
import { backoffDelay } from "@fungi.computer/almanac";
|
|
3
4
|
const PageSize = 64;
|
|
4
5
|
/** Maximum receiver attempts retained from BirdDog. */
|
|
5
6
|
export const maxDeliveryAttempts = 20;
|
|
6
|
-
/** BirdDog's retained
|
|
7
|
-
export const
|
|
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
|
+
};
|
|
8
12
|
const earlier = (a, b) => b === undefined ? a : a === undefined ? b : Math.min(a, b);
|
|
9
13
|
const logDeliveryFailure = (cause) => {
|
|
10
14
|
console.error("Carrier Post outbox delivery failed", {
|
|
11
15
|
failureKind: cause instanceof Error ? "Error" : "NonErrorThrown",
|
|
12
16
|
});
|
|
13
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
|
+
}
|
|
14
29
|
/** The same bounded delivery and receipt machinery serves Post and BirdDog. */
|
|
15
30
|
export function makePostOutbox(options) {
|
|
16
|
-
const
|
|
17
|
-
mail: options.message,
|
|
18
|
-
attempts: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
19
|
-
nextAttemptAt: Schema.optional(Schema.Number.check(Schema.isGreaterThanOrEqualTo(0))),
|
|
20
|
-
});
|
|
21
|
-
const Row = Schema.Union([
|
|
22
|
-
Pending,
|
|
23
|
-
Schema.Struct({ state: Schema.Literal("delivered") }),
|
|
24
|
-
]);
|
|
25
|
-
const Text = Schema.fromJsonString(Row);
|
|
31
|
+
const Text = postRowText(options.message);
|
|
26
32
|
const decode = (value) => Schema.decodeUnknownEffect(Text)(value).pipe(Effect.mapError(options.invalidRow));
|
|
27
33
|
const encode = (row) => Effect.try({
|
|
28
34
|
try: () => Schema.encodeSync(Text)(row),
|
|
@@ -60,7 +66,7 @@ export function makePostOutbox(options) {
|
|
|
60
66
|
return undefined;
|
|
61
67
|
}
|
|
62
68
|
const attempts = row.attempts + 1;
|
|
63
|
-
const nextAttemptAt = now +
|
|
69
|
+
const nextAttemptAt = now + backoffDelay(attempts, postRetryPolicy);
|
|
64
70
|
yield* options.outbox.put(stored.key, yield* encode({ mail: row.mail, attempts, nextAttemptAt }));
|
|
65
71
|
return attempts < maxDeliveryAttempts ? nextAttemptAt : undefined;
|
|
66
72
|
});
|
|
@@ -95,5 +101,52 @@ export function makePostOutbox(options) {
|
|
|
95
101
|
}
|
|
96
102
|
return dead;
|
|
97
103
|
});
|
|
98
|
-
|
|
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
|
+
});
|
|
99
152
|
}
|
package/dist/post-postgres.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export type PostgresPost = SqlPostRuntime & Readonly<{
|
|
|
10
10
|
}>;
|
|
11
11
|
/** Carrier-owned PostgreSQL schema, applied by the deployment migration owner. */
|
|
12
12
|
export declare const postPostgresSchema: readonly string[];
|
|
13
|
-
/** Bind the Post registry to
|
|
13
|
+
/** Bind the Post registry to PostgreSQL; route deadlines are rejected with PostError. */
|
|
14
14
|
export declare function createPostgresPost<R extends Record<string, PostRegistryRoute>>(options: SqlPostOptions<R> & Readonly<{
|
|
15
15
|
database: Pick<Pool, "query" | "connect">;
|
|
16
16
|
watchdog: PostgresWatchdog;
|
package/dist/post-postgres.js
CHANGED
|
@@ -10,7 +10,7 @@ const Job = Schema.Struct({
|
|
|
10
10
|
priority: Schema.Number,
|
|
11
11
|
payload_json: Schema.fromJsonString(Schema.Unknown),
|
|
12
12
|
});
|
|
13
|
-
/** Bind the Post registry to
|
|
13
|
+
/** Bind the Post registry to PostgreSQL; route deadlines are rejected with PostError. */
|
|
14
14
|
export function createPostgresPost(options) {
|
|
15
15
|
const enqueue = async (client, source) => {
|
|
16
16
|
const native = source;
|
package/dist/post.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type PostDeliveryResult } from "./internal/post-contract.js";
|
|
|
3
3
|
export { PostError, PostDeliveryResultSchema, } from "./internal/post-contract.js";
|
|
4
4
|
export type { PostDeliveryResult } from "./internal/post-contract.js";
|
|
5
5
|
import type { WatchdogRunningJob, WatchdogSqliteOwner, WatchdogRuntime, WatchdogTransactionalProjection } from "@fungi.computer/watchdog";
|
|
6
|
-
export { makePostOutbox,
|
|
6
|
+
export { makePostOutbox, postOutboxPending } from "./post-outbox.js";
|
|
7
7
|
export type { PostOutbox, PostOutboxStore } from "./post-outbox.js";
|
|
8
8
|
/** Wire envelope parsed at the durable row and receiver boundaries. */
|
|
9
9
|
export declare const PostEnvelopeSchema: Schema.Struct<{
|
|
@@ -23,6 +23,7 @@ export declare const PostRowSchema: Schema.fromJsonString<Schema.Union<readonly
|
|
|
23
23
|
}>;
|
|
24
24
|
readonly attempts: Schema.Int;
|
|
25
25
|
readonly nextAttemptAt: Schema.Number;
|
|
26
|
+
readonly deadlineAt: Schema.optional<Schema.Number>;
|
|
26
27
|
readonly jobId: Schema.optional<Schema.String>;
|
|
27
28
|
readonly delivery: Schema.optional<Schema.Struct<{
|
|
28
29
|
readonly key: Schema.String;
|
|
@@ -44,7 +45,8 @@ export declare const PostRowSchema: Schema.fromJsonString<Schema.Union<readonly
|
|
|
44
45
|
readonly event: Schema.Unknown;
|
|
45
46
|
}>;
|
|
46
47
|
readonly attempts: Schema.Int;
|
|
47
|
-
readonly reason: Schema.Literals<readonly ["refused", "exhausted"]>;
|
|
48
|
+
readonly reason: Schema.Literals<readonly ["refused", "exhausted", "expired"]>;
|
|
49
|
+
readonly deadlineAt: Schema.optional<Schema.Number>;
|
|
48
50
|
readonly receipt: Schema.optional<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
|
|
49
51
|
readonly delivery: Schema.optional<Schema.Struct<{
|
|
50
52
|
readonly key: Schema.String;
|
|
@@ -62,6 +64,8 @@ export type PostRow = Schema.Schema.Type<typeof PostRowSchema>;
|
|
|
62
64
|
export type PostRoute<A, E, D = E, AI = A, EI = E, DI = EI> = Readonly<{
|
|
63
65
|
/** Fixed registry label shared by routes to one recipient namespace. */
|
|
64
66
|
addressKind: string;
|
|
67
|
+
/** Optional SQLite Post lifetime of 1 ms to 7 days from admission; relational adapters reject it. */
|
|
68
|
+
deadlineMs?: number | undefined;
|
|
65
69
|
/** Address parser for this kind. */
|
|
66
70
|
address: Schema.Codec<A, AI>;
|
|
67
71
|
/** Staged event parser for this kind. */
|
|
@@ -104,6 +108,8 @@ export type PostRoute<A, E, D = E, AI = A, EI = E, DI = EI> = Readonly<{
|
|
|
104
108
|
export type PostRegistryRoute = Readonly<{
|
|
105
109
|
/** Recipient namespace identity used for per-address ordering. */
|
|
106
110
|
addressKind: string;
|
|
111
|
+
/** Optional SQLite Post lifetime of 1 ms to 7 days from admission; relational adapters reject it. */
|
|
112
|
+
deadlineMs?: number | undefined;
|
|
107
113
|
/** Parser for this address kind. */
|
|
108
114
|
address: Schema.Codec<unknown, unknown>;
|
|
109
115
|
/** Parser for this event kind. */
|
|
@@ -147,11 +153,13 @@ export type PostRuntime<R extends Record<string, PostRegistryRoute>> = Readonly<
|
|
|
147
153
|
}>>;
|
|
148
154
|
/** Recover settled delivery jobs and enqueue due retries after a wake. */
|
|
149
155
|
enqueueDue: (limit?: number, afterKey?: string) => Promise<string | undefined>;
|
|
150
|
-
/**
|
|
156
|
+
/** Release settled pending retries from backoff without resetting attempts. */
|
|
157
|
+
releaseNow: (limit?: number, afterKey?: string) => Promise<string | undefined>;
|
|
158
|
+
/** Read an ordered page of refused, exhausted or expired keys for operators. */
|
|
151
159
|
deadLetters: (limit?: number, afterKey?: string) => Readonly<{
|
|
152
160
|
letters: readonly Readonly<{
|
|
153
161
|
key: string;
|
|
154
|
-
reason: "refused" | "exhausted";
|
|
162
|
+
reason: "refused" | "exhausted" | "expired";
|
|
155
163
|
envelope: PostEnvelope;
|
|
156
164
|
attempts: number;
|
|
157
165
|
}>[];
|
package/dist/post.js
CHANGED
|
@@ -3,11 +3,13 @@ import { PostError, } from "./internal/post-contract.js";
|
|
|
3
3
|
export { PostError, PostDeliveryResultSchema, } from "./internal/post-contract.js";
|
|
4
4
|
import { deliverPost, postDisposition, deliveryReceipt, } from "./internal/post-execution.js";
|
|
5
5
|
import { defineIdFamily } from "@fungi.computer/cairn";
|
|
6
|
-
import {
|
|
7
|
-
|
|
6
|
+
import { backoffDelay } from "@fungi.computer/almanac";
|
|
7
|
+
import { maxDeliveryAttempts, postRetryPolicy } from "./post-outbox.js";
|
|
8
|
+
export { makePostOutbox, postOutboxPending } from "./post-outbox.js";
|
|
8
9
|
const Key = Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(256));
|
|
9
10
|
const JobPayload = Schema.Struct({ postKey: Key });
|
|
10
11
|
const PageLimit = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(64));
|
|
12
|
+
const maxRouteDeadlineMs = 7 * 24 * 60 * 60 * 1_000;
|
|
11
13
|
const ids = defineIdFamily({
|
|
12
14
|
family: "post",
|
|
13
15
|
version: 1,
|
|
@@ -27,6 +29,7 @@ export const PostRowSchema = Schema.fromJsonString(Schema.Union([
|
|
|
27
29
|
envelope: PostEnvelopeSchema,
|
|
28
30
|
attempts: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
29
31
|
nextAttemptAt: Schema.Number.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
32
|
+
deadlineAt: Schema.optional(Schema.Number.check(Schema.isGreaterThanOrEqualTo(0))),
|
|
30
33
|
jobId: Schema.optional(Key),
|
|
31
34
|
delivery: Schema.optional(PostEnvelopeSchema),
|
|
32
35
|
nextKey: Schema.optional(Key),
|
|
@@ -40,7 +43,8 @@ export const PostRowSchema = Schema.fromJsonString(Schema.Union([
|
|
|
40
43
|
state: Schema.Literal("dead"),
|
|
41
44
|
envelope: PostEnvelopeSchema,
|
|
42
45
|
attempts: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
43
|
-
reason: Schema.Literals(["refused", "exhausted"]),
|
|
46
|
+
reason: Schema.Literals(["refused", "exhausted", "expired"]),
|
|
47
|
+
deadlineAt: Schema.optional(Schema.Number.check(Schema.isGreaterThanOrEqualTo(0))),
|
|
44
48
|
receipt: Schema.optional(Schema.Json),
|
|
45
49
|
delivery: Schema.optional(PostEnvelopeSchema),
|
|
46
50
|
nextKey: Schema.optional(Key),
|
|
@@ -78,6 +82,8 @@ export function definePostRoute(route) {
|
|
|
78
82
|
? receiptOperations(route.address, route.event, route)
|
|
79
83
|
: receiptOperations(route.address, route.prepare.event, route);
|
|
80
84
|
const prepare = route.prepare;
|
|
85
|
+
if (route.deadlineMs !== undefined)
|
|
86
|
+
Schema.decodeUnknownSync(Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: maxRouteDeadlineMs })))(route.deadlineMs);
|
|
81
87
|
return {
|
|
82
88
|
...route,
|
|
83
89
|
...delivery,
|
|
@@ -178,12 +184,16 @@ export function createPost(options) {
|
|
|
178
184
|
const queue = queueOf(envelope);
|
|
179
185
|
const previousKey = store.read(tailKey(queue));
|
|
180
186
|
const previous = previousKey === undefined ? undefined : readRow(previousKey);
|
|
181
|
-
const
|
|
187
|
+
const admittedAt = options.now();
|
|
188
|
+
const baseRow = {
|
|
182
189
|
state: "pending",
|
|
183
190
|
envelope,
|
|
184
191
|
attempts: 0,
|
|
185
|
-
nextAttemptAt:
|
|
192
|
+
nextAttemptAt: admittedAt,
|
|
186
193
|
};
|
|
194
|
+
const row = route.deadlineMs === undefined
|
|
195
|
+
? baseRow
|
|
196
|
+
: { ...baseRow, deadlineAt: admittedAt + route.deadlineMs };
|
|
187
197
|
putRow(envelope.key, row);
|
|
188
198
|
if (previousKey !== undefined && previous?.state === "pending") {
|
|
189
199
|
putRow(previousKey, { ...previous, nextKey: envelope.key });
|
|
@@ -227,8 +237,13 @@ export function createPost(options) {
|
|
|
227
237
|
envelope: row.envelope,
|
|
228
238
|
attempts: row.attempts,
|
|
229
239
|
};
|
|
240
|
+
const settled = row.deadlineAt === undefined
|
|
241
|
+
? dead
|
|
242
|
+
: { ...dead, deadlineAt: row.deadlineAt };
|
|
230
243
|
receipt =
|
|
231
|
-
row.delivery === undefined
|
|
244
|
+
row.delivery === undefined
|
|
245
|
+
? settled
|
|
246
|
+
: { ...settled, delivery: row.delivery };
|
|
232
247
|
}
|
|
233
248
|
else
|
|
234
249
|
receipt = terminal;
|
|
@@ -247,12 +262,16 @@ export function createPost(options) {
|
|
|
247
262
|
throw new PostError("delivery row is missing");
|
|
248
263
|
if (row.state !== "pending" || row.jobId !== job.jobId)
|
|
249
264
|
return undefined;
|
|
250
|
-
if (row.
|
|
265
|
+
if (row.deadlineAt !== undefined && options.now() >= row.deadlineAt) {
|
|
266
|
+
finish(postKey, row, { state: "dead", reason: "expired" });
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
if (row.deadlineAt === undefined && row.attempts >= maxDeliveryAttempts) {
|
|
251
270
|
finish(postKey, row, { state: "dead", reason: "exhausted" });
|
|
252
271
|
return undefined;
|
|
253
272
|
}
|
|
254
273
|
const attempts = row.attempts + 1;
|
|
255
|
-
const nextAttemptAt = options.now() +
|
|
274
|
+
const nextAttemptAt = Math.min(options.now() + backoffDelay(attempts, postRetryPolicy), row.deadlineAt ?? Number.POSITIVE_INFINITY);
|
|
256
275
|
const started = { ...row, attempts, nextAttemptAt };
|
|
257
276
|
// Preserve recovery evidence before invoking the receiver. A failed receipt
|
|
258
277
|
// write leaves this row available for retry after Watchdog settles the job.
|
|
@@ -281,7 +300,11 @@ export function createPost(options) {
|
|
|
281
300
|
const current = readRow(postKey);
|
|
282
301
|
if (current?.state !== "pending" || current.jobId !== job.jobId)
|
|
283
302
|
return;
|
|
284
|
-
const disposition = postDisposition(evidence, route, current.attempts, maxDeliveryAttempts
|
|
303
|
+
const disposition = postDisposition(evidence, route, current.attempts, maxDeliveryAttempts, {
|
|
304
|
+
deadlineExpired: current.deadlineAt !== undefined &&
|
|
305
|
+
options.now() >= current.deadlineAt,
|
|
306
|
+
retryUntilDeadline: current.deadlineAt !== undefined,
|
|
307
|
+
});
|
|
285
308
|
if (disposition.state === "keep")
|
|
286
309
|
return;
|
|
287
310
|
if (disposition.state === "delivered") {
|
|
@@ -298,10 +321,16 @@ export function createPost(options) {
|
|
|
298
321
|
finish(postKey, current, disposition, deliveryReceipt(evidence));
|
|
299
322
|
}
|
|
300
323
|
else {
|
|
301
|
-
const
|
|
302
|
-
if (
|
|
303
|
-
|
|
304
|
-
|
|
324
|
+
const now = options.now();
|
|
325
|
+
if (current.deadlineAt !== undefined && now >= current.deadlineAt) {
|
|
326
|
+
finish(postKey, current, { state: "dead", reason: "expired" });
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
const due = Math.min(now + backoffDelay(current.attempts, postRetryPolicy), current.deadlineAt ?? Number.POSITIVE_INFINITY);
|
|
330
|
+
if (due !== current.nextAttemptAt) {
|
|
331
|
+
putRow(postKey, { ...current, nextAttemptAt: due });
|
|
332
|
+
retryAt = due;
|
|
333
|
+
}
|
|
305
334
|
}
|
|
306
335
|
}
|
|
307
336
|
});
|
|
@@ -329,7 +358,14 @@ export function createPost(options) {
|
|
|
329
358
|
const current = readRow(row.envelope.key);
|
|
330
359
|
if (current?.state !== "pending" || current.jobId !== row.jobId)
|
|
331
360
|
return;
|
|
332
|
-
if (current.
|
|
361
|
+
if (current.deadlineAt !== undefined &&
|
|
362
|
+
options.now() >= current.deadlineAt)
|
|
363
|
+
finish(current.envelope.key, current, {
|
|
364
|
+
state: "dead",
|
|
365
|
+
reason: "expired",
|
|
366
|
+
});
|
|
367
|
+
else if (current.deadlineAt === undefined &&
|
|
368
|
+
current.attempts >= maxDeliveryAttempts)
|
|
333
369
|
finish(current.envelope.key, current, {
|
|
334
370
|
state: "dead",
|
|
335
371
|
reason: "exhausted",
|
|
@@ -369,13 +405,16 @@ export function createPost(options) {
|
|
|
369
405
|
enqueueJob(pending);
|
|
370
406
|
}
|
|
371
407
|
else {
|
|
372
|
-
const
|
|
408
|
+
const retry = {
|
|
373
409
|
state: "pending",
|
|
374
410
|
envelope: current.envelope,
|
|
375
411
|
attempts: 0,
|
|
376
412
|
nextAttemptAt: options.now(),
|
|
377
413
|
delivery: current.delivery,
|
|
378
414
|
};
|
|
415
|
+
const pending = current.deadlineAt === undefined
|
|
416
|
+
? retry
|
|
417
|
+
: { ...retry, deadlineAt: current.deadlineAt };
|
|
379
418
|
putRow(key, pending);
|
|
380
419
|
const queue = queueOf(pending.envelope);
|
|
381
420
|
const previousKey = store.read(tailKey(queue));
|
|
@@ -391,6 +430,41 @@ export function createPost(options) {
|
|
|
391
430
|
});
|
|
392
431
|
await options.scheduleWake(options.now());
|
|
393
432
|
};
|
|
433
|
+
const releasePendingRow = async (row, now) => {
|
|
434
|
+
if (row.state !== "pending" ||
|
|
435
|
+
row.jobId === undefined ||
|
|
436
|
+
row.nextAttemptAt <= now)
|
|
437
|
+
return false;
|
|
438
|
+
const work = await options.readJob(row.jobId);
|
|
439
|
+
if (work.status === "missing")
|
|
440
|
+
throw new PostError("delivery job is missing");
|
|
441
|
+
if (work.job.state !== "settled")
|
|
442
|
+
return false;
|
|
443
|
+
return store.commit(() => {
|
|
444
|
+
const current = readRow(row.envelope.key);
|
|
445
|
+
if (current?.state !== "pending" ||
|
|
446
|
+
current.jobId !== row.jobId ||
|
|
447
|
+
current.nextAttemptAt <= now)
|
|
448
|
+
return false;
|
|
449
|
+
const pending = { ...current, nextAttemptAt: now };
|
|
450
|
+
putRow(row.envelope.key, pending);
|
|
451
|
+
enqueueJob(pending);
|
|
452
|
+
return true;
|
|
453
|
+
});
|
|
454
|
+
};
|
|
455
|
+
const releaseNow = async (limit = 64, afterKey) => {
|
|
456
|
+
const pageSize = Schema.decodeUnknownSync(PageLimit)(limit);
|
|
457
|
+
const rows = store.list(pageSize, afterKey);
|
|
458
|
+
const now = options.now();
|
|
459
|
+
let released = false;
|
|
460
|
+
for (const stored of rows) {
|
|
461
|
+
const row = Schema.decodeUnknownSync(PostRowSchema)(stored.value);
|
|
462
|
+
released = (await releasePendingRow(row, now)) || released;
|
|
463
|
+
}
|
|
464
|
+
if (released)
|
|
465
|
+
await options.scheduleWake(now);
|
|
466
|
+
return rows.length === pageSize ? rows.at(-1)?.key : undefined;
|
|
467
|
+
};
|
|
394
468
|
const deadLetters = (limit = 64, afterKey) => {
|
|
395
469
|
const pageSize = Schema.decodeUnknownSync(PageLimit)(limit);
|
|
396
470
|
const rows = store.list(pageSize, afterKey);
|
|
@@ -418,6 +492,7 @@ export function createPost(options) {
|
|
|
418
492
|
retry,
|
|
419
493
|
execute,
|
|
420
494
|
enqueueDue,
|
|
495
|
+
releaseNow,
|
|
421
496
|
deadLetters,
|
|
422
497
|
read: readRow,
|
|
423
498
|
nextWake,
|
package/docs/api/inspection.md
CHANGED
|
@@ -157,7 +157,7 @@ const PostReceiptSummarySchema: Union<
|
|
|
157
157
|
state: Literal<"dead">;
|
|
158
158
|
kind: String;
|
|
159
159
|
attempts: Int;
|
|
160
|
-
reason: Literals<readonly ["refused", "exhausted"]>;
|
|
160
|
+
reason: Literals<readonly ["refused", "exhausted", "expired"]>;
|
|
161
161
|
}>,
|
|
162
162
|
]
|
|
163
163
|
>;
|
|
@@ -188,7 +188,7 @@ const PostInspectionPageSchema: Union<readonly [Struct<{
|
|
|
188
188
|
state: Literal<"dead">;
|
|
189
189
|
kind: String;
|
|
190
190
|
attempts: Int;
|
|
191
|
-
reason: Literals<readonly [..., ...]>;
|
|
191
|
+
reason: Literals<readonly [..., ..., ...]>;
|
|
192
192
|
}>]>>;
|
|
193
193
|
nextCursor: NullOr<String>;
|
|
194
194
|
}>]>;
|
|
@@ -275,7 +275,7 @@ jobs or arming a wake. The trusted host must admit an operator before calling.
|
|
|
275
275
|
|
|
276
276
|
#### Returns
|
|
277
277
|
|
|
278
|
-
| Name | Type
|
|
279
|
-
| --------- |
|
|
280
|
-
| `email()` | (`input`) => \| \{ `status`: `"uninitialized"`; \} \| \{ `status`: `"available"`; `records`: readonly `object`[]; `nextCursor`: `string` \| `null`; \}
|
|
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. |
|
|
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`: `"expired"` \| `"refused"` \| `"exhausted"`; \})[]; `nextCursor`: `string` \| `null`; \} | Read Post metadata; filtered pages retain their continuation through other states. |
|
package/docs/api/post-d1.md
CHANGED
|
@@ -114,14 +114,13 @@ Carrier-owned D1 schema, applied by the deployment migration owner.
|
|
|
114
114
|
function createD1Post<R>(options): D1Post;
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
-
Bind the Post registry to
|
|
118
|
-
engine.
|
|
117
|
+
Bind the Post registry to D1; route deadlines are rejected with PostError.
|
|
119
118
|
|
|
120
119
|
#### Type Parameters
|
|
121
120
|
|
|
122
|
-
| Type Parameter
|
|
123
|
-
|
|
|
124
|
-
| `R` _extends_ `Record`\<`string`, `Readonly`\<\{ `addressKind`: `string`; `address`: `Codec`\<`unknown`, `unknown`\>; `event`: `Codec`\<`unknown`, `unknown`\>; `encodeEnvelope`: (`envelope`) => `object`; `parseDelivery`: (`envelope`) => `object`; `prepareEnvelope?`: (`envelope`, `signal`) => `Promise`\<\{ `key`: `string`; `kind`: `string`; `address`: `unknown`; `event`: `unknown`; \}\>; `recordDelivered?`: (`envelope`) => `void`; `nameOf`: (`envelope`) => `string`; `namespace`: `Readonly`\<\{ `getByName`: (`name`) => `Readonly`\<\{ `deliver`: (`envelope`) => `Promise`\<[`PostDeliveryResult`](post.md#postdeliveryresult)\>; \}\>; \}\>; `recordCursor?`: (`envelope`, `cursor`) => `void`; `recordRefused?`: (`envelope`) => `void`; \}\>\> |
|
|
121
|
+
| Type Parameter |
|
|
122
|
+
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
123
|
+
| `R` _extends_ `Record`\<`string`, `Readonly`\<\{ `addressKind`: `string`; `deadlineMs?`: `number`; `address`: `Codec`\<`unknown`, `unknown`\>; `event`: `Codec`\<`unknown`, `unknown`\>; `encodeEnvelope`: (`envelope`) => `object`; `parseDelivery`: (`envelope`) => `object`; `prepareEnvelope?`: (`envelope`, `signal`) => `Promise`\<\{ `key`: `string`; `kind`: `string`; `address`: `unknown`; `event`: `unknown`; \}\>; `recordDelivered?`: (`envelope`) => `void`; `nameOf`: (`envelope`) => `string`; `namespace`: `Readonly`\<\{ `getByName`: (`name`) => `Readonly`\<\{ `deliver`: (`envelope`) => `Promise`\<[`PostDeliveryResult`](post.md#postdeliveryresult)\>; \}\>; \}\>; `recordCursor?`: (`envelope`, `cursor`) => `void`; `recordRefused?`: (`envelope`) => `void`; \}\>\> |
|
|
125
124
|
|
|
126
125
|
#### Parameters
|
|
127
126
|
|
|
@@ -52,13 +52,14 @@ Carrier-owned PostgreSQL schema, applied by the deployment migration owner.
|
|
|
52
52
|
function createPostgresPost<R>(options): PostgresPost;
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
Bind the Post registry to
|
|
55
|
+
Bind the Post registry to PostgreSQL; route deadlines are rejected with
|
|
56
|
+
PostError.
|
|
56
57
|
|
|
57
58
|
#### Type Parameters
|
|
58
59
|
|
|
59
|
-
| Type Parameter
|
|
60
|
-
|
|
|
61
|
-
| `R` _extends_ `Record`\<`string`, `Readonly`\<\{ `addressKind`: `string`; `address`: `Codec`\<`unknown`, `unknown`\>; `event`: `Codec`\<`unknown`, `unknown`\>; `encodeEnvelope`: (`envelope`) => `object`; `parseDelivery`: (`envelope`) => `object`; `prepareEnvelope?`: (`envelope`, `signal`) => `Promise`\<\{ `key`: `string`; `kind`: `string`; `address`: `unknown`; `event`: `unknown`; \}\>; `recordDelivered?`: (`envelope`) => `void`; `nameOf`: (`envelope`) => `string`; `namespace`: `Readonly`\<\{ `getByName`: (`name`) => `Readonly`\<\{ `deliver`: (`envelope`) => `Promise`\<[`PostDeliveryResult`](post.md#postdeliveryresult)\>; \}\>; \}\>; `recordCursor?`: (`envelope`, `cursor`) => `void`; `recordRefused?`: (`envelope`) => `void`; \}\>\> |
|
|
60
|
+
| Type Parameter |
|
|
61
|
+
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
62
|
+
| `R` _extends_ `Record`\<`string`, `Readonly`\<\{ `addressKind`: `string`; `deadlineMs?`: `number`; `address`: `Codec`\<`unknown`, `unknown`\>; `event`: `Codec`\<`unknown`, `unknown`\>; `encodeEnvelope`: (`envelope`) => `object`; `parseDelivery`: (`envelope`) => `object`; `prepareEnvelope?`: (`envelope`, `signal`) => `Promise`\<\{ `key`: `string`; `kind`: `string`; `address`: `unknown`; `event`: `unknown`; \}\>; `recordDelivered?`: (`envelope`) => `void`; `nameOf`: (`envelope`) => `string`; `namespace`: `Readonly`\<\{ `getByName`: (`name`) => `Readonly`\<\{ `deliver`: (`envelope`) => `Promise`\<[`PostDeliveryResult`](post.md#postdeliveryresult)\>; \}\>; \}\>; `recordCursor?`: (`envelope`, `cursor`) => `void`; `recordRefused?`: (`envelope`) => `void`; \}\>\> |
|
|
62
63
|
|
|
63
64
|
#### Parameters
|
|
64
65
|
|
package/docs/api/post.md
CHANGED
|
@@ -12,26 +12,27 @@
|
|
|
12
12
|
|
|
13
13
|
### PostError
|
|
14
14
|
|
|
15
|
-
Post rejected an unknown route or
|
|
15
|
+
Post rejected an unknown route, conflicting key, or unsupported policy.
|
|
16
16
|
|
|
17
17
|
#### Extends
|
|
18
18
|
|
|
19
19
|
- `YieldableError`\<`this`\> & `object` & `Readonly`\<\{ `message`: `string`;
|
|
20
|
-
\}\>
|
|
20
|
+
`reason?`: `"relational-deadline-unsupported"`; \}\>
|
|
21
21
|
|
|
22
22
|
#### Constructors
|
|
23
23
|
|
|
24
24
|
##### Constructor
|
|
25
25
|
|
|
26
26
|
```ts
|
|
27
|
-
new PostError(message): PostError;
|
|
27
|
+
new PostError(message, reason?): PostError;
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
###### Parameters
|
|
31
31
|
|
|
32
|
-
| Parameter | Type
|
|
33
|
-
| --------- |
|
|
34
|
-
| `message` | `string`
|
|
32
|
+
| Parameter | Type |
|
|
33
|
+
| --------- | ----------------------------------- |
|
|
34
|
+
| `message` | `string` |
|
|
35
|
+
| `reason?` | `"relational-deadline-unsupported"` |
|
|
35
36
|
|
|
36
37
|
###### Returns
|
|
37
38
|
|
|
@@ -42,11 +43,24 @@ new PostError(message): PostError;
|
|
|
42
43
|
```ts
|
|
43
44
|
Data.TaggedError("PostError")<{
|
|
44
45
|
readonly message: string;
|
|
46
|
+
readonly reason?: "relational-deadline-unsupported";
|
|
45
47
|
}>.constructor
|
|
46
48
|
```
|
|
47
49
|
|
|
48
50
|
#### Properties
|
|
49
51
|
|
|
52
|
+
##### reason?
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
readonly optional reason?: "relational-deadline-unsupported";
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
###### Inherited from
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
Data.TaggedError("PostError").reason;
|
|
62
|
+
```
|
|
63
|
+
|
|
50
64
|
##### message
|
|
51
65
|
|
|
52
66
|
```ts
|
|
@@ -118,6 +132,7 @@ Durable, string-backed rows. A delivered receipt must outlive source replay.
|
|
|
118
132
|
type PostOutbox<M, E> = Readonly<{
|
|
119
133
|
enqueue: (message, at?) => Effect.Effect<number | undefined, E>;
|
|
120
134
|
drain: () => Effect.Effect<void, E>;
|
|
135
|
+
nextWakeAt: () => Effect.Effect<number | undefined, E>;
|
|
121
136
|
deadLetters: (limit, afterKey?) => Effect.Effect<M[], E>;
|
|
122
137
|
}>;
|
|
123
138
|
```
|
|
@@ -158,6 +173,7 @@ Pending row, terminal receipt, or dead letter.
|
|
|
158
173
|
```ts
|
|
159
174
|
type PostRoute<A, E, D, AI, EI, DI> = Readonly<{
|
|
160
175
|
addressKind: string;
|
|
176
|
+
deadlineMs?: number;
|
|
161
177
|
address: Schema.Codec<A, AI>;
|
|
162
178
|
event: Schema.Codec<E, EI>;
|
|
163
179
|
name: (address) => string;
|
|
@@ -204,6 +220,7 @@ Schema-correlated ordinary events or prepared delivery facts.
|
|
|
204
220
|
```ts
|
|
205
221
|
type PostRegistryRoute = Readonly<{
|
|
206
222
|
addressKind: string;
|
|
223
|
+
deadlineMs?: number;
|
|
207
224
|
address: Schema.Codec<unknown, unknown>;
|
|
208
225
|
event: Schema.Codec<unknown, unknown>;
|
|
209
226
|
encodeEnvelope: (envelope) => PostEnvelope;
|
|
@@ -232,13 +249,14 @@ type PostRuntime<R> = Readonly<{
|
|
|
232
249
|
}>
|
|
233
250
|
>;
|
|
234
251
|
enqueueDue: (limit?, afterKey?) => Promise<string | undefined>;
|
|
252
|
+
releaseNow: (limit?, afterKey?) => Promise<string | undefined>;
|
|
235
253
|
deadLetters: (
|
|
236
254
|
limit?,
|
|
237
255
|
afterKey?,
|
|
238
256
|
) => Readonly<{
|
|
239
257
|
letters: readonly Readonly<{
|
|
240
258
|
key: string;
|
|
241
|
-
reason: "refused" | "exhausted";
|
|
259
|
+
reason: "refused" | "exhausted" | "expired";
|
|
242
260
|
envelope: PostEnvelope;
|
|
243
261
|
attempts: number;
|
|
244
262
|
}>[];
|
|
@@ -322,6 +340,7 @@ const PostRowSchema: fromJsonString<
|
|
|
322
340
|
}>;
|
|
323
341
|
attempts: Int;
|
|
324
342
|
nextAttemptAt: Number;
|
|
343
|
+
deadlineAt: optional<Number>;
|
|
325
344
|
jobId: optional<String>;
|
|
326
345
|
delivery: optional<
|
|
327
346
|
Struct<{
|
|
@@ -347,7 +366,8 @@ const PostRowSchema: fromJsonString<
|
|
|
347
366
|
event: Unknown;
|
|
348
367
|
}>;
|
|
349
368
|
attempts: Int;
|
|
350
|
-
reason: Literals<readonly ["refused", "exhausted"]>;
|
|
369
|
+
reason: Literals<readonly ["refused", "exhausted", "expired"]>;
|
|
370
|
+
deadlineAt: optional<Number>;
|
|
351
371
|
receipt: optional<Codec<Json, Json, never, never>>;
|
|
352
372
|
delivery: optional<
|
|
353
373
|
Struct<{
|
|
@@ -368,33 +388,42 @@ Persisted Post row parser.
|
|
|
368
388
|
|
|
369
389
|
## Functions
|
|
370
390
|
|
|
371
|
-
###
|
|
391
|
+
### makePostOutbox()
|
|
372
392
|
|
|
373
393
|
```ts
|
|
374
|
-
function
|
|
394
|
+
function makePostOutbox<M, E>(options): PostOutbox<M, E>;
|
|
375
395
|
```
|
|
376
396
|
|
|
377
|
-
|
|
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` |
|
|
378
405
|
|
|
379
406
|
#### Parameters
|
|
380
407
|
|
|
381
|
-
| Parameter
|
|
382
|
-
|
|
|
383
|
-
| `
|
|
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`; \}\> |
|
|
384
411
|
|
|
385
412
|
#### Returns
|
|
386
413
|
|
|
387
|
-
`
|
|
414
|
+
[`PostOutbox`](#postoutbox)\<`M`, `E`\>
|
|
388
415
|
|
|
389
416
|
---
|
|
390
417
|
|
|
391
|
-
###
|
|
418
|
+
### postOutboxPending()
|
|
392
419
|
|
|
393
420
|
```ts
|
|
394
|
-
function
|
|
421
|
+
function postOutboxPending<M, E>(options): Effect<boolean, E>;
|
|
395
422
|
```
|
|
396
423
|
|
|
397
|
-
|
|
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.
|
|
398
427
|
|
|
399
428
|
#### Type Parameters
|
|
400
429
|
|
|
@@ -405,13 +434,13 @@ The same bounded delivery and receipt machinery serves Post and BirdDog.
|
|
|
405
434
|
|
|
406
435
|
#### Parameters
|
|
407
436
|
|
|
408
|
-
| Parameter | Type
|
|
409
|
-
| --------- |
|
|
410
|
-
| `options` | `Readonly`\<\{ `message`: `Schema.Codec`\<`M`\>; `
|
|
437
|
+
| Parameter | Type |
|
|
438
|
+
| --------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
439
|
+
| `options` | `Readonly`\<\{ `message`: `Schema.Codec`\<`M`\>; `outbox`: `Pick`\<[`PostOutboxStore`](#postoutboxstore)\<`E`\>, `"list"`\>; \}\> |
|
|
411
440
|
|
|
412
441
|
#### Returns
|
|
413
442
|
|
|
414
|
-
|
|
443
|
+
`Effect`\<`boolean`, `E`\>
|
|
415
444
|
|
|
416
445
|
---
|
|
417
446
|
|
|
@@ -422,6 +451,7 @@ The same bounded delivery and receipt machinery serves Post and BirdDog.
|
|
|
422
451
|
```ts
|
|
423
452
|
function definePostRoute<A, E, AI, EI>(route): PostRoute<A, E, E, AI, EI, EI> & Omit<Readonly<{
|
|
424
453
|
addressKind: string;
|
|
454
|
+
deadlineMs?: number;
|
|
425
455
|
address: Codec<unknown, unknown>;
|
|
426
456
|
event: Codec<unknown, unknown>;
|
|
427
457
|
encodeEnvelope: (envelope) => object;
|
|
@@ -457,29 +487,30 @@ Preserve each route's inferred address and event types in a registry.
|
|
|
457
487
|
|
|
458
488
|
##### Parameters
|
|
459
489
|
|
|
460
|
-
| Parameter | Type
|
|
461
|
-
| --------- |
|
|
462
|
-
| `route` | `Readonly`\<\{ `addressKind`: `string`; `address`: `Codec`\<`A`, `AI`\>; `event`: `Codec`\<`E`, `EI`\>; `name`: (`address`) => `string`; `namespace`: `Readonly`\<\{ `getByName`: (`name`) => `Readonly`\<\{ `deliver`: (`envelope`) => `Promise`\<[`PostDeliveryResult`](#postdeliveryresult)\>; \}\>; \}\>; \}\> & `Readonly`\<\{ `prepare?`: `undefined`; `onDelivered?`: (`address`, `event`) => `void`; `onCursor?`: (`address`, `event`, `cursor`) => `void`; `onRefused?`: (`address`, `event`) => `void`; \}\> |
|
|
490
|
+
| Parameter | Type |
|
|
491
|
+
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
492
|
+
| `route` | `Readonly`\<\{ `addressKind`: `string`; `deadlineMs?`: `number`; `address`: `Codec`\<`A`, `AI`\>; `event`: `Codec`\<`E`, `EI`\>; `name`: (`address`) => `string`; `namespace`: `Readonly`\<\{ `getByName`: (`name`) => `Readonly`\<\{ `deliver`: (`envelope`) => `Promise`\<[`PostDeliveryResult`](#postdeliveryresult)\>; \}\>; \}\>; \}\> & `Readonly`\<\{ `prepare?`: `undefined`; `onDelivered?`: (`address`, `event`) => `void`; `onCursor?`: (`address`, `event`, `cursor`) => `void`; `onRefused?`: (`address`, `event`) => `void`; \}\> |
|
|
463
493
|
|
|
464
494
|
##### Returns
|
|
465
495
|
|
|
466
496
|
[`PostRoute`](#postroute)\<`A`, `E`, `E`, `AI`, `EI`, `EI`\> &
|
|
467
|
-
`Omit`\<`Readonly`\<\{ `addressKind`: `string`; `
|
|
468
|
-
`
|
|
469
|
-
|
|
470
|
-
`prepareEnvelope?`: (`envelope`, `signal`) =>
|
|
471
|
-
`
|
|
472
|
-
`recordDelivered?`: (`envelope`) => `void`; `nameOf`:
|
|
473
|
-
`namespace`: `Readonly`\<\{ `getByName`: (`name`) =>
|
|
474
|
-
(`envelope`) => `Promise`\<...\>; \}\>; \}\>;
|
|
475
|
-
`
|
|
476
|
-
\| `"event"`\>
|
|
497
|
+
`Omit`\<`Readonly`\<\{ `addressKind`: `string`; `deadlineMs?`: `number`;
|
|
498
|
+
`address`: `Codec`\<`unknown`, `unknown`\>; `event`: `Codec`\<`unknown`,
|
|
499
|
+
`unknown`\>; `encodeEnvelope`: (`envelope`) => `object`; `parseDelivery`:
|
|
500
|
+
(`envelope`) => `object`; `prepareEnvelope?`: (`envelope`, `signal`) =>
|
|
501
|
+
`Promise`\<\{ `key`: `string`; `kind`: `string`; `address`: `unknown`; `event`:
|
|
502
|
+
`unknown`; \}\>; `recordDelivered?`: (`envelope`) => `void`; `nameOf`:
|
|
503
|
+
(`envelope`) => `string`; `namespace`: `Readonly`\<\{ `getByName`: (`name`) =>
|
|
504
|
+
`Readonly`\<\{ `deliver`: (`envelope`) => `Promise`\<...\>; \}\>; \}\>;
|
|
505
|
+
`recordCursor?`: (`envelope`, `cursor`) => `void`; `recordRefused?`:
|
|
506
|
+
(`envelope`) => `void`; \}\>, `"address"` \| `"event"`\>
|
|
477
507
|
|
|
478
508
|
#### Call Signature
|
|
479
509
|
|
|
480
510
|
```ts
|
|
481
511
|
function definePostRoute<A, E, D, AI, EI, DI>(route): PostRoute<A, E, D, AI, EI, DI> & Omit<Readonly<{
|
|
482
512
|
addressKind: string;
|
|
513
|
+
deadlineMs?: number;
|
|
483
514
|
address: Codec<unknown, unknown>;
|
|
484
515
|
event: Codec<unknown, unknown>;
|
|
485
516
|
encodeEnvelope: (envelope) => object;
|
|
@@ -517,54 +548,23 @@ Preserve each route's inferred address and event types in a registry.
|
|
|
517
548
|
|
|
518
549
|
##### Parameters
|
|
519
550
|
|
|
520
|
-
| Parameter | Type
|
|
521
|
-
| --------- |
|
|
522
|
-
| `route` | `Readonly`\<\{ `addressKind`: `string`; `address`: `Codec`\<`A`, `AI`\>; `event`: `Codec`\<`E`, `EI`\>; `name`: (`address`) => `string`; `namespace`: `Readonly`\<\{ `getByName`: (`name`) => `Readonly`\<\{ `deliver`: (`envelope`) => `Promise`\<[`PostDeliveryResult`](#postdeliveryresult)\>; \}\>; \}\>; \}\> & `Readonly`\<\{ `prepare`: `Readonly`\<\{ `event`: `Schema.Codec`\<`D`, `DI`\>; `run`: (`address`, `intent`, `signal`) => `Promise`\<`D`\>; \}\>; `onDelivered?`: (`address`, `event`) => `void`; `onCursor?`: (`address`, `event`, `cursor`) => `void`; `onRefused?`: (`address`, `event`) => `void`; \}\> |
|
|
551
|
+
| Parameter | Type |
|
|
552
|
+
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
553
|
+
| `route` | `Readonly`\<\{ `addressKind`: `string`; `deadlineMs?`: `number`; `address`: `Codec`\<`A`, `AI`\>; `event`: `Codec`\<`E`, `EI`\>; `name`: (`address`) => `string`; `namespace`: `Readonly`\<\{ `getByName`: (`name`) => `Readonly`\<\{ `deliver`: (`envelope`) => `Promise`\<[`PostDeliveryResult`](#postdeliveryresult)\>; \}\>; \}\>; \}\> & `Readonly`\<\{ `prepare`: `Readonly`\<\{ `event`: `Schema.Codec`\<`D`, `DI`\>; `run`: (`address`, `intent`, `signal`) => `Promise`\<`D`\>; \}\>; `onDelivered?`: (`address`, `event`) => `void`; `onCursor?`: (`address`, `event`, `cursor`) => `void`; `onRefused?`: (`address`, `event`) => `void`; \}\> |
|
|
523
554
|
|
|
524
555
|
##### Returns
|
|
525
556
|
|
|
526
557
|
[`PostRoute`](#postroute)\<`A`, `E`, `D`, `AI`, `EI`, `DI`\> &
|
|
527
|
-
`Omit`\<`Readonly`\<\{ `addressKind`: `string`; `
|
|
528
|
-
`
|
|
529
|
-
|
|
530
|
-
`prepareEnvelope?`: (`envelope`, `signal`) =>
|
|
531
|
-
`
|
|
532
|
-
`recordDelivered?`: (`envelope`) => `void`; `nameOf`:
|
|
533
|
-
`namespace`: `Readonly`\<\{ `getByName`: (`name`) =>
|
|
534
|
-
(`envelope`) => `Promise`\<...\>; \}\>; \}\>;
|
|
535
|
-
`
|
|
536
|
-
\| `"event"`\>
|
|
537
|
-
|
|
538
|
-
---
|
|
539
|
-
|
|
540
|
-
### migratePostEvents()
|
|
541
|
-
|
|
542
|
-
```ts
|
|
543
|
-
function migratePostEvents<A, AI, B, BI>(options): void;
|
|
544
|
-
```
|
|
545
|
-
|
|
546
|
-
Migrate persisted ordinary events once, before opening their producer. Carrier
|
|
547
|
-
preserves delivery evidence, ordering and Watchdog job identities. A failed
|
|
548
|
-
decoder or transform rolls back both rows and the migration receipt.
|
|
549
|
-
|
|
550
|
-
#### Type Parameters
|
|
551
|
-
|
|
552
|
-
| Type Parameter |
|
|
553
|
-
| -------------- |
|
|
554
|
-
| `A` |
|
|
555
|
-
| `AI` |
|
|
556
|
-
| `B` |
|
|
557
|
-
| `BI` |
|
|
558
|
-
|
|
559
|
-
#### Parameters
|
|
560
|
-
|
|
561
|
-
| Parameter | Type |
|
|
562
|
-
| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
563
|
-
| `options` | `Readonly`\<\{ `owner`: `WatchdogSqliteOwner`; `id`: `string`; `kind`: `string`; `toKind?`: `string`; `from`: `Schema.Codec`\<`A`, `AI`\>; `to`: `Schema.Codec`\<`B`, `BI`\>; `migrate`: (`event`) => `B`; \}\> |
|
|
564
|
-
|
|
565
|
-
#### Returns
|
|
566
|
-
|
|
567
|
-
`void`
|
|
558
|
+
`Omit`\<`Readonly`\<\{ `addressKind`: `string`; `deadlineMs?`: `number`;
|
|
559
|
+
`address`: `Codec`\<`unknown`, `unknown`\>; `event`: `Codec`\<`unknown`,
|
|
560
|
+
`unknown`\>; `encodeEnvelope`: (`envelope`) => `object`; `parseDelivery`:
|
|
561
|
+
(`envelope`) => `object`; `prepareEnvelope?`: (`envelope`, `signal`) =>
|
|
562
|
+
`Promise`\<\{ `key`: `string`; `kind`: `string`; `address`: `unknown`; `event`:
|
|
563
|
+
`unknown`; \}\>; `recordDelivered?`: (`envelope`) => `void`; `nameOf`:
|
|
564
|
+
(`envelope`) => `string`; `namespace`: `Readonly`\<\{ `getByName`: (`name`) =>
|
|
565
|
+
`Readonly`\<\{ `deliver`: (`envelope`) => `Promise`\<...\>; \}\>; \}\>;
|
|
566
|
+
`recordCursor?`: (`envelope`, `cursor`) => `void`; `recordRefused?`:
|
|
567
|
+
(`envelope`) => `void`; \}\>, `"address"` \| `"event"`\>
|
|
568
568
|
|
|
569
569
|
---
|
|
570
570
|
|
|
@@ -578,9 +578,9 @@ Decode at both persisted and DO boundaries with the route's Effect Schema.
|
|
|
578
578
|
|
|
579
579
|
#### Type Parameters
|
|
580
580
|
|
|
581
|
-
| Type Parameter
|
|
582
|
-
|
|
|
583
|
-
| `R` _extends_ `Record`\<`string`, `Readonly`\<\{ `addressKind`: `string`; `address`: `Codec`\<`unknown`, `unknown`\>; `event`: `Codec`\<`unknown`, `unknown`\>; `encodeEnvelope`: (`envelope`) => `object`; `parseDelivery`: (`envelope`) => `object`; `prepareEnvelope?`: (`envelope`, `signal`) => `Promise`\<\{ `key`: `string`; `kind`: `string`; `address`: `unknown`; `event`: `unknown`; \}\>; `recordDelivered?`: (`envelope`) => `void`; `nameOf`: (`envelope`) => `string`; `namespace`: `Readonly`\<\{ `getByName`: (`name`) => `Readonly`\<\{ `deliver`: (`envelope`) => `Promise`\<[`PostDeliveryResult`](#postdeliveryresult)\>; \}\>; \}\>; `recordCursor?`: (`envelope`, `cursor`) => `void`; `recordRefused?`: (`envelope`) => `void`; \}\>\> |
|
|
581
|
+
| Type Parameter |
|
|
582
|
+
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
583
|
+
| `R` _extends_ `Record`\<`string`, `Readonly`\<\{ `addressKind`: `string`; `deadlineMs?`: `number`; `address`: `Codec`\<`unknown`, `unknown`\>; `event`: `Codec`\<`unknown`, `unknown`\>; `encodeEnvelope`: (`envelope`) => `object`; `parseDelivery`: (`envelope`) => `object`; `prepareEnvelope?`: (`envelope`, `signal`) => `Promise`\<\{ `key`: `string`; `kind`: `string`; `address`: `unknown`; `event`: `unknown`; \}\>; `recordDelivered?`: (`envelope`) => `void`; `nameOf`: (`envelope`) => `string`; `namespace`: `Readonly`\<\{ `getByName`: (`name`) => `Readonly`\<\{ `deliver`: (`envelope`) => `Promise`\<[`PostDeliveryResult`](#postdeliveryresult)\>; \}\>; \}\>; `recordCursor?`: (`envelope`, `cursor`) => `void`; `recordRefused?`: (`envelope`) => `void`; \}\>\> |
|
|
584
584
|
|
|
585
585
|
#### Parameters
|
|
586
586
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fungi.computer/carrier",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"author": "Levi <levi@technomancy.dev>",
|
|
5
5
|
"description": "Durable email submission and recipient delivery tracking",
|
|
6
6
|
"type": "module",
|
|
@@ -48,8 +48,9 @@
|
|
|
48
48
|
],
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@cloudflare/workers-types": "5.20260828.1",
|
|
51
|
-
"@fungi.computer/
|
|
52
|
-
"@fungi.computer/
|
|
51
|
+
"@fungi.computer/almanac": "0.2.0",
|
|
52
|
+
"@fungi.computer/cairn": "0.1.0",
|
|
53
|
+
"@fungi.computer/watchdog": "0.1.0",
|
|
53
54
|
"effect": "4.0.2",
|
|
54
55
|
"pg": "8.23.0",
|
|
55
56
|
"@types/pg": "8.23.1"
|