@amalgm/channels 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PURPOSE.md +48 -13
- package/README.md +64 -6
- package/dist/host/auth.js +7 -3
- package/dist/host/auth.js.map +1 -1
- package/dist/host/config.d.ts +3 -1
- package/dist/host/config.js +3 -1
- package/dist/host/config.js.map +1 -1
- package/dist/host/inbox-repository.d.ts +10 -0
- package/dist/host/inbox-repository.js +57 -0
- package/dist/host/inbox-repository.js.map +1 -0
- package/dist/host/index.d.ts +4 -0
- package/dist/host/index.js +4 -0
- package/dist/host/index.js.map +1 -1
- package/dist/host/ingress-repository.d.ts +10 -0
- package/dist/host/ingress-repository.js +61 -0
- package/dist/host/ingress-repository.js.map +1 -0
- package/dist/host/main.js +32 -3
- package/dist/host/main.js.map +1 -1
- package/dist/host/repository.d.ts +4 -3
- package/dist/host/repository.js +23 -3
- package/dist/host/repository.js.map +1 -1
- package/dist/host/resend-webhook.d.ts +14 -0
- package/dist/host/resend-webhook.js +172 -0
- package/dist/host/resend-webhook.js.map +1 -0
- package/dist/host/resend.js +4 -0
- package/dist/host/resend.js.map +1 -1
- package/dist/host/server.d.ts +2 -1
- package/dist/host/server.js +85 -17
- package/dist/host/server.js.map +1 -1
- package/dist/host/wakeups.d.ts +22 -0
- package/dist/host/wakeups.js +30 -0
- package/dist/host/wakeups.js.map +1 -0
- package/dist/src/api-error.d.ts +2 -0
- package/dist/src/api-error.js +13 -0
- package/dist/src/api-error.js.map +1 -0
- package/dist/src/cli/arguments.d.ts +20 -0
- package/dist/src/cli/arguments.js +46 -0
- package/dist/src/cli/arguments.js.map +1 -0
- package/dist/src/cli/input.d.ts +6 -0
- package/dist/src/cli/input.js +38 -0
- package/dist/src/cli/input.js.map +1 -0
- package/dist/src/cli/main.d.ts +7 -0
- package/dist/src/cli/main.js +27 -0
- package/dist/src/cli/main.js.map +1 -0
- package/dist/src/cli/run.d.ts +13 -0
- package/dist/src/cli/run.js +47 -0
- package/dist/src/cli/run.js.map +1 -0
- package/dist/src/cli/shell-runtime.d.ts +9 -0
- package/dist/src/cli/shell-runtime.js +106 -0
- package/dist/src/cli/shell-runtime.js.map +1 -0
- package/dist/src/cli-main.d.ts +2 -0
- package/dist/src/cli-main.js +4 -0
- package/dist/src/cli-main.js.map +1 -0
- package/dist/src/cli.d.ts +3 -0
- package/dist/src/cli.js +4 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/client.js +5 -4
- package/dist/src/client.js.map +1 -1
- package/dist/src/command-surface.d.ts +23 -0
- package/dist/src/command-surface.js +53 -0
- package/dist/src/command-surface.js.map +1 -0
- package/dist/src/contract.d.ts +33 -7
- package/dist/src/email/blocks.d.ts +7 -0
- package/dist/src/email/blocks.js +43 -0
- package/dist/src/email/blocks.js.map +1 -0
- package/dist/src/email/escape.d.ts +3 -0
- package/dist/src/email/escape.js +19 -0
- package/dist/src/email/escape.js.map +1 -0
- package/dist/src/email/inbound.d.ts +83 -0
- package/dist/src/email/inbound.js +162 -0
- package/dist/src/email/inbound.js.map +1 -0
- package/dist/src/email/inline.d.ts +1 -0
- package/dist/src/email/inline.js +31 -0
- package/dist/src/email/inline.js.map +1 -0
- package/dist/src/email/markdown.d.ts +1 -0
- package/dist/src/email/markdown.js +117 -0
- package/dist/src/email/markdown.js.map +1 -0
- package/dist/src/email/notification.d.ts +2 -0
- package/dist/src/email/notification.js +18 -0
- package/dist/src/email/notification.js.map +1 -0
- package/dist/src/email/styles.d.ts +33 -0
- package/dist/src/email/styles.js +34 -0
- package/dist/src/email/styles.js.map +1 -0
- package/dist/src/email/system.d.ts +13 -0
- package/dist/src/email/system.js +31 -0
- package/dist/src/email/system.js.map +1 -0
- package/dist/src/email/thread-token.d.ts +8 -0
- package/dist/src/email/thread-token.js +70 -0
- package/dist/src/email/thread-token.js.map +1 -0
- package/dist/src/errors.d.ts +3 -2
- package/dist/src/errors.js +3 -1
- package/dist/src/errors.js.map +1 -1
- package/dist/src/http.d.ts +2 -0
- package/dist/src/http.js +84 -3
- package/dist/src/http.js.map +1 -1
- package/dist/src/inbox/client.d.ts +7 -0
- package/dist/src/inbox/client.js +79 -0
- package/dist/src/inbox/client.js.map +1 -0
- package/dist/src/inbox/contract.d.ts +37 -0
- package/dist/src/inbox/contract.js +2 -0
- package/dist/src/inbox/contract.js.map +1 -0
- package/dist/src/inbox/events.d.ts +7 -0
- package/dist/src/inbox/events.js +46 -0
- package/dist/src/inbox/events.js.map +1 -0
- package/dist/src/inbox/runner.d.ts +14 -0
- package/dist/src/inbox/runner.js +93 -0
- package/dist/src/inbox/runner.js.map +1 -0
- package/dist/src/inbox/service.d.ts +9 -0
- package/dist/src/inbox/service.js +56 -0
- package/dist/src/inbox/service.js.map +1 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.js +11 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp.js +20 -52
- package/dist/src/mcp.js.map +1 -1
- package/dist/src/service.js +7 -3
- package/dist/src/service.js.map +1 -1
- package/dist/src/validation.d.ts +2 -2
- package/dist/src/validation.js +2 -2
- package/dist/src/validation.js.map +1 -1
- package/dist/src/worker.d.ts +4 -1
- package/dist/src/worker.js +27 -5
- package/dist/src/worker.js.map +1 -1
- package/package.json +12 -5
- package/skills/channels/SKILL.md +14 -14
- package/supabase/migrations/20260903000000_bidirectional_email.sql +630 -0
package/PURPOSE.md
CHANGED
|
@@ -2,30 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
Channels is the sole owner of
|
|
6
|
-
user.
|
|
7
|
-
resolves
|
|
8
|
-
and hands it to the configured provider.
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
Channels is the sole owner of carrying discrete messages between an Amalgm
|
|
6
|
+
user and external communication channels. An agent supplies content and a
|
|
7
|
+
channel; Channels resolves that authenticated user's destination, persists one
|
|
8
|
+
durable delivery, and hands it to the configured provider. A verified inbound
|
|
9
|
+
provider event resolves one user, one channel thread, and one pinned computer,
|
|
10
|
+
then waits durably until that computer admits it into the matching local Chat
|
|
11
|
+
session. Callers never choose another user, handle provider credentials, or
|
|
12
|
+
infer whether delivery happened from a network attempt.
|
|
11
13
|
|
|
12
|
-
The SDK owns
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
The SDK owns send validation, channel-thread identity, inbound admission,
|
|
15
|
+
idempotency, leases, delivery state, and history. The HTTP API, MCP server,
|
|
16
|
+
global CLI, machine runner, provider adapters, and Fly host are adapters over
|
|
17
|
+
that SDK.
|
|
18
|
+
Supabase is the durable authority only for provider delivery and inbound work
|
|
19
|
+
that no computer has accepted yet. Chat sessions, transcripts, and agent
|
|
20
|
+
execution stay on the pinned user's computer. The hosted service is its own
|
|
21
|
+
deployable and does not run inside or depend on Amalgm Gateway.
|
|
17
22
|
|
|
18
23
|
## Axioms
|
|
19
24
|
|
|
20
|
-
1. Every delivery belongs to exactly
|
|
25
|
+
1. Every user delivery and every accepted inbound message belongs to exactly
|
|
26
|
+
one authenticated user.
|
|
21
27
|
2. A public send input never contains a user id, email address, phone number,
|
|
22
28
|
provider credential, or other recipient coordinate.
|
|
23
29
|
3. One idempotency key names at most one delivery for a user.
|
|
30
|
+
Provider retries use that delivery's globally unique id, never the caller's
|
|
31
|
+
user-scoped key.
|
|
24
32
|
4. A provider attempt begins only after a durable, exclusively leased delivery
|
|
25
33
|
exists; completion is accepted only from that lease.
|
|
26
34
|
5. Failed and expired leases remain visible and retryable. They are never
|
|
27
35
|
reported as delivered.
|
|
28
|
-
6. Supabase is authoritative for pending work
|
|
36
|
+
6. Supabase is authoritative for pending provider work, pending inbound work,
|
|
37
|
+
channel-thread routing, and permanent delivery history; it is never a Chat
|
|
38
|
+
transcript store.
|
|
29
39
|
7. Provider responses are reduced to bounded identifiers and safe errors before
|
|
30
40
|
persistence; secrets and recipient coordinates never enter public records.
|
|
31
41
|
8. The SDK is the behavior. HTTP, MCP, CLI, workers, and provider adapters stay
|
|
@@ -36,6 +46,27 @@ Amalgm Gateway.
|
|
|
36
46
|
or deliver its work.
|
|
37
47
|
11. The configured public origin, never forwarding headers, reconstructs the
|
|
38
48
|
DPoP request URL behind the Fly proxy.
|
|
49
|
+
12. One Channels renderer derives the HTML email and plaintext fallback from
|
|
50
|
+
the same persisted message; provider adapters cannot invent presentation.
|
|
51
|
+
13. One channel thread names exactly one Chat session and one pinned computer.
|
|
52
|
+
A new external thread creates a new session; every reply in that thread
|
|
53
|
+
reuses it.
|
|
54
|
+
14. A provider webhook is authenticated and durably deduplicated before it can
|
|
55
|
+
create work. Inbound account identity additionally requires an aligned
|
|
56
|
+
DMARC pass for the visible sender; subjects never authenticate or route.
|
|
57
|
+
15. An inbound envelope is complete offline state. A machine may claim only
|
|
58
|
+
envelopes pinned to its DPoP-bound computer identity, and hosted message
|
|
59
|
+
content is removed after the local Chat turn is durably admitted.
|
|
60
|
+
16. Agent text is not an outbound message. Only an explicit Channels send call
|
|
61
|
+
creates a delivery, and session identity is supplied by the trusted local
|
|
62
|
+
Chat relay rather than by the model.
|
|
63
|
+
17. Machine wakeups are content-free, cross-host broadcasts that accelerate
|
|
64
|
+
claims but never own correctness. Connect and reconnect always reconcile
|
|
65
|
+
the durable inbox.
|
|
66
|
+
18. Provider acceptance means sent, not delivered. Provider events alone move
|
|
67
|
+
a sent delivery to delivered or failed.
|
|
68
|
+
19. The global CLI projects the same command definitions as MCP and reaches
|
|
69
|
+
hosted effects only through Shell's authenticated loopback MCP route.
|
|
39
70
|
|
|
40
71
|
## Tree
|
|
41
72
|
|
|
@@ -43,9 +74,13 @@ Amalgm Gateway.
|
|
|
43
74
|
src/contract.ts public SDK shapes
|
|
44
75
|
src/service.ts validation and user-bound behavior
|
|
45
76
|
src/worker.ts lease-to-provider delivery pump
|
|
77
|
+
src/inbox/ machine claim, acceptance, and event-driven drain
|
|
46
78
|
src/http.ts HTTP adapter
|
|
47
79
|
src/client.ts browser/host API client
|
|
48
80
|
src/mcp.ts agent adapter
|
|
81
|
+
src/command-surface.ts shared MCP and CLI command definitions
|
|
82
|
+
src/cli/ global Shell-loopback command adapter
|
|
83
|
+
src/email/ canonical presentation, threading, and inbound rules
|
|
49
84
|
host/ Supabase, auth, provider, server, and Fly composition
|
|
50
85
|
supabase/migrations/ durable schema and atomic RPCs
|
|
51
86
|
skills/channels/ agent usage guidance
|
package/README.md
CHANGED
|
@@ -1,16 +1,74 @@
|
|
|
1
1
|
# @amalgm/channels
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Channels carries deliberate, discrete messages between an Amalgm user and
|
|
4
|
+
external communication channels. Email is the first bidirectional channel.
|
|
5
|
+
|
|
6
|
+
An agent does its work locally and explicitly calls one send command with the
|
|
7
|
+
text it wants the user to receive. Ordinary assistant output is never copied,
|
|
8
|
+
streamed, or inferred into an external message.
|
|
5
9
|
|
|
6
10
|
```ts
|
|
7
11
|
const channels = service.for({ userId, scopes: ['channels:send'] });
|
|
8
|
-
await channels.
|
|
12
|
+
await channels.send({
|
|
13
|
+
channel: 'email',
|
|
14
|
+
title: 'Research complete',
|
|
15
|
+
message: 'The report is ready.',
|
|
16
|
+
idempotencyKey: runId,
|
|
17
|
+
});
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Email behavior
|
|
21
|
+
|
|
22
|
+
- A new email thread creates one new local Chat session.
|
|
23
|
+
- Every reply in that email thread resumes the same session.
|
|
24
|
+
- The first message pins the thread to the user's last-used computer.
|
|
25
|
+
- If that computer is offline, the inbound message waits durably and drains in
|
|
26
|
+
order when Shell reconnects. The user does not need to resend it.
|
|
27
|
+
- Once the machine durably admits the turn, hosted message content is erased.
|
|
28
|
+
- Agent replies are separate sends through `amalgm_channels_send`; partial
|
|
29
|
+
assistant output never leaves the machine.
|
|
30
|
+
|
|
31
|
+
Production mail is sent as `Amalgm <emails@amalgm.ai>`. Replies use an opaque,
|
|
32
|
+
signed address on `reply.amalgm.ai`, so threading does not depend on a subject
|
|
33
|
+
line or a guess about the user's latest session.
|
|
34
|
+
|
|
35
|
+
## One command surface
|
|
36
|
+
|
|
37
|
+
The SDK owns the send command definition, validation, and behavior. MCP and
|
|
38
|
+
both CLI entry points project that same definition:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
amalgm channels send --input '{
|
|
42
|
+
"channel": "email",
|
|
43
|
+
"title": "Finished",
|
|
44
|
+
"message": "The work is complete.",
|
|
45
|
+
"idempotency_key": "run-123"
|
|
46
|
+
}'
|
|
47
|
+
|
|
48
|
+
amalgm-channels send --file message.json
|
|
49
|
+
printf '%s' '{"channel":"email","message":"Done"}' |
|
|
50
|
+
amalgm channels send --stdin
|
|
9
51
|
```
|
|
10
52
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
53
|
+
The global `amalgm channels` command discovers the running Shell and uses its
|
|
54
|
+
private loopback MCP route and local admission token. It never exports a hosted
|
|
55
|
+
access token or the machine's DPoP key. The recipient is always the signed-in
|
|
56
|
+
user; no command accepts an email address or user id.
|
|
57
|
+
|
|
58
|
+
## Composition
|
|
59
|
+
|
|
60
|
+
- `createChannelsApi` exposes the HTTP adapter.
|
|
61
|
+
- `createChannelsMcpServer` exposes the agent tool.
|
|
62
|
+
- `createChannelsCommands` is the reusable MCP/CLI command surface.
|
|
63
|
+
- `createChannelMachineRunner` drains held inbound work on Shell.
|
|
64
|
+
- `createChannelsHost` composes the Fly service.
|
|
65
|
+
|
|
66
|
+
Supabase stores only provider delivery history, thread routing, and inbound
|
|
67
|
+
work not yet admitted by a computer. Chat sessions, transcripts, and execution
|
|
68
|
+
remain on the user's machine. See [PURPOSE.md](./PURPOSE.md) for the governing
|
|
69
|
+
purpose and invariants.
|
|
70
|
+
|
|
71
|
+
## Verify
|
|
14
72
|
|
|
15
73
|
```bash
|
|
16
74
|
npm run verify
|
package/dist/host/auth.js
CHANGED
|
@@ -17,9 +17,12 @@ export function createChannelsAuthenticator(options) {
|
|
|
17
17
|
scopes: Object.freeze(['channels:read', 'channels:send']),
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
const pathname = new URL(request.url).pathname;
|
|
21
|
+
const required = pathname.startsWith('/v1/machine/')
|
|
22
|
+
? ['channels:receive']
|
|
23
|
+
: request.method.toUpperCase() === 'POST'
|
|
24
|
+
? ['channels:send']
|
|
25
|
+
: ['channels:read'];
|
|
23
26
|
const principal = await authorizeMachineResource({
|
|
24
27
|
authorization,
|
|
25
28
|
dpop: request.headers.get('dpop'),
|
|
@@ -31,6 +34,7 @@ export function createChannelsAuthenticator(options) {
|
|
|
31
34
|
}, ports);
|
|
32
35
|
return Object.freeze({
|
|
33
36
|
userId: principal.userId,
|
|
37
|
+
computerId: principal.computerId,
|
|
34
38
|
scopes: principal.scopes.filter((scope) => scope.startsWith('channels:')),
|
|
35
39
|
});
|
|
36
40
|
};
|
package/dist/host/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../host/auth.ts"],"names":[],"mappings":"AACA,OAAO,EACL,wBAAwB,GAGzB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,qBAAqB,EACrB,SAAS,EACT,SAAS,GAEV,MAAM,MAAM,CAAC;AACd,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,MAAM,UAAU,2BAA2B,CAAC,OAI3C;IACC,MAAM,IAAI,GAAG,kBAAkB,CAAC,IAAI,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,wBAAwB,CAAC,CAAC,CAAC;IACpF,MAAM,KAAK,GAAG,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAE3C,OAAO,KAAK,EAAE,OAAgB,EAA6B,EAAE;QAC3D,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;QAC3D,IAAI,aAAa,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YACzC,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;YAC3D,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC1D,IAAI,MAAM,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI;gBAAE,MAAM,IAAI,aAAa,CAAC,WAAW,EAAE,2BAA2B,CAAC,CAAC;YACzG,OAAO,MAAM,CAAC,MAAM,CAAC;gBACnB,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;gBAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,eAAe,EAAE,eAAe,CAAU,CAAC;aACnE,CAAC,CAAC;QACL,CAAC;QACD,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,MAAM;
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../host/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/host/config.d.ts
CHANGED
|
@@ -5,7 +5,9 @@ export interface ChannelsHostConfig {
|
|
|
5
5
|
readonly supabaseServiceRoleKey: string;
|
|
6
6
|
readonly authorizationIssuer: string;
|
|
7
7
|
readonly resendApiKey: string;
|
|
8
|
+
readonly resendWebhookSecret: string;
|
|
8
9
|
readonly emailFrom: string;
|
|
9
|
-
readonly
|
|
10
|
+
readonly emailInbox: string;
|
|
11
|
+
readonly emailThreadSecret: string;
|
|
10
12
|
}
|
|
11
13
|
export declare function channelsHostConfig(env?: Readonly<Record<string, string | undefined>>): ChannelsHostConfig;
|
package/dist/host/config.js
CHANGED
|
@@ -6,8 +6,10 @@ export function channelsHostConfig(env = process.env) {
|
|
|
6
6
|
supabaseServiceRoleKey: required(env.SUPABASE_SERVICE_ROLE_KEY, 'SUPABASE_SERVICE_ROLE_KEY'),
|
|
7
7
|
authorizationIssuer: httpsUrl(env.AMALGM_AUTHORIZATION_ISSUER, 'AMALGM_AUTHORIZATION_ISSUER'),
|
|
8
8
|
resendApiKey: required(env.RESEND_API_KEY, 'RESEND_API_KEY'),
|
|
9
|
+
resendWebhookSecret: required(env.RESEND_WEBHOOK_SECRET, 'RESEND_WEBHOOK_SECRET'),
|
|
9
10
|
emailFrom: required(env.CHANNELS_EMAIL_FROM, 'CHANNELS_EMAIL_FROM'),
|
|
10
|
-
|
|
11
|
+
emailInbox: required(env.CHANNELS_EMAIL_INBOX, 'CHANNELS_EMAIL_INBOX'),
|
|
12
|
+
emailThreadSecret: required(env.CHANNELS_EMAIL_THREAD_SECRET, 'CHANNELS_EMAIL_THREAD_SECRET'),
|
|
11
13
|
});
|
|
12
14
|
}
|
|
13
15
|
function required(value, name) {
|
package/dist/host/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../host/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../host/config.ts"],"names":[],"mappings":"AAaA,MAAM,UAAU,kBAAkB,CAChC,MAAoD,OAAO,CAAC,GAAG;IAE/D,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,CAAC;QACxC,YAAY,EAAE,QAAQ,CAAC,GAAG,CAAC,qBAAqB,EAAE,uBAAuB,CAAC;QAC1E,WAAW,EAAE,QAAQ,CAAC,GAAG,CAAC,YAAY,IAAI,GAAG,CAAC,wBAAwB,EAAE,cAAc,CAAC;QACvF,sBAAsB,EAAE,QAAQ,CAAC,GAAG,CAAC,yBAAyB,EAAE,2BAA2B,CAAC;QAC5F,mBAAmB,EAAE,QAAQ,CAAC,GAAG,CAAC,2BAA2B,EAAE,6BAA6B,CAAC;QAC7F,YAAY,EAAE,QAAQ,CAAC,GAAG,CAAC,cAAc,EAAE,gBAAgB,CAAC;QAC5D,mBAAmB,EAAE,QAAQ,CAAC,GAAG,CAAC,qBAAqB,EAAE,uBAAuB,CAAC;QACjF,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,mBAAmB,EAAE,qBAAqB,CAAC;QACnE,UAAU,EAAE,QAAQ,CAAC,GAAG,CAAC,oBAAoB,EAAE,sBAAsB,CAAC;QACtE,iBAAiB,EAAE,QAAQ,CAAC,GAAG,CAAC,4BAA4B,EAAE,8BAA8B,CAAC;KAC9F,CAAC,CAAC;AACL,CAAC;AAED,SAAS,QAAQ,CAAC,KAAyB,EAAE,IAAY;IACvD,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,cAAc,CAAC,CAAC;IAC3D,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;AACtB,CAAC;AAED,SAAS,QAAQ,CAAC,KAAyB,EAAE,IAAY;IACvD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,KAAK,WAAW,IAAI,MAAM,CAAC,QAAQ,KAAK,WAAW,CAAC;IACjF,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,KAAK,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,CAAC,EAAE,CAAC;QAC5E,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,iBAAiB,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,OAAO,CACd,KAAyB,EACzB,QAAgB,EAChB,OAAe,EACf,OAAe;IAEf,MAAM,MAAM,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9D,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,OAAO,IAAI,MAAM,GAAG,OAAO,EAAE,CAAC;QACtE,MAAM,IAAI,KAAK,CAAC,4BAA4B,OAAO,OAAO,OAAO,EAAE,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
import type { ClaimedInboundMessage, InboundLease, MachineInboxRepository } from '../src/inbox/contract.js';
|
|
3
|
+
export declare class SupabaseMachineInboxRepository implements MachineInboxRepository {
|
|
4
|
+
#private;
|
|
5
|
+
private readonly client;
|
|
6
|
+
constructor(client: SupabaseClient);
|
|
7
|
+
claim(userId: string, computerId: string, limit: number, leaseSeconds: number): Promise<readonly ClaimedInboundMessage[]>;
|
|
8
|
+
accept(userId: string, computerId: string, message: InboundLease, now: Date): Promise<boolean>;
|
|
9
|
+
release(userId: string, computerId: string, message: InboundLease, error: string, now: Date): Promise<boolean>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ChannelsError } from '../src/errors.js';
|
|
2
|
+
export class SupabaseMachineInboxRepository {
|
|
3
|
+
client;
|
|
4
|
+
constructor(client) {
|
|
5
|
+
this.client = client;
|
|
6
|
+
}
|
|
7
|
+
async claim(userId, computerId, limit, leaseSeconds) {
|
|
8
|
+
const rows = await this.#rpc('claim_amalgm_channel_inbound', {
|
|
9
|
+
p_user_id: userId,
|
|
10
|
+
p_computer_id: computerId,
|
|
11
|
+
p_limit: limit,
|
|
12
|
+
p_lease_seconds: leaseSeconds,
|
|
13
|
+
});
|
|
14
|
+
return rows.map((row) => Object.freeze({
|
|
15
|
+
id: text(row.id),
|
|
16
|
+
channel: 'email',
|
|
17
|
+
threadId: text(row.thread_id),
|
|
18
|
+
sessionId: text(row.session_id),
|
|
19
|
+
startsSession: row.starts_session === true,
|
|
20
|
+
subject: text(row.subject),
|
|
21
|
+
message: text(row.message),
|
|
22
|
+
providerMessageId: text(row.provider_message_id),
|
|
23
|
+
receivedAt: timestamp(row.received_at),
|
|
24
|
+
leaseToken: text(row.lease_token),
|
|
25
|
+
attempts: number(row.attempts),
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
accept(userId, computerId, message, now) {
|
|
29
|
+
return this.#rpc('accept_amalgm_channel_inbound', {
|
|
30
|
+
p_user_id: userId,
|
|
31
|
+
p_computer_id: computerId,
|
|
32
|
+
p_message_id: message.id,
|
|
33
|
+
p_lease_token: message.leaseToken,
|
|
34
|
+
p_now: now.toISOString(),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
release(userId, computerId, message, error, now) {
|
|
38
|
+
return this.#rpc('release_amalgm_channel_inbound', {
|
|
39
|
+
p_user_id: userId,
|
|
40
|
+
p_computer_id: computerId,
|
|
41
|
+
p_message_id: message.id,
|
|
42
|
+
p_lease_token: message.leaseToken,
|
|
43
|
+
p_error: error,
|
|
44
|
+
p_now: now.toISOString(),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async #rpc(name, arguments_) {
|
|
48
|
+
const result = await this.client.rpc(name, arguments_);
|
|
49
|
+
if (result.error)
|
|
50
|
+
throw new ChannelsError('internal', `Channels inbox storage failed: ${result.error.message}`);
|
|
51
|
+
return result.data;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const text = (value) => typeof value === 'string' ? value : String(value ?? '');
|
|
55
|
+
const number = (value) => Number(value) || 0;
|
|
56
|
+
const timestamp = (value) => new Date(String(value)).toISOString();
|
|
57
|
+
//# sourceMappingURL=inbox-repository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbox-repository.js","sourceRoot":"","sources":["../../host/inbox-repository.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAIjD,MAAM,OAAO,8BAA8B;IACZ;IAA7B,YAA6B,MAAsB;QAAtB,WAAM,GAAN,MAAM,CAAgB;IAAG,CAAC;IAEvD,KAAK,CAAC,KAAK,CACT,MAAc,EACd,UAAkB,EAClB,KAAa,EACb,YAAoB;QAEpB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAQ,8BAA8B,EAAE;YAClE,SAAS,EAAE,MAAM;YACjB,aAAa,EAAE,UAAU;YACzB,OAAO,EAAE,KAAK;YACd,eAAe,EAAE,YAAY;SAC9B,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC;YACrC,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAChB,OAAO,EAAE,OAAgB;YACzB,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC;YAC7B,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC;YAC/B,aAAa,EAAE,GAAG,CAAC,cAAc,KAAK,IAAI;YAC1C,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;YAC1B,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;YAC1B,iBAAiB,EAAE,IAAI,CAAC,GAAG,CAAC,mBAAmB,CAAC;YAChD,UAAU,EAAE,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC;YACtC,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC;YACjC,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;SAC/B,CAAC,CAAC,CAAC;IACN,CAAC;IAED,MAAM,CAAC,MAAc,EAAE,UAAkB,EAAE,OAAqB,EAAE,GAAS;QACzE,OAAO,IAAI,CAAC,IAAI,CAAC,+BAA+B,EAAE;YAChD,SAAS,EAAE,MAAM;YACjB,aAAa,EAAE,UAAU;YACzB,YAAY,EAAE,OAAO,CAAC,EAAE;YACxB,aAAa,EAAE,OAAO,CAAC,UAAU;YACjC,KAAK,EAAE,GAAG,CAAC,WAAW,EAAE;SACzB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,CACL,MAAc,EACd,UAAkB,EAClB,OAAqB,EACrB,KAAa,EACb,GAAS;QAET,OAAO,IAAI,CAAC,IAAI,CAAC,gCAAgC,EAAE;YACjD,SAAS,EAAE,MAAM;YACjB,aAAa,EAAE,UAAU;YACzB,YAAY,EAAE,OAAO,CAAC,EAAE;YACxB,aAAa,EAAE,OAAO,CAAC,UAAU;YACjC,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,GAAG,CAAC,WAAW,EAAE;SACzB,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,IAAI,CAAI,IAAY,EAAE,UAAmC;QAC7D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QACvD,IAAI,MAAM,CAAC,KAAK;YAAE,MAAM,IAAI,aAAa,CAAC,UAAU,EAAE,kCAAkC,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAChH,OAAO,MAAM,CAAC,IAAS,CAAC;IAC1B,CAAC;CACF;AAED,MAAM,IAAI,GAAG,CAAC,KAAc,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;AACzF,MAAM,MAAM,GAAG,CAAC,KAAc,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACtD,MAAM,SAAS,GAAG,CAAC,KAAc,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC"}
|
package/dist/host/index.d.ts
CHANGED
|
@@ -4,3 +4,7 @@ export * from './recipient.js';
|
|
|
4
4
|
export * from './repository.js';
|
|
5
5
|
export * from './resend.js';
|
|
6
6
|
export * from './server.js';
|
|
7
|
+
export * from './wakeups.js';
|
|
8
|
+
export * from './inbox-repository.js';
|
|
9
|
+
export * from './ingress-repository.js';
|
|
10
|
+
export * from './resend-webhook.js';
|
package/dist/host/index.js
CHANGED
|
@@ -4,4 +4,8 @@ export * from './recipient.js';
|
|
|
4
4
|
export * from './repository.js';
|
|
5
5
|
export * from './resend.js';
|
|
6
6
|
export * from './server.js';
|
|
7
|
+
export * from './wakeups.js';
|
|
8
|
+
export * from './inbox-repository.js';
|
|
9
|
+
export * from './ingress-repository.js';
|
|
10
|
+
export * from './resend-webhook.js';
|
|
7
11
|
//# sourceMappingURL=index.js.map
|
package/dist/host/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../host/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../host/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
import type { EmailIngressRepository, InboundAdmission, NormalizedReceivedEmail, ProviderEmailEvent } from '../src/email/inbound.js';
|
|
3
|
+
export declare class SupabaseEmailIngressRepository implements EmailIngressRepository {
|
|
4
|
+
#private;
|
|
5
|
+
private readonly client;
|
|
6
|
+
constructor(client: SupabaseClient);
|
|
7
|
+
admit(input: NormalizedReceivedEmail): Promise<InboundAdmission>;
|
|
8
|
+
recordNotice(recordId: string, providerId: string, now: Date): Promise<boolean>;
|
|
9
|
+
applyProviderEvent(input: ProviderEmailEvent): Promise<boolean>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ChannelsError } from '../src/errors.js';
|
|
2
|
+
export class SupabaseEmailIngressRepository {
|
|
3
|
+
client;
|
|
4
|
+
constructor(client) {
|
|
5
|
+
this.client = client;
|
|
6
|
+
}
|
|
7
|
+
async admit(input) {
|
|
8
|
+
const row = await this.#rpc('admit_amalgm_channel_email', { p_email: input });
|
|
9
|
+
const kind = text(row.kind);
|
|
10
|
+
if (kind === 'accepted') {
|
|
11
|
+
return Object.freeze({
|
|
12
|
+
kind,
|
|
13
|
+
userId: text(row.user_id),
|
|
14
|
+
messageId: text(row.message_id),
|
|
15
|
+
threadId: text(row.thread_id),
|
|
16
|
+
sessionId: text(row.session_id),
|
|
17
|
+
targetComputerId: text(row.target_computer_id),
|
|
18
|
+
targetComputerName: text(row.target_computer_name),
|
|
19
|
+
targetOnline: row.target_online === true,
|
|
20
|
+
senderEmail: text(row.sender_email),
|
|
21
|
+
subject: text(row.subject),
|
|
22
|
+
providerMessageId: text(row.provider_message_id),
|
|
23
|
+
references: strings(row.references),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
if (kind === 'unknown_sender' || kind === 'no_computer') {
|
|
27
|
+
return Object.freeze({
|
|
28
|
+
kind,
|
|
29
|
+
recordId: text(row.record_id),
|
|
30
|
+
senderEmail: text(row.sender_email),
|
|
31
|
+
subject: text(row.subject),
|
|
32
|
+
providerMessageId: text(row.provider_message_id),
|
|
33
|
+
references: strings(row.references),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
if (kind === 'duplicate')
|
|
37
|
+
return Object.freeze({ kind, recordId: text(row.record_id) });
|
|
38
|
+
if (kind === 'ignored')
|
|
39
|
+
return Object.freeze({ kind, reason: text(row.reason) });
|
|
40
|
+
throw new ChannelsError('internal', 'Channels ingress returned an unknown admission');
|
|
41
|
+
}
|
|
42
|
+
recordNotice(recordId, providerId, now) {
|
|
43
|
+
return this.#rpc('record_amalgm_channel_inbound_notice', {
|
|
44
|
+
p_message_id: recordId,
|
|
45
|
+
p_provider_id: providerId,
|
|
46
|
+
p_now: now.toISOString(),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
applyProviderEvent(input) {
|
|
50
|
+
return this.#rpc('apply_amalgm_channel_provider_event', { p_event: input });
|
|
51
|
+
}
|
|
52
|
+
async #rpc(name, arguments_) {
|
|
53
|
+
const result = await this.client.rpc(name, arguments_);
|
|
54
|
+
if (result.error)
|
|
55
|
+
throw new ChannelsError('internal', `Channels ingress storage failed: ${result.error.message}`);
|
|
56
|
+
return result.data;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const text = (value) => typeof value === 'string' ? value : String(value ?? '');
|
|
60
|
+
const strings = (value) => Array.isArray(value) ? value.map(text).filter(Boolean) : [];
|
|
61
|
+
//# sourceMappingURL=ingress-repository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingress-repository.js","sourceRoot":"","sources":["../../host/ingress-repository.ts"],"names":[],"mappings":"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"}
|
package/dist/host/main.js
CHANGED
|
@@ -2,32 +2,61 @@ import { createClient } from '@supabase/supabase-js';
|
|
|
2
2
|
import { createChannelsApi } from '../src/http.js';
|
|
3
3
|
import { createChannelsService } from '../src/service.js';
|
|
4
4
|
import { ChannelsWorker } from '../src/worker.js';
|
|
5
|
+
import { EmailIngress } from '../src/email/inbound.js';
|
|
6
|
+
import { EmailThreadAddressing } from '../src/email/thread-token.js';
|
|
7
|
+
import { MachineEventHub } from '../src/inbox/events.js';
|
|
8
|
+
import { createMachineInboxService } from '../src/inbox/service.js';
|
|
5
9
|
import { createChannelsAuthenticator } from './auth.js';
|
|
6
10
|
import { channelsHostConfig } from './config.js';
|
|
7
11
|
import { SupabaseRecipientDirectory } from './recipient.js';
|
|
8
12
|
import { SupabaseDeliveryRepository } from './repository.js';
|
|
9
13
|
import { ResendEmailProvider } from './resend.js';
|
|
10
14
|
import { createChannelsHost } from './server.js';
|
|
15
|
+
import { SupabaseMachineInboxRepository } from './inbox-repository.js';
|
|
16
|
+
import { SupabaseEmailIngressRepository } from './ingress-repository.js';
|
|
17
|
+
import { createResendWebhookApi } from './resend-webhook.js';
|
|
18
|
+
import { HostedMachineWakeups } from './wakeups.js';
|
|
11
19
|
const config = channelsHostConfig();
|
|
12
20
|
const supabase = createClient(config.supabaseUrl, config.supabaseServiceRoleKey, {
|
|
13
21
|
auth: { persistSession: false, autoRefreshToken: false },
|
|
14
22
|
});
|
|
15
23
|
const repository = new SupabaseDeliveryRepository(supabase);
|
|
16
|
-
const
|
|
24
|
+
const addressing = new EmailThreadAddressing(config.emailThreadSecret, config.emailInbox);
|
|
25
|
+
const email = new ResendEmailProvider(config.resendApiKey, config.emailFrom);
|
|
26
|
+
const worker = new ChannelsWorker(repository, new SupabaseRecipientDirectory(supabase), email, addressing, { log });
|
|
27
|
+
const events = new MachineEventHub();
|
|
28
|
+
const realtime = supabase.channel('amalgm-channels-work');
|
|
29
|
+
const wakeups = new HostedMachineWakeups(events, realtime, log);
|
|
30
|
+
const ingress = new EmailIngress(new SupabaseEmailIngressRepository(supabase), addressing);
|
|
17
31
|
const api = createChannelsApi({
|
|
18
32
|
service: createChannelsService(repository),
|
|
19
33
|
authenticate: createChannelsAuthenticator({ issuer: config.authorizationIssuer, supabase }),
|
|
34
|
+
inbox: createMachineInboxService(new SupabaseMachineInboxRepository(supabase), events.stream),
|
|
20
35
|
});
|
|
21
36
|
const host = createChannelsHost({
|
|
22
37
|
publicOrigin: config.publicOrigin,
|
|
23
38
|
api,
|
|
39
|
+
webhookApi: createResendWebhookApi({
|
|
40
|
+
secret: config.resendWebhookSecret,
|
|
41
|
+
ingress,
|
|
42
|
+
email,
|
|
43
|
+
addressing,
|
|
44
|
+
wakeMachine: (userId, computerId) => wakeups.publish(userId, computerId),
|
|
45
|
+
apiKey: config.resendApiKey,
|
|
46
|
+
}),
|
|
24
47
|
worker,
|
|
25
|
-
pollIntervalMs: config.pollIntervalMs,
|
|
26
48
|
log,
|
|
27
49
|
});
|
|
50
|
+
realtime
|
|
51
|
+
.on('broadcast', { event: 'machine_wakeup' }, ({ payload }) => {
|
|
52
|
+
wakeups.receive(payload);
|
|
53
|
+
})
|
|
54
|
+
.subscribe((status) => log('realtime.status', { status }));
|
|
28
55
|
host.server.listen(config.port, '0.0.0.0', () => log('host.ready', { port: config.port }));
|
|
29
56
|
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
30
|
-
process.once(signal, () => void
|
|
57
|
+
process.once(signal, () => void supabase.removeChannel(realtime)
|
|
58
|
+
.then(() => host.close())
|
|
59
|
+
.finally(() => process.exit(0)));
|
|
31
60
|
}
|
|
32
61
|
function log(event, details = {}) {
|
|
33
62
|
console.log(JSON.stringify({ service: 'amalgm-channels', event, ...details }));
|
package/dist/host/main.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../host/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,2BAA2B,EAAE,MAAM,WAAW,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../host/main.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
-
import type { Delivery, DeliveryPage, DeliveryRepository, NormalizedListDeliveries,
|
|
2
|
+
import type { Delivery, DeliveryPage, DeliveryRepository, NormalizedListDeliveries, NormalizedSendMessage, PendingDelivery, ChannelSendContext } from '../src/contract.js';
|
|
3
3
|
export declare class SupabaseDeliveryRepository implements DeliveryRepository {
|
|
4
4
|
#private;
|
|
5
5
|
private readonly client;
|
|
6
6
|
constructor(client: SupabaseClient);
|
|
7
|
-
enqueue(userId: string, input:
|
|
7
|
+
enqueue(userId: string, computerId: string | undefined, input: NormalizedSendMessage, context: ChannelSendContext): Promise<Delivery>;
|
|
8
8
|
list(userId: string, query: NormalizedListDeliveries): Promise<DeliveryPage>;
|
|
9
9
|
get(userId: string, deliveryId: string): Promise<Delivery | null>;
|
|
10
10
|
claim(limit: number, leaseSeconds: number): Promise<readonly PendingDelivery[]>;
|
|
11
|
-
|
|
11
|
+
markSent(item: PendingDelivery, providerId: string, now: Date): Promise<boolean>;
|
|
12
12
|
fail(item: PendingDelivery, error: string, now: Date): Promise<boolean>;
|
|
13
|
+
nextWorkAt(): Promise<Date | null>;
|
|
13
14
|
}
|
package/dist/host/repository.js
CHANGED
|
@@ -4,10 +4,12 @@ export class SupabaseDeliveryRepository {
|
|
|
4
4
|
constructor(client) {
|
|
5
5
|
this.client = client;
|
|
6
6
|
}
|
|
7
|
-
async enqueue(userId, input) {
|
|
7
|
+
async enqueue(userId, computerId, input, context) {
|
|
8
8
|
return delivery(await this.#rpc('enqueue_amalgm_channel_delivery', {
|
|
9
9
|
p_user_id: userId,
|
|
10
|
+
p_computer_id: computerId ?? null,
|
|
10
11
|
p_delivery: input,
|
|
12
|
+
p_session_id: context.sessionId ?? null,
|
|
11
13
|
}));
|
|
12
14
|
}
|
|
13
15
|
async list(userId, query) {
|
|
@@ -39,6 +41,7 @@ export class SupabaseDeliveryRepository {
|
|
|
39
41
|
});
|
|
40
42
|
return rows.map((row) => {
|
|
41
43
|
const title = optional(row.title);
|
|
44
|
+
const replyToMessageId = optional(row.reply_to_message_id);
|
|
42
45
|
return {
|
|
43
46
|
id: text(row.id),
|
|
44
47
|
userId: text(row.user_id),
|
|
@@ -48,11 +51,19 @@ export class SupabaseDeliveryRepository {
|
|
|
48
51
|
idempotencyKey: text(row.idempotency_key),
|
|
49
52
|
leaseToken: text(row.lease_token),
|
|
50
53
|
attempts: number(row.attempts),
|
|
54
|
+
thread: {
|
|
55
|
+
id: text(row.thread_id),
|
|
56
|
+
sessionId: text(row.session_id),
|
|
57
|
+
subject: text(row.thread_subject),
|
|
58
|
+
messageId: text(row.rfc_message_id),
|
|
59
|
+
...(replyToMessageId ? { replyToMessageId } : {}),
|
|
60
|
+
references: strings(row.references),
|
|
61
|
+
},
|
|
51
62
|
};
|
|
52
63
|
});
|
|
53
64
|
}
|
|
54
|
-
|
|
55
|
-
return this.#rpc('
|
|
65
|
+
markSent(item, providerId, now) {
|
|
66
|
+
return this.#rpc('mark_amalgm_channel_delivery_sent', {
|
|
56
67
|
p_delivery_id: item.id,
|
|
57
68
|
p_lease_token: item.leaseToken,
|
|
58
69
|
p_provider_id: providerId,
|
|
@@ -67,6 +78,10 @@ export class SupabaseDeliveryRepository {
|
|
|
67
78
|
p_now: now.toISOString(),
|
|
68
79
|
});
|
|
69
80
|
}
|
|
81
|
+
async nextWorkAt() {
|
|
82
|
+
const value = await this.#rpc('next_amalgm_channel_delivery_at', {});
|
|
83
|
+
return value ? new Date(value) : null;
|
|
84
|
+
}
|
|
70
85
|
async #rpc(name, arguments_) {
|
|
71
86
|
const { data, error } = await this.client.rpc(name, arguments_);
|
|
72
87
|
if (error) {
|
|
@@ -81,6 +96,8 @@ function delivery(row) {
|
|
|
81
96
|
const title = optional(row.title);
|
|
82
97
|
const providerId = optional(row.provider_id);
|
|
83
98
|
const error = optional(row.error);
|
|
99
|
+
const threadId = optional(row.thread_id);
|
|
100
|
+
const sessionId = optional(row.session_id);
|
|
84
101
|
return {
|
|
85
102
|
id: text(row.id),
|
|
86
103
|
channel: 'email',
|
|
@@ -94,10 +111,13 @@ function delivery(row) {
|
|
|
94
111
|
...(optional(row.delivered_at) ? { deliveredAt: timestamp(row.delivered_at) } : {}),
|
|
95
112
|
...(providerId === undefined ? {} : { providerId }),
|
|
96
113
|
...(error === undefined ? {} : { error }),
|
|
114
|
+
...(threadId === undefined ? {} : { threadId }),
|
|
115
|
+
...(sessionId === undefined ? {} : { sessionId }),
|
|
97
116
|
};
|
|
98
117
|
}
|
|
99
118
|
const text = (value) => typeof value === 'string' ? value : String(value ?? '');
|
|
100
119
|
const optional = (value) => typeof value === 'string' && value ? value : undefined;
|
|
101
120
|
const number = (value) => Number(value) || 0;
|
|
102
121
|
const timestamp = (value) => new Date(String(value)).toISOString();
|
|
122
|
+
const strings = (value) => Array.isArray(value) ? value.map(text).filter(Boolean) : [];
|
|
103
123
|
//# sourceMappingURL=repository.js.map
|