@push.rocks/smartjmap 2.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.
@@ -111,6 +111,17 @@ export interface IJmapSubmissionInput {
111
111
  /** The raw RFC 5322 payload (the email's blob) — the backend performs the actual sending. */
112
112
  message: Uint8Array;
113
113
  }
114
+ /**
115
+ * Authenticated, admitted upload handed to a streaming backend. The backend
116
+ * must reserve quota before reading `stream`, consume it through EOF, and
117
+ * abort its storage operation when `signal` fires.
118
+ */
119
+ export interface IJmapBlobUploadStream {
120
+ stream: ReadableStream<Uint8Array>;
121
+ size: number;
122
+ type: string;
123
+ signal: AbortSignal;
124
+ }
114
125
  /** Standard result shape for the /get-style backend reads. */
115
126
  export interface IJmapGetResult<T> {
116
127
  /** Backend-owned opaque state string for the collection at read time. */
@@ -311,6 +322,14 @@ export interface IJmapMailBackend {
311
322
  blobId: string;
312
323
  size: number;
313
324
  }>;
325
+ /**
326
+ * Optional streaming upload path. When present, JmapServer requires a valid
327
+ * Content-Length and never buffers the request before invoking this method.
328
+ */
329
+ uploadBlobStream?(accountId: string, upload: IJmapBlobUploadStream): Promise<{
330
+ blobId: string;
331
+ size: number;
332
+ }>;
314
333
  /** Returns a blob's bytes and metadata, or null when unknown. */
315
334
  getBlob(accountId: string, blobId: string): Promise<IJmapBlobRecord | null>;
316
335
  /** Returns the sending identities of the account with their collection state. */
@@ -10,4 +10,4 @@ export class JmapBackendError extends Error {
10
10
  this.name = 'JmapBackendError';
11
11
  }
12
12
  }
13
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZXJmYWNlcy5iYWNrZW5kLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvaW50ZXJmYWNlcy5iYWNrZW5kLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQXdRQTs7OztHQUlHO0FBQ0gsTUFBTSxPQUFPLGdCQUFpQixTQUFRLEtBQUs7SUFDekMsWUFBbUIsSUFBWSxFQUFFLFdBQW9CO1FBQ25ELEtBQUssQ0FBQyxXQUFXLElBQUksSUFBSSxDQUFDLENBQUM7UUFEVixTQUFJLEdBQUosSUFBSSxDQUFRO1FBRTdCLElBQUksQ0FBQyxJQUFJLEdBQUcsa0JBQWtCLENBQUM7SUFDakMsQ0FBQztDQUNGIn0=
13
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZXJmYWNlcy5iYWNrZW5kLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvaW50ZXJmYWNlcy5iYWNrZW5kLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQW9SQTs7OztHQUlHO0FBQ0gsTUFBTSxPQUFPLGdCQUFpQixTQUFRLEtBQUs7SUFDekMsWUFBbUIsSUFBWSxFQUFFLFdBQW9CO1FBQ25ELEtBQUssQ0FBQyxXQUFXLElBQUksSUFBSSxDQUFDLENBQUM7UUFEVixTQUFJLEdBQUosSUFBSSxDQUFRO1FBRTdCLElBQUksQ0FBQyxJQUFJLEdBQUcsa0JBQWtCLENBQUM7SUFDakMsQ0FBQztDQUNGIn0=
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartjmap",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "private": false,
5
5
  "description": "A TypeScript JMAP toolkit (RFC 8620/8621): an event-driven mail client for reading, sending, and watching mail, plus an embeddable JMAP server core with pluggable storage.",
