@fungi.computer/carrier 0.1.0 → 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 +25 -14
- 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 +2 -0
- package/dist/post-d1.d.ts +1 -1
- package/dist/post-d1.js +1 -1
- package/dist/post-postgres.d.ts +1 -1
- package/dist/post-postgres.js +1 -1
- package/dist/post.d.ts +11 -3
- package/dist/post.js +87 -13
- 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 +58 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -215,20 +215,26 @@ ambiguous first dispatch has zero. Post `attempts` counts receiver invocations.
|
|
|
215
215
|
|
|
216
216
|
## Post
|
|
217
217
|
|
|
218
|
-
Post uses Almanac's shared pure backoff calculation
|
|
218
|
+
Post's ordinary retry policy uses Almanac's shared pure backoff calculation: 2
|
|
219
219
|
seconds initially, doubling to a 5-minute cap, with at most 20 receiver
|
|
220
220
|
attempts. The synchronous and SQL adapters share that default policy; explicit
|
|
221
|
-
SQL retry policies retain their own delays.
|
|
222
|
-
|
|
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.
|
|
223
224
|
|
|
224
225
|
`@fungi.computer/carrier/post` delivers typed, point-to-point envelopes between
|
|
225
226
|
named Durable Objects. `definePostRoute` pairs each address schema and event
|
|
226
227
|
schema with its Durable Object namespace, name function and fixed `addressKind`
|
|
227
|
-
label.
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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.
|
|
232
238
|
|
|
233
239
|
Construct `createPost` with the producer's structural SQLite `owner`, typed
|
|
234
240
|
`routes`, Watchdog's `transactional` projection and `readJob` operation, a
|
|
@@ -265,9 +271,10 @@ acknowledge a domain ledger in the same producer transaction as the Post
|
|
|
265
271
|
receipt. An acknowledgment failure leaves both pending for retry. Prepared facts
|
|
266
272
|
should carry sealed grants, never raw credentials.
|
|
267
273
|
|
|
268
|
-
A definite refusal
|
|
269
|
-
its reason and its attempt count. `deadLetters` returns
|
|
270
|
-
`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.
|
|
271
278
|
|
|
272
279
|
The same retry and receipt machinery is exported as `makePostOutbox` for a
|
|
273
280
|
string-backed outbox. [BirdDog](../bird-dog/README.md) uses it for agent reply
|
|
@@ -280,9 +287,13 @@ malformed rows do not request a wake.
|
|
|
280
287
|
## Post transactions on D1 and PostgreSQL
|
|
281
288
|
|
|
282
289
|
`@fungi.computer/carrier/post-d1` and `/post-postgres` bind the ordinary Post
|
|
283
|
-
address and event registry to Watchdog's database adapters.
|
|
284
|
-
|
|
285
|
-
|
|
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.
|
|
286
297
|
|
|
287
298
|
On D1, `post.send(envelope)` returns the envelope and job statements. Include
|
|
288
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;
|
|
@@ -128,6 +128,8 @@ function publicRow(stored) {
|
|
|
128
128
|
};
|
|
129
129
|
}
|
|
130
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");
|
|
131
133
|
const policy = Schema.decodeUnknownSync(Schema.Struct({
|
|
132
134
|
maxAttempts: Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: maxDeliveryAttempts })),
|
|
133
135
|
baseDelayMs: Schema.Int.check(Schema.isGreaterThan(0)),
|
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-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
|
@@ -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
|
@@ -9,6 +9,7 @@ export { makePostOutbox, postOutboxPending } from "./post-outbox.js";
|
|
|
9
9
|
const Key = Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(256));
|
|
10
10
|
const JobPayload = Schema.Struct({ postKey: Key });
|
|
11
11
|
const PageLimit = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(64));
|
|
12
|
+
const maxRouteDeadlineMs = 7 * 24 * 60 * 60 * 1_000;
|
|
12
13
|
const ids = defineIdFamily({
|
|
13
14
|
family: "post",
|
|
14
15
|
version: 1,
|
|
@@ -28,6 +29,7 @@ export const PostRowSchema = Schema.fromJsonString(Schema.Union([
|
|
|
28
29
|
envelope: PostEnvelopeSchema,
|
|
29
30
|
attempts: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
30
31
|
nextAttemptAt: Schema.Number.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
32
|
+
deadlineAt: Schema.optional(Schema.Number.check(Schema.isGreaterThanOrEqualTo(0))),
|
|
31
33
|
jobId: Schema.optional(Key),
|
|
32
34
|
delivery: Schema.optional(PostEnvelopeSchema),
|
|
33
35
|
nextKey: Schema.optional(Key),
|
|
@@ -41,7 +43,8 @@ export const PostRowSchema = Schema.fromJsonString(Schema.Union([
|
|
|
41
43
|
state: Schema.Literal("dead"),
|
|
42
44
|
envelope: PostEnvelopeSchema,
|
|
43
45
|
attempts: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
44
|
-
reason: Schema.Literals(["refused", "exhausted"]),
|
|
46
|
+
reason: Schema.Literals(["refused", "exhausted", "expired"]),
|
|
47
|
+
deadlineAt: Schema.optional(Schema.Number.check(Schema.isGreaterThanOrEqualTo(0))),
|
|
45
48
|
receipt: Schema.optional(Schema.Json),
|
|
46
49
|
delivery: Schema.optional(PostEnvelopeSchema),
|
|
47
50
|
nextKey: Schema.optional(Key),
|
|
@@ -79,6 +82,8 @@ export function definePostRoute(route) {
|
|
|
79
82
|
? receiptOperations(route.address, route.event, route)
|
|
80
83
|
: receiptOperations(route.address, route.prepare.event, route);
|
|
81
84
|
const prepare = route.prepare;
|
|
85
|
+
if (route.deadlineMs !== undefined)
|
|
86
|
+
Schema.decodeUnknownSync(Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: maxRouteDeadlineMs })))(route.deadlineMs);
|
|
82
87
|
return {
|
|
83
88
|
...route,
|
|
84
89
|
...delivery,
|
|
@@ -179,12 +184,16 @@ export function createPost(options) {
|
|
|
179
184
|
const queue = queueOf(envelope);
|
|
180
185
|
const previousKey = store.read(tailKey(queue));
|
|
181
186
|
const previous = previousKey === undefined ? undefined : readRow(previousKey);
|
|
182
|
-
const
|
|
187
|
+
const admittedAt = options.now();
|
|
188
|
+
const baseRow = {
|
|
183
189
|
state: "pending",
|
|
184
190
|
envelope,
|
|
185
191
|
attempts: 0,
|
|
186
|
-
nextAttemptAt:
|
|
192
|
+
nextAttemptAt: admittedAt,
|
|
187
193
|
};
|
|
194
|
+
const row = route.deadlineMs === undefined
|
|
195
|
+
? baseRow
|
|
196
|
+
: { ...baseRow, deadlineAt: admittedAt + route.deadlineMs };
|
|
188
197
|
putRow(envelope.key, row);
|
|
189
198
|
if (previousKey !== undefined && previous?.state === "pending") {
|
|
190
199
|
putRow(previousKey, { ...previous, nextKey: envelope.key });
|
|
@@ -228,8 +237,13 @@ export function createPost(options) {
|
|
|
228
237
|
envelope: row.envelope,
|
|
229
238
|
attempts: row.attempts,
|
|
230
239
|
};
|
|
240
|
+
const settled = row.deadlineAt === undefined
|
|
241
|
+
? dead
|
|
242
|
+
: { ...dead, deadlineAt: row.deadlineAt };
|
|
231
243
|
receipt =
|
|
232
|
-
row.delivery === undefined
|
|
244
|
+
row.delivery === undefined
|
|
245
|
+
? settled
|
|
246
|
+
: { ...settled, delivery: row.delivery };
|
|
233
247
|
}
|
|
234
248
|
else
|
|
235
249
|
receipt = terminal;
|
|
@@ -248,12 +262,16 @@ export function createPost(options) {
|
|
|
248
262
|
throw new PostError("delivery row is missing");
|
|
249
263
|
if (row.state !== "pending" || row.jobId !== job.jobId)
|
|
250
264
|
return undefined;
|
|
251
|
-
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) {
|
|
252
270
|
finish(postKey, row, { state: "dead", reason: "exhausted" });
|
|
253
271
|
return undefined;
|
|
254
272
|
}
|
|
255
273
|
const attempts = row.attempts + 1;
|
|
256
|
-
const nextAttemptAt = options.now() + backoffDelay(attempts, postRetryPolicy);
|
|
274
|
+
const nextAttemptAt = Math.min(options.now() + backoffDelay(attempts, postRetryPolicy), row.deadlineAt ?? Number.POSITIVE_INFINITY);
|
|
257
275
|
const started = { ...row, attempts, nextAttemptAt };
|
|
258
276
|
// Preserve recovery evidence before invoking the receiver. A failed receipt
|
|
259
277
|
// write leaves this row available for retry after Watchdog settles the job.
|
|
@@ -282,7 +300,11 @@ export function createPost(options) {
|
|
|
282
300
|
const current = readRow(postKey);
|
|
283
301
|
if (current?.state !== "pending" || current.jobId !== job.jobId)
|
|
284
302
|
return;
|
|
285
|
-
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
|
+
});
|
|
286
308
|
if (disposition.state === "keep")
|
|
287
309
|
return;
|
|
288
310
|
if (disposition.state === "delivered") {
|
|
@@ -299,10 +321,16 @@ export function createPost(options) {
|
|
|
299
321
|
finish(postKey, current, disposition, deliveryReceipt(evidence));
|
|
300
322
|
}
|
|
301
323
|
else {
|
|
302
|
-
const
|
|
303
|
-
if (
|
|
304
|
-
|
|
305
|
-
|
|
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
|
+
}
|
|
306
334
|
}
|
|
307
335
|
}
|
|
308
336
|
});
|
|
@@ -330,7 +358,14 @@ export function createPost(options) {
|
|
|
330
358
|
const current = readRow(row.envelope.key);
|
|
331
359
|
if (current?.state !== "pending" || current.jobId !== row.jobId)
|
|
332
360
|
return;
|
|
333
|
-
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)
|
|
334
369
|
finish(current.envelope.key, current, {
|
|
335
370
|
state: "dead",
|
|
336
371
|
reason: "exhausted",
|
|
@@ -370,13 +405,16 @@ export function createPost(options) {
|
|
|
370
405
|
enqueueJob(pending);
|
|
371
406
|
}
|
|
372
407
|
else {
|
|
373
|
-
const
|
|
408
|
+
const retry = {
|
|
374
409
|
state: "pending",
|
|
375
410
|
envelope: current.envelope,
|
|
376
411
|
attempts: 0,
|
|
377
412
|
nextAttemptAt: options.now(),
|
|
378
413
|
delivery: current.delivery,
|
|
379
414
|
};
|
|
415
|
+
const pending = current.deadlineAt === undefined
|
|
416
|
+
? retry
|
|
417
|
+
: { ...retry, deadlineAt: current.deadlineAt };
|
|
380
418
|
putRow(key, pending);
|
|
381
419
|
const queue = queueOf(pending.envelope);
|
|
382
420
|
const previousKey = store.read(tailKey(queue));
|
|
@@ -392,6 +430,41 @@ export function createPost(options) {
|
|
|
392
430
|
});
|
|
393
431
|
await options.scheduleWake(options.now());
|
|
394
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
|
+
};
|
|
395
468
|
const deadLetters = (limit = 64, afterKey) => {
|
|
396
469
|
const pageSize = Schema.decodeUnknownSync(PageLimit)(limit);
|
|
397
470
|
const rows = store.list(pageSize, afterKey);
|
|
@@ -419,6 +492,7 @@ export function createPost(options) {
|
|
|
419
492
|
retry,
|
|
420
493
|
execute,
|
|
421
494
|
enqueueDue,
|
|
495
|
+
releaseNow,
|
|
422
496
|
deadLetters,
|
|
423
497
|
read: readRow,
|
|
424
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
|
|
@@ -159,6 +173,7 @@ Pending row, terminal receipt, or dead letter.
|
|
|
159
173
|
```ts
|
|
160
174
|
type PostRoute<A, E, D, AI, EI, DI> = Readonly<{
|
|
161
175
|
addressKind: string;
|
|
176
|
+
deadlineMs?: number;
|
|
162
177
|
address: Schema.Codec<A, AI>;
|
|
163
178
|
event: Schema.Codec<E, EI>;
|
|
164
179
|
name: (address) => string;
|
|
@@ -205,6 +220,7 @@ Schema-correlated ordinary events or prepared delivery facts.
|
|
|
205
220
|
```ts
|
|
206
221
|
type PostRegistryRoute = Readonly<{
|
|
207
222
|
addressKind: string;
|
|
223
|
+
deadlineMs?: number;
|
|
208
224
|
address: Schema.Codec<unknown, unknown>;
|
|
209
225
|
event: Schema.Codec<unknown, unknown>;
|
|
210
226
|
encodeEnvelope: (envelope) => PostEnvelope;
|
|
@@ -233,13 +249,14 @@ type PostRuntime<R> = Readonly<{
|
|
|
233
249
|
}>
|
|
234
250
|
>;
|
|
235
251
|
enqueueDue: (limit?, afterKey?) => Promise<string | undefined>;
|
|
252
|
+
releaseNow: (limit?, afterKey?) => Promise<string | undefined>;
|
|
236
253
|
deadLetters: (
|
|
237
254
|
limit?,
|
|
238
255
|
afterKey?,
|
|
239
256
|
) => Readonly<{
|
|
240
257
|
letters: readonly Readonly<{
|
|
241
258
|
key: string;
|
|
242
|
-
reason: "refused" | "exhausted";
|
|
259
|
+
reason: "refused" | "exhausted" | "expired";
|
|
243
260
|
envelope: PostEnvelope;
|
|
244
261
|
attempts: number;
|
|
245
262
|
}>[];
|
|
@@ -323,6 +340,7 @@ const PostRowSchema: fromJsonString<
|
|
|
323
340
|
}>;
|
|
324
341
|
attempts: Int;
|
|
325
342
|
nextAttemptAt: Number;
|
|
343
|
+
deadlineAt: optional<Number>;
|
|
326
344
|
jobId: optional<String>;
|
|
327
345
|
delivery: optional<
|
|
328
346
|
Struct<{
|
|
@@ -348,7 +366,8 @@ const PostRowSchema: fromJsonString<
|
|
|
348
366
|
event: Unknown;
|
|
349
367
|
}>;
|
|
350
368
|
attempts: Int;
|
|
351
|
-
reason: Literals<readonly ["refused", "exhausted"]>;
|
|
369
|
+
reason: Literals<readonly ["refused", "exhausted", "expired"]>;
|
|
370
|
+
deadlineAt: optional<Number>;
|
|
352
371
|
receipt: optional<Codec<Json, Json, never, never>>;
|
|
353
372
|
delivery: optional<
|
|
354
373
|
Struct<{
|
|
@@ -432,6 +451,7 @@ may let their owner rest.
|
|
|
432
451
|
```ts
|
|
433
452
|
function definePostRoute<A, E, AI, EI>(route): PostRoute<A, E, E, AI, EI, EI> & Omit<Readonly<{
|
|
434
453
|
addressKind: string;
|
|
454
|
+
deadlineMs?: number;
|
|
435
455
|
address: Codec<unknown, unknown>;
|
|
436
456
|
event: Codec<unknown, unknown>;
|
|
437
457
|
encodeEnvelope: (envelope) => object;
|
|
@@ -467,29 +487,30 @@ Preserve each route's inferred address and event types in a registry.
|
|
|
467
487
|
|
|
468
488
|
##### Parameters
|
|
469
489
|
|
|
470
|
-
| Parameter | Type
|
|
471
|
-
| --------- |
|
|
472
|
-
| `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`; \}\> |
|
|
473
493
|
|
|
474
494
|
##### Returns
|
|
475
495
|
|
|
476
496
|
[`PostRoute`](#postroute)\<`A`, `E`, `E`, `AI`, `EI`, `EI`\> &
|
|
477
|
-
`Omit`\<`Readonly`\<\{ `addressKind`: `string`; `
|
|
478
|
-
`
|
|
479
|
-
|
|
480
|
-
`prepareEnvelope?`: (`envelope`, `signal`) =>
|
|
481
|
-
`
|
|
482
|
-
`recordDelivered?`: (`envelope`) => `void`; `nameOf`:
|
|
483
|
-
`namespace`: `Readonly`\<\{ `getByName`: (`name`) =>
|
|
484
|
-
(`envelope`) => `Promise`\<...\>; \}\>; \}\>;
|
|
485
|
-
`
|
|
486
|
-
\| `"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"`\>
|
|
487
507
|
|
|
488
508
|
#### Call Signature
|
|
489
509
|
|
|
490
510
|
```ts
|
|
491
511
|
function definePostRoute<A, E, D, AI, EI, DI>(route): PostRoute<A, E, D, AI, EI, DI> & Omit<Readonly<{
|
|
492
512
|
addressKind: string;
|
|
513
|
+
deadlineMs?: number;
|
|
493
514
|
address: Codec<unknown, unknown>;
|
|
494
515
|
event: Codec<unknown, unknown>;
|
|
495
516
|
encodeEnvelope: (envelope) => object;
|
|
@@ -527,23 +548,23 @@ Preserve each route's inferred address and event types in a registry.
|
|
|
527
548
|
|
|
528
549
|
##### Parameters
|
|
529
550
|
|
|
530
|
-
| Parameter | Type
|
|
531
|
-
| --------- |
|
|
532
|
-
| `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`; \}\> |
|
|
533
554
|
|
|
534
555
|
##### Returns
|
|
535
556
|
|
|
536
557
|
[`PostRoute`](#postroute)\<`A`, `E`, `D`, `AI`, `EI`, `DI`\> &
|
|
537
|
-
`Omit`\<`Readonly`\<\{ `addressKind`: `string`; `
|
|
538
|
-
`
|
|
539
|
-
|
|
540
|
-
`prepareEnvelope?`: (`envelope`, `signal`) =>
|
|
541
|
-
`
|
|
542
|
-
`recordDelivered?`: (`envelope`) => `void`; `nameOf`:
|
|
543
|
-
`namespace`: `Readonly`\<\{ `getByName`: (`name`) =>
|
|
544
|
-
(`envelope`) => `Promise`\<...\>; \}\>; \}\>;
|
|
545
|
-
`
|
|
546
|
-
\| `"event"`\>
|
|
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"`\>
|
|
547
568
|
|
|
548
569
|
---
|
|
549
570
|
|
|
@@ -557,9 +578,9 @@ Decode at both persisted and DO boundaries with the route's Effect Schema.
|
|
|
557
578
|
|
|
558
579
|
#### Type Parameters
|
|
559
580
|
|
|
560
|
-
| Type Parameter
|
|
561
|
-
|
|
|
562
|
-
| `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`; \}\>\> |
|
|
563
584
|
|
|
564
585
|
#### Parameters
|
|
565
586
|
|