@cosmicdrift/kumiko-bundled-features 0.141.0 → 0.142.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/package.json +13 -7
- package/src/inbound-mail-foundation/__tests__/feature.test.ts +169 -0
- package/src/inbound-mail-foundation/__tests__/inbound-mail-foundation.integration.test.ts +437 -0
- package/src/inbound-mail-foundation/aggregate-id.ts +40 -0
- package/src/inbound-mail-foundation/connect-routes.ts +230 -0
- package/src/inbound-mail-foundation/constants.ts +80 -0
- package/src/inbound-mail-foundation/db/queries/inbound-projections.ts +38 -0
- package/src/inbound-mail-foundation/entities.ts +168 -0
- package/src/inbound-mail-foundation/events.ts +147 -0
- package/src/inbound-mail-foundation/feature.ts +185 -0
- package/src/inbound-mail-foundation/handlers/account-state.ts +23 -0
- package/src/inbound-mail-foundation/handlers/connect-account.write.ts +112 -0
- package/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +69 -0
- package/src/inbound-mail-foundation/handlers/ingest-message.write.ts +279 -0
- package/src/inbound-mail-foundation/handlers/list-accounts.query.ts +44 -0
- package/src/inbound-mail-foundation/handlers/list-messages.query.ts +58 -0
- package/src/inbound-mail-foundation/handlers/scope-visibility.ts +14 -0
- package/src/inbound-mail-foundation/handlers/update-account.write.ts +78 -0
- package/src/inbound-mail-foundation/index.ts +97 -0
- package/src/inbound-mail-foundation/oauth-state.ts +103 -0
- package/src/inbound-mail-foundation/projection.ts +152 -0
- package/src/inbound-mail-foundation/provider-factory.ts +53 -0
- package/src/inbound-mail-foundation/tenant-destroy-hook.ts +93 -0
- package/src/inbound-mail-foundation/types.ts +250 -0
- package/src/inbound-mail-foundation/watch-supervisor.ts +482 -0
- package/src/inbound-provider-imap/__tests__/feature.test.ts +115 -0
- package/src/inbound-provider-imap/__tests__/imap-live.integration.test.ts +194 -0
- package/src/inbound-provider-imap/credential-document.ts +51 -0
- package/src/inbound-provider-imap/feature.ts +287 -0
- package/src/inbound-provider-imap/imap-client.ts +158 -0
- package/src/inbound-provider-imap/index.ts +17 -0
- package/src/inbound-provider-inmemory/feature.ts +154 -0
- package/src/inbound-provider-inmemory/index.ts +12 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Public API of the inbound-provider-imap bundled-feature.
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
type ImapCredentialDocument,
|
|
5
|
+
imapCredentialDocumentSchema,
|
|
6
|
+
type ParseCredentialResult,
|
|
7
|
+
parseImapCredentialDocument,
|
|
8
|
+
} from "./credential-document";
|
|
9
|
+
export { IMAP_PROVIDER_KEY, inboundProviderImapFeature } from "./feature";
|
|
10
|
+
export {
|
|
11
|
+
assertUidValidity,
|
|
12
|
+
buildProviderMessageId,
|
|
13
|
+
type ImapCursor,
|
|
14
|
+
mapImapError,
|
|
15
|
+
normalizeReferences,
|
|
16
|
+
parseImapCursor,
|
|
17
|
+
} from "./imap-client";
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// kumiko-feature-version: 1
|
|
2
|
+
//
|
|
3
|
+
// inbound-provider-inmemory — scriptbarer In-Memory-Provider für die
|
|
4
|
+
// inbound-mail-foundation Plugin-API. Für Tests, Demos und Sample-Apps:
|
|
5
|
+
// Messages werden per `seedInboundMessage()` eingespeist statt von einem
|
|
6
|
+
// echten Postfach gefetcht; `watch()` pusht geseedete Messages sofort
|
|
7
|
+
// (IDLE-Simulation), `fetch()` liefert sie cursor-basiert nach
|
|
8
|
+
// (Reconciliation-Pfad) — beide Pfade zusammen exercisen exakt die
|
|
9
|
+
// Dedup-Garantie der Foundation.
|
|
10
|
+
//
|
|
11
|
+
// **Pattern-Vorbild:** mail-transport-inmemory (module-level State +
|
|
12
|
+
// Test-Helper-Exports). NICHT für Production.
|
|
13
|
+
|
|
14
|
+
import type {
|
|
15
|
+
InboundFetchResult,
|
|
16
|
+
InboundMailProviderPlugin,
|
|
17
|
+
MailAccountRecord,
|
|
18
|
+
RawInboundMessage,
|
|
19
|
+
SyncCursorPayload,
|
|
20
|
+
} from "@cosmicdrift/kumiko-bundled-features/inbound-mail-foundation";
|
|
21
|
+
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
22
|
+
|
|
23
|
+
const FEATURE_NAME = "inbound-provider-inmemory";
|
|
24
|
+
export const INMEMORY_PROVIDER_KEY = "inmemory";
|
|
25
|
+
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// Module-level State — pro Account: Message-Log (append-only, wie ein
|
|
28
|
+
// echtes Postfach), aktive Watch-Handler, One-Shot-Fehlerinjektion.
|
|
29
|
+
// =============================================================================
|
|
30
|
+
|
|
31
|
+
type WatchHandlers = {
|
|
32
|
+
readonly onMessages: (msgs: readonly RawInboundMessage[]) => Promise<void>;
|
|
33
|
+
readonly onError: (err: unknown) => void;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type AccountState = {
|
|
37
|
+
readonly log: RawInboundMessage[];
|
|
38
|
+
watch: WatchHandlers | null;
|
|
39
|
+
nextFetchError: unknown;
|
|
40
|
+
nextVerifyError: unknown;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const accounts = new Map<string, AccountState>();
|
|
44
|
+
|
|
45
|
+
function stateFor(accountId: string): AccountState {
|
|
46
|
+
let state = accounts.get(accountId);
|
|
47
|
+
if (!state) {
|
|
48
|
+
state = { log: [], watch: null, nextFetchError: null, nextVerifyError: null };
|
|
49
|
+
accounts.set(accountId, state);
|
|
50
|
+
}
|
|
51
|
+
return state;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Test-Helper: Message ins Postfach legen. Läuft ein watch, wird sie
|
|
55
|
+
* sofort gepusht (IDLE-Simulation); der nächste fetch liefert sie
|
|
56
|
+
* cursor-basiert ebenfalls — Dedup der Foundation macht das idempotent. */
|
|
57
|
+
export async function seedInboundMessage(accountId: string, msg: RawInboundMessage): Promise<void> {
|
|
58
|
+
const state = stateFor(accountId);
|
|
59
|
+
state.log.push(msg);
|
|
60
|
+
if (state.watch) await state.watch.onMessages([msg]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Test-Helper: One-Shot-Fehler für den nächsten fetch (z.B.
|
|
64
|
+
* InboundAuthError, InboundCursorInvalidError). */
|
|
65
|
+
export function failNextFetchWith(accountId: string, err: unknown): void {
|
|
66
|
+
stateFor(accountId).nextFetchError = err;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Test-Helper: One-Shot-Fehler für das nächste verify. */
|
|
70
|
+
export function failNextVerifyWith(accountId: string, err: unknown): void {
|
|
71
|
+
stateFor(accountId).nextVerifyError = err;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Test-Helper: Tod der Live-Verbindung simulieren (Supervisor muss
|
|
75
|
+
* mit Backoff neu starten). */
|
|
76
|
+
export function emitWatchError(accountId: string, err: unknown): void {
|
|
77
|
+
const state = accounts.get(accountId);
|
|
78
|
+
if (state?.watch) {
|
|
79
|
+
const handlers = state.watch;
|
|
80
|
+
state.watch = null;
|
|
81
|
+
handlers.onError(err);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Test-Helper: läuft gerade ein watch für den Account? */
|
|
86
|
+
export function isWatching(accountId: string): boolean {
|
|
87
|
+
return accounts.get(accountId)?.watch != null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Test-Helper: kompletter State-Reset (Test-Isolation — Mock-States
|
|
91
|
+
* nie zwischen Tests teilen). */
|
|
92
|
+
export function resetInboundInMemory(): void {
|
|
93
|
+
accounts.clear();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// =============================================================================
|
|
97
|
+
// Plugin — cursor = { offset: number } über das append-only-Log.
|
|
98
|
+
// =============================================================================
|
|
99
|
+
|
|
100
|
+
function offsetOf(cursor: SyncCursorPayload | null): number {
|
|
101
|
+
const raw = cursor?.["offset"];
|
|
102
|
+
return typeof raw === "number" && Number.isInteger(raw) && raw >= 0 ? raw : 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const plugin: InboundMailProviderPlugin = {
|
|
106
|
+
verify: async (_ctx, account: MailAccountRecord) => {
|
|
107
|
+
const state = stateFor(account.id);
|
|
108
|
+
if (state.nextVerifyError) {
|
|
109
|
+
const err = state.nextVerifyError;
|
|
110
|
+
state.nextVerifyError = null;
|
|
111
|
+
throw err;
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
fetch: async (_ctx, account, cursor, opts): Promise<InboundFetchResult> => {
|
|
115
|
+
const state = stateFor(account.id);
|
|
116
|
+
if (state.nextFetchError) {
|
|
117
|
+
const err = state.nextFetchError;
|
|
118
|
+
state.nextFetchError = null;
|
|
119
|
+
throw err;
|
|
120
|
+
}
|
|
121
|
+
const offset = offsetOf(cursor);
|
|
122
|
+
const slice = state.log.slice(offset, offset + opts.maxMessages);
|
|
123
|
+
const nextOffset = offset + slice.length;
|
|
124
|
+
return {
|
|
125
|
+
messages: slice,
|
|
126
|
+
nextCursor: { offset: nextOffset },
|
|
127
|
+
hasMore: nextOffset < state.log.length,
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
watch: async (_ctx, account, handlers) => {
|
|
131
|
+
const state = stateFor(account.id);
|
|
132
|
+
state.watch = handlers;
|
|
133
|
+
return async () => {
|
|
134
|
+
if (state.watch === handlers) state.watch = null;
|
|
135
|
+
};
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// =============================================================================
|
|
140
|
+
// Feature-definition
|
|
141
|
+
// =============================================================================
|
|
142
|
+
|
|
143
|
+
export const inboundProviderInMemoryFeature = defineFeature(FEATURE_NAME, (r) => {
|
|
144
|
+
r.describe(
|
|
145
|
+
'Registers a scriptable in-process `"inmemory"` provider for `inbound-mail-foundation`. Seed messages with `seedInboundMessage(accountId, msg)` — an active watch pushes them immediately (IDLE simulation) and the cursor-based `fetch` re-delivers them on the reconciliation path, exercising the foundation dedup guarantee. Error injection via `failNextFetchWith`/`failNextVerifyWith`/`emitWatchError`; reset state with `resetInboundInMemory()`. For tests, demos and sample apps — not for production.',
|
|
146
|
+
);
|
|
147
|
+
r.uiHints({
|
|
148
|
+
displayLabel: "Inbound Mail · In-Memory Provider",
|
|
149
|
+
category: "notifications",
|
|
150
|
+
recommended: false,
|
|
151
|
+
});
|
|
152
|
+
r.requires("inbound-mail-foundation");
|
|
153
|
+
r.useExtension("inboundMailProvider", INMEMORY_PROVIDER_KEY, plugin);
|
|
154
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Public API of the inbound-provider-inmemory bundled-feature.
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
emitWatchError,
|
|
5
|
+
failNextFetchWith,
|
|
6
|
+
failNextVerifyWith,
|
|
7
|
+
INMEMORY_PROVIDER_KEY,
|
|
8
|
+
inboundProviderInMemoryFeature,
|
|
9
|
+
isWatching,
|
|
10
|
+
resetInboundInMemory,
|
|
11
|
+
seedInboundMessage,
|
|
12
|
+
} from "./feature";
|