@seedprotocol/archive 0.0.0-stage → 0.7.11-next.1384.g8b47246

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/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ All notable changes to this package are documented in this file.
4
+
5
+ ## 0.7.10 (2026-10-24)
6
+
7
+ - New package. `createBag` and `validateBag` write and check BagIt 1.0 bags with SHA-256 and SHA-512 payload and tag manifests; `writeBagDirectory` and `readBagDirectory` (from `@seedprotocol/archive/node`) move them to and from disk. `parseArchiveDocument` reads an h-entry archive document, `rewriteHtmlUrls` splices new `src`/`href`/`poster` values into Html, and `dublinCoreXml` and `premisXml` write Dublin Core and PREMIS 3.0 metadata.
package/README.md CHANGED
@@ -1,3 +1,70 @@
1
- # Temporary Holding Version
1
+ # @seedprotocol/archive
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm](https://img.shields.io/npm/v/@seedprotocol/archive.svg)](https://www.npmjs.com/package/@seedprotocol/archive)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ Archive packaging for [Seed Protocol](https://seedprotocol.io): [BagIt](https://www.rfc-editor.org/rfc/rfc8493)
7
+ 1.0 bags with SHA-256 and SHA-512 manifests, [PREMIS 3.0](https://www.loc.gov/standards/premis/v3/)
8
+ and Dublin Core metadata, and a reader for [h-entry](https://microformats.org/wiki/h-entry) archive
9
+ documents. It works on bytes and plain objects and depends on no other Seed package, so archive
10
+ tools and servers can use it without the SDK.
11
+
12
+ To export a Seed item, use `exportArchive` from [`@seedprotocol/sdk`](https://www.npmjs.com/package/@seedprotocol/sdk),
13
+ which builds its bag with this package. The bag's layout is described in
14
+ [docs/ARCHIVE.md](https://github.com/JournoLabs/seed-protocol-sdk/blob/main/docs/ARCHIVE.md).
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @seedprotocol/archive
20
+ ```
21
+
22
+ ## Bags
23
+
24
+ ```ts
25
+ import { createBag, validateBag } from '@seedprotocol/archive'
26
+ import { writeBagDirectory, readBagDirectory } from '@seedprotocol/archive/node'
27
+
28
+ const bag = createBag({
29
+ payload: [{ path: 'index.html', bytes: html }], // paths relative to data/
30
+ tagFiles: [{ path: 'metadata/dc.xml', bytes: dcXml }], // listed in the tag manifests
31
+ info: [['Source-Organization', 'The Daily Example']],
32
+ })
33
+ await writeBagDirectory(bag, './bag') // the directory must be empty or absent
34
+
35
+ const { valid, errors } = validateBag(await readBagDirectory('./bag'))
36
+ ```
37
+
38
+ `createBag` adds Bagging-Date, Payload-Oxum and Bag-Software-Agent unless you pass them. It throws
39
+ `ArchiveError` for a path that won't survive another file system: absolute, `..` or empty
40
+ segments, backslashes, control characters, `%`, text not in Unicode NFC, or two paths that differ
41
+ only in letter case.
42
+
43
+ `validateBag` checks bagit.txt, every payload and tag manifest entry, unlisted payload files and
44
+ Payload-Oxum. It checks SHA-256 and SHA-512 manifests and reports a manifest for any other
45
+ algorithm as an error. For an independent check, run the Library of Congress validator:
46
+ `pipx run --spec bagit==1.9.0 bagit.py --validate ./bag`.
47
+
48
+ ## Archive documents
49
+
50
+ ```ts
51
+ import { parseArchiveDocument, rewriteHtmlUrls } from '@seedprotocol/archive'
52
+
53
+ const { method, content, name, summary, authors, published, featured } = parseArchiveDocument(html)
54
+ ```
55
+
56
+ The body is read from `.h-entry .e-content`, then `[itemprop=articleBody]`, then a lone
57
+ `<article>` without its `<header>` and `<footer>`, then the whole input; `method` says which.
58
+ `content` is sliced from the source string, so it comes back byte for byte. Only the h-entry
59
+ properties archives use are read (p-name, p-summary, p-author, dt-published, u-featured, u-url,
60
+ e-content); implied properties and value-class patterns are not applied.
61
+
62
+ `rewriteHtmlUrls(html, map)` replaces whole `src`, `href` and `poster` values that `map` returns a
63
+ new URL for, by splicing them into the original string. `srcset` is not rewritten.
64
+
65
+ ## Metadata
66
+
67
+ `dublinCoreXml(record)` writes an `oai_dc:dc` record; `premisXml({ objects, events, agents })`
68
+ writes PREMIS 3.0 with event and agent types from the Library of Congress preservation
69
+ vocabularies (`valueURI="http://id.loc.gov/vocabulary/preservation/eventType/cre"` and so on).
70
+ Both validate against the official schemas.
@@ -0,0 +1,72 @@
1
+ import { sha256 as e, sha512 as t } from "@noble/hashes/sha2";
2
+ import { bytesToHex as n } from "@noble/hashes/utils";
3
+ //#region src/bytes.ts
4
+ var r = ["sha256", "sha512"], i = (r, i) => n(r === "sha256" ? e(i) : t(i)), a = new TextEncoder(), o = new TextDecoder("utf-8", { fatal: !0 }), s = (e) => a.encode(e), c = (e) => {
5
+ try {
6
+ return o.decode(e);
7
+ } catch {
8
+ return;
9
+ }
10
+ }, l = class extends Error {
11
+ code;
12
+ constructor(e, t) {
13
+ super(t), this.name = "ArchiveError", this.code = e;
14
+ }
15
+ }, u = "1.0", d = "@seedprotocol/archive (https://github.com/JournoLabs/seed-protocol-sdk)", f = /^(bagit\.txt|bag-info\.txt|fetch\.txt|(tag)?manifest-[a-z0-9]+\.txt)$/, p = /[\u0000-\u001f\u007f]/, m = /^[^\s:][^:\r\n]*$/;
16
+ function h(e) {
17
+ let t = e === "" ? "is empty" : e.startsWith("/") ? "is absolute" : e.includes("\\") ? "contains a backslash" : p.test(e) ? "contains a control character" : e.includes("%") ? "contains \"%\"" : e.split("/").some((e) => e === "" || e === "." || e === "..") ? "has an empty, \".\" or \"..\" segment" : e.normalize("NFC") === e ? void 0 : "is not in Unicode NFC";
18
+ if (t) throw new l("invalid_path", `Bag path ${JSON.stringify(e)} ${t}`);
19
+ }
20
+ var g = (e) => e.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A"), _ = (e) => e.replace(/%0D/gi, "\r").replace(/%0A/gi, "\n").replace(/%25/g, "%"), v = (e, t) => t.map((t) => `${i(e, t.bytes)} ${g(t.path)}\n`).join(""), y = (e) => e.map(([e, t]) => {
21
+ if (!m.test(e)) throw new l("invalid_bag_info", `bag-info.txt label ${JSON.stringify(e)} is not valid`);
22
+ return `${e}: ${t.replace(/\r\n?/g, "\n").split("\n").join("\n ")}\n`;
23
+ }).join(""), b = (e) => e.toISOString().slice(0, 10), x = (e, t) => e.path < t.path ? -1 : +(e.path > t.path);
24
+ function S(e) {
25
+ let t = e.payload.map((e) => (h(e.path), {
26
+ path: `data/${e.path}`,
27
+ bytes: e.bytes
28
+ })), n = (e.tagFiles ?? []).map((e) => {
29
+ if (h(e.path), e.path.startsWith("data/") || f.test(e.path)) throw new l("invalid_path", `${e.path} can't be supplied as a tag file`);
30
+ return e;
31
+ }), i = /* @__PURE__ */ new Map();
32
+ for (let { path: e } of [...t, ...n]) {
33
+ let t = e.toLowerCase(), n = i.get(t);
34
+ if (n !== void 0) throw new l("duplicate_path", n === e ? `${e} appears twice` : `${n} and ${e} differ only in letter case`);
35
+ i.set(t, e);
36
+ }
37
+ t.sort(x), n.sort(x);
38
+ let a = [...e.info ?? []], o = (e) => a.some(([t]) => t.toLowerCase() === e.toLowerCase());
39
+ if (o("Bagging-Date") || a.push(["Bagging-Date", b(e.baggingDate ?? /* @__PURE__ */ new Date())]), !o("Payload-Oxum")) {
40
+ let e = t.reduce((e, t) => e + t.bytes.byteLength, 0);
41
+ a.push(["Payload-Oxum", `${e}.${t.length}`]);
42
+ }
43
+ o("Bag-Software-Agent") || a.push(["Bag-Software-Agent", e.softwareAgent ?? "@seedprotocol/archive (https://github.com/JournoLabs/seed-protocol-sdk)"]);
44
+ let c = [
45
+ {
46
+ path: "bagit.txt",
47
+ bytes: s("BagIt-Version: 1.0\nTag-File-Character-Encoding: UTF-8\n")
48
+ },
49
+ {
50
+ path: "bag-info.txt",
51
+ bytes: s(y(a))
52
+ },
53
+ ...r.map((e) => ({
54
+ path: `manifest-${e}.txt`,
55
+ bytes: s(v(e, t))
56
+ })),
57
+ ...n
58
+ ], u = r.map((e) => ({
59
+ path: `tagmanifest-${e}.txt`,
60
+ bytes: s(v(e, c))
61
+ }));
62
+ return { files: [
63
+ ...c,
64
+ ...u,
65
+ ...t
66
+ ] };
67
+ }
68
+ var C = (e, t) => e.files.find((e) => e.path === t);
69
+ //#endregion
70
+ export { _ as a, l as c, c as d, S as i, r as l, d as n, g as o, h as r, C as s, u as t, i as u };
71
+
72
+ //# sourceMappingURL=bag-BkZhe-zN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bag-BkZhe-zN.js","names":["BAG_ALGORITHMS","digestHex","bytesToHex","algorithm","sha256","bytes","sha512","encoder","decoder","utf8","text","fromUtf8","ArchiveError","message","code","BAGIT_VERSION","DEFAULT_SOFTWARE_AGENT","RESERVED_TAG_PATH","CONTROL_CHARACTER","BAG_INFO_LABEL","assertBagPath","problem","path","segment","encodeManifestPath","decodeManifestPath","field","manifestText","files","file","bagInfoText","entries","label","value","isoDate","date","byPath","a","b","createBag","payload","options","extraTags","seen","key","previous","info","has","l","octets","sum","tagFiles","tagManifests","getBagFile","bag","f"],"sources":["../src/bytes.ts","../src/errors.ts","../src/bag.ts"],"sourcesContent":["import { sha256, sha512 } from '@noble/hashes/sha2'\nimport { bytesToHex } from '@noble/hashes/utils'\n\n/** Checksum algorithms every bag carries a manifest for, in the order the manifests are written. */\nexport const BAG_ALGORITHMS = ['sha256', 'sha512'] as const\nexport type BagAlgorithm = (typeof BAG_ALGORITHMS)[number]\n\n/** Lowercase hex digest of `bytes`. @noble/hashes rather than WebCrypto, so it also runs outside secure contexts. */\nexport const digestHex = (algorithm: BagAlgorithm, bytes: Uint8Array): string =>\n bytesToHex(algorithm === 'sha256' ? sha256(bytes) : sha512(bytes))\n\nconst encoder = new TextEncoder()\nconst decoder = new TextDecoder('utf-8', { fatal: true })\n\nexport const utf8 = (text: string): Uint8Array => encoder.encode(text)\n\n/** `bytes` as UTF-8 text, or undefined when they are not valid UTF-8. */\nexport const fromUtf8 = (bytes: Uint8Array): string | undefined => {\n try {\n return decoder.decode(bytes)\n } catch {\n return undefined\n }\n}\n","export type ArchiveErrorCode = 'invalid_path' | 'duplicate_path' | 'invalid_bag_info'\n\n/** A bag that can't be built as asked. `code` says why; the message names the file or field. */\nexport class ArchiveError extends Error {\n readonly code: ArchiveErrorCode\n\n constructor(code: ArchiveErrorCode, message: string) {\n super(message)\n this.name = 'ArchiveError'\n this.code = code\n }\n}\n","import { BAG_ALGORITHMS, digestHex, utf8 } from './bytes'\nimport { ArchiveError } from './errors'\n\n/**\n * BagIt bags (RFC 8493, BagIt 1.0). A bag here is a list of files with bag-relative paths, so\n * the same value can be written to a directory (`@seedprotocol/archive/node`), zipped, or\n * uploaded file by file. Every bag carries SHA-256 and SHA-512 payload and tag manifests.\n */\n\n/** One file of a bag. `path` is relative to the bag's base directory and uses `/`. */\nexport type BagFile = { path: string; bytes: Uint8Array }\n\n/** A bag-info.txt line. Labels may repeat, and their order is kept. */\nexport type BagInfoEntry = [label: string, value: string]\n\nexport type Bag = { files: BagFile[] }\n\nexport type CreateBagOptions = {\n /** Payload files, with paths relative to `data/` (e.g. `index.html`, `assets/<sha256>.png`). */\n payload: BagFile[]\n /**\n * Tag files other than the ones the bag writes itself, with paths relative to the bag's base\n * (e.g. `metadata/dc.xml`). They are listed in the tag manifests.\n */\n tagFiles?: BagFile[]\n /** bag-info.txt entries. Bagging-Date, Payload-Oxum and Bag-Software-Agent are added when missing. */\n info?: BagInfoEntry[]\n /** Bagging-Date. Defaults to today (UTC). */\n baggingDate?: Date\n /** Bag-Software-Agent value. */\n softwareAgent?: string\n}\n\nexport const BAGIT_VERSION = '1.0'\nexport const DEFAULT_SOFTWARE_AGENT = '@seedprotocol/archive (https://github.com/JournoLabs/seed-protocol-sdk)'\n\n/** Tag files the bag writes itself; callers can't supply files at these paths. */\nconst RESERVED_TAG_PATH = /^(bagit\\.txt|bag-info\\.txt|fetch\\.txt|(tag)?manifest-[a-z0-9]+\\.txt)$/\nconst CONTROL_CHARACTER = /[\\u0000-\\u001f\\u007f]/\nconst BAG_INFO_LABEL = /^[^\\s:][^:\\r\\n]*$/\n\n/**\n * Throws unless `path` is a portable bag-relative path: `/`-separated, no empty, `.` or `..`\n * segments, no backslash, control characters or `%`, and in Unicode NFC (two spellings of one\n * name would be different files on some file systems and the same file on others).\n *\n * `%` is refused because validators disagree on it: RFC 8493 writes it as `%25` in manifests,\n * and bagit-python 1.9.0 (the Library of Congress reference validator) doesn't decode `%25`, so\n * it reports the file as missing.\n */\nexport function assertBagPath(path: string): void {\n const problem =\n path === ''\n ? 'is empty'\n : path.startsWith('/')\n ? 'is absolute'\n : path.includes('\\\\')\n ? 'contains a backslash'\n : CONTROL_CHARACTER.test(path)\n ? 'contains a control character'\n : path.includes('%')\n ? 'contains \"%\"'\n : path.split('/').some((segment) => segment === '' || segment === '.' || segment === '..')\n ? 'has an empty, \".\" or \"..\" segment'\n : path.normalize('NFC') !== path\n ? 'is not in Unicode NFC'\n : undefined\n if (problem) throw new ArchiveError('invalid_path', `Bag path ${JSON.stringify(path)} ${problem}`)\n}\n\n/** A manifest's filepath field: RFC 8493 §2.1.3 requires `%`, CR and LF percent-encoded. */\nexport const encodeManifestPath = (path: string): string =>\n path.replace(/%/g, '%25').replace(/\\r/g, '%0D').replace(/\\n/g, '%0A')\n\nexport const decodeManifestPath = (field: string): string =>\n field.replace(/%0D/gi, '\\r').replace(/%0A/gi, '\\n').replace(/%25/g, '%')\n\nconst manifestText = (algorithm: (typeof BAG_ALGORITHMS)[number], files: BagFile[]): string =>\n files.map((file) => `${digestHex(algorithm, file.bytes)} ${encodeManifestPath(file.path)}\\n`).join('')\n\n/** bag-info.txt: one `Label: value` line per entry, with line breaks in a value folded onto indented lines. */\nconst bagInfoText = (entries: BagInfoEntry[]): string =>\n entries\n .map(([label, value]) => {\n if (!BAG_INFO_LABEL.test(label)) {\n throw new ArchiveError('invalid_bag_info', `bag-info.txt label ${JSON.stringify(label)} is not valid`)\n }\n const folded = value.replace(/\\r\\n?/g, '\\n').split('\\n').join('\\n ')\n return `${label}: ${folded}\\n`\n })\n .join('')\n\nconst isoDate = (date: Date): string => date.toISOString().slice(0, 10)\n\nconst byPath = (a: BagFile, b: BagFile): number => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)\n\n/**\n * Builds a BagIt 1.0 bag: bagit.txt, bag-info.txt, SHA-256 and SHA-512 payload manifests, the\n * caller's tag files, and SHA-256 and SHA-512 tag manifests covering every tag file.\n *\n * Throws {@link ArchiveError} for a path that isn't portable ({@link assertBagPath}), a reserved\n * tag file name, a tag file under `data/`, or two files whose paths differ only in letter case\n * (they would collide on case-insensitive file systems).\n */\nexport function createBag(options: CreateBagOptions): Bag {\n const payload = options.payload.map((file) => {\n assertBagPath(file.path)\n return { path: `data/${file.path}`, bytes: file.bytes }\n })\n const extraTags = (options.tagFiles ?? []).map((file) => {\n assertBagPath(file.path)\n if (file.path.startsWith('data/') || RESERVED_TAG_PATH.test(file.path)) {\n throw new ArchiveError('invalid_path', `${file.path} can't be supplied as a tag file`)\n }\n return file\n })\n\n const seen = new Map<string, string>()\n for (const { path } of [...payload, ...extraTags]) {\n const key = path.toLowerCase()\n const previous = seen.get(key)\n if (previous !== undefined) {\n throw new ArchiveError(\n 'duplicate_path',\n previous === path ? `${path} appears twice` : `${previous} and ${path} differ only in letter case`,\n )\n }\n seen.set(key, path)\n }\n payload.sort(byPath)\n extraTags.sort(byPath)\n\n const info = [...(options.info ?? [])]\n const has = (label: string) => info.some(([l]) => l.toLowerCase() === label.toLowerCase())\n if (!has('Bagging-Date')) info.push(['Bagging-Date', isoDate(options.baggingDate ?? new Date())])\n if (!has('Payload-Oxum')) {\n const octets = payload.reduce((sum, file) => sum + file.bytes.byteLength, 0)\n info.push(['Payload-Oxum', `${octets}.${payload.length}`])\n }\n if (!has('Bag-Software-Agent')) info.push(['Bag-Software-Agent', options.softwareAgent ?? DEFAULT_SOFTWARE_AGENT])\n\n const tagFiles: BagFile[] = [\n { path: 'bagit.txt', bytes: utf8(`BagIt-Version: ${BAGIT_VERSION}\\nTag-File-Character-Encoding: UTF-8\\n`) },\n { path: 'bag-info.txt', bytes: utf8(bagInfoText(info)) },\n ...BAG_ALGORITHMS.map((algorithm) => ({\n path: `manifest-${algorithm}.txt`,\n bytes: utf8(manifestText(algorithm, payload)),\n })),\n ...extraTags,\n ]\n const tagManifests = BAG_ALGORITHMS.map((algorithm) => ({\n path: `tagmanifest-${algorithm}.txt`,\n bytes: utf8(manifestText(algorithm, tagFiles)),\n }))\n\n return { files: [...tagFiles, ...tagManifests, ...payload] }\n}\n\n/** The bag's file at `path`, if any. */\nexport const getBagFile = (bag: Bag, path: string): BagFile | undefined => bag.files.find((f) => f.path === path)\n"],"mappings":"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"}
package/dist/bag.d.ts ADDED
@@ -0,0 +1,57 @@
1
+ /**
2
+ * BagIt bags (RFC 8493, BagIt 1.0). A bag here is a list of files with bag-relative paths, so
3
+ * the same value can be written to a directory (`@seedprotocol/archive/node`), zipped, or
4
+ * uploaded file by file. Every bag carries SHA-256 and SHA-512 payload and tag manifests.
5
+ */
6
+ /** One file of a bag. `path` is relative to the bag's base directory and uses `/`. */
7
+ export type BagFile = {
8
+ path: string;
9
+ bytes: Uint8Array;
10
+ };
11
+ /** A bag-info.txt line. Labels may repeat, and their order is kept. */
12
+ export type BagInfoEntry = [label: string, value: string];
13
+ export type Bag = {
14
+ files: BagFile[];
15
+ };
16
+ export type CreateBagOptions = {
17
+ /** Payload files, with paths relative to `data/` (e.g. `index.html`, `assets/<sha256>.png`). */
18
+ payload: BagFile[];
19
+ /**
20
+ * Tag files other than the ones the bag writes itself, with paths relative to the bag's base
21
+ * (e.g. `metadata/dc.xml`). They are listed in the tag manifests.
22
+ */
23
+ tagFiles?: BagFile[];
24
+ /** bag-info.txt entries. Bagging-Date, Payload-Oxum and Bag-Software-Agent are added when missing. */
25
+ info?: BagInfoEntry[];
26
+ /** Bagging-Date. Defaults to today (UTC). */
27
+ baggingDate?: Date;
28
+ /** Bag-Software-Agent value. */
29
+ softwareAgent?: string;
30
+ };
31
+ export declare const BAGIT_VERSION = "1.0";
32
+ export declare const DEFAULT_SOFTWARE_AGENT = "@seedprotocol/archive (https://github.com/JournoLabs/seed-protocol-sdk)";
33
+ /**
34
+ * Throws unless `path` is a portable bag-relative path: `/`-separated, no empty, `.` or `..`
35
+ * segments, no backslash, control characters or `%`, and in Unicode NFC (two spellings of one
36
+ * name would be different files on some file systems and the same file on others).
37
+ *
38
+ * `%` is refused because validators disagree on it: RFC 8493 writes it as `%25` in manifests,
39
+ * and bagit-python 1.9.0 (the Library of Congress reference validator) doesn't decode `%25`, so
40
+ * it reports the file as missing.
41
+ */
42
+ export declare function assertBagPath(path: string): void;
43
+ /** A manifest's filepath field: RFC 8493 §2.1.3 requires `%`, CR and LF percent-encoded. */
44
+ export declare const encodeManifestPath: (path: string) => string;
45
+ export declare const decodeManifestPath: (field: string) => string;
46
+ /**
47
+ * Builds a BagIt 1.0 bag: bagit.txt, bag-info.txt, SHA-256 and SHA-512 payload manifests, the
48
+ * caller's tag files, and SHA-256 and SHA-512 tag manifests covering every tag file.
49
+ *
50
+ * Throws {@link ArchiveError} for a path that isn't portable ({@link assertBagPath}), a reserved
51
+ * tag file name, a tag file under `data/`, or two files whose paths differ only in letter case
52
+ * (they would collide on case-insensitive file systems).
53
+ */
54
+ export declare function createBag(options: CreateBagOptions): Bag;
55
+ /** The bag's file at `path`, if any. */
56
+ export declare const getBagFile: (bag: Bag, path: string) => BagFile | undefined;
57
+ //# sourceMappingURL=bag.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bag.d.ts","sourceRoot":"","sources":["../src/bag.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AAEH,sFAAsF;AACtF,MAAM,MAAM,OAAO,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,CAAA;AAEzD,uEAAuE;AACvE,MAAM,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;AAEzD,MAAM,MAAM,GAAG,GAAG;IAAE,KAAK,EAAE,OAAO,EAAE,CAAA;CAAE,CAAA;AAEtC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,gGAAgG;IAChG,OAAO,EAAE,OAAO,EAAE,CAAA;IAClB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAA;IACpB,sGAAsG;IACtG,IAAI,CAAC,EAAE,YAAY,EAAE,CAAA;IACrB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,IAAI,CAAA;IAClB,gCAAgC;IAChC,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,eAAO,MAAM,aAAa,QAAQ,CAAA;AAClC,eAAO,MAAM,sBAAsB,4EAA4E,CAAA;AAO/G;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAkBhD;AAED,4FAA4F;AAC5F,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,MACqB,CAAA;AAEvE,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,KAAG,MACuB,CAAA;AAqB1E;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,GAAG,CAoDxD;AAED,wCAAwC;AACxC,eAAO,MAAM,UAAU,GAAI,KAAK,GAAG,EAAE,MAAM,MAAM,KAAG,OAAO,GAAG,SAAmD,CAAA"}
@@ -0,0 +1,9 @@
1
+ /** Checksum algorithms every bag carries a manifest for, in the order the manifests are written. */
2
+ export declare const BAG_ALGORITHMS: readonly ["sha256", "sha512"];
3
+ export type BagAlgorithm = (typeof BAG_ALGORITHMS)[number];
4
+ /** Lowercase hex digest of `bytes`. @noble/hashes rather than WebCrypto, so it also runs outside secure contexts. */
5
+ export declare const digestHex: (algorithm: BagAlgorithm, bytes: Uint8Array) => string;
6
+ export declare const utf8: (text: string) => Uint8Array;
7
+ /** `bytes` as UTF-8 text, or undefined when they are not valid UTF-8. */
8
+ export declare const fromUtf8: (bytes: Uint8Array) => string | undefined;
9
+ //# sourceMappingURL=bytes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bytes.d.ts","sourceRoot":"","sources":["../src/bytes.ts"],"names":[],"mappings":"AAGA,oGAAoG;AACpG,eAAO,MAAM,cAAc,+BAAgC,CAAA;AAC3D,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA;AAE1D,qHAAqH;AACrH,eAAO,MAAM,SAAS,GAAI,WAAW,YAAY,EAAE,OAAO,UAAU,KAAG,MACH,CAAA;AAKpE,eAAO,MAAM,IAAI,GAAI,MAAM,MAAM,KAAG,UAAkC,CAAA;AAEtE,yEAAyE;AACzE,eAAO,MAAM,QAAQ,GAAI,OAAO,UAAU,KAAG,MAAM,GAAG,SAMrD,CAAA"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Simple Dublin Core (the 15 DCMI elements), written as an `oai_dc:dc` record, the format every
3
+ * OAI-PMH repository must support. Each element may repeat; leave a field out rather than
4
+ * guessing a value.
5
+ */
6
+ export type DublinCore = Partial<Record<'title' | 'creator' | 'subject' | 'description' | 'publisher' | 'contributor' | 'date' | 'type' | 'format' | 'identifier' | 'source' | 'language' | 'relation' | 'coverage' | 'rights', string | string[]>>;
7
+ /** `record` as an `oai_dc:dc` XML document. Empty strings are left out. */
8
+ export declare function dublinCoreXml(record: DublinCore): string;
9
+ //# sourceMappingURL=dublinCore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dublinCore.d.ts","sourceRoot":"","sources":["../src/dublinCore.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,CAC9B,MAAM,CACF,OAAO,GACP,SAAS,GACT,SAAS,GACT,aAAa,GACb,WAAW,GACX,aAAa,GACb,MAAM,GACN,MAAM,GACN,QAAQ,GACR,YAAY,GACZ,QAAQ,GACR,UAAU,GACV,UAAU,GACV,UAAU,GACV,QAAQ,EACV,MAAM,GAAG,MAAM,EAAE,CAClB,CACF,CAAA;AAoBD,2EAA2E;AAC3E,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAgBxD"}
@@ -0,0 +1,7 @@
1
+ export type ArchiveErrorCode = 'invalid_path' | 'duplicate_path' | 'invalid_bag_info';
2
+ /** A bag that can't be built as asked. `code` says why; the message names the file or field. */
3
+ export declare class ArchiveError extends Error {
4
+ readonly code: ArchiveErrorCode;
5
+ constructor(code: ArchiveErrorCode, message: string);
6
+ }
7
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG,gBAAgB,GAAG,kBAAkB,CAAA;AAErF,gGAAgG;AAChG,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAA;gBAEnB,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM;CAKpD"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Reads an archive document: Html that marks up an article as a microformats2 h-entry
3
+ * (https://microformats.org/wiki/h-entry), the way PermaPress seals a Post. Documents without
4
+ * an h-entry are read by fallbacks, so archives from other clients still yield a body.
5
+ *
6
+ * Only the properties archives use are read: p-name, p-summary, p-author, dt-published,
7
+ * u-featured, u-url and e-content. This is not a full microformats2 parser: implied
8
+ * properties and value-class patterns are not applied.
9
+ */
10
+ /** How the body was found, most to least reliable. `fragment` means the whole input is the body. */
11
+ export type ArchiveDocumentMethod = 'h-entry' | 'articleBody' | 'article' | 'fragment';
12
+ export type ArchiveDocument = {
13
+ method: ArchiveDocumentMethod;
14
+ /** The body's exact source, sliced from the input (not re-serialized). */
15
+ content: string;
16
+ name?: string;
17
+ summary?: string;
18
+ /** dt-published as written: a `datetime`/`value` attribute, else the element's text. */
19
+ published?: string;
20
+ /** Each p-author: a nested h-card's p-name, else the element's text. */
21
+ authors: string[];
22
+ featured?: {
23
+ url: string;
24
+ alt?: string;
25
+ };
26
+ url?: string;
27
+ };
28
+ /**
29
+ * The body and descriptive fields of `html`. The body is taken, in order, from:
30
+ *
31
+ * 1. `.h-entry .e-content` (with the entry's other properties)
32
+ * 2. the first `[itemprop=articleBody]`
33
+ * 3. the only top-level `<article>`, without its direct `<header>` and `<footer>` children
34
+ * 4. the whole input
35
+ *
36
+ * An h-entry without e-content falls through to the later steps for its body, keeping its
37
+ * other properties.
38
+ */
39
+ export declare function parseArchiveDocument(html: string): ArchiveDocument;
40
+ //# sourceMappingURL=hEntry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hEntry.d.ts","sourceRoot":"","sources":["../src/hEntry.ts"],"names":[],"mappings":"AAYA;;;;;;;;GAQG;AAEH,oGAAoG;AACpG,MAAM,MAAM,qBAAqB,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,GAAG,UAAU,CAAA;AAEtF,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,qBAAqB,CAAA;IAC7B,0EAA0E;IAC1E,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,wFAAwF;IACxF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,wEAAwE;IACxE,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,QAAQ,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACxC,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CAAA;AA8DD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAiDlE"}
package/dist/html.d.ts ADDED
@@ -0,0 +1,39 @@
1
+ import { type DefaultTreeAdapterTypes } from 'parse5';
2
+ export type HtmlElement = DefaultTreeAdapterTypes.Element;
3
+ export type HtmlNode = DefaultTreeAdapterTypes.ChildNode;
4
+ /** `html` parsed as a fragment with source offsets, so callers can slice or splice the original string. */
5
+ export declare const parseHtmlWithLocations: (html: string) => DefaultTreeAdapterTypes.DocumentFragment;
6
+ export declare const isElement: (node: HtmlNode) => node is HtmlElement;
7
+ export declare const childElements: (node: {
8
+ childNodes?: HtmlNode[];
9
+ }) => HtmlElement[];
10
+ /** Calls `visit` for each element under `nodes`, depth first; returning false skips that element's children. */
11
+ export declare function walkElements(nodes: HtmlNode[], visit: (element: HtmlElement) => boolean | void): void;
12
+ export declare const attribute: (element: HtmlElement, name: string) => string | undefined;
13
+ export declare const classTokens: (element: HtmlElement) => string[];
14
+ /** Concatenated text of `node`, leaving out script, style and template content. */
15
+ export declare function textContent(node: HtmlNode): string;
16
+ /** The exact source of an element's children: from the end of its start tag to the start of its end tag. */
17
+ export declare function innerSource(html: string, element: HtmlElement): string;
18
+ /** The exact source of one node. */
19
+ export declare const nodeSource: (html: string, node: HtmlNode) => string;
20
+ /**
21
+ * Rewrites attribute values by splicing each new value into the original string, so every byte
22
+ * outside the rewritten attributes stays as written. Re-serializing the parsed tree would not
23
+ * round-trip (parse5 drops the newline after `<pre>`, a fragment parse drops doctype, html, head
24
+ * and body, and quoting, case and line endings are normalized), and sealed Html must not change.
25
+ *
26
+ * `rewrite` gets each attribute's entity-decoded value and returns the new value, or undefined to
27
+ * leave it. A rewritten attribute is written as `name="value"`.
28
+ */
29
+ export declare function rewriteHtmlAttributes(html: string, rewrite: (element: HtmlElement, attrName: string, value: string) => string | undefined): string;
30
+ /** Attributes whose whole value is one URL. `srcset` lists several and is not rewritten. */
31
+ export declare const URL_ATTRIBUTES: Set<string>;
32
+ /**
33
+ * Replaces whole `src`, `href` and `poster` values that `replace` maps to a new URL (e.g. a
34
+ * gateway URL to a bag-relative path). Values are compared trimmed. Other bytes are kept.
35
+ */
36
+ export declare const rewriteHtmlUrls: (html: string, replace: (url: string) => string | undefined) => string;
37
+ /** Every whole `src`, `href` and `poster` value in `html`, trimmed, each once, in document order. */
38
+ export declare function findHtmlUrls(html: string): string[];
39
+ //# sourceMappingURL=html.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../src/html.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,uBAAuB,EAAE,MAAM,QAAQ,CAAA;AAEpE,MAAM,MAAM,WAAW,GAAG,uBAAuB,CAAC,OAAO,CAAA;AACzD,MAAM,MAAM,QAAQ,GAAG,uBAAuB,CAAC,SAAS,CAAA;AAExD,2GAA2G;AAC3G,eAAO,MAAM,sBAAsB,GAAI,MAAM,MAAM,KAAG,uBAAuB,CAAC,gBACvB,CAAA;AAEvD,eAAO,MAAM,SAAS,GAAI,MAAM,QAAQ,KAAG,IAAI,IAAI,WAC6C,CAAA;AAEhG,eAAO,MAAM,aAAa,GAAI,MAAM;IAAE,UAAU,CAAC,EAAE,QAAQ,EAAE,CAAA;CAAE,KAAG,WAAW,EAClC,CAAA;AAQ3C,gHAAgH;AAChH,wBAAgB,YAAY,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,GAAG,IAAI,GAAG,IAAI,CAMrG;AAED,eAAO,MAAM,SAAS,GAAI,SAAS,WAAW,EAAE,MAAM,MAAM,KAAG,MAAM,GAAG,SACR,CAAA;AAEhE,eAAO,MAAM,WAAW,GAAI,SAAS,WAAW,KAAG,MAAM,EACS,CAAA;AAIlE,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAIlD;AAED,4GAA4G;AAC5G,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,MAAM,CAKtE;AAED,oCAAoC;AACpC,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,EAAE,MAAM,QAAQ,KAAG,MAGzD,CAAA;AAID;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GACrF,MAAM,CAkBR;AAED,4FAA4F;AAC5F,eAAO,MAAM,cAAc,aAAqC,CAAA;AAEhE;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,EAAE,SAAS,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,KAAG,MAG1F,CAAA;AAEH,qGAAqG;AACrG,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAQnD"}
@@ -0,0 +1,9 @@
1
+ export { BAGIT_VERSION, DEFAULT_SOFTWARE_AGENT, assertBagPath, createBag, decodeManifestPath, encodeManifestPath, getBagFile, type Bag, type BagFile, type BagInfoEntry, type CreateBagOptions, } from './bag';
2
+ export { validateBag, type BagValidation } from './validateBag';
3
+ export { BAG_ALGORITHMS, digestHex, type BagAlgorithm } from './bytes';
4
+ export { ArchiveError, type ArchiveErrorCode } from './errors';
5
+ export { findHtmlUrls, rewriteHtmlAttributes, rewriteHtmlUrls } from './html';
6
+ export { parseArchiveDocument, type ArchiveDocument, type ArchiveDocumentMethod } from './hEntry';
7
+ export { dublinCoreXml, type DublinCore } from './dublinCore';
8
+ export { PREMIS_AGENT_TYPES, PREMIS_EVENT_TYPES, premisXml, type Premis, type PremisAgent, type PremisAgentType, type PremisEvent, type PremisEventType, type PremisFile, type PremisIdentifier, type PremisIntellectualEntity, type PremisObject, } from './premis';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,sBAAsB,EACtB,aAAa,EACb,SAAS,EACT,kBAAkB,EAClB,kBAAkB,EAClB,UAAU,EACV,KAAK,GAAG,EACR,KAAK,OAAO,EACZ,KAAK,YAAY,EACjB,KAAK,gBAAgB,GACtB,MAAM,OAAO,CAAA;AACd,OAAO,EAAE,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAA;AAC/D,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAA;AACtE,OAAO,EAAE,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAC9D,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAA;AAC7E,OAAO,EAAE,oBAAoB,EAAE,KAAK,eAAe,EAAE,KAAK,qBAAqB,EAAE,MAAM,UAAU,CAAA;AACjG,OAAO,EAAE,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,SAAS,EACT,KAAK,MAAM,EACX,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,wBAAwB,EAC7B,KAAK,YAAY,GAClB,MAAM,UAAU,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,277 @@
1
+ import { a as e, c as t, d as n, i as r, l as i, n as a, o, r as s, s as c, t as l, u } from "./bag-BkZhe-zN.js";
2
+ import { parseFragment as d } from "parse5";
3
+ //#region src/validateBag.ts
4
+ var f = /^(tag)?manifest-([a-z0-9]+)\.txt$/, p = (e) => {
5
+ let t = [];
6
+ for (let n of e.replace(/\r\n?/g, "\n").split("\n")) {
7
+ if (n === "") continue;
8
+ let e = t[t.length - 1];
9
+ if (/^\s/.test(n) && e) {
10
+ e[1] += `\n${n.trim()}`;
11
+ continue;
12
+ }
13
+ let r = n.indexOf(":");
14
+ r > 0 && t.push([n.slice(0, r).trim(), n.slice(r + 1).trim()]);
15
+ }
16
+ return t;
17
+ };
18
+ function m(t) {
19
+ let r = [], a = new Map(t.files.map((e) => [e.path, e])), o = (e) => {
20
+ let t = a.get(e);
21
+ if (!t) return;
22
+ let i = n(t.bytes);
23
+ return i === void 0 && r.push(`${e} is not valid UTF-8`), i;
24
+ }, s = o("bagit.txt");
25
+ if (s === void 0) r.push("bagit.txt is missing");
26
+ else {
27
+ let e = new Map(p(s)), t = e.get("BagIt-Version");
28
+ t !== "1.0" && t !== "0.97" && r.push(`bagit.txt declares BagIt-Version ${t ?? "(none)"}`), e.get("Tag-File-Character-Encoding")?.toUpperCase() !== "UTF-8" && r.push("bagit.txt does not declare Tag-File-Character-Encoding: UTF-8");
29
+ }
30
+ let c = t.files.filter((e) => e.path.startsWith("data/")).map((e) => e.path), l = t.files.map((e) => ({
31
+ file: e,
32
+ match: f.exec(e.path)
33
+ })).filter((e) => e.match !== null);
34
+ l.some(({ match: e }) => !e[1]) || r.push("no payload manifest (manifest-<algorithm>.txt)");
35
+ for (let { file: t, match: n } of l) {
36
+ let s = !!n[1], l = n[2];
37
+ if (!i.includes(l)) {
38
+ r.push(`${t.path}: algorithm ${l} is not supported by this validator`);
39
+ continue;
40
+ }
41
+ let d = o(t.path);
42
+ if (d === void 0) continue;
43
+ let f = /* @__PURE__ */ new Set();
44
+ for (let n of d.split("\n")) {
45
+ if (n.trim() === "") continue;
46
+ let i = /^([0-9a-fA-F]+)\s+(.+)$/.exec(n.replace(/\r$/, ""));
47
+ if (!i) {
48
+ r.push(`${t.path}: malformed line ${JSON.stringify(n)}`);
49
+ continue;
50
+ }
51
+ let o = e(i[2]);
52
+ f.add(o), !s && !o.startsWith("data/") && r.push(`${t.path}: ${o} is outside data/`);
53
+ let c = a.get(o);
54
+ c ? u(l, c.bytes) !== i[1].toLowerCase() && r.push(`${t.path}: ${o} does not match its ${l} checksum`) : r.push(`${t.path}: ${o} is listed but missing`);
55
+ }
56
+ if (!s) for (let e of c) f.has(e) || r.push(`${t.path}: payload file ${e} is not listed`);
57
+ }
58
+ let d = o("bag-info.txt"), m = d === void 0 ? void 0 : p(d).find(([e]) => e === "Payload-Oxum")?.[1];
59
+ if (m !== void 0) {
60
+ let e = t.files.filter((e) => e.path.startsWith("data/")).reduce((e, t) => e + t.bytes.byteLength, 0);
61
+ m !== `${e}.${c.length}` && r.push(`bag-info.txt: Payload-Oxum ${m} does not match the payload (${e}.${c.length})`);
62
+ }
63
+ return {
64
+ valid: r.length === 0,
65
+ errors: r
66
+ };
67
+ }
68
+ //#endregion
69
+ //#region src/html.ts
70
+ var h = (e) => d(e, { sourceCodeLocationInfo: !0 }), g = (e) => e.nodeName !== "#text" && e.nodeName !== "#comment" && e.nodeName !== "#documentType", _ = (e) => (e.childNodes ?? []).filter(g), v = (e) => e.tagName === "template" ? e.content?.childNodes ?? [] : e.childNodes ?? [];
71
+ function y(e, t) {
72
+ for (let n of e) g(n) && t(n) !== !1 && y(v(n), t);
73
+ }
74
+ var b = (e, t) => e.attrs.find((e) => e.name === t && !e.prefix)?.value, x = (e) => (b(e, "class") ?? "").split(/\s+/).filter(Boolean), S = /* @__PURE__ */ new Set([
75
+ "script",
76
+ "style",
77
+ "template"
78
+ ]);
79
+ function C(e) {
80
+ return e.nodeName === "#text" ? e.value : !g(e) || S.has(e.tagName) ? "" : (e.childNodes ?? []).map(C).join("");
81
+ }
82
+ function w(e, t) {
83
+ let n = t.sourceCodeLocation;
84
+ if (!n?.startTag) return "";
85
+ let r = n.endTag?.startOffset ?? n.endOffset;
86
+ return e.slice(n.startTag.endOffset, r);
87
+ }
88
+ var T = (e, t) => {
89
+ let n = t.sourceCodeLocation;
90
+ return n ? e.slice(n.startOffset, n.endOffset) : "";
91
+ }, E = (e) => e.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
92
+ function D(e, t) {
93
+ let n = h(e), r = [];
94
+ y(n.childNodes, (e) => {
95
+ for (let n of e.attrs) {
96
+ if (n.prefix) continue;
97
+ let i = t(e, n.name, n.value), a = e.sourceCodeLocation?.attrs?.[n.name];
98
+ i !== void 0 && a && r.push({
99
+ start: a.startOffset,
100
+ end: a.endOffset,
101
+ text: `${n.name}="${E(i)}"`
102
+ });
103
+ }
104
+ });
105
+ let i = e;
106
+ for (let e of r.sort((e, t) => t.start - e.start)) i = i.slice(0, e.start) + e.text + i.slice(e.end);
107
+ return i;
108
+ }
109
+ var O = /* @__PURE__ */ new Set([
110
+ "src",
111
+ "href",
112
+ "poster"
113
+ ]), k = (e, t) => D(e, (e, n, r) => O.has(n) ? t(r.trim()) : void 0);
114
+ function A(e) {
115
+ let t = /* @__PURE__ */ new Set();
116
+ return y(h(e).childNodes, (e) => {
117
+ for (let n of e.attrs) !n.prefix && O.has(n.name) && n.value.trim() && t.add(n.value.trim());
118
+ }), [...t];
119
+ }
120
+ //#endregion
121
+ //#region src/hEntry.ts
122
+ var j = (e) => x(e).some((e) => e.startsWith("h-")), M = (e) => C(e).replace(/\s+/g, " ").trim(), N = (e, t) => {
123
+ let n;
124
+ return y(e, (e) => {
125
+ if (n) return !1;
126
+ if (x(e).includes(t)) return n = e, !1;
127
+ }), n;
128
+ }, P = /* @__PURE__ */ new Set([
129
+ "time",
130
+ "ins",
131
+ "del"
132
+ ]);
133
+ function F(e, t) {
134
+ let n = {
135
+ method: "h-entry",
136
+ content: "",
137
+ authors: []
138
+ }, r = !1;
139
+ return y(_(t), (t) => {
140
+ let i = x(t), a = j(t);
141
+ if (i.includes("p-author")) {
142
+ let e = M((a ? N(_(t), "p-name") : void 0) || t);
143
+ e && n.authors.push(e);
144
+ }
145
+ if (a) return !1;
146
+ if (i.includes("p-name") && n.name === void 0 && (n.name = M(t)), i.includes("p-summary") && n.summary === void 0 && (n.summary = M(t)), i.includes("dt-published") && n.published === void 0 && (n.published = (P.has(t.tagName) ? b(t, "datetime") : void 0) ?? b(t, "value") ?? M(t)), i.includes("u-featured") && n.featured === void 0) {
147
+ let e = b(t, "src") ?? b(t, "href") ?? M(t);
148
+ e && (n.featured = {
149
+ url: e.trim(),
150
+ alt: b(t, "alt")
151
+ });
152
+ }
153
+ if (i.includes("u-url") && n.url === void 0) {
154
+ let e = b(t, "href") ?? b(t, "src") ?? M(t);
155
+ e && (n.url = e.trim());
156
+ }
157
+ if (i.includes("e-content") && !r) return r = !0, n.content = w(e, t), !1;
158
+ }), n;
159
+ }
160
+ function I(e) {
161
+ let t = h(e).childNodes, n, r;
162
+ if (y(t, (e) => {
163
+ if (r) return !1;
164
+ if (x(e).includes("h-entry")) return r = e, !1;
165
+ }), r && (n = F(e, r), n.content || N(_(r), "e-content"))) return n;
166
+ let i = n ? {
167
+ name: n.name,
168
+ summary: n.summary,
169
+ published: n.published,
170
+ authors: n.authors,
171
+ featured: n.featured,
172
+ url: n.url
173
+ } : { authors: [] }, a;
174
+ if (y(t, (e) => {
175
+ if (a) return !1;
176
+ if (b(e, "itemprop")?.split(/\s+/).includes("articleBody")) return a = e, !1;
177
+ }), a) return {
178
+ ...i,
179
+ method: "articleBody",
180
+ content: w(e, a)
181
+ };
182
+ let o = [];
183
+ if (y(t, (e) => {
184
+ if (e.tagName === "article") return o.push(e), !1;
185
+ }), o.length === 1) {
186
+ let t = (o[0].childNodes ?? []).filter((e) => !("tagName" in e) || e.tagName !== "header" && e.tagName !== "footer").map((t) => T(e, t)).join("");
187
+ return {
188
+ ...i,
189
+ method: "article",
190
+ content: t
191
+ };
192
+ }
193
+ return {
194
+ ...i,
195
+ method: "fragment",
196
+ content: e
197
+ };
198
+ }
199
+ //#endregion
200
+ //#region src/xml.ts
201
+ var L = /[\u0000-\u0008\u000b\u000c\u000e-\u001f￾￿]/g, R = (e) => e.replace(L, "�").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;"), z = (e) => Object.entries(e ?? {}).filter((e) => e[1] !== void 0).map(([e, t]) => ` ${e}="${R(t)}"`).join(""), B = (e, t, n, r) => n === void 0 ? "" : `${e}<${t}${z(r)}>${R(n)}</${t}>\n`, V = (e, t, n, r) => n === "" ? "" : `${e}<${t}${z(r)}>\n${n}${e}</${t}>\n`, H = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", U = [
202
+ "title",
203
+ "creator",
204
+ "subject",
205
+ "description",
206
+ "publisher",
207
+ "contributor",
208
+ "date",
209
+ "type",
210
+ "format",
211
+ "identifier",
212
+ "source",
213
+ "language",
214
+ "relation",
215
+ "coverage",
216
+ "rights"
217
+ ];
218
+ function W(e) {
219
+ let t = "";
220
+ for (let n of U) {
221
+ let r = e[n], i = r === void 0 ? [] : Array.isArray(r) ? r : [r];
222
+ for (let e of i) e !== "" && (t += B(" ", `dc:${n}`, e));
223
+ }
224
+ return H + "<oai_dc:dc xmlns:oai_dc=\"http://www.openarchives.org/OAI/2.0/oai_dc/\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.openarchives.org/OAI/2.0/oai_dc/ http://www.openarchives.org/OAI/2.0/oai_dc.xsd\">\n" + t + "</oai_dc:dc>\n";
225
+ }
226
+ //#endregion
227
+ //#region src/premis.ts
228
+ var G = {
229
+ creation: "cre",
230
+ modification: "mod",
231
+ "metadata modification": "mem",
232
+ "message digest calculation": "mes",
233
+ "information package creation": "ipc",
234
+ "fixity check": "fix",
235
+ validation: "val",
236
+ "digital signature generation": "dsg",
237
+ capture: "cap",
238
+ replication: "rep"
239
+ }, K = {
240
+ person: "per",
241
+ organization: "org",
242
+ software: "sof",
243
+ hardware: "har"
244
+ }, q = "http://id.loc.gov/vocabulary/preservation", J = (e, t, n) => V(e, t, B(`${e} `, `premis:${t.replace("premis:", "")}Type`, n.type) + B(`${e} `, `premis:${t.replace("premis:", "")}Value`, n.value)), Y = (e, t, n) => {
245
+ let r = t === "linkingAgentIdentifier" ? "linkingAgentRole" : "linkingObjectRole";
246
+ return V(e, `premis:${t}`, B(`${e} `, `premis:${t}Type`, n.type) + B(`${e} `, `premis:${t}Value`, n.value) + B(`${e} `, `premis:${r}`, n.role));
247
+ };
248
+ function X(e) {
249
+ let t = " ", n = e.identifiers.map((e) => J(t, "premis:objectIdentifier", e)).join("");
250
+ if (e.kind === "intellectualEntity") return V(" ", "premis:object", n, { "xsi:type": "premis:intellectualEntity" });
251
+ let r = Object.entries(e.fixity).map(([e, n]) => V(`${t} `, "premis:fixity", B(`${t} `, "premis:messageDigestAlgorithm", e) + B(`${t} `, "premis:messageDigest", n))).join("");
252
+ return V(" ", "premis:object", n + V(t, "premis:objectCharacteristics", B(`${t} `, "premis:compositionLevel", "0") + r + B(`${t} `, "premis:size", String(e.size)) + V(`${t} `, "premis:format", V(`${t} `, "premis:formatDesignation", B(`${t} `, "premis:formatName", e.formatName)))) + B(t, "premis:originalName", e.originalName), { "xsi:type": "premis:file" });
253
+ }
254
+ function Z(e) {
255
+ let t = " ", n = G[e.type];
256
+ return V(" ", "premis:event", J(t, "premis:eventIdentifier", e.identifier) + B(t, "premis:eventType", e.type, {
257
+ authority: "eventType",
258
+ authorityURI: `${q}/eventType`,
259
+ valueURI: `${q}/eventType/${n}`
260
+ }) + B(t, "premis:eventDateTime", e.dateTime) + V(t, "premis:eventDetailInformation", B(`${t} `, "premis:eventDetail", e.detail)) + V(t, "premis:eventOutcomeInformation", B(`${t} `, "premis:eventOutcome", e.outcome) + V(`${t} `, "premis:eventOutcomeDetail", B(`${t} `, "premis:eventOutcomeDetailNote", e.outcomeNote))) + (e.agents ?? []).map((e) => Y(t, "linkingAgentIdentifier", e)).join("") + (e.objects ?? []).map((e) => Y(t, "linkingObjectIdentifier", e)).join(""));
261
+ }
262
+ function Q(e) {
263
+ let t = " ";
264
+ return V(" ", "premis:agent", e.identifiers.map((e) => J(t, "premis:agentIdentifier", e)).join("") + B(t, "premis:agentName", e.name) + (e.type ? B(t, "premis:agentType", e.type, {
265
+ authority: "agentType",
266
+ authorityURI: `${q}/agentType`,
267
+ valueURI: `${q}/agentType/${K[e.type]}`
268
+ }) : "") + B(t, "premis:agentVersion", e.version) + B(t, "premis:agentNote", e.note));
269
+ }
270
+ function $(e) {
271
+ if (e.objects.length === 0) throw Error("PREMIS needs at least one object");
272
+ return H + "<premis:premis xmlns:premis=\"http://www.loc.gov/premis/v3\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.loc.gov/premis/v3 https://www.loc.gov/standards/premis/premis.xsd\" version=\"3.0\">\n" + e.objects.map(X).join("") + (e.events ?? []).map(Z).join("") + (e.agents ?? []).map(Q).join("") + "</premis:premis>\n";
273
+ }
274
+ //#endregion
275
+ export { t as ArchiveError, l as BAGIT_VERSION, i as BAG_ALGORITHMS, a as DEFAULT_SOFTWARE_AGENT, K as PREMIS_AGENT_TYPES, G as PREMIS_EVENT_TYPES, s as assertBagPath, r as createBag, e as decodeManifestPath, u as digestHex, W as dublinCoreXml, o as encodeManifestPath, A as findHtmlUrls, c as getBagFile, I as parseArchiveDocument, $ as premisXml, D as rewriteHtmlAttributes, k as rewriteHtmlUrls, m as validateBag };
276
+
277
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["decodeManifestPath","ArchiveError","fromUtf8","createBag","BAG_ALGORITHMS","DEFAULT_SOFTWARE_AGENT","assertBagPath","getBagFile","BAGIT_VERSION","MANIFEST","parseTagLines","entries","line","text","last","colon","validateBag","errors","byPath","bag","file","path","decoded","declaration","fields","version","payloadPaths","f","manifests","m","match","isTag","algorithm","body","listed","parsed","target","digestHex","info","oxum","label","octets","sum","parseHtmlWithLocations","parseFragment","html","isElement","node","childElements","childrenOf","walkElements","nodes","visit","attribute","element","a","name","classTokens","NON_TEXT_ELEMENTS","textContent","innerSource","location","end","nodeSource","escapeAttributeValue","value","rewriteHtmlAttributes","fragment","edits","attr","next","rewrite","out","edit","b","URL_ATTRIBUTES","rewriteHtmlUrls","attrName","replace","findHtmlUrls","urls","isMicroformatRoot","c","cleanText","firstWithClass","found","roots","token","DATETIME_ELEMENTS","readHEntry","doc","contentFound","root","classes","nested","author","url","parseArchiveDocument","top","entry","entryRoot","base","articleBody","articles","content","INVALID_XML_CHARACTER","escapeXml","attributes","attrs","textElement","indent","parentElement","children","XML_DECLARATION","ELEMENT_ORDER","dublinCoreXml","record","values","v","PREMIS_EVENT_TYPES","PREMIS_AGENT_TYPES","VOCABULARY","identifier","id","linking","role","objectXml","i","ids","object","fixity","digest","eventXml","code","event","agent","agentXml","premisXml","premis","encodeManifestPath"],"sources":["../src/validateBag.ts","../src/html.ts","../src/hEntry.ts","../src/xml.ts","../src/dublinCore.ts","../src/premis.ts"],"sourcesContent":["import { decodeManifestPath, type Bag, type BagFile } from './bag'\nimport { BAG_ALGORITHMS, digestHex, fromUtf8, type BagAlgorithm } from './bytes'\n\nexport type BagValidation = {\n valid: boolean\n /** One line per problem, naming the file. Empty when `valid`. */\n errors: string[]\n}\n\nconst MANIFEST = /^(tag)?manifest-([a-z0-9]+)\\.txt$/\n\n/** `label: value` pairs of a tag file, with indented continuation lines folded back in. */\nconst parseTagLines = (text: string): [string, string][] => {\n const entries: [string, string][] = []\n for (const line of text.replace(/\\r\\n?/g, '\\n').split('\\n')) {\n if (line === '') continue\n const last = entries[entries.length - 1]\n if (/^\\s/.test(line) && last) {\n last[1] += `\\n${line.trim()}`\n continue\n }\n const colon = line.indexOf(':')\n if (colon > 0) entries.push([line.slice(0, colon).trim(), line.slice(colon + 1).trim()])\n }\n return entries\n}\n\n/**\n * Checks a bag the way a BagIt validator does: bagit.txt declares BagIt 1.0 (or 0.97) in UTF-8,\n * there is at least one payload manifest, every payload file is listed in every payload manifest\n * and matches it, every listed file exists, every tag manifest entry matches, and Payload-Oxum\n * (when present) agrees with the payload. Only SHA-256 and SHA-512 manifests are checked; a\n * manifest for another algorithm is reported as an error rather than skipped.\n */\nexport function validateBag(bag: Bag): BagValidation {\n const errors: string[] = []\n const byPath = new Map<string, BagFile>(bag.files.map((file) => [file.path, file]))\n const text = (path: string): string | undefined => {\n const file = byPath.get(path)\n if (!file) return undefined\n const decoded = fromUtf8(file.bytes)\n if (decoded === undefined) errors.push(`${path} is not valid UTF-8`)\n return decoded\n }\n\n const declaration = text('bagit.txt')\n if (declaration === undefined) {\n errors.push('bagit.txt is missing')\n } else {\n const fields = new Map(parseTagLines(declaration))\n const version = fields.get('BagIt-Version')\n if (version !== '1.0' && version !== '0.97') errors.push(`bagit.txt declares BagIt-Version ${version ?? '(none)'}`)\n if (fields.get('Tag-File-Character-Encoding')?.toUpperCase() !== 'UTF-8') {\n errors.push('bagit.txt does not declare Tag-File-Character-Encoding: UTF-8')\n }\n }\n\n const payloadPaths = bag.files.filter((f) => f.path.startsWith('data/')).map((f) => f.path)\n const manifests = bag.files\n .map((file) => ({ file, match: MANIFEST.exec(file.path) }))\n .filter((m): m is { file: BagFile; match: RegExpExecArray } => m.match !== null)\n\n if (!manifests.some(({ match }) => !match[1])) errors.push('no payload manifest (manifest-<algorithm>.txt)')\n\n for (const { file, match } of manifests) {\n const isTag = !!match[1]\n const algorithm = match[2] as string\n if (!(BAG_ALGORITHMS as readonly string[]).includes(algorithm)) {\n errors.push(`${file.path}: algorithm ${algorithm} is not supported by this validator`)\n continue\n }\n const body = text(file.path)\n if (body === undefined) continue\n const listed = new Set<string>()\n for (const line of body.split('\\n')) {\n if (line.trim() === '') continue\n const parsed = /^([0-9a-fA-F]+)\\s+(.+)$/.exec(line.replace(/\\r$/, ''))\n if (!parsed) {\n errors.push(`${file.path}: malformed line ${JSON.stringify(line)}`)\n continue\n }\n const path = decodeManifestPath(parsed[2] as string)\n listed.add(path)\n if (!isTag && !path.startsWith('data/')) errors.push(`${file.path}: ${path} is outside data/`)\n const target = byPath.get(path)\n if (!target) {\n errors.push(`${file.path}: ${path} is listed but missing`)\n } else if (digestHex(algorithm as BagAlgorithm, target.bytes) !== (parsed[1] as string).toLowerCase()) {\n errors.push(`${file.path}: ${path} does not match its ${algorithm} checksum`)\n }\n }\n if (!isTag) {\n for (const path of payloadPaths) {\n if (!listed.has(path)) errors.push(`${file.path}: payload file ${path} is not listed`)\n }\n }\n }\n\n const info = text('bag-info.txt')\n const oxum = info !== undefined ? parseTagLines(info).find(([label]) => label === 'Payload-Oxum')?.[1] : undefined\n if (oxum !== undefined) {\n const octets = bag.files.filter((f) => f.path.startsWith('data/')).reduce((sum, f) => sum + f.bytes.byteLength, 0)\n if (oxum !== `${octets}.${payloadPaths.length}`) {\n errors.push(`bag-info.txt: Payload-Oxum ${oxum} does not match the payload (${octets}.${payloadPaths.length})`)\n }\n }\n\n return { valid: errors.length === 0, errors }\n}\n","import { parseFragment, type DefaultTreeAdapterTypes } from 'parse5'\n\nexport type HtmlElement = DefaultTreeAdapterTypes.Element\nexport type HtmlNode = DefaultTreeAdapterTypes.ChildNode\n\n/** `html` parsed as a fragment with source offsets, so callers can slice or splice the original string. */\nexport const parseHtmlWithLocations = (html: string): DefaultTreeAdapterTypes.DocumentFragment =>\n parseFragment(html, { sourceCodeLocationInfo: true })\n\nexport const isElement = (node: HtmlNode): node is HtmlElement =>\n node.nodeName !== '#text' && node.nodeName !== '#comment' && node.nodeName !== '#documentType'\n\nexport const childElements = (node: { childNodes?: HtmlNode[] }): HtmlElement[] =>\n (node.childNodes ?? []).filter(isElement)\n\n/** An element's children, including a `<template>`'s content. */\nconst childrenOf = (node: HtmlElement): HtmlNode[] =>\n node.tagName === 'template'\n ? ((node as DefaultTreeAdapterTypes.Template).content?.childNodes ?? [])\n : (node.childNodes ?? [])\n\n/** Calls `visit` for each element under `nodes`, depth first; returning false skips that element's children. */\nexport function walkElements(nodes: HtmlNode[], visit: (element: HtmlElement) => boolean | void): void {\n for (const node of nodes) {\n if (!isElement(node)) continue\n if (visit(node) === false) continue\n walkElements(childrenOf(node), visit)\n }\n}\n\nexport const attribute = (element: HtmlElement, name: string): string | undefined =>\n element.attrs.find((a) => a.name === name && !a.prefix)?.value\n\nexport const classTokens = (element: HtmlElement): string[] =>\n (attribute(element, 'class') ?? '').split(/\\s+/).filter(Boolean)\n\nconst NON_TEXT_ELEMENTS = new Set(['script', 'style', 'template'])\n\n/** Concatenated text of `node`, leaving out script, style and template content. */\nexport function textContent(node: HtmlNode): string {\n if (node.nodeName === '#text') return (node as DefaultTreeAdapterTypes.TextNode).value\n if (!isElement(node) || NON_TEXT_ELEMENTS.has(node.tagName)) return ''\n return (node.childNodes ?? []).map(textContent).join('')\n}\n\n/** The exact source of an element's children: from the end of its start tag to the start of its end tag. */\nexport function innerSource(html: string, element: HtmlElement): string {\n const location = element.sourceCodeLocation\n if (!location?.startTag) return ''\n const end = location.endTag?.startOffset ?? location.endOffset\n return html.slice(location.startTag.endOffset, end)\n}\n\n/** The exact source of one node. */\nexport const nodeSource = (html: string, node: HtmlNode): string => {\n const location = node.sourceCodeLocation\n return location ? html.slice(location.startOffset, location.endOffset) : ''\n}\n\nconst escapeAttributeValue = (value: string): string => value.replace(/&/g, '&amp;').replace(/\"/g, '&quot;')\n\n/**\n * Rewrites attribute values by splicing each new value into the original string, so every byte\n * outside the rewritten attributes stays as written. Re-serializing the parsed tree would not\n * round-trip (parse5 drops the newline after `<pre>`, a fragment parse drops doctype, html, head\n * and body, and quoting, case and line endings are normalized), and sealed Html must not change.\n *\n * `rewrite` gets each attribute's entity-decoded value and returns the new value, or undefined to\n * leave it. A rewritten attribute is written as `name=\"value\"`.\n */\nexport function rewriteHtmlAttributes(\n html: string,\n rewrite: (element: HtmlElement, attrName: string, value: string) => string | undefined,\n): string {\n const fragment = parseHtmlWithLocations(html)\n const edits: { start: number; end: number; text: string }[] = []\n walkElements(fragment.childNodes, (element) => {\n for (const attr of element.attrs) {\n if (attr.prefix) continue\n const next = rewrite(element, attr.name, attr.value)\n const location = element.sourceCodeLocation?.attrs?.[attr.name]\n if (next === undefined || !location) continue\n edits.push({ start: location.startOffset, end: location.endOffset, text: `${attr.name}=\"${escapeAttributeValue(next)}\"` })\n }\n })\n\n let out = html\n for (const edit of edits.sort((a, b) => b.start - a.start)) {\n out = out.slice(0, edit.start) + edit.text + out.slice(edit.end)\n }\n return out\n}\n\n/** Attributes whose whole value is one URL. `srcset` lists several and is not rewritten. */\nexport const URL_ATTRIBUTES = new Set(['src', 'href', 'poster'])\n\n/**\n * Replaces whole `src`, `href` and `poster` values that `replace` maps to a new URL (e.g. a\n * gateway URL to a bag-relative path). Values are compared trimmed. Other bytes are kept.\n */\nexport const rewriteHtmlUrls = (html: string, replace: (url: string) => string | undefined): string =>\n rewriteHtmlAttributes(html, (_element, attrName, value) =>\n URL_ATTRIBUTES.has(attrName) ? replace(value.trim()) : undefined,\n )\n\n/** Every whole `src`, `href` and `poster` value in `html`, trimmed, each once, in document order. */\nexport function findHtmlUrls(html: string): string[] {\n const urls = new Set<string>()\n walkElements(parseHtmlWithLocations(html).childNodes, (element) => {\n for (const attr of element.attrs) {\n if (!attr.prefix && URL_ATTRIBUTES.has(attr.name) && attr.value.trim()) urls.add(attr.value.trim())\n }\n })\n return [...urls]\n}\n","import {\n attribute,\n childElements,\n classTokens,\n innerSource,\n nodeSource,\n parseHtmlWithLocations,\n textContent,\n walkElements,\n type HtmlElement,\n} from './html'\n\n/**\n * Reads an archive document: Html that marks up an article as a microformats2 h-entry\n * (https://microformats.org/wiki/h-entry), the way PermaPress seals a Post. Documents without\n * an h-entry are read by fallbacks, so archives from other clients still yield a body.\n *\n * Only the properties archives use are read: p-name, p-summary, p-author, dt-published,\n * u-featured, u-url and e-content. This is not a full microformats2 parser: implied\n * properties and value-class patterns are not applied.\n */\n\n/** How the body was found, most to least reliable. `fragment` means the whole input is the body. */\nexport type ArchiveDocumentMethod = 'h-entry' | 'articleBody' | 'article' | 'fragment'\n\nexport type ArchiveDocument = {\n method: ArchiveDocumentMethod\n /** The body's exact source, sliced from the input (not re-serialized). */\n content: string\n name?: string\n summary?: string\n /** dt-published as written: a `datetime`/`value` attribute, else the element's text. */\n published?: string\n /** Each p-author: a nested h-card's p-name, else the element's text. */\n authors: string[]\n featured?: { url: string; alt?: string }\n url?: string\n}\n\nconst isMicroformatRoot = (element: HtmlElement): boolean => classTokens(element).some((c) => c.startsWith('h-'))\n\nconst cleanText = (element: HtmlElement): string => textContent(element).replace(/\\s+/g, ' ').trim()\n\nconst firstWithClass = (roots: HtmlElement[], token: string): HtmlElement | undefined => {\n let found: HtmlElement | undefined\n walkElements(roots, (element) => {\n if (found) return false\n if (classTokens(element).includes(token)) {\n found = element\n return false\n }\n })\n return found\n}\n\nconst DATETIME_ELEMENTS = new Set(['time', 'ins', 'del'])\n\nfunction readHEntry(html: string, root: HtmlElement): ArchiveDocument {\n const doc: ArchiveDocument = { method: 'h-entry', content: '', authors: [] }\n let contentFound = false\n\n walkElements(childElements(root), (element) => {\n const classes = classTokens(element)\n const nested = isMicroformatRoot(element)\n\n if (classes.includes('p-author')) {\n const name = nested ? firstWithClass(childElements(element), 'p-name') : undefined\n const author = name ? cleanText(name) : cleanText(element)\n if (author) doc.authors.push(author)\n }\n // A nested microformat's own properties belong to it, not to this entry.\n if (nested) return false\n\n if (classes.includes('p-name') && doc.name === undefined) doc.name = cleanText(element)\n if (classes.includes('p-summary') && doc.summary === undefined) doc.summary = cleanText(element)\n if (classes.includes('dt-published') && doc.published === undefined) {\n doc.published =\n (DATETIME_ELEMENTS.has(element.tagName) ? attribute(element, 'datetime') : undefined) ??\n attribute(element, 'value') ??\n cleanText(element)\n }\n if (classes.includes('u-featured') && doc.featured === undefined) {\n const url = attribute(element, 'src') ?? attribute(element, 'href') ?? cleanText(element)\n if (url) doc.featured = { url: url.trim(), alt: attribute(element, 'alt') }\n }\n if (classes.includes('u-url') && doc.url === undefined) {\n const url = attribute(element, 'href') ?? attribute(element, 'src') ?? cleanText(element)\n if (url) doc.url = url.trim()\n }\n if (classes.includes('e-content') && !contentFound) {\n contentFound = true\n doc.content = innerSource(html, element)\n // The body's own markup can't add properties to the entry.\n return false\n }\n })\n return doc\n}\n\n/**\n * The body and descriptive fields of `html`. The body is taken, in order, from:\n *\n * 1. `.h-entry .e-content` (with the entry's other properties)\n * 2. the first `[itemprop=articleBody]`\n * 3. the only top-level `<article>`, without its direct `<header>` and `<footer>` children\n * 4. the whole input\n *\n * An h-entry without e-content falls through to the later steps for its body, keeping its\n * other properties.\n */\nexport function parseArchiveDocument(html: string): ArchiveDocument {\n const fragment = parseHtmlWithLocations(html)\n const top = fragment.childNodes\n\n let entry: ArchiveDocument | undefined\n let entryRoot: HtmlElement | undefined\n walkElements(top, (element) => {\n if (entryRoot) return false\n if (classTokens(element).includes('h-entry')) {\n entryRoot = element\n return false\n }\n })\n if (entryRoot) {\n entry = readHEntry(html, entryRoot)\n if (entry.content || firstWithClass(childElements(entryRoot), 'e-content')) return entry\n }\n const base: Omit<ArchiveDocument, 'method' | 'content'> = entry\n ? { name: entry.name, summary: entry.summary, published: entry.published, authors: entry.authors, featured: entry.featured, url: entry.url }\n : { authors: [] }\n\n let articleBody: HtmlElement | undefined\n walkElements(top, (element) => {\n if (articleBody) return false\n if (attribute(element, 'itemprop')?.split(/\\s+/).includes('articleBody')) {\n articleBody = element\n return false\n }\n })\n if (articleBody) return { ...base, method: 'articleBody', content: innerSource(html, articleBody) }\n\n // Top-level here means outside any other article; html/head/body wrappers of a full document don't count.\n const articles: HtmlElement[] = []\n walkElements(top, (element) => {\n if (element.tagName === 'article') {\n articles.push(element)\n return false\n }\n })\n if (articles.length === 1) {\n const article = articles[0] as HtmlElement\n const content = (article.childNodes ?? [])\n .filter((node) => !('tagName' in node) || (node.tagName !== 'header' && node.tagName !== 'footer'))\n .map((node) => nodeSource(html, node))\n .join('')\n return { ...base, method: 'article', content }\n }\n\n return { ...base, method: 'fragment', content: html }\n}\n","// XML 1.0 forbids these code points even as character references.\nconst INVALID_XML_CHARACTER = /[\\u0000-\\u0008\\u000b\\u000c\\u000e-\\u001f￾￿]/g\n\n/** `text` escaped for XML element content and double-quoted attribute values. */\nexport const escapeXml = (text: string): string =>\n text\n .replace(INVALID_XML_CHARACTER, '�')\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n\nexport type XmlAttributes = Record<string, string | undefined>\n\nconst attributes = (attrs: XmlAttributes | undefined): string =>\n Object.entries(attrs ?? {})\n .filter((entry): entry is [string, string] => entry[1] !== undefined)\n .map(([name, value]) => ` ${name}=\"${escapeXml(value)}\"`)\n .join('')\n\n/** An element with text content, or nothing when `text` is undefined. */\nexport const textElement = (indent: string, name: string, text: string | undefined, attrs?: XmlAttributes): string =>\n text === undefined ? '' : `${indent}<${name}${attributes(attrs)}>${escapeXml(text)}</${name}>\\n`\n\n/** An element wrapping already-rendered children, or nothing when they are empty. */\nexport const parentElement = (indent: string, name: string, children: string, attrs?: XmlAttributes): string =>\n children === '' ? '' : `${indent}<${name}${attributes(attrs)}>\\n${children}${indent}</${name}>\\n`\n\nexport const XML_DECLARATION = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n'\n","import { textElement, XML_DECLARATION } from './xml'\n\n/**\n * Simple Dublin Core (the 15 DCMI elements), written as an `oai_dc:dc` record, the format every\n * OAI-PMH repository must support. Each element may repeat; leave a field out rather than\n * guessing a value.\n */\nexport type DublinCore = Partial<\n Record<\n | 'title'\n | 'creator'\n | 'subject'\n | 'description'\n | 'publisher'\n | 'contributor'\n | 'date'\n | 'type'\n | 'format'\n | 'identifier'\n | 'source'\n | 'language'\n | 'relation'\n | 'coverage'\n | 'rights',\n string | string[]\n >\n>\n\nconst ELEMENT_ORDER: (keyof DublinCore)[] = [\n 'title',\n 'creator',\n 'subject',\n 'description',\n 'publisher',\n 'contributor',\n 'date',\n 'type',\n 'format',\n 'identifier',\n 'source',\n 'language',\n 'relation',\n 'coverage',\n 'rights',\n]\n\n/** `record` as an `oai_dc:dc` XML document. Empty strings are left out. */\nexport function dublinCoreXml(record: DublinCore): string {\n let body = ''\n for (const element of ELEMENT_ORDER) {\n const value = record[element]\n const values = value === undefined ? [] : Array.isArray(value) ? value : [value]\n for (const v of values) if (v !== '') body += textElement(' ', `dc:${element}`, v)\n }\n return (\n XML_DECLARATION +\n '<oai_dc:dc xmlns:oai_dc=\"http://www.openarchives.org/OAI/2.0/oai_dc/\"' +\n ' xmlns:dc=\"http://purl.org/dc/elements/1.1/\"' +\n ' xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"' +\n ' xsi:schemaLocation=\"http://www.openarchives.org/OAI/2.0/oai_dc/ http://www.openarchives.org/OAI/2.0/oai_dc.xsd\">\\n' +\n body +\n '</oai_dc:dc>\\n'\n )\n}\n","import { parentElement, textElement, XML_DECLARATION } from './xml'\n\n/**\n * PREMIS 3.0 preservation metadata (https://www.loc.gov/standards/premis/v3/): the objects in a\n * bag, the events in their history and the agents behind those events. Event and agent types use\n * the Library of Congress preservation vocabularies, written with their id.loc.gov value URIs.\n */\n\nexport type PremisIdentifier = { type: string; value: string }\n\n/** The thing a bag preserves, such as a Seed: identifiers only. */\nexport type PremisIntellectualEntity = {\n kind: 'intellectualEntity'\n identifiers: PremisIdentifier[]\n}\n\n/** One payload file, with its fixity and format. */\nexport type PremisFile = {\n kind: 'file'\n identifiers: PremisIdentifier[]\n /** Hex digests by algorithm name, e.g. `{ 'SHA-256': '…' }`. */\n fixity: Record<string, string>\n size: number\n /** A MIME type, or another format name. */\n formatName: string\n /** The file's name where it came from, e.g. its Arweave transaction id. */\n originalName?: string\n}\n\nexport type PremisObject = PremisIntellectualEntity | PremisFile\n\n/** Event types from https://id.loc.gov/vocabulary/preservation/eventType, by label. */\nexport const PREMIS_EVENT_TYPES = {\n creation: 'cre',\n modification: 'mod',\n 'metadata modification': 'mem',\n 'message digest calculation': 'mes',\n 'information package creation': 'ipc',\n 'fixity check': 'fix',\n validation: 'val',\n 'digital signature generation': 'dsg',\n capture: 'cap',\n replication: 'rep',\n} as const\nexport type PremisEventType = keyof typeof PREMIS_EVENT_TYPES\n\nexport type PremisEvent = {\n identifier: PremisIdentifier\n type: PremisEventType\n /** ISO 8601. */\n dateTime: string\n detail?: string\n outcome?: 'success' | 'failure'\n outcomeNote?: string\n agents?: (PremisIdentifier & { role?: string })[]\n objects?: (PremisIdentifier & { role?: string })[]\n}\n\n/** Agent types from https://id.loc.gov/vocabulary/preservation/agentType. */\nexport const PREMIS_AGENT_TYPES = { person: 'per', organization: 'org', software: 'sof', hardware: 'har' } as const\nexport type PremisAgentType = keyof typeof PREMIS_AGENT_TYPES\n\nexport type PremisAgent = {\n identifiers: PremisIdentifier[]\n name?: string\n /** Left out when unknown, e.g. an EVM address that may be a person or an organization. */\n type?: PremisAgentType\n version?: string\n note?: string\n}\n\nexport type Premis = { objects: PremisObject[]; events?: PremisEvent[]; agents?: PremisAgent[] }\n\nconst VOCABULARY = 'http://id.loc.gov/vocabulary/preservation'\n\nconst identifier = (indent: string, element: string, id: PremisIdentifier): string =>\n parentElement(\n indent,\n element,\n textElement(`${indent} `, `premis:${element.replace('premis:', '')}Type`, id.type) +\n textElement(`${indent} `, `premis:${element.replace('premis:', '')}Value`, id.value),\n )\n\nconst linking = (indent: string, element: 'linkingAgentIdentifier' | 'linkingObjectIdentifier', id: PremisIdentifier & { role?: string }) => {\n const role = element === 'linkingAgentIdentifier' ? 'linkingAgentRole' : 'linkingObjectRole'\n return parentElement(\n indent,\n `premis:${element}`,\n textElement(`${indent} `, `premis:${element}Type`, id.type) +\n textElement(`${indent} `, `premis:${element}Value`, id.value) +\n textElement(`${indent} `, `premis:${role}`, id.role),\n )\n}\n\nfunction objectXml(object: PremisObject): string {\n const i = ' '\n const ids = object.identifiers.map((id) => identifier(i, 'premis:objectIdentifier', id)).join('')\n if (object.kind === 'intellectualEntity') {\n return parentElement(' ', 'premis:object', ids, { 'xsi:type': 'premis:intellectualEntity' })\n }\n const fixity = Object.entries(object.fixity)\n .map(([algorithm, digest]) =>\n parentElement(\n `${i} `,\n 'premis:fixity',\n textElement(`${i} `, 'premis:messageDigestAlgorithm', algorithm) +\n textElement(`${i} `, 'premis:messageDigest', digest),\n ),\n )\n .join('')\n const characteristics = parentElement(\n i,\n 'premis:objectCharacteristics',\n textElement(`${i} `, 'premis:compositionLevel', '0') +\n fixity +\n textElement(`${i} `, 'premis:size', String(object.size)) +\n parentElement(\n `${i} `,\n 'premis:format',\n parentElement(`${i} `, 'premis:formatDesignation', textElement(`${i} `, 'premis:formatName', object.formatName)),\n ),\n )\n return parentElement(\n ' ',\n 'premis:object',\n ids + characteristics + textElement(i, 'premis:originalName', object.originalName),\n { 'xsi:type': 'premis:file' },\n )\n}\n\nfunction eventXml(event: PremisEvent): string {\n const i = ' '\n const code = PREMIS_EVENT_TYPES[event.type]\n return parentElement(\n ' ',\n 'premis:event',\n identifier(i, 'premis:eventIdentifier', event.identifier) +\n textElement(i, 'premis:eventType', event.type, {\n authority: 'eventType',\n authorityURI: `${VOCABULARY}/eventType`,\n valueURI: `${VOCABULARY}/eventType/${code}`,\n }) +\n textElement(i, 'premis:eventDateTime', event.dateTime) +\n parentElement(i, 'premis:eventDetailInformation', textElement(`${i} `, 'premis:eventDetail', event.detail)) +\n parentElement(\n i,\n 'premis:eventOutcomeInformation',\n textElement(`${i} `, 'premis:eventOutcome', event.outcome) +\n parentElement(\n `${i} `,\n 'premis:eventOutcomeDetail',\n textElement(`${i} `, 'premis:eventOutcomeDetailNote', event.outcomeNote),\n ),\n ) +\n (event.agents ?? []).map((agent) => linking(i, 'linkingAgentIdentifier', agent)).join('') +\n (event.objects ?? []).map((object) => linking(i, 'linkingObjectIdentifier', object)).join(''),\n )\n}\n\nfunction agentXml(agent: PremisAgent): string {\n const i = ' '\n return parentElement(\n ' ',\n 'premis:agent',\n agent.identifiers.map((id) => identifier(i, 'premis:agentIdentifier', id)).join('') +\n textElement(i, 'premis:agentName', agent.name) +\n (agent.type\n ? textElement(i, 'premis:agentType', agent.type, {\n authority: 'agentType',\n authorityURI: `${VOCABULARY}/agentType`,\n valueURI: `${VOCABULARY}/agentType/${PREMIS_AGENT_TYPES[agent.type]}`,\n })\n : '') +\n textElement(i, 'premis:agentVersion', agent.version) +\n textElement(i, 'premis:agentNote', agent.note),\n )\n}\n\n/** `premis` as a PREMIS 3.0 XML document. PREMIS requires at least one object. */\nexport function premisXml(premis: Premis): string {\n if (premis.objects.length === 0) throw new Error('PREMIS needs at least one object')\n return (\n XML_DECLARATION +\n '<premis:premis xmlns:premis=\"http://www.loc.gov/premis/v3\"' +\n ' xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"' +\n ' xsi:schemaLocation=\"http://www.loc.gov/premis/v3 https://www.loc.gov/standards/premis/premis.xsd\"' +\n ' version=\"3.0\">\\n' +\n premis.objects.map(objectXml).join('') +\n (premis.events ?? []).map(eventXml).join('') +\n (premis.agents ?? []).map(agentXml).join('') +\n '</premis:premis>\\n'\n )\n}\n"],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { type Bag } from '../bag';
2
+ /**
3
+ * Writes `bag` under `directory`, which must not exist or be empty, so a bag never mixes with
4
+ * leftover files that its manifests don't list.
5
+ */
6
+ export declare function writeBagDirectory(bag: Bag, directory: string): Promise<void>;
7
+ /** Every file under `directory` as a bag, with `/`-separated paths. */
8
+ export declare function readBagDirectory(directory: string): Promise<Bag>;
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,GAAG,EAAE,MAAM,QAAQ,CAAA;AAEhD;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYlF;AAED,uEAAuE;AACvE,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CActE"}
package/dist/node.js ADDED
@@ -0,0 +1,32 @@
1
+ import { r as e } from "./bag-BkZhe-zN.js";
2
+ import { mkdir as t, readFile as n, readdir as r, writeFile as i } from "node:fs/promises";
3
+ import a from "node:path";
4
+ //#region src/node/index.ts
5
+ async function o(n, o) {
6
+ if ((await r(o).catch((e) => {
7
+ if (e.code === "ENOENT") return [];
8
+ throw e;
9
+ })).length > 0) throw Error(`${o} is not empty`);
10
+ for (let r of n.files) {
11
+ e(r.path);
12
+ let n = a.join(o, ...r.path.split("/"));
13
+ await t(a.dirname(n), { recursive: !0 }), await i(n, r.bytes);
14
+ }
15
+ }
16
+ async function s(e) {
17
+ let t = await r(e, {
18
+ recursive: !0,
19
+ withFileTypes: !0
20
+ });
21
+ return { files: await Promise.all(t.filter((e) => e.isFile()).map(async (t) => {
22
+ let r = a.join(t.parentPath, t.name);
23
+ return {
24
+ path: a.relative(e, r).split(a.sep).join("/"),
25
+ bytes: new Uint8Array(await n(r))
26
+ };
27
+ })) };
28
+ }
29
+ //#endregion
30
+ export { s as readBagDirectory, o as writeBagDirectory };
31
+
32
+ //# sourceMappingURL=node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.js","names":["assertBagPath","path","writeBagDirectory","readdir","directory","error","file","bag","target","mkdir","writeFile","readBagDirectory","entries","entry","full","readFile"],"sources":["../src/node/index.ts"],"sourcesContent":["import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises'\nimport path from 'node:path'\nimport { assertBagPath, type Bag } from '../bag'\n\n/**\n * Writes `bag` under `directory`, which must not exist or be empty, so a bag never mixes with\n * leftover files that its manifests don't list.\n */\nexport async function writeBagDirectory(bag: Bag, directory: string): Promise<void> {\n const existing = await readdir(directory).catch((error: NodeJS.ErrnoException) => {\n if (error.code === 'ENOENT') return []\n throw error\n })\n if (existing.length > 0) throw new Error(`${directory} is not empty`)\n for (const file of bag.files) {\n assertBagPath(file.path)\n const target = path.join(directory, ...file.path.split('/'))\n await mkdir(path.dirname(target), { recursive: true })\n await writeFile(target, file.bytes)\n }\n}\n\n/** Every file under `directory` as a bag, with `/`-separated paths. */\nexport async function readBagDirectory(directory: string): Promise<Bag> {\n const entries = await readdir(directory, { recursive: true, withFileTypes: true })\n const files = await Promise.all(\n entries\n .filter((entry) => entry.isFile())\n .map(async (entry) => {\n const full = path.join(entry.parentPath, entry.name)\n return {\n path: path.relative(directory, full).split(path.sep).join('/'),\n bytes: new Uint8Array(await readFile(full)),\n }\n }),\n )\n return { files }\n}\n"],"mappings":";;;;AAQA,eAAsBE,EAAkB,GAAU,GAAkC;CAKlF,KAAI,MAJmBC,EAAQC,CAAS,CAAC,CAAC,OAAO,MAAiC;EAChF,IAAIC,EAAM,SAAS,UAAU,OAAO,CAAC;EACrC,MAAMA;CACR,CAAC,EAAA,CACY,SAAS,GAAG,MAAU,MAAM,GAAGD,EAAU,cAAc;CACpE,KAAK,IAAME,KAAQC,EAAI,OAAO;EAC5B,EAAcD,EAAK,IAAI;EACvB,IAAME,IAASP,EAAK,KAAKG,GAAW,GAAGE,EAAK,KAAK,MAAM,GAAG,CAAC;EAE3D,AADA,MAAMG,EAAMR,EAAK,QAAQO,CAAM,GAAG,EAAE,WAAW,GAAK,CAAC,GACrD,MAAME,EAAUF,GAAQF,EAAK,KAAK;CACpC;AACF;AAGA,eAAsBK,EAAiB,GAAiC;CACtE,IAAMC,IAAU,MAAMT,EAAQC,GAAW;EAAE,WAAW;EAAM,eAAe;CAAK,CAAC;CAYjF,OAAO,EAAE,OAAA,MAXW,QAAQ,IAC1BQ,EACG,QAAQ,MAAUC,EAAM,OAAO,CAAC,CAAC,CACjC,IAAI,OAAO,MAAU;EACpB,IAAMC,IAAOb,EAAK,KAAKY,EAAM,YAAYA,EAAM,IAAI;EACnD,OAAO;GACL,MAAMZ,EAAK,SAASG,GAAWU,CAAI,CAAC,CAAC,MAAMb,EAAK,GAAG,CAAC,CAAC,KAAK,GAAG;GAC7D,OAAO,IAAI,WAAW,MAAMc,EAASD,CAAI,CAAC;EAC5C;CACF,CAAC,CACL,EACe;AACjB"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * PREMIS 3.0 preservation metadata (https://www.loc.gov/standards/premis/v3/): the objects in a
3
+ * bag, the events in their history and the agents behind those events. Event and agent types use
4
+ * the Library of Congress preservation vocabularies, written with their id.loc.gov value URIs.
5
+ */
6
+ export type PremisIdentifier = {
7
+ type: string;
8
+ value: string;
9
+ };
10
+ /** The thing a bag preserves, such as a Seed: identifiers only. */
11
+ export type PremisIntellectualEntity = {
12
+ kind: 'intellectualEntity';
13
+ identifiers: PremisIdentifier[];
14
+ };
15
+ /** One payload file, with its fixity and format. */
16
+ export type PremisFile = {
17
+ kind: 'file';
18
+ identifiers: PremisIdentifier[];
19
+ /** Hex digests by algorithm name, e.g. `{ 'SHA-256': '…' }`. */
20
+ fixity: Record<string, string>;
21
+ size: number;
22
+ /** A MIME type, or another format name. */
23
+ formatName: string;
24
+ /** The file's name where it came from, e.g. its Arweave transaction id. */
25
+ originalName?: string;
26
+ };
27
+ export type PremisObject = PremisIntellectualEntity | PremisFile;
28
+ /** Event types from https://id.loc.gov/vocabulary/preservation/eventType, by label. */
29
+ export declare const PREMIS_EVENT_TYPES: {
30
+ readonly creation: "cre";
31
+ readonly modification: "mod";
32
+ readonly 'metadata modification': "mem";
33
+ readonly 'message digest calculation': "mes";
34
+ readonly 'information package creation': "ipc";
35
+ readonly 'fixity check': "fix";
36
+ readonly validation: "val";
37
+ readonly 'digital signature generation': "dsg";
38
+ readonly capture: "cap";
39
+ readonly replication: "rep";
40
+ };
41
+ export type PremisEventType = keyof typeof PREMIS_EVENT_TYPES;
42
+ export type PremisEvent = {
43
+ identifier: PremisIdentifier;
44
+ type: PremisEventType;
45
+ /** ISO 8601. */
46
+ dateTime: string;
47
+ detail?: string;
48
+ outcome?: 'success' | 'failure';
49
+ outcomeNote?: string;
50
+ agents?: (PremisIdentifier & {
51
+ role?: string;
52
+ })[];
53
+ objects?: (PremisIdentifier & {
54
+ role?: string;
55
+ })[];
56
+ };
57
+ /** Agent types from https://id.loc.gov/vocabulary/preservation/agentType. */
58
+ export declare const PREMIS_AGENT_TYPES: {
59
+ readonly person: "per";
60
+ readonly organization: "org";
61
+ readonly software: "sof";
62
+ readonly hardware: "har";
63
+ };
64
+ export type PremisAgentType = keyof typeof PREMIS_AGENT_TYPES;
65
+ export type PremisAgent = {
66
+ identifiers: PremisIdentifier[];
67
+ name?: string;
68
+ /** Left out when unknown, e.g. an EVM address that may be a person or an organization. */
69
+ type?: PremisAgentType;
70
+ version?: string;
71
+ note?: string;
72
+ };
73
+ export type Premis = {
74
+ objects: PremisObject[];
75
+ events?: PremisEvent[];
76
+ agents?: PremisAgent[];
77
+ };
78
+ /** `premis` as a PREMIS 3.0 XML document. PREMIS requires at least one object. */
79
+ export declare function premisXml(premis: Premis): string;
80
+ //# sourceMappingURL=premis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"premis.d.ts","sourceRoot":"","sources":["../src/premis.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH,MAAM,MAAM,gBAAgB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAE9D,mEAAmE;AACnE,MAAM,MAAM,wBAAwB,GAAG;IACrC,IAAI,EAAE,oBAAoB,CAAA;IAC1B,WAAW,EAAE,gBAAgB,EAAE,CAAA;CAChC,CAAA;AAED,oDAAoD;AACpD,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,gBAAgB,EAAE,CAAA;IAC/B,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAA;IAClB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG,wBAAwB,GAAG,UAAU,CAAA;AAEhE,uFAAuF;AACvF,eAAO,MAAM,kBAAkB;;;;;;;;;;;CAWrB,CAAA;AACV,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,kBAAkB,CAAA;AAE7D,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,EAAE,gBAAgB,CAAA;IAC5B,IAAI,EAAE,eAAe,CAAA;IACrB,gBAAgB;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,CAAC,gBAAgB,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE,CAAA;IACjD,OAAO,CAAC,EAAE,CAAC,gBAAgB,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE,CAAA;CACnD,CAAA;AAED,6EAA6E;AAC7E,eAAO,MAAM,kBAAkB;;;;;CAAoF,CAAA;AACnH,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,kBAAkB,CAAA;AAE7D,MAAM,MAAM,WAAW,GAAG;IACxB,WAAW,EAAE,gBAAgB,EAAE,CAAA;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,0FAA0F;IAC1F,IAAI,CAAC,EAAE,eAAe,CAAA;IACtB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,MAAM,GAAG;IAAE,OAAO,EAAE,YAAY,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,EAAE,CAAA;CAAE,CAAA;AA2GhG,kFAAkF;AAClF,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAahD"}
@@ -0,0 +1,15 @@
1
+ import { type Bag } from './bag';
2
+ export type BagValidation = {
3
+ valid: boolean;
4
+ /** One line per problem, naming the file. Empty when `valid`. */
5
+ errors: string[];
6
+ };
7
+ /**
8
+ * Checks a bag the way a BagIt validator does: bagit.txt declares BagIt 1.0 (or 0.97) in UTF-8,
9
+ * there is at least one payload manifest, every payload file is listed in every payload manifest
10
+ * and matches it, every listed file exists, every tag manifest entry matches, and Payload-Oxum
11
+ * (when present) agrees with the payload. Only SHA-256 and SHA-512 manifests are checked; a
12
+ * manifest for another algorithm is reported as an error rather than skipped.
13
+ */
14
+ export declare function validateBag(bag: Bag): BagValidation;
15
+ //# sourceMappingURL=validateBag.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validateBag.d.ts","sourceRoot":"","sources":["../src/validateBag.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,GAAG,EAAgB,MAAM,OAAO,CAAA;AAGlE,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,OAAO,CAAA;IACd,iEAAiE;IACjE,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB,CAAA;AAoBD;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,aAAa,CA0EnD"}
package/dist/xml.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ /** `text` escaped for XML element content and double-quoted attribute values. */
2
+ export declare const escapeXml: (text: string) => string;
3
+ export type XmlAttributes = Record<string, string | undefined>;
4
+ /** An element with text content, or nothing when `text` is undefined. */
5
+ export declare const textElement: (indent: string, name: string, text: string | undefined, attrs?: XmlAttributes) => string;
6
+ /** An element wrapping already-rendered children, or nothing when they are empty. */
7
+ export declare const parentElement: (indent: string, name: string, children: string, attrs?: XmlAttributes) => string;
8
+ export declare const XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
9
+ //# sourceMappingURL=xml.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xml.d.ts","sourceRoot":"","sources":["../src/xml.ts"],"names":[],"mappings":"AAGA,iFAAiF;AACjF,eAAO,MAAM,SAAS,GAAI,MAAM,MAAM,KAAG,MAMb,CAAA;AAE5B,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;AAQ9D,yEAAyE;AACzE,eAAO,MAAM,WAAW,GAAI,QAAQ,MAAM,EAAE,MAAM,MAAM,EAAE,MAAM,MAAM,GAAG,SAAS,EAAE,QAAQ,aAAa,KAAG,MACV,CAAA;AAElG,qFAAqF;AACrF,eAAO,MAAM,aAAa,GAAI,QAAQ,MAAM,EAAE,MAAM,MAAM,EAAE,UAAU,MAAM,EAAE,QAAQ,aAAa,KAAG,MACH,CAAA;AAEnG,eAAO,MAAM,eAAe,iDAA6C,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,75 @@
1
1
  {
2
2
  "name": "@seedprotocol/archive",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.7.11-next.1384.g8b47246",
4
+ "description": "Standards-based archive packaging for Seed Protocol items: BagIt (RFC 8493) bags with SHA-256/SHA-512 manifests, Dublin Core and PREMIS metadata, and an h-entry archive document reader",
5
+ "keywords": [
6
+ "seed-protocol",
7
+ "archive",
8
+ "bagit",
9
+ "rfc8493",
10
+ "premis",
11
+ "dublin-core",
12
+ "h-entry",
13
+ "digital-preservation"
14
+ ],
15
+ "type": "module",
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "import": {
21
+ "types": "./dist/index.d.ts",
22
+ "default": "./dist/index.js"
23
+ },
24
+ "require": {
25
+ "types": "./dist/index.d.ts",
26
+ "default": "./dist/index.js"
27
+ }
28
+ },
29
+ "./node": {
30
+ "import": {
31
+ "types": "./dist/node/index.d.ts",
32
+ "default": "./dist/node.js"
33
+ },
34
+ "require": {
35
+ "types": "./dist/node/index.d.ts",
36
+ "default": "./dist/node.js"
37
+ }
38
+ },
39
+ "./package.json": "./package.json"
40
+ },
41
+ "files": [
42
+ "dist",
43
+ "CHANGELOG.md"
44
+ ],
45
+ "scripts": {
46
+ "build": "rimraf dist && tsc -p tsconfig.build.json && vite build",
47
+ "test": "vitest run --reporter=default",
48
+ "prepublishOnly": "bun run build && node ../../scripts/sync-versions.js"
49
+ },
50
+ "dependencies": {
51
+ "@noble/hashes": "^1.8.0",
52
+ "parse5": "8.0.1"
53
+ },
54
+ "devDependencies": {
55
+ "@types/node": "^24.10.1",
56
+ "rimraf": "^6.0.1",
57
+ "typescript": "~5.9.3",
58
+ "vite": "^8.0.0",
59
+ "vitest": "^4.1.11"
60
+ },
61
+ "author": "Keith Axline <keith@journodao.xyz>",
62
+ "license": "MIT",
63
+ "repository": {
64
+ "type": "git",
65
+ "url": "git+https://github.com/JournoLabs/seed-protocol-sdk.git",
66
+ "directory": "packages/archive"
67
+ },
68
+ "homepage": "https://github.com/JournoLabs/seed-protocol-sdk/tree/main/packages/archive#readme",
69
+ "bugs": {
70
+ "url": "https://github.com/JournoLabs/seed-protocol-sdk/issues"
71
+ },
72
+ "publishConfig": {
73
+ "access": "public"
74
+ }
75
+ }