@orkestrel/scaffold 0.0.66 → 0.0.68
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/dist/bin/main.js +67 -44
- package/dist/bin/main.js.map +1 -1
- package/dist/host/agents/templates/brief.md +9 -0
- package/dist/host/claude/agents/orkestrel.md +8 -8
- package/dist/host/claude/rules/names.md +15 -0
- package/dist/host/claude/rules/tests.md +33 -4
- package/dist/host/claude/rules/workspace.md +14 -2
- package/dist/host/dotfiles/prettierignore +3 -0
- package/dist/host/guides/README.md +65 -0
- package/dist/host/guides/abort.md +169 -0
- package/dist/host/guides/agent.md +1509 -0
- package/dist/host/guides/brief.md +1266 -0
- package/dist/host/guides/browser.md +2200 -0
- package/dist/host/guides/budget.md +196 -0
- package/dist/host/guides/codec.md +519 -0
- package/dist/host/guides/console.md +785 -0
- package/dist/host/guides/contract.md +1193 -0
- package/dist/host/guides/csv.md +541 -0
- package/dist/host/guides/database.md +2518 -0
- package/dist/host/guides/emitter.md +233 -0
- package/dist/host/guides/form.md +1791 -0
- package/dist/host/guides/html.md +717 -0
- package/dist/host/guides/indexeddb.md +505 -0
- package/dist/host/guides/interpret.md +1029 -0
- package/dist/host/guides/lsp.md +515 -0
- package/dist/host/guides/markdown.md +964 -0
- package/dist/host/guides/mcp.md +5554 -0
- package/dist/host/guides/middleware.md +927 -0
- package/dist/host/guides/msg.md +440 -0
- package/dist/host/guides/ndjson.md +120 -0
- package/dist/host/guides/ollama.md +380 -0
- package/dist/host/guides/pool.md +280 -0
- package/dist/host/guides/probe.md +1210 -0
- package/dist/host/guides/process.md +1620 -0
- package/dist/host/guides/program.md +1110 -0
- package/dist/host/guides/qualifier.md +854 -0
- package/dist/host/guides/queue.md +370 -0
- package/dist/host/guides/rater.md +330 -0
- package/dist/host/guides/reason.md +1122 -0
- package/dist/host/guides/relation.md +373 -0
- package/dist/host/guides/router.md +753 -0
- package/dist/host/guides/scaffold.md +192 -31
- package/dist/host/guides/sea.md +383 -0
- package/dist/host/guides/server.md +752 -0
- package/dist/host/guides/sqlite.md +330 -0
- package/dist/host/guides/sse.md +187 -0
- package/dist/host/guides/supervisor.md +4890 -0
- package/dist/host/guides/table.md +1556 -0
- package/dist/host/guides/template.md +280 -0
- package/dist/host/guides/terminal.md +1145 -0
- package/dist/host/guides/test.md +2969 -0
- package/dist/host/guides/timeout.md +252 -0
- package/dist/host/guides/tool.md +311 -0
- package/dist/host/guides/toolbox.md +1038 -0
- package/dist/host/guides/websocket.md +282 -0
- package/dist/host/guides/worker.md +615 -0
- package/dist/host/guides/workflow.md +1507 -0
- package/dist/host/guides/workspace.md +595 -0
- package/dist/host/manifest.json +1218 -10
- package/dist/host/tests/policy.test.ts +279 -2
- package/dist/host/tests/setupPolicy.ts +437 -6
- package/dist/src/core/index.cjs +44 -22
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +33 -9
- package/dist/src/core/index.d.ts +33 -9
- package/dist/src/core/index.js +43 -23
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +1750 -1567
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +106 -24
- package/dist/src/server/index.d.ts +106 -24
- package/dist/src/server/index.js +1751 -1570
- package/dist/src/server/index.js.map +1 -1
- package/package.json +9 -9
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
# MSG
|
|
2
|
+
|
|
3
|
+
> A zero-dependency parser for Outlook `.msg` (CFB/OLE2 compound binary) and `.eml`
|
|
4
|
+
> (RFC 2822 / MIME) email files, projecting either format into one structured `EmailChain`.
|
|
5
|
+
|
|
6
|
+
A pure-ES encoding layer (Base64, UTF-8, Latin-1, Windows-1252, quoted-printable, RFC 2047
|
|
7
|
+
encoded words) and the CFB sector and directory machinery in `parsers.ts`, `helpers.ts`, and
|
|
8
|
+
`shapers.ts` back either format without a `TextDecoder` dependency, so the whole surface stays
|
|
9
|
+
usable in the core's DOM/Node-free environment. Source: [`src/core`](../src/core). Surfaced
|
|
10
|
+
through the `@src/core` barrel.
|
|
11
|
+
|
|
12
|
+
## Surface
|
|
13
|
+
|
|
14
|
+
One `MSG` class parses either format. Construction is eager: the constructor
|
|
15
|
+
either fully parses the input or throws a typed `MSGError`. A parsed instance
|
|
16
|
+
exposes the structured `chain` (`EmailChain`) for either format. For `.msg`
|
|
17
|
+
input it also exposes the raw MAPI field tree (`fields`) plus `attachment` and
|
|
18
|
+
`burn` access.
|
|
19
|
+
|
|
20
|
+
Parse a raw file's bytes without knowing its format ahead of time — `.eml` or
|
|
21
|
+
`.msg` — and narrow the `Result` before touching the parsed chain. `createMSG`
|
|
22
|
+
is the `Result`-returning dual of `new MSG()`: every parse failure surfaces as
|
|
23
|
+
a `Failure<MSGError>` rather than throwing, and an unexpected non-`MSGError`
|
|
24
|
+
error still propagates. Reach for `new MSG()` directly when a thrown
|
|
25
|
+
`MSGError` is the control flow you want:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { createMSG, isSuccess } from '@orkestrel/msg'
|
|
29
|
+
|
|
30
|
+
const result = createMSG({ bytes, name: 'message.eml' })
|
|
31
|
+
if (isSuccess(result)) {
|
|
32
|
+
const msg = result.value
|
|
33
|
+
console.log(msg.chain.format)
|
|
34
|
+
console.log(msg.chain.messages[0].text)
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Types
|
|
39
|
+
|
|
40
|
+
From [`types.ts`](../src/core/types.ts). A `Shape` cell holds an interface's data members as bare
|
|
41
|
+
names in braces, `?` marking an optional member and `plus` introducing its call-signature members,
|
|
42
|
+
and a type alias's own type literal with a union's arms escaped as `\|`. `MSGInterface`'s
|
|
43
|
+
call-signature members are documented under [`## Methods`](#methods).
|
|
44
|
+
|
|
45
|
+
| Type | Kind | Shape | Summary |
|
|
46
|
+
| --------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
47
|
+
| `Success<T>` | interface | `{ success: true, value }` | Represents a successful `Result`, carrying the value the operation produced. |
|
|
48
|
+
| `Failure<E>` | interface | `{ success: false, error }` | Represents a failed `Result`, carrying the error the operation produced. |
|
|
49
|
+
| `Result<T, E>` | type | `Success<T> \| Failure<E>` | Represents a discriminated union for operations that can succeed or fail safely. |
|
|
50
|
+
| `MSGEncoding` | type | `'utf-8' \| 'utf-16le' \| 'windows-1252' \| 'latin1'` | Names a supported text encoding for decoding non-Unicode MSG strings and MIME part bodies. |
|
|
51
|
+
| `MSGErrorCode` | type | `'UNSUPPORTED' \| 'MALFORMED' \| 'CYCLE' \| 'RANGE' \| 'BURN'` | Names a machine-readable classification for an `MSGError`. |
|
|
52
|
+
| `MSGFieldType` | type | `'string' \| 'unicode' \| 'binary' \| 'time' \| 'integer' \| 'boolean'` | Names a MAPI property data type tag. |
|
|
53
|
+
| `MSGRecipientRole` | type | `'to' \| 'cc' \| 'bcc'` | Names a recipient role in a message. |
|
|
54
|
+
| `MSGDirectoryEntry` | interface | `{ category, name, previousProperty, nextProperty, childProperty, startBlock, sizeBlock, children? }` | Represents a CFB directory entry describing a storage or stream in the compound file. |
|
|
55
|
+
| `MSGMutableFieldData` | interface | `{ category, attachments?, recipients?, innerMSGContent?, innerMSGContentFields?, dataId?, contentLength?, folderId?, [key: string] }` | Represents an internal accumulator for MSG field extraction whose members are all readonly. The extraction path writes each resolved field through `Object.assign`, then narrows the accumulator to `MSGFieldData` at the public boundary. |
|
|
56
|
+
| `MSGNameIdEntry` | interface | `{ useName, name?, propertySet?, propertyLid? }` | Represents a resolved named property entry from the `__nameid_version1.0` storage. |
|
|
57
|
+
| `MSGBurnerEntry` | interface | `{ name, category, length, binaryProvider?, children? }` | Describes one CFB entry for `burnCFB`, the compound-file binary writer. Entries form a flat list whose root storage sits at index 0, each entry's children reachable through its `children` indices. |
|
|
58
|
+
| `MSGBurnerLiteEntry` | interface | `{ entry, left, right, child, firstSector, mini, red }` | Represents an internal lite entry with tree metadata used during CFB burn. Tracks red-black coloring and sector allocation alongside the source MSGBurnerEntry. |
|
|
59
|
+
| `MSGFieldData` | interface | `{ category, subject?, senderName?, senderEmail?, senderAddressType?, senderSMTPAddress?, sentRepresentingSMTPAddress?, body?, headers?, bodyHTML?, html?, compressedRTF?, messageClass?, messageFlags?, messageId?, internetCodepage?, messageCodepage?, messageLocaleId?, clientSubmitTime?, messageDeliveryTime?, creationTime?, lastModificationTime?, lastModifierName?, creatorSMTPAddress?, lastModifierSMTPAddress?, preview?, conversationTopic?, normalizedSubject?, name?, email?, addressType?, smtpAddress?, recipientRole?, extension?, fileNameShort?, fileName?, contentId?, attachmentHidden?, mimeType?, contentLength?, dataId?, folderId?, innerMSGContent?, innerMSGContentFields?, attachments?, recipients?, departmentName?, middleName?, generation?, surname?, givenName?, companyName?, jobTitle?, location?, postalAddress?, streetAddress?, postalCode?, country?, stateOrProvince?, homePhone?, mobilePhone?, businessPhone?, businessFax?, businessHomePage?, namePrefix?, homeAddressCity?, appointmentStart?, appointmentEnd?, clipStart?, clipEnd?, timeZoneDescription?, appointmentLocation?, appointmentOldLocation?, globalAppointmentId?, votingResponse?, internetAccountName?, yomiFirstName?, yomiLastName?, yomiCompanyName?, primaryEmailAddress?, primaryEmailDisplayName?, primaryEmailOriginalDisplayName?, fileUnder?, workAddressCity?, workAddressStreet?, workAddressState?, workAddressPostalCode?, workAddressCountry?, workAddressCountryCode?, addressCountryCode?, contactWebPage?, workAddress?, instantMessagingAddress?, fax1AddressType?, fax1EmailAddress?, fax1OriginalDisplayName?, fax2AddressType?, fax2EmailAddress?, fax2OriginalDisplayName?, fax3AddressType?, fax3EmailAddress?, fax3OriginalDisplayName? }` | Holds the field data parsed out of one MSG entity — the root message, an attachment, or a recipient — across its email, recipient, attachment, contact, and appointment fields. |
|
|
60
|
+
| `MSGAttachment` | interface | `{ name, bytes }` | Holds extracted attachment content from an MSG file. |
|
|
61
|
+
| `MSGSourceInterface` | interface | `{} plus parse, attachment` | Represents the parsed MSG source `extractMessageFromMSG` reads from: the field tree plus indexed attachment access. |
|
|
62
|
+
| `EmailFormat` | type | `'eml' \| 'msg'` | Names a supported email file format. |
|
|
63
|
+
| `MIMEHeader` | interface | `{ value, params }` | Represents a parsed MIME header: its primary value and its parameter map. |
|
|
64
|
+
| `MIMEPart` | interface | `{ headers, body, parts }` | Represents a recursive MIME part tree node. |
|
|
65
|
+
| `EmailAttachment` | interface | `{ name, mimeType, bytes }` | Represents an attachment extracted from an email message. |
|
|
66
|
+
| `EmailMessage` | interface | `{ from, to, cc, subject, date, text, html, attachments }` | Represents a structured email message extracted from a parsed file. |
|
|
67
|
+
| `EmailChain` | interface | `{ format, messages }` | Represents a parsed email chain from a single file. |
|
|
68
|
+
| `EmailInput` | interface | `{ bytes, name?, mime? }` | Represents the raw email input handed to `createMSG` or `new MSG()`. |
|
|
69
|
+
| `MSGInput` | type | `Uint8Array \| ArrayBuffer \| EmailInput` | Represents the raw input `createMSG` and `new MSG()` accept: binary MSG bytes, an `ArrayBuffer` over them, or an `EmailInput` carrying a file-name or MIME hint. |
|
|
70
|
+
| `MSGOptions` | interface | `{ encoding? }` | Configures the creation of an `MSGInterface`. |
|
|
71
|
+
| `MSGInterface` | interface | `{ options, chain, fields } plus attachment, burn` | Exposes the public surface of a parsed MSG/EML file. |
|
|
72
|
+
|
|
73
|
+
### Constants
|
|
74
|
+
|
|
75
|
+
The CFB layout offsets and sizes, the MSG/EML sniffing tables, the MAPI field name mappings, and the burner's geometry, from [`constants.ts`](../src/core/constants.ts). None of them carries runtime behavior. Every value is a fixed offset, a size, or a lookup table that the parsing and burning code in `MSG.ts`, `parsers.ts`, and `helpers.ts` reads.
|
|
76
|
+
|
|
77
|
+
A `Shape` cell holds the constant's declared type.
|
|
78
|
+
|
|
79
|
+
| Constant | Kind | Shape | Summary |
|
|
80
|
+
| ----------------------------------- | ----- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
81
|
+
| `MSG_FILE_HEADER` | const | `readonly number[]` | Holds the 8-byte CFB/OLE2 magic signature (`D0 CF 11 E0 A1 B1 1A E1`) a `.msg` file must open with. |
|
|
82
|
+
| `MSG_UNUSED_BLOCK` | const | `number` | Names the FAT and mini-FAT sentinel for an unallocated sector (`-1`). |
|
|
83
|
+
| `MSG_END_OF_CHAIN` | const | `number` | Names the FAT and mini-FAT sentinel marking a sector chain's end (`-2`). |
|
|
84
|
+
| `MSG_SECTOR_SIZE` | const | `number` | Holds the standard CFB sector size in bytes (512), read when the header's sector shift is `MSG_S_BIG_BLOCK_MARK` and written by every burn. |
|
|
85
|
+
| `MSG_S_BIG_BLOCK_MARK` | const | `number` | Holds the header sector-shift value selecting `MSG_SECTOR_SIZE`, `9` (the byte at offset 30). |
|
|
86
|
+
| `MSG_L_BIG_BLOCK_SIZE` | const | `number` | Holds the large CFB sector size in bytes (4096), read when the header's sector shift is `MSG_L_BIG_BLOCK_MARK`. |
|
|
87
|
+
| `MSG_L_BIG_BLOCK_MARK` | const | `number` | Holds the header sector-shift value selecting `MSG_L_BIG_BLOCK_SIZE`, `12` (the byte at offset 30). |
|
|
88
|
+
| `MSG_MINI_SECTOR_SIZE` | const | `number` | Holds the CFB mini-stream sector size in bytes (64), read by the parse path and written by every burn. |
|
|
89
|
+
| `MSG_MINI_STREAM_CUTOFF` | const | `number` | Sets the stream size (4096) at and above which a stream leaves the mini-stream for standard sectors. |
|
|
90
|
+
| `MSG_HEADER_PROPERTY_START_OFFSET` | const | `number` | Locates the root directory sector's start within the CFB header (`0x30`). |
|
|
91
|
+
| `MSG_HEADER_BAT_START_OFFSET` | const | `number` | Locates the DIFAT's first 109 FAT sector entries within the CFB header (`0x4c`). |
|
|
92
|
+
| `MSG_HEADER_BAT_COUNT_OFFSET` | const | `number` | Locates the total FAT sector count within the CFB header (`0x2c`). |
|
|
93
|
+
| `MSG_HEADER_SBAT_START_OFFSET` | const | `number` | Locates the mini-FAT's first sector within the CFB header (`0x3c`). |
|
|
94
|
+
| `MSG_HEADER_SBAT_COUNT_OFFSET` | const | `number` | Locates the mini-FAT sector count within the CFB header (`0x40`). |
|
|
95
|
+
| `MSG_HEADER_XBAT_START_OFFSET` | const | `number` | Locates the first DIFAT (XBAT) sector within the CFB header (`0x44`). |
|
|
96
|
+
| `MSG_HEADER_XBAT_COUNT_OFFSET` | const | `number` | Locates the DIFAT (XBAT) sector count within the CFB header (`0x48`). |
|
|
97
|
+
| `MSG_PROP_NO_INDEX` | const | `number` | Names the directory-entry sentinel standing for no such property in the previous, next, and child index fields (`-1`). |
|
|
98
|
+
| `MSG_MAX_HIERARCHY_DEPTH` | const | `number` | Caps the recursion depth of the directory-tree traversal, guarding against a cyclic or hostile property chain (`64`). |
|
|
99
|
+
| `MSG_DIRECTORY_ENTRY_SIZE` | const | `number` | Holds the fixed size in bytes (128) of one CFB directory entry, read by the parse path and written by every burn. |
|
|
100
|
+
| `MSG_PROP_NAME_SIZE_OFFSET` | const | `number` | Locates the entry name's UTF-16 byte length within a directory entry (`0x40`). |
|
|
101
|
+
| `MSG_PROP_CATEGORY_OFFSET` | const | `number` | Locates the object-category byte within a directory entry, mirroring the Compound File Binary object type field (`0x42`). |
|
|
102
|
+
| `MSG_PROP_PREVIOUS_PROPERTY_OFFSET` | const | `number` | Locates the red-black tree's previous sibling index within a directory entry (`0x44`). |
|
|
103
|
+
| `MSG_PROP_NEXT_PROPERTY_OFFSET` | const | `number` | Locates the red-black tree's next sibling index within a directory entry (`0x48`). |
|
|
104
|
+
| `MSG_PROP_CHILD_PROPERTY_OFFSET` | const | `number` | Locates the first child storage index within a directory entry (`0x4c`). |
|
|
105
|
+
| `MSG_PROP_START_BLOCK_OFFSET` | const | `number` | Locates the entry's starting sector of stream data within a directory entry (`0x74`). |
|
|
106
|
+
| `MSG_PROP_SIZE_OFFSET` | const | `number` | Locates the stream byte length within a directory entry (`0x78`). |
|
|
107
|
+
| `MSG_CATEGORY_UNALLOCATED` | const | `number` | Names the directory-entry category byte for an unallocated (free) slot (`0`). |
|
|
108
|
+
| `MSG_CATEGORY_DIRECTORY` | const | `number` | Names the directory-entry category byte for a storage (folder-like) entry (`1`). |
|
|
109
|
+
| `MSG_CATEGORY_DOCUMENT` | const | `number` | Names the directory-entry category byte for a stream (document) entry (`2`). |
|
|
110
|
+
| `MSG_CATEGORY_ROOT` | const | `number` | Names the directory-entry category byte for the single root storage entry (`5`). |
|
|
111
|
+
| `MSG_PREFIX_ATTACHMENT` | const | `string` | Holds the storage name prefix for an attachment entry (`'__attach_version1.0'`). |
|
|
112
|
+
| `MSG_PREFIX_RECIPIENT` | const | `string` | Holds the storage name prefix for a recipient entry (`'__recip_version1.0'`). |
|
|
113
|
+
| `MSG_PREFIX_DOCUMENT` | const | `string` | Holds the stream name prefix for a MAPI property document (substg) entry (`'__substg1.'`). |
|
|
114
|
+
| `MSG_PREFIX_NAMEID` | const | `string` | Holds the storage name of the named-property mapping table (`'__nameid_version1.0'`). |
|
|
115
|
+
| `MSG_FIELD_NAME_MAPPING` | const | `Readonly<Record<string, string>>` | Maps a MAPI property tag's hex to its short field name, for example `subject`. |
|
|
116
|
+
| `MSG_FIELD_FULL_NAME_MAPPING` | const | `Readonly<Record<string, string>>` | Maps a compound tag's full 8-character hex to its fully-qualified field name. |
|
|
117
|
+
| `MSG_FIELD_TYPE_MAPPING` | const | `Readonly<Record<string, MSGFieldType>>` | Maps a MAPI property type tag's hex to its `MSGFieldType` decode tag. |
|
|
118
|
+
| `MSG_FIELD_CLASS_ATTACHMENT_DATA` | const | `string` | Identifies the MAPI tag naming an attachment's binary data stream (`'3701'`). |
|
|
119
|
+
| `MSG_FIELD_DIR_TYPE_INNER_MSG` | const | `string` | Names the MAPI type tag identifying an embedded `.msg` attachment storage (`'000d'`). |
|
|
120
|
+
| `MSG_MAPI_RECIPIENT_TO` | const | `number` | Names the MAPI recipient-type value mapping to `MSGRecipientRole` `'to'` (`1`). |
|
|
121
|
+
| `MSG_MAPI_RECIPIENT_CC` | const | `number` | Names the MAPI recipient-type value mapping to `MSGRecipientRole` `'cc'` (`2`). |
|
|
122
|
+
| `MSG_MAPI_RECIPIENT_BCC` | const | `number` | Names the MAPI recipient-type value mapping to `MSGRecipientRole` `'bcc'` (`3`). |
|
|
123
|
+
| `MSG_PIDLID_MAPPING` | const | `Readonly<Record<string, Readonly<Record<number, string>>>>` | Maps a named-property set GUID to its long-ID-to-field-name table. |
|
|
124
|
+
| `MSG_BURNER_INTS_PER_SECTOR` | const | `number` | Holds the number of 32-bit FAT and DIFAT entries one standard sector carries (128). |
|
|
125
|
+
| `MSG_BURNER_DIFAT_HEADER_SLOTS` | const | `number` | Caps the DIFAT entries stored directly in the CFB header (109). |
|
|
126
|
+
| `MSG_BURNER_FAT_SECTOR_MARKER` | const | `number` | Marks a sector as itself part of the FAT (-3). |
|
|
127
|
+
| `MSG_BURNER_DIFAT_SECTOR_MARKER` | const | `number` | Marks a sector as part of the DIFAT (-4). |
|
|
128
|
+
| `MSG_BURNER_NAME_MAX` | const | `number` | Caps the UTF-16 code units a written CFB directory entry name may hold (31). |
|
|
129
|
+
| `MSG_BURNER_ROOT_CLSID` | const | `readonly number[]` | Holds the 16-byte CLSID `burnCFB` writes for a compound file's root storage entry. |
|
|
130
|
+
| `EML_EXTENSIONS` | const | `readonly string[]` | Lists the file-name extensions sniffed as the `'eml'` `EmailFormat`, the RFC 2822 / MIME email files. |
|
|
131
|
+
| `MSG_EXTENSIONS` | const | `readonly string[]` | Lists the file-name extensions sniffed as the `'msg'` `EmailFormat`, the Outlook binary email files. |
|
|
132
|
+
| `EML_MIME_TYPES` | const | `readonly string[]` | Lists the MIME types sniffed as the `'eml'` `EmailFormat`, the RFC 2822 / MIME email files. |
|
|
133
|
+
| `MSG_MIME_TYPES` | const | `readonly string[]` | Lists the MIME types sniffed as the `'msg'` `EmailFormat`, the Outlook binary email files. |
|
|
134
|
+
| `FALLBACK_CHARSET` | const | `string` | Names the charset `resolveEncoding` falls back to when a MIME part body's label is unrecognized (`'utf-8'`). |
|
|
135
|
+
| `FALLBACK_ATTACHMENT_NAME` | const | `string` | Names the file name `extractMessage` falls back to for an attachment part carrying no `filename` or `name` parameter (`'attachment'`). |
|
|
136
|
+
| `MIME_EXTENSIONS` | const | `ReadonlyMap<string, string>` | Maps a common MIME type to the file extension `inferExtension` gives it during file extraction. |
|
|
137
|
+
| `MIME_MAX_DEPTH` | const | `number` | Caps the multipart nesting depth `parseMIMEPart` accepts (50). |
|
|
138
|
+
| `UTF8_SEQUENCE_MINIMUM` | const | `Readonly<Record<number, number>>` | Holds the minimum valid code point for each UTF-8 sequence length, keyed by the number of continuation bytes (1, 2, or 3). |
|
|
139
|
+
| `WINDOWS_1252_HIGH` | const | `readonly number[]` | Holds the Windows-1252 code point table for the high bytes `0x80`\-`0x9F`. |
|
|
140
|
+
|
|
141
|
+
### Errors
|
|
142
|
+
|
|
143
|
+
From [`errors.ts`](../src/core/errors.ts) — every MSG/EML parsing or burning failure `throw`s (or, for `createMSG`, returns) an `MSGError` carrying a machine-readable `code`.
|
|
144
|
+
|
|
145
|
+
| Symbol | Kind | Signature | Summary |
|
|
146
|
+
| ------------ | -------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
147
|
+
| `MSGError` | class | `new MSGError(code, message, context?)` | Represents an error thrown or returned by the MSG/EML parsing and burning surfaces, carrying a machine-readable `MSGErrorCode` and optional structured `context`. |
|
|
148
|
+
| `isMSGError` | function | `(value: unknown) => value is MSGError` | Narrows an unknown caught (or `Failure.error`) value to an `MSGError`. |
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { isMSGError, MSGError } from '@orkestrel/msg'
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
throw new MSGError('MALFORMED', 'bad input', { offset: 8 })
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (isMSGError(error) && error.code === 'MALFORMED') console.log(error.context)
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Helpers
|
|
161
|
+
|
|
162
|
+
Pure, mostly-total leaves from [`helpers.ts`](../src/core/helpers.ts) — the `Result` constructors/guards, the CFB byte/string/UUID readers and magic check `MSG.ts` composes, the format sniffer, and the MIME/text codecs `parsers.ts` and `shapers.ts` compose.
|
|
163
|
+
|
|
164
|
+
| Helper | Kind | Signature | Summary |
|
|
165
|
+
| --------------------- | -------- | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
166
|
+
| `success` | function | `<T>(value: T) => Success<T>` | Constructs a `Success` wrapping a value. |
|
|
167
|
+
| `failure` | function | `<E>(error: E) => Failure<E>` | Constructs a `Failure` wrapping an error. |
|
|
168
|
+
| `isSuccess` | function | `<T, E>(result: Result<T, E>) => result is Success<T>` | Narrows a `Result` to a `Success`. |
|
|
169
|
+
| `isFailure` | function | `<T, E>(result: Result<T, E>) => result is Failure<E>` | Narrows a `Result` to a `Failure`. |
|
|
170
|
+
| `truncateAtNull` | function | `(text: string) => string` | Truncates a string at its first `\0` character. |
|
|
171
|
+
| `readUTF16String` | function | `(view: DataView, offset: number, charCount: number) => string` | Reads a UTF-16LE string out of a `DataView`, throwing when the requested range exceeds the view's bounds. |
|
|
172
|
+
| `decodeText` | function | `(bytes: Uint8Array, encoding?: MSGEncoding) => string` | Decodes bytes into a string with a pure-ES decoder, dispatching on the named encoding. Default: `windows-1252`. |
|
|
173
|
+
| `fileTimeToUTCString` | function | `(low: number, high: number) => string` | Converts a Windows FILETIME (100-ns ticks since 1601-01-01) to a UTC date string. |
|
|
174
|
+
| `toHexLower` | function | `(value: number, length: number) => string` | Converts a number to a zero-padded lowercase hex string of the requested digit count. |
|
|
175
|
+
| `readMicrosoftUUID` | function | `(bytes: Uint8Array, offset: number) => string` | Reads a mixed-endian Microsoft UUID starting at an offset in a byte array, throwing past the array's bounds. |
|
|
176
|
+
| `roundUpToMultiple` | function | `(value: number, boundary: number) => number` | Rounds a value up to the nearest multiple of a boundary, which must be a power of 2. |
|
|
177
|
+
| `computeSectors` | function | `(bytes: number, sectorSize: number) => number` | Computes how many sectors of a given size hold a byte count, answering 0 for a count at or below zero. |
|
|
178
|
+
| `compareCFBName` | function | `(a: string, b: string) => number` | Orders two directory names as the compound file format requires, comparing by UTF-16 length first and then by uppercased code points. |
|
|
179
|
+
| `isMSGFile` | function | `(view: DataView) => boolean` | Reports whether a `DataView`'s first 8 bytes match the CFB magic signature. |
|
|
180
|
+
| `decodeBase64` | function | `(text: string) => Uint8Array` | Decodes a Base64 string into raw bytes without relying on `atob`, ignoring ASCII whitespace and tolerating missing padding. |
|
|
181
|
+
| `encodeUTF8` | function | `(text: string) => Uint8Array` | Encodes a string into UTF-8 bytes, pairing surrogates and encoding a lone (unpaired) surrogate as U+FFFD. |
|
|
182
|
+
| `decodeUTF8` | function | `(bytes: Uint8Array) => string` | Decodes UTF-8 bytes into a string WHATWG-style: an invalid byte sequence decodes as U+FFFD rather than throwing. |
|
|
183
|
+
| `decodeLatin1` | function | `(bytes: Uint8Array) => string` | Decodes Latin-1 (ISO-8859-1) bytes into a string, byte-for-code-point. |
|
|
184
|
+
| `decodeWindows1252` | function | `(bytes: Uint8Array) => string` | Decodes Windows-1252 bytes into a string, resolving the `0x80`\-`0x9F` range through `WINDOWS_1252_HIGH` and otherwise matching `decodeLatin1`. |
|
|
185
|
+
| `resolveEncoding` | function | `(label: string \| undefined) => MSGEncoding` | Resolves a free-form charset label, as a MIME `charset` parameter carries it, to a supported `MSGEncoding`, falling back to `FALLBACK_CHARSET`'s encoding for an unrecognized or absent label. |
|
|
186
|
+
| `detectFormat` | function | `(name?: string, mime?: string) => EmailFormat \| undefined` | Derives the `EmailFormat` from a file name, a MIME type, or both, answering `undefined` when neither hints at a format. |
|
|
187
|
+
| `parseMIMEHeaders` | function | `(text: string) => ReadonlyMap<string, MIMEHeader>` | Parses an RFC 2822 / MIME header block, folding continuation lines. |
|
|
188
|
+
| `decodeMIMEEncoding` | function | `(body: string, encoding: string) => Uint8Array` | Decodes a MIME body — `base64`, `quoted-printable`, or passthrough — into raw bytes, throwing on invalid Base64. |
|
|
189
|
+
| `decodeMIMEText` | function | `(body: string, encoding: string, charset: string) => string` | Decodes a MIME body to text through `decodeMIMEEncoding` and `resolveEncoding`, taking the charset from a free-form label. |
|
|
190
|
+
| `decodeMIMEWords` | function | `(text: string) => string` | Decodes the RFC 2047 encoded words (`=?charset?B/Q?...?=`) in a header value, reading the Base64 (`B`) and quoted-printable (`Q`) forms alike. |
|
|
191
|
+
| `formatEmailAddress` | function | `(name: string \| undefined, email: string \| undefined) => string` | Formats a display name and an email address into `"Name <email>"`, or into whichever half is present. |
|
|
192
|
+
| `inferExtension` | function | `(mimeType?: string, fileName?: string) => string` | Infers an attachment's file extension from its file name or its MIME type, returning the extension with its leading dot and falling back to `.bin`. |
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
import {
|
|
196
|
+
success,
|
|
197
|
+
failure,
|
|
198
|
+
isSuccess,
|
|
199
|
+
isFailure,
|
|
200
|
+
truncateAtNull,
|
|
201
|
+
readUTF16String,
|
|
202
|
+
fileTimeToUTCString,
|
|
203
|
+
toHexLower,
|
|
204
|
+
readMicrosoftUUID,
|
|
205
|
+
roundUpToMultiple,
|
|
206
|
+
computeSectors,
|
|
207
|
+
compareCFBName,
|
|
208
|
+
isMSGFile,
|
|
209
|
+
decodeBase64,
|
|
210
|
+
encodeUTF8,
|
|
211
|
+
decodeUTF8,
|
|
212
|
+
decodeLatin1,
|
|
213
|
+
decodeWindows1252,
|
|
214
|
+
resolveEncoding,
|
|
215
|
+
detectFormat,
|
|
216
|
+
parseMIMEHeaders,
|
|
217
|
+
decodeMIMEEncoding,
|
|
218
|
+
decodeMIMEText,
|
|
219
|
+
formatEmailAddress,
|
|
220
|
+
inferExtension,
|
|
221
|
+
} from '@orkestrel/msg'
|
|
222
|
+
|
|
223
|
+
truncateAtNull('abc\0def') // 'abc'
|
|
224
|
+
toHexLower(255, 4) // '00ff'
|
|
225
|
+
roundUpToMultiple(10, 8) // 16
|
|
226
|
+
computeSectors(100, 64) // 2
|
|
227
|
+
compareCFBName('a', 'b') // negative
|
|
228
|
+
isMSGFile(new DataView(new Uint8Array(8).buffer)) // false — no CFB magic
|
|
229
|
+
detectFormat('message.eml', undefined) // 'eml'
|
|
230
|
+
isSuccess(success(1)) // true
|
|
231
|
+
isFailure(failure(new Error())) // true
|
|
232
|
+
decodeLatin1(new Uint8Array([65])) // 'A'
|
|
233
|
+
decodeWindows1252(new Uint8Array([65])) // 'A'
|
|
234
|
+
resolveEncoding('utf-8') // 'utf-8'
|
|
235
|
+
formatEmailAddress('A', 'a@x.dev') // 'A <a@x.dev>'
|
|
236
|
+
inferExtension('image/png') // '.png'
|
|
237
|
+
decodeMIMEEncoding('aGk=', 'base64') // Uint8Array of 'hi'
|
|
238
|
+
decodeMIMEText('aGk=', 'base64', 'utf-8') // 'hi'
|
|
239
|
+
encodeUTF8('hi') // Uint8Array
|
|
240
|
+
decodeUTF8(new Uint8Array([65])) // 'A'
|
|
241
|
+
|
|
242
|
+
const view = new DataView(new Uint8Array([0x48, 0x00, 0x69, 0x00]).buffer)
|
|
243
|
+
readUTF16String(view, 0, 2) // 'Hi'
|
|
244
|
+
fileTimeToUTCString(0, 0) // a UTC date string
|
|
245
|
+
readMicrosoftUUID(new Uint8Array(16), 0) // a UUID string
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Shapers
|
|
249
|
+
|
|
250
|
+
The value shapers from [`shapers.ts`](../src/core/shapers.ts) build a finished value out of an already-parsed source. `burnCFB` compiles a flat readonly entry graph into a standalone binary, without mutating the caller's descriptors. `extractMessage` and `extractMessageFromMSG` project a parsed MIME tree or a parsed MSG field tree into an `EmailMessage`.
|
|
251
|
+
|
|
252
|
+
| Shaper | Kind | Signature | Summary |
|
|
253
|
+
| ----------------------- | -------- | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
254
|
+
| `burnCFB` | function | `(entries: readonly MSGBurnerEntry[]) => Uint8Array` | Reconstitutes a valid CFB (Compound Binary File) binary from a flat list of `MSGBurnerEntry` descriptors — root storage at index 0, its children reachable through `children` indices. |
|
|
255
|
+
| `extractMessageFromMSG` | function | `(reader: MSGSourceInterface) => EmailMessage` | Extracts one `EmailMessage` from the field data and attachments of a parsed MSG source; a corrupt attachment is skipped rather than fatal. |
|
|
256
|
+
| `extractMessage` | function | `(part: MIMEPart) => EmailMessage` | Extracts one `EmailMessage` by walking a parsed `MIMEPart` tree for its text, HTML, and attachments. |
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
import {
|
|
260
|
+
burnCFB,
|
|
261
|
+
extractMessage,
|
|
262
|
+
extractMessageFromMSG,
|
|
263
|
+
MSG_CATEGORY_ROOT,
|
|
264
|
+
parseMIMEPart,
|
|
265
|
+
} from '@orkestrel/msg'
|
|
266
|
+
import type { MSGAttachment, MSGBurnerEntry, MSGFieldData } from '@orkestrel/msg'
|
|
267
|
+
|
|
268
|
+
const entries: readonly MSGBurnerEntry[] = [
|
|
269
|
+
{ name: 'Root Entry', category: MSG_CATEGORY_ROOT, length: 0 },
|
|
270
|
+
]
|
|
271
|
+
burnCFB(entries) // Uint8Array — a standalone CFB binary
|
|
272
|
+
|
|
273
|
+
const part = parseMIMEPart('Subject: Hi\n\nBody text')
|
|
274
|
+
extractMessage(part) // EmailMessage — { from: '', to: [], subject: 'Hi', text: 'Body text', ... }
|
|
275
|
+
|
|
276
|
+
const fields: MSGFieldData = { category: 'msg', subject: 'Hi' }
|
|
277
|
+
extractMessageFromMSG({
|
|
278
|
+
parse: () => fields,
|
|
279
|
+
attachment: (index: number): MSGAttachment => ({
|
|
280
|
+
name: `a${index}`,
|
|
281
|
+
bytes: new Uint8Array(0),
|
|
282
|
+
}),
|
|
283
|
+
}) // EmailMessage
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Parsers
|
|
287
|
+
|
|
288
|
+
MIME-tree parsing from [`parsers.ts`](../src/core/parsers.ts) — the recursive coercion from raw text to a typed tree, built on `helpers.ts`'s header parser.
|
|
289
|
+
|
|
290
|
+
| Parser | Kind | Signature | Summary |
|
|
291
|
+
| --------------- | -------- | ------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
292
|
+
| `parseMIMEPart` | function | `(raw: string, depth?: number) => MIMEPart` | Parses raw RFC 2822 / MIME text into a `MIMEPart` tree, throwing past `MIME_MAX_DEPTH` levels of nesting. |
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
import { parseMIMEPart } from '@orkestrel/msg'
|
|
296
|
+
|
|
297
|
+
parseMIMEPart('Subject: Hi\n\nBody text') // MIMEPart — { headers, body: 'Body text', parts: [] }
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Validators
|
|
301
|
+
|
|
302
|
+
The from-unknown guards from [`validators.ts`](../src/core/validators.ts). Each validates an arbitrary `unknown` value from scratch, against the `EmailChain`, `EmailMessage`, or `EmailAttachment` shape, the `EmailFormat` union, or the plain-record shape those checks are built on. `isMSGError` differs: it narrows a value that is already typed.
|
|
303
|
+
|
|
304
|
+
In a guard table a `Shape` cell holds the type the guard narrows to.
|
|
305
|
+
|
|
306
|
+
| Guard | Kind | Shape | Summary |
|
|
307
|
+
| ------------------- | -------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
308
|
+
| `isRecord` | function | `Record<string, unknown>` | Narrows an unknown value to a plain record: a total from-unknown guard, true for a non-null, non-array object. |
|
|
309
|
+
| `isEmailFormat` | function | `EmailFormat` | Narrows an unknown value to a valid `EmailFormat`: a total from-unknown guard, true for `'eml'` and `'msg'`. |
|
|
310
|
+
| `isEmailAttachment` | function | `EmailAttachment` | Narrows an unknown value to an `EmailAttachment`: a total from-unknown guard over `name`, `mimeType`, and `bytes`. |
|
|
311
|
+
| `isEmailMessage` | function | `EmailMessage` | Narrows an unknown value to an `EmailMessage`: a total from-unknown guard over every member, validating `attachments` recursively through `isEmailAttachment`. |
|
|
312
|
+
| `isEmailChain` | function | `EmailChain` | Narrows an unknown value to an `EmailChain`: a total from-unknown guard over `format` and `messages`, validating `messages` recursively through `isEmailMessage`. |
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
import {
|
|
316
|
+
isEmailAttachment,
|
|
317
|
+
isEmailChain,
|
|
318
|
+
isEmailFormat,
|
|
319
|
+
isEmailMessage,
|
|
320
|
+
isRecord,
|
|
321
|
+
} from '@orkestrel/msg'
|
|
322
|
+
|
|
323
|
+
isRecord({}) // true
|
|
324
|
+
isEmailFormat('eml') // true
|
|
325
|
+
isEmailAttachment({ name: 'a.txt', mimeType: 'text/plain', bytes: new Uint8Array() }) // true
|
|
326
|
+
isEmailMessage({
|
|
327
|
+
from: '',
|
|
328
|
+
to: [],
|
|
329
|
+
cc: [],
|
|
330
|
+
subject: '',
|
|
331
|
+
date: undefined,
|
|
332
|
+
text: '',
|
|
333
|
+
html: '',
|
|
334
|
+
attachments: [],
|
|
335
|
+
}) // true
|
|
336
|
+
isEmailChain({ format: 'eml', messages: [] }) // true
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Classes
|
|
340
|
+
|
|
341
|
+
The one implementing class, from [`MSG.ts`](../src/core/MSG.ts) — documented in full under its own
|
|
342
|
+
heading following this table. `MSGError` is a class too, and sits in the [`### Errors`](#errors)
|
|
343
|
+
table beside the guard that narrows to it.
|
|
344
|
+
|
|
345
|
+
| Class | Kind | Summary |
|
|
346
|
+
| ----- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
347
|
+
| `MSG` | class | Parses raw `.eml` or `.msg` file bytes into a structured `EmailChain`, exposing — for `.msg` input — the raw MAPI field tree, attachment binary access, and CFB reconstitution. |
|
|
348
|
+
|
|
349
|
+
### `MSG`
|
|
350
|
+
|
|
351
|
+
The implementing class of `MSGInterface`, from [`MSG.ts`](../src/core/MSG.ts). Construction is eager and total-or-throw. `new MSG(input, options?)` walks the CFB sector and directory chains with `DataView` for `.msg` input, bounds-checking every offset and cycle-guarding every chain. It runs the pure-ES MIME parser for `.eml` input. Input it cannot parse throws a typed `MSGError` rather than a raw `RangeError` — `UNSUPPORTED` for an unrecognized format, and `MALFORMED`, `CYCLE`, or `RANGE` for a structurally invalid one. `chain` exposes the parsed `EmailChain` for either format, and `chain.format` distinguishes them. `fields` exposes the raw MAPI field tree, present only for `'msg'` input. `attachment(index)` and `burn()` reconstitute different things and are never rewired into each other: `attachment(index)` rebuilds one embedded `.msg` from that attachment's own stored subtree, and `burn()` rebuilds the whole file the instance was constructed from. See [`## Methods`](#methods) for its public call-signature surface.
|
|
352
|
+
|
|
353
|
+
```ts
|
|
354
|
+
import { MSG } from '@orkestrel/msg'
|
|
355
|
+
|
|
356
|
+
const msg = new MSG({ bytes, name: 'message.eml' })
|
|
357
|
+
msg.options // {} when not configured; the encoding default is applied at read time
|
|
358
|
+
msg.chain.format // 'eml' | 'msg'
|
|
359
|
+
msg.chain.messages[0].text
|
|
360
|
+
msg.fields // undefined for 'eml' input; MSGFieldData for 'msg' input
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Factories
|
|
364
|
+
|
|
365
|
+
From [`factories.ts`](../src/core/factories.ts).
|
|
366
|
+
|
|
367
|
+
| Factory | Kind | Signature | Summary |
|
|
368
|
+
| ----------- | -------- | --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
369
|
+
| `createMSG` | function | `(input: MSGInput, options?: MSGOptions) => Result<MSGInterface, MSGError>` | Creates an `MSGInterface` for raw `.eml` or `.msg` input and returns it inside a `Result`: every parse failure surfaces as a `Failure` carrying the `MSGError` instead of throwing, and an unexpected non-`MSGError` error still propagates. |
|
|
370
|
+
|
|
371
|
+
#### Parse an email file and read its format
|
|
372
|
+
|
|
373
|
+
Narrows the `createMSG` `Result` with `isSuccess` before reading the parsed `chain`'s format.
|
|
374
|
+
|
|
375
|
+
```ts
|
|
376
|
+
import { createMSG, isSuccess } from '@orkestrel/msg'
|
|
377
|
+
|
|
378
|
+
const result = createMSG(bytes)
|
|
379
|
+
if (isSuccess(result)) {
|
|
380
|
+
console.log(result.value.chain.format)
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
## Methods
|
|
385
|
+
|
|
386
|
+
The public methods of the behavioral interfaces this package publishes. `options`, `chain`, and `fields` are readonly properties of `MSGInterface`, Surface-documented earlier; each table here lists exactly its interface's call-signature members.
|
|
387
|
+
|
|
388
|
+
#### `MSGInterface`
|
|
389
|
+
|
|
390
|
+
| Method | Returns | Summary |
|
|
391
|
+
| ------------ | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
392
|
+
| `attachment` | `MSGAttachment` | Reads attachment binary content by zero-based index, returning its `name` and `bytes`. Requires `'msg'` input: `'eml'` input carries no MAPI field tree, so every index throws — read an `.eml` file's attachments from `chain.messages[0].attachments` instead. |
|
|
393
|
+
| `burn` | `Uint8Array` | Rebuilds the whole parsed message as a standalone CFB/`.msg` binary, from the directory entry list and allocated sector map read during construction. |
|
|
394
|
+
|
|
395
|
+
#### `MSGSourceInterface`
|
|
396
|
+
|
|
397
|
+
| Method | Returns | Summary |
|
|
398
|
+
| ------------ | --------------- | ----------------------------------------------------------------------------------------- |
|
|
399
|
+
| `parse` | `MSGFieldData` | Reads the parsed MAPI field tree `extractMessageFromMSG` projects into an `EmailMessage`. |
|
|
400
|
+
| `attachment` | `MSGAttachment` | Reads attachment binary content by zero-based index, returning its `name` and `bytes`. |
|
|
401
|
+
|
|
402
|
+
```ts
|
|
403
|
+
import { createMSG, isSuccess } from '@orkestrel/msg'
|
|
404
|
+
|
|
405
|
+
const result = createMSG(bytes) // Uint8Array of a .msg file
|
|
406
|
+
if (isSuccess(result)) {
|
|
407
|
+
const msg = result.value
|
|
408
|
+
const first = msg.attachment(0) // { name, bytes } — throws MSGError('RANGE') if none exists
|
|
409
|
+
const rebuilt = msg.burn() // Uint8Array — a standalone CFB/.msg binary
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
## Round-trip semantics
|
|
414
|
+
|
|
415
|
+
`burn()` and `burnCFB` are reconstitution, not byte-identity: for a parsed `.msg` `msg`, re-parsing `msg.burn()` yields an EQUIVALENT parsed model (`new MSG(msg.burn()).fields` structurally matches `msg.fields`, and `isMSGFile` passes on the rebuilt bytes) — the rebuilt binary is not guaranteed to be byte-identical to the original file. Sector padding, directory ordering, and unused-space contents may differ; only the parsed shape and CFB validity are guaranteed to round-trip.
|
|
416
|
+
|
|
417
|
+
## Embedded vs. top-level burn
|
|
418
|
+
|
|
419
|
+
Two burn paths exist and are never rewired into each other:
|
|
420
|
+
|
|
421
|
+
- **`attachment(index)`** — when an attachment is itself an embedded `.msg` (`innerMSGContent === true`), its stored directory entry selects that attachment's own storage subtree for CFB reconstitution.
|
|
422
|
+
- **`burn()`** — rebuilds the whole parsed message, from the directory entry list and allocated sector map the constructor read out of the input file.
|
|
423
|
+
|
|
424
|
+
A caller extracting an embedded `.msg` attachment and burning it standalone goes through `attachment()`; rebuilding the file `MSG` was constructed from goes through `burn()`.
|
|
425
|
+
|
|
426
|
+
## Tests
|
|
427
|
+
|
|
428
|
+
- [`tests/guides.test.ts`](../tests/guides.test.ts) — the `## Surface` ↔ `src/core` bijection (value and type exports), the `MSGInterface` ↔ `MSG` method bijection, and the equality gate: every `Summary` cell against its declaration's description paragraph, the titled `Parse an email file and read its format` fence against the `@example` block of that title (pinned so the titled pair cannot be retired silently), and the README pitch against this guide's tagline. It also runs the flagship fences and asserts the values their comments claim.
|
|
429
|
+
- [`tests/src/core/MSG.test.ts`](../tests/src/core/MSG.test.ts) — construction (`.eml` / `.msg` / malformed input), `chain`, `fields`, `attachment`, `burn`, and the embedded-`.msg` extraction path.
|
|
430
|
+
- [`tests/src/core/factories.test.ts`](../tests/src/core/factories.test.ts) — `createMSG`'s `Result` contract (`Success`/`Failure`, with parse failures surfaced as `Failure<MSGError>` rather than thrown).
|
|
431
|
+
- [`tests/src/core/parsers.test.ts`](../tests/src/core/parsers.test.ts) — `isMSGFile` / `decodeUTF8` / `detectFormat` / `parseMIMEPart` / `extractMessage` / `extractMessageFromMSG`, incl. `MIME_MAX_DEPTH` cycle guarding.
|
|
432
|
+
- [`tests/src/core/helpers.test.ts`](../tests/src/core/helpers.test.ts) — the `Result` constructors/guards, CFB byte/string/UUID readers, and MIME/text codecs.
|
|
433
|
+
- [`tests/src/core/shapers.test.ts`](../tests/src/core/shapers.test.ts) — `burnCFB` validity, mini-stream/FAT/DIFAT boundaries, red-black directory ordering, cycle/name limits, and real-parser round trips.
|
|
434
|
+
- [`tests/src/core/validators.test.ts`](../tests/src/core/validators.test.ts) — `isRecord` / `isEmailFormat` / `isEmailAttachment` / `isEmailMessage` / `isEmailChain` soundness on well-formed and malformed input.
|
|
435
|
+
- `MSGError` shape and `isMSGError` narrowing are covered across [`tests/src/core/MSG.test.ts`](../tests/src/core/MSG.test.ts), [`tests/src/core/factories.test.ts`](../tests/src/core/factories.test.ts), and [`tests/src/core/helpers.test.ts`](../tests/src/core/helpers.test.ts) — no standalone `errors.test.ts` file exists.
|
|
436
|
+
|
|
437
|
+
## See also
|
|
438
|
+
|
|
439
|
+
- [`AGENTS.md`](../AGENTS.md) — the coding contract, and the rule files its rule map names: `.claude/rules/typescript.md` § Errors and outcomes owns the `Result` and throw pattern, and `.claude/rules/documentation.md` § Parity owns documentation-as-contracts.
|
|
440
|
+
- [`README.md`](README.md) — the guides index.
|