@push.rocks/smartjmap 1.0.0 → 2.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.
@@ -1,110 +1,180 @@
1
- import type { IJmapEmailAddress } from './classes.jmapclient.js';
2
- export interface IJmapServerMailbox {
3
- id: string;
4
- name: string;
5
- role: string | null;
6
- parentId: string | null;
7
- sortOrder: number;
8
- }
9
- export interface IJmapServerBlob {
10
- data: Buffer;
11
- type: string;
12
- name?: string;
13
- }
14
- export interface IJmapServerChangeEntry {
15
- state: number;
16
- created: string[];
17
- updated: string[];
18
- destroyed: string[];
19
- }
20
- export interface IJmapServerIdentity {
21
- id: string;
22
- name: string;
23
- email: string;
24
- mayDelete: boolean;
25
- }
26
- export interface IJmapServerSubmission {
27
- id: string;
28
- emailId: string;
29
- identityId: string;
30
- undoStatus: string;
31
- sendAt: string;
1
+ import { type IJmapMailBackend, type TJmapPrincipal } from "./interfaces.backend.js";
2
+ /**
3
+ * Semantic capability marker for the bounded streaming-upload server surface.
4
+ * Consumers must check this exact value before relying on streaming backend
5
+ * uploads, upload concurrency limits, Bearer-only challenges, or error mapping.
6
+ */
7
+ export declare const JMAP_SERVER_STREAMING_API_VERSION: 1;
8
+ /** Options for constructing a JmapServer. */
9
+ export interface IJmapServerOptions {
10
+ /** The storage backend; defaults to a fresh in-memory `MemoryMailBackend`. */
11
+ backend?: IJmapMailBackend;
12
+ /**
13
+ * Resolves an incoming Request to an authenticated principal (return null
14
+ * to reject with 401). Defaults to a static Basic/Bearer registry fed via
15
+ * `addUser`/`addBearerToken`.
16
+ */
17
+ authenticate?: (request: Request) => Promise<TJmapPrincipal | null>;
18
+ /**
19
+ * WWW-Authenticate challenges advertised on a 401 response. Defaults to
20
+ * Bearer and Basic for the built-in registry. Custom authenticators should
21
+ * list only the schemes they actually accept.
22
+ */
23
+ authenticationChallenges?: string[];
24
+ /**
25
+ * Absolute base URL (e.g. `https://mail.example.com`) prefixed to the URLs
26
+ * advertised in the session object. Defaults to relative URLs, which JMAP
27
+ * clients resolve against the session resource URL.
28
+ */
29
+ baseUrl?: string;
30
+ /** Overrides for the limits advertised in the JMAP session and enforced by this server. */
31
+ limits?: Partial<IJmapServerLimits>;
32
+ /**
33
+ * Process-wide upload ceiling across authenticated principals. Must be at
34
+ * least the advertised per-principal maxConcurrentUpload value.
35
+ */
36
+ maxConcurrentUploadServer?: number;
37
+ /** Maps backend upload failures to an RFC 7807 response instead of a generic 500. */
38
+ mapUploadError?: (error: unknown) => IJmapUploadProblem | null | undefined;
32
39
  }
33
- export interface IJmapServerUser {
34
- username: string;
35
- password: string;
36
- bearerTokens: Set<string>;
37
- accountId: string;
38
- mailboxes: Map<string, IJmapServerMailbox>;
39
- emails: Map<string, Record<string, any>>;
40
- blobs: Map<string, IJmapServerBlob>;
41
- submissions: Map<string, IJmapServerSubmission>;
42
- identities: IJmapServerIdentity[];
43
- state: number;
44
- changes: IJmapServerChangeEntry[];
45
- idCounter: number;
40
+ export interface IJmapServerLimits {
41
+ maxSizeUpload: number;
42
+ maxConcurrentUpload: number;
43
+ maxSizeRequest: number;
44
+ maxConcurrentRequests: number;
45
+ maxCallsInRequest: number;
46
+ maxObjectsInGet: number;
47
+ maxObjectsInSet: number;
46
48
  }
47
- export interface IJmapServerEmailInput {
48
- from: IJmapEmailAddress | IJmapEmailAddress[];
49
- to: IJmapEmailAddress | IJmapEmailAddress[];
50
- cc?: IJmapEmailAddress[];
51
- bcc?: IJmapEmailAddress[];
52
- subject?: string;
53
- textBody?: string;
54
- htmlBody?: string;
55
- keywords?: Record<string, boolean>;
56
- receivedAt?: string;
57
- attachments?: Array<{
58
- name: string;
59
- type: string;
60
- content: string | Uint8Array;
61
- }>;
49
+ export interface IJmapUploadProblem {
50
+ status: number;
51
+ type?: string;
52
+ detail: string;
53
+ limit?: string;
54
+ retryAfterSeconds?: number;
62
55
  }
56
+ /** The advertised — and enforced — RFC 8620 core limits. */
57
+ export declare const JMAP_SERVER_LIMITS: IJmapServerLimits;
63
58
  /**
64
- * A minimal in-memory JMAP server (RFC 8620 core + RFC 8621 mail subset)
65
- * for offline testing. Serves the session resource at /.well-known/jmap and
66
- * /jmap/session, the API at /jmap/api, blob downloads at /jmap/download/...,
67
- * and StateChange pushes via SSE at /jmap/eventsource.
59
+ * A JMAP server core (RFC 8620) with the RFC 8621 mail method surface,
60
+ * dispatching all storage into a pluggable `IJmapMailBackend`.
61
+ *
62
+ * The primary API is the web-standard `fetchHandler(request)` — mount it in
63
+ * any Request/Response runtime (Deno, Bun, service workers, Node ≥18). The
64
+ * `start(port)`/`stop()` pair wraps it in a node:http server for convenience.
65
+ *
66
+ * Endpoints: session (`/.well-known/jmap`, `/jmap/session`), API
67
+ * (`/jmap/api`), upload (`/jmap/upload/{accountId}`), download
68
+ * (`/jmap/download/{accountId}/{blobId}/{name}?type=`), and event source
69
+ * (`/jmap/eventsource` — SSE StateChange pushes per RFC 8620 §7.3).
70
+ *
71
+ * With no options this behaves as the offline test helper: an in-memory
72
+ * backend plus a static credential registry (`addUser`/`addBearerToken`).
68
73
  */
69
74
  export declare class JmapServer {
70
- users: Map<string, IJmapServerUser>;
71
- private server;
75
+ readonly backend: IJmapMailBackend;
76
+ private options;
77
+ private readonly limits;
78
+ private readonly maxConcurrentUploadServer;
79
+ private registry;
80
+ private httpServer;
72
81
  private sockets;
73
82
  private eventStreams;
74
- constructor();
83
+ private changeFeedUnsubscribe;
84
+ private cleanupErrors;
85
+ private activeApiRequests;
86
+ private activeUploadRequests;
87
+ private activeUploadsByPrincipal;
88
+ private activeNodeRequests;
89
+ private nodeRequestControllers;
90
+ private stopping;
91
+ private authenticationChallenges;
92
+ constructor(options?: IJmapServerOptions);
93
+ /**
94
+ * Registers Basic credentials in the default authenticator's registry.
95
+ * Only consulted when no custom `authenticate` option is set. Account data
96
+ * itself lives in the backend (the memory backend auto-creates accounts).
97
+ */
75
98
  addUser(username: string, password: string): void;
76
- /** Registers a valid Bearer token for a user. */
99
+ /** Registers a valid Bearer token for a user in the default authenticator. */
77
100
  addBearerToken(username: string, token: string): void;
78
- createMailbox(username: string, name: string, role?: string): string;
79
- /** Seeds an email into a user's mailbox; bumps state and pushes a StateChange. Returns the email id. */
80
- addEmail(username: string, mailboxName: string, input: IJmapServerEmailInput): string;
81
- /** Starts the server. Resolves with the bound port (pass 0 for an ephemeral port). */
101
+ /** Starts a node:http server around fetchHandler. Resolves with the bound port (pass 0 for ephemeral). */
82
102
  start(port: number): Promise<number>;
83
- /** Stops the server, ending open event streams and destroying open connections. */
103
+ /**
104
+ * Stops the server: closes all event streams (clearing their ping timers
105
+ * and the backend change-feed subscription), destroys open connections,
106
+ * and closes the node listener when one was started.
107
+ */
84
108
  stop(): Promise<void>;
85
- private handleRequest;
86
- private authenticate;
109
+ /**
110
+ * Handles a JMAP request end to end and returns the Response. This is the
111
+ * runtime-agnostic core — mount it directly as a Deno/Bun fetch handler.
112
+ */
113
+ fetchHandler(request: Request): Promise<Response>;
114
+ private authenticatePrincipal;
87
115
  private handleSession;
116
+ private buildUrl;
88
117
  private handleApi;
89
- private handleEventSource;
90
- private handleDownload;
118
+ /**
119
+ * Resolves `#argument` ResultReference objects (RFC 8620 §3.7) against the
120
+ * responses of earlier method calls in the same request.
121
+ */
122
+ private resolveBackReferences;
91
123
  private dispatchMethod;
124
+ private requireAccountId;
92
125
  private methodMailboxGet;
93
126
  private methodMailboxQuery;
127
+ private methodMailboxChanges;
128
+ private methodThreadGet;
94
129
  private methodEmailGet;
130
+ /** Applies the Email/get body-value fetch flags and maxBodyValueBytes truncation. */
131
+ private buildBodyValues;
95
132
  private methodEmailQuery;
96
133
  private methodEmailChanges;
97
134
  private methodEmailSet;
135
+ /**
136
+ * Normalizes an Email/set update patch (RFC 8620 §5.3): full `keywords`/
137
+ * `mailboxIds` replacements and single-key JSON-pointer patches
138
+ * (`keywords/$seen`, `mailboxIds/<id>`). Email properties other than these
139
+ * two are immutable (RFC 8621 §4.6).
140
+ */
141
+ private parseEmailUpdatePatch;
98
142
  private methodIdentityGet;
99
143
  private methodEmailSubmissionSet;
100
- private requireUser;
101
- private createEmailFromSpec;
144
+ /** Uses the client-provided envelope or derives it from the email's addresses. */
145
+ private resolveEnvelope;
146
+ private cancelRequestBody;
147
+ private readRequestBodyBounded;
148
+ private createExactUploadBody;
149
+ private mappedUploadErrorResponse;
150
+ private requestBodyErrorResponse;
151
+ private handleStreamingUpload;
152
+ private handleUpload;
153
+ private handleDownload;
154
+ private handleEventSource;
155
+ private registerStream;
156
+ private unregisterStream;
157
+ private closeStream;
158
+ private writeToStream;
159
+ /**
160
+ * Pushes a StateChange to matching event streams. Per-type states are
161
+ * deduplicated per stream, so a mutation notified both by the backend
162
+ * change feed and by the post-request push is delivered once.
163
+ */
164
+ private pushStateChange;
102
165
  /**
103
- * Applies an Email/set update patch: either whole properties (e.g. keywords)
104
- * or JSON-pointer style paths like "keywords/$seen" (RFC 8620 §5.3).
166
+ * Emits a StateChange after a mutating JMAP request, covering backends
167
+ * whose change feed only reports out-of-band mutations. Deduplicated
168
+ * against feed-driven pushes via the per-stream state tracking.
105
169
  */
106
- private applyPatch;
107
- /** Increments the account state, records the change, and pushes a StateChange to open event streams. */
108
- private bumpState;
109
- private notifyStateChange;
170
+ private pushAfterMutation;
171
+ private nodeRequestBody;
172
+ private handleNodeRequest;
173
+ private validateProperties;
174
+ private projectObject;
175
+ private validatePosition;
176
+ private validateChangesArgs;
177
+ private jsonResponse;
178
+ private problemResponse;
179
+ private limitResponse;
110
180
  }