@fungi.computer/carrier 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +468 -2
- package/dist/cloudflare.d.ts +32 -0
- package/dist/cloudflare.js +80 -0
- package/dist/email.d.ts +2 -0
- package/dist/email.js +2 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +28 -0
- package/dist/inspection.d.ts +108 -0
- package/dist/inspection.js +188 -0
- package/dist/internal/cloudflare-send.d.ts +9 -0
- package/dist/internal/cloudflare-send.js +79 -0
- package/dist/internal/contracts.d.ts +94 -0
- package/dist/internal/contracts.js +7 -0
- package/dist/internal/owner.d.ts +24 -0
- package/dist/internal/owner.js +419 -0
- package/dist/internal/policy.d.ts +25 -0
- package/dist/internal/policy.js +18 -0
- package/dist/internal/post-contract.d.ts +27 -0
- package/dist/internal/post-contract.js +22 -0
- package/dist/internal/post-execution.d.ts +45 -0
- package/dist/internal/post-execution.js +73 -0
- package/dist/internal/post-sql.d.ts +51 -0
- package/dist/internal/post-sql.js +259 -0
- package/dist/internal/receipt-schema.d.ts +3 -0
- package/dist/internal/receipt-schema.js +12 -0
- package/dist/post-d1.d.ts +36 -0
- package/dist/post-d1.js +55 -0
- package/dist/post-outbox.d.ts +52 -0
- package/dist/post-outbox.js +152 -0
- package/dist/post-postgres.d.ts +25 -0
- package/dist/post-postgres.js +79 -0
- package/dist/post.d.ts +189 -0
- package/dist/post.js +437 -0
- package/dist/testing.d.ts +16 -0
- package/dist/testing.js +20 -0
- package/docs/api/README.md +20 -0
- package/docs/api/cloudflare.md +100 -0
- package/docs/api/email.md +87 -0
- package/docs/api/index.md +343 -0
- package/docs/api/inspection.md +281 -0
- package/docs/api/post-d1.md +184 -0
- package/docs/api/post-postgres.md +108 -0
- package/docs/api/post.md +599 -0
- package/docs/api/testing.md +46 -0
- package/package.json +95 -3
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
import { purgePostgresQueues, } from "@fungi.computer/watchdog/postgres";
|
|
3
|
+
import { jobSelect, openSqlPost, postSchema, preparePost, } from "./internal/post-sql.js";
|
|
4
|
+
/** Carrier-owned PostgreSQL schema, applied by the deployment migration owner. */
|
|
5
|
+
export const postPostgresSchema = postSchema(true);
|
|
6
|
+
const Job = Schema.Struct({
|
|
7
|
+
job_id: Schema.String,
|
|
8
|
+
queue_name: Schema.String,
|
|
9
|
+
lane: Schema.String,
|
|
10
|
+
priority: Schema.Number,
|
|
11
|
+
payload_json: Schema.fromJsonString(Schema.Unknown),
|
|
12
|
+
});
|
|
13
|
+
/** Bind the Post registry to a PostgreSQL owner and the shared delivery engine. */
|
|
14
|
+
export function createPostgresPost(options) {
|
|
15
|
+
const enqueue = async (client, source) => {
|
|
16
|
+
const native = source;
|
|
17
|
+
const rows = (await client.query(native.text, [...native.values]))
|
|
18
|
+
.rows;
|
|
19
|
+
for (const raw of rows) {
|
|
20
|
+
const job = Schema.decodeUnknownSync(Job)(raw);
|
|
21
|
+
await options.watchdog.enqueue(client, {
|
|
22
|
+
jobId: job.job_id,
|
|
23
|
+
queue: job.queue_name,
|
|
24
|
+
lane: job.lane,
|
|
25
|
+
priority: job.priority,
|
|
26
|
+
payload: job.payload_json,
|
|
27
|
+
state: "queued",
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const runtime = openSqlPost({
|
|
32
|
+
query: async (statement) => (await options.database.query(statement.text, [
|
|
33
|
+
...statement.values,
|
|
34
|
+
])).rows,
|
|
35
|
+
commit: async (statements, key, jobId) => {
|
|
36
|
+
const client = await options.database.connect();
|
|
37
|
+
try {
|
|
38
|
+
await client.query("BEGIN");
|
|
39
|
+
for (const statement of statements)
|
|
40
|
+
await client.query(statement.text, [...statement.values]);
|
|
41
|
+
await enqueue(client, jobSelect(key, true, jobId));
|
|
42
|
+
await client.query("COMMIT");
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
await client.query("ROLLBACK");
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
client.release();
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
}, options);
|
|
53
|
+
return {
|
|
54
|
+
...runtime,
|
|
55
|
+
send: async (client, envelope) => {
|
|
56
|
+
const prepared = preparePost(options, envelope);
|
|
57
|
+
await client.query(prepared.statement.text, [
|
|
58
|
+
...prepared.statement.values,
|
|
59
|
+
]);
|
|
60
|
+
await enqueue(client, jobSelect(prepared.key, true));
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** Purge selected recipient queues, including all envelopes and attempts.
|
|
65
|
+
* The trusted SELECT yields queue_name from producer/envelope facts, not job state. */
|
|
66
|
+
export function purgePostgresPost(source) {
|
|
67
|
+
return [
|
|
68
|
+
// Serialize erasure with send/retry before taking the jobs' deletion snapshot.
|
|
69
|
+
{
|
|
70
|
+
text: `UPDATE carrier_post_rows SET post_key=post_key WHERE queue_name IN (SELECT queue_name FROM (${source.text}) AS erasure)`,
|
|
71
|
+
values: source.values,
|
|
72
|
+
},
|
|
73
|
+
...purgePostgresQueues(source),
|
|
74
|
+
{
|
|
75
|
+
text: `DELETE FROM carrier_post_rows WHERE queue_name IN (SELECT queue_name FROM (${source.text}) AS erasure)`,
|
|
76
|
+
values: source.values,
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
}
|
package/dist/post.d.ts
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
import { type PostDeliveryResult } from "./internal/post-contract.js";
|
|
3
|
+
export { PostError, PostDeliveryResultSchema, } from "./internal/post-contract.js";
|
|
4
|
+
export type { PostDeliveryResult } from "./internal/post-contract.js";
|
|
5
|
+
import type { WatchdogRunningJob, WatchdogSqliteOwner, WatchdogRuntime, WatchdogTransactionalProjection } from "@fungi.computer/watchdog";
|
|
6
|
+
export { makePostOutbox, postOutboxPending } from "./post-outbox.js";
|
|
7
|
+
export type { PostOutbox, PostOutboxStore } from "./post-outbox.js";
|
|
8
|
+
/** Wire envelope parsed at the durable row and receiver boundaries. */
|
|
9
|
+
export declare const PostEnvelopeSchema: Schema.Struct<{
|
|
10
|
+
readonly key: Schema.String;
|
|
11
|
+
readonly kind: Schema.String;
|
|
12
|
+
readonly address: Schema.Unknown;
|
|
13
|
+
readonly event: Schema.Unknown;
|
|
14
|
+
}>;
|
|
15
|
+
/** Persisted Post row parser. */
|
|
16
|
+
export declare const PostRowSchema: Schema.fromJsonString<Schema.Union<readonly [Schema.Struct<{
|
|
17
|
+
readonly state: Schema.Literal<"pending">;
|
|
18
|
+
readonly envelope: Schema.Struct<{
|
|
19
|
+
readonly key: Schema.String;
|
|
20
|
+
readonly kind: Schema.String;
|
|
21
|
+
readonly address: Schema.Unknown;
|
|
22
|
+
readonly event: Schema.Unknown;
|
|
23
|
+
}>;
|
|
24
|
+
readonly attempts: Schema.Int;
|
|
25
|
+
readonly nextAttemptAt: Schema.Number;
|
|
26
|
+
readonly jobId: Schema.optional<Schema.String>;
|
|
27
|
+
readonly delivery: Schema.optional<Schema.Struct<{
|
|
28
|
+
readonly key: Schema.String;
|
|
29
|
+
readonly kind: Schema.String;
|
|
30
|
+
readonly address: Schema.Unknown;
|
|
31
|
+
readonly event: Schema.Unknown;
|
|
32
|
+
}>>;
|
|
33
|
+
readonly nextKey: Schema.optional<Schema.String>;
|
|
34
|
+
}>, Schema.Struct<{
|
|
35
|
+
readonly state: Schema.Literal<"delivered">;
|
|
36
|
+
readonly receipt: Schema.optional<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
|
|
37
|
+
readonly nextKey: Schema.optional<Schema.String>;
|
|
38
|
+
}>, Schema.Struct<{
|
|
39
|
+
readonly state: Schema.Literal<"dead">;
|
|
40
|
+
readonly envelope: Schema.Struct<{
|
|
41
|
+
readonly key: Schema.String;
|
|
42
|
+
readonly kind: Schema.String;
|
|
43
|
+
readonly address: Schema.Unknown;
|
|
44
|
+
readonly event: Schema.Unknown;
|
|
45
|
+
}>;
|
|
46
|
+
readonly attempts: Schema.Int;
|
|
47
|
+
readonly reason: Schema.Literals<readonly ["refused", "exhausted"]>;
|
|
48
|
+
readonly receipt: Schema.optional<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
|
|
49
|
+
readonly delivery: Schema.optional<Schema.Struct<{
|
|
50
|
+
readonly key: Schema.String;
|
|
51
|
+
readonly kind: Schema.String;
|
|
52
|
+
readonly address: Schema.Unknown;
|
|
53
|
+
readonly event: Schema.Unknown;
|
|
54
|
+
}>>;
|
|
55
|
+
readonly nextKey: Schema.optional<Schema.String>;
|
|
56
|
+
}>]>>;
|
|
57
|
+
/** One addressed event identified by the producer's stable key. */
|
|
58
|
+
export type PostEnvelope = Schema.Schema.Type<typeof PostEnvelopeSchema>;
|
|
59
|
+
/** Pending row, terminal receipt, or dead letter. */
|
|
60
|
+
export type PostRow = Schema.Schema.Type<typeof PostRowSchema>;
|
|
61
|
+
/** Schema-correlated ordinary events or prepared delivery facts. */
|
|
62
|
+
export type PostRoute<A, E, D = E, AI = A, EI = E, DI = EI> = Readonly<{
|
|
63
|
+
/** Fixed registry label shared by routes to one recipient namespace. */
|
|
64
|
+
addressKind: string;
|
|
65
|
+
/** Address parser for this kind. */
|
|
66
|
+
address: Schema.Codec<A, AI>;
|
|
67
|
+
/** Staged event parser for this kind. */
|
|
68
|
+
event: Schema.Codec<E, EI>;
|
|
69
|
+
/** Stable Durable Object name for one address. */
|
|
70
|
+
name: (address: A) => string;
|
|
71
|
+
/** Durable Object namespace for this kind. */
|
|
72
|
+
namespace: Readonly<{
|
|
73
|
+
/** Obtain the addressed Durable Object. */
|
|
74
|
+
getByName: (name: string) => Readonly<{
|
|
75
|
+
/** Idempotently apply one addressed envelope. */
|
|
76
|
+
deliver: (envelope: PostEnvelope) => Promise<PostDeliveryResult>;
|
|
77
|
+
}>;
|
|
78
|
+
}>;
|
|
79
|
+
}> & (Readonly<{
|
|
80
|
+
/** Prepare one fact before receiver I/O; its callbacks consume that fact. */
|
|
81
|
+
prepare: Readonly<{
|
|
82
|
+
/** Parser for the prepared delivery fact. */
|
|
83
|
+
event: Schema.Codec<D, DI>;
|
|
84
|
+
/** Resolve the staged intent into a delivery fact. */
|
|
85
|
+
run: (address: A, intent: E, signal: AbortSignal) => Promise<D>;
|
|
86
|
+
}>;
|
|
87
|
+
/** Acknowledge delivery in the producer's receipt transaction. */
|
|
88
|
+
onDelivered?: (address: A, event: D) => void;
|
|
89
|
+
/** Commit the receiver cursor in the producer's receipt transaction. */
|
|
90
|
+
onCursor?: (address: A, event: D, cursor: number) => void;
|
|
91
|
+
/** Revoke a subscription after permanent refusal. */
|
|
92
|
+
onRefused?: (address: A, event: D) => void;
|
|
93
|
+
}> | Readonly<{
|
|
94
|
+
/** An ordinary event is also its delivery fact. */
|
|
95
|
+
prepare?: undefined;
|
|
96
|
+
/** Acknowledge delivery in the producer's receipt transaction. */
|
|
97
|
+
onDelivered?: (address: A, event: E) => void;
|
|
98
|
+
/** Commit the receiver cursor in the producer's receipt transaction. */
|
|
99
|
+
onCursor?: (address: A, event: E, cursor: number) => void;
|
|
100
|
+
/** Revoke a subscription after permanent refusal. */
|
|
101
|
+
onRefused?: (address: A, event: E) => void;
|
|
102
|
+
}>);
|
|
103
|
+
/** Runtime operations that preserve the route's schema evidence. */
|
|
104
|
+
export type PostRegistryRoute = Readonly<{
|
|
105
|
+
/** Recipient namespace identity used for per-address ordering. */
|
|
106
|
+
addressKind: string;
|
|
107
|
+
/** Parser for this address kind. */
|
|
108
|
+
address: Schema.Codec<unknown, unknown>;
|
|
109
|
+
/** Parser for this event kind. */
|
|
110
|
+
event: Schema.Codec<unknown, unknown>;
|
|
111
|
+
/** Encode a producer's domain values with this route's schemas. */
|
|
112
|
+
encodeEnvelope: (envelope: PostEnvelope) => PostEnvelope;
|
|
113
|
+
/** Decode a prepared wire fact, or the ordinary event when no preparation is needed. */
|
|
114
|
+
parseDelivery: (envelope: PostEnvelope) => PostEnvelope;
|
|
115
|
+
/** Prepare the wire fact before receiver I/O. */
|
|
116
|
+
prepareEnvelope?: ((envelope: PostEnvelope, signal: AbortSignal) => Promise<PostEnvelope>) | undefined;
|
|
117
|
+
/** Acknowledge producer domain state in the receipt transaction. */
|
|
118
|
+
recordDelivered?: ((envelope: PostEnvelope) => void) | undefined;
|
|
119
|
+
/** Decode the address and return its stable Durable Object name. */
|
|
120
|
+
nameOf: (envelope: Pick<PostEnvelope, "address">) => string;
|
|
121
|
+
/** Durable Object namespace. */
|
|
122
|
+
namespace: PostRoute<never, never>["namespace"];
|
|
123
|
+
/** Apply a typed receiver cursor in the producer transaction. */
|
|
124
|
+
recordCursor?: ((envelope: PostEnvelope, cursor: number) => void) | undefined;
|
|
125
|
+
/** Apply a typed permanent refusal in the producer transaction. */
|
|
126
|
+
recordRefused?: ((envelope: PostEnvelope) => void) | undefined;
|
|
127
|
+
}>;
|
|
128
|
+
/** Preserve each route's inferred address and event types in a registry. */
|
|
129
|
+
export declare function definePostRoute<A, E, AI = A, EI = E>(route: Extract<PostRoute<A, E, E, AI, EI, EI>, {
|
|
130
|
+
prepare?: undefined;
|
|
131
|
+
}>): PostRoute<A, E, E, AI, EI, EI> & Omit<PostRegistryRoute, "address" | "event">;
|
|
132
|
+
export declare function definePostRoute<A, E, D, AI = A, EI = E, DI = EI>(route: Extract<PostRoute<A, E, D, AI, EI, DI>, {
|
|
133
|
+
prepare: unknown;
|
|
134
|
+
}>): PostRoute<A, E, D, AI, EI, DI> & Omit<PostRegistryRoute, "address" | "event">;
|
|
135
|
+
/** One producer's Post operations. */
|
|
136
|
+
export type PostRuntime<R extends Record<string, PostRegistryRoute>> = Readonly<{
|
|
137
|
+
/** Write a row and Watchdog job in the producer's own transaction. */
|
|
138
|
+
send: <K extends keyof R & string>(input: Readonly<{
|
|
139
|
+
key: string;
|
|
140
|
+
kind: K;
|
|
141
|
+
address: Schema.Schema.Type<R[K]["address"]>;
|
|
142
|
+
event: Schema.Schema.Type<R[K]["event"]>;
|
|
143
|
+
}>) => void;
|
|
144
|
+
/** Run as the Watchdog executor for a claimed Post job. */
|
|
145
|
+
execute: (job: WatchdogRunningJob) => Promise<Readonly<{
|
|
146
|
+
kind: "completed";
|
|
147
|
+
}>>;
|
|
148
|
+
/** Recover settled delivery jobs and enqueue due retries after a wake. */
|
|
149
|
+
enqueueDue: (limit?: number, afterKey?: string) => Promise<string | undefined>;
|
|
150
|
+
/** Read an ordered page of refused or exhausted keys for operators. */
|
|
151
|
+
deadLetters: (limit?: number, afterKey?: string) => Readonly<{
|
|
152
|
+
letters: readonly Readonly<{
|
|
153
|
+
key: string;
|
|
154
|
+
reason: "refused" | "exhausted";
|
|
155
|
+
envelope: PostEnvelope;
|
|
156
|
+
attempts: number;
|
|
157
|
+
}>[];
|
|
158
|
+
nextCursor: string | undefined;
|
|
159
|
+
}>;
|
|
160
|
+
/** The latest staged event of one kind for an address, including its terminal receipt. */
|
|
161
|
+
latest: <K extends keyof R & string>(kind: K, address: Schema.Schema.Type<R[K]["address"]>) => Readonly<{
|
|
162
|
+
key: string;
|
|
163
|
+
row: PostRow;
|
|
164
|
+
}> | undefined;
|
|
165
|
+
/** Requeue a failed event or expedite its settled retry, preserving its prepared fact. */
|
|
166
|
+
retry: (key: string) => Promise<void>;
|
|
167
|
+
/** Inspect one producer-local row or receipt. */
|
|
168
|
+
read: (key: string) => PostRow | undefined;
|
|
169
|
+
/** Earliest pending queue head, including a settled job waiting to retry. */
|
|
170
|
+
nextWake: () => number | null;
|
|
171
|
+
}>;
|
|
172
|
+
/** Decode at both persisted and DO boundaries with the route's Effect Schema. */
|
|
173
|
+
export declare function createPost<R extends Record<string, PostRegistryRoute>>(options: Readonly<{
|
|
174
|
+
routes: R;
|
|
175
|
+
owner: WatchdogSqliteOwner;
|
|
176
|
+
transactional: WatchdogTransactionalProjection;
|
|
177
|
+
readJob: WatchdogRuntime["read"];
|
|
178
|
+
now: () => number;
|
|
179
|
+
scheduleWake: (at: number) => Promise<void>;
|
|
180
|
+
}>): PostRuntime<R>;
|
|
181
|
+
/** Consumer admission runs inside the receiver's own SQLite transaction. */
|
|
182
|
+
export declare function admitPost<E, I>(options: Readonly<{
|
|
183
|
+
key: string;
|
|
184
|
+
event: unknown;
|
|
185
|
+
schema: Schema.Codec<E, I>;
|
|
186
|
+
hasReceipt: (key: string) => boolean;
|
|
187
|
+
apply: (event: E) => PostDeliveryResult;
|
|
188
|
+
putReceipt: (key: string) => void;
|
|
189
|
+
}>): PostDeliveryResult;
|