marc-ts 0.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.
package/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+
2
+
3
+ Copyright 2026 James Fournie <james.fournie@gmail.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,642 @@
1
+ # marc-ts
2
+
3
+ > TypeScript MARC21 library for Node.js and browsers
4
+
5
+ [![npm version](https://img.shields.io/npm/v/marc-ts.svg)](https://www.npmjs.com/package/marc-ts)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Features
9
+
10
+ - **Immutable API** - All operations return new objects, never mutate existing records
11
+ - **Zero runtime dependencies** - Works in browsers and Node.js (≥14) without any dependencies
12
+ - **Type-safe** - Full TypeScript type definitions with strict typing
13
+ - **Well-tested** - >90% code coverage with comprehensive test suite
14
+ - **Universal** - Runs in Node.js and modern browsers (Chrome, Firefox, Safari, Edge)
15
+ - **Functional design** - Pure functions for composability and predictability
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install marc-ts
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```typescript
26
+ import { parseMarcRecord, serializeMarcRecord, title, author, isbn, subjects } from 'marc-ts';
27
+ import { parseMarcXml, serializeMarcXml } from 'marc-ts/xml';
28
+ import { parseMarcJson, serializeMarcJsonString } from 'marc-ts/json';
29
+ import { parseMarcTxt, serializeMarcTxt } from 'marc-ts/txt';
30
+
31
+ // --- ISO 2709 binary (MARC21) ---
32
+ const buffer = new Uint8Array([...]); // Your MARC21 binary data
33
+ const result = parseMarcRecord(buffer);
34
+
35
+ if (result.record) {
36
+ console.log('Title:', title(result.record));
37
+ console.log('Author:', author(result.record));
38
+ console.log('ISBNs:', isbn(result.record));
39
+ console.log('Subjects:', subjects(result.record));
40
+ }
41
+ if (result.warnings.length > 0) {
42
+ console.warn('Parsing warnings:', result.warnings);
43
+ }
44
+
45
+ // Serialize back to binary (UTF-8 by default; pass { encoding: 'marc8' } for MARC-8 output)
46
+ const binary = serializeMarcRecord(result.record!, { encoding: 'utf8' });
47
+
48
+ // --- MARCXML ---
49
+ const xmlString = `<?xml version="1.0"?>
50
+ <collection xmlns="http://www.loc.gov/MARC21/slim">
51
+ <record>
52
+ <leader>00000nam a2200000 4500</leader>
53
+ <datafield tag="245" ind1="1" ind2="0">
54
+ <subfield code="a">The Hobbit</subfield>
55
+ </datafield>
56
+ </record>
57
+ </collection>`;
58
+
59
+ const [xmlRecord] = parseMarcXml(xmlString);
60
+ console.log('Title from XML:', title(xmlRecord));
61
+ const roundtripXml = serializeMarcXml([xmlRecord]); // back to a <collection> document
62
+
63
+ // --- MARC-in-JSON ---
64
+ const jsonString = JSON.stringify({
65
+ leader: '00000nam a2200000 4500',
66
+ fields: [
67
+ { '245': { subfields: [{ a: 'The Hobbit' }], ind1: '1', ind2: '0' } },
68
+ ],
69
+ });
70
+
71
+ const jsonRecord = parseMarcJson(jsonString);
72
+ console.log('Title from JSON:', title(jsonRecord));
73
+ const roundtripJson = serializeMarcJsonString(jsonRecord); // back to a JSON string
74
+
75
+ // --- MARCBreaker (marctxt) ---
76
+ const txtString = `=LDR 00000nam a2200000 4500
77
+ =001 5490
78
+ =245 10$aThe Hobbit /$cJ.R.R. Tolkien.
79
+ `;
80
+
81
+ const [txtRecord] = parseMarcTxt(txtString);
82
+ console.log('Title from marctxt:', title(txtRecord));
83
+ const roundtripTxt = serializeMarcTxt([txtRecord]); // back to marctxt string
84
+
85
+ // --- MARC-8 binary ---
86
+ // parseMarcRecord detects MARC-8 automatically from leader byte 9 (' ');
87
+ // records are decoded to Unicode transparently — no special handling needed.
88
+ const marc8Buffer = new Uint8Array([...]); // MARC-8 encoded binary
89
+ const marc8Result = parseMarcRecord(marc8Buffer); // decoded to Unicode automatically
90
+ ```
91
+
92
+ ## Why marc-ts?
93
+
94
+ Existing JavaScript/TypeScript MARC libraries are often:
95
+ - Node.js-only (using streams, fs, Buffer APIs)
96
+ - Class-based OOP patterns that don't leverage TypeScript's strengths
97
+ - Mutable APIs that can lead to unexpected bugs
98
+ - Lacking comprehensive type definitions
99
+
100
+ **marc-ts** addresses these limitations:
101
+ - Universal browser and Node.js compatibility
102
+ - TypeScript-native with full type safety and functional patterns
103
+ - Immutable operations for safer, more predictable code
104
+ - Zero runtime dependencies for minimal bundle size
105
+
106
+ ## Core Concepts
107
+
108
+ ### Immutability
109
+
110
+ Mutation-style operations in **marc-ts** return new records or fields rather than modifying existing ones:
111
+
112
+ ```typescript
113
+ const updated = appendField(record, field); // record remains unchanged
114
+ ```
115
+
116
+ This approach prevents accidental mutations and makes code easier to reason about, especially in reactive frameworks like React or Vue.
117
+
118
+ ### Functional API
119
+
120
+ **marc-ts** uses pure functions for maximum composability:
121
+
122
+ ```typescript
123
+ // Extract metadata using pure functions
124
+ const bookTitle = title(record);
125
+ const bookAuthor = author(record);
126
+
127
+ // Access fields functionally
128
+ const titleField = getField(record, '245');
129
+ ```
130
+
131
+ ### Type Safety
132
+
133
+ Full TypeScript types ensure compile-time correctness:
134
+
135
+ ```typescript
136
+ import type { MarcRecord, DataField } from 'marc-ts';
137
+ import { isDataField } from 'marc-ts';
138
+
139
+ const field = getField(record, '245');
140
+ if (field && isDataField(field)) {
141
+ // TypeScript knows field is a DataField
142
+ const titleValue = getSubfield(field, 'a');
143
+ }
144
+ ```
145
+
146
+ ## API Reference
147
+
148
+ ### Parsing and Serialization
149
+
150
+ #### `parseMarcRecord(buffer, options?): ParseResult`
151
+
152
+ Parse ISO2709 binary data into a MARC record.
153
+
154
+ ```typescript
155
+ const result = parseMarcRecord(buffer, {
156
+ strict: false, // If true, throw on fatal parse errors
157
+ maxWarnings: 100, // Maximum warnings to collect
158
+ });
159
+
160
+ if (result.record) {
161
+ // Successfully parsed
162
+ } else {
163
+ // Parsing failed, check result.warnings
164
+ }
165
+ ```
166
+
167
+ Recoverable issues may still be returned in `warnings`, such as MARC leader compatibility warnings.
168
+
169
+ #### `parseMarcRecordStrict(buffer): MarcRecord`
170
+
171
+ Convenience wrapper for strict parsing (throws on fatal parse errors).
172
+
173
+ ```typescript
174
+ try {
175
+ const record = parseMarcRecordStrict(buffer);
176
+ } catch (error) {
177
+ console.error('Parsing failed:', error);
178
+ }
179
+ ```
180
+
181
+ #### `serializeMarcRecord(record): Uint8Array`
182
+
183
+ Serialize a MARC record to ISO2709 binary format.
184
+
185
+ ```typescript
186
+ const buffer = serializeMarcRecord(record);
187
+ // Can be written to file or transmitted over network
188
+ ```
189
+
190
+ `parseMarcRecord` decodes UTF-8 records and MARC-8 records signaled by leader
191
+ byte 9. MARC-8 decoding handles escape-designated scripts such as ANSEL Latin,
192
+ Greek, Hebrew, Cyrillic, Arabic, subscript/superscript, and mapped EACC/CJK
193
+ triples. MARC-8 serialization is intentionally conservative: `encoding:
194
+ 'marc8'` writes ASCII plus ANSEL Latin/combining characters and replaces
195
+ unsupported Unicode characters with `?`.
196
+
197
+ **EACC coverage caveat:** the bundled EACC table maps only ~33 of the ~16,000
198
+ official triples. Records with substantial Chinese/Japanese/Korean content
199
+ will mostly decode to U+FFFD. For CJK catalogs, prefer UTF-8 sources
200
+ (`leader[9] === 'a'`).
201
+
202
+ **Surfacing lossy MARC-8 encoding:** because `serializeMarcRecord` returns a
203
+ plain `Uint8Array`, lossy substitutions are invisible to callers. Use
204
+ `serializeMarcRecordWithWarnings(record, { encoding: 'marc8' })` to get
205
+ `{ bytes, warnings }` — any character that could not be encoded surfaces as
206
+ an `encoding_error` warning. For just-the-encoder visibility, use
207
+ `unicodeToMarc8WithStats(text)` to get `{ bytes, lossyCount }`.
208
+
209
+ ### Convenience Accessors
210
+
211
+ Extract common bibliographic metadata:
212
+
213
+ | Function | Field | Description | Example |
214
+ |----------|-------|-------------|---------|
215
+ | `title(record)` | 245 $a$b | Full title with subtitle | `"The Catcher in the Rye"` |
216
+ | `titleProper(record)` | 245 $a | Main title only | `"The Catcher in the Rye"` |
217
+ | `author(record)` | 100/110 $a | Main author/creator | `"Salinger, J. D."` |
218
+ | `edition(record)` | 250 $a | Edition statement | `"1st ed."` |
219
+ | `publisher(record)` | 260/264 $b | Publisher name | `"Little, Brown,"` |
220
+ | `publicationDate(record)` | 260/264 $c | Publication date | `"1951."` |
221
+ | `isbn(record)` | 020 $a | ISBN(s) - array | `["978-0-316-76948-0"]` |
222
+ | `issn(record)` | 022 $a | ISSN | `"0028-0836"` |
223
+ | `lccn(record)` | 010 $a | Library of Congress Control Number | `"50011915"` |
224
+ | `subjects(record)` | 6XX $a | All subject headings - array | `["Fiction", "History"]` |
225
+ | `seriesStatement(record)` | 490 $a | Series statement | `"Penguin classics"` |
226
+
227
+ ### Field Access
228
+
229
+ #### `getField(record, tag): ControlField | DataField | undefined`
230
+
231
+ Get the first field with a specific tag.
232
+
233
+ ```typescript
234
+ const titleField = getField(record, '245');
235
+ ```
236
+
237
+ #### `getFields(record, tag): (ControlField | DataField)[]`
238
+
239
+ Get all fields with a specific tag.
240
+
241
+ ```typescript
242
+ const subjectFields = getFields(record, '650');
243
+ ```
244
+
245
+ #### `getSubfield(field, code): string | undefined`
246
+
247
+ Get the first subfield value from a data field.
248
+
249
+ ```typescript
250
+ const field = getField(record, '245');
251
+ if (field && isDataField(field)) {
252
+ const titleValue = getSubfield(field, 'a');
253
+ }
254
+ ```
255
+
256
+ #### `getSubfields(field, code): string[]`
257
+
258
+ Get all subfield values with a specific code (for repeatable subfields).
259
+
260
+ ```typescript
261
+ const field = getField(record, '650');
262
+ if (field && isDataField(field)) {
263
+ const subdivisions = getSubfields(field, 'x');
264
+ }
265
+ ```
266
+
267
+ #### `getAllSubfields(field): Array<{ code: string; value: string }>`
268
+
269
+ Get all subfields from a data field.
270
+
271
+ ```typescript
272
+ const field = getField(record, '245');
273
+ if (field && isDataField(field)) {
274
+ const allSubfields = getAllSubfields(field);
275
+ }
276
+ ```
277
+
278
+ ### Wildcard Querying
279
+
280
+ #### `getFieldsByPattern(record, pattern): (ControlField | DataField)[]`
281
+
282
+ Match fields using wildcard patterns (`.` or `X` = any digit).
283
+
284
+ ```typescript
285
+ // Get all 6XX subject fields
286
+ const subjects = getFieldsByPattern(record, '6..');
287
+
288
+ // Get all 7XX added entry fields
289
+ const addedEntries = getFieldsByPattern(record, '7XX');
290
+
291
+ // Get all X00 fields (100, 200, ..., 900)
292
+ const x00Fields = getFieldsByPattern(record, 'X00');
293
+ ```
294
+
295
+ #### `getFirstFieldByPattern(record, pattern): ControlField | DataField | undefined`
296
+
297
+ Get the first field matching a wildcard pattern.
298
+
299
+ ```typescript
300
+ const firstSubject = getFirstFieldByPattern(record, '6..');
301
+ ```
302
+
303
+ ### Field Operations (Immutable)
304
+
305
+ All operations return new records/fields without mutating the original.
306
+
307
+ #### `appendField(record, field): MarcRecord`
308
+
309
+ Append a field to the end of a record.
310
+
311
+ ```typescript
312
+ const newField: DataField = {
313
+ tag: '650',
314
+ indicator1: ' ',
315
+ indicator2: '0',
316
+ subfields: [{ code: 'a', value: 'New subject' }],
317
+ };
318
+
319
+ const updated = appendField(record, newField);
320
+ // record is unchanged, updated has the new field
321
+ ```
322
+
323
+ #### `insertFieldBefore(record, tag, field): MarcRecord`
324
+
325
+ Insert a field before the first occurrence of a tag.
326
+
327
+ ```typescript
328
+ const updated = insertFieldBefore(record, '700', newField);
329
+ ```
330
+
331
+ #### `insertFieldAfter(record, tag, field): MarcRecord`
332
+
333
+ Insert a field after the first occurrence of a tag.
334
+
335
+ ```typescript
336
+ const updated = insertFieldAfter(record, '245', newField);
337
+ ```
338
+
339
+ #### `insertGroupedField(record, field): MarcRecord`
340
+
341
+ Insert a field maintaining MARC block order (00X → 0XX → 1XX → ... → 9XX).
342
+
343
+ ```typescript
344
+ const updated = insertGroupedField(record, field);
345
+ // Field is inserted in proper MARC order
346
+ ```
347
+
348
+ #### `removeFields(record, tag): MarcRecord`
349
+
350
+ Remove all fields with a specific tag.
351
+
352
+ ```typescript
353
+ const updated = removeFields(record, '650');
354
+ ```
355
+
356
+ #### `removeField(record, field): MarcRecord`
357
+
358
+ Remove a specific field instance using reference equality.
359
+
360
+ ```typescript
361
+ const field = getField(record, '650');
362
+ const updated = field ? removeField(record, field) : record;
363
+ ```
364
+
365
+ #### Subfield Operations
366
+
367
+ ```typescript
368
+ // Add subfield to a field
369
+ const updated = addSubfield(field, 'b', 'Subtitle');
370
+
371
+ // Remove all subfields with code
372
+ const updated = removeSubfield(field, 'x');
373
+
374
+ // Replace first subfield with code
375
+ const updated = replaceSubfield(field, 'a', 'New value');
376
+ ```
377
+
378
+ ### Clone and Equality
379
+
380
+ #### `cloneRecord(record): MarcRecord`
381
+
382
+ Create a deep copy of a record.
383
+
384
+ ```typescript
385
+ const copy = cloneRecord(record);
386
+ // Modifying copy will not affect record
387
+ ```
388
+
389
+ #### `recordsEqual(a, b, ignoreFieldOrder?): boolean`
390
+
391
+ Check if two records are equal.
392
+
393
+ ```typescript
394
+ if (recordsEqual(record1, record2)) {
395
+ console.log('Records are identical');
396
+ }
397
+
398
+ // Ignore field order
399
+ if (recordsEqual(record1, record2, true)) {
400
+ console.log('Records have same content');
401
+ }
402
+ ```
403
+
404
+ #### `fieldsEqual(a, b): boolean`
405
+
406
+ Check if two fields are equal.
407
+
408
+ ```typescript
409
+ if (fieldsEqual(field1, field2)) {
410
+ console.log('Fields are identical');
411
+ }
412
+ ```
413
+
414
+ ### Warnings
415
+
416
+ #### `createWarning(type, message, position?, tag?): MarcWarning`
417
+
418
+ Create a parsing warning object.
419
+
420
+ ```typescript
421
+ const warning = createWarning('invalid_field', 'Field is out of bounds', 42, '245');
422
+ ```
423
+
424
+ ## Additional Formats
425
+
426
+ ### MARCXML (`marc-ts/xml`)
427
+
428
+ Import from the `marc-ts/xml` subpath for MARCXML support (Library of Congress schema).
429
+
430
+ ```typescript
431
+ import {
432
+ parseMarcXml,
433
+ parseMarcXmlRecord,
434
+ serializeMarcXml,
435
+ serializeMarcXmlRecord,
436
+ } from 'marc-ts/xml';
437
+ ```
438
+
439
+ #### `parseMarcXml(xml): MarcRecord[]`
440
+
441
+ Parse a MARCXML string containing a `<collection>` or one or more bare `<record>` elements.
442
+
443
+ ```typescript
444
+ const records = parseMarcXml(xmlString);
445
+ // Returns all records found in the document
446
+ ```
447
+
448
+ #### `parseMarcXmlRecord(xml): MarcRecord`
449
+
450
+ Parse a MARCXML string expected to contain exactly one `<record>`. Throws if none is found.
451
+
452
+ ```typescript
453
+ const record = parseMarcXmlRecord(xmlString);
454
+ ```
455
+
456
+ #### `serializeMarcXml(records): string`
457
+
458
+ Serialize one or more records into a full MARCXML `<collection>` document (with XML declaration).
459
+
460
+ ```typescript
461
+ const xml = serializeMarcXml([record1, record2]);
462
+ ```
463
+
464
+ #### `serializeMarcXmlRecord(record): string`
465
+
466
+ Serialize a single record to a `<record>` XML element string (no collection wrapper or XML declaration).
467
+
468
+ ```typescript
469
+ const recordXml = serializeMarcXmlRecord(record);
470
+ ```
471
+
472
+ ---
473
+
474
+ ### MARC-in-JSON (`marc-ts/json`)
475
+
476
+ Import from the `marc-ts/json` subpath for [MARC-in-JSON](https://wiki.code4lib.org/MARCJSONification) support (used by Open Library and many REST APIs).
477
+
478
+ ```typescript
479
+ import {
480
+ parseMarcJson,
481
+ serializeMarcJson,
482
+ serializeMarcJsonString,
483
+ } from 'marc-ts/json';
484
+ import type { MarcJsonObject } from 'marc-ts/json';
485
+ ```
486
+
487
+ The format represents each field as a single-key object in an array:
488
+
489
+ ```json
490
+ {
491
+ "leader": "01142cam a2200301 a 4500",
492
+ "fields": [
493
+ { "001": "5490" },
494
+ { "245": { "subfields": [{ "a": "The Hobbit" }], "ind1": "1", "ind2": "0" } }
495
+ ]
496
+ }
497
+ ```
498
+
499
+ #### `parseMarcJson(json): MarcRecord`
500
+
501
+ Parse a MARC-in-JSON object or JSON string into a `MarcRecord`. Throws on structural errors.
502
+
503
+ ```typescript
504
+ const record = parseMarcJson(jsonString); // from a JSON string
505
+ const record = parseMarcJson(jsonObject); // from a plain object
506
+ ```
507
+
508
+ #### `serializeMarcJson(record): MarcJsonObject`
509
+
510
+ Serialize a `MarcRecord` to a MARC-in-JSON plain object.
511
+
512
+ ```typescript
513
+ const obj = serializeMarcJson(record);
514
+ // obj.leader, obj.fields — ready for JSON.stringify or further processing
515
+ ```
516
+
517
+ #### `serializeMarcJsonString(record): string`
518
+
519
+ Serialize a `MarcRecord` directly to a JSON string.
520
+
521
+ ```typescript
522
+ const json = serializeMarcJsonString(record);
523
+ ```
524
+
525
+ ---
526
+
527
+ ### MARCBreaker / marctxt (`marc-ts/txt`)
528
+
529
+ Import from the `marc-ts/txt` subpath for MARCBreaker support. This format (also called MARCMaker or marctxt) is a human-readable line-oriented representation originated by the Library of Congress MARCMaker/MARCBreaker tools and widely used for editing MARC data in plain text.
530
+
531
+ ```typescript
532
+ import {
533
+ parseMarcTxt,
534
+ parseMarcTxtRecord,
535
+ serializeMarcTxt,
536
+ serializeMarcTxtRecord,
537
+ } from 'marc-ts/txt';
538
+ ```
539
+
540
+ Each field occupies one line. Blank indicators are written as `\`. Subfields use `$` followed by a single-character code. Records are separated by blank lines:
541
+
542
+ ```
543
+ =LDR 00706cam a2200217 a 4500
544
+ =001 5490
545
+ =003 OCoLC
546
+ =245 14$aThe Hobbit /$cJ.R.R. Tolkien.
547
+ =650 \1$aHobbits (Fictitious characters)$vFiction.
548
+ ```
549
+
550
+ **Value escaping.** Standard MARCBreaker has no way to represent a literal `$`
551
+ in a value, and `marc-ts` follows the same convention as other tools for the
552
+ remaining reserved characters:
553
+
554
+ - `$` → `{dollar}`
555
+ - `{` → `{lcub}`, `}` → `{rcub}`
556
+ - `\` → `{bsol}`
557
+
558
+ Embedded newlines (`\n`) in field values are replaced with a space on
559
+ serialize, matching the behavior of other MARCBreaker tools. Source values that
560
+ do not contain any of these characters are emitted verbatim.
561
+
562
+ #### `parseMarcTxt(text): MarcRecord[]`
563
+
564
+ Parse a marctxt string containing one or more records separated by blank lines. Accepts both `\n` and `\r\n` line endings.
565
+
566
+ ```typescript
567
+ const records = parseMarcTxt(txtString);
568
+ // Returns all records found
569
+ ```
570
+
571
+ #### `parseMarcTxtRecord(text): MarcRecord`
572
+
573
+ Parse a marctxt string expected to contain exactly one record. Throws if none is found.
574
+
575
+ ```typescript
576
+ const record = parseMarcTxtRecord(txtString);
577
+ ```
578
+
579
+ #### `serializeMarcTxt(records): string`
580
+
581
+ Serialize one or more records into a marctxt string, with records separated by blank lines.
582
+
583
+ ```typescript
584
+ const txt = serializeMarcTxt([record1, record2]);
585
+ ```
586
+
587
+ #### `serializeMarcTxtRecord(record): string`
588
+
589
+ Serialize a single record to marctxt (no surrounding blank line).
590
+
591
+ ```typescript
592
+ const txt = serializeMarcTxtRecord(record);
593
+ ```
594
+
595
+ ---
596
+
597
+ ## Browser Usage
598
+
599
+ **marc-ts** works in modern browsers without any bundler configuration:
600
+
601
+ ```html
602
+ <!DOCTYPE html>
603
+ <html>
604
+ <head>
605
+ <title>marc-ts Browser Example</title>
606
+ </head>
607
+ <body>
608
+ <input type="file" id="fileInput" accept=".mrc" />
609
+ <pre id="output"></pre>
610
+
611
+ <script type="module">
612
+ import { parseMarcRecord, title, author } from 'https://cdn.skypack.dev/marc-ts';
613
+
614
+ document.getElementById('fileInput').addEventListener('change', async (e) => {
615
+ const file = e.target.files[0];
616
+ const arrayBuffer = await file.arrayBuffer();
617
+ const buffer = new Uint8Array(arrayBuffer);
618
+
619
+ const result = parseMarcRecord(buffer);
620
+ if (result.record) {
621
+ document.getElementById('output').textContent = `
622
+ Title: ${title(result.record) || 'N/A'}
623
+ Author: ${author(result.record) || 'N/A'}
624
+ `;
625
+ }
626
+ });
627
+ </script>
628
+ </body>
629
+ </html>
630
+ ```
631
+
632
+ ## Examples
633
+
634
+ See the [examples/](./examples/) directory for more examples:
635
+ - [basic-usage.ts](./examples/basic-usage.ts) - Common usage patterns
636
+ - [browser.html](./examples/browser.html) - Browser integration
637
+
638
+ ## Development
639
+
640
+ Requires Node.js **20.19** or **22.12+** (driven by Vite 8). Older Node versions
641
+ are EOL and will fail to install the dev toolchain. The compiled output is
642
+ compatible with modern browsers and any actively-supported Node release.
@@ -0,0 +1,53 @@
1
+ import { MarcRecord, ControlField, DataField } from './types';
2
+ /**
3
+ * Create a deep clone of a MARC record.
4
+ *
5
+ * @param record - The MARC record to clone
6
+ * @returns A new, independent copy of the record
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const copy = cloneRecord(record);
11
+ * // Modifying copy will not affect record
12
+ * ```
13
+ */
14
+ export declare function cloneRecord(record: MarcRecord): MarcRecord;
15
+ /**
16
+ * Check if two MARC records are deeply equal.
17
+ *
18
+ * @param a - First record to compare
19
+ * @param b - Second record to compare
20
+ * @param ignoreFieldOrder - If true, records with same fields in different order are considered equal
21
+ * @returns True if records are equal
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * if (recordsEqual(record1, record2)) {
26
+ * console.log('Records are identical');
27
+ * }
28
+ *
29
+ * // Ignore field order
30
+ * if (recordsEqual(record1, record2, true)) {
31
+ * console.log('Records have same content');
32
+ * }
33
+ * ```
34
+ */
35
+ export declare function recordsEqual(a: MarcRecord, b: MarcRecord, ignoreFieldOrder?: boolean): boolean;
36
+ /**
37
+ * Check if two fields are equal.
38
+ *
39
+ * @param a - First field to compare
40
+ * @param b - Second field to compare
41
+ * @returns True if fields are equal
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * const field1 = getField(record1, '245');
46
+ * const field2 = getField(record2, '245');
47
+ * if (field1 && field2 && fieldsEqual(field1, field2)) {
48
+ * console.log('Title fields are identical');
49
+ * }
50
+ * ```
51
+ */
52
+ export declare function fieldsEqual(a: ControlField | DataField, b: ControlField | DataField): boolean;
53
+ //# sourceMappingURL=clone.d.ts.map