6
6
  "exports": {
@@ -12,12 +12,11 @@
12
12
  "author": "Task Venture Capital GmbH <hello@task.vc>",
13
13
  "license": "MIT",
14
14
  "devDependencies": {
15
- "@git.zone/tsbuild": "^4.4.0",
16
- "@git.zone/tsrun": "^2.0.3",
17
- "@git.zone/tstest": "^3.6.3",
18
- "@types/node": "^25.6.0"
15
+ "@git.zone/tsbuild": "^4.4.2",
16
+ "@git.zone/tsrun": "^2.0.5",
17
+ "@git.zone/tstest": "^3.6.6",
18
+ "@types/node": "^26.1.1"
19
19
  },
20
- "dependencies": {},
21
20
  "repository": {
22
21
  "type": "git",
23
22
  "url": "git+https://code.foss.global/push.rocks/smartjmap.git"
package/readme.md CHANGED
@@ -4,10 +4,10 @@ A TypeScript JMAP toolkit (RFC 8620/8621): an event-driven mail client for readi
4
4
 
5
5
  ## Install
6
6
 
7
- To install `@push.rocks/smartjmap`, use pnpm (or npm):
7
+ Install `@push.rocks/smartjmap` with pnpm:
8
8
 
9
9
  ```sh
10
- pnpm install @push.rocks/smartjmap
10
+ pnpm add @push.rocks/smartjmap
11
11
  ```
12
12
 
13
13
  The package has zero runtime dependencies — it uses the global `fetch`, `Request`/`Response`, and `ReadableStream` available in Node.js 18+ (and in Deno/Bun for the server core).
@@ -144,8 +144,16 @@ const message = await jmapClient.getEmail(messages[0].id);
144
144
  // Convenience: set the $seen keyword
145
145
  await jmapClient.markSeen(message.id);
146
146
 
147
+ // Mark unread, toggle flagged, move, or permanently destroy
148
+ await jmapClient.markUnseen(message.id);
149
+ await jmapClient.setFlagged(message.id, true);
150
+ await jmapClient.setMailboxes(message.id, ['archive-mailbox-id']);
151
+
147
152
  // Replace the full keywords object
148
153
  await jmapClient.setKeywords(message.id, { $seen: true, $flagged: true });
154
+
155
+ // Permanently destroy the email after any other mutations are complete
156
+ await jmapClient.destroyEmail(message.id);
149
157
  ```
150
158
 
151
159
  ### Sending Mail
@@ -220,6 +228,17 @@ await jmapClient.disconnect();
220
228
 
221
229
  `JmapServer` handles the protocol; storage is behind the `IJmapMailBackend` interface. Endpoints served: the session resource (`/.well-known/jmap` and `/jmap/session`), the API (`/jmap/api`), uploads (`/jmap/upload/{accountId}`), downloads (`/jmap/download/{accountId}/{blobId}/{name}?type=`), and StateChange pushes via SSE (`/jmap/eventsource`, RFC 8620 §7.3 with `types`, `closeafter=state`, and `ping` keepalive support).
222
230
 
231
+ Production consumers that rely on bounded streaming uploads can verify the
232
+ server surface before starting a listener:
233
+
234
+ ```typescript
235
+ import { JMAP_SERVER_STREAMING_API_VERSION } from '@push.rocks/smartjmap';
236
+
237
+ if (JMAP_SERVER_STREAMING_API_VERSION !== 1) {
238
+ throw new Error('This application requires smartjmap streaming server API v1.');
239
+ }
240
+ ```
241
+
223
242
  ### The Offline Test Server
224
243
 
225
244
  With no options, `JmapServer` uses a fresh `MemoryMailBackend` plus a static credential registry — an offline JMAP server for tests. Seed users on the server (auth) and mail on the backend (storage):
@@ -287,13 +306,19 @@ The `authenticate` option replaces the built-in Basic/Bearer registry entirely.
287
306
  import { JmapServer } from '@push.rocks/smartjmap';
288
307
 
289
308
  const jmapServer = new JmapServer({
309
+ authenticationChallenges: ['Bearer realm="jmap"'],
290
310
  authenticate: async (request: Request) => {
291
- const token = request.headers.get('x-app-token');
311
+ const match = request.headers.get('authorization')?.match(/^Bearer\s+(.+)$/i);
312
+ const token = match?.[1];
292
313
  return token === 'sesame' ? { username: 'appuser' } : null;
293
314
  },
294
315
  });
295
316
  ```
296
317
 
318
+ Set `authenticationChallenges` to the exact schemes accepted by a custom
319
+ authenticator. The built-in registry advertises both Bearer and Basic by
320
+ default.
321
+
297
322
  ### Implementing a Real Backend
298
323
 
299
324
  Implement `IJmapMailBackend` against your own store to serve real mail. All state strings are backend-owned opaque strings. The contract (all methods async except `subscribeToChanges`, which returns its unsubscribe function synchronously):
@@ -307,7 +332,7 @@ Implement `IJmapMailBackend` against your own store to serve real mail. All stat
307
332
  | `getEmailChanges(accountId, sinceState, maxChanges?)` | `Email/changes` (return null for `cannotCalculateChanges`) |
308
333
  | `setEmails(accountId, request)` | `Email/set` (creates, normalized keyword/mailbox updates, destroys) |
309
334
  | `getThreads(accountId, ids)` | `Thread/get` |
310
- | `uploadBlob(accountId, data, type)` / `getBlob(accountId, blobId)` | upload/download endpoints and raw-message access |
335
+ | `uploadBlob(accountId, data, type)` / optional `uploadBlobStream(accountId, upload)` / `getBlob(accountId, blobId)` | buffered or streaming upload, download endpoints, and raw-message access |
311
336
  | `getIdentities(accountId)` | `Identity/get` |
312
337
  | `submitEmail(accountId, submission)` | `EmailSubmission/set` — receives the resolved envelope plus the raw RFC 5322 payload; your backend performs the actual sending |
313
338
  | `subscribeToChanges(listener)` | StateChange pushes — the server's only push feed, so notify on all mutations, including those made through JMAP requests, not just out-of-band changes |
@@ -325,9 +350,15 @@ const jmapServer = new JmapServer({
325
350
 
326
351
  `MemoryMailBackend` is the reference implementation — a readable starting point for the expected semantics.
327
352
 
353
+ When `uploadBlobStream` is implemented, the server authenticates and admits the
354
+ request before reading, requires `Content-Length`, and passes `stream`, exact
355
+ `size`, media `type`, and an abort `signal`. The backend must consume exactly
356
+ `size` bytes, reserve quota before reading the stream, reject short or oversized
357
+ input, honor cancellation, and remove partial storage on failure.
358
+
328
359
  ### What the Server Advertises (and What It Doesn't)
329
360
 
330
- The session object advertises `urn:ietf:params:jmap:core`, `urn:ietf:params:jmap:mail`, and `urn:ietf:params:jmap:submission`. The core limits are enforced, not just advertised (exported as `JMAP_SERVER_LIMITS`): `maxSizeRequest` 10 MB, `maxCallsInRequest` 16, `maxObjectsInGet`/`maxObjectsInSet` 500, `maxSizeUpload` 50 MB, and `maxConcurrentRequests` 4 — violations produce request-level `limit` errors or `requestTooLarge` method errors. `maxDelayedSend` is 0 (no delayed send) and `mayCreateTopLevelMailbox` is false (no `Mailbox/set`).
361
+ The session object advertises `urn:ietf:params:jmap:core`, `urn:ietf:params:jmap:mail`, and `urn:ietf:params:jmap:submission`. The core limits are enforced, not just advertised (exported as `JMAP_SERVER_LIMITS`): `maxSizeRequest` 10 MB, `maxCallsInRequest` 16, `maxObjectsInGet`/`maxObjectsInSet` 500, `maxSizeUpload` 50 MB, `maxConcurrentRequests` 4, and per-principal `maxConcurrentUpload` 4 — violations produce request-level `limit` errors, `requestTooLarge` method errors, or upload problem details. Constructor `limits` override advertised limits; `maxConcurrentUploadServer` adds a process-wide upload ceiling, and `mapUploadError` maps storage failures to application-specific RFC 7807 responses. `maxDelayedSend` is 0 (no delayed send) and `mayCreateTopLevelMailbox` is false (no `Mailbox/set`).
331
362
 
332
363
  The request layer implements `using` validation (`unknownCapability`), `#`-prefixed back-references with full `ResultReference` `{ resultOf, name, path }` JSON-pointer resolution including `/*` array expansion, client-provided `createdIds` maps, and `unknownMethod`/`invalidArguments`/`serverFail` error responses.
333
364
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartjmap',
6
- version: '2.0.0',
6
+ version: '2.1.0',
7
7
  description: 'A TypeScript JMAP toolkit (RFC 8620/8621): an event-driven mail client for reading, sending, and watching mail, plus an embeddable JMAP server core with pluggable storage.'
8
8
  }